@warlock.js/core 4.2.11 → 4.4.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/esm/application/application.d.mts +126 -4
  3. package/esm/application/application.d.mts.map +1 -1
  4. package/esm/application/application.mjs +133 -0
  5. package/esm/application/application.mjs.map +1 -1
  6. package/esm/application/index.d.mts +1 -1
  7. package/esm/cli/commands/dev-server.command.mjs +4 -2
  8. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  9. package/esm/cli/commands/update.command.mjs +20 -0
  10. package/esm/cli/commands/update.command.mjs.map +1 -0
  11. package/esm/cli/framework-cli-commands.mjs +2 -0
  12. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  13. package/esm/cli/types.d.mts +2 -1
  14. package/esm/cli/types.d.mts.map +1 -1
  15. package/esm/config/config-getter.mjs +5 -5
  16. package/esm/config/config-getter.mjs.map +1 -1
  17. package/esm/config/config-loader.mjs +2 -2
  18. package/esm/config/config-loader.mjs.map +1 -1
  19. package/esm/connectors/access-connector.mjs +2 -2
  20. package/esm/connectors/access-connector.mjs.map +1 -1
  21. package/esm/connectors/cache-connector.mjs +2 -2
  22. package/esm/connectors/cache-connector.mjs.map +1 -1
  23. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  24. package/esm/connectors/connectors-manager.mjs +4 -1
  25. package/esm/connectors/connectors-manager.mjs.map +1 -1
  26. package/esm/connectors/database-connector.mjs +2 -2
  27. package/esm/connectors/database-connector.mjs.map +1 -1
  28. package/esm/connectors/herald-connector.mjs +2 -2
  29. package/esm/connectors/herald-connector.mjs.map +1 -1
  30. package/esm/connectors/http-connector.d.mts +8 -0
  31. package/esm/connectors/http-connector.d.mts.map +1 -1
  32. package/esm/connectors/http-connector.mjs +40 -7
  33. package/esm/connectors/http-connector.mjs.map +1 -1
  34. package/esm/connectors/logger-connector.mjs +2 -2
  35. package/esm/connectors/logger-connector.mjs.map +1 -1
  36. package/esm/connectors/mail-connector.mjs +2 -2
  37. package/esm/connectors/mail-connector.mjs.map +1 -1
  38. package/esm/connectors/notifications-connector.mjs +2 -2
  39. package/esm/connectors/notifications-connector.mjs.map +1 -1
  40. package/esm/connectors/socket-connector.mjs +3 -3
  41. package/esm/connectors/socket-connector.mjs.map +1 -1
  42. package/esm/dev-server/check-for-updates.mjs +59 -0
  43. package/esm/dev-server/check-for-updates.mjs.map +1 -0
  44. package/esm/dev-server/development-server.mjs +7 -0
  45. package/esm/dev-server/development-server.mjs.map +1 -1
  46. package/esm/dev-server/loader/transpile-cache.mjs +1 -1
  47. package/esm/http/config.mjs +2 -2
  48. package/esm/http/config.mjs.map +1 -1
  49. package/esm/http/createHttpApplication.mjs +2 -2
  50. package/esm/http/createHttpApplication.mjs.map +1 -1
  51. package/esm/http/health.d.mts +57 -0
  52. package/esm/http/health.d.mts.map +1 -0
  53. package/esm/http/health.mjs +75 -0
  54. package/esm/http/health.mjs.map +1 -0
  55. package/esm/http/index.d.mts +2 -1
  56. package/esm/http/index.mjs +2 -1
  57. package/esm/http/middleware/idempotency.middleware.mjs +5 -5
  58. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  59. package/esm/http/middleware/inject-request-context.mjs +2 -2
  60. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  61. package/esm/http/middleware/maintenance.middleware.mjs +4 -4
  62. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  63. package/esm/http/plugins.mjs +9 -9
  64. package/esm/http/plugins.mjs.map +1 -1
  65. package/esm/http/response.mjs +5 -5
  66. package/esm/http/response.mjs.map +1 -1
  67. package/esm/http/server.d.mts +16 -1
  68. package/esm/http/server.d.mts.map +1 -1
  69. package/esm/http/server.mjs +23 -3
  70. package/esm/http/server.mjs.map +1 -1
  71. package/esm/http/types.d.mts +45 -0
  72. package/esm/http/types.d.mts.map +1 -1
  73. package/esm/index.d.mts +7 -4
  74. package/esm/index.mjs +8 -5
  75. package/esm/production/production-builder.mjs +2 -2
  76. package/esm/production/production-builder.mjs.map +1 -1
  77. package/esm/updater/package-manager.mjs +32 -0
  78. package/esm/updater/package-manager.mjs.map +1 -0
  79. package/esm/updater/update-warlock-packages.mjs +134 -0
  80. package/esm/updater/update-warlock-packages.mjs.map +1 -0
  81. package/esm/utils/environment.d.mts +6 -1
  82. package/esm/utils/environment.d.mts.map +1 -1
  83. package/esm/utils/environment.mjs.map +1 -1
  84. package/esm/utils/index.d.mts +4 -2
  85. package/esm/utils/index.mjs +2 -0
  86. package/esm/utils/npm-registry.d.mts +24 -0
  87. package/esm/utils/npm-registry.d.mts.map +1 -0
  88. package/esm/utils/npm-registry.mjs +43 -0
  89. package/esm/utils/npm-registry.mjs.map +1 -0
  90. package/esm/utils/paths.mjs +2 -2
  91. package/esm/utils/paths.mjs.map +1 -1
  92. package/esm/utils/version-compare.d.mts +20 -0
  93. package/esm/utils/version-compare.d.mts.map +1 -0
  94. package/esm/utils/version-compare.mjs +70 -0
  95. package/esm/utils/version-compare.mjs.map +1 -0
  96. package/esm/validation/validateAll.mjs +2 -2
  97. package/esm/validation/validateAll.mjs.map +1 -1
  98. package/esm/warlock-config/types.d.mts +9 -0
  99. package/esm/warlock-config/types.d.mts.map +1 -1
  100. package/llms-full.txt +249 -2
  101. package/llms.txt +3 -1
  102. package/package.json +9 -9
  103. package/skills/health-checks/SKILL.md +98 -0
  104. package/skills/run-app/SKILL.md +3 -0
  105. package/skills/update-packages/SKILL.md +78 -0
  106. package/skills/use-app-context/SKILL.md +62 -2
