prosebook 0.2.0 → 0.3.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.
Files changed (112) hide show
  1. package/README.md +274 -29
  2. package/dist/bun.d.ts +34 -0
  3. package/dist/bun.d.ts.map +1 -0
  4. package/dist/bun.js +98 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/bundle-DboItAre.js +111 -0
  7. package/dist/bundle-DboItAre.js.map +1 -0
  8. package/dist/bundle-KD-DiiFa.d.ts +6 -0
  9. package/dist/bundle-KD-DiiFa.d.ts.map +1 -0
  10. package/dist/cli.js +51 -56
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compile-xF3F4oIp.d.ts +16 -0
  13. package/dist/compile-xF3F4oIp.d.ts.map +1 -0
  14. package/dist/{config-D--RITk2.js → config-BPSmoYel.js} +36 -212
  15. package/dist/config-BPSmoYel.js.map +1 -0
  16. package/dist/{config-_4HfnJQu.d.ts → config-DdMkKt_N.d.ts} +61 -2
  17. package/dist/config-DdMkKt_N.d.ts.map +1 -0
  18. package/dist/config.d.ts +2 -2
  19. package/dist/config.js +1 -1
  20. package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
  21. package/dist/copy-B3V_QRkT.d.ts.map +1 -0
  22. package/dist/copy-Bo_FIoLy.js +154 -0
  23. package/dist/copy-Bo_FIoLy.js.map +1 -0
  24. package/dist/endpoints-CnUfCkIk.js +96 -0
  25. package/dist/endpoints-CnUfCkIk.js.map +1 -0
  26. package/dist/generate-9pR3i5Bj.js +5173 -0
  27. package/dist/generate-9pR3i5Bj.js.map +1 -0
  28. package/dist/helpers-BWGcD0kS.js +96 -0
  29. package/dist/helpers-BWGcD0kS.js.map +1 -0
  30. package/dist/icu-Dej0mNsF.js +218 -0
  31. package/dist/icu-Dej0mNsF.js.map +1 -0
  32. package/dist/index.d.ts +12 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -188
  35. package/dist/inspector.d.ts +4 -1
  36. package/dist/inspector.d.ts.map +1 -1
  37. package/dist/inspector.js +7 -2
  38. package/dist/inspector.js.map +1 -1
  39. package/dist/locale-DpQA1pqH.js +70 -0
  40. package/dist/locale-DpQA1pqH.js.map +1 -0
  41. package/dist/next-loader.d.ts +11 -0
  42. package/dist/next-loader.d.ts.map +1 -0
  43. package/dist/next-loader.js +17 -0
  44. package/dist/next-loader.js.map +1 -0
  45. package/dist/next.d.ts +30 -0
  46. package/dist/next.d.ts.map +1 -0
  47. package/dist/next.js +160 -0
  48. package/dist/next.js.map +1 -0
  49. package/dist/react/index.d.ts +3 -3
  50. package/dist/react/index.d.ts.map +1 -1
  51. package/dist/react/index.js.map +1 -1
  52. package/dist/react/server.d.ts +20 -0
  53. package/dist/react/server.d.ts.map +1 -0
  54. package/dist/react/server.js +34 -0
  55. package/dist/react/server.js.map +1 -0
  56. package/dist/{sarif-CI8HOokm.js → sarif-CWyTAQ0y.js} +103 -87
  57. package/dist/sarif-CWyTAQ0y.js.map +1 -0
  58. package/dist/scan-Bx0_yDSi.js +387 -0
  59. package/dist/scan-Bx0_yDSi.js.map +1 -0
  60. package/dist/tools.d.ts +83 -90
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +5 -4
  63. package/dist/vite.d.ts +3 -2
  64. package/dist/vite.d.ts.map +1 -1
  65. package/dist/vite.js +29 -109
  66. package/dist/vite.js.map +1 -1
  67. package/package.json +37 -4
  68. package/src/bun.ts +132 -0
  69. package/src/cli.ts +60 -58
  70. package/src/index.ts +1 -1
  71. package/src/inspector.ts +8 -3
  72. package/src/next-loader.ts +16 -0
  73. package/src/next.ts +188 -0
  74. package/src/react/index.tsx +6 -6
  75. package/src/react/server.ts +37 -0
  76. package/src/runtime/copy.ts +22 -3
  77. package/src/runtime/helpers.ts +54 -18
  78. package/src/runtime/locale.ts +19 -0
  79. package/src/runtime/types.ts +21 -6
  80. package/src/tools/bundle.ts +131 -0
  81. package/src/tools/catalog.ts +2 -2
  82. package/src/tools/compile.ts +60 -23
  83. package/src/tools/config.ts +39 -0
  84. package/src/tools/endpoints.ts +121 -0
  85. package/src/tools/entry.ts +77 -0
  86. package/src/tools/generate.ts +2 -0
  87. package/src/tools/icu.ts +63 -7
  88. package/src/tools/index.ts +6 -3
  89. package/src/tools/lock.ts +7 -0
  90. package/src/tools/mf1.ts +21 -7
  91. package/src/tools/mf2.ts +249 -57
  92. package/src/tools/migrate.ts +3 -2
  93. package/src/tools/native/emit.ts +87 -0
  94. package/src/tools/native/index.ts +36 -0
  95. package/src/tools/native/kotlin.ts +269 -0
  96. package/src/tools/native/model.ts +280 -0
  97. package/src/tools/native/number.ts +210 -0
  98. package/src/tools/native/plurals.ts +103 -0
  99. package/src/tools/native/rust.ts +367 -0
  100. package/src/tools/native/swift.ts +326 -0
  101. package/src/tools/syntax.ts +3 -3
  102. package/src/tools/todo.ts +118 -0
  103. package/src/vite.ts +28 -114
  104. package/dist/config-D--RITk2.js.map +0 -1
  105. package/dist/config-_4HfnJQu.d.ts.map +0 -1
  106. package/dist/copy-DXnzXsJa.d.ts.map +0 -1
  107. package/dist/helpers-BPuPGjQP.js +0 -61
  108. package/dist/helpers-BPuPGjQP.js.map +0 -1
  109. package/dist/index.js.map +0 -1
  110. package/dist/sarif-CI8HOokm.js.map +0 -1
  111. package/dist/scan-Ch2BfIUD.js +0 -2561
  112. package/dist/scan-Ch2BfIUD.js.map +0 -1
