@prestack/thread 0.0.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/CHANGELOG.md +192 -0
- package/dist/attach.d.ts +1 -0
- package/dist/attach.js +1 -0
- package/dist/core/actor.d.ts +12 -0
- package/dist/core/actor.js +8 -0
- package/dist/core/asking.d.ts +180 -0
- package/dist/core/asking.js +1109 -0
- package/dist/core/attach.d.ts +54 -0
- package/dist/core/attach.js +510 -0
- package/dist/core/chain.d.ts +16 -0
- package/dist/core/chain.js +114 -0
- package/dist/core/cli.d.ts +2 -0
- package/dist/core/cli.js +1682 -0
- package/dist/core/condition.d.ts +52 -0
- package/dist/core/condition.js +198 -0
- package/dist/core/descend.d.ts +81 -0
- package/dist/core/descend.js +879 -0
- package/dist/core/finding.d.ts +12 -0
- package/dist/core/finding.js +23 -0
- package/dist/core/generators/meta.d.ts +2 -0
- package/dist/core/generators/meta.js +823 -0
- package/dist/core/init.d.ts +15 -0
- package/dist/core/init.js +134 -0
- package/dist/core/invoked.d.ts +9 -0
- package/dist/core/invoked.js +41 -0
- package/dist/core/round.d.ts +1 -0
- package/dist/core/round.js +57 -0
- package/dist/core/rows.d.ts +26 -0
- package/dist/core/rows.js +354 -0
- package/dist/core/scope.d.ts +6 -0
- package/dist/core/scope.js +12 -0
- package/dist/core/thread.d.ts +26 -0
- package/dist/core/thread.js +5 -0
- package/dist/core/tree.d.ts +12 -0
- package/dist/core/tree.js +56 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +311 -0
- package/dist/derivation/attach.d.ts +2 -0
- package/dist/derivation/attach.js +61 -0
- package/dist/derivation/generators/build.d.ts +16 -0
- package/dist/derivation/generators/build.js +217 -0
- package/dist/derivation/generators/check.d.ts +18 -0
- package/dist/derivation/generators/check.js +78 -0
- package/dist/derivation/generators/derivation.d.ts +3 -0
- package/dist/derivation/generators/derivation.js +273 -0
- package/dist/derivation/generators/determinism.d.ts +20 -0
- package/dist/derivation/generators/determinism.js +110 -0
- package/dist/derivation/generators/release.d.ts +3 -0
- package/dist/derivation/generators/release.js +183 -0
- package/dist/derivation/makers/release.d.ts +1 -0
- package/dist/derivation/makers/release.js +107 -0
- package/dist/derivation/release.d.ts +10 -0
- package/dist/derivation/release.js +12 -0
- package/dist/integrations/attach.d.ts +6 -0
- package/dist/integrations/attach.js +177 -0
- package/dist/integrations/generators/read.d.ts +1 -0
- package/dist/integrations/generators/read.js +92 -0
- package/dist/integrations/generators/sdk.d.ts +1 -0
- package/dist/integrations/generators/sdk.js +148 -0
- package/dist/integrations/generators/secret.d.ts +1 -0
- package/dist/integrations/generators/secret.js +233 -0
- package/dist/integrations/generators/target.d.ts +1 -0
- package/dist/integrations/generators/target.js +765 -0
- package/dist/integrations/generators/vault.d.ts +1 -0
- package/dist/integrations/generators/vault.js +164 -0
- package/dist/integrations/sdk.d.ts +6 -0
- package/dist/integrations/sdk.js +151 -0
- package/dist/integrations/secret.d.ts +16 -0
- package/dist/integrations/secret.js +30 -0
- package/dist/integrations/target.d.ts +49 -0
- package/dist/integrations/target.js +225 -0
- package/dist/integrations/vault.d.ts +12 -0
- package/dist/integrations/vault.js +104 -0
- package/dist/integrity/attach.d.ts +2 -0
- package/dist/integrity/attach.js +213 -0
- package/dist/integrity/check-threads.d.ts +10 -0
- package/dist/integrity/check-threads.js +153 -0
- package/dist/integrity/constraint.d.ts +16 -0
- package/dist/integrity/constraint.js +1105 -0
- package/dist/integrity/generators/compiler.d.ts +1 -0
- package/dist/integrity/generators/compiler.js +130 -0
- package/dist/integrity/generators/gate.d.ts +1 -0
- package/dist/integrity/generators/gate.js +172 -0
- package/dist/integrity/generators/issues.d.ts +1 -0
- package/dist/integrity/generators/issues.js +100 -0
- package/dist/integrity/generators/publish.d.ts +1 -0
- package/dist/integrity/generators/publish.js +186 -0
- package/dist/integrity/generators/rule.d.ts +2 -0
- package/dist/integrity/generators/rule.js +213 -0
- package/dist/integrity/generators/seal.d.ts +4 -0
- package/dist/integrity/generators/seal.js +101 -0
- package/dist/integrity/generators/trial.d.ts +39 -0
- package/dist/integrity/generators/trial.js +254 -0
- package/dist/integrity/lint.d.ts +50 -0
- package/dist/integrity/lint.js +161 -0
- package/dist/integrity/lsp/bin.d.ts +2 -0
- package/dist/integrity/lsp/bin.js +35 -0
- package/dist/integrity/lsp/diagnostics.d.ts +25 -0
- package/dist/integrity/lsp/diagnostics.js +57 -0
- package/dist/integrity/lsp/findings.d.ts +20 -0
- package/dist/integrity/lsp/findings.js +39 -0
- package/dist/integrity/lsp/lint.d.ts +7 -0
- package/dist/integrity/lsp/lint.js +95 -0
- package/dist/integrity/lsp/protocol.d.ts +20 -0
- package/dist/integrity/lsp/protocol.js +57 -0
- package/dist/integrity/lsp/server.d.ts +15 -0
- package/dist/integrity/lsp/server.js +171 -0
- package/dist/integrity/sealed.d.ts +1 -0
- package/dist/integrity/sealed.js +56 -0
- package/dist/integrity/slopnator.d.ts +90 -0
- package/dist/integrity/slopnator.js +267 -0
- package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
- package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
- package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
- package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
- package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_island.js +85 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
- package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
- package/dist/integrity/slopnators/sl_missing_row.js +180 -0
- package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
- package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
- package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_comment.js +41 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
- package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
- package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
- package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
- package/dist/integrity/slopnators/sl_stale_path.js +92 -0
- package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
- package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
- package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
- package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undescribed.js +54 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
- package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
- package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
- package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
- package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
- package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
- package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unversioned.js +31 -0
- package/dist/integrity/sweep.d.ts +24 -0
- package/dist/integrity/sweep.js +264 -0
- package/dist/knowledge/attach.d.ts +2 -0
- package/dist/knowledge/attach.js +339 -0
- package/dist/knowledge/generators/docsgen.d.ts +1 -0
- package/dist/knowledge/generators/docsgen.js +145 -0
- package/dist/knowledge/generators/glossary.d.ts +1 -0
- package/dist/knowledge/generators/glossary.js +56 -0
- package/dist/knowledge/generators/skills.d.ts +1 -0
- package/dist/knowledge/generators/skills.js +222 -0
- package/dist/knowledge/glossary.d.ts +6 -0
- package/dist/knowledge/glossary.js +1 -0
- package/dist/operations/access.d.ts +27 -0
- package/dist/operations/access.js +61 -0
- package/dist/operations/attach.d.ts +6 -0
- package/dist/operations/attach.js +414 -0
- package/dist/operations/capabilities.d.ts +37 -0
- package/dist/operations/capabilities.js +35 -0
- package/dist/operations/cost.d.ts +14 -0
- package/dist/operations/cost.js +37 -0
- package/dist/operations/dispatch.d.ts +14 -0
- package/dist/operations/dispatch.js +327 -0
- package/dist/operations/error-results.d.ts +4 -0
- package/dist/operations/error-results.js +25 -0
- package/dist/operations/errors/catalog.d.ts +58 -0
- package/dist/operations/errors/catalog.js +91 -0
- package/dist/operations/errors/classes.d.ts +73 -0
- package/dist/operations/errors/classes.js +22 -0
- package/dist/operations/errors/errors.d.ts +52 -0
- package/dist/operations/errors/errors.js +257 -0
- package/dist/operations/generators/capability.d.ts +5 -0
- package/dist/operations/generators/capability.js +603 -0
- package/dist/operations/generators/compile.d.ts +2 -0
- package/dist/operations/generators/compile.js +541 -0
- package/dist/operations/generators/grant.d.ts +1 -0
- package/dist/operations/generators/grant.js +267 -0
- package/dist/operations/generators/operation.d.ts +1 -0
- package/dist/operations/generators/operation.js +811 -0
- package/dist/operations/generators/permits.d.ts +1 -0
- package/dist/operations/generators/permits.js +196 -0
- package/dist/operations/generators/seed.d.ts +20 -0
- package/dist/operations/generators/seed.js +285 -0
- package/dist/operations/generators/trial.d.ts +53 -0
- package/dist/operations/generators/trial.js +376 -0
- package/dist/operations/http.d.ts +30 -0
- package/dist/operations/http.js +121 -0
- package/dist/operations/makers/capability.d.ts +1 -0
- package/dist/operations/makers/capability.js +117 -0
- package/dist/operations/makers/trial.d.ts +1 -0
- package/dist/operations/makers/trial.js +413 -0
- package/dist/operations/memory.d.ts +104 -0
- package/dist/operations/memory.js +365 -0
- package/dist/operations/metering.d.ts +55 -0
- package/dist/operations/metering.js +247 -0
- package/dist/operations/nodes.d.ts +35 -0
- package/dist/operations/nodes.js +15 -0
- package/dist/operations/operations.d.ts +18 -0
- package/dist/operations/operations.js +5 -0
- package/dist/operations/queue-policy.d.ts +3 -0
- package/dist/operations/queue-policy.js +8 -0
- package/dist/operations/result.d.ts +19 -0
- package/dist/operations/result.js +10 -0
- package/dist/operations/seams.d.ts +239 -0
- package/dist/operations/seams.js +45 -0
- package/dist/operations/store.d.ts +14 -0
- package/dist/operations/store.js +1 -0
- package/package.json +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { dirname, join, relative } from 'node:path';
|
|
11
|
+
import { argv, stderr, stdout } from 'node:process';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import ts from 'typescript';
|
|
14
|
+
import { exit } from "../../core/invoked.js";
|
|
15
|
+
import { named as packageOf } from "../../core/scope.js";
|
|
16
|
+
import { findTree, inTree, strayFiles } from "../../core/tree.js";
|
|
17
|
+
import { unclassified, valued } from "../secret.js";
|
|
18
|
+
import { refusals, servesBrowser } from "../target.js";
|
|
19
|
+
import { SEALED_AT } from "../vault.js";
|
|
20
|
+
const TREE = findTree();
|
|
21
|
+
const AT = 'src/vault.ts';
|
|
22
|
+
const here = (one) => relative(TREE.at, one).split('\\').join('/');
|
|
23
|
+
const from = inTree(TREE, 'secrets.ts');
|
|
24
|
+
const path = join(TREE.at, AT);
|
|
25
|
+
const toward = relative(dirname(path), fileURLToPath(new URL('../vault.ts', import.meta.url))).split('\\').join('/');
|
|
26
|
+
const opens = toward.startsWith('.') ? toward : `./${toward}`;
|
|
27
|
+
const declares = relative(dirname(path), from).split('\\').join('/');
|
|
28
|
+
const sheet = declares.startsWith('.') ? declares : `./${declares}`;
|
|
29
|
+
function refuse(because) {
|
|
30
|
+
stderr.write(`vault: ${because}\n`);
|
|
31
|
+
exit(1);
|
|
32
|
+
}
|
|
33
|
+
let secrets = [];
|
|
34
|
+
try {
|
|
35
|
+
const taken = (await import(__rewriteRelativeImportExtension(new URL(`file://${from}`).href)));
|
|
36
|
+
secrets = taken.SECRETS ?? [];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
refuse(`${here(from)} could not be read. Attach \`${packageOf('integrations')}\` first — an app states its secrets there.`);
|
|
40
|
+
}
|
|
41
|
+
const declared = secrets.map((one) => one.id).sort();
|
|
42
|
+
const SOURCE = `import { opened } from '${opens}'
|
|
43
|
+
|
|
44
|
+
import { SECRETS } from '${sheet}'
|
|
45
|
+
|
|
46
|
+
export const vault = opened(process.cwd(), SECRETS.map((one) => one.id), process.env.NODE_ENV === 'production' ? 'production' : 'sandbox')
|
|
47
|
+
`;
|
|
48
|
+
async function taken(file, list) {
|
|
49
|
+
const at = inTree(TREE, file);
|
|
50
|
+
if (!existsSync(at))
|
|
51
|
+
return [];
|
|
52
|
+
try {
|
|
53
|
+
const held = (await import(__rewriteRelativeImportExtension(new URL(`file://${at}`).href)));
|
|
54
|
+
return held[list] ?? [];
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function imported(at, text) {
|
|
61
|
+
const source = ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
62
|
+
const found = [];
|
|
63
|
+
const walk = (node) => {
|
|
64
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
65
|
+
const said = node.moduleSpecifier;
|
|
66
|
+
if (said !== undefined && ts.isStringLiteral(said))
|
|
67
|
+
found.push(said.text);
|
|
68
|
+
}
|
|
69
|
+
else if (ts.isCallExpression(node)) {
|
|
70
|
+
const first = node.arguments[0];
|
|
71
|
+
const dynamic = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
72
|
+
const legacy = ts.isIdentifier(node.expression) && node.expression.text === 'require';
|
|
73
|
+
if ((dynamic || legacy) && first !== undefined && ts.isStringLiteral(first))
|
|
74
|
+
found.push(first.text);
|
|
75
|
+
}
|
|
76
|
+
ts.forEachChild(node, walk);
|
|
77
|
+
};
|
|
78
|
+
walk(source);
|
|
79
|
+
return found;
|
|
80
|
+
}
|
|
81
|
+
function names(specifier, id) {
|
|
82
|
+
const bare = specifier.replace(/\.(?:m|c)?[jt]sx?$/, '');
|
|
83
|
+
return bare === `operations/${id}` || bare.endsWith(`/operations/${id}`);
|
|
84
|
+
}
|
|
85
|
+
async function reaching() {
|
|
86
|
+
const targets = await taken('targets.ts', 'TARGETS');
|
|
87
|
+
const withheld = new Map();
|
|
88
|
+
for (const one of targets) {
|
|
89
|
+
const said = servesBrowser(one, secrets);
|
|
90
|
+
if (said !== true)
|
|
91
|
+
withheld.set(one.id, said);
|
|
92
|
+
}
|
|
93
|
+
if (withheld.size === 0)
|
|
94
|
+
return [];
|
|
95
|
+
const operations = await taken('operations.ts', 'MEMBERS');
|
|
96
|
+
const risky = new Map();
|
|
97
|
+
for (const one of operations) {
|
|
98
|
+
for (const word of one.requires ?? []) {
|
|
99
|
+
const said = withheld.get(word);
|
|
100
|
+
if (said !== undefined)
|
|
101
|
+
risky.set(one.id, said);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (risky.size === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const found = [];
|
|
107
|
+
for (const one of strayFiles(TREE.at, '', '.tsx')) {
|
|
108
|
+
let text;
|
|
109
|
+
try {
|
|
110
|
+
text = readFileSync(join(TREE.at, one), 'utf8');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const specifiers = imported(one, text);
|
|
116
|
+
for (const [id, said] of risky) {
|
|
117
|
+
if (!specifiers.some((each) => names(each, id)))
|
|
118
|
+
continue;
|
|
119
|
+
found.push(`${one} is a \`.tsx\` file and it imports \`${id}\`. ${said} This holds whoever bundles it — ` +
|
|
120
|
+
'a page is a page whether Next, Vite or anything else compiles it, so the refusal is about the ' +
|
|
121
|
+
'import rather than about any one build. Call it from a server and let the page ask the server');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return found;
|
|
125
|
+
}
|
|
126
|
+
async function stated() {
|
|
127
|
+
const targets = await taken('targets.ts', 'TARGETS');
|
|
128
|
+
const calls = await taken('targets.ts', 'CALLS');
|
|
129
|
+
const entities = (await taken('entities.ts', 'ENTITIES')).map((one) => one.id);
|
|
130
|
+
return [
|
|
131
|
+
...unclassified(secrets),
|
|
132
|
+
...valued(secrets),
|
|
133
|
+
...refusals(targets, secrets, calls, entities),
|
|
134
|
+
...(await reaching()),
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
const checking = argv.includes('--check');
|
|
138
|
+
const opening = `${String(declared.length)} declared secret(s)`;
|
|
139
|
+
if (checking) {
|
|
140
|
+
const wrong = await stated();
|
|
141
|
+
if (wrong.length > 0) {
|
|
142
|
+
refuse([
|
|
143
|
+
`${String(wrong.length)} thing(s) this app states about what it reaches will not hold:`,
|
|
144
|
+
...wrong.map((one) => ` ${one}`),
|
|
145
|
+
].join('\n'));
|
|
146
|
+
}
|
|
147
|
+
if (!existsSync(path)) {
|
|
148
|
+
refuse(`${AT} is not there, and it is what opens this app's sealed values. Run \`thread vault\` — it seeds the file ` +
|
|
149
|
+
'once, and from then on it is yours.');
|
|
150
|
+
}
|
|
151
|
+
stdout.write(`vault: ${AT} is seeded and opens ${opening}, read off ${here(from)} as they are declared.\n`);
|
|
152
|
+
exit(0);
|
|
153
|
+
}
|
|
154
|
+
if (existsSync(path)) {
|
|
155
|
+
stdout.write(`vault: ${AT} is already yours — it opens ${opening}, read off ${here(from)} as they are declared.\n`);
|
|
156
|
+
exit(0);
|
|
157
|
+
}
|
|
158
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
159
|
+
writeFileSync(path, SOURCE, 'utf8');
|
|
160
|
+
stdout.write(`vault: seeded ${AT} — what opens a secret's value at the moment of use, and yours from here.\n` +
|
|
161
|
+
` It opens the values sealed under ${SEALED_AT} for the secrets declared in ${here(from)}, and refuses every other name.\n` +
|
|
162
|
+
' `thread create secret` declares one and seals its value; nothing else ever takes a value.\n' +
|
|
163
|
+
' To open values from somewhere else in production, replace opened here with what reads them.\n');
|
|
164
|
+
exit(0);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { argv, stderr, stdout } from 'node:process';
|
|
11
|
+
import { answer, asking, given, only, wordsIn } from "../core/asking.js";
|
|
12
|
+
import { exit } from "../core/invoked.js";
|
|
13
|
+
import { findTree, inTree } from "../core/tree.js";
|
|
14
|
+
import { REACH, reachOf } from "./target.js";
|
|
15
|
+
import { opened } from "./vault.js";
|
|
16
|
+
const PRODUCTION = '--production';
|
|
17
|
+
const METHOD = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
|
|
18
|
+
function isClass(fn) {
|
|
19
|
+
return /^class[\s{]/.test(Function.prototype.toString.call(fn));
|
|
20
|
+
}
|
|
21
|
+
const ASKS = [
|
|
22
|
+
{
|
|
23
|
+
key: 'args',
|
|
24
|
+
says: 'a JSON value — an array for positional arguments, anything else for one, blank for none',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
const TAKES = ASKS.map((one) => one.key);
|
|
28
|
+
const ASKED = ASKS.map((one) => `\`${one.key}\`: ${one.says}`);
|
|
29
|
+
export async function ran(name) {
|
|
30
|
+
const say = (because) => {
|
|
31
|
+
stderr.write(`${name}: ${because}\n`);
|
|
32
|
+
exit(1);
|
|
33
|
+
};
|
|
34
|
+
const tree = findTree();
|
|
35
|
+
let targets = [];
|
|
36
|
+
try {
|
|
37
|
+
const taken = (await import(__rewriteRelativeImportExtension(new URL(`file://${inTree(tree, 'targets.ts')}`).href)));
|
|
38
|
+
targets = taken.TARGETS ?? [];
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return say('`_threads/targets.ts` could not be read, so nothing says what this integration is.');
|
|
42
|
+
}
|
|
43
|
+
const mine = targets.find((one) => one.id === `${REACH.cli}${name}` || one.id === `${REACH.sdk}${name}`);
|
|
44
|
+
if (mine === undefined) {
|
|
45
|
+
return say(`no target called \`${REACH.cli}${name}\` or \`${REACH.sdk}${name}\` is declared here, so this command should ` +
|
|
46
|
+
'not exist. Declare it with `thread create target`, or take its row out of `_threads/derivation.ts`.');
|
|
47
|
+
}
|
|
48
|
+
const does = mine.offers ?? [];
|
|
49
|
+
const passed = argv.slice(2);
|
|
50
|
+
const sub = passed[0];
|
|
51
|
+
if (sub === undefined) {
|
|
52
|
+
stdout.write(`${name}: ${mine.describes}\n\n${String(does.length)} command(s), read off \`${mine.uses ?? ''}\` when \`${mine.id}\` was declared:\n` +
|
|
53
|
+
`${does.join(', ') || '(none were readable — name them with `thread create target edit`)'}\n\n` +
|
|
54
|
+
`thread run ${name} <command> [anything else] — arguments and flags reach \`${mine.uses ?? ''}\` untouched.\n` +
|
|
55
|
+
`It takes ${String(ASKED.length)} answer(s): ${ASKED.join('; ')}.\n`);
|
|
56
|
+
exit(0);
|
|
57
|
+
}
|
|
58
|
+
if (!does.includes(sub)) {
|
|
59
|
+
return say(`\`${sub}\` is not one of the ${String(does.length)} command(s) \`${mine.uses ?? ''}\` stated when \`${mine.id}\` ` +
|
|
60
|
+
`was declared. It declared: ${does.join(', ') || '(nothing)'}. If the tool has changed, read it again with ` +
|
|
61
|
+
`\`thread create target edit ${mine.id}\`.`);
|
|
62
|
+
}
|
|
63
|
+
if (reachOf(mine.id) === 'sdk') {
|
|
64
|
+
return run({ name, module: mine.uses ?? '', requires: mine.requires });
|
|
65
|
+
}
|
|
66
|
+
const went = spawnSync(mine.uses ?? '', passed, { cwd: tree.at, stdio: 'inherit' });
|
|
67
|
+
exit(went.status ?? 1);
|
|
68
|
+
}
|
|
69
|
+
export async function run(declared) {
|
|
70
|
+
const refuse = (because) => {
|
|
71
|
+
stderr.write(`${declared.name}: ${because}\n`);
|
|
72
|
+
exit(1);
|
|
73
|
+
};
|
|
74
|
+
const method = wordsIn([])[0];
|
|
75
|
+
if (method === undefined) {
|
|
76
|
+
return refuse(`takes the method to call, dot-separated for a namespaced one — e.g. \`thread run ${declared.name} customers.list\`.`);
|
|
77
|
+
}
|
|
78
|
+
if (!METHOD.test(method)) {
|
|
79
|
+
return refuse(`\`${method}\` is not a method path — one name, or names joined by dots, e.g. \`customers.list\``);
|
|
80
|
+
}
|
|
81
|
+
const piped = given();
|
|
82
|
+
only(piped, TAKES, `\`thread run ${declared.name} ${method}\``);
|
|
83
|
+
const { ask, shut } = asking(piped);
|
|
84
|
+
const typed = (piped.length > 0
|
|
85
|
+
? (answer(piped, TAKES[0] ?? 'args') ?? '')
|
|
86
|
+
: await ask('args', 'args, as JSON — an array for positional arguments, anything else for one (blank for none): ')).trim();
|
|
87
|
+
shut();
|
|
88
|
+
let args = [];
|
|
89
|
+
if (typed !== '') {
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(typed);
|
|
93
|
+
}
|
|
94
|
+
catch (thrown) {
|
|
95
|
+
return refuse(`that is not JSON: ${thrown instanceof Error ? thrown.message : String(thrown)}. Nothing was called.`);
|
|
96
|
+
}
|
|
97
|
+
args = Array.isArray(parsed) ? parsed : [parsed];
|
|
98
|
+
}
|
|
99
|
+
const env = argv.includes(PRODUCTION) ? 'production' : 'sandbox';
|
|
100
|
+
const opening = opened(findTree().at, declared.requires, env);
|
|
101
|
+
let resolved;
|
|
102
|
+
try {
|
|
103
|
+
resolved = await opening(declared.requires);
|
|
104
|
+
}
|
|
105
|
+
catch (thrown) {
|
|
106
|
+
return refuse(thrown instanceof Error ? thrown.message : String(thrown));
|
|
107
|
+
}
|
|
108
|
+
const credentials = declared.requires.map((one) => resolved[one] ?? '');
|
|
109
|
+
let mod;
|
|
110
|
+
try {
|
|
111
|
+
mod = (await import(__rewriteRelativeImportExtension(declared.module)));
|
|
112
|
+
}
|
|
113
|
+
catch (thrown) {
|
|
114
|
+
return refuse(`\`${declared.module}\` could not be imported: ${thrown instanceof Error ? thrown.message : String(thrown)}. It was ` +
|
|
115
|
+
`installed when this was declared — if it is gone, \`npm install ${declared.module}\` puts it back.`);
|
|
116
|
+
}
|
|
117
|
+
const entry = mod.default ?? mod;
|
|
118
|
+
const asFunction = entry;
|
|
119
|
+
const asConstructor = entry;
|
|
120
|
+
const REACHES = new Set(['constructor', '__proto__', 'prototype']);
|
|
121
|
+
const client = typeof entry !== 'function' ? entry : isClass(asFunction) ? Reflect.construct(asConstructor, credentials) : asFunction(...credentials);
|
|
122
|
+
let owner = client;
|
|
123
|
+
let target = client;
|
|
124
|
+
for (const segment of method.split('.')) {
|
|
125
|
+
if (REACHES.has(segment)) {
|
|
126
|
+
return refuse(`\`${segment}\` is not an operation of \`${declared.module}\` — it is a step every JavaScript value has, and ` +
|
|
127
|
+
'walking to it leaves what was installed. What may run here is what the SDK exports, and a person fixed that ' +
|
|
128
|
+
'set when they declared it.');
|
|
129
|
+
}
|
|
130
|
+
owner = target;
|
|
131
|
+
target = target?.[segment];
|
|
132
|
+
}
|
|
133
|
+
if (typeof target !== 'function') {
|
|
134
|
+
return refuse(`\`${method}\` on \`${declared.module}\` is ${target === undefined ? 'nothing' : `a ${typeof target}`}, not a method that can be called`);
|
|
135
|
+
}
|
|
136
|
+
let result;
|
|
137
|
+
try {
|
|
138
|
+
result = await target.apply(owner, args);
|
|
139
|
+
}
|
|
140
|
+
catch (thrown) {
|
|
141
|
+
return refuse(`\`${method}\` on \`${declared.module}\` threw: ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
142
|
+
}
|
|
143
|
+
stdout.write(`${declared.name}: ${method} (${env})\n`);
|
|
144
|
+
try {
|
|
145
|
+
stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
stdout.write(`${String(result)}\n`);
|
|
149
|
+
}
|
|
150
|
+
exit(0);
|
|
151
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Member } from '../core/thread.ts';
|
|
2
|
+
export declare const SECRECY: {
|
|
3
|
+
readonly server: "sec_";
|
|
4
|
+
readonly public: "pub_";
|
|
5
|
+
};
|
|
6
|
+
export declare const SAID: Readonly<Record<keyof typeof SECRECY, string>>;
|
|
7
|
+
export interface Secret extends Member {
|
|
8
|
+
readonly sends?: {
|
|
9
|
+
readonly header: string;
|
|
10
|
+
readonly prefix?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function secrecyOf(id: string): keyof typeof SECRECY | undefined;
|
|
14
|
+
export declare const isServerOnly: (id: string) => boolean;
|
|
15
|
+
export declare const unclassified: (secrets: readonly Secret[]) => readonly string[];
|
|
16
|
+
export declare const valued: (secrets: readonly Secret[]) => readonly string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const SECRECY = {
|
|
2
|
+
server: 'sec_',
|
|
3
|
+
public: 'pub_',
|
|
4
|
+
};
|
|
5
|
+
export const SAID = {
|
|
6
|
+
server: 'A value only a server may ever hold — an API key, a connection string, a signing secret. Everything a browser bundle reaches is in the bundle and belongs to whoever opens the page, so a target requiring one of these is refused a browser entry when it is built rather than when somebody reads it.',
|
|
7
|
+
public: 'A value that may reach a browser: a base url, a publishable key, a project id. It is still a secret and still lives in the sealed store, because it still differs between a sandbox and production and nothing else here keeps that difference. What it is not is a thing that has to be withheld.',
|
|
8
|
+
};
|
|
9
|
+
export function secrecyOf(id) {
|
|
10
|
+
return Object.keys(SECRECY).find((one) => id.startsWith(SECRECY[one]));
|
|
11
|
+
}
|
|
12
|
+
export const isServerOnly = (id) => secrecyOf(id) === 'server';
|
|
13
|
+
export const unclassified = (secrets) => secrets
|
|
14
|
+
.filter((one) => secrecyOf(one.id) === undefined)
|
|
15
|
+
.map((one) => `\`${one.id}\` wears neither \`${SECRECY.server}\` nor \`${SECRECY.public}\`, so nothing can tell whether it may ` +
|
|
16
|
+
'reach a browser — and that prefix is the only thing standing between a server credential and a bundle');
|
|
17
|
+
const VALUED = ['value', 'secret', 'key', 'token', 'password', 'env'];
|
|
18
|
+
export const valued = (secrets) => {
|
|
19
|
+
const found = [];
|
|
20
|
+
for (const one of secrets) {
|
|
21
|
+
for (const named of VALUED) {
|
|
22
|
+
if (!(named in one))
|
|
23
|
+
continue;
|
|
24
|
+
found.push(`\`${one.id}\` carries a \`${named}\`. A secret row holds a name and a sentence and never a value — this row is ` +
|
|
25
|
+
'read by the descent, by every generator and by anything that prints the tree, while a credential is read by ' +
|
|
26
|
+
'one thing at one moment. Put the value in the sealed store with `thread create secret`');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return found;
|
|
30
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Member } from '../core/thread.ts';
|
|
2
|
+
import { type Secret } from './secret.ts';
|
|
3
|
+
export declare const REACH: {
|
|
4
|
+
readonly postgres: "pg_";
|
|
5
|
+
readonly pglite: "pl_";
|
|
6
|
+
readonly browser: "zu_";
|
|
7
|
+
readonly https: "ht_";
|
|
8
|
+
readonly sdk: "sdk_";
|
|
9
|
+
readonly cli: "cli_";
|
|
10
|
+
};
|
|
11
|
+
export type Reach = keyof typeof REACH;
|
|
12
|
+
export interface Holds {
|
|
13
|
+
readonly takes: 'entities' | 'calls' | 'names';
|
|
14
|
+
readonly from: 'server' | 'browser' | 'either';
|
|
15
|
+
readonly addressed: boolean;
|
|
16
|
+
readonly arrives: 'npm' | 'machine' | null;
|
|
17
|
+
readonly migrations: boolean;
|
|
18
|
+
readonly references: boolean;
|
|
19
|
+
readonly sql: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const HOLDS: Readonly<Record<Reach, Holds>>;
|
|
22
|
+
export declare const SAID: Readonly<Record<Reach, string>>;
|
|
23
|
+
export interface Target extends Member {
|
|
24
|
+
readonly takes: readonly string[];
|
|
25
|
+
readonly requires: readonly string[];
|
|
26
|
+
readonly uses?: string;
|
|
27
|
+
readonly offers?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export interface Call extends Member {
|
|
30
|
+
readonly of: string;
|
|
31
|
+
readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
32
|
+
readonly path: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const CALL_PREFIX = "cl_";
|
|
35
|
+
export declare const fills: (one: Call) => readonly string[];
|
|
36
|
+
export declare function reachOf(id: string): Reach | undefined;
|
|
37
|
+
export declare function holdsOf(id: string): Holds | undefined;
|
|
38
|
+
export declare function addressOf(one: Target, secrets: readonly Secret[]): Secret | undefined;
|
|
39
|
+
export declare function credentialsOf(one: Target, secrets: readonly Secret[]): readonly Secret[];
|
|
40
|
+
export declare function servesBrowser(one: Target, secrets: readonly Secret[]): true | string;
|
|
41
|
+
export declare const unreachable: (targets: readonly Target[]) => readonly string[];
|
|
42
|
+
export declare function misnamed(target: string, name: string): string | undefined;
|
|
43
|
+
export declare function misaccepted(targets: readonly Target[], entities: readonly string[], calls: readonly Call[]): readonly string[];
|
|
44
|
+
export declare function unaddressed(targets: readonly Target[], secrets: readonly Secret[]): readonly string[];
|
|
45
|
+
export declare function unheld(targets: readonly Target[], secrets: readonly Secret[]): readonly string[];
|
|
46
|
+
export declare function unanswered(calls: readonly Call[], targets: readonly Target[]): readonly string[];
|
|
47
|
+
export declare function misused(targets: readonly Target[]): readonly string[];
|
|
48
|
+
export declare function refusals(targets: readonly Target[], secrets: readonly Secret[], calls: readonly Call[], entities: readonly string[]): readonly string[];
|
|
49
|
+
export declare function targetOf(entity: string, targets: readonly Target[]): Target | string;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { SECRECY, secrecyOf } from "./secret.js";
|
|
2
|
+
export const REACH = {
|
|
3
|
+
postgres: 'pg_',
|
|
4
|
+
pglite: 'pl_',
|
|
5
|
+
browser: 'zu_',
|
|
6
|
+
https: 'ht_',
|
|
7
|
+
sdk: 'sdk_',
|
|
8
|
+
cli: 'cli_',
|
|
9
|
+
};
|
|
10
|
+
export const HOLDS = {
|
|
11
|
+
postgres: { takes: 'entities', from: 'server', addressed: true, arrives: null, migrations: true, references: true, sql: true },
|
|
12
|
+
pglite: { takes: 'entities', from: 'either', addressed: true, arrives: null, migrations: true, references: true, sql: true },
|
|
13
|
+
browser: { takes: 'entities', from: 'browser', addressed: false, arrives: null, migrations: false, references: false, sql: false },
|
|
14
|
+
https: { takes: 'calls', from: 'either', addressed: true, arrives: null, migrations: false, references: false, sql: false },
|
|
15
|
+
sdk: { takes: 'names', from: 'either', addressed: false, arrives: 'npm', migrations: false, references: false, sql: false },
|
|
16
|
+
cli: { takes: 'names', from: 'server', addressed: false, arrives: 'machine', migrations: false, references: false, sql: false },
|
|
17
|
+
};
|
|
18
|
+
export const SAID = {
|
|
19
|
+
postgres: 'A postgres this app connects to over the network. It keeps entities and holds everything a declaration can state about their shape — tables, references and invariants — so what is kept here is held to that shape by the database itself rather than by whatever remembered to check. Who may touch a row is not asked of it: that is answered by the app, against the caller who actually arrived. It is reached from a server only: a connection to it from a page is a database somebody else can open. This is where a thing goes when losing it would matter.',
|
|
20
|
+
pglite: 'A postgres running inside the process that opened it. It keeps entities and holds tables, references and invariants, exactly as the networked one does — what differs is who can reach it, not what it can be told to hold. What belongs here is what the caller is already entitled to see: a local copy, a working set, an offline cache.',
|
|
21
|
+
browser: 'A store in the browser, held in memory the viewer owns. It keeps entities and it is addressed by nothing, because it is already where the code runs. It holds no migration, because a page load builds it from nothing every time. What belongs here is what the interface needs to be responsive and can afford to lose.',
|
|
22
|
+
https: 'Something this app calls over https — a vendor, a service, another app. It accepts calls rather than entities: each is declared with the verb and the path it takes, so a node names the call and never writes a url. Its address and its credential are both secrets, and whether it may be reached from a browser is decided by which kind of secret it needs rather than by the reach itself.',
|
|
23
|
+
sdk: 'An npm package this app calls directly instead of over the wire — stripe, supabase, neon. Declaring one installs it and reads the methods it publishes, then asks which of them this app may call. What is picked is written down, and a method that is not on that list is refused before the package is imported: what can ever run is fixed when the target is declared, not by whoever calls it. Credentials come from the vault at the moment of the call and are never written anywhere.',
|
|
24
|
+
cli: 'A program this app runs as a command — git is the one that made this necessary. It is not installed, because it is not a dependency: it is either on the machine or it is not, and declaring one checks that it is there and says what to do when it is not. What it accepts is its subcommands, read off the program itself at the moment it is declared and written down: the whole surface the tool really has, because it was asked rather than typed, and a subcommand that is not on that list never runs. Declaring one is what brings `thread run <name>` into being, so the CLI grows a command by the app stating an integration rather than by anybody writing a verb — and an app that has not declared it has no such command at all.',
|
|
25
|
+
};
|
|
26
|
+
export const CALL_PREFIX = 'cl_';
|
|
27
|
+
const FILLED = /\{([a-z][a-z0-9_]*)\}/g;
|
|
28
|
+
export const fills = (one) => [...one.path.matchAll(FILLED)].map((each) => each[1] ?? '');
|
|
29
|
+
export function reachOf(id) {
|
|
30
|
+
return Object.keys(REACH).find((one) => id.startsWith(REACH[one]));
|
|
31
|
+
}
|
|
32
|
+
export function holdsOf(id) {
|
|
33
|
+
const what = reachOf(id);
|
|
34
|
+
return what === undefined ? undefined : HOLDS[what];
|
|
35
|
+
}
|
|
36
|
+
export function addressOf(one, secrets) {
|
|
37
|
+
if (holdsOf(one.id)?.addressed !== true)
|
|
38
|
+
return undefined;
|
|
39
|
+
const held = new Map(secrets.map((each) => [each.id, each]));
|
|
40
|
+
return one.requires.map((each) => held.get(each)).find((each) => each !== undefined && each.sends === undefined);
|
|
41
|
+
}
|
|
42
|
+
export function credentialsOf(one, secrets) {
|
|
43
|
+
const held = new Map(secrets.map((each) => [each.id, each]));
|
|
44
|
+
return one.requires
|
|
45
|
+
.map((each) => held.get(each))
|
|
46
|
+
.filter((each) => each !== undefined && each.sends !== undefined);
|
|
47
|
+
}
|
|
48
|
+
export function servesBrowser(one, secrets) {
|
|
49
|
+
const holds = holdsOf(one.id);
|
|
50
|
+
if (holds === undefined) {
|
|
51
|
+
return `\`${one.id}\` wears none of the prefixes a target is reached by, so nothing can tell where it may be reached from`;
|
|
52
|
+
}
|
|
53
|
+
if (holds.from === 'server') {
|
|
54
|
+
return (`\`${one.id}\` is reached over ${String(reachOf(one.id))}, and that reach is server-only whatever it is ` +
|
|
55
|
+
'authenticated with — the refusal is about the reach itself and not about any credential');
|
|
56
|
+
}
|
|
57
|
+
const held = new Map(secrets.map((each) => [each.id, each]));
|
|
58
|
+
for (const named of one.requires) {
|
|
59
|
+
const secret = held.get(named);
|
|
60
|
+
if (secret === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (secrecyOf(secret.id) === 'server') {
|
|
63
|
+
return (`\`${one.id}\` requires \`${secret.id}\`, which wears \`${SECRECY.server}\` — a value only a server may hold. ` +
|
|
64
|
+
'Everything a browser bundle reaches is in the bundle, so this would ship the credential to whoever opens the page');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
export const unreachable = (targets) => targets
|
|
70
|
+
.filter((one) => reachOf(one.id) === undefined)
|
|
71
|
+
.map((one) => `\`${one.id}\` wears none of the prefixes a target is reached by (${Object.values(REACH).join(', ')}), so ` +
|
|
72
|
+
'nothing can tell how to reach it — and how a thing is reached decides what may be declared for it');
|
|
73
|
+
const SHAPED = {
|
|
74
|
+
sdk: /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/,
|
|
75
|
+
cli: /^[a-z][a-z0-9-]*$/,
|
|
76
|
+
};
|
|
77
|
+
const EVERY_VALUE = new Set(['constructor', '__proto__', 'prototype']);
|
|
78
|
+
export function misnamed(target, name) {
|
|
79
|
+
const reach = reachOf(target);
|
|
80
|
+
if (reach !== 'sdk' && reach !== 'cli')
|
|
81
|
+
return undefined;
|
|
82
|
+
if (EVERY_VALUE.has(name)) {
|
|
83
|
+
return (`\`${name}\` is a step every JavaScript value has rather than something \`${target}\` offers, and walking to ` +
|
|
84
|
+
'it leaves what was declared');
|
|
85
|
+
}
|
|
86
|
+
if (!SHAPED[reach].test(name)) {
|
|
87
|
+
return reach === 'sdk'
|
|
88
|
+
? `\`${name}\` is not a method path — one name, or names joined by dots, e.g. \`customers.list\``
|
|
89
|
+
: `\`${name}\` is not a subcommand — one word as the program spells it, e.g. \`commit\` or \`rev-parse\``;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
export function misaccepted(targets, entities, calls) {
|
|
94
|
+
const isEntity = new Set(entities);
|
|
95
|
+
const isCall = new Set(calls.map((one) => one.id));
|
|
96
|
+
const found = [];
|
|
97
|
+
for (const one of targets) {
|
|
98
|
+
const holds = holdsOf(one.id);
|
|
99
|
+
if (holds === undefined)
|
|
100
|
+
continue;
|
|
101
|
+
for (const named of one.takes) {
|
|
102
|
+
if (holds.takes === 'entities' && isEntity.has(named))
|
|
103
|
+
continue;
|
|
104
|
+
if (holds.takes === 'calls' && isCall.has(named))
|
|
105
|
+
continue;
|
|
106
|
+
if (holds.takes === 'names') {
|
|
107
|
+
found.push(`\`${one.id}\` is reached as ${String(reachOf(one.id))} and its \`takes\` names "${named}" — what an ` +
|
|
108
|
+
'installed package or a program responds to goes in `offers`, not here. `takes` is a demand, and the ' +
|
|
109
|
+
'descent requires every word on one to be coined by a row in this tree; a method name never can be');
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
found.push(`\`${one.id}\` is reached over ${String(reachOf(one.id))} and so accepts ${holds.takes}, and its \`takes\` names ` +
|
|
113
|
+
`"${named}", which is ${isEntity.has(named) ? 'an entity' : isCall.has(named) ? 'a call' : 'nothing this app declares'}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return found;
|
|
117
|
+
}
|
|
118
|
+
export function unaddressed(targets, secrets) {
|
|
119
|
+
const held = new Map(secrets.map((one) => [one.id, one]));
|
|
120
|
+
const found = [];
|
|
121
|
+
for (const one of targets) {
|
|
122
|
+
const holds = holdsOf(one.id);
|
|
123
|
+
if (holds === undefined)
|
|
124
|
+
continue;
|
|
125
|
+
const addresses = one.requires
|
|
126
|
+
.map((each) => held.get(each))
|
|
127
|
+
.filter((each) => each !== undefined && each.sends === undefined);
|
|
128
|
+
if (!holds.addressed) {
|
|
129
|
+
if (holds.arrives === null && addresses.length > 0) {
|
|
130
|
+
found.push(`\`${one.id}\` is reached over ${String(reachOf(one.id))}, which is addressed by nothing and needs nothing to ` +
|
|
131
|
+
`reach it, and it requires \`${addresses[0]?.id ?? ''}\` — there is nowhere for that value to go`);
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (addresses.length === 0) {
|
|
136
|
+
found.push(`\`${one.id}\` is reached over ${String(reachOf(one.id))} and nothing says where it is. Require one secret with ` +
|
|
137
|
+
'no `sends` — that one is its address. A credential with nowhere to be sent is a value nothing can use');
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (addresses.length > 1) {
|
|
141
|
+
found.push(`\`${one.id}\` requires ${addresses.map((each) => `\`${each.id}\``).join(' and ')}, and neither says where it goes, ` +
|
|
142
|
+
'so both read as its address. One place is reached at one address');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return found;
|
|
146
|
+
}
|
|
147
|
+
export function unheld(targets, secrets) {
|
|
148
|
+
const known = new Set(secrets.map((one) => one.id));
|
|
149
|
+
const found = [];
|
|
150
|
+
for (const one of targets) {
|
|
151
|
+
for (const named of one.requires) {
|
|
152
|
+
if (known.has(named))
|
|
153
|
+
continue;
|
|
154
|
+
found.push(`\`${one.id}\` requires "${named}", and this app declares no secret by that name. A secret is declared before it ` +
|
|
155
|
+
'is required, so the one place saying who may hold a value is never whichever place happened to need it');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return found;
|
|
159
|
+
}
|
|
160
|
+
export function unanswered(calls, targets) {
|
|
161
|
+
const held = new Map(targets.map((one) => [one.id, one]));
|
|
162
|
+
const found = [];
|
|
163
|
+
for (const one of calls) {
|
|
164
|
+
const of = held.get(one.of);
|
|
165
|
+
if (of === undefined) {
|
|
166
|
+
found.push(`\`${one.id}\` is a call on "${one.of}", and this app declares no target by that name`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const holds = holdsOf(of.id);
|
|
170
|
+
if (holds !== undefined && holds.takes !== 'calls') {
|
|
171
|
+
found.push(`\`${one.id}\` is a call on \`${of.id}\`, which is reached over ${String(reachOf(of.id))} and keeps entities. ` +
|
|
172
|
+
'A place you keep rows in is not a place you issue verbs at');
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (!of.takes.includes(one.id)) {
|
|
176
|
+
found.push(`\`${one.id}\` is a call on \`${of.id}\` and \`${of.id}\` does not accept it. Add it to that target's \`takes\` — ` +
|
|
177
|
+
'what a target accepts is read off the target, not gathered from whatever pointed at it');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return found;
|
|
181
|
+
}
|
|
182
|
+
export function misused(targets) {
|
|
183
|
+
const found = [];
|
|
184
|
+
for (const one of targets) {
|
|
185
|
+
const holds = holdsOf(one.id);
|
|
186
|
+
if (holds === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
if (holds.arrives !== null && (one.uses ?? '') === '') {
|
|
189
|
+
found.push(`\`${one.id}\` is reached as ${String(reachOf(one.id))} and does not say what it uses — ` +
|
|
190
|
+
`${holds.arrives === 'npm' ? 'the npm package to install' : 'the program to run'} goes in \`uses\`, because the ` +
|
|
191
|
+
'id is what this app calls it and not what it is');
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (holds.arrives === null && one.uses !== undefined) {
|
|
195
|
+
found.push(`\`${one.id}\` is reached over ${String(reachOf(one.id))}, where nothing is installed and nothing is spawned, ` +
|
|
196
|
+
`and it states \`uses: '${one.uses}'\` — there is nothing for that name to be`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
201
|
+
export function refusals(targets, secrets, calls, entities) {
|
|
202
|
+
return [
|
|
203
|
+
...unreachable(targets),
|
|
204
|
+
...unheld(targets, secrets),
|
|
205
|
+
...unaddressed(targets, secrets),
|
|
206
|
+
...unanswered(calls, targets),
|
|
207
|
+
...misaccepted(targets, entities, calls),
|
|
208
|
+
...misused(targets),
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
export function targetOf(entity, targets) {
|
|
212
|
+
const keeping = targets.filter((one) => holdsOf(one.id)?.takes === 'entities');
|
|
213
|
+
const found = keeping.filter((one) => one.takes.includes(entity));
|
|
214
|
+
const only = found[0];
|
|
215
|
+
if (only === undefined) {
|
|
216
|
+
return (`"${entity}" is kept by no target. Add its id to the \`takes\` of one of ` +
|
|
217
|
+
`${keeping.length === 0 ? '(this app declares none that keep things)' : keeping.map((one) => `\`${one.id}\``).join(', ')} — ` +
|
|
218
|
+
'an entity nothing keeps is a table no database builds, and nothing else here would ever say so');
|
|
219
|
+
}
|
|
220
|
+
if (found.length > 1) {
|
|
221
|
+
return (`"${entity}" is kept by ${found.map((one) => `\`${one.id}\``).join(' and ')}. One thing kept in two places is two ` +
|
|
222
|
+
'things that will disagree, and neither declaration says which one is right');
|
|
223
|
+
}
|
|
224
|
+
return only;
|
|
225
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Env } from '../core/actor.ts';
|
|
2
|
+
export declare const VAULT_DIR = ".thread";
|
|
3
|
+
export declare const SEALED_AT = ".thread/vault.sealed.json";
|
|
4
|
+
export declare const KEY_AT = ".thread/vault.key";
|
|
5
|
+
export declare const ALGORITHM = "aes-256-gcm";
|
|
6
|
+
export declare const KEY_BYTES = 32;
|
|
7
|
+
export declare const keyOf: (root: string) => Buffer | undefined;
|
|
8
|
+
export declare function keyed(root: string): Buffer;
|
|
9
|
+
export declare function seal(root: string, named: string, env: Env, value: string): void;
|
|
10
|
+
export declare const sealedIn: (root: string, env: Env) => readonly string[];
|
|
11
|
+
export declare function opened(root: string, declared: readonly string[], env: Env): (refs: readonly string[]) => Promise<Readonly<Record<string, string>>>;
|
|
12
|
+
export declare const IGNORES: readonly string[];
|