experimental-ash 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/src/chunks/authored-module-loader-DcCfCiBm.js +4 -0
  3. package/dist/src/chunks/client-DLHAGI2g.js +4 -0
  4. package/dist/src/chunks/dev-authored-source-watcher-CBID_Dwh.js +1 -0
  5. package/dist/src/chunks/{errors-DsO9xmQL.js → errors-HYWjHxV6.js} +1 -1
  6. package/dist/src/chunks/{gray-matter-BxS7ZG-J.js → gray-matter-D-9jHwOT.js} +4 -4
  7. package/dist/src/chunks/host-zBy9FyyX.js +70 -0
  8. package/dist/src/chunks/{jsdist-M7JFZoA4.js → jsdist-BkeWZ7EZ.js} +61 -61
  9. package/dist/src/chunks/package-HUaeub_D.js +1 -0
  10. package/dist/src/chunks/{paths-jcVjBqc3.js → paths-CebY5GCi.js} +2 -2
  11. package/dist/src/chunks/{picocolors-Bq-tZK9u.js → picocolors-aAkqW4On.js} +1 -1
  12. package/dist/src/chunks/token-D98SQdvs.js +1 -0
  13. package/dist/src/chunks/{url-BVRhVE2O.js → url-JdCGA634.js} +1 -1
  14. package/dist/src/cli/commands/info.js +1 -1
  15. package/dist/src/cli/dev/environment.d.ts +16 -2
  16. package/dist/src/cli/dev/environment.js +1 -1
  17. package/dist/src/cli/dev/input-requests.js +1 -1
  18. package/dist/src/cli/dev/repl.js +3 -3
  19. package/dist/src/cli/dev/url.js +1 -1
  20. package/dist/src/cli/run.js +1 -1
  21. package/dist/src/cli/ui/output.js +1 -1
  22. package/dist/src/client/index.d.ts +1 -0
  23. package/dist/src/client/index.js +1 -0
  24. package/dist/src/compiled/.vendor-stamp.json +7 -6
  25. package/dist/src/compiled/@ai-sdk/anthropic/index.js +1 -1
  26. package/dist/src/compiled/@ai-sdk/google/index.js +1 -1
  27. package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
  28. package/dist/src/compiled/@ai-sdk/openai/index.js +1 -1
  29. package/dist/src/compiled/@ai-sdk/otel/index.js +1 -1
  30. package/dist/src/compiled/@chat-adapter/slack/_slack-web-api.d.ts +7 -0
  31. package/dist/src/compiled/@chat-adapter/slack/index.d.ts +418 -109
  32. package/dist/src/compiled/@chat-adapter/slack/index.js +31 -27
  33. package/dist/src/compiled/@chat-adapter/slack/package.json +1 -1
  34. package/dist/src/compiled/@chat-adapter/state-memory/index.d.ts +8 -2
  35. package/dist/src/compiled/@chat-adapter/state-memory/index.js +1 -1
  36. package/dist/src/compiled/@chat-adapter/state-memory/package.json +1 -1
  37. package/dist/src/compiled/@opentelemetry/api/index.js +1 -1
  38. package/dist/src/compiled/@vercel/sandbox/index.js +19 -17
  39. package/dist/src/compiled/@vercel/sandbox/package.json +1 -1
  40. package/dist/src/compiled/@workflow/core/index.js +2 -2
  41. package/dist/src/compiled/@workflow/core/runtime.js +22 -22
  42. package/dist/src/compiled/@workflow/core/workflow.js +1 -1
  43. package/dist/src/compiled/@workflow/errors/index.js +1 -1
  44. package/dist/src/compiled/_chunks/node/auth-ZhCJAHxl.js +2 -0
  45. package/dist/src/compiled/_chunks/node/{dist-BdWHjlRQ.js → dist-BQYUcBqu.js} +31 -31
  46. package/dist/src/compiled/_chunks/node/{chunk-Dd2tEFlW.js → dist-BdTs18CF.js} +1 -1
  47. package/dist/src/compiled/_chunks/node/retry-DkR2H1Y0.js +1 -0
  48. package/dist/src/compiled/_chunks/node/token-CoIbMZkq.js +1 -0
  49. package/dist/src/compiled/_chunks/node/{version-BMyZn3Y2.js → version-D4IYmfaS.js} +1 -1
  50. package/dist/src/compiled/_chunks/workflow/coerce-BhzIW-Hm.js +1 -0
  51. package/dist/src/compiled/_chunks/workflow/{compat-CIROS3w4.js → compat-DcEvieoj.js} +1 -1
  52. package/dist/src/compiled/_chunks/workflow/{core-DOVmxHH8.js → core-XWIi7wKc.js} +3 -3
  53. package/dist/src/compiled/_chunks/workflow/{dist-C7wPwOI9.js → dist-DO14ZaQj.js} +1 -1
  54. package/dist/src/compiled/_chunks/workflow/{dist-CpUQh3NH.js → dist-DZZY3Zyp.js} +1 -1
  55. package/dist/src/compiled/_chunks/workflow/resume-hook-DOMbNs-S.js +51 -0
  56. package/dist/src/compiled/_chunks/workflow/{schemas-DFZoEyCn.js → schemas-DmgDnhW3.js} +1 -1
  57. package/dist/src/compiled/_chunks/workflow/sleep-CRjce49s.js +1 -0
  58. package/dist/src/compiled/_chunks/workflow/{src-ClRYdO4-.js → src-B54rYDvB.js} +1 -1
  59. package/dist/src/compiled/_chunks/workflow/symbols-D8paKc8P.js +9 -0
  60. package/dist/src/compiled/_chunks/workflow/{token-CsNmv7KW.js → token-D9z1dMB6.js} +1 -1
  61. package/dist/src/compiled/_chunks/workflow/{token-j5Cl4rrs.js → token-DV7rQw_t.js} +1 -1
  62. package/dist/src/compiled/chat/index.d.ts +6 -3143
  63. package/dist/src/compiled/chat/index.js +1 -1
  64. package/dist/src/compiled/chat/{jsx-runtime-DxGwoLu2.d.ts → jsx-runtime-CFq1K_Ve.d.ts} +1 -1
  65. package/dist/src/compiled/chat/package.json +1 -1
  66. package/dist/src/compiled/jose/index.js +2 -2
  67. package/dist/src/compiled/jsonc-parser/LICENSE.md +21 -0
  68. package/dist/src/compiled/jsonc-parser/index.d.ts +13 -0
  69. package/dist/src/compiled/jsonc-parser/index.js +15 -0
  70. package/dist/src/compiled/jsonc-parser/package.json +7 -0
  71. package/dist/src/compiled/just-bash/index.js +773 -773
  72. package/dist/src/compiled/just-bash/package.json +1 -1
  73. package/dist/src/compiled/turndown/index.js +1 -1
  74. package/dist/src/compiled/zod/index.js +1 -1
  75. package/dist/src/compiled/zod-validation-error/index.js +1 -1
  76. package/dist/src/evals/cli/eval.js +1 -1
  77. package/dist/src/evals/loaders/yaml.js +1 -1
  78. package/dist/src/evals/runner/discover.js +1 -1
  79. package/dist/src/evals/runner/execute-suite.js +1 -1
  80. package/dist/src/evals/runner/reporters/console.js +1 -1
  81. package/dist/src/evals/scorers/autoevals-client.js +1 -1
  82. package/dist/src/evals/scorers/autoevals.js +1 -1
  83. package/dist/src/evals/scorers/json.js +1 -1
  84. package/dist/src/evals/scorers/run.js +1 -1
  85. package/dist/src/evals/scorers/sql.js +1 -1
  86. package/dist/src/evals/scorers/text.js +1 -1
  87. package/dist/src/internal/application/package.js +1 -1
  88. package/dist/src/internal/authored-asset-import-plugin.d.ts +5 -0
  89. package/dist/src/internal/authored-asset-import-plugin.js +141 -0
  90. package/dist/src/internal/authored-module-bundle.d.ts +4 -0
  91. package/dist/src/internal/authored-module-bundle.js +19 -0
  92. package/dist/src/internal/authored-module-loader.d.ts +6 -0
  93. package/dist/src/internal/authored-module-loader.js +114 -125
  94. package/dist/src/internal/authored-package-tsconfig-paths.d.ts +9 -0
  95. package/dist/src/internal/authored-package-tsconfig-paths.js +209 -0
  96. package/dist/src/internal/nitro/host/channel-routes.d.ts +9 -2
  97. package/dist/src/internal/nitro/host/dev-authored-source-watcher.d.ts +12 -1
  98. package/dist/src/internal/nitro/host/dev-authored-source-watcher.js +19 -3
  99. package/dist/src/internal/nitro/host/schedule-task-routes.d.ts +7 -3
  100. package/dist/src/internal/nitro/host/start-development-server.js +2 -0
  101. package/package.json +17 -17
  102. package/dist/src/chunks/authored-module-loader-XcFLnl49.js +0 -2
  103. package/dist/src/chunks/client-nshDsWNF.js +0 -4
  104. package/dist/src/chunks/dev-authored-source-watcher-CApkYfee.js +0 -7
  105. package/dist/src/chunks/host-3sfHdmXL.js +0 -65
  106. package/dist/src/chunks/package-DmsQgn4v.js +0 -1
  107. package/dist/src/chunks/prewarm-DslujbK5.js +0 -6
  108. package/dist/src/chunks/token-DtoyQZy2.js +0 -1
  109. package/dist/src/chunks/token-util-CHjOk3A7.js +0 -1
  110. package/dist/src/compiled/_chunks/node/auth-vbe4XEEK.js +0 -2
  111. package/dist/src/compiled/_chunks/node/dist-DMSq2ehP.js +0 -1
  112. package/dist/src/compiled/_chunks/node/retry-BOcy5BbJ.js +0 -1
  113. package/dist/src/compiled/_chunks/node/token-D-BTJHoU.js +0 -1
  114. package/dist/src/compiled/_chunks/workflow/coerce-Dh0wa7P9.js +0 -1
  115. package/dist/src/compiled/_chunks/workflow/context-errors-Bbvvp-li.js +0 -6
  116. package/dist/src/compiled/_chunks/workflow/dist-C_oiE-l7.js +0 -40
  117. package/dist/src/compiled/_chunks/workflow/resume-hook-C3VWUPii.js +0 -12
  118. package/dist/src/compiled/_chunks/workflow/sleep-QTkC1VFe.js +0 -1
  119. package/dist/src/compiled/_chunks/workflow/symbols-QezhMuLg.js +0 -4
  120. package/dist/src/compiled/chat/_workflow-serde.d.ts +0 -5
  121. /package/dist/src/chunks/{chunk-8L7ocgPr.js → chunk-DSjMdhoD.js} +0 -0
  122. /package/dist/src/chunks/{guards-CjJ3lmju.js → guards-26p6sOw3.js} +0 -0
  123. /package/dist/src/chunks/{input-requests-BsBi7_5K.js → input-requests-DJiy6dG9.js} +0 -0
  124. /package/dist/src/chunks/{runtime-model-BWu6M_hq.js → runtime-model-Dh0Nz64z.js} +0 -0
  125. /package/dist/src/chunks/{types-MZUhN0Zy.js → types-DDA2QUED.js} +0 -0
  126. /package/dist/src/compiled/_chunks/workflow/{chunk-DHhdAPOb.js → chunk-DSjMdhoD.js} +0 -0