@@ -0,0 +1,111 @@
1
+ import { c as unseenReads, d as localeModule, i as keyRefs, l as compileLocale, r as isReferenced, s as sourceFiles, t as READERS } from "./scan-Bx0_yDSi.js";
2
+ import { relative } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { existsSync, readFileSync, watch } from "node:fs";
5
+ // src/tools/bundle.ts
6
+ /**
7
+ * What the bundler integrations share: which messages a build ships, the reads that would show a key
8
+ * once their message is pruned, and the code a dev server appends so an edit swaps messages in place.
9
+ *
10
+ * Vite sees every module before it writes a chunk, so it prunes after the fact (`vite.ts`). Next's
11
+ * loaders and Bun's `onLoad` hand over a locale module before the code that reads it has been seen,
12
+ * so they read the source under `scan.dirs` first (`scanSources`) and load the locale already pruned.
13
+ */
14
+ /** Whether a build keeps a key: some module names it, or `keep` lists it, exactly or as `prefix.*`. */
15
+ function keeper(refs, keep = []) {
16
+ return (key) => isReferenced(key, refs) || keep.some((k) => k.endsWith(".*") ? key.startsWith(k.slice(0, -1)) : k === key);
17
+ }
18
+ /** The unseen reads that are a copy's: a name only counts once some module binds a reader to it. */
19
+ function unseenOf(reads) {
20
+ const all = [...reads];
21
+ const readers = /* @__PURE__ */ new Set([...READERS, ...all.flatMap((r) => [...r.aliases])]);
22
+ return all.flatMap((r) => r.unseen).filter((u) => u.name === "Text" || readers.has(u.name));
23
+ }
24
+ /** The error a strict build fails with, each read at its path from `root`. */
25
+ function unseenMessage(unseen, root) {
26
+ const at = (file) => relative(root, file).replaceAll("\\", "/");
27
+ return [
28
+ `${unseen.length} read(s) whose key the build can't see, so their messages would be pruned and the key shown instead:`,
29
+ ...unseen.map((u) => ` ${at(u.file)}:${u.line}:${u.column} ${u.code}`),
30
+ "Write each key at the read, as a literal or `area.${id}`; or, where every key it reads is written elsewhere or listed in `keep`, mark the line `prosebook-ignore`. `prune: \"loose\"` turns this check off."
31
+ ].join("\n");
32
+ }
33
+ /**
34
+ * Every key the app's source names and, with `strict`, its unseen reads. The generated modules are
35
+ * left out, since they name every key.
36
+ */
37
+ function scanSources(config, strict) {
38
+ const refs = {
39
+ exact: /* @__PURE__ */ new Set(),
40
+ prefixes: /* @__PURE__ */ new Set()
41
+ };
42
+ const reads = [];
43
+ for (const file of sourceFiles(config.scan.dirs, config.scan.extensions, [config.out])) {
44
+ const code = readFileSync(file, "utf8");
45
+ keyRefs(code, refs);
46
+ if (strict) reads.push(unseenReads(file, code));
47
+ }
48
+ return {
49
+ refs,
50
+ unseen: unseenOf(reads)
51
+ };
52
+ }
53
+ /** Calls `change` once a burst of edits to the catalog or its translations settles. Returns the stop. */
54
+ function watchCatalog(config, change) {
55
+ let timer;
56
+ const watchers = [config.catalog, config.locales].filter((dir) => existsSync(dir)).map((dir) => watch(dir, { recursive: true }, (_event, file) => {
57
+ if (file === null || !file.endsWith(".yaml")) return;
58
+ clearTimeout(timer);
59
+ timer = setTimeout(change, 50);
60
+ }));
61
+ return () => {
62
+ clearTimeout(timer);
63
+ for (const w of watchers) w.close();
64
+ };
65
+ }
66
+ /** A locale's module holding only the keys `kept`, as JavaScript, for a bundler to load in its place. */
67
+ function prunedModule(entries, kept) {
68
+ return localeModule(compileLocale(entries, kept), "// Pruned by prosebook: only the messages the code names.", false);
69
+ }
70
+ /**
71
+ * Appended to a locale module in dev. Accepts its own update and hands the new messages to every
72
+ * copy (`HotHook`); a page loaded before the hooks existed reloads instead.
73
+ */
74
+ const hotLocale = (locale) => `
75
+ if (import.meta.hot) {
76
+ import.meta.hot.accept((next) => {
77
+ const hot = globalThis.__PROSEBOOK_HOT__;
78
+ if (next === undefined || hot === undefined) return import.meta.hot.invalidate();
79
+ for (const update of hot) update(${JSON.stringify(locale)}, next.default);
80
+ });
81
+ }
82
+ `;
83
+ /** Appended to the index in dev: its doc comments change with every edit; only a new locale reloads. */
84
+ const HOT_INDEX = `
85
+ if (import.meta.hot) {
86
+ import.meta.hot.accept((next) => {
87
+ if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();
88
+ });
89
+ }
90
+ `;
91
+ /**
92
+ * A locale module in dev under a bundler that runs it again when it changes, as Turbopack's Fast
93
+ * Refresh does, but has no `import.meta.hot`: each run hands its messages to every copy. The first
94
+ * run finds no copy holding the locale yet, so it changes nothing.
95
+ */
96
+ const notifyingLocale = (code, locale) => `${code.replace(/^export default /m, "const messages = ")}
97
+ export default messages;
98
+ globalThis.__PROSEBOOK_HOT__?.forEach((update) => update(${JSON.stringify(locale)}, messages));
99
+ `;
100
+ /** Prepended to the index in dev, so the set every copy adds itself to exists before any copy does. */
101
+ const HOT_SET = "globalThis.__PROSEBOOK_HOT__ ??= new Set();\n";
102
+ /**
103
+ * A module of prosebook's own beside the one at `url`, which passes its `import.meta.url`: the source
104
+ * when run from source, the built file in dist.
105
+ */
106
+ const beside = (url, name) => fileURLToPath(new URL(`./${name}${url.endsWith(".ts") ? ".ts" : ".js"}`, url)).replaceAll("\\", "/");
107
+ /** Code that starts the inspector (`inspector.ts`, imported from `path`) on the page, its endpoints under `base`. */
108
+ const installInspector = (path, base) => `import { install as installInspector } from ${JSON.stringify(path)};\ninstallInspector(${JSON.stringify({ base })});\n`;
109
+ export { installInspector as a, prunedModule as c, unseenOf as d, watchCatalog as f, hotLocale as i, scanSources as l, HOT_SET as n, keeper as o, beside as r, notifyingLocale as s, HOT_INDEX as t, unseenMessage as u };
110
+
111
+ //# sourceMappingURL=bundle-DboItAre.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-DboItAre.js","names":[],"sources":["../src/tools/bundle.ts"],"sourcesContent":["/**\n * What the bundler integrations share: which messages a build ships, the reads that would show a key\n * once their message is pruned, and the code a dev server appends so an edit swaps messages in place.\n *\n * Vite sees every module before it writes a chunk, so it prunes after the fact (`vite.ts`). Next's\n * loaders and Bun's `onLoad` hand over a locale module before the code that reads it has been seen,\n * so they read the source under `scan.dirs` first (`scanSources`) and load the locale already pruned.\n */\nimport { existsSync, readFileSync, watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { compileLocale, localeModule, type Entry } from './compile.ts';\nimport type { ResolvedConfig } from './config.ts';\nimport { isReferenced, keyRefs, READERS, sourceFiles, unseenReads, type KeyRefs, type Reads, type UnseenRead } from './scan.ts';\n\n/** How a build prunes: `strict` also fails on a read whose key it can't see. */\nexport type Prune = 'strict' | 'loose' | false;\n\n/** Whether a build keeps a key: some module names it, or `keep` lists it, exactly or as `prefix.*`. */\nexport function keeper(refs: KeyRefs, keep: string[] = []): (key: string) => boolean {\n return (key) => isReferenced(key, refs) || keep.some((k) => (k.endsWith('.*') ? key.startsWith(k.slice(0, -1)) : k === key));\n}\n\n/** The unseen reads that are a copy's: a name only counts once some module binds a reader to it. */\nexport function unseenOf(reads: Iterable<Reads>): UnseenRead[] {\n const all = [...reads];\n const readers = new Set([...READERS, ...all.flatMap((r) => [...r.aliases])]);\n return all.flatMap((r) => r.unseen).filter((u) => u.name === 'Text' || readers.has(u.name));\n}\n\n/** The error a strict build fails with, each read at its path from `root`. */\nexport function unseenMessage(unseen: UnseenRead[], root: string): string {\n const at = (file: string) => relative(root, file).replaceAll('\\\\', '/');\n return [\n `${unseen.length} read(s) whose key the build can't see, so their messages would be pruned and the key shown instead:`,\n ...unseen.map((u) => ` ${at(u.file)}:${u.line}:${u.column} ${u.code}`),\n 'Write each key at the read, as a literal or `area.${id}`; or, where every key it reads is written elsewhere or listed in `keep`, mark the line `prosebook-ignore`. `prune: \"loose\"` turns this check off.',\n ].join('\\n');\n}\n\nexport interface SourceScan {\n refs: KeyRefs;\n unseen: UnseenRead[];\n}\n\n/**\n * Every key the app's source names and, with `strict`, its unseen reads. The generated modules are\n * left out, since they name every key.\n */\nexport function scanSources(config: ResolvedConfig, strict: boolean): SourceScan {\n const refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const reads: Reads[] = [];\n for (const file of sourceFiles(config.scan.dirs, config.scan.extensions, [config.out])) {\n const code = readFileSync(file, 'utf8');\n keyRefs(code, refs);\n if (strict) reads.push(unseenReads(file, code));\n }\n return { refs, unseen: unseenOf(reads) };\n}\n\n/** Calls `change` once a burst of edits to the catalog or its translations settles. Returns the stop. */\nexport function watchCatalog(config: ResolvedConfig, change: () => void): () => void {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const watchers = [config.catalog, config.locales]\n .filter((dir) => existsSync(dir))\n .map((dir) =>\n watch(dir, { recursive: true }, (_event, file) => {\n if (file === null || !file.endsWith('.yaml')) return;\n clearTimeout(timer);\n timer = setTimeout(change, 50);\n }),\n );\n return () => {\n clearTimeout(timer);\n for (const w of watchers) w.close();\n };\n}\n\n/** A locale's module holding only the keys `kept`, as JavaScript, for a bundler to load in its place. */\nexport function prunedModule(entries: Entry[], kept: (key: string) => boolean): string {\n return localeModule(compileLocale(entries, kept), '// Pruned by prosebook: only the messages the code names.', false);\n}\n\n/**\n * Appended to a locale module in dev. Accepts its own update and hands the new messages to every\n * copy (`HotHook`); a page loaded before the hooks existed reloads instead.\n */\nexport const hotLocale = (locale: string) => `\nif (import.meta.hot) {\n import.meta.hot.accept((next) => {\n const hot = globalThis.__PROSEBOOK_HOT__;\n if (next === undefined || hot === undefined) return import.meta.hot.invalidate();\n for (const update of hot) update(${JSON.stringify(locale)}, next.default);\n });\n}\n`;\n\n/** Appended to the index in dev: its doc comments change with every edit; only a new locale reloads. */\nexport const HOT_INDEX = `\nif (import.meta.hot) {\n import.meta.hot.accept((next) => {\n if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();\n });\n}\n`;\n\n/**\n * A locale module in dev under a bundler that runs it again when it changes, as Turbopack's Fast\n * Refresh does, but has no `import.meta.hot`: each run hands its messages to every copy. The first\n * run finds no copy holding the locale yet, so it changes nothing.\n */\nexport const notifyingLocale = (code: string, locale: string) =>\n `${code.replace(/^export default /m, 'const messages = ')}\nexport default messages;\nglobalThis.__PROSEBOOK_HOT__?.forEach((update) => update(${JSON.stringify(locale)}, messages));\n`;\n\n/** Prepended to the index in dev, so the set every copy adds itself to exists before any copy does. */\nexport const HOT_SET = 'globalThis.__PROSEBOOK_HOT__ ??= new Set();\\n';\n\n/**\n * A module of prosebook's own beside the one at `url`, which passes its `import.meta.url`: the source\n * when run from source, the built file in dist.\n */\nexport const beside = (url: string, name: string) =>\n fileURLToPath(new URL(`./${name}${url.endsWith('.ts') ? '.ts' : '.js'}`, url)).replaceAll('\\\\', '/');\n\n/** Code that starts the inspector (`inspector.ts`, imported from `path`) on the page, its endpoints under `base`. */\nexport const installInspector = (path: string, base: string) =>\n `import { install as installInspector } from ${JSON.stringify(path)};\\ninstallInspector(${JSON.stringify({ base })});\\n`;\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,OAAO,MAAe,OAAiB,CAAC,GAA6B;CACnF,QAAQ,QAAQ,aAAa,KAAK,IAAI,KAAK,KAAK,MAAM,MAAO,EAAE,SAAS,IAAI,IAAI,IAAI,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,MAAM,GAAI;AAC7H;;AAGA,SAAgB,SAAS,OAAsC;CAC7D,MAAM,MAAM,CAAC,GAAG,KAAK;CACrB,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;CAC3E,OAAO,IAAI,SAAS,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE,SAAS,UAAU,QAAQ,IAAI,EAAE,IAAI,CAAC;AAC5F;;AAGA,SAAgB,cAAc,QAAsB,MAAsB;CACxE,MAAM,MAAM,SAAiB,SAAS,MAAM,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;CACtE,OAAO;EACL,GAAG,OAAO,OAAO;EACjB,GAAG,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM;EACvE;CACF,CAAC,CAAC,KAAK,IAAI;AACb;;;;;AAWA,SAAgB,YAAY,QAAwB,QAA6B;CAC/E,MAAM,OAAgB;EAAE,uBAAO,IAAI,IAAI;EAAG,0BAAU,IAAI,IAAI;CAAE;CAC9D,MAAM,QAAiB,CAAC;CACxB,KAAK,MAAM,QAAQ,YAAY,OAAO,KAAK,MAAM,OAAO,KAAK,YAAY,CAAC,OAAO,GAAG,CAAC,GAAG;EACtF,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,QAAQ,MAAM,IAAI;EAClB,IAAI,QAAQ,MAAM,KAAK,YAAY,MAAM,IAAI,CAAC;CAChD;CACA,OAAO;EAAE;EAAM,QAAQ,SAAS,KAAK;CAAE;AACzC;;AAGA,SAAgB,aAAa,QAAwB,QAAgC;CACnF,IAAI;CACJ,MAAM,WAAW,CAAC,OAAO,SAAS,OAAO,OAAO,CAAC,CAC9C,QAAQ,QAAQ,WAAW,GAAG,CAAC,CAAC,CAChC,KAAK,QACJ,MAAM,KAAK,EAAE,WAAW,KAAK,IAAI,QAAQ,SAAS;EAChD,IAAI,SAAS,QAAQ,CAAC,KAAK,SAAS,OAAO,GAAG;EAC9C,aAAa,KAAK;EAClB,QAAQ,WAAW,QAAQ,EAAE;CAC/B,CAAC,CACH;CACF,aAAa;EACX,aAAa,KAAK;EAClB,KAAK,MAAM,KAAK,UAAU,EAAE,MAAM;CACpC;AACF;;AAGA,SAAgB,aAAa,SAAkB,MAAwC;CACrF,OAAO,aAAa,cAAc,SAAS,IAAI,GAAG,6DAA6D,KAAK;AACtH;;;;;AAMA,MAAa,aAAa,WAAmB;;;;;uCAKN,KAAK,UAAU,MAAM,EAAE;;;;;AAM9D,MAAa,YAAY;;;;;;;;;;;;AAazB,MAAa,mBAAmB,MAAc,WAC5C,GAAG,KAAK,QAAQ,qBAAqB,mBAAmB,EAAE;;2DAED,KAAK,UAAU,MAAM,EAAE;;;AAIlF,MAAa,UAAU;;;;;AAMvB,MAAa,UAAU,KAAa,SAClC,cAAc,IAAI,IAAI,KAAK,OAAO,IAAI,SAAS,KAAK,IAAI,QAAQ,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW,MAAM,GAAG;;AAGrG,MAAa,oBAAoB,MAAc,SAC7C,+CAA+C,KAAK,UAAU,IAAI,EAAE,sBAAsB,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE"}
@@ -0,0 +1,6 @@
1
+ import "./compile-xF3F4oIp.js";
2
+ import "./config-DdMkKt_N.js";
3
+ /** How a build prunes: `strict` also fails on a read whose key it can't see. */
4
+ type Prune = 'strict' | 'loose' | false;
5
+ export { Prune as t };
6
+ //# sourceMappingURL=bundle-KD-DiiFa.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-KD-DiiFa.d.ts","names":[],"sources":["../src/tools/bundle.ts"],"mappings":";;;KAiBY"}
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { F as SYNTAX_NAME, O as CatalogError, P as whereOf, S as applyFixes, j as entryLocator, o as scan, p as render, r as isReferenced, s as sourceFiles, u as generate } from "./scan-Ch2BfIUD.js";
3
- import { p as lint, r as loadConfig, t as CONFIG_FILES } from "./config-D--RITk2.js";
4
- import { a as wordDiff, i as formatDiff, l as planExtract, n as applyMigrate, o as applyExtract, r as planMigrate, t as SarifReport } from "./sarif-CI8HOokm.js";
2
+ import { f as watchCatalog } from "./bundle-DboItAre.js";
3
+ import { D as whereOf, O as SYNTAX_NAME, _ as applyFixes, a as render, l as changeOf, n as generate, w as entryLocator, x as CatalogError, y as importTranslations } from "./generate-9pR3i5Bj.js";
4
+ import { p as lint, r as loadConfig, t as CONFIG_FILES } from "./config-BPSmoYel.js";
5
+ import { a as applyExtract, c as planExtract, i as planMigrate, n as todoOf, r as applyMigrate, t as SarifReport } from "./sarif-CWyTAQ0y.js";
6
+ import { o as scan, r as isReferenced, s as sourceFiles } from "./scan-Bx0_yDSi.js";
5
7
  import { join, relative, resolve } from "node:path";
