@theholocron/holocron-plugin-clerk 2.0.0-alpha.0 → 2.0.0-alpha.5

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
@@ -3,6 +3,12 @@
3
3
  Clerk plugin for [Holocron](../cli). Implements the `auth` capability
4
4
  against [Clerk's Backend REST API](https://clerk.com/docs/reference/backend-api).
5
5
 
6
+ ## Install
7
+
8
+ ```bash
9
+ pnpm add -D @theholocron/holocron-plugin-clerk@alpha
10
+ ```
11
+
6
12
  ## Auth
7
13
 
8
14
  Token resolution order:
@@ -12,7 +18,7 @@ Token resolution order:
12
18
  3. `CLERK_SECRET_KEY` env var (Clerk's own default; what their docs reference)
13
19
 
14
20
  > **Why not the `clerk` CLI?** Rando's adapter shells out to `npx clerk@latest
15
- > api …`, but the `clerk` CLI just wraps the same REST API holocron talks to.
21
+ api …`, but the `clerk` CLI just wraps the same REST API holocron talks to.
16
22
  > Direct REST drops a system-binary dependency and matches the uniform auth/REST
17
23
  > pattern across the other holocron plugins.
18
24
 
@@ -20,9 +26,9 @@ Token resolution order:
20
26
 
21
27
  ```jsonc
22
28
  {
23
- "providers": {
24
- "auth": "clerk"
25
- }
29
+ "providers": {
30
+ "auth": "clerk",
31
+ },
26
32
  }
27
33
  ```
28
34
 
@@ -32,17 +38,20 @@ for Development, `sk_live_*` for Production.
32
38
 
33
39
  ## What's implemented
34
40
 
35
- | Method | What it does |
36
- | ------------------------- | ----------------------------------------------------------------------------- |
37
- | `describe` | Declares `CLERK_PUBLISHABLE_KEY` + `CLERK_SECRET_KEY` as runtime envs |
38
- | `whoami` | `GET /users/count` reachability probe (returns user count) |
39
- | `ensureWebhookApp` | `POST /webhooks/svix` — idempotent; already-exists → `{alreadyExists:true}` |
40
- | `getWebhookDashboardUrl` | `POST /webhooks/svix_url` — deep-link to Svix dashboard |
41
- | `createUser` | `POST /users` — seeds users (test fixtures, admin bootstrap) |
41
+ | Method | What it does |
42
+ | ------------------------ | --------------------------------------------------------------------------- |
43
+ | `describe` | Declares `CLERK_PUBLISHABLE_KEY` + `CLERK_SECRET_KEY` as runtime envs |
44
+ | `whoami` | `GET /users/count` reachability probe (returns user count) |
45
+ | `ensureWebhookApp` | `POST /webhooks/svix` — idempotent; already-exists → `{alreadyExists:true}` |
46
+ | `getWebhookDashboardUrl` | `POST /webhooks/svix_url` — deep-link to Svix dashboard |
47
+ | `createUser` | `POST /users` — seeds users (test fixtures, admin bootstrap) |
42
48
 
43
49
  ## Status
44
50
 
45
- **v0.0.0 — first port.** Capability matches Rando's
46
- `adapters/clerk-cli.ts` surface plus a holocron-native `describe()`,
47
- all via direct REST against `api.clerk.com/v1`. No `clerk` CLI binary
48
- required on the operator's machine.
51
+ **`v2.0.0-alpha.0`**published on npm under the `alpha` dist-tag.
52
+ [Release notes](https://github.com/theholocron/holocron/releases/tag/v2.0.0-alpha.0).
53
+ APIs may still shift before stable v2.0.0.
54
+
55
+ Real Svix HMAC verification in `parseWebhook` is deferred to
56
+ [#80](https://github.com/theholocron/holocron/issues/80) — current
57
+ implementation validates shape only.
package/dist/index.d.mts CHANGED
@@ -4,10 +4,13 @@ import { Auth, AuthDescription, AuthEvent, AuthIdentity, AuthUser, CreateAuthUse
4
4
  /**
5
5
  * Token resolution for the Clerk plugin.
6
6
  *
7
- * Resolution order:
7
+ * Resolution order (matches the standard 4-step precedence set by
8
+ * `.notes/tech-auth-bootstrap.spec.md`):
8
9
  * 1. explicit `cliToken` argument (from `--token` flag)
9
10
  * 2. HOLOCRON_CLERK_SECRET_KEY env var (preferred — explicit intent)
10
11
  * 3. CLERK_SECRET_KEY env var (the default Clerk's docs reference)
12
+ * 4. keyring (com.theholocron.cli / "clerk")
13
+ * 5. AuthError naming all four options + the bootstrap hint
11
14
  *
12
15
  * The key (sk_test_* / sk_live_*) determines which Clerk instance —
13
16
  * Development or Production — every call hits.
@@ -20,6 +23,8 @@ interface ResolveTokenInput {
20
23
  cliToken?: string;
21
24
  /** Env vars; passed in for testability. Defaults to `process.env`. */
22
25
  env?: NodeJS.ProcessEnv;
26
+ /** Keyring lookup fn; passed in for testability. Defaults to `getToken(provider)`. */
27
+ keyring?: (provider: string) => string | null;
23
28
  }
24
29
  declare function resolveToken(input?: ResolveTokenInput): string;
25
30
  //#endregion
@@ -38,7 +43,7 @@ interface RestClientOptions {
38
43
  baseUrl?: string;
39
44
  }
40
45
  interface RequestOptions {
41
- method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
46
+ method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
42
47
  body?: unknown;
43
48
  query?: Record<string, string>;
44
49
  }
@@ -69,6 +74,32 @@ declare class ClerkAuth implements Auth {
69
74
  //#region src/parse-webhook.d.ts
70
75
  declare function parseWebhook(input: ParseWebhookInput): Promise<AuthEvent>;
71
76
  //#endregion
77
+ //#region src/verify-token.d.ts
78
+ /**
79
+ * `verifyToken` — plugin-level export used by `holocron auth set` +
80
+ * `holocron auth check`. Hits Clerk's `/v1/instance` endpoint, which
81
+ * requires a valid secret key and returns instance metadata (id,
82
+ * environment_type, etc.). Invalid keys → 401.
83
+ *
84
+ * Clerk doesn't have a traditional user-oriented whoami — the "authed
85
+ * entity" is your Clerk INSTANCE, not a user. `/v1/instance` is the
86
+ * canonical "is this secret key valid?" endpoint.
87
+ */
88
+ interface VerifyTokenSuccess {
89
+ ok: true;
90
+ subject: string;
91
+ }
92
+ interface VerifyTokenFailure {
93
+ ok: false;
94
+ message: string;
95
+ }
96
+ type VerifyTokenResult = VerifyTokenSuccess | VerifyTokenFailure;
97
+ interface VerifyTokenOptions {
98
+ baseUrl?: string;
99
+ fetch?: typeof fetch;
100
+ }
101
+ declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult>;
102
+ //#endregion
72
103
  //#region src/index.d.ts
73
104
  interface ClerkPluginOptions extends ResolveTokenInput {
74
105
  /** Override base URL for tests. */
@@ -88,5 +119,12 @@ declare function createPlugin(options?: ClerkPluginOptions): {
88
119
  auth: () => Auth;
89
120
  };
90
121
  };
122
+ /**
123
+ * One-line hint printed by `holocron auth set clerk` when no token
124
+ * is supplied or the supplied token is rejected. Points operators at
125
+ * the Clerk dashboard's API Keys section — MUST be the SECRET key
126
+ * (sk_test_* / sk_live_*), not the publishable key.
127
+ */
128
+ declare const AUTH_HINT: string;
91
129
  //#endregion
92
- export { AuthError, ClerkAuth, ClerkPluginOptions, ClerkRestClient, PluginContext, ResolveTokenInput, auth, createContext, createPlugin, parseWebhook, resolveToken };
130
+ export { AUTH_HINT, AuthError, ClerkAuth, ClerkPluginOptions, ClerkRestClient, PluginContext, ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, auth, createContext, createPlugin, parseWebhook, resolveToken, verifyToken };
package/dist/index.mjs CHANGED
@@ -1,12 +1,15 @@
1
- import { ProviderApiError, WebhookVerificationError } from "@theholocron/cli";
1
+ import { ProviderApiError, WebhookVerificationError, getToken } from "@theholocron/cli";
2
2
  //#region src/auth.ts
3
3
  /**
4
4
  * Token resolution for the Clerk plugin.
5
5
  *
6
- * Resolution order:
6
+ * Resolution order (matches the standard 4-step precedence set by
7
+ * `.notes/tech-auth-bootstrap.spec.md`):
7
8
  * 1. explicit `cliToken` argument (from `--token` flag)
8
9
  * 2. HOLOCRON_CLERK_SECRET_KEY env var (preferred — explicit intent)
9
10
  * 3. CLERK_SECRET_KEY env var (the default Clerk's docs reference)
11
+ * 4. keyring (com.theholocron.cli / "clerk")
12
+ * 5. AuthError naming all four options + the bootstrap hint
10
13
  *
11
14
  * The key (sk_test_* / sk_live_*) determines which Clerk instance —
12
15
  * Development or Production — every call hits.
@@ -16,8 +19,9 @@ var AuthError = class extends Error {
16
19
  };
17
20
  function resolveToken(input = {}) {
18
21
  const env = input.env ?? process.env;
19
- const token = input.cliToken || env.HOLOCRON_CLERK_SECRET_KEY || env.CLERK_SECRET_KEY;
20
- if (!token) throw new AuthError("no Clerk secret key found. Pass --token <KEY>, or set HOLOCRON_CLERK_SECRET_KEY / CLERK_SECRET_KEY.");
22
+ const keyring = input.keyring ?? getToken;
23
+ const token = input.cliToken || env["HOLOCRON_CLERK_SECRET_KEY"] || env["CLERK_SECRET_KEY"] || keyring("clerk");
24
+ if (!token) throw new AuthError("no Clerk secret key found. Pass --token <KEY>, set HOLOCRON_CLERK_SECRET_KEY / CLERK_SECRET_KEY, or run: holocron auth set clerk <KEY>");
21
25
  return token;
22
26
  }
23
27
  //#endregion
@@ -127,7 +131,9 @@ var ClerkRestClient = class {
127
131
  constructor(opts) {
128
132
  this.token = opts.token;
129
133
  this.fetchImpl = opts.fetch ?? globalThis.fetch;
130
- this.baseUrl = (opts.baseUrl ?? "https://api.clerk.com/v1").replace(/\/+$/, "");
134
+ let url = opts.baseUrl ?? "https://api.clerk.com/v1";
135
+ while (url.endsWith("/")) url = url.slice(0, -1);
136
+ this.baseUrl = url;
131
137
  }
132
138
  async request(path, opts = {}) {
133
139
  const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`);
@@ -230,6 +236,36 @@ async function verifySignature(input) {
230
236
  return Promise.resolve();
231
237
  }
232
238
  //#endregion
239
+ //#region src/verify-token.ts
240
+ /**
241
+ * `verifyToken` — plugin-level export used by `holocron auth set` +
242
+ * `holocron auth check`. Hits Clerk's `/v1/instance` endpoint, which
243
+ * requires a valid secret key and returns instance metadata (id,
244
+ * environment_type, etc.). Invalid keys → 401.
245
+ *
246
+ * Clerk doesn't have a traditional user-oriented whoami — the "authed
247
+ * entity" is your Clerk INSTANCE, not a user. `/v1/instance` is the
248
+ * canonical "is this secret key valid?" endpoint.
249
+ */
250
+ async function verifyToken(token, opts = {}) {
251
+ const restOpts = { token };
252
+ if (opts.baseUrl !== void 0) restOpts.baseUrl = opts.baseUrl;
253
+ if (opts.fetch !== void 0) restOpts.fetch = opts.fetch;
254
+ const rest = new ClerkRestClient(restOpts);
255
+ try {
256
+ const instance = await rest.request("/instance");
257
+ return {
258
+ ok: true,
259
+ subject: `${instance?.environment_type ?? "unknown"} instance ${instance?.id ?? "unknown"}`
260
+ };
261
+ } catch (err) {
262
+ return {
263
+ ok: false,
264
+ message: err instanceof Error ? err.message : String(err)
265
+ };
266
+ }
267
+ }
268
+ //#endregion
233
269
  //#region src/index.ts
234
270
  function createContext(options = {}) {
235
271
  const restOpts = { token: resolveToken(options) };
@@ -250,5 +286,12 @@ function createPlugin(options = {}) {
250
286
  capabilities: { auth: () => auth(ctx) }
251
287
  };
252
288
  }
289
+ /**
290
+ * One-line hint printed by `holocron auth set clerk` when no token
291
+ * is supplied or the supplied token is rejected. Points operators at
292
+ * the Clerk dashboard's API Keys section — MUST be the SECRET key
293
+ * (sk_test_* / sk_live_*), not the publishable key.
294
+ */
295
+ const AUTH_HINT = "grab your SECRET key (sk_test_* / sk_live_*) at https://dashboard.clerk.com → API Keys, then run: holocron auth set clerk <KEY>. Do NOT use the publishable key.";
253
296
  //#endregion
254
- export { AuthError, ClerkAuth, ClerkRestClient, auth, createContext, createPlugin, parseWebhook, resolveToken };
297
+ export { AUTH_HINT, AuthError, ClerkAuth, ClerkRestClient, auth, createContext, createPlugin, parseWebhook, resolveToken, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-clerk",
3
- "version": "2.0.0-alpha.0",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "Holocron plugin for Clerk. Implements the auth capability against Clerk's Backend REST API.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-clerk#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "2.0.0-alpha.0"
24
+ "@theholocron/cli": "2.0.0-alpha.5"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@theholocron/tsconfig": "^4.1.0",
@@ -32,7 +32,8 @@
32
32
  "typescript": "^5.9.3",
33
33
  "vitest": "^3.2.6",
34
34
  "tsdown": "^0.22.3",
35
- "@theholocron/cli": "2.0.0-alpha.0"
35
+ "tsx": "^4.22.4",
36
+ "@theholocron/cli": "2.0.0-alpha.5"
36
37
  },
37
38
  "publishConfig": {
38
39
  "access": "public"
@@ -47,7 +48,8 @@
47
48
  "typecheck": "tsc --noEmit",
48
49
  "test": "vitest run",
49
50
  "test:watch": "vitest",
50
- "test:coverage": "vitest run --coverage"
51
+ "test:coverage": "vitest run --coverage",
52
+ "validate": "tsx scripts/validate.mjs"
51
53
  },
52
54
  "types": "./dist/index.d.mts"
53
55
  }