@warlock.js/core 4.12.0 → 4.14.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 +214 -0
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +1 -1
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/build.command.mjs +1 -1
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +1 -1
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/model.generator.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/generate/generators/repository.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs +1 -1
- package/esm/cli/commands/generate/utils/writer.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +1 -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/storage-put.command.mjs +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +2 -2
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/{cli → commands}/cli-command.d.mts +1 -1
- package/esm/commands/cli-command.d.mts.map +1 -0
- package/esm/{cli → commands}/cli-command.mjs +1 -1
- package/esm/commands/cli-command.mjs.map +1 -0
- package/esm/{cli → commands}/types.d.mts +1 -1
- package/esm/commands/types.d.mts.map +1 -0
- package/esm/config/load-config-files.mjs +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/base-connector.mjs +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +5 -3
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +0 -2
- package/esm/connectors/http-connector.mjs.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 +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -2
- package/esm/dev-server/file-event-handler.mjs +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.mjs +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.mjs +1 -1
- package/esm/dev-server/file-operations.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +3 -18
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -1
- package/esm/dev-server/layer-executor.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +2 -2
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +20 -8
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/http/build-cors-options.mjs +32 -0
- package/esm/http/build-cors-options.mjs.map +1 -0
- package/esm/http/middleware/max-body-size.middleware.d.mts +13 -5
- package/esm/http/middleware/max-body-size.middleware.d.mts.map +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs +13 -5
- package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +2 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +3 -8
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +9 -17
- package/esm/index.mjs +4 -15
- package/esm/production/production-builder.mjs +51 -3
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/router/router.d.mts +9 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +58 -5
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +24 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +2 -3
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +38 -15
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-connectors-selection.d.mts +14 -0
- package/esm/tests/test-connectors-selection.d.mts.map +1 -0
- package/esm/tests/test-connectors-selection.mjs +72 -0
- package/esm/tests/test-connectors-selection.mjs.map +1 -0
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +34 -13
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-lifecycle-state.mjs +27 -0
- package/esm/tests/test-lifecycle-state.mjs.map +1 -0
- package/esm/tests/test-setup-timeout.mjs +53 -0
- package/esm/tests/test-setup-timeout.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +64 -8
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +317 -11
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/utils/framework-vesion.mjs +1 -1
- package/esm/utils/index.d.mts +1 -0
- package/esm/utils/index.mjs +1 -0
- package/esm/utils/normalized-path.d.mts +55 -0
- package/esm/utils/normalized-path.d.mts.map +1 -0
- package/esm/{dev-server/path.mjs → utils/normalized-path.mjs} +15 -2
- package/esm/utils/normalized-path.mjs.map +1 -0
- package/esm/vite/index.d.mts +2 -0
- package/esm/vite/index.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts +1 -1
- package/esm/vite/lower-stage3-decorators.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.mjs.map +1 -1
- package/esm/warlock-config/index.mjs +1 -1
- package/esm/warlock-config/types.d.mts +34 -1
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs +1 -2
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +78 -23
- package/llms.txt +3 -3
- package/package.json +24 -12
- package/skills/lower-stage3-decorators/SKILL.md +4 -2
- package/skills/run-app/SKILL.md +6 -0
- package/skills/test-http/SKILL.md +37 -12
- package/skills/test-service/SKILL.md +167 -24
- package/skills/use-middleware/SKILL.md +5 -1
- package/esm/cli/cli-command.d.mts.map +0 -1
- package/esm/cli/cli-command.mjs.map +0 -1
- package/esm/cli/types.d.mts.map +0 -1
- package/esm/dev-server/files-orchestrator.d.mts +0 -89
- package/esm/dev-server/files-orchestrator.d.mts.map +0 -1
- package/esm/dev-server/files-watcher.d.mts +0 -69
- package/esm/dev-server/files-watcher.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts +0 -55
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts +0 -44
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts +0 -60
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts +0 -119
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts.map +0 -1
- package/esm/dev-server/health-checker/index.mjs +0 -7
- package/esm/dev-server/module-loader.d.mts +0 -63
- package/esm/dev-server/module-loader.d.mts.map +0 -1
- package/esm/dev-server/path.mjs.map +0 -1
- package/esm/tests/test-server-port-channel.d.mts +0 -27
- package/esm/tests/test-server-port-channel.d.mts.map +0 -1
- /package/esm/{cli → commands}/index.d.mts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config-files.mjs","names":[],"sources":["../../../../../../../core/src/config/load-config-files.ts"],"sourcesContent":["import { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { Path } from \"../
|
|
1
|
+
{"version":3,"file":"load-config-files.mjs","names":[],"sources":["../../../../../../../core/src/config/load-config-files.ts"],"sourcesContent":["import { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { getCertainFilesFromDirectory, getFilesFromDirectory } from \"../dev-server/utils\";\nimport { srcPath } from \"../utils\";\nimport { configManager } from \"./config-manager\";\n\n/**\n * Load config files\n * either config file names (without extensions) or true to load all config files\n */\nexport async function loadConfigFiles(config: string[] | true) {\n let relativePaths: string[] = [];\n if (config === true) {\n const configFilesAbsulatePaths = await getFilesFromDirectory(srcPath(\"config\"));\n relativePaths = configFilesAbsulatePaths.map((path) => Path.toRelative(path));\n } else {\n // define only the given config files\n\n const configFilesAbsulatePaths = await getCertainFilesFromDirectory(srcPath(\"config\"), config);\n\n relativePaths = configFilesAbsulatePaths.map((path) => Path.toRelative(path));\n }\n\n const configFiles = await Promise.all(\n relativePaths.map((relativePath) => filesOrchestrator.add(relativePath)),\n );\n\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.files);\n\n await configManager.loadAll(configFiles);\n}\n"],"mappings":";;;;;;;;;;;;AAUA,eAAsB,gBAAgB,QAAyB;CAC7D,IAAI,gBAA0B,CAAC;CAC/B,IAAI,WAAW,MAEb,iBAAgB,MADuB,sBAAsB,QAAQ,QAAQ,CAAC,EACtC,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;MAM5E,iBAAgB,MAFuB,6BAA6B,QAAQ,QAAQ,GAAG,MAAM,EAErD,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;CAG9E,MAAM,cAAc,MAAM,QAAQ,IAChC,cAAc,KAAK,iBAAiB,kBAAkB,IAAI,YAAY,CAAC,CACzE;CAEA,kBAAkB,sBAAsB,QAAQ,kBAAkB,KAAK;CAEvE,MAAM,cAAc,QAAQ,WAAW;AACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/base-connector.ts"],"sourcesContent":["import { Path } from \"../
|
|
1
|
+
{"version":3,"file":"base-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/base-connector.ts"],"sourcesContent":["import { Path } from \"../utils/normalized-path\";\nimport { ConnectorLifecyclePhase } from \"./types\";\nimport type { Connector, ConnectorName } from \"./types\";\n\n/**\n * Base Connector Class\n * Provides common functionality for all connectors\n */\nexport abstract class BaseConnector implements Connector {\n /**\n * Connector name\n */\n public abstract readonly name: ConnectorName;\n\n /**\n * Initialization priority\n */\n public abstract readonly priority: number;\n\n /**\n * Lifecycle phase. Defaults to `Early`; override to `Late` if the\n * connector consumes state user code registers at import time\n * (routes, socket listeners).\n */\n public readonly lifecyclePhase: ConnectorLifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger restart when changed\n * Use relative paths\n */\n protected abstract readonly watchedFiles: string[];\n\n /**\n * Whether the connector is currently active\n */\n protected active: boolean = false;\n\n /**\n * Check if connector is active\n */\n public isActive(): boolean {\n return this.active;\n }\n\n /**\n * Boot the connector\n */\n public async boot(): Promise<void> {\n return;\n }\n\n /**\n * Initialize the connector\n */\n public abstract start(): Promise<void>;\n\n /**\n * Restart the connector\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.start();\n }\n\n /**\n * Shutdown the connector\n */\n public abstract shutdown(): Promise<void>;\n\n /**\n * Determine if connector should restart based on changed files\n */\n public shouldRestart(changedFiles: string[]): boolean {\n // Check if any changed file matches watched files\n return changedFiles.some((file) => this.isWatchedFile(file));\n }\n\n /**\n * Check if a file is watched by this connector\n */\n protected isWatchedFile(file: string): boolean {\n const relativePath = Path.toRelative(file);\n\n return this.watchedFiles.some((watchedFile) => {\n // Exact match\n if (watchedFile === relativePath) {\n return true;\n }\n\n // Pattern match (e.g., \"config/*.ts\")\n if (watchedFile.includes(\"*\")) {\n const pattern = new RegExp(\"^\" + watchedFile.replace(/\\*/g, \".*\") + \"$\");\n return pattern.test(relativePath);\n }\n\n return false;\n });\n }\n}\n"],"mappings":";;;;;;;;AAQA,IAAsB,gBAAtB,MAAyD;;;gBA2B3B;;;;;CAK5B,AAAO,WAAoB;EACzB,OAAO,KAAK;CACd;;;;CAKA,MAAa,OAAsB,CAEnC;;;;CAUA,MAAa,UAAyB;EACpC,MAAM,KAAK,SAAS;EACpB,MAAM,KAAK,MAAM;CACnB;;;;CAUA,AAAO,cAAc,cAAiC;EAEpD,OAAO,aAAa,MAAM,SAAS,KAAK,cAAc,IAAI,CAAC;CAC7D;;;;CAKA,AAAU,cAAc,MAAuB;EAC7C,MAAM,eAAe,KAAK,WAAW,IAAI;EAEzC,OAAO,KAAK,aAAa,MAAM,gBAAgB;GAE7C,IAAI,gBAAgB,cAClB,OAAO;GAIT,IAAI,YAAY,SAAS,GAAG,GAE1B,OAAO,IADa,OAAO,MAAM,YAAY,QAAQ,OAAO,IAAI,IAAI,GACvD,CAAC,CAAC,KAAK,YAAY;GAGlC,OAAO;EACT,CAAC;CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"mappings":";;;cAgBa,iBAAA;EAAA;;;EAAA,iBAIM,UAAA;EA+BF;;;;EA8B0C;;;EAvClD,QAAA,IAAY,UAAA,EAAY,SAAA;EAmEC;;;EA1DzB,IAAA,IAAQ,SAAA;EATR;;;EAgBM,KAAA,CAAM,eAAA,GAAkB,aAAA,KAAkB,OAAA;EAPxC;;;;;;;;;EA8BF,UAAA,CAAW,KAAA,EAAO,uBAAA,GAA0B,OAAA;EAiBX;;;EAAjC,YAAA,CAAa,kBAAA,EAAoB,aAAA,KAAkB,OAAA;EAWvC;;;EAAZ,QAAA,IAAY,OAAA;EA8Ed;;;;AAA2C;;;;;;;;EAzB/C,qBAAA;AAAA;AAAA,cAyBI,iBAAA,EAAiB,iBAA0B"}
|
|
@@ -4,7 +4,6 @@ import { AccessConnector } from "./access-connector.mjs";
|
|
|
4
4
|
import { AiConnector } from "./ai-connector.mjs";
|
|
5
5
|
import { CacheConnector } from "./cache-connector.mjs";
|
|
6
6
|
import { HeraldConnector } from "./herald-connector.mjs";
|
|
7
|
-
import { devServeLog } from "../dev-server/dev-logger.mjs";
|
|
8
7
|
import { DatabaseConnector } from "./database-connector.mjs";
|
|
9
8
|
import { HttpConnector } from "./http-connector.mjs";
|
|
10
9
|
import { LoggerConnector } from "./logger-connector.mjs";
|
|
@@ -12,7 +11,7 @@ import { MailerConnector } from "./mail-connector.mjs";
|
|
|
12
11
|
import { NotificationsConnector } from "./notifications-connector.mjs";
|
|
13
12
|
import { SocketConnector } from "./socket-connector.mjs";
|
|
14
13
|
import { StorageConnector } from "./storage.connector.mjs";
|
|
15
|
-
import {
|
|
14
|
+
import { log } from "@warlock.js/logger";
|
|
16
15
|
|
|
17
16
|
//#region ../core/src/connectors/connectors-manager.ts
|
|
18
17
|
var ConnectorsManager = class {
|
|
@@ -82,8 +81,11 @@ var ConnectorsManager = class {
|
|
|
82
81
|
for (const connector of [...this.connectors].reverse()) try {
|
|
83
82
|
await connector.shutdown();
|
|
84
83
|
} catch (error) {
|
|
85
|
-
|
|
84
|
+
try {
|
|
85
|
+
await log.error("connectors", "shutdown", error, { connector: connector.name });
|
|
86
|
+
} catch {}
|
|
86
87
|
}
|
|
88
|
+
await log.flush();
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
91
|
* Shutdown connectors on process kill
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import { Application } from \"../application\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AccessConnector } from \"./access-connector\";\r\nimport { AiConnector } from \"./ai-connector\";\r\nimport { CacheConnector } from \"./cache-connector\";\r\nimport { DatabaseConnector } from \"./database-connector\";\r\nimport { HeraldConnector } from \"./herald-connector\";\r\nimport { HttpConnector } from \"./http-connector\";\r\nimport { LoggerConnector } from \"./logger-connector\";\r\nimport { MailerConnector } from \"./mail-connector\";\r\nimport { NotificationsConnector } from \"./notifications-connector\";\r\nimport { SocketConnector } from \"./socket-connector\";\r\nimport { StorageConnector } from \"./storage.connector\";\r\nimport { ConnectorLifecyclePhase } from \"./types\";\r\nimport type { Connector, ConnectorName } from \"./types\";\r\n\r\nexport class ConnectorsManager {\r\n /**\r\n * Connectors list\r\n */\r\n private readonly connectors: Connector[] = [];\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor() {\r\n this.register(new LoggerConnector());\r\n this.register(new MailerConnector());\r\n this.register(new HttpConnector());\r\n this.register(new DatabaseConnector());\r\n this.register(new HeraldConnector());\r\n this.register(new CacheConnector());\r\n this.register(new StorageConnector());\r\n this.register(new SocketConnector());\r\n this.register(new NotificationsConnector());\r\n this.register(new AccessConnector());\r\n this.register(new AiConnector());\r\n }\r\n\r\n /**\r\n * Register a connector\r\n */\r\n public register(...connectors: Connector[]): void {\r\n this.connectors.push(...connectors);\r\n // sort connectors by priority\r\n this.connectors.sort((a, b) => a.priority - b.priority);\r\n }\r\n\r\n /**\r\n * Get all connectors\r\n */\r\n public list(): Connector[] {\r\n return this.connectors;\r\n }\r\n\r\n /**\r\n * start all connectors\r\n */\r\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\r\n const connectorsList = connectorsNames\r\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\r\n : this.connectors;\r\n\r\n for (const connector of connectorsList) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of connectorsList) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors in the given lifecycle phase.\r\n *\r\n * The production builder and dev preload split startup around app\r\n * code: early phase before app imports, late phase after. Within a\r\n * phase, all connectors `boot()` first, then all `start()`, so\r\n * cross-connector wiring inside the phase still works (e.g. socket\r\n * reads http's instance during its own boot).\r\n */\r\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\r\n const phaseConnectors = this.connectors.filter(\r\n (connector) => connector.lifecyclePhase === phase,\r\n );\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors except the given ones\r\n */\r\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\r\n await this.start(\r\n this.connectors\r\n .filter((connector) => !excludedConnectors.includes(connector.name))\r\n .map((connector) => connector.name),\r\n );\r\n }\r\n\r\n /**\r\n * Shutdown all connectors\r\n */\r\n public async shutdown(): Promise<void> {\r\n // App-owned teardown runs first, while every connector (db, cache, http) is\r\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\r\n await Application.runShutdownHooks();\r\n\r\n // Shut down connectors in reverse priority order. Copy the list first —\r\n // `reverse()` mutates in place, so reversing the live array would corrupt\r\n // the order on a second shutdown pass.\r\n for (const connector of [...this.connectors].reverse()) {\r\n try {\r\n await connector.shutdown();\r\n } catch (error) {\r\n try {\r\n // Awaited deliberately: the caller (`shutdownOnProcessKill`) calls\r\n // `process.exit(0)` as soon as this resolves, so an un-awaited log is\r\n // a log that never happens.\r\n await log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\r\n } catch {\r\n // Reporting a failure must never become a worse failure than the one\r\n // being reported. `Logger.log()` fans out to channels without\r\n // isolating them, so a channel that throws synchronously rejects this\r\n // call — and this call sits INSIDE the catch, so that rejection\r\n // escapes `shutdown()` entirely: the remaining connectors are never\r\n // torn down and `process.exit(0)` never runs, leaving the process\r\n // alive on the handles they still hold.\r\n //\r\n // Swallowed rather than re-reported: the only channel we could report\r\n // it through is the one that just threw.\r\n }\r\n }\r\n }\r\n\r\n // `Logger.log()` hands each entry to `channel.log()` without awaiting it, so\r\n // awaiting the calls above is not enough on its own — a buffered or async\r\n // channel (file, Sentry) still loses the entry when the process exits.\r\n // Drained once here rather than per failure: shutdown is exactly when\r\n // buffered logs must reach their destination, and re-draining every channel\r\n // once per failed connector buys nothing.\r\n await log.flush();\r\n }\r\n\r\n /**\r\n * Shutdown connectors on process kill\r\n *\r\n * Registers signal handlers that run an idempotent graceful shutdown:\r\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\r\n * - SIGTERM: Termination signal (Unix primarily)\r\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\r\n *\r\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\r\n * await async teardown (the process exits once the callback returns), so\r\n * shutdown is driven entirely by the signal handlers above.\r\n */\r\n public shutdownOnProcessKill(): void {\r\n let isShuttingDown = false;\r\n\r\n const gracefulShutdown = async (signal: string) => {\r\n if (isShuttingDown) return;\r\n isShuttingDown = true;\r\n\r\n console.log(`\\nExiting...`);\r\n await this.shutdown();\r\n process.exit(0);\r\n };\r\n\r\n // Unix signals\r\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\r\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\r\n\r\n // Windows-specific: handle when process is about to exit\r\n // This is more reliable on Windows for spawned child processes\r\n if (process.platform === \"win32\") {\r\n // Handle Ctrl+C on Windows specifically\r\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\r\n }\r\n }\r\n}\r\n\r\nexport const connectorsManager = new ConnectorsManager();\r\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,oBAAb,MAA+B;;;;CAS7B,AAAO,cAAc;oBALsB,CAAC;EAM1C,KAAK,SAAS,IAAI,gBAAgB,CAAC;EACnC,KAAK,SAAS,IAAI,gBAAgB,CAAC;EACnC,KAAK,SAAS,IAAI,cAAc,CAAC;EACjC,KAAK,SAAS,IAAI,kBAAkB,CAAC;EACrC,KAAK,SAAS,IAAI,gBAAgB,CAAC;EACnC,KAAK,SAAS,IAAI,eAAe,CAAC;EAClC,KAAK,SAAS,IAAI,iBAAiB,CAAC;EACpC,KAAK,SAAS,IAAI,gBAAgB,CAAC;EACnC,KAAK,SAAS,IAAI,uBAAuB,CAAC;EAC1C,KAAK,SAAS,IAAI,gBAAgB,CAAC;EACnC,KAAK,SAAS,IAAI,YAAY,CAAC;CACjC;;;;CAKA,AAAO,SAAS,GAAG,YAA+B;EAChD,KAAK,WAAW,KAAK,GAAG,UAAU;EAElC,KAAK,WAAW,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CACxD;;;;CAKA,AAAO,OAAoB;EACzB,OAAO,KAAK;CACd;;;;CAKA,MAAa,MAAM,iBAAkD;EACnE,MAAM,iBAAiB,kBACnB,KAAK,WAAW,QAAQ,cAAc,gBAAgB,SAAS,UAAU,IAAI,CAAC,IAC9E,KAAK;EAET,KAAK,MAAM,aAAa,gBACtB,MAAM,UAAU,KAAK;EAGvB,KAAK,MAAM,aAAa,gBACtB,MAAM,UAAU,MAAM;CAE1B;;;;;;;;;;CAWA,MAAa,WAAW,OAA+C;EACrE,MAAM,kBAAkB,KAAK,WAAW,QACrC,cAAc,UAAU,mBAAmB,KAC9C;EAEA,KAAK,MAAM,aAAa,iBACtB,MAAM,UAAU,KAAK;EAGvB,KAAK,MAAM,aAAa,iBACtB,MAAM,UAAU,MAAM;CAE1B;;;;CAKA,MAAa,aAAa,oBAAoD;EAC5E,MAAM,KAAK,MACT,KAAK,WACF,QAAQ,cAAc,CAAC,mBAAmB,SAAS,UAAU,IAAI,CAAC,CAAC,CACnE,KAAK,cAAc,UAAU,IAAI,CACtC;CACF;;;;CAKA,MAAa,WAA0B;EAGrC,MAAM,YAAY,iBAAiB;EAKnC,KAAK,MAAM,aAAa,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC,QAAQ,GACnD,IAAI;GACF,MAAM,UAAU,SAAS;EAC3B,SAAS,OAAO;GACd,IAAI;IAIF,MAAM,IAAI,MAAM,cAAc,YAAY,OAAgB,EAAE,WAAW,UAAU,KAAK,CAAC;GACzF,QAAQ,CAWR;EACF;EASF,MAAM,IAAI,MAAM;CAClB;;;;;;;;;;;;;CAcA,AAAO,wBAA8B;EACnC,IAAI,iBAAiB;EAErB,MAAM,mBAAmB,OAAO,WAAmB;GACjD,IAAI,gBAAgB;GACpB,iBAAiB;GAEjB,QAAQ,IAAI,cAAc;GAC1B,MAAM,KAAK,SAAS;GACpB,QAAQ,KAAK,CAAC;EAChB;EAGA,QAAQ,GAAG,gBAAgB,iBAAiB,QAAQ,CAAC;EACrD,QAAQ,GAAG,iBAAiB,iBAAiB,SAAS,CAAC;EAIvD,IAAI,QAAQ,aAAa,SAEvB,QAAQ,GAAG,gBAAgB,iBAAiB,QAAQ,CAAC;CAEzD;AACF;AAEA,MAAa,oBAAoB,IAAI,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"http-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"mappings":";;;;;;;AAqCA;;cAAa,aAAA,SAAsB,aAAA;EAAA,SACjB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,IAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EAmBb;;;;;EAAA,mBAZE,YAAA;EAV2B;;;EAAA,UAepC,IAAA,GAAO,eAAA;EAbO;;;;;EAoBX,IAAA,IAAI,OAAA;EAPP;;;;;;;EAAA,QA0CF,oBAAA;EAgFgB;;;;;EAhDX,KAAA,IAAS,OAAA;EAqFqB;;;;;EArC9B,OAAA,IAAW,OAAA;;;;EASX,QAAA,IAAY,OAAA;;;;;;EA4BlB,aAAA,CAAc,YAAA;AAAA"}
|
|
@@ -8,7 +8,6 @@ import "../application/index.mjs";
|
|
|
8
8
|
import { health } from "../http/health.mjs";
|
|
9
9
|
import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
|
|
10
10
|
import { BaseConnector } from "./base-connector.mjs";
|
|
11
|
-
import { devLogError } from "../dev-server/dev-logger.mjs";
|
|
12
11
|
import config from "@mongez/config";
|
|
13
12
|
import { log } from "@warlock.js/logger";
|
|
14
13
|
import { colors } from "@mongez/copper";
|
|
@@ -98,7 +97,6 @@ var HttpConnector = class extends BaseConnector {
|
|
|
98
97
|
const baseUrl = config.get("app.baseUrl");
|
|
99
98
|
log.success(`http`, "connection", `Server ready at ${baseUrl}`);
|
|
100
99
|
} catch (error) {
|
|
101
|
-
devLogError("Error while starting http server", error);
|
|
102
100
|
await log.fatal("http", "connection", error);
|
|
103
101
|
await log.flush();
|
|
104
102
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
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":";;;;;;;;;;;;;;;;AAeA,SAAS,iBAAiB,aAA0B;CAClD,QAAQ,aAAR;EACE,KAAK,eACH,OAAO,OAAO,cAAc,WAAW;EACzC,KAAK,QACH,OAAO,OAAO,aAAa,WAAW;EACxC,KAAK,cACH,OAAO,OAAO,YAAY,WAAW;EACvC,SACE,OAAO,OAAO,MAAM,WAAW;CACnC;AACF;;;;;AAMA,MAAM,2BAA2B;;;;;AAMjC,IAAa,gBAAb,cAAmC,cAAc;;;cACxB;;;sBASW,CAAC,sBAAsB,qBAAqB;;;;;;;CAY9E,MAAa,OAAO;EAClB,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YAAY;EAEjB,MAAM,OAAO,WAAW;EACxB,IAAI,KACF,QACA,cACA,gCAAgC,KAAK,MAAM,iBAAiB,YAAY,WAAW,EAAE,MACvF;EAEA,KAAK,OAAO,gBAAgB,WAAW,aAAa;EAEpD,UAAU,IAAI,eAAe,KAAK,IAAI;EAEtC,MAAM,oBAAoB,KAAK,IAAI;EAKnC,WAHgB,OAAO,IAAI,aAGV,CAAC;EAIlB,KAAK,qBAAqB;CAC5B;;;;;;;;CASA,AAAQ,uBAA6B;EACnC,IAAI,CAAC,KAAK,MACR;EAGF,MAAM,eAAe,OAAO,IAAI,aAAa;EAE7C,IAAI,cAAc,YAAY,OAC5B;EAGF,MAAM,eAAe,cAAc,QAAQ;EAC3C,MAAM,gBAAgB,cAAc,iBAAiB;EAErD,KAAK,KAAK,IAAI,cAAc,OAAO,UAAe,UAAe;GAC/D,MAAM,SAAS,OAAO,SAAS;GAE/B,OAAO,MAAM,KAAK,OAAO,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM;EACnE,CAAC;EAED,KAAK,KAAK,IAAI,eAAe,OAAO,UAAe,UAAe;GAChE,MAAM,SAAS,MAAM,OAAO,UAAU;GAEtC,OAAO,MAAM,KAAK,OAAO,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM;EACnE,CAAC;CACH;;;;;;CAOA,MAAa,QAAuB;EAClC,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,cAAc,CAAC,KAAK,MAAM;EAE/B,IAAI,YAAY,oBAAoB,eAClC,OAAO,cAAc,KAAK,IAAI;OAE9B,OAAO,KAAK,KAAK,IAAI;EAOvB,OAAO,+BAA+B,OAAO,WAAW,CAAC;EACzD,IAAI,KAAK,QAAQ,UAAU,GAAG,OAAO,WAAW,EAAE,qBAAqB;EAEvE,IAAI;GAEF,MAAM,KAAK,KAAK,OAAO;IACrB,MAAM,WAAW;IACjB,MAAM,WAAW,QAAQ;GAC3B,CAAC;GAED,YAAY,cAAc,WAAW,IAAI;GAEzC,MAAM,UAAU,OAAO,IAAI,aAAa;GAExC,IAAI,QAAQ,QAAQ,cAAc,mBAAmB,SAAS;EAChE,SAAS,OAAO;GACd,YAAY,oCAAoC,KAAK;GAMrD,MAAM,IAAI,MAAM,QAAQ,cAAc,KAAK;GAC3C,MAAM,IAAI,MAAM;GAEhB,QAAQ,KAAK,CAAC;EAChB;EAEA,KAAK,SAAS;CAChB;;;;;;CAOA,MAAa,UAAyB;EACpC,MAAM,KAAK,SAAS;EACpB,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,MAAM;CACnB;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,MAAM,SAAS,cAAc;EAE7B,IAAI,QAAQ;GACV,MAAM,UAAU,OAAO,IAAI,iCAAiC,wBAAwB;GAGpF,IAAI,CAAC,MAFiB,uBAAuB,QAAQ,OAAO,GAG1D,IAAI,KACF,QACA,YACA,2CAA2C,QAAQ,mBACrD;EAEJ;EAEA,KAAK,SAAS;CAChB;;;;;;CAOA,AAAO,cAAc,cAAiC;EAEpD,OAAO,aAAa,MAAM,SAAS;GACjC,MAAM,eAAe,KAAK,QAAQ,OAAO,GAAG;GAC5C,OAAO,KAAK,aAAa,SAAS,YAAY;EAChD,CAAC;CACH;AACF"}
|
|
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 { 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 // A failed listen()/port-bind at boot means the app can't serve — fatal.\r\n // `log.fatal` reaches Sentry/file; `await log.flush()` drains buffered and\r\n // async channels before the process exits.\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { createDatabase } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { CommandActionData } from \"../
|
|
1
|
+
{"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { createDatabase } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { config } from \"../config\";\r\n\r\nexport async function createDatabaseAction(command: CommandActionData) {\r\n const name = command.args[0];\r\n const { connection = \"default\" } = command.options;\r\n\r\n const connectionName =\r\n connection === \"default\" || connection === true ? undefined : String(connection);\r\n\r\n let databaseName = name;\r\n\r\n if (!databaseName) {\r\n if (connection === \"default\") {\r\n databaseName = config.get(\"database\")?.database;\r\n }\r\n }\r\n\r\n if (!databaseName) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n \"Database name is required. Please provide a name or configure it in the database config.\",\r\n );\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"create\", `Creating database ${colors.cyan(databaseName)}...`);\r\n\r\n try {\r\n const { created } = await createDatabase(databaseName, { connection: connectionName });\r\n\r\n if (created) {\r\n log.success(\r\n \"database\",\r\n \"create\",\r\n `Database ${colors.green(databaseName)} created successfully.`,\r\n );\r\n } else {\r\n log.warn(\"database\", \"create\", `Database ${colors.yellow(databaseName)} already exists.`);\r\n }\r\n } catch (error: any) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n `Failed to create database ${colors.red(databaseName)}: ${error.message}`,\r\n );\r\n }\r\n}\r\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB,SAA4B;CACrE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,MAAM,iBACJ,eAAe,aAAa,eAAe,OAAO,SAAY,OAAO,UAAU;CAEjF,IAAI,eAAe;CAEnB,IAAI,CAAC,cACH;MAAI,eAAe,WACjB,eAAe,OAAO,IAAI,UAAU,CAAC,EAAE;CACzC;CAGF,IAAI,CAAC,cAAc;EACjB,IAAI,MACF,YACA,UACA,0FACF;EACA;CACF;CAEA,IAAI,KAAK,YAAY,UAAU,qBAAqB,OAAO,KAAK,YAAY,EAAE,IAAI;CAElF,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,eAAe,cAAc,EAAE,YAAY,eAAe,CAAC;EAErF,IAAI,SACF,IAAI,QACF,YACA,UACA,YAAY,OAAO,MAAM,YAAY,EAAE,uBACzC;OAEA,IAAI,KAAK,YAAY,UAAU,YAAY,OAAO,OAAO,YAAY,EAAE,iBAAiB;CAE5F,SAAS,OAAY;EACnB,IAAI,MACF,YACA,UACA,6BAA6B,OAAO,IAAI,YAAY,EAAE,IAAI,MAAM,SAClE;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drop-tables-action.mjs","names":[],"sources":["../../../../../../../core/src/database/drop-tables-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { dataSourceRegistry, dropAllTables } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { confirm } from \"../cli/commands/generate/utils/prompt\";\r\nimport { CommandActionData } from \"../
|
|
1
|
+
{"version":3,"file":"drop-tables-action.mjs","names":[],"sources":["../../../../../../../core/src/database/drop-tables-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { dataSourceRegistry, dropAllTables } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { confirm } from \"../cli/commands/generate/utils/prompt\";\r\nimport { CommandActionData } from \"../commands/types\";\r\n\r\nexport async function dropTablesAction(command: CommandActionData) {\r\n const { force } = command.options;\r\n\r\n if (force) {\r\n const { dropped } = await dropAllTables();\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n return;\r\n }\r\n\r\n // Preview phase — we keep direct driver access so we can show per-table\r\n // row counts before asking for confirmation. The actual drop still goes\r\n // through the Cascade Operations API.\r\n const driver = dataSourceRegistry.get().driver;\r\n const tables = await driver.blueprint.listTables();\r\n\r\n if (tables.length === 0) {\r\n log.warn(\"database\", \"drop\", \"No tables found in the database.\");\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"drop\", `Found ${colors.yellowBright(tables.length)} tables:`);\r\n\r\n for (const table of tables) {\r\n const count = await driver.queryBuilder(table).count();\r\n console.log(` - ${colors.cyan(table)}: ${colors.green(count)} rows`);\r\n }\r\n\r\n const confirmed = await confirm(\r\n `Are you sure you want to drop all ${colors.red(tables.length)} tables?`,\r\n );\r\n\r\n if (!confirmed) {\r\n log.info(\"database\", \"drop\", \"Operation cancelled.\");\r\n return;\r\n }\r\n\r\n const { dropped } = await dropAllTables();\r\n\r\n log.success(\r\n \"database\",\r\n \"drop\",\r\n `Dropped ${colors.yellowBright(dropped)} tables successfully.`,\r\n );\r\n}\r\n"],"mappings":";;;;;;AAMA,eAAsB,iBAAiB,SAA4B;CACjE,MAAM,EAAE,UAAU,QAAQ;CAE1B,IAAI,OAAO;EACT,MAAM,EAAE,YAAY,MAAM,cAAc;EACxC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;EACA;CACF;CAKA,MAAM,SAAS,mBAAmB,IAAI,CAAC,CAAC;CACxC,MAAM,SAAS,MAAM,OAAO,UAAU,WAAW;CAEjD,IAAI,OAAO,WAAW,GAAG;EACvB,IAAI,KAAK,YAAY,QAAQ,kCAAkC;EAC/D;CACF;CAEA,IAAI,KAAK,YAAY,QAAQ,SAAS,OAAO,aAAa,OAAO,MAAM,EAAE,SAAS;CAElF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,MAAM,OAAO,aAAa,KAAK,CAAC,CAAC,MAAM;EACrD,QAAQ,IAAI,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,OAAO,MAAM,KAAK,EAAE,MAAM;CACtE;CAMA,IAAI,CAAC,MAJmB,QACtB,qCAAqC,OAAO,IAAI,OAAO,MAAM,EAAE,SACjE,GAEgB;EACd,IAAI,KAAK,YAAY,QAAQ,sBAAsB;EACnD;CACF;CAEA,MAAM,EAAE,YAAY,MAAM,cAAc;CAExC,IAAI,QACF,YACA,QACA,WAAW,OAAO,aAAa,OAAO,EAAE,sBAC1C;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
1
2
|
import { srcPath } from "../utils/paths.mjs";
|
|
2
3
|
import "../utils/index.mjs";
|
|
3
|
-
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";
|
|
@@ -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\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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"}
|
|
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 \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
1
2
|
import { srcPath } from "../utils/paths.mjs";
|
|
2
3
|
import "../utils/index.mjs";
|
|
3
|
-
import { Path } from "../dev-server/path.mjs";
|
|
4
4
|
import { getFilesFromDirectory } from "../dev-server/utils.mjs";
|
|
5
5
|
import { filesOrchestrator } from "../dev-server/files-orchestrator.mjs";
|
|
6
6
|
import { seedRecordsTableName, seedsTableName } from "./seeds/utils.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed-command-action.mjs","names":["runInTransaction"],"sources":["../../../../../../../core/src/database/seed-command-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n DataSource,\n dataSourceRegistry,\n transaction as runInTransaction,\n} from \"@warlock.js/cascade\";\nimport { CommandActionData } from \"../
|
|
1
|
+
{"version":3,"file":"seed-command-action.mjs","names":["runInTransaction"],"sources":["../../../../../../../core/src/database/seed-command-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n DataSource,\r\n dataSourceRegistry,\r\n transaction as runInTransaction,\r\n} from \"@warlock.js/cascade\";\r\nimport { CommandActionData } from \"../commands/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { Seeder } from \"./seeds/seeder\";\r\nimport { SeedersManager } from \"./seeds/seeders.manager\";\r\nimport { SeedClock, SeedRecordRef } from \"./seeds/types\";\r\nimport { seedRecordsTableName, seedsTableName } from \"./seeds/utils\";\r\n\r\n/**\r\n * Programmatic-only overrides for {@link seedCommandAction}.\r\n *\r\n * Not derived from CLI flags (the CLI passes only {@link CommandActionData}),\r\n * so a back-fill script or a test can inject behaviour the command line can't\r\n * express.\r\n */\r\nexport type SeedCommandOverrides = {\r\n /**\r\n * Clock the run reads for the `now` handed to each seeder and for the\r\n * seeds-log metadata timestamps. Defaults to `() => new Date()`. Forwarded to\r\n * {@link SeedersManager}'s `clock` option.\r\n */\r\n clock?: SeedClock;\r\n};\r\n\r\nasync function clearAllTables(datasource: DataSource) {\r\n const tables = await datasource.driver.blueprint.listTables();\r\n\r\n for (const table of tables) {\r\n await datasource.driver.truncateTable(table, { cascade: true });\r\n }\r\n}\r\n\r\ntype SeedRecordRow = SeedRecordRef & { id: number };\r\n\r\n/**\r\n * Undo seeded data by deleting the records a seeder tracked via `track`.\r\n *\r\n * Reads `seed_records` (all seeders, or a single `seederName`), then deletes\r\n * the referenced records in REVERSE seed-order and, within each seed, in\r\n * reverse insertion-order. Insertion id is strictly increasing across the\r\n * whole run, so deleting by descending `id` satisfies both at once. The whole\r\n * operation runs in a single transaction, and the matching seeds-log rows are\r\n * reset afterwards so `once: true` seeds re-run on the next `warlock seed`.\r\n *\r\n * @returns the number of tracked records deleted.\r\n */\r\nexport async function dropSeedRecords(\r\n datasource: DataSource,\r\n seederName?: string,\r\n): Promise<number> {\r\n const driver = datasource.driver;\r\n\r\n if (!(await driver.blueprint.tableExists(seedRecordsTableName))) {\r\n return 0;\r\n }\r\n\r\n const query = driver.queryBuilder<SeedRecordRow>(seedRecordsTableName);\r\n\r\n if (seederName) {\r\n query.where(\"seeder\", seederName);\r\n }\r\n\r\n const rows = await query.get();\r\n\r\n if (rows.length === 0) {\r\n return 0;\r\n }\r\n\r\n // Descending insertion id == reverse seed-order + reverse within-seed order.\r\n const ordered = [...rows].sort((a, b) => b.id - a.id);\r\n\r\n await runInTransaction(async () => {\r\n for (const row of ordered) {\r\n await driver.deleteMany(row.table, { id: row.recordId });\r\n }\r\n\r\n // Clear the tracked refs we just acted on.\r\n if (seederName) {\r\n await driver.deleteMany(seedRecordsTableName, { seeder: seederName });\r\n } else {\r\n await driver.deleteMany(seedRecordsTableName, {});\r\n }\r\n\r\n // Reset the seeds-log rows so `once: true` seeds re-run.\r\n const seederNames = seederName\r\n ? [seederName]\r\n : [...new Set(ordered.map((row) => row.seeder))];\r\n\r\n for (const name of seederNames) {\r\n await driver.deleteMany(seedsTableName, { name });\r\n }\r\n });\r\n\r\n return ordered.length;\r\n}\r\n\r\n/**\r\n * Run database seeds.\r\n *\r\n * @example\r\n * ```ts\r\n * import { seedCommandAction } from \"@warlock.js/core\";\r\n *\r\n * await seedCommandAction({\r\n * command: \"seed\",\r\n * options: { fresh: true, list: false }\r\n * });\r\n * ```\r\n *\r\n * @param overrides Programmatic-only overrides (e.g. an injected `clock`); the\r\n * CLI never passes this, so the command-line signature stays unchanged.\r\n */\r\nexport async function seedCommandAction(\r\n options: CommandActionData,\r\n overrides?: SeedCommandOverrides,\r\n) {\r\n const { path, fresh, transaction, list, drop } = options.options;\r\n\r\n const datasource = dataSourceRegistry.get();\r\n\r\n if (drop) {\r\n // `--drop` (boolean) undoes every tracked record; `--drop=<name>` scopes to\r\n // a single seeder.\r\n const seederName = typeof drop === \"string\" ? drop : undefined;\r\n\r\n const deleted = await dropSeedRecords(datasource, seederName);\r\n\r\n const scope = seederName ? colors.cyan(seederName) : \"all seeders\";\r\n console.log(\r\n `🗑️ Dropped ${colors.redBright(deleted)} tracked record(s) for ${scope}; matching seeds-log entries reset.`,\r\n );\r\n return;\r\n }\r\n\r\n if (fresh) {\r\n await clearAllTables(datasource);\r\n }\r\n\r\n if (list) {\r\n const seedFiles = await listSeedsFiles();\r\n\r\n if (seedFiles.length === 0) {\r\n console.log(\"No seeds found.\");\r\n return;\r\n }\r\n\r\n const seedersManager = new SeedersManager();\r\n\r\n seedersManager.register(...seedFiles);\r\n\r\n const seeds = seedersManager.sort().seeders.map((seed) => {\r\n return {\r\n name: seed.name,\r\n order: seed.order,\r\n enabled: seed.enabled,\r\n };\r\n });\r\n\r\n console.table(seeds);\r\n\r\n console.log(\r\n `Total Seeds: ${colors.blueBright(seeds.length)}, enabled: ${colors.greenBright(seeds.filter((seed) => seed.enabled !== false).length)}, disabled: ${colors.redBright(seeds.filter((seed) => seed.enabled === false).length)}`,\r\n );\r\n return;\r\n }\r\n\r\n const seeds = path\r\n ? [await loadSeedFile(Path.toAbsolute(path as string))]\r\n : await listSeedsFiles();\r\n\r\n const seedersManager = new SeedersManager({ clock: overrides?.clock });\r\n\r\n seedersManager.register(...seeds);\r\n\r\n await seedersManager.run(transaction as boolean);\r\n}\r\n\r\nasync function listSeedsFiles() {\r\n const seedsFiles = await getFilesFromDirectory(srcPath(\"app\"), \"*/seeds/*.ts\");\r\n\r\n const seeds = [];\r\n\r\n for (const seedFile of seedsFiles) {\r\n const seed = await loadSeedFile(seedFile);\r\n seeds.push(seed);\r\n }\r\n\r\n return seeds;\r\n}\r\n\r\nasync function loadSeedFile(absPath: string): Promise<Seeder> {\r\n const relativePath = Path.toRelative(absPath);\r\n const seedImport = await filesOrchestrator.load<{ default: Seeder }>(relativePath);\r\n\r\n if (!seedImport || !seedImport.default) {\r\n throw new Error(`Seeder file ${relativePath} does not export a default seeder.`);\r\n }\r\n\r\n return seedImport.default;\r\n}\r\n"],"mappings":";;;;;;;;;;;AAgCA,eAAe,eAAe,YAAwB;CACpD,MAAM,SAAS,MAAM,WAAW,OAAO,UAAU,WAAW;CAE5D,KAAK,MAAM,SAAS,QAClB,MAAM,WAAW,OAAO,cAAc,OAAO,EAAE,SAAS,KAAK,CAAC;AAElE;;;;;;;;;;;;;AAgBA,eAAsB,gBACpB,YACA,YACiB;CACjB,MAAM,SAAS,WAAW;CAE1B,IAAI,CAAE,MAAM,OAAO,UAAU,2BAAgC,GAC3D,OAAO;CAGT,MAAM,QAAQ,OAAO,aAA4B,oBAAoB;CAErE,IAAI,YACF,MAAM,MAAM,UAAU,UAAU;CAGlC,MAAM,OAAO,MAAM,MAAM,IAAI;CAE7B,IAAI,KAAK,WAAW,GAClB,OAAO;CAIT,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CAEpD,MAAMA,YAAiB,YAAY;EACjC,KAAK,MAAM,OAAO,SAChB,MAAM,OAAO,WAAW,IAAI,OAAO,EAAE,IAAI,IAAI,SAAS,CAAC;EAIzD,IAAI,YACF,MAAM,OAAO,WAAW,sBAAsB,EAAE,QAAQ,WAAW,CAAC;OAEpE,MAAM,OAAO,WAAW,sBAAsB,CAAC,CAAC;EAIlD,MAAM,cAAc,aAChB,CAAC,UAAU,IACX,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC;EAEjD,KAAK,MAAM,QAAQ,aACjB,MAAM,OAAO,WAAW,gBAAgB,EAAE,KAAK,CAAC;CAEpD,CAAC;CAED,OAAO,QAAQ;AACjB;;;;;;;;;;;;;;;;;AAkBA,eAAsB,kBACpB,SACA,WACA;CACA,MAAM,EAAE,MAAM,OAAO,aAAa,MAAM,SAAS,QAAQ;CAEzD,MAAM,aAAa,mBAAmB,IAAI;CAE1C,IAAI,MAAM;EAGR,MAAM,aAAa,OAAO,SAAS,WAAW,OAAO;EAErD,MAAM,UAAU,MAAM,gBAAgB,YAAY,UAAU;EAE5D,MAAM,QAAQ,aAAa,OAAO,KAAK,UAAU,IAAI;EACrD,QAAQ,IACN,gBAAgB,OAAO,UAAU,OAAO,EAAE,yBAAyB,MAAM,oCAC3E;EACA;CACF;CAEA,IAAI,OACF,MAAM,eAAe,UAAU;CAGjC,IAAI,MAAM;EACR,MAAM,YAAY,MAAM,eAAe;EAEvC,IAAI,UAAU,WAAW,GAAG;GAC1B,QAAQ,IAAI,iBAAiB;GAC7B;EACF;EAEA,MAAM,iBAAiB,IAAI,eAAe;EAE1C,eAAe,SAAS,GAAG,SAAS;EAEpC,MAAM,QAAQ,eAAe,KAAK,CAAC,CAAC,QAAQ,KAAK,SAAS;GACxD,OAAO;IACL,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,SAAS,KAAK;GAChB;EACF,CAAC;EAED,QAAQ,MAAM,KAAK;EAEnB,QAAQ,IACN,gBAAgB,OAAO,WAAW,MAAM,MAAM,EAAE,aAAa,OAAO,YAAY,MAAM,QAAQ,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,MAAM,EAAE,cAAc,OAAO,UAAU,MAAM,QAAQ,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,MAAM,GAC7N;EACA;CACF;CAEA,MAAM,QAAQ,OACV,CAAC,MAAM,aAAa,KAAK,WAAW,IAAc,CAAC,CAAC,IACpD,MAAM,eAAe;CAEzB,MAAM,iBAAiB,IAAI,eAAe,EAAE,OAAO,WAAW,MAAM,CAAC;CAErE,eAAe,SAAS,GAAG,KAAK;CAEhC,MAAM,eAAe,IAAI,WAAsB;AACjD;AAEA,eAAe,iBAAiB;CAC9B,MAAM,aAAa,MAAM,sBAAsB,QAAQ,KAAK,GAAG,cAAc;CAE7E,MAAM,QAAQ,CAAC;CAEf,KAAK,MAAM,YAAY,YAAY;EACjC,MAAM,OAAO,MAAM,aAAa,QAAQ;EACxC,MAAM,KAAK,IAAI;CACjB;CAEA,OAAO;AACT;AAEA,eAAe,aAAa,SAAkC;CAC5D,MAAM,eAAe,KAAK,WAAW,OAAO;CAC5C,MAAM,aAAa,MAAM,kBAAkB,KAA0B,YAAY;CAEjF,IAAI,CAAC,cAAc,CAAC,WAAW,SAC7B,MAAM,IAAI,MAAM,eAAe,aAAa,mCAAmC;CAGjF,OAAO,WAAW;AACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\
|
|
1
|
+
{"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport dayjs from \"dayjs\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Dev server logger — Vite-style formatting helpers.\n */\n\nfunction timestamp(): string {\n return colors.dim(`${dayjs().format(\"HH:mm:ss A\")}`);\n}\n\nexport function devLog(message: string) {\n console.log(`${timestamp()} ${message}`);\n}\n\nexport function devLogSuccess(message: string) {\n console.log(`${timestamp()} ${colors.green(\"✓\")} ${colors.green(message)}`);\n}\n\n/**\n * Colourise a stack trace so the eye lands on *your* code.\n *\n * - Error header (`ReferenceError: x is not defined`) → bold red.\n * - Frames in project `src/` → highlighted: a green `›` pointer, yellow\n * function name, cyan relative path, dim `:line:col`. These are almost\n * always where the bug is.\n * - Framework (`@warlock.js`), `node_modules`, and `node:` internal frames\n * → dimmed and relativised. Still there for context, just out of the way.\n *\n * Source maps are enabled in dev, so the paths/lines here are already the\n * original `.ts` locations — this only changes how they're painted.\n */\nexport function formatErrorStack(stack: string): string {\n const frame = /^(\\s*)at (.+?) \\((.+):(\\d+):(\\d+)\\)$/;\n const bareFrame = /^(\\s*)at (.+):(\\d+):(\\d+)$/;\n let headerDone = false;\n\n return stack\n .split(\"\\n\")\n .map(line => {\n const withFn = line.match(frame);\n const bare = line.match(bareFrame);\n\n if (!withFn && !bare) {\n // Header line(s) before the first frame.\n const painted = headerDone ? colors.dim(line) : colors.bold(colors.red(line));\n return painted;\n }\n\n headerDone = true;\n\n const fn = withFn ? withFn[2] : \"\";\n const file = withFn ? withFn[3] : bare![2];\n const lineNo = withFn ? withFn[4] : bare![3];\n const col = withFn ? withFn[5] : bare![4];\n\n const isNodeInternal = file.startsWith(\"node:\");\n const isDep = file.includes(\"node_modules\");\n const isFramework = /[\\\\/]@warlock\\.js[\\\\/]/.test(file);\n const isUserCode = !isNodeInternal && !isDep && !isFramework;\n\n const rel = isNodeInternal ? file : Path.toRelative(file);\n const loc = `:${lineNo}:${col}`;\n\n if (isUserCode) {\n return (\n ` ${colors.green(\"›\")} ${colors.dim(\"at\")} ` +\n `${colors.yellow(fn || \"<anonymous>\")} ` +\n `${colors.cyan(rel)}${colors.dim(loc)}`\n );\n }\n\n const label = fn ? `at ${fn} ${rel}${loc}` : `at ${rel}${loc}`;\n return ` ${colors.dim(label)}`;\n })\n .join(\"\\n\");\n}\n\nexport function devLogError(message: string, error?: any) {\n console.log(`${timestamp()} ${colors.red(\"✗\")} ${colors.red(message)}`);\n if (error?.stack) console.log(formatErrorStack(error.stack));\n}\n\nexport function devLogWarn(message: string) {\n console.log(`${timestamp()} ${colors.yellow(\"⚠\")} ${colors.yellow(message)}`);\n}\n\nexport function devLogInfo(message: string) {\n console.log(`${timestamp()} ${colors.cyan(message)}`);\n}\n\nexport function devLogDim(message: string) {\n console.log(`${timestamp()} ${colors.dim(message)}`);\n}\n\nexport function devLogHMR(file: string, dependents?: number) {\n const relativePath = Path.toRelative(file);\n const depInfo = dependents\n ? colors.dim(` +${dependents} module${dependents > 1 ? \"s\" : \"\"}`)\n : \"\";\n console.log(\n `${timestamp()} 🔥 ${colors.green(\"hmr update\")} ${colors.dim(relativePath)}${depInfo}`,\n );\n}\n\nexport function devLogConfig(file: string, connectors?: string[]) {\n const relativePath = Path.toRelative(file);\n const connectorInfo =\n connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(\", \")}`) : \"\";\n console.log(\n `${timestamp()} ${colors.cyan(\"config reload\")} ${colors.dim(relativePath)}${connectorInfo}`,\n );\n}\n\nexport function devLogReady(message: string) {\n console.log(`\\n${timestamp()} ${colors.green(\"➜\")} ${colors.bold(message)}`);\n}\n\nexport function devLogSection(title: string) {\n console.log(`\\n${timestamp()} ${colors.bold(colors.cyan(title))}`);\n}\n\n/**\n * Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the\n * project root. The loader hook keeps source paths in the URL so we only\n * need to strip the absolute prefix — no cache-path translation any more.\n */\nexport function formatModuleNotFoundError(error: Error, suggestions?: string[]): string {\n const match = error.message.match(/Cannot find module '([^']+)' imported from '([^']+)'/);\n if (!match) return error.message;\n\n const [, modulePath, importerPath] = match;\n const lines: string[] = [\n \"\",\n `${colors.red(\"❌ MODULE NOT FOUND\")}`,\n \"\",\n `${colors.dim(\"Cannot find:\")} ${colors.cyan(Path.toRelative(modulePath))}`,\n \"\",\n `${colors.dim(\"Imported by:\")}`,\n ` ${colors.yellow(\"→\")} ${colors.white(Path.toRelative(importerPath))}`,\n ];\n\n if (suggestions && suggestions.length > 0) {\n lines.push(\"\");\n lines.push(`${colors.dim(\"Did you mean?\")}`);\n suggestions.forEach(s => lines.push(` ${colors.cyan(\"→\")} ${colors.green(s)}`));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** @deprecated alias retained for older callers. Use `devLog` directly. */\nexport const devServeLog = devLog;\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Application } from "../application/application.mjs";
|
|
2
2
|
import "../application/index.mjs";
|
|
3
3
|
import { ConnectorLifecyclePhase } from "../connectors/types.mjs";
|
|
4
|
-
import { devLogInfo, devLogReady, devLogSection, devLogWarn, devServeLog } from "./dev-logger.mjs";
|
|
5
4
|
import { connectorsManager } from "../connectors/connectors-manager.mjs";
|
|
6
5
|
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
6
|
+
import "../warlock-config/index.mjs";
|
|
7
|
+
import { devLogInfo, devLogReady, devLogSection, devLogWarn, devServeLog } from "./dev-logger.mjs";
|
|
7
8
|
import { MANIFEST_PATH } from "./flags.mjs";
|
|
8
9
|
import { filesOrchestrator } from "./files-orchestrator.mjs";
|
|
9
|
-
import "../warlock-config/index.mjs";
|
|
10
10
|
import { restartDevServer } from "./restart-dev-server.mjs";
|
|
11
11
|
import { devServerShortcuts } from "./shortcuts.mjs";
|
|
12
12
|
import { LayerExecutor } from "./layer-executor.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-event-handler.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-event-handler.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { debounce } from \"@mongez/reinforcements\";\nimport type { DependencyGraph } from \"./dependency-graph\";\nimport { devLogSuccess } from \"./dev-logger\";\nimport type { FileManager } from \"./file-manager\";\nimport type { FileOperations } from \"./file-operations\";\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\nimport type { ManifestManager } from \"./manifest-manager\";\nimport { clearFileExistsCache } from \"./parse-imports\";\nimport { Path } from \"
|
|
1
|
+
{"version":3,"file":"file-event-handler.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-event-handler.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { debounce } from \"@mongez/reinforcements\";\nimport type { DependencyGraph } from \"./dependency-graph\";\nimport { devLogSuccess } from \"./dev-logger\";\nimport type { FileManager } from \"./file-manager\";\nimport type { FileOperations } from \"./file-operations\";\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\nimport type { ManifestManager } from \"./manifest-manager\";\nimport { clearFileExistsCache } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Receives raw watcher events and processes them in a single debounced batch.\n * Order within a batch: adds → changes → deletes, so changes can reference\n * newly-added files and deletes fire last.\n */\nexport class FileEventHandler {\n private pendingChanges = new Set<string>();\n private pendingAdds = new Set<string>();\n private pendingDeletes = new Set<string>();\n\n private readonly processPendingEvents = debounce(() => this.processBatch(), 150);\n\n constructor(\n private readonly fileOperations: FileOperations,\n private readonly manifest: ManifestManager,\n private readonly dependencyGraph: DependencyGraph,\n private readonly files: Map<string, FileManager>,\n ) {}\n\n public handleFileChange(absolutePath: string): void {\n this.pendingChanges.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n public handleFileAdd(absolutePath: string): void {\n this.pendingAdds.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n public handleFileDelete(absolutePath: string): void {\n this.pendingDeletes.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n private async processBatch(): Promise<void> {\n const changes = Array.from(this.pendingChanges);\n const adds = Array.from(this.pendingAdds);\n const deletes = Array.from(this.pendingDeletes);\n\n this.pendingChanges.clear();\n this.pendingAdds.clear();\n this.pendingDeletes.clear();\n\n if (changes.length === 0 && adds.length === 0 && deletes.length === 0) return;\n\n // Both .env files and warlock.config.ts live outside src/ — they should\n // never enter the dep graph, only ride along in the batch event so the\n // dev server can react (config reload / restart warning).\n const externalChanges = changes.filter(isExternalPath);\n const codeChanges = changes.filter(p => !isExternalPath(p));\n const codeAdds = adds.filter(p => !isExternalPath(p));\n\n // Multi-file batches can race the filesystem on Windows.\n if (codeAdds.length + codeChanges.length > 1) {\n await new Promise(resolve => setTimeout(resolve, 500));\n clearFileExistsCache();\n }\n\n await this.processBatchAdds(codeAdds);\n const changedCodePaths = await this.processBatchChanges(codeChanges);\n await this.processBatchDeletes(deletes);\n\n this.fileOperations.updateFileDependents();\n this.fileOperations.syncFilesToManifest();\n await this.manifest.save();\n\n // Emit only the code paths that genuinely changed (hash differs). A no-op\n // save — an editor that fsyncs without writing — reports no change and is\n // dropped here, where we still know the pre-change hash. (Doing this\n // downstream is impossible: the source has already been overwritten, so a\n // content compare always looks unchanged — which is exactly why emptying a\n // file used to silently skip HMR.) External paths (.env / warlock.config.ts)\n // ride along untouched so the dev server can still react to them.\n events.trigger(\"dev-server:batch-complete\", {\n added: adds,\n changed: [...externalChanges, ...changedCodePaths],\n deleted: deletes,\n });\n }\n\n /**\n * Reprocess each changed file and return only the paths that genuinely\n * changed. `updateFile` returns false when the on-disk hash matches the\n * last-processed hash (e.g. an editor that fsyncs on save without writing),\n * so those no-ops are kept out of the reload batch. Emptying a file changes\n * its hash, so it is correctly reported as changed.\n */\n private async processBatchChanges(relativePaths: string[]): Promise<string[]> {\n const changed: string[] = [];\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async path => {\n if (await this.fileOperations.updateFile(path)) {\n changed.push(path);\n }\n });\n return changed;\n }\n\n private async processBatchAdds(relativePaths: string[]): Promise<void> {\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async path => {\n try {\n await this.fileOperations.addFile(path);\n devLogSuccess(`Added file: ${path}`);\n } catch (error) {\n console.error(`Failed to add file ${path}:`, error);\n }\n });\n }\n\n private async processBatchDeletes(relativePaths: string[]): Promise<void> {\n for (const relativePath of relativePaths) {\n await this.fileOperations.deleteFile(relativePath);\n devLogSuccess(`Deleted file: ${relativePath}`);\n }\n }\n}\n\nfunction isEnvFile(path: string): boolean {\n const basename = path.split(\"/\").pop() || path;\n return basename === \".env\" || basename.startsWith(\".env.\");\n}\n\n/** Paths watched but never added to the dependency graph. */\nfunction isExternalPath(path: string): boolean {\n return isEnvFile(path) || path === \"warlock.config.ts\";\n}\n\nasync function runInBatches<T>(\n items: T[],\n size: number,\n fn: (item: T) => Promise<unknown>,\n): Promise<void> {\n if (items.length === 0) return;\n for (let i = 0; i < items.length; i += size) {\n await Promise.all(items.slice(i, i + size).map(fn));\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-manager.ts"],"sourcesContent":["import events from \"@mongez/events\";\r\nimport { getFileAsync, lastModifiedAsync } from \"@warlock.js/fs\";\r\nimport crypto from \"crypto\";\r\nimport { DEV_SERVER_EVENTS } from \"./events\";\r\nimport { type FileOperations } from \"./file-operations\";\r\nimport { isTypeOnlyFile, parseImports } from \"./parse-imports\";\r\nimport { Path } from \"./path\";\r\nimport type { FileManifest, FileState, FileType } from \"./types\";\r\n\r\nexport type CleanupFunction = () => void;\r\n\r\n/**\r\n * FileManager — per-file metadata for the dependency graph.\r\n *\r\n * The loader hook transpiles and caches on-demand at import time, so this\r\n * class never touches disk for anything beyond reading source to hash it\r\n * and parse its imports. Its job is keeping the metadata that the\r\n * orchestrator needs to invalidate dependents on change.\r\n */\r\nexport class FileManager {\r\n public relativePath = \"\";\r\n public lastModified = 0;\r\n public hash = \"\";\r\n public source = \"\";\r\n\r\n /** Files this file imports (relative paths). */\r\n public dependencies = new Set<string>();\r\n\r\n /**\r\n * Subset of `dependencies` reached only through type-only imports — a single\r\n * runtime occurrence makes the whole edge runtime.\r\n */\r\n public typeOnlyDependencies = new Set<string>();\r\n\r\n /** Original specifier → resolved absolute path. */\r\n public importMap = new Map<string, string>();\r\n\r\n /** Files that import this file (relative paths). */\r\n public dependents = new Set<string>();\r\n\r\n public type: FileType | undefined;\r\n\r\n /** Cleanup hooks the loaded module registered for itself. */\r\n public cleanup: CleanupFunction[] = [];\r\n\r\n /** Whether the source has zero runtime imports/exports. */\r\n public isTypeOnlyFile = false;\r\n\r\n public state: FileState = \"idle\";\r\n\r\n public constructor(\r\n public readonly absolutePath: string,\r\n public files: Map<string, FileManager>,\r\n public fileOperations: FileOperations,\r\n ) {}\r\n\r\n public addCleanup(cleanup: CleanupFunction | CleanupFunction[]) {\r\n const next = Array.isArray(cleanup) ? cleanup : [cleanup];\r\n this.cleanup.push(...next);\r\n this.cleanup = [...new Set(this.cleanup)];\r\n }\r\n\r\n public resetCleanup() {\r\n this.cleanup = [];\r\n }\r\n\r\n /**\r\n * Initial setup. Restores from manifest if the hash still matches,\r\n * otherwise re-processes from disk.\r\n */\r\n public async init(fileManifest?: Partial<FileManifest>): Promise<void> {\r\n this.relativePath = Path.toRelative(this.absolutePath);\r\n this.detectFileType();\r\n\r\n if (fileManifest) {\r\n await this.initFromManifest(fileManifest);\r\n } else {\r\n await this.process();\r\n }\r\n }\r\n\r\n /**\r\n * Read source, hash, parse imports, emit ready. The only file-system\r\n * touch happens here; transpilation is the loader hook's job.\r\n *\r\n * @param force - re-parse even when the hash matches.\r\n * @returns true if the file was (re)parsed.\r\n */\r\n public async process({ force = false }: { force?: boolean } = {}): Promise<boolean> {\r\n if (!this.relativePath) this.relativePath = Path.toRelative(this.absolutePath);\r\n if (!this.type) this.detectFileType();\r\n\r\n this.state = \"loading\";\r\n\r\n let newSource: string;\r\n\r\n try {\r\n newSource = await getFileAsync(this.absolutePath);\r\n } catch {\r\n this.state = \"deleted\";\r\n return false;\r\n }\r\n\r\n const newHash = crypto.createHash(\"sha256\").update(newSource).digest(\"hex\");\r\n\r\n if (!force && newHash === this.hash) {\r\n this.state = \"ready\";\r\n return false;\r\n }\r\n\r\n this.source = newSource;\r\n this.hash = newHash;\r\n this.lastModified = (await lastModifiedAsync(this.absolutePath)).getTime();\r\n\r\n await this.rebuildImportMetadata();\r\n this.isTypeOnlyFile = isTypeOnlyFile(this.source);\r\n\r\n this.state = \"ready\";\r\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Parse the current source and refresh `importMap`, `dependencies`, and\r\n * `typeOnlyDependencies`. A dependency is classified type-only iff every\r\n * import/export statement that resolves to it is type-only — a single\r\n * runtime reference makes the whole edge runtime.\r\n */\r\n protected async rebuildImportMetadata(): Promise<void> {\r\n const resolved = await parseImports(this.source, this.absolutePath);\r\n\r\n this.importMap = new Map();\r\n this.dependencies = new Set();\r\n this.typeOnlyDependencies = new Set();\r\n\r\n const runtimePaths = new Set<string>();\r\n\r\n for (const [originalPath, { absolutePath, isTypeOnly }] of resolved) {\r\n this.importMap.set(originalPath, absolutePath);\r\n\r\n const relativePath = Path.toRelative(absolutePath);\r\n this.dependencies.add(relativePath);\r\n\r\n if (!isTypeOnly) {\r\n runtimePaths.add(relativePath);\r\n }\r\n }\r\n\r\n for (const dependency of this.dependencies) {\r\n if (!runtimePaths.has(dependency)) {\r\n this.typeOnlyDependencies.add(dependency);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Restore from a cached manifest entry. If the on-disk hash matches the\r\n * manifest hash, dep-graph metadata is restored from the manifest without\r\n * re-parsing. Otherwise the file is reprocessed.\r\n */\r\n protected async initFromManifest(fileManifest: Partial<FileManifest>): Promise<void> {\r\n this.type = fileManifest.type;\r\n\r\n this.state = \"loading\";\r\n\r\n try {\r\n this.source = await getFileAsync(this.absolutePath);\r\n } catch {\r\n this.state = \"deleted\";\r\n return;\r\n }\r\n\r\n const currentHash = crypto.createHash(\"sha256\").update(this.source).digest(\"hex\");\r\n\r\n if (currentHash !== fileManifest.hash) {\r\n await this.process({ force: true });\r\n return;\r\n }\r\n\r\n this.hash = fileManifest.hash!;\r\n this.lastModified = fileManifest.lastModified!;\r\n this.dependencies = new Set(fileManifest.dependencies || []);\r\n this.typeOnlyDependencies = new Set(fileManifest.typeOnlyDependencies || []);\r\n this.dependents = new Set(fileManifest.dependents || []);\r\n\r\n this.state = \"ready\";\r\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\r\n }\r\n\r\n protected detectFileType(): void {\r\n const path = this.relativePath;\r\n\r\n if (path.includes(\"main.ts\") || path.includes(\"main.tsx\")) {\r\n this.type = \"main\";\r\n } else if (path.startsWith(\"src/config/\")) {\r\n this.type = \"config\";\r\n } else if (path.endsWith(\"routes.ts\") || path.endsWith(\"routes.tsx\")) {\r\n this.type = \"route\";\r\n } else if (path.includes(\"/events/\")) {\r\n this.type = \"event\";\r\n } else if (path.includes(\"controller\")) {\r\n this.type = \"controller\";\r\n } else if (path.includes(\"service\")) {\r\n this.type = \"service\";\r\n } else if (path.endsWith(\".model.ts\")) {\r\n this.type = \"model\";\r\n } else {\r\n this.type = \"other\";\r\n }\r\n }\r\n\r\n public toManifest(): FileManifest {\r\n return {\r\n absolutePath: this.absolutePath,\r\n relativePath: this.relativePath,\r\n lastModified: this.lastModified,\r\n hash: this.hash,\r\n dependencies: Array.from(this.dependencies),\r\n typeOnlyDependencies: Array.from(this.typeOnlyDependencies),\r\n dependents: Array.from(this.dependents),\r\n type: this.type!,\r\n };\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAa,cAAb,MAAyB;CA+BvB,AAAO,YACL,AAAgB,cAChB,AAAO,OACP,AAAO,gBACP;EAHgB;EACT;EACA;sBAjCa;sBACA;cACR;gBACE;sCAGM,IAAI,IAAY;8CAMR,IAAI,IAAY;mCAG3B,IAAI,IAAoB;oCAGvB,IAAI,IAAY;iBAKA,CAAC;wBAGb;eAEE;CAMvB;CAEH,AAAO,WAAW,SAA8C;EAC9D,MAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;EACxD,KAAK,QAAQ,KAAK,GAAG,IAAI;EACzB,KAAK,UAAU,CAAC,GAAG,IAAI,IAAI,KAAK,OAAO,CAAC;CAC1C;CAEA,AAAO,eAAe;EACpB,KAAK,UAAU,CAAC;CAClB;;;;;CAMA,MAAa,KAAK,cAAqD;EACrE,KAAK,eAAe,KAAK,WAAW,KAAK,YAAY;EACrD,KAAK,eAAe;EAEpB,IAAI,cACF,MAAM,KAAK,iBAAiB,YAAY;OAExC,MAAM,KAAK,QAAQ;CAEvB;;;;;;;;CASA,MAAa,QAAQ,EAAE,QAAQ,UAA+B,CAAC,GAAqB;EAClF,IAAI,CAAC,KAAK,cAAc,KAAK,eAAe,KAAK,WAAW,KAAK,YAAY;EAC7E,IAAI,CAAC,KAAK,MAAM,KAAK,eAAe;EAEpC,KAAK,QAAQ;EAEb,IAAI;EAEJ,IAAI;GACF,YAAY,MAAM,aAAa,KAAK,YAAY;EAClD,QAAQ;GACN,KAAK,QAAQ;GACb,OAAO;EACT;EAEA,MAAM,UAAU,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,KAAK;EAE1E,IAAI,CAAC,SAAS,YAAY,KAAK,MAAM;GACnC,KAAK,QAAQ;GACb,OAAO;EACT;EAEA,KAAK,SAAS;EACd,KAAK,OAAO;EACZ,KAAK,gBAAgB,MAAM,kBAAkB,KAAK,YAAY,EAAC,CAAE,QAAQ;EAEzE,MAAM,KAAK,sBAAsB;EACjC,KAAK,iBAAiB,eAAe,KAAK,MAAM;EAEhD,KAAK,QAAQ;EACb,OAAO,QAAQ,kBAAkB,YAAY,IAAI;EAEjD,OAAO;CACT;;;;;;;CAQA,MAAgB,wBAAuC;EACrD,MAAM,WAAW,MAAM,aAAa,KAAK,QAAQ,KAAK,YAAY;EAElE,KAAK,4BAAY,IAAI,IAAI;EACzB,KAAK,+BAAe,IAAI,IAAI;EAC5B,KAAK,uCAAuB,IAAI,IAAI;EAEpC,MAAM,+BAAe,IAAI,IAAY;EAErC,KAAK,MAAM,CAAC,cAAc,EAAE,cAAc,iBAAiB,UAAU;GACnE,KAAK,UAAU,IAAI,cAAc,YAAY;GAE7C,MAAM,eAAe,KAAK,WAAW,YAAY;GACjD,KAAK,aAAa,IAAI,YAAY;GAElC,IAAI,CAAC,YACH,aAAa,IAAI,YAAY;EAEjC;EAEA,KAAK,MAAM,cAAc,KAAK,cAC5B,IAAI,CAAC,aAAa,IAAI,UAAU,GAC9B,KAAK,qBAAqB,IAAI,UAAU;CAG9C;;;;;;CAOA,MAAgB,iBAAiB,cAAoD;EACnF,KAAK,OAAO,aAAa;EAEzB,KAAK,QAAQ;EAEb,IAAI;GACF,KAAK,SAAS,MAAM,aAAa,KAAK,YAAY;EACpD,QAAQ;GACN,KAAK,QAAQ;GACb;EACF;EAIA,IAFoB,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,OAAO,KAE7D,MAAM,aAAa,MAAM;GACrC,MAAM,KAAK,QAAQ,EAAE,OAAO,KAAK,CAAC;GAClC;EACF;EAEA,KAAK,OAAO,aAAa;EACzB,KAAK,eAAe,aAAa;EACjC,KAAK,eAAe,IAAI,IAAI,aAAa,gBAAgB,CAAC,CAAC;EAC3D,KAAK,uBAAuB,IAAI,IAAI,aAAa,wBAAwB,CAAC,CAAC;EAC3E,KAAK,aAAa,IAAI,IAAI,aAAa,cAAc,CAAC,CAAC;EAEvD,KAAK,QAAQ;EACb,OAAO,QAAQ,kBAAkB,YAAY,IAAI;CACnD;CAEA,AAAU,iBAAuB;EAC/B,MAAM,OAAO,KAAK;EAElB,IAAI,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,UAAU,GACtD,KAAK,OAAO;OACP,IAAI,KAAK,WAAW,aAAa,GACtC,KAAK,OAAO;OACP,IAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,YAAY,GACjE,KAAK,OAAO;OACP,IAAI,KAAK,SAAS,UAAU,GACjC,KAAK,OAAO;OACP,IAAI,KAAK,SAAS,YAAY,GACnC,KAAK,OAAO;OACP,IAAI,KAAK,SAAS,SAAS,GAChC,KAAK,OAAO;OACP,IAAI,KAAK,SAAS,WAAW,GAClC,KAAK,OAAO;OAEZ,KAAK,OAAO;CAEhB;CAEA,AAAO,aAA2B;EAChC,OAAO;GACL,cAAc,KAAK;GACnB,cAAc,KAAK;GACnB,cAAc,KAAK;GACnB,MAAM,KAAK;GACX,cAAc,MAAM,KAAK,KAAK,YAAY;GAC1C,sBAAsB,MAAM,KAAK,KAAK,oBAAoB;GAC1D,YAAY,MAAM,KAAK,KAAK,UAAU;GACtC,MAAM,KAAK;EACb;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"file-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-manager.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { getFileAsync, lastModifiedAsync } from \"@warlock.js/fs\";\nimport crypto from \"crypto\";\nimport { DEV_SERVER_EVENTS } from \"./events\";\nimport { type FileOperations } from \"./file-operations\";\nimport { isTypeOnlyFile, parseImports } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\nimport type { FileManifest, FileState, FileType } from \"./types\";\n\nexport type CleanupFunction = () => void;\n\n/**\n * FileManager — per-file metadata for the dependency graph.\n *\n * The loader hook transpiles and caches on-demand at import time, so this\n * class never touches disk for anything beyond reading source to hash it\n * and parse its imports. Its job is keeping the metadata that the\n * orchestrator needs to invalidate dependents on change.\n */\nexport class FileManager {\n public relativePath = \"\";\n public lastModified = 0;\n public hash = \"\";\n public source = \"\";\n\n /** Files this file imports (relative paths). */\n public dependencies = new Set<string>();\n\n /**\n * Subset of `dependencies` reached only through type-only imports — a single\n * runtime occurrence makes the whole edge runtime.\n */\n public typeOnlyDependencies = new Set<string>();\n\n /** Original specifier → resolved absolute path. */\n public importMap = new Map<string, string>();\n\n /** Files that import this file (relative paths). */\n public dependents = new Set<string>();\n\n public type: FileType | undefined;\n\n /** Cleanup hooks the loaded module registered for itself. */\n public cleanup: CleanupFunction[] = [];\n\n /** Whether the source has zero runtime imports/exports. */\n public isTypeOnlyFile = false;\n\n public state: FileState = \"idle\";\n\n public constructor(\n public readonly absolutePath: string,\n public files: Map<string, FileManager>,\n public fileOperations: FileOperations,\n ) {}\n\n public addCleanup(cleanup: CleanupFunction | CleanupFunction[]) {\n const next = Array.isArray(cleanup) ? cleanup : [cleanup];\n this.cleanup.push(...next);\n this.cleanup = [...new Set(this.cleanup)];\n }\n\n public resetCleanup() {\n this.cleanup = [];\n }\n\n /**\n * Initial setup. Restores from manifest if the hash still matches,\n * otherwise re-processes from disk.\n */\n public async init(fileManifest?: Partial<FileManifest>): Promise<void> {\n this.relativePath = Path.toRelative(this.absolutePath);\n this.detectFileType();\n\n if (fileManifest) {\n await this.initFromManifest(fileManifest);\n } else {\n await this.process();\n }\n }\n\n /**\n * Read source, hash, parse imports, emit ready. The only file-system\n * touch happens here; transpilation is the loader hook's job.\n *\n * @param force - re-parse even when the hash matches.\n * @returns true if the file was (re)parsed.\n */\n public async process({ force = false }: { force?: boolean } = {}): Promise<boolean> {\n if (!this.relativePath) this.relativePath = Path.toRelative(this.absolutePath);\n if (!this.type) this.detectFileType();\n\n this.state = \"loading\";\n\n let newSource: string;\n\n try {\n newSource = await getFileAsync(this.absolutePath);\n } catch {\n this.state = \"deleted\";\n return false;\n }\n\n const newHash = crypto.createHash(\"sha256\").update(newSource).digest(\"hex\");\n\n if (!force && newHash === this.hash) {\n this.state = \"ready\";\n return false;\n }\n\n this.source = newSource;\n this.hash = newHash;\n this.lastModified = (await lastModifiedAsync(this.absolutePath)).getTime();\n\n await this.rebuildImportMetadata();\n this.isTypeOnlyFile = isTypeOnlyFile(this.source);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n\n return true;\n }\n\n /**\n * Parse the current source and refresh `importMap`, `dependencies`, and\n * `typeOnlyDependencies`. A dependency is classified type-only iff every\n * import/export statement that resolves to it is type-only — a single\n * runtime reference makes the whole edge runtime.\n */\n protected async rebuildImportMetadata(): Promise<void> {\n const resolved = await parseImports(this.source, this.absolutePath);\n\n this.importMap = new Map();\n this.dependencies = new Set();\n this.typeOnlyDependencies = new Set();\n\n const runtimePaths = new Set<string>();\n\n for (const [originalPath, { absolutePath, isTypeOnly }] of resolved) {\n this.importMap.set(originalPath, absolutePath);\n\n const relativePath = Path.toRelative(absolutePath);\n this.dependencies.add(relativePath);\n\n if (!isTypeOnly) {\n runtimePaths.add(relativePath);\n }\n }\n\n for (const dependency of this.dependencies) {\n if (!runtimePaths.has(dependency)) {\n this.typeOnlyDependencies.add(dependency);\n }\n }\n }\n\n /**\n * Restore from a cached manifest entry. If the on-disk hash matches the\n * manifest hash, dep-graph metadata is restored from the manifest without\n * re-parsing. Otherwise the file is reprocessed.\n */\n protected async initFromManifest(fileManifest: Partial<FileManifest>): Promise<void> {\n this.type = fileManifest.type;\n\n this.state = \"loading\";\n\n try {\n this.source = await getFileAsync(this.absolutePath);\n } catch {\n this.state = \"deleted\";\n return;\n }\n\n const currentHash = crypto.createHash(\"sha256\").update(this.source).digest(\"hex\");\n\n if (currentHash !== fileManifest.hash) {\n await this.process({ force: true });\n return;\n }\n\n this.hash = fileManifest.hash!;\n this.lastModified = fileManifest.lastModified!;\n this.dependencies = new Set(fileManifest.dependencies || []);\n this.typeOnlyDependencies = new Set(fileManifest.typeOnlyDependencies || []);\n this.dependents = new Set(fileManifest.dependents || []);\n\n this.state = \"ready\";\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, this);\n }\n\n protected detectFileType(): void {\n const path = this.relativePath;\n\n if (path.includes(\"main.ts\") || path.includes(\"main.tsx\")) {\n this.type = \"main\";\n } else if (path.startsWith(\"src/config/\")) {\n this.type = \"config\";\n } else if (path.endsWith(\"routes.ts\") || path.endsWith(\"routes.tsx\")) {\n this.type = \"route\";\n } else if (path.includes(\"/events/\")) {\n this.type = \"event\";\n } else if (path.includes(\"controller\")) {\n this.type = \"controller\";\n } else if (path.includes(\"service\")) {\n this.type = \"service\";\n } else if (path.endsWith(\".model.ts\")) {\n this.type = \"model\";\n } else {\n this.type = \"other\";\n }\n }\n\n public toManifest(): FileManifest {\n return {\n absolutePath: this.absolutePath,\n relativePath: this.relativePath,\n lastModified: this.lastModified,\n hash: this.hash,\n dependencies: Array.from(this.dependencies),\n typeOnlyDependencies: Array.from(this.typeOnlyDependencies),\n dependents: Array.from(this.dependents),\n type: this.type!,\n };\n }\n}\n"],"mappings":"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"}
|