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.
- package/README.md +274 -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-DboItAre.js +111 -0
- package/dist/bundle-DboItAre.js.map +1 -0
- package/dist/bundle-KD-DiiFa.d.ts +6 -0
- package/dist/bundle-KD-DiiFa.d.ts.map +1 -0
- package/dist/cli.js +51 -56
- package/dist/cli.js.map +1 -1
- package/dist/compile-xF3F4oIp.d.ts +16 -0
- package/dist/compile-xF3F4oIp.d.ts.map +1 -0
- package/dist/{config-D--RITk2.js → config-BPSmoYel.js} +36 -212
- package/dist/config-BPSmoYel.js.map +1 -0
- package/dist/{config-_4HfnJQu.d.ts → config-DdMkKt_N.d.ts} +61 -2
- package/dist/config-DdMkKt_N.d.ts.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-CnUfCkIk.js +96 -0
- package/dist/endpoints-CnUfCkIk.js.map +1 -0
- package/dist/generate-9pR3i5Bj.js +5173 -0
- package/dist/generate-9pR3i5Bj.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-CWyTAQ0y.js} +103 -87
- package/dist/sarif-CWyTAQ0y.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 +39 -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/emit.ts +87 -0
- package/src/tools/native/index.ts +36 -0
- package/src/tools/native/kotlin.ts +269 -0
- package/src/tools/native/model.ts +280 -0
- package/src/tools/native/number.ts +210 -0
- package/src/tools/native/plurals.ts +103 -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
|
@@ -412,6 +412,34 @@ declare function compileRule(name: string, rule: Rule): CheckRule;
|
|
|
412
412
|
/** `text` with the edits made, leaving out any that overlaps one before it. */
|
|
413
413
|
declare function applyEdits(text: string, edits: Edit[]): string;
|
|
414
414
|
declare function lint(messages: Message[], config: ResolvedConfig): Finding[];
|
|
415
|
+
type Element = MessageFormatElement;
|
|
416
|
+
/** A function as written: `:number minimumFractionDigits=2`. Kept on the styles it produces, to write back. */
|
|
417
|
+
interface Mf2Function {
|
|
418
|
+
name: string;
|
|
419
|
+
options: Record<string, string>;
|
|
420
|
+
/** Options given by an argument, as `currency=$code`: the option's name to the argument's. */
|
|
421
|
+
args?: Record<string, string>;
|
|
422
|
+
/** `u:dir`: the direction the value is written in, which picks the mark isolating it. */
|
|
423
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
424
|
+
}
|
|
425
|
+
/** An MF2 message as the element tree. Throws, with the fix, where it can't be read or compiled. */
|
|
426
|
+
declare function parseMf2(text: string, locale: string, isolate?: Isolate): Element[];
|
|
427
|
+
/**
|
|
428
|
+
* When a placeholder is isolated from the text around it, so a value written the other way can't
|
|
429
|
+
* reorder it: `'rtl'` in a locale not known to run left to right, or where `u:dir` asks; `'always'`
|
|
430
|
+
* also for every value of unknown direction (a string), as the spec's default strategy has it.
|
|
431
|
+
*/
|
|
432
|
+
type Isolate = 'rtl' | 'always' | false;
|
|
433
|
+
/** The `Intl.NumberFormat` options a numeric function formats with. */
|
|
434
|
+
declare function intlNumber(fn: Mf2Function): Intl.NumberFormatOptions;
|
|
435
|
+
/**
|
|
436
|
+
* The `Intl.DateTimeFormat` options a date or time function formats with. MF2's date and time
|
|
437
|
+
* functions are still a draft; this follows the spec's semantic options as the `messageformat`
|
|
438
|
+
* package does, so a message reads the same in both.
|
|
439
|
+
*/
|
|
440
|
+
declare function intlDate(fn: Mf2Function): Intl.DateTimeFormatOptions;
|
|
441
|
+
/** A message tree as MF2 text. Throws, saying what has no MF2 equivalent, where it can't be written. */
|
|
442
|
+
declare function toMf2(ast: Element[], locale: string): string;
|
|
415
443
|
interface WordLists {
|
|
416
444
|
/** Words a string never uses, in groups that say why. */
|
|
417
445
|
banned?: {
|
|
@@ -437,6 +465,24 @@ interface LintConfig extends WordLists {
|
|
|
437
465
|
/** Severities for one area's entries (a key's first segment) over `rules`: `{ native: { 'sentence-case': 'off' } }`. */
|
|
438
466
|
areas?: Record<string, RuleSeverities>;
|
|
439
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Native code generated from the catalog, for the parts of an app outside the browser: a tray, a
|
|
470
|
+
* menu bar, a CLI. One file, holding every locale, with a typed function per message.
|
|
471
|
+
*/
|
|
472
|
+
interface NativeTarget {
|
|
473
|
+
language: 'rust' | 'swift' | 'kotlin';
|
|
474
|
+
/** The file written. */
|
|
475
|
+
out: string;
|
|
476
|
+
/**
|
|
477
|
+
* Key prefixes the file holds: `['native']` takes every `native.*` key, which it names without the
|
|
478
|
+
* prefix (`native.tray.quit` is `tray_quit`). Every key, named in full, by default.
|
|
479
|
+
*/
|
|
480
|
+
keys?: string[];
|
|
481
|
+
/** The type holding the locales and messages: `Locale` in Rust, `Copy` in Swift and Kotlin. */
|
|
482
|
+
name?: string;
|
|
483
|
+
/** Kotlin's package. */
|
|
484
|
+
package?: string;
|
|
485
|
+
}
|
|
440
486
|
interface ProsebookConfig {
|
|
441
487
|
/** The source-language catalog: one `<area>.yaml` per area. */
|
|
442
488
|
catalog?: string;
|
|
@@ -453,6 +499,13 @@ interface ProsebookConfig {
|
|
|
453
499
|
* translations alike; `prosebook migrate` rewrites a catalog from one to the other.
|
|
454
500
|
*/
|
|
455
501
|
messageFormat?: 1 | 2;
|
|
502
|
+
/**
|
|
503
|
+
* In MF2, when a placeholder is wrapped in Unicode isolation marks, so a value written the other
|
|
504
|
+
* way (a Latin name in Hebrew) can't reorder the text around it. `'rtl'`, the default: in locales
|
|
505
|
+
* written right to left, and wherever `u:dir` asks. `'always'`: also every string in any locale,
|
|
506
|
+
* as the spec's default has it. `false`: never.
|
|
507
|
+
*/
|
|
508
|
+
isolate?: Isolate;
|
|
456
509
|
/** Locales that ship in the main bundle beside the source, rather than loading when first used. */
|
|
457
510
|
bundled?: string[];
|
|
458
511
|
/** A pseudo-locale generated from the source (accented and lengthened) to find layout bugs and hardcoded text. */
|
|
@@ -470,6 +523,8 @@ interface ProsebookConfig {
|
|
|
470
523
|
/** Where a string may sit, with what each surface means. */
|
|
471
524
|
surfaces?: Record<string, string>;
|
|
472
525
|
lint?: LintConfig;
|
|
526
|
+
/** Rust, Swift or Kotlin files `generate` writes beside the TypeScript. */
|
|
527
|
+
native?: NativeTarget[];
|
|
473
528
|
/** Where `prosebook scan` looks for hardcoded text and catalog keys. */
|
|
474
529
|
scan?: {
|
|
475
530
|
dirs?: string[];
|
|
@@ -486,6 +541,7 @@ interface ResolvedConfig {
|
|
|
486
541
|
lock: string;
|
|
487
542
|
source: string;
|
|
488
543
|
messageFormat: 1 | 2;
|
|
544
|
+
isolate: Isolate;
|
|
489
545
|
bundled: string[];
|
|
490
546
|
pseudo: string | false;
|
|
491
547
|
stale: 'use' | 'fallback';
|
|
@@ -501,6 +557,9 @@ interface ResolvedConfig {
|
|
|
501
557
|
}>;
|
|
502
558
|
areas: Record<string, Record<string, Severity>>;
|
|
503
559
|
} & Required<WordLists>;
|
|
560
|
+
native: (NativeTarget & {
|
|
561
|
+
name: string;
|
|
562
|
+
})[];
|
|
504
563
|
scan: {
|
|
505
564
|
dirs: string[];
|
|
506
565
|
extensions: string[];
|
|
@@ -513,5 +572,5 @@ declare const CONFIG_FILES: string[];
|
|
|
513
572
|
/** Finds and reads the config at `root`, or the defaults when there is none. */
|
|
514
573
|
declare function loadConfig(root: string, inline?: ProsebookConfig): Promise<ResolvedConfig>;
|
|
515
574
|
declare function resolveConfig(root: string, config: ProsebookConfig): ResolvedConfig;
|
|
516
|
-
export {
|
|
517
|
-
//# sourceMappingURL=config-
|
|
575
|
+
export { loadCatalog as $, RuleContext as A, DEFAULT_KINDS as B, Finding as C, Readings as D, Plugin as E, lint as F, Catalog as G, KindRule as H, phrase as I, KEY_PATTERN as J, CatalogError as K, sentences as L, applyEdits as M, compileRule as N, Report as O, definePlugin as P, entryLocation as Q, singularOf as R, ExistenceRule as S, OccurrenceRule as T, STRICT_LISTS as U, DEFAULT_SURFACES as V, Severity as W, Problem as X, Message as Y, Translation as Z, CapitalizationRule as _, ResolvedConfig as a, CoreRule as b, loadConfig as c, Mf2Function as d, previewOf as et, intlDate as f, CORE_RULES as g, toMf2 as h, ProsebookConfig as i, SubstitutionRule as j, Rule as k, resolveConfig as l, parseMf2 as m, LintConfig as n, MessageFormatElement as nt, WordLists as o, intlNumber as p, EntryLocation as q, NativeTarget as r, defineConfig as s, CONFIG_FILES as t, whereOf as tt, Isolate as u, CheckRule as v, HOLE as w, Edit as x, ConditionalRule as y, words as z };
|
|
576
|
+
//# sourceMappingURL=config-DdMkKt_N.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-DdMkKt_N.d.ts","names":[],"sources":["../src/tools/catalog.ts","../src/tools/presets.ts","../src/tools/lint.ts","../src/tools/mf2.ts","../src/tools/config.ts"],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { CONFIG_FILES, type CapitalizationRule, type CheckRule, type ConditionalRule, type ExistenceRule, HOLE, type KindRule, LintConfig, type OccurrenceRule, type Plugin, ProsebookConfig, type Readings, type Report, ResolvedConfig, type Rule, type RuleContext, type Severity, type SubstitutionRule, WordLists, defineConfig, definePlugin, loadConfig, phrase, resolveConfig, sentences, words };
|
|
1
|
+
import { A as RuleContext, D as Readings, E as Plugin, H as KindRule, I as phrase, L as sentences, O as Report, P as definePlugin, S as ExistenceRule, T as OccurrenceRule, W as Severity, _ as CapitalizationRule, a as ResolvedConfig, c as loadConfig, i as ProsebookConfig, j as SubstitutionRule, k as Rule, l as resolveConfig, n as LintConfig, o as WordLists, r as NativeTarget, s as defineConfig, t as CONFIG_FILES, v as CheckRule, w as HOLE, y as ConditionalRule, z as words } from "./config-DdMkKt_N.js";
|
|
2
|
+
export { CONFIG_FILES, type CapitalizationRule, type CheckRule, type ConditionalRule, type ExistenceRule, HOLE, type KindRule, LintConfig, NativeTarget, type OccurrenceRule, type Plugin, ProsebookConfig, type Readings, type Report, ResolvedConfig, type Rule, type RuleContext, type Severity, type SubstitutionRule, WordLists, defineConfig, definePlugin, loadConfig, phrase, resolveConfig, sentences, words };
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as CONFIG_FILES, l as HOLE, n as defineConfig, f as definePlugin, r as loadConfig, m as phrase, i as resolveConfig, h as sentences, _ as words } from "./config-
|
|
1
|
+
export { t as CONFIG_FILES, l as HOLE, n as defineConfig, f as definePlugin, r as loadConfig, m as phrase, i as resolveConfig, h as sentences, _ as words } from "./config-BPSmoYel.js";
|
|
@@ -44,12 +44,20 @@ type DefaultTags = Register extends {
|
|
|
44
44
|
*/
|
|
45
45
|
interface Helpers {
|
|
46
46
|
readonly locale: string;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
/**
|
|
48
|
+
* `{x, number, …}` and the `#` of a plural; `given` holds the options arguments give, in MF2.
|
|
49
|
+
* Nothing where there is no value or `Intl` refuses it, so the message shows `e`'s fallback.
|
|
50
|
+
*/
|
|
51
|
+
n(value: number, options?: Intl.NumberFormatOptions, given?: Intl.NumberFormatOptions): string | undefined;
|
|
52
|
+
/** `{x, date, …}` and `{x, time, …}`, as `n`. */
|
|
53
|
+
d(value: Date | number, options?: Intl.DateTimeFormatOptions, given?: Intl.DateTimeFormatOptions): string | undefined;
|
|
54
|
+
/** A placeholder's fallback, `{x}`, where it has no value or `n` or `d` gave nothing: reports why. */
|
|
55
|
+
e(fallback: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* `{x, plural, …}` and `{x, selectordinal, …}`: picks an arm and renders it. MF2 passes the
|
|
58
|
+
* options its selector formats with, since those decide the category: `1.0` is `other` in English.
|
|
59
|
+
*/
|
|
60
|
+
p(value: number, offset: number, ordinal: boolean, arms: Arms, options?: Intl.PluralRulesOptions, given?: Intl.PluralRulesOptions): unknown;
|
|
53
61
|
/** `{x, select, …}`. */
|
|
54
62
|
s(value: unknown, arms: Arms): unknown;
|
|
55
63
|
/** `<tag>…</tag>`: the tag's function from the values, or the bare children when there is none. */
|
|
@@ -123,6 +131,11 @@ interface CopyOptions<L extends string> {
|
|
|
123
131
|
};
|
|
124
132
|
/** What a key the catalog lacks reads as. Defaults to the key itself. */
|
|
125
133
|
onMissing?: (key: string, locale: L) => string;
|
|
134
|
+
/**
|
|
135
|
+
* Told of a placeholder that had no value, or one `Intl` refused (`currency: 'euro'`): the message
|
|
136
|
+
* still renders, showing `{name}` there. Defaults to `console.error`.
|
|
137
|
+
*/
|
|
138
|
+
onError?: (error: Error, key: string, locale: L) => void;
|
|
126
139
|
}
|
|
127
140
|
/** Its methods don't use `this`, so they can be passed around and destructured: `const { t } = copy`. */
|
|
128
141
|
interface Copy<M extends MessageShapes = MessageShapes, L extends string = string> {
|
|
@@ -159,4 +172,4 @@ interface Copy<M extends MessageShapes = MessageShapes, L extends string = strin
|
|
|
159
172
|
/** A catalog reader with a current locale. Create one per app, or one per request on a server. */
|
|
160
173
|
declare function createCopy<M extends MessageShapes, L extends string>(catalog: Catalog<M, L>, options?: CopyOptions<L>): Copy<M, L>;
|
|
161
174
|
export { RichValues as C, RichParam as S, ReadHook as _, defineCatalog as a, RegisteredMessages as b, Chunks as c, DefaultTags as d, DevHooks as f, MessageShapes as g, MessageShape as h, createCopy as i, Compiled as l, HotHook as m, Copy as n, AnyTagFn as o, Helpers as p, CopyOptions as r, ArgsParam as s, Catalog as t, CompiledMessages as u, Register as v, TagFn as w, RichKey as x, RegisteredLocale as y };
|
|
162
|
-
//# sourceMappingURL=copy-
|
|
175
|
+
//# sourceMappingURL=copy-B3V_QRkT.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-B3V_QRkT.d.ts","names":[],"sources":["../src/runtime/types.ts","../src/runtime/copy.ts"],"mappings":";;;;;;;;UASiB;EACf;EACA;;;KAIU;GAAmB,cAAc;;;;;;;;;;;;;;;UAgB5B;KAEL,qBAAqB;EAAmB,gBAAgB,UAAU;IAAkB,IAAI;KACxF,mBAAmB;EAAmB,cAAc;IAAqB;;KAEzE,cAAc;EAAmB,YAAY;IAAqB;;;;;UAM7D;WACN;;;;;EAKT,EAAE,eAAe,UAAU,KAAK,qBAAqB,QAAQ,KAAK;;EAElE,EAAE,OAAO,eAAe,UAAU,KAAK,uBAAuB,QAAQ,KAAK;;EAE3E,EAAE;;;;;EAKF,EACE,eACA,gBACA,kBACA,MAAM,MACN,UAAU,KAAK,oBACf,QAAQ,KAAK;;EAGf,EAAE,gBAAgB,MAAM;;EAExB,EAAE,QAAQ,QAAQ,aAAa;;KAGrB;GAAU;;KAIV;YAAqB;;;;;;;KAOrB,YAAY,aAAa,cAAc;;KAGvC,WAAW,gBAAgB,UAAU;;;;;KAMrC;EAAa,qBAAqB;EAAU,oBAAoB,IAAI;;;KAGpE,sBAAsB,QAAQ,QAAQ,GAAG;;KAGzC;YAA+B,cAAc;;;KAG7C,OAAO,eAAe;KACtB,MAAM,MAAM,QAAQ,OAAO,OAAO;;KAElC,YAAY;KAEnB,OAAO,gBAAgB;;KAGhB,UAAU,UAAU,gBAAgB,OAAO,2BAA2B,OAAO,cAAc,MAAM;KAExG,UAAU,UAAU,cAAc,QAAQ,KAAK,QAAQ,WAAW,eAAe,MAAM,WACzF,KAAK,QAAQ,WAAW,gBAAgB,MAAM;;KAIrC,WAAW,UAAU,cAAc,KAAK,YAAY,UAAU,GAAG;KACjE,UAAU,UAAU,cAAc,KAC5C,OAAO,WAAW,GAAG,oBAAoB,SAAS,WAAW,GAAG,OAAO,QAAQ,WAAW,GAAG;;KAGnF,QAAQ,UAAU,oBAAoB,WAAW,KAAK,EAAE,sCAAsC,WAAU;KCxG/G,eAAe;EAAU,SAAS;;;UAGtB,QAAQ,UAAU,gBAAgB,eAAe;;WAEvD,QAAQ;WACR,kBAAkB;;WAElB,sBAAsB,UAAU,KAAK;;WAErC,qBAAqB,UAAU,KAAK;;WAEpC,cAAc;;;iBAIT,cAAc,UAAU,eAAe,2BACrD,SAAS,KAAK,QAAQ,GAAG,mBACxB,QAAQ,GAAG;UAIG,YAAY;;EAE3B,SAAS;;EAET;KAAU,cAAc;;;EAExB,aAAa,aAAa,QAAQ;;;;;EAKlC,WAAW,OAAO,OAAO,aAAa,QAAQ;;;UAI/B,KAAK,UAAU,gBAAgB,eAAe;;WAEpD,QAAQ;WACR,kBAAkB;WAClB,QAAQ;;WAER;cAAkB,cAAc;;;WAEhC,OAAO;;EAEhB,EAAE,gBAAgB,YAAY,YAAY,KAAK,MAAM,MAAM,UAAU,EAAE;;EAEvE,KAAK,UAAU,QAAQ,IAAI,aAAa,YAAY,KAAK,MAAM,QAAQ,UAAU,EAAE,IAAI,KAAK,OAAO;EACnG,IAAI,YAAY,cAAc,aAAa;;EAE3C,UAAU,YAAY,MAAM,wCAAwC;;WAE3D;;EAET,UAAU,YAAY;;EAEtB,OAAO,YAAY,wBAAwB,UAAU,KAAK;;EAE1D,KAAK,YAAY,OAAO,eAAe,UAAU,KAAK;;EAEtD,KAAK,YAAY,OAAO,kBAAkB,UAAU,KAAK;;EAEzD,SAAS,YAAY,eAAe,MAAM,KAAK,wBAAwB,UAAU,KAAK;;;iBAaxE,WAAW,UAAU,eAAe,kBAAkB,SAAS,QAAQ,GAAG,IAAI,UAAS,YAAY,KAAU,KAAK,GAAG"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { n as flatten, t as createHelpers } from "./helpers-BWGcD0kS.js";
|
|
2
|
+
import { n as chainOf, r as negotiate } from "./locale-DpQA1pqH.js";
|
|
3
|
+
// src/runtime/copy.ts
|
|
4
|
+
/** Ties a generated catalog to its message shapes. Called by generated code. */
|
|
5
|
+
function defineCatalog(catalog) {
|
|
6
|
+
return catalog;
|
|
7
|
+
}
|
|
8
|
+
/** A message as the text it puts on screen: rendered again with its tags left as their text. */
|
|
9
|
+
function textOf(message, values, h) {
|
|
10
|
+
if (typeof message === "string") return message;
|
|
11
|
+
const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== "function"));
|
|
12
|
+
return flatten(message(plain, h)).join("");
|
|
13
|
+
}
|
|
14
|
+
/** A catalog reader with a current locale. Create one per app, or one per request on a server. */
|
|
15
|
+
function createCopy(catalog, options = {}) {
|
|
16
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
17
|
+
const pending = /* @__PURE__ */ new Map();
|
|
18
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
19
|
+
const defaultTags = options.tags ?? {};
|
|
20
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
21
|
+
let reading;
|
|
22
|
+
const helpers = (l) => createHelpers(l, (error) => {
|
|
23
|
+
if (reading === void 0) return;
|
|
24
|
+
if (options.onError) options.onError(error, reading, l);
|
|
25
|
+
else console.error(`prosebook: ${reading} in ${l}: ${error.message}`);
|
|
26
|
+
});
|
|
27
|
+
for (const [locale, messages] of Object.entries(catalog.messages)) loaded.set(locale, {
|
|
28
|
+
messages,
|
|
29
|
+
h: helpers(locale)
|
|
30
|
+
});
|
|
31
|
+
let locale = catalog.source;
|
|
32
|
+
let chain = layers(chainOf(locale, catalog.locales, catalog.source));
|
|
33
|
+
let latest = 0;
|
|
34
|
+
let version = 0;
|
|
35
|
+
function changed() {
|
|
36
|
+
version++;
|
|
37
|
+
for (const listener of listeners) listener();
|
|
38
|
+
}
|
|
39
|
+
globalThis.__PROSEBOOK_HOT__?.add((l, messages) => {
|
|
40
|
+
const layer = loaded.get(l);
|
|
41
|
+
if (layer === void 0) return;
|
|
42
|
+
layer.messages = messages;
|
|
43
|
+
changed();
|
|
44
|
+
});
|
|
45
|
+
function layers(locales) {
|
|
46
|
+
return locales.flatMap((l) => loaded.get(l) ?? []);
|
|
47
|
+
}
|
|
48
|
+
function load(l) {
|
|
49
|
+
const done = loaded.get(l);
|
|
50
|
+
if (done) return Promise.resolve(done);
|
|
51
|
+
let p = pending.get(l);
|
|
52
|
+
if (p === void 0) {
|
|
53
|
+
const loader = catalog.loaders[l];
|
|
54
|
+
if (loader === void 0) return Promise.reject(/* @__PURE__ */ new Error(`prosebook: no messages for locale "${l}"`));
|
|
55
|
+
p = loader().then((mod) => {
|
|
56
|
+
const layer = {
|
|
57
|
+
messages: mod.default,
|
|
58
|
+
h: helpers(l)
|
|
59
|
+
};
|
|
60
|
+
loaded.set(l, layer);
|
|
61
|
+
pending.delete(l);
|
|
62
|
+
return layer;
|
|
63
|
+
});
|
|
64
|
+
p.catch(() => pending.delete(l));
|
|
65
|
+
pending.set(l, p);
|
|
66
|
+
}
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
async function setLocale(next) {
|
|
70
|
+
const target = negotiate(next, catalog.locales, catalog.source);
|
|
71
|
+
const ticket = ++latest;
|
|
72
|
+
const locales = chainOf(target, catalog.locales, catalog.source);
|
|
73
|
+
await Promise.all(locales.map(load));
|
|
74
|
+
if (ticket !== latest || target === locale) return;
|
|
75
|
+
locale = target;
|
|
76
|
+
chain = layers(locales);
|
|
77
|
+
formatters.clear();
|
|
78
|
+
changed();
|
|
79
|
+
}
|
|
80
|
+
function find(key) {
|
|
81
|
+
for (const layer of chain) {
|
|
82
|
+
const message = layer.messages[key];
|
|
83
|
+
if (message !== void 0) return [message, layer.h];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function render(key, values) {
|
|
87
|
+
const found = find(key);
|
|
88
|
+
if (found === void 0) return options.onMissing ? options.onMissing(key, locale) : key;
|
|
89
|
+
const [message, h] = found;
|
|
90
|
+
reading = key;
|
|
91
|
+
let out;
|
|
92
|
+
try {
|
|
93
|
+
out = typeof message === "string" ? message : message(values, h);
|
|
94
|
+
} finally {
|
|
95
|
+
reading = void 0;
|
|
96
|
+
}
|
|
97
|
+
const read = globalThis.__PROSEBOOK_READ__;
|
|
98
|
+
if (read !== void 0) read(key, typeof out === "string" ? out : textOf(message, values, h), h.locale);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function formatter(kind, options, make) {
|
|
102
|
+
const id = `${kind}${JSON.stringify(options ?? {})}`;
|
|
103
|
+
let made = formatters.get(id);
|
|
104
|
+
if (made === void 0) formatters.set(id, made = make());
|
|
105
|
+
return made;
|
|
106
|
+
}
|
|
107
|
+
const ready = options.locale === void 0 ? Promise.resolve() : setLocale(options.locale);
|
|
108
|
+
return {
|
|
109
|
+
get locale() {
|
|
110
|
+
return locale;
|
|
111
|
+
},
|
|
112
|
+
get version() {
|
|
113
|
+
return version;
|
|
114
|
+
},
|
|
115
|
+
locales: catalog.locales,
|
|
116
|
+
source: catalog.source,
|
|
117
|
+
tags: defaultTags,
|
|
118
|
+
ready,
|
|
119
|
+
t(key, ...args) {
|
|
120
|
+
const out = render(key, args[0] ?? {});
|
|
121
|
+
return typeof out === "string" ? out : flatten(out).join("");
|
|
122
|
+
},
|
|
123
|
+
rich(key, ...values) {
|
|
124
|
+
const out = render(key, {
|
|
125
|
+
...defaultTags,
|
|
126
|
+
...values[0]
|
|
127
|
+
});
|
|
128
|
+
return flatten(out);
|
|
129
|
+
},
|
|
130
|
+
has(key) {
|
|
131
|
+
return find(key) !== void 0;
|
|
132
|
+
},
|
|
133
|
+
setLocale,
|
|
134
|
+
subscribe(listener) {
|
|
135
|
+
listeners.add(listener);
|
|
136
|
+
return () => listeners.delete(listener);
|
|
137
|
+
},
|
|
138
|
+
number(value, opts) {
|
|
139
|
+
return formatter("n", opts, () => new Intl.NumberFormat(locale, opts)).format(value);
|
|
140
|
+
},
|
|
141
|
+
date(value, opts) {
|
|
142
|
+
return formatter("d", opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);
|
|
143
|
+
},
|
|
144
|
+
list(items, opts) {
|
|
145
|
+
return formatter("l", opts, () => new Intl.ListFormat(locale, opts)).format(items);
|
|
146
|
+
},
|
|
147
|
+
relative(value, unit, opts) {
|
|
148
|
+
return formatter("r", opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
export { defineCatalog as n, createCopy as t };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=copy-Bo_FIoLy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-Bo_FIoLy.js","names":[],"sources":["../src/runtime/copy.ts"],"sourcesContent":["import { createHelpers, flatten } from './helpers.ts';\nimport { chainOf, negotiate } from './locale.ts';\nimport type {\n AnyTagFn,\n ArgsParam,\n Chunks,\n Compiled,\n CompiledMessages,\n Helpers,\n DevHooks,\n MessageShapes,\n RichKey,\n RichParam,\n Values,\n} from './types.ts';\n\ntype Loader = () => Promise<{ default: CompiledMessages }>;\n\n/** A generated catalog: which locales exist, which ship in the bundle and how to fetch the rest. */\nexport interface Catalog<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale the catalog is written in, which every other falls back to. */\n readonly source: L;\n readonly locales: readonly L[];\n /** Locales whose messages ship in the bundle. Always holds the source. */\n readonly messages: { readonly [locale in L]?: CompiledMessages };\n /** Locales fetched when first asked for. */\n readonly loaders: { readonly [locale in L]?: Loader };\n /** Type-only: carries the message shapes from the generated module to `createCopy`. */\n readonly '~messages'?: M;\n}\n\n/** Ties a generated catalog to its message shapes. Called by generated code. */\nexport function defineCatalog<M extends MessageShapes, L extends string = string>(\n catalog: Omit<Catalog<M, L>, '~messages'>,\n): Catalog<M, L> {\n return catalog;\n}\n\nexport interface CopyOptions<L extends string> {\n /** The locale to start in, or the locales a reader prefers, most preferred first. Defaults to the source. */\n locale?: L | (string & {}) | readonly string[];\n /** Tag renderers every rich read gets unless it supplies its own, such as `b` or `link`. */\n tags?: { [tag: string]: AnyTagFn };\n /** What a key the catalog lacks reads as. Defaults to the key itself. */\n onMissing?: (key: string, locale: L) => string;\n /**\n * Told of a placeholder that had no value, or one `Intl` refused (`currency: 'euro'`): the message\n * still renders, showing `{name}` there. Defaults to `console.error`.\n */\n onError?: (error: Error, key: string, locale: L) => void;\n}\n\n/** Its methods don't use `this`, so they can be passed around and destructured: `const { t } = copy`. */\nexport interface Copy<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale messages are coming back in. */\n readonly locale: L;\n readonly locales: readonly L[];\n readonly source: L;\n /** The tag renderers rich reads fall back to, from `CopyOptions.tags`. */\n readonly tags: { readonly [tag: string]: AnyTagFn };\n /** Settles once the starting locale's messages are loaded. */\n readonly ready: Promise<void>;\n /** A message as plain text. Tags render as the text they wrap. */\n t<K extends keyof M & string>(this: void, key: K, ...args: ArgsParam<M[K]>): string;\n /** A message with elements inside it: each tag's function turns what it wraps into a node. */\n rich<K extends RichKey<M>, N = unknown>(this: void, key: K, ...values: RichParam<M[K], N>): Chunks<N>;\n has(this: void, key: string): key is keyof M & string;\n /** Switches locale once its messages (and its fallbacks') are loaded. */\n setLocale(this: void, next: L | (string & {}) | readonly string[]): Promise<void>;\n /** Counts changes to what messages read as: each locale switch, and each catalog edit in dev. */\n readonly version: number;\n /** Called after each locale switch, and in dev after each catalog edit. Returns the unsubscribe. */\n subscribe(this: void, listener: () => void): () => void;\n /** Formats a number in the current locale. */\n number(this: void, value: number | bigint, options?: Intl.NumberFormatOptions): string;\n /** Formats a date in the current locale. */\n date(this: void, value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /** Joins items as a list in the current locale: `a, b and c`. */\n list(this: void, items: Iterable<string>, options?: Intl.ListFormatOptions): string;\n /** `in 3 days`, `2 hours ago`, in the current locale. */\n relative(this: void, value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n}\n\ntype Layer = { messages: CompiledMessages; h: Helpers };\n\n/** A message as the text it puts on screen: rendered again with its tags left as their text. */\nfunction textOf(message: Compiled, values: Values, h: Helpers): string {\n if (typeof message === 'string') return message;\n const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== 'function'));\n return flatten(message(plain, h)).join('');\n}\n\n/** A catalog reader with a current locale. Create one per app, or one per request on a server. */\nexport function createCopy<M extends MessageShapes, L extends string>(catalog: Catalog<M, L>, options: CopyOptions<L> = {}): Copy<M, L> {\n const loaded = new Map<L, Layer>();\n const pending = new Map<L, Promise<Layer>>();\n const listeners = new Set<() => void>();\n const defaultTags = options.tags ?? {};\n const formatters = new Map<string, Intl.NumberFormat | Intl.DateTimeFormat | Intl.ListFormat | Intl.RelativeTimeFormat>();\n // The key being rendered, which a helper's report names; none while the inspector renders it again.\n let reading: string | undefined;\n const helpers = (l: L) =>\n createHelpers(l, (error) => {\n if (reading === undefined) return;\n if (options.onError) options.onError(error, reading, l);\n else console.error(`prosebook: ${reading} in ${l}: ${error.message}`);\n });\n\n for (const [locale, messages] of Object.entries(catalog.messages) as [L, CompiledMessages][])\n loaded.set(locale, { messages, h: helpers(locale) });\n\n let locale = catalog.source;\n let chain: Layer[] = layers(chainOf(locale, catalog.locales, catalog.source));\n let latest = 0;\n let version = 0;\n\n function changed() {\n version++;\n for (const listener of listeners) listener();\n }\n\n // A layer's messages are swapped in place, so the chain that holds it reads the new ones.\n (globalThis as DevHooks).__PROSEBOOK_HOT__?.add((l, messages) => {\n const layer = loaded.get(l as L);\n if (layer === undefined) return;\n layer.messages = messages;\n changed();\n });\n\n function layers(locales: L[]): Layer[] {\n return locales.flatMap((l) => loaded.get(l) ?? []);\n }\n\n function load(l: L): Promise<Layer> {\n const done = loaded.get(l);\n if (done) return Promise.resolve(done);\n let p = pending.get(l);\n if (p === undefined) {\n // oxlint-disable-next-line typescript/no-unsafe-enum-assignment -- `L` is a string union, not an enum; the rule misreads the mapped type\n const loader = catalog.loaders[l];\n if (loader === undefined) return Promise.reject(new Error(`prosebook: no messages for locale \"${l}\"`));\n p = loader().then((mod) => {\n const layer = { messages: mod.default, h: helpers(l) };\n loaded.set(l, layer);\n pending.delete(l);\n return layer;\n });\n p.catch(() => pending.delete(l));\n pending.set(l, p);\n }\n return p;\n }\n\n async function setLocale(next: L | (string & {}) | readonly string[]): Promise<void> {\n const target = negotiate(next, catalog.locales, catalog.source);\n const ticket = ++latest;\n const locales = chainOf(target, catalog.locales, catalog.source);\n await Promise.all(locales.map(load));\n // A slower load must not undo a later switch.\n if (ticket !== latest || target === locale) return;\n locale = target;\n chain = layers(locales);\n formatters.clear();\n changed();\n }\n\n function find(key: string): [Compiled, Helpers] | undefined {\n for (const layer of chain) {\n const message = layer.messages[key];\n if (message !== undefined) return [message, layer.h];\n }\n return undefined;\n }\n\n function render(key: string, values: Values): unknown {\n const found = find(key);\n if (found === undefined) return options.onMissing ? options.onMissing(key, locale) : key;\n const [message, h] = found;\n reading = key;\n let out: unknown;\n try {\n out = typeof message === 'string' ? message : message(values, h);\n } finally {\n reading = undefined;\n }\n const read = (globalThis as DevHooks).__PROSEBOOK_READ__;\n if (read !== undefined) read(key, typeof out === 'string' ? out : textOf(message, values, h), h.locale);\n return out;\n }\n\n function formatter<F>(kind: string, options: object | undefined, make: () => F): F {\n const id = `${kind}${JSON.stringify(options ?? {})}`;\n let made = formatters.get(id) as F | undefined;\n if (made === undefined) formatters.set(id, (made = make()) as never);\n return made;\n }\n\n const ready = options.locale === undefined ? Promise.resolve() : setLocale(options.locale);\n\n return {\n get locale() {\n return locale;\n },\n get version() {\n return version;\n },\n locales: catalog.locales,\n source: catalog.source,\n tags: defaultTags,\n ready,\n t(key, ...args) {\n const out = render(key, (args[0] as Values | undefined) ?? {});\n return typeof out === 'string' ? out : flatten(out).join('');\n },\n rich(key, ...values) {\n const out = render(key, { ...defaultTags, ...(values[0] as Values | undefined) });\n return flatten(out) as Chunks<never>;\n },\n has(key): key is keyof M & string {\n return find(key) !== undefined;\n },\n setLocale,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n number(value, opts) {\n return formatter('n', opts, () => new Intl.NumberFormat(locale, opts)).format(value);\n },\n date(value, opts) {\n return formatter('d', opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);\n },\n list(items, opts) {\n return formatter('l', opts, () => new Intl.ListFormat(locale, opts)).format(items);\n },\n relative(value, unit, opts) {\n return formatter('r', opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { C as entryLocation, T as loadCatalog, b as readEntry, v as editEntry, x as CatalogError } from "./generate-9pR3i5Bj.js";
|
|
2
|
+
// src/tools/endpoints.ts
|
|
3
|
+
/** Answers the inspector's requests, and nothing for any other path. */
|
|
4
|
+
function inspectorEndpoints(config, { base = "/", open }) {
|
|
5
|
+
return async (req) => {
|
|
6
|
+
const url = new URL(req.url);
|
|
7
|
+
if (url.pathname === `${base}__prosebook/open`) return openEntry(await config, url, open);
|
|
8
|
+
if (url.pathname === `${base}__prosebook/entry`) return entry(await config, req, url);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when the
|
|
13
|
+
* locale has none. Read from disk, so a key added since the page loaded is found.
|
|
14
|
+
*/
|
|
15
|
+
async function openEntry(config, url, open) {
|
|
16
|
+
const key = url.searchParams.get("key") ?? "";
|
|
17
|
+
let message;
|
|
18
|
+
try {
|
|
19
|
+
message = loadCatalog(config).messages.find((m) => m.key === key);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return failed(e);
|
|
22
|
+
}
|
|
23
|
+
if (message === void 0) return new Response(`no entry for "${key}" in the catalog: has it been renamed since the page loaded?`, { status: 404 });
|
|
24
|
+
const locale = url.searchParams.has("catalog") ? void 0 : url.searchParams.get("locale") ?? void 0;
|
|
25
|
+
return open(entryLocation(config, message, locale));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`
|
|
29
|
+
* applies an edit (`entry.ts`). A POST writes files, so only the page itself may send one: same origin,
|
|
30
|
+
* and JSON, which a form on another site can't send without asking first.
|
|
31
|
+
*/
|
|
32
|
+
async function entry(config, req, url) {
|
|
33
|
+
if (req.method === "GET") try {
|
|
34
|
+
return Response.json(readEntry(config, url.searchParams.get("key") ?? ""));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return failed(e);
|
|
37
|
+
}
|
|
38
|
+
const origin = req.headers.get("origin");
|
|
39
|
+
const sameOrigin = origin === null || new URL(origin).host === url.host;
|
|
40
|
+
if (req.method !== "POST" || !sameOrigin || req.headers.get("content-type")?.startsWith("application/json") !== true) return Response.json({ problems: [{
|
|
41
|
+
severity: "error",
|
|
42
|
+
message: "edits come from the page, as JSON"
|
|
43
|
+
}] }, { status: 403 });
|
|
44
|
+
try {
|
|
45
|
+
const { key, edit, write } = await req.json();
|
|
46
|
+
const result = editEntry(config, key, edit, write === true);
|
|
47
|
+
return Response.json(result, { status: result.problems.length > 0 ? 422 : 200 });
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return failed(e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** A redirect within the page's origin, to a launcher the dev server answers itself. */
|
|
53
|
+
const redirect = (path) => new Response(null, {
|
|
54
|
+
status: 307,
|
|
55
|
+
headers: { Location: path }
|
|
56
|
+
});
|
|
57
|
+
function failed(e) {
|
|
58
|
+
return Response.json({ problems: e instanceof CatalogError ? e.problems : [{
|
|
59
|
+
severity: "error",
|
|
60
|
+
message: e instanceof Error ? e.message : String(e)
|
|
61
|
+
}] }, { status: e instanceof CatalogError ? 409 : 400 });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The endpoints as Node middleware, for Vite's server and Next's. The request's host is the one the
|
|
65
|
+
* page was served from: a proxy's `x-forwarded-host`, as Next's rewrite sets, before its own.
|
|
66
|
+
*/
|
|
67
|
+
function nodeMiddleware(handle) {
|
|
68
|
+
return (req, res, next) => {
|
|
69
|
+
if (req.url?.includes("/__prosebook/") !== true) return next();
|
|
70
|
+
const forwarded = req.headers["x-forwarded-host"];
|
|
71
|
+
const host = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.headers.host ?? "localhost";
|
|
72
|
+
const headers = new Headers();
|
|
73
|
+
for (const [name, value] of Object.entries(req.headers)) for (const v of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, v);
|
|
74
|
+
const chunks = [];
|
|
75
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
76
|
+
req.on("end", () => {
|
|
77
|
+
const body = req.method === "GET" || req.method === "HEAD" ? void 0 : Buffer.concat(chunks);
|
|
78
|
+
handle(new Request(`http://${host}${req.url}`, {
|
|
79
|
+
method: req.method,
|
|
80
|
+
headers,
|
|
81
|
+
body
|
|
82
|
+
})).then(async (response) => {
|
|
83
|
+
if (response === void 0) return next();
|
|
84
|
+
res.statusCode = response.status;
|
|
85
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
86
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
87
|
+
}).catch((e) => {
|
|
88
|
+
res.statusCode = 500;
|
|
89
|
+
res.end(e instanceof Error ? e.message : String(e));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export { nodeMiddleware as n, redirect as r, inspectorEndpoints as t };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=endpoints-CnUfCkIk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints-CnUfCkIk.js","names":[],"sources":["../src/tools/endpoints.ts"],"sourcesContent":["/**\n * The dev server's half of the inspector (`inspector.ts`, `inspector-dialog.ts`): `__prosebook/open`,\n * which opens an entry in the editor, and `__prosebook/entry`, which reads and edits one. Written over\n * `Request` and `Response`, so each integration mounts them its own way: Vite as middleware, Bun as\n * `Bun.serve` routes, Next behind a rewrite to a server of their own. Each opens the editor its dev\n * server already launches.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { CatalogError, entryLocation, loadCatalog, type EntryLocation } from './catalog.ts';\nimport type { ResolvedConfig } from './config.ts';\nimport { editEntry, readEntry, type EntryEdit } from './entry.ts';\n\nexport interface EndpointOptions {\n /** The path the endpoints sit under, ending in `/`: `/` by default. */\n base?: string;\n /** Opens an entry in the editor: a redirect to the dev server's own launcher, or a reply once it has. */\n open: (at: EntryLocation) => Response | Promise<Response>;\n}\n\n/** Answers the inspector's requests, and nothing for any other path. */\nexport function inspectorEndpoints(\n config: ResolvedConfig | Promise<ResolvedConfig>,\n { base = '/', open }: EndpointOptions,\n): (req: Request) => Promise<Response | undefined> {\n return async (req) => {\n const url = new URL(req.url);\n if (url.pathname === `${base}__prosebook/open`) return openEntry(await config, url, open);\n if (url.pathname === `${base}__prosebook/entry`) return entry(await config, req, url);\n return undefined;\n };\n}\n\n/**\n * `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when the\n * locale has none. Read from disk, so a key added since the page loaded is found.\n */\nasync function openEntry(config: ResolvedConfig, url: URL, open: EndpointOptions['open']): Promise<Response> {\n const key = url.searchParams.get('key') ?? '';\n let message;\n try {\n message = loadCatalog(config).messages.find((m) => m.key === key);\n } catch (e) {\n return failed(e);\n }\n if (message === undefined)\n return new Response(`no entry for \"${key}\" in the catalog: has it been renamed since the page loaded?`, { status: 404 });\n const locale = url.searchParams.has('catalog') ? undefined : (url.searchParams.get('locale') ?? undefined);\n return open(entryLocation(config, message, locale));\n}\n\n/**\n * The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`\n * applies an edit (`entry.ts`). A POST writes files, so only the page itself may send one: same origin,\n * and JSON, which a form on another site can't send without asking first.\n */\nasync function entry(config: ResolvedConfig, req: Request, url: URL): Promise<Response> {\n if (req.method === 'GET') {\n try {\n return Response.json(readEntry(config, url.searchParams.get('key') ?? ''));\n } catch (e) {\n return failed(e);\n }\n }\n const origin = req.headers.get('origin');\n const sameOrigin = origin === null || new URL(origin).host === url.host;\n if (req.method !== 'POST' || !sameOrigin || req.headers.get('content-type')?.startsWith('application/json') !== true)\n return Response.json({ problems: [{ severity: 'error', message: 'edits come from the page, as JSON' }] }, { status: 403 });\n try {\n const { key, edit, write } = (await req.json()) as { key: string; edit: EntryEdit; write?: boolean };\n const result = editEntry(config, key, edit, write === true);\n return Response.json(result, { status: result.problems.length > 0 ? 422 : 200 });\n } catch (e) {\n return failed(e);\n }\n}\n\n/** A redirect within the page's origin, to a launcher the dev server answers itself. */\nexport const redirect = (path: string) => new Response(null, { status: 307, headers: { Location: path } });\n\nfunction failed(e: unknown): Response {\n return Response.json(\n { problems: e instanceof CatalogError ? e.problems : [{ severity: 'error', message: e instanceof Error ? e.message : String(e) }] },\n { status: e instanceof CatalogError ? 409 : 400 },\n );\n}\n\n/**\n * The endpoints as Node middleware, for Vite's server and Next's. The request's host is the one the\n * page was served from: a proxy's `x-forwarded-host`, as Next's rewrite sets, before its own.\n */\nexport function nodeMiddleware(\n handle: (req: Request) => Promise<Response | undefined>,\n): (req: IncomingMessage, res: ServerResponse, next: () => void) => void {\n return (req, res, next) => {\n // Before reading the body, which a request for anything else leaves to whoever answers it.\n if (req.url?.includes('/__prosebook/') !== true) return next();\n const forwarded = req.headers['x-forwarded-host'];\n const host = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.headers.host ?? 'localhost';\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers))\n for (const v of Array.isArray(value) ? value : value === undefined ? [] : [value]) headers.append(name, v);\n\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => {\n const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Buffer.concat(chunks);\n handle(new Request(`http://${host}${req.url}`, { method: req.method, headers, body }))\n .then(async (response) => {\n if (response === undefined) return next();\n res.statusCode = response.status;\n response.headers.forEach((value, name) => res.setHeader(name, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n })\n .catch((e: unknown) => {\n res.statusCode = 500;\n res.end(e instanceof Error ? e.message : String(e));\n });\n });\n };\n}\n"],"mappings":"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"}
|