alepha 0.15.1 → 0.15.3
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 +68 -80
- package/dist/api/audits/index.d.ts +10 -33
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +10 -33
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +10 -3
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +10 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +162 -155
- 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.d.ts +10 -4
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +10 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +43 -50
- 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 +1081 -760
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2539 -218
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +138 -132
- 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 +20 -40
- 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 +440 -8
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +1861 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +179 -7
- 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 +4 -0
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +638 -5645
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2550 -368
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +203 -45
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2060 -71
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +70 -40
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +34 -13
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +90 -40
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +70 -40
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +15 -0
- 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 +323 -20
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1857 -7
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +90 -8
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +91 -20
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +11 -4
- 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/logger/index.d.ts +17 -66
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +14 -63
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -30
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +12 -35
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +3 -3
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +39 -20
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +517 -540
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +58 -71
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +18 -10
- 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/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 +1974 -0
- package/dist/react/router/index.browser.js.map +1 -0
- package/dist/react/router/index.d.ts +1956 -0
- package/dist/react/router/index.d.ts.map +1 -0
- package/dist/react/router/index.js +4722 -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 +41 -44
- 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 +11 -2
- 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/scheduler/index.d.ts +11 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +11 -2
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +140 -49
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +164 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +12 -7
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +12 -7
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +7 -22
- 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 +10 -2
- 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 +40 -16
- 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 +124 -23
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +231 -14
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +13 -23
- 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 +8 -2
- 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 +11 -3
- 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.d.ts +11 -6
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +11 -6
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +10 -3
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +10 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +9 -3
- 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 +8 -2
- 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 +30 -35
- 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 +137 -4
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +1853 -5
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +309 -6
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1854 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +309 -7
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +1856 -7
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +1218 -0
- package/dist/system/index.browser.js.map +1 -0
- package/dist/{file → system}/index.d.ts +343 -16
- package/dist/system/index.d.ts.map +1 -0
- package/dist/{file → system}/index.js +419 -22
- package/dist/system/index.js.map +1 -0
- package/dist/thread/index.d.ts +11 -2
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +11 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +12 -5
- 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/vite/index.d.ts +5 -6272
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +23 -10
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +12 -8
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +12 -8
- package/dist/websocket/index.js.map +1 -1
- package/package.json +82 -11
- 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 +55 -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 -1
- 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 +1 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -37
- package/src/cli/commands/db.ts +22 -18
- package/src/cli/commands/deploy.ts +1 -1
- package/src/cli/commands/dev.ts +1 -20
- package/src/cli/commands/gen/env.ts +5 -2
- package/src/cli/commands/gen/openapi.ts +5 -2
- package/src/cli/commands/init.spec.ts +588 -0
- package/src/cli/commands/init.ts +115 -58
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/commands/typecheck.ts +11 -0
- package/src/cli/providers/AppEntryProvider.ts +1 -1
- package/src/cli/providers/ViteBuildProvider.ts +8 -50
- package/src/cli/providers/ViteDevServerProvider.ts +35 -16
- package/src/cli/services/AlephaCliUtils.ts +52 -121
- package/src/cli/services/PackageManagerUtils.ts +129 -11
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +148 -81
- package/src/cli/services/ViteUtils.ts +82 -0
- package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +30 -0
- package/src/cli/templates/gitignore.ts +39 -0
- package/src/cli/{assets → templates}/mainCss.ts +11 -2
- package/src/cli/templates/mainServerTs.ts +33 -0
- package/src/cli/templates/webAppRouterTs.ts +74 -0
- package/src/cli/templates/webHelloComponentTsx.ts +30 -0
- package/src/command/helpers/Runner.spec.ts +139 -0
- package/src/command/helpers/Runner.ts +7 -22
- package/src/command/index.ts +12 -4
- package/src/command/providers/CliProvider.spec.ts +1392 -0
- package/src/command/providers/CliProvider.ts +320 -47
- package/src/core/Alepha.ts +34 -27
- 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/providers/EventManager.spec.ts +0 -71
- package/src/core/providers/EventManager.ts +3 -15
- package/src/core/providers/Json.ts +2 -14
- 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/mcp/index.ts +10 -27
- package/src/mcp/transports/SseMcpTransport.ts +0 -11
- package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.bun.ts +5 -3
- package/src/orm/index.ts +23 -53
- package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/orm/services/Repository.ts +7 -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 +146 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
- package/src/react/router/primitives/$page.spec.tsx +676 -0
- package/src/react/router/primitives/$page.ts +489 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +312 -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/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
- package/src/react/router/providers/ReactServerProvider.ts +487 -0
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -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 +90 -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 +63 -41
- 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 +16 -6
- 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/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
- 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/index.ts +11 -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 +36 -0
- package/src/system/index.ts +62 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/{file → system}/providers/FileSystemProvider.ts +24 -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 +47 -2
- 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/tasks/buildClient.ts +2 -7
- package/src/vite/tasks/buildServer.ts +19 -13
- package/src/vite/tasks/generateCloudflare.ts +10 -7
- package/src/vite/tasks/generateDocker.ts +4 -0
- package/src/websocket/index.ts +12 -8
- package/dist/file/index.d.ts.map +0 -1
- package/dist/file/index.js.map +0 -1
- package/src/cli/assets/apiIndexTs.ts +0 -16
- package/src/cli/assets/mainServerTs.ts +0 -24
- package/src/cli/assets/webAppRouterTs.ts +0 -16
- package/src/cli/assets/webHelloComponentTsx.ts +0 -20
- package/src/cli/providers/ViteTemplateProvider.ts +0 -27
- package/src/file/index.ts +0 -43
- /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
- /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
- /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
- /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
- /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
- /package/src/cli/{assets → templates}/tsconfigJson.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/command/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, KIND, Primitive, TypeBoxError, createPrimitive, t } from "alepha";
|
|
1
|
+
import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Json, KIND, Primitive, TypeBoxError, createPrimitive, isFileLike, t } from "alepha";
|
|
2
2
|
import { stdin, stdout } from "node:process";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
4
|
import { $logger } from "alepha/logger";
|
|
5
5
|
import * as fs from "node:fs/promises";
|
|
6
|
-
import { cp, glob, readFile, rm } from "node:fs/promises";
|
|
6
|
+
import { access, copyFile, cp, glob, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { DateTimeProvider } from "alepha/datetime";
|
|
9
|
-
import {
|
|
9
|
+
import { createReadStream } from "node:fs";
|
|
10
|
+
import { PassThrough, Readable } from "node:stream";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { exec, spawn } from "node:child_process";
|
|
10
13
|
|
|
11
14
|
//#region ../../src/command/helpers/Asker.ts
|
|
12
15
|
var Asker = class {
|
|
@@ -225,6 +228,1846 @@ var PrettyPrint = class {
|
|
|
225
228
|
}
|
|
226
229
|
};
|
|
227
230
|
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region ../../src/system/providers/FileSystemProvider.ts
|
|
233
|
+
/**
|
|
234
|
+
* FileSystem interface providing utilities for working with files.
|
|
235
|
+
*/
|
|
236
|
+
var FileSystemProvider = class {};
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region ../../src/system/providers/MemoryFileSystemProvider.ts
|
|
240
|
+
/**
|
|
241
|
+
* In-memory implementation of FileSystemProvider for testing.
|
|
242
|
+
*
|
|
243
|
+
* This provider stores all files and directories in memory, making it ideal for
|
|
244
|
+
* unit tests that need to verify file operations without touching the real file system.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* // In tests, substitute the real FileSystemProvider with MemoryFileSystemProvider
|
|
249
|
+
* const alepha = Alepha.create().with({
|
|
250
|
+
* provide: FileSystemProvider,
|
|
251
|
+
* use: MemoryFileSystemProvider,
|
|
252
|
+
* });
|
|
253
|
+
*
|
|
254
|
+
* // Run code that uses FileSystemProvider
|
|
255
|
+
* const service = alepha.inject(MyService);
|
|
256
|
+
* await service.saveFile("test.txt", "Hello World");
|
|
257
|
+
*
|
|
258
|
+
* // Verify the file was written
|
|
259
|
+
* const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
260
|
+
* expect(memoryFs.files.get("test.txt")?.toString()).toBe("Hello World");
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
var MemoryFileSystemProvider = class {
|
|
264
|
+
json = $inject(Json);
|
|
265
|
+
/**
|
|
266
|
+
* In-memory storage for files (path -> content)
|
|
267
|
+
*/
|
|
268
|
+
files = /* @__PURE__ */ new Map();
|
|
269
|
+
/**
|
|
270
|
+
* In-memory storage for directories
|
|
271
|
+
*/
|
|
272
|
+
directories = /* @__PURE__ */ new Set();
|
|
273
|
+
/**
|
|
274
|
+
* Track mkdir calls for test assertions
|
|
275
|
+
*/
|
|
276
|
+
mkdirCalls = [];
|
|
277
|
+
/**
|
|
278
|
+
* Track writeFile calls for test assertions
|
|
279
|
+
*/
|
|
280
|
+
writeFileCalls = [];
|
|
281
|
+
/**
|
|
282
|
+
* Track readFile calls for test assertions
|
|
283
|
+
*/
|
|
284
|
+
readFileCalls = [];
|
|
285
|
+
/**
|
|
286
|
+
* Track rm calls for test assertions
|
|
287
|
+
*/
|
|
288
|
+
rmCalls = [];
|
|
289
|
+
/**
|
|
290
|
+
* Track join calls for test assertions
|
|
291
|
+
*/
|
|
292
|
+
joinCalls = [];
|
|
293
|
+
/**
|
|
294
|
+
* Error to throw on mkdir (for testing error handling)
|
|
295
|
+
*/
|
|
296
|
+
mkdirError = null;
|
|
297
|
+
/**
|
|
298
|
+
* Error to throw on writeFile (for testing error handling)
|
|
299
|
+
*/
|
|
300
|
+
writeFileError = null;
|
|
301
|
+
/**
|
|
302
|
+
* Error to throw on readFile (for testing error handling)
|
|
303
|
+
*/
|
|
304
|
+
readFileError = null;
|
|
305
|
+
constructor(options = {}) {
|
|
306
|
+
this.mkdirError = options.mkdirError ?? null;
|
|
307
|
+
this.writeFileError = options.writeFileError ?? null;
|
|
308
|
+
this.readFileError = options.readFileError ?? null;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Join path segments using forward slashes.
|
|
312
|
+
* Uses Node's path.join for proper normalization (handles .. and .)
|
|
313
|
+
*/
|
|
314
|
+
join(...paths) {
|
|
315
|
+
this.joinCalls.push(paths);
|
|
316
|
+
return join(...paths);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Create a FileLike object from various sources.
|
|
320
|
+
*/
|
|
321
|
+
createFile(options) {
|
|
322
|
+
if ("path" in options) {
|
|
323
|
+
const filePath = options.path;
|
|
324
|
+
const buffer = this.files.get(filePath);
|
|
325
|
+
if (buffer === void 0) throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
|
|
326
|
+
return {
|
|
327
|
+
name: options.name ?? filePath.split("/").pop() ?? "file",
|
|
328
|
+
type: options.type ?? "application/octet-stream",
|
|
329
|
+
size: buffer.byteLength,
|
|
330
|
+
lastModified: Date.now(),
|
|
331
|
+
stream: () => {
|
|
332
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
333
|
+
},
|
|
334
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
335
|
+
text: async () => buffer.toString("utf-8")
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if ("buffer" in options) {
|
|
339
|
+
const buffer = options.buffer;
|
|
340
|
+
return {
|
|
341
|
+
name: options.name ?? "file",
|
|
342
|
+
type: options.type ?? "application/octet-stream",
|
|
343
|
+
size: buffer.byteLength,
|
|
344
|
+
lastModified: Date.now(),
|
|
345
|
+
stream: () => {
|
|
346
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
347
|
+
},
|
|
348
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
349
|
+
text: async () => buffer.toString("utf-8")
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if ("text" in options) {
|
|
353
|
+
const buffer = Buffer.from(options.text, "utf-8");
|
|
354
|
+
return {
|
|
355
|
+
name: options.name ?? "file.txt",
|
|
356
|
+
type: options.type ?? "text/plain",
|
|
357
|
+
size: buffer.byteLength,
|
|
358
|
+
lastModified: Date.now(),
|
|
359
|
+
stream: () => {
|
|
360
|
+
throw new Error("Stream not implemented in MemoryFileSystemProvider");
|
|
361
|
+
},
|
|
362
|
+
arrayBuffer: async () => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
|
363
|
+
text: async () => options.text
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
throw new Error("MemoryFileSystemProvider.createFile: unsupported options. Only buffer and text are supported.");
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Remove a file or directory from memory.
|
|
370
|
+
*/
|
|
371
|
+
async rm(path, options) {
|
|
372
|
+
this.rmCalls.push({
|
|
373
|
+
path,
|
|
374
|
+
options
|
|
375
|
+
});
|
|
376
|
+
if (!(this.files.has(path) || this.directories.has(path)) && !options?.force) throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
|
|
377
|
+
if (this.directories.has(path)) if (options?.recursive) {
|
|
378
|
+
this.directories.delete(path);
|
|
379
|
+
for (const filePath of this.files.keys()) if (filePath.startsWith(`${path}/`)) this.files.delete(filePath);
|
|
380
|
+
for (const dirPath of this.directories) if (dirPath.startsWith(`${path}/`)) this.directories.delete(dirPath);
|
|
381
|
+
} else throw new Error(`EISDIR: illegal operation on a directory, rm '${path}'`);
|
|
382
|
+
else this.files.delete(path);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Copy a file or directory in memory.
|
|
386
|
+
*/
|
|
387
|
+
async cp(src, dest, options) {
|
|
388
|
+
if (this.directories.has(src)) {
|
|
389
|
+
if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
|
|
390
|
+
this.directories.add(dest);
|
|
391
|
+
for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
|
|
392
|
+
const newPath = filePath.replace(src, dest);
|
|
393
|
+
this.files.set(newPath, Buffer.from(content));
|
|
394
|
+
}
|
|
395
|
+
} else if (this.files.has(src)) {
|
|
396
|
+
const content = this.files.get(src);
|
|
397
|
+
this.files.set(dest, Buffer.from(content));
|
|
398
|
+
} else throw new Error(`ENOENT: no such file or directory, cp '${src}'`);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Move/rename a file or directory in memory.
|
|
402
|
+
*/
|
|
403
|
+
async mv(src, dest) {
|
|
404
|
+
if (this.directories.has(src)) {
|
|
405
|
+
this.directories.delete(src);
|
|
406
|
+
this.directories.add(dest);
|
|
407
|
+
for (const [filePath, content] of this.files) if (filePath.startsWith(`${src}/`)) {
|
|
408
|
+
const newPath = filePath.replace(src, dest);
|
|
409
|
+
this.files.delete(filePath);
|
|
410
|
+
this.files.set(newPath, content);
|
|
411
|
+
}
|
|
412
|
+
} else if (this.files.has(src)) {
|
|
413
|
+
const content = this.files.get(src);
|
|
414
|
+
this.files.delete(src);
|
|
415
|
+
this.files.set(dest, content);
|
|
416
|
+
} else throw new Error(`ENOENT: no such file or directory, mv '${src}'`);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Create a directory in memory.
|
|
420
|
+
*/
|
|
421
|
+
async mkdir(path, options) {
|
|
422
|
+
this.mkdirCalls.push({
|
|
423
|
+
path,
|
|
424
|
+
options
|
|
425
|
+
});
|
|
426
|
+
if (this.mkdirError) throw this.mkdirError;
|
|
427
|
+
if (this.directories.has(path) && !options?.recursive) throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
|
|
428
|
+
this.directories.add(path);
|
|
429
|
+
if (options?.recursive) {
|
|
430
|
+
const parts = path.split("/").filter(Boolean);
|
|
431
|
+
let current = "";
|
|
432
|
+
for (const part of parts) {
|
|
433
|
+
current = current ? `${current}/${part}` : part;
|
|
434
|
+
this.directories.add(current);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* List files in a directory.
|
|
440
|
+
*/
|
|
441
|
+
async ls(path, options) {
|
|
442
|
+
const normalizedPath = path.replace(/\/$/, "");
|
|
443
|
+
const entries = /* @__PURE__ */ new Set();
|
|
444
|
+
for (const filePath of this.files.keys()) if (filePath.startsWith(`${normalizedPath}/`)) {
|
|
445
|
+
const relativePath = filePath.slice(normalizedPath.length + 1);
|
|
446
|
+
const parts = relativePath.split("/");
|
|
447
|
+
if (options?.recursive) entries.add(relativePath);
|
|
448
|
+
else entries.add(parts[0]);
|
|
449
|
+
}
|
|
450
|
+
for (const dirPath of this.directories) if (dirPath.startsWith(`${normalizedPath}/`) && dirPath !== normalizedPath) {
|
|
451
|
+
const relativePath = dirPath.slice(normalizedPath.length + 1);
|
|
452
|
+
const parts = relativePath.split("/");
|
|
453
|
+
if (options?.recursive) entries.add(relativePath);
|
|
454
|
+
else if (parts.length === 1) entries.add(parts[0]);
|
|
455
|
+
}
|
|
456
|
+
let result = Array.from(entries);
|
|
457
|
+
if (!options?.hidden) result = result.filter((entry) => !entry.startsWith("."));
|
|
458
|
+
return result.sort();
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check if a file or directory exists in memory.
|
|
462
|
+
*/
|
|
463
|
+
async exists(path) {
|
|
464
|
+
return this.files.has(path) || this.directories.has(path);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Read a file from memory.
|
|
468
|
+
*/
|
|
469
|
+
async readFile(path) {
|
|
470
|
+
this.readFileCalls.push(path);
|
|
471
|
+
if (this.readFileError) throw this.readFileError;
|
|
472
|
+
const content = this.files.get(path);
|
|
473
|
+
if (!content) throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
474
|
+
return content;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Read a file from memory as text.
|
|
478
|
+
*/
|
|
479
|
+
async readTextFile(path) {
|
|
480
|
+
return (await this.readFile(path)).toString("utf-8");
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Read a file from memory as JSON.
|
|
484
|
+
*/
|
|
485
|
+
async readJsonFile(path) {
|
|
486
|
+
const text = await this.readTextFile(path);
|
|
487
|
+
return this.json.parse(text);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Write a file to memory.
|
|
491
|
+
*/
|
|
492
|
+
async writeFile(path, data) {
|
|
493
|
+
const dataStr = typeof data === "string" ? data : data instanceof Buffer || data instanceof Uint8Array ? data.toString("utf-8") : await data.text();
|
|
494
|
+
this.writeFileCalls.push({
|
|
495
|
+
path,
|
|
496
|
+
data: dataStr
|
|
497
|
+
});
|
|
498
|
+
if (this.writeFileError) throw this.writeFileError;
|
|
499
|
+
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");
|
|
500
|
+
this.files.set(path, buffer);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Reset all in-memory state (useful between tests).
|
|
504
|
+
*/
|
|
505
|
+
reset() {
|
|
506
|
+
this.files.clear();
|
|
507
|
+
this.directories.clear();
|
|
508
|
+
this.mkdirCalls = [];
|
|
509
|
+
this.writeFileCalls = [];
|
|
510
|
+
this.readFileCalls = [];
|
|
511
|
+
this.rmCalls = [];
|
|
512
|
+
this.joinCalls = [];
|
|
513
|
+
this.mkdirError = null;
|
|
514
|
+
this.writeFileError = null;
|
|
515
|
+
this.readFileError = null;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Check if a file was written during the test.
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
|
|
523
|
+
* ```
|
|
524
|
+
*/
|
|
525
|
+
wasWritten(path) {
|
|
526
|
+
return this.writeFileCalls.some((call) => call.path === path);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Check if a file was written with content matching a pattern.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```typescript
|
|
533
|
+
* expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
wasWrittenMatching(path, pattern) {
|
|
537
|
+
const call = this.writeFileCalls.find((c) => c.path === path);
|
|
538
|
+
return call ? pattern.test(call.data) : false;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Check if a file was read during the test.
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* ```typescript
|
|
545
|
+
* expect(fs.wasRead("/project/package.json")).toBe(true);
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
wasRead(path) {
|
|
549
|
+
return this.readFileCalls.includes(path);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Check if a file was deleted during the test.
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
wasDeleted(path) {
|
|
560
|
+
return this.rmCalls.some((call) => call.path === path);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get the content of a file as a string (convenience method for testing).
|
|
564
|
+
*/
|
|
565
|
+
getFileContent(path) {
|
|
566
|
+
return this.files.get(path)?.toString("utf-8");
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region ../../src/system/providers/MemoryShellProvider.ts
|
|
572
|
+
/**
|
|
573
|
+
* In-memory implementation of ShellProvider for testing.
|
|
574
|
+
*
|
|
575
|
+
* Records all commands that would be executed without actually running them.
|
|
576
|
+
* Can be configured to return specific outputs or throw errors for testing.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```typescript
|
|
580
|
+
* // In tests, substitute the real ShellProvider with MemoryShellProvider
|
|
581
|
+
* const alepha = Alepha.create().with({
|
|
582
|
+
* provide: ShellProvider,
|
|
583
|
+
* use: MemoryShellProvider,
|
|
584
|
+
* });
|
|
585
|
+
*
|
|
586
|
+
* // Configure mock behavior
|
|
587
|
+
* const shell = alepha.inject(MemoryShellProvider);
|
|
588
|
+
* shell.configure({
|
|
589
|
+
* outputs: { "echo hello": "hello\n" },
|
|
590
|
+
* errors: { "failing-cmd": "Command failed" },
|
|
591
|
+
* });
|
|
592
|
+
*
|
|
593
|
+
* // Or use the fluent API
|
|
594
|
+
* shell.outputs.set("another-cmd", "output");
|
|
595
|
+
* shell.errors.set("another-error", "Error message");
|
|
596
|
+
*
|
|
597
|
+
* // Run code that uses ShellProvider
|
|
598
|
+
* const service = alepha.inject(MyService);
|
|
599
|
+
* await service.doSomething();
|
|
600
|
+
*
|
|
601
|
+
* // Verify commands were called
|
|
602
|
+
* expect(shell.calls).toHaveLength(2);
|
|
603
|
+
* expect(shell.calls[0].command).toBe("yarn install");
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
var MemoryShellProvider = class {
|
|
607
|
+
/**
|
|
608
|
+
* All recorded shell calls.
|
|
609
|
+
*/
|
|
610
|
+
calls = [];
|
|
611
|
+
/**
|
|
612
|
+
* Simulated outputs for specific commands.
|
|
613
|
+
*/
|
|
614
|
+
outputs = /* @__PURE__ */ new Map();
|
|
615
|
+
/**
|
|
616
|
+
* Commands that should throw an error.
|
|
617
|
+
*/
|
|
618
|
+
errors = /* @__PURE__ */ new Map();
|
|
619
|
+
/**
|
|
620
|
+
* Commands considered installed in the system PATH.
|
|
621
|
+
*/
|
|
622
|
+
installedCommands = /* @__PURE__ */ new Set();
|
|
623
|
+
/**
|
|
624
|
+
* Configure the mock with predefined outputs, errors, and installed commands.
|
|
625
|
+
*/
|
|
626
|
+
configure(options) {
|
|
627
|
+
if (options.outputs) for (const [cmd, output] of Object.entries(options.outputs)) this.outputs.set(cmd, output);
|
|
628
|
+
if (options.errors) for (const [cmd, error] of Object.entries(options.errors)) this.errors.set(cmd, error);
|
|
629
|
+
if (options.installedCommands) for (const cmd of options.installedCommands) this.installedCommands.add(cmd);
|
|
630
|
+
return this;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Record command and return simulated output.
|
|
634
|
+
*/
|
|
635
|
+
async run(command, options = {}) {
|
|
636
|
+
this.calls.push({
|
|
637
|
+
command,
|
|
638
|
+
options
|
|
639
|
+
});
|
|
640
|
+
const errorMsg = this.errors.get(command);
|
|
641
|
+
if (errorMsg) throw new Error(errorMsg);
|
|
642
|
+
return this.outputs.get(command) ?? "";
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Check if a specific command was called.
|
|
646
|
+
*/
|
|
647
|
+
wasCalled(command) {
|
|
648
|
+
return this.calls.some((call) => call.command === command);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Check if a command matching a pattern was called.
|
|
652
|
+
*/
|
|
653
|
+
wasCalledMatching(pattern) {
|
|
654
|
+
return this.calls.some((call) => pattern.test(call.command));
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get all calls matching a pattern.
|
|
658
|
+
*/
|
|
659
|
+
getCallsMatching(pattern) {
|
|
660
|
+
return this.calls.filter((call) => pattern.test(call.command));
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Check if a command is installed.
|
|
664
|
+
*/
|
|
665
|
+
async isInstalled(command) {
|
|
666
|
+
return this.installedCommands.has(command);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Reset all recorded state.
|
|
670
|
+
*/
|
|
671
|
+
reset() {
|
|
672
|
+
this.calls = [];
|
|
673
|
+
this.outputs.clear();
|
|
674
|
+
this.errors.clear();
|
|
675
|
+
this.installedCommands.clear();
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region ../../src/system/services/FileDetector.ts
|
|
681
|
+
/**
|
|
682
|
+
* Service for detecting file types and getting content types.
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```typescript
|
|
686
|
+
* const detector = alepha.inject(FileDetector);
|
|
687
|
+
*
|
|
688
|
+
* // Get content type from filename
|
|
689
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
690
|
+
*
|
|
691
|
+
* // Detect file type by magic bytes
|
|
692
|
+
* const stream = createReadStream('image.png');
|
|
693
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
694
|
+
* console.log(result.mimeType); // 'image/png'
|
|
695
|
+
* console.log(result.verified); // true if magic bytes match
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
var FileDetector = class FileDetector {
|
|
699
|
+
/**
|
|
700
|
+
* Magic byte signatures for common file formats.
|
|
701
|
+
* Each signature is represented as an array of bytes or null (wildcard).
|
|
702
|
+
*/
|
|
703
|
+
static MAGIC_BYTES = {
|
|
704
|
+
png: [{
|
|
705
|
+
signature: [
|
|
706
|
+
137,
|
|
707
|
+
80,
|
|
708
|
+
78,
|
|
709
|
+
71,
|
|
710
|
+
13,
|
|
711
|
+
10,
|
|
712
|
+
26,
|
|
713
|
+
10
|
|
714
|
+
],
|
|
715
|
+
mimeType: "image/png"
|
|
716
|
+
}],
|
|
717
|
+
jpg: [
|
|
718
|
+
{
|
|
719
|
+
signature: [
|
|
720
|
+
255,
|
|
721
|
+
216,
|
|
722
|
+
255,
|
|
723
|
+
224
|
|
724
|
+
],
|
|
725
|
+
mimeType: "image/jpeg"
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
signature: [
|
|
729
|
+
255,
|
|
730
|
+
216,
|
|
731
|
+
255,
|
|
732
|
+
225
|
|
733
|
+
],
|
|
734
|
+
mimeType: "image/jpeg"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
signature: [
|
|
738
|
+
255,
|
|
739
|
+
216,
|
|
740
|
+
255,
|
|
741
|
+
226
|
|
742
|
+
],
|
|
743
|
+
mimeType: "image/jpeg"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
signature: [
|
|
747
|
+
255,
|
|
748
|
+
216,
|
|
749
|
+
255,
|
|
750
|
+
227
|
|
751
|
+
],
|
|
752
|
+
mimeType: "image/jpeg"
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
signature: [
|
|
756
|
+
255,
|
|
757
|
+
216,
|
|
758
|
+
255,
|
|
759
|
+
232
|
|
760
|
+
],
|
|
761
|
+
mimeType: "image/jpeg"
|
|
762
|
+
}
|
|
763
|
+
],
|
|
764
|
+
jpeg: [
|
|
765
|
+
{
|
|
766
|
+
signature: [
|
|
767
|
+
255,
|
|
768
|
+
216,
|
|
769
|
+
255,
|
|
770
|
+
224
|
|
771
|
+
],
|
|
772
|
+
mimeType: "image/jpeg"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
signature: [
|
|
776
|
+
255,
|
|
777
|
+
216,
|
|
778
|
+
255,
|
|
779
|
+
225
|
|
780
|
+
],
|
|
781
|
+
mimeType: "image/jpeg"
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
signature: [
|
|
785
|
+
255,
|
|
786
|
+
216,
|
|
787
|
+
255,
|
|
788
|
+
226
|
|
789
|
+
],
|
|
790
|
+
mimeType: "image/jpeg"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
signature: [
|
|
794
|
+
255,
|
|
795
|
+
216,
|
|
796
|
+
255,
|
|
797
|
+
227
|
|
798
|
+
],
|
|
799
|
+
mimeType: "image/jpeg"
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
signature: [
|
|
803
|
+
255,
|
|
804
|
+
216,
|
|
805
|
+
255,
|
|
806
|
+
232
|
|
807
|
+
],
|
|
808
|
+
mimeType: "image/jpeg"
|
|
809
|
+
}
|
|
810
|
+
],
|
|
811
|
+
gif: [{
|
|
812
|
+
signature: [
|
|
813
|
+
71,
|
|
814
|
+
73,
|
|
815
|
+
70,
|
|
816
|
+
56,
|
|
817
|
+
55,
|
|
818
|
+
97
|
|
819
|
+
],
|
|
820
|
+
mimeType: "image/gif"
|
|
821
|
+
}, {
|
|
822
|
+
signature: [
|
|
823
|
+
71,
|
|
824
|
+
73,
|
|
825
|
+
70,
|
|
826
|
+
56,
|
|
827
|
+
57,
|
|
828
|
+
97
|
|
829
|
+
],
|
|
830
|
+
mimeType: "image/gif"
|
|
831
|
+
}],
|
|
832
|
+
webp: [{
|
|
833
|
+
signature: [
|
|
834
|
+
82,
|
|
835
|
+
73,
|
|
836
|
+
70,
|
|
837
|
+
70,
|
|
838
|
+
null,
|
|
839
|
+
null,
|
|
840
|
+
null,
|
|
841
|
+
null,
|
|
842
|
+
87,
|
|
843
|
+
69,
|
|
844
|
+
66,
|
|
845
|
+
80
|
|
846
|
+
],
|
|
847
|
+
mimeType: "image/webp"
|
|
848
|
+
}],
|
|
849
|
+
bmp: [{
|
|
850
|
+
signature: [66, 77],
|
|
851
|
+
mimeType: "image/bmp"
|
|
852
|
+
}],
|
|
853
|
+
ico: [{
|
|
854
|
+
signature: [
|
|
855
|
+
0,
|
|
856
|
+
0,
|
|
857
|
+
1,
|
|
858
|
+
0
|
|
859
|
+
],
|
|
860
|
+
mimeType: "image/x-icon"
|
|
861
|
+
}],
|
|
862
|
+
tiff: [{
|
|
863
|
+
signature: [
|
|
864
|
+
73,
|
|
865
|
+
73,
|
|
866
|
+
42,
|
|
867
|
+
0
|
|
868
|
+
],
|
|
869
|
+
mimeType: "image/tiff"
|
|
870
|
+
}, {
|
|
871
|
+
signature: [
|
|
872
|
+
77,
|
|
873
|
+
77,
|
|
874
|
+
0,
|
|
875
|
+
42
|
|
876
|
+
],
|
|
877
|
+
mimeType: "image/tiff"
|
|
878
|
+
}],
|
|
879
|
+
tif: [{
|
|
880
|
+
signature: [
|
|
881
|
+
73,
|
|
882
|
+
73,
|
|
883
|
+
42,
|
|
884
|
+
0
|
|
885
|
+
],
|
|
886
|
+
mimeType: "image/tiff"
|
|
887
|
+
}, {
|
|
888
|
+
signature: [
|
|
889
|
+
77,
|
|
890
|
+
77,
|
|
891
|
+
0,
|
|
892
|
+
42
|
|
893
|
+
],
|
|
894
|
+
mimeType: "image/tiff"
|
|
895
|
+
}],
|
|
896
|
+
pdf: [{
|
|
897
|
+
signature: [
|
|
898
|
+
37,
|
|
899
|
+
80,
|
|
900
|
+
68,
|
|
901
|
+
70,
|
|
902
|
+
45
|
|
903
|
+
],
|
|
904
|
+
mimeType: "application/pdf"
|
|
905
|
+
}],
|
|
906
|
+
zip: [
|
|
907
|
+
{
|
|
908
|
+
signature: [
|
|
909
|
+
80,
|
|
910
|
+
75,
|
|
911
|
+
3,
|
|
912
|
+
4
|
|
913
|
+
],
|
|
914
|
+
mimeType: "application/zip"
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
signature: [
|
|
918
|
+
80,
|
|
919
|
+
75,
|
|
920
|
+
5,
|
|
921
|
+
6
|
|
922
|
+
],
|
|
923
|
+
mimeType: "application/zip"
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
signature: [
|
|
927
|
+
80,
|
|
928
|
+
75,
|
|
929
|
+
7,
|
|
930
|
+
8
|
|
931
|
+
],
|
|
932
|
+
mimeType: "application/zip"
|
|
933
|
+
}
|
|
934
|
+
],
|
|
935
|
+
rar: [{
|
|
936
|
+
signature: [
|
|
937
|
+
82,
|
|
938
|
+
97,
|
|
939
|
+
114,
|
|
940
|
+
33,
|
|
941
|
+
26,
|
|
942
|
+
7
|
|
943
|
+
],
|
|
944
|
+
mimeType: "application/vnd.rar"
|
|
945
|
+
}],
|
|
946
|
+
"7z": [{
|
|
947
|
+
signature: [
|
|
948
|
+
55,
|
|
949
|
+
122,
|
|
950
|
+
188,
|
|
951
|
+
175,
|
|
952
|
+
39,
|
|
953
|
+
28
|
|
954
|
+
],
|
|
955
|
+
mimeType: "application/x-7z-compressed"
|
|
956
|
+
}],
|
|
957
|
+
tar: [{
|
|
958
|
+
signature: [
|
|
959
|
+
117,
|
|
960
|
+
115,
|
|
961
|
+
116,
|
|
962
|
+
97,
|
|
963
|
+
114
|
|
964
|
+
],
|
|
965
|
+
mimeType: "application/x-tar"
|
|
966
|
+
}],
|
|
967
|
+
gz: [{
|
|
968
|
+
signature: [31, 139],
|
|
969
|
+
mimeType: "application/gzip"
|
|
970
|
+
}],
|
|
971
|
+
tgz: [{
|
|
972
|
+
signature: [31, 139],
|
|
973
|
+
mimeType: "application/gzip"
|
|
974
|
+
}],
|
|
975
|
+
mp3: [
|
|
976
|
+
{
|
|
977
|
+
signature: [255, 251],
|
|
978
|
+
mimeType: "audio/mpeg"
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
signature: [255, 243],
|
|
982
|
+
mimeType: "audio/mpeg"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
signature: [255, 242],
|
|
986
|
+
mimeType: "audio/mpeg"
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
signature: [
|
|
990
|
+
73,
|
|
991
|
+
68,
|
|
992
|
+
51
|
|
993
|
+
],
|
|
994
|
+
mimeType: "audio/mpeg"
|
|
995
|
+
}
|
|
996
|
+
],
|
|
997
|
+
wav: [{
|
|
998
|
+
signature: [
|
|
999
|
+
82,
|
|
1000
|
+
73,
|
|
1001
|
+
70,
|
|
1002
|
+
70,
|
|
1003
|
+
null,
|
|
1004
|
+
null,
|
|
1005
|
+
null,
|
|
1006
|
+
null,
|
|
1007
|
+
87,
|
|
1008
|
+
65,
|
|
1009
|
+
86,
|
|
1010
|
+
69
|
|
1011
|
+
],
|
|
1012
|
+
mimeType: "audio/wav"
|
|
1013
|
+
}],
|
|
1014
|
+
ogg: [{
|
|
1015
|
+
signature: [
|
|
1016
|
+
79,
|
|
1017
|
+
103,
|
|
1018
|
+
103,
|
|
1019
|
+
83
|
|
1020
|
+
],
|
|
1021
|
+
mimeType: "audio/ogg"
|
|
1022
|
+
}],
|
|
1023
|
+
flac: [{
|
|
1024
|
+
signature: [
|
|
1025
|
+
102,
|
|
1026
|
+
76,
|
|
1027
|
+
97,
|
|
1028
|
+
67
|
|
1029
|
+
],
|
|
1030
|
+
mimeType: "audio/flac"
|
|
1031
|
+
}],
|
|
1032
|
+
mp4: [
|
|
1033
|
+
{
|
|
1034
|
+
signature: [
|
|
1035
|
+
null,
|
|
1036
|
+
null,
|
|
1037
|
+
null,
|
|
1038
|
+
null,
|
|
1039
|
+
102,
|
|
1040
|
+
116,
|
|
1041
|
+
121,
|
|
1042
|
+
112
|
|
1043
|
+
],
|
|
1044
|
+
mimeType: "video/mp4"
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
signature: [
|
|
1048
|
+
null,
|
|
1049
|
+
null,
|
|
1050
|
+
null,
|
|
1051
|
+
null,
|
|
1052
|
+
102,
|
|
1053
|
+
116,
|
|
1054
|
+
121,
|
|
1055
|
+
112,
|
|
1056
|
+
105,
|
|
1057
|
+
115,
|
|
1058
|
+
111,
|
|
1059
|
+
109
|
|
1060
|
+
],
|
|
1061
|
+
mimeType: "video/mp4"
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
signature: [
|
|
1065
|
+
null,
|
|
1066
|
+
null,
|
|
1067
|
+
null,
|
|
1068
|
+
null,
|
|
1069
|
+
102,
|
|
1070
|
+
116,
|
|
1071
|
+
121,
|
|
1072
|
+
112,
|
|
1073
|
+
109,
|
|
1074
|
+
112,
|
|
1075
|
+
52,
|
|
1076
|
+
50
|
|
1077
|
+
],
|
|
1078
|
+
mimeType: "video/mp4"
|
|
1079
|
+
}
|
|
1080
|
+
],
|
|
1081
|
+
webm: [{
|
|
1082
|
+
signature: [
|
|
1083
|
+
26,
|
|
1084
|
+
69,
|
|
1085
|
+
223,
|
|
1086
|
+
163
|
|
1087
|
+
],
|
|
1088
|
+
mimeType: "video/webm"
|
|
1089
|
+
}],
|
|
1090
|
+
avi: [{
|
|
1091
|
+
signature: [
|
|
1092
|
+
82,
|
|
1093
|
+
73,
|
|
1094
|
+
70,
|
|
1095
|
+
70,
|
|
1096
|
+
null,
|
|
1097
|
+
null,
|
|
1098
|
+
null,
|
|
1099
|
+
null,
|
|
1100
|
+
65,
|
|
1101
|
+
86,
|
|
1102
|
+
73,
|
|
1103
|
+
32
|
|
1104
|
+
],
|
|
1105
|
+
mimeType: "video/x-msvideo"
|
|
1106
|
+
}],
|
|
1107
|
+
mov: [{
|
|
1108
|
+
signature: [
|
|
1109
|
+
null,
|
|
1110
|
+
null,
|
|
1111
|
+
null,
|
|
1112
|
+
null,
|
|
1113
|
+
102,
|
|
1114
|
+
116,
|
|
1115
|
+
121,
|
|
1116
|
+
112,
|
|
1117
|
+
113,
|
|
1118
|
+
116,
|
|
1119
|
+
32,
|
|
1120
|
+
32
|
|
1121
|
+
],
|
|
1122
|
+
mimeType: "video/quicktime"
|
|
1123
|
+
}],
|
|
1124
|
+
mkv: [{
|
|
1125
|
+
signature: [
|
|
1126
|
+
26,
|
|
1127
|
+
69,
|
|
1128
|
+
223,
|
|
1129
|
+
163
|
|
1130
|
+
],
|
|
1131
|
+
mimeType: "video/x-matroska"
|
|
1132
|
+
}],
|
|
1133
|
+
docx: [{
|
|
1134
|
+
signature: [
|
|
1135
|
+
80,
|
|
1136
|
+
75,
|
|
1137
|
+
3,
|
|
1138
|
+
4
|
|
1139
|
+
],
|
|
1140
|
+
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
1141
|
+
}],
|
|
1142
|
+
xlsx: [{
|
|
1143
|
+
signature: [
|
|
1144
|
+
80,
|
|
1145
|
+
75,
|
|
1146
|
+
3,
|
|
1147
|
+
4
|
|
1148
|
+
],
|
|
1149
|
+
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
1150
|
+
}],
|
|
1151
|
+
pptx: [{
|
|
1152
|
+
signature: [
|
|
1153
|
+
80,
|
|
1154
|
+
75,
|
|
1155
|
+
3,
|
|
1156
|
+
4
|
|
1157
|
+
],
|
|
1158
|
+
mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
1159
|
+
}],
|
|
1160
|
+
doc: [{
|
|
1161
|
+
signature: [
|
|
1162
|
+
208,
|
|
1163
|
+
207,
|
|
1164
|
+
17,
|
|
1165
|
+
224,
|
|
1166
|
+
161,
|
|
1167
|
+
177,
|
|
1168
|
+
26,
|
|
1169
|
+
225
|
|
1170
|
+
],
|
|
1171
|
+
mimeType: "application/msword"
|
|
1172
|
+
}],
|
|
1173
|
+
xls: [{
|
|
1174
|
+
signature: [
|
|
1175
|
+
208,
|
|
1176
|
+
207,
|
|
1177
|
+
17,
|
|
1178
|
+
224,
|
|
1179
|
+
161,
|
|
1180
|
+
177,
|
|
1181
|
+
26,
|
|
1182
|
+
225
|
|
1183
|
+
],
|
|
1184
|
+
mimeType: "application/vnd.ms-excel"
|
|
1185
|
+
}],
|
|
1186
|
+
ppt: [{
|
|
1187
|
+
signature: [
|
|
1188
|
+
208,
|
|
1189
|
+
207,
|
|
1190
|
+
17,
|
|
1191
|
+
224,
|
|
1192
|
+
161,
|
|
1193
|
+
177,
|
|
1194
|
+
26,
|
|
1195
|
+
225
|
|
1196
|
+
],
|
|
1197
|
+
mimeType: "application/vnd.ms-powerpoint"
|
|
1198
|
+
}]
|
|
1199
|
+
};
|
|
1200
|
+
/**
|
|
1201
|
+
* All possible format signatures for checking against actual file content
|
|
1202
|
+
*/
|
|
1203
|
+
static ALL_SIGNATURES = Object.entries(FileDetector.MAGIC_BYTES).flatMap(([ext, signatures]) => signatures.map((sig) => ({
|
|
1204
|
+
ext,
|
|
1205
|
+
...sig
|
|
1206
|
+
})));
|
|
1207
|
+
/**
|
|
1208
|
+
* MIME type map for file extensions.
|
|
1209
|
+
*
|
|
1210
|
+
* Can be used to get the content type of file based on its extension.
|
|
1211
|
+
* Feel free to add more mime types in your project!
|
|
1212
|
+
*/
|
|
1213
|
+
static mimeMap = {
|
|
1214
|
+
json: "application/json",
|
|
1215
|
+
txt: "text/plain",
|
|
1216
|
+
html: "text/html",
|
|
1217
|
+
htm: "text/html",
|
|
1218
|
+
xml: "application/xml",
|
|
1219
|
+
csv: "text/csv",
|
|
1220
|
+
pdf: "application/pdf",
|
|
1221
|
+
md: "text/markdown",
|
|
1222
|
+
markdown: "text/markdown",
|
|
1223
|
+
rtf: "application/rtf",
|
|
1224
|
+
css: "text/css",
|
|
1225
|
+
js: "application/javascript",
|
|
1226
|
+
mjs: "application/javascript",
|
|
1227
|
+
ts: "application/typescript",
|
|
1228
|
+
jsx: "text/jsx",
|
|
1229
|
+
tsx: "text/tsx",
|
|
1230
|
+
zip: "application/zip",
|
|
1231
|
+
rar: "application/vnd.rar",
|
|
1232
|
+
"7z": "application/x-7z-compressed",
|
|
1233
|
+
tar: "application/x-tar",
|
|
1234
|
+
gz: "application/gzip",
|
|
1235
|
+
tgz: "application/gzip",
|
|
1236
|
+
png: "image/png",
|
|
1237
|
+
jpg: "image/jpeg",
|
|
1238
|
+
jpeg: "image/jpeg",
|
|
1239
|
+
gif: "image/gif",
|
|
1240
|
+
webp: "image/webp",
|
|
1241
|
+
svg: "image/svg+xml",
|
|
1242
|
+
bmp: "image/bmp",
|
|
1243
|
+
ico: "image/x-icon",
|
|
1244
|
+
tiff: "image/tiff",
|
|
1245
|
+
tif: "image/tiff",
|
|
1246
|
+
mp3: "audio/mpeg",
|
|
1247
|
+
wav: "audio/wav",
|
|
1248
|
+
ogg: "audio/ogg",
|
|
1249
|
+
m4a: "audio/mp4",
|
|
1250
|
+
aac: "audio/aac",
|
|
1251
|
+
flac: "audio/flac",
|
|
1252
|
+
mp4: "video/mp4",
|
|
1253
|
+
webm: "video/webm",
|
|
1254
|
+
avi: "video/x-msvideo",
|
|
1255
|
+
mov: "video/quicktime",
|
|
1256
|
+
wmv: "video/x-ms-wmv",
|
|
1257
|
+
flv: "video/x-flv",
|
|
1258
|
+
mkv: "video/x-matroska",
|
|
1259
|
+
doc: "application/msword",
|
|
1260
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1261
|
+
xls: "application/vnd.ms-excel",
|
|
1262
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1263
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
1264
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1265
|
+
woff: "font/woff",
|
|
1266
|
+
woff2: "font/woff2",
|
|
1267
|
+
ttf: "font/ttf",
|
|
1268
|
+
otf: "font/otf",
|
|
1269
|
+
eot: "application/vnd.ms-fontobject"
|
|
1270
|
+
};
|
|
1271
|
+
/**
|
|
1272
|
+
* Reverse MIME type map for looking up extensions from MIME types.
|
|
1273
|
+
* Prefers shorter, more common extensions when multiple exist.
|
|
1274
|
+
*/
|
|
1275
|
+
static reverseMimeMap = (() => {
|
|
1276
|
+
const reverse = {};
|
|
1277
|
+
for (const [ext, mimeType] of Object.entries(FileDetector.mimeMap)) if (!reverse[mimeType]) reverse[mimeType] = ext;
|
|
1278
|
+
return reverse;
|
|
1279
|
+
})();
|
|
1280
|
+
/**
|
|
1281
|
+
* Returns the file extension for a given MIME type.
|
|
1282
|
+
*
|
|
1283
|
+
* @param mimeType - The MIME type to look up
|
|
1284
|
+
* @returns The file extension (without dot), or "bin" if not found
|
|
1285
|
+
*
|
|
1286
|
+
* @example
|
|
1287
|
+
* ```typescript
|
|
1288
|
+
* const detector = alepha.inject(FileDetector);
|
|
1289
|
+
* const ext = detector.getExtensionFromMimeType("image/png"); // "png"
|
|
1290
|
+
* const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
|
|
1291
|
+
* ```
|
|
1292
|
+
*/
|
|
1293
|
+
getExtensionFromMimeType(mimeType) {
|
|
1294
|
+
return FileDetector.reverseMimeMap[mimeType] || "bin";
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Returns the content type of file based on its filename.
|
|
1298
|
+
*
|
|
1299
|
+
* @param filename - The filename to check
|
|
1300
|
+
* @returns The MIME type
|
|
1301
|
+
*
|
|
1302
|
+
* @example
|
|
1303
|
+
* ```typescript
|
|
1304
|
+
* const detector = alepha.inject(FileDetector);
|
|
1305
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
1306
|
+
* ```
|
|
1307
|
+
*/
|
|
1308
|
+
getContentType(filename) {
|
|
1309
|
+
const ext = filename.toLowerCase().split(".").pop() || "";
|
|
1310
|
+
return FileDetector.mimeMap[ext] || "application/octet-stream";
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Detects the file type by checking magic bytes against the stream content.
|
|
1314
|
+
*
|
|
1315
|
+
* @param stream - The readable stream to check
|
|
1316
|
+
* @param filename - The filename (used to get the extension)
|
|
1317
|
+
* @returns File type information including MIME type, extension, and verification status
|
|
1318
|
+
*
|
|
1319
|
+
* @example
|
|
1320
|
+
* ```typescript
|
|
1321
|
+
* const detector = alepha.inject(FileDetector);
|
|
1322
|
+
* const stream = createReadStream('image.png');
|
|
1323
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
1324
|
+
* console.log(result.mimeType); // 'image/png'
|
|
1325
|
+
* console.log(result.verified); // true if magic bytes match
|
|
1326
|
+
* ```
|
|
1327
|
+
*/
|
|
1328
|
+
async detectFileType(stream, filename) {
|
|
1329
|
+
const expectedMimeType = this.getContentType(filename);
|
|
1330
|
+
const lastDotIndex = filename.lastIndexOf(".");
|
|
1331
|
+
const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex + 1).toLowerCase() : "";
|
|
1332
|
+
const { buffer, stream: newStream } = await this.peekBytes(stream, 16);
|
|
1333
|
+
const expectedSignatures = FileDetector.MAGIC_BYTES[ext];
|
|
1334
|
+
if (expectedSignatures) {
|
|
1335
|
+
for (const { signature, mimeType } of expectedSignatures) if (this.matchesSignature(buffer, signature)) return {
|
|
1336
|
+
mimeType,
|
|
1337
|
+
extension: ext,
|
|
1338
|
+
verified: true,
|
|
1339
|
+
stream: newStream
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
for (const { ext: detectedExt, signature, mimeType } of FileDetector.ALL_SIGNATURES) if (detectedExt !== ext && this.matchesSignature(buffer, signature)) return {
|
|
1343
|
+
mimeType,
|
|
1344
|
+
extension: detectedExt,
|
|
1345
|
+
verified: true,
|
|
1346
|
+
stream: newStream
|
|
1347
|
+
};
|
|
1348
|
+
return {
|
|
1349
|
+
mimeType: expectedMimeType,
|
|
1350
|
+
extension: ext,
|
|
1351
|
+
verified: false,
|
|
1352
|
+
stream: newStream
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
|
|
1357
|
+
* This approach reads the entire stream upfront to avoid complex async handling issues.
|
|
1358
|
+
*
|
|
1359
|
+
* @protected
|
|
1360
|
+
*/
|
|
1361
|
+
async peekBytes(stream, numBytes) {
|
|
1362
|
+
const chunks = [];
|
|
1363
|
+
for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1364
|
+
const allData = Buffer.concat(chunks);
|
|
1365
|
+
return {
|
|
1366
|
+
buffer: allData.subarray(0, numBytes),
|
|
1367
|
+
stream: Readable.from(allData)
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Checks if a buffer matches a magic byte signature.
|
|
1372
|
+
*
|
|
1373
|
+
* @protected
|
|
1374
|
+
*/
|
|
1375
|
+
matchesSignature(buffer, signature) {
|
|
1376
|
+
if (buffer.length < signature.length) return false;
|
|
1377
|
+
for (let i = 0; i < signature.length; i++) if (signature[i] !== null && buffer[i] !== signature[i]) return false;
|
|
1378
|
+
return true;
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region ../../src/system/providers/NodeFileSystemProvider.ts
|
|
1384
|
+
/**
|
|
1385
|
+
* Node.js implementation of FileSystem interface.
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* ```typescript
|
|
1389
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1390
|
+
*
|
|
1391
|
+
* // Create from URL
|
|
1392
|
+
* const file1 = fs.createFile({ url: "file:///path/to/file.png" });
|
|
1393
|
+
*
|
|
1394
|
+
* // Create from Buffer
|
|
1395
|
+
* const file2 = fs.createFile({ buffer: Buffer.from("hello"), name: "hello.txt" });
|
|
1396
|
+
*
|
|
1397
|
+
* // Create from text
|
|
1398
|
+
* const file3 = fs.createFile({ text: "Hello, world!", name: "greeting.txt" });
|
|
1399
|
+
*
|
|
1400
|
+
* // File operations
|
|
1401
|
+
* await fs.mkdir("/tmp/mydir", { recursive: true });
|
|
1402
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt");
|
|
1403
|
+
* await fs.mv("/old/path.txt", "/new/path.txt");
|
|
1404
|
+
* const files = await fs.ls("/tmp");
|
|
1405
|
+
* await fs.rm("/tmp/file.txt");
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
var NodeFileSystemProvider = class {
|
|
1409
|
+
detector = $inject(FileDetector);
|
|
1410
|
+
json = $inject(Json);
|
|
1411
|
+
join(...paths) {
|
|
1412
|
+
return join(...paths);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Creates a FileLike object from various sources.
|
|
1416
|
+
*
|
|
1417
|
+
* @param options - Options for creating the file
|
|
1418
|
+
* @returns A FileLike object
|
|
1419
|
+
*
|
|
1420
|
+
* @example
|
|
1421
|
+
* ```typescript
|
|
1422
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1423
|
+
*
|
|
1424
|
+
* // From URL
|
|
1425
|
+
* const file1 = fs.createFile({ url: "https://example.com/image.png" });
|
|
1426
|
+
*
|
|
1427
|
+
* // From Buffer
|
|
1428
|
+
* const file2 = fs.createFile({
|
|
1429
|
+
* buffer: Buffer.from("hello"),
|
|
1430
|
+
* name: "hello.txt",
|
|
1431
|
+
* type: "text/plain"
|
|
1432
|
+
* });
|
|
1433
|
+
*
|
|
1434
|
+
* // From text
|
|
1435
|
+
* const file3 = fs.createFile({ text: "Hello!", name: "greeting.txt" });
|
|
1436
|
+
*
|
|
1437
|
+
* // From stream with detection
|
|
1438
|
+
* const stream = createReadStream("/path/to/file.png");
|
|
1439
|
+
* const file4 = fs.createFile({ stream, name: "image.png" });
|
|
1440
|
+
* ```
|
|
1441
|
+
*/
|
|
1442
|
+
createFile(options) {
|
|
1443
|
+
if ("path" in options) {
|
|
1444
|
+
const path = options.path;
|
|
1445
|
+
const filename = path.split("/").pop() || "file";
|
|
1446
|
+
return this.createFileFromUrl(`file://${path}`, {
|
|
1447
|
+
type: options.type,
|
|
1448
|
+
name: options.name || filename
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if ("url" in options) return this.createFileFromUrl(options.url, {
|
|
1452
|
+
type: options.type,
|
|
1453
|
+
name: options.name
|
|
1454
|
+
});
|
|
1455
|
+
if ("response" in options) {
|
|
1456
|
+
if (!options.response.body) throw new AlephaError("Response has no body stream");
|
|
1457
|
+
const res = options.response;
|
|
1458
|
+
const sizeHeader = res.headers.get("content-length");
|
|
1459
|
+
const size = sizeHeader ? parseInt(sizeHeader, 10) : void 0;
|
|
1460
|
+
let name = options.name;
|
|
1461
|
+
const contentDisposition = res.headers.get("content-disposition");
|
|
1462
|
+
if (contentDisposition && !name) {
|
|
1463
|
+
const match = contentDisposition.match(/filename="?([^"]+)"?/);
|
|
1464
|
+
if (match) name = match[1];
|
|
1465
|
+
}
|
|
1466
|
+
const type = options.type || res.headers.get("content-type") || void 0;
|
|
1467
|
+
return this.createFileFromStream(options.response.body, {
|
|
1468
|
+
type,
|
|
1469
|
+
name,
|
|
1470
|
+
size
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
if ("file" in options) return this.createFileFromWebFile(options.file, {
|
|
1474
|
+
type: options.type,
|
|
1475
|
+
name: options.name,
|
|
1476
|
+
size: options.size
|
|
1477
|
+
});
|
|
1478
|
+
if ("buffer" in options) return this.createFileFromBuffer(options.buffer, {
|
|
1479
|
+
type: options.type,
|
|
1480
|
+
name: options.name
|
|
1481
|
+
});
|
|
1482
|
+
if ("arrayBuffer" in options) return this.createFileFromBuffer(Buffer.from(options.arrayBuffer), {
|
|
1483
|
+
type: options.type,
|
|
1484
|
+
name: options.name
|
|
1485
|
+
});
|
|
1486
|
+
if ("text" in options) return this.createFileFromBuffer(Buffer.from(options.text, "utf-8"), {
|
|
1487
|
+
type: options.type || "text/plain",
|
|
1488
|
+
name: options.name || "file.txt"
|
|
1489
|
+
});
|
|
1490
|
+
if ("stream" in options) return this.createFileFromStream(options.stream, {
|
|
1491
|
+
type: options.type,
|
|
1492
|
+
name: options.name,
|
|
1493
|
+
size: options.size
|
|
1494
|
+
});
|
|
1495
|
+
throw new AlephaError("Invalid createFile options: no valid source provided");
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Removes a file or directory.
|
|
1499
|
+
*
|
|
1500
|
+
* @param path - The path to remove
|
|
1501
|
+
* @param options - Remove options
|
|
1502
|
+
*
|
|
1503
|
+
* @example
|
|
1504
|
+
* ```typescript
|
|
1505
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1506
|
+
*
|
|
1507
|
+
* // Remove a file
|
|
1508
|
+
* await fs.rm("/tmp/file.txt");
|
|
1509
|
+
*
|
|
1510
|
+
* // Remove a directory recursively
|
|
1511
|
+
* await fs.rm("/tmp/mydir", { recursive: true });
|
|
1512
|
+
*
|
|
1513
|
+
* // Remove with force (no error if doesn't exist)
|
|
1514
|
+
* await fs.rm("/tmp/maybe-exists.txt", { force: true });
|
|
1515
|
+
* ```
|
|
1516
|
+
*/
|
|
1517
|
+
async rm(path, options) {
|
|
1518
|
+
await rm(path, options);
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Copies a file or directory.
|
|
1522
|
+
*
|
|
1523
|
+
* @param src - Source path
|
|
1524
|
+
* @param dest - Destination path
|
|
1525
|
+
* @param options - Copy options
|
|
1526
|
+
*
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```typescript
|
|
1529
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1530
|
+
*
|
|
1531
|
+
* // Copy a file
|
|
1532
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt");
|
|
1533
|
+
*
|
|
1534
|
+
* // Copy a directory recursively
|
|
1535
|
+
* await fs.cp("/src/dir", "/dest/dir", { recursive: true });
|
|
1536
|
+
*
|
|
1537
|
+
* // Copy with force (overwrite existing)
|
|
1538
|
+
* await fs.cp("/src/file.txt", "/dest/file.txt", { force: true });
|
|
1539
|
+
* ```
|
|
1540
|
+
*/
|
|
1541
|
+
async cp(src, dest, options) {
|
|
1542
|
+
if ((await stat(src)).isDirectory()) {
|
|
1543
|
+
if (!options?.recursive) throw new Error(`Cannot copy directory without recursive option: ${src}`);
|
|
1544
|
+
await cp(src, dest, {
|
|
1545
|
+
recursive: true,
|
|
1546
|
+
force: options?.force ?? false
|
|
1547
|
+
});
|
|
1548
|
+
} else await copyFile(src, dest);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Moves/renames a file or directory.
|
|
1552
|
+
*
|
|
1553
|
+
* @param src - Source path
|
|
1554
|
+
* @param dest - Destination path
|
|
1555
|
+
*
|
|
1556
|
+
* @example
|
|
1557
|
+
* ```typescript
|
|
1558
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1559
|
+
*
|
|
1560
|
+
* // Move/rename a file
|
|
1561
|
+
* await fs.mv("/old/path.txt", "/new/path.txt");
|
|
1562
|
+
*
|
|
1563
|
+
* // Move a directory
|
|
1564
|
+
* await fs.mv("/old/dir", "/new/dir");
|
|
1565
|
+
* ```
|
|
1566
|
+
*/
|
|
1567
|
+
async mv(src, dest) {
|
|
1568
|
+
await rename(src, dest);
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Creates a directory.
|
|
1572
|
+
*
|
|
1573
|
+
* @param path - The directory path to create
|
|
1574
|
+
* @param options - Mkdir options
|
|
1575
|
+
*
|
|
1576
|
+
* @example
|
|
1577
|
+
* ```typescript
|
|
1578
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1579
|
+
*
|
|
1580
|
+
* // Create a directory
|
|
1581
|
+
* await fs.mkdir("/tmp/mydir");
|
|
1582
|
+
*
|
|
1583
|
+
* // Create nested directories
|
|
1584
|
+
* await fs.mkdir("/tmp/path/to/dir", { recursive: true });
|
|
1585
|
+
*
|
|
1586
|
+
* // Create with specific permissions
|
|
1587
|
+
* await fs.mkdir("/tmp/mydir", { mode: 0o755 });
|
|
1588
|
+
* ```
|
|
1589
|
+
*/
|
|
1590
|
+
async mkdir(path, options = {}) {
|
|
1591
|
+
const p = mkdir(path, {
|
|
1592
|
+
recursive: options.recursive ?? true,
|
|
1593
|
+
mode: options.mode
|
|
1594
|
+
});
|
|
1595
|
+
if (options.force === false) await p;
|
|
1596
|
+
else await p.catch(() => {});
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Lists files in a directory.
|
|
1600
|
+
*
|
|
1601
|
+
* @param path - The directory path to list
|
|
1602
|
+
* @param options - List options
|
|
1603
|
+
* @returns Array of filenames
|
|
1604
|
+
*
|
|
1605
|
+
* @example
|
|
1606
|
+
* ```typescript
|
|
1607
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1608
|
+
*
|
|
1609
|
+
* // List files in a directory
|
|
1610
|
+
* const files = await fs.ls("/tmp");
|
|
1611
|
+
* console.log(files); // ["file1.txt", "file2.txt", "subdir"]
|
|
1612
|
+
*
|
|
1613
|
+
* // List with hidden files
|
|
1614
|
+
* const allFiles = await fs.ls("/tmp", { hidden: true });
|
|
1615
|
+
*
|
|
1616
|
+
* // List recursively
|
|
1617
|
+
* const allFilesRecursive = await fs.ls("/tmp", { recursive: true });
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
async ls(path, options) {
|
|
1621
|
+
const entries = await readdir(path);
|
|
1622
|
+
const filteredEntries = options?.hidden ? entries : entries.filter((e) => !e.startsWith("."));
|
|
1623
|
+
if (options?.recursive) {
|
|
1624
|
+
const allFiles = [];
|
|
1625
|
+
for (const entry of filteredEntries) {
|
|
1626
|
+
const fullPath = join(path, entry);
|
|
1627
|
+
if ((await stat(fullPath)).isDirectory()) {
|
|
1628
|
+
allFiles.push(entry);
|
|
1629
|
+
const subFiles = await this.ls(fullPath, options);
|
|
1630
|
+
allFiles.push(...subFiles.map((f) => join(entry, f)));
|
|
1631
|
+
} else allFiles.push(entry);
|
|
1632
|
+
}
|
|
1633
|
+
return allFiles;
|
|
1634
|
+
}
|
|
1635
|
+
return filteredEntries;
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Checks if a file or directory exists.
|
|
1639
|
+
*
|
|
1640
|
+
* @param path - The path to check
|
|
1641
|
+
* @returns True if the path exists, false otherwise
|
|
1642
|
+
*
|
|
1643
|
+
* @example
|
|
1644
|
+
* ```typescript
|
|
1645
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1646
|
+
*
|
|
1647
|
+
* if (await fs.exists("/tmp/file.txt")) {
|
|
1648
|
+
* console.log("File exists");
|
|
1649
|
+
* }
|
|
1650
|
+
* ```
|
|
1651
|
+
*/
|
|
1652
|
+
async exists(path) {
|
|
1653
|
+
try {
|
|
1654
|
+
await access(path);
|
|
1655
|
+
return true;
|
|
1656
|
+
} catch {
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Reads the content of a file.
|
|
1662
|
+
*
|
|
1663
|
+
* @param path - The file path to read
|
|
1664
|
+
* @returns The file content as a Buffer
|
|
1665
|
+
*
|
|
1666
|
+
* @example
|
|
1667
|
+
* ```typescript
|
|
1668
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1669
|
+
*
|
|
1670
|
+
* const buffer = await fs.readFile("/tmp/file.txt");
|
|
1671
|
+
* console.log(buffer.toString("utf-8"));
|
|
1672
|
+
* ```
|
|
1673
|
+
*/
|
|
1674
|
+
async readFile(path) {
|
|
1675
|
+
return await readFile(path);
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Writes data to a file.
|
|
1679
|
+
*
|
|
1680
|
+
* @param path - The file path to write to
|
|
1681
|
+
* @param data - The data to write (Buffer or string)
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* ```typescript
|
|
1685
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1686
|
+
*
|
|
1687
|
+
* // Write string
|
|
1688
|
+
* await fs.writeFile("/tmp/file.txt", "Hello, world!");
|
|
1689
|
+
*
|
|
1690
|
+
* // Write Buffer
|
|
1691
|
+
* await fs.writeFile("/tmp/file.bin", Buffer.from([0x01, 0x02, 0x03]));
|
|
1692
|
+
* ```
|
|
1693
|
+
*/
|
|
1694
|
+
async writeFile(path, data) {
|
|
1695
|
+
if (isFileLike(data)) {
|
|
1696
|
+
await writeFile(path, Readable.from(data.stream()));
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
await writeFile(path, data);
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Reads the content of a file as a string.
|
|
1703
|
+
*
|
|
1704
|
+
* @param path - The file path to read
|
|
1705
|
+
* @returns The file content as a string
|
|
1706
|
+
*
|
|
1707
|
+
* @example
|
|
1708
|
+
* ```typescript
|
|
1709
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1710
|
+
* const content = await fs.readTextFile("/tmp/file.txt");
|
|
1711
|
+
* ```
|
|
1712
|
+
*/
|
|
1713
|
+
async readTextFile(path) {
|
|
1714
|
+
return (await this.readFile(path)).toString("utf-8");
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Reads the content of a file as JSON.
|
|
1718
|
+
*
|
|
1719
|
+
* @param path - The file path to read
|
|
1720
|
+
* @returns The parsed JSON content
|
|
1721
|
+
*
|
|
1722
|
+
* @example
|
|
1723
|
+
* ```typescript
|
|
1724
|
+
* const fs = alepha.inject(NodeFileSystemProvider);
|
|
1725
|
+
* const config = await fs.readJsonFile<{ name: string }>("/tmp/config.json");
|
|
1726
|
+
* ```
|
|
1727
|
+
*/
|
|
1728
|
+
async readJsonFile(path) {
|
|
1729
|
+
const text = await this.readTextFile(path);
|
|
1730
|
+
return this.json.parse(text);
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Creates a FileLike object from a Web File.
|
|
1734
|
+
*
|
|
1735
|
+
* @protected
|
|
1736
|
+
*/
|
|
1737
|
+
createFileFromWebFile(source, options = {}) {
|
|
1738
|
+
const name = options.name ?? source.name;
|
|
1739
|
+
return {
|
|
1740
|
+
name,
|
|
1741
|
+
type: options.type ?? (source.type || this.detector.getContentType(name)),
|
|
1742
|
+
size: options.size ?? source.size ?? 0,
|
|
1743
|
+
lastModified: source.lastModified || Date.now(),
|
|
1744
|
+
stream: () => source.stream(),
|
|
1745
|
+
arrayBuffer: async () => {
|
|
1746
|
+
return await source.arrayBuffer();
|
|
1747
|
+
},
|
|
1748
|
+
text: async () => {
|
|
1749
|
+
return await source.text();
|
|
1750
|
+
}
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Creates a FileLike object from a Buffer.
|
|
1755
|
+
*
|
|
1756
|
+
* @protected
|
|
1757
|
+
*/
|
|
1758
|
+
createFileFromBuffer(source, options = {}) {
|
|
1759
|
+
const name = options.name ?? "file";
|
|
1760
|
+
return {
|
|
1761
|
+
name,
|
|
1762
|
+
type: options.type ?? this.detector.getContentType(options.name ?? name),
|
|
1763
|
+
size: source.byteLength,
|
|
1764
|
+
lastModified: Date.now(),
|
|
1765
|
+
stream: () => Readable.from(source),
|
|
1766
|
+
arrayBuffer: async () => {
|
|
1767
|
+
return this.bufferToArrayBuffer(source);
|
|
1768
|
+
},
|
|
1769
|
+
text: async () => {
|
|
1770
|
+
return source.toString("utf-8");
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Creates a FileLike object from a stream.
|
|
1776
|
+
*
|
|
1777
|
+
* @protected
|
|
1778
|
+
*/
|
|
1779
|
+
createFileFromStream(source, options = {}) {
|
|
1780
|
+
let buffer = null;
|
|
1781
|
+
return {
|
|
1782
|
+
name: options.name ?? "file",
|
|
1783
|
+
type: options.type ?? this.detector.getContentType(options.name ?? "file"),
|
|
1784
|
+
size: options.size ?? 0,
|
|
1785
|
+
lastModified: Date.now(),
|
|
1786
|
+
stream: () => source,
|
|
1787
|
+
_buffer: null,
|
|
1788
|
+
arrayBuffer: async () => {
|
|
1789
|
+
buffer ??= await this.streamToBuffer(source);
|
|
1790
|
+
return this.bufferToArrayBuffer(buffer);
|
|
1791
|
+
},
|
|
1792
|
+
text: async () => {
|
|
1793
|
+
buffer ??= await this.streamToBuffer(source);
|
|
1794
|
+
return buffer.toString("utf-8");
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Creates a FileLike object from a URL.
|
|
1800
|
+
*
|
|
1801
|
+
* @protected
|
|
1802
|
+
*/
|
|
1803
|
+
createFileFromUrl(url, options = {}) {
|
|
1804
|
+
const parsedUrl = new URL(url);
|
|
1805
|
+
const filename = options.name || parsedUrl.pathname.split("/").pop() || "file";
|
|
1806
|
+
let buffer = null;
|
|
1807
|
+
return {
|
|
1808
|
+
name: filename,
|
|
1809
|
+
type: options.type ?? this.detector.getContentType(filename),
|
|
1810
|
+
size: 0,
|
|
1811
|
+
lastModified: Date.now(),
|
|
1812
|
+
stream: () => this.createStreamFromUrl(url),
|
|
1813
|
+
arrayBuffer: async () => {
|
|
1814
|
+
buffer ??= await this.loadFromUrl(url);
|
|
1815
|
+
return this.bufferToArrayBuffer(buffer);
|
|
1816
|
+
},
|
|
1817
|
+
text: async () => {
|
|
1818
|
+
buffer ??= await this.loadFromUrl(url);
|
|
1819
|
+
return buffer.toString("utf-8");
|
|
1820
|
+
},
|
|
1821
|
+
filepath: url
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Gets a streaming response from a URL.
|
|
1826
|
+
*
|
|
1827
|
+
* @protected
|
|
1828
|
+
*/
|
|
1829
|
+
getStreamingResponse(url) {
|
|
1830
|
+
const stream = new PassThrough();
|
|
1831
|
+
fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err));
|
|
1832
|
+
return stream;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Loads data from a URL.
|
|
1836
|
+
*
|
|
1837
|
+
* @protected
|
|
1838
|
+
*/
|
|
1839
|
+
async loadFromUrl(url) {
|
|
1840
|
+
const parsedUrl = new URL(url);
|
|
1841
|
+
if (parsedUrl.protocol === "file:") return await readFile(fileURLToPath(url));
|
|
1842
|
+
else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
|
1843
|
+
const response = await fetch(url);
|
|
1844
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
1845
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1846
|
+
return Buffer.from(arrayBuffer);
|
|
1847
|
+
} else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Creates a stream from a URL.
|
|
1851
|
+
*
|
|
1852
|
+
* @protected
|
|
1853
|
+
*/
|
|
1854
|
+
createStreamFromUrl(url) {
|
|
1855
|
+
const parsedUrl = new URL(url);
|
|
1856
|
+
if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url));
|
|
1857
|
+
else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return this.getStreamingResponse(url);
|
|
1858
|
+
else throw new AlephaError(`Unsupported protocol: ${parsedUrl.protocol}`);
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Converts a stream-like object to a Buffer.
|
|
1862
|
+
*
|
|
1863
|
+
* @protected
|
|
1864
|
+
*/
|
|
1865
|
+
async streamToBuffer(streamLike) {
|
|
1866
|
+
const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike);
|
|
1867
|
+
return new Promise((resolve, reject) => {
|
|
1868
|
+
const buffer = [];
|
|
1869
|
+
stream.on("data", (chunk) => buffer.push(Buffer.from(chunk)));
|
|
1870
|
+
stream.on("end", () => resolve(Buffer.concat(buffer)));
|
|
1871
|
+
stream.on("error", (err) => reject(new AlephaError("Error converting stream", { cause: err })));
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Converts a Node.js Buffer to an ArrayBuffer.
|
|
1876
|
+
*
|
|
1877
|
+
* @protected
|
|
1878
|
+
*/
|
|
1879
|
+
bufferToArrayBuffer(buffer) {
|
|
1880
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
|
|
1884
|
+
//#endregion
|
|
1885
|
+
//#region ../../src/system/providers/NodeShellProvider.ts
|
|
1886
|
+
/**
|
|
1887
|
+
* Node.js implementation of ShellProvider.
|
|
1888
|
+
*
|
|
1889
|
+
* Executes shell commands using Node.js child_process module.
|
|
1890
|
+
* Supports binary resolution from node_modules/.bin for local packages.
|
|
1891
|
+
*/
|
|
1892
|
+
var NodeShellProvider = class {
|
|
1893
|
+
log = $logger();
|
|
1894
|
+
fs = $inject(FileSystemProvider);
|
|
1895
|
+
/**
|
|
1896
|
+
* Run a shell command or binary.
|
|
1897
|
+
*/
|
|
1898
|
+
async run(command, options = {}) {
|
|
1899
|
+
const { resolve = false, capture = false, root, env } = options;
|
|
1900
|
+
const cwd = root ?? process.cwd();
|
|
1901
|
+
this.log.debug(`Shell: ${command}`, {
|
|
1902
|
+
cwd,
|
|
1903
|
+
resolve,
|
|
1904
|
+
capture
|
|
1905
|
+
});
|
|
1906
|
+
let executable;
|
|
1907
|
+
let args;
|
|
1908
|
+
if (resolve) {
|
|
1909
|
+
const [bin, ...rest] = command.split(" ");
|
|
1910
|
+
executable = await this.resolveExecutable(bin, cwd);
|
|
1911
|
+
args = rest;
|
|
1912
|
+
} else [executable, ...args] = command.split(" ");
|
|
1913
|
+
if (capture) return this.execCapture(command, {
|
|
1914
|
+
cwd,
|
|
1915
|
+
env
|
|
1916
|
+
});
|
|
1917
|
+
return this.execInherit(executable, args, {
|
|
1918
|
+
cwd,
|
|
1919
|
+
env
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Execute command with inherited stdio (streams to terminal).
|
|
1924
|
+
*/
|
|
1925
|
+
async execInherit(executable, args, options) {
|
|
1926
|
+
const proc = spawn(executable, args, {
|
|
1927
|
+
stdio: "inherit",
|
|
1928
|
+
cwd: options.cwd,
|
|
1929
|
+
env: {
|
|
1930
|
+
...process.env,
|
|
1931
|
+
...options.env
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
return new Promise((resolve, reject) => {
|
|
1935
|
+
proc.on("exit", (code) => {
|
|
1936
|
+
if (code === 0 || code === null) resolve("");
|
|
1937
|
+
else reject(new AlephaError(`Command exited with code ${code}`));
|
|
1938
|
+
});
|
|
1939
|
+
proc.on("error", reject);
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Execute command and capture stdout.
|
|
1944
|
+
*/
|
|
1945
|
+
execCapture(command, options) {
|
|
1946
|
+
return new Promise((resolve, reject) => {
|
|
1947
|
+
exec(command, {
|
|
1948
|
+
cwd: options.cwd,
|
|
1949
|
+
env: {
|
|
1950
|
+
...process.env,
|
|
1951
|
+
LOG_FORMAT: "pretty",
|
|
1952
|
+
...options.env
|
|
1953
|
+
}
|
|
1954
|
+
}, (err, stdout) => {
|
|
1955
|
+
if (err) {
|
|
1956
|
+
err.stdout = stdout;
|
|
1957
|
+
reject(err);
|
|
1958
|
+
} else resolve(stdout);
|
|
1959
|
+
});
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Resolve executable path from node_modules/.bin.
|
|
1964
|
+
*
|
|
1965
|
+
* Search order:
|
|
1966
|
+
* 1. Local: node_modules/.bin/
|
|
1967
|
+
* 2. Pnpm nested: node_modules/alepha/node_modules/.bin/
|
|
1968
|
+
* 3. Monorepo: Walk up to 3 parent directories
|
|
1969
|
+
*/
|
|
1970
|
+
async resolveExecutable(name, root) {
|
|
1971
|
+
const suffix = process.platform === "win32" ? ".cmd" : "";
|
|
1972
|
+
let execPath = await this.findExecutable(root, `node_modules/.bin/${name}${suffix}`);
|
|
1973
|
+
if (!execPath) execPath = await this.findExecutable(root, `node_modules/alepha/node_modules/.bin/${name}${suffix}`);
|
|
1974
|
+
if (!execPath) {
|
|
1975
|
+
let parentDir = this.fs.join(root, "..");
|
|
1976
|
+
for (let i = 0; i < 3; i++) {
|
|
1977
|
+
execPath = await this.findExecutable(parentDir, `node_modules/.bin/${name}${suffix}`);
|
|
1978
|
+
if (execPath) break;
|
|
1979
|
+
parentDir = this.fs.join(parentDir, "..");
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (!execPath) throw new AlephaError(`Could not find executable for '${name}'. Make sure the package is installed.`);
|
|
1983
|
+
return execPath;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Check if executable exists at path.
|
|
1987
|
+
*/
|
|
1988
|
+
async findExecutable(root, relativePath) {
|
|
1989
|
+
const fullPath = this.fs.join(root, relativePath);
|
|
1990
|
+
if (await this.fs.exists(fullPath)) return fullPath;
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Check if a command is installed and available in the system PATH.
|
|
1994
|
+
*/
|
|
1995
|
+
isInstalled(command) {
|
|
1996
|
+
return new Promise((resolve) => {
|
|
1997
|
+
exec(process.platform === "win32" ? `where ${command}` : `command -v ${command}`, (error) => resolve(!error));
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
//#endregion
|
|
2003
|
+
//#region ../../src/system/providers/ShellProvider.ts
|
|
2004
|
+
/**
|
|
2005
|
+
* Abstract provider for executing shell commands and binaries.
|
|
2006
|
+
*
|
|
2007
|
+
* Implementations:
|
|
2008
|
+
* - `NodeShellProvider` - Real shell execution using Node.js child_process
|
|
2009
|
+
* - `MemoryShellProvider` - In-memory mock for testing
|
|
2010
|
+
*
|
|
2011
|
+
* @example
|
|
2012
|
+
* ```typescript
|
|
2013
|
+
* class MyService {
|
|
2014
|
+
* protected readonly shell = $inject(ShellProvider);
|
|
2015
|
+
*
|
|
2016
|
+
* async build() {
|
|
2017
|
+
* // Run shell command directly
|
|
2018
|
+
* await this.shell.run("yarn install");
|
|
2019
|
+
*
|
|
2020
|
+
* // Run local binary with resolution
|
|
2021
|
+
* await this.shell.run("vite build", { resolve: true });
|
|
2022
|
+
*
|
|
2023
|
+
* // Capture output
|
|
2024
|
+
* const output = await this.shell.run("echo hello", { capture: true });
|
|
2025
|
+
* }
|
|
2026
|
+
* }
|
|
2027
|
+
* ```
|
|
2028
|
+
*/
|
|
2029
|
+
var ShellProvider = class {};
|
|
2030
|
+
|
|
2031
|
+
//#endregion
|
|
2032
|
+
//#region ../../src/system/index.ts
|
|
2033
|
+
/**
|
|
2034
|
+
* | type | quality | stability |
|
|
2035
|
+
* |------|---------|-----------|
|
|
2036
|
+
* | tooling | standard | stable |
|
|
2037
|
+
*
|
|
2038
|
+
* System-level abstractions for portable code across runtimes.
|
|
2039
|
+
*
|
|
2040
|
+
* **Features:**
|
|
2041
|
+
* - File system operations (read, write, exists, etc.)
|
|
2042
|
+
* - Shell command execution
|
|
2043
|
+
* - File type detection and MIME utilities
|
|
2044
|
+
* - Memory implementations for testing
|
|
2045
|
+
*
|
|
2046
|
+
* @module alepha.system
|
|
2047
|
+
*/
|
|
2048
|
+
const AlephaSystem = $module({
|
|
2049
|
+
name: "alepha.system",
|
|
2050
|
+
primitives: [],
|
|
2051
|
+
services: [
|
|
2052
|
+
FileDetector,
|
|
2053
|
+
FileSystemProvider,
|
|
2054
|
+
MemoryFileSystemProvider,
|
|
2055
|
+
NodeFileSystemProvider,
|
|
2056
|
+
ShellProvider,
|
|
2057
|
+
MemoryShellProvider,
|
|
2058
|
+
NodeShellProvider
|
|
2059
|
+
],
|
|
2060
|
+
register: (alepha) => alepha.with({
|
|
2061
|
+
optional: true,
|
|
2062
|
+
provide: FileSystemProvider,
|
|
2063
|
+
use: NodeFileSystemProvider
|
|
2064
|
+
}).with({
|
|
2065
|
+
optional: true,
|
|
2066
|
+
provide: ShellProvider,
|
|
2067
|
+
use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider
|
|
2068
|
+
})
|
|
2069
|
+
});
|
|
2070
|
+
|
|
228
2071
|
//#endregion
|
|
229
2072
|
//#region ../../src/command/errors/CommandError.ts
|
|
230
2073
|
var CommandError = class extends AlephaError {
|
|
@@ -239,10 +2082,12 @@ var Runner = class {
|
|
|
239
2082
|
startTime = Date.now();
|
|
240
2083
|
prettyPrint = $inject(PrettyPrint);
|
|
241
2084
|
alepha = $inject(Alepha);
|
|
2085
|
+
shell = $inject(ShellProvider);
|
|
242
2086
|
run;
|
|
243
2087
|
cliName = "";
|
|
244
2088
|
commandName = "";
|
|
245
2089
|
firstTaskStarted = false;
|
|
2090
|
+
taskCounter = 0;
|
|
246
2091
|
constructor() {
|
|
247
2092
|
this.run = this.createRunMethod();
|
|
248
2093
|
}
|
|
@@ -256,6 +2101,8 @@ var Runner = class {
|
|
|
256
2101
|
startCommand(cliName, commandName) {
|
|
257
2102
|
this.cliName = cliName;
|
|
258
2103
|
this.commandName = commandName;
|
|
2104
|
+
this.firstTaskStarted = false;
|
|
2105
|
+
this.taskCounter = 0;
|
|
259
2106
|
}
|
|
260
2107
|
createRunMethod() {
|
|
261
2108
|
const runFn = async (cmd, options) => {
|
|
@@ -305,19 +2152,9 @@ var Runner = class {
|
|
|
305
2152
|
return runFn;
|
|
306
2153
|
}
|
|
307
2154
|
async exec(cmd, opts = {}) {
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
env: {
|
|
312
|
-
...process.env,
|
|
313
|
-
LOG_FORMAT: "pretty"
|
|
314
|
-
}
|
|
315
|
-
}, (err, stdout) => {
|
|
316
|
-
if (err) {
|
|
317
|
-
err.stdout = stdout;
|
|
318
|
-
reject(err);
|
|
319
|
-
} else resolve(stdout);
|
|
320
|
-
});
|
|
2155
|
+
return this.shell.run(cmd, {
|
|
2156
|
+
root: opts.root,
|
|
2157
|
+
capture: true
|
|
321
2158
|
});
|
|
322
2159
|
}
|
|
323
2160
|
/**
|
|
@@ -347,7 +2184,7 @@ var Runner = class {
|
|
|
347
2184
|
}
|
|
348
2185
|
async executeTask(task) {
|
|
349
2186
|
const now = Date.now();
|
|
350
|
-
const taskId = task.
|
|
2187
|
+
const taskId = `task-${++this.taskCounter}`;
|
|
351
2188
|
if (this.useDynamicLogger) this.prettyPrint.startSpinner(taskId, task.name);
|
|
352
2189
|
else this.log.info(`Starting '${task.name}' ...`);
|
|
353
2190
|
let stdout = "";
|
|
@@ -494,6 +2331,32 @@ const cliOptions = $atom({
|
|
|
494
2331
|
}),
|
|
495
2332
|
default: {}
|
|
496
2333
|
});
|
|
2334
|
+
/**
|
|
2335
|
+
* CLI provider for parsing and executing commands.
|
|
2336
|
+
*
|
|
2337
|
+
* Handles:
|
|
2338
|
+
* - Command resolution (simple, nested, colon-notation)
|
|
2339
|
+
* - Flag and argument parsing
|
|
2340
|
+
* - Environment variable validation
|
|
2341
|
+
* - Help generation
|
|
2342
|
+
* - Pre/post command hooks
|
|
2343
|
+
*
|
|
2344
|
+
* @example
|
|
2345
|
+
* ```typescript
|
|
2346
|
+
* // Define a command
|
|
2347
|
+
* class MyCommands {
|
|
2348
|
+
* build = $command({
|
|
2349
|
+
* name: "build",
|
|
2350
|
+
* description: "Build the project",
|
|
2351
|
+
* flags: t.object({ watch: t.optional(t.boolean()) }),
|
|
2352
|
+
* handler: async ({ flags }) => { ... }
|
|
2353
|
+
* });
|
|
2354
|
+
* }
|
|
2355
|
+
*
|
|
2356
|
+
* // CLI automatically discovers and executes commands
|
|
2357
|
+
* const alepha = Alepha.create().with(MyCommands);
|
|
2358
|
+
* ```
|
|
2359
|
+
*/
|
|
497
2360
|
var CliProvider = class {
|
|
498
2361
|
env = $env(envSchema);
|
|
499
2362
|
alepha = $inject(Alepha);
|
|
@@ -512,11 +2375,18 @@ var CliProvider = class {
|
|
|
512
2375
|
get argv() {
|
|
513
2376
|
return this.options.argv || (typeof process !== "undefined" ? process.argv.slice(2) : []);
|
|
514
2377
|
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Global flags available to all commands.
|
|
2380
|
+
*/
|
|
515
2381
|
globalFlags = { help: {
|
|
516
2382
|
aliases: ["h", "help"],
|
|
517
2383
|
description: "Show this help message",
|
|
518
2384
|
schema: t.boolean()
|
|
519
2385
|
} };
|
|
2386
|
+
/**
|
|
2387
|
+
* Main entry point - resolves and executes the command from process.argv.
|
|
2388
|
+
* This is the production execution path with full lifecycle support.
|
|
2389
|
+
*/
|
|
520
2390
|
onReady = $hook({
|
|
521
2391
|
on: "ready",
|
|
522
2392
|
handler: async () => {
|
|
@@ -526,7 +2396,7 @@ var CliProvider = class {
|
|
|
526
2396
|
if (this.parseFlags(argv, Object.entries(this.getAllGlobalFlags()).map(([key, value]) => ({
|
|
527
2397
|
key,
|
|
528
2398
|
...value
|
|
529
|
-
}))).help) {
|
|
2399
|
+
})), { strict: false }).help) {
|
|
530
2400
|
this.printHelp(command);
|
|
531
2401
|
return;
|
|
532
2402
|
}
|
|
@@ -549,7 +2419,15 @@ var CliProvider = class {
|
|
|
549
2419
|
}
|
|
550
2420
|
});
|
|
551
2421
|
/**
|
|
552
|
-
* Execute a command with
|
|
2422
|
+
* Execute a command with full lifecycle support.
|
|
2423
|
+
*
|
|
2424
|
+
* This is the production execution path that includes:
|
|
2425
|
+
* - Mode-based .env file loading
|
|
2426
|
+
* - Pre/post command hooks
|
|
2427
|
+
* - Runner session for pretty CLI output
|
|
2428
|
+
* - Alepha context wrapper for proper scoping
|
|
2429
|
+
*
|
|
2430
|
+
* @see run() for a lightweight test-only alternative
|
|
553
2431
|
*/
|
|
554
2432
|
async executeCommand(command, argv, isRootCommand) {
|
|
555
2433
|
const root = process.cwd();
|
|
@@ -559,7 +2437,7 @@ var CliProvider = class {
|
|
|
559
2437
|
if (modeValue === void 0 && typeof command.options.mode === "string") modeValue = command.options.mode;
|
|
560
2438
|
await this.loadModeEnv(root, modeValue);
|
|
561
2439
|
}
|
|
562
|
-
const commandFlags = this.parseCommandFlags(argv, command.flags);
|
|
2440
|
+
const commandFlags = this.parseCommandFlags(argv, command.flags, { modeEnabled: !!command.options.mode });
|
|
563
2441
|
const commandArgs = this.parseCommandArgs(argv, command.options.args, isRootCommand, command.flags);
|
|
564
2442
|
const commandEnv = this.parseCommandEnv(command.env, command.name);
|
|
565
2443
|
await this.alepha.context.run(async () => {
|
|
@@ -598,7 +2476,7 @@ var CliProvider = class {
|
|
|
598
2476
|
});
|
|
599
2477
|
}
|
|
600
2478
|
/**
|
|
601
|
-
* Remove consumed command path arguments from argv.
|
|
2479
|
+
* Remove consumed command path arguments from argv (keeps flags and remaining args).
|
|
602
2480
|
*/
|
|
603
2481
|
removeConsumedArgs(argv, consumedArgs) {
|
|
604
2482
|
const result = [];
|
|
@@ -650,38 +2528,94 @@ var CliProvider = class {
|
|
|
650
2528
|
consumedArgs
|
|
651
2529
|
};
|
|
652
2530
|
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Get all registered commands in the application.
|
|
2533
|
+
*/
|
|
653
2534
|
get commands() {
|
|
654
2535
|
return this.alepha.primitives($command);
|
|
655
2536
|
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Execute a command handler with given arguments.
|
|
2539
|
+
*
|
|
2540
|
+
* This is a **lightweight test helper** that directly invokes the command handler
|
|
2541
|
+
* without the full production lifecycle. It intentionally skips:
|
|
2542
|
+
* - Pre/post command hooks
|
|
2543
|
+
* - Runner session (pretty CLI output)
|
|
2544
|
+
* - Alepha context wrapper
|
|
2545
|
+
* - .env.{mode} file loading
|
|
2546
|
+
*
|
|
2547
|
+
* For production execution, the `onReady` hook uses `executeCommand()` which
|
|
2548
|
+
* provides the full lifecycle. Merging them would either make this method too
|
|
2549
|
+
* heavy for simple testing or require many optional parameters to toggle behaviors.
|
|
2550
|
+
*
|
|
2551
|
+
* @example
|
|
2552
|
+
* ```typescript
|
|
2553
|
+
* // In tests
|
|
2554
|
+
* const cli = alepha.inject(CliProvider);
|
|
2555
|
+
* const cmd = alepha.inject(InitCommand);
|
|
2556
|
+
*
|
|
2557
|
+
* await cli.run(cmd.init, "--agent --pm=yarn");
|
|
2558
|
+
* await cli.run(cmd.init, { argv: "--agent", root: "/project" });
|
|
2559
|
+
* ```
|
|
2560
|
+
*/
|
|
2561
|
+
async run(command, options = {}) {
|
|
2562
|
+
const opts = typeof options === "string" || Array.isArray(options) ? { argv: options } : options;
|
|
2563
|
+
const args = typeof opts.argv === "string" ? opts.argv.split(" ").filter(Boolean) : opts.argv ?? [];
|
|
2564
|
+
const root = opts.root ?? process.cwd();
|
|
2565
|
+
const commandFlags = this.parseCommandFlags(args, command.flags, { modeEnabled: !!command.options.mode });
|
|
2566
|
+
const commandArgs = this.parseCommandArgs(args, command.options.args, true, command.flags);
|
|
2567
|
+
const commandEnv = this.parseCommandEnv(command.env, command.name);
|
|
2568
|
+
let modeValue;
|
|
2569
|
+
if (command.options.mode) {
|
|
2570
|
+
modeValue = this.parseModeFlag(args);
|
|
2571
|
+
if (modeValue === void 0 && typeof command.options.mode === "string") modeValue = command.options.mode;
|
|
2572
|
+
}
|
|
2573
|
+
await command.options.handler({
|
|
2574
|
+
flags: commandFlags,
|
|
2575
|
+
args: commandArgs,
|
|
2576
|
+
env: commandEnv,
|
|
2577
|
+
run: this.runner.run,
|
|
2578
|
+
ask: this.asker.ask,
|
|
2579
|
+
fs,
|
|
2580
|
+
glob,
|
|
2581
|
+
root,
|
|
2582
|
+
help: () => this.printHelp(command),
|
|
2583
|
+
mode: modeValue
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
/** Find a command by name or alias */
|
|
656
2587
|
findCommand(name) {
|
|
657
2588
|
return this.commands.findLast((command) => command.name === name || command.aliases.includes(name));
|
|
658
2589
|
}
|
|
659
|
-
/**
|
|
660
|
-
* Find all pre-hooks for a command.
|
|
661
|
-
*/
|
|
2590
|
+
/** Find all pre-hooks for a command (commands named `pre{commandName}`) */
|
|
662
2591
|
findPreHooks(commandName) {
|
|
663
2592
|
return this.commands.filter((cmd) => cmd.name === `pre${commandName}`);
|
|
664
2593
|
}
|
|
665
|
-
/**
|
|
666
|
-
* Find all post-hooks for a command.
|
|
667
|
-
*/
|
|
2594
|
+
/** Find all post-hooks for a command (commands named `post{commandName}`) */
|
|
668
2595
|
findPostHooks(commandName) {
|
|
669
2596
|
return this.commands.filter((cmd) => cmd.name === `post${commandName}`);
|
|
670
2597
|
}
|
|
671
|
-
/**
|
|
672
|
-
* Get global flags (help only, root command flags are NOT global).
|
|
673
|
-
*/
|
|
2598
|
+
/** Get global flags (help only, root command flags are NOT global) */
|
|
674
2599
|
getAllGlobalFlags() {
|
|
675
2600
|
return { ...this.globalFlags };
|
|
676
2601
|
}
|
|
677
|
-
|
|
2602
|
+
/** Parse command flags from argv using the command's flag schema */
|
|
2603
|
+
parseCommandFlags(argv, schema, options = {}) {
|
|
2604
|
+
const { modeEnabled = false } = options;
|
|
678
2605
|
const flagDefs = Object.entries(schema.properties).map(([key, value]) => ({
|
|
679
2606
|
key,
|
|
680
2607
|
aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : void 0) ?? []],
|
|
681
2608
|
description: value.description,
|
|
682
2609
|
schema: value
|
|
683
2610
|
}));
|
|
2611
|
+
if (modeEnabled) flagDefs.push({
|
|
2612
|
+
key: "__mode__",
|
|
2613
|
+
aliases: ["mode", "m"],
|
|
2614
|
+
description: void 0,
|
|
2615
|
+
schema: t.string()
|
|
2616
|
+
});
|
|
684
2617
|
const parsed = this.parseFlags(argv, flagDefs);
|
|
2618
|
+
delete parsed.__mode__;
|
|
685
2619
|
for (const [key, value] of Object.entries(schema.properties)) if (!(key in parsed) && t.schema.isOptional(value)) {
|
|
686
2620
|
const innerSchema = value;
|
|
687
2621
|
if (innerSchema && "default" in innerSchema) parsed[key] = innerSchema.default;
|
|
@@ -693,6 +2627,7 @@ var CliProvider = class {
|
|
|
693
2627
|
throw error;
|
|
694
2628
|
}
|
|
695
2629
|
}
|
|
2630
|
+
/** Parse and validate environment variables using the command's env schema */
|
|
696
2631
|
parseCommandEnv(schema, commandName) {
|
|
697
2632
|
const result = {};
|
|
698
2633
|
const missing = [];
|
|
@@ -714,9 +2649,7 @@ var CliProvider = class {
|
|
|
714
2649
|
throw error;
|
|
715
2650
|
}
|
|
716
2651
|
}
|
|
717
|
-
/**
|
|
718
|
-
* Parse --mode or -m flag from argv.
|
|
719
|
-
*/
|
|
2652
|
+
/** Parse --mode or -m flag from argv for environment file loading */
|
|
720
2653
|
parseModeFlag(argv) {
|
|
721
2654
|
for (let i = 0; i < argv.length; i++) {
|
|
722
2655
|
const arg = argv[i];
|
|
@@ -728,16 +2661,16 @@ var CliProvider = class {
|
|
|
728
2661
|
}
|
|
729
2662
|
}
|
|
730
2663
|
}
|
|
731
|
-
/**
|
|
732
|
-
* Load environment files based on mode.
|
|
733
|
-
*/
|
|
2664
|
+
/** Load .env and .env.{mode} files into process.env */
|
|
734
2665
|
async loadModeEnv(root, mode) {
|
|
735
2666
|
const envFiles = [".env"];
|
|
736
2667
|
if (mode) envFiles.push(`.env.${mode}`);
|
|
737
2668
|
this.log.debug(`Loading env files: ${envFiles.join(", ")}`);
|
|
738
2669
|
await this.envUtils.loadEnv(root, envFiles);
|
|
739
2670
|
}
|
|
740
|
-
|
|
2671
|
+
/** Low-level flag parser - extracts flag values from argv based on definitions */
|
|
2672
|
+
parseFlags(argv, flagDefs, options = {}) {
|
|
2673
|
+
const { strict = true } = options;
|
|
741
2674
|
const result = {};
|
|
742
2675
|
for (let i = 0; i < argv.length; i++) {
|
|
743
2676
|
const arg = argv[i];
|
|
@@ -745,9 +2678,19 @@ var CliProvider = class {
|
|
|
745
2678
|
const [rawKey, ...valueParts] = arg.replace(/^-{1,2}/, "").split("=");
|
|
746
2679
|
let value = valueParts.join("=");
|
|
747
2680
|
const def = flagDefs.find((d) => d.aliases.includes(rawKey));
|
|
748
|
-
if (!def)
|
|
2681
|
+
if (!def) {
|
|
2682
|
+
if (strict) throw new CommandError(`Unknown flag: --${rawKey}`);
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
const isUnionWithBoolean = t.schema.isUnion(def.schema) && def.schema.anyOf.some((s) => t.schema.isBoolean(s));
|
|
749
2686
|
if (t.schema.isBoolean(def.schema)) result[def.key] = true;
|
|
750
|
-
else if (value)
|
|
2687
|
+
else if (isUnionWithBoolean && !value) {
|
|
2688
|
+
const nextArg = argv[i + 1];
|
|
2689
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
2690
|
+
result[def.key] = nextArg;
|
|
2691
|
+
i++;
|
|
2692
|
+
} else result[def.key] = true;
|
|
2693
|
+
} else if (value) try {
|
|
751
2694
|
if (t.schema.isObject(def.schema) || t.schema.isArray(def.schema)) result[def.key] = JSON.parse(value);
|
|
752
2695
|
else result[def.key] = value;
|
|
753
2696
|
} catch {
|
|
@@ -768,9 +2711,7 @@ var CliProvider = class {
|
|
|
768
2711
|
}
|
|
769
2712
|
return result;
|
|
770
2713
|
}
|
|
771
|
-
/**
|
|
772
|
-
* Get indices of argv elements that are consumed by flags (including space-separated values).
|
|
773
|
-
*/
|
|
2714
|
+
/** Get indices of argv elements consumed by flags (for separating args from flags) */
|
|
774
2715
|
getFlagConsumedIndices(argv, flagDefs) {
|
|
775
2716
|
const consumed = /* @__PURE__ */ new Set();
|
|
776
2717
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -781,7 +2722,11 @@ var CliProvider = class {
|
|
|
781
2722
|
const hasEqualValue = valueParts.length > 0;
|
|
782
2723
|
const def = flagDefs.find((d) => d.aliases.includes(rawKey));
|
|
783
2724
|
if (!def) continue;
|
|
784
|
-
|
|
2725
|
+
const isUnionWithBoolean = t.schema.isUnion(def.schema) && def.schema.anyOf.some((s) => t.schema.isBoolean(s));
|
|
2726
|
+
if (!t.schema.isBoolean(def.schema) && !isUnionWithBoolean && !hasEqualValue) {
|
|
2727
|
+
const nextArg = argv[i + 1];
|
|
2728
|
+
if (nextArg && !nextArg.startsWith("-")) consumed.add(i + 1);
|
|
2729
|
+
} else if (isUnionWithBoolean && !hasEqualValue) {
|
|
785
2730
|
const nextArg = argv[i + 1];
|
|
786
2731
|
if (nextArg && !nextArg.startsWith("-")) consumed.add(i + 1);
|
|
787
2732
|
}
|
|
@@ -821,6 +2766,7 @@ var CliProvider = class {
|
|
|
821
2766
|
throw error;
|
|
822
2767
|
}
|
|
823
2768
|
}
|
|
2769
|
+
/** Convert a string argument value to the appropriate type based on schema */
|
|
824
2770
|
parseArgumentValue(value, schema) {
|
|
825
2771
|
if (t.schema.isString(schema)) return value;
|
|
826
2772
|
if (t.schema.isNumber(schema) || t.schema.isInteger(schema)) {
|
|
@@ -837,6 +2783,7 @@ var CliProvider = class {
|
|
|
837
2783
|
}
|
|
838
2784
|
return value;
|
|
839
2785
|
}
|
|
2786
|
+
/** Generate usage string for command arguments (e.g., "<path>" or "[path]") */
|
|
840
2787
|
generateArgsUsage(schema) {
|
|
841
2788
|
if (!schema) return "";
|
|
842
2789
|
if (t.schema.isOptional(schema)) {
|
|
@@ -852,6 +2799,7 @@ var CliProvider = class {
|
|
|
852
2799
|
const typeName = this.getTypeName(schema);
|
|
853
2800
|
return ` <${"title" in schema ? schema.title : "arg1"}${typeName}>`;
|
|
854
2801
|
}
|
|
2802
|
+
/** Get display type name for a schema (e.g., ": number", ": boolean") */
|
|
855
2803
|
getTypeName(schema) {
|
|
856
2804
|
if (!schema) return "";
|
|
857
2805
|
if (t.schema.isString(schema)) return "";
|
|
@@ -860,6 +2808,12 @@ var CliProvider = class {
|
|
|
860
2808
|
if (t.schema.isBoolean(schema)) return ": boolean";
|
|
861
2809
|
return "";
|
|
862
2810
|
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Print help for a specific command or general CLI help.
|
|
2813
|
+
*
|
|
2814
|
+
* @param command - If provided, shows help for this specific command.
|
|
2815
|
+
* If omitted, shows general CLI help with all commands.
|
|
2816
|
+
*/
|
|
863
2817
|
printHelp(command) {
|
|
864
2818
|
const cliName = this.name || "cli";
|
|
865
2819
|
const c = this.color;
|
|
@@ -893,13 +2847,14 @@ var CliProvider = class {
|
|
|
893
2847
|
...Object.entries(command.flags.properties).map(([key, value]) => ({
|
|
894
2848
|
key,
|
|
895
2849
|
schema: value,
|
|
896
|
-
aliases: value.
|
|
2850
|
+
aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : [])],
|
|
897
2851
|
description: value.description
|
|
898
2852
|
})),
|
|
899
2853
|
...command.options.mode ? [{
|
|
900
2854
|
key: "mode",
|
|
901
2855
|
aliases: ["m", "mode"],
|
|
902
|
-
description: typeof command.options.mode === "string" ? `Environment mode - loads .env.{mode} (default: ${command.options.mode})` : "Environment mode (e.g., production, staging) - loads .env.{mode}"
|
|
2856
|
+
description: typeof command.options.mode === "string" ? `Environment mode - loads .env.{mode} (default: ${command.options.mode})` : "Environment mode (e.g., production, staging) - loads .env.{mode}",
|
|
2857
|
+
schema: t.string()
|
|
903
2858
|
}] : [],
|
|
904
2859
|
...Object.entries(this.getAllGlobalFlags()).map(([key, value]) => ({
|
|
905
2860
|
key,
|
|
@@ -907,11 +2862,14 @@ var CliProvider = class {
|
|
|
907
2862
|
}))
|
|
908
2863
|
];
|
|
909
2864
|
const maxFlagLength = this.getMaxFlagLength(flags);
|
|
910
|
-
for (const
|
|
911
|
-
const
|
|
2865
|
+
for (const flag of flags) {
|
|
2866
|
+
const { aliases, description } = flag;
|
|
2867
|
+
const schema = "schema" in flag ? flag.schema : void 0;
|
|
2868
|
+
const flagStr = (Array.isArray(aliases) ? aliases : [aliases]).slice().sort((a, b) => a.length - b.length).map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ");
|
|
912
2869
|
const coloredFlag = c.set("GREY_LIGHT", flagStr);
|
|
913
2870
|
const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
|
|
914
|
-
this.
|
|
2871
|
+
const formattedDesc = this.formatFlagDescription(description, schema);
|
|
2872
|
+
this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
|
|
915
2873
|
}
|
|
916
2874
|
const envVars = Object.entries(command.env.properties);
|
|
917
2875
|
if (envVars.length > 0) {
|
|
@@ -946,21 +2904,21 @@ var CliProvider = class {
|
|
|
946
2904
|
const globalFlags = [...rootCommand ? Object.entries(rootCommand.flags.properties).map(([key, value]) => ({
|
|
947
2905
|
key,
|
|
948
2906
|
aliases: [key, ...value.aliases ?? (value.alias ? [value.alias] : void 0) ?? []],
|
|
949
|
-
description: value.description
|
|
2907
|
+
description: value.description,
|
|
2908
|
+
schema: value
|
|
950
2909
|
})) : [], ...Object.values(this.getAllGlobalFlags())];
|
|
951
2910
|
const maxFlagLength = this.getMaxFlagLength(globalFlags);
|
|
952
|
-
for (const { aliases, description } of globalFlags) {
|
|
2911
|
+
for (const { aliases, description, schema } of globalFlags) {
|
|
953
2912
|
const flagStr = aliases.map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ");
|
|
954
2913
|
const coloredFlag = c.set("GREY_LIGHT", flagStr);
|
|
955
2914
|
const padding = " ".repeat(Math.max(0, maxFlagLength - flagStr.length));
|
|
956
|
-
this.
|
|
2915
|
+
const formattedDesc = this.formatFlagDescription(description, schema);
|
|
2916
|
+
this.log.info(` ${coloredFlag}${padding} ${formattedDesc}`);
|
|
957
2917
|
}
|
|
958
2918
|
}
|
|
959
2919
|
this.log.info("");
|
|
960
2920
|
}
|
|
961
|
-
/**
|
|
962
|
-
* Generate colored args usage string for help display.
|
|
963
|
-
*/
|
|
2921
|
+
/** Generate colored usage string for command arguments (for help display) */
|
|
964
2922
|
generateColoredArgsUsage(schema) {
|
|
965
2923
|
if (!schema) return "";
|
|
966
2924
|
const c = this.color;
|
|
@@ -979,9 +2937,7 @@ var CliProvider = class {
|
|
|
979
2937
|
const key = "title" in schema ? schema.title : "arg1";
|
|
980
2938
|
return ` ${c.set("CYAN", `<${key}${typeName}>`)}`;
|
|
981
2939
|
}
|
|
982
|
-
/**
|
|
983
|
-
* Get the full command path (e.g., "deploy vercel" for a child command).
|
|
984
|
-
*/
|
|
2940
|
+
/** Get the full command path (e.g., "deploy vercel" for a nested command) */
|
|
985
2941
|
getCommandPath(command) {
|
|
986
2942
|
const path = [command.name];
|
|
987
2943
|
let current = command;
|
|
@@ -993,49 +2949,82 @@ var CliProvider = class {
|
|
|
993
2949
|
}
|
|
994
2950
|
return path.join(" ");
|
|
995
2951
|
}
|
|
996
|
-
/**
|
|
997
|
-
* Find the parent command of a given command.
|
|
998
|
-
*/
|
|
2952
|
+
/** Find the parent command of a nested command */
|
|
999
2953
|
findParentCommand(command) {
|
|
1000
2954
|
for (const cmd of this.commands) if (cmd.children.includes(command)) return cmd;
|
|
1001
2955
|
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Get top-level commands (commands that are not children of other commands).
|
|
1004
|
-
*/
|
|
2956
|
+
/** Get top-level commands (commands that are not children of other commands) */
|
|
1005
2957
|
getTopLevelCommands() {
|
|
1006
2958
|
const allChildren = /* @__PURE__ */ new Set();
|
|
1007
2959
|
for (const command of this.commands) for (const child of command.children) allChildren.add(child);
|
|
1008
2960
|
return this.commands.filter((cmd) => !allChildren.has(cmd));
|
|
1009
2961
|
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Get max length for child command display.
|
|
1012
|
-
*/
|
|
2962
|
+
/** Calculate max display length for child commands (for help alignment) */
|
|
1013
2963
|
getMaxChildCmdLength(children) {
|
|
1014
2964
|
return Math.max(...children.filter((c) => !c.options.hide).map((c) => {
|
|
1015
2965
|
return `${[c.name, ...c.aliases].join(", ")}${this.generateArgsUsage(c.options.args)}`.length;
|
|
1016
2966
|
}), 0);
|
|
1017
2967
|
}
|
|
2968
|
+
/** Calculate max display length for commands (for help alignment) */
|
|
1018
2969
|
getMaxCmdLength(commands) {
|
|
1019
2970
|
return Math.max(...commands.filter((c) => !c.options.hide && c.name !== "").map((c) => {
|
|
1020
2971
|
return `${[c.name, ...c.aliases].join(", ")}${c.hasChildren ? " <command>" : this.generateArgsUsage(c.options.args)}`.length;
|
|
1021
2972
|
}));
|
|
1022
2973
|
}
|
|
2974
|
+
/** Calculate max display length for flags (for help alignment) */
|
|
1023
2975
|
getMaxFlagLength(flags) {
|
|
1024
2976
|
return Math.max(...flags.map((f) => {
|
|
1025
2977
|
return (Array.isArray(f.aliases) ? f.aliases : [f.aliases]).map((a) => a.length === 1 ? `-${a}` : `--${a}`).join(", ").length;
|
|
1026
2978
|
}));
|
|
1027
2979
|
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Extract enum values from a schema if it represents an enum.
|
|
2982
|
+
* Returns undefined if the schema is not an enum.
|
|
2983
|
+
*/
|
|
2984
|
+
getEnumValues(schema) {
|
|
2985
|
+
if (!schema) return void 0;
|
|
2986
|
+
if ("enum" in schema && Array.isArray(schema.enum) && schema.enum.every((v) => typeof v === "string")) return schema.enum;
|
|
2987
|
+
if (t.schema.isUnion(schema)) {
|
|
2988
|
+
const union = schema;
|
|
2989
|
+
const values = [];
|
|
2990
|
+
for (const variant of union.anyOf) if (t.schema.isString(variant) && "const" in variant && typeof variant.const === "string") values.push(variant.const);
|
|
2991
|
+
else return;
|
|
2992
|
+
return values.length > 0 ? values : void 0;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Format flag description with enum values if applicable.
|
|
2997
|
+
*/
|
|
2998
|
+
formatFlagDescription(description, schema) {
|
|
2999
|
+
const baseDesc = description ?? "";
|
|
3000
|
+
if (!schema) return baseDesc;
|
|
3001
|
+
const enumValues = this.getEnumValues(schema);
|
|
3002
|
+
if (enumValues && enumValues.length > 0) {
|
|
3003
|
+
const valuesStr = enumValues.join(", ");
|
|
3004
|
+
const enumHint = this.color.set("GREY_DARK", `[${valuesStr}]`);
|
|
3005
|
+
return baseDesc ? `${baseDesc} ${enumHint}` : enumHint;
|
|
3006
|
+
}
|
|
3007
|
+
return baseDesc;
|
|
3008
|
+
}
|
|
1028
3009
|
};
|
|
1029
3010
|
|
|
1030
3011
|
//#endregion
|
|
1031
3012
|
//#region ../../src/command/index.ts
|
|
1032
3013
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
3014
|
+
* | type | quality | stability |
|
|
3015
|
+
* |------|---------|-----------|
|
|
3016
|
+
* | tooling | rare | stable |
|
|
3017
|
+
*
|
|
3018
|
+
* Declarative CLI command framework.
|
|
1035
3019
|
*
|
|
1036
|
-
*
|
|
3020
|
+
* **Features:**
|
|
3021
|
+
* - CLI command definitions
|
|
3022
|
+
* - Interactive CLI prompts
|
|
3023
|
+
* - Command execution
|
|
3024
|
+
* - Formatted colored output
|
|
3025
|
+
* - Environment variable utilities
|
|
3026
|
+
* - Schema validation for CLI arguments
|
|
1037
3027
|
*
|
|
1038
|
-
* @see {@link $command}
|
|
1039
3028
|
* @module alepha.command
|
|
1040
3029
|
*/
|
|
1041
3030
|
const AlephaCommand = $module({
|