@prestack/thread-ui 0.0.2 → 0.1.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.
@@ -1,15 +1,7 @@
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, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync } from "@prestack/thread/core/files";
2
+ import { valuesIn } from "@prestack/thread/core/rows";
10
3
  import { join, resolve } from 'node:path';
11
4
  import { argv, stdout } from 'node:process';
12
- import { pathToFileURL } from 'node:url';
13
5
  import { exit, invoked } from "@prestack/thread/core/invoked";
14
6
  import { named as packageOf } from "@prestack/thread/core/scope";
15
7
  import { findTree, inTree } from "@prestack/thread/core/tree";
@@ -25,7 +17,7 @@ function pascal(id) {
25
17
  .join('');
26
18
  }
27
19
  async function node(file, name) {
28
- const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(inTree(findTree(), file)).href)));
20
+ const held = valuesIn(readFileSync(inTree(findTree(), file)));
29
21
  const one = held[name];
30
22
  if (one === undefined)
31
23
  return {};
@@ -37,7 +29,7 @@ async function fileFor(root, at, id) {
37
29
  if (!existsSync(path) || !LOADABLE.test(path))
38
30
  return undefined;
39
31
  try {
40
- const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
32
+ const held = valuesIn(readFileSync(path));
41
33
  return held[exported(id)];
42
34
  }
43
35
  catch (wrong) {
@@ -1,4 +1,5 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs'
1
+ import { existsSync, readFileSync, readdirSync } from '@prestack/thread/core/files'
2
+ import { valuesIn } from '@prestack/thread/core/rows'
2
3
  import { join, resolve } from 'node:path'
3
4
  import { argv, stdout } from 'node:process'
4
5
  import { pathToFileURL } from 'node:url'
@@ -39,7 +40,7 @@ function pascal(id: string): string {
39
40
  }
40
41
 
41
42
  async function node(file: string, name: string): Promise<Node> {
42
- const held = (await import(pathToFileURL(inTree(findTree(), file)).href)) as Record<string, unknown>
43
+ const held = valuesIn(readFileSync(inTree(findTree(), file))) as Record<string, unknown>
43
44
  const one = held[name]
44
45
  if (one === undefined) return {}
45
46
  return one as Node
@@ -51,7 +52,7 @@ async function fileFor(root: string, at: string, id: string): Promise<Record<str
51
52
  const path = resolve(root, at)
52
53
  if (!existsSync(path) || !LOADABLE.test(path)) return undefined
53
54
  try {
54
- const held = (await import(pathToFileURL(path).href)) as Record<string, unknown>
55
+ const held = valuesIn(readFileSync(path)) as Record<string, unknown>
55
56
  return held[exported(id)] as Record<string, unknown> | undefined
56
57
  } catch (wrong) {
57
58
 
@@ -6,8 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
- import { dirname, join, resolve } from 'node:path';
9
+ import { existsSync, readFileSync, writeFileSync } from "@prestack/thread/core/files";
10
+ import { join, resolve } from 'node:path';
11
11
  import { argv, stderr, stdout } from 'node:process';
12
12
  import { fileURLToPath, pathToFileURL } from 'node:url';
13
13
  import { exit } from "@prestack/thread/core/invoked";
@@ -488,7 +488,6 @@ async function run() {
488
488
  'believes the wrong one. Run `thread styles` and commit what it writes.\n');
489
489
  return 1;
490
490
  }
491
- mkdirSync(dirname(at), { recursive: true });
492
491
  writeFileSync(at, made, 'utf8');
493
492
  writeFileSync(join(root, 'app/_generated/declared.ts'), wearingSource, 'utf8');
494
493
  stdout.write(`thread: wrote app/_generated/tokens.css — ${String(Object.keys(tokens).length)} tokens, ` +
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/core/files'
2
2
  import { dirname, join, resolve } from 'node:path'
3
3
  import { argv, stderr, stdout } from 'node:process'
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url'
@@ -653,7 +653,6 @@ async function run(): Promise<number> {
653
653
  return 1
654
654
  }
655
655
 
656
- mkdirSync(dirname(at), { recursive: true })
657
656
  writeFileSync(at, made, 'utf8')
658
657
  writeFileSync(join(root, 'app/_generated/declared.ts'), wearingSource, 'utf8')
659
658
 
@@ -6,8 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
- import { basename, dirname, join, resolve } from 'node:path';
9
+ import { existsSync, readFileSync, writeFileSync } from "@prestack/thread/core/files";
10
+ import { basename, join, resolve } from 'node:path';
11
11
  import { argv, stdout } from 'node:process';
12
12
  import { fileURLToPath, pathToFileURL } from 'node:url';
13
13
  import { exit, invoked } from "@prestack/thread/core/invoked";
@@ -117,7 +117,6 @@ async function run() {
117
117
  }
118
118
  else {
119
119
  for (const seed of made) {
120
- mkdirSync(dirname(join(root, seed.path)), { recursive: true });
121
120
  writeFileSync(join(root, seed.path), seed.content, 'utf8');
122
121
  stdout.write(`thread: seeded ${seed.path}\n`);
123
122
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from '@prestack/thread/core/files'
2
2
  import { basename, dirname, join, resolve } from 'node:path'
3
3
  import { argv, stdout } from 'node:process'
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url'
@@ -154,7 +154,6 @@ async function run(): Promise<number> {
154
154
  }
155
155
  } else {
156
156
  for (const seed of made) {
157
- mkdirSync(dirname(join(root, seed.path)), { recursive: true })
158
157
  writeFileSync(join(root, seed.path), seed.content, 'utf8')
159
158
  stdout.write(`thread: seeded ${seed.path}\n`)
160
159
  }
@@ -6,8 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
- import { basename, dirname, join } from 'node:path';
9
+ import { existsSync, readFileSync, writeFileSync } from "@prestack/thread/core/files";
10
+ import { basename, join } from 'node:path';
11
11
  import { argv, stdout } from 'node:process';
12
12
  import { pathToFileURL } from 'node:url';
13
13
  import { exit, invoked } from "@prestack/thread/core/invoked";
@@ -130,7 +130,6 @@ async function run() {
130
130
  }
131
131
  else {
132
132
  for (const one of made) {
133
- mkdirSync(dirname(join(tree.at, one.path)), { recursive: true });
134
133
  writeFileSync(join(tree.at, one.path), one.content, 'utf8');
135
134
  stdout.write(`thread: seeded ${one.path}\n`);
136
135
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from '@prestack/thread/core/files'
2
2
  import { basename, dirname, join } from 'node:path'
3
3
  import { argv, stdout } from 'node:process'
4
4
  import { pathToFileURL } from 'node:url'
@@ -155,7 +155,6 @@ async function run(): Promise<number> {
155
155
  }
156
156
  } else {
157
157
  for (const one of made) {
158
- mkdirSync(dirname(join(tree.at, one.path)), { recursive: true })
159
158
  writeFileSync(join(tree.at, one.path), one.content, 'utf8')
160
159
  stdout.write(`thread: seeded ${one.path}\n`)
161
160
  }
@@ -1,16 +1,8 @@
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, writeFileSync } from 'node:fs';
10
- import { basename, dirname, join } from 'node:path';
1
+ import { existsSync, readFileSync, writeFileSync } from "@prestack/thread/core/files";
2
+ import { basename, join } from 'node:path';
11
3
  import { argv, stdout } from 'node:process';
12
- import { pathToFileURL } from 'node:url';
13
4
  import { exit, invoked } from "@prestack/thread/core/invoked";
5
+ import { valuesIn } from "@prestack/thread/core/rows";
14
6
  import { findTree, inTree } from "@prestack/thread/core/tree";
15
7
  import { HOME, settled, undeclared } from "./compositions.js";
16
8
  import { PRESENTATIONS } from "./door.js";
@@ -81,14 +73,14 @@ export const ${name} = screen('${row.id}', {}, () => (
81
73
  export function seeds(rows, held) {
82
74
  return rows.filter((row) => !held(row.at)).map((row) => ({ path: row.at, content: starter(row) }));
83
75
  }
84
- async function run() {
76
+ function run() {
85
77
  const tree = findTree();
86
78
  const at = inTree(tree, 'screens.ts');
87
79
  if (!existsSync(at)) {
88
80
  stdout.write(`thread: this tree has no ${tree.name}/${basename(at)}, so it declares no screen and there is nothing to seed or check.\n`);
89
81
  return 0;
90
82
  }
91
- const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
83
+ const held = valuesIn(readFileSync(at));
92
84
  if (held.SCREEN === undefined) {
93
85
  stdout.write(`thread: ${tree.name}/screens.ts exports no \`SCREEN\`, so nothing declares a screen here.\n`);
94
86
  return 1;
@@ -104,12 +96,11 @@ async function run() {
104
96
  }
105
97
  if (!checking) {
106
98
  for (const one of made) {
107
- mkdirSync(dirname(join(tree.at, one.path)), { recursive: true });
108
99
  writeFileSync(join(tree.at, one.path), one.content, 'utf8');
109
100
  stdout.write(`thread: seeded ${one.path}\n`);
110
101
  }
111
102
  }
112
- const loose = [...(await undeclared(tree, rows)), ...settled(tree.at, rows, checking, 'thread screens', new Set())];
103
+ const loose = [...undeclared(tree, rows), ...settled(tree.at, rows, checking, 'thread screens', new Set())];
113
104
  for (const one of loose)
114
105
  stdout.write(`thread: ${one}\n`);
115
106
  stdout.write(`thread: ${String(rows.length)} screen(s) declared, ${String(made.length)} seeded, ` +
@@ -118,4 +109,4 @@ async function run() {
118
109
  return loose.length === 0 ? 0 : 1;
119
110
  }
120
111
  if (invoked(import.meta.url))
121
- exit(await run());
112
+ exit(run());
@@ -1,9 +1,9 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/core/files'
2
2
  import { basename, dirname, join } from 'node:path'
3
3
  import { argv, stdout } from 'node:process'
4
- import { pathToFileURL } from 'node:url'
5
4
 
6
5
  import { exit, invoked } from '@prestack/thread/core/invoked'
6
+ import { valuesIn } from '@prestack/thread/core/rows'
7
7
  import type { Threaded } from '@prestack/thread/core/thread'
8
8
  import { findTree, inTree } from '@prestack/thread/core/tree'
9
9
 
@@ -93,14 +93,14 @@ export function seeds(rows: readonly Threaded[], held: (at: string) => boolean):
93
93
  return rows.filter((row) => !held(row.at)).map((row) => ({ path: row.at, content: starter(row) }))
94
94
  }
95
95
 
96
- async function run(): Promise<number> {
96
+ function run(): number {
97
97
  const tree = findTree()
98
98
  const at = inTree(tree, 'screens.ts')
99
99
  if (!existsSync(at)) {
100
100
  stdout.write(`thread: this tree has no ${tree.name}/${basename(at)}, so it declares no screen and there is nothing to seed or check.\n`)
101
101
  return 0
102
102
  }
103
- const held = (await import(pathToFileURL(at).href)) as { SCREEN?: { readonly declares?: readonly Threaded[] } }
103
+ const held = valuesIn(readFileSync(at)) as { SCREEN?: { readonly declares?: readonly Threaded[] } }
104
104
 
105
105
  if (held.SCREEN === undefined) {
106
106
  stdout.write(`thread: ${tree.name}/screens.ts exports no \`SCREEN\`, so nothing declares a screen here.\n`)
@@ -122,13 +122,12 @@ async function run(): Promise<number> {
122
122
 
123
123
  if (!checking) {
124
124
  for (const one of made) {
125
- mkdirSync(dirname(join(tree.at, one.path)), { recursive: true })
126
125
  writeFileSync(join(tree.at, one.path), one.content, 'utf8')
127
126
  stdout.write(`thread: seeded ${one.path}\n`)
128
127
  }
129
128
  }
130
129
 
131
- const loose = [...(await undeclared(tree, rows)), ...settled(tree.at, rows, checking, 'thread screens', new Set<string>())]
130
+ const loose = [...undeclared(tree, rows), ...settled(tree.at, rows, checking, 'thread screens', new Set<string>())]
132
131
  for (const one of loose) stdout.write(`thread: ${one}\n`)
133
132
 
134
133
  stdout.write(
@@ -139,4 +138,4 @@ async function run(): Promise<number> {
139
138
  return loose.length === 0 ? 0 : 1
140
139
  }
141
140
 
142
- if (invoked(import.meta.url)) exit(await run())
141
+ if (invoked(import.meta.url)) exit(run())
package/dist/ui/tokens.js CHANGED
@@ -6,7 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
9
+ import { existsSync, readdirSync, writeFileSync } from "@prestack/thread/core/files";
10
10
  import { basename, dirname, join } from 'node:path';
11
11
  import { argv, stdout } from 'node:process';
12
12
  import { pathToFileURL } from 'node:url';
@@ -169,7 +169,6 @@ async function run() {
169
169
  }
170
170
  else {
171
171
  for (const seed of made) {
172
- mkdirSync(dirname(join(tree.at, seed.path)), { recursive: true });
173
172
  writeFileSync(join(tree.at, seed.path), seed.content, 'utf8');
174
173
  stdout.write(`thread: seeded ${seed.path}\n`);
175
174
  }
package/dist/ui/tokens.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readdirSync, writeFileSync } from '@prestack/thread/core/files'
2
2
  import { basename, dirname, join } from 'node:path'
3
3
  import { argv, stdout } from 'node:process'
4
4
  import { pathToFileURL } from 'node:url'
@@ -198,7 +198,6 @@ async function run(): Promise<number> {
198
198
  }
199
199
  } else {
200
200
  for (const seed of made) {
201
- mkdirSync(dirname(join(tree.at, seed.path)), { recursive: true })
202
201
  writeFileSync(join(tree.at, seed.path), seed.content, 'utf8')
203
202
  stdout.write(`thread: seeded ${seed.path}\n`)
204
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prestack/thread-ui",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -17,6 +17,6 @@
17
17
  "react-dom": "^19.0.0",
18
18
  "typescript": "^6.0.0",
19
19
  "zod": "^4.5.4",
20
- "@prestack/thread": "^0.0.3"
20
+ "@prestack/thread": "^0.1.3"
21
21
  }
22
22
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,230 +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 { readFileSync, writeFileSync } from 'node:fs';
10
- import { join } from 'node:path';
11
- import { argv, exit, stdout } from 'node:process';
12
- import { pathToFileURL } from 'node:url';
13
- import { ELSEWHERE, RECEIPT, answer, answering, asking, given, guidance, issue, marked, named, needed, only, prompted, refuse, spend, within, wordsIn, wrapped } from "@prestack/thread/core/asking";
14
- import { named as packageOf } from "@prestack/thread/core/scope";
15
- import { findTree, inTree } from "@prestack/thread/core/tree";
16
- import { keyProps, keysIn, readWording, walk, wordingFile } from "../generators/i18n.js";
17
- import { fileFor, languagesIn, sourceIn } from "../localizations.js";
18
- const TREE = findTree();
19
- const DOTTED = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
20
- const KEY = { at: 'key', guide: 'lowercase words joined by dots — `checkout.confirm`, `errors.db.not_found.title`' };
21
- async function languages() {
22
- const path = inTree(TREE, `localizations.${'ts'}`);
23
- let sheet = {};
24
- try {
25
- sheet = (await import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
26
- }
27
- catch {
28
- sheet = {};
29
- }
30
- if (sheet.LOCALIZATION === undefined) {
31
- refuse(`there is no ${within(TREE.at, path)} declaring a \`LOCALIZATION\` here, so this app is in no ` +
32
- `language and a message has nowhere to go. Run \`npx thread attach ${packageOf('ui')}\`.`);
33
- }
34
- const rows = [...(sheet.LOCALIZATION.holds ?? []), ...(sheet.LOCALIZATION.declares ?? [])];
35
- const held = languagesIn(rows);
36
- const source = sourceIn(rows);
37
- if (held.length === 0 || source === undefined) {
38
- refuse('no language is declared, so there is nothing to write a message in. Add one with `npx thread create language`.');
39
- }
40
- return { held, source };
41
- }
42
- function usage(props) {
43
- const found = new Map();
44
- for (const file of walk(TREE.at)) {
45
- const text = readFileSync(file, 'utf8');
46
- for (const key of new Set(keysIn(text, props))) {
47
- const held = found.get(key) ?? [];
48
- held.push(within(TREE.at, file));
49
- found.set(key, held);
50
- }
51
- }
52
- return found;
53
- }
54
- const mark = (key, said) => marked([key, said]);
55
- function write(at, language, held) {
56
- writeFileSync(join(at, fileFor(language)), wordingFile(held), 'utf8');
57
- }
58
- async function run() {
59
- const { held: spoken, source } = await languages();
60
- const props = keyProps(TREE.at);
61
- if (props.length === 0) {
62
- refuse(`nothing in the installed \`${packageOf('ui')}\` declares a prop of type \`MessageKey\`, so no key could be ` +
63
- 'recognised anywhere. That is a broken install rather than an app with no wording.');
64
- }
65
- const LANGUAGES = spoken.map((one) => ({ at: one.id, guide: `what it says in ${one.name}` }));
66
- const NEW = [KEY, ...LANGUAGES];
67
- const wording = new Map();
68
- for (const one of spoken)
69
- wording.set(one.id, { ...readWording(TREE.at, one.id) });
70
- const written = wording.get(source) ?? {};
71
- const keys = Object.keys(written).sort();
72
- const used = usage(props);
73
- const blank = (key) => spoken.filter((one) => ((wording.get(one.id) ?? {})[key] ?? '') === '').map((one) => one.id);
74
- const listing = [
75
- `${String(keys.length)} message(s), in ${spoken.map((one) => one.id).join(', ')} — written in ${source}:`,
76
- '',
77
- ...keys.map((key) => {
78
- const missing = blank(key);
79
- const said = (written[key] ?? '') || '(nothing yet)';
80
- return (` [${mark(key, written[key] ?? '')}] ${key.padEnd(38)} ${said.length > 46 ? `${said.slice(0, 45)}…` : said}` +
81
- `${missing.length === 0 ? '' : ` · still to write: ${missing.join(', ')}`}` +
82
- `${(used.get(key) ?? []).length === 0 ? ' · named by nothing here' : ''}`);
83
- }),
84
- ].join('\n');
85
- const words = wordsIn(['new']);
86
- const verb = words[0];
87
- const flagged = argv.indexOf(RECEIPT);
88
- if (flagged === -1) {
89
- stdout.write(`${listing}\n`);
90
- const nonce = issue(TREE.at, listing);
91
- stdout.write([
92
- '',
93
- ...answering('message'),
94
- '',
95
- ...guidance(NEW),
96
- '',
97
- ...wrapped('Every language is asked for and none may be left blank. A key that exists in one language and ' +
98
- 'not another is what makes a screen come out half translated, and it is refused here rather ' +
99
- 'than found by whoever is reading the app in the other one.', ' '),
100
- '',
101
- ` npx thread create message new ${RECEIPT} ${nonce}`,
102
- '',
103
- ...ELSEWHERE,
104
- '',
105
- ...wrapped('A rename moves the wording in every language and rewrites every file in the interface that ' +
106
- 'names it, in one run.', ' '),
107
- '',
108
- ].join('\n'));
109
- stdout.write(`create: receipt ${nonce} — spent on use, and stale the moment the wording changes.\n`);
110
- return 0;
111
- }
112
- spend(TREE.at, listing, argv[flagged + 1], `the wording`);
113
- const all = given();
114
- const { piped, ask, shut } = asking(all);
115
- const ASKS = {
116
- new: ['do', ...named(NEW)],
117
- edit: ['do', 'mark', ...named(NEW)],
118
- remove: ['do', 'mark'],
119
- };
120
- const doing = verb ?? (await ask('do', 'new, edit or remove: '));
121
- const allowed = ASKS[doing];
122
- if (allowed === undefined) {
123
- shut();
124
- refuse(`there is nothing called "${doing}" to do here — say new, edit, remove, or read, which needs no receipt`);
125
- }
126
- if (piped)
127
- only(all, allowed, `\`${doing}\``);
128
- if (doing === 'remove' || doing === 'edit') {
129
- const which = words[1] ?? (piped ? needed(all, 'mark', `\`${doing}\``) : await ask('mark', 'mark: '));
130
- const one = keys.find((key) => mark(key, written[key] ?? '') === which);
131
- if (one === undefined) {
132
- shut();
133
- refuse(`\`${which}\` is not a mark this sheet issued — they are printed in brackets beside each message.`);
134
- }
135
- if (doing === 'remove') {
136
- const where = used.get(one) ?? [];
137
- if (where.length > 0) {
138
- shut();
139
- refuse(`\`${one}\` is named by ${where.slice(0, 6).join(', ')}` +
140
- `${where.length > 6 ? ` and ${String(where.length - 6)} more` : ''}. ` +
141
- 'Removing it would leave those asking for wording that does not exist, which throws the moment ' +
142
- 'the screen renders. Take it out of the interface first.');
143
- }
144
- shut();
145
- for (const language of spoken) {
146
- const made = { ...(wording.get(language.id) ?? {}) };
147
- delete made[one];
148
- write(TREE.at, language.id, made);
149
- }
150
- stdout.write(`create: removed ${one} from ${String(spoken.length)} language file(s)\n`);
151
- return 0;
152
- }
153
- const renamed = piped ? (answer(all, KEY.at) ?? '') : await ask(KEY.at, `key (${one}, blank to keep) `);
154
- if (renamed !== '' && !DOTTED.test(renamed)) {
155
- shut();
156
- refuse(`\`${renamed}\` is not a key — lowercase words joined by dots.`);
157
- }
158
- if (renamed !== '' && written[renamed] !== undefined) {
159
- shut();
160
- refuse(`\`${renamed}\` is already a message. Two keys with one name is one key, and the other's wording is lost.`);
161
- }
162
- const said = new Map();
163
- for (const language of spoken) {
164
- const now = (wording.get(language.id) ?? {})[one] ?? '';
165
- const next = piped
166
- ? (answer(all, language.id) ?? '')
167
- : await ask(language.id, `${language.name} (${now || 'nothing yet'}) `);
168
- said.set(language.id, next === '' ? now : next);
169
- }
170
- shut();
171
- const name = renamed === '' ? one : renamed;
172
- for (const language of spoken) {
173
- const made = { ...(wording.get(language.id) ?? {}) };
174
- delete made[one];
175
- made[name] = said.get(language.id) ?? '';
176
- write(TREE.at, language.id, made);
177
- }
178
- let moved = 0;
179
- if (renamed !== '') {
180
- for (const file of walk(TREE.at)) {
181
- const text = readFileSync(file, 'utf8');
182
- if (!keysIn(text, props).includes(one))
183
- continue;
184
- writeFileSync(file, text.replace(new RegExp(`(\\b(?:${props.join('|')})\\s*=\\s*")${one.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`, 'g'), `$1${name}$2`), 'utf8');
185
- moved += 1;
186
- }
187
- }
188
- stdout.write(`create: ${one}${renamed === '' ? '' : ` is now ${name}`} — rewritten in ${String(spoken.length)} language file(s)` +
189
- `${moved === 0 ? '' : ` and ${String(moved)} file(s) of the interface`}\n`);
190
- const still = blankIn(said, spoken);
191
- if (still.length > 0) {
192
- stdout.write(`create: still nothing in ${still.join(', ')} — the gate refuses while that holds.\n`);
193
- }
194
- return 0;
195
- }
196
- const key = piped ? needed(all, KEY.at, '`new`') : await ask(KEY.at, prompted(KEY));
197
- if (!DOTTED.test(key)) {
198
- shut();
199
- refuse(`\`${key}\` is not a key — lowercase words joined by dots, like \`checkout.confirm\`.`);
200
- }
201
- if (written[key] !== undefined) {
202
- shut();
203
- refuse(`\`${key}\` is already a message. Edit it by its mark, or choose another key.`);
204
- }
205
- const said = new Map();
206
- for (const language of spoken) {
207
- const next = piped
208
- ? (answer(all, language.id) ?? '')
209
- : await ask(language.id, `what it says in ${language.name}: `);
210
- if (next === '') {
211
- shut();
212
- refuse(`\`${key}\` has nothing to say in ${language.name}. Every language is answered at the moment a key ` +
213
- 'is made, because a key that exists in one language and not another is a screen that comes out ' +
214
- 'half translated. There is no way past this on purpose.');
215
- }
216
- said.set(language.id, next);
217
- }
218
- shut();
219
- for (const language of spoken) {
220
- write(TREE.at, language.id, { ...(wording.get(language.id) ?? {}), [key]: said.get(language.id) ?? '' });
221
- }
222
- stdout.write(`create: wrote ${key} in ${spoken.map((one) => one.id).join(', ')}\n`);
223
- stdout.write(`create: name it in the interface with one of: ${props.join(', ')} — for example \`label="${key}"\`.\n`);
224
- return 0;
225
- }
226
- function blankIn(said, spoken) {
227
- return spoken.filter((one) => (said.get(one.id) ?? '') === '').map((one) => one.id);
228
- }
229
- if (argv[1] !== undefined)
230
- exit(await run());