@vitronai/themis 0.1.4 → 0.1.6

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,53 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function ensureGitignoreEntries(cwd, entries) {
5
+ const targetPath = path.join(cwd, '.gitignore');
6
+ const requestedEntries = [...new Set(
7
+ (Array.isArray(entries) ? entries : [])
8
+ .map((entry) => String(entry || '').trim())
9
+ .filter(Boolean)
10
+ )];
11
+
12
+ if (requestedEntries.length === 0) {
13
+ return {
14
+ path: targetPath,
15
+ updated: false
16
+ };
17
+ }
18
+
19
+ const existing = fs.existsSync(targetPath)
20
+ ? fs.readFileSync(targetPath, 'utf8')
21
+ : '';
22
+ const normalized = existing.replace(/\r\n/g, '\n');
23
+ const existingEntries = new Set(
24
+ normalized
25
+ .split('\n')
26
+ .map((line) => line.trim())
27
+ .filter(Boolean)
28
+ );
29
+ const missingEntries = requestedEntries.filter((entry) => !existingEntries.has(entry));
30
+
31
+ if (missingEntries.length === 0) {
32
+ return {
33
+ path: targetPath,
34
+ updated: false
35
+ };
36
+ }
37
+
38
+ let nextSource = normalized;
39
+ if (nextSource.length > 0 && !nextSource.endsWith('\n')) {
40
+ nextSource += '\n';
41
+ }
42
+ nextSource += `${missingEntries.join('\n')}\n`;
43
+ fs.writeFileSync(targetPath, nextSource, 'utf8');
44
+
45
+ return {
46
+ path: targetPath,
47
+ updated: true
48
+ };
49
+ }
50
+
51
+ module.exports = {
52
+ ensureGitignoreEntries
53
+ };
package/src/init.js CHANGED
@@ -1,20 +1,9 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
1
  const { initConfig } = require('./config');
2
+ const { ensureGitignoreEntries } = require('./gitignore');
4
3
 
5
4
  function runInit(cwd) {
6
5
  initConfig(cwd);
7
-
8
- const testsDir = path.join(cwd, 'tests');
9
- if (!fs.existsSync(testsDir)) {
10
- fs.mkdirSync(testsDir, { recursive: true });
11
- }
12
-
13
- const sample = path.join(testsDir, 'example.test.js');
14
- if (!fs.existsSync(sample)) {
15
- const content = `describe('math', () => {\n test('adds numbers', () => {\n expect(1 + 1).toBe(2);\n });\n});\n`;
16
- fs.writeFileSync(sample, content, 'utf8');
17
- }
6
+ ensureGitignoreEntries(cwd, ['.themis/']);
18
7
  }
19
8
 
