@theholocron/holocron-plugin-vercel 5.0.0-alpha.27 → 5.0.0-alpha.271

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
@@ -64,13 +64,27 @@ Capability covers:
64
64
  an app) — `defaultFramework` only applies to `ensureProject()`'s
65
65
  Git-linked path.
66
66
  - `getDeployment()` — fetch a deployment by id
67
- - `ensureCustomDomain()` — idempotent add (list → check → add). Adding
68
- a domain new to the Vercel account returns `verified: false` with a
69
- DNS verification challenge (usually a CNAME to a per-project target
70
- Vercel generates — never a fixed well-known host); this method only
71
- adds the domain, reading the challenge to finish DNS setup is the
72
- caller's own job today (no `Wiki.dnsRecord()`-style shared surface
73
- for it yet)
67
+ - `ensureCustomDomain()` — idempotent add (list → add-if-missing →
68
+ check DNS routing). Ownership (`verified`) and DNS routing are
69
+ separate: once a team already owns a domain's apex, every new
70
+ subdomain auto-verifies immediately with no fresh challenge, so
71
+ attachment alone can't tell you whether traffic is actually routed
72
+ to Vercel yet. `domains.config()` answers that regardless of
73
+ ownership state, returning a per-project CNAME target — never a
74
+ fixed well-known host — only when DNS isn't already configured. The
75
+ returned DNS record is handed to the configured `dns` provider by
76
+ `holocron repo setup`.
77
+
78
+ Declare the production domain in the Vercel provider options. On `holocron repo setup`,
79
+ the domain is attached to the project and any Vercel verification CNAME is
80
+ upserted through the configured DNS provider:
81
+
82
+ ```ts
83
+ providers: {
84
+ deployment: ["vercel", { teamId: "team_…", domain: "sentinel.example.com" }],
85
+ dns: "cloudflare",
86
+ }
87
+ ```
74
88
 
75
89
  Out of scope for alpha.0 (file a follow-up if needed):
76
90
 
package/dist/index.d.mts CHANGED
@@ -1,17 +1,19 @@
1
- import { AuthError, DeployFunctionConfig, DeployFunctionResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, ResolveTokenInput } from "@theholocron/cli";
1
+ import { AuthError, DeployFunctionConfig, DeployFunctionResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, DnsRecordRequest, PluginToken, ResolveTokenInput, VerifyTokenFailure, VerifyTokenResult, VerifyTokenResult as VerifyTokenResult$1, VerifyTokenSuccess } from "@theholocron/cli";
2
2
  import { VercelClient, VercelClient as VercelClient$1, VercelClientOptions, createVercelClient } from "@theholocron/vercel-client";
3
- //#region src/auth.d.ts
4
- declare const resolveToken: (input?: ResolveTokenInput) => string;
5
- //#endregion
6
- //#region src/capabilities/deployment.d.ts
3
+ export declare const resolveToken: import("@theholocron/cli").TokenResolver;
4
+ /** Every token this plugin takes, for `holocron doctor tokens`. */
5
+ export declare const tokens: PluginToken[];
7
6
  interface DeploymentOptions {
8
7
  /** Optional framework hint passed to project creates. Defaults to "nextjs". */
9
8
  defaultFramework?: string;
9
+ /** Custom domain that `holocron repo setup` should attach to this project. */
10
+ domain?: string;
10
11
  }
