create-m5kdev 0.38.14 → 0.38.16

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 (37) hide show
  1. package/dist/src/catalog.cjs +5 -1
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +2 -1
  4. package/dist/src/create.cjs +6 -3
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs +5 -0
  7. package/dist/src/doctor.cjs.map +1 -1
  8. package/dist/src/fs.cjs +5 -2
  9. package/dist/src/fs.cjs.map +1 -1
  10. package/dist/src/init.cjs +2 -1
  11. package/dist/src/init.cjs.map +1 -1
  12. package/dist/src/prompts.cjs +28 -4
  13. package/dist/src/prompts.cjs.map +1 -1
  14. package/dist/src/prompts.d.cts +11 -2
  15. package/dist/src/reconcile.cjs +2 -3
  16. package/dist/src/reconcile.cjs.map +1 -1
  17. package/dist/src/runCli.cjs +21 -11
  18. package/dist/src/runCli.cjs.map +1 -1
  19. package/dist/src/template.cjs +21 -4
  20. package/dist/src/template.cjs.map +1 -1
  21. package/dist/src/template.d.cts +6 -3
  22. package/dist/src/types.d.cts +1 -1
  23. package/dist/src/update.cjs +91 -6
  24. package/dist/src/update.cjs.map +1 -1
  25. package/dist/src/update.d.cts +5 -0
  26. package/package.json +1 -1
  27. package/templates/minimal-app/.gitignore.tpl +1 -0
  28. package/templates/minimal-app/AGENTS.md.tpl +14 -2
  29. package/templates/minimal-app/CLAUDE.md.tpl +12 -1
  30. package/templates/minimal-app/README.md.tpl +32 -14
  31. package/templates/minimal-app/apps/landing/.env.example.tpl +1 -0
  32. package/templates/minimal-app/apps/landing/.env.tpl +1 -0
  33. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +3 -9
  34. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +2 -0
  35. package/templates/minimal-app/package.json.tpl +2 -0
  36. package/templates/minimal-app/pnpm-workspace.yaml.tpl +12 -12
  37. package/templates/minimal-app/template.manifest.json +8 -3
@@ -23,8 +23,11 @@ function walkPackageJsonFiles(directory, out = []) {
23
23
  }
24
24
  return out;
25
25
  }
