@wneng/create-keel 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/schema/options.ts","../src/errors.ts","../src/cli.ts","../src/orchestrator.ts","../src/input/defaults.ts","../src/input/config.ts","../src/input/prompter.ts","../src/fragments/loader.ts","../src/schema/manifest.ts","../src/fragments/resolver.ts","../src/render/context.ts","../src/plan/builder.ts","../src/render/renderer.ts","../src/plan/syntax.ts","../src/schema/metadata.ts","../src/plan/metadata.ts","../src/writer/writer.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["/**\r\n * The Scaffolder's own SemVer. Kept in a dedicated module so that the CLI,\r\n * orchestrator, and metadata writer can import a single source of truth\r\n * without creating a dependency cycle through index.ts.\r\n */\r\nexport const SCAFFOLDER_VERSION = '0.1.0';\r\n","/**\r\n * Options schema and (de)serialization for the Scaffolder.\r\n *\r\n * Single source of truth for:\r\n * - Interactive prompt value constraints\r\n * - --config file validation\r\n * - Internal TypeScript types\r\n *\r\n * Round-trip invariant (Property 4): parseOptions(serializeOptions(o)) deepEquals o.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport addFormats from 'ajv-formats';\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Value domains\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const BACKEND_LANGUAGES = ['node', 'python', 'go', 'java', 'none'] as const;\r\nexport type BackendLanguage = (typeof BACKEND_LANGUAGES)[number];\r\n\r\nexport const FRONTEND_FRAMEWORKS = ['react', 'vue', 'none'] as const;\r\nexport type FrontendFramework = (typeof FRONTEND_FRAMEWORKS)[number];\r\n\r\nexport const AGENT_KINDS = ['rust-desktop', 'none'] as const;\r\nexport type AgentKind = (typeof AGENT_KINDS)[number];\r\n\r\nexport const MOBILE_KINDS = ['react-native', 'flutter', 'none'] as const;\r\nexport type MobileKind = (typeof MOBILE_KINDS)[number];\r\n\r\nexport const MINIAPP_KINDS = ['wechat', 'none'] as const;\r\nexport type MiniappKind = (typeof MINIAPP_KINDS)[number];\r\n\r\nexport const DEPLOY_TARGETS = ['docker-compose', 'kubernetes', 'bare-metal', 'none'] as const;\r\nexport type DeployTarget = (typeof DEPLOY_TARGETS)[number];\r\n\r\nexport const CONTRACT_KINDS = ['rest', 'rest+events', 'events-only', 'none'] as const;\r\nexport type ContractKind = (typeof CONTRACT_KINDS)[number];\r\n\r\nexport const AI_TOOLS = ['kiro', 'cursor', 'claude-code', 'codex', 'none'] as const;\r\nexport type AiTool = (typeof AI_TOOLS)[number];\r\n\r\nexport const LICENSE_KINDS = ['mit', 'apache-2.0', 'proprietary'] as const;\r\nexport type LicenseKind = (typeof LICENSE_KINDS)[number];\r\n\r\nexport const CI_PLATFORMS = ['gitee', 'github'] as const;\r\nexport type CiPlatform = (typeof CI_PLATFORMS)[number];\r\n\r\nexport const ROLE_KINDS = ['design', 'qa', 'field', 'data', 'legal-security', 'marketing'] as const;\r\nexport type Role = (typeof ROLE_KINDS)[number];\r\n\r\nexport const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;\r\nexport const PROJECT_NAME_PATTERN_SOURCE = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Options type\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface Options {\r\n readonly projectName: string;\r\n readonly backend: BackendLanguage;\r\n readonly frontend: FrontendFramework;\r\n readonly mobile: MobileKind;\r\n readonly miniapp: MiniappKind;\r\n readonly agent: AgentKind;\r\n readonly deploy: DeployTarget;\r\n readonly contract: ContractKind;\r\n readonly ai: AiTool;\r\n readonly license: LicenseKind;\r\n readonly gitLfs: boolean;\r\n readonly integrations: boolean;\r\n readonly ci: CiPlatform;\r\n readonly roles: readonly Role[];\r\n}\r\n\r\n/**\r\n * Stable field order for serialization; round-trip relies on this being\r\n * consistent with JSON Schema's additionalProperties: false.\r\n */\r\nexport const OPTIONS_FIELD_ORDER: readonly (keyof Options)[] = [\r\n 'projectName',\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'roles',\r\n];\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema (Draft-07)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const OPTIONS_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/options.json',\r\n title: 'ScaffolderOptions',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: [\r\n 'projectName',\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'roles',\r\n ],\r\n properties: {\r\n projectName: {\r\n type: 'string',\r\n pattern: PROJECT_NAME_PATTERN_SOURCE,\r\n },\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n roles: {\r\n type: 'array',\r\n items: { type: 'string', enum: [...ROLE_KINDS] },\r\n uniqueItems: true,\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\naddFormats(ajv);\r\nconst validateFn = ajv.compile<Options>(OPTIONS_SCHEMA);\r\n\r\nexport interface OptionsValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class OptionsValidationError extends Error {\r\n readonly issues: readonly OptionsValidationIssue[];\r\n\r\n constructor(issues: readonly OptionsValidationIssue[]) {\r\n super(\r\n `Invalid Scaffolder options:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'OptionsValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): OptionsValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path: e.instancePath || (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\n/**\r\n * Assert that an unknown value is a valid Options, throwing otherwise.\r\n * Returns the same value narrowed to Options on success.\r\n */\r\nexport function assertOptions(value: unknown): Options {\r\n if (!validateFn(value)) {\r\n throw new OptionsValidationError(collectIssues());\r\n }\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parse / serialize (round-trip)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a JSON or YAML string into Options.\r\n * Accepts either format; detection is by trying JSON first, then YAML.\r\n */\r\nexport function parseOptions(input: string): Options {\r\n let raw: unknown;\r\n const trimmed = input.trim();\r\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\r\n try {\r\n raw = JSON.parse(trimmed);\r\n } catch (e) {\r\n throw new OptionsValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n } else {\r\n try {\r\n raw = parseYaml(input);\r\n } catch (e) {\r\n throw new OptionsValidationError([\r\n { path: '', message: `invalid YAML: ${(e as Error).message}` },\r\n ]);\r\n }\r\n }\r\n return assertOptions(raw);\r\n}\r\n\r\n/**\r\n * Serialize Options to a canonical JSON string.\r\n * Field order is fixed by OPTIONS_FIELD_ORDER to guarantee byte-stable output.\r\n */\r\nexport function serializeOptions(options: Options): string {\r\n const ordered: Record<string, unknown> = {};\r\n for (const key of OPTIONS_FIELD_ORDER) {\r\n ordered[key] = options[key];\r\n }\r\n return JSON.stringify(ordered, null, 2);\r\n}\r\n\r\n/**\r\n * Return the default Options (projectName must still be supplied by the caller).\r\n */\r\nexport function defaultOptions(projectName: string): Options {\r\n return {\r\n projectName,\r\n backend: 'node',\r\n frontend: 'react',\r\n mobile: 'none',\r\n miniapp: 'none',\r\n agent: 'none',\r\n deploy: 'docker-compose',\r\n contract: 'rest',\r\n ai: 'kiro',\r\n license: 'mit',\r\n gitLfs: false,\r\n integrations: false,\r\n ci: 'gitee',\r\n roles: [],\r\n };\r\n}\r\n","/**\r\n * Error taxonomy and exit code mapping for the Scaffolder.\r\n *\r\n * The three categories below partition every failure path into exactly one\r\n * exit code, keeping behavior stable for downstream automation.\r\n * - UserInputError → exit code 1\r\n * - FilesystemError → exit code 2\r\n * - TemplateError → exit code 3\r\n */\r\n\r\nexport type ExitCode = 0 | 1 | 2 | 3;\r\n\r\nexport interface ThreeLinePayload {\r\n readonly title: string;\r\n readonly message: string;\r\n readonly suggestion: string;\r\n}\r\n\r\nexport abstract class ScaffolderError extends Error {\r\n abstract readonly exitCode: ExitCode;\r\n abstract readonly title: string;\r\n readonly suggestion: string;\r\n\r\n constructor(message: string, suggestion: string) {\r\n super(message);\r\n this.name = new.target.name;\r\n this.suggestion = suggestion;\r\n }\r\n\r\n toThreeLine(): ThreeLinePayload {\r\n return { title: this.title, message: this.message, suggestion: this.suggestion };\r\n }\r\n}\r\n\r\nexport class UserInputError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 1;\r\n readonly title = 'UserInputError';\r\n}\r\n\r\nexport class FilesystemError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 2;\r\n readonly title = 'FilesystemError';\r\n}\r\n\r\nexport class TemplateError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 3;\r\n readonly title = 'TemplateError';\r\n}\r\n\r\n/**\r\n * Format a ScaffolderError as the canonical three-line terminal output\r\n * (§15.5). Non-Scaffolder errors fall through to a single line.\r\n */\r\nexport function formatError(err: unknown): string {\r\n if (err instanceof ScaffolderError) {\r\n const { title, message, suggestion } = err.toThreeLine();\r\n return `✗ ${title}\\n ${message}\\n 建议:${suggestion}`;\r\n }\r\n if (err instanceof Error) {\r\n return `✗ UnexpectedError\\n ${err.message}\\n 建议:请在 issue 中附上完整堆栈`;\r\n }\r\n return `✗ UnexpectedError\\n ${String(err)}\\n 建议:请在 issue 中附上完整上下文`;\r\n}\r\n","/**\r\n * CLI entry routing.\r\n *\r\n * A hand-rolled parser is used rather than cac's built-in --version / --help\r\n * handling, because cac writes those outputs directly to stdout and swallows\r\n * the opportunity to route them through our test-friendly CliIO sink.\r\n *\r\n * This module owns:\r\n * - subcommand dispatch (`create`; `--help` / `--version` short-circuit)\r\n * - global flag definitions\r\n * - projectName pattern validation (R1.5)\r\n * - mapping ScaffolderError → exit code\r\n */\r\n\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\nimport { PROJECT_NAME_PATTERN, ROLE_KINDS, type Role } from './schema/options.js';\r\nimport { ScaffolderError, UserInputError, formatError, type ExitCode } from './errors.js';\r\n\r\nexport interface CliFlags {\r\n readonly yes: boolean;\r\n readonly force: boolean;\r\n readonly dryRun: boolean;\r\n readonly quiet: boolean;\r\n readonly verbose: boolean;\r\n readonly config?: string;\r\n readonly ci?: 'gitee' | 'github';\r\n readonly roles?: readonly Role[];\r\n}\r\n\r\nexport interface CreateCommandInput {\r\n readonly projectName: string;\r\n readonly flags: CliFlags;\r\n}\r\n\r\nexport type CreateHandler = (input: CreateCommandInput) => Promise<void>;\r\n\r\nexport interface CliIO {\r\n readonly argv: readonly string[];\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n readonly onCreate: CreateHandler;\r\n}\r\n\r\n/**\r\n * Parse and dispatch. Returns the exit code the process should terminate with.\r\n * Never calls process.exit directly; the concrete binary is responsible for\r\n * that so tests can run `run()` repeatedly in-process.\r\n *\r\n * `argv` is expected to follow the Node convention: `[node, script, ...args]`.\r\n */\r\nexport async function run(io: CliIO): Promise<ExitCode> {\r\n const args = io.argv.slice(2);\r\n\r\n // Short-circuit for help / version before any subcommand validation, so\r\n // that `--help` alone always succeeds.\r\n if (args.some((a) => a === '-h' || a === '--help')) {\r\n io.stdout(helpText());\r\n return 0;\r\n }\r\n if (args.some((a) => a === '-v' || a === '--version')) {\r\n io.stdout(SCAFFOLDER_VERSION);\r\n return 0;\r\n }\r\n\r\n if (args.length === 0) {\r\n io.stdout(helpText());\r\n return 0;\r\n }\r\n\r\n const [command, ...rest] = args;\r\n if (command !== 'create') {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `unknown command: ${JSON.stringify(command)}`,\r\n '可用的命令:create。查看帮助:--help',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n try {\r\n const { projectName, flags } = parseCreateArgs(rest);\r\n await io.onCreate({ projectName, flags });\r\n return 0;\r\n } catch (err) {\r\n io.stderr(formatError(err));\r\n if (err instanceof ScaffolderError) {\r\n return err.exitCode;\r\n }\r\n return 1;\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parsing\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedCreateArgs {\r\n projectName: string;\r\n flags: CliFlags;\r\n}\r\n\r\nfunction parseCreateArgs(args: readonly string[]): ParsedCreateArgs {\r\n let projectName: string | undefined;\r\n let yes = false;\r\n let force = false;\r\n let dryRun = false;\r\n let quiet = false;\r\n let verbose = false;\r\n let config: string | undefined;\r\n let ci: 'gitee' | 'github' | undefined;\r\n let roles: readonly Role[] | undefined;\r\n\r\n for (let i = 0; i < args.length; i += 1) {\r\n const a = args[i]!;\r\n switch (a) {\r\n case '--yes':\r\n yes = true;\r\n break;\r\n case '--force':\r\n force = true;\r\n break;\r\n case '--dry-run':\r\n dryRun = true;\r\n break;\r\n case '--quiet':\r\n quiet = true;\r\n break;\r\n case '--verbose':\r\n verbose = true;\r\n break;\r\n case '--config': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--config requires a path argument',\r\n '示例:--config ./scaffolder.yaml',\r\n );\r\n }\r\n config = next;\r\n i += 1;\r\n break;\r\n }\r\n case '--ci': {\r\n const next = args[i + 1];\r\n if (next !== 'gitee' && next !== 'github') {\r\n throw new UserInputError(\r\n `invalid --ci value: ${JSON.stringify(next ?? '')}`,\r\n '--ci 只接受 gitee 或 github',\r\n );\r\n }\r\n ci = next;\r\n i += 1;\r\n break;\r\n }\r\n case '--roles': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--roles requires a comma-separated list',\r\n '示例:--roles qa,field,marketing;可选值:' + ROLE_KINDS.join(','),\r\n );\r\n }\r\n const parts = next\r\n .split(',')\r\n .map((s) => s.trim())\r\n .filter((s) => s.length > 0);\r\n const seen = new Set<string>();\r\n for (const part of parts) {\r\n if (!(ROLE_KINDS as readonly string[]).includes(part)) {\r\n throw new UserInputError(\r\n `invalid --roles value: ${JSON.stringify(part)}`,\r\n '可选角色:' + ROLE_KINDS.join(','),\r\n );\r\n }\r\n if (seen.has(part)) {\r\n throw new UserInputError(\r\n `duplicate role: ${part}`,\r\n '--roles 中每个角色仅能出现一次',\r\n );\r\n }\r\n seen.add(part);\r\n }\r\n roles = parts as readonly Role[];\r\n i += 1;\r\n break;\r\n }\r\n default: {\r\n if (a.startsWith('--')) {\r\n throw new UserInputError(\r\n `unknown option: ${a}`,\r\n '查看可用选项:create-keel --help',\r\n );\r\n }\r\n if (projectName === undefined) {\r\n projectName = a;\r\n } else {\r\n throw new UserInputError(\r\n `unexpected extra argument: ${JSON.stringify(a)}`,\r\n '每次只能创建一个项目',\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (projectName === undefined) {\r\n // In task 4 the orchestrator will replace this with an interactive\r\n // prompt. For now treat the missing name as a user error so the CLI\r\n // layer stays self-contained.\r\n throw new UserInputError(\r\n 'missing <project-name>',\r\n '请提供项目名,例如:npx @wneng/create-keel create my-app',\r\n );\r\n }\r\n if (!PROJECT_NAME_PATTERN.test(projectName)) {\r\n throw new UserInputError(\r\n `invalid project name: ${JSON.stringify(projectName)}`,\r\n '项目名应匹配 ^[a-z0-9][a-z0-9-]{0,62}$,例如:my-app',\r\n );\r\n }\r\n\r\n const flags: CliFlags = {\r\n yes,\r\n force,\r\n dryRun,\r\n quiet,\r\n verbose,\r\n ...(config !== undefined ? { config } : {}),\r\n ...(ci !== undefined ? { ci } : {}),\r\n ...(roles !== undefined ? { roles } : {}),\r\n };\r\n\r\n return { projectName, flags };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Help\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction helpText(): string {\r\n return [\r\n 'create-keel — scaffold a Contract-First project',\r\n '',\r\n 'USAGE',\r\n ' npx @wneng/create-keel create <project-name> [options]',\r\n '',\r\n 'OPTIONS',\r\n ' --yes Skip interactive prompts and use defaults',\r\n ' --force Overwrite a non-empty target directory after confirmation',\r\n ' --dry-run Compute the plan without writing any files',\r\n ' --quiet Only emit errors and the final summary',\r\n ' --verbose Emit per-file render/write logs',\r\n ' --config <path> Read options from a JSON or YAML file',\r\n ' --ci <platform> Which CI platform to scaffold (gitee|github, default: gitee)',\r\n ' --roles <list> Comma-separated role directories to scaffold',\r\n ' (' + ROLE_KINDS.join('|') + '; default: none)',\r\n ' -h, --help Show this help',\r\n ' -v, --version Show the scaffolder version',\r\n '',\r\n 'See .kiro/specs/project-scaffolder/ for the full specification.',\r\n ].join('\\n');\r\n}\r\n","/**\r\n * Orchestrator — drives the full create flow end-to-end:\r\n *\r\n * CLI args\r\n * ↓\r\n * Defaults + ConfigFile + CLI flags + Prompter\r\n * ↓ (merged with precedence: CLI > config > interactive > defaults)\r\n * Validated Options\r\n * ↓\r\n * Fragment loader + resolver\r\n * ↓\r\n * Template renderer → File plan builder\r\n * ↓\r\n * Syntax validator\r\n * ↓\r\n * Writer (with rollback) or DryRun reporter\r\n * ↓\r\n * .scaffolder.json metadata (always as part of the same atomic write)\r\n */\r\n\r\nimport * as path from 'node:path';\r\n\r\nimport { UserInputError } from './errors.js';\r\nimport type { CliFlags } from './cli.js';\r\nimport { buildDefaultOptions } from './input/defaults.js';\r\nimport { loadConfigFile, type PartialOptions } from './input/config.js';\r\nimport {\r\n InquirerPrompter,\r\n promptForOptions,\r\n type Prompter,\r\n} from './input/prompter.js';\r\nimport { assertOptions, type Options } from './schema/options.js';\r\nimport { loadFragments } from './fragments/loader.js';\r\nimport { selectFragments } from './fragments/resolver.js';\r\nimport { buildRenderContext } from './render/context.js';\r\nimport { buildFilePlan } from './plan/builder.js';\r\nimport { validatePlanSyntax } from './plan/syntax.js';\r\nimport { appendScaffolderMetadata } from './plan/metadata.js';\r\nimport { writePlan } from './writer/writer.js';\r\nimport { Reporter, verbosityFromFlags } from './reporter.js';\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\nimport type { TemplateFragmentRef } from './schema/metadata.js';\r\n\r\nexport interface OrchestratorIO {\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n readonly isTTY: boolean;\r\n /** Injected for tests; production uses InquirerPrompter. */\r\n readonly prompter?: Prompter;\r\n /** Directory to write into; defaults to `./<projectName>` under cwd. */\r\n readonly cwd?: string;\r\n /** Templates root override (for tests). */\r\n readonly templatesRoot?: string;\r\n /** Frozen clock, for deterministic tests. */\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface OrchestratorInput {\r\n readonly projectName: string;\r\n readonly flags: CliFlags;\r\n readonly io: OrchestratorIO;\r\n}\r\n\r\nexport interface OrchestratorResult {\r\n readonly options: Options;\r\n readonly targetDirectory: string;\r\n readonly fileCount: number;\r\n readonly dryRun: boolean;\r\n}\r\n\r\nexport async function runCreate(input: OrchestratorInput): Promise<OrchestratorResult> {\r\n const reporter = new Reporter(\r\n { stdout: input.io.stdout, stderr: input.io.stderr },\r\n verbosityFromFlags(input.flags),\r\n );\r\n\r\n reporter.stage('options');\r\n const options = await resolveOptions(input);\r\n reporter.options(options);\r\n\r\n reporter.stage('templates');\r\n const { fragments } = await loadFragments(input.io.templatesRoot);\r\n const selected = selectFragments(options, fragments);\r\n reporter.info(`selected ${selected.length} fragment(s)`);\r\n for (const f of selected) {\r\n reporter.verbose(` ${f.manifest.name}@${f.manifest.version} (priority ${f.manifest.priority})`);\r\n }\r\n\r\n reporter.stage('plan');\r\n const context = buildRenderContext({\r\n options,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n ...(input.io.now !== undefined ? { now: input.io.now } : {}),\r\n });\r\n let plan = await buildFilePlan({ fragments: selected, context });\r\n const fragmentRefs: TemplateFragmentRef[] = selected.map((f) => ({\r\n name: f.manifest.name,\r\n version: f.manifest.version,\r\n }));\r\n plan = appendScaffolderMetadata({\r\n plan,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n options,\r\n templateFragments: fragmentRefs,\r\n ...(input.io.now !== undefined ? { now: input.io.now } : {}),\r\n });\r\n validatePlanSyntax(plan);\r\n reporter.info(`planned ${plan.files.length} file(s) across ${plan.directories.length} directory`);\r\n\r\n const cwd = input.io.cwd ?? process.cwd();\r\n const targetDirectory = path.resolve(cwd, options.projectName);\r\n\r\n if (input.flags.dryRun) {\r\n reporter.stage('dry-run');\r\n reporter.dryRunReport(plan);\r\n reporter.summary('dry-run', plan.files.length, targetDirectory);\r\n return { options, targetDirectory, fileCount: plan.files.length, dryRun: true };\r\n }\r\n\r\n reporter.stage('write');\r\n const result = await writePlan(plan, { targetDirectory, force: input.flags.force ?? false });\r\n for (const f of plan.files) {\r\n reporter.writeProgress(f);\r\n }\r\n reporter.info(`wrote ${result.createdFiles.length} file(s)`);\r\n reporter.summary('created', result.createdFiles.length, targetDirectory);\r\n\r\n return {\r\n options,\r\n targetDirectory,\r\n fileCount: result.createdFiles.length,\r\n dryRun: false,\r\n };\r\n}\r\n\r\n/**\r\n * Build the final Options by merging sources in order of precedence:\r\n * CLI flags > --config file > interactive prompt > defaults\r\n */\r\nexport async function resolveOptions(input: OrchestratorInput): Promise<Options> {\r\n const { projectName, flags, io } = input;\r\n\r\n let merged: PartialOptions = {};\r\n if (flags.config !== undefined) {\r\n merged = { ...merged, ...(await loadConfigFile(flags.config)) };\r\n }\r\n\r\n merged = {\r\n ...merged,\r\n projectName,\r\n ...(flags.ci !== undefined ? { ci: flags.ci } : {}),\r\n ...(flags.roles !== undefined ? { roles: flags.roles } : {}),\r\n };\r\n\r\n if (flags.yes) {\r\n const full = { ...buildDefaultOptions(projectName), ...merged };\r\n return assertOptions(full);\r\n }\r\n\r\n if (!io.isTTY) {\r\n throw new UserInputError(\r\n 'no TTY available and neither --yes nor a complete --config was provided',\r\n '在非交互环境下请使用 --yes 或提供完整的 --config',\r\n );\r\n }\r\n\r\n const prompter = io.prompter ?? new InquirerPrompter();\r\n const prompted = await promptForOptions({\r\n prompter,\r\n prefilled: merged,\r\n isTTY: io.isTTY,\r\n report: (axis, value, usedDefault) => {\r\n if (!flags.quiet && usedDefault) {\r\n io.stdout(` ${axis}: ${value} (default)`);\r\n } else if (flags.verbose) {\r\n io.stdout(` ${axis}: ${value}`);\r\n }\r\n },\r\n });\r\n\r\n return assertOptions(prompted);\r\n}\r\n","/**\r\n * DefaultsProvider — single, externally-readable source of truth for every\r\n * Option Axis default. Kept separate from `schema/options.ts` because that\r\n * file owns the *value domains* while this file owns the *policy choices*.\r\n *\r\n * These defaults drive:\r\n * - Interactive prompt preselection\r\n * - --yes behavior (pick defaults for every axis)\r\n * - Fallback for any axis missing from a --config file\r\n */\r\n\r\nimport type {\r\n AgentKind,\r\n AiTool,\r\n BackendLanguage,\r\n CiPlatform,\r\n ContractKind,\r\n DeployTarget,\r\n FrontendFramework,\r\n LicenseKind,\r\n MiniappKind,\r\n MobileKind,\r\n Options,\r\n Role,\r\n} from '../schema/options.js';\r\n\r\nexport interface OptionDefaults {\r\n readonly backend: BackendLanguage;\r\n readonly frontend: FrontendFramework;\r\n readonly mobile: MobileKind;\r\n readonly miniapp: MiniappKind;\r\n readonly agent: AgentKind;\r\n readonly deploy: DeployTarget;\r\n readonly contract: ContractKind;\r\n readonly ai: AiTool;\r\n readonly license: LicenseKind;\r\n readonly gitLfs: boolean;\r\n readonly integrations: boolean;\r\n readonly ci: CiPlatform;\r\n readonly roles: readonly Role[];\r\n}\r\n\r\n/**\r\n * Default values for every Option Axis except `projectName`, which has no\r\n * reasonable default and must always be supplied explicitly.\r\n */\r\nexport const OPTION_DEFAULTS: OptionDefaults = {\r\n backend: 'node',\r\n frontend: 'react',\r\n mobile: 'none',\r\n miniapp: 'none',\r\n agent: 'none',\r\n deploy: 'docker-compose',\r\n contract: 'rest',\r\n ai: 'kiro',\r\n license: 'mit',\r\n gitLfs: false,\r\n integrations: false,\r\n ci: 'gitee',\r\n roles: [],\r\n};\r\n\r\n/**\r\n * Build a complete Options object from defaults given a projectName.\r\n * Callers use this for `--yes` and as the fallback during prompt\r\n * cancellation.\r\n */\r\nexport function buildDefaultOptions(projectName: string): Options {\r\n return { projectName, ...OPTION_DEFAULTS };\r\n}\r\n","/**\r\n * ConfigFileLoader — load a Partial<Options> from a JSON or YAML file.\r\n *\r\n * The loader is deliberately forgiving about *what fields are present* (any\r\n * subset is OK, missing fields fall back to defaults) but strict about *the\r\n * values that are present* (each must pass the corresponding value-domain\r\n * check from OptionsSchema). This mirrors Requirement 3.5.\r\n */\r\n\r\nimport Ajv, { type ValidateFunction } from 'ajv';\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n PROJECT_NAME_PATTERN_SOURCE,\r\n ROLE_KINDS,\r\n type Options,\r\n} from '../schema/options.js';\r\n\r\nexport type PartialOptions = Partial<Options>;\r\n\r\nconst PARTIAL_OPTIONS_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/partial-options.json',\r\n title: 'PartialScaffolderOptions',\r\n type: 'object',\r\n additionalProperties: false,\r\n properties: {\r\n projectName: { type: 'string', pattern: PROJECT_NAME_PATTERN_SOURCE },\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n roles: {\r\n type: 'array',\r\n items: { type: 'string', enum: [...ROLE_KINDS] },\r\n uniqueItems: true,\r\n },\r\n },\r\n} as const;\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\nconst validate: ValidateFunction<PartialOptions> = ajv.compile<PartialOptions>(\r\n PARTIAL_OPTIONS_SCHEMA,\r\n);\r\n\r\nfunction formatIssues(): string {\r\n const errs = validate.errors ?? [];\r\n return errs\r\n .map((e) => {\r\n const path =\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : '<root>');\r\n return `${path}: ${e.message ?? 'validation error'}`;\r\n })\r\n .join('; ');\r\n}\r\n\r\n/**\r\n * Load and validate a config file. Format is decided by extension:\r\n * .json → JSON\r\n * .yaml | .yml → YAML\r\n * Anything else is rejected as a UserInputError.\r\n *\r\n * Throws UserInputError on any problem (missing file, bad extension, parse\r\n * error, schema violation). Exit code 1 per §15.2.\r\n */\r\nexport async function loadConfigFile(filePath: string): Promise<PartialOptions> {\r\n const ext = path.extname(filePath).toLowerCase();\r\n const isJson = ext === '.json';\r\n const isYaml = ext === '.yaml' || ext === '.yml';\r\n if (!isJson && !isYaml) {\r\n throw new UserInputError(\r\n `unsupported config extension: ${ext || '<none>'}`,\r\n '请使用 .json 或 .yaml / .yml 文件',\r\n );\r\n }\r\n\r\n let text: string;\r\n try {\r\n text = await fs.readFile(filePath, 'utf8');\r\n } catch (e) {\r\n throw new UserInputError(\r\n `failed to read config file: ${(e as Error).message}`,\r\n '请确认文件路径存在且可读',\r\n );\r\n }\r\n\r\n let raw: unknown;\r\n try {\r\n raw = isJson ? JSON.parse(text) : parseYaml(text);\r\n } catch (e) {\r\n throw new UserInputError(\r\n `invalid ${isJson ? 'JSON' : 'YAML'} in config: ${(e as Error).message}`,\r\n '请检查文件内容是否为合法的 JSON / YAML',\r\n );\r\n }\r\n\r\n if (raw === null || raw === undefined) {\r\n return {};\r\n }\r\n if (typeof raw !== 'object' || Array.isArray(raw)) {\r\n throw new UserInputError(\r\n 'config root must be an object',\r\n '示例:{ \"backend\": \"node\", \"frontend\": \"react\" }',\r\n );\r\n }\r\n\r\n if (!validate(raw)) {\r\n throw new UserInputError(\r\n `config file does not match schema: ${formatIssues()}`,\r\n '参考 defaults 中的字段名与取值域',\r\n );\r\n }\r\n\r\n // Strip prototype to guarantee plain data and drop undefined-valued keys.\r\n const out: Record<string, unknown> = {};\r\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\r\n if (v !== undefined) out[k] = v;\r\n }\r\n return out as PartialOptions;\r\n}\r\n\r\n/**\r\n * Parse config from an in-memory string. Convenience for tests and for\r\n * callers that already hold the file content.\r\n */\r\nexport function parseConfigText(text: string, format: 'json' | 'yaml'): PartialOptions {\r\n let raw: unknown;\r\n try {\r\n raw = format === 'json' ? JSON.parse(text) : parseYaml(text);\r\n } catch (e) {\r\n throw new UserInputError(\r\n `invalid ${format.toUpperCase()} in config: ${(e as Error).message}`,\r\n '请检查内容语法',\r\n );\r\n }\r\n if (raw === null || raw === undefined) return {};\r\n if (typeof raw !== 'object' || Array.isArray(raw)) {\r\n throw new UserInputError(\r\n 'config root must be an object',\r\n '示例:{ \"backend\": \"node\" }',\r\n );\r\n }\r\n if (!validate(raw)) {\r\n throw new UserInputError(\r\n `config does not match schema: ${formatIssues()}`,\r\n '参考 defaults 中的字段名与取值域',\r\n );\r\n }\r\n return raw as PartialOptions;\r\n}\r\n","/**\r\n * InteractivePrompter — collect missing option values from the user.\r\n *\r\n * The prompter is abstracted behind a narrow interface so tests can drive\r\n * the flow deterministically. The concrete `InquirerPrompter` delegates to\r\n * `@inquirer/prompts` at runtime.\r\n *\r\n * Design decisions:\r\n * - Each Option Axis maps to exactly one select or confirm; the order matches\r\n * design §Components.Input Sources (projectName → backend → frontend →\r\n * deploy → contract → ai → license → gitLfs → ci).\r\n * - Only axes missing from the incoming PartialOptions are prompted; already\r\n * provided values are accepted as-is (with schema already having validated\r\n * them upstream).\r\n * - If stdout is not a TTY and no --yes / --config was supplied, the caller\r\n * (orchestrator) must short-circuit before reaching the prompter. The\r\n * prompter itself throws UserInputError if invoked in that state as a\r\n * belt-and-braces check.\r\n */\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n PROJECT_NAME_PATTERN,\r\n type AgentKind,\r\n type AiTool,\r\n type BackendLanguage,\r\n type CiPlatform,\r\n type ContractKind,\r\n type DeployTarget,\r\n type FrontendFramework,\r\n type LicenseKind,\r\n type MiniappKind,\r\n type MobileKind,\r\n type Options,\r\n type Role,\r\n} from '../schema/options.js';\r\nimport { OPTION_DEFAULTS } from './defaults.js';\r\nimport type { PartialOptions } from './config.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Prompter interface\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface SelectChoice<T extends string> {\r\n readonly value: T;\r\n readonly name: string;\r\n}\r\n\r\nexport interface Prompter {\r\n input(question: string, validate: (value: string) => true | string): Promise<string>;\r\n select<T extends string>(question: string, choices: readonly SelectChoice<T>[], def: T): Promise<T>;\r\n confirm(question: string, def: boolean): Promise<boolean>;\r\n checkbox<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: readonly T[],\r\n ): Promise<readonly T[]>;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Inquirer-backed implementation\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class InquirerPrompter implements Prompter {\r\n async input(question: string, validate: (value: string) => true | string): Promise<string> {\r\n const { input } = await import('@inquirer/prompts');\r\n return input({ message: question, validate });\r\n }\r\n\r\n async select<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: T,\r\n ): Promise<T> {\r\n const { select } = await import('@inquirer/prompts');\r\n return (await select({\r\n message: question,\r\n choices: choices.map((c) => ({ name: c.name, value: c.value })),\r\n default: def,\r\n })) as T;\r\n }\r\n\r\n async confirm(question: string, def: boolean): Promise<boolean> {\r\n const { confirm } = await import('@inquirer/prompts');\r\n return confirm({ message: question, default: def });\r\n }\r\n\r\n async checkbox<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: readonly T[],\r\n ): Promise<readonly T[]> {\r\n const { checkbox } = await import('@inquirer/prompts');\r\n const defSet = new Set(def);\r\n return (await checkbox({\r\n message: question,\r\n choices: choices.map((c) => ({\r\n name: c.name,\r\n value: c.value,\r\n checked: defSet.has(c.value),\r\n })),\r\n })) as readonly T[];\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Orchestrating prompt sequence\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface PromptOptionsInput {\r\n readonly prompter: Prompter;\r\n readonly prefilled: PartialOptions;\r\n readonly isTTY: boolean;\r\n readonly report?: ((axis: string, value: string, usedDefault: boolean) => void) | undefined;\r\n}\r\n\r\nfunction stringChoices<T extends string>(values: readonly T[]): SelectChoice<T>[] {\r\n return values.map((v) => ({ value: v, name: v }));\r\n}\r\n\r\nexport async function promptForOptions(input: PromptOptionsInput): Promise<Options> {\r\n if (!input.isTTY) {\r\n throw new UserInputError(\r\n 'interactive prompts require a TTY',\r\n '请在交互终端中运行,或提供 --yes / --config',\r\n );\r\n }\r\n\r\n const p = input.prompter;\r\n const pre = input.prefilled;\r\n const report = input.report ?? (() => {});\r\n\r\n const projectName = pre.projectName ?? (await p.input('项目名', (v) => {\r\n if (!PROJECT_NAME_PATTERN.test(v)) {\r\n return '项目名应匹配 ^[a-z0-9][a-z0-9-]{0,62}$';\r\n }\r\n return true;\r\n }));\r\n report('projectName', projectName, false);\r\n\r\n const backend = await chooseAxis<BackendLanguage>(\r\n p,\r\n pre.backend,\r\n '后端语言',\r\n BACKEND_LANGUAGES,\r\n OPTION_DEFAULTS.backend,\r\n report,\r\n 'backend',\r\n );\r\n\r\n const frontend = await chooseAxis<FrontendFramework>(\r\n p,\r\n pre.frontend,\r\n '前端框架',\r\n FRONTEND_FRAMEWORKS,\r\n OPTION_DEFAULTS.frontend,\r\n report,\r\n 'frontend',\r\n );\r\n\r\n const mobile = await chooseAxis<MobileKind>(\r\n p,\r\n pre.mobile,\r\n '移动端(手机原生)',\r\n MOBILE_KINDS,\r\n OPTION_DEFAULTS.mobile,\r\n report,\r\n 'mobile',\r\n );\r\n\r\n const miniapp = await chooseAxis<MiniappKind>(\r\n p,\r\n pre.miniapp,\r\n '小程序',\r\n MINIAPP_KINDS,\r\n OPTION_DEFAULTS.miniapp,\r\n report,\r\n 'miniapp',\r\n );\r\n\r\n const agent = await chooseAxis<AgentKind>(\r\n p,\r\n pre.agent,\r\n '终端/桌面客户端',\r\n AGENT_KINDS,\r\n OPTION_DEFAULTS.agent,\r\n report,\r\n 'agent',\r\n );\r\n\r\n const deploy = await chooseAxis<DeployTarget>(\r\n p,\r\n pre.deploy,\r\n '部署目标',\r\n DEPLOY_TARGETS,\r\n OPTION_DEFAULTS.deploy,\r\n report,\r\n 'deploy',\r\n );\r\n\r\n const contract = await chooseAxis<ContractKind>(\r\n p,\r\n pre.contract,\r\n '契约类型',\r\n CONTRACT_KINDS,\r\n OPTION_DEFAULTS.contract,\r\n report,\r\n 'contract',\r\n );\r\n\r\n const ai = await chooseAxis<AiTool>(\r\n p,\r\n pre.ai,\r\n 'AI 工具集成',\r\n AI_TOOLS,\r\n OPTION_DEFAULTS.ai,\r\n report,\r\n 'ai',\r\n );\r\n\r\n const license = await chooseAxis<LicenseKind>(\r\n p,\r\n pre.license,\r\n '许可证',\r\n LICENSE_KINDS,\r\n OPTION_DEFAULTS.license,\r\n report,\r\n 'license',\r\n );\r\n\r\n const gitLfs =\r\n pre.gitLfs ?? (await p.confirm('启用 Git LFS 管理大文件', OPTION_DEFAULTS.gitLfs));\r\n report('gitLfs', String(gitLfs), pre.gitLfs === undefined);\r\n\r\n const integrations =\r\n pre.integrations ??\r\n (await p.confirm(\r\n '生成 docs/06-集成对接/ 骨架(当本仓库的某一端需要与外部仓库对接时启用)',\r\n OPTION_DEFAULTS.integrations,\r\n ));\r\n report('integrations', String(integrations), pre.integrations === undefined);\r\n\r\n const ci = await chooseAxis<CiPlatform>(\r\n p,\r\n pre.ci,\r\n 'CI 平台',\r\n CI_PLATFORMS,\r\n OPTION_DEFAULTS.ci,\r\n report,\r\n 'ci',\r\n );\r\n\r\n let roles: readonly Role[];\r\n if (pre.roles !== undefined) {\r\n roles = pre.roles;\r\n report('roles', roles.length > 0 ? roles.join(',') : '<none>', false);\r\n } else {\r\n const roleChoices: SelectChoice<Role>[] = [\r\n { value: 'design', name: 'design UI / UX 设计资源' },\r\n { value: 'qa', name: 'qa 测试计划 / 用例 / 缺陷库' },\r\n { value: 'field', name: 'field 客户部署手册 / 上线作业书' },\r\n { value: 'data', name: 'data 埋点契约 / 看板 / 数据资产' },\r\n { value: 'legal-security', name: 'legal-security 合规清单 / PIA / 安全评估' },\r\n { value: 'marketing', name: 'marketing 产品介绍 / 白皮书 / 案例' },\r\n ];\r\n roles = await p.checkbox<Role>(\r\n '启用哪些角色目录?(空格切换;回车确认)',\r\n roleChoices,\r\n OPTION_DEFAULTS.roles,\r\n );\r\n report('roles', roles.length > 0 ? roles.join(',') : '<none>', roles.length === 0);\r\n }\r\n\r\n return {\r\n projectName,\r\n backend,\r\n frontend,\r\n mobile,\r\n miniapp,\r\n agent,\r\n deploy,\r\n contract,\r\n ai,\r\n license,\r\n gitLfs,\r\n integrations,\r\n ci,\r\n roles,\r\n };\r\n}\r\n\r\nasync function chooseAxis<T extends string>(\r\n prompter: Prompter,\r\n prefilled: T | undefined,\r\n question: string,\r\n domain: readonly T[],\r\n def: T,\r\n report: (axis: string, value: string, usedDefault: boolean) => void,\r\n axisName: string,\r\n): Promise<T> {\r\n if (prefilled !== undefined) {\r\n report(axisName, prefilled, false);\r\n return prefilled;\r\n }\r\n const value = await prompter.select<T>(question, stringChoices(domain), def);\r\n report(axisName, value, false);\r\n return value;\r\n}\r\n","/**\r\n * Fragment loader — scans `src/templates/*` and returns the loaded, validated\r\n * TemplateManifests along with a canonical templates root directory.\r\n *\r\n * A fragment directory is laid out as:\r\n *\r\n * <templatesRoot>/<fragmentDir>/\r\n * fragment.yaml ← TemplateManifest\r\n * files/ ← source files referenced by manifest.files[].from\r\n *\r\n * The loader performs:\r\n * 1. directory discovery\r\n * 2. YAML parse + schema assertion (delegated to parseManifest)\r\n * 3. invariant checks: fragment name uniqueness across the whole set,\r\n * and that every `files[].from` exists on disk\r\n *\r\n * Any violation is a TemplateError (exit code 3) because it is an authoring\r\n * bug in the scaffolder itself, not user input.\r\n */\r\n\r\nimport { promises as fs, accessSync } from 'node:fs';\r\nimport * as path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport { ManifestValidationError, parseManifest, type TemplateManifest } from '../schema/manifest.js';\r\n\r\nexport interface LoadedFragment {\r\n readonly manifest: TemplateManifest;\r\n /** Absolute path to the fragment directory on disk. */\r\n readonly rootDir: string;\r\n}\r\n\r\nexport interface LoadFragmentsResult {\r\n readonly templatesRoot: string;\r\n readonly fragments: readonly LoadedFragment[];\r\n}\r\n\r\n/**\r\n * Resolve the default templates root relative to this source module.\r\n *\r\n * The loader walks upward from the current file until it finds the\r\n * directory containing `package.json`. Templates are expected at\r\n * `<pkg>/src/templates` (kept there in source *and* in the published\r\n * tarball via `package.json#files`), so the same resolution works in\r\n * development (running `src/...` via ts) and in the shipped `dist`\r\n * bundle.\r\n */\r\nexport function defaultTemplatesRoot(): string {\r\n const here = path.dirname(fileURLToPath(import.meta.url));\r\n const pkgRoot = findPackageRoot(here);\r\n return path.join(pkgRoot, 'src', 'templates');\r\n}\r\n\r\nfunction findPackageRoot(start: string): string {\r\n let dir = start;\r\n // Bound the walk to avoid infinite loops on weird filesystems.\r\n for (let i = 0; i < 10; i += 1) {\r\n const candidate = path.join(dir, 'package.json');\r\n try {\r\n accessSync(candidate);\r\n return dir;\r\n } catch {\r\n // fall through\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n throw new TemplateError(\r\n `unable to locate package.json above ${start}`,\r\n '请确认 scaffolder 作为 npm 包安装,src/templates/ 与 package.json 位于同一层级',\r\n );\r\n}\r\n\r\nexport async function loadFragments(rootOverride?: string): Promise<LoadFragmentsResult> {\r\n const templatesRoot = rootOverride ?? defaultTemplatesRoot();\r\n\r\n let entries: string[];\r\n try {\r\n const dirents = await fs.readdir(templatesRoot, { withFileTypes: true });\r\n entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `failed to read templates directory ${templatesRoot}: ${(e as Error).message}`,\r\n '请确认 scaffolder 安装完整(src/templates/ 存在)',\r\n );\r\n }\r\n\r\n const loaded: LoadedFragment[] = [];\r\n const seenNames = new Set<string>();\r\n\r\n for (const dir of entries.sort()) {\r\n const fragmentDir = path.join(templatesRoot, dir);\r\n const manifestPath = path.join(fragmentDir, 'fragment.yaml');\r\n\r\n let text: string;\r\n try {\r\n text = await fs.readFile(manifestPath, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `fragment \"${dir}\" is missing fragment.yaml: ${(e as Error).message}`,\r\n '每个 fragment 目录必须包含 fragment.yaml',\r\n );\r\n }\r\n\r\n let manifest: TemplateManifest;\r\n try {\r\n manifest = parseManifest(text);\r\n } catch (e) {\r\n if (e instanceof ManifestValidationError) {\r\n throw new TemplateError(\r\n `fragment \"${dir}\" has invalid manifest:\\n${e.message}`,\r\n '按 fragment.yaml schema 修复字段',\r\n );\r\n }\r\n throw e;\r\n }\r\n\r\n if (manifest.name !== dir) {\r\n throw new TemplateError(\r\n `fragment directory \"${dir}\" declares name \"${manifest.name}\"`,\r\n '目录名与 manifest.name 必须一致,便于静态索引',\r\n );\r\n }\r\n if (seenNames.has(manifest.name)) {\r\n throw new TemplateError(\r\n `duplicate fragment name: ${manifest.name}`,\r\n '每个 fragment 名称在全局必须唯一',\r\n );\r\n }\r\n seenNames.add(manifest.name);\r\n\r\n // Verify every `from` path actually exists. `to` paths are relative to\r\n // the target directory and are checked at plan build time.\r\n for (const file of manifest.files) {\r\n const fromAbs = path.join(fragmentDir, file.from);\r\n try {\r\n await fs.access(fromAbs);\r\n } catch {\r\n throw new TemplateError(\r\n `fragment \"${manifest.name}\" references missing file: ${file.from}`,\r\n '请确认 files/ 中包含该文件,或修正 manifest.files[].from',\r\n );\r\n }\r\n }\r\n\r\n loaded.push({ manifest, rootDir: fragmentDir });\r\n }\r\n\r\n return { templatesRoot, fragments: loaded };\r\n}\r\n","/**\r\n * TemplateManifest schema and (de)serialization.\r\n *\r\n * A manifest describes one Fragment: how it is selected (appliesWhen),\r\n * its priority relative to others, and the files it contributes.\r\n *\r\n * Round-trip invariant (Property 5): parseManifest(serializeManifest(m)) deepEquals m.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\r\n\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n ROLE_KINDS,\r\n type Options,\r\n type Role,\r\n} from './options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Condition expressed as equality on a subset of Options fields, plus a\r\n * special-case `role` field interpreted as \"options.roles contains this role\".\r\n *\r\n * Per design §Components.Fragment Resolver, only conjunctive equality is\r\n * supported on scalar Options fields. Array-valued `roles` cannot use\r\n * straight equality; `role` (singular) is the per-fragment role gate.\r\n */\r\nexport type AppliesWhen = Partial<Omit<Options, 'projectName' | 'roles'>> & {\r\n readonly role?: Role;\r\n};\r\n\r\nexport interface ManifestFileRule {\r\n readonly from: string;\r\n readonly to: string;\r\n readonly render: boolean;\r\n readonly mode?: number;\r\n}\r\n\r\nexport interface TemplateManifest {\r\n readonly name: string;\r\n readonly version: string;\r\n readonly appliesWhen: AppliesWhen;\r\n readonly priority: number;\r\n readonly files: readonly ManifestFileRule[];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema\r\n// ---------------------------------------------------------------------------\r\n\r\nconst SEMVER_PATTERN = '^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:-[0-9A-Za-z-.]+)?(?:\\\\+[0-9A-Za-z-.]+)?$';\r\nconst FRAGMENT_NAME_PATTERN = '^[a-z0-9][a-z0-9-]{0,62}$';\r\nconst POSIX_RELATIVE_PATH_PATTERN = '^(?!/)(?!.*(^|/)\\\\.\\\\.($|/))[^\\\\\\\\]+$';\r\n\r\nexport const MANIFEST_FILE_RULE_SCHEMA = {\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['from', 'to', 'render'],\r\n properties: {\r\n from: { type: 'string', minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },\r\n to: { type: 'string', minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },\r\n render: { type: 'boolean' },\r\n mode: { type: 'integer', minimum: 0, maximum: 0o7777 },\r\n },\r\n} as const;\r\n\r\nexport const APPLIES_WHEN_SCHEMA = {\r\n type: 'object',\r\n additionalProperties: false,\r\n properties: {\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n role: { type: 'string', enum: [...ROLE_KINDS] },\r\n },\r\n} as const;\r\n\r\nexport const MANIFEST_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/manifest.json',\r\n title: 'TemplateManifest',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['name', 'version', 'appliesWhen', 'priority', 'files'],\r\n properties: {\r\n name: { type: 'string', pattern: FRAGMENT_NAME_PATTERN },\r\n version: { type: 'string', pattern: SEMVER_PATTERN },\r\n appliesWhen: APPLIES_WHEN_SCHEMA,\r\n priority: { type: 'integer', minimum: 0, maximum: 100 },\r\n files: {\r\n type: 'array',\r\n items: MANIFEST_FILE_RULE_SCHEMA,\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\nconst validateFn = ajv.compile<TemplateManifest>(MANIFEST_SCHEMA);\r\n\r\nexport interface ManifestValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class ManifestValidationError extends Error {\r\n readonly issues: readonly ManifestValidationIssue[];\r\n\r\n constructor(issues: readonly ManifestValidationIssue[]) {\r\n super(\r\n `Invalid TemplateManifest:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'ManifestValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): ManifestValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path:\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\n/**\r\n * Also enforces invariants that pure JSON Schema cannot express:\r\n * - `files[].to` relative paths must not include \"..\" segments (redundant\r\n * with the POSIX pattern but asserted explicitly for defense in depth).\r\n * - `files[].to` values must be unique within a single manifest (duplicate\r\n * outputs from the same fragment indicate an authoring bug).\r\n */\r\nfunction assertStructuralInvariants(m: TemplateManifest): void {\r\n const seen = new Set<string>();\r\n for (const f of m.files) {\r\n if (seen.has(f.to)) {\r\n throw new ManifestValidationError([\r\n { path: '/files', message: `duplicate target path: ${f.to}` },\r\n ]);\r\n }\r\n seen.add(f.to);\r\n }\r\n}\r\n\r\nexport function assertManifest(value: unknown): TemplateManifest {\r\n if (!validateFn(value)) {\r\n throw new ManifestValidationError(collectIssues());\r\n }\r\n assertStructuralInvariants(value);\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Ordering helpers — canonical forms for round-trip\r\n// ---------------------------------------------------------------------------\r\n\r\nconst MANIFEST_TOP_ORDER: readonly (keyof TemplateManifest)[] = [\r\n 'name',\r\n 'version',\r\n 'appliesWhen',\r\n 'priority',\r\n 'files',\r\n];\r\n\r\nconst APPLIES_WHEN_ORDER: readonly (keyof AppliesWhen)[] = [\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'role',\r\n];\r\n\r\nconst FILE_RULE_ORDER: readonly (keyof ManifestFileRule)[] = ['from', 'to', 'render', 'mode'];\r\n\r\nfunction canonicalAppliesWhen(a: AppliesWhen): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of APPLIES_WHEN_ORDER) {\r\n if (a[key] !== undefined) {\r\n out[key] = a[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalFileRule(f: ManifestFileRule): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of FILE_RULE_ORDER) {\r\n if (f[key] !== undefined) {\r\n out[key] = f[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalManifest(m: TemplateManifest): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of MANIFEST_TOP_ORDER) {\r\n if (key === 'appliesWhen') {\r\n out[key] = canonicalAppliesWhen(m.appliesWhen);\r\n } else if (key === 'files') {\r\n out[key] = m.files.map(canonicalFileRule);\r\n } else {\r\n out[key] = m[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parse / serialize\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a YAML or JSON manifest string into a validated TemplateManifest.\r\n */\r\nexport function parseManifest(input: string): TemplateManifest {\r\n let raw: unknown;\r\n const trimmed = input.trim();\r\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\r\n try {\r\n raw = JSON.parse(trimmed);\r\n } catch (e) {\r\n throw new ManifestValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n } else {\r\n try {\r\n raw = parseYaml(input);\r\n } catch (e) {\r\n throw new ManifestValidationError([\r\n { path: '', message: `invalid YAML: ${(e as Error).message}` },\r\n ]);\r\n }\r\n }\r\n return assertManifest(raw);\r\n}\r\n\r\n/**\r\n * Serialize to canonical YAML (the on-disk format of fragment.yaml).\r\n * Field order is fixed to guarantee byte-stable output and round-trip equality.\r\n */\r\nexport function serializeManifest(manifest: TemplateManifest): string {\r\n return stringifyYaml(canonicalManifest(manifest), {\r\n lineWidth: 0,\r\n minContentWidth: 0,\r\n });\r\n}\r\n","/**\r\n * Fragment resolver — given a set of loaded fragments and the user's\r\n * resolved Options, return the subset whose `appliesWhen` matches.\r\n *\r\n * Matching is conjunctive equality over every field declared in\r\n * `appliesWhen`: a fragment applies iff, for every key K in `appliesWhen`,\r\n * `options[K] === appliesWhen[K]`. An empty `appliesWhen` always matches.\r\n *\r\n * Result ordering:\r\n * - Primary: `priority` ascending (lower priority applied first; higher\r\n * priority's writes win during plan building).\r\n * - Secondary: fragment name ascending, to make the output deterministic\r\n * and friendly to snapshot tests.\r\n */\r\n\r\nimport type { AppliesWhen, TemplateManifest } from '../schema/manifest.js';\r\nimport type { Options } from '../schema/options.js';\r\nimport type { LoadedFragment } from './loader.js';\r\n\r\nexport function fragmentApplies(manifest: TemplateManifest, options: Options): boolean {\r\n return appliesWhenMatches(manifest.appliesWhen, options);\r\n}\r\n\r\nexport function appliesWhenMatches(appliesWhen: AppliesWhen, options: Options): boolean {\r\n for (const key of Object.keys(appliesWhen) as Array<keyof AppliesWhen>) {\r\n const expected = appliesWhen[key];\r\n if (expected === undefined) continue;\r\n if (key === 'role') {\r\n // `role` is the singular \"this options.roles array contains X\" gate.\r\n if (!options.roles.includes(expected as (typeof options.roles)[number])) {\r\n return false;\r\n }\r\n continue;\r\n }\r\n const actual = options[key as keyof Options];\r\n if (actual !== expected) return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Select the applicable fragments for the given Options and return them in\r\n * deterministic order. Pure function of its inputs — the same Options +\r\n * same fragment list always yields the same array instance contents\r\n * (supporting Property 8: Fragment 合并幂等).\r\n */\r\nexport function selectFragments(\r\n options: Options,\r\n loaded: readonly LoadedFragment[],\r\n): readonly LoadedFragment[] {\r\n const applicable = loaded.filter((f) => fragmentApplies(f.manifest, options));\r\n return [...applicable].sort((a, b) => {\r\n if (a.manifest.priority !== b.manifest.priority) {\r\n return a.manifest.priority - b.manifest.priority;\r\n }\r\n return a.manifest.name.localeCompare(b.manifest.name);\r\n });\r\n}\r\n","/**\r\n * Render context — the *only* source of variables visible to templates.\r\n *\r\n * Every template string is evaluated against an object of this shape. By\r\n * keeping the surface minimal and explicit, undefined variable references\r\n * become trivially detectable (§13.2).\r\n *\r\n * Derived fields (year, generatedAt) are computed here so templates never\r\n * need to call `new Date()` themselves, keeping rendering deterministic.\r\n */\r\n\r\nimport type { Options } from '../schema/options.js';\r\n\r\nexport interface RenderContext {\r\n readonly options: Options;\r\n /** Four-digit year derived from `generatedAt`. */\r\n readonly year: string;\r\n /** ISO 8601 UTC timestamp. */\r\n readonly generatedAt: string;\r\n /** Scaffolder's own SemVer (for banner comments, metadata, etc.). */\r\n readonly scaffolderVersion: string;\r\n}\r\n\r\nexport interface BuildContextInput {\r\n readonly options: Options;\r\n readonly scaffolderVersion: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport function buildRenderContext(input: BuildContextInput): RenderContext {\r\n const now = input.now ?? new Date();\r\n return {\r\n options: input.options,\r\n year: String(now.getUTCFullYear()),\r\n generatedAt: now.toISOString(),\r\n scaffolderVersion: input.scaffolderVersion,\r\n };\r\n}\r\n","/**\r\n * File Plan Builder — turn a selected set of fragments into a concrete\r\n * FilePlan the writer can execute.\r\n *\r\n * Responsibilities:\r\n * 1. For each selected fragment, read each manifest file and either\r\n * render it through the Template Renderer (render: true) or copy it\r\n * verbatim (render: false).\r\n * 2. Resolve the `to` path against the target directory (which is the\r\n * project root; paths are always POSIX-style in manifests but we\r\n * store them in the plan using `path.posix` to stay deterministic\r\n * across OSes).\r\n * 3. Detect same-path conflicts across fragments. Higher priority wins;\r\n * ties throw a TemplateError (§12.4).\r\n * 4. Emit a sorted, deterministic plan so Dry Run diffs and snapshot\r\n * tests remain stable.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { LoadedFragment } from '../fragments/loader.js';\r\nimport type { RenderContext } from '../render/context.js';\r\nimport { renderFile } from '../render/renderer.js';\r\n\r\nexport interface PlannedFile {\r\n /** POSIX-style path relative to the target directory. */\r\n readonly targetPath: string;\r\n readonly content: string;\r\n /** Name of the fragment that contributed this file (post conflict resolution). */\r\n readonly contributedBy: string;\r\n readonly mode?: number;\r\n}\r\n\r\nexport interface FilePlan {\r\n readonly files: readonly PlannedFile[];\r\n /** POSIX-style directory paths, sorted, deduplicated. */\r\n readonly directories: readonly string[];\r\n}\r\n\r\nexport interface BuildPlanInput {\r\n readonly fragments: readonly LoadedFragment[];\r\n readonly context: RenderContext;\r\n}\r\n\r\n/**\r\n * Produce a plan from a list of already-selected fragments (post-resolver).\r\n * Pure over its inputs — given the same fragments + context, the returned\r\n * plan is byte-identical every time.\r\n */\r\nexport async function buildFilePlan(input: BuildPlanInput): Promise<FilePlan> {\r\n const { fragments, context } = input;\r\n\r\n interface CandidateFile extends PlannedFile {\r\n readonly priority: number;\r\n }\r\n\r\n const byPath = new Map<string, CandidateFile>();\r\n\r\n for (const fragment of fragments) {\r\n for (const rule of fragment.manifest.files) {\r\n const fromAbs = path.join(fragment.rootDir, rule.from);\r\n const targetPath = normalizePosixPath(rule.to);\r\n\r\n let content: string;\r\n if (rule.render) {\r\n content = await renderFile(fromAbs, context);\r\n } else {\r\n try {\r\n content = await fs.readFile(fromAbs, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `failed to read template source ${fromAbs}: ${(e as Error).message}`,\r\n '请确认 manifest.files[].from 指向真实文件',\r\n );\r\n }\r\n }\r\n\r\n const candidate: CandidateFile = {\r\n targetPath,\r\n content,\r\n contributedBy: fragment.manifest.name,\r\n ...(rule.mode !== undefined ? { mode: rule.mode } : {}),\r\n priority: fragment.manifest.priority,\r\n };\r\n\r\n const existing = byPath.get(targetPath);\r\n if (existing === undefined) {\r\n byPath.set(targetPath, candidate);\r\n continue;\r\n }\r\n\r\n // Conflict: resolve by priority. Strict greater-than wins; equal\r\n // priorities with differing content is a TemplateError (§12.4).\r\n if (candidate.priority > existing.priority) {\r\n byPath.set(targetPath, candidate);\r\n } else if (candidate.priority < existing.priority) {\r\n // keep existing\r\n } else if (existing.content !== candidate.content) {\r\n throw new TemplateError(\r\n `conflict at ${targetPath}: fragments \"${existing.contributedBy}\" and ` +\r\n `\"${candidate.contributedBy}\" have equal priority ${candidate.priority} ` +\r\n `but contribute different content`,\r\n '调整 fragment 的 priority,或由一个 fragment 独占该路径',\r\n );\r\n }\r\n // Equal priority + equal content → silently dedupe.\r\n }\r\n }\r\n\r\n const files = [...byPath.values()]\r\n .map(({ priority: _p, ...rest }): PlannedFile => rest)\r\n .sort((a, b) => (a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0));\r\n\r\n const directorySet = new Set<string>();\r\n for (const f of files) {\r\n let cur = path.posix.dirname(f.targetPath);\r\n while (cur && cur !== '.' && cur !== '/') {\r\n directorySet.add(cur);\r\n cur = path.posix.dirname(cur);\r\n }\r\n }\r\n const directories = [...directorySet].sort();\r\n\r\n return { files, directories };\r\n}\r\n\r\nfunction normalizePosixPath(p: string): string {\r\n // Convert any backslashes that might appear on Windows authoring to POSIX,\r\n // then collapse redundant separators.\r\n return p.split(/[\\\\/]+/).filter(Boolean).join('/');\r\n}\r\n","/**\r\n * Template Renderer — render a single file body against a RenderContext.\r\n *\r\n * Design decisions:\r\n * - Uses `eta` with the explicit `<%= … %>` interpolation syntax, which\r\n * avoids colliding with YAML / JSON brace syntax.\r\n * - Strict variable resolution: any reference to a field not present on\r\n * RenderContext throws a TemplateError (§13.2, Property 11).\r\n * - No filesystem / IO access is given to templates; the sandboxed `it`\r\n * object is the *only* data they can read.\r\n *\r\n * Two entry points are provided:\r\n * - `renderString` for in-memory content (tests, generated scripts)\r\n * - `renderFile` that reads from disk and annotates errors with the path\r\n */\r\n\r\nimport { Eta } from 'eta';\r\nimport { promises as fs } from 'node:fs';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { RenderContext } from './context.js';\r\n\r\n/**\r\n * Create a fresh Eta instance configured for Scaffolder's template dialect.\r\n *\r\n * We disable caching because templates are read directly from disk and the\r\n * fragment loader already deduplicates work; a cache would only add a\r\n * footgun for tests that reuse filenames across iterations.\r\n */\r\nfunction createEta(): Eta {\r\n return new Eta({\r\n autoEscape: false,\r\n useWith: false,\r\n cache: false,\r\n autoTrim: false,\r\n // Force tags to the explicit form to keep syntax unambiguous.\r\n tags: ['<%', '%>'],\r\n });\r\n}\r\n\r\nfunction toStrictContext(ctx: RenderContext): ProxyHandler<RenderContext> extends never\r\n ? never\r\n : Record<string, unknown> {\r\n // Wrap the context in a Proxy that throws on any unknown property access,\r\n // turning \"undefined variable\" into an immediate TemplateError rather than\r\n // silently rendering `undefined` into the output.\r\n const base: Record<string, unknown> = {\r\n options: ctx.options,\r\n year: ctx.year,\r\n generatedAt: ctx.generatedAt,\r\n scaffolderVersion: ctx.scaffolderVersion,\r\n };\r\n\r\n return new Proxy(base, {\r\n get(target, prop, receiver) {\r\n if (typeof prop === 'symbol') {\r\n return Reflect.get(target, prop, receiver);\r\n }\r\n if (!(prop in target)) {\r\n throw new TemplateError(\r\n `undefined template variable: ${String(prop)}`,\r\n '请确认变量名与 RenderContext 字段一致,或扩展 RenderContext',\r\n );\r\n }\r\n return Reflect.get(target, prop, receiver);\r\n },\r\n }) as Record<string, unknown>;\r\n}\r\n\r\nfunction rethrowRenderError(source: string, err: unknown): never {\r\n if (err instanceof TemplateError) throw err;\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new TemplateError(\r\n `render failed in ${source}: ${message}`,\r\n '检查模板语法是否与 <% %> 标签一致',\r\n );\r\n}\r\n\r\nexport function renderString(\r\n template: string,\r\n context: RenderContext,\r\n source = '<string>',\r\n): string {\r\n const eta = createEta();\r\n const strict = toStrictContext(context);\r\n try {\r\n const out = eta.renderString(template, strict);\r\n if (typeof out !== 'string') {\r\n throw new TemplateError(\r\n `renderer returned non-string for ${source}`,\r\n '这是一个 scaffolder 内部错误,请汇报',\r\n );\r\n }\r\n return out;\r\n } catch (err) {\r\n rethrowRenderError(source, err);\r\n }\r\n}\r\n\r\nexport async function renderFile(\r\n absPath: string,\r\n context: RenderContext,\r\n): Promise<string> {\r\n let text: string;\r\n try {\r\n text = await fs.readFile(absPath, 'utf8');\r\n } catch (err) {\r\n throw new TemplateError(\r\n `failed to read template ${absPath}: ${(err as Error).message}`,\r\n '请确认 fragment.yaml 中的 from 路径指向真实文件',\r\n );\r\n }\r\n return renderString(text, context, absPath);\r\n}\r\n","/**\r\n * Syntax Validator — parse every structured file in a FilePlan against its\r\n * format's grammar. Failures are TemplateError (exit code 3) because they\r\n * indicate that a template produced invalid output, not that the user did\r\n * something wrong.\r\n *\r\n * This runs *after* plan building and *before* file writing, so that a\r\n * bad render never makes it to disk (pairs with rollback: catching here\r\n * means nothing was written yet).\r\n */\r\n\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { FilePlan } from './builder.js';\r\n\r\nexport function validatePlanSyntax(plan: FilePlan): void {\r\n for (const file of plan.files) {\r\n const ext = lowerExt(file.targetPath);\r\n if (ext === 'json') {\r\n try {\r\n JSON.parse(file.content);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `invalid JSON in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${(e as Error).message}`,\r\n '检查对应模板的语法',\r\n );\r\n }\r\n } else if (ext === 'yaml' || ext === 'yml') {\r\n try {\r\n parseYaml(file.content);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `invalid YAML in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${(e as Error).message}`,\r\n '检查对应模板的缩进与引号',\r\n );\r\n }\r\n }\r\n }\r\n}\r\n\r\nfunction lowerExt(p: string): string {\r\n const dot = p.lastIndexOf('.');\r\n if (dot < 0) return '';\r\n return p.slice(dot + 1).toLowerCase();\r\n}\r\n","/**\r\n * ScaffolderMetadata schema and I/O helpers.\r\n *\r\n * This module defines the shape of `.scaffolder.json`, which is written into\r\n * every generated project so that the project can be traced back to a specific\r\n * Scaffolder version and its input options.\r\n *\r\n * Round-trip invariant (Property 6): loadMetadata(writeMetadata(d)) deepEquals d.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport addFormats from 'ajv-formats';\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport {\r\n OPTIONS_FIELD_ORDER,\r\n OPTIONS_SCHEMA,\r\n assertOptions,\r\n type Options,\r\n} from './options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TemplateFragmentRef {\r\n readonly name: string;\r\n readonly version: string;\r\n}\r\n\r\nexport interface ScaffolderMetadata {\r\n readonly scaffolderVersion: string;\r\n readonly generatedAt: string;\r\n readonly options: Options;\r\n readonly templateFragments: readonly TemplateFragmentRef[];\r\n}\r\n\r\nexport const METADATA_FILENAME = '.scaffolder.json';\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema\r\n// ---------------------------------------------------------------------------\r\n\r\nconst SEMVER_PATTERN = '^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:-[0-9A-Za-z-.]+)?(?:\\\\+[0-9A-Za-z-.]+)?$';\r\nconst FRAGMENT_NAME_PATTERN = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\nexport const METADATA_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/metadata.json',\r\n title: 'ScaffolderMetadata',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['scaffolderVersion', 'generatedAt', 'options', 'templateFragments'],\r\n properties: {\r\n scaffolderVersion: { type: 'string', pattern: SEMVER_PATTERN },\r\n generatedAt: { type: 'string', format: 'date-time' },\r\n options: OPTIONS_SCHEMA,\r\n templateFragments: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['name', 'version'],\r\n properties: {\r\n name: { type: 'string', pattern: FRAGMENT_NAME_PATTERN },\r\n version: { type: 'string', pattern: SEMVER_PATTERN },\r\n },\r\n },\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\naddFormats(ajv);\r\nconst validateFn = ajv.compile<ScaffolderMetadata>(METADATA_SCHEMA);\r\n\r\nexport interface MetadataValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class MetadataValidationError extends Error {\r\n readonly issues: readonly MetadataValidationIssue[];\r\n\r\n constructor(issues: readonly MetadataValidationIssue[]) {\r\n super(\r\n `Invalid ScaffolderMetadata:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'MetadataValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): MetadataValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path:\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\nexport function assertMetadata(value: unknown): ScaffolderMetadata {\r\n if (!validateFn(value)) {\r\n throw new MetadataValidationError(collectIssues());\r\n }\r\n // Re-validate the nested options with the dedicated assertOptions so that\r\n // any option-specific invariant lives in a single place.\r\n assertOptions(value.options);\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Canonical serialization\r\n// ---------------------------------------------------------------------------\r\n\r\nconst METADATA_TOP_ORDER: readonly (keyof ScaffolderMetadata)[] = [\r\n 'scaffolderVersion',\r\n 'generatedAt',\r\n 'options',\r\n 'templateFragments',\r\n];\r\n\r\nfunction canonicalOptions(options: Options): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of OPTIONS_FIELD_ORDER) {\r\n out[key] = options[key];\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalFragmentRef(ref: TemplateFragmentRef): Record<string, unknown> {\r\n return { name: ref.name, version: ref.version };\r\n}\r\n\r\nfunction canonicalMetadata(metadata: ScaffolderMetadata): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of METADATA_TOP_ORDER) {\r\n if (key === 'options') {\r\n out[key] = canonicalOptions(metadata.options);\r\n } else if (key === 'templateFragments') {\r\n out[key] = metadata.templateFragments.map(canonicalFragmentRef);\r\n } else {\r\n out[key] = metadata[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nexport function serializeMetadata(metadata: ScaffolderMetadata): string {\r\n return JSON.stringify(canonicalMetadata(metadata), null, 2) + '\\n';\r\n}\r\n\r\nexport function parseMetadata(input: string): ScaffolderMetadata {\r\n let raw: unknown;\r\n try {\r\n raw = JSON.parse(input);\r\n } catch (e) {\r\n throw new MetadataValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n return assertMetadata(raw);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Filesystem I/O\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Write the metadata file into the given target directory.\r\n * The directory is assumed to already exist (the writer stage creates it).\r\n */\r\nexport async function writeMetadataFile(\r\n targetDirectory: string,\r\n metadata: ScaffolderMetadata,\r\n): Promise<string> {\r\n const filePath = path.join(targetDirectory, METADATA_FILENAME);\r\n await fs.writeFile(filePath, serializeMetadata(metadata), 'utf8');\r\n return filePath;\r\n}\r\n\r\n/**\r\n * Load and validate an existing `.scaffolder.json` from the target directory.\r\n * Intended for future `upgrade`-like commands; the current `create` flow does\r\n * not read it back, but having a symmetric API makes round-trip testing easy.\r\n */\r\nexport async function loadMetadataFile(targetDirectory: string): Promise<ScaffolderMetadata> {\r\n const filePath = path.join(targetDirectory, METADATA_FILENAME);\r\n const text = await fs.readFile(filePath, 'utf8');\r\n return parseMetadata(text);\r\n}\r\n\r\n/**\r\n * Build a metadata object from the current scaffolder version, options, and\r\n * applied fragments. Uses UTC ISO 8601 for `generatedAt`.\r\n */\r\nexport function buildMetadata(params: {\r\n scaffolderVersion: string;\r\n options: Options;\r\n templateFragments: readonly TemplateFragmentRef[];\r\n now?: Date;\r\n}): ScaffolderMetadata {\r\n const now = params.now ?? new Date();\r\n return {\r\n scaffolderVersion: params.scaffolderVersion,\r\n generatedAt: now.toISOString(),\r\n options: params.options,\r\n templateFragments: [...params.templateFragments],\r\n };\r\n}\r\n","/**\r\n * Helper to append the `.scaffolder.json` metadata file to a FilePlan.\r\n *\r\n * Keeping this outside the writer ensures metadata participates in the\r\n * same atomic write-or-rollback cycle as every other generated file\r\n * (requirement 10.3).\r\n */\r\n\r\nimport type { FilePlan, PlannedFile } from './builder.js';\r\nimport {\r\n METADATA_FILENAME,\r\n buildMetadata,\r\n serializeMetadata,\r\n type TemplateFragmentRef,\r\n} from '../schema/metadata.js';\r\nimport type { Options } from '../schema/options.js';\r\n\r\nexport interface AppendMetadataInput {\r\n readonly plan: FilePlan;\r\n readonly scaffolderVersion: string;\r\n readonly options: Options;\r\n readonly templateFragments: readonly TemplateFragmentRef[];\r\n readonly now?: Date;\r\n}\r\n\r\nexport function appendScaffolderMetadata(input: AppendMetadataInput): FilePlan {\r\n const metadata = buildMetadata({\r\n scaffolderVersion: input.scaffolderVersion,\r\n options: input.options,\r\n templateFragments: input.templateFragments,\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n const file: PlannedFile = {\r\n targetPath: METADATA_FILENAME,\r\n content: serializeMetadata(metadata),\r\n contributedBy: '__scaffolder_metadata__',\r\n };\r\n // Insert sorted by targetPath to keep plan ordering stable.\r\n const files = [...input.plan.files, file].sort((a, b) =>\r\n a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0,\r\n );\r\n return { files, directories: input.plan.directories };\r\n}\r\n","/**\r\n * File Writer with Rollback Journal.\r\n *\r\n * Algorithm (design §Components.File Writer):\r\n * 1. If the target directory does not exist, create it and record the\r\n * top-most created path for possible rollback.\r\n * 2. Create every directory in plan.directories (sorted parent-first).\r\n * Track each newly-created one.\r\n * 3. Write files in deterministic order. Track each write.\r\n * 4. On any failure in steps 2 or 3, undo in reverse: delete files,\r\n * then remove empty directories that we created.\r\n *\r\n * `--force` semantics:\r\n * - Before writing a file that already exists, capture its original\r\n * content (bounded by FORCE_BACKUP_BYTE_LIMIT) so rollback can restore it.\r\n * - If any existing file exceeds the limit, refuse to proceed; this is a\r\n * UserInputError (§2.2) — the user asked for --force on a workspace that\r\n * is too risky to cover with our bounded backup.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { FilesystemError, UserInputError } from '../errors.js';\r\nimport type { FilePlan } from '../plan/builder.js';\r\n\r\nexport interface WriteOptions {\r\n readonly targetDirectory: string;\r\n readonly force: boolean;\r\n}\r\n\r\nexport interface WriteResult {\r\n readonly createdFiles: readonly string[];\r\n readonly createdDirectories: readonly string[];\r\n}\r\n\r\nconst FORCE_BACKUP_BYTE_LIMIT = 10 * 1024 * 1024; // 10 MB\r\n\r\ninterface FileBackup {\r\n readonly absPath: string;\r\n readonly originalContent: string;\r\n}\r\n\r\ninterface Journal {\r\n // Populated in insertion order; rolled back in reverse.\r\n readonly createdFiles: string[];\r\n readonly createdDirectories: string[];\r\n readonly overwrites: FileBackup[];\r\n}\r\n\r\n/**\r\n * Write the plan to disk. On any error throws and leaves the filesystem\r\n * in its pre-call state (best effort), then re-raises.\r\n *\r\n * Returns paths that *we* created (useful for Reporter / tests).\r\n */\r\nexport async function writePlan(plan: FilePlan, opts: WriteOptions): Promise<WriteResult> {\r\n const absTarget = path.resolve(opts.targetDirectory);\r\n const journal: Journal = { createdFiles: [], createdDirectories: [], overwrites: [] };\r\n\r\n // Preflight existence check + --force handling.\r\n const targetState = await inspectTarget(absTarget);\r\n if (targetState.exists && targetState.nonEmpty && !opts.force) {\r\n throw new UserInputError(\r\n `target directory is not empty: ${absTarget}`,\r\n '使用不同路径,或加 --force(小心:会覆盖既有文件)',\r\n );\r\n }\r\n\r\n // Preflight: if --force AND target exists AND any file we are about to\r\n // overwrite exceeds the backup limit, refuse early (§9.1 bullet 3).\r\n if (opts.force && targetState.exists) {\r\n await preflightBackupCapacity(absTarget, plan);\r\n }\r\n\r\n try {\r\n // Step 1: ensure the target directory itself.\r\n if (!targetState.exists) {\r\n await fs.mkdir(absTarget, { recursive: true });\r\n journal.createdDirectories.push(absTarget);\r\n }\r\n\r\n // Step 2: subdirectories.\r\n for (const rel of plan.directories) {\r\n const abs = path.join(absTarget, rel);\r\n try {\r\n await fs.mkdir(abs);\r\n journal.createdDirectories.push(abs);\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\r\n // Pre-existing directory; do not add to journal (we did not create it).\r\n continue;\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n // Step 3: files.\r\n for (const file of plan.files) {\r\n const abs = path.join(absTarget, file.targetPath);\r\n await fs.mkdir(path.dirname(abs), { recursive: true });\r\n\r\n // Capture original content if overwriting under --force.\r\n if (opts.force) {\r\n try {\r\n const prev = await fs.readFile(abs, 'utf8');\r\n journal.overwrites.push({ absPath: abs, originalContent: prev });\r\n } catch {\r\n // File did not exist; this is a creation, not an overwrite.\r\n }\r\n }\r\n\r\n await fs.writeFile(abs, file.content, 'utf8');\r\n if (file.mode !== undefined) {\r\n await fs.chmod(abs, file.mode);\r\n }\r\n journal.createdFiles.push(abs);\r\n }\r\n\r\n return {\r\n createdFiles: journal.createdFiles,\r\n createdDirectories: journal.createdDirectories,\r\n };\r\n } catch (err) {\r\n await rollback(journal);\r\n // Wrap filesystem-level errors; leave ScaffolderError subclasses as-is.\r\n if (err instanceof UserInputError) throw err;\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new FilesystemError(\r\n `file writer failed; changes rolled back: ${message}`,\r\n '检查权限与磁盘空间,必要时删除残留目录后重试',\r\n );\r\n }\r\n}\r\n\r\nasync function inspectTarget(\r\n absTarget: string,\r\n): Promise<{ exists: boolean; nonEmpty: boolean }> {\r\n try {\r\n const entries = await fs.readdir(absTarget);\r\n return { exists: true, nonEmpty: entries.length > 0 };\r\n } catch (err) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code === 'ENOENT') return { exists: false, nonEmpty: false };\r\n throw new FilesystemError(\r\n `cannot inspect target directory ${absTarget}: ${(err as Error).message}`,\r\n '检查路径的访问权限',\r\n );\r\n }\r\n}\r\n\r\nasync function preflightBackupCapacity(absTarget: string, plan: FilePlan): Promise<void> {\r\n for (const file of plan.files) {\r\n const abs = path.join(absTarget, file.targetPath);\r\n try {\r\n const stat = await fs.stat(abs);\r\n if (stat.isFile() && stat.size > FORCE_BACKUP_BYTE_LIMIT) {\r\n throw new UserInputError(\r\n `--force would overwrite ${abs} which is larger than the 10 MB rollback limit`,\r\n '手动备份 / 删除该文件后重试',\r\n );\r\n }\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') continue;\r\n if (err instanceof UserInputError) throw err;\r\n // Any other stat error: surface as UserInputError because --force on\r\n // an unreadable tree is unsafe.\r\n throw new UserInputError(\r\n `cannot inspect existing file ${abs}: ${(err as Error).message}`,\r\n '请确认目标目录下所有文件可读',\r\n );\r\n }\r\n }\r\n}\r\n\r\nasync function rollback(journal: Journal): Promise<void> {\r\n // 1) Restore overwrites first (fewer chances of leaving the user worse off).\r\n for (let i = journal.overwrites.length - 1; i >= 0; i -= 1) {\r\n const { absPath, originalContent } = journal.overwrites[i]!;\r\n try {\r\n await fs.writeFile(absPath, originalContent, 'utf8');\r\n } catch {\r\n // best-effort rollback\r\n }\r\n }\r\n\r\n // 2) Delete files we created (not restored overwrites).\r\n const overwriteSet = new Set(journal.overwrites.map((o) => o.absPath));\r\n for (let i = journal.createdFiles.length - 1; i >= 0; i -= 1) {\r\n const p = journal.createdFiles[i]!;\r\n if (overwriteSet.has(p)) continue; // already restored content\r\n try {\r\n await fs.rm(p, { force: true });\r\n } catch {\r\n // best-effort\r\n }\r\n }\r\n\r\n // 3) Remove directories we created, deepest first (same as reverse order\r\n // because we created them parent-first).\r\n for (let i = journal.createdDirectories.length - 1; i >= 0; i -= 1) {\r\n const dir = journal.createdDirectories[i]!;\r\n try {\r\n await fs.rmdir(dir);\r\n } catch {\r\n // Likely non-empty (pre-existing peer content). Leave it.\r\n }\r\n }\r\n}\r\n","/**\r\n * Reporter — turns plans into terminal output at three verbosity levels.\r\n *\r\n * - `quiet` — only errors + a one-line final summary\r\n * - default — stage headers + per-stage summaries\r\n * - `verbose` — default plus per-file render/write logs\r\n *\r\n * All output goes through the injected `stdout` / `stderr` sinks, never\r\n * directly to `process.stdout`, so tests can capture it without stubbing.\r\n */\r\n\r\nimport type { FilePlan } from './plan/builder.js';\r\nimport type { Options } from './schema/options.js';\r\n\r\nexport type Verbosity = 'quiet' | 'default' | 'verbose';\r\n\r\nexport interface ReporterIO {\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n}\r\n\r\nexport class Reporter {\r\n constructor(\r\n private readonly io: ReporterIO,\r\n private readonly verbosity: Verbosity,\r\n ) {}\r\n\r\n stage(label: string): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(`\\n─── ${label} ───`);\r\n }\r\n\r\n info(line: string): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(line);\r\n }\r\n\r\n verbose(line: string): void {\r\n if (this.verbosity !== 'verbose') return;\r\n this.io.stdout(line);\r\n }\r\n\r\n options(options: Options): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(` projectName: ${options.projectName}`);\r\n this.io.stdout(` backend: ${options.backend}`);\r\n this.io.stdout(` frontend: ${options.frontend}`);\r\n this.io.stdout(` mobile: ${options.mobile}`);\r\n this.io.stdout(` miniapp: ${options.miniapp}`);\r\n this.io.stdout(` agent: ${options.agent}`);\r\n this.io.stdout(` deploy: ${options.deploy}`);\r\n this.io.stdout(` contract: ${options.contract}`);\r\n this.io.stdout(` ai: ${options.ai}`);\r\n this.io.stdout(` license: ${options.license}`);\r\n this.io.stdout(` gitLfs: ${String(options.gitLfs)}`);\r\n this.io.stdout(` integrations: ${String(options.integrations)}`);\r\n this.io.stdout(` ci: ${options.ci}`);\r\n this.io.stdout(` roles: ${options.roles.length > 0 ? options.roles.join(',') : '<none>'}`);\r\n }\r\n\r\n dryRunReport(plan: FilePlan): void {\r\n this.io.stdout('\\n[dry-run] would create the following files:');\r\n for (const f of plan.files) {\r\n const bytes = Buffer.byteLength(f.content, 'utf8');\r\n this.io.stdout(` ${f.targetPath} (${bytes} bytes, from ${f.contributedBy})`);\r\n }\r\n this.io.stdout(`\\n[dry-run] total: ${plan.files.length} file(s)`);\r\n }\r\n\r\n writeProgress(file: { targetPath: string; contributedBy: string; content: string }): void {\r\n if (this.verbosity !== 'verbose') return;\r\n const bytes = Buffer.byteLength(file.content, 'utf8');\r\n this.io.stdout(` wrote ${file.targetPath} (${bytes}B, from ${file.contributedBy})`);\r\n }\r\n\r\n summary(kind: 'created' | 'dry-run', fileCount: number, targetDir: string): void {\r\n // Always emit, even in quiet.\r\n if (kind === 'created') {\r\n this.io.stdout(`✓ created ${fileCount} file(s) at ${targetDir}`);\r\n } else {\r\n this.io.stdout(`✓ dry-run: ${fileCount} file(s) would be created at ${targetDir}`);\r\n }\r\n }\r\n}\r\n\r\nexport function verbosityFromFlags(flags: { quiet?: boolean; verbose?: boolean }): Verbosity {\r\n if (flags.quiet && flags.verbose) return 'verbose';\r\n if (flags.quiet) return 'quiet';\r\n if (flags.verbose) return 'verbose';\r\n return 'default';\r\n}\r\n","/**\r\n * Binary entry point.\r\n *\r\n * Thin wiring: hook stdin/stdout/stderr and process.argv into run(),\r\n * delegate `create` to the orchestrator, and translate the returned\r\n * exit code into process.exit.\r\n */\r\n\r\nimport { run, type CreateCommandInput } from './cli.js';\r\nimport { runCreate } from './orchestrator.js';\r\n\r\nasync function handleCreate(input: CreateCommandInput): Promise<void> {\r\n await runCreate({\r\n projectName: input.projectName,\r\n flags: input.flags,\r\n io: {\r\n stdout: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.log(line);\r\n },\r\n stderr: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.error(line);\r\n },\r\n isTTY: Boolean(process.stdout.isTTY),\r\n },\r\n });\r\n}\r\n\r\nconst exitCode = await run({\r\n argv: process.argv,\r\n stdout: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.log(line);\r\n },\r\n stderr: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.error(line);\r\n },\r\n onCreate: handleCreate,\r\n});\r\n\r\nprocess.exit(exitCode);\r\n"],"mappings":"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","names":["path","Ajv","parseYaml","ajv","Ajv","path","parseYaml","validate","fs","path","Ajv","parseYaml","ajv","Ajv","validateFn","collectIssues","parseYaml","fs","fs","path","fs","fs","fs","parseYaml","parseYaml","Ajv","addFormats","SEMVER_PATTERN","FRAGMENT_NAME_PATTERN","ajv","Ajv","addFormats","validateFn","fs","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/schema/options.ts","../src/errors.ts","../src/cli.ts","../src/orchestrator.ts","../src/input/defaults.ts","../src/input/config.ts","../src/input/prompter.ts","../src/fragments/loader.ts","../src/schema/manifest.ts","../src/fragments/resolver.ts","../src/render/context.ts","../src/plan/builder.ts","../src/render/renderer.ts","../src/plan/syntax.ts","../src/schema/metadata.ts","../src/plan/metadata.ts","../src/writer/writer.ts","../src/reporter.ts","../src/index.ts"],"sourcesContent":["/**\r\n * The Scaffolder's own SemVer. Kept in a dedicated module so that the CLI,\r\n * orchestrator, and metadata writer can import a single source of truth\r\n * without creating a dependency cycle through index.ts.\r\n */\r\nexport const SCAFFOLDER_VERSION = '0.1.2';\r\n","/**\r\n * Options schema and (de)serialization for the Scaffolder.\r\n *\r\n * Single source of truth for:\r\n * - Interactive prompt value constraints\r\n * - --config file validation\r\n * - Internal TypeScript types\r\n *\r\n * Round-trip invariant (Property 4): parseOptions(serializeOptions(o)) deepEquals o.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport addFormats from 'ajv-formats';\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Value domains\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const BACKEND_LANGUAGES = ['node', 'python', 'go', 'java', 'none'] as const;\r\nexport type BackendLanguage = (typeof BACKEND_LANGUAGES)[number];\r\n\r\nexport const FRONTEND_FRAMEWORKS = ['react', 'vue', 'none'] as const;\r\nexport type FrontendFramework = (typeof FRONTEND_FRAMEWORKS)[number];\r\n\r\nexport const AGENT_KINDS = ['rust-desktop', 'none'] as const;\r\nexport type AgentKind = (typeof AGENT_KINDS)[number];\r\n\r\nexport const MOBILE_KINDS = ['react-native', 'flutter', 'none'] as const;\r\nexport type MobileKind = (typeof MOBILE_KINDS)[number];\r\n\r\nexport const MINIAPP_KINDS = ['wechat', 'none'] as const;\r\nexport type MiniappKind = (typeof MINIAPP_KINDS)[number];\r\n\r\nexport const DEPLOY_TARGETS = ['docker-compose', 'kubernetes', 'bare-metal', 'none'] as const;\r\nexport type DeployTarget = (typeof DEPLOY_TARGETS)[number];\r\n\r\nexport const CONTRACT_KINDS = ['rest', 'rest+events', 'events-only', 'none'] as const;\r\nexport type ContractKind = (typeof CONTRACT_KINDS)[number];\r\n\r\nexport const AI_TOOLS = ['kiro', 'cursor', 'claude-code', 'codex', 'none'] as const;\r\nexport type AiTool = (typeof AI_TOOLS)[number];\r\n\r\nexport const LICENSE_KINDS = ['mit', 'apache-2.0', 'proprietary'] as const;\r\nexport type LicenseKind = (typeof LICENSE_KINDS)[number];\r\n\r\nexport const CI_PLATFORMS = ['gitee', 'github'] as const;\r\nexport type CiPlatform = (typeof CI_PLATFORMS)[number];\r\n\r\nexport const ROLE_KINDS = ['design', 'qa', 'field', 'data', 'legal-security', 'marketing'] as const;\r\nexport type Role = (typeof ROLE_KINDS)[number];\r\n\r\nexport const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;\r\nexport const PROJECT_NAME_PATTERN_SOURCE = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Options type\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface Options {\r\n readonly projectName: string;\r\n readonly backend: BackendLanguage;\r\n readonly frontend: FrontendFramework;\r\n readonly mobile: MobileKind;\r\n readonly miniapp: MiniappKind;\r\n readonly agent: AgentKind;\r\n readonly deploy: DeployTarget;\r\n readonly contract: ContractKind;\r\n readonly ai: AiTool;\r\n readonly license: LicenseKind;\r\n readonly gitLfs: boolean;\r\n readonly integrations: boolean;\r\n readonly ci: CiPlatform;\r\n readonly roles: readonly Role[];\r\n}\r\n\r\n/**\r\n * Stable field order for serialization; round-trip relies on this being\r\n * consistent with JSON Schema's additionalProperties: false.\r\n */\r\nexport const OPTIONS_FIELD_ORDER: readonly (keyof Options)[] = [\r\n 'projectName',\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'roles',\r\n];\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema (Draft-07)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const OPTIONS_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/options.json',\r\n title: 'ScaffolderOptions',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: [\r\n 'projectName',\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'roles',\r\n ],\r\n properties: {\r\n projectName: {\r\n type: 'string',\r\n pattern: PROJECT_NAME_PATTERN_SOURCE,\r\n },\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n roles: {\r\n type: 'array',\r\n items: { type: 'string', enum: [...ROLE_KINDS] },\r\n uniqueItems: true,\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\naddFormats(ajv);\r\nconst validateFn = ajv.compile<Options>(OPTIONS_SCHEMA);\r\n\r\nexport interface OptionsValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class OptionsValidationError extends Error {\r\n readonly issues: readonly OptionsValidationIssue[];\r\n\r\n constructor(issues: readonly OptionsValidationIssue[]) {\r\n super(\r\n `Invalid Scaffolder options:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'OptionsValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): OptionsValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path: e.instancePath || (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\n/**\r\n * Assert that an unknown value is a valid Options, throwing otherwise.\r\n * Returns the same value narrowed to Options on success.\r\n */\r\nexport function assertOptions(value: unknown): Options {\r\n if (!validateFn(value)) {\r\n throw new OptionsValidationError(collectIssues());\r\n }\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parse / serialize (round-trip)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a JSON or YAML string into Options.\r\n * Accepts either format; detection is by trying JSON first, then YAML.\r\n */\r\nexport function parseOptions(input: string): Options {\r\n let raw: unknown;\r\n const trimmed = input.trim();\r\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\r\n try {\r\n raw = JSON.parse(trimmed);\r\n } catch (e) {\r\n throw new OptionsValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n } else {\r\n try {\r\n raw = parseYaml(input);\r\n } catch (e) {\r\n throw new OptionsValidationError([\r\n { path: '', message: `invalid YAML: ${(e as Error).message}` },\r\n ]);\r\n }\r\n }\r\n return assertOptions(raw);\r\n}\r\n\r\n/**\r\n * Serialize Options to a canonical JSON string.\r\n * Field order is fixed by OPTIONS_FIELD_ORDER to guarantee byte-stable output.\r\n */\r\nexport function serializeOptions(options: Options): string {\r\n const ordered: Record<string, unknown> = {};\r\n for (const key of OPTIONS_FIELD_ORDER) {\r\n ordered[key] = options[key];\r\n }\r\n return JSON.stringify(ordered, null, 2);\r\n}\r\n\r\n/**\r\n * Return the default Options (projectName must still be supplied by the caller).\r\n */\r\nexport function defaultOptions(projectName: string): Options {\r\n return {\r\n projectName,\r\n backend: 'node',\r\n frontend: 'react',\r\n mobile: 'none',\r\n miniapp: 'none',\r\n agent: 'none',\r\n deploy: 'docker-compose',\r\n contract: 'rest',\r\n ai: 'kiro',\r\n license: 'mit',\r\n gitLfs: false,\r\n integrations: false,\r\n ci: 'gitee',\r\n roles: [],\r\n };\r\n}\r\n","/**\r\n * Error taxonomy and exit code mapping for the Scaffolder.\r\n *\r\n * The three categories below partition every failure path into exactly one\r\n * exit code, keeping behavior stable for downstream automation.\r\n * - UserInputError → exit code 1\r\n * - FilesystemError → exit code 2\r\n * - TemplateError → exit code 3\r\n */\r\n\r\nexport type ExitCode = 0 | 1 | 2 | 3;\r\n\r\nexport interface ThreeLinePayload {\r\n readonly title: string;\r\n readonly message: string;\r\n readonly suggestion: string;\r\n}\r\n\r\nexport abstract class ScaffolderError extends Error {\r\n abstract readonly exitCode: ExitCode;\r\n abstract readonly title: string;\r\n readonly suggestion: string;\r\n\r\n constructor(message: string, suggestion: string) {\r\n super(message);\r\n this.name = new.target.name;\r\n this.suggestion = suggestion;\r\n }\r\n\r\n toThreeLine(): ThreeLinePayload {\r\n return { title: this.title, message: this.message, suggestion: this.suggestion };\r\n }\r\n}\r\n\r\nexport class UserInputError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 1;\r\n readonly title = 'UserInputError';\r\n}\r\n\r\nexport class FilesystemError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 2;\r\n readonly title = 'FilesystemError';\r\n}\r\n\r\nexport class TemplateError extends ScaffolderError {\r\n readonly exitCode: ExitCode = 3;\r\n readonly title = 'TemplateError';\r\n}\r\n\r\n/**\r\n * Format a ScaffolderError as the canonical three-line terminal output\r\n * (§15.5). Non-Scaffolder errors fall through to a single line.\r\n */\r\nexport function formatError(err: unknown): string {\r\n if (err instanceof ScaffolderError) {\r\n const { title, message, suggestion } = err.toThreeLine();\r\n return `✗ ${title}\\n ${message}\\n 建议:${suggestion}`;\r\n }\r\n if (err instanceof Error) {\r\n return `✗ UnexpectedError\\n ${err.message}\\n 建议:请在 issue 中附上完整堆栈`;\r\n }\r\n return `✗ UnexpectedError\\n ${String(err)}\\n 建议:请在 issue 中附上完整上下文`;\r\n}\r\n","/**\r\n * CLI entry routing.\r\n *\r\n * A hand-rolled parser is used rather than cac's built-in --version / --help\r\n * handling, because cac writes those outputs directly to stdout and swallows\r\n * the opportunity to route them through our test-friendly CliIO sink.\r\n *\r\n * This module owns:\r\n * - subcommand dispatch (`create`; `--help` / `--version` short-circuit)\r\n * - global flag definitions\r\n * - projectName pattern validation (R1.5)\r\n * - mapping ScaffolderError → exit code\r\n */\r\n\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\nimport { PROJECT_NAME_PATTERN, ROLE_KINDS, type Role } from './schema/options.js';\r\nimport { ScaffolderError, UserInputError, formatError, type ExitCode } from './errors.js';\r\n\r\nexport interface CliFlags {\r\n readonly yes: boolean;\r\n readonly force: boolean;\r\n readonly dryRun: boolean;\r\n readonly quiet: boolean;\r\n readonly verbose: boolean;\r\n readonly config?: string;\r\n readonly ci?: 'gitee' | 'github';\r\n readonly roles?: readonly Role[];\r\n}\r\n\r\nexport interface CreateCommandInput {\r\n readonly projectName: string;\r\n readonly flags: CliFlags;\r\n}\r\n\r\nexport type CreateHandler = (input: CreateCommandInput) => Promise<void>;\r\n\r\nexport interface CliIO {\r\n readonly argv: readonly string[];\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n readonly onCreate: CreateHandler;\r\n}\r\n\r\n/**\r\n * Parse and dispatch. Returns the exit code the process should terminate with.\r\n * Never calls process.exit directly; the concrete binary is responsible for\r\n * that so tests can run `run()` repeatedly in-process.\r\n *\r\n * `argv` is expected to follow the Node convention: `[node, script, ...args]`.\r\n */\r\nexport async function run(io: CliIO): Promise<ExitCode> {\r\n const args = io.argv.slice(2);\r\n\r\n // Short-circuit for help / version before any subcommand validation, so\r\n // that `--help` alone always succeeds.\r\n if (args.some((a) => a === '-h' || a === '--help')) {\r\n io.stdout(helpText());\r\n return 0;\r\n }\r\n if (args.some((a) => a === '-v' || a === '--version')) {\r\n io.stdout(SCAFFOLDER_VERSION);\r\n return 0;\r\n }\r\n\r\n if (args.length === 0) {\r\n io.stdout(helpText());\r\n return 0;\r\n }\r\n\r\n const [command, ...rest] = args;\r\n if (command !== 'create') {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `unknown command: ${JSON.stringify(command)}`,\r\n '可用的命令:create。查看帮助:--help',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n try {\r\n const { projectName, flags } = parseCreateArgs(rest);\r\n await io.onCreate({ projectName, flags });\r\n return 0;\r\n } catch (err) {\r\n io.stderr(formatError(err));\r\n if (err instanceof ScaffolderError) {\r\n return err.exitCode;\r\n }\r\n return 1;\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parsing\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedCreateArgs {\r\n projectName: string;\r\n flags: CliFlags;\r\n}\r\n\r\nfunction parseCreateArgs(args: readonly string[]): ParsedCreateArgs {\r\n let projectName: string | undefined;\r\n let yes = false;\r\n let force = false;\r\n let dryRun = false;\r\n let quiet = false;\r\n let verbose = false;\r\n let config: string | undefined;\r\n let ci: 'gitee' | 'github' | undefined;\r\n let roles: readonly Role[] | undefined;\r\n\r\n for (let i = 0; i < args.length; i += 1) {\r\n const a = args[i]!;\r\n switch (a) {\r\n case '--yes':\r\n yes = true;\r\n break;\r\n case '--force':\r\n force = true;\r\n break;\r\n case '--dry-run':\r\n dryRun = true;\r\n break;\r\n case '--quiet':\r\n quiet = true;\r\n break;\r\n case '--verbose':\r\n verbose = true;\r\n break;\r\n case '--config': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--config requires a path argument',\r\n '示例:--config ./scaffolder.yaml',\r\n );\r\n }\r\n config = next;\r\n i += 1;\r\n break;\r\n }\r\n case '--ci': {\r\n const next = args[i + 1];\r\n if (next !== 'gitee' && next !== 'github') {\r\n throw new UserInputError(\r\n `invalid --ci value: ${JSON.stringify(next ?? '')}`,\r\n '--ci 只接受 gitee 或 github',\r\n );\r\n }\r\n ci = next;\r\n i += 1;\r\n break;\r\n }\r\n case '--roles': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--roles requires a comma-separated list',\r\n '示例:--roles qa,field,marketing;可选值:' + ROLE_KINDS.join(','),\r\n );\r\n }\r\n const parts = next\r\n .split(',')\r\n .map((s) => s.trim())\r\n .filter((s) => s.length > 0);\r\n const seen = new Set<string>();\r\n for (const part of parts) {\r\n if (!(ROLE_KINDS as readonly string[]).includes(part)) {\r\n throw new UserInputError(\r\n `invalid --roles value: ${JSON.stringify(part)}`,\r\n '可选角色:' + ROLE_KINDS.join(','),\r\n );\r\n }\r\n if (seen.has(part)) {\r\n throw new UserInputError(\r\n `duplicate role: ${part}`,\r\n '--roles 中每个角色仅能出现一次',\r\n );\r\n }\r\n seen.add(part);\r\n }\r\n roles = parts as readonly Role[];\r\n i += 1;\r\n break;\r\n }\r\n default: {\r\n if (a.startsWith('--')) {\r\n throw new UserInputError(\r\n `unknown option: ${a}`,\r\n '查看可用选项:create-keel --help',\r\n );\r\n }\r\n if (projectName === undefined) {\r\n projectName = a;\r\n } else {\r\n throw new UserInputError(\r\n `unexpected extra argument: ${JSON.stringify(a)}`,\r\n '每次只能创建一个项目',\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (projectName === undefined) {\r\n // In task 4 the orchestrator will replace this with an interactive\r\n // prompt. For now treat the missing name as a user error so the CLI\r\n // layer stays self-contained.\r\n throw new UserInputError(\r\n 'missing <project-name>',\r\n '请提供项目名,例如:npx @wneng/create-keel create my-app',\r\n );\r\n }\r\n if (!PROJECT_NAME_PATTERN.test(projectName)) {\r\n throw new UserInputError(\r\n `invalid project name: ${JSON.stringify(projectName)}`,\r\n '项目名应匹配 ^[a-z0-9][a-z0-9-]{0,62}$,例如:my-app',\r\n );\r\n }\r\n\r\n const flags: CliFlags = {\r\n yes,\r\n force,\r\n dryRun,\r\n quiet,\r\n verbose,\r\n ...(config !== undefined ? { config } : {}),\r\n ...(ci !== undefined ? { ci } : {}),\r\n ...(roles !== undefined ? { roles } : {}),\r\n };\r\n\r\n return { projectName, flags };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Help\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction helpText(): string {\r\n return [\r\n 'create-keel — scaffold a Contract-First project',\r\n '',\r\n 'USAGE',\r\n ' npx @wneng/create-keel create <project-name> [options]',\r\n '',\r\n 'OPTIONS',\r\n ' --yes Skip interactive prompts and use defaults',\r\n ' --force Overwrite a non-empty target directory after confirmation',\r\n ' --dry-run Compute the plan without writing any files',\r\n ' --quiet Only emit errors and the final summary',\r\n ' --verbose Emit per-file render/write logs',\r\n ' --config <path> Read options from a JSON or YAML file',\r\n ' --ci <platform> Which CI platform to scaffold (gitee|github, default: gitee)',\r\n ' --roles <list> Comma-separated role directories to scaffold',\r\n ' (' + ROLE_KINDS.join('|') + '; default: none)',\r\n ' -h, --help Show this help',\r\n ' -v, --version Show the scaffolder version',\r\n '',\r\n 'See .kiro/specs/project-scaffolder/ for the full specification.',\r\n ].join('\\n');\r\n}\r\n","/**\r\n * Orchestrator — drives the full create flow end-to-end:\r\n *\r\n * CLI args\r\n * ↓\r\n * Defaults + ConfigFile + CLI flags + Prompter\r\n * ↓ (merged with precedence: CLI > config > interactive > defaults)\r\n * Validated Options\r\n * ↓\r\n * Fragment loader + resolver\r\n * ↓\r\n * Template renderer → File plan builder\r\n * ↓\r\n * Syntax validator\r\n * ↓\r\n * Writer (with rollback) or DryRun reporter\r\n * ↓\r\n * .scaffolder.json metadata (always as part of the same atomic write)\r\n */\r\n\r\nimport * as path from 'node:path';\r\n\r\nimport { UserInputError } from './errors.js';\r\nimport type { CliFlags } from './cli.js';\r\nimport { buildDefaultOptions } from './input/defaults.js';\r\nimport { loadConfigFile, type PartialOptions } from './input/config.js';\r\nimport {\r\n InquirerPrompter,\r\n promptForOptions,\r\n type Prompter,\r\n} from './input/prompter.js';\r\nimport { assertOptions, type Options } from './schema/options.js';\r\nimport { loadFragments } from './fragments/loader.js';\r\nimport { selectFragments } from './fragments/resolver.js';\r\nimport { buildRenderContext } from './render/context.js';\r\nimport { buildFilePlan } from './plan/builder.js';\r\nimport { validatePlanSyntax } from './plan/syntax.js';\r\nimport { appendScaffolderMetadata } from './plan/metadata.js';\r\nimport { writePlan } from './writer/writer.js';\r\nimport { Reporter, verbosityFromFlags } from './reporter.js';\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\nimport type { TemplateFragmentRef } from './schema/metadata.js';\r\n\r\nexport interface OrchestratorIO {\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n readonly isTTY: boolean;\r\n /** Injected for tests; production uses InquirerPrompter. */\r\n readonly prompter?: Prompter;\r\n /** Directory to write into; defaults to `./<projectName>` under cwd. */\r\n readonly cwd?: string;\r\n /** Templates root override (for tests). */\r\n readonly templatesRoot?: string;\r\n /** Frozen clock, for deterministic tests. */\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface OrchestratorInput {\r\n readonly projectName: string;\r\n readonly flags: CliFlags;\r\n readonly io: OrchestratorIO;\r\n}\r\n\r\nexport interface OrchestratorResult {\r\n readonly options: Options;\r\n readonly targetDirectory: string;\r\n readonly fileCount: number;\r\n readonly dryRun: boolean;\r\n}\r\n\r\nexport async function runCreate(input: OrchestratorInput): Promise<OrchestratorResult> {\r\n const reporter = new Reporter(\r\n { stdout: input.io.stdout, stderr: input.io.stderr },\r\n verbosityFromFlags(input.flags),\r\n );\r\n\r\n reporter.stage('options');\r\n const options = await resolveOptions(input);\r\n reporter.options(options);\r\n\r\n reporter.stage('templates');\r\n const { fragments } = await loadFragments(input.io.templatesRoot);\r\n const selected = selectFragments(options, fragments);\r\n reporter.info(`selected ${selected.length} fragment(s)`);\r\n for (const f of selected) {\r\n reporter.verbose(` ${f.manifest.name}@${f.manifest.version} (priority ${f.manifest.priority})`);\r\n }\r\n\r\n reporter.stage('plan');\r\n const context = buildRenderContext({\r\n options,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n ...(input.io.now !== undefined ? { now: input.io.now } : {}),\r\n });\r\n let plan = await buildFilePlan({ fragments: selected, context });\r\n const fragmentRefs: TemplateFragmentRef[] = selected.map((f) => ({\r\n name: f.manifest.name,\r\n version: f.manifest.version,\r\n }));\r\n plan = appendScaffolderMetadata({\r\n plan,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n options,\r\n templateFragments: fragmentRefs,\r\n ...(input.io.now !== undefined ? { now: input.io.now } : {}),\r\n });\r\n validatePlanSyntax(plan);\r\n reporter.info(`planned ${plan.files.length} file(s) across ${plan.directories.length} directory`);\r\n\r\n const cwd = input.io.cwd ?? process.cwd();\r\n const targetDirectory = path.resolve(cwd, options.projectName);\r\n\r\n if (input.flags.dryRun) {\r\n reporter.stage('dry-run');\r\n reporter.dryRunReport(plan);\r\n reporter.summary('dry-run', plan.files.length, targetDirectory);\r\n return { options, targetDirectory, fileCount: plan.files.length, dryRun: true };\r\n }\r\n\r\n reporter.stage('write');\r\n const result = await writePlan(plan, { targetDirectory, force: input.flags.force ?? false });\r\n for (const f of plan.files) {\r\n reporter.writeProgress(f);\r\n }\r\n reporter.info(`wrote ${result.createdFiles.length} file(s)`);\r\n reporter.summary('created', result.createdFiles.length, targetDirectory);\r\n\r\n return {\r\n options,\r\n targetDirectory,\r\n fileCount: result.createdFiles.length,\r\n dryRun: false,\r\n };\r\n}\r\n\r\n/**\r\n * Build the final Options by merging sources in order of precedence:\r\n * CLI flags > --config file > interactive prompt > defaults\r\n */\r\nexport async function resolveOptions(input: OrchestratorInput): Promise<Options> {\r\n const { projectName, flags, io } = input;\r\n\r\n let merged: PartialOptions = {};\r\n if (flags.config !== undefined) {\r\n merged = { ...merged, ...(await loadConfigFile(flags.config)) };\r\n }\r\n\r\n merged = {\r\n ...merged,\r\n projectName,\r\n ...(flags.ci !== undefined ? { ci: flags.ci } : {}),\r\n ...(flags.roles !== undefined ? { roles: flags.roles } : {}),\r\n };\r\n\r\n if (flags.yes) {\r\n const full = { ...buildDefaultOptions(projectName), ...merged };\r\n return assertOptions(full);\r\n }\r\n\r\n if (!io.isTTY) {\r\n throw new UserInputError(\r\n 'no TTY available and neither --yes nor a complete --config was provided',\r\n '在非交互环境下请使用 --yes 或提供完整的 --config',\r\n );\r\n }\r\n\r\n const prompter = io.prompter ?? new InquirerPrompter();\r\n const prompted = await promptForOptions({\r\n prompter,\r\n prefilled: merged,\r\n isTTY: io.isTTY,\r\n report: (axis, value, usedDefault) => {\r\n if (!flags.quiet && usedDefault) {\r\n io.stdout(` ${axis}: ${value} (default)`);\r\n } else if (flags.verbose) {\r\n io.stdout(` ${axis}: ${value}`);\r\n }\r\n },\r\n });\r\n\r\n return assertOptions(prompted);\r\n}\r\n","/**\r\n * DefaultsProvider — single, externally-readable source of truth for every\r\n * Option Axis default. Kept separate from `schema/options.ts` because that\r\n * file owns the *value domains* while this file owns the *policy choices*.\r\n *\r\n * These defaults drive:\r\n * - Interactive prompt preselection\r\n * - --yes behavior (pick defaults for every axis)\r\n * - Fallback for any axis missing from a --config file\r\n */\r\n\r\nimport type {\r\n AgentKind,\r\n AiTool,\r\n BackendLanguage,\r\n CiPlatform,\r\n ContractKind,\r\n DeployTarget,\r\n FrontendFramework,\r\n LicenseKind,\r\n MiniappKind,\r\n MobileKind,\r\n Options,\r\n Role,\r\n} from '../schema/options.js';\r\n\r\nexport interface OptionDefaults {\r\n readonly backend: BackendLanguage;\r\n readonly frontend: FrontendFramework;\r\n readonly mobile: MobileKind;\r\n readonly miniapp: MiniappKind;\r\n readonly agent: AgentKind;\r\n readonly deploy: DeployTarget;\r\n readonly contract: ContractKind;\r\n readonly ai: AiTool;\r\n readonly license: LicenseKind;\r\n readonly gitLfs: boolean;\r\n readonly integrations: boolean;\r\n readonly ci: CiPlatform;\r\n readonly roles: readonly Role[];\r\n}\r\n\r\n/**\r\n * Default values for every Option Axis except `projectName`, which has no\r\n * reasonable default and must always be supplied explicitly.\r\n */\r\nexport const OPTION_DEFAULTS: OptionDefaults = {\r\n backend: 'node',\r\n frontend: 'react',\r\n mobile: 'none',\r\n miniapp: 'none',\r\n agent: 'none',\r\n deploy: 'docker-compose',\r\n contract: 'rest',\r\n ai: 'kiro',\r\n license: 'mit',\r\n gitLfs: false,\r\n integrations: false,\r\n ci: 'gitee',\r\n roles: [],\r\n};\r\n\r\n/**\r\n * Build a complete Options object from defaults given a projectName.\r\n * Callers use this for `--yes` and as the fallback during prompt\r\n * cancellation.\r\n */\r\nexport function buildDefaultOptions(projectName: string): Options {\r\n return { projectName, ...OPTION_DEFAULTS };\r\n}\r\n","/**\r\n * ConfigFileLoader — load a Partial<Options> from a JSON or YAML file.\r\n *\r\n * The loader is deliberately forgiving about *what fields are present* (any\r\n * subset is OK, missing fields fall back to defaults) but strict about *the\r\n * values that are present* (each must pass the corresponding value-domain\r\n * check from OptionsSchema). This mirrors Requirement 3.5.\r\n */\r\n\r\nimport Ajv, { type ValidateFunction } from 'ajv';\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n PROJECT_NAME_PATTERN_SOURCE,\r\n ROLE_KINDS,\r\n type Options,\r\n} from '../schema/options.js';\r\n\r\nexport type PartialOptions = Partial<Options>;\r\n\r\nconst PARTIAL_OPTIONS_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/partial-options.json',\r\n title: 'PartialScaffolderOptions',\r\n type: 'object',\r\n additionalProperties: false,\r\n properties: {\r\n projectName: { type: 'string', pattern: PROJECT_NAME_PATTERN_SOURCE },\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n roles: {\r\n type: 'array',\r\n items: { type: 'string', enum: [...ROLE_KINDS] },\r\n uniqueItems: true,\r\n },\r\n },\r\n} as const;\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\nconst validate: ValidateFunction<PartialOptions> = ajv.compile<PartialOptions>(\r\n PARTIAL_OPTIONS_SCHEMA,\r\n);\r\n\r\nfunction formatIssues(): string {\r\n const errs = validate.errors ?? [];\r\n return errs\r\n .map((e) => {\r\n const path =\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : '<root>');\r\n return `${path}: ${e.message ?? 'validation error'}`;\r\n })\r\n .join('; ');\r\n}\r\n\r\n/**\r\n * Load and validate a config file. Format is decided by extension:\r\n * .json → JSON\r\n * .yaml | .yml → YAML\r\n * Anything else is rejected as a UserInputError.\r\n *\r\n * Throws UserInputError on any problem (missing file, bad extension, parse\r\n * error, schema violation). Exit code 1 per §15.2.\r\n */\r\nexport async function loadConfigFile(filePath: string): Promise<PartialOptions> {\r\n const ext = path.extname(filePath).toLowerCase();\r\n const isJson = ext === '.json';\r\n const isYaml = ext === '.yaml' || ext === '.yml';\r\n if (!isJson && !isYaml) {\r\n throw new UserInputError(\r\n `unsupported config extension: ${ext || '<none>'}`,\r\n '请使用 .json 或 .yaml / .yml 文件',\r\n );\r\n }\r\n\r\n let text: string;\r\n try {\r\n text = await fs.readFile(filePath, 'utf8');\r\n } catch (e) {\r\n throw new UserInputError(\r\n `failed to read config file: ${(e as Error).message}`,\r\n '请确认文件路径存在且可读',\r\n );\r\n }\r\n\r\n let raw: unknown;\r\n try {\r\n raw = isJson ? JSON.parse(text) : parseYaml(text);\r\n } catch (e) {\r\n throw new UserInputError(\r\n `invalid ${isJson ? 'JSON' : 'YAML'} in config: ${(e as Error).message}`,\r\n '请检查文件内容是否为合法的 JSON / YAML',\r\n );\r\n }\r\n\r\n if (raw === null || raw === undefined) {\r\n return {};\r\n }\r\n if (typeof raw !== 'object' || Array.isArray(raw)) {\r\n throw new UserInputError(\r\n 'config root must be an object',\r\n '示例:{ \"backend\": \"node\", \"frontend\": \"react\" }',\r\n );\r\n }\r\n\r\n if (!validate(raw)) {\r\n throw new UserInputError(\r\n `config file does not match schema: ${formatIssues()}`,\r\n '参考 defaults 中的字段名与取值域',\r\n );\r\n }\r\n\r\n // Strip prototype to guarantee plain data and drop undefined-valued keys.\r\n const out: Record<string, unknown> = {};\r\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\r\n if (v !== undefined) out[k] = v;\r\n }\r\n return out as PartialOptions;\r\n}\r\n\r\n/**\r\n * Parse config from an in-memory string. Convenience for tests and for\r\n * callers that already hold the file content.\r\n */\r\nexport function parseConfigText(text: string, format: 'json' | 'yaml'): PartialOptions {\r\n let raw: unknown;\r\n try {\r\n raw = format === 'json' ? JSON.parse(text) : parseYaml(text);\r\n } catch (e) {\r\n throw new UserInputError(\r\n `invalid ${format.toUpperCase()} in config: ${(e as Error).message}`,\r\n '请检查内容语法',\r\n );\r\n }\r\n if (raw === null || raw === undefined) return {};\r\n if (typeof raw !== 'object' || Array.isArray(raw)) {\r\n throw new UserInputError(\r\n 'config root must be an object',\r\n '示例:{ \"backend\": \"node\" }',\r\n );\r\n }\r\n if (!validate(raw)) {\r\n throw new UserInputError(\r\n `config does not match schema: ${formatIssues()}`,\r\n '参考 defaults 中的字段名与取值域',\r\n );\r\n }\r\n return raw as PartialOptions;\r\n}\r\n","/**\r\n * InteractivePrompter — collect missing option values from the user.\r\n *\r\n * The prompter is abstracted behind a narrow interface so tests can drive\r\n * the flow deterministically. The concrete `InquirerPrompter` delegates to\r\n * `@inquirer/prompts` at runtime.\r\n *\r\n * Design decisions:\r\n * - Each Option Axis maps to exactly one select or confirm; the order matches\r\n * design §Components.Input Sources (projectName → backend → frontend →\r\n * deploy → contract → ai → license → gitLfs → ci).\r\n * - Only axes missing from the incoming PartialOptions are prompted; already\r\n * provided values are accepted as-is (with schema already having validated\r\n * them upstream).\r\n * - If stdout is not a TTY and no --yes / --config was supplied, the caller\r\n * (orchestrator) must short-circuit before reaching the prompter. The\r\n * prompter itself throws UserInputError if invoked in that state as a\r\n * belt-and-braces check.\r\n */\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n PROJECT_NAME_PATTERN,\r\n type AgentKind,\r\n type AiTool,\r\n type BackendLanguage,\r\n type CiPlatform,\r\n type ContractKind,\r\n type DeployTarget,\r\n type FrontendFramework,\r\n type LicenseKind,\r\n type MiniappKind,\r\n type MobileKind,\r\n type Options,\r\n type Role,\r\n} from '../schema/options.js';\r\nimport { OPTION_DEFAULTS } from './defaults.js';\r\nimport type { PartialOptions } from './config.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Prompter interface\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface SelectChoice<T extends string> {\r\n readonly value: T;\r\n readonly name: string;\r\n}\r\n\r\nexport interface Prompter {\r\n input(question: string, validate: (value: string) => true | string): Promise<string>;\r\n select<T extends string>(question: string, choices: readonly SelectChoice<T>[], def: T): Promise<T>;\r\n confirm(question: string, def: boolean): Promise<boolean>;\r\n checkbox<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: readonly T[],\r\n ): Promise<readonly T[]>;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Inquirer-backed implementation\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class InquirerPrompter implements Prompter {\r\n async input(question: string, validate: (value: string) => true | string): Promise<string> {\r\n const { input } = await import('@inquirer/prompts');\r\n return input({ message: question, validate });\r\n }\r\n\r\n async select<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: T,\r\n ): Promise<T> {\r\n const { select } = await import('@inquirer/prompts');\r\n return (await select({\r\n message: question,\r\n choices: choices.map((c) => ({ name: c.name, value: c.value })),\r\n default: def,\r\n })) as T;\r\n }\r\n\r\n async confirm(question: string, def: boolean): Promise<boolean> {\r\n const { confirm } = await import('@inquirer/prompts');\r\n return confirm({ message: question, default: def });\r\n }\r\n\r\n async checkbox<T extends string>(\r\n question: string,\r\n choices: readonly SelectChoice<T>[],\r\n def: readonly T[],\r\n ): Promise<readonly T[]> {\r\n const { checkbox } = await import('@inquirer/prompts');\r\n const defSet = new Set(def);\r\n return (await checkbox({\r\n message: question,\r\n choices: choices.map((c) => ({\r\n name: c.name,\r\n value: c.value,\r\n checked: defSet.has(c.value),\r\n })),\r\n })) as readonly T[];\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Orchestrating prompt sequence\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface PromptOptionsInput {\r\n readonly prompter: Prompter;\r\n readonly prefilled: PartialOptions;\r\n readonly isTTY: boolean;\r\n readonly report?: ((axis: string, value: string, usedDefault: boolean) => void) | undefined;\r\n}\r\n\r\nfunction stringChoices<T extends string>(values: readonly T[]): SelectChoice<T>[] {\r\n return values.map((v) => ({ value: v, name: v }));\r\n}\r\n\r\nexport async function promptForOptions(input: PromptOptionsInput): Promise<Options> {\r\n if (!input.isTTY) {\r\n throw new UserInputError(\r\n 'interactive prompts require a TTY',\r\n '请在交互终端中运行,或提供 --yes / --config',\r\n );\r\n }\r\n\r\n const p = input.prompter;\r\n const pre = input.prefilled;\r\n const report = input.report ?? (() => {});\r\n\r\n const projectName = pre.projectName ?? (await p.input('项目名', (v) => {\r\n if (!PROJECT_NAME_PATTERN.test(v)) {\r\n return '项目名应匹配 ^[a-z0-9][a-z0-9-]{0,62}$';\r\n }\r\n return true;\r\n }));\r\n report('projectName', projectName, false);\r\n\r\n const backend = await chooseAxis<BackendLanguage>(\r\n p,\r\n pre.backend,\r\n '后端语言',\r\n BACKEND_LANGUAGES,\r\n OPTION_DEFAULTS.backend,\r\n report,\r\n 'backend',\r\n );\r\n\r\n const frontend = await chooseAxis<FrontendFramework>(\r\n p,\r\n pre.frontend,\r\n '前端框架',\r\n FRONTEND_FRAMEWORKS,\r\n OPTION_DEFAULTS.frontend,\r\n report,\r\n 'frontend',\r\n );\r\n\r\n const mobile = await chooseAxis<MobileKind>(\r\n p,\r\n pre.mobile,\r\n '移动端(手机原生)',\r\n MOBILE_KINDS,\r\n OPTION_DEFAULTS.mobile,\r\n report,\r\n 'mobile',\r\n );\r\n\r\n const miniapp = await chooseAxis<MiniappKind>(\r\n p,\r\n pre.miniapp,\r\n '小程序',\r\n MINIAPP_KINDS,\r\n OPTION_DEFAULTS.miniapp,\r\n report,\r\n 'miniapp',\r\n );\r\n\r\n const agent = await chooseAxis<AgentKind>(\r\n p,\r\n pre.agent,\r\n '终端/桌面客户端',\r\n AGENT_KINDS,\r\n OPTION_DEFAULTS.agent,\r\n report,\r\n 'agent',\r\n );\r\n\r\n const deploy = await chooseAxis<DeployTarget>(\r\n p,\r\n pre.deploy,\r\n '部署目标',\r\n DEPLOY_TARGETS,\r\n OPTION_DEFAULTS.deploy,\r\n report,\r\n 'deploy',\r\n );\r\n\r\n const contract = await chooseAxis<ContractKind>(\r\n p,\r\n pre.contract,\r\n '契约类型',\r\n CONTRACT_KINDS,\r\n OPTION_DEFAULTS.contract,\r\n report,\r\n 'contract',\r\n );\r\n\r\n const ai = await chooseAxis<AiTool>(\r\n p,\r\n pre.ai,\r\n 'AI 工具集成',\r\n AI_TOOLS,\r\n OPTION_DEFAULTS.ai,\r\n report,\r\n 'ai',\r\n );\r\n\r\n const license = await chooseAxis<LicenseKind>(\r\n p,\r\n pre.license,\r\n '许可证',\r\n LICENSE_KINDS,\r\n OPTION_DEFAULTS.license,\r\n report,\r\n 'license',\r\n );\r\n\r\n const gitLfs =\r\n pre.gitLfs ?? (await p.confirm('启用 Git LFS 管理大文件', OPTION_DEFAULTS.gitLfs));\r\n report('gitLfs', String(gitLfs), pre.gitLfs === undefined);\r\n\r\n const integrations =\r\n pre.integrations ??\r\n (await p.confirm(\r\n '生成 docs/06-集成对接/ 骨架(当本仓库的某一端需要与外部仓库对接时启用)',\r\n OPTION_DEFAULTS.integrations,\r\n ));\r\n report('integrations', String(integrations), pre.integrations === undefined);\r\n\r\n const ci = await chooseAxis<CiPlatform>(\r\n p,\r\n pre.ci,\r\n 'CI 平台',\r\n CI_PLATFORMS,\r\n OPTION_DEFAULTS.ci,\r\n report,\r\n 'ci',\r\n );\r\n\r\n let roles: readonly Role[];\r\n if (pre.roles !== undefined) {\r\n roles = pre.roles;\r\n report('roles', roles.length > 0 ? roles.join(',') : '<none>', false);\r\n } else {\r\n const roleChoices: SelectChoice<Role>[] = [\r\n { value: 'design', name: 'design UI / UX 设计资源' },\r\n { value: 'qa', name: 'qa 测试计划 / 用例 / 缺陷库' },\r\n { value: 'field', name: 'field 客户部署手册 / 上线作业书' },\r\n { value: 'data', name: 'data 埋点契约 / 看板 / 数据资产' },\r\n { value: 'legal-security', name: 'legal-security 合规清单 / PIA / 安全评估' },\r\n { value: 'marketing', name: 'marketing 产品介绍 / 白皮书 / 案例' },\r\n ];\r\n roles = await p.checkbox<Role>(\r\n '启用哪些角色目录?(空格切换;回车确认)',\r\n roleChoices,\r\n OPTION_DEFAULTS.roles,\r\n );\r\n report('roles', roles.length > 0 ? roles.join(',') : '<none>', roles.length === 0);\r\n }\r\n\r\n return {\r\n projectName,\r\n backend,\r\n frontend,\r\n mobile,\r\n miniapp,\r\n agent,\r\n deploy,\r\n contract,\r\n ai,\r\n license,\r\n gitLfs,\r\n integrations,\r\n ci,\r\n roles,\r\n };\r\n}\r\n\r\nasync function chooseAxis<T extends string>(\r\n prompter: Prompter,\r\n prefilled: T | undefined,\r\n question: string,\r\n domain: readonly T[],\r\n def: T,\r\n report: (axis: string, value: string, usedDefault: boolean) => void,\r\n axisName: string,\r\n): Promise<T> {\r\n if (prefilled !== undefined) {\r\n report(axisName, prefilled, false);\r\n return prefilled;\r\n }\r\n const value = await prompter.select<T>(question, stringChoices(domain), def);\r\n report(axisName, value, false);\r\n return value;\r\n}\r\n","/**\r\n * Fragment loader — scans `src/templates/*` and returns the loaded, validated\r\n * TemplateManifests along with a canonical templates root directory.\r\n *\r\n * A fragment directory is laid out as:\r\n *\r\n * <templatesRoot>/<fragmentDir>/\r\n * fragment.yaml ← TemplateManifest\r\n * files/ ← source files referenced by manifest.files[].from\r\n *\r\n * The loader performs:\r\n * 1. directory discovery\r\n * 2. YAML parse + schema assertion (delegated to parseManifest)\r\n * 3. invariant checks: fragment name uniqueness across the whole set,\r\n * and that every `files[].from` exists on disk\r\n *\r\n * Any violation is a TemplateError (exit code 3) because it is an authoring\r\n * bug in the scaffolder itself, not user input.\r\n */\r\n\r\nimport { promises as fs, accessSync } from 'node:fs';\r\nimport * as path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport { ManifestValidationError, parseManifest, type TemplateManifest } from '../schema/manifest.js';\r\n\r\nexport interface LoadedFragment {\r\n readonly manifest: TemplateManifest;\r\n /** Absolute path to the fragment directory on disk. */\r\n readonly rootDir: string;\r\n}\r\n\r\nexport interface LoadFragmentsResult {\r\n readonly templatesRoot: string;\r\n readonly fragments: readonly LoadedFragment[];\r\n}\r\n\r\n/**\r\n * Resolve the default templates root relative to this source module.\r\n *\r\n * The loader walks upward from the current file until it finds the\r\n * directory containing `package.json`. Templates are expected at\r\n * `<pkg>/src/templates` (kept there in source *and* in the published\r\n * tarball via `package.json#files`), so the same resolution works in\r\n * development (running `src/...` via ts) and in the shipped `dist`\r\n * bundle.\r\n */\r\nexport function defaultTemplatesRoot(): string {\r\n const here = path.dirname(fileURLToPath(import.meta.url));\r\n const pkgRoot = findPackageRoot(here);\r\n return path.join(pkgRoot, 'src', 'templates');\r\n}\r\n\r\nfunction findPackageRoot(start: string): string {\r\n let dir = start;\r\n // Bound the walk to avoid infinite loops on weird filesystems.\r\n for (let i = 0; i < 10; i += 1) {\r\n const candidate = path.join(dir, 'package.json');\r\n try {\r\n accessSync(candidate);\r\n return dir;\r\n } catch {\r\n // fall through\r\n }\r\n const parent = path.dirname(dir);\r\n if (parent === dir) break;\r\n dir = parent;\r\n }\r\n throw new TemplateError(\r\n `unable to locate package.json above ${start}`,\r\n '请确认 scaffolder 作为 npm 包安装,src/templates/ 与 package.json 位于同一层级',\r\n );\r\n}\r\n\r\nexport async function loadFragments(rootOverride?: string): Promise<LoadFragmentsResult> {\r\n const templatesRoot = rootOverride ?? defaultTemplatesRoot();\r\n\r\n let entries: string[];\r\n try {\r\n const dirents = await fs.readdir(templatesRoot, { withFileTypes: true });\r\n entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `failed to read templates directory ${templatesRoot}: ${(e as Error).message}`,\r\n '请确认 scaffolder 安装完整(src/templates/ 存在)',\r\n );\r\n }\r\n\r\n const loaded: LoadedFragment[] = [];\r\n const seenNames = new Set<string>();\r\n\r\n for (const dir of entries.sort()) {\r\n const fragmentDir = path.join(templatesRoot, dir);\r\n const manifestPath = path.join(fragmentDir, 'fragment.yaml');\r\n\r\n let text: string;\r\n try {\r\n text = await fs.readFile(manifestPath, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `fragment \"${dir}\" is missing fragment.yaml: ${(e as Error).message}`,\r\n '每个 fragment 目录必须包含 fragment.yaml',\r\n );\r\n }\r\n\r\n let manifest: TemplateManifest;\r\n try {\r\n manifest = parseManifest(text);\r\n } catch (e) {\r\n if (e instanceof ManifestValidationError) {\r\n throw new TemplateError(\r\n `fragment \"${dir}\" has invalid manifest:\\n${e.message}`,\r\n '按 fragment.yaml schema 修复字段',\r\n );\r\n }\r\n throw e;\r\n }\r\n\r\n if (manifest.name !== dir) {\r\n throw new TemplateError(\r\n `fragment directory \"${dir}\" declares name \"${manifest.name}\"`,\r\n '目录名与 manifest.name 必须一致,便于静态索引',\r\n );\r\n }\r\n if (seenNames.has(manifest.name)) {\r\n throw new TemplateError(\r\n `duplicate fragment name: ${manifest.name}`,\r\n '每个 fragment 名称在全局必须唯一',\r\n );\r\n }\r\n seenNames.add(manifest.name);\r\n\r\n // Verify every `from` path actually exists. `to` paths are relative to\r\n // the target directory and are checked at plan build time.\r\n for (const file of manifest.files) {\r\n const fromAbs = path.join(fragmentDir, file.from);\r\n try {\r\n await fs.access(fromAbs);\r\n } catch {\r\n throw new TemplateError(\r\n `fragment \"${manifest.name}\" references missing file: ${file.from}`,\r\n '请确认 files/ 中包含该文件,或修正 manifest.files[].from',\r\n );\r\n }\r\n }\r\n\r\n loaded.push({ manifest, rootDir: fragmentDir });\r\n }\r\n\r\n return { templatesRoot, fragments: loaded };\r\n}\r\n","/**\r\n * TemplateManifest schema and (de)serialization.\r\n *\r\n * A manifest describes one Fragment: how it is selected (appliesWhen),\r\n * its priority relative to others, and the files it contributes.\r\n *\r\n * Round-trip invariant (Property 5): parseManifest(serializeManifest(m)) deepEquals m.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\r\n\r\nimport {\r\n AGENT_KINDS,\r\n AI_TOOLS,\r\n BACKEND_LANGUAGES,\r\n CI_PLATFORMS,\r\n CONTRACT_KINDS,\r\n DEPLOY_TARGETS,\r\n FRONTEND_FRAMEWORKS,\r\n LICENSE_KINDS,\r\n MINIAPP_KINDS,\r\n MOBILE_KINDS,\r\n ROLE_KINDS,\r\n type Options,\r\n type Role,\r\n} from './options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Condition expressed as equality on a subset of Options fields, plus a\r\n * special-case `role` field interpreted as \"options.roles contains this role\".\r\n *\r\n * Per design §Components.Fragment Resolver, only conjunctive equality is\r\n * supported on scalar Options fields. Array-valued `roles` cannot use\r\n * straight equality; `role` (singular) is the per-fragment role gate.\r\n */\r\nexport type AppliesWhen = Partial<Omit<Options, 'projectName' | 'roles'>> & {\r\n readonly role?: Role;\r\n};\r\n\r\nexport interface ManifestFileRule {\r\n readonly from: string;\r\n readonly to: string;\r\n readonly render: boolean;\r\n readonly mode?: number;\r\n}\r\n\r\nexport interface TemplateManifest {\r\n readonly name: string;\r\n readonly version: string;\r\n readonly appliesWhen: AppliesWhen;\r\n readonly priority: number;\r\n readonly files: readonly ManifestFileRule[];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema\r\n// ---------------------------------------------------------------------------\r\n\r\nconst SEMVER_PATTERN = '^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:-[0-9A-Za-z-.]+)?(?:\\\\+[0-9A-Za-z-.]+)?$';\r\nconst FRAGMENT_NAME_PATTERN = '^[a-z0-9][a-z0-9-]{0,62}$';\r\nconst POSIX_RELATIVE_PATH_PATTERN = '^(?!/)(?!.*(^|/)\\\\.\\\\.($|/))[^\\\\\\\\]+$';\r\n\r\nexport const MANIFEST_FILE_RULE_SCHEMA = {\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['from', 'to', 'render'],\r\n properties: {\r\n from: { type: 'string', minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },\r\n to: { type: 'string', minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },\r\n render: { type: 'boolean' },\r\n mode: { type: 'integer', minimum: 0, maximum: 0o7777 },\r\n },\r\n} as const;\r\n\r\nexport const APPLIES_WHEN_SCHEMA = {\r\n type: 'object',\r\n additionalProperties: false,\r\n properties: {\r\n backend: { type: 'string', enum: [...BACKEND_LANGUAGES] },\r\n frontend: { type: 'string', enum: [...FRONTEND_FRAMEWORKS] },\r\n mobile: { type: 'string', enum: [...MOBILE_KINDS] },\r\n miniapp: { type: 'string', enum: [...MINIAPP_KINDS] },\r\n agent: { type: 'string', enum: [...AGENT_KINDS] },\r\n deploy: { type: 'string', enum: [...DEPLOY_TARGETS] },\r\n contract: { type: 'string', enum: [...CONTRACT_KINDS] },\r\n ai: { type: 'string', enum: [...AI_TOOLS] },\r\n license: { type: 'string', enum: [...LICENSE_KINDS] },\r\n gitLfs: { type: 'boolean' },\r\n integrations: { type: 'boolean' },\r\n ci: { type: 'string', enum: [...CI_PLATFORMS] },\r\n role: { type: 'string', enum: [...ROLE_KINDS] },\r\n },\r\n} as const;\r\n\r\nexport const MANIFEST_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/manifest.json',\r\n title: 'TemplateManifest',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['name', 'version', 'appliesWhen', 'priority', 'files'],\r\n properties: {\r\n name: { type: 'string', pattern: FRAGMENT_NAME_PATTERN },\r\n version: { type: 'string', pattern: SEMVER_PATTERN },\r\n appliesWhen: APPLIES_WHEN_SCHEMA,\r\n priority: { type: 'integer', minimum: 0, maximum: 100 },\r\n files: {\r\n type: 'array',\r\n items: MANIFEST_FILE_RULE_SCHEMA,\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\nconst validateFn = ajv.compile<TemplateManifest>(MANIFEST_SCHEMA);\r\n\r\nexport interface ManifestValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class ManifestValidationError extends Error {\r\n readonly issues: readonly ManifestValidationIssue[];\r\n\r\n constructor(issues: readonly ManifestValidationIssue[]) {\r\n super(\r\n `Invalid TemplateManifest:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'ManifestValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): ManifestValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path:\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\n/**\r\n * Also enforces invariants that pure JSON Schema cannot express:\r\n * - `files[].to` relative paths must not include \"..\" segments (redundant\r\n * with the POSIX pattern but asserted explicitly for defense in depth).\r\n * - `files[].to` values must be unique within a single manifest (duplicate\r\n * outputs from the same fragment indicate an authoring bug).\r\n */\r\nfunction assertStructuralInvariants(m: TemplateManifest): void {\r\n const seen = new Set<string>();\r\n for (const f of m.files) {\r\n if (seen.has(f.to)) {\r\n throw new ManifestValidationError([\r\n { path: '/files', message: `duplicate target path: ${f.to}` },\r\n ]);\r\n }\r\n seen.add(f.to);\r\n }\r\n}\r\n\r\nexport function assertManifest(value: unknown): TemplateManifest {\r\n if (!validateFn(value)) {\r\n throw new ManifestValidationError(collectIssues());\r\n }\r\n assertStructuralInvariants(value);\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Ordering helpers — canonical forms for round-trip\r\n// ---------------------------------------------------------------------------\r\n\r\nconst MANIFEST_TOP_ORDER: readonly (keyof TemplateManifest)[] = [\r\n 'name',\r\n 'version',\r\n 'appliesWhen',\r\n 'priority',\r\n 'files',\r\n];\r\n\r\nconst APPLIES_WHEN_ORDER: readonly (keyof AppliesWhen)[] = [\r\n 'backend',\r\n 'frontend',\r\n 'mobile',\r\n 'miniapp',\r\n 'agent',\r\n 'deploy',\r\n 'contract',\r\n 'ai',\r\n 'license',\r\n 'gitLfs',\r\n 'integrations',\r\n 'ci',\r\n 'role',\r\n];\r\n\r\nconst FILE_RULE_ORDER: readonly (keyof ManifestFileRule)[] = ['from', 'to', 'render', 'mode'];\r\n\r\nfunction canonicalAppliesWhen(a: AppliesWhen): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of APPLIES_WHEN_ORDER) {\r\n if (a[key] !== undefined) {\r\n out[key] = a[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalFileRule(f: ManifestFileRule): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of FILE_RULE_ORDER) {\r\n if (f[key] !== undefined) {\r\n out[key] = f[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalManifest(m: TemplateManifest): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of MANIFEST_TOP_ORDER) {\r\n if (key === 'appliesWhen') {\r\n out[key] = canonicalAppliesWhen(m.appliesWhen);\r\n } else if (key === 'files') {\r\n out[key] = m.files.map(canonicalFileRule);\r\n } else {\r\n out[key] = m[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Parse / serialize\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a YAML or JSON manifest string into a validated TemplateManifest.\r\n */\r\nexport function parseManifest(input: string): TemplateManifest {\r\n let raw: unknown;\r\n const trimmed = input.trim();\r\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\r\n try {\r\n raw = JSON.parse(trimmed);\r\n } catch (e) {\r\n throw new ManifestValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n } else {\r\n try {\r\n raw = parseYaml(input);\r\n } catch (e) {\r\n throw new ManifestValidationError([\r\n { path: '', message: `invalid YAML: ${(e as Error).message}` },\r\n ]);\r\n }\r\n }\r\n return assertManifest(raw);\r\n}\r\n\r\n/**\r\n * Serialize to canonical YAML (the on-disk format of fragment.yaml).\r\n * Field order is fixed to guarantee byte-stable output and round-trip equality.\r\n */\r\nexport function serializeManifest(manifest: TemplateManifest): string {\r\n return stringifyYaml(canonicalManifest(manifest), {\r\n lineWidth: 0,\r\n minContentWidth: 0,\r\n });\r\n}\r\n","/**\r\n * Fragment resolver — given a set of loaded fragments and the user's\r\n * resolved Options, return the subset whose `appliesWhen` matches.\r\n *\r\n * Matching is conjunctive equality over every field declared in\r\n * `appliesWhen`: a fragment applies iff, for every key K in `appliesWhen`,\r\n * `options[K] === appliesWhen[K]`. An empty `appliesWhen` always matches.\r\n *\r\n * Result ordering:\r\n * - Primary: `priority` ascending (lower priority applied first; higher\r\n * priority's writes win during plan building).\r\n * - Secondary: fragment name ascending, to make the output deterministic\r\n * and friendly to snapshot tests.\r\n */\r\n\r\nimport type { AppliesWhen, TemplateManifest } from '../schema/manifest.js';\r\nimport type { Options } from '../schema/options.js';\r\nimport type { LoadedFragment } from './loader.js';\r\n\r\nexport function fragmentApplies(manifest: TemplateManifest, options: Options): boolean {\r\n return appliesWhenMatches(manifest.appliesWhen, options);\r\n}\r\n\r\nexport function appliesWhenMatches(appliesWhen: AppliesWhen, options: Options): boolean {\r\n for (const key of Object.keys(appliesWhen) as Array<keyof AppliesWhen>) {\r\n const expected = appliesWhen[key];\r\n if (expected === undefined) continue;\r\n if (key === 'role') {\r\n // `role` is the singular \"this options.roles array contains X\" gate.\r\n if (!options.roles.includes(expected as (typeof options.roles)[number])) {\r\n return false;\r\n }\r\n continue;\r\n }\r\n const actual = options[key as keyof Options];\r\n if (actual !== expected) return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Select the applicable fragments for the given Options and return them in\r\n * deterministic order. Pure function of its inputs — the same Options +\r\n * same fragment list always yields the same array instance contents\r\n * (supporting Property 8: Fragment 合并幂等).\r\n */\r\nexport function selectFragments(\r\n options: Options,\r\n loaded: readonly LoadedFragment[],\r\n): readonly LoadedFragment[] {\r\n const applicable = loaded.filter((f) => fragmentApplies(f.manifest, options));\r\n return [...applicable].sort((a, b) => {\r\n if (a.manifest.priority !== b.manifest.priority) {\r\n return a.manifest.priority - b.manifest.priority;\r\n }\r\n return a.manifest.name.localeCompare(b.manifest.name);\r\n });\r\n}\r\n","/**\r\n * Render context — the *only* source of variables visible to templates.\r\n *\r\n * Every template string is evaluated against an object of this shape. By\r\n * keeping the surface minimal and explicit, undefined variable references\r\n * become trivially detectable (§13.2).\r\n *\r\n * Derived fields (year, generatedAt) are computed here so templates never\r\n * need to call `new Date()` themselves, keeping rendering deterministic.\r\n */\r\n\r\nimport type { Options } from '../schema/options.js';\r\n\r\nexport interface RenderContext {\r\n readonly options: Options;\r\n /** Four-digit year derived from `generatedAt`. */\r\n readonly year: string;\r\n /** ISO 8601 UTC timestamp. */\r\n readonly generatedAt: string;\r\n /** Scaffolder's own SemVer (for banner comments, metadata, etc.). */\r\n readonly scaffolderVersion: string;\r\n}\r\n\r\nexport interface BuildContextInput {\r\n readonly options: Options;\r\n readonly scaffolderVersion: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport function buildRenderContext(input: BuildContextInput): RenderContext {\r\n const now = input.now ?? new Date();\r\n return {\r\n options: input.options,\r\n year: String(now.getUTCFullYear()),\r\n generatedAt: now.toISOString(),\r\n scaffolderVersion: input.scaffolderVersion,\r\n };\r\n}\r\n","/**\r\n * File Plan Builder — turn a selected set of fragments into a concrete\r\n * FilePlan the writer can execute.\r\n *\r\n * Responsibilities:\r\n * 1. For each selected fragment, read each manifest file and either\r\n * render it through the Template Renderer (render: true) or copy it\r\n * verbatim (render: false).\r\n * 2. Resolve the `to` path against the target directory (which is the\r\n * project root; paths are always POSIX-style in manifests but we\r\n * store them in the plan using `path.posix` to stay deterministic\r\n * across OSes).\r\n * 3. Detect same-path conflicts across fragments. Higher priority wins;\r\n * ties throw a TemplateError (§12.4).\r\n * 4. Emit a sorted, deterministic plan so Dry Run diffs and snapshot\r\n * tests remain stable.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { LoadedFragment } from '../fragments/loader.js';\r\nimport type { RenderContext } from '../render/context.js';\r\nimport { renderFile } from '../render/renderer.js';\r\n\r\nexport interface PlannedFile {\r\n /** POSIX-style path relative to the target directory. */\r\n readonly targetPath: string;\r\n readonly content: string;\r\n /** Name of the fragment that contributed this file (post conflict resolution). */\r\n readonly contributedBy: string;\r\n readonly mode?: number;\r\n}\r\n\r\nexport interface FilePlan {\r\n readonly files: readonly PlannedFile[];\r\n /** POSIX-style directory paths, sorted, deduplicated. */\r\n readonly directories: readonly string[];\r\n}\r\n\r\nexport interface BuildPlanInput {\r\n readonly fragments: readonly LoadedFragment[];\r\n readonly context: RenderContext;\r\n}\r\n\r\n/**\r\n * Produce a plan from a list of already-selected fragments (post-resolver).\r\n * Pure over its inputs — given the same fragments + context, the returned\r\n * plan is byte-identical every time.\r\n */\r\nexport async function buildFilePlan(input: BuildPlanInput): Promise<FilePlan> {\r\n const { fragments, context } = input;\r\n\r\n interface CandidateFile extends PlannedFile {\r\n readonly priority: number;\r\n }\r\n\r\n const byPath = new Map<string, CandidateFile>();\r\n\r\n for (const fragment of fragments) {\r\n for (const rule of fragment.manifest.files) {\r\n const fromAbs = path.join(fragment.rootDir, rule.from);\r\n const targetPath = normalizePosixPath(rule.to);\r\n\r\n let content: string;\r\n if (rule.render) {\r\n content = await renderFile(fromAbs, context);\r\n } else {\r\n try {\r\n content = await fs.readFile(fromAbs, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `failed to read template source ${fromAbs}: ${(e as Error).message}`,\r\n '请确认 manifest.files[].from 指向真实文件',\r\n );\r\n }\r\n }\r\n\r\n const candidate: CandidateFile = {\r\n targetPath,\r\n content,\r\n contributedBy: fragment.manifest.name,\r\n ...(rule.mode !== undefined ? { mode: rule.mode } : {}),\r\n priority: fragment.manifest.priority,\r\n };\r\n\r\n const existing = byPath.get(targetPath);\r\n if (existing === undefined) {\r\n byPath.set(targetPath, candidate);\r\n continue;\r\n }\r\n\r\n // Conflict: resolve by priority. Strict greater-than wins; equal\r\n // priorities with differing content is a TemplateError (§12.4).\r\n if (candidate.priority > existing.priority) {\r\n byPath.set(targetPath, candidate);\r\n } else if (candidate.priority < existing.priority) {\r\n // keep existing\r\n } else if (existing.content !== candidate.content) {\r\n throw new TemplateError(\r\n `conflict at ${targetPath}: fragments \"${existing.contributedBy}\" and ` +\r\n `\"${candidate.contributedBy}\" have equal priority ${candidate.priority} ` +\r\n `but contribute different content`,\r\n '调整 fragment 的 priority,或由一个 fragment 独占该路径',\r\n );\r\n }\r\n // Equal priority + equal content → silently dedupe.\r\n }\r\n }\r\n\r\n const files = [...byPath.values()]\r\n .map(({ priority: _p, ...rest }): PlannedFile => rest)\r\n .sort((a, b) => (a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0));\r\n\r\n const directorySet = new Set<string>();\r\n for (const f of files) {\r\n let cur = path.posix.dirname(f.targetPath);\r\n while (cur && cur !== '.' && cur !== '/') {\r\n directorySet.add(cur);\r\n cur = path.posix.dirname(cur);\r\n }\r\n }\r\n const directories = [...directorySet].sort();\r\n\r\n return { files, directories };\r\n}\r\n\r\nfunction normalizePosixPath(p: string): string {\r\n // Convert any backslashes that might appear on Windows authoring to POSIX,\r\n // then collapse redundant separators.\r\n return p.split(/[\\\\/]+/).filter(Boolean).join('/');\r\n}\r\n","/**\r\n * Template Renderer — render a single file body against a RenderContext.\r\n *\r\n * Design decisions:\r\n * - Uses `eta` with the explicit `<%= … %>` interpolation syntax, which\r\n * avoids colliding with YAML / JSON brace syntax.\r\n * - Strict variable resolution: any reference to a field not present on\r\n * RenderContext throws a TemplateError (§13.2, Property 11).\r\n * - No filesystem / IO access is given to templates; the sandboxed `it`\r\n * object is the *only* data they can read.\r\n *\r\n * Two entry points are provided:\r\n * - `renderString` for in-memory content (tests, generated scripts)\r\n * - `renderFile` that reads from disk and annotates errors with the path\r\n */\r\n\r\nimport { Eta } from 'eta';\r\nimport { promises as fs } from 'node:fs';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { RenderContext } from './context.js';\r\n\r\n/**\r\n * Create a fresh Eta instance configured for Scaffolder's template dialect.\r\n *\r\n * We disable caching because templates are read directly from disk and the\r\n * fragment loader already deduplicates work; a cache would only add a\r\n * footgun for tests that reuse filenames across iterations.\r\n */\r\nfunction createEta(): Eta {\r\n return new Eta({\r\n autoEscape: false,\r\n useWith: false,\r\n cache: false,\r\n autoTrim: false,\r\n // Force tags to the explicit form to keep syntax unambiguous.\r\n tags: ['<%', '%>'],\r\n });\r\n}\r\n\r\nfunction toStrictContext(ctx: RenderContext): ProxyHandler<RenderContext> extends never\r\n ? never\r\n : Record<string, unknown> {\r\n // Wrap the context in a Proxy that throws on any unknown property access,\r\n // turning \"undefined variable\" into an immediate TemplateError rather than\r\n // silently rendering `undefined` into the output.\r\n const base: Record<string, unknown> = {\r\n options: ctx.options,\r\n year: ctx.year,\r\n generatedAt: ctx.generatedAt,\r\n scaffolderVersion: ctx.scaffolderVersion,\r\n };\r\n\r\n return new Proxy(base, {\r\n get(target, prop, receiver) {\r\n if (typeof prop === 'symbol') {\r\n return Reflect.get(target, prop, receiver);\r\n }\r\n if (!(prop in target)) {\r\n throw new TemplateError(\r\n `undefined template variable: ${String(prop)}`,\r\n '请确认变量名与 RenderContext 字段一致,或扩展 RenderContext',\r\n );\r\n }\r\n return Reflect.get(target, prop, receiver);\r\n },\r\n }) as Record<string, unknown>;\r\n}\r\n\r\nfunction rethrowRenderError(source: string, err: unknown): never {\r\n if (err instanceof TemplateError) throw err;\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new TemplateError(\r\n `render failed in ${source}: ${message}`,\r\n '检查模板语法是否与 <% %> 标签一致',\r\n );\r\n}\r\n\r\nexport function renderString(\r\n template: string,\r\n context: RenderContext,\r\n source = '<string>',\r\n): string {\r\n const eta = createEta();\r\n const strict = toStrictContext(context);\r\n try {\r\n const out = eta.renderString(template, strict);\r\n if (typeof out !== 'string') {\r\n throw new TemplateError(\r\n `renderer returned non-string for ${source}`,\r\n '这是一个 scaffolder 内部错误,请汇报',\r\n );\r\n }\r\n return out;\r\n } catch (err) {\r\n rethrowRenderError(source, err);\r\n }\r\n}\r\n\r\nexport async function renderFile(\r\n absPath: string,\r\n context: RenderContext,\r\n): Promise<string> {\r\n let text: string;\r\n try {\r\n text = await fs.readFile(absPath, 'utf8');\r\n } catch (err) {\r\n throw new TemplateError(\r\n `failed to read template ${absPath}: ${(err as Error).message}`,\r\n '请确认 fragment.yaml 中的 from 路径指向真实文件',\r\n );\r\n }\r\n return renderString(text, context, absPath);\r\n}\r\n","/**\r\n * Syntax Validator — parse every structured file in a FilePlan against its\r\n * format's grammar. Failures are TemplateError (exit code 3) because they\r\n * indicate that a template produced invalid output, not that the user did\r\n * something wrong.\r\n *\r\n * This runs *after* plan building and *before* file writing, so that a\r\n * bad render never makes it to disk (pairs with rollback: catching here\r\n * means nothing was written yet).\r\n */\r\n\r\nimport { parse as parseYaml } from 'yaml';\r\n\r\nimport { TemplateError } from '../errors.js';\r\nimport type { FilePlan } from './builder.js';\r\n\r\nexport function validatePlanSyntax(plan: FilePlan): void {\r\n for (const file of plan.files) {\r\n const ext = lowerExt(file.targetPath);\r\n if (ext === 'json') {\r\n try {\r\n JSON.parse(file.content);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `invalid JSON in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${(e as Error).message}`,\r\n '检查对应模板的语法',\r\n );\r\n }\r\n } else if (ext === 'yaml' || ext === 'yml') {\r\n try {\r\n parseYaml(file.content);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `invalid YAML in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${(e as Error).message}`,\r\n '检查对应模板的缩进与引号',\r\n );\r\n }\r\n }\r\n }\r\n}\r\n\r\nfunction lowerExt(p: string): string {\r\n const dot = p.lastIndexOf('.');\r\n if (dot < 0) return '';\r\n return p.slice(dot + 1).toLowerCase();\r\n}\r\n","/**\r\n * ScaffolderMetadata schema and I/O helpers.\r\n *\r\n * This module defines the shape of `.scaffolder.json`, which is written into\r\n * every generated project so that the project can be traced back to a specific\r\n * Scaffolder version and its input options.\r\n *\r\n * Round-trip invariant (Property 6): loadMetadata(writeMetadata(d)) deepEquals d.\r\n */\r\n\r\nimport Ajv from 'ajv';\r\nimport addFormats from 'ajv-formats';\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport {\r\n OPTIONS_FIELD_ORDER,\r\n OPTIONS_SCHEMA,\r\n assertOptions,\r\n type Options,\r\n} from './options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TemplateFragmentRef {\r\n readonly name: string;\r\n readonly version: string;\r\n}\r\n\r\nexport interface ScaffolderMetadata {\r\n readonly scaffolderVersion: string;\r\n readonly generatedAt: string;\r\n readonly options: Options;\r\n readonly templateFragments: readonly TemplateFragmentRef[];\r\n}\r\n\r\nexport const METADATA_FILENAME = '.scaffolder.json';\r\n\r\n// ---------------------------------------------------------------------------\r\n// JSON Schema\r\n// ---------------------------------------------------------------------------\r\n\r\nconst SEMVER_PATTERN = '^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:-[0-9A-Za-z-.]+)?(?:\\\\+[0-9A-Za-z-.]+)?$';\r\nconst FRAGMENT_NAME_PATTERN = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\nexport const METADATA_SCHEMA = {\r\n $schema: 'http://json-schema.org/draft-07/schema#',\r\n $id: 'https://cgsy.example.com/schemas/scaffolder/metadata.json',\r\n title: 'ScaffolderMetadata',\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['scaffolderVersion', 'generatedAt', 'options', 'templateFragments'],\r\n properties: {\r\n scaffolderVersion: { type: 'string', pattern: SEMVER_PATTERN },\r\n generatedAt: { type: 'string', format: 'date-time' },\r\n options: OPTIONS_SCHEMA,\r\n templateFragments: {\r\n type: 'array',\r\n items: {\r\n type: 'object',\r\n additionalProperties: false,\r\n required: ['name', 'version'],\r\n properties: {\r\n name: { type: 'string', pattern: FRAGMENT_NAME_PATTERN },\r\n version: { type: 'string', pattern: SEMVER_PATTERN },\r\n },\r\n },\r\n },\r\n },\r\n} as const;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Validator\r\n// ---------------------------------------------------------------------------\r\n\r\nconst ajv = new Ajv({ allErrors: true, strict: true });\r\naddFormats(ajv);\r\nconst validateFn = ajv.compile<ScaffolderMetadata>(METADATA_SCHEMA);\r\n\r\nexport interface MetadataValidationIssue {\r\n readonly path: string;\r\n readonly message: string;\r\n}\r\n\r\nexport class MetadataValidationError extends Error {\r\n readonly issues: readonly MetadataValidationIssue[];\r\n\r\n constructor(issues: readonly MetadataValidationIssue[]) {\r\n super(\r\n `Invalid ScaffolderMetadata:\\n` +\r\n issues.map((i) => ` - ${i.path || '<root>'}: ${i.message}`).join('\\n'),\r\n );\r\n this.name = 'MetadataValidationError';\r\n this.issues = issues;\r\n }\r\n}\r\n\r\nfunction collectIssues(): MetadataValidationIssue[] {\r\n const errs = validateFn.errors ?? [];\r\n return errs.map((e) => ({\r\n path:\r\n e.instancePath ||\r\n (e.params && 'missingProperty' in e.params\r\n ? `/${String((e.params as { missingProperty: string }).missingProperty)}`\r\n : ''),\r\n message: e.message ?? 'validation error',\r\n }));\r\n}\r\n\r\nexport function assertMetadata(value: unknown): ScaffolderMetadata {\r\n if (!validateFn(value)) {\r\n throw new MetadataValidationError(collectIssues());\r\n }\r\n // Re-validate the nested options with the dedicated assertOptions so that\r\n // any option-specific invariant lives in a single place.\r\n assertOptions(value.options);\r\n return value;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Canonical serialization\r\n// ---------------------------------------------------------------------------\r\n\r\nconst METADATA_TOP_ORDER: readonly (keyof ScaffolderMetadata)[] = [\r\n 'scaffolderVersion',\r\n 'generatedAt',\r\n 'options',\r\n 'templateFragments',\r\n];\r\n\r\nfunction canonicalOptions(options: Options): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of OPTIONS_FIELD_ORDER) {\r\n out[key] = options[key];\r\n }\r\n return out;\r\n}\r\n\r\nfunction canonicalFragmentRef(ref: TemplateFragmentRef): Record<string, unknown> {\r\n return { name: ref.name, version: ref.version };\r\n}\r\n\r\nfunction canonicalMetadata(metadata: ScaffolderMetadata): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n for (const key of METADATA_TOP_ORDER) {\r\n if (key === 'options') {\r\n out[key] = canonicalOptions(metadata.options);\r\n } else if (key === 'templateFragments') {\r\n out[key] = metadata.templateFragments.map(canonicalFragmentRef);\r\n } else {\r\n out[key] = metadata[key];\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nexport function serializeMetadata(metadata: ScaffolderMetadata): string {\r\n return JSON.stringify(canonicalMetadata(metadata), null, 2) + '\\n';\r\n}\r\n\r\nexport function parseMetadata(input: string): ScaffolderMetadata {\r\n let raw: unknown;\r\n try {\r\n raw = JSON.parse(input);\r\n } catch (e) {\r\n throw new MetadataValidationError([\r\n { path: '', message: `invalid JSON: ${(e as Error).message}` },\r\n ]);\r\n }\r\n return assertMetadata(raw);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Filesystem I/O\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Write the metadata file into the given target directory.\r\n * The directory is assumed to already exist (the writer stage creates it).\r\n */\r\nexport async function writeMetadataFile(\r\n targetDirectory: string,\r\n metadata: ScaffolderMetadata,\r\n): Promise<string> {\r\n const filePath = path.join(targetDirectory, METADATA_FILENAME);\r\n await fs.writeFile(filePath, serializeMetadata(metadata), 'utf8');\r\n return filePath;\r\n}\r\n\r\n/**\r\n * Load and validate an existing `.scaffolder.json` from the target directory.\r\n * Intended for future `upgrade`-like commands; the current `create` flow does\r\n * not read it back, but having a symmetric API makes round-trip testing easy.\r\n */\r\nexport async function loadMetadataFile(targetDirectory: string): Promise<ScaffolderMetadata> {\r\n const filePath = path.join(targetDirectory, METADATA_FILENAME);\r\n const text = await fs.readFile(filePath, 'utf8');\r\n return parseMetadata(text);\r\n}\r\n\r\n/**\r\n * Build a metadata object from the current scaffolder version, options, and\r\n * applied fragments. Uses UTC ISO 8601 for `generatedAt`.\r\n */\r\nexport function buildMetadata(params: {\r\n scaffolderVersion: string;\r\n options: Options;\r\n templateFragments: readonly TemplateFragmentRef[];\r\n now?: Date;\r\n}): ScaffolderMetadata {\r\n const now = params.now ?? new Date();\r\n return {\r\n scaffolderVersion: params.scaffolderVersion,\r\n generatedAt: now.toISOString(),\r\n options: params.options,\r\n templateFragments: [...params.templateFragments],\r\n };\r\n}\r\n","/**\r\n * Helper to append the `.scaffolder.json` metadata file to a FilePlan.\r\n *\r\n * Keeping this outside the writer ensures metadata participates in the\r\n * same atomic write-or-rollback cycle as every other generated file\r\n * (requirement 10.3).\r\n */\r\n\r\nimport type { FilePlan, PlannedFile } from './builder.js';\r\nimport {\r\n METADATA_FILENAME,\r\n buildMetadata,\r\n serializeMetadata,\r\n type TemplateFragmentRef,\r\n} from '../schema/metadata.js';\r\nimport type { Options } from '../schema/options.js';\r\n\r\nexport interface AppendMetadataInput {\r\n readonly plan: FilePlan;\r\n readonly scaffolderVersion: string;\r\n readonly options: Options;\r\n readonly templateFragments: readonly TemplateFragmentRef[];\r\n readonly now?: Date;\r\n}\r\n\r\nexport function appendScaffolderMetadata(input: AppendMetadataInput): FilePlan {\r\n const metadata = buildMetadata({\r\n scaffolderVersion: input.scaffolderVersion,\r\n options: input.options,\r\n templateFragments: input.templateFragments,\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n const file: PlannedFile = {\r\n targetPath: METADATA_FILENAME,\r\n content: serializeMetadata(metadata),\r\n contributedBy: '__scaffolder_metadata__',\r\n };\r\n // Insert sorted by targetPath to keep plan ordering stable.\r\n const files = [...input.plan.files, file].sort((a, b) =>\r\n a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0,\r\n );\r\n return { files, directories: input.plan.directories };\r\n}\r\n","/**\r\n * File Writer with Rollback Journal.\r\n *\r\n * Algorithm (design §Components.File Writer):\r\n * 1. If the target directory does not exist, create it and record the\r\n * top-most created path for possible rollback.\r\n * 2. Create every directory in plan.directories (sorted parent-first).\r\n * Track each newly-created one.\r\n * 3. Write files in deterministic order. Track each write.\r\n * 4. On any failure in steps 2 or 3, undo in reverse: delete files,\r\n * then remove empty directories that we created.\r\n *\r\n * `--force` semantics:\r\n * - Before writing a file that already exists, capture its original\r\n * content (bounded by FORCE_BACKUP_BYTE_LIMIT) so rollback can restore it.\r\n * - If any existing file exceeds the limit, refuse to proceed; this is a\r\n * UserInputError (§2.2) — the user asked for --force on a workspace that\r\n * is too risky to cover with our bounded backup.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { FilesystemError, UserInputError } from '../errors.js';\r\nimport type { FilePlan } from '../plan/builder.js';\r\n\r\nexport interface WriteOptions {\r\n readonly targetDirectory: string;\r\n readonly force: boolean;\r\n}\r\n\r\nexport interface WriteResult {\r\n readonly createdFiles: readonly string[];\r\n readonly createdDirectories: readonly string[];\r\n}\r\n\r\nconst FORCE_BACKUP_BYTE_LIMIT = 10 * 1024 * 1024; // 10 MB\r\n\r\ninterface FileBackup {\r\n readonly absPath: string;\r\n readonly originalContent: string;\r\n}\r\n\r\ninterface Journal {\r\n // Populated in insertion order; rolled back in reverse.\r\n readonly createdFiles: string[];\r\n readonly createdDirectories: string[];\r\n readonly overwrites: FileBackup[];\r\n}\r\n\r\n/**\r\n * Write the plan to disk. On any error throws and leaves the filesystem\r\n * in its pre-call state (best effort), then re-raises.\r\n *\r\n * Returns paths that *we* created (useful for Reporter / tests).\r\n */\r\nexport async function writePlan(plan: FilePlan, opts: WriteOptions): Promise<WriteResult> {\r\n const absTarget = path.resolve(opts.targetDirectory);\r\n const journal: Journal = { createdFiles: [], createdDirectories: [], overwrites: [] };\r\n\r\n // Preflight existence check + --force handling.\r\n const targetState = await inspectTarget(absTarget);\r\n if (targetState.exists && targetState.nonEmpty && !opts.force) {\r\n throw new UserInputError(\r\n `target directory is not empty: ${absTarget}`,\r\n '使用不同路径,或加 --force(小心:会覆盖既有文件)',\r\n );\r\n }\r\n\r\n // Preflight: if --force AND target exists AND any file we are about to\r\n // overwrite exceeds the backup limit, refuse early (§9.1 bullet 3).\r\n if (opts.force && targetState.exists) {\r\n await preflightBackupCapacity(absTarget, plan);\r\n }\r\n\r\n try {\r\n // Step 1: ensure the target directory itself.\r\n if (!targetState.exists) {\r\n await fs.mkdir(absTarget, { recursive: true });\r\n journal.createdDirectories.push(absTarget);\r\n }\r\n\r\n // Step 2: subdirectories.\r\n for (const rel of plan.directories) {\r\n const abs = path.join(absTarget, rel);\r\n try {\r\n await fs.mkdir(abs);\r\n journal.createdDirectories.push(abs);\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\r\n // Pre-existing directory; do not add to journal (we did not create it).\r\n continue;\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n // Step 3: files.\r\n for (const file of plan.files) {\r\n const abs = path.join(absTarget, file.targetPath);\r\n await fs.mkdir(path.dirname(abs), { recursive: true });\r\n\r\n // Capture original content if overwriting under --force.\r\n if (opts.force) {\r\n try {\r\n const prev = await fs.readFile(abs, 'utf8');\r\n journal.overwrites.push({ absPath: abs, originalContent: prev });\r\n } catch {\r\n // File did not exist; this is a creation, not an overwrite.\r\n }\r\n }\r\n\r\n await fs.writeFile(abs, file.content, 'utf8');\r\n if (file.mode !== undefined) {\r\n await fs.chmod(abs, file.mode);\r\n }\r\n journal.createdFiles.push(abs);\r\n }\r\n\r\n return {\r\n createdFiles: journal.createdFiles,\r\n createdDirectories: journal.createdDirectories,\r\n };\r\n } catch (err) {\r\n await rollback(journal);\r\n // Wrap filesystem-level errors; leave ScaffolderError subclasses as-is.\r\n if (err instanceof UserInputError) throw err;\r\n const message = err instanceof Error ? err.message : String(err);\r\n throw new FilesystemError(\r\n `file writer failed; changes rolled back: ${message}`,\r\n '检查权限与磁盘空间,必要时删除残留目录后重试',\r\n );\r\n }\r\n}\r\n\r\nasync function inspectTarget(\r\n absTarget: string,\r\n): Promise<{ exists: boolean; nonEmpty: boolean }> {\r\n try {\r\n const entries = await fs.readdir(absTarget);\r\n return { exists: true, nonEmpty: entries.length > 0 };\r\n } catch (err) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code === 'ENOENT') return { exists: false, nonEmpty: false };\r\n throw new FilesystemError(\r\n `cannot inspect target directory ${absTarget}: ${(err as Error).message}`,\r\n '检查路径的访问权限',\r\n );\r\n }\r\n}\r\n\r\nasync function preflightBackupCapacity(absTarget: string, plan: FilePlan): Promise<void> {\r\n for (const file of plan.files) {\r\n const abs = path.join(absTarget, file.targetPath);\r\n try {\r\n const stat = await fs.stat(abs);\r\n if (stat.isFile() && stat.size > FORCE_BACKUP_BYTE_LIMIT) {\r\n throw new UserInputError(\r\n `--force would overwrite ${abs} which is larger than the 10 MB rollback limit`,\r\n '手动备份 / 删除该文件后重试',\r\n );\r\n }\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') continue;\r\n if (err instanceof UserInputError) throw err;\r\n // Any other stat error: surface as UserInputError because --force on\r\n // an unreadable tree is unsafe.\r\n throw new UserInputError(\r\n `cannot inspect existing file ${abs}: ${(err as Error).message}`,\r\n '请确认目标目录下所有文件可读',\r\n );\r\n }\r\n }\r\n}\r\n\r\nasync function rollback(journal: Journal): Promise<void> {\r\n // 1) Restore overwrites first (fewer chances of leaving the user worse off).\r\n for (let i = journal.overwrites.length - 1; i >= 0; i -= 1) {\r\n const { absPath, originalContent } = journal.overwrites[i]!;\r\n try {\r\n await fs.writeFile(absPath, originalContent, 'utf8');\r\n } catch {\r\n // best-effort rollback\r\n }\r\n }\r\n\r\n // 2) Delete files we created (not restored overwrites).\r\n const overwriteSet = new Set(journal.overwrites.map((o) => o.absPath));\r\n for (let i = journal.createdFiles.length - 1; i >= 0; i -= 1) {\r\n const p = journal.createdFiles[i]!;\r\n if (overwriteSet.has(p)) continue; // already restored content\r\n try {\r\n await fs.rm(p, { force: true });\r\n } catch {\r\n // best-effort\r\n }\r\n }\r\n\r\n // 3) Remove directories we created, deepest first (same as reverse order\r\n // because we created them parent-first).\r\n for (let i = journal.createdDirectories.length - 1; i >= 0; i -= 1) {\r\n const dir = journal.createdDirectories[i]!;\r\n try {\r\n await fs.rmdir(dir);\r\n } catch {\r\n // Likely non-empty (pre-existing peer content). Leave it.\r\n }\r\n }\r\n}\r\n","/**\r\n * Reporter — turns plans into terminal output at three verbosity levels.\r\n *\r\n * - `quiet` — only errors + a one-line final summary\r\n * - default — stage headers + per-stage summaries\r\n * - `verbose` — default plus per-file render/write logs\r\n *\r\n * All output goes through the injected `stdout` / `stderr` sinks, never\r\n * directly to `process.stdout`, so tests can capture it without stubbing.\r\n */\r\n\r\nimport type { FilePlan } from './plan/builder.js';\r\nimport type { Options } from './schema/options.js';\r\n\r\nexport type Verbosity = 'quiet' | 'default' | 'verbose';\r\n\r\nexport interface ReporterIO {\r\n readonly stdout: (line: string) => void;\r\n readonly stderr: (line: string) => void;\r\n}\r\n\r\nexport class Reporter {\r\n constructor(\r\n private readonly io: ReporterIO,\r\n private readonly verbosity: Verbosity,\r\n ) {}\r\n\r\n stage(label: string): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(`\\n─── ${label} ───`);\r\n }\r\n\r\n info(line: string): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(line);\r\n }\r\n\r\n verbose(line: string): void {\r\n if (this.verbosity !== 'verbose') return;\r\n this.io.stdout(line);\r\n }\r\n\r\n options(options: Options): void {\r\n if (this.verbosity === 'quiet') return;\r\n this.io.stdout(` projectName: ${options.projectName}`);\r\n this.io.stdout(` backend: ${options.backend}`);\r\n this.io.stdout(` frontend: ${options.frontend}`);\r\n this.io.stdout(` mobile: ${options.mobile}`);\r\n this.io.stdout(` miniapp: ${options.miniapp}`);\r\n this.io.stdout(` agent: ${options.agent}`);\r\n this.io.stdout(` deploy: ${options.deploy}`);\r\n this.io.stdout(` contract: ${options.contract}`);\r\n this.io.stdout(` ai: ${options.ai}`);\r\n this.io.stdout(` license: ${options.license}`);\r\n this.io.stdout(` gitLfs: ${String(options.gitLfs)}`);\r\n this.io.stdout(` integrations: ${String(options.integrations)}`);\r\n this.io.stdout(` ci: ${options.ci}`);\r\n this.io.stdout(` roles: ${options.roles.length > 0 ? options.roles.join(',') : '<none>'}`);\r\n }\r\n\r\n dryRunReport(plan: FilePlan): void {\r\n this.io.stdout('\\n[dry-run] would create the following files:');\r\n for (const f of plan.files) {\r\n const bytes = Buffer.byteLength(f.content, 'utf8');\r\n this.io.stdout(` ${f.targetPath} (${bytes} bytes, from ${f.contributedBy})`);\r\n }\r\n this.io.stdout(`\\n[dry-run] total: ${plan.files.length} file(s)`);\r\n }\r\n\r\n writeProgress(file: { targetPath: string; contributedBy: string; content: string }): void {\r\n if (this.verbosity !== 'verbose') return;\r\n const bytes = Buffer.byteLength(file.content, 'utf8');\r\n this.io.stdout(` wrote ${file.targetPath} (${bytes}B, from ${file.contributedBy})`);\r\n }\r\n\r\n summary(kind: 'created' | 'dry-run', fileCount: number, targetDir: string): void {\r\n // Always emit, even in quiet.\r\n if (kind === 'created') {\r\n this.io.stdout(`✓ created ${fileCount} file(s) at ${targetDir}`);\r\n } else {\r\n this.io.stdout(`✓ dry-run: ${fileCount} file(s) would be created at ${targetDir}`);\r\n }\r\n }\r\n}\r\n\r\nexport function verbosityFromFlags(flags: { quiet?: boolean; verbose?: boolean }): Verbosity {\r\n if (flags.quiet && flags.verbose) return 'verbose';\r\n if (flags.quiet) return 'quiet';\r\n if (flags.verbose) return 'verbose';\r\n return 'default';\r\n}\r\n","/**\r\n * Binary entry point.\r\n *\r\n * Thin wiring: hook stdin/stdout/stderr and process.argv into run(),\r\n * delegate `create` to the orchestrator, and translate the returned\r\n * exit code into process.exit.\r\n */\r\n\r\nimport { run, type CreateCommandInput } from './cli.js';\r\nimport { runCreate } from './orchestrator.js';\r\n\r\nasync function handleCreate(input: CreateCommandInput): Promise<void> {\r\n await runCreate({\r\n projectName: input.projectName,\r\n flags: input.flags,\r\n io: {\r\n stdout: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.log(line);\r\n },\r\n stderr: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.error(line);\r\n },\r\n isTTY: Boolean(process.stdout.isTTY),\r\n },\r\n });\r\n}\r\n\r\nconst exitCode = await run({\r\n argv: process.argv,\r\n stdout: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.log(line);\r\n },\r\n stderr: (line) => {\r\n // eslint-disable-next-line no-console\r\n console.error(line);\r\n },\r\n onCreate: handleCreate,\r\n});\r\n\r\nprocess.exit(exitCode);\r\n"],"mappings":"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","names":["path","Ajv","parseYaml","ajv","Ajv","path","parseYaml","validate","fs","path","Ajv","parseYaml","ajv","Ajv","validateFn","collectIssues","parseYaml","fs","fs","path","fs","fs","fs","parseYaml","parseYaml","Ajv","addFormats","SEMVER_PATTERN","FRAGMENT_NAME_PATTERN","ajv","Ajv","addFormats","validateFn","fs","path","fs"]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
__pycache__/
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
|
|
7
|
+
# Build output
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
target/
|
|
12
|
+
|
|
13
|
+
# Env / secrets
|
|
14
|
+
.env
|
|
15
|
+
.env.local
|
|
16
|
+
*.pem
|
|
17
|
+
*.key
|
|
18
|
+
|
|
19
|
+
# Logs
|
|
20
|
+
*.log
|
|
21
|
+
|
|
22
|
+
# IDE / OS
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
@@ -9,10 +9,10 @@ files:
|
|
|
9
9
|
- from: files/README.md
|
|
10
10
|
to: README.md
|
|
11
11
|
render: true
|
|
12
|
-
- from: files
|
|
12
|
+
- from: files/_gitignore
|
|
13
13
|
to: .gitignore
|
|
14
14
|
render: false
|
|
15
|
-
- from: files
|
|
15
|
+
- from: files/_gitattributes
|
|
16
16
|
to: .gitattributes
|
|
17
17
|
render: true
|
|
18
18
|
- from: files/.editorconfig
|
|
File without changes
|