alepha 0.15.2 → 0.15.3
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 +68 -80
- package/dist/api/audits/index.d.ts +332 -332
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts +170 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/jobs/index.d.ts +151 -151
- package/dist/api/keys/index.d.ts +195 -195
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +260 -260
- package/dist/api/users/index.d.ts +22 -11
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +7 -2
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +7 -2
- package/dist/bucket/index.js.map +1 -1
- package/dist/cli/index.d.ts +191 -74
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +215 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +10 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +67 -13
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +28 -21
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +28 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +28 -21
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.d.ts +8 -0
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +7 -2
- package/dist/email/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -5
- package/dist/orm/index.bun.js +32 -16
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +4 -1
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +34 -22
- package/dist/orm/index.js.map +1 -1
- package/dist/react/router/index.browser.js +9 -15
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +295 -407
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +566 -776
- package/dist/react/router/index.js.map +1 -1
- package/dist/redis/index.d.ts +19 -19
- package/dist/security/index.d.ts +42 -42
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -167
- package/dist/server/core/index.d.ts +9 -9
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/links/index.d.ts +39 -39
- package/dist/server/static/index.js +7 -2
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +8 -0
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +7 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +8 -0
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -2
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +734 -12
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +8 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +7 -2
- package/dist/system/index.js.map +1 -1
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +15 -7
- package/dist/vite/index.js.map +1 -1
- package/package.json +4 -2
- package/src/api/logs/TODO.md +13 -10
- package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +149 -32
- package/src/cli/commands/db.ts +5 -7
- package/src/cli/commands/init.spec.ts +50 -6
- package/src/cli/commands/init.ts +28 -5
- package/src/cli/providers/ViteDevServerProvider.ts +1 -10
- package/src/cli/services/AlephaCliUtils.ts +16 -0
- package/src/cli/services/PackageManagerUtils.ts +2 -0
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +28 -6
- package/src/cli/templates/agentMd.ts +6 -1
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +18 -4
- package/src/cli/templates/webAppRouterTs.ts +25 -1
- package/src/cli/templates/webHelloComponentTsx.ts +15 -5
- package/src/command/helpers/Runner.spec.ts +135 -0
- package/src/command/helpers/Runner.ts +4 -1
- package/src/command/providers/CliProvider.spec.ts +325 -0
- package/src/command/providers/CliProvider.ts +117 -7
- package/src/core/Alepha.ts +32 -25
- package/src/orm/index.bun.ts +1 -1
- package/src/orm/index.ts +2 -6
- package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/react/router/hooks/useActive.ts +1 -1
- package/src/react/router/hooks/useRouter.ts +1 -1
- package/src/react/router/index.ts +4 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
- package/src/react/router/primitives/$page.spec.tsx +0 -32
- package/src/react/router/primitives/$page.ts +6 -14
- package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
- package/src/react/router/providers/ReactPageProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.ts +7 -78
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
- package/src/react/router/services/ReactRouter.ts +13 -13
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
- package/src/security/providers/ServerSecurityProvider.ts +30 -22
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/system/index.browser.ts +25 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/system/providers/FileSystemProvider.ts +8 -0
- package/src/system/providers/NodeFileSystemProvider.ts +11 -2
- package/src/vite/tasks/buildServer.ts +2 -12
- package/src/vite/tasks/generateCloudflare.ts +10 -7
- package/src/vite/tasks/generateDocker.ts +4 -0
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
generateVercel,
|
|
13
13
|
prerenderPages,
|
|
14
14
|
} from "alepha/vite";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
type BuildRuntime,
|
|
17
|
+
type BuildTarget,
|
|
18
|
+
buildOptions,
|
|
19
|
+
} from "../atoms/buildOptions.ts";
|
|
16
20
|
import { AppEntryProvider } from "../providers/AppEntryProvider.ts";
|
|
17
21
|
import { ViteBuildProvider } from "../providers/ViteBuildProvider.ts";
|
|
18
22
|
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
@@ -29,6 +33,41 @@ export class BuildCommand {
|
|
|
29
33
|
protected readonly viteBuildProvider = $inject(ViteBuildProvider);
|
|
30
34
|
protected readonly options = $use(buildOptions);
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the effective runtime based on target and explicit runtime flag.
|
|
38
|
+
*
|
|
39
|
+
* Some targets force a specific runtime:
|
|
40
|
+
* - `cloudflare` always uses `workerd`
|
|
41
|
+
* - `vercel` always uses `node`
|
|
42
|
+
* - `docker` and bare deployments respect the runtime flag
|
|
43
|
+
*
|
|
44
|
+
* @throws {AlephaError} If an incompatible runtime is specified for a target
|
|
45
|
+
*/
|
|
46
|
+
protected resolveRuntime(
|
|
47
|
+
target: BuildTarget | undefined,
|
|
48
|
+
runtime: BuildRuntime | undefined,
|
|
49
|
+
): BuildRuntime {
|
|
50
|
+
if (target === "cloudflare") {
|
|
51
|
+
if (runtime && runtime !== "workerd") {
|
|
52
|
+
throw new AlephaError(
|
|
53
|
+
`Target 'cloudflare' requires 'workerd' runtime, got '${runtime}'`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return "workerd";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (target === "vercel") {
|
|
60
|
+
if (runtime && runtime !== "node") {
|
|
61
|
+
throw new AlephaError(
|
|
62
|
+
`Target 'vercel' requires 'node' runtime, got '${runtime}'`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return "node";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return runtime ?? "node";
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
public readonly build = $command({
|
|
33
72
|
name: "build",
|
|
34
73
|
mode: "production",
|
|
@@ -39,19 +78,23 @@ export class BuildCommand {
|
|
|
39
78
|
description: "Generate build stats report",
|
|
40
79
|
}),
|
|
41
80
|
),
|
|
42
|
-
|
|
43
|
-
t.
|
|
44
|
-
|
|
81
|
+
target: t.optional(
|
|
82
|
+
t.enum(["bare", "docker", "vercel", "cloudflare"], {
|
|
83
|
+
aliases: ["t"],
|
|
84
|
+
description: "Deployment target",
|
|
45
85
|
}),
|
|
46
86
|
),
|
|
47
|
-
|
|
48
|
-
t.
|
|
49
|
-
|
|
87
|
+
runtime: t.optional(
|
|
88
|
+
t.enum(["node", "bun", "workerd"], {
|
|
89
|
+
aliases: ["r"],
|
|
90
|
+
description: "JavaScript runtime",
|
|
50
91
|
}),
|
|
51
92
|
),
|
|
52
|
-
|
|
53
|
-
t.boolean({
|
|
54
|
-
|
|
93
|
+
image: t.optional(
|
|
94
|
+
t.union([t.boolean(), t.text()], {
|
|
95
|
+
aliases: ["i"],
|
|
96
|
+
description:
|
|
97
|
+
"Build Docker image. Use -i for latest, -i=<version> for specific version",
|
|
55
98
|
}),
|
|
56
99
|
),
|
|
57
100
|
sitemap: t.optional(
|
|
@@ -59,11 +102,6 @@ export class BuildCommand {
|
|
|
59
102
|
description: "Generate sitemap.xml with base URL",
|
|
60
103
|
}),
|
|
61
104
|
),
|
|
62
|
-
bun: t.optional(
|
|
63
|
-
t.boolean({
|
|
64
|
-
description: "Prioritize .bun.ts entry files for Bun runtime",
|
|
65
|
-
}),
|
|
66
|
-
),
|
|
67
105
|
}),
|
|
68
106
|
handler: async ({ flags, run, root }) => {
|
|
69
107
|
process.env.NODE_ENV = "production";
|
|
@@ -88,6 +126,22 @@ export class BuildCommand {
|
|
|
88
126
|
const options = this.options;
|
|
89
127
|
await this.utils.loadEnv(root, [".env", ".env.production"]);
|
|
90
128
|
|
|
129
|
+
// Resolve target and runtime
|
|
130
|
+
const target = flags.target ?? options.target;
|
|
131
|
+
const runtime = this.resolveRuntime(
|
|
132
|
+
target,
|
|
133
|
+
flags.runtime ?? options.runtime,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Validate --image requires --target=docker
|
|
137
|
+
if (flags.image && target !== "docker") {
|
|
138
|
+
throw new AlephaError(
|
|
139
|
+
`Flag '--image' requires '--target=docker', got '${target ?? "bare"}'`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.log.trace("Build configuration", { target, runtime });
|
|
144
|
+
|
|
91
145
|
const stats = flags.stats ?? options.stats ?? false;
|
|
92
146
|
let template = "";
|
|
93
147
|
let hasClient = false;
|
|
@@ -140,20 +194,11 @@ export class BuildCommand {
|
|
|
140
194
|
const clientIndexPath = `${distDir}/${publicDir}/index.html`;
|
|
141
195
|
const clientBuilt = await this.fs.exists(clientIndexPath);
|
|
142
196
|
|
|
197
|
+
// Set export conditions based on runtime
|
|
143
198
|
const conditions: string[] = [];
|
|
144
|
-
|
|
145
|
-
// bun:
|
|
146
|
-
// - alepha
|
|
147
|
-
// - react-dom
|
|
148
|
-
|
|
149
|
-
if (flags.bun) {
|
|
199
|
+
if (runtime === "bun") {
|
|
150
200
|
conditions.push("bun");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// workerd:
|
|
154
|
-
// - react-dom
|
|
155
|
-
// - postgres
|
|
156
|
-
if (options.cloudflare) {
|
|
201
|
+
} else if (runtime === "workerd") {
|
|
157
202
|
conditions.push("workerd");
|
|
158
203
|
}
|
|
159
204
|
|
|
@@ -204,8 +249,8 @@ export class BuildCommand {
|
|
|
204
249
|
});
|
|
205
250
|
}
|
|
206
251
|
|
|
207
|
-
// Generate deployment
|
|
208
|
-
if (
|
|
252
|
+
// Generate deployment configuration based on target
|
|
253
|
+
if (target === "vercel") {
|
|
209
254
|
await run({
|
|
210
255
|
name: "add Vercel config",
|
|
211
256
|
handler: () =>
|
|
@@ -217,7 +262,7 @@ export class BuildCommand {
|
|
|
217
262
|
});
|
|
218
263
|
}
|
|
219
264
|
|
|
220
|
-
if (
|
|
265
|
+
if (target === "cloudflare") {
|
|
221
266
|
await run({
|
|
222
267
|
name: "add Cloudflare config",
|
|
223
268
|
handler: () =>
|
|
@@ -228,15 +273,87 @@ export class BuildCommand {
|
|
|
228
273
|
});
|
|
229
274
|
}
|
|
230
275
|
|
|
231
|
-
if (
|
|
276
|
+
if (target === "docker") {
|
|
277
|
+
// Auto-configure Docker based on runtime
|
|
278
|
+
const dockerFrom =
|
|
279
|
+
options.docker?.from ??
|
|
280
|
+
(runtime === "bun" ? "oven/bun:alpine" : "node:24-alpine");
|
|
281
|
+
const dockerCommand =
|
|
282
|
+
options.docker?.command ?? (runtime === "bun" ? "bun" : "node");
|
|
283
|
+
|
|
232
284
|
await run({
|
|
233
285
|
name: "add Docker config",
|
|
234
286
|
handler: () =>
|
|
235
287
|
generateDocker({
|
|
236
288
|
distDir,
|
|
237
|
-
|
|
289
|
+
image: dockerFrom,
|
|
290
|
+
command: dockerCommand,
|
|
238
291
|
}),
|
|
239
292
|
});
|
|
293
|
+
|
|
294
|
+
// Build Docker image if --image flag is provided
|
|
295
|
+
if (flags.image) {
|
|
296
|
+
const imageConfig = options.docker?.image;
|
|
297
|
+
const flagValue =
|
|
298
|
+
typeof flags.image === "string" ? flags.image : null;
|
|
299
|
+
|
|
300
|
+
let imageTag: string;
|
|
301
|
+
let version: string;
|
|
302
|
+
|
|
303
|
+
if (!flagValue) {
|
|
304
|
+
// -i (no value) → use config tag:latest
|
|
305
|
+
if (!imageConfig?.tag) {
|
|
306
|
+
throw new AlephaError(
|
|
307
|
+
"Flag '--image' requires 'build.docker.image.tag' in config",
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
version = "latest";
|
|
311
|
+
imageTag = `${imageConfig.tag}:${version}`;
|
|
312
|
+
} else if (flagValue.startsWith(":")) {
|
|
313
|
+
// -i=:1.3.4 → version only, prepend config tag
|
|
314
|
+
if (!imageConfig?.tag) {
|
|
315
|
+
throw new AlephaError(
|
|
316
|
+
"Flag '--image=:version' requires 'build.docker.image.tag' in config",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
version = flagValue.slice(1); // remove leading ":"
|
|
320
|
+
imageTag = `${imageConfig.tag}:${version}`;
|
|
321
|
+
} else if (flagValue.includes(":")) {
|
|
322
|
+
// -i=toto:1.3.4 → full image with version
|
|
323
|
+
imageTag = flagValue;
|
|
324
|
+
version = flagValue.split(":")[1];
|
|
325
|
+
} else {
|
|
326
|
+
// -i=toto → image name without version → add :latest
|
|
327
|
+
imageTag = `${flagValue}:latest`;
|
|
328
|
+
version = "latest";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const args: string[] = [];
|
|
332
|
+
|
|
333
|
+
// Add custom args
|
|
334
|
+
if (imageConfig?.args) {
|
|
335
|
+
args.push(imageConfig.args);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Add OCI labels if enabled
|
|
339
|
+
if (imageConfig?.oci) {
|
|
340
|
+
const revision = await this.utils.getGitRevision();
|
|
341
|
+
const created = new Date().toISOString();
|
|
342
|
+
|
|
343
|
+
args.push(
|
|
344
|
+
`--label "org.opencontainers.image.revision=${revision}"`,
|
|
345
|
+
);
|
|
346
|
+
args.push(`--label "org.opencontainers.image.created=${created}"`);
|
|
347
|
+
args.push(`--label "org.opencontainers.image.version=${version}"`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const argsStr = args.length > 0 ? `${args.join(" ")} ` : "";
|
|
351
|
+
const dockerCmd = `docker build ${argsStr}-t ${imageTag} ${distDir}`;
|
|
352
|
+
|
|
353
|
+
await run(dockerCmd, {
|
|
354
|
+
alias: `docker build ${imageTag}`,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
240
357
|
}
|
|
241
358
|
},
|
|
242
359
|
});
|
package/src/cli/commands/db.ts
CHANGED
|
@@ -405,20 +405,18 @@ export class DbCommand {
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
const url = options.providerUrl;
|
|
408
|
-
if (!url.startsWith("
|
|
409
|
-
throw new AlephaError(
|
|
410
|
-
"D1 provider URL must start with 'cloudflare-d1://'.",
|
|
411
|
-
);
|
|
408
|
+
if (!url.startsWith("d1://")) {
|
|
409
|
+
throw new AlephaError("D1 provider URL must start with 'd1://'.");
|
|
412
410
|
}
|
|
413
411
|
|
|
414
412
|
const [, databaseId] = url
|
|
415
|
-
.replace("
|
|
416
|
-
.replace("
|
|
413
|
+
.replace("d1://", "")
|
|
414
|
+
.replace("d1:", "")
|
|
417
415
|
.split(":");
|
|
418
416
|
|
|
419
417
|
if (!databaseId) {
|
|
420
418
|
throw new AlephaError(
|
|
421
|
-
"Database ID is missing in the D1 provider URL. Cloudflare D1 URL format:
|
|
419
|
+
"Database ID is missing in the D1 provider URL. Cloudflare D1 URL format: d1://<database_name>:<database_id>",
|
|
422
420
|
);
|
|
423
421
|
}
|
|
424
422
|
|
|
@@ -76,16 +76,16 @@ describe("alepha init", () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
-
// Agent Files (--
|
|
79
|
+
// AI Agent Files (--ai flag)
|
|
80
80
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
81
|
|
|
82
|
-
describe("--
|
|
82
|
+
describe("--ai flag", () => {
|
|
83
83
|
it("should create CLAUDE.md when claude CLI is installed", async () => {
|
|
84
84
|
const { fs, shell, cli, cmd, json } = createTestEnv();
|
|
85
85
|
await setupProject(fs, json);
|
|
86
86
|
shell.installedCommands.add("claude");
|
|
87
87
|
|
|
88
|
-
await cli.run(cmd.init, { argv: "--
|
|
88
|
+
await cli.run(cmd.init, { argv: "--ai", root: "/project" });
|
|
89
89
|
|
|
90
90
|
expect(fs.wasWritten("/project/CLAUDE.md")).toBe(true);
|
|
91
91
|
expect(fs.wasWritten("/project/AGENTS.md")).toBe(false);
|
|
@@ -95,7 +95,7 @@ describe("alepha init", () => {
|
|
|
95
95
|
const { fs, cli, cmd, json } = createTestEnv();
|
|
96
96
|
await setupProject(fs, json);
|
|
97
97
|
|
|
98
|
-
await cli.run(cmd.init, { argv: "--
|
|
98
|
+
await cli.run(cmd.init, { argv: "--ai", root: "/project" });
|
|
99
99
|
|
|
100
100
|
expect(fs.wasWritten("/project/AGENTS.md")).toBe(true);
|
|
101
101
|
expect(fs.wasWritten("/project/CLAUDE.md")).toBe(false);
|
|
@@ -105,7 +105,7 @@ describe("alepha init", () => {
|
|
|
105
105
|
const { fs, cli, cmd, json } = createTestEnv();
|
|
106
106
|
await setupProject(fs, json);
|
|
107
107
|
|
|
108
|
-
await cli.run(cmd.init, { argv: "--
|
|
108
|
+
await cli.run(cmd.init, { argv: "--ai", root: "/project" });
|
|
109
109
|
|
|
110
110
|
expect(fs.wasWrittenMatching("/project/AGENTS.md", /Alepha/)).toBe(true);
|
|
111
111
|
expect(fs.wasWrittenMatching("/project/AGENTS.md", /alepha lint/)).toBe(
|
|
@@ -113,7 +113,7 @@ describe("alepha init", () => {
|
|
|
113
113
|
);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
it("should not create agent files without --
|
|
116
|
+
it("should not create agent files without --ai flag", async () => {
|
|
117
117
|
const { fs, cli, cmd, json } = createTestEnv();
|
|
118
118
|
await setupProject(fs, json);
|
|
119
119
|
|
|
@@ -180,6 +180,50 @@ describe("alepha init", () => {
|
|
|
180
180
|
expect(shell.wasCalled("npm install")).toBe(true);
|
|
181
181
|
expect(shell.wasCalled("yarn install")).toBe(false);
|
|
182
182
|
});
|
|
183
|
+
|
|
184
|
+
it("should accept --pm=yarn", async () => {
|
|
185
|
+
const { fs, shell, cli, cmd, json } = createTestEnv();
|
|
186
|
+
await setupProject(fs, json);
|
|
187
|
+
|
|
188
|
+
await cli.run(cmd.init, { argv: "--pm=yarn", root: "/project" });
|
|
189
|
+
|
|
190
|
+
expect(shell.wasCalled("yarn install")).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should accept --pm=pnpm", async () => {
|
|
194
|
+
const { fs, shell, cli, cmd, json } = createTestEnv();
|
|
195
|
+
await setupProject(fs, json);
|
|
196
|
+
|
|
197
|
+
await cli.run(cmd.init, { argv: "--pm=pnpm", root: "/project" });
|
|
198
|
+
|
|
199
|
+
expect(shell.wasCalled("pnpm install")).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should accept --pm=bun", async () => {
|
|
203
|
+
const { fs, shell, cli, cmd, json } = createTestEnv();
|
|
204
|
+
await setupProject(fs, json);
|
|
205
|
+
|
|
206
|
+
await cli.run(cmd.init, { argv: "--pm=bun", root: "/project" });
|
|
207
|
+
|
|
208
|
+
expect(shell.wasCalled("bun install")).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should reject invalid --pm value", async () => {
|
|
212
|
+
const { fs, cli, cmd, json } = createTestEnv();
|
|
213
|
+
await setupProject(fs, json);
|
|
214
|
+
|
|
215
|
+
await expect(
|
|
216
|
+
cli.run(cmd.init, { argv: "--pm=invalid", root: "/project" }),
|
|
217
|
+
).rejects.toThrowError(/Invalid flag/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should show enum values in help for --pm flag", async () => {
|
|
221
|
+
const { alepha, cli, cmd } = createTestEnv();
|
|
222
|
+
await alepha.start();
|
|
223
|
+
|
|
224
|
+
// Verify printHelp works with the init command (which has an enum flag)
|
|
225
|
+
expect(() => cli.printHelp(cmd.init)).not.toThrow();
|
|
226
|
+
});
|
|
183
227
|
});
|
|
184
228
|
|
|
185
229
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -26,9 +26,8 @@ export class InitCommand {
|
|
|
26
26
|
}),
|
|
27
27
|
),
|
|
28
28
|
flags: t.object({
|
|
29
|
-
|
|
29
|
+
ai: t.optional(
|
|
30
30
|
t.boolean({
|
|
31
|
-
aliases: ["a"],
|
|
32
31
|
description:
|
|
33
32
|
"Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)",
|
|
34
33
|
}),
|
|
@@ -56,6 +55,17 @@ export class InitCommand {
|
|
|
56
55
|
"Include @alepha/ui (components, auth portal, admin portal)",
|
|
57
56
|
}),
|
|
58
57
|
),
|
|
58
|
+
auth: t.optional(
|
|
59
|
+
t.boolean({
|
|
60
|
+
description:
|
|
61
|
+
"Include authentication (AppSecurity, $uiAuth). Implies --api --ui --react",
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
admin: t.optional(
|
|
65
|
+
t.boolean({
|
|
66
|
+
description: "Include admin portal ($uiAdmin). Implies --auth",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
59
69
|
test: t.optional(
|
|
60
70
|
t.boolean({ description: "Include Vitest and create test directory" }),
|
|
61
71
|
),
|
|
@@ -69,9 +79,17 @@ export class InitCommand {
|
|
|
69
79
|
handler: async ({ run, flags, root, args }) => {
|
|
70
80
|
if (args) {
|
|
71
81
|
root = this.fs.join(root, args);
|
|
72
|
-
await this.fs.mkdir(root);
|
|
82
|
+
await this.fs.mkdir(root, { force: true });
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
// Flag cascading: --admin → --auth → --ui → --react, --api
|
|
86
|
+
if (flags.admin) {
|
|
87
|
+
flags.auth = true;
|
|
88
|
+
}
|
|
89
|
+
if (flags.auth) {
|
|
90
|
+
flags.api = true;
|
|
91
|
+
flags.ui = true;
|
|
92
|
+
}
|
|
75
93
|
if (flags.ui) {
|
|
76
94
|
flags.react = true;
|
|
77
95
|
}
|
|
@@ -81,7 +99,7 @@ export class InitCommand {
|
|
|
81
99
|
|
|
82
100
|
// Detect agent type: claude CLI → CLAUDE.md, else → AGENTS.md
|
|
83
101
|
let agentType: "claude" | "agents" | false = false;
|
|
84
|
-
if (flags.
|
|
102
|
+
if (flags.ai) {
|
|
85
103
|
const hasClaudeCli = await this.utils.isInstalledAsync("claude");
|
|
86
104
|
agentType = hasClaudeCli ? "claude" : "agents";
|
|
87
105
|
}
|
|
@@ -112,12 +130,17 @@ export class InitCommand {
|
|
|
112
130
|
force,
|
|
113
131
|
});
|
|
114
132
|
if (flags.api) {
|
|
115
|
-
await this.scaffolder.ensureApiProject(root, {
|
|
133
|
+
await this.scaffolder.ensureApiProject(root, {
|
|
134
|
+
auth: !!flags.auth,
|
|
135
|
+
force,
|
|
136
|
+
});
|
|
116
137
|
}
|
|
117
138
|
if (flags.react && !isExpo) {
|
|
118
139
|
await this.scaffolder.ensureWebProject(root, {
|
|
119
140
|
api: !!flags.api,
|
|
120
141
|
ui: !!flags.ui,
|
|
142
|
+
auth: !!flags.auth,
|
|
143
|
+
admin: !!flags.admin,
|
|
121
144
|
force,
|
|
122
145
|
});
|
|
123
146
|
}
|
|
@@ -3,7 +3,6 @@ import { $logger } from "alepha/logger";
|
|
|
3
3
|
import { FileSystemProvider } from "alepha/system";
|
|
4
4
|
import { importVite, importViteReact, viteAlephaSsrPreload } from "alepha/vite";
|
|
5
5
|
import type { InlineConfig, Plugin, ViteDevServer } from "vite";
|
|
6
|
-
import { ViteUtils } from "../services/ViteUtils.ts";
|
|
7
6
|
import type { AppEntry } from "./AppEntryProvider.ts";
|
|
8
7
|
|
|
9
8
|
export interface ViteDevServerOptions {
|
|
@@ -49,7 +48,6 @@ export interface ViteDevServerOptions {
|
|
|
49
48
|
export class ViteDevServerProvider {
|
|
50
49
|
protected readonly log = $logger();
|
|
51
50
|
protected readonly fs = $inject(FileSystemProvider);
|
|
52
|
-
protected readonly templateProvider = $inject(ViteUtils);
|
|
53
51
|
protected server!: ViteDevServer;
|
|
54
52
|
protected options!: ViteDevServerOptions;
|
|
55
53
|
protected alepha: Alepha | null = null;
|
|
@@ -240,20 +238,13 @@ export class ViteDevServerProvider {
|
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
/**
|
|
243
|
-
* Setup Alepha instance with Vite middleware
|
|
241
|
+
* Setup Alepha instance with Vite middleware.
|
|
244
242
|
*/
|
|
245
243
|
protected async setupAlepha(): Promise<void> {
|
|
246
244
|
if (!this.alepha || !this.hasReact()) {
|
|
247
245
|
return;
|
|
248
246
|
}
|
|
249
247
|
|
|
250
|
-
const template = await this.server.transformIndexHtml(
|
|
251
|
-
"/",
|
|
252
|
-
this.templateProvider.generateIndexHtml(this.options.entry),
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
this.alepha.store.set("alepha.react.server.template", template);
|
|
256
|
-
|
|
257
248
|
this.alepha.events.on("server:onRequest", {
|
|
258
249
|
priority: "first",
|
|
259
250
|
callback: async ({ request }) => {
|
|
@@ -145,4 +145,20 @@ ${models.map((it: string) => `export const ${it} = models["${it}"];`).join("\n")
|
|
|
145
145
|
public isInstalledAsync(cmd: string): Promise<boolean> {
|
|
146
146
|
return this.shell.isInstalled(cmd);
|
|
147
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get the current git revision (commit SHA).
|
|
151
|
+
*
|
|
152
|
+
* @returns The short commit SHA or "unknown" if not in a git repo
|
|
153
|
+
*/
|
|
154
|
+
public async getGitRevision(): Promise<string> {
|
|
155
|
+
try {
|
|
156
|
+
const result = await this.shell.run("git rev-parse --short HEAD", {
|
|
157
|
+
capture: true,
|
|
158
|
+
});
|
|
159
|
+
return result.trim();
|
|
160
|
+
} catch {
|
|
161
|
+
return "unknown";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
148
164
|
}
|
|
@@ -388,6 +388,8 @@ export class PackageManagerUtils {
|
|
|
388
388
|
if (modes.react) {
|
|
389
389
|
dependencies.react = alephaDeps.react;
|
|
390
390
|
dependencies["react-dom"] = alephaDeps["react-dom"];
|
|
391
|
+
devDependencies["@vitejs/plugin-react"] =
|
|
392
|
+
alephaDeps["@vitejs/plugin-react"];
|
|
391
393
|
devDependencies["@types/react"] = alephaDeps["@types/react"];
|
|
392
394
|
}
|
|
393
395
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import {
|
|
3
|
+
FileSystemProvider,
|
|
4
|
+
MemoryFileSystemProvider,
|
|
5
|
+
MemoryShellProvider,
|
|
6
|
+
ShellProvider,
|
|
7
|
+
} from "alepha/system";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { ProjectScaffolder } from "./ProjectScaffolder.ts";
|
|
10
|
+
|
|
11
|
+
describe("ProjectScaffolder", () => {
|
|
12
|
+
const createTestEnv = () => {
|
|
13
|
+
const alepha = Alepha.create()
|
|
14
|
+
.with({ provide: FileSystemProvider, use: MemoryFileSystemProvider })
|
|
15
|
+
.with({ provide: ShellProvider, use: MemoryShellProvider });
|
|
16
|
+
|
|
17
|
+
const scaffolder = alepha.inject(ProjectScaffolder);
|
|
18
|
+
|
|
19
|
+
return { alepha, scaffolder };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// getAppName
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe("getAppName", () => {
|
|
27
|
+
it("should return lowercase directory name", () => {
|
|
28
|
+
const { scaffolder } = createTestEnv();
|
|
29
|
+
|
|
30
|
+
expect(scaffolder.getAppName("/project/MyApp")).toBe("myapp");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should remove dashes from directory name", () => {
|
|
34
|
+
const { scaffolder } = createTestEnv();
|
|
35
|
+
|
|
36
|
+
expect(scaffolder.getAppName("/project/my-cool-app")).toBe("mycoolapp");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should remove underscores from directory name", () => {
|
|
40
|
+
const { scaffolder } = createTestEnv();
|
|
41
|
+
|
|
42
|
+
expect(scaffolder.getAppName("/project/my_cool_app")).toBe("mycoolapp");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should remove spaces from directory name", () => {
|
|
46
|
+
const { scaffolder } = createTestEnv();
|
|
47
|
+
|
|
48
|
+
expect(scaffolder.getAppName("/project/my cool app")).toBe("mycoolapp");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should remove dots from directory name", () => {
|
|
52
|
+
const { scaffolder } = createTestEnv();
|
|
53
|
+
|
|
54
|
+
expect(scaffolder.getAppName("/project/my.cool.app")).toBe("mycoolapp");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should remove digits from directory name", () => {
|
|
58
|
+
const { scaffolder } = createTestEnv();
|
|
59
|
+
|
|
60
|
+
expect(scaffolder.getAppName("/project/app123")).toBe("app");
|
|
61
|
+
expect(scaffolder.getAppName("/project/my2app")).toBe("myapp");
|
|
62
|
+
expect(scaffolder.getAppName("/project/v2-app")).toBe("vapp");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should handle combination of special characters", () => {
|
|
66
|
+
const { scaffolder } = createTestEnv();
|
|
67
|
+
|
|
68
|
+
expect(scaffolder.getAppName("/project/my-cool_app.v2")).toBe(
|
|
69
|
+
"mycoolappv",
|
|
70
|
+
);
|
|
71
|
+
expect(scaffolder.getAppName("/project/test_app-2.0")).toBe("testapp");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should fallback to 'app' when all characters are removed", () => {
|
|
75
|
+
const { scaffolder } = createTestEnv();
|
|
76
|
+
|
|
77
|
+
expect(scaffolder.getAppName("/project/123")).toBe("app");
|
|
78
|
+
expect(scaffolder.getAppName("/project/---")).toBe("app");
|
|
79
|
+
expect(scaffolder.getAppName("/project/1.2.3")).toBe("app");
|
|
80
|
+
expect(scaffolder.getAppName("/project/_-._")).toBe("app");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle deeply nested paths", () => {
|
|
84
|
+
const { scaffolder } = createTestEnv();
|
|
85
|
+
|
|
86
|
+
expect(scaffolder.getAppName("/workspace/packages/apps/my-app")).toBe(
|
|
87
|
+
"myapp",
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should handle root-level directories", () => {
|
|
92
|
+
const { scaffolder } = createTestEnv();
|
|
93
|
+
|
|
94
|
+
expect(scaffolder.getAppName("/myapp")).toBe("myapp");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|