@sanoma-ai/workflows 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -52
- 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 +35 -21
- 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 +7 -24
- package/dist/apps.js +51 -67
- 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 +24 -0
- package/dist/authentication.js +68 -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 +34 -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 +10 -6
- package/dist/client.js +96 -43
- 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 +14 -3
- package/dist/config.js +55 -14
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +168 -0
- package/dist/connection.js +327 -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/cucumber.d.ts +4 -2
- package/dist/cucumber.js +8 -3
- package/dist/cucumber.js.map +1 -1
- package/dist/db.js +21 -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 +9 -177
- package/dist/describe.js +71 -34
- 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 +3 -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/index.d.ts +10 -2
- 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 +1 -16
- package/dist/lint.js.map +1 -1
- package/dist/load.d.ts +74 -0
- package/dist/load.js +357 -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 +10 -1
- package/dist/op.js +1 -1
- package/dist/op.js.map +1 -1
- package/dist/outline.js +13 -4
- package/dist/outline.js.map +1 -1
- package/dist/paths.d.ts +18 -0
- package/dist/paths.js +21 -3
- 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 +27 -4
- package/dist/resource.js +49 -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 +2 -0
- package/dist/run.js.map +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +7 -3
- package/dist/sandbox.js.map +1 -1
- package/dist/scenario.d.ts +7 -0
- package/dist/scenario.js +33 -3
- 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/shared.d.ts +17 -11
- package/dist/shared.js +34 -7
- package/dist/shared.js.map +1 -1
- package/dist/sites.d.ts +4 -93
- package/dist/sites.js +61 -84
- package/dist/sites.js.map +1 -1
- package/dist/source.d.ts +6 -0
- package/dist/source.js +13 -0
- package/dist/source.js.map +1 -1
- package/dist/steps.d.ts +3 -1
- 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 +0 -5
- package/package.json +6 -2
- package/src/apply-steps.ts +5 -2
- package/src/apply.ts +70 -29
- package/src/approvals.ts +7 -1
- package/src/apps.ts +54 -85
- package/src/ast.ts +6 -6
- package/src/authentication.ts +74 -0
- package/src/call-workflow.ts +5 -4
- package/src/call.ts +53 -9
- package/src/cedar.ts +14 -1
- package/src/change-steps.ts +7 -4
- package/src/change.ts +10 -0
- package/src/client.ts +108 -41
- package/src/collection.ts +10 -2
- package/src/config.ts +72 -13
- package/src/connection.ts +446 -0
- package/src/credentials.ts +94 -281
- package/src/cucumber.ts +16 -3
- package/src/db.ts +21 -2
- package/src/define.ts +7 -2
- package/src/definitions.ts +31 -0
- package/src/deployment-key.ts +23 -0
- package/src/describe.ts +102 -230
- package/src/description.ts +426 -0
- package/src/direct.ts +3 -0
- package/src/drift.ts +10 -2
- package/src/env.ts +4 -2
- package/src/index.ts +42 -1
- package/src/ledger.ts +3 -1
- package/src/lint.ts +1 -16
- package/src/load.ts +437 -0
- package/src/lockfile.ts +10 -3
- package/src/op.ts +10 -1
- package/src/outline.ts +18 -5
- package/src/paths.ts +24 -2
- package/src/plugin.ts +3 -24
- package/src/policy.ts +2 -0
- package/src/resource.ts +67 -6
- package/src/resources.ts +246 -301
- package/src/run.ts +2 -0
- package/src/sandbox.ts +11 -3
- package/src/scenario.ts +45 -3
- 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/shared.ts +35 -10
- package/src/sites.ts +70 -180
- package/src/source.ts +14 -0
- package/src/steps.ts +3 -1
- 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/dist/resources.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
|
-
import { readFileSync
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { mayDeclarePeople } from "./change.js";
|
|
3
|
+
import { isCollection } from "./collection.js";
|
|
4
|
+
import { filesRead, loadModules, madeOf, madeWith } from "./load.js";
|
|
5
5
|
import { LOCKFILE, readLockfile } from "./lockfile.js";
|
|
6
|
-
import {
|
|
7
|
-
import { APPS_DIR, filesUnder, kindDirs } from "./paths.js";
|
|
6
|
+
import { APPS_DIR, COLLECTIONS_FILE, filesUnder, kindDirs, relPath } from "./paths.js";
|
|
8
7
|
import { peopleConnector, sanoma } from "./person.js";
|
|
9
8
|
import { wakeupOps } from "./wakeup-type.js";
|
|
10
|
-
import { resourceTypesOf } from "./resource.js";
|
|
9
|
+
import { DECLARED, isDeclared, isObject, resourceTypesOf } from "./resource.js";
|
|
11
10
|
import { Principal } from "./define.js";
|
|
12
11
|
import { NotAdmittedError } from "./errors.js";
|
|
13
|
-
import { AGENTS, ANONYMOUS, ANONYMOUS_PRINCIPAL, declaresPeople, isVisitor, personOf, principalOf, problemLine,
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/** The last read of the people under each root, by what its data files were then (`filesStamp`). */
|
|
17
|
-
const peopleRead = new Map();
|
|
18
|
-
/** Each data file under `root` with its size and modification time: unchanged while the files are. */
|
|
19
|
-
const filesStamp = (root) => kindDirs(root, "resources")
|
|
20
|
-
.flatMap(dataFilesUnder)
|
|
21
|
-
.map((path) => {
|
|
22
|
-
// Gone since it was listed (an editor's save by rename): stamped as such, and read next time.
|
|
23
|
-
const stat = statSync(path, { bigint: true, throwIfNoEntry: false });
|
|
24
|
-
return `${path} ${stat?.size} ${stat?.mtimeNs}`;
|
|
25
|
-
})
|
|
26
|
-
.join("\n");
|
|
12
|
+
import { AGENTS, ANONYMOUS, ANONYMOUS_PRINCIPAL, declaresPeople, isVisitor, personOf, principalOf, problemLine, RUNTIME_AGENTS, VISITORS, } from "./shared.js";
|
|
13
|
+
/** The people each load of the people's files declares: the same answer while none of them changed. */
|
|
14
|
+
const peopleRead = new WeakMap();
|
|
27
15
|
/** The names no data file may give a group: what each is already. */
|
|
28
16
|
const RESERVED_GROUPS = new Map([
|
|
29
17
|
[AGENTS, "the group every agent is in"],
|
|
@@ -35,28 +23,38 @@ const RESERVED_GROUPS = new Map([
|
|
|
35
23
|
* company's, so neither the policy (`readPeople`) nor `describeConfig` counts it.
|
|
36
24
|
*/
|
|
37
25
|
export const inAppPeople = (r) => r.vendor === "sanoma" && Object.hasOwn(sanoma, r.type) && r.file.startsWith(`${APPS_DIR}/`);
|
|
26
|
+
const PEOPLE_CONNECTORS = [peopleConnector, wakeupOps];
|
|
27
|
+
const PEOPLE_TYPES = resourceTypesOf(PEOPLE_CONNECTORS);
|
|
28
|
+
const peopleLoad = (root) => loadModules(root, dataFilesOf(root), PEOPLE_CONNECTORS, { only: mayDeclarePeople, types: PEOPLE_TYPES });
|
|
29
|
+
/**
|
|
30
|
+
* The company files the people under `root` are read from (`readPeople`), by path relative to the
|
|
31
|
+
* root: each file that may declare them and every company file it imports, so an edit to a helper
|
|
32
|
+
* a people file reads its emails from is a change to the rules too.
|
|
33
|
+
*/
|
|
34
|
+
export const peopleSources = (root) => filesRead(peopleLoad(root));
|
|
38
35
|
/**
|
|
39
36
|
* The people, agents and groups the data files under `root` declare (`sanoma.person`, `.agent`,
|
|
40
|
-
* `.group`),
|
|
41
|
-
*
|
|
42
|
-
*
|
|
37
|
+
* `.group`), as `principalOf` takes them, with what is wrong in their files; none without a root.
|
|
38
|
+
* Only the files that may declare them (`mayDeclarePeople`) are loaded, with the people's and the
|
|
39
|
+
* wakeup's constructors, which the same `sanoma` declares; loaded again only when one of them
|
|
40
|
+
* changed (by its size and time), so a new colleague is admitted once their file is saved, and a
|
|
41
|
+
* request costs a `stat` per file.
|
|
43
42
|
*/
|
|
44
43
|
export function readPeople(root) {
|
|
45
44
|
if (root === undefined)
|
|
46
45
|
return { people: [], problems: [] };
|
|
47
|
-
const
|
|
48
|
-
const cached = peopleRead.get(
|
|
49
|
-
if (cached
|
|
50
|
-
return cached
|
|
51
|
-
//
|
|
52
|
-
const read =
|
|
46
|
+
const loaded = peopleLoad(root);
|
|
47
|
+
const cached = peopleRead.get(loaded);
|
|
48
|
+
if (cached)
|
|
49
|
+
return cached;
|
|
50
|
+
// The people are never bound, so the lockfile is not read.
|
|
51
|
+
const read = declarationsOf(root, loaded, PEOPLE_TYPES, { bindings: {}, problems: [] }, true);
|
|
53
52
|
const ofPeople = read.resources.filter((r) => Object.hasOwn(sanoma, r.type));
|
|
54
53
|
const people = ofPeople.filter((r) => !inAppPeople(r));
|
|
55
|
-
|
|
56
|
-
const own = read.problems.filter((p) => p.file !== LOCKFILE);
|
|
54
|
+
const own = read.problems;
|
|
57
55
|
for (const r of ofPeople.filter(inAppPeople)) {
|
|
58
56
|
own.push({
|
|
59
|
-
|
|
57
|
+
...r.at,
|
|
60
58
|
message: `${r.id} declares a ${r.type} in an app: the people are the company's, declared in resources/ at the root`,
|
|
61
59
|
});
|
|
62
60
|
}
|
|
@@ -65,14 +63,14 @@ export function readPeople(root) {
|
|
|
65
63
|
if (r.type === "group") {
|
|
66
64
|
const reserved = RESERVED_GROUPS.get(r.name);
|
|
67
65
|
if (reserved)
|
|
68
|
-
own.push({
|
|
66
|
+
own.push({ ...r.at, message: `${r.id} is named "${r.name}", ${reserved}: give it another name` });
|
|
69
67
|
continue;
|
|
70
68
|
}
|
|
71
69
|
const id = r.name;
|
|
72
70
|
const other = ids.get(id);
|
|
73
71
|
if (other || RUNTIME_AGENTS.includes(id) || isVisitor({ id })) {
|
|
74
72
|
own.push({
|
|
75
|
-
|
|
73
|
+
...r.at,
|
|
76
74
|
message: other
|
|
77
75
|
? `${r.id} has the id "${id}", which ${other.id} has: give each person and agent their own`
|
|
78
76
|
: isVisitor({ id })
|
|
@@ -83,7 +81,7 @@ export function readPeople(root) {
|
|
|
83
81
|
ids.set(id, r);
|
|
84
82
|
}
|
|
85
83
|
const declared = { people, problems: own };
|
|
86
|
-
peopleRead.set(
|
|
84
|
+
peopleRead.set(loaded, declared);
|
|
87
85
|
return declared;
|
|
88
86
|
}
|
|
89
87
|
/**
|
|
@@ -123,253 +121,247 @@ export function admit(asking, { people, problems }, signIn = false) {
|
|
|
123
121
|
throw new NotAdmittedError(who);
|
|
124
122
|
}
|
|
125
123
|
/**
|
|
126
|
-
*
|
|
127
|
-
* `*.test.ts`, `*.d.ts` and `node_modules
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* its
|
|
124
|
+
* Loads `resources/` under `root`, the config's directory, and each app's: each `.ts` file in it
|
|
125
|
+
* but `*.test.ts`, `*.d.ts` and `node_modules` (`loadModules`), its constructor imports (a
|
|
126
|
+
* connector's `<info.package>/resources`, `sanoma/resources`) served the constructors of
|
|
127
|
+
* `types`. Each export whose value a constructor made is a declaration, `<file>#<export>`, in the
|
|
128
|
+
* file that made it (its non-default name when the file gives two); each resource it names is the
|
|
129
|
+
* declaration that holds that very object.
|
|
131
130
|
*
|
|
132
|
-
* Problems:
|
|
133
|
-
*
|
|
134
|
-
*
|
|
131
|
+
* Problems: a file that throws as it loads (a value its type refuses, a type its vendor does not
|
|
132
|
+
* declare, an import of what the config does not serve), at the place its error's stack gives; a
|
|
133
|
+
* resource made but not exported, or exported under two names; one naming a resource no data file
|
|
134
|
+
* exports; a type that is not the config's; and two resources of one type with one identity (and
|
|
135
|
+
* connection). A declaration with a problem is left out, and so is any that names one.
|
|
135
136
|
*
|
|
136
|
-
* The collections `resources/data.ts` declares are listed
|
|
137
|
-
*
|
|
137
|
+
* The collections `resources/data.ts` declares (`collection()`) are listed by their exports
|
|
138
|
+
* (`collections`), matched to no connector, resource type or lockfile: the runtime imports that
|
|
139
|
+
* file. A collection in another file, or anything else exported from `data.ts`, is a problem.
|
|
138
140
|
*
|
|
139
141
|
* Each declaration is joined to its binding in the lockfile (`readLockfile`) by its key,
|
|
140
|
-
* `<file>#<export>`; a binding no export declares is an orphan
|
|
141
|
-
* binding of another type than its declaration's,
|
|
142
|
-
*
|
|
143
|
-
* `partial` reads part of the files: only those whose source it passes, and in them, an import of
|
|
144
|
-
* a connector not in `connectors`, or of a file left out, is left unread rather than reported.
|
|
142
|
+
* `<file>#<export>`; a binding no export declares is an orphan, unless its file failed to load.
|
|
143
|
+
* Problems: the lockfile's, a binding of another type than its declaration's, two declarations
|
|
144
|
+
* bound to one object, and several wakeups.
|
|
145
145
|
*/
|
|
146
|
-
export function
|
|
147
|
-
const problems = [];
|
|
146
|
+
export function loadDataFiles(root, connectors, types = resourceTypesOf(connectors)) {
|
|
148
147
|
if (root === undefined) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
return { resources: [], bindings: {}, orphans: [], collections: [], problems };
|
|
148
|
+
const message = "The config has no root, so its data files cannot be found: make it with defineConfig, which records its file, or set its `root`";
|
|
149
|
+
return { resources: [], bindings: {}, orphans: [], collections: [], problems: [{ message }] };
|
|
153
150
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
const loaded = loadModules(root, dataFilesOf(root), connectors, { types });
|
|
152
|
+
return declarationsOf(root, loaded, types, readLockfile(root), false);
|
|
153
|
+
}
|
|
154
|
+
/** What the loaded data files declare, joined to the lockfile's bindings. */
|
|
155
|
+
function declarationsOf(root, loaded, types, lock, partial) {
|
|
156
|
+
const problems = [...lock.problems];
|
|
157
|
+
const failed = new Set();
|
|
158
|
+
const collectionsFiles = new Set(kindDirs(root, "resources").map((dir) => relPath(root, `${dir}/${COLLECTIONS_FILE}`)));
|
|
159
|
+
const COLLECTIONS_ONLY = "resources/data.ts holds collections only, since workflows and the runtime import it: declare a connector's resources in another data file";
|
|
160
|
+
// Each exported value, by identity, to the names it is exported under.
|
|
161
|
+
const names = new Map();
|
|
162
|
+
const exported = new Set();
|
|
163
|
+
const collections = [];
|
|
164
|
+
for (const [file, l] of loaded) {
|
|
165
|
+
if ("failedIn" in l) {
|
|
166
|
+
failed.add(file);
|
|
164
167
|
continue;
|
|
165
168
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
// The runtime's own (`peopleConnector`, package `sanoma`) is imported from either name of its entry.
|
|
183
|
-
if (entries.get("sanoma/resources") === "sanoma")
|
|
184
|
-
for (const entry of SECRET_ENTRIES)
|
|
185
|
-
entries.set(entry, "sanoma");
|
|
186
|
-
// What each file's names stand for, and every resource read by its id.
|
|
187
|
-
const vendors = new Map();
|
|
188
|
-
const named = new Map();
|
|
189
|
-
const declarations = new Map();
|
|
190
|
-
for (const [path, file] of files) {
|
|
191
|
-
const fileVendors = new Map();
|
|
192
|
-
const fileNamed = new Map();
|
|
193
|
-
for (const imp of file.data.imports) {
|
|
194
|
-
if (imp.kind === "constructors") {
|
|
195
|
-
const vendor = entries.get(imp.source);
|
|
196
|
-
if (vendor === undefined && partial)
|
|
197
|
-
continue;
|
|
198
|
-
if (vendor === undefined) {
|
|
199
|
-
file.problem(imp.start, `"${imp.source}" is not the resources entry of a connector in the config's \`connectors\`: add the connector, and import from its package's \`/resources\``);
|
|
200
|
-
}
|
|
201
|
-
else if (imp.imported !== vendor) {
|
|
202
|
-
file.problem(imp.start, `"${imp.source}" exports its constructors as ${vendor}: \`import { ${vendor} } from "${imp.source}"\``);
|
|
169
|
+
if ("error" in l) {
|
|
170
|
+
failed.add(file);
|
|
171
|
+
const message = loadProblem(l.error, root, file, partial);
|
|
172
|
+
if (message !== undefined)
|
|
173
|
+
problems.push({ file, ...(l.line !== undefined && { line: l.line, column: l.column ?? 1 }), message });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
for (const [name, value] of l.exports) {
|
|
177
|
+
const at = { file, export: name };
|
|
178
|
+
if (isCollection(value)) {
|
|
179
|
+
if (!collectionsFiles.has(file)) {
|
|
180
|
+
problems.push({
|
|
181
|
+
...at,
|
|
182
|
+
message: `a collection is declared in resources/data.ts, which workflows and the runtime import: move ${name} there`,
|
|
183
|
+
});
|
|
203
184
|
}
|
|
204
185
|
else {
|
|
205
|
-
|
|
186
|
+
collections.push({ id: `${file}#${name}`, file, name: value.name, at });
|
|
206
187
|
}
|
|
207
|
-
continue;
|
|
208
188
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
else if (!other.data.declared.includes(imp.imported)) {
|
|
217
|
-
file.problem(imp.start, `${imp.imported} is not a resource ${other.rel} declares`);
|
|
218
|
-
}
|
|
219
|
-
else if (other.data.exports.some((e) => e.name === imp.imported)) {
|
|
220
|
-
fileNamed.set(imp.local, `${other.rel}#${imp.imported}`);
|
|
189
|
+
else if (isDeclared(value)) {
|
|
190
|
+
exported.add(value);
|
|
191
|
+
if (collectionsFiles.has(file))
|
|
192
|
+
problems.push({ ...at, message: COLLECTIONS_ONLY });
|
|
193
|
+
else
|
|
194
|
+
names.set(value, [...(names.get(value) ?? []), at]);
|
|
221
195
|
}
|
|
222
|
-
// Else its value was refused, and reported in its file: a reference to it fails quietly.
|
|
223
196
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
197
|
+
}
|
|
198
|
+
// Each declaration: one name per object, a non-default one over `default`.
|
|
199
|
+
const declarations = new Map();
|
|
200
|
+
const idOf = new Map();
|
|
201
|
+
for (const [value, ats] of names) {
|
|
202
|
+
const named = ats.filter((at) => at.export !== "default");
|
|
203
|
+
const kept = named.length ? named : ats;
|
|
204
|
+
if (kept.length > 1) {
|
|
205
|
+
const [first, ...rest] = kept.map((at) => `${at.file}#${at.export}`);
|
|
206
|
+
problems.push({
|
|
207
|
+
...kept[1],
|
|
208
|
+
message: `${rest.join(", ")} and ${first} are one ${value[DECLARED]}: export each resource under one name`,
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const at = kept[0];
|
|
213
|
+
const type = types.get(value[DECLARED]);
|
|
214
|
+
if (type === undefined || madeOf(value) === undefined) {
|
|
215
|
+
if (!partial)
|
|
216
|
+
problems.push({
|
|
217
|
+
...at,
|
|
218
|
+
message: `${value[DECLARED]} is not a resource type of a connector in the config's \`connectors\`: add the connector, and import from its package's \`/resources\``,
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
228
221
|
}
|
|
229
|
-
|
|
230
|
-
|
|
222
|
+
const id = `${at.file}#${at.export}`;
|
|
223
|
+
declarations.set(id, { value, at });
|
|
224
|
+
idOf.set(value, id);
|
|
231
225
|
}
|
|
232
|
-
//
|
|
226
|
+
// A resource made and never exported is declared nowhere: a typo'd export, or one written inline.
|
|
227
|
+
for (const value of madeWith(loaded)) {
|
|
228
|
+
const made = madeOf(value);
|
|
229
|
+
if (!isDeclared(value) || exported.has(value) || failed.has(made.file))
|
|
230
|
+
continue;
|
|
231
|
+
problems.push({
|
|
232
|
+
file: made.file,
|
|
233
|
+
message: `${aResource(value)} is made here but not exported: declare each resource as its own \`export const <name> = <vendor>.<type>({ … })\``,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// Each declaration after the ones it names; one with a problem is left out, and so is any that names it.
|
|
233
237
|
const done = new Map();
|
|
234
|
-
const visiting = [];
|
|
235
238
|
const byName = new Map();
|
|
239
|
+
const leaveOut = (id, message) => {
|
|
240
|
+
if (message !== undefined)
|
|
241
|
+
problems.push({ ...declarations.get(id).at, message });
|
|
242
|
+
done.set(id, undefined);
|
|
243
|
+
};
|
|
236
244
|
function declare(id) {
|
|
237
245
|
if (done.has(id))
|
|
238
246
|
return done.get(id);
|
|
239
|
-
const {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const { vendor, type, name } = result.declared;
|
|
249
|
-
const key = `${vendor}.${type} ${JSON.stringify(name)}`;
|
|
250
|
-
const first = byName.get(key);
|
|
251
|
-
if (first === undefined)
|
|
252
|
-
byName.set(key, id);
|
|
253
|
-
else {
|
|
254
|
-
file.problem(exp.start, `${key} is declared twice, also as ${first}: declare each resource once, and import it where it is used`);
|
|
255
|
-
done.set(id, undefined);
|
|
247
|
+
const { value } = declarations.get(id);
|
|
248
|
+
const refs = {};
|
|
249
|
+
for (const [path, target] of Object.entries(madeOf(value).refers)) {
|
|
250
|
+
const targetId = idOf.get(target);
|
|
251
|
+
// A partial load serves only some types: a resource of another (a wakeup's host) is not its to judge.
|
|
252
|
+
if (targetId === undefined && partial && !types.has(target[DECLARED]))
|
|
253
|
+
continue;
|
|
254
|
+
if (targetId === undefined) {
|
|
255
|
+
leaveOut(id, `${path} names ${aResource(target)} that no data file exports: export it from a data file and import it here`);
|
|
256
256
|
return undefined;
|
|
257
257
|
}
|
|
258
|
+
if (!declare(targetId))
|
|
259
|
+
return void done.set(id, undefined);
|
|
260
|
+
refs[path] = targetId;
|
|
258
261
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (vendor === undefined)
|
|
266
|
-
return undefined;
|
|
267
|
-
const resource = types.get(`${vendor}.${exp.type}`);
|
|
268
|
-
if (!resource) {
|
|
269
|
-
const known = [...types.values()].filter((r) => r.vendor === vendor).map((r) => r.type);
|
|
270
|
-
file.problem(exp.start, `${vendor} has no resource type ${exp.type}: its types are ${known.join(", ")}`);
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
const refs = {};
|
|
274
|
-
let ok = true;
|
|
275
|
-
// The fields with each name of a resource as that resource, and where each is named.
|
|
276
|
-
const given = (v, at) => {
|
|
277
|
-
if (v instanceof Reference) {
|
|
278
|
-
// A name that did not resolve was reported where it is bound.
|
|
279
|
-
const target = named.get(file).get(v.name);
|
|
280
|
-
const declared = target === undefined ? undefined : declare(target)?.declared;
|
|
281
|
-
if (!declared)
|
|
282
|
-
ok = false;
|
|
283
|
-
else
|
|
284
|
-
refs[at] = target;
|
|
285
|
-
return declared;
|
|
262
|
+
if (value.name !== undefined) {
|
|
263
|
+
const key = `${value[DECLARED]} ${JSON.stringify(value.name)}${value.connection ? ` (${value.connection.$connection})` : ""}`;
|
|
264
|
+
const first = byName.get(key);
|
|
265
|
+
if (first !== undefined) {
|
|
266
|
+
leaveOut(id, `${key} is declared twice, also as ${first}: declare each resource once, and import it where it is used`);
|
|
267
|
+
return undefined;
|
|
286
268
|
}
|
|
287
|
-
|
|
288
|
-
return v.map((item, i) => given(item, `${at}.${i}`));
|
|
289
|
-
if (v === null || typeof v !== "object")
|
|
290
|
-
return v;
|
|
291
|
-
return Object.fromEntries(Object.entries(v).map(([k, item]) => [k, given(item, at ? `${at}.${k}` : k)]));
|
|
292
|
-
};
|
|
293
|
-
const fields = given(exp.desired, "");
|
|
294
|
-
if (!ok)
|
|
295
|
-
return undefined;
|
|
296
|
-
try {
|
|
297
|
-
// Checked as running the file would: by the resource type, which allows a resource only where its `fields.references` say.
|
|
298
|
-
return { declared: resource(fields), refs };
|
|
299
|
-
}
|
|
300
|
-
catch (e) {
|
|
301
|
-
// One line per problem: zod's own spans lines.
|
|
302
|
-
file.problem(exp.start, `export const ${exp.name}: ${e.message.replaceAll(/\s*\n\s*/g, " ")}`);
|
|
303
|
-
return undefined;
|
|
269
|
+
byName.set(key, id);
|
|
304
270
|
}
|
|
271
|
+
done.set(id, refs);
|
|
272
|
+
return refs;
|
|
305
273
|
}
|
|
306
274
|
// Each binding against its declaration: of its type, and the only one bound to its object.
|
|
307
275
|
const boundBy = new Map();
|
|
308
|
-
for (const [id, {
|
|
276
|
+
for (const [id, { value }] of declarations) {
|
|
309
277
|
const binding = lock.bindings[id];
|
|
310
|
-
|
|
311
|
-
const vendor = vendors.get(file).get(exp.vendor);
|
|
312
|
-
if (binding === undefined || vendor === undefined)
|
|
278
|
+
if (binding === undefined)
|
|
313
279
|
continue;
|
|
314
|
-
const
|
|
315
|
-
const object = `${binding.type} ${binding.id}`;
|
|
280
|
+
const object = `${binding.type} ${binding.id}${binding.connection ? ` (${binding.connection.$connection})` : ""}`;
|
|
316
281
|
const first = boundBy.get(object);
|
|
317
|
-
const why = binding.
|
|
318
|
-
? `${id}
|
|
319
|
-
:
|
|
320
|
-
? `${id} is bound to
|
|
321
|
-
: undefined
|
|
282
|
+
const why = value.connection !== undefined && binding.connection?.$connection !== value.connection.$connection
|
|
283
|
+
? `${id} changes the connection of a bound resource: explicitly unbind it before selecting another account`
|
|
284
|
+
: binding.type !== value[DECLARED]
|
|
285
|
+
? `${id} is bound to a ${binding.type} in ${LOCKFILE}, but declares a ${value[DECLARED]}`
|
|
286
|
+
: first !== undefined
|
|
287
|
+
? `${id} is bound to the same ${object} as ${first} in ${LOCKFILE}: bind each object once`
|
|
288
|
+
: undefined;
|
|
322
289
|
if (why === undefined)
|
|
323
290
|
boundBy.set(object, id);
|
|
324
|
-
else
|
|
325
|
-
|
|
326
|
-
file.problem(exp.start, why);
|
|
327
|
-
done.set(id, undefined);
|
|
328
|
-
}
|
|
291
|
+
else
|
|
292
|
+
leaveOut(id, why);
|
|
329
293
|
}
|
|
330
294
|
// An instance registers with one wakeup: several are each a problem, and left out, as is any that names one.
|
|
331
|
-
const wakeups = [...declarations].filter((
|
|
295
|
+
const wakeups = [...declarations.keys()].filter((id) => types.get(declarations.get(id).value[DECLARED])?.wakeup);
|
|
332
296
|
if (wakeups.length > 1) {
|
|
333
|
-
for (const
|
|
334
|
-
|
|
335
|
-
file.problem(exp.start, `an instance has one wakeup: leave out this one or ${others.join(", ")}`);
|
|
336
|
-
done.set(id, undefined);
|
|
297
|
+
for (const id of wakeups) {
|
|
298
|
+
leaveOut(id, `an instance has one wakeup: leave out this one or ${wakeups.filter((other) => other !== id).join(", ")}`);
|
|
337
299
|
}
|
|
338
300
|
}
|
|
339
|
-
|
|
301
|
+
// A binding under a file that failed to load is no orphan: what it declares is not known.
|
|
340
302
|
const orphans = Object.entries(lock.bindings)
|
|
341
|
-
.filter(([key]) => !
|
|
303
|
+
.filter(([key]) => !declarations.has(key) && !failed.has(key.slice(0, key.lastIndexOf("#"))))
|
|
342
304
|
.map(([key, binding]) => ({ key, ...binding }));
|
|
343
305
|
const resources = [];
|
|
344
|
-
for (const [id, {
|
|
345
|
-
const
|
|
346
|
-
if (!
|
|
306
|
+
for (const [id, { value, at }] of declarations) {
|
|
307
|
+
const refs = declare(id);
|
|
308
|
+
if (!refs)
|
|
347
309
|
continue;
|
|
348
|
-
const { vendor, type, name: identity, desired } =
|
|
310
|
+
const { vendor, type, name: identity, desired, connection } = value;
|
|
349
311
|
const binding = lock.bindings[id]?.id;
|
|
350
312
|
resources.push({
|
|
351
313
|
id,
|
|
352
|
-
file: file
|
|
314
|
+
file: at.file,
|
|
353
315
|
vendor,
|
|
354
316
|
type,
|
|
355
|
-
name: identity ??
|
|
317
|
+
name: identity ?? at.export,
|
|
356
318
|
...(identity !== undefined && { identity }),
|
|
357
319
|
...(binding !== undefined && { binding }),
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
320
|
+
at,
|
|
321
|
+
// A copy: the loaded value is the generation's, read again while no file changes.
|
|
322
|
+
desired: structuredClone(desired),
|
|
323
|
+
...(connection && { connection: { ...connection } }),
|
|
324
|
+
refs,
|
|
361
325
|
});
|
|
362
326
|
}
|
|
363
|
-
const collections = [...files.values()].flatMap(({ rel, data }) => data.collections.map((c) => ({
|
|
364
|
-
id: `${rel}#${c.export}`,
|
|
365
|
-
file: rel,
|
|
366
|
-
// An app's, `apps/<app>/resources/data.ts`, is named with its app, as `collection()` names it.
|
|
367
|
-
name: qualified(rel.startsWith(`${APPS_DIR}/`) ? rel.split("/")[1] : undefined, c.name),
|
|
368
|
-
span: [c.start, c.end],
|
|
369
|
-
})));
|
|
370
327
|
problems.sort((a, b) => (a.file ?? "").localeCompare(b.file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.column ?? 0) - (b.column ?? 0));
|
|
371
328
|
return { resources, bindings: lock.bindings, orphans, collections, problems };
|
|
372
329
|
}
|
|
330
|
+
/** A resource as a problem names it: `a github.repository "website"`, or without its identity when it has none. */
|
|
331
|
+
const aResource = (value) => `a ${value[DECLARED]}${value.name === undefined ? "" : ` "${value.name}"`}`;
|
|
332
|
+
/**
|
|
333
|
+
* What a data file that failed to load is told, on one line: an import Node could not find (a
|
|
334
|
+
* connector the config does not have, a file that is not there), top-level `await`, or the
|
|
335
|
+
* error's own message (a constructor's refusal: `github.repository: no field wiki`). Nothing, when
|
|
336
|
+
* `partial`, for a connector's constructors left out of the load in a file that declares no
|
|
337
|
+
* person, agent or group (`sanoma.wakeup` beside its host's connector).
|
|
338
|
+
*/
|
|
339
|
+
function loadProblem(error, root, file, partial) {
|
|
340
|
+
const { code, message } = isObject(error) ? error : {};
|
|
341
|
+
const text = typeof message === "string" ? message : String(error);
|
|
342
|
+
if (code === "ERR_REQUIRE_ASYNC_MODULE")
|
|
343
|
+
return "a data file is loaded synchronously: leave out top-level `await`, and compute values where they are used";
|
|
344
|
+
if (code === "ERR_MODULE_NOT_FOUND") {
|
|
345
|
+
const missing = /^Cannot find (?:package|module) '([^']+)'/.exec(text)?.[1];
|
|
346
|
+
const source = readFileSync(`${root}/${file}`, "utf8");
|
|
347
|
+
// The specifier as written: Node names a package by its name, a file by its path.
|
|
348
|
+
const written = missing &&
|
|
349
|
+
[...source.matchAll(/from\s*["']([^"']+)["']/g)]
|
|
350
|
+
.map((m) => m[1])
|
|
351
|
+
.find((spec) => spec === missing ||
|
|
352
|
+
spec.startsWith(`${missing}/`) ||
|
|
353
|
+
(missing.startsWith("/") && missing.endsWith(`/${spec.replace(/^(?:\.\.?\/)+/, "")}`)));
|
|
354
|
+
if (written?.endsWith("/resources") && partial && !/\.\s*(?:person|agent|group)\s*\(/.test(source))
|
|
355
|
+
return undefined;
|
|
356
|
+
if (written?.endsWith("/resources"))
|
|
357
|
+
return `"${written}" is not the resources entry of a connector in the config's \`connectors\`: add the connector, and import from its package's \`/resources\``;
|
|
358
|
+
if (written !== undefined)
|
|
359
|
+
return `"${written}" cannot be found: import a file that is there, or a package the company has`;
|
|
360
|
+
}
|
|
361
|
+
return text.replaceAll(/\s*\n\s*/g, " ");
|
|
362
|
+
}
|
|
363
|
+
/** The data files under `root`: the root's `resources/`, then each app's, each sorted. */
|
|
364
|
+
const dataFilesOf = (root) => kindDirs(root, "resources").flatMap(dataFilesUnder);
|
|
373
365
|
/** The data files under `dir`, sorted: regular `.ts` files, less tests, declarations and `node_modules`. */
|
|
374
366
|
const dataFilesUnder = (dir) => filesUnder(dir, (name) => name.endsWith(".ts") && !name.endsWith(".test.ts") && !name.endsWith(".d.ts"));
|
|
375
367
|
//# sourceMappingURL=resources.js.map
|