alepha 0.15.0 → 0.15.2
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 +43 -98
- package/dist/api/audits/index.d.ts +630 -653
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +12 -35
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +365 -358
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +12 -5
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +255 -248
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +413 -0
- package/dist/api/keys/index.d.ts.map +1 -0
- package/dist/api/keys/index.js +476 -0
- package/dist/api/keys/index.js.map +1 -0
- package/dist/api/notifications/index.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +84 -78
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +14 -8
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +528 -535
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +30 -37
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +1221 -910
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2556 -248
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +142 -136
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +12 -4
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +142 -162
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +31 -44
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +595 -171
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +1856 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +225 -53
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +213 -7
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +6 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +834 -226
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2872 -417
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +458 -310
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2011 -76
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +309 -97
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +796 -701
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +329 -97
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +309 -97
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +59 -44
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +15 -0
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +314 -19
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1852 -7
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +5500 -5418
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +113 -42
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +219 -212
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +11 -4
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +41 -90
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +15 -68
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +228 -230
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +32 -31
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +12 -12
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +90 -80
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +1434 -1459
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +112 -130
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +262 -254
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js +14 -6
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/react/auth/index.browser.js +108 -0
- package/dist/react/auth/index.browser.js.map +1 -0
- package/dist/react/auth/index.d.ts +100 -0
- package/dist/react/auth/index.d.ts.map +1 -0
- package/dist/react/auth/index.js +145 -0
- package/dist/react/auth/index.js.map +1 -0
- package/dist/react/core/index.d.ts +469 -0
- package/dist/react/core/index.d.ts.map +1 -0
- package/dist/react/core/index.js +464 -0
- package/dist/react/core/index.js.map +1 -0
- package/dist/react/form/index.d.ts +232 -0
- package/dist/react/form/index.d.ts.map +1 -0
- package/dist/react/form/index.js +432 -0
- package/dist/react/form/index.js.map +1 -0
- package/dist/react/head/index.browser.js +423 -0
- package/dist/react/head/index.browser.js.map +1 -0
- package/dist/react/head/index.d.ts +288 -0
- package/dist/react/head/index.d.ts.map +1 -0
- package/dist/react/head/index.js +465 -0
- package/dist/react/head/index.js.map +1 -0
- package/dist/react/i18n/index.d.ts +175 -0
- package/dist/react/i18n/index.d.ts.map +1 -0
- package/dist/react/i18n/index.js +224 -0
- package/dist/react/i18n/index.js.map +1 -0
- package/dist/react/router/index.browser.js +1980 -0
- package/dist/react/router/index.browser.js.map +1 -0
- package/dist/react/router/index.d.ts +2068 -0
- package/dist/react/router/index.d.ts.map +1 -0
- package/dist/react/router/index.js +4932 -0
- package/dist/react/router/index.js.map +1 -0
- package/dist/react/websocket/index.d.ts +117 -0
- package/dist/react/websocket/index.d.ts.map +1 -0
- package/dist/react/websocket/index.js +107 -0
- package/dist/react/websocket/index.js.map +1 -0
- package/dist/redis/index.bun.js +4 -0
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +127 -130
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +16 -25
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +80 -71
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js +11 -2
- package/dist/retry/index.js.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 +119 -28
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +404 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +642 -228
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1579 -37
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1141 -111
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1261 -25
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +63 -78
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +7 -22
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +13 -5
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +10 -2
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +46 -22
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +7 -5
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +307 -196
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +271 -38
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +24 -34
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js +7 -21
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +25 -19
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js +8 -2
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +13 -5
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js +11 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +133 -128
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -11
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +524 -4
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4472 -7
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +15 -9
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js +9 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +110 -104
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js +8 -2
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +46 -51
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js +18 -55
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +181 -48
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +1848 -5
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +348 -53
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1849 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +312 -18
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +1854 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +496 -0
- package/dist/system/index.browser.js.map +1 -0
- package/dist/system/index.d.ts +1158 -0
- package/dist/system/index.d.ts.map +1 -0
- package/dist/{file → system}/index.js +412 -20
- package/dist/system/index.js.map +1 -0
- package/dist/thread/index.d.ts +82 -73
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +13 -4
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +330 -323
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js +12 -5
- package/dist/topic/core/index.js.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 +163 -5825
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +130 -477
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +3 -3
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +287 -283
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +15 -11
- package/dist/websocket/index.js.map +1 -1
- package/package.json +86 -17
- package/src/api/audits/index.ts +10 -33
- package/src/api/files/__tests__/$bucket.spec.ts +1 -1
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
- package/src/api/files/controllers/FileController.spec.ts +1 -1
- package/src/api/files/index.ts +10 -3
- package/src/api/files/jobs/FileJobs.spec.ts +1 -1
- package/src/api/files/services/FileService.spec.ts +1 -1
- package/src/api/jobs/index.ts +10 -3
- package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
- package/src/api/keys/controllers/ApiKeyController.ts +103 -0
- package/src/api/keys/entities/apiKeyEntity.ts +41 -0
- package/src/api/keys/index.ts +49 -0
- package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
- package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
- package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
- package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
- package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
- package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
- package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
- package/src/api/keys/services/ApiKeyService.ts +306 -0
- package/src/api/logs/TODO.md +52 -0
- package/src/api/notifications/index.ts +10 -4
- package/src/api/parameters/index.ts +9 -30
- package/src/api/parameters/primitives/$config.ts +12 -4
- package/src/api/parameters/services/ConfigStore.ts +9 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
- package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
- package/src/api/users/index.ts +14 -3
- package/src/api/users/primitives/$realm.ts +33 -5
- package/src/api/users/providers/RealmProvider.ts +1 -12
- package/src/api/users/services/SessionService.ts +1 -11
- package/src/api/verifications/controllers/VerificationController.ts +2 -0
- package/src/api/verifications/index.ts +10 -4
- package/src/batch/index.ts +9 -36
- package/src/batch/primitives/$batch.ts +0 -8
- package/src/batch/providers/BatchProvider.ts +29 -2
- package/src/bucket/__tests__/shared.ts +1 -1
- package/src/bucket/index.ts +13 -6
- package/src/bucket/primitives/$bucket.ts +1 -1
- package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
- package/src/cache/core/__tests__/shared.ts +30 -0
- package/src/cache/core/index.ts +11 -6
- package/src/cache/core/primitives/$cache.spec.ts +5 -0
- package/src/cache/core/providers/CacheProvider.ts +17 -0
- package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
- package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
- package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
- package/src/cli/apps/AlephaCli.ts +3 -16
- package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -2
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +64 -52
- package/src/cli/commands/db.ts +17 -11
- package/src/cli/commands/deploy.ts +1 -1
- package/src/cli/commands/dev.ts +13 -49
- package/src/cli/commands/gen/env.ts +6 -3
- package/src/cli/commands/gen/openapi.ts +5 -2
- package/src/cli/commands/init.spec.ts +544 -0
- package/src/cli/commands/init.ts +101 -58
- package/src/cli/commands/lint.ts +8 -2
- package/src/cli/commands/typecheck.ts +11 -0
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +40 -0
- package/src/cli/providers/ViteDevServerProvider.ts +378 -0
- package/src/cli/services/AlephaCliUtils.ts +39 -93
- package/src/cli/services/PackageManagerUtils.ts +140 -17
- package/src/cli/services/ProjectScaffolder.ts +169 -101
- package/src/cli/services/ViteUtils.ts +82 -0
- package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +41 -28
- package/src/cli/{assets → templates}/apiHelloControllerTs.ts +2 -1
- package/src/cli/{assets → templates}/biomeJson.ts +2 -1
- package/src/cli/{assets → templates}/dummySpecTs.ts +2 -1
- package/src/cli/{assets → templates}/editorconfig.ts +2 -1
- package/src/cli/templates/gitignore.ts +39 -0
- package/src/cli/{assets → templates}/mainBrowserTs.ts +2 -1
- package/src/cli/templates/mainCss.ts +33 -0
- package/src/cli/templates/mainServerTs.ts +33 -0
- package/src/cli/{assets → templates}/tsconfigJson.ts +2 -1
- package/src/cli/templates/webAppRouterTs.ts +50 -0
- package/src/cli/templates/webHelloComponentTsx.ts +20 -0
- package/src/command/helpers/Runner.spec.ts +4 -0
- package/src/command/helpers/Runner.ts +3 -21
- package/src/command/index.ts +12 -4
- package/src/command/providers/CliProvider.spec.ts +1067 -0
- package/src/command/providers/CliProvider.ts +203 -40
- package/src/core/Alepha.ts +3 -9
- package/src/core/__tests__/Alepha-start.spec.ts +4 -4
- package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +20 -0
- package/src/core/primitives/$module.ts +12 -0
- package/src/core/providers/EventManager.spec.ts +0 -71
- package/src/core/providers/EventManager.ts +3 -15
- package/src/core/providers/Json.ts +2 -14
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +257 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +396 -14
- package/src/core/providers/SchemaValidator.spec.ts +236 -0
- package/src/datetime/index.ts +15 -0
- package/src/email/index.ts +10 -5
- package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
- package/src/email/providers/LocalEmailProvider.ts +1 -1
- package/src/fake/__tests__/keyName.example.ts +1 -1
- package/src/fake/__tests__/keyName.spec.ts +5 -5
- package/src/fake/index.ts +9 -6
- package/src/fake/providers/FakeProvider.spec.ts +258 -40
- package/src/fake/providers/FakeProvider.ts +133 -19
- package/src/lock/core/index.ts +11 -4
- package/src/logger/index.ts +17 -66
- package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +13 -27
- package/src/mcp/transports/SseMcpTransport.ts +6 -7
- package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.bun.ts +4 -2
- package/src/orm/index.ts +21 -47
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -0
- package/src/orm/services/Repository.ts +18 -3
- package/src/queue/core/index.ts +14 -6
- package/src/react/auth/__tests__/$auth.spec.ts +202 -0
- package/src/react/auth/hooks/useAuth.ts +32 -0
- package/src/react/auth/index.browser.ts +13 -0
- package/src/react/auth/index.shared.ts +2 -0
- package/src/react/auth/index.ts +48 -0
- package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
- package/src/react/auth/services/ReactAuth.ts +135 -0
- package/src/react/core/__tests__/Router.spec.tsx +169 -0
- package/src/react/core/components/ClientOnly.tsx +49 -0
- package/src/react/core/components/ErrorBoundary.tsx +73 -0
- package/src/react/core/contexts/AlephaContext.ts +7 -0
- package/src/react/core/contexts/AlephaProvider.tsx +42 -0
- package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
- package/src/react/core/hooks/useAction.ts +480 -0
- package/src/react/core/hooks/useAlepha.ts +26 -0
- package/src/react/core/hooks/useClient.ts +17 -0
- package/src/react/core/hooks/useEvents.ts +51 -0
- package/src/react/core/hooks/useInject.ts +12 -0
- package/src/react/core/hooks/useStore.ts +52 -0
- package/src/react/core/index.ts +90 -0
- package/src/react/form/components/FormState.tsx +17 -0
- package/src/react/form/errors/FormValidationError.ts +18 -0
- package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
- package/src/react/form/hooks/useForm.ts +47 -0
- package/src/react/form/hooks/useFormState.ts +130 -0
- package/src/react/form/index.ts +44 -0
- package/src/react/form/services/FormModel.ts +614 -0
- package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
- package/src/react/head/helpers/SeoExpander.ts +142 -0
- package/src/react/head/hooks/useHead.spec.tsx +288 -0
- package/src/react/head/hooks/useHead.ts +62 -0
- package/src/react/head/index.browser.ts +26 -0
- package/src/react/head/index.ts +44 -0
- package/src/react/head/interfaces/Head.ts +105 -0
- package/src/react/head/primitives/$head.ts +25 -0
- package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
- package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
- package/src/react/head/providers/HeadProvider.ts +168 -0
- package/src/react/head/providers/ServerHeadProvider.ts +31 -0
- package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
- package/src/react/i18n/components/Localize.spec.tsx +357 -0
- package/src/react/i18n/components/Localize.tsx +35 -0
- package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
- package/src/react/i18n/hooks/useI18n.ts +18 -0
- package/src/react/i18n/index.ts +41 -0
- package/src/react/i18n/primitives/$dictionary.ts +69 -0
- package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
- package/src/react/i18n/providers/I18nProvider.ts +278 -0
- package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
- package/src/react/router/__tests__/page-head.spec.ts +48 -0
- package/src/react/router/__tests__/seo-head.spec.ts +125 -0
- package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
- package/src/react/router/components/ErrorViewer.tsx +872 -0
- package/src/react/router/components/Link.tsx +23 -0
- package/src/react/router/components/NestedView.tsx +223 -0
- package/src/react/router/components/NotFound.tsx +30 -0
- package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
- package/src/react/router/contexts/RouterLayerContext.ts +12 -0
- package/src/react/router/errors/Redirection.ts +28 -0
- package/src/react/router/hooks/useActive.ts +52 -0
- package/src/react/router/hooks/useQueryParams.ts +63 -0
- package/src/react/router/hooks/useRouter.ts +20 -0
- package/src/react/router/hooks/useRouterState.ts +11 -0
- package/src/react/router/index.browser.ts +45 -0
- package/src/react/router/index.shared.ts +19 -0
- package/src/react/router/index.ts +142 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
- package/src/react/router/primitives/$page.spec.tsx +708 -0
- package/src/react/router/primitives/$page.ts +497 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +309 -0
- package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
- package/src/react/router/providers/ReactPageProvider.ts +726 -0
- package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
- package/src/react/router/providers/ReactServerProvider.ts +558 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +979 -0
- package/src/react/router/providers/SSRManifestProvider.ts +334 -0
- package/src/react/router/services/ReactPageServerService.ts +48 -0
- package/src/react/router/services/ReactPageService.ts +27 -0
- package/src/react/router/services/ReactRouter.ts +262 -0
- package/src/react/websocket/hooks/useRoom.tsx +242 -0
- package/src/react/websocket/index.ts +7 -0
- package/src/redis/__tests__/redis.spec.ts +13 -0
- package/src/redis/index.ts +9 -25
- package/src/redis/providers/BunRedisProvider.ts +9 -0
- package/src/redis/providers/NodeRedisProvider.ts +8 -0
- package/src/redis/providers/RedisProvider.ts +16 -0
- package/src/retry/index.ts +11 -2
- package/src/router/index.ts +15 -0
- package/src/scheduler/index.ts +11 -2
- package/src/security/__tests__/BasicAuth.spec.ts +2 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +13 -5
- package/src/security/index.ts +15 -10
- package/src/security/interfaces/IssuerResolver.ts +27 -0
- package/src/security/primitives/$issuer.ts +55 -0
- package/src/security/providers/SecurityProvider.ts +179 -0
- package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
- package/src/security/providers/ServerSecurityProvider.ts +36 -22
- package/src/server/auth/index.ts +12 -7
- package/src/server/cache/index.ts +7 -22
- package/src/server/compress/index.ts +10 -2
- package/src/server/cookies/index.ts +7 -5
- package/src/server/cookies/primitives/$cookie.ts +33 -11
- package/src/server/core/index.ts +17 -7
- package/src/server/core/interfaces/ServerRequest.ts +83 -1
- package/src/server/core/primitives/$action.spec.ts +1 -1
- package/src/server/core/primitives/$action.ts +8 -3
- package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +77 -22
- package/src/server/core/providers/ServerLoggerProvider.ts +2 -2
- package/src/server/core/providers/ServerProvider.ts +9 -12
- package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
- package/src/server/core/services/ServerRequestParser.ts +306 -13
- package/src/server/cors/index.ts +7 -21
- package/src/server/cors/primitives/$cors.ts +6 -2
- package/src/server/health/index.ts +8 -2
- package/src/server/helmet/index.ts +11 -3
- package/src/server/links/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +13 -6
- package/src/server/metrics/index.ts +10 -3
- package/src/server/multipart/index.ts +9 -3
- package/src/server/proxy/index.ts +8 -2
- package/src/server/rate-limit/index.ts +21 -25
- package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
- package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
- package/src/server/static/index.ts +8 -2
- package/src/server/static/providers/ServerStaticProvider.ts +1 -1
- package/src/server/swagger/index.ts +9 -4
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/src/sms/index.ts +9 -5
- package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.ts +1 -1
- package/src/system/index.browser.ts +11 -0
- package/src/system/index.ts +62 -0
- package/src/{file → system}/providers/FileSystemProvider.ts +16 -0
- package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
- package/src/system/providers/MemoryShellProvider.ts +164 -0
- package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
- package/src/{file → system}/providers/NodeFileSystemProvider.ts +36 -0
- package/src/system/providers/NodeShellProvider.ts +184 -0
- package/src/system/providers/ShellProvider.ts +74 -0
- package/src/{file → system}/services/FileDetector.spec.ts +2 -2
- package/src/thread/index.ts +11 -2
- package/src/topic/core/index.ts +12 -5
- package/src/vite/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +2 -8
- package/src/vite/tasks/buildServer.ts +84 -21
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- package/src/websocket/index.ts +12 -8
- package/dist/file/index.d.ts +0 -839
- package/dist/file/index.d.ts.map +0 -1
- package/dist/file/index.js.map +0 -1
- package/src/cli/assets/indexHtml.ts +0 -15
- package/src/cli/assets/mainServerTs.ts +0 -24
- package/src/cli/assets/webAppRouterTs.ts +0 -15
- package/src/cli/assets/webHelloComponentTsx.ts +0 -16
- package/src/cli/commands/format.ts +0 -23
- package/src/file/index.ts +0 -43
- package/src/vite/helpers/boot.ts +0 -117
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -71
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/dist/orm/{chunk-DtkW-qnP.js → chunk-DH6iiROE.js} +0 -0
- /package/src/cli/{assets → templates}/apiIndexTs.ts +0 -0
- /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
- /package/src/{file → system}/errors/FileError.ts +0 -0
- /package/src/{file → system}/services/FileDetector.ts +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,16 +1,1852 @@
|
|
|
1
|
-
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, t } from "alepha";
|
|
2
|
-
import { FileSystemProvider } from "alepha/file";
|
|
3
|
-
import { $command, CliProvider, EnvUtils } from "alepha/command";
|
|
4
|
-
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
5
|
-
import { boot, buildClient, buildServer, copyAssets, devServer, generateCloudflare, generateDocker, generateSitemap, generateVercel, prerenderPages } from "alepha/vite";
|
|
6
|
-
import { exec, spawn } from "node:child_process";
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { $atom, $hook, $inject, $module, $use, Alepha, AlephaError, Json, isFileLike, t } from "alepha";
|
|
8
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { createReadStream, readFileSync } from "node:fs";
|
|
4
|
+
import { access, copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { PassThrough, Readable } from "node:stream";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { exec, spawn } from "node:child_process";
|
|
8
|
+
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
9
|
+
import { $command, CliProvider, EnvUtils } from "alepha/command";
|
|
10
|
+
import { buildClient, buildServer, copyAssets, generateCloudflare, generateDocker, generateSitemap, generateVercel, importVite, importViteReact, prerenderPages, viteAlephaSsrPreload } from "alepha/vite";
|
|
9
11
|
import { promisify } from "node:util";
|
|
10
12
|
import { ServerSwaggerProvider } from "alepha/server/swagger";
|
|
11
|
-
import { access, readFile, readdir } from "node:fs/promises";
|
|
12
13
|
import * as os from "node:os";
|
|
13
14
|
|
|
15
|
+
//#region ../../src/system/providers/FileSystemProvider.ts
|
|
16
|
+
/**
|
|
17
|
+
* FileSystem interface providing utilities for working with files.
|
|
18
|
+
*/
|
|
19
|
+
var FileSystemProvider = class {};
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region ../../src/system/providers/MemoryFileSystemProvider.ts
|
|
23
|
+
/**
|
|
24
|
+
* In-memory implementation of FileSystemProvider for testing.
|
|
25
|
+
*
|
|
26
|
+
* This provider stores all files and directories in memory, making it ideal for
|
|
27
|
+
* unit tests that need to verify file operations without touching the real file system.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // In tests, substitute the real FileSystemProvider with MemoryFileSystemProvider
|
|
32
|
+
* const alepha = Alepha.create().with({
|
|
33
|
+
* provide: FileSystemProvider,
|
|
34
|
+
* use: MemoryFileSystemProvider,
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Run code that uses FileSystemProvider
|
|
38
|
+
* const service = alepha.inject(MyService);
|
|
39
|
+
* await service.saveFile("test.txt", "Hello World");
|
|
40
|
+
*
|
|
41
|
+
* // Verify the file was written
|
|
42
|
+
* const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
43
|
+
* expect(memoryFs.files.get("test.txt")?.toString()).toBe("Hello World");
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
var MemoryFileSystemProvider = class {
|
|
47
|
+
json = $inject(Json);
|
|
48
|
+
/**
|
|
49
|
+
* In-memory storage for files (path -> content)
|
|
50
|
+
*/
|
|
51
|
+
files = /* @__PURE__ */ new Map();
|
|
52
|
+
/**
|
|
53
|
+
* In-memory storage for directories
|
|
54
|
+
*/
|
|
55
|
+
directories = /* @__PURE__ */ new Set();
|
|
56
|
+
/**
|
|
57
|
+
* Track mkdir calls for test assertions
|
|
58
|
+
*/
|
|
59
|
+
mkdirCalls = [];
|
|
60
|
+
/**
|
|
61
|
+
* Track writeFile calls for test assertions
|
|
62
|
+
*/
|
|
63
|
+
writeFileCalls = [];
|
|
64
|
+
/**
|
|
65
|
+
* Track readFile calls for test assertions
|
|
66
|
+
*/
|
|
67
|
+
readFileCalls = [];
|
|
68
|
+
/**
|
|
69
|
+
* Track rm calls for test assertions
|
|
70
|
+
*/
|
|
71
|
+
rmCalls = [];
|
|
72
|
+
/**
|
|
73
|
+
* Track join calls for test assertions
|
|
74
|
+
*/
|
|
75
|
+
joinCalls = [];
|
|
76
|
+
/**
|
|
77
|
+
* Error to throw on mkdir (for testing error handling)
|
|
78
|
+
*/
|
|
79
|
+
mkdirError = null;
|
|
80
|
+
/**
|
|
81
|
+
* Error to throw on writeFile (for testing error handling)
|
|
82
|
+
*/
|
|
83
|
+
writeFileError = null;
|
|
84
|
+
/**
|
|
85
|
+
* Error to throw on readFile (for testing error handling)
|
|
86
|
+
*/
|
|
87
|
+
readFileError = null;
|
|
88
|
+
constructor(options = {}) {
|
|
89
|
+
this.mkdirError = options.mkdirError ?? null;
|
|
90
|
+
this.writeFileError = options.writeFileError ?? null;
|
|
91
|
+
this.readFileError = options.readFileError ?? null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Join path segments using forward slashes.
|
|
95
|
+
* Uses Node's path.join for proper normalization (handles .. and .)
|
|
96
|
+
*/
|
|
97
|
+
join(...paths) {
|
|
98
|
+
this.joinCalls.push(paths);
|
|
99
|
+
return join(...paths);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create a FileLike object from various sources.
|
|
103
|
+
*/
|
|
104
|
+
createFile(options) {
|
|
105
|
+
if ("path" in options) {
|
|
106
|
+
const filePath = options.path;
|
|
107
|
+
const buffer = this.files.get(filePath);
|
|
108
|
+
if (buffer === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
|
|
109
|
+
return {
|
|
110
|
+
name: options.name ?? filePath.split("/").pop() ?? "file",
|
|
111
|
+
type: options.type ?? "application/octet-stream",
|
|
112
|
+
size: buffer.byteLength,
|
|
113
|
+
lastModified: Date.now(),
|
|
114
|
+
stream: () => {
|
|
115
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
116
|
+
},
|
|
117
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
118
|
+
text: async () => buffer.toString("utf-8")
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if ("buffer" in options) {
|
|
122
|
+
const buffer = options.buffer;
|
|
123
|
+
return {
|
|
124
|
+
name: options.name ?? "file",
|
|
125
|
+
type: options.type ?? "application/octet-stream",
|
|
126
|
+
size: buffer.byteLength,
|
|
127
|
+
lastModified: Date.now(),
|
|
128
|
+
stream: () => {
|
|
129
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
130
|
+
},
|
|
131
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
132
|
+
text: async () => buffer.toString("utf-8")
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if ("text" in options) {
|
|
136
|
+
const buffer = Buffer.from(options.text, "utf-8");
|
|
137
|
+
return {
|
|
138
|
+
name: options.name ?? "file.txt",
|
|
139
|
+
type: options.type ?? "text/plain",
|
|
140
|
+
size: buffer.byteLength,
|
|
141
|
+
lastModified: Date.now(),
|
|
142
|
+
stream: () => {
|
|
143
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
144
|
+
},
|
|
145
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
146
|
+
text: async () => options.text
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
throw new Error("MemoryFileSystemProvider.createFile: unsupported options. Only buffer and text are supported.");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove a file or directory from memory.
|
|
153
|
+
*/
|
|
154
|
+
async rm(path, options) {
|
|
155
|
+
this.rmCalls.push({
|
|
156
|
+
path,
|
|
157
|
+
options
|
|
158
|
+
});
|
|
159
|
+
if (!(this.files.has(path) || this.directories.has(path)) && !options?.force) throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
|
|
160
|
+
if (this.directories.has(path)) if (options?.recursive) {
|
|
161
|
+
this.directories.delete(path);
|
|
162
|
+
for (const filePath of this.files.keys()) if (filePath.startsWith(`${path}/`)) this.files.delete(filePath);
|
|
163
|
+
for (const dirPath of this.directories) if (dirPath.startsWith(`${path}/`)) this.directories.delete(dirPath);
|
|
164
|
+
} else throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
|
|
165
|
+
else this.files.delete(path);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Copy a file or directory in memory.
|
|
169
|
+
*/
|
|
170
|
+
async cp(src, dest, options) {
|
|
171
|
+
if (this.directories.has(src)) {
|
|
172
|
+
if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
|
|
173
|
+
this.directories.add(dest);
|
|
174
|
+
for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
|
|
175
|
+
const newPath = filePath.replace(src, dest);
|
|
176
|
+
this.files.set(newPath, Buffer.from(content));
|
|
177
|
+
}
|
|
178
|
+
} else if (this.files.has(src)) {
|
|
179
|
+
const content = this.files.get(src);
|
|
180
|
+
this.files.set(dest, Buffer.from(content));
|
|
181
|
+
} else throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Move/rename a file or directory in memory.
|
|
185
|
+
*/
|
|
186
|
+
async mv(src, dest) {
|
|
187
|
+
if (this.directories.has(src)) {
|
|
188
|
+
this.directories.delete(src);
|
|
189
|
+
this.directories.add(dest);
|
|
190
|
+
for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
|
|
191
|
+
const newPath = filePath.replace(src, dest);
|
|
192
|
+
this.files.delete(filePath);
|
|
193
|
+
this.files.set(newPath, content);
|
|
194
|
+
}
|
|
195
|
+
} else if (this.files.has(src)) {
|
|
196
|
+
const content = this.files.get(src);
|
|
197
|
+
this.files.delete(src);
|
|
198
|
+
this.files.set(dest, content);
|
|
199
|
+
} else throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Create a directory in memory.
|
|
203
|
+
*/
|
|
204
|
+
async mkdir(path, options) {
|
|
205
|
+
this.mkdirCalls.push({
|
|
206
|
+
path,
|
|
207
|
+
options
|
|
208
|
+
});
|
|
209
|
+
if (this.mkdirError) throw this.mkdirError;
|
|
210
|
+
if (this.directories.has(path) && !options?.recursive) throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
|
|
211
|
+
this.directories.add(path);
|
|
212
|
+
if (options?.recursive) {
|
|
213
|
+
const parts = path.split("/").filter(Boolean);
|
|
214
|
+
let current = "";
|
|
215
|
+
for (const part of parts) {
|
|
216
|
+
current = current ? `${current}/${part}` : part;
|
|
217
|
+
this.directories.add(current);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* List files in a directory.
|
|
223
|
+
*/
|
|
224
|
+
async ls(path, options) {
|
|
225
|
+
const normalizedPath = path.replace(/\/$/, "");
|
|
226
|
+
const entries = /* @__PURE__ */ new Set();
|
|
227
|
+
for (const filePath of this.files.keys()) if (filePath.startsWith(`${normalizedPath}/`)) {
|
|
228
|
+
const relativePath = filePath.slice(normalizedPath.length + 1);
|
|
229
|
+
const parts = relativePath.split("/");
|
|
230
|
+
if (options?.recursive) entries.add(relativePath);
|
|
231
|
+
else entries.add(parts[0]);
|
|
232
|
+
}
|
|
233
|
+
for (const dirPath of this.directories) if (dirPath.startsWith(`${normalizedPath}/`) && dirPath !== normalizedPath) {
|
|
234
|
+
const relativePath = dirPath.slice(normalizedPath.length + 1);
|
|
235
|
+
const parts = relativePath.split("/");
|
|
236
|
+
if (options?.recursive) entries.add(relativePath);
|
|
237
|
+
else if (parts.length === 1) entries.add(parts[0]);
|
|
238
|
+
}
|
|
239
|
+
let result = Array.from(entries);
|
|
240
|
+
if (!options?.hidden) result = result.filter((entry) => !entry.startsWith("."));
|
|
241
|
+
return result.sort();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if a file or directory exists in memory.
|
|
245
|
+
*/
|
|
246
|
+
async exists(path) {
|
|
247
|
+
return this.files.has(path) || this.directories.has(path);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Read a file from memory.
|
|
251
|
+
*/
|
|
252
|
+
async readFile(path) {
|
|
253
|
+
this.readFileCalls.push(path);
|
|
254
|
+
if (this.readFileError) throw this.readFileError;
|
|
255
|
+
const content = this.files.get(path);
|
|
256
|
+
if (!content) throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
257
|
+
return content;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Read a file from memory as text.
|
|
261
|
+
*/
|
|
262
|
+
async readTextFile(path) {
|
|
263
|
+
return (await this.readFile(path)).toString("utf-8");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Read a file from memory as JSON.
|
|
267
|
+
*/
|
|
268
|
+
async readJsonFile(path) {
|
|
269
|
+
const text = await this.readTextFile(path);
|
|
270
|
+
return this.json.parse(text);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Write a file to memory.
|
|
274
|
+
*/
|
|
275
|
+
async writeFile(path, data) {
|
|
276
|
+
const dataStr = typeof data === "string" ? data : data instanceof Buffer || data instanceof Uint8Array ? data.toString("utf-8") : await data.text();
|
|
277
|
+
this.writeFileCalls.push({
|
|
278
|
+
path,
|
|
279
|
+
data: dataStr
|
|
280
|
+
});
|
|
281
|
+
if (this.writeFileError) throw this.writeFileError;
|
|
282
|
+
const buffer = typeof data === "string" ? Buffer.from(data, "utf-8") : data instanceof Buffer ? data : data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(await data.text(), "utf-8");
|
|
283
|
+
this.files.set(path, buffer);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Reset all in-memory state (useful between tests).
|
|
287
|
+
*/
|
|
288
|
+
reset() {
|
|
289
|
+
this.files.clear();
|
|
290
|
+
this.directories.clear();
|
|
291
|
+
this.mkdirCalls = [];
|
|
292
|
+
this.writeFileCalls = [];
|
|
293
|
+
this.readFileCalls = [];
|
|
294
|
+
this.rmCalls = [];
|
|
295
|
+
this.joinCalls = [];
|
|
296
|
+
this.mkdirError = null;
|
|
297
|
+
this.writeFileError = null;
|
|
298
|
+
this.readFileError = null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Check if a file was written during the test.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
wasWritten(path) {
|
|
309
|
+
return this.writeFileCalls.some((call) => call.path === path);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Check if a file was written with content matching a pattern.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
wasWrittenMatching(path, pattern) {
|
|
320
|
+
const call = this.writeFileCalls.find((c) => c.path === path);
|
|
321
|
+
return call ? pattern.test(call.data) : false;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Check if a file was read during the test.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* expect(fs.wasRead("/project/package.json")).toBe(true);
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
wasRead(path) {
|
|
332
|
+
return this.readFileCalls.includes(path);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Check if a file was deleted during the test.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
wasDeleted(path) {
|
|
343
|
+
return this.rmCalls.some((call) => call.path === path);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get the content of a file as a string (convenience method for testing).
|
|
347
|
+
*/
|
|
348
|
+
getFileContent(path) {
|
|
349
|
+
return this.files.get(path)?.toString("utf-8");
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region ../../src/system/providers/MemoryShellProvider.ts
|
|
355
|
+
/**
|
|
356
|
+
* In-memory implementation of ShellProvider for testing.
|
|
357
|
+
*
|
|
358
|
+
* Records all commands that would be executed without actually running them.
|
|
359
|
+
* Can be configured to return specific outputs or throw errors for testing.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* // In tests, substitute the real ShellProvider with MemoryShellProvider
|
|
364
|
+
* const alepha = Alepha.create().with({
|
|
365
|
+
* provide: ShellProvider,
|
|
366
|
+
* use: MemoryShellProvider,
|
|
367
|
+
* });
|
|
368
|
+
*
|
|
369
|
+
* // Configure mock behavior
|
|
370
|
+
* const shell = alepha.inject(MemoryShellProvider);
|
|
371
|
+
* shell.configure({
|
|
372
|
+
* outputs: { "echo hello": "hello\n" },
|
|
373
|
+
* errors: { "failing-cmd": "Command failed" },
|
|
374
|
+
* });
|
|
375
|
+
*
|
|
376
|
+
* // Or use the fluent API
|
|
377
|
+
* shell.outputs.set("another-cmd", "output");
|
|
378
|
+
* shell.errors.set("another-error", "Error message");
|
|
379
|
+
*
|
|
380
|
+
* // Run code that uses ShellProvider
|
|
381
|
+
* const service = alepha.inject(MyService);
|
|
382
|
+
* await service.doSomething();
|
|
383
|
+
*
|
|
384
|
+
* // Verify commands were called
|
|
385
|
+
* expect(shell.calls).toHaveLength(2);
|
|
386
|
+
* expect(shell.calls[0].command).toBe("yarn install");
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
var MemoryShellProvider = class {
|
|
390
|
+
/**
|
|
391
|
+
* All recorded shell calls.
|
|
392
|
+
*/
|
|
393
|
+
calls = [];
|
|
394
|
+
/**
|
|
395
|
+
* Simulated outputs for specific commands.
|
|
396
|
+
*/
|
|
397
|
+
outputs = /* @__PURE__ */ new Map();
|
|
398
|
+
/**
|
|
399
|
+
* Commands that should throw an error.
|
|
400
|
+
*/
|
|
401
|
+
errors = /* @__PURE__ */ new Map();
|
|
402
|
+
/**
|
|
403
|
+
* Commands considered installed in the system PATH.
|
|
404
|
+
*/
|
|
405
|
+
installedCommands = /* @__PURE__ */ new Set();
|
|
406
|
+
/**
|
|
407
|
+
* Configure the mock with predefined outputs, errors, and installed commands.
|
|
408
|
+
*/
|
|
409
|
+
configure(options) {
|
|
410
|
+
if (options.outputs) for (const [cmd, output] of Object.entries(options.outputs)) this.outputs.set(cmd, output);
|
|
411
|
+
if (options.errors) for (const [cmd, error] of Object.entries(options.errors)) this.errors.set(cmd, error);
|
|
412
|
+
if (options.installedCommands) for (const cmd of options.installedCommands) this.installedCommands.add(cmd);
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Record command and return simulated output.
|
|
417
|
+
*/
|
|
418
|
+
async run(command, options = {}) {
|
|
419
|
+
this.calls.push({
|
|
420
|
+
command,
|
|
421
|
+
options
|
|
422
|
+
});
|
|
423
|
+
const errorMsg = this.errors.get(command);
|
|
424
|
+
if (errorMsg) throw new Error(errorMsg);
|
|
425
|
+
return this.outputs.get(command) ?? "";
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check if a specific command was called.
|
|
429
|
+
*/
|
|
430
|
+
wasCalled(command) {
|
|
431
|
+
return this.calls.some((call) => call.command === command);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Check if a command matching a pattern was called.
|
|
435
|
+
*/
|
|
436
|
+
wasCalledMatching(pattern) {
|
|
437
|
+
return this.calls.some((call) => pattern.test(call.command));
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get all calls matching a pattern.
|
|
441
|
+
*/
|
|
442
|
+
getCallsMatching(pattern) {
|
|
443
|
+
return this.calls.filter((call) => pattern.test(call.command));
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Check if a command is installed.
|
|
447
|
+
*/
|
|
448
|
+
async isInstalled(command) {
|
|
449
|
+
return this.installedCommands.has(command);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Reset all recorded state.
|
|
453
|
+
*/
|
|
454
|
+
reset() {
|
|
455
|
+
this.calls = [];
|
|
456
|
+
this.outputs.clear();
|
|
457
|
+
this.errors.clear();
|
|
458
|
+
this.installedCommands.clear();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region ../../src/system/services/FileDetector.ts
|
|
464
|
+
/**
|
|
465
|
+
* Service for detecting file types and getting content types.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* const detector = alepha.inject(FileDetector);
|
|
470
|
+
*
|
|
471
|
+
* // Get content type from filename
|
|
472
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
473
|
+
*
|
|
474
|
+
* // Detect file type by magic bytes
|
|
475
|
+
* const stream = createReadStream('image.png');
|
|
476
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
477
|
+
* console.log(result.mimeType); // 'image/png'
|
|
478
|
+
* console.log(result.verified); // true if magic bytes match
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
var FileDetector = class FileDetector {
|
|
482
|
+
/**
|
|
483
|
+
* Magic byte signatures for common file formats.
|
|
484
|
+
* Each signature is represented as an array of bytes or null (wildcard).
|
|
485
|
+
*/
|
|
486
|
+
static MAGIC_BYTES = {
|
|
487
|
+
png: [{
|
|
488
|
+
signature: [
|
|
489
|
+
137,
|
|
490
|
+
80,
|
|
491
|
+
78,
|
|
492
|
+
71,
|
|
493
|
+
13,
|
|
494
|
+
10,
|
|
495
|
+
26,
|
|
496
|
+
10
|
|
497
|
+
],
|
|
498
|
+
mimeType: "image/png"
|
|
499
|
+
}],
|
|
500
|
+
jpg: [
|
|
501
|
+
{
|
|
502
|
+
signature: [
|
|
503
|
+
255,
|
|
504
|
+
216,
|
|
505
|
+
255,
|
|
506
|
+
224
|
|
507
|
+
],
|
|
508
|
+
mimeType: "image/jpeg"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
signature: [
|
|
512
|
+
255,
|
|
513
|
+
216,
|
|
514
|
+
255,
|
|
515
|
+
225
|
|
516
|
+
],
|
|
517
|
+
mimeType: "image/jpeg"
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
signature: [
|
|
521
|
+
255,
|
|
522
|
+
216,
|
|
523
|
+
255,
|
|
524
|
+
226
|
|
525
|
+
],
|
|
526
|
+
mimeType: "image/jpeg"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
signature: [
|
|
530
|
+
255,
|
|
531
|
+
216,
|
|
532
|
+
255,
|
|
533
|
+
227
|
|
534
|
+
],
|
|
535
|
+
mimeType: "image/jpeg"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
signature: [
|
|
539
|
+
255,
|
|
540
|
+
216,
|
|
541
|
+
255,
|
|
542
|
+
232
|
|
543
|
+
],
|
|
544
|
+
mimeType: "image/jpeg"
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
jpeg: [
|
|
548
|
+
{
|
|
549
|
+
signature: [
|
|
550
|
+
255,
|
|
551
|
+
216,
|
|
552
|
+
255,
|
|
553
|
+
224
|
|
554
|
+
],
|
|
555
|
+
mimeType: "image/jpeg"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
signature: [
|
|
559
|
+
255,
|
|
560
|
+
216,
|
|
561
|
+
255,
|
|
562
|
+
225
|
|
563
|
+
],
|
|
564
|
+
mimeType: "image/jpeg"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
signature: [
|
|
568
|
+
255,
|
|
569
|
+
216,
|
|
570
|
+
255,
|
|
571
|
+
226
|
|
572
|
+
],
|
|
573
|
+
mimeType: "image/jpeg"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
signature: [
|
|
577
|
+
255,
|
|
578
|
+
216,
|
|
579
|
+
255,
|
|
580
|
+
227
|
|
581
|
+
],
|
|
582
|
+
mimeType: "image/jpeg"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
signature: [
|
|
586
|
+
255,
|
|
587
|
+
216,
|
|
588
|
+
255,
|
|
589
|
+
232
|
|
590
|
+
],
|
|
591
|
+
mimeType: "image/jpeg"
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
gif: [{
|
|
595
|
+
signature: [
|
|
596
|
+
71,
|
|
597
|
+
73,
|
|
598
|
+
70,
|
|
599
|
+
56,
|
|
600
|
+
55,
|
|
601
|
+
97
|
|
602
|
+
],
|
|
603
|
+
mimeType: "image/gif"
|
|
604
|
+
}, {
|
|
605
|
+
signature: [
|
|
606
|
+
71,
|
|
607
|
+
73,
|
|
608
|
+
70,
|
|
609
|
+
56,
|
|
610
|
+
57,
|
|
611
|
+
97
|
|
612
|
+
],
|
|
613
|
+
mimeType: "image/gif"
|
|
614
|
+
}],
|
|
615
|
+
webp: [{
|
|
616
|
+
signature: [
|
|
617
|
+
82,
|
|
618
|
+
73,
|
|
619
|
+
70,
|
|
620
|
+
70,
|
|
621
|
+
null,
|
|
622
|
+
null,
|
|
623
|
+
null,
|
|
624
|
+
null,
|
|
625
|
+
87,
|
|
626
|
+
69,
|
|
627
|
+
66,
|
|
628
|
+
80
|
|
629
|
+
],
|
|
630
|
+
mimeType: "image/webp"
|
|
631
|
+
}],
|
|
632
|
+
bmp: [{
|
|
633
|
+
signature: [66, 77],
|
|
634
|
+
mimeType: "image/bmp"
|
|
635
|
+
}],
|
|
636
|
+
ico: [{
|
|
637
|
+
signature: [
|
|
638
|
+
0,
|
|
639
|
+
0,
|
|
640
|
+
1,
|
|
641
|
+
0
|
|
642
|
+
],
|
|
643
|
+
mimeType: "image/x-icon"
|
|
644
|
+
}],
|
|
645
|
+
tiff: [{
|
|
646
|
+
signature: [
|
|
647
|
+
73,
|
|
648
|
+
73,
|
|
649
|
+
42,
|
|
650
|
+
0
|
|
651
|
+
],
|
|
652
|
+
mimeType: "image/tiff"
|
|
653
|
+
}, {
|
|
654
|
+
signature: [
|
|
655
|
+
77,
|
|
656
|
+
77,
|
|
657
|
+
0,
|
|
658
|
+
42
|
|
659
|
+
],
|
|
660
|
+
mimeType: "image/tiff"
|
|
661
|
+
}],
|
|
662
|
+
tif: [{
|
|
663
|
+
signature: [
|
|
664
|
+
73,
|
|
665
|
+
73,
|
|
666
|
+
42,
|
|
667
|
+
0
|
|
668
|
+
],
|
|
669
|
+
mimeType: "image/tiff"
|
|
670
|
+
}, {
|
|
671
|
+
signature: [
|
|
672
|
+
77,
|
|
673
|
+
77,
|
|
674
|
+
0,
|
|
675
|
+
42
|
|
676
|
+
],
|
|
677
|
+
mimeType: "image/tiff"
|
|
678
|
+
}],
|
|
679
|
+
pdf: [{
|
|
680
|
+
signature: [
|
|
681
|
+
37,
|
|
682
|
+
80,
|
|
683
|
+
68,
|
|
684
|
+
70,
|
|
685
|
+
45
|
|
686
|
+
],
|
|
687
|
+
mimeType: "application/pdf"
|
|
688
|
+
}],
|
|
689
|
+
zip: [
|
|
690
|
+
{
|
|
691
|
+
signature: [
|
|
692
|
+
80,
|
|
693
|
+
75,
|
|
694
|
+
3,
|
|
695
|
+
4
|
|
696
|
+
],
|
|
697
|
+
mimeType: "application/zip"
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
signature: [
|
|
701
|
+
80,
|
|
702
|
+
75,
|
|
703
|
+
5,
|
|
704
|
+
6
|
|
705
|
+
],
|
|
706
|
+
mimeType: "application/zip"
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
signature: [
|
|
710
|
+
80,
|
|
711
|
+
75,
|
|
712
|
+
7,
|
|
713
|
+
8
|
|
714
|
+
],
|
|
715
|
+
mimeType: "application/zip"
|
|
716
|
+
}
|
|
717
|
+
],
|
|
718
|
+
rar: [{
|
|
719
|
+
signature: [
|
|
720
|
+
82,
|
|
721
|
+
97,
|
|
722
|
+
114,
|
|
723
|
+
33,
|
|
724
|
+
26,
|
|
725
|
+
7
|
|
726
|
+
],
|
|
727
|
+
mimeType: "application/vnd.rar"
|
|
728
|
+
}],
|
|
729
|
+
"7z": [{
|
|
730
|
+
signature: [
|
|
731
|
+
55,
|
|
732
|
+
122,
|
|
733
|
+
188,
|
|
734
|
+
175,
|
|
735
|
+
39,
|
|
736
|
+
28
|
|
737
|
+
],
|
|
738
|
+
mimeType: "application/x-7z-compressed"
|
|
739
|
+
}],
|
|
740
|
+
tar: [{
|
|
741
|
+
signature: [
|
|
742
|
+
117,
|
|
743
|
+
115,
|
|
744
|
+
116,
|
|
745
|
+
97,
|
|
746
|
+
114
|
|
747
|
+
],
|
|
748
|
+
mimeType: "application/x-tar"
|
|
749
|
+
}],
|
|
750
|
+
gz: [{
|
|
751
|
+
signature: [31, 139],
|
|
752
|
+
mimeType: "application/gzip"
|
|
753
|
+
}],
|
|
754
|
+
tgz: [{
|
|
755
|
+
signature: [31, 139],
|
|
756
|
+
mimeType: "application/gzip"
|
|
757
|
+
}],
|
|
758
|
+
mp3: [
|
|
759
|
+
{
|
|
760
|
+
signature: [255, 251],
|
|
761
|
+
mimeType: "audio/mpeg"
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
signature: [255, 243],
|
|
765
|
+
mimeType: "audio/mpeg"
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
signature: [255, 242],
|
|
769
|
+
mimeType: "audio/mpeg"
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
signature: [
|
|
773
|
+
73,
|
|
774
|
+
68,
|
|
775
|
+
51
|
|
776
|
+
],
|
|
777
|
+
mimeType: "audio/mpeg"
|
|
778
|
+
}
|
|
779
|
+
],
|
|
780
|
+
wav: [{
|
|
781
|
+
signature: [
|
|
782
|
+
82,
|
|
783
|
+
73,
|
|
784
|
+
70,
|
|
785
|
+
70,
|
|
786
|
+
null,
|
|
787
|
+
null,
|
|
788
|
+
null,
|
|
789
|
+
null,
|
|
790
|
+
87,
|
|
791
|
+
65,
|
|
792
|
+
86,
|
|
793
|
+
69
|
|
794
|
+
],
|
|
795
|
+
mimeType: "audio/wav"
|
|
796
|
+
}],
|
|
797
|
+
ogg: [{
|
|
798
|
+
signature: [
|
|
799
|
+
79,
|
|
800
|
+
103,
|
|
801
|
+
103,
|
|
802
|
+
83
|
|
803
|
+
],
|
|
804
|
+
mimeType: "audio/ogg"
|
|
805
|
+
}],
|
|
806
|
+
flac: [{
|
|
807
|
+
signature: [
|
|
808
|
+
102,
|
|
809
|
+
76,
|
|
810
|
+
97,
|
|
811
|
+
67
|
|
812
|
+
],
|
|
813
|
+
mimeType: "audio/flac"
|
|
814
|
+
}],
|
|
815
|
+
mp4: [
|
|
816
|
+
{
|
|
817
|
+
signature: [
|
|
818
|
+
null,
|
|
819
|
+
null,
|
|
820
|
+
null,
|
|
821
|
+
null,
|
|
822
|
+
102,
|
|
823
|
+
116,
|
|
824
|
+
121,
|
|
825
|
+
112
|
|
826
|
+
],
|
|
827
|
+
mimeType: "video/mp4"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
signature: [
|
|
831
|
+
null,
|
|
832
|
+
null,
|
|
833
|
+
null,
|
|
834
|
+
null,
|
|
835
|
+
102,
|
|
836
|
+
116,
|
|
837
|
+
121,
|
|
838
|
+
112,
|
|
839
|
+
105,
|
|
840
|
+
115,
|
|
841
|
+
111,
|
|
842
|
+
109
|
|
843
|
+
],
|
|
844
|
+
mimeType: "video/mp4"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
signature: [
|
|
848
|
+
null,
|
|
849
|
+
null,
|
|
850
|
+
null,
|
|
851
|
+
null,
|
|
852
|
+
102,
|
|
853
|
+
116,
|
|
854
|
+
121,
|
|
855
|
+
112,
|
|
856
|
+
109,
|
|
857
|
+
112,
|
|
858
|
+
52,
|
|
859
|
+
50
|
|
860
|
+
],
|
|
861
|
+
mimeType: "video/mp4"
|
|
862
|
+
}
|
|
863
|
+
],
|
|
864
|
+
webm: [{
|
|
865
|
+
signature: [
|
|
866
|
+
26,
|
|
867
|
+
69,
|
|
868
|
+
223,
|
|
869
|
+
163
|
|
870
|
+
],
|
|
871
|
+
mimeType: "video/webm"
|
|
872
|
+
}],
|
|
873
|
+
avi: [{
|
|
874
|
+
signature: [
|
|
875
|
+
82,
|
|
876
|
+
73,
|
|
877
|
+
70,
|
|
878
|
+
70,
|
|
879
|
+
null,
|
|
880
|
+
null,
|
|
881
|
+
null,
|
|
882
|
+
null,
|
|
883
|
+
65,
|
|
884
|
+
86,
|
|
885
|
+
73,
|
|
886
|
+
32
|
|
887
|
+
],
|
|
888
|
+
mimeType: "video/x-msvideo"
|
|
889
|
+
}],
|
|
890
|
+
mov: [{
|
|
891
|
+
signature: [
|
|
892
|
+
null,
|
|
893
|
+
null,
|
|
894
|
+
null,
|
|
895
|
+
null,
|
|
896
|
+
102,
|
|
897
|
+
116,
|
|
898
|
+
121,
|
|
899
|
+
112,
|
|
900
|
+
113,
|
|
901
|
+
116,
|
|
902
|
+
32,
|
|
903
|
+
32
|
|
904
|
+
],
|
|
905
|
+
mimeType: "video/quicktime"
|
|
906
|
+
}],
|
|
907
|
+
mkv: [{
|
|
908
|
+
signature: [
|
|
909
|
+
26,
|
|
910
|
+
69,
|
|
911
|
+
223,
|
|
912
|
+
163
|
|
913
|
+
],
|
|
914
|
+
mimeType: "video/x-matroska"
|
|
915
|
+
}],
|
|
916
|
+
docx: [{
|
|
917
|
+
signature: [
|
|
918
|
+
80,
|
|
919
|
+
75,
|
|
920
|
+
3,
|
|
921
|
+
4
|
|
922
|
+
],
|
|
923
|
+
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
924
|
+
}],
|
|
925
|
+
xlsx: [{
|
|
926
|
+
signature: [
|
|
927
|
+
80,
|
|
928
|
+
75,
|
|
929
|
+
3,
|
|
930
|
+
4
|
|
931
|
+
],
|
|
932
|
+
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
933
|
+
}],
|
|
934
|
+
pptx: [{
|
|
935
|
+
signature: [
|
|
936
|
+
80,
|
|
937
|
+
75,
|
|
938
|
+
3,
|
|
939
|
+
4
|
|
940
|
+
],
|
|
941
|
+
mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
942
|
+
}],
|
|
943
|
+
doc: [{
|
|
944
|
+
signature: [
|
|
945
|
+
208,
|
|
946
|
+
207,
|
|
947
|
+
17,
|
|
948
|
+
224,
|
|
949
|
+
161,
|
|
950
|
+
177,
|
|
951
|
+
26,
|
|
952
|
+
225
|
|
953
|
+
],
|
|
954
|
+
mimeType: "application/msword"
|
|
955
|
+
}],
|
|
956
|
+
xls: [{
|
|
957
|
+
signature: [
|
|
958
|
+
208,
|
|
959
|
+
207,
|
|
960
|
+
17,
|
|
961
|
+
224,
|
|
962
|
+
161,
|
|
963
|
+
177,
|
|
964
|
+
26,
|
|
965
|
+
225
|
|
966
|
+
],
|
|
967
|
+
mimeType: "application/vnd.ms-excel"
|
|
968
|
+
}],
|
|
969
|
+
ppt: [{
|
|
970
|
+
signature: [
|
|
971
|
+
208,
|
|
972
|
+
207,
|
|
973
|
+
17,
|
|
974
|
+
224,
|
|
975
|
+
161,
|
|
976
|
+
177,
|
|
977
|
+
26,
|
|
978
|
+
225
|
|
979
|
+
],
|
|
980
|
+
mimeType: "application/vnd.ms-powerpoint"
|
|
981
|
+
}]
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
984
|
+
* All possible format signatures for checking against actual file content
|
|
985
|
+
*/
|
|
986
|
+
static ALL_SIGNATURES = Object.entries(FileDetector.MAGIC_BYTES).flatMap(([ext, signatures]) => signatures.map((sig) => ({
|
|
987
|
+
ext,
|
|
988
|
+
...sig
|
|
989
|
+
})));
|
|
990
|
+
/**
|
|
991
|
+
* MIME type map for file extensions.
|
|
992
|
+
*
|
|
993
|
+
* Can be used to get the content type of file based on its extension.
|
|
994
|
+
* Feel free to add more mime types in your project!
|
|
995
|
+
*/
|
|
996
|
+
static mimeMap = {
|
|
997
|
+
json: "application/json",
|
|
998
|
+
txt: "text/plain",
|
|
999
|
+
html: "text/html",
|
|
1000
|
+
htm: "text/html",
|
|
1001
|
+
xml: "application/xml",
|
|
1002
|
+
csv: "text/csv",
|
|
1003
|
+
pdf: "application/pdf",
|
|
1004
|
+
md: "text/markdown",
|
|
1005
|
+
markdown: "text/markdown",
|
|
1006
|
+
rtf: "application/rtf",
|
|
1007
|
+
css: "text/css",
|
|
1008
|
+
js: "application/javascript",
|
|
1009
|
+
mjs: "application/javascript",
|
|
1010
|
+
ts: "application/typescript",
|
|
1011
|
+
jsx: "text/jsx",
|
|
1012
|
+
tsx: "text/tsx",
|
|
1013
|
+
zip: "application/zip",
|
|
1014
|
+
rar: "application/vnd.rar",
|
|
1015
|
+
"7z": "application/x-7z-compressed",
|
|
1016
|
+
tar: "application/x-tar",
|
|
1017
|
+
gz: "application/gzip",
|
|
1018
|
+
tgz: "application/gzip",
|
|
1019
|
+
png: "image/png",
|
|
1020
|
+
jpg: "image/jpeg",
|
|
1021
|
+
jpeg: "image/jpeg",
|
|
1022
|
+
gif: "image/gif",
|
|
1023
|
+
webp: "image/webp",
|
|
1024
|
+
svg: "image/svg+xml",
|
|
1025
|
+
bmp: "image/bmp",
|
|
1026
|
+
ico: "image/x-icon",
|
|
1027
|
+
tiff: "image/tiff",
|
|
1028
|
+
tif: "image/tiff",
|
|
1029
|
+
mp3: "audio/mpeg",
|
|
1030
|
+
wav: "audio/wav",
|
|
1031
|
+
ogg: "audio/ogg",
|
|
1032
|
+
m4a: "audio/mp4",
|
|
1033
|
+
aac: "audio/aac",
|
|
1034
|
+
flac: "audio/flac",
|
|
1035
|
+
mp4: "video/mp4",
|
|
1036
|
+
webm: "video/webm",
|
|
1037
|
+
avi: "video/x-msvideo",
|
|
1038
|
+
mov: "video/quicktime",
|
|
1039
|
+
wmv: "video/x-ms-wmv",
|
|
1040
|
+
flv: "video/x-flv",
|
|
1041
|
+
mkv: "video/x-matroska",
|
|
1042
|
+
doc: "application/msword",
|
|
1043
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1044
|
+
xls: "application/vnd.ms-excel",
|
|
1045
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1046
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
1047
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1048
|
+
woff: "font/woff",
|
|
1049
|
+
woff2: "font/woff2",
|
|
1050
|
+
ttf: "font/ttf",
|
|
1051
|
+
otf: "font/otf",
|
|
1052
|
+
eot: "application/vnd.ms-fontobject"
|
|
1053
|
+
};
|
|
1054
|
+
/**
|
|
1055
|
+
* Reverse MIME type map for looking up extensions from MIME types.
|
|
1056
|
+
* Prefers shorter, more common extensions when multiple exist.
|
|
1057
|
+
*/
|
|
1058
|
+
static reverseMimeMap = (() => {
|
|
1059
|
+
const reverse = {};
|
|
1060
|
+
for (const [ext, mimeType] of Object.entries(FileDetector.mimeMap)) if (!reverse[mimeType]) reverse[mimeType] = ext;
|
|
1061
|
+
return reverse;
|
|
1062
|
+
})();
|
|
1063
|
+
/**
|
|
1064
|
+
* Returns the file extension for a given MIME type.
|
|
1065
|
+
*
|
|
1066
|
+
* @param mimeType - The MIME type to look up
|
|
1067
|
+
* @returns The file extension (without dot), or "bin" if not found
|
|
1068
|
+
*
|
|
1069
|
+
* @example
|
|
1070
|
+
* ```typescript
|
|
1071
|
+
* const detector = alepha.inject(FileDetector);
|
|
1072
|
+
* const ext = detector.getExtensionFromMimeType("image/png"); // "png"
|
|
1073
|
+
* const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
|
|
1074
|
+
* ```
|
|
1075
|
+
*/
|
|
1076
|
+
getExtensionFromMimeType(mimeType) {
|
|
1077
|
+
return FileDetector.reverseMimeMap[mimeType] || "bin";
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Returns the content type of file based on its filename.
|
|
1081
|
+
*
|
|
1082
|
+
* @param filename - The filename to check
|
|
1083
|
+
* @returns The MIME type
|
|
1084
|
+
*
|
|
1085
|
+
* @example
|
|
1086
|
+
* ```typescript
|
|
1087
|
+
* const detector = alepha.inject(FileDetector);
|
|
1088
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
1089
|
+
* ```
|
|
1090
|
+
*/
|
|
1091
|
+
getContentType(filename) {
|
|
1092
|
+
const ext = filename.toLowerCase().split(".").pop() || "";
|
|
1093
|
+
return FileDetector.mimeMap[ext] || "application/octet-stream";
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Detects the file type by checking magic bytes against the stream content.
|
|
1097
|
+
*
|
|
1098
|
+
* @param stream - The readable stream to check
|
|
1099
|
+
* @param filename - The filename (used to get the extension)
|
|
1100
|
+
* @returns File type information including MIME type, extension, and verification status
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```typescript
|
|
1104
|
+
* const detector = alepha.inject(FileDetector);
|
|
1105
|
+
* const stream = createReadStream('image.png');
|
|
1106
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
1107
|
+
* console.log(result.mimeType); // 'image/png'
|
|
1108
|
+
* console.log(result.verified); // true if magic bytes match
|
|
1109
|
+
* ```
|
|
1110
|
+
*/
|
|
1111
|
+
async detectFileType(stream, filename) {
|
|
1112
|
+
const expectedMimeType = this.getContentType(filename);
|
|
1113
|
+
const lastDotIndex = filename.lastIndexOf(".");
|
|
1114
|
+
const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
|
|
1115
|
+
const { buffer, stream: newStream } = await this.peekBytes(stream, 16);
|
|
1116
|
+
const expectedSignatures = FileDetector.MAGIC_BYTES[ext];
|
|
1117
|
+
if (expectedSignatures) {
|
|
1118
|
+
for (const { signature, mimeType } of expectedSignatures) if (this.matchesSignature(buffer, signature)) return {
|
|
1119
|
+
mimeType,
|
|
1120
|
+
extension: ext,
|
|
1121
|
+
verified: true,
|
|
1122
|
+
stream: newStream
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
for (const { ext: detectedExt, signature, mimeType } of FileDetector.ALL_SIGNATURES) if (detectedExt !== ext && this.matchesSignature(buffer, signature)) return {
|
|
1126
|
+
mimeType,
|
|
1127
|
+
extension: detectedExt,
|
|
1128
|
+
verified: true,
|
|
1129
|
+
stream: newStream
|
|
1130
|
+
};
|
|
1131
|
+
return {
|
|
1132
|
+
mimeType: expectedMimeType,
|
|
1133
|
+
extension: ext,
|
|
1134
|
+
verified: false,
|
|
1135
|
+
stream: newStream
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
|
|
1140
|
+
* This approach reads the entire stream upfront to avoid complex async handling issues.
|
|
1141
|
+
*
|
|
1142
|
+
* @protected
|
|
1143
|
+
*/
|
|
1144
|
+
async peekBytes(stream, numBytes) {
|
|
1145
|
+
const chunks = [];
|
|
1146
|
+
for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1147
|
+
const allData = Buffer.concat(chunks);
|
|
1148
|
+
return {
|
|
1149
|
+
buffer: allData.subarray(0, numBytes),
|
|
1150
|
+
stream: Readable.from(allData)
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Checks if a buffer matches a magic byte signature.
|
|
1155
|
+
*
|
|
1156
|
+
* @protected
|
|
1157
|
+
*/
|
|
1158
|
+
matchesSignature(buffer, signature) {
|
|
1159
|
+
if (buffer.length < signature.length) return false;
|
|
1160
|
+
for (let i = 0; i < signature.length; i++) if (signature[i] !== null && buffer[i] !== signature[i]) return false;
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
//#endregion
|
|
1166
|
+
//#region ../../src/system/providers/NodeFileSystemProvider.ts
|
|
1167
|
+
/**
|
|
1168
|
+
* Node.js implementation of FileSystem interface.
|
|
1169
|
+
*
|
|
1170
|
+
* @example
|
|
1171
|
+
* ```typescript
|
|
1172
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1173
|
+
*
|
|
1174
|
+
* // Create from URL
|
|
1175
|
+
* const file1 = fs.createFile({ url: "file:///path/to/file.png" });
|
|
1176
|
+
*
|
|
1177
|
+
* // Create from Buffer
|
|
1178
|
+
* const file2 = fs.createFile({ buffer: Buffer.from("hello"), name: "hello.txt" });
|
|
1179
|
+
*
|
|
1180
|
+
* // Create from text
|
|
1181
|
+
* const file3 = fs.createFile({ text: "Hello, world!", name: "greeting.txt" });
|
|
1182
|
+
*
|
|
1183
|
+
* // File operations
|
|
1184
|
+
* await fs.mkdir("/tmp/mydir", { recursive: true });
|
|
1185
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt");
|
|
1186
|
+
* await fs.mv("/old/path.txt", "/new/path.txt");
|
|
1187
|
+
* const files = await fs.ls("/tmp");
|
|
1188
|
+
* await fs.rm("/tmp/file.txt");
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
1191
|
+
var NodeFileSystemProvider = class {
|
|
1192
|
+
detector = $inject(FileDetector);
|
|
1193
|
+
json = $inject(Json);
|
|
1194
|
+
join(...paths) {
|
|
1195
|
+
return join(...paths);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Creates a FileLike object from various sources.
|
|
1199
|
+
*
|
|
1200
|
+
* @param options - Options for creating the file
|
|
1201
|
+
* @returns A FileLike object
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* ```typescript
|
|
1205
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1206
|
+
*
|
|
1207
|
+
* // From URL
|
|
1208
|
+
* const file1 = fs.createFile({ url: "https://example.com/image.png" });
|
|
1209
|
+
*
|
|
1210
|
+
* // From Buffer
|
|
1211
|
+
* const file2 = fs.createFile({
|
|
1212
|
+
* buffer: Buffer.from("hello"),
|
|
1213
|
+
* name: "hello.txt",
|
|
1214
|
+
* type: "text/plain"
|
|
1215
|
+
* });
|
|
1216
|
+
*
|
|
1217
|
+
* // From text
|
|
1218
|
+
* const file3 = fs.createFile({ text: "Hello!", name: "greeting.txt" });
|
|
1219
|
+
*
|
|
1220
|
+
* // From stream with detection
|
|
1221
|
+
* const stream = createReadStream("/path/to/file.png");
|
|
1222
|
+
* const file4 = fs.createFile({ stream, name: "image.png" });
|
|
1223
|
+
* ```
|
|
1224
|
+
*/
|
|
1225
|
+
createFile(options) {
|
|
1226
|
+
if ("path" in options) {
|
|
1227
|
+
const path = options.path;
|
|
1228
|
+
const filename = path.split("/").pop() || "file";
|
|
1229
|
+
return this.createFileFromUrl(`file://${path}`, {
|
|
1230
|
+
type: options.type,
|
|
1231
|
+
name: options.name || filename
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
if ("url" in options) return this.createFileFromUrl(options.url, {
|
|
1235
|
+
type: options.type,
|
|
1236
|
+
name: options.name
|
|
1237
|
+
});
|
|
1238
|
+
if ("response" in options) {
|
|
1239
|
+
if (!options.response.body) throw new AlephaError("Response has no body stream");
|
|
1240
|
+
const res = options.response;
|
|
1241
|
+
const sizeHeader = res.headers.get("content-length");
|
|
1242
|
+
const size = sizeHeader ? parseInt(sizeHeader, 10) : void 0;
|
|
1243
|
+
let name = options.name;
|
|
1244
|
+
const contentDisposition = res.headers.get("content-disposition");
|
|
1245
|
+
if (contentDisposition && !name) {
|
|
1246
|
+
const match = contentDisposition.match(/filename="?([^"]+)"?/);
|
|
1247
|
+
if (match) name = match[1];
|
|
1248
|
+
}
|
|
1249
|
+
const type = options.type || res.headers.get("content-type") || void 0;
|
|
1250
|
+
return this.createFileFromStream(options.response.body, {
|
|
1251
|
+
type,
|
|
1252
|
+
name,
|
|
1253
|
+
size
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
if ("file" in options) return this.createFileFromWebFile(options.file, {
|
|
1257
|
+
type: options.type,
|
|
1258
|
+
name: options.name,
|
|
1259
|
+
size: options.size
|
|
1260
|
+
});
|
|
1261
|
+
if ("buffer" in options) return this.createFileFromBuffer(options.buffer, {
|
|
1262
|
+
type: options.type,
|
|
1263
|
+
name: options.name
|
|
1264
|
+
});
|
|
1265
|
+
if ("arrayBuffer" in options) return this.createFileFromBuffer(Buffer.from(options.arrayBuffer), {
|
|
1266
|
+
type: options.type,
|
|
1267
|
+
name: options.name
|
|
1268
|
+
});
|
|
1269
|
+
if ("text" in options) return this.createFileFromBuffer(Buffer.from(options.text, "utf-8"), {
|
|
1270
|
+
type: options.type || "text/plain",
|
|
1271
|
+
name: options.name || "file.txt"
|
|
1272
|
+
});
|
|
1273
|
+
if ("stream" in options) return this.createFileFromStream(options.stream, {
|
|
1274
|
+
type: options.type,
|
|
1275
|
+
name: options.name,
|
|
1276
|
+
size: options.size
|
|
1277
|
+
});
|
|
1278
|
+
throw new AlephaError("Invalid createFile options: no valid source provided");
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Removes a file or directory.
|
|
1282
|
+
*
|
|
1283
|
+
* @param path - The path to remove
|
|
1284
|
+
* @param options - Remove options
|
|
1285
|
+
*
|
|
1286
|
+
* @example
|
|
1287
|
+
* ```typescript
|
|
1288
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1289
|
+
*
|
|
1290
|
+
* // Remove a file
|
|
1291
|
+
* await fs.rm("/tmp/file.txt");
|
|
1292
|
+
*
|
|
1293
|
+
* // Remove a directory recursively
|
|
1294
|
+
* await fs.rm("/tmp/mydir", { recursive: true });
|
|
1295
|
+
*
|
|
1296
|
+
* // Remove with force (no error if doesn't exist)
|
|
1297
|
+
* await fs.rm("/tmp/maybe-exists.txt", { force: true });
|
|
1298
|
+
* ```
|
|
1299
|
+
*/
|
|
1300
|
+
async rm(path, options) {
|
|
1301
|
+
await rm(path, options);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Copies a file or directory.
|
|
1305
|
+
*
|
|
1306
|
+
* @param src - Source path
|
|
1307
|
+
* @param dest - Destination path
|
|
1308
|
+
* @param options - Copy options
|
|
1309
|
+
*
|
|
1310
|
+
* @example
|
|
1311
|
+
* ```typescript
|
|
1312
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1313
|
+
*
|
|
1314
|
+
* // Copy a file
|
|
1315
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt");
|
|
1316
|
+
*
|
|
1317
|
+
* // Copy a directory recursively
|
|
1318
|
+
* await fs.cp("/src/dir", "/dest/dir", { recursive: true });
|
|
1319
|
+
*
|
|
1320
|
+
* // Copy with force (overwrite existing)
|
|
1321
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt", { force: true });
|
|
1322
|
+
* ```
|
|
1323
|
+
*/
|
|
1324
|
+
async cp(src, dest, options) {
|
|
1325
|
+
if ((await stat(src)).isDirectory()) {
|
|
1326
|
+
if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
|
|
1327
|
+
await cp(src, dest, {
|
|
1328
|
+
recursive: true,
|
|
1329
|
+
force: options?.force ?? false
|
|
1330
|
+
});
|
|
1331
|
+
} else await copyFile(src, dest);
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Moves/renames a file or directory.
|
|
1335
|
+
*
|
|
1336
|
+
* @param src - Source path
|
|
1337
|
+
* @param dest - Destination path
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1342
|
+
*
|
|
1343
|
+
* // Move/rename a file
|
|
1344
|
+
* await fs.mv("/old/path.txt", "/new/path.txt");
|
|
1345
|
+
*
|
|
1346
|
+
* // Move a directory
|
|
1347
|
+
* await fs.mv("/old/dir", "/new/dir");
|
|
1348
|
+
* ```
|
|
1349
|
+
*/
|
|
1350
|
+
async mv(src, dest) {
|
|
1351
|
+
await rename(src, dest);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Creates a directory.
|
|
1355
|
+
*
|
|
1356
|
+
* @param path - The directory path to create
|
|
1357
|
+
* @param options - Mkdir options
|
|
1358
|
+
*
|
|
1359
|
+
* @example
|
|
1360
|
+
* ```typescript
|
|
1361
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1362
|
+
*
|
|
1363
|
+
* // Create a directory
|
|
1364
|
+
* await fs.mkdir("/tmp/mydir");
|
|
1365
|
+
*
|
|
1366
|
+
* // Create nested directories
|
|
1367
|
+
* await fs.mkdir("/tmp/path/to/dir", { recursive: true });
|
|
1368
|
+
*
|
|
1369
|
+
* // Create with specific permissions
|
|
1370
|
+
* await fs.mkdir("/tmp/mydir", { mode: 0o755 });
|
|
1371
|
+
* ```
|
|
1372
|
+
*/
|
|
1373
|
+
async mkdir(path, options) {
|
|
1374
|
+
await mkdir(path, options);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Lists files in a directory.
|
|
1378
|
+
*
|
|
1379
|
+
* @param path - The directory path to list
|
|
1380
|
+
* @param options - List options
|
|
1381
|
+
* @returns Array of filenames
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* ```typescript
|
|
1385
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1386
|
+
*
|
|
1387
|
+
* // List files in a directory
|
|
1388
|
+
* const files = await fs.ls("/tmp");
|
|
1389
|
+
* console.log(files); // ["file1.txt", "file2.txt", "subdir"]
|
|
1390
|
+
*
|
|
1391
|
+
* // List with hidden files
|
|
1392
|
+
* const allFiles = await fs.ls("/tmp", { hidden: true });
|
|
1393
|
+
*
|
|
1394
|
+
* // List recursively
|
|
1395
|
+
* const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
|
|
1396
|
+
* ```
|
|
1397
|
+
*/
|
|
1398
|
+
async ls(path, options) {
|
|
1399
|
+
const entries = await readdir(path);
|
|
1400
|
+
const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
|
|
1401
|
+
if (options?.recursive) {
|
|
1402
|
+
const allFiles = [];
|
|
1403
|
+
for (const entry of filteredEntries) {
|
|
1404
|
+
const fullPath = join(path, entry);
|
|
1405
|
+
if ((await stat(fullPath)).isDirectory()) {
|
|
1406
|
+
allFiles.push(entry);
|
|
1407
|
+
const subFiles = await this.ls(fullPath, options);
|
|
1408
|
+
allFiles.push(...subFiles.map((f) => join(entry, f)));
|
|
1409
|
+
} else allFiles.push(entry);
|
|
1410
|
+
}
|
|
1411
|
+
return allFiles;
|
|
1412
|
+
}
|
|
1413
|
+
return filteredEntries;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Checks if a file or directory exists.
|
|
1417
|
+
*
|
|
1418
|
+
* @param path - The path to check
|
|
1419
|
+
* @returns True if the path exists, false otherwise
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* ```typescript
|
|
1423
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1424
|
+
*
|
|
1425
|
+
* if (await fs.exists("/tmp/file.txt")) {
|
|
1426
|
+
* console.log("File exists");
|
|
1427
|
+
* }
|
|
1428
|
+
* ```
|
|
1429
|
+
*/
|
|
1430
|
+
async exists(path) {
|
|
1431
|
+
try {
|
|
1432
|
+
await access(path);
|
|
1433
|
+
return true;
|
|
1434
|
+
} catch {
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Reads the content of a file.
|
|
1440
|
+
*
|
|
1441
|
+
* @param path - The file path to read
|
|
1442
|
+
* @returns The file content as a Buffer
|
|
1443
|
+
*
|
|
1444
|
+
* @example
|
|
1445
|
+
* ```typescript
|
|
1446
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1447
|
+
*
|
|
1448
|
+
* const buffer = await fs.readFile("/tmp/file.txt");
|
|
1449
|
+
* console.log(buffer.toString("utf-8"));
|
|
1450
|
+
* ```
|
|
1451
|
+
*/
|
|
1452
|
+
async readFile(path) {
|
|
1453
|
+
return await readFile(path);
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Writes data to a file.
|
|
1457
|
+
*
|
|
1458
|
+
* @param path - The file path to write to
|
|
1459
|
+
* @param data - The data to write (Buffer or string)
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* ```typescript
|
|
1463
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1464
|
+
*
|
|
1465
|
+
* // Write string
|
|
1466
|
+
* await fs.writeFile("/tmp/file.txt", "Hello, world!");
|
|
1467
|
+
*
|
|
1468
|
+
* // Write Buffer
|
|
1469
|
+
* await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
|
|
1470
|
+
* ```
|
|
1471
|
+
*/
|
|
1472
|
+
async writeFile(path, data) {
|
|
1473
|
+
if (isFileLike(data)) {
|
|
1474
|
+
await writeFile(path, Readable.from(data.stream()));
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
await writeFile(path, data);
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Reads the content of a file as a string.
|
|
1481
|
+
*
|
|
1482
|
+
* @param path - The file path to read
|
|
1483
|
+
* @returns The file content as a string
|
|
1484
|
+
*
|
|
1485
|
+
* @example
|
|
1486
|
+
* ```typescript
|
|
1487
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1488
|
+
* const content = await fs.readTextFile("/tmp/file.txt");
|
|
1489
|
+
* ```
|
|
1490
|
+
*/
|
|
1491
|
+
async readTextFile(path) {
|
|
1492
|
+
return (await this.readFile(path)).toString("utf-8");
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Reads the content of a file as JSON.
|
|
1496
|
+
*
|
|
1497
|
+
* @param path - The file path to read
|
|
1498
|
+
* @returns The parsed JSON content
|
|
1499
|
+
*
|
|
1500
|
+
* @example
|
|
1501
|
+
* ```typescript
|
|
1502
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1503
|
+
* const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
|
|
1504
|
+
* ```
|
|
1505
|
+
*/
|
|
1506
|
+
async readJsonFile(path) {
|
|
1507
|
+
const text = await this.readTextFile(path);
|
|
1508
|
+
return this.json.parse(text);
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Creates a FileLike object from a Web File.
|
|
1512
|
+
*
|
|
1513
|
+
* @protected
|
|
1514
|
+
*/
|
|
1515
|
+
createFileFromWebFile(source, options = {}) {
|
|
1516
|
+
const name = options.name ?? source.name;
|
|
1517
|
+
return {
|
|
1518
|
+
name,
|
|
1519
|
+
type: options.type ?? (source.type || this.detector.getContentType(name)),
|
|
1520
|
+
size: options.size ?? source.size ?? 0,
|
|
1521
|
+
lastModified: source.lastModified || Date.now(),
|
|
1522
|
+
stream: () => source.stream(),
|
|
1523
|
+
arrayBuffer: async () => {
|
|
1524
|
+
return await source.arrayBuffer();
|
|
1525
|
+
},
|
|
1526
|
+
text: async () => {
|
|
1527
|
+
return await source.text();
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Creates a FileLike object from a Buffer.
|
|
1533
|
+
*
|
|
1534
|
+
* @protected
|
|
1535
|
+
*/
|
|
1536
|
+
createFileFromBuffer(source, options = {}) {
|
|
1537
|
+
const name = options.name ?? "file";
|
|
1538
|
+
return {
|
|
1539
|
+
name,
|
|
1540
|
+
type: options.type ?? this.detector.getContentType(options.name ?? name),
|
|
1541
|
+
size: source.byteLength,
|
|
1542
|
+
lastModified: Date.now(),
|
|
1543
|
+
stream: () => Readable.from(source),
|
|
1544
|
+
arrayBuffer: async () => {
|
|
1545
|
+
return this.bufferToArrayBuffer(source);
|
|
1546
|
+
},
|
|
1547
|
+
text: async () => {
|
|
1548
|
+
return source.toString("utf-8");
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Creates a FileLike object from a stream.
|
|
1554
|
+
*
|
|
1555
|
+
* @protected
|
|
1556
|
+
*/
|
|
1557
|
+
createFileFromStream(source, options = {}) {
|
|
1558
|
+
let buffer = null;
|
|
1559
|
+
return {
|
|
1560
|
+
name: options.name ?? "file",
|
|
1561
|
+
type: options.type ?? this.detector.getContentType(options.name ?? "file"),
|
|
1562
|
+
size: options.size ?? 0,
|
|
1563
|
+
lastModified: Date.now(),
|
|
1564
|
+
stream: () => source,
|
|
1565
|
+
_buffer: null,
|
|
1566
|
+
arrayBuffer: async () => {
|
|
1567
|
+
buffer ??= await this.streamToBuffer(source);
|
|
1568
|
+
return this.bufferToArrayBuffer(buffer);
|
|
1569
|
+
},
|
|
1570
|
+
text: async () => {
|
|
1571
|
+
buffer ??= await this.streamToBuffer(source);
|
|
1572
|
+
return buffer.toString("utf-8");
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Creates a FileLike object from a URL.
|
|
1578
|
+
*
|
|
1579
|
+
* @protected
|
|
1580
|
+
*/
|
|
1581
|
+
createFileFromUrl(url, options = {}) {
|
|
1582
|
+
const parsedUrl = new URL(url);
|
|
1583
|
+
const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
|
|
1584
|
+
let buffer = null;
|
|
1585
|
+
return {
|
|
1586
|
+
name: filename,
|
|
1587
|
+
type: options.type ?? this.detector.getContentType(filename),
|
|
1588
|
+
size: 0,
|
|
1589
|
+
lastModified: Date.now(),
|
|
1590
|
+
stream: () => this.createStreamFromUrl(url),
|
|
1591
|
+
arrayBuffer: async () => {
|
|
1592
|
+
buffer ??= await this.loadFromUrl(url);
|
|
1593
|
+
return this.bufferToArrayBuffer(buffer);
|
|
1594
|
+
},
|
|
1595
|
+
text: async () => {
|
|
1596
|
+
buffer ??= await this.loadFromUrl(url);
|
|
1597
|
+
return buffer.toString("utf-8");
|
|
1598
|
+
},
|
|
1599
|
+
filepath: url
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Gets a streaming response from a URL.
|
|
1604
|
+
*
|
|
1605
|
+
* @protected
|
|
1606
|
+
*/
|
|
1607
|
+
getStreamingResponse(url) {
|
|
1608
|
+
const stream = new PassThrough();
|
|
1609
|
+
fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
|
|
1610
|
+
return stream;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Loads data from a URL.
|
|
1614
|
+
*
|
|
1615
|
+
* @protected
|
|
1616
|
+
*/
|
|
1617
|
+
async loadFromUrl(url) {
|
|
1618
|
+
const parsedUrl = new URL(url);
|
|
1619
|
+
if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
|
|
1620
|
+
else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
|
1621
|
+
const response = await fetch(url);
|
|
1622
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
1623
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1624
|
+
return Buffer.from(arrayBuffer);
|
|
1625
|
+
} else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Creates a stream from a URL.
|
|
1629
|
+
*
|
|
1630
|
+
* @protected
|
|
1631
|
+
*/
|
|
1632
|
+
createStreamFromUrl(url) {
|
|
1633
|
+
const parsedUrl = new URL(url);
|
|
1634
|
+
if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
|
|
1635
|
+
else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
|
|
1636
|
+
else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Converts a stream-like object to a Buffer.
|
|
1640
|
+
*
|
|
1641
|
+
* @protected
|
|
1642
|
+
*/
|
|
1643
|
+
async streamToBuffer(streamLike) {
|
|
1644
|
+
const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
|
|
1645
|
+
return new Promise((resolve, reject) => {
|
|
1646
|
+
const buffer = [];
|
|
1647
|
+
stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
|
|
1648
|
+
stream.on("end", () => resolve(Buffer.concat(buffer)));
|
|
1649
|
+
stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Converts a Node.js Buffer to an ArrayBuffer.
|
|
1654
|
+
*
|
|
1655
|
+
* @protected
|
|
1656
|
+
*/
|
|
1657
|
+
bufferToArrayBuffer(buffer) {
|
|
1658
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region ../../src/system/providers/NodeShellProvider.ts
|
|
1664
|
+
/**
|
|
1665
|
+
* Node.js implementation of ShellProvider.
|
|
1666
|
+
*
|
|
1667
|
+
* Executes shell commands using Node.js child_process module.
|
|
1668
|
+
* Supports binary resolution from node_modules/.bin for local packages.
|
|
1669
|
+
*/
|
|
1670
|
+
var NodeShellProvider = class {
|
|
1671
|
+
log = $logger();
|
|
1672
|
+
fs = $inject(FileSystemProvider);
|
|
1673
|
+
/**
|
|
1674
|
+
* Run a shell command or binary.
|
|
1675
|
+
*/
|
|
1676
|
+
async run(command, options = {}) {
|
|
1677
|
+
const { resolve = false, capture = false, root, env } = options;
|
|
1678
|
+
const cwd = root ?? process.cwd();
|
|
1679
|
+
this.log.debug(`Shell: ${command}`, {
|
|
1680
|
+
cwd,
|
|
1681
|
+
resolve,
|
|
1682
|
+
capture
|
|
1683
|
+
});
|
|
1684
|
+
let executable;
|
|
1685
|
+
let args;
|
|
1686
|
+
if (resolve) {
|
|
1687
|
+
const [bin, ...rest] = command.split(" ");
|
|
1688
|
+
executable = await this.resolveExecutable(bin, cwd);
|
|
1689
|
+
args = rest;
|
|
1690
|
+
} else [executable, ...args] = command.split(" ");
|
|
1691
|
+
if (capture) return this.execCapture(command, {
|
|
1692
|
+
cwd,
|
|
1693
|
+
env
|
|
1694
|
+
});
|
|
1695
|
+
return this.execInherit(executable, args, {
|
|
1696
|
+
cwd,
|
|
1697
|
+
env
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Execute command with inherited stdio (streams to terminal).
|
|
1702
|
+
*/
|
|
1703
|
+
async execInherit(executable, args, options) {
|
|
1704
|
+
const proc = spawn(executable, args, {
|
|
1705
|
+
stdio: "inherit",
|
|
1706
|
+
cwd: options.cwd,
|
|
1707
|
+
env: {
|
|
1708
|
+
...process.env,
|
|
1709
|
+
...options.env
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
return new Promise((resolve, reject) => {
|
|
1713
|
+
proc.on("exit", (code) => {
|
|
1714
|
+
if (code === 0 || code === null) resolve("");
|
|
1715
|
+
else reject(new AlephaError(`Command exited with code ${code}`));
|
|
1716
|
+
});
|
|
1717
|
+
proc.on("error", reject);
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Execute command and capture stdout.
|
|
1722
|
+
*/
|
|
1723
|
+
execCapture(command, options) {
|
|
1724
|
+
return new Promise((resolve, reject) => {
|
|
1725
|
+
exec(command, {
|
|
1726
|
+
cwd: options.cwd,
|
|
1727
|
+
env: {
|
|
1728
|
+
...process.env,
|
|
1729
|
+
LOG_FORMAT: "pretty",
|
|
1730
|
+
...options.env
|
|
1731
|
+
}
|
|
1732
|
+
}, (err, stdout) => {
|
|
1733
|
+
if (err) {
|
|
1734
|
+
err.stdout = stdout;
|
|
1735
|
+
reject(err);
|
|
1736
|
+
} else resolve(stdout);
|
|
1737
|
+
});
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Resolve executable path from node_modules/.bin.
|
|
1742
|
+
*
|
|
1743
|
+
* Search order:
|
|
1744
|
+
* 1. Local: node_modules/.bin/
|
|
1745
|
+
* 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
|
|
1746
|
+
* 3. Monorepo: Walk up to 3 parent directories
|
|
1747
|
+
*/
|
|
1748
|
+
async resolveExecutable(name, root) {
|
|
1749
|
+
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
1750
|
+
let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
|
|
1751
|
+
if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
|
|
1752
|
+
if (!execPath) {
|
|
1753
|
+
let parentDir = this.fs.join(root, "..");
|
|
1754
|
+
for (let i = 0; i < 3; i++) {
|
|
1755
|
+
execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
|
|
1756
|
+
if (execPath) break;
|
|
1757
|
+
parentDir = this.fs.join(parentDir, "..");
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
|
|
1761
|
+
return execPath;
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Check if executable exists at path.
|
|
1765
|
+
*/
|
|
1766
|
+
async findExecutable(root, relativePath) {
|
|
1767
|
+
const fullPath = this.fs.join(root, relativePath);
|
|
1768
|
+
if (await this.fs.exists(fullPath)) return fullPath;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Check if a command is installed and available in the system PATH.
|
|
1772
|
+
*/
|
|
1773
|
+
isInstalled(command) {
|
|
1774
|
+
return new Promise((resolve) => {
|
|
1775
|
+
exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
//#endregion
|
|
1781
|
+
//#region ../../src/system/providers/ShellProvider.ts
|
|
1782
|
+
/**
|
|
1783
|
+
* Abstract provider for executing shell commands and binaries.
|
|
1784
|
+
*
|
|
1785
|
+
* Implementations:
|
|
1786
|
+
* - `NodeShellProvider` - Real shell execution using Node.js child_process
|
|
1787
|
+
* - `MemoryShellProvider` - In-memory mock for testing
|
|
1788
|
+
*
|
|
1789
|
+
* @example
|
|
1790
|
+
* ```typescript
|
|
1791
|
+
* class MyService {
|
|
1792
|
+
* protected readonly shell = $inject(ShellProvider);
|
|
1793
|
+
*
|
|
1794
|
+
* async build() {
|
|
1795
|
+
* // Run shell command directly
|
|
1796
|
+
* await this.shell.run("yarn install");
|
|
1797
|
+
*
|
|
1798
|
+
* // Run local binary with resolution
|
|
1799
|
+
* await this.shell.run("vite build", { resolve: true });
|
|
1800
|
+
*
|
|
1801
|
+
* // Capture output
|
|
1802
|
+
* const output = await this.shell.run("echo hello", { capture: true });
|
|
1803
|
+
* }
|
|
1804
|
+
* }
|
|
1805
|
+
* ```
|
|
1806
|
+
*/
|
|
1807
|
+
var ShellProvider = class {};
|
|
1808
|
+
|
|
1809
|
+
//#endregion
|
|
1810
|
+
//#region ../../src/system/index.ts
|
|
1811
|
+
/**
|
|
1812
|
+
* | type | quality | stability |
|
|
1813
|
+
* |------|---------|-----------|
|
|
1814
|
+
* | tooling | standard | stable |
|
|
1815
|
+
*
|
|
1816
|
+
* System-level abstractions for portable code across runtimes.
|
|
1817
|
+
*
|
|
1818
|
+
* **Features:**
|
|
1819
|
+
* - File system operations (read, write, exists, etc.)
|
|
1820
|
+
* - Shell command execution
|
|
1821
|
+
* - File type detection and MIME utilities
|
|
1822
|
+
* - Memory implementations for testing
|
|
1823
|
+
*
|
|
1824
|
+
* @module alepha.system
|
|
1825
|
+
*/
|
|
1826
|
+
const AlephaSystem = $module({
|
|
1827
|
+
name: "alepha.system",
|
|
1828
|
+
primitives: [],
|
|
1829
|
+
services: [
|
|
1830
|
+
FileDetector,
|
|
1831
|
+
FileSystemProvider,
|
|
1832
|
+
MemoryFileSystemProvider,
|
|
1833
|
+
NodeFileSystemProvider,
|
|
1834
|
+
ShellProvider,
|
|
1835
|
+
MemoryShellProvider,
|
|
1836
|
+
NodeShellProvider
|
|
1837
|
+
],
|
|
1838
|
+
register: (alepha) => alepha.with({
|
|
1839
|
+
optional: true,
|
|
1840
|
+
provide: FileSystemProvider,
|
|
1841
|
+
use: NodeFileSystemProvider
|
|
1842
|
+
}).with({
|
|
1843
|
+
optional: true,
|
|
1844
|
+
provide: ShellProvider,
|
|
1845
|
+
use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
|
|
1846
|
+
})
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
//#endregion
|
|
14
1850
|
//#region ../../src/core/constants/KIND.ts
|
|
15
1851
|
/**
|
|
16
1852
|
* Used for identifying primitives.
|
|
@@ -79,7 +1915,7 @@ $atom$1[KIND] = "atom";
|
|
|
79
1915
|
* Options can be overridden via vite.config.ts or CLI flags.
|
|
80
1916
|
*/
|
|
81
1917
|
const buildOptions = $atom$1({
|
|
82
|
-
name: "alepha.build.options",
|
|
1918
|
+
name: "alepha.cli.build.options",
|
|
83
1919
|
description: "Build configuration options",
|
|
84
1920
|
schema: t.object({
|
|
85
1921
|
stats: t.optional(t.boolean({ default: false })),
|
|
@@ -102,6 +1938,187 @@ const buildOptions = $atom$1({
|
|
|
102
1938
|
default: {}
|
|
103
1939
|
});
|
|
104
1940
|
|
|
1941
|
+
//#endregion
|
|
1942
|
+
//#region ../../src/cli/atoms/appEntryOptions.ts
|
|
1943
|
+
const appEntryOptions = $atom({
|
|
1944
|
+
name: "alepha.cli.appEntry.options",
|
|
1945
|
+
schema: t.object({
|
|
1946
|
+
server: t.optional(t.text()),
|
|
1947
|
+
browser: t.optional(t.text()),
|
|
1948
|
+
style: t.optional(t.text())
|
|
1949
|
+
}),
|
|
1950
|
+
default: {}
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
//#endregion
|
|
1954
|
+
//#region ../../src/cli/providers/AppEntryProvider.ts
|
|
1955
|
+
/**
|
|
1956
|
+
* Service for locating entry files in Alepha projects.
|
|
1957
|
+
*
|
|
1958
|
+
* Originally in alepha/vite, moved to CLI to avoid cli -> vite dependency.
|
|
1959
|
+
*/
|
|
1960
|
+
var AppEntryProvider = class {
|
|
1961
|
+
fs = $inject(FileSystemProvider);
|
|
1962
|
+
options = $use(appEntryOptions);
|
|
1963
|
+
serverEntries = [
|
|
1964
|
+
"main.server.ts",
|
|
1965
|
+
"main.server.tsx",
|
|
1966
|
+
"main.ts",
|
|
1967
|
+
"main.tsx"
|
|
1968
|
+
];
|
|
1969
|
+
browserEntries = [
|
|
1970
|
+
"main.browser.ts",
|
|
1971
|
+
"main.browser.tsx",
|
|
1972
|
+
"main.ts",
|
|
1973
|
+
"main.tsx"
|
|
1974
|
+
];
|
|
1975
|
+
styleEntries = [
|
|
1976
|
+
"main.css",
|
|
1977
|
+
"styles.css",
|
|
1978
|
+
"style.css"
|
|
1979
|
+
];
|
|
1980
|
+
/**
|
|
1981
|
+
* Get application entry points.
|
|
1982
|
+
*
|
|
1983
|
+
* Server entry is required, an error is thrown if not found.
|
|
1984
|
+
* Browser entry is optional.
|
|
1985
|
+
*
|
|
1986
|
+
* It will first check for custom entries in options, see appEntryOptions.
|
|
1987
|
+
*/
|
|
1988
|
+
async getAppEntry(root) {
|
|
1989
|
+
const appEntry = {
|
|
1990
|
+
root,
|
|
1991
|
+
server: ""
|
|
1992
|
+
};
|
|
1993
|
+
if (this.options.server) {
|
|
1994
|
+
if (!await this.fs.exists(this.fs.join(root, this.options.server))) throw new AlephaError(`Custom server entry "${this.options.server}" not found.`);
|
|
1995
|
+
appEntry.server = this.options.server;
|
|
1996
|
+
}
|
|
1997
|
+
if (this.options.browser) {
|
|
1998
|
+
if (!await this.fs.exists(this.fs.join(root, this.options.browser))) throw new AlephaError(`Custom browser entry "${this.options.browser}" not found.`);
|
|
1999
|
+
appEntry.browser = this.options.browser;
|
|
2000
|
+
}
|
|
2001
|
+
if (this.options.style) {
|
|
2002
|
+
if (!await this.fs.exists(this.fs.join(root, this.options.style))) throw new AlephaError(`Custom style entry "${this.options.style}" not found.`);
|
|
2003
|
+
appEntry.style = this.options.style;
|
|
2004
|
+
}
|
|
2005
|
+
const srcFiles = await this.fs.ls(this.fs.join(root, "src"));
|
|
2006
|
+
if (!appEntry.server) {
|
|
2007
|
+
for (const entry of this.serverEntries) if (srcFiles.includes(entry)) {
|
|
2008
|
+
appEntry.server = this.fs.join("src", entry);
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (!appEntry.server) throw new AlephaError("No server entry found. Please, add a main.server.ts file in the src/ directory or configure a custom entry in alepha.config.ts.");
|
|
2013
|
+
if (!appEntry.browser) {
|
|
2014
|
+
for (const entry of this.browserEntries) if (srcFiles.includes(entry)) {
|
|
2015
|
+
appEntry.browser = this.fs.join("src", entry);
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (!appEntry.style) {
|
|
2020
|
+
for (const entry of this.styleEntries) if (srcFiles.includes(entry)) {
|
|
2021
|
+
appEntry.style = this.fs.join("src", entry);
|
|
2022
|
+
break;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return appEntry;
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
//#endregion
|
|
2030
|
+
//#region ../../src/cli/services/ViteUtils.ts
|
|
2031
|
+
var ViteUtils = class {
|
|
2032
|
+
fs = $inject(FileSystemProvider);
|
|
2033
|
+
viteDevServer;
|
|
2034
|
+
generateIndexHtml(entry) {
|
|
2035
|
+
const style = entry.style;
|
|
2036
|
+
const browser = entry.browser ?? entry.server;
|
|
2037
|
+
return `
|
|
2038
|
+
<!DOCTYPE html>
|
|
2039
|
+
<html lang="en">
|
|
2040
|
+
<head>
|
|
2041
|
+
<meta charset="UTF-8" />
|
|
2042
|
+
<title>App</title>
|
|
2043
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
2044
|
+
${style ? `<link rel="stylesheet" href="/${style}" />` : ""}
|
|
2045
|
+
</head>
|
|
2046
|
+
<body>
|
|
2047
|
+
<div id="root"></div>
|
|
2048
|
+
<script type="module" src="/${browser}"><\/script>
|
|
2049
|
+
</body>
|
|
2050
|
+
</html>
|
|
2051
|
+
`.trim();
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* We need to close the Vite dev server after build is done.
|
|
2055
|
+
*/
|
|
2056
|
+
onReady = $hook({
|
|
2057
|
+
on: "ready",
|
|
2058
|
+
priority: "last",
|
|
2059
|
+
handler: async () => {
|
|
2060
|
+
await this.viteDevServer?.close();
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
onStop = $hook({
|
|
2064
|
+
on: "stop",
|
|
2065
|
+
handler: async () => {
|
|
2066
|
+
await this.viteDevServer?.close();
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
async runAlepha(opts) {
|
|
2070
|
+
const { createServer } = await importVite();
|
|
2071
|
+
process.env.NODE_ENV = opts.mode;
|
|
2072
|
+
process.env.ALEPHA_CLI_IMPORT = "true";
|
|
2073
|
+
process.env.LOG_LEVEL ??= "warn";
|
|
2074
|
+
/**
|
|
2075
|
+
* 01/26 Vite 7
|
|
2076
|
+
* "runnerImport" doesn't work as expected here. (e.g. build docs fail)
|
|
2077
|
+
* -> We still use devServer and ssrLoadModule for now.
|
|
2078
|
+
* -> This is clearly a bad stuff, we need to find better way.
|
|
2079
|
+
*/
|
|
2080
|
+
this.viteDevServer = await createServer({
|
|
2081
|
+
server: { middlewareMode: true },
|
|
2082
|
+
appType: "custom",
|
|
2083
|
+
logLevel: "silent"
|
|
2084
|
+
});
|
|
2085
|
+
await this.viteDevServer.ssrLoadModule(opts.entry.server);
|
|
2086
|
+
const alepha = globalThis.__alepha;
|
|
2087
|
+
if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
|
|
2088
|
+
return alepha;
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
|
|
2092
|
+
//#endregion
|
|
2093
|
+
//#region ../../src/cli/providers/ViteBuildProvider.ts
|
|
2094
|
+
var ViteBuildProvider = class {
|
|
2095
|
+
alepha;
|
|
2096
|
+
appEntry;
|
|
2097
|
+
viteUtils = $inject(ViteUtils);
|
|
2098
|
+
async init(opts) {
|
|
2099
|
+
const alepha = await this.viteUtils.runAlepha({
|
|
2100
|
+
entry: opts.entry,
|
|
2101
|
+
mode: "production"
|
|
2102
|
+
});
|
|
2103
|
+
this.alepha = alepha;
|
|
2104
|
+
this.appEntry = opts.entry;
|
|
2105
|
+
return alepha;
|
|
2106
|
+
}
|
|
2107
|
+
hasClient() {
|
|
2108
|
+
if (!this.alepha) throw new AlephaError("ViteBuildProvider not initialized");
|
|
2109
|
+
try {
|
|
2110
|
+
this.alepha.inject("ReactServerProvider");
|
|
2111
|
+
return true;
|
|
2112
|
+
} catch {
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
generateIndexHtml() {
|
|
2117
|
+
if (!this.appEntry) throw new AlephaError("ViteBuildProvider not initialized");
|
|
2118
|
+
return this.viteUtils.generateIndexHtml(this.appEntry);
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
|
|
105
2122
|
//#endregion
|
|
106
2123
|
//#region ../../src/cli/services/AlephaCliUtils.ts
|
|
107
2124
|
/**
|
|
@@ -117,36 +2134,24 @@ var AlephaCliUtils = class {
|
|
|
117
2134
|
log = $logger();
|
|
118
2135
|
fs = $inject(FileSystemProvider);
|
|
119
2136
|
envUtils = $inject(EnvUtils);
|
|
2137
|
+
boot = $inject(AppEntryProvider);
|
|
2138
|
+
shell = $inject(ShellProvider);
|
|
2139
|
+
viteUtils = $inject(ViteUtils);
|
|
120
2140
|
/**
|
|
121
2141
|
* Execute a command with inherited stdio.
|
|
2142
|
+
*
|
|
2143
|
+
* @param command - The command to execute
|
|
2144
|
+
* @param options.root - Working directory
|
|
2145
|
+
* @param options.env - Additional environment variables
|
|
2146
|
+
* @param options.global - If true, run command directly without resolving from node_modules
|
|
122
2147
|
*/
|
|
123
2148
|
async exec(command, options = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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);
|
|
2149
|
+
await this.shell.run(command, {
|
|
2150
|
+
root: options.root,
|
|
2151
|
+
env: options.env,
|
|
2152
|
+
resolve: !options.global,
|
|
2153
|
+
capture: false
|
|
2154
|
+
});
|
|
150
2155
|
}
|
|
151
2156
|
/**
|
|
152
2157
|
* Write a configuration file to node_modules/.alepha directory.
|
|
@@ -159,25 +2164,14 @@ var AlephaCliUtils = class {
|
|
|
159
2164
|
this.log.debug(`Config file written: ${path}`);
|
|
160
2165
|
return path;
|
|
161
2166
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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}`);
|
|
2167
|
+
async loadAlephaFromServerEntryFile(opts) {
|
|
2168
|
+
let entry;
|
|
2169
|
+
if ("root" in opts) entry = await this.boot.getAppEntry(opts.root);
|
|
2170
|
+
else entry = opts.entry;
|
|
2171
|
+
return await this.viteUtils.runAlepha({
|
|
2172
|
+
entry,
|
|
2173
|
+
mode: opts.mode
|
|
2174
|
+
});
|
|
181
2175
|
}
|
|
182
2176
|
/**
|
|
183
2177
|
* Generate JavaScript code for Drizzle entities export.
|
|
@@ -205,12 +2199,39 @@ ${models.map((it) => `export const ${it} = models["${it}"];`).join("\n")}
|
|
|
205
2199
|
async exists(root, path) {
|
|
206
2200
|
return this.fs.exists(this.fs.join(root, path));
|
|
207
2201
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
2202
|
+
/**
|
|
2203
|
+
* Check if a command is installed and available in the system PATH.
|
|
2204
|
+
*/
|
|
2205
|
+
isInstalledAsync(cmd) {
|
|
2206
|
+
return this.shell.isInstalled(cmd);
|
|
211
2207
|
}
|
|
212
2208
|
};
|
|
213
2209
|
|
|
2210
|
+
//#endregion
|
|
2211
|
+
//#region ../../package.json
|
|
2212
|
+
var devDependencies = {
|
|
2213
|
+
"@biomejs/biome": "^2.3.13",
|
|
2214
|
+
"@electric-sql/pglite": "^0.3.15",
|
|
2215
|
+
"@faker-js/faker": "^10.2.0",
|
|
2216
|
+
"@testing-library/dom": "^10.4.1",
|
|
2217
|
+
"@testing-library/react": "^16.3.2",
|
|
2218
|
+
"@types/node": "^25.1.0",
|
|
2219
|
+
"@types/nodemailer": "^7.0.9",
|
|
2220
|
+
"@types/react": "^19.2.10",
|
|
2221
|
+
"@types/react-dom": "^19.2.3",
|
|
2222
|
+
"@types/ws": "^8.18.1",
|
|
2223
|
+
"cron-schedule": "^6.0.0",
|
|
2224
|
+
"jose": "^6.1.3",
|
|
2225
|
+
"jsdom": "^27.4.0",
|
|
2226
|
+
"openid-client": "^6.8.1",
|
|
2227
|
+
"prom-client": "^15.1.3",
|
|
2228
|
+
"react": "^19.2.4",
|
|
2229
|
+
"react-dom": "^19.2.4",
|
|
2230
|
+
"swagger-ui-dist": "^5.31.0",
|
|
2231
|
+
"tsdown": "^0.20.1",
|
|
2232
|
+
"vitest": "^4.0.18"
|
|
2233
|
+
};
|
|
2234
|
+
|
|
214
2235
|
//#endregion
|
|
215
2236
|
//#region ../../src/cli/version.ts
|
|
216
2237
|
const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8"));
|
|
@@ -234,11 +2255,8 @@ var PackageManagerUtils = class {
|
|
|
234
2255
|
/**
|
|
235
2256
|
* Detect the package manager used in the project.
|
|
236
2257
|
*/
|
|
237
|
-
async getPackageManager(root,
|
|
238
|
-
if (
|
|
239
|
-
if (flags?.pnpm) return "pnpm";
|
|
240
|
-
if (flags?.npm) return "npm";
|
|
241
|
-
if (flags?.bun) return "bun";
|
|
2258
|
+
async getPackageManager(root, pm) {
|
|
2259
|
+
if (pm) return pm;
|
|
242
2260
|
if (this.alepha.isBun()) return "bun";
|
|
243
2261
|
if (await this.fs.exists(this.fs.join(root, "bun.lock"))) return "bun";
|
|
244
2262
|
if (await this.fs.exists(this.fs.join(root, "yarn.lock"))) return "yarn";
|
|
@@ -246,6 +2264,46 @@ var PackageManagerUtils = class {
|
|
|
246
2264
|
return "npm";
|
|
247
2265
|
}
|
|
248
2266
|
/**
|
|
2267
|
+
* Detect workspace context when inside a monorepo package.
|
|
2268
|
+
*
|
|
2269
|
+
* Checks if we're inside a workspace package (e.g., packages/my-pkg or apps/my-app)
|
|
2270
|
+
* by looking 2 levels up for workspace indicators like lockfiles and config files.
|
|
2271
|
+
*
|
|
2272
|
+
* @param root - The current package directory
|
|
2273
|
+
* @returns Workspace context with root path, PM, and config presence
|
|
2274
|
+
*/
|
|
2275
|
+
async getWorkspaceContext(root) {
|
|
2276
|
+
const workspaceRoot = this.fs.join(root, "..", "..");
|
|
2277
|
+
const [hasYarnLock, hasPnpmLock, hasNpmLock, hasBunLock] = await Promise.all([
|
|
2278
|
+
this.fs.exists(this.fs.join(workspaceRoot, "yarn.lock")),
|
|
2279
|
+
this.fs.exists(this.fs.join(workspaceRoot, "pnpm-lock.yaml")),
|
|
2280
|
+
this.fs.exists(this.fs.join(workspaceRoot, "package-lock.json")),
|
|
2281
|
+
this.fs.exists(this.fs.join(workspaceRoot, "bun.lock"))
|
|
2282
|
+
]);
|
|
2283
|
+
const [hasBiome, hasEditorConfig, hasTsConfig, hasWorkspacePackageJson] = await Promise.all([
|
|
2284
|
+
this.fs.exists(this.fs.join(workspaceRoot, "biome.json")),
|
|
2285
|
+
this.fs.exists(this.fs.join(workspaceRoot, ".editorconfig")),
|
|
2286
|
+
this.fs.exists(this.fs.join(workspaceRoot, "tsconfig.json")),
|
|
2287
|
+
this.fs.exists(this.fs.join(workspaceRoot, "package.json"))
|
|
2288
|
+
]);
|
|
2289
|
+
const isPackage = (hasYarnLock || hasPnpmLock || hasNpmLock || hasBunLock) && hasWorkspacePackageJson;
|
|
2290
|
+
let packageManager = null;
|
|
2291
|
+
if (hasYarnLock) packageManager = "yarn";
|
|
2292
|
+
else if (hasPnpmLock) packageManager = "pnpm";
|
|
2293
|
+
else if (hasBunLock) packageManager = "bun";
|
|
2294
|
+
else if (hasNpmLock) packageManager = "npm";
|
|
2295
|
+
return {
|
|
2296
|
+
isPackage,
|
|
2297
|
+
workspaceRoot: isPackage ? workspaceRoot : null,
|
|
2298
|
+
packageManager,
|
|
2299
|
+
config: {
|
|
2300
|
+
biomeJson: hasBiome,
|
|
2301
|
+
editorconfig: hasEditorConfig,
|
|
2302
|
+
tsconfigJson: hasTsConfig
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
249
2307
|
* Get the install command for a package.
|
|
250
2308
|
*/
|
|
251
2309
|
async getInstallCommand(root, packageName, dev = true) {
|
|
@@ -283,17 +2341,33 @@ var PackageManagerUtils = class {
|
|
|
283
2341
|
return this.hasDependency(root, "expo");
|
|
284
2342
|
}
|
|
285
2343
|
/**
|
|
2344
|
+
* Check if React is present in the project.
|
|
2345
|
+
*/
|
|
2346
|
+
async hasReact(root) {
|
|
2347
|
+
return this.hasDependency(root, "react");
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
286
2350
|
* Install a dependency if it's missing from the project.
|
|
2351
|
+
* Optionally checks workspace root for the dependency in monorepo setups.
|
|
287
2352
|
*/
|
|
288
2353
|
async ensureDependency(root, packageName, options = {}) {
|
|
289
|
-
const { dev = true } = options;
|
|
2354
|
+
const { dev = true, checkWorkspace = false } = options;
|
|
290
2355
|
if (await this.hasDependency(root, packageName)) {
|
|
291
2356
|
this.log.debug(`Dependency '${packageName}' is already installed`);
|
|
292
2357
|
return;
|
|
293
2358
|
}
|
|
2359
|
+
if (checkWorkspace) {
|
|
2360
|
+
const workspace = await this.getWorkspaceContext(root);
|
|
2361
|
+
if (workspace.workspaceRoot) {
|
|
2362
|
+
if (await this.hasDependency(workspace.workspaceRoot, packageName)) {
|
|
2363
|
+
this.log.debug(`Dependency '${packageName}' is already installed in workspace root`);
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
294
2368
|
const cmd = await this.getInstallCommand(root, packageName, dev);
|
|
295
2369
|
if (options.run) await options.run(cmd, {
|
|
296
|
-
alias: `
|
|
2370
|
+
alias: `add ${packageName}`,
|
|
297
2371
|
root
|
|
298
2372
|
});
|
|
299
2373
|
else if (options.exec) {
|
|
@@ -368,21 +2442,26 @@ var PackageManagerUtils = class {
|
|
|
368
2442
|
await this.writePackageJson(root, content);
|
|
369
2443
|
return content;
|
|
370
2444
|
}
|
|
371
|
-
const packageJson
|
|
2445
|
+
const packageJson = await this.readPackageJson(root);
|
|
372
2446
|
const newContent = this.generatePackageJsonContent(modes);
|
|
373
|
-
packageJson
|
|
374
|
-
packageJson
|
|
375
|
-
packageJson
|
|
376
|
-
packageJson
|
|
377
|
-
Object.assign(packageJson
|
|
378
|
-
Object.assign(packageJson
|
|
379
|
-
Object.assign(packageJson
|
|
380
|
-
await this.writePackageJson(root, packageJson
|
|
381
|
-
return packageJson
|
|
2447
|
+
packageJson.type = "module";
|
|
2448
|
+
packageJson.dependencies ??= {};
|
|
2449
|
+
packageJson.devDependencies ??= {};
|
|
2450
|
+
packageJson.scripts ??= {};
|
|
2451
|
+
Object.assign(packageJson.dependencies, newContent.dependencies);
|
|
2452
|
+
Object.assign(packageJson.devDependencies, newContent.devDependencies);
|
|
2453
|
+
Object.assign(packageJson.scripts, newContent.scripts);
|
|
2454
|
+
await this.writePackageJson(root, packageJson);
|
|
2455
|
+
return packageJson;
|
|
382
2456
|
}
|
|
383
2457
|
generatePackageJsonContent(modes) {
|
|
2458
|
+
const alephaDeps = devDependencies;
|
|
384
2459
|
const dependencies = { alepha: `^${version}` };
|
|
385
|
-
const devDependencies = {};
|
|
2460
|
+
const devDependencies$1 = {};
|
|
2461
|
+
if (!modes.isPackage) {
|
|
2462
|
+
devDependencies$1["@biomejs/biome"] = alephaDeps["@biomejs/biome"];
|
|
2463
|
+
if (modes.test) devDependencies$1.vitest = alephaDeps.vitest;
|
|
2464
|
+
}
|
|
386
2465
|
const scripts = {
|
|
387
2466
|
dev: "alepha dev",
|
|
388
2467
|
build: "alepha build",
|
|
@@ -390,20 +2469,20 @@ var PackageManagerUtils = class {
|
|
|
390
2469
|
typecheck: "alepha typecheck",
|
|
391
2470
|
verify: "alepha verify"
|
|
392
2471
|
};
|
|
393
|
-
if (modes.
|
|
2472
|
+
if (modes.test) scripts.test = "vitest run";
|
|
2473
|
+
if (modes.ui) {
|
|
394
2474
|
dependencies["@alepha/ui"] = `^${version}`;
|
|
395
|
-
modes.
|
|
2475
|
+
modes.react = true;
|
|
396
2476
|
}
|
|
397
|
-
if (modes.
|
|
398
|
-
dependencies
|
|
399
|
-
dependencies
|
|
400
|
-
|
|
401
|
-
devDependencies["@types/react"] = "^19.2.0";
|
|
2477
|
+
if (modes.react) {
|
|
2478
|
+
dependencies.react = alephaDeps.react;
|
|
2479
|
+
dependencies["react-dom"] = alephaDeps["react-dom"];
|
|
2480
|
+
devDependencies$1["@types/react"] = alephaDeps["@types/react"];
|
|
402
2481
|
}
|
|
403
2482
|
return {
|
|
404
2483
|
type: "module",
|
|
405
2484
|
dependencies,
|
|
406
|
-
devDependencies,
|
|
2485
|
+
devDependencies: devDependencies$1,
|
|
407
2486
|
scripts
|
|
408
2487
|
};
|
|
409
2488
|
}
|
|
@@ -416,87 +2495,20 @@ var PackageManagerUtils = class {
|
|
|
416
2495
|
};
|
|
417
2496
|
|
|
418
2497
|
//#endregion
|
|
419
|
-
//#region ../../src/cli/
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
}),
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
`.trim();
|
|
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
|
-
};
|
|
2498
|
+
//#region ../../src/cli/templates/agentMd.ts
|
|
2499
|
+
const agentMd = (type, options = {}) => {
|
|
2500
|
+
const { react = false, projectName = "my-app" } = options;
|
|
2501
|
+
const header = type === "claude" ? `# CLAUDE.md
|
|
453
2502
|
|
|
454
|
-
|
|
455
|
-
//#region ../../src/cli/assets/biomeJson.ts
|
|
456
|
-
const biomeJson = `
|
|
457
|
-
{
|
|
458
|
-
"$schema": "https://biomejs.dev/schemas/latest/schema.json",
|
|
459
|
-
"vcs": {
|
|
460
|
-
"enabled": true,
|
|
461
|
-
"clientKind": "git"
|
|
462
|
-
},
|
|
463
|
-
"files": {
|
|
464
|
-
"ignoreUnknown": true,
|
|
465
|
-
"includes": ["**", "!node_modules", "!dist"]
|
|
466
|
-
},
|
|
467
|
-
"formatter": {
|
|
468
|
-
"enabled": true,
|
|
469
|
-
"useEditorconfig": true
|
|
470
|
-
},
|
|
471
|
-
"linter": {
|
|
472
|
-
"enabled": true,
|
|
473
|
-
"rules": {
|
|
474
|
-
"recommended": true
|
|
475
|
-
},
|
|
476
|
-
"domains": {
|
|
477
|
-
"react": "recommended"
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
"assist": {
|
|
481
|
-
"actions": {
|
|
482
|
-
"source": {
|
|
483
|
-
"organizeImports": "on"
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
`.trim();
|
|
2503
|
+
This file provides guidance to Claude Code when working with this Alepha project.` : `# AGENTS.md
|
|
489
2504
|
|
|
490
|
-
|
|
491
|
-
//#region ../../src/cli/assets/claudeMd.ts
|
|
492
|
-
const claudeMd = (options = {}) => {
|
|
493
|
-
const { react = false, projectName = "my-app" } = options;
|
|
2505
|
+
This file provides guidance to AI coding assistants when working with this Alepha project.`;
|
|
494
2506
|
const reactSection = react ? `
|
|
495
2507
|
## React & Frontend
|
|
496
2508
|
|
|
497
2509
|
### Pages with \`$page\`
|
|
498
2510
|
\`\`\`tsx
|
|
499
|
-
import { $page } from "
|
|
2511
|
+
import { $page } from "alepha/react/router";
|
|
500
2512
|
import { $client } from "alepha/server/links";
|
|
501
2513
|
import type { UserController } from "./UserController.ts";
|
|
502
2514
|
|
|
@@ -522,9 +2534,9 @@ class AppRouter {
|
|
|
522
2534
|
|
|
523
2535
|
### React Hooks
|
|
524
2536
|
\`\`\`typescript
|
|
525
|
-
import { useAlepha, useClient, useStore, useAction, useInject } from "
|
|
526
|
-
import { useRouter, useActive } from "
|
|
527
|
-
import { useForm } from "
|
|
2537
|
+
import { useAlepha, useClient, useStore, useAction, useInject } from "alepha/react";
|
|
2538
|
+
import { useRouter, useActive } from "alepha/react/router";
|
|
2539
|
+
import { useForm } from "alepha/react/form";
|
|
528
2540
|
\`\`\`
|
|
529
2541
|
|
|
530
2542
|
- \`useClient<Controller>()\` - Type-safe API calls
|
|
@@ -549,8 +2561,8 @@ ${projectName}/
|
|
|
549
2561
|
│ │ ├── AppRouter.ts # Routes with $page
|
|
550
2562
|
│ │ └── index.ts # Web module definition with $module
|
|
551
2563
|
│ ├── main.server.ts # Server entry
|
|
552
|
-
│
|
|
553
|
-
|
|
2564
|
+
│ ├── main.browser.ts # Browser entry (React only)
|
|
2565
|
+
│ └── main.css # CSS entry (React only)
|
|
554
2566
|
├── package.json
|
|
555
2567
|
└── tsconfig.json
|
|
556
2568
|
\`\`\`
|
|
@@ -569,9 +2581,7 @@ ${projectName}/
|
|
|
569
2581
|
└── tsconfig.json
|
|
570
2582
|
\`\`\`
|
|
571
2583
|
`;
|
|
572
|
-
return
|
|
573
|
-
|
|
574
|
-
This file provides guidance to Claude Code when working with this Alepha project.
|
|
2584
|
+
return `${header}
|
|
575
2585
|
|
|
576
2586
|
## Overview
|
|
577
2587
|
|
|
@@ -585,16 +2595,18 @@ This is an **Alepha** project - a convention-driven TypeScript framework for typ
|
|
|
585
2595
|
|
|
586
2596
|
## Rules
|
|
587
2597
|
|
|
588
|
-
- Use TypeScript strict mode
|
|
2598
|
+
- Use TypeScript strict mode, always check types (\`alepha typecheck\`)
|
|
589
2599
|
- Use Biome for formatting (\`alepha lint\`)
|
|
590
|
-
- Use Vitest for testing
|
|
591
|
-
- One file = one class
|
|
2600
|
+
- Use Vitest for testing (\`alepha test\`)
|
|
2601
|
+
- One file = one class, multiple interfaces/types allowed
|
|
592
2602
|
- Primitives are class properties (except \`$entity\`, \`$atom\`)
|
|
593
2603
|
- No decorators, no Express/Fastify patterns
|
|
594
2604
|
- No manual instantiation - use dependency injection
|
|
595
2605
|
- Use \`protected\` instead of \`private\` for class members
|
|
596
2606
|
- Import with file extensions: \`import { User } from "./User.ts"\`
|
|
597
2607
|
- Use \`t\` from Alepha for schemas (not Zod)
|
|
2608
|
+
- Prefer \`t.text()\` over \`t.string()\` for user input (has default max length, auto-trim, supports lowercase option)
|
|
2609
|
+
- One file = one schema (schemas/createUserSchema.ts)
|
|
598
2610
|
|
|
599
2611
|
## Project Structure
|
|
600
2612
|
${projectStructure}
|
|
@@ -637,17 +2649,12 @@ export const userEntity = $entity({
|
|
|
637
2649
|
id: db.primaryKey(),
|
|
638
2650
|
email: t.email(),
|
|
639
2651
|
createdAt: db.createdAt(),
|
|
640
|
-
updatedAt: db.updatedAt(),
|
|
641
2652
|
}),
|
|
642
2653
|
indexes: [{ column: "email", unique: true }],
|
|
643
2654
|
});
|
|
644
2655
|
|
|
645
2656
|
class UserService {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
async findById(id: string) {
|
|
649
|
-
return this.repo.findById(id);
|
|
650
|
-
}
|
|
2657
|
+
userRepository = $repository(userEntity);
|
|
651
2658
|
}
|
|
652
2659
|
\`\`\`
|
|
653
2660
|
|
|
@@ -657,11 +2664,6 @@ import { $inject } from "alepha";
|
|
|
657
2664
|
|
|
658
2665
|
class OrderService {
|
|
659
2666
|
userService = $inject(UserService); // Within same module
|
|
660
|
-
|
|
661
|
-
async createOrder(userId: string) {
|
|
662
|
-
const user = await this.userService.findById(userId);
|
|
663
|
-
// ...
|
|
664
|
-
}
|
|
665
2667
|
}
|
|
666
2668
|
|
|
667
2669
|
// Cross-module: use $client instead of $inject
|
|
@@ -725,7 +2727,7 @@ ${reactSection}
|
|
|
725
2727
|
| \`$bucket\` | \`alepha/bucket\` | File storage |
|
|
726
2728
|
| \`$issuer\` | \`alepha/security\` | JWT tokens |
|
|
727
2729
|
| \`$command\` | \`alepha/command\` | CLI commands |${react ? `
|
|
728
|
-
| \`$page\` |
|
|
2730
|
+
| \`$page\` | \`alepha/react/router\` | React pages with SSR |
|
|
729
2731
|
| \`$atom\` | \`alepha\` | Global state |` : ""}
|
|
730
2732
|
|
|
731
2733
|
## Testing
|
|
@@ -773,15 +2775,92 @@ alepha test # Run tests (if applicable)
|
|
|
773
2775
|
alepha build # Build the project
|
|
774
2776
|
\`\`\`
|
|
775
2777
|
|
|
776
|
-
## Documentation
|
|
2778
|
+
## Documentation
|
|
2779
|
+
|
|
2780
|
+
- Full docs: https://alepha.dev/llms.txt
|
|
2781
|
+
- Detailed docs: https://alepha.dev/llms-full.txt
|
|
2782
|
+
|
|
2783
|
+
## Source Code Access
|
|
2784
|
+
|
|
2785
|
+
Full framework source available at \`node_modules/alepha/src/\`.
|
|
2786
|
+
Read primitives directly when you need implementation details.
|
|
2787
|
+
`.trim();
|
|
2788
|
+
};
|
|
2789
|
+
|
|
2790
|
+
//#endregion
|
|
2791
|
+
//#region ../../src/cli/templates/apiHelloControllerTs.ts
|
|
2792
|
+
const apiHelloControllerTs = () => `
|
|
2793
|
+
import { t } from "alepha";
|
|
2794
|
+
import { $action } from "alepha/server";
|
|
2795
|
+
|
|
2796
|
+
export class HelloController {
|
|
2797
|
+
hello = $action({
|
|
2798
|
+
path: "/hello",
|
|
2799
|
+
schema: {
|
|
2800
|
+
response: t.object({
|
|
2801
|
+
message: t.string(),
|
|
2802
|
+
}),
|
|
2803
|
+
},
|
|
2804
|
+
handler: () => ({
|
|
2805
|
+
message: "Hello, Alepha!",
|
|
2806
|
+
}),
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
`.trim();
|
|
2810
|
+
|
|
2811
|
+
//#endregion
|
|
2812
|
+
//#region ../../src/cli/templates/apiIndexTs.ts
|
|
2813
|
+
const apiIndexTs = (options = {}) => {
|
|
2814
|
+
const { appName = "app" } = options;
|
|
2815
|
+
return `
|
|
2816
|
+
import { $module } from "alepha";
|
|
2817
|
+
import { HelloController } from "./controllers/HelloController.ts";
|
|
777
2818
|
|
|
778
|
-
|
|
779
|
-
|
|
2819
|
+
export const ApiModule = $module({
|
|
2820
|
+
name: "${appName}.api",
|
|
2821
|
+
services: [HelloController],
|
|
2822
|
+
});
|
|
780
2823
|
`.trim();
|
|
781
2824
|
};
|
|
782
2825
|
|
|
783
2826
|
//#endregion
|
|
784
|
-
//#region ../../src/cli/
|
|
2827
|
+
//#region ../../src/cli/templates/biomeJson.ts
|
|
2828
|
+
const biomeJson = () => `
|
|
2829
|
+
{
|
|
2830
|
+
"$schema": "https://biomejs.dev/schemas/latest/schema.json",
|
|
2831
|
+
"vcs": {
|
|
2832
|
+
"enabled": true,
|
|
2833
|
+
"clientKind": "git"
|
|
2834
|
+
},
|
|
2835
|
+
"files": {
|
|
2836
|
+
"ignoreUnknown": true,
|
|
2837
|
+
"includes": ["**", "!node_modules", "!dist"]
|
|
2838
|
+
},
|
|
2839
|
+
"formatter": {
|
|
2840
|
+
"enabled": true,
|
|
2841
|
+
"useEditorconfig": true
|
|
2842
|
+
},
|
|
2843
|
+
"linter": {
|
|
2844
|
+
"enabled": true,
|
|
2845
|
+
"rules": {
|
|
2846
|
+
"recommended": true
|
|
2847
|
+
},
|
|
2848
|
+
"domains": {
|
|
2849
|
+
"react": "recommended"
|
|
2850
|
+
}
|
|
2851
|
+
},
|
|
2852
|
+
"assist": {
|
|
2853
|
+
"actions": {
|
|
2854
|
+
"source": {
|
|
2855
|
+
"organizeImports": "on"
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
`.trim();
|
|
2861
|
+
|
|
2862
|
+
//#endregion
|
|
2863
|
+
//#region ../../src/cli/templates/dummySpecTs.ts
|
|
785
2864
|
const dummySpecTs = () => `
|
|
786
2865
|
import { test, expect } from "vitest";
|
|
787
2866
|
|
|
@@ -791,8 +2870,8 @@ test("dummy test", () => {
|
|
|
791
2870
|
`.trim();
|
|
792
2871
|
|
|
793
2872
|
//#endregion
|
|
794
|
-
//#region ../../src/cli/
|
|
795
|
-
const editorconfig = `
|
|
2873
|
+
//#region ../../src/cli/templates/editorconfig.ts
|
|
2874
|
+
const editorconfig = () => `
|
|
796
2875
|
# https://editorconfig.org
|
|
797
2876
|
|
|
798
2877
|
root = true
|
|
@@ -807,23 +2886,48 @@ indent_size = 2
|
|
|
807
2886
|
`.trim();
|
|
808
2887
|
|
|
809
2888
|
//#endregion
|
|
810
|
-
//#region ../../src/cli/
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
2889
|
+
//#region ../../src/cli/templates/gitignore.ts
|
|
2890
|
+
const gitignore = () => `
|
|
2891
|
+
# Dependencies
|
|
2892
|
+
node_modules/
|
|
2893
|
+
|
|
2894
|
+
# Build outputs
|
|
2895
|
+
dist/
|
|
2896
|
+
.vite/
|
|
2897
|
+
|
|
2898
|
+
# Environment files
|
|
2899
|
+
.env
|
|
2900
|
+
.env.*
|
|
2901
|
+
!.env.example
|
|
2902
|
+
|
|
2903
|
+
# IDE
|
|
2904
|
+
.idea/
|
|
2905
|
+
*.swp
|
|
2906
|
+
*.swo
|
|
2907
|
+
|
|
2908
|
+
# OS
|
|
2909
|
+
.DS_Store
|
|
2910
|
+
Thumbs.db
|
|
2911
|
+
|
|
2912
|
+
# Logs
|
|
2913
|
+
*.log
|
|
2914
|
+
logs/
|
|
2915
|
+
|
|
2916
|
+
# Test coverage
|
|
2917
|
+
coverage/
|
|
2918
|
+
|
|
2919
|
+
# Yarn
|
|
2920
|
+
.yarn/*
|
|
2921
|
+
!.yarn/patches
|
|
2922
|
+
!.yarn/plugins
|
|
2923
|
+
!.yarn/releases
|
|
2924
|
+
!.yarn/sdks
|
|
2925
|
+
!.yarn/versions
|
|
2926
|
+
.pnp.*
|
|
823
2927
|
`.trim();
|
|
824
2928
|
|
|
825
2929
|
//#endregion
|
|
826
|
-
//#region ../../src/cli/
|
|
2930
|
+
//#region ../../src/cli/templates/mainBrowserTs.ts
|
|
827
2931
|
const mainBrowserTs = () => `
|
|
828
2932
|
import { Alepha, run } from "alepha";
|
|
829
2933
|
import { WebModule } from "./web/index.ts";
|
|
@@ -836,58 +2940,115 @@ run(alepha);
|
|
|
836
2940
|
`.trim();
|
|
837
2941
|
|
|
838
2942
|
//#endregion
|
|
839
|
-
//#region ../../src/cli/
|
|
2943
|
+
//#region ../../src/cli/templates/mainCss.ts
|
|
2944
|
+
const mainCss = (options = {}) => {
|
|
2945
|
+
if (options.ui) return `@import "@alepha/ui/styles";`;
|
|
2946
|
+
return `
|
|
2947
|
+
* {
|
|
2948
|
+
box-sizing: border-box;
|
|
2949
|
+
margin: 0;
|
|
2950
|
+
padding: 0;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
html,
|
|
2954
|
+
body {
|
|
2955
|
+
height: 100%;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
body {
|
|
2959
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
2960
|
+
"Helvetica Neue", Arial, sans-serif;
|
|
2961
|
+
line-height: 1.5;
|
|
2962
|
+
-webkit-font-smoothing: antialiased;
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
#root {
|
|
2966
|
+
height: 100%;
|
|
2967
|
+
}
|
|
2968
|
+
`.trim();
|
|
2969
|
+
};
|
|
2970
|
+
|
|
2971
|
+
//#endregion
|
|
2972
|
+
//#region ../../src/cli/templates/mainServerTs.ts
|
|
840
2973
|
const mainServerTs = (options = {}) => {
|
|
841
|
-
const { react = false } = options;
|
|
2974
|
+
const { api = false, react = false } = options;
|
|
2975
|
+
const imports = [];
|
|
2976
|
+
const withs = [];
|
|
2977
|
+
if (api) {
|
|
2978
|
+
imports.push(`import { ApiModule } from "./api/index.ts";`);
|
|
2979
|
+
withs.push(`alepha.with(ApiModule);`);
|
|
2980
|
+
}
|
|
2981
|
+
if (react) {
|
|
2982
|
+
imports.push(`import { WebModule } from "./web/index.ts";`);
|
|
2983
|
+
withs.push(`alepha.with(WebModule);`);
|
|
2984
|
+
}
|
|
842
2985
|
return `
|
|
843
2986
|
import { Alepha, run } from "alepha";
|
|
844
|
-
|
|
845
|
-
${react ? `import { WebModule } from "./web/index.ts";\n` : ""}
|
|
2987
|
+
${imports.length > 0 ? `${imports.join("\n")}\n` : ""}
|
|
846
2988
|
const alepha = Alepha.create();
|
|
2989
|
+
${withs.length > 0 ? `\n${withs.join("\n")}` : ""}
|
|
847
2990
|
|
|
848
|
-
alepha.with(ApiModule);
|
|
849
|
-
${react ? `alepha.with(WebModule);\n` : ""}
|
|
850
2991
|
run(alepha);
|
|
851
2992
|
`.trim();
|
|
852
2993
|
};
|
|
853
2994
|
|
|
854
2995
|
//#endregion
|
|
855
|
-
//#region ../../src/cli/
|
|
856
|
-
const tsconfigJson = `
|
|
2996
|
+
//#region ../../src/cli/templates/tsconfigJson.ts
|
|
2997
|
+
const tsconfigJson = () => `
|
|
857
2998
|
{
|
|
858
2999
|
"extends": "alepha/tsconfig.base"
|
|
859
3000
|
}
|
|
860
3001
|
`.trim();
|
|
861
3002
|
|
|
862
3003
|
//#endregion
|
|
863
|
-
//#region ../../src/cli/
|
|
864
|
-
const webAppRouterTs = () =>
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
import
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
3004
|
+
//#region ../../src/cli/templates/webAppRouterTs.ts
|
|
3005
|
+
const webAppRouterTs = (options) => {
|
|
3006
|
+
const imports = [];
|
|
3007
|
+
const classMembers = [];
|
|
3008
|
+
if (options.ui) imports.push("import { $ui } from \"@alepha/ui\";");
|
|
3009
|
+
imports.push("import { $page } from \"alepha/react/router\";");
|
|
3010
|
+
if (options.api) {
|
|
3011
|
+
imports.push("import { $client } from \"alepha/server/links\";");
|
|
3012
|
+
imports.push("import type { HelloController } from \"../api/controllers/HelloController.ts\";");
|
|
3013
|
+
classMembers.push(" api = $client<HelloController>();");
|
|
3014
|
+
}
|
|
3015
|
+
if (options.ui) {
|
|
3016
|
+
classMembers.push(" ui = $ui();");
|
|
3017
|
+
classMembers.push(` layout = $page({
|
|
3018
|
+
parent: this.ui.root,
|
|
3019
|
+
children: () => [this.home],
|
|
3020
|
+
});`);
|
|
3021
|
+
}
|
|
3022
|
+
if (options.api) classMembers.push(` home = $page({
|
|
873
3023
|
path: "/",
|
|
874
3024
|
lazy: () => import("./components/Hello.tsx"),
|
|
875
3025
|
loader: () => this.api.hello(),
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
|
|
3026
|
+
});`);
|
|
3027
|
+
else classMembers.push(` home = $page({
|
|
3028
|
+
path: "/",
|
|
3029
|
+
lazy: () => import("./components/Hello.tsx"),
|
|
3030
|
+
});`);
|
|
3031
|
+
return `${imports.join("\n")}
|
|
3032
|
+
|
|
3033
|
+
export class AppRouter {
|
|
3034
|
+
${classMembers.join("\n\n")}
|
|
3035
|
+
}`;
|
|
3036
|
+
};
|
|
879
3037
|
|
|
880
3038
|
//#endregion
|
|
881
|
-
//#region ../../src/cli/
|
|
882
|
-
const webHelloComponentTsx = () => `
|
|
3039
|
+
//#region ../../src/cli/templates/webHelloComponentTsx.ts
|
|
3040
|
+
const webHelloComponentTsx = () => `import { useState } from "react";
|
|
3041
|
+
|
|
883
3042
|
interface Props {
|
|
884
|
-
message
|
|
3043
|
+
message?: string;
|
|
885
3044
|
}
|
|
886
3045
|
|
|
887
3046
|
const Hello = (props: Props) => {
|
|
3047
|
+
const [message, setMessage] = useState(props.message ?? "");
|
|
888
3048
|
return (
|
|
889
3049
|
<div>
|
|
890
|
-
<h1>{
|
|
3050
|
+
<h1>{message}</h1>
|
|
3051
|
+
<input value={message} onChange={e => setMessage(e.target.value)} />
|
|
891
3052
|
<p>Edit this component in src/web/components/Hello.tsx</p>
|
|
892
3053
|
</div>
|
|
893
3054
|
);
|
|
@@ -897,7 +3058,7 @@ export default Hello;
|
|
|
897
3058
|
`.trim();
|
|
898
3059
|
|
|
899
3060
|
//#endregion
|
|
900
|
-
//#region ../../src/cli/
|
|
3061
|
+
//#region ../../src/cli/templates/webIndexTs.ts
|
|
901
3062
|
const webIndexTs = (options = {}) => {
|
|
902
3063
|
const { appName = "app" } = options;
|
|
903
3064
|
return `
|
|
@@ -926,6 +3087,7 @@ var ProjectScaffolder = class {
|
|
|
926
3087
|
log = $logger();
|
|
927
3088
|
fs = $inject(FileSystemProvider);
|
|
928
3089
|
pm = $inject(PackageManagerUtils);
|
|
3090
|
+
utils = $inject(AlephaCliUtils);
|
|
929
3091
|
/**
|
|
930
3092
|
* Get the app name from the directory name.
|
|
931
3093
|
*
|
|
@@ -942,69 +3104,99 @@ var ProjectScaffolder = class {
|
|
|
942
3104
|
*/
|
|
943
3105
|
async ensureConfig(root, opts) {
|
|
944
3106
|
const tasks = [];
|
|
3107
|
+
const force = opts.force ?? false;
|
|
3108
|
+
const checkWorkspace = opts.checkWorkspace ?? false;
|
|
945
3109
|
if (opts.packageJson) tasks.push(this.pm.ensurePackageJson(root, typeof opts.packageJson === "boolean" ? {} : opts.packageJson).then(() => {}));
|
|
946
|
-
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root));
|
|
947
|
-
if (opts.
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
3110
|
+
if (opts.tsconfigJson) tasks.push(this.ensureTsConfig(root, { force }));
|
|
3111
|
+
if (opts.biomeJson) tasks.push(this.ensureBiomeConfig(root, {
|
|
3112
|
+
force,
|
|
3113
|
+
checkWorkspace
|
|
3114
|
+
}));
|
|
3115
|
+
if (opts.editorconfig) tasks.push(this.ensureEditorConfig(root, {
|
|
3116
|
+
force,
|
|
3117
|
+
checkWorkspace
|
|
3118
|
+
}));
|
|
3119
|
+
if (opts.agentMd) tasks.push(this.ensureAgentMd(root, {
|
|
3120
|
+
...opts.agentMd,
|
|
3121
|
+
force
|
|
3122
|
+
}));
|
|
951
3123
|
await Promise.all(tasks);
|
|
952
3124
|
}
|
|
953
|
-
async ensureTsConfig(root) {
|
|
954
|
-
if (await this.existsInParents(root, "tsconfig.json")) return;
|
|
955
|
-
await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson);
|
|
3125
|
+
async ensureTsConfig(root, opts = {}) {
|
|
3126
|
+
if (!opts.force && await this.existsInParents(root, "tsconfig.json")) return;
|
|
3127
|
+
await this.fs.writeFile(this.fs.join(root, "tsconfig.json"), tsconfigJson());
|
|
3128
|
+
}
|
|
3129
|
+
async ensureBiomeConfig(root, opts = {}) {
|
|
3130
|
+
if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, "biome.json")) return;
|
|
3131
|
+
await this.ensureFile(root, "biome.json", biomeJson(), opts.force);
|
|
956
3132
|
}
|
|
957
|
-
async
|
|
958
|
-
await this.
|
|
3133
|
+
async ensureEditorConfig(root, opts = {}) {
|
|
3134
|
+
if (!opts.force && opts.checkWorkspace && await this.existsInParents(root, ".editorconfig")) return;
|
|
3135
|
+
await this.ensureFile(root, ".editorconfig", editorconfig(), opts.force);
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Ensure git repository is initialized with .gitignore.
|
|
3139
|
+
*
|
|
3140
|
+
* @returns true if git was initialized, false if already exists or git unavailable
|
|
3141
|
+
*/
|
|
3142
|
+
async ensureGitRepo(root, opts = {}) {
|
|
3143
|
+
const gitDir = this.fs.join(root, ".git");
|
|
3144
|
+
if (!opts.force && await this.fs.exists(gitDir)) return false;
|
|
3145
|
+
if (!await this.utils.isInstalledAsync("git")) return false;
|
|
3146
|
+
await this.utils.exec("git init", {
|
|
3147
|
+
root,
|
|
3148
|
+
global: true
|
|
3149
|
+
});
|
|
3150
|
+
await this.ensureFile(root, ".gitignore", gitignore(), opts.force);
|
|
3151
|
+
return true;
|
|
959
3152
|
}
|
|
960
|
-
async
|
|
961
|
-
|
|
3153
|
+
async ensureAgentMd(root, options) {
|
|
3154
|
+
const filename = options.type === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
3155
|
+
await this.ensureFile(root, filename, agentMd(options.type, options), options.force);
|
|
962
3156
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
3157
|
+
/**
|
|
3158
|
+
* Ensure src/main.server.ts exists with correct module imports.
|
|
3159
|
+
*/
|
|
3160
|
+
async ensureMainServerTs(root, opts = {}) {
|
|
3161
|
+
const srcDir = this.fs.join(root, "src");
|
|
3162
|
+
await this.fs.mkdir(srcDir, { recursive: true });
|
|
3163
|
+
await this.ensureFile(srcDir, "main.server.ts", mainServerTs({
|
|
3164
|
+
api: opts.api,
|
|
3165
|
+
react: opts.react
|
|
3166
|
+
}), opts.force);
|
|
966
3167
|
}
|
|
967
3168
|
/**
|
|
968
|
-
* Ensure
|
|
3169
|
+
* Ensure API module structure exists.
|
|
969
3170
|
*
|
|
970
3171
|
* Creates:
|
|
971
|
-
* - src/main.server.ts (entry point)
|
|
972
3172
|
* - src/api/index.ts (API module)
|
|
973
3173
|
* - src/api/controllers/HelloController.ts (example controller)
|
|
974
3174
|
*/
|
|
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
|
-
}
|
|
3175
|
+
async ensureApiProject(root, opts = {}) {
|
|
980
3176
|
const appName = this.getAppName(root);
|
|
981
3177
|
await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
|
|
982
|
-
await this.
|
|
983
|
-
await this.
|
|
984
|
-
await this.fs.writeFile(this.fs.join(srcDir, "api/controllers/HelloController.ts"), apiHelloControllerTs());
|
|
3178
|
+
await this.ensureFile(root, "src/api/index.ts", apiIndexTs({ appName }), opts.force);
|
|
3179
|
+
await this.ensureFile(root, "src/api/controllers/HelloController.ts", apiHelloControllerTs(), opts.force);
|
|
985
3180
|
}
|
|
986
3181
|
/**
|
|
987
|
-
* Ensure
|
|
3182
|
+
* Ensure web/React project structure exists.
|
|
988
3183
|
*
|
|
989
3184
|
* Creates:
|
|
990
|
-
* -
|
|
991
|
-
* - src/main.
|
|
992
|
-
* - src/api/index.ts, src/api/controllers/HelloController.ts
|
|
3185
|
+
* - src/main.browser.ts
|
|
3186
|
+
* - src/main.css
|
|
993
3187
|
* - src/web/index.ts, src/web/AppRouter.ts, src/web/components/Hello.tsx
|
|
994
3188
|
*/
|
|
995
|
-
async
|
|
996
|
-
if (await this.fs.exists(this.fs.join(root, "index.html"))) return;
|
|
3189
|
+
async ensureWebProject(root, opts = {}) {
|
|
997
3190
|
const appName = this.getAppName(root);
|
|
998
|
-
await this.fs.mkdir(this.fs.join(root, "src/api/controllers"), { recursive: true });
|
|
999
3191
|
await this.fs.mkdir(this.fs.join(root, "src/web/components"), { recursive: true });
|
|
1000
|
-
await this.
|
|
1001
|
-
await this.
|
|
1002
|
-
await this.
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
await this.
|
|
1007
|
-
await this.
|
|
3192
|
+
await this.ensureFile(root, "src/main.css", mainCss({ ui: opts.ui }), opts.force);
|
|
3193
|
+
await this.ensureFile(root, "src/web/index.ts", webIndexTs({ appName }), opts.force);
|
|
3194
|
+
await this.ensureFile(root, "src/web/AppRouter.ts", webAppRouterTs({
|
|
3195
|
+
api: opts.api,
|
|
3196
|
+
ui: opts.ui
|
|
3197
|
+
}), opts.force);
|
|
3198
|
+
await this.ensureFile(root, "src/web/components/Hello.tsx", webHelloComponentTsx(), opts.force);
|
|
3199
|
+
await this.ensureFile(root, "src/main.browser.ts", mainBrowserTs(), opts.force);
|
|
1008
3200
|
}
|
|
1009
3201
|
/**
|
|
1010
3202
|
* Ensure test directory exists with a dummy test file.
|
|
@@ -1019,9 +3211,12 @@ var ProjectScaffolder = class {
|
|
|
1019
3211
|
}
|
|
1020
3212
|
if ((await this.fs.ls(testDir)).length === 0) await this.fs.writeFile(dummyPath, dummySpecTs());
|
|
1021
3213
|
}
|
|
1022
|
-
|
|
3214
|
+
/**
|
|
3215
|
+
* Write a file, optionally overriding if it exists.
|
|
3216
|
+
*/
|
|
3217
|
+
async ensureFile(root, relativePath, content, force) {
|
|
1023
3218
|
const fullPath = this.fs.join(root, relativePath);
|
|
1024
|
-
if (!await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
|
|
3219
|
+
if (force || !await this.fs.exists(fullPath)) await this.fs.writeFile(fullPath, content);
|
|
1025
3220
|
}
|
|
1026
3221
|
/**
|
|
1027
3222
|
* Check if a file exists in the given directory or any parent directory.
|
|
@@ -1045,15 +3240,13 @@ var BuildCommand = class {
|
|
|
1045
3240
|
utils = $inject(AlephaCliUtils);
|
|
1046
3241
|
pm = $inject(PackageManagerUtils);
|
|
1047
3242
|
scaffolder = $inject(ProjectScaffolder);
|
|
3243
|
+
boot = $inject(AppEntryProvider);
|
|
3244
|
+
viteBuildProvider = $inject(ViteBuildProvider);
|
|
1048
3245
|
options = $use(buildOptions);
|
|
1049
3246
|
build = $command({
|
|
1050
3247
|
name: "build",
|
|
1051
3248
|
mode: "production",
|
|
1052
3249
|
description: "Build the project for production",
|
|
1053
|
-
args: t.optional(t.text({
|
|
1054
|
-
title: "path",
|
|
1055
|
-
description: "Filepath to build"
|
|
1056
|
-
})),
|
|
1057
3250
|
flags: t.object({
|
|
1058
3251
|
stats: t.optional(t.boolean({ description: "Generate build stats report" })),
|
|
1059
3252
|
vercel: t.optional(t.boolean({ description: "Generate Vercel deployment configuration" })),
|
|
@@ -1062,53 +3255,70 @@ var BuildCommand = class {
|
|
|
1062
3255
|
sitemap: t.optional(t.text({ description: "Generate sitemap.xml with base URL" })),
|
|
1063
3256
|
bun: t.optional(t.boolean({ description: "Prioritize .bun.ts entry files for Bun runtime" }))
|
|
1064
3257
|
}),
|
|
1065
|
-
handler: async ({ flags,
|
|
1066
|
-
process.env.ALEPHA_BUILD_MODE = "cli";
|
|
3258
|
+
handler: async ({ flags, run, root }) => {
|
|
1067
3259
|
process.env.NODE_ENV = "production";
|
|
1068
3260
|
if (await this.pm.hasExpo(root)) return;
|
|
1069
3261
|
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
1070
|
-
const entry = await boot.
|
|
3262
|
+
const entry = await this.boot.getAppEntry(root);
|
|
1071
3263
|
this.log.trace("Entry file found", { entry });
|
|
1072
3264
|
const distDir = "dist";
|
|
1073
|
-
const
|
|
1074
|
-
await this.pm.ensureDependency(root, "vite", {
|
|
1075
|
-
run,
|
|
1076
|
-
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
1077
|
-
});
|
|
3265
|
+
const publicDir = "public";
|
|
1078
3266
|
await run.rm("dist", { alias: "clean dist" });
|
|
1079
3267
|
const options = this.options;
|
|
1080
3268
|
await this.utils.loadEnv(root, [".env", ".env.production"]);
|
|
1081
3269
|
const stats = flags.stats ?? options.stats ?? false;
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
3270
|
+
let template = "";
|
|
3271
|
+
let hasClient = false;
|
|
3272
|
+
let alepha;
|
|
3273
|
+
await run({
|
|
3274
|
+
name: "analyze app",
|
|
3275
|
+
handler: async () => {
|
|
3276
|
+
alepha = await this.viteBuildProvider.init({ entry });
|
|
3277
|
+
hasClient = this.viteBuildProvider.hasClient();
|
|
3278
|
+
if (hasClient) template = this.viteBuildProvider.generateIndexHtml();
|
|
3279
|
+
}
|
|
1091
3280
|
});
|
|
3281
|
+
if (!alepha) throw new AlephaError("Alepha instance not found");
|
|
3282
|
+
if (hasClient) {
|
|
3283
|
+
const indexHtmlPath = this.fs.join(root, "index.html");
|
|
3284
|
+
await this.fs.writeFile(indexHtmlPath, template);
|
|
3285
|
+
try {
|
|
3286
|
+
await run({
|
|
3287
|
+
name: "vite build client",
|
|
3288
|
+
handler: () => buildClient({
|
|
3289
|
+
silent: true,
|
|
3290
|
+
dist: `${distDir}/${publicDir}`,
|
|
3291
|
+
stats,
|
|
3292
|
+
precompress: true
|
|
3293
|
+
})
|
|
3294
|
+
});
|
|
3295
|
+
} finally {
|
|
3296
|
+
await this.fs.rm(indexHtmlPath);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
1092
3299
|
await run({
|
|
1093
3300
|
name: "vite build server",
|
|
1094
3301
|
handler: async () => {
|
|
1095
|
-
|
|
3302
|
+
if (!alepha) throw new AlephaError("Alepha instance not found");
|
|
3303
|
+
const clientIndexPath = `${distDir}/${publicDir}/index.html`;
|
|
1096
3304
|
const clientBuilt = await this.fs.exists(clientIndexPath);
|
|
1097
3305
|
const conditions = [];
|
|
1098
3306
|
if (flags.bun) conditions.push("bun");
|
|
1099
3307
|
if (options.cloudflare) conditions.push("workerd");
|
|
1100
3308
|
await buildServer({
|
|
1101
3309
|
silent: true,
|
|
1102
|
-
entry,
|
|
3310
|
+
entry: entry.server,
|
|
1103
3311
|
distDir,
|
|
1104
|
-
clientDir: clientBuilt ?
|
|
3312
|
+
clientDir: clientBuilt ? publicDir : void 0,
|
|
1105
3313
|
stats,
|
|
1106
|
-
conditions
|
|
3314
|
+
conditions,
|
|
3315
|
+
alepha
|
|
1107
3316
|
});
|
|
1108
3317
|
if (clientBuilt) await this.fs.rm(clientIndexPath);
|
|
1109
3318
|
}
|
|
1110
3319
|
});
|
|
1111
3320
|
await copyAssets({
|
|
3321
|
+
alepha,
|
|
1112
3322
|
root,
|
|
1113
3323
|
entry: `${distDir}/index.js`,
|
|
1114
3324
|
distDir,
|
|
@@ -1116,31 +3326,24 @@ var BuildCommand = class {
|
|
|
1116
3326
|
});
|
|
1117
3327
|
if (hasClient) {
|
|
1118
3328
|
const sitemapHostname = flags.sitemap ?? options.sitemap?.hostname;
|
|
1119
|
-
if (sitemapHostname) await
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
baseUrl: sitemapHostname
|
|
1125
|
-
}));
|
|
1126
|
-
}
|
|
3329
|
+
if (sitemapHostname) await generateSitemap({
|
|
3330
|
+
alepha,
|
|
3331
|
+
baseUrl: sitemapHostname,
|
|
3332
|
+
output: `${distDir}/${publicDir}/sitemap.xml`,
|
|
3333
|
+
run
|
|
1127
3334
|
});
|
|
1128
|
-
await
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
entry: `${distDir}/index.js`,
|
|
1134
|
-
compress: true
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
3335
|
+
await prerenderPages({
|
|
3336
|
+
alepha,
|
|
3337
|
+
dist: `${distDir}/${publicDir}`,
|
|
3338
|
+
compress: true,
|
|
3339
|
+
run
|
|
1137
3340
|
});
|
|
1138
3341
|
}
|
|
1139
3342
|
if (flags.vercel || options.vercel) await run({
|
|
1140
3343
|
name: "add Vercel config",
|
|
1141
3344
|
handler: () => generateVercel({
|
|
1142
3345
|
distDir,
|
|
1143
|
-
clientDir,
|
|
3346
|
+
clientDir: publicDir,
|
|
1144
3347
|
config: options.vercel
|
|
1145
3348
|
})
|
|
1146
3349
|
});
|
|
@@ -1187,6 +3390,7 @@ var DbCommand = class {
|
|
|
1187
3390
|
log = $logger();
|
|
1188
3391
|
fs = $inject(FileSystemProvider);
|
|
1189
3392
|
utils = $inject(AlephaCliUtils);
|
|
3393
|
+
entryProvider = $inject(AppEntryProvider);
|
|
1190
3394
|
/**
|
|
1191
3395
|
* Check if database migrations are up to date.
|
|
1192
3396
|
*/
|
|
@@ -1201,7 +3405,11 @@ var DbCommand = class {
|
|
|
1201
3405
|
handler: async ({ args, root }) => {
|
|
1202
3406
|
const rootDir = root;
|
|
1203
3407
|
this.log.debug(`Using project root: ${rootDir}`);
|
|
1204
|
-
const
|
|
3408
|
+
const entry = await this.entryProvider.getAppEntry(root);
|
|
3409
|
+
const alepha = await this.utils.loadAlephaFromServerEntryFile({
|
|
3410
|
+
mode: "development",
|
|
3411
|
+
entry
|
|
3412
|
+
});
|
|
1205
3413
|
const repositoryProvider = alepha.inject("RepositoryProvider");
|
|
1206
3414
|
const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
|
|
1207
3415
|
const accepted = /* @__PURE__ */ new Set([]);
|
|
@@ -1365,7 +3573,11 @@ var DbCommand = class {
|
|
|
1365
3573
|
if (options.env) envFiles.push(`.env.${options.env}`);
|
|
1366
3574
|
await this.utils.loadEnv(rootDir, envFiles);
|
|
1367
3575
|
this.log.debug(`Using project root: ${rootDir}`);
|
|
1368
|
-
const
|
|
3576
|
+
const entry = await this.entryProvider.getAppEntry(rootDir);
|
|
3577
|
+
const alepha = await this.utils.loadAlephaFromServerEntryFile({
|
|
3578
|
+
mode: "development",
|
|
3579
|
+
entry
|
|
3580
|
+
});
|
|
1369
3581
|
const drizzleKitProvider = alepha.inject("DrizzleKitProvider");
|
|
1370
3582
|
const repositoryProvider = alepha.inject("RepositoryProvider");
|
|
1371
3583
|
const accepted = /* @__PURE__ */ new Set([]);
|
|
@@ -1389,11 +3601,11 @@ var DbCommand = class {
|
|
|
1389
3601
|
providerUrl: provider.url,
|
|
1390
3602
|
providerDriver: provider.driver,
|
|
1391
3603
|
dialect,
|
|
1392
|
-
entry,
|
|
3604
|
+
entry: this.fs.join(rootDir, entry.server),
|
|
1393
3605
|
rootDir
|
|
1394
3606
|
});
|
|
1395
3607
|
const flags = options.commandFlags ? ` ${options.commandFlags}` : "";
|
|
1396
|
-
await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: "--import tsx" } });
|
|
3608
|
+
await this.utils.exec(`drizzle-kit ${options.command} --config=${drizzleConfigJsPath}${flags}`, { env: { NODE_OPTIONS: [process.env.NODE_OPTIONS, "--import tsx"].filter(Boolean).join(" ") } });
|
|
1397
3609
|
}
|
|
1398
3610
|
}
|
|
1399
3611
|
/**
|
|
@@ -1529,70 +3741,275 @@ var DeployCommand = class {
|
|
|
1529
3741
|
};
|
|
1530
3742
|
|
|
1531
3743
|
//#endregion
|
|
1532
|
-
//#region ../../src/cli/
|
|
1533
|
-
|
|
3744
|
+
//#region ../../src/cli/providers/ViteDevServerProvider.ts
|
|
3745
|
+
/**
|
|
3746
|
+
* Vite development server with Alepha integration.
|
|
3747
|
+
*
|
|
3748
|
+
* Architecture:
|
|
3749
|
+
* - Vite runs in middleware mode (no HTTP server)
|
|
3750
|
+
* - Alepha is the HTTP server via server:onRequest event
|
|
3751
|
+
* - Request flow: Page requests → Alepha SSR, Assets → Vite middleware
|
|
3752
|
+
*
|
|
3753
|
+
* HMR Strategy:
|
|
3754
|
+
* - Browser-only changes (CSS, client components) → Vite HMR (React Fast Refresh)
|
|
3755
|
+
* - Server-only changes → Restart Alepha → Full browser reload
|
|
3756
|
+
* - Shared changes → Restart Alepha → Let Vite HMR propagate
|
|
3757
|
+
*
|
|
3758
|
+
* Features:
|
|
3759
|
+
* - Automatic .env reload detection
|
|
3760
|
+
* - Error recovery on next file change
|
|
3761
|
+
* - Optimized module invalidation (only changed files + importers)
|
|
3762
|
+
*/
|
|
3763
|
+
var ViteDevServerProvider = class {
|
|
1534
3764
|
log = $logger();
|
|
1535
3765
|
fs = $inject(FileSystemProvider);
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
alepha =
|
|
3766
|
+
templateProvider = $inject(ViteUtils);
|
|
3767
|
+
server;
|
|
3768
|
+
options;
|
|
3769
|
+
alepha = null;
|
|
3770
|
+
hasError = false;
|
|
3771
|
+
changedFiles = /* @__PURE__ */ new Set();
|
|
3772
|
+
async init(options) {
|
|
3773
|
+
this.options = options;
|
|
3774
|
+
await this.createViteServer();
|
|
3775
|
+
return await this.loadAlepha(true);
|
|
3776
|
+
}
|
|
3777
|
+
async start() {
|
|
3778
|
+
await this.alepha?.start();
|
|
3779
|
+
}
|
|
1540
3780
|
/**
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
* - If an index.html file is found in the project root, it will run Vite in dev mode.
|
|
1544
|
-
* - Otherwise, it will look for a server entry file and run it with tsx in watch mode.
|
|
3781
|
+
* Create the Vite server in middleware mode.
|
|
1545
3782
|
*/
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
3783
|
+
async createViteServer() {
|
|
3784
|
+
const { createServer } = await importVite();
|
|
3785
|
+
const viteReact = await importViteReact();
|
|
3786
|
+
const plugins = [];
|
|
3787
|
+
if (viteReact) plugins.push(viteReact());
|
|
3788
|
+
plugins.push(viteAlephaSsrPreload());
|
|
3789
|
+
plugins.push(this.createHmrPlugin());
|
|
3790
|
+
this.server = await createServer({
|
|
3791
|
+
root: this.options.root,
|
|
3792
|
+
plugins,
|
|
3793
|
+
server: { middlewareMode: true },
|
|
3794
|
+
appType: "custom",
|
|
3795
|
+
customLogger: {
|
|
3796
|
+
info: () => {},
|
|
3797
|
+
warn: this.log.warn.bind(this.log),
|
|
3798
|
+
error: this.log.error.bind(this.log),
|
|
3799
|
+
warnOnce: this.log.warn.bind(this.log),
|
|
3800
|
+
clearScreen: () => {},
|
|
3801
|
+
hasWarned: false,
|
|
3802
|
+
hasErrorLogged: () => false
|
|
1559
3803
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
3804
|
+
});
|
|
3805
|
+
this.server.restart = async () => {
|
|
3806
|
+
const startTime = Date.now();
|
|
3807
|
+
try {
|
|
3808
|
+
this.hasError = true;
|
|
3809
|
+
await this.loadAlepha(false);
|
|
3810
|
+
await this.alepha?.start();
|
|
3811
|
+
this.log.debug(`Env reloaded in ${Date.now() - startTime}ms`);
|
|
3812
|
+
this.sendBrowserReload();
|
|
3813
|
+
} catch (err) {
|
|
3814
|
+
this.hasError = true;
|
|
3815
|
+
this.log.error("Reload failed", err);
|
|
3816
|
+
this.log.warn("Waiting for file changes to retry...");
|
|
3817
|
+
this.alepha = null;
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Vite plugin to handle HMR for Alepha.
|
|
3823
|
+
*/
|
|
3824
|
+
createHmrPlugin() {
|
|
3825
|
+
return {
|
|
3826
|
+
name: "alepha-hmr",
|
|
3827
|
+
handleHotUpdate: async (ctx) => {
|
|
3828
|
+
if (ctx.file.includes("/.idea/")) return [];
|
|
3829
|
+
const firstModule = ctx.modules[0];
|
|
3830
|
+
const isBrowserOnly = firstModule && !firstModule._ssrModule;
|
|
3831
|
+
const isServerOnly = firstModule && !firstModule._clientModule;
|
|
3832
|
+
if (isBrowserOnly) return;
|
|
3833
|
+
const startTime = Date.now();
|
|
3834
|
+
try {
|
|
3835
|
+
this.changedFiles.add(ctx.file);
|
|
3836
|
+
await this.loadAlepha(false);
|
|
3837
|
+
await this.alepha?.start();
|
|
3838
|
+
this.log.debug(`Reloaded in ${Date.now() - startTime}ms`);
|
|
3839
|
+
if (isServerOnly) {
|
|
3840
|
+
this.sendBrowserReload();
|
|
3841
|
+
return [];
|
|
3842
|
+
}
|
|
3843
|
+
return;
|
|
3844
|
+
} catch (err) {
|
|
3845
|
+
this.hasError = true;
|
|
3846
|
+
this.log.error("Reload failed", err);
|
|
3847
|
+
this.log.warn("Waiting for file changes to retry...");
|
|
3848
|
+
this.alepha = null;
|
|
3849
|
+
return [];
|
|
3850
|
+
}
|
|
1569
3851
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Send browser reload signal via custom event.
|
|
3856
|
+
* Browser listens for 'alepha:reload' and does window.location.reload()
|
|
3857
|
+
*/
|
|
3858
|
+
sendBrowserReload() {
|
|
3859
|
+
this.server.ws.send({
|
|
3860
|
+
type: "custom",
|
|
3861
|
+
event: "alepha:reload",
|
|
3862
|
+
data: {}
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
/**
|
|
3866
|
+
* Setup environment variables for dev mode.
|
|
3867
|
+
*/
|
|
3868
|
+
async setupEnvironment() {
|
|
3869
|
+
const { loadEnv } = await importVite();
|
|
3870
|
+
const env = loadEnv(process.env.NODE_ENV || "development", this.options.root, "");
|
|
3871
|
+
for (const [key, value] of Object.entries(env)) process.env[key] ??= value;
|
|
3872
|
+
process.env.NODE_ENV ??= "development";
|
|
3873
|
+
process.env.VITE_ALEPHA_DEV = "true";
|
|
3874
|
+
process.env.SERVER_HOST ??= this.options.host?.toString() ?? "localhost";
|
|
3875
|
+
process.env.SERVER_PORT ??= String(this.options.port ?? (process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3e3));
|
|
3876
|
+
}
|
|
3877
|
+
/**
|
|
3878
|
+
* Load or reload the Alepha instance.
|
|
3879
|
+
*/
|
|
3880
|
+
async loadAlepha(isInitialLoad = false) {
|
|
3881
|
+
if (this.alepha) {
|
|
3882
|
+
await this.alepha.stop().catch((err) => this.log.warn("Error stopping Alepha", err));
|
|
3883
|
+
this.alepha = null;
|
|
3884
|
+
}
|
|
3885
|
+
if (isInitialLoad || this.hasError) this.server.moduleGraph.invalidateAll();
|
|
3886
|
+
else this.invalidateModulesWithImporters();
|
|
3887
|
+
this.changedFiles.clear();
|
|
3888
|
+
const envSnapshot = { ...process.env };
|
|
3889
|
+
await this.setupEnvironment();
|
|
3890
|
+
await this.server.ssrLoadModule(this.options.entry.server);
|
|
3891
|
+
const alepha = globalThis.__alepha;
|
|
3892
|
+
if (!alepha) throw new AlephaError("Alepha instance not found after loading entry module");
|
|
3893
|
+
this.alepha = alepha;
|
|
3894
|
+
await this.setupAlepha();
|
|
3895
|
+
this.hasError = false;
|
|
3896
|
+
process.env = envSnapshot;
|
|
3897
|
+
return alepha;
|
|
3898
|
+
}
|
|
3899
|
+
hasReact() {
|
|
3900
|
+
try {
|
|
3901
|
+
this.alepha?.inject("ReactServerProvider");
|
|
3902
|
+
return true;
|
|
3903
|
+
} catch {
|
|
3904
|
+
return false;
|
|
1572
3905
|
}
|
|
1573
|
-
});
|
|
1574
|
-
async isBunProject(root) {
|
|
1575
|
-
if (this.alepha.isBun()) return true;
|
|
1576
|
-
return this.fs.exists(this.fs.join(root, "bun.lock"));
|
|
1577
3906
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
3907
|
+
/**
|
|
3908
|
+
* Setup Alepha instance with Vite middleware and template.
|
|
3909
|
+
*/
|
|
3910
|
+
async setupAlepha() {
|
|
3911
|
+
if (!this.alepha || !this.hasReact()) return;
|
|
3912
|
+
const template = await this.server.transformIndexHtml("/", this.templateProvider.generateIndexHtml(this.options.entry));
|
|
3913
|
+
this.alepha.store.set("alepha.react.server.template", template);
|
|
3914
|
+
this.alepha.events.on("server:onRequest", {
|
|
3915
|
+
priority: "first",
|
|
3916
|
+
callback: async ({ request }) => {
|
|
3917
|
+
const node = request.raw.node;
|
|
3918
|
+
if (!node || this.isPageRequest(node.req)) return;
|
|
3919
|
+
if (await this.runViteMiddleware(node.req, node.res, request)) {
|
|
3920
|
+
request.reply.status = node.res.statusCode || 200;
|
|
3921
|
+
request.reply.body = null;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
/**
|
|
3927
|
+
* Check if request is for an HTML page (not an asset).
|
|
3928
|
+
*/
|
|
3929
|
+
isPageRequest(req) {
|
|
3930
|
+
const url = req.url || "/";
|
|
3931
|
+
if (url === "/" || url === "/index.html") return true;
|
|
3932
|
+
if (url.startsWith("/@") || url.startsWith("/__vite")) return false;
|
|
3933
|
+
if (/\.\w+$/.test(url.split("?")[0])) return false;
|
|
3934
|
+
return true;
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* Run Vite middleware and detect if it handled the request.
|
|
3938
|
+
*/
|
|
3939
|
+
async runViteMiddleware(req, res, ctx) {
|
|
3940
|
+
if (res.headersSent || res.writableEnded) return false;
|
|
3941
|
+
return new Promise((resolve) => {
|
|
3942
|
+
let resolved = false;
|
|
3943
|
+
const done = (handled) => {
|
|
3944
|
+
if (resolved) return;
|
|
3945
|
+
resolved = true;
|
|
3946
|
+
if (handled) ctx.metadata.vite = true;
|
|
3947
|
+
resolve(handled);
|
|
3948
|
+
};
|
|
3949
|
+
const originalSetHeader = res.setHeader.bind(res);
|
|
3950
|
+
const originalWriteHead = res.writeHead?.bind(res);
|
|
3951
|
+
const originalWrite = res.write.bind(res);
|
|
3952
|
+
const originalEnd = res.end.bind(res);
|
|
3953
|
+
const guardedCall = (fn, ...args) => {
|
|
3954
|
+
if (resolved && !ctx.metadata.vite) return;
|
|
3955
|
+
return fn(...args);
|
|
3956
|
+
};
|
|
3957
|
+
res.setHeader = (...args) => guardedCall(originalSetHeader, ...args);
|
|
3958
|
+
if (originalWriteHead) res.writeHead = (...args) => guardedCall(originalWriteHead, ...args);
|
|
3959
|
+
res.write = (...args) => guardedCall(originalWrite, ...args);
|
|
3960
|
+
res.end = (...args) => guardedCall(originalEnd, ...args);
|
|
3961
|
+
res.on("finish", () => done(true));
|
|
3962
|
+
res.on("close", () => res.headersSent && done(true));
|
|
3963
|
+
this.server.middlewares(req, res, () => done(false));
|
|
3964
|
+
setImmediate(() => {
|
|
3965
|
+
if (res.headersSent || res.writableEnded) done(true);
|
|
3966
|
+
});
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3969
|
+
/**
|
|
3970
|
+
* Invalidate modules and all their importers.
|
|
3971
|
+
*/
|
|
3972
|
+
invalidateModulesWithImporters() {
|
|
3973
|
+
const invalidated = /* @__PURE__ */ new Set();
|
|
3974
|
+
const queue = [...this.changedFiles];
|
|
3975
|
+
while (queue.length > 0) {
|
|
3976
|
+
const file = queue.pop();
|
|
3977
|
+
if (invalidated.has(file)) continue;
|
|
3978
|
+
const mod = this.server.moduleGraph.getModuleById(file);
|
|
3979
|
+
if (!mod) continue;
|
|
3980
|
+
this.server.moduleGraph.invalidateModule(mod);
|
|
3981
|
+
invalidated.add(file);
|
|
3982
|
+
for (const importer of mod.importers) if (importer.id && !invalidated.has(importer.id)) queue.push(importer.id);
|
|
3983
|
+
}
|
|
1580
3984
|
}
|
|
1581
3985
|
};
|
|
1582
3986
|
|
|
1583
3987
|
//#endregion
|
|
1584
|
-
//#region ../../src/cli/commands/
|
|
1585
|
-
var
|
|
3988
|
+
//#region ../../src/cli/commands/dev.ts
|
|
3989
|
+
var DevCommand = class {
|
|
3990
|
+
log = $logger();
|
|
3991
|
+
fs = $inject(FileSystemProvider);
|
|
1586
3992
|
utils = $inject(AlephaCliUtils);
|
|
1587
3993
|
pm = $inject(PackageManagerUtils);
|
|
1588
3994
|
scaffolder = $inject(ProjectScaffolder);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
3995
|
+
alepha = $inject(Alepha);
|
|
3996
|
+
viteDevServer = $inject(ViteDevServerProvider);
|
|
3997
|
+
boot = $inject(AppEntryProvider);
|
|
3998
|
+
/**
|
|
3999
|
+
* Will run the project in watch mode.
|
|
4000
|
+
*/
|
|
4001
|
+
dev = $command({
|
|
4002
|
+
name: "dev",
|
|
4003
|
+
description: "Run the project in development mode",
|
|
1592
4004
|
handler: async ({ root }) => {
|
|
1593
|
-
await this.scaffolder.ensureConfig(root, {
|
|
1594
|
-
|
|
1595
|
-
|
|
4005
|
+
await this.scaffolder.ensureConfig(root, { tsconfigJson: true });
|
|
4006
|
+
const entry = await this.boot.getAppEntry(root);
|
|
4007
|
+
this.log.debug("Entry file found", { entry });
|
|
4008
|
+
await this.viteDevServer.init({
|
|
4009
|
+
root,
|
|
4010
|
+
entry
|
|
4011
|
+
});
|
|
4012
|
+
await this.viteDevServer.start();
|
|
1596
4013
|
}
|
|
1597
4014
|
});
|
|
1598
4015
|
};
|
|
@@ -1627,7 +4044,7 @@ const DEFAULT_IGNORE = [
|
|
|
1627
4044
|
* ```
|
|
1628
4045
|
*/
|
|
1629
4046
|
const changelogOptions = $atom({
|
|
1630
|
-
name: "alepha.changelog",
|
|
4047
|
+
name: "alepha.cli.changelog.options",
|
|
1631
4048
|
schema: t.object({ ignore: t.optional(t.array(t.string())) }),
|
|
1632
4049
|
default: { ignore: DEFAULT_IGNORE }
|
|
1633
4050
|
});
|
|
@@ -1827,7 +4244,10 @@ var GenEnvCommand = class {
|
|
|
1827
4244
|
description: "Output file path (e.g., .env)"
|
|
1828
4245
|
})) }),
|
|
1829
4246
|
handler: async ({ root, flags }) => {
|
|
1830
|
-
const
|
|
4247
|
+
const alepha = await this.utils.loadAlephaFromServerEntryFile({
|
|
4248
|
+
root,
|
|
4249
|
+
mode: "development"
|
|
4250
|
+
});
|
|
1831
4251
|
try {
|
|
1832
4252
|
const { env } = alepha.dump();
|
|
1833
4253
|
let dotEnvFile = "";
|
|
@@ -1835,7 +4255,7 @@ var GenEnvCommand = class {
|
|
|
1835
4255
|
if (value.description) dotEnvFile += `# ${value.description.split("\n").join("\n# ")}\n`;
|
|
1836
4256
|
if (value.required && !value.default) dotEnvFile += `# (required)\n`;
|
|
1837
4257
|
if (value.enum) dotEnvFile += `# Possible values: ${value.enum.join(", ")}\n`;
|
|
1838
|
-
dotEnvFile +=
|
|
4258
|
+
dotEnvFile += `#${key}=${value.default || ""}\n\n`;
|
|
1839
4259
|
}
|
|
1840
4260
|
if (flags.out) await this.fs.writeFile(this.fs.join(root, flags.out), dotEnvFile);
|
|
1841
4261
|
else this.log.info(dotEnvFile);
|
|
@@ -1860,7 +4280,10 @@ var OpenApiCommand = class {
|
|
|
1860
4280
|
description: "Output file path"
|
|
1861
4281
|
})) }),
|
|
1862
4282
|
handler: async ({ root, flags }) => {
|
|
1863
|
-
const
|
|
4283
|
+
const alepha = await this.utils.loadAlephaFromServerEntryFile({
|
|
4284
|
+
root,
|
|
4285
|
+
mode: "development"
|
|
4286
|
+
});
|
|
1864
4287
|
try {
|
|
1865
4288
|
const openapiProvider = alepha.inject(ServerSwaggerProvider);
|
|
1866
4289
|
await alepha.events.emit("configure", alepha);
|
|
@@ -1929,70 +4352,91 @@ var InitCommand = class {
|
|
|
1929
4352
|
flags: t.object({
|
|
1930
4353
|
agent: t.optional(t.boolean({
|
|
1931
4354
|
aliases: ["a"],
|
|
1932
|
-
description: "Add CLAUDE.md
|
|
4355
|
+
description: "Add AI agent instructions (CLAUDE.md if claude CLI installed, else AGENTS.md)"
|
|
1933
4356
|
})),
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
4357
|
+
pm: t.optional(t.enum([
|
|
4358
|
+
"yarn",
|
|
4359
|
+
"npm",
|
|
4360
|
+
"pnpm",
|
|
4361
|
+
"bun"
|
|
4362
|
+
], { description: "Package manager to use" })),
|
|
4363
|
+
api: t.optional(t.boolean({ description: "Include API module structure (src/api/)" })),
|
|
4364
|
+
react: t.optional(t.boolean({
|
|
1939
4365
|
aliases: ["r"],
|
|
1940
|
-
description: "Include
|
|
4366
|
+
description: "Include React dependencies and web module (src/web/)"
|
|
1941
4367
|
})),
|
|
1942
|
-
|
|
1943
|
-
test: t.optional(t.boolean({ description: "Include Vitest and create test directory" }))
|
|
4368
|
+
ui: t.optional(t.boolean({ description: "Include @alepha/ui (components, auth portal, admin portal)" })),
|
|
4369
|
+
test: t.optional(t.boolean({ description: "Include Vitest and create test directory" })),
|
|
4370
|
+
force: t.optional(t.boolean({
|
|
4371
|
+
aliases: ["f"],
|
|
4372
|
+
description: "Override existing files"
|
|
4373
|
+
}))
|
|
1944
4374
|
}),
|
|
1945
4375
|
handler: async ({ run, flags, root, args }) => {
|
|
1946
|
-
if (flags.admin) flags.web = true;
|
|
1947
4376
|
if (args) {
|
|
1948
4377
|
root = this.fs.join(root, args);
|
|
1949
4378
|
await this.fs.mkdir(root);
|
|
1950
4379
|
}
|
|
4380
|
+
if (flags.ui) flags.react = true;
|
|
4381
|
+
const workspace = await this.pm.getWorkspaceContext(root);
|
|
4382
|
+
let agentType = false;
|
|
4383
|
+
if (flags.agent) agentType = await this.utils.isInstalledAsync("claude") ? "claude" : "agents";
|
|
1951
4384
|
const isExpo = await this.pm.hasExpo(root);
|
|
4385
|
+
const force = !!flags.force;
|
|
1952
4386
|
await run({
|
|
1953
4387
|
name: "ensuring configuration files",
|
|
1954
4388
|
handler: async () => {
|
|
1955
4389
|
await this.scaffolder.ensureConfig(root, {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
4390
|
+
force,
|
|
4391
|
+
tsconfigJson: !workspace.config.tsconfigJson,
|
|
4392
|
+
packageJson: {
|
|
4393
|
+
...flags,
|
|
4394
|
+
isPackage: workspace.isPackage
|
|
4395
|
+
},
|
|
4396
|
+
biomeJson: !workspace.config.biomeJson,
|
|
4397
|
+
editorconfig: !workspace.config.editorconfig,
|
|
4398
|
+
agentMd: agentType ? {
|
|
4399
|
+
type: agentType,
|
|
4400
|
+
react: !!flags.react,
|
|
4401
|
+
ui: !!flags.ui
|
|
1964
4402
|
} : false
|
|
1965
4403
|
});
|
|
1966
|
-
|
|
4404
|
+
await this.scaffolder.ensureMainServerTs(root, {
|
|
4405
|
+
api: !!flags.api,
|
|
4406
|
+
react: !!flags.react && !isExpo,
|
|
4407
|
+
force
|
|
4408
|
+
});
|
|
4409
|
+
if (flags.api) await this.scaffolder.ensureApiProject(root, { force });
|
|
4410
|
+
if (flags.react && !isExpo) await this.scaffolder.ensureWebProject(root, {
|
|
4411
|
+
api: !!flags.api,
|
|
4412
|
+
ui: !!flags.ui,
|
|
4413
|
+
force
|
|
4414
|
+
});
|
|
1967
4415
|
}
|
|
1968
4416
|
});
|
|
1969
|
-
const pmName = await this.pm.getPackageManager(root, flags);
|
|
1970
|
-
if (pmName === "yarn") {
|
|
4417
|
+
const pmName = await this.pm.getPackageManager(workspace.workspaceRoot ?? root, flags.pm ?? workspace.packageManager ?? void 0);
|
|
4418
|
+
if (!workspace.isPackage) if (pmName === "yarn") {
|
|
1971
4419
|
await this.pm.ensureYarn(root);
|
|
1972
4420
|
await run("yarn set version stable", { root });
|
|
1973
4421
|
} else if (pmName === "bun") await this.pm.ensureBun(root);
|
|
1974
4422
|
else if (pmName === "pnpm") await this.pm.ensurePnpm(root);
|
|
1975
4423
|
else await this.pm.ensureNpm(root);
|
|
4424
|
+
const installRoot = workspace.workspaceRoot ?? root;
|
|
1976
4425
|
await run(`${pmName} install`, {
|
|
1977
4426
|
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)
|
|
4427
|
+
root: installRoot
|
|
1987
4428
|
});
|
|
1988
|
-
if (flags.test)
|
|
1989
|
-
await this.scaffolder.ensureTestDir(root);
|
|
1990
|
-
await run(`${pmName} ${pmName === "yarn" ? "add" : "install"} -D vitest`, { alias: "setup testing with Vitest" });
|
|
1991
|
-
}
|
|
4429
|
+
if (flags.test) await this.scaffolder.ensureTestDir(root);
|
|
1992
4430
|
await run(`${pmName} run lint`, {
|
|
1993
4431
|
alias: "running linter",
|
|
1994
|
-
root
|
|
4432
|
+
root: installRoot
|
|
1995
4433
|
});
|
|
4434
|
+
if (!workspace.isPackage) {
|
|
4435
|
+
if (await this.scaffolder.ensureGitRepo(root, { force })) await run("git add .", {
|
|
4436
|
+
alias: "staging generated files",
|
|
4437
|
+
root
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
1996
4440
|
}
|
|
1997
4441
|
});
|
|
1998
4442
|
};
|
|
@@ -2007,9 +4451,15 @@ var LintCommand = class {
|
|
|
2007
4451
|
name: "lint",
|
|
2008
4452
|
description: "Run linter across the codebase using Biome",
|
|
2009
4453
|
handler: async ({ root }) => {
|
|
2010
|
-
await this.scaffolder.ensureConfig(root, {
|
|
2011
|
-
|
|
2012
|
-
|
|
4454
|
+
await this.scaffolder.ensureConfig(root, {
|
|
4455
|
+
biomeJson: true,
|
|
4456
|
+
checkWorkspace: true
|
|
4457
|
+
});
|
|
4458
|
+
await this.pm.ensureDependency(root, "@biomejs/biome", {
|
|
4459
|
+
checkWorkspace: true,
|
|
4460
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
4461
|
+
});
|
|
4462
|
+
await this.utils.exec("biome check --fix");
|
|
2013
4463
|
}
|
|
2014
4464
|
});
|
|
2015
4465
|
};
|
|
@@ -2074,6 +4524,7 @@ var TypecheckCommand = class {
|
|
|
2074
4524
|
utils = $inject(AlephaCliUtils);
|
|
2075
4525
|
pm = $inject(PackageManagerUtils);
|
|
2076
4526
|
log = $logger();
|
|
4527
|
+
scaffolder = $inject(ProjectScaffolder);
|
|
2077
4528
|
/**
|
|
2078
4529
|
* Run TypeScript type checking across the codebase with no emit.
|
|
2079
4530
|
*/
|
|
@@ -2083,7 +4534,14 @@ var TypecheckCommand = class {
|
|
|
2083
4534
|
description: "Check TypeScript types across the codebase",
|
|
2084
4535
|
handler: async ({ root }) => {
|
|
2085
4536
|
this.log.info("Starting TypeScript type checking...");
|
|
2086
|
-
await this.
|
|
4537
|
+
await this.scaffolder.ensureConfig(root, {
|
|
4538
|
+
tsconfigJson: true,
|
|
4539
|
+
checkWorkspace: true
|
|
4540
|
+
});
|
|
4541
|
+
await this.pm.ensureDependency(root, "typescript", {
|
|
4542
|
+
checkWorkspace: true,
|
|
4543
|
+
exec: (cmd, opts) => this.utils.exec(cmd, opts)
|
|
4544
|
+
});
|
|
2087
4545
|
await this.utils.exec("tsc --noEmit");
|
|
2088
4546
|
this.log.info("TypeScript type checking completed successfully.");
|
|
2089
4547
|
}
|
|
@@ -2127,15 +4585,6 @@ var VerifyCommand = class {
|
|
|
2127
4585
|
//#endregion
|
|
2128
4586
|
//#region ../../src/cli/apps/AlephaCli.ts
|
|
2129
4587
|
/**
|
|
2130
|
-
* Register `tsx` when running in Node.js, ignore for Bun.
|
|
2131
|
-
*
|
|
2132
|
-
* It's required to have a full TypeScript support. (mostly .tsx files)
|
|
2133
|
-
*/
|
|
2134
|
-
if (typeof Bun === "undefined") {
|
|
2135
|
-
const { register } = await import("tsx/esm/api");
|
|
2136
|
-
register();
|
|
2137
|
-
}
|
|
2138
|
-
/**
|
|
2139
4588
|
* Allow to extend Alepha CLI via `alepha.config.ts` file located in the project root.
|
|
2140
4589
|
*/
|
|
2141
4590
|
var AlephaCliExtension = class {
|
|
@@ -2162,7 +4611,6 @@ const AlephaCli = $module({
|
|
|
2162
4611
|
DbCommand,
|
|
2163
4612
|
DeployCommand,
|
|
2164
4613
|
DevCommand,
|
|
2165
|
-
FormatCommand,
|
|
2166
4614
|
InitCommand,
|
|
2167
4615
|
LintCommand,
|
|
2168
4616
|
RootCommand,
|
|
@@ -2170,6 +4618,7 @@ const AlephaCli = $module({
|
|
|
2170
4618
|
TypecheckCommand,
|
|
2171
4619
|
VerifyCommand,
|
|
2172
4620
|
GenCommand,
|
|
4621
|
+
AppEntryProvider,
|
|
2173
4622
|
GitProvider
|
|
2174
4623
|
]
|
|
2175
4624
|
});
|
|
@@ -2232,6 +4681,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
2232
4681
|
sourcemap: true,
|
|
2233
4682
|
fixedExtension: false,
|
|
2234
4683
|
platform: "node",
|
|
4684
|
+
inlineOnly: false,
|
|
2235
4685
|
external,
|
|
2236
4686
|
dts: { sourcemap: true }
|
|
2237
4687
|
});
|
|
@@ -2241,6 +4691,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
2241
4691
|
platform: "neutral",
|
|
2242
4692
|
sourcemap: true,
|
|
2243
4693
|
dts: false,
|
|
4694
|
+
inlineOnly: false,
|
|
2244
4695
|
external
|
|
2245
4696
|
});
|
|
2246
4697
|
if (item.browser) entries.push({
|
|
@@ -2249,6 +4700,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
2249
4700
|
platform: "browser",
|
|
2250
4701
|
sourcemap: true,
|
|
2251
4702
|
dts: false,
|
|
4703
|
+
inlineOnly: false,
|
|
2252
4704
|
external
|
|
2253
4705
|
});
|
|
2254
4706
|
if (item.bun) entries.push({
|
|
@@ -2258,6 +4710,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
2258
4710
|
sourcemap: true,
|
|
2259
4711
|
fixedExtension: false,
|
|
2260
4712
|
dts: false,
|
|
4713
|
+
inlineOnly: false,
|
|
2261
4714
|
external
|
|
2262
4715
|
});
|
|
2263
4716
|
const config = this.fs.join(tmpDir, `tsdown-${item.name.replace("/", "-")}.config.js`);
|
|
@@ -2281,6 +4734,7 @@ var AlephaPackageBuilderCli = class {
|
|
|
2281
4734
|
}
|
|
2282
4735
|
});
|
|
2283
4736
|
};
|
|
4737
|
+
var AlephaPackageBuilderCli_default = AlephaPackageBuilderCli;
|
|
2284
4738
|
async function getAllFiles(dir) {
|
|
2285
4739
|
const files = [];
|
|
2286
4740
|
async function scan(currentDir) {
|
|
@@ -2384,6 +4838,7 @@ const defineConfig = (runConfig) => {
|
|
|
2384
4838
|
if (config.services) for (const it of config.services) alepha.with(it);
|
|
2385
4839
|
if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = String(value);
|
|
2386
4840
|
if (config.build) alepha.set(buildOptions, config.build);
|
|
4841
|
+
if (config.entry) alepha.set(appEntryOptions, config.entry);
|
|
2387
4842
|
return { ...config.commands };
|
|
2388
4843
|
};
|
|
2389
4844
|
};
|
|
@@ -2393,5 +4848,5 @@ const defineConfig = (runConfig) => {
|
|
|
2393
4848
|
const defineAlephaConfig = defineConfig;
|
|
2394
4849
|
|
|
2395
4850
|
//#endregion
|
|
2396
|
-
export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand,
|
|
4851
|
+
export { AlephaCli, AlephaCliUtils, AlephaPackageBuilderCli_default as AlephaPackageBuilderCli, AppEntryProvider, BuildCommand, ChangelogCommand, CleanCommand, DEFAULT_IGNORE, DbCommand, DeployCommand, DevCommand, GitMessageParser, GitProvider, InitCommand, LintCommand, OpenApiCommand, RootCommand, TestCommand, TypecheckCommand, VerifyCommand, analyzeModules, changelogOptions, defineAlephaConfig, defineConfig, version };
|
|
2397
4852
|
//# sourceMappingURL=index.js.map
|