@warlock.js/core 5.1.0 → 5.2.1
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/CHANGELOG.md +152 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
- package/esm/benchmark/benchmark.d.mts.map +1 -1
- package/esm/benchmark/channels/console.channel.mjs.map +1 -1
- package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
- package/esm/benchmark/profiler.d.mts.map +1 -1
- package/esm/benchmark/profiler.mjs.map +1 -1
- package/esm/bootstrap/setup.d.mts.map +1 -1
- package/esm/bootstrap/setup.mjs.map +1 -1
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs +16 -10
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
- package/esm/cli/commands/routes/diff-page-routes.mjs +84 -0
- package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -0
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
- package/esm/cli/commands/routes/route-row.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs +84 -0
- package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -0
- package/esm/cli/commands/routes.command.mjs +15 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +8 -1
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/cli/framework-cli-commands.mjs +2 -1
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/cli/start.mjs.map +1 -1
- package/esm/cli/string-similarity.mjs.map +1 -1
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/config/config-loader.d.mts +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts.map +1 -1
- package/esm/config/config-special-handlers.d.mts +1 -1
- package/esm/config/config-special-handlers.mjs.map +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/access-connector.d.mts.map +1 -1
- package/esm/connectors/ai-connector.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/base-connector.d.mts.map +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.d.mts.map +1 -1
- package/esm/connectors/describe-server-address.mjs +20 -3
- package/esm/connectors/describe-server-address.mjs.map +1 -1
- package/esm/connectors/herald-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +5 -1
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.d.mts.map +1 -1
- package/esm/connectors/mail-connector.d.mts.map +1 -1
- package/esm/connectors/notifications-connector.d.mts.map +1 -1
- package/esm/connectors/register-configured-connectors.mjs.map +1 -1
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/container/index.d.mts.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
- package/esm/database/seeds/seeder.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/seeds-table-migration.mjs +1 -1
- package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
- package/esm/database/utils.mjs.map +1 -1
- package/esm/dev-server/check-for-updates.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.d.mts.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.d.mts.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs +10 -3
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
- package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
- package/esm/dev-server/loader/source-slug.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs +12 -3
- package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
- package/esm/dev-server/loader/version-registry.mjs.map +1 -1
- package/esm/dev-server/manifest-manager.d.mts.map +1 -1
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/ready-block.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/supervisor.mjs +9 -6
- package/esm/dev-server/supervisor.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/encryption/encrypt.mjs.map +1 -1
- package/esm/encryption/hash.mjs.map +1 -1
- package/esm/encryption/password.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +11 -4
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +2 -1
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/ai-anthropic.feature.mjs +3 -1
- package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-bedrock.feature.mjs +3 -1
- package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
- package/esm/generations/features/ai-google.feature.mjs +3 -1
- package/esm/generations/features/ai-google.feature.mjs.map +1 -1
- package/esm/generations/features/ai-ollama.feature.mjs +3 -1
- package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
- package/esm/generations/features/ai-openai.feature.mjs +3 -1
- package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
- package/esm/generations/features/ai-panoptic.feature.mjs +2 -1
- package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-tools.feature.mjs +2 -1
- package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
- package/esm/generations/features/ai-workspace.feature.mjs +2 -1
- package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
- package/esm/generations/features/ai.feature.mjs +2 -1
- package/esm/generations/features/ai.feature.mjs.map +1 -1
- package/esm/generations/features/herald.feature.mjs +2 -1
- package/esm/generations/features/herald.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs +3 -2
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/scheduler.feature.mjs +3 -1
- package/esm/generations/features/scheduler.feature.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/types.mjs +14 -0
- package/esm/generations/features/types.mjs.map +1 -0
- package/esm/generations/features/web.feature.mjs +10 -2
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +169 -28
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/boot-port-preflight.d.mts +46 -0
- package/esm/http/boot-port-preflight.d.mts.map +1 -0
- package/esm/http/boot-port-preflight.mjs +87 -0
- package/esm/http/boot-port-preflight.mjs.map +1 -0
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/context/request-memo.d.mts.map +1 -1
- package/esm/http/context/request-memo.mjs.map +1 -1
- package/esm/http/createHttpApplication.d.mts.map +1 -1
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/index.d.mts +1 -0
- package/esm/http/index.mjs +1 -0
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/ip-filter.middleware.d.mts +1 -1
- package/esm/http/middleware/ip-filter.middleware.mjs +1 -1
- package/esm/http/middleware/ip-filter.middleware.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/port-preflight.d.mts +8 -0
- package/esm/http/port-preflight.d.mts.map +1 -1
- package/esm/http/port-preflight.mjs +8 -1
- package/esm/http/port-preflight.mjs.map +1 -1
- package/esm/http/ready-report.d.mts.map +1 -1
- package/esm/http/request-controller.d.mts.map +1 -1
- package/esm/http/request.d.mts +14 -2
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +16 -2
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +3 -3
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +26 -2
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/types.d.mts +1 -3
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploaded-file.d.mts.map +1 -1
- package/esm/http/uploaded-file.mjs.map +1 -1
- package/esm/http/uploads-config.d.mts.map +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +6 -1
- package/esm/index.mjs +6 -1
- package/esm/logger/fatal-visibility.d.mts +25 -0
- package/esm/logger/fatal-visibility.d.mts.map +1 -0
- package/esm/logger/fatal-visibility.mjs +31 -0
- package/esm/logger/fatal-visibility.mjs.map +1 -0
- package/esm/logger/index.d.mts +1 -0
- package/esm/logger/index.mjs +1 -0
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/mail.d.mts.map +1 -1
- package/esm/mail/mail.mjs.map +1 -1
- package/esm/mail/mailer-pool.d.mts.map +1 -1
- package/esm/mail/mailer-pool.mjs +2 -0
- package/esm/mail/mailer-pool.mjs.map +1 -1
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/test-mailbox.d.mts.map +1 -1
- package/esm/mail/test-mailbox.mjs.map +1 -1
- package/esm/production/assert-dist-ready-to-start.mjs +24 -0
- package/esm/production/assert-dist-ready-to-start.mjs.map +1 -0
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/build-app-production.mjs.map +1 -1
- package/esm/production/dist-build-manifest.mjs +47 -0
- package/esm/production/dist-build-manifest.mjs.map +1 -0
- package/esm/production/production-builder.mjs +68 -13
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +21 -9
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/promote-dist.mjs +115 -0
- package/esm/production/promote-dist.mjs.map +1 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/register-resource.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/index.d.mts +3 -0
- package/esm/router/index.mjs +3 -0
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/normalize-request-path.d.mts +14 -0
- package/esm/router/normalize-request-path.d.mts.map +1 -0
- package/esm/router/normalize-request-path.mjs +18 -0
- package/esm/router/normalize-request-path.mjs.map +1 -0
- package/esm/router/normalize-route-path.d.mts +28 -0
- package/esm/router/normalize-route-path.d.mts.map +1 -0
- package/esm/router/normalize-route-path.mjs +33 -0
- package/esm/router/normalize-route-path.mjs.map +1 -0
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
- package/esm/router/route-name-method-suffix.d.mts +33 -0
- package/esm/router/route-name-method-suffix.d.mts.map +1 -0
- package/esm/router/route-name-method-suffix.mjs +39 -0
- package/esm/router/route-name-method-suffix.mjs.map +1 -0
- package/esm/router/route-registry.d.mts.map +1 -1
- package/esm/router/route-registry.mjs +3 -2
- package/esm/router/route-registry.mjs.map +1 -1
- package/esm/router/router.d.mts +14 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +50 -11
- package/esm/router/router.mjs.map +1 -1
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/storage/config.d.mts.map +1 -1
- package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage-file.d.mts.map +1 -1
- package/esm/storage/storage-file.mjs.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/types.d.mts +14 -0
- package/esm/storage/types.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +2 -1
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-connectors-selection.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-setup-timeout.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/updater/update-warlock-packages.mjs.map +1 -1
- package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
- package/esm/use-cases/use-case-events.d.mts.map +1 -1
- package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
- package/esm/use-cases/use-case.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/database-log.d.mts.map +1 -1
- package/esm/utils/database-log.mjs.map +1 -1
- package/esm/utils/environment.d.mts.map +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/get-localized.mjs.map +1 -1
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/paths.d.mts.map +1 -1
- package/esm/utils/promise-all-object.d.mts.map +1 -1
- package/esm/utils/promise-all-object.mjs.map +1 -1
- package/esm/utils/sluggable.mjs.map +1 -1
- package/esm/utils/version-compare.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
- package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
- package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
- package/esm/validation/validators/file-validator.mjs.map +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
- package/esm/warlock-config/default-configurations.mjs.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +205 -90
- package/llms.txt +5 -5
- package/package.json +12 -12
- package/skills/add-connector/SKILL.md +35 -28
- package/skills/create-controller/SKILL.md +1 -1
- package/skills/create-module/SKILL.md +12 -12
- package/skills/hash-password/SKILL.md +4 -4
- package/skills/run-app/SKILL.md +77 -16
- package/skills/send-mail/SKILL.md +2 -2
- package/skills/use-middleware/SKILL.md +13 -4
- package/skills/use-model-transformers/SKILL.md +2 -2
- package/skills/use-repository/SKILL.md +1 -1
- package/skills/validate-input/SKILL.md +1 -1
- package/skills/warlock-conventions/SKILL.md +1 -1
- package/skills/warlock-doctor/SKILL.md +3 -3
- package/skills/warlock-routes/SKILL.md +47 -9
- package/skills/write-cli-command/SKILL.md +1 -1
- package/skills/write-seeder/SKILL.md +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-imports.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/parse-imports.ts"],"sourcesContent":["import { fileExistsAsync, directoryExistsAsync } from \"@warlock.js/fs\";\nimport { ImportSpecifier, parse } from \"es-module-lexer\";\nimport path from \"node:path\";\nimport { Path } from \"../utils/normalized-path\";\nimport { tsconfigManager } from \"./tsconfig-manager\";\n\n/**\n * Detect if a file contains only type definitions (no runtime code)\n *\n * A file is considered type-only if ALL its exports are:\n * - interface declarations\n * - type alias declarations\n * - export type { ... } statements\n * - export type { ... } from \"...\" statements\n *\n * Files with any of these are NOT type-only:\n * - export const/let/var\n * - export function\n * - export class\n * - export default (non-type)\n * - export { ... } (without type keyword)\n * - export * from (without type keyword)\n *\n * @param source - The source code to analyze\n * @returns true if the file exports only types\n */\nexport function isTypeOnlyFile(source: string): boolean {\n // Remove comments to avoid false positives\n const withoutComments = source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // block comments\n .replace(/\\/\\/.*$/gm, \"\"); // line comments\n\n // Remove string literals to avoid false positives\n const withoutStrings = withoutComments\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, \"``\");\n\n // Patterns for type-only exports (these are safe)\n const typeOnlyPatterns = [\n /export\\s+type\\s+\\{[^}]*\\}/g, // export type { Foo, Bar }\n /export\\s+type\\s+\\{[^}]*\\}\\s+from\\s+['\"]/g, // export type { Foo } from \"...\"\n /export\\s+interface\\s+\\w+/g, // export interface Foo\n /export\\s+type\\s+\\w+\\s*=/g, // export type Foo =\n ];\n\n // Remove all type-only exports from consideration\n let remaining = withoutStrings;\n for (const pattern of typeOnlyPatterns) {\n remaining = remaining.replace(pattern, \"\");\n }\n\n // Patterns for runtime exports (these make the file NOT type-only)\n const runtimeExportPatterns = [\n /export\\s+(?:const|let|var)\\s+\\w+/g, // export const/let/var foo\n /export\\s+function\\s+\\w+/g, // export function foo\n /export\\s+async\\s+function\\s+\\w+/g, // export async function foo\n /export\\s+class\\s+\\w+/g, // export class Foo\n /export\\s+enum\\s+\\w+/g, // export enum Foo (enums have runtime value)\n /export\\s+default\\s+(?!type\\s)/g, // export default (not type)\n /export\\s+\\{[^}]*\\}(?!\\s+from)/g, // export { foo } (local re-export without type)\n /export\\s+\\{[^}]*\\}\\s+from\\s+['\"][^'\"]+['\"]/g, // export { foo } from (without type)\n /export\\s+\\*\\s+from\\s+['\"][^'\"]+['\"]/g, // export * from (re-exports everything)\n /export\\s+\\*\\s+as\\s+\\w+/g, // export * as namespace\n ];\n\n for (const pattern of runtimeExportPatterns) {\n if (pattern.test(remaining)) {\n // Reset regex lastIndex for subsequent tests\n pattern.lastIndex = 0;\n\n // Special case: check if export { } contains only type exports\n if (pattern.source.includes(\"export\\\\s+\\\\{\")) {\n const matches = remaining.match(/export\\s+\\{([^}]*)\\}(?:\\s+from\\s+['\"][^'\"]+['\"])?/g);\n if (matches) {\n for (const match of matches) {\n // Skip if it's already a type export\n if (/^export\\s+type\\s+/.test(match)) continue;\n\n // Extract the specifiers\n const specifiersMatch = match.match(/export\\s+\\{([^}]*)\\}/);\n if (specifiersMatch) {\n const specifiers = specifiersMatch[1];\n const items = specifiers\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n // If any specifier is NOT prefixed with \"type \", this is a runtime export\n const hasRuntimeSpecifier = items.some((item) => !item.startsWith(\"type \"));\n if (hasRuntimeSpecifier) {\n return false;\n }\n }\n }\n }\n continue;\n }\n\n return false;\n }\n }\n\n // If we get here, no runtime exports were found\n // But we should also verify the file has at least some type exports\n // (an empty file or file with only imports is not really \"type-only\")\n const hasTypeExports = typeOnlyPatterns.some((pattern) => {\n pattern.lastIndex = 0;\n return pattern.test(withoutStrings);\n });\n\n return hasTypeExports;\n}\n\n/**\n * Check if an import statement is a pure `import type` (the keyword sits\n * immediately after `import`, covering `import type { X }`, `import type Foo`,\n * and `import type * as Ns`).\n */\nfunction isTypeOnlyImport(line: string): boolean {\n const trimmed = line.trim();\n\n return trimmed.startsWith(\"import type \") || !!trimmed.match(/^import\\s+type\\s+[\\{\\*]/);\n}\n\n/**\n * Decide whether an `import ... from \"m\"` statement contributes any runtime\n * binding. Returns false for pure `import type` and for destructured imports\n * where every specifier is prefixed with the `type` keyword.\n */\nfunction hasRuntimeImports(line: string): boolean {\n const trimmed = line.trim();\n\n if (isTypeOnlyImport(trimmed)) {\n return false;\n }\n\n const specifiersMatch = trimmed.match(/import\\s+(?:type\\s+)?\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return true;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return !items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Decide whether an `export ... from \"m\"` statement re-exports only types.\n * Covers `export type { X } from`, `export type * from`, and\n * `export { type X, type Y } from`.\n */\nfunction isExportTypeOnlyStatement(line: string): boolean {\n const trimmed = line.trim();\n\n if (/^export\\s+type\\s+/.test(trimmed)) {\n return true;\n }\n\n const specifiersMatch = trimmed.match(/export\\s+\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return false;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Extract import paths using regex (more reliable for TypeScript).\n * Each entry carries an `isTypeOnly` flag: true when every occurrence of the\n * path in the source is a type-only statement (no runtime binding).\n * A single runtime occurrence makes the path runtime.\n */\nfunction extractImportPathsWithRegex(\n source: string,\n): Array<{ path: string; originalLine: string; isTypeOnly: boolean }> {\n const imports: Array<{ path: string; originalLine: string; isTypeOnly: boolean }> = [];\n const seenPaths = new Map<string, number>();\n\n /**\n * Record an import path with its type-only flag.\n * If the same path appears in multiple statements, the edge is type-only\n * iff every occurrence is type-only — a single runtime statement makes it runtime.\n */\n const record = (importPath: string, originalLine: string, isTypeOnly: boolean): void => {\n if (!importPath) {\n return;\n }\n\n const existingIndex = seenPaths.get(importPath);\n\n if (existingIndex === undefined) {\n seenPaths.set(importPath, imports.length);\n imports.push({ path: importPath, originalLine, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n imports[existingIndex].isTypeOnly = false;\n }\n };\n\n // Pattern 1: Standard ES module imports (handles multiline)\n // Matches: import { ... } from \"path\", import Foo from \"path\", import Foo, { ... } from \"path\"\n const importRegex =\n /import\\s+(?:type\\s+)?(\\{[\\s\\S]*?\\}|\\*\\s+as\\s+\\w+|\\w+(?:\\s*,\\s*\\{[\\s\\S]*?\\})?)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n let match;\n\n while ((match = importRegex.exec(source)) !== null) {\n const fullMatch = match[0];\n const importPath = match[2];\n const isTypeOnly = !hasRuntimeImports(fullMatch);\n\n record(importPath, fullMatch, isTypeOnly);\n }\n\n // Pattern 1b: Side-effect imports - import \"path\" — always runtime\n const sideEffectRegex = /import\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = sideEffectRegex.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 2: Dynamic imports - import(\"path\") — always runtime\n const dynamicImportPattern = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\n while ((match = dynamicImportPattern.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 3: Export from - export ... from \"path\"\n const exportFromPattern = /export\\s+(?:\\{[^}]*\\}|\\*|\\w+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = exportFromPattern.exec(source)) !== null) {\n const fullMatch = match[0];\n\n record(match[1], fullMatch, isExportTypeOnlyStatement(fullMatch));\n }\n\n return imports;\n}\n\n/**\n * Metadata for a single resolved import edge.\n * `isTypeOnly` is true iff every import/export statement that references this\n * path is type-only (pure `import type`, `export type`, or destructured lists\n * where every specifier carries the `type` keyword). A single runtime\n * occurrence flips the flag to false — that's what matters for cycle detection.\n */\nexport type ResolvedImport = {\n absolutePath: string;\n isTypeOnly: boolean;\n};\n\n/**\n * Parse import/export statements in a TS/JS source file and return the\n * resolved dependency edges keyed by the original import path. Includes\n * type-only edges (flagged) so downstream consumers can distinguish runtime\n * cycles from type-only ones.\n *\n * @example\n * const importMap = await parseImports(source, \"/abs/path/to/file.ts\");\n * for (const [importPath, { absolutePath, isTypeOnly }] of importMap) {\n * // ...\n * }\n */\nexport async function parseImports(\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n try {\n // Skip .d.ts files - they're type declarations, not runtime code\n if (filePath.endsWith(\".d.ts\")) {\n return new Map();\n }\n\n // Try es-module-lexer first (faster and more accurate for simple cases)\n try {\n const [imports] = await parse(source);\n\n if (imports && imports.length > 0) {\n return await resolveImports(imports as ImportSpecifier[], source, filePath);\n }\n } catch (lexerError) {\n // es-module-lexer failed, fall back to regex-based extraction\n // This is common with TypeScript files that have complex syntax\n }\n\n // Fallback: Use regex-based extraction (more forgiving with TypeScript)\n const regexImports = extractImportPathsWithRegex(source);\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const { path: importPath, isTypeOnly } of regexImports) {\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (resolvedPath) {\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n }\n\n return resolvedImports;\n } catch (error) {\n console.error(`Error parsing imports for ${filePath}:`, error);\n\n return new Map();\n }\n}\n\n/**\n * Merge a resolved import into the output map, preserving the type-only rule:\n * a path is type-only iff every statement that references it is type-only.\n */\nfunction mergeResolvedImport(\n target: Map<string, ResolvedImport>,\n importPath: string,\n absolutePath: string,\n isTypeOnly: boolean,\n): void {\n const existing = target.get(importPath);\n\n if (!existing) {\n target.set(importPath, { absolutePath, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n existing.isTypeOnly = false;\n }\n}\n\n/**\n * Resolve imports from `es-module-lexer` output. For each specifier, slice the\n * raw statement (`ss..se`) and re-run the type-only heuristic so we can flag\n * the edge correctly — the lexer itself does not distinguish `import type`.\n */\nasync function resolveImports(\n imports: ImportSpecifier[],\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const imp of imports) {\n const importPath = imp.n;\n\n if (!importPath) {\n continue;\n }\n\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (!resolvedPath) {\n continue;\n }\n\n const isTypeOnly = isLexerStatementTypeOnly(imp, source);\n\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n\n return resolvedImports;\n}\n\n/**\n * Classify a single lexer-reported statement as type-only or runtime.\n * Dynamic imports (`import(...)`) are always runtime. For static\n * imports/export-from we slice the raw source and apply the same heuristic\n * the regex fallback uses.\n */\nfunction isLexerStatementTypeOnly(imp: ImportSpecifier, source: string): boolean {\n // Dynamic imports always execute — they cannot be type-only.\n // es-module-lexer reports dynamic with a.n === undefined typically, but\n // we guard by checking the statement prefix too.\n const statementStart = imp.ss;\n const statementEnd = imp.se;\n\n if (\n typeof statementStart !== \"number\" ||\n typeof statementEnd !== \"number\" ||\n statementEnd <= statementStart\n ) {\n return false;\n }\n\n const statement = source.slice(statementStart, statementEnd);\n const trimmed = statement.trim();\n\n if (trimmed.startsWith(\"import(\") || /^\\bimport\\s*\\(/.test(trimmed)) {\n return false;\n }\n\n if (trimmed.startsWith(\"export\")) {\n return isExportTypeOnlyStatement(statement);\n }\n\n return !hasRuntimeImports(statement);\n}\n\n/**\n * Resolve alias imports to actual file paths with extensions\n * Example: app/users/services/get-users.service -> /absolute/path/to/src/app/users/services/get-users.service.ts\n */\nasync function resolveAliasImport(importPath: string): Promise<string | null> {\n // Use tsconfig manager to resolve the alias to an absolute path\n const resolvedBase = tsconfigManager.resolveAliasToAbsolute(importPath);\n\n if (!resolvedBase) return null;\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Resolve relative imports to actual file paths\n * Example: ./../services/get-user.service -> /absolute/path/to/services/get-user.service.ts\n */\nasync function resolveRelativeImport(\n importPath: string,\n currentFilePath: string,\n): Promise<string | null> {\n const dir = path.dirname(currentFilePath);\n // Use path.resolve to handle .. and . properly, then normalize to forward slashes\n const resolvedBase = Path.normalize(path.resolve(dir, importPath));\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Try to resolve a file path by checking different extensions\n * TypeScript/JavaScript files can be imported without extensions\n *\n * @TODO: For better performance, we need to check the files in files orchestrator\n * instead of using the file system as we will be fetching all project files anyway.\n */\n// Cache for file existence checks to avoid redundant filesystem calls\nconst fileExistsCache = new Map<string, boolean>();\n\n/**\n * Clear the file exists cache\n * Should be called when new files are created to ensure fresh lookups\n */\nexport function clearFileExistsCache(): void {\n fileExistsCache.clear();\n}\n\nasync function cachedFileExists(filePath: string): Promise<boolean> {\n if (fileExistsCache.has(filePath)) {\n return fileExistsCache.get(filePath)!;\n }\n const exists = (await fileExistsAsync(filePath)) as boolean;\n fileExistsCache.set(filePath, exists);\n return exists;\n}\n\nasync function tryResolveWithExtensions(basePath: string): Promise<string | null> {\n // Normalize the base path first (handle Windows paths)\n const normalizedBase = Path.normalize(basePath);\n\n // List of extensions to try, in order of preference\n const extensions = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"];\n const validExtensions = new Set(extensions);\n\n // If the path already has a VALID code file extension, check if it exists\n const ext = path.extname(normalizedBase);\n if (ext && validExtensions.has(ext)) {\n if (await cachedFileExists(normalizedBase)) {\n return normalizedBase;\n }\n // If explicit extension doesn't exist, return null\n return null;\n }\n\n // Try all extensions in parallel for better performance\n const pathsToCheck = extensions.map((extension) => normalizedBase + extension);\n const results = await Promise.all(\n pathsToCheck.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n // Return the first one that exists (in order of preference)\n for (const result of results) {\n if (result.exists) {\n return result.path;\n }\n }\n\n // Try index files in directory\n if (await directoryExistsAsync(normalizedBase)) {\n const indexPaths = extensions.map((extension) =>\n Path.join(normalizedBase, `index${extension}`),\n );\n const indexResults = await Promise.all(\n indexPaths.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n for (const result of indexResults) {\n if (result.exists) {\n return result.path;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Check if import is a Node.js built-in module\n */\nfunction isNodeBuiltin(importPath: string): boolean {\n const builtins = [\n \"fs\",\n \"path\",\n \"http\",\n \"https\",\n \"crypto\",\n \"stream\",\n \"util\",\n \"events\",\n \"buffer\",\n \"child_process\",\n \"os\",\n \"url\",\n \"querystring\",\n \"zlib\",\n \"net\",\n \"tls\",\n \"dns\",\n \"dgram\",\n \"cluster\",\n \"worker_threads\",\n \"perf_hooks\",\n \"async_hooks\",\n \"timers\",\n \"readline\",\n \"repl\",\n \"vm\",\n \"assert\",\n \"console\",\n \"process\",\n \"v8\",\n ];\n\n // Check for node: prefix or direct builtin name\n if (importPath.startsWith(\"node:\")) return true;\n\n const moduleName = importPath.split(\"/\")[0];\n return builtins.includes(moduleName);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-imports.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/parse-imports.ts"],"sourcesContent":["import { fileExistsAsync, directoryExistsAsync } from \"@warlock.js/fs\";\nimport { type ImportSpecifier, parse } from \"es-module-lexer\";\nimport path from \"node:path\";\nimport { Path } from \"../utils/normalized-path\";\nimport { tsconfigManager } from \"./tsconfig-manager\";\n\n/**\n * Detect if a file contains only type definitions (no runtime code)\n *\n * A file is considered type-only if ALL its exports are:\n * - interface declarations\n * - type alias declarations\n * - export type { ... } statements\n * - export type { ... } from \"...\" statements\n *\n * Files with any of these are NOT type-only:\n * - export const/let/var\n * - export function\n * - export class\n * - export default (non-type)\n * - export { ... } (without type keyword)\n * - export * from (without type keyword)\n *\n * @param source - The source code to analyze\n * @returns true if the file exports only types\n */\nexport function isTypeOnlyFile(source: string): boolean {\n // Remove comments to avoid false positives\n const withoutComments = source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // block comments\n .replace(/\\/\\/.*$/gm, \"\"); // line comments\n\n // Remove string literals to avoid false positives\n const withoutStrings = withoutComments\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, \"``\");\n\n // Patterns for type-only exports (these are safe)\n const typeOnlyPatterns = [\n /export\\s+type\\s+\\{[^}]*\\}/g, // export type { Foo, Bar }\n /export\\s+type\\s+\\{[^}]*\\}\\s+from\\s+['\"]/g, // export type { Foo } from \"...\"\n /export\\s+interface\\s+\\w+/g, // export interface Foo\n /export\\s+type\\s+\\w+\\s*=/g, // export type Foo =\n ];\n\n // Remove all type-only exports from consideration\n let remaining = withoutStrings;\n for (const pattern of typeOnlyPatterns) {\n remaining = remaining.replace(pattern, \"\");\n }\n\n // Patterns for runtime exports (these make the file NOT type-only)\n const runtimeExportPatterns = [\n /export\\s+(?:const|let|var)\\s+\\w+/g, // export const/let/var foo\n /export\\s+function\\s+\\w+/g, // export function foo\n /export\\s+async\\s+function\\s+\\w+/g, // export async function foo\n /export\\s+class\\s+\\w+/g, // export class Foo\n /export\\s+enum\\s+\\w+/g, // export enum Foo (enums have runtime value)\n /export\\s+default\\s+(?!type\\s)/g, // export default (not type)\n /export\\s+\\{[^}]*\\}(?!\\s+from)/g, // export { foo } (local re-export without type)\n /export\\s+\\{[^}]*\\}\\s+from\\s+['\"][^'\"]+['\"]/g, // export { foo } from (without type)\n /export\\s+\\*\\s+from\\s+['\"][^'\"]+['\"]/g, // export * from (re-exports everything)\n /export\\s+\\*\\s+as\\s+\\w+/g, // export * as namespace\n ];\n\n for (const pattern of runtimeExportPatterns) {\n if (pattern.test(remaining)) {\n // Reset regex lastIndex for subsequent tests\n pattern.lastIndex = 0;\n\n // Special case: check if export { } contains only type exports\n if (pattern.source.includes(\"export\\\\s+\\\\{\")) {\n const matches = remaining.match(/export\\s+\\{([^}]*)\\}(?:\\s+from\\s+['\"][^'\"]+['\"])?/g);\n if (matches) {\n for (const match of matches) {\n // Skip if it's already a type export\n if (/^export\\s+type\\s+/.test(match)) continue;\n\n // Extract the specifiers\n const specifiersMatch = match.match(/export\\s+\\{([^}]*)\\}/);\n if (specifiersMatch) {\n const specifiers = specifiersMatch[1];\n const items = specifiers\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n // If any specifier is NOT prefixed with \"type \", this is a runtime export\n const hasRuntimeSpecifier = items.some((item) => !item.startsWith(\"type \"));\n if (hasRuntimeSpecifier) {\n return false;\n }\n }\n }\n }\n continue;\n }\n\n return false;\n }\n }\n\n // If we get here, no runtime exports were found\n // But we should also verify the file has at least some type exports\n // (an empty file or file with only imports is not really \"type-only\")\n const hasTypeExports = typeOnlyPatterns.some((pattern) => {\n pattern.lastIndex = 0;\n return pattern.test(withoutStrings);\n });\n\n return hasTypeExports;\n}\n\n/**\n * Check if an import statement is a pure `import type` (the keyword sits\n * immediately after `import`, covering `import type { X }`, `import type Foo`,\n * and `import type * as Ns`).\n */\nfunction isTypeOnlyImport(line: string): boolean {\n const trimmed = line.trim();\n\n return trimmed.startsWith(\"import type \") || !!trimmed.match(/^import\\s+type\\s+[\\{\\*]/);\n}\n\n/**\n * Decide whether an `import ... from \"m\"` statement contributes any runtime\n * binding. Returns false for pure `import type` and for destructured imports\n * where every specifier is prefixed with the `type` keyword.\n */\nfunction hasRuntimeImports(line: string): boolean {\n const trimmed = line.trim();\n\n if (isTypeOnlyImport(trimmed)) {\n return false;\n }\n\n const specifiersMatch = trimmed.match(/import\\s+(?:type\\s+)?\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return true;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return !items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Decide whether an `export ... from \"m\"` statement re-exports only types.\n * Covers `export type { X } from`, `export type * from`, and\n * `export { type X, type Y } from`.\n */\nfunction isExportTypeOnlyStatement(line: string): boolean {\n const trimmed = line.trim();\n\n if (/^export\\s+type\\s+/.test(trimmed)) {\n return true;\n }\n\n const specifiersMatch = trimmed.match(/export\\s+\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return false;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Extract import paths using regex (more reliable for TypeScript).\n * Each entry carries an `isTypeOnly` flag: true when every occurrence of the\n * path in the source is a type-only statement (no runtime binding).\n * A single runtime occurrence makes the path runtime.\n */\nfunction extractImportPathsWithRegex(\n source: string,\n): Array<{ path: string; originalLine: string; isTypeOnly: boolean }> {\n const imports: Array<{ path: string; originalLine: string; isTypeOnly: boolean }> = [];\n const seenPaths = new Map<string, number>();\n\n /**\n * Record an import path with its type-only flag.\n * If the same path appears in multiple statements, the edge is type-only\n * iff every occurrence is type-only — a single runtime statement makes it runtime.\n */\n const record = (importPath: string, originalLine: string, isTypeOnly: boolean): void => {\n if (!importPath) {\n return;\n }\n\n const existingIndex = seenPaths.get(importPath);\n\n if (existingIndex === undefined) {\n seenPaths.set(importPath, imports.length);\n imports.push({ path: importPath, originalLine, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n imports[existingIndex].isTypeOnly = false;\n }\n };\n\n // Pattern 1: Standard ES module imports (handles multiline)\n // Matches: import { ... } from \"path\", import Foo from \"path\", import Foo, { ... } from \"path\"\n const importRegex =\n /import\\s+(?:type\\s+)?(\\{[\\s\\S]*?\\}|\\*\\s+as\\s+\\w+|\\w+(?:\\s*,\\s*\\{[\\s\\S]*?\\})?)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n let match;\n\n while ((match = importRegex.exec(source)) !== null) {\n const fullMatch = match[0];\n const importPath = match[2];\n const isTypeOnly = !hasRuntimeImports(fullMatch);\n\n record(importPath, fullMatch, isTypeOnly);\n }\n\n // Pattern 1b: Side-effect imports - import \"path\" — always runtime\n const sideEffectRegex = /import\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = sideEffectRegex.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 2: Dynamic imports - import(\"path\") — always runtime\n const dynamicImportPattern = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\n while ((match = dynamicImportPattern.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 3: Export from - export ... from \"path\"\n const exportFromPattern = /export\\s+(?:\\{[^}]*\\}|\\*|\\w+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = exportFromPattern.exec(source)) !== null) {\n const fullMatch = match[0];\n\n record(match[1], fullMatch, isExportTypeOnlyStatement(fullMatch));\n }\n\n return imports;\n}\n\n/**\n * Metadata for a single resolved import edge.\n * `isTypeOnly` is true iff every import/export statement that references this\n * path is type-only (pure `import type`, `export type`, or destructured lists\n * where every specifier carries the `type` keyword). A single runtime\n * occurrence flips the flag to false — that's what matters for cycle detection.\n */\nexport type ResolvedImport = {\n absolutePath: string;\n isTypeOnly: boolean;\n};\n\n/**\n * Parse import/export statements in a TS/JS source file and return the\n * resolved dependency edges keyed by the original import path. Includes\n * type-only edges (flagged) so downstream consumers can distinguish runtime\n * cycles from type-only ones.\n *\n * @example\n * const importMap = await parseImports(source, \"/abs/path/to/file.ts\");\n * for (const [importPath, { absolutePath, isTypeOnly }] of importMap) {\n * // ...\n * }\n */\nexport async function parseImports(\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n try {\n // Skip .d.ts files - they're type declarations, not runtime code\n if (filePath.endsWith(\".d.ts\")) {\n return new Map();\n }\n\n // Try es-module-lexer first (faster and more accurate for simple cases)\n try {\n const [imports] = await parse(source);\n\n if (imports && imports.length > 0) {\n return await resolveImports(imports as ImportSpecifier[], source, filePath);\n }\n } catch (lexerError) {\n // es-module-lexer failed, fall back to regex-based extraction\n // This is common with TypeScript files that have complex syntax\n }\n\n // Fallback: Use regex-based extraction (more forgiving with TypeScript)\n const regexImports = extractImportPathsWithRegex(source);\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const { path: importPath, isTypeOnly } of regexImports) {\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (resolvedPath) {\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n }\n\n return resolvedImports;\n } catch (error) {\n console.error(`Error parsing imports for ${filePath}:`, error);\n\n return new Map();\n }\n}\n\n/**\n * Merge a resolved import into the output map, preserving the type-only rule:\n * a path is type-only iff every statement that references it is type-only.\n */\nfunction mergeResolvedImport(\n target: Map<string, ResolvedImport>,\n importPath: string,\n absolutePath: string,\n isTypeOnly: boolean,\n): void {\n const existing = target.get(importPath);\n\n if (!existing) {\n target.set(importPath, { absolutePath, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n existing.isTypeOnly = false;\n }\n}\n\n/**\n * Resolve imports from `es-module-lexer` output. For each specifier, slice the\n * raw statement (`ss..se`) and re-run the type-only heuristic so we can flag\n * the edge correctly — the lexer itself does not distinguish `import type`.\n */\nasync function resolveImports(\n imports: ImportSpecifier[],\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const imp of imports) {\n const importPath = imp.n;\n\n if (!importPath) {\n continue;\n }\n\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (!resolvedPath) {\n continue;\n }\n\n const isTypeOnly = isLexerStatementTypeOnly(imp, source);\n\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n\n return resolvedImports;\n}\n\n/**\n * Classify a single lexer-reported statement as type-only or runtime.\n * Dynamic imports (`import(...)`) are always runtime. For static\n * imports/export-from we slice the raw source and apply the same heuristic\n * the regex fallback uses.\n */\nfunction isLexerStatementTypeOnly(imp: ImportSpecifier, source: string): boolean {\n // Dynamic imports always execute — they cannot be type-only.\n // es-module-lexer reports dynamic with a.n === undefined typically, but\n // we guard by checking the statement prefix too.\n const statementStart = imp.ss;\n const statementEnd = imp.se;\n\n if (\n typeof statementStart !== \"number\" ||\n typeof statementEnd !== \"number\" ||\n statementEnd <= statementStart\n ) {\n return false;\n }\n\n const statement = source.slice(statementStart, statementEnd);\n const trimmed = statement.trim();\n\n if (trimmed.startsWith(\"import(\") || /^\\bimport\\s*\\(/.test(trimmed)) {\n return false;\n }\n\n if (trimmed.startsWith(\"export\")) {\n return isExportTypeOnlyStatement(statement);\n }\n\n return !hasRuntimeImports(statement);\n}\n\n/**\n * Resolve alias imports to actual file paths with extensions\n * Example: app/users/services/get-users.service -> /absolute/path/to/src/app/users/services/get-users.service.ts\n */\nasync function resolveAliasImport(importPath: string): Promise<string | null> {\n // Use tsconfig manager to resolve the alias to an absolute path\n const resolvedBase = tsconfigManager.resolveAliasToAbsolute(importPath);\n\n if (!resolvedBase) return null;\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Resolve relative imports to actual file paths\n * Example: ./../services/get-user.service -> /absolute/path/to/services/get-user.service.ts\n */\nasync function resolveRelativeImport(\n importPath: string,\n currentFilePath: string,\n): Promise<string | null> {\n const dir = path.dirname(currentFilePath);\n // Use path.resolve to handle .. and . properly, then normalize to forward slashes\n const resolvedBase = Path.normalize(path.resolve(dir, importPath));\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Try to resolve a file path by checking different extensions\n * TypeScript/JavaScript files can be imported without extensions\n *\n * @TODO: For better performance, we need to check the files in files orchestrator\n * instead of using the file system as we will be fetching all project files anyway.\n */\n// Cache for file existence checks to avoid redundant filesystem calls\nconst fileExistsCache = new Map<string, boolean>();\n\n/**\n * Clear the file exists cache\n * Should be called when new files are created to ensure fresh lookups\n */\nexport function clearFileExistsCache(): void {\n fileExistsCache.clear();\n}\n\nasync function cachedFileExists(filePath: string): Promise<boolean> {\n if (fileExistsCache.has(filePath)) {\n return fileExistsCache.get(filePath)!;\n }\n const exists = (await fileExistsAsync(filePath)) as boolean;\n fileExistsCache.set(filePath, exists);\n return exists;\n}\n\nasync function tryResolveWithExtensions(basePath: string): Promise<string | null> {\n // Normalize the base path first (handle Windows paths)\n const normalizedBase = Path.normalize(basePath);\n\n // List of extensions to try, in order of preference\n const extensions = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"];\n const validExtensions = new Set(extensions);\n\n // If the path already has a VALID code file extension, check if it exists\n const ext = path.extname(normalizedBase);\n if (ext && validExtensions.has(ext)) {\n if (await cachedFileExists(normalizedBase)) {\n return normalizedBase;\n }\n // If explicit extension doesn't exist, return null\n return null;\n }\n\n // Try all extensions in parallel for better performance\n const pathsToCheck = extensions.map((extension) => normalizedBase + extension);\n const results = await Promise.all(\n pathsToCheck.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n // Return the first one that exists (in order of preference)\n for (const result of results) {\n if (result.exists) {\n return result.path;\n }\n }\n\n // Try index files in directory\n if (await directoryExistsAsync(normalizedBase)) {\n const indexPaths = extensions.map((extension) =>\n Path.join(normalizedBase, `index${extension}`),\n );\n const indexResults = await Promise.all(\n indexPaths.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n for (const result of indexResults) {\n if (result.exists) {\n return result.path;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Check if import is a Node.js built-in module\n */\nfunction isNodeBuiltin(importPath: string): boolean {\n const builtins = [\n \"fs\",\n \"path\",\n \"http\",\n \"https\",\n \"crypto\",\n \"stream\",\n \"util\",\n \"events\",\n \"buffer\",\n \"child_process\",\n \"os\",\n \"url\",\n \"querystring\",\n \"zlib\",\n \"net\",\n \"tls\",\n \"dns\",\n \"dgram\",\n \"cluster\",\n \"worker_threads\",\n \"perf_hooks\",\n \"async_hooks\",\n \"timers\",\n \"readline\",\n \"repl\",\n \"vm\",\n \"assert\",\n \"console\",\n \"process\",\n \"v8\",\n ];\n\n // Check for node: prefix or direct builtin name\n if (importPath.startsWith(\"node:\")) return true;\n\n const moduleName = importPath.split(\"/\")[0];\n return builtins.includes(moduleName);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ready-block.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/ready-block.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { Application } from \"../application\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { getHttpReadyReport, type HttpReadyReport } from \"../http/ready-report\";\n\n/**\n * The one screen `warlock dev` exists to print.\n *\n * ## What was wrong with the lines this replaces\n *\n * Every fact was announced by whichever component discovered it, the moment it\n * discovered it, in that component's own format:\n *\n * ℹ info (21:28:36.095) [http] [routes] 14 route(s) registered\n * ✓ success (21:28:36.118) [http] [connection] Server ready at http://[::1]:2030\n * 21:28:36 ➜ Development Server is ready in 660.72ms\n *\n * The ORDER of that output is the order the work finished in, which is not a\n * fact about the application and changes between runs. There was no single\n * place stating where to point a browser, and the one URL on screen was an IPv6\n * literal most terminals will not linkify. This is a screen a developer reads a\n * hundred times a day.\n *\n * So the facts are collected (`http/ready-report.ts`) and rendered once, here,\n * after boot has actually completed.\n *\n * ## The rule this file must not break\n *\n * **Warnings never move into the block.** They are logged by whoever found them\n * at the moment they were found, which puts them ABOVE this block — visible,\n * out of the summary, and impossible to mistake for decoration. A block that\n * absorbs warnings is a block that hides them, and this file is deliberately\n * incapable of receiving one: nothing in {@link ReadyBlockFacts} can carry a\n * warning.\n *\n * ## Why the URL is not the address that was bound\n *\n * `listen()` returns what it bound (`http://[::1]:2030` for a dual-stack\n * `localhost`). The block shows `http://localhost:2030`, which resolves back to\n * that exact socket. The transform is display-only and lives in\n * `connectors/describe-server-address.ts`; nothing here touches `host`, and the\n * set of interfaces the server accepts on is unchanged.\n */\n\nexport type ReadyBlockFacts = {\n /** The bound HTTP surface, or `undefined` for a process with no HTTP server. */\n http?: HttpReadyReport;\n /** Whether a web/SSR surface (the `web` connector) serves on the same origin. */\n hasWebSurface: boolean;\n /** `development` / `production` / `test`. */\n mode: string;\n /** How long the boot took, when the caller measured it. */\n bootDurationMs?: number;\n};\n\n/** Collect the facts from the places that own them. Pure read, no printing. */\nexport function collectReadyBlockFacts(bootDurationMs?: number): ReadyBlockFacts {\n return {\n http: getHttpReadyReport(),\n // The web connector serves pages on the SAME origin as the API — one port\n // serves everything in v5 — so its presence is what makes the \"Web\" row\n // true, not a second address. Showing the row unconditionally would tell an\n // API-only app it has a page surface it does not have.\n hasWebSurface: connectorsManager.list().some((connector) => connector.name === \"web\"),\n mode: Application.environment,\n bootDurationMs,\n };\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms.toFixed(0)}ms`;\n if (ms > 60_000) return `${(ms / 60_000).toFixed(2)}m`;\n\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/** ` ➜ App http://localhost:2042` */\nfunction urlRow(label: string, url: string, note?: string): string {\n const suffix = note ? ` ${colors.dim(note)}` : \"\";\n\n return ` ${colors.green(\"➜\")} ${colors.bold(label.padEnd(8))}${colors.cyan(url)}${suffix}`;\n}\n\n/** `Port` `2042`, joined into one dim summary line. */\nfunction factRow(pairs: [string, string][]): string {\n const rendered = pairs.map(([key, value]) => `${colors.dim(key)} ${colors.white(value)}`);\n\n return ` ${rendered.join(colors.dim(\" · \"))}`;\n}\n\n/**\n * Render the block as lines. Separated from printing so a test can assert the\n * exact content without capturing stdout, and so the renderer stays pure.\n */\nexport function renderReadyBlock(facts: ReadyBlockFacts): string[] {\n const lines: string[] = [\"\"];\n\n if (facts.http) {\n // A wildcard bind reaches more than loopback. `localhost` is still correct\n // and still reaches it, but the reader is told the bind is wider rather\n // than left to infer it from a URL that no longer says so.\n const note = facts.http.wildcardBind ? \"(all interfaces)\" : undefined;\n\n lines.push(urlRow(\"App\", facts.http.url, note));\n\n if (facts.hasWebSurface) {\n // Same origin, deliberately: pages and API share one port. Repeating the\n // URL is not redundancy here — it answers \"where do I open the site?\"\n // without the reader having to know that.\n lines.push(urlRow(\"Web\", facts.http.url));\n }\n\n if (facts.http.publicUrl) {\n // `publicUrl` arrives pre-labelled from `describeServerAddress`; the block\n // supplies its own label, so strip the prose and keep the address.\n const address = facts.http.publicUrl.replace(/^[^:]*:\\s*/, \"\");\n lines.push(urlRow(\"Public\", address));\n }\n }\n\n const pairs: [string, string][] = [];\n\n if (facts.http) {\n pairs.push([\"port\", String(facts.http.port)]);\n }\n\n pairs.push([\"mode\", facts.mode]);\n\n if (facts.http) {\n pairs.push([\"routes\", String(facts.http.routeCount)]);\n }\n\n if (facts.bootDurationMs !== undefined) {\n pairs.push([\"ready in\", formatDuration(facts.bootDurationMs)]);\n }\n\n lines.push(factRow(pairs));\n lines.push(\"\");\n\n return lines;\n}\n\n/** Collect, render, print. The only function the dev server needs. */\nexport function printReadyBlock(bootDurationMs?: number): void {\n for (const line of renderReadyBlock(collectReadyBlockFacts(bootDurationMs))) {\n console.log(line);\n }\n}\n"],"mappings":";;;;;;;;AAwDA,SAAgB,uBAAuB,gBAA0C;CAC/E,OAAO;EACL,MAAM,mBAAmB;EAKzB,eAAe,kBAAkB,KAAK,
|
|
1
|
+
{"version":3,"file":"ready-block.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/ready-block.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { Application } from \"../application\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { getHttpReadyReport, type HttpReadyReport } from \"../http/ready-report\";\n\n/**\n * The one screen `warlock dev` exists to print.\n *\n * ## What was wrong with the lines this replaces\n *\n * Every fact was announced by whichever component discovered it, the moment it\n * discovered it, in that component's own format:\n *\n * ℹ info (21:28:36.095) [http] [routes] 14 route(s) registered\n * ✓ success (21:28:36.118) [http] [connection] Server ready at http://[::1]:2030\n * 21:28:36 ➜ Development Server is ready in 660.72ms\n *\n * The ORDER of that output is the order the work finished in, which is not a\n * fact about the application and changes between runs. There was no single\n * place stating where to point a browser, and the one URL on screen was an IPv6\n * literal most terminals will not linkify. This is a screen a developer reads a\n * hundred times a day.\n *\n * So the facts are collected (`http/ready-report.ts`) and rendered once, here,\n * after boot has actually completed.\n *\n * ## The rule this file must not break\n *\n * **Warnings never move into the block.** They are logged by whoever found them\n * at the moment they were found, which puts them ABOVE this block — visible,\n * out of the summary, and impossible to mistake for decoration. A block that\n * absorbs warnings is a block that hides them, and this file is deliberately\n * incapable of receiving one: nothing in {@link ReadyBlockFacts} can carry a\n * warning.\n *\n * ## Why the URL is not the address that was bound\n *\n * `listen()` returns what it bound (`http://[::1]:2030` for a dual-stack\n * `localhost`). The block shows `http://localhost:2030`, which resolves back to\n * that exact socket. The transform is display-only and lives in\n * `connectors/describe-server-address.ts`; nothing here touches `host`, and the\n * set of interfaces the server accepts on is unchanged.\n */\n\nexport type ReadyBlockFacts = {\n /** The bound HTTP surface, or `undefined` for a process with no HTTP server. */\n http?: HttpReadyReport;\n /** Whether a web/SSR surface (the `web` connector) serves on the same origin. */\n hasWebSurface: boolean;\n /** `development` / `production` / `test`. */\n mode: string;\n /** How long the boot took, when the caller measured it. */\n bootDurationMs?: number;\n};\n\n/** Collect the facts from the places that own them. Pure read, no printing. */\nexport function collectReadyBlockFacts(bootDurationMs?: number): ReadyBlockFacts {\n return {\n http: getHttpReadyReport(),\n // The web connector serves pages on the SAME origin as the API — one port\n // serves everything in v5 — so its presence is what makes the \"Web\" row\n // true, not a second address. Showing the row unconditionally would tell an\n // API-only app it has a page surface it does not have.\n hasWebSurface: connectorsManager.list().some((connector) => connector.name === \"web\"),\n mode: Application.environment,\n bootDurationMs,\n };\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms.toFixed(0)}ms`;\n if (ms > 60_000) return `${(ms / 60_000).toFixed(2)}m`;\n\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/** ` ➜ App http://localhost:2042` */\nfunction urlRow(label: string, url: string, note?: string): string {\n const suffix = note ? ` ${colors.dim(note)}` : \"\";\n\n return ` ${colors.green(\"➜\")} ${colors.bold(label.padEnd(8))}${colors.cyan(url)}${suffix}`;\n}\n\n/** `Port` `2042`, joined into one dim summary line. */\nfunction factRow(pairs: [string, string][]): string {\n const rendered = pairs.map(([key, value]) => `${colors.dim(key)} ${colors.white(value)}`);\n\n return ` ${rendered.join(colors.dim(\" · \"))}`;\n}\n\n/**\n * Render the block as lines. Separated from printing so a test can assert the\n * exact content without capturing stdout, and so the renderer stays pure.\n */\nexport function renderReadyBlock(facts: ReadyBlockFacts): string[] {\n const lines: string[] = [\"\"];\n\n if (facts.http) {\n // A wildcard bind reaches more than loopback. `localhost` is still correct\n // and still reaches it, but the reader is told the bind is wider rather\n // than left to infer it from a URL that no longer says so.\n const note = facts.http.wildcardBind ? \"(all interfaces)\" : undefined;\n\n lines.push(urlRow(\"App\", facts.http.url, note));\n\n if (facts.hasWebSurface) {\n // Same origin, deliberately: pages and API share one port. Repeating the\n // URL is not redundancy here — it answers \"where do I open the site?\"\n // without the reader having to know that.\n lines.push(urlRow(\"Web\", facts.http.url));\n }\n\n if (facts.http.publicUrl) {\n // `publicUrl` arrives pre-labelled from `describeServerAddress`; the block\n // supplies its own label, so strip the prose and keep the address.\n const address = facts.http.publicUrl.replace(/^[^:]*:\\s*/, \"\");\n lines.push(urlRow(\"Public\", address));\n }\n }\n\n const pairs: [string, string][] = [];\n\n if (facts.http) {\n pairs.push([\"port\", String(facts.http.port)]);\n }\n\n pairs.push([\"mode\", facts.mode]);\n\n if (facts.http) {\n pairs.push([\"routes\", String(facts.http.routeCount)]);\n }\n\n if (facts.bootDurationMs !== undefined) {\n pairs.push([\"ready in\", formatDuration(facts.bootDurationMs)]);\n }\n\n lines.push(factRow(pairs));\n lines.push(\"\");\n\n return lines;\n}\n\n/** Collect, render, print. The only function the dev server needs. */\nexport function printReadyBlock(bootDurationMs?: number): void {\n for (const line of renderReadyBlock(collectReadyBlockFacts(bootDurationMs))) {\n console.log(line);\n }\n}\n"],"mappings":";;;;;;;;AAwDA,SAAgB,uBAAuB,gBAA0C;CAC/E,OAAO;EACL,MAAM,mBAAmB;EAKzB,eAAe,kBAAkB,KAAK,EAAE,MAAM,cAAc,UAAU,SAAS,KAAK;EACpF,MAAM,YAAY;EAClB;CACF;AACF;AAEA,SAAS,eAAe,IAAoB;CAC1C,IAAI,KAAK,KAAM,OAAO,GAAG,GAAG,QAAQ,CAAC,EAAE;CACvC,IAAI,KAAK,KAAQ,OAAO,IAAI,KAAK,KAAQ,QAAQ,CAAC,EAAE;CAEpD,OAAO,IAAI,KAAK,KAAM,QAAQ,CAAC,EAAE;AACnC;;AAGA,SAAS,OAAO,OAAe,KAAa,MAAuB;CACjE,MAAM,SAAS,OAAO,IAAI,OAAO,IAAI,IAAI,MAAM;CAE/C,OAAO,KAAK,OAAO,MAAM,GAAG,EAAE,IAAI,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,OAAO,KAAK,GAAG,IAAI;AACtF;;AAGA,SAAS,QAAQ,OAAmC;CAGlD,OAAO,QAFU,MAAM,KAAK,CAAC,KAAK,WAAW,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,MAAM,KAAK,GAE/D,EAAE,KAAK,OAAO,IAAI,OAAO,CAAC;AAClD;;;;;AAMA,SAAgB,iBAAiB,OAAkC;CACjE,MAAM,QAAkB,CAAC,EAAE;CAE3B,IAAI,MAAM,MAAM;EAId,MAAM,OAAO,MAAM,KAAK,eAAe,qBAAqB;EAE5D,MAAM,KAAK,OAAO,OAAO,MAAM,KAAK,KAAK,IAAI,CAAC;EAE9C,IAAI,MAAM,eAIR,MAAM,KAAK,OAAO,OAAO,MAAM,KAAK,GAAG,CAAC;EAG1C,IAAI,MAAM,KAAK,WAAW;GAGxB,MAAM,UAAU,MAAM,KAAK,UAAU,QAAQ,cAAc,EAAE;GAC7D,MAAM,KAAK,OAAO,UAAU,OAAO,CAAC;EACtC;CACF;CAEA,MAAM,QAA4B,CAAC;CAEnC,IAAI,MAAM,MACR,MAAM,KAAK,CAAC,QAAQ,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC;CAG9C,MAAM,KAAK,CAAC,QAAQ,MAAM,IAAI,CAAC;CAE/B,IAAI,MAAM,MACR,MAAM,KAAK,CAAC,UAAU,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC;CAGtD,IAAI,MAAM,mBAAmB,QAC3B,MAAM,KAAK,CAAC,YAAY,eAAe,MAAM,cAAc,CAAC,CAAC;CAG/D,MAAM,KAAK,QAAQ,KAAK,CAAC;CACzB,MAAM,KAAK,EAAE;CAEb,OAAO;AACT;;AAGA,SAAgB,gBAAgB,gBAA+B;CAC7D,KAAK,MAAM,QAAQ,iBAAiB,uBAAuB,cAAc,CAAC,GACxE,QAAQ,IAAI,IAAI;AAEpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"special-files-collector.d.mts","names":[],"sources":["../../../../../../../core/src/dev-server/special-files-collector.ts"],"mappings":";;;KAEY,eAAA;;AAAZ;;;cAMa,qBAAA;EAAA,iBACM,OAAA;EAQV,OAAA,CAAQ,KAAA,EAAO,GAAA,SAAY,WAAA;EAO3B,OAAA,CAAQ,WAAA,EAAa,WAAA;EAIrB,UAAA,CAAW,YAAA;EAIX,UAAA,CAAW,WAAA,EAAa,WAAA;EAKxB,WAAA,CAAY,YAAA,WAAuB,eAAA;EAUnC,cAAA,CAAe,IAAA,EAAM,eAAA,GAAkB,WAAA;EAUvC,QAAA,
|
|
1
|
+
{"version":3,"file":"special-files-collector.d.mts","names":[],"sources":["../../../../../../../core/src/dev-server/special-files-collector.ts"],"mappings":";;;KAEY,eAAA;;AAAZ;;;cAMa,qBAAA;EAAA,iBACM,OAAA;EAQV,OAAA,CAAQ,KAAA,EAAO,GAAA,SAAY,WAAA;EAO3B,OAAA,CAAQ,WAAA,EAAa,WAAA;EAIrB,UAAA,CAAW,YAAA;EAIX,UAAA,CAAW,WAAA,EAAa,WAAA;EAKxB,WAAA,CAAY,YAAA,WAAuB,eAAA;EAUnC,cAAA,CAAe,IAAA,EAAM,eAAA,GAAkB,WAAA;EAUvC,QAAA,CAAA,GAAY,MAAA,CAAO,eAAA;EAUnB,KAAA,CAAA;EAAA,QAIC,UAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"special-files-collector.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/special-files-collector.ts"],"sourcesContent":["import { FileManager } from \"./file-manager\";\r\n\r\nexport type SpecialFileType = \"config\" | \"main\" | \"route\" | \"event\" | \"locale\";\r\n\r\n/**\r\n * Categorises files into the kinds the dev server treats specially\r\n * (config / main / route / event / locale) and exposes typed accessors.\r\n */\r\nexport class SpecialFilesCollector {\r\n private readonly buckets: Record<SpecialFileType, Map<string, FileManager>> = {\r\n config: new Map(),\r\n main: new Map(),\r\n route: new Map(),\r\n event: new Map(),\r\n locale: new Map(),\r\n };\r\n\r\n public collect(files: Map<string, FileManager>): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.clear();\r\n for (const [relativePath, fileManager] of files) {\r\n this.categorise(relativePath, fileManager);\r\n }\r\n }\r\n\r\n public addFile(fileManager: FileManager): void {\r\n this.categorise(fileManager.relativePath, fileManager);\r\n }\r\n\r\n public removeFile(relativePath: string): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.delete(relativePath);\r\n }\r\n\r\n public updateFile(fileManager: FileManager): void {\r\n this.removeFile(fileManager.relativePath);\r\n this.categorise(fileManager.relativePath, fileManager);\r\n }\r\n\r\n public getFileType(relativePath: string): SpecialFileType | null {\r\n for (const [type, bucket] of Object.entries(this.buckets) as [\r\n SpecialFileType,\r\n Map<string, FileManager>,\r\n ][]) {\r\n if (bucket.has(relativePath)) return type;\r\n }\r\n return null;\r\n }\r\n\r\n public getFilesByType(type: SpecialFileType): FileManager[] {\r\n const files = Array.from(this.buckets[type].values());\r\n // Routes are loaded sequentially in alphabetical order so registration\r\n // is deterministic across runs.\r\n if (type === \"route\") {\r\n files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));\r\n }\r\n return files;\r\n }\r\n\r\n public getStats(): Record<SpecialFileType, number> {\r\n return {\r\n config: this.buckets.config.size,\r\n main: this.buckets.main.size,\r\n route: this.buckets.route.size,\r\n event: this.buckets.event.size,\r\n locale: this.buckets.locale.size,\r\n };\r\n }\r\n\r\n public clear(): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.clear();\r\n }\r\n\r\n private categorise(relativePath: string, fileManager: FileManager): void {\r\n if (isConfigFile(relativePath)) {\r\n this.buckets.config.set(relativePath, fileManager);\r\n } else if (isMainFile(relativePath)) {\r\n this.buckets.main.set(relativePath, fileManager);\r\n } else if (isRouteFile(relativePath)) {\r\n this.buckets.route.set(relativePath, fileManager);\r\n } else if (isEventFile(relativePath)) {\r\n this.buckets.event.set(relativePath, fileManager);\r\n } else if (isLocaleFile(relativePath)) {\r\n this.buckets.locale.set(relativePath, fileManager);\r\n }\r\n }\r\n}\r\n\r\nconst isConfigFile = (path: string) => /^src\\/config\\/.*\\.(ts|tsx)$/.test(path);\r\nconst isMainFile = (path: string) =>\r\n /^src\\/app\\/[^/]+\\/main\\.(ts|tsx)$/.test(path) ||\r\n path === \"src/app/main.ts\" ||\r\n path === \"src/app/main.tsx\";\r\nconst isRouteFile = (path: string) => /^src\\/app\\/[^/]+\\/routes\\.(ts|tsx)$/.test(path);\r\nconst isEventFile = (path: string) => /^src\\/app\\/[^/]+\\/events\\/[^/]+\\.(ts|tsx)$/.test(path);\r\nconst isLocaleFile = (path: string) => /^src\\/app\\/[^/]+\\/utils\\/locales\\.(ts|tsx)$/.test(path);\r\n"],"mappings":";;;;;AAQA,IAAa,wBAAb,MAAmC;;iBAC6C;GAC5E,wBAAQ,IAAI,IAAI;GAChB,sBAAM,IAAI,IAAI;GACd,uBAAO,IAAI,IAAI;GACf,uBAAO,IAAI,IAAI;GACf,wBAAQ,IAAI,IAAI;EAClB;;CAEA,AAAO,QAAQ,OAAuC;EACpD,KAAK,MAAM,UAAU,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,MAAM;EAC/D,KAAK,MAAM,CAAC,cAAc,gBAAgB,OACxC,KAAK,WAAW,cAAc,WAAW;CAE7C;CAEA,AAAO,QAAQ,aAAgC;EAC7C,KAAK,WAAW,YAAY,cAAc,WAAW;CACvD;CAEA,AAAO,WAAW,cAA4B;EAC5C,KAAK,MAAM,UAAU,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,OAAO,YAAY;CAC9E;CAEA,AAAO,WAAW,aAAgC;EAChD,KAAK,WAAW,YAAY,YAAY;EACxC,KAAK,WAAW,YAAY,cAAc,WAAW;CACvD;CAEA,AAAO,YAAY,cAA8C;EAC/D,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,KAAK,OAAO,GAItD,IAAI,OAAO,IAAI,YAAY,GAAG,OAAO;EAEvC,OAAO;CACT;CAEA,AAAO,eAAe,MAAsC;EAC1D,MAAM,QAAQ,MAAM,KAAK,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"special-files-collector.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/special-files-collector.ts"],"sourcesContent":["import { FileManager } from \"./file-manager\";\r\n\r\nexport type SpecialFileType = \"config\" | \"main\" | \"route\" | \"event\" | \"locale\";\r\n\r\n/**\r\n * Categorises files into the kinds the dev server treats specially\r\n * (config / main / route / event / locale) and exposes typed accessors.\r\n */\r\nexport class SpecialFilesCollector {\r\n private readonly buckets: Record<SpecialFileType, Map<string, FileManager>> = {\r\n config: new Map(),\r\n main: new Map(),\r\n route: new Map(),\r\n event: new Map(),\r\n locale: new Map(),\r\n };\r\n\r\n public collect(files: Map<string, FileManager>): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.clear();\r\n for (const [relativePath, fileManager] of files) {\r\n this.categorise(relativePath, fileManager);\r\n }\r\n }\r\n\r\n public addFile(fileManager: FileManager): void {\r\n this.categorise(fileManager.relativePath, fileManager);\r\n }\r\n\r\n public removeFile(relativePath: string): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.delete(relativePath);\r\n }\r\n\r\n public updateFile(fileManager: FileManager): void {\r\n this.removeFile(fileManager.relativePath);\r\n this.categorise(fileManager.relativePath, fileManager);\r\n }\r\n\r\n public getFileType(relativePath: string): SpecialFileType | null {\r\n for (const [type, bucket] of Object.entries(this.buckets) as [\r\n SpecialFileType,\r\n Map<string, FileManager>,\r\n ][]) {\r\n if (bucket.has(relativePath)) return type;\r\n }\r\n return null;\r\n }\r\n\r\n public getFilesByType(type: SpecialFileType): FileManager[] {\r\n const files = Array.from(this.buckets[type].values());\r\n // Routes are loaded sequentially in alphabetical order so registration\r\n // is deterministic across runs.\r\n if (type === \"route\") {\r\n files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));\r\n }\r\n return files;\r\n }\r\n\r\n public getStats(): Record<SpecialFileType, number> {\r\n return {\r\n config: this.buckets.config.size,\r\n main: this.buckets.main.size,\r\n route: this.buckets.route.size,\r\n event: this.buckets.event.size,\r\n locale: this.buckets.locale.size,\r\n };\r\n }\r\n\r\n public clear(): void {\r\n for (const bucket of Object.values(this.buckets)) bucket.clear();\r\n }\r\n\r\n private categorise(relativePath: string, fileManager: FileManager): void {\r\n if (isConfigFile(relativePath)) {\r\n this.buckets.config.set(relativePath, fileManager);\r\n } else if (isMainFile(relativePath)) {\r\n this.buckets.main.set(relativePath, fileManager);\r\n } else if (isRouteFile(relativePath)) {\r\n this.buckets.route.set(relativePath, fileManager);\r\n } else if (isEventFile(relativePath)) {\r\n this.buckets.event.set(relativePath, fileManager);\r\n } else if (isLocaleFile(relativePath)) {\r\n this.buckets.locale.set(relativePath, fileManager);\r\n }\r\n }\r\n}\r\n\r\nconst isConfigFile = (path: string) => /^src\\/config\\/.*\\.(ts|tsx)$/.test(path);\r\nconst isMainFile = (path: string) =>\r\n /^src\\/app\\/[^/]+\\/main\\.(ts|tsx)$/.test(path) ||\r\n path === \"src/app/main.ts\" ||\r\n path === \"src/app/main.tsx\";\r\nconst isRouteFile = (path: string) => /^src\\/app\\/[^/]+\\/routes\\.(ts|tsx)$/.test(path);\r\nconst isEventFile = (path: string) => /^src\\/app\\/[^/]+\\/events\\/[^/]+\\.(ts|tsx)$/.test(path);\r\nconst isLocaleFile = (path: string) => /^src\\/app\\/[^/]+\\/utils\\/locales\\.(ts|tsx)$/.test(path);\r\n"],"mappings":";;;;;AAQA,IAAa,wBAAb,MAAmC;;iBAC6C;GAC5E,wBAAQ,IAAI,IAAI;GAChB,sBAAM,IAAI,IAAI;GACd,uBAAO,IAAI,IAAI;GACf,uBAAO,IAAI,IAAI;GACf,wBAAQ,IAAI,IAAI;EAClB;;CAEA,AAAO,QAAQ,OAAuC;EACpD,KAAK,MAAM,UAAU,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,MAAM;EAC/D,KAAK,MAAM,CAAC,cAAc,gBAAgB,OACxC,KAAK,WAAW,cAAc,WAAW;CAE7C;CAEA,AAAO,QAAQ,aAAgC;EAC7C,KAAK,WAAW,YAAY,cAAc,WAAW;CACvD;CAEA,AAAO,WAAW,cAA4B;EAC5C,KAAK,MAAM,UAAU,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,OAAO,YAAY;CAC9E;CAEA,AAAO,WAAW,aAAgC;EAChD,KAAK,WAAW,YAAY,YAAY;EACxC,KAAK,WAAW,YAAY,cAAc,WAAW;CACvD;CAEA,AAAO,YAAY,cAA8C;EAC/D,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,KAAK,OAAO,GAItD,IAAI,OAAO,IAAI,YAAY,GAAG,OAAO;EAEvC,OAAO;CACT;CAEA,AAAO,eAAe,MAAsC;EAC1D,MAAM,QAAQ,MAAM,KAAK,KAAK,QAAQ,MAAM,OAAO,CAAC;EAGpD,IAAI,SAAS,SACX,MAAM,MAAM,GAAG,MAAM,EAAE,aAAa,cAAc,EAAE,YAAY,CAAC;EAEnE,OAAO;CACT;CAEA,AAAO,WAA4C;EACjD,OAAO;GACL,QAAQ,KAAK,QAAQ,OAAO;GAC5B,MAAM,KAAK,QAAQ,KAAK;GACxB,OAAO,KAAK,QAAQ,MAAM;GAC1B,OAAO,KAAK,QAAQ,MAAM;GAC1B,QAAQ,KAAK,QAAQ,OAAO;EAC9B;CACF;CAEA,AAAO,QAAc;EACnB,KAAK,MAAM,UAAU,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,MAAM;CACjE;CAEA,AAAQ,WAAW,cAAsB,aAAgC;EACvE,IAAI,aAAa,YAAY,GAC3B,KAAK,QAAQ,OAAO,IAAI,cAAc,WAAW;OAC5C,IAAI,WAAW,YAAY,GAChC,KAAK,QAAQ,KAAK,IAAI,cAAc,WAAW;OAC1C,IAAI,YAAY,YAAY,GACjC,KAAK,QAAQ,MAAM,IAAI,cAAc,WAAW;OAC3C,IAAI,YAAY,YAAY,GACjC,KAAK,QAAQ,MAAM,IAAI,cAAc,WAAW;OAC3C,IAAI,aAAa,YAAY,GAClC,KAAK,QAAQ,OAAO,IAAI,cAAc,WAAW;CAErD;AACF;AAEA,MAAM,gBAAgB,SAAiB,8BAA8B,KAAK,IAAI;AAC9E,MAAM,cAAc,SAClB,oCAAoC,KAAK,IAAI,KAC7C,SAAS,qBACT,SAAS;AACX,MAAM,eAAe,SAAiB,sCAAsC,KAAK,IAAI;AACrF,MAAM,eAAe,SAAiB,6CAA6C,KAAK,IAAI;AAC5F,MAAM,gBAAgB,SAAiB,8CAA8C,KAAK,IAAI"}
|
|
@@ -51,12 +51,11 @@ function isDevWorker() {
|
|
|
51
51
|
function superviseDevServer(now = Date.now) {
|
|
52
52
|
let worker;
|
|
53
53
|
let shuttingDown = false;
|
|
54
|
-
let startedAt = 0;
|
|
55
54
|
/** Timestamps of recent crash-restarts, used to detect flapping. */
|
|
56
55
|
let crashes = [];
|
|
57
56
|
const spawnWorker = () => {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const spawnedAt = now();
|
|
58
|
+
const spawnedWorker = spawn(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
|
|
60
59
|
cwd: process.cwd(),
|
|
61
60
|
env: {
|
|
62
61
|
...process.env,
|
|
@@ -65,12 +64,16 @@ function superviseDevServer(now = Date.now) {
|
|
|
65
64
|
stdio: "inherit",
|
|
66
65
|
windowsHide: false
|
|
67
66
|
});
|
|
68
|
-
worker
|
|
67
|
+
worker = spawnedWorker;
|
|
68
|
+
spawnedWorker.on("error", (error) => {
|
|
69
|
+
if (worker !== spawnedWorker) return;
|
|
69
70
|
shuttingDown = true;
|
|
70
71
|
devLogError(`Could not start the development server: ${error.message}`);
|
|
71
72
|
process.exit(1);
|
|
72
73
|
});
|
|
73
|
-
|
|
74
|
+
spawnedWorker.on("exit", (code, signal) => {
|
|
75
|
+
if (worker !== spawnedWorker) return;
|
|
76
|
+
worker = void 0;
|
|
74
77
|
releaseTerminal();
|
|
75
78
|
if (shuttingDown) {
|
|
76
79
|
process.exit(signal ? 1 : code ?? 0);
|
|
@@ -80,7 +83,7 @@ function superviseDevServer(now = Date.now) {
|
|
|
80
83
|
spawnWorker();
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
83
|
-
if ((signal !== null || (code ?? 0) !== 0) && shouldRecoverFromCrash(now() -
|
|
86
|
+
if ((signal !== null || (code ?? 0) !== 0) && shouldRecoverFromCrash(now() - spawnedAt)) {
|
|
84
87
|
devLogWarn(`Development server ${signal ? `was killed by ${signal}` : `exited with code ${code}`} — restarting.`);
|
|
85
88
|
spawnWorker();
|
|
86
89
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/supervisor.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport { devLogError, devLogWarn } from \"./dev-logger\";\n\n/**\n * Set on the worker process so it knows it was launched by a supervisor —\n * and so `warlock dev` inside the worker runs the server instead of\n * supervising a third process.\n */\nexport const WORKER_ENV_FLAG = \"WARLOCK_DEV_WORKER\";\n\n/**\n * Exit code a worker uses to ask for a fresh process. Chosen as EX_TEMPFAIL,\n * whose conventional meaning (\"temporary failure, retry\") is close enough that\n * a stray supervisor-less exit reads sensibly in a shell.\n */\nexport const RESTART_EXIT_CODE = 75;\n\n/** Signals the supervisor must hand to the worker rather than act on itself. */\nconst FORWARDED_SIGNALS: NodeJS.Signals[] = [\"SIGTERM\", \"SIGHUP\"];\n\n/**\n * How long a worker must survive before an abnormal exit counts as a crash\n * worth recovering from rather than a failed boot worth reporting once.\n */\nconst HEALTHY_UPTIME_MS = 5_000;\n\n/** Crashes allowed inside {@link CRASH_WINDOW_MS} before we stop restarting. */\nconst CRASH_RESTART_LIMIT = 3;\n\n/** The rolling window flapping is measured over. */\nconst CRASH_WINDOW_MS = 60_000;\n\n/** Whether this process is the worker (i.e. a supervisor already exists). */\nexport function isDevWorker(): boolean {\n return process.env[WORKER_ENV_FLAG] === \"1\";\n}\n\n/**\n * Run `warlock dev` as a supervised pair: a thin parent that owns the terminal\n * and a disposable child that is the actual dev server.\n *\n * The child asks to be replaced by exiting {@link RESTART_EXIT_CODE}; the\n * supervisor spawns a fresh one in its place. Because the parent is never\n * itself replaced, the process tree stays exactly two deep no matter how many\n * restarts happen — the previous design re-exec'd itself and left one idle\n * waiter behind per restart.\n *\n * The supervisor deliberately loads no config, no connectors, and no\n * application code, so a worker that wedges or dies can always be replaced by\n * a process that is known-good.\n *\n * Never resolves: the returned promise is a deliberate dead end so callers can\n * `return` it to stop everything downstream (notably the command's preloaders,\n * which must only ever run in the worker). The process ends with the last\n * worker, not with this promise.\n */\nexport function superviseDevServer(now: () => number = Date.now): Promise<never> {\n let worker: ChildProcess | undefined;\n let shuttingDown = false;\n
|
|
1
|
+
{"version":3,"file":"supervisor.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/supervisor.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport { devLogError, devLogWarn } from \"./dev-logger\";\n\n/**\n * Set on the worker process so it knows it was launched by a supervisor —\n * and so `warlock dev` inside the worker runs the server instead of\n * supervising a third process.\n */\nexport const WORKER_ENV_FLAG = \"WARLOCK_DEV_WORKER\";\n\n/**\n * Exit code a worker uses to ask for a fresh process. Chosen as EX_TEMPFAIL,\n * whose conventional meaning (\"temporary failure, retry\") is close enough that\n * a stray supervisor-less exit reads sensibly in a shell.\n */\nexport const RESTART_EXIT_CODE = 75;\n\n/** Signals the supervisor must hand to the worker rather than act on itself. */\nconst FORWARDED_SIGNALS: NodeJS.Signals[] = [\"SIGTERM\", \"SIGHUP\"];\n\n/**\n * How long a worker must survive before an abnormal exit counts as a crash\n * worth recovering from rather than a failed boot worth reporting once.\n */\nconst HEALTHY_UPTIME_MS = 5_000;\n\n/** Crashes allowed inside {@link CRASH_WINDOW_MS} before we stop restarting. */\nconst CRASH_RESTART_LIMIT = 3;\n\n/** The rolling window flapping is measured over. */\nconst CRASH_WINDOW_MS = 60_000;\n\n/** Whether this process is the worker (i.e. a supervisor already exists). */\nexport function isDevWorker(): boolean {\n return process.env[WORKER_ENV_FLAG] === \"1\";\n}\n\n/**\n * Run `warlock dev` as a supervised pair: a thin parent that owns the terminal\n * and a disposable child that is the actual dev server.\n *\n * The child asks to be replaced by exiting {@link RESTART_EXIT_CODE}; the\n * supervisor spawns a fresh one in its place. Because the parent is never\n * itself replaced, the process tree stays exactly two deep no matter how many\n * restarts happen — the previous design re-exec'd itself and left one idle\n * waiter behind per restart.\n *\n * The supervisor deliberately loads no config, no connectors, and no\n * application code, so a worker that wedges or dies can always be replaced by\n * a process that is known-good.\n *\n * Never resolves: the returned promise is a deliberate dead end so callers can\n * `return` it to stop everything downstream (notably the command's preloaders,\n * which must only ever run in the worker). The process ends with the last\n * worker, not with this promise.\n */\nexport function superviseDevServer(now: () => number = Date.now): Promise<never> {\n let worker: ChildProcess | undefined;\n let shuttingDown = false;\n\n /** Timestamps of recent crash-restarts, used to detect flapping. */\n let crashes: number[] = [];\n\n const spawnWorker = (): void => {\n const spawnedAt = now();\n\n const spawnedWorker = spawn(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {\n cwd: process.cwd(),\n env: { ...process.env, [WORKER_ENV_FLAG]: \"1\" },\n stdio: \"inherit\",\n windowsHide: false,\n });\n worker = spawnedWorker;\n\n spawnedWorker.on(\"error\", (error) => {\n if (worker !== spawnedWorker) return;\n\n shuttingDown = true;\n devLogError(`Could not start the development server: ${error.message}`);\n process.exit(1);\n });\n\n spawnedWorker.on(\"exit\", (code, signal) => {\n // A replaced worker can finish emitting lifecycle events after its\n // successor is already healthy. It must not spend that successor's crash\n // budget, spawn another worker, or print a terminal flapping diagnosis.\n if (worker !== spawnedWorker) return;\n\n worker = undefined;\n releaseTerminal();\n\n // `return` after every exit deliberately: `process.exit` is typed\n // `never`, but treating it as control flow means anything that stubs it\n // out turns \"we are shutting down\" into \"spawn another worker\".\n if (shuttingDown) {\n process.exit(signal ? 1 : (code ?? 0));\n return;\n }\n\n if (code === RESTART_EXIT_CODE) {\n spawnWorker();\n return;\n }\n\n const crashed = signal !== null || (code ?? 0) !== 0;\n\n if (crashed && shouldRecoverFromCrash(now() - spawnedAt)) {\n devLogWarn(\n `Development server ${signal ? `was killed by ${signal}` : `exited with code ${code}`} — restarting.`,\n );\n spawnWorker();\n return;\n }\n\n // The worker owns its own error reporting; the supervisor only mirrors\n // the outcome so `npm run dev` and CI see the code they expect.\n process.exit(signal ? 1 : (code ?? 0));\n });\n };\n\n /**\n * Whether a worker that died on its own should be replaced.\n *\n * The rule is minimum healthy uptime, not a bare retry count. A worker that\n * dies almost immediately failed to *boot* — a broken config, a port already\n * taken — and it has already printed why; respawning would just reprint the\n * same error and bury it. A worker that ran fine for a while and then died\n * (OOM, a native crash, a dead loader thread) is the case worth recovering,\n * because the state that killed it is usually not reproduced on a fresh boot.\n *\n * Flapping is still capped: enough crashes inside one window and we stop and\n * say so, rather than restarting forever behind the developer's back.\n */\n function shouldRecoverFromCrash(uptimeMs: number): boolean {\n if (uptimeMs < HEALTHY_UPTIME_MS) {\n return false;\n }\n\n const at = now();\n\n crashes = [...crashes.filter((time) => at - time < CRASH_WINDOW_MS), at];\n\n if (crashes.length > CRASH_RESTART_LIMIT) {\n devLogError(\n `Development server crashed ${crashes.length} times in ` +\n `${Math.round(CRASH_WINDOW_MS / 1000)}s — not restarting again. ` +\n `Fix the cause and run \\`warlock dev\\` when you're ready.`,\n );\n return false;\n }\n\n return true;\n }\n\n // SIGINT reaches the worker directly from the terminal (same process group),\n // so the supervisor must NOT act on it — exiting here would orphan a server\n // that is still draining. It only notes that the next exit is final.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n });\n\n for (const signal of FORWARDED_SIGNALS) {\n process.on(signal, () => {\n shuttingDown = true;\n worker?.kill(signal);\n });\n }\n\n spawnWorker();\n\n return new Promise<never>(() => {\n // Intentionally never settles — see the doc comment.\n });\n}\n\n/**\n * Put the shared TTY back into a sane state after a worker exits.\n *\n * The worker takes stdin into raw mode for its keyboard shortcuts and releases\n * it on shutdown — but a worker that was killed outright never got the chance,\n * and the mode belongs to the terminal, not the process that set it. Without\n * this, the next worker (or the user's shell) inherits a stdin with no line\n * editing and no Ctrl+C.\n */\nfunction releaseTerminal(): void {\n try {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n } catch {\n // Nothing to recover if the terminal is already gone.\n }\n}\n"],"mappings":";;;;;;;;;AAQA,MAAa,kBAAkB;;;;;;AAO/B,MAAa,oBAAoB;;AAGjC,MAAM,oBAAsC,CAAC,WAAW,QAAQ;;;;;AAMhE,MAAM,oBAAoB;;AAG1B,MAAM,sBAAsB;;AAG5B,MAAM,kBAAkB;;AAGxB,SAAgB,cAAuB;CACrC,OAAO,QAAQ,IAAI,qBAAqB;AAC1C;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,mBAAmB,MAAoB,KAAK,KAAqB;CAC/E,IAAI;CACJ,IAAI,eAAe;;CAGnB,IAAI,UAAoB,CAAC;CAEzB,MAAM,oBAA0B;EAC9B,MAAM,YAAY,IAAI;EAEtB,MAAM,gBAAgB,MAAM,QAAQ,UAAU,CAAC,GAAG,QAAQ,UAAU,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG;GAC7F,KAAK,QAAQ,IAAI;GACjB,KAAK;IAAE,GAAG,QAAQ;KAAM,kBAAkB;GAAI;GAC9C,OAAO;GACP,aAAa;EACf,CAAC;EACD,SAAS;EAET,cAAc,GAAG,UAAU,UAAU;GACnC,IAAI,WAAW,eAAe;GAE9B,eAAe;GACf,YAAY,2CAA2C,MAAM,SAAS;GACtE,QAAQ,KAAK,CAAC;EAChB,CAAC;EAED,cAAc,GAAG,SAAS,MAAM,WAAW;GAIzC,IAAI,WAAW,eAAe;GAE9B,SAAS;GACT,gBAAgB;GAKhB,IAAI,cAAc;IAChB,QAAQ,KAAK,SAAS,IAAK,QAAQ,CAAE;IACrC;GACF;GAEA,IAAI,aAA4B;IAC9B,YAAY;IACZ;GACF;GAIA,KAFgB,WAAW,SAAS,QAAQ,OAAO,MAEpC,uBAAuB,IAAI,IAAI,SAAS,GAAG;IACxD,WACE,sBAAsB,SAAS,iBAAiB,WAAW,oBAAoB,OAAO,eACxF;IACA,YAAY;IACZ;GACF;GAIA,QAAQ,KAAK,SAAS,IAAK,QAAQ,CAAE;EACvC,CAAC;CACH;;;;;;;;;;;;;;CAeA,SAAS,uBAAuB,UAA2B;EACzD,IAAI,WAAW,mBACb,OAAO;EAGT,MAAM,KAAK,IAAI;EAEf,UAAU,CAAC,GAAG,QAAQ,QAAQ,SAAS,KAAK,OAAO,eAAe,GAAG,EAAE;EAEvE,IAAI,QAAQ,SAAS,qBAAqB;GACxC,YACE,8BAA8B,QAAQ,OAAO,YACxC,KAAK,MAAM,kBAAkB,GAAI,EAAE,mFAE1C;GACA,OAAO;EACT;EAEA,OAAO;CACT;CAKA,QAAQ,GAAG,gBAAgB;EACzB,eAAe;CACjB,CAAC;CAED,KAAK,MAAM,UAAU,mBACnB,QAAQ,GAAG,cAAc;EACvB,eAAe;EACf,QAAQ,KAAK,MAAM;CACrB,CAAC;CAGH,YAAY;CAEZ,OAAO,IAAI,cAAqB,CAEhC,CAAC;AACH;;;;;;;;;;AAWA,SAAS,kBAAwB;CAC/B,IAAI;EACF,IAAI,QAAQ,MAAM,OAChB,QAAQ,MAAM,WAAW,KAAK;CAElC,QAAQ,CAER;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class TSConfigManager {\n /**\n * Aliases list (from tsconfig paths)\n */\n public aliases: Record<string, string[]> = {};\n\n /**\n * Base URL for resolving paths\n */\n public baseUrl: string = \".\";\n\n /**\n * TSConfig\n */\n public tsconfig: any;\n\n public init() {\n if (this.tsconfig) return;\n\n // use typescript to load the tsconfig.json file\n const output = ts.readConfigFile(Path.toAbsolute(\"tsconfig.json\"), ts.sys.readFile);\n\n this.tsconfig = output.config!;\n\n this.aliases = output.config?.compilerOptions?.paths || {};\n\n this.baseUrl = output.config?.compilerOptions?.baseUrl || \".\";\n }\n\n /**\n * Check if the given path is an alias\n * This checks if it's a REAL path alias (not an external package alias)\n *\n * Real aliases map to local paths (e.g., app/* -> src/app/*, src/* -> src/*)\n * External package aliases map to themselves with @ prefix (e.g., @warlock.js/core -> @warlock.js/core)\n */\n public isAlias(path: string) {\n if (!this.tsconfig) {\n this.init();\n }\n\n return Object.keys(this.aliases).some((alias) => {\n // Remove /* from alias pattern for matching\n const aliasPattern = alias.replace(\"/*\", \"\");\n\n if (!path.startsWith(aliasPattern)) {\n return false;\n }\n\n // Check if this is a real alias or just an external package mapping\n const aliasTargets = this.aliases[alias];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return false;\n }\n\n // If the alias starts with @, it's likely an external package alias\n // Example: \"@warlock.js/core\" -> \"@warlock.js/core\" (external package)\n if (aliasPattern.startsWith(\"@\")) {\n return false;\n }\n\n // Otherwise, it's a real path alias (including self-referencing ones like src/* -> src/*)\n // Example: \"app/*\" -> \"src/app/*\" (real alias)\n // Example: \"src/*\" -> \"src/*\" (self-referencing alias, still valid)\n return true;\n });\n }\n\n /**\n * Get the alias key that matches the given import path\n */\n public getMatchingAlias(path: string): string | null {\n const aliasKey = Object.keys(this.aliases).find((alias) => {\n const aliasPattern = alias.replace(\"/*\", \"\");\n return path.startsWith(aliasPattern);\n });\n\n return aliasKey || null;\n }\n\n /**\n * Resolve an alias import path to a relative path based on tsconfig paths\n * Example: \"app/users/services/get-users.service\" -> \"src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias (e.g., \"app/users/services/get-users.service\")\n * @returns The resolved relative path or null if alias not found\n */\n public resolveAliasPath(checkingPath: string): string | null {\n // Find matching alias from tsconfig paths\n const aliasKey = this.getMatchingAlias(checkingPath);\n\n if (!aliasKey) return null;\n\n const aliasTargets = this.aliases[aliasKey];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return null;\n }\n\n // Get the first target path (usually there's only one)\n const targetPattern = aliasTargets[0];\n\n // Replace alias pattern with target pattern\n const aliasPattern = aliasKey.replace(\"/*\", \"\");\n const targetBase = targetPattern.replace(\"/*\", \"\");\n // Remove any leading slash so path.join does not drop the base\n const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\\\]/, \"\");\n\n // Join the target base with the relative part\n const resolvedPath = path.join(targetBase, relativePart);\n\n return Path.normalize(resolvedPath);\n }\n\n /**\n * Resolve an alias import path to an absolute path\n * Example: \"app/users/services/get-users.service\" -> \"/absolute/path/to/src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias\n * @returns The resolved absolute path or null if alias not found\n */\n public resolveAliasToAbsolute(path: string): string | null {\n const relativePath = this.resolveAliasPath(path);\n\n if (!relativePath) return null;\n\n return Path.normalize(Path.toAbsolute(relativePath));\n }\n}\n\nexport const tsconfigManager = new TSConfigManager();\n"],"mappings":";;;;;AAIA,IAAa,kBAAb,MAA6B;;iBAIgB,CAAC;iBAKnB;;CAOzB,AAAO,OAAO;EACZ,IAAI,KAAK,UAAU;EAGnB,MAAM,SAAS,GAAG,eAAe,KAAK,WAAW,eAAe,GAAG,GAAG,IAAI,QAAQ;EAElF,KAAK,WAAW,OAAO;EAEvB,KAAK,UAAU,OAAO,QAAQ,iBAAiB,SAAS,CAAC;EAEzD,KAAK,UAAU,OAAO,QAAQ,iBAAiB,WAAW;CAC5D;;;;;;;;CASA,AAAO,QAAQ,MAAc;EAC3B,IAAI,CAAC,KAAK,UACR,KAAK,KAAK;EAGZ,OAAO,OAAO,KAAK,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class TSConfigManager {\n /**\n * Aliases list (from tsconfig paths)\n */\n public aliases: Record<string, string[]> = {};\n\n /**\n * Base URL for resolving paths\n */\n public baseUrl: string = \".\";\n\n /**\n * TSConfig\n */\n public tsconfig: any;\n\n public init() {\n if (this.tsconfig) return;\n\n // use typescript to load the tsconfig.json file\n const output = ts.readConfigFile(Path.toAbsolute(\"tsconfig.json\"), ts.sys.readFile);\n\n this.tsconfig = output.config!;\n\n this.aliases = output.config?.compilerOptions?.paths || {};\n\n this.baseUrl = output.config?.compilerOptions?.baseUrl || \".\";\n }\n\n /**\n * Check if the given path is an alias\n * This checks if it's a REAL path alias (not an external package alias)\n *\n * Real aliases map to local paths (e.g., app/* -> src/app/*, src/* -> src/*)\n * External package aliases map to themselves with @ prefix (e.g., @warlock.js/core -> @warlock.js/core)\n */\n public isAlias(path: string) {\n if (!this.tsconfig) {\n this.init();\n }\n\n return Object.keys(this.aliases).some((alias) => {\n // Remove /* from alias pattern for matching\n const aliasPattern = alias.replace(\"/*\", \"\");\n\n if (!path.startsWith(aliasPattern)) {\n return false;\n }\n\n // Check if this is a real alias or just an external package mapping\n const aliasTargets = this.aliases[alias];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return false;\n }\n\n // If the alias starts with @, it's likely an external package alias\n // Example: \"@warlock.js/core\" -> \"@warlock.js/core\" (external package)\n if (aliasPattern.startsWith(\"@\")) {\n return false;\n }\n\n // Otherwise, it's a real path alias (including self-referencing ones like src/* -> src/*)\n // Example: \"app/*\" -> \"src/app/*\" (real alias)\n // Example: \"src/*\" -> \"src/*\" (self-referencing alias, still valid)\n return true;\n });\n }\n\n /**\n * Get the alias key that matches the given import path\n */\n public getMatchingAlias(path: string): string | null {\n const aliasKey = Object.keys(this.aliases).find((alias) => {\n const aliasPattern = alias.replace(\"/*\", \"\");\n return path.startsWith(aliasPattern);\n });\n\n return aliasKey || null;\n }\n\n /**\n * Resolve an alias import path to a relative path based on tsconfig paths\n * Example: \"app/users/services/get-users.service\" -> \"src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias (e.g., \"app/users/services/get-users.service\")\n * @returns The resolved relative path or null if alias not found\n */\n public resolveAliasPath(checkingPath: string): string | null {\n // Find matching alias from tsconfig paths\n const aliasKey = this.getMatchingAlias(checkingPath);\n\n if (!aliasKey) return null;\n\n const aliasTargets = this.aliases[aliasKey];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return null;\n }\n\n // Get the first target path (usually there's only one)\n const targetPattern = aliasTargets[0];\n\n // Replace alias pattern with target pattern\n const aliasPattern = aliasKey.replace(\"/*\", \"\");\n const targetBase = targetPattern.replace(\"/*\", \"\");\n // Remove any leading slash so path.join does not drop the base\n const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\\\]/, \"\");\n\n // Join the target base with the relative part\n const resolvedPath = path.join(targetBase, relativePart);\n\n return Path.normalize(resolvedPath);\n }\n\n /**\n * Resolve an alias import path to an absolute path\n * Example: \"app/users/services/get-users.service\" -> \"/absolute/path/to/src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias\n * @returns The resolved absolute path or null if alias not found\n */\n public resolveAliasToAbsolute(path: string): string | null {\n const relativePath = this.resolveAliasPath(path);\n\n if (!relativePath) return null;\n\n return Path.normalize(Path.toAbsolute(relativePath));\n }\n}\n\nexport const tsconfigManager = new TSConfigManager();\n"],"mappings":";;;;;AAIA,IAAa,kBAAb,MAA6B;;iBAIgB,CAAC;iBAKnB;;CAOzB,AAAO,OAAO;EACZ,IAAI,KAAK,UAAU;EAGnB,MAAM,SAAS,GAAG,eAAe,KAAK,WAAW,eAAe,GAAG,GAAG,IAAI,QAAQ;EAElF,KAAK,WAAW,OAAO;EAEvB,KAAK,UAAU,OAAO,QAAQ,iBAAiB,SAAS,CAAC;EAEzD,KAAK,UAAU,OAAO,QAAQ,iBAAiB,WAAW;CAC5D;;;;;;;;CASA,AAAO,QAAQ,MAAc;EAC3B,IAAI,CAAC,KAAK,UACR,KAAK,KAAK;EAGZ,OAAO,OAAO,KAAK,KAAK,OAAO,EAAE,MAAM,UAAU;GAE/C,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAE3C,IAAI,CAAC,KAAK,WAAW,YAAY,GAC/B,OAAO;GAIT,MAAM,eAAe,KAAK,QAAQ;GAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;GAKT,IAAI,aAAa,WAAW,GAAG,GAC7B,OAAO;GAMT,OAAO;EACT,CAAC;CACH;;;;CAKA,AAAO,iBAAiB,MAA6B;EAMnD,OALiB,OAAO,KAAK,KAAK,OAAO,EAAE,MAAM,UAAU;GACzD,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAC3C,OAAO,KAAK,WAAW,YAAY;EACrC,CAEc,KAAK;CACrB;;;;;;;;CASA,AAAO,iBAAiB,cAAqC;EAE3D,MAAM,WAAW,KAAK,iBAAiB,YAAY;EAEnD,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,eAAe,KAAK,QAAQ;EAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;EAIT,MAAM,gBAAgB,aAAa;EAGnC,MAAM,eAAe,SAAS,QAAQ,MAAM,EAAE;EAC9C,MAAM,aAAa,cAAc,QAAQ,MAAM,EAAE;EAEjD,MAAM,eAAe,aAAa,UAAU,aAAa,MAAM,EAAE,QAAQ,UAAU,EAAE;EAGrF,MAAM,eAAe,KAAK,KAAK,YAAY,YAAY;EAEvD,OAAO,KAAK,UAAU,YAAY;CACpC;;;;;;;;CASA,AAAO,uBAAuB,MAA6B;EACzD,MAAM,eAAe,KAAK,iBAAiB,IAAI;EAE/C,IAAI,CAAC,cAAc,OAAO;EAE1B,OAAO,KAAK,UAAU,KAAK,WAAW,YAAY,CAAC;CACrD;AACF;AAEA,MAAa,kBAAkB,IAAI,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n\n const [manifestExists, storageExists, configExists] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"âš ï¸ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return changedPath.includes(\"src/config/\") || changedPath.includes(\"config/\");\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate config types: ${error}`);\n }\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`âš ï¸ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`âš ï¸ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) =>\n Path.normalize(file).includes(\"src/config/\"),\n );\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n\n const [manifestExists, storageExists, configExists] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"âš ï¸ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return changedPath.includes(\"src/config/\") || changedPath.includes(\"config/\");\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate config types: ${error}`);\n }\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`âš ï¸ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`âš ï¸ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) =>\n Path.normalize(file).includes(\"src/config/\"),\n );\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/utils.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport glob from \"fast-glob\";\nimport { srcPath, warlockPath } from \"../utils\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Ensure the .warlock/ directory exists. Used by the loader-hook\n * registration which writes its bundled hook file into this directory.\n */\nexport async function ensureWarlockDirectory() {\n await ensureDirectoryAsync(warlockPath());\n}\n\n/**\n * Glob the project's `src/` directory for `.ts`/`.tsx` files. Returns\n * normalised absolute paths.\n */\nexport async function getFilesFromDirectory(directoryPath = srcPath(), pattern = \"**/*.{ts,tsx}\") {\n const files = await glob(`${Path.normalize(directoryPath)}/${pattern}`, { absolute: true });\n return files.map(file => Path.normalize(file));\n}\n\nexport async function getCertainFilesFromDirectory(directoryPath: string, filesNames: string[]) {\n const pattern = filesNames.length === 1 ? filesNames[0] : `(${filesNames.join(\"|\")})`;\n return getFilesFromDirectory(directoryPath, pattern + \".{ts,tsx}\");\n}\n\nexport function areSetsEqual<T>(set1: Set<T>, set2: Set<T>): boolean {\n if (set1.size !== set2.size) return false;\n for (const item of set1) {\n if (!set2.has(item)) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;;;;;;AASA,eAAsB,yBAAyB;CAC7C,MAAM,qBAAqB,YAAY,CAAC;AAC1C;;;;;AAMA,eAAsB,sBAAsB,gBAAgB,QAAQ,GAAG,UAAU,iBAAiB;CAEhG,QAAO,MADa,KAAK,GAAG,KAAK,UAAU,aAAa,EAAE,GAAG,WAAW,EAAE,UAAU,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/utils.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport glob from \"fast-glob\";\nimport { srcPath, warlockPath } from \"../utils\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Ensure the .warlock/ directory exists. Used by the loader-hook\n * registration which writes its bundled hook file into this directory.\n */\nexport async function ensureWarlockDirectory() {\n await ensureDirectoryAsync(warlockPath());\n}\n\n/**\n * Glob the project's `src/` directory for `.ts`/`.tsx` files. Returns\n * normalised absolute paths.\n */\nexport async function getFilesFromDirectory(directoryPath = srcPath(), pattern = \"**/*.{ts,tsx}\") {\n const files = await glob(`${Path.normalize(directoryPath)}/${pattern}`, { absolute: true });\n return files.map(file => Path.normalize(file));\n}\n\nexport async function getCertainFilesFromDirectory(directoryPath: string, filesNames: string[]) {\n const pattern = filesNames.length === 1 ? filesNames[0] : `(${filesNames.join(\"|\")})`;\n return getFilesFromDirectory(directoryPath, pattern + \".{ts,tsx}\");\n}\n\nexport function areSetsEqual<T>(set1: Set<T>, set2: Set<T>): boolean {\n if (set1.size !== set2.size) return false;\n for (const item of set1) {\n if (!set2.has(item)) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;;;;;;AASA,eAAsB,yBAAyB;CAC7C,MAAM,qBAAqB,YAAY,CAAC;AAC1C;;;;;AAMA,eAAsB,sBAAsB,gBAAgB,QAAQ,GAAG,UAAU,iBAAiB;CAEhG,QAAO,MADa,KAAK,GAAG,KAAK,UAAU,aAAa,EAAE,GAAG,WAAW,EAAE,UAAU,KAAK,CAAC,GAC7E,KAAI,SAAQ,KAAK,UAAU,IAAI,CAAC;AAC/C;AAEA,eAAsB,6BAA6B,eAAuB,YAAsB;CAE9F,OAAO,sBAAsB,gBADb,WAAW,WAAW,IAAI,WAAW,KAAK,IAAI,WAAW,KAAK,GAAG,EAAE,MAC7B,WAAW;AACnE;AAEA,SAAgB,aAAgB,MAAc,MAAuB;CACnE,IAAI,KAAK,SAAS,KAAK,MAAM,OAAO;CACpC,KAAK,MAAM,QAAQ,MACjB,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,OAAO;CAE9B,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.mjs","names":[],"sources":["../../../../../../../core/src/encryption/encrypt.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { config } from \"../config\";\nimport { EncryptionConfigurations } from \"./types\";\n\n/**\n * Default encryption algorithm.\n */\nconst DEFAULT_ALGORITHM = \"aes-256-gcm\";\n\n/**\n * Get the encryption key from config, validated as a 32-byte hex string.\n */\nfunction getEncryptionKey(): Buffer {\n const key = config.key<EncryptionConfigurations[\"key\"]>(\"encryption.key\");\n\n if (!key) {\n throw new Error(\n \"Missing encryption key. Set 'encryption.key' in your config (64 hex characters = 32 bytes).\",\n );\n }\n\n const buffer = Buffer.from(key, \"hex\");\n\n if (buffer.length !== 32) {\n throw new Error(\n `Encryption key must be exactly 32 bytes (64 hex characters). Got ${buffer.length} bytes for key ${key}.`,\n );\n }\n\n return buffer;\n}\n\n/**\n * Get the configured encryption algorithm, defaults to aes-256-gcm.\n */\nfunction getAlgorithm(): string {\n return config.key<EncryptionConfigurations[\"algorithm\"]>(\n \"encryption.algorithm\",\n DEFAULT_ALGORITHM,\n )!;\n}\n\n/**\n * Encrypts a plaintext string using AES-256-GCM (or the configured algorithm).\n * Returns a combined string in the format `iv:ciphertext:authTag`.\n *\n * @example\n * import { encrypt } from \"@warlock.js/core\";\n *\n * const encrypted = encrypt(\"sk-proj-12345\");\n * // => \"a1b2c3...:d4e5f6...:g7h8i9...\"\n */\nexport function encrypt(plainText: string): string {\n if (!plainText) return plainText;\n\n const keyBuffer = getEncryptionKey();\n const algorithm = getAlgorithm();\n\n try {\n // IV must be unique per encryption. 16 bytes is standard for AES.\n const iv = crypto.randomBytes(16);\n const cipher = crypto.createCipheriv(algorithm, keyBuffer, iv);\n\n let encrypted = cipher.update(plainText, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n\n // Auth tag validates ciphertext integrity (tamper detection)\n const authTag = (cipher as crypto.CipherGCM).getAuthTag().toString(\"hex\");\n\n return `${iv.toString(\"hex\")}:${encrypted}:${authTag}`;\n } catch (error) {\n throw new Error(`Encryption failed: ${(error as Error).message}`);\n }\n}\n\n/**\n * Decrypts a string previously encrypted with {@link encrypt}.\n * Expects the format `iv:ciphertext:authTag`.\n *\n * @example\n * import { decrypt } from \"@warlock.js/core\";\n *\n * const original = decrypt(encryptedString);\n * // => \"sk-proj-12345\"\n */\nexport function decrypt(cipherText: string): string {\n if (!cipherText) return cipherText;\n\n const keyBuffer = getEncryptionKey();\n const algorithm = getAlgorithm();\n\n try {\n const parts = cipherText.split(\":\");\n\n if (parts.length !== 3) {\n throw new Error(\"Invalid encrypted format. Expected iv:ciphertext:authTag\");\n }\n\n const [ivHex, encryptedHex, authTagHex] = parts;\n\n const decipher = crypto.createDecipheriv(algorithm, keyBuffer, Buffer.from(ivHex, \"hex\"));\n\n (decipher as crypto.DecipherGCM).setAuthTag(Buffer.from(authTagHex, \"hex\"));\n\n let decrypted = decipher.update(encryptedHex, \"hex\", \"utf8\");\n // .final() throws if auth tag is invalid (data tampered)\n decrypted += decipher.final(\"utf8\");\n\n return decrypted;\n } catch (error) {\n throw new Error(`Decryption failed: ${(error as Error).message}`);\n }\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,oBAAoB;;;;AAK1B,SAAS,mBAA2B;CAClC,MAAM,MAAM,OAAO,IAAqC,gBAAgB;CAExE,IAAI,CAAC,KACH,MAAM,IAAI,MACR,6FACF;CAGF,MAAM,SAAS,OAAO,KAAK,KAAK,KAAK;CAErC,IAAI,OAAO,WAAW,IACpB,MAAM,IAAI,MACR,oEAAoE,OAAO,OAAO,iBAAiB,IAAI,EACzG;CAGF,OAAO;AACT;;;;AAKA,SAAS,eAAuB;CAC9B,OAAO,OAAO,IACZ,wBACA,iBACF;AACF;;;;;;;;;;;AAYA,SAAgB,QAAQ,WAA2B;CACjD,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,YAAY,iBAAiB;CACnC,MAAM,YAAY,aAAa;CAE/B,IAAI;EAEF,MAAM,KAAK,OAAO,YAAY,EAAE;EAChC,MAAM,SAAS,OAAO,eAAe,WAAW,WAAW,EAAE;EAE7D,IAAI,YAAY,OAAO,OAAO,WAAW,QAAQ,KAAK;EACtD,aAAa,OAAO,MAAM,KAAK;EAG/B,MAAM,UAAW,OAA4B,WAAW,
|
|
1
|
+
{"version":3,"file":"encrypt.mjs","names":[],"sources":["../../../../../../../core/src/encryption/encrypt.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { config } from \"../config\";\nimport { type EncryptionConfigurations } from \"./types\";\n\n/**\n * Default encryption algorithm.\n */\nconst DEFAULT_ALGORITHM = \"aes-256-gcm\";\n\n/**\n * Get the encryption key from config, validated as a 32-byte hex string.\n */\nfunction getEncryptionKey(): Buffer {\n const key = config.key<EncryptionConfigurations[\"key\"]>(\"encryption.key\");\n\n if (!key) {\n throw new Error(\n \"Missing encryption key. Set 'encryption.key' in your config (64 hex characters = 32 bytes).\",\n );\n }\n\n const buffer = Buffer.from(key, \"hex\");\n\n if (buffer.length !== 32) {\n throw new Error(\n `Encryption key must be exactly 32 bytes (64 hex characters). Got ${buffer.length} bytes for key ${key}.`,\n );\n }\n\n return buffer;\n}\n\n/**\n * Get the configured encryption algorithm, defaults to aes-256-gcm.\n */\nfunction getAlgorithm(): string {\n return config.key<EncryptionConfigurations[\"algorithm\"]>(\n \"encryption.algorithm\",\n DEFAULT_ALGORITHM,\n )!;\n}\n\n/**\n * Encrypts a plaintext string using AES-256-GCM (or the configured algorithm).\n * Returns a combined string in the format `iv:ciphertext:authTag`.\n *\n * @example\n * import { encrypt } from \"@warlock.js/core\";\n *\n * const encrypted = encrypt(\"sk-proj-12345\");\n * // => \"a1b2c3...:d4e5f6...:g7h8i9...\"\n */\nexport function encrypt(plainText: string): string {\n if (!plainText) return plainText;\n\n const keyBuffer = getEncryptionKey();\n const algorithm = getAlgorithm();\n\n try {\n // IV must be unique per encryption. 16 bytes is standard for AES.\n const iv = crypto.randomBytes(16);\n const cipher = crypto.createCipheriv(algorithm, keyBuffer, iv);\n\n let encrypted = cipher.update(plainText, \"utf8\", \"hex\");\n encrypted += cipher.final(\"hex\");\n\n // Auth tag validates ciphertext integrity (tamper detection)\n const authTag = (cipher as crypto.CipherGCM).getAuthTag().toString(\"hex\");\n\n return `${iv.toString(\"hex\")}:${encrypted}:${authTag}`;\n } catch (error) {\n throw new Error(`Encryption failed: ${(error as Error).message}`);\n }\n}\n\n/**\n * Decrypts a string previously encrypted with {@link encrypt}.\n * Expects the format `iv:ciphertext:authTag`.\n *\n * @example\n * import { decrypt } from \"@warlock.js/core\";\n *\n * const original = decrypt(encryptedString);\n * // => \"sk-proj-12345\"\n */\nexport function decrypt(cipherText: string): string {\n if (!cipherText) return cipherText;\n\n const keyBuffer = getEncryptionKey();\n const algorithm = getAlgorithm();\n\n try {\n const parts = cipherText.split(\":\");\n\n if (parts.length !== 3) {\n throw new Error(\"Invalid encrypted format. Expected iv:ciphertext:authTag\");\n }\n\n const [ivHex, encryptedHex, authTagHex] = parts;\n\n const decipher = crypto.createDecipheriv(algorithm, keyBuffer, Buffer.from(ivHex, \"hex\"));\n\n (decipher as crypto.DecipherGCM).setAuthTag(Buffer.from(authTagHex, \"hex\"));\n\n let decrypted = decipher.update(encryptedHex, \"hex\", \"utf8\");\n // .final() throws if auth tag is invalid (data tampered)\n decrypted += decipher.final(\"utf8\");\n\n return decrypted;\n } catch (error) {\n throw new Error(`Decryption failed: ${(error as Error).message}`);\n }\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,oBAAoB;;;;AAK1B,SAAS,mBAA2B;CAClC,MAAM,MAAM,OAAO,IAAqC,gBAAgB;CAExE,IAAI,CAAC,KACH,MAAM,IAAI,MACR,6FACF;CAGF,MAAM,SAAS,OAAO,KAAK,KAAK,KAAK;CAErC,IAAI,OAAO,WAAW,IACpB,MAAM,IAAI,MACR,oEAAoE,OAAO,OAAO,iBAAiB,IAAI,EACzG;CAGF,OAAO;AACT;;;;AAKA,SAAS,eAAuB;CAC9B,OAAO,OAAO,IACZ,wBACA,iBACF;AACF;;;;;;;;;;;AAYA,SAAgB,QAAQ,WAA2B;CACjD,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,YAAY,iBAAiB;CACnC,MAAM,YAAY,aAAa;CAE/B,IAAI;EAEF,MAAM,KAAK,OAAO,YAAY,EAAE;EAChC,MAAM,SAAS,OAAO,eAAe,WAAW,WAAW,EAAE;EAE7D,IAAI,YAAY,OAAO,OAAO,WAAW,QAAQ,KAAK;EACtD,aAAa,OAAO,MAAM,KAAK;EAG/B,MAAM,UAAW,OAA4B,WAAW,EAAE,SAAS,KAAK;EAExE,OAAO,GAAG,GAAG,SAAS,KAAK,EAAE,GAAG,UAAU,GAAG;CAC/C,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,sBAAuB,MAAgB,SAAS;CAClE;AACF;;;;;;;;;;;AAYA,SAAgB,QAAQ,YAA4B;CAClD,IAAI,CAAC,YAAY,OAAO;CAExB,MAAM,YAAY,iBAAiB;CACnC,MAAM,YAAY,aAAa;CAE/B,IAAI;EACF,MAAM,QAAQ,WAAW,MAAM,GAAG;EAElC,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,MAAM,0DAA0D;EAG5E,MAAM,CAAC,OAAO,cAAc,cAAc;EAE1C,MAAM,WAAW,OAAO,iBAAiB,WAAW,WAAW,OAAO,KAAK,OAAO,KAAK,CAAC;EAExF,AAAC,SAAgC,WAAW,OAAO,KAAK,YAAY,KAAK,CAAC;EAE1E,IAAI,YAAY,SAAS,OAAO,cAAc,OAAO,MAAM;EAE3D,aAAa,SAAS,MAAM,MAAM;EAElC,OAAO;CACT,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,sBAAuB,MAAgB,SAAS;CAClE;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.mjs","names":[],"sources":["../../../../../../../core/src/encryption/hash.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { config } from \"../config\";\nimport { EncryptionConfigurations } from \"./types\";\n\n/**\n * Creates a deterministic HMAC-SHA256 hash of the given string.\n *\n * Useful for creating searchable, unique fingerprints of sensitive data\n * (e.g., an API key) without storing the plaintext.\n *\n * Uses a dedicated HMAC key from config. Falls back to the encryption key\n * if no separate HMAC key is configured.\n *\n * @example\n * import { hmacHash } from \"@warlock.js/core\";\n *\n * const fingerprint = hmacHash(\"sk-proj-12345\");\n * // Store `fingerprint` in DB for lookups, store encrypted value separately\n */\nexport function hmacHash(plainText: string): string {\n if (!plainText) return plainText;\n\n const hmacKey =\n config.key<EncryptionConfigurations[\"hmacKey\"]>(\"encryption.hmacKey\") ||\n config.key<EncryptionConfigurations[\"key\"]>(\"encryption.key\");\n\n if (!hmacKey) {\n throw new Error(\n \"Missing HMAC key. Set 'encryption.hmacKey' (or 'encryption.key') in your config.\",\n );\n }\n\n const keyBuffer = Buffer.from(hmacKey, \"hex\");\n\n return crypto.createHmac(\"sha256\", keyBuffer).update(plainText).digest(\"hex\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,SAAS,WAA2B;CAClD,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,UACJ,OAAO,IAAyC,oBAAoB,KACpE,OAAO,IAAqC,gBAAgB;CAE9D,IAAI,CAAC,SACH,MAAM,IAAI,MACR,kFACF;CAGF,MAAM,YAAY,OAAO,KAAK,SAAS,KAAK;CAE5C,OAAO,OAAO,WAAW,UAAU,SAAS,
|
|
1
|
+
{"version":3,"file":"hash.mjs","names":[],"sources":["../../../../../../../core/src/encryption/hash.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { config } from \"../config\";\nimport { type EncryptionConfigurations } from \"./types\";\n\n/**\n * Creates a deterministic HMAC-SHA256 hash of the given string.\n *\n * Useful for creating searchable, unique fingerprints of sensitive data\n * (e.g., an API key) without storing the plaintext.\n *\n * Uses a dedicated HMAC key from config. Falls back to the encryption key\n * if no separate HMAC key is configured.\n *\n * @example\n * import { hmacHash } from \"@warlock.js/core\";\n *\n * const fingerprint = hmacHash(\"sk-proj-12345\");\n * // Store `fingerprint` in DB for lookups, store encrypted value separately\n */\nexport function hmacHash(plainText: string): string {\n if (!plainText) return plainText;\n\n const hmacKey =\n config.key<EncryptionConfigurations[\"hmacKey\"]>(\"encryption.hmacKey\") ||\n config.key<EncryptionConfigurations[\"key\"]>(\"encryption.key\");\n\n if (!hmacKey) {\n throw new Error(\n \"Missing HMAC key. Set 'encryption.hmacKey' (or 'encryption.key') in your config.\",\n );\n }\n\n const keyBuffer = Buffer.from(hmacKey, \"hex\");\n\n return crypto.createHmac(\"sha256\", keyBuffer).update(plainText).digest(\"hex\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,SAAS,WAA2B;CAClD,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,UACJ,OAAO,IAAyC,oBAAoB,KACpE,OAAO,IAAqC,gBAAgB;CAE9D,IAAI,CAAC,SACH,MAAM,IAAI,MACR,kFACF;CAGF,MAAM,YAAY,OAAO,KAAK,SAAS,KAAK;CAE5C,OAAO,OAAO,WAAW,UAAU,SAAS,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK;AAC9E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password.mjs","names":[],"sources":["../../../../../../../core/src/encryption/password.ts"],"sourcesContent":["import { config } from \"../config\";\r\nimport { EncryptionPasswordConfigurations } from \"./types\";\r\n\r\n/**\r\n * Default bcrypt salt rounds.\r\n */\r\nconst DEFAULT_SALT_ROUNDS = 12;\r\n\r\n// ============================================================\r\n// Lazy-loaded bcryptjs SDK Types\r\n// ============================================================\r\n\r\n/**\r\n * Cached bcryptjs module (loaded once, reused)\r\n */\r\nlet Bcryptjs: typeof import(\"bcryptjs\");\r\n\r\nlet isModuleExists: boolean | null = null;\r\n\r\n/**\r\n * Installation instructions for bcryptjs package\r\n */\r\nconst BCRYPTJS_INSTALL_INSTRUCTIONS = `\r\nPassword encryption requires the bcryptjs package.\r\nInstall it with:\r\n\r\n yarn add bcryptjs\r\n\r\nOr with your preferred package manager:\r\n\r\n npm install bcryptjs\r\n pnpm add bcryptjs\r\n yarn add bcryptjs\r\n`.trim();\r\n\r\n/**\r\n * Load bcryptjs module\r\n */\r\nasync function loadBcryptjs() {\r\n try {\r\n Bcryptjs = await import(\"bcryptjs\");\r\n isModuleExists = true;\r\n } catch {\r\n isModuleExists = false;\r\n }\r\n}\r\n\r\nloadBcryptjs();\r\n\r\n/**\r\n * Hashes a password using bcrypt (async — does not block the event loop).\r\n *\r\n * Salt rounds are read from \\`encryption.password.saltRounds\\` config,\r\n * defaulting to 12.\r\n *\r\n * @example\r\n * import { hashPassword } from \"@warlock.js/core\";\r\n *\r\n * const hashed = await hashPassword(\"user-password-123\");\r\n * // Store \\`hashed\\` in the database\r\n */\r\nexport async function hashPassword(password: string): Promise<string> {\r\n if (!isModuleExists) {\r\n throw new Error(BCRYPTJS_INSTALL_INSTRUCTIONS);\r\n }\r\n\r\n const saltRounds = config.key<EncryptionPasswordConfigurations[\"salt\"]>(\r\n \"encryption.password.salt\",\r\n DEFAULT_SALT_ROUNDS,\r\n )!;\r\n\r\n return Bcryptjs.hash(String(password), saltRounds);\r\n}\r\n\r\n/**\r\n * Verifies a plain password against a bcrypt hash (async — does not block the event loop).\r\n *\r\n * @example\r\n * import { verifyPassword } from \"@warlock.js/core\";\r\n *\r\n * const isValid = await verifyPassword(\"user-password-123\", storedHash);\r\n */\r\nexport async function verifyPassword(\r\n plainPassword: string,\r\n hashedPassword: string,\r\n): Promise<boolean> {\r\n if (!isModuleExists) {\r\n throw new Error(BCRYPTJS_INSTALL_INSTRUCTIONS);\r\n }\r\n\r\n return Bcryptjs.compare(String(plainPassword), String(hashedPassword));\r\n}\r\n"],"mappings":";;;;;;;AAMA,MAAM,sBAAsB;;;;AAS5B,IAAI;AAEJ,IAAI,iBAAiC;;;;AAKrC,MAAM,gCAAgC;;;;;;;;;;;EAWpC,KAAK;;;;AAKP,eAAe,eAAe;CAC5B,IAAI;EACF,WAAW,MAAM,OAAO;EACxB,iBAAiB;CACnB,QAAQ;EACN,iBAAiB;CACnB;AACF;AAEA,aAAa;;;;;;;;;;;;;AAcb,eAAsB,aAAa,UAAmC;CACpE,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,aAAa,OAAO,IACxB,4BACA,mBACF;CAEA,OAAO,SAAS,KAAK,OAAO,QAAQ,GAAG,UAAU;AACnD;;;;;;;;;AAUA,eAAsB,eACpB,eACA,gBACkB;CAClB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,6BAA6B;CAG/C,OAAO,SAAS,QAAQ,OAAO,aAAa,GAAG,OAAO,cAAc,CAAC;AACvE"}
|
|
1
|
+
{"version":3,"file":"password.mjs","names":[],"sources":["../../../../../../../core/src/encryption/password.ts"],"sourcesContent":["import { config } from \"../config\";\r\nimport { type EncryptionPasswordConfigurations } from \"./types\";\r\n\r\n/**\r\n * Default bcrypt salt rounds.\r\n */\r\nconst DEFAULT_SALT_ROUNDS = 12;\r\n\r\n// ============================================================\r\n// Lazy-loaded bcryptjs SDK Types\r\n// ============================================================\r\n\r\n/**\r\n * Cached bcryptjs module (loaded once, reused)\r\n */\r\nlet Bcryptjs: typeof import(\"bcryptjs\");\r\n\r\nlet isModuleExists: boolean | null = null;\r\n\r\n/**\r\n * Installation instructions for bcryptjs package\r\n */\r\nconst BCRYPTJS_INSTALL_INSTRUCTIONS = `\r\nPassword encryption requires the bcryptjs package.\r\nInstall it with:\r\n\r\n yarn add bcryptjs\r\n\r\nOr with your preferred package manager:\r\n\r\n npm install bcryptjs\r\n pnpm add bcryptjs\r\n yarn add bcryptjs\r\n`.trim();\r\n\r\n/**\r\n * Load bcryptjs module\r\n */\r\nasync function loadBcryptjs() {\r\n try {\r\n Bcryptjs = await import(\"bcryptjs\");\r\n isModuleExists = true;\r\n } catch {\r\n isModuleExists = false;\r\n }\r\n}\r\n\r\nloadBcryptjs();\r\n\r\n/**\r\n * Hashes a password using bcrypt (async — does not block the event loop).\r\n *\r\n * Salt rounds are read from \\`encryption.password.saltRounds\\` config,\r\n * defaulting to 12.\r\n *\r\n * @example\r\n * import { hashPassword } from \"@warlock.js/core\";\r\n *\r\n * const hashed = await hashPassword(\"user-password-123\");\r\n * // Store \\`hashed\\` in the database\r\n */\r\nexport async function hashPassword(password: string): Promise<string> {\r\n if (!isModuleExists) {\r\n throw new Error(BCRYPTJS_INSTALL_INSTRUCTIONS);\r\n }\r\n\r\n const saltRounds = config.key<EncryptionPasswordConfigurations[\"salt\"]>(\r\n \"encryption.password.salt\",\r\n DEFAULT_SALT_ROUNDS,\r\n )!;\r\n\r\n return Bcryptjs.hash(String(password), saltRounds);\r\n}\r\n\r\n/**\r\n * Verifies a plain password against a bcrypt hash (async — does not block the event loop).\r\n *\r\n * @example\r\n * import { verifyPassword } from \"@warlock.js/core\";\r\n *\r\n * const isValid = await verifyPassword(\"user-password-123\", storedHash);\r\n */\r\nexport async function verifyPassword(\r\n plainPassword: string,\r\n hashedPassword: string,\r\n): Promise<boolean> {\r\n if (!isModuleExists) {\r\n throw new Error(BCRYPTJS_INSTALL_INSTRUCTIONS);\r\n }\r\n\r\n return Bcryptjs.compare(String(plainPassword), String(hashedPassword));\r\n}\r\n"],"mappings":";;;;;;;AAMA,MAAM,sBAAsB;;;;AAS5B,IAAI;AAEJ,IAAI,iBAAiC;;;;AAKrC,MAAM,gCAAgC;;;;;;;;;;;EAWpC,KAAK;;;;AAKP,eAAe,eAAe;CAC5B,IAAI;EACF,WAAW,MAAM,OAAO;EACxB,iBAAiB;CACnB,QAAQ;EACN,iBAAiB;CACnB;AACF;AAEA,aAAa;;;;;;;;;;;;;AAcb,eAAsB,aAAa,UAAmC;CACpE,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,aAAa,OAAO,IACxB,4BACA,mBACF;CAEA,OAAO,SAAS,KAAK,OAAO,QAAQ,GAAG,UAAU;AACnD;;;;;;;;;AAUA,eAAsB,eACpB,eACA,gBACkB;CAClB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,6BAA6B;CAG/C,OAAO,SAAS,QAAQ,OAAO,aAAa,GAAG,OAAO,cAAc,CAAC;AACvE"}
|