@warlock.js/core 5.12.0 → 5.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 +86 -54
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs +2 -0
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/database/utils.d.mts +5 -1
- package/esm/database/utils.d.mts.map +1 -1
- package/esm/database/utils.mjs +7 -3
- package/esm/database/utils.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs +23 -5
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +6 -3
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/translation-type-generator.mjs +28 -0
- package/esm/dev-server/translation-type-generator.mjs.map +1 -0
- package/esm/dev-server/tsconfig-manager.mjs +1 -0
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +41 -5
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/encryption/index.mjs +1 -1
- package/esm/errors/esbuild-binary-missing-error.mjs +20 -0
- package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -0
- package/esm/generations/features/auth-google.feature.mjs +18 -0
- package/esm/generations/features/auth-google.feature.mjs.map +1 -0
- package/esm/generations/features/auth-passkeys.feature.mjs +19 -0
- package/esm/generations/features/auth-passkeys.feature.mjs.map +1 -0
- package/esm/generations/features/bull-board.feature.mjs +65 -0
- package/esm/generations/features/bull-board.feature.mjs.map +1 -0
- package/esm/generations/features/index.mjs +8 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/queue.feature.mjs +70 -0
- package/esm/generations/features/queue.feature.mjs.map +1 -0
- package/esm/generations/features/shared/insert-connector-entry.mjs +68 -0
- package/esm/generations/features/shared/insert-connector-entry.mjs.map +1 -0
- package/esm/generations/features/shared/insert-queue-dashboard-block.mjs +55 -0
- package/esm/generations/features/shared/insert-queue-dashboard-block.mjs.map +1 -0
- package/esm/generations/features/web.feature.mjs +4 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +4 -4
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/middleware/cache-response-middleware.d.mts +12 -0
- package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs +15 -3
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/index.mjs +1 -1
- package/esm/production/esbuild-preflight.mjs +23 -13
- package/esm/production/esbuild-preflight.mjs.map +1 -1
- package/llms-full.txt +56 -24
- package/llms.txt +2 -2
- package/package.json +11 -12
- package/skills/run-app/SKILL.md +6 -2
- package/skills/use-localization/SKILL.md +24 -21
- package/skills/use-middleware/SKILL.md +24 -0
- package/skills/write-cli-command/SKILL.md +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-event-handler.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-event-handler.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { debounce } from \"@mongez/reinforcements\";\nimport type { DependencyGraph } from \"./dependency-graph\";\nimport { devLogSuccess } from \"./dev-logger\";\nimport type { FileManager } from \"./file-manager\";\nimport type { FileOperations } from \"./file-operations\";\nimport { FILE_PROCESSING_BATCH_SIZE, isTimingsEnabled } from \"./flags\";\nimport type { ManifestManager } from \"./manifest-manager\";\nimport { clearFileExistsCache } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Receives raw watcher events and processes them in a single debounced batch.\n * Order within a batch: adds → changes → deletes, so changes can reference\n * newly-added files and deletes fire last.\n */\nexport class FileEventHandler {\n private pendingChanges = new Set<string>();\n private pendingAdds = new Set<string>();\n private pendingDeletes = new Set<string>();\n\n /** When this batch's first watcher event arrived — the debounce-wait phase start. */\n private batchStartedAt?: number;\n\n /** Slowest watcher-settle duration seen so far this batch, when `devServer.timings` is on. */\n private watcherSettleMs?: number;\n\n private readonly processPendingEvents = debounce(() => this.processBatch(), 50);\n\n constructor(\n private readonly fileOperations: FileOperations,\n private readonly manifest: ManifestManager,\n private readonly dependencyGraph: DependencyGraph,\n private readonly files: Map<string, FileManager>,\n ) {}\n\n public handleFileChange(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingChanges.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n public handleFileAdd(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingAdds.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n public handleFileDelete(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingDeletes.add(Path.toRelative(absolutePath));\n this.processPendingEvents();\n }\n\n /**\n * Mark the debounce-wait phase start on the first event of a batch, and\n * track the slowest watcher-settle duration seen this batch. `settleMs`\n * only arrives when `devServer.timings` is on (see `FilesWatcher`), so\n * this is a no-op past the cheap `performance.now()` call when it's off.\n */\n private recordTimingStart(settleMs?: number): void {\n if (this.batchStartedAt === undefined) {\n this.batchStartedAt = performance.now();\n }\n\n if (settleMs === undefined) return;\n\n this.watcherSettleMs =\n this.watcherSettleMs === undefined ? settleMs : Math.max(this.watcherSettleMs, settleMs);\n }\n\n private async processBatch(): Promise<void> {\n const changes = Array.from(this.pendingChanges);\n const adds = Array.from(this.pendingAdds);\n const deletes = Array.from(this.pendingDeletes);\n\n this.pendingChanges.clear();\n this.pendingAdds.clear();\n this.pendingDeletes.clear();\n\n const debounceWaitMs =\n this.batchStartedAt !== undefined ? performance.now() - this.batchStartedAt : undefined;\n const watcherSettleMs = this.watcherSettleMs;\n this.batchStartedAt = undefined;\n this.watcherSettleMs = undefined;\n\n if (changes.length === 0 && adds.length === 0 && deletes.length === 0) return;\n\n // Both .env files and warlock.config.ts live outside src/ — they should\n // never enter the dep graph, only ride along in the batch event so the\n // dev server can react (config reload / restart warning).\n const externalChanges = changes.filter(isExternalPath);\n const externalAdds = adds.filter(isExternalPath);\n const codeChanges = changes.filter((p) => !isExternalPath(p));\n const codeAdds = adds.filter((p) => !isExternalPath(p));\n\n // Multi-file batches can race the filesystem on Windows.\n if (codeAdds.length + codeChanges.length > 1) {\n await new Promise((resolve) => setTimeout(resolve, 500));\n clearFileExistsCache();\n }\n\n const { added: addedCodePaths, vanished } = await this.processBatchAdds(codeAdds);\n const changedCodePaths = await this.processBatchChanges(codeChanges);\n await this.processBatchDeletes(deletes);\n\n this.fileOperations.updateFileDependents();\n this.fileOperations.syncFilesToManifest();\n await this.manifest.save();\n\n // Emit only the code paths that genuinely changed (hash differs). A no-op\n // save — an editor that fsyncs without writing — reports no change and is\n // dropped here, where we still know the pre-change hash. (Doing this\n // downstream is impossible: the source has already been overwritten, so a\n // content compare always looks unchanged — which is exactly why emptying a\n // file used to silently skip HMR.) External paths (.env / warlock.config.ts)\n // ride along untouched so the dev server can still react to them. Paths\n // that vanished between the add event and their read/stat (a rename or\n // move racing the filesystem) are folded into `deleted` instead of\n // `added` — they were already unwound as deletions in processBatchAdds.\n events.trigger(\"dev-server:batch-complete\", {\n added: [...externalAdds, ...addedCodePaths],\n changed: [...externalChanges, ...changedCodePaths],\n deleted: [...deletes, ...vanished],\n timings: isTimingsEnabled()\n ? { watcherSettleMs: watcherSettleMs ?? 0, debounceWaitMs: debounceWaitMs ?? 0 }\n : undefined,\n });\n }\n\n /**\n * Reprocess each changed file and return only the paths that genuinely\n * changed. `updateFile` returns false when the on-disk hash matches the\n * last-processed hash (e.g. an editor that fsyncs on save without writing),\n * so those no-ops are kept out of the reload batch. Emptying a file changes\n * its hash, so it is correctly reported as changed.\n */\n private async processBatchChanges(relativePaths: string[]): Promise<string[]> {\n const changed: string[] = [];\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async (path) => {\n if (await this.fileOperations.updateFile(path)) {\n changed.push(path);\n }\n });\n return changed;\n }\n\n /**\n * Process pending add events. Returns the paths that genuinely became\n * available (`added`) separately from paths that no longer existed by the\n * time they were read/stat'd (`vanished`) — the latter is a rename or move\n * racing the filesystem, not a failure, so it is unwound as a deletion\n * instead of being logged as an error.\n */\n private async processBatchAdds(\n relativePaths: string[],\n ): Promise<{ added: string[]; vanished: string[] }> {\n const added: string[] = [];\n const vanished: string[] = [];\n\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async (path) => {\n try {\n const fileManager = await this.fileOperations.addFile(path);\n\n if (fileManager.state === \"deleted\") {\n await this.fileOperations.deleteFile(path);\n vanished.push(path);\n return;\n }\n\n added.push(path);\n devLogSuccess(`Added file: ${path}`);\n } catch (error) {\n console.error(`Failed to add file ${path}:`, error);\n }\n });\n\n return { added, vanished };\n }\n\n private async processBatchDeletes(relativePaths: string[]): Promise<void> {\n for (const relativePath of relativePaths) {\n await this.fileOperations.deleteFile(relativePath);\n devLogSuccess(`Deleted file: ${relativePath}`);\n }\n }\n}\n\nfunction isEnvFile(path: string): boolean {\n const basename = path.split(\"/\").pop() || path;\n return basename === \".env\" || basename.startsWith(\".env.\");\n}\n\n/** Paths watched but never added to the dependency graph. */\nfunction isExternalPath(path: string): boolean {\n return isEnvFile(path) || path === \"warlock.config.ts\";\n}\n\nasync function runInBatches<T>(\n items: T[],\n size: number,\n fn: (item: T) => Promise<unknown>,\n): Promise<void> {\n if (items.length === 0) return;\n for (let i = 0; i < items.length; i += size) {\n await Promise.all(items.slice(i, i + size).map(fn));\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAa,mBAAb,MAA8B;CAa5B,YACE,AAAiB,gBACjB,AAAiB,UACjB,AAAiB,iBACjB,AAAiB,OACjB;EAJiB;EACA;EACA;EACA;wCAhBM,IAAI,IAAY;qCACnB,IAAI,IAAY;wCACb,IAAI,IAAY;8BAQD,eAAe,KAAK,aAAa,GAAG,EAAE;CAO3E;CAEH,AAAO,iBAAiB,cAAsB,UAAyB;EACrE,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,eAAe,IAAI,KAAK,WAAW,YAAY,CAAC;EACrD,KAAK,qBAAqB;CAC5B;CAEA,AAAO,cAAc,cAAsB,UAAyB;EAClE,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,YAAY,IAAI,KAAK,WAAW,YAAY,CAAC;EAClD,KAAK,qBAAqB;CAC5B;CAEA,AAAO,iBAAiB,cAAsB,UAAyB;EACrE,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,eAAe,IAAI,KAAK,WAAW,YAAY,CAAC;EACrD,KAAK,qBAAqB;CAC5B;;;;;;;CAQA,AAAQ,kBAAkB,UAAyB;EACjD,IAAI,KAAK,mBAAmB,QAC1B,KAAK,iBAAiB,YAAY,IAAI;EAGxC,IAAI,aAAa,QAAW;EAE5B,KAAK,kBACH,KAAK,oBAAoB,SAAY,WAAW,KAAK,IAAI,KAAK,iBAAiB,QAAQ;CAC3F;CAEA,MAAc,eAA8B;EAC1C,MAAM,UAAU,MAAM,KAAK,KAAK,cAAc;EAC9C,MAAM,OAAO,MAAM,KAAK,KAAK,WAAW;EACxC,MAAM,UAAU,MAAM,KAAK,KAAK,cAAc;EAE9C,KAAK,eAAe,MAAM;EAC1B,KAAK,YAAY,MAAM;EACvB,KAAK,eAAe,MAAM;EAE1B,MAAM,iBACJ,KAAK,mBAAmB,SAAY,YAAY,IAAI,IAAI,KAAK,iBAAiB;EAChF,MAAM,kBAAkB,KAAK;EAC7B,KAAK,iBAAiB;EACtB,KAAK,kBAAkB;EAEvB,IAAI,QAAQ,WAAW,KAAK,KAAK,WAAW,KAAK,QAAQ,WAAW,GAAG;EAKvE,MAAM,kBAAkB,QAAQ,OAAO,cAAc;EACrD,MAAM,eAAe,KAAK,OAAO,cAAc;EAC/C,MAAM,cAAc,QAAQ,QAAQ,MAAM,CAAC,eAAe,CAAC,CAAC;EAC5D,MAAM,WAAW,KAAK,QAAQ,MAAM,CAAC,eAAe,CAAC,CAAC;EAGtD,IAAI,SAAS,SAAS,YAAY,SAAS,GAAG;GAC5C,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;GACvD,qBAAqB;EACvB;EAEA,MAAM,EAAE,OAAO,gBAAgB,aAAa,MAAM,KAAK,iBAAiB,QAAQ;EAChF,MAAM,mBAAmB,MAAM,KAAK,oBAAoB,WAAW;EACnE,MAAM,KAAK,oBAAoB,OAAO;EAEtC,KAAK,eAAe,qBAAqB;EACzC,KAAK,eAAe,oBAAoB;EACxC,MAAM,KAAK,SAAS,KAAK;EAYzB,OAAO,QAAQ,6BAA6B;GAC1C,OAAO,CAAC,GAAG,cAAc,GAAG,cAAc;GAC1C,SAAS,CAAC,GAAG,iBAAiB,GAAG,gBAAgB;GACjD,SAAS,CAAC,GAAG,SAAS,GAAG,QAAQ;GACjC,SAAS,iBAAiB,IACtB;IAAE,iBAAiB,mBAAmB;IAAG,gBAAgB,kBAAkB;GAAE,IAC7E;EACN,CAAC;CACH;;;;;;;;CASA,MAAc,oBAAoB,eAA4C;EAC5E,MAAM,UAAoB,CAAC;EAC3B,MAAM,aAAa,oBAA2C,OAAO,SAAS;GAC5E,IAAI,MAAM,KAAK,eAAe,WAAW,IAAI,GAC3C,QAAQ,KAAK,IAAI;EAErB,CAAC;EACD,OAAO;CACT;;;;;;;;CASA,MAAc,iBACZ,eACkD;EAClD,MAAM,QAAkB,CAAC;EACzB,MAAM,WAAqB,CAAC;EAE5B,MAAM,aAAa,oBAA2C,OAAO,SAAS;GAC5E,IAAI;IAGF,KAAI,MAFsB,KAAK,eAAe,QAAQ,IAAI,EAE3C,CAAC,UAAU,WAAW;KACnC,MAAM,KAAK,eAAe,WAAW,IAAI;KACzC,SAAS,KAAK,IAAI;KAClB;IACF;IAEA,MAAM,KAAK,IAAI;IACf,cAAc,eAAe,MAAM;GACrC,SAAS,OAAO;IACd,QAAQ,MAAM,sBAAsB,KAAK,IAAI,KAAK;GACpD;EACF,CAAC;EAED,OAAO;GAAE;GAAO;EAAS;CAC3B;CAEA,MAAc,oBAAoB,eAAwC;EACxE,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,KAAK,eAAe,WAAW,YAAY;GACjD,cAAc,iBAAiB,cAAc;EAC/C;CACF;AACF;AAEA,SAAS,UAAU,MAAuB;CACxC,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;CAC1C,OAAO,aAAa,UAAU,SAAS,WAAW,OAAO;AAC3D;;AAGA,SAAS,eAAe,MAAuB;CAC7C,OAAO,UAAU,IAAI,KAAK,SAAS;AACrC;AAEA,eAAe,aACb,OACA,MACA,IACe;CACf,IAAI,MAAM,WAAW,GAAG;CACxB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MACrC,MAAM,QAAQ,IAAI,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAEtD"}
|
|
1
|
+
{"version":3,"file":"file-event-handler.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/file-event-handler.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type { DependencyGraph } from \"./dependency-graph\";\nimport { devLogSuccess } from \"./dev-logger\";\nimport type { FileManager } from \"./file-manager\";\nimport type { FileOperations } from \"./file-operations\";\nimport { FILE_PROCESSING_BATCH_SIZE, isTimingsEnabled } from \"./flags\";\nimport type { ManifestManager } from \"./manifest-manager\";\nimport { clearFileExistsCache } from \"./parse-imports\";\nimport { Path } from \"../utils/normalized-path\";\n\n/** Lets an isolated editor save reach HMR without the former fixed 50ms delay. */\nconst ISOLATED_SAVE_QUIET_WINDOW_MS = 12;\n\n/** Bounds a sustained formatter or checkout stream so it cannot postpone HMR forever. */\nconst BATCH_MAX_WAIT_MS = 60;\n\n/**\n * Receives raw watcher events and processes them in a single debounced batch.\n * Order within a batch: adds → changes → deletes, so changes can reference\n * newly-added files and deletes fire last.\n */\nexport class FileEventHandler {\n private pendingChanges = new Set<string>();\n private pendingAdds = new Set<string>();\n private pendingDeletes = new Set<string>();\n\n /** When this batch's first watcher event arrived — the debounce-wait phase start. */\n private batchStartedAt?: number;\n\n /** Slowest watcher-settle duration seen so far this batch, when `devServer.timings` is on. */\n private watcherSettleMs?: number;\n\n private debounceTimer?: ReturnType<typeof setTimeout>;\n private maxWaitTimer?: ReturnType<typeof setTimeout>;\n\n constructor(\n private readonly fileOperations: FileOperations,\n private readonly manifest: ManifestManager,\n private readonly dependencyGraph: DependencyGraph,\n private readonly files: Map<string, FileManager>,\n ) {}\n\n public handleFileChange(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingChanges.add(Path.toRelative(absolutePath));\n this.schedulePendingEvents();\n }\n\n public handleFileAdd(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingAdds.add(Path.toRelative(absolutePath));\n this.schedulePendingEvents();\n }\n\n public handleFileDelete(absolutePath: string, settleMs?: number): void {\n this.recordTimingStart(settleMs);\n this.pendingDeletes.add(Path.toRelative(absolutePath));\n this.schedulePendingEvents();\n }\n\n private schedulePendingEvents(): void {\n if (this.debounceTimer !== undefined) {\n clearTimeout(this.debounceTimer);\n }\n\n this.debounceTimer = setTimeout(() => this.flushPendingEvents(), ISOLATED_SAVE_QUIET_WINDOW_MS);\n\n if (this.maxWaitTimer === undefined) {\n this.maxWaitTimer = setTimeout(() => this.flushPendingEvents(), BATCH_MAX_WAIT_MS);\n }\n }\n\n private flushPendingEvents(): void {\n if (this.debounceTimer !== undefined) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = undefined;\n }\n\n if (this.maxWaitTimer !== undefined) {\n clearTimeout(this.maxWaitTimer);\n this.maxWaitTimer = undefined;\n }\n\n void this.processBatch();\n }\n\n /**\n * Mark the debounce-wait phase start on the first event of a batch, and\n * track the slowest watcher-settle duration seen this batch. `settleMs`\n * only arrives when `devServer.timings` is on (see `FilesWatcher`), so\n * this is a no-op past the cheap `performance.now()` call when it's off.\n */\n private recordTimingStart(settleMs?: number): void {\n if (this.batchStartedAt === undefined) {\n this.batchStartedAt = performance.now();\n }\n\n if (settleMs === undefined) return;\n\n this.watcherSettleMs =\n this.watcherSettleMs === undefined ? settleMs : Math.max(this.watcherSettleMs, settleMs);\n }\n\n private async processBatch(): Promise<void> {\n const changes = Array.from(this.pendingChanges);\n const adds = Array.from(this.pendingAdds);\n const deletes = Array.from(this.pendingDeletes);\n\n this.pendingChanges.clear();\n this.pendingAdds.clear();\n this.pendingDeletes.clear();\n\n const debounceWaitMs =\n this.batchStartedAt !== undefined ? performance.now() - this.batchStartedAt : undefined;\n const watcherSettleMs = this.watcherSettleMs;\n this.batchStartedAt = undefined;\n this.watcherSettleMs = undefined;\n\n if (changes.length === 0 && adds.length === 0 && deletes.length === 0) return;\n\n // Both .env files and warlock.config.ts live outside src/ — they should\n // never enter the dep graph, only ride along in the batch event so the\n // dev server can react (config reload / restart warning).\n const externalChanges = changes.filter(isExternalPath);\n const externalAdds = adds.filter(isExternalPath);\n const codeChanges = changes.filter((p) => !isExternalPath(p));\n const codeAdds = adds.filter((p) => !isExternalPath(p));\n\n // Multi-file batches can race the filesystem on Windows.\n if (codeAdds.length + codeChanges.length > 1) {\n await new Promise((resolve) => setTimeout(resolve, 500));\n clearFileExistsCache();\n }\n\n const { added: addedCodePaths, vanished } = await this.processBatchAdds(codeAdds);\n const changedCodePaths = await this.processBatchChanges(codeChanges);\n await this.processBatchDeletes(deletes);\n\n this.fileOperations.updateFileDependents();\n this.fileOperations.syncFilesToManifest();\n await this.manifest.save();\n\n // Emit only the code paths that genuinely changed (hash differs). A no-op\n // save — an editor that fsyncs without writing — reports no change and is\n // dropped here, where we still know the pre-change hash. (Doing this\n // downstream is impossible: the source has already been overwritten, so a\n // content compare always looks unchanged — which is exactly why emptying a\n // file used to silently skip HMR.) External paths (.env / warlock.config.ts)\n // ride along untouched so the dev server can still react to them. Paths\n // that vanished between the add event and their read/stat (a rename or\n // move racing the filesystem) are folded into `deleted` instead of\n // `added` — they were already unwound as deletions in processBatchAdds.\n events.trigger(\"dev-server:batch-complete\", {\n added: [...externalAdds, ...addedCodePaths],\n changed: [...externalChanges, ...changedCodePaths],\n deleted: [...deletes, ...vanished],\n timings: isTimingsEnabled()\n ? { watcherSettleMs: watcherSettleMs ?? 0, debounceWaitMs: debounceWaitMs ?? 0 }\n : undefined,\n });\n }\n\n /**\n * Reprocess each changed file and return only the paths that genuinely\n * changed. `updateFile` returns false when the on-disk hash matches the\n * last-processed hash (e.g. an editor that fsyncs on save without writing),\n * so those no-ops are kept out of the reload batch. Emptying a file changes\n * its hash, so it is correctly reported as changed.\n */\n private async processBatchChanges(relativePaths: string[]): Promise<string[]> {\n const changed: string[] = [];\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async (path) => {\n if (await this.fileOperations.updateFile(path)) {\n changed.push(path);\n }\n });\n return changed;\n }\n\n /**\n * Process pending add events. Returns the paths that genuinely became\n * available (`added`) separately from paths that no longer existed by the\n * time they were read/stat'd (`vanished`) — the latter is a rename or move\n * racing the filesystem, not a failure, so it is unwound as a deletion\n * instead of being logged as an error.\n */\n private async processBatchAdds(\n relativePaths: string[],\n ): Promise<{ added: string[]; vanished: string[] }> {\n const added: string[] = [];\n const vanished: string[] = [];\n\n await runInBatches(relativePaths, FILE_PROCESSING_BATCH_SIZE, async (path) => {\n try {\n const fileManager = await this.fileOperations.addFile(path);\n\n if (fileManager.state === \"deleted\") {\n await this.fileOperations.deleteFile(path);\n vanished.push(path);\n return;\n }\n\n added.push(path);\n devLogSuccess(`Added file: ${path}`);\n } catch (error) {\n console.error(`Failed to add file ${path}:`, error);\n }\n });\n\n return { added, vanished };\n }\n\n private async processBatchDeletes(relativePaths: string[]): Promise<void> {\n for (const relativePath of relativePaths) {\n await this.fileOperations.deleteFile(relativePath);\n devLogSuccess(`Deleted file: ${relativePath}`);\n }\n }\n}\n\nfunction isEnvFile(path: string): boolean {\n const basename = path.split(\"/\").pop() || path;\n return basename === \".env\" || basename.startsWith(\".env.\");\n}\n\n/** Paths watched but never added to the dependency graph. */\nfunction isExternalPath(path: string): boolean {\n return isEnvFile(path) || path === \"warlock.config.ts\";\n}\n\nasync function runInBatches<T>(\n items: T[],\n size: number,\n fn: (item: T) => Promise<unknown>,\n): Promise<void> {\n if (items.length === 0) return;\n for (let i = 0; i < items.length; i += size) {\n await Promise.all(items.slice(i, i + size).map(fn));\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { Path } from "../utils/normalized-path.mjs";
|
|
|
2
2
|
import { rootPath, srcPath } from "../utils/paths.mjs";
|
|
3
3
|
import "../utils/index.mjs";
|
|
4
4
|
import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
|
|
5
|
+
import path from "node:path";
|
|
5
6
|
import { Random } from "@mongez/reinforcements";
|
|
6
7
|
import events from "@mongez/events";
|
|
7
8
|
import chokidar from "chokidar";
|
|
@@ -67,8 +68,10 @@ var FilesWatcher = class {
|
|
|
67
68
|
},
|
|
68
69
|
depth: 99
|
|
69
70
|
});
|
|
70
|
-
if (timingsEnabled) watcher.on("raw", (_event,
|
|
71
|
-
const
|
|
71
|
+
if (timingsEnabled) watcher.on("raw", (_event, evPath, details) => {
|
|
72
|
+
const watchedPath = details?.watchedPath;
|
|
73
|
+
const absolutePath = watchedPath ? path.resolve(watchedPath, evPath) : evPath;
|
|
74
|
+
const normalized = Path.normalize(absolutePath);
|
|
72
75
|
if (!this.rawEventTimestamps.has(normalized)) this.rawEventTimestamps.set(normalized, performance.now());
|
|
73
76
|
});
|
|
74
77
|
watcher.on("add", (filePath) => this.triggerEvent("add", filePath));
|
|
@@ -92,7 +95,7 @@ var FilesWatcher = class {
|
|
|
92
95
|
settleMs = performance.now() - rawEventAt;
|
|
93
96
|
this.rawEventTimestamps.delete(normalized);
|
|
94
97
|
}
|
|
95
|
-
events.trigger(`file-watcher.${this.id}.${event}`, normalized, error
|
|
98
|
+
events.trigger(`file-watcher.${this.id}.${event}`, normalized, error ?? settleMs);
|
|
96
99
|
}
|
|
97
100
|
/**
|
|
98
101
|
* On file change event
|
|
@@ -1 +1 @@
|
|
|
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, settleMs?: number) => void;\ntype FileDeleteCallback = (filePath: string, settleMs?: number) => void;\ntype FileAddCallback = (filePath: string, settleMs?: number) => 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 * First-seen timestamp per path from chokidar's `raw` event, which fires\n * BEFORE `awaitWriteFinish` stabilises the change. Diffing it against the\n * stabilised `add`/`change` timestamp gives the actual watcher-settle\n * duration for the phase-timing line. Only populated when\n * `devServer.timings` is on — see `watch()` — so a disabled flag costs\n * nothing here beyond the `undefined` checks below.\n */\n private readonly rawEventTimestamps = new Map<string, number>();\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 timingsEnabled = devServerConfig?.timings === true;\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 if (timingsEnabled) {\n watcher.on(\"raw\", (_event, path) => {\n const normalized = Path.normalize(path);\n if (!this.rawEventTimestamps.has(normalized)) {\n this.rawEventTimestamps.set(normalized, performance.now());\n }\n });\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 const normalized = Path.normalize(filePath);\n const rawEventAt = this.rawEventTimestamps.get(normalized);\n let settleMs: number | undefined;\n\n if (rawEventAt !== undefined) {\n settleMs = performance.now() - rawEventAt;\n this.rawEventTimestamps.delete(normalized);\n }\n\n events.trigger(`file-watcher.${this.id}.${event}`, normalized, error, settleMs);\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;4CAUW,IAAI,IAAoB;;;;;;CAM9D,MAAa,MAAM,QAAsB;EAEvC,MAAM,kBAAkB,MAAM,qBAAqB,QAAQ,WAAW;EACtE,MAAM,kBAAkB,iBAAiB;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,iBAAiB,iBAAiB,YAAY;EAEpD,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,IAAI,gBACF,QAAQ,GAAG,QAAQ,QAAQ,SAAS;GAClC,MAAM,aAAa,KAAK,UAAU,IAAI;GACtC,IAAI,CAAC,KAAK,mBAAmB,IAAI,UAAU,GACzC,KAAK,mBAAmB,IAAI,YAAY,YAAY,IAAI,CAAC;EAE7D,CAAC;EAGH,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,MAAM,aAAa,KAAK,UAAU,QAAQ;EAC1C,MAAM,aAAa,KAAK,mBAAmB,IAAI,UAAU;EACzD,IAAI;EAEJ,IAAI,eAAe,QAAW;GAC5B,WAAW,YAAY,IAAI,IAAI;GAC/B,KAAK,mBAAmB,OAAO,UAAU;EAC3C;EAEA,OAAO,QAAQ,gBAAgB,KAAK,GAAG,GAAG,SAAS,YAAY,OAAO,QAAQ;CAChF;;;;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"}
|
|
1
|
+
{"version":3,"file":"files-watcher.mjs","names":["nodePath"],"sources":["../../../../../../../core/src/dev-server/files-watcher.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { Random } from \"@mongez/reinforcements\";\nimport chokidar from \"chokidar\";\nimport nodePath from \"node:path\";\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, settleMs?: number) => void;\ntype FileDeleteCallback = (filePath: string, settleMs?: number) => void;\ntype FileAddCallback = (filePath: string, settleMs?: number) => 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 * First-seen timestamp per path from chokidar's `raw` event, which fires\n * BEFORE `awaitWriteFinish` stabilises the change. Diffing it against the\n * stabilised `add`/`change` timestamp gives the actual watcher-settle\n * duration for the phase-timing line. Only populated when\n * `devServer.timings` is on — see `watch()` — so a disabled flag costs\n * nothing here beyond the `undefined` checks below.\n */\n private readonly rawEventTimestamps = new Map<string, number>();\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 timingsEnabled = devServerConfig?.timings === true;\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 if (timingsEnabled) {\n watcher.on(\"raw\", (_event, evPath, details) => {\n // On the native (non-fsevents) handler chokidar uses on Windows/Linux,\n // `evPath` is only the changed entry's path *relative to the watched\n // directory* (see chokidar's `handler.js` `handleEvent`/`emitRaw`),\n // not the absolute path the later `add`/`change` event carries. Only\n // fsevents (macOS) hands back an already-absolute path. Reconstruct\n // the absolute path from `details.watchedPath` so the timestamp is\n // keyed the same way the stabilised event looks it up below —\n // otherwise the lookup always misses and the phase silently reads 0.\n const watchedPath = (details as { watchedPath?: string } | undefined)?.watchedPath;\n const absolutePath = watchedPath ? nodePath.resolve(watchedPath, evPath) : evPath;\n const normalized = Path.normalize(absolutePath);\n if (!this.rawEventTimestamps.has(normalized)) {\n this.rawEventTimestamps.set(normalized, performance.now());\n }\n });\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 const normalized = Path.normalize(filePath);\n const rawEventAt = this.rawEventTimestamps.get(normalized);\n let settleMs: number | undefined;\n\n if (rawEventAt !== undefined) {\n settleMs = performance.now() - rawEventAt;\n this.rawEventTimestamps.delete(normalized);\n }\n\n // `error` and `settleMs` share the same second-argument slot: an error\n // event's subscriber reads it as `Error`, every other event's subscriber\n // reads it as the watcher-settle duration (see the `On*Callback` types\n // above). Passing both positionally (`normalized, error, settleMs`) used\n // to leave `settleMs` in an unread third slot, so every change/add/delete\n // subscriber always received `undefined` in its `settleMs` parameter —\n // the watcher phase's timing was silently discarded here even when the\n // raw-event timestamp above resolved correctly.\n events.trigger(`file-watcher.${this.id}.${event}`, normalized, error ?? settleMs);\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":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/dev-server/translation-type-generator.ts
|
|
4
|
+
/** Extract literal lookup keys registered through groupedTranslations calls. */
|
|
5
|
+
function extractTranslationKeys(sourceFile) {
|
|
6
|
+
const keys = /* @__PURE__ */ new Set();
|
|
7
|
+
const visit = (node) => {
|
|
8
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "groupedTranslations") {
|
|
9
|
+
const [groupArgument, dictionaryArgument] = node.arguments;
|
|
10
|
+
if (ts.isStringLiteral(groupArgument) && dictionaryArgument && ts.isObjectLiteralExpression(dictionaryArgument)) for (const property of dictionaryArgument.properties) {
|
|
11
|
+
if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) continue;
|
|
12
|
+
const key = getPropertyName(property.name);
|
|
13
|
+
if (key !== void 0) keys.add(`${groupArgument.text}.${key}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
ts.forEachChild(node, visit);
|
|
17
|
+
};
|
|
18
|
+
ts.forEachChild(sourceFile, visit);
|
|
19
|
+
return Array.from(keys).sort();
|
|
20
|
+
}
|
|
21
|
+
function getPropertyName(name) {
|
|
22
|
+
if (name === void 0) return;
|
|
23
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { extractTranslationKeys };
|
|
28
|
+
//# sourceMappingURL=translation-type-generator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translation-type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/translation-type-generator.ts"],"sourcesContent":["import ts from \"typescript\";\n\n/** Extract literal lookup keys registered through groupedTranslations calls. */\nexport function extractTranslationKeys(sourceFile: ts.SourceFile): string[] {\n const keys = new Set<string>();\n\n const visit = (node: ts.Node): void => {\n if (\n ts.isCallExpression(node) &&\n ts.isIdentifier(node.expression) &&\n node.expression.text === \"groupedTranslations\"\n ) {\n const [groupArgument, dictionaryArgument] = node.arguments;\n\n if (\n ts.isStringLiteral(groupArgument) &&\n dictionaryArgument &&\n ts.isObjectLiteralExpression(dictionaryArgument)\n ) {\n for (const property of dictionaryArgument.properties) {\n if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) {\n continue;\n }\n\n const key = getPropertyName(property.name);\n if (key !== undefined) {\n keys.add(`${groupArgument.text}.${key}`);\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return Array.from(keys).sort();\n}\n\nfunction getPropertyName(name: ts.PropertyName | undefined): string | undefined {\n if (name === undefined) {\n return undefined;\n }\n\n if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {\n return name.text;\n }\n\n return undefined;\n}\n"],"mappings":";;;;AAGA,SAAgB,uBAAuB,YAAqC;CAC1E,MAAM,uBAAO,IAAI,IAAY;CAE7B,MAAM,SAAS,SAAwB;EACrC,IACE,GAAG,iBAAiB,IAAI,KACxB,GAAG,aAAa,KAAK,UAAU,KAC/B,KAAK,WAAW,SAAS,uBACzB;GACA,MAAM,CAAC,eAAe,sBAAsB,KAAK;GAEjD,IACE,GAAG,gBAAgB,aAAa,KAChC,sBACA,GAAG,0BAA0B,kBAAkB,GAE/C,KAAK,MAAM,YAAY,mBAAmB,YAAY;IACpD,IAAI,CAAC,GAAG,qBAAqB,QAAQ,KAAK,CAAC,GAAG,8BAA8B,QAAQ,GAClF;IAGF,MAAM,MAAM,gBAAgB,SAAS,IAAI;IACzC,IAAI,QAAQ,QACV,KAAK,IAAI,GAAG,cAAc,KAAK,GAAG,KAAK;GAE3C;EAEJ;EAEA,GAAG,aAAa,MAAM,KAAK;CAC7B;CAEA,GAAG,aAAa,YAAY,KAAK;CAEjC,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,KAAK;AAC/B;AAEA,SAAS,gBAAgB,MAAuD;CAC9E,IAAI,SAAS,QACX;CAGF,IAAI,GAAG,aAAa,IAAI,KAAK,GAAG,gBAAgB,IAAI,KAAK,GAAG,iBAAiB,IAAI,GAC/E,OAAO,KAAK;AAIhB"}
|
|
@@ -55,6 +55,7 @@ var TSConfigManager = class {
|
|
|
55
55
|
const aliasTargets = this.aliases[aliasKey];
|
|
56
56
|
if (!Array.isArray(aliasTargets) || aliasTargets.length === 0) return null;
|
|
57
57
|
const targetPattern = aliasTargets[0];
|
|
58
|
+
if (targetPattern === void 0) return null;
|
|
58
59
|
const aliasPattern = aliasKey.replace("/*", "");
|
|
59
60
|
const targetBase = targetPattern.replace("/*", "");
|
|
60
61
|
const relativePart = checkingPath.substring(aliasPattern.length).replace(/^[/\\]/, "");
|
|
@@ -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 \"../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;
|
|
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 if (targetPattern === undefined) {\n return null;\n }\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;EAEnC,IAAI,kBAAkB,QACpB,OAAO;EAIT,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"}
|
|
@@ -5,6 +5,7 @@ import { devLogError, devLogInfo, devLogSuccess, devServeLog } from "./dev-logge
|
|
|
5
5
|
import { filesOrchestrator } from "./files-orchestrator.mjs";
|
|
6
6
|
import { readConfigAst } from "./read-config-ast.mjs";
|
|
7
7
|
import { runTypingsGeneration } from "./run-typings-generation.mjs";
|
|
8
|
+
import { extractTranslationKeys } from "./translation-type-generator.mjs";
|
|
8
9
|
import { join, resolve } from "path";
|
|
9
10
|
import { ensureDirectoryAsync } from "@warlock.js/fs";
|
|
10
11
|
import { constants } from "fs";
|
|
@@ -38,16 +39,19 @@ var TypeGenerator = class {
|
|
|
38
39
|
await this.ensureOutputDir();
|
|
39
40
|
const storageFile = join(this.outputDir, "storage.d.ts");
|
|
40
41
|
const configFile = join(this.outputDir, "config.d.ts");
|
|
41
|
-
const
|
|
42
|
+
const translationsFile = join(this.outputDir, "translations.d.ts");
|
|
43
|
+
const [manifestExists, storageExists, configExists, translationsExist] = await Promise.all([
|
|
42
44
|
this.exists(this.manifestPath),
|
|
43
45
|
this.exists(storageFile),
|
|
44
|
-
this.exists(configFile)
|
|
46
|
+
this.exists(configFile),
|
|
47
|
+
this.exists(translationsFile)
|
|
45
48
|
]);
|
|
46
|
-
if (!manifestExists || !storageExists || !configExists) await this.fullGeneration();
|
|
49
|
+
if (!manifestExists || !storageExists || !configExists || !translationsExist) await this.fullGeneration();
|
|
47
50
|
else {
|
|
48
51
|
await this.loadManifest();
|
|
49
52
|
await this.reconcile();
|
|
50
53
|
}
|
|
54
|
+
await this.generateTranslationTypes();
|
|
51
55
|
await this.saveManifest();
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
@@ -130,7 +134,7 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
|
|
|
130
134
|
* Check if a file change should trigger type regeneration
|
|
131
135
|
*/
|
|
132
136
|
shouldRegenerateTypes(changedPath) {
|
|
133
|
-
return changedPath.includes("src/config/") || changedPath.includes("config/");
|
|
137
|
+
return changedPath.includes("src/config/") || changedPath.includes("config/") || this.isLocalesFile(changedPath);
|
|
134
138
|
}
|
|
135
139
|
/**
|
|
136
140
|
* Handle file change - uses incremental update via cache
|
|
@@ -142,6 +146,10 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
|
|
|
142
146
|
await this.saveManifest();
|
|
143
147
|
return;
|
|
144
148
|
}
|
|
149
|
+
if (this.isLocalesFile(changedPath)) {
|
|
150
|
+
await this.generateTranslationTypes();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
145
153
|
const match = changedPath.match(/config\/([^/]+)\.[^.]+$/);
|
|
146
154
|
if (!match) return;
|
|
147
155
|
const configName = match[1];
|
|
@@ -183,6 +191,31 @@ ${driverKeys.map((k) => ` ${this.toInterfaceKey(k)}: true;`).join("\n")}
|
|
|
183
191
|
devServeLog(`âš ï¸ Failed to generate config types: ${error}`);
|
|
184
192
|
}
|
|
185
193
|
}
|
|
194
|
+
/** Generate web's app-augmented translation-key registry. */
|
|
195
|
+
async generateTranslationTypes() {
|
|
196
|
+
const keys = /* @__PURE__ */ new Set();
|
|
197
|
+
for (const [path, fileManager] of filesOrchestrator.getFiles()) {
|
|
198
|
+
if (!this.isLocalesFile(path)) continue;
|
|
199
|
+
const sourceFile = await readConfigAst(fileManager.absolutePath);
|
|
200
|
+
if (sourceFile) for (const key of extractTranslationKeys(sourceFile)) keys.add(key);
|
|
201
|
+
}
|
|
202
|
+
const content = `// Auto-generated by Warlock.js - DO NOT EDIT
|
|
203
|
+
// Generated from groupedTranslations calls in app locale files
|
|
204
|
+
|
|
205
|
+
import "@warlock.js/web";
|
|
206
|
+
|
|
207
|
+
declare module "@warlock.js/web" {
|
|
208
|
+
interface TranslationKeyRegistry {
|
|
209
|
+
${Array.from(keys).sort().map((key) => ` ${JSON.stringify(key)}: true;`).join("\n")}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
await writeFile(join(this.outputDir, "translations.d.ts"), content, "utf-8");
|
|
214
|
+
devLogSuccess(`Generated translation types: ${keys.size} keys`);
|
|
215
|
+
}
|
|
216
|
+
isLocalesFile(path) {
|
|
217
|
+
return Path.normalize(path).includes("/utils/locales.");
|
|
218
|
+
}
|
|
186
219
|
/**
|
|
187
220
|
* Quote an interface key that isn't a valid JS identifier (e.g. a config
|
|
188
221
|
* named `use-cases` or a storage driver `do-spaces`) so the generated
|
|
@@ -430,7 +463,10 @@ ${allKeys.map((key) => ` "${key}": true;`).join("\n")}
|
|
|
430
463
|
* touched a config file, since nothing else contributes to them.
|
|
431
464
|
*/
|
|
432
465
|
async executeTypingsGenerator(upcomingFiles) {
|
|
433
|
-
if (!Array.from(new Set(upcomingFiles)).some((file) =>
|
|
466
|
+
if (!Array.from(new Set(upcomingFiles)).some((file) => {
|
|
467
|
+
const normalizedPath = Path.normalize(file);
|
|
468
|
+
return normalizedPath.includes("src/config/") || this.isLocalesFile(normalizedPath);
|
|
469
|
+
})) return;
|
|
434
470
|
await runTypingsGeneration(this.typingsGenerationPorts);
|
|
435
471
|
}
|
|
436
472
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n\n const [manifestExists, storageExists, configExists] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"âš ï¸ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys\n .map((k) => ` ${this.toInterfaceKey(k)}: true;`)\n .join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return changedPath.includes(\"src/config/\") || changedPath.includes(\"config/\");\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate config types: ${error}`);\n }\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`âš ï¸ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`âš ï¸ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) =>\n Path.normalize(file).includes(\"src/config/\"),\n );\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"type-generator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/type-generator.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport ts from \"typescript\";\nimport { warlockPath } from \"../utils\";\nimport { devLogError, devLogInfo, devLogSuccess, devServeLog } from \"./dev-logger\";\nimport { readConfigAst } from \"./read-config-ast\";\nimport { runTypingsGeneration, type TypingsGenerationPorts } from \"./run-typings-generation\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { extractTranslationKeys } from \"./translation-type-generator\";\n\n/**\n * Typings manifest structure for tracking file hashes\n */\ntype TypingsManifest = {\n version: string;\n lastBuildTime: number;\n storage: {\n sourceHash: string;\n drivers: string[];\n } | null;\n config: Record<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >;\n};\n\n/**\n * TypeGenerator - Generates TypeScript type definitions from config files\n *\n * Parses source config files using the TypeScript Compiler API to extract\n * keys and generate module augmentation types for IDE autocomplete.\n *\n * Uses manifest-based reconciliation to only regenerate when source files change.\n */\nexport class TypeGenerator {\n /**\n * Output directory for generated typings\n */\n private outputDir = warlockPath(\"typings\");\n\n /**\n * Path to typings manifest file\n */\n private manifestPath = join(this.outputDir, \"typings-manifest.json\");\n\n /**\n * Cached manifest data\n */\n private manifest: TypingsManifest | null = null;\n\n /**\n * Cache for config type info and keys\n */\n private configCache = new Map<\n string,\n {\n sourceHash: string;\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }\n >();\n\n /**\n * Generate all framework type definitions\n *\n * Uses manifest-based reconciliation:\n * - If output files don't exist: full regeneration\n * - If files exist: only regenerate changed configs\n */\n public async generateAll(): Promise<void> {\n await this.ensureOutputDir();\n\n const storageFile = join(this.outputDir, \"storage.d.ts\");\n const configFile = join(this.outputDir, \"config.d.ts\");\n const translationsFile = join(this.outputDir, \"translations.d.ts\");\n\n const [manifestExists, storageExists, configExists, translationsExist] = await Promise.all([\n this.exists(this.manifestPath),\n this.exists(storageFile),\n this.exists(configFile),\n this.exists(translationsFile),\n ]);\n\n if (!manifestExists || !storageExists || !configExists || !translationsExist) {\n // Full regeneration (first run or files deleted)\n await this.fullGeneration();\n } else {\n // Load manifest for hash comparison\n await this.loadManifest();\n // Reconciliation: only regenerate changed files\n await this.reconcile();\n }\n\n await this.generateTranslationTypes();\n\n await this.saveManifest();\n }\n\n /**\n * Full regeneration of all type files\n */\n private async fullGeneration(): Promise<void> {\n // Generate storage types\n const storageConfigPath = await this.findConfigFile(\"storage\");\n if (storageConfigPath) {\n this.generateStorageTypes(storageConfigPath);\n }\n\n // Generate config types\n await this.generateConfigTypes();\n }\n\n /**\n * Reconcile: only regenerate changed files\n */\n private async reconcile(): Promise<void> {\n const files = filesOrchestrator.getFiles();\n let storageChanged = false;\n let configChanged = false;\n let unchangedCount = 0;\n\n // Check storage config\n for (const [path, fileManager] of files) {\n if (path.startsWith(\"src/config/storage\")) {\n const manifestEntry = this.manifest?.storage;\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n storageChanged = true;\n await this.generateStorageTypes(path);\n }\n break;\n }\n }\n\n // Check config files\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name (remove dir prefix and extension)\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n const manifestEntry = this.manifest?.config[configName];\n\n if (!manifestEntry || manifestEntry.sourceHash !== fileManager.hash) {\n // File changed or new - regenerate\n configChanged = true;\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n } else {\n // Unchanged - load from manifest\n unchangedCount++;\n this.configCache.set(configName, manifestEntry);\n }\n }\n\n if (configChanged) {\n await this.writeConfigTypesFromCache();\n } else {\n devLogInfo(`Config types unchanged (${unchangedCount} configs cached)`);\n }\n }\n\n /**\n * Generate storage driver name types\n */\n public async generateStorageTypes(configPath: string): Promise<void> {\n try {\n const driverKeys = await this.extractStorageDriverKeys(configPath);\n\n if (driverKeys.length === 0) {\n devServeLog(\"âš ï¸ No storage drivers found in config\");\n return;\n }\n\n // Get file hash from filesOrchestrator\n const fileManager = filesOrchestrator.getFiles().get(configPath);\n const sourceHash = fileManager?.hash || \"\";\n\n // Update manifest\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.storage = {\n sourceHash,\n drivers: driverKeys,\n };\n\n const interfaceContent = driverKeys\n .map((k) => ` ${this.toInterfaceKey(k)}: true;`)\n .join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from: ${configPath}\n// Regenerates on dev-server start and when storage config changes\n\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface StorageDriverRegistry {\n${interfaceContent}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"storage.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(`Generated storage types: ${driverKeys.join(\", \")}`);\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate storage types: ${error}`);\n }\n }\n\n /**\n * Check if a file change should trigger type regeneration\n */\n public shouldRegenerateTypes(changedPath: string): boolean {\n return (\n changedPath.includes(\"src/config/\") ||\n changedPath.includes(\"config/\") ||\n this.isLocalesFile(changedPath)\n );\n }\n\n /**\n * Handle file change - uses incremental update via cache\n */\n public async handleFileChange(changedPath: string): Promise<void> {\n if (!this.shouldRegenerateTypes(changedPath)) {\n return;\n }\n\n // Regenerate storage types if storage config changed\n if (changedPath.includes(\"config/storage\")) {\n await this.generateStorageTypes(changedPath);\n await this.saveManifest();\n return;\n }\n\n if (this.isLocalesFile(changedPath)) {\n await this.generateTranslationTypes();\n return;\n }\n\n // Extract config name from path\n const match = changedPath.match(/config\\/([^/]+)\\.[^.]+$/);\n if (!match) {\n return;\n }\n\n const configName = match[1];\n if (configName === \"index\") return;\n\n devLogInfo(`Config changed: ${configName}, updating...`);\n\n // Get file manager for hash\n const fileManager = filesOrchestrator.getFiles().get(changedPath);\n const sourceHash = fileManager?.hash || Date.now().toString();\n\n // Update only the changed config in cache (use optimized combined extraction)\n const configDir = join(process.cwd(), \"src/config\");\n const configPath = join(configDir, `${configName}.ts`);\n\n const info = await this.extractConfigInfo(configPath, configName);\n\n this.configCache.set(configName, {\n sourceHash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n\n // Regenerate config.d.ts from cache\n await this.writeConfigTypesFromCache();\n await this.saveManifest();\n }\n\n /**\n * Generate config types - populates cache and writes file\n */\n public async generateConfigTypes(): Promise<void> {\n try {\n const files = filesOrchestrator.getFiles();\n\n // Clear and repopulate cache\n this.configCache.clear();\n\n for (const [path, fileManager] of files) {\n if (!path.startsWith(\"src/config/\")) continue;\n if (path.includes(\"index\")) continue;\n\n // Extract config name\n const configName = path.replace(\"src/config/\", \"\").replace(/\\.[^.]+$/, \"\");\n\n // One parse per config, both extractions off it — see `read-config-ast.ts`.\n const info = await this.extractConfigInfo(fileManager.absolutePath, configName);\n\n this.configCache.set(configName, {\n sourceHash: fileManager.hash,\n typeName: info.typeName,\n importSource: info.importSource,\n keys: info.keys,\n });\n }\n\n await this.writeConfigTypesFromCache();\n } catch (error) {\n devServeLog(`âš ï¸ Failed to generate config types: ${error}`);\n }\n }\n\n /** Generate web's app-augmented translation-key registry. */\n private async generateTranslationTypes(): Promise<void> {\n const keys = new Set<string>();\n\n for (const [path, fileManager] of filesOrchestrator.getFiles()) {\n if (!this.isLocalesFile(path)) {\n continue;\n }\n\n const sourceFile = await readConfigAst(fileManager.absolutePath);\n if (sourceFile) {\n for (const key of extractTranslationKeys(sourceFile)) {\n keys.add(key);\n }\n }\n }\n\n const entries = Array.from(keys)\n .sort()\n .map((key) => ` ${JSON.stringify(key)}: true;`)\n .join(\"\\n\");\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Generated from groupedTranslations calls in app locale files\n\nimport \"@warlock.js/web\";\n\ndeclare module \"@warlock.js/web\" {\n interface TranslationKeyRegistry {\n${entries}\n }\n}\n`;\n\n await writeFile(join(this.outputDir, \"translations.d.ts\"), content, \"utf-8\");\n devLogSuccess(`Generated translation types: ${keys.size} keys`);\n }\n\n private isLocalesFile(path: string): boolean {\n return Path.normalize(path).includes(\"/utils/locales.\");\n }\n\n /**\n * Quote an interface key that isn't a valid JS identifier (e.g. a config\n * named `use-cases` or a storage driver `do-spaces`) so the generated\n * `.d.ts` stays syntactically valid. Identifier-safe names are left bare\n * to keep the output clean.\n */\n private toInterfaceKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n }\n\n /**\n * Write config.d.ts from cached data\n */\n private async writeConfigTypesFromCache(): Promise<void> {\n const configTypeInfos: Array<{\n name: string;\n typeName: string | null;\n importSource: string | null;\n }> = [];\n const allKeys: string[] = [];\n\n // Update manifest config section\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.config = {};\n\n for (const [name, data] of this.configCache) {\n configTypeInfos.push({\n name,\n typeName: data.typeName,\n importSource: data.importSource,\n });\n allKeys.push(...data.keys);\n\n // Store in manifest\n this.manifest.config[name] = data;\n }\n\n // Group imports by source\n const imports = new Map<string, Set<string>>();\n for (const info of configTypeInfos) {\n if (info.typeName && info.importSource) {\n if (!imports.has(info.importSource)) {\n imports.set(info.importSource, new Set());\n }\n imports.get(info.importSource)!.add(info.typeName);\n }\n }\n\n const importStatements = Array.from(imports.entries())\n .map(([source, types]) => `import type { ${Array.from(types).join(\", \")} } from \"${source}\";`)\n .join(\"\\n\");\n\n const configEntries = configTypeInfos\n .map((info) => ` ${this.toInterfaceKey(info.name)}: ${info.typeName || \"unknown\"};`)\n .join(\"\\n\");\n\n const keyEntries = allKeys.map((key) => ` \"${key}\": true;`).join(\"\\n\");\n\n const content = `// Auto-generated by Warlock.js - DO NOT EDIT\n// Regenerates on dev-server start and when config files change\n\n${importStatements}\nimport \"@warlock.js/core\";\n\ndeclare module \"@warlock.js/core\" {\n interface ConfigRegistry {\n${configEntries}\n }\n\n interface ConfigKeyRegistry {\n${keyEntries}\n }\n}\n`;\n\n const outputPath = join(this.outputDir, \"config.d.ts\");\n await writeFile(outputPath, content, \"utf-8\");\n\n devLogSuccess(\n `Generated config types: ${this.configCache.size} configs, ${allKeys.length} keys`,\n );\n }\n\n // ============================================================\n // Manifest Management\n // ============================================================\n\n /**\n * Load manifest from disk\n */\n private async loadManifest(): Promise<boolean> {\n try {\n if (await this.exists(this.manifestPath)) {\n const content = await readFile(this.manifestPath, \"utf-8\");\n this.manifest = JSON.parse(content);\n return true;\n }\n } catch {\n // Manifest corrupted or missing\n }\n this.manifest = null;\n return false;\n }\n\n /**\n * Save manifest to disk\n */\n private async saveManifest(): Promise<void> {\n if (!this.manifest) {\n this.manifest = this.createEmptyManifest();\n }\n this.manifest.lastBuildTime = Date.now();\n\n await writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2), \"utf-8\");\n }\n\n /**\n * Create empty manifest structure\n */\n private createEmptyManifest(): TypingsManifest {\n return {\n version: \"1.0.0\",\n lastBuildTime: Date.now(),\n storage: null,\n config: {},\n };\n }\n\n // ============================================================\n // Type Extraction Methods\n // ============================================================\n\n /**\n * Extract BOTH type info AND keys in a single pass\n *\n * One parse serves both extractions. It used to be one whole TypeScript\n * Program per file — halved from two by an earlier pass, which optimised\n * inside a premise that did not need to hold. `read-config-ast.ts` has the\n * numbers.\n *\n * @param configPath Absolute path to the config file\n * @param configName Config name (e.g., \"auth\", \"notifications\")\n * @returns Combined result with type info and keys\n */\n private async extractConfigInfo(\n configPath: string,\n configName: string,\n ): Promise<{\n typeName: string | null;\n importSource: string | null;\n keys: string[];\n }> {\n if (!(await this.exists(configPath))) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // Parse, do not resolve — `read-config-ast.ts` explains why, with numbers.\n const sourceFile = await readConfigAst(configPath);\n\n if (!sourceFile) {\n return { typeName: null, importSource: null, keys: [] };\n }\n\n // === Type Info Extraction ===\n const importedTypes = new Map<string, string>();\n const localExportedTypes = new Set<string>();\n let foundTypeName: string | null = null;\n\n // === Keys Extraction ===\n const keys: string[] = [];\n\n const visitForTypes = (node: ts.Node): void => {\n // Collect imported types\n if (ts.isImportDeclaration(node)) {\n const moduleSpecifier = node.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const source = moduleSpecifier.text;\n const importClause = node.importClause;\n if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) {\n for (const element of importClause.namedBindings.elements) {\n importedTypes.set(element.name.text, source);\n }\n }\n }\n }\n\n // Collect locally exported types\n if (ts.isTypeAliasDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Collect locally exported interfaces\n if (ts.isInterfaceDeclaration(node)) {\n const modifiers = ts.getModifiers(node);\n if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {\n localExportedTypes.add(node.name.text);\n }\n }\n\n // Find type used on config variable + extract keys\n if (ts.isVariableDeclaration(node)) {\n // Type info\n if (node.type && ts.isTypeReferenceNode(node.type)) {\n foundTypeName = node.type.typeName.getText(sourceFile);\n }\n\n // Keys extraction\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const visitKeys = (objNode: ts.ObjectLiteralExpression, prefix: string): void => {\n for (const prop of objNode.properties) {\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const keyName = prop.name.getText(sourceFile);\n const fullKey = prefix ? `${prefix}.${keyName}` : keyName;\n keys.push(fullKey);\n if (ts.isObjectLiteralExpression(prop.initializer)) {\n visitKeys(prop.initializer, fullKey);\n }\n }\n }\n };\n visitKeys(node.initializer, configName);\n }\n }\n\n ts.forEachChild(node, visitForTypes);\n };\n\n ts.forEachChild(sourceFile, visitForTypes);\n\n // Resolve type info\n let typeName: string | null = null;\n let importSource: string | null = null;\n\n if (foundTypeName) {\n if (importedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n importSource = importedTypes.get(foundTypeName)!;\n } else if (localExportedTypes.has(foundTypeName)) {\n typeName = foundTypeName;\n const relativePath = Path.toRelative(configPath).replace(/\\.(ts|tsx)$/, \"\");\n importSource = `../../${relativePath}`;\n }\n }\n\n return { typeName, importSource, keys };\n }\n\n /**\n * Extract driver keys from storage config\n */\n private async extractStorageDriverKeys(configPath: string): Promise<string[]> {\n const absolutePath = resolve(configPath);\n\n if (!(await this.exists(absolutePath))) {\n devServeLog(`âš ï¸ Storage config not found: ${absolutePath}`);\n return [];\n }\n\n const sourceFile = await readConfigAst(absolutePath);\n\n if (!sourceFile) {\n devServeLog(`âš ï¸ Could not parse storage config: ${absolutePath}`);\n return [];\n }\n\n const keys: string[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node)) {\n const propName = node.name.getText(sourceFile);\n\n if (propName === \"drivers\" && ts.isObjectLiteralExpression(node.initializer)) {\n for (const prop of node.initializer.properties) {\n if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {\n const keyName = prop.name?.getText(sourceFile);\n\n if (keyName) {\n keys.push(keyName);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return keys;\n }\n\n /**\n * Find a config file by name\n */\n private async findConfigFile(configName: string): Promise<string | undefined> {\n const possiblePaths = [`src/config/${configName}.ts`, `config/${configName}.ts`];\n\n for (const path of possiblePaths) {\n const fullPath = join(process.cwd(), path);\n\n if (await this.exists(fullPath)) {\n return path;\n }\n }\n\n try {\n const files = filesOrchestrator.getFiles();\n\n for (const [filePath] of files) {\n if (filePath.includes(`config/${configName}`)) {\n return filePath;\n }\n }\n } catch {\n // Files orchestrator not initialized yet\n }\n\n return undefined;\n }\n\n /**\n * Ensure output directory exists\n */\n private async ensureOutputDir(): Promise<void> {\n await ensureDirectoryAsync(this.outputDir);\n }\n\n /**\n * Check if a path exists (async wrapper)\n */\n private async exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * The ports `runTypingsGeneration` needs, bound to this instance and this\n * package's dev logger.\n */\n private get typingsGenerationPorts(): TypingsGenerationPorts {\n return {\n generate: () => this.generateAll(),\n info: (message) => devLogInfo(message),\n success: (message) => devLogSuccess(message),\n error: (message) => devLogError(message),\n };\n }\n\n /**\n * Generate every config's typings.\n *\n * Runs IN THIS PROCESS. It used to spawn `npx warlock generate.typings`,\n * which could not resolve in a source checkout — see\n * `run-typings-generation.ts` for the whole story.\n */\n public async executeGenerateAllCommand(): Promise<void> {\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n\n /**\n * Regenerate typings after a batch reload — but only when the batch actually\n * touched a config file, since nothing else contributes to them.\n */\n public async executeTypingsGenerator(upcomingFiles: string[]): Promise<void> {\n const touchedAConfig = Array.from(new Set(upcomingFiles)).some((file) => {\n const normalizedPath = Path.normalize(file);\n return normalizedPath.includes(\"src/config/\") || this.isLocalesFile(normalizedPath);\n });\n\n if (!touchedAConfig) return;\n\n /*\n The changed paths are deliberately NOT passed along. The previous version\n built a `files` array here and then threw it away, spawning the same\n whole-project command as the branch above — so \"incremental\" was a name,\n not a behaviour. `generateAll()` reads the orchestrator, which the batch\n reload has already updated, so the full pass is both correct and the only\n pass that ever actually ran.\n */\n await runTypingsGeneration(this.typingsGenerationPorts);\n }\n}\n\n/**\n * Singleton instance for use throughout dev-server\n */\nexport const typeGenerator = new TypeGenerator();\n"],"mappings":"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"}
|
package/esm/encryption/index.mjs
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region ../core/src/errors/esbuild-binary-missing-error.ts
|
|
2
|
+
/**
|
|
3
|
+
* Raised when `warlock dev` / `warlock build` cannot use esbuild because its
|
|
4
|
+
* platform-native binary was never installed or linked.
|
|
5
|
+
*
|
|
6
|
+
* The message names what is missing and the exact fix — reinstall with the
|
|
7
|
+
* package manager without skipping install scripts — so the failure is
|
|
8
|
+
* actionable at the point it is thrown, instead of surfacing later as an
|
|
9
|
+
* opaque low-level error from deep inside the bundler.
|
|
10
|
+
*/
|
|
11
|
+
var EsbuildBinaryMissingError = class extends Error {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super("esbuild's native binary is not installed or linked for this platform, so `warlock dev` / `warlock build` cannot run.\n\nThis usually happens when a platform package such as `@esbuild/win32-x64` was never installed, or the package manager skipped esbuild's postinstall script that links it. Fix it with:\n\n pnpm approve-builds\n\nthen reinstall dependencies (do not skip install scripts), or reinstall with your package manager's equivalent of allowing build scripts.", options);
|
|
14
|
+
this.name = "EsbuildBinaryMissingError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { EsbuildBinaryMissingError };
|
|
20
|
+
//# sourceMappingURL=esbuild-binary-missing-error.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esbuild-binary-missing-error.mjs","names":[],"sources":["../../../../../../../core/src/errors/esbuild-binary-missing-error.ts"],"sourcesContent":["/**\n * Raised when `warlock dev` / `warlock build` cannot use esbuild because its\n * platform-native binary was never installed or linked.\n *\n * The message names what is missing and the exact fix — reinstall with the\n * package manager without skipping install scripts — so the failure is\n * actionable at the point it is thrown, instead of surfacing later as an\n * opaque low-level error from deep inside the bundler.\n */\nexport class EsbuildBinaryMissingError extends Error {\n public constructor(options?: { cause?: unknown }) {\n super(\n \"esbuild's native binary is not installed or linked for this platform, so \" +\n \"`warlock dev` / `warlock build` cannot run.\\n\\n\" +\n \"This usually happens when a platform package such as `@esbuild/win32-x64` \" +\n \"was never installed, or the package manager skipped esbuild's postinstall \" +\n \"script that links it. Fix it with:\\n\\n\" +\n \" pnpm approve-builds\\n\\n\" +\n \"then reinstall dependencies (do not skip install scripts), \" +\n \"or reinstall with your package manager's equivalent of allowing build scripts.\",\n options,\n );\n\n this.name = \"EsbuildBinaryMissingError\";\n }\n}\n"],"mappings":";;;;;;;;;;AASA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,SAA+B;EAChD,MACE,wdAQA,OACF;EAEA,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/generations/features/auth-google.feature.ts
|
|
4
|
+
/**
|
|
5
|
+
* `warlock add auth-google` — Google sign-in for @warlock.js/auth. `jose` verifies
|
|
6
|
+
* the id_token; auth loads it lazily, so it is only needed once this is added.
|
|
7
|
+
*/
|
|
8
|
+
const authGoogleFeature = {
|
|
9
|
+
description: "Google sign-in for @warlock.js/auth (installs jose for id_token verification). Configure auth.providers.google, then call startProviderLogin / completeProviderLogin",
|
|
10
|
+
dependencies: {
|
|
11
|
+
"@warlock.js/auth": INSTALLED_WARLOCK_VERSION,
|
|
12
|
+
jose: "^6.1.0"
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { authGoogleFeature };
|
|
18
|
+
//# sourceMappingURL=auth-google.feature.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-google.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/auth-google.feature.ts"],"sourcesContent":["import { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\n/**\n * `warlock add auth-google` — Google sign-in for @warlock.js/auth. `jose` verifies\n * the id_token; auth loads it lazily, so it is only needed once this is added.\n */\nexport const authGoogleFeature: FeatureDefinition = {\n description:\n \"Google sign-in for @warlock.js/auth (installs jose for id_token verification). Configure auth.providers.google, then call startProviderLogin / completeProviderLogin\",\n dependencies: {\n \"@warlock.js/auth\": INSTALLED_WARLOCK_VERSION,\n jose: \"^6.1.0\",\n },\n};\n"],"mappings":";;;;;;;AAMA,MAAa,oBAAuC;CAClD,aACE;CACF,cAAc;EACZ,oBAAoB;EACpB,MAAM;CACR;AACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { INSTALLED_WARLOCK_VERSION } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/generations/features/auth-passkeys.feature.ts
|
|
4
|
+
/**
|
|
5
|
+
* `warlock add auth-passkeys` — passkey (WebAuthn) login for @warlock.js/auth.
|
|
6
|
+
* Installs the server library only; the browser half is `@simplewebauthn/browser`,
|
|
7
|
+
* which belongs in whatever bundle runs the ceremony.
|
|
8
|
+
*/
|
|
9
|
+
const authPasskeysFeature = {
|
|
10
|
+
description: "Passkey login for @warlock.js/auth (installs @simplewebauthn/server; add @simplewebauthn/browser to your client). Configure auth.passkeys { rpID, rpName, origin }",
|
|
11
|
+
dependencies: {
|
|
12
|
+
"@warlock.js/auth": INSTALLED_WARLOCK_VERSION,
|
|
13
|
+
"@simplewebauthn/server": "^13.1.0"
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { authPasskeysFeature };
|
|
19
|
+
//# sourceMappingURL=auth-passkeys.feature.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-passkeys.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/auth-passkeys.feature.ts"],"sourcesContent":["import { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\n/**\n * `warlock add auth-passkeys` — passkey (WebAuthn) login for @warlock.js/auth.\n * Installs the server library only; the browser half is `@simplewebauthn/browser`,\n * which belongs in whatever bundle runs the ceremony.\n */\nexport const authPasskeysFeature: FeatureDefinition = {\n description:\n \"Passkey login for @warlock.js/auth (installs @simplewebauthn/server; add @simplewebauthn/browser to your client). Configure auth.passkeys { rpID, rpName, origin }\",\n dependencies: {\n \"@warlock.js/auth\": INSTALLED_WARLOCK_VERSION,\n \"@simplewebauthn/server\": \"^13.1.0\",\n },\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,sBAAyC;CACpD,aACE;CACF,cAAc;EACZ,oBAAoB;EACpB,0BAA0B;CAC5B;AACF"}
|