@techninja/clearstack 0.4.34 → 0.4.36

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/lib/check.js CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  import { existsSync } from 'node:fs';
8
8
  import { resolve } from 'node:path';
9
- import { runCmd } from './spec-utils.js';
9
+ import { runCmd, elapsed, normalise, printResult } from './spec-utils.js';
10
10
  import { countFiles, checkFileLines, checkImports } from './spec-scan.js';
11
11
  import { checkI18n } from './spec-i18n.js';
12
12
  import { loadConfig, buildCmds, detectRunner } from './spec-config.js';
13
13
  import { findTypeConfigs } from './spec-types.js';
14
14
 
15
- export { runCmd, runExtCmd, elapsed } from './spec-utils.js';
15
+ export { runCmd, runExtCmd, elapsed, normalise, printResult } from './spec-utils.js';
16
16
  export { findFiles, countFiles, checkFileLines, checkImports } from './spec-scan.js';
17
17
  export { checkI18n } from './spec-i18n.js';
18
18
  export { loadConfig, buildCmds, detectRunner } from './spec-config.js';
@@ -20,7 +20,7 @@ export { loadConfig, buildCmds, detectRunner } from './spec-config.js';
20
20
  /** @typedef {{ key: string, name: string, parent?: string, watchExts?: string[], fix?: () => void, run: (opts?: object) => (boolean | import('./spec-utils.js').CheckResult | Promise<import('./spec-utils.js').CheckResult>) }} Check */
21
21
 
22
22
  /**
23
- *
23
+ * Load project extensions from clearstack.spec.js.
24
24
  */
25
25
  async function loadExtensions(projectDir) {
26
26
  const extPath = resolve(projectDir, 'clearstack.spec.js');
@@ -93,14 +93,28 @@ export async function check(projectDir, scope, opts) {
93
93
  console.log(`Available: ${[...tops, ...parentKeys(checks)].join(', ')}`);
94
94
  process.exit(1);
95
95
  }
96
- const results = await Promise.all(matched.map((c) => c.run(opts)));
97
- if (!results.every((r) => typeof r === 'boolean' ? r : r.pass)) process.exit(1);
96
+ const results = [];
97
+ for (const c of matched) {
98
+ const start = performance.now();
99
+ const raw = await c.run({ ...opts, quiet: true });
100
+ const r = normalise(raw, c.name, elapsed(start));
101
+ printResult(r);
102
+ results.push(r);
103
+ }
104
+ if (!results.every((r) => r.pass)) process.exit(1);
98
105
  return;
99
106
  }
100
107
  console.log('🔍 Clearstack compliance checking now... 💙\n');
101
108
  const runnable = checks.filter((c) => !c.keyBinding);
102
- const results = await Promise.all(runnable.map((c) => c.run(opts)));
103
- const passed = results.filter((r) => typeof r === 'boolean' ? r : r.pass).length;
109
+ const results = [];
110
+ for (const c of runnable) {
111
+ const start = performance.now();
112
+ const raw = await c.run({ ...opts, quiet: true });
113
+ const r = normalise(raw, c.name, elapsed(start));
114
+ printResult(r);
115
+ results.push(r);
116
+ }
117
+ const passed = results.filter((r) => r.pass).length;
104
118
  console.log(`\n${'='.repeat(40)}`);
105
119
  console.log(`${passed}/${results.length} checks passed.`);
106
120
  if (passed < results.length) process.exit(1);