26
+ function stripFeatureMarkerLines(source) {
27
+ return source.replace(/\r\n?/g, "\n").split("\n").filter((line) => !/^[ \t]*(?:\/\/|#)[ \t]*m5k:[a-z-]+:(start|end)[ \t]*$/.test(line)).join("\n");
28
+ }
26
29
  function readJson(filePath) {
27
- return JSON.parse(node_fs.default.readFileSync(filePath, "utf8"));
30
+ return JSON.parse(stripFeatureMarkerLines(node_fs.default.readFileSync(filePath, "utf8")));
28
31
  }
29
32
  function asCatalog(value) {
30
33
  return Object.fromEntries(Object.entries(value ?? {}).map(([name, version]) => [name, String(version)]));
@@ -200,6 +203,7 @@ exports.readCatalog = readCatalog;
200
203
  exports.readDefaultCatalog = readDefaultCatalog;
201
204
  exports.renderConsumerWorkspace = renderConsumerWorkspace;
202
205
  exports.sortedCatalog = sortedCatalog;
206
+ exports.stripFeatureMarkerLines = stripFeatureMarkerLines;
203
207
  exports.walkPackageJsonFiles = walkPackageJsonFiles;
204
208
 
205
209
  //# 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 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":";;;;;;;;AAIA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACD;AAED,MAAa,uBAAuB;AACpC,MAAa,4BAA4B,WAAW;AASpD,SAAgB,qBAAqB,WAAmB,MAAgB,EAAE,EAAY;AACpF,MAAK,MAAM,SAASA,QAAAA,QAAG,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,EAAE;AACtE,MAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,OAAQ;EAC5D,MAAM,YAAYC,UAAAA,QAAK,KAAK,WAAW,MAAM,KAAK;AAClD,MAAI,MAAM,aAAa,CACrB,sBAAqB,WAAW,IAAI;WAC3B,MAAM,QAAQ,IAAI,MAAM,SAAS,eAC1C,KAAI,KAAK,UAAU;;AAGvB,QAAO;;AAGT,SAAS,SAAS,UAA2C;AAC3D,QAAO,KAAK,MAAMD,QAAAA,QAAG,aAAa,UAAU,OAAO,CAAC;;AAGtD,SAAS,UAAU,OAAqE;AACtF,QAAO,OAAO,YACZ,OAAO,QAAQ,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,aAAa,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC,CAC9E;;AAGH,SAAgB,4CACd,WACU;CACV,MAAM,wBAAQ,IAAI,KAAa;AAE/B,MAAK,MAAM,YAAY,UACrB,MAAK,MAAM,SAAS,mBAAmB;EACrC,MAAM,eAAe,SAAS;AAC9B,OAAK,MAAM,CAAC,MAAM,cAAc,OAAO,QAAQ,gBAAgB,EAAE,CAAC,CAChE,KACE,UAAU,WAAW,WAAW,IAC/B,KAAK,WAAW,WAAW,IAAI,UAAU,WAAW,aAAa,CAElE,OAAM,IAAI,KAAK;;AAMvB,QAAO,CAAC,GAAG,MAAM,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;;AAGpE,SAAgB,+BAA+B,cAA2C;AACxF,QAAO,4CAA4C,aAAa,IAAI,SAAS,CAAC;;AAQhF,SAAgB,gDACd,WAC6B;CAC7B,MAAM,eAA4C,EAAE;AACpD,MAAK,MAAM,YAAY,UACrB,MAAK,MAAM,SAAS,kBAClB,MAAK,MAAM,CAAC,MAAM,cAAc,OAAO,QACpC,SAAS,UAAiD,EAAE,CAC9D,CACC,KAAI,UAAU,WAAW,WAAW,CAAE,cAAa,KAAK;EAAE;EAAM;EAAW,CAAC;AAIlF,QAAO;;AAGT,SAAgB,mCACd,cAC6B;AAC7B,QAAO,gDAAgD,aAAa,IAAI,SAAS,CAAC;;;AAIpF,SAAgB,iCAAiC,UAAmC;CAClF,MAAM,cAAcC,UAAAA,QAAK,KAAK,UAAU,WAAW;CACnD,MAAM,OAAwB,EAAE;AAChC,KAAI,CAACD,QAAAA,QAAG,WAAW,YAAY,CAAE,QAAO;AAExC,MAAK,MAAM,SAASA,QAAAA,QAAG,YAAY,aAAa,EAAE,eAAe,MAAM,CAAC,EAAE;AACxE,MAAI,CAAC,MAAM,aAAa,IAAI,CAAC,MAAM,KAAK,WAAW,UAAU,CAAE;EAC/D,MAAM,eAAeC,UAAAA,QAAK,KAAK,aAAa,MAAM,MAAM,eAAe;AACvE,MAAI,CAACD,QAAAA,QAAG,WAAW,aAAa,CAAE;EAClC,MAAM,WAAW,SAAS,aAAa;EACvC,MAAM,OAAO,SAAS;EACtB,MAAM,UAAU,SAAS;AACzB,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,SAAU;AAC7D,MAAI,CAAC,KAAK,WAAW,kBAAkB,CAAE;AACzC,OAAK,QAAQ;;AAGf,QAAO;;AAGT,SAAgB,qBAAqB,SAIjB;CAClB,MAAM,gBAAgBC,UAAAA,QAAK,KAAK,QAAQ,UAAU,sBAAsB;CAIxE,MAAM,iBAAA,GAAA,KAAA,OAHkBD,QAAAA,QAAG,aAAa,eAAe,OAAO,CAAC,CAG/B,WAAW,EAAE;CAK7C,MAAM,QAAQ,+BAJO,CACnB,GAAG,qBAAqB,QAAQ,iBAAiB,EACjDC,UAAAA,QAAK,KAAK,QAAQ,wBAAwB,mBAAmB,CAC9D,CACyD;CAC1D,MAAM,UAA2B,EAAE;CACnC,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,WAAW,WAAW,EAAE;GAC/B,MAAM,cAAc,KAAK,MAAM,EAAkB;GACjD,MAAM,cAAcA,UAAAA,QAAK,KAAK,QAAQ,UAAU,YAAY,aAAa,eAAe;AACxF,OAAI,CAACD,QAAAA,QAAG,WAAW,YAAY,EAAE;AAC/B,YAAQ,KAAK,KAAK;AAClB;;GAEF,MAAM,kBAAkB,SAAS,YAAY;AAC7C,OAAI,OAAO,gBAAgB,YAAY,UAAU;AAC/C,YAAQ,KAAK,KAAK;AAClB;;AAEF,WAAQ,QAAQ,gBAAgB;AAChC;;EAGF,MAAM,UAAU,cAAc;AAC9B,MAAI,YAAY,KAAA,GAAW;AACzB,WAAQ,KAAK,KAAK;AAClB;;AAEF,UAAQ,QAAQ,OAAO,QAAQ;;AAGjC,QAAO,OAAO,SAAS,iCAAiC,QAAQ,SAAS,CAAC;AAE1E,KAAI,QAAQ,SAAS,EACnB,OAAM,IAAI,MAAM,0CAA0C,QAAQ,KAAK,KAAK,GAAG;AAGjF,QAAO,cAAc,QAAQ;;AAG/B,SAAgB,cAAc,SAA2C;AACvE,QAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CAC7E;;AAGH,SAAgB,wBAAwB,QAAgB,SAAkC;CACxF,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;CAC/B,MAAM,WACJ,UAAU,YAAY,OAAO,UAAU,aAAa,WAChD,EAAE,GAAI,UAAU,UAAsC,GACtD,EAAE;AACR,UAAS,wBAAwB,cAAc,QAAQ;AACvD,WAAU,WAAW;AACrB,KAAI,UAAU,YAAY,KAAA,EAAW,WAAU,UAAU,EAAE;AAC3D,SAAA,GAAA,KAAA,WAAiB,WAAW,EAAE,WAAW,GAAG,CAAC;;AAG/C,SAAgB,mBAAmB,QAAiC;AAElE,QAAO,WAAA,GAAA,KAAA,OADiB,OAAO,CACJ,QAAQ;;AAGrC,SAAgB,YAAY,QAAiC;CAC3D,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;CAC/B,MAAM,QAAQ,UAAU,WAAW;AACnC,KAAI,UAAU,KAAA,EAAW,QAAO,UAAU,MAAM;AAChD,QAAO,UAAU,UAAU,QAAQ;;AAGrC,SAAgB,kBACd,SACA,eACA,QAAQ,oBACF;CACN,MAAM,WAAW,IAAI,IAAI,cAAc;CACvC,MAAM,UAAU,cAAc,QAAQ,SAAS,EAAE,QAAQ,SAAS;CAClE,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,QAAQ,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC;AAC3E,KAAI,QAAQ,SAAS,KAAK,SAAS,SAAS,GAAG;EAC7C,MAAM,UAAU,CACd,QAAQ,SAAS,IAAI,YAAY,QAAQ,KAAK,KAAK,KAAK,KAAA,GACxD,SAAS,SAAS,IAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAA,EAC5D,CAAC,OAAO,QAAQ;AACjB,QAAM,IAAI,MAAM,WAAW,MAAM,IAAI,QAAQ,KAAK,KAAK,CAAC,GAAG;;;AAW/D,SAAgB,oBAAoB,SAIwC;CAC1E,MAAM,YAAA,GAAA,KAAA,eAAyB,QAAQ,OAAO;CAC9C,MAAM,aAAA,GAAA,KAAA,OAAkB,QAAQ,OAAO;CACvC,MAAM,eAAe,UAAU,WAAW,0BAA0B,KAAA;CACpE,MAAM,QAAQ,UAAU,UAAU,WAAW,sBAAsB;CACnE,MAAM,WAAW,UAAU,UAAU,QAAQ;CAC7C,MAAM,YAAoC,EAAE;CAC5C,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,KAAK,EAAE,GAAG,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC;CACrF,MAAM,cAAc,CAAC,YAAY,qBAAqB;AAEtD,KAAI,SAAS,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,KAAA,EACvC,KAAI,SAAS,IAAI,WAAW,KAAK,KAAA,EAC/B,UAAS,IAAI,YAAY,SAAS,WAAW,GAAG,uBAAuB,EAAE,EAAE,CAAC,CAAC;KAE7E,UAAS,MAAM,CAAC,GAAG,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC,CAAC;AAI7D,MAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC,EAAE;EAC9E,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,WAAW,eAAe,MAAM,QAAQ,KAAA,MAAc,SAAS;EACrE,MAAM,SAAS,QAAQ,OAAO;AAE9B,MAAI,SAAS,KAAA,GAAW;AACtB,OAAI,YAAY,KAAA,GAAW;AACzB,aAAS,MAAM,CAAC,GAAG,aAAa,KAAK,EAAE,OAAO;AAC9C,aAAS,SAAS,CAAC,WAAW,KAAK,CAAC;cAC3B,YAAY,OAAQ,WAAU,KAAK;IAAE;IAAM,OAAO;IAAS;IAAQ,CAAC;AAC/E;;AAEF,MAAI,WAAW,KAAA,GAAW;AACxB,OAAI,YAAY,MAAM;AACpB,aAAS,SAAS,CAAC,GAAG,aAAa,KAAK,CAAC;AACzC,aAAS,SAAS,CAAC,WAAW,KAAK,CAAC;cAC3B,YAAY,KAAA,EAAW,WAAU,KAAK;IAAE;IAAM;IAAM,OAAO;IAAS,CAAC;AAChF;;AAEF,MAAI,YAAY,MAAM;AACpB,YAAS,MAAM,CAAC,GAAG,aAAa,KAAK,EAAE,OAAO;AAC9C,YAAS,SAAS,CAAC,WAAW,KAAK,CAAC;aAC3B,YAAY,UAAU,WAAW,KAC1C,WAAU,KAAK;GAAE;GAAM;GAAM,OAAO;GAAS;GAAQ,CAAC;;CAI1D,MAAM,SAAS,SAAS,SAAS,EAAE,WAAW,GAAG,CAAC;AAClD,QAAO;EAAE,QAAQ;EAAQ,SAAS,WAAW,QAAQ;EAAQ;EAAW"}
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\nexport function stripFeatureMarkerLines(source: string): string {\r\n return source\r\n .replace(/\\r\\n?/g, \"\\n\")\r\n .split(\"\\n\")\r\n .filter((line) => !/^[ \\t]*(?:\\/\\/|#)[ \\t]*m5k:[a-z-]+:(start|end)[ \\t]*$/.test(line))\r\n .join(\"\\n\");\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(stripFeatureMarkerLines(fs.readFileSync(filePath, \"utf8\"))) as Record<\r\n string,\r\n unknown\r\n >;\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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"}
@@ -3,6 +3,7 @@ declare const MANAGED_CATALOG_NAME = "m5kdev";
3
3
  declare const MANAGED_CATALOG_SPECIFIER = "catalog:m5kdev";
4
4
  type ConsumerCatalog = Record<string, string>;
5
5
  declare function walkPackageJsonFiles(directory: string, out?: string[]): string[];
6
+ declare function stripFeatureMarkerLines(source: string): string;
6
7
  declare function collectConsumerDependencyNamesFromManifests(manifests: readonly Record<string, unknown>[]): string[];
7
8
  declare function collectConsumerDependencyNames(packageFiles: readonly string[]): string[];
8
9
  interface CatalogProtocolDependency {
@@ -39,5 +40,5 @@ declare function mergeManagedCatalog(options: {
39
40
  conflicts: CatalogMergeConflict[];
40
41
  };
41
42
  //#endregion
42
- export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, collectOptionalBackendModulePins, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
43
+ export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, collectOptionalBackendModulePins, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, stripFeatureMarkerLines, walkPackageJsonFiles };
43
44
  //# sourceMappingURL=catalog.d.cts.map
@@ -26,22 +26,25 @@ async function scaffoldProject(initialOptions) {
26
26
  packageScope: require_src_strings.derivePackageScope(appSlug),
27
27
  betterAuthSecret: require_src_strings.createBetterAuthSecret()
28
28
  };
29
+ const platform = options.platform ?? "web";
30
+ if (platform === "landing" && options.testHarness) throw new Error("The e2e test harness requires a Webapp. Do not pass --with-test-harness with --platform landing.");
29
31
  const enabledFeatures = require_src_template.getEnabledFeatures({
30
- platform: options.platform ?? "web",
32
+ platform,
31
33
  testHarness: Boolean(options.testHarness),
32
34
  modules: options.modules ?? []
33
35
  });
34
36
  const manifest = require_src_template.loadTemplateManifest(templateDirectory);
37
+ const templateFeatures = require_src_template.withImpliedFeatures(enabledFeatures);
35
38
  const excludePrefixes = require_src_template.getExcludedFeaturePaths(manifest, enabledFeatures);
36
39
  const createdTargetDirectory = await require_src_fs.ensureDirectoryState(targetDirectory, options.force);
37
40
  try {
38
41
  await require_src_fs.copyTemplateDirectory(templateDirectory, targetDirectory, context, {
39
42
  excludePrefixes,
40
- enabledFeatures
43
+ enabledFeatures: templateFeatures
41
44
  });
42
45
  const renderedFiles = await require_src_fs.collectTemplateFiles(templateDirectory, context, {
43
46
  excludePrefixes,
44
- enabledFeatures
47
+ enabledFeatures: templateFeatures
45
48
  });
46
49
  await require_src_state.writeManagedState(targetDirectory, require_src_state.createManagedState({
47
50
  templateVersion: require_src_state.getCliVersion(),
@@ -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 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"}
1
+ {"version":3,"file":"create.cjs","names":["resolveCreateCommandOptions","slugifyAppName","path","getTemplateRoot","derivePackageScope","createBetterAuthSecret","getEnabledFeatures","loadTemplateManifest","withImpliedFeatures","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 {\r\n getEnabledFeatures,\r\n getExcludedFeaturePaths,\r\n loadTemplateManifest,\r\n withImpliedFeatures,\r\n} 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 platform = options.platform ?? \"web\";\r\n if (platform === \"landing\" && options.testHarness) {\r\n throw new Error(\r\n \"The e2e test harness requires a Webapp. Do not pass --with-test-harness with --platform landing.\"\r\n );\r\n }\r\n const enabledFeatures = getEnabledFeatures({\r\n platform,\r\n testHarness: Boolean(options.testHarness),\r\n modules: options.modules ?? [],\r\n });\r\n const manifest = loadTemplateManifest(templateDirectory);\r\n const templateFeatures = withImpliedFeatures(enabledFeatures);\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: templateFeatures,\r\n });\r\n const renderedFiles = await collectTemplateFiles(templateDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures: templateFeatures,\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":";;;;;;;;;;;;;;AA4BA,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,WAAW,QAAQ,YAAY;AACrC,KAAI,aAAa,aAAa,QAAQ,YACpC,OAAM,IAAI,MACR,mGACD;CAEH,MAAM,kBAAkBC,qBAAAA,mBAAmB;EACzC;EACA,aAAa,QAAQ,QAAQ,YAAY;EACzC,SAAS,QAAQ,WAAW,EAAE;EAC/B,CAAC;CACF,MAAM,WAAWC,qBAAAA,qBAAqB,kBAAkB;CACxD,MAAM,mBAAmBC,qBAAAA,oBAAoB,gBAAgB;CAC7D,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,iBAAiB;GAClB,CAAC;EACF,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,mBAAmB,SAAS;GAC3E;GACA,iBAAiB;GAClB,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"}
@@ -80,6 +80,11 @@ async function diagnoseManagedRepo(options) {
80
80
  seenFeatures.add(feature);
81
81
  }
82
82
  const requiredPaths = new Set(manifest.requiredPaths);
83
+ if (require_src_template.hasProductStack(state.template.features)) {
84
+ requiredPaths.add("apps/shared/package.json");
85
+ requiredPaths.add("apps/server/package.json");
86
+ requiredPaths.add("apps/email/package.json");
87
+ }
83
88
  if (state.template.features.includes("webapp")) requiredPaths.add("apps/webapp/package.json");
84
89
  if (state.template.features.includes("expo")) requiredPaths.add("apps/expo/package.json");
85
90
  if (state.template.features.includes("test-harness")) requiredPaths.add("apps/e2e/package.json");
@@ -1 +1 @@
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":";;;;;;;;;;;;;;;;;;;AAoBA,MAAM,iBAAA,GAAA,UAAA,WAA0BA,mBAAAA,SAAS;AACzC,MAAM,0BAA0B,MAAU;AAQ1C,eAAe,OAAO,UAAoC;AACxD,QAAOC,iBAAAA,QACJ,OAAO,SAAS,CAChB,WAAW,KAAK,CAChB,YAAY,MAAM;;AAGvB,eAAsB,oBAAoB,SAAmD;CAC3F,MAAM,cAA4B,EAAE;CACpC,IAAI,QAAQ,QAAQ;AACpB,KAAI,CAAC,MACH,KAAI;AACF,UAAQ,MAAMC,kBAAAA,iBAAiB,QAAQ,SAAS;UACzC,OAAO;AACd,cAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS,iBAAiB,QAAQ,MAAM,UAAU;GAClD,MAAM;GACN,YAAY;GACb,CAAC;AACF,SAAOC,wBAAAA,uBAAuB,YAAY;;CAI9C,MAAM,aAAaC,kBAAAA,eAAe;AAClC,KAAI,MAAM,SAAS,SAAA,cACjB,aAAY,KAAK;EACf,MAAM;EACN,UAAU;EACV,SAAS,8BAA8B,MAAM,SAAS;EACtD,MAAM;EACP,CAAC;AAEJ,KAAI,CAAC,OAAA,QAAO,MAAM,MAAM,SAAS,QAAQ,CACvC,aAAY,KAAK;EACf,MAAM;EACN,UAAU;EACV,SAAS,6BAA6B,MAAM,SAAS;EACrD,MAAM;EACP,CAAC;UACO,OAAA,QAAO,GAAG,MAAM,SAAS,SAAS,WAAW,CACtD,aAAY,KAAK;EACf,MAAM;EACN,UAAU;EACV,SAAS,iCAAiC,MAAM,SAAS,QAAQ,2BAA2B,WAAW;EACvG,YAAY,8BAA8B,MAAM,SAAS,QAAQ;EAClE,CAAC;UACO,OAAA,QAAO,GAAG,MAAM,SAAS,SAAS,WAAW,CACtD,aAAY,KAAK;EACf,MAAM;EACN,UAAU;EACV,SAAS,YAAY,WAAW,uCAAuC,MAAM,SAAS,QAAQ;EAC9F,YAAY,0CAA0C,WAAW;EAClE,CAAC;CAGJ,MAAM,WAAWC,qBAAAA,qBAAqBC,kBAAAA,iBAAiB,CAAC;CACxD,MAAM,+BAAe,IAAI,KAAa;AACtC,MAAK,MAAM,WAAW,MAAM,SAAS,UAAU;AAC7C,MAAI,aAAa,IAAI,QAAQ,CAC3B,aAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS,WAAW,QAAQ;GAC5B,MAAM;GACP,CAAC;WACO,EAAE,WAAW,SAAS,UAC/B,aAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS,4BAA4B;GACrC,MAAM;GACP,CAAC;AAEJ,eAAa,IAAI,QAAQ;;CAE3B,MAAM,gBAAgB,IAAI,IAAI,SAAS,cAAc;AACrD,KAAI,MAAM,SAAS,SAAS,SAAS,SAAS,CAAE,eAAc,IAAI,2BAA2B;AAC7F,KAAI,MAAM,SAAS,SAAS,SAAS,OAAO,CAAE,eAAc,IAAI,yBAAyB;AACzF,KAAI,MAAM,SAAS,SAAS,SAAS,eAAe,CAAE,eAAc,IAAI,wBAAwB;AAChG,MAAK,MAAM,gBAAgB,eAAe;EACxC,MAAM,UAAU,MAAMC,oBAAAA,sBAAsB,QAAQ,UAAU,aAAa;AAC3E,MAAI,QACF,aAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS,gDAAgD,QAAQ;GACjE,MAAM;GACP,CAAC;WACO,CAAE,MAAM,OAAOC,UAAAA,QAAK,KAAK,QAAQ,UAAU,aAAa,CAAC,CAClE,aAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS;GACT,MAAM;GACN,YACE;GACH,CAAC;;CAIN,MAAM,wBAAwB;CAC9B,MAAM,gBAAgBA,UAAAA,QAAK,KAAK,QAAQ,UAAU,sBAAsB;AAExE,KAAI,CADqB,MAAMD,oBAAAA,sBAAsB,QAAQ,UAAU,sBAAsB,IACnE,MAAM,OAAO,cAAc,CACnD,KAAI;EACF,MAAM,kBAAkB,MAAMN,iBAAAA,QAAG,SAAS,eAAe,OAAO;EAChE,MAAM,gBAAgBQ,oBAAAA,YAAY,gBAAgB;EAClD,MAAM,iBAAiBC,oBAAAA,mBAAmB,gBAAgB;AAC1D,OAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,MAAM,QAAQ,EAAE;AAC5D,OAAI,CAAC,OAAA,QAAO,WAAW,SAAS,CAC9B,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS,GAAG,KAAK,wCAAwC,SAAS;IAClE,MAAM;IACP,CAAC;AAEJ,OAAI,EAAE,QAAQ,eACZ,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS,yBAAyB,KAAK;IACvC,MAAM;IACN,YAAY,WAAW,KAAK,IAAI,SAAS;IAC1C,CAAC;YACO,cAAc,UAAU,SACjC,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS,GAAG,KAAK,MAAM,cAAc,MAAM,6BAA6B,SAAS;IACjF,MAAM;IACN,YAAY,OAAO,KAAK,MAAM,SAAS;IACxC,CAAC;;EAIN,MAAM,gBAAgBF,UAAAA,QAAK,KAAK,QAAQ,UAAU,OAAO;EACzD,MAAM,eAAe,CAACA,UAAAA,QAAK,KAAK,QAAQ,UAAU,eAAe,CAAC;AAClE,MAAI,MAAM,OAAO,cAAc,CAAE,cAAa,KAAK,GAAGG,oBAAAA,qBAAqB,cAAc,CAAC;AAC1F,OAAK,MAAM,EAAE,MAAM,eAAeC,oBAAAA,mCAAmC,aAAa,EAAE;GAClF,MAAM,UACJ,cAAcC,oBAAAA,4BACV,gBACA,cAAc,aACZ,iBACA,KAAA;AACR,OAAI,YAAY,KAAA,EAAW;AAC3B,OAAI,EAAE,QAAQ,SACZ,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS,GAAG,KAAK,QAAQ,UAAU;IACnC,MAAM;IACP,CAAC;;UAGC,OAAO;AACd,cAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS,iBAAiB,QAAQ,MAAM,UAAU;GAClD,MAAM;GACP,CAAC;;AAIN,MAAK,MAAM,CAAC,cAAc,aAAa,OAAO,QAAQ,MAAM,MAAM,EAAE;AAClE,MAAI,SAAS,WAAW,SAAU;EAClC,MAAM,WAAWL,UAAAA,QAAK,KAAK,QAAQ,UAAU,aAAa;EAC1D,MAAM,UAAU,MAAMD,oBAAAA,sBAAsB,QAAQ,UAAU,aAAa;AAC3E,MAAI,SAAS;AACX,eAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS,uCAAuC,QAAQ;IACxD,MAAM;IACP,CAAC;AACF;;EAEF,MAAM,OAAO,MAAMN,iBAAAA,QAAG,MAAM,SAAS,CAAC,YAAY,KAAK;AACvD,MAAI,CAAC,MAAM;AACT,OAAI,SAAS,WAAW,SACtB,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS;IACT,MAAM;IACP,CAAC;AAEJ;;AAEF,MAAI,CAAC,KAAK,QAAQ,CAAE;EACpB,MAAM,UAAU,MAAMA,iBAAAA,QAAG,SAAS,SAAS;AAC3C,MAAI,SAAS,UAAUa,kBAAAA,OAAO,QAAQ,KAAK,SAAS,OAClD,aAAY,KAAK;GACf,MAAM;GACN,UAAU;GACV,SAAS;GACT,MAAM;GACP,CAAC;AAEJ,MAAI,SAAS,SAAS,QAAQ;GAC5B,MAAM,SAAS,QAAQ,SAAS,OAAO;AACvC,OACE,iFAAiF,KAC/E,OACD,CAED,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS;IACT,MAAM;IACP,CAAC;AAEJ,OAAI,4BAA4B,KAAK,OAAO,CAC1C,aAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SAAS;IACT,MAAM;IACP,CAAC;;;CAKR,MAAM,UAAUC,gCAAAA,qBAAqB,OAAO,WAAW;AACvD,MAAK,MAAM,aAAa,QACtB,aAAY,KAAK;EACf,MAAM;EACN,UAAU;EACV,SAAS,UAAU;EACnB,aAAa,UAAU;EACvB,OAAO,UAAU;EACjB,YAAY,8BAA8B,WAAW;EACtD,CAAC;AAEJ,aAAY,KACV,GAAI,MAAMC,gCAAAA,uBAAuB;EAC/B,UAAU,QAAQ;EAClB;EACA,SAAS,IAAIC,oBAAAA,UAAU,QAAQ,SAAS;EACzC,CAAC,CACH;AAED,KAAI,QAAQ,MAAM;EAChB,MAAM,cAAcT,UAAAA,QAAK,KAAK,QAAQ,UAAU,eAAe;EAC/D,IAAI;AACJ,MAAI;AACF,qBAAkB,KAAK,MAAM,MAAMP,iBAAAA,QAAG,SAAS,aAAa,OAAO,CAAC;WAG7D,OAAO;AACd,eAAY,KAAK;IACf,MAAM;IACN,UAAU;IACV,SACE,iBAAiB,QACb,gCAAgC,MAAM,YACtC;IACN,MAAM;IACP,CAAC;;AAEJ,MAAI,gBACF,MAAK,MAAM,UAAU;GAAC;GAAe;GAAQ;GAAQ,EAAW;AAC9D,OAAI,CAAC,gBAAgB,UAAU,QAAS;AACxC,OAAI;AACF,UAAM,cAAc,QAAQ,CAAC,OAAO,EAAE;KACpC,KAAK,QAAQ;KACb,KAAK,QAAQ;KACb,SAAS;KACV,CAAC;YACK,OAAO;AACd,gBAAY,KAAK;KACf,MAAM,WAAW,OAAO,QAAQ,KAAK,IAAI,CAAC,aAAa,CAAC;KACxD,UAAU;KACV,SAAS,GAAG,OAAO,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KACpF,MAAM;KACP,CAAC;;;;AAMV,QAAOE,wBAAAA,uBAAuB,YAAY"}
1
+ {"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","hasProductStack","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 { hasProductStack, 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 (hasProductStack(state.template.features)) {\r\n requiredPaths.add(\"apps/shared/package.json\");\r\n requiredPaths.add(\"apps/server/package.json\");\r\n requiredPaths.add(\"apps/email/package.json\");\r\n }\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
@@ -47,8 +47,7 @@ async function collectTemplateFiles(templateDirectory, context, options = {}, re
47
47
  const targetName = require_src_strings.renderTemplate(entry.name.endsWith(".tpl") ? entry.name.slice(0, -4) : entry.name, context);
48
48
  const relativePath = relativeBase ? `${relativeBase}/${targetName}` : targetName;
49
49
  if (relativePath === "template.manifest.json") continue;
50
- const asDirPrefix = `${relativePath}/`;
51
- if (excludePrefixes.some((prefix) => relativePath === prefix || asDirPrefix === prefix || relativePath.startsWith(prefix))) continue;
50
+ if (excludePrefixes.some((prefix) => matchesExcludedPrefix(relativePath, prefix))) continue;
52
51
  if (entry.isDirectory()) {
53
52
  files.push(...await collectTemplateFiles(sourcePath, context, options, relativePath));
54
53
  continue;
@@ -99,6 +98,10 @@ function isTextFile(fileName) {
99
98
  if (TEXT_BASENAMES.has(withoutTpl)) return true;
100
99
  return TEXT_EXTENSIONS.has(node_path.default.extname(withoutTpl));
101
100
  }
101
+ function matchesExcludedPrefix(relativePath, prefix) {
102
+ if (prefix.endsWith("/")) return relativePath === prefix.slice(0, -1) || relativePath.startsWith(prefix);
103
+ return relativePath === prefix || relativePath.startsWith(`${prefix}/`);
104
+ }
102
105
  /**
103
106
  * Removes feature marker lines. Blocks for disabled features are dropped with
104
107
  * their content; blocks for enabled features keep the content, lose the markers.
@@ -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 \".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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"}
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 if (excludePrefixes.some((prefix) => matchesExcludedPrefix(relativePath, prefix))) {\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\nfunction matchesExcludedPrefix(relativePath: string, prefix: string): boolean {\r\n if (prefix.endsWith(\"/\")) {\r\n return relativePath === prefix.slice(0, -1) || relativePath.startsWith(prefix);\r\n }\r\n return relativePath === prefix || relativePath.startsWith(`${prefix}/`);\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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"}
package/dist/src/init.cjs CHANGED
@@ -75,9 +75,10 @@ async function initializeManagedRepo(options) {
75
75
  const enabledFeatures = await inferFeatures(options.repoRoot);
76
76
  const templateRoot = require_src_paths.getTemplateRoot();
77
77
  const manifest = require_src_template.loadTemplateManifest(templateRoot);
78
+ const templateFeatures = require_src_template.withImpliedFeatures(enabledFeatures);
78
79
  const renderedFiles = await require_src_fs.collectTemplateFiles(templateRoot, context, {
79
80
  excludePrefixes: require_src_template.getExcludedFeaturePaths(manifest, enabledFeatures),
80
- enabledFeatures
81
+ enabledFeatures: templateFeatures
81
82
  });
82
83
  const version = require_src_state.getCliVersion();
83
84
  const state = require_src_state.createManagedState({
@@ -1 +1 @@
1
- {"version":3,"file":"init.cjs","names":["path","fs","derivePackageScope","toDisplayName","readline","STATE_FILE_NAME","getTemplateRoot","loadTemplateManifest","collectTemplateFiles","getExcludedFeaturePaths","getCliVersion","createManagedState","diagnoseManagedRepo","writeManagedState"],"sources":["../../src/init.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { diagnoseManagedRepo } from \"./doctor\";\r\nimport { collectTemplateFiles } from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { createManagedState, getCliVersion, STATE_FILE_NAME, writeManagedState } from \"./state\";\r\nimport { derivePackageScope, toDisplayName } from \"./strings\";\r\nimport { getExcludedFeaturePaths, loadTemplateManifest } from \"./template\";\r\nimport type { TemplateContext } from \"./types\";\r\n\r\nexport interface InitCommandOptions {\r\n repoRoot: string;\r\n yes: boolean;\r\n force: boolean;\r\n}\r\n\r\nasync function inferPackageScope(repoRoot: string, fallbackSlug: string): Promise<string> {\r\n const appsRoot = path.join(repoRoot, \"apps\");\r\n const scopes = new Map<string, number>();\r\n for (const entry of await fs.readdir(appsRoot, { withFileTypes: true }).catch(() => [])) {\r\n if (!entry.isDirectory()) continue;\r\n const packagePath = path.join(appsRoot, entry.name, \"package.json\");\r\n try {\r\n const manifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as { name?: string };\r\n const match = manifest.name?.match(/^(@[^/]+)\\//);\r\n if (match) scopes.set(match[1], (scopes.get(match[1]) ?? 0) + 1);\r\n } catch {\r\n // Optional app directories do not all need package manifests.\r\n }\r\n }\r\n return (\r\n [...scopes.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ??\r\n derivePackageScope(fallbackSlug)\r\n );\r\n}\r\n\r\nasync function inferContext(repoRoot: string): Promise<TemplateContext> {\r\n const rootPackage = JSON.parse(\r\n await fs.readFile(path.join(repoRoot, \"package.json\"), \"utf8\")\r\n ) as {\r\n name?: string;\r\n description?: string;\r\n };\r\n if (!rootPackage.name)\r\n throw new Error(\"Cannot infer the app slug: root package.json has no name.\");\r\n const appSlug = rootPackage.name.split(\"/\").at(-1);\r\n if (!appSlug) throw new Error(\"Cannot infer the app slug from root package.json.\");\r\n let appName = toDisplayName(appSlug.replace(/[-_]+/g, \" \"));\r\n const constantsPath = path.join(repoRoot, \"apps/shared/src/modules/app/app.constants.ts\");\r\n try {\r\n const constants = await fs.readFile(constantsPath, \"utf8\");\r\n appName = constants.match(/APP_NAME\\s*=\\s*[\"']([^\"']+)[\"']/)?.[1] ?? appName;\r\n } catch {\r\n // The required-path diagnostic will explain missing shared configuration.\r\n }\r\n return {\r\n appName,\r\n appDescription: rootPackage.description ?? `${appName} uses the m5kdev stack.`,\r\n appSlug,\r\n packageScope: await inferPackageScope(repoRoot, appSlug),\r\n betterAuthSecret: \"ignored-by-managed-state\",\r\n };\r\n}\r\n\r\nasync function inferFeatures(repoRoot: string): Promise<Set<string>> {\r\n const enabled = new Set<string>();\r\n const has = (relativePath: string) =>\r\n fs\r\n .access(path.join(repoRoot, relativePath))\r\n .then(() => true)\r\n .catch(() => false);\r\n if (await has(\"apps/webapp\")) enabled.add(\"webapp\");\r\n if (await has(\"apps/expo\")) enabled.add(\"expo\");\r\n if (await has(\"apps/e2e\")) enabled.add(\"test-harness\");\r\n return enabled;\r\n}\r\n\r\nasync function confirmInitialization(version: string, yes: boolean): Promise<void> {\r\n if (yes) return;\r\n if (!process.stdin.isTTY) {\r\n throw new Error(\r\n \"Initialization requires confirmation. Re-run with --yes in a non-interactive shell.\"\r\n );\r\n }\r\n const prompt = readline.createInterface({ input, output });\r\n try {\r\n const answer = await prompt.question(\r\n `Record this repository as template ${version} without applying older migrations? y/N: `\r\n );\r\n if (!/^y(es)?$/i.test(answer.trim())) throw new Error(\"Initialization cancelled.\");\r\n } finally {\r\n prompt.close();\r\n }\r\n}\r\n\r\nexport async function initializeManagedRepo(options: InitCommandOptions) {\r\n const statePath = path.join(options.repoRoot, STATE_FILE_NAME);\r\n if (\r\n !options.force &&\r\n (await fs\r\n .access(statePath)\r\n .then(() => true)\r\n .catch(() => false))\r\n ) {\r\n throw new Error(`${STATE_FILE_NAME} already exists. Use --force to re-baseline intentionally.`);\r\n }\r\n await fs.access(path.join(options.repoRoot, \"pnpm-workspace.yaml\"));\r\n const context = await inferContext(options.repoRoot);\r\n const enabledFeatures = await inferFeatures(options.repoRoot);\r\n const templateRoot = getTemplateRoot();\r\n const manifest = loadTemplateManifest(templateRoot);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n const renderedFiles = await collectTemplateFiles(templateRoot, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n const version = getCliVersion();\r\n const state = createManagedState({\r\n templateVersion: version,\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n });\r\n const report = await diagnoseManagedRepo({ repoRoot: options.repoRoot, state });\r\n if (!report.ok) return { initialized: false, state, report };\r\n\r\n await confirmInitialization(version, options.yes);\r\n await writeManagedState(options.repoRoot, state);\r\n return { initialized: true, state, report };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,eAAe,kBAAkB,UAAkB,cAAuC;CACxF,MAAM,WAAWA,UAAAA,QAAK,KAAK,UAAU,OAAO;CAC5C,MAAM,yBAAS,IAAI,KAAqB;AACxC,MAAK,MAAM,SAAS,MAAMC,iBAAAA,QAAG,QAAQ,UAAU,EAAE,eAAe,MAAM,CAAC,CAAC,YAAY,EAAE,CAAC,EAAE;AACvF,MAAI,CAAC,MAAM,aAAa,CAAE;EAC1B,MAAM,cAAcD,UAAAA,QAAK,KAAK,UAAU,MAAM,MAAM,eAAe;AACnE,MAAI;GAEF,MAAM,QADW,KAAK,MAAM,MAAMC,iBAAAA,QAAG,SAAS,aAAa,OAAO,CAAC,CAC5C,MAAM,MAAM,cAAc;AACjD,OAAI,MAAO,QAAO,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,GAAG,IAAI,KAAK,EAAE;UAC1D;;AAIV,QACE,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MACrEC,oBAAAA,mBAAmB,aAAa;;AAIpC,eAAe,aAAa,UAA4C;CACtE,MAAM,cAAc,KAAK,MACvB,MAAMD,iBAAAA,QAAG,SAASD,UAAAA,QAAK,KAAK,UAAU,eAAe,EAAE,OAAO,CAC/D;AAID,KAAI,CAAC,YAAY,KACf,OAAM,IAAI,MAAM,4DAA4D;CAC9E,MAAM,UAAU,YAAY,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG;AAClD,KAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oDAAoD;CAClF,IAAI,UAAUG,oBAAAA,cAAc,QAAQ,QAAQ,UAAU,IAAI,CAAC;CAC3D,MAAM,gBAAgBH,UAAAA,QAAK,KAAK,UAAU,+CAA+C;AACzF,KAAI;AAEF,aADkB,MAAMC,iBAAAA,QAAG,SAAS,eAAe,OAAO,EACtC,MAAM,kCAAkC,GAAG,MAAM;SAC/D;AAGR,QAAO;EACL;EACA,gBAAgB,YAAY,eAAe,GAAG,QAAQ;EACtD;EACA,cAAc,MAAM,kBAAkB,UAAU,QAAQ;EACxD,kBAAkB;EACnB;;AAGH,eAAe,cAAc,UAAwC;CACnE,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,OAAO,iBACXA,iBAAAA,QACG,OAAOD,UAAAA,QAAK,KAAK,UAAU,aAAa,CAAC,CACzC,WAAW,KAAK,CAChB,YAAY,MAAM;AACvB,KAAI,MAAM,IAAI,cAAc,CAAE,SAAQ,IAAI,SAAS;AACnD,KAAI,MAAM,IAAI,YAAY,CAAE,SAAQ,IAAI,OAAO;AAC/C,KAAI,MAAM,IAAI,WAAW,CAAE,SAAQ,IAAI,eAAe;AACtD,QAAO;;AAGT,eAAe,sBAAsB,SAAiB,KAA6B;AACjF,KAAI,IAAK;AACT,KAAI,CAAC,QAAQ,MAAM,MACjB,OAAM,IAAI,MACR,sFACD;CAEH,MAAM,SAASI,uBAAAA,QAAS,gBAAgB;EAAE,OAAA,aAAA;EAAO,QAAA,aAAA;EAAQ,CAAC;AAC1D,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,SAC1B,sCAAsC,QAAQ,2CAC/C;AACD,MAAI,CAAC,YAAY,KAAK,OAAO,MAAM,CAAC,CAAE,OAAM,IAAI,MAAM,4BAA4B;WAC1E;AACR,SAAO,OAAO;;;AAIlB,eAAsB,sBAAsB,SAA6B;CACvE,MAAM,YAAYJ,UAAAA,QAAK,KAAK,QAAQ,UAAUK,kBAAAA,gBAAgB;AAC9D,KACE,CAAC,QAAQ,SACR,MAAMJ,iBAAAA,QACJ,OAAO,UAAU,CACjB,WAAW,KAAK,CAChB,YAAY,MAAM,CAErB,OAAM,IAAI,MAAM,GAAGI,kBAAAA,gBAAgB,4DAA4D;AAEjG,OAAMJ,iBAAAA,QAAG,OAAOD,UAAAA,QAAK,KAAK,QAAQ,UAAU,sBAAsB,CAAC;CACnE,MAAM,UAAU,MAAM,aAAa,QAAQ,SAAS;CACpD,MAAM,kBAAkB,MAAM,cAAc,QAAQ,SAAS;CAC7D,MAAM,eAAeM,kBAAAA,iBAAiB;CACtC,MAAM,WAAWC,qBAAAA,qBAAqB,aAAa;CAEnD,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,cAAc,SAAS;EACtE,iBAFsBC,qBAAAA,wBAAwB,UAAU,gBAAgB;EAGxE;EACD,CAAC;CACF,MAAM,UAAUC,kBAAAA,eAAe;CAC/B,MAAM,QAAQC,kBAAAA,mBAAmB;EAC/B,iBAAiB;EACjB;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,SAAS,MAAMC,mBAAAA,oBAAoB;EAAE,UAAU,QAAQ;EAAU;EAAO,CAAC;AAC/E,KAAI,CAAC,OAAO,GAAI,QAAO;EAAE,aAAa;EAAO;EAAO;EAAQ;AAE5D,OAAM,sBAAsB,SAAS,QAAQ,IAAI;AACjD,OAAMC,kBAAAA,kBAAkB,QAAQ,UAAU,MAAM;AAChD,QAAO;EAAE,aAAa;EAAM;EAAO;EAAQ"}
1
+ {"version":3,"file":"init.cjs","names":["path","fs","derivePackageScope","toDisplayName","readline","STATE_FILE_NAME","getTemplateRoot","loadTemplateManifest","withImpliedFeatures","collectTemplateFiles","getExcludedFeaturePaths","getCliVersion","createManagedState","diagnoseManagedRepo","writeManagedState"],"sources":["../../src/init.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { diagnoseManagedRepo } from \"./doctor\";\r\nimport { collectTemplateFiles } from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { createManagedState, getCliVersion, STATE_FILE_NAME, writeManagedState } from \"./state\";\r\nimport { derivePackageScope, toDisplayName } from \"./strings\";\r\nimport { getExcludedFeaturePaths, loadTemplateManifest, withImpliedFeatures } from \"./template\";\r\nimport type { TemplateContext } from \"./types\";\r\n\r\nexport interface InitCommandOptions {\r\n repoRoot: string;\r\n yes: boolean;\r\n force: boolean;\r\n}\r\n\r\nasync function inferPackageScope(repoRoot: string, fallbackSlug: string): Promise<string> {\r\n const appsRoot = path.join(repoRoot, \"apps\");\r\n const scopes = new Map<string, number>();\r\n for (const entry of await fs.readdir(appsRoot, { withFileTypes: true }).catch(() => [])) {\r\n if (!entry.isDirectory()) continue;\r\n const packagePath = path.join(appsRoot, entry.name, \"package.json\");\r\n try {\r\n const manifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as { name?: string };\r\n const match = manifest.name?.match(/^(@[^/]+)\\//);\r\n if (match) scopes.set(match[1], (scopes.get(match[1]) ?? 0) + 1);\r\n } catch {\r\n // Optional app directories do not all need package manifests.\r\n }\r\n }\r\n return (\r\n [...scopes.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ??\r\n derivePackageScope(fallbackSlug)\r\n );\r\n}\r\n\r\nasync function inferContext(repoRoot: string): Promise<TemplateContext> {\r\n const rootPackage = JSON.parse(\r\n await fs.readFile(path.join(repoRoot, \"package.json\"), \"utf8\")\r\n ) as {\r\n name?: string;\r\n description?: string;\r\n };\r\n if (!rootPackage.name)\r\n throw new Error(\"Cannot infer the app slug: root package.json has no name.\");\r\n const appSlug = rootPackage.name.split(\"/\").at(-1);\r\n if (!appSlug) throw new Error(\"Cannot infer the app slug from root package.json.\");\r\n let appName = toDisplayName(appSlug.replace(/[-_]+/g, \" \"));\r\n const constantsPath = path.join(repoRoot, \"apps/shared/src/modules/app/app.constants.ts\");\r\n try {\r\n const constants = await fs.readFile(constantsPath, \"utf8\");\r\n appName = constants.match(/APP_NAME\\s*=\\s*[\"']([^\"']+)[\"']/)?.[1] ?? appName;\r\n } catch {\r\n // The required-path diagnostic will explain missing shared configuration.\r\n }\r\n return {\r\n appName,\r\n appDescription: rootPackage.description ?? `${appName} uses the m5kdev stack.`,\r\n appSlug,\r\n packageScope: await inferPackageScope(repoRoot, appSlug),\r\n betterAuthSecret: \"ignored-by-managed-state\",\r\n };\r\n}\r\n\r\nasync function inferFeatures(repoRoot: string): Promise<Set<string>> {\r\n const enabled = new Set<string>();\r\n const has = (relativePath: string) =>\r\n fs\r\n .access(path.join(repoRoot, relativePath))\r\n .then(() => true)\r\n .catch(() => false);\r\n if (await has(\"apps/webapp\")) enabled.add(\"webapp\");\r\n if (await has(\"apps/expo\")) enabled.add(\"expo\");\r\n if (await has(\"apps/e2e\")) enabled.add(\"test-harness\");\r\n return enabled;\r\n}\r\n\r\nasync function confirmInitialization(version: string, yes: boolean): Promise<void> {\r\n if (yes) return;\r\n if (!process.stdin.isTTY) {\r\n throw new Error(\r\n \"Initialization requires confirmation. Re-run with --yes in a non-interactive shell.\"\r\n );\r\n }\r\n const prompt = readline.createInterface({ input, output });\r\n try {\r\n const answer = await prompt.question(\r\n `Record this repository as template ${version} without applying older migrations? y/N: `\r\n );\r\n if (!/^y(es)?$/i.test(answer.trim())) throw new Error(\"Initialization cancelled.\");\r\n } finally {\r\n prompt.close();\r\n }\r\n}\r\n\r\nexport async function initializeManagedRepo(options: InitCommandOptions) {\r\n const statePath = path.join(options.repoRoot, STATE_FILE_NAME);\r\n if (\r\n !options.force &&\r\n (await fs\r\n .access(statePath)\r\n .then(() => true)\r\n .catch(() => false))\r\n ) {\r\n throw new Error(`${STATE_FILE_NAME} already exists. Use --force to re-baseline intentionally.`);\r\n }\r\n await fs.access(path.join(options.repoRoot, \"pnpm-workspace.yaml\"));\r\n const context = await inferContext(options.repoRoot);\r\n const enabledFeatures = await inferFeatures(options.repoRoot);\r\n const templateRoot = getTemplateRoot();\r\n const manifest = loadTemplateManifest(templateRoot);\r\n const templateFeatures = withImpliedFeatures(enabledFeatures);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n const renderedFiles = await collectTemplateFiles(templateRoot, context, {\r\n excludePrefixes,\r\n enabledFeatures: templateFeatures,\r\n });\r\n const version = getCliVersion();\r\n const state = createManagedState({\r\n templateVersion: version,\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n });\r\n const report = await diagnoseManagedRepo({ repoRoot: options.repoRoot, state });\r\n if (!report.ok) return { initialized: false, state, report };\r\n\r\n await confirmInitialization(version, options.yes);\r\n await writeManagedState(options.repoRoot, state);\r\n return { initialized: true, state, report };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,eAAe,kBAAkB,UAAkB,cAAuC;CACxF,MAAM,WAAWA,UAAAA,QAAK,KAAK,UAAU,OAAO;CAC5C,MAAM,yBAAS,IAAI,KAAqB;AACxC,MAAK,MAAM,SAAS,MAAMC,iBAAAA,QAAG,QAAQ,UAAU,EAAE,eAAe,MAAM,CAAC,CAAC,YAAY,EAAE,CAAC,EAAE;AACvF,MAAI,CAAC,MAAM,aAAa,CAAE;EAC1B,MAAM,cAAcD,UAAAA,QAAK,KAAK,UAAU,MAAM,MAAM,eAAe;AACnE,MAAI;GAEF,MAAM,QADW,KAAK,MAAM,MAAMC,iBAAAA,QAAG,SAAS,aAAa,OAAO,CAAC,CAC5C,MAAM,MAAM,cAAc;AACjD,OAAI,MAAO,QAAO,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,GAAG,IAAI,KAAK,EAAE;UAC1D;;AAIV,QACE,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MACrEC,oBAAAA,mBAAmB,aAAa;;AAIpC,eAAe,aAAa,UAA4C;CACtE,MAAM,cAAc,KAAK,MACvB,MAAMD,iBAAAA,QAAG,SAASD,UAAAA,QAAK,KAAK,UAAU,eAAe,EAAE,OAAO,CAC/D;AAID,KAAI,CAAC,YAAY,KACf,OAAM,IAAI,MAAM,4DAA4D;CAC9E,MAAM,UAAU,YAAY,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG;AAClD,KAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oDAAoD;CAClF,IAAI,UAAUG,oBAAAA,cAAc,QAAQ,QAAQ,UAAU,IAAI,CAAC;CAC3D,MAAM,gBAAgBH,UAAAA,QAAK,KAAK,UAAU,+CAA+C;AACzF,KAAI;AAEF,aADkB,MAAMC,iBAAAA,QAAG,SAAS,eAAe,OAAO,EACtC,MAAM,kCAAkC,GAAG,MAAM;SAC/D;AAGR,QAAO;EACL;EACA,gBAAgB,YAAY,eAAe,GAAG,QAAQ;EACtD;EACA,cAAc,MAAM,kBAAkB,UAAU,QAAQ;EACxD,kBAAkB;EACnB;;AAGH,eAAe,cAAc,UAAwC;CACnE,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,OAAO,iBACXA,iBAAAA,QACG,OAAOD,UAAAA,QAAK,KAAK,UAAU,aAAa,CAAC,CACzC,WAAW,KAAK,CAChB,YAAY,MAAM;AACvB,KAAI,MAAM,IAAI,cAAc,CAAE,SAAQ,IAAI,SAAS;AACnD,KAAI,MAAM,IAAI,YAAY,CAAE,SAAQ,IAAI,OAAO;AAC/C,KAAI,MAAM,IAAI,WAAW,CAAE,SAAQ,IAAI,eAAe;AACtD,QAAO;;AAGT,eAAe,sBAAsB,SAAiB,KAA6B;AACjF,KAAI,IAAK;AACT,KAAI,CAAC,QAAQ,MAAM,MACjB,OAAM,IAAI,MACR,sFACD;CAEH,MAAM,SAASI,uBAAAA,QAAS,gBAAgB;EAAE,OAAA,aAAA;EAAO,QAAA,aAAA;EAAQ,CAAC;AAC1D,KAAI;EACF,MAAM,SAAS,MAAM,OAAO,SAC1B,sCAAsC,QAAQ,2CAC/C;AACD,MAAI,CAAC,YAAY,KAAK,OAAO,MAAM,CAAC,CAAE,OAAM,IAAI,MAAM,4BAA4B;WAC1E;AACR,SAAO,OAAO;;;AAIlB,eAAsB,sBAAsB,SAA6B;CACvE,MAAM,YAAYJ,UAAAA,QAAK,KAAK,QAAQ,UAAUK,kBAAAA,gBAAgB;AAC9D,KACE,CAAC,QAAQ,SACR,MAAMJ,iBAAAA,QACJ,OAAO,UAAU,CACjB,WAAW,KAAK,CAChB,YAAY,MAAM,CAErB,OAAM,IAAI,MAAM,GAAGI,kBAAAA,gBAAgB,4DAA4D;AAEjG,OAAMJ,iBAAAA,QAAG,OAAOD,UAAAA,QAAK,KAAK,QAAQ,UAAU,sBAAsB,CAAC;CACnE,MAAM,UAAU,MAAM,aAAa,QAAQ,SAAS;CACpD,MAAM,kBAAkB,MAAM,cAAc,QAAQ,SAAS;CAC7D,MAAM,eAAeM,kBAAAA,iBAAiB;CACtC,MAAM,WAAWC,qBAAAA,qBAAqB,aAAa;CACnD,MAAM,mBAAmBC,qBAAAA,oBAAoB,gBAAgB;CAE7D,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,cAAc,SAAS;EACtE,iBAFsBC,qBAAAA,wBAAwB,UAAU,gBAAgB;EAGxE,iBAAiB;EAClB,CAAC;CACF,MAAM,UAAUC,kBAAAA,eAAe;CAC/B,MAAM,QAAQC,kBAAAA,mBAAmB;EAC/B,iBAAiB;EACjB;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,SAAS,MAAMC,mBAAAA,oBAAoB;EAAE,UAAU,QAAQ;EAAU;EAAO,CAAC;AAC/E,KAAI,CAAC,OAAO,GAAI,QAAO;EAAE,aAAa;EAAO;EAAO;EAAQ;AAE5D,OAAM,sBAAsB,SAAS,QAAQ,IAAI;AACjD,OAAMC,kBAAAA,kBAAkB,QAAQ,UAAU,MAAM;AAChD,QAAO;EAAE,aAAa;EAAM;EAAO;EAAQ"}
@@ -25,7 +25,7 @@ async function promptValue(question, fallback) {
25
25
  }
26
26
  function parsePlatform(value) {
27
27
  const normalized = value.trim().toLowerCase();
28
- if (normalized === "web" || normalized === "expo" || normalized === "both") return normalized;
28
+ if (normalized === "web" || normalized === "expo" || normalized === "both" || normalized === "landing") return normalized;
29
29
  }
30
30
  function formatBackendModulesPrompt(choices) {
31
31
  return `Backend modules — comma-separated ids, or none [${choices.map((choice) => choice.experimental ? `${choice.id} (experimental)` : choice.id).join(", ")}]`;
@@ -64,12 +64,31 @@ async function resolveCreateCommandOptions(options) {
64
64
  requireInteractive(resolved.yes);
65
65
  if (resolved.yes) resolved.platform = "web";
66
66
  else {
67
- const answer = await promptValue("App platform — web, expo, or both", "web");
67
+ const answer = await promptValue("App platform — web, expo, both, or landing", "web");
68
68
  const platform = parsePlatform(answer);
69
- if (!platform) throw new Error(`Invalid platform "${answer}". Use web, expo, or both.`);
69
+ if (!platform) throw new Error(`Invalid platform "${answer}". Use web, expo, both, or landing.`);
70
70
  resolved.platform = platform;
71
71
  }
72
72
  }
73
+ if (resolved.platform === "landing") {
74
+ if (resolved.testHarness) throw new Error("The e2e test harness requires a Webapp. Do not pass --with-test-harness with --platform landing.");
75
+ resolved.testHarness = false;
76
+ resolved.modules = resolved.modules ?? [];
77
+ return resolved;
78
+ }
79
+ if (resolved.testHarness === void 0 || resolved.modules === void 0) {
80
+ const extras = await resolveHarnessAndModuleOptions({
81
+ yes: Boolean(resolved.yes),
82
+ testHarness: resolved.testHarness,
83
+ modules: resolved.modules
84
+ });
85
+ resolved.testHarness = extras.testHarness;
86
+ resolved.modules = extras.modules;
87
+ }
88
+ return resolved;
89
+ }
90
+ async function resolveHarnessAndModuleOptions(options) {
91
+ const resolved = { ...options };
73
92
  if (resolved.testHarness === void 0) {
74
93
  requireInteractive(resolved.yes);
75
94
  if (resolved.yes) resolved.testHarness = false;
@@ -89,7 +108,10 @@ async function resolveCreateCommandOptions(options) {
89
108
  });
90
109
  }
91
110
  }
92
- return resolved;
111
+ return {
112
+ testHarness: Boolean(resolved.testHarness),
113
+ modules: resolved.modules ?? []
114
+ };
93
115
  }
94
116
  function pathBaseName(value) {
95
117
  return value.replace(/[\\/]+$/g, "").split(/[\\/]/).filter(Boolean).at(-1) || "M5 Starter";
@@ -97,6 +119,8 @@ function pathBaseName(value) {
97
119
  //#endregion
98
120
  exports.formatBackendModulesPrompt = formatBackendModulesPrompt;
99
121
  exports.parseBackendModulesAnswer = parseBackendModulesAnswer;
122
+ exports.parsePlatform = parsePlatform;
100
123
  exports.resolveCreateCommandOptions = resolveCreateCommandOptions;
124
+ exports.resolveHarnessAndModuleOptions = resolveHarnessAndModuleOptions;
101
125
 
102
126
  //# sourceMappingURL=prompts.cjs.map
@@ -1 +1 @@
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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"}
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\nexport function parsePlatform(value: string): AppPlatform | undefined {\r\n const normalized = value.trim().toLowerCase();\r\n if (\r\n normalized === \"web\" ||\r\n normalized === \"expo\" ||\r\n normalized === \"both\" ||\r\n normalized === \"landing\"\r\n ) {\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, both, or landing\", \"web\");\r\n const platform = parsePlatform(answer);\r\n if (!platform) {\r\n throw new Error(`Invalid platform \"${answer}\". Use web, expo, both, or landing.`);\r\n }\r\n resolved.platform = platform;\r\n }\r\n }\r\n\r\n if (resolved.platform === \"landing\") {\r\n if (resolved.testHarness) {\r\n throw new Error(\r\n \"The e2e test harness requires a Webapp. Do not pass --with-test-harness with --platform landing.\"\r\n );\r\n }\r\n resolved.testHarness = false;\r\n resolved.modules = resolved.modules ?? [];\r\n return resolved;\r\n }\r\n\r\n if (resolved.testHarness === undefined || resolved.modules === undefined) {\r\n const extras = await resolveHarnessAndModuleOptions({\r\n yes: Boolean(resolved.yes),\r\n testHarness: resolved.testHarness,\r\n modules: resolved.modules,\r\n });\r\n resolved.testHarness = extras.testHarness;\r\n resolved.modules = extras.modules;\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\nexport async function resolveHarnessAndModuleOptions(options: {\r\n yes: boolean;\r\n testHarness?: boolean;\r\n modules?: string[];\r\n}): Promise<{ testHarness: boolean; modules: string[] }> {\r\n const resolved = { ...options };\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 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 return { testHarness: Boolean(resolved.testHarness), modules: resolved.modules ?? [] };\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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"}
@@ -1,13 +1,22 @@
1
- import { CreateCommandOptions } from "./types.cjs";
1
+ import { AppPlatform, CreateCommandOptions } from "./types.cjs";
2
2
  import { BackendModuleChoice } from "./template.cjs";
3
3
 
4
4
  //#region src/prompts.d.ts
5
+ declare function parsePlatform(value: string): AppPlatform | undefined;
5
6
  declare function formatBackendModulesPrompt(choices: readonly BackendModuleChoice[]): string;
6
7
  declare function parseBackendModulesAnswer(options: {
7
8
  answer: string;
8
9
  choices: readonly BackendModuleChoice[];
9
10
  }): string[];
10
11
  declare function resolveCreateCommandOptions(options: CreateCommandOptions): Promise<CreateCommandOptions>;
12
+ declare function resolveHarnessAndModuleOptions(options: {
13
+ yes: boolean;
14
+ testHarness?: boolean;
15
+ modules?: string[];
16
+ }): Promise<{
17
+ testHarness: boolean;
18
+ modules: string[];
19
+ }>;
11
20
  //#endregion
12
- export { formatBackendModulesPrompt, parseBackendModulesAnswer, resolveCreateCommandOptions };
21
+ export { formatBackendModulesPrompt, parseBackendModulesAnswer, parsePlatform, resolveCreateCommandOptions, resolveHarnessAndModuleOptions };
13
22
  //# sourceMappingURL=prompts.d.cts.map
@@ -45,7 +45,7 @@ function toTemplateContext(state) {
45
45
  }
46
46
  async function renderForState(templateRoot, state) {
47
47
  const manifest = require_src_template.loadTemplateManifest(templateRoot);
48
- const enabledFeatures = new Set(state.template.features);
48
+ const enabledFeatures = require_src_template.withImpliedFeatures(new Set(state.template.features));
49
49
  return {
50
50
  manifest,
51
51
  files: await require_src_fs.collectTemplateFiles(templateRoot, toTemplateContext(state), {
@@ -90,10 +90,9 @@ async function mergeText(base, local, target) {
90
90
  async function reconcileTemplates(options) {
91
91
  const changes = new require_src_changes.ChangeSet(options.repoRoot);
92
92
  const target = await renderForState(options.targetTemplateRoot, options.state);
93
- const enabledFeatures = new Set(options.state.template.features);
94
93
  const targetState = require_src_state.createManagedState({
95
94
  templateVersion: options.targetVersion,
96
- enabledFeatures,
95
+ enabledFeatures: new Set(options.state.template.features),
97
96
  context: toTemplateContext(options.state),
98
97
  renderedFiles: target.files,
99
98
  manifest: target.manifest,