@agentuity/cli 1.0.59 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +2 -3
- package/dist/cmd/build/app-config-extractor.d.ts +27 -0
- package/dist/cmd/build/app-config-extractor.d.ts.map +1 -0
- package/dist/cmd/build/app-config-extractor.js +152 -0
- package/dist/cmd/build/app-config-extractor.js.map +1 -0
- package/dist/cmd/build/app-router-detector.d.ts +2 -5
- package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
- package/dist/cmd/build/app-router-detector.js +130 -154
- package/dist/cmd/build/app-router-detector.js.map +1 -1
- package/dist/cmd/build/ci.d.ts.map +1 -1
- package/dist/cmd/build/ci.js +5 -21
- package/dist/cmd/build/ci.js.map +1 -1
- package/dist/cmd/build/ids.d.ts +11 -0
- package/dist/cmd/build/ids.d.ts.map +1 -0
- package/dist/cmd/build/ids.js +18 -0
- package/dist/cmd/build/ids.js.map +1 -0
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +8 -0
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +166 -487
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +43 -14
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +290 -129
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/build/vite/config-loader.d.ts +15 -20
- package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
- package/dist/cmd/build/vite/config-loader.js +41 -74
- package/dist/cmd/build/vite/config-loader.js.map +1 -1
- package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/docs-generator.js +0 -2
- package/dist/cmd/build/vite/docs-generator.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +0 -36
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
- package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
- package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +97 -177
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/server-bundler.js +1 -1
- package/dist/cmd/build/vite/server-bundler.js.map +1 -1
- package/dist/cmd/build/vite/static-renderer.d.ts +0 -2
- package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
- package/dist/cmd/build/vite/static-renderer.js +19 -13
- package/dist/cmd/build/vite/static-renderer.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +175 -69
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +14 -13
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +42 -190
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
- package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
- package/dist/cmd/build/vite/ws-proxy.js +95 -0
- package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +0 -3
- package/dist/cmd/build/vite-bundler.js.map +1 -1
- package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy-fork.js +15 -36
- package/dist/cmd/cloud/deploy-fork.js.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/exec.js +28 -86
- package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
- package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/run.js +2 -9
- package/dist/cmd/cloud/sandbox/run.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +2 -2
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/coder/hub-url.d.ts.map +1 -1
- package/dist/cmd/coder/hub-url.js +1 -3
- package/dist/cmd/coder/hub-url.js.map +1 -1
- package/dist/cmd/coder/start.js +6 -6
- package/dist/cmd/coder/start.js.map +1 -1
- package/dist/cmd/coder/tui-init.d.ts +2 -2
- package/dist/cmd/coder/tui-init.js +2 -2
- package/dist/cmd/coder/tui-init.js.map +1 -1
- package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
- package/dist/cmd/dev/file-watcher.js +2 -8
- package/dist/cmd/dev/file-watcher.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +432 -752
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/process-manager.d.ts +104 -0
- package/dist/cmd/dev/process-manager.d.ts.map +1 -0
- package/dist/cmd/dev/process-manager.js +204 -0
- package/dist/cmd/dev/process-manager.js.map +1 -0
- package/dist/errors.d.ts +10 -24
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -42
- package/dist/errors.js.map +1 -1
- package/dist/schema-generator.d.ts.map +1 -1
- package/dist/schema-generator.js +12 -2
- package/dist/schema-generator.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +5 -19
- package/dist/tui.js.map +1 -1
- package/dist/utils/version-mismatch.d.ts +39 -0
- package/dist/utils/version-mismatch.d.ts.map +1 -0
- package/dist/utils/version-mismatch.js +161 -0
- package/dist/utils/version-mismatch.js.map +1 -0
- package/package.json +6 -6
- package/src/cmd/ai/prompt/agent.md +0 -1
- package/src/cmd/ai/prompt/api.md +0 -7
- package/src/cmd/ai/prompt/web.md +51 -213
- package/src/cmd/build/app-config-extractor.ts +186 -0
- package/src/cmd/build/app-router-detector.ts +152 -182
- package/src/cmd/build/ci.ts +5 -21
- package/src/cmd/build/ids.ts +19 -0
- package/src/cmd/build/index.ts +10 -0
- package/src/cmd/build/vite/agent-discovery.ts +208 -679
- package/src/cmd/build/vite/bun-dev-server.ts +383 -146
- package/src/cmd/build/vite/config-loader.ts +45 -77
- package/src/cmd/build/vite/docs-generator.ts +0 -2
- package/src/cmd/build/vite/index.ts +1 -42
- package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
- package/src/cmd/build/vite/route-discovery.ts +116 -274
- package/src/cmd/build/vite/server-bundler.ts +1 -1
- package/src/cmd/build/vite/static-renderer.ts +23 -15
- package/src/cmd/build/vite/vite-asset-server-config.ts +200 -70
- package/src/cmd/build/vite/vite-asset-server.ts +25 -15
- package/src/cmd/build/vite/vite-builder.ts +49 -220
- package/src/cmd/build/vite/ws-proxy.ts +126 -0
- package/src/cmd/build/vite-bundler.ts +0 -4
- package/src/cmd/cloud/deploy-fork.ts +16 -39
- package/src/cmd/cloud/sandbox/exec.ts +23 -130
- package/src/cmd/cloud/sandbox/run.ts +2 -9
- package/src/cmd/cloud/sandbox/snapshot/build.ts +2 -2
- package/src/cmd/coder/hub-url.ts +1 -3
- package/src/cmd/coder/start.ts +6 -6
- package/src/cmd/coder/tui-init.ts +4 -4
- package/src/cmd/dev/file-watcher.ts +2 -9
- package/src/cmd/dev/index.ts +476 -859
- package/src/cmd/dev/process-manager.ts +261 -0
- package/src/errors.ts +12 -44
- package/src/schema-generator.ts +12 -2
- package/src/tui.ts +5 -18
- package/src/utils/version-mismatch.ts +204 -0
- package/dist/cmd/build/ast.d.ts +0 -78
- package/dist/cmd/build/ast.d.ts.map +0 -1
- package/dist/cmd/build/ast.js +0 -2703
- package/dist/cmd/build/ast.js.map +0 -1
- package/dist/cmd/build/entry-generator.d.ts +0 -25
- package/dist/cmd/build/entry-generator.d.ts.map +0 -1
- package/dist/cmd/build/entry-generator.js +0 -695
- package/dist/cmd/build/entry-generator.js.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
- package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
- package/dist/cmd/build/vite/api-mount-path.js +0 -83
- package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
- package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
- package/dist/cmd/build/vite/registry-generator.js +0 -1108
- package/dist/cmd/build/vite/registry-generator.js.map +0 -1
- package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
- package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
- package/dist/cmd/build/webanalytics-generator.js +0 -178
- package/dist/cmd/build/webanalytics-generator.js.map +0 -1
- package/dist/cmd/build/workbench.d.ts +0 -7
- package/dist/cmd/build/workbench.d.ts.map +0 -1
- package/dist/cmd/build/workbench.js +0 -55
- package/dist/cmd/build/workbench.js.map +0 -1
- package/dist/utils/route-migration.d.ts +0 -62
- package/dist/utils/route-migration.d.ts.map +0 -1
- package/dist/utils/route-migration.js +0 -630
- package/dist/utils/route-migration.js.map +0 -1
- package/dist/utils/stream-capture.d.ts +0 -9
- package/dist/utils/stream-capture.d.ts.map +0 -1
- package/dist/utils/stream-capture.js +0 -34
- package/dist/utils/stream-capture.js.map +0 -1
- package/src/cmd/build/ast.ts +0 -3529
- package/src/cmd/build/entry-generator.ts +0 -760
- package/src/cmd/build/vite/api-mount-path.ts +0 -87
- package/src/cmd/build/vite/registry-generator.ts +0 -1267
- package/src/cmd/build/webanalytics-generator.ts +0 -197
- package/src/cmd/build/workbench.ts +0 -58
- package/src/utils/route-migration.ts +0 -757
- package/src/utils/stream-capture.ts +0 -39
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Config Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts analytics and workbench config from the user's createApp() call
|
|
5
|
+
* in app.ts. This is the v2 approach where createApp() is the single source
|
|
6
|
+
* of truth for runtime configuration (replacing agentuity.config.ts).
|
|
7
|
+
*
|
|
8
|
+
* Uses TypeScript's compiler API to reliably detect and extract values.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import ts from 'typescript';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import type { Logger } from '../../types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracted runtime config from createApp() call.
|
|
17
|
+
*/
|
|
18
|
+
export interface ExtractedAppConfig {
|
|
19
|
+
/** analytics option value: boolean, object, or undefined if not set */
|
|
20
|
+
analytics?: boolean | Record<string, unknown>;
|
|
21
|
+
/** workbench option value: boolean, string, object, or undefined if not set */
|
|
22
|
+
workbench?: boolean | string | Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extract analytics and workbench config from a createApp() call.
|
|
27
|
+
*
|
|
28
|
+
* Uses TypeScript AST to find `createApp({ ... })` and extract the values
|
|
29
|
+
* of the `analytics` and `workbench` properties.
|
|
30
|
+
*/
|
|
31
|
+
function extractCreateAppConfig(sourceFile: ts.SourceFile): ExtractedAppConfig {
|
|
32
|
+
const result: ExtractedAppConfig = {};
|
|
33
|
+
|
|
34
|
+
// Walk the AST looking for createApp({ ... }) calls
|
|
35
|
+
function visit(node: ts.Node): void {
|
|
36
|
+
// Check for createApp(...) call
|
|
37
|
+
if (
|
|
38
|
+
ts.isCallExpression(node) &&
|
|
39
|
+
ts.isIdentifier(node.expression) &&
|
|
40
|
+
node.expression.text === 'createApp' &&
|
|
41
|
+
node.arguments.length > 0
|
|
42
|
+
) {
|
|
43
|
+
const firstArg = node.arguments[0];
|
|
44
|
+
if (!firstArg) return;
|
|
45
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
46
|
+
// Found createApp({ ... }) — extract properties
|
|
47
|
+
for (const prop of firstArg.properties) {
|
|
48
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
49
|
+
|
|
50
|
+
const name = ts.isIdentifier(prop.name) ? prop.name.text : undefined;
|
|
51
|
+
if (!name) continue;
|
|
52
|
+
|
|
53
|
+
// prop.initializer should always exist for PropertyAssignment, but check to satisfy TS
|
|
54
|
+
if (!('initializer' in prop)) continue;
|
|
55
|
+
const initializer = (prop as { initializer: ts.Expression }).initializer;
|
|
56
|
+
|
|
57
|
+
if (name === 'analytics') {
|
|
58
|
+
const value = extractValue(initializer);
|
|
59
|
+
if (value !== undefined) {
|
|
60
|
+
if (typeof value === 'boolean') {
|
|
61
|
+
result.analytics = value;
|
|
62
|
+
} else if (typeof value === 'object') {
|
|
63
|
+
result.analytics = value as Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
// Ignore string/number for analytics
|
|
66
|
+
}
|
|
67
|
+
} else if (name === 'workbench') {
|
|
68
|
+
const value = extractValue(initializer);
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
if (typeof value === 'boolean' || typeof value === 'string') {
|
|
71
|
+
result.workbench = value;
|
|
72
|
+
} else if (typeof value === 'object') {
|
|
73
|
+
result.workbench = value as Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
// Ignore number for workbench
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ts.forEachChild(node, visit);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
visit(sourceFile);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extract a JavaScript value from a TypeScript AST expression node.
|
|
91
|
+
* Handles: boolean literals, string literals, numeric literals,
|
|
92
|
+
* object literals (as Record), and identifier references (by name).
|
|
93
|
+
*/
|
|
94
|
+
function extractValue(
|
|
95
|
+
node: ts.Node
|
|
96
|
+
): boolean | string | number | Record<string, unknown> | undefined {
|
|
97
|
+
if (ts.isLiteralExpression(node)) {
|
|
98
|
+
// String literal or numeric literal
|
|
99
|
+
if (ts.isStringLiteral(node)) {
|
|
100
|
+
return node.text;
|
|
101
|
+
}
|
|
102
|
+
if (ts.isNumericLiteral(node)) {
|
|
103
|
+
return Number(node.text);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (ts.isIdentifier(node)) {
|
|
115
|
+
// Return the identifier name (e.g., a variable reference)
|
|
116
|
+
return node.text;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
120
|
+
const obj: Record<string, unknown> = {};
|
|
121
|
+
for (const prop of node.properties) {
|
|
122
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
123
|
+
if (!ts.isIdentifier(prop.name)) continue;
|
|
124
|
+
|
|
125
|
+
const key = prop.name.text;
|
|
126
|
+
obj[key] = extractValue(prop.initializer);
|
|
127
|
+
}
|
|
128
|
+
return obj;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detect and extract analytics/workbench config from app.ts.
|
|
136
|
+
*
|
|
137
|
+
* This is the v2 approach: runtime config lives in createApp() only.
|
|
138
|
+
* The CLI reads these values at build time via AST parsing.
|
|
139
|
+
*/
|
|
140
|
+
export async function extractAppConfig(
|
|
141
|
+
rootDir: string,
|
|
142
|
+
logger: Logger
|
|
143
|
+
): Promise<ExtractedAppConfig> {
|
|
144
|
+
// Look for app.ts in root first, then src/
|
|
145
|
+
let appFile = join(rootDir, 'app.ts');
|
|
146
|
+
if (!(await Bun.file(appFile).exists())) {
|
|
147
|
+
appFile = join(rootDir, 'src', 'app.ts');
|
|
148
|
+
if (!(await Bun.file(appFile).exists())) {
|
|
149
|
+
logger.trace('[config-extract] No app.ts found');
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const source = await Bun.file(appFile).text();
|
|
156
|
+
|
|
157
|
+
// Quick bail-out before parsing
|
|
158
|
+
if (!source.includes('createApp')) {
|
|
159
|
+
logger.trace('[config-extract] No createApp call in %s', appFile);
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Parse with TypeScript
|
|
164
|
+
const sourceFile = ts.createSourceFile(
|
|
165
|
+
appFile,
|
|
166
|
+
source,
|
|
167
|
+
ts.ScriptTarget.Latest,
|
|
168
|
+
true,
|
|
169
|
+
ts.ScriptKind.TS
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const config = extractCreateAppConfig(sourceFile);
|
|
173
|
+
|
|
174
|
+
if (config.analytics !== undefined) {
|
|
175
|
+
logger.debug('[config-extract] Found analytics in createApp(): %o', config.analytics);
|
|
176
|
+
}
|
|
177
|
+
if (config.workbench !== undefined) {
|
|
178
|
+
logger.debug('[config-extract] Found workbench in createApp(): %o', config.workbench);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return config;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logger.warn('[config-extract] Failed to parse app.ts:', error);
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -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
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Uses TypeScript's compiler API to reliably detect the pattern, consistent with
|
|
9
|
+
* the lifecycle generator approach.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import ts from 'typescript';
|
|
13
13
|
import { join, dirname, resolve } from 'node:path';
|
|
14
|
-
import {
|
|
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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
if (routerValue.type === 'Identifier') {
|
|
110
|
-
return [{ path: '/api', varName: routerValue.name }];
|
|
114
|
+
return path && varName ? { path, varName } : null;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
181
|
+
|
|
182
|
+
ts.forEachChild(node, visit);
|
|
129
183
|
}
|
|
130
184
|
|
|
131
|
-
|
|
185
|
+
visit(sourceFile);
|
|
186
|
+
return result;
|
|
132
187
|
}
|
|
133
188
|
|
|
134
189
|
/**
|
|
135
|
-
*
|
|
190
|
+
* Build import map from the source file: variable name → import path
|
|
136
191
|
*/
|
|
137
|
-
function
|
|
138
|
-
|
|
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
|
|
142
|
-
if (
|
|
195
|
+
for (const stmt of sourceFile.statements) {
|
|
196
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
143
197
|
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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
|
-
|
|
174
|
-
if (!
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
194
|
-
const contents = transpiler.transformSync(source);
|
|
244
|
+
const appDir = dirname(appFile);
|
|
195
245
|
|
|
196
|
-
// Quick
|
|
197
|
-
if (!
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|