@psg2/env-sync 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +109 -18
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -6906,7 +6906,7 @@ var require_public_api = __commonJS((exports) => {
6906
6906
  });
6907
6907
 
6908
6908
  // src/cli.ts
6909
- import { readFileSync as readFileSync2 } from "fs";
6909
+ import { readFileSync as readFileSync3 } from "fs";
6910
6910
  import { resolve as resolve4 } from "path";
6911
6911
 
6912
6912
  // src/config.ts
@@ -7280,8 +7280,84 @@ async function syncGitHub(name, target, vars, _configDir, opts) {
7280
7280
  }
7281
7281
 
7282
7282
  // src/targets/vercel.ts
7283
- import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
7283
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
7284
+ import { homedir } from "os";
7284
7285
  import { resolve as resolve3 } from "path";
7286
+ function getVercelAuthToken() {
7287
+ const candidates = [
7288
+ `${homedir()}/Library/Application Support/com.vercel.cli/auth.json`,
7289
+ `${homedir()}/.local/share/com.vercel.cli/auth.json`,
7290
+ process.env.XDG_CONFIG_HOME ? `${process.env.XDG_CONFIG_HOME}/com.vercel.cli/auth.json` : undefined,
7291
+ process.env.APPDATA ? `${process.env.APPDATA}/com.vercel.cli/auth.json` : undefined
7292
+ ].filter(Boolean);
7293
+ for (const path of candidates) {
7294
+ if (!existsSync3(path))
7295
+ continue;
7296
+ try {
7297
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
7298
+ if (raw.token)
7299
+ return raw.token;
7300
+ } catch {}
7301
+ }
7302
+ throw new Error("Vercel auth token not found in any of the standard CLI store paths. Run `vercel login` first.");
7303
+ }
7304
+ function getProjectInfo(configDir, override) {
7305
+ const path = resolve3(configDir, ".vercel/project.json");
7306
+ if (!existsSync3(path)) {
7307
+ throw new Error(`.vercel/project.json not found at ${configDir}. Run \`vercel link\` first.`);
7308
+ }
7309
+ const data = JSON.parse(readFileSync2(path, "utf8"));
7310
+ if (override && data.projectName !== override) {
7311
+ console.warn(` \u26A0 Configured project '${override}' differs from .vercel/project.json '${data.projectName}'. Using linked project.`);
7312
+ }
7313
+ return { projectId: data.projectId, teamId: data.orgId };
7314
+ }
7315
+ async function vercelApi(method, endpoint, teamId, token, body) {
7316
+ const url = `https://api.vercel.com${endpoint}${endpoint.includes("?") ? "&" : "?"}teamId=${encodeURIComponent(teamId)}`;
7317
+ const headers = {
7318
+ Authorization: `Bearer ${token}`
7319
+ };
7320
+ if (body !== undefined)
7321
+ headers["Content-Type"] = "application/json";
7322
+ const res = await fetch(url, {
7323
+ method,
7324
+ headers,
7325
+ body: body !== undefined ? JSON.stringify(body) : undefined
7326
+ });
7327
+ const text = await res.text();
7328
+ let parsed;
7329
+ try {
7330
+ parsed = text ? JSON.parse(text) : undefined;
7331
+ } catch {
7332
+ parsed = text;
7333
+ }
7334
+ return { ok: res.ok, status: res.status, body: parsed };
7335
+ }
7336
+ async function listExistingEnvVars(projectId, teamId, token) {
7337
+ const res = await vercelApi("GET", `/v9/projects/${projectId}/env`, teamId, token);
7338
+ if (!res.ok) {
7339
+ throw new Error(`Failed to list env vars: ${res.status} ${JSON.stringify(res.body)}`);
7340
+ }
7341
+ const body = res.body;
7342
+ return body.envs ?? [];
7343
+ }
7344
+ async function deleteEnvVar(projectId, teamId, token, envId) {
7345
+ const res = await vercelApi("DELETE", `/v9/projects/${projectId}/env/${envId}`, teamId, token);
7346
+ if (!res.ok) {
7347
+ throw new Error(`Delete env var ${envId} failed: ${res.status} ${JSON.stringify(res.body)}`);
7348
+ }
7349
+ }
7350
+ async function createEnvVar(projectId, teamId, token, payload) {
7351
+ const res = await vercelApi("POST", `/v10/projects/${projectId}/env`, teamId, token, {
7352
+ key: payload.key,
7353
+ value: payload.value,
7354
+ target: payload.target,
7355
+ type: payload.type ?? "encrypted"
7356
+ });
7357
+ if (!res.ok) {
7358
+ throw new Error(`Create env var ${payload.key} failed: ${res.status} ${JSON.stringify(res.body)}`);
7359
+ }
7360
+ }
7285
7361
  async function syncVercel(name, target, vars, configDir, opts) {
7286
7362
  const errors2 = [];
7287
7363
  const envLabel = target.environments.join(", ");
@@ -7289,29 +7365,44 @@ async function syncVercel(name, target, vars, configDir, opts) {
7289
7365
  console.log(` Would back up and push ${vars.length} vars to Vercel [${envLabel}]`);
7290
7366
  return { target: name, type: "vercel", vars: vars.length, errors: errors2 };
7291
7367
  }
7368
+ let token;
7369
+ let projectId;
7370
+ let teamId;
7371
+ try {
7372
+ token = getVercelAuthToken();
7373
+ const info = getProjectInfo(configDir, target.project);
7374
+ projectId = info.projectId;
7375
+ teamId = info.teamId;
7376
+ } catch (err) {
7377
+ errors2.push(` \u2717 Vercel API setup: ${err instanceof Error ? err.message : err}`);
7378
+ return { target: name, type: "vercel", vars: vars.length, errors: errors2 };
7379
+ }
7292
7380
  for (const env of target.environments) {
7293
7381
  await backupVercelEnv(env, target.project, configDir);
7294
7382
  }
7383
+ let existing;
7384
+ try {
7385
+ existing = await listExistingEnvVars(projectId, teamId, token);
7386
+ } catch (err) {
7387
+ errors2.push(` \u2717 List env failed: ${err instanceof Error ? err.message : err}`);
7388
+ return { target: name, type: "vercel", vars: vars.length, errors: errors2 };
7389
+ }
7295
7390
  for (const v of vars) {
7296
7391
  for (const env of target.environments) {
7297
7392
  try {
7298
- const args = ["env", "add", v.key, env, "--force"];
7299
- if (target.project)
7300
- args.push("--project", target.project);
7301
- const proc = Bun.spawn(["vercel", ...args], {
7302
- stdin: new TextEncoder().encode(v.value),
7303
- stdout: "pipe",
7304
- stderr: "pipe"
7305
- });
7306
- const stderr = await new Response(proc.stderr).text();
7307
- const exitCode = await proc.exited;
7308
- if (exitCode !== 0) {
7309
- errors2.push(` \u2717 ${v.key} [${env}]: ${stderr.trim()}`);
7310
- } else {
7311
- console.log(` \u2713 ${v.key} [${env}]`);
7393
+ const conflict = existing.find((e) => e.key === v.key && e.target.includes(env) && !e.gitBranch);
7394
+ if (conflict) {
7395
+ await deleteEnvVar(projectId, teamId, token, conflict.id);
7396
+ existing = existing.filter((e) => e.id !== conflict.id);
7312
7397
  }
7398
+ await createEnvVar(projectId, teamId, token, {
7399
+ key: v.key,
7400
+ value: v.value,
7401
+ target: [env]
7402
+ });
7403
+ console.log(` \u2713 ${v.key} [${env}]`);
7313
7404
  } catch (err) {
7314
- errors2.push(` \u2717 ${v.key} [${env}]: ${err}`);
7405
+ errors2.push(` \u2717 ${v.key} [${env}]: ${err instanceof Error ? err.message : err}`);
7315
7406
  }
7316
7407
  }
7317
7408
  }
@@ -7513,7 +7604,7 @@ function parseArgs(args) {
7513
7604
  case "-v":
7514
7605
  case "--version": {
7515
7606
  const pkgPath = resolve4(import.meta.dirname ?? ".", "..", "package.json");
7516
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
7607
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
7517
7608
  console.log(`env-sync v${pkg.version}`);
7518
7609
  process.exit(0);
7519
7610
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psg2/env-sync",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Declarative env var management — 1Password → local files, Vercel, GitHub secrets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,7 +46,7 @@
46
46
  "yaml": "^2.7.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@biomejs/biome": "^1.9.4",
49
+ "@biomejs/biome": "^2.4.7",
50
50
  "@types/bun": "^1.2.4"
51
51
  }
52
52
  }