@theholocron/holocron-plugin-vercel 2.0.0-alpha.9 → 2.0.0

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,3 +1,5 @@
1
+ <!-- editorconfig-checker-disable-file -->
2
+
1
3
  # `@theholocron/holocron-plugin-vercel`
2
4
 
3
5
  Vercel plugin for [Holocron](../cli). Implements the `deployment`
@@ -5,8 +7,10 @@ capability against the [Vercel REST API](https://vercel.com/docs/rest-api).
5
7
 
6
8
  ## Install
7
9
 
10
+ <!-- prettier-ignore -->
8
11
  ```bash
9
12
  pnpm add -D @theholocron/holocron-plugin-vercel@alpha
13
+
10
14
  ```
11
15
 
12
16
  ## Auth
@@ -23,17 +27,19 @@ always cover what holocron needs at the API level. Explicit token only.
23
27
 
24
28
  ## Config
25
29
 
30
+ <!-- prettier-ignore -->
26
31
  ```jsonc
27
32
  // holocron.config.json
28
33
  {
29
- "providers": {
30
- "deployment": ["vercel", { "teamId": "team_xxx" }],
31
- },
34
+ "providers": {
35
+ "deployment": ["vercel", { "teamId": "team_xxx" }],
36
+ },
32
37
  }
38
+
33
39
  ```
34
40
 
35
41
  - `teamId` (optional) — Vercel team id. When set, all requests are
36
- scoped to that team. Leave unset for personal-account projects.
42
+ scoped to that team. Leave unset for personal-account projects.
37
43
 
38
44
  ## Status
39
45
 
package/dist/index.d.mts CHANGED
@@ -1,66 +1,8 @@
1
- import { Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger } from "@theholocron/cli";
1
+ import { AuthError, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, ResolveTokenInput } from "@theholocron/cli";
2
+ import { VercelClient, VercelClient as VercelClient$1, createVercelClient } from "@theholocron/vercel-client";
2
3
 
3
4
  //#region src/auth.d.ts
4
- /**
5
- * Token resolution for the Vercel plugin.
6
- *
7
- * Resolution order (matches the standard 4-step precedence set by
8
- * `.notes/tech-auth-bootstrap.spec.md`):
9
- * 1. explicit `cliToken` argument (from `--token` flag)
10
- * 2. HOLOCRON_VERCEL_TOKEN env var (preferred — explicit intent)
11
- * 3. VERCEL_TOKEN env var (the default the Vercel CLI also reads)
12
- * 4. keyring (com.theholocron.cli / "vercel")
13
- * 5. AuthError naming all four options + the bootstrap hint
14
- *
15
- * No `vercel auth` fallback — Vercel CLI auth is per-account-scoped
16
- * and the resulting tokens don't always cover team operations.
17
- */
18
- declare class AuthError extends Error {
19
- name: string;
20
- }
21
- interface ResolveTokenInput {
22
- /** From `--token` CLI flag. */
23
- cliToken?: string;
24
- /** Env vars; passed in for testability. Defaults to `process.env`. */
25
- env?: NodeJS.ProcessEnv;
26
- /** Keyring lookup fn; passed in for testability. Defaults to `getToken(provider)`. */
27
- keyring?: (provider: string) => string | null;
28
- }
29
- declare function resolveToken(input?: ResolveTokenInput): string;
30
- //#endregion
31
- //#region src/rest.d.ts
32
- /**
33
- * Thin REST wrapper around api.vercel.com.
34
- *
35
- * Differences from the GitHub REST client:
36
- * - Vercel uses simple `Bearer <token>` (no api-version header)
37
- * - Team scoping is a query-string param (`teamId=...`), not a path
38
- * prefix; the client appends it to every URL when configured
39
- * - 204 responses are honored the same way; transport failures get
40
- * wrapped in `ProviderApiError` with `status: 0` for clear hint
41
- * output
42
- */
43
- interface RestClientOptions {
44
- token: string;
45
- /** Vercel team id. When set, scoped to that team for every request. */
46
- teamId?: string;
47
- fetch?: typeof fetch;
48
- baseUrl?: string;
49
- }
50
- interface RequestOptions {
51
- method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
52
- body?: unknown;
53
- /** Additional query-string params. */
54
- query?: Record<string, string>;
55
- }
56
- declare class VercelRestClient {
57
- private readonly token;
58
- private readonly teamId?;
59
- private readonly fetchImpl;
60
- readonly baseUrl: string;
61
- constructor(opts: RestClientOptions);
62
- request<T>(path: string, opts?: RequestOptions): Promise<T>;
63
- }
5
+ declare const resolveToken: (input?: import("@theholocron/http-client").ResolveTokenInput) => string;
64
6
  //#endregion
65
7
  //#region src/capabilities/deployment.d.ts
66
8
  interface DeploymentOptions {
@@ -68,11 +10,11 @@ interface DeploymentOptions {
68
10
  defaultFramework?: string;
69
11
  }
70
12
  declare class VercelDeployment implements Deployment {
71
- private readonly rest;
13
+ private readonly client;
72
14
  readonly key: "deployment";
73
15
  readonly providerName = "vercel";
74
16
  private readonly defaultFramework;
75
- constructor(rest: VercelRestClient, opts?: DeploymentOptions);
17
+ constructor(client: VercelClient$1, opts?: DeploymentOptions);
76
18
  listProjects(): Promise<DeploymentProject[]>;
77
19
  ensureProject(input: {
78
20
  name: string;
@@ -89,7 +31,6 @@ declare class VercelDeployment implements Deployment {
89
31
  target?: DeploymentTrigger;
90
32
  }): Promise<DeploymentRecord>;
91
33
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
92
- /** GET project by name with 404→null soft-skip. */
93
34
  private getProjectByName;
94
35
  }
95
36
  //#endregion
@@ -127,7 +68,7 @@ interface VercelPluginOptions extends ResolveTokenInput {
127
68
  }
128
69
  interface PluginContext {
129
70
  options: VercelPluginOptions;
130
- rest: VercelRestClient;
71
+ client: VercelClient;
131
72
  }
132
73
  declare function createContext(options?: VercelPluginOptions): PluginContext;
133
74
  declare function deployment(ctx: PluginContext): Deployment;
@@ -139,10 +80,8 @@ declare function createPlugin(options?: VercelPluginOptions): {
139
80
  };
140
81
  /**
141
82
  * One-line hint printed by `holocron auth set vercel` when no token
142
- * is supplied or the supplied token is rejected. Points operators at
143
- * https://vercel.com/account/tokens where PATs are minted with
144
- * "Full Account" scope for team-level ops.
83
+ * is supplied or the supplied token is rejected.
145
84
  */
146
85
  declare const AUTH_HINT: string;
147
86
  //#endregion
148
- export { AUTH_HINT, AuthError, PluginContext, ResolveTokenInput, VercelDeployment, VercelPluginOptions, VercelRestClient, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, deployment, resolveToken, verifyToken };
87
+ export { AUTH_HINT, AuthError, PluginContext, type ResolveTokenInput, type VercelClient, VercelDeployment, VercelPluginOptions, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, createVercelClient, deployment, resolveToken, verifyToken };
package/dist/index.mjs CHANGED
@@ -1,29 +1,12 @@
1
- import { ProviderApiError, getToken } from "@theholocron/cli";
1
+ import { AuthError, ProviderApiError, createResolveToken } from "@theholocron/cli";
2
+ import { createVercelClient } from "@theholocron/vercel-client";
2
3
  //#region src/auth.ts
3
- /**
4
- * Token resolution for the Vercel plugin.
5
- *
6
- * Resolution order (matches the standard 4-step precedence set by
7
- * `.notes/tech-auth-bootstrap.spec.md`):
8
- * 1. explicit `cliToken` argument (from `--token` flag)
9
- * 2. HOLOCRON_VERCEL_TOKEN env var (preferred — explicit intent)
10
- * 3. VERCEL_TOKEN env var (the default the Vercel CLI also reads)
11
- * 4. keyring (com.theholocron.cli / "vercel")
12
- * 5. AuthError naming all four options + the bootstrap hint
13
- *
14
- * No `vercel auth` fallback — Vercel CLI auth is per-account-scoped
15
- * and the resulting tokens don't always cover team operations.
16
- */
17
- var AuthError = class extends Error {
18
- name = "AuthError";
19
- };
20
- function resolveToken(input = {}) {
21
- const env = input.env ?? process.env;
22
- const keyring = input.keyring ?? getToken;
23
- const token = input.cliToken || env["HOLOCRON_VERCEL_TOKEN"] || env["VERCEL_TOKEN"] || keyring("vercel");
24
- if (!token) throw new AuthError("no Vercel token found. Pass --token <PAT>, set HOLOCRON_VERCEL_TOKEN / VERCEL_TOKEN, or run: holocron auth set vercel <PAT>");
25
- return token;
26
- }
4
+ const resolveToken = createResolveToken({
5
+ envName: "HOLOCRON_VERCEL_TOKEN",
6
+ vendorEnvName: "VERCEL_TOKEN",
7
+ keyringService: "vercel",
8
+ errorMessage: "no Vercel token found. Pass --token <PAT>, set HOLOCRON_VERCEL_TOKEN / VERCEL_TOKEN, or run: holocron auth set vercel <PAT>"
9
+ });
27
10
  //#endregion
28
11
  //#region src/capabilities/deployment.ts
29
12
  /**
@@ -45,83 +28,59 @@ function resolveToken(input = {}) {
45
28
  * sealed-box / libsodium step on the client.
46
29
  */
47
30
  var VercelDeployment = class {
48
- rest;
31
+ client;
49
32
  key = "deployment";
50
33
  providerName = "vercel";
51
34
  defaultFramework;
52
- constructor(rest, opts = {}) {
53
- this.rest = rest;
35
+ constructor(client, opts = {}) {
36
+ this.client = client;
54
37
  this.defaultFramework = opts.defaultFramework ?? "nextjs";
55
38
  }
56
39
  async listProjects() {
57
- return (await this.rest.request("/v10/projects")).projects.map(mapProject);
40
+ const { projects } = await this.client.projects.list();
41
+ return projects.map(mapProject);
58
42
  }
59
43
  async ensureProject(input) {
60
44
  const existing = await this.getProjectByName(input.name);
61
45
  if (existing) return existing;
62
- const body = {
46
+ return mapProject(await this.client.projects.create({
63
47
  name: input.name,
64
- framework: input.framework ?? this.defaultFramework
65
- };
66
- if (input.repo) body.gitRepository = {
67
- type: "github",
68
- repo: input.repo
69
- };
70
- if (input.rootDirectory) body.rootDirectory = input.rootDirectory;
71
- return mapProject(await this.rest.request("/v11/projects", {
72
- method: "POST",
73
- body
48
+ framework: input.framework ?? this.defaultFramework,
49
+ repo: input.repo,
50
+ rootDirectory: input.rootDirectory
74
51
  }));
75
52
  }
76
53
  async updateProjectSettings(projectId, settings) {
77
- const body = {};
78
- if (settings.previewDeploymentsDisabled !== void 0) body.previewDeploymentsDisabled = settings.previewDeploymentsDisabled;
79
- if (settings.gitProviderCreateDeployments !== void 0) body.gitProviderOptions = { createDeployments: settings.gitProviderCreateDeployments };
80
- return mapProject(await this.rest.request(`/v9/projects/${encodeURIComponent(projectId)}`, {
81
- method: "PATCH",
82
- body
54
+ return mapProject(await this.client.projects.update(projectId, {
55
+ previewDeploymentsDisabled: settings.previewDeploymentsDisabled,
56
+ gitProviderOptions: settings.gitProviderCreateDeployments !== void 0 ? { createDeployments: settings.gitProviderCreateDeployments } : void 0
83
57
  }));
84
58
  }
85
59
  async listEnvVars(projectId, target) {
86
- return (await this.rest.request(`/v9/projects/${encodeURIComponent(projectId)}/env`)).envs.filter((e) => e.target.includes(target)).map((e) => e.key);
60
+ const { envs } = await this.client.env.list(projectId);
61
+ return envs.filter((e) => e.target.includes(target)).map((e) => e.key);
87
62
  }
88
63
  async setEnvVar(projectId, target, name, value) {
89
- await this.rest.request(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
90
- method: "POST",
91
- query: { upsert: "true" },
92
- body: {
93
- key: name,
94
- value,
95
- target: [target],
96
- type: "encrypted"
97
- }
98
- });
64
+ await this.client.env.set(projectId, target, name, value);
99
65
  }
100
66
  async triggerDeployment(input) {
101
- const project = await this.rest.request(`/v10/projects/${encodeURIComponent(input.projectId)}`);
67
+ const project = await this.client.projects.get(input.projectId);
102
68
  const repoId = project.link?.repoId;
103
69
  if (!repoId) throw new ProviderApiError(`Vercel project "${project.name}" has no linked GitHub repo — cannot trigger a deployment. Link the repo first.`, 400, void 0);
104
- return mapDeployment(await this.rest.request("/v13/deployments", {
105
- method: "POST",
106
- body: {
107
- name: project.name,
108
- gitSource: {
109
- type: "github",
110
- ref: input.branch,
111
- repoId
112
- },
113
- ...input.target ? { target: input.target } : {}
114
- }
70
+ return mapDeployment(await this.client.deployments.trigger({
71
+ projectName: project.name,
72
+ branch: input.branch,
73
+ repoId,
74
+ target: input.target
115
75
  }), input.branch);
116
76
  }
117
77
  async getDeployment(deploymentId) {
118
- const raw = await this.rest.request(`/v13/deployments/${encodeURIComponent(deploymentId)}`);
78
+ const raw = await this.client.deployments.get(deploymentId);
119
79
  return mapDeployment(raw, raw.meta?.githubCommitRef ?? null);
120
80
  }
121
- /** GET project by name with 404→null soft-skip. */
122
81
  async getProjectByName(name) {
123
82
  try {
124
- return mapProject(await this.rest.request(`/v10/projects/${encodeURIComponent(name)}`));
83
+ return mapProject(await this.client.projects.get(name));
125
84
  } catch (err) {
126
85
  if (err instanceof ProviderApiError && err.status === 404) return null;
127
86
  throw err;
@@ -159,66 +118,6 @@ function normalizeState(s) {
159
118
  }
160
119
  }
161
120
  //#endregion
162
- //#region src/rest.ts
163
- /**
164
- * Thin REST wrapper around api.vercel.com.
165
- *
166
- * Differences from the GitHub REST client:
167
- * - Vercel uses simple `Bearer <token>` (no api-version header)
168
- * - Team scoping is a query-string param (`teamId=...`), not a path
169
- * prefix; the client appends it to every URL when configured
170
- * - 204 responses are honored the same way; transport failures get
171
- * wrapped in `ProviderApiError` with `status: 0` for clear hint
172
- * output
173
- */
174
- var VercelRestClient = class {
175
- token;
176
- teamId;
177
- fetchImpl;
178
- baseUrl;
179
- constructor(opts) {
180
- this.token = opts.token;
181
- if (opts.teamId !== void 0) this.teamId = opts.teamId;
182
- this.fetchImpl = opts.fetch ?? globalThis.fetch;
183
- let url = opts.baseUrl ?? "https://api.vercel.com";
184
- while (url.endsWith("/")) url = url.slice(0, -1);
185
- this.baseUrl = url;
186
- }
187
- async request(path, opts = {}) {
188
- const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`);
189
- if (this.teamId) url.searchParams.set("teamId", this.teamId);
190
- for (const [k, v] of Object.entries(opts.query ?? {})) url.searchParams.set(k, v);
191
- const fullUrl = url.toString();
192
- const headers = {
193
- authorization: `Bearer ${this.token}`,
194
- accept: "application/json"
195
- };
196
- const init = {
197
- method: opts.method ?? "GET",
198
- headers
199
- };
200
- if (opts.body !== void 0) {
201
- headers["content-type"] = "application/json";
202
- init.body = JSON.stringify(opts.body);
203
- }
204
- let res;
205
- try {
206
- res = await this.fetchImpl(fullUrl, init);
207
- } catch (err) {
208
- const detail = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
209
- throw new ProviderApiError(`Vercel ${init.method} ${path} failed: ${detail}`, 0, void 0);
210
- }
211
- if (!res.ok) {
212
- const body = await res.text().catch(() => "");
213
- throw new ProviderApiError(`Vercel ${init.method} ${path} → ${res.status}`, res.status, body);
214
- }
215
- if (res.status === 204) return void 0;
216
- const text = await res.text();
217
- if (!text) return void 0;
218
- return JSON.parse(text);
219
- }
220
- };
221
- //#endregion
222
121
  //#region src/verify-token.ts
223
122
  /**
224
123
  * `verifyToken` — plugin-level export used by `holocron auth set` +
@@ -226,12 +125,13 @@ var VercelRestClient = class {
226
125
  * response into the normalized `VerifyTokenResult` shape.
227
126
  */
228
127
  async function verifyToken(token, opts = {}) {
229
- const restOpts = { token };
230
- if (opts.baseUrl !== void 0) restOpts.baseUrl = opts.baseUrl;
231
- if (opts.fetch !== void 0) restOpts.fetch = opts.fetch;
232
- const rest = new VercelRestClient(restOpts);
128
+ const client = createVercelClient({
129
+ token,
130
+ baseUrl: opts.baseUrl,
131
+ fetch: opts.fetch
132
+ });
233
133
  try {
234
- const res = await rest.request("/v2/user");
134
+ const res = await client.user.get();
235
135
  return {
236
136
  ok: true,
237
137
  subject: `user @ ${res?.user?.email ?? res?.user?.username ?? res?.user?.name ?? res?.user?.id ?? "unknown"}`
@@ -246,19 +146,20 @@ async function verifyToken(token, opts = {}) {
246
146
  //#endregion
247
147
  //#region src/index.ts
248
148
  function createContext(options = {}) {
249
- const restOpts = { token: resolveToken(options) };
250
- if (options.teamId !== void 0) restOpts.teamId = options.teamId;
251
- if (options.baseUrl !== void 0) restOpts.baseUrl = options.baseUrl;
252
- if (options.fetch !== void 0) restOpts.fetch = options.fetch;
253
149
  return {
254
150
  options,
255
- rest: new VercelRestClient(restOpts)
151
+ client: createVercelClient({
152
+ token: resolveToken(options),
153
+ teamId: options.teamId,
154
+ baseUrl: options.baseUrl,
155
+ fetch: options.fetch
156
+ })
256
157
  };
257
158
  }
258
159
  function deployment(ctx) {
259
160
  const opts = {};
260
161
  if (ctx.options.defaultFramework !== void 0) opts.defaultFramework = ctx.options.defaultFramework;
261
- return new VercelDeployment(ctx.rest, opts);
162
+ return new VercelDeployment(ctx.client, opts);
262
163
  }
263
164
  function createPlugin(options = {}) {
264
165
  const ctx = createContext(options);
@@ -269,10 +170,8 @@ function createPlugin(options = {}) {
269
170
  }
270
171
  /**
271
172
  * One-line hint printed by `holocron auth set vercel` when no token
272
- * is supplied or the supplied token is rejected. Points operators at
273
- * https://vercel.com/account/tokens where PATs are minted with
274
- * "Full Account" scope for team-level ops.
173
+ * is supplied or the supplied token is rejected.
275
174
  */
276
175
  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>";
277
176
  //#endregion
278
- export { AUTH_HINT, AuthError, VercelDeployment, VercelRestClient, createContext, createPlugin, deployment, resolveToken, verifyToken };
177
+ 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": "2.0.0-alpha.9",
3
+ "version": "2.0.0",
4
4
  "description": "Holocron plugin for Vercel. Implements the deployment capability against the Vercel REST API.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-vercel#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",
@@ -21,19 +21,31 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "2.0.0-alpha.9"
24
+ "@theholocron/vercel-client": "^1.1.0",
25
+ "@theholocron/cli": "2.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@theholocron/vercel-client": {
29
+ "optional": false
30
+ }
25
31
  },
26
32
  "devDependencies": {
27
- "@theholocron/tsconfig": "^4.1.0",
28
- "@tsconfig/node-lts": "^24.0.0",
29
- "@vitest/coverage-v8": "^3.2.6",
30
- "eslint": "^9.36.0",
31
- "globals": "^16.5.0",
32
- "typescript": "^5.9.3",
33
- "vitest": "^3.2.6",
33
+ "@theholocron/eslint-config": "^7.3.0",
34
+ "@theholocron/tsconfig": "^7.3.0",
35
+ "@theholocron/tsdown-config": "^7.3.0",
36
+ "@theholocron/vercel-client": "^1.1.0",
37
+ "@theholocron/vitest-config": "^7.3.0",
38
+ "@types/node": "^26",
39
+ "@vitest/coverage-v8": "^4.1.10",
40
+ "@vitest/eslint-plugin": "^1.6.23",
41
+ "eslint": "^10.7.0",
42
+ "eslint-plugin-n": "^18.2.2",
43
+ "globals": "^17.7.0",
34
44
  "tsdown": "^0.22.3",
35
45
  "tsx": "^4.22.4",
36
- "@theholocron/cli": "2.0.0-alpha.9"
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^4.1.10",
48
+ "@theholocron/cli": "2.0.0"
37
49
  },
38
50
  "publishConfig": {
39
51
  "access": "public"