@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 CHANGED
@@ -1,32 +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
- npx @peachlife/artisan test --artifact ./dist/mycli
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 container filesystem side-effects.
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` in your root:
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 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`.
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 (ideal for debugging) |
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 `--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.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peachlife/artisan",
3
- "version": "0.1.2",
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,