@warlock.js/core 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/bin/warlock.js +3 -0
- package/esm/_virtual/_rolldown/runtime.mjs +7 -0
- package/esm/cli/cli-commands.manager.mjs +290 -0
- package/esm/cli/cli-commands.manager.mjs.map +1 -0
- package/esm/cli/cli-commands.utils.mjs +217 -0
- package/esm/cli/cli-commands.utils.mjs.map +1 -0
- package/esm/cli/commands/add.command.mjs +27 -0
- package/esm/cli/commands/add.command.mjs.map +1 -0
- package/esm/cli/commands/build.command.mjs +14 -0
- package/esm/cli/commands/build.command.mjs.map +1 -0
- package/esm/cli/commands/create-database.command.mjs +24 -0
- package/esm/cli/commands/create-database.command.mjs.map +1 -0
- package/esm/cli/commands/dev-server.command.mjs +48 -0
- package/esm/cli/commands/dev-server.command.mjs.map +1 -0
- package/esm/cli/commands/drop-tables.command.mjs +23 -0
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -0
- package/esm/cli/commands/generate/generate.command.mjs +201 -0
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs +49 -0
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/controller.generator.mjs +48 -0
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/migration.generator.mjs +81 -0
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/model.generator.mjs +60 -0
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/module.generator.mjs +113 -0
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/repository.generator.mjs +42 -0
- package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/resource.generator.mjs +42 -0
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/service.generator.mjs +42 -0
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/stubs.mjs +514 -0
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/name-parser.mjs +69 -0
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/path-resolver.mjs +56 -0
- package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/prompt.mjs +28 -0
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/writer.mjs +60 -0
- package/esm/cli/commands/generate/utils/writer.mjs.map +1 -0
- package/esm/cli/commands/migrate.command.mjs +60 -0
- package/esm/cli/commands/migrate.command.mjs.map +1 -0
- package/esm/cli/commands/seed.command.mjs +41 -0
- package/esm/cli/commands/seed.command.mjs.map +1 -0
- package/esm/cli/commands/start-production.command.mjs +50 -0
- package/esm/cli/commands/start-production.command.mjs.map +1 -0
- package/esm/cli/commands/storage-put.action.mjs +75 -0
- package/esm/cli/commands/storage-put.action.mjs.map +1 -0
- package/esm/cli/commands/storage-put.command.mjs +46 -0
- package/esm/cli/commands/storage-put.command.mjs.map +1 -0
- package/esm/cli/commands/typings-generator.command.mjs +38 -0
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -0
- package/esm/cli/commands-loader.mjs +50 -0
- package/esm/cli/commands-loader.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +37 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -0
- package/esm/cli/parse-cli-args.mjs +62 -0
- package/esm/cli/parse-cli-args.mjs.map +1 -0
- package/esm/cli/start.d.mts +1 -0
- package/esm/cli/start.mjs +8 -0
- package/esm/cli/start.mjs.map +1 -0
- package/esm/cli/string-similarity.mjs +56 -0
- package/esm/cli/string-similarity.mjs.map +1 -0
- package/esm/database/create-database-action.mjs +32 -0
- package/esm/database/create-database-action.mjs.map +1 -0
- package/esm/database/drop-tables-action.mjs +35 -0
- package/esm/database/drop-tables-action.mjs.map +1 -0
- package/esm/database/migrate-action.mjs +99 -0
- package/esm/database/migrate-action.mjs.map +1 -0
- package/esm/database/seed-command-action.mjs +74 -0
- package/esm/database/seed-command-action.mjs.map +1 -0
- package/esm/database/seeds/seeders.manager.mjs +129 -0
- package/esm/database/seeds/seeders.manager.mjs.map +1 -0
- package/esm/database/seeds/seeds-table-migration.mjs +31 -0
- package/esm/database/seeds/seeds-table-migration.mjs.map +1 -0
- package/esm/database/seeds/utils.mjs +6 -0
- package/esm/database/seeds/utils.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs +12 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +115 -0
- package/esm/dev-server/development-server.mjs.map +1 -0
- package/esm/dev-server/layer-executor.mjs +114 -0
- package/esm/dev-server/layer-executor.mjs.map +1 -0
- package/esm/dev-server/start-development-server.mjs +63 -0
- package/esm/dev-server/start-development-server.mjs.map +1 -0
- package/esm/dev-server/type-generator.mjs +453 -0
- package/esm/dev-server/type-generator.mjs.map +1 -0
- package/esm/generations/add-command.action.mjs +480 -0
- package/esm/generations/add-command.action.mjs.map +1 -0
- package/esm/generations/stubs.mjs +436 -0
- package/esm/generations/stubs.mjs.map +1 -0
- package/esm/manifest/manifest-manager.mjs +79 -0
- package/esm/manifest/manifest-manager.mjs.map +1 -0
- package/esm/production/build-app-production.mjs +14 -0
- package/esm/production/build-app-production.mjs.map +1 -0
- package/esm/production/esbuild-plugins.mjs +37 -0
- package/esm/production/esbuild-plugins.mjs.map +1 -0
- package/esm/production/production-builder.mjs +264 -0
- package/esm/production/production-builder.mjs.map +1 -0
- package/esm/production/resolve-build-config.mjs +28 -0
- package/esm/production/resolve-build-config.mjs.map +1 -0
- package/esm/utils/framework-vesion.mjs +10 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/package.json +15 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/utils.ts"],"sourcesContent":["export const seedsTableName = \"_seeds\";\n"],"mappings":";AAAA,MAAa,iBAAiB"}
|
|
@@ -65,6 +65,17 @@ function devLogInfo(message) {
|
|
|
65
65
|
function devLogDim(message) {
|
|
66
66
|
console.log(`${timestamp()} ${colors.dim(message)}`);
|
|
67
67
|
}
|
|
68
|
+
function devLogHMR(file, dependents) {
|
|
69
|
+
const relativePath = Path.toRelative(file);
|
|
70
|
+
const depInfo = dependents ? colors.dim(` +${dependents} module${dependents > 1 ? "s" : ""}`) : "";
|
|
71
|
+
console.log(`${timestamp()} 🔥 ${colors.green("hmr update")} ${colors.dim(relativePath)}${depInfo}`);
|
|
72
|
+
}
|
|
73
|
+
function devLogReady(message) {
|
|
74
|
+
console.log(`\n${timestamp()} ${colors.green("➜")} ${colors.bold(message)}`);
|
|
75
|
+
}
|
|
76
|
+
function devLogSection(title) {
|
|
77
|
+
console.log(`\n${timestamp()} ${colors.bold(colors.cyan(title))}`);
|
|
78
|
+
}
|
|
68
79
|
/**
|
|
69
80
|
* Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the
|
|
70
81
|
* project root. The loader hook keeps source paths in the URL so we only
|
|
@@ -95,5 +106,5 @@ function formatModuleNotFoundError(error, suggestions) {
|
|
|
95
106
|
const devServeLog = devLog;
|
|
96
107
|
|
|
97
108
|
//#endregion
|
|
98
|
-
export { devLogDim, devLogError, devLogInfo, devLogSuccess, devLogWarn, devServeLog, formatModuleNotFoundError };
|
|
109
|
+
export { devLogDim, devLogError, devLogHMR, devLogInfo, devLogReady, devLogSection, devLogSuccess, devLogWarn, devServeLog, formatModuleNotFoundError };
|
|
99
110
|
//# sourceMappingURL=dev-logger.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport dayjs from \"dayjs\";\r\nimport { Path } from \"./path\";\r\n\r\n/**\r\n * Dev server logger — Vite-style formatting helpers.\r\n */\r\n\r\nfunction timestamp(): string {\r\n return colors.dim(`${dayjs().format(\"HH:mm:ss A\")}`);\r\n}\r\n\r\nexport function devLog(message: string) {\r\n console.log(`${timestamp()} ${message}`);\r\n}\r\n\r\nexport function devLogSuccess(message: string) {\r\n console.log(`${timestamp()} ${colors.green(\"✓\")} ${colors.green(message)}`);\r\n}\r\n\r\n/**\r\n * Colourise a stack trace so the eye lands on *your* code.\r\n *\r\n * - Error header (`ReferenceError: x is not defined`) → bold red.\r\n * - Frames in project `src/` → highlighted: a green `›` pointer, yellow\r\n * function name, cyan relative path, dim `:line:col`. These are almost\r\n * always where the bug is.\r\n * - Framework (`@warlock.js`), `node_modules`, and `node:` internal frames\r\n * → dimmed and relativised. Still there for context, just out of the way.\r\n *\r\n * Source maps are enabled in dev, so the paths/lines here are already the\r\n * original `.ts` locations — this only changes how they're painted.\r\n */\r\nexport function formatErrorStack(stack: string): string {\r\n const frame = /^(\\s*)at (.+?) \\((.+):(\\d+):(\\d+)\\)$/;\r\n const bareFrame = /^(\\s*)at (.+):(\\d+):(\\d+)$/;\r\n let headerDone = false;\r\n\r\n return stack\r\n .split(\"\\n\")\r\n .map(line => {\r\n const withFn = line.match(frame);\r\n const bare = line.match(bareFrame);\r\n\r\n if (!withFn && !bare) {\r\n // Header line(s) before the first frame.\r\n const painted = headerDone ? colors.dim(line) : colors.bold(colors.red(line));\r\n return painted;\r\n }\r\n\r\n headerDone = true;\r\n\r\n const fn = withFn ? withFn[2] : \"\";\r\n const file = withFn ? withFn[3] : bare![2];\r\n const lineNo = withFn ? withFn[4] : bare![3];\r\n const col = withFn ? withFn[5] : bare![4];\r\n\r\n const isNodeInternal = file.startsWith(\"node:\");\r\n const isDep = file.includes(\"node_modules\");\r\n const isFramework = /[\\\\/]@warlock\\.js[\\\\/]/.test(file);\r\n const isUserCode = !isNodeInternal && !isDep && !isFramework;\r\n\r\n const rel = isNodeInternal ? file : Path.toRelative(file);\r\n const loc = `:${lineNo}:${col}`;\r\n\r\n if (isUserCode) {\r\n return (\r\n ` ${colors.green(\"›\")} ${colors.dim(\"at\")} ` +\r\n `${colors.yellow(fn || \"<anonymous>\")} ` +\r\n `${colors.cyan(rel)}${colors.dim(loc)}`\r\n );\r\n }\r\n\r\n const label = fn ? `at ${fn} ${rel}${loc}` : `at ${rel}${loc}`;\r\n return ` ${colors.dim(label)}`;\r\n })\r\n .join(\"\\n\");\r\n}\r\n\r\nexport function devLogError(message: string, error?: any) {\r\n console.log(`${timestamp()} ${colors.red(\"✗\")} ${colors.red(message)}`);\r\n if (error?.stack) console.log(formatErrorStack(error.stack));\r\n}\r\n\r\nexport function devLogWarn(message: string) {\r\n console.log(`${timestamp()} ${colors.yellow(\"⚠\")} ${colors.yellow(message)}`);\r\n}\r\n\r\nexport function devLogInfo(message: string) {\r\n console.log(`${timestamp()} ${colors.cyan(message)}`);\r\n}\r\n\r\nexport function devLogDim(message: string) {\r\n console.log(`${timestamp()} ${colors.dim(message)}`);\r\n}\r\n\r\nexport function devLogHMR(file: string, dependents?: number) {\r\n const relativePath = Path.toRelative(file);\r\n const depInfo = dependents\r\n ? colors.dim(` +${dependents} module${dependents > 1 ? \"s\" : \"\"}`)\r\n : \"\";\r\n console.log(\r\n `${timestamp()} 🔥 ${colors.green(\"hmr update\")} ${colors.dim(relativePath)}${depInfo}`,\r\n );\r\n}\r\n\r\nexport function devLogConfig(file: string, connectors?: string[]) {\r\n const relativePath = Path.toRelative(file);\r\n const connectorInfo =\r\n connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(\", \")}`) : \"\";\r\n console.log(\r\n `${timestamp()} ${colors.cyan(\"config reload\")} ${colors.dim(relativePath)}${connectorInfo}`,\r\n );\r\n}\r\n\r\nexport function devLogReady(message: string) {\r\n console.log(`\\n${timestamp()} ${colors.green(\"➜\")} ${colors.bold(message)}`);\r\n}\r\n\r\nexport function devLogSection(title: string) {\r\n console.log(`\\n${timestamp()} ${colors.bold(colors.cyan(title))}`);\r\n}\r\n\r\n/**\r\n * Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the\r\n * project root. The loader hook keeps source paths in the URL so we only\r\n * need to strip the absolute prefix — no cache-path translation any more.\r\n */\r\nexport function formatModuleNotFoundError(error: Error, suggestions?: string[]): string {\r\n const match = error.message.match(/Cannot find module '([^']+)' imported from '([^']+)'/);\r\n if (!match) return error.message;\r\n\r\n const [, modulePath, importerPath] = match;\r\n const lines: string[] = [\r\n \"\",\r\n `${colors.red(\"❌ MODULE NOT FOUND\")}`,\r\n \"\",\r\n `${colors.dim(\"Cannot find:\")} ${colors.cyan(Path.toRelative(modulePath))}`,\r\n \"\",\r\n `${colors.dim(\"Imported by:\")}`,\r\n ` ${colors.yellow(\"→\")} ${colors.white(Path.toRelative(importerPath))}`,\r\n ];\r\n\r\n if (suggestions && suggestions.length > 0) {\r\n lines.push(\"\");\r\n lines.push(`${colors.dim(\"Did you mean?\")}`);\r\n suggestions.forEach(s => lines.push(` ${colors.cyan(\"→\")} ${colors.green(s)}`));\r\n }\r\n\r\n lines.push(\"\");\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** @deprecated alias retained for older callers. Use `devLog` directly. */\r\nexport const devServeLog = devLog;\r\n"],"mappings":";;;;;;;;AAQA,SAAS,YAAoB;CAC3B,OAAO,OAAO,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,YAAY,GAAG;AACrD;AAEA,SAAgB,OAAO,SAAiB;CACtC,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,SAAS;AACzC;AAEA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,OAAO,MAAM,GAAG,EAAE,GAAG,OAAO,MAAM,OAAO,GAAG;AAC5E;;;;;;;;;;;;;;AAeA,SAAgB,iBAAiB,OAAuB;CACtD,MAAM,QAAQ;CACd,MAAM,YAAY;CAClB,IAAI,aAAa;CAEjB,OAAO,MACJ,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ;EACX,MAAM,SAAS,KAAK,MAAM,KAAK;EAC/B,MAAM,OAAO,KAAK,MAAM,SAAS;EAEjC,IAAI,CAAC,UAAU,CAAC,MAGd,OADgB,aAAa,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC;EAI9E,aAAa;EAEb,MAAM,KAAK,SAAS,OAAO,KAAK;EAChC,MAAM,OAAO,SAAS,OAAO,KAAK,KAAM;EACxC,MAAM,SAAS,SAAS,OAAO,KAAK,KAAM;EAC1C,MAAM,MAAM,SAAS,OAAO,KAAK,KAAM;EAEvC,MAAM,iBAAiB,KAAK,WAAW,OAAO;EAC9C,MAAM,QAAQ,KAAK,SAAS,cAAc;EAC1C,MAAM,cAAc,yBAAyB,KAAK,IAAI;EACtD,MAAM,aAAa,CAAC,kBAAkB,CAAC,SAAS,CAAC;EAEjD,MAAM,MAAM,iBAAiB,OAAO,KAAK,WAAW,IAAI;EACxD,MAAM,MAAM,IAAI,OAAO,GAAG;EAE1B,IAAI,YACF,OACE,KAAK,OAAO,MAAM,GAAG,EAAE,GAAG,OAAO,IAAI,IAAI,EAAE,GACxC,OAAO,OAAO,MAAM,aAAa,EAAE,GACnC,OAAO,KAAK,GAAG,IAAI,OAAO,IAAI,GAAG;EAIxC,MAAM,QAAQ,KAAK,MAAM,GAAG,GAAG,MAAM,QAAQ,MAAM,MAAM;EACzD,OAAO,OAAO,OAAO,IAAI,KAAK;CAChC,CAAC,CAAC,CACD,KAAK,IAAI;AACd;AAEA,SAAgB,YAAY,SAAiB,OAAa;CACxD,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,IAAI,OAAO,GAAG;CACtE,IAAI,OAAO,OAAO,QAAQ,IAAI,iBAAiB,MAAM,KAAK,CAAC;AAC7D;AAEA,SAAgB,WAAW,SAAiB;CAC1C,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,OAAO,OAAO,GAAG;AAC9E;AAEA,SAAgB,WAAW,SAAiB;CAC1C,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,OAAO,KAAK,OAAO,GAAG;AACtD;AAEA,SAAgB,UAAU,SAAiB;CACzC,QAAQ,IAAI,GAAG,UAAU,EAAE,GAAG,OAAO,IAAI,OAAO,GAAG;AACrD;;;;;;
|
|
1
|
+
{"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport dayjs from \"dayjs\";\r\nimport { Path } from \"./path\";\r\n\r\n/**\r\n * Dev server logger — Vite-style formatting helpers.\r\n */\r\n\r\nfunction timestamp(): string {\r\n return colors.dim(`${dayjs().format(\"HH:mm:ss A\")}`);\r\n}\r\n\r\nexport function devLog(message: string) {\r\n console.log(`${timestamp()} ${message}`);\r\n}\r\n\r\nexport function devLogSuccess(message: string) {\r\n console.log(`${timestamp()} ${colors.green(\"✓\")} ${colors.green(message)}`);\r\n}\r\n\r\n/**\r\n * Colourise a stack trace so the eye lands on *your* code.\r\n *\r\n * - Error header (`ReferenceError: x is not defined`) → bold red.\r\n * - Frames in project `src/` → highlighted: a green `›` pointer, yellow\r\n * function name, cyan relative path, dim `:line:col`. These are almost\r\n * always where the bug is.\r\n * - Framework (`@warlock.js`), `node_modules`, and `node:` internal frames\r\n * → dimmed and relativised. Still there for context, just out of the way.\r\n *\r\n * Source maps are enabled in dev, so the paths/lines here are already the\r\n * original `.ts` locations — this only changes how they're painted.\r\n */\r\nexport function formatErrorStack(stack: string): string {\r\n const frame = /^(\\s*)at (.+?) \\((.+):(\\d+):(\\d+)\\)$/;\r\n const bareFrame = /^(\\s*)at (.+):(\\d+):(\\d+)$/;\r\n let headerDone = false;\r\n\r\n return stack\r\n .split(\"\\n\")\r\n .map(line => {\r\n const withFn = line.match(frame);\r\n const bare = line.match(bareFrame);\r\n\r\n if (!withFn && !bare) {\r\n // Header line(s) before the first frame.\r\n const painted = headerDone ? colors.dim(line) : colors.bold(colors.red(line));\r\n return painted;\r\n }\r\n\r\n headerDone = true;\r\n\r\n const fn = withFn ? withFn[2] : \"\";\r\n const file = withFn ? withFn[3] : bare![2];\r\n const lineNo = withFn ? withFn[4] : bare![3];\r\n const col = withFn ? withFn[5] : bare![4];\r\n\r\n const isNodeInternal = file.startsWith(\"node:\");\r\n const isDep = file.includes(\"node_modules\");\r\n const isFramework = /[\\\\/]@warlock\\.js[\\\\/]/.test(file);\r\n const isUserCode = !isNodeInternal && !isDep && !isFramework;\r\n\r\n const rel = isNodeInternal ? file : Path.toRelative(file);\r\n const loc = `:${lineNo}:${col}`;\r\n\r\n if (isUserCode) {\r\n return (\r\n ` ${colors.green(\"›\")} ${colors.dim(\"at\")} ` +\r\n `${colors.yellow(fn || \"<anonymous>\")} ` +\r\n `${colors.cyan(rel)}${colors.dim(loc)}`\r\n );\r\n }\r\n\r\n const label = fn ? `at ${fn} ${rel}${loc}` : `at ${rel}${loc}`;\r\n return ` ${colors.dim(label)}`;\r\n })\r\n .join(\"\\n\");\r\n}\r\n\r\nexport function devLogError(message: string, error?: any) {\r\n console.log(`${timestamp()} ${colors.red(\"✗\")} ${colors.red(message)}`);\r\n if (error?.stack) console.log(formatErrorStack(error.stack));\r\n}\r\n\r\nexport function devLogWarn(message: string) {\r\n console.log(`${timestamp()} ${colors.yellow(\"⚠\")} ${colors.yellow(message)}`);\r\n}\r\n\r\nexport function devLogInfo(message: string) {\r\n console.log(`${timestamp()} ${colors.cyan(message)}`);\r\n}\r\n\r\nexport function devLogDim(message: string) {\r\n console.log(`${timestamp()} ${colors.dim(message)}`);\r\n}\r\n\r\nexport function devLogHMR(file: string, dependents?: number) {\r\n const relativePath = Path.toRelative(file);\r\n const depInfo = dependents\r\n ? colors.dim(` +${dependents} module${dependents > 1 ? \"s\" : \"\"}`)\r\n : \"\";\r\n console.log(\r\n `${timestamp()} 🔥 ${colors.green(\"hmr update\")} ${colors.dim(relativePath)}${depInfo}`,\r\n );\r\n}\r\n\r\nexport function devLogConfig(file: string, connectors?: string[]) {\r\n const relativePath = Path.toRelative(file);\r\n const connectorInfo =\r\n connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(\", \")}`) : \"\";\r\n console.log(\r\n `${timestamp()} ${colors.cyan(\"config reload\")} ${colors.dim(relativePath)}${connectorInfo}`,\r\n );\r\n}\r\n\r\nexport function devLogReady(message: string) {\r\n console.log(`\\n${timestamp()} ${colors.green(\"➜\")} ${colors.bold(message)}`);\r\n}\r\n\r\nexport function devLogSection(title: string) {\r\n console.log(`\\n${timestamp()} ${colors.bold(colors.cyan(title))}`);\r\n}\r\n\r\n/**\r\n * Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the\r\n * project root. The loader hook keeps source paths in the URL so we only\r\n * need to strip the absolute prefix — no cache-path translation any more.\r\n */\r\nexport function formatModuleNotFoundError(error: Error, suggestions?: string[]): string {\r\n const match = error.message.match(/Cannot find module '([^']+)' imported from '([^']+)'/);\r\n if (!match) return error.message;\r\n\r\n const [, modulePath, importerPath] = match;\r\n const lines: string[] = [\r\n \"\",\r\n `${colors.red(\"❌ MODULE NOT FOUND\")}`,\r\n \"\",\r\n `${colors.dim(\"Cannot find:\")} ${colors.cyan(Path.toRelative(modulePath))}`,\r\n \"\",\r\n `${colors.dim(\"Imported by:\")}`,\r\n ` ${colors.yellow(\"→\")} ${colors.white(Path.toRelative(importerPath))}`,\r\n ];\r\n\r\n if (suggestions && suggestions.length > 0) {\r\n lines.push(\"\");\r\n lines.push(`${colors.dim(\"Did you mean?\")}`);\r\n suggestions.forEach(s => lines.push(` ${colors.cyan(\"→\")} ${colors.green(s)}`));\r\n }\r\n\r\n lines.push(\"\");\r\n return lines.join(\"\\n\");\r\n}\r\n\r\n/** @deprecated alias retained for older callers. Use `devLog` directly. */\r\nexport const devServeLog = devLog;\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ConnectorLifecyclePhase } from "../connectors/types.mjs";
|
|
2
|
+
import { devLogReady, devLogSection, devLogWarn, devServeLog } from "./dev-logger.mjs";
|
|
3
|
+
import { connectorsManager } from "../connectors/connectors-manager.mjs";
|
|
4
|
+
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
5
|
+
import { MANIFEST_PATH } from "./flags.mjs";
|
|
6
|
+
import { filesOrchestrator } from "./files-orchestrator.mjs";
|
|
7
|
+
import "../warlock-config/index.mjs";
|
|
8
|
+
import { LayerExecutor } from "./layer-executor.mjs";
|
|
9
|
+
import { typeGenerator } from "./type-generator.mjs";
|
|
10
|
+
import { colors } from "@mongez/copper";
|
|
11
|
+
import events from "@mongez/events";
|
|
12
|
+
import { fileExistsAsync, getFileAsync, unlinkAsync } from "@warlock.js/fs";
|
|
13
|
+
|
|
14
|
+
//#region ../@warlock.js/core/src/dev-server/development-server.ts
|
|
15
|
+
/**
|
|
16
|
+
* Top-level coordinator for `warlock dev`. Wires the file orchestrator, the
|
|
17
|
+
* connectors, and the layer executor together, and listens for the watcher's
|
|
18
|
+
* batched events to drive HMR.
|
|
19
|
+
*/
|
|
20
|
+
var DevelopmentServer = class {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.running = false;
|
|
23
|
+
this.options = options;
|
|
24
|
+
devLogSection("Starting Development Server...");
|
|
25
|
+
}
|
|
26
|
+
async start() {
|
|
27
|
+
try {
|
|
28
|
+
const startedAt = performance.now();
|
|
29
|
+
if (this.options.fresh && await fileExistsAsync(MANIFEST_PATH)) {
|
|
30
|
+
await unlinkAsync(MANIFEST_PATH);
|
|
31
|
+
devServeLog(colors.cyanBright("Cleared manifest (--fresh)"));
|
|
32
|
+
}
|
|
33
|
+
await filesOrchestrator.init();
|
|
34
|
+
await filesOrchestrator.initializeAll();
|
|
35
|
+
await filesOrchestrator.watchFiles();
|
|
36
|
+
filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());
|
|
37
|
+
this.setupEventListeners();
|
|
38
|
+
await this.autoDiscoverFiles();
|
|
39
|
+
await filesOrchestrator.moduleLoader.loadAll();
|
|
40
|
+
await connectorsManager.startPhase("late");
|
|
41
|
+
this.layerExecutor = new LayerExecutor(filesOrchestrator.getDependencyGraph(), filesOrchestrator.specialFilesCollector, filesOrchestrator.moduleLoader, (absolutePath) => filesOrchestrator.bumpVersion(absolutePath), () => filesOrchestrator.flushVersionBumps());
|
|
42
|
+
this.running = true;
|
|
43
|
+
const duration = performance.now() - startedAt;
|
|
44
|
+
devLogReady(`Development Server is ready in ${colors.greenBright(parseDuration(duration))}`);
|
|
45
|
+
const devServerConfig = await warlockConfigManager.get("devServer");
|
|
46
|
+
const generateTypings = this.options.generateTypings ?? devServerConfig?.generateTypings ?? true;
|
|
47
|
+
const healthCheckers = this.options.healthCheckers ?? devServerConfig?.healthCheckers ?? true;
|
|
48
|
+
if (generateTypings) typeGenerator.executeGenerateAllCommand();
|
|
49
|
+
if (healthCheckers) filesOrchestrator.startCheckingHealth(healthCheckers === true ? void 0 : healthCheckers);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
devServeLog(colors.redBright(`Failed to start Development Server: ${error}`));
|
|
52
|
+
await this.shutdown();
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Eagerly import files whose decorators populate global registries so any
|
|
58
|
+
* symbol-by-name resolution later in boot finds them.
|
|
59
|
+
*/
|
|
60
|
+
async autoDiscoverFiles() {
|
|
61
|
+
const discoveryTypes = ["model"];
|
|
62
|
+
for (const file of filesOrchestrator.files.values()) if (file.type && discoveryTypes.includes(file.type)) await filesOrchestrator.moduleLoader.loadModule(file, file.type);
|
|
63
|
+
}
|
|
64
|
+
setupEventListeners() {
|
|
65
|
+
events.on("dev-server:batch-complete", (batch) => this.handleBatchComplete(batch));
|
|
66
|
+
}
|
|
67
|
+
async handleBatchComplete(batch) {
|
|
68
|
+
if (!this.running || !this.layerExecutor) return;
|
|
69
|
+
if (batch.changed.includes("warlock.config.ts")) devLogWarn("warlock.config.ts changed — restart the dev server to apply.");
|
|
70
|
+
if (batch.changed.length > 0) batch.changed = await dropNoOpChanges(batch.changed);
|
|
71
|
+
if (batch.added.length + batch.changed.length + batch.deleted.length === 0) return;
|
|
72
|
+
const codeFiles = [...batch.added, ...batch.changed].filter((p) => !isEnvPath(p));
|
|
73
|
+
try {
|
|
74
|
+
await this.layerExecutor.executeBatchReload(codeFiles, filesOrchestrator.getFiles(), batch.deleted, batch.changed);
|
|
75
|
+
typeGenerator.executeTypingsGenerator([...batch.added, ...batch.changed]);
|
|
76
|
+
filesOrchestrator.checkHealth(batch);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
devServeLog(colors.redBright(`Failed to execute batch reload: ${error}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async shutdown() {
|
|
82
|
+
if (!this.running) return;
|
|
83
|
+
devServeLog(colors.redBright("Shutting down Development Server..."));
|
|
84
|
+
this.running = false;
|
|
85
|
+
await connectorsManager.shutdown();
|
|
86
|
+
devServeLog(colors.greenBright("Development Server stopped"));
|
|
87
|
+
}
|
|
88
|
+
isRunning() {
|
|
89
|
+
return this.running;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
async function dropNoOpChanges(changedPaths) {
|
|
93
|
+
return (await Promise.all(changedPaths.map(async (relativePath) => {
|
|
94
|
+
if (isEnvPath(relativePath)) return relativePath;
|
|
95
|
+
const file = filesOrchestrator.files.get(relativePath);
|
|
96
|
+
if (!file) return null;
|
|
97
|
+
const content = await getFileAsync(file.absolutePath);
|
|
98
|
+
if (content.trim() === file.source) return null;
|
|
99
|
+
file.source = content;
|
|
100
|
+
return relativePath;
|
|
101
|
+
}))).filter((p) => p !== null);
|
|
102
|
+
}
|
|
103
|
+
function isEnvPath(path) {
|
|
104
|
+
const basename = path.split("/").pop() ?? path;
|
|
105
|
+
return basename === ".env" || basename.startsWith(".env.");
|
|
106
|
+
}
|
|
107
|
+
function parseDuration(ms) {
|
|
108
|
+
if (ms < 1e3) return `${ms.toFixed(2)}ms`;
|
|
109
|
+
if (ms > 6e4) return `${(ms / 6e4).toFixed(2)}m`;
|
|
110
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { DevelopmentServer };
|
|
115
|
+
//# sourceMappingURL=development-server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"development-server.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/development-server.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync, getFileAsync, unlinkAsync } from \"@warlock.js/fs\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { warlockConfigManager } from \"../warlock-config\";\r\nimport { devLogReady, devLogSection, devLogWarn, devServeLog } from \"./dev-logger\";\r\nimport { filesOrchestrator } from \"./files-orchestrator\";\r\nimport { MANIFEST_PATH } from \"./flags\";\r\nimport { LayerExecutor } from \"./layer-executor\";\r\nimport type { StartDevServerOptions } from \"./start-development-server\";\r\nimport { typeGenerator } from \"./type-generator\";\r\n\r\ntype Batch = { added: string[]; changed: string[]; deleted: string[] };\r\n\r\n/**\r\n * Top-level coordinator for `warlock dev`. Wires the file orchestrator, the\r\n * connectors, and the layer executor together, and listens for the watcher's\r\n * batched events to drive HMR.\r\n */\r\nexport class DevelopmentServer {\r\n private layerExecutor?: LayerExecutor;\r\n private running = false;\r\n private readonly options: StartDevServerOptions;\r\n\r\n public constructor(options: StartDevServerOptions = {}) {\r\n this.options = options;\r\n devLogSection(\"Starting Development Server...\");\r\n }\r\n\r\n public async start(): Promise<void> {\r\n try {\r\n const startedAt = performance.now();\r\n\r\n // --fresh deletes the manifest so reconciliation re-parses every file\r\n // from disk. Transpile caching is owned by the loader hook (in-memory).\r\n if (this.options.fresh && (await fileExistsAsync(MANIFEST_PATH))) {\r\n await unlinkAsync(MANIFEST_PATH);\r\n devServeLog(colors.cyanBright(\"Cleared manifest (--fresh)\"));\r\n }\r\n\r\n await filesOrchestrator.init();\r\n await filesOrchestrator.initializeAll();\r\n await filesOrchestrator.watchFiles();\r\n\r\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\r\n\r\n this.setupEventListeners();\r\n\r\n // Decorator-driven registries (models, etc.) must be populated before\r\n // routes/services can resolve symbols by name.\r\n await this.autoDiscoverFiles();\r\n\r\n await filesOrchestrator.moduleLoader.loadAll();\r\n\r\n // Late-phase connectors (http, socket) bind after app code has\r\n // registered routes/listeners.\r\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\r\n\r\n this.layerExecutor = new LayerExecutor(\r\n filesOrchestrator.getDependencyGraph(),\r\n filesOrchestrator.specialFilesCollector,\r\n filesOrchestrator.moduleLoader,\r\n (absolutePath) => filesOrchestrator.bumpVersion(absolutePath),\r\n () => filesOrchestrator.flushVersionBumps(),\r\n );\r\n\r\n this.running = true;\r\n\r\n const duration = performance.now() - startedAt;\r\n devLogReady(`Development Server is ready in ${colors.greenBright(parseDuration(duration))}`);\r\n\r\n // Precedence: explicit CLI option > devServer.* config > default.\r\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\r\n const generateTypings =\r\n this.options.generateTypings ?? devServerConfig?.generateTypings ?? true;\r\n const healthCheckers = this.options.healthCheckers ?? devServerConfig?.healthCheckers ?? true;\r\n\r\n if (generateTypings) typeGenerator.executeGenerateAllCommand();\r\n\r\n if (healthCheckers) {\r\n filesOrchestrator.startCheckingHealth(healthCheckers === true ? undefined : healthCheckers);\r\n }\r\n } catch (error) {\r\n devServeLog(colors.redBright(`Failed to start Development Server: ${error}`));\r\n await this.shutdown();\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Eagerly import files whose decorators populate global registries so any\r\n * symbol-by-name resolution later in boot finds them.\r\n */\r\n private async autoDiscoverFiles(): Promise<void> {\r\n const discoveryTypes = [\"model\"] as const;\r\n for (const file of filesOrchestrator.files.values()) {\r\n if (file.type && (discoveryTypes as readonly string[]).includes(file.type)) {\r\n await filesOrchestrator.moduleLoader.loadModule(file, file.type);\r\n }\r\n }\r\n }\r\n\r\n private setupEventListeners(): void {\r\n events.on(\"dev-server:batch-complete\", (batch: Batch) => this.handleBatchComplete(batch));\r\n }\r\n\r\n private async handleBatchComplete(batch: Batch): Promise<void> {\r\n if (!this.running || !this.layerExecutor) return;\r\n\r\n // warlock.config.ts holds settings read at boot (CLI commands, build\r\n // options, watch patterns, scheduled jobs). Hot-reloading it would\r\n // leave running services configured with stale values, so tell the\r\n // dev they need a restart and don't pretend to apply the change.\r\n if (batch.changed.includes(\"warlock.config.ts\")) {\r\n devLogWarn(\"warlock.config.ts changed — restart the dev server to apply.\");\r\n }\r\n\r\n // Some editors fsync on save without writing — drop no-op changes.\r\n if (batch.changed.length > 0) {\r\n batch.changed = await dropNoOpChanges(batch.changed);\r\n }\r\n\r\n const total = batch.added.length + batch.changed.length + batch.deleted.length;\r\n if (total === 0) return;\r\n\r\n const codeFiles = [...batch.added, ...batch.changed].filter((p) => !isEnvPath(p));\r\n\r\n try {\r\n await this.layerExecutor.executeBatchReload(\r\n codeFiles,\r\n filesOrchestrator.getFiles(),\r\n batch.deleted,\r\n batch.changed,\r\n );\r\n\r\n typeGenerator.executeTypingsGenerator([...batch.added, ...batch.changed]);\r\n\r\n filesOrchestrator.checkHealth(batch);\r\n } catch (error) {\r\n devServeLog(colors.redBright(`Failed to execute batch reload: ${error}`));\r\n }\r\n }\r\n\r\n public async shutdown(): Promise<void> {\r\n if (!this.running) return;\r\n devServeLog(colors.redBright(\"Shutting down Development Server...\"));\r\n this.running = false;\r\n await connectorsManager.shutdown();\r\n devServeLog(colors.greenBright(\"Development Server stopped\"));\r\n }\r\n\r\n public isRunning(): boolean {\r\n return this.running;\r\n }\r\n}\r\n\r\nasync function dropNoOpChanges(changedPaths: string[]): Promise<string[]> {\r\n const kept = await Promise.all(\r\n changedPaths.map(async (relativePath) => {\r\n if (isEnvPath(relativePath)) return relativePath;\r\n\r\n const file = filesOrchestrator.files.get(relativePath);\r\n if (!file) return null;\r\n\r\n const content = await getFileAsync(file.absolutePath);\r\n if (content.trim() === file.source) return null;\r\n\r\n file.source = content;\r\n return relativePath;\r\n }),\r\n );\r\n\r\n return kept.filter((p): p is string => p !== null);\r\n}\r\n\r\nfunction isEnvPath(path: string): boolean {\r\n const basename = path.split(\"/\").pop() ?? path;\r\n return basename === \".env\" || basename.startsWith(\".env.\");\r\n}\r\n\r\nfunction parseDuration(ms: number): string {\r\n if (ms < 1000) return `${ms.toFixed(2)}ms`;\r\n if (ms > 60_000) return `${(ms / 60_000).toFixed(2)}m`;\r\n return `${(ms / 1000).toFixed(2)}s`;\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { devLogHMR } from "./dev-logger.mjs";
|
|
2
|
+
import { connectorsManager } from "../connectors/connectors-manager.mjs";
|
|
3
|
+
import { configManager } from "../config/config-manager.mjs";
|
|
4
|
+
import { loadEnv } from "@mongez/dotenv";
|
|
5
|
+
|
|
6
|
+
//#region ../@warlock.js/core/src/dev-server/layer-executor.ts
|
|
7
|
+
/**
|
|
8
|
+
* Decides what to reload when a batch of files changes.
|
|
9
|
+
*
|
|
10
|
+
* Strategy: bump the hook's version counter for every file in the
|
|
11
|
+
* invalidation chain, wait for the hook worker to flush, then re-import
|
|
12
|
+
* any special files (config / main / routes / events / locales) the chain
|
|
13
|
+
* touched and restart any connector whose watched-files overlap the change.
|
|
14
|
+
*/
|
|
15
|
+
var LayerExecutor = class {
|
|
16
|
+
constructor(dependencyGraph, specialFilesCollector, moduleLoader, bumpVersion, flushVersionBumps) {
|
|
17
|
+
this.dependencyGraph = dependencyGraph;
|
|
18
|
+
this.specialFilesCollector = specialFilesCollector;
|
|
19
|
+
this.moduleLoader = moduleLoader;
|
|
20
|
+
this.bumpVersion = bumpVersion;
|
|
21
|
+
this.flushVersionBumps = flushVersionBumps;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Entry point for the file watcher batch.
|
|
25
|
+
*
|
|
26
|
+
* @param changedPaths - code files added or changed in this batch
|
|
27
|
+
* @param filesMap - all tracked files (relativePath → FileManager)
|
|
28
|
+
* @param deletedFiles - paths that were removed from disk
|
|
29
|
+
* @param allChangedPaths - includes .env so we can detect config reloads
|
|
30
|
+
*/
|
|
31
|
+
async executeBatchReload(changedPaths, filesMap, deletedFiles, allChangedPaths) {
|
|
32
|
+
const envFilesChanged = (allChangedPaths ?? []).some(isEnvPath);
|
|
33
|
+
if (changedPaths.length === 0 && deletedFiles.length === 0 && !envFilesChanged) return;
|
|
34
|
+
for (const path of deletedFiles) {
|
|
35
|
+
const file = filesMap.get(path);
|
|
36
|
+
if (file) this.moduleLoader.cleanupDeletedModule(file);
|
|
37
|
+
}
|
|
38
|
+
if (changedPaths.length === 0 && envFilesChanged) {
|
|
39
|
+
const configPaths = await this.reloadAffectedModules([".env"], filesMap);
|
|
40
|
+
await this.restartAffectedConnectors(configPaths);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (changedPaths.length === 0) return;
|
|
44
|
+
const invalidationChain = /* @__PURE__ */ new Set();
|
|
45
|
+
for (const path of changedPaths) {
|
|
46
|
+
for (const file of this.dependencyGraph.getInvalidationChain(path)) invalidationChain.add(file);
|
|
47
|
+
devLogHMR(path, invalidationChain.size - 1);
|
|
48
|
+
}
|
|
49
|
+
const chain = Array.from(invalidationChain);
|
|
50
|
+
for (const relativePath of chain) {
|
|
51
|
+
const file = filesMap.get(relativePath);
|
|
52
|
+
if (!file) continue;
|
|
53
|
+
this.moduleLoader.runCleanup(file);
|
|
54
|
+
this.bumpVersion(file.absolutePath);
|
|
55
|
+
await file.process({ force: true });
|
|
56
|
+
}
|
|
57
|
+
await this.flushVersionBumps();
|
|
58
|
+
const affectedConfigPaths = await this.reloadAffectedModules(chain, filesMap);
|
|
59
|
+
await this.restartAffectedConnectors([...changedPaths, ...affectedConfigPaths]);
|
|
60
|
+
}
|
|
61
|
+
async restartAffectedConnectors(affectedFiles) {
|
|
62
|
+
const toRestart = connectorsManager.list().filter((connector) => connector.shouldRestart(affectedFiles));
|
|
63
|
+
for (const connector of toRestart) await connector.restart();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Re-import every special file whose path or dependency-set intersects the
|
|
67
|
+
* invalidation chain. Returns the relative paths of any config files that
|
|
68
|
+
* reloaded so the caller can pass them to the connector-restart pass.
|
|
69
|
+
*/
|
|
70
|
+
async reloadAffectedModules(chain, filesMap) {
|
|
71
|
+
const isEnvAffected = chain.some(isEnvPath);
|
|
72
|
+
if (isEnvAffected) await loadEnv();
|
|
73
|
+
const isAffected = (file) => isFileAffected(file, chain);
|
|
74
|
+
const affectedModels = chain.map((path) => filesMap.get(path)).filter((file) => !!file && file.type === "model");
|
|
75
|
+
for (const file of affectedModels) await this.moduleLoader.loadModule(file, "model");
|
|
76
|
+
const collector = this.specialFilesCollector;
|
|
77
|
+
const affectedConfigs = collector.getFilesByType("config").filter((file) => isEnvAffected ? true : isAffected(file));
|
|
78
|
+
const affectedMains = collector.getFilesByType("main").filter(isAffected);
|
|
79
|
+
const affectedRoutes = collector.getFilesByType("route").filter(isAffected);
|
|
80
|
+
const affectedEvents = collector.getFilesByType("event").filter(isAffected);
|
|
81
|
+
const affectedLocales = collector.getFilesByType("locale").filter(isAffected);
|
|
82
|
+
if (!(affectedConfigs.length > 0 || affectedMains.length > 0 || affectedRoutes.length > 0 || affectedEvents.length > 0 || affectedLocales.length > 0)) {
|
|
83
|
+
const tail = filesMap.get(chain[chain.length - 1]);
|
|
84
|
+
if (tail) await this.moduleLoader.reloadModule(tail);
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const configPaths = [];
|
|
88
|
+
for (const file of affectedConfigs) {
|
|
89
|
+
await configManager.reload(file);
|
|
90
|
+
configPaths.push(file.relativePath);
|
|
91
|
+
}
|
|
92
|
+
for (const file of affectedLocales) await this.moduleLoader.reloadModule(file);
|
|
93
|
+
for (const file of affectedMains) await this.moduleLoader.reloadModule(file);
|
|
94
|
+
for (const file of affectedEvents) await this.moduleLoader.reloadModule(file);
|
|
95
|
+
for (const file of affectedRoutes) await this.moduleLoader.reloadModule(file);
|
|
96
|
+
return configPaths;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function isEnvPath(path) {
|
|
100
|
+
const basename = path.split("/").pop() ?? path;
|
|
101
|
+
return basename === ".env" || basename.startsWith(".env.");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A file is "affected" if it itself is in the chain or imports something in it.
|
|
105
|
+
*/
|
|
106
|
+
function isFileAffected(file, chain) {
|
|
107
|
+
if (chain.includes(file.relativePath)) return true;
|
|
108
|
+
for (const dep of file.dependencies) if (chain.includes(dep)) return true;
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
export { LayerExecutor };
|
|
114
|
+
//# sourceMappingURL=layer-executor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-executor.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/layer-executor.ts"],"sourcesContent":["import { loadEnv } from \"@mongez/dotenv\";\r\nimport { configManager } from \"../config/config-manager\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport type { DependencyGraph } from \"./dependency-graph\";\r\nimport { devLogHMR } from \"./dev-logger\";\r\nimport { FileManager } from \"./file-manager\";\r\nimport type { ModuleLoader } from \"./module-loader\";\r\nimport type { SpecialFilesCollector } from \"./special-files-collector\";\r\n\r\n/**\r\n * Decides what to reload when a batch of files changes.\r\n *\r\n * Strategy: bump the hook's version counter for every file in the\r\n * invalidation chain, wait for the hook worker to flush, then re-import\r\n * any special files (config / main / routes / events / locales) the chain\r\n * touched and restart any connector whose watched-files overlap the change.\r\n */\r\nexport class LayerExecutor {\r\n public constructor(\r\n private readonly dependencyGraph: DependencyGraph,\r\n private readonly specialFilesCollector: SpecialFilesCollector,\r\n private readonly moduleLoader: ModuleLoader,\r\n private readonly bumpVersion: (absolutePath: string) => void,\r\n private readonly flushVersionBumps: () => Promise<void>,\r\n ) {}\r\n\r\n /**\r\n * Entry point for the file watcher batch.\r\n *\r\n * @param changedPaths - code files added or changed in this batch\r\n * @param filesMap - all tracked files (relativePath → FileManager)\r\n * @param deletedFiles - paths that were removed from disk\r\n * @param allChangedPaths - includes .env so we can detect config reloads\r\n */\r\n public async executeBatchReload(\r\n changedPaths: string[],\r\n filesMap: Map<string, FileManager>,\r\n deletedFiles: string[],\r\n allChangedPaths?: string[],\r\n ): Promise<void> {\r\n const envFilesChanged = (allChangedPaths ?? []).some(isEnvPath);\r\n\r\n if (changedPaths.length === 0 && deletedFiles.length === 0 && !envFilesChanged) {\r\n return;\r\n }\r\n\r\n // Deletes: clean up routes/cleanup hooks for files that no longer exist.\r\n for (const path of deletedFiles) {\r\n const file = filesMap.get(path);\r\n if (file) this.moduleLoader.cleanupDeletedModule(file);\r\n }\r\n\r\n // Env-only change: reload all configs, restart connectors that watch them.\r\n if (changedPaths.length === 0 && envFilesChanged) {\r\n const configPaths = await this.reloadAffectedModules([\".env\"], filesMap);\r\n await this.restartAffectedConnectors(configPaths);\r\n return;\r\n }\r\n\r\n if (changedPaths.length === 0) return;\r\n\r\n const invalidationChain = new Set<string>();\r\n for (const path of changedPaths) {\r\n for (const file of this.dependencyGraph.getInvalidationChain(path)) {\r\n invalidationChain.add(file);\r\n }\r\n devLogHMR(path, invalidationChain.size - 1);\r\n }\r\n\r\n const chain = Array.from(invalidationChain);\r\n\r\n // Step 1: bump version counters so the next import() is fresh.\r\n for (const relativePath of chain) {\r\n const file = filesMap.get(relativePath);\r\n if (!file) continue;\r\n this.moduleLoader.runCleanup(file);\r\n this.bumpVersion(file.absolutePath);\r\n await file.process({ force: true });\r\n }\r\n\r\n // Step 2: wait for the hook worker to ack every bump.\r\n // Without this, resolve() may still return the old ?v=N URL.\r\n await this.flushVersionBumps();\r\n\r\n // Step 3: re-import affected special files.\r\n const affectedConfigPaths = await this.reloadAffectedModules(chain, filesMap);\r\n\r\n // Step 4: restart any connector whose watched-files overlap the chain.\r\n await this.restartAffectedConnectors([...changedPaths, ...affectedConfigPaths]);\r\n }\r\n\r\n private async restartAffectedConnectors(affectedFiles: string[]): Promise<void> {\r\n const toRestart = connectorsManager\r\n .list()\r\n .filter(connector => connector.shouldRestart(affectedFiles));\r\n\r\n for (const connector of toRestart) {\r\n await connector.restart();\r\n }\r\n }\r\n\r\n /**\r\n * Re-import every special file whose path or dependency-set intersects the\r\n * invalidation chain. Returns the relative paths of any config files that\r\n * reloaded so the caller can pass them to the connector-restart pass.\r\n */\r\n private async reloadAffectedModules(\r\n chain: string[],\r\n filesMap: Map<string, FileManager>,\r\n ): Promise<string[]> {\r\n const isEnvAffected = chain.some(isEnvPath);\r\n if (isEnvAffected) await loadEnv();\r\n\r\n const isAffected = (file: FileManager) => isFileAffected(file, chain);\r\n\r\n // Models self-register via the @RegisterModel decorator and rely on the\r\n // module-loader's registerCleanup() to attach Model.$cleanup (which\r\n // unregisters them on the next reload). That only happens inside\r\n // loadModule(), which models hit exactly once — at boot, via\r\n // autoDiscoverFiles. During HMR they're otherwise re-imported\r\n // *transitively* through routes, which never re-runs registerCleanup, so\r\n // after the first reload the cleanup list is empty and the registration\r\n // leaks (\"Model X is already registered\" on every subsequent edit).\r\n //\r\n // Re-importing changed model files through loadModule here re-attaches\r\n // $cleanup every cycle. It runs before the route pass so the decorator\r\n // registers once; the transitive route import then hits the cached ?v=N\r\n // and does not double-register.\r\n const affectedModels = chain\r\n .map(path => filesMap.get(path))\r\n .filter((file): file is FileManager => !!file && file.type === \"model\");\r\n\r\n for (const file of affectedModels) {\r\n await this.moduleLoader.loadModule(file, \"model\");\r\n }\r\n\r\n const collector = this.specialFilesCollector;\r\n const affectedConfigs = collector\r\n .getFilesByType(\"config\")\r\n .filter(file => (isEnvAffected ? true : isAffected(file)));\r\n const affectedMains = collector.getFilesByType(\"main\").filter(isAffected);\r\n const affectedRoutes = collector.getFilesByType(\"route\").filter(isAffected);\r\n const affectedEvents = collector.getFilesByType(\"event\").filter(isAffected);\r\n const affectedLocales = collector.getFilesByType(\"locale\").filter(isAffected);\r\n\r\n const hasSpecialFiles =\r\n affectedConfigs.length > 0 ||\r\n affectedMains.length > 0 ||\r\n affectedRoutes.length > 0 ||\r\n affectedEvents.length > 0 ||\r\n affectedLocales.length > 0;\r\n\r\n // No entry points touched: reloading internal files alone is wasted work\r\n // because the hook will re-import them on next access anyway. But the\r\n // dep chain's last hop is usually the user-facing edge — give it a kick.\r\n if (!hasSpecialFiles) {\r\n const tail = filesMap.get(chain[chain.length - 1]);\r\n if (tail) await this.moduleLoader.reloadModule(tail);\r\n return [];\r\n }\r\n\r\n const configPaths: string[] = [];\r\n for (const file of affectedConfigs) {\r\n await configManager.reload(file);\r\n configPaths.push(file.relativePath);\r\n }\r\n\r\n // Order matters: locales first (translations used by main), main before\r\n // routes (registers state routes consume), events between (listeners).\r\n for (const file of affectedLocales) await this.moduleLoader.reloadModule(file);\r\n for (const file of affectedMains) await this.moduleLoader.reloadModule(file);\r\n for (const file of affectedEvents) await this.moduleLoader.reloadModule(file);\r\n for (const file of affectedRoutes) await this.moduleLoader.reloadModule(file);\r\n\r\n return configPaths;\r\n }\r\n}\r\n\r\nfunction isEnvPath(path: string): boolean {\r\n const basename = path.split(\"/\").pop() ?? path;\r\n return basename === \".env\" || basename.startsWith(\".env.\");\r\n}\r\n\r\n/**\r\n * A file is \"affected\" if it itself is in the chain or imports something in it.\r\n */\r\nfunction isFileAffected(file: FileManager, chain: string[]): boolean {\r\n if (chain.includes(file.relativePath)) return true;\r\n for (const dep of file.dependencies) {\r\n if (chain.includes(dep)) return true;\r\n }\r\n return false;\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { devLogError, devServeLog } from "./dev-logger.mjs";
|
|
2
|
+
import { DevelopmentServer } from "./development-server.mjs";
|
|
3
|
+
import { colors } from "@mongez/copper";
|
|
4
|
+
|
|
5
|
+
//#region ../@warlock.js/core/src/dev-server/start-development-server.ts
|
|
6
|
+
let handlersRegistered = false;
|
|
7
|
+
/**
|
|
8
|
+
* Start the development server.
|
|
9
|
+
*
|
|
10
|
+
* Boots the file orchestrator, loads app modules, starts late-phase
|
|
11
|
+
* connectors, then wires graceful shutdown for SIGINT/SIGTERM (plus
|
|
12
|
+
* SIGHUP on Windows where Ctrl+C in spawned children lands as SIGHUP).
|
|
13
|
+
*
|
|
14
|
+
* If startup throws, shuts down cleanly and exits 1 — dev should die
|
|
15
|
+
* loudly rather than hang with a half-initialized state.
|
|
16
|
+
*/
|
|
17
|
+
async function startDevelopmentServer(options = {}) {
|
|
18
|
+
process.setSourceMapsEnabled(true);
|
|
19
|
+
const devServer = new DevelopmentServer(options);
|
|
20
|
+
registerShutdownHandlers(devServer);
|
|
21
|
+
try {
|
|
22
|
+
await devServer.start();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
devLogError(`Failed to start Development Server: ${error.message}`);
|
|
25
|
+
await safeShutdown(devServer);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return devServer;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register signal handlers once per process. Guards against handler
|
|
32
|
+
* accumulation if `startDevelopmentServer` is invoked more than once
|
|
33
|
+
* (tests, programmatic restart).
|
|
34
|
+
*/
|
|
35
|
+
function registerShutdownHandlers(devServer) {
|
|
36
|
+
if (handlersRegistered) return;
|
|
37
|
+
handlersRegistered = true;
|
|
38
|
+
const onSignal = (signal) => async () => {
|
|
39
|
+
devServeLog(colors.yellow(`📡 Received ${signal}`));
|
|
40
|
+
const ok = await safeShutdown(devServer);
|
|
41
|
+
process.exit(ok ? 0 : 1);
|
|
42
|
+
};
|
|
43
|
+
process.on("SIGINT", onSignal("SIGINT"));
|
|
44
|
+
process.on("SIGTERM", onSignal("SIGTERM"));
|
|
45
|
+
if (process.platform === "win32") process.on("SIGHUP", onSignal("SIGHUP"));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Shut down the dev server without swallowing async errors. Returns
|
|
49
|
+
* whether shutdown completed cleanly so the caller picks the exit code.
|
|
50
|
+
*/
|
|
51
|
+
async function safeShutdown(devServer) {
|
|
52
|
+
try {
|
|
53
|
+
await devServer.shutdown();
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
devLogError(`Shutdown failed: ${error.message}`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { startDevelopmentServer };
|
|
63
|
+
//# sourceMappingURL=start-development-server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-development-server.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/start-development-server.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { devLogError, devServeLog } from \"./dev-logger\";\r\nimport { DevelopmentServer } from \"./development-server\";\r\n\r\nlet handlersRegistered = false;\r\n\r\n/**\r\n * Options accepted by `startDevelopmentServer`. CLI flags translate to\r\n * these; programmatic callers (tests, scripts) can pass them directly.\r\n *\r\n * `undefined` for any field means \"fall through to `warlock.config.ts >\r\n * devServer.*` defaults.\" Pass an explicit value only to override.\r\n */\r\nexport type StartDevServerOptions = {\r\n /**\r\n * Delete `.warlock/manifest.json` before starting — forces every file\r\n * to be re-parsed from disk instead of restored from the cached graph.\r\n */\r\n fresh?: boolean;\r\n /** Run background type generation. Default from devServer config (true). */\r\n generateTypings?: boolean;\r\n /** Run file health checkers. Default from devServer config (true). */\r\n healthCheckers?: boolean;\r\n};\r\n\r\n/**\r\n * Start the development server.\r\n *\r\n * Boots the file orchestrator, loads app modules, starts late-phase\r\n * connectors, then wires graceful shutdown for SIGINT/SIGTERM (plus\r\n * SIGHUP on Windows where Ctrl+C in spawned children lands as SIGHUP).\r\n *\r\n * If startup throws, shuts down cleanly and exits 1 — dev should die\r\n * loudly rather than hang with a half-initialized state.\r\n */\r\nexport async function startDevelopmentServer(\r\n options: StartDevServerOptions = {},\r\n): Promise<DevelopmentServer> {\r\n // V8 honours inline source maps embedded by the transpile cache (and\r\n // tsx's own maps in passthrough mode), so thrown-error stack frames point\r\n // at the original `src/**.ts` line/column instead of transpiled output.\r\n process.setSourceMapsEnabled(true);\r\n\r\n const devServer = new DevelopmentServer(options);\r\n\r\n registerShutdownHandlers(devServer);\r\n\r\n try {\r\n await devServer.start();\r\n } catch (error) {\r\n devLogError(`Failed to start Development Server: ${(error as Error).message}`);\r\n await safeShutdown(devServer);\r\n process.exit(1);\r\n }\r\n\r\n return devServer;\r\n}\r\n\r\n/**\r\n * Register signal handlers once per process. Guards against handler\r\n * accumulation if `startDevelopmentServer` is invoked more than once\r\n * (tests, programmatic restart).\r\n */\r\nfunction registerShutdownHandlers(devServer: DevelopmentServer): void {\r\n if (handlersRegistered) return;\r\n handlersRegistered = true;\r\n\r\n const onSignal = (signal: NodeJS.Signals) => async () => {\r\n devServeLog(colors.yellow(`📡 Received ${signal}`));\r\n const ok = await safeShutdown(devServer);\r\n process.exit(ok ? 0 : 1);\r\n };\r\n\r\n process.on(\"SIGINT\", onSignal(\"SIGINT\"));\r\n process.on(\"SIGTERM\", onSignal(\"SIGTERM\"));\r\n // Ctrl+C in spawned Windows children lands as SIGHUP, not SIGINT.\r\n if (process.platform === \"win32\") {\r\n process.on(\"SIGHUP\", onSignal(\"SIGHUP\"));\r\n }\r\n}\r\n\r\n/**\r\n * Shut down the dev server without swallowing async errors. Returns\r\n * whether shutdown completed cleanly so the caller picks the exit code.\r\n */\r\nasync function safeShutdown(devServer: DevelopmentServer): Promise<boolean> {\r\n try {\r\n await devServer.shutdown();\r\n return true;\r\n } catch (error) {\r\n devLogError(`Shutdown failed: ${(error as Error).message}`);\r\n return false;\r\n }\r\n}\r\n"],"mappings":";;;;;AAIA,IAAI,qBAAqB;;;;;;;;;;;AA+BzB,eAAsB,uBACpB,UAAiC,CAAC,GACN;CAI5B,QAAQ,qBAAqB,IAAI;CAEjC,MAAM,YAAY,IAAI,kBAAkB,OAAO;CAE/C,yBAAyB,SAAS;CAElC,IAAI;EACF,MAAM,UAAU,MAAM;CACxB,SAAS,OAAO;EACd,YAAY,uCAAwC,MAAgB,SAAS;EAC7E,MAAM,aAAa,SAAS;EAC5B,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,yBAAyB,WAAoC;CACpE,IAAI,oBAAoB;CACxB,qBAAqB;CAErB,MAAM,YAAY,WAA2B,YAAY;EACvD,YAAY,OAAO,OAAO,eAAe,QAAQ,CAAC;EAClD,MAAM,KAAK,MAAM,aAAa,SAAS;EACvC,QAAQ,KAAK,KAAK,IAAI,CAAC;CACzB;CAEA,QAAQ,GAAG,UAAU,SAAS,QAAQ,CAAC;CACvC,QAAQ,GAAG,WAAW,SAAS,SAAS,CAAC;CAEzC,IAAI,QAAQ,aAAa,SACvB,QAAQ,GAAG,UAAU,SAAS,QAAQ,CAAC;AAE3C;;;;;AAMA,eAAe,aAAa,WAAgD;CAC1E,IAAI;EACF,MAAM,UAAU,SAAS;EACzB,OAAO;CACT,SAAS,OAAO;EACd,YAAY,oBAAqB,MAAgB,SAAS;EAC1D,OAAO;CACT;AACF"}
|