create-m5kdev 0.38.15 → 0.38.17

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
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.cjs","names":["execFile","fs","path","os","loadTemplateManifest","collectTemplateFiles","getExcludedFeaturePaths","getTemplateFilePolicy","ChangeSet","createManagedState","findRepositorySymlink","mergeManagedCatalog","sha256"],"sources":["../../src/reconcile.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { mergeManagedCatalog } from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { collectTemplateFiles } from \"./fs\";\r\nimport { createManagedState, type ManagedState, sha256 } from \"./state\";\r\nimport { getExcludedFeaturePaths, getTemplateFilePolicy, loadTemplateManifest } from \"./template\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport interface BaseTemplateProvider {\r\n getTemplateRoot(version: string): Promise<{ root: string; cleanup: () => Promise<void> }>;\r\n}\r\n\r\nexport const npmBaseTemplateProvider: BaseTemplateProvider = {\r\n async getTemplateRoot(version) {\r\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), \"m5kdev-template-\"));\r\n try {\r\n const { default: pacote } = await import(\"pacote\");\r\n await pacote.extract(`create-m5kdev@${version}`, directory);\r\n const root = path.join(directory, \"templates\", \"minimal-app\");\r\n await fs.access(path.join(root, \"template.manifest.json\"));\r\n return { root, cleanup: () => fs.rm(directory, { recursive: true, force: true }) };\r\n } catch (error) {\r\n await fs.rm(directory, { recursive: true, force: true });\r\n throw error;\r\n }\r\n },\r\n};\r\n\r\nfunction toTemplateContext(state: ManagedState): TemplateContext {\r\n return { ...state.template.context, betterAuthSecret: \"ignored-by-managed-state\" };\r\n}\r\n\r\nasync function renderForState(\r\n templateRoot: string,\r\n state: ManagedState\r\n): Promise<{\r\n files: RenderedTemplateFile[];\r\n manifest: ReturnType<typeof loadTemplateManifest>;\r\n}> {\r\n const manifest = loadTemplateManifest(templateRoot);\r\n const enabledFeatures = new Set(state.template.features);\r\n return {\r\n manifest,\r\n files: await collectTemplateFiles(templateRoot, toTemplateContext(state), {\r\n enabledFeatures,\r\n excludePrefixes: getExcludedFeaturePaths(manifest, enabledFeatures),\r\n ignoreContentFor: (relativePath) =>\r\n getTemplateFilePolicy(manifest, relativePath) === \"ignore\",\r\n }),\r\n };\r\n}\r\n\r\nasync function mergeText(base: Buffer, local: Buffer, target: Buffer): Promise<string | undefined> {\r\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), \"m5kdev-merge-\"));\r\n const localPath = path.join(directory, \"local\");\r\n const basePath = path.join(directory, \"base\");\r\n const targetPath = path.join(directory, \"target\");\r\n try {\r\n await Promise.all([\r\n fs.writeFile(localPath, local),\r\n fs.writeFile(basePath, base),\r\n fs.writeFile(targetPath, target),\r\n ]);\r\n try {\r\n const result = await execFileAsync(\r\n \"git\",\r\n [\"merge-file\", \"-p\", \"--diff3\", localPath, basePath, targetPath],\r\n {\r\n encoding: \"utf8\",\r\n }\r\n );\r\n return result.stdout;\r\n } catch (error) {\r\n const mergeError = error as Error & { code?: number };\r\n if (typeof mergeError.code === \"number\" && mergeError.code >= 1 && mergeError.code <= 127) {\r\n return undefined;\r\n }\r\n throw error;\r\n }\r\n } finally {\r\n await fs.rm(directory, { recursive: true, force: true });\r\n }\r\n}\r\n\r\nexport interface ReconcileResult {\r\n changes: ChangeSet;\r\n targetState: ManagedState;\r\n dependenciesChanged: boolean;\r\n}\r\n\r\nexport async function reconcileTemplates(options: {\r\n repoRoot: string;\r\n state: ManagedState;\r\n targetTemplateRoot: string;\r\n targetVersion: string;\r\n baseProvider?: BaseTemplateProvider;\r\n}): Promise<ReconcileResult> {\r\n const changes = new ChangeSet(options.repoRoot);\r\n const target = await renderForState(options.targetTemplateRoot, options.state);\r\n const enabledFeatures = new Set(options.state.template.features);\r\n const targetState = createManagedState({\r\n templateVersion: options.targetVersion,\r\n enabledFeatures,\r\n context: toTemplateContext(options.state),\r\n renderedFiles: target.files,\r\n manifest: target.manifest,\r\n appliedMigrations: options.state.appliedMigrations,\r\n });\r\n const targetFiles = new Map(target.files.map((file) => [file.relativePath, file]));\r\n const mergeCandidates: Array<{\r\n path: string;\r\n basePath: string;\r\n local: Buffer;\r\n target: RenderedTemplateFile;\r\n deletePath?: string;\r\n }> = [];\r\n let dependenciesChanged = false;\r\n\r\n const workspacePath = \"pnpm-workspace.yaml\";\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspacePath);\r\n if (workspaceSymlink) {\r\n changes.addConflict({\r\n path: workspacePath,\r\n reason: \"The managed workspace file is a symbolic link.\",\r\n });\r\n }\r\n const workspace = workspaceSymlink ? undefined : await changes.read(workspacePath);\r\n if (workspace) {\r\n const catalogMerge = mergeManagedCatalog({\r\n source: workspace.toString(\"utf8\"),\r\n base: options.state.catalog,\r\n target: targetState.catalog,\r\n });\r\n for (const conflict of catalogMerge.conflicts) {\r\n changes.addConflict({\r\n path: workspacePath,\r\n reason: `Catalog entry ${conflict.name} changed both locally and in the target template.`,\r\n });\r\n }\r\n if (catalogMerge.changed) {\r\n changes.addChange({\r\n kind: \"modify\",\r\n path: workspacePath,\r\n content: Buffer.from(catalogMerge.source),\r\n reason: \"Reconcile managed consumer catalog\",\r\n });\r\n dependenciesChanged = true;\r\n }\r\n }\r\n\r\n const paths = new Set([...Object.keys(options.state.files), ...targetFiles.keys()]);\r\n paths.delete(workspacePath);\r\n\r\n for (const rename of target.manifest.sync.renames ?? []) {\r\n const baseline = options.state.files[rename.from];\r\n const targetFile = targetFiles.get(rename.to);\r\n if (!baseline || !targetFile) {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: `Invalid template rename ${rename.from} -> ${rename.to}.`,\r\n });\r\n continue;\r\n }\r\n paths.delete(rename.from);\r\n paths.delete(rename.to);\r\n const sourcePath = path.join(options.repoRoot, rename.from);\r\n const destinationPath = path.join(options.repoRoot, rename.to);\r\n const sourceSymlink = await findRepositorySymlink(options.repoRoot, rename.from);\r\n const destinationSymlink = await findRepositorySymlink(options.repoRoot, rename.to);\r\n if (sourceSymlink || destinationSymlink) {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: \"A renamed managed path contains a symbolic link.\",\r\n });\r\n continue;\r\n }\r\n const sourceStat = await fs.lstat(sourcePath).catch(() => null);\r\n const destinationStat = await fs.lstat(destinationPath).catch(() => null);\r\n if (destinationStat && rename.from !== rename.to) {\r\n changes.addConflict({\r\n path: rename.to,\r\n reason: \"A template rename targets an existing app-owned path.\",\r\n });\r\n continue;\r\n }\r\n const local = sourceStat?.isFile() ? await fs.readFile(sourcePath) : undefined;\r\n if (!local) {\r\n if (targetState.files[rename.to]?.policy === \"ensure\") {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: targetFile.content,\r\n reason: `Restore renamed ensured path from ${rename.from}`,\r\n });\r\n }\r\n continue;\r\n }\r\n const localHash = sha256(local);\r\n const targetHash = sha256(targetFile.content);\r\n if (localHash === baseline.sha256) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: rename.from,\r\n reason: `Rename template path to ${rename.to}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: targetFile.content,\r\n reason: `Rename template path from ${rename.from}`,\r\n });\r\n } else if (localHash === targetHash) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: rename.from,\r\n reason: `Rename template path to ${rename.to}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: local,\r\n reason: `Rename template path from ${rename.from}`,\r\n });\r\n } else if (baseline.kind === \"binary\" || targetFile.kind === \"binary\") {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: \"Renamed binary content changed locally and in the target template.\",\r\n });\r\n } else {\r\n mergeCandidates.push({\r\n path: rename.to,\r\n basePath: rename.from,\r\n local,\r\n target: targetFile,\r\n deletePath: rename.from,\r\n });\r\n }\r\n }\r\n\r\n for (const relativePath of [...paths].sort((left, right) => left.localeCompare(right))) {\r\n const baseline = options.state.files[relativePath];\r\n const targetFile = targetFiles.get(relativePath);\r\n const policy = targetState.files[relativePath]?.policy ?? baseline?.policy ?? \"merge\";\r\n if (policy === \"ignore\") continue;\r\n\r\n const localPath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: `Managed path contains symbolic link ${symlink}.`,\r\n });\r\n continue;\r\n }\r\n const localStat = await fs.lstat(localPath).catch(() => null);\r\n const local = localStat?.isFile() ? await fs.readFile(localPath) : undefined;\r\n const localHash = local ? sha256(local) : undefined;\r\n const baseHash = baseline?.sha256;\r\n const targetHash = targetFile ? sha256(targetFile.content) : undefined;\r\n\r\n if (!baseline && targetFile) {\r\n if (!local) {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Add template path\",\r\n });\r\n } else if (localHash !== targetHash && policy !== \"ensure\") {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"The target template adds a path already owned by the app.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (baseline && !targetFile) {\r\n if (local && localHash === baseHash) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: relativePath,\r\n reason: \"Remove obsolete template path\",\r\n });\r\n } else if (local) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"The template removed a locally customized path.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (!baseline || !targetFile) continue;\r\n\r\n if (!local) {\r\n if (policy === \"ensure\") {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Restore ensured path\",\r\n });\r\n } else if (targetHash !== baseHash) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"A locally deleted path changed in the target template.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (localHash === targetHash || (localHash !== baseHash && targetHash === baseHash)) continue;\r\n if (localHash === baseHash) {\r\n changes.addChange({\r\n kind: \"modify\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Update unchanged template path\",\r\n });\r\n if (relativePath === \"package.json\" || relativePath.endsWith(\"/package.json\"))\r\n dependenciesChanged = true;\r\n continue;\r\n }\r\n if (baseline.kind === \"binary\" || targetFile.kind === \"binary\") {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"Binary content changed locally and in the target template.\",\r\n });\r\n continue;\r\n }\r\n mergeCandidates.push({ path: relativePath, basePath: relativePath, local, target: targetFile });\r\n }\r\n\r\n if (mergeCandidates.length > 0) {\r\n let baseRoot: Awaited<ReturnType<BaseTemplateProvider[\"getTemplateRoot\"]>> | undefined;\r\n try {\r\n baseRoot = await (options.baseProvider ?? npmBaseTemplateProvider).getTemplateRoot(\r\n options.state.template.version\r\n );\r\n const base = await renderForState(baseRoot.root, options.state);\r\n const baseFiles = new Map(base.files.map((file) => [file.relativePath, file]));\r\n for (const candidate of mergeCandidates) {\r\n const baseFile = baseFiles.get(candidate.basePath);\r\n if (\r\n !baseFile ||\r\n sha256(baseFile.content) !== options.state.files[candidate.basePath]?.sha256\r\n ) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: \"Historical template content does not match the stored baseline hash.\",\r\n });\r\n continue;\r\n }\r\n const merged = await mergeText(baseFile.content, candidate.local, candidate.target.content);\r\n if (merged === undefined) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: \"Three-way merge produced conflicts.\",\r\n });\r\n } else if (merged !== candidate.local.toString(\"utf8\")) {\r\n if (candidate.deletePath) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: candidate.deletePath,\r\n reason: `Rename template path to ${candidate.path}`,\r\n });\r\n }\r\n changes.addChange({\r\n kind: candidate.deletePath ? \"add\" : \"modify\",\r\n path: candidate.path,\r\n content: Buffer.from(merged),\r\n reason: \"Three-way merge local and template changes\",\r\n });\r\n if (candidate.path === \"package.json\" || candidate.path.endsWith(\"/package.json\")) {\r\n dependenciesChanged = true;\r\n }\r\n } else if (candidate.deletePath) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: candidate.deletePath,\r\n reason: `Rename template path to ${candidate.path}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: candidate.path,\r\n content: candidate.local,\r\n reason: `Rename template path from ${candidate.deletePath}`,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n for (const candidate of mergeCandidates) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: `Unable to load historical template ${options.state.template.version}: ${error instanceof Error ? error.message : String(error)}`,\r\n });\r\n }\r\n } finally {\r\n await baseRoot?.cleanup();\r\n }\r\n }\r\n\r\n return { changes, targetState, dependenciesChanged };\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"reconcile.cjs","names":["execFile","fs","path","os","loadTemplateManifest","withImpliedFeatures","collectTemplateFiles","getExcludedFeaturePaths","getTemplateFilePolicy","ChangeSet","createManagedState","findRepositorySymlink","mergeManagedCatalog","sha256"],"sources":["../../src/reconcile.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport os from \"node:os\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { mergeManagedCatalog } from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { collectTemplateFiles } from \"./fs\";\r\nimport { createManagedState, type ManagedState, sha256 } from \"./state\";\r\nimport {\r\n getExcludedFeaturePaths,\r\n getTemplateFilePolicy,\r\n loadTemplateManifest,\r\n withImpliedFeatures,\r\n} from \"./template\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport interface BaseTemplateProvider {\r\n getTemplateRoot(version: string): Promise<{ root: string; cleanup: () => Promise<void> }>;\r\n}\r\n\r\nexport const npmBaseTemplateProvider: BaseTemplateProvider = {\r\n async getTemplateRoot(version) {\r\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), \"m5kdev-template-\"));\r\n try {\r\n const { default: pacote } = await import(\"pacote\");\r\n await pacote.extract(`create-m5kdev@${version}`, directory);\r\n const root = path.join(directory, \"templates\", \"minimal-app\");\r\n await fs.access(path.join(root, \"template.manifest.json\"));\r\n return { root, cleanup: () => fs.rm(directory, { recursive: true, force: true }) };\r\n } catch (error) {\r\n await fs.rm(directory, { recursive: true, force: true });\r\n throw error;\r\n }\r\n },\r\n};\r\n\r\nfunction toTemplateContext(state: ManagedState): TemplateContext {\r\n return { ...state.template.context, betterAuthSecret: \"ignored-by-managed-state\" };\r\n}\r\n\r\nasync function renderForState(\r\n templateRoot: string,\r\n state: ManagedState\r\n): Promise<{\r\n files: RenderedTemplateFile[];\r\n manifest: ReturnType<typeof loadTemplateManifest>;\r\n}> {\r\n const manifest = loadTemplateManifest(templateRoot);\r\n const enabledFeatures = withImpliedFeatures(new Set(state.template.features));\r\n return {\r\n manifest,\r\n files: await collectTemplateFiles(templateRoot, toTemplateContext(state), {\r\n enabledFeatures,\r\n excludePrefixes: getExcludedFeaturePaths(manifest, enabledFeatures),\r\n ignoreContentFor: (relativePath) =>\r\n getTemplateFilePolicy(manifest, relativePath) === \"ignore\",\r\n }),\r\n };\r\n}\r\n\r\nasync function mergeText(base: Buffer, local: Buffer, target: Buffer): Promise<string | undefined> {\r\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), \"m5kdev-merge-\"));\r\n const localPath = path.join(directory, \"local\");\r\n const basePath = path.join(directory, \"base\");\r\n const targetPath = path.join(directory, \"target\");\r\n try {\r\n await Promise.all([\r\n fs.writeFile(localPath, local),\r\n fs.writeFile(basePath, base),\r\n fs.writeFile(targetPath, target),\r\n ]);\r\n try {\r\n const result = await execFileAsync(\r\n \"git\",\r\n [\"merge-file\", \"-p\", \"--diff3\", localPath, basePath, targetPath],\r\n {\r\n encoding: \"utf8\",\r\n }\r\n );\r\n return result.stdout;\r\n } catch (error) {\r\n const mergeError = error as Error & { code?: number };\r\n if (typeof mergeError.code === \"number\" && mergeError.code >= 1 && mergeError.code <= 127) {\r\n return undefined;\r\n }\r\n throw error;\r\n }\r\n } finally {\r\n await fs.rm(directory, { recursive: true, force: true });\r\n }\r\n}\r\n\r\nexport interface ReconcileResult {\r\n changes: ChangeSet;\r\n targetState: ManagedState;\r\n dependenciesChanged: boolean;\r\n}\r\n\r\nexport async function reconcileTemplates(options: {\r\n repoRoot: string;\r\n state: ManagedState;\r\n targetTemplateRoot: string;\r\n targetVersion: string;\r\n baseProvider?: BaseTemplateProvider;\r\n}): Promise<ReconcileResult> {\r\n const changes = new ChangeSet(options.repoRoot);\r\n const target = await renderForState(options.targetTemplateRoot, options.state);\r\n const targetState = createManagedState({\r\n templateVersion: options.targetVersion,\r\n enabledFeatures: new Set(options.state.template.features),\r\n context: toTemplateContext(options.state),\r\n renderedFiles: target.files,\r\n manifest: target.manifest,\r\n appliedMigrations: options.state.appliedMigrations,\r\n });\r\n const targetFiles = new Map(target.files.map((file) => [file.relativePath, file]));\r\n const mergeCandidates: Array<{\r\n path: string;\r\n basePath: string;\r\n local: Buffer;\r\n target: RenderedTemplateFile;\r\n deletePath?: string;\r\n }> = [];\r\n let dependenciesChanged = false;\r\n\r\n const workspacePath = \"pnpm-workspace.yaml\";\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspacePath);\r\n if (workspaceSymlink) {\r\n changes.addConflict({\r\n path: workspacePath,\r\n reason: \"The managed workspace file is a symbolic link.\",\r\n });\r\n }\r\n const workspace = workspaceSymlink ? undefined : await changes.read(workspacePath);\r\n if (workspace) {\r\n const catalogMerge = mergeManagedCatalog({\r\n source: workspace.toString(\"utf8\"),\r\n base: options.state.catalog,\r\n target: targetState.catalog,\r\n });\r\n for (const conflict of catalogMerge.conflicts) {\r\n changes.addConflict({\r\n path: workspacePath,\r\n reason: `Catalog entry ${conflict.name} changed both locally and in the target template.`,\r\n });\r\n }\r\n if (catalogMerge.changed) {\r\n changes.addChange({\r\n kind: \"modify\",\r\n path: workspacePath,\r\n content: Buffer.from(catalogMerge.source),\r\n reason: \"Reconcile managed consumer catalog\",\r\n });\r\n dependenciesChanged = true;\r\n }\r\n }\r\n\r\n const paths = new Set([...Object.keys(options.state.files), ...targetFiles.keys()]);\r\n paths.delete(workspacePath);\r\n\r\n for (const rename of target.manifest.sync.renames ?? []) {\r\n const baseline = options.state.files[rename.from];\r\n const targetFile = targetFiles.get(rename.to);\r\n if (!baseline || !targetFile) {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: `Invalid template rename ${rename.from} -> ${rename.to}.`,\r\n });\r\n continue;\r\n }\r\n paths.delete(rename.from);\r\n paths.delete(rename.to);\r\n const sourcePath = path.join(options.repoRoot, rename.from);\r\n const destinationPath = path.join(options.repoRoot, rename.to);\r\n const sourceSymlink = await findRepositorySymlink(options.repoRoot, rename.from);\r\n const destinationSymlink = await findRepositorySymlink(options.repoRoot, rename.to);\r\n if (sourceSymlink || destinationSymlink) {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: \"A renamed managed path contains a symbolic link.\",\r\n });\r\n continue;\r\n }\r\n const sourceStat = await fs.lstat(sourcePath).catch(() => null);\r\n const destinationStat = await fs.lstat(destinationPath).catch(() => null);\r\n if (destinationStat && rename.from !== rename.to) {\r\n changes.addConflict({\r\n path: rename.to,\r\n reason: \"A template rename targets an existing app-owned path.\",\r\n });\r\n continue;\r\n }\r\n const local = sourceStat?.isFile() ? await fs.readFile(sourcePath) : undefined;\r\n if (!local) {\r\n if (targetState.files[rename.to]?.policy === \"ensure\") {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: targetFile.content,\r\n reason: `Restore renamed ensured path from ${rename.from}`,\r\n });\r\n }\r\n continue;\r\n }\r\n const localHash = sha256(local);\r\n const targetHash = sha256(targetFile.content);\r\n if (localHash === baseline.sha256) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: rename.from,\r\n reason: `Rename template path to ${rename.to}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: targetFile.content,\r\n reason: `Rename template path from ${rename.from}`,\r\n });\r\n } else if (localHash === targetHash) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: rename.from,\r\n reason: `Rename template path to ${rename.to}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: rename.to,\r\n content: local,\r\n reason: `Rename template path from ${rename.from}`,\r\n });\r\n } else if (baseline.kind === \"binary\" || targetFile.kind === \"binary\") {\r\n changes.addConflict({\r\n path: rename.from,\r\n reason: \"Renamed binary content changed locally and in the target template.\",\r\n });\r\n } else {\r\n mergeCandidates.push({\r\n path: rename.to,\r\n basePath: rename.from,\r\n local,\r\n target: targetFile,\r\n deletePath: rename.from,\r\n });\r\n }\r\n }\r\n\r\n for (const relativePath of [...paths].sort((left, right) => left.localeCompare(right))) {\r\n const baseline = options.state.files[relativePath];\r\n const targetFile = targetFiles.get(relativePath);\r\n const policy = targetState.files[relativePath]?.policy ?? baseline?.policy ?? \"merge\";\r\n if (policy === \"ignore\") continue;\r\n\r\n const localPath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: `Managed path contains symbolic link ${symlink}.`,\r\n });\r\n continue;\r\n }\r\n const localStat = await fs.lstat(localPath).catch(() => null);\r\n const local = localStat?.isFile() ? await fs.readFile(localPath) : undefined;\r\n const localHash = local ? sha256(local) : undefined;\r\n const baseHash = baseline?.sha256;\r\n const targetHash = targetFile ? sha256(targetFile.content) : undefined;\r\n\r\n if (!baseline && targetFile) {\r\n if (!local) {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Add template path\",\r\n });\r\n } else if (localHash !== targetHash && policy !== \"ensure\") {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"The target template adds a path already owned by the app.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (baseline && !targetFile) {\r\n if (local && localHash === baseHash) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: relativePath,\r\n reason: \"Remove obsolete template path\",\r\n });\r\n } else if (local) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"The template removed a locally customized path.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (!baseline || !targetFile) continue;\r\n\r\n if (!local) {\r\n if (policy === \"ensure\") {\r\n changes.addChange({\r\n kind: \"add\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Restore ensured path\",\r\n });\r\n } else if (targetHash !== baseHash) {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"A locally deleted path changed in the target template.\",\r\n });\r\n }\r\n continue;\r\n }\r\n if (localHash === targetHash || (localHash !== baseHash && targetHash === baseHash)) continue;\r\n if (localHash === baseHash) {\r\n changes.addChange({\r\n kind: \"modify\",\r\n path: relativePath,\r\n content: targetFile.content,\r\n reason: \"Update unchanged template path\",\r\n });\r\n if (relativePath === \"package.json\" || relativePath.endsWith(\"/package.json\"))\r\n dependenciesChanged = true;\r\n continue;\r\n }\r\n if (baseline.kind === \"binary\" || targetFile.kind === \"binary\") {\r\n changes.addConflict({\r\n path: relativePath,\r\n reason: \"Binary content changed locally and in the target template.\",\r\n });\r\n continue;\r\n }\r\n mergeCandidates.push({ path: relativePath, basePath: relativePath, local, target: targetFile });\r\n }\r\n\r\n if (mergeCandidates.length > 0) {\r\n let baseRoot: Awaited<ReturnType<BaseTemplateProvider[\"getTemplateRoot\"]>> | undefined;\r\n try {\r\n baseRoot = await (options.baseProvider ?? npmBaseTemplateProvider).getTemplateRoot(\r\n options.state.template.version\r\n );\r\n const base = await renderForState(baseRoot.root, options.state);\r\n const baseFiles = new Map(base.files.map((file) => [file.relativePath, file]));\r\n for (const candidate of mergeCandidates) {\r\n const baseFile = baseFiles.get(candidate.basePath);\r\n if (\r\n !baseFile ||\r\n sha256(baseFile.content) !== options.state.files[candidate.basePath]?.sha256\r\n ) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: \"Historical template content does not match the stored baseline hash.\",\r\n });\r\n continue;\r\n }\r\n const merged = await mergeText(baseFile.content, candidate.local, candidate.target.content);\r\n if (merged === undefined) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: \"Three-way merge produced conflicts.\",\r\n });\r\n } else if (merged !== candidate.local.toString(\"utf8\")) {\r\n if (candidate.deletePath) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: candidate.deletePath,\r\n reason: `Rename template path to ${candidate.path}`,\r\n });\r\n }\r\n changes.addChange({\r\n kind: candidate.deletePath ? \"add\" : \"modify\",\r\n path: candidate.path,\r\n content: Buffer.from(merged),\r\n reason: \"Three-way merge local and template changes\",\r\n });\r\n if (candidate.path === \"package.json\" || candidate.path.endsWith(\"/package.json\")) {\r\n dependenciesChanged = true;\r\n }\r\n } else if (candidate.deletePath) {\r\n changes.addChange({\r\n kind: \"delete\",\r\n path: candidate.deletePath,\r\n reason: `Rename template path to ${candidate.path}`,\r\n });\r\n changes.addChange({\r\n kind: \"add\",\r\n path: candidate.path,\r\n content: candidate.local,\r\n reason: `Rename template path from ${candidate.deletePath}`,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n for (const candidate of mergeCandidates) {\r\n changes.addConflict({\r\n path: candidate.path,\r\n reason: `Unable to load historical template ${options.state.template.version}: ${error instanceof Error ? error.message : String(error)}`,\r\n });\r\n }\r\n } finally {\r\n await baseRoot?.cleanup();\r\n }\r\n }\r\n\r\n return { changes, targetState, dependenciesChanged };\r\n}\r\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_src_prompts = require("./prompts.cjs");
2
3
  const require_src_create = require("./create.cjs");
3
4
  const require_src_diagnostics = require("./diagnostics.cjs");
4
5
  const require_src_doctor = require("./doctor.cjs");
@@ -71,8 +72,11 @@ async function runCreate(parsed) {
71
72
  console.log("Next steps:");
72
73
  console.log(` cd ${result.targetDirectory}`);
73
74
  if (options.skipInstall) console.log(" pnpm install");
74
- console.log(" pnpm --filter ./apps/server drizzle:migrate");
75
- console.log(" pnpm dev");
75
+ if (options.platform === "landing") console.log(" pnpm dev");
76
+ else {
77
+ console.log(" pnpm --filter ./apps/server drizzle:migrate");
78
+ console.log(" pnpm dev");
79
+ }
76
80
  }
77
81
  async function runInit(parsed) {
78
82
  validateRepositoryCommand(parsed, [
@@ -107,12 +111,19 @@ async function runUpdate(parsed) {
107
111
  validateRepositoryCommand(parsed, [
108
112
  "dry-run",
109
113
  "skip-install",
110
- "json"
114
+ "json",
115
+ "platform",
116
+ "yes"
111
117
  ]);
118
+ const platformOption = getStringOption(parsed.options, "platform");
119
+ const platform = platformOption ? require_src_prompts.parsePlatform(platformOption) : void 0;
120
+ if (platformOption && !platform) throw new Error(`Invalid --platform "${platformOption}". Use web, expo, or both.`);
112
121
  const result = await require_src_update.updateManagedRepo({
113
122
  repoRoot: process.cwd(),
114
123
  dryRun: Boolean(parsed.options["dry-run"]),
115
- skipInstall: Boolean(parsed.options["skip-install"])
124
+ skipInstall: Boolean(parsed.options["skip-install"]),
125
+ yes: Boolean(parsed.options.yes),
126
+ platform
116
127
  });
117
128
  if (parsed.options.json) console.log(JSON.stringify(result, null, 2));
118
129
  else {
@@ -176,12 +187,9 @@ function validateOptions(parsed, allowedOptions) {
176
187
  for (const option of Object.keys(parsed.options)) if (!allowed.has(option)) throw new Error(`--${option} is not valid for m5kdev ${parsed.command}.`);
177
188
  }
178
189
  function toCreateCommandOptions(parsed) {
179
- const platform = getStringOption(parsed.options, "platform");
180
- if (platform && ![
181
- "web",
182
- "expo",
183
- "both"
184
- ].includes(platform)) throw new Error(`Invalid --platform "${platform}". Use web, expo, or both.`);
190
+ const platformOption = getStringOption(parsed.options, "platform");
191
+ const platform = platformOption ? require_src_prompts.parsePlatform(platformOption) : void 0;
192
+ if (platformOption && !platform) throw new Error(`Invalid --platform "${platformOption}". Use web, expo, both, or landing.`);
185
193
  return {
186
194
  targetDirectory: parsed.directory,
187
195
  appName: getStringOption(parsed.options, "name"),
@@ -216,7 +224,7 @@ function printHelp(command) {
216
224
  if (command === "create") {
217
225
  console.log(" --name <value> Set the app name");
218
226
  console.log(" --description <value> Set the app description");
219
- console.log(" --platform <value> web (default), expo, or both");
227
+ console.log(" --platform <value> web (default), expo, both, or landing");
220
228
  console.log(" --with-test-harness Include the e2e test harness");
221
229
  console.log(" --yes Accept defaults for missing prompts");
222
230
  console.log(" --force Allow a non-empty directory");
@@ -231,6 +239,8 @@ function printHelp(command) {
231
239
  console.log(" --json Emit machine-readable output");
232
240
  } else if (command === "update") {
233
241
  console.log(" --dry-run Build the complete plan without writes");
242
+ console.log(" --platform <value> landing-only escape hatch: web, expo, or both");
243
+ console.log(" --yes Skip module and e2e prompts for --platform");
234
244
  console.log(" --skip-install Apply sources without pnpm install");
235
245
  console.log(" --json Emit machine-readable output");
236
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runCli.cjs","names":["scaffoldProject","initializeManagedRepo","diagnoseManagedRepo","updateManagedRepo"],"sources":["../../src/runCli.ts"],"sourcesContent":["import { scaffoldProject } from \"./create\";\r\nimport { printDiagnosticReport } from \"./diagnostics\";\r\nimport { diagnoseManagedRepo } from \"./doctor\";\r\nimport { initializeManagedRepo } from \"./init\";\r\nimport type { CreateCommandOptions } from \"./types\";\r\nimport { updateManagedRepo } from \"./update\";\r\n\r\ninterface ParsedCli {\r\n command?: string;\r\n directory?: string;\r\n help?: boolean;\r\n options: Record<string, string | boolean>;\r\n}\r\n\r\nconst COMMANDS = new Set([\"create\", \"init\", \"doctor\", \"update\"]);\r\nconst STRING_FLAGS = new Set([\"name\", \"description\", \"platform\"]);\r\nconst BOOLEAN_FLAGS = new Set([\r\n \"yes\",\r\n \"force\",\r\n \"skip-install\",\r\n \"skip-git\",\r\n \"with-test-harness\",\r\n \"dry-run\",\r\n \"json\",\r\n \"full\",\r\n]);\r\n\r\nexport async function runCli(argv: readonly string[]): Promise<void> {\r\n const parsed = parseCli(argv);\r\n\r\n if (\r\n !parsed.command ||\r\n parsed.command === \"--help\" ||\r\n parsed.command === \"-h\" ||\r\n parsed.command === \"help\"\r\n ) {\r\n printHelp();\r\n return;\r\n }\r\n if (parsed.help) {\r\n printHelp(parsed.command);\r\n return;\r\n }\r\n\r\n switch (parsed.command) {\r\n case \"create\":\r\n await runCreate(parsed);\r\n return;\r\n case \"init\":\r\n await runInit(parsed);\r\n return;\r\n case \"doctor\":\r\n await runDoctor(parsed);\r\n return;\r\n case \"update\":\r\n await runUpdate(parsed);\r\n return;\r\n default:\r\n throw new Error(`Unknown command: ${parsed.command}`);\r\n }\r\n}\r\n\r\nasync function runCreate(parsed: ParsedCli): Promise<void> {\r\n validateOptions(parsed, [\r\n \"name\",\r\n \"description\",\r\n \"platform\",\r\n \"with-test-harness\",\r\n \"yes\",\r\n \"force\",\r\n \"skip-install\",\r\n \"skip-git\",\r\n ]);\r\n const options = toCreateCommandOptions(parsed);\r\n const result = await scaffoldProject(options);\r\n\r\n console.log(\"\");\r\n console.log(`Scaffolded ${result.context.appName} in ${result.targetDirectory}`);\r\n console.log(\"\");\r\n console.log(\"Next steps:\");\r\n console.log(` cd ${result.targetDirectory}`);\r\n if (options.skipInstall) console.log(\" pnpm install\");\r\n console.log(\" pnpm --filter ./apps/server drizzle:migrate\");\r\n console.log(\" pnpm dev\");\r\n}\r\n\r\nasync function runInit(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"yes\", \"force\", \"json\"]);\r\n const json = Boolean(parsed.options.json);\r\n const result = await initializeManagedRepo({\r\n repoRoot: process.cwd(),\r\n yes: Boolean(parsed.options.yes),\r\n force: Boolean(parsed.options.force),\r\n });\r\n if (json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n } else {\r\n printDiagnosticReport(result.report);\r\n console.log(\r\n result.initialized\r\n ? `Initialized .m5kdev.json at template ${result.state.template.version}.`\r\n : \"Initialization was not applied.\"\r\n );\r\n }\r\n if (!result.initialized) process.exitCode = 1;\r\n}\r\n\r\nasync function runDoctor(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"full\", \"json\"]);\r\n const report = await diagnoseManagedRepo({\r\n repoRoot: process.cwd(),\r\n full: Boolean(parsed.options.full),\r\n });\r\n if (parsed.options.json) console.log(JSON.stringify(report, null, 2));\r\n else printDiagnosticReport(report);\r\n if (!report.ok) process.exitCode = 1;\r\n}\r\n\r\nasync function runUpdate(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"dry-run\", \"skip-install\", \"json\"]);\r\n const result = await updateManagedRepo({\r\n repoRoot: process.cwd(),\r\n dryRun: Boolean(parsed.options[\"dry-run\"]),\r\n skipInstall: Boolean(parsed.options[\"skip-install\"]),\r\n });\r\n if (parsed.options.json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n } else {\r\n const action = result.dryRun ? \"Planned\" : result.applied ? \"Applied\" : \"Blocked\";\r\n console.log(`${action} update ${result.fromVersion} -> ${result.targetVersion}.`);\r\n for (const change of result.changes) {\r\n console.log(` ${change.kind.padEnd(6)} ${change.path} — ${change.reason}`);\r\n }\r\n for (const conflict of result.conflicts) {\r\n console.log(` conflict ${conflict.path} — ${conflict.reason}`);\r\n }\r\n if (result.migrations.length > 0) console.log(`Migrations: ${result.migrations.join(\", \")}`);\r\n if (result.installRequired) console.log(\"Dependencies changed. Run: pnpm install\");\r\n if (result.changes.length === 0 && result.conflicts.length === 0)\r\n console.log(\"Already up to date.\");\r\n }\r\n if (result.conflicts.length > 0) process.exitCode = 1;\r\n}\r\n\r\nexport function parseCli(argv: readonly string[]): ParsedCli {\r\n const normalizedArgv = normalizeCliArgv(argv);\r\n const [command, ...rest] = normalizedArgv;\r\n const options: Record<string, string | boolean> = {};\r\n let directory: string | undefined;\r\n let help = false;\r\n\r\n for (let index = 0; index < rest.length; index += 1) {\r\n const token = rest[index];\r\n if (token === \"--help\" || token === \"-h\") {\r\n help = true;\r\n continue;\r\n }\r\n if (!token.startsWith(\"--\")) {\r\n if (directory) throw new Error(`Unexpected positional argument: ${token}`);\r\n directory = token;\r\n continue;\r\n }\r\n\r\n const [rawKey, inlineValue] = token.slice(2).split(\"=\", 2);\r\n if (BOOLEAN_FLAGS.has(rawKey)) {\r\n if (inlineValue !== undefined) throw new Error(`--${rawKey} does not accept a value.`);\r\n options[rawKey] = true;\r\n continue;\r\n }\r\n if (!STRING_FLAGS.has(rawKey)) throw new Error(`Unknown option: --${rawKey}`);\r\n\r\n const nextValue = inlineValue ?? rest[index + 1];\r\n if (!nextValue || nextValue.startsWith(\"--\")) throw new Error(`Missing value for --${rawKey}`);\r\n options[rawKey] = nextValue;\r\n if (inlineValue === undefined) index += 1;\r\n }\r\n return { command, directory, help, options };\r\n}\r\n\r\nfunction normalizeCliArgv(argv: readonly string[]): readonly string[] {\r\n const [firstToken] = argv;\r\n if (!firstToken || COMMANDS.has(firstToken) || firstToken === \"--help\" || firstToken === \"-h\")\r\n return argv;\r\n if (firstToken === \"help\") return [\"--help\"];\r\n return [\"create\", ...argv];\r\n}\r\n\r\nfunction validateRepositoryCommand(parsed: ParsedCli, allowedOptions: readonly string[]): void {\r\n if (parsed.directory)\r\n throw new Error(\r\n `${parsed.command} operates on the current directory and accepts no directory argument.`\r\n );\r\n validateOptions(parsed, allowedOptions);\r\n}\r\n\r\nfunction validateOptions(parsed: ParsedCli, allowedOptions: readonly string[]): void {\r\n const allowed = new Set(allowedOptions);\r\n for (const option of Object.keys(parsed.options)) {\r\n if (!allowed.has(option))\r\n throw new Error(`--${option} is not valid for m5kdev ${parsed.command}.`);\r\n }\r\n}\r\n\r\nfunction toCreateCommandOptions(parsed: ParsedCli): CreateCommandOptions {\r\n const platform = getStringOption(parsed.options, \"platform\");\r\n if (platform && ![\"web\", \"expo\", \"both\"].includes(platform)) {\r\n throw new Error(`Invalid --platform \"${platform}\". Use web, expo, or both.`);\r\n }\r\n return {\r\n targetDirectory: parsed.directory,\r\n appName: getStringOption(parsed.options, \"name\"),\r\n appDescription: getStringOption(parsed.options, \"description\"),\r\n platform: platform as CreateCommandOptions[\"platform\"],\r\n testHarness: parsed.options[\"with-test-harness\"] ? true : undefined,\r\n yes: Boolean(parsed.options.yes),\r\n force: Boolean(parsed.options.force),\r\n skipInstall: Boolean(parsed.options[\"skip-install\"]),\r\n skipGit: Boolean(parsed.options[\"skip-git\"]),\r\n };\r\n}\r\n\r\nfunction getStringOption(\r\n options: Record<string, string | boolean>,\r\n key: string\r\n): string | undefined {\r\n const value = options[key];\r\n return typeof value === \"string\" ? value : undefined;\r\n}\r\n\r\nfunction printHelp(command?: string): void {\r\n console.log(\"m5kdev\");\r\n console.log(\"\");\r\n if (!command) {\r\n console.log(\"Usage:\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> [directory] [create options]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> init [--yes] [--force] [--json]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> doctor [--full] [--json]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> update [--dry-run] [--skip-install] [--json]\");\r\n console.log(\"\");\r\n console.log(\"Run m5kdev <command> --help for command options.\");\r\n return;\r\n }\r\n console.log(`Usage: m5kdev ${command}${command === \"create\" ? \" [directory]\" : \"\"} [options]`);\r\n console.log(\"\");\r\n if (command === \"create\") {\r\n console.log(\" --name <value> Set the app name\");\r\n console.log(\" --description <value> Set the app description\");\r\n console.log(\" --platform <value> web (default), expo, or both\");\r\n console.log(\" --with-test-harness Include the e2e test harness\");\r\n console.log(\" --yes Accept defaults for missing prompts\");\r\n console.log(\" --force Allow a non-empty directory\");\r\n console.log(\" --skip-install Skip pnpm install\");\r\n console.log(\" --skip-git Skip git init\");\r\n } else if (command === \"init\") {\r\n console.log(\" --yes Confirm non-interactively\");\r\n console.log(\" --force Replace existing managed state\");\r\n console.log(\" --json Emit machine-readable output\");\r\n } else if (command === \"doctor\") {\r\n console.log(\" --full Also run check-types, lint, and build scripts\");\r\n console.log(\" --json Emit machine-readable output\");\r\n } else if (command === \"update\") {\r\n console.log(\" --dry-run Build the complete plan without writes\");\r\n console.log(\" --skip-install Apply sources without pnpm install\");\r\n console.log(\" --json Emit machine-readable output\");\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"runCli.cjs","names":["scaffoldProject","initializeManagedRepo","diagnoseManagedRepo","parsePlatform","updateManagedRepo"],"sources":["../../src/runCli.ts"],"sourcesContent":["import { scaffoldProject } from \"./create\";\r\nimport { printDiagnosticReport } from \"./diagnostics\";\r\nimport { diagnoseManagedRepo } from \"./doctor\";\r\nimport { initializeManagedRepo } from \"./init\";\r\nimport { parsePlatform } from \"./prompts\";\r\nimport type { CreateCommandOptions } from \"./types\";\r\nimport { updateManagedRepo } from \"./update\";\r\n\r\ninterface ParsedCli {\r\n command?: string;\r\n directory?: string;\r\n help?: boolean;\r\n options: Record<string, string | boolean>;\r\n}\r\n\r\nconst COMMANDS = new Set([\"create\", \"init\", \"doctor\", \"update\"]);\r\nconst STRING_FLAGS = new Set([\"name\", \"description\", \"platform\"]);\r\nconst BOOLEAN_FLAGS = new Set([\r\n \"yes\",\r\n \"force\",\r\n \"skip-install\",\r\n \"skip-git\",\r\n \"with-test-harness\",\r\n \"dry-run\",\r\n \"json\",\r\n \"full\",\r\n]);\r\n\r\nexport async function runCli(argv: readonly string[]): Promise<void> {\r\n const parsed = parseCli(argv);\r\n\r\n if (\r\n !parsed.command ||\r\n parsed.command === \"--help\" ||\r\n parsed.command === \"-h\" ||\r\n parsed.command === \"help\"\r\n ) {\r\n printHelp();\r\n return;\r\n }\r\n if (parsed.help) {\r\n printHelp(parsed.command);\r\n return;\r\n }\r\n\r\n switch (parsed.command) {\r\n case \"create\":\r\n await runCreate(parsed);\r\n return;\r\n case \"init\":\r\n await runInit(parsed);\r\n return;\r\n case \"doctor\":\r\n await runDoctor(parsed);\r\n return;\r\n case \"update\":\r\n await runUpdate(parsed);\r\n return;\r\n default:\r\n throw new Error(`Unknown command: ${parsed.command}`);\r\n }\r\n}\r\n\r\nasync function runCreate(parsed: ParsedCli): Promise<void> {\r\n validateOptions(parsed, [\r\n \"name\",\r\n \"description\",\r\n \"platform\",\r\n \"with-test-harness\",\r\n \"yes\",\r\n \"force\",\r\n \"skip-install\",\r\n \"skip-git\",\r\n ]);\r\n const options = toCreateCommandOptions(parsed);\r\n const result = await scaffoldProject(options);\r\n\r\n console.log(\"\");\r\n console.log(`Scaffolded ${result.context.appName} in ${result.targetDirectory}`);\r\n console.log(\"\");\r\n console.log(\"Next steps:\");\r\n console.log(` cd ${result.targetDirectory}`);\r\n if (options.skipInstall) console.log(\" pnpm install\");\r\n if (options.platform === \"landing\") {\r\n console.log(\" pnpm dev\");\r\n } else {\r\n console.log(\" pnpm --filter ./apps/server drizzle:migrate\");\r\n console.log(\" pnpm dev\");\r\n }\r\n}\r\n\r\nasync function runInit(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"yes\", \"force\", \"json\"]);\r\n const json = Boolean(parsed.options.json);\r\n const result = await initializeManagedRepo({\r\n repoRoot: process.cwd(),\r\n yes: Boolean(parsed.options.yes),\r\n force: Boolean(parsed.options.force),\r\n });\r\n if (json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n } else {\r\n printDiagnosticReport(result.report);\r\n console.log(\r\n result.initialized\r\n ? `Initialized .m5kdev.json at template ${result.state.template.version}.`\r\n : \"Initialization was not applied.\"\r\n );\r\n }\r\n if (!result.initialized) process.exitCode = 1;\r\n}\r\n\r\nasync function runDoctor(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"full\", \"json\"]);\r\n const report = await diagnoseManagedRepo({\r\n repoRoot: process.cwd(),\r\n full: Boolean(parsed.options.full),\r\n });\r\n if (parsed.options.json) console.log(JSON.stringify(report, null, 2));\r\n else printDiagnosticReport(report);\r\n if (!report.ok) process.exitCode = 1;\r\n}\r\n\r\nasync function runUpdate(parsed: ParsedCli): Promise<void> {\r\n validateRepositoryCommand(parsed, [\"dry-run\", \"skip-install\", \"json\", \"platform\", \"yes\"]);\r\n const platformOption = getStringOption(parsed.options, \"platform\");\r\n const platform = platformOption ? parsePlatform(platformOption) : undefined;\r\n if (platformOption && !platform) {\r\n throw new Error(`Invalid --platform \"${platformOption}\". Use web, expo, or both.`);\r\n }\r\n const result = await updateManagedRepo({\r\n repoRoot: process.cwd(),\r\n dryRun: Boolean(parsed.options[\"dry-run\"]),\r\n skipInstall: Boolean(parsed.options[\"skip-install\"]),\r\n yes: Boolean(parsed.options.yes),\r\n platform,\r\n });\r\n if (parsed.options.json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n } else {\r\n const action = result.dryRun ? \"Planned\" : result.applied ? \"Applied\" : \"Blocked\";\r\n console.log(`${action} update ${result.fromVersion} -> ${result.targetVersion}.`);\r\n for (const change of result.changes) {\r\n console.log(` ${change.kind.padEnd(6)} ${change.path} — ${change.reason}`);\r\n }\r\n for (const conflict of result.conflicts) {\r\n console.log(` conflict ${conflict.path} — ${conflict.reason}`);\r\n }\r\n if (result.migrations.length > 0) console.log(`Migrations: ${result.migrations.join(\", \")}`);\r\n if (result.installRequired) console.log(\"Dependencies changed. Run: pnpm install\");\r\n if (result.changes.length === 0 && result.conflicts.length === 0)\r\n console.log(\"Already up to date.\");\r\n }\r\n if (result.conflicts.length > 0) process.exitCode = 1;\r\n}\r\n\r\nexport function parseCli(argv: readonly string[]): ParsedCli {\r\n const normalizedArgv = normalizeCliArgv(argv);\r\n const [command, ...rest] = normalizedArgv;\r\n const options: Record<string, string | boolean> = {};\r\n let directory: string | undefined;\r\n let help = false;\r\n\r\n for (let index = 0; index < rest.length; index += 1) {\r\n const token = rest[index];\r\n if (token === \"--help\" || token === \"-h\") {\r\n help = true;\r\n continue;\r\n }\r\n if (!token.startsWith(\"--\")) {\r\n if (directory) throw new Error(`Unexpected positional argument: ${token}`);\r\n directory = token;\r\n continue;\r\n }\r\n\r\n const [rawKey, inlineValue] = token.slice(2).split(\"=\", 2);\r\n if (BOOLEAN_FLAGS.has(rawKey)) {\r\n if (inlineValue !== undefined) throw new Error(`--${rawKey} does not accept a value.`);\r\n options[rawKey] = true;\r\n continue;\r\n }\r\n if (!STRING_FLAGS.has(rawKey)) throw new Error(`Unknown option: --${rawKey}`);\r\n\r\n const nextValue = inlineValue ?? rest[index + 1];\r\n if (!nextValue || nextValue.startsWith(\"--\")) throw new Error(`Missing value for --${rawKey}`);\r\n options[rawKey] = nextValue;\r\n if (inlineValue === undefined) index += 1;\r\n }\r\n return { command, directory, help, options };\r\n}\r\n\r\nfunction normalizeCliArgv(argv: readonly string[]): readonly string[] {\r\n const [firstToken] = argv;\r\n if (!firstToken || COMMANDS.has(firstToken) || firstToken === \"--help\" || firstToken === \"-h\")\r\n return argv;\r\n if (firstToken === \"help\") return [\"--help\"];\r\n return [\"create\", ...argv];\r\n}\r\n\r\nfunction validateRepositoryCommand(parsed: ParsedCli, allowedOptions: readonly string[]): void {\r\n if (parsed.directory)\r\n throw new Error(\r\n `${parsed.command} operates on the current directory and accepts no directory argument.`\r\n );\r\n validateOptions(parsed, allowedOptions);\r\n}\r\n\r\nfunction validateOptions(parsed: ParsedCli, allowedOptions: readonly string[]): void {\r\n const allowed = new Set(allowedOptions);\r\n for (const option of Object.keys(parsed.options)) {\r\n if (!allowed.has(option))\r\n throw new Error(`--${option} is not valid for m5kdev ${parsed.command}.`);\r\n }\r\n}\r\n\r\nfunction toCreateCommandOptions(parsed: ParsedCli): CreateCommandOptions {\r\n const platformOption = getStringOption(parsed.options, \"platform\");\r\n const platform = platformOption ? parsePlatform(platformOption) : undefined;\r\n if (platformOption && !platform) {\r\n throw new Error(`Invalid --platform \"${platformOption}\". Use web, expo, both, or landing.`);\r\n }\r\n return {\r\n targetDirectory: parsed.directory,\r\n appName: getStringOption(parsed.options, \"name\"),\r\n appDescription: getStringOption(parsed.options, \"description\"),\r\n platform,\r\n testHarness: parsed.options[\"with-test-harness\"] ? true : undefined,\r\n yes: Boolean(parsed.options.yes),\r\n force: Boolean(parsed.options.force),\r\n skipInstall: Boolean(parsed.options[\"skip-install\"]),\r\n skipGit: Boolean(parsed.options[\"skip-git\"]),\r\n };\r\n}\r\n\r\nfunction getStringOption(\r\n options: Record<string, string | boolean>,\r\n key: string\r\n): string | undefined {\r\n const value = options[key];\r\n return typeof value === \"string\" ? value : undefined;\r\n}\r\n\r\nfunction printHelp(command?: string): void {\r\n console.log(\"m5kdev\");\r\n console.log(\"\");\r\n if (!command) {\r\n console.log(\"Usage:\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> [directory] [create options]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> init [--yes] [--force] [--json]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> doctor [--full] [--json]\");\r\n console.log(\" pnpm dlx create-m5kdev@<version> update [--dry-run] [--skip-install] [--json]\");\r\n console.log(\"\");\r\n console.log(\"Run m5kdev <command> --help for command options.\");\r\n return;\r\n }\r\n console.log(`Usage: m5kdev ${command}${command === \"create\" ? \" [directory]\" : \"\"} [options]`);\r\n console.log(\"\");\r\n if (command === \"create\") {\r\n console.log(\" --name <value> Set the app name\");\r\n console.log(\" --description <value> Set the app description\");\r\n console.log(\" --platform <value> web (default), expo, both, or landing\");\r\n console.log(\" --with-test-harness Include the e2e test harness\");\r\n console.log(\" --yes Accept defaults for missing prompts\");\r\n console.log(\" --force Allow a non-empty directory\");\r\n console.log(\" --skip-install Skip pnpm install\");\r\n console.log(\" --skip-git Skip git init\");\r\n } else if (command === \"init\") {\r\n console.log(\" --yes Confirm non-interactively\");\r\n console.log(\" --force Replace existing managed state\");\r\n console.log(\" --json Emit machine-readable output\");\r\n } else if (command === \"doctor\") {\r\n console.log(\" --full Also run check-types, lint, and build scripts\");\r\n console.log(\" --json Emit machine-readable output\");\r\n } else if (command === \"update\") {\r\n console.log(\" --dry-run Build the complete plan without writes\");\r\n console.log(\" --platform <value> landing-only escape hatch: web, expo, or both\");\r\n console.log(\" --yes Skip module and e2e prompts for --platform\");\r\n console.log(\" --skip-install Apply sources without pnpm install\");\r\n console.log(\" --json Emit machine-readable output\");\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAeA,MAAM,WAAW,IAAI,IAAI;CAAC;CAAU;CAAQ;CAAU;CAAS,CAAC;AAChE,MAAM,eAAe,IAAI,IAAI;CAAC;CAAQ;CAAe;CAAW,CAAC;AACjE,MAAM,gBAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,eAAsB,OAAO,MAAwC;CACnE,MAAM,SAAS,SAAS,KAAK;AAE7B,KACE,CAAC,OAAO,WACR,OAAO,YAAY,YACnB,OAAO,YAAY,QACnB,OAAO,YAAY,QACnB;AACA,aAAW;AACX;;AAEF,KAAI,OAAO,MAAM;AACf,YAAU,OAAO,QAAQ;AACzB;;AAGF,SAAQ,OAAO,SAAf;EACE,KAAK;AACH,SAAM,UAAU,OAAO;AACvB;EACF,KAAK;AACH,SAAM,QAAQ,OAAO;AACrB;EACF,KAAK;AACH,SAAM,UAAU,OAAO;AACvB;EACF,KAAK;AACH,SAAM,UAAU,OAAO;AACvB;EACF,QACE,OAAM,IAAI,MAAM,oBAAoB,OAAO,UAAU;;;AAI3D,eAAe,UAAU,QAAkC;AACzD,iBAAgB,QAAQ;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,MAAM,UAAU,uBAAuB,OAAO;CAC9C,MAAM,SAAS,MAAMA,mBAAAA,gBAAgB,QAAQ;AAE7C,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,cAAc,OAAO,QAAQ,QAAQ,MAAM,OAAO,kBAAkB;AAChF,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,cAAc;AAC1B,SAAQ,IAAI,QAAQ,OAAO,kBAAkB;AAC7C,KAAI,QAAQ,YAAa,SAAQ,IAAI,iBAAiB;AACtD,KAAI,QAAQ,aAAa,UACvB,SAAQ,IAAI,aAAa;MACpB;AACL,UAAQ,IAAI,gDAAgD;AAC5D,UAAQ,IAAI,aAAa;;;AAI7B,eAAe,QAAQ,QAAkC;AACvD,2BAA0B,QAAQ;EAAC;EAAO;EAAS;EAAO,CAAC;CAC3D,MAAM,OAAO,QAAQ,OAAO,QAAQ,KAAK;CACzC,MAAM,SAAS,MAAMC,iBAAAA,sBAAsB;EACzC,UAAU,QAAQ,KAAK;EACvB,KAAK,QAAQ,OAAO,QAAQ,IAAI;EAChC,OAAO,QAAQ,OAAO,QAAQ,MAAM;EACrC,CAAC;AACF,KAAI,KACF,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;MACvC;AACL,0BAAA,sBAAsB,OAAO,OAAO;AACpC,UAAQ,IACN,OAAO,cACH,wCAAwC,OAAO,MAAM,SAAS,QAAQ,KACtE,kCACL;;AAEH,KAAI,CAAC,OAAO,YAAa,SAAQ,WAAW;;AAG9C,eAAe,UAAU,QAAkC;AACzD,2BAA0B,QAAQ,CAAC,QAAQ,OAAO,CAAC;CACnD,MAAM,SAAS,MAAMC,mBAAAA,oBAAoB;EACvC,UAAU,QAAQ,KAAK;EACvB,MAAM,QAAQ,OAAO,QAAQ,KAAK;EACnC,CAAC;AACF,KAAI,OAAO,QAAQ,KAAM,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;KAChE,yBAAA,sBAAsB,OAAO;AAClC,KAAI,CAAC,OAAO,GAAI,SAAQ,WAAW;;AAGrC,eAAe,UAAU,QAAkC;AACzD,2BAA0B,QAAQ;EAAC;EAAW;EAAgB;EAAQ;EAAY;EAAM,CAAC;CACzF,MAAM,iBAAiB,gBAAgB,OAAO,SAAS,WAAW;CAClE,MAAM,WAAW,iBAAiBC,oBAAAA,cAAc,eAAe,GAAG,KAAA;AAClE,KAAI,kBAAkB,CAAC,SACrB,OAAM,IAAI,MAAM,uBAAuB,eAAe,4BAA4B;CAEpF,MAAM,SAAS,MAAMC,mBAAAA,kBAAkB;EACrC,UAAU,QAAQ,KAAK;EACvB,QAAQ,QAAQ,OAAO,QAAQ,WAAW;EAC1C,aAAa,QAAQ,OAAO,QAAQ,gBAAgB;EACpD,KAAK,QAAQ,OAAO,QAAQ,IAAI;EAChC;EACD,CAAC;AACF,KAAI,OAAO,QAAQ,KACjB,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;MACvC;EACL,MAAM,SAAS,OAAO,SAAS,YAAY,OAAO,UAAU,YAAY;AACxE,UAAQ,IAAI,GAAG,OAAO,UAAU,OAAO,YAAY,MAAM,OAAO,cAAc,GAAG;AACjF,OAAK,MAAM,UAAU,OAAO,QAC1B,SAAQ,IAAI,KAAK,OAAO,KAAK,OAAO,EAAE,CAAC,GAAG,OAAO,KAAK,KAAK,OAAO,SAAS;AAE7E,OAAK,MAAM,YAAY,OAAO,UAC5B,SAAQ,IAAI,cAAc,SAAS,KAAK,KAAK,SAAS,SAAS;AAEjE,MAAI,OAAO,WAAW,SAAS,EAAG,SAAQ,IAAI,eAAe,OAAO,WAAW,KAAK,KAAK,GAAG;AAC5F,MAAI,OAAO,gBAAiB,SAAQ,IAAI,0CAA0C;AAClF,MAAI,OAAO,QAAQ,WAAW,KAAK,OAAO,UAAU,WAAW,EAC7D,SAAQ,IAAI,sBAAsB;;AAEtC,KAAI,OAAO,UAAU,SAAS,EAAG,SAAQ,WAAW;;AAGtD,SAAgB,SAAS,MAAoC;CAE3D,MAAM,CAAC,SAAS,GAAG,QADI,iBAAiB,KAAK;CAE7C,MAAM,UAA4C,EAAE;CACpD,IAAI;CACJ,IAAI,OAAO;AAEX,MAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;EACnD,MAAM,QAAQ,KAAK;AACnB,MAAI,UAAU,YAAY,UAAU,MAAM;AACxC,UAAO;AACP;;AAEF,MAAI,CAAC,MAAM,WAAW,KAAK,EAAE;AAC3B,OAAI,UAAW,OAAM,IAAI,MAAM,mCAAmC,QAAQ;AAC1E,eAAY;AACZ;;EAGF,MAAM,CAAC,QAAQ,eAAe,MAAM,MAAM,EAAE,CAAC,MAAM,KAAK,EAAE;AAC1D,MAAI,cAAc,IAAI,OAAO,EAAE;AAC7B,OAAI,gBAAgB,KAAA,EAAW,OAAM,IAAI,MAAM,KAAK,OAAO,2BAA2B;AACtF,WAAQ,UAAU;AAClB;;AAEF,MAAI,CAAC,aAAa,IAAI,OAAO,CAAE,OAAM,IAAI,MAAM,qBAAqB,SAAS;EAE7E,MAAM,YAAY,eAAe,KAAK,QAAQ;AAC9C,MAAI,CAAC,aAAa,UAAU,WAAW,KAAK,CAAE,OAAM,IAAI,MAAM,uBAAuB,SAAS;AAC9F,UAAQ,UAAU;AAClB,MAAI,gBAAgB,KAAA,EAAW,UAAS;;AAE1C,QAAO;EAAE;EAAS;EAAW;EAAM;EAAS;;AAG9C,SAAS,iBAAiB,MAA4C;CACpE,MAAM,CAAC,cAAc;AACrB,KAAI,CAAC,cAAc,SAAS,IAAI,WAAW,IAAI,eAAe,YAAY,eAAe,KACvF,QAAO;AACT,KAAI,eAAe,OAAQ,QAAO,CAAC,SAAS;AAC5C,QAAO,CAAC,UAAU,GAAG,KAAK;;AAG5B,SAAS,0BAA0B,QAAmB,gBAAyC;AAC7F,KAAI,OAAO,UACT,OAAM,IAAI,MACR,GAAG,OAAO,QAAQ,uEACnB;AACH,iBAAgB,QAAQ,eAAe;;AAGzC,SAAS,gBAAgB,QAAmB,gBAAyC;CACnF,MAAM,UAAU,IAAI,IAAI,eAAe;AACvC,MAAK,MAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,CAC9C,KAAI,CAAC,QAAQ,IAAI,OAAO,CACtB,OAAM,IAAI,MAAM,KAAK,OAAO,2BAA2B,OAAO,QAAQ,GAAG;;AAI/E,SAAS,uBAAuB,QAAyC;CACvE,MAAM,iBAAiB,gBAAgB,OAAO,SAAS,WAAW;CAClE,MAAM,WAAW,iBAAiBD,oBAAAA,cAAc,eAAe,GAAG,KAAA;AAClE,KAAI,kBAAkB,CAAC,SACrB,OAAM,IAAI,MAAM,uBAAuB,eAAe,qCAAqC;AAE7F,QAAO;EACL,iBAAiB,OAAO;EACxB,SAAS,gBAAgB,OAAO,SAAS,OAAO;EAChD,gBAAgB,gBAAgB,OAAO,SAAS,cAAc;EAC9D;EACA,aAAa,OAAO,QAAQ,uBAAuB,OAAO,KAAA;EAC1D,KAAK,QAAQ,OAAO,QAAQ,IAAI;EAChC,OAAO,QAAQ,OAAO,QAAQ,MAAM;EACpC,aAAa,QAAQ,OAAO,QAAQ,gBAAgB;EACpD,SAAS,QAAQ,OAAO,QAAQ,YAAY;EAC7C;;AAGH,SAAS,gBACP,SACA,KACoB;CACpB,MAAM,QAAQ,QAAQ;AACtB,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG7C,SAAS,UAAU,SAAwB;AACzC,SAAQ,IAAI,SAAS;AACrB,SAAQ,IAAI,GAAG;AACf,KAAI,CAAC,SAAS;AACZ,UAAQ,IAAI,SAAS;AACrB,UAAQ,IAAI,kEAAkE;AAC9E,UAAQ,IAAI,qEAAqE;AACjF,UAAQ,IAAI,8DAA8D;AAC1E,UAAQ,IAAI,kFAAkF;AAC9F,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,mDAAmD;AAC/D;;AAEF,SAAQ,IAAI,iBAAiB,UAAU,YAAY,WAAW,iBAAiB,GAAG,YAAY;AAC9F,SAAQ,IAAI,GAAG;AACf,KAAI,YAAY,UAAU;AACxB,UAAQ,IAAI,8CAA8C;AAC1D,UAAQ,IAAI,qDAAqD;AACjE,UAAQ,IAAI,mEAAmE;AAC/E,UAAQ,IAAI,0DAA0D;AACtE,UAAQ,IAAI,iEAAiE;AAC7E,UAAQ,IAAI,yDAAyD;AACrE,UAAQ,IAAI,+CAA+C;AAC3D,UAAQ,IAAI,2CAA2C;YAC9C,YAAY,QAAQ;AAC7B,UAAQ,IAAI,uDAAuD;AACnE,UAAQ,IAAI,4DAA4D;AACxE,UAAQ,IAAI,0DAA0D;YAC7D,YAAY,UAAU;AAC/B,UAAQ,IAAI,2EAA2E;AACvF,UAAQ,IAAI,0DAA0D;YAC7D,YAAY,UAAU;AAC/B,UAAQ,IAAI,oEAAoE;AAChF,UAAQ,IAAI,2EAA2E;AACvF,UAAQ,IAAI,wEAAwE;AACpF,UAAQ,IAAI,gEAAgE;AAC5E,UAAQ,IAAI,0DAA0D"}
@@ -5,6 +5,16 @@ node_fs = require_runtime.__toESM(node_fs);
5
5
  let node_path = require("node:path");
6
6
  node_path = require_runtime.__toESM(node_path);
7
7
  //#region src/template.ts
8
+ const PRODUCT_STACK_FEATURE = "server";
9
+ function withImpliedFeatures(enabled) {
10
+ const next = new Set(enabled);
11
+ if (next.has("webapp") || next.has("expo")) next.add(PRODUCT_STACK_FEATURE);
12
+ return next;
13
+ }
14
+ function hasProductStack(enabled) {
15
+ const features = enabled instanceof Set ? enabled : new Set(enabled);
16
+ return features.has("webapp") || features.has("expo") || features.has("server");
17
+ }
8
18
  function loadTemplateManifest(templateDirectory) {
9
19
  const manifestPath = node_path.default.join(templateDirectory, "template.manifest.json");
10
20
  if (!node_fs.default.existsSync(manifestPath)) throw new Error(`Template manifest is missing: ${manifestPath}`);
@@ -28,7 +38,7 @@ function getTemplateFilePolicy(manifest, relativePath) {
28
38
  }
29
39
  function getFeatureKind(options) {
30
40
  if (options.config.kind) return options.config.kind;
31
- if (options.id === "webapp" || options.id === "expo") return "platform";
41
+ if (options.id === "webapp" || options.id === "expo" || options.id === "server") return "platform";
32
42
  if (options.id === "test-harness") return "harness";
33
43
  return "module";
34
44
  }
@@ -44,21 +54,28 @@ function listBackendModuleChoices(manifest) {
44
54
  }
45
55
  function getEnabledFeatures(options) {
46
56
  const enabled = /* @__PURE__ */ new Set();
47
- if (options.platform !== "expo") enabled.add("webapp");
48
- if (options.platform !== "web") enabled.add("expo");
57
+ if (options.platform !== "expo" && options.platform !== "landing") enabled.add("webapp");
58
+ if (options.platform !== "web" && options.platform !== "landing") enabled.add("expo");
49
59
  if (options.testHarness) enabled.add("test-harness");
50
60
  for (const id of options.modules ?? []) enabled.add(id);
51
61
  return enabled;
52
62
  }
63
+ const LANDING_LOCAL_ENV_PATHS = ["apps/landing/.env", "apps/landing/.env.example"];
53
64
  function getExcludedFeaturePaths(manifest, enabledFeatures) {
54
- return Object.entries(manifest.features).filter(([feature]) => !enabledFeatures.has(feature)).flatMap(([, config]) => [...config.paths]);
65
+ const effective = withImpliedFeatures(enabledFeatures);
66
+ const excluded = Object.entries(manifest.features).filter(([feature]) => !effective.has(feature)).flatMap(([, config]) => [...config.paths]);
67
+ if (hasProductStack(effective)) excluded.push(...LANDING_LOCAL_ENV_PATHS);
68
+ return excluded;
55
69
  }
56
70
  //#endregion
71
+ exports.PRODUCT_STACK_FEATURE = PRODUCT_STACK_FEATURE;
57
72
  exports.getEnabledFeatures = getEnabledFeatures;
58
73
  exports.getExcludedFeaturePaths = getExcludedFeaturePaths;
59
74
  exports.getFeatureKind = getFeatureKind;
60
75
  exports.getTemplateFilePolicy = getTemplateFilePolicy;
76
+ exports.hasProductStack = hasProductStack;
61
77
  exports.listBackendModuleChoices = listBackendModuleChoices;
62
78
  exports.loadTemplateManifest = loadTemplateManifest;
79
+ exports.withImpliedFeatures = withImpliedFeatures;
63
80
 
64
81
  //# sourceMappingURL=template.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.cjs","names":["path","fs"],"sources":["../../src/template.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type { TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from \"./types\";\r\n\r\nexport function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest {\r\n const manifestPath = path.join(templateDirectory, \"template.manifest.json\");\r\n if (!fs.existsSync(manifestPath)) {\r\n throw new Error(`Template manifest is missing: ${manifestPath}`);\r\n }\r\n return JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as TemplateFeatureManifest;\r\n}\r\n\r\nfunction globToRegExp(pattern: string): RegExp {\r\n let source = \"^\";\r\n for (let index = 0; index < pattern.length; index += 1) {\r\n const character = pattern[index];\r\n if (character === \"*\" && pattern[index + 1] === \"*\") {\r\n source += \".*\";\r\n index += 1;\r\n } else if (character === \"*\") {\r\n source += \"[^/]*\";\r\n } else {\r\n source += character.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n }\r\n }\r\n return new RegExp(`${source}$`);\r\n}\r\n\r\nexport function getTemplateFilePolicy(\r\n manifest: TemplateFeatureManifest,\r\n relativePath: string\r\n): TemplateFilePolicy {\r\n for (const rule of manifest.sync.rules) {\r\n if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;\r\n }\r\n return manifest.sync.defaultPolicy;\r\n}\r\n\r\nexport interface BackendModuleChoice {\r\n id: string;\r\n label: string;\r\n experimental: boolean;\r\n}\r\n\r\nexport function getFeatureKind(options: {\r\n id: string;\r\n config: TemplateFeatureManifest[\"features\"][string];\r\n}): TemplateFeatureKind {\r\n if (options.config.kind) return options.config.kind;\r\n if (options.id === \"webapp\" || options.id === \"expo\") return \"platform\";\r\n if (options.id === \"test-harness\") return \"harness\";\r\n return \"module\";\r\n}\r\n\r\nexport function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[] {\r\n return Object.entries(manifest.features)\r\n .filter(([id, config]) => getFeatureKind({ id, config }) === \"module\")\r\n .map(([id, config]) => ({\r\n id,\r\n label: config.label ?? id,\r\n experimental: Boolean(config.experimental),\r\n }));\r\n}\r\n\r\nexport function getEnabledFeatures(options: {\r\n platform: \"web\" | \"expo\" | \"both\";\r\n testHarness: boolean;\r\n modules?: readonly string[];\r\n}): Set<string> {\r\n const enabled = new Set<string>();\r\n if (options.platform !== \"expo\") enabled.add(\"webapp\");\r\n if (options.platform !== \"web\") enabled.add(\"expo\");\r\n if (options.testHarness) enabled.add(\"test-harness\");\r\n for (const id of options.modules ?? []) enabled.add(id);\r\n return enabled;\r\n}\r\n\r\nexport function getExcludedFeaturePaths(\r\n manifest: TemplateFeatureManifest,\r\n enabledFeatures: ReadonlySet<string>\r\n): string[] {\r\n return Object.entries(manifest.features)\r\n .filter(([feature]) => !enabledFeatures.has(feature))\r\n .flatMap(([, config]) => [...config.paths]);\r\n}\r\n"],"mappings":";;;;;;;AAIA,SAAgB,qBAAqB,mBAAoD;CACvF,MAAM,eAAeA,UAAAA,QAAK,KAAK,mBAAmB,yBAAyB;AAC3E,KAAI,CAACC,QAAAA,QAAG,WAAW,aAAa,CAC9B,OAAM,IAAI,MAAM,iCAAiC,eAAe;AAElE,QAAO,KAAK,MAAMA,QAAAA,QAAG,aAAa,cAAc,OAAO,CAAC;;AAG1D,SAAS,aAAa,SAAyB;CAC7C,IAAI,SAAS;AACb,MAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,YAAY,QAAQ;AAC1B,MAAI,cAAc,OAAO,QAAQ,QAAQ,OAAO,KAAK;AACnD,aAAU;AACV,YAAS;aACA,cAAc,IACvB,WAAU;MAEV,WAAU,UAAU,QAAQ,uBAAuB,OAAO;;AAG9D,QAAO,IAAI,OAAO,GAAG,OAAO,GAAG;;AAGjC,SAAgB,sBACd,UACA,cACoB;AACpB,MAAK,MAAM,QAAQ,SAAS,KAAK,MAC/B,KAAI,aAAa,KAAK,QAAQ,CAAC,KAAK,aAAa,CAAE,QAAO,KAAK;AAEjE,QAAO,SAAS,KAAK;;AASvB,SAAgB,eAAe,SAGP;AACtB,KAAI,QAAQ,OAAO,KAAM,QAAO,QAAQ,OAAO;AAC/C,KAAI,QAAQ,OAAO,YAAY,QAAQ,OAAO,OAAQ,QAAO;AAC7D,KAAI,QAAQ,OAAO,eAAgB,QAAO;AAC1C,QAAO;;AAGT,SAAgB,yBAAyB,UAA0D;AACjG,QAAO,OAAO,QAAQ,SAAS,SAAS,CACrC,QAAQ,CAAC,IAAI,YAAY,eAAe;EAAE;EAAI;EAAQ,CAAC,KAAK,SAAS,CACrE,KAAK,CAAC,IAAI,aAAa;EACtB;EACA,OAAO,OAAO,SAAS;EACvB,cAAc,QAAQ,OAAO,aAAa;EAC3C,EAAE;;AAGP,SAAgB,mBAAmB,SAInB;CACd,MAAM,0BAAU,IAAI,KAAa;AACjC,KAAI,QAAQ,aAAa,OAAQ,SAAQ,IAAI,SAAS;AACtD,KAAI,QAAQ,aAAa,MAAO,SAAQ,IAAI,OAAO;AACnD,KAAI,QAAQ,YAAa,SAAQ,IAAI,eAAe;AACpD,MAAK,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAE,SAAQ,IAAI,GAAG;AACvD,QAAO;;AAGT,SAAgB,wBACd,UACA,iBACU;AACV,QAAO,OAAO,QAAQ,SAAS,SAAS,CACrC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,IAAI,QAAQ,CAAC,CACpD,SAAS,GAAG,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC"}
1
+ {"version":3,"file":"template.cjs","names":["path","fs"],"sources":["../../src/template.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type {\r\n AppPlatform,\r\n TemplateFeatureKind,\r\n TemplateFeatureManifest,\r\n TemplateFilePolicy,\r\n} from \"./types\";\r\n\r\nexport const PRODUCT_STACK_FEATURE = \"server\";\r\n\r\nexport function withImpliedFeatures(enabled: ReadonlySet<string>): Set<string> {\r\n const next = new Set(enabled);\r\n if (next.has(\"webapp\") || next.has(\"expo\")) next.add(PRODUCT_STACK_FEATURE);\r\n return next;\r\n}\r\n\r\nexport function hasProductStack(enabled: ReadonlySet<string> | readonly string[]): boolean {\r\n const features = enabled instanceof Set ? enabled : new Set(enabled);\r\n return features.has(\"webapp\") || features.has(\"expo\") || features.has(PRODUCT_STACK_FEATURE);\r\n}\r\n\r\nexport function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest {\r\n const manifestPath = path.join(templateDirectory, \"template.manifest.json\");\r\n if (!fs.existsSync(manifestPath)) {\r\n throw new Error(`Template manifest is missing: ${manifestPath}`);\r\n }\r\n return JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as TemplateFeatureManifest;\r\n}\r\n\r\nfunction globToRegExp(pattern: string): RegExp {\r\n let source = \"^\";\r\n for (let index = 0; index < pattern.length; index += 1) {\r\n const character = pattern[index];\r\n if (character === \"*\" && pattern[index + 1] === \"*\") {\r\n source += \".*\";\r\n index += 1;\r\n } else if (character === \"*\") {\r\n source += \"[^/]*\";\r\n } else {\r\n source += character.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n }\r\n }\r\n return new RegExp(`${source}$`);\r\n}\r\n\r\nexport function getTemplateFilePolicy(\r\n manifest: TemplateFeatureManifest,\r\n relativePath: string\r\n): TemplateFilePolicy {\r\n for (const rule of manifest.sync.rules) {\r\n if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;\r\n }\r\n return manifest.sync.defaultPolicy;\r\n}\r\n\r\nexport interface BackendModuleChoice {\r\n id: string;\r\n label: string;\r\n experimental: boolean;\r\n}\r\n\r\nexport function getFeatureKind(options: {\r\n id: string;\r\n config: TemplateFeatureManifest[\"features\"][string];\r\n}): TemplateFeatureKind {\r\n if (options.config.kind) return options.config.kind;\r\n if (options.id === \"webapp\" || options.id === \"expo\" || options.id === PRODUCT_STACK_FEATURE)\r\n return \"platform\";\r\n if (options.id === \"test-harness\") return \"harness\";\r\n return \"module\";\r\n}\r\n\r\nexport function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[] {\r\n return Object.entries(manifest.features)\r\n .filter(([id, config]) => getFeatureKind({ id, config }) === \"module\")\r\n .map(([id, config]) => ({\r\n id,\r\n label: config.label ?? id,\r\n experimental: Boolean(config.experimental),\r\n }));\r\n}\r\n\r\nexport function getEnabledFeatures(options: {\r\n platform: AppPlatform;\r\n testHarness: boolean;\r\n modules?: readonly string[];\r\n}): Set<string> {\r\n const enabled = new Set<string>();\r\n if (options.platform !== \"expo\" && options.platform !== \"landing\") enabled.add(\"webapp\");\r\n if (options.platform !== \"web\" && options.platform !== \"landing\") enabled.add(\"expo\");\r\n if (options.testHarness) enabled.add(\"test-harness\");\r\n for (const id of options.modules ?? []) enabled.add(id);\r\n return enabled;\r\n}\r\n\r\nconst LANDING_LOCAL_ENV_PATHS = [\"apps/landing/.env\", \"apps/landing/.env.example\"] as const;\r\n\r\nexport function getExcludedFeaturePaths(\r\n manifest: TemplateFeatureManifest,\r\n enabledFeatures: ReadonlySet<string>\r\n): string[] {\r\n const effective = withImpliedFeatures(enabledFeatures);\r\n const excluded = Object.entries(manifest.features)\r\n .filter(([feature]) => !effective.has(feature))\r\n .flatMap(([, config]) => [...config.paths]);\r\n if (hasProductStack(effective)) excluded.push(...LANDING_LOCAL_ENV_PATHS);\r\n return excluded;\r\n}\r\n"],"mappings":";;;;;;;AASA,MAAa,wBAAwB;AAErC,SAAgB,oBAAoB,SAA2C;CAC7E,MAAM,OAAO,IAAI,IAAI,QAAQ;AAC7B,KAAI,KAAK,IAAI,SAAS,IAAI,KAAK,IAAI,OAAO,CAAE,MAAK,IAAI,sBAAsB;AAC3E,QAAO;;AAGT,SAAgB,gBAAgB,SAA2D;CACzF,MAAM,WAAW,mBAAmB,MAAM,UAAU,IAAI,IAAI,QAAQ;AACpE,QAAO,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,OAAO,IAAI,SAAS,IAAA,SAA0B;;AAG9F,SAAgB,qBAAqB,mBAAoD;CACvF,MAAM,eAAeA,UAAAA,QAAK,KAAK,mBAAmB,yBAAyB;AAC3E,KAAI,CAACC,QAAAA,QAAG,WAAW,aAAa,CAC9B,OAAM,IAAI,MAAM,iCAAiC,eAAe;AAElE,QAAO,KAAK,MAAMA,QAAAA,QAAG,aAAa,cAAc,OAAO,CAAC;;AAG1D,SAAS,aAAa,SAAyB;CAC7C,IAAI,SAAS;AACb,MAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,YAAY,QAAQ;AAC1B,MAAI,cAAc,OAAO,QAAQ,QAAQ,OAAO,KAAK;AACnD,aAAU;AACV,YAAS;aACA,cAAc,IACvB,WAAU;MAEV,WAAU,UAAU,QAAQ,uBAAuB,OAAO;;AAG9D,QAAO,IAAI,OAAO,GAAG,OAAO,GAAG;;AAGjC,SAAgB,sBACd,UACA,cACoB;AACpB,MAAK,MAAM,QAAQ,SAAS,KAAK,MAC/B,KAAI,aAAa,KAAK,QAAQ,CAAC,KAAK,aAAa,CAAE,QAAO,KAAK;AAEjE,QAAO,SAAS,KAAK;;AASvB,SAAgB,eAAe,SAGP;AACtB,KAAI,QAAQ,OAAO,KAAM,QAAO,QAAQ,OAAO;AAC/C,KAAI,QAAQ,OAAO,YAAY,QAAQ,OAAO,UAAU,QAAQ,OAAA,SAC9D,QAAO;AACT,KAAI,QAAQ,OAAO,eAAgB,QAAO;AAC1C,QAAO;;AAGT,SAAgB,yBAAyB,UAA0D;AACjG,QAAO,OAAO,QAAQ,SAAS,SAAS,CACrC,QAAQ,CAAC,IAAI,YAAY,eAAe;EAAE;EAAI;EAAQ,CAAC,KAAK,SAAS,CACrE,KAAK,CAAC,IAAI,aAAa;EACtB;EACA,OAAO,OAAO,SAAS;EACvB,cAAc,QAAQ,OAAO,aAAa;EAC3C,EAAE;;AAGP,SAAgB,mBAAmB,SAInB;CACd,MAAM,0BAAU,IAAI,KAAa;AACjC,KAAI,QAAQ,aAAa,UAAU,QAAQ,aAAa,UAAW,SAAQ,IAAI,SAAS;AACxF,KAAI,QAAQ,aAAa,SAAS,QAAQ,aAAa,UAAW,SAAQ,IAAI,OAAO;AACrF,KAAI,QAAQ,YAAa,SAAQ,IAAI,eAAe;AACpD,MAAK,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAE,SAAQ,IAAI,GAAG;AACvD,QAAO;;AAGT,MAAM,0BAA0B,CAAC,qBAAqB,4BAA4B;AAElF,SAAgB,wBACd,UACA,iBACU;CACV,MAAM,YAAY,oBAAoB,gBAAgB;CACtD,MAAM,WAAW,OAAO,QAAQ,SAAS,SAAS,CAC/C,QAAQ,CAAC,aAAa,CAAC,UAAU,IAAI,QAAQ,CAAC,CAC9C,SAAS,GAAG,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC;AAC7C,KAAI,gBAAgB,UAAU,CAAE,UAAS,KAAK,GAAG,wBAAwB;AACzE,QAAO"}
@@ -1,6 +1,9 @@
1
- import { TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
1
+ import { AppPlatform, TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
2
2
 
3
3
  //#region src/template.d.ts
4
+ declare const PRODUCT_STACK_FEATURE = "server";
5
+ declare function withImpliedFeatures(enabled: ReadonlySet<string>): Set<string>;
6
+ declare function hasProductStack(enabled: ReadonlySet<string> | readonly string[]): boolean;
4
7
  declare function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest;
5
8
  declare function getTemplateFilePolicy(manifest: TemplateFeatureManifest, relativePath: string): TemplateFilePolicy;
6
9
  interface BackendModuleChoice {
@@ -14,11 +17,11 @@ declare function getFeatureKind(options: {
14
17
  }): TemplateFeatureKind;
15
18
  declare function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[];
16
19
  declare function getEnabledFeatures(options: {
17
- platform: "web" | "expo" | "both";
20
+ platform: AppPlatform;
18
21
  testHarness: boolean;
19
22
  modules?: readonly string[];
20
23
  }): Set<string>;
21
24
  declare function getExcludedFeaturePaths(manifest: TemplateFeatureManifest, enabledFeatures: ReadonlySet<string>): string[];
22
25
  //#endregion
23
- export { BackendModuleChoice, getEnabledFeatures, getExcludedFeaturePaths, getFeatureKind, getTemplateFilePolicy, listBackendModuleChoices, loadTemplateManifest };
26
+ export { BackendModuleChoice, PRODUCT_STACK_FEATURE, getEnabledFeatures, getExcludedFeaturePaths, getFeatureKind, getTemplateFilePolicy, hasProductStack, listBackendModuleChoices, loadTemplateManifest, withImpliedFeatures };
24
27
  //# sourceMappingURL=template.d.cts.map
@@ -1,5 +1,5 @@
1
1
  //#region src/types.d.ts
2
- type AppPlatform = "web" | "expo" | "both";
2
+ type AppPlatform = "web" | "expo" | "both" | "landing";
3
3
  interface PromptValues {
4
4
  targetDirectory?: string;
5
5
  appName?: string;
@@ -1,11 +1,16 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_src_fs = require("./fs.cjs");
3
4
  const require_src_paths = require("./paths.cjs");
5
+ const require_src_template = require("./template.cjs");
6
+ const require_src_prompts = require("./prompts.cjs");
4
7
  const require_src_state = require("./state.cjs");
5
8
  const require_src_migrations_registry = require("./migrations/registry.cjs");
6
9
  const require_src_reconcile = require("./reconcile.cjs");
7
10
  let node_path = require("node:path");
8
11
  node_path = require_runtime.__toESM(node_path);
12
+ let node_fs_promises = require("node:fs/promises");
13
+ node_fs_promises = require_runtime.__toESM(node_fs_promises);
9
14
  let node_child_process = require("node:child_process");
10
15
  let node_util = require("node:util");
11
16
  let semver = require("semver");
@@ -18,32 +23,112 @@ async function assertCleanGit(repoRoot) {
18
23
  encoding: "utf8"
19
24
  })).stdout.trim()) throw new Error("Write-mode update requires a clean Git worktree. Commit or stash changes first.");
20
25
  }
26
+ async function resolveUpgradeState(options) {
27
+ if (!options.platform) return options.state;
28
+ if (options.platform === "landing") throw new Error("m5kdev update --platform cannot switch to landing.");
29
+ const current = new Set(options.state.template.features);
30
+ if (current.has("webapp") || current.has("expo")) throw new Error("m5kdev update --platform is only for landing-only apps. This repository already has a Webapp or Expo client.");
31
+ const extras = await require_src_prompts.resolveHarnessAndModuleOptions({
32
+ yes: options.yes,
33
+ testHarness: options.testHarness,
34
+ modules: options.modules
35
+ });
36
+ const enabledFeatures = require_src_template.getEnabledFeatures({
37
+ platform: options.platform,
38
+ testHarness: extras.testHarness,
39
+ modules: extras.modules
40
+ });
41
+ return {
42
+ ...options.state,
43
+ template: {
44
+ ...options.state.template,
45
+ features: [...enabledFeatures].sort()
46
+ }
47
+ };
48
+ }
49
+ function overlayEnv(base, overlay) {
50
+ if (!overlay.trim()) return base;
51
+ let next = base;
52
+ for (const line of overlay.split("\n")) {
53
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
54
+ if (!match) continue;
55
+ const [, key, value] = match;
56
+ const assignment = `${key}=${value}`;
57
+ const pattern = new RegExp(`^${key}=.*$`, "m");
58
+ if (pattern.test(next)) next = next.replace(pattern, assignment);
59
+ else next = `${next}${next.endsWith("\n") || next.length === 0 ? "" : "\n"}${assignment}\n`;
60
+ }
61
+ return next;
62
+ }
63
+ async function planLandingEnvMigration(options) {
64
+ const sharedEnvRelative = "apps/shared/.env";
65
+ const landingEnvRelative = "apps/landing/.env";
66
+ const landingExampleRelative = "apps/landing/.env.example";
67
+ const landingEnv = await node_fs_promises.default.readFile(node_path.default.join(options.repoRoot, landingEnvRelative), "utf8").catch(() => "");
68
+ const existing = await options.changes.read(sharedEnvRelative);
69
+ const sharedEnv = (await require_src_fs.collectTemplateFiles(options.templateRoot, {
70
+ ...options.state.template.context,
71
+ betterAuthSecret: "ignored-by-managed-state"
72
+ }, { enabledFeatures: require_src_template.withImpliedFeatures(new Set(options.state.template.features)) })).find((file) => file.relativePath === sharedEnvRelative);
73
+ const base = existing?.toString("utf8") || sharedEnv?.content.toString("utf8") || "";
74
+ if (base.length > 0) {
75
+ const content = overlayEnv(base, landingEnv);
76
+ if (!existing || content !== existing.toString("utf8")) options.changes.addChange({
77
+ kind: existing ? "modify" : "add",
78
+ path: sharedEnvRelative,
79
+ content: Buffer.from(content),
80
+ reason: "Copy Landing env into Deploy home"
81
+ });
82
+ }
83
+ for (const relativePath of [landingEnvRelative, landingExampleRelative]) {
84
+ if (!await node_fs_promises.default.access(node_path.default.join(options.repoRoot, relativePath)).then(() => true).catch(() => false)) continue;
85
+ options.changes.addChange({
86
+ kind: "delete",
87
+ path: relativePath,
88
+ reason: "Remove Landing-local env after attaching Deploy home"
89
+ });
90
+ }
91
+ }
21
92
  async function updateManagedRepo(options) {
22
93
  const state = await require_src_state.readManagedState(options.repoRoot);
23
94
  const targetVersion = options.targetVersion ?? require_src_state.getCliVersion();
24
95
  if (semver.default.gt(state.template.version, targetVersion)) throw new Error(`The running CLI ${targetVersion} is older than managed state ${state.template.version}.`);
96
+ const upgradedState = await resolveUpgradeState({
97
+ state,
98
+ platform: options.platform,
99
+ yes: Boolean(options.yes),
100
+ testHarness: options.testHarness,
101
+ modules: options.modules
102
+ });
25
103
  if (!options.dryRun) await (options.assertClean ?? assertCleanGit)(options.repoRoot);
104
+ const templateRoot = options.targetTemplateRoot ?? require_src_paths.getTemplateRoot();
26
105
  const reconciliation = await require_src_reconcile.reconcileTemplates({
27
106
  repoRoot: options.repoRoot,
28
- state,
29
- targetTemplateRoot: options.targetTemplateRoot ?? require_src_paths.getTemplateRoot(),
107
+ state: upgradedState,
108
+ targetTemplateRoot: templateRoot,
30
109
  targetVersion,
31
110
  baseProvider: options.baseProvider
32
111
  });
112
+ if (options.platform && options.platform !== "landing") await planLandingEnvMigration({
113
+ repoRoot: options.repoRoot,
114
+ templateRoot,
115
+ state: upgradedState,
116
+ changes: reconciliation.changes
117
+ });
33
118
  const registry = options.registry ?? require_src_migrations_registry.getMigrationRegistry();
34
- const pending = require_src_migrations_registry.getPendingMigrations(state, targetVersion, registry);
119
+ const pending = require_src_migrations_registry.getPendingMigrations(upgradedState, targetVersion, registry);
35
120
  const appliedMigrations = [];
36
121
  for (const migration of pending) {
37
122
  const context = {
38
123
  repoRoot: options.repoRoot,
39
- state,
124
+ state: upgradedState,
40
125
  changes: reconciliation.changes
41
126
  };
42
127
  if (migration.applies && !await migration.applies(context)) continue;
43
128
  await migration.plan(context);
44
129
  appliedMigrations.push(migration.id);
45
130
  }
46
- reconciliation.targetState.appliedMigrations = [...new Set([...state.appliedMigrations, ...appliedMigrations])];
131
+ reconciliation.targetState.appliedMigrations = [...new Set([...upgradedState.appliedMigrations, ...appliedMigrations])];
47
132
  const validationDiagnostics = await require_src_migrations_registry.runMigrationValidators({
48
133
  repoRoot: options.repoRoot,
49
134
  state: reconciliation.targetState,
@@ -58,7 +143,7 @@ async function updateManagedRepo(options) {
58
143
  }
59
144
  const stateContent = Buffer.from(`${JSON.stringify(reconciliation.targetState, null, 2)}\n`);
60
145
  const currentState = await reconciliation.changes.read(require_src_state.STATE_FILE_NAME);
61
- if (!currentState || !currentState.equals(stateContent)) reconciliation.changes.addChange({
146
+ if (!currentState?.equals(stateContent)) reconciliation.changes.addChange({
62
147
  kind: currentState ? "modify" : "add",
63
148
  path: require_src_state.STATE_FILE_NAME,
64
149
  content: stateContent,
@@ -1 +1 @@
1
- {"version":3,"file":"update.cjs","names":["execFile","readManagedState","getCliVersion","reconcileTemplates","getTemplateRoot","getMigrationRegistry","getPendingMigrations","runMigrationValidators","STATE_FILE_NAME","path"],"sources":["../../src/update.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport type { PlannedChange, PlannedConflict } from \"./changes\";\r\nimport {\r\n getMigrationRegistry,\r\n getPendingMigrations,\r\n runMigrationValidators,\r\n} from \"./migrations/registry\";\r\nimport type { MigrationDefinition } from \"./migrations/types\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { type BaseTemplateProvider, reconcileTemplates } from \"./reconcile\";\r\nimport { getCliVersion, readManagedState, STATE_FILE_NAME } from \"./state\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport interface UpdateCommandOptions {\r\n repoRoot: string;\r\n dryRun: boolean;\r\n skipInstall: boolean;\r\n baseProvider?: BaseTemplateProvider;\r\n /** Test seam; published commands always use the embedded production registry. */\r\n registry?: readonly MigrationDefinition[];\r\n /** Test seams for fixture-version updates. */\r\n targetTemplateRoot?: string;\r\n targetVersion?: string;\r\n assertClean?: (repoRoot: string) => Promise<void>;\r\n install?: (repoRoot: string) => Promise<void>;\r\n}\r\n\r\nexport interface UpdateResult {\r\n fromVersion: string;\r\n targetVersion: string;\r\n dryRun: boolean;\r\n applied: boolean;\r\n changes: Array<Omit<PlannedChange, \"content\">>;\r\n conflicts: PlannedConflict[];\r\n migrations: string[];\r\n dependenciesChanged: boolean;\r\n installRequired: boolean;\r\n}\r\n\r\nasync function assertCleanGit(repoRoot: string): Promise<void> {\r\n const result = await execFileAsync(\"git\", [\"status\", \"--porcelain\"], {\r\n cwd: repoRoot,\r\n encoding: \"utf8\",\r\n });\r\n if (result.stdout.trim()) {\r\n throw new Error(\r\n \"Write-mode update requires a clean Git worktree. Commit or stash changes first.\"\r\n );\r\n }\r\n}\r\n\r\nexport async function updateManagedRepo(options: UpdateCommandOptions): Promise<UpdateResult> {\r\n const state = await readManagedState(options.repoRoot);\r\n const targetVersion = options.targetVersion ?? getCliVersion();\r\n if (semver.gt(state.template.version, targetVersion)) {\r\n throw new Error(\r\n `The running CLI ${targetVersion} is older than managed state ${state.template.version}.`\r\n );\r\n }\r\n if (!options.dryRun) await (options.assertClean ?? assertCleanGit)(options.repoRoot);\r\n\r\n const reconciliation = await reconcileTemplates({\r\n repoRoot: options.repoRoot,\r\n state,\r\n targetTemplateRoot: options.targetTemplateRoot ?? getTemplateRoot(),\r\n targetVersion,\r\n baseProvider: options.baseProvider,\r\n });\r\n const registry = options.registry ?? getMigrationRegistry();\r\n const pending = getPendingMigrations(state, targetVersion, registry);\r\n const appliedMigrations: string[] = [];\r\n for (const migration of pending) {\r\n const context = { repoRoot: options.repoRoot, state, changes: reconciliation.changes };\r\n if (migration.applies && !(await migration.applies(context))) continue;\r\n await migration.plan(context);\r\n appliedMigrations.push(migration.id);\r\n }\r\n reconciliation.targetState.appliedMigrations = [\r\n ...new Set([...state.appliedMigrations, ...appliedMigrations]),\r\n ];\r\n\r\n const validationDiagnostics = await runMigrationValidators(\r\n {\r\n repoRoot: options.repoRoot,\r\n state: reconciliation.targetState,\r\n changes: reconciliation.changes,\r\n },\r\n registry\r\n );\r\n for (const diagnostic of validationDiagnostics) {\r\n if (diagnostic.severity !== \"error\") continue;\r\n reconciliation.changes.addConflict({\r\n path: diagnostic.path ?? \".m5kdev.json\",\r\n reason: `[${diagnostic.code}] ${diagnostic.message}`,\r\n });\r\n }\r\n\r\n const stateContent = Buffer.from(`${JSON.stringify(reconciliation.targetState, null, 2)}\\n`);\r\n const currentState = await reconciliation.changes.read(STATE_FILE_NAME);\r\n if (!currentState || !currentState.equals(stateContent)) {\r\n reconciliation.changes.addChange({\r\n kind: currentState ? \"modify\" : \"add\",\r\n path: STATE_FILE_NAME,\r\n content: stateContent,\r\n reason: \"Advance managed template state\",\r\n });\r\n }\r\n\r\n const result: UpdateResult = {\r\n fromVersion: state.template.version,\r\n targetVersion,\r\n dryRun: options.dryRun,\r\n applied: false,\r\n changes: [...reconciliation.changes.changes.values()].map(\r\n ({ content: _content, ...change }) => change\r\n ),\r\n conflicts: [...reconciliation.changes.conflicts],\r\n migrations: appliedMigrations,\r\n dependenciesChanged: reconciliation.dependenciesChanged,\r\n installRequired: reconciliation.dependenciesChanged && options.skipInstall,\r\n };\r\n if (options.dryRun || result.conflicts.length > 0) return result;\r\n\r\n await reconciliation.changes.apply();\r\n result.applied = true;\r\n if (reconciliation.dependenciesChanged && !options.skipInstall) {\r\n if (options.install) await options.install(path.resolve(options.repoRoot));\r\n else\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: path.resolve(options.repoRoot),\r\n env: process.env,\r\n });\r\n }\r\n return result;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,iBAAA,GAAA,UAAA,WAA0BA,mBAAAA,SAAS;AA4BzC,eAAe,eAAe,UAAiC;AAK7D,MAJe,MAAM,cAAc,OAAO,CAAC,UAAU,cAAc,EAAE;EACnE,KAAK;EACL,UAAU;EACX,CAAC,EACS,OAAO,MAAM,CACtB,OAAM,IAAI,MACR,kFACD;;AAIL,eAAsB,kBAAkB,SAAsD;CAC5F,MAAM,QAAQ,MAAMC,kBAAAA,iBAAiB,QAAQ,SAAS;CACtD,MAAM,gBAAgB,QAAQ,iBAAiBC,kBAAAA,eAAe;AAC9D,KAAI,OAAA,QAAO,GAAG,MAAM,SAAS,SAAS,cAAc,CAClD,OAAM,IAAI,MACR,mBAAmB,cAAc,+BAA+B,MAAM,SAAS,QAAQ,GACxF;AAEH,KAAI,CAAC,QAAQ,OAAQ,QAAO,QAAQ,eAAe,gBAAgB,QAAQ,SAAS;CAEpF,MAAM,iBAAiB,MAAMC,sBAAAA,mBAAmB;EAC9C,UAAU,QAAQ;EAClB;EACA,oBAAoB,QAAQ,sBAAsBC,kBAAAA,iBAAiB;EACnE;EACA,cAAc,QAAQ;EACvB,CAAC;CACF,MAAM,WAAW,QAAQ,YAAYC,gCAAAA,sBAAsB;CAC3D,MAAM,UAAUC,gCAAAA,qBAAqB,OAAO,eAAe,SAAS;CACpE,MAAM,oBAA8B,EAAE;AACtC,MAAK,MAAM,aAAa,SAAS;EAC/B,MAAM,UAAU;GAAE,UAAU,QAAQ;GAAU;GAAO,SAAS,eAAe;GAAS;AACtF,MAAI,UAAU,WAAW,CAAE,MAAM,UAAU,QAAQ,QAAQ,CAAG;AAC9D,QAAM,UAAU,KAAK,QAAQ;AAC7B,oBAAkB,KAAK,UAAU,GAAG;;AAEtC,gBAAe,YAAY,oBAAoB,CAC7C,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,mBAAmB,GAAG,kBAAkB,CAAC,CAC/D;CAED,MAAM,wBAAwB,MAAMC,gCAAAA,uBAClC;EACE,UAAU,QAAQ;EAClB,OAAO,eAAe;EACtB,SAAS,eAAe;EACzB,EACD,SACD;AACD,MAAK,MAAM,cAAc,uBAAuB;AAC9C,MAAI,WAAW,aAAa,QAAS;AACrC,iBAAe,QAAQ,YAAY;GACjC,MAAM,WAAW,QAAQ;GACzB,QAAQ,IAAI,WAAW,KAAK,IAAI,WAAW;GAC5C,CAAC;;CAGJ,MAAM,eAAe,OAAO,KAAK,GAAG,KAAK,UAAU,eAAe,aAAa,MAAM,EAAE,CAAC,IAAI;CAC5F,MAAM,eAAe,MAAM,eAAe,QAAQ,KAAKC,kBAAAA,gBAAgB;AACvE,KAAI,CAAC,gBAAgB,CAAC,aAAa,OAAO,aAAa,CACrD,gBAAe,QAAQ,UAAU;EAC/B,MAAM,eAAe,WAAW;EAChC,MAAMA,kBAAAA;EACN,SAAS;EACT,QAAQ;EACT,CAAC;CAGJ,MAAM,SAAuB;EAC3B,aAAa,MAAM,SAAS;EAC5B;EACA,QAAQ,QAAQ;EAChB,SAAS;EACT,SAAS,CAAC,GAAG,eAAe,QAAQ,QAAQ,QAAQ,CAAC,CAAC,KACnD,EAAE,SAAS,UAAU,GAAG,aAAa,OACvC;EACD,WAAW,CAAC,GAAG,eAAe,QAAQ,UAAU;EAChD,YAAY;EACZ,qBAAqB,eAAe;EACpC,iBAAiB,eAAe,uBAAuB,QAAQ;EAChE;AACD,KAAI,QAAQ,UAAU,OAAO,UAAU,SAAS,EAAG,QAAO;AAE1D,OAAM,eAAe,QAAQ,OAAO;AACpC,QAAO,UAAU;AACjB,KAAI,eAAe,uBAAuB,CAAC,QAAQ,YACjD,KAAI,QAAQ,QAAS,OAAM,QAAQ,QAAQC,UAAAA,QAAK,QAAQ,QAAQ,SAAS,CAAC;KAExE,OAAM,cAAc,QAAQ,CAAC,UAAU,EAAE;EACvC,KAAKA,UAAAA,QAAK,QAAQ,QAAQ,SAAS;EACnC,KAAK,QAAQ;EACd,CAAC;AAEN,QAAO"}
1
+ {"version":3,"file":"update.cjs","names":["execFile","resolveHarnessAndModuleOptions","getEnabledFeatures","fs","path","collectTemplateFiles","withImpliedFeatures","readManagedState","getCliVersion","getTemplateRoot","reconcileTemplates","getMigrationRegistry","getPendingMigrations","runMigrationValidators","STATE_FILE_NAME"],"sources":["../../src/update.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 type { ChangeSet, PlannedChange, PlannedConflict } from \"./changes\";\r\nimport { collectTemplateFiles } from \"./fs\";\r\nimport {\r\n getMigrationRegistry,\r\n getPendingMigrations,\r\n runMigrationValidators,\r\n} from \"./migrations/registry\";\r\nimport type { MigrationDefinition } from \"./migrations/types\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { resolveHarnessAndModuleOptions } from \"./prompts\";\r\nimport { type BaseTemplateProvider, reconcileTemplates } from \"./reconcile\";\r\nimport { getCliVersion, type ManagedState, readManagedState, STATE_FILE_NAME } from \"./state\";\r\nimport { getEnabledFeatures, withImpliedFeatures } from \"./template\";\r\nimport type { AppPlatform } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport interface UpdateCommandOptions {\r\n repoRoot: string;\r\n dryRun: boolean;\r\n skipInstall: boolean;\r\n yes?: boolean;\r\n platform?: AppPlatform;\r\n testHarness?: boolean;\r\n modules?: string[];\r\n baseProvider?: BaseTemplateProvider;\r\n /** Test seam; published commands always use the embedded production registry. */\r\n registry?: readonly MigrationDefinition[];\r\n /** Test seams for fixture-version updates. */\r\n targetTemplateRoot?: string;\r\n targetVersion?: string;\r\n assertClean?: (repoRoot: string) => Promise<void>;\r\n install?: (repoRoot: string) => Promise<void>;\r\n}\r\n\r\nexport interface UpdateResult {\r\n fromVersion: string;\r\n targetVersion: string;\r\n dryRun: boolean;\r\n applied: boolean;\r\n changes: Array<Omit<PlannedChange, \"content\">>;\r\n conflicts: PlannedConflict[];\r\n migrations: string[];\r\n dependenciesChanged: boolean;\r\n installRequired: boolean;\r\n}\r\n\r\nasync function assertCleanGit(repoRoot: string): Promise<void> {\r\n const result = await execFileAsync(\"git\", [\"status\", \"--porcelain\"], {\r\n cwd: repoRoot,\r\n encoding: \"utf8\",\r\n });\r\n if (result.stdout.trim()) {\r\n throw new Error(\r\n \"Write-mode update requires a clean Git worktree. Commit or stash changes first.\"\r\n );\r\n }\r\n}\r\n\r\nasync function resolveUpgradeState(options: {\r\n state: ManagedState;\r\n platform?: AppPlatform;\r\n yes: boolean;\r\n testHarness?: boolean;\r\n modules?: string[];\r\n}): Promise<ManagedState> {\r\n if (!options.platform) return options.state;\r\n if (options.platform === \"landing\") {\r\n throw new Error(\"m5kdev update --platform cannot switch to landing.\");\r\n }\r\n const current = new Set(options.state.template.features);\r\n if (current.has(\"webapp\") || current.has(\"expo\")) {\r\n throw new Error(\r\n \"m5kdev update --platform is only for landing-only apps. This repository already has a Webapp or Expo client.\"\r\n );\r\n }\r\n const extras = await resolveHarnessAndModuleOptions({\r\n yes: options.yes,\r\n testHarness: options.testHarness,\r\n modules: options.modules,\r\n });\r\n const enabledFeatures = getEnabledFeatures({\r\n platform: options.platform,\r\n testHarness: extras.testHarness,\r\n modules: extras.modules,\r\n });\r\n return {\r\n ...options.state,\r\n template: {\r\n ...options.state.template,\r\n features: [...enabledFeatures].sort(),\r\n },\r\n };\r\n}\r\n\r\nfunction overlayEnv(base: string, overlay: string): string {\r\n if (!overlay.trim()) return base;\r\n let next = base;\r\n for (const line of overlay.split(\"\\n\")) {\r\n const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);\r\n if (!match) continue;\r\n const [, key, value] = match;\r\n const assignment = `${key}=${value}`;\r\n const pattern = new RegExp(`^${key}=.*$`, \"m\");\r\n if (pattern.test(next)) next = next.replace(pattern, assignment);\r\n else next = `${next}${next.endsWith(\"\\n\") || next.length === 0 ? \"\" : \"\\n\"}${assignment}\\n`;\r\n }\r\n return next;\r\n}\r\n\r\nasync function planLandingEnvMigration(options: {\r\n repoRoot: string;\r\n templateRoot: string;\r\n state: ManagedState;\r\n changes: ChangeSet;\r\n}): Promise<void> {\r\n const sharedEnvRelative = \"apps/shared/.env\";\r\n const landingEnvRelative = \"apps/landing/.env\";\r\n const landingExampleRelative = \"apps/landing/.env.example\";\r\n const landingEnv = await fs\r\n .readFile(path.join(options.repoRoot, landingEnvRelative), \"utf8\")\r\n .catch(() => \"\");\r\n const existing = await options.changes.read(sharedEnvRelative);\r\n const files = await collectTemplateFiles(\r\n options.templateRoot,\r\n {\r\n ...options.state.template.context,\r\n betterAuthSecret: \"ignored-by-managed-state\",\r\n },\r\n {\r\n enabledFeatures: withImpliedFeatures(new Set(options.state.template.features)),\r\n }\r\n );\r\n const sharedEnv = files.find((file) => file.relativePath === sharedEnvRelative);\r\n const base = existing?.toString(\"utf8\") || sharedEnv?.content.toString(\"utf8\") || \"\";\r\n if (base.length > 0) {\r\n const content = overlayEnv(base, landingEnv);\r\n if (!existing || content !== existing.toString(\"utf8\")) {\r\n options.changes.addChange({\r\n kind: existing ? \"modify\" : \"add\",\r\n path: sharedEnvRelative,\r\n content: Buffer.from(content),\r\n reason: \"Copy Landing env into Deploy home\",\r\n });\r\n }\r\n }\r\n for (const relativePath of [landingEnvRelative, landingExampleRelative]) {\r\n const present = await fs\r\n .access(path.join(options.repoRoot, relativePath))\r\n .then(() => true)\r\n .catch(() => false);\r\n if (!present) continue;\r\n options.changes.addChange({\r\n kind: \"delete\",\r\n path: relativePath,\r\n reason: \"Remove Landing-local env after attaching Deploy home\",\r\n });\r\n }\r\n}\r\n\r\nexport async function updateManagedRepo(options: UpdateCommandOptions): Promise<UpdateResult> {\r\n const state = await readManagedState(options.repoRoot);\r\n const targetVersion = options.targetVersion ?? getCliVersion();\r\n if (semver.gt(state.template.version, targetVersion)) {\r\n throw new Error(\r\n `The running CLI ${targetVersion} is older than managed state ${state.template.version}.`\r\n );\r\n }\r\n const upgradedState = await resolveUpgradeState({\r\n state,\r\n platform: options.platform,\r\n yes: Boolean(options.yes),\r\n testHarness: options.testHarness,\r\n modules: options.modules,\r\n });\r\n if (!options.dryRun) await (options.assertClean ?? assertCleanGit)(options.repoRoot);\r\n\r\n const templateRoot = options.targetTemplateRoot ?? getTemplateRoot();\r\n const reconciliation = await reconcileTemplates({\r\n repoRoot: options.repoRoot,\r\n state: upgradedState,\r\n targetTemplateRoot: templateRoot,\r\n targetVersion,\r\n baseProvider: options.baseProvider,\r\n });\r\n if (options.platform && options.platform !== \"landing\") {\r\n await planLandingEnvMigration({\r\n repoRoot: options.repoRoot,\r\n templateRoot,\r\n state: upgradedState,\r\n changes: reconciliation.changes,\r\n });\r\n }\r\n const registry = options.registry ?? getMigrationRegistry();\r\n const pending = getPendingMigrations(upgradedState, targetVersion, registry);\r\n const appliedMigrations: string[] = [];\r\n for (const migration of pending) {\r\n const context = {\r\n repoRoot: options.repoRoot,\r\n state: upgradedState,\r\n changes: reconciliation.changes,\r\n };\r\n if (migration.applies && !(await migration.applies(context))) continue;\r\n await migration.plan(context);\r\n appliedMigrations.push(migration.id);\r\n }\r\n reconciliation.targetState.appliedMigrations = [\r\n ...new Set([...upgradedState.appliedMigrations, ...appliedMigrations]),\r\n ];\r\n\r\n const validationDiagnostics = await runMigrationValidators(\r\n {\r\n repoRoot: options.repoRoot,\r\n state: reconciliation.targetState,\r\n changes: reconciliation.changes,\r\n },\r\n registry\r\n );\r\n for (const diagnostic of validationDiagnostics) {\r\n if (diagnostic.severity !== \"error\") continue;\r\n reconciliation.changes.addConflict({\r\n path: diagnostic.path ?? \".m5kdev.json\",\r\n reason: `[${diagnostic.code}] ${diagnostic.message}`,\r\n });\r\n }\r\n\r\n const stateContent = Buffer.from(`${JSON.stringify(reconciliation.targetState, null, 2)}\\n`);\r\n const currentState = await reconciliation.changes.read(STATE_FILE_NAME);\r\n if (!currentState?.equals(stateContent)) {\r\n reconciliation.changes.addChange({\r\n kind: currentState ? \"modify\" : \"add\",\r\n path: STATE_FILE_NAME,\r\n content: stateContent,\r\n reason: \"Advance managed template state\",\r\n });\r\n }\r\n\r\n const result: UpdateResult = {\r\n fromVersion: state.template.version,\r\n targetVersion,\r\n dryRun: options.dryRun,\r\n applied: false,\r\n changes: [...reconciliation.changes.changes.values()].map(\r\n ({ content: _content, ...change }) => change\r\n ),\r\n conflicts: [...reconciliation.changes.conflicts],\r\n migrations: appliedMigrations,\r\n dependenciesChanged: reconciliation.dependenciesChanged,\r\n installRequired: reconciliation.dependenciesChanged && options.skipInstall,\r\n };\r\n if (options.dryRun || result.conflicts.length > 0) return result;\r\n\r\n await reconciliation.changes.apply();\r\n result.applied = true;\r\n if (reconciliation.dependenciesChanged && !options.skipInstall) {\r\n if (options.install) await options.install(path.resolve(options.repoRoot));\r\n else\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: path.resolve(options.repoRoot),\r\n env: process.env,\r\n });\r\n }\r\n return result;\r\n}\r\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { PlannedChange, PlannedConflict } from "./changes.cjs";
2
+ import { AppPlatform } from "./types.cjs";
2
3
  import { MigrationDefinition } from "./migrations/types.cjs";
3
4
  import { BaseTemplateProvider } from "./reconcile.cjs";
4
5
 
@@ -7,6 +8,10 @@ interface UpdateCommandOptions {
7
8
  repoRoot: string;
8
9
  dryRun: boolean;
9
10
  skipInstall: boolean;
11
+ yes?: boolean;
12
+ platform?: AppPlatform;
13
+ testHarness?: boolean;
14
+ modules?: string[];
10
15
  baseProvider?: BaseTemplateProvider;
11
16
  /** Test seam; published commands always use the embedded production registry. */
12
17
  registry?: readonly MigrationDefinition[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m5kdev",
3
- "version": "0.38.15",
3
+ "version": "0.38.17",
4
4
  "license": "GPL-3.0-only",
5
5
  "engines": {
6
6
  "node": ">=24"