@prestack/thread 0.1.3 → 0.1.5
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 +563 -0
- package/dist/core/actor.d.ts +1 -1
- package/dist/core/actor.js +5 -4
- package/dist/core/actor.ts +5 -4
- package/dist/core/attach.d.ts +0 -7
- package/dist/core/attach.js +38 -67
- package/dist/core/attach.ts +34 -80
- package/dist/core/chain.js +13 -17
- package/dist/core/chain.ts +11 -15
- package/dist/core/condition.d.ts +4 -4
- package/dist/core/condition.js +2 -2
- package/dist/core/descend.d.ts +4 -2
- package/dist/core/descend.js +91 -23
- package/dist/core/descend.ts +90 -21
- package/dist/core/errors/catalogue.d.ts +52 -0
- package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
- package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
- package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
- package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
- package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
- package/dist/core/errors/namespaces.d.ts +3 -0
- package/dist/core/errors/namespaces.js +15 -0
- package/dist/core/errors/namespaces.ts +19 -0
- package/dist/core/finding.js +2 -2
- package/dist/core/finding.ts +1 -2
- package/dist/core/init.d.ts +1 -1
- package/dist/core/init.js +51 -5
- package/dist/core/init.ts +58 -5
- package/dist/core/invoked.d.ts +1 -1
- package/dist/core/invoked.js +3 -1
- package/dist/core/invoked.ts +5 -2
- package/dist/core/round.js +36 -22
- package/dist/core/round.ts +26 -9
- package/dist/core/rows.d.ts +4 -0
- package/dist/core/rows.js +31 -2
- package/dist/core/rows.ts +33 -1
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/scope.ts +2 -0
- package/dist/core/thread.d.ts +3 -3
- package/dist/core/tree.d.ts +4 -1
- package/dist/core/tree.js +26 -6
- package/dist/core/tree.ts +29 -6
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +3 -1
- package/dist/core/types.ts +2 -1
- package/dist/derivation/attach.js +3 -2
- package/dist/derivation/attach.ts +2 -1
- package/dist/derivation/generators/build.js +6 -6
- package/dist/derivation/generators/build.ts +1 -1
- package/dist/derivation/generators/check.js +1 -1
- package/dist/derivation/generators/determinism.js +1 -1
- package/dist/derivation/generators/release.js +60 -47
- package/dist/derivation/generators/release.ts +66 -43
- package/dist/derivation/release.d.ts +7 -3
- package/dist/derivation/release.js +14 -1
- package/dist/derivation/release.ts +22 -2
- package/dist/integrations/attach.d.ts +4 -0
- package/dist/integrations/attach.js +37 -56
- package/dist/integrations/attach.ts +54 -60
- package/dist/integrations/cli.d.ts +37 -0
- package/dist/integrations/cli.js +440 -0
- package/dist/integrations/cli.ts +543 -0
- package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
- package/dist/integrations/disk.js +435 -0
- package/dist/integrations/disk.ts +465 -0
- package/dist/integrations/generators/read.js +6 -6
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +10 -11
- package/dist/integrations/generators/vault.ts +4 -5
- package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
- package/dist/integrations/https.js +170 -0
- package/dist/integrations/https.ts +216 -0
- package/dist/integrations/pglite.d.ts +26 -0
- package/dist/integrations/pglite.js +143 -0
- package/dist/integrations/pglite.ts +206 -0
- package/dist/integrations/secret.d.ts +8 -2
- package/dist/integrations/secret.js +3 -5
- package/dist/integrations/secret.ts +10 -6
- package/dist/integrations/target.d.ts +155 -14
- package/dist/integrations/target.js +171 -50
- package/dist/integrations/target.ts +377 -61
- package/dist/integrations/vault.d.ts +1 -0
- package/dist/integrations/vault.js +20 -5
- package/dist/integrations/vault.ts +17 -5
- package/dist/integrity/attach.js +8 -77
- package/dist/integrity/attach.ts +7 -76
- package/dist/integrity/check-threads.js +5 -5
- package/dist/integrity/check-threads.ts +2 -2
- package/dist/integrity/constraint.d.ts +0 -1
- package/dist/integrity/constraint.js +267 -366
- package/dist/integrity/constraint.ts +267 -375
- package/dist/integrity/generators/compiler.js +14 -17
- package/dist/integrity/generators/compiler.ts +10 -15
- package/dist/integrity/generators/gate.js +15 -15
- package/dist/integrity/generators/gate.ts +10 -10
- package/dist/integrity/generators/issues.js +7 -7
- package/dist/integrity/generators/publish.js +39 -57
- package/dist/integrity/generators/publish.ts +44 -55
- package/dist/integrity/generators/trial.d.ts +3 -3
- package/dist/integrity/generators/trial.js +133 -165
- package/dist/integrity/generators/trial.ts +145 -169
- package/dist/integrity/lint.js +9 -13
- package/dist/integrity/lint.ts +7 -10
- package/dist/integrity/lsp/bin.js +5 -4
- package/dist/integrity/lsp/bin.ts +3 -1
- package/dist/integrity/lsp/diagnostics.js +3 -3
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +19 -30
- package/dist/integrity/lsp/lint.ts +16 -32
- package/dist/integrity/lsp/server.js +3 -3
- package/dist/integrity/slopnator.d.ts +3 -13
- package/dist/integrity/slopnator.js +59 -38
- package/dist/integrity/slopnator.ts +54 -38
- package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
- package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
- package/dist/integrity/slopnators/sl_inert_island.js +10 -8
- package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
- package/dist/integrity/slopnators/sl_missing_row.js +6 -51
- package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
- package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
- package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
- package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
- package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
- package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
- package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
- package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
- package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
- package/dist/integrity/slopnators/sl_stale_path.js +5 -22
- package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
- package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
- package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
- package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
- package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
- package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
- package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
- package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
- package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
- package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
- package/dist/integrity/slopnators/sl_underived_route.js +61 -0
- package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
- package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
- package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
- package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
- package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
- package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
- package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
- package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
- package/dist/integrity/slopnators/sl_unversioned.js +6 -4
- package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
- package/dist/integrity/sweep.d.ts +1 -0
- package/dist/integrity/sweep.js +105 -42
- package/dist/integrity/sweep.ts +115 -42
- package/dist/knowledge/attach.js +4 -3
- package/dist/knowledge/attach.ts +3 -2
- package/dist/knowledge/generators/docsgen.js +8 -6
- package/dist/knowledge/generators/docsgen.ts +4 -2
- package/dist/knowledge/generators/glossary.js +4 -4
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/access.d.ts +23 -0
- package/dist/operations/access.js +28 -1
- package/dist/operations/access.ts +43 -0
- package/dist/operations/attach.js +33 -113
- package/dist/operations/attach.ts +51 -117
- package/dist/operations/capabilities.d.ts +14 -5
- package/dist/operations/capabilities.js +13 -4
- package/dist/operations/capabilities.ts +27 -8
- package/dist/operations/dispatch.d.ts +8 -2
- package/dist/operations/dispatch.js +128 -185
- package/dist/operations/dispatch.ts +138 -191
- package/dist/operations/error-results.d.ts +1 -1
- package/dist/operations/error-results.js +2 -2
- package/dist/operations/error-results.ts +2 -2
- package/dist/operations/generators/capability.d.ts +11 -1
- package/dist/operations/generators/capability.js +481 -224
- package/dist/operations/generators/capability.ts +562 -233
- package/dist/operations/generators/compile.js +66 -39
- package/dist/operations/generators/compile.ts +78 -39
- package/dist/operations/generators/permits.js +11 -12
- package/dist/operations/generators/permits.ts +3 -4
- package/dist/operations/generators/seed.d.ts +1 -0
- package/dist/operations/generators/seed.js +19 -17
- package/dist/operations/generators/seed.ts +22 -14
- package/dist/operations/generators/trial.d.ts +6 -8
- package/dist/operations/generators/trial.js +200 -70
- package/dist/operations/generators/trial.ts +230 -93
- package/dist/operations/nodes.d.ts +15 -16
- package/dist/operations/nodes.ts +16 -17
- package/dist/operations/operations.d.ts +40 -4
- package/dist/operations/operations.js +1 -0
- package/dist/operations/operations.ts +61 -4
- package/dist/operations/result.d.ts +2 -2
- package/dist/operations/result.ts +2 -2
- package/package.json +1 -4
- package/dist/core/answers.d.ts +0 -171
- package/dist/core/answers.js +0 -789
- package/dist/core/answers.ts +0 -999
- package/dist/core/client.d.ts +0 -63
- package/dist/core/client.js +0 -249
- package/dist/core/client.ts +0 -289
- package/dist/core/files.js +0 -171
- package/dist/core/files.ts +0 -222
- package/dist/integrations/module.d.ts +0 -6
- package/dist/integrations/module.js +0 -153
- package/dist/integrations/module.ts +0 -191
- package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
- package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
- package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
- package/dist/operations/cost.d.ts +0 -14
- package/dist/operations/cost.js +0 -37
- package/dist/operations/cost.ts +0 -58
- package/dist/operations/errors/errors.d.ts +0 -52
- package/dist/operations/http.js +0 -124
- package/dist/operations/http.ts +0 -169
- package/dist/operations/memory.d.ts +0 -104
- package/dist/operations/memory.js +0 -365
- package/dist/operations/memory.ts +0 -470
- package/dist/operations/metering.d.ts +0 -55
- package/dist/operations/metering.js +0 -247
- package/dist/operations/metering.ts +0 -328
- package/dist/operations/queue-policy.d.ts +0 -3
- package/dist/operations/queue-policy.js +0 -8
- package/dist/operations/queue-policy.ts +0 -10
- package/dist/operations/seams.d.ts +0 -239
- package/dist/operations/seams.js +0 -45
- package/dist/operations/seams.ts +0 -364
- package/dist/operations/store.d.ts +0 -14
- package/dist/operations/store.js +0 -1
- package/dist/operations/store.ts +0 -29
- /package/dist/{operations → core}/errors/classes.d.ts +0 -0
- /package/dist/{operations → core}/errors/classes.js +0 -0
- /package/dist/{operations → core}/errors/classes.ts +0 -0
- /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
- /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
package/dist/core/answers.js
DELETED
|
@@ -1,789 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "./files.js";
|
|
3
|
-
import { join, relative } from 'node:path';
|
|
4
|
-
import { argv, exit, stderr, stdin, stdout } from 'node:process';
|
|
5
|
-
import { createInterface } from 'node:readline';
|
|
6
|
-
import { LISTS, listFields, listShape, shapesNamed } from "./descend.js";
|
|
7
|
-
import { quoted, valuesIn } from "./rows.js";
|
|
8
|
-
export const RECEIPT = '--receipt';
|
|
9
|
-
export const ENDS = '.';
|
|
10
|
-
export const FROM = '--from';
|
|
11
|
-
export const CHAINED = '--chained';
|
|
12
|
-
export const BODY = 'body';
|
|
13
|
-
export const WRITES = 'writes';
|
|
14
|
-
export const asFlag = (key) => `--${key}`;
|
|
15
|
-
export function refuse(because) {
|
|
16
|
-
stderr.write(`create: ${because}\n`);
|
|
17
|
-
exit(1);
|
|
18
|
-
}
|
|
19
|
-
export function slug(one) {
|
|
20
|
-
return one
|
|
21
|
-
.trim()
|
|
22
|
-
.toLowerCase()
|
|
23
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
24
|
-
.replace(/^_+|_+$/g, '');
|
|
25
|
-
}
|
|
26
|
-
export function within(root, path) {
|
|
27
|
-
return relative(root, path).split('\\').join('/');
|
|
28
|
-
}
|
|
29
|
-
const NAMED = /^([a-z][A-Za-z0-9_.-]*):[ \t]?(.*)$/;
|
|
30
|
-
export function parsed(text) {
|
|
31
|
-
const lines = text.split('\n');
|
|
32
|
-
const found = [];
|
|
33
|
-
for (let at = 0; at < lines.length; at += 1) {
|
|
34
|
-
const line = lines[at] ?? '';
|
|
35
|
-
if (line.trim() === '' || line.trimStart().startsWith('#'))
|
|
36
|
-
continue;
|
|
37
|
-
const held = NAMED.exec(line);
|
|
38
|
-
if (held === null) {
|
|
39
|
-
refuse(`line ${String(at + 1)} is neither blank nor an answer: ${JSON.stringify(line.slice(0, 60))}. ` +
|
|
40
|
-
'Every answer is written `name: value`, and a body is written under `body:` and ended by a line ' +
|
|
41
|
-
`holding only ${ENDS}.`);
|
|
42
|
-
}
|
|
43
|
-
const key = held[1] ?? '';
|
|
44
|
-
if (key !== 'body') {
|
|
45
|
-
found.push({ at: at + 1, key, value: (held[2] ?? '').trim() });
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const body = [];
|
|
49
|
-
const beside = held[2] ?? '';
|
|
50
|
-
if (beside.trim() !== '')
|
|
51
|
-
body.push(beside);
|
|
52
|
-
let shut = false;
|
|
53
|
-
for (at += 1; at < lines.length; at += 1) {
|
|
54
|
-
if ((lines[at] ?? '').trim() === ENDS) {
|
|
55
|
-
shut = true;
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
body.push(lines[at] ?? '');
|
|
59
|
-
}
|
|
60
|
-
if (!shut) {
|
|
61
|
-
refuse(`the \`body:\` opened on line ${String(found.length === 0 ? 1 : at)} is never closed — a body ends at a ` +
|
|
62
|
-
`line holding only ${ENDS}, and this one runs to the end of the input.`);
|
|
63
|
-
}
|
|
64
|
-
found.push({ at: at + 1, key, value: body.join('\n').trim() });
|
|
65
|
-
}
|
|
66
|
-
return found;
|
|
67
|
-
}
|
|
68
|
-
export function scoped(given, opens) {
|
|
69
|
-
const own = [];
|
|
70
|
-
const each = [];
|
|
71
|
-
const lists = new Set(LISTS);
|
|
72
|
-
let mine = false;
|
|
73
|
-
for (const one of given) {
|
|
74
|
-
if (one.key === opens) {
|
|
75
|
-
each.push([one]);
|
|
76
|
-
mine = true;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (lists.has(one.key)) {
|
|
80
|
-
mine = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (mine)
|
|
84
|
-
(each[each.length - 1] ?? []).push(one);
|
|
85
|
-
else if (each.length === 0)
|
|
86
|
-
own.push(one);
|
|
87
|
-
}
|
|
88
|
-
return { own, each };
|
|
89
|
-
}
|
|
90
|
-
const locate = (one) => one.at > 0 ? `line ${String(one.at)}` : `\`--${one.key}\``;
|
|
91
|
-
export const named = (all) => all.map((one) => one.at);
|
|
92
|
-
export const prompted = (one) => one.asks ?? `${one.at} — ${one.guide}${one.optional === true ? ', or blank' : ''}: `;
|
|
93
|
-
export const mapped = (one, said) => one.map === true && !(one.worded === true && !said.includes('='));
|
|
94
|
-
export const CARRIES = ['cites', 'requires', 'takes', 'by', 'runs'];
|
|
95
|
-
export function ownAnswers(all) {
|
|
96
|
-
const opens = new Set(LISTS);
|
|
97
|
-
const at = all.findIndex((one) => opens.has(one.key));
|
|
98
|
-
return at === -1 ? all : all.slice(0, at);
|
|
99
|
-
}
|
|
100
|
-
export async function stating(asks, all, piped, ask, line, settled) {
|
|
101
|
-
const own = ownAnswers(all);
|
|
102
|
-
const skip = new Set([...settled, ...LISTS]);
|
|
103
|
-
const stated = [];
|
|
104
|
-
for (const one of asks) {
|
|
105
|
-
if (skip.has(one.at))
|
|
106
|
-
continue;
|
|
107
|
-
const given = (piped ? (answer(own, one.at) ?? '') : await ask(one.at, prompted(one))).trim();
|
|
108
|
-
if (given === '')
|
|
109
|
-
continue;
|
|
110
|
-
stated.push({ field: one.at, said: given, list: one.list === true, map: one.map === true, boolean: one.boolean === true, worded: one.worded === true });
|
|
111
|
-
}
|
|
112
|
-
if (piped)
|
|
113
|
-
return { stated, kids: childrenIn(all) };
|
|
114
|
-
const kids = new Map();
|
|
115
|
-
for (const list of LISTS) {
|
|
116
|
-
if (!asks.some((one) => one.at === list))
|
|
117
|
-
continue;
|
|
118
|
-
const found = await children(list, all, ask, line);
|
|
119
|
-
if (found.length > 0)
|
|
120
|
-
kids.set(list, found);
|
|
121
|
-
}
|
|
122
|
-
return { stated, kids };
|
|
123
|
-
}
|
|
124
|
-
export function arrayValue(pad, key, said, found) {
|
|
125
|
-
if (found === undefined || found.length === 0)
|
|
126
|
-
return asList(said);
|
|
127
|
-
const lines = rowLines(pad, [], new Map([[key, found]]));
|
|
128
|
-
return ['[', ...lines.slice(1, -1), `${pad}]`].join('\n');
|
|
129
|
-
}
|
|
130
|
-
const BARE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
131
|
-
const NUMBER = /^-?\d+(?:\.\d+)?$/;
|
|
132
|
-
const nested = (held) => {
|
|
133
|
-
const pairs = Object.entries(held).map(([key, value]) => {
|
|
134
|
-
const name = BARE.test(key) ? key : quoted(key);
|
|
135
|
-
if (typeof value === 'object' && value !== null)
|
|
136
|
-
return `${name}: ${nested(value)}`;
|
|
137
|
-
const said = String(value);
|
|
138
|
-
return `${name}: ${NUMBER.test(said) ? said : quoted(said)}`;
|
|
139
|
-
});
|
|
140
|
-
return pairs.length === 0 ? '{}' : `{ ${pairs.join(', ')} }`;
|
|
141
|
-
};
|
|
142
|
-
export const asMap = (said) => {
|
|
143
|
-
const held = {};
|
|
144
|
-
for (const one of said.split(',').map((each) => each.trim()).filter((each) => each !== '')) {
|
|
145
|
-
const at = one.indexOf('=');
|
|
146
|
-
if (at < 1) {
|
|
147
|
-
refuse(`\`${one}\` is one entry of a name-and-value field, and it states no value. Write each one as ` +
|
|
148
|
-
'`name=value`, separated by commas — `core=*` says that package at any version, and a dot in the name ' +
|
|
149
|
-
'nests it: `dark.background=colour-surface`. Nothing was written.');
|
|
150
|
-
}
|
|
151
|
-
const path = one.slice(0, at).trim().split('.');
|
|
152
|
-
const last = path[path.length - 1] ?? '';
|
|
153
|
-
let into = held;
|
|
154
|
-
for (const step of path.slice(0, -1)) {
|
|
155
|
-
const next = into[step];
|
|
156
|
-
if (next !== undefined && (typeof next !== 'object' || next === null)) {
|
|
157
|
-
refuse(`\`${one}\` nests under \`${step}\`, and \`${step}\` is already given a value of its own. One or the other. Nothing was written.`);
|
|
158
|
-
}
|
|
159
|
-
if (next === undefined)
|
|
160
|
-
into[step] = {};
|
|
161
|
-
into = into[step];
|
|
162
|
-
}
|
|
163
|
-
if (typeof into[last] === 'object' && into[last] !== null) {
|
|
164
|
-
refuse(`\`${one}\` gives \`${last}\` a value, and entries already nest under it. One or the other. Nothing was written.`);
|
|
165
|
-
}
|
|
166
|
-
into[last] = one.slice(at + 1).trim();
|
|
167
|
-
}
|
|
168
|
-
return nested(held);
|
|
169
|
-
};
|
|
170
|
-
export function gathered(all, fields) {
|
|
171
|
-
const maps = new Set(fields.filter((one) => one.map).map((one) => one.name));
|
|
172
|
-
const entries = new Map();
|
|
173
|
-
for (const one of all) {
|
|
174
|
-
const dot = one.key.indexOf('.');
|
|
175
|
-
if (dot === -1 || !maps.has(one.key.slice(0, dot)))
|
|
176
|
-
continue;
|
|
177
|
-
const head = one.key.slice(0, dot);
|
|
178
|
-
const held = entries.get(head) ?? { at: one.at, parts: [] };
|
|
179
|
-
held.parts.push(`${one.key.slice(dot + 1)}=${one.value}`);
|
|
180
|
-
entries.set(head, held);
|
|
181
|
-
}
|
|
182
|
-
const written = new Set();
|
|
183
|
-
const out = [];
|
|
184
|
-
for (const one of all) {
|
|
185
|
-
const dot = one.key.indexOf('.');
|
|
186
|
-
const head = dot === -1 ? one.key : one.key.slice(0, dot);
|
|
187
|
-
if (dot === -1 || !entries.has(head)) {
|
|
188
|
-
out.push(one);
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (written.has(head))
|
|
192
|
-
continue;
|
|
193
|
-
written.add(head);
|
|
194
|
-
const held = entries.get(head);
|
|
195
|
-
out.push({ at: held?.at ?? one.at, key: head, value: (held?.parts ?? []).join(', ') });
|
|
196
|
-
}
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
const asList = (said) => `[${said
|
|
200
|
-
.split(',')
|
|
201
|
-
.map((one) => one.trim())
|
|
202
|
-
.filter((one) => one !== '')
|
|
203
|
-
.map(quoted)
|
|
204
|
-
.join(', ')}]`;
|
|
205
|
-
export const asBoolean = (said) => {
|
|
206
|
-
const word = said.trim().toLowerCase();
|
|
207
|
-
if (word === 'yes' || word === 'y' || word === 'true')
|
|
208
|
-
return 'true';
|
|
209
|
-
if (word === 'no' || word === 'n' || word === 'false')
|
|
210
|
-
return 'false';
|
|
211
|
-
refuse(`\`${said}\` is not an answer to a yes-or-no field. Say yes or no. Nothing was written.`);
|
|
212
|
-
};
|
|
213
|
-
export function rowLines(pad, stated, kids) {
|
|
214
|
-
const kid = (one) => [
|
|
215
|
-
`${pad} {`,
|
|
216
|
-
`${pad} id: ${quoted(one.id)},`,
|
|
217
|
-
`${pad} at: ${quoted(one.at)},`,
|
|
218
|
-
`${pad} describes: ${quoted(one.describes)},`,
|
|
219
|
-
...(one.by === undefined || one.by === '' ? [] : [`${pad} by: ${quoted(one.by)},`]),
|
|
220
|
-
...(one.runs === undefined || one.runs === '' ? [] : [`${pad} runs: ${quoted(one.runs)},`]),
|
|
221
|
-
`${pad} version: 1,`,
|
|
222
|
-
`${pad} },`,
|
|
223
|
-
];
|
|
224
|
-
return [
|
|
225
|
-
...stated.map((one) => `${pad}${one.field}: ${mapped(one, one.said) ? asMap(one.said) : one.list ? asList(one.said) : one.boolean === true ? asBoolean(one.said) : quoted(one.said)},`),
|
|
226
|
-
...[...kids].flatMap(([list, found]) => [`${pad}${list}: [`, ...found.flatMap(kid), `${pad}],`]),
|
|
227
|
-
];
|
|
228
|
-
}
|
|
229
|
-
export function withChildren(row, lines) {
|
|
230
|
-
if (lines.length === 0)
|
|
231
|
-
return row;
|
|
232
|
-
const all = row.split('\n');
|
|
233
|
-
let at = all.length - 1;
|
|
234
|
-
while (at >= 0 && (all[at] ?? '').trim() !== '},' && (all[at] ?? '').trim() !== '}')
|
|
235
|
-
at -= 1;
|
|
236
|
-
if (at < 0) {
|
|
237
|
-
refuse('the row just written has no closing brace on a line of its own, so what it declares has nowhere to go. ' +
|
|
238
|
-
'Nothing more was written, because adding the row and dropping its children quietly is the one outcome ' +
|
|
239
|
-
'nobody could see.');
|
|
240
|
-
}
|
|
241
|
-
return [...all.slice(0, at), ...lines, ...all.slice(at)].join('\n');
|
|
242
|
-
}
|
|
243
|
-
export function childrenIn(all) {
|
|
244
|
-
const found = new Map();
|
|
245
|
-
for (const list of LISTS) {
|
|
246
|
-
const each = [];
|
|
247
|
-
for (const group of scoped(all, list).each) {
|
|
248
|
-
const id = (answer(group, list) ?? '').trim();
|
|
249
|
-
if (id === '')
|
|
250
|
-
continue;
|
|
251
|
-
const at = (answer(group, 'at') ?? '').trim();
|
|
252
|
-
const describes = (answer(group, 'describes') ?? '').trim();
|
|
253
|
-
if (at === '' || describes === '') {
|
|
254
|
-
refuse(`\`${list} ${id}\` names no ${at === '' ? 'file' : 'meaning'}. Every row says which file it is about and ` +
|
|
255
|
-
'what that file is, and one missing either is a row the next person cannot act on. Nothing more was written.');
|
|
256
|
-
}
|
|
257
|
-
const body = (answer(group, BODY) ?? '').trim();
|
|
258
|
-
const writes = (answer(group, WRITES) ?? '').trim();
|
|
259
|
-
each.push({
|
|
260
|
-
id,
|
|
261
|
-
at,
|
|
262
|
-
describes,
|
|
263
|
-
...(writes === '' ? {} : { writes }),
|
|
264
|
-
...(list === 'declares'
|
|
265
|
-
? {}
|
|
266
|
-
: { by: (answer(group, 'by') ?? '').trim(), runs: (answer(group, 'runs') ?? '').trim() }),
|
|
267
|
-
...(body === '' ? {} : { body }),
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
if (each.length > 0)
|
|
271
|
-
found.set(list, each);
|
|
272
|
-
}
|
|
273
|
-
return found;
|
|
274
|
-
}
|
|
275
|
-
export const BODIED = ['declares', 'seeds'];
|
|
276
|
-
export const WRITTEN = ['derives', 'seeds'];
|
|
277
|
-
export function seeding(kids, root) {
|
|
278
|
-
const said = [];
|
|
279
|
-
for (const list of BODIED) {
|
|
280
|
-
for (const one of kids.get(list) ?? []) {
|
|
281
|
-
const into = join(root, one.at);
|
|
282
|
-
const there = existsSync(into);
|
|
283
|
-
if (one.body === undefined || one.body === '') {
|
|
284
|
-
said.push(there
|
|
285
|
-
? `${one.at} was already on disk, and the row now names it.`
|
|
286
|
-
: `${one.at} is declared and nothing is in it yet — write it with ` +
|
|
287
|
-
`\`${TYPED} edit ${one.at} --from <path>\`.`);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (there) {
|
|
291
|
-
said.push(`${one.at} was already on disk, so the body you wrote was NOT put over it. Nothing was lost — ` +
|
|
292
|
-
`\`${TYPED} read ${one.at}\` prints what is there.`);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
writeFileSync(into, one.body.endsWith('\n') ? one.body : `${one.body}\n`, 'utf8');
|
|
296
|
-
said.push(`wrote ${one.at} with the body you gave it.`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return said;
|
|
300
|
-
}
|
|
301
|
-
const ASKED = {
|
|
302
|
-
declares: 'name each file a person writes that this one owns',
|
|
303
|
-
derives: 'name each file a command writes from this one, and the command that writes it',
|
|
304
|
-
seeds: 'name each file a command shapes here',
|
|
305
|
-
};
|
|
306
|
-
export async function children(list, all, ask, line) {
|
|
307
|
-
const written = WRITTEN.includes(list);
|
|
308
|
-
const out = [];
|
|
309
|
-
const owed = (id, at, describes) => {
|
|
310
|
-
if (at !== '' && describes !== '')
|
|
311
|
-
return;
|
|
312
|
-
refuse(`\`${list} ${id}\` names no ${at === '' ? 'file' : 'meaning'}. Every row says which file it is about and what ` +
|
|
313
|
-
'that file is, and one missing either is a row the next person cannot act on. Nothing more was written.');
|
|
314
|
-
};
|
|
315
|
-
const bodies = [
|
|
316
|
-
...(written ? [`${asFlag(WRITES)} is the generator's own body — the code that writes the template`] : []),
|
|
317
|
-
...(BODIED.includes(list) ? [`${asFlag(BODY)} is the body of the file itself`] : []),
|
|
318
|
-
];
|
|
319
|
-
stdout.write(`\n ${list} — ${ASKED[list] ?? ''}. ` +
|
|
320
|
-
(bodies.length === 0
|
|
321
|
-
? ''
|
|
322
|
-
: `Each one takes ${bodies.length === 1 ? 'one body' : `${String(bodies.length)} bodies, and they are never the same text`}: ` +
|
|
323
|
-
`${bodies.join('; ')}. `) +
|
|
324
|
-
'A blank id ends the list.\n');
|
|
325
|
-
for (;;) {
|
|
326
|
-
const id = (await ask(list, ` ${list} — id, or blank to stop: `)).trim();
|
|
327
|
-
if (id === '')
|
|
328
|
-
break;
|
|
329
|
-
const at = (await ask('at', ' at — the file this names: ')).trim();
|
|
330
|
-
const describes = (await ask('describes', ' describes — what that file is, to somebody who has not opened it: ')).trim();
|
|
331
|
-
owed(id, at, describes);
|
|
332
|
-
const by = written ? (await ask('by', ' by — the command that writes it: ')).trim() : undefined;
|
|
333
|
-
const runs = written ? (await ask('runs', ' runs — the command that catches it drifting: ')).trim() : undefined;
|
|
334
|
-
const writes = written
|
|
335
|
-
? await bodied(`the generator behind \`${by ?? 'that command'}\` — this is ${asFlag(WRITES)}`, '', false, all, line)
|
|
336
|
-
: undefined;
|
|
337
|
-
const body = BODIED.includes(list)
|
|
338
|
-
? await bodied(`${at} itself — this is ${asFlag(BODY)}`, '', false, all, line)
|
|
339
|
-
: undefined;
|
|
340
|
-
out.push({
|
|
341
|
-
id,
|
|
342
|
-
at,
|
|
343
|
-
describes,
|
|
344
|
-
...(written ? { by, runs } : {}),
|
|
345
|
-
...(writes === undefined ? {} : { writes }),
|
|
346
|
-
...(body === undefined ? {} : { body }),
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
return out;
|
|
350
|
-
}
|
|
351
|
-
export async function bodied(what, shows, piped, all, line) {
|
|
352
|
-
const from = argv[argv.indexOf(FROM) + 1];
|
|
353
|
-
if (argv.includes(FROM) && from !== undefined && !from.startsWith('-')) {
|
|
354
|
-
try {
|
|
355
|
-
return refuse(`${FROM} seeds the body out of a file, and a file is not a receipt: nothing here checked it and no descent reached it. Give the body with ${asFlag(BODY)} on the command. Nothing was written.`);
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
refuse(`\`${FROM} ${from}\` names a file nothing could read, so there is no body to seed and nothing was ` +
|
|
359
|
-
`written. Write it first, then run this again — or drop ${FROM} and type the body when it is asked for.`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (piped) {
|
|
363
|
-
const said = answer(all, BODY);
|
|
364
|
-
return said === undefined || said.trim() === '' ? undefined : said;
|
|
365
|
-
}
|
|
366
|
-
if (shows.trim() === '')
|
|
367
|
-
stdout.write(` body — write what goes in ${what}.\n`);
|
|
368
|
-
else {
|
|
369
|
-
stdout.write(` body — what a ${what} is handed looks like this:\n`);
|
|
370
|
-
for (const one of shows.split('\n'))
|
|
371
|
-
stdout.write(` ${one}\n`);
|
|
372
|
-
}
|
|
373
|
-
stdout.write(` write yours, then a line holding only ${ENDS}. A first line of ${ENDS} leaves it unwritten, and\n` +
|
|
374
|
-
` \`${TYPED} trials\` reports it unproven until somebody writes it.\n` +
|
|
375
|
-
` Or give ${FROM} <path> instead and the file at that path is what gets seeded, which is the one\n` +
|
|
376
|
-
' form that needs no terminal.\n');
|
|
377
|
-
const held = [];
|
|
378
|
-
for (;;) {
|
|
379
|
-
const next = await line();
|
|
380
|
-
if (next === undefined || next.trim() === ENDS)
|
|
381
|
-
break;
|
|
382
|
-
held.push(next);
|
|
383
|
-
}
|
|
384
|
-
const written = held.join('\n').trim();
|
|
385
|
-
return written === '' ? undefined : written;
|
|
386
|
-
}
|
|
387
|
-
export function only(given, allowed, where) {
|
|
388
|
-
unasked(ownAnswers(given), allowed, where);
|
|
389
|
-
}
|
|
390
|
-
export function unasked(given, allowed, where) {
|
|
391
|
-
for (const one of given) {
|
|
392
|
-
if (CARRIES.includes(one.key) || one.key === WRITES)
|
|
393
|
-
continue;
|
|
394
|
-
if (allowed.includes(one.key))
|
|
395
|
-
continue;
|
|
396
|
-
refuse(`${locate(one)} answers \`${one.key}\`, and ${where} takes ${allowed.map((each) => `\`${each}\``).join(', ')}. ` +
|
|
397
|
-
'Nothing was written.');
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
export function answer(given, key) {
|
|
401
|
-
const found = given.filter((one) => one.key === key);
|
|
402
|
-
if (found.length > 1) {
|
|
403
|
-
refuse(`\`${key}\` is answered ${String(found.length)} times: ${found.map((one) => locate(one)).join(', ')}. ` +
|
|
404
|
-
'Nothing was written.');
|
|
405
|
-
}
|
|
406
|
-
const held = found[0]?.value;
|
|
407
|
-
return held === undefined ? undefined : guarded(key, held);
|
|
408
|
-
}
|
|
409
|
-
export function accepted(given) {
|
|
410
|
-
if (given.length === 0)
|
|
411
|
-
return 'nothing was accepted before it';
|
|
412
|
-
return `accepted so far: ${given.map((one) => `${one.key} (${locate(one)})`).join(', ')}`;
|
|
413
|
-
}
|
|
414
|
-
export function needed(given, key, where) {
|
|
415
|
-
const held = answer(given, key);
|
|
416
|
-
if (held === undefined || held === '') {
|
|
417
|
-
refuse(`${where} states no \`${key}\`, and it is not optional. ${accepted(given)}. Nothing was written.`);
|
|
418
|
-
}
|
|
419
|
-
return held;
|
|
420
|
-
}
|
|
421
|
-
export function wrapped(text, pad) {
|
|
422
|
-
const lines = [];
|
|
423
|
-
let held = '';
|
|
424
|
-
for (const word of text.split(/\s+/).filter((one) => one !== '')) {
|
|
425
|
-
if (held !== '' && `${held} ${word}`.length > 92) {
|
|
426
|
-
lines.push(`${pad}${held}`);
|
|
427
|
-
held = word;
|
|
428
|
-
}
|
|
429
|
-
else
|
|
430
|
-
held = held === '' ? word : `${held} ${word}`;
|
|
431
|
-
}
|
|
432
|
-
if (held !== '')
|
|
433
|
-
lines.push(`${pad}${held}`);
|
|
434
|
-
return lines;
|
|
435
|
-
}
|
|
436
|
-
export function marked(parts) {
|
|
437
|
-
return createHash('sha256').update(parts.join('\n')).digest('hex').slice(0, 6);
|
|
438
|
-
}
|
|
439
|
-
const citesOf = (held) => Array.isArray(held)
|
|
440
|
-
? held.filter((one) => one !== null && typeof one === 'object' && typeof one.on === 'string' && typeof one.said === 'string')
|
|
441
|
-
: [];
|
|
442
|
-
const NAMED_SHAPE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
443
|
-
const rowLike = (one) => one !== null && typeof one === 'object' && typeof one.id === 'string' && typeof one.describes === 'string';
|
|
444
|
-
const kinded = (shape) => shape
|
|
445
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
446
|
-
.toLowerCase();
|
|
447
|
-
const oneOf = (list) => {
|
|
448
|
-
const word = list.toLowerCase().split('_').join('-');
|
|
449
|
-
return word.endsWith('s') ? word.slice(0, -1) : word;
|
|
450
|
-
};
|
|
451
|
-
export async function declared(tree) {
|
|
452
|
-
const found = new Map();
|
|
453
|
-
const walked = new Set();
|
|
454
|
-
const says = new Map();
|
|
455
|
-
const parents = new Map();
|
|
456
|
-
const sheetsBy = new Map();
|
|
457
|
-
const meant = (shape, sheetAt) => {
|
|
458
|
-
if (shape === undefined)
|
|
459
|
-
return '';
|
|
460
|
-
try {
|
|
461
|
-
const named = shapesNamed();
|
|
462
|
-
for (const at of named.get(shape) ?? []) {
|
|
463
|
-
if (at === sheetAt)
|
|
464
|
-
continue;
|
|
465
|
-
let others = 0;
|
|
466
|
-
for (const [, files] of named)
|
|
467
|
-
if (files.includes(at))
|
|
468
|
-
others += 1;
|
|
469
|
-
if (others !== 1)
|
|
470
|
-
continue;
|
|
471
|
-
const told = says.get(at);
|
|
472
|
-
if (told !== undefined && told !== '')
|
|
473
|
-
return told;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
return '';
|
|
478
|
-
}
|
|
479
|
-
return '';
|
|
480
|
-
};
|
|
481
|
-
const sheet = (one) => {
|
|
482
|
-
const at = one?.at;
|
|
483
|
-
return typeof at === 'string' && at.startsWith(`${tree.name}/`) && at.endsWith('.ts') ? at : undefined;
|
|
484
|
-
};
|
|
485
|
-
const walk = async (at, top) => {
|
|
486
|
-
if (walked.has(at))
|
|
487
|
-
return;
|
|
488
|
-
walked.add(at);
|
|
489
|
-
let read;
|
|
490
|
-
try {
|
|
491
|
-
read = valuesIn(readFileSync(join(tree.at, at)));
|
|
492
|
-
}
|
|
493
|
-
catch {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
for (const [name, value] of Object.entries(read)) {
|
|
497
|
-
const node = value;
|
|
498
|
-
const lists = Array.isArray(value)
|
|
499
|
-
? [{ list: name, rows: value }]
|
|
500
|
-
: ['holds', 'declares'].flatMap((list) => {
|
|
501
|
-
const rows = node?.[list];
|
|
502
|
-
return Array.isArray(rows) ? [{ list, rows }] : [];
|
|
503
|
-
});
|
|
504
|
-
const rowed = lists.filter((one) => one.rows.every(rowLike));
|
|
505
|
-
const first = rowed[0];
|
|
506
|
-
if (first === undefined)
|
|
507
|
-
continue;
|
|
508
|
-
if (Array.isArray(value)) {
|
|
509
|
-
const noun = oneOf(name);
|
|
510
|
-
sheetsBy.set(name, new Set([...(sheetsBy.get(name) ?? []), at]));
|
|
511
|
-
if (!found.has(noun)) {
|
|
512
|
-
found.set(noun, {
|
|
513
|
-
at,
|
|
514
|
-
list: name,
|
|
515
|
-
shape: listShape(at, name),
|
|
516
|
-
fields: listFields(at, name),
|
|
517
|
-
describes: '',
|
|
518
|
-
holds: first.rows.length,
|
|
519
|
-
cites: [],
|
|
520
|
-
top,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
else if (at !== within(tree.at, tree.root)) {
|
|
525
|
-
const files = typeof node?.files === 'string' && node.files !== '' ? node.files : undefined;
|
|
526
|
-
const held = files === undefined ? first : (rowed.find((one) => one.list === 'declares') ?? first);
|
|
527
|
-
const kind = listShape(at, held.list);
|
|
528
|
-
const noun = kind === undefined || (kind === 'Threaded' && files === undefined)
|
|
529
|
-
? undefined
|
|
530
|
-
: kind !== 'Threaded' && NAMED_SHAPE.test(kind)
|
|
531
|
-
? kinded(kind)
|
|
532
|
-
: typeof node?.id === 'string' && node.id !== ''
|
|
533
|
-
? node.id
|
|
534
|
-
: undefined;
|
|
535
|
-
if (typeof noun === 'string' && noun !== '' && !found.has(noun)) {
|
|
536
|
-
found.set(noun, {
|
|
537
|
-
at,
|
|
538
|
-
list: held.list,
|
|
539
|
-
shape: kind,
|
|
540
|
-
fields: listFields(at, held.list),
|
|
541
|
-
describes: typeof node?.describes === 'string' ? node.describes : '',
|
|
542
|
-
holds: held.rows.length,
|
|
543
|
-
cites: citesOf(node?.cites),
|
|
544
|
-
top,
|
|
545
|
-
...(files === undefined ? {} : { files }),
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
for (const one of rowed.flatMap((each) => each.rows)) {
|
|
550
|
-
const where = one.at;
|
|
551
|
-
const told = one.describes;
|
|
552
|
-
if (typeof where === 'string' && typeof told === 'string' && !says.has(where))
|
|
553
|
-
says.set(where, told);
|
|
554
|
-
const next = sheet(one);
|
|
555
|
-
if (next === undefined)
|
|
556
|
-
continue;
|
|
557
|
-
const row = one;
|
|
558
|
-
if (!parents.has(next))
|
|
559
|
-
parents.set(next, row);
|
|
560
|
-
await walk(next, top ?? { id: typeof row.id === 'string' ? row.id : '', describes: typeof row.describes === 'string' ? row.describes : '' });
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
await walk(within(tree.at, tree.root), undefined);
|
|
565
|
-
const out = new Map();
|
|
566
|
-
for (const [noun, one] of found) {
|
|
567
|
-
const parent = parents.get(one.at);
|
|
568
|
-
const said = typeof parent?.describes === 'string' ? parent.describes : '';
|
|
569
|
-
const owned = (sheetsBy.get(one.list)?.size ?? 0) <= 1;
|
|
570
|
-
out.set(noun, {
|
|
571
|
-
...one,
|
|
572
|
-
describes: one.describes !== '' ? one.describes : owned && said !== '' ? said : meant(one.shape, one.at),
|
|
573
|
-
cites: one.cites.length > 0 ? one.cites : citesOf(parent?.cites),
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
return out;
|
|
577
|
-
}
|
|
578
|
-
export function nouns(all, verb = 'create') {
|
|
579
|
-
const found = new Map();
|
|
580
|
-
const opens = `thread ${verb} `;
|
|
581
|
-
for (const one of all) {
|
|
582
|
-
const by = one.by;
|
|
583
|
-
if (typeof by !== 'string' || !by.startsWith(opens))
|
|
584
|
-
continue;
|
|
585
|
-
const word = by.slice(opens.length).trim();
|
|
586
|
-
if (word === '' || typeof one.at !== 'string')
|
|
587
|
-
continue;
|
|
588
|
-
const already = found.get(word);
|
|
589
|
-
if (already !== undefined && already !== one.at) {
|
|
590
|
-
refuse(`two rows both state \`by: 'thread ${verb} ${word}'\` — ${already} and ${one.at} — so which one answers a ` +
|
|
591
|
-
`${word} is whichever was written first. One noun, one file.`);
|
|
592
|
-
}
|
|
593
|
-
found.set(word, one.at);
|
|
594
|
-
}
|
|
595
|
-
return found;
|
|
596
|
-
}
|
|
597
|
-
export function asking(held) {
|
|
598
|
-
const piped = answered();
|
|
599
|
-
const rl = piped ? undefined : createInterface({ input: stdin, output: stdout });
|
|
600
|
-
const lines = rl?.[Symbol.asyncIterator]();
|
|
601
|
-
return {
|
|
602
|
-
piped,
|
|
603
|
-
ask: async (key, question, from = held) => {
|
|
604
|
-
const already = answer(from, key);
|
|
605
|
-
if (already !== undefined)
|
|
606
|
-
return guarded(key, already);
|
|
607
|
-
if (piped)
|
|
608
|
-
return '';
|
|
609
|
-
stdout.write(question);
|
|
610
|
-
const next = await lines?.next();
|
|
611
|
-
return next === undefined || next.done === true ? '' : guarded(key, next.value.trim());
|
|
612
|
-
},
|
|
613
|
-
line: async () => {
|
|
614
|
-
const next = await lines?.next();
|
|
615
|
-
return next === undefined || next.done === true ? undefined : next.value;
|
|
616
|
-
},
|
|
617
|
-
shut: () => rl?.close(),
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
const ANSWERS = '--answers';
|
|
621
|
-
function fromFile() {
|
|
622
|
-
const at = argv.indexOf(ANSWERS);
|
|
623
|
-
if (at === -1)
|
|
624
|
-
return undefined;
|
|
625
|
-
const path = argv[at + 1];
|
|
626
|
-
if (path === undefined || path.startsWith('-')) {
|
|
627
|
-
refuse(`${ANSWERS} needs the path of a file holding the answers, one \`name: value\` to a line.`);
|
|
628
|
-
}
|
|
629
|
-
try {
|
|
630
|
-
return readFileSync(path, 'utf8');
|
|
631
|
-
}
|
|
632
|
-
catch {
|
|
633
|
-
refuse(`${ANSWERS} names ${path}, and there is no file there to read the answers from.`);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
const OWN = '--own';
|
|
637
|
-
const CHECK = '--check';
|
|
638
|
-
function flagged() {
|
|
639
|
-
const found = [];
|
|
640
|
-
for (let at = 2; at < argv.length; at += 1) {
|
|
641
|
-
const one = argv[at] ?? '';
|
|
642
|
-
if (!one.startsWith('--') ||
|
|
643
|
-
one === RECEIPT ||
|
|
644
|
-
one === ANSWERS ||
|
|
645
|
-
one === OWN ||
|
|
646
|
-
one === CHECK ||
|
|
647
|
-
one === FROM ||
|
|
648
|
-
one === CHAINED)
|
|
649
|
-
continue;
|
|
650
|
-
const value = argv[at + 1];
|
|
651
|
-
if (value === undefined || value.startsWith('-')) {
|
|
652
|
-
refuse(`\`${one}\` was given no value — an answer given as a flag is \`${one} value\`, with the value right after it.`);
|
|
653
|
-
}
|
|
654
|
-
found.push({ at: 0, key: one.slice(2), value });
|
|
655
|
-
at += 1;
|
|
656
|
-
}
|
|
657
|
-
return found;
|
|
658
|
-
}
|
|
659
|
-
export function answered() {
|
|
660
|
-
return argv.includes(ANSWERS) || stdin.isTTY !== true;
|
|
661
|
-
}
|
|
662
|
-
export function given() {
|
|
663
|
-
const flags = flagged();
|
|
664
|
-
const fromFlag = new Set(flags.map((one) => one.key));
|
|
665
|
-
const held = fromFile();
|
|
666
|
-
if (held !== undefined)
|
|
667
|
-
return read([...flags, ...parsed(held).filter((one) => !fromFlag.has(one.key))]);
|
|
668
|
-
const text = handed();
|
|
669
|
-
if (text === undefined)
|
|
670
|
-
return read(flags);
|
|
671
|
-
return read([...flags, ...parsed(text).filter((one) => !fromFlag.has(one.key))]);
|
|
672
|
-
}
|
|
673
|
-
const FROM_END = '-from';
|
|
674
|
-
function read(all) {
|
|
675
|
-
return all.map((one) => {
|
|
676
|
-
if (!one.key.endsWith(FROM_END))
|
|
677
|
-
return one;
|
|
678
|
-
const key = one.key.slice(0, -FROM_END.length);
|
|
679
|
-
if (key === '')
|
|
680
|
-
return one;
|
|
681
|
-
if (all.some((each) => each.key === key)) {
|
|
682
|
-
refuse(`\`${asFlag(key)}\` and \`${asFlag(one.key)}\` both answer ${key}, and they do not have to agree. ` +
|
|
683
|
-
'Give one of them. Nothing was written.');
|
|
684
|
-
}
|
|
685
|
-
try {
|
|
686
|
-
return refuse(`${asFlag(key)}${FROM_END} takes what this writes out of a file, and a file is not a receipt: nothing here checked it and no descent reached it. Give the text on the command with ${asFlag(key)}. Nothing was written.`);
|
|
687
|
-
}
|
|
688
|
-
catch {
|
|
689
|
-
refuse(`\`${asFlag(one.key)} ${one.value}\` names a file nothing could read, so there is no ${key} to write and ` +
|
|
690
|
-
`nothing was written. Write that file first, or give the text itself with ${asFlag(key)}.`);
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
let stood;
|
|
695
|
-
let taken = false;
|
|
696
|
-
export function handed() {
|
|
697
|
-
if (taken)
|
|
698
|
-
return stood;
|
|
699
|
-
taken = true;
|
|
700
|
-
if (stdin.isTTY === true)
|
|
701
|
-
return undefined;
|
|
702
|
-
try {
|
|
703
|
-
stood = readFileSync(0, 'utf8');
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
stood = undefined;
|
|
707
|
-
}
|
|
708
|
-
return stood;
|
|
709
|
-
}
|
|
710
|
-
export const TYPED = 'thread';
|
|
711
|
-
export const CARRIED = [
|
|
712
|
-
{ verb: 'read', typed: `${TYPED} read <row>`, does: 'prints every field a row states' },
|
|
713
|
-
{ verb: 'edit', typed: `${TYPED} edit <row> --<field> <value>`, does: 'changes any field it was declared with' },
|
|
714
|
-
{ verb: 'remove', typed: `${TYPED} edit <row> --remove`, does: 'takes the row out of the tree' },
|
|
715
|
-
];
|
|
716
|
-
const elsewhere = CARRIED.map((one) => `\`${one.typed}\` ${one.does}`);
|
|
717
|
-
export function wordsIn(offered) {
|
|
718
|
-
const values = new Set([argv[argv.indexOf(RECEIPT) + 1], argv[argv.indexOf(ANSWERS) + 1], ...flagged().map((one) => one.value)]);
|
|
719
|
-
const words = argv.slice(2).filter((one) => !one.startsWith('-') && !values.has(one));
|
|
720
|
-
const first = words[0];
|
|
721
|
-
if (offered.length === 0 || first === undefined || offered.includes(first))
|
|
722
|
-
return words;
|
|
723
|
-
const carried = CARRIED.find((one) => one.verb === first);
|
|
724
|
-
if (carried !== undefined) {
|
|
725
|
-
refuse(`\`${first}\` is not a word this command answers, because every row in this tree has it and it is ` +
|
|
726
|
-
`answered in one place rather than once per thing. \`${carried.typed}\` ${carried.does}, whatever ` +
|
|
727
|
-
'declared it, and it finds the row by its id or by the mark printed beside it. Nothing was written, ' +
|
|
728
|
-
'and nothing was read.');
|
|
729
|
-
}
|
|
730
|
-
refuse(`\`${first}\` is not one of the things this command does. It does: ${offered.join(', ')}. ` +
|
|
731
|
-
`Reading, changing and removing a row are done elsewhere, because they reach every row rather than ` +
|
|
732
|
-
`only this one's: ${elsewhere.join('; ')}. ` +
|
|
733
|
-
'Run it with none of those to see what already exists and exactly what each of them asks for. ' +
|
|
734
|
-
'Nothing was written, and nothing was read.');
|
|
735
|
-
}
|
|
736
|
-
const ANNOUNCED = [
|
|
737
|
-
{ what: 'a Stripe secret key', pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{16,}/ },
|
|
738
|
-
{ what: 'a Stripe restricted key', pattern: /\brk_(?:live|test)_[A-Za-z0-9]{16,}/ },
|
|
739
|
-
{ what: 'a GitHub token', pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}/ },
|
|
740
|
-
{ what: 'a GitHub personal access token', pattern: /\bgithub_pat_[A-Za-z0-9_]{30,}/ },
|
|
741
|
-
{ what: 'an AWS access key id', pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/ },
|
|
742
|
-
{ what: 'a Slack token', pattern: /\bxox[abposr]-[A-Za-z0-9-]{10,}/ },
|
|
743
|
-
{ what: 'an OpenAI key', pattern: /\bsk-[A-Za-z0-9_-]{32,}/ },
|
|
744
|
-
{ what: 'an Anthropic key', pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}/ },
|
|
745
|
-
{ what: 'a Google API key', pattern: /\bAIza[A-Za-z0-9_-]{35}\b/ },
|
|
746
|
-
{ what: 'a JSON web token', pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
|
|
747
|
-
{ what: 'a private key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/ },
|
|
748
|
-
{ what: 'a password inside a connection string', pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{6,}@/ },
|
|
749
|
-
];
|
|
750
|
-
const ASSIGNED = /\b(?:secret|password|passwd|api_?key|apikey|access_?token|auth_?token|private_?key|client_?secret|credential)s?\b\s*[:=]\s*(['"`])([^'"`\n]{16,})\1/gi;
|
|
751
|
-
const MIXED = /^(?=.*[a-z])(?=.*[A-Z0-9])[A-Za-z0-9_+/=.-]{16,}$/;
|
|
752
|
-
const NOT = [
|
|
753
|
-
/^(?:sec|pub)_[a-z0-9_]+$/,
|
|
754
|
-
/^\$\{/,
|
|
755
|
-
/^(?:x{3,}|X{3,}|\.{3,}|<[^>]+>|change[-_]?me|your[-_]|example|placeholder|redacted|todo|test|dummy|sample|fake|dGhpcyBpcyBub3QgYSBzZWNyZXQ)/i,
|
|
756
|
-
];
|
|
757
|
-
function guarded(key, value) {
|
|
758
|
-
const carried = credentialIn(value);
|
|
759
|
-
if (carried !== undefined) {
|
|
760
|
-
refuse(`\`${key}\` holds ${carried}. A credential is never typed into a declaration — not in a describes, not ` +
|
|
761
|
-
'anywhere but a secret. Declare it with `thread create secret`, name it where the thing that needs it ' +
|
|
762
|
-
'states what it requires, and it is resolved through the vault at the moment it is used and never ' +
|
|
763
|
-
'written down. Nothing was written.');
|
|
764
|
-
}
|
|
765
|
-
const address = ADDRESS.exec(value);
|
|
766
|
-
if (address !== null) {
|
|
767
|
-
refuse(`\`${key}\` holds the address \`${address[0]}\`. An address is a secret here, not a sentence — the same ` +
|
|
768
|
-
'thing behind another host is another environment, and one written into a declaration is one nothing ' +
|
|
769
|
-
'can point elsewhere. Declare it with `thread create secret` and name it where the thing that needs it ' +
|
|
770
|
-
'states what it requires. Nothing was written.');
|
|
771
|
-
}
|
|
772
|
-
return value;
|
|
773
|
-
}
|
|
774
|
-
const ADDRESS = /\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`)]+/i;
|
|
775
|
-
export function credentialIn(text) {
|
|
776
|
-
for (const one of ANNOUNCED) {
|
|
777
|
-
if (one.pattern.test(text))
|
|
778
|
-
return one.what;
|
|
779
|
-
}
|
|
780
|
-
for (const found of text.matchAll(ASSIGNED)) {
|
|
781
|
-
const value = found[2] ?? '';
|
|
782
|
-
if (NOT.some((each) => each.test(value)))
|
|
783
|
-
continue;
|
|
784
|
-
if (!MIXED.test(value))
|
|
785
|
-
continue;
|
|
786
|
-
return `a ${(found[0].split(/[:=]/)[0] ?? '').trim()} written down as a literal ${String(value.length)} characters long`;
|
|
787
|
-
}
|
|
788
|
-
return undefined;
|
|
789
|
-
}
|