@@ -0,0 +1,76 @@
1
+ /**
2
+ * runExtCmd — shell command runner for clearstack.spec.js extensions.
3
+ * @module lib/spec-utils-ext
4
+ */
5
+
6
+ import { execSync, spawn } from 'node:child_process';
7
+ import { elapsed } from './spec-utils.js';
8
+
9
+ /**
10
+ * Run an arbitrary shell command for a custom extension check.
11
+ * Handles timing, output filtering, and consistent ✅/❌ printing.
12
+ * Ignores lines matching node_modules or any paths in `ignorePaths`.
13
+ * Use this in clearstack.spec.js extensions instead of execSync directly.
14
+ *
15
+ * @param {string} label - Display name for the check
16
+ * @param {string} cmd - Shell command to run
17
+ * @param {{ quiet?: boolean, cwd?: string, ignorePaths?: string[], onProgress?: (line: string) => void }} [opts]
18
+ * @returns {import('./spec-utils.js').CheckResult | Promise<import('./spec-utils.js').CheckResult>}
19
+ */
20
+ export function runExtCmd(label, cmd, opts = {}) {
21
+ const start = performance.now();
22
+ const cwd = opts.cwd ?? process.cwd();
23
+ const suffix = () => ` (${elapsed(start)})`;
24
+ const ignoreRe = [
25
+ /[/\\]node_modules[/\\]/,
26
+ ...(opts.ignorePaths ?? []).map((p) => new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))),
27
+ ];
28
+ const filter = (l) => l.trim() && !ignoreRe.some((re) => re.test(l)) && (!opts.lineFilter || opts.lineFilter(l));
29
+
30
+ if (opts.onProgress) {
31
+ return new Promise((resolve) => {
32
+ const [bin, ...args] = cmd.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/);
33
+ const proc = spawn(bin, args, { cwd, shell: true });
34
+ const lines = [];
35
+ const onLine = (chunk) => {
36
+ for (const l of chunk.toString().split('\n')) {
37
+ if (!filter(l)) continue;
38
+ lines.push(l);
39
+ opts.onProgress(l);
40
+ }
41
+ };
42
+ proc.stdout.on('data', onLine);
43
+ proc.stderr.on('data', onLine);
44
+ proc.on('close', (code) => {
45
+ if (code === 0 || lines.length === 0) {
46
+ if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
47
+ resolve({ pass: true, label, time: elapsed(start) });
48
+ } else {
49
+ if (!opts.quiet) {
50
+ console.log(` ❌ ${label}${suffix()}`);
51
+ for (const l of lines) console.log(` ${l}`);
52
+ }
53
+ resolve({ pass: false, label, time: elapsed(start), errors: lines });
54
+ }
55
+ });
56
+ });
57
+ }
58
+
59
+ try {
60
+ execSync(cmd, { cwd, stdio: 'pipe', encoding: 'utf-8' });
61
+ if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
62
+ return { pass: true, label, time: elapsed(start) };
63
+ } catch (err) {
64
+ const out = ((err.stdout || '') + (err.stderr || '')).trim();
65
+ const errors = out.split('\n').filter(filter);
66
+ if (errors.length === 0) {
67
+ if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
68
+ return { pass: true, label, time: elapsed(start) };
69
+ }
70
+ if (!opts.quiet) {
71
+ console.log(` ❌ ${label}${suffix()}`);
72
+ for (const line of errors) console.log(` ${line}`);
73
+ }
74
+ return { pass: false, label, time: elapsed(start), errors };
75
+ }
76
+ }
package/lib/spec-utils.js CHANGED
@@ -1,10 +1,13 @@
1
1
  /**
2
- * Shared spec utilities — timing and command running.
2
+ * Shared spec utilities — timing, result normalisation, and command running.
3
+ * runExtCmd (for clearstack.spec.js extensions) lives in spec-utils-ext.js.
3
4
  * File scanning lives in spec-scan.js.
4
5
  * @module lib/spec-utils
5
6
  */
6
7
 
7
- import { execSync, spawn } from 'node:child_process';
8
+ import { execSync } from 'node:child_process';
9
+
10
+ export { runExtCmd } from './spec-utils-ext.js';
8
11
 
9
12
  /** @param {number} start */
