@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/describe.ts
CHANGED
|
@@ -1,53 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
2
|
import type { z } from "zod";
|
|
3
3
|
import { type AppEntry, appsAt } from "./apps.ts";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { readSource, type SourceFile } from "./ast.ts";
|
|
5
|
+
import { readPolicyFiles } from "./cedar.ts";
|
|
6
|
+
import { recordSchemaOf } from "./collection.ts";
|
|
6
7
|
import { connectorsOf, resolveConfig, rootOf, type SanomaConfig } from "./config.ts";
|
|
7
|
-
import { type Builtin, jsonSchemaOf, opIdsOf, type
|
|
8
|
+
import { type Builtin, jsonSchemaOf, opIdsOf, type Use } from "./define.ts";
|
|
9
|
+
import {
|
|
10
|
+
CAPABILITIES,
|
|
11
|
+
type CollectionEntry,
|
|
12
|
+
type ConfigDescription,
|
|
13
|
+
type OpEntry,
|
|
14
|
+
type ResourceTypeEntry,
|
|
15
|
+
type VendorEntry,
|
|
16
|
+
type WorkflowEntry,
|
|
17
|
+
} from "./description.ts";
|
|
8
18
|
import { isDirect } from "./direct.ts";
|
|
19
|
+
import { type CredentialStatus, credentialsOf } from "./env.ts";
|
|
9
20
|
import { lintWorkflow } from "./lint.ts";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
21
|
+
import { fresh, stampOf, stampsOf } from "./load.ts";
|
|
22
|
+
import { LOCKFILE } from "./lockfile.ts";
|
|
23
|
+
import { VENDOR, type VendorInfo } from "./op.ts";
|
|
24
|
+
import { outlineWithSource } from "./outline.ts";
|
|
25
|
+
import { APPS_DIR, kindDirs } from "./paths.ts";
|
|
12
26
|
import { allowAll, policyOpOf } from "./policy.ts";
|
|
13
|
-
import { type Resource,
|
|
14
|
-
import {
|
|
15
|
-
type DataFiles,
|
|
16
|
-
type DeclaredCollection,
|
|
17
|
-
type DeclaredResource,
|
|
18
|
-
inAppPeople,
|
|
19
|
-
readDataFiles,
|
|
20
|
-
type ResourceProblem,
|
|
21
|
-
} from "./resources.ts";
|
|
27
|
+
import { type Resource, resourceTypesOf, valueAt } from "./resource.ts";
|
|
28
|
+
import { type DeclaredResource, inAppPeople, loadDataFiles, type ResourceProblem } from "./resources.ts";
|
|
22
29
|
import { apiNameProblem } from "./sdk.ts";
|
|
23
|
-
import { apiNameOf, BUILT_IN_WORKFLOWS, DATA_VENDOR, problemLine } from "./shared.ts";
|
|
30
|
+
import { apiNameOf, BUILT_IN_WORKFLOWS, DATA_VENDOR, errorMessage, problemLine } from "./shared.ts";
|
|
24
31
|
import { readSites, type SiteEntry } from "./sites.ts";
|
|
25
32
|
|
|
26
33
|
// `@sanoma-ai/workflows/describe`: what a UI renders from, apart from the main entry.
|
|
34
|
+
// What `describeConfig` answers, as zod schemas and the types they infer (`./description`).
|
|
35
|
+
export * from "./description.ts";
|
|
27
36
|
export { outlineWorkflow, type Outline, type OutlineNode, type Span } from "./outline.ts";
|
|
28
|
-
|
|
29
|
-
export type { LintProblem } from "./ast.ts";
|
|
30
|
-
export {
|
|
31
|
-
admit,
|
|
32
|
-
readDataFiles,
|
|
33
|
-
readPeople,
|
|
34
|
-
type DataFiles,
|
|
35
|
-
type DeclaredCollection,
|
|
36
|
-
type DeclaredPeople,
|
|
37
|
-
type DeclaredResource,
|
|
38
|
-
type ResourceProblem,
|
|
39
|
-
} from "./resources.ts";
|
|
37
|
+
export { admit, loadDataFiles, readPeople, type DataFiles, type DeclaredPeople } from "./resources.ts";
|
|
40
38
|
// The writer: setting a resource's fields in its data file, and rendering a new one.
|
|
41
|
-
export {
|
|
42
|
-
|
|
39
|
+
export {
|
|
40
|
+
type DataFileContent,
|
|
41
|
+
type DataValue,
|
|
42
|
+
formatOptions,
|
|
43
|
+
NotLiteral,
|
|
44
|
+
Reference,
|
|
45
|
+
renderDataFile,
|
|
46
|
+
setFields,
|
|
47
|
+
} from "./writer.ts";
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
* The resources the config's data files declare,
|
|
46
|
-
* one error listing every problem,
|
|
50
|
+
* The resources the config's data files declare, as loading them makes them (`loadDataFiles`);
|
|
51
|
+
* throws one error listing every problem, one per line, for a test or CI to fail on.
|
|
47
52
|
* `describeConfig` lists the same, with the problems beside them instead.
|
|
48
53
|
*/
|
|
49
54
|
export function readResources(config: SanomaConfig): DeclaredResource[] {
|
|
50
|
-
const { resources, problems } =
|
|
55
|
+
const { resources, problems } = loadDataFiles(rootOf(config), connectorsOf(config));
|
|
51
56
|
if (problems.length) {
|
|
52
57
|
const lines = problems.map((p) => ` ${problemLine(p)}`);
|
|
53
58
|
throw new Error(`The resources' data files have problems:\n${lines.join("\n")}`);
|
|
@@ -64,19 +69,10 @@ export {
|
|
|
64
69
|
type Bindings,
|
|
65
70
|
type Rebind,
|
|
66
71
|
} from "./lockfile.ts";
|
|
67
|
-
// The apps under `apps/`,
|
|
68
|
-
export { crossAppImports, readApps
|
|
72
|
+
// The apps under `apps/`, with what their `app.ts` declares.
|
|
73
|
+
export { crossAppImports, readApps } from "./apps.ts";
|
|
69
74
|
// What the sites call, read from their source: a workflow's Used by.
|
|
70
|
-
export {
|
|
71
|
-
readSites,
|
|
72
|
-
type SiteCall,
|
|
73
|
-
type SiteEntry,
|
|
74
|
-
type SitePage,
|
|
75
|
-
siteContentPages,
|
|
76
|
-
siteIcons,
|
|
77
|
-
siteLocales,
|
|
78
|
-
sitesUnder,
|
|
79
|
-
} from "./sites.ts";
|
|
75
|
+
export { readSites, siteContentPages, siteIcons, siteLocales, sitesUnder } from "./sites.ts";
|
|
80
76
|
// The generated SDK, `.sanoma/api.ts`: a site's `api` typed from this description.
|
|
81
77
|
export { API_FILE, generateApi, writeApi, writeGenerated } from "./sdk.ts";
|
|
82
78
|
// The Cedar schema, `.sanoma/schema.cedarschema`: what a company's policies are validated and decided against.
|
|
@@ -87,160 +83,51 @@ export { previewRequests } from "./shared.ts";
|
|
|
87
83
|
// The apply workflow's plan and report, which the app renders.
|
|
88
84
|
export type { ApplyInput, ApplyPlan, ApplyReport, ApplyResult, PlanAnswer, PlanStep } from "./apply.ts";
|
|
89
85
|
|
|
90
|
-
/** What a workflow is, read from its definition: enough to draw a start form and show what it may call. */
|
|
91
|
-
export interface WorkflowEntry {
|
|
92
|
-
/** Its name: `<app>.<name>` for one an app's folder defines. */
|
|
93
|
-
name: string;
|
|
94
|
-
/** The app whose folder defines it (`apps/<app>/workflows/`); absent for one at the root. */
|
|
95
|
-
app?: string;
|
|
96
|
-
title?: string;
|
|
97
|
-
/** `manual`, or the schedule it starts on (`defineWorkflow`'s `trigger`). */
|
|
98
|
-
trigger: Trigger;
|
|
99
|
-
/**
|
|
100
|
-
* Set for a workflow the runtime adds itself, `drift`, `apply` or `call`, rather than the config: a label. It
|
|
101
|
-
* starts like any other.
|
|
102
|
-
*/
|
|
103
|
-
builtin?: true;
|
|
104
|
-
/** The input, as JSON Schema (draft 2020-12), from the zod schema. */
|
|
105
|
-
input: Record<string, unknown>;
|
|
106
|
-
/** Operation ids the workflow may call, in the order declared. */
|
|
107
|
-
ops: string[];
|
|
108
|
-
/** Built-ins the workflow may call. */
|
|
109
|
-
builtins: Builtin[];
|
|
110
|
-
/** The file that defined it, absolute. */
|
|
111
|
-
file: string;
|
|
112
|
-
/** What its `run` calls, in order, read from its file (`outlineWorkflow`), which the worker holds its runs to. */
|
|
113
|
-
outline: Outline;
|
|
114
|
-
/**
|
|
115
|
-
* What is wrong in its file, by position: what `lintWorkflow` finds (syntax, an import a
|
|
116
|
-
* workflow may not make, `instanceof` against the runtime's error classes, what the outline
|
|
117
|
-
* cannot draw in each `run`), read the one way the outline is, so a check sees what the
|
|
118
|
-
* worker refuses; and, at 1:1, an outline error that is the file's (it cannot be read, or holds
|
|
119
|
-
* no workflow of this name). A file defining several workflows gives each its problems. Empty
|
|
120
|
-
* for the built-in `drift`, which the package's tests hold to the lint.
|
|
121
|
-
*/
|
|
122
|
-
problems: LintProblem[];
|
|
123
|
-
/**
|
|
124
|
-
* The text the outline's spans index into: the workflow's file, with `\n` line endings.
|
|
125
|
-
* Absent only when the outline is `{ error }`. Whole files: a server may keep it from clients.
|
|
126
|
-
*/
|
|
127
|
-
source?: string;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** What an operation is, read from its connector: enough to badge it and show its contract. */
|
|
131
|
-
export interface OpEntry {
|
|
132
|
-
id: string;
|
|
133
|
-
vendor: string;
|
|
134
|
-
resource: string;
|
|
135
|
-
name: string;
|
|
136
|
-
effect: Effect;
|
|
137
|
-
idempotent: boolean;
|
|
138
|
-
description?: string;
|
|
139
|
-
/** How a scenario's steps name it: Cucumber expressions over its input's fields. */
|
|
140
|
-
phrases?: { given?: string; expect?: string };
|
|
141
|
-
/** Whether it may be called directly, outside a run (`isDirect`): the one rule, read here, never derived again. */
|
|
142
|
-
direct: boolean;
|
|
143
|
-
/** What a call sends, as JSON Schema (`io: "input"`: fields with defaults are optional). */
|
|
144
|
-
input: Record<string, unknown>;
|
|
145
|
-
/** What a call returns once its schema has parsed the vendor's reply (`io: "output"`). */
|
|
146
|
-
output: Record<string, unknown>;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Who a vendor is, from its connector's `VendorInfo`: enough to name it, show its logo and link to its connector. */
|
|
150
|
-
export interface VendorEntry {
|
|
151
|
-
/** The connector's `title`, else the vendor's id. */
|
|
152
|
-
title: string;
|
|
153
|
-
/** The logo as `data:image/svg+xml` URLs, for an `<img>`: `dark` is for dark backgrounds. */
|
|
154
|
-
logo?: { src: string; dark?: string };
|
|
155
|
-
/** The connector's npm package name. */
|
|
156
|
-
package?: string;
|
|
157
|
-
/** The connector package's `homepage` from its package.json: where its code and README are, as an https URL. */
|
|
158
|
-
homepage?: string;
|
|
159
|
-
/** Whether the vendor's connector attaches a fake (`withSetup`), for sandbox runs. */
|
|
160
|
-
fake: boolean;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** A resource type a connector declares with `defineResource`. */
|
|
164
|
-
export interface ResourceTypeEntry {
|
|
165
|
-
/** `<vendor>.<type>`, such as `github.repository`. */
|
|
166
|
-
id: string;
|
|
167
|
-
vendor: string;
|
|
168
|
-
type: string;
|
|
169
|
-
title: string;
|
|
170
|
-
/** How a declared resource's identity is made, such as `name` or `repository_id:pattern`; absent when it is found only by its binding. */
|
|
171
|
-
identity?: string;
|
|
172
|
-
/** Its flagged fields, by dotted path. */
|
|
173
|
-
fields: ResourceFields;
|
|
174
|
-
/**
|
|
175
|
-
* Its state, as a read returns it, as JSON Schema (`io: "output"`), with `$id`
|
|
176
|
-
* `sanoma:resource-type/<id>`: the operations' contracts `$ref` it instead of repeating it.
|
|
177
|
-
*/
|
|
178
|
-
schema: Record<string, unknown>;
|
|
179
|
-
/** Its operations' ids, sorted: `<id>.import` and `<id>.read`, and for a writable type its change operations. */
|
|
180
|
-
ops: string[];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** A collection the config declares (`collection()`). */
|
|
184
|
-
export interface CollectionEntry {
|
|
185
|
-
/** Its name: `<app>.<name>` for one an app's `resources/data.ts` declares. */
|
|
186
|
-
name: string;
|
|
187
|
-
/** The app whose folder declares it (`apps/<app>/resources/data.ts`); absent for one at the root. */
|
|
188
|
-
app?: string;
|
|
189
|
-
title?: string;
|
|
190
|
-
/** The field each record is found by. */
|
|
191
|
-
key: string;
|
|
192
|
-
/** Its fields as declared. */
|
|
193
|
-
fields: Record<string, Field>;
|
|
194
|
-
/** What `save` accepts, as JSON Schema, with `$id` `sanoma:collection/<name>`. */
|
|
195
|
-
record: Record<string, unknown>;
|
|
196
|
-
/** Its operations' ids: `data.<name>.get`, `.list`, `.save` and `.delete`. */
|
|
197
|
-
ops: string[];
|
|
198
|
-
/** Where `resources/data.ts` declares it, read as the data files are; absent for one declared elsewhere. */
|
|
199
|
-
declared?: DeclaredCollection;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
86
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
87
|
+
* `describeConfig(config)`, kept and made again only once the files it loaded have changed: each
|
|
88
|
+
* company file a load read and its folder (`stampsOf`), the lockfile, and the folders a new data
|
|
89
|
+
* file, app or site would appear in, one `stat` each per call. What the config imports (its
|
|
90
|
+
* workflows, connectors and policies) is read once, as the worker reads it. When describing again
|
|
91
|
+
* throws, the last description stands until the files change again.
|
|
206
92
|
*/
|
|
207
|
-
export
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
93
|
+
export function liveDescription(config: SanomaConfig): () => ConfigDescription {
|
|
94
|
+
const root = rootOf(config);
|
|
95
|
+
if (root === undefined) {
|
|
96
|
+
const description = describeConfig(config);
|
|
97
|
+
return () => description;
|
|
98
|
+
}
|
|
99
|
+
/** The description and what it read; `last`, kept (and said) when describing again throws. */
|
|
100
|
+
const described = (last?: ConfigDescription) => {
|
|
101
|
+
// The folders and the lockfile as they are before describing, so a change while it reads is seen after.
|
|
102
|
+
const extra = [
|
|
103
|
+
resolve(root, LOCKFILE),
|
|
104
|
+
resolve(root, APPS_DIR),
|
|
105
|
+
...kindDirs(root, "sites"),
|
|
106
|
+
...kindDirs(root, "resources"),
|
|
107
|
+
].map((path) => [path, stampOf(path)] as const);
|
|
108
|
+
let description: ConfigDescription;
|
|
109
|
+
try {
|
|
110
|
+
description = describeConfig(config);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (last === undefined) throw err;
|
|
113
|
+
console.warn(
|
|
114
|
+
`sanoma: the files could not be described again, so the last description stands: ${errorMessage(err)}`,
|
|
115
|
+
);
|
|
116
|
+
description = last;
|
|
117
|
+
}
|
|
118
|
+
return { description, stamps: new Map([...extra, ...stampsOf(root)]) };
|
|
119
|
+
};
|
|
120
|
+
let now = described();
|
|
121
|
+
// The workflows as the config imported them, which the worker runs: their outlines and sources
|
|
122
|
+
// from the first description, never a file edited since.
|
|
123
|
+
const { workflows } = now.description;
|
|
124
|
+
return () => {
|
|
125
|
+
if (!fresh(now.stamps)) {
|
|
126
|
+
now = described(now.description);
|
|
127
|
+
now.description.workflows = workflows;
|
|
128
|
+
}
|
|
129
|
+
return now.description;
|
|
130
|
+
};
|
|
244
131
|
}
|
|
245
132
|
|
|
246
133
|
/**
|
|
@@ -254,31 +141,45 @@ export interface ConfigDescription {
|
|
|
254
141
|
export function describeConfig(config: SanomaConfig): ConfigDescription {
|
|
255
142
|
const resolved = resolveConfig(config);
|
|
256
143
|
const vendors: Record<string, VendorEntry> = {};
|
|
144
|
+
// The variables each driver declares, without a value: their names and manuals alone.
|
|
145
|
+
const env = credentialsOf(resolved.driversByVendor, () => undefined);
|
|
257
146
|
// A vendor whose operations are split over several connectors is named by the first; its
|
|
258
147
|
// resource types are every connector's.
|
|
259
148
|
for (const connector of config.connectors) {
|
|
260
149
|
const { id, info } = connector[VENDOR];
|
|
261
|
-
vendors[id] ??= vendorEntry(id, info, resolved.fakes.has(id));
|
|
150
|
+
vendors[id] ??= vendorEntry(id, info, resolved.fakes.has(id), env.get(id));
|
|
262
151
|
}
|
|
263
152
|
const resources = resourceTypesOf(connectorsOf(config));
|
|
153
|
+
// The types a host's `source` names: the repositories a host clones.
|
|
154
|
+
const sources = new Set(
|
|
155
|
+
[...resources.values()].flatMap(({ host, fields }) => {
|
|
156
|
+
const named = host && fields.references?.[host.inputs.source]?.type;
|
|
157
|
+
return named === undefined ? [] : [named].flat();
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
264
160
|
// Each resource type's state is described once, on its entry, and referenced from its operations.
|
|
265
161
|
const refs = new Map<z.ZodType, string>([...resources].map(([id, r]) => [r.schema, resourceTypeUri(id)]));
|
|
266
162
|
const resourceTypes: ResourceTypeEntry[] = [...resources]
|
|
267
|
-
.map(([id,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
163
|
+
.map(([id, spec]) => {
|
|
164
|
+
const { vendor, type, title, identity, fields, schema, ops } = spec;
|
|
165
|
+
const capabilities = CAPABILITIES.filter((c) => (c === "source" ? sources.has(id) : spec[c] !== undefined));
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
vendor,
|
|
169
|
+
type,
|
|
170
|
+
title,
|
|
171
|
+
identity,
|
|
172
|
+
fields,
|
|
173
|
+
schema: { $id: resourceTypeUri(id), ...toJsonSchema(schema, `${id} state`, "output") },
|
|
174
|
+
ops: Object.keys(ops)
|
|
175
|
+
.map((name) => `${id}.${name}`)
|
|
176
|
+
.toSorted(),
|
|
177
|
+
...(capabilities.length ? { capabilities } : {}),
|
|
178
|
+
};
|
|
179
|
+
})
|
|
279
180
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
280
181
|
|
|
281
|
-
const dataFiles =
|
|
182
|
+
const dataFiles = loadDataFiles(resolved.root, connectorsOf(config), resources);
|
|
282
183
|
const declared = new Map(dataFiles.collections.map((d) => [d.name, d]));
|
|
283
184
|
const collections: CollectionEntry[] = [...resolved.collections.values()]
|
|
284
185
|
.map((c) => ({
|
|
@@ -344,6 +245,28 @@ export function describeConfig(config: SanomaConfig): ConfigDescription {
|
|
|
344
245
|
);
|
|
345
246
|
|
|
346
247
|
return {
|
|
248
|
+
secrets: {
|
|
249
|
+
store: resolved.secrets.store?.id ?? "postgres",
|
|
250
|
+
namespace: resolved.secrets.namespace ?? "default",
|
|
251
|
+
bindings: Object.fromEntries(
|
|
252
|
+
Object.entries(resolved.secrets.bindings ?? {}).map(([name, b]) => [
|
|
253
|
+
name,
|
|
254
|
+
{
|
|
255
|
+
path: b.bootstrap ?? b.env ?? b.path!,
|
|
256
|
+
management: b.management ?? (b.env || b.bootstrap ? "external" : "sanoma"),
|
|
257
|
+
store: b.bootstrap ? "bootstrap" : b.env ? "environment" : (resolved.secrets.store?.id ?? "postgres"),
|
|
258
|
+
},
|
|
259
|
+
]),
|
|
260
|
+
),
|
|
261
|
+
},
|
|
262
|
+
connections: [...resolved.connections.catalog.definitions.values()].map((d) => ({
|
|
263
|
+
id: d.$connection,
|
|
264
|
+
vendor: d.vendor,
|
|
265
|
+
method: d.method,
|
|
266
|
+
scopes: d.scopes,
|
|
267
|
+
...(d.account && { account: d.account }),
|
|
268
|
+
default: d.default,
|
|
269
|
+
})),
|
|
347
270
|
appName: resolved.appName,
|
|
348
271
|
version: resolved.version,
|
|
349
272
|
workflows,
|
|
@@ -358,13 +281,14 @@ export function describeConfig(config: SanomaConfig): ConfigDescription {
|
|
|
358
281
|
...dataFiles.problems,
|
|
359
282
|
...appProblems,
|
|
360
283
|
...appClashes(apps, collections, sites),
|
|
361
|
-
...domainProblems(
|
|
284
|
+
...domainProblems(dataFiles.resources, resources, sites),
|
|
362
285
|
],
|
|
363
286
|
apps,
|
|
364
287
|
policy: {
|
|
365
288
|
defined: resolved.policy !== allowAll,
|
|
366
289
|
...(resolved.policy.version === undefined ? {} : { version: resolved.policy.version }),
|
|
367
290
|
...(resolved.policyProblems.length ? { problems: resolved.policyProblems } : {}),
|
|
291
|
+
files: resolved.root === undefined ? [] : readPolicyFiles(resolved.root),
|
|
368
292
|
},
|
|
369
293
|
sites,
|
|
370
294
|
};
|
|
@@ -404,11 +328,9 @@ function appClashes(
|
|
|
404
328
|
* only a public site at a domain, and one site at each.
|
|
405
329
|
*/
|
|
406
330
|
function domainProblems(
|
|
407
|
-
root: string | undefined,
|
|
408
331
|
declared: readonly DeclaredResource[],
|
|
409
332
|
types: ReadonlyMap<string, Resource>,
|
|
410
333
|
sites: readonly SiteEntry[],
|
|
411
|
-
read: (path: string) => SourceFile | string,
|
|
412
334
|
): ResourceProblem[] {
|
|
413
335
|
const isPublic = new Set(sites.filter((site) => site.public).map((site) => site.name));
|
|
414
336
|
const problems: ResourceProblem[] = [];
|
|
@@ -426,22 +348,11 @@ function domainProblems(
|
|
|
426
348
|
const others = `${names.length === 2 ? "both " : ""}${names.slice(0, -1).join(", ")}`;
|
|
427
349
|
messages.push(`${domain} is the domain of ${others} and ${names.at(-1)}`);
|
|
428
350
|
}
|
|
429
|
-
|
|
351
|
+
problems.push(...messages.map((message) => ({ ...resource.at, message })));
|
|
430
352
|
}
|
|
431
353
|
return problems;
|
|
432
354
|
}
|
|
433
355
|
|
|
434
|
-
/** `message` at a declaration's `export const`. */
|
|
435
|
-
function problemAt(
|
|
436
|
-
root: string,
|
|
437
|
-
resource: DeclaredResource,
|
|
438
|
-
read: (path: string) => SourceFile | string,
|
|
439
|
-
message: string,
|
|
440
|
-
): ResourceProblem {
|
|
441
|
-
const file = read(join(root, resource.file));
|
|
442
|
-
return { file: resource.file, ...(typeof file === "string" ? {} : file.at(resource.span[0])), message };
|
|
443
|
-
}
|
|
444
|
-
|
|
445
356
|
/**
|
|
446
357
|
* A site's `api` names each workflow `apiNameOf(name)` beside the vendors' operations and the
|
|
447
358
|
* collections (`api.data`), so a workflow whose name comes out as another's, a vendor's id or
|
|
@@ -477,7 +388,12 @@ function memoised<A, R>(fn: (arg: A) => R): (arg: A) => R {
|
|
|
477
388
|
/** A logo's SVG markup as a `data:` URL, for an `<img>`. */
|
|
478
389
|
export const svgDataUrl = (svg: string) => `data:image/svg+xml,${encodeURIComponent(svg.trim())}`;
|
|
479
390
|
|
|
480
|
-
const vendorEntry = (
|
|
391
|
+
const vendorEntry = (
|
|
392
|
+
id: string,
|
|
393
|
+
info: VendorInfo | undefined,
|
|
394
|
+
fake: boolean,
|
|
395
|
+
env: readonly CredentialStatus[] | undefined,
|
|
396
|
+
): VendorEntry => ({
|
|
481
397
|
title: info?.title ?? id,
|
|
482
398
|
...(info?.logo && {
|
|
483
399
|
logo: { src: svgDataUrl(info.logo.svg), ...(info.logo.dark && { dark: svgDataUrl(info.logo.dark) }) },
|
|
@@ -485,6 +401,13 @@ const vendorEntry = (id: string, info: VendorInfo | undefined, fake: boolean): V
|
|
|
485
401
|
...(info?.package && { package: info.package }),
|
|
486
402
|
...(info?.homepage && { homepage: info.homepage }),
|
|
487
403
|
fake,
|
|
404
|
+
...(env && {
|
|
405
|
+
credentials: env.map(({ name, description, optional }) => ({
|
|
406
|
+
name,
|
|
407
|
+
...(description !== undefined && { description }),
|
|
408
|
+
...(optional && { optional }),
|
|
409
|
+
})),
|
|
410
|
+
}),
|
|
488
411
|
});
|
|
489
412
|
|
|
490
413
|
/** Where a resource type's state schema is, for a `$ref`. */
|