@timber-js/app 0.2.0-alpha.111 → 0.2.0-alpha.113

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.
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAmBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAkF1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAyLxD"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAoBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA0F1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAmNxD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * _segment type generation and directory-to-route mapping.
3
+ *
4
+ * Generates per-route _segment.d.ts files in .timber/types/ that mirror
5
+ * the app/ directory structure. TypeScript's rootDirs feature makes these
6
+ * importable as './_segment' from route files.
7
+ *
8
+ * Design doc: design/41-global-params.md §The _segment Module
9
+ */
10
+ import type { RouteTree } from './types.js';
11
+ /**
12
+ * Build a map of absolute filesystem directory → route pattern.
13
+ * Used by resolveId to map a _segment import's importer to the correct route.
14
+ *
15
+ * Walks every segment (not just leaves) so layouts and middleware
16
+ * at any depth can import _segment.
17
+ */
18
+ export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string>;
19
+ /**
20
+ * Generate per-route _segment.d.ts files in .timber/types/ mirroring
21
+ * the app/ directory structure. These files are importable via TypeScript's
22
+ * rootDirs feature.
23
+ */
24
+ export declare function writeSegmentTypes(routeTree: RouteTree, appDir: string, root: string): Promise<void>;
25
+ //# sourceMappingURL=segment-codegen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/segment-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBvF;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAqBf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.111",
3
+ "version": "0.2.0-alpha.113",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -8,9 +8,10 @@
8
8
  */
9
9
 
10
10
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
11
- import { join, resolve } from 'node:path';
11
+ import { join } from 'node:path';
12
12
  import { scanRoutes } from './routing/scanner.js';
13
13
  import type { SegmentNode } from './routing/types.js';
14
+ import { resolveAppDir } from './plugin-context.js';
14
15
 
15
16
  /**
16
17
  * Collect all unique bracket-keyed dynamic segment names from the route tree.
@@ -151,11 +152,7 @@ export interface SchemaSyncResult {
151
152
  * Run the schema sync: scan filesystem, diff against schema, append missing.
152
153
  */
