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
|
@@ -24,6 +24,36 @@ class TestCliProvider extends CliProvider {
|
|
|
24
24
|
public testFindCommand = this.findCommand.bind(this);
|
|
25
25
|
public testFindPreHooks = this.findPreHooks.bind(this);
|
|
26
26
|
public testFindPostHooks = this.findPostHooks.bind(this);
|
|
27
|
+
public testGetEnumValues = this.getEnumValues.bind(this);
|
|
28
|
+
public testFormatFlagDescription = this.formatFlagDescription.bind(this);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract flag definitions from a command's flags schema (for testing printHelp logic).
|
|
32
|
+
*/
|
|
33
|
+
public testExtractFlagDefs(flagsSchema: any) {
|
|
34
|
+
return Object.entries(flagsSchema.properties).map(([key, value]) => ({
|
|
35
|
+
key,
|
|
36
|
+
schema: value,
|
|
37
|
+
aliases: [
|
|
38
|
+
key,
|
|
39
|
+
...((value as any).aliases ??
|
|
40
|
+
((value as any).alias ? [(value as any).alias] : [])),
|
|
41
|
+
],
|
|
42
|
+
description: (value as any).description,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format aliases array into flag string (e.g., "-t, --target").
|
|
48
|
+
* Sorts by length (shorter first).
|
|
49
|
+
*/
|
|
50
|
+
public testFormatFlagStr(aliases: string[]): string {
|
|
51
|
+
return aliases
|
|
52
|
+
.slice()
|
|
53
|
+
.sort((a, b) => a.length - b.length)
|
|
54
|
+
.map((a) => (a.length === 1 ? `-${a}` : `--${a}`))
|
|
55
|
+
.join(", ");
|
|
56
|
+
}
|
|
27
57
|
}
|
|
28
58
|
|
|
29
59
|
describe("CliProvider", () => {
|
|
@@ -130,6 +160,128 @@ describe("CliProvider", () => {
|
|
|
130
160
|
cli.testParseFlags(["--config", "{invalid}"], flagDefs),
|
|
131
161
|
).toThrow("Invalid JSON");
|
|
132
162
|
});
|
|
163
|
+
|
|
164
|
+
it("should parse union(boolean, text) flag without value as true", () => {
|
|
165
|
+
const cli = createTestCli();
|
|
166
|
+
const flagDefs = [
|
|
167
|
+
{
|
|
168
|
+
key: "image",
|
|
169
|
+
aliases: ["i", "image"],
|
|
170
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const result = cli.testParseFlags(["-i"], flagDefs);
|
|
175
|
+
expect(result.image).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should parse union(boolean, text) flag with = value as string", () => {
|
|
179
|
+
const cli = createTestCli();
|
|
180
|
+
const flagDefs = [
|
|
181
|
+
{
|
|
182
|
+
key: "image",
|
|
183
|
+
aliases: ["i", "image"],
|
|
184
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
const result = cli.testParseFlags(["-i=1.3.4"], flagDefs);
|
|
189
|
+
expect(result.image).toBe("1.3.4");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should parse union(boolean, text) flag with space value as string", () => {
|
|
193
|
+
const cli = createTestCli();
|
|
194
|
+
const flagDefs = [
|
|
195
|
+
{
|
|
196
|
+
key: "image",
|
|
197
|
+
aliases: ["i", "image"],
|
|
198
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const result = cli.testParseFlags(["-i", "1.3.4"], flagDefs);
|
|
203
|
+
expect(result.image).toBe("1.3.4");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should parse union(boolean, text) flag without value when followed by another flag", () => {
|
|
207
|
+
const cli = createTestCli();
|
|
208
|
+
const flagDefs = [
|
|
209
|
+
{
|
|
210
|
+
key: "image",
|
|
211
|
+
aliases: ["i", "image"],
|
|
212
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
213
|
+
},
|
|
214
|
+
{ key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const result = cli.testParseFlags(["-i", "-v"], flagDefs);
|
|
218
|
+
expect(result.image).toBe(true);
|
|
219
|
+
expect(result.verbose).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should parse union(boolean, text) flag with long form without value", () => {
|
|
223
|
+
const cli = createTestCli();
|
|
224
|
+
const flagDefs = [
|
|
225
|
+
{
|
|
226
|
+
key: "image",
|
|
227
|
+
aliases: ["i", "image"],
|
|
228
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
const result = cli.testParseFlags(["--image"], flagDefs);
|
|
233
|
+
expect(result.image).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should parse union(boolean, text) flag at end of argv without value", () => {
|
|
237
|
+
const cli = createTestCli();
|
|
238
|
+
const flagDefs = [
|
|
239
|
+
{
|
|
240
|
+
key: "image",
|
|
241
|
+
aliases: ["i", "image"],
|
|
242
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
243
|
+
},
|
|
244
|
+
{ key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const result = cli.testParseFlags(["-v", "-i"], flagDefs);
|
|
248
|
+
expect(result.verbose).toBe(true);
|
|
249
|
+
expect(result.image).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should parse union(boolean, text) with empty = value as empty string", () => {
|
|
253
|
+
const cli = createTestCli();
|
|
254
|
+
const flagDefs = [
|
|
255
|
+
{
|
|
256
|
+
key: "image",
|
|
257
|
+
aliases: ["i", "image"],
|
|
258
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
// Note: --image= results in empty string value
|
|
263
|
+
const result = cli.testParseFlags(["--image="], flagDefs);
|
|
264
|
+
// Empty string is falsy, so it goes through the "no value" path → true
|
|
265
|
+
// This is expected behavior - use --image="" if you need empty string
|
|
266
|
+
expect(result.image).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should parse union(boolean, text) with value containing special chars", () => {
|
|
270
|
+
const cli = createTestCli();
|
|
271
|
+
const flagDefs = [
|
|
272
|
+
{
|
|
273
|
+
key: "image",
|
|
274
|
+
aliases: ["i", "image"],
|
|
275
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const result = cli.testParseFlags(
|
|
280
|
+
["--image=my-org/my-app:v1.2.3-beta"],
|
|
281
|
+
flagDefs,
|
|
282
|
+
);
|
|
283
|
+
expect(result.image).toBe("my-org/my-app:v1.2.3-beta");
|
|
284
|
+
});
|
|
133
285
|
});
|
|
134
286
|
|
|
135
287
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -439,6 +591,56 @@ describe("CliProvider", () => {
|
|
|
439
591
|
expect(result.has(0)).toBe(true); // --name=value
|
|
440
592
|
expect(result.has(1)).toBe(false); // arg
|
|
441
593
|
});
|
|
594
|
+
|
|
595
|
+
it("should consume value for union(boolean, text) when value is provided", () => {
|
|
596
|
+
const cli = createTestCli();
|
|
597
|
+
const flagDefs = [
|
|
598
|
+
{
|
|
599
|
+
key: "image",
|
|
600
|
+
aliases: ["i", "image"],
|
|
601
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
602
|
+
},
|
|
603
|
+
];
|
|
604
|
+
|
|
605
|
+
const result = cli.testGetFlagConsumedIndices(
|
|
606
|
+
["-i", "1.3.4", "arg"],
|
|
607
|
+
flagDefs,
|
|
608
|
+
);
|
|
609
|
+
expect(result.has(0)).toBe(true); // -i
|
|
610
|
+
expect(result.has(1)).toBe(true); // 1.3.4 (consumed as value)
|
|
611
|
+
expect(result.has(2)).toBe(false); // arg
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("should not consume next arg for union(boolean, text) when next is a flag", () => {
|
|
615
|
+
const cli = createTestCli();
|
|
616
|
+
const flagDefs = [
|
|
617
|
+
{
|
|
618
|
+
key: "image",
|
|
619
|
+
aliases: ["i", "image"],
|
|
620
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
621
|
+
},
|
|
622
|
+
{ key: "verbose", aliases: ["v", "verbose"], schema: t.boolean() },
|
|
623
|
+
];
|
|
624
|
+
|
|
625
|
+
const result = cli.testGetFlagConsumedIndices(["-i", "-v"], flagDefs);
|
|
626
|
+
expect(result.has(0)).toBe(true); // -i
|
|
627
|
+
expect(result.has(1)).toBe(true); // -v (not consumed by -i, but by itself)
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it("should not consume next arg for union(boolean, text) at end of argv", () => {
|
|
631
|
+
const cli = createTestCli();
|
|
632
|
+
const flagDefs = [
|
|
633
|
+
{
|
|
634
|
+
key: "image",
|
|
635
|
+
aliases: ["i", "image"],
|
|
636
|
+
schema: t.union([t.boolean(), t.text()]),
|
|
637
|
+
},
|
|
638
|
+
];
|
|
639
|
+
|
|
640
|
+
const result = cli.testGetFlagConsumedIndices(["-i"], flagDefs);
|
|
641
|
+
expect(result.has(0)).toBe(true); // -i
|
|
642
|
+
expect(result.size).toBe(1);
|
|
643
|
+
});
|
|
442
644
|
});
|
|
443
645
|
|
|
444
646
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -932,6 +1134,129 @@ describe("CliProvider", () => {
|
|
|
932
1134
|
// Just verify it doesn't throw - hidden commands filtered in getTopLevelCommands
|
|
933
1135
|
expect(() => cli.printHelp()).not.toThrow();
|
|
934
1136
|
});
|
|
1137
|
+
|
|
1138
|
+
it("should include flag aliases in help output", () => {
|
|
1139
|
+
const cli = createTestCli();
|
|
1140
|
+
|
|
1141
|
+
const flagsSchema = t.object({
|
|
1142
|
+
target: t.optional(
|
|
1143
|
+
t.enum(["bare", "docker", "vercel"], {
|
|
1144
|
+
aliases: ["t"],
|
|
1145
|
+
description: "Deployment target",
|
|
1146
|
+
}),
|
|
1147
|
+
),
|
|
1148
|
+
runtime: t.optional(
|
|
1149
|
+
t.enum(["node", "bun"], {
|
|
1150
|
+
alias: "r", // singular alias
|
|
1151
|
+
description: "JavaScript runtime",
|
|
1152
|
+
}),
|
|
1153
|
+
),
|
|
1154
|
+
verbose: t.optional(
|
|
1155
|
+
t.boolean({
|
|
1156
|
+
description: "Verbose output",
|
|
1157
|
+
}),
|
|
1158
|
+
),
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
const flagDefs = cli.testExtractFlagDefs(flagsSchema);
|
|
1162
|
+
|
|
1163
|
+
// target should have key + aliases array
|
|
1164
|
+
const targetFlag = flagDefs.find((f) => f.key === "target");
|
|
1165
|
+
expect(targetFlag?.aliases).toContain("target");
|
|
1166
|
+
expect(targetFlag?.aliases).toContain("t");
|
|
1167
|
+
expect(cli.testFormatFlagStr(targetFlag!.aliases)).toBe("-t, --target");
|
|
1168
|
+
|
|
1169
|
+
// runtime should have key + alias (singular)
|
|
1170
|
+
const runtimeFlag = flagDefs.find((f) => f.key === "runtime");
|
|
1171
|
+
expect(runtimeFlag?.aliases).toContain("runtime");
|
|
1172
|
+
expect(runtimeFlag?.aliases).toContain("r");
|
|
1173
|
+
expect(cli.testFormatFlagStr(runtimeFlag!.aliases)).toBe("-r, --runtime");
|
|
1174
|
+
|
|
1175
|
+
// verbose should only have key (no aliases defined)
|
|
1176
|
+
const verboseFlag = flagDefs.find((f) => f.key === "verbose");
|
|
1177
|
+
expect(verboseFlag?.aliases).toEqual(["verbose"]);
|
|
1178
|
+
expect(cli.testFormatFlagStr(verboseFlag!.aliases)).toBe("--verbose");
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1183
|
+
// getEnumValues
|
|
1184
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1185
|
+
|
|
1186
|
+
describe("getEnumValues", () => {
|
|
1187
|
+
it("should extract values from t.enum schema", () => {
|
|
1188
|
+
const cli = createTestCli();
|
|
1189
|
+
const schema = t.enum(["yarn", "npm", "pnpm", "bun"]);
|
|
1190
|
+
|
|
1191
|
+
const values = cli.testGetEnumValues(schema);
|
|
1192
|
+
|
|
1193
|
+
expect(values).toEqual(["yarn", "npm", "pnpm", "bun"]);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it("should return undefined for non-enum schemas", () => {
|
|
1197
|
+
const cli = createTestCli();
|
|
1198
|
+
|
|
1199
|
+
expect(cli.testGetEnumValues(t.string())).toBeUndefined();
|
|
1200
|
+
expect(cli.testGetEnumValues(t.boolean())).toBeUndefined();
|
|
1201
|
+
expect(cli.testGetEnumValues(t.number())).toBeUndefined();
|
|
1202
|
+
expect(cli.testGetEnumValues(t.object({}))).toBeUndefined();
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
it("should return undefined for empty or undefined schema", () => {
|
|
1206
|
+
const cli = createTestCli();
|
|
1207
|
+
|
|
1208
|
+
expect(cli.testGetEnumValues(undefined as any)).toBeUndefined();
|
|
1209
|
+
});
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1213
|
+
// formatFlagDescription
|
|
1214
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1215
|
+
|
|
1216
|
+
describe("formatFlagDescription", () => {
|
|
1217
|
+
it("should append enum values to description", () => {
|
|
1218
|
+
const cli = createTestCli();
|
|
1219
|
+
const schema = t.enum(["yarn", "npm", "pnpm", "bun"]);
|
|
1220
|
+
|
|
1221
|
+
const result = cli.testFormatFlagDescription(
|
|
1222
|
+
"Package manager to use",
|
|
1223
|
+
schema,
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
expect(result).toContain("Package manager to use");
|
|
1227
|
+
expect(result).toContain("yarn");
|
|
1228
|
+
expect(result).toContain("npm");
|
|
1229
|
+
expect(result).toContain("pnpm");
|
|
1230
|
+
expect(result).toContain("bun");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it("should return only enum values when description is empty", () => {
|
|
1234
|
+
const cli = createTestCli();
|
|
1235
|
+
const schema = t.enum(["a", "b", "c"]);
|
|
1236
|
+
|
|
1237
|
+
const result = cli.testFormatFlagDescription(undefined, schema);
|
|
1238
|
+
|
|
1239
|
+
expect(result).toContain("a");
|
|
1240
|
+
expect(result).toContain("b");
|
|
1241
|
+
expect(result).toContain("c");
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
it("should return original description for non-enum schemas", () => {
|
|
1245
|
+
const cli = createTestCli();
|
|
1246
|
+
|
|
1247
|
+
expect(cli.testFormatFlagDescription("A string flag", t.string())).toBe(
|
|
1248
|
+
"A string flag",
|
|
1249
|
+
);
|
|
1250
|
+
expect(cli.testFormatFlagDescription("A boolean flag", t.boolean())).toBe(
|
|
1251
|
+
"A boolean flag",
|
|
1252
|
+
);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
it("should return empty string when no description and no enum", () => {
|
|
1256
|
+
const cli = createTestCli();
|
|
1257
|
+
|
|
1258
|
+
expect(cli.testFormatFlagDescription(undefined, t.string())).toBe("");
|
|
1259
|
+
});
|
|
935
1260
|
});
|
|
936
1261
|
|
|
937
1262
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type Static,
|
|
11
11
|
type TObject,
|
|
12
12
|
type TSchema,
|
|
13
|
+
type TUnion,
|
|
13
14
|
TypeBoxError,
|
|
14
15
|
t,
|
|
15
16
|
} from "alepha";
|
|
@@ -658,8 +659,24 @@ export class CliProvider {
|
|
|
658
659
|
continue;
|
|
659
660
|
}
|
|
660
661
|
|
|
662
|
+
// Check if schema is a union containing boolean (allows flag without value)
|
|
663
|
+
const isUnionWithBoolean =
|
|
664
|
+
t.schema.isUnion(def.schema) &&
|
|
665
|
+
(def.schema as TUnion).anyOf.some((s) => t.schema.isBoolean(s));
|
|
666
|
+
|
|
661
667
|
if (t.schema.isBoolean(def.schema)) {
|
|
662
668
|
result[def.key] = true;
|
|
669
|
+
} else if (isUnionWithBoolean && !value) {
|
|
670
|
+
// Union with boolean: --flag without value → true
|
|
671
|
+
const nextArg = argv[i + 1];
|
|
672
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
673
|
+
// Has a value after space: --flag value
|
|
674
|
+
result[def.key] = nextArg;
|
|
675
|
+
i++; // consume next arg
|
|
676
|
+
} else {
|
|
677
|
+
// No value: --flag → true
|
|
678
|
+
result[def.key] = true;
|
|
679
|
+
}
|
|
663
680
|
} else if (value) {
|
|
664
681
|
// Value provided via --flag=value syntax
|
|
665
682
|
try {
|
|
@@ -713,8 +730,24 @@ export class CliProvider {
|
|
|
713
730
|
const def = flagDefs.find((d) => d.aliases.includes(rawKey));
|
|
714
731
|
if (!def) continue;
|
|
715
732
|
|
|
733
|
+
// Check if schema is a union containing boolean
|
|
734
|
+
const isUnionWithBoolean =
|
|
735
|
+
t.schema.isUnion(def.schema) &&
|
|
736
|
+
(def.schema as TUnion).anyOf.some((s) => t.schema.isBoolean(s));
|
|
737
|
+
|
|
716
738
|
// If not a boolean flag and no = value, the next arg is consumed as the value
|
|
717
|
-
|
|
739
|
+
// Exception: union with boolean can work without a value
|
|
740
|
+
if (
|
|
741
|
+
!t.schema.isBoolean(def.schema) &&
|
|
742
|
+
!isUnionWithBoolean &&
|
|
743
|
+
!hasEqualValue
|
|
744
|
+
) {
|
|
745
|
+
const nextArg = argv[i + 1];
|
|
746
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
747
|
+
consumed.add(i + 1);
|
|
748
|
+
}
|
|
749
|
+
} else if (isUnionWithBoolean && !hasEqualValue) {
|
|
750
|
+
// Union with boolean: check if next arg looks like a value (not a flag)
|
|
718
751
|
const nextArg = argv[i + 1];
|
|
719
752
|
if (nextArg && !nextArg.startsWith("-")) {
|
|
720
753
|
consumed.add(i + 1);
|
|
@@ -928,7 +961,11 @@ export class CliProvider {
|
|
|
928
961
|
...Object.entries(command.flags.properties).map(([key, value]) => ({
|
|
929
962
|
key,
|
|
930
963
|
schema: value,
|
|
931
|
-
aliases:
|
|
964
|
+
aliases: [
|
|
965
|
+
key,
|
|
966
|
+
...((value as any).aliases ??
|
|
967
|
+
((value as any).alias ? [(value as any).alias] : [])),
|
|
968
|
+
],
|
|
932
969
|
description: (value as any).description,
|
|
933
970
|
})),
|
|
934
971
|
// Add --mode flag if command has mode option enabled
|
|
@@ -941,6 +978,7 @@ export class CliProvider {
|
|
|
941
978
|
typeof command.options.mode === "string"
|
|
942
979
|
? `Environment mode - loads .env.{mode} (default: ${command.options.mode})`
|
|
943
980
|
: "Environment mode (e.g., production, staging) - loads .env.{mode}",
|
|
981
|
+
schema: t.string() as TSchema,
|
|
944
982
|
},
|
|
945
983
|
]
|
|
946
984
|
: []),
|
|
@@ -951,13 +989,20 @@ export class CliProvider {
|
|
|
951
989
|
];
|
|
952
990
|
|
|
953
991
|
const maxFlagLength = this.getMaxFlagLength(flags);
|
|
954
|
-
for (const
|
|
955
|
-
const
|
|
992
|
+
for (const flag of flags) {
|
|
993
|
+
const { aliases, description } = flag;
|
|
994
|
+
const schema = "schema" in flag ? (flag.schema as TSchema) : undefined;
|
|
995
|
+
// Sort aliases by length (shorter first: -t before --target)
|
|
996
|
+
const sortedAliases = (Array.isArray(aliases) ? aliases : [aliases])
|
|
997
|
+
.slice()
|
|
998
|
+
.sort((a, b) => a.length - b.length);
|
|
999
|
+
const flagStr = sortedAliases
|
|
956
1000
|
.map((a: string) => (a.length === 1 ? `-${a}` : `--${a}`))
|
|
957
1001
|
.join(", ");
|
|
958
1002
|
const coloredFlag = c.set("GREY_LIGHT", flagStr);
|
|
959
1003
|
const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
|
|
960
|
-
this.
|
|
1004
|
+
const formattedDesc = this.formatFlagDescription(description, schema);
|
|
1005
|
+
this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
|
|
961
1006
|
}
|
|
962
1007
|
|
|
963
1008
|
// Show environment variables if defined
|
|
@@ -1024,6 +1069,7 @@ export class CliProvider {
|
|
|
1024
1069
|
[]),
|
|
1025
1070
|
],
|
|
1026
1071
|
description: (value as any).description,
|
|
1072
|
+
schema: value as TSchema,
|
|
1027
1073
|
}))
|
|
1028
1074
|
: [];
|
|
1029
1075
|
|
|
@@ -1032,13 +1078,14 @@ export class CliProvider {
|
|
|
1032
1078
|
...Object.values(this.getAllGlobalFlags()),
|
|
1033
1079
|
];
|
|
1034
1080
|
const maxFlagLength = this.getMaxFlagLength(globalFlags);
|
|
1035
|
-
for (const { aliases, description } of globalFlags) {
|
|
1081
|
+
for (const { aliases, description, schema } of globalFlags) {
|
|
1036
1082
|
const flagStr = aliases
|
|
1037
1083
|
.map((a) => (a.length === 1 ? `-${a}` : `--${a}`))
|
|
1038
1084
|
.join(", ");
|
|
1039
1085
|
const coloredFlag = c.set("GREY_LIGHT", flagStr);
|
|
1040
1086
|
const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
|
|
1041
|
-
this.
|
|
1087
|
+
const formattedDesc = this.formatFlagDescription(description, schema);
|
|
1088
|
+
this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
|
|
1042
1089
|
}
|
|
1043
1090
|
}
|
|
1044
1091
|
this.log.info(""); // Newline
|
|
@@ -1159,4 +1206,67 @@ export class CliProvider {
|
|
|
1159
1206
|
}),
|
|
1160
1207
|
);
|
|
1161
1208
|
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Extract enum values from a schema if it represents an enum.
|
|
1212
|
+
* Returns undefined if the schema is not an enum.
|
|
1213
|
+
*/
|
|
1214
|
+
protected getEnumValues(schema: TSchema): string[] | undefined {
|
|
1215
|
+
if (!schema) return undefined;
|
|
1216
|
+
|
|
1217
|
+
// Check if schema has an enum property (t.enum creates schemas with this)
|
|
1218
|
+
if (
|
|
1219
|
+
"enum" in schema &&
|
|
1220
|
+
Array.isArray(schema.enum) &&
|
|
1221
|
+
schema.enum.every((v) => typeof v === "string")
|
|
1222
|
+
) {
|
|
1223
|
+
return schema.enum as string[];
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Also check for union of string literals (alternative enum representation)
|
|
1227
|
+
if (t.schema.isUnion(schema)) {
|
|
1228
|
+
const union = schema as TUnion;
|
|
1229
|
+
const values: string[] = [];
|
|
1230
|
+
|
|
1231
|
+
for (const variant of union.anyOf) {
|
|
1232
|
+
// Check if the variant is a string literal (has const property)
|
|
1233
|
+
if (
|
|
1234
|
+
t.schema.isString(variant) &&
|
|
1235
|
+
"const" in variant &&
|
|
1236
|
+
typeof variant.const === "string"
|
|
1237
|
+
) {
|
|
1238
|
+
values.push(variant.const);
|
|
1239
|
+
} else {
|
|
1240
|
+
// Not all variants are string literals, not a simple enum
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
return values.length > 0 ? values : undefined;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return undefined;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Format flag description with enum values if applicable.
|
|
1253
|
+
*/
|
|
1254
|
+
protected formatFlagDescription(
|
|
1255
|
+
description: string | undefined,
|
|
1256
|
+
schema: TSchema | undefined,
|
|
1257
|
+
): string {
|
|
1258
|
+
const baseDesc = description ?? "";
|
|
1259
|
+
|
|
1260
|
+
if (!schema) return baseDesc;
|
|
1261
|
+
|
|
1262
|
+
const enumValues = this.getEnumValues(schema);
|
|
1263
|
+
if (enumValues && enumValues.length > 0) {
|
|
1264
|
+
const valuesStr = enumValues.join(", ");
|
|
1265
|
+
const c = this.color;
|
|
1266
|
+
const enumHint = c.set("GREY_DARK", `[${valuesStr}]`);
|
|
1267
|
+
return baseDesc ? `${baseDesc} ${enumHint}` : enumHint;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return baseDesc;
|
|
1271
|
+
}
|
|
1162
1272
|
}
|
package/src/core/Alepha.ts
CHANGED
|
@@ -472,44 +472,51 @@ export class Alepha {
|
|
|
472
472
|
|
|
473
473
|
this.starting = Promise.withResolvers();
|
|
474
474
|
|
|
475
|
-
|
|
475
|
+
try {
|
|
476
|
+
const now = Date.now();
|
|
476
477
|
|
|
477
|
-
|
|
478
|
+
this.log?.info("Starting App...");
|
|
478
479
|
|
|
479
|
-
for (const [key] of this.substitutions.entries()) {
|
|
480
|
-
this.inject(key);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const target = this.store.get("alepha.target");
|
|
484
|
-
if (target) {
|
|
485
|
-
this.modules = [];
|
|
486
|
-
this.registry = new Map();
|
|
487
|
-
this.primitiveRegistry = new Map();
|
|
488
|
-
this.pendingInstantiations = [];
|
|
489
|
-
this.substitutions = new Map();
|
|
490
|
-
this.events.clear();
|
|
491
|
-
delete (target as any)[MODULE];
|
|
492
|
-
this.with(target);
|
|
493
480
|
for (const [key] of this.substitutions.entries()) {
|
|
494
481
|
this.inject(key);
|
|
495
482
|
}
|
|
496
|
-
}
|
|
497
483
|
|
|
498
|
-
|
|
484
|
+
const target = this.store.get("alepha.target");
|
|
485
|
+
if (target) {
|
|
486
|
+
this.modules = [];
|
|
487
|
+
this.registry = new Map();
|
|
488
|
+
this.primitiveRegistry = new Map();
|
|
489
|
+
this.pendingInstantiations = [];
|
|
490
|
+
this.substitutions = new Map();
|
|
491
|
+
this.events.clear();
|
|
492
|
+
delete (target as any)[MODULE];
|
|
493
|
+
this.with(target);
|
|
494
|
+
for (const [key] of this.substitutions.entries()) {
|
|
495
|
+
this.inject(key);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
499
498
|
|
|
500
|
-
|
|
499
|
+
this.locked = true;
|
|
501
500
|
|
|
502
|
-
|
|
501
|
+
await this.events.emit("configure", this, { log: true });
|
|
503
502
|
|
|
504
|
-
|
|
503
|
+
this.configured = true;
|
|
505
504
|
|
|
506
|
-
|
|
505
|
+
await this.events.emit("start", this, { log: true });
|
|
507
506
|
|
|
508
|
-
|
|
507
|
+
this.started = true;
|
|
509
508
|
|
|
510
|
-
|
|
509
|
+
await this.events.emit("ready", this, { log: true });
|
|
511
510
|
|
|
512
|
-
|
|
511
|
+
this.log?.info(`App is now ready [${Date.now() - now}ms]`);
|
|
512
|
+
|
|
513
|
+
this.ready = true;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
this.starting.reject(error);
|
|
516
|
+
const promise = this.starting.promise;
|
|
517
|
+
this.starting = undefined;
|
|
518
|
+
return promise;
|
|
519
|
+
}
|
|
513
520
|
|
|
514
521
|
this.starting.resolve(this);
|
|
515
522
|
this.starting = undefined;
|
package/src/orm/index.bun.ts
CHANGED
|
@@ -52,7 +52,7 @@ export const AlephaOrm = $module({
|
|
|
52
52
|
const url = env.DATABASE_URL;
|
|
53
53
|
const isPostgres = url?.startsWith("postgres:");
|
|
54
54
|
|
|
55
|
-
if (url?.startsWith("
|
|
55
|
+
if (url?.startsWith("d1:")) {
|
|
56
56
|
alepha.with({
|
|
57
57
|
optional: true,
|
|
58
58
|
provide: DatabaseProvider,
|
package/src/orm/index.ts
CHANGED
|
@@ -152,14 +152,10 @@ export const AlephaOrm = $module({
|
|
|
152
152
|
alepha.with(RepositoryProvider);
|
|
153
153
|
|
|
154
154
|
const url = env.DATABASE_URL;
|
|
155
|
-
const hasPGlite = !!PglitePostgresProvider.importPglite();
|
|
156
155
|
const isPostgres = url?.startsWith("postgres:");
|
|
157
|
-
const isSqlite = url?.startsWith("sqlite:");
|
|
158
|
-
const isMemory = url?.includes(":memory:");
|
|
159
|
-
const isFile = !!url && !isPostgres && !isMemory;
|
|
160
156
|
const isBun = alepha.isBun();
|
|
161
157
|
|
|
162
|
-
if (url?.startsWith("
|
|
158
|
+
if (url?.startsWith("d1:")) {
|
|
163
159
|
alepha.with({
|
|
164
160
|
optional: true,
|
|
165
161
|
provide: DatabaseProvider,
|
|
@@ -168,7 +164,7 @@ export const AlephaOrm = $module({
|
|
|
168
164
|
return;
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
if (
|
|
167
|
+
if (url?.startsWith("pglite:")) {
|
|
172
168
|
alepha.with({
|
|
173
169
|
optional: true,
|
|
174
170
|
provide: DatabaseProvider,
|
|
@@ -154,7 +154,10 @@ export class BunSqliteProvider extends DatabaseProvider {
|
|
|
154
154
|
},
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
// Never migrate in serverless mode - migrations should be applied during deployment
|
|
158
|
+
if (!this.alepha.isServerless()) {
|
|
159
|
+
await this.migrate();
|
|
160
|
+
}
|
|
158
161
|
|
|
159
162
|
this.log.info(`Using Bun SQLite database at ${filepath}`);
|
|
160
163
|
},
|