create-m5kdev 0.32.7 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/src/catalog.cjs +55 -10
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +11 -1
  4. package/dist/src/doctor.cjs +13 -7
  5. package/dist/src/doctor.cjs.map +1 -1
  6. package/package.json +1 -1
  7. package/templates/minimal-app/apps/e2e/package.json.tpl +7 -7
  8. package/templates/minimal-app/apps/email/package.json.tpl +13 -13
  9. package/templates/minimal-app/apps/expo/package.json.tpl +59 -59
  10. package/templates/minimal-app/apps/server/AGENTS.md.tpl +2 -2
  11. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +3 -0
  12. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1932 -0
  13. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +7 -0
  14. package/templates/minimal-app/apps/server/package.json.tpl +55 -56
  15. package/templates/minimal-app/apps/server/src/app.ts.tpl +113 -141
  16. package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
  17. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +5 -7
  18. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +6 -5
  19. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +1 -1
  20. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -1
  21. package/templates/minimal-app/apps/shared/package.json.tpl +5 -5
  22. package/templates/minimal-app/apps/webapp/package.json.tpl +33 -33
  23. package/templates/minimal-app/package.json.tpl +3 -3
  24. package/templates/minimal-app/pnpm-workspace.yaml.tpl +60 -58
  25. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
@@ -12,6 +12,8 @@ const DEPENDENCY_FIELDS = [
12
12
  "peerDependencies",
13
13
  "optionalDependencies"
14
14
  ];
15
+ const MANAGED_CATALOG_NAME = "m5kdev";
16
+ const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;
15
17
  function walkPackageJsonFiles(directory, out = []) {
16
18
  for (const entry of node_fs.default.readdirSync(directory, { withFileTypes: true })) {
17
19
  if (entry.name === "node_modules" || entry.name === ".git") continue;
@@ -24,6 +26,9 @@ function walkPackageJsonFiles(directory, out = []) {
24
26
  function readJson(filePath) {
25
27
  return JSON.parse(node_fs.default.readFileSync(filePath, "utf8"));
26
28
  }
29
+ function asCatalog(value) {
30
+ return Object.fromEntries(Object.entries(value ?? {}).map(([name, version]) => [name, String(version)]));
31
+ }
27
32
  function collectConsumerDependencyNamesFromManifests(manifests) {
28
33
  const names = /* @__PURE__ */ new Set();
29
34
  for (const manifest of manifests) for (const field of DEPENDENCY_FIELDS) {
@@ -35,6 +40,17 @@ 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
+ }
38
54
  function buildConsumerCatalog(options) {
39
55
  const workspacePath = node_path.default.join(options.repoRoot, "pnpm-workspace.yaml");
40
56
  const sourceCatalog = (0, yaml.parse)(node_fs.default.readFileSync(workspacePath, "utf8")).catalog ?? {};
@@ -67,14 +83,25 @@ function buildConsumerCatalog(options) {
67
83
  if (missing.length > 0) throw new Error(`Missing consumer catalog versions for: ${missing.join(", ")}`);
68
84
  return catalog;
69
85
  }
86
+ function sortedCatalog(catalog) {
87
+ return Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
88
+ }
70
89
  function renderConsumerWorkspace(source, catalog) {
71
90
  const workspace = (0, yaml.parse)(source);
72
- workspace.catalog = Object.fromEntries(Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right)));
91
+ const catalogs = workspace.catalogs && typeof workspace.catalogs === "object" ? { ...workspace.catalogs } : {};
92
+ catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);
93
+ workspace.catalogs = catalogs;
94
+ if (workspace.catalog === void 0) workspace.catalog = {};
73
95
  return (0, yaml.stringify)(workspace, { lineWidth: 0 });
74
96
  }
97
+ function readDefaultCatalog(source) {
98
+ return asCatalog((0, yaml.parse)(source).catalog);
99
+ }
75
100
  function readCatalog(source) {
76
101
  const workspace = (0, yaml.parse)(source);
77
- return Object.fromEntries(Object.entries(workspace.catalog ?? {}).map(([name, version]) => [name, String(version)]));
102
+ const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];
103
+ if (named !== void 0) return asCatalog(named);
104
+ return asCatalog(workspace.catalog);
78
105
  }
79
106
  function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
80
107
  const expected = new Set(expectedNames);
@@ -87,16 +114,24 @@ function assertCatalogKeys(catalog, expectedNames, label = "consumer catalog") {
87
114
  }
88
115
  function mergeManagedCatalog(options) {
89
116
  const document = (0, yaml.parseDocument)(options.source);
90
- const local = readCatalog(options.source);
117
+ const workspace = (0, yaml.parse)(options.source);
118
+ const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== void 0;
119
+ const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);
120
+ const fallback = asCatalog(workspace.catalog);
91
121
  const conflicts = [];
92
122
  const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);
