@peachlife/artisan 0.1.1 → 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 CHANGED
@@ -1,37 +1,49 @@
1
1
  # Artisan
2
2
 
3
- > **End-to-end artifact testing for CLIs.** Stop merging green unit tests for broken binaries.
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 distro matrix (Debian, Alpine, Arch) and lets you assert stdout, stderr, exit codes, and real filesystem mutations using a familiar Vitest API.
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
- * **Catch runtime blindspots:** Detect `glibc` vs `musl` mismatches, missing system dependencies, and hardcoded host paths.
14
- * **Agent-proof your workflow:** LLMs are great at writing passing unit tests for code that fails at runtime. Artisan enforces that "done" means the *artifact* works.
15
- * **Zero-config discovery:** Automatically finds executables in `package.json#bin`, `./dist`, or `./bin`.
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
- Run Artisan directly. If you don't have tests yet, it will automatically bootstrap your environment, install dependencies, and generate a starter test.
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
30
  npx @peachlife/artisan test
23
31
  ```
24
32
 
25
- **Explicit setup** scaffold without running tests, then run when ready:
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:
26
38
 
27
39
  ```bash
28
- npx @peachlife/artisan init
29
- npx @peachlife/artisan test
40
+ npx @peachlife/artisan test --artifact ./build/my-cli
30
41
  ```
31
42
 
32
43
  ## The API
33
44
 
34
- Test what users actually observe. Assert on exit codes, output, and container filesystem side-effects.
45
+ Test what users actually observe. Assert on exit codes, output, and
46
+ container filesystem side-effects.
35
47
 
36
48
  ```javascript
37
49
  // tests/artisan/cli.artisan.test.mjs
@@ -62,20 +74,25 @@ test("writes expected output to the filesystem", async ({ run, setup }) => {
62
74
 
63
75
  ## Matrix Testing via Config
64
76
 
65
- Need to test across different environments? Drop an `artisan.config.json` in your root:
77
+ Need to test across different environments? Drop an `artisan.config.json`
78
+ in your root:
66
79
 
67
80
  ```json
68
81
  {
69
- "artifact": "./dist/mycli",
70
82
  "distros": ["debian:stable-slim", "alpine:latest", "archlinux/archlinux:latest"],
71
83
  "testMatch": "**/*.artisan.test.mjs",
72
84
  "parallel": true
73
85
  }
74
86
  ```
75
87
 
88
+ Set `"artifact"` only if auto-discovery can't find your binary (multiple
89
+ executables or a non-standard path).
90
+
76
91
  ## Config Fixtures
77
92
 
78
- Config parsing is where most CLIs silently fail (`~` paths, wrong XDG locations, missing files). Artisan can capture your host configuration, scrub secrets, and mount it cleanly into the sandbox's `$XDG_CONFIG_HOME`.
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`.
79
96
 
80
97
  ```bash
81
98
  # Capture, sanitize, and inject real config into your tests
@@ -85,17 +102,17 @@ npx @peachlife/artisan add config ~/.config/mycli --name mycli-config
85
102
  ## CLI Reference
86
103
 
87
104
  | Command | Description |
88
- |---|---|
105
+ | --- | --- |
89
106
  | `npx @peachlife/artisan test` | Run all tests across the configured matrix |
90
107
  | `npx @peachlife/artisan test <file>` | Run a specific test file |
91
108
  | `npx @peachlife/artisan test -t "auth"` | Filter test execution by regex |
92
- | `npx @peachlife/artisan test --serial` | Run distros sequentially (ideal for debugging) |
109
+ | `npx @peachlife/artisan test --serial` | Run distros sequentially |
93
110
  | `npx @peachlife/artisan init` | Scaffold setup without running tests |
94
111
 
95
112
  ### Exit Codes
96
113
 
97
114
  | Code | Meaning |
98
- |---|---|
115
+ | --- | --- |
99
116
  | `0` | All tests passed |
100
117
  | `1` | One or more tests failed / timed out |
101
118
  | `2` | Usage or configuration error |
@@ -106,7 +123,9 @@ npx @peachlife/artisan add config ~/.config/mycli --name mycli-config
106
123
 
107
124
  Artisan runs perfectly in CI as long as the runner has Docker access.
108
125
 
109
- > **CI tip:** if your pipeline manages its own `npm ci` step, pass `--no-install` to `artisan init` or `artisan test --bootstrap` to skip the redundant install.
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.
110
129
 
111
130
  ```yaml
112
131
  name: Artifact Tests
@@ -122,7 +141,7 @@ jobs:
122
141
  node-version: 22
123
142
  - run: npm ci
124
143
  - run: npm run build # Ensure your artifact is compiled!
125
- - run: npx @peachlife/artisan test --no-color
144
+ - run: npx @peachlife/artisan test --artifact <my-cli> --no-color
126
145
  ```
127
146
 
128
147
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peachlife/artisan",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "artisan": "./bin/artisan.mjs"
@@ -101,7 +101,7 @@ export function parseDistros(distrosRaw) {
101
101
  return distros;
102
102
  }
103
103
 
104
- async function writeConfig({ cwd, artifact, distros, force }) {
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, artifact, distros: distroList, force });
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,
@@ -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 executable artifacts found: ${executable.join(", ")}. Pass --artifact or set "artifact" in artisan.config.json`,
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
- 'No artifact specified and no executable artifact was auto-discovered. Set "artifact" in artisan.config.json or pass --artifact',
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
 
@@ -1,5 +1,4 @@
1
1
  {
2
- "artifact": "{{ARTIFACT}}",
3
2
  "distros": [{{DISTROS}}],
4
3
  "testMatch": "**/*.artisan.test.mjs",
5
4
  "parallel": true,