create-m5kdev 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/catalog.cjs +21 -1
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +3 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs.map +1 -1
- package/dist/src/fs.cjs +10 -5
- package/dist/src/fs.cjs.map +1 -1
- package/dist/src/prompts.cjs +32 -0
- package/dist/src/prompts.cjs.map +1 -1
- package/dist/src/prompts.d.cts +7 -1
- package/dist/src/template.cjs +23 -4
- package/dist/src/template.cjs.map +1 -1
- package/dist/src/template.d.cts +17 -3
- package/dist/src/types.d.cts +11 -4
- package/package.json +4 -1
- package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +1 -0
- package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
- package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
- package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/server-event-emit.mdc +41 -0
- package/templates/minimal-app/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +14 -10
- package/templates/minimal-app/CLAUDE.md.tpl +133 -131
- package/templates/minimal-app/README.md.tpl +51 -19
- package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
- package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
- package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
- package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
- package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
- package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
- package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
- package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
- package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
- package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
- package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
- package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
- package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
- package/templates/minimal-app/apps/email/package.json.tpl +34 -34
- package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
- package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
- package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
- package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
- package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
- package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
- package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
- package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
- package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
- package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
- package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
- package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
- package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
- package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
- package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
- package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
- package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
- package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
- package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
- package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
- package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
- package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
- package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
- package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
- package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
- package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
- package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
- package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
- package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
- package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
- package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
- package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
- package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
- package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
- package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
- package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +55 -46
- package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
- package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
- package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +5 -3
- package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +57 -54
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
- package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
- package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
- package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
- package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
- package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
- package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
- package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
- package/templates/minimal-app/apps/shared/.env.tpl +5 -0
- package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
- package/templates/minimal-app/apps/shared/package.json.tpl +26 -24
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
- package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/demo-workflow/demo-workflow.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
- package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
- package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
- package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +49 -44
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
- package/templates/minimal-app/apps/webapp/src/StarterServerEventProvider.tsx.tpl +38 -0
- package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
- package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
- package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
- package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
- package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
- package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
- package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
- package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
- package/templates/minimal-app/biome.json.tpl +17 -12
- package/templates/minimal-app/package.json.tpl +7 -2
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
- package/templates/minimal-app/template.manifest.json +93 -2
- package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
- package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
- package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
- package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
package/dist/src/catalog.cjs
CHANGED
|
@@ -51,6 +51,24 @@ function collectCatalogProtocolDependenciesFromManifests(manifests) {
|
|
|
51
51
|
function collectCatalogProtocolDependencies(packageFiles) {
|
|
52
52
|
return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));
|
|
53
53
|
}
|
|
54
|
+
/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
|
|
55
|
+
function collectOptionalBackendModulePins(repoRoot) {
|
|
56
|
+
const packagesDir = node_path.default.join(repoRoot, "packages");
|
|
57
|
+
const pins = {};
|
|
58
|
+
if (!node_fs.default.existsSync(packagesDir)) return pins;
|
|
59
|
+
for (const entry of node_fs.default.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
60
|
+
if (!entry.isDirectory() || !entry.name.startsWith("module-")) continue;
|
|
61
|
+
const manifestPath = node_path.default.join(packagesDir, entry.name, "package.json");
|
|
62
|
+
if (!node_fs.default.existsSync(manifestPath)) continue;
|
|
63
|
+
const manifest = readJson(manifestPath);
|
|
64
|
+
const name = manifest.name;
|
|
65
|
+
const version = manifest.version;
|
|
66
|
+
if (typeof name !== "string" || typeof version !== "string") continue;
|
|
67
|
+
if (!name.startsWith("@m5kdev/module-")) continue;
|
|
68
|
+
pins[name] = version;
|
|
69
|
+
}
|
|
70
|
+
return pins;
|
|
71
|
+
}
|
|
54
72
|
function buildConsumerCatalog(options) {
|
|
55
73
|
const workspacePath = node_path.default.join(options.repoRoot, "pnpm-workspace.yaml");
|
|
56
74
|
const sourceCatalog = (0, yaml.parse)(node_fs.default.readFileSync(workspacePath, "utf8")).catalog ?? {};
|
|
@@ -80,8 +98,9 @@ function buildConsumerCatalog(options) {
|
|
|
80
98
|
}
|
|
81
99
|
catalog[name] = String(version);
|
|
82
100
|
}
|
|
101
|
+
Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));
|
|
83
102
|
if (missing.length > 0) throw new Error(`Missing consumer catalog versions for: ${missing.join(", ")}`);
|
|
84
|
-
return catalog;
|
|
103
|
+
return sortedCatalog(catalog);
|
|
85
104
|
}
|
|
86
105
|
function sortedCatalog(catalog) {
|
|
87
106
|
return Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -175,6 +194,7 @@ exports.collectCatalogProtocolDependencies = collectCatalogProtocolDependencies;
|
|
|
175
194
|
exports.collectCatalogProtocolDependenciesFromManifests = collectCatalogProtocolDependenciesFromManifests;
|
|
176
195
|
exports.collectConsumerDependencyNames = collectConsumerDependencyNames;
|
|
177
196
|
exports.collectConsumerDependencyNamesFromManifests = collectConsumerDependencyNamesFromManifests;
|
|
197
|
+
exports.collectOptionalBackendModulePins = collectOptionalBackendModulePins;
|
|
178
198
|
exports.mergeManagedCatalog = mergeManagedCatalog;
|
|
179
199
|
exports.readCatalog = readCatalog;
|
|
180
200
|
exports.readDefaultCatalog = readDefaultCatalog;
|
package/dist/src/catalog.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport const MANAGED_CATALOG_NAME = \"m5kdev\";\r\nexport const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\ninterface WorkspaceCatalogFile {\r\n catalog?: Record<string, string | number>;\r\n catalogs?: Record<string, Record<string, string | number> | undefined>;\r\n}\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nfunction asCatalog(value: Record<string, string | number> | undefined): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(value ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport interface CatalogProtocolDependency {\r\n name: string;\r\n specifier: string;\r\n}\r\n\r\nexport function collectCatalogProtocolDependenciesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): CatalogProtocolDependency[] {\r\n const dependencies: CatalogProtocolDependency[] = [];\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n for (const [name, specifier] of Object.entries(\r\n (manifest[field] as Record<string, string> | undefined) ?? {}\r\n )) {\r\n if (specifier.startsWith(\"catalog:\")) dependencies.push({ name, specifier });\r\n }\r\n }\r\n }\r\n return dependencies;\r\n}\r\n\r\nexport function collectCatalogProtocolDependencies(\r\n packageFiles: readonly string[]\r\n): CatalogProtocolDependency[] {\r\n return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return catalog;\r\n}\r\n\r\nexport function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n const catalogs =\r\n workspace.catalogs && typeof workspace.catalogs === \"object\"\r\n ? { ...(workspace.catalogs as Record<string, unknown>) }\r\n : {};\r\n catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);\r\n workspace.catalogs = catalogs;\r\n if (workspace.catalog === undefined) workspace.catalog = {};\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readDefaultCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];\r\n if (named !== undefined) return asCatalog(named);\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const workspace = parse(options.source) as WorkspaceCatalogFile;\r\n const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== undefined;\r\n const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);\r\n const fallback = asCatalog(workspace.catalog);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n const managedPath = [\"catalogs\", MANAGED_CATALOG_NAME] as const;\r\n\r\n if (document.getIn([...managedPath]) === undefined) {\r\n if (document.get(\"catalogs\") === undefined) {\r\n document.set(\"catalogs\", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));\r\n } else {\r\n document.setIn([...managedPath], document.createNode({}));\r\n }\r\n }\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = (namedPresent ? named[name] : undefined) ?? fallback[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) {\r\n document.deleteIn([...managedPath, name]);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport const MANAGED_CATALOG_NAME = \"m5kdev\";\r\nexport const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\ninterface WorkspaceCatalogFile {\r\n catalog?: Record<string, string | number>;\r\n catalogs?: Record<string, Record<string, string | number> | undefined>;\r\n}\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nfunction asCatalog(value: Record<string, string | number> | undefined): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(value ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport interface CatalogProtocolDependency {\r\n name: string;\r\n specifier: string;\r\n}\r\n\r\nexport function collectCatalogProtocolDependenciesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): CatalogProtocolDependency[] {\r\n const dependencies: CatalogProtocolDependency[] = [];\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n for (const [name, specifier] of Object.entries(\r\n (manifest[field] as Record<string, string> | undefined) ?? {}\r\n )) {\r\n if (specifier.startsWith(\"catalog:\")) dependencies.push({ name, specifier });\r\n }\r\n }\r\n }\r\n return dependencies;\r\n}\r\n\r\nexport function collectCatalogProtocolDependencies(\r\n packageFiles: readonly string[]\r\n): CatalogProtocolDependency[] {\r\n return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\n/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */\r\nexport function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog {\r\n const packagesDir = path.join(repoRoot, \"packages\");\r\n const pins: ConsumerCatalog = {};\r\n if (!fs.existsSync(packagesDir)) return pins;\r\n\r\n for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {\r\n if (!entry.isDirectory() || !entry.name.startsWith(\"module-\")) continue;\r\n const manifestPath = path.join(packagesDir, entry.name, \"package.json\");\r\n if (!fs.existsSync(manifestPath)) continue;\r\n const manifest = readJson(manifestPath);\r\n const name = manifest.name;\r\n const version = manifest.version;\r\n if (typeof name !== \"string\" || typeof version !== \"string\") continue;\r\n if (!name.startsWith(\"@m5kdev/module-\")) continue;\r\n pins[name] = version;\r\n }\r\n\r\n return pins;\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return sortedCatalog(catalog);\r\n}\r\n\r\nexport function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n const catalogs =\r\n workspace.catalogs && typeof workspace.catalogs === \"object\"\r\n ? { ...(workspace.catalogs as Record<string, unknown>) }\r\n : {};\r\n catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);\r\n workspace.catalogs = catalogs;\r\n if (workspace.catalog === undefined) workspace.catalog = {};\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readDefaultCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];\r\n if (named !== undefined) return asCatalog(named);\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const workspace = parse(options.source) as WorkspaceCatalogFile;\r\n const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== undefined;\r\n const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);\r\n const fallback = asCatalog(workspace.catalog);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n const managedPath = [\"catalogs\", MANAGED_CATALOG_NAME] as const;\r\n\r\n if (document.getIn([...managedPath]) === undefined) {\r\n if (document.get(\"catalogs\") === undefined) {\r\n document.set(\"catalogs\", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));\r\n } else {\r\n document.setIn([...managedPath], document.createNode({}));\r\n }\r\n }\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = (namedPresent ? named[name] : undefined) ?? fallback[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) {\r\n document.deleteIn([...managedPath, name]);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":";;;;;;;;AAIA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACD;AAED,MAAa,uBAAuB;AACpC,MAAa,4BAA4B,WAAW;AASpD,SAAgB,qBAAqB,WAAmB,MAAgB,EAAE,EAAY;AACpF,MAAK,MAAM,SAASA,QAAAA,QAAG,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,EAAE;AACtE,MAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,OAAQ;EAC5D,MAAM,YAAYC,UAAAA,QAAK,KAAK,WAAW,MAAM,KAAK;AAClD,MAAI,MAAM,aAAa,CACrB,sBAAqB,WAAW,IAAI;WAC3B,MAAM,QAAQ,IAAI,MAAM,SAAS,eAC1C,KAAI,KAAK,UAAU;;AAGvB,QAAO;;AAGT,SAAS,SAAS,UAA2C;AAC3D,QAAO,KAAK,MAAMD,QAAAA,QAAG,aAAa,UAAU,OAAO,CAAC;;AAGtD,SAAS,UAAU,OAAqE;AACtF,QAAO,OAAO,YACZ,OAAO,QAAQ,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,aAAa,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC,CAC9E;;AAGH,SAAgB,4CACd,WACU;CACV,MAAM,wBAAQ,IAAI,KAAa;AAE/B,MAAK,MAAM,YAAY,UACrB,MAAK,MAAM,SAAS,mBAAmB;EACrC,MAAM,eAAe,SAAS;AAC9B,OAAK,MAAM,CAAC,MAAM,cAAc,OAAO,QAAQ,gBAAgB,EAAE,CAAC,CAChE,KACE,UAAU,WAAW,WAAW,IAC/B,KAAK,WAAW,WAAW,IAAI,UAAU,WAAW,aAAa,CAElE,OAAM,IAAI,KAAK;;AAMvB,QAAO,CAAC,GAAG,MAAM,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC;;AAGpE,SAAgB,+BAA+B,cAA2C;AACxF,QAAO,4CAA4C,aAAa,IAAI,SAAS,CAAC;;AAQhF,SAAgB,gDACd,WAC6B;CAC7B,MAAM,eAA4C,EAAE;AACpD,MAAK,MAAM,YAAY,UACrB,MAAK,MAAM,SAAS,kBAClB,MAAK,MAAM,CAAC,MAAM,cAAc,OAAO,QACpC,SAAS,UAAiD,EAAE,CAC9D,CACC,KAAI,UAAU,WAAW,WAAW,CAAE,cAAa,KAAK;EAAE;EAAM;EAAW,CAAC;AAIlF,QAAO;;AAGT,SAAgB,mCACd,cAC6B;AAC7B,QAAO,gDAAgD,aAAa,IAAI,SAAS,CAAC;;;AAIpF,SAAgB,iCAAiC,UAAmC;CAClF,MAAM,cAAcC,UAAAA,QAAK,KAAK,UAAU,WAAW;CACnD,MAAM,OAAwB,EAAE;AAChC,KAAI,CAACD,QAAAA,QAAG,WAAW,YAAY,CAAE,QAAO;AAExC,MAAK,MAAM,SAASA,QAAAA,QAAG,YAAY,aAAa,EAAE,eAAe,MAAM,CAAC,EAAE;AACxE,MAAI,CAAC,MAAM,aAAa,IAAI,CAAC,MAAM,KAAK,WAAW,UAAU,CAAE;EAC/D,MAAM,eAAeC,UAAAA,QAAK,KAAK,aAAa,MAAM,MAAM,eAAe;AACvE,MAAI,CAACD,QAAAA,QAAG,WAAW,aAAa,CAAE;EAClC,MAAM,WAAW,SAAS,aAAa;EACvC,MAAM,OAAO,SAAS;EACtB,MAAM,UAAU,SAAS;AACzB,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,SAAU;AAC7D,MAAI,CAAC,KAAK,WAAW,kBAAkB,CAAE;AACzC,OAAK,QAAQ;;AAGf,QAAO;;AAGT,SAAgB,qBAAqB,SAIjB;CAClB,MAAM,gBAAgBC,UAAAA,QAAK,KAAK,QAAQ,UAAU,sBAAsB;CAIxE,MAAM,iBAAA,GAAA,KAAA,OAHkBD,QAAAA,QAAG,aAAa,eAAe,OAAO,CAAC,CAG/B,WAAW,EAAE;CAK7C,MAAM,QAAQ,+BAJO,CACnB,GAAG,qBAAqB,QAAQ,iBAAiB,EACjDC,UAAAA,QAAK,KAAK,QAAQ,wBAAwB,mBAAmB,CAC9D,CACyD;CAC1D,MAAM,UAA2B,EAAE;CACnC,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,WAAW,WAAW,EAAE;GAC/B,MAAM,cAAc,KAAK,MAAM,EAAkB;GACjD,MAAM,cAAcA,UAAAA,QAAK,KAAK,QAAQ,UAAU,YAAY,aAAa,eAAe;AACxF,OAAI,CAACD,QAAAA,QAAG,WAAW,YAAY,EAAE;AAC/B,YAAQ,KAAK,KAAK;AAClB;;GAEF,MAAM,kBAAkB,SAAS,YAAY;AAC7C,OAAI,OAAO,gBAAgB,YAAY,UAAU;AAC/C,YAAQ,KAAK,KAAK;AAClB;;AAEF,WAAQ,QAAQ,gBAAgB;AAChC;;EAGF,MAAM,UAAU,cAAc;AAC9B,MAAI,YAAY,KAAA,GAAW;AACzB,WAAQ,KAAK,KAAK;AAClB;;AAEF,UAAQ,QAAQ,OAAO,QAAQ;;AAGjC,QAAO,OAAO,SAAS,iCAAiC,QAAQ,SAAS,CAAC;AAE1E,KAAI,QAAQ,SAAS,EACnB,OAAM,IAAI,MAAM,0CAA0C,QAAQ,KAAK,KAAK,GAAG;AAGjF,QAAO,cAAc,QAAQ;;AAG/B,SAAgB,cAAc,SAA2C;AACvE,QAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CAC7E;;AAGH,SAAgB,wBAAwB,QAAgB,SAAkC;CACxF,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;CAC/B,MAAM,WACJ,UAAU,YAAY,OAAO,UAAU,aAAa,WAChD,EAAE,GAAI,UAAU,UAAsC,GACtD,EAAE;AACR,UAAS,wBAAwB,cAAc,QAAQ;AACvD,WAAU,WAAW;AACrB,KAAI,UAAU,YAAY,KAAA,EAAW,WAAU,UAAU,EAAE;AAC3D,SAAA,GAAA,KAAA,WAAiB,WAAW,EAAE,WAAW,GAAG,CAAC;;AAG/C,SAAgB,mBAAmB,QAAiC;AAElE,QAAO,WAAA,GAAA,KAAA,OADiB,OAAO,CACJ,QAAQ;;AAGrC,SAAgB,YAAY,QAAiC;CAC3D,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;CAC/B,MAAM,QAAQ,UAAU,WAAW;AACnC,KAAI,UAAU,KAAA,EAAW,QAAO,UAAU,MAAM;AAChD,QAAO,UAAU,UAAU,QAAQ;;AAGrC,SAAgB,kBACd,SACA,eACA,QAAQ,oBACF;CACN,MAAM,WAAW,IAAI,IAAI,cAAc;CACvC,MAAM,UAAU,cAAc,QAAQ,SAAS,EAAE,QAAQ,SAAS;CAClE,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,QAAQ,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC;AAC3E,KAAI,QAAQ,SAAS,KAAK,SAAS,SAAS,GAAG;EAC7C,MAAM,UAAU,CACd,QAAQ,SAAS,IAAI,YAAY,QAAQ,KAAK,KAAK,KAAK,KAAA,GACxD,SAAS,SAAS,IAAI,aAAa,SAAS,KAAK,KAAK,KAAK,KAAA,EAC5D,CAAC,OAAO,QAAQ;AACjB,QAAM,IAAI,MAAM,WAAW,MAAM,IAAI,QAAQ,KAAK,KAAK,CAAC,GAAG;;;AAW/D,SAAgB,oBAAoB,SAIwC;CAC1E,MAAM,YAAA,GAAA,KAAA,eAAyB,QAAQ,OAAO;CAC9C,MAAM,aAAA,GAAA,KAAA,OAAkB,QAAQ,OAAO;CACvC,MAAM,eAAe,UAAU,WAAW,0BAA0B,KAAA;CACpE,MAAM,QAAQ,UAAU,UAAU,WAAW,sBAAsB;CACnE,MAAM,WAAW,UAAU,UAAU,QAAQ;CAC7C,MAAM,YAAoC,EAAE;CAC5C,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,KAAK,EAAE,GAAG,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC;CACrF,MAAM,cAAc,CAAC,YAAY,qBAAqB;AAEtD,KAAI,SAAS,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,KAAA,EACvC,KAAI,SAAS,IAAI,WAAW,KAAK,KAAA,EAC/B,UAAS,IAAI,YAAY,SAAS,WAAW,GAAG,uBAAuB,EAAE,EAAE,CAAC,CAAC;KAE7E,UAAS,MAAM,CAAC,GAAG,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC,CAAC;AAI7D,MAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC,EAAE;EAC9E,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,WAAW,eAAe,MAAM,QAAQ,KAAA,MAAc,SAAS;EACrE,MAAM,SAAS,QAAQ,OAAO;AAE9B,MAAI,SAAS,KAAA,GAAW;AACtB,OAAI,YAAY,KAAA,GAAW;AACzB,aAAS,MAAM,CAAC,GAAG,aAAa,KAAK,EAAE,OAAO;AAC9C,aAAS,SAAS,CAAC,WAAW,KAAK,CAAC;cAC3B,YAAY,OAAQ,WAAU,KAAK;IAAE;IAAM,OAAO;IAAS;IAAQ,CAAC;AAC/E;;AAEF,MAAI,WAAW,KAAA,GAAW;AACxB,OAAI,YAAY,MAAM;AACpB,aAAS,SAAS,CAAC,GAAG,aAAa,KAAK,CAAC;AACzC,aAAS,SAAS,CAAC,WAAW,KAAK,CAAC;cAC3B,YAAY,KAAA,EAAW,WAAU,KAAK;IAAE;IAAM;IAAM,OAAO;IAAS,CAAC;AAChF;;AAEF,MAAI,YAAY,MAAM;AACpB,YAAS,MAAM,CAAC,GAAG,aAAa,KAAK,EAAE,OAAO;AAC9C,YAAS,SAAS,CAAC,WAAW,KAAK,CAAC;aAC3B,YAAY,UAAU,WAAW,KAC1C,WAAU,KAAK;GAAE;GAAM;GAAM,OAAO;GAAS;GAAQ,CAAC;;CAI1D,MAAM,SAAS,SAAS,SAAS,EAAE,WAAW,GAAG,CAAC;AAClD,QAAO;EAAE,QAAQ;EAAQ,SAAS,WAAW,QAAQ;EAAQ;EAAW"}
|
package/dist/src/catalog.d.cts
CHANGED
|
@@ -11,6 +11,8 @@ interface CatalogProtocolDependency {
|
|
|
11
11
|
}
|
|
12
12
|
declare function collectCatalogProtocolDependenciesFromManifests(manifests: readonly Record<string, unknown>[]): CatalogProtocolDependency[];
|
|
13
13
|
declare function collectCatalogProtocolDependencies(packageFiles: readonly string[]): CatalogProtocolDependency[];
|
|
14
|
+
/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
|
|
15
|
+
declare function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog;
|
|
14
16
|
declare function buildConsumerCatalog(options: {
|
|
15
17
|
repoRoot: string;
|
|
16
18
|
starterDirectory: string;
|
|
@@ -37,5 +39,5 @@ declare function mergeManagedCatalog(options: {
|
|
|
37
39
|
conflicts: CatalogMergeConflict[];
|
|
38
40
|
};
|
|
39
41
|
//#endregion
|
|
40
|
-
export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
|
|
42
|
+
export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, collectOptionalBackendModulePins, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
|
|
41
43
|
//# sourceMappingURL=catalog.d.cts.map
|
package/dist/src/create.cjs
CHANGED
|
@@ -4,8 +4,8 @@ const require_src_constants = require("./constants.cjs");
|
|
|
4
4
|
const require_src_strings = require("./strings.cjs");
|
|
5
5
|
const require_src_fs = require("./fs.cjs");
|
|
6
6
|
const require_src_paths = require("./paths.cjs");
|
|
7
|
-
const require_src_prompts = require("./prompts.cjs");
|
|
8
7
|
const require_src_template = require("./template.cjs");
|
|
8
|
+
const require_src_prompts = require("./prompts.cjs");
|
|
9
9
|
const require_src_state = require("./state.cjs");
|
|
10
10
|
let node_path = require("node:path");
|
|
11
11
|
node_path = require_runtime.__toESM(node_path);
|
|
@@ -26,7 +26,11 @@ async function scaffoldProject(initialOptions) {
|
|
|
26
26
|
packageScope: require_src_strings.derivePackageScope(appSlug),
|
|
27
27
|
betterAuthSecret: require_src_strings.createBetterAuthSecret()
|
|
28
28
|
};
|
|
29
|
-
const enabledFeatures = require_src_template.getEnabledFeatures(
|
|
29
|
+
const enabledFeatures = require_src_template.getEnabledFeatures({
|
|
30
|
+
platform: options.platform ?? "web",
|
|
31
|
+
testHarness: Boolean(options.testHarness),
|
|
32
|
+
modules: options.modules ?? []
|
|
33
|
+
});
|
|
30
34
|
const manifest = require_src_template.loadTemplateManifest(templateDirectory);
|
|
31
35
|
const excludePrefixes = require_src_template.getExcludedFeaturePaths(manifest, enabledFeatures);
|
|
32
36
|
const createdTargetDirectory = await require_src_fs.ensureDirectoryState(targetDirectory, options.force);
|
package/dist/src/create.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.cjs","names":["resolveCreateCommandOptions","slugifyAppName","path","getTemplateRoot","derivePackageScope","createBetterAuthSecret","getEnabledFeatures","loadTemplateManifest","getExcludedFeaturePaths","ensureDirectoryState","copyTemplateDirectory","collectTemplateFiles","writeManagedState","createManagedState","getCliVersion","fs","runGitInit","runInstall","TEMPLATE_NAME"],"sources":["../../src/create.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport {\r\n collectTemplateFiles,\r\n copyTemplateDirectory,\r\n ensureDirectoryState,\r\n runGitInit,\r\n runInstall,\r\n} from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { resolveCreateCommandOptions } from \"./prompts\";\r\nimport { createManagedState, getCliVersion, writeManagedState } from \"./state\";\r\nimport { createBetterAuthSecret, derivePackageScope, slugifyAppName } from \"./strings\";\r\nimport { getEnabledFeatures, getExcludedFeaturePaths, loadTemplateManifest } from \"./template\";\r\nimport type { CreateCommandOptions, TemplateContext } from \"./types\";\r\n\r\nexport interface ScaffoldResult {\r\n context: TemplateContext;\r\n targetDirectory: string;\r\n templateName: string;\r\n}\r\n\r\nexport async function scaffoldProject(\r\n initialOptions: CreateCommandOptions\r\n): Promise<ScaffoldResult> {\r\n const options = await resolveCreateCommandOptions(initialOptions);\r\n const { appName, appDescription, targetDirectory: targetDirectoryOption } = options;\r\n if (!appName || !appDescription || !targetDirectoryOption) {\r\n throw new Error(\"Unable to resolve the required create options.\");\r\n }\r\n const appSlug = slugifyAppName(appName);\r\n const targetDirectory = path.resolve(process.cwd(), targetDirectoryOption);\r\n const templateDirectory = getTemplateRoot();\r\n\r\n const context: TemplateContext = {\r\n appName,\r\n appDescription,\r\n appSlug,\r\n packageScope: derivePackageScope(appSlug),\r\n betterAuthSecret: createBetterAuthSecret(),\r\n };\r\n\r\n const enabledFeatures = getEnabledFeatures(\r\n options.platform ?? \"web\",\r\n Boolean(options.testHarness)\r\n );\r\n const manifest = loadTemplateManifest(templateDirectory);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n\r\n const createdTargetDirectory = await ensureDirectoryState(targetDirectory, options.force);\r\n try {\r\n await copyTemplateDirectory(templateDirectory, targetDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n const renderedFiles = await collectTemplateFiles(templateDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n await writeManagedState(\r\n targetDirectory,\r\n createManagedState({\r\n templateVersion: getCliVersion(),\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n })\r\n );\r\n } catch (error) {\r\n if (createdTargetDirectory) {\r\n await fs.rm(targetDirectory, { recursive: true, force: true });\r\n }\r\n throw error;\r\n }\r\n\r\n if (!options.skipGit) {\r\n await runGitInit(targetDirectory);\r\n }\r\n\r\n if (!options.skipInstall) {\r\n await runInstall(targetDirectory);\r\n }\r\n\r\n return {\r\n context,\r\n targetDirectory,\r\n templateName: TEMPLATE_NAME,\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAsB,gBACpB,gBACyB;CACzB,MAAM,UAAU,MAAMA,oBAAAA,4BAA4B,eAAe;CACjE,MAAM,EAAE,SAAS,gBAAgB,iBAAiB,0BAA0B;AAC5E,KAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,sBAClC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,UAAUC,oBAAAA,eAAe,QAAQ;CACvC,MAAM,kBAAkBC,UAAAA,QAAK,QAAQ,QAAQ,KAAK,EAAE,sBAAsB;CAC1E,MAAM,oBAAoBC,kBAAAA,iBAAiB;CAE3C,MAAM,UAA2B;EAC/B;EACA;EACA;EACA,cAAcC,oBAAAA,mBAAmB,QAAQ;EACzC,kBAAkBC,oBAAAA,wBAAwB;EAC3C;CAED,MAAM,kBAAkBC,qBAAAA,
|
|
1
|
+
{"version":3,"file":"create.cjs","names":["resolveCreateCommandOptions","slugifyAppName","path","getTemplateRoot","derivePackageScope","createBetterAuthSecret","getEnabledFeatures","loadTemplateManifest","getExcludedFeaturePaths","ensureDirectoryState","copyTemplateDirectory","collectTemplateFiles","writeManagedState","createManagedState","getCliVersion","fs","runGitInit","runInstall","TEMPLATE_NAME"],"sources":["../../src/create.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport {\r\n collectTemplateFiles,\r\n copyTemplateDirectory,\r\n ensureDirectoryState,\r\n runGitInit,\r\n runInstall,\r\n} from \"./fs\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { resolveCreateCommandOptions } from \"./prompts\";\r\nimport { createManagedState, getCliVersion, writeManagedState } from \"./state\";\r\nimport { createBetterAuthSecret, derivePackageScope, slugifyAppName } from \"./strings\";\r\nimport { getEnabledFeatures, getExcludedFeaturePaths, loadTemplateManifest } from \"./template\";\r\nimport type { CreateCommandOptions, TemplateContext } from \"./types\";\r\n\r\nexport interface ScaffoldResult {\r\n context: TemplateContext;\r\n targetDirectory: string;\r\n templateName: string;\r\n}\r\n\r\nexport async function scaffoldProject(\r\n initialOptions: CreateCommandOptions\r\n): Promise<ScaffoldResult> {\r\n const options = await resolveCreateCommandOptions(initialOptions);\r\n const { appName, appDescription, targetDirectory: targetDirectoryOption } = options;\r\n if (!appName || !appDescription || !targetDirectoryOption) {\r\n throw new Error(\"Unable to resolve the required create options.\");\r\n }\r\n const appSlug = slugifyAppName(appName);\r\n const targetDirectory = path.resolve(process.cwd(), targetDirectoryOption);\r\n const templateDirectory = getTemplateRoot();\r\n\r\n const context: TemplateContext = {\r\n appName,\r\n appDescription,\r\n appSlug,\r\n packageScope: derivePackageScope(appSlug),\r\n betterAuthSecret: createBetterAuthSecret(),\r\n };\r\n\r\n const enabledFeatures = getEnabledFeatures({\r\n platform: options.platform ?? \"web\",\r\n testHarness: Boolean(options.testHarness),\r\n modules: options.modules ?? [],\r\n });\r\n const manifest = loadTemplateManifest(templateDirectory);\r\n const excludePrefixes = getExcludedFeaturePaths(manifest, enabledFeatures);\r\n\r\n const createdTargetDirectory = await ensureDirectoryState(targetDirectory, options.force);\r\n try {\r\n await copyTemplateDirectory(templateDirectory, targetDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n const renderedFiles = await collectTemplateFiles(templateDirectory, context, {\r\n excludePrefixes,\r\n enabledFeatures,\r\n });\r\n await writeManagedState(\r\n targetDirectory,\r\n createManagedState({\r\n templateVersion: getCliVersion(),\r\n enabledFeatures,\r\n context,\r\n renderedFiles,\r\n manifest,\r\n })\r\n );\r\n } catch (error) {\r\n if (createdTargetDirectory) {\r\n await fs.rm(targetDirectory, { recursive: true, force: true });\r\n }\r\n throw error;\r\n }\r\n\r\n if (!options.skipGit) {\r\n await runGitInit(targetDirectory);\r\n }\r\n\r\n if (!options.skipInstall) {\r\n await runInstall(targetDirectory);\r\n }\r\n\r\n return {\r\n context,\r\n targetDirectory,\r\n templateName: TEMPLATE_NAME,\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAuBA,eAAsB,gBACpB,gBACyB;CACzB,MAAM,UAAU,MAAMA,oBAAAA,4BAA4B,eAAe;CACjE,MAAM,EAAE,SAAS,gBAAgB,iBAAiB,0BAA0B;AAC5E,KAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,sBAClC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,UAAUC,oBAAAA,eAAe,QAAQ;CACvC,MAAM,kBAAkBC,UAAAA,QAAK,QAAQ,QAAQ,KAAK,EAAE,sBAAsB;CAC1E,MAAM,oBAAoBC,kBAAAA,iBAAiB;CAE3C,MAAM,UAA2B;EAC/B;EACA;EACA;EACA,cAAcC,oBAAAA,mBAAmB,QAAQ;EACzC,kBAAkBC,oBAAAA,wBAAwB;EAC3C;CAED,MAAM,kBAAkBC,qBAAAA,mBAAmB;EACzC,UAAU,QAAQ,YAAY;EAC9B,aAAa,QAAQ,QAAQ,YAAY;EACzC,SAAS,QAAQ,WAAW,EAAE;EAC/B,CAAC;CACF,MAAM,WAAWC,qBAAAA,qBAAqB,kBAAkB;CACxD,MAAM,kBAAkBC,qBAAAA,wBAAwB,UAAU,gBAAgB;CAE1E,MAAM,yBAAyB,MAAMC,eAAAA,qBAAqB,iBAAiB,QAAQ,MAAM;AACzF,KAAI;AACF,QAAMC,eAAAA,sBAAsB,mBAAmB,iBAAiB,SAAS;GACvE;GACA;GACD,CAAC;EACF,MAAM,gBAAgB,MAAMC,eAAAA,qBAAqB,mBAAmB,SAAS;GAC3E;GACA;GACD,CAAC;AACF,QAAMC,kBAAAA,kBACJ,iBACAC,kBAAAA,mBAAmB;GACjB,iBAAiBC,kBAAAA,eAAe;GAChC;GACA;GACA;GACA;GACD,CAAC,CACH;UACM,OAAO;AACd,MAAI,uBACF,OAAMC,iBAAAA,QAAG,GAAG,iBAAiB;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAEhE,QAAM;;AAGR,KAAI,CAAC,QAAQ,QACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,KAAI,CAAC,QAAQ,YACX,OAAMC,eAAAA,WAAW,gBAAgB;AAGnC,QAAO;EACL;EACA;EACA,cAAcC,sBAAAA;EACf"}
|
package/dist/src/doctor.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","readDefaultCatalog","walkPackageJsonFiles","collectCatalogProtocolDependencies","MANAGED_CATALOG_SPECIFIER","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport {\r\n MANAGED_CATALOG_SPECIFIER,\r\n collectCatalogProtocolDependencies,\r\n readCatalog,\r\n readDefaultCatalog,\r\n walkPackageJsonFiles,\r\n} from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const workspaceSource = await fs.readFile(workspacePath, \"utf8\");\r\n const actualCatalog = readCatalog(workspaceSource);\r\n const defaultCatalog = readDefaultCatalog(workspaceSource);\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const { name, specifier } of collectCatalogProtocolDependencies(packageFiles)) {\r\n const catalog =\r\n specifier === MANAGED_CATALOG_SPECIFIER\r\n ? actualCatalog\r\n : specifier === \"catalog:\"\r\n ? defaultCatalog\r\n : undefined;\r\n if (catalog === undefined) continue;\r\n if (!(name in catalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${name} uses ${specifier} but has no matching catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","readDefaultCatalog","walkPackageJsonFiles","collectCatalogProtocolDependencies","MANAGED_CATALOG_SPECIFIER","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport {\r\n collectCatalogProtocolDependencies,\r\n MANAGED_CATALOG_SPECIFIER,\r\n readCatalog,\r\n readDefaultCatalog,\r\n walkPackageJsonFiles,\r\n} from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const workspaceSource = await fs.readFile(workspacePath, \"utf8\");\r\n const actualCatalog = readCatalog(workspaceSource);\r\n const defaultCatalog = readDefaultCatalog(workspaceSource);\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const { name, specifier } of collectCatalogProtocolDependencies(packageFiles)) {\r\n const catalog =\r\n specifier === MANAGED_CATALOG_SPECIFIER\r\n ? actualCatalog\r\n : specifier === \"catalog:\"\r\n ? defaultCatalog\r\n : undefined;\r\n if (catalog === undefined) continue;\r\n if (!(name in catalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${name} uses ${specifier} but has no matching catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
|
package/dist/src/fs.cjs
CHANGED
|
@@ -22,16 +22,21 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
22
22
|
".yaml",
|
|
23
23
|
".yml",
|
|
24
24
|
".txt",
|
|
25
|
-
".mdc"
|
|
25
|
+
".mdc",
|
|
26
|
+
".toml"
|
|
26
27
|
]);
|
|
27
28
|
const TEXT_BASENAMES = new Set([
|
|
28
29
|
".gitignore",
|
|
29
30
|
".env",
|
|
30
31
|
".env.example",
|
|
31
|
-
".
|
|
32
|
+
".env.production",
|
|
33
|
+
".env.production.example",
|
|
34
|
+
".npmrc",
|
|
35
|
+
".dockerignore",
|
|
36
|
+
"Dockerfile"
|
|
32
37
|
]);
|
|
33
38
|
/** Feature marker blocks: lines between `// m5k:<feature>:start` and `// m5k:<feature>:end`. */
|
|
34
|
-
const MARKER_PATTERN = /^[ \t]
|
|
39
|
+
const MARKER_PATTERN = /^[ \t]*(?:\/\/|#)[ \t]*m5k:([a-z-]+):(start|end)[ \t]*$/;
|
|
35
40
|
async function collectTemplateFiles(templateDirectory, context, options = {}, relativeBase = "") {
|
|
36
41
|
const entries = await node_fs_promises.default.readdir(templateDirectory, { withFileTypes: true });
|
|
37
42
|
const excludePrefixes = options.excludePrefixes ?? [];
|
|
@@ -66,9 +71,9 @@ async function collectTemplateFiles(templateDirectory, context, options = {}, re
|
|
|
66
71
|
});
|
|
67
72
|
continue;
|
|
68
73
|
}
|
|
69
|
-
const content = await node_fs_promises.default.readFile(sourcePath, "utf8");
|
|
74
|
+
const content = (await node_fs_promises.default.readFile(sourcePath, "utf8")).replace(/\r\n?/g, "\n");
|
|
70
75
|
files.push({
|
|
71
|
-
content: Buffer.from(applyFeatureMarkers(require_src_strings.renderTemplate(content, context), enabledFeatures)
|
|
76
|
+
content: Buffer.from(applyFeatureMarkers(require_src_strings.renderTemplate(content, context), enabledFeatures), "utf8"),
|
|
72
77
|
kind: "text",
|
|
73
78
|
relativePath
|
|
74
79
|
});
|
package/dist/src/fs.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.cjs","names":["execFile","fs","path","renderTemplate"],"sources":["../../src/fs.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { renderTemplate } from \"./strings\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nconst TEXT_EXTENSIONS = new Set([\r\n \".ts\",\r\n \".tsx\",\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".json\",\r\n \".md\",\r\n \".css\",\r\n \".html\",\r\n \".svg\",\r\n \".yaml\",\r\n \".yml\",\r\n \".txt\",\r\n \".mdc\",\r\n]);\r\nconst TEXT_BASENAMES = new Set([\".gitignore\"
|
|
1
|
+
{"version":3,"file":"fs.cjs","names":["execFile","fs","path","renderTemplate"],"sources":["../../src/fs.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport { renderTemplate } from \"./strings\";\r\nimport type { RenderedTemplateFile, TemplateContext } from \"./types\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\n\r\nconst TEXT_EXTENSIONS = new Set([\r\n \".ts\",\r\n \".tsx\",\r\n \".js\",\r\n \".mjs\",\r\n \".cjs\",\r\n \".json\",\r\n \".md\",\r\n \".css\",\r\n \".html\",\r\n \".svg\",\r\n \".yaml\",\r\n \".yml\",\r\n \".txt\",\r\n \".mdc\",\r\n \".toml\",\r\n]);\r\nconst TEXT_BASENAMES = new Set([\r\n \".gitignore\",\r\n \".env\",\r\n \".env.example\",\r\n \".env.production\",\r\n \".env.production.example\",\r\n \".npmrc\",\r\n \".dockerignore\",\r\n \"Dockerfile\",\r\n]);\r\n\r\n/** Feature marker blocks: lines between `// m5k:<feature>:start` and `// m5k:<feature>:end`. */\r\nconst MARKER_PATTERN = /^[ \\t]*(?:\\/\\/|#)[ \\t]*m5k:([a-z-]+):(start|end)[ \\t]*$/;\r\n\r\nexport interface CopyTemplateOptions {\r\n /** Repo-root-relative path prefixes to skip entirely (disabled features). */\r\n excludePrefixes?: readonly string[];\r\n /** Features whose marker blocks should be kept (markers removed, content kept). */\r\n enabledFeatures?: ReadonlySet<string>;\r\n /** Update inspection can retain ignored-file metadata without rendering secret content. */\r\n ignoreContentFor?: (relativePath: string) => boolean;\r\n}\r\n\r\nexport async function collectTemplateFiles(\r\n templateDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {},\r\n relativeBase = \"\"\r\n): Promise<RenderedTemplateFile[]> {\r\n const entries = await fs.readdir(templateDirectory, { withFileTypes: true });\r\n const excludePrefixes = options.excludePrefixes ?? [];\r\n const enabledFeatures = options.enabledFeatures ?? new Set<string>();\r\n const files: RenderedTemplateFile[] = [];\r\n\r\n for (const entry of entries) {\r\n const sourcePath = path.join(templateDirectory, entry.name);\r\n const targetName = renderTemplate(\r\n entry.name.endsWith(\".tpl\") ? entry.name.slice(0, -4) : entry.name,\r\n context\r\n );\r\n const relativePath = relativeBase ? `${relativeBase}/${targetName}` : targetName;\r\n\r\n if (relativePath === \"template.manifest.json\") continue;\r\n\r\n const asDirPrefix = `${relativePath}/`;\r\n if (\r\n excludePrefixes.some(\r\n (prefix) =>\r\n relativePath === prefix || asDirPrefix === prefix || relativePath.startsWith(prefix)\r\n )\r\n ) {\r\n continue;\r\n }\r\n\r\n if (entry.isDirectory()) {\r\n files.push(...(await collectTemplateFiles(sourcePath, context, options, relativePath)));\r\n continue;\r\n }\r\n if (entry.isSymbolicLink()) {\r\n throw new Error(`Template symlinks are not supported: ${relativePath}`);\r\n }\r\n if (!entry.isFile()) continue;\r\n\r\n if (options.ignoreContentFor?.(relativePath)) {\r\n files.push({\r\n content: Buffer.alloc(0),\r\n kind: isTextFile(entry.name) ? \"text\" : \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n if (!isTextFile(entry.name)) {\r\n files.push({\r\n content: await fs.readFile(sourcePath),\r\n kind: \"binary\",\r\n relativePath,\r\n });\r\n continue;\r\n }\r\n\r\n const content = (await fs.readFile(sourcePath, \"utf8\")).replace(/\\r\\n?/g, \"\\n\");\r\n files.push({\r\n content: Buffer.from(\r\n applyFeatureMarkers(renderTemplate(content, context), enabledFeatures),\r\n \"utf8\"\r\n ),\r\n kind: \"text\",\r\n relativePath,\r\n });\r\n }\r\n\r\n return files;\r\n}\r\n\r\n/**\r\n * Ensures the target directory exists and may be written.\r\n * Returns true when this call created the directory (safe to remove on failure).\r\n */\r\nexport async function ensureDirectoryState(\r\n targetDirectory: string,\r\n force: boolean\r\n): Promise<boolean> {\r\n const stat = await fs.stat(targetDirectory).catch(() => null);\r\n\r\n if (!stat) {\r\n await fs.mkdir(targetDirectory, { recursive: true });\r\n return true;\r\n }\r\n\r\n if (!stat.isDirectory()) {\r\n throw new Error(`Target path is not a directory: ${targetDirectory}`);\r\n }\r\n\r\n const entries = await fs.readdir(targetDirectory);\r\n if (entries.length > 0 && !force) {\r\n throw new Error(\r\n `Target directory is not empty: ${targetDirectory}. Re-run with --force to overwrite.`\r\n );\r\n }\r\n\r\n return false;\r\n}\r\n\r\nfunction isTextFile(fileName: string): boolean {\r\n const withoutTpl = fileName.endsWith(\".tpl\") ? fileName.slice(0, -4) : fileName;\r\n if (TEXT_BASENAMES.has(withoutTpl)) return true;\r\n return TEXT_EXTENSIONS.has(path.extname(withoutTpl));\r\n}\r\n\r\n/**\r\n * Removes feature marker lines. Blocks for disabled features are dropped with\r\n * their content; blocks for enabled features keep the content, lose the markers.\r\n */\r\nexport function applyFeatureMarkers(content: string, enabledFeatures: ReadonlySet<string>): string {\r\n const lines = content.split(\"\\n\");\r\n const out: string[] = [];\r\n let skippingFeature: string | undefined;\r\n\r\n for (const line of lines) {\r\n const match = line.match(MARKER_PATTERN);\r\n if (!match) {\r\n if (skippingFeature === undefined) out.push(line);\r\n continue;\r\n }\r\n\r\n const [, feature, edge] = match;\r\n if (edge === \"start\") {\r\n if (!enabledFeatures.has(feature)) skippingFeature = feature;\r\n } else if (skippingFeature === feature) {\r\n skippingFeature = undefined;\r\n }\r\n // marker lines themselves are never emitted\r\n }\r\n\r\n return out.join(\"\\n\");\r\n}\r\n\r\nexport async function copyTemplateDirectory(\r\n templateDirectory: string,\r\n targetDirectory: string,\r\n context: TemplateContext,\r\n options: CopyTemplateOptions = {}\r\n): Promise<void> {\r\n const files = await collectTemplateFiles(templateDirectory, context, options);\r\n for (const file of files) {\r\n const targetPath = path.join(targetDirectory, file.relativePath);\r\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\r\n await fs.writeFile(targetPath, file.content);\r\n }\r\n}\r\n\r\nexport async function runInstall(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"pnpm\", [\"install\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n\r\nexport async function runGitInit(targetDirectory: string): Promise<void> {\r\n await execFileAsync(\"git\", [\"init\"], {\r\n cwd: targetDirectory,\r\n env: process.env,\r\n });\r\n}\r\n"],"mappings":"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"}
|
package/dist/src/prompts.cjs
CHANGED
|
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
3
|
const require_src_constants = require("./constants.cjs");
|
|
4
4
|
const require_src_strings = require("./strings.cjs");
|
|
5
|
+
const require_src_paths = require("./paths.cjs");
|
|
6
|
+
const require_src_template = require("./template.cjs");
|
|
5
7
|
let node_process = require("node:process");
|
|
6
8
|
let node_readline_promises = require("node:readline/promises");
|
|
7
9
|
node_readline_promises = require_runtime.__toESM(node_readline_promises);
|
|
@@ -25,6 +27,23 @@ function parsePlatform(value) {
|
|
|
25
27
|
const normalized = value.trim().toLowerCase();
|
|
26
28
|
if (normalized === "web" || normalized === "expo" || normalized === "both") return normalized;
|
|
27
29
|
}
|
|
30
|
+
function formatBackendModulesPrompt(choices) {
|
|
31
|
+
return `Backend modules — comma-separated ids, or none [${choices.map((choice) => choice.experimental ? `${choice.id} (experimental)` : choice.id).join(", ")}]`;
|
|
32
|
+
}
|
|
33
|
+
function parseBackendModulesAnswer(options) {
|
|
34
|
+
const trimmed = options.answer.trim().toLowerCase();
|
|
35
|
+
if (!trimmed || trimmed === "none" || trimmed === "n") return [];
|
|
36
|
+
const allowed = new Map(options.choices.map((choice) => [choice.id, choice.id]));
|
|
37
|
+
const selected = [];
|
|
38
|
+
for (const raw of trimmed.split(",")) {
|
|
39
|
+
const id = raw.trim();
|
|
40
|
+
if (!id) continue;
|
|
41
|
+
const resolved = allowed.get(id);
|
|
42
|
+
if (!resolved) throw new Error(`Unknown Backend Module "${id}". Use comma-separated ids from the prompt, or none.`);
|
|
43
|
+
if (!selected.includes(resolved)) selected.push(resolved);
|
|
44
|
+
}
|
|
45
|
+
return selected;
|
|
46
|
+
}
|
|
28
47
|
async function resolveCreateCommandOptions(options) {
|
|
29
48
|
const resolved = { ...options };
|
|
30
49
|
if (!resolved.appName) {
|
|
@@ -59,12 +78,25 @@ async function resolveCreateCommandOptions(options) {
|
|
|
59
78
|
resolved.testHarness = /^y(es)?$/i.test(answer.trim());
|
|
60
79
|
}
|
|
61
80
|
}
|
|
81
|
+
if (resolved.modules === void 0) {
|
|
82
|
+
requireInteractive(resolved.yes);
|
|
83
|
+
if (resolved.yes) resolved.modules = [];
|
|
84
|
+
else {
|
|
85
|
+
const choices = require_src_template.listBackendModuleChoices(require_src_template.loadTemplateManifest(require_src_paths.getTemplateRoot()));
|
|
86
|
+
resolved.modules = parseBackendModulesAnswer({
|
|
87
|
+
answer: await promptValue(formatBackendModulesPrompt(choices), "none"),
|
|
88
|
+
choices
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
62
92
|
return resolved;
|
|
63
93
|
}
|
|
64
94
|
function pathBaseName(value) {
|
|
65
95
|
return value.replace(/[\\/]+$/g, "").split(/[\\/]/).filter(Boolean).at(-1) || "M5 Starter";
|
|
66
96
|
}
|
|
67
97
|
//#endregion
|
|
98
|
+
exports.formatBackendModulesPrompt = formatBackendModulesPrompt;
|
|
99
|
+
exports.parseBackendModulesAnswer = parseBackendModulesAnswer;
|
|
68
100
|
exports.resolveCreateCommandOptions = resolveCreateCommandOptions;
|
|
69
101
|
|
|
70
102
|
//# sourceMappingURL=prompts.cjs.map
|
package/dist/src/prompts.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.cjs","names":["readline","toDisplayName","DEFAULT_APP_NAME","slugifyAppName","getDefaultDescription"],"sources":["../../src/prompts.ts"],"sourcesContent":["import { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { DEFAULT_APP_NAME, getDefaultDescription } from \"./constants\";\r\nimport { slugifyAppName, toDisplayName } from \"./strings\";\r\nimport type { AppPlatform, CreateCommandOptions } from \"./types\";\r\n\r\nfunction requireInteractive(yes: boolean): void {\r\n if (!yes && !process.stdin.isTTY) {\r\n throw new Error(\r\n \"Missing required values in a non-interactive shell. Pass --yes or provide flags.\"\r\n );\r\n }\r\n}\r\n\r\nasync function promptValue(question: string, fallback?: string): Promise<string> {\r\n const rl = readline.createInterface({ input, output });\r\n\r\n try {\r\n const suffix = fallback ? ` (${fallback})` : \"\";\r\n const response = await rl.question(`${question}${suffix}: `);\r\n return response.trim() || fallback || \"\";\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\nfunction parsePlatform(value: string): AppPlatform | undefined {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized === \"web\" || normalized === \"expo\" || normalized === \"both\") {\r\n return normalized;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport async function resolveCreateCommandOptions(\r\n options: CreateCommandOptions\r\n): Promise<CreateCommandOptions> {\r\n const resolved = { ...options };\r\n\r\n if (!resolved.appName) {\r\n requireInteractive(resolved.yes);\r\n resolved.appName = resolved.yes\r\n ? resolved.targetDirectory\r\n ? toDisplayName(pathBaseName(resolved.targetDirectory))\r\n : DEFAULT_APP_NAME\r\n : await promptValue(\r\n \"App name\",\r\n resolved.targetDirectory ? pathBaseName(resolved.targetDirectory) : DEFAULT_APP_NAME\r\n );\r\n }\r\n\r\n resolved.appName = toDisplayName(resolved.appName);\r\n\r\n if (!resolved.targetDirectory) {\r\n requireInteractive(resolved.yes);\r\n resolved.targetDirectory = resolved.yes\r\n ? slugifyAppName(resolved.appName)\r\n : await promptValue(\"Target directory\", slugifyAppName(resolved.appName));\r\n }\r\n\r\n if (!resolved.appDescription) {\r\n requireInteractive(resolved.yes);\r\n const fallback = getDefaultDescription(resolved.appName);\r\n resolved.appDescription = resolved.yes\r\n ? fallback\r\n : await promptValue(\"App description\", fallback);\r\n }\r\n\r\n if (!resolved.platform) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.platform = \"web\";\r\n } else {\r\n const answer = await promptValue(\"App platform — web, expo, or both\", \"web\");\r\n const platform = parsePlatform(answer);\r\n if (!platform) {\r\n throw new Error(`Invalid platform \"${answer}\". Use web, expo, or both.`);\r\n }\r\n resolved.platform = platform;\r\n }\r\n }\r\n\r\n if (resolved.testHarness === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.testHarness = false;\r\n } else {\r\n const answer = await promptValue(\"Include the e2e test harness? y/N\", \"n\");\r\n resolved.testHarness = /^y(es)?$/i.test(answer.trim());\r\n }\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\nfunction pathBaseName(value: string): string {\r\n const normalized = value.replace(/[\\\\/]+$/g, \"\");\r\n const segments = normalized.split(/[\\\\/]/).filter(Boolean);\r\n return segments.at(-1) || DEFAULT_APP_NAME;\r\n}\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompts.cjs","names":["readline","toDisplayName","DEFAULT_APP_NAME","slugifyAppName","getDefaultDescription","listBackendModuleChoices","loadTemplateManifest","getTemplateRoot"],"sources":["../../src/prompts.ts"],"sourcesContent":["import { stdin as input, stdout as output } from \"node:process\";\r\nimport readline from \"node:readline/promises\";\r\nimport { DEFAULT_APP_NAME, getDefaultDescription } from \"./constants\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { slugifyAppName, toDisplayName } from \"./strings\";\r\nimport type { BackendModuleChoice } from \"./template\";\r\nimport { listBackendModuleChoices, loadTemplateManifest } from \"./template\";\r\nimport type { AppPlatform, CreateCommandOptions } from \"./types\";\r\n\r\nfunction requireInteractive(yes: boolean): void {\r\n if (!yes && !process.stdin.isTTY) {\r\n throw new Error(\r\n \"Missing required values in a non-interactive shell. Pass --yes or provide flags.\"\r\n );\r\n }\r\n}\r\n\r\nasync function promptValue(question: string, fallback?: string): Promise<string> {\r\n const rl = readline.createInterface({ input, output });\r\n\r\n try {\r\n const suffix = fallback ? ` (${fallback})` : \"\";\r\n const response = await rl.question(`${question}${suffix}: `);\r\n return response.trim() || fallback || \"\";\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\nfunction parsePlatform(value: string): AppPlatform | undefined {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized === \"web\" || normalized === \"expo\" || normalized === \"both\") {\r\n return normalized;\r\n }\r\n return undefined;\r\n}\r\n\r\nexport function formatBackendModulesPrompt(choices: readonly BackendModuleChoice[]): string {\r\n const catalog = choices\r\n .map((choice) => (choice.experimental ? `${choice.id} (experimental)` : choice.id))\r\n .join(\", \");\r\n return `Backend modules — comma-separated ids, or none [${catalog}]`;\r\n}\r\n\r\nexport function parseBackendModulesAnswer(options: {\r\n answer: string;\r\n choices: readonly BackendModuleChoice[];\r\n}): string[] {\r\n const trimmed = options.answer.trim().toLowerCase();\r\n if (!trimmed || trimmed === \"none\" || trimmed === \"n\") return [];\r\n const allowed = new Map(options.choices.map((choice) => [choice.id, choice.id]));\r\n const selected: string[] = [];\r\n for (const raw of trimmed.split(\",\")) {\r\n const id = raw.trim();\r\n if (!id) continue;\r\n const resolved = allowed.get(id);\r\n if (!resolved) {\r\n throw new Error(\r\n `Unknown Backend Module \"${id}\". Use comma-separated ids from the prompt, or none.`\r\n );\r\n }\r\n if (!selected.includes(resolved)) selected.push(resolved);\r\n }\r\n return selected;\r\n}\r\n\r\nexport async function resolveCreateCommandOptions(\r\n options: CreateCommandOptions\r\n): Promise<CreateCommandOptions> {\r\n const resolved = { ...options };\r\n\r\n if (!resolved.appName) {\r\n requireInteractive(resolved.yes);\r\n resolved.appName = resolved.yes\r\n ? resolved.targetDirectory\r\n ? toDisplayName(pathBaseName(resolved.targetDirectory))\r\n : DEFAULT_APP_NAME\r\n : await promptValue(\r\n \"App name\",\r\n resolved.targetDirectory ? pathBaseName(resolved.targetDirectory) : DEFAULT_APP_NAME\r\n );\r\n }\r\n\r\n resolved.appName = toDisplayName(resolved.appName);\r\n\r\n if (!resolved.targetDirectory) {\r\n requireInteractive(resolved.yes);\r\n resolved.targetDirectory = resolved.yes\r\n ? slugifyAppName(resolved.appName)\r\n : await promptValue(\"Target directory\", slugifyAppName(resolved.appName));\r\n }\r\n\r\n if (!resolved.appDescription) {\r\n requireInteractive(resolved.yes);\r\n const fallback = getDefaultDescription(resolved.appName);\r\n resolved.appDescription = resolved.yes\r\n ? fallback\r\n : await promptValue(\"App description\", fallback);\r\n }\r\n\r\n if (!resolved.platform) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.platform = \"web\";\r\n } else {\r\n const answer = await promptValue(\"App platform — web, expo, or both\", \"web\");\r\n const platform = parsePlatform(answer);\r\n if (!platform) {\r\n throw new Error(`Invalid platform \"${answer}\". Use web, expo, or both.`);\r\n }\r\n resolved.platform = platform;\r\n }\r\n }\r\n\r\n if (resolved.testHarness === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.testHarness = false;\r\n } else {\r\n const answer = await promptValue(\"Include the e2e test harness? y/N\", \"n\");\r\n resolved.testHarness = /^y(es)?$/i.test(answer.trim());\r\n }\r\n }\r\n\r\n if (resolved.modules === undefined) {\r\n requireInteractive(resolved.yes);\r\n if (resolved.yes) {\r\n resolved.modules = [];\r\n } else {\r\n const choices = listBackendModuleChoices(loadTemplateManifest(getTemplateRoot()));\r\n const answer = await promptValue(formatBackendModulesPrompt(choices), \"none\");\r\n resolved.modules = parseBackendModulesAnswer({ answer, choices });\r\n }\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\nfunction pathBaseName(value: string): string {\r\n const normalized = value.replace(/[\\\\/]+$/g, \"\");\r\n const segments = normalized.split(/[\\\\/]/).filter(Boolean);\r\n return segments.at(-1) || DEFAULT_APP_NAME;\r\n}\r\n"],"mappings":"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"}
|
package/dist/src/prompts.d.cts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { CreateCommandOptions } from "./types.cjs";
|
|
2
|
+
import { BackendModuleChoice } from "./template.cjs";
|
|
2
3
|
|
|
3
4
|
//#region src/prompts.d.ts
|
|
5
|
+
declare function formatBackendModulesPrompt(choices: readonly BackendModuleChoice[]): string;
|
|
6
|
+
declare function parseBackendModulesAnswer(options: {
|
|
7
|
+
answer: string;
|
|
8
|
+
choices: readonly BackendModuleChoice[];
|
|
9
|
+
}): string[];
|
|
4
10
|
declare function resolveCreateCommandOptions(options: CreateCommandOptions): Promise<CreateCommandOptions>;
|
|
5
11
|
//#endregion
|
|
6
|
-
export { resolveCreateCommandOptions };
|
|
12
|
+
export { formatBackendModulesPrompt, parseBackendModulesAnswer, resolveCreateCommandOptions };
|
|
7
13
|
//# sourceMappingURL=prompts.d.cts.map
|
package/dist/src/template.cjs
CHANGED
|
@@ -26,11 +26,28 @@ function getTemplateFilePolicy(manifest, relativePath) {
|
|
|
26
26
|
for (const rule of manifest.sync.rules) if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;
|
|
27
27
|
return manifest.sync.defaultPolicy;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function getFeatureKind(options) {
|
|
30
|
+
if (options.config.kind) return options.config.kind;
|
|
31
|
+
if (options.id === "webapp" || options.id === "expo") return "platform";
|
|
32
|
+
if (options.id === "test-harness") return "harness";
|
|
33
|
+
return "module";
|
|
34
|
+
}
|
|
35
|
+
function listBackendModuleChoices(manifest) {
|
|
36
|
+
return Object.entries(manifest.features).filter(([id, config]) => getFeatureKind({
|
|
37
|
+
id,
|
|
38
|
+
config
|
|
39
|
+
}) === "module").map(([id, config]) => ({
|
|
40
|
+
id,
|
|
41
|
+
label: config.label ?? id,
|
|
42
|
+
experimental: Boolean(config.experimental)
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
function getEnabledFeatures(options) {
|
|
30
46
|
const enabled = /* @__PURE__ */ new Set();
|
|
31
|
-
if (platform !== "expo") enabled.add("webapp");
|
|
32
|
-
if (platform !== "web") enabled.add("expo");
|
|
33
|
-
if (testHarness) enabled.add("test-harness");
|
|
47
|
+
if (options.platform !== "expo") enabled.add("webapp");
|
|
48
|
+
if (options.platform !== "web") enabled.add("expo");
|
|
49
|
+
if (options.testHarness) enabled.add("test-harness");
|
|
50
|
+
for (const id of options.modules ?? []) enabled.add(id);
|
|
34
51
|
return enabled;
|
|
35
52
|
}
|
|
36
53
|
function getExcludedFeaturePaths(manifest, enabledFeatures) {
|
|
@@ -39,7 +56,9 @@ function getExcludedFeaturePaths(manifest, enabledFeatures) {
|
|
|
39
56
|
//#endregion
|
|
40
57
|
exports.getEnabledFeatures = getEnabledFeatures;
|
|
41
58
|
exports.getExcludedFeaturePaths = getExcludedFeaturePaths;
|
|
59
|
+
exports.getFeatureKind = getFeatureKind;
|
|
42
60
|
exports.getTemplateFilePolicy = getTemplateFilePolicy;
|
|
61
|
+
exports.listBackendModuleChoices = listBackendModuleChoices;
|
|
43
62
|
exports.loadTemplateManifest = loadTemplateManifest;
|
|
44
63
|
|
|
45
64
|
//# 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 { 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 function getEnabledFeatures(\r\n platform: \"web\" | \"expo\" | \"both\"
|
|
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"}
|
package/dist/src/template.d.cts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
|
|
1
|
+
import { TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/template.d.ts
|
|
4
4
|
declare function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest;
|
|
5
5
|
declare function getTemplateFilePolicy(manifest: TemplateFeatureManifest, relativePath: string): TemplateFilePolicy;
|
|
6
|
-
|
|
6
|
+
interface BackendModuleChoice {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
experimental: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function getFeatureKind(options: {
|
|
12
|
+
id: string;
|
|
13
|
+
config: TemplateFeatureManifest["features"][string];
|
|
14
|
+
}): TemplateFeatureKind;
|
|
15
|
+
declare function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[];
|
|
16
|
+
declare function getEnabledFeatures(options: {
|
|
17
|
+
platform: "web" | "expo" | "both";
|
|
18
|
+
testHarness: boolean;
|
|
19
|
+
modules?: readonly string[];
|
|
20
|
+
}): Set<string>;
|
|
7
21
|
declare function getExcludedFeaturePaths(manifest: TemplateFeatureManifest, enabledFeatures: ReadonlySet<string>): string[];
|
|
8
22
|
//#endregion
|
|
9
|
-
export { getEnabledFeatures, getExcludedFeaturePaths, getTemplateFilePolicy, loadTemplateManifest };
|
|
23
|
+
export { BackendModuleChoice, getEnabledFeatures, getExcludedFeaturePaths, getFeatureKind, getTemplateFilePolicy, listBackendModuleChoices, loadTemplateManifest };
|
|
10
24
|
//# sourceMappingURL=template.d.cts.map
|
package/dist/src/types.d.cts
CHANGED
|
@@ -6,6 +6,8 @@ interface PromptValues {
|
|
|
6
6
|
appDescription?: string;
|
|
7
7
|
platform?: AppPlatform;
|
|
8
8
|
testHarness?: boolean;
|
|
9
|
+
/** Optional Backend Module feature ids (billing, workflows, …). `--yes` defaults to none. */
|
|
10
|
+
modules?: string[];
|
|
9
11
|
}
|
|
10
12
|
interface CreateCommandOptions extends PromptValues {
|
|
11
13
|
yes: boolean;
|
|
@@ -20,11 +22,16 @@ interface TemplateContext {
|
|
|
20
22
|
packageScope: string;
|
|
21
23
|
betterAuthSecret: string;
|
|
22
24
|
}
|
|
25
|
+
type TemplateFeatureKind = "platform" | "harness" | "module";
|
|
26
|
+
interface TemplateFeatureConfig {
|
|
27
|
+
paths: readonly string[];
|
|
28
|
+
kind?: TemplateFeatureKind;
|
|
29
|
+
experimental?: boolean;
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
23
32
|
interface TemplateFeatureManifest {
|
|
24
33
|
schemaVersion: number;
|
|
25
|
-
features: Record<string,
|
|
26
|
-
paths: readonly string[];
|
|
27
|
-
}>;
|
|
34
|
+
features: Record<string, TemplateFeatureConfig>;
|
|
28
35
|
requiredPaths: readonly string[];
|
|
29
36
|
sync: {
|
|
30
37
|
defaultPolicy: TemplateFilePolicy;
|
|
@@ -45,5 +52,5 @@ interface RenderedTemplateFile {
|
|
|
45
52
|
relativePath: string;
|
|
46
53
|
}
|
|
47
54
|
//#endregion
|
|
48
|
-
export { AppPlatform, CreateCommandOptions, PromptValues, RenderedTemplateFile, TemplateContext, TemplateFeatureManifest, TemplateFilePolicy };
|
|
55
|
+
export { AppPlatform, CreateCommandOptions, PromptValues, RenderedTemplateFile, TemplateContext, TemplateFeatureConfig, TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy };
|
|
49
56
|
//# sourceMappingURL=types.d.cts.map
|