@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
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
scaffoldRoute,
|
|
6
|
+
type RouteDataSource,
|
|
7
|
+
type RouteKind,
|
|
8
|
+
} from "./create-route";
|
|
9
|
+
import { type CommandContext, type CommandResult } from "./registry";
|
|
10
|
+
|
|
11
|
+
const FEATURE_NAME_PLACEHOLDER = "__AURORA_FEATURE_NAME__";
|
|
12
|
+
const FEATURE_SLUG_PLACEHOLDER = "__AURORA_FEATURE_SLUG__";
|
|
13
|
+
const ROUTE_KINDS: readonly RouteKind[] = [
|
|
14
|
+
"crud",
|
|
15
|
+
"read-only",
|
|
16
|
+
"form",
|
|
17
|
+
"dashboard",
|
|
18
|
+
"empty",
|
|
19
|
+
];
|
|
20
|
+
const ROUTE_DATA_SOURCES: readonly RouteDataSource[] = ["database", "api", "none"];
|
|
21
|
+
|
|
22
|
+
interface CreateFeatureOptions {
|
|
23
|
+
rawName: string;
|
|
24
|
+
featureSlug: string;
|
|
25
|
+
featureName: string;
|
|
26
|
+
routeEnabled: boolean;
|
|
27
|
+
routeKind: RouteKind;
|
|
28
|
+
routeDataSource: RouteDataSource;
|
|
29
|
+
routeAuth: string;
|
|
30
|
+
routeRealtime: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CreateFeatureReport {
|
|
34
|
+
mode: "create-feature";
|
|
35
|
+
cwd: string;
|
|
36
|
+
featureSlug: string;
|
|
37
|
+
featureName: string;
|
|
38
|
+
featureRoot: string;
|
|
39
|
+
createdFileCount: number;
|
|
40
|
+
routeEnabled: boolean;
|
|
41
|
+
routeKind: RouteKind;
|
|
42
|
+
routeDataSource: RouteDataSource;
|
|
43
|
+
routeAuth: string;
|
|
44
|
+
routeRealtime: boolean;
|
|
45
|
+
routeRoot?: string;
|
|
46
|
+
routeCreatedFileCount: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseCreateFeatureOptions(
|
|
50
|
+
args: ReadonlyArray<string>,
|
|
51
|
+
): CreateFeatureOptions | CommandResult {
|
|
52
|
+
if (args.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
exitCode: 2,
|
|
55
|
+
stderr: "aurora create-feature: feature name is required",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const firstArg = args[0];
|
|
60
|
+
if (!firstArg || firstArg.startsWith("-")) {
|
|
61
|
+
return {
|
|
62
|
+
exitCode: 2,
|
|
63
|
+
stderr: "aurora create-feature: feature name must be provided before options",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const slug = toFeatureSlug(firstArg);
|
|
68
|
+
if (!slug) {
|
|
69
|
+
return {
|
|
70
|
+
exitCode: 2,
|
|
71
|
+
stderr:
|
|
72
|
+
"aurora create-feature: feature name must include at least one letter or number",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const options: CreateFeatureOptions = {
|
|
77
|
+
rawName: firstArg,
|
|
78
|
+
featureSlug: slug,
|
|
79
|
+
featureName: toFeatureName(slug),
|
|
80
|
+
routeEnabled: true,
|
|
81
|
+
routeKind: "form",
|
|
82
|
+
routeDataSource: "database",
|
|
83
|
+
routeAuth: "public",
|
|
84
|
+
routeRealtime: false,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
88
|
+
const arg = args[i];
|
|
89
|
+
|
|
90
|
+
if (arg === "--kind") {
|
|
91
|
+
const value = args[i + 1];
|
|
92
|
+
if (!value || !isRouteKind(value)) {
|
|
93
|
+
return {
|
|
94
|
+
exitCode: 2,
|
|
95
|
+
stderr:
|
|
96
|
+
`aurora create-feature: --kind requires one of ${ROUTE_KINDS.join("|")}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
options.routeKind = value;
|
|
101
|
+
i += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (arg === "--data") {
|
|
106
|
+
const value = args[i + 1];
|
|
107
|
+
if (!value || !isRouteDataSource(value)) {
|
|
108
|
+
return {
|
|
109
|
+
exitCode: 2,
|
|
110
|
+
stderr:
|
|
111
|
+
`aurora create-feature: --data requires one of ${ROUTE_DATA_SOURCES.join("|")}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
options.routeDataSource = value;
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (arg === "--auth") {
|
|
121
|
+
const value = args[i + 1];
|
|
122
|
+
if (!value) {
|
|
123
|
+
return {
|
|
124
|
+
exitCode: 2,
|
|
125
|
+
stderr:
|
|
126
|
+
"aurora create-feature: --auth requires 'public', 'user', 'admin', or 'permission:<name>'",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const parsedAuth = parseAuthMode(value);
|
|
131
|
+
if (!parsedAuth) {
|
|
132
|
+
return {
|
|
133
|
+
exitCode: 2,
|
|
134
|
+
stderr:
|
|
135
|
+
"aurora create-feature: --auth requires 'public', 'user', 'admin', or 'permission:<name>'",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
options.routeAuth = parsedAuth;
|
|
140
|
+
i += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (arg === "--route") {
|
|
145
|
+
options.routeEnabled = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (arg === "--no-route") {
|
|
150
|
+
options.routeEnabled = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (arg === "--realtime") {
|
|
155
|
+
options.routeRealtime = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (arg === "--no-realtime") {
|
|
160
|
+
options.routeRealtime = false;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
exitCode: 2,
|
|
166
|
+
stderr: `aurora create-feature: unknown option '${arg}'`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return options;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isRouteKind(value: string): value is RouteKind {
|
|
174
|
+
return ROUTE_KINDS.includes(value as RouteKind);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isRouteDataSource(value: string): value is RouteDataSource {
|
|
178
|
+
return ROUTE_DATA_SOURCES.includes(value as RouteDataSource);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseAuthMode(value: string): string | undefined {
|
|
182
|
+
const normalized = value.trim();
|
|
183
|
+
if (normalized === "public" || normalized === "user" || normalized === "admin") {
|
|
184
|
+
return normalized;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (/^permission:[a-z0-9:_-]+$/i.test(normalized)) {
|
|
188
|
+
return normalized;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function toFeatureSlug(rawName: string): string {
|
|
195
|
+
return rawName
|
|
196
|
+
.trim()
|
|
197
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
198
|
+
.replace(/^-+|-+$/g, "")
|
|
199
|
+
.replace(/-+/g, "-")
|
|
200
|
+
.toLowerCase();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function toFeatureName(slug: string): string {
|
|
204
|
+
const pascal = slug
|
|
205
|
+
.split("-")
|
|
206
|
+
.filter((part) => part.length > 0)
|
|
207
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
208
|
+
.join("");
|
|
209
|
+
|
|
210
|
+
if (/^[A-Za-z_]/.test(pascal)) {
|
|
211
|
+
return pascal;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return `Feature${pascal}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isNonEmptyDirectory(path: string): boolean {
|
|
218
|
+
if (!existsSync(path)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
return readdirSync(path).length > 0;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function collectTemplateFiles(
|
|
230
|
+
templateRoot: string,
|
|
231
|
+
currentDirectory: string,
|
|
232
|
+
featureName: string,
|
|
233
|
+
featureSlug: string,
|
|
234
|
+
): ReadonlyArray<{ path: string; content: string }> {
|
|
235
|
+
const files: Array<{ path: string; content: string }> = [];
|
|
236
|
+
const entries = readdirSync(currentDirectory, { withFileTypes: true }).sort((left, right) =>
|
|
237
|
+
left.name.localeCompare(right.name),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
const absolutePath = join(currentDirectory, entry.name);
|
|
242
|
+
if (entry.isDirectory()) {
|
|
243
|
+
files.push(
|
|
244
|
+
...collectTemplateFiles(
|
|
245
|
+
templateRoot,
|
|
246
|
+
absolutePath,
|
|
247
|
+
featureName,
|
|
248
|
+
featureSlug,
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!entry.isFile()) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const relativePath = absolutePath
|
|
259
|
+
.slice(templateRoot.length + 1)
|
|
260
|
+
.replaceAll("\\", "/");
|
|
261
|
+
const source = readFileSync(absolutePath, "utf8");
|
|
262
|
+
const withFeatureName = source.split(FEATURE_NAME_PLACEHOLDER).join(featureName);
|
|
263
|
+
const withFeatureSlug = withFeatureName
|
|
264
|
+
.split(FEATURE_SLUG_PLACEHOLDER)
|
|
265
|
+
.join(featureSlug);
|
|
266
|
+
|
|
267
|
+
files.push({
|
|
268
|
+
path: relativePath,
|
|
269
|
+
content: withFeatureSlug,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return files;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function loadFeatureSkeletonTemplate(
|
|
277
|
+
featureName: string,
|
|
278
|
+
featureSlug: string,
|
|
279
|
+
): ReadonlyArray<{ path: string; content: string }> | CommandResult {
|
|
280
|
+
const templateRoot = resolve(import.meta.dir, "../templates/feature-skeleton");
|
|
281
|
+
if (!existsSync(templateRoot)) {
|
|
282
|
+
return {
|
|
283
|
+
exitCode: 1,
|
|
284
|
+
stderr: `aurora create-feature: missing template directory ${templateRoot}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return collectTemplateFiles(templateRoot, templateRoot, featureName, featureSlug);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function scaffoldFeature(
|
|
292
|
+
options: CreateFeatureOptions,
|
|
293
|
+
context: CommandContext,
|
|
294
|
+
): CreateFeatureReport | CommandResult {
|
|
295
|
+
const featureRoot = resolve(context.cwd, "src/features", options.featureSlug);
|
|
296
|
+
if (isNonEmptyDirectory(featureRoot)) {
|
|
297
|
+
return {
|
|
298
|
+
exitCode: 2,
|
|
299
|
+
stderr: `aurora create-feature: target directory already exists and is not empty: ${featureRoot}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const templateFiles = loadFeatureSkeletonTemplate(
|
|
304
|
+
options.featureName,
|
|
305
|
+
options.featureSlug,
|
|
306
|
+
);
|
|
307
|
+
if ("exitCode" in templateFiles) {
|
|
308
|
+
return templateFiles;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const routeRoot = resolve(context.cwd, "src/routes", options.featureSlug);
|
|
312
|
+
if (options.routeEnabled && isNonEmptyDirectory(routeRoot)) {
|
|
313
|
+
return {
|
|
314
|
+
exitCode: 2,
|
|
315
|
+
stderr: `aurora create-feature: target directory already exists and is not empty: ${routeRoot}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
mkdirSync(featureRoot, { recursive: true });
|
|
321
|
+
|
|
322
|
+
for (const file of templateFiles) {
|
|
323
|
+
const absolutePath = join(featureRoot, file.path);
|
|
324
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
325
|
+
writeFileSync(absolutePath, `${file.content}\n`, "utf8");
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
329
|
+
return {
|
|
330
|
+
exitCode: 1,
|
|
331
|
+
stderr: `aurora create-feature: failed to scaffold feature at ${featureRoot}: ${message}`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let routeCreatedFileCount = 0;
|
|
336
|
+
let finalRouteRoot: string | undefined;
|
|
337
|
+
|
|
338
|
+
if (options.routeEnabled) {
|
|
339
|
+
const routeReport = scaffoldRoute(
|
|
340
|
+
{
|
|
341
|
+
routeSlug: options.featureSlug,
|
|
342
|
+
routeName: options.featureName,
|
|
343
|
+
kind: options.routeKind,
|
|
344
|
+
dataSource: options.routeDataSource,
|
|
345
|
+
auth: options.routeAuth,
|
|
346
|
+
realtime: options.routeRealtime,
|
|
347
|
+
},
|
|
348
|
+
context,
|
|
349
|
+
{ commandName: "aurora create-feature" },
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if ("exitCode" in routeReport) {
|
|
353
|
+
return routeReport;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
routeCreatedFileCount = routeReport.createdFileCount;
|
|
357
|
+
finalRouteRoot = routeReport.routeRoot;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
mode: "create-feature",
|
|
362
|
+
cwd: context.cwd,
|
|
363
|
+
featureSlug: options.featureSlug,
|
|
364
|
+
featureName: options.featureName,
|
|
365
|
+
featureRoot,
|
|
366
|
+
createdFileCount: templateFiles.length + routeCreatedFileCount,
|
|
367
|
+
routeEnabled: options.routeEnabled,
|
|
368
|
+
routeKind: options.routeKind,
|
|
369
|
+
routeDataSource: options.routeDataSource,
|
|
370
|
+
routeAuth: options.routeAuth,
|
|
371
|
+
routeRealtime: options.routeRealtime,
|
|
372
|
+
routeRoot: finalRouteRoot,
|
|
373
|
+
routeCreatedFileCount,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renderCreateFeatureReport(report: CreateFeatureReport): string {
|
|
378
|
+
const lines = [
|
|
379
|
+
"aurora create-feature bootstrap",
|
|
380
|
+
`cwd: ${report.cwd}`,
|
|
381
|
+
`feature_slug: ${report.featureSlug}`,
|
|
382
|
+
`feature_name: ${report.featureName}`,
|
|
383
|
+
`feature_root: ${report.featureRoot}`,
|
|
384
|
+
`route_scaffold: ${report.routeEnabled ? "enabled" : "disabled"}`,
|
|
385
|
+
`route_kind: ${report.routeKind}`,
|
|
386
|
+
`route_data_source: ${report.routeDataSource}`,
|
|
387
|
+
`route_auth: ${report.routeAuth}`,
|
|
388
|
+
`route_realtime: ${report.routeRealtime ? "enabled" : "disabled"}`,
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
if (report.routeRoot) {
|
|
392
|
+
lines.push(`route_root: ${report.routeRoot}`);
|
|
393
|
+
lines.push(`route_created_files: ${report.routeCreatedFileCount}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
lines.push(`created_files: ${report.createdFileCount}`);
|
|
397
|
+
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function runCreateFeatureCommand(
|
|
402
|
+
args: ReadonlyArray<string>,
|
|
403
|
+
context: CommandContext,
|
|
404
|
+
): CommandResult {
|
|
405
|
+
const options = parseCreateFeatureOptions(args);
|
|
406
|
+
if ("exitCode" in options) {
|
|
407
|
+
return options;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const report = scaffoldFeature(options, context);
|
|
411
|
+
if ("exitCode" in report) {
|
|
412
|
+
return report;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
exitCode: 0,
|
|
417
|
+
stdout: renderCreateFeatureReport(report),
|
|
418
|
+
};
|
|
419
|
+
}
|