@peachlife/artisan 0.1.2 → 0.1.3
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 +41 -17
- package/package.json +1 -1
- package/src/cli/bootstrap.mjs +3 -7
- package/src/cli/commands/init.mjs +0 -1
- package/src/cli/parser.mjs +0 -3
- package/src/core/artifact-manager.mjs +2 -2
- package/templates/config.json.tmpl +0 -1
package/README.md
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
# Artisan
|
|
2
2
|
|
|
3
|
-
> **End-to-end artifact testing for CLIs.**
|
|
3
|
+
> **End-to-end artifact testing for CLIs.**
|
|
4
|
+
> Stop merging green unit tests for broken binaries.
|
|
4
5
|
|
|
5
|
-
Unit tests verify your logic. Artisan proves your binary actually executes.
|
|
6
|
+
Unit tests verify your logic. Artisan proves your binary actually executes.
|
|
6
7
|
|
|
7
|
-
Artisan mounts your compiled CLI into ephemeral Linux containers across a
|
|
8
|
+
Artisan mounts your compiled CLI into ephemeral Linux containers across a
|
|
9
|
+
distro matrix (Debian, Alpine, Arch) and lets you assert stdout, stderr,
|
|
10
|
+
exit codes, and real filesystem mutations using a familiar Vitest API.
|
|
8
11
|
|
|
9
12
|
Backed by **Testcontainers**, **Execa**, and **Vitest**.
|
|
10
13
|
|
|
11
14
|
## Why Artisan?
|
|
12
15
|
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
+
* **Catch runtime blindspots:** Detect `glibc` vs `musl` mismatches,
|
|
17
|
+
missing system dependencies, and hardcoded host paths.
|
|
18
|
+
* **Agent-proof your workflow:** LLMs are great at writing passing unit
|
|
19
|
+
tests for code that fails at runtime. Artisan enforces that "done" means
|
|
20
|
+
the *artifact* works.
|
|
21
|
+
* **Zero-config discovery:** Automatically finds executables in
|
|
22
|
+
`package.json#bin`, `./dist`, or `./bin`.
|
|
16
23
|
|
|
17
24
|
## Quickstart
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
Build your project, then run Artisan. It auto-discovers a single executable
|
|
27
|
+
in `dist/` or `bin/` — no config needed.
|
|
20
28
|
|
|
21
29
|
```bash
|
|
22
|
-
npx @peachlife/artisan test
|
|
30
|
+
npx @peachlife/artisan test
|
|
23
31
|
```
|
|
24
32
|
|
|
33
|
+
If you don't have tests yet, Artisan bootstraps your environment, installs
|
|
34
|
+
dependencies, and generates a starter test on the first run.
|
|
35
|
+
|
|
36
|
+
If you have multiple binaries or a non-standard output path, point to
|
|
37
|
+
a single artifact:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx @peachlife/artisan test --artifact ./build/my-cli
|
|
25
41
|
```
|
|
26
42
|
|
|
27
43
|
## The API
|
|
28
44
|
|
|
29
|
-
Test what users actually observe. Assert on exit codes, output, and
|
|
45
|
+
Test what users actually observe. Assert on exit codes, output, and
|
|
46
|
+
container filesystem side-effects.
|
|
30
47
|
|
|
31
48
|
```javascript
|
|
32
49
|
// tests/artisan/cli.artisan.test.mjs
|
|
@@ -57,20 +74,25 @@ test("writes expected output to the filesystem", async ({ run, setup }) => {
|
|
|
57
74
|
|
|
58
75
|
## Matrix Testing via Config
|
|
59
76
|
|
|
60
|
-
Need to test across different environments? Drop an `artisan.config.json`
|
|
77
|
+
Need to test across different environments? Drop an `artisan.config.json`
|
|
78
|
+
in your root:
|
|
61
79
|
|
|
62
80
|
```json
|
|
63
81
|
{
|
|
64
|
-
"artifact": "./dist/mycli",
|
|
65
82
|
"distros": ["debian:stable-slim", "alpine:latest", "archlinux/archlinux:latest"],
|
|
66
83
|
"testMatch": "**/*.artisan.test.mjs",
|
|
67
84
|
"parallel": true
|
|
68
85
|
}
|
|
69
86
|
```
|
|
70
87
|
|
|
88
|
+
Set `"artifact"` only if auto-discovery can't find your binary (multiple
|
|
89
|
+
executables or a non-standard path).
|
|
90
|
+
|
|
71
91
|
## Config Fixtures
|
|
72
92
|
|
|
73
|
-
Config parsing is where most CLIs silently fail (`~` paths, wrong XDG
|
|
93
|
+
Config parsing is where most CLIs silently fail (`~` paths, wrong XDG
|
|
94
|
+
locations, missing files). Artisan can capture your host configuration,
|
|
95
|
+
scrub secrets, and mount it cleanly into the sandbox's `$XDG_CONFIG_HOME`.
|
|
74
96
|
|
|
75
97
|
```bash
|
|
76
98
|
# Capture, sanitize, and inject real config into your tests
|
|
@@ -80,17 +102,17 @@ npx @peachlife/artisan add config ~/.config/mycli --name mycli-config
|
|
|
80
102
|
## CLI Reference
|
|
81
103
|
|
|
82
104
|
| Command | Description |
|
|
83
|
-
|
|
105
|
+
| --- | --- |
|
|
84
106
|
| `npx @peachlife/artisan test` | Run all tests across the configured matrix |
|
|
85
107
|
| `npx @peachlife/artisan test <file>` | Run a specific test file |
|
|
86
108
|
| `npx @peachlife/artisan test -t "auth"` | Filter test execution by regex |
|
|
87
|
-
| `npx @peachlife/artisan test --serial` | Run distros sequentially
|
|
109
|
+
| `npx @peachlife/artisan test --serial` | Run distros sequentially |
|
|
88
110
|
| `npx @peachlife/artisan init` | Scaffold setup without running tests |
|
|
89
111
|
|
|
90
112
|
### Exit Codes
|
|
91
113
|
|
|
92
114
|
| Code | Meaning |
|
|
93
|
-
|
|
115
|
+
| --- | --- |
|
|
94
116
|
| `0` | All tests passed |
|
|
95
117
|
| `1` | One or more tests failed / timed out |
|
|
96
118
|
| `2` | Usage or configuration error |
|
|
@@ -101,7 +123,9 @@ npx @peachlife/artisan add config ~/.config/mycli --name mycli-config
|
|
|
101
123
|
|
|
102
124
|
Artisan runs perfectly in CI as long as the runner has Docker access.
|
|
103
125
|
|
|
104
|
-
> **CI tip:** if your pipeline manages its own `npm ci` step, pass
|
|
126
|
+
> **CI tip:** if your pipeline manages its own `npm ci` step, pass
|
|
127
|
+
> `--no-install` to `artisan init` or `artisan test --bootstrap` to skip
|
|
128
|
+
> the redundant install.
|
|
105
129
|
|
|
106
130
|
```yaml
|
|
107
131
|
name: Artifact Tests
|
|
@@ -117,7 +141,7 @@ jobs:
|
|
|
117
141
|
node-version: 22
|
|
118
142
|
- run: npm ci
|
|
119
143
|
- run: npm run build # Ensure your artifact is compiled!
|
|
120
|
-
- run: npx @peachlife/artisan test --no-color
|
|
144
|
+
- run: npx @peachlife/artisan test --artifact <my-cli> --no-color
|
|
121
145
|
```
|
|
122
146
|
|
|
123
147
|
## License
|
package/package.json
CHANGED
package/src/cli/bootstrap.mjs
CHANGED
|
@@ -101,7 +101,7 @@ export function parseDistros(distrosRaw) {
|
|
|
101
101
|
return distros;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
async function writeConfig({ cwd,
|
|
104
|
+
async function writeConfig({ cwd, distros, force }) {
|
|
105
105
|
const configPath = join(cwd, "artisan.config.json");
|
|
106
106
|
if (!force && (await fileExists(configPath))) {
|
|
107
107
|
console.log(
|
|
@@ -111,10 +111,7 @@ async function writeConfig({ cwd, artifact, distros, force }) {
|
|
|
111
111
|
}
|
|
112
112
|
const tmpl = await readTemplate(TEMPLATES_DIR, "config.json.tmpl");
|
|
113
113
|
const distrosJson = distros.map((d) => JSON.stringify(d)).join(", ");
|
|
114
|
-
const content = renderTemplate(tmpl, {
|
|
115
|
-
ARTIFACT: artifact,
|
|
116
|
-
DISTROS: distrosJson,
|
|
117
|
-
});
|
|
114
|
+
const content = renderTemplate(tmpl, { DISTROS: distrosJson });
|
|
118
115
|
await writeFile(configPath, content, "utf8");
|
|
119
116
|
console.log("Created artisan.config.json");
|
|
120
117
|
}
|
|
@@ -154,14 +151,13 @@ async function installDependencies({ cwd, installer }) {
|
|
|
154
151
|
*/
|
|
155
152
|
export async function bootstrapProject({
|
|
156
153
|
cwd = process.cwd(),
|
|
157
|
-
artifact = "./dist/mycli",
|
|
158
154
|
distros = DEFAULT_DISTROS.join(","),
|
|
159
155
|
force = false,
|
|
160
156
|
install = false,
|
|
161
157
|
installer = runInstall,
|
|
162
158
|
} = {}) {
|
|
163
159
|
const distroList = parseDistros(distros);
|
|
164
|
-
await writeConfig({ cwd,
|
|
160
|
+
await writeConfig({ cwd, distros: distroList, force });
|
|
165
161
|
await writeStarterTest({ cwd, force });
|
|
166
162
|
if (install) {
|
|
167
163
|
await installDependencies({ cwd, installer });
|
|
@@ -10,7 +10,6 @@ import { bootstrapProject } from "../bootstrap.mjs";
|
|
|
10
10
|
export async function runInit(options = {}) {
|
|
11
11
|
await bootstrapProject({
|
|
12
12
|
cwd: process.cwd(),
|
|
13
|
-
artifact: options.artifact,
|
|
14
13
|
distros: options.distros,
|
|
15
14
|
force: options.force ?? false,
|
|
16
15
|
install: options.install === true,
|
package/src/cli/parser.mjs
CHANGED
|
@@ -57,10 +57,7 @@ export function createProgram() {
|
|
|
57
57
|
program
|
|
58
58
|
.command("init")
|
|
59
59
|
.description("Scaffold Artisan into the current project")
|
|
60
|
-
.option("-a, --artifact <path>", "Path to compiled CLI binary")
|
|
61
60
|
.option("-d, --distros <list>", "Comma-separated Docker images")
|
|
62
|
-
.option("--testMatch <glob>", "Test file glob pattern")
|
|
63
|
-
.option("-y, --yes", "Skip prompts, use defaults/flags")
|
|
64
61
|
.option("--force", "Overwrite existing config/test files")
|
|
65
62
|
.option("--no-install", "Skip dependency installation")
|
|
66
63
|
.action((options) => runInit(options));
|
|
@@ -94,11 +94,11 @@ async function discoverArtifact(cwd) {
|
|
|
94
94
|
if (executable.length === 1) return executable[0];
|
|
95
95
|
if (executable.length > 1) {
|
|
96
96
|
throw new UsageError(
|
|
97
|
-
`Multiple
|
|
97
|
+
`Multiple executables found in dist/ and bin/ — cannot choose automatically:\n ${executable.join("\n ")}\n\nSet "artifact" in artisan.config.json or pass --artifact to pick one.`,
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
throw new UsageError(
|
|
101
|
-
|
|
101
|
+
"No executable found in dist/ or bin/. Build your project first, then run artisan again.\nOr pass --artifact to point directly to your binary.",
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
|