@@ -1 +1 @@
1
- {"version":3,"file":"production-builder.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/production/production-builder.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n ensureDirectoryAsync,\r\n fileExistsAsync,\r\n putFileAsync,\r\n removeDirectoryAsync,\r\n} from \"@warlock.js/fs\";\r\nimport esbuild from \"esbuild\";\r\nimport glob from \"fast-glob\";\r\nimport path from \"path\";\r\nimport { tsconfigManager } from \"../dev-server/tsconfig-manager\";\r\nimport { appPath, warlockPath } from \"../utils\";\r\nimport { nativeNodeModulesPlugin } from \"./esbuild-plugins\";\r\nimport { resolveBuildConfig, type ResolvedBuildConfig } from \"./resolve-build-config\";\r\nimport { toCamelCase, toKebabCase } from \"@mongez/reinforcements\";\r\n\r\n/**\r\n * Production Builder\r\n * Generates production-ready files and bundles them for deployment\r\n * Build options are loaded from warlock.config.ts\r\n */\r\nexport class ProductionBuilder {\r\n private options!: ResolvedBuildConfig;\r\n private readonly productionDir = warlockPath(\"production\");\r\n\r\n /**\r\n * Main build entry point\r\n */\r\n public async build(): Promise<void> {\r\n console.log(colors.cyan(\"Building for production...\\n\"));\r\n\r\n // Step 1: Initialize options from config\r\n await this.initializeOptions();\r\n\r\n // Step 2: Generate combined files\r\n await this.generateCombinedFiles();\r\n\r\n // Step 3: Generate entry point\r\n await this.generateEntryPoint();\r\n\r\n // Step 4: Bundle with esbuild\r\n await this.bundle();\r\n\r\n // // Step 5: Remove production folder\r\n await removeDirectoryAsync(this.productionDir);\r\n\r\n console.log(colors.green(\"Build complete!\"));\r\n console.log(`Start production server by running ${colors.cyan(\"warlock start\")}`);\r\n }\r\n\r\n /**\r\n * Initialize options from warlock.config.ts\r\n */\r\n private async initializeOptions(): Promise<void> {\r\n this.options = resolveBuildConfig();\r\n\r\n // Ensure production directory exists\r\n await ensureDirectoryAsync(this.productionDir);\r\n }\r\n\r\n /**\r\n * Track which special files were generated\r\n */\r\n private generatedFiles = {\r\n locales: false,\r\n events: false,\r\n main: false,\r\n routes: false,\r\n };\r\n\r\n /**\r\n * Generate all combined files\r\n */\r\n private async generateCombinedFiles(): Promise<void> {\r\n console.log(colors.yellow(\" Generating production files...\"));\r\n\r\n // Generate bootstrap.ts file\r\n await this.generateBootstrap();\r\n\r\n // Generate config loader\r\n await this.generateConfigLoader();\r\n\r\n // Generate special files and track which ones have content\r\n const [locales, events, main, routes] = await Promise.all([\r\n this.generateLocales(),\r\n this.generateEvents(),\r\n this.generateMain(),\r\n this.generateRoutes(),\r\n ]);\r\n\r\n this.generatedFiles = { locales, events, main, routes };\r\n }\r\n\r\n /**\r\n * Generate bootstrap.ts - ensures bootstrap() runs first and sets production environment\r\n */\r\n private async generateBootstrap(): Promise<void> {\r\n let content = `import { bootstrap, Application } from \"@warlock.js/core\";\r\n\r\n// Set production environment\r\nApplication.setRuntimeStrategy(\"production\");\r\nApplication.setEnvironment(\"production\");\r\n\r\n// Bootstrap the application\r\nbootstrap();\r\n`;\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n content += \"import './../../src/app/bootstrap';\\n\";\r\n }\r\n\r\n await putFileAsync(path.join(this.productionDir, \"bootstrap.ts\"), content);\r\n }\r\n\r\n /**\r\n * Glob for module files matching a pattern\r\n * Returns relative paths from .warlock/production/ to src/app/\r\n */\r\n private async globModule(fileName: string): Promise<string[]> {\r\n const pattern = `**/${fileName}.{ts,tsx}`;\r\n const appDirectory = appPath();\r\n\r\n const files = await glob(pattern, {\r\n cwd: appDirectory,\r\n absolute: false,\r\n });\r\n\r\n // Convert to relative paths from .warlock/production/ to src/app/\r\n // e.g., \"users/main\" -> \"../../src/app/users/main\"\r\n return files.map((file) => \"../../src/app/\" + file.replace(/\\.(ts|tsx)$/, \"\"));\r\n }\r\n\r\n /**\r\n * Glob for files in a specific directory pattern\r\n * Returns relative paths from .warlock/production/ to src/app/\r\n */\r\n private async globModuleDirectory(directory: string): Promise<string[]> {\r\n const pattern = `**/${directory}/*.{ts,tsx}`;\r\n const appDirectory = appPath();\r\n\r\n const files = await glob(pattern, {\r\n cwd: appDirectory,\r\n absolute: false,\r\n });\r\n\r\n return files.map((file) => \"../../src/app/\" + file.replace(/\\.(ts|tsx)$/, \"\"));\r\n }\r\n\r\n /**\r\n * Generate config-loader.ts\r\n */\r\n private async generateConfigLoader(): Promise<void> {\r\n const configDirectory = path.join(process.cwd(), \"src/config\");\r\n\r\n const files = await glob(\"*.{ts,tsx}\", {\r\n cwd: configDirectory,\r\n absolute: false,\r\n });\r\n\r\n const configNames = files.map((f) => f.replace(/\\.(ts|tsx)$/, \"\"));\r\n\r\n const imports: string[] = [\r\n 'import config from \"@mongez/config\";',\r\n 'import { configSpecialHandlers } from \"@warlock.js/core\";',\r\n ];\r\n const configImports: string[] = [];\r\n const configSetCalls: string[] = [];\r\n const executors: string[] = [];\r\n\r\n for (const configName of configNames) {\r\n const properConfigName = toCamelCase(configName);\r\n const varName = `${properConfigName}Config`;\r\n configImports.push(`import ${varName} from \"../../src/config/${configName}\";`);\r\n configSetCalls.push(`config.set(\"${properConfigName}\", ${varName});`);\r\n executors.push(`await configSpecialHandlers.execute(\"${properConfigName}\", ${varName});`);\r\n }\r\n\r\n let content = [\r\n ...imports,\r\n \"\",\r\n \"// Config imports\",\r\n ...configImports,\r\n \"\",\r\n \"// Register configs\",\r\n ...configSetCalls,\r\n \"\",\r\n \"// Special handlers\",\r\n ...executors,\r\n \"\",\r\n ].join(\"\\n\");\r\n\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n content += \"import './../../src/app/prestart';\\n\";\r\n }\r\n\r\n await putFileAsync(path.join(this.productionDir, \"config-loader.ts\"), content);\r\n }\r\n\r\n /**\r\n * Generate locales.ts (only if there are locale files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateLocales(): Promise<boolean> {\r\n const files = await this.globModule(\"utils/locales\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"locales.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate events.ts (only if there are event files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateEvents(): Promise<boolean> {\r\n const files = await this.globModuleDirectory(\"events\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"events.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate main.ts (only if there are main files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateMain(): Promise<boolean> {\r\n const files = await this.globModule(\"main\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"main.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate routes.ts (only if there are route files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateRoutes(): Promise<boolean> {\r\n const files = await this.globModule(\"routes\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"routes.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate a file with imports from all given files\r\n */\r\n private async generateImportsFile(importPaths: string[], outputFile: string): Promise<void> {\r\n const imports = importPaths.map((importPath) => `import \"${importPath}\";`);\r\n const content = imports.join(\"\\n\") + \"\\n\";\r\n await putFileAsync(path.join(this.productionDir, outputFile), content);\r\n }\r\n\r\n /**\r\n * Generate the main entry point (app.ts)\r\n */\r\n private async generateEntryPoint(): Promise<void> {\r\n console.log(colors.yellow(\" Generating entry point...\"));\r\n\r\n // Build imports based on which files were generated\r\n const imports: string[] = [\r\n \"// 1. Bootstrap (loads .env, initializes framework)\",\r\n 'import \"./bootstrap\";',\r\n \"\",\r\n \"// 2. Load configs\",\r\n 'import \"./config-loader\";',\r\n \"\",\r\n \"// 3. Start early-phase connectors (database, cache, logger, ...)\",\r\n \"// so data sources, cache, etc. are ready before app code runs\",\r\n 'import { connectorsManager, ConnectorLifecyclePhase } from \"@warlock.js/core\";',\r\n \"await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\",\r\n ];\r\n\r\n // App code uses dynamic `await import(...)` so each module's side\r\n // effects fire at THIS point in execution. Static imports would be\r\n // hoisted to module-instantiation time (before the early-phase await\r\n // resolves), which is exactly the bug this split is meant to fix.\r\n // Requires `splitting: true` in esbuild (set below in bundle()).\r\n imports.push(\"\", \"// 4. Load app code (events, locales, main, routes)\");\r\n\r\n if (this.generatedFiles.events) {\r\n imports.push('await import(\"./events\");');\r\n }\r\n if (this.generatedFiles.locales) {\r\n imports.push('await import(\"./locales\");');\r\n }\r\n if (this.generatedFiles.main) {\r\n imports.push('await import(\"./main\");');\r\n }\r\n if (this.generatedFiles.routes) {\r\n imports.push('await import(\"./routes\");');\r\n }\r\n\r\n // Start late-phase connectors after app code registers routes/listeners\r\n imports.push(\r\n \"\",\r\n \"// 5. Start late-phase connectors (http, socket) — routes and\",\r\n \"// listeners registered by app code are now ready to bind\",\r\n \"await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\",\r\n \"connectorsManager.shutdownOnProcessKill();\",\r\n );\r\n\r\n const content = imports.join(\"\\n\") + \"\\n\";\r\n await putFileAsync(path.join(this.productionDir, \"app.ts\"), content);\r\n }\r\n\r\n /**\r\n * Bundle with esbuild\r\n */\r\n private async bundle(): Promise<void> {\r\n console.log(colors.magenta(\" Bundling with esbuild...\"));\r\n\r\n const entryPoint = path.join(this.productionDir, \"app.ts\");\r\n const outDir = this.options!.outDirectory!;\r\n const outFileName = this.options!.outFile!;\r\n // Strip extension so entryNames produces \"<base>.js\" via esbuild\r\n const entryName = path.basename(outFileName, path.extname(outFileName));\r\n\r\n await ensureDirectoryAsync(outDir);\r\n\r\n const alias = this.buildAliasMapFromTsconfig();\r\n\r\n await esbuild.build({\r\n platform: \"node\",\r\n entryPoints: [entryPoint],\r\n bundle: true,\r\n // Required so dynamic `await import(\"./main\")` in the generated\r\n // app.ts produces separate chunks loaded at the runtime call site,\r\n // instead of inlining the modules at instantiation time (which\r\n // would defeat the early/late phase split).\r\n splitting: true,\r\n packages: \"external\",\r\n minify: this.options!.minify,\r\n sourcemap: this.options!.sourcemap === true ? \"linked\" : this.options!.sourcemap,\r\n format: \"esm\",\r\n // Targeting a concrete Node version (not \"esnext\") so esbuild\r\n // transpiles TC39 stage 3 decorators into helpers — Node does not\r\n // implement them natively yet.\r\n target: [\"node22\"],\r\n outdir: outDir,\r\n entryNames: entryName,\r\n alias,\r\n plugins: [nativeNodeModulesPlugin],\r\n });\r\n }\r\n\r\n /**\r\n * Build an alias map from tsconfig `paths` so esbuild resolves local\r\n * source aliases (e.g. `@warlock.js/cascade`) to their on-disk source\r\n * folders during bundling. Without this, `packages: \"external\"` would\r\n * leave those bare specifiers as raw imports and Node would fail to\r\n * resolve them at runtime (they aren't installed in node_modules).\r\n *\r\n * Only exact (non-wildcard) aliases are included — esbuild's `alias`\r\n * option doesn't support glob-style mappings.\r\n */\r\n private buildAliasMapFromTsconfig(): Record<string, string> {\r\n tsconfigManager.init();\r\n\r\n const alias: Record<string, string> = {};\r\n const baseUrl = path.resolve(process.cwd(), tsconfigManager.baseUrl);\r\n\r\n for (const [from, to] of Object.entries(tsconfigManager.aliases)) {\r\n if (from.endsWith(\"/*\") || !Array.isArray(to) || to.length === 0) {\r\n continue;\r\n }\r\n\r\n alias[from] = path.resolve(baseUrl, to[0]);\r\n }\r\n\r\n return alias;\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"production-builder.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/production/production-builder.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport {\r\n ensureDirectoryAsync,\r\n fileExistsAsync,\r\n putFileAsync,\r\n removeDirectoryAsync,\r\n} from \"@warlock.js/fs\";\r\nimport esbuild from \"esbuild\";\r\nimport glob from \"fast-glob\";\r\nimport path from \"path\";\r\nimport { tsconfigManager } from \"../dev-server/tsconfig-manager\";\r\nimport { appPath, warlockPath } from \"../utils\";\r\nimport { nativeNodeModulesPlugin } from \"./esbuild-plugins\";\r\nimport { resolveBuildConfig, type ResolvedBuildConfig } from \"./resolve-build-config\";\r\nimport { toCamelCase, toKebabCase } from \"@mongez/reinforcements\";\r\n\r\n/**\r\n * Production Builder\r\n * Generates production-ready files and bundles them for deployment\r\n * Build options are loaded from warlock.config.ts\r\n */\r\nexport class ProductionBuilder {\r\n private options!: ResolvedBuildConfig;\r\n private readonly productionDir = warlockPath(\"production\");\r\n\r\n /**\r\n * Main build entry point\r\n */\r\n public async build(): Promise<void> {\r\n console.log(colors.cyan(\"Building for production...\\n\"));\r\n\r\n // Step 1: Initialize options from config\r\n await this.initializeOptions();\r\n\r\n // Step 2: Generate combined files\r\n await this.generateCombinedFiles();\r\n\r\n // Step 3: Generate entry point\r\n await this.generateEntryPoint();\r\n\r\n // Step 4: Bundle with esbuild\r\n await this.bundle();\r\n\r\n // // Step 5: Remove production folder\r\n await removeDirectoryAsync(this.productionDir);\r\n\r\n console.log(colors.green(\"Build complete!\"));\r\n console.log(`Start production server by running ${colors.cyan(\"warlock start\")}`);\r\n }\r\n\r\n /**\r\n * Initialize options from warlock.config.ts\r\n */\r\n private async initializeOptions(): Promise<void> {\r\n this.options = resolveBuildConfig();\r\n\r\n // Ensure production directory exists\r\n await ensureDirectoryAsync(this.productionDir);\r\n }\r\n\r\n /**\r\n * Track which special files were generated\r\n */\r\n private generatedFiles = {\r\n locales: false,\r\n events: false,\r\n main: false,\r\n routes: false,\r\n };\r\n\r\n /**\r\n * Generate all combined files\r\n */\r\n private async generateCombinedFiles(): Promise<void> {\r\n console.log(colors.yellow(\" Generating production files...\"));\r\n\r\n // Generate bootstrap.ts file\r\n await this.generateBootstrap();\r\n\r\n // Generate config loader\r\n await this.generateConfigLoader();\r\n\r\n // Generate special files and track which ones have content\r\n const [locales, events, main, routes] = await Promise.all([\r\n this.generateLocales(),\r\n this.generateEvents(),\r\n this.generateMain(),\r\n this.generateRoutes(),\r\n ]);\r\n\r\n this.generatedFiles = { locales, events, main, routes };\r\n }\r\n\r\n /**\r\n * Generate bootstrap.ts - ensures bootstrap() runs first and sets production environment\r\n */\r\n private async generateBootstrap(): Promise<void> {\r\n let content = `import { bootstrap, Application } from \"@warlock.js/core\";\r\n\r\n// Set production environment\r\nApplication.setRuntimeStrategy(\"production\");\r\nApplication.setEnvironment(\"production\");\r\n\r\n// Bootstrap the application\r\nbootstrap();\r\n`;\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n content += \"import './../../src/app/bootstrap';\\n\";\r\n }\r\n\r\n await putFileAsync(path.join(this.productionDir, \"bootstrap.ts\"), content);\r\n }\r\n\r\n /**\r\n * Glob for module files matching a pattern\r\n * Returns relative paths from .warlock/production/ to src/app/\r\n */\r\n private async globModule(fileName: string): Promise<string[]> {\r\n const pattern = `**/${fileName}.{ts,tsx}`;\r\n const appDirectory = appPath();\r\n\r\n const files = await glob(pattern, {\r\n cwd: appDirectory,\r\n absolute: false,\r\n });\r\n\r\n // Convert to relative paths from .warlock/production/ to src/app/\r\n // e.g., \"users/main\" -> \"../../src/app/users/main\"\r\n return files.map((file) => \"../../src/app/\" + file.replace(/\\.(ts|tsx)$/, \"\"));\r\n }\r\n\r\n /**\r\n * Glob for files in a specific directory pattern\r\n * Returns relative paths from .warlock/production/ to src/app/\r\n */\r\n private async globModuleDirectory(directory: string): Promise<string[]> {\r\n const pattern = `**/${directory}/*.{ts,tsx}`;\r\n const appDirectory = appPath();\r\n\r\n const files = await glob(pattern, {\r\n cwd: appDirectory,\r\n absolute: false,\r\n });\r\n\r\n return files.map((file) => \"../../src/app/\" + file.replace(/\\.(ts|tsx)$/, \"\"));\r\n }\r\n\r\n /**\r\n * Generate config-loader.ts\r\n */\r\n private async generateConfigLoader(): Promise<void> {\r\n const configDirectory = path.join(process.cwd(), \"src/config\");\r\n\r\n const files = await glob(\"*.{ts,tsx}\", {\r\n cwd: configDirectory,\r\n absolute: false,\r\n });\r\n\r\n const configNames = files.map((f) => f.replace(/\\.(ts|tsx)$/, \"\"));\r\n\r\n const imports: string[] = [\r\n 'import config from \"@mongez/config\";',\r\n 'import { configSpecialHandlers } from \"@warlock.js/core\";',\r\n ];\r\n const configImports: string[] = [];\r\n const configSetCalls: string[] = [];\r\n const executors: string[] = [];\r\n\r\n for (const configName of configNames) {\r\n const properConfigName = toCamelCase(configName);\r\n const varName = `${properConfigName}Config`;\r\n configImports.push(`import ${varName} from \"../../src/config/${configName}\";`);\r\n configSetCalls.push(`config.set(\"${properConfigName}\", ${varName});`);\r\n executors.push(`await configSpecialHandlers.execute(\"${properConfigName}\", ${varName});`);\r\n }\r\n\r\n let content = [\r\n ...imports,\r\n \"\",\r\n \"// Config imports\",\r\n ...configImports,\r\n \"\",\r\n \"// Register configs\",\r\n ...configSetCalls,\r\n \"\",\r\n \"// Special handlers\",\r\n ...executors,\r\n \"\",\r\n ].join(\"\\n\");\r\n\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n content += \"import './../../src/app/prestart';\\n\";\r\n }\r\n\r\n await putFileAsync(path.join(this.productionDir, \"config-loader.ts\"), content);\r\n }\r\n\r\n /**\r\n * Generate locales.ts (only if there are locale files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateLocales(): Promise<boolean> {\r\n const files = await this.globModule(\"utils/locales\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"locales.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate events.ts (only if there are event files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateEvents(): Promise<boolean> {\r\n const files = await this.globModuleDirectory(\"events\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"events.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate main.ts (only if there are main files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateMain(): Promise<boolean> {\r\n const files = await this.globModule(\"main\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"main.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate routes.ts (only if there are route files)\r\n * @returns true if file was generated with content\r\n */\r\n private async generateRoutes(): Promise<boolean> {\r\n const files = await this.globModule(\"routes\");\r\n if (files.length === 0) return false;\r\n await this.generateImportsFile(files, \"routes.ts\");\r\n return true;\r\n }\r\n\r\n /**\r\n * Generate a file with imports from all given files\r\n */\r\n private async generateImportsFile(importPaths: string[], outputFile: string): Promise<void> {\r\n const imports = importPaths.map((importPath) => `import \"${importPath}\";`);\r\n const content = imports.join(\"\\n\") + \"\\n\";\r\n await putFileAsync(path.join(this.productionDir, outputFile), content);\r\n }\r\n\r\n /**\r\n * Generate the main entry point (app.ts)\r\n */\r\n private async generateEntryPoint(): Promise<void> {\r\n console.log(colors.yellow(\" Generating entry point...\"));\r\n\r\n // Build imports based on which files were generated\r\n const imports: string[] = [\r\n \"// 1. Bootstrap (loads .env, initializes framework)\",\r\n 'import \"./bootstrap\";',\r\n \"\",\r\n \"// 2. Load configs\",\r\n 'import \"./config-loader\";',\r\n \"\",\r\n \"// 3. Start early-phase connectors (database, cache, logger, ...)\",\r\n \"// so data sources, cache, etc. are ready before app code runs\",\r\n 'import { Application, connectorsManager, ConnectorLifecyclePhase } from \"@warlock.js/core\";',\r\n \"await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\",\r\n ];\r\n\r\n // App code uses dynamic `await import(...)` so each module's side\r\n // effects fire at THIS point in execution. Static imports would be\r\n // hoisted to module-instantiation time (before the early-phase await\r\n // resolves), which is exactly the bug this split is meant to fix.\r\n // Requires `splitting: true` in esbuild (set below in bundle()).\r\n imports.push(\"\", \"// 4. Load app code (events, locales, main, routes)\");\r\n\r\n if (this.generatedFiles.events) {\r\n imports.push('await import(\"./events\");');\r\n }\r\n if (this.generatedFiles.locales) {\r\n imports.push('await import(\"./locales\");');\r\n }\r\n if (this.generatedFiles.main) {\r\n imports.push('await import(\"./main\");');\r\n }\r\n if (this.generatedFiles.routes) {\r\n imports.push('await import(\"./routes\");');\r\n }\r\n\r\n // Start late-phase connectors after app code registers routes/listeners\r\n imports.push(\r\n \"\",\r\n \"// 5. Start late-phase connectors (http, socket) — routes and\",\r\n \"// listeners registered by app code are now ready to bind\",\r\n \"await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\",\r\n \"\",\r\n \"// 6. Signal a complete boot so `Application.onceBooted(...)` listeners fire\",\r\n \"Application.markBooted({ environment: Application.environment, runtimeStrategy: Application.runtimeStrategy });\",\r\n \"connectorsManager.shutdownOnProcessKill();\",\r\n );\r\n\r\n const content = imports.join(\"\\n\") + \"\\n\";\r\n await putFileAsync(path.join(this.productionDir, \"app.ts\"), content);\r\n }\r\n\r\n /**\r\n * Bundle with esbuild\r\n */\r\n private async bundle(): Promise<void> {\r\n console.log(colors.magenta(\" Bundling with esbuild...\"));\r\n\r\n const entryPoint = path.join(this.productionDir, \"app.ts\");\r\n const outDir = this.options!.outDirectory!;\r\n const outFileName = this.options!.outFile!;\r\n // Strip extension so entryNames produces \"<base>.js\" via esbuild\r\n const entryName = path.basename(outFileName, path.extname(outFileName));\r\n\r\n await ensureDirectoryAsync(outDir);\r\n\r\n const alias = this.buildAliasMapFromTsconfig();\r\n\r\n await esbuild.build({\r\n platform: \"node\",\r\n entryPoints: [entryPoint],\r\n bundle: true,\r\n // Required so dynamic `await import(\"./main\")` in the generated\r\n // app.ts produces separate chunks loaded at the runtime call site,\r\n // instead of inlining the modules at instantiation time (which\r\n // would defeat the early/late phase split).\r\n splitting: true,\r\n packages: \"external\",\r\n minify: this.options!.minify,\r\n sourcemap: this.options!.sourcemap === true ? \"linked\" : this.options!.sourcemap,\r\n format: \"esm\",\r\n // Targeting a concrete Node version (not \"esnext\") so esbuild\r\n // transpiles TC39 stage 3 decorators into helpers — Node does not\r\n // implement them natively yet.\r\n target: [\"node22\"],\r\n outdir: outDir,\r\n entryNames: entryName,\r\n alias,\r\n plugins: [nativeNodeModulesPlugin],\r\n });\r\n }\r\n\r\n /**\r\n * Build an alias map from tsconfig `paths` so esbuild resolves local\r\n * source aliases (e.g. `@warlock.js/cascade`) to their on-disk source\r\n * folders during bundling. Without this, `packages: \"external\"` would\r\n * leave those bare specifiers as raw imports and Node would fail to\r\n * resolve them at runtime (they aren't installed in node_modules).\r\n *\r\n * Only exact (non-wildcard) aliases are included — esbuild's `alias`\r\n * option doesn't support glob-style mappings.\r\n */\r\n private buildAliasMapFromTsconfig(): Record<string, string> {\r\n tsconfigManager.init();\r\n\r\n const alias: Record<string, string> = {};\r\n const baseUrl = path.resolve(process.cwd(), tsconfigManager.baseUrl);\r\n\r\n for (const [from, to] of Object.entries(tsconfigManager.aliases)) {\r\n if (from.endsWith(\"/*\") || !Array.isArray(to) || to.length === 0) {\r\n continue;\r\n }\r\n\r\n alias[from] = path.resolve(baseUrl, to[0]);\r\n }\r\n\r\n return alias;\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { rootPath } from "../utils/paths.mjs";
2
+ import "../utils/index.mjs";
3
+ import { fileExistsAsync } from "@warlock.js/fs";
4
+
5
+ //#region ../@warlock.js/core/src/updater/package-manager.ts
6
+ /**
7
+ * Detect the project's package manager from its lockfile, falling back to
8
+ * npm when none is present. The lookup order matches `warlock add`, so both
9
+ * commands agree on a project that happens to carry more than one lockfile.
10
+ */
11
+ async function detectPackageManager() {
12
+ if (await fileExistsAsync(rootPath("package-lock.json"))) return "npm";
13
+ if (await fileExistsAsync(rootPath("yarn.lock"))) return "yarn";
14
+ if (await fileExistsAsync(rootPath("pnpm-lock.yaml"))) return "pnpm";
15
+ return "npm";
16
+ }
17
+ /**
18
+ * The lockfile-syncing install command for the given manager. No package
19
+ * arguments — `warlock update` rewrites the versions in package.json first,
20
+ * then a plain install reconciles `node_modules` to match.
21
+ */
22
+ function getInstallCommand(packageManager) {
23
+ switch (packageManager) {
24
+ case "yarn": return "yarn install";
25
+ case "pnpm": return "pnpm install";
26
+ default: return "npm install";
27
+ }
28
+ }
29
+
30
+ //#endregion
31
+ export { detectPackageManager, getInstallCommand };
32
+ //# sourceMappingURL=package-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/updater/package-manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\nimport { rootPath } from \"../utils\";\n\n/** Package managers the framework knows how to drive. */\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\";\n\n/**\n * Detect the project's package manager from its lockfile, falling back to\n * npm when none is present. The lookup order matches `warlock add`, so both\n * commands agree on a project that happens to carry more than one lockfile.\n */\nexport async function detectPackageManager(): Promise<PackageManager> {\n if (await fileExistsAsync(rootPath(\"package-lock.json\"))) {\n return \"npm\";\n }\n\n if (await fileExistsAsync(rootPath(\"yarn.lock\"))) {\n return \"yarn\";\n }\n\n if (await fileExistsAsync(rootPath(\"pnpm-lock.yaml\"))) {\n return \"pnpm\";\n }\n\n return \"npm\";\n}\n\n/**\n * The lockfile-syncing install command for the given manager. No package\n * arguments — `warlock update` rewrites the versions in package.json first,\n * then a plain install reconciles `node_modules` to match.\n */\nexport function getInstallCommand(packageManager: PackageManager): string {\n switch (packageManager) {\n case \"yarn\":\n return \"yarn install\";\n\n case \"pnpm\":\n return \"pnpm install\";\n\n default:\n return \"npm install\";\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,eAAsB,uBAAgD;CACpE,IAAI,MAAM,gBAAgB,SAAS,mBAAmB,CAAC,GACrD,OAAO;CAGT,IAAI,MAAM,gBAAgB,SAAS,WAAW,CAAC,GAC7C,OAAO;CAGT,IAAI,MAAM,gBAAgB,SAAS,gBAAgB,CAAC,GAClD,OAAO;CAGT,OAAO;AACT;;;;;;AAOA,SAAgB,kBAAkB,gBAAwC;CACxE,QAAQ,gBAAR;EACE,KAAK,QACH,OAAO;EAET,KAAK,QACH,OAAO;EAET,SACE,OAAO;CACX;AACF"}
@@ -0,0 +1,134 @@
1
+ import { fetchLatestVersion } from "../utils/npm-registry.mjs";
2
+ import { rootPath } from "../utils/paths.mjs";
3
+ import { isNewerVersion } from "../utils/version-compare.mjs";
4
+ import "../utils/index.mjs";
5
+ import { detectPackageManager, getInstallCommand } from "./package-manager.mjs";
6
+ import { colors } from "@mongez/copper";
7
+ import { fileExistsAsync, getJsonFileAsync, putJsonFileAsync } from "@warlock.js/fs";
8
+ import { execSync } from "node:child_process";
9
+
10
+ //#region ../@warlock.js/core/src/updater/update-warlock-packages.ts
11
+ /** Scope prefix that identifies a Warlock framework package. */
12
+ const WARLOCK_SCOPE = "@warlock.js/";
13
+ /** The dependency maps in package.json we update, in display order. */
14
+ const DEPENDENCY_SECTIONS = ["dependencies", "devDependencies"];
15
+ /**
16
+ * Update every `@warlock.js/*` package listed in the project's root
17
+ * package.json to its latest published version, then reconcile `node_modules`
18
+ * via the detected package manager.
19
+ *
20
+ * The original range operator on each dependency (`^`, `~`, or an exact pin)
21
+ * is preserved; specs that aren't a plain semver — `workspace:*`, `*`,
22
+ * `latest`, git/file URLs — are left untouched. Only genuine upgrades are
23
+ * written, so re-running on an already-current project is a no-op.
24
+ */
25
+ async function updateWarlockPackages(options = {}) {
26
+ const runInstall = options.install ?? true;
27
+ const packageJsonPath = rootPath("package.json");
28
+ if (!await fileExistsAsync(packageJsonPath)) {
29
+ console.log(`${colors.red("✖")} No package.json found at the project root.`);
30
+ return;
31
+ }
32
+ const packageJson = await getJsonFileAsync(packageJsonPath);
33
+ const dependencies = collectWarlockDependencies(packageJson);
34
+ if (dependencies.length === 0) {
35
+ console.log(`${colors.yellow("⚠")} No @warlock.js packages found in package.json.`);
36
+ return;
37
+ }
38
+ console.log(`${colors.cyan("›")} Checking ${colors.bold(String(dependencies.length))} @warlock.js package(s) for updates…`);
39
+ const updates = resolvePackageUpdates(await resolveLatestVersions(dependencies));
40
+ if (updates.length === 0) {
41
+ console.log(`${colors.green("✓")} All @warlock.js packages are already up to date.`);
42
+ return;
43
+ }
44
+ applyUpdates(packageJson, updates);
45
+ await putJsonFileAsync(packageJsonPath, packageJson);
46
+ printUpdates(updates);
47
+ if (!runInstall) {
48
+ console.log(colors.dim("Skipped install (--no-install). Run your package manager to apply the changes."));
49
+ return;
50
+ }
51
+ await installDependencies();
52
+ }
53
+ /** Collect every `@warlock.js/*` dependency across the relevant sections. */
54
+ function collectWarlockDependencies(packageJson) {
55
+ const dependencies = [];
56
+ for (const section of DEPENDENCY_SECTIONS) {
57
+ const map = packageJson[section];
58
+ if (!map) continue;
59
+ for (const [name, current] of Object.entries(map)) if (name.startsWith(WARLOCK_SCOPE)) dependencies.push({
60
+ name,
61
+ section,
62
+ current
63
+ });
64
+ }
65
+ return dependencies;
66
+ }
67
+ /** Resolve each dependency's latest version from the registry in parallel. */
68
+ async function resolveLatestVersions(dependencies) {
69
+ return Promise.all(dependencies.map(async (dependency) => ({
70
+ ...dependency,
71
+ latest: await fetchLatestVersion(dependency.name)
72
+ })));
73
+ }
74
+ /**
75
+ * Turn resolved dependencies into the concrete set of version bumps to write.
76
+ * Pure (no I/O) so it can be unit-tested directly: skips lookups that failed,
77
+ * non-semver specs, and anything already at (or ahead of) the latest version,
78
+ * and preserves the original range operator on everything it does rewrite.
79
+ */
80
+ function resolvePackageUpdates(resolved) {
81
+ const updates = [];
82
+ for (const dependency of resolved) {
83
+ if (!dependency.latest) continue;
84
+ const parsed = parseSpec(dependency.current);
85
+ if (!parsed) continue;
86
+ if (!isNewerVersion(dependency.latest, parsed.version)) continue;
87
+ updates.push({
88
+ name: dependency.name,
89
+ section: dependency.section,
90
+ from: dependency.current,
91
+ to: `${parsed.operator}${dependency.latest}`
92
+ });
93
+ }
94
+ return updates;
95
+ }
96
+ /**
97
+ * Split a dependency spec into its range operator and concrete version.
98
+ * Returns `undefined` for specs we won't touch — `workspace:*`, `*`,
99
+ * `latest`, multi-part ranges, or git/file URLs — leaving them exactly as
100
+ * the author wrote them.
101
+ */
102
+ function parseSpec(spec) {
103
+ const match = spec.trim().match(/^(\^|~)?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
104
+ if (!match) return;
105
+ return {
106
+ operator: match[1] ?? "",
107
+ version: match[2]
108
+ };
109
+ }
110
+ /** Write the resolved bumps back into the package.json object in place. */
111
+ function applyUpdates(packageJson, updates) {
112
+ for (const update of updates) {
113
+ const map = packageJson[update.section];
114
+ if (map) map[update.name] = update.to;
115
+ }
116
+ }
117
+ /** Print the list of applied version bumps. */
118
+ function printUpdates(updates) {
119
+ console.log(`${colors.green("✓")} Updated ${colors.bold(String(updates.length))} package(s):`);
120
+ for (const update of updates) console.log(` ${colors.cyan(update.name)} ${colors.dim(update.from)} ${colors.dim("→")} ${colors.greenBright(update.to)}`);
121
+ }
122
+ /** Reconcile node_modules to the rewritten package.json via the project PM. */
123
+ async function installDependencies() {
124
+ const installCommand = getInstallCommand(await detectPackageManager());
125
+ console.log(`${colors.cyan("›")} Running ${colors.bold(installCommand)}…`);
126
+ execSync(installCommand, {
127
+ cwd: process.cwd(),
128
+ stdio: "inherit"
129
+ });
130
+ }
131
+
132
+ //#endregion
133
+ export { updateWarlockPackages };
134
+ //# sourceMappingURL=update-warlock-packages.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-warlock-packages.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/updater/update-warlock-packages.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { fileExistsAsync, getJsonFileAsync, putJsonFileAsync } from \"@warlock.js/fs\";\nimport { execSync } from \"node:child_process\";\nimport { rootPath } from \"../utils\";\nimport { fetchLatestVersion } from \"../utils/npm-registry\";\nimport { isNewerVersion } from \"../utils/version-compare\";\nimport { detectPackageManager, getInstallCommand } from \"./package-manager\";\n\n/** Scope prefix that identifies a Warlock framework package. */\nconst WARLOCK_SCOPE = \"@warlock.js/\";\n\n/** The dependency maps in package.json we update, in display order. */\nconst DEPENDENCY_SECTIONS = [\"dependencies\", \"devDependencies\"] as const;\n\ntype DependencySection = (typeof DEPENDENCY_SECTIONS)[number];\n\ntype DependencyMap = Record<string, string>;\n\ntype PackageJson = Record<string, unknown> & {\n dependencies?: DependencyMap;\n devDependencies?: DependencyMap;\n};\n\n/** A `@warlock.js/*` dependency found in package.json. */\ntype WarlockDependency = {\n name: string;\n section: DependencySection;\n /** The current spec exactly as written, e.g. `^4.2.0`. */\n current: string;\n};\n\n/** A dependency whose latest version has been resolved from the registry. */\nexport type ResolvedDependency = WarlockDependency & {\n /** Latest version from npm, or `undefined` when the lookup failed. */\n latest: string | undefined;\n};\n\n/** A concrete version bump to write back into package.json. */\nexport type PackageUpdate = {\n name: string;\n section: DependencySection;\n /** Existing spec, e.g. `^4.2.0`. */\n from: string;\n /** New spec with the original range operator preserved, e.g. `^4.3.0`. */\n to: string;\n};\n\n/** Options accepted by {@link updateWarlockPackages}. */\nexport type UpdateWarlockPackagesOptions = {\n /** Run the package manager install after rewriting versions (default true). */\n install?: boolean;\n};\n\n/**\n * Update every `@warlock.js/*` package listed in the project's root\n * package.json to its latest published version, then reconcile `node_modules`\n * via the detected package manager.\n *\n * The original range operator on each dependency (`^`, `~`, or an exact pin)\n * is preserved; specs that aren't a plain semver — `workspace:*`, `*`,\n * `latest`, git/file URLs — are left untouched. Only genuine upgrades are\n * written, so re-running on an already-current project is a no-op.\n */\nexport async function updateWarlockPackages(\n options: UpdateWarlockPackagesOptions = {},\n): Promise<void> {\n const runInstall = options.install ?? true;\n const packageJsonPath = rootPath(\"package.json\");\n\n if (!(await fileExistsAsync(packageJsonPath))) {\n console.log(`${colors.red(\"✖\")} No package.json found at the project root.`);\n return;\n }\n\n const packageJson = (await getJsonFileAsync(packageJsonPath)) as PackageJson;\n const dependencies = collectWarlockDependencies(packageJson);\n\n if (dependencies.length === 0) {\n console.log(`${colors.yellow(\"⚠\")} No @warlock.js packages found in package.json.`);\n return;\n }\n\n console.log(\n `${colors.cyan(\"›\")} Checking ${colors.bold(String(dependencies.length))} ` +\n `@warlock.js package(s) for updates…`,\n );\n\n const resolved = await resolveLatestVersions(dependencies);\n const updates = resolvePackageUpdates(resolved);\n\n if (updates.length === 0) {\n console.log(`${colors.green(\"✓\")} All @warlock.js packages are already up to date.`);\n return;\n }\n\n applyUpdates(packageJson, updates);\n await putJsonFileAsync(packageJsonPath, packageJson);\n\n printUpdates(updates);\n\n if (!runInstall) {\n console.log(\n colors.dim(\"Skipped install (--no-install). Run your package manager to apply the changes.\"),\n );\n return;\n }\n\n await installDependencies();\n}\n\n/** Collect every `@warlock.js/*` dependency across the relevant sections. */\nfunction collectWarlockDependencies(packageJson: PackageJson): WarlockDependency[] {\n const dependencies: WarlockDependency[] = [];\n\n for (const section of DEPENDENCY_SECTIONS) {\n const map = packageJson[section];\n\n if (!map) {\n continue;\n }\n\n for (const [name, current] of Object.entries(map)) {\n if (name.startsWith(WARLOCK_SCOPE)) {\n dependencies.push({ name, section, current });\n }\n }\n }\n\n return dependencies;\n}\n\n/** Resolve each dependency's latest version from the registry in parallel. */\nasync function resolveLatestVersions(\n dependencies: WarlockDependency[],\n): Promise<ResolvedDependency[]> {\n return Promise.all(\n dependencies.map(async (dependency) => ({\n ...dependency,\n latest: await fetchLatestVersion(dependency.name),\n })),\n );\n}\n\n/**\n * Turn resolved dependencies into the concrete set of version bumps to write.\n * Pure (no I/O) so it can be unit-tested directly: skips lookups that failed,\n * non-semver specs, and anything already at (or ahead of) the latest version,\n * and preserves the original range operator on everything it does rewrite.\n */\nexport function resolvePackageUpdates(resolved: ResolvedDependency[]): PackageUpdate[] {\n const updates: PackageUpdate[] = [];\n\n for (const dependency of resolved) {\n if (!dependency.latest) {\n continue;\n }\n\n const parsed = parseSpec(dependency.current);\n\n if (!parsed) {\n continue;\n }\n\n if (!isNewerVersion(dependency.latest, parsed.version)) {\n continue;\n }\n\n updates.push({\n name: dependency.name,\n section: dependency.section,\n from: dependency.current,\n to: `${parsed.operator}${dependency.latest}`,\n });\n }\n\n return updates;\n}\n\n/**\n * Split a dependency spec into its range operator and concrete version.\n * Returns `undefined` for specs we won't touch — `workspace:*`, `*`,\n * `latest`, multi-part ranges, or git/file URLs — leaving them exactly as\n * the author wrote them.\n */\nfunction parseSpec(spec: string): { operator: string; version: string } | undefined {\n const match = spec.trim().match(/^(\\^|~)?(\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?)$/);\n\n if (!match) {\n return undefined;\n }\n\n return { operator: match[1] ?? \"\", version: match[2] };\n}\n\n/** Write the resolved bumps back into the package.json object in place. */\nfunction applyUpdates(packageJson: PackageJson, updates: PackageUpdate[]): void {\n for (const update of updates) {\n const map = packageJson[update.section];\n\n if (map) {\n map[update.name] = update.to;\n }\n }\n}\n\n/** Print the list of applied version bumps. */\nfunction printUpdates(updates: PackageUpdate[]): void {\n console.log(`${colors.green(\"✓\")} Updated ${colors.bold(String(updates.length))} package(s):`);\n\n for (const update of updates) {\n console.log(\n ` ${colors.cyan(update.name)} ` +\n `${colors.dim(update.from)} ${colors.dim(\"→\")} ${colors.greenBright(update.to)}`,\n );\n }\n}\n\n/** Reconcile node_modules to the rewritten package.json via the project PM. */\nasync function installDependencies(): Promise<void> {\n const packageManager = await detectPackageManager();\n const installCommand = getInstallCommand(packageManager);\n\n console.log(`${colors.cyan(\"›\")} Running ${colors.bold(installCommand)}…`);\n\n execSync(installCommand, { cwd: process.cwd(), stdio: \"inherit\" });\n}\n"],"mappings":";;;;;;;;;;;AASA,MAAM,gBAAgB;;AAGtB,MAAM,sBAAsB,CAAC,gBAAgB,iBAAiB;;;;;;;;;;;AAmD9D,eAAsB,sBACpB,UAAwC,CAAC,GAC1B;CACf,MAAM,aAAa,QAAQ,WAAW;CACtC,MAAM,kBAAkB,SAAS,cAAc;CAE/C,IAAI,CAAE,MAAM,gBAAgB,eAAe,GAAI;EAC7C,QAAQ,IAAI,GAAG,OAAO,IAAI,GAAG,EAAE,4CAA4C;EAC3E;CACF;CAEA,MAAM,cAAe,MAAM,iBAAiB,eAAe;CAC3D,MAAM,eAAe,2BAA2B,WAAW;CAE3D,IAAI,aAAa,WAAW,GAAG;EAC7B,QAAQ,IAAI,GAAG,OAAO,OAAO,GAAG,EAAE,gDAAgD;EAClF;CACF;CAEA,QAAQ,IACN,GAAG,OAAO,KAAK,GAAG,EAAE,YAAY,OAAO,KAAK,OAAO,aAAa,MAAM,CAAC,EAAE,qCAE3E;CAGA,MAAM,UAAU,sBAAsB,MADf,sBAAsB,YAAY,CACX;CAE9C,IAAI,QAAQ,WAAW,GAAG;EACxB,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,kDAAkD;EACnF;CACF;CAEA,aAAa,aAAa,OAAO;CACjC,MAAM,iBAAiB,iBAAiB,WAAW;CAEnD,aAAa,OAAO;CAEpB,IAAI,CAAC,YAAY;EACf,QAAQ,IACN,OAAO,IAAI,gFAAgF,CAC7F;EACA;CACF;CAEA,MAAM,oBAAoB;AAC5B;;AAGA,SAAS,2BAA2B,aAA+C;CACjF,MAAM,eAAoC,CAAC;CAE3C,KAAK,MAAM,WAAW,qBAAqB;EACzC,MAAM,MAAM,YAAY;EAExB,IAAI,CAAC,KACH;EAGF,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,GAAG,GAC9C,IAAI,KAAK,WAAW,aAAa,GAC/B,aAAa,KAAK;GAAE;GAAM;GAAS;EAAQ,CAAC;CAGlD;CAEA,OAAO;AACT;;AAGA,eAAe,sBACb,cAC+B;CAC/B,OAAO,QAAQ,IACb,aAAa,IAAI,OAAO,gBAAgB;EACtC,GAAG;EACH,QAAQ,MAAM,mBAAmB,WAAW,IAAI;CAClD,EAAE,CACJ;AACF;;;;;;;AAQA,SAAgB,sBAAsB,UAAiD;CACrF,MAAM,UAA2B,CAAC;CAElC,KAAK,MAAM,cAAc,UAAU;EACjC,IAAI,CAAC,WAAW,QACd;EAGF,MAAM,SAAS,UAAU,WAAW,OAAO;EAE3C,IAAI,CAAC,QACH;EAGF,IAAI,CAAC,eAAe,WAAW,QAAQ,OAAO,OAAO,GACnD;EAGF,QAAQ,KAAK;GACX,MAAM,WAAW;GACjB,SAAS,WAAW;GACpB,MAAM,WAAW;GACjB,IAAI,GAAG,OAAO,WAAW,WAAW;EACtC,CAAC;CACH;CAEA,OAAO;AACT;;;;;;;AAQA,SAAS,UAAU,MAAiE;CAClF,MAAM,QAAQ,KAAK,KAAK,CAAC,CAAC,MAAM,8CAA8C;CAE9E,IAAI,CAAC,OACH;CAGF,OAAO;EAAE,UAAU,MAAM,MAAM;EAAI,SAAS,MAAM;CAAG;AACvD;;AAGA,SAAS,aAAa,aAA0B,SAAgC;CAC9E,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,YAAY,OAAO;EAE/B,IAAI,KACF,IAAI,OAAO,QAAQ,OAAO;CAE9B;AACF;;AAGA,SAAS,aAAa,SAAgC;CACpD,QAAQ,IAAI,GAAG,OAAO,MAAM,GAAG,EAAE,WAAW,OAAO,KAAK,OAAO,QAAQ,MAAM,CAAC,EAAE,aAAa;CAE7F,KAAK,MAAM,UAAU,SACnB,QAAQ,IACN,MAAM,OAAO,KAAK,OAAO,IAAI,EAAE,IAC1B,OAAO,IAAI,OAAO,IAAI,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,YAAY,OAAO,EAAE,GACjF;AAEJ;;AAGA,eAAe,sBAAqC;CAElD,MAAM,iBAAiB,kBAAkB,MADZ,qBAAqB,CACK;CAEvD,QAAQ,IAAI,GAAG,OAAO,KAAK,GAAG,EAAE,WAAW,OAAO,KAAK,cAAc,EAAE,EAAE;CAEzE,SAAS,gBAAgB;EAAE,KAAK,QAAQ,IAAI;EAAG,OAAO;CAAU,CAAC;AACnE"}
@@ -1,7 +1,12 @@
1
1
  //#region ../@warlock.js/core/src/utils/environment.d.ts
2
2
  type Environment = "development" | "production" | "test";
3
+ /**
4
+ * How the framework hosts your code — the dev server (watcher + HMR) or the
5
+ * bundled production output. A separate axis from {@link Environment}.
6
+ */
7
+ type RuntimeStrategy = "production" | "development";
3
8
  declare function environment(): Environment;
4
9
  declare function setEnvironment(env: Environment): void;
5
10
  //#endregion
6
- export { Environment, environment, setEnvironment };
11
+ export { Environment, RuntimeStrategy, environment, setEnvironment };
7
12
  //# sourceMappingURL=environment.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"environment.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/environment.ts"],"mappings":";KAAY,WAAA;AAAA,iBAEI,WAAA,IAAe,WAAW;AAAA,iBAI1B,cAAA,CAAe,GAAgB,EAAX,WAAW"}
1
+ {"version":3,"file":"environment.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/environment.ts"],"mappings":";KAAY,WAAA;AAAZ;;;;AAAA,KAMY,eAAA;AAAA,iBAEI,WAAA,IAAe,WAAW;AAAA,iBAI1B,cAAA,CAAe,GAAgB,EAAX,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"environment.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/environment.ts"],"sourcesContent":["export type Environment = \"development\" | \"production\" | \"test\";\n\nexport function environment(): Environment {\n return (process.env.NODE_ENV as Environment) || \"development\";\n}\n\nexport function setEnvironment(env: Environment) {\n process.env.NODE_ENV = env;\n}\n"],"mappings":";AAEA,SAAgB,cAA2B;CACzC,OAAQ,QAAQ,IAAI,YAA4B;AAClD;AAEA,SAAgB,eAAe,KAAkB;CAC/C,QAAQ,IAAI,WAAW;AACzB"}
1
+ {"version":3,"file":"environment.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/environment.ts"],"sourcesContent":["export type Environment = \"development\" | \"production\" | \"test\";\n\n/**\n * How the framework hosts your code — the dev server (watcher + HMR) or the\n * bundled production output. A separate axis from {@link Environment}.\n */\nexport type RuntimeStrategy = \"production\" | \"development\";\n\nexport function environment(): Environment {\n return (process.env.NODE_ENV as Environment) || \"development\";\n}\n\nexport function setEnvironment(env: Environment) {\n process.env.NODE_ENV = env;\n}\n"],"mappings":";AAQA,SAAgB,cAA2B;CACzC,OAAQ,QAAQ,IAAI,YAA4B;AAClD;AAEA,SAAgB,eAAe,KAAkB;CAC/C,QAAQ,IAAI,WAAW;AACzB"}
@@ -1,11 +1,13 @@
1
1
  import { LocalizedObject, getLocalized } from "./get-localized.mjs";
2
- import { Environment, environment, setEnvironment } from "./environment.mjs";
2
+ import { Environment, RuntimeStrategy, environment, setEnvironment } from "./environment.mjs";
3
3
  import { appLog } from "./app-log.mjs";
4
4
  import { DatabaseLog, DatabaseLogOptions } from "./database-log.mjs";
5
+ import { fetchLatestVersion } from "./npm-registry.mjs";
5
6
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./paths.mjs";
6
7
  import { promiseAllObject } from "./promise-all-object.mjs";
7
8
  import { Queue } from "./queue.mjs";
8
9
  import { sleep } from "./sleep.mjs";
9
10
  import { sluggable } from "./sluggable.mjs";
10
11
  import { toJson } from "./to-json.mjs";
11
- import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
12
+ import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
13
+ import { isNewerVersion } from "./version-compare.mjs";
@@ -2,6 +2,7 @@ import { appLog } from "./app-log.mjs";
2
2
  import { DatabaseLog } from "./database-log.mjs";
3
3
  import { environment, setEnvironment } from "./environment.mjs";
4
4
  import { getLocalized } from "./get-localized.mjs";
5
+ import { fetchLatestVersion } from "./npm-registry.mjs";
5
6
  import { appPath, cachePath, configPath, logsPath, paths, publicPath, rootPath, sanitizePath, srcPath, storagePath, tempPath, uploadsPath, warlockPath } from "./paths.mjs";
6
7
  import { promiseAllObject } from "./promise-all-object.mjs";
7
8
  import { Queue } from "./queue.mjs";
@@ -9,5 +10,6 @@ import { sleep } from "./sleep.mjs";
9
10
  import { sluggable } from "./sluggable.mjs";
10
11
  import { toJson } from "./to-json.mjs";
11
12
  import { assetsUrl, publicUrl, setBaseUrl, uploadsUrl, url } from "./urls.mjs";
13
+ import { isNewerVersion } from "./version-compare.mjs";
12
14
 
13
15
  export { };
@@ -0,0 +1,24 @@
1
+ //#region ../@warlock.js/core/src/utils/npm-registry.d.ts
2
+ /**
3
+ * Minimal npm-registry access for the framework's self-update tooling
4
+ * (the dev-server update notice + the `warlock update` command).
5
+ *
6
+ * No SDK and no caching layer — just the public registry over `fetch`.
7
+ * Every lookup is best-effort: any failure resolves to `undefined` so a
8
+ * registry hiccup can never break the dev server or the CLI.
9
+ */
10
+ /**
11
+ * Fetch the latest published version of a package from the npm registry.
12
+ *
13
+ * Hits the abbreviated `/<package>/latest` endpoint, which returns only the
14
+ * latest dist-tag manifest (not the full packument). Resolves to `undefined`
15
+ * on any failure — offline, timeout, non-200 response, or malformed payload —
16
+ * and never throws, so callers can stay fail-silent.
17
+ *
18
+ * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.
19
+ * @param timeoutMs Abort budget in milliseconds (defaults to 2500).
20
+ */
21
+ declare function fetchLatestVersion(packageName: string, timeoutMs?: number): Promise<string | undefined>;
22
+ //#endregion
23
+ export { fetchLatestVersion };
24
+ //# sourceMappingURL=npm-registry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/npm-registry.ts"],"mappings":";;AAwBA;;;;;;;;AAGU;;;;;;;;;;iBAHY,kBAAA,CACpB,WAAA,UACA,SAAA,YACC,OAAO"}
@@ -0,0 +1,43 @@
1
+ //#region ../@warlock.js/core/src/utils/npm-registry.ts
2
+ /**
3
+ * Minimal npm-registry access for the framework's self-update tooling
4
+ * (the dev-server update notice + the `warlock update` command).
5
+ *
6
+ * No SDK and no caching layer — just the public registry over `fetch`.
7
+ * Every lookup is best-effort: any failure resolves to `undefined` so a
8
+ * registry hiccup can never break the dev server or the CLI.
9
+ */
10
+ const REGISTRY_BASE_URL = "https://registry.npmjs.org";
11
+ const DEFAULT_TIMEOUT_MS = 2500;
12
+ /**
13
+ * Fetch the latest published version of a package from the npm registry.
14
+ *
15
+ * Hits the abbreviated `/<package>/latest` endpoint, which returns only the
16
+ * latest dist-tag manifest (not the full packument). Resolves to `undefined`
17
+ * on any failure — offline, timeout, non-200 response, or malformed payload —
18
+ * and never throws, so callers can stay fail-silent.
19
+ *
20
+ * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.
21
+ * @param timeoutMs Abort budget in milliseconds (defaults to 2500).
22
+ */
23
+ async function fetchLatestVersion(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
26
+ try {
27
+ const response = await fetch(`${REGISTRY_BASE_URL}/${packageName}/latest`, {
28
+ signal: controller.signal,
29
+ headers: { accept: "application/json" }
30
+ });
31
+ if (!response.ok) return;
32
+ const data = await response.json();
33
+ return typeof data.version === "string" ? data.version : void 0;
34
+ } catch {
35
+ return;
36
+ } finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+
41
+ //#endregion
42
+ export { fetchLatestVersion };
43
+ //# sourceMappingURL=npm-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/npm-registry.ts"],"sourcesContent":["/**\n * Minimal npm-registry access for the framework's self-update tooling\n * (the dev-server update notice + the `warlock update` command).\n *\n * No SDK and no caching layer — just the public registry over `fetch`.\n * Every lookup is best-effort: any failure resolves to `undefined` so a\n * registry hiccup can never break the dev server or the CLI.\n */\n\nconst REGISTRY_BASE_URL = \"https://registry.npmjs.org\";\n\nconst DEFAULT_TIMEOUT_MS = 2500;\n\n/**\n * Fetch the latest published version of a package from the npm registry.\n *\n * Hits the abbreviated `/<package>/latest` endpoint, which returns only the\n * latest dist-tag manifest (not the full packument). Resolves to `undefined`\n * on any failure — offline, timeout, non-200 response, or malformed payload —\n * and never throws, so callers can stay fail-silent.\n *\n * @param packageName Fully-qualified npm name, e.g. `@warlock.js/core`.\n * @param timeoutMs Abort budget in milliseconds (defaults to 2500).\n */\nexport async function fetchLatestVersion(\n packageName: string,\n timeoutMs: number = DEFAULT_TIMEOUT_MS,\n): Promise<string | undefined> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_BASE_URL}/${packageName}/latest`, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as { version?: unknown };\n\n return typeof data.version === \"string\" ? data.version : undefined;\n } catch {\n // Best-effort: offline, DNS failure, abort/timeout, or bad JSON — all \"unknown\".\n return undefined;\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,oBAAoB;AAE1B,MAAM,qBAAqB;;;;;;;;;;;;AAa3B,eAAsB,mBACpB,aACA,YAAoB,oBACS;CAC7B,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,SAAS;CAE5D,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,kBAAkB,GAAG,YAAY,UAAU;GACzE,QAAQ,WAAW;GACnB,SAAS,EAAE,QAAQ,mBAAmB;EACxC,CAAC;EAED,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAElC,OAAO,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;CAC3D,QAAQ;EAEN;CACF,UAAU;EACR,aAAa,KAAK;CACpB;AACF"}
@@ -1,4 +1,4 @@
1
- import config from "@mongez/config";
1
+ import baseConfig from "@mongez/config";
2
2
  import path from "path";
3
3
 
4
4
  //#region ../@warlock.js/core/src/utils/paths.ts
@@ -28,7 +28,7 @@ function storagePath(relativePath = "") {
28
28
  * If no path is given, it will return the absolute path to the uploads folder
29
29
  */
30
30
  function uploadsPath(relativePath = "") {
31
- const configPath = config.get("uploads.root");
31
+ const configPath = baseConfig.get("uploads.root");
32
32
  if (!configPath) return rootPath("storage", "uploads", relativePath);
33
33
  return typeof configPath === "function" ? configPath(relativePath) : path.resolve(configPath, relativePath);
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"paths.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/paths.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport path from \"path\";\r\n\r\n/**\r\n * Get root path or join the given paths to the root path\r\n */\r\nexport function rootPath(...paths: string[]) {\r\n return path.resolve(process.cwd(), ...paths);\r\n}\r\n\r\n/**\r\n * Get src directory path or join the given paths to the src directory path\r\n */\r\nexport function srcPath(...paths: string[]) {\r\n return rootPath(\"src\", ...paths);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the storage folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the storage folder\r\n */\r\nexport function storagePath(relativePath = \"\") {\r\n return rootPath(\"storage\", relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the uploads folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the uploads folder\r\n */\r\nexport function uploadsPath(relativePath = \"\") {\r\n const configPath = config.get(\"uploads.root\");\r\n if (!configPath) {\r\n return rootPath(\"storage\", \"uploads\", relativePath);\r\n }\r\n\r\n return typeof configPath === \"function\"\r\n ? configPath(relativePath)\r\n : path.resolve(configPath, relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the public folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the public folder\r\n */\r\nexport function publicPath(relativePath = \"\") {\r\n return rootPath(\"public\", relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the cache folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the cache folder\r\n */\r\nexport function cachePath(relativePath = \"\") {\r\n return rootPath(\"storage\", \"cache\", relativePath);\r\n}\r\n\r\n/**\r\n * App path\r\n */\r\nexport function appPath(relativePath = \"\") {\r\n return rootPath(\"src/app\", relativePath);\r\n}\r\n\r\n/**\r\n * Get logs directory path\r\n */\r\nexport function logsPath(relativePath = \"\") {\r\n return rootPath(\"storage/logs\", relativePath);\r\n}\r\n\r\n/**\r\n * Get a temp path\r\n */\r\nexport function tempPath(relativePath = \"\") {\r\n return rootPath(\"storage/tmp\", relativePath);\r\n}\r\n\r\n/**\r\n * Remove any invalid characters from the file path using regex\r\n * It should accept any language character, numbers, and the following characters: _ - .\r\n */\r\nconst invalidCharsRegex = /[<>:\"/\\\\|?*]/g; // Regex to match invalid characters\r\nexport function sanitizePath(filePath: string) {\r\n return filePath.replace(invalidCharsRegex, \"\"); // Replace invalid characters with an empty string\r\n}\r\n\r\n/**\r\n * Warlock path\r\n * PLEASE DO NOT add any files in this directory as it may be deleted\r\n */\r\nexport function warlockPath(...path: string[]) {\r\n return rootPath(\".warlock\", ...path);\r\n}\r\n\r\n/**\r\n * Get config directory path\r\n */\r\nexport function configPath(...path: string[]) {\r\n return rootPath(\"src/config\", ...path);\r\n}\r\n\r\nexport const paths = {\r\n root: rootPath,\r\n src: srcPath,\r\n storage: storagePath,\r\n logs: logsPath,\r\n uploads: uploadsPath,\r\n public: publicPath,\r\n cache: cachePath,\r\n app: appPath,\r\n temp: tempPath,\r\n warlock: warlockPath,\r\n config: configPath,\r\n sanitize: sanitizePath,\r\n};\r\n"],"mappings":";;;;;;;AAMA,SAAgB,SAAS,GAAG,OAAiB;CAC3C,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,GAAG,KAAK;AAC7C;;;;AAKA,SAAgB,QAAQ,GAAG,OAAiB;CAC1C,OAAO,SAAS,OAAO,GAAG,KAAK;AACjC;;;;;;AAOA,SAAgB,YAAY,eAAe,IAAI;CAC7C,OAAO,SAAS,WAAW,YAAY;AACzC;;;;;;AAOA,SAAgB,YAAY,eAAe,IAAI;CAC7C,MAAM,aAAa,OAAO,IAAI,cAAc;CAC5C,IAAI,CAAC,YACH,OAAO,SAAS,WAAW,WAAW,YAAY;CAGpD,OAAO,OAAO,eAAe,aACzB,WAAW,YAAY,IACvB,KAAK,QAAQ,YAAY,YAAY;AAC3C;;;;;;AAOA,SAAgB,WAAW,eAAe,IAAI;CAC5C,OAAO,SAAS,UAAU,YAAY;AACxC;;;;;;AAOA,SAAgB,UAAU,eAAe,IAAI;CAC3C,OAAO,SAAS,WAAW,SAAS,YAAY;AAClD;;;;AAKA,SAAgB,QAAQ,eAAe,IAAI;CACzC,OAAO,SAAS,WAAW,YAAY;AACzC;;;;AAKA,SAAgB,SAAS,eAAe,IAAI;CAC1C,OAAO,SAAS,gBAAgB,YAAY;AAC9C;;;;AAKA,SAAgB,SAAS,eAAe,IAAI;CAC1C,OAAO,SAAS,eAAe,YAAY;AAC7C;;;;;AAMA,MAAM,oBAAoB;AAC1B,SAAgB,aAAa,UAAkB;CAC7C,OAAO,SAAS,QAAQ,mBAAmB,EAAE;AAC/C;;;;;AAMA,SAAgB,YAAY,GAAG,MAAgB;CAC7C,OAAO,SAAS,YAAY,GAAG,IAAI;AACrC;;;;AAKA,SAAgB,WAAW,GAAG,MAAgB;CAC5C,OAAO,SAAS,cAAc,GAAG,IAAI;AACvC;AAEA,MAAa,QAAQ;CACnB,MAAM;CACN,KAAK;CACL,SAAS;CACT,MAAM;CACN,SAAS;CACT,QAAQ;CACR,OAAO;CACP,KAAK;CACL,MAAM;CACN,SAAS;CACT,QAAQ;CACR,UAAU;AACZ"}
1
+ {"version":3,"file":"paths.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/utils/paths.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport path from \"path\";\r\n\r\n/**\r\n * Get root path or join the given paths to the root path\r\n */\r\nexport function rootPath(...paths: string[]) {\r\n return path.resolve(process.cwd(), ...paths);\r\n}\r\n\r\n/**\r\n * Get src directory path or join the given paths to the src directory path\r\n */\r\nexport function srcPath(...paths: string[]) {\r\n return rootPath(\"src\", ...paths);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the storage folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the storage folder\r\n */\r\nexport function storagePath(relativePath = \"\") {\r\n return rootPath(\"storage\", relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the uploads folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the uploads folder\r\n */\r\nexport function uploadsPath(relativePath = \"\") {\r\n const configPath = config.get(\"uploads.root\");\r\n if (!configPath) {\r\n return rootPath(\"storage\", \"uploads\", relativePath);\r\n }\r\n\r\n return typeof configPath === \"function\"\r\n ? configPath(relativePath)\r\n : path.resolve(configPath, relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the public folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the public folder\r\n */\r\nexport function publicPath(relativePath = \"\") {\r\n return rootPath(\"public\", relativePath);\r\n}\r\n\r\n/**\r\n * Get the absolute path to the cache folder to the given path\r\n *\r\n * If no path is given, it will return the absolute path to the cache folder\r\n */\r\nexport function cachePath(relativePath = \"\") {\r\n return rootPath(\"storage\", \"cache\", relativePath);\r\n}\r\n\r\n/**\r\n * App path\r\n */\r\nexport function appPath(relativePath = \"\") {\r\n return rootPath(\"src/app\", relativePath);\r\n}\r\n\r\n/**\r\n * Get logs directory path\r\n */\r\nexport function logsPath(relativePath = \"\") {\r\n return rootPath(\"storage/logs\", relativePath);\r\n}\r\n\r\n/**\r\n * Get a temp path\r\n */\r\nexport function tempPath(relativePath = \"\") {\r\n return rootPath(\"storage/tmp\", relativePath);\r\n}\r\n\r\n/**\r\n * Remove any invalid characters from the file path using regex\r\n * It should accept any language character, numbers, and the following characters: _ - .\r\n */\r\nconst invalidCharsRegex = /[<>:\"/\\\\|?*]/g; // Regex to match invalid characters\r\nexport function sanitizePath(filePath: string) {\r\n return filePath.replace(invalidCharsRegex, \"\"); // Replace invalid characters with an empty string\r\n}\r\n\r\n/**\r\n * Warlock path\r\n * PLEASE DO NOT add any files in this directory as it may be deleted\r\n */\r\nexport function warlockPath(...path: string[]) {\r\n return rootPath(\".warlock\", ...path);\r\n}\r\n\r\n/**\r\n * Get config directory path\r\n */\r\nexport function configPath(...path: string[]) {\r\n return rootPath(\"src/config\", ...path);\r\n}\r\n\r\nexport const paths = {\r\n root: rootPath,\r\n src: srcPath,\r\n storage: storagePath,\r\n logs: logsPath,\r\n uploads: uploadsPath,\r\n public: publicPath,\r\n cache: cachePath,\r\n app: appPath,\r\n temp: tempPath,\r\n warlock: warlockPath,\r\n config: configPath,\r\n sanitize: sanitizePath,\r\n};\r\n"],"mappings":";;;;;;;AAMA,SAAgB,SAAS,GAAG,OAAiB;CAC3C,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,GAAG,KAAK;AAC7C;;;;AAKA,SAAgB,QAAQ,GAAG,OAAiB;CAC1C,OAAO,SAAS,OAAO,GAAG,KAAK;AACjC;;;;;;AAOA,SAAgB,YAAY,eAAe,IAAI;CAC7C,OAAO,SAAS,WAAW,YAAY;AACzC;;;;;;AAOA,SAAgB,YAAY,eAAe,IAAI;CAC7C,MAAM,aAAaA,WAAO,IAAI,cAAc;CAC5C,IAAI,CAAC,YACH,OAAO,SAAS,WAAW,WAAW,YAAY;CAGpD,OAAO,OAAO,eAAe,aACzB,WAAW,YAAY,IACvB,KAAK,QAAQ,YAAY,YAAY;AAC3C;;;;;;AAOA,SAAgB,WAAW,eAAe,IAAI;CAC5C,OAAO,SAAS,UAAU,YAAY;AACxC;;;;;;AAOA,SAAgB,UAAU,eAAe,IAAI;CAC3C,OAAO,SAAS,WAAW,SAAS,YAAY;AAClD;;;;AAKA,SAAgB,QAAQ,eAAe,IAAI;CACzC,OAAO,SAAS,WAAW,YAAY;AACzC;;;;AAKA,SAAgB,SAAS,eAAe,IAAI;CAC1C,OAAO,SAAS,gBAAgB,YAAY;AAC9C;;;;AAKA,SAAgB,SAAS,eAAe,IAAI;CAC1C,OAAO,SAAS,eAAe,YAAY;AAC7C;;;;;AAMA,MAAM,oBAAoB;AAC1B,SAAgB,aAAa,UAAkB;CAC7C,OAAO,SAAS,QAAQ,mBAAmB,EAAE;AAC/C;;;;;AAMA,SAAgB,YAAY,GAAG,MAAgB;CAC7C,OAAO,SAAS,YAAY,GAAG,IAAI;AACrC;;;;AAKA,SAAgB,WAAW,GAAG,MAAgB;CAC5C,OAAO,SAAS,cAAc,GAAG,IAAI;AACvC;AAEA,MAAa,QAAQ;CACnB,MAAM;CACN,KAAK;CACL,SAAS;CACT,MAAM;CACN,SAAS;CACT,QAAQ;CACR,OAAO;CACP,KAAK;CACL,MAAM;CACN,SAAS;CACT,QAAQ;CACR,UAAU;AACZ"}
@@ -0,0 +1,20 @@
1
+ //#region ../@warlock.js/core/src/utils/version-compare.d.ts
2
+ /**
3
+ * Zero-dependency semver comparison — just enough for the framework's
4
+ * self-update tooling (the dev-server update notice + the `warlock update`
5
+ * command).
6
+ *
7
+ * We deliberately avoid pulling in a semver library: the only question these
8
+ * callers ask is "is the published version newer than what's installed?".
9
+ */
10
+ /**
11
+ * Return `true` when `latest` is a strictly newer semantic version than
12
+ * `current`. Compares the numeric `major.minor.patch` first, then treats a
13
+ * stable release as newer than a prerelease of the same core
14
+ * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag
15
+ * on a version string we can't understand.
16
+ */
17
+ declare function isNewerVersion(latest: string, current: string): boolean;
18
+ //#endregion
19
+ export { isNewerVersion };
20
+ //# sourceMappingURL=version-compare.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-compare.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/version-compare.ts"],"mappings":";;AAkGA;;;;AAA8D;;;;;;;;;;iBAA9C,cAAA,CAAe,MAAA,UAAgB,OAAe"}
@@ -0,0 +1,70 @@
1
+ //#region ../@warlock.js/core/src/utils/version-compare.ts
2
+ /**
3
+ * Parse a semver string into its numeric core + prerelease identifiers.
4
+ * Tolerates a leading `v` and ignores build metadata (`+…`). Returns
5
+ * `undefined` when the core isn't exactly three non-negative integers.
6
+ */
7
+ function parseVersion(version) {
8
+ const [coreAndPrerelease] = version.trim().replace(/^v/i, "").split("+");
9
+ const [core, ...prereleaseParts] = coreAndPrerelease.split("-");
10
+ const segments = core.split(".");
11
+ if (segments.length !== 3) return;
12
+ const numbers = segments.map((segment) => Number(segment));
13
+ if (numbers.some((value) => !Number.isInteger(value) || value < 0)) return;
14
+ const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join("-").split(".") : [];
15
+ return {
16
+ core: [
17
+ numbers[0],
18
+ numbers[1],
19
+ numbers[2]
20
+ ],
21
+ prerelease
22
+ };
23
+ }
24
+ /**
25
+ * Compare two prerelease identifier lists per semver rules: numeric
26
+ * identifiers compare numerically, alphanumeric ones lexically, a numeric
27
+ * identifier ranks below an alphanumeric one, and a longer list wins when
28
+ * otherwise equal. Returns a negative / zero / positive number.
29
+ */
30
+ function comparePrerelease(left, right) {
31
+ const length = Math.max(left.length, right.length);
32
+ for (let index = 0; index < length; index++) {
33
+ const a = left[index];
34
+ const b = right[index];
35
+ if (a === void 0) return -1;
36
+ if (b === void 0) return 1;
37
+ const aIsNumeric = /^\d+$/.test(a);
38
+ const bIsNumeric = /^\d+$/.test(b);
39
+ if (aIsNumeric && bIsNumeric) {
40
+ const diff = Number(a) - Number(b);
41
+ if (diff !== 0) return diff;
42
+ continue;
43
+ }
44
+ if (aIsNumeric !== bIsNumeric) return aIsNumeric ? -1 : 1;
45
+ if (a !== b) return a < b ? -1 : 1;
46
+ }
47
+ return 0;
48
+ }
49
+ /**
50
+ * Return `true` when `latest` is a strictly newer semantic version than
51
+ * `current`. Compares the numeric `major.minor.patch` first, then treats a
52
+ * stable release as newer than a prerelease of the same core
53
+ * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag
54
+ * on a version string we can't understand.
55
+ */
56
+ function isNewerVersion(latest, current) {
57
+ const latestParsed = parseVersion(latest);
58
+ const currentParsed = parseVersion(current);
59
+ if (!latestParsed || !currentParsed) return false;
60
+ for (let index = 0; index < 3; index++) if (latestParsed.core[index] !== currentParsed.core[index]) return latestParsed.core[index] > currentParsed.core[index];
61
+ const latestIsStable = latestParsed.prerelease.length === 0;
62
+ const currentIsStable = currentParsed.prerelease.length === 0;
63
+ if (latestIsStable && currentIsStable) return false;
64
+ if (latestIsStable !== currentIsStable) return latestIsStable;
65
+ return comparePrerelease(latestParsed.prerelease, currentParsed.prerelease) > 0;
66
+ }
67
+
68
+ //#endregion
69
+ export { isNewerVersion };
70
+ //# sourceMappingURL=version-compare.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-compare.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/utils/version-compare.ts"],"sourcesContent":["/**\n * Zero-dependency semver comparison — just enough for the framework's\n * self-update tooling (the dev-server update notice + the `warlock update`\n * command).\n *\n * We deliberately avoid pulling in a semver library: the only question these\n * callers ask is \"is the published version newer than what's installed?\".\n */\n\ntype ParsedVersion = {\n /** Numeric core as `[major, minor, patch]`. */\n core: [number, number, number];\n /** Dot-separated prerelease identifiers; empty for a stable release. */\n prerelease: string[];\n};\n\n/**\n * Parse a semver string into its numeric core + prerelease identifiers.\n * Tolerates a leading `v` and ignores build metadata (`+…`). Returns\n * `undefined` when the core isn't exactly three non-negative integers.\n */\nfunction parseVersion(version: string): ParsedVersion | undefined {\n const cleaned = version.trim().replace(/^v/i, \"\");\n const [coreAndPrerelease] = cleaned.split(\"+\"); // drop build metadata\n const [core, ...prereleaseParts] = coreAndPrerelease.split(\"-\");\n const segments = core.split(\".\");\n\n if (segments.length !== 3) {\n return undefined;\n }\n\n const numbers = segments.map((segment) => Number(segment));\n\n if (numbers.some((value) => !Number.isInteger(value) || value < 0)) {\n return undefined;\n }\n\n const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join(\"-\").split(\".\") : [];\n\n return {\n core: [numbers[0], numbers[1], numbers[2]],\n prerelease,\n };\n}\n\n/**\n * Compare two prerelease identifier lists per semver rules: numeric\n * identifiers compare numerically, alphanumeric ones lexically, a numeric\n * identifier ranks below an alphanumeric one, and a longer list wins when\n * otherwise equal. Returns a negative / zero / positive number.\n */\nfunction comparePrerelease(left: string[], right: string[]): number {\n const length = Math.max(left.length, right.length);\n\n for (let index = 0; index < length; index++) {\n const a = left[index];\n const b = right[index];\n\n if (a === undefined) {\n return -1;\n }\n\n if (b === undefined) {\n return 1;\n }\n\n const aIsNumeric = /^\\d+$/.test(a);\n const bIsNumeric = /^\\d+$/.test(b);\n\n if (aIsNumeric && bIsNumeric) {\n const diff = Number(a) - Number(b);\n\n if (diff !== 0) {\n return diff;\n }\n\n continue;\n }\n\n if (aIsNumeric !== bIsNumeric) {\n return aIsNumeric ? -1 : 1;\n }\n\n if (a !== b) {\n return a < b ? -1 : 1;\n }\n }\n\n return 0;\n}\n\n/**\n * Return `true` when `latest` is a strictly newer semantic version than\n * `current`. Compares the numeric `major.minor.patch` first, then treats a\n * stable release as newer than a prerelease of the same core\n * (`1.2.0` > `1.2.0-beta`). Unparseable input yields `false` — we never nag\n * on a version string we can't understand.\n */\nexport function isNewerVersion(latest: string, current: string): boolean {\n const latestParsed = parseVersion(latest);\n const currentParsed = parseVersion(current);\n\n if (!latestParsed || !currentParsed) {\n return false;\n }\n\n for (let index = 0; index < 3; index++) {\n if (latestParsed.core[index] !== currentParsed.core[index]) {\n return latestParsed.core[index] > currentParsed.core[index];\n }\n }\n\n // Equal numeric core — order by prerelease (a stable release beats a prerelease).\n const latestIsStable = latestParsed.prerelease.length === 0;\n const currentIsStable = currentParsed.prerelease.length === 0;\n\n if (latestIsStable && currentIsStable) {\n return false;\n }\n\n if (latestIsStable !== currentIsStable) {\n return latestIsStable;\n }\n\n return comparePrerelease(latestParsed.prerelease, currentParsed.prerelease) > 0;\n}\n"],"mappings":";;;;;;AAqBA,SAAS,aAAa,SAA4C;CAEhE,MAAM,CAAC,qBADS,QAAQ,KAAK,CAAC,CAAC,QAAQ,OAAO,EACZ,CAAC,CAAC,MAAM,GAAG;CAC7C,MAAM,CAAC,MAAM,GAAG,mBAAmB,kBAAkB,MAAM,GAAG;CAC9D,MAAM,WAAW,KAAK,MAAM,GAAG;CAE/B,IAAI,SAAS,WAAW,GACtB;CAGF,MAAM,UAAU,SAAS,KAAK,YAAY,OAAO,OAAO,CAAC;CAEzD,IAAI,QAAQ,MAAM,UAAU,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,CAAC,GAC/D;CAGF,MAAM,aAAa,gBAAgB,SAAS,IAAI,gBAAgB,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;CAExF,OAAO;EACL,MAAM;GAAC,QAAQ;GAAI,QAAQ;GAAI,QAAQ;EAAE;EACzC;CACF;AACF;;;;;;;AAQA,SAAS,kBAAkB,MAAgB,OAAyB;CAClE,MAAM,SAAS,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM;CAEjD,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS;EAC3C,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,MAAM;EAEhB,IAAI,MAAM,QACR,OAAO;EAGT,IAAI,MAAM,QACR,OAAO;EAGT,MAAM,aAAa,QAAQ,KAAK,CAAC;EACjC,MAAM,aAAa,QAAQ,KAAK,CAAC;EAEjC,IAAI,cAAc,YAAY;GAC5B,MAAM,OAAO,OAAO,CAAC,IAAI,OAAO,CAAC;GAEjC,IAAI,SAAS,GACX,OAAO;GAGT;EACF;EAEA,IAAI,eAAe,YACjB,OAAO,aAAa,KAAK;EAG3B,IAAI,MAAM,GACR,OAAO,IAAI,IAAI,KAAK;CAExB;CAEA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,QAAgB,SAA0B;CACvE,MAAM,eAAe,aAAa,MAAM;CACxC,MAAM,gBAAgB,aAAa,OAAO;CAE1C,IAAI,CAAC,gBAAgB,CAAC,eACpB,OAAO;CAGT,KAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAC7B,IAAI,aAAa,KAAK,WAAW,cAAc,KAAK,QAClD,OAAO,aAAa,KAAK,SAAS,cAAc,KAAK;CAKzD,MAAM,iBAAiB,aAAa,WAAW,WAAW;CAC1D,MAAM,kBAAkB,cAAc,WAAW,WAAW;CAE5D,IAAI,kBAAkB,iBACpB,OAAO;CAGT,IAAI,mBAAmB,iBACrB,OAAO;CAGT,OAAO,kBAAkB,aAAa,YAAY,cAAc,UAAU,IAAI;AAChF"}
@@ -1,5 +1,5 @@
1
1
  import { v } from "@warlock.js/seal";
2
- import config from "@mongez/config";
2
+ import baseConfig from "@mongez/config";
3
3
  import { log } from "@warlock.js/logger";
4
4
  import { merge } from "@mongez/reinforcements";
5
5
 
@@ -40,7 +40,7 @@ async function validateAll(validation, request, response) {
40
40
  if (validation.validate) {
41
41
  const result = await validation.validate(request, response);
42
42
  if (result) {
43
- if (!response.statusCode) response.setStatusCode(config.get("validation.responseStatus", 400));
43
+ if (!response.statusCode) response.setStatusCode(baseConfig.get("validation.responseStatus", 400));
44
44
  log.info("validation", "failed", "Validation failed");
45
45
  return result;
46
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validateAll.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/validation/validateAll.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { merge } from \"@mongez/reinforcements\";\nimport { log } from \"@warlock.js/logger\";\nimport { v } from \"@warlock.js/seal\";\nimport type { Request, Response } from \"../http\";\nimport type { RequestHandlerValidation, Route } from \"../router\";\n\nfunction resolveDataToParse(validating: RequestHandlerValidation[\"validating\"], request: Request) {\n if (!validating || validating.length === 0) return request.allExceptParams();\n\n let data: any = {};\n\n for (const validatingType of validating) {\n if (validatingType === \"body\") {\n data = merge(data, request.body);\n }\n\n if (validatingType === \"query\") {\n data = merge(data, request.query);\n }\n\n if (validatingType === \"params\") {\n data = merge(data, request.params);\n }\n\n if (validatingType === \"headers\") {\n data = merge(data, request.headers);\n }\n }\n\n return data;\n}\n\n/**\n * Validate the request route\n */\nexport async function validateAll(\n validation: Route[\"handler\"][\"validation\"],\n request: Request,\n response: Response,\n) {\n if (!validation) return;\n\n log.info(\"validation\", \"started\", \"Start validating the request\");\n\n if (validation.schema) {\n log.info(\"validation\", \"schema\", \"Validating request schema\");\n try {\n const data = resolveDataToParse(validation.validating, request);\n const result = await v.validate(validation.schema, data);\n\n if (result.data && result.isValid) {\n request.setValidatedData(result.data);\n }\n\n if (!result.isValid) {\n log.warn(\"validation\", \"schema\", \"Schema Validation failed\");\n return response.failedSchema(result);\n }\n\n log.success(\"validation\", \"schema\", \"Schema Validation passed\");\n } catch (error) {\n log.warn(\"app.validation\", \"error\", error);\n throw error;\n }\n }\n\n if (validation.validate) {\n const result = await validation.validate(request, response);\n\n // if there is a result, it means it failed\n if (result) {\n // check if there is no response status code, then set it to config value or 400 as default\n if (!response.statusCode) {\n response.setStatusCode(config.get(\"validation.responseStatus\", 400));\n }\n\n log.info(\"validation\", \"failed\", \"Validation failed\");\n\n return result;\n }\n\n log.info(\"validation\", \"passed\", \"Validation passed\");\n }\n}\n"],"mappings":";;;;;;AAOA,SAAS,mBAAmB,YAAoD,SAAkB;CAChG,IAAI,CAAC,cAAc,WAAW,WAAW,GAAG,OAAO,QAAQ,gBAAgB;CAE3E,IAAI,OAAY,CAAC;CAEjB,KAAK,MAAM,kBAAkB,YAAY;EACvC,IAAI,mBAAmB,QACrB,OAAO,MAAM,MAAM,QAAQ,IAAI;EAGjC,IAAI,mBAAmB,SACrB,OAAO,MAAM,MAAM,QAAQ,KAAK;EAGlC,IAAI,mBAAmB,UACrB,OAAO,MAAM,MAAM,QAAQ,MAAM;EAGnC,IAAI,mBAAmB,WACrB,OAAO,MAAM,MAAM,QAAQ,OAAO;CAEtC;CAEA,OAAO;AACT;;;;AAKA,eAAsB,YACpB,YACA,SACA,UACA;CACA,IAAI,CAAC,YAAY;CAEjB,IAAI,KAAK,cAAc,WAAW,8BAA8B;CAEhE,IAAI,WAAW,QAAQ;EACrB,IAAI,KAAK,cAAc,UAAU,2BAA2B;EAC5D,IAAI;GACF,MAAM,OAAO,mBAAmB,WAAW,YAAY,OAAO;GAC9D,MAAM,SAAS,MAAM,EAAE,SAAS,WAAW,QAAQ,IAAI;GAEvD,IAAI,OAAO,QAAQ,OAAO,SACxB,QAAQ,iBAAiB,OAAO,IAAI;GAGtC,IAAI,CAAC,OAAO,SAAS;IACnB,IAAI,KAAK,cAAc,UAAU,0BAA0B;IAC3D,OAAO,SAAS,aAAa,MAAM;GACrC;GAEA,IAAI,QAAQ,cAAc,UAAU,0BAA0B;EAChE,SAAS,OAAO;GACd,IAAI,KAAK,kBAAkB,SAAS,KAAK;GACzC,MAAM;EACR;CACF;CAEA,IAAI,WAAW,UAAU;EACvB,MAAM,SAAS,MAAM,WAAW,SAAS,SAAS,QAAQ;EAG1D,IAAI,QAAQ;GAEV,IAAI,CAAC,SAAS,YACZ,SAAS,cAAc,OAAO,IAAI,6BAA6B,GAAG,CAAC;GAGrE,IAAI,KAAK,cAAc,UAAU,mBAAmB;GAEpD,OAAO;EACT;EAEA,IAAI,KAAK,cAAc,UAAU,mBAAmB;CACtD;AACF"}
1
+ {"version":3,"file":"validateAll.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/validation/validateAll.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { merge } from \"@mongez/reinforcements\";\nimport { log } from \"@warlock.js/logger\";\nimport { v } from \"@warlock.js/seal\";\nimport type { Request, Response } from \"../http\";\nimport type { RequestHandlerValidation, Route } from \"../router\";\n\nfunction resolveDataToParse(validating: RequestHandlerValidation[\"validating\"], request: Request) {\n if (!validating || validating.length === 0) return request.allExceptParams();\n\n let data: any = {};\n\n for (const validatingType of validating) {\n if (validatingType === \"body\") {\n data = merge(data, request.body);\n }\n\n if (validatingType === \"query\") {\n data = merge(data, request.query);\n }\n\n if (validatingType === \"params\") {\n data = merge(data, request.params);\n }\n\n if (validatingType === \"headers\") {\n data = merge(data, request.headers);\n }\n }\n\n return data;\n}\n\n/**\n * Validate the request route\n */\nexport async function validateAll(\n validation: Route[\"handler\"][\"validation\"],\n request: Request,\n response: Response,\n) {\n if (!validation) return;\n\n log.info(\"validation\", \"started\", \"Start validating the request\");\n\n if (validation.schema) {\n log.info(\"validation\", \"schema\", \"Validating request schema\");\n try {\n const data = resolveDataToParse(validation.validating, request);\n const result = await v.validate(validation.schema, data);\n\n if (result.data && result.isValid) {\n request.setValidatedData(result.data);\n }\n\n if (!result.isValid) {\n log.warn(\"validation\", \"schema\", \"Schema Validation failed\");\n return response.failedSchema(result);\n }\n\n log.success(\"validation\", \"schema\", \"Schema Validation passed\");\n } catch (error) {\n log.warn(\"app.validation\", \"error\", error);\n throw error;\n }\n }\n\n if (validation.validate) {\n const result = await validation.validate(request, response);\n\n // if there is a result, it means it failed\n if (result) {\n // check if there is no response status code, then set it to config value or 400 as default\n if (!response.statusCode) {\n response.setStatusCode(config.get(\"validation.responseStatus\", 400));\n }\n\n log.info(\"validation\", \"failed\", \"Validation failed\");\n\n return result;\n }\n\n log.info(\"validation\", \"passed\", \"Validation passed\");\n }\n}\n"],"mappings":";;;;;;AAOA,SAAS,mBAAmB,YAAoD,SAAkB;CAChG,IAAI,CAAC,cAAc,WAAW,WAAW,GAAG,OAAO,QAAQ,gBAAgB;CAE3E,IAAI,OAAY,CAAC;CAEjB,KAAK,MAAM,kBAAkB,YAAY;EACvC,IAAI,mBAAmB,QACrB,OAAO,MAAM,MAAM,QAAQ,IAAI;EAGjC,IAAI,mBAAmB,SACrB,OAAO,MAAM,MAAM,QAAQ,KAAK;EAGlC,IAAI,mBAAmB,UACrB,OAAO,MAAM,MAAM,QAAQ,MAAM;EAGnC,IAAI,mBAAmB,WACrB,OAAO,MAAM,MAAM,QAAQ,OAAO;CAEtC;CAEA,OAAO;AACT;;;;AAKA,eAAsB,YACpB,YACA,SACA,UACA;CACA,IAAI,CAAC,YAAY;CAEjB,IAAI,KAAK,cAAc,WAAW,8BAA8B;CAEhE,IAAI,WAAW,QAAQ;EACrB,IAAI,KAAK,cAAc,UAAU,2BAA2B;EAC5D,IAAI;GACF,MAAM,OAAO,mBAAmB,WAAW,YAAY,OAAO;GAC9D,MAAM,SAAS,MAAM,EAAE,SAAS,WAAW,QAAQ,IAAI;GAEvD,IAAI,OAAO,QAAQ,OAAO,SACxB,QAAQ,iBAAiB,OAAO,IAAI;GAGtC,IAAI,CAAC,OAAO,SAAS;IACnB,IAAI,KAAK,cAAc,UAAU,0BAA0B;IAC3D,OAAO,SAAS,aAAa,MAAM;GACrC;GAEA,IAAI,QAAQ,cAAc,UAAU,0BAA0B;EAChE,SAAS,OAAO;GACd,IAAI,KAAK,kBAAkB,SAAS,KAAK;GACzC,MAAM;EACR;CACF;CAEA,IAAI,WAAW,UAAU;EACvB,MAAM,SAAS,MAAM,WAAW,SAAS,SAAS,QAAQ;EAG1D,IAAI,QAAQ;GAEV,IAAI,CAAC,SAAS,YACZ,SAAS,cAAcA,WAAO,IAAI,6BAA6B,GAAG,CAAC;GAGrE,IAAI,KAAK,cAAc,UAAU,mBAAmB;GAEpD,OAAO;EACT;EAEA,IAAI,KAAK,cAAc,UAAU,mBAAmB;CACtD;AACF"}