@sanoma-ai/workflows 0.9.1 → 0.10.1
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/README.md +193 -50
- package/SECRET-SUPPORT.md +33 -0
- package/dist/apply-steps.js +5 -2
- package/dist/apply-steps.js.map +1 -1
- package/dist/apply.d.ts +3 -0
- package/dist/apply.js +27 -13
- package/dist/apply.js.map +1 -1
- package/dist/approvals.d.ts +1 -0
- package/dist/approvals.js +6 -1
- package/dist/approvals.js.map +1 -1
- package/dist/apps.d.ts +13 -24
- package/dist/apps.js +69 -68
- package/dist/apps.js.map +1 -1
- package/dist/ast.d.ts +9 -7
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -1
- package/dist/authentication.d.ts +54 -0
- package/dist/authentication.js +138 -0
- package/dist/authentication.js.map +1 -0
- package/dist/call-workflow.d.ts +5 -0
- package/dist/call-workflow.js +4 -3
- package/dist/call-workflow.js.map +1 -1
- package/dist/call.d.ts +3 -2
- package/dist/call.js +36 -7
- package/dist/call.js.map +1 -1
- package/dist/cedar.js +14 -1
- package/dist/cedar.js.map +1 -1
- package/dist/change-steps.js +7 -5
- package/dist/change-steps.js.map +1 -1
- package/dist/change.d.ts +7 -0
- package/dist/change.js +7 -0
- package/dist/change.js.map +1 -1
- package/dist/client.d.ts +62 -10
- package/dist/client.js +272 -47
- package/dist/client.js.map +1 -1
- package/dist/collection.d.ts +7 -0
- package/dist/collection.js +5 -2
- package/dist/collection.js.map +1 -1
- package/dist/config.d.ts +16 -4
- package/dist/config.js +89 -21
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +389 -0
- package/dist/connection.js +657 -0
- package/dist/connection.js.map +1 -0
- package/dist/credentials.d.ts +7 -53
- package/dist/credentials.js +84 -247
- package/dist/credentials.js.map +1 -1
- package/dist/db.js +34 -2
- package/dist/db.js.map +1 -1
- package/dist/define.d.ts +9 -2
- package/dist/define.js.map +1 -1
- package/dist/definitions.d.ts +20 -0
- package/dist/definitions.js +16 -0
- package/dist/definitions.js.map +1 -0
- package/dist/deployment-key.d.ts +2 -0
- package/dist/deployment-key.js +22 -0
- package/dist/deployment-key.js.map +1 -0
- package/dist/describe.d.ts +15 -175
- package/dist/describe.js +122 -35
- package/dist/describe.js.map +1 -1
- package/dist/description.d.ts +532 -0
- package/dist/description.js +381 -0
- package/dist/description.js.map +1 -0
- package/dist/direct.js +7 -0
- package/dist/direct.js.map +1 -1
- package/dist/drift.d.ts +1 -1
- package/dist/drift.js +1 -1
- package/dist/drift.js.map +1 -1
- package/dist/env.d.ts +2 -0
- package/dist/env.js +2 -2
- package/dist/env.js.map +1 -1
- package/dist/fake.d.ts +13 -1
- package/dist/fake.js +70 -0
- package/dist/fake.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +2 -0
- package/dist/ledger.js +1 -1
- package/dist/ledger.js.map +1 -1
- package/dist/lint.d.ts +0 -9
- package/dist/lint.js +2 -17
- package/dist/lint.js.map +1 -1
- package/dist/load.d.ts +85 -0
- package/dist/load.js +391 -0
- package/dist/load.js.map +1 -0
- package/dist/lockfile.d.ts +2 -0
- package/dist/lockfile.js +8 -3
- package/dist/lockfile.js.map +1 -1
- package/dist/op.d.ts +22 -5
- package/dist/op.js +20 -2
- package/dist/op.js.map +1 -1
- package/dist/outline.d.ts +5 -0
- package/dist/outline.js +46 -5
- package/dist/outline.js.map +1 -1
- package/dist/paths.d.ts +18 -2
- package/dist/paths.js +19 -7
- package/dist/paths.js.map +1 -1
- package/dist/plugin.d.ts +0 -11
- package/dist/plugin.js +3 -18
- package/dist/plugin.js.map +1 -1
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js.map +1 -1
- package/dist/resource.d.ts +10 -4
- package/dist/resource.js +11 -4
- package/dist/resource.js.map +1 -1
- package/dist/resources.d.ts +33 -76
- package/dist/resources.js +219 -227
- package/dist/resources.js.map +1 -1
- package/dist/run.d.ts +4 -0
- package/dist/run.js.map +1 -1
- package/dist/sandbox.d.ts +5 -2
- package/dist/sandbox.js +56 -7
- package/dist/sandbox.js.map +1 -1
- package/dist/scenario.d.ts +5 -0
- package/dist/scenario.js +3 -2
- package/dist/scenario.js.map +1 -1
- package/dist/scenarios.js +7 -1
- package/dist/scenarios.js.map +1 -1
- package/dist/schema.js +8 -0
- package/dist/schema.js.map +1 -1
- package/dist/secret-store-check.d.ts +11 -0
- package/dist/secret-store-check.js +76 -0
- package/dist/secret-store-check.js.map +1 -0
- package/dist/secret-store.d.ts +106 -0
- package/dist/secret-store.js +228 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/secret.d.ts +2 -0
- package/dist/secret.js +32 -0
- package/dist/secret.js.map +1 -1
- package/dist/shared.d.ts +21 -11
- package/dist/shared.js +41 -7
- package/dist/shared.js.map +1 -1
- package/dist/sites.d.ts +4 -93
- package/dist/sites.js +76 -84
- package/dist/sites.js.map +1 -1
- package/dist/steps.d.ts +3 -0
- package/dist/steps.js.map +1 -1
- package/dist/version.d.ts +2 -1
- package/dist/version.js +2 -1
- package/dist/version.js.map +1 -1
- package/dist/wakeup-driver.js +15 -6
- package/dist/wakeup-driver.js.map +1 -1
- package/dist/worker.js +12 -8
- package/dist/worker.js.map +1 -1
- package/dist/writer.d.ts +53 -14
- package/dist/writer.js +113 -43
- package/dist/writer.js.map +1 -1
- package/oxlint.json +1 -5
- package/package.json +6 -1
- package/src/apply-steps.ts +5 -2
- package/src/apply.ts +54 -21
- package/src/approvals.ts +7 -1
- package/src/apps.ts +71 -86
- package/src/ast.ts +6 -6
- package/src/authentication.ts +191 -0
- package/src/call-workflow.ts +5 -4
- package/src/call.ts +52 -9
- package/src/cedar.ts +14 -1
- package/src/change-steps.ts +7 -4
- package/src/change.ts +10 -0
- package/src/client.ts +346 -45
- package/src/collection.ts +10 -2
- package/src/config.ts +107 -19
- package/src/connection.ts +912 -0
- package/src/credentials.ts +94 -281
- package/src/db.ts +34 -2
- package/src/define.ts +7 -2
- package/src/definitions.ts +31 -0
- package/src/deployment-key.ts +23 -0
- package/src/describe.ts +150 -227
- package/src/description.ts +426 -0
- package/src/direct.ts +7 -0
- package/src/drift.ts +10 -2
- package/src/env.ts +4 -2
- package/src/fake.ts +71 -0
- package/src/index.ts +53 -1
- package/src/ledger.ts +3 -1
- package/src/lint.ts +2 -17
- package/src/load.ts +466 -0
- package/src/lockfile.ts +10 -3
- package/src/op.ts +41 -7
- package/src/outline.ts +53 -7
- package/src/paths.ts +21 -6
- package/src/plugin.ts +3 -24
- package/src/policy.ts +2 -0
- package/src/resource.ts +24 -6
- package/src/resources.ts +246 -301
- package/src/run.ts +4 -0
- package/src/sandbox.ts +61 -7
- package/src/scenario.ts +24 -6
- package/src/scenarios.ts +7 -1
- package/src/schema.ts +10 -0
- package/src/secret-store-check.ts +93 -0
- package/src/secret-store.ts +314 -0
- package/src/secret.ts +29 -0
- package/src/shared.ts +44 -10
- package/src/sites.ts +85 -179
- package/src/steps.ts +3 -2
- package/src/version.ts +2 -1
- package/src/wakeup-driver.ts +22 -6
- package/src/worker.ts +12 -8
- package/src/writer.ts +145 -56
- package/dist/datafile.d.ts +0 -97
- package/dist/datafile.js +0 -479
- package/dist/datafile.js.map +0 -1
- package/src/datafile.ts +0 -601
package/src/shared.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { OpEntry, WorkflowEntry } from "./describe.ts";
|
|
|
5
5
|
import type { LedgerRecord } from "./ledger.ts";
|
|
6
6
|
import type { Effect } from "./op.ts";
|
|
7
7
|
import type { PreviewRequest } from "./policy.ts";
|
|
8
|
-
import type { DeclaredResource, ResourceProblem } from "./
|
|
8
|
+
import type { DeclaredResource, ResourceProblem } from "./description.ts";
|
|
9
9
|
import type { OutlineNode } from "./outline.ts";
|
|
10
10
|
|
|
11
11
|
// Types only: resource types as a UI reads them. Erased from the bundle.
|
|
@@ -18,7 +18,7 @@ export type {
|
|
|
18
18
|
ResourceFields,
|
|
19
19
|
ResourceSpec,
|
|
20
20
|
} from "./resource.ts";
|
|
21
|
-
export type { DeclaredResource, ResourceProblem } from "./
|
|
21
|
+
export type { At, DeclaredResource, ResourceProblem } from "./description.ts";
|
|
22
22
|
export type { DriftField, DriftReport, DriftResult, DriftStatus } from "./drift.ts";
|
|
23
23
|
export type { ApplyPlan, ApplyReport, ApplyResult, PlanAnswer, PlanStep } from "./apply.ts";
|
|
24
24
|
export type { CredentialStatus } from "./env.ts";
|
|
@@ -31,6 +31,8 @@ export type { AvailableApp, AvailableApps } from "./apps-list.ts";
|
|
|
31
31
|
|
|
32
32
|
// Sanoma's claims, data (`sanoma/claims` re-exports them).
|
|
33
33
|
export * from "./claims.ts";
|
|
34
|
+
// A site's `site.ts` declares itself with it (`sanoma/site` calls it, typing its icon).
|
|
35
|
+
export { defineSite, type SiteDefinition } from "./definitions.ts";
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* What a `ctx.sleep` without a `{ name }` is called, in its outline node and its ledger record
|
|
@@ -87,6 +89,15 @@ export const SITE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
|
87
89
|
*/
|
|
88
90
|
export const APP_NAME = /^[a-z][a-z0-9]*$/;
|
|
89
91
|
|
|
92
|
+
/** Where a company's apps are, under its root: `apps/<app>/`. */
|
|
93
|
+
export const APPS_DIR = "apps";
|
|
94
|
+
|
|
95
|
+
/** The app a path relative to a company's root is in: `<app>` under `apps/<app>/`; undefined for any other. */
|
|
96
|
+
export function appOfPath(rel: string): string | undefined {
|
|
97
|
+
const [top, app, ...rest] = rel.split("/");
|
|
98
|
+
return top === APPS_DIR && app !== undefined && rest.length && APP_NAME.test(app) ? app : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
/** A name declared under `apps/<app>/` as the runtime has it, `<app>.<name>`; one declared at the root as it is. */
|
|
91
102
|
export const qualified = (app: string | undefined, name: string): string =>
|
|
92
103
|
app === undefined ? name : `${app}.${name}`;
|
|
@@ -490,17 +501,40 @@ export function flatten(nodes: readonly OutlineNode[]): OutlineNode[] {
|
|
|
490
501
|
return nodes.flatMap((node) => [node, ...flatten(childLists(node).flat())]);
|
|
491
502
|
}
|
|
492
503
|
|
|
493
|
-
/**
|
|
494
|
-
|
|
495
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Where a problem is, `file:line:column`, `file#export` or `file`; undefined for one that is the
|
|
506
|
+
* config's, with no file.
|
|
507
|
+
*/
|
|
508
|
+
export const problemAt = (p: ResourceProblem): string | undefined => {
|
|
509
|
+
if (p.file === undefined) return undefined;
|
|
510
|
+
if (p.line !== undefined) return `${p.file}:${p.line}:${p.column ?? 1}`;
|
|
511
|
+
return p.export === undefined ? p.file : `${p.file}#${p.export}`;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* A problem as one line: `file:line:column: message`, `file#export: message` or `file: message`
|
|
516
|
+
* without a position, or the message alone without a file.
|
|
517
|
+
*/
|
|
518
|
+
export function problemLine(p: ResourceProblem): string {
|
|
519
|
+
const at = problemAt(p);
|
|
520
|
+
return at === undefined ? p.message : `${at}: ${p.message}`;
|
|
521
|
+
}
|
|
496
522
|
|
|
497
523
|
/**
|
|
498
|
-
*
|
|
499
|
-
* the
|
|
524
|
+
* Where the export `name` (`default` for the default export) is in a file's text, as UTF-16
|
|
525
|
+
* offsets: from its `export` to the next `export`, `import`, `const` or comment at the start of a
|
|
526
|
+
* line, or the end, less trailing blank space. Read from the text alone, for highlighting it; undefined
|
|
527
|
+
* when no line starts it.
|
|
500
528
|
*/
|
|
501
|
-
export function
|
|
502
|
-
|
|
503
|
-
|
|
529
|
+
export function exportSpan(text: string, name: string): [number, number] | undefined {
|
|
530
|
+
const declared = name === "default" ? "default" : `const\\s+${name.replaceAll("$", "\\$")}`;
|
|
531
|
+
const start = new RegExp(`^export\\s+${declared}(?![\\w$])`, "m").exec(text);
|
|
532
|
+
if (!start) return undefined;
|
|
533
|
+
const after = start.index + start[0].length;
|
|
534
|
+
// A comment at the start of a line is the next declaration's.
|
|
535
|
+
const next = /^(?:(?:export|import|const)\b|\/[/*])/m.exec(text.slice(after));
|
|
536
|
+
const end = next ? after + next.index : text.length;
|
|
537
|
+
return [start.index, start.index + text.slice(start.index, end).trimEnd().length];
|
|
504
538
|
}
|
|
505
539
|
|
|
506
540
|
/** A warning as one line, as `problemLine` with `warning: ` before its message: what is worth fixing but not wrong. */
|
package/src/sites.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
import { basename, join
|
|
3
|
-
import {
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { childrenOf, keyName, type LintProblem, type Node, readSource, type SourceFile } from "./ast.ts";
|
|
4
5
|
import { type CedarPolicySource, visitorPermits } from "./cedar.ts";
|
|
5
|
-
import {
|
|
6
|
-
import type { ConfigDescription } from "./describe.ts";
|
|
6
|
+
import type { ConfigDescription, SiteCall, SiteEntry, SitePage } from "./description.ts";
|
|
7
7
|
import { stringValue, unwrap } from "./outline.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { exportsOf, loadModules } from "./load.ts";
|
|
9
|
+
import { APPS_DIR, appFolders, filesUnder, isDirectory, relPath, SITE_ENTRIES } from "./paths.ts";
|
|
10
|
+
import { isObject } from "./resource.ts";
|
|
11
|
+
import type { ResourceProblem } from "./resources.ts";
|
|
9
12
|
import { START } from "./schema.ts";
|
|
10
13
|
import {
|
|
11
14
|
ANONYMOUS,
|
|
@@ -19,100 +22,14 @@ import {
|
|
|
19
22
|
WORKFLOW_ACTION,
|
|
20
23
|
} from "./shared.ts";
|
|
21
24
|
|
|
22
|
-
// What a company's sites call, read from their source with oxc and never run
|
|
23
|
-
// a workflow's page shows as Used by, and
|
|
25
|
+
// What a company's sites call, read from their source with oxc and never run (their pages are
|
|
26
|
+
// browser code, the site's outline): the reverse index a workflow's page shows as Used by, and
|
|
27
|
+
// the names `sanoma check` holds to the config. A site's `site.ts` is loaded.
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
export interface SiteCall {
|
|
27
|
-
/** What it calls: a workflow, a collection (`api.data.<name>.<op>`) or an operation (`api.<vendor>.<resource>.<op>`). */
|
|
28
|
-
kind: "workflow" | "collection" | "op";
|
|
29
|
-
/**
|
|
30
|
-
* The workflow's name (`RunForm`'s `workflow`, or the workflow whose `apiNameOf` is the name
|
|
31
|
-
* `api` gives; that name itself when none is), the collection's name, or the operation's id.
|
|
32
|
-
*/
|
|
33
|
-
target: string;
|
|
34
|
-
/**
|
|
35
|
-
* What it calls of the workflow (`start`, or a read of its runs: `result`, `latest`, …) or the
|
|
36
|
-
* collection (`list`, `save`, …), as written; absent for a workflow called as itself or by
|
|
37
|
-
* `RunForm`, which starts it, and for an operation.
|
|
38
|
-
*/
|
|
39
|
-
method?: string;
|
|
40
|
-
/** `<RunForm workflow="…" />` or a call on `api`, from `sanoma/site`. */
|
|
41
|
-
via: "RunForm" | "api";
|
|
42
|
-
/** The file, relative to the config's root, with `/`: `sites/sales/routes/index.tsx`. */
|
|
43
|
-
file: string;
|
|
44
|
-
/** From 1. */
|
|
45
|
-
line: number;
|
|
46
|
-
/** From 1, in UTF-16 code units. */
|
|
47
|
-
column: number;
|
|
48
|
-
/**
|
|
49
|
-
* The path of the page the file is, from its `createFileRoute` (or `createLazyFileRoute`) id: groups and pathless
|
|
50
|
-
* segments left out, no trailing slash (`/sites/sales`). Absent for a file that is no page,
|
|
51
|
-
* such as a component's.
|
|
52
|
-
*/
|
|
53
|
-
page?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** A page of a site: a route file's path, and for a page per content entry the kind it reads. */
|
|
57
|
-
export interface SitePage {
|
|
58
|
-
/** As `SiteCall.page` has it: `/sites/www`, `/sites/www/blog/$slug`. */
|
|
59
|
-
path: string;
|
|
60
|
-
/**
|
|
61
|
-
* For a path with one parameter (`$slug`), the content kind whose entries are its pages: the one
|
|
62
|
-
* kind its file reads by that parameter, `content.<kind>.get(params.slug)` (or `slug`,
|
|
63
|
-
* destructured) with `content` from `sanoma/site`.
|
|
64
|
-
*/
|
|
65
|
-
content?: string;
|
|
66
|
-
/**
|
|
67
|
-
* `false` when its route says `staticData: { sitemap: false }`, written out: the sitemap leaves it
|
|
68
|
-
* out. Its index route's `sitemap` (`true` or `false`) decides over a layout's at its path; a
|
|
69
|
-
* pathless or group layout's says nothing.
|
|
70
|
-
*/
|
|
71
|
-
sitemap?: false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** A site, `sites/<name>/` or an app's `apps/<app>/sites/<name>/`, and what it calls. */
|
|
75
|
-
export interface SiteEntry {
|
|
76
|
-
/** Its folder's name, `<app>/<name>` for an app's (`siteKey`): where it is mounted, `/sites/<name>`. */
|
|
77
|
-
name: string;
|
|
78
|
-
/** The app whose folder holds it (`apps/<app>/sites/`); absent for one at the root. */
|
|
79
|
-
app?: string;
|
|
80
|
-
/** `defineSite({ title })` in its `site.ts`, when that is a string literal. */
|
|
81
|
-
title?: string;
|
|
82
|
-
/** `defineSite({ icon })` in its `site.ts`: a Lucide icon's name, as written. */
|
|
83
|
-
icon?: string;
|
|
84
|
-
/** `defineSite({ public: true })`: served to visitors, not people. */
|
|
85
|
-
public?: true;
|
|
86
|
-
/** `defineSite({ login })`, the connector whose login half signs visitors in, as written: one that names none is among `problems`. */
|
|
87
|
-
login?: string;
|
|
88
|
-
/**
|
|
89
|
-
* `defineSite({ locales })`, a public site's languages, the first its default: kept only when
|
|
90
|
-
* every one is a `LOCALE`, written once, on a public site with no login; else a problem.
|
|
91
|
-
*/
|
|
92
|
-
locales?: string[];
|
|
93
|
-
/** The host a public site answers on: set by `startApp` from its `domains`, never by `readSites`. */
|
|
94
|
-
domain?: string;
|
|
95
|
-
/** Its pages, by path: one for each path its `routes/` files give (a layout and its index are one). */
|
|
96
|
-
pages: SitePage[];
|
|
97
|
-
/** Its calls, by file and then in the file's order. */
|
|
98
|
-
calls: SiteCall[];
|
|
99
|
-
/**
|
|
100
|
-
* Each call to a workflow, a collection or an operation the config does not have, or to an
|
|
101
|
-
* operation a page may not call directly, or, on a public site, that no policy lets a visitor
|
|
102
|
-
* make, at its file; then what is wrong in its `site.ts`.
|
|
103
|
-
*/
|
|
104
|
-
problems: (LintProblem & { file: string })[];
|
|
105
|
-
/**
|
|
106
|
-
* On a public site, each page with one parameter whose file reads content the sitemap cannot
|
|
107
|
-
* tie to it (`SitePage.content`), so the sitemap lists none of its pages: at the first read.
|
|
108
|
-
*/
|
|
109
|
-
warnings: (LintProblem & { file: string })[];
|
|
110
|
-
}
|
|
29
|
+
export type { SiteCall, SiteEntry, SitePage };
|
|
111
30
|
|
|
112
31
|
/** Where a site's code is: its pages and its components. */
|
|
113
32
|
const SITE_DIRS = ["routes", "components"];
|
|
114
|
-
/** Where `api`, `RunForm` and `defineSite` come from: `sanoma/site`, which re-exports the app's. */
|
|
115
|
-
const SITE_SOURCES: ReadonlySet<string> = new Set(["sanoma/site", "@sanoma-ai/app/site"]);
|
|
116
33
|
const KIND_NAMES = { workflow: "workflow", collection: "collection", op: "operation" } as const;
|
|
117
34
|
/** What a route's loader and head name its path's parameters by. */
|
|
118
35
|
const PARAMS: ReadonlySet<string> = new Set(["params"]);
|
|
@@ -212,15 +129,14 @@ export function readSites(
|
|
|
212
129
|
const read = readSiteFiles(root, dir);
|
|
213
130
|
const resolved = read.flatMap(({ uses }) => uses.filter((use) => !isAppCollection(use)).map(resolve));
|
|
214
131
|
const pages = pagesOf(read);
|
|
215
|
-
const
|
|
216
|
-
const { problems: optionProblems, ...options } = siteOptionsOf(join(root, siteFile), connectors);
|
|
132
|
+
const { problems: optionProblems, ...options } = siteDefinitionOf(root, dir, connectors);
|
|
217
133
|
const calls = resolved.map((r) => r.call);
|
|
218
134
|
const problems = [
|
|
219
135
|
...resolved.flatMap(({ call, problem }) => {
|
|
220
136
|
const message = problem ?? (options.public ? refused(call, options.login) : undefined);
|
|
221
137
|
return message === undefined ? [] : [{ file: call.file, line: call.line, column: call.column, message }];
|
|
222
138
|
}),
|
|
223
|
-
...optionProblems
|
|
139
|
+
...optionProblems,
|
|
224
140
|
];
|
|
225
141
|
const sorted = [...pages.keys()].toSorted().map((path) => pages.get(path)!);
|
|
226
142
|
// A page's file it warns of, unless another of its files (a lazy one, its layout) gives its kind.
|
|
@@ -261,7 +177,7 @@ const readSiteFiles = (root: string, dir: string) =>
|
|
|
261
177
|
.toSorted()
|
|
262
178
|
.flatMap((path) => {
|
|
263
179
|
const file = readSource(path);
|
|
264
|
-
return typeof file === "string" ? [] : [readSiteFile(file,
|
|
180
|
+
return typeof file === "string" ? [] : [readSiteFile(file, relPath(root, path))];
|
|
265
181
|
});
|
|
266
182
|
|
|
267
183
|
/**
|
|
@@ -282,13 +198,13 @@ export function siteContentPages(root: string): Record<string, string> {
|
|
|
282
198
|
/**
|
|
283
199
|
* Each site's languages (`defineSite({ locales })`, the first its default) under `root`, by name,
|
|
284
200
|
* for the sites whose `site.ts` gives them without a problem there: what the app's build gives
|
|
285
|
-
* its router (`virtual:sanoma/locales`).
|
|
201
|
+
* its router (`virtual:sanoma/locales`). Loaded as `readSites` loads them.
|
|
286
202
|
*/
|
|
287
203
|
export function siteLocales(root: string): Record<string, string[]> {
|
|
288
204
|
const none = new Set<string>();
|
|
289
205
|
return Object.fromEntries(
|
|
290
206
|
sitesUnder(root).flatMap(({ name, dir }) => {
|
|
291
|
-
const { locales } =
|
|
207
|
+
const { locales } = siteDefinitionOf(root, dir, { vendors: none, logins: none });
|
|
292
208
|
return locales ? [[name, locales]] : [];
|
|
293
209
|
}),
|
|
294
210
|
);
|
|
@@ -297,13 +213,13 @@ export function siteLocales(root: string): Record<string, string[]> {
|
|
|
297
213
|
/**
|
|
298
214
|
* Each site's icon (`defineSite({ icon })`, a Lucide icon's name as written) under `root`, by name,
|
|
299
215
|
* for the sites whose `site.ts` gives one: the icons the app's build bundles (`virtual:sanoma/icons`).
|
|
300
|
-
*
|
|
216
|
+
* Loaded as `readSites` loads it.
|
|
301
217
|
*/
|
|
302
218
|
export function siteIcons(root: string): Record<string, string> {
|
|
303
219
|
const none = new Set<string>();
|
|
304
220
|
return Object.fromEntries(
|
|
305
221
|
sitesUnder(root).flatMap(({ name, dir }) => {
|
|
306
|
-
const { icon } =
|
|
222
|
+
const { icon } = siteDefinitionOf(root, dir, { vendors: none, logins: none });
|
|
307
223
|
return icon === undefined ? [] : [[name, icon]];
|
|
308
224
|
}),
|
|
309
225
|
);
|
|
@@ -347,9 +263,9 @@ function readSiteFile(
|
|
|
347
263
|
{ program, at }: SourceFile,
|
|
348
264
|
file: string,
|
|
349
265
|
): { uses: Written[]; route?: FileRoute; warning?: LintProblem & { file: string } } {
|
|
350
|
-
const api = importedAs(program,
|
|
351
|
-
const runForm = importedAs(program,
|
|
352
|
-
const content = importedAs(program,
|
|
266
|
+
const api = importedAs(program, SITE_ENTRIES, "api");
|
|
267
|
+
const runForm = importedAs(program, SITE_ENTRIES, "RunForm");
|
|
268
|
+
const content = importedAs(program, SITE_ENTRIES, "content");
|
|
353
269
|
if (!api.size && !runForm.size && !file.includes("/routes/")) return { uses: [] };
|
|
354
270
|
const found: Omit<Written, "page">[] = [];
|
|
355
271
|
/** Each `content.<kind>.get(arg)`: its kind, its argument and where it is. */
|
|
@@ -504,99 +420,89 @@ type SiteConnectors = { vendors: ReadonlySet<string>; logins: ReadonlySet<string
|
|
|
504
420
|
/** A property of an object literal that is written out: not a spread, its name not computed. */
|
|
505
421
|
const isWritten = (p: Node) => p.type === "Property" && !p.computed;
|
|
506
422
|
|
|
507
|
-
/** What `site.ts`
|
|
508
|
-
const
|
|
423
|
+
/** What `site.ts`'s default export, `defineSite({ … })`, may say. */
|
|
424
|
+
const SiteDefinitionSchema = z.strictObject({
|
|
425
|
+
title: z.string().optional(),
|
|
426
|
+
icon: z.string().optional(),
|
|
427
|
+
public: z.boolean().optional(),
|
|
428
|
+
login: z.string().optional(),
|
|
429
|
+
locales: z.array(z.string()).optional(),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
/** What `site.ts` says of who may see a site: read together or not at all. */
|
|
433
|
+
const ACCESS = ["public", "login", "locales"];
|
|
509
434
|
|
|
510
435
|
/**
|
|
511
|
-
* What
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* `locales` is kept only when it has no problem.
|
|
436
|
+
* What the site in `dir` (relative to `root`) says of itself in its `site.ts`, loaded
|
|
437
|
+
* (`loadModules`, `defineSite` served from `sanoma/site`), and what is wrong there: its default
|
|
438
|
+
* export, `defineSite({ … })`, held to its schema; then a `login` on a site that is not public, or
|
|
439
|
+
* naming no connector of the config with a login half, and `locales` that are not a public site's
|
|
440
|
+
* (with no login) language tags, each once. `locales` is kept only when it has no problem. Nothing
|
|
441
|
+
* for no file, or no default export.
|
|
516
442
|
*/
|
|
517
|
-
function
|
|
518
|
-
|
|
443
|
+
function siteDefinitionOf(
|
|
444
|
+
root: string,
|
|
445
|
+
dir: string,
|
|
519
446
|
connectors: SiteConnectors,
|
|
520
|
-
): Pick<SiteEntry, "title" | "icon" | "public" | "login" | "locales"> & {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
447
|
+
): Pick<SiteEntry, "at" | "title" | "icon" | "public" | "login" | "locales"> & {
|
|
448
|
+
problems: (ResourceProblem & { file: string })[];
|
|
449
|
+
} {
|
|
450
|
+
const file = `${dir}/site.ts`;
|
|
451
|
+
if (!existsSync(join(root, file))) return { problems: [] };
|
|
452
|
+
// A helper it imports threw: said at the helper.
|
|
453
|
+
const loaded = exportsOf(loadModules(root, [join(root, file)], []), file);
|
|
454
|
+
if (loaded === undefined) return { problems: [] };
|
|
455
|
+
if ("problem" in loaded) return { problems: [loaded.problem] };
|
|
456
|
+
const value = loaded.exports.find(([name]) => name === "default")?.[1];
|
|
457
|
+
if (value === undefined) return { problems: [] };
|
|
458
|
+
const problem = (message: string) => ({ file, export: "default", message });
|
|
459
|
+
const problems: (ResourceProblem & { file: string })[] = [];
|
|
460
|
+
let parsed = SiteDefinitionSchema.safeParse(value);
|
|
461
|
+
if (!parsed.success) {
|
|
462
|
+
const why = z.prettifyError(parsed.error).replaceAll(/\s*\n\s*/g, " ");
|
|
463
|
+
problems.push(problem(`its default export is \`defineSite({ … })\` from "sanoma/site": ${why}`));
|
|
464
|
+
// The fields it names wrongly (and the keys it does not know) left out, the rest still read:
|
|
465
|
+
// a bad icon keeps a site's `public`, `login` and `locales`. Those three say who may see it,
|
|
466
|
+
// so a wrong one leaves out all three: never a site read as public without its login.
|
|
467
|
+
const wrong = new Set(
|
|
468
|
+
parsed.error.issues.flatMap((i) => (i.code === "unrecognized_keys" ? i.keys : i.path.slice(0, 1))),
|
|
541
469
|
);
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
470
|
+
if (ACCESS.some((key) => wrong.has(key))) for (const key of ACCESS) wrong.add(key);
|
|
471
|
+
if (!isObject(value) || !wrong.size) return { problems };
|
|
472
|
+
parsed = SiteDefinitionSchema.safeParse(
|
|
473
|
+
Object.fromEntries(Object.entries(value).filter(([key]) => !wrong.has(key))),
|
|
474
|
+
);
|
|
475
|
+
if (!parsed.success) return { problems };
|
|
546
476
|
}
|
|
547
|
-
|
|
548
|
-
const property = (name: string): Node =>
|
|
549
|
-
options?.properties.findLast((p: Node) => isWritten(p) && keyName(p.key) === name);
|
|
550
|
-
|
|
551
|
-
const title = stringValue(property("title")?.value);
|
|
552
|
-
// Cosmetic: one that is not a string (tsc refuses it) is shown as none, never a problem that stops a site.
|
|
553
|
-
const icon = stringValue(property("icon")?.value);
|
|
554
|
-
const publicAt = property("public");
|
|
555
|
-
const flag = unwrap(publicAt?.value);
|
|
556
|
-
const isPublic = flag?.type === "Literal" && flag.value === true;
|
|
557
|
-
const publicUnread = publicAt !== undefined && !(flag?.type === "Literal" && typeof flag.value === "boolean");
|
|
558
|
-
if (publicUnread) problem(publicAt, unread("`public`", "`true` or `false`"));
|
|
559
|
-
|
|
560
|
-
const loginAt = property("login");
|
|
561
|
-
const login = stringValue(loginAt?.value);
|
|
477
|
+
const { title, icon, public: isPublic, login, locales } = parsed.data;
|
|
562
478
|
const loginProblem = (): string | undefined => {
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
if (!
|
|
566
|
-
if (!connectors.vendors.has(login)) return `login names "${login}", which is not a connector in sanoma.config.ts`;
|
|
567
|
-
if (!connectors.logins.has(login)) return `login names "${login}", whose connector has no login half`;
|
|
479
|
+
if (!isPublic) return "a login is for a public site: add `public: true`, or remove `login`";
|
|
480
|
+
if (!connectors.vendors.has(login!)) return `login names "${login}", which is not a connector in sanoma.config.ts`;
|
|
481
|
+
if (!connectors.logins.has(login!)) return `login names "${login}", whose connector has no login half`;
|
|
568
482
|
return undefined;
|
|
569
483
|
};
|
|
570
|
-
const
|
|
571
|
-
if (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
// A hole or a spread is no string.
|
|
576
|
-
const tags: (string | undefined)[] | undefined =
|
|
577
|
-
list?.type === "ArrayExpression" ? list.elements.map((e: Node) => stringValue(e)) : undefined;
|
|
578
|
-
const locales = tags?.every((tag): tag is string => tag !== undefined) ? tags : undefined;
|
|
579
|
-
const localesProblem = (): string | undefined => {
|
|
580
|
-
if (locales === undefined) return unread("`locales`", "an array of strings");
|
|
581
|
-
if (!locales.length) return "`locales` names a site's languages, its default first: give one or more, or remove it";
|
|
582
|
-
const bad = locales.find((locale) => !LOCALE.test(locale));
|
|
484
|
+
const loginMessage = login !== undefined ? loginProblem() : undefined;
|
|
485
|
+
if (loginMessage) problems.push(problem(loginMessage));
|
|
486
|
+
const localesProblem = (list: string[]): string | undefined => {
|
|
487
|
+
if (!list.length) return "`locales` names a site's languages, its default first: give one or more, or remove it";
|
|
488
|
+
const bad = list.find((locale) => !LOCALE.test(locale));
|
|
583
489
|
if (bad !== undefined) return `"${bad}" is not a language tag such as "en", "fr" or "pt-BR"`;
|
|
584
|
-
const twice =
|
|
490
|
+
const twice = list.find((locale, i) => list.indexOf(locale) !== i);
|
|
585
491
|
if (twice !== undefined) return `"${twice}" is in \`locales\` twice`;
|
|
586
|
-
|
|
587
|
-
if ((!isPublic && !publicUnread) || loginAt) {
|
|
492
|
+
if (!isPublic || login !== undefined) {
|
|
588
493
|
return "`locales` are for a public site with no login: add `public: true`, or remove `locales`";
|
|
589
494
|
}
|
|
590
495
|
return undefined;
|
|
591
496
|
};
|
|
592
|
-
const localesMessage =
|
|
593
|
-
if (localesMessage) problem(
|
|
497
|
+
const localesMessage = locales && localesProblem(locales);
|
|
498
|
+
if (localesMessage) problems.push(problem(localesMessage));
|
|
594
499
|
return {
|
|
500
|
+
at: { file, export: "default" },
|
|
595
501
|
...(title !== undefined && { title }),
|
|
596
502
|
...(icon !== undefined && { icon }),
|
|
597
503
|
...(isPublic && { public: true as const }),
|
|
598
504
|
...(login !== undefined && { login }),
|
|
599
|
-
...(
|
|
505
|
+
...(locales && !localesMessage && { locales }),
|
|
600
506
|
problems,
|
|
601
507
|
};
|
|
602
508
|
}
|
package/src/steps.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LedgerRecord } from "./ledger.ts";
|
|
2
2
|
import type { Op } from "./op.ts";
|
|
3
|
+
import type { ConnectionRef } from "./connection.ts";
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
* Steps a connector (or a company) ships for its Gherkin tests, as plain data: expressions and
|
|
@@ -19,8 +20,8 @@ type AnyOp<I = any, O = any> = Op<string, string, string, I, O>;
|
|
|
19
20
|
* seed's name (else the type's) as the export.
|
|
20
21
|
*/
|
|
21
22
|
export type Seed =
|
|
22
|
-
| { op: AnyOp; input: Record<string, unknown>; as?: string }
|
|
23
|
-
| { op: AnyOp; fault: "failNext" | "rateLimit" | "loseReply" };
|
|
23
|
+
| { op: AnyOp; input: Record<string, unknown>; as?: string; connection?: ConnectionRef }
|
|
24
|
+
| { op: AnyOp; fault: "failNext" | "rateLimit" | "loseReply"; connection?: ConnectionRef };
|
|
24
25
|
|
|
25
26
|
/** A call through an operation: what it was given, and what it returned. */
|
|
26
27
|
export interface Call<I, O> {
|
package/src/version.ts
CHANGED
|
@@ -18,8 +18,9 @@ export const RUNTIME_VERSION = (
|
|
|
18
18
|
* 3: a start the policy holds waits on its approval before the body (2026-10-21).
|
|
19
19
|
* 4: a sleep for a duration reads DBOS's clock for its start (2026-10-25).
|
|
20
20
|
* 5: a scheduled run admits its starter and asks the policy in two steps before the body (2026-10-25).
|
|
21
|
+
* 6: pin a named connection before policy and recheck its binding before use (2026-10-29).
|
|
21
22
|
*/
|
|
22
|
-
export const STEP_LAYOUT =
|
|
23
|
+
export const STEP_LAYOUT = 6;
|
|
23
24
|
|
|
24
25
|
let dbosVersion: string | undefined;
|
|
25
26
|
|
package/src/wakeup-driver.ts
CHANGED
|
@@ -154,8 +154,11 @@ const env = z.object({
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
/** The variable `name`, read when an operation is called, or the driver's refusal. */
|
|
157
|
-
function required(
|
|
158
|
-
|
|
157
|
+
function required(
|
|
158
|
+
values: Record<string, string | undefined>,
|
|
159
|
+
name: "SANOMA_WAKEUP_URL" | "SANOMA_WAKEUP_ACCOUNT_TOKEN",
|
|
160
|
+
): string {
|
|
161
|
+
const value = values[name];
|
|
159
162
|
if (!value) throw refused(`${name} is not set (${env.shape[name].description})`);
|
|
160
163
|
return value;
|
|
161
164
|
}
|
|
@@ -167,10 +170,10 @@ const Problem = z.object({ error: z.string() });
|
|
|
167
170
|
const instance = (id: string) => `/wakeup/instances/${encodeURIComponent(id)}`;
|
|
168
171
|
|
|
169
172
|
/** The service at `SANOMA_WAKEUP_URL`, read when an operation is called: each request's failure a `DriverError`. */
|
|
170
|
-
function serviceApi(timeoutMs: number): WakeupApi {
|
|
171
|
-
const base = required("SANOMA_WAKEUP_URL").replace(/\/+$/, "");
|
|
173
|
+
function serviceApi(timeoutMs: number, values: Record<string, string | undefined>): WakeupApi {
|
|
174
|
+
const base = required(values, "SANOMA_WAKEUP_URL").replace(/\/+$/, "");
|
|
172
175
|
if (!URL.canParse(base)) throw refused("SANOMA_WAKEUP_URL is not a URL");
|
|
173
|
-
const account = () => required("SANOMA_WAKEUP_ACCOUNT_TOKEN");
|
|
176
|
+
const account = () => required(values, "SANOMA_WAKEUP_ACCOUNT_TOKEN");
|
|
174
177
|
/**
|
|
175
178
|
* One request: its reply parsed by `reply` (none: not read), a 404 `undefined` where `absent`
|
|
176
179
|
* says it means no such instance, and any other answer but a 2xx a `DriverError`.
|
|
@@ -257,7 +260,20 @@ function serviceApi(timeoutMs: number): WakeupApi {
|
|
|
257
260
|
* `register` send). `timeoutMs` is how long one request may take: 10 s by default.
|
|
258
261
|
*/
|
|
259
262
|
export function sanomaWakeupDriver({ timeoutMs = 10_000 }: { timeoutMs?: number } = {}) {
|
|
260
|
-
|
|
263
|
+
const call = async (name: keyof ReturnType<typeof wakeupCalls>, input: unknown, context: CallContext) => {
|
|
264
|
+
const values = Object.fromEntries(
|
|
265
|
+
await Promise.all(Object.keys(env.shape).map(async (key) => [key, await context.secrets.get(key)])),
|
|
266
|
+
);
|
|
267
|
+
const methods = wakeupCalls(() => serviceApi(timeoutMs, values));
|
|
268
|
+
return (methods[name] as (input: unknown, context: CallContext) => Promise<unknown>)(input, context);
|
|
269
|
+
};
|
|
270
|
+
const methods = Object.fromEntries(
|
|
271
|
+
["import", "read", "plan", "create", "update", "delete", "register"].map((name) => [
|
|
272
|
+
name,
|
|
273
|
+
(input: unknown, context: CallContext) => call(name as keyof ReturnType<typeof wakeupCalls>, input, context),
|
|
274
|
+
]),
|
|
275
|
+
);
|
|
276
|
+
return defineDriver(wakeupOps, { wakeup: methods } as never, { env });
|
|
261
277
|
}
|
|
262
278
|
|
|
263
279
|
/** The wakeup's step for Gherkin tests: an instance at the service before the run, named `wakeup` (`$wakeup.id`). */
|
package/src/worker.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { awaitApproval } from "./approvals.ts";
|
|
|
3
3
|
import { buildCtx, decideStart, isInfrastructureError, startHoldOf } from "./call.ts";
|
|
4
4
|
import { dbosStatusesOf } from "./client.ts";
|
|
5
5
|
import { declaredWakeup, type ResolvedConfig, resolveConfig, type SanomaConfig } from "./config.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { configuredSecrets } from "./credentials.ts";
|
|
7
7
|
import { type Db, DBOS_DATABASE_OPTIONS, lazyPool, prepareDatabase, stampVersion } from "./db.ts";
|
|
8
8
|
import { deadlineRegistry, type Deadlines, idle, onOwnQueue, type SelfStop, stopWhenIdle } from "./deadlines.ts";
|
|
9
9
|
import type { WorkflowDefinition } from "./define.ts";
|
|
@@ -123,6 +123,7 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
123
123
|
const horizonMs = options.idleHorizonMs ?? 60_000;
|
|
124
124
|
const state: WorkerState = {
|
|
125
125
|
app: resolved.appName,
|
|
126
|
+
connections: resolved.connections,
|
|
126
127
|
ops: resolved.ops,
|
|
127
128
|
drivers: resolved.drivers,
|
|
128
129
|
fakes: resolved.fakes,
|
|
@@ -134,7 +135,7 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
134
135
|
policy: resolved.policy,
|
|
135
136
|
...(resolved.root !== undefined && { root: resolved.root }),
|
|
136
137
|
ledger: ledger.store,
|
|
137
|
-
secrets:
|
|
138
|
+
secrets: configuredSecrets(resolved.secrets, () => secrets.db()),
|
|
138
139
|
workflows: resolved.workflows,
|
|
139
140
|
outlines: new Map([...resolved.workflows].map(([name, wf]) => [name, outlineOf(wf)])),
|
|
140
141
|
// `registered` is filled below, before any run starts: every name of this config's is registered then.
|
|
@@ -146,7 +147,6 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
146
147
|
stopped: false,
|
|
147
148
|
deadlines: deadlineRegistry(),
|
|
148
149
|
};
|
|
149
|
-
const credentials = await watchCredentials(resolved.databaseUrl, resolved.driversByVendor);
|
|
150
150
|
const worker: Worker = {
|
|
151
151
|
unconfigured: [...credentialsOf(resolved.driversByVendor, (name) => process.env[name])]
|
|
152
152
|
.filter(([vendor, statuses]) => unconfigured(vendor, statuses))
|
|
@@ -164,8 +164,8 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
164
164
|
let failed: { error: unknown } | undefined;
|
|
165
165
|
for (const close of [
|
|
166
166
|
() => DBOS.shutdown(),
|
|
167
|
-
() => credentials.close(),
|
|
168
167
|
() => secrets.close(),
|
|
168
|
+
() => resolved.connectionPool.close(),
|
|
169
169
|
() => ledger.close(),
|
|
170
170
|
async () => resolved.dataStore?.close(),
|
|
171
171
|
]) {
|
|
@@ -184,6 +184,7 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
184
184
|
const previous = current;
|
|
185
185
|
let launched = false;
|
|
186
186
|
try {
|
|
187
|
+
await configuredSecrets(resolved.secrets, () => secrets.db()).validate();
|
|
187
188
|
for (const name of resolved.workflows.keys()) {
|
|
188
189
|
if (!registered.has(name)) registered.set(name, register(name));
|
|
189
190
|
}
|
|
@@ -252,9 +253,12 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
|
|
|
252
253
|
let stopFailed: { error: unknown } | undefined;
|
|
253
254
|
if (launched) await worker.stop().catch((error: unknown) => (stopFailed = { error }));
|
|
254
255
|
else {
|
|
255
|
-
await
|
|
256
|
-
.close()
|
|
257
|
-
.
|
|
256
|
+
await Promise.all([
|
|
257
|
+
secrets.close(),
|
|
258
|
+
ledger.close(),
|
|
259
|
+
resolved.dataStore?.close(),
|
|
260
|
+
resolved.connectionPool.close(),
|
|
261
|
+
]);
|
|
258
262
|
}
|
|
259
263
|
current = previous;
|
|
260
264
|
if (stopFailed) {
|
|
@@ -438,7 +442,7 @@ async function runOn(state: WorkerState, name: string, start: RunArgs | Date): P
|
|
|
438
442
|
if (sandbox !== undefined) {
|
|
439
443
|
// Imported here, so a live worker never loads the Gherkin parser or faker.
|
|
440
444
|
const { seedSandbox } = await import("./sandbox.ts");
|
|
441
|
-
await seedSandbox(run, sandbox);
|
|
445
|
+
await seedSandbox(run, sandbox, parsed);
|
|
442
446
|
}
|
|
443
447
|
output = await currentRun.run(run, () => wf.run(buildCtx(wf, run), parsed));
|
|
444
448
|
} finally {
|