@formatjs/cli-lib 8.3.1 → 8.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/package.json CHANGED
@@ -1,32 +1,36 @@
1
1
  {
2
2
  "name": "@formatjs/cli-lib",
3
3
  "description": "Lib for CLI for formatjs.",
4
- "version": "8.3.1",
4
+ "version": "8.4.0",
5
5
  "license": "MIT",
6
6
  "author": "Linjie Ding <linjie@airtable.com>",
7
7
  "type": "module",
8
+ "types": "index.d.ts",
9
+ "exports": {
10
+ ".": "./index.js"
11
+ },
8
12
  "engines": {
9
13
  "node": ">= 20.12.0"
10
14
  },
11
15
  "dependencies": {
12
16
  "@types/estree": "^1.0.8",
13
17
  "@types/fs-extra": "^11.0.4",
14
- "@types/node": "^22.19.5",
18
+ "@types/node": "22 || 24",
15
19
  "commander": "^14.0.0",
16
20
  "fast-glob": "^3.3.3",
17
21
  "fs-extra": "^11.3.3",
18
22
  "json-stable-stringify": "^1.3.0",
19
23
  "loud-rejection": "^2",
20
- "tslib": "^2.8.1",
21
24
  "typescript": "^5.6.0",
22
- "@formatjs/icu-messageformat-parser": "3.5.1",
23
- "@formatjs/ts-transformer": "4.4.0",
24
- "@formatjs/icu-skeleton-parser": "2.1.1"
25
+ "@formatjs/icu-messageformat-parser": "3.5.2",
26
+ "@formatjs/ts-transformer": "4.4.1",
27
+ "@formatjs/icu-skeleton-parser": "2.1.2"
25
28
  },
26
29
  "peerDependencies": {
27
30
  "@glimmer/syntax": "^0.84.3 || ^0.95.0",
28
31
  "@vue/compiler-core": "^3.5.0",
29
32
  "content-tag": "^4.1.0",
33
+ "svelte": "^5.0.0",
30
34
  "vue": "^3.5.0"
31
35
  },
32
36
  "bugs": "https://github.com/formatjs/formatjs/issues",
@@ -70,6 +74,9 @@
70
74
  },
71
75
  "content-tag": {
72
76
  "optional": true
77
+ },
78
+ "svelte": {
79
+ "optional": true
73
80
  }
74
81
  },
75
82
  "repository": "formatjs/formatjs.git"
package/src/compile.d.ts CHANGED
@@ -33,6 +33,10 @@ export interface Opts {
33
33
  * any attributes
34
34
  */
35
35
  ignoreTag?: boolean;
36
+ /**
37
+ * An AbortSignal to cancel the compilation
38
+ */
39
+ signal?: AbortSignal;
36
40
  }
37
41
  /**
38
42
  * Aggregate `inputFiles` into a single JSON blob and compile.
package/src/compile.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { parse } from "@formatjs/icu-messageformat-parser";
2
- import { outputFile, readJSON } from "fs-extra/esm";
2
+ import { outputFile } from "fs-extra/esm";
3
+ import { readFile } from "fs/promises";
3
4
  import * as stringifyNs from "json-stable-stringify";
4
5
  import { debug, warn, writeStdout } from "./console_utils.js";
5
6
  import { resolveBuiltinFormatter } from "./formatters/index.js";
@@ -16,12 +17,16 @@ const stringify = stringifyNs.default || stringifyNs;
16
17
  */
