@warlock.js/core 4.10.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -0
- package/esm/application/application.d.mts +19 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +25 -0
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/boot-signal.d.mts +77 -0
- package/esm/application/boot-signal.d.mts.map +1 -0
- package/esm/application/boot-signal.mjs +64 -0
- package/esm/application/boot-signal.mjs.map +1 -0
- package/esm/application/index.d.mts +1 -0
- package/esm/application/index.mjs +1 -0
- package/esm/bootstrap.mjs +2 -2
- package/esm/bootstrap.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs +31 -8
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs +57 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +4 -2
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +42 -21
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/migrate.command.mjs +6 -2
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +4 -25
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/parse-cli-args.mjs +86 -9
- package/esm/cli/parse-cli-args.mjs.map +1 -1
- package/esm/cli/types.d.mts +6 -1
- package/esm/cli/types.d.mts.map +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts +27 -0
- package/esm/config/config-setter.d.mts.map +1 -0
- package/esm/config/config-setter.mjs +10 -0
- package/esm/config/config-setter.mjs.map +1 -0
- package/esm/config/index.mjs +1 -0
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/ai-connector.mjs +2 -2
- package/esm/connectors/ai-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +8 -7
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts +11 -5
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/connectors/storage.connector.mjs +11 -5
- package/esm/connectors/storage.connector.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +53 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/pending-exit-code.mjs +39 -0
- package/esm/database/pending-exit-code.mjs.map +1 -0
- package/esm/database/resolve-pending-migrations.mjs +49 -0
- package/esm/database/resolve-pending-migrations.mjs.map +1 -0
- package/esm/dev-server/loader/load-hook.mjs +1 -1
- package/esm/generations/stubs.mjs +1 -1
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/index.d.mts +1 -0
- package/esm/http/index.mjs +1 -0
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/port-preflight.d.mts +34 -0
- package/esm/http/port-preflight.d.mts.map +1 -0
- package/esm/http/port-preflight.mjs +66 -0
- package/esm/http/port-preflight.mjs.map +1 -0
- package/esm/http/response.mjs +5 -5
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.mjs +4 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/image/image.d.mts +3 -2
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs +63 -17
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +6 -2
- package/esm/index.mjs +5 -1
- package/esm/mail/mailer-pool.d.mts.map +1 -1
- package/esm/mail/mailer-pool.mjs.map +1 -1
- package/esm/production/assert-generated-imports.mjs +94 -0
- package/esm/production/assert-generated-imports.mjs.map +1 -0
- package/esm/production/production-builder.mjs +29 -4
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +108 -0
- package/esm/production/production-supervisor.mjs.map +1 -0
- package/esm/react/index.d.mts +4 -0
- package/esm/react/index.d.mts.map +1 -1
- package/esm/react/index.mjs +88 -14
- package/esm/react/index.mjs.map +1 -1
- package/esm/storage/storage.d.mts +16 -0
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +24 -1
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/tests/index.d.mts +2 -1
- package/esm/tests/index.mjs +1 -0
- package/esm/tests/start-http-development-server.d.mts +15 -2
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +43 -16
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +2 -1
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-server-port-channel.d.mts +27 -0
- package/esm/tests/test-server-port-channel.d.mts.map +1 -0
- package/esm/tests/test-server-port-channel.mjs +32 -0
- package/esm/tests/test-server-port-channel.mjs.map +1 -0
- package/esm/utils/load-environment.mjs +59 -0
- package/esm/utils/load-environment.mjs.map +1 -0
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +90 -3
- package/llms.txt +1 -1
- package/package.json +13 -13
- package/skills/process-image/SKILL.md +3 -1
- package/skills/run-app/SKILL.md +63 -0
- package/skills/test-http/SKILL.md +27 -3
- package/skills/write-cli-command/SKILL.md +19 -1
|
@@ -9,7 +9,7 @@ import { health } from "../http/health.mjs";
|
|
|
9
9
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
10
10
|
import { BaseConnector } from "./base-connector.mjs";
|
|
11
11
|
import { devLogError } from "../dev-server/dev-logger.mjs";
|
|
12
|
-
import
|
|
12
|
+
import config from "@mongez/config";
|
|
13
13
|
import { log } from "@warlock.js/logger";
|
|
14
14
|
import { colors } from "@mongez/copper";
|
|
15
15
|
|
|
@@ -45,14 +45,14 @@ var HttpConnector = class extends BaseConnector {
|
|
|
45
45
|
* `start()` so it reads the router after app code has registered.
|
|
46
46
|
*/
|
|
47
47
|
async boot() {
|
|
48
|
-
const httpConfig =
|
|
48
|
+
const httpConfig = config.get("http");
|
|
49
49
|
if (!httpConfig) return;
|
|
50
50
|
const port = httpConfig.port;
|
|
51
51
|
log.info(`http`, "connection", `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`);
|
|
52
52
|
this.http = startHttpServer(httpConfig.serverOptions);
|
|
53
53
|
container.set("http.server", this.http);
|
|
54
54
|
await registerHttpPlugins(this.http);
|
|
55
|
-
setBaseUrl(
|
|
55
|
+
setBaseUrl(config.get("app.baseUrl"));
|
|
56
56
|
this.registerHealthRoutes();
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
@@ -64,7 +64,7 @@ var HttpConnector = class extends BaseConnector {
|
|
|
64
64
|
*/
|
|
65
65
|
registerHealthRoutes() {
|
|
66
66
|
if (!this.http) return;
|
|
67
|
-
const healthConfig =
|
|
67
|
+
const healthConfig = config.get("http.health");
|
|
68
68
|
if (healthConfig?.enabled === false) return;
|
|
69
69
|
const livenessPath = healthConfig?.path ?? "/health";
|
|
70
70
|
const readinessPath = healthConfig?.readinessPath ?? "/ready";
|
|
@@ -83,7 +83,7 @@ var HttpConnector = class extends BaseConnector {
|
|
|
83
83
|
* app code without losing routes.
|
|
84
84
|
*/
|
|
85
85
|
async start() {
|
|
86
|
-
const httpConfig =
|
|
86
|
+
const httpConfig = config.get("http");
|
|
87
87
|
if (!httpConfig || !this.http) return;
|
|
88
88
|
if (Application.runtimeStrategy === "development") router.scanDevServer(this.http);
|
|
89
89
|
else router.scan(this.http);
|
|
@@ -94,7 +94,8 @@ var HttpConnector = class extends BaseConnector {
|
|
|
94
94
|
port: httpConfig.port,
|
|
95
95
|
host: httpConfig.host || "localhost"
|
|
96
96
|
});
|
|
97
|
-
|
|
97
|
+
Application.setServedPort(httpConfig.port);
|
|
98
|
+
const baseUrl = config.get("app.baseUrl");
|
|
98
99
|
log.success(`http`, "connection", `Server ready at ${baseUrl}`);
|
|
99
100
|
} catch (error) {
|
|
100
101
|
devLogError("Error while starting http server", error);
|
|
@@ -121,7 +122,7 @@ var HttpConnector = class extends BaseConnector {
|
|
|
121
122
|
if (!this.active) return;
|
|
122
123
|
const server = getHttpServer();
|
|
123
124
|
if (server) {
|
|
124
|
-
const timeout =
|
|
125
|
+
const timeout = config.get("http.gracefulShutdown.timeout", DEFAULT_SHUTDOWN_TIMEOUT);
|
|
125
126
|
if (!await closeServerWithTimeout(server, timeout)) log.warn("http", "shutdown", `In-flight requests did not drain within ${timeout}ms; closing anyway`);
|
|
126
127
|
}
|
|
127
128
|
this.active = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-connector.mjs","names":["config"],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { Application } from \"../application\";\r\nimport { devLogError } from \"../dev-server/dev-logger\";\r\nimport { health } from \"../http/health\";\r\nimport { registerHttpPlugins } from \"../http/plugins\";\r\nimport { closeServerWithTimeout, FastifyInstance, getHttpServer, startHttpServer } from \"../http/server\";\r\nimport { router } from \"../router/router\";\r\nimport { Environment } from \"../utils\";\r\nimport { setBaseUrl } from \"../utils/urls\";\r\nimport { container } from \"./../container\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\nfunction environmentColor(environment: Environment) {\r\n switch (environment) {\r\n case \"development\":\r\n return colors.magentaBright(environment);\r\n case \"test\":\r\n return colors.yellowBright(environment);\r\n case \"production\":\r\n return colors.greenBright(environment);\r\n default:\r\n return colors.white(environment);\r\n }\r\n}\r\n\r\n/**\r\n * Default time to wait for in-flight requests to drain on shutdown before the\r\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\n/**\r\n * HTTP Connector\r\n * Manages HTTP server (Fastify) lifecycle\r\n */\r\nexport class HttpConnector extends BaseConnector {\r\n public readonly name = \"http\";\r\n public readonly priority = ConnectorPriority.HTTP;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\r\n\r\n /**\r\n * Files that trigger HTTP server restart\r\n * Note: routes.ts changes will be handled by HMR with wildcard routing\r\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\r\n */\r\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\r\n\r\n /**\r\n * Fastify Server instance\r\n */\r\n protected http?: FastifyInstance;\r\n\r\n /**\r\n * Boot the connector — construction only (create Fastify, register\r\n * plugins, populate container). Route scanning is deferred to\r\n * `start()` so it reads the router after app code has registered.\r\n */\r\n public async boot() {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig) return;\r\n\r\n const port = httpConfig.port;\r\n log.info(\r\n `http`,\r\n \"connection\",\r\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\r\n );\r\n\r\n this.http = startHttpServer(httpConfig.serverOptions);\r\n\r\n container.set(\"http.server\", this.http);\r\n\r\n await registerHttpPlugins(this.http);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n // update base url\r\n setBaseUrl(baseUrl);\r\n\r\n // Liveness/readiness endpoints — registered on Fastify before start() scans\r\n // the app router, so they exist by the time the server listens.\r\n this.registerHealthRoutes();\r\n }\r\n\r\n /**\r\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\r\n * endpoints directly on the Fastify instance — infra routes, kept off the app\r\n * router so they're immune to HMR and never collide with route scanning. Opt\r\n * out via `http.health.enabled = false`; rename via `http.health.path` /\r\n * `http.health.readinessPath`.\r\n */\r\n private registerHealthRoutes(): void {\r\n if (!this.http) {\r\n return;\r\n }\r\n\r\n const healthConfig = config.get(\"http.health\");\r\n\r\n if (healthConfig?.enabled === false) {\r\n return;\r\n }\r\n\r\n const livenessPath = healthConfig?.path ?? \"/health\";\r\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\r\n\r\n this.http.get(livenessPath, async (_request: any, reply: any) => {\r\n const result = health.liveness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n\r\n this.http.get(readinessPath, async (_request: any, reply: any) => {\r\n const result = await health.readiness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n }\r\n\r\n /**\r\n * Initialize HTTP server — bind app-registered routes to Fastify\r\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\r\n * app code without losing routes.\r\n */\r\n public async start(): Promise<void> {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig || !this.http) return;\r\n\r\n if (Application.runtimeStrategy === \"development\") {\r\n router.scanDevServer(this.http);\r\n } else {\r\n router.scan(this.http);\r\n }\r\n\r\n // Surface the route table as a readiness signal + a boot log, so an empty\r\n // or partial route surface (e.g. a route module that failed to register) is\r\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\r\n // keyed by name, so re-running on restart is idempotent.\r\n health.addRoutesRegisteredCheck(() => router.routeCount());\r\n log.info(\"http\", \"routes\", `${router.routeCount()} route(s) registered`);\r\n\r\n try {\r\n // We can use the url of the server\r\n await this.http.listen({\r\n port: httpConfig.port,\r\n host: httpConfig.host || \"localhost\",\r\n });\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n log.success(`http`, \"connection\", `Server ready at ${baseUrl}`);\r\n } catch (error) {\r\n devLogError(\"Error while starting http server\", error);\r\n\r\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\r\n // devLogError above is the dev-server console UI and bypasses the logger\r\n // pipeline, so also route it through `log.fatal` to reach Sentry/file,\r\n // then `await log.flush()` to drain buffered/async channels before exit.\r\n await log.fatal(\"http\", \"connection\", error);\r\n await log.flush();\r\n\r\n process.exit(1); // stop the process, exit with error\r\n }\r\n\r\n this.active = true;\r\n }\r\n\r\n /**\r\n * Restart — needs a fresh Fastify instance since `start()` now\r\n * re-runs `router.scan()`, and re-scanning the same Fastify would\r\n * register duplicate route handlers.\r\n */\r\n public async restart(): Promise<void> {\r\n await this.shutdown();\r\n await this.boot();\r\n await this.start();\r\n }\r\n\r\n /**\r\n * Shutdown HTTP server\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n const server = getHttpServer();\r\n\r\n if (server) {\r\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n const drained = await closeServerWithTimeout(server, timeout);\r\n\r\n if (!drained) {\r\n log.warn(\r\n \"http\",\r\n \"shutdown\",\r\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\r\n );\r\n }\r\n }\r\n\r\n this.active = false;\r\n }\r\n\r\n /**\r\n * Override shouldRestart to handle routes.ts specially\r\n * routes.ts changes should NOT restart the server (use HMR instead)\r\n * Now receives config file paths directly from layer-executor\r\n */\r\n public shouldRestart(changedFiles: string[]): boolean {\r\n // Only restart for config changes, not routes\r\n return changedFiles.some((file) => {\r\n const relativePath = file.replace(/\\\\/g, \"/\");\r\n return this.watchedFiles.includes(relativePath);\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { Application } from \"../application\";\r\nimport { devLogError } from \"../dev-server/dev-logger\";\r\nimport { health } from \"../http/health\";\r\nimport { registerHttpPlugins } from \"../http/plugins\";\r\nimport { closeServerWithTimeout, FastifyInstance, getHttpServer, startHttpServer } from \"../http/server\";\r\nimport { router } from \"../router/router\";\r\nimport { Environment } from \"../utils\";\r\nimport { setBaseUrl } from \"../utils/urls\";\r\nimport { container } from \"./../container\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\nfunction environmentColor(environment: Environment) {\r\n switch (environment) {\r\n case \"development\":\r\n return colors.magentaBright(environment);\r\n case \"test\":\r\n return colors.yellowBright(environment);\r\n case \"production\":\r\n return colors.greenBright(environment);\r\n default:\r\n return colors.white(environment);\r\n }\r\n}\r\n\r\n/**\r\n * Default time to wait for in-flight requests to drain on shutdown before the\r\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\n/**\r\n * HTTP Connector\r\n * Manages HTTP server (Fastify) lifecycle\r\n */\r\nexport class HttpConnector extends BaseConnector {\r\n public readonly name = \"http\";\r\n public readonly priority = ConnectorPriority.HTTP;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\r\n\r\n /**\r\n * Files that trigger HTTP server restart\r\n * Note: routes.ts changes will be handled by HMR with wildcard routing\r\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\r\n */\r\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\r\n\r\n /**\r\n * Fastify Server instance\r\n */\r\n protected http?: FastifyInstance;\r\n\r\n /**\r\n * Boot the connector — construction only (create Fastify, register\r\n * plugins, populate container). Route scanning is deferred to\r\n * `start()` so it reads the router after app code has registered.\r\n */\r\n public async boot() {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig) return;\r\n\r\n const port = httpConfig.port;\r\n log.info(\r\n `http`,\r\n \"connection\",\r\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\r\n );\r\n\r\n this.http = startHttpServer(httpConfig.serverOptions);\r\n\r\n container.set(\"http.server\", this.http);\r\n\r\n await registerHttpPlugins(this.http);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n // update base url\r\n setBaseUrl(baseUrl);\r\n\r\n // Liveness/readiness endpoints — registered on Fastify before start() scans\r\n // the app router, so they exist by the time the server listens.\r\n this.registerHealthRoutes();\r\n }\r\n\r\n /**\r\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\r\n * endpoints directly on the Fastify instance — infra routes, kept off the app\r\n * router so they're immune to HMR and never collide with route scanning. Opt\r\n * out via `http.health.enabled = false`; rename via `http.health.path` /\r\n * `http.health.readinessPath`.\r\n */\r\n private registerHealthRoutes(): void {\r\n if (!this.http) {\r\n return;\r\n }\r\n\r\n const healthConfig = config.get(\"http.health\");\r\n\r\n if (healthConfig?.enabled === false) {\r\n return;\r\n }\r\n\r\n const livenessPath = healthConfig?.path ?? \"/health\";\r\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\r\n\r\n this.http.get(livenessPath, async (_request: any, reply: any) => {\r\n const result = health.liveness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n\r\n this.http.get(readinessPath, async (_request: any, reply: any) => {\r\n const result = await health.readiness();\r\n\r\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\r\n });\r\n }\r\n\r\n /**\r\n * Initialize HTTP server — bind app-registered routes to Fastify\r\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\r\n * app code without losing routes.\r\n */\r\n public async start(): Promise<void> {\r\n const httpConfig = config.get(\"http\");\r\n\r\n if (!httpConfig || !this.http) return;\r\n\r\n if (Application.runtimeStrategy === \"development\") {\r\n router.scanDevServer(this.http);\r\n } else {\r\n router.scan(this.http);\r\n }\r\n\r\n // Surface the route table as a readiness signal + a boot log, so an empty\r\n // or partial route surface (e.g. a route module that failed to register) is\r\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\r\n // keyed by name, so re-running on restart is idempotent.\r\n health.addRoutesRegisteredCheck(() => router.routeCount());\r\n log.info(\"http\", \"routes\", `${router.routeCount()} route(s) registered`);\r\n\r\n try {\r\n // We can use the url of the server\r\n await this.http.listen({\r\n port: httpConfig.port,\r\n host: httpConfig.host || \"localhost\",\r\n });\r\n\r\n Application.setServedPort(httpConfig.port);\r\n\r\n const baseUrl = config.get(\"app.baseUrl\");\r\n\r\n log.success(`http`, \"connection\", `Server ready at ${baseUrl}`);\r\n } catch (error) {\r\n devLogError(\"Error while starting http server\", error);\r\n\r\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\r\n // devLogError above is the dev-server console UI and bypasses the logger\r\n // pipeline, so also route it through `log.fatal` to reach Sentry/file,\r\n // then `await log.flush()` to drain buffered/async channels before exit.\r\n await log.fatal(\"http\", \"connection\", error);\r\n await log.flush();\r\n\r\n process.exit(1); // stop the process, exit with error\r\n }\r\n\r\n this.active = true;\r\n }\r\n\r\n /**\r\n * Restart — needs a fresh Fastify instance since `start()` now\r\n * re-runs `router.scan()`, and re-scanning the same Fastify would\r\n * register duplicate route handlers.\r\n */\r\n public async restart(): Promise<void> {\r\n await this.shutdown();\r\n await this.boot();\r\n await this.start();\r\n }\r\n\r\n /**\r\n * Shutdown HTTP server\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n const server = getHttpServer();\r\n\r\n if (server) {\r\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n const drained = await closeServerWithTimeout(server, timeout);\r\n\r\n if (!drained) {\r\n log.warn(\r\n \"http\",\r\n \"shutdown\",\r\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\r\n );\r\n }\r\n }\r\n\r\n this.active = false;\r\n }\r\n\r\n /**\r\n * Override shouldRestart to handle routes.ts specially\r\n * routes.ts changes should NOT restart the server (use HMR instead)\r\n * Now receives config file paths directly from layer-executor\r\n */\r\n public shouldRestart(changedFiles: string[]): boolean {\r\n // Only restart for config changes, not routes\r\n return changedFiles.some((file) => {\r\n const relativePath = file.replace(/\\\\/g, \"/\");\r\n return this.watchedFiles.includes(relativePath);\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
|
2
2
|
import { BaseConnector } from "./base-connector.mjs";
|
|
3
3
|
import { setLogConfigurations } from "../logger/logger.mjs";
|
|
4
4
|
import "../logger/index.mjs";
|
|
5
|
-
import
|
|
5
|
+
import config from "@mongez/config";
|
|
6
6
|
import { log } from "@warlock.js/logger";
|
|
7
7
|
|
|
8
8
|
//#region ../core/src/connectors/logger-connector.ts
|
|
@@ -22,7 +22,7 @@ var LoggerConnector = class extends BaseConnector {
|
|
|
22
22
|
* Initialize logger configurations
|
|
23
23
|
*/
|
|
24
24
|
async start() {
|
|
25
|
-
const logConfig =
|
|
25
|
+
const logConfig = config.get("log");
|
|
26
26
|
if (!logConfig) return;
|
|
27
27
|
try {
|
|
28
28
|
setLogConfigurations(logConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger-connector.mjs","names":[
|
|
1
|
+
{"version":3,"file":"logger-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/logger-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { setLogConfigurations } from \"../logger\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Logger Connector\r\n * Manages logger lifecycle and ensures synchronous flushing on termination\r\n */\r\nexport class LoggerConnector extends BaseConnector {\r\n public readonly name = \"logger\";\r\n public readonly priority = ConnectorPriority.LOGGER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger logger restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/log.ts\", \"src/config/log.tsx\"];\r\n\r\n /**\r\n * Initialize logger configurations\r\n */\r\n public async start(): Promise<void> {\r\n const logConfig = config.get(\"log\");\r\n\r\n if (!logConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setLogConfigurations(logConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize logger:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown logger and flush messages synchronously\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n log.flushSync();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to flush logger:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAUA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,qBAAqB,oBAAoB;;;;;CAK5E,MAAa,QAAuB;EAClC,MAAM,YAAY,OAAO,IAAI,KAAK;EAElC,IAAI,CAAC,WACH;EAGF,IAAI;GACF,qBAAqB,SAAS;GAC9B,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,IAAI,UAAU;GACd,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,KAAK;GAC9C,MAAM;EACR;CACF;AACF"}
|
|
@@ -3,7 +3,7 @@ import { BaseConnector } from "./base-connector.mjs";
|
|
|
3
3
|
import { setMailConfigurations } from "../mail/config.mjs";
|
|
4
4
|
import { closeAllMailers } from "../mail/mailer-pool.mjs";
|
|
5
5
|
import "../mail/index.mjs";
|
|
6
|
-
import
|
|
6
|
+
import config from "@mongez/config";
|
|
7
7
|
|
|
8
8
|
//#region ../core/src/connectors/mail-connector.ts
|
|
9
9
|
/**
|
|
@@ -26,7 +26,7 @@ var MailerConnector = class extends BaseConnector {
|
|
|
26
26
|
* Initialize mailer configurations
|
|
27
27
|
*/
|
|
28
28
|
async start() {
|
|
29
|
-
const mailConfig =
|
|
29
|
+
const mailConfig = config.get("mail");
|
|
30
30
|
if (!mailConfig) return;
|
|
31
31
|
try {
|
|
32
32
|
setMailConfigurations(mailConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail-connector.mjs","names":[
|
|
1
|
+
{"version":3,"file":"mail-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/mail-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { closeAllMailers, setMailConfigurations } from \"../mail\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Mailer Connector\r\n * Manages mailer lifecycle and ensures graceful pool shutdown\r\n */\r\nexport class MailerConnector extends BaseConnector {\r\n public readonly name = \"mailer\";\r\n public readonly priority = ConnectorPriority.MAILER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger mailer restart\r\n */\r\n protected readonly watchedFiles = [\".env\", \"src/config/mail.ts\", \"src/config/mail.tsx\"];\r\n\r\n /**\r\n * Initialize mailer configurations\r\n */\r\n public async start(): Promise<void> {\r\n const mailConfig = config.get(\"mail\");\r\n\r\n if (!mailConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setMailConfigurations(mailConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize mailer:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown mailer pool\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n closeAllMailers();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to close all mailers:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW;GAAC;GAAQ;GAAsB;EAAqB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YACH;EAGF,IAAI;GACF,sBAAsB,UAAU;GAChC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,gBAAgB;GAChB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
2
2
|
import { BaseConnector } from "./base-connector.mjs";
|
|
3
|
-
import
|
|
3
|
+
import config from "@mongez/config";
|
|
4
4
|
import { log } from "@warlock.js/logger";
|
|
5
5
|
|
|
6
6
|
//#region ../core/src/connectors/notifications-connector.ts
|
|
@@ -28,7 +28,7 @@ var NotificationsConnector = class extends BaseConnector {
|
|
|
28
28
|
* Register the notifications configuration with the package.
|
|
29
29
|
*/
|
|
30
30
|
async start() {
|
|
31
|
-
const notificationsConfig =
|
|
31
|
+
const notificationsConfig = config.get("notifications");
|
|
32
32
|
if (!notificationsConfig) return;
|
|
33
33
|
const { setNotificationConfig } = await import("@warlock.js/notifications");
|
|
34
34
|
setNotificationConfig(notificationsConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-connector.mjs","names":[
|
|
1
|
+
{"version":3,"file":"notifications-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/notifications-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Notifications Connector\n *\n * Wires `@warlock.js/notifications` from `src/config/notifications.ts` at boot:\n * reads the exported config object and hands it to the package's\n * `setNotificationConfig`. Config files stay declarative — they export the\n * config, the connector performs the side-effect.\n *\n * The package is lazy-imported (only when a notifications config is present),\n * so core carries no hard dependency on it — the same pattern as the herald\n * connector with `@warlock.js/herald`.\n */\nexport class NotificationsConnector extends BaseConnector {\n public readonly name = \"notifications\";\n public readonly priority = ConnectorPriority.NOTIFICATIONS;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger a notifications restart\n */\n protected readonly watchedFiles = [\"src/config/notifications.ts\"];\n\n /**\n * Register the notifications configuration with the package.\n */\n public async start(): Promise<void> {\n const notificationsConfig = config.get(\"notifications\");\n\n if (!notificationsConfig) {\n return;\n }\n\n const { setNotificationConfig } = await import(\"@warlock.js/notifications\");\n\n setNotificationConfig(notificationsConfig);\n this.active = true;\n\n log.info(\"notifications\", \"configured\", \"Notifications wired from config/notifications.ts\");\n }\n\n /**\n * Nothing to tear down — the package holds no external connection of its own\n * (the optional queue worker + its broker own their lifecycle). Clear the\n * active flag so a dev restart re-registers from the (possibly changed) config.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,IAAa,yBAAb,cAA4C,cAAc;;;cACjC;;;sBAOW,CAAC,6BAA6B;;;;;CAKhE,MAAa,QAAuB;EAClC,MAAM,sBAAsB,OAAO,IAAI,eAAe;EAEtD,IAAI,CAAC,qBACH;EAGF,MAAM,EAAE,0BAA0B,MAAM,OAAO;EAE/C,sBAAsB,mBAAmB;EACzC,KAAK,SAAS;EAEd,IAAI,KAAK,iBAAiB,cAAc,kDAAkD;CAC5F;;;;;;CAOA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,KAAK,SAAS;CAChB;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { container } from "../container/index.mjs";
|
|
2
2
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
3
3
|
import { BaseConnector } from "./base-connector.mjs";
|
|
4
|
-
import
|
|
4
|
+
import config from "@mongez/config";
|
|
5
5
|
import { log } from "@warlock.js/logger";
|
|
6
6
|
import { createServer } from "http";
|
|
7
7
|
import { createServer as createServer$1 } from "https";
|
|
@@ -40,7 +40,7 @@ var SocketConnector = class extends BaseConnector {
|
|
|
40
40
|
* Boot the connector
|
|
41
41
|
*/
|
|
42
42
|
async boot() {
|
|
43
|
-
const socketConfig =
|
|
43
|
+
const socketConfig = config.get("socket");
|
|
44
44
|
if (!socketConfig) return;
|
|
45
45
|
let SocketServer;
|
|
46
46
|
try {
|
|
@@ -66,7 +66,7 @@ var SocketConnector = class extends BaseConnector {
|
|
|
66
66
|
* Initialize Socket server
|
|
67
67
|
*/
|
|
68
68
|
async start() {
|
|
69
|
-
if (!
|
|
69
|
+
if (!config.get("socket") || !this.socket) return;
|
|
70
70
|
log.success("socket", "connection", "Established Socket.IO server");
|
|
71
71
|
this.active = true;
|
|
72
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-connector.mjs","names":["
|
|
1
|
+
{"version":3,"file":"socket-connector.mjs","names":["createHttpsServer","createHttpServer"],"sources":["../../../../../../../core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server;\n if (container.has(\"http.server\")) {\n const fastify = container.get(\"http.server\");\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl ? createHttpsServer() : createHttpServer();\n server.listen(socketConfig.port);\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n this.socket = new SocketServer(server, socketConfig.options);\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n if (container.has(\"socket\")) {\n const socket = container.get(\"socket\");\n // socket.io's close() takes a callback — await it so shutdown doesn't\n // report done while sockets are still draining.\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n }\n\n if (this.ownsRawServer && container.has(\"socket.rawServer\")) {\n const server = container.get(\"socket.rawServer\");\n await new Promise<void>((resolve, reject) => {\n server.close((error?: Error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAM,8BAA8B;;;;;;;;;;;EAWlC,KAAK;;;;;AAMP,IAAa,kBAAb,cAAqC,cAAc;;;cACX;;;sBASJ,CAAC,sBAAsB;uBAY/B;;;;;CAK1B,MAAa,OAAO;EAClB,MAAM,eAAe,OAAO,IAAI,QAAQ;EAExC,IAAI,CAAC,cAAc;EAInB,IAAI;EACJ,IAAI;GACF,CAAC,CAAE,QAAQ,gBAAiB,MAAM,OAAO;EAC3C,QAAQ;GACN,MAAM,IAAI,MAAM,2BAA2B;EAC7C;EAEA,IAAI,KAAK,UAAU,cAAc,2BAA2B;EAK5D,IAAI;EACJ,IAAI,UAAU,IAAI,aAAa,GAAG;GAEhC,SADgB,UAAU,IAAI,aACf,CAAC,CAAC;GACjB,KAAK,gBAAgB;EACvB,OAAO;GACL,SAAS,aAAa,MAAMA,eAAkB,IAAIC,aAAiB;GACnE,OAAO,OAAO,aAAa,IAAI;GAC/B,KAAK,gBAAgB;EACvB;EAEA,UAAU,IAAI,oBAAoB,MAAM;EAExC,KAAK,SAAS,IAAI,aAAa,QAAQ,aAAa,OAAO;EAE3D,UAAU,IAAI,UAAU,KAAK,MAAM;CACrC;;;;CAKA,MAAa,QAAuB;EAKlC,IAAI,CAJiB,OAAO,IAAI,QAIhB,KAAK,CAAC,KAAK,QAAQ;EAEnC,IAAI,QAAQ,UAAU,cAAc,8BAA8B;EAElE,KAAK,SAAS;CAChB;;;;;;;;;;CAWA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI,UAAU,IAAI,QAAQ,GAAG;GAC3B,MAAM,SAAS,UAAU,IAAI,QAAQ;GAGrC,MAAM,IAAI,SAAe,YAAY;IACnC,OAAO,YAAY,QAAQ,CAAC;GAC9B,CAAC;EACH;EAEA,IAAI,KAAK,iBAAiB,UAAU,IAAI,kBAAkB,GAAG;GAC3D,MAAM,SAAS,UAAU,IAAI,kBAAkB;GAC/C,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,OAAO,OAAO,UAAkB;KAC9B,IAAI,OACF,OAAO,KAAK;UAEZ,QAAQ;IAEZ,CAAC;GACH,CAAC;EACH;EAEA,KAAK,SAAS;CAChB;AACF"}
|
|
@@ -15,14 +15,20 @@ declare class StorageConnector extends BaseConnector {
|
|
|
15
15
|
*/
|
|
16
16
|
protected readonly watchedFiles: string[];
|
|
17
17
|
/**
|
|
18
|
-
* Initialize
|
|
18
|
+
* Initialize storage.
|
|
19
19
|
*
|
|
20
20
|
* Unlike its siblings (database, cache, herald), this connector does NOT
|
|
21
21
|
* early-return when `config.get("storage")` is absent. Storage is
|
|
22
|
-
* intentionally always-on: `storage.init()`
|
|
23
|
-
*
|
|
24
|
-
* works out of the box even when a project ships no
|
|
25
|
-
* A config-presence guard here would break that
|
|
22
|
+
* intentionally always-on: `storage.init()` registers a built-in `local`
|
|
23
|
+
* driver rooted at `uploadsPath()` before reading configured drivers, so file
|
|
24
|
+
* storage works out of the box even when a project ships no
|
|
25
|
+
* `src/config/storage.ts`. A config-presence guard here would break that.
|
|
26
|
+
*
|
|
27
|
+
* That fallback is real as of 4.11.0. Until then this comment described it
|
|
28
|
+
* and nothing implemented it, so every app without `src/config/storage.ts`
|
|
29
|
+
* died at boot with `Storage driver "local" is not configured` — a decision
|
|
30
|
+
* to skip the guard, taken on the authority of behaviour nobody had written.
|
|
31
|
+
* See `Storage.registerBuiltInLocalDriver`.
|
|
26
32
|
*/
|
|
27
33
|
start(): Promise<void>;
|
|
28
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"mappings":";;;;;;AASA;;cAAa,gBAAA,SAAyB,aAAA;EAAA,SACpB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,OAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,KAAA;
|
|
1
|
+
{"version":3,"file":"storage.connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"mappings":";;;;;;AASA;;cAAa,gBAAA,SAAyB,aAAA;EAAA,SACpB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,OAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,KAAA;EAiCL;;;EAAA,mBA5BN,YAAA;EARiB;;;;;;;;;;;;;;AAoCJ;;EAVnB,KAAA,IAAS,OAAA;;;;EAUT,QAAA,IAAY,OAAA;AAAA"}
|
|
@@ -18,14 +18,20 @@ var StorageConnector = class extends BaseConnector {
|
|
|
18
18
|
this.watchedFiles = ["src/config/storage.ts", "src/config/storage.tsx"];
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Initialize
|
|
21
|
+
* Initialize storage.
|
|
22
22
|
*
|
|
23
23
|
* Unlike its siblings (database, cache, herald), this connector does NOT
|
|
24
24
|
* early-return when `config.get("storage")` is absent. Storage is
|
|
25
|
-
* intentionally always-on: `storage.init()`
|
|
26
|
-
*
|
|
27
|
-
* works out of the box even when a project ships no
|
|
28
|
-
* A config-presence guard here would break that
|
|
25
|
+
* intentionally always-on: `storage.init()` registers a built-in `local`
|
|
26
|
+
* driver rooted at `uploadsPath()` before reading configured drivers, so file
|
|
27
|
+
* storage works out of the box even when a project ships no
|
|
28
|
+
* `src/config/storage.ts`. A config-presence guard here would break that.
|
|
29
|
+
*
|
|
30
|
+
* That fallback is real as of 4.11.0. Until then this comment described it
|
|
31
|
+
* and nothing implemented it, so every app without `src/config/storage.ts`
|
|
32
|
+
* died at boot with `Storage driver "local" is not configured` — a decision
|
|
33
|
+
* to skip the guard, taken on the authority of behaviour nobody had written.
|
|
34
|
+
* See `Storage.registerBuiltInLocalDriver`.
|
|
29
35
|
*/
|
|
30
36
|
async start() {
|
|
31
37
|
await loadS3();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"sourcesContent":["import { storage } from \"../storage\";\nimport { loadS3 } from \"../storage/drivers/cloud-driver\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class StorageConnector extends BaseConnector {\n public readonly name = \"storage\";\n public readonly priority = ConnectorPriority.STORAGE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/storage.ts\", \"src/config/storage.tsx\"];\n\n /**\n * Initialize
|
|
1
|
+
{"version":3,"file":"storage.connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/storage.connector.ts"],"sourcesContent":["import { storage } from \"../storage\";\nimport { loadS3 } from \"../storage/drivers/cloud-driver\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Cache Connector\n * Manages cache engine connection lifecycle\n */\nexport class StorageConnector extends BaseConnector {\n public readonly name = \"storage\";\n public readonly priority = ConnectorPriority.STORAGE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger cache restart\n */\n protected readonly watchedFiles = [\"src/config/storage.ts\", \"src/config/storage.tsx\"];\n\n /**\n * Initialize storage.\n *\n * Unlike its siblings (database, cache, herald), this connector does NOT\n * early-return when `config.get(\"storage\")` is absent. Storage is\n * intentionally always-on: `storage.init()` registers a built-in `local`\n * driver rooted at `uploadsPath()` before reading configured drivers, so file\n * storage works out of the box even when a project ships no\n * `src/config/storage.ts`. A config-presence guard here would break that.\n *\n * That fallback is real as of 4.11.0. Until then this comment described it\n * and nothing implemented it, so every app without `src/config/storage.ts`\n * died at boot with `Storage driver \"local\" is not configured` — a decision\n * to skip the guard, taken on the authority of behaviour nobody had written.\n * See `Storage.registerBuiltInLocalDriver`.\n */\n public async start(): Promise<void> {\n await loadS3();\n await storage.init();\n\n this.active = true;\n }\n\n /**\n * Shutdown cache connection\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n storage.reset();\n\n this.active = false;\n }\n}\n"],"mappings":";;;;;;;;;;;AASA,IAAa,mBAAb,cAAsC,cAAc;;;cAC3B;;;sBAOW,CAAC,yBAAyB,wBAAwB;;;;;;;;;;;;;;;;;;CAkBpF,MAAa,QAAuB;EAClC,MAAM,OAAO;EACb,MAAM,QAAQ,KAAK;EAEnB,KAAK,SAAS;CAChB;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,QAAQ,MAAM;EAEd,KAAK,SAAS;CAChB;AACF"}
|
|
@@ -4,13 +4,30 @@ import { Path } from "../dev-server/path.mjs";
|
|
|
4
4
|
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
5
5
|
import { getFilesFromDirectory } from "../dev-server/utils.mjs";
|
|
6
6
|
import { filesOrchestrator } from "../dev-server/files-orchestrator.mjs";
|
|
7
|
+
import { PENDING_EXIT_CODE, exitCodeFor } from "./pending-exit-code.mjs";
|
|
8
|
+
import { resolvePendingMigrations } from "./resolve-pending-migrations.mjs";
|
|
7
9
|
import { exportMigrationsSQL, freshMigrate, listExecutedMigrations, migrationRunner, rollbackMigrations, runMigrations } from "@warlock.js/cascade";
|
|
8
10
|
import path from "path";
|
|
9
11
|
import { colors } from "@mongez/copper";
|
|
10
12
|
import dayjs from "dayjs";
|
|
11
13
|
|
|
12
14
|
//#region ../core/src/database/migrate-action.ts
|
|
15
|
+
/**
|
|
16
|
+
* `--list` — print the migration state: what has run, then what will run next.
|
|
17
|
+
*
|
|
18
|
+
* The executed section is printed FIRST and unconditionally, before anything
|
|
19
|
+
* touches disk or config. That ordering is the contract: reading migrations
|
|
20
|
+
* from the table cannot fail because of a broken migration file, and `--list`
|
|
21
|
+
* is the command an operator reaches for *during* an incident. The pending
|
|
22
|
+
* section is best-effort on top of an answer that is already on screen.
|
|
23
|
+
*
|
|
24
|
+
* Always exits 0. It is a report; `--pending` is the gate.
|
|
25
|
+
*/
|
|
13
26
|
async function listMigrationsAction() {
|
|
27
|
+
await printExecutedMigrations();
|
|
28
|
+
printPendingMigrations(await resolvePendingMigrations(loadAllMigrations));
|
|
29
|
+
}
|
|
30
|
+
async function printExecutedMigrations() {
|
|
14
31
|
const createdMigrations = await listExecutedMigrations();
|
|
15
32
|
console.log(`\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\n`);
|
|
16
33
|
if (createdMigrations.length === 0) {
|
|
@@ -26,6 +43,40 @@ async function listMigrationsAction() {
|
|
|
26
43
|
console.log("");
|
|
27
44
|
}
|
|
28
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Print the pending section shared by `--list` and `--pending`.
|
|
48
|
+
*
|
|
49
|
+
* An unavailable result prints an explicit line and NEVER a count. "0 pending"
|
|
50
|
+
* over a tree we failed to read is the one output this whole feature exists to
|
|
51
|
+
* prevent.
|
|
52
|
+
*/
|
|
53
|
+
function printPendingMigrations(result) {
|
|
54
|
+
if (result.type === "unavailable") {
|
|
55
|
+
console.log(`${colors.yellowBright("Pending: unavailable")} — ${result.reason}\n` + colors.gray(" The executed list above is still accurate.\n"));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const { migrations } = result;
|
|
59
|
+
console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\n`);
|
|
60
|
+
if (migrations.length === 0) {
|
|
61
|
+
console.log(colors.gray(" Nothing pending — the database is up to date.\n"));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
migrations.forEach((migration, index) => {
|
|
65
|
+
console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);
|
|
66
|
+
});
|
|
67
|
+
console.log("");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `--pending` — the scriptable half. Same computation as `--list`'s pending
|
|
71
|
+
* section, different exit contract: see {@link PENDING_EXIT_CODE}.
|
|
72
|
+
*/
|
|
73
|
+
async function pendingMigrationsAction() {
|
|
74
|
+
const result = await resolvePendingMigrations(loadAllMigrations);
|
|
75
|
+
printPendingMigrations(result);
|
|
76
|
+
const exitCode = exitCodeFor(result);
|
|
77
|
+
if (exitCode === PENDING_EXIT_CODE.unavailable) console.error(colors.redBright("Could not determine the pending set — exiting 2 rather than reporting 0."));
|
|
78
|
+
process.exit(exitCode);
|
|
79
|
+
}
|
|
29
80
|
async function allMigrationsFilesAction() {
|
|
30
81
|
const files = (await migrationFiles()).map((path) => Path.toRelative(path));
|
|
31
82
|
console.log(`Total Migration Files: ${colors.green(files.length)}`);
|
|
@@ -37,8 +88,9 @@ async function allMigrationsFilesAction() {
|
|
|
37
88
|
* If rollback is provided, then run the migration runner against all files in reverse order
|
|
38
89
|
*/
|
|
39
90
|
async function migrateAction(options) {
|
|
40
|
-
const { fresh, path, rollback, all, list, sql, pendingOnly, compact } = options.options;
|
|
91
|
+
const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;
|
|
41
92
|
if (list) return await listMigrationsAction();
|
|
93
|
+
if (pending) return await pendingMigrationsAction();
|
|
42
94
|
if (all) return await allMigrationsFilesAction();
|
|
43
95
|
if (path) await loadMigrationFile(Path.toAbsolute(path));
|
|
44
96
|
else await loadAllMigrations();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n exportMigrationsSQL,\r\n freshMigrate,\r\n listExecutedMigrations,\r\n Migration,\r\n migrationRunner,\r\n rollbackMigrations,\r\n runMigrations,\r\n} from \"@warlock.js/cascade\";\r\nimport dayjs from \"dayjs\";\r\nimport path from \"path\";\r\nimport { CommandActionData } from \"../cli/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../dev-server/path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\n\r\nasync function listMigrationsAction() {\r\n const createdMigrations = await listExecutedMigrations();\r\n\r\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\r\n\r\n if (createdMigrations.length === 0) {\r\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\r\n return;\r\n }\r\n\r\n // Display each migration as a block\r\n for (const migration of createdMigrations) {\r\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\r\n const createdAt = migration.createdAt\r\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\r\n : null;\r\n\r\n // Migration name with checkmark icon\r\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\r\n\r\n // Executed date\r\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\r\n\r\n // Created date (if available)\r\n if (createdAt) {\r\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\r\n }\r\n\r\n console.log(\"\"); // Empty line between migrations\r\n }\r\n}\r\n\r\nasync function allMigrationsFilesAction() {\r\n // get all available migration files in the project\r\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\r\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\r\n\r\n for (const file of files) {\r\n console.log(colors.yellowBright(file));\r\n }\r\n}\r\n\r\n/**\r\n * If path is provided, then run the migration runner against that file only\r\n * If fresh is provided, then rollback all migrations and run all migrations\r\n * If rollback is provided, then run the migration runner against all files in reverse order\r\n */\r\nexport async function migrateAction(options: CommandActionData) {\r\n const { fresh, path, rollback, all, list, sql, pendingOnly, compact } = options.options;\r\n\r\n if (list) {\r\n return await listMigrationsAction();\r\n }\r\n\r\n if (all) {\r\n return await allMigrationsFilesAction();\r\n }\r\n\r\n if (path) {\r\n await loadMigrationFile(Path.toAbsolute(path as string));\r\n } else {\r\n await loadAllMigrations();\r\n }\r\n\r\n if (fresh && rollback) {\r\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\r\n process.exit(1);\r\n }\r\n\r\n if (rollback) {\r\n await rollbackMigrations({ all: true });\r\n return;\r\n }\r\n\r\n if (sql) {\r\n await exportMigrationsSQL({\r\n pendingOnly: pendingOnly as boolean,\r\n compact: compact as boolean,\r\n });\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await freshMigrate();\r\n return;\r\n }\r\n\r\n await runMigrations();\r\n}\r\n\r\nasync function loadMigrationFile(absPath: string) {\r\n const relativePath = Path.toRelative(absPath);\r\n\r\n const loadedModule = await filesOrchestrator.load<{ default: typeof Migration }>(relativePath);\r\n\r\n if (!loadedModule?.default) {\r\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\r\n }\r\n\r\n const MigrationClass = loadedModule.default;\r\n\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = path\r\n .basename(absPath)\r\n .split(\".\")[0]\r\n .replace(\"-migration\", \"\")\r\n .replace(\"_migration\", \"\");\r\n }\r\n\r\n // Extract createdAt timestamp from filename if not already set\r\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\r\n if (!MigrationClass.createdAt) {\r\n const filename = path.basename(absPath);\r\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\r\n if (timestampMatch) {\r\n MigrationClass.createdAt = timestampMatch[1];\r\n }\r\n }\r\n\r\n migrationRunner.register(MigrationClass);\r\n}\r\n\r\n/**\r\n *\r\n * @returns List of absolute paths to migration files\r\n */\r\nasync function migrationFiles() {\r\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\r\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\r\n srcPath(\"app\"),\r\n \"*/migrations/*\",\r\n );\r\n\r\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles];\r\n\r\n return migrations;\r\n}\r\n\r\nasync function loadAllMigrations() {\r\n // Load config-registered migrations (from packages like @warlock.js/auth)\r\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\r\n\r\n for (const MigrationClass of configMigrations) {\r\n // Use class name as migration name if not set\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = MigrationClass.name;\r\n }\r\n migrationRunner.register(MigrationClass);\r\n }\r\n\r\n // Always load file-based migrations from src/app, regardless of config\r\n const migrations = await migrationFiles();\r\n for (const migrationFile of migrations) {\r\n await loadMigrationFile(migrationFile);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migrate-action.mjs","names":[],"sources":["../../../../../../../core/src/database/migrate-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n exportMigrationsSQL,\r\n freshMigrate,\r\n listExecutedMigrations,\r\n Migration,\r\n migrationRunner,\r\n rollbackMigrations,\r\n runMigrations,\r\n} from \"@warlock.js/cascade\";\r\nimport dayjs from \"dayjs\";\r\nimport path from \"path\";\r\nimport { CommandActionData } from \"../cli/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../dev-server/path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { exitCodeFor, PENDING_EXIT_CODE } from \"./pending-exit-code\";\r\nimport {\r\n resolvePendingMigrations,\r\n type PendingMigrationsResult,\r\n} from \"./resolve-pending-migrations\";\r\n\r\n/**\r\n * `--list` — print the migration state: what has run, then what will run next.\r\n *\r\n * The executed section is printed FIRST and unconditionally, before anything\r\n * touches disk or config. That ordering is the contract: reading migrations\r\n * from the table cannot fail because of a broken migration file, and `--list`\r\n * is the command an operator reaches for *during* an incident. The pending\r\n * section is best-effort on top of an answer that is already on screen.\r\n *\r\n * Always exits 0. It is a report; `--pending` is the gate.\r\n */\r\nasync function listMigrationsAction() {\r\n await printExecutedMigrations();\r\n\r\n const pending = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(pending);\r\n}\r\n\r\nasync function printExecutedMigrations() {\r\n const createdMigrations = await listExecutedMigrations();\r\n\r\n console.log(`\\nTotal Executed Migrations: ${colors.green(createdMigrations.length)}\\n`);\r\n\r\n if (createdMigrations.length === 0) {\r\n console.log(colors.gray(\" No migrations have been executed yet.\\n\"));\r\n return;\r\n }\r\n\r\n // Display each migration as a block\r\n for (const migration of createdMigrations) {\r\n const executedAt = dayjs(migration.executedAt).format(\"DD-MM-YYYY hh:mm:ss A\");\r\n const createdAt = migration.createdAt\r\n ? dayjs(migration.createdAt).format(\"DD-MM-YYYY hh:mm:ss A\")\r\n : null;\r\n\r\n // Migration name with checkmark icon\r\n console.log(` ${colors.green(\"✔\")} ${colors.cyanBright(migration.name)}`);\r\n\r\n // Executed date\r\n console.log(` ${colors.gray(\"Executed:\")} ${colors.white(executedAt)}`);\r\n\r\n // Created date (if available)\r\n if (createdAt) {\r\n console.log(` ${colors.gray(\"Created:\")} ${colors.yellow(createdAt)}`);\r\n }\r\n\r\n console.log(\"\"); // Empty line between migrations\r\n }\r\n}\r\n\r\n/**\r\n * Print the pending section shared by `--list` and `--pending`.\r\n *\r\n * An unavailable result prints an explicit line and NEVER a count. \"0 pending\"\r\n * over a tree we failed to read is the one output this whole feature exists to\r\n * prevent.\r\n */\r\nfunction printPendingMigrations(result: PendingMigrationsResult) {\r\n if (result.type === \"unavailable\") {\r\n console.log(\r\n `${colors.yellowBright(\"Pending: unavailable\")} — ${result.reason}\\n` +\r\n colors.gray(\" The executed list above is still accurate.\\n\"),\r\n );\r\n return;\r\n }\r\n\r\n const { migrations } = result;\r\n\r\n console.log(`Total Pending Migrations: ${colors.green(migrations.length)}\\n`);\r\n\r\n if (migrations.length === 0) {\r\n console.log(colors.gray(\" Nothing pending — the database is up to date.\\n\"));\r\n return;\r\n }\r\n\r\n // Numbered because the ORDER is the answer: this is what the next\r\n // `warlock migrate` will do, in the sequence it will do it.\r\n migrations.forEach((migration, index) => {\r\n console.log(` ${colors.gray(`${index + 1}.`)} ${colors.cyanBright(migration.name)}`);\r\n });\r\n\r\n console.log(\"\");\r\n}\r\n\r\n/**\r\n * `--pending` — the scriptable half. Same computation as `--list`'s pending\r\n * section, different exit contract: see {@link PENDING_EXIT_CODE}.\r\n */\r\nasync function pendingMigrationsAction() {\r\n const result = await resolvePendingMigrations(loadAllMigrations);\r\n\r\n printPendingMigrations(result);\r\n\r\n const exitCode = exitCodeFor(result);\r\n\r\n if (exitCode === PENDING_EXIT_CODE.unavailable) {\r\n console.error(\r\n colors.redBright(\"Could not determine the pending set — exiting 2 rather than reporting 0.\"),\r\n );\r\n }\r\n\r\n process.exit(exitCode);\r\n}\r\n\r\nasync function allMigrationsFilesAction() {\r\n // get all available migration files in the project\r\n const files = (await migrationFiles()).map((path) => Path.toRelative(path));\r\n console.log(`Total Migration Files: ${colors.green(files.length)}`);\r\n\r\n for (const file of files) {\r\n console.log(colors.yellowBright(file));\r\n }\r\n}\r\n\r\n/**\r\n * If path is provided, then run the migration runner against that file only\r\n * If fresh is provided, then rollback all migrations and run all migrations\r\n * If rollback is provided, then run the migration runner against all files in reverse order\r\n */\r\nexport async function migrateAction(options: CommandActionData) {\r\n const { fresh, path, rollback, all, list, pending, sql, pendingOnly, compact } = options.options;\r\n\r\n if (list) {\r\n return await listMigrationsAction();\r\n }\r\n\r\n if (pending) {\r\n return await pendingMigrationsAction();\r\n }\r\n\r\n if (all) {\r\n return await allMigrationsFilesAction();\r\n }\r\n\r\n if (path) {\r\n await loadMigrationFile(Path.toAbsolute(path as string));\r\n } else {\r\n await loadAllMigrations();\r\n }\r\n\r\n if (fresh && rollback) {\r\n console.log(colors.redBright(\"You can't use --fresh and --rollback together\"));\r\n process.exit(1);\r\n }\r\n\r\n if (rollback) {\r\n await rollbackMigrations({ all: true });\r\n return;\r\n }\r\n\r\n if (sql) {\r\n await exportMigrationsSQL({\r\n pendingOnly: pendingOnly as boolean,\r\n compact: compact as boolean,\r\n });\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await freshMigrate();\r\n return;\r\n }\r\n\r\n await runMigrations();\r\n}\r\n\r\nasync function loadMigrationFile(absPath: string) {\r\n const relativePath = Path.toRelative(absPath);\r\n\r\n const loadedModule = await filesOrchestrator.load<{ default: typeof Migration }>(relativePath);\r\n\r\n if (!loadedModule?.default) {\r\n throw new Error(`${Path.toRelative(absPath)} must have a default export`);\r\n }\r\n\r\n const MigrationClass = loadedModule.default;\r\n\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = path\r\n .basename(absPath)\r\n .split(\".\")[0]\r\n .replace(\"-migration\", \"\")\r\n .replace(\"_migration\", \"\");\r\n }\r\n\r\n // Extract createdAt timestamp from filename if not already set\r\n // Expected format: MM-DD-YYYY_HH-MM-SS-name.migration.ts\r\n if (!MigrationClass.createdAt) {\r\n const filename = path.basename(absPath);\r\n const timestampMatch = filename.match(/^(\\d{2}-\\d{2}-\\d{4}_\\d{2}-\\d{2}-\\d{2})/);\r\n if (timestampMatch) {\r\n MigrationClass.createdAt = timestampMatch[1];\r\n }\r\n }\r\n\r\n migrationRunner.register(MigrationClass);\r\n}\r\n\r\n/**\r\n *\r\n * @returns List of absolute paths to migration files\r\n */\r\nasync function migrationFiles() {\r\n const migrationFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/models/*/migrations/*\");\r\n const separateMigrationsFolderFIles = await getFilesFromDirectory(\r\n srcPath(\"app\"),\r\n \"*/migrations/*\",\r\n );\r\n\r\n const migrations = [...migrationFiles, ...separateMigrationsFolderFIles];\r\n\r\n return migrations;\r\n}\r\n\r\nasync function loadAllMigrations() {\r\n // Load config-registered migrations (from packages like @warlock.js/auth)\r\n const configMigrations = warlockConfigManager.get(\"database\")?.migrations || [];\r\n\r\n for (const MigrationClass of configMigrations) {\r\n // Use class name as migration name if not set\r\n if (!MigrationClass.migrationName) {\r\n MigrationClass.migrationName = MigrationClass.name;\r\n }\r\n migrationRunner.register(MigrationClass);\r\n }\r\n\r\n // Always load file-based migrations from src/app, regardless of config\r\n const migrations = await migrationFiles();\r\n for (const migrationFile of migrations) {\r\n await loadMigrationFile(migrationFile);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region ../core/src/database/pending-exit-code.ts
|
|
2
|
+
/**
|
|
3
|
+
* Exit codes for `warlock migrate --pending`.
|
|
4
|
+
*
|
|
5
|
+
* `--pending` is a GATE, not a report: it is written into `migrate --pending &&
|
|
6
|
+
* deploy` and into CI steps asserting nothing is outstanding. So it must
|
|
7
|
+
* distinguish three outcomes, not two.
|
|
8
|
+
*
|
|
9
|
+
* Two codes would fold `pending` and `unavailable` into a single non-zero, and
|
|
10
|
+
* a script could no longer tell "three migrations are waiting" from "I could
|
|
11
|
+
* not work out what is waiting". Those demand opposite responses — the first is
|
|
12
|
+
* "run them", the second is "stop and get a human".
|
|
13
|
+
*
|
|
14
|
+
* `warlock migrate --list` is the report half and always exits `0`, whatever
|
|
15
|
+
* the pending section says.
|
|
16
|
+
*/
|
|
17
|
+
const PENDING_EXIT_CODE = {
|
|
18
|
+
/** Computed, and nothing is pending. */
|
|
19
|
+
clear: 0,
|
|
20
|
+
/** Computed, and at least one migration is pending. */
|
|
21
|
+
pending: 1,
|
|
22
|
+
/** Could not be computed. Never conflate this with `clear`. */
|
|
23
|
+
unavailable: 2
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Map a resolution outcome onto the exit code a gate should report.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* exitCodeFor({ type: "resolved", migrations: [] }); // 0
|
|
30
|
+
* exitCodeFor({ type: "unavailable", reason: "…" }); // 2
|
|
31
|
+
*/
|
|
32
|
+
function exitCodeFor(result) {
|
|
33
|
+
if (result.type === "unavailable") return PENDING_EXIT_CODE.unavailable;
|
|
34
|
+
return result.migrations.length > 0 ? PENDING_EXIT_CODE.pending : PENDING_EXIT_CODE.clear;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { PENDING_EXIT_CODE, exitCodeFor };
|
|
39
|
+
//# sourceMappingURL=pending-exit-code.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-exit-code.mjs","names":[],"sources":["../../../../../../../core/src/database/pending-exit-code.ts"],"sourcesContent":["import type { PendingMigrationsResult } from \"./resolve-pending-migrations\";\n\n/**\n * Exit codes for `warlock migrate --pending`.\n *\n * `--pending` is a GATE, not a report: it is written into `migrate --pending &&\n * deploy` and into CI steps asserting nothing is outstanding. So it must\n * distinguish three outcomes, not two.\n *\n * Two codes would fold `pending` and `unavailable` into a single non-zero, and\n * a script could no longer tell \"three migrations are waiting\" from \"I could\n * not work out what is waiting\". Those demand opposite responses — the first is\n * \"run them\", the second is \"stop and get a human\".\n *\n * `warlock migrate --list` is the report half and always exits `0`, whatever\n * the pending section says.\n */\nexport const PENDING_EXIT_CODE = {\n /** Computed, and nothing is pending. */\n clear: 0,\n /** Computed, and at least one migration is pending. */\n pending: 1,\n /** Could not be computed. Never conflate this with `clear`. */\n unavailable: 2,\n} as const;\n\nexport type PendingExitCode = (typeof PENDING_EXIT_CODE)[keyof typeof PENDING_EXIT_CODE];\n\n/**\n * Map a resolution outcome onto the exit code a gate should report.\n *\n * @example\n * exitCodeFor({ type: \"resolved\", migrations: [] }); // 0\n * exitCodeFor({ type: \"unavailable\", reason: \"…\" }); // 2\n */\nexport function exitCodeFor(result: PendingMigrationsResult): PendingExitCode {\n if (result.type === \"unavailable\") {\n return PENDING_EXIT_CODE.unavailable;\n }\n\n return result.migrations.length > 0 ? PENDING_EXIT_CODE.pending : PENDING_EXIT_CODE.clear;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,oBAAoB;;CAE/B,OAAO;;CAEP,SAAS;;CAET,aAAa;AACf;;;;;;;;AAWA,SAAgB,YAAY,QAAkD;CAC5E,IAAI,OAAO,SAAS,eAClB,OAAO,kBAAkB;CAG3B,OAAO,OAAO,WAAW,SAAS,IAAI,kBAAkB,UAAU,kBAAkB;AACtF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { listPendingMigrations } from "@warlock.js/cascade";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/database/resolve-pending-migrations.ts
|
|
4
|
+
/**
|
|
5
|
+
* Load the project's migrations, then compute which of them have not run yet.
|
|
6
|
+
*
|
|
7
|
+
* `loadMigrations` must be the SAME registration path a real `warlock migrate`
|
|
8
|
+
* takes. Skipping it does not fail — it silently yields an empty pending set,
|
|
9
|
+
* because the pending set is computed from the runner's registry.
|
|
10
|
+
*
|
|
11
|
+
* Loading executes project code (a migration file is imported, and a broken one
|
|
12
|
+
* throws), so every failure is converted into an `unavailable` result rather
|
|
13
|
+
* than propagating. Callers decide what an unknown means for them: a listing
|
|
14
|
+
* prints it and carries on, a gate refuses to pass.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const result = await resolvePendingMigrations(loadAllMigrations);
|
|
18
|
+
*
|
|
19
|
+
* if (result.type === "unavailable") {
|
|
20
|
+
* console.error(`Pending: unavailable — ${result.reason}`);
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
async function resolvePendingMigrations(loadMigrations) {
|
|
24
|
+
try {
|
|
25
|
+
await loadMigrations();
|
|
26
|
+
return {
|
|
27
|
+
type: "resolved",
|
|
28
|
+
migrations: await listPendingMigrations()
|
|
29
|
+
};
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
type: "unavailable",
|
|
33
|
+
reason: describeFailure(error)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reduce a thrown value to a single line an operator can act on. The message is
|
|
39
|
+
* the actionable part — a stack trace in the middle of a listing buries the
|
|
40
|
+
* executed section the reader still needs.
|
|
41
|
+
*/
|
|
42
|
+
function describeFailure(error) {
|
|
43
|
+
if (error instanceof Error) return error.message;
|
|
44
|
+
return String(error);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { resolvePendingMigrations };
|
|
49
|
+
//# sourceMappingURL=resolve-pending-migrations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-pending-migrations.mjs","names":[],"sources":["../../../../../../../core/src/database/resolve-pending-migrations.ts"],"sourcesContent":["import { listPendingMigrations, type PendingMigration } from \"@warlock.js/cascade\";\n\n/**\n * Outcome of trying to compute the pending migration set.\n *\n * The \"unavailable\" arm exists because the two failure modes are NOT the same\n * answer: a database with nothing pending and a tree we could not read both\n * produce an empty list, and only one of them means it is safe to proceed.\n * Collapsing them into `[]` is how a pre-flight check reports a confident\n * all-clear over an unknown.\n */\nexport type PendingMigrationsResult =\n | {\n readonly type: \"resolved\";\n readonly migrations: PendingMigration[];\n }\n | {\n readonly type: \"unavailable\";\n readonly reason: string;\n };\n\n/**\n * Load the project's migrations, then compute which of them have not run yet.\n *\n * `loadMigrations` must be the SAME registration path a real `warlock migrate`\n * takes. Skipping it does not fail — it silently yields an empty pending set,\n * because the pending set is computed from the runner's registry.\n *\n * Loading executes project code (a migration file is imported, and a broken one\n * throws), so every failure is converted into an `unavailable` result rather\n * than propagating. Callers decide what an unknown means for them: a listing\n * prints it and carries on, a gate refuses to pass.\n *\n * @example\n * const result = await resolvePendingMigrations(loadAllMigrations);\n *\n * if (result.type === \"unavailable\") {\n * console.error(`Pending: unavailable — ${result.reason}`);\n * }\n */\nexport async function resolvePendingMigrations(\n loadMigrations: () => Promise<void>,\n): Promise<PendingMigrationsResult> {\n try {\n await loadMigrations();\n\n const migrations = await listPendingMigrations();\n\n return { type: \"resolved\", migrations };\n } catch (error) {\n return { type: \"unavailable\", reason: describeFailure(error) };\n }\n}\n\n/**\n * Reduce a thrown value to a single line an operator can act on. The message is\n * the actionable part — a stack trace in the middle of a listing buries the\n * executed section the reader still needs.\n */\nfunction describeFailure(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,yBACpB,gBACkC;CAClC,IAAI;EACF,MAAM,eAAe;EAIrB,OAAO;GAAE,MAAM;GAAY,kBAFF,sBAAsB;EAET;CACxC,SAAS,OAAO;EACd,OAAO;GAAE,MAAM;GAAe,QAAQ,gBAAgB,KAAK;EAAE;CAC/D;AACF;;;;;;AAOA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,iBAAiB,OACnB,OAAO,MAAM;CAGf,OAAO,OAAO,KAAK;AACrB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TranspileCache, cacheKey } from "./transpile-cache.mjs";
|
|
2
2
|
import { sourceSlug } from "./source-slug.mjs";
|
|
3
|
-
import { transformSync } from "esbuild";
|
|
4
3
|
import { readFileSync } from "node:fs";
|
|
4
|
+
import { transformSync } from "esbuild";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
//#region ../core/src/dev-server/loader/load-hook.ts
|
|
@@ -280,7 +280,7 @@ export default {
|
|
|
280
280
|
},
|
|
281
281
|
} as SocketOptions;
|
|
282
282
|
`;
|
|
283
|
-
const communicatorsConfigStub = `import { env } from "@
|
|
283
|
+
const communicatorsConfigStub = `import { env } from "@warlock.js/core";
|
|
284
284
|
import type { BrokerConfigurations, RabbitMQClientOptions } from "@warlock.js/herald";
|
|
285
285
|
|
|
286
286
|
const heraldConfigurations: BrokerConfigurations<RabbitMQClientOptions> = {
|