alepha 0.14.4 → 0.15.0
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 +1 -4
- package/dist/api/audits/index.d.ts +619 -731
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts +185 -298
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +0 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +245 -356
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.d.ts +238 -350
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +499 -611
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +1 -2
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1697 -1804
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +178 -151
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +132 -132
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.d.ts +122 -122
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +1 -2
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +163 -163
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts +46 -46
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cli/index.d.ts +302 -299
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +966 -564
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +303 -299
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +11 -7
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +419 -99
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +718 -625
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +420 -99
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +419 -99
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +44 -44
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +4 -4
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +97 -50
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +129 -33
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +7981 -14
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/file/index.d.ts +523 -390
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +253 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +208 -208
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +25 -26
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +197 -197
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/orm/chunk-DtkW-qnP.js +38 -0
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2814 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +1205 -1057
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2056 -1753
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +248 -248
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/redis/index.bun.js +285 -0
- package/dist/redis/index.bun.js.map +1 -0
- package/dist/redis/index.d.ts +118 -136
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +18 -38
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +69 -69
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/router/index.d.ts +6 -6
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +25 -25
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/security/index.browser.js +5 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +417 -254
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +386 -86
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +277 -277
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +20 -20
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +60 -57
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +3 -3
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/cookies/index.d.ts +6 -6
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +242 -150
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +288 -122
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +11 -12
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +0 -1
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/helmet/index.d.ts +2 -2
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +84 -85
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +1 -2
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/multipart/index.d.ts +6 -6
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +102 -103
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts +16 -16
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts +44 -44
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +48 -49
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +13 -11
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -7
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +71 -72
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/topic/core/index.d.ts +318 -318
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts +6 -6
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +5720 -159
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +41 -18
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +6 -6
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +247 -247
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +6 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +9 -14
- package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
- package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
- package/src/api/users/entities/users.ts +1 -1
- package/src/api/users/index.ts +8 -8
- package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
- package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
- package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
- package/src/api/users/services/CredentialService.ts +7 -7
- package/src/api/users/services/IdentityService.ts +4 -4
- package/src/api/users/services/RegistrationService.spec.ts +25 -27
- package/src/api/users/services/RegistrationService.ts +38 -27
- package/src/api/users/services/SessionCrudService.ts +3 -3
- package/src/api/users/services/SessionService.spec.ts +3 -3
- package/src/api/users/services/SessionService.ts +28 -9
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
- package/src/cli/assets/apiHelloControllerTs.ts +18 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/claudeMd.ts +303 -0
- package/src/cli/assets/mainBrowserTs.ts +2 -2
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/webAppRouterTs.ts +15 -0
- package/src/cli/assets/webHelloComponentTsx.ts +16 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/commands/build.ts +41 -21
- package/src/cli/commands/db.ts +21 -18
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +13 -17
- package/src/cli/commands/format.ts +8 -2
- package/src/cli/commands/init.ts +74 -29
- package/src/cli/commands/lint.ts +8 -2
- package/src/cli/commands/test.ts +8 -2
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +4 -2
- package/src/cli/services/AlephaCliUtils.ts +39 -600
- package/src/cli/services/PackageManagerUtils.ts +301 -0
- package/src/cli/services/ProjectScaffolder.ts +306 -0
- package/src/command/helpers/Runner.ts +15 -3
- package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/primitives/$hook.ts +6 -2
- package/src/core/primitives/$module.spec.ts +4 -0
- package/src/core/providers/AlsProvider.ts +1 -1
- package/src/core/providers/CodecManager.spec.ts +12 -6
- package/src/core/providers/CodecManager.ts +26 -6
- package/src/core/providers/EventManager.ts +169 -13
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
- package/src/core/providers/StateManager.spec.ts +27 -16
- package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
- package/src/email/providers/LocalEmailProvider.ts +52 -15
- package/src/email/providers/NodemailerEmailProvider.ts +167 -56
- package/src/file/errors/FileError.ts +7 -0
- package/src/file/index.ts +9 -1
- package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
- package/src/orm/index.browser.ts +1 -19
- package/src/orm/index.bun.ts +77 -0
- package/src/orm/index.shared-server.ts +22 -0
- package/src/orm/index.shared.ts +15 -0
- package/src/orm/index.ts +19 -39
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
- package/src/orm/services/Repository.ts +8 -0
- package/src/redis/index.bun.ts +35 -0
- package/src/redis/providers/BunRedisProvider.ts +12 -43
- package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
- package/src/redis/providers/NodeRedisProvider.ts +16 -34
- package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
- package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
- package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
- package/src/security/index.browser.ts +5 -0
- package/src/security/index.ts +90 -7
- package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
- package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
- package/src/security/primitives/$role.ts +5 -5
- package/src/security/primitives/$serviceAccount.spec.ts +5 -5
- package/src/security/primitives/$serviceAccount.ts +3 -3
- package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
- package/src/server/auth/primitives/$auth.ts +10 -10
- package/src/server/auth/primitives/$authCredentials.ts +3 -3
- package/src/server/auth/primitives/$authGithub.ts +3 -3
- package/src/server/auth/primitives/$authGoogle.ts +3 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
- package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
- package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
- package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
- package/src/server/core/providers/ServerProvider.ts +144 -21
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/index.ts +1 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/swagger/index.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/helpers/boot.ts +28 -17
- package/src/vite/tasks/buildServer.ts +12 -1
- package/src/vite/tasks/devServer.ts +3 -1
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/dist/server/security/index.browser.js +0 -13
- package/dist/server/security/index.browser.js.map +0 -1
- package/dist/server/security/index.d.ts +0 -173
- package/dist/server/security/index.d.ts.map +0 -1
- package/dist/server/security/index.js +0 -311
- package/dist/server/security/index.js.map +0 -1
- package/src/cli/assets/appRouterTs.ts +0 -9
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
|
|
2
2
|
import { FileSystemProvider } from "alepha/file";
|
|
3
|
-
import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
5
3
|
import { $command, CliProvider, EnvUtils } from "alepha/command";
|
|
6
4
|
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
7
5
|
import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
|
|
8
6
|
import { exec, spawn } from "node:child_process";
|
|
9
7
|
import { readFileSync } from "node:fs";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
10
9
|
import { promisify } from "node:util";
|
|
11
10
|
import { ServerSwaggerProvider } from "alepha/server/swagger";
|
|
11
|
+
import { access, readFile, readdir } from "node:fs/promises";
|
|
12
12
|
import * as os from "node:os";
|
|
13
13
|
|
|
14
14
|
//#region ../../src/core/constants/KIND.ts
|
|
@@ -103,17 +103,354 @@ const buildOptions = $atom$1({
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
//#endregion
|
|
106
|
-
//#region ../../src/cli/
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
//#region ../../src/cli/services/AlephaCliUtils.ts
|
|
107
|
+
/**
|
|
108
|
+
* Core utility service for CLI commands.
|
|
109
|
+
*
|
|
110
|
+
* Provides:
|
|
111
|
+
* - Command execution
|
|
112
|
+
* - File editing helpers
|
|
113
|
+
* - Drizzle/ORM utilities
|
|
114
|
+
* - Environment loading
|
|
115
|
+
*/
|
|
116
|
+
var AlephaCliUtils = class {
|
|
117
|
+
log = $logger();
|
|
118
|
+
fs = $inject(FileSystemProvider);
|
|
119
|
+
envUtils = $inject(EnvUtils);
|
|
120
|
+
/**
|
|
121
|
+
* Execute a command with inherited stdio.
|
|
122
|
+
*/
|
|
123
|
+
async exec(command, options = {}) {
|
|
124
|
+
const root = options.root ?? process.cwd();
|
|
125
|
+
this.log.debug(`Executing command: ${command}`, { cwd: root });
|
|
126
|
+
const runExec = async (app$1, args$1) => {
|
|
127
|
+
const prog = spawn(app$1, args$1, {
|
|
128
|
+
stdio: "inherit",
|
|
129
|
+
cwd: root,
|
|
130
|
+
env: {
|
|
131
|
+
...process.env,
|
|
132
|
+
...options.env
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
await new Promise((resolve) => prog.on("exit", () => {
|
|
136
|
+
resolve();
|
|
137
|
+
}));
|
|
138
|
+
};
|
|
139
|
+
if (options.global) {
|
|
140
|
+
const [app$1, ...args$1] = command.split(" ");
|
|
141
|
+
await runExec(app$1, args$1);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
145
|
+
const [app, ...args] = command.split(" ");
|
|
146
|
+
let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`);
|
|
147
|
+
if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`);
|
|
148
|
+
if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
|
|
149
|
+
await runExec(execPath, args);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Write a configuration file to node_modules/.alepha directory.
|
|
153
|
+
*/
|
|
154
|
+
async writeConfigFile(name, content, root = process.cwd()) {
|
|
155
|
+
const dir = this.fs.join(root, "node_modules", ".alepha");
|
|
156
|
+
await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
|
|
157
|
+
const path = this.fs.join(dir, name);
|
|
158
|
+
await this.fs.writeFile(path, content);
|
|
159
|
+
this.log.debug(`Config file written: ${path}`);
|
|
160
|
+
return path;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Load Alepha instance from a server entry file.
|
|
164
|
+
*/
|
|
165
|
+
async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
|
|
166
|
+
process.env.ALEPHA_CLI_IMPORT = "true";
|
|
167
|
+
const entry = await boot.getServerEntry(rootDir, explicitEntry);
|
|
168
|
+
delete global.__alepha;
|
|
169
|
+
const mod = await import(entry);
|
|
170
|
+
this.log.debug(`Load entry: ${entry}`);
|
|
171
|
+
if (mod.default instanceof Alepha) return {
|
|
172
|
+
alepha: mod.default,
|
|
173
|
+
entry
|
|
174
|
+
};
|
|
175
|
+
const g = global;
|
|
176
|
+
if (g.__alepha) return {
|
|
177
|
+
alepha: g.__alepha,
|
|
178
|
+
entry
|
|
179
|
+
};
|
|
180
|
+
throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate JavaScript code for Drizzle entities export.
|
|
184
|
+
*/
|
|
185
|
+
generateEntitiesJs(entry, provider, models = []) {
|
|
186
|
+
return `
|
|
187
|
+
import "${entry}";
|
|
188
|
+
import { DrizzleKitProvider, Repository } from "alepha/orm";
|
|
109
189
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
190
|
+
const alepha = globalThis.__alepha;
|
|
191
|
+
const kit = alepha.inject(DrizzleKitProvider);
|
|
192
|
+
const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
|
|
193
|
+
const models = kit.getModels(provider);
|
|
194
|
+
|
|
195
|
+
${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
196
|
+
|
|
197
|
+
`.trim();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Load environment variables from a .env file.
|
|
201
|
+
*/
|
|
202
|
+
async loadEnv(root, files = [".env"]) {
|
|
203
|
+
await this.envUtils.loadEnv(root, files);
|
|
204
|
+
}
|
|
205
|
+
async exists(root, path) {
|
|
206
|
+
return this.fs.exists(this.fs.join(root, path));
|
|
207
|
+
}
|
|
208
|
+
async checkFileExists(root, name) {
|
|
209
|
+
const configPath = this.fs.join(root, name);
|
|
210
|
+
if (await this.fs.exists(configPath)) return configPath;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region ../../src/cli/version.ts
|
|
216
|
+
const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
|
|
217
|
+
const version = packageJson.version;
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region ../../src/cli/services/PackageManagerUtils.ts
|
|
221
|
+
/**
|
|
222
|
+
* Utility service for package manager operations.
|
|
223
|
+
*
|
|
224
|
+
* Handles detection, installation, and cleanup for:
|
|
225
|
+
* - Yarn
|
|
226
|
+
* - npm
|
|
227
|
+
* - pnpm
|
|
228
|
+
* - Bun
|
|
229
|
+
*/
|
|
230
|
+
var PackageManagerUtils = class {
|
|
231
|
+
log = $logger();
|
|
232
|
+
fs = $inject(FileSystemProvider);
|
|
233
|
+
alepha = $inject(Alepha);
|
|
234
|
+
/**
|
|
235
|
+
* Detect the package manager used in the project.
|
|
236
|
+
*/
|
|
237
|
+
async getPackageManager(root, flags) {
|
|
238
|
+
if (flags?.yarn) return "yarn";
|
|
239
|
+
if (flags?.pnpm) return "pnpm";
|
|
240
|
+
if (flags?.npm) return "npm";
|
|
241
|
+
if (flags?.bun) return "bun";
|
|
242
|
+
if (this.alepha.isBun()) return "bun";
|
|
243
|
+
if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
|
|
244
|
+
if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
|
|
245
|
+
if (await this.fs.exists(this.fs.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
246
|
+
return "npm";
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get the install command for a package.
|
|
250
|
+
*/
|
|
251
|
+
async getInstallCommand(root, packageName, dev = true) {
|
|
252
|
+
const pm = await this.getPackageManager(root);
|
|
253
|
+
let cmd;
|
|
254
|
+
switch (pm) {
|
|
255
|
+
case "yarn":
|
|
256
|
+
cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
|
|
257
|
+
break;
|
|
258
|
+
case "pnpm":
|
|
259
|
+
cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
|
|
260
|
+
break;
|
|
261
|
+
case "bun":
|
|
262
|
+
cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
|
|
263
|
+
break;
|
|
264
|
+
default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
|
|
265
|
+
}
|
|
266
|
+
return cmd.replace(/\s+/g, " ").trim();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if a dependency is installed in the project.
|
|
270
|
+
*/
|
|
271
|
+
async hasDependency(root, packageName) {
|
|
272
|
+
try {
|
|
273
|
+
const pkg = await this.readPackageJson(root);
|
|
274
|
+
return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if Expo is present in the project.
|
|
281
|
+
*/
|
|
282
|
+
async hasExpo(root) {
|
|
283
|
+
return this.hasDependency(root, "expo");
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Install a dependency if it's missing from the project.
|
|
287
|
+
*/
|
|
288
|
+
async ensureDependency(root, packageName, options = {}) {
|
|
289
|
+
const { dev = true } = options;
|
|
290
|
+
if (await this.hasDependency(root, packageName)) {
|
|
291
|
+
this.log.debug(`Dependency '${packageName}' is already installed`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const cmd = await this.getInstallCommand(root, packageName, dev);
|
|
295
|
+
if (options.run) await options.run(cmd, {
|
|
296
|
+
alias: `installing ${packageName}`,
|
|
297
|
+
root
|
|
298
|
+
});
|
|
299
|
+
else if (options.exec) {
|
|
300
|
+
this.log.debug(`Installing ${packageName}`);
|
|
301
|
+
await options.exec(cmd, {
|
|
302
|
+
global: true,
|
|
303
|
+
root
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async ensureYarn(root) {
|
|
308
|
+
const yarnrcPath = this.fs.join(root, ".yarnrc.yml");
|
|
309
|
+
if (!await this.fs.exists(yarnrcPath)) await this.fs.writeFile(yarnrcPath, "nodeLinker: node-modules");
|
|
310
|
+
await this.removeAllPmFilesExcept(root, "yarn");
|
|
311
|
+
}
|
|
312
|
+
async ensureBun(root) {
|
|
313
|
+
await this.removeAllPmFilesExcept(root, "bun");
|
|
314
|
+
}
|
|
315
|
+
async ensurePnpm(root) {
|
|
316
|
+
await this.removeAllPmFilesExcept(root, "pnpm");
|
|
317
|
+
}
|
|
318
|
+
async ensureNpm(root) {
|
|
319
|
+
await this.removeAllPmFilesExcept(root, "npm");
|
|
320
|
+
}
|
|
321
|
+
async removeAllPmFilesExcept(root, except) {
|
|
322
|
+
if (except !== "yarn") await this.removeYarn(root);
|
|
323
|
+
if (except !== "pnpm") await this.removePnpm(root);
|
|
324
|
+
if (except !== "npm") await this.removeNpm(root);
|
|
325
|
+
if (except !== "bun") await this.removeBun(root);
|
|
326
|
+
}
|
|
327
|
+
async removeYarn(root) {
|
|
328
|
+
await this.removeFiles(root, [
|
|
329
|
+
".yarn",
|
|
330
|
+
".yarnrc.yml",
|
|
331
|
+
"yarn.lock"
|
|
332
|
+
]);
|
|
333
|
+
await this.editPackageJson(root, (pkg) => {
|
|
334
|
+
delete pkg.packageManager;
|
|
335
|
+
return pkg;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async removePnpm(root) {
|
|
339
|
+
await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
|
|
340
|
+
await this.editPackageJson(root, (pkg) => {
|
|
341
|
+
delete pkg.packageManager;
|
|
342
|
+
return pkg;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async removeNpm(root) {
|
|
346
|
+
await this.removeFiles(root, ["package-lock.json"]);
|
|
347
|
+
}
|
|
348
|
+
async removeBun(root) {
|
|
349
|
+
await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
|
|
350
|
+
}
|
|
351
|
+
async readPackageJson(root) {
|
|
352
|
+
const content = await this.fs.createFile({ path: this.fs.join(root, "package.json") }).text();
|
|
353
|
+
return JSON.parse(content);
|
|
354
|
+
}
|
|
355
|
+
async writePackageJson(root, content) {
|
|
356
|
+
await this.fs.writeFile(this.fs.join(root, "package.json"), JSON.stringify(content, null, 2));
|
|
357
|
+
}
|
|
358
|
+
async editPackageJson(root, editFn) {
|
|
359
|
+
try {
|
|
360
|
+
const updated = editFn(await this.readPackageJson(root));
|
|
361
|
+
await this.writePackageJson(root, updated);
|
|
362
|
+
} catch {}
|
|
363
|
+
}
|
|
364
|
+
async ensurePackageJson(root, modes) {
|
|
365
|
+
const packageJsonPath = this.fs.join(root, "package.json");
|
|
366
|
+
if (!await this.fs.exists(packageJsonPath)) {
|
|
367
|
+
const content = this.generatePackageJsonContent(modes);
|
|
368
|
+
await this.writePackageJson(root, content);
|
|
369
|
+
return content;
|
|
370
|
+
}
|
|
371
|
+
const packageJson$1 = await this.readPackageJson(root);
|
|
372
|
+
const newContent = this.generatePackageJsonContent(modes);
|
|
373
|
+
packageJson$1.type = "module";
|
|
374
|
+
packageJson$1.dependencies ??= {};
|
|
375
|
+
packageJson$1.devDependencies ??= {};
|
|
376
|
+
packageJson$1.scripts ??= {};
|
|
377
|
+
Object.assign(packageJson$1.dependencies, newContent.dependencies);
|
|
378
|
+
Object.assign(packageJson$1.devDependencies, newContent.devDependencies);
|
|
379
|
+
Object.assign(packageJson$1.scripts, newContent.scripts);
|
|
380
|
+
await this.writePackageJson(root, packageJson$1);
|
|
381
|
+
return packageJson$1;
|
|
382
|
+
}
|
|
383
|
+
generatePackageJsonContent(modes) {
|
|
384
|
+
const dependencies = { alepha: `^${version}` };
|
|
385
|
+
const devDependencies = {};
|
|
386
|
+
const scripts = {
|
|
387
|
+
dev: "alepha dev",
|
|
388
|
+
build: "alepha build",
|
|
389
|
+
lint: "alepha lint",
|
|
390
|
+
typecheck: "alepha typecheck",
|
|
391
|
+
verify: "alepha verify"
|
|
392
|
+
};
|
|
393
|
+
if (modes.admin) {
|
|
394
|
+
dependencies["@alepha/ui"] = `^${version}`;
|
|
395
|
+
modes.web = true;
|
|
396
|
+
}
|
|
397
|
+
if (modes.web) {
|
|
398
|
+
dependencies["@alepha/react"] = `^${version}`;
|
|
399
|
+
dependencies.react = "^19.2.0";
|
|
400
|
+
dependencies["react-dom"] = "^19.2.0";
|
|
401
|
+
devDependencies["@types/react"] = "^19.2.0";
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
type: "module",
|
|
405
|
+
dependencies,
|
|
406
|
+
devDependencies,
|
|
407
|
+
scripts
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async removeFiles(root, files) {
|
|
411
|
+
await Promise.all(files.map((file) => this.fs.rm(this.fs.join(root, file), {
|
|
412
|
+
force: true,
|
|
413
|
+
recursive: true
|
|
414
|
+
})));
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region ../../src/cli/assets/apiHelloControllerTs.ts
|
|
420
|
+
const apiHelloControllerTs = () => `
|
|
421
|
+
import { t } from "alepha";
|
|
422
|
+
import { $action } from "alepha/server";
|
|
423
|
+
|
|
424
|
+
export class HelloController {
|
|
425
|
+
hello = $action({
|
|
426
|
+
path: "/hello",
|
|
427
|
+
schema: {
|
|
428
|
+
response: t.object({
|
|
429
|
+
message: t.string(),
|
|
430
|
+
}),
|
|
431
|
+
},
|
|
432
|
+
handler: () => ({
|
|
433
|
+
message: "Hello, Alepha!",
|
|
434
|
+
}),
|
|
113
435
|
});
|
|
114
436
|
}
|
|
115
437
|
`.trim();
|
|
116
438
|
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region ../../src/cli/assets/apiIndexTs.ts
|
|
441
|
+
const apiIndexTs = (options = {}) => {
|
|
442
|
+
const { appName = "app" } = options;
|
|
443
|
+
return `
|
|
444
|
+
import { $module } from "alepha";
|
|
445
|
+
import { HelloController } from "./controllers/HelloController.ts";
|
|
446
|
+
|
|
447
|
+
export const ApiModule = $module({
|
|
448
|
+
name: "${appName}.api",
|
|
449
|
+
services: [HelloController],
|
|
450
|
+
});
|
|
451
|
+
`.trim();
|
|
452
|
+
};
|
|
453
|
+
|
|
117
454
|
//#endregion
|
|
118
455
|
//#region ../../src/cli/assets/biomeJson.ts
|
|
119
456
|
const biomeJson = `
|
|
@@ -150,6 +487,299 @@ const biomeJson = `
|
|
|
150
487
|
}
|
|
151
488
|
`.trim();
|
|
152
489
|
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region ../../src/cli/assets/claudeMd.ts
|
|
492
|
+
const claudeMd = (options = {}) => {
|
|
493
|
+
const { react = false, projectName = "my-app" } = options;
|
|
494
|
+
const reactSection = react ? `
|
|
495
|
+
## React & Frontend
|
|
496
|
+
|
|
497
|
+
### Pages with \`$page\`
|
|
498
|
+
\`\`\`tsx
|
|
499
|
+
import { $page } from "@alepha/react/router";
|
|
500
|
+
import { $client } from "alepha/server/links";
|
|
501
|
+
import type { UserController } from "./UserController.ts";
|
|
502
|
+
|
|
503
|
+
class AppRouter {
|
|
504
|
+
api = $client<UserController>();
|
|
505
|
+
|
|
506
|
+
users = $page({
|
|
507
|
+
path: "/users",
|
|
508
|
+
loader: async () => ({ users: await this.api.listUsers() }),
|
|
509
|
+
component: ({ users }) => (
|
|
510
|
+
<ul>{users.map(u => <li key={u.id}>{u.email}</li>)}</ul>
|
|
511
|
+
),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
userDetail = $page({
|
|
515
|
+
path: "/users/:id",
|
|
516
|
+
schema: { params: t.object({ id: t.uuid() }) },
|
|
517
|
+
loader: async ({ params }) => ({ user: await this.api.getUser({ params }) }),
|
|
518
|
+
lazy: () => import("./UserDetail.tsx"), // Code splitting
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
\`\`\`
|
|
522
|
+
|
|
523
|
+
### React Hooks
|
|
524
|
+
\`\`\`typescript
|
|
525
|
+
import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
|
|
526
|
+
import { useRouter, useActive } from "@alepha/react/router";
|
|
527
|
+
import { useForm } from "@alepha/react/form";
|
|
528
|
+
\`\`\`
|
|
529
|
+
|
|
530
|
+
- \`useClient<Controller>()\` - Type-safe API calls
|
|
531
|
+
- \`useStore(atom)\` - Global state (returns \`[value, setValue]\`)
|
|
532
|
+
- \`useAction({ handler })\` - Async operations with loading/error state
|
|
533
|
+
- \`useRouter<AppRouter>()\` - Type-safe navigation
|
|
534
|
+
- \`useForm({ schema, handler })\` - Type-safe forms with validation
|
|
535
|
+
` : "";
|
|
536
|
+
const projectStructure = react ? `
|
|
537
|
+
\`\`\`
|
|
538
|
+
${projectName}/
|
|
539
|
+
├── src/
|
|
540
|
+
│ ├── api/ # Backend
|
|
541
|
+
│ │ ├── controllers/ # API controllers with $action
|
|
542
|
+
│ │ ├── services/ # Business logic
|
|
543
|
+
│ │ ├── entities/ # Database entities with $entity
|
|
544
|
+
│ │ ├── providers/ # External service wrappers
|
|
545
|
+
│ │ └── index.ts # API module definition with $module
|
|
546
|
+
│ ├── web/ # Frontend (React only)
|
|
547
|
+
│ │ ├── components/ # React components
|
|
548
|
+
│ │ ├── atoms/ # State atoms with $atom
|
|
549
|
+
│ │ ├── AppRouter.ts # Routes with $page
|
|
550
|
+
│ │ └── index.ts # Web module definition with $module
|
|
551
|
+
│ ├── main.server.ts # Server entry
|
|
552
|
+
│ └── main.browser.ts # Browser entry (React only)
|
|
553
|
+
├── index.html # (React only)
|
|
554
|
+
├── package.json
|
|
555
|
+
└── tsconfig.json
|
|
556
|
+
\`\`\`
|
|
557
|
+
` : `
|
|
558
|
+
\`\`\`
|
|
559
|
+
${projectName}/
|
|
560
|
+
├── src/
|
|
561
|
+
│ ├── api/ # Backend
|
|
562
|
+
│ │ ├── controllers/ # API controllers with $action
|
|
563
|
+
│ │ ├── services/ # Business logic
|
|
564
|
+
│ │ ├── entities/ # Database entities with $entity
|
|
565
|
+
│ │ ├── providers/ # External service wrappers
|
|
566
|
+
│ │ └── index.ts # API module definition with $module
|
|
567
|
+
│ └── main.server.ts # Server entry (always use main.server.ts)
|
|
568
|
+
├── package.json
|
|
569
|
+
└── tsconfig.json
|
|
570
|
+
\`\`\`
|
|
571
|
+
`;
|
|
572
|
+
return `# CLAUDE.md
|
|
573
|
+
|
|
574
|
+
This file provides guidance to Claude Code when working with this Alepha project.
|
|
575
|
+
|
|
576
|
+
## Overview
|
|
577
|
+
|
|
578
|
+
This is an **Alepha** project - a convention-driven TypeScript framework for type-safe full-stack applications.
|
|
579
|
+
|
|
580
|
+
**Key Concepts:**
|
|
581
|
+
- **Primitives**: Features defined with \`$\`-prefixed functions (\`$action\`, \`$entity\`, \`$page\`)
|
|
582
|
+
- **Class-Based**: Services are classes, primitives are class properties
|
|
583
|
+
- **Zero-Config**: Code structure IS the configuration
|
|
584
|
+
- **End-to-End Types**: Types flow from database → API → ${react ? "React" : "client"}
|
|
585
|
+
|
|
586
|
+
## Rules
|
|
587
|
+
|
|
588
|
+
- Use TypeScript strict mode
|
|
589
|
+
- Use Biome for formatting (\`alepha lint\`)
|
|
590
|
+
- Use Vitest for testing
|
|
591
|
+
- One file = one class
|
|
592
|
+
- Primitives are class properties (except \`$entity\`, \`$atom\`)
|
|
593
|
+
- No decorators, no Express/Fastify patterns
|
|
594
|
+
- No manual instantiation - use dependency injection
|
|
595
|
+
- Use \`protected\` instead of \`private\` for class members
|
|
596
|
+
- Import with file extensions: \`import { User } from "./User.ts"\`
|
|
597
|
+
- Use \`t\` from Alepha for schemas (not Zod)
|
|
598
|
+
|
|
599
|
+
## Project Structure
|
|
600
|
+
${projectStructure}
|
|
601
|
+
## Core Primitives
|
|
602
|
+
|
|
603
|
+
### API with \`$action\`
|
|
604
|
+
\`\`\`typescript
|
|
605
|
+
import { t } from "alepha";
|
|
606
|
+
import { $action } from "alepha/server";
|
|
607
|
+
|
|
608
|
+
class UserController {
|
|
609
|
+
getUser = $action({
|
|
610
|
+
path: "/users/:id", // → GET /api/users/:id
|
|
611
|
+
schema: {
|
|
612
|
+
params: t.object({ id: t.uuid() }),
|
|
613
|
+
response: t.object({ id: t.uuid(), email: t.email() }),
|
|
614
|
+
},
|
|
615
|
+
handler: async ({ params }) => this.userRepo.findById(params.id),
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
createUser = $action({
|
|
619
|
+
// POST inferred from body schema
|
|
620
|
+
schema: {
|
|
621
|
+
body: t.object({ email: t.email() }),
|
|
622
|
+
response: userEntity.schema,
|
|
623
|
+
},
|
|
624
|
+
handler: async ({ body }) => this.userRepo.create(body),
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
\`\`\`
|
|
628
|
+
|
|
629
|
+
### Database with \`$entity\` and \`$repository\`
|
|
630
|
+
\`\`\`typescript
|
|
631
|
+
import { $entity, $repository, db } from "alepha/orm";
|
|
632
|
+
|
|
633
|
+
// Entity defined at module level (for drizzle-kit compatibility)
|
|
634
|
+
export const userEntity = $entity({
|
|
635
|
+
name: "users",
|
|
636
|
+
schema: t.object({
|
|
637
|
+
id: db.primaryKey(),
|
|
638
|
+
email: t.email(),
|
|
639
|
+
createdAt: db.createdAt(),
|
|
640
|
+
updatedAt: db.updatedAt(),
|
|
641
|
+
}),
|
|
642
|
+
indexes: [{ column: "email", unique: true }],
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
class UserService {
|
|
646
|
+
repo = $repository(userEntity);
|
|
647
|
+
|
|
648
|
+
async findById(id: string) {
|
|
649
|
+
return this.repo.findById(id);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
\`\`\`
|
|
653
|
+
|
|
654
|
+
### Dependency Injection
|
|
655
|
+
\`\`\`typescript
|
|
656
|
+
import { $inject } from "alepha";
|
|
657
|
+
|
|
658
|
+
class OrderService {
|
|
659
|
+
userService = $inject(UserService); // Within same module
|
|
660
|
+
|
|
661
|
+
async createOrder(userId: string) {
|
|
662
|
+
const user = await this.userService.findById(userId);
|
|
663
|
+
// ...
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Cross-module: use $client instead of $inject
|
|
668
|
+
class AppRouter {
|
|
669
|
+
api = $client<OrderController>(); // Type-safe HTTP client
|
|
670
|
+
}
|
|
671
|
+
\`\`\`
|
|
672
|
+
|
|
673
|
+
### Modules with \`$module\`
|
|
674
|
+
\`\`\`typescript
|
|
675
|
+
// src/api/index.ts - Groups all API services
|
|
676
|
+
import { $module } from "alepha";
|
|
677
|
+
|
|
678
|
+
export const ApiModule = $module({
|
|
679
|
+
name: "app.api",
|
|
680
|
+
services: [
|
|
681
|
+
UserController,
|
|
682
|
+
OrderController,
|
|
683
|
+
UserService,
|
|
684
|
+
],
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// src/web/index.ts - Groups all web services (React only)
|
|
688
|
+
export const WebModule = $module({
|
|
689
|
+
name: "app.web",
|
|
690
|
+
services: [AppRouter, Toaster],
|
|
691
|
+
register(alepha) {
|
|
692
|
+
// Optional: configure additional services
|
|
693
|
+
alepha.with(SomeLibrary);
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
\`\`\`
|
|
697
|
+
|
|
698
|
+
### Environment Variables
|
|
699
|
+
\`\`\`typescript
|
|
700
|
+
import { $env, t } from "alepha";
|
|
701
|
+
|
|
702
|
+
class AppConfig {
|
|
703
|
+
env = $env(t.object({
|
|
704
|
+
DATABASE_URL: t.string(),
|
|
705
|
+
API_KEY: t.optional(t.string()),
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
\`\`\`
|
|
709
|
+
${reactSection}
|
|
710
|
+
## Quick Reference
|
|
711
|
+
|
|
712
|
+
| Primitive | Import | Purpose |
|
|
713
|
+
|-----------|--------|---------|
|
|
714
|
+
| \`$inject\` | \`alepha\` | Dependency injection |
|
|
715
|
+
| \`$env\` | \`alepha\` | Environment variables |
|
|
716
|
+
| \`$hook\` | \`alepha\` | Lifecycle hooks |
|
|
717
|
+
| \`$logger\` | \`alepha/logger\` | Structured logging |
|
|
718
|
+
| \`$action\` | \`alepha/server\` | REST API endpoints |
|
|
719
|
+
| \`$route\` | \`alepha/server\` | Low-level HTTP routes |
|
|
720
|
+
| \`$entity\` | \`alepha/orm\` | Database tables |
|
|
721
|
+
| \`$repository\` | \`alepha/orm\` | Type-safe data access |
|
|
722
|
+
| \`$queue\` | \`alepha/queue\` | Background jobs |
|
|
723
|
+
| \`$scheduler\` | \`alepha/scheduler\` | Cron tasks |
|
|
724
|
+
| \`$cache\` | \`alepha/cache\` | Cached computations |
|
|
725
|
+
| \`$bucket\` | \`alepha/bucket\` | File storage |
|
|
726
|
+
| \`$issuer\` | \`alepha/security\` | JWT tokens |
|
|
727
|
+
| \`$command\` | \`alepha/command\` | CLI commands |${react ? `
|
|
728
|
+
| \`$page\` | \`@alepha/react/router\` | React pages with SSR |
|
|
729
|
+
| \`$atom\` | \`alepha\` | Global state |` : ""}
|
|
730
|
+
|
|
731
|
+
## Testing
|
|
732
|
+
|
|
733
|
+
\`\`\`typescript
|
|
734
|
+
import { describe, it, expect } from "vitest";
|
|
735
|
+
import { Alepha } from "alepha";
|
|
736
|
+
|
|
737
|
+
describe("UserService", () => {
|
|
738
|
+
it("should create user", async () => {
|
|
739
|
+
const alepha = Alepha.create().with(UserService);
|
|
740
|
+
const service = alepha.inject(UserService);
|
|
741
|
+
|
|
742
|
+
const user = await service.create({ email: "test@example.com" });
|
|
743
|
+
expect(user.email).toBe("test@example.com");
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it("should mock dependencies", async () => {
|
|
747
|
+
const alepha = Alepha.create()
|
|
748
|
+
.with(OrderService)
|
|
749
|
+
.with({ provide: PaymentGateway, use: MockPaymentGateway });
|
|
750
|
+
|
|
751
|
+
const service = alepha.inject(OrderService);
|
|
752
|
+
// PaymentGateway is now mocked
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
\`\`\`
|
|
756
|
+
|
|
757
|
+
## Common Mistakes
|
|
758
|
+
|
|
759
|
+
1. **DON'T use decorators** - Use primitives (\`$action\`, not \`@Get()\`)
|
|
760
|
+
2. **DON'T use Zod** - Use TypeBox via \`t\` from Alepha
|
|
761
|
+
3. **DON'T use Express patterns** - No \`app.get()\`, \`router.use()\`
|
|
762
|
+
4. **DON'T inject across modules** - Use \`$client\` for cross-module calls
|
|
763
|
+
5. **DON'T use async constructors** - Use \`$hook({ on: "start" })\`
|
|
764
|
+
6. **DON'T instantiate manually** - Let DI container manage instances
|
|
765
|
+
|
|
766
|
+
## After Code Changes
|
|
767
|
+
|
|
768
|
+
Always run:
|
|
769
|
+
\`\`\`bash
|
|
770
|
+
alepha lint # Format and lint
|
|
771
|
+
alepha typecheck # Type checking
|
|
772
|
+
alepha test # Run tests (if applicable)
|
|
773
|
+
alepha build # Build the project
|
|
774
|
+
\`\`\`
|
|
775
|
+
|
|
776
|
+
## Documentation
|
|
777
|
+
|
|
778
|
+
- Full docs: https://alepha.dev/llms.txt
|
|
779
|
+
- Detailed docs: https://alepha.dev/llms-full.txt
|
|
780
|
+
`.trim();
|
|
781
|
+
};
|
|
782
|
+
|
|
153
783
|
//#endregion
|
|
154
784
|
//#region ../../src/cli/assets/dummySpecTs.ts
|
|
155
785
|
const dummySpecTs = () => `
|
|
@@ -191,442 +821,197 @@ const indexHtml = (browserEntry) => `
|
|
|
191
821
|
</body>
|
|
192
822
|
</html>
|
|
193
823
|
`.trim();
|
|
194
|
-
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region ../../src/cli/assets/mainBrowserTs.ts
|
|
197
|
-
const mainBrowserTs = () => `
|
|
198
|
-
import { Alepha, run } from "alepha";
|
|
199
|
-
import {
|
|
200
|
-
|
|
201
|
-
const alepha = Alepha.create();
|
|
202
|
-
|
|
203
|
-
alepha.with(
|
|
204
|
-
|
|
205
|
-
run(alepha);
|
|
206
|
-
`.trim();
|
|
207
|
-
|
|
208
|
-
//#endregion
|
|
209
|
-
//#region ../../src/cli/assets/
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
//#endregion
|
|
225
|
-
//#region ../../src/cli/assets/tsconfigJson.ts
|
|
226
|
-
const tsconfigJson = `
|
|
227
|
-
{
|
|
228
|
-
"extends": "alepha/tsconfig.base"
|
|
229
|
-
}
|
|
230
|
-
`.trim();
|
|
231
|
-
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region ../../src/cli/
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
* @returns The absolute path to the created file
|
|
300
|
-
*
|
|
301
|
-
* @example
|
|
302
|
-
* ```ts
|
|
303
|
-
* const runner = alepha.inject(ProcessRunner);
|
|
304
|
-
* const configPath = await runner.writeConfigFile("biome.json", biomeConfig);
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
async writeConfigFile(name, content, root = process.cwd()) {
|
|
308
|
-
const dir = join(root, "node_modules", ".alepha");
|
|
309
|
-
await mkdir(dir, { recursive: true }).catch(() => null);
|
|
310
|
-
const path = join(dir, name);
|
|
311
|
-
await writeFile(path, content);
|
|
312
|
-
this.log.debug(`Config file written: ${path}`);
|
|
313
|
-
return path;
|
|
314
|
-
}
|
|
315
|
-
async editFile(root, name, editFn) {
|
|
316
|
-
const filePath = join(root, name);
|
|
317
|
-
try {
|
|
318
|
-
await writeFile(filePath, await editFn(await readFile(filePath, "utf8")));
|
|
319
|
-
} catch (error) {
|
|
320
|
-
this.log.debug("Could not edit file", error);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
async editJsonFile(root, name, editFn) {
|
|
324
|
-
return await this.editFile(root, name, async (content) => {
|
|
325
|
-
const newObj = await editFn(JSON.parse(content));
|
|
326
|
-
return JSON.stringify(newObj, null, 2);
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
async removeFiles(root, files) {
|
|
330
|
-
await Promise.all(files.map((file) => this.fs.rm(join(root, file), {
|
|
331
|
-
force: true,
|
|
332
|
-
recursive: true
|
|
333
|
-
})));
|
|
334
|
-
}
|
|
335
|
-
async removeYarn(root) {
|
|
336
|
-
await this.removeFiles(root, [
|
|
337
|
-
".yarn",
|
|
338
|
-
".yarnrc.yml",
|
|
339
|
-
"yarn.lock"
|
|
340
|
-
]);
|
|
341
|
-
await this.editJsonFile(root, "package.json", (pkg) => {
|
|
342
|
-
delete pkg.packageManager;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
async removePnpm(root) {
|
|
346
|
-
await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
|
|
347
|
-
await this.editJsonFile(root, "package.json", (pkg) => {
|
|
348
|
-
delete pkg.packageManager;
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
async removeNpm(root) {
|
|
352
|
-
await this.removeFiles(root, ["package-lock.json"]);
|
|
353
|
-
}
|
|
354
|
-
async removeBun(root) {
|
|
355
|
-
await this.removeFiles(root, ["bun.lockb", "bun.lock"]);
|
|
356
|
-
}
|
|
357
|
-
async removeAllPmFilesExcept(root, except) {
|
|
358
|
-
if (except !== "yarn") await this.removeYarn(root);
|
|
359
|
-
if (except !== "pnpm") await this.removePnpm(root);
|
|
360
|
-
if (except !== "npm") await this.removeNpm(root);
|
|
361
|
-
if (except !== "bun") await this.removeBun(root);
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Ensure Yarn is configured in the project directory.
|
|
365
|
-
*
|
|
366
|
-
* Creates a .yarnrc.yml file with node-modules linker if it doesn't exist.
|
|
367
|
-
*
|
|
368
|
-
* @param root - The root directory of the project
|
|
369
|
-
*/
|
|
370
|
-
async ensureYarn(root) {
|
|
371
|
-
await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
|
|
372
|
-
await this.removeAllPmFilesExcept(root, "yarn");
|
|
373
|
-
}
|
|
374
|
-
async ensureBun(root) {
|
|
375
|
-
await this.removeAllPmFilesExcept(root, "bun");
|
|
376
|
-
}
|
|
377
|
-
async ensurePnpm(root) {
|
|
378
|
-
await this.removeAllPmFilesExcept(root, "pnpm");
|
|
379
|
-
}
|
|
380
|
-
async ensureNpm(root) {
|
|
381
|
-
await this.removeAllPmFilesExcept(root, "npm");
|
|
382
|
-
}
|
|
824
|
+
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region ../../src/cli/assets/mainBrowserTs.ts
|
|
827
|
+
const mainBrowserTs = () => `
|
|
828
|
+
import { Alepha, run } from "alepha";
|
|
829
|
+
import { WebModule } from "./web/index.ts";
|
|
830
|
+
|
|
831
|
+
const alepha = Alepha.create();
|
|
832
|
+
|
|
833
|
+
alepha.with(WebModule);
|
|
834
|
+
|
|
835
|
+
run(alepha);
|
|
836
|
+
`.trim();
|
|
837
|
+
|
|
838
|
+
//#endregion
|
|
839
|
+
//#region ../../src/cli/assets/mainServerTs.ts
|
|
840
|
+
const mainServerTs = (options = {}) => {
|
|
841
|
+
const { react = false } = options;
|
|
842
|
+
return `
|
|
843
|
+
import { Alepha, run } from "alepha";
|
|
844
|
+
import { ApiModule } from "./api/index.ts";
|
|
845
|
+
${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
|
|
846
|
+
const alepha = Alepha.create();
|
|
847
|
+
|
|
848
|
+
alepha.with(ApiModule);
|
|
849
|
+
${react ? `alepha.with(WebModule);\n` : ""}
|
|
850
|
+
run(alepha);
|
|
851
|
+
`.trim();
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region ../../src/cli/assets/tsconfigJson.ts
|
|
856
|
+
const tsconfigJson = `
|
|
857
|
+
{
|
|
858
|
+
"extends": "alepha/tsconfig.base"
|
|
859
|
+
}
|
|
860
|
+
`.trim();
|
|
861
|
+
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region ../../src/cli/assets/webAppRouterTs.ts
|
|
864
|
+
const webAppRouterTs = () => `
|
|
865
|
+
import { $page } from "@alepha/react/router";
|
|
866
|
+
import { $client } from "alepha/server/links";
|
|
867
|
+
import type { HelloController } from "../api/controllers/HelloController.ts";
|
|
868
|
+
|
|
869
|
+
export class AppRouter {
|
|
870
|
+
api = $client<HelloController>();
|
|
871
|
+
|
|
872
|
+
home = $page({
|
|
873
|
+
path: "/",
|
|
874
|
+
lazy: () => import("./components/Hello.tsx"),
|
|
875
|
+
loader: () => this.api.hello(),
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
`.trim();
|
|
879
|
+
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region ../../src/cli/assets/webHelloComponentTsx.ts
|
|
882
|
+
const webHelloComponentTsx = () => `
|
|
883
|
+
interface Props {
|
|
884
|
+
message: string;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const Hello = (props: Props) => {
|
|
888
|
+
return (
|
|
889
|
+
<div>
|
|
890
|
+
<h1>{props.message}</h1>
|
|
891
|
+
<p>Edit this component in src/web/components/Hello.tsx</p>
|
|
892
|
+
</div>
|
|
893
|
+
);
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
export default Hello;
|
|
897
|
+
`.trim();
|
|
898
|
+
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region ../../src/cli/assets/webIndexTs.ts
|
|
901
|
+
const webIndexTs = (options = {}) => {
|
|
902
|
+
const { appName = "app" } = options;
|
|
903
|
+
return `
|
|
904
|
+
import { $module } from "alepha";
|
|
905
|
+
import { AppRouter } from "./AppRouter.ts";
|
|
906
|
+
|
|
907
|
+
export const WebModule = $module({
|
|
908
|
+
name: "${appName}.web",
|
|
909
|
+
services: [AppRouter],
|
|
910
|
+
});
|
|
911
|
+
`.trim();
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region ../../src/cli/services/ProjectScaffolder.ts
|
|
916
|
+
/**
|
|
917
|
+
* Service for scaffolding new Alepha projects.
|
|
918
|
+
*
|
|
919
|
+
* Handles creation of:
|
|
920
|
+
* - Project structure (src/api, src/web)
|
|
921
|
+
* - Configuration files (tsconfig, biome, editorconfig)
|
|
922
|
+
* - Entry points (main.server.ts, main.browser.ts)
|
|
923
|
+
* - Example code (HelloController, Hello component)
|
|
924
|
+
*/
|
|
925
|
+
var ProjectScaffolder = class {
|
|
926
|
+
log = $logger();
|
|
927
|
+
fs = $inject(FileSystemProvider);
|
|
928
|
+
pm = $inject(PackageManagerUtils);
|
|
383
929
|
/**
|
|
384
|
-
*
|
|
930
|
+
* Get the app name from the directory name.
|
|
385
931
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
932
|
+
* Converts the directory name to a valid module name:
|
|
933
|
+
* - Converts to lowercase
|
|
934
|
+
* - Replaces spaces, dashes, underscores with nothing
|
|
935
|
+
* - Falls back to "app" if empty
|
|
388
936
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const devDependencies = {};
|
|
392
|
-
const scripts = {
|
|
393
|
-
dev: "alepha dev",
|
|
394
|
-
build: "alepha build",
|
|
395
|
-
lint: "alepha lint",
|
|
396
|
-
typecheck: "alepha typecheck",
|
|
397
|
-
verify: "alepha verify"
|
|
398
|
-
};
|
|
399
|
-
if (modes.ui) {
|
|
400
|
-
dependencies["@alepha/ui"] = `^${version}`;
|
|
401
|
-
modes.react = true;
|
|
402
|
-
}
|
|
403
|
-
if (modes.react) {
|
|
404
|
-
dependencies["@alepha/react"] = `^${version}`;
|
|
405
|
-
dependencies.react = "^19.2.0";
|
|
406
|
-
dependencies["react-dom"] = "^19.2.0";
|
|
407
|
-
devDependencies["@types/react"] = "^19.2.0";
|
|
408
|
-
}
|
|
409
|
-
return {
|
|
410
|
-
type: "module",
|
|
411
|
-
dependencies,
|
|
412
|
-
devDependencies,
|
|
413
|
-
scripts
|
|
414
|
-
};
|
|
937
|
+
getAppName(root) {
|
|
938
|
+
return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
|
|
415
939
|
}
|
|
416
940
|
/**
|
|
417
|
-
* Ensure
|
|
418
|
-
*
|
|
419
|
-
* Creates a new package.json if none exists, or updates an existing one to:
|
|
420
|
-
* - Set "type": "module"
|
|
421
|
-
* - Add Alepha dependencies
|
|
422
|
-
* - Add standard scripts
|
|
423
|
-
*
|
|
424
|
-
* @param root - The root directory of the project
|
|
425
|
-
* @param modes - Configuration for which dependencies to include
|
|
941
|
+
* Ensure all configuration files exist.
|
|
426
942
|
*/
|
|
427
|
-
async ensurePackageJson(root, modes) {
|
|
428
|
-
const packageJsonPath = join(root, "package.json");
|
|
429
|
-
try {
|
|
430
|
-
await access(packageJsonPath);
|
|
431
|
-
} catch (error) {
|
|
432
|
-
const obj = this.generatePackageJsonContent(modes);
|
|
433
|
-
await writeFile(packageJsonPath, JSON.stringify(obj, null, 2));
|
|
434
|
-
return obj;
|
|
435
|
-
}
|
|
436
|
-
const content = await readFile(packageJsonPath, "utf8");
|
|
437
|
-
const packageJson$1 = JSON.parse(content);
|
|
438
|
-
const newPackageJson = this.generatePackageJsonContent(modes);
|
|
439
|
-
packageJson$1.type = "module";
|
|
440
|
-
packageJson$1.dependencies ??= {};
|
|
441
|
-
packageJson$1.devDependencies ??= {};
|
|
442
|
-
packageJson$1.scripts ??= {};
|
|
443
|
-
Object.assign(packageJson$1.dependencies, newPackageJson.dependencies);
|
|
444
|
-
Object.assign(packageJson$1.devDependencies, newPackageJson.devDependencies);
|
|
445
|
-
Object.assign(packageJson$1.scripts, newPackageJson.scripts);
|
|
446
|
-
await writeFile(packageJsonPath, JSON.stringify(packageJson$1, null, 2));
|
|
447
|
-
return packageJson$1;
|
|
448
|
-
}
|
|
449
943
|
async ensureConfig(root, opts) {
|
|
450
944
|
const tasks = [];
|
|
451
|
-
if (opts.packageJson) tasks.push(this.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson));
|
|
945
|
+
if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
|
|
452
946
|
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
|
|
453
|
-
if (opts.indexHtml) tasks.push(this.
|
|
947
|
+
if (opts.indexHtml) tasks.push(this.ensureReactProject(root));
|
|
454
948
|
if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
|
|
455
949
|
if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
|
|
456
|
-
|
|
950
|
+
if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd));
|
|
951
|
+
await Promise.all(tasks);
|
|
457
952
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Ensure tsconfig.json exists in the project.
|
|
460
|
-
*
|
|
461
|
-
* Creates a standard Alepha tsconfig.json if none exists.
|
|
462
|
-
*
|
|
463
|
-
* @param root - The root directory of the project
|
|
464
|
-
*/
|
|
465
953
|
async ensureTsConfig(root) {
|
|
466
|
-
await this.
|
|
467
|
-
|
|
468
|
-
async checkFileExists(root, name, checkParentDirectories = false) {
|
|
469
|
-
const configPath = join(root, name);
|
|
470
|
-
if (!checkParentDirectories) try {
|
|
471
|
-
await access(configPath);
|
|
472
|
-
return configPath;
|
|
473
|
-
} catch {
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
let currentDir = root;
|
|
477
|
-
const maxIterations = 10;
|
|
478
|
-
let level = 0;
|
|
479
|
-
while (level < maxIterations) {
|
|
480
|
-
try {
|
|
481
|
-
const maybe = join(currentDir, name);
|
|
482
|
-
await access(maybe);
|
|
483
|
-
return maybe;
|
|
484
|
-
} catch {
|
|
485
|
-
const parentDir = join(currentDir, "..");
|
|
486
|
-
if (parentDir === currentDir) break;
|
|
487
|
-
currentDir = parentDir;
|
|
488
|
-
}
|
|
489
|
-
level += 1;
|
|
490
|
-
}
|
|
954
|
+
if (await this.existsInParents(root, "tsconfig.json")) return;
|
|
955
|
+
await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
|
|
491
956
|
}
|
|
492
|
-
async ensureFileExists(root, name, content, checkParentDirectories = false) {
|
|
493
|
-
if (!await this.checkFileExists(root, name, checkParentDirectories)) await writeFile(join(root, name), content);
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Get the path to Biome configuration file.
|
|
497
|
-
*
|
|
498
|
-
* Looks for an existing biome.json in the project root, or creates one if it doesn't exist.
|
|
499
|
-
*/
|
|
500
957
|
async ensureBiomeConfig(root) {
|
|
501
|
-
await this.
|
|
958
|
+
await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
|
|
502
959
|
}
|
|
503
|
-
/**
|
|
504
|
-
* Ensure .editorconfig exists in the project.
|
|
505
|
-
*
|
|
506
|
-
* Creates a standard .editorconfig if none exists.
|
|
507
|
-
*
|
|
508
|
-
* @param root - The root directory of the project
|
|
509
|
-
*/
|
|
510
960
|
async ensureEditorConfig(root) {
|
|
511
|
-
await this.
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Load Alepha instance from a server entry file.
|
|
515
|
-
*
|
|
516
|
-
* Dynamically imports the server entry file and extracts the Alepha instance.
|
|
517
|
-
* Skips the automatic start process.
|
|
518
|
-
*
|
|
519
|
-
* @param rootDir - The root directory of the project
|
|
520
|
-
* @param explicitEntry - Optional explicit path to the entry file
|
|
521
|
-
* @returns Object containing the Alepha instance and the entry file path
|
|
522
|
-
* @throws {AlephaError} If the Alepha instance cannot be found
|
|
523
|
-
*/
|
|
524
|
-
async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
|
|
525
|
-
process.env.ALEPHA_CLI_IMPORT = "true";
|
|
526
|
-
const entry = await boot.getServerEntry(rootDir, explicitEntry);
|
|
527
|
-
delete global.__alepha;
|
|
528
|
-
const mod = await import(entry);
|
|
529
|
-
this.log.debug(`Load entry: ${entry}`);
|
|
530
|
-
if (mod.default instanceof Alepha) return {
|
|
531
|
-
alepha: mod.default,
|
|
532
|
-
entry
|
|
533
|
-
};
|
|
534
|
-
const g = global;
|
|
535
|
-
if (g.__alepha) return {
|
|
536
|
-
alepha: g.__alepha,
|
|
537
|
-
entry
|
|
538
|
-
};
|
|
539
|
-
throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
|
|
961
|
+
await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
|
|
540
962
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
* Creates a temporary entities.js file that imports from the entry file
|
|
545
|
-
* and exports database models for Drizzle Kit to process.
|
|
546
|
-
*
|
|
547
|
-
* @param entry - Path to the server entry file
|
|
548
|
-
* @param provider - Name of the database provider
|
|
549
|
-
* @param models - Array of model names to export
|
|
550
|
-
* @returns JavaScript code as a string
|
|
551
|
-
*/
|
|
552
|
-
generateEntitiesJs(entry, provider, models = []) {
|
|
553
|
-
return `
|
|
554
|
-
import "${entry}";
|
|
555
|
-
import { DrizzleKitProvider, Repository } from "alepha/orm";
|
|
556
|
-
|
|
557
|
-
const alepha = globalThis.__alepha;
|
|
558
|
-
const kit = alepha.inject(DrizzleKitProvider);
|
|
559
|
-
const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
|
|
560
|
-
const models = kit.getModels(provider);
|
|
561
|
-
|
|
562
|
-
${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
563
|
-
|
|
564
|
-
`.trim();
|
|
963
|
+
async ensureClaudeMd(root, options = {}) {
|
|
964
|
+
const path = this.fs.join(root, "CLAUDE.md");
|
|
965
|
+
if (!await this.fs.exists(path)) await this.fs.writeFile(path, claudeMd(options));
|
|
565
966
|
}
|
|
566
967
|
/**
|
|
567
|
-
*
|
|
968
|
+
* Ensure src/main.server.ts exists with full API structure.
|
|
568
969
|
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
970
|
+
* Creates:
|
|
971
|
+
* - src/main.server.ts (entry point)
|
|
972
|
+
* - src/api/index.ts (API module)
|
|
973
|
+
* - src/api/controllers/HelloController.ts (example controller)
|
|
571
974
|
*/
|
|
572
|
-
async
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) return "pnpm";
|
|
583
|
-
return "npm";
|
|
584
|
-
}
|
|
585
|
-
async ensureIndexHtml(root) {
|
|
586
|
-
if (await this.fs.exists(join(root, "index.html"))) return;
|
|
587
|
-
const serverEntry = "src/main.server.ts";
|
|
588
|
-
const browserEntry = "src/main.browser.ts";
|
|
589
|
-
const appRouter = "src/AppRouter.ts";
|
|
590
|
-
await this.fs.writeFile(join(root, "index.html"), indexHtml(browserEntry));
|
|
591
|
-
try {
|
|
592
|
-
await this.fs.mkdir(join(root, "src"), { recursive: true });
|
|
593
|
-
} catch {}
|
|
594
|
-
if (!await this.fs.exists(join(root, browserEntry))) await this.fs.writeFile(join(root, browserEntry), mainBrowserTs());
|
|
595
|
-
if (!await this.fs.exists(join(root, serverEntry))) await this.fs.writeFile(join(root, serverEntry), mainBrowserTs());
|
|
596
|
-
if (!await this.fs.exists(join(root, appRouter))) await this.fs.writeFile(join(root, appRouter), appRouterTs());
|
|
597
|
-
}
|
|
598
|
-
async exists(root, dirName) {
|
|
599
|
-
return this.fs.exists(join(root, dirName));
|
|
975
|
+
async ensureApiProject(root) {
|
|
976
|
+
const srcDir = this.fs.join(root, "src");
|
|
977
|
+
if (await this.fs.exists(srcDir)) {
|
|
978
|
+
if ((await this.fs.ls(srcDir)).length > 0) return;
|
|
979
|
+
}
|
|
980
|
+
const appName = this.getAppName(root);
|
|
981
|
+
await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
|
|
982
|
+
await this.fs.writeFile(this.fs.join(srcDir, "main.server.ts"), mainServerTs());
|
|
983
|
+
await this.fs.writeFile(this.fs.join(srcDir, "api/index.ts"), apiIndexTs({ appName }));
|
|
984
|
+
await this.fs.writeFile(this.fs.join(srcDir, "api/controllers/HelloController.ts"), apiHelloControllerTs());
|
|
600
985
|
}
|
|
601
986
|
/**
|
|
602
|
-
* Ensure
|
|
987
|
+
* Ensure full React project structure exists.
|
|
603
988
|
*
|
|
604
|
-
* Creates
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
989
|
+
* Creates:
|
|
990
|
+
* - index.html
|
|
991
|
+
* - src/main.server.ts, src/main.browser.ts
|
|
992
|
+
* - src/api/index.ts, src/api/controllers/HelloController.ts
|
|
993
|
+
* - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
|
|
608
994
|
*/
|
|
609
|
-
async
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
995
|
+
async ensureReactProject(root) {
|
|
996
|
+
if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
|
|
997
|
+
const appName = this.getAppName(root);
|
|
998
|
+
await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
|
|
999
|
+
await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
|
|
1000
|
+
await this.fs.writeFile(this.fs.join(root, "index.html"), indexHtml("src/main.browser.ts"));
|
|
1001
|
+
await this.ensureFileIfNotExists(root, "src/api/index.ts", apiIndexTs({ appName }));
|
|
1002
|
+
await this.ensureFileIfNotExists(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs());
|
|
1003
|
+
await this.ensureFileIfNotExists(root, "src/main.server.ts", mainServerTs({ react: true }));
|
|
1004
|
+
await this.ensureFileIfNotExists(root, "src/web/index.ts", webIndexTs({ appName }));
|
|
1005
|
+
await this.ensureFileIfNotExists(root, "src/web/AppRouter.ts", webAppRouterTs());
|
|
1006
|
+
await this.ensureFileIfNotExists(root, "src/web/components/Hello.tsx", webHelloComponentTsx());
|
|
1007
|
+
await this.ensureFileIfNotExists(root, "src/main.browser.ts", mainBrowserTs());
|
|
618
1008
|
}
|
|
619
1009
|
/**
|
|
620
1010
|
* Ensure test directory exists with a dummy test file.
|
|
621
|
-
*
|
|
622
|
-
* Creates the test directory and a dummy.spec.ts file if the test directory
|
|
623
|
-
* doesn't exist or is empty.
|
|
624
|
-
*
|
|
625
|
-
* @param root - The root directory of the project
|
|
626
1011
|
*/
|
|
627
1012
|
async ensureTestDir(root) {
|
|
628
|
-
const testDir = join(root, "test");
|
|
629
|
-
const dummyPath = join(testDir, "dummy.spec.ts");
|
|
1013
|
+
const testDir = this.fs.join(root, "test");
|
|
1014
|
+
const dummyPath = this.fs.join(testDir, "dummy.spec.ts");
|
|
630
1015
|
if (!await this.fs.exists(testDir)) {
|
|
631
1016
|
await this.fs.mkdir(testDir, { recursive: true });
|
|
632
1017
|
await this.fs.writeFile(dummyPath, dummySpecTs());
|
|
@@ -634,68 +1019,20 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
|
634
1019
|
}
|
|
635
1020
|
if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
|
|
636
1021
|
}
|
|
637
|
-
async
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Check if a dependency is installed in the project.
|
|
643
|
-
*
|
|
644
|
-
* @param root - The root directory of the project
|
|
645
|
-
* @param packageName - The name of the package to check
|
|
646
|
-
* @returns True if the package is in dependencies or devDependencies
|
|
647
|
-
*/
|
|
648
|
-
async hasDependency(root, packageName) {
|
|
649
|
-
try {
|
|
650
|
-
const pkg = await this.readPackageJson(root);
|
|
651
|
-
return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
|
|
652
|
-
} catch {
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Check if Expo is present in the project.
|
|
658
|
-
*
|
|
659
|
-
* @param root - The root directory of the project
|
|
660
|
-
* @returns True if expo is in dependencies or devDependencies
|
|
661
|
-
*/
|
|
662
|
-
async hasExpo(root) {
|
|
663
|
-
return this.hasDependency(root, "expo");
|
|
664
|
-
}
|
|
665
|
-
async getInstallCommand(root, packageName, dev = true) {
|
|
666
|
-
const pm = await this.getPackageManager(root);
|
|
667
|
-
let cmd;
|
|
668
|
-
switch (pm) {
|
|
669
|
-
case "yarn":
|
|
670
|
-
cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
|
|
671
|
-
break;
|
|
672
|
-
case "pnpm":
|
|
673
|
-
cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
|
|
674
|
-
break;
|
|
675
|
-
case "bun":
|
|
676
|
-
cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
|
|
677
|
-
break;
|
|
678
|
-
default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
|
|
679
|
-
}
|
|
680
|
-
return cmd.replace(/\s+/g, " ").trim();
|
|
1022
|
+
async ensureFileIfNotExists(root, relativePath, content) {
|
|
1023
|
+
const fullPath = this.fs.join(root, relativePath);
|
|
1024
|
+
if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
|
|
681
1025
|
}
|
|
682
1026
|
/**
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
* Automatically detects the package manager (yarn, pnpm, npm) and installs
|
|
686
|
-
* the package as a dev dependency if not already present.
|
|
1027
|
+
* Check if a file exists in the given directory or any parent directory.
|
|
687
1028
|
*/
|
|
688
|
-
async
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
this.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (options.run) await options.run(cmd, { alias: `installing ${packageName}` });
|
|
696
|
-
else {
|
|
697
|
-
this.log.debug(`Installing ${packageName}`);
|
|
698
|
-
await this.exec(cmd, { global: true });
|
|
1029
|
+
async existsInParents(root, filename) {
|
|
1030
|
+
let current = root;
|
|
1031
|
+
while (true) {
|
|
1032
|
+
if (await this.fs.exists(this.fs.join(current, filename))) return true;
|
|
1033
|
+
const parent = dirname(current);
|
|
1034
|
+
if (parent === current) return false;
|
|
1035
|
+
current = parent;
|
|
699
1036
|
}
|
|
700
1037
|
}
|
|
701
1038
|
};
|
|
@@ -704,7 +1041,10 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
|
704
1041
|
//#region ../../src/cli/commands/build.ts
|
|
705
1042
|
var BuildCommand = class {
|
|
706
1043
|
log = $logger();
|
|
1044
|
+
fs = $inject(FileSystemProvider);
|
|
707
1045
|
utils = $inject(AlephaCliUtils);
|
|
1046
|
+
pm = $inject(PackageManagerUtils);
|
|
1047
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
708
1048
|
options = $use(buildOptions);
|
|
709
1049
|
build = $command({
|
|
710
1050
|
name: "build",
|
|
@@ -719,27 +1059,27 @@ var BuildCommand = class {
|
|
|
719
1059
|
vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
|
|
720
1060
|
cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
|
|
721
1061
|
docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
|
|
722
|
-
sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" }))
|
|
1062
|
+
sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
|
|
1063
|
+
bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
|
|
723
1064
|
}),
|
|
724
1065
|
handler: async ({ flags, args, run, root }) => {
|
|
725
1066
|
process.env.ALEPHA_BUILD_MODE = "cli";
|
|
726
1067
|
process.env.NODE_ENV = "production";
|
|
727
|
-
if (await this.
|
|
728
|
-
await this.
|
|
1068
|
+
if (await this.pm.hasExpo(root)) return;
|
|
1069
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
729
1070
|
const entry = await boot.getServerEntry(root, args);
|
|
730
1071
|
this.log.trace("Entry file found", { entry });
|
|
731
1072
|
const distDir = "dist";
|
|
732
1073
|
const clientDir = "public";
|
|
733
|
-
await this.
|
|
1074
|
+
await this.pm.ensureDependency(root, "vite", {
|
|
1075
|
+
run,
|
|
1076
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1077
|
+
});
|
|
734
1078
|
await run.rm("dist", { alias: "clean dist" });
|
|
735
1079
|
const options = this.options;
|
|
736
1080
|
await this.utils.loadEnv(root, [".env", ".env.production"]);
|
|
737
1081
|
const stats = flags.stats ?? options.stats ?? false;
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
await access(join(root, "index.html"));
|
|
741
|
-
hasClient = true;
|
|
742
|
-
} catch {}
|
|
1082
|
+
const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
|
|
743
1083
|
if (hasClient) await run({
|
|
744
1084
|
name: "vite build client",
|
|
745
1085
|
handler: () => buildClient({
|
|
@@ -752,19 +1092,20 @@ var BuildCommand = class {
|
|
|
752
1092
|
await run({
|
|
753
1093
|
name: "vite build server",
|
|
754
1094
|
handler: async () => {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1095
|
+
const clientIndexPath = `${distDir}/${clientDir}/index.html`;
|
|
1096
|
+
const clientBuilt = await this.fs.exists(clientIndexPath);
|
|
1097
|
+
const conditions = [];
|
|
1098
|
+
if (flags.bun) conditions.push("bun");
|
|
1099
|
+
if (options.cloudflare) conditions.push("workerd");
|
|
760
1100
|
await buildServer({
|
|
761
1101
|
silent: true,
|
|
762
1102
|
entry,
|
|
763
1103
|
distDir,
|
|
764
1104
|
clientDir: clientBuilt ? clientDir : void 0,
|
|
765
|
-
stats
|
|
1105
|
+
stats,
|
|
1106
|
+
conditions
|
|
766
1107
|
});
|
|
767
|
-
if (clientBuilt) await
|
|
1108
|
+
if (clientBuilt) await this.fs.rm(clientIndexPath);
|
|
768
1109
|
}
|
|
769
1110
|
});
|
|
770
1111
|
await copyAssets({
|
|
@@ -778,7 +1119,7 @@ var BuildCommand = class {
|
|
|
778
1119
|
if (sitemapHostname) await run({
|
|
779
1120
|
name: "add sitemap",
|
|
780
1121
|
handler: async () => {
|
|
781
|
-
await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
|
|
1122
|
+
await this.fs.writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
|
|
782
1123
|
entry: `${distDir}/index.js`,
|
|
783
1124
|
baseUrl: sitemapHostname
|
|
784
1125
|
}));
|
|
@@ -844,6 +1185,7 @@ const drizzleCommandFlags = t.object({
|
|
|
844
1185
|
});
|
|
845
1186
|
var DbCommand = class {
|
|
846
1187
|
log = $logger();
|
|
1188
|
+
fs = $inject(FileSystemProvider);
|
|
847
1189
|
utils = $inject(AlephaCliUtils);
|
|
848
1190
|
/**
|
|
849
1191
|
* Check if database migrations are up to date.
|
|
@@ -868,15 +1210,16 @@ var DbCommand = class {
|
|
|
868
1210
|
const providerName = provider.name;
|
|
869
1211
|
if (accepted.has(providerName)) continue;
|
|
870
1212
|
accepted.add(providerName);
|
|
871
|
-
const migrationDir = join(rootDir, "migrations", providerName);
|
|
872
|
-
const
|
|
873
|
-
if (!
|
|
1213
|
+
const migrationDir = this.fs.join(rootDir, "migrations", providerName);
|
|
1214
|
+
const journalBuffer = await this.fs.readFile(`${migrationDir}/meta/_journal.json`).catch(() => null);
|
|
1215
|
+
if (!journalBuffer) {
|
|
874
1216
|
this.log.info("No migration journal found.");
|
|
875
1217
|
return;
|
|
876
1218
|
}
|
|
877
|
-
const journal = JSON.parse(
|
|
1219
|
+
const journal = JSON.parse(journalBuffer.toString("utf-8"));
|
|
878
1220
|
const lastMigration = journal.entries[journal.entries.length - 1];
|
|
879
|
-
const
|
|
1221
|
+
const snapshotBuffer = await this.fs.readFile(`${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`);
|
|
1222
|
+
const lastSnapshot = JSON.parse(snapshotBuffer.toString("utf-8"));
|
|
880
1223
|
const models = drizzleKitProvider.getModels(provider);
|
|
881
1224
|
const kit = drizzleKitProvider.importDrizzleKit();
|
|
882
1225
|
const now = kit.generateDrizzleJson(models, lastSnapshot.id);
|
|
@@ -1030,6 +1373,7 @@ var DbCommand = class {
|
|
|
1030
1373
|
const provider = primitive.provider;
|
|
1031
1374
|
const providerName = provider.name;
|
|
1032
1375
|
const dialect = provider.dialect;
|
|
1376
|
+
if (providerName === "") continue;
|
|
1033
1377
|
if (accepted.has(providerName)) continue;
|
|
1034
1378
|
accepted.add(providerName);
|
|
1035
1379
|
if (options.provider && options.provider !== providerName) {
|
|
@@ -1043,6 +1387,7 @@ var DbCommand = class {
|
|
|
1043
1387
|
provider,
|
|
1044
1388
|
providerName,
|
|
1045
1389
|
providerUrl: provider.url,
|
|
1390
|
+
providerDriver: provider.driver,
|
|
1046
1391
|
dialect,
|
|
1047
1392
|
entry,
|
|
1048
1393
|
rootDir
|
|
@@ -1064,9 +1409,9 @@ var DbCommand = class {
|
|
|
1064
1409
|
dbCredentials: { url: options.providerUrl }
|
|
1065
1410
|
};
|
|
1066
1411
|
if (options.provider.schema) config.schemaFilter = options.provider.schema;
|
|
1067
|
-
if (options.
|
|
1068
|
-
if (options.
|
|
1069
|
-
if (options.dialect === "sqlite") if (options.
|
|
1412
|
+
if (options.providerDriver === "d1") config.driver = "d1-http";
|
|
1413
|
+
if (options.providerDriver === "pglite") config.driver = "pglite";
|
|
1414
|
+
if (options.dialect === "sqlite") if (options.providerDriver === "d1") {
|
|
1070
1415
|
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
1071
1416
|
if (!token) throw new AlephaError("CLOUDFLARE_API_TOKEN environment variable is not set. https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit");
|
|
1072
1417
|
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
@@ -1083,7 +1428,7 @@ var DbCommand = class {
|
|
|
1083
1428
|
} else {
|
|
1084
1429
|
let url = options.providerUrl;
|
|
1085
1430
|
url = url.replace("sqlite://", "").replace("file://", "");
|
|
1086
|
-
url = join(options.rootDir, url);
|
|
1431
|
+
url = this.fs.join(options.rootDir, url);
|
|
1087
1432
|
config.dbCredentials = { url };
|
|
1088
1433
|
}
|
|
1089
1434
|
const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
|
|
@@ -1095,7 +1440,9 @@ var DbCommand = class {
|
|
|
1095
1440
|
//#region ../../src/cli/commands/deploy.ts
|
|
1096
1441
|
var DeployCommand = class {
|
|
1097
1442
|
log = $logger();
|
|
1443
|
+
fs = $inject(FileSystemProvider);
|
|
1098
1444
|
utils = $inject(AlephaCliUtils);
|
|
1445
|
+
pm = $inject(PackageManagerUtils);
|
|
1099
1446
|
/**
|
|
1100
1447
|
* Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
|
|
1101
1448
|
*
|
|
@@ -1143,7 +1490,10 @@ var DeployCommand = class {
|
|
|
1143
1490
|
this.log.debug("Running database migrations before deployment...");
|
|
1144
1491
|
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
1145
1492
|
}
|
|
1146
|
-
await this.
|
|
1493
|
+
await this.pm.ensureDependency(root, "vercel", {
|
|
1494
|
+
dev: true,
|
|
1495
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1496
|
+
});
|
|
1147
1497
|
const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
|
|
1148
1498
|
this.log.debug(`Deploying to Vercel with command: ${command}`);
|
|
1149
1499
|
await this.utils.exec(command);
|
|
@@ -1154,15 +1504,21 @@ var DeployCommand = class {
|
|
|
1154
1504
|
this.log.debug("Running database migrations before deployment...");
|
|
1155
1505
|
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
1156
1506
|
}
|
|
1157
|
-
await this.
|
|
1507
|
+
await this.pm.ensureDependency(root, "wrangler", {
|
|
1508
|
+
dev: true,
|
|
1509
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1510
|
+
});
|
|
1158
1511
|
const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
|
|
1159
1512
|
this.log.info(`Deploying to Cloudflare with command: ${command}`);
|
|
1160
1513
|
await this.utils.exec(command);
|
|
1161
1514
|
return;
|
|
1162
1515
|
}
|
|
1163
1516
|
if (await this.utils.exists(root, "dist/public/404.html")) {
|
|
1164
|
-
await this.
|
|
1165
|
-
|
|
1517
|
+
await this.pm.ensureDependency(root, "surge", {
|
|
1518
|
+
dev: true,
|
|
1519
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1520
|
+
});
|
|
1521
|
+
const distPath = this.fs.join(root, "dist/public");
|
|
1166
1522
|
this.log.debug(`Deploying to Surge from directory: ${distPath}`);
|
|
1167
1523
|
await this.utils.exec(`surge ${distPath}`);
|
|
1168
1524
|
return;
|
|
@@ -1176,7 +1532,10 @@ var DeployCommand = class {
|
|
|
1176
1532
|
//#region ../../src/cli/commands/dev.ts
|
|
1177
1533
|
var DevCommand = class {
|
|
1178
1534
|
log = $logger();
|
|
1535
|
+
fs = $inject(FileSystemProvider);
|
|
1179
1536
|
utils = $inject(AlephaCliUtils);
|
|
1537
|
+
pm = $inject(PackageManagerUtils);
|
|
1538
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1180
1539
|
alepha = $inject(Alepha);
|
|
1181
1540
|
/**
|
|
1182
1541
|
* Will run the project in watch mode.
|
|
@@ -1192,8 +1551,8 @@ var DevCommand = class {
|
|
|
1192
1551
|
description: "Filepath to run"
|
|
1193
1552
|
})),
|
|
1194
1553
|
handler: async ({ args, root }) => {
|
|
1195
|
-
const expo = await this.
|
|
1196
|
-
await this.
|
|
1554
|
+
const expo = await this.pm.hasExpo(root);
|
|
1555
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
1197
1556
|
if (expo) {
|
|
1198
1557
|
await this.utils.exec("expo start");
|
|
1199
1558
|
return;
|
|
@@ -1208,26 +1567,16 @@ var DevCommand = class {
|
|
|
1208
1567
|
await this.utils.exec(cmd, { global: exe === "bun" });
|
|
1209
1568
|
return;
|
|
1210
1569
|
}
|
|
1211
|
-
await this.
|
|
1570
|
+
await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1212
1571
|
await devServer();
|
|
1213
1572
|
}
|
|
1214
1573
|
});
|
|
1215
1574
|
async isBunProject(root) {
|
|
1216
1575
|
if (this.alepha.isBun()) return true;
|
|
1217
|
-
|
|
1218
|
-
await access(join(root, "bun.lock"));
|
|
1219
|
-
return true;
|
|
1220
|
-
} catch {
|
|
1221
|
-
return false;
|
|
1222
|
-
}
|
|
1576
|
+
return this.fs.exists(this.fs.join(root, "bun.lock"));
|
|
1223
1577
|
}
|
|
1224
1578
|
async isFullstackProject(root) {
|
|
1225
|
-
|
|
1226
|
-
await access(join(root, "index.html"));
|
|
1227
|
-
return true;
|
|
1228
|
-
} catch {
|
|
1229
|
-
return false;
|
|
1230
|
-
}
|
|
1579
|
+
return this.fs.exists(this.fs.join(root, "index.html"));
|
|
1231
1580
|
}
|
|
1232
1581
|
};
|
|
1233
1582
|
|
|
@@ -1235,12 +1584,14 @@ var DevCommand = class {
|
|
|
1235
1584
|
//#region ../../src/cli/commands/format.ts
|
|
1236
1585
|
var FormatCommand = class {
|
|
1237
1586
|
utils = $inject(AlephaCliUtils);
|
|
1587
|
+
pm = $inject(PackageManagerUtils);
|
|
1588
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1238
1589
|
format = $command({
|
|
1239
1590
|
name: "format",
|
|
1240
1591
|
description: "Format the codebase using Biome",
|
|
1241
1592
|
handler: async ({ root }) => {
|
|
1242
|
-
await this.
|
|
1243
|
-
await this.
|
|
1593
|
+
await this.scaffolder.ensureConfig(root, { biomeJson: true });
|
|
1594
|
+
await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1244
1595
|
await this.utils.exec("biome format --fix");
|
|
1245
1596
|
}
|
|
1246
1597
|
});
|
|
@@ -1560,6 +1911,9 @@ var GenCommand = class {
|
|
|
1560
1911
|
//#region ../../src/cli/commands/init.ts
|
|
1561
1912
|
var InitCommand = class {
|
|
1562
1913
|
utils = $inject(AlephaCliUtils);
|
|
1914
|
+
pm = $inject(PackageManagerUtils);
|
|
1915
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1916
|
+
fs = $inject(FileSystemProvider);
|
|
1563
1917
|
/**
|
|
1564
1918
|
* Ensure the project has the necessary Alepha configuration files.
|
|
1565
1919
|
* Add the correct dependencies to package.json and install them.
|
|
@@ -1567,45 +1921,78 @@ var InitCommand = class {
|
|
|
1567
1921
|
init = $command({
|
|
1568
1922
|
name: "init",
|
|
1569
1923
|
description: "Add missing Alepha configuration files to the project",
|
|
1924
|
+
args: t.optional(t.text({
|
|
1925
|
+
title: "path",
|
|
1926
|
+
trim: true,
|
|
1927
|
+
lowercase: true
|
|
1928
|
+
})),
|
|
1570
1929
|
flags: t.object({
|
|
1930
|
+
agent: t.optional(t.boolean({
|
|
1931
|
+
aliases: ["a"],
|
|
1932
|
+
description: "Add CLAUDE.md for Claude Code AI assistant"
|
|
1933
|
+
})),
|
|
1571
1934
|
yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
|
|
1572
1935
|
pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
|
|
1573
1936
|
npm: t.optional(t.boolean({ description: "Use npm package manager" })),
|
|
1574
1937
|
bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
|
|
1575
|
-
|
|
1576
|
-
|
|
1938
|
+
web: t.optional(t.boolean({
|
|
1939
|
+
aliases: ["r"],
|
|
1940
|
+
description: "Include Alepha React dependencies"
|
|
1941
|
+
})),
|
|
1942
|
+
admin: t.optional(t.boolean({ description: "Include Alepha UI dependencies" })),
|
|
1577
1943
|
test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
|
|
1578
1944
|
}),
|
|
1579
|
-
handler: async ({ run, flags, root }) => {
|
|
1580
|
-
if (flags.
|
|
1581
|
-
|
|
1945
|
+
handler: async ({ run, flags, root, args }) => {
|
|
1946
|
+
if (flags.admin) flags.web = true;
|
|
1947
|
+
if (args) {
|
|
1948
|
+
root = this.fs.join(root, args);
|
|
1949
|
+
await this.fs.mkdir(root);
|
|
1950
|
+
}
|
|
1951
|
+
const isExpo = await this.pm.hasExpo(root);
|
|
1582
1952
|
await run({
|
|
1583
1953
|
name: "ensuring configuration files",
|
|
1584
1954
|
handler: async () => {
|
|
1585
|
-
await this.
|
|
1955
|
+
await this.scaffolder.ensureConfig(root, {
|
|
1586
1956
|
tsconfigJson: true,
|
|
1587
1957
|
packageJson: flags,
|
|
1588
1958
|
biomeJson: true,
|
|
1589
1959
|
editorconfig: true,
|
|
1590
|
-
indexHtml: !!flags.
|
|
1960
|
+
indexHtml: !!flags.web && !isExpo,
|
|
1961
|
+
claudeMd: flags.agent ? {
|
|
1962
|
+
react: !!flags.web,
|
|
1963
|
+
ui: !!flags.admin
|
|
1964
|
+
} : false
|
|
1591
1965
|
});
|
|
1592
|
-
if (!flags.
|
|
1966
|
+
if (!flags.web) await this.scaffolder.ensureApiProject(root);
|
|
1593
1967
|
}
|
|
1594
1968
|
});
|
|
1595
|
-
const
|
|
1596
|
-
if (
|
|
1597
|
-
await this.
|
|
1598
|
-
await run("yarn set version stable");
|
|
1599
|
-
} else if (
|
|
1600
|
-
else if (
|
|
1601
|
-
else await this.
|
|
1602
|
-
await run(`${
|
|
1603
|
-
|
|
1604
|
-
|
|
1969
|
+
const pmName = await this.pm.getPackageManager(root, flags);
|
|
1970
|
+
if (pmName === "yarn") {
|
|
1971
|
+
await this.pm.ensureYarn(root);
|
|
1972
|
+
await run("yarn set version stable", { root });
|
|
1973
|
+
} else if (pmName === "bun") await this.pm.ensureBun(root);
|
|
1974
|
+
else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
|
|
1975
|
+
else await this.pm.ensureNpm(root);
|
|
1976
|
+
await run(`${pmName} install`, {
|
|
1977
|
+
alias: `installing dependencies with ${pmName}`,
|
|
1978
|
+
root
|
|
1979
|
+
});
|
|
1980
|
+
if (!isExpo) await this.pm.ensureDependency(root, "vite", {
|
|
1981
|
+
run,
|
|
1982
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1983
|
+
});
|
|
1984
|
+
await this.pm.ensureDependency(root, "@biomejs/biome", {
|
|
1985
|
+
run,
|
|
1986
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1987
|
+
});
|
|
1605
1988
|
if (flags.test) {
|
|
1606
|
-
await this.
|
|
1607
|
-
await run(`${
|
|
1989
|
+
await this.scaffolder.ensureTestDir(root);
|
|
1990
|
+
await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
|
|
1608
1991
|
}
|
|
1992
|
+
await run(`${pmName} run lint`, {
|
|
1993
|
+
alias: "running linter",
|
|
1994
|
+
root
|
|
1995
|
+
});
|
|
1609
1996
|
}
|
|
1610
1997
|
});
|
|
1611
1998
|
};
|
|
@@ -1614,12 +2001,14 @@ var InitCommand = class {
|
|
|
1614
2001
|
//#region ../../src/cli/commands/lint.ts
|
|
1615
2002
|
var LintCommand = class {
|
|
1616
2003
|
utils = $inject(AlephaCliUtils);
|
|
2004
|
+
pm = $inject(PackageManagerUtils);
|
|
2005
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1617
2006
|
lint = $command({
|
|
1618
2007
|
name: "lint",
|
|
1619
2008
|
description: "Run linter across the codebase using Biome",
|
|
1620
2009
|
handler: async ({ root }) => {
|
|
1621
|
-
await this.
|
|
1622
|
-
await this.
|
|
2010
|
+
await this.scaffolder.ensureConfig(root, { biomeJson: true });
|
|
2011
|
+
await this.pm.ensureDependency(root, "@biomejs/biome", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1623
2012
|
await this.utils.exec("biome check --formatter-enabled=false --fix");
|
|
1624
2013
|
}
|
|
1625
2014
|
});
|
|
@@ -1657,6 +2046,8 @@ var RootCommand = class {
|
|
|
1657
2046
|
//#region ../../src/cli/commands/test.ts
|
|
1658
2047
|
var TestCommand = class {
|
|
1659
2048
|
utils = $inject(AlephaCliUtils);
|
|
2049
|
+
pm = $inject(PackageManagerUtils);
|
|
2050
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1660
2051
|
test = $command({
|
|
1661
2052
|
name: "test",
|
|
1662
2053
|
description: "Run tests using Vitest",
|
|
@@ -1669,8 +2060,8 @@ var TestCommand = class {
|
|
|
1669
2060
|
description: "Additional arguments to pass to Vitest. E.g., --coverage"
|
|
1670
2061
|
})) }),
|
|
1671
2062
|
handler: async ({ root, flags, env }) => {
|
|
1672
|
-
await this.
|
|
1673
|
-
await this.
|
|
2063
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
2064
|
+
await this.pm.ensureDependency(root, "vitest", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1674
2065
|
const config = flags.config ? `--config=${flags.config}` : "";
|
|
1675
2066
|
await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
|
|
1676
2067
|
}
|
|
@@ -1681,6 +2072,7 @@ var TestCommand = class {
|
|
|
1681
2072
|
//#region ../../src/cli/commands/typecheck.ts
|
|
1682
2073
|
var TypecheckCommand = class {
|
|
1683
2074
|
utils = $inject(AlephaCliUtils);
|
|
2075
|
+
pm = $inject(PackageManagerUtils);
|
|
1684
2076
|
log = $logger();
|
|
1685
2077
|
/**
|
|
1686
2078
|
* Run TypeScript type checking across the codebase with no emit.
|
|
@@ -1691,7 +2083,7 @@ var TypecheckCommand = class {
|
|
|
1691
2083
|
description: "Check TypeScript types across the codebase",
|
|
1692
2084
|
handler: async ({ root }) => {
|
|
1693
2085
|
this.log.info("Starting TypeScript type checking...");
|
|
1694
|
-
await this.
|
|
2086
|
+
await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1695
2087
|
await this.utils.exec("tsc --noEmit");
|
|
1696
2088
|
this.log.info("TypeScript type checking completed successfully.");
|
|
1697
2089
|
}
|
|
@@ -1702,6 +2094,7 @@ var TypecheckCommand = class {
|
|
|
1702
2094
|
//#region ../../src/cli/commands/verify.ts
|
|
1703
2095
|
var VerifyCommand = class {
|
|
1704
2096
|
utils = $inject(AlephaCliUtils);
|
|
2097
|
+
pm = $inject(PackageManagerUtils);
|
|
1705
2098
|
/**
|
|
1706
2099
|
* Run a series of verification commands to ensure code quality and correctness.
|
|
1707
2100
|
*
|
|
@@ -1723,9 +2116,9 @@ var VerifyCommand = class {
|
|
|
1723
2116
|
await run("alepha format");
|
|
1724
2117
|
await run("alepha lint");
|
|
1725
2118
|
await run("alepha typecheck");
|
|
1726
|
-
if ((await this.
|
|
2119
|
+
if ((await this.pm.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
|
|
1727
2120
|
if (await this.utils.exists(root, "migrations")) await run("alepha db check-migrations");
|
|
1728
|
-
if (!await this.
|
|
2121
|
+
if (!await this.pm.hasExpo(root)) await run("alepha build");
|
|
1729
2122
|
await run("alepha clean");
|
|
1730
2123
|
}
|
|
1731
2124
|
});
|
|
@@ -1791,11 +2184,11 @@ var AlephaPackageBuilderCli = class {
|
|
|
1791
2184
|
root: true,
|
|
1792
2185
|
handler: async ({ run, root }) => {
|
|
1793
2186
|
const modules = [];
|
|
1794
|
-
const
|
|
1795
|
-
const pkgData = JSON.parse(
|
|
2187
|
+
const pkgBuffer = await this.fs.readFile("package.json");
|
|
2188
|
+
const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
|
|
1796
2189
|
const packageName = pkgData.name;
|
|
1797
2190
|
await run("analyze modules", async () => {
|
|
1798
|
-
modules.push(...await analyzeModules(join(root, this.src), packageName));
|
|
2191
|
+
modules.push(...await analyzeModules(this.fs.join(root, this.src), packageName));
|
|
1799
2192
|
});
|
|
1800
2193
|
pkgData.exports = {};
|
|
1801
2194
|
for (const item of modules) {
|
|
@@ -1807,6 +2200,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
1807
2200
|
if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
|
|
1808
2201
|
else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
|
|
1809
2202
|
if (item.browser) pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
|
|
2203
|
+
if (item.bun) pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
|
|
1810
2204
|
pkgData.exports[path].import = `./src/${item.name}/index.ts`;
|
|
1811
2205
|
pkgData.exports[path].default = `./src/${item.name}/index.ts`;
|
|
1812
2206
|
}
|
|
@@ -1819,33 +2213,30 @@ var AlephaPackageBuilderCli = class {
|
|
|
1819
2213
|
pkgData.exports["./json/styles"] = "./src/json/styles.css";
|
|
1820
2214
|
}
|
|
1821
2215
|
await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
|
|
1822
|
-
const tmpDir = join(root, "node_modules/.alepha");
|
|
2216
|
+
const tmpDir = this.fs.join(root, "node_modules/.alepha");
|
|
1823
2217
|
await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
|
|
1824
|
-
await this.fs.writeFile(join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
|
|
1825
|
-
const
|
|
1826
|
-
const external = Object.keys(JSON.parse(
|
|
2218
|
+
await this.fs.writeFile(this.fs.join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
|
|
2219
|
+
const tsconfigBuffer = await this.fs.readFile(this.fs.join(root, "../../tsconfig.json"));
|
|
2220
|
+
const external = Object.keys(JSON.parse(tsconfigBuffer.toString("utf-8")).compilerOptions.paths);
|
|
1827
2221
|
external.push("bun");
|
|
1828
2222
|
external.push("bun:sqlite");
|
|
1829
2223
|
await run.rm(this.dist);
|
|
1830
2224
|
const build = async (item) => {
|
|
1831
2225
|
const entries = [];
|
|
1832
|
-
const src = join(root, this.src, item.name);
|
|
1833
|
-
const dest = join(root, this.dist, item.name);
|
|
2226
|
+
const src = this.fs.join(root, this.src, item.name);
|
|
2227
|
+
const dest = this.fs.join(root, this.dist, item.name);
|
|
1834
2228
|
entries.push({
|
|
1835
|
-
entry: join(src, "index.ts"),
|
|
2229
|
+
entry: this.fs.join(src, "index.ts"),
|
|
1836
2230
|
outDir: dest,
|
|
1837
2231
|
format: ["esm"],
|
|
1838
2232
|
sourcemap: true,
|
|
1839
2233
|
fixedExtension: false,
|
|
1840
2234
|
platform: "node",
|
|
1841
2235
|
external,
|
|
1842
|
-
dts: {
|
|
1843
|
-
sourcemap: true,
|
|
1844
|
-
resolve: false
|
|
1845
|
-
}
|
|
2236
|
+
dts: { sourcemap: true }
|
|
1846
2237
|
});
|
|
1847
2238
|
if (item.native) entries.push({
|
|
1848
|
-
entry: join(src, "index.native.ts"),
|
|
2239
|
+
entry: this.fs.join(src, "index.native.ts"),
|
|
1849
2240
|
outDir: dest,
|
|
1850
2241
|
platform: "neutral",
|
|
1851
2242
|
sourcemap: true,
|
|
@@ -1853,14 +2244,23 @@ var AlephaPackageBuilderCli = class {
|
|
|
1853
2244
|
external
|
|
1854
2245
|
});
|
|
1855
2246
|
if (item.browser) entries.push({
|
|
1856
|
-
entry: join(src, "index.browser.ts"),
|
|
2247
|
+
entry: this.fs.join(src, "index.browser.ts"),
|
|
1857
2248
|
outDir: dest,
|
|
1858
2249
|
platform: "browser",
|
|
1859
2250
|
sourcemap: true,
|
|
1860
2251
|
dts: false,
|
|
1861
2252
|
external
|
|
1862
2253
|
});
|
|
1863
|
-
|
|
2254
|
+
if (item.bun) entries.push({
|
|
2255
|
+
entry: this.fs.join(src, "index.bun.ts"),
|
|
2256
|
+
outDir: dest,
|
|
2257
|
+
platform: "node",
|
|
2258
|
+
sourcemap: true,
|
|
2259
|
+
fixedExtension: false,
|
|
2260
|
+
dts: false,
|
|
2261
|
+
external
|
|
2262
|
+
});
|
|
2263
|
+
const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
|
|
1864
2264
|
await this.fs.writeFile(config, `export default ${JSON.stringify(entries, null, 2)};`);
|
|
1865
2265
|
await run(`npx tsdown -c=${config}`);
|
|
1866
2266
|
};
|
|
@@ -1934,7 +2334,7 @@ function detectCircularDependencies(modules) {
|
|
|
1934
2334
|
}
|
|
1935
2335
|
for (const module of modules) {
|
|
1936
2336
|
const cycle = hasCycle(module.name);
|
|
1937
|
-
if (cycle) throw new
|
|
2337
|
+
if (cycle) throw new AlephaError(`Circular dependency detected: ${cycle.join(" -> ")}`);
|
|
1938
2338
|
}
|
|
1939
2339
|
}
|
|
1940
2340
|
async function analyzeModules(srcDir, packageName) {
|
|
@@ -1948,6 +2348,7 @@ async function analyzeModules(srcDir, packageName) {
|
|
|
1948
2348
|
const dependencies = /* @__PURE__ */ new Set();
|
|
1949
2349
|
const hasBrowser = await fileExists(join(modulePath, "index.browser.ts"));
|
|
1950
2350
|
const hasNative = await fileExists(join(modulePath, "index.native.ts"));
|
|
2351
|
+
const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
|
|
1951
2352
|
const hasNode = await fileExists(join(modulePath, "index.node.ts"));
|
|
1952
2353
|
const files = await getAllFiles(modulePath);
|
|
1953
2354
|
for (const file of files) {
|
|
@@ -1964,6 +2365,7 @@ async function analyzeModules(srcDir, packageName) {
|
|
|
1964
2365
|
};
|
|
1965
2366
|
if (hasNative) module.native = true;
|
|
1966
2367
|
if (hasBrowser) module.browser = true;
|
|
2368
|
+
if (hasBun) module.bun = true;
|
|
1967
2369
|
if (hasNode) module.node = true;
|
|
1968
2370
|
modules.push(module);
|
|
1969
2371
|
} else await scanDirectory(modulePath, moduleName);
|