@agentuity/cli 0.0.86 → 0.0.88
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 +7 -0
- package/dist/bun-path.d.ts.map +1 -1
- package/dist/bun-path.js +1 -3
- package/dist/bun-path.js.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cmd/ai/index.d.ts.map +1 -1
- package/dist/cmd/ai/index.js +1 -0
- package/dist/cmd/ai/index.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +5 -0
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +152 -7
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/config-loader.d.ts +16 -0
- package/dist/cmd/build/config-loader.d.ts.map +1 -0
- package/dist/cmd/build/config-loader.js +165 -0
- package/dist/cmd/build/config-loader.js.map +1 -0
- package/dist/cmd/build/patch/_util.js +6 -6
- package/dist/cmd/build/patch/_util.js.map +1 -1
- package/dist/cmd/build/patch/llm.js +1 -1
- package/dist/cmd/build/patch/llm.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +36 -15
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/build/route-discovery.d.ts +8 -4
- package/dist/cmd/build/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/route-discovery.js +10 -5
- package/dist/cmd/build/route-discovery.js.map +1 -1
- package/dist/cmd/build/workbench.d.ts +1 -0
- package/dist/cmd/build/workbench.d.ts.map +1 -1
- package/dist/cmd/build/workbench.js +8 -1
- package/dist/cmd/build/workbench.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/redis/get.d.ts +2 -0
- package/dist/cmd/cloud/redis/get.d.ts.map +1 -0
- package/dist/cmd/cloud/redis/get.js +62 -0
- package/dist/cmd/cloud/redis/get.js.map +1 -0
- package/dist/cmd/cloud/redis/index.d.ts +2 -0
- package/dist/cmd/cloud/redis/index.d.ts.map +1 -0
- package/dist/cmd/cloud/redis/index.js +13 -0
- package/dist/cmd/cloud/redis/index.js.map +1 -0
- package/dist/cmd/cloud/scp/download.js +3 -3
- package/dist/cmd/cloud/scp/download.js.map +1 -1
- package/dist/cmd/cloud/scp/upload.js +3 -3
- package/dist/cmd/cloud/scp/upload.js.map +1 -1
- package/dist/cmd/cloud/ssh.js +3 -3
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +5 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +7 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/profile/create.d.ts.map +1 -1
- package/dist/cmd/profile/create.js +1 -0
- package/dist/cmd/profile/create.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts +20 -0
- package/dist/cmd/upgrade/index.d.ts.map +1 -0
- package/dist/cmd/upgrade/index.js +307 -0
- package/dist/cmd/upgrade/index.js.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/cmd/version/index.js +1 -0
- package/dist/cmd/version/index.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -94
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tui.d.ts +5 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +23 -0
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/dependency-checker.d.ts +20 -0
- package/dist/utils/dependency-checker.d.ts.map +1 -0
- package/dist/utils/dependency-checker.js +161 -0
- package/dist/utils/dependency-checker.js.map +1 -0
- package/dist/version-check.d.ts +13 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +177 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +6 -4
- package/src/bun-path.ts +1 -3
- package/src/cli.ts +3 -3
- package/src/cmd/ai/index.ts +1 -0
- package/src/cmd/build/ast.ts +7 -0
- package/src/cmd/build/bundler.ts +181 -8
- package/src/cmd/build/config-loader.ts +200 -0
- package/src/cmd/build/patch/_util.ts +6 -6
- package/src/cmd/build/patch/llm.ts +1 -1
- package/src/cmd/build/plugin.ts +40 -17
- package/src/cmd/build/route-discovery.ts +10 -5
- package/src/cmd/build/workbench.ts +9 -1
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/cloud/redis/get.ts +72 -0
- package/src/cmd/cloud/redis/index.ts +13 -0
- package/src/cmd/cloud/scp/download.ts +3 -3
- package/src/cmd/cloud/scp/upload.ts +3 -3
- package/src/cmd/cloud/ssh.ts +3 -3
- package/src/cmd/dev/index.ts +11 -0
- package/src/cmd/index.ts +8 -0
- package/src/cmd/profile/create.ts +1 -0
- package/src/cmd/project/download.ts +1 -1
- package/src/cmd/upgrade/index.ts +365 -0
- package/src/cmd/version/index.ts +1 -0
- package/src/config.ts +12 -121
- package/src/git-helper.ts +4 -4
- package/src/index.ts +4 -0
- package/src/tui.ts +27 -0
- package/src/types.ts +80 -0
- package/src/utils/dependency-checker.ts +207 -0
- package/src/version-check.ts +234 -0
package/src/cmd/build/bundler.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $, semver } from 'bun';
|
|
2
2
|
import { join, relative, resolve, dirname, basename } from 'node:path';
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, rmSync, readdirSync, readFileSync } from 'node:fs';
|
|
4
4
|
import gitParseUrl from 'git-url-parse';
|
|
5
5
|
import { StructuredError } from '@agentuity/core';
|
|
6
6
|
import * as tui from '../../tui';
|
|
@@ -10,10 +10,12 @@ import { getFilesRecursively } from './file';
|
|
|
10
10
|
import { getVersion } from '../../version';
|
|
11
11
|
import type { Project } from '../../types';
|
|
12
12
|
import { fixDuplicateExportsInDirectory } from './fix-duplicate-exports';
|
|
13
|
-
import type { Logger } from '../../types';
|
|
13
|
+
import type { Logger, BuildContext } from '../../types';
|
|
14
14
|
import { generateWorkbenchMainTsx, generateWorkbenchIndexHtml } from './workbench';
|
|
15
15
|
import { analyzeWorkbench, type WorkbenchAnalysis } from './ast';
|
|
16
16
|
import { type DeployOptions } from '../../schemas/deploy';
|
|
17
|
+
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
18
|
+
import { loadBuildConfig, executeBuildConfig, mergeBuildConfig } from './config-loader';
|
|
17
19
|
|
|
18
20
|
const minBunVersion = '>=1.3.3';
|
|
19
21
|
|
|
@@ -78,6 +80,77 @@ const InvalidBunVersion = StructuredError('InvalidBunVersion')<{
|
|
|
78
80
|
required: string;
|
|
79
81
|
}>();
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Finds the workspace root by walking up the directory tree looking for a package.json with workspaces
|
|
85
|
+
*/
|
|
86
|
+
function findWorkspaceRoot(startDir: string): string | null {
|
|
87
|
+
let currentDir = startDir;
|
|
88
|
+
while (true) {
|
|
89
|
+
const pkgPath = join(currentDir, 'package.json');
|
|
90
|
+
if (existsSync(pkgPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
93
|
+
if (pkg.workspaces) {
|
|
94
|
+
return currentDir;
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// Ignore parse errors, continue searching
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const parent = resolve(currentDir, '..');
|
|
101
|
+
if (parent === currentDir) break; // reached filesystem root
|
|
102
|
+
currentDir = parent;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Finds a package by searching multiple locations:
|
|
109
|
+
* 1. App-level node_modules
|
|
110
|
+
* 2. Workspace root node_modules
|
|
111
|
+
* 3. Workspace packages directory (for workspace packages)
|
|
112
|
+
*
|
|
113
|
+
* @param rootDir - Root directory of the project
|
|
114
|
+
* @param packageName - Package name (e.g., '@agentuity/workbench')
|
|
115
|
+
* @param logger - Optional logger for debug messages
|
|
116
|
+
* @returns Path to the package directory, or null if not found
|
|
117
|
+
*/
|
|
118
|
+
function findPackagePath(rootDir: string, packageName: string, logger?: Logger): string | null {
|
|
119
|
+
const [scope, name] = packageName.startsWith('@')
|
|
120
|
+
? packageName.slice(1).split('/')
|
|
121
|
+
: [null, packageName];
|
|
122
|
+
|
|
123
|
+
// 1. Try app-level node_modules
|
|
124
|
+
const appLevelPath = scope
|
|
125
|
+
? join(rootDir, 'node_modules', `@${scope}`, name)
|
|
126
|
+
: join(rootDir, 'node_modules', name);
|
|
127
|
+
if (existsSync(appLevelPath)) {
|
|
128
|
+
logger?.debug(`Found ${packageName} at app level: ${appLevelPath}`);
|
|
129
|
+
return appLevelPath;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 2. Try workspace root node_modules
|
|
133
|
+
const workspaceRoot = findWorkspaceRoot(rootDir);
|
|
134
|
+
if (workspaceRoot) {
|
|
135
|
+
const rootLevelPath = scope
|
|
136
|
+
? join(workspaceRoot, 'node_modules', `@${scope}`, name)
|
|
137
|
+
: join(workspaceRoot, 'node_modules', name);
|
|
138
|
+
if (existsSync(rootLevelPath)) {
|
|
139
|
+
logger?.debug(`Found ${packageName} at workspace root: ${rootLevelPath}`);
|
|
140
|
+
return rootLevelPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3. Try workspace packages directory
|
|
144
|
+
const workspacePackagePath = join(workspaceRoot, 'packages', name);
|
|
145
|
+
if (existsSync(workspacePackagePath)) {
|
|
146
|
+
logger?.debug(`Found ${packageName} in workspace packages: ${workspacePackagePath}`);
|
|
147
|
+
return workspacePackagePath;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
81
154
|
const handleBuildFailure = (buildResult: BuildResult) => {
|
|
82
155
|
// Collect all build errors with full details
|
|
83
156
|
const errorMessages = buildResult.logs
|
|
@@ -130,6 +203,14 @@ export async function bundle({
|
|
|
130
203
|
const versionOutput = await checkBunVersion();
|
|
131
204
|
output.push(...versionOutput);
|
|
132
205
|
|
|
206
|
+
// Check and upgrade @agentuity/* dependencies if needed
|
|
207
|
+
const upgradeResult = await checkAndUpgradeDependencies(rootDir, logger);
|
|
208
|
+
if (upgradeResult.failed.length > 0 && process.stdin.isTTY) {
|
|
209
|
+
throw new BuildFailedError({
|
|
210
|
+
message: `Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
133
214
|
const outDir = customOutDir ?? join(rootDir, '.agentuity');
|
|
134
215
|
const srcDir = join(rootDir, 'src');
|
|
135
216
|
|
|
@@ -293,8 +374,23 @@ export async function bundle({
|
|
|
293
374
|
const tsconfigPath = join(rootDir, 'tsconfig.json');
|
|
294
375
|
const hasTsconfig = existsSync(tsconfigPath);
|
|
295
376
|
|
|
377
|
+
// Load user build config (if it exists)
|
|
378
|
+
const buildConfigFunction = await loadBuildConfig(rootDir);
|
|
379
|
+
|
|
380
|
+
// Helper to create build context for config function
|
|
381
|
+
const createBuildContext = (): BuildContext => ({
|
|
382
|
+
rootDir,
|
|
383
|
+
dev,
|
|
384
|
+
outDir,
|
|
385
|
+
srcDir,
|
|
386
|
+
orgId,
|
|
387
|
+
projectId,
|
|
388
|
+
region,
|
|
389
|
+
logger,
|
|
390
|
+
});
|
|
391
|
+
|
|
296
392
|
await (async () => {
|
|
297
|
-
const
|
|
393
|
+
const baseConfig: Bun.BuildConfig = {
|
|
298
394
|
entrypoints: appEntrypoints,
|
|
299
395
|
root: rootDir,
|
|
300
396
|
outdir: outDir,
|
|
@@ -321,7 +417,19 @@ export async function bundle({
|
|
|
321
417
|
},
|
|
322
418
|
tsconfig: hasTsconfig ? tsconfigPath : undefined,
|
|
323
419
|
};
|
|
324
|
-
|
|
420
|
+
|
|
421
|
+
// Apply user config for 'api' phase
|
|
422
|
+
let finalConfig = baseConfig;
|
|
423
|
+
if (buildConfigFunction) {
|
|
424
|
+
const userConfig = await executeBuildConfig(
|
|
425
|
+
buildConfigFunction,
|
|
426
|
+
'api',
|
|
427
|
+
createBuildContext()
|
|
428
|
+
);
|
|
429
|
+
finalConfig = mergeBuildConfig(baseConfig, userConfig);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const buildResult = await Bun.build(finalConfig);
|
|
325
433
|
if (!buildResult.success) {
|
|
326
434
|
handleBuildFailure(buildResult);
|
|
327
435
|
}
|
|
@@ -376,6 +484,9 @@ export async function bundle({
|
|
|
376
484
|
}
|
|
377
485
|
}
|
|
378
486
|
|
|
487
|
+
// must always set for the template always
|
|
488
|
+
define['process.env.AGENTUITY_PUBLIC_WORKBENCH_PATH'] = JSON.stringify('');
|
|
489
|
+
|
|
379
490
|
// Analyze workbench config early to set environment variables for web build
|
|
380
491
|
if (existsSync(appFile)) {
|
|
381
492
|
if (!workbench) {
|
|
@@ -426,7 +537,7 @@ export async function bundle({
|
|
|
426
537
|
mkdirSync(join(webOutDir, 'asset'), { recursive: true });
|
|
427
538
|
const isLocalRegion = region === 'local' || region === 'l';
|
|
428
539
|
|
|
429
|
-
const
|
|
540
|
+
const baseConfig: Bun.BuildConfig = {
|
|
430
541
|
entrypoints: webEntrypoints,
|
|
431
542
|
root: webDir,
|
|
432
543
|
outdir: webOutDir,
|
|
@@ -455,7 +566,19 @@ export async function bundle({
|
|
|
455
566
|
},
|
|
456
567
|
tsconfig: hasTsconfig ? tsconfigPath : undefined,
|
|
457
568
|
};
|
|
458
|
-
|
|
569
|
+
|
|
570
|
+
// Apply user config for 'web' phase
|
|
571
|
+
let finalConfig = baseConfig;
|
|
572
|
+
if (buildConfigFunction) {
|
|
573
|
+
const userConfig = await executeBuildConfig(
|
|
574
|
+
buildConfigFunction,
|
|
575
|
+
'web',
|
|
576
|
+
createBuildContext()
|
|
577
|
+
);
|
|
578
|
+
finalConfig = mergeBuildConfig(baseConfig, userConfig);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const result = await Bun.build(finalConfig);
|
|
459
582
|
if (result.success) {
|
|
460
583
|
// Fix duplicate exports caused by Bun splitting bug
|
|
461
584
|
// See: https://github.com/oven-sh/bun/issues/5344
|
|
@@ -495,12 +618,51 @@ export async function bundle({
|
|
|
495
618
|
|
|
496
619
|
// Generate files using templates
|
|
497
620
|
await Bun.write(join(tempWorkbenchDir, 'main.tsx'), generateWorkbenchMainTsx(config));
|
|
621
|
+
|
|
622
|
+
// Copy the pre-built standalone.css from workbench package instead of generating it
|
|
623
|
+
// This ensures we use the built version with Tailwind already processed
|
|
624
|
+
const workbenchPackagePath = findPackagePath(rootDir, '@agentuity/workbench', logger);
|
|
625
|
+
const workbenchStylesOut = join(tempWorkbenchDir, 'styles.css');
|
|
626
|
+
|
|
627
|
+
if (workbenchPackagePath) {
|
|
628
|
+
const workbenchStylesPath = join(workbenchPackagePath, 'dist', 'standalone.css');
|
|
629
|
+
const workbenchStylesSourcePath = join(workbenchPackagePath, 'src', 'standalone.css');
|
|
630
|
+
|
|
631
|
+
if (existsSync(workbenchStylesPath)) {
|
|
632
|
+
cpSync(workbenchStylesPath, workbenchStylesOut);
|
|
633
|
+
logger.debug('Copied workbench dist/standalone.css to temp workbench dir');
|
|
634
|
+
} else if (existsSync(workbenchStylesSourcePath)) {
|
|
635
|
+
// Fallback: copy source CSS file (contains Tailwind directives that need processing)
|
|
636
|
+
cpSync(workbenchStylesSourcePath, workbenchStylesOut);
|
|
637
|
+
logger.warn(
|
|
638
|
+
'Workbench dist/standalone.css not found, using source CSS. Ensure @agentuity/workbench is built.'
|
|
639
|
+
);
|
|
640
|
+
} else {
|
|
641
|
+
throw new BuildFailedError({
|
|
642
|
+
message: `Workbench styles not found in ${workbenchPackagePath}. Expected either:
|
|
643
|
+
- ${workbenchStylesPath}
|
|
644
|
+
- ${workbenchStylesSourcePath}
|
|
645
|
+
|
|
646
|
+
Make sure @agentuity/workbench is built.`,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
throw new BuildFailedError({
|
|
651
|
+
message: `Workbench package not found. Searched in:
|
|
652
|
+
- App-level node_modules
|
|
653
|
+
- Workspace root node_modules
|
|
654
|
+
- Workspace packages directory
|
|
655
|
+
|
|
656
|
+
Make sure @agentuity/workbench is installed or available in the workspace.`,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
498
660
|
const workbenchIndexFile = join(tempWorkbenchDir, 'index.html');
|
|
499
661
|
await Bun.write(workbenchIndexFile, generateWorkbenchIndexHtml());
|
|
500
662
|
|
|
501
663
|
// Bundle workbench using generated files
|
|
502
664
|
// Disable splitting to avoid CommonJS/ESM module resolution conflicts
|
|
503
|
-
const
|
|
665
|
+
const workbenchBaseConfig: Bun.BuildConfig = {
|
|
504
666
|
entrypoints: [workbenchIndexFile],
|
|
505
667
|
outdir: join(outDir, 'workbench'),
|
|
506
668
|
sourcemap: dev ? 'inline' : 'linked',
|
|
@@ -519,7 +681,18 @@ export async function bundle({
|
|
|
519
681
|
},
|
|
520
682
|
};
|
|
521
683
|
|
|
522
|
-
|
|
684
|
+
// Apply user config for 'workbench' phase
|
|
685
|
+
let finalWorkbenchConfig = workbenchBaseConfig;
|
|
686
|
+
if (buildConfigFunction) {
|
|
687
|
+
const userConfig = await executeBuildConfig(
|
|
688
|
+
buildConfigFunction,
|
|
689
|
+
'workbench',
|
|
690
|
+
createBuildContext()
|
|
691
|
+
);
|
|
692
|
+
finalWorkbenchConfig = mergeBuildConfig(workbenchBaseConfig, userConfig);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const workbenchResult = await Bun.build(finalWorkbenchConfig);
|
|
523
696
|
if (workbenchResult.success) {
|
|
524
697
|
logger.debug('Workbench bundled successfully');
|
|
525
698
|
// Clean up temp directory
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { StructuredError } from '@agentuity/core';
|
|
3
|
+
import type { BuildConfigFunction, BuildPhase, BuildContext, BuildConfig } from '../../types';
|
|
4
|
+
|
|
5
|
+
const BuildConfigLoadError = StructuredError('BuildConfigLoadError');
|
|
6
|
+
const BuildConfigValidationError = StructuredError('BuildConfigValidationError');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reserved define keys that cannot be overridden by user config
|
|
10
|
+
*/
|
|
11
|
+
const RESERVED_DEFINE_PREFIXES = ['process.env.AGENTUITY_', 'process.env.NODE_ENV'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load and validate agentuity.config.ts from the project root
|
|
15
|
+
*/
|
|
16
|
+
export async function loadBuildConfig(rootDir: string): Promise<BuildConfigFunction | null> {
|
|
17
|
+
const configPath = join(rootDir, 'agentuity.config.ts');
|
|
18
|
+
|
|
19
|
+
// Check if config file exists
|
|
20
|
+
const configFile = Bun.file(configPath);
|
|
21
|
+
if (!(await configFile.exists())) {
|
|
22
|
+
return null; // No config file is OK - it's optional
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Import the config file (Bun handles TypeScript natively)
|
|
27
|
+
// Use file:// URL to ensure absolute path resolution
|
|
28
|
+
const configModule = await import(`file://${configPath}`);
|
|
29
|
+
|
|
30
|
+
// Get the default export
|
|
31
|
+
const configFunction = configModule.default;
|
|
32
|
+
|
|
33
|
+
// Validate it's a function
|
|
34
|
+
if (typeof configFunction !== 'function') {
|
|
35
|
+
throw new BuildConfigValidationError({
|
|
36
|
+
message: `agentuity.config.ts must export a default function, got ${typeof configFunction}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return configFunction as BuildConfigFunction;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
// If it's already our error, re-throw
|
|
43
|
+
if (error instanceof BuildConfigValidationError || error instanceof BuildConfigLoadError) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Wrap other errors
|
|
48
|
+
throw new BuildConfigLoadError({
|
|
49
|
+
message: `Failed to load agentuity.config.ts: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
cause: error instanceof Error ? error : undefined,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute the build config function for a specific phase and validate the result
|
|
57
|
+
*/
|
|
58
|
+
export async function executeBuildConfig(
|
|
59
|
+
configFunction: BuildConfigFunction,
|
|
60
|
+
phase: BuildPhase,
|
|
61
|
+
context: BuildContext
|
|
62
|
+
): Promise<BuildConfig> {
|
|
63
|
+
try {
|
|
64
|
+
// Execute the config function (may be async)
|
|
65
|
+
const config = await configFunction(phase, context);
|
|
66
|
+
|
|
67
|
+
// Validate the result is an object
|
|
68
|
+
if (!config || typeof config !== 'object') {
|
|
69
|
+
throw new BuildConfigValidationError({
|
|
70
|
+
message: `Build config for phase "${phase}" must return an object, got ${typeof config}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate plugins array if provided
|
|
75
|
+
if (config.plugins !== undefined) {
|
|
76
|
+
if (!Array.isArray(config.plugins)) {
|
|
77
|
+
throw new BuildConfigValidationError({
|
|
78
|
+
message: `Build config plugins for phase "${phase}" must be an array, got ${typeof config.plugins}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Validate each plugin has a name property (basic BunPlugin check)
|
|
82
|
+
for (const plugin of config.plugins) {
|
|
83
|
+
if (!plugin || typeof plugin !== 'object' || !('name' in plugin)) {
|
|
84
|
+
throw new BuildConfigValidationError({
|
|
85
|
+
message: `Invalid plugin in phase "${phase}": plugins must be BunPlugin objects with a name property`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate external array if provided
|
|
92
|
+
if (config.external !== undefined) {
|
|
93
|
+
if (!Array.isArray(config.external)) {
|
|
94
|
+
throw new BuildConfigValidationError({
|
|
95
|
+
message: `Build config external for phase "${phase}" must be an array, got ${typeof config.external}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Validate each external is a string
|
|
99
|
+
for (const ext of config.external) {
|
|
100
|
+
if (typeof ext !== 'string') {
|
|
101
|
+
throw new BuildConfigValidationError({
|
|
102
|
+
message: `Invalid external in phase "${phase}": all externals must be strings, got ${typeof ext}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate and filter define object if provided
|
|
109
|
+
if (config.define !== undefined) {
|
|
110
|
+
if (typeof config.define !== 'object' || config.define === null) {
|
|
111
|
+
throw new BuildConfigValidationError({
|
|
112
|
+
message: `Build config define for phase "${phase}" must be an object, got ${typeof config.define}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for reserved keys and filter them out
|
|
117
|
+
const filteredDefine: Record<string, string> = {};
|
|
118
|
+
const blockedKeys: string[] = [];
|
|
119
|
+
|
|
120
|
+
for (const [key, value] of Object.entries(config.define)) {
|
|
121
|
+
// Check if this key starts with any reserved prefix
|
|
122
|
+
const isReserved = RESERVED_DEFINE_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
123
|
+
|
|
124
|
+
if (isReserved) {
|
|
125
|
+
blockedKeys.push(key);
|
|
126
|
+
continue; // Skip reserved keys
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate value is a string
|
|
130
|
+
if (typeof value !== 'string') {
|
|
131
|
+
throw new BuildConfigValidationError({
|
|
132
|
+
message: `Build config define values for phase "${phase}" must be strings, got ${typeof value} for key "${key}"`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
filteredDefine[key] = value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Warn if we blocked any keys
|
|
140
|
+
if (blockedKeys.length > 0) {
|
|
141
|
+
context.logger.warn(
|
|
142
|
+
`Build config for phase "${phase}" attempted to override reserved define keys (ignored): ${blockedKeys.join(', ')}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Replace with filtered version
|
|
147
|
+
config.define = filteredDefine;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return config;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// If it's already our error, re-throw
|
|
153
|
+
if (error instanceof BuildConfigValidationError) {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Wrap other errors
|
|
158
|
+
throw new BuildConfigLoadError({
|
|
159
|
+
message: `Failed to execute build config for phase "${phase}": ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
+
cause: error instanceof Error ? error : undefined,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Merge user build config with base Bun.BuildConfig
|
|
167
|
+
* User config is applied AFTER base config with safeguards
|
|
168
|
+
* Returns a complete Bun.BuildConfig with user overrides applied
|
|
169
|
+
*/
|
|
170
|
+
export function mergeBuildConfig(
|
|
171
|
+
baseConfig: import('bun').BuildConfig,
|
|
172
|
+
userConfig: BuildConfig
|
|
173
|
+
): import('bun').BuildConfig {
|
|
174
|
+
const merged = { ...baseConfig };
|
|
175
|
+
|
|
176
|
+
// Merge plugins (user plugins come AFTER Agentuity plugin)
|
|
177
|
+
if (userConfig.plugins && userConfig.plugins.length > 0) {
|
|
178
|
+
merged.plugins = [...(baseConfig.plugins ?? []), ...userConfig.plugins];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Merge external (combine arrays, dedupe)
|
|
182
|
+
if (userConfig.external && userConfig.external.length > 0) {
|
|
183
|
+
const existingExternal = Array.isArray(baseConfig.external)
|
|
184
|
+
? baseConfig.external
|
|
185
|
+
: baseConfig.external
|
|
186
|
+
? [baseConfig.external]
|
|
187
|
+
: [];
|
|
188
|
+
merged.external = [...new Set([...existingExternal, ...userConfig.external])];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Merge define (user defines come last, but reserved keys already filtered)
|
|
192
|
+
if (userConfig.define && Object.keys(userConfig.define).length > 0) {
|
|
193
|
+
merged.define = {
|
|
194
|
+
...baseConfig.define,
|
|
195
|
+
...userConfig.define,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return merged;
|
|
200
|
+
}
|
|
@@ -44,13 +44,13 @@ export function generateGatewayEnvGuard(
|
|
|
44
44
|
provider: string
|
|
45
45
|
): string {
|
|
46
46
|
return `{
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
process.env.${apikey} =
|
|
51
|
-
process.env.${apibase} =
|
|
47
|
+
const _agentuity_sdk_key = process.env.AGENTUITY_SDK_KEY;
|
|
48
|
+
const _agentuity_url = process.env.AGENTUITY_AIGATEWAY_URL || process.env.AGENTUITY_TRANSPORT_URL || (_agentuity_sdk_key ? 'https://agentuity.ai' : '');
|
|
49
|
+
if (_agentuity_url && _agentuity_sdk_key) {
|
|
50
|
+
process.env.${apikey} = _agentuity_sdk_key;
|
|
51
|
+
process.env.${apibase} = _agentuity_url + '/gateway/${provider}';
|
|
52
52
|
console.debug('Enabled Agentuity AI Gateway for ${provider}');
|
|
53
|
-
} else {
|
|
53
|
+
} else if (!process.env.${apikey}) {
|
|
54
54
|
${generateEnvWarning(apikey)}
|
|
55
55
|
}
|
|
56
56
|
}
|
package/src/cmd/build/plugin.ts
CHANGED
|
@@ -336,8 +336,11 @@ const AgentuityBundler: BunPlugin = {
|
|
|
336
336
|
// Setup workbench configuration - evaluate fresh each time during builds
|
|
337
337
|
const workbenchConfig = await setupWorkbench(srcDir);
|
|
338
338
|
|
|
339
|
+
// Store web routes for later (must be registered AFTER API routes)
|
|
340
|
+
let webRoutesInsert: string | null = null;
|
|
341
|
+
|
|
339
342
|
if (existsSync(indexFile)) {
|
|
340
|
-
|
|
343
|
+
webRoutesInsert = `import { serveStatic } from 'hono/bun';
|
|
341
344
|
import { getRouter, registerDevModeRoutes } from '@agentuity/runtime';
|
|
342
345
|
import { readFileSync, existsSync } from 'node:fs';
|
|
343
346
|
|
|
@@ -375,6 +378,8 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
375
378
|
index += html;
|
|
376
379
|
}
|
|
377
380
|
}
|
|
381
|
+
// make paths absolute
|
|
382
|
+
index = index.replaceAll('./web/', '/web/');
|
|
378
383
|
const webstatic = serveStatic({ root: import.meta.dir + '/web' });
|
|
379
384
|
// In dev mode, serve from source; in prod, serve from build output
|
|
380
385
|
const publicRoot = ${isDevMode} ? ${JSON.stringify(join(srcDir, 'web', 'public'))} : import.meta.dir + '/web/public';
|
|
@@ -385,22 +390,22 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
385
390
|
// Serve public assets at root (e.g., /favicon.ico) - must be last
|
|
386
391
|
router.get('/*', async (c, next) => {
|
|
387
392
|
const path = c.req.path;
|
|
393
|
+
// Skip API routes - let them 404 naturally
|
|
394
|
+
if (path === '/api' || path.startsWith('/api/')) {
|
|
395
|
+
return c.notFound();
|
|
396
|
+
}
|
|
388
397
|
// Prevent directory traversal attacks
|
|
389
398
|
if (path.includes('..') || path.includes('%2e%2e')) {
|
|
390
399
|
return c.notFound();
|
|
391
400
|
}
|
|
392
|
-
//
|
|
393
|
-
if (path
|
|
394
|
-
|
|
395
|
-
// serveStatic calls next() internally if file not found
|
|
396
|
-
return await publicstatic(c, next);
|
|
397
|
-
} catch (err) {
|
|
398
|
-
return next();
|
|
399
|
-
}
|
|
401
|
+
// Don't catch workbench routes (already handled above)
|
|
402
|
+
if (path.startsWith('/workbench')) {
|
|
403
|
+
return c.notFound();
|
|
400
404
|
}
|
|
401
|
-
|
|
405
|
+
// serve default for any path not explicitly matched
|
|
406
|
+
return c.html(index);
|
|
402
407
|
});
|
|
403
|
-
})()
|
|
408
|
+
})();`;
|
|
404
409
|
}
|
|
405
410
|
|
|
406
411
|
// Build agentInfo from all discovered agents and track directories
|
|
@@ -495,6 +500,14 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
495
500
|
}
|
|
496
501
|
|
|
497
502
|
for (const apiFile of apiFiles) {
|
|
503
|
+
// Quick check: skip files that don't contain createRouter or Hono
|
|
504
|
+
// This avoids expensive AST parsing for utility files
|
|
505
|
+
const fileContent = await Bun.file(apiFile).text();
|
|
506
|
+
if (!fileContent.includes('createRouter') && !fileContent.includes('new Hono')) {
|
|
507
|
+
logger.trace(`Skipping ${apiFile}: no createRouter or Hono found`);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
498
511
|
try {
|
|
499
512
|
const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
|
|
500
513
|
|
|
@@ -574,12 +587,17 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
574
587
|
});
|
|
575
588
|
}
|
|
576
589
|
} catch (error) {
|
|
577
|
-
// Skip files that don't have
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
590
|
+
// Skip files that don't have proper router setup despite containing createRouter/Hono
|
|
591
|
+
// (e.g., files that import but don't use them, or have syntax errors)
|
|
592
|
+
if (error instanceof Error) {
|
|
593
|
+
if (
|
|
594
|
+
error.message.includes('could not find default export') ||
|
|
595
|
+
error.message.includes('could not find an proper createRouter')
|
|
596
|
+
) {
|
|
597
|
+
logger.trace(`Skipping ${apiFile}: ${error.message}`);
|
|
598
|
+
} else {
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
583
601
|
} else {
|
|
584
602
|
throw error;
|
|
585
603
|
}
|
|
@@ -619,6 +637,11 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
619
637
|
})();`);
|
|
620
638
|
}
|
|
621
639
|
|
|
640
|
+
// Add web routes AFTER API routes (catch-all must be last)
|
|
641
|
+
if (webRoutesInsert) {
|
|
642
|
+
inserts.push(webRoutesInsert);
|
|
643
|
+
}
|
|
644
|
+
|
|
622
645
|
const file = Bun.file(args.path);
|
|
623
646
|
let contents = await file.text();
|
|
624
647
|
// Use AST-based parsing to reliably find createApp statement end
|
|
@@ -19,17 +19,21 @@ export interface DiscoveredRouteFile {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Recursively discover all TypeScript
|
|
22
|
+
* Recursively discover all TypeScript files in an API directory.
|
|
23
|
+
* Any .ts file CAN be a route if it exports a router, but not all files MUST be routes.
|
|
24
|
+
* Files without router exports (utility files, types, helpers) are discovered but gracefully skipped during processing.
|
|
25
|
+
*
|
|
23
26
|
* Supports nested structures like:
|
|
24
|
-
* - src/api/index.ts (root API router)
|
|
27
|
+
* - src/api/index.ts (root API router - handled separately)
|
|
25
28
|
* - src/api/auth/route.ts -> mounted at /api/auth
|
|
29
|
+
* - src/api/auth/login.ts -> mounted at /api/auth
|
|
26
30
|
* - src/api/v1/users/route.ts -> mounted at /api/v1/users
|
|
27
|
-
* - src/api/
|
|
31
|
+
* - src/api/auth/helpers.ts -> discovered but skipped if no router export
|
|
28
32
|
*
|
|
29
33
|
* @param apiDir - Absolute path to the src/api directory
|
|
30
34
|
* @param currentDir - Current directory being scanned (used for recursion)
|
|
31
35
|
* @param results - Accumulated results (used for recursion)
|
|
32
|
-
* @returns Array of discovered
|
|
36
|
+
* @returns Array of discovered TypeScript files with mount information
|
|
33
37
|
*/
|
|
34
38
|
export function discoverRouteFiles(
|
|
35
39
|
apiDir: string,
|
|
@@ -63,8 +67,9 @@ export function discoverRouteFiles(
|
|
|
63
67
|
|
|
64
68
|
// For subdirectory files, determine mount path
|
|
65
69
|
// src/api/auth/route.ts -> /api/auth
|
|
70
|
+
// src/api/auth/login.ts -> /api/auth
|
|
66
71
|
// src/api/v1/users/route.ts -> /api/v1/users
|
|
67
|
-
// src/api/admin/
|
|
72
|
+
// src/api/admin/helpers.ts -> /api/admin (discovered but may be skipped during processing)
|
|
68
73
|
const pathParts = relativePath.split('/');
|
|
69
74
|
pathParts.pop(); // Remove filename
|
|
70
75
|
|
|
@@ -8,7 +8,6 @@ export function generateWorkbenchMainTsx(config: WorkbenchConfig): string {
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { createRoot } from 'react-dom/client';
|
|
10
10
|
import { App } from '@agentuity/workbench';
|
|
11
|
-
import '@agentuity/workbench/styles';
|
|
12
11
|
|
|
13
12
|
// Root element
|
|
14
13
|
const rootElement = document.getElementById('root');
|
|
@@ -23,6 +22,14 @@ root.render(<App configBase64="${encodedConfig}" />);
|
|
|
23
22
|
`;
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
export function generateWorkbenchStylesCss(): string {
|
|
26
|
+
// This file will be replaced with the actual dist/styles.css content during build
|
|
27
|
+
// We use @import here as a placeholder, but the bundler should resolve it to the built file
|
|
28
|
+
return `/* Generated workbench styles - will be replaced with dist/styles.css */
|
|
29
|
+
@import '@agentuity/workbench/styles-standalone';
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
export function generateWorkbenchIndexHtml(): string {
|
|
27
34
|
return `<!DOCTYPE html>
|
|
28
35
|
<html lang="en">
|
|
@@ -30,6 +37,7 @@ export function generateWorkbenchIndexHtml(): string {
|
|
|
30
37
|
<meta charset="UTF-8">
|
|
31
38
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
32
39
|
<title>Agentuity Workbench</title>
|
|
40
|
+
<link rel="stylesheet" href="./styles.css">
|
|
33
41
|
</head>
|
|
34
42
|
<body>
|
|
35
43
|
<div id="root"></div>
|