@agentuity/cli 1.0.47 → 2.0.0-beta.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 (115) hide show
  1. package/dist/cmd/build/app-router-detector.d.ts +2 -5
  2. package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
  3. package/dist/cmd/build/app-router-detector.js +130 -154
  4. package/dist/cmd/build/app-router-detector.js.map +1 -1
  5. package/dist/cmd/build/ids.d.ts +11 -0
  6. package/dist/cmd/build/ids.d.ts.map +1 -0
  7. package/dist/cmd/build/ids.js +18 -0
  8. package/dist/cmd/build/ids.js.map +1 -0
  9. package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
  10. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/agent-discovery.js +166 -487
  12. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  13. package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
  14. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  15. package/dist/cmd/build/vite/bun-dev-server.js +67 -134
  16. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  17. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.js +0 -2
  19. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  20. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/index.js +0 -36
  22. package/dist/cmd/build/vite/index.js.map +1 -1
  23. package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
  24. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
  26. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  27. package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
  28. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  29. package/dist/cmd/build/vite/route-discovery.js +97 -177
  30. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  31. package/dist/cmd/build/vite/server-bundler.js +1 -1
  32. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  33. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/static-renderer.js +1 -9
  35. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  36. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
  37. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/vite-asset-server-config.js +171 -18
  39. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
  41. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  42. package/dist/cmd/build/vite/vite-asset-server.js +14 -13
  43. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +6 -34
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
  48. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
  49. package/dist/cmd/build/vite/ws-proxy.js +95 -0
  50. package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
  51. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  52. package/dist/cmd/build/vite-bundler.js +0 -3
  53. package/dist/cmd/build/vite-bundler.js.map +1 -1
  54. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  55. package/dist/cmd/dev/file-watcher.js +2 -8
  56. package/dist/cmd/dev/file-watcher.js.map +1 -1
  57. package/dist/cmd/dev/index.d.ts.map +1 -1
  58. package/dist/cmd/dev/index.js +369 -720
  59. package/dist/cmd/dev/index.js.map +1 -1
  60. package/package.json +6 -8
  61. package/src/cmd/ai/prompt/agent.md +0 -1
  62. package/src/cmd/ai/prompt/api.md +0 -7
  63. package/src/cmd/ai/prompt/web.md +51 -213
  64. package/src/cmd/build/app-router-detector.ts +152 -182
  65. package/src/cmd/build/ids.ts +19 -0
  66. package/src/cmd/build/vite/agent-discovery.ts +208 -679
  67. package/src/cmd/build/vite/bun-dev-server.ts +78 -154
  68. package/src/cmd/build/vite/docs-generator.ts +0 -2
  69. package/src/cmd/build/vite/index.ts +1 -42
  70. package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
  71. package/src/cmd/build/vite/route-discovery.ts +116 -274
  72. package/src/cmd/build/vite/server-bundler.ts +1 -1
  73. package/src/cmd/build/vite/static-renderer.ts +1 -11
  74. package/src/cmd/build/vite/vite-asset-server-config.ts +196 -20
  75. package/src/cmd/build/vite/vite-asset-server.ts +25 -15
  76. package/src/cmd/build/vite/vite-builder.ts +6 -51
  77. package/src/cmd/build/vite/ws-proxy.ts +126 -0
  78. package/src/cmd/build/vite-bundler.ts +0 -4
  79. package/src/cmd/dev/file-watcher.ts +2 -9
  80. package/src/cmd/dev/index.ts +409 -832
  81. package/dist/cmd/build/ast.d.ts +0 -78
  82. package/dist/cmd/build/ast.d.ts.map +0 -1
  83. package/dist/cmd/build/ast.js +0 -2703
  84. package/dist/cmd/build/ast.js.map +0 -1
  85. package/dist/cmd/build/entry-generator.d.ts +0 -25
  86. package/dist/cmd/build/entry-generator.d.ts.map +0 -1
  87. package/dist/cmd/build/entry-generator.js +0 -695
  88. package/dist/cmd/build/entry-generator.js.map +0 -1
  89. package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
  90. package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
  91. package/dist/cmd/build/vite/api-mount-path.js +0 -83
  92. package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
  93. package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
  94. package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
  95. package/dist/cmd/build/vite/registry-generator.js +0 -1108
  96. package/dist/cmd/build/vite/registry-generator.js.map +0 -1
  97. package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
  98. package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
  99. package/dist/cmd/build/webanalytics-generator.js +0 -178
  100. package/dist/cmd/build/webanalytics-generator.js.map +0 -1
  101. package/dist/cmd/build/workbench.d.ts +0 -7
  102. package/dist/cmd/build/workbench.d.ts.map +0 -1
  103. package/dist/cmd/build/workbench.js +0 -55
  104. package/dist/cmd/build/workbench.js.map +0 -1
  105. package/dist/utils/route-migration.d.ts +0 -62
  106. package/dist/utils/route-migration.d.ts.map +0 -1
  107. package/dist/utils/route-migration.js +0 -630
  108. package/dist/utils/route-migration.js.map +0 -1
  109. package/src/cmd/build/ast.ts +0 -3529
  110. package/src/cmd/build/entry-generator.ts +0 -760
  111. package/src/cmd/build/vite/api-mount-path.ts +0 -87
  112. package/src/cmd/build/vite/registry-generator.ts +0 -1267
  113. package/src/cmd/build/webanalytics-generator.ts +0 -197
  114. package/src/cmd/build/workbench.ts +0 -58
  115. package/src/utils/route-migration.ts +0 -757
