@activate-core/test-forge 0.1.1 → 0.2.0

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,82 @@
1
+ /**
2
+ * testforge — generated-code syntax gate.
3
+ *
4
+ * Before any generated test/seed is written to disk, its content is
5
+ * parse-checked here. This is what stops malformed output (prose that
6
+ * leaked past cleaning, a half-written file, an unbalanced brace) from
7
+ * ever reaching the project — the single gate that would have prevented
8
+ * the broken-seed defects.
9
+ *
10
+ * We stay dependency-free:
11
+ * - JavaScript (.js/.cjs/.mjs): checked EXACTLY with Node's built-in
12
+ * `node --check`, which parses without executing.
13
+ * - TypeScript (.ts/.tsx): no exact parser is guaranteed at runtime
14
+ * (the project we run inside may not have `typescript` installed),
15
+ * so we fall back to a structural sanity check that still catches
16
+ * the common failure — prose leaking into the file — without
17
+ * false-rejecting valid TypeScript.
18
+ */
19
+ import { execFileSync } from 'node:child_process';
20
+ import { writeFileSync, mkdtempSync, rmSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import { tmpdir } from 'node:os';
23
+ /** True if `content` parses (or, for TS, passes a structural check). */
24
+ export function isSyntaxValid(content, ext) {
25
+ if (!content.trim())
26
+ return false;
27
+ const isJs = /\.(c|m)?js$/i.test(ext);
28
+ if (isJs) {
29
+ return nodeCheck(content, ext);
30
+ }
31
+ return looksStructurallyClean(content);
32
+ }
33
+ /** Write to a temp file and run `node --check` on it. */
34
+ function nodeCheck(content, ext) {
35
+ const dir = mkdtempSync(join(tmpdir(), 'testforge-check-'));
36
+ // `.mjs` gets ES-module parsing; anything else checks fine as `.js`
37
+ // (CommonJS), and `node --check` accepts both `import` and `require`
38
+ // there depending on extension — so preserve an explicit `.mjs`.
39
+ const fileExt = /\.mjs$/i.test(ext) ? '.mjs' : '.js';
40
+ const file = join(dir, `check${fileExt}`);
41
+ try {
42
+ writeFileSync(file, content, 'utf-8');
43
+ execFileSync(process.execPath, ['--check', file], { stdio: 'ignore' });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ finally {
50
+ rmSync(dir, { recursive: true, force: true });
51
+ }
52
+ }
53
+ /**
54
+ * Best-effort TypeScript check: the file must START with code (not a
55
+ * sentence) and have balanced braces/parens. This catches leaked prose
56
+ * — the actual failure mode we saw — without a real TS parser.
57
+ */
58
+ function looksStructurallyClean(content) {
59
+ const firstLine = content
60
+ .trim()
61
+ .split('\n')
62
+ .find((l) => l.trim() !== '') ?? '';
63
+ const t = firstLine.trim();
64
+ const startsWithCode = t.startsWith('//') ||
65
+ t.startsWith('/*') ||
66
+ t.startsWith('import ') ||
67
+ t.startsWith('export ') ||
68
+ t.startsWith('const ') ||
69
+ t.startsWith('let ') ||
70
+ t.startsWith('var ') ||
71
+ t.startsWith('function ') ||
72
+ t.startsWith('async ') ||
73
+ t.startsWith('require(') ||
74
+ t.startsWith("'use strict'") ||
75
+ t.startsWith('"use strict"');
76
+ if (!startsWithCode)
77
+ return false;
78
+ const count = (s, ch) => s.split(ch).length - 1;
79
+ const balanced = count(content, '{') === count(content, '}') &&
80
+ count(content, '(') === count(content, ')');
81
+ return balanced;
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@activate-core/test-forge",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "AI-powered QA automation. Scans a project, drafts journey docs, and generates seeded Jest/Playwright tests. Works in any project, with any AI CLI.",
5
5
  "type": "module",
6
6
  "bin": {