@chemx/starter-kit 26.9.25-943 → 26.9.26-253

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.
@@ -0,0 +1,224 @@
1
+ import { executeBuild } from './build/executor.js';
2
+ import { findProjectRoot } from './build/detector.js';
3
+ import { resolvePackageManager, loadLocalPackageJson } from './build/detector.js';
4
+ import { ANSI } from './theme.js';
5
+ import { parseCommandFromArgs, stripAnsi, checkNodeModules } from './verify-helpers.js';
6
+
7
+ export const detectLintCommand = (customCmd, cwd = process.cwd(), isFix = false, targetPath = null) => {
8
+ if (customCmd && customCmd.trim().length > 0) return customCmd.trim();
9
+ const pkg = loadLocalPackageJson(cwd);
10
+ const scripts = (pkg && pkg.scripts) || {};
11
+ const pm = resolvePackageManager(cwd);
12
+
13
+ const cleanTarget = targetPath ? targetPath.trim() : null;
14
+
15
+ if (cleanTarget) {
16
+ return `npx eslint ${isFix ? '--fix ' : ''}${cleanTarget}`;
17
+ }
18
+
19
+ if (isFix) {
20
+ if (scripts['lint:fix']) return `${pm} run lint:fix`;
21
+ if (scripts.lint) return `${pm} run lint -- --fix`;
22
+ return 'npx eslint --fix .';
23
+ }
24
+
25
+ if (scripts.lint) return `${pm} run lint`;
26
+ return 'npx eslint .';
27
+ };
28
+
29
+ export const parseLintOutput = (stdout = '', stderr = '') => {
30
+ const combined = `${stdout}\n${stderr}`;
31
+ const lines = combined.split(/\r?\n/);
32
+
33
+ const errors = [];
34
+ const warnings = [];
35
+ let currentFile = null;
36
+ let fixableCount = 0;
37
+
38
+ // Pattern: /path/to/file.ext or file.ext (standalone line with no leading whitespace or : )
39
+ const fileHeaderRegex = /^([^\s].+\.[a-zA-Z0-9]+)$/;
40
+ // Pattern: 156:51 error message rule-name
41
+ const issueRegex = /^\s*([0-9]+):([0-9]+)\s+(error|warning)\s+(.+?)\s+([a-zA-Z0-9_/@-]+)\s*$/;
42
+ // Pattern: fixable count
43
+ const fixableRegex = /([0-9]+)\s+(?:errors?|warnings?|problems?).*potentially fixable with the `--fix` option/i;
44
+
45
+ for (const rawLine of lines) {
46
+ const clean = stripAnsi(rawLine);
47
+ const trimmed = clean.trim();
48
+ if (!trimmed) continue;
49
+
50
+ const fixMatch = clean.match(fixableRegex);
51
+ if (fixMatch) {
52
+ fixableCount = parseInt(fixMatch[1], 10);
53
+ continue;
54
+ }
55
+
56
+ const fileMatch = clean.match(fileHeaderRegex);
57
+ if (fileMatch && !clean.includes('✖') && !clean.includes('✔') && !clean.includes('problems')) {
58
+ currentFile = fileMatch[1].trim();
59
+ continue;
60
+ }
61
+
62
+ const issueMatch = clean.match(issueRegex);
63
+ if (issueMatch) {
64
+ const issue = {
65
+ file: currentFile || 'unknown',
66
+ line: parseInt(issueMatch[1], 10),
67
+ column: parseInt(issueMatch[2], 10),
68
+ severity: issueMatch[3].toLowerCase(),
69
+ message: issueMatch[4].trim(),
70
+ ruleId: issueMatch[5].trim()
71
+ };
72
+
73
+ if (issue.severity === 'error') {
74
+ errors.push(issue);
75
+ } else {
76
+ warnings.push(issue);
77
+ }
78
+ }
79
+ }
80
+
81
+ return {
82
+ errors,
83
+ warnings,
84
+ errorCount: errors.length,
85
+ warningCount: warnings.length,
86
+ fixableCount
87
+ };
88
+ };
89
+
90
+ export const runLintAudit = async (rawArgs = [], isCli = false, options = {}) => {
91
+ if (rawArgs.includes('--help') || rawArgs.includes('-h') || rawArgs.includes('help')) {
92
+ const isJson = rawArgs.includes('--json');
93
+ if (isJson) {
94
+ process.stdout.write(JSON.stringify({ help: true, success: true }) + '\n');
95
+ } else {
96
+ process.stdout.write([
97
+ `${ANSI.BOLD}USAGE${ANSI.RESET}`,
98
+ ` chemx lint [path] [options] [-- <command>]`,
99
+ '',
100
+ `${ANSI.BOLD}OPTIONS${ANSI.RESET}`,
101
+ ` --fix Automatically fix fixable lint and tree-formatting errors`,
102
+ ` --json Output structured diagnostics as JSON for AI agents`,
103
+ ` --raw Do not capture or format output`,
104
+ ` -h, --help Show this help message`,
105
+ '',
106
+ `${ANSI.BOLD}EXAMPLES${ANSI.RESET}`,
107
+ ` chemx lint`,
108
+ ` chemx lint --fix`,
109
+ ` chemx lint apps/youmeos/components/blueprints/desktop/u-desktop.vue --fix`,
110
+ ''
111
+ ].join('\n'));
112
+ }
113
+ if (isCli) process.exit(0);
114
+ return { help: true, success: true };
115
+ }
116
+
117
+ const isJson = rawArgs.includes('--json') || options.json === true;
118
+ const isRaw = rawArgs.includes('--raw') || options.raw === true;
119
+ const isFix = rawArgs.includes('--fix') || options.fix === true;
120
+ const customCmd = parseCommandFromArgs(rawArgs) || options.command;
121
+ const cwd = findProjectRoot(options.cwd || process.cwd());
122
+
123
+ // Extract optional target path argument (first non-flag argument)
124
+ const targetPath = rawArgs.find((arg) => !arg.startsWith('-') && arg !== 'lint' && arg !== 'check:lint' && arg !== 'eslint') || null;
125
+
126
+ const nmStatus = checkNodeModules(cwd);
127
+ if (nmStatus) {
128
+ const friendlyMsg = nmStatus.msg('linting');
129
+ const report = {
130
+ success: false,
131
+ exitCode: 1,
132
+ command: customCmd || 'lint',
133
+ durationMs: 0,
134
+ errorCount: 1,
135
+ warningCount: 0,
136
+ fixableCount: 0,
137
+ executionError: friendlyMsg,
138
+ errors: [
139
+ {
140
+ file: 'package.json',
141
+ line: 1,
142
+ column: 1,
143
+ severity: 'error',
144
+ ruleId: 'MISSING_NODE_MODULES',
145
+ message: friendlyMsg
146
+ }
147
+ ],
148
+ warnings: []
149
+ };
150
+ if (isJson) {
151
+ if (options.print !== false) {
152
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
153
+ }
154
+ if (isCli) process.exit(1);
155
+ return report;
156
+ }
157
+ if (options.print !== false) {
158
+ process.stdout.write(`\n ${ANSI.RED}✖${ANSI.RESET} ${ANSI.BOLD}${friendlyMsg}${ANSI.RESET}\n\n`);
159
+ }
160
+ if (isCli) process.exit(1);
161
+ return report;
162
+ }
163
+
164
+ const command = detectLintCommand(customCmd, cwd, isFix, targetPath);
165
+ const execution = await executeBuild(command, cwd, { raw: isRaw });
166
+ const parsed = parseLintOutput(execution.stdout, execution.stderr);
167
+
168
+ // If exitCode is non-zero but regex didn't parse items (e.g. fatal syntax error), capture raw error
169
+ let executionError = null;
170
+ if (execution.exitCode !== 0 && parsed.errors.length === 0) {
171
+ const rawLines = `${execution.stderr}\n${execution.stdout}`.split('\n').map((l) => l.trim()).filter(Boolean);
172
+ executionError = rawLines.find((l) => /error|not found|failed/i.test(l)) || rawLines[0] || `Command exited with code ${execution.exitCode}`;
173
+ }
174
+
175
+ const isSuccess = execution.exitCode === 0 && parsed.errors.length === 0;
176
+
177
+ const report = {
178
+ success: isSuccess,
179
+ exitCode: execution.exitCode,
180
+ command,
181
+ durationMs: execution.durationMs,
182
+ errorCount: parsed.errorCount,
183
+ warningCount: parsed.warningCount,
184
+ fixableCount: parsed.fixableCount,
185
+ executionError,
186
+ errors: parsed.errors,
187
+ warnings: parsed.warnings
188
+ };
189
+
190
+ if (isJson) {
191
+ if (options.print !== false) {
192
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
193
+ }
194
+ if (isCli) process.exit(report.success ? 0 : 1);
195
+ return report;
196
+ }
197
+
198
+ if (options.print !== false) {
199
+ if (report.success) {
200
+ const warnInfo = report.warningCount > 0 ? ` with ${report.warningCount} warning(s)` : '';
201
+ process.stdout.write(` ${ANSI.LIME}✔${ANSI.RESET} ${ANSI.BOLD}Linter passed${warnInfo}${ANSI.RESET} ${ANSI.DIM}(in ${report.durationMs}ms)${ANSI.RESET}\n`);
202
+ } else {
203
+ if (report.executionError) {
204
+ process.stdout.write(`\n ${ANSI.RED}✖${ANSI.RESET} ${ANSI.BOLD}Lint Execution Error:${ANSI.RESET} ${report.executionError}\n\n`);
205
+ } else {
206
+ process.stdout.write(`\n ${ANSI.RED}✖${ANSI.RESET} ${ANSI.BOLD}Lint Failures (${report.errorCount} error${report.errorCount === 1 ? '' : 's'}${report.warningCount > 0 ? `, ${report.warningCount} warning(s)` : ''})${ANSI.RESET}\n`);
207
+ const displayLimit = 10;
208
+ for (const err of report.errors.slice(0, displayLimit)) {
209
+ process.stdout.write(` ${ANSI.RED}✖${ANSI.RESET} ${ANSI.BOLD}${err.file}:${err.line}:${err.column}${ANSI.RESET} ${err.message} ${ANSI.DIM}(${err.ruleId})${ANSI.RESET}\n`);
210
+ }
211
+ if (report.errors.length > displayLimit) {
212
+ process.stdout.write(` ${ANSI.DIM}...and ${report.errors.length - displayLimit} more error(s)${ANSI.RESET}\n`);
213
+ }
214
+ if (report.fixableCount > 0) {
215
+ process.stdout.write(`\n ${ANSI.CYAN}💡 ${report.fixableCount} issue(s) potentially fixable with 'chemx lint --fix'${ANSI.RESET}\n`);
216
+ }
217
+ process.stdout.write('\n');
218
+ }
219
+ }
220
+ }
221
+
222
+ if (isCli) process.exit(report.success ? 0 : 1);
223
+ return report;
224
+ };
@@ -0,0 +1,44 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { detectLintCommand, parseLintOutput } from './verify-lint.js';
4
+
5
+ test('VerifyLint: detectLintCommand resolves custom command when provided', () => {
6
+ const cmd = detectLintCommand('pnpm custom:lint');
7
+ assert.strictEqual(cmd, 'pnpm custom:lint');
8
+ });
9
+
10
+ test('VerifyLint: detectLintCommand handles targetPath and --fix', () => {
11
+ const cmd = detectLintCommand(null, process.cwd(), true, 'src/test.vue');
12
+ assert.strictEqual(cmd, 'npx eslint --fix src/test.vue');
13
+
14
+ const cmdNoFix = detectLintCommand(null, process.cwd(), false, 'src/test.vue');
15
+ assert.strictEqual(cmdNoFix, 'npx eslint src/test.vue');
16
+ });
17
+
18
+ test('VerifyLint: detectLintCommand defaults to eslint or package script', () => {
19
+ const cmd = detectLintCommand(null, process.cwd(), false, null);
20
+ assert.ok(cmd.includes('eslint') || cmd.includes('lint'));
21
+ });
22
+
23
+ test('VerifyLint: parseLintOutput extracts errors, warnings, and fixable count', () => {
24
+ const sampleOutput = `
25
+ /path/to/component.vue
26
+ 12:5 error 'unused' is defined but never used no-unused-vars
27
+ 24:10 warning Unexpected console statement no-console
28
+
29
+ ✖ 2 problems (1 error, 1 warning)
30
+ 1 error and 0 warnings potentially fixable with the \`--fix\` option.
31
+ `;
32
+
33
+ const parsed = parseLintOutput(sampleOutput);
34
+ assert.strictEqual(parsed.errorCount, 1);
35
+ assert.strictEqual(parsed.warningCount, 1);
36
+ assert.strictEqual(parsed.fixableCount, 1);
37
+ assert.strictEqual(parsed.errors.length, 1);
38
+ assert.strictEqual(parsed.warnings.length, 1);
39
+ assert.strictEqual(parsed.errors[0].file, '/path/to/component.vue');
40
+ assert.strictEqual(parsed.errors[0].line, 12);
41
+ assert.strictEqual(parsed.errors[0].column, 5);
42
+ assert.strictEqual(parsed.errors[0].ruleId, 'no-unused-vars');
43
+ assert.strictEqual(parsed.warnings[0].ruleId, 'no-console');
44
+ });
package/cli/verify.js CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  parseTypecheckOutput,
23
23
  parseTestOutput
