@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.
- package/dist/cli.js +109 -18
- 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
|
|
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
|
|
7299
|
-
if (
|
|
7300
|
-
|
|
7301
|
-
|
|
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(
|
|
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.
|
|
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": "^
|
|
49
|
+
"@biomejs/biome": "^2.4.7",
|
|
50
50
|
"@types/bun": "^1.2.4"
|
|
51
51
|
}
|
|
52
52
|
}
|