@prestack/thread 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/dist/core/{asking.d.ts → answers.d.ts} +6 -14
- package/dist/core/{asking.js → answers.js} +13 -332
- package/dist/core/{asking.ts → answers.ts} +14 -357
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +2 -3
- package/dist/core/chain.js +4 -11
- package/dist/core/chain.ts +4 -3
- package/dist/core/client.d.ts +57 -0
- package/dist/core/client.js +186 -0
- package/dist/core/client.ts +227 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +109 -16
- package/dist/core/descend.ts +120 -16
- package/dist/core/files.d.ts +45 -0
- package/dist/core/files.js +95 -0
- package/dist/core/files.ts +137 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +10 -2
- package/dist/core/init.ts +8 -2
- package/dist/core/invoked.js +1 -1
- package/dist/core/invoked.ts +1 -1
- package/dist/core/rows.d.ts +2 -0
- package/dist/core/rows.js +65 -0
- package/dist/core/rows.ts +58 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/tree.ts +1 -1
- package/dist/derivation/attach.js +0 -12
- package/dist/derivation/attach.ts +0 -12
- package/dist/derivation/generators/build.js +8 -7
- package/dist/derivation/generators/build.ts +8 -7
- package/dist/derivation/generators/release.d.ts +1 -1
- package/dist/derivation/generators/release.js +15 -23
- package/dist/derivation/generators/release.ts +15 -15
- package/dist/integrations/attach.js +1 -19
- package/dist/integrations/attach.ts +1 -19
- package/dist/integrations/generators/read.js +2 -2
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +4 -12
- package/dist/integrations/generators/vault.ts +4 -4
- package/dist/integrations/module.js +5 -3
- package/dist/integrations/module.ts +5 -3
- package/dist/integrations/vault.js +5 -5
- package/dist/integrations/vault.ts +5 -5
- package/dist/integrity/attach.js +0 -12
- package/dist/integrity/attach.ts +0 -12
- package/dist/integrity/check-threads.js +1 -1
- package/dist/integrity/check-threads.ts +1 -1
- package/dist/integrity/constraint.d.ts +11 -2
- package/dist/integrity/constraint.js +163 -20
- package/dist/integrity/constraint.ts +174 -22
- package/dist/integrity/generators/compiler.js +1 -1
- package/dist/integrity/generators/compiler.ts +1 -1
- package/dist/integrity/generators/gate.js +8 -16
- package/dist/integrity/generators/gate.ts +8 -7
- package/dist/integrity/generators/publish.js +14 -26
- package/dist/integrity/generators/publish.ts +14 -17
- package/dist/integrity/generators/trial.d.ts +2 -2
- package/dist/integrity/generators/trial.js +108 -50
- package/dist/integrity/generators/trial.ts +107 -48
- package/dist/integrity/lint.js +3 -3
- package/dist/integrity/lint.ts +3 -3
- package/dist/integrity/lsp/diagnostics.js +1 -1
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +1 -1
- package/dist/integrity/lsp/lint.ts +1 -1
- package/dist/integrity/slopnator.d.ts +1 -14
- package/dist/integrity/slopnator.js +8 -40
- package/dist/integrity/slopnator.ts +9 -51
- package/dist/integrity/slopnators/sl_broken_lineage.js +4 -2
- package/dist/integrity/slopnators/sl_broken_lineage.ts +4 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +24 -3
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +24 -3
- package/dist/integrity/slopnators/sl_derived_drift.js +10 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +10 -4
- package/dist/integrity/slopnators/sl_inert_island.js +7 -13
- package/dist/integrity/slopnators/sl_inert_island.ts +7 -16
- package/dist/integrity/slopnators/sl_inert_slopnator.js +8 -8
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +8 -8
- package/dist/integrity/slopnators/sl_missing_row.js +14 -9
- package/dist/integrity/slopnators/sl_missing_row.ts +14 -10
- package/dist/integrity/slopnators/sl_no_child_process.js +8 -8
- package/dist/integrity/slopnators/sl_no_child_process.ts +8 -8
- package/dist/integrity/slopnators/sl_no_import_cycle.js +1 -1
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +1 -1
- package/dist/integrity/slopnators/sl_no_such_command.js +20 -5
- package/dist/integrity/slopnators/sl_no_such_command.ts +22 -5
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +8 -8
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +8 -8
- package/dist/integrity/slopnators/sl_stale_path.js +91 -18
- package/dist/integrity/slopnators/sl_stale_path.ts +97 -18
- package/dist/integrity/slopnators/sl_twice_meant.js +25 -2
- package/dist/integrity/slopnators/sl_twice_meant.ts +28 -2
- package/dist/integrity/slopnators/sl_twice_parented.js +26 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +26 -5
- package/dist/integrity/slopnators/sl_undeclared_node.js +18 -2
- package/dist/integrity/slopnators/sl_undeclared_node.ts +18 -2
- package/dist/integrity/slopnators/sl_undeclared_operation.js +3 -1
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +3 -1
- package/dist/integrity/slopnators/sl_undeclared_variant.js +6 -2
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +6 -2
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -4
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +3 -4
- package/dist/integrity/slopnators/sl_unkept_rows.js +1 -1
- package/dist/integrity/slopnators/sl_unkept_rows.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +13 -8
- package/dist/integrity/slopnators/sl_unpaired_member.ts +16 -8
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +16 -3
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +16 -3
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +1 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +18 -10
- package/dist/integrity/slopnators/sl_unthreaded.ts +19 -11
- package/dist/integrity/slopnators/sl_untranslated_key.js +1 -1
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -1
- package/dist/integrity/slopnators/sl_unversioned.js +16 -2
- package/dist/integrity/slopnators/sl_unversioned.ts +16 -2
- package/dist/integrity/sweep.js +17 -3
- package/dist/integrity/sweep.ts +20 -3
- package/dist/knowledge/attach.js +0 -6
- package/dist/knowledge/attach.ts +0 -6
- package/dist/knowledge/generators/docsgen.js +12 -3
- package/dist/knowledge/generators/docsgen.ts +12 -2
- package/dist/knowledge/generators/glossary.js +1 -1
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/attach.js +2 -26
- package/dist/operations/attach.ts +2 -27
- package/dist/operations/dispatch.js +2 -3
- package/dist/operations/dispatch.ts +2 -3
- package/dist/operations/generators/capability.js +4 -7
- package/dist/operations/generators/capability.ts +3 -6
- package/dist/operations/generators/compile.js +8 -13
- package/dist/operations/generators/compile.ts +8 -4
- package/dist/operations/generators/permits.js +4 -13
- package/dist/operations/generators/permits.ts +3 -3
- package/dist/operations/generators/seed.js +5 -2
- package/dist/operations/generators/seed.ts +6 -2
- package/dist/operations/generators/trial.js +10 -10
- package/dist/operations/generators/trial.ts +10 -10
- package/dist/operations/http.js +4 -1
- package/dist/operations/http.ts +6 -1
- package/package.json +2 -2
- package/dist/core/cli.d.ts +0 -2
- package/dist/core/cli.js +0 -1761
- package/dist/core/cli.ts +0 -2004
- package/dist/core/generators/meta.d.ts +0 -2
- package/dist/core/generators/meta.js +0 -829
- package/dist/core/generators/meta.ts +0 -1006
- package/dist/derivation/generators/derivation.d.ts +0 -3
- package/dist/derivation/generators/derivation.js +0 -273
- package/dist/derivation/generators/derivation.ts +0 -347
- package/dist/derivation/makers/release.d.ts +0 -1
- package/dist/derivation/makers/release.js +0 -107
- package/dist/derivation/makers/release.ts +0 -135
- package/dist/integrations/generators/secret.d.ts +0 -1
- package/dist/integrations/generators/secret.js +0 -233
- package/dist/integrations/generators/secret.ts +0 -289
- package/dist/integrations/generators/target.d.ts +0 -1
- package/dist/integrations/generators/target.js +0 -775
- package/dist/integrations/generators/target.ts +0 -944
- package/dist/integrity/generators/rule.d.ts +0 -2
- package/dist/integrity/generators/rule.js +0 -213
- package/dist/integrity/generators/rule.ts +0 -271
- package/dist/integrity/generators/seal.d.ts +0 -4
- package/dist/integrity/generators/seal.js +0 -101
- package/dist/integrity/generators/seal.ts +0 -117
- package/dist/integrity/sealed.d.ts +0 -1
- package/dist/integrity/sealed.js +0 -56
- package/dist/integrity/sealed.ts +0 -56
- package/dist/knowledge/generators/skills.d.ts +0 -1
- package/dist/knowledge/generators/skills.js +0 -222
- package/dist/knowledge/generators/skills.ts +0 -263
- package/dist/operations/generators/grant.d.ts +0 -1
- package/dist/operations/generators/grant.js +0 -267
- package/dist/operations/generators/grant.ts +0 -336
- package/dist/operations/generators/operation.d.ts +0 -1
- package/dist/operations/generators/operation.js +0 -811
- package/dist/operations/generators/operation.ts +0 -972
- package/dist/operations/makers/capability.d.ts +0 -1
- package/dist/operations/makers/capability.js +0 -117
- package/dist/operations/makers/capability.ts +0 -166
- package/dist/operations/makers/trial.d.ts +0 -1
- package/dist/operations/makers/trial.js +0 -413
- package/dist/operations/makers/trial.ts +0 -520
|
@@ -1,107 +0,0 @@
|
|
|
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, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { argv, exit, stdout } from 'node:process';
|
|
11
|
-
import { pathToFileURL } from 'node:url';
|
|
12
|
-
import { RECEIPT, answer, answering, asking, given, guidance, issue, named, only, prompted, refuse, slug, spend, wordsIn } from "../../core/asking.js";
|
|
13
|
-
import { listBounds, withRow } from "../../core/rows.js";
|
|
14
|
-
import { named as packageOf } from "../../core/scope.js";
|
|
15
|
-
import { findTree, inTree } from "../../core/tree.js";
|
|
16
|
-
import { CHECKED_BY, KINDS, PREFIXES, SAID, WRITES, WRITTEN_BY } from "../release.js";
|
|
17
|
-
const TREE = findTree();
|
|
18
|
-
const SHEET = 'derivation.ts';
|
|
19
|
-
const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
20
|
-
const NAME = { at: 'name', guide: 'lowercase words joined by dashes' };
|
|
21
|
-
const KIND = { at: 'kind', guide: `one of ${KINDS.join(', ')} — the prefix on the id says which` };
|
|
22
|
-
const ASKS = [NAME, KIND];
|
|
23
|
-
const at = inTree(TREE, SHEET);
|
|
24
|
-
if (!existsSync(at)) {
|
|
25
|
-
refuse(`there is no ${SHEET} here, so a release has nowhere to go. Run \`npx thread attach ${packageOf('derivation')}\` — ` +
|
|
26
|
-
"the sheet is the package's to write, not yours.");
|
|
27
|
-
}
|
|
28
|
-
const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
|
|
29
|
-
const already = held.DERIVATION?.derives ?? [];
|
|
30
|
-
const listing = [
|
|
31
|
-
already.length === 0
|
|
32
|
-
? 'no releases yet, so this app declares nowhere to go.'
|
|
33
|
-
: `${String(already.length)} release(s):`,
|
|
34
|
-
...already.map((one) => ` ${String(one.id)} — writes ${String(one.at)} — ${String(one.describes)}`),
|
|
35
|
-
'',
|
|
36
|
-
'what a release is:',
|
|
37
|
-
'',
|
|
38
|
-
...KINDS.map((one) => ` ${one} → ${PREFIXES[one]}<name>, writing ${WRITES[one].join(' and ')}`),
|
|
39
|
-
...KINDS.flatMap((one) => ['', ` ${SAID[one]}`]),
|
|
40
|
-
].join('\n');
|
|
41
|
-
const words = wordsIn(['new']);
|
|
42
|
-
const piped = given();
|
|
43
|
-
only(piped, named(ASKS), 'a release');
|
|
44
|
-
const { ask, shut } = asking(piped);
|
|
45
|
-
const nonce = issue(TREE.at, listing);
|
|
46
|
-
if (words[0] !== 'new') {
|
|
47
|
-
stdout.write(`${listing}\n\n`);
|
|
48
|
-
stdout.write(` npx thread create release new <name> ${RECEIPT} ${nonce}\n\n`);
|
|
49
|
-
stdout.write(`${answering('release').join('\n')}\n\n`);
|
|
50
|
-
stdout.write(`${guidance(ASKS).join('\n')}\n\n`);
|
|
51
|
-
stdout.write(` ${RECEIPT} is spent by one run and refused if ${SHEET} changed after it was issued — what is\n` +
|
|
52
|
-
' listed above is what you are writing against, and handing one back says you read it.\n');
|
|
53
|
-
exit(1);
|
|
54
|
-
}
|
|
55
|
-
spend(TREE.at, listing, argv[argv.indexOf(RECEIPT) + 1], SHEET);
|
|
56
|
-
const name = slug(piped.length > 0 ? (answer(piped, NAME.at) ?? '') : await ask(NAME.at, prompted(NAME)));
|
|
57
|
-
if (name === '' || !KEBAB.test(name)) {
|
|
58
|
-
shut();
|
|
59
|
-
refuse(`"${name}" cannot be the name of a release — it is written in kebab-case, so main or blue-green.`);
|
|
60
|
-
}
|
|
61
|
-
const said = piped.length > 0 ? (answer(piped, KIND.at) ?? '') : await ask(KIND.at, prompted(KIND));
|
|
62
|
-
const kind = KINDS.find((one) => one === said);
|
|
63
|
-
if (kind === undefined) {
|
|
64
|
-
shut();
|
|
65
|
-
refuse(`"${said}" is not something this can release to. It is one of ${KINDS.join(', ')}.`);
|
|
66
|
-
}
|
|
67
|
-
shut();
|
|
68
|
-
const id = `${PREFIXES[kind]}${name.replace(/-/g, '_')}`;
|
|
69
|
-
if (already.some((one) => one.id === id))
|
|
70
|
-
refuse(`\`${id}\` is already declared in ${SHEET}.`);
|
|
71
|
-
const text = readFileSync(at, 'utf8');
|
|
72
|
-
const bounds = listBounds(text, 'derives');
|
|
73
|
-
if (bounds === undefined) {
|
|
74
|
-
refuse(`${SHEET} states no \`derives\` list on its node, so this app was attached before releases existed. ` +
|
|
75
|
-
`Run \`npx thread attach ${packageOf('derivation')}\` again — it adds the list without touching what is in the sheet.`);
|
|
76
|
-
}
|
|
77
|
-
const [first, ...rest] = WRITES[kind];
|
|
78
|
-
const row = [
|
|
79
|
-
' {',
|
|
80
|
-
` id: '${id}',`,
|
|
81
|
-
` at: '${String(first)}',`,
|
|
82
|
-
` describes: '${SAID[kind].replace(/\\/g, '\\\\').replace(/'/g, "\\'")}',`,
|
|
83
|
-
' version: 1,',
|
|
84
|
-
` by: '${WRITTEN_BY}',`,
|
|
85
|
-
` runs: '${CHECKED_BY}',`,
|
|
86
|
-
...(rest.length === 0
|
|
87
|
-
? []
|
|
88
|
-
: [
|
|
89
|
-
' derives: [',
|
|
90
|
-
...rest.flatMap((one) => [
|
|
91
|
-
' {',
|
|
92
|
-
` id: '${id}_${one.split('.')[0] ?? one}',`,
|
|
93
|
-
` at: '${one}',`,
|
|
94
|
-
` describes: 'What runs the image this release builds, named beside it because one row states one address.',`,
|
|
95
|
-
' version: 1,',
|
|
96
|
-
` by: '${WRITTEN_BY}',`,
|
|
97
|
-
` runs: '${CHECKED_BY}',`,
|
|
98
|
-
' },',
|
|
99
|
-
]),
|
|
100
|
-
' ],',
|
|
101
|
-
]),
|
|
102
|
-
' },',
|
|
103
|
-
].join('\n');
|
|
104
|
-
writeFileSync(at, withRow(text, bounds, row), 'utf8');
|
|
105
|
-
stdout.write(`create: declared ${id} in ${SHEET} — it writes ${WRITES[kind].join(' and ')}.\n`);
|
|
106
|
-
stdout.write(`create: run \`${WRITTEN_BY}\`, and the gate checks it from here on without a line written for it.\n`);
|
|
107
|
-
exit(0);
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { argv, exit, stdout } from 'node:process'
|
|
3
|
-
import { pathToFileURL } from 'node:url'
|
|
4
|
-
|
|
5
|
-
import { RECEIPT, answer, answering, asking, given, guidance, issue, named, only, prompted, refuse, slug, spend, wordsIn, type Asked } from '../../core/asking.ts'
|
|
6
|
-
import { listBounds, withRow } from '../../core/rows.ts'
|
|
7
|
-
import { named as packageOf } from '../../core/scope.ts'
|
|
8
|
-
import { findTree, inTree } from '../../core/tree.ts'
|
|
9
|
-
|
|
10
|
-
import { CHECKED_BY, KINDS, PREFIXES, SAID, WRITES, WRITTEN_BY } from '../release.ts'
|
|
11
|
-
|
|
12
|
-
const TREE = findTree()
|
|
13
|
-
|
|
14
|
-
const SHEET = 'derivation.ts'
|
|
15
|
-
|
|
16
|
-
const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/
|
|
17
|
-
|
|
18
|
-
const NAME: Asked = { at: 'name', guide: 'lowercase words joined by dashes' }
|
|
19
|
-
|
|
20
|
-
const KIND: Asked = { at: 'kind', guide: `one of ${KINDS.join(', ')} — the prefix on the id says which` }
|
|
21
|
-
|
|
22
|
-
const ASKS: readonly Asked[] = [NAME, KIND]
|
|
23
|
-
|
|
24
|
-
interface Row {
|
|
25
|
-
readonly id?: string
|
|
26
|
-
readonly at?: string
|
|
27
|
-
readonly describes?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const at = inTree(TREE, SHEET)
|
|
31
|
-
|
|
32
|
-
if (!existsSync(at)) {
|
|
33
|
-
refuse(
|
|
34
|
-
`there is no ${SHEET} here, so a release has nowhere to go. Run \`npx thread attach ${packageOf('derivation')}\` — ` +
|
|
35
|
-
"the sheet is the package's to write, not yours.",
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const held = (await import(pathToFileURL(at).href)) as { DERIVATION?: { readonly derives?: readonly Row[] } }
|
|
40
|
-
const already = held.DERIVATION?.derives ?? []
|
|
41
|
-
|
|
42
|
-
const listing = [
|
|
43
|
-
already.length === 0
|
|
44
|
-
? 'no releases yet, so this app declares nowhere to go.'
|
|
45
|
-
: `${String(already.length)} release(s):`,
|
|
46
|
-
...already.map((one) => ` ${String(one.id)} — writes ${String(one.at)} — ${String(one.describes)}`),
|
|
47
|
-
'',
|
|
48
|
-
'what a release is:',
|
|
49
|
-
'',
|
|
50
|
-
...KINDS.map((one) => ` ${one} → ${PREFIXES[one]}<name>, writing ${WRITES[one].join(' and ')}`),
|
|
51
|
-
...KINDS.flatMap((one) => ['', ` ${SAID[one]}`]),
|
|
52
|
-
].join('\n')
|
|
53
|
-
|
|
54
|
-
const words = wordsIn(['new'])
|
|
55
|
-
|
|
56
|
-
const piped = given()
|
|
57
|
-
only(piped, named(ASKS), 'a release')
|
|
58
|
-
const { ask, shut } = asking(piped)
|
|
59
|
-
|
|
60
|
-
const nonce = issue(TREE.at, listing)
|
|
61
|
-
|
|
62
|
-
if (words[0] !== 'new') {
|
|
63
|
-
stdout.write(`${listing}\n\n`)
|
|
64
|
-
stdout.write(` npx thread create release new <name> ${RECEIPT} ${nonce}\n\n`)
|
|
65
|
-
stdout.write(`${answering('release').join('\n')}\n\n`)
|
|
66
|
-
stdout.write(`${guidance(ASKS).join('\n')}\n\n`)
|
|
67
|
-
stdout.write(
|
|
68
|
-
` ${RECEIPT} is spent by one run and refused if ${SHEET} changed after it was issued — what is\n` +
|
|
69
|
-
' listed above is what you are writing against, and handing one back says you read it.\n',
|
|
70
|
-
)
|
|
71
|
-
exit(1)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
spend(TREE.at, listing, argv[argv.indexOf(RECEIPT) + 1], SHEET)
|
|
75
|
-
|
|
76
|
-
const name = slug(piped.length > 0 ? (answer(piped, NAME.at) ?? '') : await ask(NAME.at, prompted(NAME)))
|
|
77
|
-
if (name === '' || !KEBAB.test(name)) {
|
|
78
|
-
shut()
|
|
79
|
-
refuse(`"${name}" cannot be the name of a release — it is written in kebab-case, so main or blue-green.`)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const said = piped.length > 0 ? (answer(piped, KIND.at) ?? '') : await ask(KIND.at, prompted(KIND))
|
|
83
|
-
const kind = KINDS.find((one) => one === said)
|
|
84
|
-
if (kind === undefined) {
|
|
85
|
-
shut()
|
|
86
|
-
refuse(`"${said}" is not something this can release to. It is one of ${KINDS.join(', ')}.`)
|
|
87
|
-
}
|
|
88
|
-
shut()
|
|
89
|
-
|
|
90
|
-
const id = `${PREFIXES[kind]}${name.replace(/-/g, '_')}`
|
|
91
|
-
if (already.some((one) => one.id === id)) refuse(`\`${id}\` is already declared in ${SHEET}.`)
|
|
92
|
-
|
|
93
|
-
const text = readFileSync(at, 'utf8')
|
|
94
|
-
const bounds = listBounds(text, 'derives')
|
|
95
|
-
if (bounds === undefined) {
|
|
96
|
-
refuse(
|
|
97
|
-
`${SHEET} states no \`derives\` list on its node, so this app was attached before releases existed. ` +
|
|
98
|
-
`Run \`npx thread attach ${packageOf('derivation')}\` again — it adds the list without touching what is in the sheet.`,
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const [first, ...rest] = WRITES[kind]
|
|
103
|
-
|
|
104
|
-
const row = [
|
|
105
|
-
' {',
|
|
106
|
-
` id: '${id}',`,
|
|
107
|
-
` at: '${String(first)}',`,
|
|
108
|
-
` describes: '${SAID[kind].replace(/\\/g, '\\\\').replace(/'/g, "\\'")}',`,
|
|
109
|
-
' version: 1,',
|
|
110
|
-
` by: '${WRITTEN_BY}',`,
|
|
111
|
-
` runs: '${CHECKED_BY}',`,
|
|
112
|
-
...(rest.length === 0
|
|
113
|
-
? []
|
|
114
|
-
: [
|
|
115
|
-
' derives: [',
|
|
116
|
-
...rest.flatMap((one) => [
|
|
117
|
-
' {',
|
|
118
|
-
` id: '${id}_${one.split('.')[0] ?? one}',`,
|
|
119
|
-
` at: '${one}',`,
|
|
120
|
-
` describes: 'What runs the image this release builds, named beside it because one row states one address.',`,
|
|
121
|
-
' version: 1,',
|
|
122
|
-
` by: '${WRITTEN_BY}',`,
|
|
123
|
-
` runs: '${CHECKED_BY}',`,
|
|
124
|
-
' },',
|
|
125
|
-
]),
|
|
126
|
-
' ],',
|
|
127
|
-
]),
|
|
128
|
-
' },',
|
|
129
|
-
].join('\n')
|
|
130
|
-
|
|
131
|
-
writeFileSync(at, withRow(text, bounds, row), 'utf8')
|
|
132
|
-
|
|
133
|
-
stdout.write(`create: declared ${id} in ${SHEET} — it writes ${WRITES[kind].join(' and ')}.\n`)
|
|
134
|
-
stdout.write(`create: run \`${WRITTEN_BY}\`, and the gate checks it from here on without a line written for it.\n`)
|
|
135
|
-
exit(0)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,233 +0,0 @@
|
|
|
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 { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
-
import { join, relative } from 'node:path';
|
|
12
|
-
import { argv, stdin, stdout } from 'node:process';
|
|
13
|
-
import { createInterface } from 'node:readline';
|
|
14
|
-
import { exit } from "../../core/invoked.js";
|
|
15
|
-
import { ELSEWHERE, RECEIPT, answer, asking, asksOf, given, issue, needed, only, ownAnswers, refuse, rowLines, seeding, slug, spend, stating, wordsIn } from "../../core/asking.js";
|
|
16
|
-
import { holdsRow, listBounds, withRow } from "../../core/rows.js";
|
|
17
|
-
import { named as packageOf } from "../../core/scope.js";
|
|
18
|
-
import { findTree, inTree } from "../../core/tree.js";
|
|
19
|
-
import { SAID, SECRECY, secrecyOf } from "../secret.js";
|
|
20
|
-
import { IGNORES, KEY_AT, SEALED_AT, keyOf, seal, sealedIn } from "../vault.js";
|
|
21
|
-
const TREE = findTree();
|
|
22
|
-
const FILE = 'secrets.ts';
|
|
23
|
-
const LIST = 'SECRETS';
|
|
24
|
-
const NAME = /^[a-z][a-z0-9_]*$/;
|
|
25
|
-
const ENVS = ['sandbox', 'production'];
|
|
26
|
-
const WORDS = Object.keys(SECRECY);
|
|
27
|
-
const ASKS = [
|
|
28
|
-
{ key: 'name', says: ['lower_snake_case, and the prefix is added for you'] },
|
|
29
|
-
{ key: 'holder', says: [`who may hold it — ${WORDS.join(', ')}`] },
|
|
30
|
-
{ key: 'describes', says: ['what it is for, in one sentence'], edits: true },
|
|
31
|
-
{ key: 'header', says: ['blank if this value IS the address of what requires it'], edits: true },
|
|
32
|
-
{ key: 'prefix', says: ['what precedes it in that header, trailing space and all'], edits: true },
|
|
33
|
-
];
|
|
34
|
-
const TAKES = ASKS.map((one) => one.key);
|
|
35
|
-
const EDITS = ASKS.filter((one) => one.edits === true).map((one) => one.key);
|
|
36
|
-
const ASKED = ASKS.flatMap((one) => one.says.map((line, at) => ` ${at === 0 ? `${one.key}:`.padEnd(32) : ' '.repeat(32)}${line}`));
|
|
37
|
-
const here = (one) => relative(TREE.at, one).split('\\').join('/');
|
|
38
|
-
const path = inTree(TREE, FILE);
|
|
39
|
-
const at = here(path);
|
|
40
|
-
import { quoted } from "../../core/rows.js";
|
|
41
|
-
let secrets = [];
|
|
42
|
-
try {
|
|
43
|
-
const taken = (await import(__rewriteRelativeImportExtension(new URL(`file://${path}`).href)));
|
|
44
|
-
secrets = taken.SECRETS ?? [];
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
refuse(`${at} could not be read. Attach \`${packageOf('integrations')}\` first — an app states its secrets there.`);
|
|
48
|
-
}
|
|
49
|
-
const held = new Map(ENVS.map((env) => [env, new Set(sealedIn(TREE.at, env))]));
|
|
50
|
-
const setIn = (id) => {
|
|
51
|
-
const found = ENVS.filter((env) => held.get(env)?.has(id) === true);
|
|
52
|
-
return found.length === 0 ? 'no value anywhere yet' : `set for ${found.join(' and ')}`;
|
|
53
|
-
};
|
|
54
|
-
async function sealing(id) {
|
|
55
|
-
const hidden = async (question) => {
|
|
56
|
-
stdout.write(question);
|
|
57
|
-
const rl = createInterface({ input: stdin, output: stdout, terminal: true });
|
|
58
|
-
rl._writeToOutput = () => { };
|
|
59
|
-
const value = await new Promise((done) => rl.question('', done));
|
|
60
|
-
rl.close();
|
|
61
|
-
stdout.write('\n');
|
|
62
|
-
return value;
|
|
63
|
-
};
|
|
64
|
-
let sealed = 0;
|
|
65
|
-
for (const env of ENVS) {
|
|
66
|
-
const value = await hidden(` ${id} for ${env} (paste it, or press enter to skip): `);
|
|
67
|
-
if (value.trim() === '')
|
|
68
|
-
continue;
|
|
69
|
-
seal(TREE.at, id, env, value);
|
|
70
|
-
sealed += 1;
|
|
71
|
-
}
|
|
72
|
-
if (sealed === 0)
|
|
73
|
-
return 0;
|
|
74
|
-
const ignore = join(TREE.at, '.gitignore');
|
|
75
|
-
const already = existsSync(ignore) ? readFileSync(ignore, 'utf8') : '';
|
|
76
|
-
const missing = IGNORES.filter((one) => !already.split('\n').some((line) => line.trim() === one));
|
|
77
|
-
if (missing.length > 0) {
|
|
78
|
-
appendFileSync(ignore, `${already.endsWith('\n') || already === '' ? '' : '\n'}${missing.join('\n')}\n`);
|
|
79
|
-
stdout.write(`secret: added ${missing.join(', ')} to .gitignore — the key is never committed.\n`);
|
|
80
|
-
}
|
|
81
|
-
stdout.write(`secret: sealed ${id} for ${String(sealed)} environment(s) into ${SEALED_AT}.\n`);
|
|
82
|
-
return sealed;
|
|
83
|
-
}
|
|
84
|
-
const listing = secrets.length === 0
|
|
85
|
-
? 'no secrets yet.'
|
|
86
|
-
: [
|
|
87
|
-
`${String(secrets.length)} secret(s):`,
|
|
88
|
-
...secrets.flatMap((one) => [
|
|
89
|
-
` ${one.id} — ${secrecyOf(one.id) ?? 'wearing no prefix, which is refused'}, ${setIn(one.id)}`,
|
|
90
|
-
` ${one.sends === undefined ? 'no `sends`, so it is an address' : `sent as ${one.sends.header}: ${one.sends.prefix ?? ''}<value>`}`,
|
|
91
|
-
]),
|
|
92
|
-
].join('\n');
|
|
93
|
-
const words = wordsIn(['new', 'set']);
|
|
94
|
-
const verb = words[0];
|
|
95
|
-
stdout.write(`${listing}\n`);
|
|
96
|
-
if (keyOf(TREE.at) === undefined && secrets.length > 0) {
|
|
97
|
-
stdout.write(`\nthere is no vault key at ${KEY_AT}, so nothing above can be opened here. It is never committed — ` +
|
|
98
|
-
'whoever set this app up has it.\n');
|
|
99
|
-
}
|
|
100
|
-
const method = (nonce) => [
|
|
101
|
-
'',
|
|
102
|
-
'what this makes:',
|
|
103
|
-
'',
|
|
104
|
-
` thread create secret new <name> ${RECEIPT} ${nonce}`,
|
|
105
|
-
` thread create secret set <id> ${RECEIPT} ${nonce}`,
|
|
106
|
-
'',
|
|
107
|
-
...ELSEWHERE,
|
|
108
|
-
'',
|
|
109
|
-
` ${RECEIPT} is spent by one run and refused if ${at} moved after it was issued.`,
|
|
110
|
-
'',
|
|
111
|
-
'who may hold a value. The word is what you send; the prefix is what the id wears, and the prefix',
|
|
112
|
-
'IS who may hold it — no field says so:',
|
|
113
|
-
'',
|
|
114
|
-
...WORDS.flatMap((one) => [` ${one.padEnd(7)} → ${SECRECY[one]}<name>`, ` ${SAID[one]}`, '']),
|
|
115
|
-
'`new` asks, in this order:',
|
|
116
|
-
'',
|
|
117
|
-
...ASKED,
|
|
118
|
-
'',
|
|
119
|
-
'and then, at a terminal only, it asks for the value itself — echo off, sealed as you press enter.',
|
|
120
|
-
'A piped run declares the row and says so instead, because there is no terminal to read one from.',
|
|
121
|
-
'',
|
|
122
|
-
'`edit` asks the same three, and a blank answer keeps what is already there — except that naming',
|
|
123
|
-
'a DIFFERENT header drops the old prefix unless you give a new one, because a prefix belongs to',
|
|
124
|
-
'the scheme its header names. `-` as the header removes the header and makes this an address.',
|
|
125
|
-
'',
|
|
126
|
-
'It will not change who may hold a value. That is the prefix, the prefix is the id, and an id that',
|
|
127
|
-
'changes is a different secret — every target requiring the old name would go on requiring a name',
|
|
128
|
-
'nothing declares. Declare the other one and move what requires it.',
|
|
129
|
-
'',
|
|
130
|
-
'`set` is the same prompt on its own, for changing a value later or giving one a `new` run skipped.',
|
|
131
|
-
'It takes no answers and cannot be piped. The value goes from the keyboard into the sealed store and',
|
|
132
|
-
`nowhere else — not into a row, not into this output, not into ${SEALED_AT} in any readable form.`,
|
|
133
|
-
'',
|
|
134
|
-
'So an agent may declare a secret and may never give it one. That is the point of the split.',
|
|
135
|
-
].join('\n');
|
|
136
|
-
const flagged = argv.indexOf(RECEIPT);
|
|
137
|
-
if (verb === undefined && flagged === -1) {
|
|
138
|
-
const nonce = issue(TREE.at, listing);
|
|
139
|
-
stdout.write(`${method(nonce)}\n`);
|
|
140
|
-
stdout.write(`\nsecret: receipt ${nonce} — spent on use, and stale the moment ${at} changes.\n`);
|
|
141
|
-
exit(0);
|
|
142
|
-
}
|
|
143
|
-
spend(TREE.at, listing, argv[flagged + 1], at);
|
|
144
|
-
if (verb !== 'new' && verb !== 'set' && verb !== 'edit') {
|
|
145
|
-
refuse(`there is nothing called "${verb ?? ''}" to do here — say new, edit or set`);
|
|
146
|
-
}
|
|
147
|
-
const text = readFileSync(path, 'utf8');
|
|
148
|
-
const where = listBounds(text, LIST);
|
|
149
|
-
if (where === undefined)
|
|
150
|
-
refuse(`${at} states no \`${LIST}\` list to declare a secret in`);
|
|
151
|
-
if (verb === 'set') {
|
|
152
|
-
const id = words[1] ?? refuse('`set` takes the id of a secret this app already declares');
|
|
153
|
-
const one = secrets.find((each) => each.id === id);
|
|
154
|
-
if (one === undefined) {
|
|
155
|
-
refuse(`\`${id}\` is not a secret this app declares. Declare it first with \`thread create secret new\` — a value is ` +
|
|
156
|
-
'given to something that said it needed it, or not at all.');
|
|
157
|
-
}
|
|
158
|
-
if (stdin.isTTY !== true) {
|
|
159
|
-
refuse('a value is typed by a person at a terminal, and this run is piped. Nothing was written. Run ' +
|
|
160
|
-
`\`thread create secret set ${id}\` yourself — an agent may declare a secret and may never give it one.`);
|
|
161
|
-
}
|
|
162
|
-
if ((await sealing(id)) === 0) {
|
|
163
|
-
stdout.write('secret: nothing typed, so nothing was sealed.\n');
|
|
164
|
-
exit(0);
|
|
165
|
-
}
|
|
166
|
-
const kept = spawnSync('npx', ['thread', 'vault'], { cwd: TREE.at, encoding: 'utf8' });
|
|
167
|
-
stdout.write(`${`${kept.stdout ?? ''}${kept.stderr ?? ''}`.trim()}\n`);
|
|
168
|
-
exit(kept.status === 0 ? 0 : 1);
|
|
169
|
-
}
|
|
170
|
-
const piped = given();
|
|
171
|
-
only(piped, TAKES, 'a secret');
|
|
172
|
-
const { ask, line, shut } = asking(piped);
|
|
173
|
-
const own = ownAnswers(piped);
|
|
174
|
-
const name = slug(words[1] ?? (await ask('name', 'name: ')));
|
|
175
|
-
if (name === '') {
|
|
176
|
-
shut();
|
|
177
|
-
refuse('a secret has to be called something');
|
|
178
|
-
}
|
|
179
|
-
if (!NAME.test(name)) {
|
|
180
|
-
shut();
|
|
181
|
-
refuse(`"${name}" cannot be the name of a secret — it is written in lower_snake_case`);
|
|
182
|
-
}
|
|
183
|
-
const holder = (piped.length > 0 ? needed(own, 'holder', 'a secret') : await ask('holder', `who may hold it — ${WORDS.join(', ')}: `)).trim();
|
|
184
|
-
if (!WORDS.includes(holder)) {
|
|
185
|
-
shut();
|
|
186
|
-
refuse(`there is no such holder as "${holder}". A secret is held by a ${WORDS.join(' or a ')}, and that prefix is the ` +
|
|
187
|
-
'only thing standing between a server credential and a browser bundle.');
|
|
188
|
-
}
|
|
189
|
-
const word = holder;
|
|
190
|
-
const id = `${SECRECY[word]}${name}`;
|
|
191
|
-
if (secrets.some((one) => one.id === id) || holdsRow(text, where, id)) {
|
|
192
|
-
shut();
|
|
193
|
-
refuse(`\`${id}\` is already declared in ${at}`);
|
|
194
|
-
}
|
|
195
|
-
const describes = (piped.length > 0 ? needed(own, 'describes', 'a secret') : await ask('describes', 'what is it for? ')).trim();
|
|
196
|
-
if (describes === '') {
|
|
197
|
-
shut();
|
|
198
|
-
refuse('a secret says what it is for — the sentence is what somebody reads before requiring it');
|
|
199
|
-
}
|
|
200
|
-
const header = (piped.length > 0 ? (answer(own, 'header') ?? '') : await ask('header', 'header (blank if this value IS an address): ')).trim();
|
|
201
|
-
const typed = header === '' ? '' : (piped.length > 0 ? (answer(own, 'prefix') ?? '') : await ask('prefix', 'prefix in that header, e.g. "Bearer ": '));
|
|
202
|
-
const { stated, kids } = await stating(asksOf('secret', 'Secret', {}, ['id', 'at', 'describes', 'version', 'sends']), piped, piped.length > 0, ask, line, []);
|
|
203
|
-
shut();
|
|
204
|
-
const prefix = typed === '' || typed.endsWith(' ') ? typed : `${typed} `;
|
|
205
|
-
const row = [
|
|
206
|
-
' {',
|
|
207
|
-
` id: ${quoted(id)},`,
|
|
208
|
-
` at: '_threads/${FILE}',`,
|
|
209
|
-
` describes: ${quoted(describes)},`,
|
|
210
|
-
' version: 1,',
|
|
211
|
-
...(header === ''
|
|
212
|
-
? []
|
|
213
|
-
: [` sends: { header: ${quoted(header.toLowerCase())}${prefix === '' ? '' : `, prefix: ${quoted(prefix)}`} },`]),
|
|
214
|
-
...rowLines(' ', stated, kids),
|
|
215
|
-
' },',
|
|
216
|
-
].join('\n');
|
|
217
|
-
writeFileSync(path, withRow(text, where, row), 'utf8');
|
|
218
|
-
for (const told of seeding(kids, TREE.at))
|
|
219
|
-
stdout.write(`secret: ${told}\n`);
|
|
220
|
-
stdout.write(`secret: declared ${id} in ${at}${header === '' ? ' — it has no `sends`, so it is the address of whatever requires it' : ''}.\n`);
|
|
221
|
-
if (stdin.isTTY === true) {
|
|
222
|
-
stdout.write(`\n${SAID[word]}\n\nThe value is not echoed and is sealed straight into ${SEALED_AT}.\n`);
|
|
223
|
-
if ((await sealing(id)) === 0) {
|
|
224
|
-
stdout.write(`secret: nothing typed, so ${id} has no value yet. Run \`thread create secret set ${id}\` when you have it.\n`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
stdout.write(`secret: it has no value anywhere yet, and this run is piped — a value is typed by a person at a terminal. ` +
|
|
229
|
-
`Run \`thread create secret set ${id}\` yourself.\n`);
|
|
230
|
-
}
|
|
231
|
-
const kept = spawnSync('npx', ['thread', 'vault'], { cwd: TREE.at, encoding: 'utf8' });
|
|
232
|
-
stdout.write(`${`${kept.stdout ?? ''}${kept.stderr ?? ''}`.trim()}\n`);
|
|
233
|
-
exit(kept.status === 0 ? 0 : 1);
|