bintastic 3.0.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.
- package/README.md +121 -0
- package/dist/index.cjs +175 -0
- package/dist/index.d.cts +112 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +147 -0
- package/package.json +102 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# bintastic
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://badge.fury.io/js/bintastic)
|
|
5
|
+
[](https://github.com/scalvert/bintastic/blob/master/package.json)
|
|
6
|
+

|
|
7
|
+
[](#badge)
|
|
8
|
+
|
|
9
|
+
> **Note:** This package was formerly published as `@scalvert/bin-tester`.
|
|
10
|
+
|
|
11
|
+
A test harness for Node.js CLI tools.
|
|
12
|
+
|
|
13
|
+
Testing a CLI isn't like testing a library—you can't just import functions and call them. You need to spawn your CLI as a subprocess, give it real files to work with, and capture its output. bintastic simplifies this:
|
|
14
|
+
|
|
15
|
+
```ts snippet=basic-example.ts
|
|
16
|
+
import { createBintastic } from 'bintastic';
|
|
17
|
+
|
|
18
|
+
describe('my-cli', () => {
|
|
19
|
+
const { setupProject, teardownProject, runBin } = createBintastic({
|
|
20
|
+
binPath: './bin/my-cli.js',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let project;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
project = await setupProject();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
teardownProject();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('processes files', async () => {
|
|
34
|
+
project.files = { 'input.txt': 'hello' };
|
|
35
|
+
await project.write();
|
|
36
|
+
|
|
37
|
+
const result = await runBin('input.txt');
|
|
38
|
+
|
|
39
|
+
expect(result.exitCode).toBe(0);
|
|
40
|
+
expect(result.stdout).toContain('processed');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm add bintastic --save-dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
`createBintastic` returns helpers for setting up projects, running your CLI, and cleaning up:
|
|
54
|
+
|
|
55
|
+
```ts snippet=create-bintastic.ts
|
|
56
|
+
const { setupProject, teardownProject, runBin } = createBintastic({
|
|
57
|
+
binPath: './bin/my-cli.js',
|
|
58
|
+
staticArgs: ['--verbose'], // args passed to every invocation
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Setup and teardown:**
|
|
63
|
+
|
|
64
|
+
```ts snippet=setup-teardown.ts
|
|
65
|
+
const project = await setupProject(); // creates temp directory
|
|
66
|
+
// ... run tests ...
|
|
67
|
+
teardownProject(); // removes temp directory
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Writing fixture files:**
|
|
71
|
+
|
|
72
|
+
```ts snippet=writing-fixtures.ts
|
|
73
|
+
project.files = {
|
|
74
|
+
'src/index.js': 'export default 42;',
|
|
75
|
+
'package.json': JSON.stringify({ name: 'test' }),
|
|
76
|
+
};
|
|
77
|
+
await project.write();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Running your CLI:**
|
|
81
|
+
|
|
82
|
+
```ts snippet=running-cli.ts
|
|
83
|
+
const result = await runBin('--flag', 'arg');
|
|
84
|
+
|
|
85
|
+
result.exitCode; // number
|
|
86
|
+
result.stdout; // string
|
|
87
|
+
result.stderr; // string
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Debugging
|
|
91
|
+
|
|
92
|
+
Set `BINTASTIC_DEBUG` to enable the Node inspector and preserve fixtures for inspection:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
BINTASTIC_DEBUG=attach npm test # attach debugger
|
|
96
|
+
BINTASTIC_DEBUG=break npm test # break on first line
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Or use `runBinDebug()` programmatically:
|
|
100
|
+
|
|
101
|
+
```ts snippet=run-bin-debug.ts
|
|
102
|
+
await runBinDebug('--flag'); // runs with --inspect
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For VS Code, add to `.vscode/launch.json`:
|
|
106
|
+
|
|
107
|
+
```jsonc
|
|
108
|
+
{
|
|
109
|
+
"name": "Debug Tests",
|
|
110
|
+
"type": "node",
|
|
111
|
+
"request": "launch",
|
|
112
|
+
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/vitest",
|
|
113
|
+
"runtimeArgs": ["run"],
|
|
114
|
+
"autoAttachChildProcesses": true,
|
|
115
|
+
"console": "integratedTerminal",
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## API
|
|
120
|
+
|
|
121
|
+
See the [full API documentation](https://scalvert.github.io/bintastic/).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BintasticProject: () => BintasticProject,
|
|
24
|
+
createBintastic: () => createBintastic
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/create-bintastic.ts
|
|
29
|
+
var import_execa2 = require("execa");
|
|
30
|
+
|
|
31
|
+
// src/project.ts
|
|
32
|
+
var import_execa = require("execa");
|
|
33
|
+
var import_fixturify_project = require("fixturify-project");
|
|
34
|
+
var ROOT = process.cwd();
|
|
35
|
+
var BintasticProject = class extends import_fixturify_project.Project {
|
|
36
|
+
/**
|
|
37
|
+
* Constructs an instance of a BintasticProject.
|
|
38
|
+
* @param {string} name - The name of the project. Used within the package.json as the name property.
|
|
39
|
+
* @param {string} version - The version of the project. Used within the package.json as the version property.
|
|
40
|
+
* @param {Function} cb - An optional callback for additional setup steps after the project is constructed.
|
|
41
|
+
*/
|
|
42
|
+
constructor(name = "fake-project", version, cb) {
|
|
43
|
+
super(name, version, cb);
|
|
44
|
+
this._dirChanged = false;
|
|
45
|
+
this.pkg = Object.assign({}, this.pkg, {
|
|
46
|
+
license: "MIT",
|
|
47
|
+
description: "Fake project",
|
|
48
|
+
repository: "http://fakerepo.com"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Runs `git init` inside a project.
|
|
53
|
+
* @returns {*} {ResultPromise}
|
|
54
|
+
*/
|
|
55
|
+
gitInit() {
|
|
56
|
+
return (0, import_execa.execa)("git", ["init", "-q", this.baseDir]);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Writes the project files to disk.
|
|
60
|
+
*/
|
|
61
|
+
async write() {
|
|
62
|
+
return super.write();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Changes a directory from inside the project.
|
|
66
|
+
*/
|
|
67
|
+
async chdir() {
|
|
68
|
+
this._dirChanged = true;
|
|
69
|
+
await this.write();
|
|
70
|
+
process.chdir(this.baseDir);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Correctly disposes of the project, observing when the directory has been changed.
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
dispose() {
|
|
77
|
+
if (this._dirChanged) {
|
|
78
|
+
process.chdir(ROOT);
|
|
79
|
+
}
|
|
80
|
+
return super.dispose();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/create-bintastic.ts
|
|
85
|
+
var DEFAULT_BINTASTIC_OPTIONS = {
|
|
86
|
+
staticArgs: []
|
|
87
|
+
};
|
|
88
|
+
function parseArgs(args) {
|
|
89
|
+
if (args.length > 0 && typeof args[args.length - 1] === "object") {
|
|
90
|
+
const argsCopy = [...args];
|
|
91
|
+
const execaOptions = argsCopy.pop();
|
|
92
|
+
return {
|
|
93
|
+
args: argsCopy,
|
|
94
|
+
execaOptions
|
|
95
|
+
};
|
|
96
|
+
} else {
|
|
97
|
+
return {
|
|
98
|
+
args: [...args],
|
|
99
|
+
execaOptions: {}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function createBintastic(options) {
|
|
104
|
+
let project;
|
|
105
|
+
const mergedOptions = {
|
|
106
|
+
...DEFAULT_BINTASTIC_OPTIONS,
|
|
107
|
+
...options
|
|
108
|
+
};
|
|
109
|
+
function runBin(...args) {
|
|
110
|
+
const mergedRunOptions = parseArgs(args);
|
|
111
|
+
const binPath = typeof mergedOptions.binPath === "function" ? mergedOptions.binPath(project) : mergedOptions.binPath;
|
|
112
|
+
const optionsEnv = mergedRunOptions.execaOptions.env;
|
|
113
|
+
const debugEnv = optionsEnv?.BINTASTIC_DEBUG ?? process.env.BINTASTIC_DEBUG;
|
|
114
|
+
const nodeOptions = [];
|
|
115
|
+
if (debugEnv && debugEnv !== "0" && debugEnv.toLowerCase() !== "false") {
|
|
116
|
+
if (debugEnv.toLowerCase() === "break") {
|
|
117
|
+
nodeOptions.push("--inspect-brk=0");
|
|
118
|
+
} else {
|
|
119
|
+
nodeOptions.push("--inspect=0");
|
|
120
|
+
}
|
|
121
|
+
console.log(`[bintastic] Debugging enabled. Fixture: ${project.baseDir}`);
|
|
122
|
+
}
|
|
123
|
+
const resolvedCwd = mergedRunOptions.execaOptions.cwd ?? project.baseDir;
|
|
124
|
+
return (0, import_execa2.execaNode)(binPath, [...mergedOptions.staticArgs, ...mergedRunOptions.args], {
|
|
125
|
+
reject: false,
|
|
126
|
+
cwd: resolvedCwd,
|
|
127
|
+
nodeOptions,
|
|
128
|
+
...mergedRunOptions.execaOptions
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function runBinDebug(...args) {
|
|
132
|
+
const parsedArgs = parseArgs(args);
|
|
133
|
+
const debugEnv = process.env.BINTASTIC_DEBUG || "attach";
|
|
134
|
+
parsedArgs.execaOptions = {
|
|
135
|
+
...parsedArgs.execaOptions,
|
|
136
|
+
env: {
|
|
137
|
+
...parsedArgs.execaOptions.env,
|
|
138
|
+
BINTASTIC_DEBUG: debugEnv
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const reconstructedArgs = [...parsedArgs.args, parsedArgs.execaOptions];
|
|
142
|
+
return runBin(...reconstructedArgs);
|
|
143
|
+
}
|
|
144
|
+
async function setupProject() {
|
|
145
|
+
project = "createProject" in mergedOptions ? await mergedOptions.createProject() : new BintasticProject();
|
|
146
|
+
await project.write();
|
|
147
|
+
return project;
|
|
148
|
+
}
|
|
149
|
+
async function setupTmpDir() {
|
|
150
|
+
if (typeof project === "undefined") {
|
|
151
|
+
await setupProject();
|
|
152
|
+
}
|
|
153
|
+
return project.baseDir;
|
|
154
|
+
}
|
|
155
|
+
function teardownProject() {
|
|
156
|
+
const debugEnv = process.env.BINTASTIC_DEBUG;
|
|
157
|
+
if (debugEnv && debugEnv !== "0" && debugEnv.toLowerCase() !== "false") {
|
|
158
|
+
console.log(`[bintastic] Fixture preserved: ${project.baseDir}`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
project.dispose();
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
runBin,
|
|
165
|
+
runBinDebug,
|
|
166
|
+
setupProject,
|
|
167
|
+
teardownProject,
|
|
168
|
+
setupTmpDir
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
172
|
+
0 && (module.exports = {
|
|
173
|
+
BintasticProject,
|
|
174
|
+
createBintastic
|
|
175
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ResultPromise, Options } from 'execa';
|
|
2
|
+
import { Project } from 'fixturify-project';
|
|
3
|
+
|
|
4
|
+
declare class BintasticProject extends Project {
|
|
5
|
+
private _dirChanged;
|
|
6
|
+
/**
|
|
7
|
+
* Constructs an instance of a BintasticProject.
|
|
8
|
+
* @param {string} name - The name of the project. Used within the package.json as the name property.
|
|
9
|
+
* @param {string} version - The version of the project. Used within the package.json as the version property.
|
|
10
|
+
* @param {Function} cb - An optional callback for additional setup steps after the project is constructed.
|
|
11
|
+
*/
|
|
12
|
+
constructor(name?: string, version?: string, cb?: (project: Project) => void);
|
|
13
|
+
/**
|
|
14
|
+
* Runs `git init` inside a project.
|
|
15
|
+
* @returns {*} {ResultPromise}
|
|
16
|
+
*/
|
|
17
|
+
gitInit(): ResultPromise;
|
|
18
|
+
/**
|
|
19
|
+
* Writes the project files to disk.
|
|
20
|
+
*/
|
|
21
|
+
write(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Changes a directory from inside the project.
|
|
24
|
+
*/
|
|
25
|
+
chdir(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Correctly disposes of the project, observing when the directory has been changed.
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
dispose(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for configuring bintastic.
|
|
35
|
+
*/
|
|
36
|
+
interface BintasticOptions<TProject> {
|
|
37
|
+
/**
|
|
38
|
+
* The absolute path to the bin to invoke
|
|
39
|
+
*/
|
|
40
|
+
binPath: string | (<TProject extends BintasticProject>(project: TProject) => string);
|
|
41
|
+
/**
|
|
42
|
+
* An array of static arguments that will be used every time when running the bin
|
|
43
|
+
*/
|
|
44
|
+
staticArgs?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* An optional function to use to create the project. Use this if you want to provide a custom implementation of a BintasticProject.
|
|
47
|
+
*/
|
|
48
|
+
createProject?: () => Promise<TProject>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Function signature for running the configured CLI binary.
|
|
52
|
+
*/
|
|
53
|
+
interface RunBin {
|
|
54
|
+
/**
|
|
55
|
+
* A runBin implementation that takes no parameters.
|
|
56
|
+
* @returns {*} {ResultPromise}
|
|
57
|
+
*/
|
|
58
|
+
(): ResultPromise;
|
|
59
|
+
/**
|
|
60
|
+
* A runBin implementation that takes string varargs.
|
|
61
|
+
* @param {...RunBinArgs} args
|
|
62
|
+
* @returns {*} {ResultPromise}
|
|
63
|
+
*/
|
|
64
|
+
(...args: [...binArgs: string[]]): ResultPromise;
|
|
65
|
+
/**
|
|
66
|
+
* A runBin implementation that takes an Options object.
|
|
67
|
+
* @param {...RunBinArgs} args
|
|
68
|
+
* @returns {*} {ResultPromise}
|
|
69
|
+
*/
|
|
70
|
+
(...args: [execaOptions: Options]): ResultPromise;
|
|
71
|
+
/**
|
|
72
|
+
* A runBin implementation that takes string or an Options object varargs.
|
|
73
|
+
* @param {...RunBinArgs} args
|
|
74
|
+
* @returns {*} {ResultPromise}
|
|
75
|
+
*/
|
|
76
|
+
(...args: [...binArgs: string[], execaOptions: Options]): ResultPromise;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The result returned by createBintastic.
|
|
80
|
+
*/
|
|
81
|
+
interface CreateBintasticResult<TProject extends BintasticProject> {
|
|
82
|
+
/**
|
|
83
|
+
* Runs the configured bin function via execa.
|
|
84
|
+
*/
|
|
85
|
+
runBin: RunBin;
|
|
86
|
+
/**
|
|
87
|
+
* Sets up the specified project for use within tests.
|
|
88
|
+
*/
|
|
89
|
+
setupProject: () => Promise<TProject>;
|
|
90
|
+
/**
|
|
91
|
+
* Sets up a tmp directory for use within tests.
|
|
92
|
+
*/
|
|
93
|
+
setupTmpDir: () => Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Tears the project down, ensuring the tmp directory is removed.
|
|
96
|
+
* When BINTASTIC_DEBUG is set, fixtures are preserved for inspection.
|
|
97
|
+
*/
|
|
98
|
+
teardownProject: () => void;
|
|
99
|
+
/**
|
|
100
|
+
* Runs the configured bin with Node inspector enabled in attach mode (--inspect).
|
|
101
|
+
* Set BINTASTIC_DEBUG=break to break on first line instead.
|
|
102
|
+
*/
|
|
103
|
+
runBinDebug: RunBin;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates the bintastic API functions to use within tests.
|
|
107
|
+
* @param {BintasticOptions<TProject>} options - An object of bintastic options
|
|
108
|
+
* @returns {CreateBintasticResult<TProject>} - A project instance.
|
|
109
|
+
*/
|
|
110
|
+
declare function createBintastic<TProject extends BintasticProject>(options: BintasticOptions<TProject>): CreateBintasticResult<TProject>;
|
|
111
|
+
|
|
112
|
+
export { type BintasticOptions, BintasticProject, type CreateBintasticResult, type RunBin, createBintastic };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ResultPromise, Options } from 'execa';
|
|
2
|
+
import { Project } from 'fixturify-project';
|
|
3
|
+
|
|
4
|
+
declare class BintasticProject extends Project {
|
|
5
|
+
private _dirChanged;
|
|
6
|
+
/**
|
|
7
|
+
* Constructs an instance of a BintasticProject.
|
|
8
|
+
* @param {string} name - The name of the project. Used within the package.json as the name property.
|
|
9
|
+
* @param {string} version - The version of the project. Used within the package.json as the version property.
|
|
10
|
+
* @param {Function} cb - An optional callback for additional setup steps after the project is constructed.
|
|
11
|
+
*/
|
|
12
|
+
constructor(name?: string, version?: string, cb?: (project: Project) => void);
|
|
13
|
+
/**
|
|
14
|
+
* Runs `git init` inside a project.
|
|
15
|
+
* @returns {*} {ResultPromise}
|
|
16
|
+
*/
|
|
17
|
+
gitInit(): ResultPromise;
|
|
18
|
+
/**
|
|
19
|
+
* Writes the project files to disk.
|
|
20
|
+
*/
|
|
21
|
+
write(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Changes a directory from inside the project.
|
|
24
|
+
*/
|
|
25
|
+
chdir(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Correctly disposes of the project, observing when the directory has been changed.
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
dispose(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for configuring bintastic.
|
|
35
|
+
*/
|
|
36
|
+
interface BintasticOptions<TProject> {
|
|
37
|
+
/**
|
|
38
|
+
* The absolute path to the bin to invoke
|
|
39
|
+
*/
|
|
40
|
+
binPath: string | (<TProject extends BintasticProject>(project: TProject) => string);
|
|
41
|
+
/**
|
|
42
|
+
* An array of static arguments that will be used every time when running the bin
|
|
43
|
+
*/
|
|
44
|
+
staticArgs?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* An optional function to use to create the project. Use this if you want to provide a custom implementation of a BintasticProject.
|
|
47
|
+
*/
|
|
48
|
+
createProject?: () => Promise<TProject>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Function signature for running the configured CLI binary.
|
|
52
|
+
*/
|
|
53
|
+
interface RunBin {
|
|
54
|
+
/**
|
|
55
|
+
* A runBin implementation that takes no parameters.
|
|
56
|
+
* @returns {*} {ResultPromise}
|
|
57
|
+
*/
|
|
58
|
+
(): ResultPromise;
|
|
59
|
+
/**
|
|
60
|
+
* A runBin implementation that takes string varargs.
|
|
61
|
+
* @param {...RunBinArgs} args
|
|
62
|
+
* @returns {*} {ResultPromise}
|
|
63
|
+
*/
|
|
64
|
+
(...args: [...binArgs: string[]]): ResultPromise;
|
|
65
|
+
/**
|
|
66
|
+
* A runBin implementation that takes an Options object.
|
|
67
|
+
* @param {...RunBinArgs} args
|
|
68
|
+
* @returns {*} {ResultPromise}
|
|
69
|
+
*/
|
|
70
|
+
(...args: [execaOptions: Options]): ResultPromise;
|
|
71
|
+
/**
|
|
72
|
+
* A runBin implementation that takes string or an Options object varargs.
|
|
73
|
+
* @param {...RunBinArgs} args
|
|
74
|
+
* @returns {*} {ResultPromise}
|
|
75
|
+
*/
|
|
76
|
+
(...args: [...binArgs: string[], execaOptions: Options]): ResultPromise;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The result returned by createBintastic.
|
|
80
|
+
*/
|
|
81
|
+
interface CreateBintasticResult<TProject extends BintasticProject> {
|
|
82
|
+
/**
|
|
83
|
+
* Runs the configured bin function via execa.
|
|
84
|
+
*/
|
|
85
|
+
runBin: RunBin;
|
|
86
|
+
/**
|
|
87
|
+
* Sets up the specified project for use within tests.
|
|
88
|
+
*/
|
|
89
|
+
setupProject: () => Promise<TProject>;
|
|
90
|
+
/**
|
|
91
|
+
* Sets up a tmp directory for use within tests.
|
|
92
|
+
*/
|
|
93
|
+
setupTmpDir: () => Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Tears the project down, ensuring the tmp directory is removed.
|
|
96
|
+
* When BINTASTIC_DEBUG is set, fixtures are preserved for inspection.
|
|
97
|
+
*/
|
|
98
|
+
teardownProject: () => void;
|
|
99
|
+
/**
|
|
100
|
+
* Runs the configured bin with Node inspector enabled in attach mode (--inspect).
|
|
101
|
+
* Set BINTASTIC_DEBUG=break to break on first line instead.
|
|
102
|
+
*/
|
|
103
|
+
runBinDebug: RunBin;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates the bintastic API functions to use within tests.
|
|
107
|
+
* @param {BintasticOptions<TProject>} options - An object of bintastic options
|
|
108
|
+
* @returns {CreateBintasticResult<TProject>} - A project instance.
|
|
109
|
+
*/
|
|
110
|
+
declare function createBintastic<TProject extends BintasticProject>(options: BintasticOptions<TProject>): CreateBintasticResult<TProject>;
|
|
111
|
+
|
|
112
|
+
export { type BintasticOptions, BintasticProject, type CreateBintasticResult, type RunBin, createBintastic };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/create-bintastic.ts
|
|
2
|
+
import { execaNode } from "execa";
|
|
3
|
+
|
|
4
|
+
// src/project.ts
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import { Project } from "fixturify-project";
|
|
7
|
+
var ROOT = process.cwd();
|
|
8
|
+
var BintasticProject = class extends Project {
|
|
9
|
+
/**
|
|
10
|
+
* Constructs an instance of a BintasticProject.
|
|
11
|
+
* @param {string} name - The name of the project. Used within the package.json as the name property.
|
|
12
|
+
* @param {string} version - The version of the project. Used within the package.json as the version property.
|
|
13
|
+
* @param {Function} cb - An optional callback for additional setup steps after the project is constructed.
|
|
14
|
+
*/
|
|
15
|
+
constructor(name = "fake-project", version, cb) {
|
|
16
|
+
super(name, version, cb);
|
|
17
|
+
this._dirChanged = false;
|
|
18
|
+
this.pkg = Object.assign({}, this.pkg, {
|
|
19
|
+
license: "MIT",
|
|
20
|
+
description: "Fake project",
|
|
21
|
+
repository: "http://fakerepo.com"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs `git init` inside a project.
|
|
26
|
+
* @returns {*} {ResultPromise}
|
|
27
|
+
*/
|
|
28
|
+
gitInit() {
|
|
29
|
+
return execa("git", ["init", "-q", this.baseDir]);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Writes the project files to disk.
|
|
33
|
+
*/
|
|
34
|
+
async write() {
|
|
35
|
+
return super.write();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Changes a directory from inside the project.
|
|
39
|
+
*/
|
|
40
|
+
async chdir() {
|
|
41
|
+
this._dirChanged = true;
|
|
42
|
+
await this.write();
|
|
43
|
+
process.chdir(this.baseDir);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Correctly disposes of the project, observing when the directory has been changed.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
dispose() {
|
|
50
|
+
if (this._dirChanged) {
|
|
51
|
+
process.chdir(ROOT);
|
|
52
|
+
}
|
|
53
|
+
return super.dispose();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/create-bintastic.ts
|
|
58
|
+
var DEFAULT_BINTASTIC_OPTIONS = {
|
|
59
|
+
staticArgs: []
|
|
60
|
+
};
|
|
61
|
+
function parseArgs(args) {
|
|
62
|
+
if (args.length > 0 && typeof args[args.length - 1] === "object") {
|
|
63
|
+
const argsCopy = [...args];
|
|
64
|
+
const execaOptions = argsCopy.pop();
|
|
65
|
+
return {
|
|
66
|
+
args: argsCopy,
|
|
67
|
+
execaOptions
|
|
68
|
+
};
|
|
69
|
+
} else {
|
|
70
|
+
return {
|
|
71
|
+
args: [...args],
|
|
72
|
+
execaOptions: {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function createBintastic(options) {
|
|
77
|
+
let project;
|
|
78
|
+
const mergedOptions = {
|
|
79
|
+
...DEFAULT_BINTASTIC_OPTIONS,
|
|
80
|
+
...options
|
|
81
|
+
};
|
|
82
|
+
function runBin(...args) {
|
|
83
|
+
const mergedRunOptions = parseArgs(args);
|
|
84
|
+
const binPath = typeof mergedOptions.binPath === "function" ? mergedOptions.binPath(project) : mergedOptions.binPath;
|
|
85
|
+
const optionsEnv = mergedRunOptions.execaOptions.env;
|
|
86
|
+
const debugEnv = optionsEnv?.BINTASTIC_DEBUG ?? process.env.BINTASTIC_DEBUG;
|
|
87
|
+
const nodeOptions = [];
|
|
88
|
+
if (debugEnv && debugEnv !== "0" && debugEnv.toLowerCase() !== "false") {
|
|
89
|
+
if (debugEnv.toLowerCase() === "break") {
|
|
90
|
+
nodeOptions.push("--inspect-brk=0");
|
|
91
|
+
} else {
|
|
92
|
+
nodeOptions.push("--inspect=0");
|
|
93
|
+
}
|
|
94
|
+
console.log(`[bintastic] Debugging enabled. Fixture: ${project.baseDir}`);
|
|
95
|
+
}
|
|
96
|
+
const resolvedCwd = mergedRunOptions.execaOptions.cwd ?? project.baseDir;
|
|
97
|
+
return execaNode(binPath, [...mergedOptions.staticArgs, ...mergedRunOptions.args], {
|
|
98
|
+
reject: false,
|
|
99
|
+
cwd: resolvedCwd,
|
|
100
|
+
nodeOptions,
|
|
101
|
+
...mergedRunOptions.execaOptions
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function runBinDebug(...args) {
|
|
105
|
+
const parsedArgs = parseArgs(args);
|
|
106
|
+
const debugEnv = process.env.BINTASTIC_DEBUG || "attach";
|
|
107
|
+
parsedArgs.execaOptions = {
|
|
108
|
+
...parsedArgs.execaOptions,
|
|
109
|
+
env: {
|
|
110
|
+
...parsedArgs.execaOptions.env,
|
|
111
|
+
BINTASTIC_DEBUG: debugEnv
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const reconstructedArgs = [...parsedArgs.args, parsedArgs.execaOptions];
|
|
115
|
+
return runBin(...reconstructedArgs);
|
|
116
|
+
}
|
|
117
|
+
async function setupProject() {
|
|
118
|
+
project = "createProject" in mergedOptions ? await mergedOptions.createProject() : new BintasticProject();
|
|
119
|
+
await project.write();
|
|
120
|
+
return project;
|
|
121
|
+
}
|
|
122
|
+
async function setupTmpDir() {
|
|
123
|
+
if (typeof project === "undefined") {
|
|
124
|
+
await setupProject();
|
|
125
|
+
}
|
|
126
|
+
return project.baseDir;
|
|
127
|
+
}
|
|
128
|
+
function teardownProject() {
|
|
129
|
+
const debugEnv = process.env.BINTASTIC_DEBUG;
|
|
130
|
+
if (debugEnv && debugEnv !== "0" && debugEnv.toLowerCase() !== "false") {
|
|
131
|
+
console.log(`[bintastic] Fixture preserved: ${project.baseDir}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
project.dispose();
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
runBin,
|
|
138
|
+
runBinDebug,
|
|
139
|
+
setupProject,
|
|
140
|
+
teardownProject,
|
|
141
|
+
setupTmpDir
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
BintasticProject,
|
|
146
|
+
createBintastic
|
|
147
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bintastic",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A test harness for Node.js CLI tools",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"test",
|
|
8
|
+
"harness",
|
|
9
|
+
"binary",
|
|
10
|
+
"subprocess"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/scalvert/bintastic.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/scalvert/bintastic#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/scalvert/bintastic/issues"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Steve Calvert <steve.calvert@gmail.com>",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"require": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
35
|
+
"docs": "typedoc",
|
|
36
|
+
"docs:check": "markdown-code check",
|
|
37
|
+
"docs:sync": "markdown-code sync",
|
|
38
|
+
"format": "prettier --write .",
|
|
39
|
+
"format:check": "prettier --check .",
|
|
40
|
+
"lint": "eslint . && npm run format:check && npm run docs:check",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"test": "npm run lint && npm run test:vitest",
|
|
43
|
+
"test:vitest": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"watch": "npm run build -- --watch src"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"execa": "^9.6.1",
|
|
52
|
+
"fixturify-project": "^7.1.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
56
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
57
|
+
"@babel/plugin-transform-typescript": "^7.27.0",
|
|
58
|
+
"@babel/preset-env": "^7.26.9",
|
|
59
|
+
"@babel/preset-typescript": "^7.27.0",
|
|
60
|
+
"@release-it-plugins/lerna-changelog": "^8.0.1",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
62
|
+
"@typescript-eslint/parser": "^8.29.1",
|
|
63
|
+
"eslint": "^9.24.0",
|
|
64
|
+
"eslint-config-prettier": "^10.1.2",
|
|
65
|
+
"eslint-plugin-jsdoc": "^61.5.0",
|
|
66
|
+
"eslint-plugin-node": "^11.1.0",
|
|
67
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
68
|
+
"eslint-plugin-unicorn": "^62.0.0",
|
|
69
|
+
"fixturify": "^3.0.0",
|
|
70
|
+
"markdown-code": "^0.6.1",
|
|
71
|
+
"prettier": "^3.5.3",
|
|
72
|
+
"release-it": "^19.2.2",
|
|
73
|
+
"tsup": "^8.4.0",
|
|
74
|
+
"type-fest": "^5.3.1",
|
|
75
|
+
"typedoc": "^0.28.15",
|
|
76
|
+
"typescript": "^5.8.3",
|
|
77
|
+
"vite": "^7.3.0",
|
|
78
|
+
"vitest": "^4.0.16"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=22"
|
|
82
|
+
},
|
|
83
|
+
"publishConfig": {
|
|
84
|
+
"registry": "https://registry.npmjs.org",
|
|
85
|
+
"access": "public"
|
|
86
|
+
},
|
|
87
|
+
"release-it": {
|
|
88
|
+
"plugins": {
|
|
89
|
+
"@release-it-plugins/lerna-changelog": {
|
|
90
|
+
"infile": "CHANGELOG.md",
|
|
91
|
+
"launchEditor": true
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"git": {
|
|
95
|
+
"tagName": "v${version}"
|
|
96
|
+
},
|
|
97
|
+
"github": {
|
|
98
|
+
"release": true,
|
|
99
|
+
"tokenRef": "GITHUB_AUTH"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|