@techninja/clearstack 0.4.1 โ 0.4.3
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/bin/cli.js +1 -1
- package/lib/check.js +5 -5
- package/lib/spec-i18n.js +36 -7
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -62,7 +62,7 @@ async function run(action) {
|
|
|
62
62
|
} else if (action === 'check') {
|
|
63
63
|
const subs = args.filter((a) => a !== cmd && !a.startsWith('-'));
|
|
64
64
|
const { check } = await import('../lib/check.js');
|
|
65
|
-
await check(process.cwd(), subs.join(' ') || undefined);
|
|
65
|
+
await check(process.cwd(), subs.join(' ') || undefined, { verbose: !!flags.verbose });
|
|
66
66
|
} else if (action === 'report') {
|
|
67
67
|
const { report } = await import('../lib/report.js');
|
|
68
68
|
report(process.cwd(), { json: !!flags.json });
|
package/lib/check.js
CHANGED
|
@@ -14,7 +14,7 @@ export { checkFileLines, runCmd, countFiles, findFiles, elapsed, checkImports }
|
|
|
14
14
|
export { checkI18n } from './spec-i18n.js';
|
|
15
15
|
export { loadConfig, buildCmds, detectRunner } from './spec-config.js';
|
|
16
16
|
|
|
17
|
-
/** @typedef {{ key: string, name: string, parent?: string, run: () => boolean }} Check */
|
|
17
|
+
/** @typedef {{ key: string, name: string, parent?: string, run: (opts?: object) => boolean }} Check */
|
|
18
18
|
|
|
19
19
|
/** Find all jsconfig.json files โ main config + subdirectories. */
|
|
20
20
|
function findTypeConfigs(dir, runner) {
|
|
@@ -53,7 +53,7 @@ export function buildChecks(dir, cfg, cmds) {
|
|
|
53
53
|
{ key: 'lines', name: `Code (max ${cfg.codeMax} lines)`, parent: 'code',
|
|
54
54
|
run: () => checkFileLines(dir, cfg.codeExt, cfg.codeMax, cfg.ignore, `Code (max ${cfg.codeMax} lines)`, { exclude: cfg.testPattern }).pass },
|
|
55
55
|
{ key: 'i18n', name: 'i18n readiness', parent: 'code',
|
|
56
|
-
run: () => checkI18n(dir, cfg.ignore, 'i18n readiness').pass },
|
|
56
|
+
run: (runOpts) => checkI18n(dir, cfg.ignore, 'i18n readiness', runOpts).pass },
|
|
57
57
|
{ key: 'tests', name: `Tests (max ${cfg.testMax} lines)`,
|
|
58
58
|
run: () => checkFileLines(dir, cfg.codeExt, cfg.testMax, cfg.ignore, `Tests (max ${cfg.testMax} lines)`, { include: cfg.testPattern }).pass },
|
|
59
59
|
{ key: 'docs', name: `Docs (max ${cfg.docsMax} lines)`,
|
|
@@ -85,7 +85,7 @@ export function parentKeys(checks) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/** Run the full spec compliance check (used by clearstack CLI and scripts/spec.js). */
|
|
88
|
-
export async function check(projectDir, scope) {
|
|
88
|
+
export async function check(projectDir, scope, opts) {
|
|
89
89
|
const cfg = loadConfig(projectDir);
|
|
90
90
|
const cmds = buildCmds(projectDir);
|
|
91
91
|
const checks = buildChecks(projectDir, cfg, cmds);
|
|
@@ -98,13 +98,13 @@ export async function check(projectDir, scope) {
|
|
|
98
98
|
console.log(`Available: ${[...tops, ...parentKeys(checks)].join(', ')}`);
|
|
99
99
|
process.exit(1);
|
|
100
100
|
}
|
|
101
|
-
const ok = matched.every((c) => c.run());
|
|
101
|
+
const ok = matched.every((c) => c.run(opts));
|
|
102
102
|
if (!ok) process.exit(1);
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
console.log('๐ Clearstack compliance checking now... ๐\n');
|
|
107
|
-
const results = checks.map((c) => c.run());
|
|
107
|
+
const results = checks.map((c) => c.run(opts));
|
|
108
108
|
const passed = results.filter(Boolean).length;
|
|
109
109
|
console.log(`\n${'='.repeat(40)}`);
|
|
110
110
|
console.log(`${passed}/${results.length} checks passed.`);
|
package/lib/spec-i18n.js
CHANGED
|
@@ -17,12 +17,15 @@ const LOC_RE = /\blocalize\s*\(/;
|
|
|
17
17
|
const INIT_RE = /loadLocale|localize\s*\(/;
|
|
18
18
|
const TPL_RE = /html`[\s\S]*?`/g;
|
|
19
19
|
|
|
20
|
+
/** Strip leading > and trailing <$ from a prose match, trim. */
|
|
21
|
+
const cleanHit = (h) => h.replace(/^>\s*/, '').replace(/\s*[<$]$/, '').trim();
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* Check i18n readiness โ informational, always passes.
|
|
22
25
|
* @param {string} root
|
|
23
26
|
* @param {string[]} ignoreDirs
|
|
24
27
|
* @param {string} label
|
|
25
|
-
* @param {{ quiet?: boolean }} [opts]
|
|
28
|
+
* @param {{ quiet?: boolean, verbose?: boolean }} [opts]
|
|
26
29
|
* @returns {CheckResult}
|
|
27
30
|
*/
|
|
28
31
|
export function checkI18n(root, ignoreDirs, label, opts) {
|
|
@@ -41,6 +44,8 @@ export function checkI18n(root, ignoreDirs, label, opts) {
|
|
|
41
44
|
hasInit = false,
|
|
42
45
|
hardcodedHits = 0,
|
|
43
46
|
templateFiles = 0;
|
|
47
|
+
/** @type {Array<{file: string, text: string, line: number}>} */
|
|
48
|
+
const verboseHits = [];
|
|
44
49
|
|
|
45
50
|
for (const file of srcFiles) {
|
|
46
51
|
const src = readFileSync(resolve(root, file), 'utf-8');
|
|
@@ -53,7 +58,17 @@ export function checkI18n(root, ignoreDirs, label, opts) {
|
|
|
53
58
|
templateFiles++;
|
|
54
59
|
for (const tpl of templates) {
|
|
55
60
|
const hits = tpl.match(PROSE_RE);
|
|
56
|
-
if (hits)
|
|
61
|
+
if (hits) {
|
|
62
|
+
hardcodedHits += hits.length;
|
|
63
|
+
if (opts?.verbose) {
|
|
64
|
+
for (const h of hits) {
|
|
65
|
+
const text = cleanHit(h);
|
|
66
|
+
const idx = src.indexOf(text);
|
|
67
|
+
const line = idx >= 0 ? src.slice(0, idx).split('\n').length : 0;
|
|
68
|
+
verboseHits.push({ file, text, line });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
}
|
|
@@ -71,19 +86,33 @@ export function checkI18n(root, ignoreDirs, label, opts) {
|
|
|
71
86
|
const hasOverrides = localesDir ? existsSync(resolve(localesDir, 'overrides.json')) : false;
|
|
72
87
|
|
|
73
88
|
const patternParts = [
|
|
74
|
-
usesT ? `t()
|
|
75
|
-
usesMsg ? `msg\`
|
|
76
|
-
usesLocalize ? `localize()
|
|
89
|
+
usesT ? `t() \u00d7${usesT}` : '',
|
|
90
|
+
usesMsg ? `msg\` \u00d7${usesMsg}` : '',
|
|
91
|
+
usesLocalize ? `localize() \u00d7${usesLocalize}` : '',
|
|
77
92
|
].filter(Boolean);
|
|
78
93
|
|
|
79
94
|
const time = elapsed(start);
|
|
80
95
|
if (!opts?.quiet) {
|
|
81
|
-
const icon = patternParts.length ? '
|
|
96
|
+
const icon = patternParts.length ? '\u2705' : '\u26a0\ufe0f ';
|
|
82
97
|
const pattern = patternParts.length ? patternParts.join(', ') : 'none detected';
|
|
83
98
|
const langs = languages.length ? languages.join(', ') : 'en only';
|
|
84
|
-
const init = `${hasInit ? 'yes' : 'not detected'}
|
|
99
|
+
const init = `${hasInit ? 'yes' : 'not detected'} \u00b7 Overrides: ${hasOverrides ? 'yes' : 'no'} \u00b7 Languages: ${langs}`;
|
|
85
100
|
const unwrapped = hardcodedHits > 0 ? `\n Unwrapped: ~${hardcodedHits} prose strings across ${templateFiles} template files` : '';
|
|
86
101
|
console.log(` ${icon} ${label} (${time})\n Pattern: ${pattern}\n Init: ${init}${unwrapped}`);
|
|
102
|
+
if (opts?.verbose && verboseHits.length) {
|
|
103
|
+
console.log('');
|
|
104
|
+
const byFile = {};
|
|
105
|
+
for (const v of verboseHits) {
|
|
106
|
+
(byFile[v.file] ||= []).push(v);
|
|
107
|
+
}
|
|
108
|
+
for (const [file, entries] of Object.entries(byFile)) {
|
|
109
|
+
console.log(` ${file}`);
|
|
110
|
+
for (const e of entries) {
|
|
111
|
+
const truncated = e.text.length > 60 ? e.text.slice(0, 60) + '\u2026' : e.text;
|
|
112
|
+
console.log(` L${e.line || '?'} ${truncated}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
87
116
|
}
|
|
88
117
|
|
|
89
118
|
return { pass: true, label, time };
|
package/package.json
CHANGED