@theholocron/holocron-plugin-vercel 5.0.0-alpha.26 → 5.0.0-alpha.261

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,9 +64,30 @@ 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 → 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
+ ```
67
88
 
68
89
  Out of scope for alpha.0 (file a follow-up if needed):
69
90
 
70
- - Domain management (`addDomain` / `removeDomain`)
91
+ - Domain removal (`removeDomain`)
71
92
  - Deletion (`deleteProject`)
72
93
  - Marketplace integrations (e.g. `vercel install neon` for vault-managed databases)
package/dist/index.d.mts CHANGED
@@ -1,17 +1,17 @@
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, 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: (input?: ResolveTokenInput) => string;
7
4
  interface DeploymentOptions {
8
5
  /** Optional framework hint passed to project creates. Defaults to "nextjs". */
9
6
  defaultFramework?: string;
7
+ /** Custom domain that `holocron repo setup` should attach to this project. */
8
+ domain?: string;
10
9
  }
11
- declare class VercelDeployment implements Deployment {
10
+ export declare class VercelDeployment implements Deployment {
12
11
  private readonly client;
13
12
  readonly key: "deployment";
14
13
  readonly providerName = "vercel";
14
+ readonly domain?: string;
15
15
  private readonly defaultFramework;
16
16
  constructor(client: () => VercelClient$1, opts?: DeploymentOptions);
17
17
  listProjects(): Promise<DeploymentProject[]>;
@@ -31,49 +31,56 @@ declare class VercelDeployment implements Deployment {
31
31
  }): Promise<DeploymentRecord>;
32
32
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
33
33
  deployFunction(projectId: string, config: DeployFunctionConfig): Promise<DeployFunctionResult>;
34
+ /**
35
+ * Idempotent — matches `Deployment.ensureCustomDomain`'s contract
36
+ * (never break on re-runs, per CLAUDE.md's probe-then-act standard).
37
+ *
38
+ * Ownership (`domains.add`'s `verified`) and DNS routing are separate
39
+ * concerns: once a team already owns the domain's apex (true for any
40
+ * org with more than one project on the same domain), every new
41
+ * subdomain auto-verifies immediately with no `verification`
42
+ * challenge — re-adding an already-attached domain returns 409, not
43
+ * a fresh one. So attachment alone can't tell us whether traffic is
44
+ * actually routed to Vercel yet. `domains.config()` (Vercel's DNS
45
+ * *configuration* check) answers that regardless of ownership state:
46
+ * `misconfigured` + a `recommendedCNAME`, per-project, never a fixed
47
+ * well-known host — always read it from this response, never
48
+ * hardcode it. Returned as a `DnsRecordRequest` — the caller (e.g.
49
+ * `holocron repo setup`'s custom-domain step) hands it straight to
50
+ * `dns.upsertRecord()`, same as `Wiki.dnsRecord()` already does for
51
+ * the wiki's own domain.
52
+ */
53
+ ensureCustomDomain(projectId: string, hostname: string): Promise<DnsRecordRequest | null>;
34
54
  private getProjectByName;
35
55
  }
36
- //#endregion
37
- //#region src/verify-token.d.ts
38
- /**
39
- * `verifyToken` — plugin-level export used by `holocron auth set` +
40
- * `holocron auth check`. Hits `GET /v2/user` and translates the
41
- * response into the normalized `VerifyTokenResult` shape.
42
- */
43
- interface VerifyTokenSuccess {
44
- ok: true;
45
- subject: string;
46
- }
47
- interface VerifyTokenFailure {
48
- ok: false;
49
- message: string;
50
- }
51
- type VerifyTokenResult = VerifyTokenSuccess | VerifyTokenFailure;
52
56
  interface VerifyTokenOptions {
53
57
  baseUrl?: string;
54
58
  fetch?: typeof fetch;
55
59
  }
56
- declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult>;
57
- //#endregion
58
- //#region src/index.d.ts
59
- interface VercelPluginOptions extends ResolveTokenInput {
60
+ export declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<VerifyTokenResult$1>;
61
+ export interface VercelPluginOptions extends ResolveTokenInput {
60
62
  /** Vercel team id. Set when working with a team-owned project. */
61
63
  teamId?: string;
62
64
  /** Default framework slug for new project creates. Defaults to "nextjs". */
63
65
  defaultFramework?: string;
66
+ /** Custom domain that `holocron repo setup` should attach to the Vercel project. */
67
+ domain?: string;
64
68
  /** Override base URL for tests. */
65
69
  baseUrl?: string;
66
70
  /** Override `fetch` for tests. */
67
71
  fetch?: typeof fetch;
68
72
  }
69
- interface PluginContext {
73
+ export interface PluginContext {
70
74
  options: VercelPluginOptions;
71
75
  client: () => VercelClient;
72
76
  }
73
- declare function createContext(options?: VercelPluginOptions): PluginContext;
74
- declare function deployment(ctx: PluginContext): Deployment;
75
- declare function createPlugin(options?: VercelPluginOptions): {
77
+ export declare function createContext(options?: VercelPluginOptions): PluginContext;
78
+ export declare function deployment(ctx: PluginContext): Deployment;
79
+ export declare function createPlugin(options?: VercelPluginOptions): {
76
80
  name: string;
81
+ probes: {
82
+ connection: import("@theholocron/cli").Probe;
83
+ };
77
84
  capabilities: {
78
85
  deployment: () => Deployment;
79
86
  };
@@ -82,6 +89,5 @@ declare function createPlugin(options?: VercelPluginOptions): {
82
89
  * One-line hint printed by `holocron auth set vercel` when no token
83
90
  * is supplied or the supplied token is rejected.
84
91
  */
85
- declare const AUTH_HINT: string;
86
- //#endregion
87
- 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 };
92
+ export declare const AUTH_HINT: string;
93
+ export { AuthError, type ResolveTokenInput, type VercelClient, type VercelClientOptions, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createVercelClient };
package/dist/index.mjs CHANGED
@@ -1,22 +1,24 @@
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
+ // src/capabilities/deployment.ts
12
12
  var VercelDeployment = class {
13
13
  client;
14
14
  key = "deployment";
15
15
  providerName = "vercel";
16
+ domain;
16
17
  defaultFramework;
17
18
  constructor(client, opts = {}) {
18
19
  this.client = client;
19
20
  this.defaultFramework = opts.defaultFramework ?? "nextjs";
21
+ this.domain = opts.domain;
20
22
  }
21
23
  async listProjects() {
22
24
  const { projects } = await this.client().projects.list();
@@ -75,6 +77,49 @@ var VercelDeployment = class {
75
77
  url: raw.url
76
78
  };
77
79
  }
80
+ /**
81
+ * Idempotent — matches `Deployment.ensureCustomDomain`'s contract
82
+ * (never break on re-runs, per CLAUDE.md's probe-then-act standard).
83
+ *
84
+ * Ownership (`domains.add`'s `verified`) and DNS routing are separate
85
+ * concerns: once a team already owns the domain's apex (true for any
86
+ * org with more than one project on the same domain), every new
87
+ * subdomain auto-verifies immediately with no `verification`
88
+ * challenge — re-adding an already-attached domain returns 409, not
89
+ * a fresh one. So attachment alone can't tell us whether traffic is
90
+ * actually routed to Vercel yet. `domains.config()` (Vercel's DNS
91
+ * *configuration* check) answers that regardless of ownership state:
92
+ * `misconfigured` + a `recommendedCNAME`, per-project, never a fixed
93
+ * well-known host — always read it from this response, never
94
+ * hardcode it. Returned as a `DnsRecordRequest` — the caller (e.g.
95
+ * `holocron repo setup`'s custom-domain step) hands it straight to
96
+ * `dns.upsertRecord()`, same as `Wiki.dnsRecord()` already does for
97
+ * the wiki's own domain.
98
+ */
99
+ async ensureCustomDomain(projectId, hostname) {
100
+ const { domains: existing } = await this.client().domains.list(projectId);
101
+ let apexName = existing.find((d) => d.name === hostname)?.apexName;
102
+ if (!apexName) try {
103
+ apexName = (await this.client().domains.add(projectId, hostname)).apexName;
104
+ } catch (err) {
105
+ if (!(err instanceof ProviderApiError && err.status === 409)) throw err;
106
+ const { domains: refreshed } = await this.client().domains.list(projectId);
107
+ apexName = refreshed.find((d) => d.name === hostname)?.apexName;
108
+ }
109
+ if (!apexName) return null;
110
+ const config = await this.client().domains.config(hostname, projectId);
111
+ if (!config.misconfigured) return null;
112
+ const cname = config.recommendedCNAME[0]?.value;
113
+ if (!cname) return null;
114
+ return {
115
+ zone: apexName,
116
+ record: {
117
+ type: "CNAME",
118
+ name: hostname,
119
+ content: cname
120
+ }
121
+ };
122
+ }
78
123
  async getProjectByName(name) {
79
124
  try {
80
125
  return mapProject(await this.client().projects.get(name));
@@ -102,6 +147,7 @@ function mapDeployment(raw, branch) {
102
147
  status: normalizeState(raw.readyState)
103
148
  };
104
149
  if (raw.target) record.target = raw.target;
150
+ if (raw.errorMessage) record.errorMessage = raw.errorMessage;
105
151
  return record;
106
152
  }
107
153
  function normalizeState(s) {
@@ -114,13 +160,7 @@ function normalizeState(s) {
114
160
  case "CANCELED": return "cancelled";
115
161
  }
116
162
  }
117
- //#endregion
118
- //#region src/verify-token.ts
119
- /**
120
- * `verifyToken` — plugin-level export used by `holocron auth set` +
121
- * `holocron auth check`. Hits `GET /v2/user` and translates the
122
- * response into the normalized `VerifyTokenResult` shape.
123
- */
163
+ // src/verify-token.ts
124
164
  async function verifyToken(token, opts = {}) {
125
165
  const client = createVercelClient({
126
166
  token,
@@ -136,12 +176,11 @@ async function verifyToken(token, opts = {}) {
136
176
  } catch (err) {
137
177
  return {
138
178
  ok: false,
139
- message: err instanceof Error ? err.message : String(err)
179
+ message: errorMessage(err)
140
180
  };
141
181
  }
142
182
  }
143
- //#endregion
144
- //#region src/index.ts
183
+ // src/index.ts
145
184
  function createContext(options = {}) {
146
185
  let client;
147
186
  return {
@@ -157,12 +196,23 @@ function createContext(options = {}) {
157
196
  function deployment(ctx) {
158
197
  const opts = {};
159
198
  if (ctx.options.defaultFramework !== void 0) opts.defaultFramework = ctx.options.defaultFramework;
199
+ if (ctx.options.domain !== void 0) opts.domain = ctx.options.domain;
160
200
  return new VercelDeployment(ctx.client, opts);
161
201
  }
162
202
  function createPlugin(options = {}) {
163
203
  const ctx = createContext(options);
164
204
  return {
165
205
  name: "@theholocron/holocron-plugin-vercel",
206
+ probes: { connection: connectionProbe({
207
+ provider: "Vercel",
208
+ url: options.baseUrl ?? "https://api.vercel.com",
209
+ fetch: options.fetch,
210
+ token: () => resolveToken(options),
211
+ verify: (token) => verifyToken(token, {
212
+ baseUrl: options.baseUrl,
213
+ fetch: options.fetch
214
+ })
215
+ }) },
166
216
  capabilities: { deployment: () => deployment(ctx) }
167
217
  };
168
218
  }
@@ -171,5 +221,4 @@ function createPlugin(options = {}) {
171
221
  * is supplied or the supplied token is rejected.
172
222
  */
173
223
  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>";
174
- //#endregion
175
224
  export { AUTH_HINT, AuthError, VercelDeployment, createContext, createPlugin, createVercelClient, deployment, resolveToken, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-vercel",
3
- "version": "5.0.0-alpha.26",
3
+ "version": "5.0.0-alpha.261",
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://github.com/theholocron/holocron/tree/main/plugins/holocron-plugin-vercel#readme",
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.22.0",
35
- "@theholocron/tsconfig": "^8.0.0",
36
- "@theholocron/tsdown-config": "^8.0.0",
37
- "@theholocron/vercel-client": "^1.22.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.26"
42
+ "vitest": "^5.0.5",
43
+ "@theholocron/cli": "5.0.0-alpha.261",
44
+ "@theholocron/eslint-config": "^8.9.0-alpha.3",
45
+ "@theholocron/http-client": "^1.57.1-alpha.2",
46
+ "@theholocron/tsconfig": "^8.9.0-alpha.3",
47
+ "@theholocron/tsdown-config": "^8.9.0-alpha.3",
48
+ "@theholocron/vitest-config": "^8.9.0-alpha.3",
49
+ "@theholocron/vercel-client": "^1.57.1-alpha.2",
50
+ "@theholocron/misc-utils": "^1.16.1-alpha.1"
50
51
  },
51
52
  "peerDependencies": {
52
- "@theholocron/vercel-client": "^1.22.0",
53
- "@theholocron/cli": "5.0.0-alpha.26"
53
+ "@theholocron/misc-utils": "^1.16.0",
54
+ "@theholocron/vercel-client": "^1.43.1",
55
+ "@theholocron/cli": "5.0.0-alpha.261"
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
  }