@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 +21 -7
- package/lib/spec-utils-ext.js +76 -0
- package/lib/spec-utils.js +39 -72
- package/package.json +5 -2
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 =
|
|
97
|
-
|
|
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 =
|
|
103
|
-
const
|
|
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
|
|
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.
|
|
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.
|
|
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",
|