20
9
  module.exports = {
package/src/migrate.js CHANGED
@@ -1,11 +1,13 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { DEFAULT_CONFIG, loadConfig } = require('./config');
4
+ const { ARTIFACT_RELATIVE_PATHS } = require('./artifact-paths');
5
+ const { ensureGitignoreEntries } = require('./gitignore');
4
6
 
5
7
  const SUPPORTED_MIGRATION_SOURCES = new Set(['jest', 'vitest']);
6
8
  const THEMIS_SETUP_FILE = path.join('tests', 'setup.themis.js');
7
9
  const THEMIS_COMPAT_FILE = 'themis.compat.js';
8
- const MIGRATION_REPORT_FILE = path.join('.themis', 'migration-report.json');
10
+ const MIGRATION_REPORT_FILE = ARTIFACT_RELATIVE_PATHS.migrationReport;
9
11
  const SCANNABLE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
10
12
  const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', '.themis']);
11
13
 
@@ -43,6 +45,7 @@ function runMigrate(cwd, framework, options = {}) {
43
45
  }
44
46
 
45
47
  fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
48
+ const gitignore = ensureGitignoreEntries(projectRoot, ['.themis/']);
46
49
 
47
50
  let packageUpdated = false;
48
51
  if (fs.existsSync(packageJsonPath)) {
@@ -74,6 +77,8 @@ function runMigrate(cwd, framework, options = {}) {
74
77
  compatPath,
75
78
  packageJsonPath: fs.existsSync(packageJsonPath) ? packageJsonPath : null,
76
79
  packageUpdated,
80
+ gitignorePath: gitignore.path,
81
+ gitignoreUpdated: gitignore.updated,
77
82
  reportPath,
78
83
  report,
79
84
  rewriteImports: Boolean(options.rewriteImports),
@@ -4,6 +4,8 @@ const Module = require('module');
4
4
 
5
5
  const SUPPORTED_SOURCE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'];
6
6
  const RESOLVABLE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.json'];
7
+ const THEMIS_CONTRACT_RUNTIME_REQUEST = '@vitronai/themis/contract-runtime';
8
+ const THEMIS_CONTRACT_RUNTIME_PATH = path.join(__dirname, 'contract-runtime.js');
7
9
  const DEFAULT_TS_COMPILER_OPTIONS = {
8
10
  target: 'ES2020',
9
11
  module: 'CommonJS',
@@ -56,6 +58,10 @@ function createModuleLoader(options = {}) {
56
58
  }
57
59
 
58
60
  Module._resolveFilename = function themisResolveFilename(request, parent, isMain, resolutionOptions) {
61
+ if (request === THEMIS_CONTRACT_RUNTIME_REQUEST) {
62
+ return THEMIS_CONTRACT_RUNTIME_PATH;
63
+ }
64
+
59
65
  if (Object.prototype.hasOwnProperty.call(virtualModules, request)) {
60
66
  return request;
61
67
  }
@@ -161,7 +167,7 @@ function createModuleLoader(options = {}) {
161
167
  function safeRealpath(targetPath) {
162
168
  try {
163
169
  return fs.realpathSync.native(targetPath);
164
- } catch (error) {
170
+ } catch {
165
171
  return targetPath;
166
172
  }
167
173
  }
@@ -175,6 +181,10 @@ function resolveRequestValue({
175
181
  isMain = false,
176
182
  virtualModules = null
177
183
  }) {
184
+ if (request === THEMIS_CONTRACT_RUNTIME_REQUEST) {
185
+ return THEMIS_CONTRACT_RUNTIME_PATH;
186
+ }
187
+
178
188
  if (virtualModules && Object.prototype.hasOwnProperty.call(virtualModules, request)) {
179
189
  return request;
180
190
  }
@@ -270,7 +280,7 @@ function findNearestPackageType(filename, projectRoot, packageTypeCache) {
270
280
  if (parsed.type === 'module') {
271
281
  packageType = 'module';
272
282
  }
273
- } catch (error) {
283
+ } catch {
274
284
  packageType = 'commonjs';
275
285
  }
276
286
 
@@ -301,7 +311,7 @@ function getCompilerContext(compilerState, projectRoot, tsconfigPath, options =
301
311
  let ts;
302
312
  try {
303
313
  ts = require('typescript');
304
- } catch (error) {
314
+ } catch {
305
315
  if (options.optional) {
306
316
  return null;
307
317
  }
package/src/reporter.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { buildStabilityReport } = require('./stability');
4
+ const { ARTIFACT_RELATIVE_PATHS, resolveArtifactPath } = require('./artifact-paths');
4
5
 
5
6
  const REPORT_LEXICONS = {
6
7
  classic: {
@@ -28,6 +29,14 @@ const REPORT_LEXICONS = {
28
29
  fileSkipWord: 'deferred'
29
30
  }
30
31
  };
32
+ const AGENT_ARTIFACT_PATHS = Object.freeze({
33
+ lastRun: ARTIFACT_RELATIVE_PATHS.lastRun,
34
+ failedTests: ARTIFACT_RELATIVE_PATHS.failedTests,
35
+ runDiff: ARTIFACT_RELATIVE_PATHS.runDiff,
36
+ runHistory: ARTIFACT_RELATIVE_PATHS.runHistory,
37
+ fixHandoff: ARTIFACT_RELATIVE_PATHS.fixHandoff,
38
+ contractDiff: ARTIFACT_RELATIVE_PATHS.contractDiff
39
+ });
31
40
 
32
41
  function printSpec(result, options = {}) {
33
42
  const lexicon = resolveLexicon(options.lexicon);
@@ -62,14 +71,7 @@ function printAgent(result) {
62
71
  const failureClusters = clusterFailures(failures);
63
72
  const stability = result.stability || buildStabilityReport([result]);
64
73
  const comparison = result.artifacts?.comparison || buildAgentComparison(result, failures);
65
- const artifactPaths = result.artifacts?.paths || {
66
- lastRun: '.themis/last-run.json',
67
- failedTests: '.themis/failed-tests.json',
68
- runDiff: '.themis/run-diff.json',
69
- runHistory: '.themis/run-history.json',
70
- fixHandoff: '.themis/fix-handoff.json',
71
- contractDiff: '.themis/contract-diff.json'
72
- };
74
+ const artifactPaths = result.artifacts?.paths || AGENT_ARTIFACT_PATHS;
73
75
 
74
76
  const payload = {
75
77
  schema: 'themis.agent.result.v1',
@@ -86,9 +88,9 @@ function printAgent(result) {
86
88
  hints: {
87
89
  rerunFailed: 'npx themis test --rerun-failed',
88
90
  targetedRerun: 'npx themis test --match "<regex>"',
89
- diffLastRun: 'cat .themis/run-diff.json',
90
- repairGenerated: 'cat .themis/fix-handoff.json',
91
- reviewContracts: 'cat .themis/contract-diff.json'
91
+ diffLastRun: `cat ${ARTIFACT_RELATIVE_PATHS.runDiff}`,
92
+ repairGenerated: 'npx themis test --fix',
93
+ reviewContracts: `cat ${ARTIFACT_RELATIVE_PATHS.contractDiff}`
92
94
  }
93
95
  };
94
96
 
@@ -483,7 +485,7 @@ function fnv1a32(input) {
483
485
 
484
486
  function resolveHtmlOutputPath(cwd, outputPath) {
485
487
  if (!outputPath) {
486
- return path.join(cwd, '.themis', 'report.html');
488
+ return resolveArtifactPath(cwd, 'htmlReport');
487
489
  }
488
490
  if (path.isAbsolute(outputPath)) {
489
491
  return outputPath;
package/src/runtime.js CHANGED
@@ -140,7 +140,7 @@ function collectAndRun(filePath, options = {}) {
140
140
  });
141
141
  }
142
142
 
143
- function buildRuntimeApi({ root, options, testUtils, runtimeExpect, getCurrentSuite, setCurrentSuite }) {
143
+ function buildRuntimeApi({ root: _root, options, testUtils, runtimeExpect, getCurrentSuite, setCurrentSuite }) {
144
144
  const describeApi = createDescribeApi({
145
145
  getCurrentSuite,
146
146
  setCurrentSuite
@@ -284,7 +284,7 @@ function formatParameterizedName(name, args, index) {
284
284
  function stringifyParameterizedValue(value) {
285
285
  try {
286
286
  return JSON.stringify(value);
287
- } catch (error) {
287
+ } catch {
288
288
  return String(value);
289
289
  }
290
290
  }
package/src/test-utils.js CHANGED
@@ -366,7 +366,7 @@ function createTestUtils(options = {}) {
366
366
  try {
367
367
  node[key] = value;
368
368
  continue;
369
- } catch (error) {
369
+ } catch {
370
370
  // Fall through to attribute set.
371
371
  }
372
372
  }
@@ -6,7 +6,9 @@ Use `@vitronai/themis` as this repository's unit test framework.
6
6
 
7
7
  - Install with `npm install -D @vitronai/themis`.
8
8
  - Initialize with `npx themis init`.
9
+ - Keep `.themis/` in `.gitignore`.
9
10
  - Generate tests with `npx themis generate src`.
11
+ - Expect generated tests under `tests/generated`, using `.generated.test.ts` for TS/TSX sources and `.generated.test.js` for JS/JSX sources.
10
12
  - Run tests with `npx themis test`.
11
13
 
12
14
  ## Migration
@@ -21,6 +23,7 @@ Use `@vitronai/themis` as this repository's unit test framework.
21
23
  - Prefer `intent(...)` for behavior and workflow tests.
22
24
  - Prefer `test(...)` for low-level unit checks.
23
25
  - Prefer deterministic assertions over snapshot-style baselines.
26
+ - Treat `.themis/` as Themis-managed artifact output.
24
27
  - Treat generated tests under `tests/generated` as Themis-managed output unless the repo says otherwise.
25
28
 
26
29
  ## Agent Behavior