6
- import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
7
- import { stringify } from "yaml";
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
9
  import { parseArgs } from "node:util";
9
10
  // src/cli.ts
10
11
  /**
@@ -23,6 +24,9 @@ Commands:
23
24
  status Translation coverage and stale translations per locale (--strict, --verbose)
24
25
  accept <locale> [keys] Mark stale translations as still right (--all for every stale one)
25
26
  todo <locale> Print the missing and stale entries of a locale, ready to translate
27
+ (--prompt: as a prompt for a model, saying how to reply)
28
+ import <locale> [file] Write translations from todo's YAML filled in, or a model's reply to --prompt
29
+ (stdin without a file); each is checked first, and one that fails is left out
26
30
  scan Find hardcoded text in JSX and catalog keys no code reads (--strict)
27
31
  extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it
28
32
  (dry run unless --write; --area <name> puts every entry in one area)
@@ -68,6 +72,7 @@ async function main(argv) {
68
72
  all: { type: "boolean" },
69
73
  write: { type: "boolean" },
70
74
  fix: { type: "boolean" },
75
+ prompt: { type: "boolean" },
71
76
  area: { type: "string" },
72
77
  format: { type: "string" },
73
78
  help: {
@@ -110,7 +115,8 @@ async function main(argv) {
110
115
  code = runStatus(config, values.strict === true, values.verbose === true, report);
111
116
  break;
112
117
  case "accept": return runAccept(config, rest, values.all === true);
113
- case "todo": return runTodo(config, rest[0]);
118
+ case "todo": return runTodo(config, rest[0], values.prompt === true);
119
+ case "import": return runImport(config, rest[0], rest[1]);
114
120
  case "scan":
115
121
  code = runScan(config, values.strict === true, report);
116
122
  break;
@@ -194,15 +200,7 @@ function watchMode(config) {
194
200
  }
195
201
  };
196
202
  regenerate();
197
- let timer;
198
- for (const dir of [config.catalog, config.locales]) {
199
- if (!existsSync(dir)) continue;
200
- watch(dir, { recursive: true }, (_event, file) => {
201
- if (file === null || !file.endsWith(".yaml")) return;
202
- clearTimeout(timer);
203
- timer = setTimeout(regenerate, 50);
204
- });
205
- }
203
+ watchCatalog(config, regenerate);
206
204
  out("prosebook: watching the catalog for changes");
207
205
  return new Promise(() => {});
208
206
  }
@@ -222,15 +220,7 @@ function runLint(config, report, fix = false) {
222
220
  findings = lint(catalog.messages, config);
223
221
  }
224
222
  }
225
- const locate = entryLocator(config.root);
226
- const placed = findings.map((f) => ({
227
- ...f,
228
- ...locate(f.file, f.key, f.at?.start, config.source)
229
- }));
230
- for (const f of placed) {
231
- const severity = f.severity === "error" ? "error" : "warn ";
232
- err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === void 0 ? "" : " (fixable)"}`);
233
- }
223
+ const placed = printFindings(config, findings);
234
224
  if (report !== void 0) {
235
225
  for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);
236
226
  report.rule("allow", RULES.allow);
@@ -252,6 +242,19 @@ function runLint(config, report, fix = false) {
252
242
  }
253
243
  return errors > 0 ? 1 : 0;
254
244
  }
245
+ /** Each finding on its line, placed at its entry's words. */
246
+ function printFindings(config, findings) {
247
+ const locate = entryLocator(config.root);
248
+ const placed = findings.map((f) => ({
249
+ ...f,
250
+ ...locate(f.file, f.key, f.at?.start, config.source)
251
+ }));
252
+ for (const f of placed) {
253
+ const severity = f.severity === "error" ? "error" : "warn ";
254
+ err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === void 0 ? "" : " (fixable)"}`);
255
+ }
256
+ return placed;
257
+ }
255
258
  function missingKeys(catalog, locale) {
256
259
  return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);
257
260
  }
@@ -324,44 +327,36 @@ function runAccept(config, args, all) {
324
327
  out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);
325
328
  return 0;
326
329
  }
327
- /** What changed in the source of a stale translation, as a word diff, or why that isn't known. */
328
- function changeOf(stale) {
329
- if (stale.was === void 0) return "the earlier text isn't recorded (the lock predates prosebook 0.2)";
330
- return formatDiff(wordDiff(stale.was, stale.now)).replaceAll("\n", " ");
331
- }
332
- function runTodo(config, locale) {
330
+ function runTodo(config, locale, prompt) {
333
331
  if (locale === void 0) {
334
- err("Usage: prosebook todo <locale>");
332
+ err("Usage: prosebook todo <locale> [--prompt]");
335
333
  return 1;
336
334
  }
337
335
  const { catalog, stale } = render(config);
338
- const staleKeys = stale.get(locale) ?? /* @__PURE__ */ new Map();
339
- const byArea = /* @__PURE__ */ new Map();
340
- let count = 0;
341
- for (const m of catalog.messages) {
342
- const current = m.translations.get(locale);
343
- if (current !== void 0 && !staleKeys.has(m.key)) continue;
344
- count += 1;
345
- const lines = byArea.get(m.area) ?? [];
346
- lines.push("");
347
- if (m.context) lines.push(`# ${m.context}`);
348
- for (const [arg, said] of m.argDocs) lines.push(`# {${arg}}: ${said}`);
349
- lines.push(`# ${config.source}: ${m.text.replaceAll("\n", " ")}`);
350
- const change = staleKeys.get(m.key);
351
- if (change !== void 0) lines.push(`# stale, the source changed: ${changeOf(change)}`);
352
- lines.push(stringify({ [m.key]: current?.text ?? "" }, { lineWidth: 0 }).trimEnd());
353
- byArea.set(m.area, lines);
354
- }
355
- if (count === 0) {
356
- out(`# ${locale} is complete`);
357
- return 0;
336
+ const todo = todoOf(config, catalog, stale.get(locale) ?? /* @__PURE__ */ new Map(), locale, prompt);
337
+ out(todo.text);
338
+ if (prompt && todo.count > 0) err(`\nHand this to a model, save its reply, and \`prosebook import ${locale} <reply>\`.`);
339
+ return 0;
340
+ }
341
+ function runImport(config, locale, file) {
342
+ if (locale === void 0 || (file === void 0 || file === "-") && process.stdin.isTTY) {
343
+ err("Usage: prosebook import <locale> <file>, or the reply on stdin");
344
+ return 1;
358
345
  }
359
- out(`# prosebook todo: ${count} entr${count === 1 ? "y" : "ies"} for ${locale}. Fill them in and paste each block into ${rel(config, config.locales)}/${locale}/<area>.yaml (or ${locale}.yaml).`);
360
- for (const [area, lines] of byArea) {
361
- out(`\n# ── ${area}.yaml ──`);
362
- out(lines.join("\n"));
346
+ const reply = readFileSync(file === void 0 || file === "-" ? 0 : resolve(file), "utf8");
347
+ const { written, unchanged, problems } = importTranslations(config, locale, reply);
348
+ reportProblems(config, problems);
349
+ if (written.length + unchanged.length > 0) {
350
+ const stale = render(config).stale.get(locale) ?? /* @__PURE__ */ new Map();
351
+ const kept = unchanged.filter((key) => stale.has(key));
352
+ const { catalog } = generate(config, { accept: /* @__PURE__ */ new Map([[locale, new Set(kept)]]) });
353
+ out(`prosebook: imported ${written.length} translation(s) into ${locale}${kept.length > 0 ? `, and accepted ${kept.length} stale one(s) as still right` : ""}`);
354
+ const imported = new Set(written);
355
+ printFindings(config, lint(catalog.messages.filter((m) => imported.has(m.key)), config).filter((f) => f.locale === locale));
363
356
  }
364
- return 0;
357
+ if (problems.length === 0) return 0;
358
+ err(`\n${problems.length} left out: fix them, or hand these lines back to the model, and import again.`);
359
+ return 1;
365
360
  }
366
361
  function runScan(config, strict, report) {
367
362
  const { catalog } = render(config);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `prosebook` command. Each subcommand is a function of the resolved config; `check` runs the\n * ones CI needs in one go.\n */\nimport { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { stringify } from 'yaml';\n\nimport { CatalogError, entryLocator, whereOf, type Catalog, type Problem } from './tools/catalog.ts';\nimport { CONFIG_FILES, loadConfig, type ResolvedConfig } from './tools/config.ts';\nimport { formatDiff, wordDiff } from './tools/diff.ts';\nimport { applyFixes } from './tools/entry.ts';\nimport { applyExtract, planExtract } from './tools/extract.ts';\nimport { generate, render } from './tools/generate.ts';\nimport { lint } from './tools/lint.ts';\nimport type { StaleTranslation } from './tools/lock.ts';\nimport { applyMigrate, planMigrate } from './tools/migrate.ts';\nimport { SarifReport } from './tools/sarif.ts';\nimport { isReferenced, scan, sourceFiles } from './tools/scan.ts';\nimport { SYNTAX_NAME } from './tools/syntax.ts';\n\nconst HELP = `prosebook: every string your app shows, in one typed catalog\n\nUsage: prosebook <command> [options]\n\nCommands:\n init Create a config and a first catalog file\n generate Compile the catalog into typed modules (--check: fail if out of date, --watch)\n lint Check the messages and their translations against the writing rules\n (--fix: write the fixes the rules know, such as spellings and …)\n status Translation coverage and stale translations per locale (--strict, --verbose)\n accept <locale> [keys] Mark stale translations as still right (--all for every stale one)\n todo <locale> Print the missing and stale entries of a locale, ready to translate\n scan Find hardcoded text in JSX and catalog keys no code reads (--strict)\n extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it\n (dry run unless --write; --area <name> puts every entry in one area)\n migrate <1|2> Rewrite the catalog and translations in ICU MessageFormat (1) or MessageFormat 2\n (dry run unless --write)\n check generate --check, lint and status --strict together, for CI\n\nOptions:\n --root <dir> Project root (default: the current directory)\n --format <text|sarif> With lint, scan, status and check: sarif writes a SARIF 2.1.0 log to stdout\n for code scanning, and the usual text to stderr\n -h, --help Show this help\n`;\n\nconst SARIF_COMMANDS = ['lint', 'scan', 'status', 'check'];\n\n/** What the checks that aren't lint rules hold the project to, for the rules in a SARIF log. */\nconst RULES = {\n catalog: 'The catalog and its translations parse, and each translation keeps the placeholders of its source.',\n 'out-of-date': 'The generated modules match the catalog: run `prosebook generate`.',\n 'missing-translation': 'Every locale translates every entry.',\n 'stale-translation': 'A translation was made from the source text as it is now.',\n 'hardcoded-text': 'Text a person reads comes from the catalog, not from a literal in JSX.',\n 'unused-key': 'Code reads every catalog key.',\n allow: \"An entry's exemption names a rule the entry breaks, and says why in a sentence.\",\n};\n\n/** Text for people. Under `--format sarif` stdout holds only the log, so this goes to stderr. */\nlet out = (s = '') => process.stdout.write(`${s}\\n`);\nconst err = (s = '') => process.stderr.write(`${s}\\n`);\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n root: { type: 'string' },\n check: { type: 'boolean' },\n watch: { type: 'boolean' },\n strict: { type: 'boolean' },\n verbose: { type: 'boolean' },\n all: { type: 'boolean' },\n write: { type: 'boolean' },\n fix: { type: 'boolean' },\n area: { type: 'string' },\n format: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...rest] = positionals;\n if (values.help || command === undefined) {\n out(HELP);\n return 0;\n }\n if (values.format !== undefined && values.format !== 'text' && values.format !== 'sarif') {\n err(`prosebook: --format is text or sarif, not \"${values.format}\"`);\n return 1;\n }\n const report = values.format === 'sarif' ? new SarifReport() : undefined;\n if (report !== undefined) {\n if (!SARIF_COMMANDS.includes(command)) {\n err(`prosebook: --format sarif works with ${SARIF_COMMANDS.join(', ')}`);\n return 1;\n }\n out = err;\n }\n const root = values.root ?? process.cwd();\n if (command === 'init') return init(root);\n const config = await loadConfig(root);\n let code: number;\n try {\n switch (command) {\n case 'generate':\n case 'gen':\n if (values.watch) return watchMode(config);\n return runGenerate(config, values.check === true);\n case 'lint':\n code = runLint(config, report, values.fix === true);\n break;\n case 'status':\n code = runStatus(config, values.strict === true, values.verbose === true, report);\n break;\n case 'accept':\n return runAccept(config, rest, values.all === true);\n case 'todo':\n return runTodo(config, rest[0]);\n case 'scan':\n code = runScan(config, values.strict === true, report);\n break;\n case 'extract':\n return runExtract(config, rest, values.write === true, values.area);\n case 'migrate':\n return runMigrate(config, rest[0], values.write === true);\n case 'check': {\n const results = [runGenerate(config, true, report), runLint(config, report), runStatus(config, true, false, report)];\n code = results.some((r) => r !== 0) ? 1 : 0;\n break;\n }\n default:\n err(`prosebook: no command \"${command}\"\\n`);\n out(HELP);\n return 1;\n }\n } catch (e) {\n if (!(e instanceof CatalogError)) throw e;\n reportProblems(config, e.problems, report);\n err(`\\n${e.problems.filter((p) => p.severity === 'error').length} error(s) in the catalog.`);\n code = 1;\n }\n if (report !== undefined) process.stdout.write(`${JSON.stringify(report.toJSON(config.root, tool()), null, 2)}\\n`);\n return code;\n}\n\nfunction tool() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string; homepage: string };\n return { name: 'prosebook', version: pkg.version, informationUri: pkg.homepage };\n}\n\nfunction reportProblems(config: ResolvedConfig, problems: Problem[], report?: SarifReport) {\n for (const p of problems) err(`${p.severity === 'error' ? 'error' : 'warn '} ${whereOf(p)}: ${p.message}`);\n if (report === undefined) return;\n const locate = entryLocator(config.root);\n report.rule('catalog', RULES.catalog);\n for (const p of problems) {\n report.add({\n rule: 'catalog',\n level: p.severity === 'error' ? 'error' : 'warning',\n message: p.message,\n file: p.file,\n ...(p.file !== undefined && p.key !== undefined ? locate(p.file, p.key) : {}),\n });\n }\n}\n\nfunction rel(config: ResolvedConfig, path: string) {\n return relative(config.root, path).replaceAll('\\\\', '/');\n}\n\nfunction runGenerate(config: ResolvedConfig, check: boolean, report?: SarifReport): number {\n const { changed, catalog, stale } = generate(config, { check });\n reportProblems(config, catalog.problems, report);\n const staleCount = [...stale.values()].reduce((n, s) => n + s.size, 0);\n const summary = `${catalog.messages.length} messages, ${catalog.locales.length + 1} locale(s)${staleCount > 0 ? `, ${staleCount} stale translation(s)` : ''}`;\n if (check) {\n if (changed.length === 0) {\n out(`prosebook: generated files are up to date (${summary})`);\n return 0;\n }\n err(`prosebook: out of date, run \\`prosebook generate\\`:\\n ${changed.map((p) => rel(config, p)).join('\\n ')}`);\n report?.rule('out-of-date', RULES['out-of-date']);\n for (const p of changed)\n report?.add({ rule: 'out-of-date', level: 'error', message: 'out of date: run `prosebook generate`', file: rel(config, p) });\n return 1;\n }\n out(`prosebook: ${changed.length === 0 ? 'nothing changed' : `wrote ${changed.length} file(s)`} (${summary})`);\n return 0;\n}\n\nfunction watchMode(config: ResolvedConfig): Promise<number> {\n const regenerate = () => {\n try {\n runGenerate(config, false);\n } catch (e) {\n if (e instanceof CatalogError) reportProblems(config, e.problems);\n else err(String(e));\n }\n };\n regenerate();\n let timer: ReturnType<typeof setTimeout> | undefined;\n for (const dir of [config.catalog, config.locales]) {\n if (!existsSync(dir)) continue;\n watch(dir, { recursive: true }, (_event, file) => {\n if (file === null || !file.endsWith('.yaml')) return;\n clearTimeout(timer);\n timer = setTimeout(regenerate, 50);\n });\n }\n out('prosebook: watching the catalog for changes');\n return new Promise(() => {});\n}\n\nfunction runLint(config: ResolvedConfig, report?: SarifReport, fix = false): number {\n let { catalog } = render(config);\n let findings = lint(catalog.messages, config);\n if (fix) {\n const { fixed, problems } = applyFixes(config, catalog, findings);\n if (problems.length > 0) {\n reportProblems(config, problems, report);\n err('The fixes would break the catalog, so nothing was written.');\n return 1;\n }\n if (fixed > 0) {\n out(`prosebook: fixed ${fixed} finding(s); run prosebook generate to compile them`);\n ({ catalog } = render(config));\n findings = lint(catalog.messages, config);\n }\n }\n const locate = entryLocator(config.root);\n const placed = findings.map((f) => ({ ...f, ...locate(f.file, f.key, f.at?.start, config.source) }));\n for (const f of placed) {\n const severity = f.severity === 'error' ? 'error' : 'warn ';\n err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === undefined ? '' : ' (fixable)'}`);\n }\n if (report !== undefined) {\n for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);\n report.rule('allow', RULES.allow);\n for (const f of placed) {\n report.add({\n rule: f.rule,\n level: f.severity === 'error' ? 'error' : 'warning',\n message: `${f.key}: ${f.message}`,\n file: f.file,\n line: f.line,\n column: f.column,\n });\n }\n }\n const errors = findings.filter((f) => f.severity === 'error').length;\n const fixable = findings.filter((f) => f.fix !== undefined).length;\n if (findings.length === 0) {\n out(`prosebook: ${catalog.messages.length} messages pass the writing rules`);\n } else {\n err(\n `\\n${errors} error(s), ${findings.length - errors} warning(s). Fix the string, or add \\`allow: { <rule>: <why> }\\` where the rule is wrong about it.`,\n );\n if (fixable > 0) err(`${fixable} of them can be fixed with prosebook lint --fix.`);\n }\n return errors > 0 ? 1 : 0;\n}\n\nfunction missingKeys(catalog: Catalog, locale: string): string[] {\n return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);\n}\n\nfunction runStatus(config: ResolvedConfig, strict: boolean, verbose: boolean, report?: SarifReport): number {\n const { catalog, stale } = render(config);\n if (report !== undefined) reportStatus(config, catalog, stale, strict ? 'error' : 'warning', report);\n const total = catalog.messages.length;\n if (catalog.locales.length === 0) {\n out(`prosebook: ${total} messages in ${config.source}, no translations yet`);\n return 0;\n }\n let incomplete = false;\n out(`${'locale'.padEnd(10)} ${'translated'.padStart(12)} ${'missing'.padStart(8)} ${'stale'.padStart(6)}`);\n for (const locale of catalog.locales) {\n const missing = missingKeys(catalog, locale);\n const staleHere = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const staleKeys = [...staleHere.keys()];\n const done = total - missing.length;\n if (missing.length > 0 || staleKeys.length > 0) incomplete = true;\n const pct = total === 0 ? 100 : Math.floor((done / total) * 100);\n out(\n `${locale.padEnd(10)} ${`${done}/${total} ${String(pct).padStart(3)}%`.padStart(12)} ${String(missing.length).padStart(8)} ${String(staleKeys.length).padStart(6)}`,\n );\n if (verbose) {\n for (const key of missing) out(` missing ${key}`);\n for (const [key, s] of staleHere) out(` stale ${key}: ${changeOf(s)}`);\n }\n }\n if (incomplete && !verbose)\n out('\\n`prosebook status --verbose` lists the keys; `prosebook todo <locale>` prints them ready to translate.');\n return strict && incomplete ? 1 : 0;\n}\n\nfunction reportStatus(\n config: ResolvedConfig,\n catalog: Catalog,\n stale: Map<string, Map<string, StaleTranslation>>,\n level: 'error' | 'warning',\n report: SarifReport,\n) {\n const locate = entryLocator(config.root);\n report.rule('missing-translation', RULES['missing-translation']);\n report.rule('stale-translation', RULES['stale-translation']);\n for (const locale of catalog.locales) {\n for (const m of catalog.messages) {\n if (m.translations.has(locale)) continue;\n const message = `${m.key} has no ${locale} translation: \\`prosebook todo ${locale}\\` prints it ready to translate`;\n report.add({ rule: 'missing-translation', level, message, file: m.file, ...locate(m.file, m.key) });\n }\n for (const [key, s] of stale.get(locale) ?? []) {\n const file = catalog.messages.find((m) => m.key === key)?.translations.get(locale)?.file;\n if (file === undefined) continue;\n const message = `the ${locale} translation of ${key} was made from an earlier source (${changeOf(s)}): update it, or \\`prosebook accept ${locale} ${key}\\` if it is still right`;\n report.add({ rule: 'stale-translation', level, message, file, ...locate(file, key) });\n }\n }\n}\n\nfunction runAccept(config: ResolvedConfig, args: string[], all: boolean): number {\n const [locale, ...keys] = args;\n if (locale === undefined || (keys.length === 0 && !all)) {\n err('Usage: prosebook accept <locale> <key>... | prosebook accept <locale> --all');\n return 1;\n }\n const before = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const accepting = all ? new Set(before.keys()) : new Set(keys);\n for (const key of accepting) if (!before.has(key)) err(`warn ${locale} ${key} is not stale`);\n generate(config, { accept: new Map([[locale, all ? true : new Set(keys)]]) });\n out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);\n return 0;\n}\n\n/** What changed in the source of a stale translation, as a word diff, or why that isn't known. */\nfunction changeOf(stale: StaleTranslation): string {\n if (stale.was === undefined) return \"the earlier text isn't recorded (the lock predates prosebook 0.2)\";\n return formatDiff(wordDiff(stale.was, stale.now)).replaceAll('\\n', ' ');\n}\n\nfunction runTodo(config: ResolvedConfig, locale: string | undefined): number {\n if (locale === undefined) {\n err('Usage: prosebook todo <locale>');\n return 1;\n }\n const { catalog, stale } = render(config);\n const staleKeys = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const byArea = new Map<string, string[]>();\n let count = 0;\n for (const m of catalog.messages) {\n const current = m.translations.get(locale);\n if (current !== undefined && !staleKeys.has(m.key)) continue;\n count += 1;\n const lines = byArea.get(m.area) ?? [];\n lines.push('');\n if (m.context) lines.push(`# ${m.context}`);\n for (const [arg, said] of m.argDocs) lines.push(`# {${arg}}: ${said}`);\n lines.push(`# ${config.source}: ${m.text.replaceAll('\\n', ' ')}`);\n const change = staleKeys.get(m.key);\n if (change !== undefined) lines.push(`# stale, the source changed: ${changeOf(change)}`);\n lines.push(stringify({ [m.key]: current?.text ?? '' }, { lineWidth: 0 }).trimEnd());\n byArea.set(m.area, lines);\n }\n if (count === 0) {\n out(`# ${locale} is complete`);\n return 0;\n }\n out(\n `# prosebook todo: ${count} entr${count === 1 ? 'y' : 'ies'} for ${locale}. Fill them in and paste each block into ${rel(config, config.locales)}/${locale}/<area>.yaml (or ${locale}.yaml).`,\n );\n for (const [area, lines] of byArea) {\n out(`\\n# ── ${area}.yaml ──`);\n out(lines.join('\\n'));\n }\n return 0;\n}\n\nfunction runScan(config: ResolvedConfig, strict: boolean, report?: SarifReport): number {\n const { catalog } = render(config);\n const result = scan(config.scan.dirs, config.scan.extensions, config.scan.attributes, config.root, [config.out]);\n for (const h of result.hardcoded)\n err(`${h.file}:${h.line}:${h.column} hardcoded ${h.where === 'text' ? 'text' : `${h.where}=`} \"${h.text}\"`);\n const unused = catalog.messages.filter((m) => !isReferenced(m.key, result.refs));\n for (const m of unused) err(`${m.file} ${m.key}: no code reads this key`);\n if (report !== undefined) {\n const level = strict ? 'error' : 'warning';\n const locate = entryLocator(config.root);\n report.rule('hardcoded-text', RULES['hardcoded-text']);\n report.rule('unused-key', RULES['unused-key']);\n for (const h of result.hardcoded) {\n const what = `${h.where === 'text' ? '' : `${h.where}=`}\"${h.text}\"`;\n const message = `${what} is hardcoded: move it into the catalog with \\`prosebook extract\\`, or mark the line \\`prosebook-ignore\\``;\n report.add({ rule: 'hardcoded-text', level, message, file: h.file, line: h.line, column: h.column });\n }\n for (const m of unused)\n report.add({ rule: 'unused-key', level, message: `no code reads ${m.key}: remove it`, file: m.file, ...locate(m.file, m.key) });\n }\n out(\n `prosebook: scanned ${result.files} file(s): ${result.hardcoded.length} hardcoded string(s), ${unused.length} unused key(s)` +\n (result.hardcoded.length > 0\n ? '. `prosebook extract` moves the text into the catalog; mark a deliberate line `prosebook-ignore`.'\n : ''),\n );\n return strict && result.hardcoded.length + unused.length > 0 ? 1 : 0;\n}\n\nasync function runExtract(config: ResolvedConfig, paths: string[], write: boolean, area: string | undefined): Promise<number> {\n const targets = paths.length > 0 ? paths.map((p) => resolve(config.root, p)) : config.scan.dirs;\n const files = sourceFiles(targets, ['.tsx', '.jsx'], [config.out]).concat(targets.filter((t) => /\\.[jt]sx$/.test(t) && existsSync(t)));\n const plan = await planExtract(config, [...new Set(files)], { area });\n const extractions = plan.files.flatMap((f) => f.extractions);\n for (const x of extractions) {\n out(`${x.file}:${x.line}:${x.column} ${x.where === 'text' ? '' : `${x.where}=`}\"${x.message}\"`);\n out(` → ${x.key}${x.reused ? ' (already in the catalog)' : ''}: ${x.replacement.trim()}`);\n }\n for (const f of plan.files) for (const w of f.warnings) err(`warn ${w}`);\n if (extractions.length === 0) {\n out('prosebook: no hardcoded text to extract');\n return 0;\n }\n const changedFiles = plan.files.filter((f) => f.edits.length > 0).length;\n const areas = [...new Set(plan.entries.map((e) => e.area))].map((a) => `${a}.yaml`);\n const summary = `${extractions.length} string(s) in ${changedFiles} file(s), ${plan.entries.length} new entr${plan.entries.length === 1 ? 'y' : 'ies'}${areas.length > 0 ? ` in ${areas.join(', ')}` : ''}`;\n out(`\nPlaceholder types: ${plan.types === 'syntax' ? 'read from the code alone (no TypeScript 7 project found)' : `from ${plan.types}`}.`);\n if (!write) {\n out(`prosebook: would extract ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyExtract(config, plan);\n out(`\nprosebook: extracted ${summary}.`);\n out('The new entries are marked `TODO(prosebook extract)`: check each key, kind, context and argument, then run `prosebook lint`.');\n return runGenerate(config, false);\n}\n\nfunction runMigrate(config: ResolvedConfig, target: string | undefined, write: boolean): number {\n const to = target === '1' ? 1 : target === '2' ? 2 : undefined;\n if (to === undefined) {\n err('Usage: prosebook migrate <1|2> [--write]');\n return 1;\n }\n if (config.messageFormat === to) {\n out(`prosebook: the catalog is already in ${SYNTAX_NAME[to]}`);\n return 0;\n }\n const plan = planMigrate(config, to);\n const where = (x: { file: string; key: string; locale: string }) =>\n `${x.file} ${x.key}${x.locale === config.source ? '' : ` (${x.locale})`}`;\n for (const r of plan.rewrites) out(`${where(r)}\\n ${r.after.replaceAll('\\n', '\\n ')}`);\n for (const c of plan.changed) err(`warn ${where(c)}: renders differently for ${c.values}: \"${c.before}\" becomes \"${c.after}\"`);\n for (const b of plan.blocked) err(`error ${where(b)}: ${b.reason}`);\n if (plan.blocked.length > 0) {\n err(`\nprosebook: ${plan.blocked.length} message(s) have no ${SYNTAX_NAME[to]} equivalent. Change them, then migrate again; nothing was written.`);\n return 1;\n }\n const files = new Set(plan.rewrites.map((r) => r.file)).size;\n const summary = `${plan.rewrites.length} message(s) in ${files} file(s) in ${SYNTAX_NAME[to]}`;\n if (!write) {\n out(`\nprosebook: would rewrite ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyMigrate(config, plan);\n out(`\nprosebook: rewrote ${summary}.`);\n out(`Now set \\`messageFormat: ${to}\\` in the config, then run \\`prosebook generate\\`.`);\n return 0;\n}\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'prosebook/config';\n\nexport default defineConfig({\n catalog: 'copy/catalog',\n locales: 'copy/locales',\n out: 'src/copy',\n source: 'en',\n // Write messages in Unicode MessageFormat 2 rather than ICU MessageFormat:\n // messageFormat: 2,\n});\n`;\n\nconst SAMPLE_CATALOG = `# yaml-language-server: $schema=./catalog.schema.json\napp.title:\n kind: name\n surface: title\n context: The app's name, in the window title and the header.\n en: My app\n\napp.greeting:\n kind: state\n surface: status\n context: Greets the person signed in, at the top of the home page.\n args:\n name: the person's first name\n en: Welcome back, {name}.\n`;\n\nfunction init(root: string): number {\n const configured = CONFIG_FILES.find((f) => existsSync(join(root, f)));\n if (configured === undefined) {\n writeFileSync(join(root, 'prosebook.config.ts'), SAMPLE_CONFIG);\n out('created prosebook.config.ts');\n }\n const catalogDir = join(root, 'copy', 'catalog');\n if (!existsSync(catalogDir)) {\n mkdirSync(catalogDir, { recursive: true });\n mkdirSync(join(root, 'copy', 'locales'), { recursive: true });\n writeFileSync(join(catalogDir, 'app.yaml'), SAMPLE_CATALOG);\n out('created copy/catalog/app.yaml');\n }\n out('\\nNext: `prosebook generate`, then `createCopy(catalog)` from the generated src/copy.');\n return 0;\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e: unknown) => {\n err(e instanceof Error ? (e.stack ?? e.message) : String(e));\n process.exit(1);\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `prosebook` command. Each subcommand is a function of the resolved config; `check` runs the\n * ones CI needs in one go.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { watchCatalog } from './tools/bundle.ts';\nimport { CatalogError, entryLocator, whereOf, type Catalog, type Problem } from './tools/catalog.ts';\nimport { CONFIG_FILES, loadConfig, type ResolvedConfig } from './tools/config.ts';\nimport { applyFixes, importTranslations } from './tools/entry.ts';\nimport { applyExtract, planExtract } from './tools/extract.ts';\nimport { generate, render } from './tools/generate.ts';\nimport { lint, type Finding } from './tools/lint.ts';\nimport { changeOf, type StaleTranslation } from './tools/lock.ts';\nimport { applyMigrate, planMigrate } from './tools/migrate.ts';\nimport { SarifReport } from './tools/sarif.ts';\nimport { isReferenced, scan, sourceFiles } from './tools/scan.ts';\nimport { SYNTAX_NAME } from './tools/syntax.ts';\nimport { todoOf } from './tools/todo.ts';\n\nconst HELP = `prosebook: every string your app shows, in one typed catalog\n\nUsage: prosebook <command> [options]\n\nCommands:\n init Create a config and a first catalog file\n generate Compile the catalog into typed modules (--check: fail if out of date, --watch)\n lint Check the messages and their translations against the writing rules\n (--fix: write the fixes the rules know, such as spellings and …)\n status Translation coverage and stale translations per locale (--strict, --verbose)\n accept <locale> [keys] Mark stale translations as still right (--all for every stale one)\n todo <locale> Print the missing and stale entries of a locale, ready to translate\n (--prompt: as a prompt for a model, saying how to reply)\n import <locale> [file] Write translations from todo's YAML filled in, or a model's reply to --prompt\n (stdin without a file); each is checked first, and one that fails is left out\n scan Find hardcoded text in JSX and catalog keys no code reads (--strict)\n extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it\n (dry run unless --write; --area <name> puts every entry in one area)\n migrate <1|2> Rewrite the catalog and translations in ICU MessageFormat (1) or MessageFormat 2\n (dry run unless --write)\n check generate --check, lint and status --strict together, for CI\n\nOptions:\n --root <dir> Project root (default: the current directory)\n --format <text|sarif> With lint, scan, status and check: sarif writes a SARIF 2.1.0 log to stdout\n for code scanning, and the usual text to stderr\n -h, --help Show this help\n`;\n\nconst SARIF_COMMANDS = ['lint', 'scan', 'status', 'check'];\n\n/** What the checks that aren't lint rules hold the project to, for the rules in a SARIF log. */\nconst RULES = {\n catalog: 'The catalog and its translations parse, and each translation keeps the placeholders of its source.',\n 'out-of-date': 'The generated modules match the catalog: run `prosebook generate`.',\n 'missing-translation': 'Every locale translates every entry.',\n 'stale-translation': 'A translation was made from the source text as it is now.',\n 'hardcoded-text': 'Text a person reads comes from the catalog, not from a literal in JSX.',\n 'unused-key': 'Code reads every catalog key.',\n allow: \"An entry's exemption names a rule the entry breaks, and says why in a sentence.\",\n};\n\n/** Text for people. Under `--format sarif` stdout holds only the log, so this goes to stderr. */\nlet out = (s = '') => process.stdout.write(`${s}\\n`);\nconst err = (s = '') => process.stderr.write(`${s}\\n`);\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n root: { type: 'string' },\n check: { type: 'boolean' },\n watch: { type: 'boolean' },\n strict: { type: 'boolean' },\n verbose: { type: 'boolean' },\n all: { type: 'boolean' },\n write: { type: 'boolean' },\n fix: { type: 'boolean' },\n prompt: { type: 'boolean' },\n area: { type: 'string' },\n format: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...rest] = positionals;\n if (values.help || command === undefined) {\n out(HELP);\n return 0;\n }\n if (values.format !== undefined && values.format !== 'text' && values.format !== 'sarif') {\n err(`prosebook: --format is text or sarif, not \"${values.format}\"`);\n return 1;\n }\n const report = values.format === 'sarif' ? new SarifReport() : undefined;\n if (report !== undefined) {\n if (!SARIF_COMMANDS.includes(command)) {\n err(`prosebook: --format sarif works with ${SARIF_COMMANDS.join(', ')}`);\n return 1;\n }\n out = err;\n }\n const root = values.root ?? process.cwd();\n if (command === 'init') return init(root);\n const config = await loadConfig(root);\n let code: number;\n try {\n switch (command) {\n case 'generate':\n case 'gen':\n if (values.watch) return watchMode(config);\n return runGenerate(config, values.check === true);\n case 'lint':\n code = runLint(config, report, values.fix === true);\n break;\n case 'status':\n code = runStatus(config, values.strict === true, values.verbose === true, report);\n break;\n case 'accept':\n return runAccept(config, rest, values.all === true);\n case 'todo':\n return runTodo(config, rest[0], values.prompt === true);\n case 'import':\n return runImport(config, rest[0], rest[1]);\n case 'scan':\n code = runScan(config, values.strict === true, report);\n break;\n case 'extract':\n return runExtract(config, rest, values.write === true, values.area);\n case 'migrate':\n return runMigrate(config, rest[0], values.write === true);\n case 'check': {\n const results = [runGenerate(config, true, report), runLint(config, report), runStatus(config, true, false, report)];\n code = results.some((r) => r !== 0) ? 1 : 0;\n break;\n }\n default:\n err(`prosebook: no command \"${command}\"\\n`);\n out(HELP);\n return 1;\n }\n } catch (e) {\n if (!(e instanceof CatalogError)) throw e;\n reportProblems(config, e.problems, report);\n err(`\\n${e.problems.filter((p) => p.severity === 'error').length} error(s) in the catalog.`);\n code = 1;\n }\n if (report !== undefined) process.stdout.write(`${JSON.stringify(report.toJSON(config.root, tool()), null, 2)}\\n`);\n return code;\n}\n\nfunction tool() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string; homepage: string };\n return { name: 'prosebook', version: pkg.version, informationUri: pkg.homepage };\n}\n\nfunction reportProblems(config: ResolvedConfig, problems: Problem[], report?: SarifReport) {\n for (const p of problems) err(`${p.severity === 'error' ? 'error' : 'warn '} ${whereOf(p)}: ${p.message}`);\n if (report === undefined) return;\n const locate = entryLocator(config.root);\n report.rule('catalog', RULES.catalog);\n for (const p of problems) {\n report.add({\n rule: 'catalog',\n level: p.severity === 'error' ? 'error' : 'warning',\n message: p.message,\n file: p.file,\n ...(p.file !== undefined && p.key !== undefined ? locate(p.file, p.key) : {}),\n });\n }\n}\n\nfunction rel(config: ResolvedConfig, path: string) {\n return relative(config.root, path).replaceAll('\\\\', '/');\n}\n\nfunction runGenerate(config: ResolvedConfig, check: boolean, report?: SarifReport): number {\n const { changed, catalog, stale } = generate(config, { check });\n reportProblems(config, catalog.problems, report);\n const staleCount = [...stale.values()].reduce((n, s) => n + s.size, 0);\n const summary = `${catalog.messages.length} messages, ${catalog.locales.length + 1} locale(s)${staleCount > 0 ? `, ${staleCount} stale translation(s)` : ''}`;\n if (check) {\n if (changed.length === 0) {\n out(`prosebook: generated files are up to date (${summary})`);\n return 0;\n }\n err(`prosebook: out of date, run \\`prosebook generate\\`:\\n ${changed.map((p) => rel(config, p)).join('\\n ')}`);\n report?.rule('out-of-date', RULES['out-of-date']);\n for (const p of changed)\n report?.add({ rule: 'out-of-date', level: 'error', message: 'out of date: run `prosebook generate`', file: rel(config, p) });\n return 1;\n }\n out(`prosebook: ${changed.length === 0 ? 'nothing changed' : `wrote ${changed.length} file(s)`} (${summary})`);\n return 0;\n}\n\nfunction watchMode(config: ResolvedConfig): Promise<number> {\n const regenerate = () => {\n try {\n runGenerate(config, false);\n } catch (e) {\n if (e instanceof CatalogError) reportProblems(config, e.problems);\n else err(String(e));\n }\n };\n regenerate();\n watchCatalog(config, regenerate);\n out('prosebook: watching the catalog for changes');\n return new Promise(() => {});\n}\n\nfunction runLint(config: ResolvedConfig, report?: SarifReport, fix = false): number {\n let { catalog } = render(config);\n let findings = lint(catalog.messages, config);\n if (fix) {\n const { fixed, problems } = applyFixes(config, catalog, findings);\n if (problems.length > 0) {\n reportProblems(config, problems, report);\n err('The fixes would break the catalog, so nothing was written.');\n return 1;\n }\n if (fixed > 0) {\n out(`prosebook: fixed ${fixed} finding(s); run prosebook generate to compile them`);\n ({ catalog } = render(config));\n findings = lint(catalog.messages, config);\n }\n }\n const placed = printFindings(config, findings);\n if (report !== undefined) {\n for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);\n report.rule('allow', RULES.allow);\n for (const f of placed) {\n report.add({\n rule: f.rule,\n level: f.severity === 'error' ? 'error' : 'warning',\n message: `${f.key}: ${f.message}`,\n file: f.file,\n line: f.line,\n column: f.column,\n });\n }\n }\n const errors = findings.filter((f) => f.severity === 'error').length;\n const fixable = findings.filter((f) => f.fix !== undefined).length;\n if (findings.length === 0) {\n out(`prosebook: ${catalog.messages.length} messages pass the writing rules`);\n } else {\n err(\n `\\n${errors} error(s), ${findings.length - errors} warning(s). Fix the string, or add \\`allow: { <rule>: <why> }\\` where the rule is wrong about it.`,\n );\n if (fixable > 0) err(`${fixable} of them can be fixed with prosebook lint --fix.`);\n }\n return errors > 0 ? 1 : 0;\n}\n\n/** Each finding on its line, placed at its entry's words. */\nfunction printFindings(config: ResolvedConfig, findings: Finding[]) {\n const locate = entryLocator(config.root);\n const placed = findings.map((f) => ({ ...f, ...locate(f.file, f.key, f.at?.start, config.source) }));\n for (const f of placed) {\n const severity = f.severity === 'error' ? 'error' : 'warn ';\n err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === undefined ? '' : ' (fixable)'}`);\n }\n return placed;\n}\n\nfunction missingKeys(catalog: Catalog, locale: string): string[] {\n return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);\n}\n\nfunction runStatus(config: ResolvedConfig, strict: boolean, verbose: boolean, report?: SarifReport): number {\n const { catalog, stale } = render(config);\n if (report !== undefined) reportStatus(config, catalog, stale, strict ? 'error' : 'warning', report);\n const total = catalog.messages.length;\n if (catalog.locales.length === 0) {\n out(`prosebook: ${total} messages in ${config.source}, no translations yet`);\n return 0;\n }\n let incomplete = false;\n out(`${'locale'.padEnd(10)} ${'translated'.padStart(12)} ${'missing'.padStart(8)} ${'stale'.padStart(6)}`);\n for (const locale of catalog.locales) {\n const missing = missingKeys(catalog, locale);\n const staleHere = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const staleKeys = [...staleHere.keys()];\n const done = total - missing.length;\n if (missing.length > 0 || staleKeys.length > 0) incomplete = true;\n const pct = total === 0 ? 100 : Math.floor((done / total) * 100);\n out(\n `${locale.padEnd(10)} ${`${done}/${total} ${String(pct).padStart(3)}%`.padStart(12)} ${String(missing.length).padStart(8)} ${String(staleKeys.length).padStart(6)}`,\n );\n if (verbose) {\n for (const key of missing) out(` missing ${key}`);\n for (const [key, s] of staleHere) out(` stale ${key}: ${changeOf(s)}`);\n }\n }\n if (incomplete && !verbose)\n out('\\n`prosebook status --verbose` lists the keys; `prosebook todo <locale>` prints them ready to translate.');\n return strict && incomplete ? 1 : 0;\n}\n\nfunction reportStatus(\n config: ResolvedConfig,\n catalog: Catalog,\n stale: Map<string, Map<string, StaleTranslation>>,\n level: 'error' | 'warning',\n report: SarifReport,\n) {\n const locate = entryLocator(config.root);\n report.rule('missing-translation', RULES['missing-translation']);\n report.rule('stale-translation', RULES['stale-translation']);\n for (const locale of catalog.locales) {\n for (const m of catalog.messages) {\n if (m.translations.has(locale)) continue;\n const message = `${m.key} has no ${locale} translation: \\`prosebook todo ${locale}\\` prints it ready to translate`;\n report.add({ rule: 'missing-translation', level, message, file: m.file, ...locate(m.file, m.key) });\n }\n for (const [key, s] of stale.get(locale) ?? []) {\n const file = catalog.messages.find((m) => m.key === key)?.translations.get(locale)?.file;\n if (file === undefined) continue;\n const message = `the ${locale} translation of ${key} was made from an earlier source (${changeOf(s)}): update it, or \\`prosebook accept ${locale} ${key}\\` if it is still right`;\n report.add({ rule: 'stale-translation', level, message, file, ...locate(file, key) });\n }\n }\n}\n\nfunction runAccept(config: ResolvedConfig, args: string[], all: boolean): number {\n const [locale, ...keys] = args;\n if (locale === undefined || (keys.length === 0 && !all)) {\n err('Usage: prosebook accept <locale> <key>... | prosebook accept <locale> --all');\n return 1;\n }\n const before = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const accepting = all ? new Set(before.keys()) : new Set(keys);\n for (const key of accepting) if (!before.has(key)) err(`warn ${locale} ${key} is not stale`);\n generate(config, { accept: new Map([[locale, all ? true : new Set(keys)]]) });\n out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);\n return 0;\n}\n\nfunction runTodo(config: ResolvedConfig, locale: string | undefined, prompt: boolean): number {\n if (locale === undefined) {\n err('Usage: prosebook todo <locale> [--prompt]');\n return 1;\n }\n const { catalog, stale } = render(config);\n const todo = todoOf(config, catalog, stale.get(locale) ?? new Map(), locale, prompt);\n out(todo.text);\n if (prompt && todo.count > 0) err(`\\nHand this to a model, save its reply, and \\`prosebook import ${locale} <reply>\\`.`);\n return 0;\n}\n\nfunction runImport(config: ResolvedConfig, locale: string | undefined, file: string | undefined): number {\n if (locale === undefined || ((file === undefined || file === '-') && process.stdin.isTTY)) {\n err('Usage: prosebook import <locale> <file>, or the reply on stdin');\n return 1;\n }\n const reply = readFileSync(file === undefined || file === '-' ? 0 : resolve(file), 'utf8');\n const { written, unchanged, problems } = importTranslations(config, locale, reply);\n reportProblems(config, problems);\n if (written.length + unchanged.length > 0) {\n // A stale translation given back unchanged was looked at and still holds.\n const stale = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const kept = unchanged.filter((key) => stale.has(key));\n const { catalog } = generate(config, { accept: new Map([[locale, new Set(kept)]]) });\n out(\n `prosebook: imported ${written.length} translation(s) into ${locale}${kept.length > 0 ? `, and accepted ${kept.length} stale one(s) as still right` : ''}`,\n );\n const imported = new Set(written);\n printFindings(\n config,\n lint(\n catalog.messages.filter((m) => imported.has(m.key)),\n config,\n ).filter((f) => f.locale === locale),\n );\n }\n if (problems.length === 0) return 0;\n err(`\\n${problems.length} left out: fix them, or hand these lines back to the model, and import again.`);\n return 1;\n}\n\nfunction runScan(config: ResolvedConfig, strict: boolean, report?: SarifReport): number {\n const { catalog } = render(config);\n const result = scan(config.scan.dirs, config.scan.extensions, config.scan.attributes, config.root, [config.out]);\n for (const h of result.hardcoded)\n err(`${h.file}:${h.line}:${h.column} hardcoded ${h.where === 'text' ? 'text' : `${h.where}=`} \"${h.text}\"`);\n const unused = catalog.messages.filter((m) => !isReferenced(m.key, result.refs));\n for (const m of unused) err(`${m.file} ${m.key}: no code reads this key`);\n if (report !== undefined) {\n const level = strict ? 'error' : 'warning';\n const locate = entryLocator(config.root);\n report.rule('hardcoded-text', RULES['hardcoded-text']);\n report.rule('unused-key', RULES['unused-key']);\n for (const h of result.hardcoded) {\n const what = `${h.where === 'text' ? '' : `${h.where}=`}\"${h.text}\"`;\n const message = `${what} is hardcoded: move it into the catalog with \\`prosebook extract\\`, or mark the line \\`prosebook-ignore\\``;\n report.add({ rule: 'hardcoded-text', level, message, file: h.file, line: h.line, column: h.column });\n }\n for (const m of unused)\n report.add({ rule: 'unused-key', level, message: `no code reads ${m.key}: remove it`, file: m.file, ...locate(m.file, m.key) });\n }\n out(\n `prosebook: scanned ${result.files} file(s): ${result.hardcoded.length} hardcoded string(s), ${unused.length} unused key(s)` +\n (result.hardcoded.length > 0\n ? '. `prosebook extract` moves the text into the catalog; mark a deliberate line `prosebook-ignore`.'\n : ''),\n );\n return strict && result.hardcoded.length + unused.length > 0 ? 1 : 0;\n}\n\nasync function runExtract(config: ResolvedConfig, paths: string[], write: boolean, area: string | undefined): Promise<number> {\n const targets = paths.length > 0 ? paths.map((p) => resolve(config.root, p)) : config.scan.dirs;\n const files = sourceFiles(targets, ['.tsx', '.jsx'], [config.out]).concat(targets.filter((t) => /\\.[jt]sx$/.test(t) && existsSync(t)));\n const plan = await planExtract(config, [...new Set(files)], { area });\n const extractions = plan.files.flatMap((f) => f.extractions);\n for (const x of extractions) {\n out(`${x.file}:${x.line}:${x.column} ${x.where === 'text' ? '' : `${x.where}=`}\"${x.message}\"`);\n out(` → ${x.key}${x.reused ? ' (already in the catalog)' : ''}: ${x.replacement.trim()}`);\n }\n for (const f of plan.files) for (const w of f.warnings) err(`warn ${w}`);\n if (extractions.length === 0) {\n out('prosebook: no hardcoded text to extract');\n return 0;\n }\n const changedFiles = plan.files.filter((f) => f.edits.length > 0).length;\n const areas = [...new Set(plan.entries.map((e) => e.area))].map((a) => `${a}.yaml`);\n const summary = `${extractions.length} string(s) in ${changedFiles} file(s), ${plan.entries.length} new entr${plan.entries.length === 1 ? 'y' : 'ies'}${areas.length > 0 ? ` in ${areas.join(', ')}` : ''}`;\n out(`\nPlaceholder types: ${plan.types === 'syntax' ? 'read from the code alone (no TypeScript 7 project found)' : `from ${plan.types}`}.`);\n if (!write) {\n out(`prosebook: would extract ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyExtract(config, plan);\n out(`\nprosebook: extracted ${summary}.`);\n out('The new entries are marked `TODO(prosebook extract)`: check each key, kind, context and argument, then run `prosebook lint`.');\n return runGenerate(config, false);\n}\n\nfunction runMigrate(config: ResolvedConfig, target: string | undefined, write: boolean): number {\n const to = target === '1' ? 1 : target === '2' ? 2 : undefined;\n if (to === undefined) {\n err('Usage: prosebook migrate <1|2> [--write]');\n return 1;\n }\n if (config.messageFormat === to) {\n out(`prosebook: the catalog is already in ${SYNTAX_NAME[to]}`);\n return 0;\n }\n const plan = planMigrate(config, to);\n const where = (x: { file: string; key: string; locale: string }) =>\n `${x.file} ${x.key}${x.locale === config.source ? '' : ` (${x.locale})`}`;\n for (const r of plan.rewrites) out(`${where(r)}\\n ${r.after.replaceAll('\\n', '\\n ')}`);\n for (const c of plan.changed) err(`warn ${where(c)}: renders differently for ${c.values}: \"${c.before}\" becomes \"${c.after}\"`);\n for (const b of plan.blocked) err(`error ${where(b)}: ${b.reason}`);\n if (plan.blocked.length > 0) {\n err(`\nprosebook: ${plan.blocked.length} message(s) have no ${SYNTAX_NAME[to]} equivalent. Change them, then migrate again; nothing was written.`);\n return 1;\n }\n const files = new Set(plan.rewrites.map((r) => r.file)).size;\n const summary = `${plan.rewrites.length} message(s) in ${files} file(s) in ${SYNTAX_NAME[to]}`;\n if (!write) {\n out(`\nprosebook: would rewrite ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyMigrate(config, plan);\n out(`\nprosebook: rewrote ${summary}.`);\n out(`Now set \\`messageFormat: ${to}\\` in the config, then run \\`prosebook generate\\`.`);\n return 0;\n}\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'prosebook/config';\n\nexport default defineConfig({\n catalog: 'copy/catalog',\n locales: 'copy/locales',\n out: 'src/copy',\n source: 'en',\n // Write messages in Unicode MessageFormat 2 rather than ICU MessageFormat:\n // messageFormat: 2,\n});\n`;\n\nconst SAMPLE_CATALOG = `# yaml-language-server: $schema=./catalog.schema.json\napp.title:\n kind: name\n surface: title\n context: The app's name, in the window title and the header.\n en: My app\n\napp.greeting:\n kind: state\n surface: status\n context: Greets the person signed in, at the top of the home page.\n args:\n name: the person's first name\n en: Welcome back, {name}.\n`;\n\nfunction init(root: string): number {\n const configured = CONFIG_FILES.find((f) => existsSync(join(root, f)));\n if (configured === undefined) {\n writeFileSync(join(root, 'prosebook.config.ts'), SAMPLE_CONFIG);\n out('created prosebook.config.ts');\n }\n const catalogDir = join(root, 'copy', 'catalog');\n if (!existsSync(catalogDir)) {\n mkdirSync(catalogDir, { recursive: true });\n mkdirSync(join(root, 'copy', 'locales'), { recursive: true });\n writeFileSync(join(catalogDir, 'app.yaml'), SAMPLE_CATALOG);\n out('created copy/catalog/app.yaml');\n }\n out('\\nNext: `prosebook generate`, then `createCopy(catalog)` from the generated src/copy.');\n return 0;\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e: unknown) => {\n err(e instanceof Error ? (e.stack ?? e.message) : String(e));\n process.exit(1);\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { nt as MessageFormatElement } from "./config-DdMkKt_N.js";
2
+ type Entry = readonly [key: string, ast: MessageFormatElement[]];
3
+ interface CompiledLocale {
4
+ /** `const oN = {…};` lines the messages refer to. */
5
+ declarations: string[];
6
+ /** `"key": message,` lines. */
7
+ rows: string[];
8
+ }
9
+ /** One locale's messages as code, keeping only `keep` when given. */
10
+ declare function compileLocale(entries: Iterable<Entry>, keep?: (key: string) => boolean): CompiledLocale;
11
+ /** The locale as an ES module whose default export is its messages. */
12
+ declare function localeModule(compiled: CompiledLocale, banner: string, ts: boolean): string;
13
+ /** The locale as one expression, for a bundler to inline in place of the module. */
14
+ declare function localeExpression(compiled: CompiledLocale): string;
15
+ export { localeModule as a, localeExpression as i, Entry as n, compileLocale as r, CompiledLocale as t };
16
+ //# sourceMappingURL=compile-xF3F4oIp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-xF3F4oIp.d.ts","names":[],"sources":["../src/tools/compile.ts"],"mappings":";KA0BY,kBAAkB,aAAa,KAAK;UAoG/B;;EAEf;;EAEA;;;iBAIc,cAAc,SAAS,SAAS,QAAQ,QAAQ,0BAA0B;;iBAW1E,aAAa,UAAU,gBAAgB,gBAAgB;;iBAUvD,iBAAiB,UAAU"}