@wneng/create-keel 0.2.0 → 0.2.1
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 +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/feature/templates/arch/adr.md.eta +31 -0
- package/src/feature/templates/backend/design.md.eta +35 -0
- package/src/feature/templates/frontend/design.md.eta +34 -0
- package/src/feature/templates/pm/prd.md.eta +37 -0
- package/src/feature/templates/sample/user-signup/arch/adr.md.eta +48 -0
- package/src/feature/templates/sample/user-signup/backend/design.md.eta +77 -0
- package/src/feature/templates/sample/user-signup/frontend/design.md.eta +54 -0
- package/src/feature/templates/sample/user-signup/pm/prd.md.eta +46 -0
- package/src/feature/templates/sample/user-signup/test/test-plan.md.eta +40 -0
- package/src/feature/templates/test/test-plan.md.eta +23 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/schema/options.ts","../src/errors.ts","../src/feature/layers.ts","../src/feature/subcommand.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/feature/orchestrator.ts","../src/feature/plan.ts","../src/feature/openapi-edit.ts","../src/feature/manifest-update.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.2.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 * FeatureLayer model — the five layers a feature can be scaffolded into.\r\n *\r\n * This module is the single source of truth for:\r\n * - the FeatureLayer string literal union\r\n * - the per-layer output path scheme (Layer_File path computation)\r\n * - the per-layer template path inside `feature/templates/`\r\n * - role-aware layer resolution (the `test` layer only fires when the\r\n * project enabled the `qa` role)\r\n *\r\n * It is deliberately pure and dependency-free so the orchestrator,\r\n * plan builder, and unit tests all share one definition.\r\n */\r\n\r\nimport { PROJECT_NAME_PATTERN, type Role } from '../schema/options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// FeatureLayer + slug\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const FEATURE_LAYERS = ['pm', 'arch', 'backend', 'frontend', 'test'] as const;\r\nexport type FeatureLayer = (typeof FEATURE_LAYERS)[number];\r\n\r\n/**\r\n * Slug pattern, intentionally identical to PROJECT_NAME_PATTERN so that\r\n * a slug can also be a project name and so the existing\r\n * `governance-lint` slug check applies without modification.\r\n */\r\nexport const FEATURE_SLUG_PATTERN = PROJECT_NAME_PATTERN;\r\nexport const FEATURE_SLUG_PATTERN_SOURCE = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\nexport function isValidFeatureSlug(value: string): boolean {\r\n return FEATURE_SLUG_PATTERN.test(value);\r\n}\r\n\r\nexport function isFeatureLayer(value: string): value is FeatureLayer {\r\n return (FEATURE_LAYERS as readonly string[]).includes(value);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// LayerArtifact — per-layer template + output path metadata\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface LayerArtifact {\r\n readonly layer: FeatureLayer;\r\n /**\r\n * Display label for human-readable output (summary / help).\r\n */\r\n readonly label: string;\r\n /**\r\n * Path of the template file relative to `tools/project-scaffolder/src/feature/templates/`.\r\n * The `placeholder` slot is used when scaffolding from `feature add`; the\r\n * `sample` slot is used when scaffolding the canonical sample feature\r\n * via `--sample-feature` (a per-slug subdirectory under\r\n * `templates/sample/<slug>/` overrides the placeholder).\r\n */\r\n readonly placeholderTemplate: string;\r\n /**\r\n * Compute the output path (POSIX, relative to project root) for a given slug.\r\n *\r\n * Frontend output uses `<slug>-web.md`. The web suffix is the only one we\r\n * scaffold today; multi-platform (mobile / miniapp) variants are out of\r\n * scope for the feature-subcommand v1 (Requirement 4.2 only mandates the\r\n * link target shape, not the file count).\r\n */\r\n readonly outputPath: (slug: string) => string;\r\n}\r\n\r\nexport const LAYER_ARTIFACTS: Record<FeatureLayer, LayerArtifact> = {\r\n pm: {\r\n layer: 'pm',\r\n label: 'PRD (docs/01-背景与需求)',\r\n placeholderTemplate: 'pm/prd.md.eta',\r\n outputPath: (slug) => `docs/01-背景与需求/prd-${slug}.md`,\r\n },\r\n arch: {\r\n layer: 'arch',\r\n label: 'ADR (docs/02-系统方案与架构)',\r\n placeholderTemplate: 'arch/adr.md.eta',\r\n // ADR file names also embed a 4-digit sequence number; the planner\r\n // resolves that prefix at scaffold time and prepends it. The slug-only\r\n // suffix is what this function returns; callers that need the full\r\n // path must compose the prefix.\r\n outputPath: (slug) => `docs/02-系统方案与架构/adr-${slug}.md`,\r\n },\r\n backend: {\r\n layer: 'backend',\r\n label: 'backend design (docs/04-后端详细设计)',\r\n placeholderTemplate: 'backend/design.md.eta',\r\n outputPath: (slug) => `docs/04-后端详细设计/${slug}.md`,\r\n },\r\n frontend: {\r\n layer: 'frontend',\r\n label: 'frontend design (docs/05-前端客户端详细设计)',\r\n placeholderTemplate: 'frontend/design.md.eta',\r\n outputPath: (slug) => `docs/05-前端客户端详细设计/${slug}-web.md`,\r\n },\r\n test: {\r\n layer: 'test',\r\n label: 'test plan (docs/07-质量与测试/test-plans)',\r\n placeholderTemplate: 'test/test-plan.md.eta',\r\n outputPath: (slug) => `docs/07-质量与测试/test-plans/${slug}.md`,\r\n },\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Role gating\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Layers that depend on a role being enabled in the project's\r\n * `options.roles` array. A layer absent from this map is unconditionally\r\n * available.\r\n *\r\n * `test` requires the `qa` role: the test plan output directory\r\n * `docs/07-质量与测试/test-plans/` only exists in projects scaffolded with\r\n * `--roles qa`, so emitting a test plan when qa is absent would write a\r\n * file into a non-existent role directory and break governance-lint.\r\n */\r\nconst LAYER_REQUIRED_ROLE: Partial<Record<FeatureLayer, Role>> = {\r\n test: 'qa',\r\n};\r\n\r\n/**\r\n * Returns true iff the project's role configuration permits this layer.\r\n */\r\nexport function layerEnabledByRoles(\r\n layer: FeatureLayer,\r\n roles: readonly Role[],\r\n): boolean {\r\n const required = LAYER_REQUIRED_ROLE[layer];\r\n if (required === undefined) return true;\r\n return roles.includes(required);\r\n}\r\n\r\n/**\r\n * Resolve the final list of layers to scaffold, given the user's request\r\n * and the project's role configuration.\r\n *\r\n * Inputs:\r\n * - `requested === 'all'` or `undefined` → start from every FeatureLayer\r\n * - `requested === FeatureLayer[]` → start from exactly that subset (order\r\n * is preserved by callers that care; this function returns canonical\r\n * FEATURE_LAYERS order for stability)\r\n *\r\n * Role gating is applied last and unconditionally: even an explicit\r\n * `--layer test` is dropped when the project lacks the `qa` role, with the\r\n * skip surfaced through the orchestrator's reporter (Requirement 2.5).\r\n *\r\n * Duplicates in `requested` are de-duplicated; output is in\r\n * FEATURE_LAYERS canonical order.\r\n */\r\nexport function resolveLayers(\r\n requested: readonly FeatureLayer[] | 'all' | undefined,\r\n roles: readonly Role[],\r\n): readonly FeatureLayer[] {\r\n const requestedSet =\r\n requested === undefined || requested === 'all'\r\n ? new Set<FeatureLayer>(FEATURE_LAYERS)\r\n : new Set<FeatureLayer>(requested);\r\n\r\n const out: FeatureLayer[] = [];\r\n for (const layer of FEATURE_LAYERS) {\r\n if (!requestedSet.has(layer)) continue;\r\n if (!layerEnabledByRoles(layer, roles)) continue;\r\n out.push(layer);\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * For a given (requested, roles) pair, return the layers that would be\r\n * dropped by role gating. The orchestrator surfaces these in the summary\r\n * so the user sees \"skipped: test (qa role disabled)\" rather than silent\r\n * absence.\r\n */\r\nexport function layersDroppedByRoles(\r\n requested: readonly FeatureLayer[] | 'all' | undefined,\r\n roles: readonly Role[],\r\n): readonly FeatureLayer[] {\r\n const requestedSet =\r\n requested === undefined || requested === 'all'\r\n ? new Set<FeatureLayer>(FEATURE_LAYERS)\r\n : new Set<FeatureLayer>(requested);\r\n\r\n const out: FeatureLayer[] = [];\r\n for (const layer of FEATURE_LAYERS) {\r\n if (!requestedSet.has(layer)) continue;\r\n if (layerEnabledByRoles(layer, roles)) continue;\r\n out.push(layer);\r\n }\r\n return out;\r\n}\r\n","/**\r\n * `feature add` subcommand argument parser.\r\n *\r\n * Mirrors the style of `parseCreateArgs` in `cli.ts`: hand-rolled parser\r\n * over a string array, throws UserInputError on every malformed input\r\n * shape so the CLI dispatcher maps to exit code 1 uniformly.\r\n */\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n FEATURE_LAYERS,\r\n FEATURE_SLUG_PATTERN_SOURCE,\r\n isFeatureLayer,\r\n isValidFeatureSlug,\r\n type FeatureLayer,\r\n} from './layers.js';\r\n\r\nexport interface FeatureAddFlags {\r\n /**\r\n * Resolved layer set:\r\n * - `'all'` ⇒ user passed `--layer all` explicitly\r\n * - `FeatureLayer[]` ⇒ user passed an explicit comma-separated subset\r\n * - `undefined` ⇒ user omitted `--layer`; orchestrator defaults to all\r\n */\r\n readonly layer?: readonly FeatureLayer[] | 'all';\r\n readonly force: boolean;\r\n readonly quiet: boolean;\r\n readonly verbose: boolean;\r\n}\r\n\r\nexport interface FeatureAddArgs {\r\n readonly slug: string;\r\n readonly flags: FeatureAddFlags;\r\n}\r\n\r\nconst ACCEPTED_LAYER_VALUES: readonly string[] = [...FEATURE_LAYERS, 'all'];\r\n\r\n/**\r\n * Parse the argument list following `feature add`. Accepts:\r\n * <slug> positional, required\r\n * --layer <list> optional; comma-separated; or the literal 'all'\r\n * --force optional; passes through to writer\r\n * --quiet / --verbose verbosity controls\r\n */\r\nexport function parseFeatureAdd(args: readonly string[]): FeatureAddArgs {\r\n let slug: string | undefined;\r\n let layer: readonly FeatureLayer[] | 'all' | undefined;\r\n let force = false;\r\n let quiet = false;\r\n let verbose = false;\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 '--force':\r\n force = 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 '--layer': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--layer requires a comma-separated list',\r\n `示例:--layer pm,backend,frontend;可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n layer = parseLayerArg(next);\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 feature add --help',\r\n );\r\n }\r\n if (slug === undefined) {\r\n slug = a;\r\n } else {\r\n throw new UserInputError(\r\n `unexpected extra argument: ${JSON.stringify(a)}`,\r\n 'feature add 每次只能处理一个 slug',\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (slug === undefined) {\r\n throw new UserInputError(\r\n 'missing <slug>',\r\n '请提供 feature slug,例如:create-keel feature add user-signup',\r\n );\r\n }\r\n\r\n // Slug pattern check — same regex as projectName, satisfying Req 1.3.\r\n if (!isValidFeatureSlug(slug)) {\r\n throw new UserInputError(\r\n `invalid feature slug: ${JSON.stringify(slug)}`,\r\n `slug 应匹配 ${FEATURE_SLUG_PATTERN_SOURCE},例如:user-signup`,\r\n );\r\n }\r\n\r\n const flags: FeatureAddFlags = {\r\n force,\r\n quiet,\r\n verbose,\r\n ...(layer !== undefined ? { layer } : {}),\r\n };\r\n\r\n return { slug, flags };\r\n}\r\n\r\n/**\r\n * Parse the `--layer` argument value. Accepts either the literal `'all'`\r\n * or a comma-separated list whose tokens all belong to FEATURE_LAYERS.\r\n *\r\n * Rejects empty input, mixed `all + concrete` lists (which would be\r\n * ambiguous), and duplicates — duplicates are most likely a typo and\r\n * silently de-duplicating could mask the user's mistake.\r\n */\r\nfunction parseLayerArg(raw: string): readonly FeatureLayer[] | 'all' {\r\n const parts = raw\r\n .split(',')\r\n .map((s) => s.trim())\r\n .filter((s) => s.length > 0);\r\n\r\n if (parts.length === 0) {\r\n throw new UserInputError(\r\n `invalid --layer value: ${JSON.stringify(raw)}`,\r\n `可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n\r\n if (parts.includes('all')) {\r\n if (parts.length > 1) {\r\n throw new UserInputError(\r\n `--layer all cannot be combined with other layer names`,\r\n `传 'all' 或一个明确子集,不要混用`,\r\n );\r\n }\r\n return 'all';\r\n }\r\n\r\n const seen = new Set<string>();\r\n const out: FeatureLayer[] = [];\r\n for (const token of parts) {\r\n if (!isFeatureLayer(token)) {\r\n throw new UserInputError(\r\n `invalid --layer value: ${JSON.stringify(token)}`,\r\n `可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n if (seen.has(token)) {\r\n throw new UserInputError(\r\n `duplicate layer: ${token}`,\r\n '--layer 中每个 layer 仅能出现一次',\r\n );\r\n }\r\n seen.add(token);\r\n out.push(token);\r\n }\r\n return out;\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\nimport { parseFeatureAdd, type FeatureAddArgs } from './feature/subcommand.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 * When true on a `create` invocation, the orchestrator runs the feature\r\n * subcommand for slug `user-signup` after the create flow completes,\r\n * populating a worked-example feature in the new project.\r\n */\r\n readonly sampleFeature?: boolean;\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\nexport type FeatureAddHandler = (input: FeatureAddArgs) => 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 * Optional handler for `feature add`. The binary entry point in\r\n * `index.ts` always provides one; tests may omit it when only the\r\n * `create` paths are exercised.\r\n */\r\n readonly onFeatureAdd?: FeatureAddHandler;\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 === 'feature') {\r\n return await dispatchFeature(io, rest);\r\n }\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 / feature。查看帮助:--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 let sampleFeature = false;\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 '--sample-feature':\r\n sampleFeature = 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 ...(sampleFeature ? { sampleFeature: true } : {}),\r\n };\r\n\r\n // Mutually exclusive: --sample-feature writes feature artifacts after\r\n // the create flow finishes. There is nothing to write to under\r\n // --dry-run, so combine them explicitly rejected.\r\n if (sampleFeature && dryRun) {\r\n throw new UserInputError(\r\n `--sample-feature cannot be combined with --dry-run`,\r\n '请去掉一项后重试',\r\n );\r\n }\r\n\r\n return { projectName, flags };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// `feature` command dispatch\r\n// ---------------------------------------------------------------------------\r\n\r\nasync function dispatchFeature(io: CliIO, args: readonly string[]): Promise<ExitCode> {\r\n // Help short-circuit for the feature command: `feature --help` /\r\n // `feature add --help` both fall through to the help screen.\r\n if (args.length === 0 || args.some((a) => a === '-h' || a === '--help')) {\r\n io.stdout(featureHelpText());\r\n return 0;\r\n }\r\n\r\n const [sub, ...rest] = args;\r\n if (sub !== 'add') {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `unknown feature subcommand: ${JSON.stringify(sub)}`,\r\n '当前可用:feature add <slug>',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n if (io.onFeatureAdd === undefined) {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `feature add is not wired in this entry point`,\r\n '这是一个 scaffolder 内部错误:onFeatureAdd 未注入',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n try {\r\n const parsed = parseFeatureAdd(rest);\r\n await io.onFeatureAdd(parsed);\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// 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 ' npx @wneng/create-keel feature add <slug> [options]',\r\n '',\r\n 'OPTIONS (create)',\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 ' --sample-feature After create, scaffold the user-signup sample feature',\r\n ' -h, --help Show this help',\r\n ' -v, --version Show the scaffolder version',\r\n '',\r\n 'OPTIONS (feature add)',\r\n ' --layer <list> Comma-separated layers to scaffold',\r\n ' (pm|arch|backend|frontend|test|all; default: all)',\r\n ' --force Overwrite existing Layer_Files for the requested layers',\r\n ' --quiet Only emit errors and the final summary',\r\n ' --verbose Emit per-file render/write logs',\r\n '',\r\n 'See .kiro/specs/feature-subcommand/ for the full specification.',\r\n ].join('\\n');\r\n}\r\n\r\nfunction featureHelpText(): string {\r\n return [\r\n 'create-keel feature — manage features inside an existing keel project',\r\n '',\r\n 'USAGE',\r\n ' npx @wneng/create-keel feature add <slug> [options]',\r\n '',\r\n 'OPTIONS',\r\n ' --layer <list> Comma-separated layers (pm|arch|backend|frontend|test|all)',\r\n ' --force Overwrite existing Layer_Files',\r\n ' --quiet Errors and summary only',\r\n ' --verbose Per-file render/write logs',\r\n '',\r\n 'EXAMPLES',\r\n ' feature add user-signup',\r\n ' feature add billing-portal --layer pm,backend',\r\n ' feature add legacy-import --force',\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 * Per-feature context. Present only when the renderer is invoked from\r\n * the feature subcommand or `--sample-feature`; project-creation\r\n * rendering leaves this undefined and the strict-variable proxy in\r\n * `render/renderer.ts` will throw if a non-feature template tries to\r\n * read it.\r\n */\r\n readonly feature?: FeatureRenderContext;\r\n}\r\n\r\nexport interface FeatureRenderContext {\r\n /** Validated kebab-case slug. */\r\n readonly slug: string;\r\n /** Title-cased version for headings (e.g. `User Signup`). */\r\n readonly title: string;\r\n /** Zero-padded 4-digit ADR sequence prefix (e.g. `0007`). */\r\n readonly nextAdrNumber: string;\r\n /**\r\n * Markdown-link target for the contracts entry of this feature, e.g.\r\n * `contracts/openapi/api.yaml#/paths/~1users~1signup`. The slash in the\r\n * path is escaped per JSON Pointer (RFC 6901) — `~1` for `/`.\r\n */\r\n readonly contractAnchor: 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 readonly feature?: FeatureRenderContext;\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 ...(input.feature !== undefined ? { feature: input.feature } : {}),\r\n };\r\n}\r\n\r\n/**\r\n * Build a FeatureRenderContext from a slug + ADR number.\r\n *\r\n * `slug` is assumed valid (caller filters via `isValidFeatureSlug` before\r\n * reaching here). `nextAdrNumber` is computed by the planner from the\r\n * existing `docs/02-系统方案与架构/adr-*.md` files.\r\n */\r\nexport function buildFeatureContext(\r\n slug: string,\r\n nextAdrNumber: string,\r\n): FeatureRenderContext {\r\n return {\r\n slug,\r\n title: slugToTitle(slug),\r\n nextAdrNumber,\r\n contractAnchor: `contracts/openapi/api.yaml#/paths/~1${slug}`,\r\n };\r\n}\r\n\r\n/** `user-signup` -> `User Signup`. Used for human-readable headings. */\r\nexport function slugToTitle(slug: string): string {\r\n return slug\r\n .split('-')\r\n .filter((s) => s.length > 0)\r\n .map((s) => s[0]!.toUpperCase() + s.slice(1))\r\n .join(' ');\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 // `feature` is intentionally only attached when the orchestrator\r\n // populated it; non-feature templates that try to read it will hit\r\n // the proxy's \"undefined template variable\" branch.\r\n ...(ctx.feature !== undefined ? { feature: ctx.feature } : {}),\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 * Slugs of features scaffolded into the project via the `feature add`\r\n * subcommand. Optional and omitted from the canonical output when empty\r\n * so projects created prior to v0.2.0 remain byte-identical after a\r\n * round trip through write → load.\r\n */\r\n readonly features?: readonly string[];\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\nconst FEATURE_SLUG_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 features: {\r\n type: 'array',\r\n items: { type: 'string', pattern: FEATURE_SLUG_PATTERN },\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<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 'features',\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 if (key === 'features') {\r\n // Omit when absent or empty so legacy v0.1.x projects stay byte-identical\r\n // after a round trip; only emit when the project has at least one feature.\r\n const features = metadata.features;\r\n if (features !== undefined && features.length > 0) {\r\n out[key] = [...features];\r\n }\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 features?: readonly string[];\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 ...(params.features !== undefined ? { features: [...params.features] } : {}),\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 * When true, skip the \"target directory is not empty\" preflight check.\r\n * Used by the feature subcommand which writes new files into an\r\n * already-scaffolded project. Default is false to preserve the create\r\n * command's existing safety check.\r\n */\r\n readonly allowNonEmptyTarget?: 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 && !opts.allowNonEmptyTarget) {\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 * Feature subcommand orchestrator — drives `feature add <slug>` end to end.\r\n *\r\n * load .scaffolder.json\r\n * ↓\r\n * resolve layers (apply role gating)\r\n * ↓\r\n * build FeaturePlan (renders templates + may emit OpenAPI edit)\r\n * ↓\r\n * filter out already-existing target paths (unless --force)\r\n * ↓\r\n * writePlan via the existing writer (rollback journal applies)\r\n * ↓\r\n * appendFeatureSlug to manifest (last step so writer rollback can revert it)\r\n * ↓\r\n * emit a summary the reporter can format\r\n *\r\n * Public entrypoint is `runFeatureAdd`. Helpers `splitPlanByExistence` and\r\n * `formatSummary` are exported so Property 4 / 5 / 10 can drive them\r\n * directly from in-memory inputs without touching the disk.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport type { FilePlan, PlannedFile } from '../plan/builder.js';\r\nimport { writePlan } from '../writer/writer.js';\r\nimport { loadMetadataFile, METADATA_FILENAME } from '../schema/metadata.js';\r\nimport { isValidFeatureSlug, layersDroppedByRoles, resolveLayers, type FeatureLayer } from './layers.js';\r\nimport { buildFeaturePlan, type FeaturePlanResult, type FeaturePlanSource } from './plan.js';\r\nimport { appendFeatureSlug } from './manifest-update.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public surface\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface RunFeatureAddInput {\r\n readonly slug: string;\r\n readonly cwd: string;\r\n /**\r\n * `undefined` ⇒ all FEATURE_LAYERS, role-gated by the project's roles.\r\n * Use `'all'` to be explicit. A non-empty subset selects exactly those\r\n * layers; role gating still applies.\r\n */\r\n readonly layers?: readonly FeatureLayer[] | 'all';\r\n /** `placeholder` for `feature add`; `sample` for `--sample-feature`. */\r\n readonly source: FeaturePlanSource;\r\n readonly force: boolean;\r\n readonly scaffolderVersion: string;\r\n readonly templatesRoot?: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface FeatureAddSummary {\r\n readonly slug: string;\r\n /** Layer_File paths actually written (POSIX, relative to project root). */\r\n readonly created: readonly string[];\r\n /** Pre-existing Layer_File paths that were left untouched (no --force). */\r\n readonly skipped: readonly string[];\r\n /** Layers dropped by role gating (e.g. `test` without `qa`). */\r\n readonly droppedByRoles: readonly FeatureLayer[];\r\n /** Anchor entries actually emitted (currently the OpenAPI path). */\r\n readonly anchorsInserted: readonly Anchor[];\r\n /** True iff `appendFeatureSlug` actually changed the manifest. */\r\n readonly manifestUpdated: boolean;\r\n /** Absolute path of the project's `.scaffolder.json`. */\r\n readonly metadataPath: string;\r\n}\r\n\r\nexport interface Anchor {\r\n readonly fromFile: string;\r\n readonly toTarget: string;\r\n}\r\n\r\nexport async function runFeatureAdd(input: RunFeatureAddInput): Promise<FeatureAddSummary> {\r\n // §1 — slug validation. UserInputError so the CLI maps to exit 1.\r\n if (!isValidFeatureSlug(input.slug)) {\r\n throw new UserInputError(\r\n `invalid feature slug: ${JSON.stringify(input.slug)}`,\r\n 'slug 应匹配 ^[a-z0-9][a-z0-9-]{0,62}$,例如:user-signup',\r\n );\r\n }\r\n\r\n // §2 — load manifest. Missing manifest is a UserInputError because the\r\n // caller is operating outside a keel-scaffolded project.\r\n let manifest;\r\n try {\r\n manifest = await loadMetadataFile(input.cwd);\r\n } catch (e) {\r\n const code = (e as NodeJS.ErrnoException).code;\r\n if (code === 'ENOENT') {\r\n throw new UserInputError(\r\n `no .scaffolder.json found at ${path.join(input.cwd, METADATA_FILENAME)}`,\r\n '请在 keel 项目根目录运行;或先用 create-keel create 创建项目',\r\n );\r\n }\r\n throw e;\r\n }\r\n\r\n // §3 — resolve layers + record role-gating drops for the summary.\r\n const requested = input.layers ?? 'all';\r\n const resolved = resolveLayers(requested, manifest.options.roles);\r\n const droppedByRoles = layersDroppedByRoles(requested, manifest.options.roles);\r\n\r\n // §4 — build the file plan.\r\n const featurePlan = await buildFeaturePlan({\r\n slug: input.slug,\r\n layers: resolved,\r\n source: input.source,\r\n options: manifest.options,\r\n cwd: input.cwd,\r\n scaffolderVersion: input.scaffolderVersion,\r\n ...(input.templatesRoot !== undefined ? { templatesRoot: input.templatesRoot } : {}),\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n\r\n // §5 — split into \"to-write\" and \"to-skip\" based on disk state. Under\r\n // --force we skip nothing and rely on the writer's overwrite behavior.\r\n const { toWrite, skipped } = await splitPlanByExistence(featurePlan, input.cwd, input.force);\r\n\r\n // §6 — execute the file writes.\r\n if (toWrite.files.length > 0) {\r\n await writePlan(toWrite, {\r\n targetDirectory: input.cwd,\r\n force: input.force,\r\n allowNonEmptyTarget: true,\r\n });\r\n }\r\n\r\n // §7 — anchors emitted. Currently the only inserted anchor is the\r\n // OpenAPI path entry; backend-design / frontend-design / test-plan\r\n // anchors live inside the rendered Layer_File content and are\r\n // validated by Property 6.\r\n const anchorsInserted: Anchor[] = [];\r\n if (\r\n featurePlan.openapiPathInserted !== undefined &&\r\n featurePlan.openapiAlreadyExisted === false &&\r\n toWrite.files.some((f) => f.targetPath === 'contracts/openapi/api.yaml')\r\n ) {\r\n anchorsInserted.push({\r\n fromFile: 'contracts/openapi/api.yaml',\r\n toTarget: featurePlan.openapiPathInserted,\r\n });\r\n }\r\n\r\n // §8 — append slug to manifest. Always last so writer rollback would\r\n // also revert any partial file state without leaving an orphaned slug\r\n // in the manifest.\r\n const manifestResult = await appendFeatureSlug(input.cwd, input.slug);\r\n\r\n return {\r\n slug: input.slug,\r\n created: toWrite.files.map((f) => f.targetPath),\r\n skipped,\r\n droppedByRoles,\r\n anchorsInserted,\r\n manifestUpdated: manifestResult.updated,\r\n metadataPath: manifestResult.metadataPath,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers (exported for property tests)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Split a feature plan into a \"files to write\" plan plus a list of\r\n * already-existing target paths that should be skipped.\r\n *\r\n * When `force` is true the entire plan goes to `toWrite` and `skipped`\r\n * is empty — the writer is responsible for the actual overwrite.\r\n */\r\nexport async function splitPlanByExistence(\r\n plan: FeaturePlanResult,\r\n cwd: string,\r\n force: boolean,\r\n): Promise<{ toWrite: FilePlan; skipped: readonly string[] }> {\r\n if (force) {\r\n return {\r\n toWrite: { files: plan.files, directories: plan.directories },\r\n skipped: [],\r\n };\r\n }\r\n\r\n const toWrite: PlannedFile[] = [];\r\n const skipped: string[] = [];\r\n\r\n for (const file of plan.files) {\r\n const exists = await fileExists(path.join(cwd, file.targetPath));\r\n if (exists) {\r\n skipped.push(file.targetPath);\r\n } else {\r\n toWrite.push(file);\r\n }\r\n }\r\n\r\n return {\r\n toWrite: { files: toWrite, directories: plan.directories },\r\n skipped,\r\n };\r\n}\r\n\r\nasync function fileExists(absPath: string): Promise<boolean> {\r\n try {\r\n await fs.access(absPath);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Render a summary payload as the canonical multi-section text the\r\n * reporter prints. Exported so Property 10 can assert structural\r\n * invariants on the text without owning a Reporter instance.\r\n *\r\n * Sections:\r\n * created: <path>\r\n * skipped: <path>\r\n * anchors: <fromFile> -> <toTarget>\r\n *\r\n * Empty sections are omitted entirely so the property doesn't have to\r\n * special-case \"no skips\" etc.\r\n */\r\nexport function formatSummary(summary: FeatureAddSummary): string {\r\n const lines: string[] = [];\r\n lines.push(`feature add: ${summary.slug}`);\r\n for (const p of summary.created) {\r\n lines.push(` created: ${p}`);\r\n }\r\n for (const p of summary.skipped) {\r\n lines.push(` skipped: ${p}`);\r\n }\r\n for (const a of summary.anchorsInserted) {\r\n lines.push(` anchors: ${a.fromFile} -> ${a.toTarget}`);\r\n }\r\n for (const layer of summary.droppedByRoles) {\r\n lines.push(` dropped (role gate): ${layer}`);\r\n }\r\n if (summary.manifestUpdated) {\r\n lines.push(` manifest: updated ${summary.metadataPath}`);\r\n }\r\n return lines.join('\\n');\r\n}\r\n","/**\r\n * Feature plan builder — turn a (slug, layers, source) tuple into a\r\n * concrete FilePlan the writer can execute.\r\n *\r\n * Differences from `plan/builder.ts` (which serves the `create` command):\r\n * - templates are rooted at `<scaffolder-pkg>/src/feature/templates/`,\r\n * not the per-fragment `files/` directory\r\n * - each layer has at most one output file; conflict resolution is not\r\n * needed\r\n * - ADR file paths embed an auto-numbered prefix that this module\r\n * computes by scanning existing `docs/02-系统方案与架构/adr-*.md`\r\n * - the plan optionally includes one extra entry for the in-place\r\n * edit of `contracts/openapi/api.yaml` produced by `openapi-edit`\r\n *\r\n * The output type is the same `FilePlan` from `plan/builder.ts` so the\r\n * existing writer can execute the result without modification.\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 { renderFile } from '../render/renderer.js';\r\nimport {\r\n buildFeatureContext,\r\n buildRenderContext,\r\n type RenderContext,\r\n} from '../render/context.js';\r\nimport type { Options } from '../schema/options.js';\r\nimport type { FilePlan, PlannedFile } from '../plan/builder.js';\r\nimport {\r\n FEATURE_LAYERS,\r\n LAYER_ARTIFACTS,\r\n type FeatureLayer,\r\n} from './layers.js';\r\nimport { addPathStub } from './openapi-edit.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Inputs / outputs\r\n// ---------------------------------------------------------------------------\r\n\r\nexport type FeaturePlanSource = 'placeholder' | 'sample';\r\n\r\nexport interface FeaturePlanInput {\r\n readonly slug: string;\r\n readonly layers: readonly FeatureLayer[];\r\n readonly source: FeaturePlanSource;\r\n readonly options: Options;\r\n readonly cwd: string;\r\n readonly scaffolderVersion: string;\r\n /** Override for tests; defaults to scanning real disk under `cwd`. */\r\n readonly templatesRoot?: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface FeaturePlanResult extends FilePlan {\r\n /** The OpenAPI path that was inserted (or already existed); undefined when no contract was edited. */\r\n readonly openapiPathInserted?: string;\r\n /** True iff the OpenAPI insert was a no-op because the path was already present. */\r\n readonly openapiAlreadyExisted?: boolean;\r\n /** Per-layer planned output paths, useful for the reporter and tests. */\r\n readonly outputs: ReadonlyMap<FeatureLayer, string>;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\nexport async function buildFeaturePlan(input: FeaturePlanInput): Promise<FeaturePlanResult> {\r\n const templatesRoot = input.templatesRoot ?? defaultFeatureTemplatesRoot();\r\n const sourceRoot =\r\n input.source === 'sample'\r\n ? path.join(templatesRoot, 'sample', input.slug)\r\n : templatesRoot;\r\n\r\n const nextAdrNumber = await computeAdrNumberForSlug(input.cwd, input.slug);\r\n const renderContext = buildRenderContext({\r\n options: input.options,\r\n scaffolderVersion: input.scaffolderVersion,\r\n feature: buildFeatureContext(input.slug, nextAdrNumber),\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n\r\n const files: PlannedFile[] = [];\r\n const outputs = new Map<FeatureLayer, string>();\r\n\r\n for (const layer of input.layers) {\r\n const artifact = LAYER_ARTIFACTS[layer];\r\n const targetPath = computeTargetPath(layer, input.slug, nextAdrNumber);\r\n outputs.set(layer, targetPath);\r\n const templatePath = path.join(sourceRoot, artifact.placeholderTemplate);\r\n const content = await renderFeatureTemplate(templatePath, renderContext, layer, input.source);\r\n files.push({\r\n targetPath,\r\n content,\r\n contributedBy: `feature:${layer}`,\r\n });\r\n }\r\n\r\n // OpenAPI editing: triggered for any run that includes the backend\r\n // layer and has a contract enabled. The sample feature passes a richer\r\n // request body / response schema to satisfy Requirement 6.4; the\r\n // placeholder run uses a minimal stub.\r\n let openapiPathInserted: string | undefined;\r\n let openapiAlreadyExisted: boolean | undefined;\r\n if (input.layers.includes('backend') && input.options.contract !== 'none') {\r\n const apiPath = 'contracts/openapi/api.yaml';\r\n const apiAbs = path.join(input.cwd, apiPath);\r\n let currentText: string;\r\n try {\r\n currentText = await fs.readFile(apiAbs, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `expected ${apiPath} to exist when contract != 'none' but cannot read it: ${(e as Error).message}`,\r\n '请确认 contracts/openapi/api.yaml 存在;或将 .scaffolder.json 中 options.contract 设为 none',\r\n );\r\n }\r\n const editResult =\r\n input.source === 'sample' && input.slug === 'user-signup'\r\n ? addPathStub({\r\n currentText,\r\n slug: input.slug,\r\n title: renderContext.feature!.title,\r\n // The sample feature owns its own canonical path + schema,\r\n // satisfying Req 6.4 (request body schema + at least one\r\n // response body schema).\r\n path: '/users/signup',\r\n operation: 'post',\r\n requestBody: {\r\n required: true,\r\n content: {\r\n 'application/json': {\r\n schema: {\r\n type: 'object',\r\n required: ['email', 'password'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string', minLength: 8 },\r\n },\r\n },\r\n },\r\n },\r\n },\r\n responses: {\r\n '201': {\r\n description: 'User created in pending state; verification email sent',\r\n content: {\r\n 'application/json': {\r\n schema: {\r\n type: 'object',\r\n required: ['id', 'email', 'state'],\r\n properties: {\r\n id: { type: 'string', format: 'uuid' },\r\n email: { type: 'string', format: 'email' },\r\n state: { type: 'string', enum: ['pending'] },\r\n },\r\n },\r\n },\r\n },\r\n },\r\n '409': {\r\n description: 'Email already registered (generic, does not leak existence)',\r\n },\r\n '429': { description: 'Rate-limited' },\r\n },\r\n })\r\n : addPathStub({\r\n currentText,\r\n slug: input.slug,\r\n title: renderContext.feature!.title,\r\n });\r\n files.push({\r\n targetPath: apiPath,\r\n content: editResult.nextText,\r\n contributedBy: 'feature:openapi',\r\n });\r\n openapiPathInserted = editResult.insertedPath;\r\n openapiAlreadyExisted = editResult.alreadyExisted;\r\n }\r\n\r\n const sortedFiles = [...files].sort((a, b) =>\r\n a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0,\r\n );\r\n const directories = collectDirectories(sortedFiles);\r\n\r\n return {\r\n files: sortedFiles,\r\n directories,\r\n outputs,\r\n ...(openapiPathInserted !== undefined ? { openapiPathInserted } : {}),\r\n ...(openapiAlreadyExisted !== undefined ? { openapiAlreadyExisted } : {}),\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internals\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Compute the actual on-disk output path for a layer + slug, including\r\n * the ADR auto-number prefix where needed.\r\n */\r\nfunction computeTargetPath(\r\n layer: FeatureLayer,\r\n slug: string,\r\n nextAdrNumber: string,\r\n): string {\r\n if (layer === 'arch') {\r\n // LAYER_ARTIFACTS.arch.outputPath returns `docs/02-.../adr-<slug>.md`;\r\n // splice in the 4-digit number to match the ADR naming convention\r\n // (`adr-NNNN-<slug>.md`) defined in docs/README.md §6.1.\r\n return `docs/02-系统方案与架构/adr-${nextAdrNumber}-${slug}.md`;\r\n }\r\n return LAYER_ARTIFACTS[layer].outputPath(slug);\r\n}\r\n\r\n/**\r\n * Read + render a per-layer template. When the source is `sample` and the\r\n * sample directory exists, fall back to placeholder for any layer the\r\n * sample omits — but in practice the sample is expected to ship every\r\n * layer it intends to populate, so a missing sample template is treated\r\n * as authoring error.\r\n */\r\nasync function renderFeatureTemplate(\r\n templatePath: string,\r\n context: RenderContext,\r\n layer: FeatureLayer,\r\n source: FeaturePlanSource,\r\n): Promise<string> {\r\n try {\r\n return await renderFile(templatePath, context);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `feature template missing for layer \"${layer}\" (${source}): ${templatePath}: ${(e as Error).message}`,\r\n source === 'sample'\r\n ? `请确认 src/feature/templates/sample/<slug>/${LAYER_ARTIFACTS[layer].placeholderTemplate} 存在`\r\n : '请确认 src/feature/templates/${layer}/ 下存在 ' + LAYER_ARTIFACTS[layer].placeholderTemplate,\r\n );\r\n }\r\n}\r\n\r\nconst ADR_FILE_RE = /^adr-(\\d{4,})-/;\r\n\r\n/**\r\n * Compute the ADR number to use for `slug`:\r\n * - if an existing `adr-NNNN-<slug>.md` is found, return that NNNN so\r\n * a re-run lands on the same path (and gets skipped by the\r\n * orchestrator's existence check)\r\n * - otherwise return one greater than the highest existing ADR number,\r\n * zero-padded to 4 digits\r\n *\r\n * Missing directory or no ADRs → `'0001'`.\r\n */\r\nasync function computeAdrNumberForSlug(cwd: string, slug: string): Promise<string> {\r\n const dir = path.join(cwd, 'docs', '02-系统方案与架构');\r\n let entries: string[];\r\n try {\r\n entries = await fs.readdir(dir);\r\n } catch {\r\n return '0001';\r\n }\r\n const slugRe = new RegExp(`^adr-(\\\\d{4,})-${escapeRegex(slug)}\\\\.md$`);\r\n let max = 0;\r\n for (const name of entries) {\r\n const slugMatch = slugRe.exec(name);\r\n if (slugMatch) {\r\n // Same slug already has an ADR — reuse its number so writes are\r\n // idempotent against existing on-disk artifacts.\r\n return slugMatch[1]!;\r\n }\r\n const m = ADR_FILE_RE.exec(name);\r\n if (m) {\r\n const n = parseInt(m[1]!, 10);\r\n if (!Number.isNaN(n) && n > max) max = n;\r\n }\r\n }\r\n return String(max + 1).padStart(4, '0');\r\n}\r\n\r\nfunction escapeRegex(s: string): string {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n}\r\n\r\n/**\r\n * Build the unique, sorted list of POSIX directories implied by the file\r\n * targets. Mirrors the helper in `plan/builder.ts`.\r\n */\r\nfunction collectDirectories(files: readonly PlannedFile[]): readonly string[] {\r\n const set = 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 set.add(cur);\r\n cur = path.posix.dirname(cur);\r\n }\r\n }\r\n return [...set].sort();\r\n}\r\n\r\n/**\r\n * Resolve `<scaffolder-pkg>/src/feature/templates/`. Walks up from this\r\n * file to find the package root (mirrors `fragments/loader.ts`).\r\n */\r\nexport function defaultFeatureTemplatesRoot(): string {\r\n const here = path.dirname(fileURLToPath(import.meta.url));\r\n const pkgRoot = findPackageRoot(here);\r\n return path.join(pkgRoot, 'src', 'feature', 'templates');\r\n}\r\n\r\nfunction findPackageRoot(start: string): string {\r\n let dir = start;\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 安装完整,src/feature/templates/ 与 package.json 位于同一层级',\r\n );\r\n}\r\n\r\n// Suppress unused-import error in some bundlers when FEATURE_LAYERS is\r\n// only referenced via type-position elsewhere.\r\nexport { FEATURE_LAYERS };\r\n","/**\r\n * OpenAPI document editor — round-trip-safe insertion of one path entry.\r\n *\r\n * Why this module is separate from `feature/plan.ts`:\r\n *\r\n * `contracts/openapi/api.yaml` is hand-edited by humans and contains\r\n * key ordering, comments, and quoting choices that have semantic value\r\n * to the author. The render pipeline used elsewhere (`render/renderer.ts`)\r\n * is one-way text templating and would destroy that structure. The `yaml`\r\n * package's `parseDocument` API (already a runtime dep at v2.5.1) preserves\r\n * the source-level AST, so `addPathStub` can mutate exactly one node and\r\n * re-emit a document whose unrelated content is byte-stable.\r\n *\r\n * Validates: Requirements 5.1 (round-trip), 5.2 (path-add preserves\r\n * untouched paths), 5.3 (YAML syntax error → UserInputError with line/col),\r\n * 5.4 (scaffolder-emitted documents satisfy round-trip).\r\n */\r\n\r\nimport { isMap, isScalar, parseDocument, YAMLMap, type Document, type YAMLError } from 'yaml';\r\n\r\nimport { TemplateError, UserInputError } from '../errors.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface OpenApiEditInput {\r\n /** Full file content of `contracts/openapi/api.yaml`. */\r\n readonly currentText: string;\r\n /** Feature slug; the new path will be `/<slug>` unless overridden. */\r\n readonly slug: string;\r\n /** Human-readable summary used inside the inserted path stub. */\r\n readonly title: string;\r\n /**\r\n * Optional override for the path key. The default `/<slug>` is what\r\n * `feature add` and `--sample-feature` use; the sample feature's own\r\n * path (`/users/signup`) overrides this.\r\n */\r\n readonly path?: string;\r\n /**\r\n * Optional override for the operation body. When omitted, a minimal\r\n * stub with only a 200 response is inserted. Callers (notably the\r\n * `--sample-feature` flow) can pass a richer shape including\r\n * requestBody and per-status response schemas to satisfy\r\n * Requirement 6.4.\r\n */\r\n readonly operation?: 'get' | 'post';\r\n readonly requestBody?: Record<string, unknown>;\r\n readonly responses?: Record<string, Record<string, unknown>>;\r\n}\r\n\r\nexport interface OpenApiEditResult {\r\n /** Serialized YAML; equals `currentText` when `alreadyExisted` is true. */\r\n readonly nextText: string;\r\n /** The full path key that was inserted (or already present). */\r\n readonly insertedPath: string;\r\n /** True if the path was already in `paths`; in that case `nextText === currentText`. */\r\n readonly alreadyExisted: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Round-trip parse + serialize (Property 8)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse and serialize without intervening edits.\r\n *\r\n * This is the canonical implementation for Requirement 5.1 / 5.4 / Property\r\n * 8: it must produce a document semantically equivalent to the input. It\r\n * is exported so the property test exercises exactly the same code path\r\n * as production callers.\r\n *\r\n * Throws UserInputError on YAML syntax error (Req 5.3); throws\r\n * TemplateError on structural problems (root not a map, etc.).\r\n */\r\nexport function roundTrip(currentText: string): string {\r\n const doc = parseAndValidate(currentText);\r\n return serializeDocument(doc);\r\n}\r\n\r\n/**\r\n * Insert one path entry into the OpenAPI document.\r\n *\r\n * Algorithm:\r\n * 1. parse via `parseDocument` (preserves order + comments + quoting)\r\n * 2. ensure the root is a map and has a `paths` map (create empty when\r\n * `paths: {}` was the syntactic form; reject when present but not a map)\r\n * 3. if the target path key already exists, return `alreadyExisted: true`\r\n * with `nextText === currentText` (zero-byte change satisfies Req 5.2's\r\n * trivial case)\r\n * 4. otherwise insert one new entry whose value is a minimal stub\r\n * (`get` operation with a 200 response)\r\n * 5. emit via `Document.toString` with options matching `parseDocument`'s\r\n * defaults so unrelated content is byte-stable\r\n */\r\nexport function addPathStub(input: OpenApiEditInput): OpenApiEditResult {\r\n const insertedPath = input.path ?? `/${input.slug}`;\r\n\r\n const doc = parseAndValidate(input.currentText);\r\n\r\n // §2: ensure paths map exists and is a map. The \"paths key absent or\r\n // present-but-empty\" cases both mean: create an empty map and proceed.\r\n // YAML lets `paths:` (with nothing on the right) parse to a Scalar\r\n // whose value is null; we treat that the same as a missing key.\r\n const pathsNode = doc.get('paths', true);\r\n let pathsMap: YAMLMap;\r\n if (\r\n pathsNode === undefined ||\r\n pathsNode === null ||\r\n (isScalar(pathsNode) && pathsNode.value === null)\r\n ) {\r\n pathsMap = new YAMLMap();\r\n // Force block style so a single inserted entry is human-readable;\r\n // the empty `{}` form would otherwise persist and produce a giant\r\n // single-line flow map after insertion.\r\n pathsMap.flow = false;\r\n doc.set('paths', pathsMap);\r\n } else if (isMap(pathsNode)) {\r\n pathsMap = pathsNode;\r\n // If the existing map was empty + flow (`paths: {}`), switch it to\r\n // block so the inserted entry uses block style. Pre-populated maps\r\n // that the user explicitly wrote in flow are left alone.\r\n if (pathsMap.items.length === 0 && pathsMap.flow) {\r\n pathsMap.flow = false;\r\n }\r\n } else {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml: 'paths' is present but is not a map`,\r\n '请把 paths 字段写成 YAML 映射(例如 paths: {})',\r\n );\r\n }\r\n\r\n // §3: idempotence — already-present path key is a no-op.\r\n if (pathsMap.has(insertedPath)) {\r\n return {\r\n nextText: input.currentText,\r\n insertedPath,\r\n alreadyExisted: true,\r\n };\r\n }\r\n\r\n // §4: create the stub. Build with createNode so quoting / styling\r\n // matches the surrounding document.\r\n const operationKey = input.operation ?? 'get';\r\n const responses = input.responses ?? { '200': { description: 'OK' } };\r\n const operationBody: Record<string, unknown> = {\r\n summary: input.title,\r\n operationId: slugToOperationId(input.slug),\r\n ...(input.requestBody !== undefined ? { requestBody: input.requestBody } : {}),\r\n responses,\r\n };\r\n const stub = doc.createNode({\r\n [operationKey]: operationBody,\r\n });\r\n pathsMap.set(insertedPath, stub);\r\n\r\n // §5: serialize.\r\n const nextText = serializeDocument(doc);\r\n\r\n return {\r\n nextText,\r\n insertedPath,\r\n alreadyExisted: false,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internals\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse and assert the document is structurally usable. Returns the\r\n * Document so callers can inspect / mutate; throws on every failure mode\r\n * Requirement 5.3 cares about.\r\n */\r\nfunction parseAndValidate(currentText: string): Document {\r\n const doc = parseDocument(currentText, {\r\n // keep node identity stable across parse → toString\r\n keepSourceTokens: true,\r\n });\r\n\r\n if (doc.errors.length > 0) {\r\n const first = doc.errors[0]!;\r\n throw yamlSyntaxError(first);\r\n }\r\n\r\n // contents may be null for an empty document, which is not a usable\r\n // OpenAPI file — surface as TemplateError, not UserInput.\r\n if (doc.contents === null) {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml is empty; an OpenAPI 3.x document is required`,\r\n '请保留至少 openapi/info/paths 三个顶层字段',\r\n );\r\n }\r\n\r\n if (!isMap(doc.contents)) {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml root must be a YAML mapping`,\r\n '请确认文件以 openapi: ... 开头,根是一个映射',\r\n );\r\n }\r\n\r\n return doc;\r\n}\r\n\r\n/**\r\n * Re-serialize a Document with options that match the parser's defaults.\r\n *\r\n * Notes on the options:\r\n * - `lineWidth: 0` disables soft-wrapping. Folded scalars in OpenAPI\r\n * description fields would otherwise be reformatted at 80 columns,\r\n * which counts as unrelated change and breaks the round-trip property.\r\n * - `indent: 2` matches the dominant style of OpenAPI files emitted by\r\n * editors; `parseDocument` does not inspect the input's indent and\r\n * always re-emits at this width, so we pin it explicitly.\r\n * - we do NOT pass `simpleKeys: true`; the default already produces the\r\n * `'200':` quoted form OpenAPI authors expect.\r\n */\r\nfunction serializeDocument(doc: Document): string {\r\n return doc.toString({\r\n lineWidth: 0,\r\n indent: 2,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a yaml package YAMLError into a UserInputError carrying\r\n * line/column info, satisfying Req 5.3.\r\n */\r\nfunction yamlSyntaxError(err: YAMLError): UserInputError {\r\n // YAMLError exposes `linePos` (start/end). Fall back to byte offset only\r\n // when linePos is missing, which the v2 parser populates for syntax errors.\r\n const linePos = err.linePos;\r\n const where =\r\n linePos !== undefined\r\n ? `line ${linePos[0].line}, column ${linePos[0].col}`\r\n : `offset ${err.pos[0]}`;\r\n return new UserInputError(\r\n `contracts/openapi/api.yaml: YAML syntax error at ${where}: ${err.message}`,\r\n '修正 YAML 语法后重试,常见原因:缩进 / 引号 / 冒号后空格缺失',\r\n );\r\n}\r\n\r\n/**\r\n * `user-signup` → `userSignup`; used as a minimally reasonable\r\n * `operationId`. OpenAPI spec recommends `operationId` be unique across the\r\n * document, which is satisfied because slugs are unique within a project\r\n * (Req 7.2 enforces that at the manifest layer).\r\n */\r\nexport function slugToOperationId(slug: string): string {\r\n return slug.replace(/-([a-z0-9])/g, (_m, c: string) => c.toUpperCase());\r\n}\r\n","/**\r\n * Manifest update — append a feature slug to `.scaffolder.json`'s\r\n * `features` array.\r\n *\r\n * Behavior contract (Requirements 7.1 / 7.2 / 7.3 / 7.4):\r\n * - missing `features` field is treated as empty array (Req 7.4)\r\n * - slug already present → no write, no error (Req 7.2)\r\n * - slug not present → append, re-canonicalise, write back via\r\n * `writeMetadataFile` so the canonical key order from\r\n * `schema/metadata.ts` is preserved\r\n * - every field other than `features` (and the implicit `generatedAt`\r\n * freshness) round-trips byte-identical, because we never construct\r\n * the metadata from scratch — we always start from `loadMetadataFile`\r\n * and only touch `features`\r\n *\r\n * The function is intentionally *not* responsible for atomicity with the\r\n * file plan; the orchestrator guarantees the manifest update is the last\r\n * step in the plan ordering, so a writer rollback can also revert it via\r\n * the existing journal.\r\n */\r\n\r\nimport * as path from 'node:path';\r\n\r\nimport {\r\n METADATA_FILENAME,\r\n loadMetadataFile,\r\n writeMetadataFile,\r\n type ScaffolderMetadata,\r\n} from '../schema/metadata.js';\r\n\r\nexport interface AppendFeatureSlugResult {\r\n /**\r\n * True iff the manifest was changed and rewritten. False when the slug\r\n * was already present (Req 7.2 idempotence).\r\n */\r\n readonly updated: boolean;\r\n /**\r\n * The full path of the manifest file. Useful for the reporter even when\r\n * `updated === false` so the summary can still cite where the (already\r\n * up-to-date) file lives.\r\n */\r\n readonly metadataPath: string;\r\n}\r\n\r\n/**\r\n * Append `slug` to the project's manifest under `features`.\r\n *\r\n * `projectDirectory` is the directory containing `.scaffolder.json` (i.e.\r\n * the project root). `loadMetadataFile` already validates the manifest\r\n * against the v0.2.0+ schema; if the manifest is missing or malformed,\r\n * the underlying I/O / validation error propagates unchanged so the\r\n * subcommand maps it to a UserInputError at a higher level.\r\n */\r\nexport async function appendFeatureSlug(\r\n projectDirectory: string,\r\n slug: string,\r\n): Promise<AppendFeatureSlugResult> {\r\n const manifest = await loadMetadataFile(projectDirectory);\r\n const next = withFeatureSlug(manifest, slug);\r\n const metadataPath = path.join(projectDirectory, METADATA_FILENAME);\r\n\r\n if (next === manifest) {\r\n return { updated: false, metadataPath };\r\n }\r\n\r\n await writeMetadataFile(projectDirectory, next);\r\n return { updated: true, metadataPath };\r\n}\r\n\r\n/**\r\n * Pure helper: produce a new ScaffolderMetadata with `slug` appended to\r\n * `features`. Returns the input by reference when no change is needed —\r\n * this is what makes Property 11's \"absent ⇒ unchanged\" assertion hold\r\n * at the object-identity level.\r\n *\r\n * Exported for the property test so the property runs without touching\r\n * the disk.\r\n */\r\nexport function withFeatureSlug(\r\n manifest: ScaffolderMetadata,\r\n slug: string,\r\n): ScaffolderMetadata {\r\n const existing = manifest.features ?? [];\r\n if (existing.includes(slug)) {\r\n return manifest;\r\n }\r\n return {\r\n ...manifest,\r\n features: [...existing, slug],\r\n };\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, `feature add` to the feature\r\n * orchestrator, and translate the returned exit code into process.exit.\r\n */\r\n\r\nimport { run, type CreateCommandInput } from './cli.js';\r\nimport { runCreate } from './orchestrator.js';\r\nimport { runFeatureAdd } from './feature/orchestrator.js';\r\nimport type { FeatureAddArgs } from './feature/subcommand.js';\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\n\r\nasync function handleCreate(input: CreateCommandInput): Promise<void> {\r\n const result = 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 // --sample-feature: after a successful create (and only when not\r\n // dry-run; the parser rejects that combination), scaffold the\r\n // canonical sample feature into the freshly created project so new\r\n // users have a worked example to read.\r\n if (input.flags.sampleFeature === true && !result.dryRun) {\r\n await runFeatureAdd({\r\n slug: 'user-signup',\r\n cwd: result.targetDirectory,\r\n source: 'sample',\r\n // Force is safe here: nothing pre-existing in a brand new project,\r\n // so the writer's nonEmpty check is the only thing to bypass.\r\n force: true,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n });\r\n }\r\n}\r\n\r\nasync function handleFeatureAdd(input: FeatureAddArgs): Promise<void> {\r\n const cwd = process.cwd();\r\n await runFeatureAdd({\r\n slug: input.slug,\r\n cwd,\r\n ...(input.flags.layer !== undefined ? { layers: input.flags.layer } : {}),\r\n source: 'placeholder',\r\n force: input.flags.force,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\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 onFeatureAdd: handleFeatureAdd,\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","fs","path","SEMVER_PATTERN","FRAGMENT_NAME_PATTERN","FEATURE_SLUG_PATTERN","ajv","Ajv","addFormats","validateFn","collectIssues","fs","fs","path","fs","fs","path","fs","accessSync","path","fileURLToPath","fs","fileURLToPath","findPackageRoot","accessSync","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/schema/options.ts","../src/errors.ts","../src/feature/layers.ts","../src/feature/subcommand.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/feature/orchestrator.ts","../src/feature/plan.ts","../src/feature/openapi-edit.ts","../src/feature/manifest-update.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.2.1';\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 * FeatureLayer model — the five layers a feature can be scaffolded into.\r\n *\r\n * This module is the single source of truth for:\r\n * - the FeatureLayer string literal union\r\n * - the per-layer output path scheme (Layer_File path computation)\r\n * - the per-layer template path inside `feature/templates/`\r\n * - role-aware layer resolution (the `test` layer only fires when the\r\n * project enabled the `qa` role)\r\n *\r\n * It is deliberately pure and dependency-free so the orchestrator,\r\n * plan builder, and unit tests all share one definition.\r\n */\r\n\r\nimport { PROJECT_NAME_PATTERN, type Role } from '../schema/options.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// FeatureLayer + slug\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const FEATURE_LAYERS = ['pm', 'arch', 'backend', 'frontend', 'test'] as const;\r\nexport type FeatureLayer = (typeof FEATURE_LAYERS)[number];\r\n\r\n/**\r\n * Slug pattern, intentionally identical to PROJECT_NAME_PATTERN so that\r\n * a slug can also be a project name and so the existing\r\n * `governance-lint` slug check applies without modification.\r\n */\r\nexport const FEATURE_SLUG_PATTERN = PROJECT_NAME_PATTERN;\r\nexport const FEATURE_SLUG_PATTERN_SOURCE = '^[a-z0-9][a-z0-9-]{0,62}$';\r\n\r\nexport function isValidFeatureSlug(value: string): boolean {\r\n return FEATURE_SLUG_PATTERN.test(value);\r\n}\r\n\r\nexport function isFeatureLayer(value: string): value is FeatureLayer {\r\n return (FEATURE_LAYERS as readonly string[]).includes(value);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// LayerArtifact — per-layer template + output path metadata\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface LayerArtifact {\r\n readonly layer: FeatureLayer;\r\n /**\r\n * Display label for human-readable output (summary / help).\r\n */\r\n readonly label: string;\r\n /**\r\n * Path of the template file relative to `tools/project-scaffolder/src/feature/templates/`.\r\n * The `placeholder` slot is used when scaffolding from `feature add`; the\r\n * `sample` slot is used when scaffolding the canonical sample feature\r\n * via `--sample-feature` (a per-slug subdirectory under\r\n * `templates/sample/<slug>/` overrides the placeholder).\r\n */\r\n readonly placeholderTemplate: string;\r\n /**\r\n * Compute the output path (POSIX, relative to project root) for a given slug.\r\n *\r\n * Frontend output uses `<slug>-web.md`. The web suffix is the only one we\r\n * scaffold today; multi-platform (mobile / miniapp) variants are out of\r\n * scope for the feature-subcommand v1 (Requirement 4.2 only mandates the\r\n * link target shape, not the file count).\r\n */\r\n readonly outputPath: (slug: string) => string;\r\n}\r\n\r\nexport const LAYER_ARTIFACTS: Record<FeatureLayer, LayerArtifact> = {\r\n pm: {\r\n layer: 'pm',\r\n label: 'PRD (docs/01-背景与需求)',\r\n placeholderTemplate: 'pm/prd.md.eta',\r\n outputPath: (slug) => `docs/01-背景与需求/prd-${slug}.md`,\r\n },\r\n arch: {\r\n layer: 'arch',\r\n label: 'ADR (docs/02-系统方案与架构)',\r\n placeholderTemplate: 'arch/adr.md.eta',\r\n // ADR file names also embed a 4-digit sequence number; the planner\r\n // resolves that prefix at scaffold time and prepends it. The slug-only\r\n // suffix is what this function returns; callers that need the full\r\n // path must compose the prefix.\r\n outputPath: (slug) => `docs/02-系统方案与架构/adr-${slug}.md`,\r\n },\r\n backend: {\r\n layer: 'backend',\r\n label: 'backend design (docs/04-后端详细设计)',\r\n placeholderTemplate: 'backend/design.md.eta',\r\n outputPath: (slug) => `docs/04-后端详细设计/${slug}.md`,\r\n },\r\n frontend: {\r\n layer: 'frontend',\r\n label: 'frontend design (docs/05-前端客户端详细设计)',\r\n placeholderTemplate: 'frontend/design.md.eta',\r\n outputPath: (slug) => `docs/05-前端客户端详细设计/${slug}-web.md`,\r\n },\r\n test: {\r\n layer: 'test',\r\n label: 'test plan (docs/07-质量与测试/test-plans)',\r\n placeholderTemplate: 'test/test-plan.md.eta',\r\n outputPath: (slug) => `docs/07-质量与测试/test-plans/${slug}.md`,\r\n },\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Role gating\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Layers that depend on a role being enabled in the project's\r\n * `options.roles` array. A layer absent from this map is unconditionally\r\n * available.\r\n *\r\n * `test` requires the `qa` role: the test plan output directory\r\n * `docs/07-质量与测试/test-plans/` only exists in projects scaffolded with\r\n * `--roles qa`, so emitting a test plan when qa is absent would write a\r\n * file into a non-existent role directory and break governance-lint.\r\n */\r\nconst LAYER_REQUIRED_ROLE: Partial<Record<FeatureLayer, Role>> = {\r\n test: 'qa',\r\n};\r\n\r\n/**\r\n * Returns true iff the project's role configuration permits this layer.\r\n */\r\nexport function layerEnabledByRoles(\r\n layer: FeatureLayer,\r\n roles: readonly Role[],\r\n): boolean {\r\n const required = LAYER_REQUIRED_ROLE[layer];\r\n if (required === undefined) return true;\r\n return roles.includes(required);\r\n}\r\n\r\n/**\r\n * Resolve the final list of layers to scaffold, given the user's request\r\n * and the project's role configuration.\r\n *\r\n * Inputs:\r\n * - `requested === 'all'` or `undefined` → start from every FeatureLayer\r\n * - `requested === FeatureLayer[]` → start from exactly that subset (order\r\n * is preserved by callers that care; this function returns canonical\r\n * FEATURE_LAYERS order for stability)\r\n *\r\n * Role gating is applied last and unconditionally: even an explicit\r\n * `--layer test` is dropped when the project lacks the `qa` role, with the\r\n * skip surfaced through the orchestrator's reporter (Requirement 2.5).\r\n *\r\n * Duplicates in `requested` are de-duplicated; output is in\r\n * FEATURE_LAYERS canonical order.\r\n */\r\nexport function resolveLayers(\r\n requested: readonly FeatureLayer[] | 'all' | undefined,\r\n roles: readonly Role[],\r\n): readonly FeatureLayer[] {\r\n const requestedSet =\r\n requested === undefined || requested === 'all'\r\n ? new Set<FeatureLayer>(FEATURE_LAYERS)\r\n : new Set<FeatureLayer>(requested);\r\n\r\n const out: FeatureLayer[] = [];\r\n for (const layer of FEATURE_LAYERS) {\r\n if (!requestedSet.has(layer)) continue;\r\n if (!layerEnabledByRoles(layer, roles)) continue;\r\n out.push(layer);\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * For a given (requested, roles) pair, return the layers that would be\r\n * dropped by role gating. The orchestrator surfaces these in the summary\r\n * so the user sees \"skipped: test (qa role disabled)\" rather than silent\r\n * absence.\r\n */\r\nexport function layersDroppedByRoles(\r\n requested: readonly FeatureLayer[] | 'all' | undefined,\r\n roles: readonly Role[],\r\n): readonly FeatureLayer[] {\r\n const requestedSet =\r\n requested === undefined || requested === 'all'\r\n ? new Set<FeatureLayer>(FEATURE_LAYERS)\r\n : new Set<FeatureLayer>(requested);\r\n\r\n const out: FeatureLayer[] = [];\r\n for (const layer of FEATURE_LAYERS) {\r\n if (!requestedSet.has(layer)) continue;\r\n if (layerEnabledByRoles(layer, roles)) continue;\r\n out.push(layer);\r\n }\r\n return out;\r\n}\r\n","/**\r\n * `feature add` subcommand argument parser.\r\n *\r\n * Mirrors the style of `parseCreateArgs` in `cli.ts`: hand-rolled parser\r\n * over a string array, throws UserInputError on every malformed input\r\n * shape so the CLI dispatcher maps to exit code 1 uniformly.\r\n */\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport {\r\n FEATURE_LAYERS,\r\n FEATURE_SLUG_PATTERN_SOURCE,\r\n isFeatureLayer,\r\n isValidFeatureSlug,\r\n type FeatureLayer,\r\n} from './layers.js';\r\n\r\nexport interface FeatureAddFlags {\r\n /**\r\n * Resolved layer set:\r\n * - `'all'` ⇒ user passed `--layer all` explicitly\r\n * - `FeatureLayer[]` ⇒ user passed an explicit comma-separated subset\r\n * - `undefined` ⇒ user omitted `--layer`; orchestrator defaults to all\r\n */\r\n readonly layer?: readonly FeatureLayer[] | 'all';\r\n readonly force: boolean;\r\n readonly quiet: boolean;\r\n readonly verbose: boolean;\r\n}\r\n\r\nexport interface FeatureAddArgs {\r\n readonly slug: string;\r\n readonly flags: FeatureAddFlags;\r\n}\r\n\r\nconst ACCEPTED_LAYER_VALUES: readonly string[] = [...FEATURE_LAYERS, 'all'];\r\n\r\n/**\r\n * Parse the argument list following `feature add`. Accepts:\r\n * <slug> positional, required\r\n * --layer <list> optional; comma-separated; or the literal 'all'\r\n * --force optional; passes through to writer\r\n * --quiet / --verbose verbosity controls\r\n */\r\nexport function parseFeatureAdd(args: readonly string[]): FeatureAddArgs {\r\n let slug: string | undefined;\r\n let layer: readonly FeatureLayer[] | 'all' | undefined;\r\n let force = false;\r\n let quiet = false;\r\n let verbose = false;\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 '--force':\r\n force = 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 '--layer': {\r\n const next = args[i + 1];\r\n if (next === undefined || next.startsWith('--')) {\r\n throw new UserInputError(\r\n '--layer requires a comma-separated list',\r\n `示例:--layer pm,backend,frontend;可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n layer = parseLayerArg(next);\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 feature add --help',\r\n );\r\n }\r\n if (slug === undefined) {\r\n slug = a;\r\n } else {\r\n throw new UserInputError(\r\n `unexpected extra argument: ${JSON.stringify(a)}`,\r\n 'feature add 每次只能处理一个 slug',\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (slug === undefined) {\r\n throw new UserInputError(\r\n 'missing <slug>',\r\n '请提供 feature slug,例如:create-keel feature add user-signup',\r\n );\r\n }\r\n\r\n // Slug pattern check — same regex as projectName, satisfying Req 1.3.\r\n if (!isValidFeatureSlug(slug)) {\r\n throw new UserInputError(\r\n `invalid feature slug: ${JSON.stringify(slug)}`,\r\n `slug 应匹配 ${FEATURE_SLUG_PATTERN_SOURCE},例如:user-signup`,\r\n );\r\n }\r\n\r\n const flags: FeatureAddFlags = {\r\n force,\r\n quiet,\r\n verbose,\r\n ...(layer !== undefined ? { layer } : {}),\r\n };\r\n\r\n return { slug, flags };\r\n}\r\n\r\n/**\r\n * Parse the `--layer` argument value. Accepts either the literal `'all'`\r\n * or a comma-separated list whose tokens all belong to FEATURE_LAYERS.\r\n *\r\n * Rejects empty input, mixed `all + concrete` lists (which would be\r\n * ambiguous), and duplicates — duplicates are most likely a typo and\r\n * silently de-duplicating could mask the user's mistake.\r\n */\r\nfunction parseLayerArg(raw: string): readonly FeatureLayer[] | 'all' {\r\n const parts = raw\r\n .split(',')\r\n .map((s) => s.trim())\r\n .filter((s) => s.length > 0);\r\n\r\n if (parts.length === 0) {\r\n throw new UserInputError(\r\n `invalid --layer value: ${JSON.stringify(raw)}`,\r\n `可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n\r\n if (parts.includes('all')) {\r\n if (parts.length > 1) {\r\n throw new UserInputError(\r\n `--layer all cannot be combined with other layer names`,\r\n `传 'all' 或一个明确子集,不要混用`,\r\n );\r\n }\r\n return 'all';\r\n }\r\n\r\n const seen = new Set<string>();\r\n const out: FeatureLayer[] = [];\r\n for (const token of parts) {\r\n if (!isFeatureLayer(token)) {\r\n throw new UserInputError(\r\n `invalid --layer value: ${JSON.stringify(token)}`,\r\n `可选值:${ACCEPTED_LAYER_VALUES.join(',')}`,\r\n );\r\n }\r\n if (seen.has(token)) {\r\n throw new UserInputError(\r\n `duplicate layer: ${token}`,\r\n '--layer 中每个 layer 仅能出现一次',\r\n );\r\n }\r\n seen.add(token);\r\n out.push(token);\r\n }\r\n return out;\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\nimport { parseFeatureAdd, type FeatureAddArgs } from './feature/subcommand.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 * When true on a `create` invocation, the orchestrator runs the feature\r\n * subcommand for slug `user-signup` after the create flow completes,\r\n * populating a worked-example feature in the new project.\r\n */\r\n readonly sampleFeature?: boolean;\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\nexport type FeatureAddHandler = (input: FeatureAddArgs) => 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 * Optional handler for `feature add`. The binary entry point in\r\n * `index.ts` always provides one; tests may omit it when only the\r\n * `create` paths are exercised.\r\n */\r\n readonly onFeatureAdd?: FeatureAddHandler;\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 === 'feature') {\r\n return await dispatchFeature(io, rest);\r\n }\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 / feature。查看帮助:--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 let sampleFeature = false;\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 '--sample-feature':\r\n sampleFeature = 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 ...(sampleFeature ? { sampleFeature: true } : {}),\r\n };\r\n\r\n // Mutually exclusive: --sample-feature writes feature artifacts after\r\n // the create flow finishes. There is nothing to write to under\r\n // --dry-run, so combine them explicitly rejected.\r\n if (sampleFeature && dryRun) {\r\n throw new UserInputError(\r\n `--sample-feature cannot be combined with --dry-run`,\r\n '请去掉一项后重试',\r\n );\r\n }\r\n\r\n return { projectName, flags };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// `feature` command dispatch\r\n// ---------------------------------------------------------------------------\r\n\r\nasync function dispatchFeature(io: CliIO, args: readonly string[]): Promise<ExitCode> {\r\n // Help short-circuit for the feature command: `feature --help` /\r\n // `feature add --help` both fall through to the help screen.\r\n if (args.length === 0 || args.some((a) => a === '-h' || a === '--help')) {\r\n io.stdout(featureHelpText());\r\n return 0;\r\n }\r\n\r\n const [sub, ...rest] = args;\r\n if (sub !== 'add') {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `unknown feature subcommand: ${JSON.stringify(sub)}`,\r\n '当前可用:feature add <slug>',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n if (io.onFeatureAdd === undefined) {\r\n io.stderr(\r\n formatError(\r\n new UserInputError(\r\n `feature add is not wired in this entry point`,\r\n '这是一个 scaffolder 内部错误:onFeatureAdd 未注入',\r\n ),\r\n ),\r\n );\r\n return 1;\r\n }\r\n\r\n try {\r\n const parsed = parseFeatureAdd(rest);\r\n await io.onFeatureAdd(parsed);\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// 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 ' npx @wneng/create-keel feature add <slug> [options]',\r\n '',\r\n 'OPTIONS (create)',\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 ' --sample-feature After create, scaffold the user-signup sample feature',\r\n ' -h, --help Show this help',\r\n ' -v, --version Show the scaffolder version',\r\n '',\r\n 'OPTIONS (feature add)',\r\n ' --layer <list> Comma-separated layers to scaffold',\r\n ' (pm|arch|backend|frontend|test|all; default: all)',\r\n ' --force Overwrite existing Layer_Files for the requested layers',\r\n ' --quiet Only emit errors and the final summary',\r\n ' --verbose Emit per-file render/write logs',\r\n '',\r\n 'See .kiro/specs/feature-subcommand/ for the full specification.',\r\n ].join('\\n');\r\n}\r\n\r\nfunction featureHelpText(): string {\r\n return [\r\n 'create-keel feature — manage features inside an existing keel project',\r\n '',\r\n 'USAGE',\r\n ' npx @wneng/create-keel feature add <slug> [options]',\r\n '',\r\n 'OPTIONS',\r\n ' --layer <list> Comma-separated layers (pm|arch|backend|frontend|test|all)',\r\n ' --force Overwrite existing Layer_Files',\r\n ' --quiet Errors and summary only',\r\n ' --verbose Per-file render/write logs',\r\n '',\r\n 'EXAMPLES',\r\n ' feature add user-signup',\r\n ' feature add billing-portal --layer pm,backend',\r\n ' feature add legacy-import --force',\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 * Per-feature context. Present only when the renderer is invoked from\r\n * the feature subcommand or `--sample-feature`; project-creation\r\n * rendering leaves this undefined and the strict-variable proxy in\r\n * `render/renderer.ts` will throw if a non-feature template tries to\r\n * read it.\r\n */\r\n readonly feature?: FeatureRenderContext;\r\n}\r\n\r\nexport interface FeatureRenderContext {\r\n /** Validated kebab-case slug. */\r\n readonly slug: string;\r\n /** Title-cased version for headings (e.g. `User Signup`). */\r\n readonly title: string;\r\n /** Zero-padded 4-digit ADR sequence prefix (e.g. `0007`). */\r\n readonly nextAdrNumber: string;\r\n /**\r\n * Markdown-link target for the contracts entry of this feature, e.g.\r\n * `contracts/openapi/api.yaml#/paths/~1users~1signup`. The slash in the\r\n * path is escaped per JSON Pointer (RFC 6901) — `~1` for `/`.\r\n */\r\n readonly contractAnchor: 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 readonly feature?: FeatureRenderContext;\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 ...(input.feature !== undefined ? { feature: input.feature } : {}),\r\n };\r\n}\r\n\r\n/**\r\n * Build a FeatureRenderContext from a slug + ADR number.\r\n *\r\n * `slug` is assumed valid (caller filters via `isValidFeatureSlug` before\r\n * reaching here). `nextAdrNumber` is computed by the planner from the\r\n * existing `docs/02-系统方案与架构/adr-*.md` files.\r\n */\r\nexport function buildFeatureContext(\r\n slug: string,\r\n nextAdrNumber: string,\r\n): FeatureRenderContext {\r\n return {\r\n slug,\r\n title: slugToTitle(slug),\r\n nextAdrNumber,\r\n contractAnchor: `contracts/openapi/api.yaml#/paths/~1${slug}`,\r\n };\r\n}\r\n\r\n/** `user-signup` -> `User Signup`. Used for human-readable headings. */\r\nexport function slugToTitle(slug: string): string {\r\n return slug\r\n .split('-')\r\n .filter((s) => s.length > 0)\r\n .map((s) => s[0]!.toUpperCase() + s.slice(1))\r\n .join(' ');\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 // `feature` is intentionally only attached when the orchestrator\r\n // populated it; non-feature templates that try to read it will hit\r\n // the proxy's \"undefined template variable\" branch.\r\n ...(ctx.feature !== undefined ? { feature: ctx.feature } : {}),\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 * Slugs of features scaffolded into the project via the `feature add`\r\n * subcommand. Optional and omitted from the canonical output when empty\r\n * so projects created prior to v0.2.0 remain byte-identical after a\r\n * round trip through write → load.\r\n */\r\n readonly features?: readonly string[];\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\nconst FEATURE_SLUG_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 features: {\r\n type: 'array',\r\n items: { type: 'string', pattern: FEATURE_SLUG_PATTERN },\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<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 'features',\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 if (key === 'features') {\r\n // Omit when absent or empty so legacy v0.1.x projects stay byte-identical\r\n // after a round trip; only emit when the project has at least one feature.\r\n const features = metadata.features;\r\n if (features !== undefined && features.length > 0) {\r\n out[key] = [...features];\r\n }\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 features?: readonly string[];\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 ...(params.features !== undefined ? { features: [...params.features] } : {}),\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 * When true, skip the \"target directory is not empty\" preflight check.\r\n * Used by the feature subcommand which writes new files into an\r\n * already-scaffolded project. Default is false to preserve the create\r\n * command's existing safety check.\r\n */\r\n readonly allowNonEmptyTarget?: 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 && !opts.allowNonEmptyTarget) {\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 * Feature subcommand orchestrator — drives `feature add <slug>` end to end.\r\n *\r\n * load .scaffolder.json\r\n * ↓\r\n * resolve layers (apply role gating)\r\n * ↓\r\n * build FeaturePlan (renders templates + may emit OpenAPI edit)\r\n * ↓\r\n * filter out already-existing target paths (unless --force)\r\n * ↓\r\n * writePlan via the existing writer (rollback journal applies)\r\n * ↓\r\n * appendFeatureSlug to manifest (last step so writer rollback can revert it)\r\n * ↓\r\n * emit a summary the reporter can format\r\n *\r\n * Public entrypoint is `runFeatureAdd`. Helpers `splitPlanByExistence` and\r\n * `formatSummary` are exported so Property 4 / 5 / 10 can drive them\r\n * directly from in-memory inputs without touching the disk.\r\n */\r\n\r\nimport { promises as fs } from 'node:fs';\r\nimport * as path from 'node:path';\r\n\r\nimport { UserInputError } from '../errors.js';\r\nimport type { FilePlan, PlannedFile } from '../plan/builder.js';\r\nimport { writePlan } from '../writer/writer.js';\r\nimport { loadMetadataFile, METADATA_FILENAME } from '../schema/metadata.js';\r\nimport { isValidFeatureSlug, layersDroppedByRoles, resolveLayers, type FeatureLayer } from './layers.js';\r\nimport { buildFeaturePlan, type FeaturePlanResult, type FeaturePlanSource } from './plan.js';\r\nimport { appendFeatureSlug } from './manifest-update.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public surface\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface RunFeatureAddInput {\r\n readonly slug: string;\r\n readonly cwd: string;\r\n /**\r\n * `undefined` ⇒ all FEATURE_LAYERS, role-gated by the project's roles.\r\n * Use `'all'` to be explicit. A non-empty subset selects exactly those\r\n * layers; role gating still applies.\r\n */\r\n readonly layers?: readonly FeatureLayer[] | 'all';\r\n /** `placeholder` for `feature add`; `sample` for `--sample-feature`. */\r\n readonly source: FeaturePlanSource;\r\n readonly force: boolean;\r\n readonly scaffolderVersion: string;\r\n readonly templatesRoot?: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface FeatureAddSummary {\r\n readonly slug: string;\r\n /** Layer_File paths actually written (POSIX, relative to project root). */\r\n readonly created: readonly string[];\r\n /** Pre-existing Layer_File paths that were left untouched (no --force). */\r\n readonly skipped: readonly string[];\r\n /** Layers dropped by role gating (e.g. `test` without `qa`). */\r\n readonly droppedByRoles: readonly FeatureLayer[];\r\n /** Anchor entries actually emitted (currently the OpenAPI path). */\r\n readonly anchorsInserted: readonly Anchor[];\r\n /** True iff `appendFeatureSlug` actually changed the manifest. */\r\n readonly manifestUpdated: boolean;\r\n /** Absolute path of the project's `.scaffolder.json`. */\r\n readonly metadataPath: string;\r\n}\r\n\r\nexport interface Anchor {\r\n readonly fromFile: string;\r\n readonly toTarget: string;\r\n}\r\n\r\nexport async function runFeatureAdd(input: RunFeatureAddInput): Promise<FeatureAddSummary> {\r\n // §1 — slug validation. UserInputError so the CLI maps to exit 1.\r\n if (!isValidFeatureSlug(input.slug)) {\r\n throw new UserInputError(\r\n `invalid feature slug: ${JSON.stringify(input.slug)}`,\r\n 'slug 应匹配 ^[a-z0-9][a-z0-9-]{0,62}$,例如:user-signup',\r\n );\r\n }\r\n\r\n // §2 — load manifest. Missing manifest is a UserInputError because the\r\n // caller is operating outside a keel-scaffolded project.\r\n let manifest;\r\n try {\r\n manifest = await loadMetadataFile(input.cwd);\r\n } catch (e) {\r\n const code = (e as NodeJS.ErrnoException).code;\r\n if (code === 'ENOENT') {\r\n throw new UserInputError(\r\n `no .scaffolder.json found at ${path.join(input.cwd, METADATA_FILENAME)}`,\r\n '请在 keel 项目根目录运行;或先用 create-keel create 创建项目',\r\n );\r\n }\r\n throw e;\r\n }\r\n\r\n // §3 — resolve layers + record role-gating drops for the summary.\r\n const requested = input.layers ?? 'all';\r\n const resolved = resolveLayers(requested, manifest.options.roles);\r\n const droppedByRoles = layersDroppedByRoles(requested, manifest.options.roles);\r\n\r\n // §4 — build the file plan.\r\n const featurePlan = await buildFeaturePlan({\r\n slug: input.slug,\r\n layers: resolved,\r\n source: input.source,\r\n options: manifest.options,\r\n cwd: input.cwd,\r\n scaffolderVersion: input.scaffolderVersion,\r\n ...(input.templatesRoot !== undefined ? { templatesRoot: input.templatesRoot } : {}),\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n\r\n // §5 — split into \"to-write\" and \"to-skip\" based on disk state. Under\r\n // --force we skip nothing and rely on the writer's overwrite behavior.\r\n const { toWrite, skipped } = await splitPlanByExistence(featurePlan, input.cwd, input.force);\r\n\r\n // §6 — execute the file writes.\r\n if (toWrite.files.length > 0) {\r\n await writePlan(toWrite, {\r\n targetDirectory: input.cwd,\r\n force: input.force,\r\n allowNonEmptyTarget: true,\r\n });\r\n }\r\n\r\n // §7 — anchors emitted. Currently the only inserted anchor is the\r\n // OpenAPI path entry; backend-design / frontend-design / test-plan\r\n // anchors live inside the rendered Layer_File content and are\r\n // validated by Property 6.\r\n const anchorsInserted: Anchor[] = [];\r\n if (\r\n featurePlan.openapiPathInserted !== undefined &&\r\n featurePlan.openapiAlreadyExisted === false &&\r\n toWrite.files.some((f) => f.targetPath === 'contracts/openapi/api.yaml')\r\n ) {\r\n anchorsInserted.push({\r\n fromFile: 'contracts/openapi/api.yaml',\r\n toTarget: featurePlan.openapiPathInserted,\r\n });\r\n }\r\n\r\n // §8 — append slug to manifest. Always last so writer rollback would\r\n // also revert any partial file state without leaving an orphaned slug\r\n // in the manifest.\r\n const manifestResult = await appendFeatureSlug(input.cwd, input.slug);\r\n\r\n return {\r\n slug: input.slug,\r\n created: toWrite.files.map((f) => f.targetPath),\r\n skipped,\r\n droppedByRoles,\r\n anchorsInserted,\r\n manifestUpdated: manifestResult.updated,\r\n metadataPath: manifestResult.metadataPath,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers (exported for property tests)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Split a feature plan into a \"files to write\" plan plus a list of\r\n * already-existing target paths that should be skipped.\r\n *\r\n * When `force` is true the entire plan goes to `toWrite` and `skipped`\r\n * is empty — the writer is responsible for the actual overwrite.\r\n */\r\nexport async function splitPlanByExistence(\r\n plan: FeaturePlanResult,\r\n cwd: string,\r\n force: boolean,\r\n): Promise<{ toWrite: FilePlan; skipped: readonly string[] }> {\r\n if (force) {\r\n return {\r\n toWrite: { files: plan.files, directories: plan.directories },\r\n skipped: [],\r\n };\r\n }\r\n\r\n const toWrite: PlannedFile[] = [];\r\n const skipped: string[] = [];\r\n\r\n for (const file of plan.files) {\r\n const exists = await fileExists(path.join(cwd, file.targetPath));\r\n if (exists) {\r\n skipped.push(file.targetPath);\r\n } else {\r\n toWrite.push(file);\r\n }\r\n }\r\n\r\n return {\r\n toWrite: { files: toWrite, directories: plan.directories },\r\n skipped,\r\n };\r\n}\r\n\r\nasync function fileExists(absPath: string): Promise<boolean> {\r\n try {\r\n await fs.access(absPath);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Render a summary payload as the canonical multi-section text the\r\n * reporter prints. Exported so Property 10 can assert structural\r\n * invariants on the text without owning a Reporter instance.\r\n *\r\n * Sections:\r\n * created: <path>\r\n * skipped: <path>\r\n * anchors: <fromFile> -> <toTarget>\r\n *\r\n * Empty sections are omitted entirely so the property doesn't have to\r\n * special-case \"no skips\" etc.\r\n */\r\nexport function formatSummary(summary: FeatureAddSummary): string {\r\n const lines: string[] = [];\r\n lines.push(`feature add: ${summary.slug}`);\r\n for (const p of summary.created) {\r\n lines.push(` created: ${p}`);\r\n }\r\n for (const p of summary.skipped) {\r\n lines.push(` skipped: ${p}`);\r\n }\r\n for (const a of summary.anchorsInserted) {\r\n lines.push(` anchors: ${a.fromFile} -> ${a.toTarget}`);\r\n }\r\n for (const layer of summary.droppedByRoles) {\r\n lines.push(` dropped (role gate): ${layer}`);\r\n }\r\n if (summary.manifestUpdated) {\r\n lines.push(` manifest: updated ${summary.metadataPath}`);\r\n }\r\n return lines.join('\\n');\r\n}\r\n","/**\r\n * Feature plan builder — turn a (slug, layers, source) tuple into a\r\n * concrete FilePlan the writer can execute.\r\n *\r\n * Differences from `plan/builder.ts` (which serves the `create` command):\r\n * - templates are rooted at `<scaffolder-pkg>/src/feature/templates/`,\r\n * not the per-fragment `files/` directory\r\n * - each layer has at most one output file; conflict resolution is not\r\n * needed\r\n * - ADR file paths embed an auto-numbered prefix that this module\r\n * computes by scanning existing `docs/02-系统方案与架构/adr-*.md`\r\n * - the plan optionally includes one extra entry for the in-place\r\n * edit of `contracts/openapi/api.yaml` produced by `openapi-edit`\r\n *\r\n * The output type is the same `FilePlan` from `plan/builder.ts` so the\r\n * existing writer can execute the result without modification.\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 { renderFile } from '../render/renderer.js';\r\nimport {\r\n buildFeatureContext,\r\n buildRenderContext,\r\n type RenderContext,\r\n} from '../render/context.js';\r\nimport type { Options } from '../schema/options.js';\r\nimport type { FilePlan, PlannedFile } from '../plan/builder.js';\r\nimport {\r\n FEATURE_LAYERS,\r\n LAYER_ARTIFACTS,\r\n type FeatureLayer,\r\n} from './layers.js';\r\nimport { addPathStub } from './openapi-edit.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Inputs / outputs\r\n// ---------------------------------------------------------------------------\r\n\r\nexport type FeaturePlanSource = 'placeholder' | 'sample';\r\n\r\nexport interface FeaturePlanInput {\r\n readonly slug: string;\r\n readonly layers: readonly FeatureLayer[];\r\n readonly source: FeaturePlanSource;\r\n readonly options: Options;\r\n readonly cwd: string;\r\n readonly scaffolderVersion: string;\r\n /** Override for tests; defaults to scanning real disk under `cwd`. */\r\n readonly templatesRoot?: string;\r\n readonly now?: Date;\r\n}\r\n\r\nexport interface FeaturePlanResult extends FilePlan {\r\n /** The OpenAPI path that was inserted (or already existed); undefined when no contract was edited. */\r\n readonly openapiPathInserted?: string;\r\n /** True iff the OpenAPI insert was a no-op because the path was already present. */\r\n readonly openapiAlreadyExisted?: boolean;\r\n /** Per-layer planned output paths, useful for the reporter and tests. */\r\n readonly outputs: ReadonlyMap<FeatureLayer, string>;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\nexport async function buildFeaturePlan(input: FeaturePlanInput): Promise<FeaturePlanResult> {\r\n const templatesRoot = input.templatesRoot ?? defaultFeatureTemplatesRoot();\r\n const sourceRoot =\r\n input.source === 'sample'\r\n ? path.join(templatesRoot, 'sample', input.slug)\r\n : templatesRoot;\r\n\r\n const nextAdrNumber = await computeAdrNumberForSlug(input.cwd, input.slug);\r\n const renderContext = buildRenderContext({\r\n options: input.options,\r\n scaffolderVersion: input.scaffolderVersion,\r\n feature: buildFeatureContext(input.slug, nextAdrNumber),\r\n ...(input.now !== undefined ? { now: input.now } : {}),\r\n });\r\n\r\n const files: PlannedFile[] = [];\r\n const outputs = new Map<FeatureLayer, string>();\r\n\r\n for (const layer of input.layers) {\r\n const artifact = LAYER_ARTIFACTS[layer];\r\n const targetPath = computeTargetPath(layer, input.slug, nextAdrNumber);\r\n outputs.set(layer, targetPath);\r\n const templatePath = path.join(sourceRoot, artifact.placeholderTemplate);\r\n const content = await renderFeatureTemplate(templatePath, renderContext, layer, input.source);\r\n files.push({\r\n targetPath,\r\n content,\r\n contributedBy: `feature:${layer}`,\r\n });\r\n }\r\n\r\n // OpenAPI editing: triggered for any run that includes the backend\r\n // layer and has a contract enabled. The sample feature passes a richer\r\n // request body / response schema to satisfy Requirement 6.4; the\r\n // placeholder run uses a minimal stub.\r\n let openapiPathInserted: string | undefined;\r\n let openapiAlreadyExisted: boolean | undefined;\r\n if (input.layers.includes('backend') && input.options.contract !== 'none') {\r\n const apiPath = 'contracts/openapi/api.yaml';\r\n const apiAbs = path.join(input.cwd, apiPath);\r\n let currentText: string;\r\n try {\r\n currentText = await fs.readFile(apiAbs, 'utf8');\r\n } catch (e) {\r\n throw new TemplateError(\r\n `expected ${apiPath} to exist when contract != 'none' but cannot read it: ${(e as Error).message}`,\r\n '请确认 contracts/openapi/api.yaml 存在;或将 .scaffolder.json 中 options.contract 设为 none',\r\n );\r\n }\r\n const editResult =\r\n input.source === 'sample' && input.slug === 'user-signup'\r\n ? addPathStub({\r\n currentText,\r\n slug: input.slug,\r\n title: renderContext.feature!.title,\r\n // The sample feature owns its own canonical path + schema,\r\n // satisfying Req 6.4 (request body schema + at least one\r\n // response body schema).\r\n path: '/users/signup',\r\n operation: 'post',\r\n requestBody: {\r\n required: true,\r\n content: {\r\n 'application/json': {\r\n schema: {\r\n type: 'object',\r\n required: ['email', 'password'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string', minLength: 8 },\r\n },\r\n },\r\n },\r\n },\r\n },\r\n responses: {\r\n '201': {\r\n description: 'User created in pending state; verification email sent',\r\n content: {\r\n 'application/json': {\r\n schema: {\r\n type: 'object',\r\n required: ['id', 'email', 'state'],\r\n properties: {\r\n id: { type: 'string', format: 'uuid' },\r\n email: { type: 'string', format: 'email' },\r\n state: { type: 'string', enum: ['pending'] },\r\n },\r\n },\r\n },\r\n },\r\n },\r\n '409': {\r\n description: 'Email already registered (generic, does not leak existence)',\r\n },\r\n '429': { description: 'Rate-limited' },\r\n },\r\n })\r\n : addPathStub({\r\n currentText,\r\n slug: input.slug,\r\n title: renderContext.feature!.title,\r\n });\r\n files.push({\r\n targetPath: apiPath,\r\n content: editResult.nextText,\r\n contributedBy: 'feature:openapi',\r\n });\r\n openapiPathInserted = editResult.insertedPath;\r\n openapiAlreadyExisted = editResult.alreadyExisted;\r\n }\r\n\r\n const sortedFiles = [...files].sort((a, b) =>\r\n a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0,\r\n );\r\n const directories = collectDirectories(sortedFiles);\r\n\r\n return {\r\n files: sortedFiles,\r\n directories,\r\n outputs,\r\n ...(openapiPathInserted !== undefined ? { openapiPathInserted } : {}),\r\n ...(openapiAlreadyExisted !== undefined ? { openapiAlreadyExisted } : {}),\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internals\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Compute the actual on-disk output path for a layer + slug, including\r\n * the ADR auto-number prefix where needed.\r\n */\r\nfunction computeTargetPath(\r\n layer: FeatureLayer,\r\n slug: string,\r\n nextAdrNumber: string,\r\n): string {\r\n if (layer === 'arch') {\r\n // LAYER_ARTIFACTS.arch.outputPath returns `docs/02-.../adr-<slug>.md`;\r\n // splice in the 4-digit number to match the ADR naming convention\r\n // (`adr-NNNN-<slug>.md`) defined in docs/README.md §6.1.\r\n return `docs/02-系统方案与架构/adr-${nextAdrNumber}-${slug}.md`;\r\n }\r\n return LAYER_ARTIFACTS[layer].outputPath(slug);\r\n}\r\n\r\n/**\r\n * Read + render a per-layer template. When the source is `sample` and the\r\n * sample directory exists, fall back to placeholder for any layer the\r\n * sample omits — but in practice the sample is expected to ship every\r\n * layer it intends to populate, so a missing sample template is treated\r\n * as authoring error.\r\n */\r\nasync function renderFeatureTemplate(\r\n templatePath: string,\r\n context: RenderContext,\r\n layer: FeatureLayer,\r\n source: FeaturePlanSource,\r\n): Promise<string> {\r\n try {\r\n return await renderFile(templatePath, context);\r\n } catch (e) {\r\n throw new TemplateError(\r\n `feature template missing for layer \"${layer}\" (${source}): ${templatePath}: ${(e as Error).message}`,\r\n source === 'sample'\r\n ? `请确认 src/feature/templates/sample/<slug>/${LAYER_ARTIFACTS[layer].placeholderTemplate} 存在`\r\n : '请确认 src/feature/templates/${layer}/ 下存在 ' + LAYER_ARTIFACTS[layer].placeholderTemplate,\r\n );\r\n }\r\n}\r\n\r\nconst ADR_FILE_RE = /^adr-(\\d{4,})-/;\r\n\r\n/**\r\n * Compute the ADR number to use for `slug`:\r\n * - if an existing `adr-NNNN-<slug>.md` is found, return that NNNN so\r\n * a re-run lands on the same path (and gets skipped by the\r\n * orchestrator's existence check)\r\n * - otherwise return one greater than the highest existing ADR number,\r\n * zero-padded to 4 digits\r\n *\r\n * Missing directory or no ADRs → `'0001'`.\r\n */\r\nasync function computeAdrNumberForSlug(cwd: string, slug: string): Promise<string> {\r\n const dir = path.join(cwd, 'docs', '02-系统方案与架构');\r\n let entries: string[];\r\n try {\r\n entries = await fs.readdir(dir);\r\n } catch {\r\n return '0001';\r\n }\r\n const slugRe = new RegExp(`^adr-(\\\\d{4,})-${escapeRegex(slug)}\\\\.md$`);\r\n let max = 0;\r\n for (const name of entries) {\r\n const slugMatch = slugRe.exec(name);\r\n if (slugMatch) {\r\n // Same slug already has an ADR — reuse its number so writes are\r\n // idempotent against existing on-disk artifacts.\r\n return slugMatch[1]!;\r\n }\r\n const m = ADR_FILE_RE.exec(name);\r\n if (m) {\r\n const n = parseInt(m[1]!, 10);\r\n if (!Number.isNaN(n) && n > max) max = n;\r\n }\r\n }\r\n return String(max + 1).padStart(4, '0');\r\n}\r\n\r\nfunction escapeRegex(s: string): string {\r\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n}\r\n\r\n/**\r\n * Build the unique, sorted list of POSIX directories implied by the file\r\n * targets. Mirrors the helper in `plan/builder.ts`.\r\n */\r\nfunction collectDirectories(files: readonly PlannedFile[]): readonly string[] {\r\n const set = 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 set.add(cur);\r\n cur = path.posix.dirname(cur);\r\n }\r\n }\r\n return [...set].sort();\r\n}\r\n\r\n/**\r\n * Resolve `<scaffolder-pkg>/src/feature/templates/`. Walks up from this\r\n * file to find the package root (mirrors `fragments/loader.ts`).\r\n */\r\nexport function defaultFeatureTemplatesRoot(): string {\r\n const here = path.dirname(fileURLToPath(import.meta.url));\r\n const pkgRoot = findPackageRoot(here);\r\n return path.join(pkgRoot, 'src', 'feature', 'templates');\r\n}\r\n\r\nfunction findPackageRoot(start: string): string {\r\n let dir = start;\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 安装完整,src/feature/templates/ 与 package.json 位于同一层级',\r\n );\r\n}\r\n\r\n// Suppress unused-import error in some bundlers when FEATURE_LAYERS is\r\n// only referenced via type-position elsewhere.\r\nexport { FEATURE_LAYERS };\r\n","/**\r\n * OpenAPI document editor — round-trip-safe insertion of one path entry.\r\n *\r\n * Why this module is separate from `feature/plan.ts`:\r\n *\r\n * `contracts/openapi/api.yaml` is hand-edited by humans and contains\r\n * key ordering, comments, and quoting choices that have semantic value\r\n * to the author. The render pipeline used elsewhere (`render/renderer.ts`)\r\n * is one-way text templating and would destroy that structure. The `yaml`\r\n * package's `parseDocument` API (already a runtime dep at v2.5.1) preserves\r\n * the source-level AST, so `addPathStub` can mutate exactly one node and\r\n * re-emit a document whose unrelated content is byte-stable.\r\n *\r\n * Validates: Requirements 5.1 (round-trip), 5.2 (path-add preserves\r\n * untouched paths), 5.3 (YAML syntax error → UserInputError with line/col),\r\n * 5.4 (scaffolder-emitted documents satisfy round-trip).\r\n */\r\n\r\nimport { isMap, isScalar, parseDocument, YAMLMap, type Document, type YAMLError } from 'yaml';\r\n\r\nimport { TemplateError, UserInputError } from '../errors.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface OpenApiEditInput {\r\n /** Full file content of `contracts/openapi/api.yaml`. */\r\n readonly currentText: string;\r\n /** Feature slug; the new path will be `/<slug>` unless overridden. */\r\n readonly slug: string;\r\n /** Human-readable summary used inside the inserted path stub. */\r\n readonly title: string;\r\n /**\r\n * Optional override for the path key. The default `/<slug>` is what\r\n * `feature add` and `--sample-feature` use; the sample feature's own\r\n * path (`/users/signup`) overrides this.\r\n */\r\n readonly path?: string;\r\n /**\r\n * Optional override for the operation body. When omitted, a minimal\r\n * stub with only a 200 response is inserted. Callers (notably the\r\n * `--sample-feature` flow) can pass a richer shape including\r\n * requestBody and per-status response schemas to satisfy\r\n * Requirement 6.4.\r\n */\r\n readonly operation?: 'get' | 'post';\r\n readonly requestBody?: Record<string, unknown>;\r\n readonly responses?: Record<string, Record<string, unknown>>;\r\n}\r\n\r\nexport interface OpenApiEditResult {\r\n /** Serialized YAML; equals `currentText` when `alreadyExisted` is true. */\r\n readonly nextText: string;\r\n /** The full path key that was inserted (or already present). */\r\n readonly insertedPath: string;\r\n /** True if the path was already in `paths`; in that case `nextText === currentText`. */\r\n readonly alreadyExisted: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Round-trip parse + serialize (Property 8)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse and serialize without intervening edits.\r\n *\r\n * This is the canonical implementation for Requirement 5.1 / 5.4 / Property\r\n * 8: it must produce a document semantically equivalent to the input. It\r\n * is exported so the property test exercises exactly the same code path\r\n * as production callers.\r\n *\r\n * Throws UserInputError on YAML syntax error (Req 5.3); throws\r\n * TemplateError on structural problems (root not a map, etc.).\r\n */\r\nexport function roundTrip(currentText: string): string {\r\n const doc = parseAndValidate(currentText);\r\n return serializeDocument(doc);\r\n}\r\n\r\n/**\r\n * Insert one path entry into the OpenAPI document.\r\n *\r\n * Algorithm:\r\n * 1. parse via `parseDocument` (preserves order + comments + quoting)\r\n * 2. ensure the root is a map and has a `paths` map (create empty when\r\n * `paths: {}` was the syntactic form; reject when present but not a map)\r\n * 3. if the target path key already exists, return `alreadyExisted: true`\r\n * with `nextText === currentText` (zero-byte change satisfies Req 5.2's\r\n * trivial case)\r\n * 4. otherwise insert one new entry whose value is a minimal stub\r\n * (`get` operation with a 200 response)\r\n * 5. emit via `Document.toString` with options matching `parseDocument`'s\r\n * defaults so unrelated content is byte-stable\r\n */\r\nexport function addPathStub(input: OpenApiEditInput): OpenApiEditResult {\r\n const insertedPath = input.path ?? `/${input.slug}`;\r\n\r\n const doc = parseAndValidate(input.currentText);\r\n\r\n // §2: ensure paths map exists and is a map. The \"paths key absent or\r\n // present-but-empty\" cases both mean: create an empty map and proceed.\r\n // YAML lets `paths:` (with nothing on the right) parse to a Scalar\r\n // whose value is null; we treat that the same as a missing key.\r\n const pathsNode = doc.get('paths', true);\r\n let pathsMap: YAMLMap;\r\n if (\r\n pathsNode === undefined ||\r\n pathsNode === null ||\r\n (isScalar(pathsNode) && pathsNode.value === null)\r\n ) {\r\n pathsMap = new YAMLMap();\r\n // Force block style so a single inserted entry is human-readable;\r\n // the empty `{}` form would otherwise persist and produce a giant\r\n // single-line flow map after insertion.\r\n pathsMap.flow = false;\r\n doc.set('paths', pathsMap);\r\n } else if (isMap(pathsNode)) {\r\n pathsMap = pathsNode;\r\n // If the existing map was empty + flow (`paths: {}`), switch it to\r\n // block so the inserted entry uses block style. Pre-populated maps\r\n // that the user explicitly wrote in flow are left alone.\r\n if (pathsMap.items.length === 0 && pathsMap.flow) {\r\n pathsMap.flow = false;\r\n }\r\n } else {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml: 'paths' is present but is not a map`,\r\n '请把 paths 字段写成 YAML 映射(例如 paths: {})',\r\n );\r\n }\r\n\r\n // §3: idempotence — already-present path key is a no-op.\r\n if (pathsMap.has(insertedPath)) {\r\n return {\r\n nextText: input.currentText,\r\n insertedPath,\r\n alreadyExisted: true,\r\n };\r\n }\r\n\r\n // §4: create the stub. Build with createNode so quoting / styling\r\n // matches the surrounding document.\r\n const operationKey = input.operation ?? 'get';\r\n const responses = input.responses ?? { '200': { description: 'OK' } };\r\n const operationBody: Record<string, unknown> = {\r\n summary: input.title,\r\n operationId: slugToOperationId(input.slug),\r\n ...(input.requestBody !== undefined ? { requestBody: input.requestBody } : {}),\r\n responses,\r\n };\r\n const stub = doc.createNode({\r\n [operationKey]: operationBody,\r\n });\r\n pathsMap.set(insertedPath, stub);\r\n\r\n // §5: serialize.\r\n const nextText = serializeDocument(doc);\r\n\r\n return {\r\n nextText,\r\n insertedPath,\r\n alreadyExisted: false,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internals\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse and assert the document is structurally usable. Returns the\r\n * Document so callers can inspect / mutate; throws on every failure mode\r\n * Requirement 5.3 cares about.\r\n */\r\nfunction parseAndValidate(currentText: string): Document {\r\n const doc = parseDocument(currentText, {\r\n // keep node identity stable across parse → toString\r\n keepSourceTokens: true,\r\n });\r\n\r\n if (doc.errors.length > 0) {\r\n const first = doc.errors[0]!;\r\n throw yamlSyntaxError(first);\r\n }\r\n\r\n // contents may be null for an empty document, which is not a usable\r\n // OpenAPI file — surface as TemplateError, not UserInput.\r\n if (doc.contents === null) {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml is empty; an OpenAPI 3.x document is required`,\r\n '请保留至少 openapi/info/paths 三个顶层字段',\r\n );\r\n }\r\n\r\n if (!isMap(doc.contents)) {\r\n throw new TemplateError(\r\n `contracts/openapi/api.yaml root must be a YAML mapping`,\r\n '请确认文件以 openapi: ... 开头,根是一个映射',\r\n );\r\n }\r\n\r\n return doc;\r\n}\r\n\r\n/**\r\n * Re-serialize a Document with options that match the parser's defaults.\r\n *\r\n * Notes on the options:\r\n * - `lineWidth: 0` disables soft-wrapping. Folded scalars in OpenAPI\r\n * description fields would otherwise be reformatted at 80 columns,\r\n * which counts as unrelated change and breaks the round-trip property.\r\n * - `indent: 2` matches the dominant style of OpenAPI files emitted by\r\n * editors; `parseDocument` does not inspect the input's indent and\r\n * always re-emits at this width, so we pin it explicitly.\r\n * - we do NOT pass `simpleKeys: true`; the default already produces the\r\n * `'200':` quoted form OpenAPI authors expect.\r\n */\r\nfunction serializeDocument(doc: Document): string {\r\n return doc.toString({\r\n lineWidth: 0,\r\n indent: 2,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a yaml package YAMLError into a UserInputError carrying\r\n * line/column info, satisfying Req 5.3.\r\n */\r\nfunction yamlSyntaxError(err: YAMLError): UserInputError {\r\n // YAMLError exposes `linePos` (start/end). Fall back to byte offset only\r\n // when linePos is missing, which the v2 parser populates for syntax errors.\r\n const linePos = err.linePos;\r\n const where =\r\n linePos !== undefined\r\n ? `line ${linePos[0].line}, column ${linePos[0].col}`\r\n : `offset ${err.pos[0]}`;\r\n return new UserInputError(\r\n `contracts/openapi/api.yaml: YAML syntax error at ${where}: ${err.message}`,\r\n '修正 YAML 语法后重试,常见原因:缩进 / 引号 / 冒号后空格缺失',\r\n );\r\n}\r\n\r\n/**\r\n * `user-signup` → `userSignup`; used as a minimally reasonable\r\n * `operationId`. OpenAPI spec recommends `operationId` be unique across the\r\n * document, which is satisfied because slugs are unique within a project\r\n * (Req 7.2 enforces that at the manifest layer).\r\n */\r\nexport function slugToOperationId(slug: string): string {\r\n return slug.replace(/-([a-z0-9])/g, (_m, c: string) => c.toUpperCase());\r\n}\r\n","/**\r\n * Manifest update — append a feature slug to `.scaffolder.json`'s\r\n * `features` array.\r\n *\r\n * Behavior contract (Requirements 7.1 / 7.2 / 7.3 / 7.4):\r\n * - missing `features` field is treated as empty array (Req 7.4)\r\n * - slug already present → no write, no error (Req 7.2)\r\n * - slug not present → append, re-canonicalise, write back via\r\n * `writeMetadataFile` so the canonical key order from\r\n * `schema/metadata.ts` is preserved\r\n * - every field other than `features` (and the implicit `generatedAt`\r\n * freshness) round-trips byte-identical, because we never construct\r\n * the metadata from scratch — we always start from `loadMetadataFile`\r\n * and only touch `features`\r\n *\r\n * The function is intentionally *not* responsible for atomicity with the\r\n * file plan; the orchestrator guarantees the manifest update is the last\r\n * step in the plan ordering, so a writer rollback can also revert it via\r\n * the existing journal.\r\n */\r\n\r\nimport * as path from 'node:path';\r\n\r\nimport {\r\n METADATA_FILENAME,\r\n loadMetadataFile,\r\n writeMetadataFile,\r\n type ScaffolderMetadata,\r\n} from '../schema/metadata.js';\r\n\r\nexport interface AppendFeatureSlugResult {\r\n /**\r\n * True iff the manifest was changed and rewritten. False when the slug\r\n * was already present (Req 7.2 idempotence).\r\n */\r\n readonly updated: boolean;\r\n /**\r\n * The full path of the manifest file. Useful for the reporter even when\r\n * `updated === false` so the summary can still cite where the (already\r\n * up-to-date) file lives.\r\n */\r\n readonly metadataPath: string;\r\n}\r\n\r\n/**\r\n * Append `slug` to the project's manifest under `features`.\r\n *\r\n * `projectDirectory` is the directory containing `.scaffolder.json` (i.e.\r\n * the project root). `loadMetadataFile` already validates the manifest\r\n * against the v0.2.0+ schema; if the manifest is missing or malformed,\r\n * the underlying I/O / validation error propagates unchanged so the\r\n * subcommand maps it to a UserInputError at a higher level.\r\n */\r\nexport async function appendFeatureSlug(\r\n projectDirectory: string,\r\n slug: string,\r\n): Promise<AppendFeatureSlugResult> {\r\n const manifest = await loadMetadataFile(projectDirectory);\r\n const next = withFeatureSlug(manifest, slug);\r\n const metadataPath = path.join(projectDirectory, METADATA_FILENAME);\r\n\r\n if (next === manifest) {\r\n return { updated: false, metadataPath };\r\n }\r\n\r\n await writeMetadataFile(projectDirectory, next);\r\n return { updated: true, metadataPath };\r\n}\r\n\r\n/**\r\n * Pure helper: produce a new ScaffolderMetadata with `slug` appended to\r\n * `features`. Returns the input by reference when no change is needed —\r\n * this is what makes Property 11's \"absent ⇒ unchanged\" assertion hold\r\n * at the object-identity level.\r\n *\r\n * Exported for the property test so the property runs without touching\r\n * the disk.\r\n */\r\nexport function withFeatureSlug(\r\n manifest: ScaffolderMetadata,\r\n slug: string,\r\n): ScaffolderMetadata {\r\n const existing = manifest.features ?? [];\r\n if (existing.includes(slug)) {\r\n return manifest;\r\n }\r\n return {\r\n ...manifest,\r\n features: [...existing, slug],\r\n };\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, `feature add` to the feature\r\n * orchestrator, and translate the returned exit code into process.exit.\r\n */\r\n\r\nimport { run, type CreateCommandInput } from './cli.js';\r\nimport { runCreate } from './orchestrator.js';\r\nimport { runFeatureAdd } from './feature/orchestrator.js';\r\nimport type { FeatureAddArgs } from './feature/subcommand.js';\r\nimport { SCAFFOLDER_VERSION } from './version.js';\r\n\r\nasync function handleCreate(input: CreateCommandInput): Promise<void> {\r\n const result = 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 // --sample-feature: after a successful create (and only when not\r\n // dry-run; the parser rejects that combination), scaffold the\r\n // canonical sample feature into the freshly created project so new\r\n // users have a worked example to read.\r\n if (input.flags.sampleFeature === true && !result.dryRun) {\r\n await runFeatureAdd({\r\n slug: 'user-signup',\r\n cwd: result.targetDirectory,\r\n source: 'sample',\r\n // Force is safe here: nothing pre-existing in a brand new project,\r\n // so the writer's nonEmpty check is the only thing to bypass.\r\n force: true,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\r\n });\r\n }\r\n}\r\n\r\nasync function handleFeatureAdd(input: FeatureAddArgs): Promise<void> {\r\n const cwd = process.cwd();\r\n await runFeatureAdd({\r\n slug: input.slug,\r\n cwd,\r\n ...(input.flags.layer !== undefined ? { layers: input.flags.layer } : {}),\r\n source: 'placeholder',\r\n force: input.flags.force,\r\n scaffolderVersion: SCAFFOLDER_VERSION,\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 onFeatureAdd: handleFeatureAdd,\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","fs","path","SEMVER_PATTERN","FRAGMENT_NAME_PATTERN","FEATURE_SLUG_PATTERN","ajv","Ajv","addFormats","validateFn","collectIssues","fs","fs","path","fs","fs","path","fs","accessSync","path","fileURLToPath","fs","fileURLToPath","findPackageRoot","accessSync","path","fs"]}
|