@@ -5,23 +5,15 @@
5
5
  * to `createApp()`. If detected, resolves the router variable(s) to their import
6
6
  * sources and mount paths.
7
7
  *
8
- * This allows the build tooling to derive route metadata from the actual code-based
9
- * route tree instead of relying on filesystem-based discovery.
8
+ * Uses TypeScript's compiler API to reliably detect the pattern, consistent with
9
+ * the lifecycle generator approach.
10
10
  */
11
11
 
12
- import * as acornLoose from 'acorn-loose';
12
+ import ts from 'typescript';
13
13
  import { join, dirname, resolve } from 'node:path';
14
- import { existsSync, statSync } from 'node:fs';
14
+ import { statSync } from 'node:fs';
15
15
  import type { Logger } from '../../types';
16
16
 
17
- interface ASTNode {
18
- type: string;
19
- start?: number;
20
- end?: number;
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- [key: string]: any;
23
- }
24
-
25
17
  /**
26
18
  * A resolved mount point from `createApp({ router })`.
27
19
  */
@@ -46,7 +38,7 @@ export interface AppRouterDetection {
46
38
  * Resolve an import path to an actual file on disk.
47
39
  * Tries the path as-is, then with common extensions.
48
40
  */
49
- function resolveImportFile(fromDir: string, importPath: string): string | null {
41
+ async function resolveImportFile(fromDir: string, importPath: string): Promise<string | null> {
50
42
  if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
51
43
  return null; // Package import — can't resolve
52
44
  }
@@ -54,7 +46,8 @@ function resolveImportFile(fromDir: string, importPath: string): string | null {
54
46
  const basePath = resolve(fromDir, importPath);
55
47
  const extensions = ['.ts', '.tsx', '/index.ts', '/index.tsx'];
56
48
 
57
- if (existsSync(basePath)) {
49
+ const baseFile = Bun.file(basePath);
50
+ if (await baseFile.exists()) {
58
51
  try {
59
52
  if (statSync(basePath).isFile()) return basePath;
60
53
  } catch {
@@ -64,7 +57,7 @@ function resolveImportFile(fromDir: string, importPath: string): string | null {
64
57
 
65
58
  for (const ext of extensions) {
66
59
  const candidate = basePath + ext;
67
- if (existsSync(candidate)) {
60
+ if (await Bun.file(candidate).exists()) {
68
61
  return candidate;
69
62
  }
70
63
  }
@@ -73,91 +66,158 @@ function resolveImportFile(fromDir: string, importPath: string): string | null {
73
66
  }
74
67
 
75
68
  /**
76
- * Extract the `router` property value from a `createApp()` call's argument object.
77
- *
78
- * Handles three forms:
79
- * - `createApp({ router: myVar })` → plain Hono, default /api mount
80
- * - `createApp({ router: { path: '/v1', router: myVar } })` → single RouteMount
81
- * - `createApp({ router: [{ path: '/v1', router: v1 }, ...] })` → array of RouteMounts
69
+ * A router mount extracted from the AST before file resolution.
82
70
  */
83
- function extractRouterFromCreateApp(
84
- callNode: ASTNode
85
- ): Array<{ path: string; varName: string }> | null {
86
- // createApp() must have at least one argument (the config object)
87
- if (!callNode.arguments || callNode.arguments.length === 0) {
88
- return null;
89
- }
71
+ interface RawMount {
72
+ path: string;
73
+ varName: string;
74
+ }
90
75
 
91
- const configArg = callNode.arguments[0] as ASTNode;
92
- if (configArg.type !== 'ObjectExpression') {
76
+ /**
77
+ * Extract router mounts from a createApp() call using TypeScript's AST.
78
+ * Returns null if no router property found.
79
+ */
80
+ function extractRouterMounts(sourceFile: ts.SourceFile): RawMount[] | null {
81
+ let result: RawMount[] | null = null;
82
+
83
+ function getStringLiteral(node: ts.Expression): string | null {
84
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
85
+ return node.text;
86
+ }
93
87
  return null;
94
88
  }
95
89
 
96
- // Find the `router` property
97
- const routerProp = configArg.properties?.find(
98
- (p: ASTNode) =>
99
- p.type === 'Property' && p.key?.type === 'Identifier' && p.key?.name === 'router'
100
- );
90
+ function extractMountFromObject(obj: ts.ObjectLiteralExpression): RawMount | null {
91
+ let path: string | undefined;
92
+ let varName: string | undefined;
101
93
 
102
- if (!routerProp) {
103
- return null; // No router property — file-based routing
104
- }
94
+ for (const prop of obj.properties) {
95
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
96
+
97
+ if (prop.name.text === 'path') {
98
+ path = getStringLiteral(prop.initializer) ?? undefined;
99
+ }
100
+ if (prop.name.text === 'router') {
101
+ if (ts.isIdentifier(prop.initializer)) {
102
+ varName = prop.initializer.text;
103
+ }
104
+ }
105
+ }
105
106
 
106
- const routerValue = routerProp.value as ASTNode;
107
+ // Also handle shorthand: { path: '/v1', router } where router is shorthand
108
+ for (const prop of obj.properties) {
109
+ if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === 'router') {
110
+ varName = prop.name.text;
111
+ }
112
+ }
107
113
 
108
- // Form 1: Plain Hono variable createApp({ router: myRouter })
109
- if (routerValue.type === 'Identifier') {
110
- return [{ path: '/api', varName: routerValue.name }];
114
+ return path && varName ? { path, varName } : null;
111
115
  }
112
116
 
113
- // Form 2: Single RouteMount → createApp({ router: { path: '/v1', router: myRouter } })
114
- if (routerValue.type === 'ObjectExpression') {
115
- const mount = extractRouteMountFromObject(routerValue);
116
- return mount ? [mount] : null;
117
+ function processRouterValue(value: ts.Expression): RawMount[] | null {
118
+ // Form 1: Identifier → createApp({ router: myRouter })
119
+ if (ts.isIdentifier(value)) {
120
+ return [{ path: '/api', varName: value.text }];
121
+ }
122
+
123
+ // Form 2: Object → createApp({ router: { path: '/v1', router: myRouter } })
124
+ if (ts.isObjectLiteralExpression(value)) {
125
+ const mount = extractMountFromObject(value);
126
+ return mount ? [mount] : null;
127
+ }
128
+
129
+ // Form 3: Array → createApp({ router: [...] })
130
+ if (ts.isArrayLiteralExpression(value)) {
131
+ const mounts: RawMount[] = [];
132
+ for (const element of value.elements) {
133
+ if (ts.isObjectLiteralExpression(element)) {
134
+ const mount = extractMountFromObject(element);
135
+ if (mount) mounts.push(mount);
136
+ }
137
+ }
138
+ return mounts.length > 0 ? mounts : null;
139
+ }
140
+
141
+ return null;
117
142
  }
118
143
 
119
- // Form 3: Array of RouteMounts → createApp({ router: [{ path: '/v1', router: v1 }, ...] })
120
- if (routerValue.type === 'ArrayExpression') {
121
- const mounts: Array<{ path: string; varName: string }> = [];
122
- for (const element of routerValue.elements || []) {
123
- if (element?.type === 'ObjectExpression') {
124
- const mount = extractRouteMountFromObject(element);
125
- if (mount) mounts.push(mount);
144
+ function visit(node: ts.Node): void {
145
+ if (result) return;
146
+
147
+ // Find createApp(...) with or without await
148
+ let callExpr: ts.CallExpression | undefined;
149
+
150
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
151
+ if (node.expression.text === 'createApp') callExpr = node;
152
+ } else if (ts.isAwaitExpression(node) && ts.isCallExpression(node.expression)) {
153
+ const call = node.expression;
154
+ if (ts.isIdentifier(call.expression) && call.expression.text === 'createApp') {
155
+ callExpr = call;
156
+ }
157
+ }
158
+
159
+ if (callExpr && callExpr.arguments.length > 0) {
160
+ const configArg = callExpr.arguments[0];
161
+ if (configArg && ts.isObjectLiteralExpression(configArg)) {
162
+ for (const prop of configArg.properties) {
163
+ // Handle: router: value
164
+ if (
165
+ ts.isPropertyAssignment(prop) &&
166
+ ts.isIdentifier(prop.name) &&
167
+ prop.name.text === 'router'
168
+ ) {
169
+ result = processRouterValue(prop.initializer);
170
+ return;
171
+ }
172
+
173
+ // Handle shorthand: createApp({ router })
174
+ if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === 'router') {
175
+ result = [{ path: '/api', varName: 'router' }];
176
+ return;
177
+ }
178
+ }
126
179
  }
127
180
  }
128
- return mounts.length > 0 ? mounts : null;
181
+
182
+ ts.forEachChild(node, visit);
129
183
  }
130
184
 
131
- return null;
185
+ visit(sourceFile);
186
+ return result;
132
187
  }
133
188
 
134
189
  /**
135
- * Extract { path, router } from an object literal AST node.
190
+ * Build import map from the source file: variable name import path
136
191
  */
137
- function extractRouteMountFromObject(objNode: ASTNode): { path: string; varName: string } | null {
138
- let path: string | undefined;
139
- let varName: string | undefined;
192
+ function buildImportMap(sourceFile: ts.SourceFile): Map<string, string> {
193
+ const importMap = new Map<string, string>();
140
194
 
141
- for (const prop of objNode.properties || []) {
142
- if (prop.type !== 'Property' || prop.key?.type !== 'Identifier') continue;
195
+ for (const stmt of sourceFile.statements) {
196
+ if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
143
197
 
144
- if (prop.key.name === 'path' && prop.value?.type === 'Literal') {
145
- path = String(prop.value.value);
198
+ const importPath = stmt.moduleSpecifier.text;
199
+ const clause = stmt.importClause;
200
+ if (!clause) continue;
201
+
202
+ // Default import: import router from './api'
203
+ if (clause.name) {
204
+ importMap.set(clause.name.text, importPath);
146
205
  }
147
- if (prop.key.name === 'router' && prop.value?.type === 'Identifier') {
148
- varName = prop.value.name;
206
+
207
+ // Named imports: import { v1, v2 } from './routers'
208
+ if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
209
+ for (const spec of clause.namedBindings.elements) {
210
+ importMap.set(spec.name.text, importPath);
211
+ }
149
212
  }
150
213
  }
151
214
 
152
- return path && varName ? { path, varName } : null;
215
+ return importMap;
153
216
  }
154
217
 
155
218
  /**
156
219
  * Detect whether `src/app.ts` uses `createApp({ router })`.
157
220
  *
158
- * Parses the file with acorn-loose, finds `createApp()` calls,
159
- * and resolves router variables to their import source files.
160
- *
161
221
  * Returns `{ detected: false, mounts: [] }` when:
162
222
  * - `src/app.ts` doesn't exist
163
223
  * - `createApp()` is called without a `router` property
@@ -169,76 +229,57 @@ export async function detectExplicitRouter(
169
229
  ): Promise<AppRouterDetection> {
170
230
  const noDetection: AppRouterDetection = { detected: false, mounts: [] };
171
231
 
172
- // Look for app.ts in src/ (standard location)
173
- const appFile = join(rootDir, 'src', 'app.ts');
174
- if (!existsSync(appFile)) {
175
- // Also try root-level app.ts
176
- const rootAppFile = join(rootDir, 'app.ts');
177
- if (!existsSync(rootAppFile)) {
232
+ // Look for app.ts in src/ (standard location), then root
233
+ let appFile = join(rootDir, 'src', 'app.ts');
234
+ if (!(await Bun.file(appFile).exists())) {
235
+ appFile = join(rootDir, 'app.ts');
236
+ if (!(await Bun.file(appFile).exists())) {
178
237
  logger.trace('[router-detect] No app.ts found');
179
238
  return noDetection;
180
239
  }
181
- return detectInFile(rootAppFile, logger);
182
240
  }
183
241
 
184
- return detectInFile(appFile, logger);
185
- }
186
-
187
- async function detectInFile(appFile: string, logger: Logger): Promise<AppRouterDetection> {
188
- const noDetection: AppRouterDetection = { detected: false, mounts: [] };
189
- const appDir = dirname(appFile);
190
-
191
242
  try {
192
243
  const source = await Bun.file(appFile).text();
193
- const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
194
- const contents = transpiler.transformSync(source);
244
+ const appDir = dirname(appFile);
195
245
 
196
- // Quick check: skip AST parsing if createApp is not called with router
197
- if (!contents.includes('createApp') || !contents.includes('router')) {
246
+ // Quick bail-out before parsing
247
+ if (!source.includes('createApp') || !source.includes('router')) {
198
248
  logger.trace('[router-detect] No createApp + router pattern found in %s', appFile);
199
249
  return noDetection;
200
250
  }
201
251
 
202
- const ast = acornLoose.parse(contents, {
203
- locations: true,
204
- ecmaVersion: 'latest',
205
- sourceType: 'module',
206
- }) as ASTNode;
207
-
208
- // Build import map: variable name → import path
209
- const importMap = new Map<string, string>();
210
- for (const node of ast.body || []) {
211
- if (node.type === 'ImportDeclaration' && node.source?.value) {
212
- for (const spec of node.specifiers || []) {
213
- if (spec.local?.name) {
214
- importMap.set(spec.local.name, String(node.source.value));
215
- }
216
- }
217
- }
218
- }
219
-
220
- // Walk all statements looking for createApp() calls
221
- const routerMounts = findCreateAppRouterCalls(ast, importMap);
252
+ // Parse with TypeScript
253
+ const sourceFile = ts.createSourceFile(
254
+ appFile,
255
+ source,
256
+ ts.ScriptTarget.Latest,
257
+ true,
258
+ ts.ScriptKind.TS
259
+ );
222
260
 
223
- if (!routerMounts || routerMounts.length === 0) {
261
+ const rawMounts = extractRouterMounts(sourceFile);
262
+ if (!rawMounts || rawMounts.length === 0) {
224
263
  logger.trace('[router-detect] createApp() found but no router property');
225
264
  return noDetection;
226
265
  }
227
266
 
267
+ // Build import map to resolve variable names to file paths
268
+ const importMap = buildImportMap(sourceFile);
269
+
228
270
  // Resolve each router variable to its file
229
271
  const mounts: DetectedRouteMount[] = [];
230
- for (const { path, varName } of routerMounts) {
272
+ for (const { path, varName } of rawMounts) {
231
273
  const importPath = importMap.get(varName);
232
274
  if (!importPath) {
233
275
  logger.debug(
234
276
  '[router-detect] Router variable %s is not imported — may be defined locally',
235
277
  varName
236
278
  );
237
- // Could be defined in the same file — skip for now
238
279
  continue;
239
280
  }
240
281
 
241
- const resolvedFile = resolveImportFile(appDir, importPath);
282
+ const resolvedFile = await resolveImportFile(appDir, importPath);
242
283
  if (!resolvedFile) {
243
284
  logger.warn(
244
285
  '[router-detect] Could not resolve import %s for router variable %s',
@@ -277,74 +318,3 @@ async function detectInFile(appFile: string, logger: Logger): Promise<AppRouterD
277
318
  return noDetection;
278
319
  }
279
320
  }
280
-
281
- /**
282
- * Walk the AST looking for `createApp({ router: ... })` calls.
283
- * Handles:
284
- * - `createApp({ router })` (top-level expression)
285
- * - `const app = await createApp({ router })` (variable declaration)
286
- * - `export const app = await createApp({ router })` (exported)
287
- */
288
- function findCreateAppRouterCalls(
289
- ast: ASTNode,
290
- importMap: Map<string, string>
291
- ): Array<{ path: string; varName: string }> | null {
292
- for (const node of ast.body || []) {
293
- // Check expression statements: createApp({ router })
294
- if (node.type === 'ExpressionStatement') {
295
- const result = checkForCreateAppCall(node.expression, importMap);
296
- if (result) return result;
297
- }
298
-
299
- // Check variable declarations: const app = await createApp({ router })
300
- if (node.type === 'VariableDeclaration') {
301
- for (const decl of node.declarations || []) {
302
- if (decl.init) {
303
- const result = checkForCreateAppCall(decl.init, importMap);
304
- if (result) return result;
305
- }
306
- }
307
- }
308
-
309
- // Check exports: export const app = await createApp({ router })
310
- if (node.type === 'ExportNamedDeclaration' && node.declaration) {
311
- if (node.declaration.type === 'VariableDeclaration') {
312
- for (const decl of node.declaration.declarations || []) {
313
- if (decl.init) {
314
- const result = checkForCreateAppCall(decl.init, importMap);
315
- if (result) return result;
316
- }
317
- }
318
- }
319
- }
320
- }
321
-
322
- return null;
323
- }
324
-
325
- /**
326
- * Check if an expression node is a `createApp({ router })` call.
327
- * Unwraps `await` expressions.
328
- */
329
- function checkForCreateAppCall(
330
- expr: ASTNode,
331
- importMap: Map<string, string>
332
- ): Array<{ path: string; varName: string }> | null {
333
- if (!expr) return null;
334
-
335
- // Unwrap AwaitExpression: await createApp(...)
336
- if (expr.type === 'AwaitExpression' && expr.argument) {
337
- return checkForCreateAppCall(expr.argument, importMap);
338
- }
339
-
340
- // Check for createApp({ router })
341
- if (
342
- expr.type === 'CallExpression' &&
343
- expr.callee?.type === 'Identifier' &&
344
- expr.callee?.name === 'createApp'
345
- ) {
346
- return extractRouterFromCreateApp(expr);
347
- }
348
-
349
- return null;
350
- }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Build-time ID generation utilities.
3
+ *
4
+ * Pure hash functions for generating deterministic IDs.
5
+ * No AST parsing — just SHA1 hashing.
6
+ */
7
+
8
+ function hashSHA1(...val: string[]): string {
9
+ const hasher = new Bun.CryptoHasher('sha1');
10
+ val.map((val) => hasher.update(val));
11
+ return hasher.digest().toHex();
12
+ }
13
+
14
+ /**
15
+ * Generate a deterministic deployment ID for devmode.
16
+ */
17
+ export function getDevmodeDeploymentId(projectId: string, endpointId: string): string {
18
+ return `devmode_${hashSHA1(projectId, endpointId)}`;
19
+ }