123
+ const managedPath = ["catalogs", MANAGED_CATALOG_NAME];
124
+ if (document.getIn([...managedPath]) === void 0) if (document.get("catalogs") === void 0) document.set("catalogs", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));
125
+ else document.setIn([...managedPath], document.createNode({}));
93
126
  for (const name of [...names].sort((left, right) => left.localeCompare(right))) {
94
127
  const base = options.base[name];
95
- const current = local[name];
128
+ const current = (namedPresent ? named[name] : void 0) ?? fallback[name];
96
129
  const target = options.target[name];
97
130
  if (base === void 0) {
98
- if (current === void 0) document.setIn(["catalog", name], target);
99
- else if (current !== target) conflicts.push({
131
+ if (current === void 0) {
132
+ document.setIn([...managedPath, name], target);
133
+ document.deleteIn(["catalog", name]);
134
+ } else if (current !== target) conflicts.push({
100
135
  name,
101
136
  local: current,
102
137
  target
@@ -104,16 +139,20 @@ function mergeManagedCatalog(options) {
104
139
  continue;
105
140
  }
106
141
  if (target === void 0) {
107
- if (current === base) document.deleteIn(["catalog", name]);
108
- else if (current !== void 0) conflicts.push({
142
+ if (current === base) {
143
+ document.deleteIn([...managedPath, name]);
144
+ document.deleteIn(["catalog", name]);
145
+ } else if (current !== void 0) conflicts.push({
109
146
  name,
110
147
  base,
111
148
  local: current
112
149
  });
113
150
  continue;
114
151
  }
115
- if (current === base) document.setIn(["catalog", name], target);
116
- else if (current !== target && target !== base) conflicts.push({
152
+ if (current === base) {
153
+ document.setIn([...managedPath, name], target);
154
+ document.deleteIn(["catalog", name]);
155
+ } else if (current !== target && target !== base) conflicts.push({
117
156
  name,
118
157
  base,
119
158
  local: current,
@@ -128,13 +167,19 @@ function mergeManagedCatalog(options) {
128
167
  };
129
168
  }
130
169
  //#endregion
170
+ exports.MANAGED_CATALOG_NAME = MANAGED_CATALOG_NAME;
171
+ exports.MANAGED_CATALOG_SPECIFIER = MANAGED_CATALOG_SPECIFIER;
131
172
  exports.assertCatalogKeys = assertCatalogKeys;
132
173
  exports.buildConsumerCatalog = buildConsumerCatalog;
174
+ exports.collectCatalogProtocolDependencies = collectCatalogProtocolDependencies;
175
+ exports.collectCatalogProtocolDependenciesFromManifests = collectCatalogProtocolDependenciesFromManifests;
133
176
  exports.collectConsumerDependencyNames = collectConsumerDependencyNames;
134
177
  exports.collectConsumerDependencyNamesFromManifests = collectConsumerDependencyNamesFromManifests;
135
178
  exports.mergeManagedCatalog = mergeManagedCatalog;
136
179
  exports.readCatalog = readCatalog;
180
+ exports.readDefaultCatalog = readDefaultCatalog;
137
181
  exports.renderConsumerWorkspace = renderConsumerWorkspace;
182
+ exports.sortedCatalog = sortedCatalog;
138
183
  exports.walkPackageJsonFiles = walkPackageJsonFiles;
139
184
 
140
185
  //# sourceMappingURL=catalog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return catalog;\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n workspace.catalog = Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as { catalog?: Record<string, string | number> };\r\n return Object.fromEntries(\r\n Object.entries(workspace.catalog ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const local = readCatalog(options.source);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = local[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) document.setIn([\"catalog\", name], target);\r\n else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) document.deleteIn([\"catalog\", name]);\r\n else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) document.setIn([\"catalog\", name], target);\r\n else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog.cjs","names":["fs","path"],"sources":["../../src/catalog.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { parse, parseDocument, stringify } from \"yaml\";\r\n\r\nconst DEPENDENCY_FIELDS = [\r\n \"dependencies\",\r\n \"devDependencies\",\r\n \"peerDependencies\",\r\n \"optionalDependencies\",\r\n] as const;\r\n\r\nexport const MANAGED_CATALOG_NAME = \"m5kdev\";\r\nexport const MANAGED_CATALOG_SPECIFIER = `catalog:${MANAGED_CATALOG_NAME}`;\r\n\r\nexport type ConsumerCatalog = Record<string, string>;\r\n\r\ninterface WorkspaceCatalogFile {\r\n catalog?: Record<string, string | number>;\r\n catalogs?: Record<string, Record<string, string | number> | undefined>;\r\n}\r\n\r\nexport function walkPackageJsonFiles(directory: string, out: string[] = []): string[] {\r\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\r\n if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\r\n const entryPath = path.join(directory, entry.name);\r\n if (entry.isDirectory()) {\r\n walkPackageJsonFiles(entryPath, out);\r\n } else if (entry.isFile() && entry.name === \"package.json\") {\r\n out.push(entryPath);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction readJson(filePath: string): Record<string, unknown> {\r\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as Record<string, unknown>;\r\n}\r\n\r\nfunction asCatalog(value: Record<string, string | number> | undefined): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(value ?? {}).map(([name, version]) => [name, String(version)])\r\n );\r\n}\r\n\r\nexport function collectConsumerDependencyNamesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): string[] {\r\n const names = new Set<string>();\r\n\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n const dependencies = manifest[field] as Record<string, string> | undefined;\r\n for (const [name, specifier] of Object.entries(dependencies ?? {})) {\r\n if (\r\n specifier.startsWith(\"catalog:\") ||\r\n (name.startsWith(\"@m5kdev/\") && specifier.startsWith(\"workspace:\"))\r\n ) {\r\n names.add(name);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return [...names].sort((left, right) => left.localeCompare(right));\r\n}\r\n\r\nexport function collectConsumerDependencyNames(packageFiles: readonly string[]): string[] {\r\n return collectConsumerDependencyNamesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport interface CatalogProtocolDependency {\r\n name: string;\r\n specifier: string;\r\n}\r\n\r\nexport function collectCatalogProtocolDependenciesFromManifests(\r\n manifests: readonly Record<string, unknown>[]\r\n): CatalogProtocolDependency[] {\r\n const dependencies: CatalogProtocolDependency[] = [];\r\n for (const manifest of manifests) {\r\n for (const field of DEPENDENCY_FIELDS) {\r\n for (const [name, specifier] of Object.entries(\r\n (manifest[field] as Record<string, string> | undefined) ?? {}\r\n )) {\r\n if (specifier.startsWith(\"catalog:\")) dependencies.push({ name, specifier });\r\n }\r\n }\r\n }\r\n return dependencies;\r\n}\r\n\r\nexport function collectCatalogProtocolDependencies(\r\n packageFiles: readonly string[]\r\n): CatalogProtocolDependency[] {\r\n return collectCatalogProtocolDependenciesFromManifests(packageFiles.map(readJson));\r\n}\r\n\r\nexport function buildConsumerCatalog(options: {\r\n repoRoot: string;\r\n starterDirectory: string;\r\n rootTemplatesDirectory: string;\r\n}): ConsumerCatalog {\r\n const workspacePath = path.join(options.repoRoot, \"pnpm-workspace.yaml\");\r\n const workspace = parse(fs.readFileSync(workspacePath, \"utf8\")) as {\r\n catalog?: Record<string, string | number>;\r\n };\r\n const sourceCatalog = workspace.catalog ?? {};\r\n const packageFiles = [\r\n ...walkPackageJsonFiles(options.starterDirectory),\r\n path.join(options.rootTemplatesDirectory, \"package.json.tpl\"),\r\n ];\r\n const names = collectConsumerDependencyNames(packageFiles);\r\n const catalog: ConsumerCatalog = {};\r\n const missing: string[] = [];\r\n\r\n for (const name of names) {\r\n if (name.startsWith(\"@m5kdev/\")) {\r\n const packageName = name.slice(\"@m5kdev/\".length);\r\n const packagePath = path.join(options.repoRoot, \"packages\", packageName, \"package.json\");\r\n if (!fs.existsSync(packagePath)) {\r\n missing.push(name);\r\n continue;\r\n }\r\n const packageManifest = readJson(packagePath);\r\n if (typeof packageManifest.version !== \"string\") {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = packageManifest.version;\r\n continue;\r\n }\r\n\r\n const version = sourceCatalog[name];\r\n if (version === undefined) {\r\n missing.push(name);\r\n continue;\r\n }\r\n catalog[name] = String(version);\r\n }\r\n\r\n if (missing.length > 0) {\r\n throw new Error(`Missing consumer catalog versions for: ${missing.join(\", \")}`);\r\n }\r\n\r\n return catalog;\r\n}\r\n\r\nexport function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog {\r\n return Object.fromEntries(\r\n Object.entries(catalog).sort(([left], [right]) => left.localeCompare(right))\r\n );\r\n}\r\n\r\nexport function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string {\r\n const workspace = parse(source) as Record<string, unknown>;\r\n const catalogs =\r\n workspace.catalogs && typeof workspace.catalogs === \"object\"\r\n ? { ...(workspace.catalogs as Record<string, unknown>) }\r\n : {};\r\n catalogs[MANAGED_CATALOG_NAME] = sortedCatalog(catalog);\r\n workspace.catalogs = catalogs;\r\n if (workspace.catalog === undefined) workspace.catalog = {};\r\n return stringify(workspace, { lineWidth: 0 });\r\n}\r\n\r\nexport function readDefaultCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function readCatalog(source: string): ConsumerCatalog {\r\n const workspace = parse(source) as WorkspaceCatalogFile;\r\n const named = workspace.catalogs?.[MANAGED_CATALOG_NAME];\r\n if (named !== undefined) return asCatalog(named);\r\n return asCatalog(workspace.catalog);\r\n}\r\n\r\nexport function assertCatalogKeys(\r\n catalog: ConsumerCatalog,\r\n expectedNames: readonly string[],\r\n label = \"consumer catalog\"\r\n): void {\r\n const expected = new Set(expectedNames);\r\n const missing = expectedNames.filter((name) => !(name in catalog));\r\n const obsolete = Object.keys(catalog).filter((name) => !expected.has(name));\r\n if (missing.length > 0 || obsolete.length > 0) {\r\n const details = [\r\n missing.length > 0 ? `missing: ${missing.join(\", \")}` : undefined,\r\n obsolete.length > 0 ? `obsolete: ${obsolete.join(\", \")}` : undefined,\r\n ].filter(Boolean);\r\n throw new Error(`Invalid ${label} (${details.join(\"; \")})`);\r\n }\r\n}\r\n\r\nexport interface CatalogMergeConflict {\r\n name: string;\r\n base?: string;\r\n local?: string;\r\n target?: string;\r\n}\r\n\r\nexport function mergeManagedCatalog(options: {\r\n source: string;\r\n base: ConsumerCatalog;\r\n target: ConsumerCatalog;\r\n}): { source: string; changed: boolean; conflicts: CatalogMergeConflict[] } {\r\n const document = parseDocument(options.source);\r\n const workspace = parse(options.source) as WorkspaceCatalogFile;\r\n const namedPresent = workspace.catalogs?.[MANAGED_CATALOG_NAME] !== undefined;\r\n const named = asCatalog(workspace.catalogs?.[MANAGED_CATALOG_NAME]);\r\n const fallback = asCatalog(workspace.catalog);\r\n const conflicts: CatalogMergeConflict[] = [];\r\n const names = new Set([...Object.keys(options.base), ...Object.keys(options.target)]);\r\n const managedPath = [\"catalogs\", MANAGED_CATALOG_NAME] as const;\r\n\r\n if (document.getIn([...managedPath]) === undefined) {\r\n if (document.get(\"catalogs\") === undefined) {\r\n document.set(\"catalogs\", document.createNode({ [MANAGED_CATALOG_NAME]: {} }));\r\n } else {\r\n document.setIn([...managedPath], document.createNode({}));\r\n }\r\n }\r\n\r\n for (const name of [...names].sort((left, right) => left.localeCompare(right))) {\r\n const base = options.base[name];\r\n const current = (namedPresent ? named[name] : undefined) ?? fallback[name];\r\n const target = options.target[name];\r\n\r\n if (base === undefined) {\r\n if (current === undefined) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target) conflicts.push({ name, local: current, target });\r\n continue;\r\n }\r\n if (target === undefined) {\r\n if (current === base) {\r\n document.deleteIn([...managedPath, name]);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== undefined) conflicts.push({ name, base, local: current });\r\n continue;\r\n }\r\n if (current === base) {\r\n document.setIn([...managedPath, name], target);\r\n document.deleteIn([\"catalog\", name]);\r\n } else if (current !== target && target !== base) {\r\n conflicts.push({ name, base, local: current, target });\r\n }\r\n }\r\n\r\n const merged = document.toString({ lineWidth: 0 });\r\n return { source: merged, changed: merged !== options.source, conflicts };\r\n}\r\n"],"mappings":"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"}
@@ -1,14 +1,24 @@
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[];
6
14
  declare function buildConsumerCatalog(options: {
7
15
  repoRoot: string;
8
16
  starterDirectory: string;
9
17
  rootTemplatesDirectory: string;
10
18
  }): ConsumerCatalog;
19
+ declare function sortedCatalog(catalog: ConsumerCatalog): ConsumerCatalog;
11
20
  declare function renderConsumerWorkspace(source: string, catalog: ConsumerCatalog): string;
21
+ declare function readDefaultCatalog(source: string): ConsumerCatalog;
12
22
  declare function readCatalog(source: string): ConsumerCatalog;
13
23
  declare function assertCatalogKeys(catalog: ConsumerCatalog, expectedNames: readonly string[], label?: string): void;
14
24
  interface CatalogMergeConflict {
@@ -27,5 +37,5 @@ declare function mergeManagedCatalog(options: {
27
37
  conflicts: CatalogMergeConflict[];
28
38
  };
29
39
  //#endregion
30
- export { CatalogMergeConflict, ConsumerCatalog, assertCatalogKeys, buildConsumerCatalog, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, mergeManagedCatalog, readCatalog, renderConsumerWorkspace, walkPackageJsonFiles };
40
+ export { CatalogMergeConflict, CatalogProtocolDependency, ConsumerCatalog, MANAGED_CATALOG_NAME, MANAGED_CATALOG_SPECIFIER, assertCatalogKeys, buildConsumerCatalog, collectCatalogProtocolDependencies, collectCatalogProtocolDependenciesFromManifests, collectConsumerDependencyNames, collectConsumerDependencyNamesFromManifests, mergeManagedCatalog, readCatalog, readDefaultCatalog, renderConsumerWorkspace, sortedCatalog, walkPackageJsonFiles };
31
41
  //# sourceMappingURL=catalog.d.cts.map
@@ -102,7 +102,9 @@ async function diagnoseManagedRepo(options) {
102
102
  const workspaceRelativePath = "pnpm-workspace.yaml";
103
103
  const workspacePath = node_path.default.join(options.repoRoot, workspaceRelativePath);
104
104
  if (!await require_src_changes.findRepositorySymlink(options.repoRoot, workspaceRelativePath) && await exists(workspacePath)) try {
105
- const actualCatalog = require_src_catalog.readCatalog(await node_fs_promises.default.readFile(workspacePath, "utf8"));
105
+ const workspaceSource = await node_fs_promises.default.readFile(workspacePath, "utf8");
106
+ const actualCatalog = require_src_catalog.readCatalog(workspaceSource);
107
+ const defaultCatalog = require_src_catalog.readDefaultCatalog(workspaceSource);
106
108
  for (const [name, expected] of Object.entries(state.catalog)) {
107
109
  if (!semver.default.validRange(expected)) diagnostics.push({
108
110
  code: "CATALOG_VERSION_INVALID",
@@ -128,12 +130,16 @@ async function diagnoseManagedRepo(options) {
128
130
  const appsDirectory = node_path.default.join(options.repoRoot, "apps");
129
131
  const packageFiles = [node_path.default.join(options.repoRoot, "package.json")];
130
132
  if (await exists(appsDirectory)) packageFiles.push(...require_src_catalog.walkPackageJsonFiles(appsDirectory));
131
- for (const dependency of require_src_catalog.collectConsumerDependencyNames(packageFiles)) if (!(dependency in actualCatalog)) diagnostics.push({
132
- code: "CATALOG_REFERENCE_MISSING",
133
- severity: "error",
134
- message: `${dependency} uses catalog: but has no catalog entry.`,
135
- path: "pnpm-workspace.yaml"
136
- });
133
+ for (const { name, specifier } of require_src_catalog.collectCatalogProtocolDependencies(packageFiles)) {
134
+ const catalog = specifier === require_src_catalog.MANAGED_CATALOG_SPECIFIER ? actualCatalog : specifier === "catalog:" ? defaultCatalog : void 0;
135
+ if (catalog === void 0) continue;
136
+ if (!(name in catalog)) diagnostics.push({
137
+ code: "CATALOG_REFERENCE_MISSING",
138
+ severity: "error",
139
+ message: `${name} uses ${specifier} but has no matching catalog entry.`,
140
+ path: "pnpm-workspace.yaml"
141
+ });
142
+ }
137
143
  } catch (error) {
138
144
  diagnostics.push({
139
145
  code: "CATALOG_INVALID",
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","walkPackageJsonFiles","collectConsumerDependencyNames","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport { collectConsumerDependencyNames, readCatalog, walkPackageJsonFiles } from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const actualCatalog = readCatalog(await fs.readFile(workspacePath, \"utf8\"));\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const dependency of collectConsumerDependencyNames(packageFiles)) {\r\n if (!(dependency in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${dependency} uses catalog: but has no catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"doctor.cjs","names":["execFile","fs","readManagedState","createDiagnosticReport","getCliVersion","loadTemplateManifest","getTemplateRoot","findRepositorySymlink","path","readCatalog","readDefaultCatalog","walkPackageJsonFiles","collectCatalogProtocolDependencies","MANAGED_CATALOG_SPECIFIER","sha256","getPendingMigrations","runMigrationValidators","ChangeSet"],"sources":["../../src/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\r\nimport fs from \"node:fs/promises\";\r\nimport path from \"node:path\";\r\nimport { promisify } from \"node:util\";\r\nimport semver from \"semver\";\r\nimport {\r\n MANAGED_CATALOG_SPECIFIER,\r\n collectCatalogProtocolDependencies,\r\n readCatalog,\r\n readDefaultCatalog,\r\n walkPackageJsonFiles,\r\n} from \"./catalog\";\r\nimport { ChangeSet, findRepositorySymlink } from \"./changes\";\r\nimport { TEMPLATE_NAME } from \"./constants\";\r\nimport { createDiagnosticReport, type Diagnostic, type DiagnosticReport } from \"./diagnostics\";\r\nimport { getPendingMigrations, runMigrationValidators } from \"./migrations/registry\";\r\nimport { getTemplateRoot } from \"./paths\";\r\nimport { getCliVersion, type ManagedState, readManagedState, sha256 } from \"./state\";\r\nimport { loadTemplateManifest } from \"./template\";\r\n\r\nconst execFileAsync = promisify(execFile);\r\nconst FULL_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;\r\n\r\nexport interface DoctorOptions {\r\n repoRoot: string;\r\n full?: boolean;\r\n state?: ManagedState;\r\n}\r\n\r\nasync function exists(filePath: string): Promise<boolean> {\r\n return fs\r\n .access(filePath)\r\n .then(() => true)\r\n .catch(() => false);\r\n}\r\n\r\nexport async function diagnoseManagedRepo(options: DoctorOptions): Promise<DiagnosticReport> {\r\n const diagnostics: Diagnostic[] = [];\r\n let state = options.state;\r\n if (!state) {\r\n try {\r\n state = await readManagedState(options.repoRoot);\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"STATE_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to read .m5kdev.json.\",\r\n path: \".m5kdev.json\",\r\n suggestion: \"Run m5kdev init to establish a managed baseline.\",\r\n });\r\n return createDiagnosticReport(diagnostics);\r\n }\r\n }\r\n\r\n const cliVersion = getCliVersion();\r\n if (state.template.name !== TEMPLATE_NAME) {\r\n diagnostics.push({\r\n code: \"STATE_TEMPLATE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unsupported template name: ${state.template.name}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!semver.valid(state.template.version)) {\r\n diagnostics.push({\r\n code: \"STATE_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `Invalid template version: ${state.template.version}`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (semver.gt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"CLI_TOO_OLD\",\r\n severity: \"error\",\r\n message: `This repository is managed by ${state.template.version}, but the running CLI is ${cliVersion}.`,\r\n suggestion: `Run pnpm dlx create-m5kdev@${state.template.version} doctor.`,\r\n });\r\n } else if (semver.lt(state.template.version, cliVersion)) {\r\n diagnostics.push({\r\n code: \"UPDATE_AVAILABLE\",\r\n severity: \"warning\",\r\n message: `Template ${cliVersion} is available; this repository is at ${state.template.version}.`,\r\n suggestion: `Preview it with pnpm dlx create-m5kdev@${cliVersion} update --dry-run.`,\r\n });\r\n }\r\n\r\n const manifest = loadTemplateManifest(getTemplateRoot());\r\n const seenFeatures = new Set<string>();\r\n for (const feature of state.template.features) {\r\n if (seenFeatures.has(feature)) {\r\n diagnostics.push({\r\n code: \"FEATURE_DUPLICATE\",\r\n severity: \"error\",\r\n message: `Feature ${feature} is listed more than once.`,\r\n path: \".m5kdev.json\",\r\n });\r\n } else if (!(feature in manifest.features)) {\r\n diagnostics.push({\r\n code: \"FEATURE_UNKNOWN\",\r\n severity: \"error\",\r\n message: `Unknown managed feature: ${feature}`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n seenFeatures.add(feature);\r\n }\r\n const requiredPaths = new Set(manifest.requiredPaths);\r\n if (state.template.features.includes(\"webapp\")) requiredPaths.add(\"apps/webapp/package.json\");\r\n if (state.template.features.includes(\"expo\")) requiredPaths.add(\"apps/expo/package.json\");\r\n if (state.template.features.includes(\"test-harness\")) requiredPaths.add(\"apps/e2e/package.json\");\r\n for (const relativePath of requiredPaths) {\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Required managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n } else if (!(await exists(path.join(options.repoRoot, relativePath)))) {\r\n diagnostics.push({\r\n code: \"REQUIRED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"Required managed path is missing.\",\r\n path: relativePath,\r\n suggestion:\r\n \"Restore the path or correct the enabled features before initializing/updating.\",\r\n });\r\n }\r\n }\r\n\r\n const workspaceRelativePath = \"pnpm-workspace.yaml\";\r\n const workspacePath = path.join(options.repoRoot, workspaceRelativePath);\r\n const workspaceSymlink = await findRepositorySymlink(options.repoRoot, workspaceRelativePath);\r\n if (!workspaceSymlink && (await exists(workspacePath))) {\r\n try {\r\n const workspaceSource = await fs.readFile(workspacePath, \"utf8\");\r\n const actualCatalog = readCatalog(workspaceSource);\r\n const defaultCatalog = readDefaultCatalog(workspaceSource);\r\n for (const [name, expected] of Object.entries(state.catalog)) {\r\n if (!semver.validRange(expected)) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_INVALID\",\r\n severity: \"error\",\r\n message: `${name} has an invalid managed SemVer range: ${expected}.`,\r\n path: \".m5kdev.json\",\r\n });\r\n }\r\n if (!(name in actualCatalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_ENTRY_MISSING\",\r\n severity: \"error\",\r\n message: `Managed catalog entry ${name} is missing.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Restore ${name}: ${expected}.`,\r\n });\r\n } else if (actualCatalog[name] !== expected) {\r\n diagnostics.push({\r\n code: \"CATALOG_VERSION_MISMATCH\",\r\n severity: \"error\",\r\n message: `${name} is ${actualCatalog[name]}, expected managed version ${expected}.`,\r\n path: \"pnpm-workspace.yaml\",\r\n suggestion: `Set ${name} to ${expected} in pnpm-workspace.yaml.`,\r\n });\r\n }\r\n }\r\n\r\n const appsDirectory = path.join(options.repoRoot, \"apps\");\r\n const packageFiles = [path.join(options.repoRoot, \"package.json\")];\r\n if (await exists(appsDirectory)) packageFiles.push(...walkPackageJsonFiles(appsDirectory));\r\n for (const { name, specifier } of collectCatalogProtocolDependencies(packageFiles)) {\r\n const catalog =\r\n specifier === MANAGED_CATALOG_SPECIFIER\r\n ? actualCatalog\r\n : specifier === \"catalog:\"\r\n ? defaultCatalog\r\n : undefined;\r\n if (catalog === undefined) continue;\r\n if (!(name in catalog)) {\r\n diagnostics.push({\r\n code: \"CATALOG_REFERENCE_MISSING\",\r\n severity: \"error\",\r\n message: `${name} uses ${specifier} but has no matching catalog entry.`,\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"CATALOG_INVALID\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Unable to parse the consumer catalog.\",\r\n path: \"pnpm-workspace.yaml\",\r\n });\r\n }\r\n }\r\n\r\n for (const [relativePath, baseline] of Object.entries(state.files)) {\r\n if (baseline.policy === \"ignore\") continue;\r\n const filePath = path.join(options.repoRoot, relativePath);\r\n const symlink = await findRepositorySymlink(options.repoRoot, relativePath);\r\n if (symlink) {\r\n diagnostics.push({\r\n code: \"MANAGED_PATH_SYMLINK\",\r\n severity: \"error\",\r\n message: `Managed path contains symbolic link ${symlink}.`,\r\n path: relativePath,\r\n });\r\n continue;\r\n }\r\n const stat = await fs.lstat(filePath).catch(() => null);\r\n if (!stat) {\r\n if (baseline.policy === \"ensure\") {\r\n diagnostics.push({\r\n code: \"ENSURED_PATH_MISSING\",\r\n severity: \"error\",\r\n message: \"An ensured template path is missing.\",\r\n path: relativePath,\r\n });\r\n }\r\n continue;\r\n }\r\n if (!stat.isFile()) continue;\r\n const content = await fs.readFile(filePath);\r\n if (baseline.sha256 && sha256(content) !== baseline.sha256) {\r\n diagnostics.push({\r\n code: \"TEMPLATE_CUSTOMIZED\",\r\n severity: \"info\",\r\n message: \"Managed template content has local customizations.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (baseline.kind === \"text\") {\r\n const source = content.toString(\"utf8\");\r\n if (\r\n /\\{\\{(?:APP_NAME|APP_DESCRIPTION|APP_SLUG|PACKAGE_SCOPE|BETTER_AUTH_SECRET)\\}\\}/.test(\r\n source\r\n )\r\n ) {\r\n diagnostics.push({\r\n code: \"UNRESOLVED_TEMPLATE_TOKEN\",\r\n severity: \"error\",\r\n message: \"Unresolved template token found.\",\r\n path: relativePath,\r\n });\r\n }\r\n if (/^(<{7}|={7}|>{7})(?: |$)/m.test(source)) {\r\n diagnostics.push({\r\n code: \"MERGE_CONFLICT_MARKER\",\r\n severity: \"error\",\r\n message: \"Unresolved merge conflict marker found.\",\r\n path: relativePath,\r\n });\r\n }\r\n }\r\n }\r\n\r\n const pending = getPendingMigrations(state, cliVersion);\r\n for (const migration of pending) {\r\n diagnostics.push({\r\n code: \"MIGRATION_PENDING\",\r\n severity: \"error\",\r\n message: migration.description,\r\n migrationId: migration.id,\r\n guide: migration.guide,\r\n suggestion: `Run pnpm dlx create-m5kdev@${cliVersion} update.`,\r\n });\r\n }\r\n diagnostics.push(\r\n ...(await runMigrationValidators({\r\n repoRoot: options.repoRoot,\r\n state,\r\n changes: new ChangeSet(options.repoRoot),\r\n }))\r\n );\r\n\r\n if (options.full) {\r\n const packagePath = path.join(options.repoRoot, \"package.json\");\r\n let packageManifest: { scripts?: Record<string, string> } | undefined;\r\n try {\r\n packageManifest = JSON.parse(await fs.readFile(packagePath, \"utf8\")) as {\r\n scripts?: Record<string, string>;\r\n };\r\n } catch (error) {\r\n diagnostics.push({\r\n code: \"PACKAGE_JSON_UNREADABLE\",\r\n severity: \"error\",\r\n message:\r\n error instanceof Error\r\n ? `Unable to read package.json: ${error.message}`\r\n : \"Unable to read package.json.\",\r\n path: \"package.json\",\r\n });\r\n }\r\n if (packageManifest) {\r\n for (const script of [\"check-types\", \"lint\", \"build\"] as const) {\r\n if (!packageManifest.scripts?.[script]) continue;\r\n try {\r\n await execFileAsync(\"pnpm\", [script], {\r\n cwd: options.repoRoot,\r\n env: process.env,\r\n timeout: FULL_COMMAND_TIMEOUT_MS,\r\n });\r\n } catch (error) {\r\n diagnostics.push({\r\n code: `COMMAND_${script.replace(\"-\", \"_\").toUpperCase()}_FAILED`,\r\n severity: \"error\",\r\n message: `${script} failed: ${error instanceof Error ? error.message : String(error)}`,\r\n path: \"package.json\",\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return createDiagnosticReport(diagnostics);\r\n}\r\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m5kdev",
3
- "version": "0.32.7",
3
+ "version": "0.33.0",
4
4
  "license": "GPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,13 +11,13 @@
11
11
  "test:e2e:expo": "playwright test --config playwright.expo.config.ts --headed"
12
12
  },
13
13
  "devDependencies": {
14
- "@m5kdev/backend": "catalog:",
15
- "@m5kdev/commons": "catalog:",
16
- "@m5kdev/config": "catalog:",
17
- "@playwright/test": "catalog:",
14
+ "@m5kdev/backend": "catalog:m5kdev",
15
+ "@m5kdev/commons": "catalog:m5kdev",
16
+ "@m5kdev/config": "catalog:m5kdev",
17
+ "@playwright/test": "catalog:m5kdev",
18
18
  "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
- "@trpc/client": "catalog:",
20
- "@types/node": "catalog:",
21
- "typescript": "catalog:"
19
+ "@trpc/client": "catalog:m5kdev",
20
+ "@types/node": "catalog:m5kdev",
21
+ "typescript": "catalog:m5kdev"
22
22
  }
23
23
  }
@@ -10,21 +10,21 @@
10
10
  "email:dev": "react-email dev --dir src/emails"
11
11
  },
12
12
  "dependencies": {
13
- "@m5kdev/commons": "catalog:",
14
- "@m5kdev/email": "catalog:",
15
- "@react-email/components": "catalog:",
16
- "react": "catalog:",
17
- "react-dom": "catalog:"
13
+ "@m5kdev/commons": "catalog:m5kdev",
14
+ "@m5kdev/email": "catalog:m5kdev",
15
+ "@react-email/components": "catalog:m5kdev",
16
+ "react": "catalog:m5kdev",
17
+ "react-dom": "catalog:m5kdev"
18
18
  },
19
19
  "devDependencies": {
20
- "@m5kdev/backend": "catalog:",
21
- "@m5kdev/config": "catalog:",
22
- "@types/node": "catalog:",
23
- "@types/react": "catalog:",
24
- "@types/react-dom": "catalog:",
25
- "react-email": "catalog:",
26
- "tslib": "catalog:",
27
- "typescript": "catalog:"
20
+ "@m5kdev/backend": "catalog:m5kdev",
21
+ "@m5kdev/config": "catalog:m5kdev",
22
+ "@types/node": "catalog:m5kdev",
23
+ "@types/react": "catalog:m5kdev",
24
+ "@types/react-dom": "catalog:m5kdev",
25
+ "react-email": "catalog:m5kdev",
26
+ "tslib": "catalog:m5kdev",
27
+ "typescript": "catalog:m5kdev"
28
28
  },
29
29
  "main": "./src/index.ts",
30
30
  "exports": {
@@ -1,59 +1,59 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/expo",
3
- "version": "1.5.3",
4
- "private": true,
5
- "main": "expo-router/entry",
6
- "scripts": {
7
- "build": "expo export --platform web --output-dir dist",
8
- "start": "expo start",
9
- "dev:standard": "EXPO_PUBLIC_ENABLE_WAITLIST=false EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18182 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15183 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15183 --host localhost",
10
- "dev:waitlist": "EXPO_PUBLIC_ENABLE_WAITLIST=true EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18183 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15184 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15184 --host localhost",
11
- "android": "expo start --android",
12
- "ios": "expo start --ios",
13
- "lint": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts",
14
- "lint:fix": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts --write",
15
- "format": "prettier --write .",
16
- "format:check": "prettier --check .",
17
- "check-types": "tsc --noEmit",
18
- "typecheck": "pnpm check-types"
19
- },
20
- "dependencies": {
21
- "@expo/metro-runtime": "~56.0.13",
22
- "@gorhom/bottom-sheet": "^5.2.14",
23
- "@m5kdev/commons": "catalog:",
24
- "@m5kdev/frontend": "catalog:",
25
- "@trpc/client": "catalog:",
26
- "expo": "~56.0.5",
27
- "expo-constants": "~56.0.16",
28
- "expo-linking": "~56.0.12",
29
- "expo-router": "~56.2.7",
30
- "expo-splash-screen": "~56.0.10",
31
- "expo-status-bar": "~56.0.4",
32
- "heroui-native": "^1.0.2",
33
- "{{PACKAGE_SCOPE}}/shared": "workspace:*",
34
- "react": "19.2.3",
35
- "react-dom": "19.2.3",
36
- "react-native": "0.85.3",
37
- "react-native-gesture-handler": "~2.31.2",
38
- "react-native-reanimated": "~4.3.1",
39
- "react-native-safe-area-context": "~5.7.0",
40
- "react-native-screens": "~4.25.2",
41
- "react-native-svg": "15.15.4",
42
- "react-native-web": "catalog:",
43
- "react-native-worklets": "0.8.3",
44
- "tailwind-merge": "^3.4.0",
45
- "tailwind-variants": "^3.2.2",
46
- "tailwindcss": "^4.1.17",
47
- "uniwind": "^1.5.0"
48
- },
49
- "devDependencies": {
50
- "@biomejs/biome": "catalog:",
51
- "@m5kdev/backend": "catalog:",
52
- "@m5kdev/config": "catalog:",
53
- "@types/react": "~19.2.15",
54
- "eslint": "^9.39.4",
55
- "eslint-config-expo": "^56.0.4",
56
- "prettier": "^3.8.3",
57
- "typescript": "~6.0.3"
58
- }
59
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/expo",
3
+ "version": "1.5.3",
4
+ "private": true,
5
+ "main": "expo-router/entry",
6
+ "scripts": {
7
+ "build": "expo export --platform web --output-dir dist",
8
+ "start": "expo start",
9
+ "dev:standard": "EXPO_PUBLIC_ENABLE_WAITLIST=false EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18182 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15183 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15183 --host localhost",
10
+ "dev:waitlist": "EXPO_PUBLIC_ENABLE_WAITLIST=true EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18183 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15184 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15184 --host localhost",
11
+ "android": "expo start --android",
12
+ "ios": "expo start --ios",
13
+ "lint": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts",
14
+ "lint:fix": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts --write",
15
+ "format": "prettier --write .",
16
+ "format:check": "prettier --check .",
17
+ "check-types": "tsc --noEmit",
18
+ "typecheck": "pnpm check-types"
19
+ },
20
+ "dependencies": {
21
+ "@expo/metro-runtime": "~56.0.13",
22
+ "@gorhom/bottom-sheet": "^5.2.14",
23
+ "@m5kdev/commons": "catalog:m5kdev",
24
+ "@m5kdev/frontend": "catalog:m5kdev",
25
+ "@trpc/client": "catalog:m5kdev",
26
+ "expo": "~56.0.5",
27
+ "expo-constants": "~56.0.16",
28
+ "expo-linking": "~56.0.12",
29
+ "expo-router": "~56.2.7",
30
+ "expo-splash-screen": "~56.0.10",
31
+ "expo-status-bar": "~56.0.4",
32
+ "heroui-native": "^1.0.2",
33
+ "{{PACKAGE_SCOPE}}/shared": "workspace:*",
34
+ "react": "catalog:m5kdev",
35
+ "react-dom": "catalog:m5kdev",
36
+ "react-native": "0.85.3",
37
+ "react-native-gesture-handler": "~2.31.2",
38
+ "react-native-reanimated": "~4.3.1",
39
+ "react-native-safe-area-context": "~5.7.0",
40
+ "react-native-screens": "~4.25.2",
41
+ "react-native-svg": "15.15.4",
42
+ "react-native-web": "catalog:m5kdev",
43
+ "react-native-worklets": "0.8.3",
44
+ "tailwind-merge": "catalog:m5kdev",
45
+ "tailwind-variants": "^3.2.2",
46
+ "tailwindcss": "catalog:m5kdev",
47
+ "uniwind": "^1.5.0"
48
+ },
49
+ "devDependencies": {
50
+ "@biomejs/biome": "catalog:m5kdev",
51
+ "@m5kdev/backend": "catalog:m5kdev",
52
+ "@m5kdev/config": "catalog:m5kdev",
53
+ "@types/react": "catalog:m5kdev",
54
+ "eslint": "^9.39.4",
55
+ "eslint-config-expo": "^56.0.4",
56
+ "prettier": "^3.8.3",
57
+ "typescript": "catalog:m5kdev"
58
+ }
59
+ }
@@ -28,7 +28,7 @@ apps/server/src/modules/<module>/
28
28
  - Repositories own persistence and query construction.
29
29
  - Services own business rules, orchestration, and context-aware defaults.
30
30
  - tRPC files own transport only and must delegate to services.
31
- - Register modules in `apps/server/src/app.ts`; use `db.ts` and `lib/auth.ts` only for scripts that need direct DB or auth access.
31
+ - Register modules in `apps/server/src/app.ts`; use `db.ts` only for scripts that need direct DB.
32
32
 
33
33
  ## Avoid Trivial Service Delegation
34
34
 
@@ -40,7 +40,7 @@ apps/server/src/modules/<module>/
40
40
  ## Workflow, Redis, and push notifications
41
41
 
42
42
  - `WorkflowModule` and `NotificationModule` are registered in `app.ts`. Start **Redis** locally (`REDIS_URL`) before `pnpm dev` on the server, or background jobs will not run.
43
- - `index.ts` calls `builtBackendApp.start()` before listening and `builtBackendApp.shutdown()` on exit.
43
+ - `index.ts` calls `builtBackendApp.start()`, which listens and handles SIGINT/SIGTERM. Extra shutdown work (telemetry) is `onShutdown` on `createBackendApp`.
44
44
  - After changing Drizzle tables, run `pnpm --filter ./apps/server sync` — do not hand-edit SQL migrations in this repo.
45
45
 
46
46
  Push-related server env vars are documented in `apps/shared/.env.example`.
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `members` ADD `name` text DEFAULT '' NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE `members` ADD `image` text;--> statement-breakpoint
3
+ ALTER TABLE `members` ADD `deleted_at` integer;