@warlock.js/core 4.12.0 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +214 -0
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +1 -1
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/build.command.mjs +1 -1
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +1 -1
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs +1 -1
- package/esm/cli/commands/generate/utils/writer.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +1 -1
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.command.mjs +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +2 -2
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/{cli → commands}/cli-command.d.mts +1 -1
- package/esm/commands/cli-command.d.mts.map +1 -0
- package/esm/{cli → commands}/cli-command.mjs +1 -1
- package/esm/commands/cli-command.mjs.map +1 -0
- package/esm/{cli → commands}/types.d.mts +1 -1
- package/esm/commands/types.d.mts.map +1 -0
- package/esm/config/load-config-files.mjs +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/base-connector.mjs +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +5 -3
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +0 -2
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -2
- package/esm/dev-server/file-event-handler.mjs +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.mjs +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.mjs +1 -1
- package/esm/dev-server/file-operations.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +3 -18
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -1
- package/esm/dev-server/layer-executor.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +2 -2
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +20 -8
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/http/build-cors-options.mjs +32 -0
- package/esm/http/build-cors-options.mjs.map +1 -0
- package/esm/http/middleware/max-body-size.middleware.d.mts +13 -5
- package/esm/http/middleware/max-body-size.middleware.d.mts.map +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs +13 -5
- package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +2 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +3 -8
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +9 -17
- package/esm/index.mjs +4 -15
- package/esm/production/production-builder.mjs +51 -3
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/router/router.d.mts +9 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +58 -5
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +24 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +2 -3
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +38 -15
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-connectors-selection.d.mts +14 -0
- package/esm/tests/test-connectors-selection.d.mts.map +1 -0
- package/esm/tests/test-connectors-selection.mjs +72 -0
- package/esm/tests/test-connectors-selection.mjs.map +1 -0
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +34 -13
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-lifecycle-state.mjs +27 -0
- package/esm/tests/test-lifecycle-state.mjs.map +1 -0
- package/esm/tests/test-setup-timeout.mjs +53 -0
- package/esm/tests/test-setup-timeout.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +64 -8
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +317 -11
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/utils/framework-vesion.mjs +1 -1
- package/esm/utils/index.d.mts +1 -0
- package/esm/utils/index.mjs +1 -0
- package/esm/utils/normalized-path.d.mts +55 -0
- package/esm/utils/normalized-path.d.mts.map +1 -0
- package/esm/{dev-server/path.mjs → utils/normalized-path.mjs} +15 -2
- package/esm/utils/normalized-path.mjs.map +1 -0
- package/esm/vite/index.d.mts +2 -0
- package/esm/vite/index.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts +1 -1
- package/esm/vite/lower-stage3-decorators.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.mjs.map +1 -1
- package/esm/warlock-config/index.mjs +1 -1
- package/esm/warlock-config/types.d.mts +34 -1
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs +1 -2
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +78 -23
- package/llms.txt +3 -3
- package/package.json +24 -12
- package/skills/lower-stage3-decorators/SKILL.md +4 -2
- package/skills/run-app/SKILL.md +6 -0
- package/skills/test-http/SKILL.md +37 -12
- package/skills/test-service/SKILL.md +167 -24
- package/skills/use-middleware/SKILL.md +5 -1
- package/esm/cli/cli-command.d.mts.map +0 -1
- package/esm/cli/cli-command.mjs.map +0 -1
- package/esm/cli/types.d.mts.map +0 -1
- package/esm/dev-server/files-orchestrator.d.mts +0 -89
- package/esm/dev-server/files-orchestrator.d.mts.map +0 -1
- package/esm/dev-server/files-watcher.d.mts +0 -69
- package/esm/dev-server/files-watcher.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts +0 -55
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts +0 -44
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts +0 -60
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts +0 -119
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts.map +0 -1
- package/esm/dev-server/health-checker/index.mjs +0 -7
- package/esm/dev-server/module-loader.d.mts +0 -63
- package/esm/dev-server/module-loader.d.mts.map +0 -1
- package/esm/dev-server/path.mjs.map +0 -1
- package/esm/tests/test-server-port-channel.d.mts +0 -27
- package/esm/tests/test-server-port-channel.d.mts.map +0 -1
- /package/esm/{cli → commands}/index.d.mts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-operations.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-operations.ts"],"sourcesContent":["import events from \"@mongez/events\";\
|
|
1
|
+
{"version":3,"file":"file-operations.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-operations.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type { DependencyGraph } from \"./dependency-graph\";\nimport { DEV_SERVER_EVENTS } from \"./events\";\nimport { FileManager } from \"./file-manager\";\nimport type { ManifestManager } from \"./manifest-manager\";\nimport { parseImports } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\nimport type { SpecialFilesCollector } from \"./special-files-collector\";\nimport { areSetsEqual } from \"./utils\";\n\n/**\n * FileOperations — add/update/delete lifecycle for a single file.\n *\n * Coordinates the dependency graph, manifest, and special-files collector\n * so the orchestrator and event handler can stay declarative.\n */\nexport class FileOperations {\n public constructor(\n private readonly files: Map<string, FileManager>,\n private readonly dependencyGraph: DependencyGraph,\n private readonly manifest: ManifestManager,\n private readonly specialFilesCollector: SpecialFilesCollector,\n ) {}\n\n /**\n * Register and process a new file. Recursively pulls in any\n * not-yet-tracked dependencies so the dep graph is complete.\n */\n public async addFile(relativePath: string): Promise<FileManager> {\n const existing = this.files.get(relativePath);\n if (existing) return existing;\n\n const fileManager = new FileManager(Path.toAbsolute(relativePath), this.files, this);\n\n // Register before processing so recursive deps see us.\n this.files.set(relativePath, fileManager);\n\n await fileManager.process();\n\n for (const depPath of fileManager.dependencies) {\n if (!this.files.has(depPath)) {\n try {\n await this.addFile(depPath);\n } catch {\n // External or missing — let the importer surface it at load time.\n }\n }\n }\n\n for (const dependency of fileManager.dependencies) {\n this.dependencyGraph.addDependency(relativePath, dependency);\n }\n\n this.specialFilesCollector.addFile(fileManager);\n\n await this.reloadFilesWaitingForDependency(relativePath);\n\n return fileManager;\n }\n\n /**\n * Reprocess a file after a change. Re-syncs the dep graph and special-files\n * collector when its imports or path classification shift.\n */\n public async updateFile(relativePath: string): Promise<boolean> {\n const fileManager = this.files.get(relativePath);\n if (!fileManager) {\n await this.addFile(relativePath);\n return true;\n }\n\n const oldDependencies = new Set(fileManager.dependencies);\n\n try {\n const hasChanged = await fileManager.process();\n if (!hasChanged) return false;\n\n if (!areSetsEqual(oldDependencies, fileManager.dependencies)) {\n this.dependencyGraph.updateFile(\n relativePath,\n fileManager.dependencies,\n fileManager.typeOnlyDependencies,\n );\n }\n\n this.specialFilesCollector.updateFile(fileManager);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Remove a file. Notifies dependents so they surface broken-import errors.\n */\n public async deleteFile(relativePath: string): Promise<void> {\n const fileManager = this.files.get(relativePath);\n if (!fileManager) return;\n\n const dependents = this.dependencyGraph.getDependents(relativePath);\n\n this.dependencyGraph.removeFile(relativePath);\n this.specialFilesCollector.removeFile(relativePath);\n this.manifest.removeFile(relativePath);\n\n for (const dependentPath of dependents) {\n const dependentFile = this.files.get(dependentPath);\n if (dependentFile) {\n events.trigger(DEV_SERVER_EVENTS.FILE_READY, dependentFile);\n }\n }\n\n // Slight delay so in-flight reads still find the FileManager.\n setTimeout(() => this.files.delete(relativePath), 300);\n }\n\n /**\n * Re-process any file whose imports might now resolve to a freshly added\n * file. Covers \"import A from './b'\" where b.ts only just got created.\n */\n private async reloadFilesWaitingForDependency(newFilePath: string): Promise<void> {\n const dependents: string[] = [];\n\n for (const [existingPath, existingFile] of this.files) {\n if (existingPath === newFilePath) continue;\n if (existingFile.state !== \"ready\") continue;\n\n try {\n const importMap = await parseImports(existingFile.source, existingFile.absolutePath);\n for (const [, resolved] of importMap) {\n if (resolved && Path.toRelative(resolved.absolutePath) === newFilePath) {\n dependents.push(existingPath);\n break;\n }\n }\n } catch {\n continue;\n }\n }\n\n for (const dependentPath of dependents) {\n const dependentFile = this.files.get(dependentPath);\n if (!dependentFile) continue;\n try {\n await dependentFile.process({ force: true });\n this.dependencyGraph.updateFile(\n dependentPath,\n dependentFile.dependencies,\n dependentFile.typeOnlyDependencies,\n );\n } catch {\n // File still has issues — ignore.\n }\n }\n }\n\n public updateFileDependents(): void {\n for (const [relativePath, fileManager] of this.files) {\n fileManager.dependents = this.dependencyGraph.getDependents(relativePath);\n }\n }\n\n public syncFilesToManifest(): void {\n for (const [relativePath, fileManager] of this.files) {\n this.manifest.setFile(relativePath, fileManager.toManifest());\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Path } from "
|
|
2
|
-
import { devLogDim, devLogSuccess } from "./dev-logger.mjs";
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
3
2
|
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
4
3
|
import { DependencyGraph } from "./dependency-graph.mjs";
|
|
4
|
+
import { devLogDim, devLogSuccess } from "./dev-logger.mjs";
|
|
5
5
|
import "./flags.mjs";
|
|
6
6
|
import { tsconfigManager } from "./tsconfig-manager.mjs";
|
|
7
7
|
import { FileEventHandler } from "./file-event-handler.mjs";
|
|
@@ -23,21 +23,6 @@ import { init } from "es-module-lexer";
|
|
|
23
23
|
|
|
24
24
|
//#region ../core/src/dev-server/files-orchestrator.ts
|
|
25
25
|
/**
|
|
26
|
-
* Register a cleanup callback that fires before the current module is
|
|
27
|
-
* unloaded by HMR. Stack-inspects the caller so user code doesn't need to
|
|
28
|
-
* thread the FileManager through.
|
|
29
|
-
*/
|
|
30
|
-
function onCleanup(callback) {
|
|
31
|
-
if (globalThis.__currentModuleFile) {
|
|
32
|
-
globalThis.__currentModuleFile.addCleanup(callback);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const callerLine = (/* @__PURE__ */ new Error()).stack?.split("\n")[2];
|
|
36
|
-
const callerFile = (callerLine?.match(/\((.+?):\d+:\d+\)/) || callerLine?.match(/at (.+?):\d+:\d+/))?.[1];
|
|
37
|
-
if (!callerFile) return;
|
|
38
|
-
filesOrchestrator.files.get(Path.toRelative(callerFile))?.addCleanup(callback);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
26
|
* Top-level coordinator for the dev server's file system.
|
|
42
27
|
*
|
|
43
28
|
* Owns: file discovery, the dependency graph, the manifest, the special-files
|
|
@@ -197,5 +182,5 @@ async function runInBatches(items, size, fn) {
|
|
|
197
182
|
const filesOrchestrator = new FilesOrchestrator();
|
|
198
183
|
|
|
199
184
|
//#endregion
|
|
200
|
-
export {
|
|
185
|
+
export { filesOrchestrator };
|
|
201
186
|
//# sourceMappingURL=files-orchestrator.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { init } from \"es-module-lexer\";\r\nimport type { MessagePort } from \"node:worker_threads\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { DependencyGraph } from \"./dependency-graph\";\r\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\r\nimport { FileEventHandler } from \"./file-event-handler\";\r\nimport { FileManager } from \"./file-manager\";\r\nimport { FileOperations } from \"./file-operations\";\r\nimport { FilesWatcher } from \"./files-watcher\";\r\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\r\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\r\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\r\nimport { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\r\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\r\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\r\nimport { registerLoader } from \"./loader/register-loader.js\";\r\nimport { ManifestManager } from \"./manifest-manager\";\r\nimport { ModuleLoader } from \"./module-loader\";\r\nimport { packageJsonManager } from \"./package-json-manager\";\r\nimport { Path } from \"./path\";\r\nimport { SpecialFilesCollector } from \"./special-files-collector\";\r\nimport { tsconfigManager } from \"./tsconfig-manager\";\r\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\r\n\r\n/**\r\n * Register a cleanup callback that fires before the current module is\r\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\r\n * thread the FileManager through.\r\n */\r\nexport function onCleanup(callback: () => any) {\r\n if (globalThis.__currentModuleFile) {\r\n globalThis.__currentModuleFile.addCleanup(callback);\r\n return;\r\n }\r\n\r\n const stack = new Error().stack;\r\n const callerLine = stack?.split(\"\\n\")[2];\r\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\r\n const callerFile = match?.[1];\r\n\r\n if (!callerFile) return;\r\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\r\n fileManager?.addCleanup(callback);\r\n}\r\n\r\n/**\r\n * Top-level coordinator for the dev server's file system.\r\n *\r\n * Owns: file discovery, the dependency graph, the manifest, the special-files\r\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\r\n * provides cache-busting via `?v=N` version tokens).\r\n */\r\nexport class FilesOrchestrator {\r\n public readonly filesWatcher = new FilesWatcher();\r\n public readonly files = new Map<string, FileManager>();\r\n private readonly manifest = new ManifestManager(this.files);\r\n private readonly dependencyGraph = new DependencyGraph();\r\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\r\n public readonly specialFilesCollector = new SpecialFilesCollector();\r\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\r\n public readonly fileOperations: FileOperations;\r\n private readonly eventHandler: FileEventHandler;\r\n\r\n /** Main-thread end of the MessageChannel to the loader hook worker. */\r\n private loaderPort: MessagePort | null = null;\r\n\r\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\r\n private readonly pendingFlushes: Array<() => void> = [];\r\n\r\n public isInitialized = false;\r\n\r\n public constructor() {\r\n this.fileOperations = new FileOperations(\r\n this.files,\r\n this.dependencyGraph,\r\n this.manifest,\r\n this.specialFilesCollector,\r\n );\r\n\r\n this.eventHandler = new FileEventHandler(\r\n this.fileOperations,\r\n this.manifest,\r\n this.dependencyGraph,\r\n this.files,\r\n );\r\n }\r\n\r\n public async add(relativePath: string): Promise<FileManager> {\r\n return this.fileOperations.addFile(relativePath);\r\n }\r\n\r\n public async load<T>(relativePath: string, type = \"other\") {\r\n const fileManager = await this.add(relativePath);\r\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\r\n }\r\n\r\n public getDependencyGraph(): DependencyGraph {\r\n return this.dependencyGraph;\r\n }\r\n\r\n public getInvalidationChain(file: string): string[] {\r\n return this.dependencyGraph.getInvalidationChain(file);\r\n }\r\n\r\n public getFiles(): Map<string, FileManager> {\r\n return this.files;\r\n }\r\n\r\n public getHealthCheckerManager(): FilesHealthcareManager {\r\n return this.healthCheckerManager;\r\n }\r\n\r\n /**\r\n * Initialise managers and register the ESM loader hook. Must be called\r\n * before any user `src/` module is dynamically imported.\r\n */\r\n public async init() {\r\n if (this.isInitialized) return;\r\n this.isInitialized = true;\r\n\r\n await init;\r\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\r\n\r\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\r\n await ensureWarlockDirectory();\r\n\r\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\r\n const transpileInit = buildTranspileInit(\r\n tsconfigManager.tsconfig?.compilerOptions,\r\n devServerConfig?.transpileCacheDebug === true,\r\n );\r\n\r\n this.loaderPort = await registerLoader(transpileInit);\r\n\r\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\r\n if (msg.type === \"sync-ack\") {\r\n this.pendingFlushes.shift()?.();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Tell the hook worker a file changed. The next `import()` of it will get\r\n * a fresh `?v=N` URL → Node cache miss → fresh content.\r\n */\r\n public bumpVersion(absolutePath: string): void {\r\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\r\n }\r\n\r\n /**\r\n * Wait until the hook worker has processed every pending bump.\r\n *\r\n * `postMessage` is async — without this, a follow-up `import()` may\r\n * resolve before the worker increments the counter and Node will return\r\n * the cached old module.\r\n */\r\n public flushVersionBumps(): Promise<void> {\r\n if (!this.loaderPort) return Promise.resolve();\r\n\r\n return new Promise<void>((resolve) => {\r\n this.pendingFlushes.push(resolve);\r\n this.loaderPort!.postMessage({ type: \"sync\" });\r\n });\r\n }\r\n\r\n /**\r\n * Discover files on disk, reconcile against the manifest, build the dep\r\n * graph, and persist the new manifest. Idempotent.\r\n */\r\n public async initializeAll() {\r\n const [filesInFilesystem, manifestExists] = await Promise.all([\r\n this.getAllFilesFromFilesystem(),\r\n this.manifest.init(),\r\n ]);\r\n\r\n if (!manifestExists) {\r\n await this.processFiles(filesInFilesystem);\r\n } else {\r\n await this.reconcileFiles(filesInFilesystem);\r\n }\r\n\r\n this.dependencyGraph.build(this.files);\r\n this.fileOperations.updateFileDependents();\r\n this.fileOperations.syncFilesToManifest();\r\n await this.manifest.save();\r\n }\r\n\r\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\r\n const filesToCheck = [...files.added, ...files.changed]\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n const filesToDelete = files.deleted\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n await this.healthCheckerManager.onFileChanges(filesToCheck);\r\n this.healthCheckerManager.removeFiles(filesToDelete);\r\n this.healthCheckerManager.checkFiles(filesToCheck);\r\n }\r\n\r\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\r\n devLogDim(\"Started File Health Checks in the background.\");\r\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\r\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\r\n await this.healthCheckerManager.validateAllFiles();\r\n }\r\n\r\n /**\r\n * Glob the src directory, returning relative paths.\r\n */\r\n public async getAllFilesFromFilesystem(): Promise<string[]> {\r\n const absolutePaths = await getFilesFromDirectory();\r\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\r\n }\r\n\r\n /**\r\n * Process every file from scratch — used when no manifest exists.\r\n */\r\n private async processFiles(filePaths: string[]) {\r\n devLogDim(`processing ${filePaths.length} files...`);\r\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.process();\r\n });\r\n devLogSuccess(`processed ${filePaths.length} files`);\r\n }\r\n\r\n private async reconcileFiles(filesInFilesystem: string[]) {\r\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\r\n const filesInFilesystemSet = new Set(filesInFilesystem);\r\n\r\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\r\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\r\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\r\n\r\n if (newFiles.length > 0 || deletedFiles.length > 0) {\r\n devLogDim(\r\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\r\n );\r\n }\r\n\r\n await this.processFiles(newFiles);\r\n\r\n for (const relativePath of deletedFiles) {\r\n this.manifest.removeFile(relativePath);\r\n this.files.delete(relativePath);\r\n }\r\n\r\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.init(this.manifest.getFile(relativePath));\r\n });\r\n }\r\n\r\n public async watchFiles() {\r\n devLogSuccess(\"watching for file changes\");\r\n\r\n this.filesWatcher.onFileChange((p) => this.eventHandler.handleFileChange(p));\r\n this.filesWatcher.onFileAdd((p) => this.eventHandler.handleFileAdd(p));\r\n this.filesWatcher.onFileDelete((p) => this.eventHandler.handleFileDelete(p));\r\n\r\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\r\n await this.filesWatcher.watch(watchConfig);\r\n }\r\n}\r\n\r\nasync function runInBatches<T>(\r\n items: T[],\r\n size: number,\r\n fn: (item: T) => Promise<unknown>,\r\n): Promise<void> {\r\n if (items.length === 0) return;\r\n for (let i = 0; i < items.length; i += size) {\r\n await Promise.all(items.slice(i, i + size).map(fn));\r\n }\r\n}\r\n\r\nexport const filesOrchestrator = new FilesOrchestrator();\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { init } from \"es-module-lexer\";\r\nimport type { MessagePort } from \"node:worker_threads\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { DependencyGraph } from \"./dependency-graph\";\r\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\r\nimport { FileEventHandler } from \"./file-event-handler\";\r\nimport { FileManager } from \"./file-manager\";\r\nimport { FileOperations } from \"./file-operations\";\r\nimport { FilesWatcher } from \"./files-watcher\";\r\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\r\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\r\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\r\nimport { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\r\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\r\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\r\nimport { registerLoader } from \"./loader/register-loader.js\";\r\nimport { ManifestManager } from \"./manifest-manager\";\r\nimport { ModuleLoader } from \"./module-loader\";\r\nimport { packageJsonManager } from \"./package-json-manager\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { SpecialFilesCollector } from \"./special-files-collector\";\r\nimport { tsconfigManager } from \"./tsconfig-manager\";\r\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\r\n\r\n/**\r\n * Register a cleanup callback that fires before the current module is\r\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\r\n * thread the FileManager through.\r\n */\r\nexport function onCleanup(callback: () => any) {\r\n if (globalThis.__currentModuleFile) {\r\n globalThis.__currentModuleFile.addCleanup(callback);\r\n return;\r\n }\r\n\r\n const stack = new Error().stack;\r\n const callerLine = stack?.split(\"\\n\")[2];\r\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\r\n const callerFile = match?.[1];\r\n\r\n if (!callerFile) return;\r\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\r\n fileManager?.addCleanup(callback);\r\n}\r\n\r\n/**\r\n * Top-level coordinator for the dev server's file system.\r\n *\r\n * Owns: file discovery, the dependency graph, the manifest, the special-files\r\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\r\n * provides cache-busting via `?v=N` version tokens).\r\n */\r\nexport class FilesOrchestrator {\r\n public readonly filesWatcher = new FilesWatcher();\r\n public readonly files = new Map<string, FileManager>();\r\n private readonly manifest = new ManifestManager(this.files);\r\n private readonly dependencyGraph = new DependencyGraph();\r\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\r\n public readonly specialFilesCollector = new SpecialFilesCollector();\r\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\r\n public readonly fileOperations: FileOperations;\r\n private readonly eventHandler: FileEventHandler;\r\n\r\n /** Main-thread end of the MessageChannel to the loader hook worker. */\r\n private loaderPort: MessagePort | null = null;\r\n\r\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\r\n private readonly pendingFlushes: Array<() => void> = [];\r\n\r\n public isInitialized = false;\r\n\r\n public constructor() {\r\n this.fileOperations = new FileOperations(\r\n this.files,\r\n this.dependencyGraph,\r\n this.manifest,\r\n this.specialFilesCollector,\r\n );\r\n\r\n this.eventHandler = new FileEventHandler(\r\n this.fileOperations,\r\n this.manifest,\r\n this.dependencyGraph,\r\n this.files,\r\n );\r\n }\r\n\r\n public async add(relativePath: string): Promise<FileManager> {\r\n return this.fileOperations.addFile(relativePath);\r\n }\r\n\r\n public async load<T>(relativePath: string, type = \"other\") {\r\n const fileManager = await this.add(relativePath);\r\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\r\n }\r\n\r\n public getDependencyGraph(): DependencyGraph {\r\n return this.dependencyGraph;\r\n }\r\n\r\n public getInvalidationChain(file: string): string[] {\r\n return this.dependencyGraph.getInvalidationChain(file);\r\n }\r\n\r\n public getFiles(): Map<string, FileManager> {\r\n return this.files;\r\n }\r\n\r\n public getHealthCheckerManager(): FilesHealthcareManager {\r\n return this.healthCheckerManager;\r\n }\r\n\r\n /**\r\n * Initialise managers and register the ESM loader hook. Must be called\r\n * before any user `src/` module is dynamically imported.\r\n */\r\n public async init() {\r\n if (this.isInitialized) return;\r\n this.isInitialized = true;\r\n\r\n await init;\r\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\r\n\r\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\r\n await ensureWarlockDirectory();\r\n\r\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\r\n const transpileInit = buildTranspileInit(\r\n tsconfigManager.tsconfig?.compilerOptions,\r\n devServerConfig?.transpileCacheDebug === true,\r\n );\r\n\r\n this.loaderPort = await registerLoader(transpileInit);\r\n\r\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\r\n if (msg.type === \"sync-ack\") {\r\n this.pendingFlushes.shift()?.();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Tell the hook worker a file changed. The next `import()` of it will get\r\n * a fresh `?v=N` URL → Node cache miss → fresh content.\r\n */\r\n public bumpVersion(absolutePath: string): void {\r\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\r\n }\r\n\r\n /**\r\n * Wait until the hook worker has processed every pending bump.\r\n *\r\n * `postMessage` is async — without this, a follow-up `import()` may\r\n * resolve before the worker increments the counter and Node will return\r\n * the cached old module.\r\n */\r\n public flushVersionBumps(): Promise<void> {\r\n if (!this.loaderPort) return Promise.resolve();\r\n\r\n return new Promise<void>((resolve) => {\r\n this.pendingFlushes.push(resolve);\r\n this.loaderPort!.postMessage({ type: \"sync\" });\r\n });\r\n }\r\n\r\n /**\r\n * Discover files on disk, reconcile against the manifest, build the dep\r\n * graph, and persist the new manifest. Idempotent.\r\n */\r\n public async initializeAll() {\r\n const [filesInFilesystem, manifestExists] = await Promise.all([\r\n this.getAllFilesFromFilesystem(),\r\n this.manifest.init(),\r\n ]);\r\n\r\n if (!manifestExists) {\r\n await this.processFiles(filesInFilesystem);\r\n } else {\r\n await this.reconcileFiles(filesInFilesystem);\r\n }\r\n\r\n this.dependencyGraph.build(this.files);\r\n this.fileOperations.updateFileDependents();\r\n this.fileOperations.syncFilesToManifest();\r\n await this.manifest.save();\r\n }\r\n\r\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\r\n const filesToCheck = [...files.added, ...files.changed]\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n const filesToDelete = files.deleted\r\n .map((file) => this.files.get(Path.toRelative(file)))\r\n .filter((file): file is FileManager => !!file);\r\n\r\n await this.healthCheckerManager.onFileChanges(filesToCheck);\r\n this.healthCheckerManager.removeFiles(filesToDelete);\r\n this.healthCheckerManager.checkFiles(filesToCheck);\r\n }\r\n\r\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\r\n devLogDim(\"Started File Health Checks in the background.\");\r\n\r\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\r\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\r\n await this.healthCheckerManager.validateAllFiles();\r\n }\r\n\r\n /**\r\n * Glob the src directory, returning relative paths.\r\n */\r\n public async getAllFilesFromFilesystem(): Promise<string[]> {\r\n const absolutePaths = await getFilesFromDirectory();\r\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\r\n }\r\n\r\n /**\r\n * Process every file from scratch — used when no manifest exists.\r\n */\r\n private async processFiles(filePaths: string[]) {\r\n devLogDim(`processing ${filePaths.length} files...`);\r\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.process();\r\n });\r\n devLogSuccess(`processed ${filePaths.length} files`);\r\n }\r\n\r\n private async reconcileFiles(filesInFilesystem: string[]) {\r\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\r\n const filesInFilesystemSet = new Set(filesInFilesystem);\r\n\r\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\r\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\r\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\r\n\r\n if (newFiles.length > 0 || deletedFiles.length > 0) {\r\n devLogDim(\r\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\r\n );\r\n }\r\n\r\n await this.processFiles(newFiles);\r\n\r\n for (const relativePath of deletedFiles) {\r\n this.manifest.removeFile(relativePath);\r\n this.files.delete(relativePath);\r\n }\r\n\r\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\r\n const fileManager = new FileManager(\r\n Path.toAbsolute(relativePath),\r\n this.files,\r\n this.fileOperations,\r\n );\r\n this.files.set(relativePath, fileManager);\r\n await fileManager.init(this.manifest.getFile(relativePath));\r\n });\r\n }\r\n\r\n public async watchFiles() {\r\n devLogSuccess(\"watching for file changes\");\r\n\r\n this.filesWatcher.onFileChange((p) => this.eventHandler.handleFileChange(p));\r\n this.filesWatcher.onFileAdd((p) => this.eventHandler.handleFileAdd(p));\r\n this.filesWatcher.onFileDelete((p) => this.eventHandler.handleFileDelete(p));\r\n\r\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\r\n await this.filesWatcher.watch(watchConfig);\r\n }\r\n}\r\n\r\nasync function runInBatches<T>(\r\n items: T[],\r\n size: number,\r\n fn: (item: T) => Promise<unknown>,\r\n): Promise<void> {\r\n if (items.length === 0) return;\r\n for (let i = 0; i < items.length; i += size) {\r\n await Promise.all(items.slice(i, i + size).map(fn));\r\n }\r\n}\r\n\r\nexport const filesOrchestrator = new FilesOrchestrator();\r\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
1
2
|
import { rootPath, srcPath } from "../utils/paths.mjs";
|
|
2
3
|
import "../utils/index.mjs";
|
|
3
|
-
import { Path } from "./path.mjs";
|
|
4
4
|
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
5
5
|
import { Random } from "@mongez/reinforcements";
|
|
6
6
|
import events from "@mongez/events";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files-watcher.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-watcher.ts"],"sourcesContent":["import events from \"@mongez/events\";\
|
|
1
|
+
{"version":3,"file":"files-watcher.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-watcher.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { Random } from \"@mongez/reinforcements\";\nimport chokidar from \"chokidar\";\nimport { rootPath, srcPath } from \"../utils\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { Path } from \"../utils/normalized-path\";\n\ntype FileWatcherEvent = \"change\" | \"delete\" | \"add\" | \"error\" | \"addDir\" | \"unlinkDir\";\n\ntype FileChangeCallback = (filePath: string) => void;\ntype FileDeleteCallback = (filePath: string) => void;\ntype FileAddCallback = (filePath: string) => void;\ntype FileErrorCallback = (filePath: string, error: Error) => void;\ntype FileAddDirCallback = (filePath: string) => void;\ntype FileUnlinkDirCallback = (filePath: string) => void;\ntype OnFileEventCallback =\n | FileChangeCallback\n | FileDeleteCallback\n | FileAddCallback\n | FileErrorCallback\n | FileAddDirCallback\n | FileUnlinkDirCallback;\n\n/**\n * Watch configuration options\n */\nexport type WatchConfig = {\n /**\n * Glob patterns to include\n */\n include?: string[];\n /**\n * Glob patterns to exclude\n */\n exclude?: string[];\n};\n\n/**\n * Default patterns to exclude from watching\n */\nconst DEFAULT_EXCLUDE = [\"**/node_modules/**\", \"**/dist/**\", \"**/.warlock/**\", \"**/.git/**\"];\n\n/**\n * All .env file variants to watch\n */\nconst ENV_FILES = [\n \".env\",\n \".env.local\",\n \".env.development\",\n \".env.development.local\",\n \".env.test\",\n \".env.test.local\",\n \".env.production\",\n \".env.production.local\",\n];\n\nexport class FilesWatcher {\n /**\n * File watcher id\n */\n private id = Random.string();\n\n /**\n * Watch for files changes\n * @param config Optional watch configuration\n */\n public async watch(config?: WatchConfig) {\n // Get user config from warlock.config.ts\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\n const userWatchConfig = devServerConfig?.watch;\n\n // Build paths to watch:\n // 1. All .env variants that exist\n // 2. warlock.config.ts (project-level settings; restart-required on change)\n // 3. src directory\n // 4. Any additional paths from user config\n const envPaths = ENV_FILES.map((file) => rootPath(file));\n const basePaths = [...envPaths, rootPath(\"warlock.config.ts\"), srcPath()];\n const additionalPaths = userWatchConfig?.include || config?.include || [];\n\n const paths = [...basePaths, ...additionalPaths].map((path) => Path.normalize(path));\n\n // Merge default exclude with config exclude\n const ignored = [\n ...DEFAULT_EXCLUDE,\n ...(userWatchConfig?.exclude || []),\n ...(config?.exclude || []),\n ];\n\n const watcher = chokidar.watch(paths, {\n ignoreInitial: true,\n ignored,\n persistent: true,\n usePolling: false, // Try native first, will fallback if needed\n interval: 100,\n binaryInterval: 300,\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n },\n // On Windows, explicitly enable recursive watching\n depth: 99,\n });\n\n watcher.on(\"add\", (filePath) => this.triggerEvent(\"add\", filePath));\n watcher.on(\"change\", (filePath) => this.triggerEvent(\"change\", filePath));\n watcher.on(\"unlink\", (filePath) => this.triggerEvent(\"delete\", filePath));\n watcher.on(\"addDir\", (filePath) => this.triggerEvent(\"addDir\", filePath));\n watcher.on(\"unlinkDir\", (filePath) => this.triggerEvent(\"unlinkDir\", filePath));\n // watcher.on(\"error\", (error: Error, filePath: string) =>\n // this.triggerEvent(\"error\", filePath, error),\n // );\n\n // Cleanup on process exit\n process.on(\"SIGINT\", async () => {\n await watcher.close();\n });\n }\n\n /**\n * Trigger event immediately (no debouncing here)\n * Debouncing is handled at the orchestrator level for batch processing\n */\n private triggerEvent(event: FileWatcherEvent, filePath: string, error?: Error) {\n events.trigger(`file-watcher.${this.id}.${event}`, Path.normalize(filePath), error);\n }\n\n /**\n * On file change event\n */\n public onFileChange(callback: FileChangeCallback) {\n return this.on(\"change\", callback);\n }\n\n /**\n * On file delete event\n */\n public onFileDelete(callback: FileDeleteCallback) {\n return this.on(\"delete\", callback);\n }\n\n /**\n * On file add event\n */\n public onFileAdd(callback: FileAddCallback) {\n return this.on(\"add\", callback);\n }\n\n /**\n * On file error event\n */\n public onFileError(callback: FileErrorCallback) {\n return this.on(\"error\", callback);\n }\n\n /**\n * On file add dir event\n */\n public onDirectoryAdd(callback: FileAddDirCallback) {\n return this.on(\"addDir\", callback);\n }\n\n /**\n * On file unlink dir event\n */\n public onDirectoryRemove(callback: FileUnlinkDirCallback) {\n return this.on(\"unlinkDir\", callback);\n }\n\n /**\n * On file event\n */\n public on(event: FileWatcherEvent, callback: OnFileEventCallback) {\n return events.subscribe(`file-watcher.${this.id}.${event}`, callback);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAwCA,MAAM,kBAAkB;CAAC;CAAsB;CAAc;CAAkB;AAAY;;;;AAK3F,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,IAAa,eAAb,MAA0B;;YAIX,OAAO,OAAO;;;;;;CAM3B,MAAa,MAAM,QAAsB;EAGvC,MAAM,mBAAkB,MADM,qBAAqB,QAAQ,WAAW,EAC/B,EAAE;EAQzC,MAAM,YAAY;GAAC,GADF,UAAU,KAAK,SAAS,SAAS,IAAI,CACzB;GAAG,SAAS,mBAAmB;GAAG,QAAQ;EAAC;EACxE,MAAM,kBAAkB,iBAAiB,WAAW,QAAQ,WAAW,CAAC;EAExE,MAAM,QAAQ,CAAC,GAAG,WAAW,GAAG,eAAe,CAAC,CAAC,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC;EAGnF,MAAM,UAAU;GACd,GAAG;GACH,GAAI,iBAAiB,WAAW,CAAC;GACjC,GAAI,QAAQ,WAAW,CAAC;EAC1B;EAEA,MAAM,UAAU,SAAS,MAAM,OAAO;GACpC,eAAe;GACf;GACA,YAAY;GACZ,YAAY;GACZ,UAAU;GACV,gBAAgB;GAChB,kBAAkB;IAChB,oBAAoB;IACpB,cAAc;GAChB;GAEA,OAAO;EACT,CAAC;EAED,QAAQ,GAAG,QAAQ,aAAa,KAAK,aAAa,OAAO,QAAQ,CAAC;EAClE,QAAQ,GAAG,WAAW,aAAa,KAAK,aAAa,UAAU,QAAQ,CAAC;EACxE,QAAQ,GAAG,WAAW,aAAa,KAAK,aAAa,UAAU,QAAQ,CAAC;EACxE,QAAQ,GAAG,WAAW,aAAa,KAAK,aAAa,UAAU,QAAQ,CAAC;EACxE,QAAQ,GAAG,cAAc,aAAa,KAAK,aAAa,aAAa,QAAQ,CAAC;EAM9E,QAAQ,GAAG,UAAU,YAAY;GAC/B,MAAM,QAAQ,MAAM;EACtB,CAAC;CACH;;;;;CAMA,AAAQ,aAAa,OAAyB,UAAkB,OAAe;EAC7E,OAAO,QAAQ,gBAAgB,KAAK,GAAG,GAAG,SAAS,KAAK,UAAU,QAAQ,GAAG,KAAK;CACpF;;;;CAKA,AAAO,aAAa,UAA8B;EAChD,OAAO,KAAK,GAAG,UAAU,QAAQ;CACnC;;;;CAKA,AAAO,aAAa,UAA8B;EAChD,OAAO,KAAK,GAAG,UAAU,QAAQ;CACnC;;;;CAKA,AAAO,UAAU,UAA2B;EAC1C,OAAO,KAAK,GAAG,OAAO,QAAQ;CAChC;;;;CAKA,AAAO,YAAY,UAA6B;EAC9C,OAAO,KAAK,GAAG,SAAS,QAAQ;CAClC;;;;CAKA,AAAO,eAAe,UAA8B;EAClD,OAAO,KAAK,GAAG,UAAU,QAAQ;CACnC;;;;CAKA,AAAO,kBAAkB,UAAiC;EACxD,OAAO,KAAK,GAAG,aAAa,QAAQ;CACtC;;;;CAKA,AAAO,GAAG,OAAyB,UAA+B;EAChE,OAAO,OAAO,UAAU,gBAAgB,KAAK,GAAG,GAAG,SAAS,QAAQ;CACtE;AACF"}
|
|
@@ -59,5 +59,5 @@ interface FileHealthCheckerContract {
|
|
|
59
59
|
stats(): Promise<HealthCheckerFilesStats>;
|
|
60
60
|
}
|
|
61
61
|
//#endregion
|
|
62
|
-
export { FileHealthCheckerContract
|
|
62
|
+
export { FileHealthCheckerContract };
|
|
63
63
|
//# sourceMappingURL=file-health-checker.contract.d.mts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { devLogHMR } from "./dev-logger.mjs";
|
|
2
1
|
import { connectorsManager } from "../connectors/connectors-manager.mjs";
|
|
2
|
+
import { devLogHMR } from "./dev-logger.mjs";
|
|
3
3
|
import { configManager } from "../config/config-manager.mjs";
|
|
4
4
|
import { loadEnv } from "@mongez/dotenv";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { register } from "node:module";
|
|
2
|
-
import { putFileAsync } from "@warlock.js/fs";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { putFileAsync } from "@warlock.js/fs";
|
|
4
4
|
import { build } from "esbuild";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { MessageChannel } from "node:worker_threads";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-json-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/package-json-manager.ts"],"sourcesContent":["import { getJsonFileAsync } from \"@warlock.js/fs\";\
|
|
1
|
+
{"version":3,"file":"package-json-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/package-json-manager.ts"],"sourcesContent":["import { getJsonFileAsync } from \"@warlock.js/fs\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class PackageJsonManager {\n /**\n * Package.json data\n */\n public packageJson: any = {};\n\n /**\n * Initialize the package.json manager\n */\n public async init() {\n this.packageJson = await getJsonFileAsync(Path.toAbsolute(\"package.json\"));\n }\n\n /**\n * Check if the given path is a package path\n */\n public isPathPackage(path: string) {\n const allPackages = Object.keys(this.packageJson.dependencies || {}).concat(\n Object.keys(this.packageJson.devDependencies || {}),\n );\n\n return allPackages.some((packageName) => path.startsWith(packageName));\n }\n}\n\nexport const packageJsonManager = new PackageJsonManager();\n"],"mappings":";;;;AAGA,IAAa,qBAAb,MAAgC;;qBAIJ,CAAC;;;;;CAK3B,MAAa,OAAO;EAClB,KAAK,cAAc,MAAM,iBAAiB,KAAK,WAAW,cAAc,CAAC;CAC3E;;;;CAKA,AAAO,cAAc,MAAc;EAKjC,OAJoB,OAAO,KAAK,KAAK,YAAY,gBAAgB,CAAC,CAAC,CAAC,CAAC,OACnE,OAAO,KAAK,KAAK,YAAY,mBAAmB,CAAC,CAAC,CAGnC,CAAC,CAAC,MAAM,gBAAgB,KAAK,WAAW,WAAW,CAAC;CACvE;AACF;AAEA,MAAa,qBAAqB,IAAI,mBAAmB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Path } from "
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
2
2
|
import { tsconfigManager } from "./tsconfig-manager.mjs";
|
|
3
|
-
import { directoryExistsAsync, fileExistsAsync } from "@warlock.js/fs";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { directoryExistsAsync, fileExistsAsync } from "@warlock.js/fs";
|
|
5
5
|
import { parse } from "es-module-lexer";
|
|
6
6
|
|
|
7
7
|
//#region ../core/src/dev-server/parse-imports.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-imports.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/parse-imports.ts"],"sourcesContent":["import { fileExistsAsync, directoryExistsAsync } from \"@warlock.js/fs\";\r\nimport { ImportSpecifier, parse } from \"es-module-lexer\";\r\nimport path from \"node:path\";\r\nimport { Path } from \"./path\";\r\nimport { tsconfigManager } from \"./tsconfig-manager\";\r\n\r\n/**\r\n * Detect if a file contains only type definitions (no runtime code)\r\n *\r\n * A file is considered type-only if ALL its exports are:\r\n * - interface declarations\r\n * - type alias declarations\r\n * - export type { ... } statements\r\n * - export type { ... } from \"...\" statements\r\n *\r\n * Files with any of these are NOT type-only:\r\n * - export const/let/var\r\n * - export function\r\n * - export class\r\n * - export default (non-type)\r\n * - export { ... } (without type keyword)\r\n * - export * from (without type keyword)\r\n *\r\n * @param source - The source code to analyze\r\n * @returns true if the file exports only types\r\n */\r\nexport function isTypeOnlyFile(source: string): boolean {\r\n // Remove comments to avoid false positives\r\n const withoutComments = source\r\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // block comments\r\n .replace(/\\/\\/.*$/gm, \"\"); // line comments\r\n\r\n // Remove string literals to avoid false positives\r\n const withoutStrings = withoutComments\r\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\r\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\r\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, \"``\");\r\n\r\n // Patterns for type-only exports (these are safe)\r\n const typeOnlyPatterns = [\r\n /export\\s+type\\s+\\{[^}]*\\}/g, // export type { Foo, Bar }\r\n /export\\s+type\\s+\\{[^}]*\\}\\s+from\\s+['\"]/g, // export type { Foo } from \"...\"\r\n /export\\s+interface\\s+\\w+/g, // export interface Foo\r\n /export\\s+type\\s+\\w+\\s*=/g, // export type Foo =\r\n ];\r\n\r\n // Remove all type-only exports from consideration\r\n let remaining = withoutStrings;\r\n for (const pattern of typeOnlyPatterns) {\r\n remaining = remaining.replace(pattern, \"\");\r\n }\r\n\r\n // Patterns for runtime exports (these make the file NOT type-only)\r\n const runtimeExportPatterns = [\r\n /export\\s+(?:const|let|var)\\s+\\w+/g, // export const/let/var foo\r\n /export\\s+function\\s+\\w+/g, // export function foo\r\n /export\\s+async\\s+function\\s+\\w+/g, // export async function foo\r\n /export\\s+class\\s+\\w+/g, // export class Foo\r\n /export\\s+enum\\s+\\w+/g, // export enum Foo (enums have runtime value)\r\n /export\\s+default\\s+(?!type\\s)/g, // export default (not type)\r\n /export\\s+\\{[^}]*\\}(?!\\s+from)/g, // export { foo } (local re-export without type)\r\n /export\\s+\\{[^}]*\\}\\s+from\\s+['\"][^'\"]+['\"]/g, // export { foo } from (without type)\r\n /export\\s+\\*\\s+from\\s+['\"][^'\"]+['\"]/g, // export * from (re-exports everything)\r\n /export\\s+\\*\\s+as\\s+\\w+/g, // export * as namespace\r\n ];\r\n\r\n for (const pattern of runtimeExportPatterns) {\r\n if (pattern.test(remaining)) {\r\n // Reset regex lastIndex for subsequent tests\r\n pattern.lastIndex = 0;\r\n\r\n // Special case: check if export { } contains only type exports\r\n if (pattern.source.includes(\"export\\\\s+\\\\{\")) {\r\n const matches = remaining.match(/export\\s+\\{([^}]*)\\}(?:\\s+from\\s+['\"][^'\"]+['\"])?/g);\r\n if (matches) {\r\n for (const match of matches) {\r\n // Skip if it's already a type export\r\n if (/^export\\s+type\\s+/.test(match)) continue;\r\n\r\n // Extract the specifiers\r\n const specifiersMatch = match.match(/export\\s+\\{([^}]*)\\}/);\r\n if (specifiersMatch) {\r\n const specifiers = specifiersMatch[1];\r\n const items = specifiers\r\n .split(\",\")\r\n .map((s) => s.trim())\r\n .filter(Boolean);\r\n\r\n // If any specifier is NOT prefixed with \"type \", this is a runtime export\r\n const hasRuntimeSpecifier = items.some((item) => !item.startsWith(\"type \"));\r\n if (hasRuntimeSpecifier) {\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n continue;\r\n }\r\n\r\n return false;\r\n }\r\n }\r\n\r\n // If we get here, no runtime exports were found\r\n // But we should also verify the file has at least some type exports\r\n // (an empty file or file with only imports is not really \"type-only\")\r\n const hasTypeExports = typeOnlyPatterns.some((pattern) => {\r\n pattern.lastIndex = 0;\r\n return pattern.test(withoutStrings);\r\n });\r\n\r\n return hasTypeExports;\r\n}\r\n\r\n/**\r\n * Check if an import statement is a pure `import type` (the keyword sits\r\n * immediately after `import`, covering `import type { X }`, `import type Foo`,\r\n * and `import type * as Ns`).\r\n */\r\nfunction isTypeOnlyImport(line: string): boolean {\r\n const trimmed = line.trim();\r\n\r\n return trimmed.startsWith(\"import type \") || !!trimmed.match(/^import\\s+type\\s+[\\{\\*]/);\r\n}\r\n\r\n/**\r\n * Decide whether an `import ... from \"m\"` statement contributes any runtime\r\n * binding. Returns false for pure `import type` and for destructured imports\r\n * where every specifier is prefixed with the `type` keyword.\r\n */\r\nfunction hasRuntimeImports(line: string): boolean {\r\n const trimmed = line.trim();\r\n\r\n if (isTypeOnlyImport(trimmed)) {\r\n return false;\r\n }\r\n\r\n const specifiersMatch = trimmed.match(/import\\s+(?:type\\s+)?\\{([^}]+)\\}/);\r\n\r\n if (!specifiersMatch) {\r\n return true;\r\n }\r\n\r\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\r\n\r\n return !items.every((item) => /^type\\s+\\w+/.test(item));\r\n}\r\n\r\n/**\r\n * Decide whether an `export ... from \"m\"` statement re-exports only types.\r\n * Covers `export type { X } from`, `export type * from`, and\r\n * `export { type X, type Y } from`.\r\n */\r\nfunction isExportTypeOnlyStatement(line: string): boolean {\r\n const trimmed = line.trim();\r\n\r\n if (/^export\\s+type\\s+/.test(trimmed)) {\r\n return true;\r\n }\r\n\r\n const specifiersMatch = trimmed.match(/export\\s+\\{([^}]+)\\}/);\r\n\r\n if (!specifiersMatch) {\r\n return false;\r\n }\r\n\r\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\r\n\r\n return items.every((item) => /^type\\s+\\w+/.test(item));\r\n}\r\n\r\n/**\r\n * Extract import paths using regex (more reliable for TypeScript).\r\n * Each entry carries an `isTypeOnly` flag: true when every occurrence of the\r\n * path in the source is a type-only statement (no runtime binding).\r\n * A single runtime occurrence makes the path runtime.\r\n */\r\nfunction extractImportPathsWithRegex(\r\n source: string,\r\n): Array<{ path: string; originalLine: string; isTypeOnly: boolean }> {\r\n const imports: Array<{ path: string; originalLine: string; isTypeOnly: boolean }> = [];\r\n const seenPaths = new Map<string, number>();\r\n\r\n /**\r\n * Record an import path with its type-only flag.\r\n * If the same path appears in multiple statements, the edge is type-only\r\n * iff every occurrence is type-only — a single runtime statement makes it runtime.\r\n */\r\n const record = (importPath: string, originalLine: string, isTypeOnly: boolean): void => {\r\n if (!importPath) {\r\n return;\r\n }\r\n\r\n const existingIndex = seenPaths.get(importPath);\r\n\r\n if (existingIndex === undefined) {\r\n seenPaths.set(importPath, imports.length);\r\n imports.push({ path: importPath, originalLine, isTypeOnly });\r\n\r\n return;\r\n }\r\n\r\n if (!isTypeOnly) {\r\n imports[existingIndex].isTypeOnly = false;\r\n }\r\n };\r\n\r\n // Pattern 1: Standard ES module imports (handles multiline)\r\n // Matches: import { ... } from \"path\", import Foo from \"path\", import Foo, { ... } from \"path\"\r\n const importRegex =\r\n /import\\s+(?:type\\s+)?(\\{[\\s\\S]*?\\}|\\*\\s+as\\s+\\w+|\\w+(?:\\s*,\\s*\\{[\\s\\S]*?\\})?)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\r\n\r\n let match;\r\n\r\n while ((match = importRegex.exec(source)) !== null) {\r\n const fullMatch = match[0];\r\n const importPath = match[2];\r\n const isTypeOnly = !hasRuntimeImports(fullMatch);\r\n\r\n record(importPath, fullMatch, isTypeOnly);\r\n }\r\n\r\n // Pattern 1b: Side-effect imports - import \"path\" — always runtime\r\n const sideEffectRegex = /import\\s+['\"]([^'\"]+)['\"]/g;\r\n\r\n while ((match = sideEffectRegex.exec(source)) !== null) {\r\n record(match[1], match[0], false);\r\n }\r\n\r\n // Pattern 2: Dynamic imports - import(\"path\") — always runtime\r\n const dynamicImportPattern = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\r\n\r\n while ((match = dynamicImportPattern.exec(source)) !== null) {\r\n record(match[1], match[0], false);\r\n }\r\n\r\n // Pattern 3: Export from - export ... from \"path\"\r\n const exportFromPattern = /export\\s+(?:\\{[^}]*\\}|\\*|\\w+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\r\n\r\n while ((match = exportFromPattern.exec(source)) !== null) {\r\n const fullMatch = match[0];\r\n\r\n record(match[1], fullMatch, isExportTypeOnlyStatement(fullMatch));\r\n }\r\n\r\n return imports;\r\n}\r\n\r\n/**\r\n * Metadata for a single resolved import edge.\r\n * `isTypeOnly` is true iff every import/export statement that references this\r\n * path is type-only (pure `import type`, `export type`, or destructured lists\r\n * where every specifier carries the `type` keyword). A single runtime\r\n * occurrence flips the flag to false — that's what matters for cycle detection.\r\n */\r\nexport type ResolvedImport = {\r\n absolutePath: string;\r\n isTypeOnly: boolean;\r\n};\r\n\r\n/**\r\n * Parse import/export statements in a TS/JS source file and return the\r\n * resolved dependency edges keyed by the original import path. Includes\r\n * type-only edges (flagged) so downstream consumers can distinguish runtime\r\n * cycles from type-only ones.\r\n *\r\n * @example\r\n * const importMap = await parseImports(source, \"/abs/path/to/file.ts\");\r\n * for (const [importPath, { absolutePath, isTypeOnly }] of importMap) {\r\n * // ...\r\n * }\r\n */\r\nexport async function parseImports(\r\n source: string,\r\n filePath: string,\r\n): Promise<Map<string, ResolvedImport>> {\r\n try {\r\n // Skip .d.ts files - they're type declarations, not runtime code\r\n if (filePath.endsWith(\".d.ts\")) {\r\n return new Map();\r\n }\r\n\r\n // Try es-module-lexer first (faster and more accurate for simple cases)\r\n try {\r\n const [imports] = await parse(source);\r\n\r\n if (imports && imports.length > 0) {\r\n return await resolveImports(imports as ImportSpecifier[], source, filePath);\r\n }\r\n } catch (lexerError) {\r\n // es-module-lexer failed, fall back to regex-based extraction\r\n // This is common with TypeScript files that have complex syntax\r\n }\r\n\r\n // Fallback: Use regex-based extraction (more forgiving with TypeScript)\r\n const regexImports = extractImportPathsWithRegex(source);\r\n const resolvedImports = new Map<string, ResolvedImport>();\r\n\r\n for (const { path: importPath, isTypeOnly } of regexImports) {\r\n if (isNodeBuiltin(importPath)) {\r\n continue;\r\n }\r\n\r\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\r\n continue;\r\n }\r\n\r\n let resolvedPath: string | null = null;\r\n\r\n if (tsconfigManager.isAlias(importPath)) {\r\n resolvedPath = await resolveAliasImport(importPath);\r\n } else if (importPath.startsWith(\".\")) {\r\n resolvedPath = await resolveRelativeImport(importPath, filePath);\r\n }\r\n\r\n if (resolvedPath) {\r\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\r\n }\r\n }\r\n\r\n return resolvedImports;\r\n } catch (error) {\r\n console.error(`Error parsing imports for ${filePath}:`, error);\r\n\r\n return new Map();\r\n }\r\n}\r\n\r\n/**\r\n * Merge a resolved import into the output map, preserving the type-only rule:\r\n * a path is type-only iff every statement that references it is type-only.\r\n */\r\nfunction mergeResolvedImport(\r\n target: Map<string, ResolvedImport>,\r\n importPath: string,\r\n absolutePath: string,\r\n isTypeOnly: boolean,\r\n): void {\r\n const existing = target.get(importPath);\r\n\r\n if (!existing) {\r\n target.set(importPath, { absolutePath, isTypeOnly });\r\n\r\n return;\r\n }\r\n\r\n if (!isTypeOnly) {\r\n existing.isTypeOnly = false;\r\n }\r\n}\r\n\r\n/**\r\n * Resolve imports from `es-module-lexer` output. For each specifier, slice the\r\n * raw statement (`ss..se`) and re-run the type-only heuristic so we can flag\r\n * the edge correctly — the lexer itself does not distinguish `import type`.\r\n */\r\nasync function resolveImports(\r\n imports: ImportSpecifier[],\r\n source: string,\r\n filePath: string,\r\n): Promise<Map<string, ResolvedImport>> {\r\n const resolvedImports = new Map<string, ResolvedImport>();\r\n\r\n for (const imp of imports) {\r\n const importPath = imp.n;\r\n\r\n if (!importPath) {\r\n continue;\r\n }\r\n\r\n if (isNodeBuiltin(importPath)) {\r\n continue;\r\n }\r\n\r\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\r\n continue;\r\n }\r\n\r\n let resolvedPath: string | null = null;\r\n\r\n if (tsconfigManager.isAlias(importPath)) {\r\n resolvedPath = await resolveAliasImport(importPath);\r\n } else if (importPath.startsWith(\".\")) {\r\n resolvedPath = await resolveRelativeImport(importPath, filePath);\r\n }\r\n\r\n if (!resolvedPath) {\r\n continue;\r\n }\r\n\r\n const isTypeOnly = isLexerStatementTypeOnly(imp, source);\r\n\r\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\r\n }\r\n\r\n return resolvedImports;\r\n}\r\n\r\n/**\r\n * Classify a single lexer-reported statement as type-only or runtime.\r\n * Dynamic imports (`import(...)`) are always runtime. For static\r\n * imports/export-from we slice the raw source and apply the same heuristic\r\n * the regex fallback uses.\r\n */\r\nfunction isLexerStatementTypeOnly(imp: ImportSpecifier, source: string): boolean {\r\n // Dynamic imports always execute — they cannot be type-only.\r\n // es-module-lexer reports dynamic with a.n === undefined typically, but\r\n // we guard by checking the statement prefix too.\r\n const statementStart = imp.ss;\r\n const statementEnd = imp.se;\r\n\r\n if (\r\n typeof statementStart !== \"number\" ||\r\n typeof statementEnd !== \"number\" ||\r\n statementEnd <= statementStart\r\n ) {\r\n return false;\r\n }\r\n\r\n const statement = source.slice(statementStart, statementEnd);\r\n const trimmed = statement.trim();\r\n\r\n if (trimmed.startsWith(\"import(\") || /^\\bimport\\s*\\(/.test(trimmed)) {\r\n return false;\r\n }\r\n\r\n if (trimmed.startsWith(\"export\")) {\r\n return isExportTypeOnlyStatement(statement);\r\n }\r\n\r\n return !hasRuntimeImports(statement);\r\n}\r\n\r\n/**\r\n * Resolve alias imports to actual file paths with extensions\r\n * Example: app/users/services/get-users.service -> /absolute/path/to/src/app/users/services/get-users.service.ts\r\n */\r\nasync function resolveAliasImport(importPath: string): Promise<string | null> {\r\n // Use tsconfig manager to resolve the alias to an absolute path\r\n const resolvedBase = tsconfigManager.resolveAliasToAbsolute(importPath);\r\n\r\n if (!resolvedBase) return null;\r\n\r\n // Try to resolve with extensions\r\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\r\n\r\n return resolvedPath;\r\n}\r\n\r\n/**\r\n * Resolve relative imports to actual file paths\r\n * Example: ./../services/get-user.service -> /absolute/path/to/services/get-user.service.ts\r\n */\r\nasync function resolveRelativeImport(\r\n importPath: string,\r\n currentFilePath: string,\r\n): Promise<string | null> {\r\n const dir = path.dirname(currentFilePath);\r\n // Use path.resolve to handle .. and . properly, then normalize to forward slashes\r\n const resolvedBase = Path.normalize(path.resolve(dir, importPath));\r\n\r\n // Try to resolve with extensions\r\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\r\n\r\n return resolvedPath;\r\n}\r\n\r\n/**\r\n * Try to resolve a file path by checking different extensions\r\n * TypeScript/JavaScript files can be imported without extensions\r\n *\r\n * @TODO: For better performance, we need to check the files in files orchestrator\r\n * instead of using the file system as we will be fetching all project files anyway.\r\n */\r\n// Cache for file existence checks to avoid redundant filesystem calls\r\nconst fileExistsCache = new Map<string, boolean>();\r\n\r\n/**\r\n * Clear the file exists cache\r\n * Should be called when new files are created to ensure fresh lookups\r\n */\r\nexport function clearFileExistsCache(): void {\r\n fileExistsCache.clear();\r\n}\r\n\r\nasync function cachedFileExists(filePath: string): Promise<boolean> {\r\n if (fileExistsCache.has(filePath)) {\r\n return fileExistsCache.get(filePath)!;\r\n }\r\n const exists = (await fileExistsAsync(filePath)) as boolean;\r\n fileExistsCache.set(filePath, exists);\r\n return exists;\r\n}\r\n\r\nasync function tryResolveWithExtensions(basePath: string): Promise<string | null> {\r\n // Normalize the base path first (handle Windows paths)\r\n const normalizedBase = Path.normalize(basePath);\r\n\r\n // List of extensions to try, in order of preference\r\n const extensions = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"];\r\n const validExtensions = new Set(extensions);\r\n\r\n // If the path already has a VALID code file extension, check if it exists\r\n const ext = path.extname(normalizedBase);\r\n if (ext && validExtensions.has(ext)) {\r\n if (await cachedFileExists(normalizedBase)) {\r\n return normalizedBase;\r\n }\r\n // If explicit extension doesn't exist, return null\r\n return null;\r\n }\r\n\r\n // Try all extensions in parallel for better performance\r\n const pathsToCheck = extensions.map((extension) => normalizedBase + extension);\r\n const results = await Promise.all(\r\n pathsToCheck.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\r\n );\r\n\r\n // Return the first one that exists (in order of preference)\r\n for (const result of results) {\r\n if (result.exists) {\r\n return result.path;\r\n }\r\n }\r\n\r\n // Try index files in directory\r\n if (await directoryExistsAsync(normalizedBase)) {\r\n const indexPaths = extensions.map((extension) =>\r\n Path.join(normalizedBase, `index${extension}`),\r\n );\r\n const indexResults = await Promise.all(\r\n indexPaths.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\r\n );\r\n\r\n for (const result of indexResults) {\r\n if (result.exists) {\r\n return result.path;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Check if import is a Node.js built-in module\r\n */\r\nfunction isNodeBuiltin(importPath: string): boolean {\r\n const builtins = [\r\n \"fs\",\r\n \"path\",\r\n \"http\",\r\n \"https\",\r\n \"crypto\",\r\n \"stream\",\r\n \"util\",\r\n \"events\",\r\n \"buffer\",\r\n \"child_process\",\r\n \"os\",\r\n \"url\",\r\n \"querystring\",\r\n \"zlib\",\r\n \"net\",\r\n \"tls\",\r\n \"dns\",\r\n \"dgram\",\r\n \"cluster\",\r\n \"worker_threads\",\r\n \"perf_hooks\",\r\n \"async_hooks\",\r\n \"timers\",\r\n \"readline\",\r\n \"repl\",\r\n \"vm\",\r\n \"assert\",\r\n \"console\",\r\n \"process\",\r\n \"v8\",\r\n ];\r\n\r\n // Check for node: prefix or direct builtin name\r\n if (importPath.startsWith(\"node:\")) return true;\r\n\r\n const moduleName = importPath.split(\"/\")[0];\r\n return builtins.includes(moduleName);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parse-imports.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/parse-imports.ts"],"sourcesContent":["import { fileExistsAsync, directoryExistsAsync } from \"@warlock.js/fs\";\nimport { ImportSpecifier, parse } from \"es-module-lexer\";\nimport path from \"node:path\";\nimport { Path } from \"../utils/normalized-path\";\nimport { tsconfigManager } from \"./tsconfig-manager\";\n\n/**\n * Detect if a file contains only type definitions (no runtime code)\n *\n * A file is considered type-only if ALL its exports are:\n * - interface declarations\n * - type alias declarations\n * - export type { ... } statements\n * - export type { ... } from \"...\" statements\n *\n * Files with any of these are NOT type-only:\n * - export const/let/var\n * - export function\n * - export class\n * - export default (non-type)\n * - export { ... } (without type keyword)\n * - export * from (without type keyword)\n *\n * @param source - The source code to analyze\n * @returns true if the file exports only types\n */\nexport function isTypeOnlyFile(source: string): boolean {\n // Remove comments to avoid false positives\n const withoutComments = source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // block comments\n .replace(/\\/\\/.*$/gm, \"\"); // line comments\n\n // Remove string literals to avoid false positives\n const withoutStrings = withoutComments\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, \"``\");\n\n // Patterns for type-only exports (these are safe)\n const typeOnlyPatterns = [\n /export\\s+type\\s+\\{[^}]*\\}/g, // export type { Foo, Bar }\n /export\\s+type\\s+\\{[^}]*\\}\\s+from\\s+['\"]/g, // export type { Foo } from \"...\"\n /export\\s+interface\\s+\\w+/g, // export interface Foo\n /export\\s+type\\s+\\w+\\s*=/g, // export type Foo =\n ];\n\n // Remove all type-only exports from consideration\n let remaining = withoutStrings;\n for (const pattern of typeOnlyPatterns) {\n remaining = remaining.replace(pattern, \"\");\n }\n\n // Patterns for runtime exports (these make the file NOT type-only)\n const runtimeExportPatterns = [\n /export\\s+(?:const|let|var)\\s+\\w+/g, // export const/let/var foo\n /export\\s+function\\s+\\w+/g, // export function foo\n /export\\s+async\\s+function\\s+\\w+/g, // export async function foo\n /export\\s+class\\s+\\w+/g, // export class Foo\n /export\\s+enum\\s+\\w+/g, // export enum Foo (enums have runtime value)\n /export\\s+default\\s+(?!type\\s)/g, // export default (not type)\n /export\\s+\\{[^}]*\\}(?!\\s+from)/g, // export { foo } (local re-export without type)\n /export\\s+\\{[^}]*\\}\\s+from\\s+['\"][^'\"]+['\"]/g, // export { foo } from (without type)\n /export\\s+\\*\\s+from\\s+['\"][^'\"]+['\"]/g, // export * from (re-exports everything)\n /export\\s+\\*\\s+as\\s+\\w+/g, // export * as namespace\n ];\n\n for (const pattern of runtimeExportPatterns) {\n if (pattern.test(remaining)) {\n // Reset regex lastIndex for subsequent tests\n pattern.lastIndex = 0;\n\n // Special case: check if export { } contains only type exports\n if (pattern.source.includes(\"export\\\\s+\\\\{\")) {\n const matches = remaining.match(/export\\s+\\{([^}]*)\\}(?:\\s+from\\s+['\"][^'\"]+['\"])?/g);\n if (matches) {\n for (const match of matches) {\n // Skip if it's already a type export\n if (/^export\\s+type\\s+/.test(match)) continue;\n\n // Extract the specifiers\n const specifiersMatch = match.match(/export\\s+\\{([^}]*)\\}/);\n if (specifiersMatch) {\n const specifiers = specifiersMatch[1];\n const items = specifiers\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n\n // If any specifier is NOT prefixed with \"type \", this is a runtime export\n const hasRuntimeSpecifier = items.some((item) => !item.startsWith(\"type \"));\n if (hasRuntimeSpecifier) {\n return false;\n }\n }\n }\n }\n continue;\n }\n\n return false;\n }\n }\n\n // If we get here, no runtime exports were found\n // But we should also verify the file has at least some type exports\n // (an empty file or file with only imports is not really \"type-only\")\n const hasTypeExports = typeOnlyPatterns.some((pattern) => {\n pattern.lastIndex = 0;\n return pattern.test(withoutStrings);\n });\n\n return hasTypeExports;\n}\n\n/**\n * Check if an import statement is a pure `import type` (the keyword sits\n * immediately after `import`, covering `import type { X }`, `import type Foo`,\n * and `import type * as Ns`).\n */\nfunction isTypeOnlyImport(line: string): boolean {\n const trimmed = line.trim();\n\n return trimmed.startsWith(\"import type \") || !!trimmed.match(/^import\\s+type\\s+[\\{\\*]/);\n}\n\n/**\n * Decide whether an `import ... from \"m\"` statement contributes any runtime\n * binding. Returns false for pure `import type` and for destructured imports\n * where every specifier is prefixed with the `type` keyword.\n */\nfunction hasRuntimeImports(line: string): boolean {\n const trimmed = line.trim();\n\n if (isTypeOnlyImport(trimmed)) {\n return false;\n }\n\n const specifiersMatch = trimmed.match(/import\\s+(?:type\\s+)?\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return true;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return !items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Decide whether an `export ... from \"m\"` statement re-exports only types.\n * Covers `export type { X } from`, `export type * from`, and\n * `export { type X, type Y } from`.\n */\nfunction isExportTypeOnlyStatement(line: string): boolean {\n const trimmed = line.trim();\n\n if (/^export\\s+type\\s+/.test(trimmed)) {\n return true;\n }\n\n const specifiersMatch = trimmed.match(/export\\s+\\{([^}]+)\\}/);\n\n if (!specifiersMatch) {\n return false;\n }\n\n const items = specifiersMatch[1].split(\",\").map((s) => s.trim()).filter(Boolean);\n\n return items.every((item) => /^type\\s+\\w+/.test(item));\n}\n\n/**\n * Extract import paths using regex (more reliable for TypeScript).\n * Each entry carries an `isTypeOnly` flag: true when every occurrence of the\n * path in the source is a type-only statement (no runtime binding).\n * A single runtime occurrence makes the path runtime.\n */\nfunction extractImportPathsWithRegex(\n source: string,\n): Array<{ path: string; originalLine: string; isTypeOnly: boolean }> {\n const imports: Array<{ path: string; originalLine: string; isTypeOnly: boolean }> = [];\n const seenPaths = new Map<string, number>();\n\n /**\n * Record an import path with its type-only flag.\n * If the same path appears in multiple statements, the edge is type-only\n * iff every occurrence is type-only — a single runtime statement makes it runtime.\n */\n const record = (importPath: string, originalLine: string, isTypeOnly: boolean): void => {\n if (!importPath) {\n return;\n }\n\n const existingIndex = seenPaths.get(importPath);\n\n if (existingIndex === undefined) {\n seenPaths.set(importPath, imports.length);\n imports.push({ path: importPath, originalLine, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n imports[existingIndex].isTypeOnly = false;\n }\n };\n\n // Pattern 1: Standard ES module imports (handles multiline)\n // Matches: import { ... } from \"path\", import Foo from \"path\", import Foo, { ... } from \"path\"\n const importRegex =\n /import\\s+(?:type\\s+)?(\\{[\\s\\S]*?\\}|\\*\\s+as\\s+\\w+|\\w+(?:\\s*,\\s*\\{[\\s\\S]*?\\})?)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n let match;\n\n while ((match = importRegex.exec(source)) !== null) {\n const fullMatch = match[0];\n const importPath = match[2];\n const isTypeOnly = !hasRuntimeImports(fullMatch);\n\n record(importPath, fullMatch, isTypeOnly);\n }\n\n // Pattern 1b: Side-effect imports - import \"path\" — always runtime\n const sideEffectRegex = /import\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = sideEffectRegex.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 2: Dynamic imports - import(\"path\") — always runtime\n const dynamicImportPattern = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\n while ((match = dynamicImportPattern.exec(source)) !== null) {\n record(match[1], match[0], false);\n }\n\n // Pattern 3: Export from - export ... from \"path\"\n const exportFromPattern = /export\\s+(?:\\{[^}]*\\}|\\*|\\w+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\n\n while ((match = exportFromPattern.exec(source)) !== null) {\n const fullMatch = match[0];\n\n record(match[1], fullMatch, isExportTypeOnlyStatement(fullMatch));\n }\n\n return imports;\n}\n\n/**\n * Metadata for a single resolved import edge.\n * `isTypeOnly` is true iff every import/export statement that references this\n * path is type-only (pure `import type`, `export type`, or destructured lists\n * where every specifier carries the `type` keyword). A single runtime\n * occurrence flips the flag to false — that's what matters for cycle detection.\n */\nexport type ResolvedImport = {\n absolutePath: string;\n isTypeOnly: boolean;\n};\n\n/**\n * Parse import/export statements in a TS/JS source file and return the\n * resolved dependency edges keyed by the original import path. Includes\n * type-only edges (flagged) so downstream consumers can distinguish runtime\n * cycles from type-only ones.\n *\n * @example\n * const importMap = await parseImports(source, \"/abs/path/to/file.ts\");\n * for (const [importPath, { absolutePath, isTypeOnly }] of importMap) {\n * // ...\n * }\n */\nexport async function parseImports(\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n try {\n // Skip .d.ts files - they're type declarations, not runtime code\n if (filePath.endsWith(\".d.ts\")) {\n return new Map();\n }\n\n // Try es-module-lexer first (faster and more accurate for simple cases)\n try {\n const [imports] = await parse(source);\n\n if (imports && imports.length > 0) {\n return await resolveImports(imports as ImportSpecifier[], source, filePath);\n }\n } catch (lexerError) {\n // es-module-lexer failed, fall back to regex-based extraction\n // This is common with TypeScript files that have complex syntax\n }\n\n // Fallback: Use regex-based extraction (more forgiving with TypeScript)\n const regexImports = extractImportPathsWithRegex(source);\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const { path: importPath, isTypeOnly } of regexImports) {\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (resolvedPath) {\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n }\n\n return resolvedImports;\n } catch (error) {\n console.error(`Error parsing imports for ${filePath}:`, error);\n\n return new Map();\n }\n}\n\n/**\n * Merge a resolved import into the output map, preserving the type-only rule:\n * a path is type-only iff every statement that references it is type-only.\n */\nfunction mergeResolvedImport(\n target: Map<string, ResolvedImport>,\n importPath: string,\n absolutePath: string,\n isTypeOnly: boolean,\n): void {\n const existing = target.get(importPath);\n\n if (!existing) {\n target.set(importPath, { absolutePath, isTypeOnly });\n\n return;\n }\n\n if (!isTypeOnly) {\n existing.isTypeOnly = false;\n }\n}\n\n/**\n * Resolve imports from `es-module-lexer` output. For each specifier, slice the\n * raw statement (`ss..se`) and re-run the type-only heuristic so we can flag\n * the edge correctly — the lexer itself does not distinguish `import type`.\n */\nasync function resolveImports(\n imports: ImportSpecifier[],\n source: string,\n filePath: string,\n): Promise<Map<string, ResolvedImport>> {\n const resolvedImports = new Map<string, ResolvedImport>();\n\n for (const imp of imports) {\n const importPath = imp.n;\n\n if (!importPath) {\n continue;\n }\n\n if (isNodeBuiltin(importPath)) {\n continue;\n }\n\n if (!importPath.startsWith(\".\") && !tsconfigManager.isAlias(importPath)) {\n continue;\n }\n\n let resolvedPath: string | null = null;\n\n if (tsconfigManager.isAlias(importPath)) {\n resolvedPath = await resolveAliasImport(importPath);\n } else if (importPath.startsWith(\".\")) {\n resolvedPath = await resolveRelativeImport(importPath, filePath);\n }\n\n if (!resolvedPath) {\n continue;\n }\n\n const isTypeOnly = isLexerStatementTypeOnly(imp, source);\n\n mergeResolvedImport(resolvedImports, importPath, resolvedPath, isTypeOnly);\n }\n\n return resolvedImports;\n}\n\n/**\n * Classify a single lexer-reported statement as type-only or runtime.\n * Dynamic imports (`import(...)`) are always runtime. For static\n * imports/export-from we slice the raw source and apply the same heuristic\n * the regex fallback uses.\n */\nfunction isLexerStatementTypeOnly(imp: ImportSpecifier, source: string): boolean {\n // Dynamic imports always execute — they cannot be type-only.\n // es-module-lexer reports dynamic with a.n === undefined typically, but\n // we guard by checking the statement prefix too.\n const statementStart = imp.ss;\n const statementEnd = imp.se;\n\n if (\n typeof statementStart !== \"number\" ||\n typeof statementEnd !== \"number\" ||\n statementEnd <= statementStart\n ) {\n return false;\n }\n\n const statement = source.slice(statementStart, statementEnd);\n const trimmed = statement.trim();\n\n if (trimmed.startsWith(\"import(\") || /^\\bimport\\s*\\(/.test(trimmed)) {\n return false;\n }\n\n if (trimmed.startsWith(\"export\")) {\n return isExportTypeOnlyStatement(statement);\n }\n\n return !hasRuntimeImports(statement);\n}\n\n/**\n * Resolve alias imports to actual file paths with extensions\n * Example: app/users/services/get-users.service -> /absolute/path/to/src/app/users/services/get-users.service.ts\n */\nasync function resolveAliasImport(importPath: string): Promise<string | null> {\n // Use tsconfig manager to resolve the alias to an absolute path\n const resolvedBase = tsconfigManager.resolveAliasToAbsolute(importPath);\n\n if (!resolvedBase) return null;\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Resolve relative imports to actual file paths\n * Example: ./../services/get-user.service -> /absolute/path/to/services/get-user.service.ts\n */\nasync function resolveRelativeImport(\n importPath: string,\n currentFilePath: string,\n): Promise<string | null> {\n const dir = path.dirname(currentFilePath);\n // Use path.resolve to handle .. and . properly, then normalize to forward slashes\n const resolvedBase = Path.normalize(path.resolve(dir, importPath));\n\n // Try to resolve with extensions\n const resolvedPath = await tryResolveWithExtensions(resolvedBase);\n\n return resolvedPath;\n}\n\n/**\n * Try to resolve a file path by checking different extensions\n * TypeScript/JavaScript files can be imported without extensions\n *\n * @TODO: For better performance, we need to check the files in files orchestrator\n * instead of using the file system as we will be fetching all project files anyway.\n */\n// Cache for file existence checks to avoid redundant filesystem calls\nconst fileExistsCache = new Map<string, boolean>();\n\n/**\n * Clear the file exists cache\n * Should be called when new files are created to ensure fresh lookups\n */\nexport function clearFileExistsCache(): void {\n fileExistsCache.clear();\n}\n\nasync function cachedFileExists(filePath: string): Promise<boolean> {\n if (fileExistsCache.has(filePath)) {\n return fileExistsCache.get(filePath)!;\n }\n const exists = (await fileExistsAsync(filePath)) as boolean;\n fileExistsCache.set(filePath, exists);\n return exists;\n}\n\nasync function tryResolveWithExtensions(basePath: string): Promise<string | null> {\n // Normalize the base path first (handle Windows paths)\n const normalizedBase = Path.normalize(basePath);\n\n // List of extensions to try, in order of preference\n const extensions = [\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"];\n const validExtensions = new Set(extensions);\n\n // If the path already has a VALID code file extension, check if it exists\n const ext = path.extname(normalizedBase);\n if (ext && validExtensions.has(ext)) {\n if (await cachedFileExists(normalizedBase)) {\n return normalizedBase;\n }\n // If explicit extension doesn't exist, return null\n return null;\n }\n\n // Try all extensions in parallel for better performance\n const pathsToCheck = extensions.map((extension) => normalizedBase + extension);\n const results = await Promise.all(\n pathsToCheck.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n // Return the first one that exists (in order of preference)\n for (const result of results) {\n if (result.exists) {\n return result.path;\n }\n }\n\n // Try index files in directory\n if (await directoryExistsAsync(normalizedBase)) {\n const indexPaths = extensions.map((extension) =>\n Path.join(normalizedBase, `index${extension}`),\n );\n const indexResults = await Promise.all(\n indexPaths.map(async (p) => ({ path: p, exists: await cachedFileExists(p) })),\n );\n\n for (const result of indexResults) {\n if (result.exists) {\n return result.path;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Check if import is a Node.js built-in module\n */\nfunction isNodeBuiltin(importPath: string): boolean {\n const builtins = [\n \"fs\",\n \"path\",\n \"http\",\n \"https\",\n \"crypto\",\n \"stream\",\n \"util\",\n \"events\",\n \"buffer\",\n \"child_process\",\n \"os\",\n \"url\",\n \"querystring\",\n \"zlib\",\n \"net\",\n \"tls\",\n \"dns\",\n \"dgram\",\n \"cluster\",\n \"worker_threads\",\n \"perf_hooks\",\n \"async_hooks\",\n \"timers\",\n \"readline\",\n \"repl\",\n \"vm\",\n \"assert\",\n \"console\",\n \"process\",\n \"v8\",\n ];\n\n // Check for node: prefix or direct builtin name\n if (importPath.startsWith(\"node:\")) return true;\n\n const moduleName = importPath.split(\"/\")[0];\n return builtins.includes(moduleName);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"
|
|
1
|
+
{"version":3,"file":"tsconfig-manager.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/tsconfig-manager.ts"],"sourcesContent":["import path from \"node:path\";\nimport ts from \"typescript\";\nimport { Path } from \"../utils/normalized-path\";\n\nexport class TSConfigManager {\n /**\n * Aliases list (from tsconfig paths)\n */\n public aliases: Record<string, string[]> = {};\n\n /**\n * Base URL for resolving paths\n */\n public baseUrl: string = \".\";\n\n /**\n * TSConfig\n */\n public tsconfig: any;\n\n public init() {\n if (this.tsconfig) return;\n\n // use typescript to load the tsconfig.json file\n const output = ts.readConfigFile(Path.toAbsolute(\"tsconfig.json\"), ts.sys.readFile);\n\n this.tsconfig = output.config!;\n\n this.aliases = output.config?.compilerOptions?.paths || {};\n\n this.baseUrl = output.config?.compilerOptions?.baseUrl || \".\";\n }\n\n /**\n * Check if the given path is an alias\n * This checks if it's a REAL path alias (not an external package alias)\n *\n * Real aliases map to local paths (e.g., app/* -> src/app/*, src/* -> src/*)\n * External package aliases map to themselves with @ prefix (e.g., @warlock.js/core -> @warlock.js/core)\n */\n public isAlias(path: string) {\n if (!this.tsconfig) {\n this.init();\n }\n\n return Object.keys(this.aliases).some((alias) => {\n // Remove /* from alias pattern for matching\n const aliasPattern = alias.replace(\"/*\", \"\");\n\n if (!path.startsWith(aliasPattern)) {\n return false;\n }\n\n // Check if this is a real alias or just an external package mapping\n const aliasTargets = this.aliases[alias];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return false;\n }\n\n // If the alias starts with @, it's likely an external package alias\n // Example: \"@warlock.js/core\" -> \"@warlock.js/core\" (external package)\n if (aliasPattern.startsWith(\"@\")) {\n return false;\n }\n\n // Otherwise, it's a real path alias (including self-referencing ones like src/* -> src/*)\n // Example: \"app/*\" -> \"src/app/*\" (real alias)\n // Example: \"src/*\" -> \"src/*\" (self-referencing alias, still valid)\n return true;\n });\n }\n\n /**\n * Get the alias key that matches the given import path\n */\n public getMatchingAlias(path: string): string | null {\n const aliasKey = Object.keys(this.aliases).find((alias) => {\n const aliasPattern = alias.replace(\"/*\", \"\");\n return path.startsWith(aliasPattern);\n });\n\n return aliasKey || null;\n }\n\n /**\n * Resolve an alias import path to a relative path based on tsconfig paths\n * Example: \"app/users/services/get-users.service\" -> \"src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias (e.g., \"app/users/services/get-users.service\")\n * @returns The resolved relative path or null if alias not found\n */\n public resolveAliasPath(checkingPath: string): string | null {\n // Find matching alias from tsconfig paths\n const aliasKey = this.getMatchingAlias(checkingPath);\n\n if (!aliasKey) return null;\n\n const aliasTargets = this.aliases[aliasKey];\n if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) {\n return null;\n }\n\n // Get the first target path (usually there's only one)\n const targetPattern = aliasTargets[0];\n\n // Replace alias pattern with target pattern\n const aliasPattern = aliasKey.replace(\"/*\", \"\");\n const targetBase = targetPattern.replace(\"/*\", \"\");\n // Remove any leading slash so path.join does not drop the base\n const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\\\]/, \"\");\n\n // Join the target base with the relative part\n const resolvedPath = path.join(targetBase, relativePart);\n\n return Path.normalize(resolvedPath);\n }\n\n /**\n * Resolve an alias import path to an absolute path\n * Example: \"app/users/services/get-users.service\" -> \"/absolute/path/to/src/app/users/services/get-users.service\"\n *\n * @param path - The import path with alias\n * @returns The resolved absolute path or null if alias not found\n */\n public resolveAliasToAbsolute(path: string): string | null {\n const relativePath = this.resolveAliasPath(path);\n\n if (!relativePath) return null;\n\n return Path.normalize(Path.toAbsolute(relativePath));\n }\n}\n\nexport const tsconfigManager = new TSConfigManager();\n"],"mappings":";;;;;AAIA,IAAa,kBAAb,MAA6B;;iBAIgB,CAAC;iBAKnB;;CAOzB,AAAO,OAAO;EACZ,IAAI,KAAK,UAAU;EAGnB,MAAM,SAAS,GAAG,eAAe,KAAK,WAAW,eAAe,GAAG,GAAG,IAAI,QAAQ;EAElF,KAAK,WAAW,OAAO;EAEvB,KAAK,UAAU,OAAO,QAAQ,iBAAiB,SAAS,CAAC;EAEzD,KAAK,UAAU,OAAO,QAAQ,iBAAiB,WAAW;CAC5D;;;;;;;;CASA,AAAO,QAAQ,MAAc;EAC3B,IAAI,CAAC,KAAK,UACR,KAAK,KAAK;EAGZ,OAAO,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GAE/C,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAE3C,IAAI,CAAC,KAAK,WAAW,YAAY,GAC/B,OAAO;GAIT,MAAM,eAAe,KAAK,QAAQ;GAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;GAKT,IAAI,aAAa,WAAW,GAAG,GAC7B,OAAO;GAMT,OAAO;EACT,CAAC;CACH;;;;CAKA,AAAO,iBAAiB,MAA6B;EAMnD,OALiB,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,UAAU;GACzD,MAAM,eAAe,MAAM,QAAQ,MAAM,EAAE;GAC3C,OAAO,KAAK,WAAW,YAAY;EACrC,CAEc,KAAK;CACrB;;;;;;;;CASA,AAAO,iBAAiB,cAAqC;EAE3D,MAAM,WAAW,KAAK,iBAAiB,YAAY;EAEnD,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,eAAe,KAAK,QAAQ;EAClC,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,WAAW,GAC1D,OAAO;EAIT,MAAM,gBAAgB,aAAa;EAGnC,MAAM,eAAe,SAAS,QAAQ,MAAM,EAAE;EAC9C,MAAM,aAAa,cAAc,QAAQ,MAAM,EAAE;EAEjD,MAAM,eAAe,aAAa,UAAU,aAAa,MAAM,CAAC,CAAC,QAAQ,UAAU,EAAE;EAGrF,MAAM,eAAe,KAAK,KAAK,YAAY,YAAY;EAEvD,OAAO,KAAK,UAAU,YAAY;CACpC;;;;;;;;CASA,AAAO,uBAAuB,MAA6B;EACzD,MAAM,eAAe,KAAK,iBAAiB,IAAI;EAE/C,IAAI,CAAC,cAAc,OAAO;EAE1B,OAAO,KAAK,UAAU,KAAK,WAAW,YAAY,CAAC;CACrD;AACF;AAEA,MAAa,kBAAkB,IAAI,gBAAgB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Path } from "../utils/normalized-path.mjs";
|
|
1
2
|
import { warlockPath } from "../utils/paths.mjs";
|
|
2
3
|
import "../utils/index.mjs";
|
|
3
|
-
import { Path } from "./path.mjs";
|
|
4
4
|
import { devLogError, devLogInfo, devLogSuccess, devServeLog } from "./dev-logger.mjs";
|
|
5
5
|
import { filesOrchestrator } from "./files-orchestrator.mjs";
|
|
6
6
|
import { env } from "@mongez/dotenv";
|