10
13
  export function elapsed(start) {
@@ -14,6 +17,40 @@ export function elapsed(start) {
14
17
 
15
18
  /** @typedef {{ pass: boolean, label: string, time: string, detail?: string, files?: number, errors?: string[], violations?: object[] }} CheckResult */
16
19
 
20
+ /**
21
+ * Normalise any check return value to a CheckResult.
22
+ * @param {boolean | object} r
23
+ * @param {string} name
24
+ * @param {string} time
25
+ * @returns {CheckResult}
26
+ */
27
+ export function normalise(r, name, time) {
28
+ if (typeof r === 'boolean') return { pass: r, label: name, time };
29
+ return { label: name, time, ...r };
30
+ }
31
+
32
+ /**
33
+ * Print a single CheckResult line (✅/❌ + errors/violations).
34
+ * @param {CheckResult} r
35
+ */
36
+ export function printResult(r) {
37
+ const detail = r.detail ? ` — ${r.detail}` : '';
38
+ const files = r.files ? ` (${r.files} files, ${r.time})` : ` (${r.time})`;
39
+ if (r.pass) {
40
+ console.log(` ✅ ${r.label}${r.detail ? detail + ` (${r.time})` : files}`);
41
+ } else {
42
+ const suffix = r.violations?.length
43
+ ? ` — ${r.violations.length} violation(s):`
44
+ : r.errors?.length
45
+ ? ` — ${r.errors.length} error(s):`
46
+ : '';
47
+ console.log(` ❌ ${r.label}${suffix} (${r.time})`);
48
+ for (const v of r.violations ?? []) console.log(` ${v.file ?? v}: ${v.lines} lines (max ${v.max})`);
49
+ for (const e of r.errors ?? []) console.log(` ${e}`);
50
+ if (!r.violations?.length && !r.errors?.length) console.log(` (no details)`);
51
+ }
52
+ }
53
+
17
54
  /**
18
55
  * Run a shell command, report pass/fail with timing.
19
56
  * @param {string} label
@@ -49,73 +86,3 @@ export function runCmd(label, cmd, cwd, stats, opts) {
49
86
  return { pass: false, label, time: elapsed(start), errors: display };
50
87
  }
51
88
  }
52
-
53
- /**
54
- * Run an arbitrary shell command for a custom extension check.
55
- * Handles timing, output filtering, and consistent ✅/❌ printing.
56
- * Ignores lines matching node_modules or any paths in `ignorePaths`.
57
- * Use this in clearstack.spec.js extensions instead of execSync directly.
58
- *
59
- * @param {string} label - Display name for the check
60
- * @param {string} cmd - Shell command to run
61
- * @param {{ quiet?: boolean, cwd?: string, ignorePaths?: string[] }} [opts]
62
- * @returns {CheckResult}
63
- */
64
- export function runExtCmd(label, cmd, opts = {}) {
65
- const start = performance.now();
66
- const cwd = opts.cwd ?? process.cwd();
67
- const suffix = () => ` (${elapsed(start)})`;
68
- const ignoreRe = [
69
- /[/\\]node_modules[/\\]/,
70
- ...(opts.ignorePaths ?? []).map((p) => new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))),
71
- ];
72
- const filter = (l) => l.trim() && !ignoreRe.some((re) => re.test(l)) && (!opts.lineFilter || opts.lineFilter(l));
73
-
74
- // Streaming mode — used when onProgress is provided
75
- if (opts.onProgress) {
76
- return new Promise((resolve) => {
77
- const [bin, ...args] = cmd.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/);
78
- const proc = spawn(bin, args, { cwd, shell: true });
79
- const lines = [];
80
- const onLine = (chunk) => {
81
- for (const l of chunk.toString().split('\n')) {
82
- if (!filter(l)) continue;
83
- lines.push(l);
84
- opts.onProgress(l);
85
- }
86
- };
87
- proc.stdout.on('data', onLine);
88
- proc.stderr.on('data', onLine);
89
- proc.on('close', (code) => {
90
- if (code === 0 || lines.length === 0) {
91
- if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
92
- resolve({ pass: true, label, time: elapsed(start) });
93
- } else {
94
- if (!opts.quiet) {
95
- console.log(` ❌ ${label}${suffix()}`);
96
- for (const l of lines) console.log(` ${l}`);
97
- }
98
- resolve({ pass: false, label, time: elapsed(start), errors: lines });
99
- }
100
- });
101
- });
102
- }
103
-
104
- try {
105
- execSync(cmd, { cwd, stdio: 'pipe', encoding: 'utf-8' });
106
- if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
107
- return { pass: true, label, time: elapsed(start) };
108
- } catch (err) {
109
- const out = ((err.stdout || '') + (err.stderr || '')).trim();
110
- const errors = out.split('\n').filter(filter);
111
- if (errors.length === 0) {
112
- if (!opts.quiet) console.log(` ✅ ${label}${suffix()}`);
113
- return { pass: true, label, time: elapsed(start) };
114
- }
115
- if (!opts.quiet) {
116
- console.log(` ❌ ${label}${suffix()}`);
117
- for (const line of errors) console.log(` ${line}`);
118
- }
119
- return { pass: false, label, time: elapsed(start), errors };
120
- }
121
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techninja/clearstack",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "type": "module",
5
5
  "description": "A no-build web component framework specification — scaffold, validate, and evolve spec-compliant projects",
6
6
  "bin": {
@@ -54,6 +54,9 @@
54
54
  "hybrids"
55
55
  ],
56
56
  "license": "MIT",
57
+ "overrides": {
58
+ "smol-toml": "^1.8.0"
59
+ },
57
60
  "dependencies": {
58
61
  "@inquirer/prompts": "^8.3.2",
59
62
  "blessed": "^0.1.81",
@@ -62,7 +65,7 @@
62
65
  "devDependencies": {
63
66
  "@open-wc/testing": "^4.0.0",
64
67
  "@types/ws": "^8.18.1",
65
- "@web/test-runner": "^0.20.0",
68
+ "@web/test-runner": "^1.0.0",
66
69
  "@web/test-runner-playwright": "^0.11.0",
67
70
  "eslint": "^10.1.0",
68
71
  "eslint-config-prettier": "^10.1.8",