create-m5kdev 0.32.8 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/catalog.cjs +76 -11
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +13 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs +13 -7
- 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/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/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +13 -10
- package/templates/minimal-app/CLAUDE.md.tpl +132 -131
- package/templates/minimal-app/README.md.tpl +50 -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/package.json.tpl +59 -59
- 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/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -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 +54 -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/0002_concerned_proudstar.sql +3 -0
- 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 -0
- 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 +15 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +57 -56
- package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
- package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
- 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 +47 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -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 -30
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -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 -115
- 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 -1
- 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 +25 -24
- package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
- 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/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 +46 -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/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 +9 -5
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
- package/templates/minimal-app/template.manifest.json +92 -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/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
package/dist/src/catalog.cjs
CHANGED
|
@@ -12,6 +12,8 @@ const DEPENDENCY_FIELDS = [
|
|
|
12
12
|
"peerDependencies",
|
|
13
13
|
"optionalDependencies"
|
|
14
14
|
];
|
|
15
|
+
const MANAGED_CATALOG_NAME = "m5kdev";
|
|
16
|
+
const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;
|
|
15
17
|
function walkPackageJsonFiles(directory, out = []) {
|
|
16
18
|
for (const entry of node_fs.default.readdirSync(directory, { withFileTypes: true })) {
|
|
17
19
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
@@ -24,6 +26,9 @@ function walkPackageJsonFiles(directory, out = []) {
|
|
|
24
26
|
function readJson(filePath) {
|
|
25
27
|
return JSON.parse(node_fs.default.readFileSync(filePath, "utf8"));
|
|
26
28
|
}
|
|
29
|
+
function asCatalog(value) {
|
|
30
|
+
return Object.fromEntries(Object.entries(value ?? {}).map(([name, version]) => [name, String(version)]));
|
|
31
|
+
}
|
|
27
32
|
function collectConsumerDependencyNamesFromManifests(manifests) {
|
|
28
33
|
const names = /* @__PURE__ */ new Set();
|
|
29
34
|
for (const manifest of manifests) for (const field of DEPENDENCY_FIELDS) {
|
|
@@ -35,6 +40,35 @@ function collectConsumerDependencyNamesFromManifests(manifests) {
|
|
|
35
40
|
function collectConsumerDependencyNames(packageFiles) {
|
|
36
41
|
return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));
|
|
37
42
|
}
|
|
43
|
+
function collectCatalogProtocolDependenciesFromManifests(manifests) {
|
|
44
|
+
const dependencies = [];
|
|
45
|
+
for (const manifest of manifests) for (const field of DEPENDENCY_FIELDS) for (const [name, specifier] of Object.entries(manifest[field] ?? {})) if (specifier.startsWith("catalog:")) dependencies.push({
|
|
46
|
+
name,
|
|
47
|
+
specifier
|
|
48
|
+
});
|
|
49
|
+
return dependencies;
|
|
50
|
+
}
|
|
51
|
+
function collectCatalogProtocolDependencies(packageFiles) {
|
|
52
|
+
return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));
|
|
53
|
+
}
|
|
54
|
+
/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
|
|
55
|
+
function collectOptionalBackendModulePins(repoRoot) {
|
|
56
|
+
const packagesDir = node_path.default.join(repoRoot, "packages");
|
|
57
|
+
const pins = {};
|
|
58
|
+
if (!node_fs.default.existsSync(packagesDir)) return pins;
|
|
59
|
+
for (const entry of node_fs.default.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
60
|
+
if (!entry.isDirectory() || !entry.name.startsWith("module-")) continue;
|
|
61
|
+
const manifestPath = node_path.default.join(packagesDir, entry.name, "package.json");
|
|
62
|
+
if (!node_fs.default.existsSync(manifestPath)) continue;
|
|
63
|
+
const manifest = readJson(manifestPath);
|
|
64
|
+
const name = manifest.name;
|
|
65
|
+
const version = manifest.version;
|
|
66
|
+
if (typeof name !== "string" || typeof version !== "string") continue;
|
|
67
|
+
if (!name.startsWith("@m5kdev/module-")) continue;
|
|
68
|
+
pins[name] = version;
|
|
69
|
+
}
|
|
70
|
+
return pins;
|
|
71
|
+
}
|
|
38
72
|
function buildConsumerCatalog(options) {
|
|
39
73
|
const workspacePath = node_path.default.join(options.repoRoot, "pnpm-workspace.yaml");
|
|
40
74
|
const sourceCatalog = (0, yaml.parse)(node_fs.default.readFileSync(workspacePath, "utf8")).catalog ?? {};
|
|
@@ -64,17 +98,29 @@ function buildConsumerCatalog(options) {
|
|
|
64
98
|
}
|
|
65
99
|
catalog[name] = String(version);
|
|
66
100
|
}
|
|
101
|
+
Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));
|
|
67
102
|
if (missing.length > 0) throw new Error(`Missing consumer catalog versions for: ${missing.join(", ")}`);
|
|
68
|
-
return catalog;
|
|
103
|
+
return sortedCatalog(catalog);
|
|
104
|
+
}
|
|
105
|
+
function sortedCatalog(catalog) {
|
|
106
|
+
return Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
|
|
69
107
|
}
|
|
70
108
|
function renderConsumerWorkspace(source, catalog) {
|
|
71
109
|
const workspace = (0, yaml.parse)(source);
|
|
72
|
-
workspace.
|
|
110
|
+
const catalogs = workspace.catalogs && typeof workspace.catalogs === "object" ? { ...workspace.catalogs } : {};
|
|
111
|
+
catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);
|
|
112
|
+
workspace.catalogs = catalogs;
|
|
113
|
+
if (workspace.catalog === void 0) workspace.catalog = {};
|
|
73
114
|
return (0, yaml.stringify)(workspace, { lineWidth: 0 });
|
|
74
115
|
}
|
|
116
|
+
function readDefaultCatalog(source) {
|
|
117
|
+
return asCatalog((0, yaml.parse)(source).catalog);
|
|
118
|
+
}
|
|
75
119
|
function readCatalog(source) {
|
|
76
120
|
const workspace = (0, yaml.parse)(source);
|
|
77
|
-
|
|
121
|
+
const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];
|
|
122
|
+
if (named !== void 0) return asCatalog(named);
|
|
123
|
+
return asCatalog(workspace.catalog);
|
|
78
124
|
}
|
|
79
125
|
function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
|
|
80
126
|
const expected = new Set(expectedNames);
|
|
@@ -87,16 +133,24 @@ function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
|
|
|
87
133
|
}
|
|
88
134
|
function mergeManagedCatalog(options) {
|
|
89
135
|
const document = (0, yaml.parseDocument)(options.source);
|
|
90
|
-
const
|
|
136
|
+
const workspace = (0, yaml.parse)(options.source);
|
|
137
|
+
const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== void 0;
|
|
138
|
+
const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);
|
|
139
|
+
const fallback = asCatalog(workspace.catalog);
|
|
91
140
|
const conflicts = [];
|
|
92
141
|
const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);
|
|
142
|
+
const managedPath = ["catalogs", MANAGED_CATALOG_NAME];
|
|
143
|
+
if (document.getIn([...managedPath]) === void 0) if (document.get("catalogs") === void 0) document.set("catalogs", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));
|
|
144
|
+
else document.setIn([...managedPath], document.createNode({}));
|
|
93
145
|
for (const name of [...names].sort((left, right) => left.localeCompare(right))) {
|
|
94
146
|
const base = options.base[name];
|
|
95
|
-
const current =
|
|
147
|
+
const current = (namedPresent ? named[name] : void 0) ?? fallback[name];
|
|
96
148
|
const target = options.target[name];
|
|
97
149
|
if (base === void 0) {
|
|
98
|
-
if (current === void 0)
|
|
99
|
-
|
|
150
|
+
if (current === void 0) {
|
|
151
|
+
document.setIn([...managedPath, name], target);
|
|
152
|
+
document.deleteIn(["catalog", name]);
|
|
153
|
+
} else if (current !== target) conflicts.push({
|
|
100
154
|
name,
|
|
101
155
|
local: current,
|
|
102
156
|
target
|
|
@@ -104,16 +158,20 @@ function mergeManagedCatalog(options) {
|
|
|
104
158
|
continue;
|
|
105
159
|
}
|
|
106
160
|
if (target === void 0) {
|
|
107
|
-
if (current === base)
|
|
108
|
-
|
|
161
|
+
if (current === base) {
|
|
162
|
+
document.deleteIn([...managedPath, name]);
|
|
163
|
+
document.deleteIn(["catalog", name]);
|
|
164
|
+
} else if (current !== void 0) conflicts.push({
|
|
109
165
|
name,
|
|
110
166
|
base,
|
|
111
167
|
local: current
|
|
112
168
|
});
|
|
113
169
|
continue;
|
|
114
170
|
}
|
|
115
|
-
if (current === base)
|
|
116
|
-
|
|
171
|
+
if (current === base) {
|
|
172
|
+
document.setIn([...managedPath, name], target);
|
|
173
|
+
document.deleteIn(["catalog", name]);
|
|
174
|
+
} else if (current !== target && target !== base) conflicts.push({
|
|
117
175
|
name,
|
|
118
176
|
base,
|
|
119
177
|
local: current,
|
|
@@ -128,13 +186,20 @@ function mergeManagedCatalog(options) {
|
|
|
128
186
|
};
|
|
129
187
|
}
|
|
130
188
|
//#endregion
|
|
189
|
+
exports.MANAGED_CATALOG_NAME = MANAGED_CATALOG_NAME;
|
|
190
|
+
exports.MANAGED_CATALOG_SPECIFIER = MANAGED_CATALOG_SPECIFIER;
|
|
131
191
|
exports.assertCatalogKeys = assertCatalogKeys;
|
|
132
192
|
exports.buildConsumerCatalog = buildConsumerCatalog;
|
|
193
|
+
exports.collectCatalogProtocolDependencies = collectCatalogProtocolDependencies;
|
|
194
|
+
exports.collectCatalogProtocolDependenciesFromManifests = collectCatalogProtocolDependenciesFromManifests;
|
|
133
195
|
exports.collectConsumerDependencyNames = collectConsumerDependencyNames;
|
|
134
196
|
exports.collectConsumerDependencyNamesFromManifests = collectConsumerDependencyNamesFromManifests;
|
|
197
|
+
exports.collectOptionalBackendModulePins = collectOptionalBackendModulePins;
|
|
135
198
|
exports.mergeManagedCatalog = mergeManagedCatalog;
|
|
136
199
|
exports.readCatalog = readCatalog;
|
|
200
|
+
exports.readDefaultCatalog = readDefaultCatalog;
|
|
137
201
|
exports.renderConsumerWorkspace = renderConsumerWorkspace;
|
|
202
|
+
exports.sortedCatalog = sortedCatalog;
|
|
138
203
|
exports.walkPackageJsonFiles = walkPackageJsonFiles;
|
|
139
204
|
|
|
140
205
|
//# sourceMappingURL=catalog.cjs.map
|
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 type ConsumerCatalog = Record<string, string>;\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return catalog;\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n workspace.catalog = Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as { catalog?: Record<string, string | number> };\r\n return Object.fromEntries(\r\n Object.entries(workspace.catalog ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const local = readCatalog(options.source);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = local[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) document.setIn([\"catalog\", name], target);\r\n else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) document.deleteIn([\"catalog\", name]);\r\n else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) document.setIn([\"catalog\", name], target);\r\n else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":";;;;;;;;AAIA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACD;AAID,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,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;;AAGhF,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,KAAI,QAAQ,SAAS,EACnB,OAAM,IAAI,MAAM,0CAA0C,QAAQ,KAAK,KAAK,GAAG;AAGjF,QAAO;;AAGT,SAAgB,wBAAwB,QAAgB,SAAkC;CACxF,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;AAC/B,WAAU,UAAU,OAAO,YACzB,OAAO,QAAQ,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,MAAM,CAAC,CAC7E;AACD,SAAA,GAAA,KAAA,WAAiB,WAAW,EAAE,WAAW,GAAG,CAAC;;AAG/C,SAAgB,YAAY,QAAiC;CAC3D,MAAM,aAAA,GAAA,KAAA,OAAkB,OAAO;AAC/B,QAAO,OAAO,YACZ,OAAO,QAAQ,UAAU,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,aAAa,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC,CAC1F;;AAGH,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,QAAQ,YAAY,QAAQ,OAAO;CACzC,MAAM,YAAoC,EAAE;CAC5C,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,KAAK,EAAE,GAAG,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC;AAErF,MAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,MAAM,UAAU,KAAK,cAAc,MAAM,CAAC,EAAE;EAC9E,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,UAAU,MAAM;EACtB,MAAM,SAAS,QAAQ,OAAO;AAE9B,MAAI,SAAS,KAAA,GAAW;AACtB,OAAI,YAAY,KAAA,EAAW,UAAS,MAAM,CAAC,WAAW,KAAK,EAAE,OAAO;YAC3D,YAAY,OAAQ,WAAU,KAAK;IAAE;IAAM,OAAO;IAAS;IAAQ,CAAC;AAC7E;;AAEF,MAAI,WAAW,KAAA,GAAW;AACxB,OAAI,YAAY,KAAM,UAAS,SAAS,CAAC,WAAW,KAAK,CAAC;YACjD,YAAY,KAAA,EAAW,WAAU,KAAK;IAAE;IAAM;IAAM,OAAO;IAAS,CAAC;AAC9E;;AAEF,MAAI,YAAY,KAAM,UAAS,MAAM,CAAC,WAAW,KAAK,EAAE,OAAO;WACtD,YAAY,UAAU,WAAW,KACxC,WAAU,KAAK;GAAE;GAAM;GAAM,OAAO;GAAS;GAAQ,CAAC;;CAI1D,MAAM,SAAS,SAAS,SAAS,EAAE,WAAW,GAAG,CAAC;AAClD,QAAO;EAAE,QAAQ;EAAQ,SAAS,WAAW,QAAQ;EAAQ;EAAW"}
|
|
1
|
+
{"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport const MANAGED_CATALOG_NAME = \"m5kdev\";\r\nexport const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\ninterface WorkspaceCatalogFile {\r\n catalog?: Record<string, string | number>;\r\n catalogs?: Record<string, Record<string, string | number> | undefined>;\r\n}\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nfunction asCatalog(value: Record<string, string | number> | undefined): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(value ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport interface CatalogProtocolDependency {\r\n name: string;\r\n specifier: string;\r\n}\r\n\r\nexport function collectCatalogProtocolDependenciesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): CatalogProtocolDependency[] {\r\n const dependencies: CatalogProtocolDependency[] = [];\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n for (const [name, specifier] of Object.entries(\r\n (manifest[field] as Record<string, string> | undefined) ?? {}\r\n )) {\r\n if (specifier.startsWith(\"catalog:\")) dependencies.push({ name, specifier });\r\n }\r\n }\r\n }\r\n return dependencies;\r\n}\r\n\r\nexport function collectCatalogProtocolDependencies(\r\n packageFiles: readonly string[]\r\n): CatalogProtocolDependency[] {\r\n return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\n/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */\r\nexport function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog {\r\n const packagesDir = path.join(repoRoot, \"packages\");\r\n const pins: ConsumerCatalog = {};\r\n if (!fs.existsSync(packagesDir)) return pins;\r\n\r\n for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {\r\n if (!entry.isDirectory() || !entry.name.startsWith(\"module-\")) continue;\r\n const manifestPath = path.join(packagesDir, entry.name, \"package.json\");\r\n if (!fs.existsSync(manifestPath)) continue;\r\n const manifest = readJson(manifestPath);\r\n const name = manifest.name;\r\n const version = manifest.version;\r\n if (typeof name !== \"string\" || typeof version !== \"string\") continue;\r\n if (!name.startsWith(\"@m5kdev/module-\")) continue;\r\n pins[name] = version;\r\n }\r\n\r\n return pins;\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n Object.assign(catalog, collectOptionalBackendModulePins(options.repoRoot));\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return sortedCatalog(catalog);\r\n}\r\n\r\nexport function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n const catalogs =\r\n workspace.catalogs && typeof workspace.catalogs === \"object\"\r\n ? { ...(workspace.catalogs as Record<string, unknown>) }\r\n : {};\r\n catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);\r\n workspace.catalogs = catalogs;\r\n if (workspace.catalog === undefined) workspace.catalog = {};\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readDefaultCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];\r\n if (named !== undefined) return asCatalog(named);\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const workspace = parse(options.source) as WorkspaceCatalogFile;\r\n const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== undefined;\r\n const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);\r\n const fallback = asCatalog(workspace.catalog);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n const managedPath = [\"catalogs\", MANAGED_CATALOG_NAME] as const;\r\n\r\n if (document.getIn([...managedPath]) === undefined) {\r\n if (document.get(\"catalogs\") === undefined) {\r\n document.set(\"catalogs\", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));\r\n } else {\r\n document.setIn([...managedPath], document.createNode({}));\r\n }\r\n }\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = (namedPresent ? named[name] : undefined) ?? fallback[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) {\r\n document.deleteIn([...managedPath, name]);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
|
package/dist/src/catalog.d.cts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
//#region src/catalog.d.ts
|
|
2
|
+
declare const MANAGED_CATALOG_NAME = "m5kdev";
|
|
3
|
+
declare const MANAGED_CATALOG_SPECIFIER = "catalog:m5kdev";
|
|
2
4
|
type ConsumerCatalog = Record<string, string>;
|
|
3
5
|
declare function walkPackageJsonFiles(directory: string, out?: string[]): string[];
|
|
4
6
|
declare function collectConsumerDependencyNamesFromManifests(manifests: readonly Record<string, unknown>[]): string[];
|
|
5
7
|
declare function collectConsumerDependencyNames(packageFiles: readonly string[]): string[];
|
|
8
|
+
interface CatalogProtocolDependency {
|
|
9
|
+
name: string;
|
|
10
|
+
specifier: string;
|
|
11
|
+
}
|
|
12
|
+
declare function collectCatalogProtocolDependenciesFromManifests(manifests: readonly Record<string, unknown>[]): CatalogProtocolDependency[];
|
|
13
|
+
declare function collectCatalogProtocolDependencies(packageFiles: readonly string[]): CatalogProtocolDependency[];
|
|
14
|
+
/** Optional `@m5kdev/module-*` pins stay in the Managed catalog even when Starter does not depend on them. */
|
|
15
|
+
declare function collectOptionalBackendModulePins(repoRoot: string): ConsumerCatalog;
|
|
6
16
|
declare function buildConsumerCatalog(options: {
|
|
7
17
|
repoRoot: string;
|
|
8
18
|
starterDirectory: string;
|
|
9
19
|
rootTemplatesDirectory: string;
|
|
10
20
|
}): ConsumerCatalog;
|
|
21
|
+
declare function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog;
|
|
11
22
|
declare function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string;
|
|
23
|
+
declare function readDefaultCatalog(source: string): ConsumerCatalog;
|
|
12
24
|
declare function readCatalog(source: string): ConsumerCatalog;
|
|
13
25
|
declare function assertCatalogKeys(catalog: ConsumerCatalog, expectedNames: readonly string[], label?: string): void;
|
|
14
26
|
interface CatalogMergeConflict {
|
|
@@ -27,5 +39,5 @@ declare function mergeManagedCatalog(options: {
|
|
|
27
39
|
conflicts: CatalogMergeConflict[];
|
|
28
40
|
};
|
|
29
41
|
//#endregion
|
|
30
|
-
export { CatalogMergeConflict, ConsumerCatalog, assertCatalogKeys, buildConsumerCatalog, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, mergeManagedCatalog, readCatalog, renderConsumerWorkspace, walkPackageJsonFiles };
|
|
42
|
+
export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, collectOptionalBackendModulePins, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
|
|
31
43
|
//# sourceMappingURL=catalog.d.cts.map
|
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
CHANGED
|
@@ -102,7 +102,9 @@ async function diagnoseManagedRepo(options) {
|
|
|
102
102
|
const workspaceRelativePath = "pnpm-workspace.yaml";
|
|
103
103
|
const workspacePath = node_path.default.join(options.repoRoot, workspaceRelativePath);
|
|
104
104
|
if (!await require_src_changes.findRepositorySymlink(options.repoRoot, workspaceRelativePath) && await exists(workspacePath)) try {
|
|
105
|
-
const
|
|
105
|
+
const workspaceSource = await node_fs_promises.default.readFile(workspacePath, "utf8");
|
|
106
|
+
const actualCatalog = require_src_catalog.readCatalog(workspaceSource);
|
|
107
|
+
const defaultCatalog = require_src_catalog.readDefaultCatalog(workspaceSource);
|
|
106
108
|
for (const [name, expected] of Object.entries(state.catalog)) {
|
|
107
109
|
if (!semver.default.validRange(expected)) diagnostics.push({
|
|
108
110
|
code: "CATALOG_VERSION_INVALID",
|
|
@@ -128,12 +130,16 @@ async function diagnoseManagedRepo(options) {
|
|
|
128
130
|
const appsDirectory = node_path.default.join(options.repoRoot, "apps");
|
|
129
131
|
const packageFiles = [node_path.default.join(options.repoRoot, "package.json")];
|
|
130
132
|
if (await exists(appsDirectory)) packageFiles.push(...require_src_catalog.walkPackageJsonFiles(appsDirectory));
|
|
131
|
-
for (const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
for (const { name, specifier } of require_src_catalog.collectCatalogProtocolDependencies(packageFiles)) {
|
|
134
|
+
const catalog = specifier === require_src_catalog.MANAGED_CATALOG_SPECIFIER ? actualCatalog : specifier === "catalog:" ? defaultCatalog : void 0;
|
|
135
|
+
if (catalog === void 0) continue;
|
|
136
|
+
if (!(name in catalog)) diagnostics.push({
|
|
137
|
+
code: "CATALOG_REFERENCE_MISSING",
|
|
138
|
+
severity: "error",
|
|
139
|
+
message: `${name} uses ${specifier} but has no matching catalog entry.`,
|
|
140
|
+
path: "pnpm-workspace.yaml"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
137
143
|
} catch (error) {
|
|
138
144
|
diagnostics.push({
|
|
139
145
|
code: "CATALOG_INVALID",
|
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","walkPackageJsonFiles","collectConsumerDependencyNames","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport { collectConsumerDependencyNames, readCatalog, walkPackageJsonFiles } from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const actualCatalog = readCatalog(await fs.readFile(workspacePath, \"utf8\"));\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const dependency of collectConsumerDependencyNames(packageFiles)) {\r\n if (!(dependency in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${dependency} uses catalog: but has no catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","readDefaultCatalog","walkPackageJsonFiles","collectCatalogProtocolDependencies","MANAGED_CATALOG_SPECIFIER","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport {\r\n collectCatalogProtocolDependencies,\r\n MANAGED_CATALOG_SPECIFIER,\r\n readCatalog,\r\n readDefaultCatalog,\r\n walkPackageJsonFiles,\r\n} from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const workspaceSource = await fs.readFile(workspacePath, \"utf8\");\r\n const actualCatalog = readCatalog(workspaceSource);\r\n const defaultCatalog = readDefaultCatalog(workspaceSource);\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const { name, specifier } of collectCatalogProtocolDependencies(packageFiles)) {\r\n const catalog =\r\n specifier === MANAGED_CATALOG_SPECIFIER\r\n ? actualCatalog\r\n : specifier === \"catalog:\"\r\n ? defaultCatalog\r\n : undefined;\r\n if (catalog === undefined) continue;\r\n if (!(name in catalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${name} uses ${specifier} but has no matching catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
|
package/dist/src/fs.cjs
CHANGED
|
@@ -22,16 +22,21 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
22
22
|
".yaml",
|
|
23
23
|
".yml",
|
|
24
24
|
".txt",
|
|
25
|
-
".mdc"
|
|
25
|
+
".mdc",
|
|
26
|
+
".toml"
|
|
26
27
|
]);
|
|
27
28
|
const TEXT_BASENAMES = new Set([
|
|
28
29
|
".gitignore",
|
|
29
30
|
".env",
|
|
30
31
|
".env.example",
|
|
31
|
-
".
|
|
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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"}
|