fluent-transpiler 0.4.1 → 0.5.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 +21 -2
- package/cli.js +11 -8
- package/index.d.ts +16 -1
- package/index.js +49 -0
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -35,12 +35,14 @@ npm i -D fluent-transpiler
|
|
|
35
35
|
## CLI
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
Usage: ftl [options] <
|
|
38
|
+
Usage: ftl [options] <inputs...>
|
|
39
39
|
|
|
40
40
|
Compile Fluent (.ftl) files to JavaScript (.js or .mjs)
|
|
41
41
|
|
|
42
42
|
Arguments:
|
|
43
|
-
|
|
43
|
+
inputs Paths to the Fluent file(s) to compile.
|
|
44
|
+
Multiple files are joined in order;
|
|
45
|
+
ids must be unique across the set.
|
|
44
46
|
|
|
45
47
|
Options:
|
|
46
48
|
--locale <locale...> What locale(s) to be used. Multiple can be set to allow for fallback. i.e. en-CA
|
|
@@ -79,3 +81,20 @@ const ftl = await readFile('./path/to/en.ftl', { encoding: 'utf8' })
|
|
|
79
81
|
const js = fluentTranspiler(ftl, { locale: 'en-CA' })
|
|
80
82
|
await writeFile('./path/to/en.mjs', js, 'utf8')
|
|
81
83
|
```
|
|
84
|
+
|
|
85
|
+
### Joining multiple files
|
|
86
|
+
|
|
87
|
+
`compile` also accepts an array of source strings, and `compileFiles` reads and
|
|
88
|
+
joins files from disk. Sources are concatenated in the order supplied; top-level
|
|
89
|
+
message and term ids must be unique across the set.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import { writeFile } from 'node:fs/promises'
|
|
93
|
+
import { compileFiles } from 'fluent-transpiler'
|
|
94
|
+
|
|
95
|
+
const js = await compileFiles(
|
|
96
|
+
['./common.ftl', './brand.ftl', './app.ftl'],
|
|
97
|
+
{ locale: 'en-CA' },
|
|
98
|
+
)
|
|
99
|
+
await writeFile('./en.mjs', js, 'utf8')
|
|
100
|
+
```
|
package/cli.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2026 will Farrell, and fluent-transpiler contributors.
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
|
-
import
|
|
7
|
+
import { compileFiles } from "./index.js";
|
|
8
8
|
|
|
9
9
|
const fileExists = async (filepath) => {
|
|
10
10
|
const stats = await stat(filepath);
|
|
@@ -16,7 +16,10 @@ const fileExists = async (filepath) => {
|
|
|
16
16
|
new Command()
|
|
17
17
|
.name("ftl")
|
|
18
18
|
.description("Compile Fluent (.ftl) files to JavaScript (.js or .mjs)")
|
|
19
|
-
.argument(
|
|
19
|
+
.argument(
|
|
20
|
+
"<inputs...>",
|
|
21
|
+
"Paths to the Fluent file(s) to compile. Multiple files are joined in order; ids must be unique across the set.",
|
|
22
|
+
)
|
|
20
23
|
.requiredOption(
|
|
21
24
|
"--locale <locale...>",
|
|
22
25
|
"What locale(s) to be used. Multiple can be set to allow for fallback. i.e. en-CA",
|
|
@@ -68,14 +71,14 @@ new Command()
|
|
|
68
71
|
"Path to store the resulting JavaScript file. Will be in ESM.",
|
|
69
72
|
),
|
|
70
73
|
)
|
|
71
|
-
.action(async (
|
|
74
|
+
.action(async (inputs, options) => {
|
|
72
75
|
options.comments = options.comments ?? false;
|
|
73
76
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
for (const input of inputs) {
|
|
78
|
+
await fileExists(input);
|
|
79
|
+
}
|
|
77
80
|
|
|
78
|
-
const js =
|
|
81
|
+
const js = await compileFiles(inputs, options);
|
|
79
82
|
if (options.output) {
|
|
80
83
|
await writeFile(options.output, js, "utf8");
|
|
81
84
|
} else {
|
package/index.d.ts
CHANGED
|
@@ -28,7 +28,22 @@ export interface CompileOptions {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Compile Fluent (.ftl) source into a JavaScript ESM string.
|
|
31
|
+
* Pass an array of source strings to join multiple files into one module;
|
|
32
|
+
* top-level message/term ids must be unique across the set.
|
|
31
33
|
*/
|
|
32
|
-
export declare function compile(
|
|
34
|
+
export declare function compile(
|
|
35
|
+
src: string | string[],
|
|
36
|
+
opts?: CompileOptions,
|
|
37
|
+
): string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read and compile one or more Fluent (.ftl) files into a single JavaScript
|
|
41
|
+
* ESM string. Files are joined in the order supplied. Top-level message/term
|
|
42
|
+
* ids must be unique across the set.
|
|
43
|
+
*/
|
|
44
|
+
export declare function compileFiles(
|
|
45
|
+
paths: string[],
|
|
46
|
+
opts?: CompileOptions,
|
|
47
|
+
): Promise<string>;
|
|
33
48
|
|
|
34
49
|
export default compile;
|
package/index.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
// Copyright 2026 will Farrell, and fluent-transpiler contributors.
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { parse } from "@fluent/syntax";
|
|
5
6
|
import { camelCase, constantCase, pascalCase, snakeCase } from "change-case";
|
|
6
7
|
|
|
8
|
+
const collectTopLevelIds = (src) => {
|
|
9
|
+
const { body } = parse(src);
|
|
10
|
+
const ids = [];
|
|
11
|
+
for (const node of body) {
|
|
12
|
+
if (node.type === "Message" || node.type === "Term") {
|
|
13
|
+
ids.push(node.id.name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return ids;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const checkDuplicates = (sources) => {
|
|
20
|
+
const seen = new Map();
|
|
21
|
+
const duplicates = [];
|
|
22
|
+
for (const { label, src } of sources) {
|
|
23
|
+
for (const id of collectTopLevelIds(src)) {
|
|
24
|
+
const prior = seen.get(id);
|
|
25
|
+
if (prior !== undefined && prior !== label) {
|
|
26
|
+
duplicates.push({ id, a: prior, b: label });
|
|
27
|
+
} else if (prior === undefined) {
|
|
28
|
+
seen.set(id, label);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (duplicates.length) {
|
|
33
|
+
const lines = duplicates.map(
|
|
34
|
+
(d) => ` - "${d.id}" defined in ${d.a} and ${d.b}`,
|
|
35
|
+
);
|
|
36
|
+
throw new Error(`Duplicate id(s) found:\n${lines.join("\n")}`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
7
40
|
const reservedWords = new Set([
|
|
8
41
|
"abstract",
|
|
9
42
|
"arguments",
|
|
@@ -81,6 +114,11 @@ const exportDefault = `(id, params) => {
|
|
|
81
114
|
}
|
|
82
115
|
`;
|
|
83
116
|
export const compile = (src, opts) => {
|
|
117
|
+
if (Array.isArray(src)) {
|
|
118
|
+
const sources = src.map((s, i) => ({ label: `source[${i}]`, src: s }));
|
|
119
|
+
checkDuplicates(sources);
|
|
120
|
+
src = src.join("\n\n");
|
|
121
|
+
}
|
|
84
122
|
const options = {
|
|
85
123
|
comments: true,
|
|
86
124
|
errorOnJunk: true,
|
|
@@ -510,4 +548,15 @@ const variableNotation = {
|
|
|
510
548
|
constantCase,
|
|
511
549
|
};
|
|
512
550
|
|
|
551
|
+
export const compileFiles = async (paths, opts) => {
|
|
552
|
+
const sources = await Promise.all(
|
|
553
|
+
paths.map(async (path) => ({
|
|
554
|
+
label: path,
|
|
555
|
+
src: await readFile(path, { encoding: "utf8" }),
|
|
556
|
+
})),
|
|
557
|
+
);
|
|
558
|
+
checkDuplicates(sources);
|
|
559
|
+
return compile(sources.map((s) => s.src).join("\n\n"), opts);
|
|
560
|
+
};
|
|
561
|
+
|
|
513
562
|
export default compile;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
".github"
|
|
4
4
|
],
|
|
5
5
|
"name": "fluent-transpiler",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"description": "Transpile Fluent (ftl) files into optimized, tree-shakable, JavaScript EcmaScript Modules (esm).",
|
|
8
8
|
"main": "index.js",
|
|
9
9
|
"types": "index.d.ts",
|
|
@@ -40,14 +40,21 @@
|
|
|
40
40
|
"test:unit": "node --test --test-force-exit --experimental-test-coverage --test-coverage-lines=100 --test-coverage-branches=100 --test-coverage-functions=100 ./**/*.test.js",
|
|
41
41
|
"test:types": "tstyche",
|
|
42
42
|
"test:perf": "node --test --test-concurrency=1 ./**/*.perf.js",
|
|
43
|
-
"test:sast": "npm run test:sast:license && npm run test:sast:lockfile && npm run test:sast:semgrep && npm run test:sast:trufflehog && npm run test:sast:trivy",
|
|
43
|
+
"test:sast": "npm run test:sast:license && npm run test:sast:lockfile && npm run test:sast:semgrep && npm run test:sast:trufflehog && npm run test:sast:gitleaks && npm run test:sast:actionlint && npm run test:sast:zizmor && npm run test:sast:trivy",
|
|
44
|
+
"test:sast:actionlint": "actionlint",
|
|
45
|
+
"test:sast:gitleaks": "gitleaks detect --source . --redact --no-banner",
|
|
44
46
|
"test:sast:license": "license-check-and-add check -f license.json",
|
|
45
47
|
"test:sast:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-schemes \"https:\" --allowed-hosts npm --validate-integrity --validate-package-names",
|
|
46
48
|
"test:sast:semgrep": "semgrep scan --config auto",
|
|
47
49
|
"test:sast:trivy": "trivy fs --scanners vuln,license --include-dev-deps --ignored-licenses 0BSD,Apache-2.0,BSD-1-Clause,BSD-2-Clause,BSD-3-Clause,CC0-1.0,CC-BY-4.0,ISC,MIT,Python-2.0 --exit-code 1 --disable-telemetry .",
|
|
48
50
|
"test:sast:trufflehog": "trufflehog filesystem --only-verified --log-level=-1 ./",
|
|
51
|
+
"test:sast:zizmor": "zizmor .github/workflows/",
|
|
49
52
|
"test:dast": "npm run test:dast:fuzz",
|
|
50
53
|
"test:dast:fuzz": "node --test ./**/*.fuzz.js",
|
|
54
|
+
"rm": "npm run rm:macos && npm run rm:node_modules && npm run rm:lock",
|
|
55
|
+
"rm:macos": "find . -name '.DS_Store' -type f -delete",
|
|
56
|
+
"rm:lock": "find . -name 'package-lock.json' -type f -delete",
|
|
57
|
+
"rm:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
|
|
51
58
|
"release:license:add": "license-check-and-add add -f license.json",
|
|
52
59
|
"release:license:remove": "license-check-and-add remove -f license.json"
|
|
53
60
|
},
|
|
@@ -80,14 +87,14 @@
|
|
|
80
87
|
},
|
|
81
88
|
"devDependencies": {
|
|
82
89
|
"@biomejs/biome": "^2.0.0",
|
|
83
|
-
"@commitlint/cli": "^
|
|
84
|
-
"@commitlint/config-conventional": "^
|
|
90
|
+
"@commitlint/cli": "^21.0.0",
|
|
91
|
+
"@commitlint/config-conventional": "^21.0.0",
|
|
85
92
|
"@fluent/bundle": "^0.19.0",
|
|
86
93
|
"fast-check": "^4.0.0",
|
|
87
94
|
"husky": "^9.0.0",
|
|
88
95
|
"license-check-and-add": "4.0.5",
|
|
89
96
|
"tinybench": "^6.0.0",
|
|
90
|
-
"tstyche": "^
|
|
97
|
+
"tstyche": "^7.0.0"
|
|
91
98
|
},
|
|
92
99
|
"funding": {
|
|
93
100
|
"type": "github",
|