veryfront 0.1.95 → 0.1.97

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 (76) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/html/schemas/html.schema.d.ts +2 -2
  3. package/esm/src/jobs/runtime-env.d.ts +5 -0
  4. package/esm/src/jobs/runtime-env.d.ts.map +1 -0
  5. package/esm/src/jobs/runtime-env.js +101 -0
  6. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  7. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +32 -1
  8. package/esm/src/modules/react-loader/ssr-module-loader/ssr-cache-manager.d.ts.map +1 -1
  9. package/esm/src/modules/react-loader/ssr-module-loader/ssr-cache-manager.js +31 -2
  10. package/esm/src/modules/react-loader/ssr-module-loader/vf-module-resolver.d.ts.map +1 -1
  11. package/esm/src/modules/react-loader/ssr-module-loader/vf-module-resolver.js +1 -0
  12. package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
  13. package/esm/src/rendering/orchestrator/module-loader/index.js +6 -1
  14. package/esm/src/rendering/page-rendering.d.ts +8 -0
  15. package/esm/src/rendering/page-rendering.d.ts.map +1 -1
  16. package/esm/src/rendering/page-rendering.js +29 -18
  17. package/esm/src/task/runner.d.ts.map +1 -1
  18. package/esm/src/task/runner.js +2 -6
  19. package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts +5 -1
  20. package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts.map +1 -1
  21. package/esm/src/transforms/mdx/esm-module-loader/cache/index.js +18 -2
  22. package/esm/src/transforms/mdx/esm-module-loader/cache-format.d.ts +2 -1
  23. package/esm/src/transforms/mdx/esm-module-loader/cache-format.d.ts.map +1 -1
  24. package/esm/src/transforms/mdx/esm-module-loader/cache-format.js +12 -5
  25. package/esm/src/transforms/mdx/esm-module-loader/loader-helpers.d.ts.map +1 -1
  26. package/esm/src/transforms/mdx/esm-module-loader/loader-helpers.js +1 -0
  27. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/cache-keys.d.ts +1 -1
  28. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/cache-keys.d.ts.map +1 -1
  29. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/cache-keys.js +2 -2
  30. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/dependency-recovery.d.ts +23 -0
  31. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/dependency-recovery.d.ts.map +1 -0
  32. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/dependency-recovery.js +112 -0
  33. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.d.ts +2 -2
  34. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.d.ts.map +1 -1
  35. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.js +33 -4
  36. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/framework-validator.d.ts +6 -1
  37. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/framework-validator.d.ts.map +1 -1
  38. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/framework-validator.js +31 -1
  39. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts +1 -0
  40. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts.map +1 -1
  41. package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +15 -6
  42. package/esm/src/transforms/mdx/esm-module-loader/types.d.ts +1 -0
  43. package/esm/src/transforms/mdx/esm-module-loader/types.d.ts.map +1 -1
  44. package/esm/src/utils/version.d.ts +1 -1
  45. package/esm/src/utils/version.js +1 -1
  46. package/esm/src/workflow/executor/workflow-executor.d.ts.map +1 -1
  47. package/esm/src/workflow/executor/workflow-executor.js +7 -1
  48. package/esm/src/workflow/types.d.ts +1 -0
  49. package/esm/src/workflow/types.d.ts.map +1 -1
  50. package/esm/src/workflow/worker/dynamic-job-entrypoint.d.ts.map +1 -1
  51. package/esm/src/workflow/worker/dynamic-job-entrypoint.js +18 -1
  52. package/esm/src/workflow/worker/job-entrypoint.d.ts.map +1 -1
  53. package/esm/src/workflow/worker/job-entrypoint.js +18 -1
  54. package/package.json +1 -1
  55. package/src/deno.js +1 -1
  56. package/src/src/jobs/runtime-env.ts +132 -0
  57. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +34 -0
  58. package/src/src/modules/react-loader/ssr-module-loader/ssr-cache-manager.ts +34 -2
  59. package/src/src/modules/react-loader/ssr-module-loader/vf-module-resolver.ts +1 -0
  60. package/src/src/rendering/orchestrator/module-loader/index.ts +12 -1
  61. package/src/src/rendering/page-rendering.ts +64 -39
  62. package/src/src/task/runner.ts +2 -8
  63. package/src/src/transforms/mdx/esm-module-loader/cache/index.ts +18 -1
  64. package/src/src/transforms/mdx/esm-module-loader/cache-format.ts +33 -1
  65. package/src/src/transforms/mdx/esm-module-loader/loader-helpers.ts +1 -0
  66. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/cache-keys.ts +2 -1
  67. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/dependency-recovery.ts +173 -0
  68. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts +43 -3
  69. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/framework-validator.ts +37 -0
  70. package/src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts +26 -11
  71. package/src/src/transforms/mdx/esm-module-loader/types.ts +1 -0
  72. package/src/src/utils/version.ts +1 -1
  73. package/src/src/workflow/executor/workflow-executor.ts +7 -1
  74. package/src/src/workflow/types.ts +1 -0
  75. package/src/src/workflow/worker/dynamic-job-entrypoint.ts +19 -1
  76. package/src/src/workflow/worker/job-entrypoint.ts +19 -1
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Distributed recovery for missing MDX ESM module dependencies.
3
+ *
4
+ * Restores missing vfmod files on fresh pods from the distributed transform
5
+ * cache, scoped to the current project and content source.
6
+ *
7
+ * @module transforms/mdx/esm-module-loader/module-fetcher/dependency-recovery
8
+ */
9
+
10
+ import { basename, dirname } from "../../../../platform/compat/path/index.js";
11
+ import { detokenizeAllCachePaths } from "../../../../cache/index.js";
12
+ import type { CacheBackend } from "../../../../cache/types.js";
13
+ import type { Logger } from "../../../../utils/logger/logger.js";
14
+ import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
15
+ import { ensureHttpBundlesExist } from "../../../esm/http-cache.js";
16
+ import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
17
+ import { getHttpBundleCacheDir } from "../../../../utils/cache-dir.js";
18
+ import { LOG_PREFIX_MDX_LOADER } from "../constants.js";
19
+ import { getLocalFs } from "../cache/index.js";
20
+ import { buildMdxEsmModuleRecoveryCacheKey } from "../cache-format.js";
21
+
22
+ const MDX_VFMOD_FILE_URL_PATTERN = /file:\/\/([^"'\s]+veryfront-mdx-esm\/[^"'\s]+\.mjs)/gi;
23
+
24
+ interface EnsureMdxModuleDependenciesOptions {
25
+ projectId: string;
26
+ contentSourceId: string;
27
+ log: Logger;
28
+ distributedCache?: CacheBackend | null;
29
+ }
30
+
31
+ interface EnsureMdxModuleDependenciesResult {
32
+ recovered: string[];
33
+ missing: string[];
34
+ }
35
+
36
+ function extractMdxModuleDependencyPaths(code: string): string[] {
37
+ const paths: string[] = [];
38
+ const seen = new Set<string>();
39
+ let match: RegExpExecArray | null;
40
+ while ((match = MDX_VFMOD_FILE_URL_PATTERN.exec(code)) !== null) {
41
+ const rawPath = match[1];
42
+ if (!rawPath) continue;
43
+ const cleanPath = rawPath.replace(/\?.*$/, "");
44
+ if (seen.has(cleanPath)) continue;
45
+ seen.add(cleanPath);
46
+ paths.push(cleanPath);
47
+ }
48
+ return paths;
49
+ }
50
+
51
+ async function ensureHttpBundleDependencies(code: string, log: Logger): Promise<boolean> {
52
+ const bundlePaths = extractHttpBundlePaths(code);
53
+ if (bundlePaths.length === 0) return true;
54
+
55
+ const failed = await ensureHttpBundlesExist(bundlePaths, getHttpBundleCacheDir());
56
+ if (failed.length === 0) return true;
57
+
58
+ log.warn(`${LOG_PREFIX_MDX_LOADER} Failed to recover HTTP bundles for vfmod dependency`, {
59
+ failed,
60
+ totalBundles: bundlePaths.length,
61
+ });
62
+ return false;
63
+ }
64
+
65
+ async function ensureModuleFileAndDeps(
66
+ absolutePath: string,
67
+ distributedCache: CacheBackend,
68
+ options: EnsureMdxModuleDependenciesOptions,
69
+ visited: Set<string>,
70
+ recovered: Set<string>,
71
+ ): Promise<boolean> {
72
+ if (visited.has(absolutePath)) return true;
73
+ visited.add(absolutePath);
74
+
75
+ const localFs = getLocalFs();
76
+
77
+ try {
78
+ const stat = await localFs.stat(absolutePath);
79
+ if (stat?.isFile) {
80
+ const existingCode = await localFs.readTextFile(absolutePath);
81
+ if (!(await ensureHttpBundleDependencies(existingCode, options.log))) return false;
82
+
83
+ for (const nestedPath of extractMdxModuleDependencyPaths(existingCode)) {
84
+ if (
85
+ !(await ensureModuleFileAndDeps(
86
+ nestedPath,
87
+ distributedCache,
88
+ options,
89
+ visited,
90
+ recovered,
91
+ ))
92
+ ) {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ return true;
98
+ }
99
+ } catch (_) {
100
+ /* expected: dependency may not exist on this pod yet */
101
+ }
102
+
103
+ const recoveryKey = buildMdxEsmModuleRecoveryCacheKey(
104
+ options.projectId,
105
+ options.contentSourceId,
106
+ basename(absolutePath),
107
+ );
108
+
109
+ const portableCode = await distributedCache.get(recoveryKey);
110
+ if (!portableCode) {
111
+ options.log.debug(`${LOG_PREFIX_MDX_LOADER} No distributed vfmod recovery entry`, {
112
+ dependencyPath: absolutePath,
113
+ recoveryKey,
114
+ });
115
+ return false;
116
+ }
117
+
118
+ const recoveredCode = detokenizeAllCachePaths(portableCode);
119
+ if (!(await ensureHttpBundleDependencies(recoveredCode, options.log))) return false;
120
+
121
+ for (const nestedPath of extractMdxModuleDependencyPaths(recoveredCode)) {
122
+ if (
123
+ !(await ensureModuleFileAndDeps(
124
+ nestedPath,
125
+ distributedCache,
126
+ options,
127
+ visited,
128
+ recovered,
129
+ ))
130
+ ) {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ await localFs.mkdir(dirname(absolutePath), { recursive: true });
136
+ await localFs.writeTextFile(absolutePath, recoveredCode);
137
+ recovered.add(absolutePath);
138
+
139
+ options.log.debug(`${LOG_PREFIX_MDX_LOADER} Recovered vfmod dependency from distributed cache`, {
140
+ dependencyPath: absolutePath,
141
+ recoveryKey,
142
+ });
143
+
144
+ return true;
145
+ }
146
+
147
+ export async function ensureMdxModuleDependencies(
148
+ code: string,
149
+ options: EnsureMdxModuleDependenciesOptions,
150
+ ): Promise<EnsureMdxModuleDependenciesResult> {
151
+ const distributedCache = options.distributedCache ?? (await getDistributedTransformBackend());
152
+ if (!distributedCache) return { recovered: [], missing: extractMdxModuleDependencyPaths(code) };
153
+
154
+ const visited = new Set<string>();
155
+ const recovered = new Set<string>();
156
+ const missing: string[] = [];
157
+
158
+ for (const dependencyPath of extractMdxModuleDependencyPaths(code)) {
159
+ const ok = await ensureModuleFileAndDeps(
160
+ dependencyPath,
161
+ distributedCache,
162
+ options,
163
+ visited,
164
+ recovered,
165
+ );
166
+ if (!ok) missing.push(dependencyPath);
167
+ }
168
+
169
+ return {
170
+ recovered: [...recovered],
171
+ missing,
172
+ };
173
+ }
@@ -23,6 +23,9 @@ import {
23
23
  findMissingFileDependenciesInCode,
24
24
  hasIncompatibleFrameworkPaths,
25
25
  } from "./framework-validator.js";
26
+ import { ensureMdxModuleDependencies } from "./dependency-recovery.js";
27
+ import { buildMdxEsmModuleFileName, buildMdxEsmModuleRecoveryCacheKey } from "../cache-format.js";
28
+ import { hashString } from "../utils/hash.js";
26
29
 
27
30
  /** TTL for cached transforms (uses centralized config) */
28
31
  const TRANSFORM_CACHE_TTL_SECONDS = TRANSFORM_DISTRIBUTED_TTL_SEC;
@@ -59,6 +62,8 @@ interface DistributedCacheReadResult {
59
62
  */
60
63
  export async function readDistributedCache(
61
64
  transformCacheKey: string,
65
+ projectId: string,
66
+ contentSourceId: string | undefined,
62
67
  normalizedPath: string,
63
68
  projectSlug: string,
64
69
  projectDir: string,
@@ -129,10 +134,26 @@ export async function readDistributedCache(
129
134
  // that don't exist on this machine.
130
135
  if (moduleCode) {
131
136
  const missingDeps = await findMissingFileDependenciesInCode(moduleCode, log);
132
- if (missingDeps.length > 0) {
137
+ if (missingDeps.length > 0 && contentSourceId) {
138
+ const recovered = await ensureMdxModuleDependencies(moduleCode, {
139
+ distributedCache,
140
+ projectId,
141
+ contentSourceId,
142
+ log,
143
+ });
144
+ if (recovered.recovered.length > 0) {
145
+ log.debug(`${LOG_PREFIX_MDX_LOADER} Recovered missing vfmod dependencies from cache`, {
146
+ normalizedPath,
147
+ recovered: recovered.recovered.slice(0, 5),
148
+ });
149
+ }
150
+ }
151
+
152
+ const unresolvedDeps = await findMissingFileDependenciesInCode(moduleCode, log);
153
+ if (unresolvedDeps.length > 0) {
133
154
  log.warn(
134
- `${LOG_PREFIX_MDX_LOADER} Cached code has ${missingDeps.length} missing file dependencies, invalidating`,
135
- { normalizedPath, missingDeps: missingDeps.slice(0, 5) },
155
+ `${LOG_PREFIX_MDX_LOADER} Cached code has ${unresolvedDeps.length} missing file dependencies, invalidating`,
156
+ { normalizedPath, missingDeps: unresolvedDeps.slice(0, 5) },
136
157
  );
137
158
  moduleCode = null;
138
159
  }
@@ -177,6 +198,8 @@ export async function readDistributedCache(
177
198
  export function writeDistributedCache(
178
199
  distributedCache: DistributedCache,
179
200
  transformCacheKey: string,
201
+ projectId: string,
202
+ contentSourceId: string,
180
203
  moduleCode: string,
181
204
  normalizedPath: string,
182
205
  log: Logger,
@@ -195,6 +218,23 @@ export function writeDistributedCache(
195
218
  });
196
219
  });
197
220
 
221
+ const moduleFileName = buildMdxEsmModuleFileName(hashString(normalizedPath + moduleCode));
222
+ const moduleRecoveryKey = buildMdxEsmModuleRecoveryCacheKey(
223
+ projectId,
224
+ contentSourceId,
225
+ moduleFileName,
226
+ );
227
+
228
+ distributedCache
229
+ .set(moduleRecoveryKey, portableCode, TRANSFORM_CACHE_TTL_SECONDS)
230
+ .catch((error) => {
231
+ log.debug(`${LOG_PREFIX_MDX_LOADER} Distributed vfmod recovery set failed`, {
232
+ normalizedPath,
233
+ moduleRecoveryKey,
234
+ error,
235
+ });
236
+ });
237
+
198
238
  // Create and store bundle manifest companion key for atomic validation
199
239
  const bundlePaths = extractHttpBundlePaths(moduleCode);
200
240
  if (bundlePaths.length > 0) {
@@ -14,6 +14,12 @@ import { getLocalFs } from "../cache/index.js";
14
14
  import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
15
15
  import { ensureHttpBundlesExist } from "../../../esm/http-cache.js";
16
16
  import { MDX_ESM_MJS_FILE_URL_PATTERN_SOURCE } from "../cache-format.js";
17
+ import { ensureMdxModuleDependencies } from "./dependency-recovery.js";
18
+
19
+ interface MdxRecoveryOptions {
20
+ projectId: string;
21
+ contentSourceId: string;
22
+ }
17
23
 
18
24
  /**
19
25
  * Check if cached code has file:// paths that are incompatible with this environment.
@@ -157,6 +163,7 @@ export async function validateCachedModule(
157
163
  log: Logger,
158
164
  pathCache: Map<string, string>,
159
165
  versionedKey: string,
166
+ recoveryOptions?: MdxRecoveryOptions,
160
167
  ): Promise<boolean> {
161
168
  // Reject caches with raw HTTP URLs - all modules should use file:// paths.
162
169
  // This ensures consistency between compiled and non-compiled modes.
@@ -192,6 +199,36 @@ export async function validateCachedModule(
192
199
  }
193
200
  }
194
201
 
202
+ if (recoveryOptions) {
203
+ const recovered = await ensureMdxModuleDependencies(cachedCode, {
204
+ ...recoveryOptions,
205
+ log,
206
+ });
207
+ if (recovered.recovered.length > 0) {
208
+ log.debug(`${LOG_PREFIX_MDX_LOADER} Recovered cached module vfmod dependencies`, {
209
+ normalizedPath,
210
+ cachedPath,
211
+ recovered: recovered.recovered.slice(0, 5),
212
+ });
213
+ }
214
+ }
215
+
216
+ const missingDeps = await findMissingFileDependenciesInCode(cachedCode, log);
217
+ if (missingDeps.length > 0) {
218
+ log.warn(`${LOG_PREFIX_MDX_LOADER} Cached module has missing vfmod dependencies`, {
219
+ normalizedPath,
220
+ cachedPath,
221
+ missingDeps: missingDeps.slice(0, 5),
222
+ });
223
+ pathCache.delete(versionedKey);
224
+ try {
225
+ await getLocalFs().remove(cachedPath);
226
+ } catch (_) {
227
+ /* expected: cached file may already be removed */
228
+ }
229
+ return false;
230
+ }
231
+
195
232
  if (!(await hasIncompatibleFrameworkPaths(cachedCode, log))) return true;
196
233
 
197
234
  log.warn(`${LOG_PREFIX_MDX_LOADER} Cached module has incompatible framework paths`, {
@@ -190,7 +190,7 @@ async function doFetchAndCacheModule(
190
190
  parentModulePath?: string,
191
191
  ): Promise<string | null> {
192
192
  const log = getLog(context);
193
- const { esmCacheDir, adapter, projectDir, projectId } = context;
193
+ const { esmCacheDir, adapter, projectDir, projectId, contentSourceId } = context;
194
194
 
195
195
  const pathCache = await getModulePathCache(esmCacheDir);
196
196
  const versionedKey = getVersionedPathCacheKey(normalizedPath);
@@ -209,6 +209,12 @@ async function doFetchAndCacheModule(
209
209
  log,
210
210
  pathCache,
211
211
  versionedKey,
212
+ context.contentSourceId
213
+ ? {
214
+ projectId: context.projectId,
215
+ contentSourceId: context.contentSourceId,
216
+ }
217
+ : undefined,
212
218
  )
213
219
  ) {
214
220
  recordModuleToSession(normalizedPath);
@@ -252,7 +258,9 @@ async function doFetchAndCacheModule(
252
258
  const { sourceCode, actualFilePath } = resolved;
253
259
 
254
260
  const contentHash = hashString(sourceCode);
255
- const transformCacheKey = getTransformCacheKey(projectId, normalizedPath, contentHash);
261
+ const transformCacheKey = contentSourceId
262
+ ? getTransformCacheKey(projectId, contentSourceId, normalizedPath, contentHash)
263
+ : null;
256
264
 
257
265
  let moduleCode: string | null = null;
258
266
  let needsDistributedCacheWrite = false;
@@ -260,14 +268,18 @@ async function doFetchAndCacheModule(
260
268
  // Try distributed cache read with full validation.
261
269
  // Returns null only if no distributed backend is configured.
262
270
  // Otherwise returns { code, distributedCache } where code may be null (miss).
263
- const distResult = await readDistributedCache(
264
- transformCacheKey,
265
- normalizedPath,
266
- projectSlug,
267
- projectDir,
268
- context.reactVersion,
269
- log,
270
- );
271
+ const distResult = transformCacheKey
272
+ ? await readDistributedCache(
273
+ transformCacheKey,
274
+ projectId,
275
+ contentSourceId,
276
+ normalizedPath,
277
+ projectSlug,
278
+ projectDir,
279
+ context.reactVersion,
280
+ log,
281
+ )
282
+ : null;
271
283
  if (distResult?.code) {
272
284
  moduleCode = distResult.code;
273
285
  }
@@ -402,10 +414,12 @@ async function doFetchAndCacheModule(
402
414
 
403
415
  // Write to distributed cache AFTER nested imports are resolved.
404
416
  // This ensures other pods get fully-resolved code without /_vf_modules/ paths.
405
- if (needsDistributedCacheWrite && distResult?.distributedCache) {
417
+ if (needsDistributedCacheWrite && distResult?.distributedCache && transformCacheKey) {
406
418
  writeDistributedCache(
407
419
  distResult.distributedCache,
408
420
  transformCacheKey,
421
+ projectId,
422
+ contentSourceId!,
409
423
  moduleCode,
410
424
  normalizedPath,
411
425
  log,
@@ -449,6 +463,7 @@ export function createModuleFetcherContext(
449
463
  projectDir: string,
450
464
  projectId: string,
451
465
  options?: {
466
+ contentSourceId?: string;
452
467
  isLocalProject?: boolean;
453
468
  projectSlug?: string;
454
469
  reactVersion?: string;
@@ -51,6 +51,7 @@ export interface ModuleFetcherContext {
51
51
  adapter: RuntimeAdapter;
52
52
  projectDir: string;
53
53
  projectId: string;
54
+ contentSourceId?: string;
54
55
  projectSlug?: string;
55
56
  isLocalProject?: boolean;
56
57
  /**
@@ -3,7 +3,7 @@ import { getEnv } from "../platform/compat/process.js";
3
3
 
4
4
  // Keep in sync with deno.json version.
5
5
  // scripts/release.ts updates this constant during releases.
6
- export const VERSION = "0.1.95";
6
+ export const VERSION = "0.1.97";
7
7
 
8
8
  export function normalizeVeryfrontVersion(version: string | undefined): string | undefined {
9
9
  if (!version) return undefined;
@@ -27,6 +27,8 @@ import type {
27
27
  import { generateId, parseDuration } from "../types.js";
28
28
  import { hasLockSupport, type WorkflowBackend } from "../backends/types.js";
29
29
  import { getCurrentRequestContext } from "../../platform/adapters/fs/veryfront/multi-project-adapter.js";
30
+ import { env as getProcessEnv } from "../../platform/compat/process.js";
31
+ import { mergeInjectedWorkflowEnv } from "../../jobs/runtime-env.js";
30
32
  import { DAGExecutor } from "./dag-executor.js";
31
33
  import { CheckpointManager } from "./checkpoint-manager.js";
32
34
  import { runWithWorkflowTenant, StepExecutor, type StepExecutorConfig } from "./step-executor.js";
@@ -196,6 +198,7 @@ export class WorkflowExecutor {
196
198
  releaseId: requestCtx.releaseId ?? null,
197
199
  }
198
200
  : undefined;
201
+ const injectedProjectEnv = mergeInjectedWorkflowEnv(undefined, getProcessEnv());
199
202
 
200
203
  const run: WorkflowRun<TInput, TOutput> = {
201
204
  id: options?.runId ?? generateId("run"),
@@ -205,7 +208,10 @@ export class WorkflowExecutor {
205
208
  input,
206
209
  nodeStates: {},
207
210
  currentNodes: [],
208
- context: { input },
211
+ context: {
212
+ input,
213
+ ...(injectedProjectEnv ? { env: injectedProjectEnv } : {}),
214
+ },
209
215
  checkpoints: [],
210
216
  pendingApprovals: [],
211
217
  createdAt: new Date(),
@@ -50,6 +50,7 @@ type DurationString = string;
50
50
  */
51
51
  export interface WorkflowContext {
52
52
  input: unknown;
53
+ env?: Record<string, string>;
53
54
  _tenant?: CapturedTenantContext;
54
55
  [nodeId: string]: unknown;
55
56
  }
@@ -27,9 +27,11 @@
27
27
 
28
28
  import { logger as baseLogger } from "../../utils/index.js";
29
29
  import { getEnv } from "../../platform/compat/process.js";
30
+ import { env as getProcessEnv } from "../../platform/compat/process.js";
30
31
  import { runWithRequestContext } from "../../platform/adapters/fs/veryfront/multi-project-adapter.js";
31
32
  import { enhanceAdapterWithFS } from "../../platform/adapters/fs/integration.js";
32
33
  import { denoAdapter } from "../../platform/adapters/runtime/deno/index.js";
34
+ import { mergeInjectedWorkflowEnv } from "../../jobs/runtime-env.js";
33
35
  import { discoverWorkflows } from "../discovery/index.js";
34
36
  import type { VeryfrontConfig } from "../../config/index.js";
35
37
  import type { WorkflowBackend } from "../backends/types.js";
@@ -109,12 +111,28 @@ export async function runDynamicWorkflowJob(
109
111
 
110
112
  try {
111
113
  // Fetch the workflow run
112
- const run = await backend.getRun(runId);
114
+ let run = await backend.getRun(runId);
113
115
  if (!run) {
114
116
  logger.error(`Workflow run not found: ${runId}`);
115
117
  return DYNAMIC_EXIT_CODES.NOT_FOUND;
116
118
  }
117
119
 
120
+ const injectedEnv = mergeInjectedWorkflowEnv(run.context.env, getProcessEnv());
121
+ if (injectedEnv) {
122
+ const currentEnv = run.context.env;
123
+ const currentSerialized = currentEnv ? JSON.stringify(currentEnv) : "";
124
+ const nextSerialized = JSON.stringify(injectedEnv);
125
+ if (currentSerialized !== nextSerialized) {
126
+ await backend.updateRun(runId, {
127
+ context: {
128
+ ...run.context,
129
+ env: injectedEnv,
130
+ },
131
+ });
132
+ run = (await backend.getRun(runId)) ?? run;
133
+ }
134
+ }
135
+
118
136
  // Get tenant context (from env or from stored run)
119
137
  const tenant = getTenantFromEnv() ?? run._tenant;
120
138
 
@@ -22,7 +22,9 @@
22
22
 
23
23
  import { logger as baseLogger } from "../../utils/index.js";
24
24
  import { getEnv } from "../../platform/compat/process.js";
25
+ import { env as getProcessEnv } from "../../platform/compat/process.js";
25
26
  import { runWithRequestContext } from "../../platform/adapters/fs/veryfront/multi-project-adapter.js";
27
+ import { mergeInjectedWorkflowEnv } from "../../jobs/runtime-env.js";
26
28
  import type { WorkflowBackend } from "../backends/types.js";
27
29
  import type { WorkflowExecutor } from "../executor/workflow-executor.js";
28
30
  import type { CapturedTenantContext, WorkflowDefinition } from "../types.js";
@@ -116,12 +118,28 @@ export async function runWorkflowJob(config: JobEntrypointConfig): Promise<numbe
116
118
 
117
119
  try {
118
120
  // Fetch the workflow run
119
- const run = await backend.getRun(runId);
121
+ let run = await backend.getRun(runId);
120
122
  if (!run) {
121
123
  logger.error(`Workflow run not found: ${runId}`);
122
124
  return EXIT_CODES.NOT_FOUND;
123
125
  }
124
126
 
127
+ const injectedEnv = mergeInjectedWorkflowEnv(run.context.env, getProcessEnv());
128
+ if (injectedEnv) {
129
+ const currentEnv = run.context.env;
130
+ const currentSerialized = currentEnv ? JSON.stringify(currentEnv) : "";
131
+ const nextSerialized = JSON.stringify(injectedEnv);
132
+ if (currentSerialized !== nextSerialized) {
133
+ await backend.updateRun(runId, {
134
+ context: {
135
+ ...run.context,
136
+ env: injectedEnv,
137
+ },
138
+ });
139
+ run = (await backend.getRun(runId)) ?? run;
140
+ }
141
+ }
142
+
125
143
  // Get tenant context (from env or from stored run)
126
144
  const tenant = getTenantFromEnv() ?? run._tenant;
127
145