@agentuity/cli 0.0.108 → 0.0.110
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/dist/build-report.d.ts +201 -0
- package/dist/build-report.d.ts.map +1 -0
- package/dist/build-report.js +335 -0
- package/dist/build-report.js.map +1 -0
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +9 -3
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +44 -1
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/typecheck.d.ts +7 -1
- package/dist/cmd/build/typecheck.d.ts.map +1 -1
- package/dist/cmd/build/typecheck.js +11 -1
- package/dist/cmd/build/typecheck.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts +2 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +2 -1
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +2 -4
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +56 -18
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts +3 -0
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +14 -1
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/build/vite-bundler.d.ts +3 -0
- package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
- package/dist/cmd/build/vite-bundler.js +14 -5
- package/dist/cmd/build/vite-bundler.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +86 -9
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +0 -1
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +109 -15
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/project/auth/generate.d.ts +5 -0
- package/dist/cmd/project/auth/generate.d.ts.map +1 -0
- package/dist/cmd/project/auth/generate.js +102 -0
- package/dist/cmd/project/auth/generate.js.map +1 -0
- package/dist/cmd/project/auth/index.d.ts +2 -0
- package/dist/cmd/project/auth/index.d.ts.map +1 -0
- package/dist/cmd/project/auth/index.js +21 -0
- package/dist/cmd/project/auth/index.js.map +1 -0
- package/dist/cmd/project/auth/init.d.ts +2 -0
- package/dist/cmd/project/auth/init.d.ts.map +1 -0
- package/dist/cmd/project/auth/init.js +220 -0
- package/dist/cmd/project/auth/init.js.map +1 -0
- package/dist/cmd/project/auth/shared.d.ts +88 -0
- package/dist/cmd/project/auth/shared.d.ts.map +1 -0
- package/dist/cmd/project/auth/shared.js +435 -0
- package/dist/cmd/project/auth/shared.js.map +1 -0
- package/dist/cmd/project/index.d.ts.map +1 -1
- package/dist/cmd/project/index.js +9 -1
- package/dist/cmd/project/index.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +106 -0
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/types.d.ts +1 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -2
- package/dist/types.js.map +1 -1
- package/package.json +5 -4
- package/src/build-report.ts +457 -0
- package/src/cmd/build/entry-generator.ts +9 -3
- package/src/cmd/build/index.ts +51 -1
- package/src/cmd/build/typecheck.ts +19 -1
- package/src/cmd/build/vite/index.ts +4 -1
- package/src/cmd/build/vite/metadata-generator.ts +4 -6
- package/src/cmd/build/vite/registry-generator.ts +58 -19
- package/src/cmd/build/vite/vite-builder.ts +18 -1
- package/src/cmd/build/vite-bundler.ts +17 -4
- package/src/cmd/cloud/deploy.ts +105 -11
- package/src/cmd/cloud/deployment/show.ts +0 -1
- package/src/cmd/dev/index.ts +115 -14
- package/src/cmd/project/auth/generate.ts +116 -0
- package/src/cmd/project/auth/index.ts +21 -0
- package/src/cmd/project/auth/init.ts +263 -0
- package/src/cmd/project/auth/shared.ts +534 -0
- package/src/cmd/project/index.ts +9 -1
- package/src/cmd/project/template-flow.ts +125 -0
- package/src/types.ts +1 -2
|
@@ -478,12 +478,10 @@ export async function generateMetadata(options: MetadataGeneratorOptions): Promi
|
|
|
478
478
|
if (options.deploymentOptions.pullRequestNumber) {
|
|
479
479
|
git.pull_request = {
|
|
480
480
|
number: options.deploymentOptions.pullRequestNumber,
|
|
481
|
-
|
|
482
|
-
url: options.deploymentOptions.pullRequestURL,
|
|
481
|
+
url: options.deploymentOptions.pullRequestUrl,
|
|
483
482
|
};
|
|
484
|
-
delete git.pullRequestCommentId;
|
|
485
483
|
delete git.pullRequestNumber;
|
|
486
|
-
delete git.
|
|
484
|
+
delete git.pullRequestUrl;
|
|
487
485
|
}
|
|
488
486
|
metadata.deployment.git = git;
|
|
489
487
|
}
|
|
@@ -706,10 +704,10 @@ function generateAgentsMd(metadata: BuildMetadata): string {
|
|
|
706
704
|
lines.push('.agentuity/');
|
|
707
705
|
lines.push('├── app.js # Bundled server application');
|
|
708
706
|
lines.push('├── agentuity.metadata.json # Build metadata and schemas');
|
|
709
|
-
if (metadata.assets?.some((a) => a.filename.startsWith('client/'))) {
|
|
707
|
+
if (metadata.assets?.some((a: { filename: string }) => a.filename.startsWith('client/'))) {
|
|
710
708
|
lines.push('├── client/ # Frontend assets (fallback, CDN by default)');
|
|
711
709
|
}
|
|
712
|
-
if (metadata.assets?.some((a) => a.filename.startsWith('public/'))) {
|
|
710
|
+
if (metadata.assets?.some((a: { filename: string }) => a.filename.startsWith('public/'))) {
|
|
713
711
|
lines.push('├── public/ # Static assets');
|
|
714
712
|
}
|
|
715
713
|
lines.push('└── AGENTS.md # This file');
|
|
@@ -36,11 +36,14 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
|
|
|
36
36
|
const generatedDir = join(srcDir, 'generated');
|
|
37
37
|
const registryPath = join(generatedDir, 'registry.ts');
|
|
38
38
|
|
|
39
|
+
// Sort agents by name for deterministic output
|
|
40
|
+
const sortedAgents = [...agents].sort((a, b) => a.name.localeCompare(b.name));
|
|
41
|
+
|
|
39
42
|
// Detect naming collisions in generated identifiers
|
|
40
43
|
const generatedNames = new Set<string>();
|
|
41
44
|
const collisions: string[] = [];
|
|
42
45
|
|
|
43
|
-
for (const agent of
|
|
46
|
+
for (const agent of sortedAgents) {
|
|
44
47
|
const camelName = toCamelCase(agent.name);
|
|
45
48
|
|
|
46
49
|
if (generatedNames.has(camelName)) {
|
|
@@ -59,7 +62,7 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
// Generate imports for all agents
|
|
62
|
-
const imports =
|
|
65
|
+
const imports = sortedAgents
|
|
63
66
|
.map(({ name, filename }) => {
|
|
64
67
|
const camelName = toCamelCase(name);
|
|
65
68
|
// Handle both './agent/...' and 'src/agent/...' formats
|
|
@@ -80,7 +83,7 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
|
|
|
80
83
|
.join('\n');
|
|
81
84
|
|
|
82
85
|
// Generate schema type exports for all agents
|
|
83
|
-
const schemaTypeExports =
|
|
86
|
+
const schemaTypeExports = sortedAgents
|
|
84
87
|
.map(({ name, description }) => {
|
|
85
88
|
const camelName = toCamelCase(name);
|
|
86
89
|
const pascalName = toPascalCase(name);
|
|
@@ -122,7 +125,7 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
|
|
|
122
125
|
.join('\n');
|
|
123
126
|
|
|
124
127
|
// Generate flat registry structure with JSDoc
|
|
125
|
-
const registry =
|
|
128
|
+
const registry = sortedAgents
|
|
126
129
|
.map(({ name, description }) => {
|
|
127
130
|
const camelName = toCamelCase(name);
|
|
128
131
|
const pascalName = toPascalCase(name);
|
|
@@ -138,7 +141,7 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
|
|
|
138
141
|
|
|
139
142
|
// Generate flat agent type definitions for AgentRegistry interface augmentation
|
|
140
143
|
// Uses the exported Agent types defined above
|
|
141
|
-
const runtimeAgentTypes =
|
|
144
|
+
const runtimeAgentTypes = sortedAgents
|
|
142
145
|
.map(({ name }) => {
|
|
143
146
|
const camelName = toCamelCase(name);
|
|
144
147
|
const pascalName = toPascalCase(name);
|
|
@@ -231,7 +234,7 @@ function generateRPCRegistryType(
|
|
|
231
234
|
apiRoutes: RouteInfo[],
|
|
232
235
|
websocketRoutes: RouteInfo[],
|
|
233
236
|
sseRoutes: RouteInfo[],
|
|
234
|
-
|
|
237
|
+
agentImports: Map<string, string>,
|
|
235
238
|
_schemaImportAliases: Map<string, Map<string, string>>,
|
|
236
239
|
agentMetadataMap: Map<string, AgentMetadata>
|
|
237
240
|
): string {
|
|
@@ -263,6 +266,10 @@ function generateRPCRegistryType(
|
|
|
263
266
|
// Add path segments - sanitize for valid TypeScript property names
|
|
264
267
|
for (let i = 0; i < pathParts.length; i++) {
|
|
265
268
|
const part = sanitizePathSegment(pathParts[i]);
|
|
269
|
+
// Skip empty segments (e.g., wildcards like '*' that sanitize to '')
|
|
270
|
+
if (!part) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
266
273
|
if (!current[part]) {
|
|
267
274
|
current[part] = {};
|
|
268
275
|
}
|
|
@@ -292,8 +299,12 @@ function generateRPCRegistryType(
|
|
|
292
299
|
// Only reference type names if route has actual schemas extracted, otherwise use 'never'
|
|
293
300
|
// Note: hasValidator may be true (e.g., zValidator('query', ...)) but no schemas extracted
|
|
294
301
|
// because only 'json' validators extract input schemas
|
|
302
|
+
// Also check if agentVariable exists but import wasn't added (missing agentImportPath)
|
|
303
|
+
const hasValidAgentImport = route.agentVariable
|
|
304
|
+
? !!agentImports.get(route.agentVariable)
|
|
305
|
+
: false;
|
|
295
306
|
const hasSchemas =
|
|
296
|
-
route.inputSchemaVariable || route.outputSchemaVariable ||
|
|
307
|
+
route.inputSchemaVariable || route.outputSchemaVariable || hasValidAgentImport;
|
|
297
308
|
|
|
298
309
|
current[terminalMethod] = {
|
|
299
310
|
input: hasSchemas ? `${pascalName}Input` : 'never',
|
|
@@ -315,7 +326,9 @@ function generateRPCRegistryType(
|
|
|
315
326
|
function treeToTypeString(node: NestedNode, indent: string = '\t\t'): string {
|
|
316
327
|
const lines: string[] = [];
|
|
317
328
|
|
|
318
|
-
|
|
329
|
+
// Sort entries alphabetically for deterministic output
|
|
330
|
+
const sortedEntries = Object.entries(node).sort(([a], [b]) => a.localeCompare(b));
|
|
331
|
+
for (const [key, value] of sortedEntries) {
|
|
319
332
|
if (
|
|
320
333
|
value &&
|
|
321
334
|
typeof value === 'object' &&
|
|
@@ -400,6 +413,10 @@ function generateRPCRuntimeMetadata(
|
|
|
400
413
|
// Sanitize path segments for valid property names (must match type generation)
|
|
401
414
|
for (const part of pathParts) {
|
|
402
415
|
const sanitized = sanitizePathSegment(part);
|
|
416
|
+
// Skip empty segments (e.g., wildcards like '*' that sanitize to '')
|
|
417
|
+
if (!sanitized) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
403
420
|
if (!current[sanitized]) current[sanitized] = {};
|
|
404
421
|
current = current[sanitized];
|
|
405
422
|
}
|
|
@@ -421,7 +438,21 @@ function generateRPCRuntimeMetadata(
|
|
|
421
438
|
websocketRoutes.forEach((r) => addRoute(r, 'websocket'));
|
|
422
439
|
sseRoutes.forEach((r) => addRoute(r, 'sse'));
|
|
423
440
|
|
|
424
|
-
|
|
441
|
+
// Sort object keys recursively for deterministic output
|
|
442
|
+
const sortObject = (obj: MetadataNode): MetadataNode => {
|
|
443
|
+
const sorted: MetadataNode = {};
|
|
444
|
+
for (const key of Object.keys(obj).sort()) {
|
|
445
|
+
const value = obj[key];
|
|
446
|
+
if (value && typeof value === 'object' && !('type' in value)) {
|
|
447
|
+
sorted[key] = sortObject(value as MetadataNode);
|
|
448
|
+
} else {
|
|
449
|
+
sorted[key] = value;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return sorted;
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
return JSON.stringify(sortObject(tree), null, '\t\t');
|
|
425
456
|
}
|
|
426
457
|
|
|
427
458
|
/**
|
|
@@ -450,10 +481,13 @@ export function generateRouteRegistry(
|
|
|
450
481
|
// If we can't read package.json, assume no React dependency
|
|
451
482
|
}
|
|
452
483
|
|
|
453
|
-
// Filter routes by type
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
484
|
+
// Filter routes by type and sort by path for deterministic output
|
|
485
|
+
const sortByPath = (a: RouteInfo, b: RouteInfo) => a.path.localeCompare(b.path);
|
|
486
|
+
const apiRoutes = routes
|
|
487
|
+
.filter((r) => r.routeType === 'api' || r.routeType === 'stream')
|
|
488
|
+
.sort(sortByPath);
|
|
489
|
+
const websocketRoutes = routes.filter((r) => r.routeType === 'websocket').sort(sortByPath);
|
|
490
|
+
const sseRoutes = routes.filter((r) => r.routeType === 'sse').sort(sortByPath);
|
|
457
491
|
|
|
458
492
|
const allRoutes = [...apiRoutes, ...websocketRoutes, ...sseRoutes];
|
|
459
493
|
|
|
@@ -637,8 +671,10 @@ export function generateRouteRegistry(
|
|
|
637
671
|
agentMeta = agentMetadataMap.get(route.agentVariable);
|
|
638
672
|
}
|
|
639
673
|
|
|
640
|
-
if
|
|
641
|
-
|
|
674
|
+
// Only generate agent-based types if the import was successfully added
|
|
675
|
+
// (import is only added when hasValidator && agentVariable && agentImportPath are all present)
|
|
676
|
+
const importName = route.agentVariable ? agentImports.get(route.agentVariable) : undefined;
|
|
677
|
+
if (importName) {
|
|
642
678
|
inputType = `InferInput<typeof ${importName}['inputSchema']>`;
|
|
643
679
|
outputType = `InferOutput<typeof ${importName}['outputSchema']>`;
|
|
644
680
|
inputSchemaType = `typeof ${importName} extends { inputSchema?: infer I } ? I : never`;
|
|
@@ -724,10 +760,16 @@ export function generateRouteRegistry(
|
|
|
724
760
|
.replace(/_+/g, '_');
|
|
725
761
|
const pascalName = toPascalCase(safeName);
|
|
726
762
|
|
|
763
|
+
// Use the exported schema types we generated above
|
|
764
|
+
// Note: agentImports.get() may return undefined if import wasn't added
|
|
765
|
+
const importName = route.agentVariable ? agentImports.get(route.agentVariable) : null;
|
|
766
|
+
|
|
727
767
|
// Use 'never' types if no schemas were actually extracted
|
|
728
768
|
// Note: hasValidator may be true (e.g., zValidator('query', ...)) but no schemas extracted
|
|
729
769
|
// because only 'json' validators extract input schemas
|
|
730
|
-
if
|
|
770
|
+
// Also check if agentVariable exists but import wasn't added (missing agentImportPath)
|
|
771
|
+
const hasValidAgentImport = route.agentVariable ? !!importName : false;
|
|
772
|
+
if (!route.inputSchemaVariable && !route.outputSchemaVariable && !hasValidAgentImport) {
|
|
731
773
|
const streamValue = route.stream === true ? 'true' : 'false';
|
|
732
774
|
return `\t'${routeKey}': {
|
|
733
775
|
\t\tinputSchema: never;
|
|
@@ -735,9 +777,6 @@ export function generateRouteRegistry(
|
|
|
735
777
|
\t\tstream: ${streamValue};
|
|
736
778
|
\t};`;
|
|
737
779
|
}
|
|
738
|
-
|
|
739
|
-
// Use the exported schema types we generated above
|
|
740
|
-
const importName = route.agentVariable ? agentImports.get(route.agentVariable)! : null;
|
|
741
780
|
const streamValue = importName
|
|
742
781
|
? `typeof ${importName} extends { stream?: infer S } ? S : false`
|
|
743
782
|
: route.stream === true
|
|
@@ -10,6 +10,7 @@ import { createRequire } from 'node:module';
|
|
|
10
10
|
import type { InlineConfig, Plugin } from 'vite';
|
|
11
11
|
import type { Logger, DeployOptions } from '../../../types';
|
|
12
12
|
import { browserEnvPlugin } from './browser-env-plugin';
|
|
13
|
+
import type { BuildReportCollector } from '../../../build-report';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Vite plugin to flatten the output structure for index.html
|
|
@@ -56,6 +57,8 @@ export interface ViteBuildOptions {
|
|
|
56
57
|
workbenchEnabled?: boolean;
|
|
57
58
|
logger: Logger;
|
|
58
59
|
deploymentOptions?: DeployOptions;
|
|
60
|
+
/** Optional collector for structured error reporting */
|
|
61
|
+
collector?: BuildReportCollector;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/**
|
|
@@ -86,6 +89,11 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
86
89
|
const { generateLifecycleTypes } = await import('./lifecycle-generator');
|
|
87
90
|
await generateLifecycleTypes(rootDir, srcDir, logger);
|
|
88
91
|
|
|
92
|
+
// Load workbench config for entry file generation
|
|
93
|
+
const { loadAgentuityConfig, getWorkbenchConfig } = await import('./config-loader');
|
|
94
|
+
const config = await loadAgentuityConfig(rootDir, logger);
|
|
95
|
+
const workbenchConfig = getWorkbenchConfig(config, dev);
|
|
96
|
+
|
|
89
97
|
// Then, generate the entry file
|
|
90
98
|
const { generateEntryFile } = await import('../entry-generator');
|
|
91
99
|
await generateEntryFile({
|
|
@@ -94,6 +102,7 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
94
102
|
deploymentId: deploymentId || '',
|
|
95
103
|
logger,
|
|
96
104
|
mode: dev ? 'dev' : 'prod',
|
|
105
|
+
workbench: workbenchConfig.enabled ? workbenchConfig : undefined,
|
|
97
106
|
});
|
|
98
107
|
|
|
99
108
|
// Finally, build with Bun.build
|
|
@@ -250,7 +259,7 @@ interface BuildResult {
|
|
|
250
259
|
* Run all builds in sequence: client -> workbench (if enabled) -> server
|
|
251
260
|
*/
|
|
252
261
|
export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Promise<BuildResult> {
|
|
253
|
-
const { rootDir, projectId = '', dev = false, logger } = options;
|
|
262
|
+
const { rootDir, projectId = '', dev = false, logger, collector } = options;
|
|
254
263
|
|
|
255
264
|
if (!dev) {
|
|
256
265
|
rmSync(join(rootDir, '.agentuity'), { force: true, recursive: true });
|
|
@@ -305,6 +314,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
|
|
|
305
314
|
// 2. Build client (only if web frontend exists)
|
|
306
315
|
if (hasWebFrontend) {
|
|
307
316
|
logger.debug('Building client assets...');
|
|
317
|
+
const endClientDiagnostic = collector?.startDiagnostic('client-build');
|
|
308
318
|
const started = Date.now();
|
|
309
319
|
await runViteBuild({
|
|
310
320
|
...options,
|
|
@@ -314,6 +324,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
|
|
|
314
324
|
});
|
|
315
325
|
result.client.included = true;
|
|
316
326
|
result.client.duration = Date.now() - started;
|
|
327
|
+
endClientDiagnostic?.();
|
|
317
328
|
} else {
|
|
318
329
|
logger.debug('Skipping client build - no src/web/index.html found');
|
|
319
330
|
}
|
|
@@ -321,6 +332,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
|
|
|
321
332
|
// 3. Build workbench (if enabled in config)
|
|
322
333
|
if (workbenchConfig.enabled) {
|
|
323
334
|
logger.debug('Building workbench assets...');
|
|
335
|
+
const endWorkbenchDiagnostic = collector?.startDiagnostic('workbench-build');
|
|
324
336
|
const started = Date.now();
|
|
325
337
|
await runViteBuild({
|
|
326
338
|
...options,
|
|
@@ -330,17 +342,21 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
|
|
|
330
342
|
});
|
|
331
343
|
result.workbench.included = true;
|
|
332
344
|
result.workbench.duration = Date.now() - started;
|
|
345
|
+
endWorkbenchDiagnostic?.();
|
|
333
346
|
}
|
|
334
347
|
|
|
335
348
|
// 4. Build server
|
|
336
349
|
logger.debug('Building server...');
|
|
350
|
+
const endServerDiagnostic = collector?.startDiagnostic('server-build');
|
|
337
351
|
const serverStarted = Date.now();
|
|
338
352
|
await runViteBuild({ ...options, mode: 'server' });
|
|
339
353
|
result.server.included = true;
|
|
340
354
|
result.server.duration = Date.now() - serverStarted;
|
|
355
|
+
endServerDiagnostic?.();
|
|
341
356
|
|
|
342
357
|
// 5. Generate metadata (after all builds complete)
|
|
343
358
|
logger.debug('Generating metadata...');
|
|
359
|
+
const endMetadataDiagnostic = collector?.startDiagnostic('metadata-generation');
|
|
344
360
|
const { generateMetadata, writeMetadataFile } = await import('./metadata-generator');
|
|
345
361
|
|
|
346
362
|
// Generate metadata
|
|
@@ -357,6 +373,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
|
|
|
357
373
|
});
|
|
358
374
|
|
|
359
375
|
writeMetadataFile(rootDir, metadata, dev, logger);
|
|
376
|
+
endMetadataDiagnostic?.();
|
|
360
377
|
logger.debug('Registry and metadata generation complete');
|
|
361
378
|
|
|
362
379
|
logger.debug('All builds complete');
|
|
@@ -12,6 +12,7 @@ import { runAllBuilds } from './vite/vite-builder';
|
|
|
12
12
|
import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
|
|
13
13
|
import { checkBunVersion } from '../../utils/bun-version-checker';
|
|
14
14
|
import * as tui from '../../tui';
|
|
15
|
+
import type { BuildReportCollector } from '../../build-report';
|
|
15
16
|
|
|
16
17
|
const AppFileNotFoundError = StructuredError('AppFileNotFoundError');
|
|
17
18
|
const BuildFailedError = StructuredError('BuildFailedError');
|
|
@@ -26,6 +27,8 @@ export interface ViteBundleOptions {
|
|
|
26
27
|
port?: number;
|
|
27
28
|
logger: Logger;
|
|
28
29
|
deploymentOptions?: DeployOptions;
|
|
30
|
+
/** Optional collector for structured error reporting */
|
|
31
|
+
collector?: BuildReportCollector;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -41,6 +44,7 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
|
|
|
41
44
|
port = 3500,
|
|
42
45
|
logger,
|
|
43
46
|
deploymentOptions,
|
|
47
|
+
collector,
|
|
44
48
|
} = options;
|
|
45
49
|
|
|
46
50
|
const output: string[] = [];
|
|
@@ -52,8 +56,10 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
|
|
|
52
56
|
// Verify app.ts exists
|
|
53
57
|
const appFile = join(rootDir, 'app.ts');
|
|
54
58
|
if (!(await Bun.file(appFile).exists())) {
|
|
59
|
+
const errorMessage = `App file not found at expected location: ${appFile}`;
|
|
60
|
+
collector?.addGeneralError('build', errorMessage, 'BUILD001');
|
|
55
61
|
throw new AppFileNotFoundError({
|
|
56
|
-
message:
|
|
62
|
+
message: errorMessage,
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -63,16 +69,20 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
|
|
|
63
69
|
.then((s) => s.isDirectory())
|
|
64
70
|
.catch(() => false);
|
|
65
71
|
if (!srcDirExists) {
|
|
72
|
+
const errorMessage = `Source directory not found: ${srcDir}`;
|
|
73
|
+
collector?.addGeneralError('build', errorMessage, 'BUILD002');
|
|
66
74
|
throw new BuildFailedError({
|
|
67
|
-
message:
|
|
75
|
+
message: errorMessage,
|
|
68
76
|
});
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
// Check and upgrade @agentuity/* dependencies if needed
|
|
72
80
|
const upgradeResult = await checkAndUpgradeDependencies(rootDir, logger);
|
|
73
81
|
if (upgradeResult.failed.length > 0 && process.stdin.isTTY) {
|
|
82
|
+
const errorMessage = `Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`;
|
|
83
|
+
collector?.addGeneralError('build', errorMessage, 'BUILD003');
|
|
74
84
|
throw new BuildFailedError({
|
|
75
|
-
message:
|
|
85
|
+
message: errorMessage,
|
|
76
86
|
});
|
|
77
87
|
}
|
|
78
88
|
|
|
@@ -90,6 +100,7 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
|
|
|
90
100
|
deploymentId,
|
|
91
101
|
logger,
|
|
92
102
|
deploymentOptions,
|
|
103
|
+
collector,
|
|
93
104
|
});
|
|
94
105
|
|
|
95
106
|
if (result.client.included) {
|
|
@@ -106,8 +117,10 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
|
|
|
106
117
|
|
|
107
118
|
return { output };
|
|
108
119
|
} catch (error) {
|
|
120
|
+
const errorMessage = `Build failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
121
|
+
collector?.addGeneralError('build', errorMessage, 'BUILD004');
|
|
109
122
|
throw new BuildFailedError({
|
|
110
|
-
message:
|
|
123
|
+
message: errorMessage,
|
|
111
124
|
});
|
|
112
125
|
}
|
|
113
126
|
}
|
package/src/cmd/cloud/deploy.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { getCommand } from '../../command-prefix';
|
|
|
45
45
|
import * as domain from '../../domain';
|
|
46
46
|
import { ErrorCode } from '../../errors';
|
|
47
47
|
import { typecheck } from '../build/typecheck';
|
|
48
|
+
import { BuildReportCollector, setGlobalCollector, clearGlobalCollector } from '../../build-report';
|
|
48
49
|
|
|
49
50
|
const DeploymentCancelledError = StructuredError(
|
|
50
51
|
'DeploymentCancelled',
|
|
@@ -93,7 +94,12 @@ export const deploySubcommand = createSubcommand({
|
|
|
93
94
|
options: z.intersection(
|
|
94
95
|
DeployOptionsSchema,
|
|
95
96
|
z.object({
|
|
96
|
-
|
|
97
|
+
reportFile: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe(
|
|
101
|
+
'file path to save build report JSON with errors, warnings, and diagnostics'
|
|
102
|
+
),
|
|
97
103
|
})
|
|
98
104
|
),
|
|
99
105
|
response: DeployResponseSchema,
|
|
@@ -102,6 +108,14 @@ export const deploySubcommand = createSubcommand({
|
|
|
102
108
|
async handler(ctx) {
|
|
103
109
|
const { project, apiClient, projectDir, config, options, logger, opts } = ctx;
|
|
104
110
|
|
|
111
|
+
// Initialize build report collector if reportFile is specified
|
|
112
|
+
const collector = new BuildReportCollector();
|
|
113
|
+
if (opts.reportFile) {
|
|
114
|
+
collector.setOutputPath(opts.reportFile);
|
|
115
|
+
collector.enableAutoWrite();
|
|
116
|
+
setGlobalCollector(collector);
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
let deployment: Deployment | undefined;
|
|
106
120
|
let build: BuildMetadata | undefined;
|
|
107
121
|
let instructions: DeploymentInstructions | undefined;
|
|
@@ -239,8 +253,13 @@ export const deploySubcommand = createSubcommand({
|
|
|
239
253
|
}
|
|
240
254
|
let capturedOutput: string[] = [];
|
|
241
255
|
const rootDir = resolve(projectDir);
|
|
256
|
+
|
|
257
|
+
// Run typecheck with collector for error reporting
|
|
258
|
+
const endTypecheckDiagnostic = collector.startDiagnostic('typecheck');
|
|
242
259
|
const started = Date.now();
|
|
243
|
-
const typeResult = await typecheck(rootDir);
|
|
260
|
+
const typeResult = await typecheck(rootDir, { collector });
|
|
261
|
+
endTypecheckDiagnostic();
|
|
262
|
+
|
|
244
263
|
if (typeResult.success) {
|
|
245
264
|
capturedOutput.push(
|
|
246
265
|
tui.muted(
|
|
@@ -248,9 +267,10 @@ export const deploySubcommand = createSubcommand({
|
|
|
248
267
|
)
|
|
249
268
|
);
|
|
250
269
|
} else {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
270
|
+
// Errors already added to collector by typecheck()
|
|
271
|
+
// Write report before returning error
|
|
272
|
+
if (opts.reportFile) {
|
|
273
|
+
await collector.forceWrite();
|
|
254
274
|
}
|
|
255
275
|
return stepError('Typecheck failed\n\n' + typeResult.output);
|
|
256
276
|
}
|
|
@@ -264,6 +284,7 @@ export const deploySubcommand = createSubcommand({
|
|
|
264
284
|
region: project.region,
|
|
265
285
|
logger: ctx.logger,
|
|
266
286
|
deploymentOptions: opts,
|
|
287
|
+
collector,
|
|
267
288
|
});
|
|
268
289
|
capturedOutput = [...capturedOutput, ...bundleResult.output];
|
|
269
290
|
build = await loadBuildMetadata(join(projectDir, '.agentuity'));
|
|
@@ -275,6 +296,10 @@ export const deploySubcommand = createSubcommand({
|
|
|
275
296
|
return stepSuccess(capturedOutput.length > 0 ? capturedOutput : undefined);
|
|
276
297
|
} catch (ex) {
|
|
277
298
|
const _ex = ex as Error;
|
|
299
|
+
// Write report before returning error
|
|
300
|
+
if (opts.reportFile) {
|
|
301
|
+
await collector.forceWrite();
|
|
302
|
+
}
|
|
278
303
|
return stepError(
|
|
279
304
|
_ex.message ?? 'Error building your project',
|
|
280
305
|
_ex,
|
|
@@ -294,6 +319,8 @@ export const deploySubcommand = createSubcommand({
|
|
|
294
319
|
return stepError('deployment instructions were null');
|
|
295
320
|
}
|
|
296
321
|
|
|
322
|
+
// Start diagnostic for zip/encrypt phase
|
|
323
|
+
const endZipDiagnostic = collector.startDiagnostic('zip-package');
|
|
297
324
|
progress(5);
|
|
298
325
|
ctx.logger.trace('Starting deployment zip creation');
|
|
299
326
|
// zip up the assets folder
|
|
@@ -319,8 +346,11 @@ export const deploySubcommand = createSubcommand({
|
|
|
319
346
|
});
|
|
320
347
|
ctx.logger.trace(`Deployment zip created: ${deploymentZip}`);
|
|
321
348
|
|
|
349
|
+
endZipDiagnostic();
|
|
350
|
+
|
|
322
351
|
progress(20);
|
|
323
352
|
// Encrypt the deployment zip using the public key from deployment
|
|
353
|
+
const endEncryptDiagnostic = collector.startDiagnostic('encrypt');
|
|
324
354
|
const encryptedZip = join(tmpdir(), `${deployment.id}.enc.zip`);
|
|
325
355
|
try {
|
|
326
356
|
ctx.logger.trace('Creating public key');
|
|
@@ -348,8 +378,11 @@ export const deploySubcommand = createSubcommand({
|
|
|
348
378
|
dst.end();
|
|
349
379
|
});
|
|
350
380
|
ctx.logger.trace('Stream finished');
|
|
381
|
+
endEncryptDiagnostic();
|
|
351
382
|
|
|
352
383
|
progress(50);
|
|
384
|
+
// Start code upload diagnostic
|
|
385
|
+
const endCodeUploadDiagnostic = collector.startDiagnostic('code-upload');
|
|
353
386
|
ctx.logger.trace(`Uploading deployment to ${instructions.deployment}`);
|
|
354
387
|
const zipfile = Bun.file(encryptedZip);
|
|
355
388
|
const fileSize = await zipfile.size;
|
|
@@ -364,8 +397,15 @@ export const deploySubcommand = createSubcommand({
|
|
|
364
397
|
});
|
|
365
398
|
ctx.logger.trace(`Upload response: ${resp.status}`);
|
|
366
399
|
if (!resp.ok) {
|
|
367
|
-
|
|
400
|
+
endCodeUploadDiagnostic();
|
|
401
|
+
const errorMsg = `Error uploading deployment: ${await resp.text()}`;
|
|
402
|
+
collector.addGeneralError('deploy', errorMsg, 'DEPLOY002');
|
|
403
|
+
if (opts.reportFile) {
|
|
404
|
+
await collector.forceWrite();
|
|
405
|
+
}
|
|
406
|
+
return stepError(errorMsg);
|
|
368
407
|
}
|
|
408
|
+
endCodeUploadDiagnostic();
|
|
369
409
|
|
|
370
410
|
progress(70);
|
|
371
411
|
ctx.logger.trace('Consuming response body');
|
|
@@ -385,11 +425,17 @@ export const deploySubcommand = createSubcommand({
|
|
|
385
425
|
progress(80);
|
|
386
426
|
let bytes = 0;
|
|
387
427
|
if (build?.assets) {
|
|
428
|
+
// Start CDN upload diagnostic
|
|
429
|
+
const endCdnUploadDiagnostic = collector.startDiagnostic('cdn-upload');
|
|
388
430
|
ctx.logger.trace(`Uploading ${build.assets.length} assets`);
|
|
389
431
|
if (!instructions.assets) {
|
|
390
|
-
|
|
391
|
-
'server did not provide asset upload URLs; upload aborted'
|
|
392
|
-
);
|
|
432
|
+
const errorMsg =
|
|
433
|
+
'server did not provide asset upload URLs; upload aborted';
|
|
434
|
+
collector.addGeneralError('deploy', errorMsg, 'DEPLOY006');
|
|
435
|
+
if (opts.reportFile) {
|
|
436
|
+
await collector.forceWrite();
|
|
437
|
+
}
|
|
438
|
+
return stepError(errorMsg);
|
|
393
439
|
}
|
|
394
440
|
|
|
395
441
|
// Workaround for Bun crash in compiled executables (https://github.com/agentuity/sdk/issues/191)
|
|
@@ -452,11 +498,17 @@ export const deploySubcommand = createSubcommand({
|
|
|
452
498
|
const resps = await Promise.all(promises);
|
|
453
499
|
for (const r of resps) {
|
|
454
500
|
if (!r.ok) {
|
|
455
|
-
|
|
501
|
+
const errorMsg = `error uploading asset: ${await r.text()}`;
|
|
502
|
+
collector.addGeneralError('deploy', errorMsg, 'DEPLOY006');
|
|
503
|
+
if (opts.reportFile) {
|
|
504
|
+
await collector.forceWrite();
|
|
505
|
+
}
|
|
506
|
+
return stepError(errorMsg);
|
|
456
507
|
}
|
|
457
508
|
}
|
|
458
509
|
}
|
|
459
510
|
ctx.logger.trace('Asset uploads complete');
|
|
511
|
+
endCdnUploadDiagnostic();
|
|
460
512
|
progress(95);
|
|
461
513
|
}
|
|
462
514
|
|
|
@@ -503,6 +555,7 @@ export const deploySubcommand = createSubcommand({
|
|
|
503
555
|
const dashboard = `${appUrl}/r/${deployment.id}`;
|
|
504
556
|
|
|
505
557
|
// Poll for deployment status with optional log streaming
|
|
558
|
+
const endDeploymentWaitDiagnostic = collector.startDiagnostic('deployment-wait');
|
|
506
559
|
const pollInterval = 500;
|
|
507
560
|
const maxAttempts = 600;
|
|
508
561
|
let attempts = 0;
|
|
@@ -615,11 +668,16 @@ export const deploySubcommand = createSubcommand({
|
|
|
615
668
|
},
|
|
616
669
|
})
|
|
617
670
|
.then(() => {
|
|
671
|
+
endDeploymentWaitDiagnostic();
|
|
618
672
|
tui.success('Your project was deployed!');
|
|
619
673
|
})
|
|
620
|
-
.catch((ex) => {
|
|
674
|
+
.catch(async (ex) => {
|
|
675
|
+
endDeploymentWaitDiagnostic();
|
|
621
676
|
// Handle cancellation
|
|
622
677
|
if (ex instanceof DeploymentCancelledError) {
|
|
678
|
+
if (opts.reportFile) {
|
|
679
|
+
await collector.forceWrite();
|
|
680
|
+
}
|
|
623
681
|
tui.warning('Deployment cancelled');
|
|
624
682
|
process.exit(130); // Standard exit code for SIGINT
|
|
625
683
|
}
|
|
@@ -628,6 +686,18 @@ export const deploySubcommand = createSubcommand({
|
|
|
628
686
|
exwithmessage.message === 'Deployment failed'
|
|
629
687
|
? ''
|
|
630
688
|
: exwithmessage.toString();
|
|
689
|
+
|
|
690
|
+
// Add error to collector
|
|
691
|
+
const isTimeout = exwithmessage.message === 'Deployment timed out';
|
|
692
|
+
collector.addGeneralError(
|
|
693
|
+
'deploy',
|
|
694
|
+
msg || 'Deployment failed',
|
|
695
|
+
isTimeout ? 'DEPLOY003' : 'DEPLOY004'
|
|
696
|
+
);
|
|
697
|
+
if (opts.reportFile) {
|
|
698
|
+
await collector.forceWrite();
|
|
699
|
+
}
|
|
700
|
+
|
|
631
701
|
tui.error(`Your deployment failed to start${msg ? `: ${msg}` : ''}`);
|
|
632
702
|
if (logs.length) {
|
|
633
703
|
const logsDir = join(getDefaultConfigDir(), 'logs');
|
|
@@ -687,9 +757,22 @@ export const deploySubcommand = createSubcommand({
|
|
|
687
757
|
},
|
|
688
758
|
});
|
|
689
759
|
|
|
760
|
+
endDeploymentWaitDiagnostic();
|
|
690
761
|
tui.success('Your project was deployed!');
|
|
691
762
|
}
|
|
692
763
|
} catch (ex) {
|
|
764
|
+
endDeploymentWaitDiagnostic();
|
|
765
|
+
const exwithmessage = ex as { message: string };
|
|
766
|
+
const isTimeout = exwithmessage?.message === 'Deployment timed out';
|
|
767
|
+
collector.addGeneralError(
|
|
768
|
+
'deploy',
|
|
769
|
+
exwithmessage?.message || String(ex),
|
|
770
|
+
isTimeout ? 'DEPLOY003' : 'DEPLOY004'
|
|
771
|
+
);
|
|
772
|
+
if (opts.reportFile) {
|
|
773
|
+
await collector.forceWrite();
|
|
774
|
+
}
|
|
775
|
+
|
|
693
776
|
const lines = [`${ex}`, ''];
|
|
694
777
|
lines.push(
|
|
695
778
|
`${tui.ICONS.arrow} ${
|
|
@@ -741,6 +824,12 @@ export const deploySubcommand = createSubcommand({
|
|
|
741
824
|
});
|
|
742
825
|
}
|
|
743
826
|
|
|
827
|
+
// Write final report on success
|
|
828
|
+
if (opts.reportFile) {
|
|
829
|
+
await collector.forceWrite();
|
|
830
|
+
}
|
|
831
|
+
clearGlobalCollector();
|
|
832
|
+
|
|
744
833
|
return {
|
|
745
834
|
success: true,
|
|
746
835
|
deploymentId: deployment.id,
|
|
@@ -756,6 +845,11 @@ export const deploySubcommand = createSubcommand({
|
|
|
756
845
|
: undefined,
|
|
757
846
|
};
|
|
758
847
|
} catch (ex) {
|
|
848
|
+
collector.addGeneralError('deploy', String(ex), 'DEPLOY004');
|
|
849
|
+
if (opts.reportFile) {
|
|
850
|
+
await collector.forceWrite();
|
|
851
|
+
}
|
|
852
|
+
clearGlobalCollector();
|
|
759
853
|
tui.fatal(`unexpected error trying to deploy project. ${ex}`);
|
|
760
854
|
}
|
|
761
855
|
},
|