@@ -1,6 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve, sep } from "node:path";
4
+ import { createAuthoredAssetImportPlugin } from "#internal/authored-asset-import-plugin.js";
5
+ import { createAuthoredModuleBundleError } from "#internal/authored-module-bundle.js";
6
+ import { createAuthoredPackageTsConfigPathsPlugin } from "#internal/authored-package-tsconfig-paths.js";
4
7
  import { expectObjectRecord } from "#internal/authored-module.js";
5
8
  import { buildWithNitroRolldown, getSingleRolldownChunk, } from "#internal/bundler/nitro-rolldown.js";
6
9
  import { createNodeEsmCompatBannerPlugin } from "#internal/node-esm-compat-banner.js";
@@ -17,22 +20,42 @@ const RESOLVE_EXTENSIONS = [
17
20
  ".cjs",
18
21
  ".json",
19
22
  ];
23
+ const CHANNEL_MODULE_CACHE_KEY = "__ashChannelModuleCache__";
24
+ const CACHED_CHANNEL_PREFIX = "ash-cached-channel:";
25
+ /**
26
+ * Registers a loaded channel module in the global cache so Rolldown-
27
+ * bundled schedule modules that import the same channel get the exact
28
+ * same route object instance.
29
+ */
30
+ export function registerChannelModuleInCache(absolutePath, moduleDefault) {
31
+ const cache = (globalThis[CHANNEL_MODULE_CACHE_KEY] ??= new Map());
32
+ cache.set(resolve(absolutePath), moduleDefault);
33
+ }
34
+ function getChannelModuleCache() {
35
+ return globalThis[CHANNEL_MODULE_CACHE_KEY];
36
+ }
20
37
  /**
21
38
  * In-flight load deduplication map keyed by the absolute module path.
22
39
  *
23
- * The compiler walks every authored slot concurrently via `Promise.all`,
24
- * so the same module path is frequently loaded twice in parallel.
25
- * Without dedup, both callers race the bundler write/import pipeline
26
- * against the same `node_modules/.cache/.../<hash>.mjs` file: one
27
- * call's `writeFile` can truncate the bundle while another's `import()`
28
- * is still resolving it, surfacing as intermittent
40
+ * The compiler walks every authored slot concurrently
41
+ * (`compileChannelDefinition` and `buildChannelRouteIdentityMap` both
42
+ * load the same channel module via `Promise.all`), so the same module
43
+ * path is frequently loaded twice in parallel. Without dedup, both
44
+ * callers race the bundler write/import pipeline against the
45
+ * same `node_modules/.cache/.../<hash>.mjs` file: one call's
46
+ * `writeFile` can truncate the bundle while another's `import()` is
47
+ * still resolving it, surfacing as intermittent
29
48
  * "Expected … to match the public Ash shape" failures during
30
49
  * compilation.
31
50
  *
32
51
  * The map only holds in-flight promises; once a load settles the entry
33
- * is cleared so subsequent compiles re-run the bundle pipeline against
34
- * the fresh source. Node's ESM cache then dedupes by content-hashed
35
- * URL for unchanged files.
52
+ * is cleared so subsequent compiles (e.g. a dev-server reload after
53
+ * the author edits a file) re-run the bundle pipeline against the
54
+ * fresh source. Node's ESM cache then dedupes by content-hashed URL for
55
+ * unchanged files. The companion "skip write when the cache file already
56
+ * exists" check inside {@link loadBundledAuthoredModule} eliminates the
57
+ * write/read race even when two non-concurrent compile passes overlap on
58
+ * the same hashed bundle path.
36
59
  */
