@uxf/scripts 11.74.4 → 11.76.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 +1 -1
- package/src/uxf-i18n-namespaces-gen/cli.js +2 -3
- package/src/uxf-i18n-namespaces-gen/index.js +224 -115
- package/src/uxf-i18n-namespaces-gen/utils/find-t-function-namespaces.js +2 -1
- package/src/uxf-i18n-namespaces-gen/utils/find-t-function-namespaces.test.js +13 -0
- package/src/uxf-sitemap-check/index.js +1 -1
package/package.json
CHANGED
|
@@ -11,19 +11,18 @@ module.exports = async () => {
|
|
|
11
11
|
.option("o", { alias: "output", default: "i18n-pages.json" })
|
|
12
12
|
.option("n", { alias: "defaultNamespace", array: true, default: ["common"] })
|
|
13
13
|
.option("p", { alias: "pagesDirectory", default: "src/pages" })
|
|
14
|
-
.option("e", { alias: "fileExtension", array: true, default: ["ts", "tsx"] })
|
|
15
14
|
.option("h", { alias: "help" })
|
|
16
15
|
.strict(false)
|
|
17
16
|
.exitProcess(false);
|
|
18
17
|
|
|
19
18
|
try {
|
|
20
|
-
const { help, include, output, defaultNamespace, pagesDirectory
|
|
19
|
+
const { help, include, output, defaultNamespace, pagesDirectory } = cli.parse(argv.slice(2));
|
|
21
20
|
|
|
22
21
|
if (Boolean(help)) {
|
|
23
22
|
return 0;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
await require("./index")(include, output, defaultNamespace, pagesDirectory
|
|
25
|
+
await require("./index")(include, output, defaultNamespace, pagesDirectory);
|
|
27
26
|
} catch (e) {
|
|
28
27
|
console.error(e);
|
|
29
28
|
return 1;
|
|
@@ -1,14 +1,134 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const madge = require("madge");
|
|
3
3
|
const path = require("path");
|
|
4
|
-
const
|
|
5
|
-
const { readFileSync, readdirSync, writeFileSync } = require("fs");
|
|
4
|
+
const { readFileSync, readdirSync, writeFileSync, statSync, existsSync } = require("fs");
|
|
6
5
|
const { findTFunctionNamespaces } = require("./utils/find-t-function-namespaces");
|
|
7
6
|
const { findTransComponentNamespaces } = require("./utils/find-trans-component-namespaces");
|
|
8
7
|
const join = require("node:path").join;
|
|
9
8
|
|
|
9
|
+
const UXF_PACKAGES_PATH = "node_modules/@uxf";
|
|
10
|
+
const FILE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "tsx", "d.ts", "mts", "cts", "d.mts", "d.cts"];
|
|
10
11
|
const TS_CONFIG_PATH = path.resolve(process.cwd(), "tsconfig.json");
|
|
11
|
-
const TS_CONFIG =
|
|
12
|
+
const TS_CONFIG = existsSync(TS_CONFIG_PATH) ? TS_CONFIG_PATH : undefined;
|
|
13
|
+
|
|
14
|
+
// Lazy-read tsconfig for paths alias resolution
|
|
15
|
+
let TS_PATHS_CACHE = null;
|
|
16
|
+
function getTsPaths() {
|
|
17
|
+
if (!TS_CONFIG) return null;
|
|
18
|
+
if (TS_PATHS_CACHE) return TS_PATHS_CACHE;
|
|
19
|
+
try {
|
|
20
|
+
const raw = JSON.parse(readFileSync(TS_CONFIG, "utf8"));
|
|
21
|
+
const compilerOptions = raw && raw.compilerOptions ? raw.compilerOptions : {};
|
|
22
|
+
const baseUrl = compilerOptions.baseUrl ? path.resolve(process.cwd(), compilerOptions.baseUrl) : process.cwd();
|
|
23
|
+
const paths = compilerOptions.paths || {};
|
|
24
|
+
TS_PATHS_CACHE = { baseUrl, paths };
|
|
25
|
+
return TS_PATHS_CACHE;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a file is allowed to be scanned for translations.
|
|
33
|
+
* @param {string} file
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isAllowedFile(file) {
|
|
37
|
+
return !file.includes("node_modules") || file.includes(UXF_PACKAGES_PATH);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a module specifier (bare, relative, or absolute) to a real filesystem path if possible.
|
|
42
|
+
* Falls back to the original specifier when not resolvable.
|
|
43
|
+
* Only allows project files and node_modules/@uxf/* to avoid scanning the entire npm tree.
|
|
44
|
+
* @param {string} spec
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function resolveModuleSpecifier(spec) {
|
|
48
|
+
try {
|
|
49
|
+
if (!spec || typeof spec !== "string") return spec;
|
|
50
|
+
if (existsSync(spec)) return spec;
|
|
51
|
+
|
|
52
|
+
const ts = getTsPaths();
|
|
53
|
+
if (ts?.paths) {
|
|
54
|
+
for (const [pattern, targets] of Object.entries(ts.paths)) {
|
|
55
|
+
const starIndex = pattern.indexOf("*");
|
|
56
|
+
|
|
57
|
+
if (starIndex !== -1) {
|
|
58
|
+
const prefix = pattern.slice(0, starIndex);
|
|
59
|
+
const suffix = pattern.slice(starIndex + 1);
|
|
60
|
+
|
|
61
|
+
if (spec.startsWith(prefix) && spec.endsWith(suffix)) {
|
|
62
|
+
const middle = spec.slice(prefix.length, spec.length - suffix.length);
|
|
63
|
+
|
|
64
|
+
for (const target of targets) {
|
|
65
|
+
const mapped = target.includes("*") ? target.replace("*", middle) : target;
|
|
66
|
+
|
|
67
|
+
const candidates = [
|
|
68
|
+
path.resolve(ts.baseUrl, mapped),
|
|
69
|
+
...FILE_EXTENSIONS.map((e) => path.resolve(ts.baseUrl, mapped + e)),
|
|
70
|
+
...FILE_EXTENSIONS.map((e) => path.join(ts.baseUrl, mapped, "index" + e)),
|
|
71
|
+
].find(existsSync);
|
|
72
|
+
|
|
73
|
+
if (candidates) return candidates;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else if (spec === pattern) {
|
|
77
|
+
const candidate = targets.map((t) => path.resolve(ts.baseUrl, t)).find(existsSync);
|
|
78
|
+
if (candidate) return candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const resolved = require.resolve(spec, { paths: [process.cwd()] });
|
|
84
|
+
return isAllowedFile(resolved) ? resolved : spec;
|
|
85
|
+
} catch {
|
|
86
|
+
return spec;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve import specifier in the context of a file path (handles relative imports correctly).
|
|
92
|
+
* @param {string} spec
|
|
93
|
+
* @param {string} fromFile
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function resolveImportFrom(spec, fromFile) {
|
|
97
|
+
try {
|
|
98
|
+
if (!spec || typeof spec !== "string") return spec;
|
|
99
|
+
|
|
100
|
+
// Relative path from the file's directory
|
|
101
|
+
if (spec.startsWith(".")) {
|
|
102
|
+
const baseDir = path.dirname(fromFile);
|
|
103
|
+
let candidate = path.resolve(baseDir, spec);
|
|
104
|
+
|
|
105
|
+
// Try as file
|
|
106
|
+
for (const e of FILE_EXTENSIONS) {
|
|
107
|
+
const c = candidate + e;
|
|
108
|
+
if (existsSync(c)) return c;
|
|
109
|
+
}
|
|
110
|
+
// Try as directory index
|
|
111
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
112
|
+
for (const e of FILE_EXTENSIONS) {
|
|
113
|
+
const idx = path.join(candidate, "index" + e);
|
|
114
|
+
if (existsSync(idx)) return idx;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Fallback to Node resolver with file dir as base
|
|
119
|
+
const resolved = require.resolve(spec, { paths: [baseDir] });
|
|
120
|
+
if (isAllowedFile(resolved)) {
|
|
121
|
+
return resolved;
|
|
122
|
+
}
|
|
123
|
+
return spec;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Bare/absolute spec
|
|
127
|
+
return resolveModuleSpecifier(spec);
|
|
128
|
+
} catch (_e) {
|
|
129
|
+
return spec;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
12
132
|
|
|
13
133
|
function removeTrailingSlash(str) {
|
|
14
134
|
return str.replace(/\/$/, "");
|
|
@@ -64,83 +184,101 @@ const filePathToRoute = (filePath) => {
|
|
|
64
184
|
return removeTrailingSlash(route);
|
|
65
185
|
};
|
|
66
186
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const resolvedFiles = [];
|
|
187
|
+
async function getMadgeTree(entries, include) {
|
|
188
|
+
// Build madge options; allow traversing UXF packages in node_modules.
|
|
189
|
+
const options = {
|
|
190
|
+
tsConfig: TS_CONFIG,
|
|
191
|
+
fileExtensions: FILE_EXTENSIONS,
|
|
192
|
+
includeNpm: true,
|
|
193
|
+
dependencyFilter: (dependency) => {
|
|
194
|
+
return !dependency.includes("node_modules") || dependency.includes(UXF_PACKAGES_PATH);
|
|
195
|
+
},
|
|
196
|
+
};
|
|
78
197
|
|
|
79
|
-
|
|
80
|
-
|
|
198
|
+
// If include is provided, limit traversal to those prefixes AND node_modules/@uxf.
|
|
199
|
+
if (Array.isArray(include) && include.length > 0) {
|
|
200
|
+
const allowedPrefixes = [...include, UXF_PACKAGES_PATH];
|
|
201
|
+
const searchDirs = new RegExp(`^(?!(${allowedPrefixes.join("|")}))`, "i");
|
|
202
|
+
options.excludeRegExp = [searchDirs];
|
|
81
203
|
}
|
|
82
204
|
|
|
83
|
-
|
|
84
|
-
|
|
205
|
+
const res = await madge(entries, options);
|
|
206
|
+
return res.obj();
|
|
207
|
+
}
|
|
85
208
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const reexportMatches = content.match(/export\s*(?:\{[^}]*\}|\*)\s*from\s*['"](\.[^'"]+)['"]/g);
|
|
209
|
+
async function getFiles(entryPoint, tree) {
|
|
210
|
+
const filesOnPath = [];
|
|
89
211
|
|
|
90
|
-
|
|
91
|
-
|
|
212
|
+
// Always include the entry point file itself (for cases when page does not import anything with translations)
|
|
213
|
+
filesOnPath.push(entryPoint);
|
|
92
214
|
|
|
93
|
-
|
|
94
|
-
const pathMatch = match.match(/from\s*['"]([^'"]+)['"]/);
|
|
95
|
-
if (pathMatch) {
|
|
96
|
-
let relativePath = pathMatch[1];
|
|
215
|
+
getTree([entryPoint], tree, filesOnPath);
|
|
97
216
|
|
|
98
|
-
|
|
99
|
-
let resolvedPath = path.resolve(fileDir, relativePath);
|
|
217
|
+
let flattenFilesOnPath = Array.from(new Set(filesOnPath.flat(Number.POSITIVE_INFINITY)));
|
|
100
218
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
219
|
+
// Helper: extract module specifiers from file content
|
|
220
|
+
const extractSpecs = (content) => {
|
|
221
|
+
const specs = new Set();
|
|
222
|
+
const reImportFrom = /import\s+[^'"\n;]*?from\s*['"]([^'"\n]+)['"]/g;
|
|
223
|
+
const reDynamicImport = /import\s*\(\s*['"]([^'"\n]+)['"]\s*\)/g;
|
|
224
|
+
const reRequire = /require\(\s*['"]([^'"\n]+)['"]\s*\)/g;
|
|
225
|
+
const reExportFrom = /export\s*(?:\{[^}]*\}|\*)\s*from\s*['"]([^'"\n]+)['"]/g;
|
|
226
|
+
for (const re of [reImportFrom, reDynamicImport, reRequire, reExportFrom]) {
|
|
227
|
+
for (const m of content.matchAll(re)) {
|
|
228
|
+
if (m && m[1]) specs.add(m[1]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return Array.from(specs);
|
|
232
|
+
};
|
|
104
233
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
234
|
+
// Resolve re-exports to get actual component files and traverse imports recursively
|
|
235
|
+
const resolvedFiles = [];
|
|
236
|
+
const queue = [];
|
|
237
|
+
const visited = new Set();
|
|
238
|
+
|
|
239
|
+
// Seed queue with initial files from madge tree
|
|
240
|
+
for (const file of flattenFilesOnPath) {
|
|
241
|
+
if (file && typeof file === "string") {
|
|
242
|
+
const fsPath = resolveModuleSpecifier(file);
|
|
243
|
+
if (typeof fsPath === "string") {
|
|
244
|
+
try {
|
|
245
|
+
if (isAllowedFile(fsPath) && statSync(fsPath).isFile()) {
|
|
246
|
+
queue.push(fsPath);
|
|
112
247
|
}
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
113
252
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
253
|
+
while (queue.length > 0) {
|
|
254
|
+
const fsPath = queue.shift();
|
|
255
|
+
if (!fsPath || visited.has(fsPath)) continue;
|
|
256
|
+
visited.add(fsPath);
|
|
257
|
+
resolvedFiles.push(fsPath);
|
|
258
|
+
|
|
259
|
+
if (!existsSync(fsPath)) continue;
|
|
260
|
+
|
|
261
|
+
// Scan direct imports/requires/exports-from and follow them
|
|
262
|
+
try {
|
|
263
|
+
const content = readFileSync(fsPath, "utf8");
|
|
264
|
+
const specs = extractSpecs(content);
|
|
265
|
+
for (const spec of specs) {
|
|
266
|
+
const p = resolveImportFrom(spec, fsPath);
|
|
267
|
+
// Only follow project files and UXF packages (resolvers already filter others)
|
|
268
|
+
if (typeof p === "string" && (!p.includes("node_modules") || p.includes(UXF_PACKAGES_PATH))) {
|
|
269
|
+
if (!visited.has(p)) {
|
|
270
|
+
try {
|
|
271
|
+
if (isAllowedFile(fsPath) && statSync(p).isFile()) {
|
|
272
|
+
queue.push(p);
|
|
123
273
|
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!foundFile) {
|
|
128
|
-
console.warn(`Could not resolve re-export: ${relativePath} from ${filePath}`);
|
|
274
|
+
} catch {}
|
|
129
275
|
}
|
|
130
276
|
}
|
|
131
277
|
}
|
|
132
|
-
|
|
133
|
-
// If we found re-exports, don't include the original index file
|
|
134
|
-
if (resolvedFiles.length > 0) {
|
|
135
|
-
return resolvedFiles;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.warn(`Error reading file ${filePath}:`, error.message);
|
|
278
|
+
} catch {}
|
|
140
279
|
}
|
|
141
280
|
|
|
142
|
-
|
|
143
|
-
return [filePath];
|
|
281
|
+
return Array.from(new Set(resolvedFiles));
|
|
144
282
|
}
|
|
145
283
|
|
|
146
284
|
/**
|
|
@@ -148,76 +286,47 @@ function resolveReexports(filePath) {
|
|
|
148
286
|
* @param output string
|
|
149
287
|
* @param defaultNamespaces string[]
|
|
150
288
|
* @param pagesDirectory string
|
|
151
|
-
* @param fileExtensions string[]
|
|
152
289
|
*/
|
|
153
|
-
function main(include, output, defaultNamespaces, pagesDirectory
|
|
290
|
+
async function main(include, output, defaultNamespaces, pagesDirectory) {
|
|
154
291
|
const result = { "*": defaultNamespaces };
|
|
155
|
-
// Negative lookahead – ignore searching for any files
|
|
156
|
-
// that aren't part of our include list
|
|
157
|
-
const searchDirs = new RegExp(`^(?!(${include.join("|")}))`, "i");
|
|
158
292
|
|
|
159
293
|
const pages = walk(pagesDirectory).flat(Number.POSITIVE_INFINITY);
|
|
160
294
|
|
|
161
|
-
madge
|
|
162
|
-
|
|
163
|
-
excludeRegExp: [searchDirs],
|
|
164
|
-
fileExtensions: fileExtensions,
|
|
165
|
-
includeNpm: true,
|
|
166
|
-
dependencyFilter: (dependency) => {
|
|
167
|
-
return !dependency.includes("node_modules") || dependency.includes("node_modules/@uxf");
|
|
168
|
-
},
|
|
169
|
-
}).then((res) => {
|
|
170
|
-
const tree = res.obj();
|
|
171
|
-
|
|
172
|
-
for (const entryPoint of pages) {
|
|
173
|
-
let namespaces = [];
|
|
174
|
-
const filesOnPath = [];
|
|
175
|
-
|
|
176
|
-
// Always include the entry point file itself (for cases when page does not import anything with translations)
|
|
177
|
-
filesOnPath.push(entryPoint);
|
|
295
|
+
// Build a global dependency tree rooted at project to leverage madge resolution across aliases/packages
|
|
296
|
+
const tree = await getMadgeTree(process.cwd(), include);
|
|
178
297
|
|
|
179
|
-
|
|
298
|
+
for (const entryPoint of pages) {
|
|
299
|
+
let namespaces = [];
|
|
180
300
|
|
|
181
|
-
|
|
301
|
+
const uniqueResolvedFiles = await getFiles(entryPoint, tree);
|
|
182
302
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (file && typeof file === "string") {
|
|
187
|
-
resolvedFiles.push(...resolveReexports(file));
|
|
188
|
-
}
|
|
303
|
+
for (const file of uniqueResolvedFiles) {
|
|
304
|
+
if (!file || !existsSync(file)) {
|
|
305
|
+
continue;
|
|
189
306
|
}
|
|
190
307
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!file || !fs.existsSync(file)) {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const fileContent = readFileSync(file).toString();
|
|
200
|
-
const fileNamespaces = findNamespaces(fileContent);
|
|
308
|
+
try {
|
|
309
|
+
const fileContent = readFileSync(file).toString();
|
|
310
|
+
const fileNamespaces = findNamespaces(fileContent);
|
|
201
311
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
312
|
+
namespaces = [...namespaces, ...fileNamespaces];
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.warn(`Error reading file ${file}:`, error.message);
|
|
206
315
|
}
|
|
316
|
+
}
|
|
207
317
|
|
|
208
|
-
|
|
318
|
+
namespaces = Array.from(new Set(namespaces)).sort();
|
|
209
319
|
|
|
210
|
-
|
|
320
|
+
const page = filePathToRoute(entryPoint);
|
|
211
321
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
322
|
+
if (namespaces.length > 0) {
|
|
323
|
+
result[page] = namespaces;
|
|
215
324
|
}
|
|
325
|
+
}
|
|
216
326
|
|
|
217
|
-
|
|
327
|
+
writeFileSync(path.resolve(process.cwd(), output), JSON.stringify(result, null, 4));
|
|
218
328
|
|
|
219
|
-
|
|
220
|
-
});
|
|
329
|
+
console.log("Namespaces generated!");
|
|
221
330
|
}
|
|
222
331
|
|
|
223
332
|
module.exports = main;
|
|
@@ -7,7 +7,18 @@ const content = `
|
|
|
7
7
|
{t("basic-dash:title-dash")}
|
|
8
8
|
{t("basicCamel:titleCamel")}
|
|
9
9
|
{t("basic_underscore:title_underscore")}
|
|
10
|
+
{t(
|
|
11
|
+
"prettier-formatting:title_underscore"
|
|
12
|
+
)}
|
|
13
|
+
{t(
|
|
14
|
+
"prettier-formatting-with-params:title_underscore",
|
|
15
|
+
{
|
|
16
|
+
param: 1,param: 1,param: 1,param: 1,
|
|
17
|
+
param: 1,param: 1,param: 1,param: 1,
|
|
18
|
+
}
|
|
19
|
+
)}
|
|
10
20
|
</div>
|
|
21
|
+
{TestfunctionEndingwitht("this-should-fail:title_underscore")}
|
|
11
22
|
<div title={
|
|
12
23
|
condition
|
|
13
24
|
? 'title'
|
|
@@ -26,6 +37,8 @@ describe("find namespaces in t functions", function () {
|
|
|
26
37
|
"basic-dash",
|
|
27
38
|
"basicCamel",
|
|
28
39
|
"basic_underscore",
|
|
40
|
+
"prettier-formatting",
|
|
41
|
+
"prettier-formatting-with-params",
|
|
29
42
|
"multiline-with-parameters",
|
|
30
43
|
"with-parameters",
|
|
31
44
|
"in-translation-parameter",
|
|
@@ -60,7 +60,7 @@ function fetcher(url, options) {
|
|
|
60
60
|
const headers = {
|
|
61
61
|
"User-Agent": "got",
|
|
62
62
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
63
|
-
"Accept-Language": "
|
|
63
|
+
"Accept-Language": "cs-CZ;q=0.8,cs;q=0.7",
|
|
64
64
|
"Cache-Control": "no-cache",
|
|
65
65
|
Connection: "keep-alive",
|
|
66
66
|
Pragma: "no-cache",
|