@infracraft/pulumi 0.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.
Potentially problematic release.
This version of @infracraft/pulumi might be problematic. Click here for more details.
- package/dist/chunk-BVYJZCqc.cjs +44 -0
- package/dist/chunk-OPjESj5l.mjs +9 -0
- package/dist/git-guard.cjs +62 -0
- package/dist/git-guard.cjs.map +1 -0
- package/dist/git-guard.d.cts +13 -0
- package/dist/git-guard.d.cts.map +1 -0
- package/dist/git-guard.d.mts +13 -0
- package/dist/git-guard.d.mts.map +1 -0
- package/dist/git-guard.mjs +56 -0
- package/dist/git-guard.mjs.map +1 -0
- package/dist/hash.cjs +40 -0
- package/dist/hash.cjs.map +1 -0
- package/dist/hash.d.cts +10 -0
- package/dist/hash.d.cts.map +1 -0
- package/dist/hash.d.mts +10 -0
- package/dist/hash.d.mts.map +1 -0
- package/dist/hash.mjs +36 -0
- package/dist/hash.mjs.map +1 -0
- package/dist/neon/branch.cjs +113 -0
- package/dist/neon/branch.cjs.map +1 -0
- package/dist/neon/branch.d.cts +40 -0
- package/dist/neon/branch.d.cts.map +1 -0
- package/dist/neon/branch.d.mts +40 -0
- package/dist/neon/branch.d.mts.map +1 -0
- package/dist/neon/branch.mjs +111 -0
- package/dist/neon/branch.mjs.map +1 -0
- package/dist/neon/client.cjs +85 -0
- package/dist/neon/client.cjs.map +1 -0
- package/dist/neon/client.d.cts +59 -0
- package/dist/neon/client.d.cts.map +1 -0
- package/dist/neon/client.d.mts +59 -0
- package/dist/neon/client.d.mts.map +1 -0
- package/dist/neon/client.mjs +84 -0
- package/dist/neon/client.mjs.map +1 -0
- package/dist/neon/database.cjs +117 -0
- package/dist/neon/database.cjs.map +1 -0
- package/dist/neon/database.d.cts +48 -0
- package/dist/neon/database.d.cts.map +1 -0
- package/dist/neon/database.d.mts +48 -0
- package/dist/neon/database.d.mts.map +1 -0
- package/dist/neon/database.mjs +115 -0
- package/dist/neon/database.mjs.map +1 -0
- package/dist/neon/endpoint.cjs +167 -0
- package/dist/neon/endpoint.cjs.map +1 -0
- package/dist/neon/endpoint.d.cts +57 -0
- package/dist/neon/endpoint.d.cts.map +1 -0
- package/dist/neon/endpoint.d.mts +57 -0
- package/dist/neon/endpoint.d.mts.map +1 -0
- package/dist/neon/endpoint.mjs +165 -0
- package/dist/neon/endpoint.mjs.map +1 -0
- package/dist/neon/index.cjs +14 -0
- package/dist/neon/index.d.cts +7 -0
- package/dist/neon/index.d.mts +7 -0
- package/dist/neon/index.mjs +8 -0
- package/dist/neon/project.cjs +120 -0
- package/dist/neon/project.cjs.map +1 -0
- package/dist/neon/project.d.cts +52 -0
- package/dist/neon/project.d.cts.map +1 -0
- package/dist/neon/project.d.mts +52 -0
- package/dist/neon/project.d.mts.map +1 -0
- package/dist/neon/project.mjs +118 -0
- package/dist/neon/project.mjs.map +1 -0
- package/dist/neon/role.cjs +135 -0
- package/dist/neon/role.cjs.map +1 -0
- package/dist/neon/role.d.cts +49 -0
- package/dist/neon/role.d.cts.map +1 -0
- package/dist/neon/role.d.mts +49 -0
- package/dist/neon/role.d.mts.map +1 -0
- package/dist/neon/role.mjs +133 -0
- package/dist/neon/role.mjs.map +1 -0
- package/dist/railway/client.cjs +59 -0
- package/dist/railway/client.cjs.map +1 -0
- package/dist/railway/client.d.cts +39 -0
- package/dist/railway/client.d.cts.map +1 -0
- package/dist/railway/client.d.mts +39 -0
- package/dist/railway/client.d.mts.map +1 -0
- package/dist/railway/client.mjs +58 -0
- package/dist/railway/client.mjs.map +1 -0
- package/dist/railway/deploy.cjs +40 -0
- package/dist/railway/deploy.cjs.map +1 -0
- package/dist/railway/deploy.d.cts +47 -0
- package/dist/railway/deploy.d.cts.map +1 -0
- package/dist/railway/deploy.d.mts +47 -0
- package/dist/railway/deploy.d.mts.map +1 -0
- package/dist/railway/deploy.mjs +37 -0
- package/dist/railway/deploy.mjs.map +1 -0
- package/dist/railway/domain.cjs +236 -0
- package/dist/railway/domain.cjs.map +1 -0
- package/dist/railway/domain.d.cts +52 -0
- package/dist/railway/domain.d.cts.map +1 -0
- package/dist/railway/domain.d.mts +52 -0
- package/dist/railway/domain.d.mts.map +1 -0
- package/dist/railway/domain.mjs +234 -0
- package/dist/railway/domain.mjs.map +1 -0
- package/dist/railway/index.cjs +16 -0
- package/dist/railway/index.d.cts +8 -0
- package/dist/railway/index.d.mts +8 -0
- package/dist/railway/index.mjs +9 -0
- package/dist/railway/project.cjs +231 -0
- package/dist/railway/project.cjs.map +1 -0
- package/dist/railway/project.d.cts +52 -0
- package/dist/railway/project.d.cts.map +1 -0
- package/dist/railway/project.d.mts +52 -0
- package/dist/railway/project.d.mts.map +1 -0
- package/dist/railway/project.mjs +229 -0
- package/dist/railway/project.mjs.map +1 -0
- package/dist/railway/service.cjs +268 -0
- package/dist/railway/service.cjs.map +1 -0
- package/dist/railway/service.d.cts +88 -0
- package/dist/railway/service.d.cts.map +1 -0
- package/dist/railway/service.d.mts +88 -0
- package/dist/railway/service.d.mts.map +1 -0
- package/dist/railway/service.mjs +266 -0
- package/dist/railway/service.mjs.map +1 -0
- package/dist/railway/variable.cjs +149 -0
- package/dist/railway/variable.cjs.map +1 -0
- package/dist/railway/variable.d.cts +52 -0
- package/dist/railway/variable.d.cts.map +1 -0
- package/dist/railway/variable.d.mts +52 -0
- package/dist/railway/variable.d.mts.map +1 -0
- package/dist/railway/variable.mjs +147 -0
- package/dist/railway/variable.mjs.map +1 -0
- package/dist/railway/volume.cjs +179 -0
- package/dist/railway/volume.cjs.map +1 -0
- package/dist/railway/volume.d.cts +52 -0
- package/dist/railway/volume.d.cts.map +1 -0
- package/dist/railway/volume.d.mts +52 -0
- package/dist/railway/volume.d.mts.map +1 -0
- package/dist/railway/volume.mjs +177 -0
- package/dist/railway/volume.mjs.map +1 -0
- package/dist/vercel/deploy.cjs +65 -0
- package/dist/vercel/deploy.cjs.map +1 -0
- package/dist/vercel/deploy.d.cts +54 -0
- package/dist/vercel/deploy.d.cts.map +1 -0
- package/dist/vercel/deploy.d.mts +54 -0
- package/dist/vercel/deploy.d.mts.map +1 -0
- package/dist/vercel/deploy.mjs +61 -0
- package/dist/vercel/deploy.mjs.map +1 -0
- package/dist/vercel/index.cjs +6 -0
- package/dist/vercel/index.d.cts +3 -0
- package/dist/vercel/index.d.mts +3 -0
- package/dist/vercel/index.mjs +4 -0
- package/dist/vercel/variable.cjs +280 -0
- package/dist/vercel/variable.cjs.map +1 -0
- package/dist/vercel/variable.d.cts +55 -0
- package/dist/vercel/variable.d.cts.map +1 -0
- package/dist/vercel/variable.d.mts +55 -0
- package/dist/vercel/variable.d.mts.map +1 -0
- package/dist/vercel/variable.mjs +278 -0
- package/dist/vercel/variable.mjs.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"volume.mjs","names":[],"sources":["../../src/railway/volume.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\n\n/** Resolved inputs for the Railway volume dynamic provider. */\nexport interface RailwayVolumeInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the volume to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Absolute path inside the container where the volume is mounted (e.g. `\"/data\"`). */\n\tmountPath: string;\n}\n\n/** Persisted state for the Railway volume, extending inputs with the Railway-assigned ID. */\ninterface RailwayVolumeOutputs extends RailwayVolumeInputs {\n\t/** Railway-assigned volume UUID (set after create or adopt). */\n\tvolumeId: string;\n}\n\nconst VOLUME_CREATE = `\n mutation($input: VolumeCreateInput!) {\n volumeCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst VOLUME_DELETE = `\n mutation($volumeId: String!) {\n volumeDelete(volumeId: $volumeId)\n }\n`;\n\nconst VOLUMES_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n volumes {\n edges {\n node {\n id\n name\n volumeInstances {\n edges {\n node {\n id\n mountPath\n serviceId\n }\n }\n }\n }\n }\n }\n }\n }\n`;\n\n/**\n * Finds an existing volume attached to a specific service within a project.\n *\n * @param client Authenticated Railway API client\n * @param projectId Railway project UUID to search within\n * @param serviceId Railway service UUID to match volume instances against\n * @returns The volume UUID if found, `undefined` otherwise\n */\nasync function findVolumeByService(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n): Promise<string | undefined> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tvolumes: {\n\t\t\t\tedges: Array<{\n\t\t\t\t\tnode: {\n\t\t\t\t\t\tid: string;\n\t\t\t\t\t\tvolumeInstances: {\n\t\t\t\t\t\t\tedges: Array<{\n\t\t\t\t\t\t\t\tnode: { serviceId: string };\n\t\t\t\t\t\t\t}>;\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t}>;\n\t\t\t};\n\t\t};\n\t}>(VOLUMES_QUERY, { projectId });\n\n\tconst match = result.project.volumes.edges.find((edge) =>\n\t\tedge.node.volumeInstances.edges.some(\n\t\t\t(vi) => vi.node.serviceId === serviceId,\n\t\t),\n\t);\n\n\treturn match?.node.id;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway persistent volumes.\n *\n * Uses adopt-or-create on `create()`: finds an existing volume by service\n * before creating a new one, making `pulumi up` idempotent from zero.\n * Volumes are immutable after creation — changing `serviceId`, `mountPath`,\n * `environmentId`, or `projectId` triggers replacement (delete + create).\n */\nclass RailwayVolumeProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Railway volume for the target service.\n\t *\n\t * @param inputs Resolved volume configuration\n\t * @returns The Railway volume UUID as the resource ID\n\t */\n\tasync create(\n\t\tinputs: RailwayVolumeInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tlet volumeId = await findVolumeByService(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t);\n\n\t\tif (volumeId) {\n\t\t\tpulumi.log.info(`Adopting existing volume for service (${volumeId})`);\n\t\t} else {\n\t\t\tconst result = await client.query<{\n\t\t\t\tvolumeCreate: { id: string };\n\t\t\t}>(VOLUME_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tmountPath: inputs.mountPath,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tvolumeId = result.volumeCreate.id;\n\t\t}\n\n\t\treturn {\n\t\t\tid: volumeId,\n\t\t\touts: { ...inputs, volumeId },\n\t\t};\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh` by looking up the volume.\n\t *\n\t * @param id Current Railway volume UUID\n\t * @param props Last known persisted state\n\t * @returns Refreshed resource ID and properties\n\t * @throws {Error} If the volume no longer exists in Railway\n\t */\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayVolumeOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst volumeId = await findVolumeByService(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t);\n\n\t\tif (!volumeId) {\n\t\t\tthrow new Error(\"Railway volume not found during refresh\");\n\t\t}\n\n\t\treturn { id: volumeId, props: { ...props, volumeId } };\n\t}\n\n\t/**\n\t * Deletes the Railway volume. Silently succeeds if already deleted.\n\t *\n\t * @param id Railway volume UUID to delete\n\t * @param props Last known persisted state (used for token)\n\t */\n\tasync delete(id: string, props: RailwayVolumeOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(VOLUME_DELETE, { volumeId: id });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway volume (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * All mutable properties trigger replacement since Railway volumes\n\t * cannot be moved between services, mount paths, environments, or projects.\n\t *\n\t * @param _id Current resource ID (unused)\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\n\t * @returns Diff result with replacement triggers\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayVolumeOutputs,\n\t\tnews: RailwayVolumeInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.mountPath !== news.mountPath) {\n\t\t\treplaces.push(\"mountPath\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Railway persistent volume with adopt-or-create semantics.\n *\n * Finds existing volumes attached to the target service before creating\n * new ones. Changing `serviceId`, `mountPath`, `environmentId`, or `projectId`\n * triggers replacement (delete + create with new settings).\n *\n * @example\n * ```typescript\n * new RailwayVolume(\"railway-volume-redis\", {\n * token: project.projectToken,\n * projectId: project.projectId,\n * serviceId: service.serviceId,\n * environmentId: project.productionEnvironmentId,\n * mountPath: \"/data\",\n * });\n * ```\n */\nexport class RailwayVolume extends pulumi.dynamic.Resource {\n\t/**\n\t * @param name Pulumi resource name (logical identifier in state)\n\t * @param args Volume configuration inputs\n\t * @param opts Standard Pulumi resource options (e.g. `dependsOn`, `parent`)\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Railway API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Railway project UUID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Railway service UUID to attach the volume to. */\n\t\t\tserviceId: pulumi.Input<string>;\n\n\t\t\t/** Railway environment UUID (e.g. production). */\n\t\t\tenvironmentId: pulumi.Input<string>;\n\n\t\t\t/** Absolute path inside the container where the volume is mounted. */\n\t\t\tmountPath: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayVolumeProvider(),\n\t\t\tname,\n\t\t\t{ ...args, volumeId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":";;;;;AA2BA,MAAM,gBAAgB;;;;;;;;AAStB,MAAM,gBAAgB;;;;;AAMtB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCtB,eAAe,oBACd,QACA,WACA,WAC8B;CAwB9B,QANc,MAjBO,OAAO,MAezB,eAAe,EAAE,UAAU,CAAC,GAEV,QAAQ,QAAQ,MAAM,MAAM,SAChD,KAAK,KAAK,gBAAgB,MAAM,MAC9B,OAAO,GAAG,KAAK,cAAc,SAC/B,CAGU,GAAG,KAAK;AACpB;;;;;;;;;AAUA,IAAM,wBAAN,MAAuE;;;;;;;CAOtE,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAI,cAAc,OAAO,KAAK;EAE7C,IAAI,WAAW,MAAM,oBACpB,QACA,OAAO,WACP,OAAO,SACR;EAEA,IAAI,UACH,OAAO,IAAI,KAAK,yCAAyC,SAAS,EAAE;OAapE,YAAW,MAXU,OAAO,MAEzB,eAAe,EACjB,OAAO;GACN,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,eAAe,OAAO;GACtB,WAAW,OAAO;EACnB,EACD,CAAC,GAEiB,aAAa;EAGhC,OAAO;GACN,IAAI;GACJ,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;;;;;;;;;CAUA,MAAM,KACL,KACA,OACqC;EAGrC,MAAM,WAAW,MAAM,oBACtB,IAHkB,cAAc,MAAM,KAGjC,GACL,MAAM,WACN,MAAM,SACP;EAEA,IAAI,CAAC,UACJ,MAAM,IAAI,MAAM,yCAAyC;EAG1D,OAAO;GAAE,IAAI;GAAU,OAAO;IAAE,GAAG;IAAO;GAAS;EAAE;CACtD;;;;;;;CAQA,MAAM,OAAO,IAAY,OAA4C;EACpE,MAAM,SAAS,IAAI,cAAc,MAAM,KAAK;EAE5C,IAAI;GACH,MAAM,OAAO,MAAM,eAAe,EAAE,UAAU,GAAG,CAAC;EACnD,QAAQ;GACP,OAAO,IAAI,KACV,0DACD;EACD;CACD;;;;;;;;;;;;CAaA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,kBAAkB,KAAK,eAC/B,SAAS,KAAK,eAAe;EAG9B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;;;;;;;;;;;;;;;;;;AAoBA,IAAa,gBAAb,cAAmC,OAAO,QAAQ,SAAS;;;;;;CAM1D,YACC,MACA,MAgBA,MACC;EACD,MACC,IAAI,sBAAsB,GAC1B,MACA;GAAE,GAAG;GAAM,UAAU;EAAU,GAC/B,IACD;CACD;AACD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
+
const require_hash = require('../hash.cjs');
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
6
|
+
let _pulumi_command = require("@pulumi/command");
|
|
7
|
+
_pulumi_command = require_chunk.__toESM(_pulumi_command, 1);
|
|
8
|
+
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
9
|
+
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
10
|
+
|
|
11
|
+
//#region src/vercel/deploy.ts
|
|
12
|
+
/**
|
|
13
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes` CLI.
|
|
14
|
+
*
|
|
15
|
+
* Triggers on source hash (computed from the app directory) and env content hash
|
|
16
|
+
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
17
|
+
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
18
|
+
* and a redeploy is triggered.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* new VercelDeploy("vercel-deploy-nexus", {
|
|
23
|
+
* token: vercelConfig.token,
|
|
24
|
+
* projectId: project.id,
|
|
25
|
+
* rootDirectory: "apps/nexus",
|
|
26
|
+
* monorepoRoot,
|
|
27
|
+
* teamId: vercelConfig.teamId,
|
|
28
|
+
* env: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
var VercelDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
33
|
+
/**
|
|
34
|
+
* @param name Pulumi resource name (logical identifier in state)
|
|
35
|
+
* @param args Deploy configuration
|
|
36
|
+
* @param opts Component resource options
|
|
37
|
+
*/
|
|
38
|
+
constructor(name, args, opts) {
|
|
39
|
+
super("infrakit:vercel:Deploy", name, {}, opts);
|
|
40
|
+
const hashParts = [require_hash.hashDirectory(node_path.join(args.monorepoRoot, args.rootDirectory))];
|
|
41
|
+
for (const dir of args.additionalSourceDirs ?? []) hashParts.push(require_hash.hashDirectory(node_path.join(args.monorepoRoot, dir)));
|
|
42
|
+
const sourceHash = hashParts.join(",");
|
|
43
|
+
const commandOpts = { parent: this };
|
|
44
|
+
if (args.dependsOn && args.dependsOn.length > 0) commandOpts.dependsOn = args.dependsOn;
|
|
45
|
+
const envHash = _pulumi_pulumi.all(Object.entries(args.env).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => _pulumi_pulumi.output(v).apply((val) => `${k}=${val}`))).apply((parts) => parts.join(","));
|
|
46
|
+
new _pulumi_command.local.Command(`${name}-deploy`, {
|
|
47
|
+
create: "vercel deploy --prod --yes",
|
|
48
|
+
triggers: [sourceHash, envHash],
|
|
49
|
+
dir: args.monorepoRoot,
|
|
50
|
+
environment: {
|
|
51
|
+
VERCEL_TOKEN: args.token,
|
|
52
|
+
VERCEL_ORG_ID: args.teamId,
|
|
53
|
+
VERCEL_PROJECT_ID: args.projectId
|
|
54
|
+
}
|
|
55
|
+
}, {
|
|
56
|
+
parent: this,
|
|
57
|
+
...commandOpts.dependsOn ? { dependsOn: commandOpts.dependsOn } : {}
|
|
58
|
+
});
|
|
59
|
+
this.registerOutputs({});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
exports.VercelDeploy = VercelDeploy;
|
|
65
|
+
//# sourceMappingURL=deploy.cjs.map
|
|
@@ -0,0 +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\";\n\n/** Configuration for a Vercel project deploy via `vercel deploy` CLI. */\nexport interface VercelDeployArgs {\n\t/** Vercel API bearer token. */\n\ttoken: pulumi.Input<string>;\n\n\t/** Vercel project ID (Output from `vercel.Project`). */\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/** Vercel team/org ID. */\n\tteamId: 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(\"vercel-deploy-nexus\", {\n * token: vercelConfig.token,\n * projectId: project.id,\n * rootDirectory: \"apps/nexus\",\n * monorepoRoot,\n * teamId: vercelConfig.teamId,\n * env: { NEXT_PUBLIC_API_URL: meshUrl },\n * });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\t/**\n\t * @param name Pulumi resource name (logical identifier in state)\n\t * @param args Deploy configuration\n\t * @param opts Component resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelDeployArgs,\n\t\topts?: pulumi.ComponentResourceOptions,\n\t) {\n\t\tsuper(\"infrakit:vercel:Deploy\", name, {}, opts);\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: args.token,\n\t\t\t\t\tVERCEL_ORG_ID: args.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: args.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,eAAb,cAAkCA,eAAO,kBAAkB;;;;;;CAM1D,YACC,MACA,MACA,MACC;EACD,MAAM,0BAA0B,MAAM,CAAC,GAAG,IAAI;EAI9C,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,KAAK;IACnB,eAAe,KAAK;IACpB,mBAAmB,KAAK;GACzB;EACD,GACA;GACC,QAAQ;GACR,GAAI,YAAY,YAAY,EAAE,WAAW,YAAY,UAAU,IAAI,CAAC;EACrE,CACD;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.cjs";
|
|
2
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
3
|
+
|
|
4
|
+
//#region src/vercel/deploy.d.ts
|
|
5
|
+
/** Configuration for a Vercel project deploy via `vercel deploy` CLI. */
|
|
6
|
+
interface VercelDeployArgs {
|
|
7
|
+
/** Vercel API bearer token. */
|
|
8
|
+
token: pulumi.Input<string>;
|
|
9
|
+
/** Vercel project ID (Output from `vercel.Project`). */
|
|
10
|
+
projectId: pulumi.Input<string>;
|
|
11
|
+
/** Relative path from monorepo root to the app directory (e.g. `"apps/nexus"`). */
|
|
12
|
+
rootDirectory: string;
|
|
13
|
+
/** Absolute path to the monorepo root (working directory for `vercel deploy`). */
|
|
14
|
+
monorepoRoot: string;
|
|
15
|
+
/** Vercel team/org ID. */
|
|
16
|
+
teamId: string;
|
|
17
|
+
/** Env var map used as deploy trigger. Hashes both keys and resolved values so value changes trigger redeploy. */
|
|
18
|
+
env: Record<string, pulumi.Input<string>>;
|
|
19
|
+
/** Additional directories (relative to monorepo root) to include in source hash. Changes in these trigger redeploy. */
|
|
20
|
+
additionalSourceDirs?: string[];
|
|
21
|
+
/** Resources that must complete before the deploy runs. */
|
|
22
|
+
dependsOn?: pulumi.Resource[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes` CLI.
|
|
26
|
+
*
|
|
27
|
+
* Triggers on source hash (computed from the app directory) and env content hash
|
|
28
|
+
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
29
|
+
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
30
|
+
* and a redeploy is triggered.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* new VercelDeploy("vercel-deploy-nexus", {
|
|
35
|
+
* token: vercelConfig.token,
|
|
36
|
+
* projectId: project.id,
|
|
37
|
+
* rootDirectory: "apps/nexus",
|
|
38
|
+
* monorepoRoot,
|
|
39
|
+
* teamId: vercelConfig.teamId,
|
|
40
|
+
* env: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare class VercelDeploy extends pulumi.ComponentResource {
|
|
45
|
+
/**
|
|
46
|
+
* @param name Pulumi resource name (logical identifier in state)
|
|
47
|
+
* @param args Deploy configuration
|
|
48
|
+
* @param opts Component resource options
|
|
49
|
+
*/
|
|
50
|
+
constructor(name: string, args: VercelDeployArgs, opts?: pulumi.ComponentResourceOptions);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { VercelDeploy, VercelDeployArgs };
|
|
54
|
+
//# sourceMappingURL=deploy.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;UAMiB,gBAAA;;EAEhB,KAAA,EAAO,MAAA,CAAO,KAAA;EAFkB;EAKhC,SAAA,EAAW,MAAA,CAAO,KAAA;EAHX;EAMP,aAAA;EASoB;EANpB,YAAA;EAYY;EATZ,MAAA;EAS2B;EAN3B,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;EAfpB;EAkBP,oBAAA;EAfA;EAkBA,SAAA,GAAY,MAAA,CAAO,QAAA;AAAA;;;;;;;;;;;;;AAAQ;AAuB5B;;;;;;;cAAa,YAAA,SAAqB,MAAA,CAAO,iBAAA;EAAP;;;;;cAOhC,IAAA,UACA,IAAA,EAAM,gBAAA,EACN,IAAA,GAAO,MAAA,CAAO,wBAAA;AAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
3
|
+
|
|
4
|
+
//#region src/vercel/deploy.d.ts
|
|
5
|
+
/** Configuration for a Vercel project deploy via `vercel deploy` CLI. */
|
|
6
|
+
interface VercelDeployArgs {
|
|
7
|
+
/** Vercel API bearer token. */
|
|
8
|
+
token: pulumi.Input<string>;
|
|
9
|
+
/** Vercel project ID (Output from `vercel.Project`). */
|
|
10
|
+
projectId: pulumi.Input<string>;
|
|
11
|
+
/** Relative path from monorepo root to the app directory (e.g. `"apps/nexus"`). */
|
|
12
|
+
rootDirectory: string;
|
|
13
|
+
/** Absolute path to the monorepo root (working directory for `vercel deploy`). */
|
|
14
|
+
monorepoRoot: string;
|
|
15
|
+
/** Vercel team/org ID. */
|
|
16
|
+
teamId: string;
|
|
17
|
+
/** Env var map used as deploy trigger. Hashes both keys and resolved values so value changes trigger redeploy. */
|
|
18
|
+
env: Record<string, pulumi.Input<string>>;
|
|
19
|
+
/** Additional directories (relative to monorepo root) to include in source hash. Changes in these trigger redeploy. */
|
|
20
|
+
additionalSourceDirs?: string[];
|
|
21
|
+
/** Resources that must complete before the deploy runs. */
|
|
22
|
+
dependsOn?: pulumi.Resource[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes` CLI.
|
|
26
|
+
*
|
|
27
|
+
* Triggers on source hash (computed from the app directory) and env content hash
|
|
28
|
+
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
29
|
+
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
30
|
+
* and a redeploy is triggered.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* new VercelDeploy("vercel-deploy-nexus", {
|
|
35
|
+
* token: vercelConfig.token,
|
|
36
|
+
* projectId: project.id,
|
|
37
|
+
* rootDirectory: "apps/nexus",
|
|
38
|
+
* monorepoRoot,
|
|
39
|
+
* teamId: vercelConfig.teamId,
|
|
40
|
+
* env: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare class VercelDeploy extends pulumi.ComponentResource {
|
|
45
|
+
/**
|
|
46
|
+
* @param name Pulumi resource name (logical identifier in state)
|
|
47
|
+
* @param args Deploy configuration
|
|
48
|
+
* @param opts Component resource options
|
|
49
|
+
*/
|
|
50
|
+
constructor(name: string, args: VercelDeployArgs, opts?: pulumi.ComponentResourceOptions);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { VercelDeploy, VercelDeployArgs };
|
|
54
|
+
//# sourceMappingURL=deploy.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;UAMiB,gBAAA;;EAEhB,KAAA,EAAO,MAAA,CAAO,KAAA;EAFkB;EAKhC,SAAA,EAAW,MAAA,CAAO,KAAA;EAHX;EAMP,aAAA;EASoB;EANpB,YAAA;EAYY;EATZ,MAAA;EAS2B;EAN3B,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;EAfpB;EAkBP,oBAAA;EAfA;EAkBA,SAAA,GAAY,MAAA,CAAO,QAAA;AAAA;;;;;;;;;;;;;AAAQ;AAuB5B;;;;;;;cAAa,YAAA,SAAqB,MAAA,CAAO,iBAAA;EAAP;;;;;cAOhC,IAAA,UACA,IAAA,EAAM,gBAAA,EACN,IAAA,GAAO,MAAA,CAAO,wBAAA;AAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
+
import { hashDirectory } from "../hash.mjs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as command from "@pulumi/command";
|
|
5
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
6
|
+
|
|
7
|
+
//#region src/vercel/deploy.ts
|
|
8
|
+
/**
|
|
9
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes` CLI.
|
|
10
|
+
*
|
|
11
|
+
* Triggers on source hash (computed from the app directory) and env content hash
|
|
12
|
+
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
13
|
+
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
14
|
+
* and a redeploy is triggered.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* new VercelDeploy("vercel-deploy-nexus", {
|
|
19
|
+
* token: vercelConfig.token,
|
|
20
|
+
* projectId: project.id,
|
|
21
|
+
* rootDirectory: "apps/nexus",
|
|
22
|
+
* monorepoRoot,
|
|
23
|
+
* teamId: vercelConfig.teamId,
|
|
24
|
+
* env: { NEXT_PUBLIC_API_URL: meshUrl },
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
var VercelDeploy = class extends pulumi.ComponentResource {
|
|
29
|
+
/**
|
|
30
|
+
* @param name Pulumi resource name (logical identifier in state)
|
|
31
|
+
* @param args Deploy configuration
|
|
32
|
+
* @param opts Component resource options
|
|
33
|
+
*/
|
|
34
|
+
constructor(name, args, opts) {
|
|
35
|
+
super("infrakit:vercel:Deploy", name, {}, opts);
|
|
36
|
+
const hashParts = [hashDirectory(path.join(args.monorepoRoot, args.rootDirectory))];
|
|
37
|
+
for (const dir of args.additionalSourceDirs ?? []) hashParts.push(hashDirectory(path.join(args.monorepoRoot, dir)));
|
|
38
|
+
const sourceHash = hashParts.join(",");
|
|
39
|
+
const commandOpts = { parent: this };
|
|
40
|
+
if (args.dependsOn && args.dependsOn.length > 0) commandOpts.dependsOn = args.dependsOn;
|
|
41
|
+
const envHash = pulumi.all(Object.entries(args.env).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => pulumi.output(v).apply((val) => `${k}=${val}`))).apply((parts) => parts.join(","));
|
|
42
|
+
new command.local.Command(`${name}-deploy`, {
|
|
43
|
+
create: "vercel deploy --prod --yes",
|
|
44
|
+
triggers: [sourceHash, envHash],
|
|
45
|
+
dir: args.monorepoRoot,
|
|
46
|
+
environment: {
|
|
47
|
+
VERCEL_TOKEN: args.token,
|
|
48
|
+
VERCEL_ORG_ID: args.teamId,
|
|
49
|
+
VERCEL_PROJECT_ID: args.projectId
|
|
50
|
+
}
|
|
51
|
+
}, {
|
|
52
|
+
parent: this,
|
|
53
|
+
...commandOpts.dependsOn ? { dependsOn: commandOpts.dependsOn } : {}
|
|
54
|
+
});
|
|
55
|
+
this.registerOutputs({});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { VercelDeploy };
|
|
61
|
+
//# sourceMappingURL=deploy.mjs.map
|
|
@@ -0,0 +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\";\n\n/** Configuration for a Vercel project deploy via `vercel deploy` CLI. */\nexport interface VercelDeployArgs {\n\t/** Vercel API bearer token. */\n\ttoken: pulumi.Input<string>;\n\n\t/** Vercel project ID (Output from `vercel.Project`). */\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/** Vercel team/org ID. */\n\tteamId: 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(\"vercel-deploy-nexus\", {\n * token: vercelConfig.token,\n * projectId: project.id,\n * rootDirectory: \"apps/nexus\",\n * monorepoRoot,\n * teamId: vercelConfig.teamId,\n * env: { NEXT_PUBLIC_API_URL: meshUrl },\n * });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\t/**\n\t * @param name Pulumi resource name (logical identifier in state)\n\t * @param args Deploy configuration\n\t * @param opts Component resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelDeployArgs,\n\t\topts?: pulumi.ComponentResourceOptions,\n\t) {\n\t\tsuper(\"infrakit:vercel:Deploy\", name, {}, opts);\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: args.token,\n\t\t\t\t\tVERCEL_ORG_ID: args.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: args.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,eAAb,cAAkC,OAAO,kBAAkB;;;;;;CAM1D,YACC,MACA,MACA,MACC;EACD,MAAM,0BAA0B,MAAM,CAAC,GAAG,IAAI;EAI9C,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,KAAK;IACnB,eAAe,KAAK;IACpB,mBAAmB,KAAK;GACzB;EACD,GACA;GACC,QAAQ;GACR,GAAI,YAAY,YAAY,EAAE,WAAW,YAAY,UAAU,IAAI,CAAC;EACrE,CACD;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_vercel_deploy = require('./deploy.cjs');
|
|
3
|
+
const require_vercel_variable = require('./variable.cjs');
|
|
4
|
+
|
|
5
|
+
exports.VercelDeploy = require_vercel_deploy.VercelDeploy;
|
|
6
|
+
exports.VercelVariable = require_vercel_variable.VercelVariable;
|
|
@@ -0,0 +1,280 @@
|
|
|
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/variable.ts
|
|
7
|
+
const VERCEL_API_URL = "https://api.vercel.com";
|
|
8
|
+
/**
|
|
9
|
+
* Computes a deterministic content hash of a variables map.
|
|
10
|
+
* Uses dynamic import to avoid capturing `crypto` in the provider closure.
|
|
11
|
+
*
|
|
12
|
+
* @param variables Key-value map of environment variables
|
|
13
|
+
* @returns Hex-encoded SHA-256 hash
|
|
14
|
+
*/
|
|
15
|
+
async function hashVariables(variables) {
|
|
16
|
+
const hash = (await import("node:crypto")).createHash("sha256");
|
|
17
|
+
const sorted = Object.entries(variables).sort(([a], [b]) => a.localeCompare(b));
|
|
18
|
+
for (const [key, value] of sorted) {
|
|
19
|
+
hash.update(key);
|
|
20
|
+
hash.update(value);
|
|
21
|
+
}
|
|
22
|
+
return hash.digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetches all environment variables for a Vercel project with decrypted values.
|
|
26
|
+
*
|
|
27
|
+
* @param token Vercel API token
|
|
28
|
+
* @param teamId Vercel team ID
|
|
29
|
+
* @param projectId Vercel project ID
|
|
30
|
+
* @returns Array of env vars with decrypted values
|
|
31
|
+
*/
|
|
32
|
+
async function fetchEnvVars(token, teamId, projectId) {
|
|
33
|
+
const listResponse = await fetch(`${VERCEL_API_URL}/v9/projects/${projectId}/env?teamId=${teamId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
34
|
+
if (!listResponse.ok) throw new Error(`Vercel API error (${listResponse.status}): ${await listResponse.text()}`);
|
|
35
|
+
const list = await listResponse.json();
|
|
36
|
+
const decrypted = [];
|
|
37
|
+
for (const env of list.envs) {
|
|
38
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${projectId}/env/${env.id}?teamId=${teamId}&decrypt=true`, { headers: { Authorization: `Bearer ${token}` } });
|
|
39
|
+
if (response.ok) decrypted.push(await response.json());
|
|
40
|
+
}
|
|
41
|
+
return decrypted;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a single Vercel env var targeting all environments.
|
|
45
|
+
*
|
|
46
|
+
* @param token Vercel API token
|
|
47
|
+
* @param teamId Vercel team ID
|
|
48
|
+
* @param projectId Vercel project ID
|
|
49
|
+
* @param key Env var name
|
|
50
|
+
* @param value Env var value
|
|
51
|
+
* @returns The created env var with its Vercel-assigned ID
|
|
52
|
+
*/
|
|
53
|
+
async function createEnvVar(token, teamId, projectId, key, value) {
|
|
54
|
+
const response = await fetch(`${VERCEL_API_URL}/v10/projects/${projectId}/env?teamId=${teamId}`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${token}`,
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
key,
|
|
62
|
+
value,
|
|
63
|
+
type: "encrypted",
|
|
64
|
+
target: [
|
|
65
|
+
"production",
|
|
66
|
+
"preview",
|
|
67
|
+
"development"
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorText = await response.text();
|
|
73
|
+
if (errorText.includes("ENV_CONFLICT")) {
|
|
74
|
+
_pulumi_pulumi.log.info(`Vercel env var "${key}" already exists, will update instead`);
|
|
75
|
+
const match = (await fetchEnvVars(token, teamId, projectId)).find((e) => e.key === key);
|
|
76
|
+
if (match) {
|
|
77
|
+
await updateEnvVar(token, teamId, projectId, match.id, value);
|
|
78
|
+
return {
|
|
79
|
+
...match,
|
|
80
|
+
value
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Vercel API error creating "${key}" (${response.status}): ${errorText}`);
|
|
85
|
+
}
|
|
86
|
+
return await response.json();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Updates a single Vercel env var value.
|
|
90
|
+
*
|
|
91
|
+
* @param token Vercel API token
|
|
92
|
+
* @param teamId Vercel team ID
|
|
93
|
+
* @param projectId Vercel project ID
|
|
94
|
+
* @param envId Vercel-assigned env var ID
|
|
95
|
+
* @param value New value
|
|
96
|
+
*/
|
|
97
|
+
async function updateEnvVar(token, teamId, projectId, envId, value) {
|
|
98
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`, {
|
|
99
|
+
method: "PATCH",
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Bearer ${token}`,
|
|
102
|
+
"Content-Type": "application/json"
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ value })
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) throw new Error(`Vercel API error updating env var (${response.status}): ${await response.text()}`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Deletes a single Vercel env var.
|
|
110
|
+
*
|
|
111
|
+
* @param token Vercel API token
|
|
112
|
+
* @param teamId Vercel team ID
|
|
113
|
+
* @param projectId Vercel project ID
|
|
114
|
+
* @param envId Vercel-assigned env var ID
|
|
115
|
+
*/
|
|
116
|
+
async function deleteEnvVar(token, teamId, projectId, envId) {
|
|
117
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${projectId}/env/${envId}?teamId=${teamId}`, {
|
|
118
|
+
method: "DELETE",
|
|
119
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok && response.status !== 404) throw new Error(`Vercel API error deleting env var (${response.status}): ${await response.text()}`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Dynamic provider implementing CRUD for Vercel project environment variables.
|
|
125
|
+
*
|
|
126
|
+
* Manages all env vars for a project as a batch (like RailwayVariable).
|
|
127
|
+
* The `read()` method fetches decrypted values from Vercel's API,
|
|
128
|
+
* enabling drift detection during `pulumi refresh`.
|
|
129
|
+
*/
|
|
130
|
+
var VercelVariableProvider = class {
|
|
131
|
+
/**
|
|
132
|
+
* Creates all env vars on the target Vercel project.
|
|
133
|
+
* Handles ENV_CONFLICT by updating existing vars instead of failing.
|
|
134
|
+
*
|
|
135
|
+
* @param inputs Resolved variable configuration
|
|
136
|
+
* @returns Composite ID in the form `{projectId}:variables`
|
|
137
|
+
*/
|
|
138
|
+
async create(inputs) {
|
|
139
|
+
const envIds = {};
|
|
140
|
+
for (const [key, value] of Object.entries(inputs.variables)) envIds[key] = (await createEnvVar(inputs.token, inputs.teamId, inputs.projectId, key, value)).id;
|
|
141
|
+
return {
|
|
142
|
+
id: `${inputs.projectId}:variables`,
|
|
143
|
+
outs: {
|
|
144
|
+
...inputs,
|
|
145
|
+
envIds,
|
|
146
|
+
contentHash: await hashVariables(inputs.variables)
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Updates variables by deleting removed keys, creating new keys,
|
|
152
|
+
* and patching changed values.
|
|
153
|
+
*
|
|
154
|
+
* @param _id Current resource ID
|
|
155
|
+
* @param olds Previous persisted state
|
|
156
|
+
* @param news New desired configuration
|
|
157
|
+
* @returns Updated outputs with current envIds
|
|
158
|
+
*/
|
|
159
|
+
async update(_id, olds, news) {
|
|
160
|
+
const envIds = { ...olds.envIds };
|
|
161
|
+
const removedKeys = Object.keys(olds.variables).filter((key) => !(key in news.variables));
|
|
162
|
+
for (const key of removedKeys) {
|
|
163
|
+
const envId = envIds[key];
|
|
164
|
+
if (envId) {
|
|
165
|
+
try {
|
|
166
|
+
await deleteEnvVar(news.token, news.teamId, news.projectId, envId);
|
|
167
|
+
} catch {
|
|
168
|
+
_pulumi_pulumi.log.warn(`Failed to delete Vercel env var "${key}" (may already be deleted)`);
|
|
169
|
+
}
|
|
170
|
+
delete envIds[key];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [key, value] of Object.entries(news.variables)) {
|
|
174
|
+
const existingId = envIds[key];
|
|
175
|
+
if (existingId && olds.variables[key] !== value) await updateEnvVar(news.token, news.teamId, news.projectId, existingId, value);
|
|
176
|
+
else if (!existingId) envIds[key] = (await createEnvVar(news.token, news.teamId, news.projectId, key, value)).id;
|
|
177
|
+
}
|
|
178
|
+
return { outs: {
|
|
179
|
+
...news,
|
|
180
|
+
envIds,
|
|
181
|
+
contentHash: await hashVariables(news.variables)
|
|
182
|
+
} };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Reads current state from Vercel API with decrypted values.
|
|
186
|
+
* Enables drift detection during `pulumi refresh`.
|
|
187
|
+
*
|
|
188
|
+
* @param id Current resource ID
|
|
189
|
+
* @param props Last known persisted state
|
|
190
|
+
* @returns Refreshed properties with actual values from Vercel
|
|
191
|
+
*/
|
|
192
|
+
async read(id, props) {
|
|
193
|
+
const actual = await fetchEnvVars(props.token, props.teamId, props.projectId);
|
|
194
|
+
const actualVariables = {};
|
|
195
|
+
const actualEnvIds = {};
|
|
196
|
+
for (const [key] of Object.entries(props.variables)) {
|
|
197
|
+
const match = actual.find((e) => e.key === key);
|
|
198
|
+
if (match) {
|
|
199
|
+
actualVariables[key] = match.value;
|
|
200
|
+
actualEnvIds[key] = match.id;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
id,
|
|
205
|
+
props: {
|
|
206
|
+
...props,
|
|
207
|
+
variables: actualVariables,
|
|
208
|
+
envIds: actualEnvIds,
|
|
209
|
+
contentHash: await hashVariables(actualVariables)
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Deletes all managed env vars from Vercel.
|
|
215
|
+
*
|
|
216
|
+
* @param _id Current resource ID
|
|
217
|
+
* @param props Last known persisted state
|
|
218
|
+
*/
|
|
219
|
+
async delete(_id, props) {
|
|
220
|
+
for (const [key, envId] of Object.entries(props.envIds)) try {
|
|
221
|
+
await deleteEnvVar(props.token, props.teamId, props.projectId, envId);
|
|
222
|
+
} catch {
|
|
223
|
+
_pulumi_pulumi.log.warn(`Failed to delete Vercel env var "${key}" (may already be deleted)`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Compares old and new variable maps by key set and value equality.
|
|
228
|
+
*
|
|
229
|
+
* @param _id Current resource ID
|
|
230
|
+
* @param olds Previous persisted state
|
|
231
|
+
* @param news New desired configuration
|
|
232
|
+
* @returns Whether any keys or values changed
|
|
233
|
+
*/
|
|
234
|
+
async diff(_id, olds, news) {
|
|
235
|
+
const oldKeys = Object.keys(olds.variables).sort().join(",");
|
|
236
|
+
const newKeys = Object.keys(news.variables).sort().join(",");
|
|
237
|
+
const valuesChanged = Object.entries(news.variables).some(([key, value]) => olds.variables[key] !== value);
|
|
238
|
+
return { changes: oldKeys !== newKeys || valuesChanged };
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Manages Vercel project environment variables as a batch with drift detection.
|
|
243
|
+
*
|
|
244
|
+
* Unlike `@pulumiverse/vercel`'s `ProjectEnvironmentVariable`, this provider
|
|
245
|
+
* reads decrypted values from Vercel's API during `pulumi refresh`, enabling
|
|
246
|
+
* detection of manually changed env var values.
|
|
247
|
+
*
|
|
248
|
+
* All variables target production, preview, and development environments.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```typescript
|
|
252
|
+
* new VercelVariable("vercel-variable-nexus", {
|
|
253
|
+
* token: vercelConfig.token,
|
|
254
|
+
* teamId: vercelConfig.teamId,
|
|
255
|
+
* projectId: project.id,
|
|
256
|
+
* variables: {
|
|
257
|
+
* NEXT_PUBLIC_API_URL: meshUrl,
|
|
258
|
+
* NEXTAUTH_SECRET: config.requireSecret("nextauthSecret"),
|
|
259
|
+
* },
|
|
260
|
+
* });
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
var VercelVariable = class extends _pulumi_pulumi.dynamic.Resource {
|
|
264
|
+
/**
|
|
265
|
+
* @param name Pulumi resource name
|
|
266
|
+
* @param args Variable configuration inputs
|
|
267
|
+
* @param opts Standard Pulumi resource options
|
|
268
|
+
*/
|
|
269
|
+
constructor(name, args, opts) {
|
|
270
|
+
super(new VercelVariableProvider(), name, {
|
|
271
|
+
...args,
|
|
272
|
+
envIds: void 0,
|
|
273
|
+
contentHash: void 0
|
|
274
|
+
}, opts);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
//#endregion
|
|
279
|
+
exports.VercelVariable = VercelVariable;
|
|
280
|
+
//# sourceMappingURL=variable.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variable.cjs","names":["pulumi"],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\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 * Uses dynamic import to avoid capturing `crypto` in the provider closure.\n *\n * @param variables Key-value map of environment variables\n * @returns Hex-encoded SHA-256 hash\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 *\n * @param token Vercel API token\n * @param teamId Vercel team ID\n * @param projectId Vercel project ID\n * @returns Array of env vars 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 *\n * @param token Vercel API token\n * @param teamId Vercel team ID\n * @param projectId Vercel project ID\n * @param key Env var name\n * @param value Env var value\n * @returns The created env var with its Vercel-assigned ID\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 *\n * @param token Vercel API token\n * @param teamId Vercel team ID\n * @param projectId Vercel project ID\n * @param envId Vercel-assigned env var ID\n * @param value New 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 *\n * @param token Vercel API token\n * @param teamId Vercel team ID\n * @param projectId Vercel project ID\n * @param envId Vercel-assigned env var ID\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 *\n * Manages all env vars for a project as a batch (like RailwayVariable).\n * The `read()` method fetches decrypted values from Vercel's API,\n * enabling drift detection during `pulumi refresh`.\n */\nclass VercelVariableProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates all env vars on the target Vercel project.\n\t * Handles ENV_CONFLICT by updating existing vars instead of failing.\n\t *\n\t * @param inputs Resolved variable configuration\n\t * @returns Composite ID in the form `{projectId}:variables`\n\t */\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\t/**\n\t * Updates variables by deleting removed keys, creating new keys,\n\t * and patching changed values.\n\t *\n\t * @param _id Current resource ID\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\n\t * @returns Updated outputs with current envIds\n\t */\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\t/**\n\t * Reads current state from Vercel API with decrypted values.\n\t * Enables drift detection during `pulumi refresh`.\n\t *\n\t * @param id Current resource ID\n\t * @param props Last known persisted state\n\t * @returns Refreshed properties with actual values from Vercel\n\t */\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\t/**\n\t * Deletes all managed env vars from Vercel.\n\t *\n\t * @param _id Current resource ID\n\t * @param props Last known persisted state\n\t */\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\t/**\n\t * Compares old and new variable maps by key set and value equality.\n\t *\n\t * @param _id Current resource ID\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\n\t * @returns Whether any keys or values changed\n\t */\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/**\n * Manages Vercel project environment variables as a batch with drift detection.\n *\n * Unlike `@pulumiverse/vercel`'s `ProjectEnvironmentVariable`, this provider\n * reads decrypted values from Vercel's API during `pulumi refresh`, enabling\n * detection of manually changed env var values.\n *\n * All variables target production, preview, and development environments.\n *\n * @example\n * ```typescript\n * new VercelVariable(\"vercel-variable-nexus\", {\n * token: vercelConfig.token,\n * teamId: vercelConfig.teamId,\n * projectId: project.id,\n * variables: {\n * NEXT_PUBLIC_API_URL: meshUrl,\n * NEXTAUTH_SECRET: config.requireSecret(\"nextauthSecret\"),\n * },\n * });\n * ```\n */\nexport class VercelVariable extends pulumi.dynamic.Resource {\n\t/** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */\n\tpublic declare readonly contentHash: pulumi.Output<string>;\n\n\t/**\n\t * @param name Pulumi resource name\n\t * @param args Variable configuration inputs\n\t * @param opts Standard Pulumi resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Vercel API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Vercel team/org ID. */\n\t\t\tteamId: pulumi.Input<string>;\n\n\t\t\t/** Vercel project ID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Key-value map of environment variable names to their values. */\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 VercelVariableProvider(),\n\t\t\tname,\n\t\t\t{ ...args, envIds: undefined, contentHash: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|