24
24
  } from './verify-helpers.js';
25
+ export { runLintAudit } from './verify-lint.js';
25
26
 
26
27
  export const runTypecheckAudit = async (rawArgs = [], isCli = false, options = {}) => {
27
28
  if (rawArgs.includes('--help') || rawArgs.includes('-h') || rawArgs.includes('help')) {
@@ -161,6 +162,12 @@ export const runTestAudit = async (rawArgs = [], isCli = false, options = {}) =>
161
162
  const customCmd = parseCommandFromArgs(rawArgs) || options.command;
162
163
  const cwd = findProjectRoot(options.cwd || process.cwd());
163
164
 
165
+ const targetFlag = (rawArgs.find((a) => a.startsWith('--target=')) || '').replace(/^--target=/, '');
166
+ const filterFlag = (rawArgs.find((a) => a.startsWith('--filter=') || a.startsWith('-t=')) || '').replace(/^--(filter|t)=/, '');
167
+ const positionalTarget = rawArgs.find((a) => !a.startsWith('-') && !['test', 'tests', 'check:test'].includes(a) && (a.endsWith('.js') || a.endsWith('.ts') || a.includes('/')));
168
+ const target = options.target || targetFlag || positionalTarget || null;
169
+ const filter = options.filter || filterFlag || null;
170
+
164
171
  const nmStatus = checkNodeModules(cwd);
165
172
  if (nmStatus) {
166
173
  const friendlyMsg = nmStatus.msg('testing');
@@ -190,7 +197,7 @@ export const runTestAudit = async (rawArgs = [], isCli = false, options = {}) =>
190
197
  return report;
191
198
  }
192
199
 
193
- const command = detectTestCommand(customCmd, cwd);
200
+ const command = detectTestCommand(customCmd, cwd, { target, filter });
194
201
  const execution = await executeBuild(command, cwd, { raw: isRaw });
195
202
  const parsed = parseTestOutput(execution.stdout, execution.stderr, execution.exitCode);
196
203
 
@@ -28,6 +28,23 @@ test('Verify: detectTestCommand resolves custom test command when provided', ()
28
28
  assert.strictEqual(cmd, 'vitest run test/unit');
29
29
  });
30
30
 
31
+ test('Verify: detectTestCommand formats target file and filter options correctly', () => {
32
+ const cmd = detectTestCommand(null, process.cwd(), {
33
+ target: 'cli/generator-jig.spec.js',
34
+ filter: 'presets'
35
+ });
36
+ assert.ok(cmd.includes('cli/generator-jig.spec.js'));
37
+ assert.ok(cmd.includes('presets'));
38
+ });
39
+
40
+ test('Verify: detectTestCommand appends target to custom command', () => {
41
+ const cmd = detectTestCommand('npx vitest run', process.cwd(), {
42
+ target: 'src/services/billing.spec.ts',
43
+ filter: 'charge'
44
+ });
45
+ assert.strictEqual(cmd, 'npx vitest run src/services/billing.spec.ts -t "charge"');
46
+ });
47
+
31
48
  test('Verify: parseTypecheckOutput extracts clean structured diagnostics', () => {
32
49
  const sampleOutput = `
33
50
  src/components/Card.tsx(14,5): error TS2322: Type 'string' is not assignable to type 'number'.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemx/starter-kit",
3
- "version": "26.9.25-943",
3
+ "version": "26.9.26-253",
4
4
  "description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
5
5
  "type": "module",
6
6
  "bin": {
@@ -118,17 +118,22 @@ elif command -v npx >/dev/null 2>&1; then
118
118
  fi
119
119
 
120
120
  if [ -n "$AUDIT_BIN" ]; then
121
- printf "%s[Chemical X] Verifying architectural health (Min Grade: %s, Min Score: %s)...%s\n" "$C_BLUE" "$MIN_GRADE" "$MIN_SCORE" "$C_RESET"
121
+ if [ "$CHEMX_VERBOSE" = "1" ]; then
122
+ printf "%s[Chemical X] Verifying architectural health (Min Grade: %s, Min Score: %s)...%s\n" "$C_BLUE" "$MIN_GRADE" "$MIN_SCORE" "$C_RESET"
123
+ fi
122
124
 
123
125
  AUDIT_CMD="$AUDIT_BIN audit --git --min-grade=$MIN_GRADE --min-score=$MIN_SCORE --non-interactive"
124
126
 
125
- if ! eval "$AUDIT_CMD < /dev/null"; then
127
+ if ! AUDIT_OUT=$(eval "$AUDIT_CMD < /dev/null" 2>&1); then
126
128
  printf "\n%s%s[Chemical X] Commit Blocked: Architectural health verification failed%s\n" "$C_BOLD" "$C_RED" "$C_RESET"
129
+ printf "%s\n\n" "$AUDIT_OUT"
127
130
  printf "%s💡 Tip: Want crystalline drop-in templates to refactor in minutes?%s\n" "$C_CYAN" "$C_RESET"
128
131
  printf " Run 'npm create chemx' or sponsor at https://github.com/sponsors/Chemical-X-Protocol\n\n"
129
132
  exit 1
130
133
  fi
131
134
  fi
132
135
 
133
- printf "%s✔ [Chemical X] Pre-commit architectural guardrails passed.%s\n" "$C_GREEN" "$C_RESET"
136
+ if [ "$CHEMX_VERBOSE" = "1" ]; then
137
+ printf "%s✔ [Chemical X] Pre-commit architectural guardrails passed.%s\n" "$C_GREEN" "$C_RESET"
138
+ fi
134
139
  exit 0