11
- declare class VercelDeployment implements Deployment {
12
+ export declare class VercelDeployment implements Deployment {
12
13
  private readonly client;
13
14
  readonly key: "deployment";
14
15
  readonly providerName = "vercel";
16
+ readonly domain?: string;
15
17
  private readonly defaultFramework;
16
18
  constructor(client: () => VercelClient$1, opts?: DeploymentOptions);
17
19
  listProjects(): Promise<DeploymentProject[]>;
@@ -32,63 +34,55 @@ declare class VercelDeployment implements Deployment {
32
34
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
33
35
  deployFunction(projectId: string, config: DeployFunctionConfig): Promise<DeployFunctionResult>;
34
36
  /**
35
- * Idempotent — no-op when `hostname` is already on the project,
36
- * matching `Deployment.ensureCustomDomain`'s contract (never break
37
- * on re-runs, per CLAUDE.md's probe-then-act standard). Adding it
38
- * the first time returns `verified: false` for a domain new to this
39
- * Vercel account — Vercel needs a DNS record (usually CNAME, a
40
- * unique per-project target) created before it routes traffic. This
41
- * method only adds the domain; reading the verification challenge
42
- * needed to finish is the caller's job (`client().domains.add()`'s
43
- * own return value carries it) — Sentinel has no `holocron setup`
44
- * orchestration to hand it to, so there's no shared "here's the DNS
45
- * record" surface to route it through the way `Wiki.dnsRecord()`
46
- * does today.
37
+ * Idempotent — matches `Deployment.ensureCustomDomain`'s contract
38
+ * (never break on re-runs, per CLAUDE.md's probe-then-act standard).
39
+ *
40
+ * Ownership (`domains.add`'s `verified`) and DNS routing are separate
41
+ * concerns: once a team already owns the domain's apex (true for any
42
+ * org with more than one project on the same domain), every new
43
+ * subdomain auto-verifies immediately with no `verification`
44
+ * challenge — re-adding an already-attached domain returns 409, not
45
+ * a fresh one. So attachment alone can't tell us whether traffic is
46
+ * actually routed to Vercel yet. `domains.config()` (Vercel's DNS
47
+ * *configuration* check) answers that regardless of ownership state:
48
+ * `misconfigured` + a `recommendedCNAME`, per-project, never a fixed
49
+ * well-known host — always read it from this response, never
50
+ * hardcode it. Returned as a `DnsRecordRequest` — the caller (e.g.
51
+ * `holocron repo setup`'s custom-domain step) hands it straight to
52
+ * `dns.upsertRecord()`, same as `Wiki.dnsRecord()` already does for
53
+ * the wiki's own domain.
47
54
  */
48
- ensureCustomDomain(projectId: string, hostname: string): Promise<void>;
55
+ ensureCustomDomain(projectId: string, hostname: string): Promise<DnsRecordRequest | null>;
49
56
  private getProjectByName;
50
57
  }
51
- //#endregion
52
- //#region src/verify-token.d.ts
53
- /**
54
- * `verifyToken` — plugin-level export used by `holocron auth set` +
55
- * `holocron auth check`. Hits `GET /v2/user` and translates the
56
- * response into the normalized `VerifyTokenResult` shape.
57
- */
58
- interface VerifyTokenSuccess {
59
- ok: true;
60
- subject: string;
61
- }
62
- interface VerifyTokenFailure {
63
- ok: false;
64
- message: string;
65
- }
66
- type VerifyTokenResult = VerifyTokenSuccess | VerifyTokenFailure;
67
58
  interface VerifyTokenOptions {
68
59
  baseUrl?: string;
69
60
  fetch?: typeof fetch;
70
61
  }
71
- declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult>;
72
- //#endregion
73
- //#region src/index.d.ts
74
- interface VercelPluginOptions extends ResolveTokenInput {
62
+ export declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult$1>;
63
+ export interface VercelPluginOptions extends ResolveTokenInput {
75
64
  /** Vercel team id. Set when working with a team-owned project. */
76
65
  teamId?: string;
77
66
  /** Default framework slug for new project creates. Defaults to "nextjs". */
78
67
  defaultFramework?: string;
68
+ /** Custom domain that `holocron repo setup` should attach to the Vercel project. */
69
+ domain?: string;
79
70
  /** Override base URL for tests. */
80
71
  baseUrl?: string;
81
72
  /** Override `fetch` for tests. */
82
73
  fetch?: typeof fetch;
83
74
  }
84
- interface PluginContext {
75
+ export interface PluginContext {
85
76
  options: VercelPluginOptions;
86
77
  client: () => VercelClient;
87
78
  }
88
- declare function createContext(options?: VercelPluginOptions): PluginContext;
89
- declare function deployment(ctx: PluginContext): Deployment;
90
- declare function createPlugin(options?: VercelPluginOptions): {
79
+ export declare function createContext(options?: VercelPluginOptions): PluginContext;
80
+ export declare function deployment(ctx: PluginContext): Deployment;
81
+ export declare function createPlugin(options?: VercelPluginOptions): {
91
82
  name: string;
83
+ probes: {
84
+ connection: import("@theholocron/cli").Probe;
85
+ };
92
86
  capabilities: {
93
87
  deployment: () => Deployment;
94
88
  };
@@ -97,6 +91,5 @@ declare function createPlugin(options?: VercelPluginOptions): {
97
91
  * One-line hint printed by `holocron auth set vercel` when no token
98
92
  * is supplied or the supplied token is rejected.
99
93
  */
100
- declare const AUTH_HINT: string;
101
- //#endregion
102
- export { AUTH_HINT, AuthError, PluginContext, type ResolveTokenInput, type VercelClient, type VercelClientOptions, VercelDeployment, VercelPluginOptions, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, createVercelClient, deployment, resolveToken, verifyToken };
94
+ export declare const AUTH_HINT: string;
95
+ export { AuthError, type ResolveTokenInput, type VercelClient, type VercelClientOptions, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createVercelClient };
package/dist/index.mjs CHANGED
@@ -1,22 +1,26 @@
1
- import { AuthError, ProviderApiError, createResolveToken } from "@theholocron/cli";
1
+ import { AuthError, ProviderApiError, connectionProbe, createResolveToken } from "@theholocron/cli";
2
2
  import { createVercelClient } from "@theholocron/vercel-client";
3
- //#region src/auth.ts
3
+ import { errorMessage } from "@theholocron/misc-utils";
4
+ // src/auth.ts
4
5
  const resolveToken = createResolveToken({
5
6
  envName: "HOLOCRON_VERCEL_TOKEN",
6
7
  vendorEnvName: "VERCEL_TOKEN",
7
8
  keyringService: "vercel",
8
9
  errorMessage: "no Vercel token found. Pass --token <PAT>, set HOLOCRON_VERCEL_TOKEN / VERCEL_TOKEN, or run: holocron auth set vercel <PAT>"
9
10
  });
10
- //#endregion
11
- //#region src/capabilities/deployment.ts
11
+ /** Every token this plugin takes, for `holocron doctor tokens`. */
12
+ const tokens = [resolveToken.token];
13
+ // src/capabilities/deployment.ts
12
14
  var VercelDeployment = class {
13
15
  client;
14
16
  key = "deployment";
15
17
  providerName = "vercel";
18
+ domain;
16
19
  defaultFramework;
17
20
  constructor(client, opts = {}) {
18
21
  this.client = client;
19
22
  this.defaultFramework = opts.defaultFramework ?? "nextjs";
23
+ this.domain = opts.domain;
20
24
  }
21
25
  async listProjects() {
22
26
  const { projects } = await this.client().projects.list();
@@ -76,23 +80,47 @@ var VercelDeployment = class {
76
80
  };
77
81
  }
78
82
  /**
79
- * Idempotent — no-op when `hostname` is already on the project,
80
- * matching `Deployment.ensureCustomDomain`'s contract (never break
81
- * on re-runs, per CLAUDE.md's probe-then-act standard). Adding it
82
- * the first time returns `verified: false` for a domain new to this
83
- * Vercel account — Vercel needs a DNS record (usually CNAME, a
84
- * unique per-project target) created before it routes traffic. This
85
- * method only adds the domain; reading the verification challenge
86
- * needed to finish is the caller's job (`client().domains.add()`'s
87
- * own return value carries it) — Sentinel has no `holocron setup`
88
- * orchestration to hand it to, so there's no shared "here's the DNS
89
- * record" surface to route it through the way `Wiki.dnsRecord()`
90
- * does today.
83
+ * Idempotent — matches `Deployment.ensureCustomDomain`'s contract
84
+ * (never break on re-runs, per CLAUDE.md's probe-then-act standard).
85
+ *
86
+ * Ownership (`domains.add`'s `verified`) and DNS routing are separate
87
+ * concerns: once a team already owns the domain's apex (true for any
88
+ * org with more than one project on the same domain), every new
89
+ * subdomain auto-verifies immediately with no `verification`
90
+ * challenge — re-adding an already-attached domain returns 409, not
91
+ * a fresh one. So attachment alone can't tell us whether traffic is
92
+ * actually routed to Vercel yet. `domains.config()` (Vercel's DNS
93
+ * *configuration* check) answers that regardless of ownership state:
94
+ * `misconfigured` + a `recommendedCNAME`, per-project, never a fixed
95
+ * well-known host — always read it from this response, never
96
+ * hardcode it. Returned as a `DnsRecordRequest` — the caller (e.g.
97
+ * `holocron repo setup`'s custom-domain step) hands it straight to
98
+ * `dns.upsertRecord()`, same as `Wiki.dnsRecord()` already does for
99
+ * the wiki's own domain.
91
100
  */
92
101
  async ensureCustomDomain(projectId, hostname) {
93
102
  const { domains: existing } = await this.client().domains.list(projectId);
94
- if (existing.some((d) => d.name === hostname)) return;
95
- await this.client().domains.add(projectId, hostname);
103
+ let apexName = existing.find((d) => d.name === hostname)?.apexName;
104
+ if (!apexName) try {
105
+ apexName = (await this.client().domains.add(projectId, hostname)).apexName;
106
+ } catch (err) {
107
+ if (!(err instanceof ProviderApiError && err.status === 409)) throw err;
108
+ const { domains: refreshed } = await this.client().domains.list(projectId);
109
+ apexName = refreshed.find((d) => d.name === hostname)?.apexName;
110
+ }
111
+ if (!apexName) return null;
112
+ const config = await this.client().domains.config(hostname, projectId);
113
+ if (!config.misconfigured) return null;
114
+ const cname = config.recommendedCNAME[0]?.value;
115
+ if (!cname) return null;
116
+ return {
117
+ zone: apexName,
118
+ record: {
119
+ type: "CNAME",
120
+ name: hostname,
121
+ content: cname
122
+ }
123
+ };
96
124
  }
97
125
  async getProjectByName(name) {
98
126
  try {
@@ -121,6 +149,7 @@ function mapDeployment(raw, branch) {
121
149
  status: normalizeState(raw.readyState)
122
150
  };
123
151
  if (raw.target) record.target = raw.target;
152
+ if (raw.errorMessage) record.errorMessage = raw.errorMessage;
124
153
  return record;
125
154
  }
126
155
  function normalizeState(s) {
@@ -133,13 +162,7 @@ function normalizeState(s) {
133
162
  case "CANCELED": return "cancelled";
134
163
  }
135
164
  }
136
- //#endregion
137
- //#region src/verify-token.ts
138
- /**
139
- * `verifyToken` — plugin-level export used by `holocron auth set` +
140
- * `holocron auth check`. Hits `GET /v2/user` and translates the
141
- * response into the normalized `VerifyTokenResult` shape.
142
- */
165
+ // src/verify-token.ts
143
166
  async function verifyToken(token, opts = {}) {
144
167
  const client = createVercelClient({
145
168
  token,
@@ -155,12 +178,11 @@ async function verifyToken(token, opts = {}) {
155
178
  } catch (err) {
156
179
  return {
157
180
  ok: false,
158
- message: err instanceof Error ? err.message : String(err)
181
+ message: errorMessage(err)
159
182
  };
160
183
  }
161
184
  }
162
- //#endregion
163
- //#region src/index.ts
185
+ // src/index.ts
164
186
  function createContext(options = {}) {
165
187
  let client;
166
188
  return {
@@ -176,12 +198,23 @@ function createContext(options = {}) {
176
198
  function deployment(ctx) {
177
199
  const opts = {};
178
200
  if (ctx.options.defaultFramework !== void 0) opts.defaultFramework = ctx.options.defaultFramework;
201
+ if (ctx.options.domain !== void 0) opts.domain = ctx.options.domain;
179
202
  return new VercelDeployment(ctx.client, opts);
180
203
  }
181
204
  function createPlugin(options = {}) {
182
205
  const ctx = createContext(options);
183
206
  return {
184
207
  name: "@theholocron/holocron-plugin-vercel",
208
+ probes: { connection: connectionProbe({
209
+ provider: "Vercel",
210
+ url: options.baseUrl ?? "https://api.vercel.com",
211
+ fetch: options.fetch,
212
+ token: () => resolveToken(options),
213
+ verify: (token) => verifyToken(token, {
214
+ baseUrl: options.baseUrl,
215
+ fetch: options.fetch
216
+ })
217
+ }) },
185
218
  capabilities: { deployment: () => deployment(ctx) }
186
219
  };
187
220
  }
@@ -190,5 +223,4 @@ function createPlugin(options = {}) {
190
223
  * is supplied or the supplied token is rejected.
191
224
  */
192
225
  const AUTH_HINT = "generate a Vercel Personal Access Token at https://vercel.com/account/tokens (scope: Full Account for team ops), then run: holocron auth set vercel <PAT>";
193
- //#endregion
194
- export { AUTH_HINT, AuthError, VercelDeployment, createContext, createPlugin, createVercelClient, deployment, resolveToken, verifyToken };
226
+ export { AUTH_HINT, AuthError, VercelDeployment, createContext, createPlugin, createVercelClient, deployment, resolveToken, tokens, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-vercel",
3
- "version": "5.0.0-alpha.27",
3
+ "version": "5.0.0-alpha.271",
4
4
  "description": "Holocron plugin for Vercel. Implements the deployment capability against the Vercel REST API.",
5
5
  "keywords": [
6
6
  "deployment",
@@ -8,12 +8,12 @@
8
8
  "plugin",
9
9
  "vercel"
10
10
  ],
11
- "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-vercel#readme",
11
+ "homepage": "https://docs.theholocron.dev/holocron/plugins/vercel/",
12
12
  "bugs": "https://github.com/theholocron/holocron/issues",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git+https://github.com/theholocron/holocron.git",
16
- "directory": "packages/holocron-plugin-vercel"
16
+ "directory": "plugins/holocron-plugin-vercel"
17
17
  },
18
18
  "license": "MIT",
19
19
  "author": "Newton Koumantzelis",
@@ -30,27 +30,29 @@
30
30
  "dist"
31
31
  ],
32
32
  "devDependencies": {
33
- "@theholocron/eslint-config": "^8.0.0",
34
- "@theholocron/http-client": "^1.23.0",
35
- "@theholocron/tsconfig": "^8.0.0",
36
- "@theholocron/tsdown-config": "^8.0.0",
37
- "@theholocron/vercel-client": "^1.23.0",
38
- "@theholocron/vitest-config": "^8.0.0",
39
33
  "@types/node": "^26",
40
- "@vitest/coverage-v8": "^4.1.11",
34
+ "@vitest/coverage-v8": "^5.0.5",
41
35
  "@vitest/eslint-plugin": "^1.6.27",
42
36
  "eslint": "^10.8.1",
43
37
  "eslint-plugin-n": "^18.3.0",
44
38
  "globals": "^17.11.0",
45
- "tsdown": "^0.22.14",
46
- "tsx": "4.23.12",
39
+ "tsdown": "^0.23.3",
40
+ "tsx": "4.23.15",
47
41
  "typescript": "^5.9.3",
48
- "vitest": "^4.1.11",
49
- "@theholocron/cli": "5.0.0-alpha.27"
42
+ "vitest": "^5.0.5",
43
+ "@theholocron/eslint-config": "^8.9.0-alpha.3",
44
+ "@theholocron/cli": "5.0.0-alpha.271",
45
+ "@theholocron/http-client": "^1.58.0-alpha.2",
46
+ "@theholocron/misc-utils": "^1.16.1-alpha.1",
47
+ "@theholocron/tsdown-config": "^8.9.0-alpha.3",
48
+ "@theholocron/tsconfig": "^8.9.0-alpha.3",
49
+ "@theholocron/vercel-client": "^1.58.0-alpha.2",
50
+ "@theholocron/vitest-config": "^8.9.0-alpha.3"
50
51
  },
51
52
  "peerDependencies": {
52
- "@theholocron/vercel-client": "^1.23.0",
53
- "@theholocron/cli": "5.0.0-alpha.27"
53
+ "@theholocron/misc-utils": "^1.16.0",
54
+ "@theholocron/vercel-client": "^1.43.1",
55
+ "@theholocron/cli": "5.0.0-alpha.271"
54
56
  },
55
57
  "peerDependenciesMeta": {
56
58
  "@theholocron/vercel-client": {}
@@ -63,10 +65,10 @@
63
65
  },
64
66
  "scripts": {
65
67
  "delivery.build": "tsdown",
66
- "sourceQuality.staticAnalysis": "holocron run sourceQuality.staticAnalysis --",
68
+ "sourceQuality.staticAnalysis": "eslint .",
67
69
  "test:watch": "vitest",
68
70
  "validate": "tsx scripts/validate.mjs",
69
- "verification.typeSafety": "holocron run verification.typeSafety --",
70
- "verification.unitTests": "holocron run verification.unitTests --"
71
+ "verification.typeSafety": "tsc --noEmit",
72
+ "verification.unitTests": "vitest run"
71
73
  }
72
74
  }