From 9e6e7e3550ca0b8d02b8d706b94dddce094820e9 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:27:47 +0900 Subject: [PATCH] update message --- .../ensemble/takt-pack-config.test.ts | 9 +++++ src/commands/ensemble/add.ts | 15 ++++--- src/features/ensemble/takt-pack-config.ts | 40 +++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/__tests__/ensemble/takt-pack-config.test.ts b/src/__tests__/ensemble/takt-pack-config.test.ts index 4afe0a6..6bb90cd 100644 --- a/src/__tests__/ensemble/takt-pack-config.test.ts +++ b/src/__tests__/ensemble/takt-pack-config.test.ts @@ -19,6 +19,7 @@ import { validateMinVersion, isVersionCompatible, checkPackageHasContent, + checkPackageHasContentWithContext, validateRealpathInsideRoot, resolvePackConfigPath, } from '../../features/ensemble/takt-pack-config.js'; @@ -247,6 +248,14 @@ describe('checkPackageHasContent', () => { expect(() => checkPackageHasContent(tempDir)).toThrow(); }); + it('should include manifest/path/hint details in contextual error', () => { + const manifestPath = join(tempDir, '.takt', 'takt-package.yaml'); + expect(() => checkPackageHasContentWithContext(tempDir, { + manifestPath, + configuredPath: '.', + })).toThrow(/path: \.takt/); + }); + it('should not throw when only faceted/ exists', () => { // Given: package with faceted/ only mkdirSync(join(tempDir, 'faceted'), { recursive: true }); diff --git a/src/commands/ensemble/add.ts b/src/commands/ensemble/add.ts index bf8af2d..1eda434 100644 --- a/src/commands/ensemble/add.ts +++ b/src/commands/ensemble/add.ts @@ -10,6 +10,7 @@ import { mkdirSync, copyFileSync, existsSync, readFileSync, writeFileSync, rmSyn import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { execFileSync } from 'node:child_process'; +import { createRequire } from 'node:module'; import { stringify as stringifyYaml } from 'yaml'; import { getEnsemblePackageDir } from '../../infra/config/paths.js'; import { parseGithubSpec } from '../../features/ensemble/github-spec.js'; @@ -18,7 +19,7 @@ import { validateTaktPackPath, validateMinVersion, isVersionCompatible, - checkPackageHasContent, + checkPackageHasContentWithContext, validateRealpathInsideRoot, resolvePackConfigPath, } from '../../features/ensemble/takt-pack-config.js'; @@ -33,7 +34,7 @@ import { confirm } from '../../shared/prompt/index.js'; import { info, success } from '../../shared/ui/index.js'; import { createLogger, getErrorMessage } from '../../shared/utils/index.js'; -// eslint-disable-next-line @typescript-eslint/no-require-imports +const require = createRequire(import.meta.url); const { version: TAKT_VERSION } = require('../../../package.json') as { version: string }; const log = createLogger('ensemble-add'); @@ -63,16 +64,15 @@ export async function ensembleAddCommand(spec: string): Promise { mkdirSync(tmpExtractDir, { recursive: true }); info(`πŸ“¦ ${owner}/${repo} @${ref} をダウンロード中...`); - execFileSync( + const tarballBuffer = execFileSync( 'gh', [ 'api', `/repos/${owner}/${repo}/tarball/${ref}`, - '--header', 'Accept: application/octet-stream', - '--output', tmpTarPath, ], { stdio: ['inherit', 'pipe', 'pipe'] }, ); + writeFileSync(tmpTarPath, tarballBuffer); const tarVerboseList = execFileSync('tar', ['tvzf', tmpTarPath], { encoding: 'utf-8', @@ -112,7 +112,10 @@ export async function ensembleAddCommand(spec: string): Promise { validateRealpathInsideRoot(packageRoot, tmpExtractDir); - checkPackageHasContent(packageRoot); + checkPackageHasContentWithContext(packageRoot, { + manifestPath: packConfigPath, + configuredPath: config.path, + }); const targets = collectCopyTargets(packageRoot); const facetFiles = targets.filter(t => t.relativePath.startsWith('faceted/')); diff --git a/src/features/ensemble/takt-pack-config.ts b/src/features/ensemble/takt-pack-config.ts index cb488ce..fc2fa2c 100644 --- a/src/features/ensemble/takt-pack-config.ts +++ b/src/features/ensemble/takt-pack-config.ts @@ -23,6 +23,11 @@ export interface TaktPackConfig { }; } +interface PackageContentCheckContext { + manifestPath?: string; + configuredPath?: string; +} + const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/; /** @@ -113,6 +118,41 @@ export function checkPackageHasContent(packageRoot: string): void { } } +/** + * Check package content and include user-facing diagnostics when empty. + * + * Adds manifest/configured-path details and a practical hint for nested layouts + * (e.g. when actual content is under ".takt/" but path remains "."). + */ +export function checkPackageHasContentWithContext( + packageRoot: string, + context: PackageContentCheckContext, +): void { + const hasFaceted = existsSync(join(packageRoot, 'faceted')); + const hasPieces = existsSync(join(packageRoot, 'pieces')); + if (hasFaceted || hasPieces) return; + + const checkedFaceted = join(packageRoot, 'faceted'); + const checkedPieces = join(packageRoot, 'pieces'); + const configuredPath = context.configuredPath ?? '.'; + const manifestPath = context.manifestPath ?? '(unknown)'; + const hint = configuredPath === '.' + ? `hint: If your package content is under ".takt/", set "path: .takt" in ${TAKT_PACKAGE_MANIFEST_FILENAME}.` + : `hint: Verify "path: ${configuredPath}" points to a directory containing faceted/ or pieces/.`; + + throw new Error( + [ + 'Package content not found.', + `manifest: ${manifestPath}`, + `configured path: ${configuredPath}`, + `resolved package root: ${packageRoot}`, + `checked: ${checkedFaceted}`, + `checked: ${checkedPieces}`, + hint, + ].join('\n'), + ); +} + /** * Resolve the path to takt-package.yaml within an extracted tarball directory. *