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.
Files changed (167) hide show
  1. package/README.md +61 -17
  2. package/dist/index.cjs +17089 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +17088 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +42 -365
  9. package/src/assets/biomeJson.ts +33 -0
  10. package/src/assets/tsconfigJson.ts +18 -0
  11. package/src/assets/viteConfigTs.ts +14 -0
  12. package/src/commands/BiomeCommands.ts +41 -0
  13. package/src/commands/CoreCommands.ts +169 -0
  14. package/src/commands/DrizzleCommands.ts +228 -0
  15. package/src/commands/VerifyCommands.ts +44 -0
  16. package/src/commands/ViteCommands.ts +119 -0
  17. package/src/index.ts +35 -0
  18. package/src/services/ProcessRunner.ts +89 -0
  19. package/src/services/ProjectUtils.ts +508 -0
  20. package/src/version.ts +7 -0
  21. package/api/files.cjs +0 -8
  22. package/api/files.d.ts +0 -438
  23. package/api/files.js +0 -1
  24. package/api/jobs.cjs +0 -8
  25. package/api/jobs.d.ts +0 -327
  26. package/api/jobs.js +0 -1
  27. package/api/notifications.cjs +0 -8
  28. package/api/notifications.d.ts +0 -263
  29. package/api/notifications.js +0 -1
  30. package/api/users.cjs +0 -8
  31. package/api/users.d.ts +0 -923
  32. package/api/users.js +0 -1
  33. package/api/verifications.cjs +0 -8
  34. package/api/verifications.d.ts +0 -1
  35. package/api/verifications.js +0 -1
  36. package/batch.cjs +0 -8
  37. package/batch.d.ts +0 -154
  38. package/batch.js +0 -1
  39. package/bucket.cjs +0 -8
  40. package/bucket.d.ts +0 -520
  41. package/bucket.js +0 -1
  42. package/cache/redis.cjs +0 -8
  43. package/cache/redis.d.ts +0 -40
  44. package/cache/redis.js +0 -1
  45. package/cache.cjs +0 -8
  46. package/cache.d.ts +0 -288
  47. package/cache.js +0 -1
  48. package/command.cjs +0 -8
  49. package/command.d.ts +0 -269
  50. package/command.js +0 -1
  51. package/core.cjs +0 -8
  52. package/core.d.ts +0 -1904
  53. package/core.js +0 -1
  54. package/datetime.cjs +0 -8
  55. package/datetime.d.ts +0 -144
  56. package/datetime.js +0 -1
  57. package/devtools.cjs +0 -8
  58. package/devtools.d.ts +0 -252
  59. package/devtools.js +0 -1
  60. package/email.cjs +0 -8
  61. package/email.d.ts +0 -187
  62. package/email.js +0 -1
  63. package/fake.cjs +0 -8
  64. package/fake.d.ts +0 -73
  65. package/fake.js +0 -1
  66. package/file.cjs +0 -8
  67. package/file.d.ts +0 -528
  68. package/file.js +0 -1
  69. package/lock/redis.cjs +0 -8
  70. package/lock/redis.d.ts +0 -24
  71. package/lock/redis.js +0 -1
  72. package/lock.cjs +0 -8
  73. package/lock.d.ts +0 -552
  74. package/lock.js +0 -1
  75. package/logger.cjs +0 -8
  76. package/logger.d.ts +0 -287
  77. package/logger.js +0 -1
  78. package/postgres.cjs +0 -8
  79. package/postgres.d.ts +0 -2143
  80. package/postgres.js +0 -1
  81. package/queue/redis.cjs +0 -8
  82. package/queue/redis.d.ts +0 -29
  83. package/queue/redis.js +0 -1
  84. package/queue.cjs +0 -8
  85. package/queue.d.ts +0 -760
  86. package/queue.js +0 -1
  87. package/react/auth.cjs +0 -8
  88. package/react/auth.d.ts +0 -504
  89. package/react/auth.js +0 -1
  90. package/react/form.cjs +0 -8
  91. package/react/form.d.ts +0 -211
  92. package/react/form.js +0 -1
  93. package/react/head.cjs +0 -8
  94. package/react/head.d.ts +0 -120
  95. package/react/head.js +0 -1
  96. package/react/i18n.cjs +0 -8
  97. package/react/i18n.d.ts +0 -168
  98. package/react/i18n.js +0 -1
  99. package/react.cjs +0 -8
  100. package/react.d.ts +0 -1263
  101. package/react.js +0 -1
  102. package/redis.cjs +0 -8
  103. package/redis.d.ts +0 -82
  104. package/redis.js +0 -1
  105. package/retry.cjs +0 -8
  106. package/retry.d.ts +0 -162
  107. package/retry.js +0 -1
  108. package/router.cjs +0 -8
  109. package/router.d.ts +0 -45
  110. package/router.js +0 -1
  111. package/scheduler.cjs +0 -8
  112. package/scheduler.d.ts +0 -145
  113. package/scheduler.js +0 -1
  114. package/security.cjs +0 -8
  115. package/security.d.ts +0 -586
  116. package/security.js +0 -1
  117. package/server/cache.cjs +0 -8
  118. package/server/cache.d.ts +0 -163
  119. package/server/cache.js +0 -1
  120. package/server/compress.cjs +0 -8
  121. package/server/compress.d.ts +0 -38
  122. package/server/compress.js +0 -1
  123. package/server/cookies.cjs +0 -8
  124. package/server/cookies.d.ts +0 -144
  125. package/server/cookies.js +0 -1
  126. package/server/cors.cjs +0 -8
  127. package/server/cors.d.ts +0 -45
  128. package/server/cors.js +0 -1
  129. package/server/health.cjs +0 -8
  130. package/server/health.d.ts +0 -58
  131. package/server/health.js +0 -1
  132. package/server/helmet.cjs +0 -8
  133. package/server/helmet.d.ts +0 -98
  134. package/server/helmet.js +0 -1
  135. package/server/links.cjs +0 -8
  136. package/server/links.d.ts +0 -322
  137. package/server/links.js +0 -1
  138. package/server/metrics.cjs +0 -8
  139. package/server/metrics.d.ts +0 -35
  140. package/server/metrics.js +0 -1
  141. package/server/multipart.cjs +0 -8
  142. package/server/multipart.d.ts +0 -42
  143. package/server/multipart.js +0 -1
  144. package/server/proxy.cjs +0 -8
  145. package/server/proxy.d.ts +0 -234
  146. package/server/proxy.js +0 -1
  147. package/server/security.cjs +0 -8
  148. package/server/security.d.ts +0 -92
  149. package/server/security.js +0 -1
  150. package/server/static.cjs +0 -8
  151. package/server/static.d.ts +0 -119
  152. package/server/static.js +0 -1
  153. package/server/swagger.cjs +0 -8
  154. package/server/swagger.d.ts +0 -161
  155. package/server/swagger.js +0 -1
  156. package/server.cjs +0 -8
  157. package/server.d.ts +0 -849
  158. package/server.js +0 -1
  159. package/topic/redis.cjs +0 -8
  160. package/topic/redis.d.ts +0 -42
  161. package/topic/redis.js +0 -1
  162. package/topic.cjs +0 -8
  163. package/topic.d.ts +0 -819
  164. package/topic.js +0 -1
  165. package/vite.cjs +0 -8
  166. package/vite.d.ts +0 -186
  167. 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
+ }