@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 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) hardcodedHits += hits.length;
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() ร—${usesT}` : '',
75
- usesMsg ? `msg\` ร—${usesMsg}` : '',
76
- usesLocalize ? `localize() ร—${usesLocalize}` : '',
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'} ยท Overrides: ${hasOverrides ? 'yes' : 'no'} ยท Languages: ${langs}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techninja/clearstack",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "description": "A no-build web component framework specification โ€” scaffold, validate, and evolve spec-compliant projects",
6
6
  "bin": {