alepha 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/audits/index.d.ts +417 -338
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts +80 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +451 -4
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.d.ts +833 -830
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/cli/index.d.ts +212 -29
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +320 -219
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +206 -9
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +306 -69
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/orm/index.d.ts +180 -126
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +486 -512
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/redis/index.js +2 -4
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/redis/index.d.ts +400 -29
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/security/index.d.ts +28 -28
- package/dist/security/index.d.ts.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.js +9 -6
- package/dist/vite/index.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/apps/AlephaCli.ts +8 -3
- package/src/cli/apps/AlephaPackageBuilderCli.ts +3 -0
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/ChangelogCommands.ts +187 -317
- package/src/cli/commands/DeployCommands.ts +118 -0
- package/src/cli/commands/DrizzleCommands.ts +28 -8
- package/src/cli/commands/ViteCommands.ts +23 -9
- package/src/cli/defineConfig.ts +15 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/services/AlephaCliUtils.ts +4 -21
- package/src/cli/services/GitMessageParser.ts +77 -0
- package/src/command/helpers/EnvUtils.ts +37 -0
- package/src/command/index.ts +3 -1
- package/src/command/primitives/$command.ts +172 -6
- package/src/command/providers/CliProvider.ts +424 -91
- package/src/core/Alepha.ts +1 -1
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.ts +8 -4
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/services/QueryManager.ts +10 -125
- package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
- package/src/redis/index.ts +65 -3
- package/src/redis/providers/BunRedisProvider.ts +304 -0
- package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/NodeRedisProvider.ts +280 -0
- package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/RedisProvider.ts +134 -140
- package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
- package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
- package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
- package/src/server/core/providers/ServerProvider.ts +7 -4
- package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
- package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/src/vite/tasks/buildServer.ts +1 -0
- package/src/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -3,7 +3,11 @@ import { join } from "node:path";
|
|
|
3
3
|
import { $inject, AlephaError, t } from "alepha";
|
|
4
4
|
import { $command } from "alepha/command";
|
|
5
5
|
import { $logger } from "alepha/logger";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
DatabaseProvider,
|
|
8
|
+
DrizzleKitProvider,
|
|
9
|
+
RepositoryProvider,
|
|
10
|
+
} from "alepha/orm";
|
|
7
11
|
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
8
12
|
|
|
9
13
|
const drizzleCommandFlags = t.object({
|
|
@@ -29,7 +33,7 @@ export class DrizzleCommands {
|
|
|
29
33
|
* Check if database migrations are up to date.
|
|
30
34
|
*/
|
|
31
35
|
check = $command({
|
|
32
|
-
name: "
|
|
36
|
+
name: "check-migrations",
|
|
33
37
|
description: "Check if database migration files are up to date",
|
|
34
38
|
args: t.optional(
|
|
35
39
|
t.text({
|
|
@@ -134,7 +138,7 @@ export class DrizzleCommands {
|
|
|
134
138
|
* - Invokes Drizzle Kit's CLI to generate migration files based on the current schema.
|
|
135
139
|
*/
|
|
136
140
|
generate = $command({
|
|
137
|
-
name: "
|
|
141
|
+
name: "generate",
|
|
138
142
|
description: "Generate migration files based on current database schema",
|
|
139
143
|
summary: false,
|
|
140
144
|
args: t.optional(
|
|
@@ -178,7 +182,7 @@ export class DrizzleCommands {
|
|
|
178
182
|
* - Invokes Drizzle Kit's push command to apply schema changes directly.
|
|
179
183
|
*/
|
|
180
184
|
push = $command({
|
|
181
|
-
name: "
|
|
185
|
+
name: "push",
|
|
182
186
|
description: "Push database schema changes directly to the database",
|
|
183
187
|
summary: false,
|
|
184
188
|
args: t.optional(
|
|
@@ -210,7 +214,7 @@ export class DrizzleCommands {
|
|
|
210
214
|
* - Invokes Drizzle Kit's migrate command to apply pending migrations.
|
|
211
215
|
*/
|
|
212
216
|
migrate = $command({
|
|
213
|
-
name: "
|
|
217
|
+
name: "migrate",
|
|
214
218
|
description: "Apply pending database migrations",
|
|
215
219
|
summary: false,
|
|
216
220
|
args: t.optional(
|
|
@@ -242,7 +246,7 @@ export class DrizzleCommands {
|
|
|
242
246
|
* - Invokes Drizzle Kit's studio command to launch the web-based database browser.
|
|
243
247
|
*/
|
|
244
248
|
studio = $command({
|
|
245
|
-
name: "
|
|
249
|
+
name: "studio",
|
|
246
250
|
description: "Launch Drizzle Studio database browser",
|
|
247
251
|
summary: false,
|
|
248
252
|
args: t.optional(
|
|
@@ -265,6 +269,18 @@ export class DrizzleCommands {
|
|
|
265
269
|
},
|
|
266
270
|
});
|
|
267
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Parent command for database operations.
|
|
274
|
+
*/
|
|
275
|
+
db = $command({
|
|
276
|
+
name: "db",
|
|
277
|
+
description: "Database management commands",
|
|
278
|
+
children: [this.check, this.generate, this.push, this.migrate, this.studio],
|
|
279
|
+
handler: async ({ help }) => {
|
|
280
|
+
help();
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
268
284
|
/**
|
|
269
285
|
* Run a drizzle-kit command for all database providers in an Alepha instance.
|
|
270
286
|
*
|
|
@@ -289,7 +305,7 @@ export class DrizzleCommands {
|
|
|
289
305
|
envFiles.push(`.env.${options.env}`);
|
|
290
306
|
}
|
|
291
307
|
|
|
292
|
-
await this.utils.
|
|
308
|
+
await this.utils.loadEnv(rootDir, envFiles);
|
|
293
309
|
|
|
294
310
|
this.log.debug(`Using project root: ${rootDir}`);
|
|
295
311
|
|
|
@@ -358,7 +374,7 @@ export class DrizzleCommands {
|
|
|
358
374
|
*/
|
|
359
375
|
public async prepareDrizzleConfig(options: {
|
|
360
376
|
kit: any;
|
|
361
|
-
provider:
|
|
377
|
+
provider: DatabaseProvider;
|
|
362
378
|
providerName: string;
|
|
363
379
|
providerUrl: string;
|
|
364
380
|
dialect: string;
|
|
@@ -387,6 +403,10 @@ export class DrizzleCommands {
|
|
|
387
403
|
},
|
|
388
404
|
};
|
|
389
405
|
|
|
406
|
+
if (options.provider.schema) {
|
|
407
|
+
config.schemaFilter = options.provider.schema;
|
|
408
|
+
}
|
|
409
|
+
|
|
390
410
|
if (options.providerName === "d1") {
|
|
391
411
|
config.driver = "d1-http";
|
|
392
412
|
}
|
|
@@ -122,11 +122,6 @@ export class ViteCommands {
|
|
|
122
122
|
description: "Generate sitemap.xml with base URL",
|
|
123
123
|
}),
|
|
124
124
|
),
|
|
125
|
-
prerender: t.optional(
|
|
126
|
-
t.boolean({
|
|
127
|
-
description: "Pre-render static pages",
|
|
128
|
-
}),
|
|
129
|
-
),
|
|
130
125
|
}),
|
|
131
126
|
handler: async ({ flags, args, run, root }) => {
|
|
132
127
|
// Tell viteAlephaBuild plugin to skip - CLI handles all tasks
|
|
@@ -165,7 +160,7 @@ export class ViteCommands {
|
|
|
165
160
|
);
|
|
166
161
|
const viteAlephaBuildOptions = alephaPlugin?.[OPTIONS] || {};
|
|
167
162
|
|
|
168
|
-
await this.utils.
|
|
163
|
+
await this.utils.loadEnv(root, [".env", ".env.production"]);
|
|
169
164
|
|
|
170
165
|
const stats = flags.stats ?? viteAlephaBuildOptions.stats ?? false;
|
|
171
166
|
const hasServer = viteAlephaBuildOptions.serverEntry !== false;
|
|
@@ -254,7 +249,7 @@ export class ViteCommands {
|
|
|
254
249
|
}
|
|
255
250
|
|
|
256
251
|
// Pre-render static pages
|
|
257
|
-
const shouldPrerender =
|
|
252
|
+
const shouldPrerender = clientOptions.prerender;
|
|
258
253
|
|
|
259
254
|
if (shouldPrerender) {
|
|
260
255
|
await run({
|
|
@@ -322,7 +317,24 @@ export class ViteCommands {
|
|
|
322
317
|
public readonly test = $command({
|
|
323
318
|
name: "test",
|
|
324
319
|
description: "Run tests using Vitest",
|
|
325
|
-
|
|
320
|
+
flags: t.object({
|
|
321
|
+
config: t.optional(
|
|
322
|
+
t.string({
|
|
323
|
+
description: "Path to Vitest config file",
|
|
324
|
+
alias: "c",
|
|
325
|
+
}),
|
|
326
|
+
),
|
|
327
|
+
}),
|
|
328
|
+
env: t.object({
|
|
329
|
+
VITEST_ARGS: t.optional(
|
|
330
|
+
t.string({
|
|
331
|
+
default: "",
|
|
332
|
+
description:
|
|
333
|
+
"Additional arguments to pass to Vitest. E.g., --coverage",
|
|
334
|
+
}),
|
|
335
|
+
),
|
|
336
|
+
}),
|
|
337
|
+
handler: async ({ root, flags, env }) => {
|
|
326
338
|
await this.utils.ensureConfig(root, {
|
|
327
339
|
tsconfigJson: true,
|
|
328
340
|
viteConfigTs: true,
|
|
@@ -331,7 +343,9 @@ export class ViteCommands {
|
|
|
331
343
|
// Ensure vitest is installed before running
|
|
332
344
|
await this.utils.ensureDependency(root, "vitest");
|
|
333
345
|
|
|
334
|
-
|
|
346
|
+
const config = flags.config ? `--config=${flags.config}` : "";
|
|
347
|
+
|
|
348
|
+
await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
|
|
335
349
|
},
|
|
336
350
|
});
|
|
337
351
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Alepha } from "alepha";
|
|
2
|
+
import type { CommandPrimitive } from "alepha/command";
|
|
3
|
+
|
|
4
|
+
export type AlephaCliConfig = (alepha: Alepha) => {
|
|
5
|
+
commands?: Record<string, CommandPrimitive>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const defineConfig = (config: AlephaCliConfig) => {
|
|
9
|
+
return (alepha: Alepha) => {
|
|
10
|
+
const { commands } = config(alepha);
|
|
11
|
+
return {
|
|
12
|
+
...commands,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
package/src/cli/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export * from "./apps/AlephaCli.ts";
|
|
2
2
|
export * from "./apps/AlephaPackageBuilderCli.ts";
|
|
3
3
|
export * from "./commands/BiomeCommands.ts";
|
|
4
|
+
export * from "./commands/ChangelogCommands.ts";
|
|
4
5
|
export * from "./commands/CoreCommands.ts";
|
|
6
|
+
export * from "./commands/DeployCommands.ts";
|
|
5
7
|
export * from "./commands/DrizzleCommands.ts";
|
|
6
8
|
export * from "./commands/VerifyCommands.ts";
|
|
7
9
|
export * from "./commands/ViteCommands.ts";
|
|
10
|
+
export * from "./defineConfig.ts";
|
|
8
11
|
export * from "./services/AlephaCliUtils.ts";
|
|
9
12
|
export * from "./version.ts";
|
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { $inject, Alepha, AlephaError } from "alepha";
|
|
5
|
-
import type
|
|
5
|
+
import { EnvUtils, type RunnerMethod } from "alepha/command";
|
|
6
6
|
import { FileSystemProvider } from "alepha/file";
|
|
7
7
|
import { $logger } from "alepha/logger";
|
|
8
8
|
import { boot } from "alepha/vite";
|
|
@@ -31,6 +31,7 @@ import { version } from "../version.ts";
|
|
|
31
31
|
export class AlephaCliUtils {
|
|
32
32
|
protected readonly log = $logger();
|
|
33
33
|
protected readonly fs = $inject(FileSystemProvider);
|
|
34
|
+
protected readonly envUtils = $inject(EnvUtils);
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Execute a command using npx with inherited stdio.
|
|
@@ -499,29 +500,11 @@ ${models.map((it: string) => `export const ${it} = models["${it}"];`).join("\n")
|
|
|
499
500
|
* Reads the .env file in the specified root directory and sets
|
|
500
501
|
* the environment variables in process.env.
|
|
501
502
|
*/
|
|
502
|
-
public async
|
|
503
|
+
public async loadEnv(
|
|
503
504
|
root: string,
|
|
504
505
|
files: string[] = [".env"],
|
|
505
506
|
): Promise<void> {
|
|
506
|
-
|
|
507
|
-
for (const file of [it, `${it}.local`]) {
|
|
508
|
-
const envPath = join(root, file);
|
|
509
|
-
try {
|
|
510
|
-
const envContent = await readFile(envPath, "utf8");
|
|
511
|
-
const lines = envContent.split("\n");
|
|
512
|
-
for (const line of lines) {
|
|
513
|
-
const [key, ...rest] = line.split("=");
|
|
514
|
-
if (key) {
|
|
515
|
-
const value = rest.join("=");
|
|
516
|
-
process.env[key.trim()] = value.trim();
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
this.log.debug(`Loaded environment variables from ${envPath}`);
|
|
520
|
-
} catch {
|
|
521
|
-
this.log.debug(`No ${file} file found at ${envPath}, skipping load.`);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
507
|
+
await this.envUtils.loadEnv(root, files);
|
|
525
508
|
}
|
|
526
509
|
|
|
527
510
|
public async getPackageManager(
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { $logger } from "alepha/logger";
|
|
2
|
+
import {
|
|
3
|
+
type ChangelogOptions,
|
|
4
|
+
DEFAULT_IGNORE,
|
|
5
|
+
} from "../atoms/changelogOptions.ts";
|
|
6
|
+
import type { Commit } from "../commands/ChangelogCommands.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service for parsing git commit messages into structured format.
|
|
10
|
+
*
|
|
11
|
+
* Only parses **conventional commits with a scope**:
|
|
12
|
+
* - `feat(scope): description` → feature
|
|
13
|
+
* - `fix(scope): description` → bug fix
|
|
14
|
+
* - `feat(scope)!: description` → breaking change
|
|
15
|
+
*
|
|
16
|
+
* Commits without scope are ignored, allowing developers to commit
|
|
17
|
+
* work-in-progress changes without polluting release notes:
|
|
18
|
+
* - `cli: work in progress` → ignored (no type)
|
|
19
|
+
* - `fix: quick patch` → ignored (no scope)
|
|
20
|
+
* - `feat(cli): add command` → included
|
|
21
|
+
*/
|
|
22
|
+
export class GitMessageParser {
|
|
23
|
+
protected readonly log = $logger();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse a git commit line into a structured Commit object.
|
|
27
|
+
*
|
|
28
|
+
* **Format:** `type(scope): description` or `type(scope)!: description`
|
|
29
|
+
*
|
|
30
|
+
* **Supported types:** feat, fix, docs, refactor, perf, revert
|
|
31
|
+
*
|
|
32
|
+
* **Breaking changes:** Use `!` before `:` (e.g., `feat(api)!: remove endpoint`)
|
|
33
|
+
*
|
|
34
|
+
* @returns Commit object or null if not matching/ignored
|
|
35
|
+
*/
|
|
36
|
+
parseCommit(line: string, config: ChangelogOptions): Commit | null {
|
|
37
|
+
// Extract hash and message from git log --oneline format
|
|
38
|
+
const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
|
|
39
|
+
if (!match) return null;
|
|
40
|
+
|
|
41
|
+
const [, hash, message] = match;
|
|
42
|
+
const ignore = config.ignore ?? DEFAULT_IGNORE;
|
|
43
|
+
|
|
44
|
+
// Conventional commit with REQUIRED scope: type(scope): description
|
|
45
|
+
// The `!` before `:` marks a breaking change
|
|
46
|
+
const conventionalMatch = message.match(
|
|
47
|
+
/^(feat|fix|docs|refactor|perf|revert)\(([^)]+)\)(!)?:\s*(.+)$/i,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!conventionalMatch) {
|
|
51
|
+
// No match - commit doesn't follow required format
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const [, type, scope, breakingMark, description] = conventionalMatch;
|
|
56
|
+
|
|
57
|
+
// Check if scope should be ignored
|
|
58
|
+
const baseScope = scope.split("/")[0];
|
|
59
|
+
if (ignore.includes(baseScope) || ignore.includes(scope)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Breaking change detection:
|
|
64
|
+
// 1. Explicit `!` marker: feat(api)!: change
|
|
65
|
+
// 2. Word "breaking" in description: feat(api): breaking change to auth
|
|
66
|
+
const breaking =
|
|
67
|
+
breakingMark === "!" || description.toLowerCase().includes("breaking");
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
hash: hash.substring(0, 8),
|
|
71
|
+
type: type.toLowerCase(),
|
|
72
|
+
scope,
|
|
73
|
+
description: description.trim(),
|
|
74
|
+
breaking,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { $logger } from "alepha/logger";
|
|
4
|
+
|
|
5
|
+
export class EnvUtils {
|
|
6
|
+
protected readonly log = $logger();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load environment variables from .env files into process.env.
|
|
10
|
+
* By default, it loads from ".env" and ".env.local".
|
|
11
|
+
* You can specify additional files to load, e.g. [".env", ".env.production"].
|
|
12
|
+
*/
|
|
13
|
+
public async loadEnv(
|
|
14
|
+
root: string,
|
|
15
|
+
files: string[] = [".env"],
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
for (const it of files) {
|
|
18
|
+
for (const file of [it, `${it}.local`]) {
|
|
19
|
+
const envPath = join(root, file);
|
|
20
|
+
try {
|
|
21
|
+
const envContent = await readFile(envPath, "utf8");
|
|
22
|
+
const lines = envContent.split("\n");
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const [key, ...rest] = line.split("=");
|
|
25
|
+
if (key) {
|
|
26
|
+
const value = rest.join("=");
|
|
27
|
+
process.env[key.trim()] = value.trim();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
this.log.debug(`Loaded environment variables from ${envPath}`);
|
|
31
|
+
} catch {
|
|
32
|
+
this.log.debug(`No ${file} file found at ${envPath}, skipping load.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/command/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { $module } from "alepha";
|
|
2
2
|
import { Asker } from "./helpers/Asker.ts";
|
|
3
|
+
import { EnvUtils } from "./helpers/EnvUtils.ts";
|
|
3
4
|
import { PrettyPrint } from "./helpers/PrettyPrint.ts";
|
|
4
5
|
import { Runner } from "./helpers/Runner.ts";
|
|
5
6
|
import { $command } from "./primitives/$command.ts";
|
|
@@ -9,6 +10,7 @@ import { CliProvider } from "./providers/CliProvider.ts";
|
|
|
9
10
|
|
|
10
11
|
export * from "./errors/CommandError.ts";
|
|
11
12
|
export * from "./helpers/Asker.ts";
|
|
13
|
+
export * from "./helpers/EnvUtils.ts";
|
|
12
14
|
export * from "./helpers/PrettyPrint.ts";
|
|
13
15
|
export * from "./helpers/Runner.ts";
|
|
14
16
|
export * from "./primitives/$command.ts";
|
|
@@ -28,7 +30,7 @@ export * from "./providers/CliProvider.ts";
|
|
|
28
30
|
export const AlephaCommand = $module({
|
|
29
31
|
name: "alepha.command",
|
|
30
32
|
primitives: [$command],
|
|
31
|
-
services: [CliProvider, Runner, Asker, PrettyPrint],
|
|
33
|
+
services: [CliProvider, Runner, Asker, PrettyPrint, EnvUtils],
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -19,17 +19,29 @@ import type { RunnerMethod } from "../helpers/Runner.ts";
|
|
|
19
19
|
* This primitive allows you to define a command, its flags, and its handler
|
|
20
20
|
* within your Alepha application structure.
|
|
21
21
|
*/
|
|
22
|
-
export const $command = <
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
export const $command = <
|
|
23
|
+
T extends TObject,
|
|
24
|
+
A extends TSchema,
|
|
25
|
+
E extends TObject,
|
|
26
|
+
>(
|
|
27
|
+
options: CommandPrimitiveOptions<T, A, E>,
|
|
28
|
+
) => createPrimitive(CommandPrimitive<T, A, E>, options);
|
|
25
29
|
|
|
26
30
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
27
31
|
|
|
28
|
-
export interface CommandPrimitiveOptions<
|
|
32
|
+
export interface CommandPrimitiveOptions<
|
|
33
|
+
T extends TObject,
|
|
34
|
+
A extends TSchema,
|
|
35
|
+
E extends TObject = TObject,
|
|
36
|
+
> {
|
|
29
37
|
/**
|
|
30
38
|
* The handler function to execute when the command is matched.
|
|
39
|
+
*
|
|
40
|
+
* For parent commands with children, the handler is called when:
|
|
41
|
+
* - The parent command is invoked without a subcommand
|
|
42
|
+
* - The parent command is invoked with --help (to show available subcommands)
|
|
31
43
|
*/
|
|
32
|
-
handler: (args: CommandHandlerArgs<T, A>) => Async<void>;
|
|
44
|
+
handler: (args: CommandHandlerArgs<T, A, E>) => Async<void>;
|
|
33
45
|
|
|
34
46
|
/**
|
|
35
47
|
* The name of the command. If omitted, the property key is used.
|
|
@@ -53,6 +65,28 @@ export interface CommandPrimitiveOptions<T extends TObject, A extends TSchema> {
|
|
|
53
65
|
*/
|
|
54
66
|
flags?: T;
|
|
55
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A TypeBox object schema defining required environment variables.
|
|
70
|
+
*
|
|
71
|
+
* Environment variables are validated before the handler runs (fail fast).
|
|
72
|
+
* They are displayed in the help output under "Env:" section.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* $command({
|
|
77
|
+
* env: t.object({
|
|
78
|
+
* VERCEL_TOKEN: t.text({ description: "Vercel API token" }),
|
|
79
|
+
* VERCEL_ORG_ID: t.optional(t.text({ description: "Organization ID" })),
|
|
80
|
+
* }),
|
|
81
|
+
* handler: async ({ env }) => {
|
|
82
|
+
* // env.VERCEL_TOKEN is typed & guaranteed to exist
|
|
83
|
+
* console.log(env.VERCEL_TOKEN);
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
env?: E;
|
|
89
|
+
|
|
56
90
|
/**
|
|
57
91
|
* An optional TypeBox schema defining the arguments for the command.
|
|
58
92
|
*
|
|
@@ -137,6 +171,86 @@ export interface CommandPrimitiveOptions<T extends TObject, A extends TSchema> {
|
|
|
137
171
|
* If true, this command will be hidden from the help output.
|
|
138
172
|
*/
|
|
139
173
|
hide?: boolean;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Adds a `--mode, -m` flag to load environment files.
|
|
177
|
+
*
|
|
178
|
+
* When enabled:
|
|
179
|
+
* - Loads `.env` and `.env.local` by default
|
|
180
|
+
* - With `--mode production`, also loads `.env.production` and `.env.production.local`
|
|
181
|
+
* - The mode value is exposed in the handler as `mode: string | undefined`
|
|
182
|
+
*
|
|
183
|
+
* Set to `true` to enable with no default, or a string to set a default mode.
|
|
184
|
+
*
|
|
185
|
+
* This follows Vite's environment loading convention.
|
|
186
|
+
* @see https://vite.dev/guide/env-and-mode
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* // No default mode
|
|
191
|
+
* build = $command({
|
|
192
|
+
* mode: true,
|
|
193
|
+
* handler: async ({ mode }) => {
|
|
194
|
+
* console.log(`Building for ${mode ?? 'development'}...`);
|
|
195
|
+
* }
|
|
196
|
+
* });
|
|
197
|
+
*
|
|
198
|
+
* // Default mode "production"
|
|
199
|
+
* deploy = $command({
|
|
200
|
+
* mode: "production",
|
|
201
|
+
* handler: async ({ mode }) => {
|
|
202
|
+
* console.log(`Deploying for ${mode}...`); // always defined
|
|
203
|
+
* }
|
|
204
|
+
* });
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* Usage:
|
|
208
|
+
* - `cli build` - loads .env (mode = undefined)
|
|
209
|
+
* - `cli build --mode production` - loads .env and .env.production
|
|
210
|
+
* - `cli deploy` - loads .env and .env.production (default mode)
|
|
211
|
+
* - `cli deploy --mode staging` - loads .env and .env.staging
|
|
212
|
+
*/
|
|
213
|
+
mode?: boolean | string;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Child commands (subcommands) for this command.
|
|
217
|
+
*
|
|
218
|
+
* When children are defined, the command becomes a parent command that
|
|
219
|
+
* can be invoked with space-separated subcommands:
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* class DeployCommands {
|
|
224
|
+
* // Subcommands
|
|
225
|
+
* vercel = $command({
|
|
226
|
+
* description: "Deploy to Vercel",
|
|
227
|
+
* handler: async () => { ... }
|
|
228
|
+
* });
|
|
229
|
+
*
|
|
230
|
+
* cloudflare = $command({
|
|
231
|
+
* description: "Deploy to Cloudflare",
|
|
232
|
+
* handler: async () => { ... }
|
|
233
|
+
* });
|
|
234
|
+
*
|
|
235
|
+
* // Parent command with children
|
|
236
|
+
* deploy = $command({
|
|
237
|
+
* description: "Deploy the application",
|
|
238
|
+
* children: [this.vercel, this.cloudflare],
|
|
239
|
+
* handler: async () => {
|
|
240
|
+
* // Called when "deploy" is invoked without subcommand
|
|
241
|
+
* console.log("Available: deploy vercel, deploy cloudflare");
|
|
242
|
+
* }
|
|
243
|
+
* });
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* This allows CLI usage like:
|
|
248
|
+
* - `cli deploy vercel` - runs the vercel subcommand
|
|
249
|
+
* - `cli deploy cloudflare` - runs the cloudflare subcommand
|
|
250
|
+
* - `cli deploy` - runs the parent handler (shows available subcommands)
|
|
251
|
+
* - `cli deploy --help` - shows help with all available subcommands
|
|
252
|
+
*/
|
|
253
|
+
children?: CommandPrimitive<any, any>[];
|
|
140
254
|
}
|
|
141
255
|
|
|
142
256
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -144,8 +258,10 @@ export interface CommandPrimitiveOptions<T extends TObject, A extends TSchema> {
|
|
|
144
258
|
export class CommandPrimitive<
|
|
145
259
|
T extends TObject = TObject,
|
|
146
260
|
A extends TSchema = TSchema,
|
|
147
|
-
|
|
261
|
+
E extends TObject = TObject,
|
|
262
|
+
> extends Primitive<CommandPrimitiveOptions<T, A, E>> {
|
|
148
263
|
public readonly flags = this.options.flags ?? t.object({});
|
|
264
|
+
public readonly env = this.options.env ?? t.object({});
|
|
149
265
|
public readonly aliases = this.options.aliases ?? [];
|
|
150
266
|
|
|
151
267
|
protected onInit() {
|
|
@@ -166,6 +282,29 @@ export class CommandPrimitive<
|
|
|
166
282
|
}
|
|
167
283
|
return this.options.name ?? `${this.config.propertyKey}`;
|
|
168
284
|
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get the child commands (subcommands) for this command.
|
|
288
|
+
*/
|
|
289
|
+
public get children(): CommandPrimitive<any, any>[] {
|
|
290
|
+
return this.options.children ?? [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if this command has child commands (is a parent command).
|
|
295
|
+
*/
|
|
296
|
+
public get hasChildren(): boolean {
|
|
297
|
+
return this.children.length > 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Find a child command by name or alias.
|
|
302
|
+
*/
|
|
303
|
+
public findChild(name: string): CommandPrimitive<any, any> | undefined {
|
|
304
|
+
return this.children.find(
|
|
305
|
+
(child) => child.name === name || child.aliases.includes(name),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
169
308
|
}
|
|
170
309
|
|
|
171
310
|
$command[KIND] = CommandPrimitive;
|
|
@@ -175,9 +314,11 @@ $command[KIND] = CommandPrimitive;
|
|
|
175
314
|
export interface CommandHandlerArgs<
|
|
176
315
|
T extends TObject,
|
|
177
316
|
A extends TSchema = TSchema,
|
|
317
|
+
E extends TObject = TObject,
|
|
178
318
|
> {
|
|
179
319
|
flags: Static<T>;
|
|
180
320
|
args: A extends TSchema ? Static<A> : Array<string>;
|
|
321
|
+
env: Static<E>;
|
|
181
322
|
run: RunnerMethod;
|
|
182
323
|
ask: AskMethod;
|
|
183
324
|
glob: typeof glob;
|
|
@@ -187,4 +328,29 @@ export interface CommandHandlerArgs<
|
|
|
187
328
|
* The root directory where the command is executed.
|
|
188
329
|
*/
|
|
189
330
|
root: string;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Display help for the current command.
|
|
334
|
+
*
|
|
335
|
+
* Useful for parent commands with children to show available subcommands
|
|
336
|
+
* when invoked without a specific subcommand.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* deploy = $command({
|
|
341
|
+
* children: [this.vercel, this.cloudflare],
|
|
342
|
+
* handler: async ({ help }) => {
|
|
343
|
+
* help(); // Shows available subcommands
|
|
344
|
+
* }
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
help: () => void;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The current execution mode (e.g., "development", "production", "staging").
|
|
352
|
+
*
|
|
353
|
+
* Use --mode flag to set this value when running the command.
|
|
354
|
+
*/
|
|
355
|
+
mode?: string;
|
|
190
356
|
}
|