@infracraft/pulumi 1.30.0 → 1.30.2

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.
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_fly_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { FlyClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_fly_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { FlyClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_fly_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { FlyClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
3
4
  const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
4
5
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
6
6
  const require_neon_client = require('./client.cjs');
7
7
  let _pulumi_pulumi = require("@pulumi/pulumi");
8
8
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,7 +1,7 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
2
3
  import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
3
4
  import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
5
5
  import { NeonClient } from "./client.mjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
@@ -2,10 +2,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
4
  const require_http_resilient_fetch = require('../http/resilient-fetch.cjs');
5
+ let _pulumi_pulumi = require("@pulumi/pulumi");
6
+ _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
5
7
 
6
8
  //#region src/neon/client.ts
7
9
  const NEON_API_URL = "https://console.neon.tech/api/v2";
8
10
  /**
11
+ * Neon runs project mutations as ASYNC operations and answers 423 ("project
12
+ * already has running conflicting operations, scheduling of new ones is
13
+ * prohibited") while earlier ones settle — its docs prescribe waiting for
14
+ * completion before the next request. A destroy immediately followed by an
15
+ * `up` on the same project hits this deterministically (live: branch/endpoint
16
+ * deletion still settling 30s later). Waiting until the lock clears is the
17
+ * documented protocol, bounded so a genuinely wedged project still fails.
18
+ */
19
+ const OPERATION_LOCK_WAIT = {
20
+ /** Delay between lock probes. */
21
+ intervalMs: 5e3,
22
+ /** Upper bound on probes (~2 minutes total). */
23
+ maxAttempts: 24
24
+ };
25
+ function sleep(ms) {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+ /**
9
29
  * REST client for the Neon API.
10
30
  *
11
31
  * @example
@@ -69,14 +89,20 @@ var NeonClient = class {
69
89
  await this.request("DELETE", path);
70
90
  }
71
91
  async request(method, path, body) {
72
- const response = await require_http_resilient_fetch.resilientFetch(`${NEON_API_URL}${path}`, {
73
- method,
74
- headers: {
75
- "Content-Type": "application/json",
76
- Authorization: `Bearer ${this.apiKey}`
77
- },
78
- body: body ? JSON.stringify(body) : void 0
79
- });
92
+ let response;
93
+ for (let attempt = 1;; attempt++) {
94
+ response = await require_http_resilient_fetch.resilientFetch(`${NEON_API_URL}${path}`, {
95
+ method,
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ Authorization: `Bearer ${this.apiKey}`
99
+ },
100
+ body: body ? JSON.stringify(body) : void 0
101
+ });
102
+ if (response.status !== 423 || attempt >= OPERATION_LOCK_WAIT.maxAttempts) break;
103
+ _pulumi_pulumi.log.info(`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1e3}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`);
104
+ await sleep(OPERATION_LOCK_WAIT.intervalMs);
105
+ }
80
106
  if (response.status === 404) throw new require_errors_api_not_found_error.ApiNotFoundError("neon", path);
81
107
  if (!response.ok) {
82
108
  const errorText = await response.text();
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["resilientFetch","ApiNotFoundError"],"sources":["../../src/neon/client.ts"],"sourcesContent":["import { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst response = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;;AAGA,MAAM,eAAe;;;;;;;;;;;;AAarB,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,MAAM,WAAW,MAAMA,4CAAe,GAAG,eAAe,QAAQ;GAC/D;GACA,SAAS;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;GAC/B;GACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;EACrC,CAAC;EAED,IAAI,SAAS,WAAW,KACvB,MAAM,IAAIC,oDAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
1
+ {"version":3,"file":"client.cjs","names":["resilientFetch","ApiNotFoundError"],"sources":["../../src/neon/client.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Neon runs project mutations as ASYNC operations and answers 423 (\"project\n * already has running conflicting operations, scheduling of new ones is\n * prohibited\") while earlier ones settle — its docs prescribe waiting for\n * completion before the next request. A destroy immediately followed by an\n * `up` on the same project hits this deterministically (live: branch/endpoint\n * deletion still settling 30s later). Waiting until the lock clears is the\n * documented protocol, bounded so a genuinely wedged project still fails.\n */\nconst OPERATION_LOCK_WAIT = {\n\t/** Delay between lock probes. */\n\tintervalMs: 5_000,\n\t/** Upper bound on probes (~2 minutes total). */\n\tmaxAttempts: 24,\n} as const;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet response: Response;\n\n\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\tresponse = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\tresponse.status !== 423 ||\n\t\t\t\tattempt >= OPERATION_LOCK_WAIT.maxAttempts\n\t\t\t) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1000}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`,\n\t\t\t);\n\n\t\t\tawait sleep(OPERATION_LOCK_WAIT.intervalMs);\n\t\t}\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;;;;AAIA,MAAM,eAAe;;;;;;;;;;AAWrB,MAAM,sBAAsB;;CAE3B,YAAY;;CAEZ,aAAa;AACd;AAEA,SAAS,MAAM,IAA2B;CACzC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;;;;;;;;;;AAaA,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,IAAI;EAEJ,KAAK,IAAI,UAAU,IAAK,WAAW;GAClC,WAAW,MAAMA,4CAAe,GAAG,eAAe,QAAQ;IACzD;IACA,SAAS;KACR,gBAAgB;KAChB,eAAe,UAAU,KAAK;IAC/B;IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;GACrC,CAAC;GAED,IACC,SAAS,WAAW,OACpB,WAAW,oBAAoB,aAE/B;GAGD,eAAO,IAAI,KACV,uEAAuE,oBAAoB,aAAa,IAAK,aAAa,QAAQ,GAAG,oBAAoB,YAAY,EACtK;GAEA,MAAM,MAAM,oBAAoB,UAAU;EAC3C;EAEA,IAAI,SAAS,WAAW,KACvB,MAAM,IAAIC,oDAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.cts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAgBA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAqCA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAgBA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAqCA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
@@ -1,10 +1,29 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
3
  import { resilientFetch } from "../http/resilient-fetch.mjs";
4
+ import * as pulumi from "@pulumi/pulumi";
4
5
 
5
6
  //#region src/neon/client.ts
6
7
  const NEON_API_URL = "https://console.neon.tech/api/v2";
7
8
  /**
9
+ * Neon runs project mutations as ASYNC operations and answers 423 ("project
10
+ * already has running conflicting operations, scheduling of new ones is
11
+ * prohibited") while earlier ones settle — its docs prescribe waiting for
12
+ * completion before the next request. A destroy immediately followed by an
13
+ * `up` on the same project hits this deterministically (live: branch/endpoint
14
+ * deletion still settling 30s later). Waiting until the lock clears is the
15
+ * documented protocol, bounded so a genuinely wedged project still fails.
16
+ */
17
+ const OPERATION_LOCK_WAIT = {
18
+ /** Delay between lock probes. */
19
+ intervalMs: 5e3,
20
+ /** Upper bound on probes (~2 minutes total). */
21
+ maxAttempts: 24
22
+ };
23
+ function sleep(ms) {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+ /**
8
27
  * REST client for the Neon API.
9
28
  *
10
29
  * @example
@@ -68,14 +87,20 @@ var NeonClient = class {
68
87
  await this.request("DELETE", path);
69
88
  }
70
89
  async request(method, path, body) {
71
- const response = await resilientFetch(`${NEON_API_URL}${path}`, {
72
- method,
73
- headers: {
74
- "Content-Type": "application/json",
75
- Authorization: `Bearer ${this.apiKey}`
76
- },
77
- body: body ? JSON.stringify(body) : void 0
78
- });
90
+ let response;
91
+ for (let attempt = 1;; attempt++) {
92
+ response = await resilientFetch(`${NEON_API_URL}${path}`, {
93
+ method,
94
+ headers: {
95
+ "Content-Type": "application/json",
96
+ Authorization: `Bearer ${this.apiKey}`
97
+ },
98
+ body: body ? JSON.stringify(body) : void 0
99
+ });
100
+ if (response.status !== 423 || attempt >= OPERATION_LOCK_WAIT.maxAttempts) break;
101
+ pulumi.log.info(`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1e3}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`);
102
+ await sleep(OPERATION_LOCK_WAIT.intervalMs);
103
+ }
79
104
  if (response.status === 404) throw new ApiNotFoundError("neon", path);
80
105
  if (!response.ok) {
81
106
  const errorText = await response.text();
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../../src/neon/client.ts"],"sourcesContent":["import { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst response = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;AAGA,MAAM,eAAe;;;;;;;;;;;;AAarB,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,MAAM,WAAW,MAAM,eAAe,GAAG,eAAe,QAAQ;GAC/D;GACA,SAAS;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;GAC/B;GACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;EACrC,CAAC;EAED,IAAI,SAAS,WAAW,KACvB,MAAM,IAAI,iBAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../src/neon/client.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Neon runs project mutations as ASYNC operations and answers 423 (\"project\n * already has running conflicting operations, scheduling of new ones is\n * prohibited\") while earlier ones settle — its docs prescribe waiting for\n * completion before the next request. A destroy immediately followed by an\n * `up` on the same project hits this deterministically (live: branch/endpoint\n * deletion still settling 30s later). Waiting until the lock clears is the\n * documented protocol, bounded so a genuinely wedged project still fails.\n */\nconst OPERATION_LOCK_WAIT = {\n\t/** Delay between lock probes. */\n\tintervalMs: 5_000,\n\t/** Upper bound on probes (~2 minutes total). */\n\tmaxAttempts: 24,\n} as const;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet response: Response;\n\n\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\tresponse = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\tresponse.status !== 423 ||\n\t\t\t\tattempt >= OPERATION_LOCK_WAIT.maxAttempts\n\t\t\t) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1000}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`,\n\t\t\t);\n\n\t\t\tawait sleep(OPERATION_LOCK_WAIT.intervalMs);\n\t\t}\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;;AAIA,MAAM,eAAe;;;;;;;;;;AAWrB,MAAM,sBAAsB;;CAE3B,YAAY;;CAEZ,aAAa;AACd;AAEA,SAAS,MAAM,IAA2B;CACzC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;;;;;;;;;;AAaA,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,IAAI;EAEJ,KAAK,IAAI,UAAU,IAAK,WAAW;GAClC,WAAW,MAAM,eAAe,GAAG,eAAe,QAAQ;IACzD;IACA,SAAS;KACR,gBAAgB;KAChB,eAAe,UAAU,KAAK;IAC/B;IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;GACrC,CAAC;GAED,IACC,SAAS,WAAW,OACpB,WAAW,oBAAoB,aAE/B;GAGD,OAAO,IAAI,KACV,uEAAuE,oBAAoB,aAAa,IAAK,aAAa,QAAQ,GAAG,oBAAoB,YAAY,EACtK;GAEA,MAAM,MAAM,oBAAoB,UAAU;EAC3C;EAEA,IAAI,SAAS,WAAW,KACvB,MAAM,IAAI,iBAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_neon_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { NeonClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_neon_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { NeonClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_neon_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { NeonClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
3
4
  const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
4
5
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
6
6
  const require_neon_client = require('./client.cjs');
7
7
  let _pulumi_pulumi = require("@pulumi/pulumi");
8
8
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,7 +1,7 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
2
3
  import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
3
4
  import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
5
5
  import { NeonClient } from "./client.mjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_vercel_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { VercelClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
3
4
  const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
4
5
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
6
6
  const require_vercel_client = require('./client.cjs');
7
7
  let _pulumi_pulumi = require("@pulumi/pulumi");
8
8
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,7 +1,7 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
2
3
  import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
3
4
  import { resolveCredential, resolveCredentialOutput } from "../dynamic/resolve-credential.mjs";
4
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
5
5
  import { VercelClient } from "./client.mjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
3
  const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
+ const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
5
  const require_vercel_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -164,11 +164,7 @@ var VercelVariableResourceProvider = class {
164
164
  /** Internal dynamic resource — not part of the public API. */
165
165
  var VercelVariableResource = class extends _pulumi_pulumi.dynamic.Resource {
166
166
  constructor(name, args, opts) {
167
- super(new VercelVariableResourceProvider(), name, {
168
- ...args,
169
- envIds: void 0,
170
- contentHash: void 0
171
- }, {
167
+ super(new VercelVariableResourceProvider(), name, args, {
172
168
  ...opts,
173
169
  additionalSecretOutputs: ["token"]
174
170
  });
@@ -193,15 +189,14 @@ var VercelVariable = class extends _pulumi_pulumi.ComponentResource {
193
189
  super("infracraft:vercel:Variable", name, {}, pulumiOpts);
194
190
  const projectId = project ? project.id : args.projectId;
195
191
  if (!projectId) throw new Error("VercelVariable: either `args.projectId` or `opts.project` must be provided");
196
- const resource = new VercelVariableResource(`${name}-resource`, {
192
+ new VercelVariableResource(`${name}-resource`, {
197
193
  token: provider.token,
198
194
  tokenEnvVar: provider.tokenEnvVar,
199
195
  teamId: provider.teamId,
200
196
  projectId,
201
197
  variables: args.variables
202
198
  }, { parent: this });
203
- this.contentHash = resource.contentHash;
204
- this.registerOutputs({ contentHash: this.contentHash });
199
+ this.registerOutputs({});
205
200
  }
206
201
  };
207
202
 
@@ -1 +1 @@
1
- {"version":3,"file":"variable.cjs","names":["ApiNotFoundError","VercelClient","resolveCredential","pulumi"],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { VercelClient } from \"./client\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\ninterface VercelVariableInputs {\n\t/** Vercel API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `VercelProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: 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 * A variable that vanishes between the list and its decrypt read is skipped.\n */\nasync function fetchEnvVars(\n\tclient: VercelClient,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst list = await client.get<{ envs: VercelEnvVar[] }>(\n\t\t`/v9/projects/${projectId}/env`,\n\t);\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst decryptedVar = await client.tryGet<VercelEnvVar>(\n\t\t\t`/v9/projects/${projectId}/env/${env.id}?decrypt=true`,\n\t\t);\n\n\t\tif (decryptedVar) {\n\t\t\tdecrypted.push(decryptedVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n * An already-existing key (ENV_CONFLICT) is updated in place instead.\n */\nasync function createEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\ttry {\n\t\treturn await client.post<VercelEnvVar>(`/v10/projects/${projectId}/env`, {\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\ttype: \"encrypted\",\n\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t});\n\t} catch (error) {\n\t\t// ENV_CONFLICT is Vercel's error code for \"key already exists\"; the client\n\t\t// surfaces the API error body in the message, so it is matched there.\n\t\tif (error instanceof Error && error.message.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(client, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(client, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow error;\n\t}\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tawait client.patch(`/v9/projects/${projectId}/env/${envId}`, { value });\n}\n\n/**\n * Deletes a single Vercel env var. Tolerates 404 (already gone) —\n * deletion is idempotent.\n */\nasync function deleteEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\ttry {\n\t\tawait client.delete(`/v9/projects/${projectId}/env/${envId}`);\n\t} catch (error) {\n\t\tif (!(error instanceof ApiNotFoundError)) {\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new VercelClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t\tinputs.teamId,\n\t\t);\n\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(client, inputs.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t\tnews.teamId,\n\t\t);\n\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(client, 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(client, news.projectId, existingId, value);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(client, news.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tconst actual = await fetchEnvVars(client, props.projectId);\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\tconst client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(client, 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\ttokenEnvVar?: 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\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\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\ttokenEnvVar: provider.tokenEnvVar,\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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"}
1
+ {"version":3,"file":"variable.cjs","names":["ApiNotFoundError","VercelClient","resolveCredential","pulumi"],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { VercelClient } from \"./client\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\ninterface VercelVariableInputs {\n\t/** Vercel API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `VercelProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: 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 * A variable that vanishes between the list and its decrypt read is skipped.\n */\nasync function fetchEnvVars(\n\tclient: VercelClient,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst list = await client.get<{ envs: VercelEnvVar[] }>(\n\t\t`/v9/projects/${projectId}/env`,\n\t);\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst decryptedVar = await client.tryGet<VercelEnvVar>(\n\t\t\t`/v9/projects/${projectId}/env/${env.id}?decrypt=true`,\n\t\t);\n\n\t\tif (decryptedVar) {\n\t\t\tdecrypted.push(decryptedVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n * An already-existing key (ENV_CONFLICT) is updated in place instead.\n */\nasync function createEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\ttry {\n\t\treturn await client.post<VercelEnvVar>(`/v10/projects/${projectId}/env`, {\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\ttype: \"encrypted\",\n\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t});\n\t} catch (error) {\n\t\t// ENV_CONFLICT is Vercel's error code for \"key already exists\"; the client\n\t\t// surfaces the API error body in the message, so it is matched there.\n\t\tif (error instanceof Error && error.message.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(client, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(client, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow error;\n\t}\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tawait client.patch(`/v9/projects/${projectId}/env/${envId}`, { value });\n}\n\n/**\n * Deletes a single Vercel env var. Tolerates 404 (already gone) —\n * deletion is idempotent.\n */\nasync function deleteEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\ttry {\n\t\tawait client.delete(`/v9/projects/${projectId}/env/${envId}`);\n\t} catch (error) {\n\t\tif (!(error instanceof ApiNotFoundError)) {\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new VercelClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t\tinputs.teamId,\n\t\t);\n\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(client, inputs.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t\tnews.teamId,\n\t\t);\n\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(client, 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(client, news.projectId, existingId, value);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(client, news.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tconst actual = await fetchEnvVars(client, props.projectId);\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\tconst client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(client, 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\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: 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\t// envIds/contentHash are state-only bookkeeping (read back by update());\n\t\t// they are deliberately NOT declared via `<name>: undefined` input\n\t\t// placeholders — undefined placeholders next to an Output-valued map input\n\t\t// deterministically failed engine serialization with \"Unexpected struct\n\t\t// type\" on this resource's create AND update (three from-zero runs,\n\t\t// 2026-07-06), the same class the project-token secret-placeholder\n\t\t// incident documented. Nothing consumes them as Outputs.\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\targs,\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\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\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\tnew VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\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.registerOutputs({});\n\t}\n}\n"],"mappings":"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"}
@@ -69,8 +69,6 @@ interface VercelVariableArgs {
69
69
  * ```
70
70
  */
71
71
  declare class VercelVariable extends pulumi.ComponentResource {
72
- /** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */
73
- readonly contentHash: pulumi.Output<string>;
74
72
  constructor(name: string, args: VercelVariableArgs, opts: VercelVariableOptions);
75
73
  }
76
74
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"variable.d.cts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAQU,oBAAA;;EAET,KAAA;EAF6B;EAK7B,WAAA;EASiB;EANjB,MAAA;EAHA;EAMA,SAAA;EAAA;EAGA,SAAA,EAAW,MAAM;AAAA;;UAIR,qBAAA,SAA8B,oBAAoB;EAAlD;EAET,MAAA,EAAQ,MAAA;;EAGR,WAAA;AAAA;;;;;AAAW;cAsIC,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAwBpB,MAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAiDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA+BpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,qBAAA,GAAwB,OAAA;EAiBnD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAwCtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EAjJJ,qCAqJH,QAAA,EAAU,cAAA;EAjGC;;;;EAuGX,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EA/LL;;;;EAoMX,SAAA,GAAY,MAAA,CAAO,KAAA;EApMO;EAuM1B,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;cAgBlC,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAvLhB;EAAA,SAyLV,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
1
+ {"version":3,"file":"variable.d.cts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAQU,oBAAA;;EAET,KAAA;EAF6B;EAK7B,WAAA;EASiB;EANjB,MAAA;EAHA;EAMA,SAAA;EAAA;EAGA,SAAA,EAAW,MAAM;AAAA;;UAIR,qBAAA,SAA8B,oBAAoB;EAAlD;EAET,MAAA,EAAQ,MAAA;;EAGR,WAAA;AAAA;;;;;AAAW;cAsIC,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAwBpB,MAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAiDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA+BpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,qBAAA,GAAwB,OAAA;EAiBnD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA6CtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EAtJJ,qCA0JH,QAAA,EAAU,cAAA;EAtGC;;;;EA4GX,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EApML;;;;EAyMX,SAAA,GAAY,MAAA,CAAO,KAAA;EAzMO;EA4M1B,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;cAgBlC,cAAA,SAAuB,MAAA,CAAO,iBAAA;cAEzC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
@@ -69,8 +69,6 @@ interface VercelVariableArgs {
69
69
  * ```
70
70
  */
71
71
  declare class VercelVariable extends pulumi.ComponentResource {
72
- /** SHA-256 hash of all key-value pairs. Use as a deploy trigger for drift-aware redeployment. */
73
- readonly contentHash: pulumi.Output<string>;
74
72
  constructor(name: string, args: VercelVariableArgs, opts: VercelVariableOptions);
75
73
  }
76
74
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"variable.d.mts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAQU,oBAAA;;EAET,KAAA;EAF6B;EAK7B,WAAA;EASiB;EANjB,MAAA;EAHA;EAMA,SAAA;EAAA;EAGA,SAAA,EAAW,MAAM;AAAA;;UAIR,qBAAA,SAA8B,oBAAoB;EAAlD;EAET,MAAA,EAAQ,MAAA;;EAGR,WAAA;AAAA;;;;;AAAW;cAsIC,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAwBpB,MAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAiDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA+BpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,qBAAA,GAAwB,OAAA;EAiBnD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAwCtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EAjJJ,qCAqJH,QAAA,EAAU,cAAA;EAjGC;;;;EAuGX,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EA/LL;;;;EAoMX,SAAA,GAAY,MAAA,CAAO,KAAA;EApMO;EAuM1B,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;cAgBlC,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAvLhB;EAAA,SAyLV,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
1
+ {"version":3,"file":"variable.d.mts","names":[],"sources":["../../src/vercel/variable.ts"],"mappings":";;;;;;;UAQU,oBAAA;;EAET,KAAA;EAF6B;EAK7B,WAAA;EASiB;EANjB,MAAA;EAHA;EAMA,SAAA;EAAA;EAGA,SAAA,EAAW,MAAM;AAAA;;UAIR,qBAAA,SAA8B,oBAAoB;EAAlD;EAET,MAAA,EAAQ,MAAA;;EAGR,WAAA;AAAA;;;;;AAAW;cAsIC,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAwBpB,MAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAiDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA+BpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,qBAAA,GAAwB,OAAA;EAiBnD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA6CtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EAtJJ,qCA0JH,QAAA,EAAU,cAAA;EAtGC;;;;EA4GX,OAAA,GAAU,aAAA;AAAA;;UAIM,kBAAA;EApML;;;;EAyMX,SAAA,GAAY,MAAA,CAAO,KAAA;EAzMO;EA4M1B,SAAA,EAAW,MAAA,CAAO,KAAA,CAAM,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;cAgBlC,cAAA,SAAuB,MAAA,CAAO,iBAAA;cAEzC,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
2
  import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
+ import { resolveCredential } from "../dynamic/resolve-credential.mjs";
4
4
  import { VercelClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -162,11 +162,7 @@ var VercelVariableResourceProvider = class {
162
162
  /** Internal dynamic resource — not part of the public API. */
163
163
  var VercelVariableResource = class extends pulumi.dynamic.Resource {
164
164
  constructor(name, args, opts) {
165
- super(new VercelVariableResourceProvider(), name, {
166
- ...args,
167
- envIds: void 0,
168
- contentHash: void 0
169
- }, {
165
+ super(new VercelVariableResourceProvider(), name, args, {
170
166
  ...opts,
171
167
  additionalSecretOutputs: ["token"]
172
168
  });
@@ -191,15 +187,14 @@ var VercelVariable = class extends pulumi.ComponentResource {
191
187
  super("infracraft:vercel:Variable", name, {}, pulumiOpts);
192
188
  const projectId = project ? project.id : args.projectId;
193
189
  if (!projectId) throw new Error("VercelVariable: either `args.projectId` or `opts.project` must be provided");
194
- const resource = new VercelVariableResource(`${name}-resource`, {
190
+ new VercelVariableResource(`${name}-resource`, {
195
191
  token: provider.token,
196
192
  tokenEnvVar: provider.tokenEnvVar,
197
193
  teamId: provider.teamId,
198
194
  projectId,
199
195
  variables: args.variables
200
196
  }, { parent: this });
201
- this.contentHash = resource.contentHash;
202
- this.registerOutputs({ contentHash: this.contentHash });
197
+ this.registerOutputs({});
203
198
  }
204
199
  };
205
200
 
@@ -1 +1 @@
1
- {"version":3,"file":"variable.mjs","names":[],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { VercelClient } from \"./client\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\ninterface VercelVariableInputs {\n\t/** Vercel API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `VercelProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: 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 * A variable that vanishes between the list and its decrypt read is skipped.\n */\nasync function fetchEnvVars(\n\tclient: VercelClient,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst list = await client.get<{ envs: VercelEnvVar[] }>(\n\t\t`/v9/projects/${projectId}/env`,\n\t);\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst decryptedVar = await client.tryGet<VercelEnvVar>(\n\t\t\t`/v9/projects/${projectId}/env/${env.id}?decrypt=true`,\n\t\t);\n\n\t\tif (decryptedVar) {\n\t\t\tdecrypted.push(decryptedVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n * An already-existing key (ENV_CONFLICT) is updated in place instead.\n */\nasync function createEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\ttry {\n\t\treturn await client.post<VercelEnvVar>(`/v10/projects/${projectId}/env`, {\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\ttype: \"encrypted\",\n\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t});\n\t} catch (error) {\n\t\t// ENV_CONFLICT is Vercel's error code for \"key already exists\"; the client\n\t\t// surfaces the API error body in the message, so it is matched there.\n\t\tif (error instanceof Error && error.message.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(client, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(client, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow error;\n\t}\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tawait client.patch(`/v9/projects/${projectId}/env/${envId}`, { value });\n}\n\n/**\n * Deletes a single Vercel env var. Tolerates 404 (already gone) —\n * deletion is idempotent.\n */\nasync function deleteEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\ttry {\n\t\tawait client.delete(`/v9/projects/${projectId}/env/${envId}`);\n\t} catch (error) {\n\t\tif (!(error instanceof ApiNotFoundError)) {\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new VercelClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t\tinputs.teamId,\n\t\t);\n\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(client, inputs.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t\tnews.teamId,\n\t\t);\n\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(client, 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(client, news.projectId, existingId, value);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(client, news.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tconst actual = await fetchEnvVars(client, props.projectId);\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\tconst client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(client, 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\ttokenEnvVar?: 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\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\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\ttokenEnvVar: provider.tokenEnvVar,\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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"}
1
+ {"version":3,"file":"variable.mjs","names":[],"sources":["../../src/vercel/variable.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { VercelClient } from \"./client\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Resolved inputs for the Vercel variable dynamic provider. */\ninterface VercelVariableInputs {\n\t/** Vercel API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `VercelProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: 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 * A variable that vanishes between the list and its decrypt read is skipped.\n */\nasync function fetchEnvVars(\n\tclient: VercelClient,\n\tprojectId: string,\n): Promise<VercelEnvVar[]> {\n\tconst list = await client.get<{ envs: VercelEnvVar[] }>(\n\t\t`/v9/projects/${projectId}/env`,\n\t);\n\n\tconst decrypted: VercelEnvVar[] = [];\n\n\tfor (const env of list.envs) {\n\t\tconst decryptedVar = await client.tryGet<VercelEnvVar>(\n\t\t\t`/v9/projects/${projectId}/env/${env.id}?decrypt=true`,\n\t\t);\n\n\t\tif (decryptedVar) {\n\t\t\tdecrypted.push(decryptedVar);\n\t\t}\n\t}\n\n\treturn decrypted;\n}\n\n/**\n * Creates a single Vercel env var targeting all environments.\n * An already-existing key (ENV_CONFLICT) is updated in place instead.\n */\nasync function createEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tkey: string,\n\tvalue: string,\n): Promise<VercelEnvVar> {\n\ttry {\n\t\treturn await client.post<VercelEnvVar>(`/v10/projects/${projectId}/env`, {\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\ttype: \"encrypted\",\n\t\t\ttarget: [\"production\", \"preview\", \"development\"],\n\t\t});\n\t} catch (error) {\n\t\t// ENV_CONFLICT is Vercel's error code for \"key already exists\"; the client\n\t\t// surfaces the API error body in the message, so it is matched there.\n\t\tif (error instanceof Error && error.message.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(client, projectId);\n\t\t\tconst match = existing.find((e) => e.key === key);\n\n\t\t\tif (match) {\n\t\t\t\tawait updateEnvVar(client, projectId, match.id, value);\n\n\t\t\t\treturn { ...match, value };\n\t\t\t}\n\t\t}\n\n\t\tthrow error;\n\t}\n}\n\n/**\n * Updates a single Vercel env var value.\n */\nasync function updateEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n\tvalue: string,\n): Promise<void> {\n\tawait client.patch(`/v9/projects/${projectId}/env/${envId}`, { value });\n}\n\n/**\n * Deletes a single Vercel env var. Tolerates 404 (already gone) —\n * deletion is idempotent.\n */\nasync function deleteEnvVar(\n\tclient: VercelClient,\n\tprojectId: string,\n\tenvId: string,\n): Promise<void> {\n\ttry {\n\t\tawait client.delete(`/v9/projects/${projectId}/env/${envId}`);\n\t} catch (error) {\n\t\tif (!(error instanceof ApiNotFoundError)) {\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Vercel project environment variables.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelVariableResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelVariableInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new VercelClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t\tinputs.teamId,\n\t\t);\n\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(client, inputs.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t\tnews.teamId,\n\t\t);\n\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(client, 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(client, news.projectId, existingId, value);\n\t\t\t} else if (!existingId) {\n\t\t\t\tconst result = await createEnvVar(client, news.projectId, key, value);\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 client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tconst actual = await fetchEnvVars(client, props.projectId);\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\tconst client = new VercelClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t\tprops.teamId,\n\t\t);\n\n\t\tfor (const [key, envId] of Object.entries(props.envIds)) {\n\t\t\ttry {\n\t\t\t\tawait deleteEnvVar(client, 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\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: 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\t// envIds/contentHash are state-only bookkeeping (read back by update());\n\t\t// they are deliberately NOT declared via `<name>: undefined` input\n\t\t// placeholders — undefined placeholders next to an Output-valued map input\n\t\t// deterministically failed engine serialization with \"Unexpected struct\n\t\t// type\" on this resource's create AND update (three from-zero runs,\n\t\t// 2026-07-06), the same class the project-token secret-placeholder\n\t\t// incident documented. Nothing consumes them as Outputs.\n\t\tsuper(\n\t\t\tnew VercelVariableResourceProvider(),\n\t\t\tname,\n\t\t\targs,\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\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\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\tnew VercelVariableResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\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.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;;;AAqCA,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;;;;;AAeA,eAAe,aACd,QACA,WAC0B;CAC1B,MAAM,OAAO,MAAM,OAAO,IACzB,gBAAgB,UAAU,KAC3B;CAEA,MAAM,YAA4B,CAAC;CAEnC,KAAK,MAAM,OAAO,KAAK,MAAM;EAC5B,MAAM,eAAe,MAAM,OAAO,OACjC,gBAAgB,UAAU,OAAO,IAAI,GAAG,cACzC;EAEA,IAAI,cACH,UAAU,KAAK,YAAY;CAE7B;CAEA,OAAO;AACR;;;;;AAMA,eAAe,aACd,QACA,WACA,KACA,OACwB;CACxB,IAAI;EACH,OAAO,MAAM,OAAO,KAAmB,iBAAiB,UAAU,OAAO;GACxE;GACA;GACA,MAAM;GACN,QAAQ;IAAC;IAAc;IAAW;GAAa;EAChD,CAAC;CACF,SAAS,OAAO;EAGf,IAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,cAAc,GAAG;GACrE,OAAO,IAAI,KACV,mBAAmB,IAAI,sCACxB;GAGA,MAAM,SAAQ,MADS,aAAa,QAAQ,SAAS,GAC9B,MAAM,MAAM,EAAE,QAAQ,GAAG;GAEhD,IAAI,OAAO;IACV,MAAM,aAAa,QAAQ,WAAW,MAAM,IAAI,KAAK;IAErD,OAAO;KAAE,GAAG;KAAO;IAAM;GAC1B;EACD;EAEA,MAAM;CACP;AACD;;;;AAKA,eAAe,aACd,QACA,WACA,OACA,OACgB;CAChB,MAAM,OAAO,MAAM,gBAAgB,UAAU,OAAO,SAAS,EAAE,MAAM,CAAC;AACvE;;;;;AAMA,eAAe,aACd,QACA,WACA,OACgB;CAChB,IAAI;EACH,MAAM,OAAO,OAAO,gBAAgB,UAAU,OAAO,OAAO;CAC7D,SAAS,OAAO;EACf,IAAI,EAAE,iBAAiB,mBACtB,MAAM;CAER;AACD;;;;;;AAOA,IAAa,iCAAb,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAI,aAClB,kBAAkB,OAAO,OAAO,OAAO,WAAW,GAClD,OAAO,MACR;EAEA,MAAM,SAAiC,CAAC;EAExC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,SAAS,GAGzD,OAAO,QAAO,MAFO,aAAa,QAAQ,OAAO,WAAW,KAAK,KAAK,GAEjD;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,IAAI,aAClB,kBAAkB,KAAK,OAAO,KAAK,WAAW,GAC9C,KAAK,MACN;EAEA,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,QAAQ,KAAK,WAAW,KAAK;IACjD,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,aAAa,QAAQ,KAAK,WAAW,YAAY,KAAK;QACtD,IAAI,CAAC,YAGX,OAAO,QAAO,MAFO,aAAa,QAAQ,KAAK,WAAW,KAAK,KAAK,GAE/C;EAEvB;EAEA,OAAO,EACN,MAAM;GACL,GAAG;GACH;GACA,aAAa,MAAM,cAAc,KAAK,SAAS;EAChD,EACD;CACD;CAEA,MAAM,KACL,IACA,OACqC;EAMrC,MAAM,SAAS,MAAM,aAAa,IALf,aAClB,kBAAkB,MAAM,OAAO,MAAM,WAAW,GAChD,MAAM,MAGgC,GAAG,MAAM,SAAS;EAEzD,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,MAAM,SAAS,IAAI,aAClB,kBAAkB,MAAM,OAAO,MAAM,WAAW,GAChD,MAAM,MACP;EAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,MAAM,GACrD,IAAI;GACH,MAAM,aAAa,QAAQ,MAAM,WAAW,KAAK;EAClD,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;CAC5D,YACC,MACA,MAOA,MACC;EAQD,MACC,IAAI,+BAA+B,GACnC,MACA,MAEA;GAAE,GAAG;GAAM,yBAAyB,CAAC,OAAO;EAAE,CAC/C;CACD;AACD;;;;;;;;;;;;;;AA0CA,IAAa,iBAAb,cAAoC,OAAO,kBAAkB;CAC5D,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,IAAI,uBACH,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,aAAa,SAAS;GACtB,QAAQ,SAAS;GACjB;GACA,WAAW,KAAK;EACjB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infracraft/pulumi",
3
- "version": "1.30.0",
3
+ "version": "1.30.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Native Pulumi providers for Railway, Neon, Vercel, and Fly.io with adopt-or-create semantics and deploy orchestration. No Terraform bridge.",