@sansavision/aurora 0.1.0-alpha.20260212.4
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/README.md +4 -0
- package/package.json +17 -0
- package/src/ai-diagnostics.ts +156 -0
- package/src/ai.ts +574 -0
- package/src/analyze.ts +669 -0
- package/src/bin/aurora.ts +15 -0
- package/src/build.ts +431 -0
- package/src/bun-test-shims.d.ts +17 -0
- package/src/create-feature.ts +419 -0
- package/src/create-route.ts +581 -0
- package/src/create.ts +425 -0
- package/src/dev.ts +126 -0
- package/src/devtools.ts +1143 -0
- package/src/doctor.ts +611 -0
- package/src/explain.ts +855 -0
- package/src/help.ts +39 -0
- package/src/index.ts +34 -0
- package/src/init.ts +1011 -0
- package/src/inspect-cache.ts +464 -0
- package/src/lsp-inline-hints.ts +254 -0
- package/src/node-shims.d.ts +26 -0
- package/src/process.d.ts +11 -0
- package/src/query-profiler.ts +520 -0
- package/src/realtime-monitor.ts +389 -0
- package/src/registry.ts +303 -0
- package/src/run.ts +37 -0
- package/src/start.ts +56 -0
- package/src/test.ts +289 -0
- package/templates/basic/README.md +16 -0
- package/templates/basic/package.json +10 -0
- package/templates/basic/src/actions/createMessage.action.server.ts +22 -0
- package/templates/basic/src/lib/auth.server.ts +11 -0
- package/templates/basic/src/queries/listMessages.server.ts +17 -0
- package/templates/basic/src/routes/index.tsx +12 -0
- package/templates/blog/README.md +17 -0
- package/templates/blog/package.json +12 -0
- package/templates/blog/public/assets/og-default.svg +17 -0
- package/templates/blog/src/content/loadPosts.server.ts +22 -0
- package/templates/blog/src/content/posts/hello-world.md +11 -0
- package/templates/blog/src/content/posts/release-notes.md +9 -0
- package/templates/blog/src/routes/index.tsx +22 -0
- package/templates/blog/src/routes/posts/[slug].tsx +19 -0
- package/templates/blog/src/seo/meta.ts +19 -0
- package/templates/dashboard/README.md +18 -0
- package/templates/dashboard/package.json +10 -0
- package/templates/dashboard/src/actions/acknowledgeAlert.action.server.ts +6 -0
- package/templates/dashboard/src/queries/getDashboardMetrics.server.ts +30 -0
- package/templates/dashboard/src/realtime/useDashboardRealtime.client.ts +13 -0
- package/templates/dashboard/src/routes/index.tsx +19 -0
- package/templates/dashboard/src/widgets/DataGrid.client.ts +8 -0
- package/templates/dashboard/src/widgets/MetricChart.client.ts +8 -0
- package/templates/desktop/README.md +18 -0
- package/templates/desktop/package.json +11 -0
- package/templates/desktop/src/actions/saveDesktopPreference.action.server.ts +28 -0
- package/templates/desktop/src/desktop/secureStorage.client.ts +20 -0
- package/templates/desktop/src/desktop/tauriBridge.client.ts +14 -0
- package/templates/desktop/src/queries/getDesktopSyncStatus.server.ts +9 -0
- package/templates/desktop/src/routes/index.tsx +27 -0
- package/templates/desktop/src/sync/offlineSyncBoundary.server.ts +27 -0
- package/templates/feature-skeleton/README.md +13 -0
- package/templates/feature-skeleton/actions/createFeature.action.server.ts +19 -0
- package/templates/feature-skeleton/index.ts +8 -0
- package/templates/feature-skeleton/queries/listFeature.server.ts +15 -0
- package/templates/feature-skeleton/realtime/useFeatureRealtime.client.ts +16 -0
- package/templates/feature-skeleton/template.manifest.json +15 -0
- package/templates/feature-skeleton/ui/FeatureView.client.tsx +14 -0
- package/templates/mobile/README.md +17 -0
- package/templates/mobile/package.json +11 -0
- package/templates/mobile/src/mobile/auth/session-handoff.client.ts +69 -0
- package/templates/mobile/src/mobile/generated/mobile-api-sdk.ts +62 -0
- package/templates/mobile/src/mobile/transport/mobile-api-transport.client.ts +122 -0
- package/templates/mobile/src/routes/index.tsx +134 -0
- package/templates/monorepo/README.md +18 -0
- package/templates/monorepo/apps/web/package.json +9 -0
- package/templates/monorepo/apps/web/src/routes/index.tsx +1 -0
- package/templates/monorepo/package.json +13 -0
- package/templates/monorepo/packages/shared/README.md +3 -0
- package/templates/monorepo/packages/ui/README.md +3 -0
- package/templates/saas/README.md +17 -0
- package/templates/saas/package.json +10 -0
- package/templates/saas/src/admin/getDashboard.server.ts +18 -0
- package/templates/saas/src/auth/session.server.ts +13 -0
- package/templates/saas/src/billing/checkout.server.ts +11 -0
- package/templates/saas/src/email/sendWelcome.server.ts +8 -0
- package/templates/saas/src/realtime/notifications.server.ts +8 -0
- package/templates/saas/src/routes/index.tsx +20 -0
- package/test/ai.test.ts +94 -0
- package/test/analyze.test.ts +301 -0
- package/test/build.test.ts +135 -0
- package/test/create-feature.test.ts +145 -0
- package/test/create-route.test.ts +117 -0
- package/test/create.test.ts +222 -0
- package/test/dev.test.ts +52 -0
- package/test/devtools.test.ts +130 -0
- package/test/doctor.test.ts +129 -0
- package/test/explain.test.ts +232 -0
- package/test/feature-skeleton.test.ts +53 -0
- package/test/fixtures/analyze/cache-input.invalid.json +1 -0
- package/test/fixtures/analyze/cache-input.missing-keyhash.v1.json +10 -0
- package/test/fixtures/analyze/cache-input.unsupported-version.v2.json +10 -0
- package/test/fixtures/analyze/cache-input.v1.json +12 -0
- package/test/fixtures/analyze/compiler-manifest/manifest.json +11 -0
- package/test/fixtures/analyze/guardrails-input.unsupported-version.v2.json +4 -0
- package/test/fixtures/analyze/guardrails-input.v1.json +49 -0
- package/test/fixtures/analyze/query-input.invalid-cache-status.v1.json +11 -0
- package/test/fixtures/analyze/query-input.unsupported-version.v2.json +11 -0
- package/test/fixtures/analyze/query-input.v1.json +18 -0
- package/test/fixtures/analyze/realtime-input.missing-lag-p95.v1.json +10 -0
- package/test/fixtures/analyze/realtime-input.unsupported-version.v2.json +8 -0
- package/test/fixtures/analyze/realtime-input.v1.json +12 -0
- package/test/fixtures/cache-inspector/cache-input.v1.json +23 -0
- package/test/fixtures/cache-inspector/invalid.json +1 -0
- package/test/fixtures/cache-inspector/snapshot.v1.json +34 -0
- package/test/fixtures/cache-inspector/unsupported-version.v2.json +13 -0
- package/test/fixtures/devtools/healthy.v1.json +130 -0
- package/test/fixtures/devtools/invalid.json +1 -0
- package/test/fixtures/devtools/unsupported-version.v2.json +8 -0
- package/test/fixtures/devtools/warn.v1.json +114 -0
- package/test/fixtures/doctor/clean/src/page.tsx +3 -0
- package/test/fixtures/doctor/findings/src/accessibility.client.tsx +7 -0
- package/test/fixtures/doctor/findings/src/migration.config.ts +3 -0
- package/test/fixtures/doctor/findings/src/page.client.tsx +5 -0
- package/test/fixtures/doctor/findings/src/perf.server.ts +15 -0
- package/test/fixtures/doctor/findings/src/routes.js +3 -0
- package/test/fixtures/doctor/findings/src/security.server.ts +7 -0
- package/test/fixtures/doctor/findings/src/users.server.ts +3 -0
- package/test/fixtures/doctor/governance/src/features/analytics/OWNERS.ts +2 -0
- package/test/fixtures/doctor/governance/src/features/analytics/page.tsx +3 -0
- package/test/fixtures/doctor/governance/src/features/billing/page.tsx +3 -0
- package/test/fixtures/explain/invalid.json +1 -0
- package/test/fixtures/explain/module-report.unsupported-version.v2.json +6 -0
- package/test/fixtures/explain/module-report.v1.json +72 -0
- package/test/fixtures/query-profiler/healthy.v1.json +11 -0
- package/test/fixtures/query-profiler/invalid.json +1 -0
- package/test/fixtures/query-profiler/unsupported-version.v2.json +6 -0
- package/test/fixtures/query-profiler/warning.v1.json +10 -0
- package/test/fixtures/realtime-monitor/healthy.v1.json +8 -0
- package/test/fixtures/realtime-monitor/invalid.json +1 -0
- package/test/fixtures/realtime-monitor/unsupported-version.v2.json +8 -0
- package/test/fixtures/realtime-monitor/warning.v1.json +8 -0
- package/test/help-parity.test.ts +104 -0
- package/test/init.test.ts +164 -0
- package/test/inspect-cache.test.ts +112 -0
- package/test/lsp-inline-hints.test.ts +65 -0
- package/test/query-profiler.test.ts +123 -0
- package/test/realtime-monitor.test.ts +115 -0
- package/test/registry.test.ts +41 -0
- package/test/start.test.ts +23 -0
- package/test/test-command.test.ts +65 -0
- package/tsconfig.json +19 -0
package/src/analyze.ts
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
evaluatePerformanceGuardrails,
|
|
6
|
+
type GuardrailIssue,
|
|
7
|
+
type PerformanceGuardrailsInput,
|
|
8
|
+
} from "../../perf/guardrails/src";
|
|
9
|
+
import { type CommandContext, type CommandResult } from "./registry";
|
|
10
|
+
|
|
11
|
+
type AnalyzeMode = "bundle" | "cache" | "realtime" | "query" | "guardrails";
|
|
12
|
+
|
|
13
|
+
interface AnalyzeOptions {
|
|
14
|
+
format: "text" | "json";
|
|
15
|
+
mode: AnalyzeMode;
|
|
16
|
+
outDir: string;
|
|
17
|
+
inputPath?: string;
|
|
18
|
+
maxDropRatio?: number;
|
|
19
|
+
maxLagP95Ms?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BundleAnalyzeReport {
|
|
23
|
+
mode: "bundle";
|
|
24
|
+
projectRoot: string;
|
|
25
|
+
outDir: string;
|
|
26
|
+
manifestPath: string;
|
|
27
|
+
manifestFound: boolean;
|
|
28
|
+
routeCount: number;
|
|
29
|
+
chunkCount: number;
|
|
30
|
+
note: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CacheAnalyzeReport {
|
|
34
|
+
mode: "cache";
|
|
35
|
+
projectRoot: string;
|
|
36
|
+
inputPath: string;
|
|
37
|
+
entries: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface RealtimeAnalyzeReport {
|
|
41
|
+
mode: "realtime";
|
|
42
|
+
projectRoot: string;
|
|
43
|
+
inputPath: string;
|
|
44
|
+
received: number;
|
|
45
|
+
delivered: number;
|
|
46
|
+
dropped: number;
|
|
47
|
+
lagP95Ms: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface QueryAnalyzeReport {
|
|
51
|
+
mode: "query";
|
|
52
|
+
projectRoot: string;
|
|
53
|
+
inputPath: string;
|
|
54
|
+
samples: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface GuardrailsAnalyzeReport {
|
|
58
|
+
mode: "guardrails";
|
|
59
|
+
projectRoot: string;
|
|
60
|
+
inputPath: string;
|
|
61
|
+
status: "ok" | "warn" | "error";
|
|
62
|
+
issues: readonly GuardrailIssue[];
|
|
63
|
+
summary: {
|
|
64
|
+
total: number;
|
|
65
|
+
warnings: number;
|
|
66
|
+
errors: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type AnalyzeReport =
|
|
71
|
+
| BundleAnalyzeReport
|
|
72
|
+
| CacheAnalyzeReport
|
|
73
|
+
| RealtimeAnalyzeReport
|
|
74
|
+
| QueryAnalyzeReport
|
|
75
|
+
| GuardrailsAnalyzeReport;
|
|
76
|
+
|
|
77
|
+
function parseAnalyzeMode(value: string): AnalyzeMode | undefined {
|
|
78
|
+
if (
|
|
79
|
+
value === "bundle" ||
|
|
80
|
+
value === "cache" ||
|
|
81
|
+
value === "realtime" ||
|
|
82
|
+
value === "query" ||
|
|
83
|
+
value === "guardrails"
|
|
84
|
+
) {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseNonNegativeNumber(raw: string | undefined, flag: string): number | CommandResult {
|
|
92
|
+
if (!raw) {
|
|
93
|
+
return {
|
|
94
|
+
exitCode: 2,
|
|
95
|
+
stderr: `aurora analyze: ${flag} requires a numeric value`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const parsed = Number(raw);
|
|
100
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
101
|
+
return {
|
|
102
|
+
exitCode: 2,
|
|
103
|
+
stderr: `aurora analyze: ${flag} must be a non-negative number`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseAnalyzeOptions(args: ReadonlyArray<string>): AnalyzeOptions | CommandResult {
|
|
111
|
+
const options: AnalyzeOptions = {
|
|
112
|
+
format: "text",
|
|
113
|
+
mode: "bundle",
|
|
114
|
+
outDir: ".aurora/analyze/compiler",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
let explicitOutDir = false;
|
|
118
|
+
let explicitInput = false;
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
121
|
+
const arg = args[i];
|
|
122
|
+
if (arg === "--format") {
|
|
123
|
+
const value = args[i + 1];
|
|
124
|
+
if (!value) {
|
|
125
|
+
return {
|
|
126
|
+
exitCode: 2,
|
|
127
|
+
stderr: "aurora analyze: --format requires 'text' or 'json'",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (value !== "text" && value !== "json") {
|
|
132
|
+
return {
|
|
133
|
+
exitCode: 2,
|
|
134
|
+
stderr: `aurora analyze: invalid format '${value}'. Expected 'text' or 'json'`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
options.format = value;
|
|
139
|
+
i += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (arg === "--mode") {
|
|
144
|
+
const value = args[i + 1];
|
|
145
|
+
if (!value) {
|
|
146
|
+
return {
|
|
147
|
+
exitCode: 2,
|
|
148
|
+
stderr:
|
|
149
|
+
"aurora analyze: --mode requires 'bundle', 'cache', 'realtime', 'query', or 'guardrails'",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const parsedMode = parseAnalyzeMode(value);
|
|
154
|
+
if (!parsedMode) {
|
|
155
|
+
return {
|
|
156
|
+
exitCode: 2,
|
|
157
|
+
stderr:
|
|
158
|
+
`aurora analyze: invalid mode '${value}'. Expected 'bundle', ` +
|
|
159
|
+
"'cache', 'realtime', 'query', or 'guardrails'",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
options.mode = parsedMode;
|
|
164
|
+
i += 1;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (arg === "--out-dir") {
|
|
169
|
+
const value = args[i + 1];
|
|
170
|
+
if (!value) {
|
|
171
|
+
return {
|
|
172
|
+
exitCode: 2,
|
|
173
|
+
stderr: "aurora analyze: --out-dir requires a path value",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
explicitOutDir = true;
|
|
178
|
+
options.outDir = value;
|
|
179
|
+
i += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (arg === "--input") {
|
|
184
|
+
const value = args[i + 1];
|
|
185
|
+
if (!value) {
|
|
186
|
+
return {
|
|
187
|
+
exitCode: 2,
|
|
188
|
+
stderr: "aurora analyze: --input requires a path value",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
explicitInput = true;
|
|
193
|
+
options.inputPath = value;
|
|
194
|
+
i += 1;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (arg === "--max-drop-ratio") {
|
|
199
|
+
const parsed = parseNonNegativeNumber(args[i + 1], "--max-drop-ratio");
|
|
200
|
+
if (typeof parsed !== "number") {
|
|
201
|
+
return parsed;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
options.maxDropRatio = parsed;
|
|
205
|
+
i += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (arg === "--max-lag-p95-ms") {
|
|
210
|
+
const parsed = parseNonNegativeNumber(args[i + 1], "--max-lag-p95-ms");
|
|
211
|
+
if (typeof parsed !== "number") {
|
|
212
|
+
return parsed;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
options.maxLagP95Ms = parsed;
|
|
216
|
+
i += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
exitCode: 2,
|
|
222
|
+
stderr: `aurora analyze: unknown option '${arg}'`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (options.mode === "bundle" && explicitInput) {
|
|
227
|
+
return {
|
|
228
|
+
exitCode: 2,
|
|
229
|
+
stderr:
|
|
230
|
+
"aurora analyze: --input is only valid when --mode is cache, realtime, query, or guardrails",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.mode !== "bundle" && explicitOutDir) {
|
|
235
|
+
return {
|
|
236
|
+
exitCode: 2,
|
|
237
|
+
stderr: "aurora analyze: --out-dir is only valid when --mode is bundle",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (options.mode !== "bundle" && !options.inputPath) {
|
|
242
|
+
return {
|
|
243
|
+
exitCode: 2,
|
|
244
|
+
stderr:
|
|
245
|
+
"aurora analyze: --input is required when --mode is cache, realtime, query, or guardrails",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
options.mode !== "realtime" &&
|
|
251
|
+
(options.maxDropRatio !== undefined || options.maxLagP95Ms !== undefined)
|
|
252
|
+
) {
|
|
253
|
+
return {
|
|
254
|
+
exitCode: 2,
|
|
255
|
+
stderr:
|
|
256
|
+
"aurora analyze: --max-drop-ratio and --max-lag-p95-ms are only valid when --mode is realtime",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return options;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function toRecord(value: unknown): Record<string, unknown> | undefined {
|
|
264
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return value as Record<string, unknown>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getRequiredString(source: Record<string, unknown>, fieldNames: ReadonlyArray<string>): string {
|
|
272
|
+
for (const fieldName of fieldNames) {
|
|
273
|
+
const value = source[fieldName];
|
|
274
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
275
|
+
return value.trim();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw new Error(`missing required string field (${fieldNames.join(" or ")})`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getRequiredNumber(source: Record<string, unknown>, fieldNames: ReadonlyArray<string>): number {
|
|
283
|
+
for (const fieldName of fieldNames) {
|
|
284
|
+
const value = source[fieldName];
|
|
285
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
throw new Error(`missing required numeric field (${fieldNames.join(" or ")})`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function assertSupportedSchemaVersion(payload: unknown, mode: Exclude<AnalyzeMode, "bundle">): void {
|
|
294
|
+
const source = toRecord(payload);
|
|
295
|
+
if (!source) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const rawSchemaVersion = source.schemaVersion;
|
|
300
|
+
if (rawSchemaVersion === undefined) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (rawSchemaVersion !== 1) {
|
|
305
|
+
throw new Error(`unsupported schemaVersion ${String(rawSchemaVersion)} for ${mode} input; expected 1`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function readInputJson(inputPath: string, context: CommandContext): unknown | CommandResult {
|
|
310
|
+
const absolutePath = resolve(context.cwd, inputPath);
|
|
311
|
+
let raw = "";
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
raw = readFileSync(absolutePath, "utf8");
|
|
315
|
+
} catch {
|
|
316
|
+
return {
|
|
317
|
+
exitCode: 1,
|
|
318
|
+
stderr: `aurora analyze: unable to read input at ${inputPath}`,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
return JSON.parse(raw);
|
|
324
|
+
} catch {
|
|
325
|
+
return {
|
|
326
|
+
exitCode: 1,
|
|
327
|
+
stderr: `aurora analyze: invalid json input at ${inputPath}`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeCacheEntries(payload: unknown): unknown[] {
|
|
333
|
+
if (Array.isArray(payload)) {
|
|
334
|
+
return payload;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const record = toRecord(payload);
|
|
338
|
+
if (record && Array.isArray(record.entries)) {
|
|
339
|
+
return record.entries;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
throw new Error("cache input must be an array or an object with entries[]");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeCacheStatus(rawStatus: unknown, rawHitValue: unknown): "hit" | "miss" | "stale" {
|
|
346
|
+
if (typeof rawStatus === "string") {
|
|
347
|
+
if (rawStatus === "hit" || rawStatus === "miss" || rawStatus === "stale") {
|
|
348
|
+
return rawStatus;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (typeof rawHitValue === "boolean") {
|
|
353
|
+
return rawHitValue ? "hit" : "miss";
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
throw new Error("missing required cache status field (cacheStatus/cache/status or hit)");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function normalizeQuerySamples(payload: unknown): unknown[] {
|
|
360
|
+
if (Array.isArray(payload)) {
|
|
361
|
+
return payload;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const record = toRecord(payload);
|
|
365
|
+
if (record && Array.isArray(record.samples)) {
|
|
366
|
+
return record.samples;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
throw new Error("query input must be an array or an object with samples[]");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function countManifestRoutes(source: Record<string, unknown>): number {
|
|
373
|
+
if (Array.isArray(source.routes)) {
|
|
374
|
+
return source.routes.length;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const routeGraph = toRecord(source.routeGraph);
|
|
378
|
+
if (routeGraph && Array.isArray(routeGraph.routes)) {
|
|
379
|
+
return routeGraph.routes.length;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function countManifestChunks(source: Record<string, unknown>): number {
|
|
386
|
+
if (Array.isArray(source.chunks)) {
|
|
387
|
+
return source.chunks.length;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const bundle = toRecord(source.bundle);
|
|
391
|
+
if (bundle && Array.isArray(bundle.chunks)) {
|
|
392
|
+
return bundle.chunks.length;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function runBundleAnalyze(options: AnalyzeOptions, context: CommandContext): BundleAnalyzeReport {
|
|
399
|
+
const manifestPath = resolve(context.cwd, options.outDir, "manifest.json");
|
|
400
|
+
let manifestFound = false;
|
|
401
|
+
let routeCount = 0;
|
|
402
|
+
let chunkCount = 0;
|
|
403
|
+
let note = "manifest not found; reporting configured out-dir only";
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const rawManifest = readFileSync(manifestPath, "utf8");
|
|
407
|
+
const parsed = JSON.parse(rawManifest);
|
|
408
|
+
const source = toRecord(parsed);
|
|
409
|
+
if (!source) {
|
|
410
|
+
throw new Error("manifest.json must contain a JSON object");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
manifestFound = true;
|
|
414
|
+
routeCount = countManifestRoutes(source);
|
|
415
|
+
chunkCount = countManifestChunks(source);
|
|
416
|
+
note = "manifest loaded";
|
|
417
|
+
} catch (error) {
|
|
418
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
419
|
+
const missingFile = message.includes("ENOENT");
|
|
420
|
+
if (!missingFile) {
|
|
421
|
+
throw new Error(`invalid bundle manifest at ${manifestPath}\n${message}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
mode: "bundle",
|
|
427
|
+
projectRoot: context.cwd,
|
|
428
|
+
outDir: options.outDir,
|
|
429
|
+
manifestPath,
|
|
430
|
+
manifestFound,
|
|
431
|
+
routeCount,
|
|
432
|
+
chunkCount,
|
|
433
|
+
note,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function runCacheAnalyze(options: AnalyzeOptions, payload: unknown, context: CommandContext): CacheAnalyzeReport {
|
|
438
|
+
assertSupportedSchemaVersion(payload, "cache");
|
|
439
|
+
const entries = normalizeCacheEntries(payload);
|
|
440
|
+
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
const source = toRecord(entry);
|
|
443
|
+
if (!source) {
|
|
444
|
+
throw new Error("cache source entry must be an object");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
getRequiredString(source, ["keyHash", "hash"]);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
mode: "cache",
|
|
452
|
+
projectRoot: context.cwd,
|
|
453
|
+
inputPath: options.inputPath ?? "",
|
|
454
|
+
entries: entries.length,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function runRealtimeAnalyze(
|
|
459
|
+
options: AnalyzeOptions,
|
|
460
|
+
payload: unknown,
|
|
461
|
+
context: CommandContext,
|
|
462
|
+
): RealtimeAnalyzeReport {
|
|
463
|
+
assertSupportedSchemaVersion(payload, "realtime");
|
|
464
|
+
const source = toRecord(payload);
|
|
465
|
+
if (!source) {
|
|
466
|
+
throw new Error("realtime input must be an object");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const received = getRequiredNumber(source, ["received", "rx", "totalReceived"]);
|
|
470
|
+
const delivered = getRequiredNumber(source, ["delivered", "tx", "totalDelivered"]);
|
|
471
|
+
const dropped = getRequiredNumber(source, ["dropped", "totalDropped"]);
|
|
472
|
+
const lagP95Ms = getRequiredNumber(source, ["lagP95Ms", "p95"]);
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
mode: "realtime",
|
|
476
|
+
projectRoot: context.cwd,
|
|
477
|
+
inputPath: options.inputPath ?? "",
|
|
478
|
+
received,
|
|
479
|
+
delivered,
|
|
480
|
+
dropped,
|
|
481
|
+
lagP95Ms,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function runQueryAnalyze(options: AnalyzeOptions, payload: unknown, context: CommandContext): QueryAnalyzeReport {
|
|
486
|
+
assertSupportedSchemaVersion(payload, "query");
|
|
487
|
+
const samples = normalizeQuerySamples(payload);
|
|
488
|
+
|
|
489
|
+
for (const sample of samples) {
|
|
490
|
+
const source = toRecord(sample);
|
|
491
|
+
if (!source) {
|
|
492
|
+
throw new Error("query sample must be an object");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
getRequiredString(source, ["queryName", "name", "query"]);
|
|
496
|
+
getRequiredString(source, ["keyHash", "key", "hash"]);
|
|
497
|
+
getRequiredNumber(source, ["durationMs", "duration", "ms"]);
|
|
498
|
+
normalizeCacheStatus(source.cacheStatus ?? source.cache ?? source.status, source.hit);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
mode: "query",
|
|
503
|
+
projectRoot: context.cwd,
|
|
504
|
+
inputPath: options.inputPath ?? "",
|
|
505
|
+
samples: samples.length,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function runGuardrailsAnalyze(
|
|
510
|
+
options: AnalyzeOptions,
|
|
511
|
+
payload: unknown,
|
|
512
|
+
context: CommandContext,
|
|
513
|
+
): GuardrailsAnalyzeReport {
|
|
514
|
+
assertSupportedSchemaVersion(payload, "guardrails");
|
|
515
|
+
const source = toRecord(payload);
|
|
516
|
+
if (!source) {
|
|
517
|
+
throw new Error("guardrails input must be an object");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const guardrails = evaluatePerformanceGuardrails(source as PerformanceGuardrailsInput);
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
mode: "guardrails",
|
|
524
|
+
projectRoot: context.cwd,
|
|
525
|
+
inputPath: options.inputPath ?? "",
|
|
526
|
+
status: guardrails.status,
|
|
527
|
+
issues: guardrails.issues,
|
|
528
|
+
summary: guardrails.summary,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function renderAnalyzeTextReport(report: AnalyzeReport): string {
|
|
533
|
+
if (report.mode === "bundle") {
|
|
534
|
+
return [
|
|
535
|
+
"aurora analyze bundle report",
|
|
536
|
+
`project_root: ${report.projectRoot}`,
|
|
537
|
+
`out_dir: ${report.outDir}`,
|
|
538
|
+
`manifest_path: ${report.manifestPath}`,
|
|
539
|
+
`manifest_found: ${report.manifestFound}`,
|
|
540
|
+
`routes: ${report.routeCount}`,
|
|
541
|
+
`chunks: ${report.chunkCount}`,
|
|
542
|
+
`note: ${report.note}`,
|
|
543
|
+
].join("\n");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (report.mode === "cache") {
|
|
547
|
+
return [
|
|
548
|
+
"aurora analyze cache report",
|
|
549
|
+
`project_root: ${report.projectRoot}`,
|
|
550
|
+
`input_path: ${report.inputPath}`,
|
|
551
|
+
`entries: ${report.entries}`,
|
|
552
|
+
].join("\n");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (report.mode === "realtime") {
|
|
556
|
+
return [
|
|
557
|
+
"aurora analyze realtime report",
|
|
558
|
+
`project_root: ${report.projectRoot}`,
|
|
559
|
+
`input_path: ${report.inputPath}`,
|
|
560
|
+
`received: ${report.received}`,
|
|
561
|
+
`delivered: ${report.delivered}`,
|
|
562
|
+
`dropped: ${report.dropped}`,
|
|
563
|
+
`lag_p95_ms: ${report.lagP95Ms}`,
|
|
564
|
+
].join("\n");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (report.mode === "guardrails") {
|
|
568
|
+
const lines = [
|
|
569
|
+
"aurora analyze guardrails report",
|
|
570
|
+
`project_root: ${report.projectRoot}`,
|
|
571
|
+
`input_path: ${report.inputPath}`,
|
|
572
|
+
`status: ${report.status}`,
|
|
573
|
+
`issues: ${report.summary.total}`,
|
|
574
|
+
`warnings: ${report.summary.warnings}`,
|
|
575
|
+
`errors: ${report.summary.errors}`,
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
if (report.issues.length > 0) {
|
|
579
|
+
lines.push("findings:");
|
|
580
|
+
for (const issue of report.issues) {
|
|
581
|
+
const location = issue.location?.file
|
|
582
|
+
? `${issue.location.file}${issue.location.line ? `:${issue.location.line}` : ""}`
|
|
583
|
+
: "unknown";
|
|
584
|
+
lines.push(`- [${issue.severity}] ${issue.code} ${location} ${issue.message}`);
|
|
585
|
+
lines.push(` fix: ${issue.remediation}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return lines.join("\n");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return [
|
|
593
|
+
"aurora analyze query report",
|
|
594
|
+
`project_root: ${report.projectRoot}`,
|
|
595
|
+
`input_path: ${report.inputPath}`,
|
|
596
|
+
`samples: ${report.samples}`,
|
|
597
|
+
].join("\n");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function formatAnalyzeModeError(options: AnalyzeOptions, error: unknown): CommandResult {
|
|
601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
602
|
+
return {
|
|
603
|
+
exitCode: 1,
|
|
604
|
+
stderr: `aurora analyze: ${options.mode} mode failed\n${message}`,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function isCommandResult(value: unknown): value is CommandResult {
|
|
609
|
+
const record = toRecord(value);
|
|
610
|
+
if (!record) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return (
|
|
615
|
+
typeof record.exitCode === "number" &&
|
|
616
|
+
(typeof record.stdout === "string" || typeof record.stderr === "string")
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function runAnalyzeCommand(args: ReadonlyArray<string>, context: CommandContext): CommandResult {
|
|
621
|
+
const options = parseAnalyzeOptions(args);
|
|
622
|
+
if ("exitCode" in options) {
|
|
623
|
+
return options;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let report: AnalyzeReport | CommandResult;
|
|
627
|
+
if (options.mode === "bundle") {
|
|
628
|
+
try {
|
|
629
|
+
report = runBundleAnalyze(options, context);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
return formatAnalyzeModeError(options, error);
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
const payload = readInputJson(options.inputPath ?? "", context);
|
|
635
|
+
if (isCommandResult(payload)) {
|
|
636
|
+
return payload;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
if (options.mode === "cache") {
|
|
641
|
+
report = runCacheAnalyze(options, payload, context);
|
|
642
|
+
} else if (options.mode === "realtime") {
|
|
643
|
+
report = runRealtimeAnalyze(options, payload, context);
|
|
644
|
+
} else if (options.mode === "guardrails") {
|
|
645
|
+
report = runGuardrailsAnalyze(options, payload, context);
|
|
646
|
+
} else {
|
|
647
|
+
report = runQueryAnalyze(options, payload, context);
|
|
648
|
+
}
|
|
649
|
+
} catch (error) {
|
|
650
|
+
return formatAnalyzeModeError(options, error);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (isCommandResult(report)) {
|
|
655
|
+
return report;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (options.format === "json") {
|
|
659
|
+
return {
|
|
660
|
+
exitCode: 0,
|
|
661
|
+
stdout: JSON.stringify(report, null, 2),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
exitCode: 0,
|
|
667
|
+
stdout: renderAnalyzeTextReport(report),
|
|
668
|
+
};
|
|
669
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { runAuroraCli } from "../run";
|
|
4
|
+
|
|
5
|
+
const result = runAuroraCli(process.argv.slice(2));
|
|
6
|
+
|
|
7
|
+
if (result.stdout) {
|
|
8
|
+
process.stdout.write(`${result.stdout}\n`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (result.stderr) {
|
|
12
|
+
process.stderr.write(`${result.stderr}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
process.exitCode = result.exitCode;
|