create-m5kdev 0.32.8 → 0.34.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 (152) hide show
  1. package/dist/src/catalog.cjs +76 -11
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +13 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs +13 -7
  7. package/dist/src/doctor.cjs.map +1 -1
  8. package/dist/src/fs.cjs +10 -5
  9. package/dist/src/fs.cjs.map +1 -1
  10. package/dist/src/prompts.cjs +32 -0
  11. package/dist/src/prompts.cjs.map +1 -1
  12. package/dist/src/prompts.d.cts +7 -1
  13. package/dist/src/template.cjs +23 -4
  14. package/dist/src/template.cjs.map +1 -1
  15. package/dist/src/template.d.cts +17 -3
  16. package/dist/src/types.d.cts +11 -4
  17. package/package.json +4 -1
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.dockerignore.tpl +31 -0
  23. package/templates/minimal-app/.gitignore.tpl +1 -0
  24. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  25. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  26. package/templates/minimal-app/README.md.tpl +50 -19
  27. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  28. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  29. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  30. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  31. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  32. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  33. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  34. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  35. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  36. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  37. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  38. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  39. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  40. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  41. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  42. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  43. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  44. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  45. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  46. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  47. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  48. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  49. package/templates/minimal-app/apps/expo/package.json.tpl +59 -59
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  75. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  76. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  77. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  78. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  79. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  80. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  81. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  82. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  83. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  84. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  85. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  86. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +3 -0
  87. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  88. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  89. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  90. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -0
  91. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  92. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +15 -1
  93. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  94. package/templates/minimal-app/apps/server/package.json.tpl +57 -56
  95. package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
  96. package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
  97. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  98. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  99. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  100. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -30
  103. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  104. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  105. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -115
  106. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  107. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  108. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  109. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -1
  110. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  111. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  112. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  113. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  114. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  115. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  116. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  117. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  118. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  119. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  120. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  121. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  122. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  123. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  124. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  125. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  126. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  127. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  128. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  129. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  130. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  131. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  132. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  133. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  134. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  135. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  136. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  137. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  138. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  139. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  140. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  141. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  142. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  143. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  144. package/templates/minimal-app/biome.json.tpl +17 -12
  145. package/templates/minimal-app/package.json.tpl +9 -5
  146. package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
  147. package/templates/minimal-app/template.manifest.json +92 -2
  148. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  149. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  150. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  151. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
  152. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
@@ -12,6 +12,8 @@ const DEPENDENCY_FIELDS = [
12
12
  "peerDependencies",
13
13
  "optionalDependencies"
14
14
  ];
