prosebook 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -29
- package/dist/bun.d.ts +34 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +98 -0
- package/dist/bun.js.map +1 -0
- package/dist/bundle-CpUIWb9j.d.ts +6 -0
- package/dist/bundle-CpUIWb9j.d.ts.map +1 -0
- package/dist/bundle-DboItAre.js +111 -0
- package/dist/bundle-DboItAre.js.map +1 -0
- package/dist/cli.js +51 -56
- package/dist/cli.js.map +1 -1
- package/dist/compile-ClK9htKr.d.ts +16 -0
- package/dist/compile-ClK9htKr.d.ts.map +1 -0
- package/dist/{config-_4HfnJQu.d.ts → config-CzaJJQ60.d.ts} +75 -2
- package/dist/config-CzaJJQ60.d.ts.map +1 -0
- package/dist/{config-D--RITk2.js → config-nWZf9QmF.js} +42 -212
- package/dist/config-nWZf9QmF.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +1 -1
- package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
- package/dist/copy-B3V_QRkT.d.ts.map +1 -0
- package/dist/copy-Bo_FIoLy.js +154 -0
- package/dist/copy-Bo_FIoLy.js.map +1 -0
- package/dist/endpoints-BgG_5Vpm.js +96 -0
- package/dist/endpoints-BgG_5Vpm.js.map +1 -0
- package/dist/generate-BW3N8EGb.js +5488 -0
- package/dist/generate-BW3N8EGb.js.map +1 -0
- package/dist/helpers-BWGcD0kS.js +96 -0
- package/dist/helpers-BWGcD0kS.js.map +1 -0
- package/dist/icu-Dej0mNsF.js +218 -0
- package/dist/icu-Dej0mNsF.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -188
- package/dist/inspector.d.ts +4 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +7 -2
- package/dist/inspector.js.map +1 -1
- package/dist/locale-DpQA1pqH.js +70 -0
- package/dist/locale-DpQA1pqH.js.map +1 -0
- package/dist/next-loader.d.ts +11 -0
- package/dist/next-loader.d.ts.map +1 -0
- package/dist/next-loader.js +17 -0
- package/dist/next-loader.js.map +1 -0
- package/dist/next.d.ts +30 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +160 -0
- package/dist/next.js.map +1 -0
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/server.d.ts +20 -0
- package/dist/react/server.d.ts.map +1 -0
- package/dist/react/server.js +34 -0
- package/dist/react/server.js.map +1 -0
- package/dist/{sarif-CI8HOokm.js → sarif-B5nrMqrT.js} +103 -87
- package/dist/sarif-B5nrMqrT.js.map +1 -0
- package/dist/scan-Bx0_yDSi.js +387 -0
- package/dist/scan-Bx0_yDSi.js.map +1 -0
- package/dist/tools.d.ts +83 -90
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +5 -4
- package/dist/vite.d.ts +3 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +29 -109
- package/dist/vite.js.map +1 -1
- package/package.json +37 -4
- package/src/bun.ts +132 -0
- package/src/cli.ts +60 -58
- package/src/index.ts +1 -1
- package/src/inspector.ts +8 -3
- package/src/next-loader.ts +16 -0
- package/src/next.ts +188 -0
- package/src/react/index.tsx +6 -6
- package/src/react/server.ts +37 -0
- package/src/runtime/copy.ts +22 -3
- package/src/runtime/helpers.ts +54 -18
- package/src/runtime/locale.ts +19 -0
- package/src/runtime/types.ts +21 -6
- package/src/tools/bundle.ts +131 -0
- package/src/tools/catalog.ts +2 -2
- package/src/tools/compile.ts +60 -23
- package/src/tools/config.ts +57 -0
- package/src/tools/endpoints.ts +121 -0
- package/src/tools/entry.ts +77 -0
- package/src/tools/generate.ts +2 -0
- package/src/tools/icu.ts +63 -7
- package/src/tools/index.ts +6 -3
- package/src/tools/lock.ts +7 -0
- package/src/tools/mf1.ts +21 -7
- package/src/tools/mf2.ts +249 -57
- package/src/tools/migrate.ts +3 -2
- package/src/tools/native/android.ts +201 -0
- package/src/tools/native/apple.ts +139 -0
- package/src/tools/native/emit.ts +87 -0
- package/src/tools/native/index.ts +48 -0
- package/src/tools/native/kotlin.ts +269 -0
- package/src/tools/native/model.ts +292 -0
- package/src/tools/native/number.ts +210 -0
- package/src/tools/native/plurals.ts +103 -0
- package/src/tools/native/printf.ts +95 -0
- package/src/tools/native/rust.ts +367 -0
- package/src/tools/native/swift.ts +326 -0
- package/src/tools/syntax.ts +3 -3
- package/src/tools/todo.ts +118 -0
- package/src/vite.ts +28 -114
- package/dist/config-D--RITk2.js.map +0 -1
- package/dist/config-_4HfnJQu.d.ts.map +0 -1
- package/dist/copy-DXnzXsJa.d.ts.map +0 -1
- package/dist/helpers-BPuPGjQP.js +0 -61
- package/dist/helpers-BPuPGjQP.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sarif-CI8HOokm.js.map +0 -1
- package/dist/scan-Ch2BfIUD.js +0 -2561
- package/dist/scan-Ch2BfIUD.js.map +0 -1
package/dist/tools.d.ts
CHANGED
|
@@ -1,18 +1,61 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import "
|
|
3
|
-
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { $ as loadCatalog, A as RuleContext, B as DEFAULT_KINDS, C as Finding, D as Readings, E as Plugin, F as lint, G as Catalog, H as KindRule, I as phrase, J as KEY_PATTERN, K as CatalogError, L as sentences, M as applyEdits, N as compileRule, O as Report, P as definePlugin, Q as entryLocation, R as singularOf, S as ExistenceRule, T as OccurrenceRule, U as STRICT_LISTS, V as DEFAULT_SURFACES, W as Severity, X as Problem, Y as Message, Z as Translation, _ as CapitalizationRule, a as ResolvedConfig, b as CoreRule, c as loadConfig, d as Mf2Function, et as previewOf, f as intlDate, g as CORE_RULES, h as toMf2, i as ProsebookConfig, j as SubstitutionRule, k as Rule, l as resolveConfig, m as parseMf2, nt as MessageFormatElement, p as intlNumber, q as EntryLocation, r as NativeTarget, s as defineConfig, tt as whereOf, u as Isolate, v as CheckRule, w as HOLE, x as Edit, y as ConditionalRule, z as words } from "./config-CzaJJQ60.js";
|
|
2
|
+
import { a as localeModule, i as localeExpression, n as Entry, r as compileLocale, t as CompiledLocale } from "./compile-ClK9htKr.js";
|
|
3
|
+
export declare function sourceFiles(dirs: string[], extensions: string[], skip?: string[]): string[];
|
|
4
|
+
interface KeyRefs {
|
|
5
|
+
exact: Set<string>;
|
|
6
|
+
/** `nav.` from `nav.${id}` or `'nav.' + id`: every key under it may be read. */
|
|
7
|
+
prefixes: Set<string>;
|
|
8
|
+
}
|
|
9
|
+
export declare function keyRefs(code: string, into?: KeyRefs): KeyRefs;
|
|
10
|
+
export declare function isReferenced(key: string, refs: KeyRefs): boolean;
|
|
11
|
+
interface Hardcoded {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
text: string;
|
|
16
|
+
/** Where it sits: `text`, or the attribute it is the value of. */
|
|
17
|
+
where: string;
|
|
9
18
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
19
|
+
/**
|
|
20
|
+
* Text that reads as code rather than prose: one token holding a path, a call, an identifier with an
|
|
21
|
+
* underscore or a dotted name, as in `form.json()` or `src/index.ts`. Trailing punctuation doesn't
|
|
22
|
+
* count, so `Done.` is still prose.
|
|
23
|
+
*/
|
|
24
|
+
export declare function looksLikeCode(text: string): boolean;
|
|
25
|
+
/** Literal text in JSX that a person reads, where it should be a catalog entry. */
|
|
26
|
+
export declare function hardcodedText(file: string, code: string, attributes: string[]): Hardcoded[];
|
|
27
|
+
interface ScanResult {
|
|
28
|
+
hardcoded: Hardcoded[];
|
|
29
|
+
refs: KeyRefs;
|
|
30
|
+
files: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function scan(dirs: string[], extensions: string[], attributes: string[], root: string, skip?: string[]): ScanResult;
|
|
33
|
+
/** A copy's methods that take a key first. */
|
|
34
|
+
export declare const READERS: Set<string>;
|
|
35
|
+
interface UnseenRead {
|
|
36
|
+
file: string;
|
|
37
|
+
line: number;
|
|
38
|
+
column: number;
|
|
39
|
+
/** The read as written, on one line. */
|
|
40
|
+
code: string;
|
|
41
|
+
/** What it calls: a reader, the name it was imported by, or `Text`. */
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
interface Reads {
|
|
45
|
+
/** Reads whose key a build can't see, in calls that may be to a copy's `t` or `rich`. */
|
|
46
|
+
unseen: UnseenRead[];
|
|
47
|
+
/** Names a copy's readers are bound to here, as in `const { rich: tRich } = copy`. */
|
|
48
|
+
aliases: Set<string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The reads in a module whose key a pruned build can't see, as in `t(item.key)` or `<Text id={id} />`:
|
|
52
|
+
* the build would leave the message out, and the key would show in its place. A build has no types,
|
|
53
|
+
* so readers are known by name: `t` and `rich` called alone or as methods, and any name imported here,
|
|
54
|
+
* which may be bound to one elsewhere (`aliases`); the caller keeps those whose name turns out to be a
|
|
55
|
+
* reader. `<Text>` counts where it is imported from `prosebook/react`. Lines marked `prosebook-ignore`
|
|
56
|
+
* are skipped.
|
|
57
|
+
*/
|
|
58
|
+
export declare function unseenReads(file: string, code: string): Reads;
|
|
16
59
|
interface Extraction {
|
|
17
60
|
file: string;
|
|
18
61
|
line: number;
|
|
@@ -143,6 +186,21 @@ export declare function applyFixes(config: ResolvedConfig, catalog: Catalog, fin
|
|
|
143
186
|
fixed: number;
|
|
144
187
|
problems: Problem[];
|
|
145
188
|
};
|
|
189
|
+
interface Imported {
|
|
190
|
+
/** Keys whose translation was written. */
|
|
191
|
+
written: string[];
|
|
192
|
+
/** Keys given back as they were: each still holds, so a stale one can be accepted. */
|
|
193
|
+
unchanged: string[];
|
|
194
|
+
/** What was left out, and why. The rest was written regardless. */
|
|
195
|
+
problems: Problem[];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Translations into `locale` from `prosebook todo`'s YAML filled in, by a person or a model, each
|
|
199
|
+
* written to the file its locale and area put it in. Text around a fenced block is left aside, as a
|
|
200
|
+
* model's reply has it, and an empty value is one left for later. An entry that would break the
|
|
201
|
+
* catalog is left out with its problem, so a reply with one bad entry still lands the others.
|
|
202
|
+
*/
|
|
203
|
+
export declare function importTranslations(config: ResolvedConfig, locale: string, reply: string): Imported;
|
|
146
204
|
interface Lock {
|
|
147
205
|
version: 2;
|
|
148
206
|
/** Source text by hash: every text some translation is anchored to. */
|
|
@@ -163,6 +221,8 @@ interface StaleTranslation {
|
|
|
163
221
|
/** The translation, made from `was`. */
|
|
164
222
|
translation: string;
|
|
165
223
|
}
|
|
224
|
+
/** What changed in the source of a stale translation, as a word diff, or why that isn't known. */
|
|
225
|
+
export declare function changeOf(stale: StaleTranslation): string;
|
|
166
226
|
interface Reconciled {
|
|
167
227
|
lock: Lock;
|
|
168
228
|
/** Per locale, per key: the translations made from a source that has changed since. */
|
|
@@ -211,24 +271,6 @@ export declare function wordDiff(before: string, after: string): DiffPart[];
|
|
|
211
271
|
export declare function formatDiff(parts: DiffPart[]): string;
|
|
212
272
|
export declare function pseudoText(text: string): string;
|
|
213
273
|
export declare function pseudoAst(ast: MessageFormatElement[]): MessageFormatElement[];
|
|
214
|
-
type Element$1 = MessageFormatElement;
|
|
215
|
-
/** A function as written: `:number minimumFractionDigits=2`. Kept on the styles it produces, to write back. */
|
|
216
|
-
interface Mf2Function {
|
|
217
|
-
name: string;
|
|
218
|
-
options: Record<string, string>;
|
|
219
|
-
}
|
|
220
|
-
/** An MF2 message as the element tree. Throws, with the fix, where it can't be read or compiled. */
|
|
221
|
-
export declare function parseMf2(text: string, locale: string): Element$1[];
|
|
222
|
-
/** The `Intl.NumberFormat` options a numeric function formats with. */
|
|
223
|
-
export declare function intlNumber(fn: Mf2Function): Intl.NumberFormatOptions;
|
|
224
|
-
/**
|
|
225
|
-
* The `Intl.DateTimeFormat` options a date or time function formats with. MF2's date and time
|
|
226
|
-
* functions are still a draft; this follows the spec's semantic options as the `messageformat`
|
|
227
|
-
* package does, so a message reads the same in both.
|
|
228
|
-
*/
|
|
229
|
-
export declare function intlDate(fn: Mf2Function): Intl.DateTimeFormatOptions;
|
|
230
|
-
/** A message tree as MF2 text. Throws, saying what has no MF2 equivalent, where it can't be written. */
|
|
231
|
-
export declare function toMf2(ast: Element$1[], locale: string): string;
|
|
232
274
|
type Element = MessageFormatElement;
|
|
233
275
|
export declare function toMf1(ast: Element[]): string;
|
|
234
276
|
type MessageFormatVersion = 1 | 2;
|
|
@@ -237,7 +279,7 @@ export declare const SYNTAX_NAME: Record<MessageFormatVersion, string>;
|
|
|
237
279
|
* A message as the element tree. MF2 is read per locale: which variants a value can reach depends
|
|
238
280
|
* on the locale's plural rules (see `matchTree` in mf2.ts).
|
|
239
281
|
*/
|
|
240
|
-
export declare function readMessage(version: MessageFormatVersion, text: string, locale: string): MessageFormatElement[];
|
|
282
|
+
export declare function readMessage(version: MessageFormatVersion, text: string, locale: string, isolate?: Isolate): MessageFormatElement[];
|
|
241
283
|
/** A tree as text in the given syntax. Throws, saying what the syntax can't express, where it can't. */
|
|
242
284
|
export declare function writeMessage(version: MessageFormatVersion, ast: MessageFormatElement[], locale: string): string;
|
|
243
285
|
interface Located {
|
|
@@ -292,6 +334,13 @@ interface TypeOracle {
|
|
|
292
334
|
export declare function typescriptOracle(root: string, files: readonly string[]): Promise<TypeOracle | undefined>;
|
|
293
335
|
/** The kind an expression's syntax gives away, where it does: `items.length`, `a * b`, `new Date()`. */
|
|
294
336
|
export declare function syntaxKind(expr: Node): ValueKind | undefined;
|
|
337
|
+
interface Todo {
|
|
338
|
+
/** How many entries want a translation. */
|
|
339
|
+
count: number;
|
|
340
|
+
/** The entries as YAML, by area, with the prompt first when one was asked for. */
|
|
341
|
+
text: string;
|
|
342
|
+
}
|
|
343
|
+
export declare function todoOf(config: ResolvedConfig, catalog: Catalog, stale: ReadonlyMap<string, StaleTranslation>, locale: string, prompt?: boolean): Todo;
|
|
295
344
|
interface SarifResult {
|
|
296
345
|
/** A rule the report describes, or one of a lint plugin's. */
|
|
297
346
|
rule: string;
|
|
@@ -317,61 +366,5 @@ export declare class SarifReport {
|
|
|
317
366
|
add(result: SarifResult): void;
|
|
318
367
|
toJSON(root: string, tool: SarifTool): object;
|
|
319
368
|
}
|
|
320
|
-
export
|
|
321
|
-
interface KeyRefs {
|
|
322
|
-
exact: Set<string>;
|
|
323
|
-
/** `nav.` from `nav.${id}` or `'nav.' + id`: every key under it may be read. */
|
|
324
|
-
prefixes: Set<string>;
|
|
325
|
-
}
|
|
326
|
-
export declare function keyRefs(code: string, into?: KeyRefs): KeyRefs;
|
|
327
|
-
export declare function isReferenced(key: string, refs: KeyRefs): boolean;
|
|
328
|
-
interface Hardcoded {
|
|
329
|
-
file: string;
|
|
330
|
-
line: number;
|
|
331
|
-
column: number;
|
|
332
|
-
text: string;
|
|
333
|
-
/** Where it sits: `text`, or the attribute it is the value of. */
|
|
334
|
-
where: string;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Text that reads as code rather than prose: one token holding a path, a call, an identifier with an
|
|
338
|
-
* underscore or a dotted name, as in `form.json()` or `src/index.ts`. Trailing punctuation doesn't
|
|
339
|
-
* count, so `Done.` is still prose.
|
|
340
|
-
*/
|
|
341
|
-
export declare function looksLikeCode(text: string): boolean;
|
|
342
|
-
/** Literal text in JSX that a person reads, where it should be a catalog entry. */
|
|
343
|
-
export declare function hardcodedText(file: string, code: string, attributes: string[]): Hardcoded[];
|
|
344
|
-
interface ScanResult {
|
|
345
|
-
hardcoded: Hardcoded[];
|
|
346
|
-
refs: KeyRefs;
|
|
347
|
-
files: number;
|
|
348
|
-
}
|
|
349
|
-
export declare function scan(dirs: string[], extensions: string[], attributes: string[], root: string, skip?: string[]): ScanResult;
|
|
350
|
-
/** A copy's methods that take a key first. */
|
|
351
|
-
export declare const READERS: Set<string>;
|
|
352
|
-
interface UnseenRead {
|
|
353
|
-
file: string;
|
|
354
|
-
line: number;
|
|
355
|
-
column: number;
|
|
356
|
-
/** The read as written, on one line. */
|
|
357
|
-
code: string;
|
|
358
|
-
/** What it calls: a reader, the name it was imported by, or `Text`. */
|
|
359
|
-
name: string;
|
|
360
|
-
}
|
|
361
|
-
interface Reads {
|
|
362
|
-
/** Reads whose key a build can't see, in calls that may be to a copy's `t` or `rich`. */
|
|
363
|
-
unseen: UnseenRead[];
|
|
364
|
-
/** Names a copy's readers are bound to here, as in `const { rich: tRich } = copy`. */
|
|
365
|
-
aliases: Set<string>;
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* The reads in a module whose key a pruned build can't see, as in `t(item.key)` or `<Text id={id} />`:
|
|
369
|
-
* the build would leave the message out, and the key would show in its place. A build has no types,
|
|
370
|
-
* so readers are known by name: `t` and `rich` called alone or as methods, and any name imported here,
|
|
371
|
-
* which may be bound to one elsewhere (`aliases`); the caller keeps those whose name turns out to be a
|
|
372
|
-
* reader. `<Text>` counts where it is imported from `prosebook/react`. Lines marked `prosebook-ignore`
|
|
373
|
-
* are skipped.
|
|
374
|
-
*/
|
|
375
|
-
export declare function unseenReads(file: string, code: string): Reads;
|
|
376
|
-
export { CORE_RULES, type CapitalizationRule, type Catalog, CatalogError, type CheckRule, type CompiledLocale, type ConditionalRule, type CoreRule, DEFAULT_KINDS, DEFAULT_SURFACES, type DiffPart, type Edit, type Entry, type EntryEdit, type EntryFields, type EntryFinding, type EntryLocation, type EntryView, type EntryYaml, type ExistenceRule, type ExtractPlan, type Extraction, type FilePlan, type Finding, type Generated, HOLE, type Hardcoded, KEY_PATTERN, type KeyRefs, type KindRule, type Lock, type Message, type MessageFormatVersion, type Mf2Function, type MigratePlan, type NewEntry, type OccurrenceRule, type Plugin, type Problem, type ProsebookConfig, type Readings, type Reads, type Report, type ResolvedConfig, type Rule, type RuleContext, STRICT_LISTS, type SarifResult, type SarifTool, type Severity, type Span, type StaleTranslation, type SubstitutionRule, type Translation, type TypeOracle, type UnseenRead, type ValueKind, applyEdits, compileRule, defineConfig, definePlugin, entryLocation, lint, loadCatalog, loadConfig, phrase, previewOf, resolveConfig, sentences, singularOf, whereOf, words };
|
|
369
|
+
export { CORE_RULES, type CapitalizationRule, type Catalog, CatalogError, type CheckRule, type CompiledLocale, type ConditionalRule, type CoreRule, DEFAULT_KINDS, DEFAULT_SURFACES, type DiffPart, type Edit, type Entry, type EntryEdit, type EntryFields, type EntryFinding, type EntryLocation, type EntryView, type EntryYaml, type ExistenceRule, type ExtractPlan, type Extraction, type FilePlan, type Finding, type Generated, HOLE, type Hardcoded, type Imported, type Isolate, KEY_PATTERN, type KeyRefs, type KindRule, type Lock, type Message, type MessageFormatVersion, type Mf2Function, type MigratePlan, type NativeTarget, type NewEntry, type OccurrenceRule, type Plugin, type Problem, type ProsebookConfig, type Readings, type Reads, type Report, type ResolvedConfig, type Rule, type RuleContext, STRICT_LISTS, type SarifResult, type SarifTool, type Severity, type Span, type StaleTranslation, type SubstitutionRule, type Todo, type Translation, type TypeOracle, type UnseenRead, type ValueKind, applyEdits, compileLocale, compileRule, defineConfig, definePlugin, entryLocation, intlDate, intlNumber, lint, loadCatalog, loadConfig, localeExpression, localeModule, parseMf2, phrase, previewOf, resolveConfig, sentences, singularOf, toMf2, whereOf, words };
|
|
377
370
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","names":["Edit"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","names":["Edit"],"sources":["../src/tools/scan.ts","../src/tools/extract.ts","../src/tools/entry.ts","../src/tools/lock.ts","../src/tools/generate.ts","../src/tools/diff.ts","../src/tools/pseudo.ts","../src/tools/mf1.ts","../src/tools/syntax.ts","../src/tools/migrate.ts","../src/tools/types.ts","../src/tools/todo.ts","../src/tools/sarif.ts"],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { A as writeMessage, C as entryLocation, D as whereOf, E as previewOf, F as toMf1, M as intlNumber, N as parseMf2, O as SYNTAX_NAME, P as toMf2, S as KEY_PATTERN, T as loadCatalog, _ as applyFixes, a as render, b as readEntry, c as pseudoText, d as hash, f as readLock, g as wordDiff, h as formatDiff, i as localesOf, j as intlDate, k as readMessage, l as changeOf, m as serializeLock, n as generate, o as writeFiles, p as reconcile, r as localeEntries, s as pseudoAst, t as catalogSchema, u as emptyLock, v as editEntry, x as CatalogError, y as importTranslations } from "./generate-BW3N8EGb.js";
|
|
2
|
+
import { a as looksLikeCode, c as unseenReads, d as localeModule, i as keyRefs, l as compileLocale, n as hardcodedText, o as scan, r as isReferenced, s as sourceFiles, t as READERS, u as localeExpression } from "./scan-Bx0_yDSi.js";
|
|
3
|
+
import { a as applyExtract, c as planExtract, d as typescriptOracle, i as planMigrate, l as slugOf, n as todoOf, o as areaOf, r as applyMigrate, s as draftPlurals, t as SarifReport, u as syntaxKind } from "./sarif-B5nrMqrT.js";
|
|
4
|
+
import { _ as words, a as DEFAULT_KINDS, c as CORE_RULES, d as compileRule, f as definePlugin, g as singularOf, h as sentences, i as resolveConfig, l as HOLE, m as phrase, n as defineConfig, o as DEFAULT_SURFACES, p as lint, r as loadConfig, s as STRICT_LISTS, u as applyEdits } from "./config-nWZf9QmF.js";
|
|
5
|
+
export { CORE_RULES, CatalogError, DEFAULT_KINDS, DEFAULT_SURFACES, HOLE, KEY_PATTERN, READERS, STRICT_LISTS, SYNTAX_NAME, SarifReport, applyEdits, applyExtract, applyFixes, applyMigrate, areaOf, catalogSchema, changeOf, compileLocale, compileRule, defineConfig, definePlugin, draftPlurals, editEntry, emptyLock, entryLocation, formatDiff, generate, hardcodedText, hash, importTranslations, intlDate, intlNumber, isReferenced, keyRefs, lint, loadCatalog, loadConfig, localeEntries, localeExpression, localeModule, localesOf, looksLikeCode, parseMf2, phrase, planExtract, planMigrate, previewOf, pseudoAst, pseudoText, readEntry, readLock, readMessage, reconcile, render, resolveConfig, scan, sentences, serializeLock, singularOf, slugOf, sourceFiles, syntaxKind, toMf1, toMf2, todoOf, typescriptOracle, unseenReads, whereOf, wordDiff, words, writeFiles, writeMessage };
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as Prune } from "./bundle-CpUIWb9j.js";
|
|
2
|
+
import { i as ProsebookConfig } from "./config-CzaJJQ60.js";
|
|
2
3
|
import { Plugin } from "vite";
|
|
3
4
|
export interface ProsebookPluginOptions {
|
|
4
5
|
/** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
|
|
@@ -13,7 +14,7 @@ export interface ProsebookPluginOptions {
|
|
|
13
14
|
* whose key it can't see, such as `t(item.key)`, which would show the key where its message was
|
|
14
15
|
* pruned. `loose` prunes without that check; `false` ships every message.
|
|
15
16
|
*/
|
|
16
|
-
prune?:
|
|
17
|
+
prune?: Prune;
|
|
17
18
|
/** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */
|
|
18
19
|
keep?: string[];
|
|
19
20
|
/** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","names":[],"sources":["../src/vite.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vite.d.ts","names":[],"sources":["../src/vite.ts"],"mappings":";;;iBA8BiB;;EAEf,SAAS;;;;;EAKT;;;;;;EAMA,QAAQ;;EAER;;EAEA;;wBAcc,UAAU,UAAS,yBAA8B"}
|
package/dist/vite.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as installInspector, d as unseenOf, i as hotLocale, n as HOT_SET, o as keeper, r as beside, t as HOT_INDEX, u as unseenMessage } from "./bundle-DboItAre.js";
|
|
2
|
+
import { n as generate, x as CatalogError } from "./generate-BW3N8EGb.js";
|
|
3
|
+
import { c as unseenReads, i as keyRefs, l as compileLocale, u as localeExpression } from "./scan-Bx0_yDSi.js";
|
|
4
|
+
import { r as loadConfig } from "./config-nWZf9QmF.js";
|
|
5
|
+
import { n as nodeMiddleware, r as redirect, t as inspectorEndpoints } from "./endpoints-BgG_5Vpm.js";
|
|
3
6
|
import { relative, resolve } from "node:path";
|
|
4
7
|
import { fileURLToPath } from "node:url";
|
|
5
8
|
// src/vite.ts
|
|
@@ -14,31 +17,18 @@ import { fileURLToPath } from "node:url";
|
|
|
14
17
|
* In dev an edit doesn't reload the page: each generated locale module accepts its own update and
|
|
15
18
|
* hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.
|
|
16
19
|
* The index accepts its own too, since its doc comments change with the source text, and reloads the
|
|
17
|
-
* page only when a locale was added or removed.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* page only when a locale was added or removed. In the browser the index imports the dev module first,
|
|
21
|
+
* which sets up the hooks and the inspector (`inspector.ts`) before any copy exists, so frameworks that
|
|
22
|
+
* render their own HTML (TanStack Start, React Router) get them too. The server opens the entry the
|
|
23
|
+
* inspector asks for through Vite's own `/__open-in-editor`, so the editor is whichever Vite already
|
|
24
|
+
* launches.
|
|
20
25
|
*/
|
|
21
26
|
const PLACEHOLDER = /__PROSEBOOK_LOCALE_(\d+)__/g;
|
|
22
27
|
const SCRIPT = /\.[cm]?[jt]sx?$/;
|
|
28
|
+
const DEV = "virtual:prosebook/dev";
|
|
23
29
|
const DEV_ID = "\0prosebook/dev";
|
|
24
|
-
const hotLocale = (locale) => `
|
|
25
|
-
if (import.meta.hot) {
|
|
26
|
-
import.meta.hot.accept((next) => {
|
|
27
|
-
const hot = globalThis.__PROSEBOOK_HOT__;
|
|
28
|
-
if (next === undefined || hot === undefined) return import.meta.hot.invalidate();
|
|
29
|
-
for (const update of hot) update(${JSON.stringify(locale)}, next.default);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
`;
|
|
33
|
-
const HOT_INDEX = `
|
|
34
|
-
if (import.meta.hot) {
|
|
35
|
-
import.meta.hot.accept((next) => {
|
|
36
|
-
if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
`;
|
|
40
30
|
const normalize = (path) => resolve(path).replaceAll("\\", "/");
|
|
41
|
-
const inspectorPath =
|
|
31
|
+
const inspectorPath = beside(import.meta.url, "inspector");
|
|
42
32
|
const ownDir = normalize(fileURLToPath(new URL(".", import.meta.url)));
|
|
43
33
|
function prosebook(options = {}) {
|
|
44
34
|
let config;
|
|
@@ -46,6 +36,7 @@ function prosebook(options = {}) {
|
|
|
46
36
|
let building = false;
|
|
47
37
|
let base = "/";
|
|
48
38
|
let viteRoot = "";
|
|
39
|
+
let kept = () => true;
|
|
49
40
|
let refs = {
|
|
50
41
|
exact: /* @__PURE__ */ new Set(),
|
|
51
42
|
prefixes: /* @__PURE__ */ new Set()
|
|
@@ -70,7 +61,6 @@ function prosebook(options = {}) {
|
|
|
70
61
|
const out = normalize(config.out);
|
|
71
62
|
return path === `${out}/index.ts` || path === `${out}/index.js`;
|
|
72
63
|
};
|
|
73
|
-
const kept = (key) => isReferenced(key, refs) || (options.keep ?? []).some((k) => k.endsWith(".*") ? key.startsWith(k.slice(0, -1)) : k === key);
|
|
74
64
|
return {
|
|
75
65
|
name: "prosebook",
|
|
76
66
|
async configResolved(resolved) {
|
|
@@ -79,19 +69,8 @@ function prosebook(options = {}) {
|
|
|
79
69
|
viteRoot = resolved.root;
|
|
80
70
|
config = await loadConfig(options.root === void 0 ? resolved.root : resolve(resolved.root, options.root), options.config);
|
|
81
71
|
},
|
|
82
|
-
transformIndexHtml() {
|
|
83
|
-
if (building) return [];
|
|
84
|
-
return [{
|
|
85
|
-
tag: "script",
|
|
86
|
-
attrs: {
|
|
87
|
-
type: "module",
|
|
88
|
-
src: `${base}@id/__x00__prosebook/dev`
|
|
89
|
-
},
|
|
90
|
-
injectTo: "head-prepend"
|
|
91
|
-
}];
|
|
92
|
-
},
|
|
93
72
|
resolveId(id) {
|
|
94
|
-
return id ===
|
|
73
|
+
return id === DEV ? DEV_ID : null;
|
|
95
74
|
},
|
|
96
75
|
buildStart() {
|
|
97
76
|
try {
|
|
@@ -103,6 +82,7 @@ function prosebook(options = {}) {
|
|
|
103
82
|
exact: /* @__PURE__ */ new Set(),
|
|
104
83
|
prefixes: /* @__PURE__ */ new Set()
|
|
105
84
|
};
|
|
85
|
+
kept = keeper(refs, options.keep);
|
|
106
86
|
reads.clear();
|
|
107
87
|
placeholders.length = 0;
|
|
108
88
|
shipped.total = shipped.kept = 0;
|
|
@@ -132,69 +112,16 @@ function prosebook(options = {}) {
|
|
|
132
112
|
server.watcher.on("add", onChange);
|
|
133
113
|
server.watcher.on("unlink", onChange);
|
|
134
114
|
if (options.inspect === false) return;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (message === void 0) {
|
|
141
|
-
res.statusCode = 404;
|
|
142
|
-
res.end(`no entry for "${key}" in the catalog: has it been renamed since the page loaded?`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const locale = url.searchParams.has("catalog") ? void 0 : url.searchParams.get("locale") ?? void 0;
|
|
146
|
-
const { file, line, column } = entryLocation(config, message, locale);
|
|
147
|
-
req.url = `${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`;
|
|
148
|
-
next();
|
|
149
|
-
});
|
|
150
|
-
server.middlewares.use((req, res, next) => {
|
|
151
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
152
|
-
if (url.pathname !== `${base}__prosebook/entry`) return next();
|
|
153
|
-
const reply = (status, body) => {
|
|
154
|
-
res.statusCode = status;
|
|
155
|
-
res.setHeader("Content-Type", "application/json");
|
|
156
|
-
res.end(JSON.stringify(body));
|
|
157
|
-
};
|
|
158
|
-
const fail = (e) => reply(e instanceof CatalogError ? 409 : 400, { problems: e instanceof CatalogError ? e.problems : [{
|
|
159
|
-
severity: "error",
|
|
160
|
-
message: e instanceof Error ? e.message : String(e)
|
|
161
|
-
}] });
|
|
162
|
-
if (req.method === "GET") {
|
|
163
|
-
try {
|
|
164
|
-
reply(200, readEntry(config, url.searchParams.get("key") ?? ""));
|
|
165
|
-
} catch (e) {
|
|
166
|
-
fail(e);
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const origin = req.headers.origin;
|
|
171
|
-
const sameOrigin = origin === void 0 || new URL(origin).host === req.headers.host;
|
|
172
|
-
if (req.method !== "POST" || !sameOrigin || req.headers["content-type"]?.startsWith("application/json") !== true) {
|
|
173
|
-
reply(403, { problems: [{
|
|
174
|
-
severity: "error",
|
|
175
|
-
message: "edits come from the page, as JSON"
|
|
176
|
-
}] });
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
let body = "";
|
|
180
|
-
req.setEncoding("utf8");
|
|
181
|
-
req.on("data", (chunk) => body += chunk);
|
|
182
|
-
req.on("end", () => {
|
|
183
|
-
try {
|
|
184
|
-
const { key, edit, write } = JSON.parse(body);
|
|
185
|
-
const result = editEntry(config, key, edit, write === true);
|
|
186
|
-
reply(result.problems.length > 0 ? 422 : 200, result);
|
|
187
|
-
} catch (e) {
|
|
188
|
-
fail(e);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
});
|
|
115
|
+
const editor = ({ file, line, column }) => redirect(`${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`);
|
|
116
|
+
server.middlewares.use(nodeMiddleware(inspectorEndpoints(config, {
|
|
117
|
+
base,
|
|
118
|
+
open: editor
|
|
119
|
+
})));
|
|
192
120
|
},
|
|
193
121
|
load(id) {
|
|
194
122
|
if (id === DEV_ID) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return `import { install } from ${JSON.stringify(inspectorPath)};\n${hot}install(${JSON.stringify({ base })});\n`;
|
|
123
|
+
if (options.inspect === false) return HOT_SET;
|
|
124
|
+
return HOT_SET + installInspector(inspectorPath, base);
|
|
198
125
|
}
|
|
199
126
|
if (!building || prune === false || generated === void 0) return null;
|
|
200
127
|
const locale = localeOf(id);
|
|
@@ -204,17 +131,18 @@ function prosebook(options = {}) {
|
|
|
204
131
|
},
|
|
205
132
|
transform: {
|
|
206
133
|
order: "pre",
|
|
207
|
-
handler(code, id) {
|
|
134
|
+
handler(code, id, transformOptions) {
|
|
208
135
|
if (!building) {
|
|
209
136
|
const locale = localeOf(id);
|
|
210
137
|
if (locale !== void 0) return {
|
|
211
138
|
code: code + hotLocale(locale),
|
|
212
139
|
map: null
|
|
213
140
|
};
|
|
214
|
-
|
|
215
|
-
|
|
141
|
+
if (!isIndex(id)) return null;
|
|
142
|
+
return {
|
|
143
|
+
code: (transformOptions?.ssr === true ? "" : `import ${JSON.stringify(DEV)};\n`) + code + HOT_INDEX,
|
|
216
144
|
map: null
|
|
217
|
-
}
|
|
145
|
+
};
|
|
218
146
|
}
|
|
219
147
|
if (prune === false || id.includes("/node_modules/") || id.startsWith("\0")) return null;
|
|
220
148
|
if (localeOf(id) !== void 0 || isIndex(id)) return null;
|
|
@@ -227,16 +155,8 @@ function prosebook(options = {}) {
|
|
|
227
155
|
buildEnd(error) {
|
|
228
156
|
if (error !== void 0 || prune !== "strict") return;
|
|
229
157
|
const live = new Set(this.getModuleIds());
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
const unseen = all.flatMap((r) => r.unseen).filter((u) => u.name === "Text" || readers.has(u.name));
|
|
233
|
-
if (unseen.length === 0) return;
|
|
234
|
-
const at = (file) => relative(viteRoot, file).replaceAll("\\", "/");
|
|
235
|
-
this.error([
|
|
236
|
-
`${unseen.length} read(s) whose key the build can't see, so their messages would be pruned and the key shown instead:`,
|
|
237
|
-
...unseen.map((u) => ` ${at(u.file)}:${u.line}:${u.column} ${u.code}`),
|
|
238
|
-
"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."
|
|
239
|
-
].join("\n"));
|
|
158
|
+
const unseen = unseenOf([...reads].filter(([id]) => live.has(id)).map(([, r]) => r));
|
|
159
|
+
if (unseen.length > 0) this.error(unseenMessage(unseen, viteRoot));
|
|
240
160
|
},
|
|
241
161
|
renderChunk(code) {
|
|
242
162
|
if (!code.includes("__PROSEBOOK_LOCALE_") || generated === void 0) return null;
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * `prosebook/vite`: the catalog as part of the build.\n *\n * Generates on start and again whenever a catalog or translation file changes, so dev never runs on\n * stale modules and a broken entry shows in the error overlay. A build ships only the messages the\n * code names: each locale module is swapped for its messages less the keys no module references\n * (see `tools/scan.ts` for how a reference is found, and why it errs towards keeping).\n *\n * In dev an edit doesn't reload the page: each generated locale module accepts its own update and\n * hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.\n * The index accepts its own too, since its doc comments change with the source text, and reloads the\n * page only when a locale was added or removed. The page also gets the inspector (`inspector.ts`), and\n * the server opens the entry it asks for through Vite's own `/__open-in-editor`, so the editor is\n * whichever Vite already launches.\n */\nimport { relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Plugin } from 'vite';\n\nimport { CatalogError, entryLocation } from './tools/catalog.ts';\nimport { compileLocale, localeExpression } from './tools/compile.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { editEntry, readEntry, type EntryEdit } from './tools/entry.ts';\nimport { generate, type Generated } from './tools/generate.ts';\nimport { isReferenced, keyRefs, READERS, unseenReads, type KeyRefs, type Reads } from './tools/scan.ts';\n\nexport interface ProsebookPluginOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /**\n * The folder the config's paths resolve from, and where its file is found: the Vite root when\n * unset. Apps that share one catalog name its folder, so each build generates the same files.\n */\n root?: string;\n /**\n * Ship only the messages the code names. `strict`, the default, also fails the build on a read\n * whose key it can't see, such as `t(item.key)`, which would show the key where its message was\n * pruned. `loose` prunes without that check; `false` ships every message.\n */\n prune?: 'strict' | 'loose' | false;\n /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */\n keep?: string[];\n /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */\n inspect?: boolean;\n}\n\nconst PLACEHOLDER = /__PROSEBOOK_LOCALE_(\\d+)__/g;\nconst SCRIPT = /\\.[cm]?[jt]sx?$/;\nconst DEV_ID = '\\0prosebook/dev';\n\nconst 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\nconst 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\nconst normalize = (path: string) => resolve(path).replaceAll('\\\\', '/');\n\n// Beside this module: the source when run from source, the built file in dist.\nconst inspectorPath = normalize(fileURLToPath(new URL(`./inspector${import.meta.url.endsWith('.ts') ? '.ts' : '.js'}`, import.meta.url)));\n// prosebook's own modules, which read keys handed to them: `<Text>` calls `rich(props.id)`.\nconst ownDir = normalize(fileURLToPath(new URL('.', import.meta.url)));\n\nexport function prosebook(options: ProsebookPluginOptions = {}): Plugin {\n let config: ResolvedConfig;\n let generated: Generated | undefined;\n let building = false;\n let base = '/';\n let viteRoot = '';\n let refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const reads = new Map<string, Reads>();\n const prune = options.prune ?? 'strict';\n const placeholders: string[] = [];\n const shipped = { total: 0, kept: 0 };\n\n const describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\n\n const localeOf = (id: string): string | undefined => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n if (!path.startsWith(`${out}/`)) return undefined;\n const name = path.slice(out.length + 1).replace(/\\.[jt]s$/, '');\n return generated?.entries.has(name) === true ? name : undefined;\n };\n\n const isIndex = (id: string) => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n return path === `${out}/index.ts` || path === `${out}/index.js`;\n };\n\n const kept = (key: string) =>\n isReferenced(key, refs) || (options.keep ?? []).some((k) => (k.endsWith('.*') ? key.startsWith(k.slice(0, -1)) : k === key));\n\n return {\n name: 'prosebook',\n\n async configResolved(resolved) {\n building = resolved.command === 'build';\n base = resolved.base;\n viteRoot = resolved.root;\n config = await loadConfig(options.root === undefined ? resolved.root : resolve(resolved.root, options.root), options.config);\n },\n\n // First in <head>, so the hot hooks are there before any copy is created.\n transformIndexHtml() {\n if (building) return [];\n return [{ tag: 'script', attrs: { type: 'module', src: `${base}@id/__x00__prosebook/dev` }, injectTo: 'head-prepend' }];\n },\n\n resolveId(id) {\n return id === DEV_ID ? id : null;\n },\n\n buildStart() {\n try {\n generated = generate(config);\n } catch (e) {\n this.error(describe(e));\n }\n refs = { exact: new Set(), prefixes: new Set() };\n reads.clear();\n placeholders.length = 0;\n shipped.total = shipped.kept = 0;\n },\n\n configureServer(server) {\n const dirs = [normalize(config.catalog), normalize(config.locales)];\n server.watcher.add(dirs);\n const onChange = (file: string) => {\n const path = normalize(file);\n if (!path.endsWith('.yaml') || !dirs.some((d) => path.startsWith(`${d}/`))) return;\n try {\n generated = generate(config);\n server.config.logger.info(`prosebook: regenerated from ${relative(config.root, path).replaceAll('\\\\', '/')}`, {\n timestamp: true,\n });\n } catch (e) {\n server.config.logger.error(describe(e), { timestamp: true });\n server.ws.send({ type: 'error', err: { message: describe(e), stack: '', plugin: 'prosebook' } });\n }\n };\n server.watcher.on('change', onChange);\n server.watcher.on('add', onChange);\n server.watcher.on('unlink', onChange);\n if (options.inspect === false) return;\n\n // `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when\n // the locale has none. Handed on to Vite's `/__open-in-editor`, which comes later in the stack.\n server.middlewares.use((req, res, next) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== `${base}__prosebook/open`) return next();\n const key = url.searchParams.get('key') ?? '';\n const message = generated?.catalog.messages.find((m) => m.key === key);\n if (message === undefined) {\n res.statusCode = 404;\n res.end(`no entry for \"${key}\" in the catalog: has it been renamed since the page loaded?`);\n return;\n }\n const locale = url.searchParams.has('catalog') ? undefined : (url.searchParams.get('locale') ?? undefined);\n const { file, line, column } = entryLocation(config, message, locale);\n req.url = `${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`;\n next();\n });\n\n // The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`\n // applies an edit (`tools/entry.ts`). A POST writes files, so only the page itself may send one:\n // same origin, and JSON, which a form on another site can't send without asking first.\n server.middlewares.use((req, res, next) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== `${base}__prosebook/entry`) return next();\n const reply = (status: number, body: unknown) => {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(body));\n };\n const fail = (e: unknown) =>\n reply(e instanceof CatalogError ? 409 : 400, {\n problems: e instanceof CatalogError ? e.problems : [{ severity: 'error', message: e instanceof Error ? e.message : String(e) }],\n });\n if (req.method === 'GET') {\n try {\n reply(200, readEntry(config, url.searchParams.get('key') ?? ''));\n } catch (e) {\n fail(e);\n }\n return;\n }\n const origin = req.headers.origin;\n const sameOrigin = origin === undefined || new URL(origin).host === req.headers.host;\n if (req.method !== 'POST' || !sameOrigin || req.headers['content-type']?.startsWith('application/json') !== true) {\n reply(403, { problems: [{ severity: 'error', message: 'edits come from the page, as JSON' }] });\n return;\n }\n let body = '';\n req.setEncoding('utf8');\n req.on('data', (chunk: string) => (body += chunk));\n req.on('end', () => {\n try {\n const { key, edit, write } = JSON.parse(body) as { key: string; edit: EntryEdit; write?: boolean };\n const result = editEntry(config, key, edit, write === true);\n reply(result.problems.length > 0 ? 422 : 200, result);\n } catch (e) {\n fail(e);\n }\n });\n });\n },\n\n load(id) {\n if (id === DEV_ID) {\n const hot = 'globalThis.__PROSEBOOK_HOT__ ??= new Set();\\n';\n if (options.inspect === false) return hot;\n return `import { install } from ${JSON.stringify(inspectorPath)};\\n${hot}install(${JSON.stringify({ base })});\\n`;\n }\n if (!building || prune === false || generated === undefined) return null;\n const locale = localeOf(id);\n if (locale === undefined) return null;\n placeholders.push(locale);\n return `export default __PROSEBOOK_LOCALE_${placeholders.length - 1}__;\\n`;\n },\n\n // Before other plugins, so a read is found in the source as written: `<Text id>` still JSX, and\n // the line it is reported at, and its `prosebook-ignore`, where the author wrote them.\n transform: {\n order: 'pre',\n handler(code, id) {\n if (!building) {\n const locale = localeOf(id);\n if (locale !== undefined) return { code: code + hotLocale(locale), map: null };\n return isIndex(id) ? { code: code + HOT_INDEX, map: null } : null;\n }\n if (prune === false || id.includes('/node_modules/') || id.startsWith('\\0')) return null;\n // The index's `Messages` type names every key, and is read here before its types are stripped.\n if (localeOf(id) !== undefined || isIndex(id)) return null;\n keyRefs(code, refs);\n const path = id.split('?', 1)[0]!;\n if (prune === 'strict' && SCRIPT.test(path) && !normalize(path).startsWith(`${ownDir}/`)) reads.set(id, unseenReads(path, code));\n return null;\n },\n },\n\n buildEnd(error) {\n if (error !== undefined || prune !== 'strict') return;\n const live = new Set(this.getModuleIds());\n const all = [...reads].filter(([id]) => live.has(id)).map(([, r]) => r);\n const readers = new Set([...READERS, ...all.flatMap((r) => [...r.aliases])]);\n const unseen = all.flatMap((r) => r.unseen).filter((u) => u.name === 'Text' || readers.has(u.name));\n if (unseen.length === 0) return;\n const at = (file: string) => relative(viteRoot, file).replaceAll('\\\\', '/');\n this.error(\n [\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 },\n\n renderChunk(code) {\n if (!code.includes('__PROSEBOOK_LOCALE_') || generated === undefined) return null;\n const entries = generated.entries;\n const replaced = code.replace(PLACEHOLDER, (_match, index: string) => {\n const locale = placeholders[Number(index)]!;\n const list = entries.get(locale) ?? [];\n const compiled = compileLocale(list, kept);\n shipped.total += list.length;\n shipped.kept += compiled.rows.length;\n return localeExpression(compiled);\n });\n return { code: replaced, map: null };\n },\n\n generateBundle() {\n if (shipped.total === 0) return;\n this.info(\n `shipped ${shipped.kept} of ${shipped.total} messages across ${placeholders.length} locale(s); pruned ${shipped.total - shipped.kept} no code names`,\n );\n },\n };\n}\n\nexport default prosebook;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+CA,MAAM,cAAc;AACpB,MAAM,SAAS;AACf,MAAM,SAAS;AAEf,MAAM,aAAa,WAAmB;;;;;uCAKC,KAAK,UAAU,MAAM,EAAE;;;;AAK9D,MAAM,YAAY;;;;;;;AAQlB,MAAM,aAAa,SAAiB,QAAQ,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;AAGtE,MAAM,gBAAgB,UAAU,cAAc,IAAI,IAAI,cAAc,YAAY,IAAI,SAAS,KAAK,IAAI,QAAQ,SAAS,YAAY,GAAG,CAAC,CAAC;AAExI,MAAM,SAAS,UAAU,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC,CAAC;AAErE,SAAgB,UAAU,UAAkC,CAAC,GAAW;CACtE,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,OAAO;CACX,IAAI,WAAW;CACf,IAAI,OAAgB;EAAE,uBAAO,IAAI,IAAI;EAAG,0BAAU,IAAI,IAAI;CAAE;CAC5D,MAAM,wBAAQ,IAAI,IAAmB;CACrC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,eAAyB,CAAC;CAChC,MAAM,UAAU;EAAE,OAAO;EAAG,MAAM;CAAE;CAEpC,MAAM,YAAY,MAAgB,aAAa,eAAe,EAAE,UAAU,aAAa,QAAS,EAAE,SAAS,EAAE,UAAW,OAAO,CAAC;CAEhI,MAAM,YAAY,OAAmC;EACnD,MAAM,OAAO,UAAU,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,EAAG;EAC3C,MAAM,MAAM,UAAU,OAAO,GAAG;EAChC,IAAI,CAAC,KAAK,WAAW,GAAG,IAAI,EAAE,GAAG,OAAO,KAAA;EACxC,MAAM,OAAO,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,QAAQ,YAAY,EAAE;EAC9D,OAAO,WAAW,QAAQ,IAAI,IAAI,MAAM,OAAO,OAAO,KAAA;CACxD;CAEA,MAAM,WAAW,OAAe;EAC9B,MAAM,OAAO,UAAU,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,EAAG;EAC3C,MAAM,MAAM,UAAU,OAAO,GAAG;EAChC,OAAO,SAAS,GAAG,IAAI,cAAc,SAAS,GAAG,IAAI;CACvD;CAEA,MAAM,QAAQ,QACZ,aAAa,KAAK,IAAI,MAAM,QAAQ,QAAQ,CAAC,EAAA,CAAG,MAAM,MAAO,EAAE,SAAS,IAAI,IAAI,IAAI,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,MAAM,GAAI;CAE7H,OAAO;EACL,MAAM;EAEN,MAAM,eAAe,UAAU;GAC7B,WAAW,SAAS,YAAY;GAChC,OAAO,SAAS;GAChB,WAAW,SAAS;GACpB,SAAS,MAAM,WAAW,QAAQ,SAAS,KAAA,IAAY,SAAS,OAAO,QAAQ,SAAS,MAAM,QAAQ,IAAI,GAAG,QAAQ,MAAM;EAC7H;EAGA,qBAAqB;GACnB,IAAI,UAAU,OAAO,CAAC;GACtB,OAAO,CAAC;IAAE,KAAK;IAAU,OAAO;KAAE,MAAM;KAAU,KAAK,GAAG,KAAK;IAA0B;IAAG,UAAU;GAAe,CAAC;EACxH;EAEA,UAAU,IAAI;GACZ,OAAO,OAAO,SAAS,KAAK;EAC9B;EAEA,aAAa;GACX,IAAI;IACF,YAAY,SAAS,MAAM;GAC7B,SAAS,GAAG;IACV,KAAK,MAAM,SAAS,CAAC,CAAC;GACxB;GACA,OAAO;IAAE,uBAAO,IAAI,IAAI;IAAG,0BAAU,IAAI,IAAI;GAAE;GAC/C,MAAM,MAAM;GACZ,aAAa,SAAS;GACtB,QAAQ,QAAQ,QAAQ,OAAO;EACjC;EAEA,gBAAgB,QAAQ;GACtB,MAAM,OAAO,CAAC,UAAU,OAAO,OAAO,GAAG,UAAU,OAAO,OAAO,CAAC;GAClE,OAAO,QAAQ,IAAI,IAAI;GACvB,MAAM,YAAY,SAAiB;IACjC,MAAM,OAAO,UAAU,IAAI;IAC3B,IAAI,CAAC,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,MAAM,MAAM,KAAK,WAAW,GAAG,EAAE,EAAE,CAAC,GAAG;IAC5E,IAAI;KACF,YAAY,SAAS,MAAM;KAC3B,OAAO,OAAO,OAAO,KAAK,+BAA+B,SAAS,OAAO,MAAM,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG,KAAK,EAC5G,WAAW,KACb,CAAC;IACH,SAAS,GAAG;KACV,OAAO,OAAO,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;KAC3D,OAAO,GAAG,KAAK;MAAE,MAAM;MAAS,KAAK;OAAE,SAAS,SAAS,CAAC;OAAG,OAAO;OAAI,QAAQ;MAAY;KAAE,CAAC;IACjG;GACF;GACA,OAAO,QAAQ,GAAG,UAAU,QAAQ;GACpC,OAAO,QAAQ,GAAG,OAAO,QAAQ;GACjC,OAAO,QAAQ,GAAG,UAAU,QAAQ;GACpC,IAAI,QAAQ,YAAY,OAAO;GAI/B,OAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACzC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;IACtD,IAAI,IAAI,aAAa,GAAG,KAAK,mBAAmB,OAAO,KAAK;IAC5D,MAAM,MAAM,IAAI,aAAa,IAAI,KAAK,KAAK;IAC3C,MAAM,UAAU,WAAW,QAAQ,SAAS,MAAM,MAAM,EAAE,QAAQ,GAAG;IACrE,IAAI,YAAY,KAAA,GAAW;KACzB,IAAI,aAAa;KACjB,IAAI,IAAI,iBAAiB,IAAI,6DAA6D;KAC1F;IACF;IACA,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI,KAAA,IAAa,IAAI,aAAa,IAAI,QAAQ,KAAK,KAAA;IAChG,MAAM,EAAE,MAAM,MAAM,WAAW,cAAc,QAAQ,SAAS,MAAM;IACpE,IAAI,MAAM,GAAG,KAAK,wBAAwB,mBAAmB,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ;IACxF,KAAK;GACP,CAAC;GAKD,OAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACzC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;IACtD,IAAI,IAAI,aAAa,GAAG,KAAK,oBAAoB,OAAO,KAAK;IAC7D,MAAM,SAAS,QAAgB,SAAkB;KAC/C,IAAI,aAAa;KACjB,IAAI,UAAU,gBAAgB,kBAAkB;KAChD,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC;IAC9B;IACA,MAAM,QAAQ,MACZ,MAAM,aAAa,eAAe,MAAM,KAAK,EAC3C,UAAU,aAAa,eAAe,EAAE,WAAW,CAAC;KAAE,UAAU;KAAS,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;IAAE,CAAC,EAChI,CAAC;IACH,IAAI,IAAI,WAAW,OAAO;KACxB,IAAI;MACF,MAAM,KAAK,UAAU,QAAQ,IAAI,aAAa,IAAI,KAAK,KAAK,EAAE,CAAC;KACjE,SAAS,GAAG;MACV,KAAK,CAAC;KACR;KACA;IACF;IACA,MAAM,SAAS,IAAI,QAAQ;IAC3B,MAAM,aAAa,WAAW,KAAA,KAAa,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,IAAI,QAAQ;IAChF,IAAI,IAAI,WAAW,UAAU,CAAC,cAAc,IAAI,QAAQ,eAAe,EAAE,WAAW,kBAAkB,MAAM,MAAM;KAChH,MAAM,KAAK,EAAE,UAAU,CAAC;MAAE,UAAU;MAAS,SAAS;KAAoC,CAAC,EAAE,CAAC;KAC9F;IACF;IACA,IAAI,OAAO;IACX,IAAI,YAAY,MAAM;IACtB,IAAI,GAAG,SAAS,UAAmB,QAAQ,KAAM;IACjD,IAAI,GAAG,aAAa;KAClB,IAAI;MACF,MAAM,EAAE,KAAK,MAAM,UAAU,KAAK,MAAM,IAAI;MAC5C,MAAM,SAAS,UAAU,QAAQ,KAAK,MAAM,UAAU,IAAI;MAC1D,MAAM,OAAO,SAAS,SAAS,IAAI,MAAM,KAAK,MAAM;KACtD,SAAS,GAAG;MACV,KAAK,CAAC;KACR;IACF,CAAC;GACH,CAAC;EACH;EAEA,KAAK,IAAI;GACP,IAAI,OAAO,QAAQ;IACjB,MAAM,MAAM;IACZ,IAAI,QAAQ,YAAY,OAAO,OAAO;IACtC,OAAO,2BAA2B,KAAK,UAAU,aAAa,EAAE,KAAK,IAAI,UAAU,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE;GAC9G;GACA,IAAI,CAAC,YAAY,UAAU,SAAS,cAAc,KAAA,GAAW,OAAO;GACpE,MAAM,SAAS,SAAS,EAAE;GAC1B,IAAI,WAAW,KAAA,GAAW,OAAO;GACjC,aAAa,KAAK,MAAM;GACxB,OAAO,qCAAqC,aAAa,SAAS,EAAE;EACtE;EAIA,WAAW;GACT,OAAO;GACP,QAAQ,MAAM,IAAI;IAChB,IAAI,CAAC,UAAU;KACb,MAAM,SAAS,SAAS,EAAE;KAC1B,IAAI,WAAW,KAAA,GAAW,OAAO;MAAE,MAAM,OAAO,UAAU,MAAM;MAAG,KAAK;KAAK;KAC7E,OAAO,QAAQ,EAAE,IAAI;MAAE,MAAM,OAAO;MAAW,KAAK;KAAK,IAAI;IAC/D;IACA,IAAI,UAAU,SAAS,GAAG,SAAS,gBAAgB,KAAK,GAAG,WAAW,IAAI,GAAG,OAAO;IAEpF,IAAI,SAAS,EAAE,MAAM,KAAA,KAAa,QAAQ,EAAE,GAAG,OAAO;IACtD,QAAQ,MAAM,IAAI;IAClB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC;IAC9B,IAAI,UAAU,YAAY,OAAO,KAAK,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,GAAG,OAAO,EAAE,GAAG,MAAM,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC;IAC/H,OAAO;GACT;EACF;EAEA,SAAS,OAAO;GACd,IAAI,UAAU,KAAA,KAAa,UAAU,UAAU;GAC/C,MAAM,OAAO,IAAI,IAAI,KAAK,aAAa,CAAC;GACxC,MAAM,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC;GACtE,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;GAC3E,MAAM,SAAS,IAAI,SAAS,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE,SAAS,UAAU,QAAQ,IAAI,EAAE,IAAI,CAAC;GAClG,IAAI,OAAO,WAAW,GAAG;GACzB,MAAM,MAAM,SAAiB,SAAS,UAAU,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;GAC1E,KAAK,MACH;IACE,GAAG,OAAO,OAAO;IACjB,GAAG,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM;IACvE;GACF,CAAC,CAAC,KAAK,IAAI,CACb;EACF;EAEA,YAAY,MAAM;GAChB,IAAI,CAAC,KAAK,SAAS,qBAAqB,KAAK,cAAc,KAAA,GAAW,OAAO;GAC7E,MAAM,UAAU,UAAU;GAS1B,OAAO;IAAE,MARQ,KAAK,QAAQ,cAAc,QAAQ,UAAkB;KACpE,MAAM,SAAS,aAAa,OAAO,KAAK;KACxC,MAAM,OAAO,QAAQ,IAAI,MAAM,KAAK,CAAC;KACrC,MAAM,WAAW,cAAc,MAAM,IAAI;KACzC,QAAQ,SAAS,KAAK;KACtB,QAAQ,QAAQ,SAAS,KAAK;KAC9B,OAAO,iBAAiB,QAAQ;IAClC,CACsB;IAAG,KAAK;GAAK;EACrC;EAEA,iBAAiB;GACf,IAAI,QAAQ,UAAU,GAAG;GACzB,KAAK,KACH,WAAW,QAAQ,KAAK,MAAM,QAAQ,MAAM,mBAAmB,aAAa,OAAO,qBAAqB,QAAQ,QAAQ,QAAQ,KAAK,eACvI;EACF;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * `prosebook/vite`: the catalog as part of the build.\n *\n * Generates on start and again whenever a catalog or translation file changes, so dev never runs on\n * stale modules and a broken entry shows in the error overlay. A build ships only the messages the\n * code names: each locale module is swapped for its messages less the keys no module references\n * (see `tools/scan.ts` for how a reference is found, and why it errs towards keeping).\n *\n * In dev an edit doesn't reload the page: each generated locale module accepts its own update and\n * hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.\n * The index accepts its own too, since its doc comments change with the source text, and reloads the\n * page only when a locale was added or removed. In the browser the index imports the dev module first,\n * which sets up the hooks and the inspector (`inspector.ts`) before any copy exists, so frameworks that\n * render their own HTML (TanStack Start, React Router) get them too. The server opens the entry the\n * inspector asks for through Vite's own `/__open-in-editor`, so the editor is whichever Vite already\n * launches.\n */\nimport { relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Plugin } from 'vite';\n\nimport { beside, HOT_INDEX, HOT_SET, hotLocale, installInspector, keeper, unseenMessage, unseenOf, type Prune } from './tools/bundle.ts';\nimport { CatalogError, type EntryLocation } from './tools/catalog.ts';\nimport { compileLocale, localeExpression } from './tools/compile.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { inspectorEndpoints, nodeMiddleware, redirect } from './tools/endpoints.ts';\nimport { generate, type Generated } from './tools/generate.ts';\nimport { keyRefs, unseenReads, type KeyRefs, type Reads } from './tools/scan.ts';\n\nexport interface ProsebookPluginOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /**\n * The folder the config's paths resolve from, and where its file is found: the Vite root when\n * unset. Apps that share one catalog name its folder, so each build generates the same files.\n */\n root?: string;\n /**\n * Ship only the messages the code names. `strict`, the default, also fails the build on a read\n * whose key it can't see, such as `t(item.key)`, which would show the key where its message was\n * pruned. `loose` prunes without that check; `false` ships every message.\n */\n prune?: Prune;\n /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */\n keep?: string[];\n /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */\n inspect?: boolean;\n}\n\nconst PLACEHOLDER = /__PROSEBOOK_LOCALE_(\\d+)__/g;\nconst SCRIPT = /\\.[cm]?[jt]sx?$/;\nconst DEV = 'virtual:prosebook/dev';\nconst DEV_ID = '\\0prosebook/dev';\n\nconst normalize = (path: string) => resolve(path).replaceAll('\\\\', '/');\n\nconst inspectorPath = beside(import.meta.url, 'inspector');\n// prosebook's own modules, which read keys handed to them: `<Text>` calls `rich(props.id)`.\nconst ownDir = normalize(fileURLToPath(new URL('.', import.meta.url)));\n\nexport function prosebook(options: ProsebookPluginOptions = {}): Plugin {\n let config: ResolvedConfig;\n let generated: Generated | undefined;\n let building = false;\n let base = '/';\n let viteRoot = '';\n let kept: (key: string) => boolean = () => true;\n let refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const reads = new Map<string, Reads>();\n const prune = options.prune ?? 'strict';\n const placeholders: string[] = [];\n const shipped = { total: 0, kept: 0 };\n\n const describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\n\n const localeOf = (id: string): string | undefined => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n if (!path.startsWith(`${out}/`)) return undefined;\n const name = path.slice(out.length + 1).replace(/\\.[jt]s$/, '');\n return generated?.entries.has(name) === true ? name : undefined;\n };\n\n const isIndex = (id: string) => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n return path === `${out}/index.ts` || path === `${out}/index.js`;\n };\n\n return {\n name: 'prosebook',\n\n async configResolved(resolved) {\n building = resolved.command === 'build';\n base = resolved.base;\n viteRoot = resolved.root;\n config = await loadConfig(options.root === undefined ? resolved.root : resolve(resolved.root, options.root), options.config);\n },\n\n resolveId(id) {\n return id === DEV ? DEV_ID : null;\n },\n\n buildStart() {\n try {\n generated = generate(config);\n } catch (e) {\n this.error(describe(e));\n }\n refs = { exact: new Set(), prefixes: new Set() };\n kept = keeper(refs, options.keep);\n reads.clear();\n placeholders.length = 0;\n shipped.total = shipped.kept = 0;\n },\n\n configureServer(server) {\n const dirs = [normalize(config.catalog), normalize(config.locales)];\n server.watcher.add(dirs);\n const onChange = (file: string) => {\n const path = normalize(file);\n if (!path.endsWith('.yaml') || !dirs.some((d) => path.startsWith(`${d}/`))) return;\n try {\n generated = generate(config);\n server.config.logger.info(`prosebook: regenerated from ${relative(config.root, path).replaceAll('\\\\', '/')}`, {\n timestamp: true,\n });\n } catch (e) {\n server.config.logger.error(describe(e), { timestamp: true });\n server.ws.send({ type: 'error', err: { message: describe(e), stack: '', plugin: 'prosebook' } });\n }\n };\n server.watcher.on('change', onChange);\n server.watcher.on('add', onChange);\n server.watcher.on('unlink', onChange);\n if (options.inspect === false) return;\n\n // Each entry opens through Vite's own `/__open-in-editor`, so the editor is whichever Vite launches.\n const editor = ({ file, line, column }: EntryLocation) =>\n redirect(`${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`);\n server.middlewares.use(nodeMiddleware(inspectorEndpoints(config, { base, open: editor })));\n },\n\n load(id) {\n if (id === DEV_ID) {\n if (options.inspect === false) return HOT_SET;\n return HOT_SET + installInspector(inspectorPath, base);\n }\n if (!building || prune === false || generated === undefined) return null;\n const locale = localeOf(id);\n if (locale === undefined) return null;\n placeholders.push(locale);\n return `export default __PROSEBOOK_LOCALE_${placeholders.length - 1}__;\\n`;\n },\n\n // Before other plugins, so a read is found in the source as written: `<Text id>` still JSX, and\n // the line it is reported at, and its `prosebook-ignore`, where the author wrote them.\n transform: {\n order: 'pre',\n handler(code, id, transformOptions) {\n if (!building) {\n const locale = localeOf(id);\n if (locale !== undefined) return { code: code + hotLocale(locale), map: null };\n if (!isIndex(id)) return null;\n // A server render has no page to inspect, and its copies are made per request.\n const dev = transformOptions?.ssr === true ? '' : `import ${JSON.stringify(DEV)};\\n`;\n return { code: dev + code + HOT_INDEX, map: null };\n }\n if (prune === false || id.includes('/node_modules/') || id.startsWith('\\0')) return null;\n // The index's `Messages` type names every key, and is read here before its types are stripped.\n if (localeOf(id) !== undefined || isIndex(id)) return null;\n keyRefs(code, refs);\n const path = id.split('?', 1)[0]!;\n if (prune === 'strict' && SCRIPT.test(path) && !normalize(path).startsWith(`${ownDir}/`)) reads.set(id, unseenReads(path, code));\n return null;\n },\n },\n\n buildEnd(error) {\n if (error !== undefined || prune !== 'strict') return;\n const live = new Set(this.getModuleIds());\n const unseen = unseenOf([...reads].filter(([id]) => live.has(id)).map(([, r]) => r));\n if (unseen.length > 0) this.error(unseenMessage(unseen, viteRoot));\n },\n\n renderChunk(code) {\n if (!code.includes('__PROSEBOOK_LOCALE_') || generated === undefined) return null;\n const entries = generated.entries;\n const replaced = code.replace(PLACEHOLDER, (_match, index: string) => {\n const locale = placeholders[Number(index)]!;\n const list = entries.get(locale) ?? [];\n const compiled = compileLocale(list, kept);\n shipped.total += list.length;\n shipped.kept += compiled.rows.length;\n return localeExpression(compiled);\n });\n return { code: replaced, map: null };\n },\n\n generateBundle() {\n if (shipped.total === 0) return;\n this.info(\n `shipped ${shipped.kept} of ${shipped.total} messages across ${placeholders.length} locale(s); pruned ${shipped.total - shipped.kept} no code names`,\n );\n },\n };\n}\n\nexport default prosebook;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,MAAM,cAAc;AACpB,MAAM,SAAS;AACf,MAAM,MAAM;AACZ,MAAM,SAAS;AAEf,MAAM,aAAa,SAAiB,QAAQ,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;AAEtE,MAAM,gBAAgB,OAAO,YAAY,KAAK,WAAW;AAEzD,MAAM,SAAS,UAAU,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC,CAAC;AAErE,SAAgB,UAAU,UAAkC,CAAC,GAAW;CACtE,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,OAAO;CACX,IAAI,WAAW;CACf,IAAI,aAAuC;CAC3C,IAAI,OAAgB;EAAE,uBAAO,IAAI,IAAI;EAAG,0BAAU,IAAI,IAAI;CAAE;CAC5D,MAAM,wBAAQ,IAAI,IAAmB;CACrC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,eAAyB,CAAC;CAChC,MAAM,UAAU;EAAE,OAAO;EAAG,MAAM;CAAE;CAEpC,MAAM,YAAY,MAAgB,aAAa,eAAe,EAAE,UAAU,aAAa,QAAS,EAAE,SAAS,EAAE,UAAW,OAAO,CAAC;CAEhI,MAAM,YAAY,OAAmC;EACnD,MAAM,OAAO,UAAU,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,EAAG;EAC3C,MAAM,MAAM,UAAU,OAAO,GAAG;EAChC,IAAI,CAAC,KAAK,WAAW,GAAG,IAAI,EAAE,GAAG,OAAO,KAAA;EACxC,MAAM,OAAO,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,QAAQ,YAAY,EAAE;EAC9D,OAAO,WAAW,QAAQ,IAAI,IAAI,MAAM,OAAO,OAAO,KAAA;CACxD;CAEA,MAAM,WAAW,OAAe;EAC9B,MAAM,OAAO,UAAU,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,EAAG;EAC3C,MAAM,MAAM,UAAU,OAAO,GAAG;EAChC,OAAO,SAAS,GAAG,IAAI,cAAc,SAAS,GAAG,IAAI;CACvD;CAEA,OAAO;EACL,MAAM;EAEN,MAAM,eAAe,UAAU;GAC7B,WAAW,SAAS,YAAY;GAChC,OAAO,SAAS;GAChB,WAAW,SAAS;GACpB,SAAS,MAAM,WAAW,QAAQ,SAAS,KAAA,IAAY,SAAS,OAAO,QAAQ,SAAS,MAAM,QAAQ,IAAI,GAAG,QAAQ,MAAM;EAC7H;EAEA,UAAU,IAAI;GACZ,OAAO,OAAO,MAAM,SAAS;EAC/B;EAEA,aAAa;GACX,IAAI;IACF,YAAY,SAAS,MAAM;GAC7B,SAAS,GAAG;IACV,KAAK,MAAM,SAAS,CAAC,CAAC;GACxB;GACA,OAAO;IAAE,uBAAO,IAAI,IAAI;IAAG,0BAAU,IAAI,IAAI;GAAE;GAC/C,OAAO,OAAO,MAAM,QAAQ,IAAI;GAChC,MAAM,MAAM;GACZ,aAAa,SAAS;GACtB,QAAQ,QAAQ,QAAQ,OAAO;EACjC;EAEA,gBAAgB,QAAQ;GACtB,MAAM,OAAO,CAAC,UAAU,OAAO,OAAO,GAAG,UAAU,OAAO,OAAO,CAAC;GAClE,OAAO,QAAQ,IAAI,IAAI;GACvB,MAAM,YAAY,SAAiB;IACjC,MAAM,OAAO,UAAU,IAAI;IAC3B,IAAI,CAAC,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,MAAM,MAAM,KAAK,WAAW,GAAG,EAAE,EAAE,CAAC,GAAG;IAC5E,IAAI;KACF,YAAY,SAAS,MAAM;KAC3B,OAAO,OAAO,OAAO,KAAK,+BAA+B,SAAS,OAAO,MAAM,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG,KAAK,EAC5G,WAAW,KACb,CAAC;IACH,SAAS,GAAG;KACV,OAAO,OAAO,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;KAC3D,OAAO,GAAG,KAAK;MAAE,MAAM;MAAS,KAAK;OAAE,SAAS,SAAS,CAAC;OAAG,OAAO;OAAI,QAAQ;MAAY;KAAE,CAAC;IACjG;GACF;GACA,OAAO,QAAQ,GAAG,UAAU,QAAQ;GACpC,OAAO,QAAQ,GAAG,OAAO,QAAQ;GACjC,OAAO,QAAQ,GAAG,UAAU,QAAQ;GACpC,IAAI,QAAQ,YAAY,OAAO;GAG/B,MAAM,UAAU,EAAE,MAAM,MAAM,aAC5B,SAAS,GAAG,KAAK,wBAAwB,mBAAmB,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG;GAC5F,OAAO,YAAY,IAAI,eAAe,mBAAmB,QAAQ;IAAE;IAAM,MAAM;GAAO,CAAC,CAAC,CAAC;EAC3F;EAEA,KAAK,IAAI;GACP,IAAI,OAAO,QAAQ;IACjB,IAAI,QAAQ,YAAY,OAAO,OAAO;IACtC,OAAO,UAAU,iBAAiB,eAAe,IAAI;GACvD;GACA,IAAI,CAAC,YAAY,UAAU,SAAS,cAAc,KAAA,GAAW,OAAO;GACpE,MAAM,SAAS,SAAS,EAAE;GAC1B,IAAI,WAAW,KAAA,GAAW,OAAO;GACjC,aAAa,KAAK,MAAM;GACxB,OAAO,qCAAqC,aAAa,SAAS,EAAE;EACtE;EAIA,WAAW;GACT,OAAO;GACP,QAAQ,MAAM,IAAI,kBAAkB;IAClC,IAAI,CAAC,UAAU;KACb,MAAM,SAAS,SAAS,EAAE;KAC1B,IAAI,WAAW,KAAA,GAAW,OAAO;MAAE,MAAM,OAAO,UAAU,MAAM;MAAG,KAAK;KAAK;KAC7E,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO;KAGzB,OAAO;MAAE,OADG,kBAAkB,QAAQ,OAAO,KAAK,UAAU,KAAK,UAAU,GAAG,EAAE,QAC3D,OAAO;MAAW,KAAK;KAAK;IACnD;IACA,IAAI,UAAU,SAAS,GAAG,SAAS,gBAAgB,KAAK,GAAG,WAAW,IAAI,GAAG,OAAO;IAEpF,IAAI,SAAS,EAAE,MAAM,KAAA,KAAa,QAAQ,EAAE,GAAG,OAAO;IACtD,QAAQ,MAAM,IAAI;IAClB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC;IAC9B,IAAI,UAAU,YAAY,OAAO,KAAK,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,GAAG,OAAO,EAAE,GAAG,MAAM,IAAI,IAAI,YAAY,MAAM,IAAI,CAAC;IAC/H,OAAO;GACT;EACF;EAEA,SAAS,OAAO;GACd,IAAI,UAAU,KAAA,KAAa,UAAU,UAAU;GAC/C,MAAM,OAAO,IAAI,IAAI,KAAK,aAAa,CAAC;GACxC,MAAM,SAAS,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;GACnF,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,cAAc,QAAQ,QAAQ,CAAC;EACnE;EAEA,YAAY,MAAM;GAChB,IAAI,CAAC,KAAK,SAAS,qBAAqB,KAAK,cAAc,KAAA,GAAW,OAAO;GAC7E,MAAM,UAAU,UAAU;GAS1B,OAAO;IAAE,MARQ,KAAK,QAAQ,cAAc,QAAQ,UAAkB;KACpE,MAAM,SAAS,aAAa,OAAO,KAAK;KACxC,MAAM,OAAO,QAAQ,IAAI,MAAM,KAAK,CAAC;KACrC,MAAM,WAAW,cAAc,MAAM,IAAI;KACzC,QAAQ,SAAS,KAAK;KACtB,QAAQ,QAAQ,SAAS,KAAK;KAC9B,OAAO,iBAAiB,QAAQ;IAClC,CACsB;IAAG,KAAK;GAAK;EACrC;EAEA,iBAAiB;GACf,IAAI,QAAQ,UAAU,GAAG;GACzB,KAAK,KACH,WAAW,QAAQ,KAAK,MAAM,QAAQ,MAAM,mBAAmB,aAAa,OAAO,qBAAqB,QAAQ,QAAQ,QAAQ,KAAK,eACvI;EACF;CACF;AACF"}
|