@infracraft/pulumi 1.0.1 → 1.1.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/dist/vercel/deploy.cjs +4 -2
- package/dist/vercel/deploy.cjs.map +1 -1
- package/dist/vercel/deploy.d.cts +11 -2
- package/dist/vercel/deploy.d.cts.map +1 -1
- package/dist/vercel/deploy.d.mts +11 -2
- package/dist/vercel/deploy.d.mts.map +1 -1
- package/dist/vercel/deploy.mjs +4 -2
- package/dist/vercel/deploy.mjs.map +1 -1
- package/dist/vercel/index.cjs +2 -0
- package/dist/vercel/index.d.cts +2 -1
- package/dist/vercel/index.d.mts +2 -1
- package/dist/vercel/index.mjs +2 -1
- package/dist/vercel/project.cjs +162 -0
- package/dist/vercel/project.cjs.map +1 -0
- package/dist/vercel/project.d.cts +72 -0
- package/dist/vercel/project.d.cts.map +1 -0
- package/dist/vercel/project.d.mts +72 -0
- package/dist/vercel/project.d.mts.map +1 -0
- package/dist/vercel/project.mjs +160 -0
- package/dist/vercel/project.mjs.map +1 -0
- package/dist/vercel/variable.cjs +4 -2
- package/dist/vercel/variable.cjs.map +1 -1
- package/dist/vercel/variable.d.cts +11 -2
- package/dist/vercel/variable.d.cts.map +1 -1
- package/dist/vercel/variable.d.mts +11 -2
- package/dist/vercel/variable.d.mts.map +1 -1
- package/dist/vercel/variable.mjs +4 -2
- package/dist/vercel/variable.mjs.map +1 -1
- package/package.json +1 -1
package/dist/vercel/deploy.cjs
CHANGED
|
@@ -29,8 +29,10 @@ _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
|
29
29
|
*/
|
|
30
30
|
var VercelDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
31
31
|
constructor(name, args, opts) {
|
|
32
|
-
const { provider, ...pulumiOpts } = opts;
|
|
32
|
+
const { provider, project, ...pulumiOpts } = opts;
|
|
33
33
|
super("infracraft:vercel:Deploy", name, {}, pulumiOpts);
|
|
34
|
+
const projectId = project ? project.id : args.projectId;
|
|
35
|
+
if (!projectId) throw new Error("VercelDeploy: either `args.projectId` or `opts.project` must be provided");
|
|
34
36
|
const hashParts = [require_hash.hashDirectory(node_path.join(args.monorepoRoot, args.rootDirectory))];
|
|
35
37
|
for (const dir of args.additionalSourceDirs ?? []) hashParts.push(require_hash.hashDirectory(node_path.join(args.monorepoRoot, dir)));
|
|
36
38
|
const sourceHash = hashParts.join(",");
|
|
@@ -44,7 +46,7 @@ var VercelDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
44
46
|
environment: {
|
|
45
47
|
VERCEL_TOKEN: provider.token,
|
|
46
48
|
VERCEL_ORG_ID: provider.teamId,
|
|
47
|
-
VERCEL_PROJECT_ID:
|
|
49
|
+
VERCEL_PROJECT_ID: projectId
|
|
48
50
|
}
|
|
49
51
|
}, {
|
|
50
52
|
parent: this,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["pulumi","hashDirectory","path","command"],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { hashDirectory } from \"../hash.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["pulumi","hashDirectory","path","command"],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { hashDirectory } from \"../hash.js\";\nimport type { VercelProject } from \"./project.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/** Relative path from monorepo root to the app directory (e.g. `\"apps/nexus\"`). */\n\trootDirectory: string;\n\n\t/** Absolute path to the monorepo root (working directory for `vercel deploy`). */\n\tmonorepoRoot: string;\n\n\t/** Env var map used as deploy trigger. Hashes both keys and resolved values so value changes trigger redeploy. */\n\tenv: Record<string, pulumi.Input<string>>;\n\n\t/** Additional directories (relative to monorepo root) to include in source hash. Changes in these trigger redeploy. */\n\tadditionalSourceDirs?: string[];\n\n\t/** Resources that must complete before the deploy runs. */\n\tdependsOn?: pulumi.Resource[];\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes` CLI.\n *\n * Triggers on source hash (computed from the app directory) and env content hash\n * (from `VercelVariable.contentHash`). When an env value changes — whether from\n * code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes\n * and a redeploy is triggered.\n *\n * @example\n * ```typescript\n * new VercelDeploy(\"nexus-deploy\", {\n * projectId: vercelProject.id,\n * rootDirectory: \"apps/nexus\",\n * monorepoRoot,\n * env: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelDeployArgs,\n\t\topts: VercelDeployOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelDeploy: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst appDir = path.join(args.monorepoRoot, args.rootDirectory);\n\n\t\tconst hashParts = [hashDirectory(appDir)];\n\n\t\tfor (const dir of args.additionalSourceDirs ?? []) {\n\t\t\thashParts.push(hashDirectory(path.join(args.monorepoRoot, dir)));\n\t\t}\n\n\t\tconst sourceHash = hashParts.join(\",\");\n\n\t\tconst commandOpts: pulumi.ResourceOptions = { parent: this };\n\n\t\tif (args.dependsOn && args.dependsOn.length > 0) {\n\t\t\tcommandOpts.dependsOn = args.dependsOn;\n\t\t}\n\n\t\tconst envHash = pulumi\n\t\t\t.all(\n\t\t\t\tObject.entries(args.env)\n\t\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t\t.map(([k, v]) => pulumi.output(v).apply((val) => `${k}=${val}`)),\n\t\t\t)\n\t\t\t.apply((parts) => parts.join(\",\"));\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: \"vercel deploy --prod --yes\",\n\t\t\t\ttriggers: [sourceHash, envHash],\n\t\t\t\tdir: args.monorepoRoot,\n\t\t\t\tenvironment: {\n\t\t\t\t\tVERCEL_TOKEN: provider.token,\n\t\t\t\t\tVERCEL_ORG_ID: provider.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: projectId,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tparent: this,\n\t\t\t\t...(commandOpts.dependsOn ? { dependsOn: commandOpts.dependsOn } : {}),\n\t\t\t},\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAa,eAAb,cAAkCA,eAAO,kBAAkB;CAC1D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,4BAA4B,MAAM,CAAC,GAAG,UAAU;EAEtD,MAAM,YAAY,UACf,QAAQ,KACP,KAAK;EAET,IAAI,CAAC,WACJ,MAAM,IAAI,MACT,0EACD;EAKD,MAAM,YAAY,CAACC,2BAFJC,UAAK,KAAK,KAAK,cAAc,KAAK,aAEX,CAAC,CAAC;EAExC,KAAK,MAAM,OAAO,KAAK,wBAAwB,CAAC,GAC/C,UAAU,KAAKD,2BAAcC,UAAK,KAAK,KAAK,cAAc,GAAG,CAAC,CAAC;EAGhE,MAAM,aAAa,UAAU,KAAK,GAAG;EAErC,MAAM,cAAsC,EAAE,QAAQ,KAAK;EAE3D,IAAI,KAAK,aAAa,KAAK,UAAU,SAAS,GAC7C,YAAY,YAAY,KAAK;EAG9B,MAAM,UAAUF,eACd,IACA,OAAO,QAAQ,KAAK,GAAG,EACrB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,KAAK,CAAC,GAAG,OAAOA,eAAO,OAAO,CAAC,EAAE,OAAO,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CACjE,EACC,OAAO,UAAU,MAAM,KAAK,GAAG,CAAC;EAElC,IAAIG,gBAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR,UAAU,CAAC,YAAY,OAAO;GAC9B,KAAK,KAAK;GACV,aAAa;IACZ,cAAc,SAAS;IACvB,eAAe,SAAS;IACxB,mBAAmB;GACpB;EACD,GACA;GACC,QAAQ;GACR,GAAI,YAAY,YAAY,EAAE,WAAW,YAAY,UAAU,IAAI,CAAC;EACrE,CACD;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
package/dist/vercel/deploy.d.cts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.cjs";
|
|
2
2
|
import { VercelProvider } from "./provider.cjs";
|
|
3
|
+
import { VercelProject } from "./project.cjs";
|
|
3
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
4
5
|
|
|
5
6
|
//#region src/vercel/deploy.d.ts
|
|
6
7
|
/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */
|
|
7
8
|
type VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
8
9
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
10
|
+
/**
|
|
11
|
+
* VercelProject resource to source the project ID from.
|
|
12
|
+
* When provided, `args.projectId` is optional and ignored if both are given.
|
|
13
|
+
*/
|
|
14
|
+
project?: VercelProject;
|
|
9
15
|
};
|
|
10
16
|
/** Args for VercelDeploy. */
|
|
11
17
|
interface VercelDeployArgs {
|
|
12
|
-
/**
|
|
13
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Vercel project ID.
|
|
20
|
+
* Required when `opts.project` is not provided.
|
|
21
|
+
*/
|
|
22
|
+
projectId?: pulumi.Input<string>;
|
|
14
23
|
/** Relative path from monorepo root to the app directory (e.g. `"apps/nexus"`). */
|
|
15
24
|
rootDirectory: string;
|
|
16
25
|
/** Absolute path to the monorepo root (working directory for `vercel deploy`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;KAQK,mBAAA,GAAsB,IAAA,CAC1B,MAAA,CAAO,wBAAA;uCAIP,QAAA,EAAU,cAAA;EALa;;;;EAWvB,OAAA,GAAU,aAAA;AAAA;;UAIM,gBAAA;EAfU;;;;EAoB1B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;EAYA,aAAA;EAZuB;EAevB,YAAA;EAXgB;EAchB,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;;EAG3B,oBAAA;EAHoB;EAMpB,SAAA,GAAY,MAAA,CAAO,QAAA;AAAA;;;;;;;;;;;;;;;;;AAAQ;AAqB5B;cAAa,YAAA,SAAqB,MAAA,CAAO,iBAAA;cAEvC,IAAA,UACA,IAAA,EAAM,gBAAA,EACN,IAAA,EAAM,mBAAA;AAAA"}
|
package/dist/vercel/deploy.d.mts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
2
|
import { VercelProvider } from "./provider.mjs";
|
|
3
|
+
import { VercelProject } from "./project.mjs";
|
|
3
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
4
5
|
|
|
5
6
|
//#region src/vercel/deploy.d.ts
|
|
6
7
|
/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */
|
|
7
8
|
type VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
8
9
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
10
|
+
/**
|
|
11
|
+
* VercelProject resource to source the project ID from.
|
|
12
|
+
* When provided, `args.projectId` is optional and ignored if both are given.
|
|
13
|
+
*/
|
|
14
|
+
project?: VercelProject;
|
|
9
15
|
};
|
|
10
16
|
/** Args for VercelDeploy. */
|
|
11
17
|
interface VercelDeployArgs {
|
|
12
|
-
/**
|
|
13
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Vercel project ID.
|
|
20
|
+
* Required when `opts.project` is not provided.
|
|
21
|
+
*/
|
|
22
|
+
projectId?: pulumi.Input<string>;
|
|
14
23
|
/** Relative path from monorepo root to the app directory (e.g. `"apps/nexus"`). */
|
|
15
24
|
rootDirectory: string;
|
|
16
25
|
/** Absolute path to the monorepo root (working directory for `vercel deploy`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;KAQK,mBAAA,GAAsB,IAAA,CAC1B,MAAA,CAAO,wBAAA;uCAIP,QAAA,EAAU,cAAA;EALa;;;;EAWvB,OAAA,GAAU,aAAA;AAAA;;UAIM,gBAAA;EAfU;;;;EAoB1B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;EAYA,aAAA;EAZuB;EAevB,YAAA;EAXgB;EAchB,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;;EAG3B,oBAAA;EAHoB;EAMpB,SAAA,GAAY,MAAA,CAAO,QAAA;AAAA;;;;;;;;;;;;;;;;;AAAQ;AAqB5B;cAAa,YAAA,SAAqB,MAAA,CAAO,iBAAA;cAEvC,IAAA,UACA,IAAA,EAAM,gBAAA,EACN,IAAA,EAAM,mBAAA;AAAA"}
|
package/dist/vercel/deploy.mjs
CHANGED
|
@@ -25,8 +25,10 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
25
25
|
*/
|
|
26
26
|
var VercelDeploy = class extends pulumi.ComponentResource {
|
|
27
27
|
constructor(name, args, opts) {
|
|
28
|
-
const { provider, ...pulumiOpts } = opts;
|
|
28
|
+
const { provider, project, ...pulumiOpts } = opts;
|
|
29
29
|
super("infracraft:vercel:Deploy", name, {}, pulumiOpts);
|
|
30
|
+
const projectId = project ? project.id : args.projectId;
|
|
31
|
+
if (!projectId) throw new Error("VercelDeploy: either `args.projectId` or `opts.project` must be provided");
|
|
30
32
|
const hashParts = [hashDirectory(path.join(args.monorepoRoot, args.rootDirectory))];
|
|
31
33
|
for (const dir of args.additionalSourceDirs ?? []) hashParts.push(hashDirectory(path.join(args.monorepoRoot, dir)));
|
|
32
34
|
const sourceHash = hashParts.join(",");
|
|
@@ -40,7 +42,7 @@ var VercelDeploy = class extends pulumi.ComponentResource {
|
|
|
40
42
|
environment: {
|
|
41
43
|
VERCEL_TOKEN: provider.token,
|
|
42
44
|
VERCEL_ORG_ID: provider.teamId,
|
|
43
|
-
VERCEL_PROJECT_ID:
|
|
45
|
+
VERCEL_PROJECT_ID: projectId
|
|
44
46
|
}
|
|
45
47
|
}, {
|
|
46
48
|
parent: this,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { hashDirectory } from \"../hash.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { hashDirectory } from \"../hash.js\";\nimport type { VercelProject } from \"./project.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/** Relative path from monorepo root to the app directory (e.g. `\"apps/nexus\"`). */\n\trootDirectory: string;\n\n\t/** Absolute path to the monorepo root (working directory for `vercel deploy`). */\n\tmonorepoRoot: string;\n\n\t/** Env var map used as deploy trigger. Hashes both keys and resolved values so value changes trigger redeploy. */\n\tenv: Record<string, pulumi.Input<string>>;\n\n\t/** Additional directories (relative to monorepo root) to include in source hash. Changes in these trigger redeploy. */\n\tadditionalSourceDirs?: string[];\n\n\t/** Resources that must complete before the deploy runs. */\n\tdependsOn?: pulumi.Resource[];\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes` CLI.\n *\n * Triggers on source hash (computed from the app directory) and env content hash\n * (from `VercelVariable.contentHash`). When an env value changes — whether from\n * code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes\n * and a redeploy is triggered.\n *\n * @example\n * ```typescript\n * new VercelDeploy(\"nexus-deploy\", {\n * projectId: vercelProject.id,\n * rootDirectory: \"apps/nexus\",\n * monorepoRoot,\n * env: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelDeployArgs,\n\t\topts: VercelDeployOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelDeploy: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst appDir = path.join(args.monorepoRoot, args.rootDirectory);\n\n\t\tconst hashParts = [hashDirectory(appDir)];\n\n\t\tfor (const dir of args.additionalSourceDirs ?? []) {\n\t\t\thashParts.push(hashDirectory(path.join(args.monorepoRoot, dir)));\n\t\t}\n\n\t\tconst sourceHash = hashParts.join(\",\");\n\n\t\tconst commandOpts: pulumi.ResourceOptions = { parent: this };\n\n\t\tif (args.dependsOn && args.dependsOn.length > 0) {\n\t\t\tcommandOpts.dependsOn = args.dependsOn;\n\t\t}\n\n\t\tconst envHash = pulumi\n\t\t\t.all(\n\t\t\t\tObject.entries(args.env)\n\t\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t\t.map(([k, v]) => pulumi.output(v).apply((val) => `${k}=${val}`)),\n\t\t\t)\n\t\t\t.apply((parts) => parts.join(\",\"));\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: \"vercel deploy --prod --yes\",\n\t\t\t\ttriggers: [sourceHash, envHash],\n\t\t\t\tdir: args.monorepoRoot,\n\t\t\t\tenvironment: {\n\t\t\t\t\tVERCEL_TOKEN: provider.token,\n\t\t\t\t\tVERCEL_ORG_ID: provider.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: projectId,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tparent: this,\n\t\t\t\t...(commandOpts.dependsOn ? { dependsOn: commandOpts.dependsOn } : {}),\n\t\t\t},\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAa,eAAb,cAAkC,OAAO,kBAAkB;CAC1D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,4BAA4B,MAAM,CAAC,GAAG,UAAU;EAEtD,MAAM,YAAY,UACf,QAAQ,KACP,KAAK;EAET,IAAI,CAAC,WACJ,MAAM,IAAI,MACT,0EACD;EAKD,MAAM,YAAY,CAAC,cAFJ,KAAK,KAAK,KAAK,cAAc,KAAK,aAEX,CAAC,CAAC;EAExC,KAAK,MAAM,OAAO,KAAK,wBAAwB,CAAC,GAC/C,UAAU,KAAK,cAAc,KAAK,KAAK,KAAK,cAAc,GAAG,CAAC,CAAC;EAGhE,MAAM,aAAa,UAAU,KAAK,GAAG;EAErC,MAAM,cAAsC,EAAE,QAAQ,KAAK;EAE3D,IAAI,KAAK,aAAa,KAAK,UAAU,SAAS,GAC7C,YAAY,YAAY,KAAK;EAG9B,MAAM,UAAU,OACd,IACA,OAAO,QAAQ,KAAK,GAAG,EACrB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,KAAK,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,EAAE,OAAO,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CACjE,EACC,OAAO,UAAU,MAAM,KAAK,GAAG,CAAC;EAElC,IAAI,QAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR,UAAU,CAAC,YAAY,OAAO;GAC9B,KAAK,KAAK;GACV,aAAa;IACZ,cAAc,SAAS;IACvB,eAAe,SAAS;IACxB,mBAAmB;GACpB;EACD,GACA;GACC,QAAQ;GACR,GAAI,YAAY,YAAY,EAAE,WAAW,YAAY,UAAU,IAAI,CAAC;EACrE,CACD;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
package/dist/vercel/index.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_vercel_deploy = require('./deploy.cjs');
|
|
3
3
|
const require_vercel_provider = require('./provider.cjs');
|
|
4
|
+
const require_vercel_project = require('./project.cjs');
|
|
4
5
|
const require_vercel_variable = require('./variable.cjs');
|
|
5
6
|
|
|
6
7
|
exports.VercelDeploy = require_vercel_deploy.VercelDeploy;
|
|
8
|
+
exports.VercelProject = require_vercel_project.VercelProject;
|
|
7
9
|
exports.VercelProvider = require_vercel_provider.VercelProvider;
|
|
8
10
|
exports.VercelVariable = require_vercel_variable.VercelVariable;
|
package/dist/vercel/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VercelProvider } from "./provider.cjs";
|
|
2
|
+
import { VercelProject, VercelProjectArgs } from "./project.cjs";
|
|
2
3
|
import { VercelDeploy, VercelDeployArgs } from "./deploy.cjs";
|
|
3
4
|
import { VercelVariable, VercelVariableArgs } from "./variable.cjs";
|
|
4
|
-
export { VercelDeploy, type VercelDeployArgs, VercelProvider, VercelVariable, type VercelVariableArgs };
|
|
5
|
+
export { VercelDeploy, type VercelDeployArgs, VercelProject, type VercelProjectArgs, VercelProvider, VercelVariable, type VercelVariableArgs };
|
package/dist/vercel/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VercelProvider } from "./provider.mjs";
|
|
2
|
+
import { VercelProject, VercelProjectArgs } from "./project.mjs";
|
|
2
3
|
import { VercelDeploy, VercelDeployArgs } from "./deploy.mjs";
|
|
3
4
|
import { VercelVariable, VercelVariableArgs } from "./variable.mjs";
|
|
4
|
-
export { VercelDeploy, type VercelDeployArgs, VercelProvider, VercelVariable, type VercelVariableArgs };
|
|
5
|
+
export { VercelDeploy, type VercelDeployArgs, VercelProject, type VercelProjectArgs, VercelProvider, VercelVariable, type VercelVariableArgs };
|
package/dist/vercel/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VercelDeploy } from "./deploy.mjs";
|
|
2
2
|
import { VercelProvider } from "./provider.mjs";
|
|
3
|
+
import { VercelProject } from "./project.mjs";
|
|
3
4
|
import { VercelVariable } from "./variable.mjs";
|
|
4
5
|
|
|
5
|
-
export { VercelDeploy, VercelProvider, VercelVariable };
|
|
6
|
+
export { VercelDeploy, VercelProject, VercelProvider, VercelVariable };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
+
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
4
|
+
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
5
|
+
|
|
6
|
+
//#region src/vercel/project.ts
|
|
7
|
+
const VERCEL_API_URL = "https://api.vercel.com";
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a Vercel project by name or ID.
|
|
10
|
+
* Returns `null` if the project is not found (404).
|
|
11
|
+
*/
|
|
12
|
+
async function fetchProject(token, teamId, idOrName) {
|
|
13
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
14
|
+
if (response.status === 404) return null;
|
|
15
|
+
if (!response.ok) throw new Error(`Vercel API error fetching project "${idOrName}" (${response.status}): ${await response.text()}`);
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Builds the project body for create / update calls.
|
|
20
|
+
* Only includes defined optional fields.
|
|
21
|
+
*/
|
|
22
|
+
function buildProjectBody(inputs) {
|
|
23
|
+
const body = { name: inputs.name };
|
|
24
|
+
if (inputs.framework !== void 0) body.framework = inputs.framework;
|
|
25
|
+
if (inputs.rootDirectory !== void 0) body.rootDirectory = inputs.rootDirectory;
|
|
26
|
+
if (inputs.buildCommand !== void 0) body.buildCommand = inputs.buildCommand;
|
|
27
|
+
if (inputs.installCommand !== void 0) body.installCommand = inputs.installCommand;
|
|
28
|
+
if (inputs.outputDirectory !== void 0) body.outputDirectory = inputs.outputDirectory;
|
|
29
|
+
return body;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dynamic provider implementing adopt-or-create for Vercel projects.
|
|
33
|
+
*
|
|
34
|
+
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
35
|
+
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
36
|
+
* Deletion is a no-op to protect production projects.
|
|
37
|
+
*/
|
|
38
|
+
var VercelProjectResourceProvider = class {
|
|
39
|
+
async create(inputs) {
|
|
40
|
+
const existing = await fetchProject(inputs.token, inputs.teamId, inputs.name);
|
|
41
|
+
let projectId;
|
|
42
|
+
if (existing) {
|
|
43
|
+
_pulumi_pulumi.log.info(`Adopting existing Vercel project "${inputs.name}" (${existing.id})`);
|
|
44
|
+
projectId = existing.id;
|
|
45
|
+
} else {
|
|
46
|
+
_pulumi_pulumi.log.info(`Vercel project "${inputs.name}" not found — creating...`);
|
|
47
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${inputs.token}`,
|
|
51
|
+
"Content-Type": "application/json"
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify(buildProjectBody(inputs))
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) throw new Error(`Vercel API error creating project "${inputs.name}" (${response.status}): ${await response.text()}`);
|
|
56
|
+
projectId = (await response.json()).id;
|
|
57
|
+
}
|
|
58
|
+
const outs = {
|
|
59
|
+
...inputs,
|
|
60
|
+
projectId
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
id: projectId,
|
|
64
|
+
outs
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async read(id, props) {
|
|
68
|
+
const project = await fetchProject(props.token, props.teamId, id);
|
|
69
|
+
if (!project) throw new Error(`Vercel project "${id}" not found during refresh`);
|
|
70
|
+
return {
|
|
71
|
+
id: project.id,
|
|
72
|
+
props: {
|
|
73
|
+
...props,
|
|
74
|
+
name: project.name,
|
|
75
|
+
projectId: project.id
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async update(id, _olds, news) {
|
|
80
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`, {
|
|
81
|
+
method: "PATCH",
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: `Bearer ${news.token}`,
|
|
84
|
+
"Content-Type": "application/json"
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(buildProjectBody(news))
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
89
|
+
return { outs: {
|
|
90
|
+
...news,
|
|
91
|
+
projectId: id
|
|
92
|
+
} };
|
|
93
|
+
}
|
|
94
|
+
async delete() {
|
|
95
|
+
_pulumi_pulumi.log.warn("Vercel project deletion skipped — projects are not deleted by Pulumi");
|
|
96
|
+
}
|
|
97
|
+
async diff(_id, olds, news) {
|
|
98
|
+
const replaces = [];
|
|
99
|
+
const changes = [];
|
|
100
|
+
if (olds.teamId !== news.teamId) replaces.push("teamId");
|
|
101
|
+
for (const field of [
|
|
102
|
+
"name",
|
|
103
|
+
"framework",
|
|
104
|
+
"rootDirectory",
|
|
105
|
+
"buildCommand",
|
|
106
|
+
"installCommand",
|
|
107
|
+
"outputDirectory"
|
|
108
|
+
]) if (olds[field] !== news[field]) changes.push(field);
|
|
109
|
+
return {
|
|
110
|
+
changes: replaces.length > 0 || changes.length > 0,
|
|
111
|
+
replaces,
|
|
112
|
+
deleteBeforeReplace: true
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Internal dynamic resource — not part of the public API. */
|
|
117
|
+
var VercelProjectResource = class extends _pulumi_pulumi.dynamic.Resource {
|
|
118
|
+
constructor(name, args, opts) {
|
|
119
|
+
super(new VercelProjectResourceProvider(), name, {
|
|
120
|
+
...args,
|
|
121
|
+
projectId: void 0
|
|
122
|
+
}, opts);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Manages a Vercel project with adopt-or-create semantics.
|
|
127
|
+
*
|
|
128
|
+
* On first `pulumi up`, looks up the project by name. If it already exists,
|
|
129
|
+
* the resource adopts it. If not, a new project is created. Deletion is a
|
|
130
|
+
* no-op to protect production projects.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const project = new VercelProject("nexus", {
|
|
135
|
+
* name: "nexus",
|
|
136
|
+
* framework: "nextjs",
|
|
137
|
+
* rootDirectory: "apps/nexus",
|
|
138
|
+
* }, { provider });
|
|
139
|
+
*
|
|
140
|
+
* new VercelVariable("nexus-vars", {
|
|
141
|
+
* projectId: project.id,
|
|
142
|
+
* variables: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
143
|
+
* }, { provider });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
var VercelProject = class extends _pulumi_pulumi.ComponentResource {
|
|
147
|
+
constructor(name, args, opts) {
|
|
148
|
+
const { provider, ...pulumiOpts } = opts;
|
|
149
|
+
super("infracraft:vercel:Project", name, {}, pulumiOpts);
|
|
150
|
+
const resource = new VercelProjectResource(`${name}-resource`, {
|
|
151
|
+
token: provider.token,
|
|
152
|
+
teamId: provider.teamId,
|
|
153
|
+
...args
|
|
154
|
+
}, { parent: this });
|
|
155
|
+
this.id = resource.projectId;
|
|
156
|
+
this.registerOutputs({ id: this.id });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
exports.VercelProject = VercelProject;
|
|
162
|
+
//# sourceMappingURL=project.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.cjs","names":["pulumi"],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset (e.g. `\"nextjs\"`, `\"remix\"`). */\n\tframework?: string;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) body.framework = inputs.framework;\n\tif (inputs.rootDirectory !== undefined)\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\tif (inputs.buildCommand !== undefined) body.buildCommand = inputs.buildCommand;\n\tif (inputs.installCommand !== undefined)\n\t\tbody.installCommand = inputs.installCommand;\n\tif (inputs.outputDirectory !== undefined)\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(inputs.token, inputs.teamId, inputs.name);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId };\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<string>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset (e.g. `\"nextjs\"`, `\"remix\"`). */\n\tframework?: pulumi.Input<string>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * variables: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.cjs";
|
|
2
|
+
import { VercelProvider } from "./provider.cjs";
|
|
3
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
4
|
+
|
|
5
|
+
//#region src/vercel/project.d.ts
|
|
6
|
+
/** Resolved inputs for the Vercel project dynamic provider. */
|
|
7
|
+
interface VercelProjectInputs {
|
|
8
|
+
/** Vercel API bearer token. */
|
|
9
|
+
token: string;
|
|
10
|
+
/** Vercel team/org ID. */
|
|
11
|
+
teamId: string;
|
|
12
|
+
/** Project name. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Framework preset (e.g. `"nextjs"`, `"remix"`). */
|
|
15
|
+
framework?: string;
|
|
16
|
+
/** Relative path to the project root within a monorepo (e.g. `"apps/nexus"`). */
|
|
17
|
+
rootDirectory?: string;
|
|
18
|
+
/** Custom build command. */
|
|
19
|
+
buildCommand?: string;
|
|
20
|
+
/** Custom install command. */
|
|
21
|
+
installCommand?: string;
|
|
22
|
+
/** Custom output directory. */
|
|
23
|
+
outputDirectory?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Options type for VercelProject — replaces Pulumi's native `provider` field. */
|
|
26
|
+
type VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
27
|
+
/** Vercel authentication context. */provider: VercelProvider;
|
|
28
|
+
};
|
|
29
|
+
/** Args for VercelProject. */
|
|
30
|
+
interface VercelProjectArgs {
|
|
31
|
+
/** Project name. Used for both adoption lookup and display name. */
|
|
32
|
+
name: pulumi.Input<string>;
|
|
33
|
+
/** Framework preset (e.g. `"nextjs"`, `"remix"`). */
|
|
34
|
+
framework?: pulumi.Input<string>;
|
|
35
|
+
/** Relative path to the project root within a monorepo (e.g. `"apps/nexus"`). */
|
|
36
|
+
rootDirectory?: pulumi.Input<string>;
|
|
37
|
+
/** Custom build command. */
|
|
38
|
+
buildCommand?: pulumi.Input<string>;
|
|
39
|
+
/** Custom install command. */
|
|
40
|
+
installCommand?: pulumi.Input<string>;
|
|
41
|
+
/** Custom output directory. */
|
|
42
|
+
outputDirectory?: pulumi.Input<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Manages a Vercel project with adopt-or-create semantics.
|
|
46
|
+
*
|
|
47
|
+
* On first `pulumi up`, looks up the project by name. If it already exists,
|
|
48
|
+
* the resource adopts it. If not, a new project is created. Deletion is a
|
|
49
|
+
* no-op to protect production projects.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const project = new VercelProject("nexus", {
|
|
54
|
+
* name: "nexus",
|
|
55
|
+
* framework: "nextjs",
|
|
56
|
+
* rootDirectory: "apps/nexus",
|
|
57
|
+
* }, { provider });
|
|
58
|
+
*
|
|
59
|
+
* new VercelVariable("nexus-vars", {
|
|
60
|
+
* projectId: project.id,
|
|
61
|
+
* variables: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
62
|
+
* }, { provider });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare class VercelProject extends pulumi.ComponentResource {
|
|
66
|
+
/** Vercel-assigned project ID. */
|
|
67
|
+
readonly id: pulumi.Output<string>;
|
|
68
|
+
constructor(name: string, args: VercelProjectArgs, opts: VercelProjectOptions);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { VercelProject, VercelProjectArgs, VercelProjectInputs };
|
|
72
|
+
//# sourceMappingURL=project.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.cts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;UAMiB,mBAAA;;EAEhB,KAAA;EAFmC;EAKnC,MAAA;EALmC;EAQnC,IAAA;EAHA;EAMA,SAAA;EAAA;EAGA,aAAA;EAGA;EAAA,YAAA;EAMA;EAHA,cAAA;EAGe;EAAf,eAAA;AAAA;;KAsOI,oBAAA,GAAuB,IAAA,CAAK,MAAA,CAAO,wBAAA;EAAZ,qCAE3B,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EANgB;EAQhC,IAAA,EAAM,MAAA,CAAO,KAAA;EANb;EASA,SAAA,GAAY,MAAA,CAAO,KAAA;EATK;EAYxB,aAAA,GAAgB,MAAA,CAAO,KAAA;EARP;EAWhB,YAAA,GAAe,MAAA,CAAO,KAAA;;EAGtB,cAAA,GAAiB,MAAA,CAAO,KAAA;EATZ;EAYZ,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;cAwBb,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAxBX;EAAA,SA0Bd,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
+
import { VercelProvider } from "./provider.mjs";
|
|
3
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
4
|
+
|
|
5
|
+
//#region src/vercel/project.d.ts
|
|
6
|
+
/** Resolved inputs for the Vercel project dynamic provider. */
|
|
7
|
+
interface VercelProjectInputs {
|
|
8
|
+
/** Vercel API bearer token. */
|
|
9
|
+
token: string;
|
|
10
|
+
/** Vercel team/org ID. */
|
|
11
|
+
teamId: string;
|
|
12
|
+
/** Project name. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Framework preset (e.g. `"nextjs"`, `"remix"`). */
|
|
15
|
+
framework?: string;
|
|
16
|
+
/** Relative path to the project root within a monorepo (e.g. `"apps/nexus"`). */
|
|
17
|
+
rootDirectory?: string;
|
|
18
|
+
/** Custom build command. */
|
|
19
|
+
buildCommand?: string;
|
|
20
|
+
/** Custom install command. */
|
|
21
|
+
installCommand?: string;
|
|
22
|
+
/** Custom output directory. */
|
|
23
|
+
outputDirectory?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Options type for VercelProject — replaces Pulumi's native `provider` field. */
|
|
26
|
+
type VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
27
|
+
/** Vercel authentication context. */provider: VercelProvider;
|
|
28
|
+
};
|
|
29
|
+
/** Args for VercelProject. */
|
|
30
|
+
interface VercelProjectArgs {
|
|
31
|
+
/** Project name. Used for both adoption lookup and display name. */
|
|
32
|
+
name: pulumi.Input<string>;
|
|
33
|
+
/** Framework preset (e.g. `"nextjs"`, `"remix"`). */
|
|
34
|
+
framework?: pulumi.Input<string>;
|
|
35
|
+
/** Relative path to the project root within a monorepo (e.g. `"apps/nexus"`). */
|
|
36
|
+
rootDirectory?: pulumi.Input<string>;
|
|
37
|
+
/** Custom build command. */
|
|
38
|
+
buildCommand?: pulumi.Input<string>;
|
|
39
|
+
/** Custom install command. */
|
|
40
|
+
installCommand?: pulumi.Input<string>;
|
|
41
|
+
/** Custom output directory. */
|
|
42
|
+
outputDirectory?: pulumi.Input<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Manages a Vercel project with adopt-or-create semantics.
|
|
46
|
+
*
|
|
47
|
+
* On first `pulumi up`, looks up the project by name. If it already exists,
|
|
48
|
+
* the resource adopts it. If not, a new project is created. Deletion is a
|
|
49
|
+
* no-op to protect production projects.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const project = new VercelProject("nexus", {
|
|
54
|
+
* name: "nexus",
|
|
55
|
+
* framework: "nextjs",
|
|
56
|
+
* rootDirectory: "apps/nexus",
|
|
57
|
+
* }, { provider });
|
|
58
|
+
*
|
|
59
|
+
* new VercelVariable("nexus-vars", {
|
|
60
|
+
* projectId: project.id,
|
|
61
|
+
* variables: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
62
|
+
* }, { provider });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare class VercelProject extends pulumi.ComponentResource {
|
|
66
|
+
/** Vercel-assigned project ID. */
|
|
67
|
+
readonly id: pulumi.Output<string>;
|
|
68
|
+
constructor(name: string, args: VercelProjectArgs, opts: VercelProjectOptions);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { VercelProject, VercelProjectArgs, VercelProjectInputs };
|
|
72
|
+
//# sourceMappingURL=project.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.mts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;UAMiB,mBAAA;;EAEhB,KAAA;EAFmC;EAKnC,MAAA;EALmC;EAQnC,IAAA;EAHA;EAMA,SAAA;EAAA;EAGA,aAAA;EAGA;EAAA,YAAA;EAMA;EAHA,cAAA;EAGe;EAAf,eAAA;AAAA;;KAsOI,oBAAA,GAAuB,IAAA,CAAK,MAAA,CAAO,wBAAA;EAAZ,qCAE3B,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EANgB;EAQhC,IAAA,EAAM,MAAA,CAAO,KAAA;EANb;EASA,SAAA,GAAY,MAAA,CAAO,KAAA;EATK;EAYxB,aAAA,GAAgB,MAAA,CAAO,KAAA;EARP;EAWhB,YAAA,GAAe,MAAA,CAAO,KAAA;;EAGtB,cAAA,GAAiB,MAAA,CAAO,KAAA;EATZ;EAYZ,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;cAwBb,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAxBX;EAAA,SA0Bd,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
3
|
+
|
|
4
|
+
//#region src/vercel/project.ts
|
|
5
|
+
const VERCEL_API_URL = "https://api.vercel.com";
|
|
6
|
+
/**
|
|
7
|
+
* Fetches a Vercel project by name or ID.
|
|
8
|
+
* Returns `null` if the project is not found (404).
|
|
9
|
+
*/
|
|
10
|
+
async function fetchProject(token, teamId, idOrName) {
|
|
11
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
12
|
+
if (response.status === 404) return null;
|
|
13
|
+
if (!response.ok) throw new Error(`Vercel API error fetching project "${idOrName}" (${response.status}): ${await response.text()}`);
|
|
14
|
+
return await response.json();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds the project body for create / update calls.
|
|
18
|
+
* Only includes defined optional fields.
|
|
19
|
+
*/
|
|
20
|
+
function buildProjectBody(inputs) {
|
|
21
|
+
const body = { name: inputs.name };
|
|
22
|
+
if (inputs.framework !== void 0) body.framework = inputs.framework;
|
|
23
|
+
if (inputs.rootDirectory !== void 0) body.rootDirectory = inputs.rootDirectory;
|
|
24
|
+
if (inputs.buildCommand !== void 0) body.buildCommand = inputs.buildCommand;
|
|
25
|
+
if (inputs.installCommand !== void 0) body.installCommand = inputs.installCommand;
|
|
26
|
+
if (inputs.outputDirectory !== void 0) body.outputDirectory = inputs.outputDirectory;
|
|
27
|
+
return body;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Dynamic provider implementing adopt-or-create for Vercel projects.
|
|
31
|
+
*
|
|
32
|
+
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
33
|
+
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
34
|
+
* Deletion is a no-op to protect production projects.
|
|
35
|
+
*/
|
|
36
|
+
var VercelProjectResourceProvider = class {
|
|
37
|
+
async create(inputs) {
|
|
38
|
+
const existing = await fetchProject(inputs.token, inputs.teamId, inputs.name);
|
|
39
|
+
let projectId;
|
|
40
|
+
if (existing) {
|
|
41
|
+
pulumi.log.info(`Adopting existing Vercel project "${inputs.name}" (${existing.id})`);
|
|
42
|
+
projectId = existing.id;
|
|
43
|
+
} else {
|
|
44
|
+
pulumi.log.info(`Vercel project "${inputs.name}" not found — creating...`);
|
|
45
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
Authorization: `Bearer ${inputs.token}`,
|
|
49
|
+
"Content-Type": "application/json"
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify(buildProjectBody(inputs))
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) throw new Error(`Vercel API error creating project "${inputs.name}" (${response.status}): ${await response.text()}`);
|
|
54
|
+
projectId = (await response.json()).id;
|
|
55
|
+
}
|
|
56
|
+
const outs = {
|
|
57
|
+
...inputs,
|
|
58
|
+
projectId
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
id: projectId,
|
|
62
|
+
outs
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async read(id, props) {
|
|
66
|
+
const project = await fetchProject(props.token, props.teamId, id);
|
|
67
|
+
if (!project) throw new Error(`Vercel project "${id}" not found during refresh`);
|
|
68
|
+
return {
|
|
69
|
+
id: project.id,
|
|
70
|
+
props: {
|
|
71
|
+
...props,
|
|
72
|
+
name: project.name,
|
|
73
|
+
projectId: project.id
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async update(id, _olds, news) {
|
|
78
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`, {
|
|
79
|
+
method: "PATCH",
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${news.token}`,
|
|
82
|
+
"Content-Type": "application/json"
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify(buildProjectBody(news))
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
87
|
+
return { outs: {
|
|
88
|
+
...news,
|
|
89
|
+
projectId: id
|
|
90
|
+
} };
|
|
91
|
+
}
|
|
92
|
+
async delete() {
|
|
93
|
+
pulumi.log.warn("Vercel project deletion skipped — projects are not deleted by Pulumi");
|
|
94
|
+
}
|
|
95
|
+
async diff(_id, olds, news) {
|
|
96
|
+
const replaces = [];
|
|
97
|
+
const changes = [];
|
|
98
|
+
if (olds.teamId !== news.teamId) replaces.push("teamId");
|
|
99
|
+
for (const field of [
|
|
100
|
+
"name",
|
|
101
|
+
"framework",
|
|
102
|
+
"rootDirectory",
|
|
103
|
+
"buildCommand",
|
|
104
|
+
"installCommand",
|
|
105
|
+
"outputDirectory"
|
|
106
|
+
]) if (olds[field] !== news[field]) changes.push(field);
|
|
107
|
+
return {
|
|
108
|
+
changes: replaces.length > 0 || changes.length > 0,
|
|
109
|
+
replaces,
|
|
110
|
+
deleteBeforeReplace: true
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/** Internal dynamic resource — not part of the public API. */
|
|
115
|
+
var VercelProjectResource = class extends pulumi.dynamic.Resource {
|
|
116
|
+
constructor(name, args, opts) {
|
|
117
|
+
super(new VercelProjectResourceProvider(), name, {
|
|
118
|
+
...args,
|
|
119
|
+
projectId: void 0
|
|
120
|
+
}, opts);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Manages a Vercel project with adopt-or-create semantics.
|
|
125
|
+
*
|
|
126
|
+
* On first `pulumi up`, looks up the project by name. If it already exists,
|
|
127
|
+
* the resource adopts it. If not, a new project is created. Deletion is a
|
|
128
|
+
* no-op to protect production projects.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const project = new VercelProject("nexus", {
|
|
133
|
+
* name: "nexus",
|
|
134
|
+
* framework: "nextjs",
|
|
135
|
+
* rootDirectory: "apps/nexus",
|
|
136
|
+
* }, { provider });
|
|
137
|
+
*
|
|
138
|
+
* new VercelVariable("nexus-vars", {
|
|
139
|
+
* projectId: project.id,
|
|
140
|
+
* variables: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
141
|
+
* }, { provider });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
var VercelProject = class extends pulumi.ComponentResource {
|
|
145
|
+
constructor(name, args, opts) {
|
|
146
|
+
const { provider, ...pulumiOpts } = opts;
|
|
147
|
+
super("infracraft:vercel:Project", name, {}, pulumiOpts);
|
|
148
|
+
const resource = new VercelProjectResource(`${name}-resource`, {
|
|
149
|
+
token: provider.token,
|
|
150
|
+
teamId: provider.teamId,
|
|
151
|
+
...args
|
|
152
|
+
}, { parent: this });
|
|
153
|
+
this.id = resource.projectId;
|
|
154
|
+
this.registerOutputs({ id: this.id });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
export { VercelProject };
|
|
160
|
+
//# sourceMappingURL=project.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.mjs","names":[],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset (e.g. `\"nextjs\"`, `\"remix\"`). */\n\tframework?: string;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) body.framework = inputs.framework;\n\tif (inputs.rootDirectory !== undefined)\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\tif (inputs.buildCommand !== undefined) body.buildCommand = inputs.buildCommand;\n\tif (inputs.installCommand !== undefined)\n\t\tbody.installCommand = inputs.installCommand;\n\tif (inputs.outputDirectory !== undefined)\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(inputs.token, inputs.teamId, inputs.name);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId };\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<string>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset (e.g. `\"nextjs\"`, `\"remix\"`). */\n\tframework?: pulumi.Input<string>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * variables: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
package/dist/vercel/variable.cjs
CHANGED
|
@@ -194,12 +194,14 @@ var VercelVariableResource = class extends _pulumi_pulumi.dynamic.Resource {
|
|
|
194
194
|
*/
|
|
195
195
|
var VercelVariable = class extends _pulumi_pulumi.ComponentResource {
|
|
196
196
|
constructor(name, args, opts) {
|
|
197
|
-
const { provider, ...pulumiOpts } = opts;
|
|
197
|
+
const { provider, project, ...pulumiOpts } = opts;
|
|
198
198
|
super("infracraft:vercel:Variable", name, {}, pulumiOpts);
|
|
199
|
+
const projectId = project ? project.id : args.projectId;
|
|
200
|
+
if (!projectId) throw new Error("VercelVariable: either `args.projectId` or `opts.project` must be provided");
|
|
199
201
|
const resource = new VercelVariableResource(`${name}-resource`, {
|
|
200
202
|
token: provider.token,
|
|
201
203
|
teamId: provider.teamId,
|
|
202
|
-
projectId
|
|
204
|
+
projectId,
|
|
203
205
|
variables: args.variables
|
|
204
206
|
}, { parent: this });
|
|
205
207
|
this.contentHash = resource.contentHash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variable.cjs","names":["pulumi"],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\nexport interface VercelVariableInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Vercel project ID. */\n\tprojectId: string;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: Record<string, string>;\n}\n\n/** Persisted state for Vercel variables. */\ninterface VercelVariableOutputs extends VercelVariableInputs {\n\t/** Map of env var keys to their Vercel-assigned IDs (for updates and deletes). */\n\tenvIds: Record<string, string>;\n\n\t/** SHA-256 hash of all key-value pairs. Changes when any value is modified (including drift fixes). */\n\tcontentHash: string;\n}\n\n/**\n * Computes a deterministic content hash of a variables map.\n */\nasync function hashVariables(\n\tvariables: Record<string, string>,\n): Promise<string> {\n\tconst crypto = await import(\"node:crypto\");\n\tconst hash = crypto.createHash(\"sha256\");\n\n\tconst sorted = Object.entries(variables).sort(([a], [b]) =>\n\t\ta.localeCompare(b),\n\t);\n\n\tfor (const [key, value] of sorted) {\n\t\thash.update(key);\n\t\thash.update(value);\n\t}\n\n\treturn hash.digest(\"hex\");\n}\n\n/** Vercel API response for a single env var. */\ninterface VercelEnvVar {\n\tid: string;\n\tkey: string;\n\tvalue: string;\n\ttype: string;\n\ttarget: string[];\n}\n\n/**\n * Fetches all environment variables for a Vercel project with decrypted values.\n */\nasync function fetchEnvVars(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst listResponse = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!listResponse.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error (${listResponse.status}): ${await listResponse.text()}`,\n\t\t);\n\t}\n\n\tconst list = (await listResponse.json()) as { envs: VercelEnvVar[] };\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${env.id}?teamId=${teamId}&decrypt=true`,\n\t\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t\t);\n\n\t\tif (response.ok) {\n\t\t\tdecrypted.push((await response.json()) as VercelEnvVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n */\nasync function createEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v10/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t\ttype: \"encrypted\",\n\t\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\n\t\tif (errorText.includes(\"ENV_CONFLICT\")) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel env var \"${key}\" already exists, will update instead`,\n\t\t\t);\n\n\t\t\tconst existing = await fetchEnvVars(token, teamId, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(token, teamId, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Vercel API error creating \"${key}\" (${response.status}): ${errorText}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelEnvVar;\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"PATCH\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ value }),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error updating env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Deletes a single Vercel env var.\n */\nasync function deleteEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t},\n\t);\n\n\tif (!response.ok && response.status !== 404) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error deleting env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n */\nclass VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst envIds: Record<string, string> = {};\n\n\t\tfor (const [key, value] of Object.entries(inputs.variables)) {\n\t\t\tconst result = await createEnvVar(\n\t\t\t\tinputs.token,\n\t\t\t\tinputs.teamId,\n\t\t\t\tinputs.projectId,\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t);\n\n\t\t\tenvIds[key] = result.id;\n\t\t}\n\n\t\treturn {\n\t\t\tid: `${inputs.projectId}:variables`,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(inputs.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst envIds = { ...olds.envIds };\n\n\t\tconst removedKeys = Object.keys(olds.variables).filter(\n\t\t\t(key) => !(key in news.variables),\n\t\t);\n\n\t\tfor (const key of removedKeys) {\n\t\t\tconst envId = envIds[key];\n\n\t\t\tif (envId) {\n\t\t\t\ttry {\n\t\t\t\t\tawait deleteEnvVar(news.token, news.teamId, news.projectId, envId);\n\t\t\t\t} catch {\n\t\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tdelete envIds[key];\n\t\t\t}\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(news.variables)) {\n\t\t\tconst existingId = envIds[key];\n\n\t\t\tif (existingId && olds.variables[key] !== value) {\n\t\t\t\tawait updateEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\texistingId,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\tkey,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\n\t\t\t\tenvIds[key] = result.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\touts: {\n\t\t\t\t...news,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(news.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelVariableOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst actual = await fetchEnvVars(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tprops.projectId,\n\t\t);\n\n\t\tconst actualVariables: Record<string, string> = {};\n\t\tconst actualEnvIds: Record<string, string> = {};\n\n\t\tfor (const [key] of Object.entries(props.variables)) {\n\t\t\tconst match = actual.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tactualVariables[key] = match.value;\n\t\t\t\tactualEnvIds[key] = match.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tvariables: actualVariables,\n\t\t\t\tenvIds: actualEnvIds,\n\t\t\t\tcontentHash: await hashVariables(actualVariables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: VercelVariableOutputs): Promise<void> {\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(props.token, props.teamId, props.projectId, envId);\n\t\t\t} catch {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst oldKeys = Object.keys(olds.variables).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.variables).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.variables).some(\n\t\t\t([key, value]) => olds.variables[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: oldKeys !== newKeys || valuesChanged,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelVariableResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, envIds: undefined, contentHash: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */\ntype VercelVariableOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelVariable. */\nexport interface VercelVariableArgs {\n\t/** Vercel project ID (Output from `vercel.Project`). */\n\tprojectId: pulumi.Input<string>;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n}\n\n/**\n * Manages Vercel project environment variables as a batch with drift detection.\n *\n * @example\n * ```typescript\n * new VercelVariable(\"nexus-vars\", {\n * projectId: vercelProject.id,\n * variables: {\n * NEXT_PUBLIC_API_URL: meshUrl,\n * },\n * }, { provider });\n * ```\n */\nexport class VercelVariable extends pulumi.ComponentResource {\n\t/** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */\n\tpublic readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelVariableArgs,\n\t\topts: VercelVariableOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Variable\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\tprojectId: args.projectId,\n\t\t\t\tvariables: args.variables,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.contentHash = resource.contentHash;\n\n\t\tthis.registerOutputs({ contentHash: this.contentHash });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"variable.cjs","names":["pulumi"],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProject } from \"./project.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\nexport interface VercelVariableInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Vercel project ID. */\n\tprojectId: string;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: Record<string, string>;\n}\n\n/** Persisted state for Vercel variables. */\ninterface VercelVariableOutputs extends VercelVariableInputs {\n\t/** Map of env var keys to their Vercel-assigned IDs (for updates and deletes). */\n\tenvIds: Record<string, string>;\n\n\t/** SHA-256 hash of all key-value pairs. Changes when any value is modified (including drift fixes). */\n\tcontentHash: string;\n}\n\n/**\n * Computes a deterministic content hash of a variables map.\n */\nasync function hashVariables(\n\tvariables: Record<string, string>,\n): Promise<string> {\n\tconst crypto = await import(\"node:crypto\");\n\tconst hash = crypto.createHash(\"sha256\");\n\n\tconst sorted = Object.entries(variables).sort(([a], [b]) =>\n\t\ta.localeCompare(b),\n\t);\n\n\tfor (const [key, value] of sorted) {\n\t\thash.update(key);\n\t\thash.update(value);\n\t}\n\n\treturn hash.digest(\"hex\");\n}\n\n/** Vercel API response for a single env var. */\ninterface VercelEnvVar {\n\tid: string;\n\tkey: string;\n\tvalue: string;\n\ttype: string;\n\ttarget: string[];\n}\n\n/**\n * Fetches all environment variables for a Vercel project with decrypted values.\n */\nasync function fetchEnvVars(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst listResponse = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!listResponse.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error (${listResponse.status}): ${await listResponse.text()}`,\n\t\t);\n\t}\n\n\tconst list = (await listResponse.json()) as { envs: VercelEnvVar[] };\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${env.id}?teamId=${teamId}&decrypt=true`,\n\t\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t\t);\n\n\t\tif (response.ok) {\n\t\t\tdecrypted.push((await response.json()) as VercelEnvVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n */\nasync function createEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v10/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t\ttype: \"encrypted\",\n\t\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\n\t\tif (errorText.includes(\"ENV_CONFLICT\")) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel env var \"${key}\" already exists, will update instead`,\n\t\t\t);\n\n\t\t\tconst existing = await fetchEnvVars(token, teamId, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(token, teamId, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Vercel API error creating \"${key}\" (${response.status}): ${errorText}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelEnvVar;\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"PATCH\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ value }),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error updating env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Deletes a single Vercel env var.\n */\nasync function deleteEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t},\n\t);\n\n\tif (!response.ok && response.status !== 404) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error deleting env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n */\nclass VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst envIds: Record<string, string> = {};\n\n\t\tfor (const [key, value] of Object.entries(inputs.variables)) {\n\t\t\tconst result = await createEnvVar(\n\t\t\t\tinputs.token,\n\t\t\t\tinputs.teamId,\n\t\t\t\tinputs.projectId,\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t);\n\n\t\t\tenvIds[key] = result.id;\n\t\t}\n\n\t\treturn {\n\t\t\tid: `${inputs.projectId}:variables`,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(inputs.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst envIds = { ...olds.envIds };\n\n\t\tconst removedKeys = Object.keys(olds.variables).filter(\n\t\t\t(key) => !(key in news.variables),\n\t\t);\n\n\t\tfor (const key of removedKeys) {\n\t\t\tconst envId = envIds[key];\n\n\t\t\tif (envId) {\n\t\t\t\ttry {\n\t\t\t\t\tawait deleteEnvVar(news.token, news.teamId, news.projectId, envId);\n\t\t\t\t} catch {\n\t\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tdelete envIds[key];\n\t\t\t}\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(news.variables)) {\n\t\t\tconst existingId = envIds[key];\n\n\t\t\tif (existingId && olds.variables[key] !== value) {\n\t\t\t\tawait updateEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\texistingId,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\tkey,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\n\t\t\t\tenvIds[key] = result.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\touts: {\n\t\t\t\t...news,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(news.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelVariableOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst actual = await fetchEnvVars(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tprops.projectId,\n\t\t);\n\n\t\tconst actualVariables: Record<string, string> = {};\n\t\tconst actualEnvIds: Record<string, string> = {};\n\n\t\tfor (const [key] of Object.entries(props.variables)) {\n\t\t\tconst match = actual.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tactualVariables[key] = match.value;\n\t\t\t\tactualEnvIds[key] = match.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tvariables: actualVariables,\n\t\t\t\tenvIds: actualEnvIds,\n\t\t\t\tcontentHash: await hashVariables(actualVariables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: VercelVariableOutputs): Promise<void> {\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(props.token, props.teamId, props.projectId, envId);\n\t\t\t} catch {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst oldKeys = Object.keys(olds.variables).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.variables).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.variables).some(\n\t\t\t([key, value]) => olds.variables[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: oldKeys !== newKeys || valuesChanged,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelVariableResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, envIds: undefined, contentHash: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */\ntype VercelVariableOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelVariable. */\nexport interface VercelVariableArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n}\n\n/**\n * Manages Vercel project environment variables as a batch with drift detection.\n *\n * @example\n * ```typescript\n * new VercelVariable(\"nexus-vars\", {\n * projectId: vercelProject.id,\n * variables: {\n * NEXT_PUBLIC_API_URL: meshUrl,\n * },\n * }, { provider });\n * ```\n */\nexport class VercelVariable extends pulumi.ComponentResource {\n\t/** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */\n\tpublic readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelVariableArgs,\n\t\topts: VercelVariableOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Variable\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelVariable: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst resource = new VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\tprojectId,\n\t\t\t\tvariables: args.variables,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.contentHash = resource.contentHash;\n\n\t\tthis.registerOutputs({ contentHash: this.contentHash });\n\t}\n}\n"],"mappings":";;;;;;AAIA,MAAM,iBAAiB;;;;AA6BvB,eAAe,cACd,WACkB;CAElB,MAAM,QAAO,MADQ,OAAO,gBACR,WAAW,QAAQ;CAEvC,MAAM,SAAS,OAAO,QAAQ,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OACpD,EAAE,cAAc,CAAC,CAClB;CAEA,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ;EAClC,KAAK,OAAO,GAAG;EACf,KAAK,OAAO,KAAK;CAClB;CAEA,OAAO,KAAK,OAAO,KAAK;AACzB;;;;AAcA,eAAe,aACd,OACA,QACA,WAC0B;CAC1B,MAAM,eAAe,MAAM,MAC1B,GAAG,eAAe,eAAe,UAAU,cAAc,UACzD,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;CAEA,IAAI,CAAC,aAAa,IACjB,MAAM,IAAI,MACT,qBAAqB,aAAa,OAAO,KAAK,MAAM,aAAa,KAAK,GACvE;CAGD,MAAM,OAAQ,MAAM,aAAa,KAAK;CAEtC,MAAM,YAA4B,CAAC;CAEnC,KAAK,MAAM,OAAO,KAAK,MAAM;EAC5B,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,IAAI,GAAG,UAAU,OAAO,gBAC1E,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;EAEA,IAAI,SAAS,IACZ,UAAU,KAAM,MAAM,SAAS,KAAK,CAAkB;CAExD;CAEA,OAAO;AACR;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,KACA,OACwB;CACxB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,gBAAgB,UAAU,cAAc,UAC1D;EACC,QAAQ;EACR,SAAS;GACR,eAAe,UAAU;GACzB,gBAAgB;EACjB;EACA,MAAM,KAAK,UAAU;GACpB;GACA;GACA,MAAM;GACN,QAAQ;IAAC;IAAc;IAAW;GAAa;EAChD,CAAC;CACF,CACD;CAEA,IAAI,CAAC,SAAS,IAAI;EACjB,MAAM,YAAY,MAAM,SAAS,KAAK;EAEtC,IAAI,UAAU,SAAS,cAAc,GAAG;GACvC,eAAO,IAAI,KACV,mBAAmB,IAAI,sCACxB;GAGA,MAAM,SAAQ,MADS,aAAa,OAAO,QAAQ,SAAS,GACrC,MAAM,MAAM,EAAE,QAAQ,GAAG;GAEhD,IAAI,OAAO;IACV,MAAM,aAAa,OAAO,QAAQ,WAAW,MAAM,IAAI,KAAK;IAE5D,OAAO;KAAE,GAAG;KAAO;IAAM;GAC1B;EACD;EAEA,MAAM,IAAI,MACT,8BAA8B,IAAI,KAAK,SAAS,OAAO,KAAK,WAC7D;CACD;CAEA,OAAQ,MAAM,SAAS,KAAK;AAC7B;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,OACA,OACgB;CAChB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,MAAM,UAAU,UAClE;EACC,QAAQ;EACR,SAAS;GACR,eAAe,UAAU;GACzB,gBAAgB;EACjB;EACA,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;CAC/B,CACD;CAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,sCAAsC,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAChF;AAEF;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,OACgB;CAChB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,MAAM,UAAU,UAClE;EACC,QAAQ;EACR,SAAS,EAAE,eAAe,UAAU,QAAQ;CAC7C,CACD;CAEA,IAAI,CAAC,SAAS,MAAM,SAAS,WAAW,KACvC,MAAM,IAAI,MACT,sCAAsC,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAChF;AAEF;;;;AAKA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAiC,CAAC;EAExC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,SAAS,GASzD,OAAO,QAAO,MARO,aACpB,OAAO,OACP,OAAO,QACP,OAAO,WACP,KACA,KACD,GAEqB;EAGtB,OAAO;GACN,IAAI,GAAG,OAAO,UAAU;GACxB,MAAM;IACL,GAAG;IACH;IACA,aAAa,MAAM,cAAc,OAAO,SAAS;GAClD;EACD;CACD;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,EAAE,GAAG,KAAK,OAAO;EAEhC,MAAM,cAAc,OAAO,KAAK,KAAK,SAAS,EAAE,QAC9C,QAAQ,EAAE,OAAO,KAAK,UACxB;EAEA,KAAK,MAAM,OAAO,aAAa;GAC9B,MAAM,QAAQ,OAAO;GAErB,IAAI,OAAO;IACV,IAAI;KACH,MAAM,aAAa,KAAK,OAAO,KAAK,QAAQ,KAAK,WAAW,KAAK;IAClE,QAAQ;KACP,eAAO,IAAI,KACV,oCAAoC,IAAI,2BACzC;IACD;IAEA,OAAO,OAAO;GACf;EACD;EAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,SAAS,GAAG;GAC1D,MAAM,aAAa,OAAO;GAE1B,IAAI,cAAc,KAAK,UAAU,SAAS,OACzC,MAAM,aACL,KAAK,OACL,KAAK,QACL,KAAK,WACL,YACA,KACD;QACM,IAAI,CAAC,YASX,OAAO,QAAO,MARO,aACpB,KAAK,OACL,KAAK,QACL,KAAK,WACL,KACA,KACD,GAEqB;EAEvB;EAEA,OAAO,EACN,MAAM;GACL,GAAG;GACH;GACA,aAAa,MAAM,cAAc,KAAK,SAAS;EAChD,EACD;CACD;CAEA,MAAM,KACL,IACA,OACqC;EACrC,MAAM,SAAS,MAAM,aACpB,MAAM,OACN,MAAM,QACN,MAAM,SACP;EAEA,MAAM,kBAA0C,CAAC;EACjD,MAAM,eAAuC,CAAC;EAE9C,KAAK,MAAM,CAAC,QAAQ,OAAO,QAAQ,MAAM,SAAS,GAAG;GACpD,MAAM,QAAQ,OAAO,MAAM,MAAM,EAAE,QAAQ,GAAG;GAE9C,IAAI,OAAO;IACV,gBAAgB,OAAO,MAAM;IAC7B,aAAa,OAAO,MAAM;GAC3B;EACD;EAEA,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,WAAW;IACX,QAAQ;IACR,aAAa,MAAM,cAAc,eAAe;GACjD;EACD;CACD;CAEA,MAAM,OAAO,KAAa,OAA6C;EACtE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,MAAM,GACrD,IAAI;GACH,MAAM,aAAa,MAAM,OAAO,MAAM,QAAQ,MAAM,WAAW,KAAK;EACrE,QAAQ;GACP,eAAO,IAAI,KACV,oCAAoC,IAAI,2BACzC;EACD;CAEF;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG;EAC3D,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG;EAE3D,MAAM,gBAAgB,OAAO,QAAQ,KAAK,SAAS,EAAE,MACnD,CAAC,KAAK,WAAW,KAAK,UAAU,SAAS,KAC3C;EAEA,OAAO,EACN,SAAS,YAAY,WAAW,cACjC;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqCA,eAAO,QAAQ,SAAS;CAG5D,YACC,MACA,MAMA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,QAAQ;GAAW,aAAa;EAAU,GACrD,IACD;CACD;AACD;;;;;;;;;;;;;;AA0CA,IAAa,iBAAb,cAAoCA,eAAO,kBAAkB;CAI5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,YAAY,UACf,QAAQ,KACP,KAAK;EAET,IAAI,CAAC,WACJ,MAAM,IAAI,MACT,4EACD;EAGD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB;GACA,WAAW,KAAK;EACjB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,cAAc,SAAS;EAE5B,KAAK,gBAAgB,EAAE,aAAa,KAAK,YAAY,CAAC;CACvD;AACD"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.cjs";
|
|
2
2
|
import { VercelProvider } from "./provider.cjs";
|
|
3
|
+
import { VercelProject } from "./project.cjs";
|
|
3
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
4
5
|
|
|
5
6
|
//#region src/vercel/variable.d.ts
|
|
@@ -17,11 +18,19 @@ interface VercelVariableInputs {
|
|
|
17
18
|
/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */
|
|
18
19
|
type VercelVariableOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
19
20
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
21
|
+
/**
|
|
22
|
+
* VercelProject resource to source the project ID from.
|
|
23
|
+
* When provided, `args.projectId` is optional and ignored if both are given.
|
|
24
|
+
*/
|
|
25
|
+
project?: VercelProject;
|
|
20
26
|
};
|
|
21
27
|
/** Args for VercelVariable. */
|
|
22
28
|
interface VercelVariableArgs {
|
|
23
|
-
/**
|
|
24
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Vercel project ID.
|
|
31
|
+
* Required when `opts.project` is not provided.
|
|
32
|
+
*/
|
|
33
|
+
projectId?: pulumi.Input<string>;
|
|
25
34
|
/** Key-value map of environment variable names to their values. */
|
|
26
35
|
variables: pulumi.Input<Record<string, pulumi.Input<string>>>;
|
|
27
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variable.d.cts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"variable.d.cts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAOiB,oBAAA;;EAEhB,KAAA;EAFoC;EAKpC,MAAA;EAMiB;EAHjB,SAAA;EAHA;EAMA,SAAA,EAAW,MAAM;AAAA;;KA6Wb,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EA9WU,qCAkXjB,QAAA,EAAU,cAAA;EALe;;;;EAWzB,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EAfY;;;;EAoB5B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;EAYA,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAZvB;AAIxB;;;;;;;;;;;;AAJwB,cA4BX,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAhB/B;EAAA,SAkBK,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
2
|
import { VercelProvider } from "./provider.mjs";
|
|
3
|
+
import { VercelProject } from "./project.mjs";
|
|
3
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
4
5
|
|
|
5
6
|
//#region src/vercel/variable.d.ts
|
|
@@ -17,11 +18,19 @@ interface VercelVariableInputs {
|
|
|
17
18
|
/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */
|
|
18
19
|
type VercelVariableOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
19
20
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
21
|
+
/**
|
|
22
|
+
* VercelProject resource to source the project ID from.
|
|
23
|
+
* When provided, `args.projectId` is optional and ignored if both are given.
|
|
24
|
+
*/
|
|
25
|
+
project?: VercelProject;
|
|
20
26
|
};
|
|
21
27
|
/** Args for VercelVariable. */
|
|
22
28
|
interface VercelVariableArgs {
|
|
23
|
-
/**
|
|
24
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Vercel project ID.
|
|
31
|
+
* Required when `opts.project` is not provided.
|
|
32
|
+
*/
|
|
33
|
+
projectId?: pulumi.Input<string>;
|
|
25
34
|
/** Key-value map of environment variable names to their values. */
|
|
26
35
|
variables: pulumi.Input<Record<string, pulumi.Input<string>>>;
|
|
27
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variable.d.mts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"variable.d.mts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAOiB,oBAAA;;EAEhB,KAAA;EAFoC;EAKpC,MAAA;EAMiB;EAHjB,SAAA;EAHA;EAMA,SAAA,EAAW,MAAM;AAAA;;KA6Wb,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EA9WU,qCAkXjB,QAAA,EAAU,cAAA;EALe;;;;EAWzB,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EAfY;;;;EAoB5B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;EAYA,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAZvB;AAIxB;;;;;;;;;;;;AAJwB,cA4BX,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAhB/B;EAAA,SAkBK,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
|
package/dist/vercel/variable.mjs
CHANGED
|
@@ -192,12 +192,14 @@ var VercelVariableResource = class extends pulumi.dynamic.Resource {
|
|
|
192
192
|
*/
|
|
193
193
|
var VercelVariable = class extends pulumi.ComponentResource {
|
|
194
194
|
constructor(name, args, opts) {
|
|
195
|
-
const { provider, ...pulumiOpts } = opts;
|
|
195
|
+
const { provider, project, ...pulumiOpts } = opts;
|
|
196
196
|
super("infracraft:vercel:Variable", name, {}, pulumiOpts);
|
|
197
|
+
const projectId = project ? project.id : args.projectId;
|
|
198
|
+
if (!projectId) throw new Error("VercelVariable: either `args.projectId` or `opts.project` must be provided");
|
|
197
199
|
const resource = new VercelVariableResource(`${name}-resource`, {
|
|
198
200
|
token: provider.token,
|
|
199
201
|
teamId: provider.teamId,
|
|
200
|
-
projectId
|
|
202
|
+
projectId,
|
|
201
203
|
variables: args.variables
|
|
202
204
|
}, { parent: this });
|
|
203
205
|
this.contentHash = resource.contentHash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variable.mjs","names":[],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\nexport interface VercelVariableInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Vercel project ID. */\n\tprojectId: string;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: Record<string, string>;\n}\n\n/** Persisted state for Vercel variables. */\ninterface VercelVariableOutputs extends VercelVariableInputs {\n\t/** Map of env var keys to their Vercel-assigned IDs (for updates and deletes). */\n\tenvIds: Record<string, string>;\n\n\t/** SHA-256 hash of all key-value pairs. Changes when any value is modified (including drift fixes). */\n\tcontentHash: string;\n}\n\n/**\n * Computes a deterministic content hash of a variables map.\n */\nasync function hashVariables(\n\tvariables: Record<string, string>,\n): Promise<string> {\n\tconst crypto = await import(\"node:crypto\");\n\tconst hash = crypto.createHash(\"sha256\");\n\n\tconst sorted = Object.entries(variables).sort(([a], [b]) =>\n\t\ta.localeCompare(b),\n\t);\n\n\tfor (const [key, value] of sorted) {\n\t\thash.update(key);\n\t\thash.update(value);\n\t}\n\n\treturn hash.digest(\"hex\");\n}\n\n/** Vercel API response for a single env var. */\ninterface VercelEnvVar {\n\tid: string;\n\tkey: string;\n\tvalue: string;\n\ttype: string;\n\ttarget: string[];\n}\n\n/**\n * Fetches all environment variables for a Vercel project with decrypted values.\n */\nasync function fetchEnvVars(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst listResponse = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!listResponse.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error (${listResponse.status}): ${await listResponse.text()}`,\n\t\t);\n\t}\n\n\tconst list = (await listResponse.json()) as { envs: VercelEnvVar[] };\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${env.id}?teamId=${teamId}&decrypt=true`,\n\t\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t\t);\n\n\t\tif (response.ok) {\n\t\t\tdecrypted.push((await response.json()) as VercelEnvVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n */\nasync function createEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v10/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t\ttype: \"encrypted\",\n\t\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\n\t\tif (errorText.includes(\"ENV_CONFLICT\")) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel env var \"${key}\" already exists, will update instead`,\n\t\t\t);\n\n\t\t\tconst existing = await fetchEnvVars(token, teamId, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(token, teamId, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Vercel API error creating \"${key}\" (${response.status}): ${errorText}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelEnvVar;\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"PATCH\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ value }),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error updating env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Deletes a single Vercel env var.\n */\nasync function deleteEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t},\n\t);\n\n\tif (!response.ok && response.status !== 404) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error deleting env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n */\nclass VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst envIds: Record<string, string> = {};\n\n\t\tfor (const [key, value] of Object.entries(inputs.variables)) {\n\t\t\tconst result = await createEnvVar(\n\t\t\t\tinputs.token,\n\t\t\t\tinputs.teamId,\n\t\t\t\tinputs.projectId,\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t);\n\n\t\t\tenvIds[key] = result.id;\n\t\t}\n\n\t\treturn {\n\t\t\tid: `${inputs.projectId}:variables`,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(inputs.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst envIds = { ...olds.envIds };\n\n\t\tconst removedKeys = Object.keys(olds.variables).filter(\n\t\t\t(key) => !(key in news.variables),\n\t\t);\n\n\t\tfor (const key of removedKeys) {\n\t\t\tconst envId = envIds[key];\n\n\t\t\tif (envId) {\n\t\t\t\ttry {\n\t\t\t\t\tawait deleteEnvVar(news.token, news.teamId, news.projectId, envId);\n\t\t\t\t} catch {\n\t\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tdelete envIds[key];\n\t\t\t}\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(news.variables)) {\n\t\t\tconst existingId = envIds[key];\n\n\t\t\tif (existingId && olds.variables[key] !== value) {\n\t\t\t\tawait updateEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\texistingId,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\tkey,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\n\t\t\t\tenvIds[key] = result.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\touts: {\n\t\t\t\t...news,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(news.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelVariableOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst actual = await fetchEnvVars(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tprops.projectId,\n\t\t);\n\n\t\tconst actualVariables: Record<string, string> = {};\n\t\tconst actualEnvIds: Record<string, string> = {};\n\n\t\tfor (const [key] of Object.entries(props.variables)) {\n\t\t\tconst match = actual.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tactualVariables[key] = match.value;\n\t\t\t\tactualEnvIds[key] = match.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tvariables: actualVariables,\n\t\t\t\tenvIds: actualEnvIds,\n\t\t\t\tcontentHash: await hashVariables(actualVariables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: VercelVariableOutputs): Promise<void> {\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(props.token, props.teamId, props.projectId, envId);\n\t\t\t} catch {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst oldKeys = Object.keys(olds.variables).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.variables).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.variables).some(\n\t\t\t([key, value]) => olds.variables[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: oldKeys !== newKeys || valuesChanged,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelVariableResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, envIds: undefined, contentHash: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */\ntype VercelVariableOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelVariable. */\nexport interface VercelVariableArgs {\n\t/** Vercel project ID (Output from `vercel.Project`). */\n\tprojectId: pulumi.Input<string>;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n}\n\n/**\n * Manages Vercel project environment variables as a batch with drift detection.\n *\n * @example\n * ```typescript\n * new VercelVariable(\"nexus-vars\", {\n * projectId: vercelProject.id,\n * variables: {\n * NEXT_PUBLIC_API_URL: meshUrl,\n * },\n * }, { provider });\n * ```\n */\nexport class VercelVariable extends pulumi.ComponentResource {\n\t/** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */\n\tpublic readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelVariableArgs,\n\t\topts: VercelVariableOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Variable\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\tprojectId: args.projectId,\n\t\t\t\tvariables: args.variables,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.contentHash = resource.contentHash;\n\n\t\tthis.registerOutputs({ contentHash: this.contentHash });\n\t}\n}\n"],"mappings":";;;;AAGA,MAAM,iBAAiB;;;;AA6BvB,eAAe,cACd,WACkB;CAElB,MAAM,QAAO,MADQ,OAAO,gBACR,WAAW,QAAQ;CAEvC,MAAM,SAAS,OAAO,QAAQ,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,OACpD,EAAE,cAAc,CAAC,CAClB;CAEA,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ;EAClC,KAAK,OAAO,GAAG;EACf,KAAK,OAAO,KAAK;CAClB;CAEA,OAAO,KAAK,OAAO,KAAK;AACzB;;;;AAcA,eAAe,aACd,OACA,QACA,WAC0B;CAC1B,MAAM,eAAe,MAAM,MAC1B,GAAG,eAAe,eAAe,UAAU,cAAc,UACzD,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;CAEA,IAAI,CAAC,aAAa,IACjB,MAAM,IAAI,MACT,qBAAqB,aAAa,OAAO,KAAK,MAAM,aAAa,KAAK,GACvE;CAGD,MAAM,OAAQ,MAAM,aAAa,KAAK;CAEtC,MAAM,YAA4B,CAAC;CAEnC,KAAK,MAAM,OAAO,KAAK,MAAM;EAC5B,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,IAAI,GAAG,UAAU,OAAO,gBAC1E,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;EAEA,IAAI,SAAS,IACZ,UAAU,KAAM,MAAM,SAAS,KAAK,CAAkB;CAExD;CAEA,OAAO;AACR;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,KACA,OACwB;CACxB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,gBAAgB,UAAU,cAAc,UAC1D;EACC,QAAQ;EACR,SAAS;GACR,eAAe,UAAU;GACzB,gBAAgB;EACjB;EACA,MAAM,KAAK,UAAU;GACpB;GACA;GACA,MAAM;GACN,QAAQ;IAAC;IAAc;IAAW;GAAa;EAChD,CAAC;CACF,CACD;CAEA,IAAI,CAAC,SAAS,IAAI;EACjB,MAAM,YAAY,MAAM,SAAS,KAAK;EAEtC,IAAI,UAAU,SAAS,cAAc,GAAG;GACvC,OAAO,IAAI,KACV,mBAAmB,IAAI,sCACxB;GAGA,MAAM,SAAQ,MADS,aAAa,OAAO,QAAQ,SAAS,GACrC,MAAM,MAAM,EAAE,QAAQ,GAAG;GAEhD,IAAI,OAAO;IACV,MAAM,aAAa,OAAO,QAAQ,WAAW,MAAM,IAAI,KAAK;IAE5D,OAAO;KAAE,GAAG;KAAO;IAAM;GAC1B;EACD;EAEA,MAAM,IAAI,MACT,8BAA8B,IAAI,KAAK,SAAS,OAAO,KAAK,WAC7D;CACD;CAEA,OAAQ,MAAM,SAAS,KAAK;AAC7B;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,OACA,OACgB;CAChB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,MAAM,UAAU,UAClE;EACC,QAAQ;EACR,SAAS;GACR,eAAe,UAAU;GACzB,gBAAgB;EACjB;EACA,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;CAC/B,CACD;CAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,sCAAsC,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAChF;AAEF;;;;AAKA,eAAe,aACd,OACA,QACA,WACA,OACgB;CAChB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,UAAU,OAAO,MAAM,UAAU,UAClE;EACC,QAAQ;EACR,SAAS,EAAE,eAAe,UAAU,QAAQ;CAC7C,CACD;CAEA,IAAI,CAAC,SAAS,MAAM,SAAS,WAAW,KACvC,MAAM,IAAI,MACT,sCAAsC,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAChF;AAEF;;;;AAKA,IAAM,iCAAN,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAiC,CAAC;EAExC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,SAAS,GASzD,OAAO,QAAO,MARO,aACpB,OAAO,OACP,OAAO,QACP,OAAO,WACP,KACA,KACD,GAEqB;EAGtB,OAAO;GACN,IAAI,GAAG,OAAO,UAAU;GACxB,MAAM;IACL,GAAG;IACH;IACA,aAAa,MAAM,cAAc,OAAO,SAAS;GAClD;EACD;CACD;CAEA,MAAM,OACL,KACA,MACA,MACuC;EACvC,MAAM,SAAS,EAAE,GAAG,KAAK,OAAO;EAEhC,MAAM,cAAc,OAAO,KAAK,KAAK,SAAS,EAAE,QAC9C,QAAQ,EAAE,OAAO,KAAK,UACxB;EAEA,KAAK,MAAM,OAAO,aAAa;GAC9B,MAAM,QAAQ,OAAO;GAErB,IAAI,OAAO;IACV,IAAI;KACH,MAAM,aAAa,KAAK,OAAO,KAAK,QAAQ,KAAK,WAAW,KAAK;IAClE,QAAQ;KACP,OAAO,IAAI,KACV,oCAAoC,IAAI,2BACzC;IACD;IAEA,OAAO,OAAO;GACf;EACD;EAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,SAAS,GAAG;GAC1D,MAAM,aAAa,OAAO;GAE1B,IAAI,cAAc,KAAK,UAAU,SAAS,OACzC,MAAM,aACL,KAAK,OACL,KAAK,QACL,KAAK,WACL,YACA,KACD;QACM,IAAI,CAAC,YASX,OAAO,QAAO,MARO,aACpB,KAAK,OACL,KAAK,QACL,KAAK,WACL,KACA,KACD,GAEqB;EAEvB;EAEA,OAAO,EACN,MAAM;GACL,GAAG;GACH;GACA,aAAa,MAAM,cAAc,KAAK,SAAS;EAChD,EACD;CACD;CAEA,MAAM,KACL,IACA,OACqC;EACrC,MAAM,SAAS,MAAM,aACpB,MAAM,OACN,MAAM,QACN,MAAM,SACP;EAEA,MAAM,kBAA0C,CAAC;EACjD,MAAM,eAAuC,CAAC;EAE9C,KAAK,MAAM,CAAC,QAAQ,OAAO,QAAQ,MAAM,SAAS,GAAG;GACpD,MAAM,QAAQ,OAAO,MAAM,MAAM,EAAE,QAAQ,GAAG;GAE9C,IAAI,OAAO;IACV,gBAAgB,OAAO,MAAM;IAC7B,aAAa,OAAO,MAAM;GAC3B;EACD;EAEA,OAAO;GACN;GACA,OAAO;IACN,GAAG;IACH,WAAW;IACX,QAAQ;IACR,aAAa,MAAM,cAAc,eAAe;GACjD;EACD;CACD;CAEA,MAAM,OAAO,KAAa,OAA6C;EACtE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,MAAM,GACrD,IAAI;GACH,MAAM,aAAa,MAAM,OAAO,MAAM,QAAQ,MAAM,WAAW,KAAK;EACrE,QAAQ;GACP,OAAO,IAAI,KACV,oCAAoC,IAAI,2BACzC;EACD;CAEF;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG;EAC3D,MAAM,UAAU,OAAO,KAAK,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG;EAE3D,MAAM,gBAAgB,OAAO,QAAQ,KAAK,SAAS,EAAE,MACnD,CAAC,KAAK,WAAW,KAAK,UAAU,SAAS,KAC3C;EAEA,OAAO,EACN,SAAS,YAAY,WAAW,cACjC;CACD;AACD;;AAGA,IAAM,yBAAN,cAAqC,OAAO,QAAQ,SAAS;CAG5D,YACC,MACA,MAMA,MACC;EACD,MACC,IAAI,+BAA+B,GACnC,MACA;GAAE,GAAG;GAAM,QAAQ;GAAW,aAAa;EAAU,GACrD,IACD;CACD;AACD;;;;;;;;;;;;;;AAiCA,IAAa,iBAAb,cAAoC,OAAO,kBAAkB;CAI5D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,8BAA8B,MAAM,CAAC,GAAG,UAAU;EAExD,MAAM,WAAW,IAAI,uBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,WAAW,KAAK;GAChB,WAAW,KAAK;EACjB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,cAAc,SAAS;EAE5B,KAAK,gBAAgB,EAAE,aAAa,KAAK,YAAY,CAAC;CACvD;AACD"}
|
|
1
|
+
{"version":3,"file":"variable.mjs","names":[],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProject } from \"./project.js\";\nimport type { VercelProvider } from \"./provider.js\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\nexport interface VercelVariableInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Vercel project ID. */\n\tprojectId: string;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: Record<string, string>;\n}\n\n/** Persisted state for Vercel variables. */\ninterface VercelVariableOutputs extends VercelVariableInputs {\n\t/** Map of env var keys to their Vercel-assigned IDs (for updates and deletes). */\n\tenvIds: Record<string, string>;\n\n\t/** SHA-256 hash of all key-value pairs. Changes when any value is modified (including drift fixes). */\n\tcontentHash: string;\n}\n\n/**\n * Computes a deterministic content hash of a variables map.\n */\nasync function hashVariables(\n\tvariables: Record<string, string>,\n): Promise<string> {\n\tconst crypto = await import(\"node:crypto\");\n\tconst hash = crypto.createHash(\"sha256\");\n\n\tconst sorted = Object.entries(variables).sort(([a], [b]) =>\n\t\ta.localeCompare(b),\n\t);\n\n\tfor (const [key, value] of sorted) {\n\t\thash.update(key);\n\t\thash.update(value);\n\t}\n\n\treturn hash.digest(\"hex\");\n}\n\n/** Vercel API response for a single env var. */\ninterface VercelEnvVar {\n\tid: string;\n\tkey: string;\n\tvalue: string;\n\ttype: string;\n\ttarget: string[];\n}\n\n/**\n * Fetches all environment variables for a Vercel project with decrypted values.\n */\nasync function fetchEnvVars(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst listResponse = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!listResponse.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error (${listResponse.status}): ${await listResponse.text()}`,\n\t\t);\n\t}\n\n\tconst list = (await listResponse.json()) as { envs: VercelEnvVar[] };\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${env.id}?teamId=${teamId}&decrypt=true`,\n\t\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t\t);\n\n\t\tif (response.ok) {\n\t\t\tdecrypted.push((await response.json()) as VercelEnvVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n */\nasync function createEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v10/projects/${projectId}/env?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t\ttype: \"encrypted\",\n\t\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\n\t\tif (errorText.includes(\"ENV_CONFLICT\")) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel env var \"${key}\" already exists, will update instead`,\n\t\t\t);\n\n\t\t\tconst existing = await fetchEnvVars(token, teamId, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(token, teamId, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Vercel API error creating \"${key}\" (${response.status}): ${errorText}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelEnvVar;\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"PATCH\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({ value }),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error updating env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Deletes a single Vercel env var.\n */\nasync function deleteEnvVar(\n\ttoken: string,\n\tteamId: string,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t},\n\t);\n\n\tif (!response.ok && response.status !== 404) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error deleting env var (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n */\nclass VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst envIds: Record<string, string> = {};\n\n\t\tfor (const [key, value] of Object.entries(inputs.variables)) {\n\t\t\tconst result = await createEnvVar(\n\t\t\t\tinputs.token,\n\t\t\t\tinputs.teamId,\n\t\t\t\tinputs.projectId,\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t);\n\n\t\t\tenvIds[key] = result.id;\n\t\t}\n\n\t\treturn {\n\t\t\tid: `${inputs.projectId}:variables`,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(inputs.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst envIds = { ...olds.envIds };\n\n\t\tconst removedKeys = Object.keys(olds.variables).filter(\n\t\t\t(key) => !(key in news.variables),\n\t\t);\n\n\t\tfor (const key of removedKeys) {\n\t\t\tconst envId = envIds[key];\n\n\t\t\tif (envId) {\n\t\t\t\ttry {\n\t\t\t\t\tawait deleteEnvVar(news.token, news.teamId, news.projectId, envId);\n\t\t\t\t} catch {\n\t\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tdelete envIds[key];\n\t\t\t}\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(news.variables)) {\n\t\t\tconst existingId = envIds[key];\n\n\t\t\tif (existingId && olds.variables[key] !== value) {\n\t\t\t\tawait updateEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\texistingId,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(\n\t\t\t\t\tnews.token,\n\t\t\t\t\tnews.teamId,\n\t\t\t\t\tnews.projectId,\n\t\t\t\t\tkey,\n\t\t\t\t\tvalue,\n\t\t\t\t);\n\n\t\t\t\tenvIds[key] = result.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\touts: {\n\t\t\t\t...news,\n\t\t\t\tenvIds,\n\t\t\t\tcontentHash: await hashVariables(news.variables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelVariableOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst actual = await fetchEnvVars(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tprops.projectId,\n\t\t);\n\n\t\tconst actualVariables: Record<string, string> = {};\n\t\tconst actualEnvIds: Record<string, string> = {};\n\n\t\tfor (const [key] of Object.entries(props.variables)) {\n\t\t\tconst match = actual.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tactualVariables[key] = match.value;\n\t\t\t\tactualEnvIds[key] = match.id;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tvariables: actualVariables,\n\t\t\t\tenvIds: actualEnvIds,\n\t\t\t\tcontentHash: await hashVariables(actualVariables),\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(_id: string, props: VercelVariableOutputs): Promise<void> {\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(props.token, props.teamId, props.projectId, envId);\n\t\t\t} catch {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Failed to delete Vercel env var \"${key}\" (may already be deleted)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelVariableOutputs,\n\t\tnews: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst oldKeys = Object.keys(olds.variables).sort().join(\",\");\n\t\tconst newKeys = Object.keys(news.variables).sort().join(\",\");\n\n\t\tconst valuesChanged = Object.entries(news.variables).some(\n\t\t\t([key, value]) => olds.variables[key] !== value,\n\t\t);\n\n\t\treturn {\n\t\t\tchanges: oldKeys !== newKeys || valuesChanged,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelVariableResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, envIds: undefined, contentHash: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelVariable — replaces Pulumi's native `provider` field. */\ntype VercelVariableOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelVariable. */\nexport interface VercelVariableArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/** Key-value map of environment variable names to their values. */\n\tvariables: pulumi.Input<Record<string, pulumi.Input<string>>>;\n}\n\n/**\n * Manages Vercel project environment variables as a batch with drift detection.\n *\n * @example\n * ```typescript\n * new VercelVariable(\"nexus-vars\", {\n * projectId: vercelProject.id,\n * variables: {\n * NEXT_PUBLIC_API_URL: meshUrl,\n * },\n * }, { provider });\n * ```\n */\nexport class VercelVariable extends pulumi.ComponentResource {\n\t/** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */\n\tpublic readonly contentHash: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelVariableArgs,\n\t\topts: VercelVariableOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Variable\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelVariable: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst resource = new VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\tprojectId,\n\t\t\t\tvariables: args.variables,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.contentHash = resource.contentHash;\n\n\t\tthis.registerOutputs({ contentHash: this.contentHash });\n\t}\n}\n"],"mappings":"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"}
|