unitbob 0.2.3 → 0.2.4

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.
@@ -1,4 +1,4 @@
1
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { assertUnitbobPath } from "./artifactPath.js";
4
4
  // The behavioral suite lives under its own root: the main `.feature` plus its
@@ -9,14 +9,21 @@ import { assertUnitbobPath } from "./artifactPath.js";
9
9
  // blob only so `check` can execute it locally.
10
10
  export const BEHAVIORAL_DIR = '.unitbob/behavioral';
11
11
  // Write a behavioral artifact envelope (main file + support files) under the
12
- // behavioral root, after checking every path is safe. The root is wiped first
13
- // so a stale file from a previous version never lingers. Returns the absolute
14
- // path of the materialized main file.
15
- export function materializeBehavioral(projectRoot, artifact) {
12
+ // behavioral root, after checking every path is safe. Stale suite artifacts are
13
+ // removed while the separately provisioned runner environment is preserved.
14
+ // Returns the absolute path of the materialized main file.
15
+ export function materializeBehavioral(projectRoot, artifact, runner) {
16
16
  const files = [artifact, ...(artifact.support_files ?? [])];
17
17
  for (const file of files)
18
18
  assertUnitbobPath(file.path, BEHAVIORAL_DIR);
19
- rmSync(join(projectRoot, BEHAVIORAL_DIR), { recursive: true, force: true });
19
+ const behavioralRoot = join(projectRoot, BEHAVIORAL_DIR);
20
+ const runnerEntries = RUNNER_ENVIRONMENT_ENTRIES[runner] ?? EMPTY_ENTRIES;
21
+ mkdirSync(behavioralRoot, { recursive: true });
22
+ for (const entry of readdirSync(behavioralRoot)) {
23
+ if (!runnerEntries.has(entry)) {
24
+ rmSync(join(behavioralRoot, entry), { recursive: true, force: true });
25
+ }
26
+ }
20
27
  let mainPath = '';
21
28
  for (const file of files) {
22
29
  const dest = join(projectRoot, file.path);
@@ -27,3 +34,9 @@ export function materializeBehavioral(projectRoot, artifact) {
27
34
  }
28
35
  return { mainPath };
29
36
  }
37
+ const EMPTY_ENTRIES = new Set();
38
+ const RUNNER_ENVIRONMENT_ENTRIES = {
39
+ cucumber: new Set(['.bundle', 'Gemfile', 'Gemfile.lock']),
40
+ 'cucumber-js': new Set(['node_modules', 'package.json', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock']),
41
+ 'pytest-bdd': new Set(['.venv']),
42
+ };
@@ -34,20 +34,18 @@ export function runBddSuite(projectRoot, runner, mainPath) {
34
34
  async function runCucumberRuby(projectRoot) {
35
35
  const features = join(BEHAVIORAL_ROOT, 'features');
36
36
  const steps = join(BEHAVIORAL_ROOT, 'step_definitions');
37
- const localBin = join(projectRoot, 'bin', 'cucumber');
38
37
  const sidecarGemfile = join(projectRoot, BEHAVIORAL_ROOT, 'Gemfile');
39
- const hasSidecarGemfile = existsSync(sidecarGemfile);
40
- const command = executable(localBin) ? localBin : 'bundle';
41
- const base = executable(localBin) ? [] : ['exec', 'cucumber'];
42
- const args = [...base, features, '--require', steps, '--format', 'message', '--out', CUCUMBER_REPORT];
38
+ if (!existsSync(sidecarGemfile)) {
39
+ throw missingRunner('Cucumber');
40
+ }
41
+ const command = 'bundle';
42
+ const args = ['exec', 'cucumber', features, '--require', steps, '--format', 'message', '--out', CUCUMBER_REPORT];
43
43
  const env = {
44
44
  ...process.env,
45
45
  RAILS_ENV: 'test',
46
46
  UNITBOB_REPO_ROOT: projectRoot,
47
47
  };
48
- if (hasSidecarGemfile) {
49
- env.BUNDLE_GEMFILE = join(BEHAVIORAL_ROOT, 'Gemfile');
50
- }
48
+ env.BUNDLE_GEMFILE = join(BEHAVIORAL_ROOT, 'Gemfile');
51
49
  const result = await runProcess(command, args, {
52
50
  cwd: projectRoot,
53
51
  timeoutMs: BDD_TIMEOUT_MS,
@@ -55,16 +53,20 @@ async function runCucumberRuby(projectRoot) {
55
53
  });
56
54
  return finalize(result, command, args, projectRoot, CUCUMBER_REPORT);
57
55
  }
56
+ function missingRunner(name) {
57
+ return new Error(`Behavioral runner missing (${name}). Run suite-prepare to provision it under ${BEHAVIORAL_ROOT}/, then run the checks again.`);
58
+ }
58
59
  // JS/TS: `@cucumber/cucumber` (cucumber-js) with the message formatter written
59
60
  // to a file.
60
61
  async function runCucumberJs(projectRoot) {
61
62
  const features = join(BEHAVIORAL_ROOT, 'features');
62
63
  const steps = join(BEHAVIORAL_ROOT, 'step_definitions', '**', '*');
63
64
  const sidecarBin = join(projectRoot, BEHAVIORAL_ROOT, 'node_modules', '.bin', 'cucumber-js');
64
- const command = executable(sidecarBin) ? sidecarBin : 'npx';
65
- const baseArgs = executable(sidecarBin) ? [] : ['cucumber-js'];
65
+ if (!executable(sidecarBin)) {
66
+ throw missingRunner('Cucumber JS');
67
+ }
68
+ const command = sidecarBin;
66
69
  const args = [
67
- ...baseArgs,
68
70
  features,
69
71
  '--require',
70
72
  steps,
@@ -122,11 +124,7 @@ async function pickPython(projectRoot) {
122
124
  if (executable(sidecarVenvPytest)) {
123
125
  return sidecarVenvPytest;
124
126
  }
125
- const probe = await runProcess('python3', ['-m', 'pytest', '--version'], {
126
- cwd: projectRoot,
127
- timeoutMs: 10_000,
128
- }).catch(() => ({ stdout: '', stderr: '', code: 1 }));
129
- return probe.code === 0 ? 'python3' : 'python';
127
+ throw missingRunner('pytest-bdd');
130
128
  }
131
129
  function executable(path) {
132
130
  try {
package/dist/verbs/run.js CHANGED
@@ -18,7 +18,7 @@ export async function run(config, _args, deps) {
18
18
  suite_file: { path: item.suite_file.path, content: item.suite_file.content },
19
19
  runner_manifest: item.runner_manifest,
20
20
  }),
21
- materializeBehavioral: (projectRoot, item) => materializeBehavioral(projectRoot, item.suite_file).mainPath,
21
+ materializeBehavioral: (projectRoot, item) => materializeBehavioral(projectRoot, item.suite_file, item.runner_manifest.runner).mainPath,
22
22
  runStructural: runStructuralByRunner,
23
23
  runBehavioral: runBddSuite,
24
24
  validateStack,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unitbob",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Unitbob connector — thin local hands for the Unitbob Rails brain. Owns no domain logic: it runs tools, relays bytes over the wire, and prints what the server returns.",
5
5
  "type": "module",
6
6
  "bin": {