eyeling 1.5.17 → 1.5.19

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/README.md CHANGED
@@ -100,7 +100,7 @@ npm run test:packlist
100
100
  ```
101
101
 
102
102
  - `test:api` runs an independent JS API test suite (does not rely on `examples/`).
103
- - `test:examples` runs the examples in `examples` directory and compares against the golden outputs in `examples/output`.
103
+ - `test:examples` runs the examples in the `examples` directory and compares against the golden outputs in `examples/output`.
104
104
  - `test:package` does a “real consumer” smoke test: `npm pack` → install tarball into a temp project → run API + CLI + examples.
105
105
  - `test:packlist` sanity-checks what will be published in the npm tarball (and the CLI shebang/bin wiring).
106
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.17",
3
+ "version": "1.5.19",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -15,16 +15,6 @@ function ok(msg) { console.log(`${C.g}OK${C.n} ${msg}`); }
15
15
  function fail(msg) { console.error(`${C.r}FAIL${C.n} ${msg}`); }
16
16
  function info(msg) { console.log(`${C.y}==${C.n} ${msg}`); }
17
17
 
18
- function padRight(s, n) {
19
- s = String(s);
20
- return s.length >= n ? s : (s + ' '.repeat(n - s.length));
21
- }
22
-
23
- function padLeft(s, n) {
24
- s = String(s);
25
- return s.length >= n ? s : (' '.repeat(n - s.length) + s);
26
- }
27
-
28
18
  function run(cmd, args, opts = {}) {
29
19
  return cp.spawnSync(cmd, args, {
30
20
  encoding: 'utf8',
@@ -44,8 +34,8 @@ function inGitWorktree(cwd) {
44
34
  return r.status === 0 && String(r.stdout).trim() === 'true';
45
35
  }
46
36
 
47
- // Expectation logic (same as bash version):
48
- // 1) If file contains a comment like: # expect-exit: 2 -> use that
37
+ // Expectation logic:
38
+ // 1) If file contains: # expect-exit: N -> use N
49
39
  // 2) Else, if it contains "=> false" -> expect exit 2
50
40
  // 3) Else -> expect exit 0
51
41
  function expectedExitCode(n3Text) {
@@ -57,25 +47,45 @@ function expectedExitCode(n3Text) {
57
47
 
58
48
  function getEyelingVersion(nodePath, eyelingJsPath, cwd) {
59
49
  const r = run(nodePath, [eyelingJsPath, '-v'], { cwd });
60
- // eyeling prints version to stdout in your CLI
61
50
  const s = (r.stdout || r.stderr || '').trim();
62
51
  return s || 'eyeling (unknown version)';
63
52
  }
64
53
 
65
- function mkTmpFile() {
54
+ function mkTmpDir() {
66
55
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-examples-'));
67
- const file = path.join(dir, 'generated.n3');
68
- return { dir, file };
56
+ return dir;
69
57
  }
70
58
 
71
59
  function rmrf(p) {
72
60
  try { fs.rmSync(p, { recursive: true, force: true }); } catch {}
73
61
  }
74
62
 
63
+ function showDiff({ IN_GIT, examplesDir, expectedPath, generatedPath, relExpectedPosix }) {
64
+ if (hasGit()) {
65
+ if (IN_GIT) {
66
+ // Show repo diff for the overwritten golden file
67
+ const d = run('git', ['diff', '--', relExpectedPosix], { cwd: examplesDir });
68
+ if (d.stdout) process.stdout.write(d.stdout);
69
+ if (d.stderr) process.stderr.write(d.stderr);
70
+ } else {
71
+ // Show no-index diff between packaged golden and generated tmp
72
+ const d = run('git', ['diff', '--no-index', expectedPath, generatedPath], { cwd: examplesDir });
73
+ // Replace tmp path in output (nice UX)
74
+ if (d.stdout) process.stdout.write(String(d.stdout).replaceAll(generatedPath, 'generated'));
75
+ if (d.stderr) process.stderr.write(String(d.stderr).replaceAll(generatedPath, 'generated'));
76
+ }
77
+ } else {
78
+ // Fallback: diff -u
79
+ const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
80
+ if (d.stdout) process.stdout.write(d.stdout);
81
+ if (d.stderr) process.stderr.write(d.stderr);
82
+ }
83
+ }
84
+
75
85
  function main() {
76
86
  const suiteStart = Date.now();
77
87
 
78
- // package root: .../test/examples.test.js -> root is one level up
88
+ // test/examples.test.js -> repo root is one level up
79
89
  const root = path.resolve(__dirname, '..');
80
90
  const examplesDir = path.join(root, 'examples');
81
91
  const outputDir = path.join(examplesDir, 'output');
@@ -83,107 +93,98 @@ function main() {
83
93
  const nodePath = process.execPath;
84
94
 
85
95
  if (!fs.existsSync(examplesDir)) {
86
- fail(`Missing examples directory: ${examplesDir}`);
96
+ fail(`Cannot find examples directory: ${examplesDir}`);
87
97
  process.exit(1);
88
98
  }
89
99
  if (!fs.existsSync(eyelingJsPath)) {
90
- fail(`Missing eyeling.js: ${eyelingJsPath}`);
100
+ fail(`Cannot find eyeling.js: ${eyelingJsPath}`);
91
101
  process.exit(1);
92
102
  }
93
103
 
94
104
  const IN_GIT = inGitWorktree(root);
95
105
 
96
- // Header
97
- console.log(`${C.y}-------------------------------------------------${C.n}`);
98
- console.log(`${C.y}running eyeling examples${C.n}`);
99
- console.log(
100
- `${C.y}using ${getEyelingVersion(nodePath, eyelingJsPath, root)} and node ${process.version}${C.n}`
101
- );
102
- console.log(`${C.y}-------------------------------------------------${C.n}`);
103
- console.log('');
104
-
105
- // In maintainer mode we write expected outputs (tracked) to examples/output/
106
- if (IN_GIT) fs.mkdirSync(outputDir, { recursive: true });
107
-
108
106
  const files = fs.readdirSync(examplesDir)
109
107
  .filter(f => f.endsWith('.n3'))
110
108
  .sort((a, b) => a.localeCompare(b));
111
109
 
110
+ info(
111
+ `Running ${files.length} examples tests (${IN_GIT ? 'git worktree mode' : 'npm-installed mode'})`
112
+ );
113
+ console.log(`${C.dim}${getEyelingVersion(nodePath, eyelingJsPath, root)}; node ${process.version}${C.n}`);
114
+
112
115
  if (files.length === 0) {
113
- info('No .n3 files found in examples/');
116
+ ok('No .n3 files found in examples/');
114
117
  process.exit(0);
115
118
  }
116
119
 
117
- let OK = 0;
118
- let DIFF = 0;
120
+ // In maintainer mode we overwrite tracked goldens in examples/output/
121
+ if (IN_GIT) fs.mkdirSync(outputDir, { recursive: true });
119
122
 
120
- for (const file of files) {
121
- const filePath = path.join(examplesDir, file);
122
- const expectedPath = path.join(outputDir, file); // examples/output/<file>
123
+ let passed = 0;
124
+ let failed = 0;
125
+
126
+ for (let i = 0; i < files.length; i++) {
127
+ const idx = String(i + 1).padStart(2, '0');
128
+ const file = files[i];
123
129
 
124
130
  const start = Date.now();
125
131
 
126
- let n3Text = '';
132
+ const filePath = path.join(examplesDir, file);
133
+ const expectedPath = path.join(outputDir, file);
134
+ const relExpectedPosix = path.posix.join('output', file); // for git diff inside examplesDir
135
+
136
+ let n3Text;
127
137
  try {
128
138
  n3Text = fs.readFileSync(filePath, 'utf8');
129
139
  } catch (e) {
130
140
  const ms = Date.now() - start;
131
- process.stdout.write(padRight(file, 36));
132
- process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
133
- console.log(`${C.r}DIFF${C.n} (cannot read input: ${e.message})`);
134
- DIFF++;
141
+ fail(`${idx} ${file} (${ms} ms)`);
142
+ fail(`Cannot read input: ${e.message}`);
143
+ failed++;
135
144
  continue;
136
145
  }
137
146
 
138
147
  const expectedRc = expectedExitCode(n3Text);
139
148
 
140
- // Decide where to write generated output
141
- let tmp = null;
149
+ // Decide where generated output goes
150
+ let tmpDir = null;
142
151
  let generatedPath = expectedPath;
143
152
 
144
153
  if (!IN_GIT) {
145
154
  // npm-installed / no .git: never modify output/ in node_modules
146
155
  if (!fs.existsSync(expectedPath)) {
147
156
  const ms = Date.now() - start;
148
- process.stdout.write(padRight(file, 36));
149
- process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
150
- console.log(`${C.r}MISSING expected output/${file}${C.n}`);
151
- DIFF++;
157
+ fail(`${idx} ${file} (${ms} ms)`);
158
+ fail(`Missing expected output/${file}`);
159
+ failed++;
152
160
  continue;
153
161
  }
154
- tmp = mkTmpFile();
155
- generatedPath = tmp.file;
162
+ tmpDir = mkTmpDir();
163
+ generatedPath = path.join(tmpDir, 'generated.n3');
156
164
  }
157
165
 
158
- // Run eyeling, capture exit code without aborting the suite
159
- // We run `node eyeling.js <file>` from examplesDir so relative paths match old behavior.
160
- const r = run(nodePath, [eyelingJsPath, file], { cwd: examplesDir });
161
- const rc = (r.status == null) ? 1 : r.status;
166
+ // Run eyeling on this file (cwd examplesDir so relative behavior matches old script)
167
+ const outFd = fs.openSync(generatedPath, 'w');
162
168
 
163
- // Write stdout to the chosen output file (expected in git mode, tmp in npm mode)
164
- try {
165
- fs.writeFileSync(generatedPath, r.stdout || '', 'utf8');
166
- } catch (e) {
167
- const ms = Date.now() - start;
168
- process.stdout.write(padRight(file, 36));
169
- process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
170
- console.log(`${C.r}DIFF${C.n} (cannot write output: ${e.message})`);
171
- DIFF++;
172
- if (tmp) rmrf(tmp.dir);
173
- continue;
174
- }
169
+ const r = cp.spawnSync(nodePath, [eyelingJsPath, file], {
170
+ cwd: examplesDir,
171
+ stdio: ['ignore', outFd, 'pipe'], // stdout -> file, stderr captured
172
+ maxBuffer: 200 * 1024 * 1024,
173
+ encoding: 'utf8'
174
+ });
175
+
176
+ fs.closeSync(outFd);
177
+
178
+ const rc = (r.status == null) ? 1 : r.status;
175
179
 
176
180
  const ms = Date.now() - start;
177
181
 
178
- // Compare outputs
182
+ // Compare output
179
183
  let diffOk = false;
180
-
181
184
  if (IN_GIT) {
182
- // Compare expectedPath against HEAD using git diff
183
- const d = run('git', ['diff', '--quiet', '--', path.posix.join('output', file)], { cwd: examplesDir });
185
+ const d = run('git', ['diff', '--quiet', '--', relExpectedPosix], { cwd: examplesDir });
184
186
  diffOk = (d.status === 0);
185
187
  } else {
186
- // Compare expectedPath vs generatedPath without needing a repo
187
188
  if (hasGit()) {
188
189
  const d = run('git', ['diff', '--no-index', '--quiet', expectedPath, generatedPath], { cwd: examplesDir });
189
190
  diffOk = (d.status === 0);
@@ -193,49 +194,51 @@ function main() {
193
194
  }
194
195
  }
195
196
 
196
- // Decide pass/fail
197
- process.stdout.write(padRight(file, 36));
198
- process.stdout.write(`${C.y}${padLeft(`${ms} ms`, 10)}${C.n} `);
197
+ const rcOk = (rc === expectedRc);
199
198
 
200
- if (diffOk && rc === expectedRc) {
201
- if (rc === 0) {
202
- console.log(`${C.g}OK${C.n}`);
199
+ if (diffOk && rcOk) {
200
+ if (expectedRc === 0) {
201
+ ok(`${idx} ${file} (${ms} ms)`);
203
202
  } else {
204
- console.log(`${C.g}OK${C.n} (exit ${rc})`);
203
+ ok(`${idx} ${file} (expected exit ${expectedRc}, ${ms} ms)`);
205
204
  }
206
- OK++;
205
+ passed++;
207
206
  } else {
208
- if (rc !== expectedRc) {
209
- console.log(`${C.r}DIFF${C.n} (exit ${rc}, expected ${expectedRc})`);
210
- } else {
211
- console.log(`${C.r}DIFF${C.n}`);
207
+ fail(`${idx} ${file} (${ms} ms)`);
208
+ if (!rcOk) {
209
+ fail(`Exit code ${rc}, expected ${expectedRc}`);
212
210
  }
213
- DIFF++;
214
-
215
- // In npm mode, show a diff (nice UX) without modifying node_modules
216
- if (!IN_GIT) {
217
- if (hasGit()) {
218
- const d = run('git', ['diff', '--no-index', expectedPath, generatedPath], { cwd: examplesDir });
219
- if (d.stdout) process.stdout.write(d.stdout);
220
- if (d.stderr) process.stderr.write(d.stderr);
221
- } else {
222
- const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
223
- if (d.stdout) process.stdout.write(d.stdout);
224
- if (d.stderr) process.stderr.write(d.stderr);
225
- }
211
+ if (!diffOk) {
212
+ fail('Output differs');
226
213
  }
214
+
215
+ // Show diffs (both modes), because this is a test runner
216
+ showDiff({
217
+ IN_GIT,
218
+ examplesDir,
219
+ expectedPath,
220
+ generatedPath,
221
+ relExpectedPosix,
222
+ });
223
+
224
+ failed++;
227
225
  }
228
226
 
229
- // cleanup tmp file
230
- if (tmp) rmrf(tmp.dir);
227
+ if (tmpDir) rmrf(tmpDir);
231
228
  }
232
229
 
233
230
  console.log('');
234
231
  const suiteMs = Date.now() - suiteStart;
235
- console.log(`${C.y}==${C.n} Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
236
- console.log(`${C.y}==${C.n} ${C.g}${OK} OK${C.n} ${C.r}${DIFF} DIFF${C.n}`);
232
+ info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
237
233
 
238
- process.exit(DIFF === 0 ? 0 : 2);
234
+ if (failed === 0) {
235
+ ok(`All examples tests passed (${passed}/${files.length})`);
236
+ process.exit(0);
237
+ } else {
238
+ fail(`Some examples tests failed (${passed}/${files.length})`);
239
+ // keep exit code 2 (matches historical behavior of examples/test)
240
+ process.exit(2);
241
+ }
239
242
  }
240
243
 
241
244
  main();