153
154
  export function runSchemaSync(projectRoot: string): SchemaSyncResult {
154
- const appDir = resolve(projectRoot, 'app');
155
-
156
- if (!existsSync(appDir)) {
157
- throw new Error(`[timber] No app/ directory found at ${appDir}`);
158
- }
155
+ const appDir = resolveAppDir(projectRoot);
159
156
 
160
157
  const filesystemSegments = collectDynamicSegments(appDir);
161
158
 
@@ -15,6 +15,7 @@ import { join } from 'node:path';
15
15
  import { scanRoutes } from '../routing/scanner.js';
16
16
  import { generateRouteMap } from '../routing/codegen.js';
17
17
  import { collectInterceptionRewrites } from '../routing/interception.js';
18
+ import { buildDirToRouteMap, writeSegmentTypes } from '../routing/segment-codegen.js';
18
19
  import {
19
20
  collectDynamicSegments,
20
21
  findSchemaFile,
@@ -55,6 +56,13 @@ const RESOLVED_SEARCH_PARAMS_REGISTRY_ID = `\0${SEARCH_PARAMS_REGISTRY_ID}`;
55
56
  const SCHEMA_ID = 'virtual:timber-schema';
56
57
  const RESOLVED_SCHEMA_ID = `\0${SCHEMA_ID}`;
57
58
 
59
+ // TIM-935: _segment virtual module prefix.
60
+ //
61
+ // Each route directory gets a virtual module that exports the route
62
+ // pattern string. `import { route } from './_segment'` resolves to
63
+ // `\0_segment:<route-pattern>` which returns `export const route = '...'`.
64
+ const SEGMENT_PREFIX = '\0_segment:';
65
+
58
66
  /**
59
67
  * File convention names we track for changes that require manifest regeneration.
60
68
  */
@@ -101,6 +109,7 @@ function writeCodegen(ctx: PluginContext): void {
101
109
  Promise.all([
102
110
  writeFile(routesPath, content, 'utf-8'),
103
111
  writeFile(envPath, TIMBER_ENV_DTS, 'utf-8'),
112
+ writeSegmentTypes(ctx.routeTree!, ctx.appDir, ctx.root),
104
113
  ])
105
114
  )
106
115
  .catch((err) => {
@@ -113,6 +122,9 @@ export function timberRouting(ctx: PluginContext): Plugin {
113
122
  /** Track warned files to avoid repeating warnings on rescan. */
114
123
  const warnedFiles = new Set<string>();
115
124
 
125
+ /** Maps absolute filesystem directory → route pattern for _segment resolution. */
126
+ let dirToRoute: Map<string, string> = new Map();
127
+
116
128
  function rescan(): void {
117
129
  // Check app/ directory exists before scanning
118
130
  const appDirWarning = checkAppDirExists(ctx.appDir);
@@ -137,6 +149,7 @@ export function timberRouting(ctx: PluginContext): Plugin {
137
149
  pageExtensions: ctx.config.pageExtensions,
138
150
  });
139
151
  ctx.timer.end('route-scan');
152
+ dirToRoute = buildDirToRouteMap(ctx.routeTree, ctx.appDir);
140
153
  writeCodegen(ctx);
141
154
 
142
155
  // Dev: warn about dynamic segments not registered in app/schema.ts.
@@ -165,7 +178,7 @@ export function timberRouting(ctx: PluginContext): Plugin {
165
178
  * - <root>/virtual:timber-route-manifest (Vite SSR build root prefix)
166
179
  * - \0virtual:timber-route-manifest (RSC plugin re-imports)
167
180
  */
168
- resolveId(id: string) {
181
+ resolveId(id: string, importer?: string) {
169
182
  // Strip \0 prefix if present (RSC plugin generates these)
170
183
  const cleanId = id.startsWith('\0') ? id.slice(1) : id;
171
184
 
@@ -191,6 +204,22 @@ export function timberRouting(ctx: PluginContext): Plugin {
191
204
  return RESOLVED_SCHEMA_ID;
192
205
  }
193
206
 
207
+ // TIM-935: _segment virtual module.
208
+ // Resolves `import { route } from './_segment'` based on the
209
+ // importing file's directory in the app/ tree.
210
+ if (
211
+ importer &&
212
+ (cleanId === './_segment' || cleanId === './_segment.js' || cleanId === './_segment.ts')
213
+ ) {
214
+ // Strip \0 prefix from importer if present
215
+ const cleanImporter = importer.startsWith('\0') ? importer.slice(1) : importer;
216
+ const importerDir = cleanImporter.substring(0, cleanImporter.lastIndexOf('/'));
217
+ const routePattern = dirToRoute.get(importerDir);
218
+ if (routePattern !== undefined) {
219
+ return `${SEGMENT_PREFIX}${routePattern}`;
220
+ }
221
+ }
222
+
194
223
  return null;
195
224
  },
196
225
 
@@ -220,6 +249,12 @@ export function timberRouting(ctx: PluginContext): Plugin {
220
249
  return generateSchemaModule(ctx.appDir);
221
250
  }
222
251
 
252
+ // TIM-935: _segment virtual module — exports the route pattern string.
253
+ if (id.startsWith(SEGMENT_PREFIX)) {
254
+ const routePattern = id.slice(SEGMENT_PREFIX.length);
255
+ return `export const route = ${JSON.stringify(routePattern)};\n`;
256
+ }
257
+
223
258
  return null;
224
259
  },
225
260
 
@@ -347,6 +382,13 @@ function invalidateManifest(server: ViteDevServer): void {
347
382
  env.moduleGraph.invalidateModule(mod);
348
383
  }
349
384
  }
385
+
386
+ // TIM-935: Invalidate all _segment virtual modules (prefixed \0_segment:)
387
+ for (const mod of env.moduleGraph.idToModuleMap.values()) {
388
+ if (mod.id?.startsWith(SEGMENT_PREFIX)) {
389
+ env.moduleGraph.invalidateModule(mod);
390
+ }
391
+ }
350
392
  }
351
393
 
352
394
  // Trigger HMR full reload — route changes are structural
@@ -0,0 +1,73 @@
1
+ /**
2
+ * _segment type generation and directory-to-route mapping.
3
+ *
4
+ * Generates per-route _segment.d.ts files in .timber/types/ that mirror
5
+ * the app/ directory structure. TypeScript's rootDirs feature makes these
6
+ * importable as './_segment' from route files.
7
+ *
8
+ * Design doc: design/41-global-params.md §The _segment Module
9
+ */
10
+
11
+ import { writeFile, mkdir } from 'node:fs/promises';
12
+ import { join } from 'node:path';
13
+ import type { RouteTree, SegmentNode } from './types.js';
14
+
15
+ /**
16
+ * Build a map of absolute filesystem directory → route pattern.
17
+ * Used by resolveId to map a _segment import's importer to the correct route.
18
+ *
19
+ * Walks every segment (not just leaves) so layouts and middleware
20
+ * at any depth can import _segment.
21
+ */
22
+ export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string> {
23
+ const map = new Map<string, string>();
24
+
25
+ function walk(node: SegmentNode, fsPath: string): void {
26
+ const routePattern = node.urlPath || '/';
27
+ map.set(fsPath, routePattern);
28
+
29
+ for (const child of node.children) {
30
+ const childDir = join(fsPath, child.segmentName);
31
+ walk(child, childDir);
32
+ }
33
+ for (const [slotName, slotNode] of Object.entries(node.slots)) {
34
+ const slotDir = join(fsPath, slotName);
35
+ walk(slotNode, slotDir);
36
+ }
37
+ }
38
+
39
+ walk(tree.root, appDir);
40
+ return map;
41
+ }
42
+
43
+ /**
44
+ * Generate per-route _segment.d.ts files in .timber/types/ mirroring
45
+ * the app/ directory structure. These files are importable via TypeScript's
46
+ * rootDirs feature.
47
+ */
48
+ export async function writeSegmentTypes(
49
+ routeTree: RouteTree,
50
+ appDir: string,
51
+ root: string
52
+ ): Promise<void> {
53
+ // Compute the relative prefix from project root to appDir.
54
+ // e.g. if appDir is /project/src/app, relative is 'src/app'
55
+ const relative = appDir.slice(root.length + 1);
56
+ const typesBase = join(root, '.timber', 'types', relative);
57
+ const map = buildDirToRouteMap(routeTree, appDir);
58
+
59
+ const writes: Promise<void>[] = [];
60
+ for (const [fsDir, routePattern] of map) {
61
+ // Mirror the app/ structure under .timber/types/
62
+ const relDir = fsDir.slice(appDir.length); // e.g. '/[artistSlug]/[year]'
63
+ const segmentDir = join(typesBase, relDir);
64
+ const segmentPath = join(segmentDir, '_segment.d.ts');
65
+ const content = `export const route: '${routePattern}';\n`;
66
+
67
+ writes.push(
68
+ mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentPath, content, 'utf-8'))
69
+ );
70
+ }
71
+
72
+ await Promise.all(writes);
73
+ }
@@ -1,137 +0,0 @@
1
- import { t as scanRoutes } from "./scanner-DmfoB7iK.js";
2
- import { join, resolve } from "node:path";
3
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
- //#region src/cli-schema-sync.ts
5
- /**
6
- * `timber schema sync` — scan filesystem for dynamic segments and append
7
- * missing entries to app/schema.ts with `codec.string` defaults.
8
- *
9
- * Only adds, never removes or modifies existing entries.
10
- *
11
- * Design doc: design/41-global-params.md §Auto-Scaffolding
12
- */
13
- /**
14
- * Collect all unique bracket-keyed dynamic segment names from the route tree.
15
- * Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.
16
- */
17
- function collectDynamicSegments(appDir) {
18
- const tree = scanRoutes(appDir);
19
- const segments = /* @__PURE__ */ new Set();
20
- walkSegments(tree.root, segments);
21
- return [...segments].sort();
22
- }
23
- function walkSegments(node, segments) {
24
- if (node.paramName) segments.add(node.segmentName);
25
- for (const child of node.children) walkSegments(child, segments);
26
- for (const slot of Object.values(node.slots)) walkSegments(slot, segments);
27
- }
28
- /**
29
- * Parse existing bracket keys from an app/schema.ts file.
30
- * Uses regex to find keys in segmentParams: { '[key]': ... } blocks.
31
- */
32
- function parseExistingSchemaKeys(content) {
33
- const keys = [];
34
- const re = /['"](\[{1,2}(?:\.{3})?\w+\]{1,2})['"]\s*:/g;
35
- let m;
36
- while ((m = re.exec(content)) !== null) keys.push(m[1]);
37
- return keys;
38
- }
39
- /** Schema file extensions to check. */
40
- var SCHEMA_EXTENSIONS = [
41
- ".ts",
42
- ".tsx",
43
- ".js",
44
- ".jsx"
45
- ];
46
- /**
47
- * Find the schema file path if it exists.
48
- */
49
- function findSchemaFile(appDir) {
50
- for (const ext of SCHEMA_EXTENSIONS) {
51
- const p = join(appDir, `schema${ext}`);
52
- if (existsSync(p)) return p;
53
- }
54
- }
55
- /**
56
- * Generate the content for a new app/schema.ts with the given segment keys.
57
- */
58
- function generateNewSchema(segments) {
59
- const lines = [];
60
- lines.push("import { defineSchema, codec } from '@timber-js/app';");
61
- lines.push("");
62
- lines.push("export default defineSchema({");
63
- lines.push(" segmentParams: {");
64
- for (const seg of segments) lines.push(` '${seg}': codec.string,`);
65
- lines.push(" },");
66
- lines.push("});");
67
- lines.push("");
68
- return lines.join("\n");
69
- }
70
- /**
71
- * Append missing segment entries to an existing schema file.
72
- * Inserts new entries before the closing `}` of segmentParams.
73
- */
74
- function appendToSchema(content, newSegments) {
75
- const spIndex = content.indexOf("segmentParams");
76
- if (spIndex === -1) throw new Error("[timber] Could not find segmentParams in schema file. Expected a defineSchema({ segmentParams: { ... } }) call.");
77
- const openBrace = content.indexOf("{", spIndex + 13);
78
- if (openBrace === -1) throw new Error("[timber] Could not find opening brace for segmentParams.");
79
- let depth = 1;
80
- let i = openBrace + 1;
81
- while (i < content.length && depth > 0) {
82
- if (content[i] === "{") depth++;
83
- else if (content[i] === "}") depth--;
84
- i++;
85
- }
86
- const closeBrace = i - 1;
87
- const existingLine = content.slice(openBrace, closeBrace).match(/^(\s+)['"]\[/m);
88
- const indent = existingLine ? existingLine[1] : " ";
89
- const insertion = newSegments.map((seg) => `${indent}'${seg}': codec.string,`).join("\n");
90
- const before = content.slice(0, closeBrace);
91
- const after = content.slice(closeBrace);
92
- return before + (before.trimEnd().endsWith(",") ? "\n" : "\n") + insertion + "\n" + after;
93
- }
94
- /**
95
- * Run the schema sync: scan filesystem, diff against schema, append missing.
96
- */
97
- function runSchemaSync(projectRoot) {
98
- const appDir = resolve(projectRoot, "app");
99
- if (!existsSync(appDir)) throw new Error(`[timber] No app/ directory found at ${appDir}`);
100
- const filesystemSegments = collectDynamicSegments(appDir);
101
- if (filesystemSegments.length === 0) return {
102
- filesystemSegments: [],
103
- existingSegments: [],
104
- addedSegments: [],
105
- created: false
106
- };
107
- const schemaPath = findSchemaFile(appDir);
108
- if (!schemaPath) {
109
- writeFileSync(join(appDir, "schema.ts"), generateNewSchema(filesystemSegments), "utf-8");
110
- return {
111
- filesystemSegments,
112
- existingSegments: [],
113
- addedSegments: filesystemSegments,
114
- created: true
115
- };
116
- }
117
- const content = readFileSync(schemaPath, "utf-8");
118
- const existingSegments = parseExistingSchemaKeys(content);
119
- const missing = filesystemSegments.filter((seg) => !existingSegments.includes(seg));
120
- if (missing.length === 0) return {
121
- filesystemSegments,
122
- existingSegments,
123
- addedSegments: [],
124
- created: false
125
- };
126
- writeFileSync(schemaPath, appendToSchema(content, missing), "utf-8");
127
- return {
128
- filesystemSegments,
129
- existingSegments,
130
- addedSegments: missing,
131
- created: false
132
- };
133
- }
134
- //#endregion
135
- export { runSchemaSync as i, findSchemaFile as n, parseExistingSchemaKeys as r, collectDynamicSegments as t };
136
-
137
- //# sourceMappingURL=cli-schema-sync-BvvuK8Rp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-schema-sync-BvvuK8Rp.js","names":[],"sources":["../../src/cli-schema-sync.ts"],"sourcesContent":["/**\n * `timber schema sync` — scan filesystem for dynamic segments and append\n * missing entries to app/schema.ts with `codec.string` defaults.\n *\n * Only adds, never removes or modifies existing entries.\n *\n * Design doc: design/41-global-params.md §Auto-Scaffolding\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { scanRoutes } from './routing/scanner.js';\nimport type { SegmentNode } from './routing/types.js';\n\n/**\n * Collect all unique bracket-keyed dynamic segment names from the route tree.\n * Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.\n */\nexport function collectDynamicSegments(appDir: string): string[] {\n const tree = scanRoutes(appDir);\n const segments = new Set<string>();\n walkSegments(tree.root, segments);\n return [...segments].sort();\n}\n\nfunction walkSegments(node: SegmentNode, segments: Set<string>): void {\n if (node.paramName) {\n // Reconstruct bracket key from segmentName (already has brackets)\n segments.add(node.segmentName);\n }\n for (const child of node.children) {\n walkSegments(child, segments);\n }\n for (const slot of Object.values(node.slots)) {\n walkSegments(slot, segments);\n }\n}\n\n/**\n * Parse existing bracket keys from an app/schema.ts file.\n * Uses regex to find keys in segmentParams: { '[key]': ... } blocks.\n */\nexport function parseExistingSchemaKeys(content: string): string[] {\n const keys: string[] = [];\n // Match quoted bracket keys like '[artistSlug]', '[...slug]', '[[...topic]]'\n const re = /['\"](\\[{1,2}(?:\\.{3})?\\w+\\]{1,2})['\"]\\s*:/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(content)) !== null) {\n keys.push(m[1]);\n }\n return keys;\n}\n\n/** Schema file extensions to check. */\nconst SCHEMA_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];\n\n/**\n * Find the schema file path if it exists.\n */\nexport function findSchemaFile(appDir: string): string | undefined {\n for (const ext of SCHEMA_EXTENSIONS) {\n const p = join(appDir, `schema${ext}`);\n if (existsSync(p)) return p;\n }\n return undefined;\n}\n\n/**\n * Generate the content for a new app/schema.ts with the given segment keys.\n */\nfunction generateNewSchema(segments: string[]): string {\n const lines: string[] = [];\n lines.push(\"import { defineSchema, codec } from '@timber-js/app';\");\n lines.push('');\n lines.push('export default defineSchema({');\n lines.push(' segmentParams: {');\n for (const seg of segments) {\n lines.push(` '${seg}': codec.string,`);\n }\n lines.push(' },');\n lines.push('});');\n lines.push('');\n return lines.join('\\n');\n}\n\n/**\n * Append missing segment entries to an existing schema file.\n * Inserts new entries before the closing `}` of segmentParams.\n */\nfunction appendToSchema(content: string, newSegments: string[]): string {\n // Find the closing brace of segmentParams object.\n // Strategy: find the last entry in segmentParams block, insert after it.\n // Look for the pattern: segmentParams: { ... }\n // We find the closing `},` or `}` that ends segmentParams.\n\n // Find \"segmentParams\" key position\n const spIndex = content.indexOf('segmentParams');\n if (spIndex === -1) {\n throw new Error(\n '[timber] Could not find segmentParams in schema file. ' +\n 'Expected a defineSchema({ segmentParams: { ... } }) call.'\n );\n }\n\n // Find the opening brace after segmentParams\n const openBrace = content.indexOf('{', spIndex + 'segmentParams'.length);\n if (openBrace === -1) {\n throw new Error('[timber] Could not find opening brace for segmentParams.');\n }\n\n // Count braces to find the matching close\n let depth = 1;\n let i = openBrace + 1;\n while (i < content.length && depth > 0) {\n if (content[i] === '{') depth++;\n else if (content[i] === '}') depth--;\n i++;\n }\n // i now points to just after the closing brace\n const closeBrace = i - 1;\n\n // Detect indentation from existing entries\n const existingLine = content.slice(openBrace, closeBrace).match(/^(\\s+)['\"]\\[/m);\n const indent = existingLine ? existingLine[1] : ' ';\n\n // Build insertion text\n const insertion = newSegments.map((seg) => `${indent}'${seg}': codec.string,`).join('\\n');\n\n // Insert before the closing brace\n const before = content.slice(0, closeBrace);\n const after = content.slice(closeBrace);\n\n // Add newline if the content before doesn't end with one\n const separator = before.trimEnd().endsWith(',') ? '\\n' : '\\n';\n\n return before + separator + insertion + '\\n' + after;\n}\n\nexport interface SchemaSyncResult {\n /** Segments found in the filesystem. */\n filesystemSegments: string[];\n /** Segments already defined in app/schema.ts. */\n existingSegments: string[];\n /** Segments that were added. */\n addedSegments: string[];\n /** Whether a new schema file was created. */\n created: boolean;\n}\n\n/**\n * Run the schema sync: scan filesystem, diff against schema, append missing.\n */\nexport function runSchemaSync(projectRoot: string): SchemaSyncResult {\n const appDir = resolve(projectRoot, 'app');\n\n if (!existsSync(appDir)) {\n throw new Error(`[timber] No app/ directory found at ${appDir}`);\n }\n\n const filesystemSegments = collectDynamicSegments(appDir);\n\n if (filesystemSegments.length === 0) {\n return {\n filesystemSegments: [],\n existingSegments: [],\n addedSegments: [],\n created: false,\n };\n }\n\n const schemaPath = findSchemaFile(appDir);\n\n if (!schemaPath) {\n // Create new schema file\n const newPath = join(appDir, 'schema.ts');\n writeFileSync(newPath, generateNewSchema(filesystemSegments), 'utf-8');\n return {\n filesystemSegments,\n existingSegments: [],\n addedSegments: filesystemSegments,\n created: true,\n };\n }\n\n // Read existing schema and find missing segments\n const content = readFileSync(schemaPath, 'utf-8');\n const existingSegments = parseExistingSchemaKeys(content);\n const missing = filesystemSegments.filter((seg) => !existingSegments.includes(seg));\n\n if (missing.length === 0) {\n return {\n filesystemSegments,\n existingSegments,\n addedSegments: [],\n created: false,\n };\n }\n\n // Append missing segments\n const updated = appendToSchema(content, missing);\n writeFileSync(schemaPath, updated, 'utf-8');\n\n return {\n filesystemSegments,\n existingSegments,\n addedSegments: missing,\n created: false,\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { i as runSchemaSync } from "./cli-schema-sync-BvvuK8Rp.js";
2
- export { runSchemaSync };