alepha 0.11.9 → 0.11.11
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 +61 -17
- package/dist/index.cjs +17089 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17088 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -365
- package/src/assets/biomeJson.ts +33 -0
- package/src/assets/tsconfigJson.ts +18 -0
- package/src/assets/viteConfigTs.ts +14 -0
- package/src/commands/BiomeCommands.ts +41 -0
- package/src/commands/CoreCommands.ts +169 -0
- package/src/commands/DrizzleCommands.ts +228 -0
- package/src/commands/VerifyCommands.ts +44 -0
- package/src/commands/ViteCommands.ts +119 -0
- package/src/index.ts +35 -0
- package/src/services/ProcessRunner.ts +89 -0
- package/src/services/ProjectUtils.ts +508 -0
- package/src/version.ts +7 -0
- package/api/files.cjs +0 -8
- package/api/files.d.ts +0 -438
- package/api/files.js +0 -1
- package/api/jobs.cjs +0 -8
- package/api/jobs.d.ts +0 -327
- package/api/jobs.js +0 -1
- package/api/notifications.cjs +0 -8
- package/api/notifications.d.ts +0 -263
- package/api/notifications.js +0 -1
- package/api/users.cjs +0 -8
- package/api/users.d.ts +0 -923
- package/api/users.js +0 -1
- package/api/verifications.cjs +0 -8
- package/api/verifications.d.ts +0 -1
- package/api/verifications.js +0 -1
- package/batch.cjs +0 -8
- package/batch.d.ts +0 -154
- package/batch.js +0 -1
- package/bucket.cjs +0 -8
- package/bucket.d.ts +0 -520
- package/bucket.js +0 -1
- package/cache/redis.cjs +0 -8
- package/cache/redis.d.ts +0 -40
- package/cache/redis.js +0 -1
- package/cache.cjs +0 -8
- package/cache.d.ts +0 -288
- package/cache.js +0 -1
- package/command.cjs +0 -8
- package/command.d.ts +0 -269
- package/command.js +0 -1
- package/core.cjs +0 -8
- package/core.d.ts +0 -1904
- package/core.js +0 -1
- package/datetime.cjs +0 -8
- package/datetime.d.ts +0 -144
- package/datetime.js +0 -1
- package/devtools.cjs +0 -8
- package/devtools.d.ts +0 -252
- package/devtools.js +0 -1
- package/email.cjs +0 -8
- package/email.d.ts +0 -187
- package/email.js +0 -1
- package/fake.cjs +0 -8
- package/fake.d.ts +0 -73
- package/fake.js +0 -1
- package/file.cjs +0 -8
- package/file.d.ts +0 -528
- package/file.js +0 -1
- package/lock/redis.cjs +0 -8
- package/lock/redis.d.ts +0 -24
- package/lock/redis.js +0 -1
- package/lock.cjs +0 -8
- package/lock.d.ts +0 -552
- package/lock.js +0 -1
- package/logger.cjs +0 -8
- package/logger.d.ts +0 -287
- package/logger.js +0 -1
- package/postgres.cjs +0 -8
- package/postgres.d.ts +0 -2143
- package/postgres.js +0 -1
- package/queue/redis.cjs +0 -8
- package/queue/redis.d.ts +0 -29
- package/queue/redis.js +0 -1
- package/queue.cjs +0 -8
- package/queue.d.ts +0 -760
- package/queue.js +0 -1
- package/react/auth.cjs +0 -8
- package/react/auth.d.ts +0 -504
- package/react/auth.js +0 -1
- package/react/form.cjs +0 -8
- package/react/form.d.ts +0 -211
- package/react/form.js +0 -1
- package/react/head.cjs +0 -8
- package/react/head.d.ts +0 -120
- package/react/head.js +0 -1
- package/react/i18n.cjs +0 -8
- package/react/i18n.d.ts +0 -168
- package/react/i18n.js +0 -1
- package/react.cjs +0 -8
- package/react.d.ts +0 -1263
- package/react.js +0 -1
- package/redis.cjs +0 -8
- package/redis.d.ts +0 -82
- package/redis.js +0 -1
- package/retry.cjs +0 -8
- package/retry.d.ts +0 -162
- package/retry.js +0 -1
- package/router.cjs +0 -8
- package/router.d.ts +0 -45
- package/router.js +0 -1
- package/scheduler.cjs +0 -8
- package/scheduler.d.ts +0 -145
- package/scheduler.js +0 -1
- package/security.cjs +0 -8
- package/security.d.ts +0 -586
- package/security.js +0 -1
- package/server/cache.cjs +0 -8
- package/server/cache.d.ts +0 -163
- package/server/cache.js +0 -1
- package/server/compress.cjs +0 -8
- package/server/compress.d.ts +0 -38
- package/server/compress.js +0 -1
- package/server/cookies.cjs +0 -8
- package/server/cookies.d.ts +0 -144
- package/server/cookies.js +0 -1
- package/server/cors.cjs +0 -8
- package/server/cors.d.ts +0 -45
- package/server/cors.js +0 -1
- package/server/health.cjs +0 -8
- package/server/health.d.ts +0 -58
- package/server/health.js +0 -1
- package/server/helmet.cjs +0 -8
- package/server/helmet.d.ts +0 -98
- package/server/helmet.js +0 -1
- package/server/links.cjs +0 -8
- package/server/links.d.ts +0 -322
- package/server/links.js +0 -1
- package/server/metrics.cjs +0 -8
- package/server/metrics.d.ts +0 -35
- package/server/metrics.js +0 -1
- package/server/multipart.cjs +0 -8
- package/server/multipart.d.ts +0 -42
- package/server/multipart.js +0 -1
- package/server/proxy.cjs +0 -8
- package/server/proxy.d.ts +0 -234
- package/server/proxy.js +0 -1
- package/server/security.cjs +0 -8
- package/server/security.d.ts +0 -92
- package/server/security.js +0 -1
- package/server/static.cjs +0 -8
- package/server/static.d.ts +0 -119
- package/server/static.js +0 -1
- package/server/swagger.cjs +0 -8
- package/server/swagger.d.ts +0 -161
- package/server/swagger.js +0 -1
- package/server.cjs +0 -8
- package/server.d.ts +0 -849
- package/server.js +0 -1
- package/topic/redis.cjs +0 -8
- package/topic/redis.d.ts +0 -42
- package/topic/redis.js +0 -1
- package/topic.cjs +0 -8
- package/topic.d.ts +0 -819
- package/topic.js +0 -1
- package/vite.cjs +0 -8
- package/vite.d.ts +0 -186
- package/vite.js +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { $command, CliProvider } from "@alepha/command";
|
|
4
|
+
import { $inject, t } from "@alepha/core";
|
|
5
|
+
import { $logger } from "@alepha/logger";
|
|
6
|
+
import { ProjectUtils } from "../services/ProjectUtils.ts";
|
|
7
|
+
import { version } from "../version.ts";
|
|
8
|
+
|
|
9
|
+
export class CoreCommands {
|
|
10
|
+
protected readonly log = $logger();
|
|
11
|
+
protected readonly cli = $inject(CliProvider);
|
|
12
|
+
protected readonly utils = $inject(ProjectUtils);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Called when no command is provided
|
|
16
|
+
*/
|
|
17
|
+
public readonly root = $command({
|
|
18
|
+
root: true,
|
|
19
|
+
flags: t.object({
|
|
20
|
+
version: t.optional(
|
|
21
|
+
t.boolean({
|
|
22
|
+
description: "Show Alepha CLI version",
|
|
23
|
+
aliases: ["v"],
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
26
|
+
}),
|
|
27
|
+
handler: async ({ flags }) => {
|
|
28
|
+
if (flags.version) {
|
|
29
|
+
this.log.info(version);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.cli.printHelp();
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new Alepha project based on one of the sample projects (for now, only one sample project is available)
|
|
39
|
+
*/
|
|
40
|
+
public readonly create = $command({
|
|
41
|
+
name: "create",
|
|
42
|
+
description: "Create a new Alepha project",
|
|
43
|
+
aliases: ["new"],
|
|
44
|
+
args: t.text({
|
|
45
|
+
title: "name",
|
|
46
|
+
}),
|
|
47
|
+
flags: t.object({
|
|
48
|
+
yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
|
|
49
|
+
pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
|
|
50
|
+
}),
|
|
51
|
+
summary: false,
|
|
52
|
+
handler: async ({ run, args, flags, root }) => {
|
|
53
|
+
const name = args;
|
|
54
|
+
const dest = join(root, name);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await access(dest);
|
|
58
|
+
this.log.error(
|
|
59
|
+
`Directory "${name}" already exists. Please choose a different project name.`,
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
} catch {
|
|
63
|
+
// Directory does not exist, proceed
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let installCmd = "npm install";
|
|
67
|
+
let execCmd = "npx";
|
|
68
|
+
if (flags.yarn) {
|
|
69
|
+
installCmd = "yarn";
|
|
70
|
+
execCmd = "yarn";
|
|
71
|
+
} else if (flags.pnpm) {
|
|
72
|
+
installCmd = "pnpm install";
|
|
73
|
+
execCmd = "pnpm";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await mkdir(dest, { recursive: true }).catch(() => null);
|
|
77
|
+
|
|
78
|
+
await run("Downloading sample project", () =>
|
|
79
|
+
this.utils.downloadSampleProject(dest),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (flags.yarn) {
|
|
83
|
+
await this.utils.ensureYarn(dest);
|
|
84
|
+
await run(`cd ${name} && yarn set version stable`, {
|
|
85
|
+
alias: "Setting Yarn to stable version",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await run(`cd ${name} && ${installCmd}`, {
|
|
90
|
+
alias: "Installing dependencies",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await run(`cd ${name} && npx alepha lint`, {
|
|
94
|
+
alias: "Linting code",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await run(`cd ${name} && npx alepha typecheck`, {
|
|
98
|
+
alias: "Type checking",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await run(`cd ${name} && npx alepha test`, {
|
|
102
|
+
alias: "Running tests",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await run(`cd ${name} && npx alepha build`, {
|
|
106
|
+
alias: "Building project",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.log.info("");
|
|
110
|
+
this.log.info(`$ cd ${name} && ${execCmd} alepha dev`.trim());
|
|
111
|
+
this.log.info("");
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Clean the project, removing the "dist" directory
|
|
117
|
+
*/
|
|
118
|
+
public readonly clean = $command({
|
|
119
|
+
name: "clean",
|
|
120
|
+
description: "Clean the project",
|
|
121
|
+
handler: async ({ run }) => {
|
|
122
|
+
await run.rm("./dist");
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Ensure the project has the necessary Alepha configuration files.
|
|
128
|
+
* Add the correct dependencies to package.json and install them.
|
|
129
|
+
*/
|
|
130
|
+
public readonly init = $command({
|
|
131
|
+
name: "init",
|
|
132
|
+
description: "Add missing Alepha configuration files to the project",
|
|
133
|
+
flags: t.object({
|
|
134
|
+
// TODO:
|
|
135
|
+
// force: t.boolean({
|
|
136
|
+
// description: "If true, all config files will be overwritten",
|
|
137
|
+
// }),
|
|
138
|
+
// choose package manager
|
|
139
|
+
yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
|
|
140
|
+
// choose which dependencies to add
|
|
141
|
+
api: t.optional(
|
|
142
|
+
t.boolean({ description: "Include Alepha Server dependencies" }),
|
|
143
|
+
),
|
|
144
|
+
react: t.optional(
|
|
145
|
+
t.boolean({ description: "Include Alepha React dependencies" }),
|
|
146
|
+
),
|
|
147
|
+
orm: t.optional(
|
|
148
|
+
t.boolean({ description: "Include Alepha ORM dependencies" }),
|
|
149
|
+
),
|
|
150
|
+
}),
|
|
151
|
+
handler: async ({ run, flags, root }) => {
|
|
152
|
+
await run("Ensuring Alepha configuration files", async () => {
|
|
153
|
+
await this.utils.ensureTsConfig(root);
|
|
154
|
+
await this.utils.ensurePackageJson(root, flags);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (flags.yarn) {
|
|
158
|
+
await this.utils.ensureYarn(root);
|
|
159
|
+
await run("yarn install", {
|
|
160
|
+
alias: "Installing dependencies with Yarn",
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
await run("npm install", {
|
|
164
|
+
alias: "Installing dependencies with npm",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { $command } from "@alepha/command";
|
|
5
|
+
import { $inject, AlephaError, t } from "@alepha/core";
|
|
6
|
+
import { $logger } from "@alepha/logger";
|
|
7
|
+
import { ProcessRunner } from "../services/ProcessRunner.ts";
|
|
8
|
+
import { ProjectUtils } from "../services/ProjectUtils.ts";
|
|
9
|
+
|
|
10
|
+
export class DrizzleCommands {
|
|
11
|
+
log = $logger();
|
|
12
|
+
runner = $inject(ProcessRunner);
|
|
13
|
+
utils = $inject(ProjectUtils);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if database migrations are up to date
|
|
17
|
+
*
|
|
18
|
+
* - Loads the Alepha instance from the specified entry file.
|
|
19
|
+
* - Retrieves all repository descriptors to gather database models.
|
|
20
|
+
* - Reads the last migration snapshot from the migration journal.
|
|
21
|
+
* - Generates the current database schema representation.
|
|
22
|
+
* - Compares the current schema with the last snapshot to detect changes.
|
|
23
|
+
* - If changes are detected, prompts the user to run the migration generation command!
|
|
24
|
+
*/
|
|
25
|
+
check = $command({
|
|
26
|
+
name: "db:check-migrations",
|
|
27
|
+
description: "Verify database migration files are up to date",
|
|
28
|
+
args: t.optional(
|
|
29
|
+
t.text({
|
|
30
|
+
title: "path",
|
|
31
|
+
description: "Path to the Alepha server entry file",
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
handler: async ({ args, root }) => {
|
|
35
|
+
const rootDir = root;
|
|
36
|
+
this.log.debug(`Using project root: ${rootDir}`);
|
|
37
|
+
const { alepha } = await this.utils.loadAlephaFromServerEntryFile(
|
|
38
|
+
rootDir,
|
|
39
|
+
args,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const models: any[] = [];
|
|
43
|
+
const repositories = alepha.descriptors("repository") as any[];
|
|
44
|
+
const kit = createRequire(import.meta.url)("drizzle-kit/api");
|
|
45
|
+
const migrationDir = join(rootDir, "migrations");
|
|
46
|
+
|
|
47
|
+
const journalFile = await readFile(
|
|
48
|
+
`${migrationDir}/meta/_journal.json`,
|
|
49
|
+
"utf-8",
|
|
50
|
+
).catch(() => null);
|
|
51
|
+
|
|
52
|
+
if (!journalFile) {
|
|
53
|
+
this.log.info(`No migration journal found.`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const journal = JSON.parse(journalFile);
|
|
58
|
+
|
|
59
|
+
const lastMigration = journal.entries[journal.entries.length - 1];
|
|
60
|
+
|
|
61
|
+
const lastSnapshot = JSON.parse(
|
|
62
|
+
await readFile(
|
|
63
|
+
`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`,
|
|
64
|
+
"utf-8",
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
for (const repository of repositories) {
|
|
69
|
+
if (!models.includes(repository.table)) {
|
|
70
|
+
models.push(repository.table);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const now = kit.generateDrizzleJson(models, lastSnapshot.id);
|
|
75
|
+
|
|
76
|
+
const migrationStatements = await new Promise<Array<any>>((resolve) => {
|
|
77
|
+
(async () => {
|
|
78
|
+
const timer = setTimeout(() => {
|
|
79
|
+
resolve([{ message: "Migration generation timed out." }]);
|
|
80
|
+
}, 5000);
|
|
81
|
+
const statements = await kit.generateMigration(lastSnapshot, now);
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
resolve(statements);
|
|
84
|
+
})();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (migrationStatements.length === 0) {
|
|
88
|
+
this.log.info("No changes detected.");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.log.info("");
|
|
93
|
+
this.log.info("Detected migration statements:");
|
|
94
|
+
this.log.info("");
|
|
95
|
+
for (const stmt of migrationStatements) {
|
|
96
|
+
this.log.info(stmt);
|
|
97
|
+
}
|
|
98
|
+
this.log.info("");
|
|
99
|
+
|
|
100
|
+
this.log.info(
|
|
101
|
+
`At least ${migrationStatements.length} change(s) detected.`,
|
|
102
|
+
);
|
|
103
|
+
this.log.info(
|
|
104
|
+
"Please, run 'alepha db:generate' to update the migration files.",
|
|
105
|
+
);
|
|
106
|
+
this.log.info("");
|
|
107
|
+
|
|
108
|
+
throw new AlephaError("Database migrations are not up to date.");
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate database migration files
|
|
114
|
+
*
|
|
115
|
+
* - Loads the Alepha instance from the specified entry file.
|
|
116
|
+
* - Retrieves all repository descriptors to gather database models.
|
|
117
|
+
* - Creates temporary entity definitions based on the current database schema.
|
|
118
|
+
* - Writes these definitions to a temporary schema file. (node_modules/.db/entities.ts)
|
|
119
|
+
* - Invokes Drizzle Kit's CLI to generate migration files based on the current schema.
|
|
120
|
+
*/
|
|
121
|
+
generate = $command({
|
|
122
|
+
name: "db:generate",
|
|
123
|
+
description: "Generate migration files based on current database schema",
|
|
124
|
+
summary: false,
|
|
125
|
+
args: t.optional(
|
|
126
|
+
t.text({
|
|
127
|
+
title: "path",
|
|
128
|
+
description: "Path to the Alepha server entry file",
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
handler: async ({ args, root }) => {
|
|
132
|
+
await this.utils.runDrizzleKitCommand({
|
|
133
|
+
root,
|
|
134
|
+
args,
|
|
135
|
+
command: "generate",
|
|
136
|
+
logMessage: (providerName, dialect) =>
|
|
137
|
+
`Generate '${providerName}' migrations (${dialect}) ...`,
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Push database schema changes directly to the database
|
|
144
|
+
*
|
|
145
|
+
* - Loads the Alepha instance from the specified entry file.
|
|
146
|
+
* - Retrieves all repository descriptors to gather database models.
|
|
147
|
+
* - Creates temporary entity definitions and Drizzle config.
|
|
148
|
+
* - Invokes Drizzle Kit's push command to apply schema changes directly.
|
|
149
|
+
*/
|
|
150
|
+
push = $command({
|
|
151
|
+
name: "db:push",
|
|
152
|
+
description: "Push database schema changes directly to the database",
|
|
153
|
+
summary: false,
|
|
154
|
+
args: t.optional(
|
|
155
|
+
t.text({
|
|
156
|
+
title: "path",
|
|
157
|
+
description: "Path to the Alepha server entry file",
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
handler: async ({ root, args }) => {
|
|
161
|
+
await this.utils.runDrizzleKitCommand({
|
|
162
|
+
root,
|
|
163
|
+
args,
|
|
164
|
+
command: "push",
|
|
165
|
+
logMessage: (providerName, dialect) =>
|
|
166
|
+
`Push '${providerName}' schema (${dialect}) ...`,
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Apply pending database migrations
|
|
173
|
+
*
|
|
174
|
+
* - Loads the Alepha instance from the specified entry file.
|
|
175
|
+
* - Retrieves all repository descriptors to gather database models.
|
|
176
|
+
* - Creates temporary entity definitions and Drizzle config.
|
|
177
|
+
* - Invokes Drizzle Kit's migrate command to apply pending migrations.
|
|
178
|
+
*/
|
|
179
|
+
migrate = $command({
|
|
180
|
+
name: "db:migrate",
|
|
181
|
+
description: "Apply pending database migrations",
|
|
182
|
+
summary: false,
|
|
183
|
+
args: t.optional(
|
|
184
|
+
t.text({
|
|
185
|
+
title: "path",
|
|
186
|
+
description: "Path to the Alepha server entry file",
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
handler: async ({ root, args }) => {
|
|
190
|
+
await this.utils.runDrizzleKitCommand({
|
|
191
|
+
root,
|
|
192
|
+
args,
|
|
193
|
+
command: "migrate",
|
|
194
|
+
logMessage: (providerName, dialect) =>
|
|
195
|
+
`Migrate '${providerName}' database (${dialect}) ...`,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Launch Drizzle Studio database browser
|
|
202
|
+
*
|
|
203
|
+
* - Loads the Alepha instance from the specified entry file.
|
|
204
|
+
* - Retrieves all repository descriptors to gather database models.
|
|
205
|
+
* - Creates temporary entity definitions and Drizzle config.
|
|
206
|
+
* - Invokes Drizzle Kit's studio command to launch the web-based database browser.
|
|
207
|
+
*/
|
|
208
|
+
studio = $command({
|
|
209
|
+
name: "db:studio",
|
|
210
|
+
description: "Launch Drizzle Studio database browser",
|
|
211
|
+
summary: false,
|
|
212
|
+
args: t.optional(
|
|
213
|
+
t.text({
|
|
214
|
+
title: "path",
|
|
215
|
+
description: "Path to the Alepha server entry file",
|
|
216
|
+
}),
|
|
217
|
+
),
|
|
218
|
+
handler: async ({ root, args }) => {
|
|
219
|
+
await this.utils.runDrizzleKitCommand({
|
|
220
|
+
root,
|
|
221
|
+
args,
|
|
222
|
+
command: "studio",
|
|
223
|
+
logMessage: (providerName, dialect) =>
|
|
224
|
+
`Launch Studio for '${providerName}' (${dialect}) ...`,
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { $command } from "@alepha/command";
|
|
2
|
+
import { $inject } from "@alepha/core";
|
|
3
|
+
import { ProcessRunner } from "../services/ProcessRunner.ts";
|
|
4
|
+
|
|
5
|
+
export class VerifyCommands {
|
|
6
|
+
protected readonly processRunner = $inject(ProcessRunner);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run a series of verification commands to ensure code quality and correctness.
|
|
10
|
+
*
|
|
11
|
+
* This command runs the following checks in order:
|
|
12
|
+
* 1. Clean the project
|
|
13
|
+
* 2. Format the code
|
|
14
|
+
* 3. Lint the code
|
|
15
|
+
* 4. Run tests
|
|
16
|
+
* 5. Type check the code
|
|
17
|
+
* 8. Build the project
|
|
18
|
+
* 9. Clean the project again
|
|
19
|
+
*/
|
|
20
|
+
public readonly verify = $command({
|
|
21
|
+
name: "verify",
|
|
22
|
+
description: "Verify the Alepha project",
|
|
23
|
+
handler: async ({ run }) => {
|
|
24
|
+
await run("alepha clean");
|
|
25
|
+
await run("alepha format");
|
|
26
|
+
await run("alepha lint");
|
|
27
|
+
await run("alepha test");
|
|
28
|
+
await run("alepha typecheck");
|
|
29
|
+
await run("alepha build");
|
|
30
|
+
await run("alepha clean");
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Run TypeScript type checking across the codebase with no emit.
|
|
36
|
+
*/
|
|
37
|
+
public readonly typecheck = $command({
|
|
38
|
+
name: "typecheck",
|
|
39
|
+
description: "Check TypeScript types across the codebase",
|
|
40
|
+
handler: async () => {
|
|
41
|
+
await this.processRunner.exec("tsc --noEmit");
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { access, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { $command } from "@alepha/command";
|
|
4
|
+
import { $inject, boot, t } from "@alepha/core";
|
|
5
|
+
import { $logger } from "@alepha/logger";
|
|
6
|
+
import { ProcessRunner } from "../services/ProcessRunner.ts";
|
|
7
|
+
import { ProjectUtils } from "../services/ProjectUtils.ts";
|
|
8
|
+
|
|
9
|
+
export class ViteCommands {
|
|
10
|
+
protected readonly log = $logger();
|
|
11
|
+
protected readonly runner = $inject(ProcessRunner);
|
|
12
|
+
protected readonly utils = $inject(ProjectUtils);
|
|
13
|
+
|
|
14
|
+
public readonly run = $command({
|
|
15
|
+
name: "run",
|
|
16
|
+
description: "Run a TypeScript file directly",
|
|
17
|
+
flags: t.object({
|
|
18
|
+
watch: t.optional(
|
|
19
|
+
t.boolean({ description: "Watch file for changes", alias: "w" }),
|
|
20
|
+
),
|
|
21
|
+
}),
|
|
22
|
+
summary: false,
|
|
23
|
+
args: t.text({ title: "path", description: "Filepath to run" }),
|
|
24
|
+
handler: async ({ args, flags, root }) => {
|
|
25
|
+
await this.utils.ensureTsConfig(root);
|
|
26
|
+
await this.runner.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Will run the project in watch mode.
|
|
32
|
+
*
|
|
33
|
+
* - If an index.html file is found in the project root, it will run Vite in dev mode.
|
|
34
|
+
* - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
|
|
35
|
+
*/
|
|
36
|
+
public readonly dev = $command({
|
|
37
|
+
name: "dev",
|
|
38
|
+
description: "Run the project in development mode",
|
|
39
|
+
args: t.optional(t.text({ title: "path", description: "Filepath to run" })),
|
|
40
|
+
handler: async ({ args, root }) => {
|
|
41
|
+
await this.utils.ensureTsConfig(root);
|
|
42
|
+
await this.utils.ensurePackageJsonModule(root);
|
|
43
|
+
const entry = await boot.getServerEntry(root, args);
|
|
44
|
+
this.log.trace("Entry file found", { entry });
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await access(join(root, "index.html"));
|
|
48
|
+
} catch {
|
|
49
|
+
this.log.trace("No index.html found, running entry file with tsx");
|
|
50
|
+
await this.runner.exec(`tsx watch ${entry}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const configPath = await this.utils.getViteConfigPath(
|
|
55
|
+
root,
|
|
56
|
+
args ? entry : undefined,
|
|
57
|
+
);
|
|
58
|
+
this.log.trace("Vite config found", { configPath });
|
|
59
|
+
await this.runner.exec(`vite -c=${configPath}`);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
public readonly build = $command({
|
|
64
|
+
name: "build",
|
|
65
|
+
description: "Build the project for production",
|
|
66
|
+
args: t.optional(
|
|
67
|
+
t.text({ title: "path", description: "Filepath to build" }),
|
|
68
|
+
),
|
|
69
|
+
flags: t.object({
|
|
70
|
+
config: t.optional(
|
|
71
|
+
t.text({ aliases: ["c"], description: "Path to config file" }),
|
|
72
|
+
),
|
|
73
|
+
stats: t.optional(
|
|
74
|
+
t.boolean({
|
|
75
|
+
description: "Generate build stats report",
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
}),
|
|
79
|
+
handler: async ({ flags, args }) => {
|
|
80
|
+
const root = process.cwd();
|
|
81
|
+
await this.utils.ensureTsConfig(root);
|
|
82
|
+
await this.utils.ensurePackageJsonModule(root);
|
|
83
|
+
const entry = await boot.getServerEntry(root, args);
|
|
84
|
+
this.log.trace("Entry file found", { entry });
|
|
85
|
+
|
|
86
|
+
await rm("dist", { recursive: true, force: true });
|
|
87
|
+
|
|
88
|
+
// DISABLED FOR NOW (waiting for vite-rolldown)
|
|
89
|
+
// if (flags.lib) {
|
|
90
|
+
// await this.runner.exec(
|
|
91
|
+
// `tsdown${flags.config ? ` -c=${flags.config}` : ""}`,
|
|
92
|
+
// );
|
|
93
|
+
// return;
|
|
94
|
+
// }
|
|
95
|
+
|
|
96
|
+
const configPath = await this.utils.getViteConfigPath(
|
|
97
|
+
root,
|
|
98
|
+
args ? entry : undefined,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const env: Record<string, string> = {};
|
|
102
|
+
if (flags.stats) {
|
|
103
|
+
env.ALEPHA_BUILD_STATS = "true";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await this.runner.exec(`vite build -c=${configPath}`, env);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
public readonly test = $command({
|
|
111
|
+
name: "test",
|
|
112
|
+
description: "Run tests using Vitest",
|
|
113
|
+
handler: async ({ root }) => {
|
|
114
|
+
await this.utils.ensureTsConfig(root);
|
|
115
|
+
const configPath = await this.utils.getViteConfigPath(root);
|
|
116
|
+
await this.runner.exec(`vitest run -c=${configPath}`);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx";
|
|
3
|
+
import { $module, Alepha, run } from "@alepha/core";
|
|
4
|
+
import { BiomeCommands } from "./commands/BiomeCommands.ts";
|
|
5
|
+
import { CoreCommands } from "./commands/CoreCommands.ts";
|
|
6
|
+
import { DrizzleCommands } from "./commands/DrizzleCommands.ts";
|
|
7
|
+
import { VerifyCommands } from "./commands/VerifyCommands.ts";
|
|
8
|
+
import { ViteCommands } from "./commands/ViteCommands.ts";
|
|
9
|
+
import { ProcessRunner } from "./services/ProcessRunner.ts";
|
|
10
|
+
import { version } from "./version.ts";
|
|
11
|
+
|
|
12
|
+
const AlephaCli = $module({
|
|
13
|
+
name: "alepha.cli",
|
|
14
|
+
services: [
|
|
15
|
+
ProcessRunner,
|
|
16
|
+
CoreCommands,
|
|
17
|
+
DrizzleCommands,
|
|
18
|
+
VerifyCommands,
|
|
19
|
+
ViteCommands,
|
|
20
|
+
BiomeCommands,
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const alepha = Alepha.create({
|
|
25
|
+
env: {
|
|
26
|
+
LOG_LEVEL: "alepha.core:warn,info",
|
|
27
|
+
LOG_FORMAT: "raw",
|
|
28
|
+
CLI_NAME: "alepha",
|
|
29
|
+
CLI_DESCRIPTION: `Alepha CLI v${version} - Create and manage Alepha projects.`,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
alepha.with(AlephaCli);
|
|
34
|
+
|
|
35
|
+
run(alepha);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { $logger } from "@alepha/logger";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service for running external processes with logging support.
|
|
8
|
+
*
|
|
9
|
+
* This service wraps Node.js child_process functionality and provides:
|
|
10
|
+
* - Automatic logging of executed commands
|
|
11
|
+
* - Promise-based execution
|
|
12
|
+
* - Inherited stdio for seamless output
|
|
13
|
+
* - Working directory context
|
|
14
|
+
* - Config file management in node_modules/.alepha
|
|
15
|
+
*/
|
|
16
|
+
export class ProcessRunner {
|
|
17
|
+
protected readonly log = $logger();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute a command using npx with inherited stdio.
|
|
21
|
+
*
|
|
22
|
+
* @param command - The command to execute (will be passed to npx)
|
|
23
|
+
* @param env - Optional environment variables to set for the command
|
|
24
|
+
* @returns Promise that resolves when the process exits
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const runner = alepha.inject(ProcessRunner);
|
|
29
|
+
* await runner.exec("tsx watch src/index.ts");
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
public async exec(
|
|
33
|
+
command: string,
|
|
34
|
+
env: Record<string, string> = {},
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
this.log.debug(`Executing command: npx ${command}`, { cwd: process.cwd() });
|
|
37
|
+
|
|
38
|
+
const prog = spawn("npx", command.split(" "), {
|
|
39
|
+
stdio: "inherit",
|
|
40
|
+
cwd: process.cwd(),
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
...env,
|
|
44
|
+
NODE_OPTIONS: "--import tsx",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await new Promise<void>((resolve) =>
|
|
49
|
+
prog.on("exit", () => {
|
|
50
|
+
resolve();
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write a configuration file to node_modules/.alepha directory.
|
|
57
|
+
*
|
|
58
|
+
* Creates the .alepha directory if it doesn't exist and writes the file with the given content.
|
|
59
|
+
*
|
|
60
|
+
* @param name - The name of the config file to create
|
|
61
|
+
* @param content - The content to write to the file
|
|
62
|
+
* @param root - The root directory (defaults to process.cwd())
|
|
63
|
+
* @returns The absolute path to the created file
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const runner = alepha.inject(ProcessRunner);
|
|
68
|
+
* const configPath = await runner.writeConfigFile("biome.json", biomeConfig);
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
public async writeConfigFile(
|
|
72
|
+
name: string,
|
|
73
|
+
content: string,
|
|
74
|
+
root = process.cwd(),
|
|
75
|
+
): Promise<string> {
|
|
76
|
+
const dir = join(root, "node_modules", ".alepha");
|
|
77
|
+
|
|
78
|
+
await mkdir(dir, {
|
|
79
|
+
recursive: true,
|
|
80
|
+
}).catch(() => null);
|
|
81
|
+
|
|
82
|
+
const path = join(dir, name);
|
|
83
|
+
await writeFile(path, content);
|
|
84
|
+
|
|
85
|
+
this.log.debug(`Config file written: ${path}`);
|
|
86
|
+
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
}
|