37
60
  const inFlightModuleLoads = new Map();
38
61
  /**
@@ -74,33 +97,77 @@ function createFileImportSpecifier(modulePath) {
74
97
  return normalizedPath;
75
98
  }
76
99
  async function loadBundledAuthoredModule(modulePath) {
100
+ const channelCache = getChannelModuleCache();
77
101
  const packageRoot = resolveAuthoredPackageRoot(modulePath);
78
- const tsconfigPath = join(packageRoot, "tsconfig.json");
79
- const tsconfigPathAliasPlugin = createTsconfigPathAliasPlugin({
80
- tsconfigPath,
81
- });
102
+ const tsconfigPath = resolveAuthoredTsConfigPath(packageRoot);
103
+ const channelIdentityPlugin = channelCache && channelCache.size > 0
104
+ ? {
105
+ name: "ash-channel-identity",
106
+ async resolveId(source, importer, options) {
107
+ if (!/channels[/\\]/.test(source) || options.kind !== "import-statement") {
108
+ return undefined;
109
+ }
110
+ const resolved = await this.resolve(source, importer, {
111
+ kind: options.kind,
112
+ skipSelf: true,
113
+ });
114
+ if (resolved === null || typeof resolved.id !== "string") {
115
+ return undefined;
116
+ }
117
+ const resolvedPath = resolve(resolved.id);
118
+ if (!channelCache.has(resolvedPath)) {
119
+ return undefined;
120
+ }
121
+ return { id: `${CACHED_CHANNEL_PREFIX}${resolvedPath}` };
122
+ },
123
+ load(id) {
124
+ if (!id.startsWith(CACHED_CHANNEL_PREFIX)) {
125
+ return undefined;
126
+ }
127
+ const cachedPath = id.slice(CACHED_CHANNEL_PREFIX.length);
128
+ return {
129
+ code: [
130
+ `const cache = globalThis["${CHANNEL_MODULE_CACHE_KEY}"];`,
131
+ `export default cache.get(${JSON.stringify(cachedPath)});`,
132
+ ].join("\n"),
133
+ moduleType: "js",
134
+ };
135
+ },
136
+ }
137
+ : null;
82
138
  const plugins = [
139
+ createAuthoredAssetImportPlugin(),
140
+ createAuthoredPackageTsConfigPathsPlugin({
141
+ appPackageRoot: packageRoot,
142
+ extensions: RESOLVE_EXTENSIONS,
143
+ }),
83
144
  createNodeEsmCompatBannerPlugin({ includeRequire: true }),
84
145
  createPackageBoundaryPlugin(packageRoot),
85
- tsconfigPathAliasPlugin,
146
+ channelIdentityPlugin,
86
147
  ].filter((plugin) => plugin !== null);
87
- const result = await buildWithNitroRolldown({
88
- cwd: packageRoot,
89
- input: modulePath,
90
- platform: "node",
91
- plugins,
92
- resolve: {
93
- extensions: [...RESOLVE_EXTENSIONS],
94
- },
95
- tsconfig: existsSync(tsconfigPath) ? tsconfigPath : false,
96
- write: false,
97
- output: {
98
- comments: false,
99
- format: "esm",
100
- sourcemap: "inline",
101
- },
102
- });
103
- const outputFile = getSingleRolldownChunk(result, `authored module for "${modulePath}"`);
148
+ let outputFile;
149
+ try {
150
+ const result = await buildWithNitroRolldown({
151
+ cwd: packageRoot,
152
+ input: modulePath,
153
+ platform: "node",
154
+ plugins,
155
+ resolve: {
156
+ extensions: [...RESOLVE_EXTENSIONS],
157
+ },
158
+ tsconfig: tsconfigPath,
159
+ write: false,
160
+ output: {
161
+ comments: false,
162
+ format: "esm",
163
+ sourcemap: "inline",
164
+ },
165
+ });
166
+ outputFile = getSingleRolldownChunk(result, `authored module for "${modulePath}"`);
167
+ }
168
+ catch (error) {
169
+ throw createAuthoredModuleBundleError(modulePath, error);
170
+ }
104
171
  const bundleHash = createHash("sha1")
105
172
  .update(modulePath)
106
173
  .update("\0")
@@ -108,12 +175,6 @@ async function loadBundledAuthoredModule(modulePath) {
108
175
  .digest("hex");
109
176
  const bundleDirectoryPath = join(packageRoot, AUTHORED_MODULE_BUNDLE_DIRECTORY_PATH);
110
177
  const bundlePath = join(bundleDirectoryPath, `${bundleHash}.mjs`);
111
- // The bundle filename is the content hash, so if the file already
112
- // exists it must already hold the correct bytes. Skipping the write
113
- // avoids re-truncating the file underneath any in-flight `import()`
114
- // that's still reading the previous compile's bytes. Sync I/O keeps
115
- // the writer atomic from JS's perspective: nothing else on this
116
- // event loop can touch the file between mkdir and writeFile.
117
178
  if (!existsSync(bundlePath)) {
118
179
  mkdirSync(bundleDirectoryPath, { recursive: true });
119
180
  writeFileSync(bundlePath, outputFile.code);
@@ -133,7 +194,11 @@ function createPackageBoundaryPlugin(packageRoot) {
133
194
  id: source,
134
195
  };
135
196
  }
136
- const importerPath = importer === undefined || importer.startsWith("\0") ? undefined : resolve(importer);
197
+ const importerPath = importer === undefined ||
198
+ importer.startsWith("\0") ||
199
+ importer.startsWith(CACHED_CHANNEL_PREFIX)
200
+ ? undefined
201
+ : resolve(importer);
137
202
  // Keep package imports authored directly by the app external by
138
203
  // default, but let symlinked/file workspace packages compile as
139
204
  // source. Those packages often export `.ts` files and rely on the
@@ -160,91 +225,6 @@ function createPackageBoundaryPlugin(packageRoot) {
160
225
  },
161
226
  };
162
227
  }
163
- function createTsconfigPathAliasPlugin(input) {
164
- const aliases = readTsconfigPathAliases(input);
165
- if (aliases.length === 0) {
166
- return null;
167
- }
168
- return {
169
- name: "ash-tsconfig-path-alias",
170
- resolveId(source) {
171
- for (const alias of aliases) {
172
- const matched = matchTsconfigPathAlias(alias.pattern, source);
173
- if (matched === null) {
174
- continue;
175
- }
176
- for (const target of alias.targets) {
177
- const candidate = resolveTsconfigPathAliasTarget(target, matched);
178
- if (candidate !== null) {
179
- return candidate;
180
- }
181
- }
182
- }
183
- return undefined;
184
- },
185
- };
186
- }
187
- function readTsconfigPathAliases(input) {
188
- if (!existsSync(input.tsconfigPath)) {
189
- return [];
190
- }
191
- try {
192
- const tsconfig = JSON.parse(readFileSync(input.tsconfigPath, "utf8"));
193
- const paths = tsconfig.compilerOptions?.paths;
194
- if (paths === null || typeof paths !== "object" || Array.isArray(paths)) {
195
- return [];
196
- }
197
- const baseUrl = typeof tsconfig.compilerOptions?.baseUrl === "string"
198
- ? tsconfig.compilerOptions.baseUrl
199
- : ".";
200
- const baseDirectory = resolve(dirname(input.tsconfigPath), baseUrl);
201
- return Object.entries(paths).flatMap(([pattern, rawTargets]) => {
202
- if (!Array.isArray(rawTargets)) {
203
- return [];
204
- }
205
- const targets = rawTargets.flatMap((rawTarget) => typeof rawTarget === "string" ? [resolve(baseDirectory, rawTarget)] : []);
206
- return targets.length === 0 ? [] : [{ pattern, targets }];
207
- });
208
- }
209
- catch {
210
- return [];
211
- }
212
- }
213
- function matchTsconfigPathAlias(pattern, source) {
214
- const wildcardIndex = pattern.indexOf("*");
215
- if (wildcardIndex === -1) {
216
- return pattern === source ? "" : null;
217
- }
218
- const prefix = pattern.slice(0, wildcardIndex);
219
- const suffix = pattern.slice(wildcardIndex + 1);
220
- if (!source.startsWith(prefix) || !source.endsWith(suffix)) {
221
- return null;
222
- }
223
- return source.slice(prefix.length, source.length - suffix.length);
224
- }
225
- function resolveTsconfigPathAliasTarget(targetPattern, matched) {
226
- const targetPath = targetPattern.replace("*", matched);
227
- const directMatch = resolveExistingPath(targetPath);
228
- if (directMatch !== null) {
229
- return directMatch;
230
- }
231
- for (const extension of RESOLVE_EXTENSIONS) {
232
- const extensionMatch = resolveExistingPath(`${targetPath}${extension}`);
233
- if (extensionMatch !== null) {
234
- return extensionMatch;
235
- }
236
- }
237
- for (const extension of RESOLVE_EXTENSIONS) {
238
- const indexMatch = resolveExistingPath(join(targetPath, `index${extension}`));
239
- if (indexMatch !== null) {
240
- return indexMatch;
241
- }
242
- }
243
- return null;
244
- }
245
- function resolveExistingPath(path) {
246
- return existsSync(path) ? path : null;
247
- }
248
228
  function isPackageImport(source) {
249
229
  if (source.startsWith(".") || source.startsWith("/") || /^[A-Za-z]:[\\/]/.test(source)) {
250
230
  return false;
@@ -255,7 +235,7 @@ function isPackageImport(source) {
255
235
  if (source.startsWith("@/")) {
256
236
  return false;
257
237
  }
258
- return true;
238
+ return !source.startsWith(CACHED_CHANNEL_PREFIX);
259
239
  }
260
240
  function isAshFrameworkImport(source) {
261
241
  return source === "experimental-ash" || source.startsWith("experimental-ash/");
@@ -268,6 +248,15 @@ function isPathInsideOrEqual(path, directory) {
268
248
  const resolvedDirectory = resolve(directory);
269
249
  return (resolvedPath === resolvedDirectory || resolvedPath.startsWith(`${resolvedDirectory}${sep}`));
270
250
  }
251
+ function resolveAuthoredTsConfigPath(packageRoot) {
252
+ for (const fileName of ["tsconfig.json", "jsconfig.json"]) {
253
+ const path = join(packageRoot, fileName);
254
+ if (existsSync(path)) {
255
+ return path;
256
+ }
257
+ }
258
+ return false;
259
+ }
271
260
  function resolveAuthoredPackageRoot(modulePath) {
272
261
  let currentDirectory = dirname(modulePath);
273
262
  while (true) {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolves tsconfig `paths` for authored source imported from linked
3
+ * workspace packages. Rolldown handles the root app tsconfig; this only
4
+ * fills the package-local config gap for sources outside the app root.
5
+ */
6
+ export declare function createAuthoredPackageTsConfigPathsPlugin(input: {
7
+ appPackageRoot: string;
8
+ extensions: readonly string[];
9
+ }): Record<string, unknown>;
@@ -0,0 +1,209 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { dirname, join, resolve, sep } from "node:path";
3
+ import { parse as parseJsonc } from "#compiled/jsonc-parser/index.js";
4
+ const packageTsConfigPathsCache = new Map();
5
+ const nearestPackageRootCache = new Map();
6
+ /**
7
+ * Resolves tsconfig `paths` for authored source imported from linked
8
+ * workspace packages. Rolldown handles the root app tsconfig; this only
9
+ * fills the package-local config gap for sources outside the app root.
10
+ */
11
+ export function createAuthoredPackageTsConfigPathsPlugin(input) {
12
+ return {
13
+ name: "ash-package-tsconfig-paths",
14
+ async resolveId(source, importer, options) {
15
+ if (importer === undefined || !isPackageImport(source)) {
16
+ return undefined;
17
+ }
18
+ const importerPath = resolve(importer);
19
+ if (isPathInsideOrEqual(importerPath, input.appPackageRoot)) {
20
+ return undefined;
21
+ }
22
+ const packageRoot = await resolveNearestPackageRoot(importerPath);
23
+ if (packageRoot === undefined || isPathInsideOrEqual(packageRoot, input.appPackageRoot)) {
24
+ return undefined;
25
+ }
26
+ const config = await loadPackageTsConfigPaths(packageRoot);
27
+ if (config === undefined) {
28
+ return undefined;
29
+ }
30
+ for (const candidate of createTsConfigPathCandidates(source, config)) {
31
+ const resolved = await this.resolve(candidate, importer, {
32
+ kind: options.kind,
33
+ skipSelf: true,
34
+ });
35
+ if (resolved !== null) {
36
+ return resolved;
37
+ }
38
+ const existingPath = await resolveExistingPath(candidate, input.extensions);
39
+ if (existingPath !== undefined) {
40
+ return { id: existingPath };
41
+ }
42
+ }
43
+ return undefined;
44
+ },
45
+ };
46
+ }
47
+ function loadPackageTsConfigPaths(packageRoot) {
48
+ const cached = packageTsConfigPathsCache.get(packageRoot);
49
+ if (cached !== undefined) {
50
+ return cached;
51
+ }
52
+ const promise = readPackageTsConfigPaths(packageRoot);
53
+ packageTsConfigPathsCache.set(packageRoot, promise);
54
+ return promise;
55
+ }
56
+ async function readPackageTsConfigPaths(packageRoot) {
57
+ const configFile = await readPackageConfig(packageRoot);
58
+ if (configFile === undefined) {
59
+ return undefined;
60
+ }
61
+ const errors = [];
62
+ const parsed = parseJsonc(configFile.source, errors, {
63
+ allowTrailingComma: true,
64
+ });
65
+ if (errors.length > 0 || !isObjectRecord(parsed)) {
66
+ return undefined;
67
+ }
68
+ const compilerOptions = parsed.compilerOptions;
69
+ if (!isObjectRecord(compilerOptions) || !isObjectRecord(compilerOptions.paths)) {
70
+ return undefined;
71
+ }
72
+ const configDirectory = dirname(configFile.path);
73
+ const baseDirectory = typeof compilerOptions.baseUrl === "string"
74
+ ? resolve(configDirectory, compilerOptions.baseUrl)
75
+ : configDirectory;
76
+ const entries = Object.entries(compilerOptions.paths)
77
+ .flatMap(([pattern, targets]) => {
78
+ if (!Array.isArray(targets) || !targets.every((target) => typeof target === "string")) {
79
+ return [];
80
+ }
81
+ return [{ pattern, targets }];
82
+ })
83
+ .sort((left, right) => right.pattern.length - left.pattern.length);
84
+ if (entries.length === 0) {
85
+ return undefined;
86
+ }
87
+ return { baseDirectory, entries };
88
+ }
89
+ async function readPackageConfig(packageRoot) {
90
+ for (const fileName of ["tsconfig.json", "jsconfig.json"]) {
91
+ const configPath = join(packageRoot, fileName);
92
+ try {
93
+ return {
94
+ path: configPath,
95
+ source: await readFile(configPath, "utf8"),
96
+ };
97
+ }
98
+ catch (error) {
99
+ if (!isPathNotFoundError(error)) {
100
+ throw error;
101
+ }
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ function createTsConfigPathCandidates(source, config) {
107
+ const candidates = [];
108
+ for (const entry of config.entries) {
109
+ const match = matchTsConfigPathPattern(source, entry.pattern);
110
+ if (match === undefined) {
111
+ continue;
112
+ }
113
+ for (const target of entry.targets) {
114
+ const targetPath = target.includes("*") ? target.replaceAll("*", match) : target;
115
+ candidates.push(resolve(config.baseDirectory, targetPath));
116
+ }
117
+ }
118
+ return candidates;
119
+ }
120
+ function matchTsConfigPathPattern(source, pattern) {
121
+ const wildcardIndex = pattern.indexOf("*");
122
+ if (wildcardIndex === -1) {
123
+ return source === pattern ? "" : undefined;
124
+ }
125
+ const prefix = pattern.slice(0, wildcardIndex);
126
+ const suffix = pattern.slice(wildcardIndex + 1);
127
+ if (!source.startsWith(prefix) || !source.endsWith(suffix)) {
128
+ return undefined;
129
+ }
130
+ return source.slice(prefix.length, source.length - suffix.length);
131
+ }
132
+ async function resolveExistingPath(path, extensions) {
133
+ if (await existsAsFile(path)) {
134
+ return path;
135
+ }
136
+ for (const extension of extensions) {
137
+ const candidate = `${path}${extension}`;
138
+ if (await existsAsFile(candidate)) {
139
+ return candidate;
140
+ }
141
+ }
142
+ for (const extension of extensions) {
143
+ const candidate = join(path, `index${extension}`);
144
+ if (await existsAsFile(candidate)) {
145
+ return candidate;
146
+ }
147
+ }
148
+ return undefined;
149
+ }
150
+ async function existsAsFile(path) {
151
+ try {
152
+ return (await stat(path)).isFile();
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ function resolveNearestPackageRoot(path) {
159
+ const startDirectory = dirname(path);
160
+ const cached = nearestPackageRootCache.get(startDirectory);
161
+ if (cached !== undefined) {
162
+ return cached;
163
+ }
164
+ const promise = findNearestPackageRoot(startDirectory);
165
+ nearestPackageRootCache.set(startDirectory, promise);
166
+ return promise;
167
+ }
168
+ async function findNearestPackageRoot(startDirectory) {
169
+ let currentDirectory = startDirectory;
170
+ while (true) {
171
+ if (await pathExists(join(currentDirectory, "package.json"))) {
172
+ return currentDirectory;
173
+ }
174
+ const parentDirectory = dirname(currentDirectory);
175
+ if (parentDirectory === currentDirectory) {
176
+ return undefined;
177
+ }
178
+ currentDirectory = parentDirectory;
179
+ }
180
+ }
181
+ async function pathExists(path) {
182
+ try {
183
+ await stat(path);
184
+ return true;
185
+ }
186
+ catch {
187
+ return false;
188
+ }
189
+ }
190
+ function isPathNotFoundError(error) {
191
+ return (typeof error === "object" &&
192
+ error !== null &&
193
+ "code" in error &&
194
+ (error.code === "ENOENT" || error.code === "ENOTDIR"));
195
+ }
196
+ function isPackageImport(source) {
197
+ if (source.startsWith(".") || source.startsWith("/") || /^[A-Za-z]:[\\/]/.test(source)) {
198
+ return false;
199
+ }
200
+ return !/^(?:node|data|file):/.test(source);
201
+ }
202
+ function isObjectRecord(value) {
203
+ return typeof value === "object" && value !== null && !Array.isArray(value);
204
+ }
205
+ function isPathInsideOrEqual(path, directory) {
206
+ const resolvedPath = resolve(path);
207
+ const resolvedDirectory = resolve(directory);
208
+ return (resolvedPath === resolvedDirectory || resolvedPath.startsWith(`${resolvedDirectory}${sep}`));
209
+ }
@@ -2,6 +2,12 @@ import type { Nitro } from "nitro/types";
2
2
  import type { ChannelMethod } from "#public/definitions/channel.js";
3
3
  import type { NitroArtifactsConfigInput } from "#internal/nitro/host/artifacts-config.js";
4
4
  import type { PreparedApplicationHost } from "#internal/nitro/host/types.js";
5
+ interface ChannelRouteNitro {
6
+ options: Pick<Nitro["options"], "handlers" | "virtual">;
7
+ routing: {
8
+ sync(): void;
9
+ };
10
+ }
5
11
  /**
6
12
  * One Nitro route registration for an Ash channel.
7
13
  */
@@ -16,7 +22,7 @@ export declare function computeChannelRouteRegistrations(preparedHost: PreparedA
16
22
  /**
17
23
  * Registers virtual Nitro handlers for the provided Ash channel routes.
18
24
  */
19
- export declare function registerChannelVirtualHandlers(nitro: Nitro, input: {
25
+ export declare function registerChannelVirtualHandlers(nitro: Pick<ChannelRouteNitro, "options">, input: {
20
26
  readonly artifactsConfig: NitroArtifactsConfigInput;
21
27
  readonly registrations: readonly NitroChannelRouteRegistration[];
22
28
  }): void;
@@ -24,8 +30,9 @@ export declare function registerChannelVirtualHandlers(nitro: Nitro, input: {
24
30
  * Replaces the currently-mounted Ash channel virtual handlers when the route
25
31
  * set changes.
26
32
  */
27
- export declare function syncChannelVirtualHandlers(nitro: Nitro, input: {
33
+ export declare function syncChannelVirtualHandlers(nitro: ChannelRouteNitro, input: {
28
34
  readonly artifactsConfig: NitroArtifactsConfigInput;
29
35
  readonly next: readonly NitroChannelRouteRegistration[];
30
36
  readonly previous: readonly NitroChannelRouteRegistration[];
31
37
  }): boolean;
38
+ export {};
@@ -1,5 +1,15 @@
1
1
  import type { Nitro } from "nitro/types";
2
2
  import type { PreparedApplicationHost } from "#internal/nitro/host/types.js";
3
+ type DevelopmentWatcherNitroOptions = Pick<Nitro["options"], "experimental" | "handlers" | "scheduledTasks" | "tasks" | "virtual"> & Partial<Pick<Nitro["options"], "dev" | "preset">>;
4
+ interface DevelopmentWatcherNitro {
5
+ hooks: {
6
+ callHook: Nitro["hooks"]["callHook"];
7
+ };
8
+ options: DevelopmentWatcherNitroOptions;
9
+ routing: {
10
+ sync(): void;
11
+ };
12
+ }
3
13
  /**
4
14
  * Handle for the authored-source development watcher.
5
15
  */
@@ -13,7 +23,8 @@ export interface AuthoredSourceWatcherHandle {
13
23
  * triggers Nitro rebuild reloads only when structural runtime wiring changes.
14
24
  */
15
25
  export declare function startAuthoredSourceWatcher(input: {
16
- nitro: Nitro;
26
+ nitro: DevelopmentWatcherNitro;
17
27
  preparedHost: PreparedApplicationHost;
18
28
  schedulesEnabled?: boolean;
19
29
  }): Promise<AuthoredSourceWatcherHandle>;
30
+ export {};
@@ -3,7 +3,7 @@ import { readdir, readFile } from "node:fs/promises";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { watch } from "chokidar";
6
- import { parse as parseJsonc } from "jsonc-parser";
6
+ import { parse as parseJsonc } from "#compiled/jsonc-parser/index.js";
7
7
  import { clearCompiledRuntimeAgentBundleCache } from "#runtime/sessions/compiled-agent-cache.js";
8
8
  import { toErrorMessage } from "#shared/errors.js";
9
9
  import { resolvePackageSourceFilePath } from "#internal/application/package.js";
@@ -12,6 +12,7 @@ import { computeChannelRouteRegistrations, syncChannelVirtualHandlers, } from "#
12
12
  import { prepareApplicationHost } from "#internal/nitro/host/prepare-application-host.js";
13
13
  import { prewarmAppSandboxes } from "#execution/sandbox/prewarm.js";
14
14
  import { syncScheduleTaskHandlers } from "#internal/nitro/host/schedule-task-routes.js";
15
+ import { getDevelopmentEnvironmentFilePaths, reloadDevelopmentEnvironmentFiles, } from "#cli/dev/environment.js";
15
16
  const DEBOUNCE_MS = 120;
16
17
  const WATCHED_LOCKFILE_NAMES = [
17
18
  "pnpm-lock.yaml",
@@ -44,6 +45,7 @@ export async function startAuthoredSourceWatcher(input) {
44
45
  let queue = Promise.resolve();
45
46
  let debounceTimer;
46
47
  const pendingEvents = new Set();
48
+ const pendingChangedPaths = new Set();
47
49
  const initialWatchPaths = await resolveAuthoredWatchPaths(currentHost);
48
50
  let currentWatchPathsByKey = createWatchPathMap(initialWatchPaths);
49
51
  const watcher = watch(initialWatchPaths, {
@@ -66,10 +68,16 @@ export async function startAuthoredSourceWatcher(input) {
66
68
  return;
67
69
  }
68
70
  const eventSummary = pendingEvents.size;
71
+ const changedPaths = [...pendingChangedPaths];
69
72
  pendingEvents.clear();
73
+ pendingChangedPaths.clear();
70
74
  const previousHost = currentHost;
75
+ const hasEnvironmentChange = hasDevelopmentEnvironmentFileChange(previousHost.appRoot, changedPaths);
71
76
  console.log(`[ash:dev] change detected (${eventSummary} event${eventSummary === 1 ? "" : "s"}), rebuilding authored artifacts...`);
72
77
  try {
78
+ if (hasEnvironmentChange) {
79
+ reloadDevelopmentEnvironmentFiles(previousHost.appRoot);
80
+ }
73
81
  const nextHost = await prepareApplicationHost(previousHost.appRoot);
74
82
  await prewarmAppSandboxes({
75
83
  appRoot: nextHost.appRoot,
@@ -77,7 +85,7 @@ export async function startAuthoredSourceWatcher(input) {
77
85
  });
78
86
  const artifactsConfig = createNitroArtifactsConfig({
79
87
  appRoot: nextHost.appRoot,
80
- dev: input.nitro.options.dev,
88
+ dev: input.nitro.options.dev === true,
81
89
  });
82
90
  const hasChannelRouteChanged = syncChannelVirtualHandlers(input.nitro, {
83
91
  artifactsConfig,
@@ -92,7 +100,7 @@ export async function startAuthoredSourceWatcher(input) {
92
100
  });
93
101
  clearCompiledRuntimeAgentBundleCache();
94
102
  currentHost = nextHost;
95
- const hasStructuralChange = hasChannelRouteChanged || hasScheduleRegistrationsChanged;
103
+ const hasStructuralChange = hasChannelRouteChanged || hasScheduleRegistrationsChanged || hasEnvironmentChange;
96
104
  if (hasStructuralChange) {
97
105
  console.log("[ash:dev] structural change detected, reloading Nitro worker...");
98
106
  await input.nitro.hooks.callHook("rollup:reload");
@@ -120,6 +128,7 @@ export async function startAuthoredSourceWatcher(input) {
120
128
  return;
121
129
  }
122
130
  pendingEvents.add(`${event}:${changedPath}`);
131
+ pendingChangedPaths.add(changedPath);
123
132
  if (debounceTimer !== undefined) {
124
133
  clearTimeout(debounceTimer);
125
134
  }
@@ -160,6 +169,9 @@ async function resolveAuthoredWatchPaths(host) {
160
169
  join(host.appRoot, TS_CONFIG_GLOB_NAME),
161
170
  ]);
162
171
  const tsconfigPaths = await resolveTsConfigWatchPaths(host.appRoot);
172
+ for (const envFilePath of getDevelopmentEnvironmentFilePaths(host.appRoot)) {
173
+ watchPaths.add(envFilePath);
174
+ }
163
175
  for (const path of tsconfigPaths) {
164
176
  watchPaths.add(path);
165
177
  }
@@ -202,6 +214,10 @@ function syncWatcherPaths(input) {
202
214
  function toWatchPathKey(path) {
203
215
  return path.replaceAll("\\", "/");
204
216
  }
217
+ function hasDevelopmentEnvironmentFileChange(appRoot, changedPaths) {
218
+ const environmentFilePathKeys = new Set(getDevelopmentEnvironmentFilePaths(appRoot).map((path) => toWatchPathKey(resolve(path))));
219
+ return changedPaths.some((path) => environmentFilePathKeys.has(toWatchPathKey(resolve(path))));
220
+ }
205
221
  function resolveLockfileSearchDirectories(appRoot) {
206
222
  const directories = [];
207
223
  let currentDirectory = resolve(appRoot);