@wneng/create-keel 0.1.0

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.
Files changed (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/index.js +1479 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +55 -0
  6. package/src/templates/.gitkeep +0 -0
  7. package/src/templates/agent-rust-desktop/files/Cargo.toml +12 -0
  8. package/src/templates/agent-rust-desktop/files/README.md +16 -0
  9. package/src/templates/agent-rust-desktop/files/generated-gitkeep +0 -0
  10. package/src/templates/agent-rust-desktop/files/main.rs +24 -0
  11. package/src/templates/agent-rust-desktop/fragment.yaml +18 -0
  12. package/src/templates/ai-claude-code/files/CLAUDE.md +19 -0
  13. package/src/templates/ai-claude-code/files/commands-README.md +7 -0
  14. package/src/templates/ai-claude-code/fragment.yaml +12 -0
  15. package/src/templates/ai-codex/files/project.md +11 -0
  16. package/src/templates/ai-codex/files/prompts-README.md +7 -0
  17. package/src/templates/ai-codex/fragment.yaml +12 -0
  18. package/src/templates/ai-cursor/files/rules-README.md +6 -0
  19. package/src/templates/ai-cursor/files/rules-project.md +15 -0
  20. package/src/templates/ai-cursor/fragment.yaml +12 -0
  21. package/src/templates/ai-kiro/files/prompts-README.md +5 -0
  22. package/src/templates/ai-kiro/files/specs-gitkeep +0 -0
  23. package/src/templates/ai-kiro/files/steering-README.md +5 -0
  24. package/src/templates/ai-kiro/files/steering-project.md +9 -0
  25. package/src/templates/ai-kiro/fragment.yaml +18 -0
  26. package/src/templates/ci-gitee/files/pipeline.yml +18 -0
  27. package/src/templates/ci-gitee/fragment.yaml +9 -0
  28. package/src/templates/ci-github/files/ci.yml +17 -0
  29. package/src/templates/ci-github/fragment.yaml +9 -0
  30. package/src/templates/contracts-base/files/CHANGELOG.md +10 -0
  31. package/src/templates/contracts-base/files/README.md +75 -0
  32. package/src/templates/contracts-base/files/_ai-policy.yaml +20 -0
  33. package/src/templates/contracts-base/files/dictionaries/domain-models.yaml +3 -0
  34. package/src/templates/contracts-base/files/dictionaries/enums.yaml +3 -0
  35. package/src/templates/contracts-base/files/environment/.env.example +3 -0
  36. package/src/templates/contracts-base/files/environment/env.schema.json +10 -0
  37. package/src/templates/contracts-base/files/errors/error-codes.yaml +6 -0
  38. package/src/templates/contracts-base/files/errors/error-response.schema.json +13 -0
  39. package/src/templates/contracts-base/files/events-gitkeep +0 -0
  40. package/src/templates/contracts-base/files/resources-examples-gitkeep +0 -0
  41. package/src/templates/contracts-base/files/resources-mocks-gitkeep +0 -0
  42. package/src/templates/contracts-base/files/states-gitkeep +0 -0
  43. package/src/templates/contracts-base/files/tests-acceptance-gitkeep +0 -0
  44. package/src/templates/contracts-base/files/tests-contracts-gitkeep +0 -0
  45. package/src/templates/contracts-base/files/tests-postman-gitkeep +0 -0
  46. package/src/templates/contracts-base/fragment.yaml +53 -0
  47. package/src/templates/contracts-events/files/asyncapi.yaml +8 -0
  48. package/src/templates/contracts-events/fragment.yaml +9 -0
  49. package/src/templates/contracts-rest/files/api.yaml +7 -0
  50. package/src/templates/contracts-rest/fragment.yaml +9 -0
  51. package/src/templates/contracts-rest-events/files/api.yaml +7 -0
  52. package/src/templates/contracts-rest-events/files/asyncapi.yaml +8 -0
  53. package/src/templates/contracts-rest-events/fragment.yaml +12 -0
  54. package/src/templates/deploy-bare-metal/files/release-checklist.md +8 -0
  55. package/src/templates/deploy-bare-metal/fragment.yaml +9 -0
  56. package/src/templates/deploy-common/files/README.md +5 -0
  57. package/src/templates/deploy-common/fragment.yaml +8 -0
  58. package/src/templates/deploy-docker-compose/files/compose.override.example.yaml +4 -0
  59. package/src/templates/deploy-docker-compose/files/docker-compose.yaml +4 -0
  60. package/src/templates/deploy-docker-compose/fragment.yaml +12 -0
  61. package/src/templates/deploy-kubernetes/files/Chart.yaml +6 -0
  62. package/src/templates/deploy-kubernetes/files/deployment.yaml +18 -0
  63. package/src/templates/deploy-kubernetes/files/namespace.yaml +4 -0
  64. package/src/templates/deploy-kubernetes/files/values.yaml +4 -0
  65. package/src/templates/deploy-kubernetes/fragment.yaml +18 -0
  66. package/src/templates/docs-skeleton/files/README.md +134 -0
  67. package/src/templates/docs-skeleton/files/arch-README.md +18 -0
  68. package/src/templates/docs-skeleton/files/arch-_ai-policy.yaml +8 -0
  69. package/src/templates/docs-skeleton/files/archive-README.md +20 -0
  70. package/src/templates/docs-skeleton/files/archive-_ai-policy.yaml +8 -0
  71. package/src/templates/docs-skeleton/files/assets-README.md +29 -0
  72. package/src/templates/docs-skeleton/files/assets-_ai-policy.yaml +8 -0
  73. package/src/templates/docs-skeleton/files/assets-design-gitkeep +0 -0
  74. package/src/templates/docs-skeleton/files/assets-diagrams-gitkeep +0 -0
  75. package/src/templates/docs-skeleton/files/assets-images-gitkeep +0 -0
  76. package/src/templates/docs-skeleton/files/be-README.md +29 -0
  77. package/src/templates/docs-skeleton/files/be-_ai-policy.yaml +8 -0
  78. package/src/templates/docs-skeleton/files/bg-README.md +21 -0
  79. package/src/templates/docs-skeleton/files/bg-_ai-policy.yaml +8 -0
  80. package/src/templates/docs-skeleton/files/eng-README.md +21 -0
  81. package/src/templates/docs-skeleton/files/eng-_ai-policy.yaml +9 -0
  82. package/src/templates/docs-skeleton/files/fe-README.md +31 -0
  83. package/src/templates/docs-skeleton/files/fe-_ai-policy.yaml +8 -0
  84. package/src/templates/docs-skeleton/files/governance-README.md +28 -0
  85. package/src/templates/docs-skeleton/files/governance-_ai-policy.yaml +13 -0
  86. package/src/templates/docs-skeleton/files/governance-ai-access-modes.md +324 -0
  87. package/src/templates/docs-skeleton/files/governance-assets.md +50 -0
  88. package/src/templates/docs-skeleton/files/governance-checklists.md +55 -0
  89. package/src/templates/docs-skeleton/files/governance-ci.md +36 -0
  90. package/src/templates/docs-skeleton/files/governance-deploy-ops.md +107 -0
  91. package/src/templates/docs-skeleton/files/governance-docs-references.md +39 -0
  92. package/src/templates/docs-skeleton/files/governance-git-workflow.md +45 -0
  93. package/src/templates/docs-skeleton/files/governance-integrations.md +66 -0
  94. package/src/templates/docs-skeleton/files/governance-security.md +69 -0
  95. package/src/templates/docs-skeleton/files/governance-tools-scripts.md +128 -0
  96. package/src/templates/docs-skeleton/files/process-docs-README.md +22 -0
  97. package/src/templates/docs-skeleton/files/process-docs-_ai-policy.yaml +10 -0
  98. package/src/templates/docs-skeleton/files/references-README.md +19 -0
  99. package/src/templates/docs-skeleton/files/references-_ai-policy.yaml +9 -0
  100. package/src/templates/docs-skeleton/files/references-legal-source.yaml +5 -0
  101. package/src/templates/docs-skeleton/files/references-standards-source.yaml +5 -0
  102. package/src/templates/docs-skeleton/files/references-vendors-source.yaml +5 -0
  103. package/src/templates/docs-skeleton/files/usage-quickstart.md +168 -0
  104. package/src/templates/docs-skeleton/fragment.yaml +129 -0
  105. package/src/templates/integrations-docs/files/README.md +53 -0
  106. package/src/templates/integrations-docs/files/_template.md +63 -0
  107. package/src/templates/integrations-docs/fragment.yaml +12 -0
  108. package/src/templates/license-apache-2/files/LICENSE +15 -0
  109. package/src/templates/license-apache-2/fragment.yaml +9 -0
  110. package/src/templates/license-mit/files/LICENSE +21 -0
  111. package/src/templates/license-mit/fragment.yaml +9 -0
  112. package/src/templates/miniapp-wechat/files/README.md +10 -0
  113. package/src/templates/miniapp-wechat/files/app.js +5 -0
  114. package/src/templates/miniapp-wechat/files/app.json +9 -0
  115. package/src/templates/miniapp-wechat/files/app.wxss +4 -0
  116. package/src/templates/miniapp-wechat/files/generated-gitkeep +0 -0
  117. package/src/templates/miniapp-wechat/files/pages-index.js +4 -0
  118. package/src/templates/miniapp-wechat/files/pages-index.json +3 -0
  119. package/src/templates/miniapp-wechat/files/pages-index.wxml +3 -0
  120. package/src/templates/miniapp-wechat/files/pages-index.wxss +10 -0
  121. package/src/templates/miniapp-wechat/files/project.config.json +13 -0
  122. package/src/templates/miniapp-wechat/files/sitemap.json +8 -0
  123. package/src/templates/miniapp-wechat/fragment.yaml +39 -0
  124. package/src/templates/mobile-flutter/files/README.md +14 -0
  125. package/src/templates/mobile-flutter/files/analysis_options.yaml +6 -0
  126. package/src/templates/mobile-flutter/files/generated-gitkeep +0 -0
  127. package/src/templates/mobile-flutter/files/main.dart +18 -0
  128. package/src/templates/mobile-flutter/files/pubspec.yaml +19 -0
  129. package/src/templates/mobile-flutter/fragment.yaml +21 -0
  130. package/src/templates/mobile-react-native/files/App.tsx +10 -0
  131. package/src/templates/mobile-react-native/files/README.md +15 -0
  132. package/src/templates/mobile-react-native/files/app.json +4 -0
  133. package/src/templates/mobile-react-native/files/generated-gitkeep +0 -0
  134. package/src/templates/mobile-react-native/files/index.js +5 -0
  135. package/src/templates/mobile-react-native/files/package.json +14 -0
  136. package/src/templates/mobile-react-native/fragment.yaml +24 -0
  137. package/src/templates/repo-skeleton/files/docs-gitkeep +0 -0
  138. package/src/templates/repo-skeleton/files/ops-README.md +6 -0
  139. package/src/templates/repo-skeleton/files/ops-k8s-gitkeep +0 -0
  140. package/src/templates/repo-skeleton/files/ops-monitoring-gitkeep +0 -0
  141. package/src/templates/repo-skeleton/files/ops-terraform-gitkeep +0 -0
  142. package/src/templates/repo-skeleton/files/scripts-README.md +11 -0
  143. package/src/templates/repo-skeleton/files/scripts-ci-gitkeep +0 -0
  144. package/src/templates/repo-skeleton/files/scripts-dev-gitkeep +0 -0
  145. package/src/templates/repo-skeleton/files/scripts-oneoff-gitkeep +0 -0
  146. package/src/templates/repo-skeleton/files/scripts-ops-gitkeep +0 -0
  147. package/src/templates/repo-skeleton/files/tools-README.md +4 -0
  148. package/src/templates/repo-skeleton/files/tools-contract-gitkeep +0 -0
  149. package/src/templates/repo-skeleton/fragment.yaml +41 -0
  150. package/src/templates/role-data/files/README.md +23 -0
  151. package/src/templates/role-data/files/_ai-policy.yaml +10 -0
  152. package/src/templates/role-data/files/dashboards-gitkeep +0 -0
  153. package/src/templates/role-data/files/data-catalog-gitkeep +0 -0
  154. package/src/templates/role-data/files/tracking-spec-gitkeep +0 -0
  155. package/src/templates/role-data/fragment.yaml +21 -0
  156. package/src/templates/role-design/files/README.md +26 -0
  157. package/src/templates/role-design/files/_ai-policy.yaml +11 -0
  158. package/src/templates/role-design/files/figma-exports-gitkeep +0 -0
  159. package/src/templates/role-design/files/ui-spec-gitkeep +0 -0
  160. package/src/templates/role-design/fragment.yaml +18 -0
  161. package/src/templates/role-field/files/README.md +33 -0
  162. package/src/templates/role-field/files/_ai-policy.yaml +13 -0
  163. package/src/templates/role-field/files/runbooks-README.md +5 -0
  164. package/src/templates/role-field/files/templates-README.md +5 -0
  165. package/src/templates/role-field/files/templates-_ai-policy.yaml +8 -0
  166. package/src/templates/role-field/fragment.yaml +21 -0
  167. package/src/templates/role-legal-security/files/README.md +33 -0
  168. package/src/templates/role-legal-security/files/_ai-policy.yaml +14 -0
  169. package/src/templates/role-legal-security/files/assessments-README.md +5 -0
  170. package/src/templates/role-legal-security/files/assessments-_ai-policy.yaml +9 -0
  171. package/src/templates/role-legal-security/files/evidence-README.md +5 -0
  172. package/src/templates/role-legal-security/files/evidence-_ai-policy.yaml +8 -0
  173. package/src/templates/role-legal-security/fragment.yaml +24 -0
  174. package/src/templates/role-marketing/files/README.md +32 -0
  175. package/src/templates/role-marketing/files/_ai-policy.yaml +14 -0
  176. package/src/templates/role-marketing/files/drafts-README.md +7 -0
  177. package/src/templates/role-marketing/files/drafts-_ai-policy.yaml +19 -0
  178. package/src/templates/role-marketing/files/published-README.md +5 -0
  179. package/src/templates/role-marketing/files/published-_ai-policy.yaml +8 -0
  180. package/src/templates/role-marketing/files/templates-README.md +5 -0
  181. package/src/templates/role-marketing/files/templates-_ai-policy.yaml +8 -0
  182. package/src/templates/role-marketing/fragment.yaml +30 -0
  183. package/src/templates/role-qa/files/README.md +20 -0
  184. package/src/templates/role-qa/files/_ai-policy.yaml +9 -0
  185. package/src/templates/role-qa/files/defects-gitkeep +0 -0
  186. package/src/templates/role-qa/files/test-cases-gitkeep +0 -0
  187. package/src/templates/role-qa/files/test-plans-gitkeep +0 -0
  188. package/src/templates/role-qa/fragment.yaml +21 -0
  189. package/src/templates/root-files/files/.editorconfig +15 -0
  190. package/src/templates/root-files/files/.gitattributes +13 -0
  191. package/src/templates/root-files/files/AGENTS.md +194 -0
  192. package/src/templates/root-files/files/README.md +16 -0
  193. package/src/templates/root-files/fragment.yaml +20 -0
  194. package/src/templates/server-go/files/README.md +3 -0
  195. package/src/templates/server-go/files/generated-gitkeep +0 -0
  196. package/src/templates/server-go/files/go.mod +3 -0
  197. package/src/templates/server-go/files/main.go +7 -0
  198. package/src/templates/server-go/fragment.yaml +18 -0
  199. package/src/templates/server-java/files/Application.java +12 -0
  200. package/src/templates/server-java/files/README.md +48 -0
  201. package/src/templates/server-java/files/application-test.yaml +17 -0
  202. package/src/templates/server-java/files/application.yaml +33 -0
  203. package/src/templates/server-java/files/generated-gitkeep +0 -0
  204. package/src/templates/server-java/files/pom.xml +97 -0
  205. package/src/templates/server-java/fragment.yaml +24 -0
  206. package/src/templates/server-node/files/README.md +6 -0
  207. package/src/templates/server-node/files/generated-gitkeep +0 -0
  208. package/src/templates/server-node/files/package.json +9 -0
  209. package/src/templates/server-node/files/src-index.ts +2 -0
  210. package/src/templates/server-node/fragment.yaml +18 -0
  211. package/src/templates/server-python/files/README.md +4 -0
  212. package/src/templates/server-python/files/app-init.py +1 -0
  213. package/src/templates/server-python/files/generated-gitkeep +0 -0
  214. package/src/templates/server-python/files/pyproject.toml +5 -0
  215. package/src/templates/server-python/fragment.yaml +18 -0
  216. package/src/templates/web-react/files/README.md +29 -0
  217. package/src/templates/web-react/files/generated-gitkeep +0 -0
  218. package/src/templates/web-react/files/index.html +12 -0
  219. package/src/templates/web-react/files/package.json +25 -0
  220. package/src/templates/web-react/files/src-App.tsx +11 -0
  221. package/src/templates/web-react/files/src-main.tsx +14 -0
  222. package/src/templates/web-react/files/tsconfig.json +20 -0
  223. package/src/templates/web-react/files/vite.config.ts +15 -0
  224. package/src/templates/web-react/fragment.yaml +30 -0
  225. package/src/templates/web-vue/files/README.md +3 -0
  226. package/src/templates/web-vue/files/generated-gitkeep +0 -0
  227. package/src/templates/web-vue/files/index.html +11 -0
  228. package/src/templates/web-vue/files/package.json +9 -0
  229. package/src/templates/web-vue/files/src-main.ts +3 -0
  230. package/src/templates/web-vue/fragment.yaml +21 -0
package/dist/index.js ADDED
@@ -0,0 +1,1479 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/version.ts
4
+ var SCAFFOLDER_VERSION = "0.1.0";
5
+
6
+ // src/schema/options.ts
7
+ import Ajv from "ajv";
8
+ import addFormats from "ajv-formats";
9
+ import { parse as parseYaml } from "yaml";
10
+ var BACKEND_LANGUAGES = ["node", "python", "go", "java", "none"];
11
+ var FRONTEND_FRAMEWORKS = ["react", "vue", "none"];
12
+ var AGENT_KINDS = ["rust-desktop", "none"];
13
+ var MOBILE_KINDS = ["react-native", "flutter", "none"];
14
+ var MINIAPP_KINDS = ["wechat", "none"];
15
+ var DEPLOY_TARGETS = ["docker-compose", "kubernetes", "bare-metal", "none"];
16
+ var CONTRACT_KINDS = ["rest", "rest+events", "events-only", "none"];
17
+ var AI_TOOLS = ["kiro", "cursor", "claude-code", "codex", "none"];
18
+ var LICENSE_KINDS = ["mit", "apache-2.0", "proprietary"];
19
+ var CI_PLATFORMS = ["gitee", "github"];
20
+ var ROLE_KINDS = ["design", "qa", "field", "data", "legal-security", "marketing"];
21
+ var PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
22
+ var PROJECT_NAME_PATTERN_SOURCE = "^[a-z0-9][a-z0-9-]{0,62}$";
23
+ var OPTIONS_FIELD_ORDER = [
24
+ "projectName",
25
+ "backend",
26
+ "frontend",
27
+ "mobile",
28
+ "miniapp",
29
+ "agent",
30
+ "deploy",
31
+ "contract",
32
+ "ai",
33
+ "license",
34
+ "gitLfs",
35
+ "integrations",
36
+ "ci",
37
+ "roles"
38
+ ];
39
+ var OPTIONS_SCHEMA = {
40
+ $schema: "http://json-schema.org/draft-07/schema#",
41
+ $id: "https://cgsy.example.com/schemas/scaffolder/options.json",
42
+ title: "ScaffolderOptions",
43
+ type: "object",
44
+ additionalProperties: false,
45
+ required: [
46
+ "projectName",
47
+ "backend",
48
+ "frontend",
49
+ "mobile",
50
+ "miniapp",
51
+ "agent",
52
+ "deploy",
53
+ "contract",
54
+ "ai",
55
+ "license",
56
+ "gitLfs",
57
+ "integrations",
58
+ "ci",
59
+ "roles"
60
+ ],
61
+ properties: {
62
+ projectName: {
63
+ type: "string",
64
+ pattern: PROJECT_NAME_PATTERN_SOURCE
65
+ },
66
+ backend: { type: "string", enum: [...BACKEND_LANGUAGES] },
67
+ frontend: { type: "string", enum: [...FRONTEND_FRAMEWORKS] },
68
+ mobile: { type: "string", enum: [...MOBILE_KINDS] },
69
+ miniapp: { type: "string", enum: [...MINIAPP_KINDS] },
70
+ agent: { type: "string", enum: [...AGENT_KINDS] },
71
+ deploy: { type: "string", enum: [...DEPLOY_TARGETS] },
72
+ contract: { type: "string", enum: [...CONTRACT_KINDS] },
73
+ ai: { type: "string", enum: [...AI_TOOLS] },
74
+ license: { type: "string", enum: [...LICENSE_KINDS] },
75
+ gitLfs: { type: "boolean" },
76
+ integrations: { type: "boolean" },
77
+ ci: { type: "string", enum: [...CI_PLATFORMS] },
78
+ roles: {
79
+ type: "array",
80
+ items: { type: "string", enum: [...ROLE_KINDS] },
81
+ uniqueItems: true
82
+ }
83
+ }
84
+ };
85
+ var ajv = new Ajv({ allErrors: true, strict: true });
86
+ addFormats(ajv);
87
+ var validateFn = ajv.compile(OPTIONS_SCHEMA);
88
+ var OptionsValidationError = class extends Error {
89
+ issues;
90
+ constructor(issues) {
91
+ super(
92
+ `Invalid Scaffolder options:
93
+ ` + issues.map((i) => ` - ${i.path || "<root>"}: ${i.message}`).join("\n")
94
+ );
95
+ this.name = "OptionsValidationError";
96
+ this.issues = issues;
97
+ }
98
+ };
99
+ function collectIssues() {
100
+ const errs = validateFn.errors ?? [];
101
+ return errs.map((e) => ({
102
+ path: e.instancePath || (e.params && "missingProperty" in e.params ? `/${String(e.params.missingProperty)}` : ""),
103
+ message: e.message ?? "validation error"
104
+ }));
105
+ }
106
+ function assertOptions(value) {
107
+ if (!validateFn(value)) {
108
+ throw new OptionsValidationError(collectIssues());
109
+ }
110
+ return value;
111
+ }
112
+
113
+ // src/errors.ts
114
+ var ScaffolderError = class extends Error {
115
+ suggestion;
116
+ constructor(message, suggestion) {
117
+ super(message);
118
+ this.name = new.target.name;
119
+ this.suggestion = suggestion;
120
+ }
121
+ toThreeLine() {
122
+ return { title: this.title, message: this.message, suggestion: this.suggestion };
123
+ }
124
+ };
125
+ var UserInputError = class extends ScaffolderError {
126
+ exitCode = 1;
127
+ title = "UserInputError";
128
+ };
129
+ var FilesystemError = class extends ScaffolderError {
130
+ exitCode = 2;
131
+ title = "FilesystemError";
132
+ };
133
+ var TemplateError = class extends ScaffolderError {
134
+ exitCode = 3;
135
+ title = "TemplateError";
136
+ };
137
+ function formatError(err) {
138
+ if (err instanceof ScaffolderError) {
139
+ const { title, message, suggestion } = err.toThreeLine();
140
+ return `\u2717 ${title}
141
+ ${message}
142
+ \u5EFA\u8BAE\uFF1A${suggestion}`;
143
+ }
144
+ if (err instanceof Error) {
145
+ return `\u2717 UnexpectedError
146
+ ${err.message}
147
+ \u5EFA\u8BAE\uFF1A\u8BF7\u5728 issue \u4E2D\u9644\u4E0A\u5B8C\u6574\u5806\u6808`;
148
+ }
149
+ return `\u2717 UnexpectedError
150
+ ${String(err)}
151
+ \u5EFA\u8BAE\uFF1A\u8BF7\u5728 issue \u4E2D\u9644\u4E0A\u5B8C\u6574\u4E0A\u4E0B\u6587`;
152
+ }
153
+
154
+ // src/cli.ts
155
+ async function run(io) {
156
+ const args = io.argv.slice(2);
157
+ if (args.some((a) => a === "-h" || a === "--help")) {
158
+ io.stdout(helpText());
159
+ return 0;
160
+ }
161
+ if (args.some((a) => a === "-v" || a === "--version")) {
162
+ io.stdout(SCAFFOLDER_VERSION);
163
+ return 0;
164
+ }
165
+ if (args.length === 0) {
166
+ io.stdout(helpText());
167
+ return 0;
168
+ }
169
+ const [command, ...rest] = args;
170
+ if (command !== "create") {
171
+ io.stderr(
172
+ formatError(
173
+ new UserInputError(
174
+ `unknown command: ${JSON.stringify(command)}`,
175
+ "\u53EF\u7528\u7684\u547D\u4EE4\uFF1Acreate\u3002\u67E5\u770B\u5E2E\u52A9\uFF1A--help"
176
+ )
177
+ )
178
+ );
179
+ return 1;
180
+ }
181
+ try {
182
+ const { projectName, flags } = parseCreateArgs(rest);
183
+ await io.onCreate({ projectName, flags });
184
+ return 0;
185
+ } catch (err) {
186
+ io.stderr(formatError(err));
187
+ if (err instanceof ScaffolderError) {
188
+ return err.exitCode;
189
+ }
190
+ return 1;
191
+ }
192
+ }
193
+ function parseCreateArgs(args) {
194
+ let projectName;
195
+ let yes = false;
196
+ let force = false;
197
+ let dryRun = false;
198
+ let quiet = false;
199
+ let verbose = false;
200
+ let config;
201
+ let ci;
202
+ let roles;
203
+ for (let i = 0; i < args.length; i += 1) {
204
+ const a = args[i];
205
+ switch (a) {
206
+ case "--yes":
207
+ yes = true;
208
+ break;
209
+ case "--force":
210
+ force = true;
211
+ break;
212
+ case "--dry-run":
213
+ dryRun = true;
214
+ break;
215
+ case "--quiet":
216
+ quiet = true;
217
+ break;
218
+ case "--verbose":
219
+ verbose = true;
220
+ break;
221
+ case "--config": {
222
+ const next = args[i + 1];
223
+ if (next === void 0 || next.startsWith("--")) {
224
+ throw new UserInputError(
225
+ "--config requires a path argument",
226
+ "\u793A\u4F8B\uFF1A--config ./scaffolder.yaml"
227
+ );
228
+ }
229
+ config = next;
230
+ i += 1;
231
+ break;
232
+ }
233
+ case "--ci": {
234
+ const next = args[i + 1];
235
+ if (next !== "gitee" && next !== "github") {
236
+ throw new UserInputError(
237
+ `invalid --ci value: ${JSON.stringify(next ?? "")}`,
238
+ "--ci \u53EA\u63A5\u53D7 gitee \u6216 github"
239
+ );
240
+ }
241
+ ci = next;
242
+ i += 1;
243
+ break;
244
+ }
245
+ case "--roles": {
246
+ const next = args[i + 1];
247
+ if (next === void 0 || next.startsWith("--")) {
248
+ throw new UserInputError(
249
+ "--roles requires a comma-separated list",
250
+ "\u793A\u4F8B\uFF1A--roles qa,field,marketing\uFF1B\u53EF\u9009\u503C\uFF1A" + ROLE_KINDS.join(",")
251
+ );
252
+ }
253
+ const parts = next.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
254
+ const seen = /* @__PURE__ */ new Set();
255
+ for (const part of parts) {
256
+ if (!ROLE_KINDS.includes(part)) {
257
+ throw new UserInputError(
258
+ `invalid --roles value: ${JSON.stringify(part)}`,
259
+ "\u53EF\u9009\u89D2\u8272\uFF1A" + ROLE_KINDS.join(",")
260
+ );
261
+ }
262
+ if (seen.has(part)) {
263
+ throw new UserInputError(
264
+ `duplicate role: ${part}`,
265
+ "--roles \u4E2D\u6BCF\u4E2A\u89D2\u8272\u4EC5\u80FD\u51FA\u73B0\u4E00\u6B21"
266
+ );
267
+ }
268
+ seen.add(part);
269
+ }
270
+ roles = parts;
271
+ i += 1;
272
+ break;
273
+ }
274
+ default: {
275
+ if (a.startsWith("--")) {
276
+ throw new UserInputError(
277
+ `unknown option: ${a}`,
278
+ "\u67E5\u770B\u53EF\u7528\u9009\u9879\uFF1Acreate-keel --help"
279
+ );
280
+ }
281
+ if (projectName === void 0) {
282
+ projectName = a;
283
+ } else {
284
+ throw new UserInputError(
285
+ `unexpected extra argument: ${JSON.stringify(a)}`,
286
+ "\u6BCF\u6B21\u53EA\u80FD\u521B\u5EFA\u4E00\u4E2A\u9879\u76EE"
287
+ );
288
+ }
289
+ }
290
+ }
291
+ }
292
+ if (projectName === void 0) {
293
+ throw new UserInputError(
294
+ "missing <project-name>",
295
+ "\u8BF7\u63D0\u4F9B\u9879\u76EE\u540D\uFF0C\u4F8B\u5982\uFF1Anpx @wneng/create-keel create my-app"
296
+ );
297
+ }
298
+ if (!PROJECT_NAME_PATTERN.test(projectName)) {
299
+ throw new UserInputError(
300
+ `invalid project name: ${JSON.stringify(projectName)}`,
301
+ "\u9879\u76EE\u540D\u5E94\u5339\u914D ^[a-z0-9][a-z0-9-]{0,62}$\uFF0C\u4F8B\u5982\uFF1Amy-app"
302
+ );
303
+ }
304
+ const flags = {
305
+ yes,
306
+ force,
307
+ dryRun,
308
+ quiet,
309
+ verbose,
310
+ ...config !== void 0 ? { config } : {},
311
+ ...ci !== void 0 ? { ci } : {},
312
+ ...roles !== void 0 ? { roles } : {}
313
+ };
314
+ return { projectName, flags };
315
+ }
316
+ function helpText() {
317
+ return [
318
+ "create-keel \u2014 scaffold a Contract-First project",
319
+ "",
320
+ "USAGE",
321
+ " npx @wneng/create-keel create <project-name> [options]",
322
+ "",
323
+ "OPTIONS",
324
+ " --yes Skip interactive prompts and use defaults",
325
+ " --force Overwrite a non-empty target directory after confirmation",
326
+ " --dry-run Compute the plan without writing any files",
327
+ " --quiet Only emit errors and the final summary",
328
+ " --verbose Emit per-file render/write logs",
329
+ " --config <path> Read options from a JSON or YAML file",
330
+ " --ci <platform> Which CI platform to scaffold (gitee|github, default: gitee)",
331
+ " --roles <list> Comma-separated role directories to scaffold",
332
+ " (" + ROLE_KINDS.join("|") + "; default: none)",
333
+ " -h, --help Show this help",
334
+ " -v, --version Show the scaffolder version",
335
+ "",
336
+ "See .kiro/specs/project-scaffolder/ for the full specification."
337
+ ].join("\n");
338
+ }
339
+
340
+ // src/orchestrator.ts
341
+ import * as path5 from "node:path";
342
+
343
+ // src/input/defaults.ts
344
+ var OPTION_DEFAULTS = {
345
+ backend: "node",
346
+ frontend: "react",
347
+ mobile: "none",
348
+ miniapp: "none",
349
+ agent: "none",
350
+ deploy: "docker-compose",
351
+ contract: "rest",
352
+ ai: "kiro",
353
+ license: "mit",
354
+ gitLfs: false,
355
+ integrations: false,
356
+ ci: "gitee",
357
+ roles: []
358
+ };
359
+ function buildDefaultOptions(projectName) {
360
+ return { projectName, ...OPTION_DEFAULTS };
361
+ }
362
+
363
+ // src/input/config.ts
364
+ import Ajv2 from "ajv";
365
+ import { promises as fs } from "node:fs";
366
+ import * as path from "node:path";
367
+ import { parse as parseYaml2 } from "yaml";
368
+ var PARTIAL_OPTIONS_SCHEMA = {
369
+ $schema: "http://json-schema.org/draft-07/schema#",
370
+ $id: "https://cgsy.example.com/schemas/scaffolder/partial-options.json",
371
+ title: "PartialScaffolderOptions",
372
+ type: "object",
373
+ additionalProperties: false,
374
+ properties: {
375
+ projectName: { type: "string", pattern: PROJECT_NAME_PATTERN_SOURCE },
376
+ backend: { type: "string", enum: [...BACKEND_LANGUAGES] },
377
+ frontend: { type: "string", enum: [...FRONTEND_FRAMEWORKS] },
378
+ mobile: { type: "string", enum: [...MOBILE_KINDS] },
379
+ miniapp: { type: "string", enum: [...MINIAPP_KINDS] },
380
+ agent: { type: "string", enum: [...AGENT_KINDS] },
381
+ deploy: { type: "string", enum: [...DEPLOY_TARGETS] },
382
+ contract: { type: "string", enum: [...CONTRACT_KINDS] },
383
+ ai: { type: "string", enum: [...AI_TOOLS] },
384
+ license: { type: "string", enum: [...LICENSE_KINDS] },
385
+ gitLfs: { type: "boolean" },
386
+ integrations: { type: "boolean" },
387
+ ci: { type: "string", enum: [...CI_PLATFORMS] },
388
+ roles: {
389
+ type: "array",
390
+ items: { type: "string", enum: [...ROLE_KINDS] },
391
+ uniqueItems: true
392
+ }
393
+ }
394
+ };
395
+ var ajv2 = new Ajv2({ allErrors: true, strict: true });
396
+ var validate = ajv2.compile(
397
+ PARTIAL_OPTIONS_SCHEMA
398
+ );
399
+ function formatIssues() {
400
+ const errs = validate.errors ?? [];
401
+ return errs.map((e) => {
402
+ const path6 = e.instancePath || (e.params && "missingProperty" in e.params ? `/${String(e.params.missingProperty)}` : "<root>");
403
+ return `${path6}: ${e.message ?? "validation error"}`;
404
+ }).join("; ");
405
+ }
406
+ async function loadConfigFile(filePath) {
407
+ const ext = path.extname(filePath).toLowerCase();
408
+ const isJson = ext === ".json";
409
+ const isYaml = ext === ".yaml" || ext === ".yml";
410
+ if (!isJson && !isYaml) {
411
+ throw new UserInputError(
412
+ `unsupported config extension: ${ext || "<none>"}`,
413
+ "\u8BF7\u4F7F\u7528 .json \u6216 .yaml / .yml \u6587\u4EF6"
414
+ );
415
+ }
416
+ let text;
417
+ try {
418
+ text = await fs.readFile(filePath, "utf8");
419
+ } catch (e) {
420
+ throw new UserInputError(
421
+ `failed to read config file: ${e.message}`,
422
+ "\u8BF7\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84\u5B58\u5728\u4E14\u53EF\u8BFB"
423
+ );
424
+ }
425
+ let raw;
426
+ try {
427
+ raw = isJson ? JSON.parse(text) : parseYaml2(text);
428
+ } catch (e) {
429
+ throw new UserInputError(
430
+ `invalid ${isJson ? "JSON" : "YAML"} in config: ${e.message}`,
431
+ "\u8BF7\u68C0\u67E5\u6587\u4EF6\u5185\u5BB9\u662F\u5426\u4E3A\u5408\u6CD5\u7684 JSON / YAML"
432
+ );
433
+ }
434
+ if (raw === null || raw === void 0) {
435
+ return {};
436
+ }
437
+ if (typeof raw !== "object" || Array.isArray(raw)) {
438
+ throw new UserInputError(
439
+ "config root must be an object",
440
+ '\u793A\u4F8B\uFF1A{ "backend": "node", "frontend": "react" }'
441
+ );
442
+ }
443
+ if (!validate(raw)) {
444
+ throw new UserInputError(
445
+ `config file does not match schema: ${formatIssues()}`,
446
+ "\u53C2\u8003 defaults \u4E2D\u7684\u5B57\u6BB5\u540D\u4E0E\u53D6\u503C\u57DF"
447
+ );
448
+ }
449
+ const out = {};
450
+ for (const [k, v] of Object.entries(raw)) {
451
+ if (v !== void 0) out[k] = v;
452
+ }
453
+ return out;
454
+ }
455
+
456
+ // src/input/prompter.ts
457
+ var InquirerPrompter = class {
458
+ async input(question, validate2) {
459
+ const { input } = await import("@inquirer/prompts");
460
+ return input({ message: question, validate: validate2 });
461
+ }
462
+ async select(question, choices, def) {
463
+ const { select } = await import("@inquirer/prompts");
464
+ return await select({
465
+ message: question,
466
+ choices: choices.map((c) => ({ name: c.name, value: c.value })),
467
+ default: def
468
+ });
469
+ }
470
+ async confirm(question, def) {
471
+ const { confirm } = await import("@inquirer/prompts");
472
+ return confirm({ message: question, default: def });
473
+ }
474
+ async checkbox(question, choices, def) {
475
+ const { checkbox } = await import("@inquirer/prompts");
476
+ const defSet = new Set(def);
477
+ return await checkbox({
478
+ message: question,
479
+ choices: choices.map((c) => ({
480
+ name: c.name,
481
+ value: c.value,
482
+ checked: defSet.has(c.value)
483
+ }))
484
+ });
485
+ }
486
+ };
487
+ function stringChoices(values) {
488
+ return values.map((v) => ({ value: v, name: v }));
489
+ }
490
+ async function promptForOptions(input) {
491
+ if (!input.isTTY) {
492
+ throw new UserInputError(
493
+ "interactive prompts require a TTY",
494
+ "\u8BF7\u5728\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u8FD0\u884C\uFF0C\u6216\u63D0\u4F9B --yes / --config"
495
+ );
496
+ }
497
+ const p = input.prompter;
498
+ const pre = input.prefilled;
499
+ const report = input.report ?? (() => {
500
+ });
501
+ const projectName = pre.projectName ?? await p.input("\u9879\u76EE\u540D", (v) => {
502
+ if (!PROJECT_NAME_PATTERN.test(v)) {
503
+ return "\u9879\u76EE\u540D\u5E94\u5339\u914D ^[a-z0-9][a-z0-9-]{0,62}$";
504
+ }
505
+ return true;
506
+ });
507
+ report("projectName", projectName, false);
508
+ const backend = await chooseAxis(
509
+ p,
510
+ pre.backend,
511
+ "\u540E\u7AEF\u8BED\u8A00",
512
+ BACKEND_LANGUAGES,
513
+ OPTION_DEFAULTS.backend,
514
+ report,
515
+ "backend"
516
+ );
517
+ const frontend = await chooseAxis(
518
+ p,
519
+ pre.frontend,
520
+ "\u524D\u7AEF\u6846\u67B6",
521
+ FRONTEND_FRAMEWORKS,
522
+ OPTION_DEFAULTS.frontend,
523
+ report,
524
+ "frontend"
525
+ );
526
+ const mobile = await chooseAxis(
527
+ p,
528
+ pre.mobile,
529
+ "\u79FB\u52A8\u7AEF\uFF08\u624B\u673A\u539F\u751F\uFF09",
530
+ MOBILE_KINDS,
531
+ OPTION_DEFAULTS.mobile,
532
+ report,
533
+ "mobile"
534
+ );
535
+ const miniapp = await chooseAxis(
536
+ p,
537
+ pre.miniapp,
538
+ "\u5C0F\u7A0B\u5E8F",
539
+ MINIAPP_KINDS,
540
+ OPTION_DEFAULTS.miniapp,
541
+ report,
542
+ "miniapp"
543
+ );
544
+ const agent = await chooseAxis(
545
+ p,
546
+ pre.agent,
547
+ "\u7EC8\u7AEF/\u684C\u9762\u5BA2\u6237\u7AEF",
548
+ AGENT_KINDS,
549
+ OPTION_DEFAULTS.agent,
550
+ report,
551
+ "agent"
552
+ );
553
+ const deploy = await chooseAxis(
554
+ p,
555
+ pre.deploy,
556
+ "\u90E8\u7F72\u76EE\u6807",
557
+ DEPLOY_TARGETS,
558
+ OPTION_DEFAULTS.deploy,
559
+ report,
560
+ "deploy"
561
+ );
562
+ const contract = await chooseAxis(
563
+ p,
564
+ pre.contract,
565
+ "\u5951\u7EA6\u7C7B\u578B",
566
+ CONTRACT_KINDS,
567
+ OPTION_DEFAULTS.contract,
568
+ report,
569
+ "contract"
570
+ );
571
+ const ai = await chooseAxis(
572
+ p,
573
+ pre.ai,
574
+ "AI \u5DE5\u5177\u96C6\u6210",
575
+ AI_TOOLS,
576
+ OPTION_DEFAULTS.ai,
577
+ report,
578
+ "ai"
579
+ );
580
+ const license = await chooseAxis(
581
+ p,
582
+ pre.license,
583
+ "\u8BB8\u53EF\u8BC1",
584
+ LICENSE_KINDS,
585
+ OPTION_DEFAULTS.license,
586
+ report,
587
+ "license"
588
+ );
589
+ const gitLfs = pre.gitLfs ?? await p.confirm("\u542F\u7528 Git LFS \u7BA1\u7406\u5927\u6587\u4EF6", OPTION_DEFAULTS.gitLfs);
590
+ report("gitLfs", String(gitLfs), pre.gitLfs === void 0);
591
+ const integrations = pre.integrations ?? await p.confirm(
592
+ "\u751F\u6210 docs/06-\u96C6\u6210\u5BF9\u63A5/ \u9AA8\u67B6\uFF08\u5F53\u672C\u4ED3\u5E93\u7684\u67D0\u4E00\u7AEF\u9700\u8981\u4E0E\u5916\u90E8\u4ED3\u5E93\u5BF9\u63A5\u65F6\u542F\u7528\uFF09",
593
+ OPTION_DEFAULTS.integrations
594
+ );
595
+ report("integrations", String(integrations), pre.integrations === void 0);
596
+ const ci = await chooseAxis(
597
+ p,
598
+ pre.ci,
599
+ "CI \u5E73\u53F0",
600
+ CI_PLATFORMS,
601
+ OPTION_DEFAULTS.ci,
602
+ report,
603
+ "ci"
604
+ );
605
+ let roles;
606
+ if (pre.roles !== void 0) {
607
+ roles = pre.roles;
608
+ report("roles", roles.length > 0 ? roles.join(",") : "<none>", false);
609
+ } else {
610
+ const roleChoices = [
611
+ { value: "design", name: "design UI / UX \u8BBE\u8BA1\u8D44\u6E90" },
612
+ { value: "qa", name: "qa \u6D4B\u8BD5\u8BA1\u5212 / \u7528\u4F8B / \u7F3A\u9677\u5E93" },
613
+ { value: "field", name: "field \u5BA2\u6237\u90E8\u7F72\u624B\u518C / \u4E0A\u7EBF\u4F5C\u4E1A\u4E66" },
614
+ { value: "data", name: "data \u57CB\u70B9\u5951\u7EA6 / \u770B\u677F / \u6570\u636E\u8D44\u4EA7" },
615
+ { value: "legal-security", name: "legal-security \u5408\u89C4\u6E05\u5355 / PIA / \u5B89\u5168\u8BC4\u4F30" },
616
+ { value: "marketing", name: "marketing \u4EA7\u54C1\u4ECB\u7ECD / \u767D\u76AE\u4E66 / \u6848\u4F8B" }
617
+ ];
618
+ roles = await p.checkbox(
619
+ "\u542F\u7528\u54EA\u4E9B\u89D2\u8272\u76EE\u5F55\uFF1F\uFF08\u7A7A\u683C\u5207\u6362\uFF1B\u56DE\u8F66\u786E\u8BA4\uFF09",
620
+ roleChoices,
621
+ OPTION_DEFAULTS.roles
622
+ );
623
+ report("roles", roles.length > 0 ? roles.join(",") : "<none>", roles.length === 0);
624
+ }
625
+ return {
626
+ projectName,
627
+ backend,
628
+ frontend,
629
+ mobile,
630
+ miniapp,
631
+ agent,
632
+ deploy,
633
+ contract,
634
+ ai,
635
+ license,
636
+ gitLfs,
637
+ integrations,
638
+ ci,
639
+ roles
640
+ };
641
+ }
642
+ async function chooseAxis(prompter, prefilled, question, domain, def, report, axisName) {
643
+ if (prefilled !== void 0) {
644
+ report(axisName, prefilled, false);
645
+ return prefilled;
646
+ }
647
+ const value = await prompter.select(question, stringChoices(domain), def);
648
+ report(axisName, value, false);
649
+ return value;
650
+ }
651
+
652
+ // src/fragments/loader.ts
653
+ import { promises as fs2, accessSync } from "node:fs";
654
+ import * as path2 from "node:path";
655
+ import { fileURLToPath } from "node:url";
656
+
657
+ // src/schema/manifest.ts
658
+ import Ajv3 from "ajv";
659
+ import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
660
+ var SEMVER_PATTERN = "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$";
661
+ var FRAGMENT_NAME_PATTERN = "^[a-z0-9][a-z0-9-]{0,62}$";
662
+ var POSIX_RELATIVE_PATH_PATTERN = "^(?!/)(?!.*(^|/)\\.\\.($|/))[^\\\\]+$";
663
+ var MANIFEST_FILE_RULE_SCHEMA = {
664
+ type: "object",
665
+ additionalProperties: false,
666
+ required: ["from", "to", "render"],
667
+ properties: {
668
+ from: { type: "string", minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },
669
+ to: { type: "string", minLength: 1, pattern: POSIX_RELATIVE_PATH_PATTERN },
670
+ render: { type: "boolean" },
671
+ mode: { type: "integer", minimum: 0, maximum: 4095 }
672
+ }
673
+ };
674
+ var APPLIES_WHEN_SCHEMA = {
675
+ type: "object",
676
+ additionalProperties: false,
677
+ properties: {
678
+ backend: { type: "string", enum: [...BACKEND_LANGUAGES] },
679
+ frontend: { type: "string", enum: [...FRONTEND_FRAMEWORKS] },
680
+ mobile: { type: "string", enum: [...MOBILE_KINDS] },
681
+ miniapp: { type: "string", enum: [...MINIAPP_KINDS] },
682
+ agent: { type: "string", enum: [...AGENT_KINDS] },
683
+ deploy: { type: "string", enum: [...DEPLOY_TARGETS] },
684
+ contract: { type: "string", enum: [...CONTRACT_KINDS] },
685
+ ai: { type: "string", enum: [...AI_TOOLS] },
686
+ license: { type: "string", enum: [...LICENSE_KINDS] },
687
+ gitLfs: { type: "boolean" },
688
+ integrations: { type: "boolean" },
689
+ ci: { type: "string", enum: [...CI_PLATFORMS] },
690
+ role: { type: "string", enum: [...ROLE_KINDS] }
691
+ }
692
+ };
693
+ var MANIFEST_SCHEMA = {
694
+ $schema: "http://json-schema.org/draft-07/schema#",
695
+ $id: "https://cgsy.example.com/schemas/scaffolder/manifest.json",
696
+ title: "TemplateManifest",
697
+ type: "object",
698
+ additionalProperties: false,
699
+ required: ["name", "version", "appliesWhen", "priority", "files"],
700
+ properties: {
701
+ name: { type: "string", pattern: FRAGMENT_NAME_PATTERN },
702
+ version: { type: "string", pattern: SEMVER_PATTERN },
703
+ appliesWhen: APPLIES_WHEN_SCHEMA,
704
+ priority: { type: "integer", minimum: 0, maximum: 100 },
705
+ files: {
706
+ type: "array",
707
+ items: MANIFEST_FILE_RULE_SCHEMA
708
+ }
709
+ }
710
+ };
711
+ var ajv3 = new Ajv3({ allErrors: true, strict: true });
712
+ var validateFn2 = ajv3.compile(MANIFEST_SCHEMA);
713
+ var ManifestValidationError = class extends Error {
714
+ issues;
715
+ constructor(issues) {
716
+ super(
717
+ `Invalid TemplateManifest:
718
+ ` + issues.map((i) => ` - ${i.path || "<root>"}: ${i.message}`).join("\n")
719
+ );
720
+ this.name = "ManifestValidationError";
721
+ this.issues = issues;
722
+ }
723
+ };
724
+ function collectIssues2() {
725
+ const errs = validateFn2.errors ?? [];
726
+ return errs.map((e) => ({
727
+ path: e.instancePath || (e.params && "missingProperty" in e.params ? `/${String(e.params.missingProperty)}` : ""),
728
+ message: e.message ?? "validation error"
729
+ }));
730
+ }
731
+ function assertStructuralInvariants(m) {
732
+ const seen = /* @__PURE__ */ new Set();
733
+ for (const f of m.files) {
734
+ if (seen.has(f.to)) {
735
+ throw new ManifestValidationError([
736
+ { path: "/files", message: `duplicate target path: ${f.to}` }
737
+ ]);
738
+ }
739
+ seen.add(f.to);
740
+ }
741
+ }
742
+ function assertManifest(value) {
743
+ if (!validateFn2(value)) {
744
+ throw new ManifestValidationError(collectIssues2());
745
+ }
746
+ assertStructuralInvariants(value);
747
+ return value;
748
+ }
749
+ function parseManifest(input) {
750
+ let raw;
751
+ const trimmed = input.trim();
752
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
753
+ try {
754
+ raw = JSON.parse(trimmed);
755
+ } catch (e) {
756
+ throw new ManifestValidationError([
757
+ { path: "", message: `invalid JSON: ${e.message}` }
758
+ ]);
759
+ }
760
+ } else {
761
+ try {
762
+ raw = parseYaml3(input);
763
+ } catch (e) {
764
+ throw new ManifestValidationError([
765
+ { path: "", message: `invalid YAML: ${e.message}` }
766
+ ]);
767
+ }
768
+ }
769
+ return assertManifest(raw);
770
+ }
771
+
772
+ // src/fragments/loader.ts
773
+ function defaultTemplatesRoot() {
774
+ const here = path2.dirname(fileURLToPath(import.meta.url));
775
+ const pkgRoot = findPackageRoot(here);
776
+ return path2.join(pkgRoot, "src", "templates");
777
+ }
778
+ function findPackageRoot(start) {
779
+ let dir = start;
780
+ for (let i = 0; i < 10; i += 1) {
781
+ const candidate = path2.join(dir, "package.json");
782
+ try {
783
+ accessSync(candidate);
784
+ return dir;
785
+ } catch {
786
+ }
787
+ const parent = path2.dirname(dir);
788
+ if (parent === dir) break;
789
+ dir = parent;
790
+ }
791
+ throw new TemplateError(
792
+ `unable to locate package.json above ${start}`,
793
+ "\u8BF7\u786E\u8BA4 scaffolder \u4F5C\u4E3A npm \u5305\u5B89\u88C5\uFF0Csrc/templates/ \u4E0E package.json \u4F4D\u4E8E\u540C\u4E00\u5C42\u7EA7"
794
+ );
795
+ }
796
+ async function loadFragments(rootOverride) {
797
+ const templatesRoot = rootOverride ?? defaultTemplatesRoot();
798
+ let entries;
799
+ try {
800
+ const dirents = await fs2.readdir(templatesRoot, { withFileTypes: true });
801
+ entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
802
+ } catch (e) {
803
+ throw new TemplateError(
804
+ `failed to read templates directory ${templatesRoot}: ${e.message}`,
805
+ "\u8BF7\u786E\u8BA4 scaffolder \u5B89\u88C5\u5B8C\u6574\uFF08src/templates/ \u5B58\u5728\uFF09"
806
+ );
807
+ }
808
+ const loaded = [];
809
+ const seenNames = /* @__PURE__ */ new Set();
810
+ for (const dir of entries.sort()) {
811
+ const fragmentDir = path2.join(templatesRoot, dir);
812
+ const manifestPath = path2.join(fragmentDir, "fragment.yaml");
813
+ let text;
814
+ try {
815
+ text = await fs2.readFile(manifestPath, "utf8");
816
+ } catch (e) {
817
+ throw new TemplateError(
818
+ `fragment "${dir}" is missing fragment.yaml: ${e.message}`,
819
+ "\u6BCF\u4E2A fragment \u76EE\u5F55\u5FC5\u987B\u5305\u542B fragment.yaml"
820
+ );
821
+ }
822
+ let manifest;
823
+ try {
824
+ manifest = parseManifest(text);
825
+ } catch (e) {
826
+ if (e instanceof ManifestValidationError) {
827
+ throw new TemplateError(
828
+ `fragment "${dir}" has invalid manifest:
829
+ ${e.message}`,
830
+ "\u6309 fragment.yaml schema \u4FEE\u590D\u5B57\u6BB5"
831
+ );
832
+ }
833
+ throw e;
834
+ }
835
+ if (manifest.name !== dir) {
836
+ throw new TemplateError(
837
+ `fragment directory "${dir}" declares name "${manifest.name}"`,
838
+ "\u76EE\u5F55\u540D\u4E0E manifest.name \u5FC5\u987B\u4E00\u81F4\uFF0C\u4FBF\u4E8E\u9759\u6001\u7D22\u5F15"
839
+ );
840
+ }
841
+ if (seenNames.has(manifest.name)) {
842
+ throw new TemplateError(
843
+ `duplicate fragment name: ${manifest.name}`,
844
+ "\u6BCF\u4E2A fragment \u540D\u79F0\u5728\u5168\u5C40\u5FC5\u987B\u552F\u4E00"
845
+ );
846
+ }
847
+ seenNames.add(manifest.name);
848
+ for (const file of manifest.files) {
849
+ const fromAbs = path2.join(fragmentDir, file.from);
850
+ try {
851
+ await fs2.access(fromAbs);
852
+ } catch {
853
+ throw new TemplateError(
854
+ `fragment "${manifest.name}" references missing file: ${file.from}`,
855
+ "\u8BF7\u786E\u8BA4 files/ \u4E2D\u5305\u542B\u8BE5\u6587\u4EF6\uFF0C\u6216\u4FEE\u6B63 manifest.files[].from"
856
+ );
857
+ }
858
+ }
859
+ loaded.push({ manifest, rootDir: fragmentDir });
860
+ }
861
+ return { templatesRoot, fragments: loaded };
862
+ }
863
+
864
+ // src/fragments/resolver.ts
865
+ function fragmentApplies(manifest, options) {
866
+ return appliesWhenMatches(manifest.appliesWhen, options);
867
+ }
868
+ function appliesWhenMatches(appliesWhen, options) {
869
+ for (const key of Object.keys(appliesWhen)) {
870
+ const expected = appliesWhen[key];
871
+ if (expected === void 0) continue;
872
+ if (key === "role") {
873
+ if (!options.roles.includes(expected)) {
874
+ return false;
875
+ }
876
+ continue;
877
+ }
878
+ const actual = options[key];
879
+ if (actual !== expected) return false;
880
+ }
881
+ return true;
882
+ }
883
+ function selectFragments(options, loaded) {
884
+ const applicable = loaded.filter((f) => fragmentApplies(f.manifest, options));
885
+ return [...applicable].sort((a, b) => {
886
+ if (a.manifest.priority !== b.manifest.priority) {
887
+ return a.manifest.priority - b.manifest.priority;
888
+ }
889
+ return a.manifest.name.localeCompare(b.manifest.name);
890
+ });
891
+ }
892
+
893
+ // src/render/context.ts
894
+ function buildRenderContext(input) {
895
+ const now = input.now ?? /* @__PURE__ */ new Date();
896
+ return {
897
+ options: input.options,
898
+ year: String(now.getUTCFullYear()),
899
+ generatedAt: now.toISOString(),
900
+ scaffolderVersion: input.scaffolderVersion
901
+ };
902
+ }
903
+
904
+ // src/plan/builder.ts
905
+ import { promises as fs4 } from "node:fs";
906
+ import * as path3 from "node:path";
907
+
908
+ // src/render/renderer.ts
909
+ import { Eta } from "eta";
910
+ import { promises as fs3 } from "node:fs";
911
+ function createEta() {
912
+ return new Eta({
913
+ autoEscape: false,
914
+ useWith: false,
915
+ cache: false,
916
+ autoTrim: false,
917
+ // Force tags to the explicit form to keep syntax unambiguous.
918
+ tags: ["<%", "%>"]
919
+ });
920
+ }
921
+ function toStrictContext(ctx) {
922
+ const base = {
923
+ options: ctx.options,
924
+ year: ctx.year,
925
+ generatedAt: ctx.generatedAt,
926
+ scaffolderVersion: ctx.scaffolderVersion
927
+ };
928
+ return new Proxy(base, {
929
+ get(target, prop, receiver) {
930
+ if (typeof prop === "symbol") {
931
+ return Reflect.get(target, prop, receiver);
932
+ }
933
+ if (!(prop in target)) {
934
+ throw new TemplateError(
935
+ `undefined template variable: ${String(prop)}`,
936
+ "\u8BF7\u786E\u8BA4\u53D8\u91CF\u540D\u4E0E RenderContext \u5B57\u6BB5\u4E00\u81F4\uFF0C\u6216\u6269\u5C55 RenderContext"
937
+ );
938
+ }
939
+ return Reflect.get(target, prop, receiver);
940
+ }
941
+ });
942
+ }
943
+ function rethrowRenderError(source, err) {
944
+ if (err instanceof TemplateError) throw err;
945
+ const message = err instanceof Error ? err.message : String(err);
946
+ throw new TemplateError(
947
+ `render failed in ${source}: ${message}`,
948
+ "\u68C0\u67E5\u6A21\u677F\u8BED\u6CD5\u662F\u5426\u4E0E <% %> \u6807\u7B7E\u4E00\u81F4"
949
+ );
950
+ }
951
+ function renderString(template, context, source = "<string>") {
952
+ const eta = createEta();
953
+ const strict = toStrictContext(context);
954
+ try {
955
+ const out = eta.renderString(template, strict);
956
+ if (typeof out !== "string") {
957
+ throw new TemplateError(
958
+ `renderer returned non-string for ${source}`,
959
+ "\u8FD9\u662F\u4E00\u4E2A scaffolder \u5185\u90E8\u9519\u8BEF\uFF0C\u8BF7\u6C47\u62A5"
960
+ );
961
+ }
962
+ return out;
963
+ } catch (err) {
964
+ rethrowRenderError(source, err);
965
+ }
966
+ }
967
+ async function renderFile(absPath, context) {
968
+ let text;
969
+ try {
970
+ text = await fs3.readFile(absPath, "utf8");
971
+ } catch (err) {
972
+ throw new TemplateError(
973
+ `failed to read template ${absPath}: ${err.message}`,
974
+ "\u8BF7\u786E\u8BA4 fragment.yaml \u4E2D\u7684 from \u8DEF\u5F84\u6307\u5411\u771F\u5B9E\u6587\u4EF6"
975
+ );
976
+ }
977
+ return renderString(text, context, absPath);
978
+ }
979
+
980
+ // src/plan/builder.ts
981
+ async function buildFilePlan(input) {
982
+ const { fragments, context } = input;
983
+ const byPath = /* @__PURE__ */ new Map();
984
+ for (const fragment of fragments) {
985
+ for (const rule of fragment.manifest.files) {
986
+ const fromAbs = path3.join(fragment.rootDir, rule.from);
987
+ const targetPath = normalizePosixPath(rule.to);
988
+ let content;
989
+ if (rule.render) {
990
+ content = await renderFile(fromAbs, context);
991
+ } else {
992
+ try {
993
+ content = await fs4.readFile(fromAbs, "utf8");
994
+ } catch (e) {
995
+ throw new TemplateError(
996
+ `failed to read template source ${fromAbs}: ${e.message}`,
997
+ "\u8BF7\u786E\u8BA4 manifest.files[].from \u6307\u5411\u771F\u5B9E\u6587\u4EF6"
998
+ );
999
+ }
1000
+ }
1001
+ const candidate = {
1002
+ targetPath,
1003
+ content,
1004
+ contributedBy: fragment.manifest.name,
1005
+ ...rule.mode !== void 0 ? { mode: rule.mode } : {},
1006
+ priority: fragment.manifest.priority
1007
+ };
1008
+ const existing = byPath.get(targetPath);
1009
+ if (existing === void 0) {
1010
+ byPath.set(targetPath, candidate);
1011
+ continue;
1012
+ }
1013
+ if (candidate.priority > existing.priority) {
1014
+ byPath.set(targetPath, candidate);
1015
+ } else if (candidate.priority < existing.priority) {
1016
+ } else if (existing.content !== candidate.content) {
1017
+ throw new TemplateError(
1018
+ `conflict at ${targetPath}: fragments "${existing.contributedBy}" and "${candidate.contributedBy}" have equal priority ${candidate.priority} but contribute different content`,
1019
+ "\u8C03\u6574 fragment \u7684 priority\uFF0C\u6216\u7531\u4E00\u4E2A fragment \u72EC\u5360\u8BE5\u8DEF\u5F84"
1020
+ );
1021
+ }
1022
+ }
1023
+ }
1024
+ const files = [...byPath.values()].map(({ priority: _p, ...rest }) => rest).sort((a, b) => a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0);
1025
+ const directorySet = /* @__PURE__ */ new Set();
1026
+ for (const f of files) {
1027
+ let cur = path3.posix.dirname(f.targetPath);
1028
+ while (cur && cur !== "." && cur !== "/") {
1029
+ directorySet.add(cur);
1030
+ cur = path3.posix.dirname(cur);
1031
+ }
1032
+ }
1033
+ const directories = [...directorySet].sort();
1034
+ return { files, directories };
1035
+ }
1036
+ function normalizePosixPath(p) {
1037
+ return p.split(/[\\/]+/).filter(Boolean).join("/");
1038
+ }
1039
+
1040
+ // src/plan/syntax.ts
1041
+ import { parse as parseYaml4 } from "yaml";
1042
+ function validatePlanSyntax(plan) {
1043
+ for (const file of plan.files) {
1044
+ const ext = lowerExt(file.targetPath);
1045
+ if (ext === "json") {
1046
+ try {
1047
+ JSON.parse(file.content);
1048
+ } catch (e) {
1049
+ throw new TemplateError(
1050
+ `invalid JSON in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${e.message}`,
1051
+ "\u68C0\u67E5\u5BF9\u5E94\u6A21\u677F\u7684\u8BED\u6CD5"
1052
+ );
1053
+ }
1054
+ } else if (ext === "yaml" || ext === "yml") {
1055
+ try {
1056
+ parseYaml4(file.content);
1057
+ } catch (e) {
1058
+ throw new TemplateError(
1059
+ `invalid YAML in generated file ${file.targetPath} (from fragment ${file.contributedBy}): ${e.message}`,
1060
+ "\u68C0\u67E5\u5BF9\u5E94\u6A21\u677F\u7684\u7F29\u8FDB\u4E0E\u5F15\u53F7"
1061
+ );
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ function lowerExt(p) {
1067
+ const dot = p.lastIndexOf(".");
1068
+ if (dot < 0) return "";
1069
+ return p.slice(dot + 1).toLowerCase();
1070
+ }
1071
+
1072
+ // src/schema/metadata.ts
1073
+ import Ajv4 from "ajv";
1074
+ import addFormats2 from "ajv-formats";
1075
+ var METADATA_FILENAME = ".scaffolder.json";
1076
+ var SEMVER_PATTERN2 = "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?$";
1077
+ var FRAGMENT_NAME_PATTERN2 = "^[a-z0-9][a-z0-9-]{0,62}$";
1078
+ var METADATA_SCHEMA = {
1079
+ $schema: "http://json-schema.org/draft-07/schema#",
1080
+ $id: "https://cgsy.example.com/schemas/scaffolder/metadata.json",
1081
+ title: "ScaffolderMetadata",
1082
+ type: "object",
1083
+ additionalProperties: false,
1084
+ required: ["scaffolderVersion", "generatedAt", "options", "templateFragments"],
1085
+ properties: {
1086
+ scaffolderVersion: { type: "string", pattern: SEMVER_PATTERN2 },
1087
+ generatedAt: { type: "string", format: "date-time" },
1088
+ options: OPTIONS_SCHEMA,
1089
+ templateFragments: {
1090
+ type: "array",
1091
+ items: {
1092
+ type: "object",
1093
+ additionalProperties: false,
1094
+ required: ["name", "version"],
1095
+ properties: {
1096
+ name: { type: "string", pattern: FRAGMENT_NAME_PATTERN2 },
1097
+ version: { type: "string", pattern: SEMVER_PATTERN2 }
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ };
1103
+ var ajv4 = new Ajv4({ allErrors: true, strict: true });
1104
+ addFormats2(ajv4);
1105
+ var validateFn3 = ajv4.compile(METADATA_SCHEMA);
1106
+ var METADATA_TOP_ORDER = [
1107
+ "scaffolderVersion",
1108
+ "generatedAt",
1109
+ "options",
1110
+ "templateFragments"
1111
+ ];
1112
+ function canonicalOptions(options) {
1113
+ const out = {};
1114
+ for (const key of OPTIONS_FIELD_ORDER) {
1115
+ out[key] = options[key];
1116
+ }
1117
+ return out;
1118
+ }
1119
+ function canonicalFragmentRef(ref) {
1120
+ return { name: ref.name, version: ref.version };
1121
+ }
1122
+ function canonicalMetadata(metadata) {
1123
+ const out = {};
1124
+ for (const key of METADATA_TOP_ORDER) {
1125
+ if (key === "options") {
1126
+ out[key] = canonicalOptions(metadata.options);
1127
+ } else if (key === "templateFragments") {
1128
+ out[key] = metadata.templateFragments.map(canonicalFragmentRef);
1129
+ } else {
1130
+ out[key] = metadata[key];
1131
+ }
1132
+ }
1133
+ return out;
1134
+ }
1135
+ function serializeMetadata(metadata) {
1136
+ return JSON.stringify(canonicalMetadata(metadata), null, 2) + "\n";
1137
+ }
1138
+ function buildMetadata(params) {
1139
+ const now = params.now ?? /* @__PURE__ */ new Date();
1140
+ return {
1141
+ scaffolderVersion: params.scaffolderVersion,
1142
+ generatedAt: now.toISOString(),
1143
+ options: params.options,
1144
+ templateFragments: [...params.templateFragments]
1145
+ };
1146
+ }
1147
+
1148
+ // src/plan/metadata.ts
1149
+ function appendScaffolderMetadata(input) {
1150
+ const metadata = buildMetadata({
1151
+ scaffolderVersion: input.scaffolderVersion,
1152
+ options: input.options,
1153
+ templateFragments: input.templateFragments,
1154
+ ...input.now !== void 0 ? { now: input.now } : {}
1155
+ });
1156
+ const file = {
1157
+ targetPath: METADATA_FILENAME,
1158
+ content: serializeMetadata(metadata),
1159
+ contributedBy: "__scaffolder_metadata__"
1160
+ };
1161
+ const files = [...input.plan.files, file].sort(
1162
+ (a, b) => a.targetPath < b.targetPath ? -1 : a.targetPath > b.targetPath ? 1 : 0
1163
+ );
1164
+ return { files, directories: input.plan.directories };
1165
+ }
1166
+
1167
+ // src/writer/writer.ts
1168
+ import { promises as fs5 } from "node:fs";
1169
+ import * as path4 from "node:path";
1170
+ var FORCE_BACKUP_BYTE_LIMIT = 10 * 1024 * 1024;
1171
+ async function writePlan(plan, opts) {
1172
+ const absTarget = path4.resolve(opts.targetDirectory);
1173
+ const journal = { createdFiles: [], createdDirectories: [], overwrites: [] };
1174
+ const targetState = await inspectTarget(absTarget);
1175
+ if (targetState.exists && targetState.nonEmpty && !opts.force) {
1176
+ throw new UserInputError(
1177
+ `target directory is not empty: ${absTarget}`,
1178
+ "\u4F7F\u7528\u4E0D\u540C\u8DEF\u5F84\uFF0C\u6216\u52A0 --force\uFF08\u5C0F\u5FC3\uFF1A\u4F1A\u8986\u76D6\u65E2\u6709\u6587\u4EF6\uFF09"
1179
+ );
1180
+ }
1181
+ if (opts.force && targetState.exists) {
1182
+ await preflightBackupCapacity(absTarget, plan);
1183
+ }
1184
+ try {
1185
+ if (!targetState.exists) {
1186
+ await fs5.mkdir(absTarget, { recursive: true });
1187
+ journal.createdDirectories.push(absTarget);
1188
+ }
1189
+ for (const rel of plan.directories) {
1190
+ const abs = path4.join(absTarget, rel);
1191
+ try {
1192
+ await fs5.mkdir(abs);
1193
+ journal.createdDirectories.push(abs);
1194
+ } catch (err) {
1195
+ if (err.code === "EEXIST") {
1196
+ continue;
1197
+ }
1198
+ throw err;
1199
+ }
1200
+ }
1201
+ for (const file of plan.files) {
1202
+ const abs = path4.join(absTarget, file.targetPath);
1203
+ await fs5.mkdir(path4.dirname(abs), { recursive: true });
1204
+ if (opts.force) {
1205
+ try {
1206
+ const prev = await fs5.readFile(abs, "utf8");
1207
+ journal.overwrites.push({ absPath: abs, originalContent: prev });
1208
+ } catch {
1209
+ }
1210
+ }
1211
+ await fs5.writeFile(abs, file.content, "utf8");
1212
+ if (file.mode !== void 0) {
1213
+ await fs5.chmod(abs, file.mode);
1214
+ }
1215
+ journal.createdFiles.push(abs);
1216
+ }
1217
+ return {
1218
+ createdFiles: journal.createdFiles,
1219
+ createdDirectories: journal.createdDirectories
1220
+ };
1221
+ } catch (err) {
1222
+ await rollback(journal);
1223
+ if (err instanceof UserInputError) throw err;
1224
+ const message = err instanceof Error ? err.message : String(err);
1225
+ throw new FilesystemError(
1226
+ `file writer failed; changes rolled back: ${message}`,
1227
+ "\u68C0\u67E5\u6743\u9650\u4E0E\u78C1\u76D8\u7A7A\u95F4\uFF0C\u5FC5\u8981\u65F6\u5220\u9664\u6B8B\u7559\u76EE\u5F55\u540E\u91CD\u8BD5"
1228
+ );
1229
+ }
1230
+ }
1231
+ async function inspectTarget(absTarget) {
1232
+ try {
1233
+ const entries = await fs5.readdir(absTarget);
1234
+ return { exists: true, nonEmpty: entries.length > 0 };
1235
+ } catch (err) {
1236
+ const code = err.code;
1237
+ if (code === "ENOENT") return { exists: false, nonEmpty: false };
1238
+ throw new FilesystemError(
1239
+ `cannot inspect target directory ${absTarget}: ${err.message}`,
1240
+ "\u68C0\u67E5\u8DEF\u5F84\u7684\u8BBF\u95EE\u6743\u9650"
1241
+ );
1242
+ }
1243
+ }
1244
+ async function preflightBackupCapacity(absTarget, plan) {
1245
+ for (const file of plan.files) {
1246
+ const abs = path4.join(absTarget, file.targetPath);
1247
+ try {
1248
+ const stat = await fs5.stat(abs);
1249
+ if (stat.isFile() && stat.size > FORCE_BACKUP_BYTE_LIMIT) {
1250
+ throw new UserInputError(
1251
+ `--force would overwrite ${abs} which is larger than the 10 MB rollback limit`,
1252
+ "\u624B\u52A8\u5907\u4EFD / \u5220\u9664\u8BE5\u6587\u4EF6\u540E\u91CD\u8BD5"
1253
+ );
1254
+ }
1255
+ } catch (err) {
1256
+ if (err.code === "ENOENT") continue;
1257
+ if (err instanceof UserInputError) throw err;
1258
+ throw new UserInputError(
1259
+ `cannot inspect existing file ${abs}: ${err.message}`,
1260
+ "\u8BF7\u786E\u8BA4\u76EE\u6807\u76EE\u5F55\u4E0B\u6240\u6709\u6587\u4EF6\u53EF\u8BFB"
1261
+ );
1262
+ }
1263
+ }
1264
+ }
1265
+ async function rollback(journal) {
1266
+ for (let i = journal.overwrites.length - 1; i >= 0; i -= 1) {
1267
+ const { absPath, originalContent } = journal.overwrites[i];
1268
+ try {
1269
+ await fs5.writeFile(absPath, originalContent, "utf8");
1270
+ } catch {
1271
+ }
1272
+ }
1273
+ const overwriteSet = new Set(journal.overwrites.map((o) => o.absPath));
1274
+ for (let i = journal.createdFiles.length - 1; i >= 0; i -= 1) {
1275
+ const p = journal.createdFiles[i];
1276
+ if (overwriteSet.has(p)) continue;
1277
+ try {
1278
+ await fs5.rm(p, { force: true });
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ for (let i = journal.createdDirectories.length - 1; i >= 0; i -= 1) {
1283
+ const dir = journal.createdDirectories[i];
1284
+ try {
1285
+ await fs5.rmdir(dir);
1286
+ } catch {
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ // src/reporter.ts
1292
+ var Reporter = class {
1293
+ constructor(io, verbosity) {
1294
+ this.io = io;
1295
+ this.verbosity = verbosity;
1296
+ }
1297
+ stage(label) {
1298
+ if (this.verbosity === "quiet") return;
1299
+ this.io.stdout(`
1300
+ \u2500\u2500\u2500 ${label} \u2500\u2500\u2500`);
1301
+ }
1302
+ info(line) {
1303
+ if (this.verbosity === "quiet") return;
1304
+ this.io.stdout(line);
1305
+ }
1306
+ verbose(line) {
1307
+ if (this.verbosity !== "verbose") return;
1308
+ this.io.stdout(line);
1309
+ }
1310
+ options(options) {
1311
+ if (this.verbosity === "quiet") return;
1312
+ this.io.stdout(` projectName: ${options.projectName}`);
1313
+ this.io.stdout(` backend: ${options.backend}`);
1314
+ this.io.stdout(` frontend: ${options.frontend}`);
1315
+ this.io.stdout(` mobile: ${options.mobile}`);
1316
+ this.io.stdout(` miniapp: ${options.miniapp}`);
1317
+ this.io.stdout(` agent: ${options.agent}`);
1318
+ this.io.stdout(` deploy: ${options.deploy}`);
1319
+ this.io.stdout(` contract: ${options.contract}`);
1320
+ this.io.stdout(` ai: ${options.ai}`);
1321
+ this.io.stdout(` license: ${options.license}`);
1322
+ this.io.stdout(` gitLfs: ${String(options.gitLfs)}`);
1323
+ this.io.stdout(` integrations: ${String(options.integrations)}`);
1324
+ this.io.stdout(` ci: ${options.ci}`);
1325
+ this.io.stdout(` roles: ${options.roles.length > 0 ? options.roles.join(",") : "<none>"}`);
1326
+ }
1327
+ dryRunReport(plan) {
1328
+ this.io.stdout("\n[dry-run] would create the following files:");
1329
+ for (const f of plan.files) {
1330
+ const bytes = Buffer.byteLength(f.content, "utf8");
1331
+ this.io.stdout(` ${f.targetPath} (${bytes} bytes, from ${f.contributedBy})`);
1332
+ }
1333
+ this.io.stdout(`
1334
+ [dry-run] total: ${plan.files.length} file(s)`);
1335
+ }
1336
+ writeProgress(file) {
1337
+ if (this.verbosity !== "verbose") return;
1338
+ const bytes = Buffer.byteLength(file.content, "utf8");
1339
+ this.io.stdout(` wrote ${file.targetPath} (${bytes}B, from ${file.contributedBy})`);
1340
+ }
1341
+ summary(kind, fileCount, targetDir) {
1342
+ if (kind === "created") {
1343
+ this.io.stdout(`\u2713 created ${fileCount} file(s) at ${targetDir}`);
1344
+ } else {
1345
+ this.io.stdout(`\u2713 dry-run: ${fileCount} file(s) would be created at ${targetDir}`);
1346
+ }
1347
+ }
1348
+ };
1349
+ function verbosityFromFlags(flags) {
1350
+ if (flags.quiet && flags.verbose) return "verbose";
1351
+ if (flags.quiet) return "quiet";
1352
+ if (flags.verbose) return "verbose";
1353
+ return "default";
1354
+ }
1355
+
1356
+ // src/orchestrator.ts
1357
+ async function runCreate(input) {
1358
+ const reporter = new Reporter(
1359
+ { stdout: input.io.stdout, stderr: input.io.stderr },
1360
+ verbosityFromFlags(input.flags)
1361
+ );
1362
+ reporter.stage("options");
1363
+ const options = await resolveOptions(input);
1364
+ reporter.options(options);
1365
+ reporter.stage("templates");
1366
+ const { fragments } = await loadFragments(input.io.templatesRoot);
1367
+ const selected = selectFragments(options, fragments);
1368
+ reporter.info(`selected ${selected.length} fragment(s)`);
1369
+ for (const f of selected) {
1370
+ reporter.verbose(` ${f.manifest.name}@${f.manifest.version} (priority ${f.manifest.priority})`);
1371
+ }
1372
+ reporter.stage("plan");
1373
+ const context = buildRenderContext({
1374
+ options,
1375
+ scaffolderVersion: SCAFFOLDER_VERSION,
1376
+ ...input.io.now !== void 0 ? { now: input.io.now } : {}
1377
+ });
1378
+ let plan = await buildFilePlan({ fragments: selected, context });
1379
+ const fragmentRefs = selected.map((f) => ({
1380
+ name: f.manifest.name,
1381
+ version: f.manifest.version
1382
+ }));
1383
+ plan = appendScaffolderMetadata({
1384
+ plan,
1385
+ scaffolderVersion: SCAFFOLDER_VERSION,
1386
+ options,
1387
+ templateFragments: fragmentRefs,
1388
+ ...input.io.now !== void 0 ? { now: input.io.now } : {}
1389
+ });
1390
+ validatePlanSyntax(plan);
1391
+ reporter.info(`planned ${plan.files.length} file(s) across ${plan.directories.length} directory`);
1392
+ const cwd = input.io.cwd ?? process.cwd();
1393
+ const targetDirectory = path5.resolve(cwd, options.projectName);
1394
+ if (input.flags.dryRun) {
1395
+ reporter.stage("dry-run");
1396
+ reporter.dryRunReport(plan);
1397
+ reporter.summary("dry-run", plan.files.length, targetDirectory);
1398
+ return { options, targetDirectory, fileCount: plan.files.length, dryRun: true };
1399
+ }
1400
+ reporter.stage("write");
1401
+ const result = await writePlan(plan, { targetDirectory, force: input.flags.force ?? false });
1402
+ for (const f of plan.files) {
1403
+ reporter.writeProgress(f);
1404
+ }
1405
+ reporter.info(`wrote ${result.createdFiles.length} file(s)`);
1406
+ reporter.summary("created", result.createdFiles.length, targetDirectory);
1407
+ return {
1408
+ options,
1409
+ targetDirectory,
1410
+ fileCount: result.createdFiles.length,
1411
+ dryRun: false
1412
+ };
1413
+ }
1414
+ async function resolveOptions(input) {
1415
+ const { projectName, flags, io } = input;
1416
+ let merged = {};
1417
+ if (flags.config !== void 0) {
1418
+ merged = { ...merged, ...await loadConfigFile(flags.config) };
1419
+ }
1420
+ merged = {
1421
+ ...merged,
1422
+ projectName,
1423
+ ...flags.ci !== void 0 ? { ci: flags.ci } : {},
1424
+ ...flags.roles !== void 0 ? { roles: flags.roles } : {}
1425
+ };
1426
+ if (flags.yes) {
1427
+ const full = { ...buildDefaultOptions(projectName), ...merged };
1428
+ return assertOptions(full);
1429
+ }
1430
+ if (!io.isTTY) {
1431
+ throw new UserInputError(
1432
+ "no TTY available and neither --yes nor a complete --config was provided",
1433
+ "\u5728\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B\u8BF7\u4F7F\u7528 --yes \u6216\u63D0\u4F9B\u5B8C\u6574\u7684 --config"
1434
+ );
1435
+ }
1436
+ const prompter = io.prompter ?? new InquirerPrompter();
1437
+ const prompted = await promptForOptions({
1438
+ prompter,
1439
+ prefilled: merged,
1440
+ isTTY: io.isTTY,
1441
+ report: (axis, value, usedDefault) => {
1442
+ if (!flags.quiet && usedDefault) {
1443
+ io.stdout(` ${axis}: ${value} (default)`);
1444
+ } else if (flags.verbose) {
1445
+ io.stdout(` ${axis}: ${value}`);
1446
+ }
1447
+ }
1448
+ });
1449
+ return assertOptions(prompted);
1450
+ }
1451
+
1452
+ // src/index.ts
1453
+ async function handleCreate(input) {
1454
+ await runCreate({
1455
+ projectName: input.projectName,
1456
+ flags: input.flags,
1457
+ io: {
1458
+ stdout: (line) => {
1459
+ console.log(line);
1460
+ },
1461
+ stderr: (line) => {
1462
+ console.error(line);
1463
+ },
1464
+ isTTY: Boolean(process.stdout.isTTY)
1465
+ }
1466
+ });
1467
+ }
1468
+ var exitCode = await run({
1469
+ argv: process.argv,
1470
+ stdout: (line) => {
1471
+ console.log(line);
1472
+ },
1473
+ stderr: (line) => {
1474
+ console.error(line);
1475
+ },
1476
+ onCreate: handleCreate
1477
+ });
1478
+ process.exit(exitCode);
1479
+ //# sourceMappingURL=index.js.map