17
18
  export async function compile(inputFiles, opts = {}) {
18
19
  debug("Compiling files:", inputFiles);
19
- const { ast, format, pseudoLocale, skipErrors, ignoreTag } = opts;
20
+ const { ast, format, pseudoLocale, skipErrors, ignoreTag, signal } = opts;
21
+ signal?.throwIfAborted();
20
22
  const formatter = await resolveBuiltinFormatter(format);
21
23
  const messages = {};
22
24
  const messageAsts = {};
23
25
  const idsWithFileName = {};
24
- const compiledFiles = await Promise.all(inputFiles.map((f) => readJSON(f).then(formatter.compile)));
26
+ const compiledFiles = await Promise.all(inputFiles.map((f) => readFile(f, {
27
+ encoding: "utf8",
28
+ signal
29
+ }).then((content) => JSON.parse(content)).then(formatter.compile)));
25
30
  debug("Compiled files:", compiledFiles);
26
31
  for (let i = 0; i < inputFiles.length; i++) {
27
32
  const inputFile = inputFiles[i];
package/src/extract.d.ts CHANGED
@@ -60,6 +60,10 @@ export type ExtractOpts = Opts & {
60
60
  * Whether to hoist selectors & flatten sentences
61
61
  */
62
62
  flatten?: boolean;
63
+ /**
64
+ * An AbortSignal to cancel the extraction
65
+ */
66
+ signal?: AbortSignal;
63
67
  } & Pick<Opts, "onMsgExtracted" | "onMetaExtracted">;
64
68
  /**
65
69
  * Extract strings from source files
package/src/extract.js CHANGED
@@ -60,6 +60,10 @@ async function processFile(source, fn, { idInterpolationPattern, ...opts }) {
60
60
  debug("Processing %s using vue extractor", fn);
61
61
  const { parseFile } = await import("./vue_extractor.js");
62
62
  parseFile(source, fn, scriptParseFn);
63
+ } else if (fn.endsWith(".svelte")) {
64
+ debug("Processing %s using svelte extractor", fn);
65
+ const { parseFile } = await import("./svelte_extractor.js");
66
+ parseFile(source, fn, scriptParseFn);
63
67
  } else if (fn.endsWith(".hbs")) {
64
68
  debug("Processing %s using hbs extractor", fn);
65
69
  const { parseFile } = await import("./hbs_extractor.js");
@@ -90,7 +94,7 @@ async function processFile(source, fn, { idInterpolationPattern, ...opts }) {
90
94
  * matters for some `format`
91
95
  */
92
96
  export async function extract(files, extractOpts) {
93
- const { throws, readFromStdin, ...opts } = extractOpts;
97
+ const { throws, readFromStdin, signal, ...opts } = extractOpts;
94
98
  // When throws is not explicitly true, we want to collect partial results
95
99
  const shouldThrow = throws === true;
96
100
  // Pass throws option to transformer for per-message error handling
@@ -114,7 +118,10 @@ export async function extract(files, extractOpts) {
114
118
  if (!shouldThrow) {
115
119
  const settledResults = await Promise.allSettled(files.map(async (fn) => {
116
120
  debug("Extracting file:", fn);
117
- const source = await readFile(fn, "utf8");
121
+ const source = await readFile(fn, {
122
+ encoding: "utf8",
123
+ signal
124
+ });
118
125
  return processFile(source, fn, optsWithThrows);
119
126
  }));
120
127
  rawResults = settledResults.map((result) => {
@@ -128,7 +135,10 @@ export async function extract(files, extractOpts) {
128
135
  } else {
129
136
  rawResults = await Promise.all(files.map(async (fn) => {
130
137
  debug("Extracting file:", fn);
131
- const source = await readFile(fn, "utf8");
138
+ const source = await readFile(fn, {
139
+ encoding: "utf8",
140
+ signal
141
+ });
132
142
  return processFile(source, fn, optsWithThrows);
133
143
  }));
134
144
  }
@@ -0,0 +1,2 @@
1
+ export type ScriptParseFn = (source: string) => void;
2
+ export declare function parseFile(source: string, filename: string, parseScriptFn: ScriptParseFn): void;
@@ -0,0 +1,117 @@
1
+ import { parse } from "svelte/compiler";
2
+ function walkFragment(fragment, source, parseScriptFn) {
3
+ if (!fragment || !fragment.nodes) {
4
+ return;
5
+ }
6
+ for (const node of fragment.nodes) {
7
+ walkNode(node, source, parseScriptFn);
8
+ }
9
+ }
10
+ function extractExpression(expression, source, parseScriptFn) {
11
+ if (!expression || expression.start == null || expression.end == null) {
12
+ return;
13
+ }
14
+ const content = source.slice(expression.start, expression.end);
15
+ try {
16
+ parseScriptFn(`(${content})`);
17
+ } catch (e) {
18
+ console.warn(`Failed to parse "${content}". Ignore this if content has no extractable message`, e);
19
+ }
20
+ }
21
+ function walkNode(node, source, parseScriptFn) {
22
+ switch (node.type) {
23
+ case "ExpressionTag":
24
+ extractExpression(node.expression, source, parseScriptFn);
25
+ break;
26
+ case "ConstTag":
27
+ // {@const x = expr} — the declaration contains the expression
28
+ if (node.declaration) {
29
+ const content = source.slice(node.declaration.start, node.declaration.end);
30
+ try {
31
+ parseScriptFn(content);
32
+ } catch (e) {
33
+ console.warn(`Failed to parse const declaration. Ignore this if content has no extractable message`, e);
34
+ }
35
+ }
36
+ break;
37
+ case "IfBlock":
38
+ extractExpression(node.test, source, parseScriptFn);
39
+ walkFragment(node.consequent, source, parseScriptFn);
40
+ walkFragment(node.alternate, source, parseScriptFn);
41
+ break;
42
+ case "EachBlock":
43
+ extractExpression(node.expression, source, parseScriptFn);
44
+ if (node.key) {
45
+ extractExpression(node.key, source, parseScriptFn);
46
+ }
47
+ walkFragment(node.body, source, parseScriptFn);
48
+ walkFragment(node.fallback, source, parseScriptFn);
49
+ break;
50
+ case "AwaitBlock":
51
+ extractExpression(node.expression, source, parseScriptFn);
52
+ walkFragment(node.pending, source, parseScriptFn);
53
+ walkFragment(node.then, source, parseScriptFn);
54
+ walkFragment(node.catch, source, parseScriptFn);
55
+ break;
56
+ case "KeyBlock":
57
+ extractExpression(node.expression, source, parseScriptFn);
58
+ walkFragment(node.fragment, source, parseScriptFn);
59
+ break;
60
+ case "SnippetBlock":
61
+ walkFragment(node.body, source, parseScriptFn);
62
+ break;
63
+ case "RegularElement":
64
+ case "Component":
65
+ case "SvelteComponent":
66
+ case "SvelteElement":
67
+ case "SvelteHead":
68
+ case "SvelteBody":
69
+ case "SvelteWindow":
70
+ case "SvelteDocument":
71
+ case "SlotElement":
72
+ case "SvelteSelf":
73
+ case "SvelteFragment":
74
+ // Process attributes
75
+ if (node.attributes) {
76
+ for (const attr of node.attributes) {
77
+ if (attr.type === "Attribute") {
78
+ if (Array.isArray(attr.value)) {
79
+ for (const v of attr.value) {
80
+ if (v.type === "ExpressionTag") {
81
+ extractExpression(v.expression, source, parseScriptFn);
82
+ }
83
+ }
84
+ } else if (typeof attr.value === "object" && attr.value !== null && attr.value.type === "ExpressionTag") {
85
+ extractExpression(attr.value.expression, source, parseScriptFn);
86
+ }
87
+ } else if (attr.type === "SpreadAttribute") {
88
+ extractExpression(attr.expression, source, parseScriptFn);
89
+ } else if (attr.expression) {
90
+ // Directive nodes (OnDirective, BindDirective, etc.)
91
+ extractExpression(attr.expression, source, parseScriptFn);
92
+ }
93
+ }
94
+ }
95
+ // Process children
96
+ walkFragment(node.fragment, source, parseScriptFn);
97
+ break;
98
+ }
99
+ }
100
+ export function parseFile(source, filename, parseScriptFn) {
101
+ const ast = parse(source, {
102
+ modern: true,
103
+ filename
104
+ });
105
+ // Walk the template fragment
106
+ walkFragment(ast.fragment, source, parseScriptFn);
107
+ // Process <script> block
108
+ if (ast.instance) {
109
+ const program = ast.instance.content;
110
+ parseScriptFn(source.slice(program.start, program.end));
111
+ }
112
+ // Process <script context="module"> / <script module> block
113
+ if (ast.module) {
114
+ const program = ast.module.content;
115
+ parseScriptFn(source.slice(program.start, program.end));
116
+ }
117
+ }