Skip to content

Commit d4e2aaf

Browse files
committed
feat(init): support local debugging of clerk-cli skill source
Extends the clerk-cli skill source resolver from a CLI_VERSION-only lookup to a four-tier flow: 1. CLERK_CLI_SKILL_SOURCE env var (explicit override, any mode) 2. CLI_VERSION → matching v${version} tag URL (released binary) 3. Local working-tree path resolved via import.meta.url (bun run dev from a checked-out repo) 4. tree/main/skills/clerk-cli (compiled local binary fallback) The local working-tree path resolution is the killer feature for developer iteration: edit skills/clerk-cli/SKILL.md in your checkout, re-run `bun run dev -- init`, and the installer reads your working tree directly. No push, no rebuild, uncommitted edits visible. resolveSkillSource is exported as a pure function (takes its three inputs as plain arguments) so it's testable without mocking process.env, CLI_VERSION, or the filesystem. Adds 9 unit tests covering every branch.
1 parent 1ab2d6e commit d4e2aaf

File tree

2 files changed

+125
-7
lines changed

2 files changed

+125
-7
lines changed

packages/cli-core/src/commands/init/README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,36 @@ After scaffolding (and after env keys are pulled or keyless instructions are pri
172172

173173
Two install commands run, sharing one runner:
174174

175-
### 1. The version-pinned `clerk-cli` skill
175+
### 1. The `clerk-cli` skill (from this repo)
176176

177177
The `clerk-cli` skill ships **from this repo** at [`<repo-root>/skills/clerk-cli/`](../../../../../skills/clerk-cli/). It is **not** bundled into the binary, it's fetched at install time by the `skills` CLI, the same way any other skill would be.
178178

179-
The source URL is constructed from `CLI_VERSION` (the compile-time global injected by `scripts/build.ts`):
179+
The source is resolved by [`resolveSkillSource()`](./skills.ts) in this priority order:
180180

181-
```
182-
https://github.com/clerk/cli/tree/v<CLI_VERSION>/skills/clerk-cli
183-
```
181+
1. **`CLERK_CLI_SKILL_SOURCE` env var**, explicit override. Accepts any source the `skills` CLI accepts (github URL, gitlab URL, local path, git URL). Wins over everything else, in any build mode. Use this for testing forks, feature branches, or custom builds. Label: `custom`.
182+
2. **`CLI_VERSION` injected at compile time**, released binary path. Builds the URL `https://github.com/clerk/cli/tree/v${CLI_VERSION}/skills/clerk-cli`. Every published release (stable, canary, snapshot) tags as `v${CLI_VERSION}` (see `.github/workflows/release.yml` and `scripts/snapshot.ts`), so the same template works for all of them. Label: `v0.0.2` (or whatever the version is).
183+
3. **Local working-tree path**, dev mode auto-detection. When running via `bun run dev` from a checked-out copy of this repo, [`resolveLocalSkillPath()`](./skills.ts) walks back from `import.meta.url` to `<repo-root>/skills/clerk-cli/` and passes the **filesystem path** as the source. Working-tree edits (committed or not) are picked up immediately, no push, no rebuild. In compiled binaries, `import.meta.url` resolves to a virtual bundle path that doesn't exist on disk, so `existsSync` returns false and this branch is skipped. Label: `local`.
184+
4. **`tree/main/skills/clerk-cli` fallback**, last resort for compiled local binaries (`bun run build:compile` without `--version`) where neither the env var nor a local working tree is available. Label: `main`.
185+
186+
The label appears in the install heading (e.g. `Installing clerk-cli skill (local) with bunx...`) so the developer can tell at a glance which source the installer is hitting.
187+
188+
#### Local debugging
184189

185-
Every published release (stable, canary, and snapshot) tags as `v${CLI_VERSION}` (see `.github/workflows/release.yml`), so the same `tree/v<version>/skills/clerk-cli` URL works for all of them. The only case without a matching tag is the local-dev sentinel `0.0.0-dev` (set by `scripts/build.ts` when no `--version` is passed), which falls back to `main`.
190+
Two ways to test changes to the skill content:
191+
192+
```sh
193+
# 1. Edit-then-run loop (the most common case): just edit the file and re-run.
194+
# bun run dev auto-detects the local working-tree path, so uncommitted
195+
# edits are visible to the installer immediately.
196+
$EDITOR skills/clerk-cli/SKILL.md
197+
bun run dev -- init # Installing clerk-cli skill (local) with ...
198+
199+
# 2. Test against a remote source: set CLERK_CLI_SKILL_SOURCE to override.
200+
# Useful for QA-ing a feature branch, a fork, or a release candidate
201+
# before it's tagged.
202+
CLERK_CLI_SKILL_SOURCE='https://github.com/clerk/cli/tree/feature-x/skills/clerk-cli' \
203+
bun run dev -- init
204+
```
186205

187206
### 2. The upstream framework-pattern skills
188207

packages/cli-core/src/commands/init/skills.test.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { test, expect, describe } from "bun:test";
2-
import { buildSkillsArgs } from "./skills.ts";
2+
import { buildSkillsArgs, resolveSkillSource } from "./skills.ts";
33

44
describe("buildSkillsArgs", () => {
55
const skills = ["clerk", "clerk-setup", "clerk-nextjs-patterns"];
@@ -42,3 +42,102 @@ describe("buildSkillsArgs", () => {
4242
expect(args).not.toContain("--skill");
4343
});
4444
});
45+
46+
const REPO = "https://github.com/clerk/cli";
47+
48+
describe("resolveSkillSource", () => {
49+
test("env override wins over everything else", () => {
50+
const result = resolveSkillSource({
51+
override: "https://github.com/fork/cli/tree/main/skills/clerk-cli",
52+
cliVersion: "0.0.2",
53+
localPath: "/home/dev/clerk/cli/skills/clerk-cli",
54+
});
55+
expect(result.source).toBe("https://github.com/fork/cli/tree/main/skills/clerk-cli");
56+
expect(result.label).toBe("custom");
57+
});
58+
59+
test("env override accepts a local path", () => {
60+
const result = resolveSkillSource({
61+
override: "./my-skills/clerk-cli",
62+
cliVersion: undefined,
63+
localPath: null,
64+
});
65+
expect(result.source).toBe("./my-skills/clerk-cli");
66+
expect(result.label).toBe("custom");
67+
});
68+
69+
test("empty override string falls through to next branch", () => {
70+
const result = resolveSkillSource({
71+
override: "",
72+
cliVersion: "0.0.2",
73+
localPath: null,
74+
});
75+
expect(result.source).toBe(`${REPO}/tree/v0.0.2/skills/clerk-cli`);
76+
expect(result.label).toBe("v0.0.2");
77+
});
78+
79+
test("released stable build → matching tag URL", () => {
80+
const result = resolveSkillSource({
81+
override: undefined,
82+
cliVersion: "0.0.2",
83+
localPath: "/home/dev/clerk/cli/skills/clerk-cli",
84+
});
85+
// CLI_VERSION beats local path — released binaries should always use
86+
// their pinned tag, even if a checked-out copy of the repo is reachable.
87+
expect(result.source).toBe(`${REPO}/tree/v0.0.2/skills/clerk-cli`);
88+
expect(result.label).toBe("v0.0.2");
89+
});
90+
91+
test("released canary build → matching canary tag URL", () => {
92+
const result = resolveSkillSource({
93+
override: undefined,
94+
cliVersion: "0.0.2-canary.v20260407010352",
95+
localPath: null,
96+
});
97+
expect(result.source).toBe(`${REPO}/tree/v0.0.2-canary.v20260407010352/skills/clerk-cli`);
98+
expect(result.label).toBe("v0.0.2-canary.v20260407010352");
99+
});
100+
101+
test('"0.0.0-dev" sentinel is treated as "no version"', () => {
102+
const result = resolveSkillSource({
103+
override: undefined,
104+
cliVersion: "0.0.0-dev",
105+
localPath: "/home/dev/clerk/cli/skills/clerk-cli",
106+
});
107+
expect(result.source).toBe("/home/dev/clerk/cli/skills/clerk-cli");
108+
expect(result.label).toBe("local");
109+
});
110+
111+
test("undefined cliVersion + local path available → local path", () => {
112+
const result = resolveSkillSource({
113+
override: undefined,
114+
cliVersion: undefined,
115+
localPath: "/Users/dev/clerk/cli/skills/clerk-cli",
116+
});
117+
expect(result.source).toBe("/Users/dev/clerk/cli/skills/clerk-cli");
118+
expect(result.label).toBe("local");
119+
});
120+
121+
test("no override, no version, no local path → main fallback", () => {
122+
const result = resolveSkillSource({
123+
override: undefined,
124+
cliVersion: undefined,
125+
localPath: null,
126+
});
127+
expect(result.source).toBe(`${REPO}/tree/main/skills/clerk-cli`);
128+
expect(result.label).toBe("main");
129+
});
130+
131+
test('"0.0.0-dev" sentinel without local path → main fallback', () => {
132+
// This is the compiled-local-binary case: bun run build:compile produced
133+
// a binary with no --version arg, no env var override, and no source
134+
// files reachable on disk.
135+
const result = resolveSkillSource({
136+
override: undefined,
137+
cliVersion: "0.0.0-dev",
138+
localPath: null,
139+
});
140+
expect(result.source).toBe(`${REPO}/tree/main/skills/clerk-cli`);
141+
expect(result.label).toBe("main");
142+
});
143+
});

0 commit comments

Comments
 (0)