alepha 0.14.3 → 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 +2 -5
- package/dist/api/audits/index.d.ts +620 -811
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts +185 -377
- 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 -435
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.d.ts +238 -429
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +236 -427
- 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 +1010 -1196
- 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 +17 -17
- 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 +384 -285
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1113 -623
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +299 -300
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +13 -9
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +445 -103
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +733 -625
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +446 -103
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +445 -103
- 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/logger/index.js +12 -2
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +197 -197
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.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 +1228 -1216
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2041 -1967
- 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 +110 -110
- 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 +62 -47
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +56 -3
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +6 -0
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +36 -1
- package/dist/server/compress/index.js.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.browser.js +2 -2
- package/dist/server/core/index.browser.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 +294 -125
- 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 +123 -124
- 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/static/index.js +4 -0
- package/dist/server/static/index.js.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 +3 -5
- 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 +5805 -249
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +599 -513
- 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/AlephaCli.ts +0 -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/atoms/buildOptions.ts +88 -0
- package/src/cli/commands/build.ts +70 -87
- package/src/cli/commands/db.ts +21 -22
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +22 -14
- package/src/cli/commands/format.ts +8 -2
- package/src/cli/commands/gen/env.ts +53 -0
- package/src/cli/commands/gen/openapi.ts +1 -1
- package/src/cli/commands/gen/resource.ts +15 -0
- package/src/cli/commands/gen.ts +7 -1
- package/src/cli/commands/init.ts +74 -30
- package/src/cli/commands/lint.ts +8 -2
- package/src/cli/commands/test.ts +8 -3
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +5 -3
- package/src/cli/defineConfig.ts +49 -7
- package/src/cli/index.ts +0 -1
- package/src/cli/services/AlephaCliUtils.ts +39 -589
- package/src/cli/services/PackageManagerUtils.ts +301 -0
- package/src/cli/services/ProjectScaffolder.ts +306 -0
- package/src/command/helpers/Runner.spec.ts +2 -2
- package/src/command/helpers/Runner.ts +16 -4
- package/src/command/primitives/$command.ts +0 -6
- package/src/command/providers/CliProvider.ts +1 -3
- package/src/core/Alepha.ts +42 -0
- 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/logger/index.ts +15 -3
- package/src/mcp/transports/StdioMcpTransport.ts +1 -1
- 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 +13 -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/queue/core/providers/WorkerProvider.spec.ts +48 -32
- 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.spec.ts +183 -0
- package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
- package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/helpers/ServerReply.ts +2 -2
- 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 +155 -22
- 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/static/providers/ServerStaticProvider.ts +10 -0
- package/src/server/swagger/index.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
- 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/helpers/importViteReact.ts +13 -0
- package/src/vite/index.ts +1 -21
- package/src/vite/plugins/viteAlephaDev.ts +16 -1
- package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
- package/src/vite/tasks/buildClient.ts +11 -0
- package/src/vite/tasks/buildServer.ts +59 -4
- package/src/vite/tasks/devServer.ts +71 -0
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/index.ts +2 -1
- 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/cli/assets/viteConfigTs.ts +0 -14
- package/src/cli/commands/run.ts +0 -24
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/plugins/viteAlepha.ts +0 -37
- package/src/vite/plugins/viteAlephaBuild.ts +0 -281
- /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,28 +1,456 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, OPTIONS, t } from "alepha";
|
|
1
|
+
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
|
|
3
2
|
import { FileSystemProvider } from "alepha/file";
|
|
4
|
-
import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
5
|
-
import { join } from "node:path";
|
|
6
3
|
import { $command, CliProvider, EnvUtils } from "alepha/command";
|
|
7
4
|
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
8
|
-
import { boot, buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
|
|
5
|
+
import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
|
|
9
6
|
import { exec, spawn } from "node:child_process";
|
|
10
7
|
import { readFileSync } from "node:fs";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
11
9
|
import { promisify } from "node:util";
|
|
12
10
|
import { ServerSwaggerProvider } from "alepha/server/swagger";
|
|
11
|
+
import { access, readFile, readdir } from "node:fs/promises";
|
|
13
12
|
import * as os from "node:os";
|
|
14
13
|
|
|
15
|
-
//#region ../../src/
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
//#region ../../src/core/constants/KIND.ts
|
|
15
|
+
/**
|
|
16
|
+
* Used for identifying primitives.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
const KIND = Symbol.for("Alepha.Kind");
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../../src/core/primitives/$atom.ts
|
|
24
|
+
/**
|
|
25
|
+
* Define an atom for state management.
|
|
26
|
+
*
|
|
27
|
+
* Atom lets you define a piece of state with a name, schema, and default value.
|
|
28
|
+
*
|
|
29
|
+
* By default, Alepha state is just a simple key-value store.
|
|
30
|
+
* Using atoms allows you to have type safety, validation, and default values for your state.
|
|
31
|
+
*
|
|
32
|
+
* You control how state is structured and validated.
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Set a schema for validation
|
|
36
|
+
* - Set a default value for initial state
|
|
37
|
+
* - Rules, like read-only, custom validation, etc.
|
|
38
|
+
* - Automatic getter access in services with {@link $use}
|
|
39
|
+
* - SSR support (server state automatically serialized and hydrated on client)
|
|
40
|
+
* - React integration (useAtom hook for automatic component re-renders)
|
|
41
|
+
* - Middleware
|
|
42
|
+
* - Persistence adapters (localStorage, Redis, database, file system, cookie, etc.)
|
|
43
|
+
* - State migrations (version upgrades when schema changes)
|
|
44
|
+
* - Documentation generation & devtools integration
|
|
45
|
+
*
|
|
46
|
+
* Common use cases:
|
|
47
|
+
* - user preferences
|
|
48
|
+
* - feature flags
|
|
49
|
+
* - configuration options
|
|
50
|
+
* - session data
|
|
51
|
+
*
|
|
52
|
+
* Atom must contain only serializable data.
|
|
53
|
+
* Avoid storing complex objects like class instances, functions, or DOM elements.
|
|
54
|
+
* If you need to store complex data, consider using identifiers or references instead.
|
|
55
|
+
*/
|
|
56
|
+
const $atom$1 = (options) => {
|
|
57
|
+
return new Atom(options);
|
|
58
|
+
};
|
|
59
|
+
var Atom = class {
|
|
60
|
+
options;
|
|
61
|
+
get schema() {
|
|
62
|
+
return this.options.schema;
|
|
63
|
+
}
|
|
64
|
+
get key() {
|
|
65
|
+
return this.options.name;
|
|
66
|
+
}
|
|
67
|
+
constructor(options) {
|
|
68
|
+
this.options = options;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
$atom$1[KIND] = "atom";
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region ../../src/cli/atoms/buildOptions.ts
|
|
75
|
+
/**
|
|
76
|
+
* Build options atom for CLI build command.
|
|
77
|
+
*
|
|
78
|
+
* Defines the available build configuration options with their defaults.
|
|
79
|
+
* Options can be overridden via vite.config.ts or CLI flags.
|
|
80
|
+
*/
|
|
81
|
+
const buildOptions = $atom$1({
|
|
82
|
+
name: "alepha.build.options",
|
|
83
|
+
description: "Build configuration options",
|
|
84
|
+
schema: t.object({
|
|
85
|
+
stats: t.optional(t.boolean({ default: false })),
|
|
86
|
+
vercel: t.optional(t.object({
|
|
87
|
+
projectName: t.optional(t.string()),
|
|
88
|
+
orgId: t.optional(t.string()),
|
|
89
|
+
projectId: t.optional(t.string()),
|
|
90
|
+
config: t.optional(t.object({ crons: t.optional(t.array(t.object({
|
|
91
|
+
path: t.string(),
|
|
92
|
+
schedule: t.string()
|
|
93
|
+
}))) }))
|
|
94
|
+
})),
|
|
95
|
+
cloudflare: t.optional(t.object({ config: t.optional(t.json()) })),
|
|
96
|
+
docker: t.optional(t.object({
|
|
97
|
+
image: t.optional(t.string({ default: "node:24-alpine" })),
|
|
98
|
+
command: t.optional(t.string({ default: "node" }))
|
|
99
|
+
})),
|
|
100
|
+
sitemap: t.optional(t.object({ hostname: t.string() }))
|
|
101
|
+
}),
|
|
102
|
+
default: {}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
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";
|
|
189
|
+
|
|
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
|
+
}),
|
|
22
435
|
});
|
|
23
436
|
}
|
|
24
437
|
`.trim();
|
|
25
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
|
+
|
|
26
454
|
//#endregion
|
|
27
455
|
//#region ../../src/cli/assets/biomeJson.ts
|
|
28
456
|
const biomeJson = `
|
|
@@ -59,6 +487,299 @@ const biomeJson = `
|
|
|
59
487
|
}
|
|
60
488
|
`.trim();
|
|
61
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
|
+
|
|
62
783
|
//#endregion
|
|
63
784
|
//#region ../../src/cli/assets/dummySpecTs.ts
|
|
64
785
|
const dummySpecTs = () => `
|
|
@@ -105,30 +826,30 @@ const indexHtml = (browserEntry) => `
|
|
|
105
826
|
//#region ../../src/cli/assets/mainBrowserTs.ts
|
|
106
827
|
const mainBrowserTs = () => `
|
|
107
828
|
import { Alepha, run } from "alepha";
|
|
108
|
-
import {
|
|
829
|
+
import { WebModule } from "./web/index.ts";
|
|
109
830
|
|
|
110
831
|
const alepha = Alepha.create();
|
|
111
832
|
|
|
112
|
-
alepha.with(
|
|
833
|
+
alepha.with(WebModule);
|
|
113
834
|
|
|
114
835
|
run(alepha);
|
|
115
836
|
`.trim();
|
|
116
837
|
|
|
117
838
|
//#endregion
|
|
118
|
-
//#region ../../src/cli/assets/
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
handler: () => "Hello, Alepha!",
|
|
127
|
-
});
|
|
128
|
-
}
|
|
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();
|
|
129
847
|
|
|
130
|
-
|
|
848
|
+
alepha.with(ApiModule);
|
|
849
|
+
${react ? `alepha.with(WebModule);\n` : ""}
|
|
850
|
+
run(alepha);
|
|
131
851
|
`.trim();
|
|
852
|
+
};
|
|
132
853
|
|
|
133
854
|
//#endregion
|
|
134
855
|
//#region ../../src/cli/assets/tsconfigJson.ts
|
|
@@ -139,407 +860,158 @@ const tsconfigJson = `
|
|
|
139
860
|
`.trim();
|
|
140
861
|
|
|
141
862
|
//#endregion
|
|
142
|
-
//#region ../../src/cli/assets/
|
|
143
|
-
const
|
|
144
|
-
import {
|
|
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";
|
|
145
868
|
|
|
146
|
-
export
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
);
|
|
153
894
|
};
|
|
895
|
+
|
|
896
|
+
export default Hello;
|
|
154
897
|
`.trim();
|
|
155
898
|
|
|
156
899
|
//#endregion
|
|
157
|
-
//#region ../../src/cli/
|
|
158
|
-
const
|
|
159
|
-
const
|
|
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
|
+
};
|
|
160
913
|
|
|
161
914
|
//#endregion
|
|
162
|
-
//#region ../../src/cli/services/
|
|
163
|
-
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* - Project
|
|
168
|
-
* -
|
|
169
|
-
* -
|
|
170
|
-
* -
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
envUtils = $inject(EnvUtils);
|
|
177
|
-
alepha = $inject(Alepha);
|
|
178
|
-
/**
|
|
179
|
-
* Execute a command using npx with inherited stdio.
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* ```ts
|
|
183
|
-
* const runner = alepha.inject(ProcessRunner);
|
|
184
|
-
* await runner.exec("tsx watch src/index.ts");
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
async exec(command, options = {}) {
|
|
188
|
-
const root = process.cwd();
|
|
189
|
-
this.log.debug(`Executing command: ${command}`, { cwd: root });
|
|
190
|
-
const runExec = async (app$1, args$1) => {
|
|
191
|
-
const prog = spawn(app$1, args$1, {
|
|
192
|
-
stdio: "inherit",
|
|
193
|
-
cwd: root,
|
|
194
|
-
env: {
|
|
195
|
-
...process.env,
|
|
196
|
-
...options.env
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
await new Promise((resolve) => prog.on("exit", () => {
|
|
200
|
-
resolve();
|
|
201
|
-
}));
|
|
202
|
-
};
|
|
203
|
-
if (options.global) {
|
|
204
|
-
const [app$1, ...args$1] = command.split(" ");
|
|
205
|
-
await runExec(app$1, args$1);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
209
|
-
const [app, ...args] = command.split(" ");
|
|
210
|
-
let execPath = await this.checkFileExists(root, `node_modules/.bin/${app}${suffix}`, true);
|
|
211
|
-
if (!execPath) execPath = await this.checkFileExists(root, `node_modules/alepha/node_modules/.bin/${app}${suffix}`, true);
|
|
212
|
-
if (!execPath) throw new AlephaError(`Could not find executable for command '${app}'. Make sure the package is installed.`);
|
|
213
|
-
await runExec(execPath, args);
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Write a configuration file to node_modules/.alepha directory.
|
|
217
|
-
*
|
|
218
|
-
* Creates the .alepha directory if it doesn't exist and writes the file with the given content.
|
|
219
|
-
*
|
|
220
|
-
* @param name - The name of the config file to create
|
|
221
|
-
* @param content - The content to write to the file
|
|
222
|
-
* @param root - The root directory (defaults to process.cwd())
|
|
223
|
-
* @returns The absolute path to the created file
|
|
224
|
-
*
|
|
225
|
-
* @example
|
|
226
|
-
* ```ts
|
|
227
|
-
* const runner = alepha.inject(ProcessRunner);
|
|
228
|
-
* const configPath = await runner.writeConfigFile("biome.json", biomeConfig);
|
|
229
|
-
* ```
|
|
230
|
-
*/
|
|
231
|
-
async writeConfigFile(name, content, root = process.cwd()) {
|
|
232
|
-
const dir = join(root, "node_modules", ".alepha");
|
|
233
|
-
await mkdir(dir, { recursive: true }).catch(() => null);
|
|
234
|
-
const path = join(dir, name);
|
|
235
|
-
await writeFile(path, content);
|
|
236
|
-
this.log.debug(`Config file written: ${path}`);
|
|
237
|
-
return path;
|
|
238
|
-
}
|
|
239
|
-
async removeFiles(root, files) {
|
|
240
|
-
await Promise.all(files.map((file) => this.fs.rm(join(root, file), {
|
|
241
|
-
force: true,
|
|
242
|
-
recursive: true
|
|
243
|
-
})));
|
|
244
|
-
}
|
|
245
|
-
async removeYarn(root) {
|
|
246
|
-
await this.removeFiles(root, [
|
|
247
|
-
".yarn",
|
|
248
|
-
".yarnrc.yml",
|
|
249
|
-
".yarn"
|
|
250
|
-
]);
|
|
251
|
-
}
|
|
252
|
-
async removePnpm(root) {
|
|
253
|
-
await this.removeFiles(root, ["pnpm-lock.yaml", "pnpm-workspace.yaml"]);
|
|
254
|
-
}
|
|
255
|
-
async removeNpm(root) {
|
|
256
|
-
await this.removeFiles(root, ["package-lock.json"]);
|
|
257
|
-
}
|
|
258
|
-
async removeBun(root) {
|
|
259
|
-
await this.removeFiles(root, ["bun.lockb"]);
|
|
260
|
-
}
|
|
261
|
-
async removeAllPmFilesExcept(root, except) {
|
|
262
|
-
if (except !== "yarn") await this.removeYarn(root);
|
|
263
|
-
if (except !== "pnpm") await this.removePnpm(root);
|
|
264
|
-
if (except !== "npm") await this.removeNpm(root);
|
|
265
|
-
if (except !== "bun") await this.removeBun(root);
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Ensure Yarn is configured in the project directory.
|
|
269
|
-
*
|
|
270
|
-
* Creates a .yarnrc.yml file with node-modules linker if it doesn't exist.
|
|
271
|
-
*
|
|
272
|
-
* @param root - The root directory of the project
|
|
273
|
-
*/
|
|
274
|
-
async ensureYarn(root) {
|
|
275
|
-
await this.ensureFileExists(root, ".yarnrc.yml", "nodeLinker: node-modules", false);
|
|
276
|
-
await this.removeAllPmFilesExcept(root, "yarn");
|
|
277
|
-
}
|
|
278
|
-
async ensureBun(root) {
|
|
279
|
-
await this.removeAllPmFilesExcept(root, "bun");
|
|
280
|
-
}
|
|
281
|
-
async ensurePnpm(root) {
|
|
282
|
-
await this.removeAllPmFilesExcept(root, "pnpm");
|
|
283
|
-
}
|
|
284
|
-
async ensureNpm(root) {
|
|
285
|
-
await this.removeAllPmFilesExcept(root, "npm");
|
|
286
|
-
}
|
|
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);
|
|
287
929
|
/**
|
|
288
|
-
*
|
|
930
|
+
* Get the app name from the directory name.
|
|
289
931
|
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
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
|
|
292
936
|
*/
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const devDependencies = {};
|
|
296
|
-
const scripts = {
|
|
297
|
-
dev: "alepha dev",
|
|
298
|
-
build: "alepha build",
|
|
299
|
-
lint: "alepha lint",
|
|
300
|
-
typecheck: "alepha typecheck",
|
|
301
|
-
verify: "alepha verify"
|
|
302
|
-
};
|
|
303
|
-
if (modes.ui) {
|
|
304
|
-
dependencies["@alepha/ui"] = `^${version}`;
|
|
305
|
-
modes.react = true;
|
|
306
|
-
}
|
|
307
|
-
if (modes.react) {
|
|
308
|
-
dependencies["@alepha/react"] = `^${version}`;
|
|
309
|
-
dependencies.react = "^19.2.0";
|
|
310
|
-
dependencies["react-dom"] = "^19.2.0";
|
|
311
|
-
devDependencies["@types/react"] = "^19.2.0";
|
|
312
|
-
}
|
|
313
|
-
return {
|
|
314
|
-
type: "module",
|
|
315
|
-
dependencies,
|
|
316
|
-
devDependencies,
|
|
317
|
-
scripts
|
|
318
|
-
};
|
|
937
|
+
getAppName(root) {
|
|
938
|
+
return basename(root).toLowerCase().replace(/[\s\-_]/g, "") || "app";
|
|
319
939
|
}
|
|
320
940
|
/**
|
|
321
|
-
* Ensure
|
|
322
|
-
*
|
|
323
|
-
* Creates a new package.json if none exists, or updates an existing one to:
|
|
324
|
-
* - Set "type": "module"
|
|
325
|
-
* - Add Alepha dependencies
|
|
326
|
-
* - Add standard scripts
|
|
327
|
-
*
|
|
328
|
-
* @param root - The root directory of the project
|
|
329
|
-
* @param modes - Configuration for which dependencies to include
|
|
941
|
+
* Ensure all configuration files exist.
|
|
330
942
|
*/
|
|
331
|
-
async ensurePackageJson(root, modes) {
|
|
332
|
-
const packageJsonPath = join(root, "package.json");
|
|
333
|
-
try {
|
|
334
|
-
await access(packageJsonPath);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
const obj = this.generatePackageJsonContent(modes);
|
|
337
|
-
await writeFile(packageJsonPath, JSON.stringify(obj, null, 2));
|
|
338
|
-
return obj;
|
|
339
|
-
}
|
|
340
|
-
const content = await readFile(packageJsonPath, "utf8");
|
|
341
|
-
const packageJson$1 = JSON.parse(content);
|
|
342
|
-
const newPackageJson = this.generatePackageJsonContent(modes);
|
|
343
|
-
packageJson$1.type = "module";
|
|
344
|
-
packageJson$1.dependencies ??= {};
|
|
345
|
-
packageJson$1.devDependencies ??= {};
|
|
346
|
-
packageJson$1.scripts ??= {};
|
|
347
|
-
Object.assign(packageJson$1.dependencies, newPackageJson.dependencies);
|
|
348
|
-
Object.assign(packageJson$1.devDependencies, newPackageJson.devDependencies);
|
|
349
|
-
Object.assign(packageJson$1.scripts, newPackageJson.scripts);
|
|
350
|
-
await writeFile(packageJsonPath, JSON.stringify(packageJson$1, null, 2));
|
|
351
|
-
return packageJson$1;
|
|
352
|
-
}
|
|
353
943
|
async ensureConfig(root, opts) {
|
|
354
944
|
const tasks = [];
|
|
355
|
-
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(() => {}));
|
|
356
946
|
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
|
|
357
|
-
if (opts.
|
|
358
|
-
if (opts.indexHtml) tasks.push(this.ensureIndexHtml(root));
|
|
947
|
+
if (opts.indexHtml) tasks.push(this.ensureReactProject(root));
|
|
359
948
|
if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root));
|
|
360
949
|
if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root));
|
|
361
|
-
|
|
950
|
+
if (opts.claudeMd) tasks.push(this.ensureClaudeMd(root, typeof opts.claudeMd === "boolean" ? {} : opts.claudeMd));
|
|
951
|
+
await Promise.all(tasks);
|
|
362
952
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Ensure tsconfig.json exists in the project.
|
|
365
|
-
*
|
|
366
|
-
* Creates a standard Alepha tsconfig.json if none exists.
|
|
367
|
-
*
|
|
368
|
-
* @param root - The root directory of the project
|
|
369
|
-
*/
|
|
370
953
|
async ensureTsConfig(root) {
|
|
371
|
-
await this.
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Ensure vite.config.ts exists in the project.
|
|
375
|
-
*
|
|
376
|
-
* Creates a standard Alepha vite.config.ts if none exists.
|
|
377
|
-
*/
|
|
378
|
-
async ensureViteConfig(root, serverEntry) {
|
|
379
|
-
await this.ensureFileExists(root, "vite.config.ts", viteConfigTs(serverEntry), false);
|
|
380
|
-
}
|
|
381
|
-
async checkFileExists(root, name, checkParentDirectories = false) {
|
|
382
|
-
const configPath = join(root, name);
|
|
383
|
-
if (!checkParentDirectories) try {
|
|
384
|
-
await access(configPath);
|
|
385
|
-
return configPath;
|
|
386
|
-
} catch {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
let currentDir = root;
|
|
390
|
-
const maxIterations = 10;
|
|
391
|
-
let level = 0;
|
|
392
|
-
while (level < maxIterations) {
|
|
393
|
-
try {
|
|
394
|
-
const maybe = join(currentDir, name);
|
|
395
|
-
await access(maybe);
|
|
396
|
-
return maybe;
|
|
397
|
-
} catch {
|
|
398
|
-
const parentDir = join(currentDir, "..");
|
|
399
|
-
if (parentDir === currentDir) break;
|
|
400
|
-
currentDir = parentDir;
|
|
401
|
-
}
|
|
402
|
-
level += 1;
|
|
403
|
-
}
|
|
954
|
+
if (await this.existsInParents(root, "tsconfig.json")) return;
|
|
955
|
+
await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
|
|
404
956
|
}
|
|
405
|
-
async ensureFileExists(root, name, content, checkParentDirectories = false) {
|
|
406
|
-
if (!await this.checkFileExists(root, name, checkParentDirectories)) await writeFile(join(root, name), content);
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Get the path to Biome configuration file.
|
|
410
|
-
*
|
|
411
|
-
* Looks for an existing biome.json in the project root, or creates one if it doesn't exist.
|
|
412
|
-
*/
|
|
413
957
|
async ensureBiomeConfig(root) {
|
|
414
|
-
await this.
|
|
958
|
+
await this.ensureFileIfNotExists(root, "biome.json", biomeJson);
|
|
415
959
|
}
|
|
416
|
-
/**
|
|
417
|
-
* Ensure .editorconfig exists in the project.
|
|
418
|
-
*
|
|
419
|
-
* Creates a standard .editorconfig if none exists.
|
|
420
|
-
*
|
|
421
|
-
* @param root - The root directory of the project
|
|
422
|
-
*/
|
|
423
960
|
async ensureEditorConfig(root) {
|
|
424
|
-
await this.
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Load Alepha instance from a server entry file.
|
|
428
|
-
*
|
|
429
|
-
* Dynamically imports the server entry file and extracts the Alepha instance.
|
|
430
|
-
* Skips the automatic start process.
|
|
431
|
-
*
|
|
432
|
-
* @param rootDir - The root directory of the project
|
|
433
|
-
* @param explicitEntry - Optional explicit path to the entry file
|
|
434
|
-
* @returns Object containing the Alepha instance and the entry file path
|
|
435
|
-
* @throws {AlephaError} If the Alepha instance cannot be found
|
|
436
|
-
*/
|
|
437
|
-
async loadAlephaFromServerEntryFile(rootDir, explicitEntry) {
|
|
438
|
-
process.env.ALEPHA_CLI_IMPORT = "true";
|
|
439
|
-
const entry = await boot.getServerEntry(rootDir, explicitEntry);
|
|
440
|
-
delete global.__alepha;
|
|
441
|
-
const mod = await import(entry);
|
|
442
|
-
this.log.debug(`Load entry: ${entry}`);
|
|
443
|
-
if (mod.default instanceof Alepha) return {
|
|
444
|
-
alepha: mod.default,
|
|
445
|
-
entry
|
|
446
|
-
};
|
|
447
|
-
const g = global;
|
|
448
|
-
if (g.__alepha) return {
|
|
449
|
-
alepha: g.__alepha,
|
|
450
|
-
entry
|
|
451
|
-
};
|
|
452
|
-
throw new AlephaError(`Could not find Alepha instance in entry file: ${entry}`);
|
|
961
|
+
await this.ensureFileIfNotExists(root, ".editorconfig", editorconfig);
|
|
453
962
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
* Creates a temporary entities.js file that imports from the entry file
|
|
458
|
-
* and exports database models for Drizzle Kit to process.
|
|
459
|
-
*
|
|
460
|
-
* @param entry - Path to the server entry file
|
|
461
|
-
* @param provider - Name of the database provider
|
|
462
|
-
* @param models - Array of model names to export
|
|
463
|
-
* @returns JavaScript code as a string
|
|
464
|
-
*/
|
|
465
|
-
generateEntitiesJs(entry, provider, models = []) {
|
|
466
|
-
return `
|
|
467
|
-
import "${entry}";
|
|
468
|
-
import { DrizzleKitProvider, Repository } from "alepha/orm";
|
|
469
|
-
|
|
470
|
-
const alepha = globalThis.__alepha;
|
|
471
|
-
const kit = alepha.inject(DrizzleKitProvider);
|
|
472
|
-
const provider = alepha.services(Repository).find((it) => it.provider.name === "${provider}").provider;
|
|
473
|
-
const models = kit.getModels(provider);
|
|
474
|
-
|
|
475
|
-
${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
476
|
-
|
|
477
|
-
`.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));
|
|
478
966
|
}
|
|
479
967
|
/**
|
|
480
|
-
*
|
|
968
|
+
* Ensure src/main.server.ts exists with full API structure.
|
|
481
969
|
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
970
|
+
* Creates:
|
|
971
|
+
* - src/main.server.ts (entry point)
|
|
972
|
+
* - src/api/index.ts (API module)
|
|
973
|
+
* - src/api/controllers/HelloController.ts (example controller)
|
|
484
974
|
*/
|
|
485
|
-
async
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (await this.checkFileExists(root, "pnpm-lock.yaml", true)) return "pnpm";
|
|
496
|
-
return "npm";
|
|
497
|
-
}
|
|
498
|
-
async ensureIndexHtml(root) {
|
|
499
|
-
if (await this.fs.exists(join(root, "index.html"))) return;
|
|
500
|
-
const serverEntry = "src/main.server.ts";
|
|
501
|
-
const browserEntry = "src/main.browser.ts";
|
|
502
|
-
const appRouter = "src/AppRouter.ts";
|
|
503
|
-
await this.fs.writeFile(join(root, "index.html"), indexHtml(browserEntry));
|
|
504
|
-
try {
|
|
505
|
-
await this.fs.mkdir(join(root, "src"), { recursive: true });
|
|
506
|
-
} catch {}
|
|
507
|
-
if (!await this.fs.exists(join(root, browserEntry))) await this.fs.writeFile(join(root, browserEntry), mainBrowserTs());
|
|
508
|
-
if (!await this.fs.exists(join(root, serverEntry))) await this.fs.writeFile(join(root, serverEntry), mainBrowserTs());
|
|
509
|
-
if (!await this.fs.exists(join(root, appRouter))) await this.fs.writeFile(join(root, appRouter), appRouterTs());
|
|
510
|
-
}
|
|
511
|
-
async exists(root, dirName) {
|
|
512
|
-
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());
|
|
513
985
|
}
|
|
514
986
|
/**
|
|
515
|
-
* Ensure
|
|
987
|
+
* Ensure full React project structure exists.
|
|
516
988
|
*
|
|
517
|
-
* Creates
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
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
|
|
521
994
|
*/
|
|
522
|
-
async
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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());
|
|
531
1008
|
}
|
|
532
1009
|
/**
|
|
533
1010
|
* Ensure test directory exists with a dummy test file.
|
|
534
|
-
*
|
|
535
|
-
* Creates the test directory and a dummy.spec.ts file if the test directory
|
|
536
|
-
* doesn't exist or is empty.
|
|
537
|
-
*
|
|
538
|
-
* @param root - The root directory of the project
|
|
539
1011
|
*/
|
|
540
1012
|
async ensureTestDir(root) {
|
|
541
|
-
const testDir = join(root, "test");
|
|
542
|
-
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");
|
|
543
1015
|
if (!await this.fs.exists(testDir)) {
|
|
544
1016
|
await this.fs.mkdir(testDir, { recursive: true });
|
|
545
1017
|
await this.fs.writeFile(dummyPath, dummySpecTs());
|
|
@@ -547,68 +1019,20 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
|
547
1019
|
}
|
|
548
1020
|
if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
|
|
549
1021
|
}
|
|
550
|
-
async
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Check if a dependency is installed in the project.
|
|
556
|
-
*
|
|
557
|
-
* @param root - The root directory of the project
|
|
558
|
-
* @param packageName - The name of the package to check
|
|
559
|
-
* @returns True if the package is in dependencies or devDependencies
|
|
560
|
-
*/
|
|
561
|
-
async hasDependency(root, packageName) {
|
|
562
|
-
try {
|
|
563
|
-
const pkg = await this.readPackageJson(root);
|
|
564
|
-
return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
|
|
565
|
-
} catch {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Check if Expo is present in the project.
|
|
571
|
-
*
|
|
572
|
-
* @param root - The root directory of the project
|
|
573
|
-
* @returns True if expo is in dependencies or devDependencies
|
|
574
|
-
*/
|
|
575
|
-
async hasExpo(root) {
|
|
576
|
-
return this.hasDependency(root, "expo");
|
|
577
|
-
}
|
|
578
|
-
async getInstallCommand(root, packageName, dev = true) {
|
|
579
|
-
const pm = await this.getPackageManager(root);
|
|
580
|
-
let cmd;
|
|
581
|
-
switch (pm) {
|
|
582
|
-
case "yarn":
|
|
583
|
-
cmd = `yarn add ${dev ? "-D" : ""} ${packageName}`;
|
|
584
|
-
break;
|
|
585
|
-
case "pnpm":
|
|
586
|
-
cmd = `pnpm add ${dev ? "-D" : ""} ${packageName}`;
|
|
587
|
-
break;
|
|
588
|
-
case "bun":
|
|
589
|
-
cmd = `bun add ${dev ? "-d" : ""} ${packageName}`;
|
|
590
|
-
break;
|
|
591
|
-
default: cmd = `npm install ${dev ? "--save-dev" : ""} ${packageName}`;
|
|
592
|
-
}
|
|
593
|
-
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);
|
|
594
1025
|
}
|
|
595
1026
|
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* Automatically detects the package manager (yarn, pnpm, npm) and installs
|
|
599
|
-
* the package as a dev dependency if not already present.
|
|
1027
|
+
* Check if a file exists in the given directory or any parent directory.
|
|
600
1028
|
*/
|
|
601
|
-
async
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (options.run) await options.run(cmd, { alias: `installing ${packageName}` });
|
|
609
|
-
else {
|
|
610
|
-
this.log.debug(`Installing ${packageName}`);
|
|
611
|
-
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;
|
|
612
1036
|
}
|
|
613
1037
|
}
|
|
614
1038
|
};
|
|
@@ -617,9 +1041,14 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
|
617
1041
|
//#region ../../src/cli/commands/build.ts
|
|
618
1042
|
var BuildCommand = class {
|
|
619
1043
|
log = $logger();
|
|
1044
|
+
fs = $inject(FileSystemProvider);
|
|
620
1045
|
utils = $inject(AlephaCliUtils);
|
|
1046
|
+
pm = $inject(PackageManagerUtils);
|
|
1047
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1048
|
+
options = $use(buildOptions);
|
|
621
1049
|
build = $command({
|
|
622
1050
|
name: "build",
|
|
1051
|
+
mode: "production",
|
|
623
1052
|
description: "Build the project for production",
|
|
624
1053
|
args: t.optional(t.text({
|
|
625
1054
|
title: "path",
|
|
@@ -630,57 +1059,53 @@ var BuildCommand = class {
|
|
|
630
1059
|
vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
|
|
631
1060
|
cloudflare: t.optional(t.boolean({ description: "Generate Cloudflare Workers configuration" })),
|
|
632
1061
|
docker: t.optional(t.boolean({ description: "Generate Docker configuration" })),
|
|
633
|
-
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" }))
|
|
634
1064
|
}),
|
|
635
1065
|
handler: async ({ flags, args, run, root }) => {
|
|
636
1066
|
process.env.ALEPHA_BUILD_MODE = "cli";
|
|
637
1067
|
process.env.NODE_ENV = "production";
|
|
638
|
-
if (await this.
|
|
639
|
-
await this.
|
|
640
|
-
viteConfigTs: true,
|
|
641
|
-
tsconfigJson: true
|
|
642
|
-
});
|
|
1068
|
+
if (await this.pm.hasExpo(root)) return;
|
|
1069
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
643
1070
|
const entry = await boot.getServerEntry(root, args);
|
|
644
1071
|
this.log.trace("Entry file found", { entry });
|
|
645
1072
|
const distDir = "dist";
|
|
646
1073
|
const clientDir = "public";
|
|
647
|
-
await this.
|
|
1074
|
+
await this.pm.ensureDependency(root, "vite", {
|
|
1075
|
+
run,
|
|
1076
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1077
|
+
});
|
|
648
1078
|
await run.rm("dist", { alias: "clean dist" });
|
|
649
|
-
const
|
|
1079
|
+
const options = this.options;
|
|
650
1080
|
await this.utils.loadEnv(root, [".env", ".env.production"]);
|
|
651
|
-
const stats = flags.stats ??
|
|
652
|
-
const
|
|
653
|
-
let hasClient = false;
|
|
654
|
-
try {
|
|
655
|
-
await access(join(root, "index.html"));
|
|
656
|
-
hasClient = true;
|
|
657
|
-
} catch {}
|
|
658
|
-
const clientOptions = typeof viteAlephaBuildOptions.client === "object" ? viteAlephaBuildOptions.client : {};
|
|
1081
|
+
const stats = flags.stats ?? options.stats ?? false;
|
|
1082
|
+
const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
|
|
659
1083
|
if (hasClient) await run({
|
|
660
1084
|
name: "vite build client",
|
|
661
1085
|
handler: () => buildClient({
|
|
662
1086
|
silent: true,
|
|
663
1087
|
dist: `${distDir}/${clientDir}`,
|
|
664
1088
|
stats,
|
|
665
|
-
precompress:
|
|
1089
|
+
precompress: true
|
|
666
1090
|
})
|
|
667
1091
|
});
|
|
668
1092
|
await run({
|
|
669
1093
|
name: "vite build server",
|
|
670
1094
|
handler: async () => {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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");
|
|
676
1100
|
await buildServer({
|
|
677
1101
|
silent: true,
|
|
678
1102
|
entry,
|
|
679
1103
|
distDir,
|
|
680
1104
|
clientDir: clientBuilt ? clientDir : void 0,
|
|
681
|
-
stats
|
|
1105
|
+
stats,
|
|
1106
|
+
conditions
|
|
682
1107
|
});
|
|
683
|
-
if (clientBuilt
|
|
1108
|
+
if (clientBuilt) await this.fs.rm(clientIndexPath);
|
|
684
1109
|
}
|
|
685
1110
|
});
|
|
686
1111
|
await copyAssets({
|
|
@@ -690,58 +1115,49 @@ var BuildCommand = class {
|
|
|
690
1115
|
run
|
|
691
1116
|
});
|
|
692
1117
|
if (hasClient) {
|
|
693
|
-
const
|
|
694
|
-
if (
|
|
1118
|
+
const sitemapHostname = flags.sitemap ?? options.sitemap?.hostname;
|
|
1119
|
+
if (sitemapHostname) await run({
|
|
695
1120
|
name: "add sitemap",
|
|
696
1121
|
handler: async () => {
|
|
697
|
-
await writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
|
|
1122
|
+
await this.fs.writeFile(`${distDir}/${clientDir}/sitemap.xml`, await generateSitemap({
|
|
698
1123
|
entry: `${distDir}/index.js`,
|
|
699
|
-
baseUrl:
|
|
1124
|
+
baseUrl: sitemapHostname
|
|
700
1125
|
}));
|
|
701
1126
|
}
|
|
702
1127
|
});
|
|
703
|
-
|
|
1128
|
+
await run({
|
|
704
1129
|
name: "pre-render pages",
|
|
705
1130
|
handler: async () => {
|
|
706
1131
|
await prerenderPages({
|
|
707
1132
|
dist: `${distDir}/${clientDir}`,
|
|
708
1133
|
entry: `${distDir}/index.js`,
|
|
709
|
-
compress:
|
|
1134
|
+
compress: true
|
|
710
1135
|
});
|
|
711
1136
|
}
|
|
712
1137
|
});
|
|
713
1138
|
}
|
|
714
|
-
if (flags.vercel ||
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const dockerConfig = typeof viteAlephaBuildOptions.docker === "object" ? viteAlephaBuildOptions.docker : {};
|
|
737
|
-
await run({
|
|
738
|
-
name: "add Docker config",
|
|
739
|
-
handler: () => generateDocker({
|
|
740
|
-
distDir,
|
|
741
|
-
...dockerConfig
|
|
742
|
-
})
|
|
743
|
-
});
|
|
744
|
-
}
|
|
1139
|
+
if (flags.vercel || options.vercel) await run({
|
|
1140
|
+
name: "add Vercel config",
|
|
1141
|
+
handler: () => generateVercel({
|
|
1142
|
+
distDir,
|
|
1143
|
+
clientDir,
|
|
1144
|
+
config: options.vercel
|
|
1145
|
+
})
|
|
1146
|
+
});
|
|
1147
|
+
if (flags.cloudflare || options.cloudflare) await run({
|
|
1148
|
+
name: "add Cloudflare config",
|
|
1149
|
+
handler: () => generateCloudflare({
|
|
1150
|
+
distDir,
|
|
1151
|
+
config: options.cloudflare?.config
|
|
1152
|
+
})
|
|
1153
|
+
});
|
|
1154
|
+
if (flags.docker || options.docker) await run({
|
|
1155
|
+
name: "add Docker config",
|
|
1156
|
+
handler: () => generateDocker({
|
|
1157
|
+
distDir,
|
|
1158
|
+
...options.docker
|
|
1159
|
+
})
|
|
1160
|
+
});
|
|
745
1161
|
}
|
|
746
1162
|
});
|
|
747
1163
|
};
|
|
@@ -769,6 +1185,7 @@ const drizzleCommandFlags = t.object({
|
|
|
769
1185
|
});
|
|
770
1186
|
var DbCommand = class {
|
|
771
1187
|
log = $logger();
|
|
1188
|
+
fs = $inject(FileSystemProvider);
|
|
772
1189
|
utils = $inject(AlephaCliUtils);
|
|
773
1190
|
/**
|
|
774
1191
|
* Check if database migrations are up to date.
|
|
@@ -793,15 +1210,16 @@ var DbCommand = class {
|
|
|
793
1210
|
const providerName = provider.name;
|
|
794
1211
|
if (accepted.has(providerName)) continue;
|
|
795
1212
|
accepted.add(providerName);
|
|
796
|
-
const migrationDir = join(rootDir, "migrations", providerName);
|
|
797
|
-
const
|
|
798
|
-
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) {
|
|
799
1216
|
this.log.info("No migration journal found.");
|
|
800
1217
|
return;
|
|
801
1218
|
}
|
|
802
|
-
const journal = JSON.parse(
|
|
1219
|
+
const journal = JSON.parse(journalBuffer.toString("utf-8"));
|
|
803
1220
|
const lastMigration = journal.entries[journal.entries.length - 1];
|
|
804
|
-
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"));
|
|
805
1223
|
const models = drizzleKitProvider.getModels(provider);
|
|
806
1224
|
const kit = drizzleKitProvider.importDrizzleKit();
|
|
807
1225
|
const now = kit.generateDrizzleJson(models, lastSnapshot.id);
|
|
@@ -837,7 +1255,6 @@ var DbCommand = class {
|
|
|
837
1255
|
generate = $command({
|
|
838
1256
|
name: "generate",
|
|
839
1257
|
description: "Generate migration files based on current database schema",
|
|
840
|
-
summary: false,
|
|
841
1258
|
args: t.optional(t.text({
|
|
842
1259
|
title: "path",
|
|
843
1260
|
description: "Path to the Alepha server entry file"
|
|
@@ -862,7 +1279,6 @@ var DbCommand = class {
|
|
|
862
1279
|
push = $command({
|
|
863
1280
|
name: "push",
|
|
864
1281
|
description: "Push database schema changes directly to the database",
|
|
865
|
-
summary: false,
|
|
866
1282
|
args: t.optional(t.text({
|
|
867
1283
|
title: "path",
|
|
868
1284
|
description: "Path to the Alepha server entry file"
|
|
@@ -885,7 +1301,6 @@ var DbCommand = class {
|
|
|
885
1301
|
migrate = $command({
|
|
886
1302
|
name: "migrate",
|
|
887
1303
|
description: "Apply pending database migrations",
|
|
888
|
-
summary: false,
|
|
889
1304
|
args: t.optional(t.text({
|
|
890
1305
|
title: "path",
|
|
891
1306
|
description: "Path to the Alepha server entry file"
|
|
@@ -908,7 +1323,6 @@ var DbCommand = class {
|
|
|
908
1323
|
studio = $command({
|
|
909
1324
|
name: "studio",
|
|
910
1325
|
description: "Launch Drizzle Studio database browser",
|
|
911
|
-
summary: false,
|
|
912
1326
|
args: t.optional(t.text({
|
|
913
1327
|
title: "path",
|
|
914
1328
|
description: "Path to the Alepha server entry file"
|
|
@@ -959,6 +1373,7 @@ var DbCommand = class {
|
|
|
959
1373
|
const provider = primitive.provider;
|
|
960
1374
|
const providerName = provider.name;
|
|
961
1375
|
const dialect = provider.dialect;
|
|
1376
|
+
if (providerName === "") continue;
|
|
962
1377
|
if (accepted.has(providerName)) continue;
|
|
963
1378
|
accepted.add(providerName);
|
|
964
1379
|
if (options.provider && options.provider !== providerName) {
|
|
@@ -972,6 +1387,7 @@ var DbCommand = class {
|
|
|
972
1387
|
provider,
|
|
973
1388
|
providerName,
|
|
974
1389
|
providerUrl: provider.url,
|
|
1390
|
+
providerDriver: provider.driver,
|
|
975
1391
|
dialect,
|
|
976
1392
|
entry,
|
|
977
1393
|
rootDir
|
|
@@ -993,9 +1409,9 @@ var DbCommand = class {
|
|
|
993
1409
|
dbCredentials: { url: options.providerUrl }
|
|
994
1410
|
};
|
|
995
1411
|
if (options.provider.schema) config.schemaFilter = options.provider.schema;
|
|
996
|
-
if (options.
|
|
997
|
-
if (options.
|
|
998
|
-
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") {
|
|
999
1415
|
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
1000
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");
|
|
1001
1417
|
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
@@ -1012,7 +1428,7 @@ var DbCommand = class {
|
|
|
1012
1428
|
} else {
|
|
1013
1429
|
let url = options.providerUrl;
|
|
1014
1430
|
url = url.replace("sqlite://", "").replace("file://", "");
|
|
1015
|
-
url = join(options.rootDir, url);
|
|
1431
|
+
url = this.fs.join(options.rootDir, url);
|
|
1016
1432
|
config.dbCredentials = { url };
|
|
1017
1433
|
}
|
|
1018
1434
|
const drizzleConfigJs = `export default ${JSON.stringify(config, null, 2)}`;
|
|
@@ -1024,7 +1440,9 @@ var DbCommand = class {
|
|
|
1024
1440
|
//#region ../../src/cli/commands/deploy.ts
|
|
1025
1441
|
var DeployCommand = class {
|
|
1026
1442
|
log = $logger();
|
|
1443
|
+
fs = $inject(FileSystemProvider);
|
|
1027
1444
|
utils = $inject(AlephaCliUtils);
|
|
1445
|
+
pm = $inject(PackageManagerUtils);
|
|
1028
1446
|
/**
|
|
1029
1447
|
* Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
|
|
1030
1448
|
*
|
|
@@ -1072,7 +1490,10 @@ var DeployCommand = class {
|
|
|
1072
1490
|
this.log.debug("Running database migrations before deployment...");
|
|
1073
1491
|
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
1074
1492
|
}
|
|
1075
|
-
await this.
|
|
1493
|
+
await this.pm.ensureDependency(root, "vercel", {
|
|
1494
|
+
dev: true,
|
|
1495
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1496
|
+
});
|
|
1076
1497
|
const command = `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
|
|
1077
1498
|
this.log.debug(`Deploying to Vercel with command: ${command}`);
|
|
1078
1499
|
await this.utils.exec(command);
|
|
@@ -1083,15 +1504,21 @@ var DeployCommand = class {
|
|
|
1083
1504
|
this.log.debug("Running database migrations before deployment...");
|
|
1084
1505
|
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
1085
1506
|
}
|
|
1086
|
-
await this.
|
|
1507
|
+
await this.pm.ensureDependency(root, "wrangler", {
|
|
1508
|
+
dev: true,
|
|
1509
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1510
|
+
});
|
|
1087
1511
|
const command = `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
|
|
1088
1512
|
this.log.info(`Deploying to Cloudflare with command: ${command}`);
|
|
1089
1513
|
await this.utils.exec(command);
|
|
1090
1514
|
return;
|
|
1091
1515
|
}
|
|
1092
1516
|
if (await this.utils.exists(root, "dist/public/404.html")) {
|
|
1093
|
-
await this.
|
|
1094
|
-
|
|
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");
|
|
1095
1522
|
this.log.debug(`Deploying to Surge from directory: ${distPath}`);
|
|
1096
1523
|
await this.utils.exec(`surge ${distPath}`);
|
|
1097
1524
|
return;
|
|
@@ -1105,7 +1532,10 @@ var DeployCommand = class {
|
|
|
1105
1532
|
//#region ../../src/cli/commands/dev.ts
|
|
1106
1533
|
var DevCommand = class {
|
|
1107
1534
|
log = $logger();
|
|
1535
|
+
fs = $inject(FileSystemProvider);
|
|
1108
1536
|
utils = $inject(AlephaCliUtils);
|
|
1537
|
+
pm = $inject(PackageManagerUtils);
|
|
1538
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1109
1539
|
alepha = $inject(Alepha);
|
|
1110
1540
|
/**
|
|
1111
1541
|
* Will run the project in watch mode.
|
|
@@ -1121,11 +1551,8 @@ var DevCommand = class {
|
|
|
1121
1551
|
description: "Filepath to run"
|
|
1122
1552
|
})),
|
|
1123
1553
|
handler: async ({ args, root }) => {
|
|
1124
|
-
const expo = await this.
|
|
1125
|
-
await this.
|
|
1126
|
-
viteConfigTs: !expo,
|
|
1127
|
-
tsconfigJson: true
|
|
1128
|
-
});
|
|
1554
|
+
const expo = await this.pm.hasExpo(root);
|
|
1555
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
1129
1556
|
if (expo) {
|
|
1130
1557
|
await this.utils.exec("expo start");
|
|
1131
1558
|
return;
|
|
@@ -1133,24 +1560,23 @@ var DevCommand = class {
|
|
|
1133
1560
|
const entry = await boot.getServerEntry(root, args);
|
|
1134
1561
|
this.log.trace("Entry file found", { entry });
|
|
1135
1562
|
if (!await this.isFullstackProject(root)) {
|
|
1136
|
-
const exe = this.
|
|
1563
|
+
const exe = await this.isBunProject(root) ? "bun" : "tsx";
|
|
1137
1564
|
let cmd = `${exe} --watch`;
|
|
1138
1565
|
if (await this.utils.exists(root, ".env")) cmd += " --env-file=./.env";
|
|
1139
1566
|
cmd += ` ${entry}`;
|
|
1140
1567
|
await this.utils.exec(cmd, { global: exe === "bun" });
|
|
1141
1568
|
return;
|
|
1142
1569
|
}
|
|
1143
|
-
await this.
|
|
1144
|
-
await
|
|
1570
|
+
await this.pm.ensureDependency(root, "vite", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1571
|
+
await devServer();
|
|
1145
1572
|
}
|
|
1146
1573
|
});
|
|
1574
|
+
async isBunProject(root) {
|
|
1575
|
+
if (this.alepha.isBun()) return true;
|
|
1576
|
+
return this.fs.exists(this.fs.join(root, "bun.lock"));
|
|
1577
|
+
}
|
|
1147
1578
|
async isFullstackProject(root) {
|
|
1148
|
-
|
|
1149
|
-
await access(join(root, "index.html"));
|
|
1150
|
-
return true;
|
|
1151
|
-
} catch {
|
|
1152
|
-
return false;
|
|
1153
|
-
}
|
|
1579
|
+
return this.fs.exists(this.fs.join(root, "index.html"));
|
|
1154
1580
|
}
|
|
1155
1581
|
};
|
|
1156
1582
|
|
|
@@ -1158,12 +1584,14 @@ var DevCommand = class {
|
|
|
1158
1584
|
//#region ../../src/cli/commands/format.ts
|
|
1159
1585
|
var FormatCommand = class {
|
|
1160
1586
|
utils = $inject(AlephaCliUtils);
|
|
1587
|
+
pm = $inject(PackageManagerUtils);
|
|
1588
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1161
1589
|
format = $command({
|
|
1162
1590
|
name: "format",
|
|
1163
1591
|
description: "Format the codebase using Biome",
|
|
1164
1592
|
handler: async ({ root }) => {
|
|
1165
|
-
await this.
|
|
1166
|
-
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) });
|
|
1167
1595
|
await this.utils.exec("biome format --fix");
|
|
1168
1596
|
}
|
|
1169
1597
|
});
|
|
@@ -1385,6 +1813,39 @@ var ChangelogCommand = class {
|
|
|
1385
1813
|
});
|
|
1386
1814
|
};
|
|
1387
1815
|
|
|
1816
|
+
//#endregion
|
|
1817
|
+
//#region ../../src/cli/commands/gen/env.ts
|
|
1818
|
+
var GenEnvCommand = class {
|
|
1819
|
+
log = $logger();
|
|
1820
|
+
utils = $inject(AlephaCliUtils);
|
|
1821
|
+
fs = $inject(FileSystemProvider);
|
|
1822
|
+
command = $command({
|
|
1823
|
+
name: "env",
|
|
1824
|
+
description: "Extract environment variables from server entry file",
|
|
1825
|
+
flags: t.object({ out: t.optional(t.text({
|
|
1826
|
+
aliases: ["o"],
|
|
1827
|
+
description: "Output file path (e.g., .env)"
|
|
1828
|
+
})) }),
|
|
1829
|
+
handler: async ({ root, flags }) => {
|
|
1830
|
+
const { alepha } = await this.utils.loadAlephaFromServerEntryFile(root);
|
|
1831
|
+
try {
|
|
1832
|
+
const { env } = alepha.dump();
|
|
1833
|
+
let dotEnvFile = "";
|
|
1834
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1835
|
+
if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
|
|
1836
|
+
if (value.required && !value.default) dotEnvFile += `# (required)\n`;
|
|
1837
|
+
if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
|
|
1838
|
+
dotEnvFile += `${key}=${value.default || ""}\n\n`;
|
|
1839
|
+
}
|
|
1840
|
+
if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
|
|
1841
|
+
else this.log.info(dotEnvFile);
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
this.log.error("Failed to extract environment variables", err);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
};
|
|
1848
|
+
|
|
1388
1849
|
//#endregion
|
|
1389
1850
|
//#region ../../src/cli/commands/gen/openapi.ts
|
|
1390
1851
|
var OpenApiCommand = class {
|
|
@@ -1420,7 +1881,7 @@ var OpenApiCommand = class {
|
|
|
1420
1881
|
this.log.error("Missing $swagger() primitive in your server configuration.");
|
|
1421
1882
|
return;
|
|
1422
1883
|
}
|
|
1423
|
-
this.log.error(`OpenAPI generation failed - ${message}`,
|
|
1884
|
+
this.log.error(`OpenAPI generation failed - ${message}`, err);
|
|
1424
1885
|
}
|
|
1425
1886
|
}
|
|
1426
1887
|
});
|
|
@@ -1431,10 +1892,15 @@ var OpenApiCommand = class {
|
|
|
1431
1892
|
var GenCommand = class {
|
|
1432
1893
|
changelog = $inject(ChangelogCommand);
|
|
1433
1894
|
openapi = $inject(OpenApiCommand);
|
|
1895
|
+
genEnv = $inject(GenEnvCommand);
|
|
1434
1896
|
gen = $command({
|
|
1435
1897
|
name: "gen",
|
|
1436
1898
|
description: "Generate code, documentation, ...",
|
|
1437
|
-
children: [
|
|
1899
|
+
children: [
|
|
1900
|
+
this.changelog.command,
|
|
1901
|
+
this.openapi.command,
|
|
1902
|
+
this.genEnv.command
|
|
1903
|
+
],
|
|
1438
1904
|
handler: async ({ help }) => {
|
|
1439
1905
|
help();
|
|
1440
1906
|
}
|
|
@@ -1445,6 +1911,9 @@ var GenCommand = class {
|
|
|
1445
1911
|
//#region ../../src/cli/commands/init.ts
|
|
1446
1912
|
var InitCommand = class {
|
|
1447
1913
|
utils = $inject(AlephaCliUtils);
|
|
1914
|
+
pm = $inject(PackageManagerUtils);
|
|
1915
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1916
|
+
fs = $inject(FileSystemProvider);
|
|
1448
1917
|
/**
|
|
1449
1918
|
* Ensure the project has the necessary Alepha configuration files.
|
|
1450
1919
|
* Add the correct dependencies to package.json and install them.
|
|
@@ -1452,46 +1921,78 @@ var InitCommand = class {
|
|
|
1452
1921
|
init = $command({
|
|
1453
1922
|
name: "init",
|
|
1454
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
|
+
})),
|
|
1455
1929
|
flags: t.object({
|
|
1930
|
+
agent: t.optional(t.boolean({
|
|
1931
|
+
aliases: ["a"],
|
|
1932
|
+
description: "Add CLAUDE.md for Claude Code AI assistant"
|
|
1933
|
+
})),
|
|
1456
1934
|
yarn: t.optional(t.boolean({ description: "Use Yarn package manager" })),
|
|
1457
1935
|
pnpm: t.optional(t.boolean({ description: "Use pnpm package manager" })),
|
|
1458
1936
|
npm: t.optional(t.boolean({ description: "Use npm package manager" })),
|
|
1459
1937
|
bun: t.optional(t.boolean({ description: "Use Bun package manager" })),
|
|
1460
|
-
|
|
1461
|
-
|
|
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" })),
|
|
1462
1943
|
test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
|
|
1463
1944
|
}),
|
|
1464
|
-
handler: async ({ run, flags, root }) => {
|
|
1465
|
-
if (flags.
|
|
1466
|
-
|
|
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);
|
|
1467
1952
|
await run({
|
|
1468
1953
|
name: "ensuring configuration files",
|
|
1469
1954
|
handler: async () => {
|
|
1470
|
-
await this.
|
|
1955
|
+
await this.scaffolder.ensureConfig(root, {
|
|
1471
1956
|
tsconfigJson: true,
|
|
1472
1957
|
packageJson: flags,
|
|
1473
1958
|
biomeJson: true,
|
|
1474
|
-
viteConfigTs: !isExpo,
|
|
1475
1959
|
editorconfig: true,
|
|
1476
|
-
indexHtml: !!flags.
|
|
1960
|
+
indexHtml: !!flags.web && !isExpo,
|
|
1961
|
+
claudeMd: flags.agent ? {
|
|
1962
|
+
react: !!flags.web,
|
|
1963
|
+
ui: !!flags.admin
|
|
1964
|
+
} : false
|
|
1477
1965
|
});
|
|
1478
|
-
if (!flags.
|
|
1966
|
+
if (!flags.web) await this.scaffolder.ensureApiProject(root);
|
|
1479
1967
|
}
|
|
1480
1968
|
});
|
|
1481
|
-
const
|
|
1482
|
-
if (
|
|
1483
|
-
await this.
|
|
1484
|
-
await run("yarn set version stable");
|
|
1485
|
-
} else if (
|
|
1486
|
-
else if (
|
|
1487
|
-
else await this.
|
|
1488
|
-
await run(`${
|
|
1489
|
-
|
|
1490
|
-
|
|
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
|
+
});
|
|
1491
1988
|
if (flags.test) {
|
|
1492
|
-
await this.
|
|
1493
|
-
await run(`${
|
|
1989
|
+
await this.scaffolder.ensureTestDir(root);
|
|
1990
|
+
await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
|
|
1494
1991
|
}
|
|
1992
|
+
await run(`${pmName} run lint`, {
|
|
1993
|
+
alias: "running linter",
|
|
1994
|
+
root
|
|
1995
|
+
});
|
|
1495
1996
|
}
|
|
1496
1997
|
});
|
|
1497
1998
|
};
|
|
@@ -1500,12 +2001,14 @@ var InitCommand = class {
|
|
|
1500
2001
|
//#region ../../src/cli/commands/lint.ts
|
|
1501
2002
|
var LintCommand = class {
|
|
1502
2003
|
utils = $inject(AlephaCliUtils);
|
|
2004
|
+
pm = $inject(PackageManagerUtils);
|
|
2005
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1503
2006
|
lint = $command({
|
|
1504
2007
|
name: "lint",
|
|
1505
2008
|
description: "Run linter across the codebase using Biome",
|
|
1506
2009
|
handler: async ({ root }) => {
|
|
1507
|
-
await this.
|
|
1508
|
-
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) });
|
|
1509
2012
|
await this.utils.exec("biome check --formatter-enabled=false --fix");
|
|
1510
2013
|
}
|
|
1511
2014
|
});
|
|
@@ -1539,34 +2042,12 @@ var RootCommand = class {
|
|
|
1539
2042
|
});
|
|
1540
2043
|
};
|
|
1541
2044
|
|
|
1542
|
-
//#endregion
|
|
1543
|
-
//#region ../../src/cli/commands/run.ts
|
|
1544
|
-
var RunCommand = class {
|
|
1545
|
-
utils = $inject(AlephaCliUtils);
|
|
1546
|
-
run = $command({
|
|
1547
|
-
name: "run",
|
|
1548
|
-
hide: true,
|
|
1549
|
-
description: "Run a TypeScript file directly",
|
|
1550
|
-
flags: t.object({ watch: t.optional(t.boolean({
|
|
1551
|
-
description: "Watch file for changes",
|
|
1552
|
-
alias: "w"
|
|
1553
|
-
})) }),
|
|
1554
|
-
summary: false,
|
|
1555
|
-
args: t.text({
|
|
1556
|
-
title: "path",
|
|
1557
|
-
description: "Filepath to run"
|
|
1558
|
-
}),
|
|
1559
|
-
handler: async ({ args, flags, root }) => {
|
|
1560
|
-
await this.utils.ensureTsConfig(root);
|
|
1561
|
-
await this.utils.exec(`tsx ${flags.watch ? "watch " : ""}${args}`);
|
|
1562
|
-
}
|
|
1563
|
-
});
|
|
1564
|
-
};
|
|
1565
|
-
|
|
1566
2045
|
//#endregion
|
|
1567
2046
|
//#region ../../src/cli/commands/test.ts
|
|
1568
2047
|
var TestCommand = class {
|
|
1569
2048
|
utils = $inject(AlephaCliUtils);
|
|
2049
|
+
pm = $inject(PackageManagerUtils);
|
|
2050
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
1570
2051
|
test = $command({
|
|
1571
2052
|
name: "test",
|
|
1572
2053
|
description: "Run tests using Vitest",
|
|
@@ -1579,11 +2060,8 @@ var TestCommand = class {
|
|
|
1579
2060
|
description: "Additional arguments to pass to Vitest. E.g., --coverage"
|
|
1580
2061
|
})) }),
|
|
1581
2062
|
handler: async ({ root, flags, env }) => {
|
|
1582
|
-
await this.
|
|
1583
|
-
|
|
1584
|
-
viteConfigTs: true
|
|
1585
|
-
});
|
|
1586
|
-
await this.utils.ensureDependency(root, "vitest");
|
|
2063
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
2064
|
+
await this.pm.ensureDependency(root, "vitest", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1587
2065
|
const config = flags.config ? `--config=${flags.config}` : "";
|
|
1588
2066
|
await this.utils.exec(`vitest run ${config} ${env.VITEST_ARGS}`);
|
|
1589
2067
|
}
|
|
@@ -1594,6 +2072,7 @@ var TestCommand = class {
|
|
|
1594
2072
|
//#region ../../src/cli/commands/typecheck.ts
|
|
1595
2073
|
var TypecheckCommand = class {
|
|
1596
2074
|
utils = $inject(AlephaCliUtils);
|
|
2075
|
+
pm = $inject(PackageManagerUtils);
|
|
1597
2076
|
log = $logger();
|
|
1598
2077
|
/**
|
|
1599
2078
|
* Run TypeScript type checking across the codebase with no emit.
|
|
@@ -1604,7 +2083,7 @@ var TypecheckCommand = class {
|
|
|
1604
2083
|
description: "Check TypeScript types across the codebase",
|
|
1605
2084
|
handler: async ({ root }) => {
|
|
1606
2085
|
this.log.info("Starting TypeScript type checking...");
|
|
1607
|
-
await this.
|
|
2086
|
+
await this.pm.ensureDependency(root, "typescript", { exec: (cmd, opts) => this.utils.exec(cmd, opts) });
|
|
1608
2087
|
await this.utils.exec("tsc --noEmit");
|
|
1609
2088
|
this.log.info("TypeScript type checking completed successfully.");
|
|
1610
2089
|
}
|
|
@@ -1615,6 +2094,7 @@ var TypecheckCommand = class {
|
|
|
1615
2094
|
//#region ../../src/cli/commands/verify.ts
|
|
1616
2095
|
var VerifyCommand = class {
|
|
1617
2096
|
utils = $inject(AlephaCliUtils);
|
|
2097
|
+
pm = $inject(PackageManagerUtils);
|
|
1618
2098
|
/**
|
|
1619
2099
|
* Run a series of verification commands to ensure code quality and correctness.
|
|
1620
2100
|
*
|
|
@@ -1636,9 +2116,9 @@ var VerifyCommand = class {
|
|
|
1636
2116
|
await run("alepha format");
|
|
1637
2117
|
await run("alepha lint");
|
|
1638
2118
|
await run("alepha typecheck");
|
|
1639
|
-
if ((await this.
|
|
1640
|
-
if (await this.utils.exists(root, "migrations")) await run("alepha db
|
|
1641
|
-
if (!await this.
|
|
2119
|
+
if ((await this.pm.readPackageJson(root)).devDependencies?.vitest) await run("alepha test");
|
|
2120
|
+
if (await this.utils.exists(root, "migrations")) await run("alepha db check-migrations");
|
|
2121
|
+
if (!await this.pm.hasExpo(root)) await run("alepha build");
|
|
1642
2122
|
await run("alepha clean");
|
|
1643
2123
|
}
|
|
1644
2124
|
});
|
|
@@ -1686,7 +2166,6 @@ const AlephaCli = $module({
|
|
|
1686
2166
|
InitCommand,
|
|
1687
2167
|
LintCommand,
|
|
1688
2168
|
RootCommand,
|
|
1689
|
-
RunCommand,
|
|
1690
2169
|
TestCommand,
|
|
1691
2170
|
TypecheckCommand,
|
|
1692
2171
|
VerifyCommand,
|
|
@@ -1705,11 +2184,11 @@ var AlephaPackageBuilderCli = class {
|
|
|
1705
2184
|
root: true,
|
|
1706
2185
|
handler: async ({ run, root }) => {
|
|
1707
2186
|
const modules = [];
|
|
1708
|
-
const
|
|
1709
|
-
const pkgData = JSON.parse(
|
|
2187
|
+
const pkgBuffer = await this.fs.readFile("package.json");
|
|
2188
|
+
const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
|
|
1710
2189
|
const packageName = pkgData.name;
|
|
1711
2190
|
await run("analyze modules", async () => {
|
|
1712
|
-
modules.push(...await analyzeModules(join(root, this.src), packageName));
|
|
2191
|
+
modules.push(...await analyzeModules(this.fs.join(root, this.src), packageName));
|
|
1713
2192
|
});
|
|
1714
2193
|
pkgData.exports = {};
|
|
1715
2194
|
for (const item of modules) {
|
|
@@ -1721,6 +2200,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
1721
2200
|
if (item.native) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.native.ts`;
|
|
1722
2201
|
else if (item.browser) pkgData.exports[path]["react-native"] = `./src/${item.name}/index.browser.ts`;
|
|
1723
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`;
|
|
1724
2204
|
pkgData.exports[path].import = `./src/${item.name}/index.ts`;
|
|
1725
2205
|
pkgData.exports[path].default = `./src/${item.name}/index.ts`;
|
|
1726
2206
|
}
|
|
@@ -1733,33 +2213,30 @@ var AlephaPackageBuilderCli = class {
|
|
|
1733
2213
|
pkgData.exports["./json/styles"] = "./src/json/styles.css";
|
|
1734
2214
|
}
|
|
1735
2215
|
await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
|
|
1736
|
-
const tmpDir = join(root, "node_modules/.alepha");
|
|
2216
|
+
const tmpDir = this.fs.join(root, "node_modules/.alepha");
|
|
1737
2217
|
await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
|
|
1738
|
-
await this.fs.writeFile(join(tmpDir, "module-dependencies.json"), JSON.stringify(modules, null, 2));
|
|
1739
|
-
const
|
|
1740
|
-
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);
|
|
1741
2221
|
external.push("bun");
|
|
1742
2222
|
external.push("bun:sqlite");
|
|
1743
2223
|
await run.rm(this.dist);
|
|
1744
2224
|
const build = async (item) => {
|
|
1745
2225
|
const entries = [];
|
|
1746
|
-
const src = join(root, this.src, item.name);
|
|
1747
|
-
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);
|
|
1748
2228
|
entries.push({
|
|
1749
|
-
entry: join(src, "index.ts"),
|
|
2229
|
+
entry: this.fs.join(src, "index.ts"),
|
|
1750
2230
|
outDir: dest,
|
|
1751
2231
|
format: ["esm"],
|
|
1752
2232
|
sourcemap: true,
|
|
1753
2233
|
fixedExtension: false,
|
|
1754
2234
|
platform: "node",
|
|
1755
2235
|
external,
|
|
1756
|
-
dts: {
|
|
1757
|
-
sourcemap: true,
|
|
1758
|
-
resolve: false
|
|
1759
|
-
}
|
|
2236
|
+
dts: { sourcemap: true }
|
|
1760
2237
|
});
|
|
1761
2238
|
if (item.native) entries.push({
|
|
1762
|
-
entry: join(src, "index.native.ts"),
|
|
2239
|
+
entry: this.fs.join(src, "index.native.ts"),
|
|
1763
2240
|
outDir: dest,
|
|
1764
2241
|
platform: "neutral",
|
|
1765
2242
|
sourcemap: true,
|
|
@@ -1767,14 +2244,23 @@ var AlephaPackageBuilderCli = class {
|
|
|
1767
2244
|
external
|
|
1768
2245
|
});
|
|
1769
2246
|
if (item.browser) entries.push({
|
|
1770
|
-
entry: join(src, "index.browser.ts"),
|
|
2247
|
+
entry: this.fs.join(src, "index.browser.ts"),
|
|
1771
2248
|
outDir: dest,
|
|
1772
2249
|
platform: "browser",
|
|
1773
2250
|
sourcemap: true,
|
|
1774
2251
|
dts: false,
|
|
1775
2252
|
external
|
|
1776
2253
|
});
|
|
1777
|
-
|
|
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`);
|
|
1778
2264
|
await this.fs.writeFile(config, `export default ${JSON.stringify(entries, null, 2)};`);
|
|
1779
2265
|
await run(`npx tsdown -c=${config}`);
|
|
1780
2266
|
};
|
|
@@ -1848,7 +2334,7 @@ function detectCircularDependencies(modules) {
|
|
|
1848
2334
|
}
|
|
1849
2335
|
for (const module of modules) {
|
|
1850
2336
|
const cycle = hasCycle(module.name);
|
|
1851
|
-
if (cycle) throw new
|
|
2337
|
+
if (cycle) throw new AlephaError(`Circular dependency detected: ${cycle.join(" -> ")}`);
|
|
1852
2338
|
}
|
|
1853
2339
|
}
|
|
1854
2340
|
async function analyzeModules(srcDir, packageName) {
|
|
@@ -1862,6 +2348,7 @@ async function analyzeModules(srcDir, packageName) {
|
|
|
1862
2348
|
const dependencies = /* @__PURE__ */ new Set();
|
|
1863
2349
|
const hasBrowser = await fileExists(join(modulePath, "index.browser.ts"));
|
|
1864
2350
|
const hasNative = await fileExists(join(modulePath, "index.native.ts"));
|
|
2351
|
+
const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
|
|
1865
2352
|
const hasNode = await fileExists(join(modulePath, "index.node.ts"));
|
|
1866
2353
|
const files = await getAllFiles(modulePath);
|
|
1867
2354
|
for (const file of files) {
|
|
@@ -1878,6 +2365,7 @@ async function analyzeModules(srcDir, packageName) {
|
|
|
1878
2365
|
};
|
|
1879
2366
|
if (hasNative) module.native = true;
|
|
1880
2367
|
if (hasBrowser) module.browser = true;
|
|
2368
|
+
if (hasBun) module.bun = true;
|
|
1881
2369
|
if (hasNode) module.node = true;
|
|
1882
2370
|
modules.push(module);
|
|
1883
2371
|
} else await scanDirectory(modulePath, moduleName);
|
|
@@ -1890,11 +2378,13 @@ async function analyzeModules(srcDir, packageName) {
|
|
|
1890
2378
|
|
|
1891
2379
|
//#endregion
|
|
1892
2380
|
//#region ../../src/cli/defineConfig.ts
|
|
1893
|
-
const defineConfig = (
|
|
2381
|
+
const defineConfig = (runConfig) => {
|
|
1894
2382
|
return (alepha) => {
|
|
1895
|
-
const
|
|
1896
|
-
for (const it of services) alepha.with(it);
|
|
1897
|
-
|
|
2383
|
+
const config = typeof runConfig === "function" ? runConfig(alepha) : runConfig;
|
|
2384
|
+
if (config.services) for (const it of config.services) alepha.with(it);
|
|
2385
|
+
if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
|
|
2386
|
+
if (config.build) alepha.set(buildOptions, config.build);
|
|
2387
|
+
return { ...config.commands };
|
|
1898
2388
|
};
|
|
1899
2389
|
};
|
|
1900
2390
|
/**
|
|
@@ -1903,5 +2393,5 @@ const defineConfig = (config) => {
|
|
|
1903
2393
|
const defineAlephaConfig = defineConfig;
|
|
1904
2394
|
|
|
1905
2395
|
//#endregion
|
|
1906
|
-
export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand,
|
|
2396
|
+
export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, FormatCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
|
|
1907
2397
|
//# sourceMappingURL=index.js.map
|