15
+ const MANAGED_CATALOG_NAME = "m5kdev";
16
+ const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;
15
17
  function walkPackageJsonFiles(directory, out = []) {
16
18
  for (const entry of node_fs.default.readdirSync(directory, { withFileTypes: true })) {
17
19
  if (entry.name === "node_modules" || entry.name === ".git") continue;
@@ -24,6 +26,9 @@ function walkPackageJsonFiles(directory, out = []) {
24
26
  function readJson(filePath) {
25
27
  return JSON.parse(node_fs.default.readFileSync(filePath, "utf8"));
26
28
  }
29
+ function asCatalog(value) {
30
+ return Object.fromEntries(Object.entries(value ?? {}).map(([name, version]) => [name, String(version)]));
31
+ }
27
32
  function collectConsumerDependencyNamesFromManifests(manifests) {
28
33
  const names = /* @__PURE__ */ new Set();
29
34
  for (const manifest of manifests) for (const field of DEPENDENCY_FIELDS) {
@@ -35,6 +40,35 @@ function collectConsumerDependencyNamesFromManifests(manifests) {
35
40
  function collectConsumerDependencyNames(packageFiles) {
36
41
  return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));
37
42
  }
43
+ function collectCatalogProtocolDependenciesFromManifests(manifests) {
44
+ const dependencies = [];
45
+ for (const manifest of manifests) for (const field of DEPENDENCY_FIELDS) for (const [name, specifier] of Object.entries(manifest[field] ?? {})) if (specifier.startsWith("catalog:")) dependencies.push({
46
+ name,
47
+ specifier
48
+ });
49
+ return dependencies;
50
+ }
51
+ function collectCatalogProtocolDependencies(packageFiles) {
52
+ return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));
53
+ }
54
+ /** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
55
+ function collectOptionalBackendModulePins(repoRoot) {
56
+ const packagesDir = node_path.default.join(repoRoot, "packages");
57
+ const pins = {};
58
+ if (!node_fs.default.existsSync(packagesDir)) return pins;
59
+ for (const entry of node_fs.default.readdirSync(packagesDir, { withFileTypes: true })) {
60
+ if (!entry.isDirectory() || !entry.name.startsWith("module-")) continue;
61
+ const manifestPath = node_path.default.join(packagesDir, entry.name, "package.json");
62
+ if (!node_fs.default.existsSync(manifestPath)) continue;
63
+ const manifest = readJson(manifestPath);
64
+ const name = manifest.name;
65
+ const version = manifest.version;
66
+ if (typeof name !== "string" || typeof version !== "string") continue;
67
+ if (!name.startsWith("@m5kdev/module-")) continue;
68
+ pins[name] = version;
69
+ }
70
+ return pins;
71
+ }
38
72
  function buildConsumerCatalog(options) {
39
73
  const workspacePath = node_path.default.join(options.repoRoot, "pnpm-workspace.yaml");
40
74
  const sourceCatalog = (0, yaml.parse)(node_fs.default.readFileSync(workspacePath, "utf8")).catalog ?? {};
@@ -64,17 +98,29 @@ function buildConsumerCatalog(options) {
64
98
  }
65
99
  catalog[name] = String(version);
66
100
  }
101
+ Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));
67
102
  if (missing.length > 0) throw new Error(`Missing consumer catalog versions for: ${missing.join(", ")}`);
68
- return catalog;
103
+ return sortedCatalog(catalog);
104
+ }
105
+ function sortedCatalog(catalog) {
106
+ return Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
69
107
  }
70
108
  function renderConsumerWorkspace(source, catalog) {
71
109
  const workspace = (0, yaml.parse)(source);
72
- workspace.catalog = Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
110
+ const catalogs = workspace.catalogs && typeof workspace.catalogs === "object" ? { ...workspace.catalogs } : {};
111
+ catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);
112
+ workspace.catalogs = catalogs;
113
+ if (workspace.catalog === void 0) workspace.catalog = {};
73
114
  return (0, yaml.stringify)(workspace, { lineWidth: 0 });
74
115
  }
116
+ function readDefaultCatalog(source) {
117
+ return asCatalog((0, yaml.parse)(source).catalog);
118
+ }
75
119
  function readCatalog(source) {
76
120
  const workspace = (0, yaml.parse)(source);
77
- return Object.fromEntries(Object.entries(workspace.catalog ?? {}).map(([name, version]) => [name, String(version)]));
121
+ const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];
122
+ if (named !== void 0) return asCatalog(named);
123
+ return asCatalog(workspace.catalog);
78
124
  }
79
125
  function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
80
126
  const expected = new Set(expectedNames);
@@ -87,16 +133,24 @@ function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
87
133
  }
88
134
  function mergeManagedCatalog(options) {
89
135
  const document = (0, yaml.parseDocument)(options.source);
90
- const local = readCatalog(options.source);
136
+ const workspace = (0, yaml.parse)(options.source);
137
+ const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== void 0;
138
+ const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);
139
+ const fallback = asCatalog(workspace.catalog);
91
140
  const conflicts = [];
92
141
  const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);
142
+ const managedPath = ["catalogs", MANAGED_CATALOG_NAME];
143
+ if (document.getIn([...managedPath]) === void 0) if (document.get("catalogs") === void 0) document.set("catalogs", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));
144
+ else document.setIn([...managedPath], document.createNode({}));
93
145
  for (const name of [...names].sort((left, right) => left.localeCompare(right))) {
94
146
  const base = options.base[name];
95
- const current = local[name];
147
+ const current = (namedPresent ? named[name] : void 0) ?? fallback[name];
96
148
  const target = options.target[name];
97
149
  if (base === void 0) {
98
- if (current === void 0) document.setIn(["catalog", name], target);
99
- else if (current !== target) conflicts.push({
150
+ if (current === void 0) {
151
+ document.setIn([...managedPath, name], target);
152
+ document.deleteIn(["catalog", name]);
153
+ } else if (current !== target) conflicts.push({
100
154
  name,
101
155
  local: current,
102
156
  target
@@ -104,16 +158,20 @@ function mergeManagedCatalog(options) {
104
158
  continue;
105
159
  }
106
160
  if (target === void 0) {
107
- if (current === base) document.deleteIn(["catalog", name]);
108
- else if (current !== void 0) conflicts.push({
161
+ if (current === base) {
162
+ document.deleteIn([...managedPath, name]);
163
+ document.deleteIn(["catalog", name]);
164
+ } else if (current !== void 0) conflicts.push({
109
165
  name,
110
166
  base,
111
167
  local: current
112
168
  });
113
169
  continue;
114
170
  }
115
- if (current === base) document.setIn(["catalog", name], target);
116
- else if (current !== target && target !== base) conflicts.push({
171
+ if (current === base) {
172
+ document.setIn([...managedPath, name], target);
173
+ document.deleteIn(["catalog", name]);
174
+ } else if (current !== target && target !== base) conflicts.push({
117
175
  name,
118
176
  base,
119
177
  local: current,
@@ -128,13 +186,20 @@ function mergeManagedCatalog(options) {
128
186
  };
129
187
  }
130
188
  //#endregion
189
+ exports.MANAGED_CATALOG_NAME = MANAGED_CATALOG_NAME;
190
+ exports.MANAGED_CATALOG_SPECIFIER = MANAGED_CATALOG_SPECIFIER;
131
191
  exports.assertCatalogKeys = assertCatalogKeys;
132
192
  exports.buildConsumerCatalog = buildConsumerCatalog;
193
+ exports.collectCatalogProtocolDependencies = collectCatalogProtocolDependencies;
194
+ exports.collectCatalogProtocolDependenciesFromManifests = collectCatalogProtocolDependenciesFromManifests;
133
195
  exports.collectConsumerDependencyNames = collectConsumerDependencyNames;
134
196
  exports.collectConsumerDependencyNamesFromManifests = collectConsumerDependencyNamesFromManifests;
197
+ exports.collectOptionalBackendModulePins = collectOptionalBackendModulePins;
135
198
  exports.mergeManagedCatalog = mergeManagedCatalog;
136
199
  exports.readCatalog = readCatalog;
200
+ exports.readDefaultCatalog = readDefaultCatalog;
137
201
  exports.renderConsumerWorkspace = renderConsumerWorkspace;
202
+ exports.sortedCatalog = sortedCatalog;
138
203
  exports.walkPackageJsonFiles = walkPackageJsonFiles;
139
204
 
140
205
  //# sourceMappingURL=catalog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return catalog;\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n workspace.catalog = Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as { catalog?: Record<string, string | number> };\r\n return Object.fromEntries(\r\n Object.entries(workspace.catalog ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const local = readCatalog(options.source);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = local[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) document.setIn([\"catalog\", name], target);\r\n else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) document.deleteIn([\"catalog\", name]);\r\n else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) document.setIn([\"catalog\", name], target);\r\n else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport const MANAGED_CATALOG_NAME = \"m5kdev\";\r\nexport const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\ninterface WorkspaceCatalogFile {\r\n catalog?: Record<string, string | number>;\r\n catalogs?: Record<string, Record<string, string | number> | undefined>;\r\n}\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nfunction asCatalog(value: Record<string, string | number> | undefined): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(value ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport interface CatalogProtocolDependency {\r\n name: string;\r\n specifier: string;\r\n}\r\n\r\nexport function collectCatalogProtocolDependenciesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): CatalogProtocolDependency[] {\r\n const dependencies: CatalogProtocolDependency[] = [];\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n for (const [name, specifier] of Object.entries(\r\n (manifest[field] as Record<string, string> | undefined) ?? {}\r\n )) {\r\n if (specifier.startsWith(\"catalog:\")) dependencies.push({ name, specifier });\r\n }\r\n }\r\n }\r\n return dependencies;\r\n}\r\n\r\nexport function collectCatalogProtocolDependencies(\r\n packageFiles: readonly string[]\r\n): CatalogProtocolDependency[] {\r\n return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\n/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */\r\nexport function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog {\r\n const packagesDir = path.join(repoRoot, \"packages\");\r\n const pins: ConsumerCatalog = {};\r\n if (!fs.existsSync(packagesDir)) return pins;\r\n\r\n for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {\r\n if (!entry.isDirectory() || !entry.name.startsWith(\"module-\")) continue;\r\n const manifestPath = path.join(packagesDir, entry.name, \"package.json\");\r\n if (!fs.existsSync(manifestPath)) continue;\r\n const manifest = readJson(manifestPath);\r\n const name = manifest.name;\r\n const version = manifest.version;\r\n if (typeof name !== \"string\" || typeof version !== \"string\") continue;\r\n if (!name.startsWith(\"@m5kdev/module-\")) continue;\r\n pins[name] = version;\r\n }\r\n\r\n return pins;\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return sortedCatalog(catalog);\r\n}\r\n\r\nexport function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n const catalogs =\r\n workspace.catalogs && typeof workspace.catalogs === \"object\"\r\n ? { ...(workspace.catalogs as Record<string, unknown>) }\r\n : {};\r\n catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);\r\n workspace.catalogs = catalogs;\r\n if (workspace.catalog === undefined) workspace.catalog = {};\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readDefaultCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];\r\n if (named !== undefined) return asCatalog(named);\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const workspace = parse(options.source) as WorkspaceCatalogFile;\r\n const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== undefined;\r\n const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);\r\n const fallback = asCatalog(workspace.catalog);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n const managedPath = [\"catalogs\", MANAGED_CATALOG_NAME] as const;\r\n\r\n if (document.getIn([...managedPath]) === undefined) {\r\n if (document.get(\"catalogs\") === undefined) {\r\n document.set(\"catalogs\", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));\r\n } else {\r\n document.setIn([...managedPath], document.createNode({}));\r\n }\r\n }\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = (namedPresent ? named[name] : undefined) ?? fallback[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) {\r\n document.deleteIn([...managedPath, name]);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
@@ -1,14 +1,26 @@
1
1
  //#region src/catalog.d.ts
2
+ declare const MANAGED_CATALOG_NAME = "m5kdev";
3
+ declare const MANAGED_CATALOG_SPECIFIER = "catalog:m5kdev";
2
4
  type ConsumerCatalog = Record<string, string>;
3
5
  declare function walkPackageJsonFiles(directory: string, out?: string[]): string[];
4
6
  declare function collectConsumerDependencyNamesFromManifests(manifests: readonly Record<string, unknown>[]): string[];
5
7
  declare function collectConsumerDependencyNames(packageFiles: readonly string[]): string[];
8
+ interface CatalogProtocolDependency {
9
+ name: string;
10
+ specifier: string;
11
+ }
12
+ declare function collectCatalogProtocolDependenciesFromManifests(manifests: readonly Record<string, unknown>[]): CatalogProtocolDependency[];
13
+ declare function collectCatalogProtocolDependencies(packageFiles: readonly string[]): CatalogProtocolDependency[];
14
+ /** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
15
+ declare function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog;
6
16
  declare function buildConsumerCatalog(options: {
7
17
  repoRoot: string;
8
18
  starterDirectory: string;
9
19
  rootTemplatesDirectory: string;
10
20
  }): ConsumerCatalog;
21
+ declare function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog;
11
22
  declare function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string;
23
+ declare function readDefaultCatalog(source: string): ConsumerCatalog;
12
24
  declare function readCatalog(source: string): ConsumerCatalog;
13
25
  declare function assertCatalogKeys(catalog: ConsumerCatalog, expectedNames: readonly string[], label?: string): void;
14
26
  interface CatalogMergeConflict {
@@ -27,5 +39,5 @@ declare function mergeManagedCatalog(options: {
27
39
  conflicts: CatalogMergeConflict[];
28
40
  };
29
41
  //#endregion
30
- export { CatalogMergeConflict, ConsumerCatalog, assertCatalogKeys, buildConsumerCatalog, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, mergeManagedCatalog, readCatalog, renderConsumerWorkspace, walkPackageJsonFiles };
42
+ export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, collectOptionalBackendModulePins, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
31
43
  //# sourceMappingURL=catalog.d.cts.map
@@ -4,8 +4,8 @@ const require_src_constants = require("./constants.cjs");
4
4
  const require_src_strings = require("./strings.cjs");
5
5
  const require_src_fs = require("./fs.cjs");
6
6
  const require_src_paths = require("./paths.cjs");
7
- const require_src_prompts = require("./prompts.cjs");
8
7
  const require_src_template = require("./template.cjs");
8
+ const require_src_prompts = require("./prompts.cjs");
9
9
  const require_src_state = require("./state.cjs");
10
10
  let node_path = require("node:path");
11
11
  node_path = require_runtime.__toESM(node_path);
@@ -26,7 +26,11 @@ async function scaffoldProject(initialOptions) {
26
26
  packageScope: require_src_strings.derivePackageScope(appSlug),
27
27
  betterAuthSecret: require_src_strings.createBetterAuthSecret()
28
28
  };
29
- const enabledFeatures = require_src_template.getEnabledFeatures(options.platform ?? "web", Boolean(options.testHarness));
29
+ const enabledFeatures = require_src_template.getEnabledFeatures({
30
+ platform: options.platform ?? "web",
31
+ testHarness: Boolean(options.testHarness),
32
+ modules: options.modules ?? []
33
+ });
30
34
  const manifest = require_src_template.loadTemplateManifest(templateDirectory);
31
35
  const excludePrefixes = require_src_template.getExcludedFeaturePaths(manifest, enabledFeatures);
32
36
  const createdTargetDirectory = await require_src_fs.ensureDirectoryState(targetDirectory, options.force);
@@ -1 +1 @@
1
- {"version":3,"file":"create.cjs","names":["resolveCreateCommandOptions","slugifyAppName","path","getTemplateRoot","derivePackageScope","createBetterAuthSecret","getEnabledFeatures","loadTemplateManifest","getExcludedFeaturePaths","ensureDirectoryState","copyTemplateDirectory","collectTemplateFiles","writeManagedState","createManagedState","getCliVersion","fs","runGitInit","runInstall","TEMPLATE_NAME"],"sources":["../../src/create.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport {\r\n collectTemplateFiles,\r\n copyTemplateDirectory,\r\n ensureDirectoryState,\r\n runGitInit,\r\n runInstall,\r\n} from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { resolveCreateCommandOptions } from \"./prompts\";\r\nimport { createManagedState, getCliVersion, writeManagedState } from \"./state\";\r\nimport { createBetterAuthSecret, derivePackageScope, slugifyAppName } from \"./strings\";\r\nimport { getEnabledFeatures, getExcludedFeaturePaths, loadTemplateManifest } from \"./template\";\r\nimport type { CreateCommandOptions, TemplateContext } from \"./types\";\r\n\r\nexport interface ScaffoldResult {\r\n context: TemplateContext;\r\n targetDirectory: string;\r\n templateName: string;\r\n}\r\n\r\nexport async function scaffoldProject(\r\n initialOptions: CreateCommandOptions\r\n): Promise<ScaffoldResult> {\r\n const options = await resolveCreateCommandOptions(initialOptions);\r\n const { appName, appDescription, targetDirectory: targetDirectoryOption } = options;\r\n if (!appName || !appDescription || !targetDirectoryOption) {\r\n throw new Error(\"Unable to resolve the required create options.\");\r\n }\r\n const appSlug = slugifyAppName(appName);\r\n const targetDirectory = path.resolve(process.cwd(), targetDirectoryOption);\r\n const templateDirectory = getTemplateRoot();\r\n\r\n const context: TemplateContext = {\r\n appName,\r\n appDescription,\r\n appSlug,\r\n packageScope: derivePackageScope(appSlug),\r\n betterAuthSecret: createBetterAuthSecret(),\r\n };\r\n\r\n const enabledFeatures = getEnabledFeatures(\r\n options.platform ?? \"web\",\r\n Boolean(options.testHarness)\r\n );\r\n const manifest = loadTemplateManifest(templateDirectory);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n\r\n const createdTargetDirectory = await ensureDirectoryState(targetDirectory, options.force);\r\n try {\r\n await copyTemplateDirectory(templateDirectory, targetDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n const renderedFiles = await collectTemplateFiles(templateDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n await writeManagedState(\r\n targetDirectory,\r\n createManagedState({\r\n templateVersion: getCliVersion(),\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n })\r\n );\r\n } catch (error) {\r\n if (createdTargetDirectory) {\r\n await fs.rm(targetDirectory, { recursive: true, force: true });\r\n }\r\n throw error;\r\n }\r\n\r\n if (!options.skipGit) {\r\n await runGitInit(targetDirectory);\r\n }\r\n\r\n if (!options.skipInstall) {\r\n await runInstall(targetDirectory);\r\n }\r\n\r\n return {\r\n context,\r\n targetDirectory,\r\n templateName: TEMPLATE_NAME,\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAsB,gBACpB,gBACyB;CACzB,MAAM,UAAU,MAAMA,oBAAAA,4BAA4B,eAAe;CACjE,MAAM,EAAE,SAAS,gBAAgB,iBAAiB,0BAA0B;AAC5E,KAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,sBAClC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,UAAUC,oBAAAA,eAAe,QAAQ;CACvC,MAAM,kBAAkBC,UAAAA,QAAK,QAAQ,QAAQ,KAAK,EAAE,sBAAsB;CAC1E,MAAM,oBAAoBC,kBAAAA,iBAAiB;CAE3C,MAAM,UAA2B;EAC/B;EACA;EACA;EACA,cAAcC,oBAAAA,mBAAmB,QAAQ;EACzC,kBAAkBC,oBAAAA,wBAAwB;EAC3C;CAED,MAAM,kBAAkBC,qBAAAA,mBACtB,QAAQ,YAAY,OACpB,QAAQ,QAAQ,YAAY,CAC7B;CACD,MAAM,WAAWC,qBAAAA,qBAAqB,kBAAkB;CACxD,MAAM,kBAAkBC,qBAAAA,wBAAwB,UAAU,gBAAgB;CAE1E,MAAM,yBAAyB,MAAMC,eAAAA,qBAAqB,iBAAiB,QAAQ,MAAM;AACzF,KAAI;AACF,QAAMC,eAAAA,sBAAsB,mBAAmB,iBAAiB,SAAS;GACvE;GACA;GACD,CAAC;EACF,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,mBAAmB,SAAS;GAC3E;GACA;GACD,CAAC;AACF,QAAMC,kBAAAA,kBACJ,iBACAC,kBAAAA,mBAAmB;GACjB,iBAAiBC,kBAAAA,eAAe;GAChC;GACA;GACA;GACA;GACD,CAAC,CACH;UACM,OAAO;AACd,MAAI,uBACF,OAAMC,iBAAAA,QAAG,GAAG,iBAAiB;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAEhE,QAAM;;AAGR,KAAI,CAAC,QAAQ,QACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,KAAI,CAAC,QAAQ,YACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,QAAO;EACL;EACA;EACA,cAAcC,sBAAAA;EACf"}
1
+ {"version":3,"file":"create.cjs","names":["resolveCreateCommandOptions","slugifyAppName","path","getTemplateRoot","derivePackageScope","createBetterAuthSecret","getEnabledFeatures","loadTemplateManifest","getExcludedFeaturePaths","ensureDirectoryState","copyTemplateDirectory","collectTemplateFiles","writeManagedState","createManagedState","getCliVersion","fs","runGitInit","runInstall","TEMPLATE_NAME"],"sources":["../../src/create.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport {\r\n collectTemplateFiles,\r\n copyTemplateDirectory,\r\n ensureDirectoryState,\r\n runGitInit,\r\n runInstall,\r\n} from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { resolveCreateCommandOptions } from \"./prompts\";\r\nimport { createManagedState, getCliVersion, writeManagedState } from \"./state\";\r\nimport { createBetterAuthSecret, derivePackageScope, slugifyAppName } from \"./strings\";\r\nimport { getEnabledFeatures, getExcludedFeaturePaths, loadTemplateManifest } from \"./template\";\r\nimport type { CreateCommandOptions, TemplateContext } from \"./types\";\r\n\r\nexport interface ScaffoldResult {\r\n context: TemplateContext;\r\n targetDirectory: string;\r\n templateName: string;\r\n}\r\n\r\nexport async function scaffoldProject(\r\n initialOptions: CreateCommandOptions\r\n): Promise<ScaffoldResult> {\r\n const options = await resolveCreateCommandOptions(initialOptions);\r\n const { appName, appDescription, targetDirectory: targetDirectoryOption } = options;\r\n if (!appName || !appDescription || !targetDirectoryOption) {\r\n throw new Error(\"Unable to resolve the required create options.\");\r\n }\r\n const appSlug = slugifyAppName(appName);\r\n const targetDirectory = path.resolve(process.cwd(), targetDirectoryOption);\r\n const templateDirectory = getTemplateRoot();\r\n\r\n const context: TemplateContext = {\r\n appName,\r\n appDescription,\r\n appSlug,\r\n packageScope: derivePackageScope(appSlug),\r\n betterAuthSecret: createBetterAuthSecret(),\r\n };\r\n\r\n const enabledFeatures = getEnabledFeatures({\r\n platform: options.platform ?? \"web\",\r\n testHarness: Boolean(options.testHarness),\r\n modules: options.modules ?? [],\r\n });\r\n const manifest = loadTemplateManifest(templateDirectory);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n\r\n const createdTargetDirectory = await ensureDirectoryState(targetDirectory, options.force);\r\n try {\r\n await copyTemplateDirectory(templateDirectory, targetDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n const renderedFiles = await collectTemplateFiles(templateDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n await writeManagedState(\r\n targetDirectory,\r\n createManagedState({\r\n templateVersion: getCliVersion(),\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n })\r\n );\r\n } catch (error) {\r\n if (createdTargetDirectory) {\r\n await fs.rm(targetDirectory, { recursive: true, force: true });\r\n }\r\n throw error;\r\n }\r\n\r\n if (!options.skipGit) {\r\n await runGitInit(targetDirectory);\r\n }\r\n\r\n if (!options.skipInstall) {\r\n await runInstall(targetDirectory);\r\n }\r\n\r\n return {\r\n context,\r\n targetDirectory,\r\n templateName: TEMPLATE_NAME,\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAsB,gBACpB,gBACyB;CACzB,MAAM,UAAU,MAAMA,oBAAAA,4BAA4B,eAAe;CACjE,MAAM,EAAE,SAAS,gBAAgB,iBAAiB,0BAA0B;AAC5E,KAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,sBAClC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,UAAUC,oBAAAA,eAAe,QAAQ;CACvC,MAAM,kBAAkBC,UAAAA,QAAK,QAAQ,QAAQ,KAAK,EAAE,sBAAsB;CAC1E,MAAM,oBAAoBC,kBAAAA,iBAAiB;CAE3C,MAAM,UAA2B;EAC/B;EACA;EACA;EACA,cAAcC,oBAAAA,mBAAmB,QAAQ;EACzC,kBAAkBC,oBAAAA,wBAAwB;EAC3C;CAED,MAAM,kBAAkBC,qBAAAA,mBAAmB;EACzC,UAAU,QAAQ,YAAY;EAC9B,aAAa,QAAQ,QAAQ,YAAY;EACzC,SAAS,QAAQ,WAAW,EAAE;EAC/B,CAAC;CACF,MAAM,WAAWC,qBAAAA,qBAAqB,kBAAkB;CACxD,MAAM,kBAAkBC,qBAAAA,wBAAwB,UAAU,gBAAgB;CAE1E,MAAM,yBAAyB,MAAMC,eAAAA,qBAAqB,iBAAiB,QAAQ,MAAM;AACzF,KAAI;AACF,QAAMC,eAAAA,sBAAsB,mBAAmB,iBAAiB,SAAS;GACvE;GACA;GACD,CAAC;EACF,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,mBAAmB,SAAS;GAC3E;GACA;GACD,CAAC;AACF,QAAMC,kBAAAA,kBACJ,iBACAC,kBAAAA,mBAAmB;GACjB,iBAAiBC,kBAAAA,eAAe;GAChC;GACA;GACA;GACA;GACD,CAAC,CACH;UACM,OAAO;AACd,MAAI,uBACF,OAAMC,iBAAAA,QAAG,GAAG,iBAAiB;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAEhE,QAAM;;AAGR,KAAI,CAAC,QAAQ,QACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,KAAI,CAAC,QAAQ,YACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,QAAO;EACL;EACA;EACA,cAAcC,sBAAAA;EACf"}
@@ -102,7 +102,9 @@ async function diagnoseManagedRepo(options) {
102
102
  const workspaceRelativePath = "pnpm-workspace.yaml";
103
103
  const workspacePath = node_path.default.join(options.repoRoot, workspaceRelativePath);
104
104
  if (!await require_src_changes.findRepositorySymlink(options.repoRoot, workspaceRelativePath) && await exists(workspacePath)) try {
105
- const actualCatalog = require_src_catalog.readCatalog(await node_fs_promises.default.readFile(workspacePath, "utf8"));
105
+ const workspaceSource = await node_fs_promises.default.readFile(workspacePath, "utf8");
106
+ const actualCatalog = require_src_catalog.readCatalog(workspaceSource);
107
+ const defaultCatalog = require_src_catalog.readDefaultCatalog(workspaceSource);
106
108
  for (const [name, expected] of Object.entries(state.catalog)) {
107
109
  if (!semver.default.validRange(expected)) diagnostics.push({
108
110
  code: "CATALOG_VERSION_INVALID",
@@ -128,12 +130,16 @@ async function diagnoseManagedRepo(options) {
128
130
  const appsDirectory = node_path.default.join(options.repoRoot, "apps");
129
131
  const packageFiles = [node_path.default.join(options.repoRoot, "package.json")];
130
132
  if (await exists(appsDirectory)) packageFiles.push(...require_src_catalog.walkPackageJsonFiles(appsDirectory));
131
- for (const dependency of require_src_catalog.collectConsumerDependencyNames(packageFiles)) if (!(dependency in actualCatalog)) diagnostics.push({
132
- code: "CATALOG_REFERENCE_MISSING",
133
- severity: "error",
134
- message: `${dependency} uses catalog: but has no catalog entry.`,
135
- path: "pnpm-workspace.yaml"
136
- });
133
+ for (const { name, specifier } of require_src_catalog.collectCatalogProtocolDependencies(packageFiles)) {
134
+ const catalog = specifier === require_src_catalog.MANAGED_CATALOG_SPECIFIER ? actualCatalog : specifier === "catalog:" ? defaultCatalog : void 0;
135
+ if (catalog === void 0) continue;
136
+ if (!(name in catalog)) diagnostics.push({
137
+ code: "CATALOG_REFERENCE_MISSING",
138
+ severity: "error",
139
+ message: `${name} uses ${specifier} but has no matching catalog entry.`,
140
+ path: "pnpm-workspace.yaml"
141
+ });
142
+ }
137
143
  } catch (error) {
138
144
  diagnostics.push({
139
145
  code: "CATALOG_INVALID",
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","walkPackageJsonFiles","collectConsumerDependencyNames","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport { collectConsumerDependencyNames, readCatalog, walkPackageJsonFiles } from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const actualCatalog = readCatalog(await fs.readFile(workspacePath, \"utf8\"));\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const dependency of collectConsumerDependencyNames(packageFiles)) {\r\n if (!(dependency in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${dependency} uses catalog: but has no catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","readDefaultCatalog","walkPackageJsonFiles","collectCatalogProtocolDependencies","MANAGED_CATALOG_SPECIFIER","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport {\r\n collectCatalogProtocolDependencies,\r\n MANAGED_CATALOG_SPECIFIER,\r\n readCatalog,\r\n readDefaultCatalog,\r\n walkPackageJsonFiles,\r\n} from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const workspaceSource = await fs.readFile(workspacePath, \"utf8\");\r\n const actualCatalog = readCatalog(workspaceSource);\r\n const defaultCatalog = readDefaultCatalog(workspaceSource);\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const { name, specifier } of collectCatalogProtocolDependencies(packageFiles)) {\r\n const catalog =\r\n specifier === MANAGED_CATALOG_SPECIFIER\r\n ? actualCatalog\r\n : specifier === \"catalog:\"\r\n ? defaultCatalog\r\n : undefined;\r\n if (catalog === undefined) continue;\r\n if (!(name in catalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${name} uses ${specifier} but has no matching catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
package/dist/src/fs.cjs CHANGED
@@ -22,16 +22,21 @@ const TEXT_EXTENSIONS = new Set([
22
22
  ".yaml",
23
23
  ".yml",
24
24
  ".txt",
25
- ".mdc"
25
+ ".mdc",
26
+ ".toml"
26
27
  ]);
27
28
  const TEXT_BASENAMES = new Set([
28
29
  ".gitignore",
29
30
  ".env",
30
31
  ".env.example",
31
- ".npmrc"
32
+ ".env.production",
33
+ ".env.production.example",
34
+ ".npmrc",
35
+ ".dockerignore",
36
+ "Dockerfile"
32
37
  ]);
33
38
  /** Feature marker blocks: lines between `// m5k:<feature>:start` and `// m5k:<feature>:end`. */
34
- const MARKER_PATTERN = /^[ \t]*\/\/ m5k:([a-z-]+):(start|end)[ \t]*$/;
39
+ const MARKER_PATTERN = /^[ \t]*(?:\/\/|#)[ \t]*m5k:([a-z-]+):(start|end)[ \t]*$/;
35
40
  async function collectTemplateFiles(templateDirectory, context, options = {}, relativeBase = "") {
36
41
  const entries = await node_fs_promises.default.readdir(templateDirectory, { withFileTypes: true });
37
42
  const excludePrefixes = options.excludePrefixes ?? [];
@@ -66,9 +71,9 @@ async function collectTemplateFiles(templateDirectory, context, options = {}, re
66
71
  });
67
72
  continue;
68
73
  }
69
- const content = await node_fs_promises.default.readFile(sourcePath, "utf8");
74
+ const content = (await node_fs_promises.default.readFile(sourcePath, "utf8")).replace(/\r\n?/g, "\n");
70
75
  files.push({
71
- content: Buffer.from(applyFeatureMarkers(require_src_strings.renderTemplate(content, context), enabledFeatures).replace(/\r\n?/g, "\n"), "utf8"),
76
+ content: Buffer.from(applyFeatureMarkers(require_src_strings.renderTemplate(content, context), enabledFeatures), "utf8"),
72
77
  kind: "text",
73
78
  relativePath
74
79
  });
@@ -1 +1 @@
1
- {"version":3,"file":"fs.cjs","names":["execFile","fs","path","renderTemplate"],"sources":["../../src/fs.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { renderTemplate } from \"./strings\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nconst TEXT_EXTENSIONS = new Set([\r\n \".ts\",\r\n \".tsx\",\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".json\",\r\n \".md\",\r\n \".css\",\r\n \".html\",\r\n \".svg\",\r\n \".yaml\",\r\n \".yml\",\r\n \".txt\",\r\n \".mdc\",\r\n]);\r\nconst TEXT_BASENAMES = new Set([\".gitignore\", \".env\", \".env.example\", \".npmrc\"]);\r\n\r\n/** Feature marker blocks: lines between `// m5k:<feature>:start` and `// m5k:<feature>:end`. */\r\nconst MARKER_PATTERN = /^[ \\t]*\\/\\/ m5k:([a-z-]+):(start|end)[ \\t]*$/;\r\n\r\nexport interface CopyTemplateOptions {\r\n /** Repo-root-relative path prefixes to skip entirely (disabled features). */\r\n excludePrefixes?: readonly string[];\r\n /** Features whose marker blocks should be kept (markers removed, content kept). */\r\n enabledFeatures?: ReadonlySet<string>;\r\n /** Update inspection can retain ignored-file metadata without rendering secret content. */\r\n ignoreContentFor?: (relativePath: string) => boolean;\r\n}\r\n\r\nexport async function collectTemplateFiles(\r\n templateDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {},\r\n relativeBase = \"\"\r\n): Promise<RenderedTemplateFile[]> {\r\n const entries = await fs.readdir(templateDirectory, { withFileTypes: true });\r\n const excludePrefixes = options.excludePrefixes ?? [];\r\n const enabledFeatures = options.enabledFeatures ?? new Set<string>();\r\n const files: RenderedTemplateFile[] = [];\r\n\r\n for (const entry of entries) {\r\n const sourcePath = path.join(templateDirectory, entry.name);\r\n const targetName = renderTemplate(\r\n entry.name.endsWith(\".tpl\") ? entry.name.slice(0, -4) : entry.name,\r\n context\r\n );\r\n const relativePath = relativeBase ? `${relativeBase}/${targetName}` : targetName;\r\n\r\n if (relativePath === \"template.manifest.json\") continue;\r\n\r\n const asDirPrefix = `${relativePath}/`;\r\n if (\r\n excludePrefixes.some(\r\n (prefix) =>\r\n relativePath === prefix || asDirPrefix === prefix || relativePath.startsWith(prefix)\r\n )\r\n ) {\r\n continue;\r\n }\r\n\r\n if (entry.isDirectory()) {\r\n files.push(...(await collectTemplateFiles(sourcePath, context, options, relativePath)));\r\n continue;\r\n }\r\n if (entry.isSymbolicLink()) {\r\n throw new Error(`Template symlinks are not supported: ${relativePath}`);\r\n }\r\n if (!entry.isFile()) continue;\r\n\r\n if (options.ignoreContentFor?.(relativePath)) {\r\n files.push({\r\n content: Buffer.alloc(0),\r\n kind: isTextFile(entry.name) ? \"text\" : \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n if (!isTextFile(entry.name)) {\r\n files.push({\r\n content: await fs.readFile(sourcePath),\r\n kind: \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n const content = await fs.readFile(sourcePath, \"utf8\");\r\n files.push({\r\n content: Buffer.from(\r\n applyFeatureMarkers(renderTemplate(content, context), enabledFeatures).replace(\r\n /\\r\\n?/g,\r\n \"\\n\"\r\n ),\r\n \"utf8\"\r\n ),\r\n kind: \"text\",\r\n relativePath,\r\n });\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Ensures the target directory exists and may be written.\r\n * Returns true when this call created the directory (safe to remove on failure).\r\n */\r\nexport async function ensureDirectoryState(\r\n targetDirectory: string,\r\n force: boolean\r\n): Promise<boolean> {\r\n const stat = await fs.stat(targetDirectory).catch(() => null);\r\n\r\n if (!stat) {\r\n await fs.mkdir(targetDirectory, { recursive: true });\r\n return true;\r\n }\r\n\r\n if (!stat.isDirectory()) {\r\n throw new Error(`Target path is not a directory: ${targetDirectory}`);\r\n }\r\n\r\n const entries = await fs.readdir(targetDirectory);\r\n if (entries.length > 0 && !force) {\r\n throw new Error(\r\n `Target directory is not empty: ${targetDirectory}. Re-run with --force to overwrite.`\r\n );\r\n }\r\n\r\n return false;\r\n}\r\n\r\nfunction isTextFile(fileName: string): boolean {\r\n const withoutTpl = fileName.endsWith(\".tpl\") ? fileName.slice(0, -4) : fileName;\r\n if (TEXT_BASENAMES.has(withoutTpl)) return true;\r\n return TEXT_EXTENSIONS.has(path.extname(withoutTpl));\r\n}\r\n\r\n/**\r\n * Removes feature marker lines. Blocks for disabled features are dropped with\r\n * their content; blocks for enabled features keep the content, lose the markers.\r\n */\r\nexport function applyFeatureMarkers(content: string, enabledFeatures: ReadonlySet<string>): string {\r\n const lines = content.split(\"\\n\");\r\n const out: string[] = [];\r\n let skippingFeature: string | undefined;\r\n\r\n for (const line of lines) {\r\n const match = line.match(MARKER_PATTERN);\r\n if (!match) {\r\n if (skippingFeature === undefined) out.push(line);\r\n continue;\r\n }\r\n\r\n const [, feature, edge] = match;\r\n if (edge === \"start\") {\r\n if (!enabledFeatures.has(feature)) skippingFeature = feature;\r\n } else if (skippingFeature === feature) {\r\n skippingFeature = undefined;\r\n }\r\n // marker lines themselves are never emitted\r\n }\r\n\r\n return out.join(\"\\n\");\r\n}\r\n\r\nexport async function copyTemplateDirectory(\r\n templateDirectory: string,\r\n targetDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {}\r\n): Promise<void> {\r\n const files = await collectTemplateFiles(templateDirectory, context, options);\r\n for (const file of files) {\r\n const targetPath = path.join(targetDirectory, file.relativePath);\r\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\r\n await fs.writeFile(targetPath, file.content);\r\n }\r\n}\r\n\r\nexport async function runInstall(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n\r\nexport async function runGitInit(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"git\", [\"init\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs.cjs","names":["execFile","fs","path","renderTemplate"],"sources":["../../src/fs.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { renderTemplate } from \"./strings\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nconst TEXT_EXTENSIONS = new Set([\r\n \".ts\",\r\n \".tsx\",\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".json\",\r\n \".md\",\r\n \".css\",\r\n \".html\",\r\n \".svg\",\r\n \".yaml\",\r\n \".yml\",\r\n \".txt\",\r\n \".mdc\",\r\n \".toml\",\r\n]);\r\nconst TEXT_BASENAMES = new Set([\r\n \".gitignore\",\r\n \".env\",\r\n \".env.example\",\r\n \".env.production\",\r\n \".env.production.example\",\r\n \".npmrc\",\r\n \".dockerignore\",\r\n \"Dockerfile\",\r\n]);\r\n\r\n/** Feature marker blocks: lines between `// m5k:<feature>:start` and `// m5k:<feature>:end`. */\r\nconst MARKER_PATTERN = /^[ \\t]*(?:\\/\\/|#)[ \\t]*m5k:([a-z-]+):(start|end)[ \\t]*$/;\r\n\r\nexport interface CopyTemplateOptions {\r\n /** Repo-root-relative path prefixes to skip entirely (disabled features). */\r\n excludePrefixes?: readonly string[];\r\n /** Features whose marker blocks should be kept (markers removed, content kept). */\r\n enabledFeatures?: ReadonlySet<string>;\r\n /** Update inspection can retain ignored-file metadata without rendering secret content. */\r\n ignoreContentFor?: (relativePath: string) => boolean;\r\n}\r\n\r\nexport async function collectTemplateFiles(\r\n templateDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {},\r\n relativeBase = \"\"\r\n): Promise<RenderedTemplateFile[]> {\r\n const entries = await fs.readdir(templateDirectory, { withFileTypes: true });\r\n const excludePrefixes = options.excludePrefixes ?? [];\r\n const enabledFeatures = options.enabledFeatures ?? new Set<string>();\r\n const files: RenderedTemplateFile[] = [];\r\n\r\n for (const entry of entries) {\r\n const sourcePath = path.join(templateDirectory, entry.name);\r\n const targetName = renderTemplate(\r\n entry.name.endsWith(\".tpl\") ? entry.name.slice(0, -4) : entry.name,\r\n context\r\n );\r\n const relativePath = relativeBase ? `${relativeBase}/${targetName}` : targetName;\r\n\r\n if (relativePath === \"template.manifest.json\") continue;\r\n\r\n const asDirPrefix = `${relativePath}/`;\r\n if (\r\n excludePrefixes.some(\r\n (prefix) =>\r\n relativePath === prefix || asDirPrefix === prefix || relativePath.startsWith(prefix)\r\n )\r\n ) {\r\n continue;\r\n }\r\n\r\n if (entry.isDirectory()) {\r\n files.push(...(await collectTemplateFiles(sourcePath, context, options, relativePath)));\r\n continue;\r\n }\r\n if (entry.isSymbolicLink()) {\r\n throw new Error(`Template symlinks are not supported: ${relativePath}`);\r\n }\r\n if (!entry.isFile()) continue;\r\n\r\n if (options.ignoreContentFor?.(relativePath)) {\r\n files.push({\r\n content: Buffer.alloc(0),\r\n kind: isTextFile(entry.name) ? \"text\" : \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n if (!isTextFile(entry.name)) {\r\n files.push({\r\n content: await fs.readFile(sourcePath),\r\n kind: \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n const content = (await fs.readFile(sourcePath, \"utf8\")).replace(/\\r\\n?/g, \"\\n\");\r\n files.push({\r\n content: Buffer.from(\r\n applyFeatureMarkers(renderTemplate(content, context), enabledFeatures),\r\n \"utf8\"\r\n ),\r\n kind: \"text\",\r\n relativePath,\r\n });\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Ensures the target directory exists and may be written.\r\n * Returns true when this call created the directory (safe to remove on failure).\r\n */\r\nexport async function ensureDirectoryState(\r\n targetDirectory: string,\r\n force: boolean\r\n): Promise<boolean> {\r\n const stat = await fs.stat(targetDirectory).catch(() => null);\r\n\r\n if (!stat) {\r\n await fs.mkdir(targetDirectory, { recursive: true });\r\n return true;\r\n }\r\n\r\n if (!stat.isDirectory()) {\r\n throw new Error(`Target path is not a directory: ${targetDirectory}`);\r\n }\r\n\r\n const entries = await fs.readdir(targetDirectory);\r\n if (entries.length > 0 && !force) {\r\n throw new Error(\r\n `Target directory is not empty: ${targetDirectory}. Re-run with --force to overwrite.`\r\n );\r\n }\r\n\r\n return false;\r\n}\r\n\r\nfunction isTextFile(fileName: string): boolean {\r\n const withoutTpl = fileName.endsWith(\".tpl\") ? fileName.slice(0, -4) : fileName;\r\n if (TEXT_BASENAMES.has(withoutTpl)) return true;\r\n return TEXT_EXTENSIONS.has(path.extname(withoutTpl));\r\n}\r\n\r\n/**\r\n * Removes feature marker lines. Blocks for disabled features are dropped with\r\n * their content; blocks for enabled features keep the content, lose the markers.\r\n */\r\nexport function applyFeatureMarkers(content: string, enabledFeatures: ReadonlySet<string>): string {\r\n const lines = content.split(\"\\n\");\r\n const out: string[] = [];\r\n let skippingFeature: string | undefined;\r\n\r\n for (const line of lines) {\r\n const match = line.match(MARKER_PATTERN);\r\n if (!match) {\r\n if (skippingFeature === undefined) out.push(line);\r\n continue;\r\n }\r\n\r\n const [, feature, edge] = match;\r\n if (edge === \"start\") {\r\n if (!enabledFeatures.has(feature)) skippingFeature = feature;\r\n } else if (skippingFeature === feature) {\r\n skippingFeature = undefined;\r\n }\r\n // marker lines themselves are never emitted\r\n }\r\n\r\n return out.join(\"\\n\");\r\n}\r\n\r\nexport async function copyTemplateDirectory(\r\n templateDirectory: string,\r\n targetDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {}\r\n): Promise<void> {\r\n const files = await collectTemplateFiles(templateDirectory, context, options);\r\n for (const file of files) {\r\n const targetPath = path.join(targetDirectory, file.relativePath);\r\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\r\n await fs.writeFile(targetPath, file.content);\r\n }\r\n}\r\n\r\nexport async function runInstall(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n\r\nexport async function runGitInit(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"git\", [\"init\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n"],"mappings":"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"}
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
3
  const require_src_constants = require("./constants.cjs");
4
4
  const require_src_strings = require("./strings.cjs");
5
+ const require_src_paths = require("./paths.cjs");
6
+ const require_src_template = require("./template.cjs");
5
7
  let node_process = require("node:process");
6
8
  let node_readline_promises = require("node:readline/promises");
7
9
  node_readline_promises = require_runtime.__toESM(node_readline_promises);
@@ -25,6 +27,23 @@ function parsePlatform(value) {
25
27
  const normalized = value.trim().toLowerCase();
26
28
  if (normalized === "web" || normalized === "expo" || normalized === "both") return normalized;
27
29
  }
30
+ function formatBackendModulesPrompt(choices) {
31
+ return `Backend modules — comma-separated ids, or none [${choices.map((choice) => choice.experimental ? `${choice.id} (experimental)` : choice.id).join(", ")}]`;
32
+ }
33
+ function parseBackendModulesAnswer(options) {
34
+ const trimmed = options.answer.trim().toLowerCase();
35
+ if (!trimmed || trimmed === "none" || trimmed === "n") return [];
36
+ const allowed = new Map(options.choices.map((choice) => [choice.id, choice.id]));
37
+ const selected = [];
38
+ for (const raw of trimmed.split(",")) {
39
+ const id = raw.trim();
40
+ if (!id) continue;
41
+ const resolved = allowed.get(id);
42
+ if (!resolved) throw new Error(`Unknown Backend Module "${id}". Use comma-separated ids from the prompt, or none.`);
43
+ if (!selected.includes(resolved)) selected.push(resolved);
44
+ }
45
+ return selected;
46
+ }
28
47
  async function resolveCreateCommandOptions(options) {
29
48
  const resolved = { ...options };
30
49
  if (!resolved.appName) {
@@ -59,12 +78,25 @@ async function resolveCreateCommandOptions(options) {
59
78
  resolved.testHarness = /^y(es)?$/i.test(answer.trim());
60
79
  }
61
80
  }
81
+ if (resolved.modules === void 0) {
82
+ requireInteractive(resolved.yes);
83
+ if (resolved.yes) resolved.modules = [];
84
+ else {
85
+ const choices = require_src_template.listBackendModuleChoices(require_src_template.loadTemplateManifest(require_src_paths.getTemplateRoot()));
86
+ resolved.modules = parseBackendModulesAnswer({
87
+ answer: await promptValue(formatBackendModulesPrompt(choices), "none"),
88
+ choices
89
+ });
90
+ }
91
+ }
62
92
  return resolved;
63
93
  }
64
94
  function pathBaseName(value) {
65
95
  return value.replace(/[\\/]+$/g, "").split(/[\\/]/).filter(Boolean).at(-1) || "M5 Starter";
66
96
  }
67
97
  //#endregion
98
+ exports.formatBackendModulesPrompt = formatBackendModulesPrompt;
99
+ exports.parseBackendModulesAnswer = parseBackendModulesAnswer;
68
100
  exports.resolveCreateCommandOptions = resolveCreateCommandOptions;
69
101
 
70
102
  //# sourceMappingURL=prompts.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.cjs","names":["readline","toDisplayName","DEFAULT_APP_NAME","slugifyAppName","getDefaultDescription"],"sources":["../../src/prompts.ts"],"sourcesContent":["import { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { DEFAULT_APP_NAME, getDefaultDescription } from \"./constants\";\r\nimport { slugifyAppName, toDisplayName } from \"./strings\";\r\nimport type { AppPlatform, CreateCommandOptions } from \"./types\";\r\n\r\nfunction requireInteractive(yes: boolean): void {\r\n if (!yes && !process.stdin.isTTY) {\r\n throw new Error(\r\n \"Missing required values in a non-interactive shell. Pass --yes or provide flags.\"\r\n );\r\n }\r\n}\r\n\r\nasync function promptValue(question: string, fallback?: string): Promise<string> {\r\n const rl = readline.createInterface({ input, output });\r\n\r\n try {\r\n const suffix = fallback ? ` (${fallback})` : \"\";\r\n const response = await rl.question(`${question}${suffix}: `);\r\n return response.trim() || fallback || \"\";\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\nfunction parsePlatform(value: string): AppPlatform | undefined {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized === \"web\" || normalized === \"expo\" || normalized === \"both\") {\r\n return normalized;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport async function resolveCreateCommandOptions(\r\n options: CreateCommandOptions\r\n): Promise<CreateCommandOptions> {\r\n const resolved = { ...options };\r\n\r\n if (!resolved.appName) {\r\n requireInteractive(resolved.yes);\r\n resolved.appName = resolved.yes\r\n ? resolved.targetDirectory\r\n ? toDisplayName(pathBaseName(resolved.targetDirectory))\r\n : DEFAULT_APP_NAME\r\n : await promptValue(\r\n \"App name\",\r\n resolved.targetDirectory ? pathBaseName(resolved.targetDirectory) : DEFAULT_APP_NAME\r\n );\r\n }\r\n\r\n resolved.appName = toDisplayName(resolved.appName);\r\n\r\n if (!resolved.targetDirectory) {\r\n requireInteractive(resolved.yes);\r\n resolved.targetDirectory = resolved.yes\r\n ? slugifyAppName(resolved.appName)\r\n : await promptValue(\"Target directory\", slugifyAppName(resolved.appName));\r\n }\r\n\r\n if (!resolved.appDescription) {\r\n requireInteractive(resolved.yes);\r\n const fallback = getDefaultDescription(resolved.appName);\r\n resolved.appDescription = resolved.yes\r\n ? fallback\r\n : await promptValue(\"App description\", fallback);\r\n }\r\n\r\n if (!resolved.platform) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.platform = \"web\";\r\n } else {\r\n const answer = await promptValue(\"App platform — web, expo, or both\", \"web\");\r\n const platform = parsePlatform(answer);\r\n if (!platform) {\r\n throw new Error(`Invalid platform \"${answer}\". Use web, expo, or both.`);\r\n }\r\n resolved.platform = platform;\r\n }\r\n }\r\n\r\n if (resolved.testHarness === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.testHarness = false;\r\n } else {\r\n const answer = await promptValue(\"Include the e2e test harness? y/N\", \"n\");\r\n resolved.testHarness = /^y(es)?$/i.test(answer.trim());\r\n }\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\nfunction pathBaseName(value: string): string {\r\n const normalized = value.replace(/[\\\\/]+$/g, \"\");\r\n const segments = normalized.split(/[\\\\/]/).filter(Boolean);\r\n return segments.at(-1) || DEFAULT_APP_NAME;\r\n}\r\n"],"mappings":";;;;;;;;AAMA,SAAS,mBAAmB,KAAoB;AAC9C,KAAI,CAAC,OAAO,CAAC,QAAQ,MAAM,MACzB,OAAM,IAAI,MACR,mFACD;;AAIL,eAAe,YAAY,UAAkB,UAAoC;CAC/E,MAAM,KAAKA,uBAAAA,QAAS,gBAAgB;EAAE,OAAA,aAAA;EAAO,QAAA,aAAA;EAAQ,CAAC;AAEtD,KAAI;EACF,MAAM,SAAS,WAAW,KAAK,SAAS,KAAK;AAE7C,UADiB,MAAM,GAAG,SAAS,GAAG,WAAW,OAAO,IAAI,EAC5C,MAAM,IAAI,YAAY;WAC9B;AACR,KAAG,OAAO;;;AAId,SAAS,cAAc,OAAwC;CAC7D,MAAM,aAAa,MAAM,MAAM,CAAC,aAAa;AAC7C,KAAI,eAAe,SAAS,eAAe,UAAU,eAAe,OAClE,QAAO;;AAKX,eAAsB,4BACpB,SAC+B;CAC/B,MAAM,WAAW,EAAE,GAAG,SAAS;AAE/B,KAAI,CAAC,SAAS,SAAS;AACrB,qBAAmB,SAAS,IAAI;AAChC,WAAS,UAAU,SAAS,MACxB,SAAS,kBACPC,oBAAAA,cAAc,aAAa,SAAS,gBAAgB,CAAC,GACrDC,sBAAAA,mBACF,MAAM,YACJ,YACA,SAAS,kBAAkB,aAAa,SAAS,gBAAgB,GAAGA,sBAAAA,iBACrE;;AAGP,UAAS,UAAUD,oBAAAA,cAAc,SAAS,QAAQ;AAElD,KAAI,CAAC,SAAS,iBAAiB;AAC7B,qBAAmB,SAAS,IAAI;AAChC,WAAS,kBAAkB,SAAS,MAChCE,oBAAAA,eAAe,SAAS,QAAQ,GAChC,MAAM,YAAY,oBAAoBA,oBAAAA,eAAe,SAAS,QAAQ,CAAC;;AAG7E,KAAI,CAAC,SAAS,gBAAgB;AAC5B,qBAAmB,SAAS,IAAI;EAChC,MAAM,WAAWC,sBAAAA,sBAAsB,SAAS,QAAQ;AACxD,WAAS,iBAAiB,SAAS,MAC/B,WACA,MAAM,YAAY,mBAAmB,SAAS;;AAGpD,KAAI,CAAC,SAAS,UAAU;AACtB,qBAAmB,SAAS,IAAI;AAChC,MAAI,SAAS,IACX,UAAS,WAAW;OACf;GACL,MAAM,SAAS,MAAM,YAAY,qCAAqC,MAAM;GAC5E,MAAM,WAAW,cAAc,OAAO;AACtC,OAAI,CAAC,SACH,OAAM,IAAI,MAAM,qBAAqB,OAAO,4BAA4B;AAE1E,YAAS,WAAW;;;AAIxB,KAAI,SAAS,gBAAgB,KAAA,GAAW;AACtC,qBAAmB,SAAS,IAAI;AAChC,MAAI,SAAS,IACX,UAAS,cAAc;OAClB;GACL,MAAM,SAAS,MAAM,YAAY,qCAAqC,IAAI;AAC1E,YAAS,cAAc,YAAY,KAAK,OAAO,MAAM,CAAC;;;AAI1D,QAAO;;AAGT,SAAS,aAAa,OAAuB;AAG3C,QAFmB,MAAM,QAAQ,YAAY,GAAG,CACpB,MAAM,QAAQ,CAAC,OAAO,QAAQ,CAC1C,GAAG,GAAG,IAAA"}
1
+ {"version":3,"file":"prompts.cjs","names":["readline","toDisplayName","DEFAULT_APP_NAME","slugifyAppName","getDefaultDescription","listBackendModuleChoices","loadTemplateManifest","getTemplateRoot"],"sources":["../../src/prompts.ts"],"sourcesContent":["import { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { DEFAULT_APP_NAME, getDefaultDescription } from \"./constants\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { slugifyAppName, toDisplayName } from \"./strings\";\r\nimport type { BackendModuleChoice } from \"./template\";\r\nimport { listBackendModuleChoices, loadTemplateManifest } from \"./template\";\r\nimport type { AppPlatform, CreateCommandOptions } from \"./types\";\r\n\r\nfunction requireInteractive(yes: boolean): void {\r\n if (!yes && !process.stdin.isTTY) {\r\n throw new Error(\r\n \"Missing required values in a non-interactive shell. Pass --yes or provide flags.\"\r\n );\r\n }\r\n}\r\n\r\nasync function promptValue(question: string, fallback?: string): Promise<string> {\r\n const rl = readline.createInterface({ input, output });\r\n\r\n try {\r\n const suffix = fallback ? ` (${fallback})` : \"\";\r\n const response = await rl.question(`${question}${suffix}: `);\r\n return response.trim() || fallback || \"\";\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\nfunction parsePlatform(value: string): AppPlatform | undefined {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized === \"web\" || normalized === \"expo\" || normalized === \"both\") {\r\n return normalized;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport function formatBackendModulesPrompt(choices: readonly BackendModuleChoice[]): string {\r\n const catalog = choices\r\n .map((choice) => (choice.experimental ? `${choice.id} (experimental)` : choice.id))\r\n .join(\", \");\r\n return `Backend modules — comma-separated ids, or none [${catalog}]`;\r\n}\r\n\r\nexport function parseBackendModulesAnswer(options: {\r\n answer: string;\r\n choices: readonly BackendModuleChoice[];\r\n}): string[] {\r\n const trimmed = options.answer.trim().toLowerCase();\r\n if (!trimmed || trimmed === \"none\" || trimmed === \"n\") return [];\r\n const allowed = new Map(options.choices.map((choice) => [choice.id, choice.id]));\r\n const selected: string[] = [];\r\n for (const raw of trimmed.split(\",\")) {\r\n const id = raw.trim();\r\n if (!id) continue;\r\n const resolved = allowed.get(id);\r\n if (!resolved) {\r\n throw new Error(\r\n `Unknown Backend Module \"${id}\". Use comma-separated ids from the prompt, or none.`\r\n );\r\n }\r\n if (!selected.includes(resolved)) selected.push(resolved);\r\n }\r\n return selected;\r\n}\r\n\r\nexport async function resolveCreateCommandOptions(\r\n options: CreateCommandOptions\r\n): Promise<CreateCommandOptions> {\r\n const resolved = { ...options };\r\n\r\n if (!resolved.appName) {\r\n requireInteractive(resolved.yes);\r\n resolved.appName = resolved.yes\r\n ? resolved.targetDirectory\r\n ? toDisplayName(pathBaseName(resolved.targetDirectory))\r\n : DEFAULT_APP_NAME\r\n : await promptValue(\r\n \"App name\",\r\n resolved.targetDirectory ? pathBaseName(resolved.targetDirectory) : DEFAULT_APP_NAME\r\n );\r\n }\r\n\r\n resolved.appName = toDisplayName(resolved.appName);\r\n\r\n if (!resolved.targetDirectory) {\r\n requireInteractive(resolved.yes);\r\n resolved.targetDirectory = resolved.yes\r\n ? slugifyAppName(resolved.appName)\r\n : await promptValue(\"Target directory\", slugifyAppName(resolved.appName));\r\n }\r\n\r\n if (!resolved.appDescription) {\r\n requireInteractive(resolved.yes);\r\n const fallback = getDefaultDescription(resolved.appName);\r\n resolved.appDescription = resolved.yes\r\n ? fallback\r\n : await promptValue(\"App description\", fallback);\r\n }\r\n\r\n if (!resolved.platform) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.platform = \"web\";\r\n } else {\r\n const answer = await promptValue(\"App platform — web, expo, or both\", \"web\");\r\n const platform = parsePlatform(answer);\r\n if (!platform) {\r\n throw new Error(`Invalid platform \"${answer}\". Use web, expo, or both.`);\r\n }\r\n resolved.platform = platform;\r\n }\r\n }\r\n\r\n if (resolved.testHarness === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.testHarness = false;\r\n } else {\r\n const answer = await promptValue(\"Include the e2e test harness? y/N\", \"n\");\r\n resolved.testHarness = /^y(es)?$/i.test(answer.trim());\r\n }\r\n }\r\n\r\n if (resolved.modules === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.modules = [];\r\n } else {\r\n const choices = listBackendModuleChoices(loadTemplateManifest(getTemplateRoot()));\r\n const answer = await promptValue(formatBackendModulesPrompt(choices), \"none\");\r\n resolved.modules = parseBackendModulesAnswer({ answer, choices });\r\n }\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\nfunction pathBaseName(value: string): string {\r\n const normalized = value.replace(/[\\\\/]+$/g, \"\");\r\n const segments = normalized.split(/[\\\\/]/).filter(Boolean);\r\n return segments.at(-1) || DEFAULT_APP_NAME;\r\n}\r\n"],"mappings":"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"}