@infracraft/pulumi 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/neon/branch.cjs +3 -11
- package/dist/neon/branch.cjs.map +1 -1
- package/dist/neon/branch.d.cts +3 -11
- package/dist/neon/branch.d.cts.map +1 -1
- package/dist/neon/branch.d.mts +3 -11
- package/dist/neon/branch.d.mts.map +1 -1
- package/dist/neon/branch.mjs +3 -11
- package/dist/neon/branch.mjs.map +1 -1
- package/dist/railway/environment.cjs +6 -13
- package/dist/railway/environment.cjs.map +1 -1
- package/dist/railway/environment.d.cts +4 -10
- package/dist/railway/environment.d.cts.map +1 -1
- package/dist/railway/environment.d.mts +4 -10
- package/dist/railway/environment.d.mts.map +1 -1
- package/dist/railway/environment.mjs +6 -13
- package/dist/railway/environment.mjs.map +1 -1
- package/dist/vercel/project.cjs +6 -13
- package/dist/vercel/project.cjs.map +1 -1
- package/dist/vercel/project.d.cts +4 -11
- package/dist/vercel/project.d.cts.map +1 -1
- package/dist/vercel/project.d.mts +4 -11
- package/dist/vercel/project.d.mts.map +1 -1
- package/dist/vercel/project.mjs +6 -13
- package/dist/vercel/project.mjs.map +1 -1
- package/package.json +1 -1
package/dist/neon/branch.cjs
CHANGED
|
@@ -23,7 +23,6 @@ var NeonBranchResourceProvider = class {
|
|
|
23
23
|
async create(inputs) {
|
|
24
24
|
const client = new require_neon_client.NeonClient(inputs.apiKey);
|
|
25
25
|
let branchId = await findBranchByName(client, inputs.projectId, inputs.name);
|
|
26
|
-
const wasAdopted = branchId !== void 0;
|
|
27
26
|
if (branchId) if (inputs.parentName) _pulumi_pulumi.log.warn(`Adopting existing Neon branch "${inputs.name}" — parentName "${inputs.parentName}" is ignored for adopted branches (existing lineage preserved).`);
|
|
28
27
|
else _pulumi_pulumi.log.info(`Adopting existing Neon branch "${inputs.name}" (${branchId})`);
|
|
29
28
|
else {
|
|
@@ -37,10 +36,7 @@ var NeonBranchResourceProvider = class {
|
|
|
37
36
|
}
|
|
38
37
|
return {
|
|
39
38
|
id: branchId,
|
|
40
|
-
outs: {
|
|
41
|
-
...inputs,
|
|
42
|
-
wasAdopted
|
|
43
|
-
}
|
|
39
|
+
outs: { ...inputs }
|
|
44
40
|
};
|
|
45
41
|
}
|
|
46
42
|
async read(id, props) {
|
|
@@ -54,14 +50,10 @@ var NeonBranchResourceProvider = class {
|
|
|
54
50
|
};
|
|
55
51
|
}
|
|
56
52
|
/**
|
|
57
|
-
* Deletes the branch
|
|
58
|
-
*
|
|
53
|
+
* Deletes the branch. Protection of shared/production branches is the consumer's
|
|
54
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
59
55
|
*/
|
|
60
56
|
async delete(id, props) {
|
|
61
|
-
if (props.wasAdopted !== false) {
|
|
62
|
-
_pulumi_pulumi.log.warn(`Neon branch "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
57
|
const client = new require_neon_client.NeonClient(props.apiKey);
|
|
66
58
|
try {
|
|
67
59
|
await client.delete(`/projects/${props.projectId}/branches/${id}`);
|
package/dist/neon/branch.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/branch.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon branch dynamic provider. */\nexport interface NeonBranchInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID (e.g. `\"quiet-forest-69719462\"`). */\n\tprojectId: string;\n\n\t/** Branch display name (e.g. `\"production\"`, `\"development\"`). */\n\tname: string;\n\n\t/**\n\t * Parent branch NAME. Resolved to `parent_id` inside `create()` via\n\t * `findBranchByName`. Omit for project-root branches.\n\t */\n\tparentName?: string;\n}\n\n/** Persisted state for the Neon branch. */\ninterface NeonBranchOutputs extends NeonBranchInputs {\n\t/**\n\t * Whether this branch already existed and was adopted (vs. created by us).\n\t * `delete()` only removes branches we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive\n\t * default) so a feature env's destroy never deletes the adopted production branch.\n\t */\n\twasAdopted?: boolean;\n}\n\n/** Neon API response for a branch. */\ninterface BranchResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for branch creation. */\ninterface BranchCreateResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for listing branches. */\ninterface BranchListResponse {\n\tbranches: Array<{\n\t\tid: string;\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Finds an existing Neon branch by name within a project.\n */\nasync function findBranchByName(\n\tclient: NeonClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.get<BranchListResponse>(\n\t\t`/projects/${projectId}/branches`,\n\t);\n\n\tconst match = result.branches.find((b) => b.name === name);\n\n\treturn match?.id;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon branches.\n *\n * Uses adopt-or-create on `create()`: finds an existing branch by name\n * before creating a new one. When `parentName` is supplied, resolves it\n * to a `parent_id` and includes it in the POST body.\n */\n/** @internal Exported only for unit testing; not part of the public API surface. */\nexport class NeonBranchResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tlet branchId = await findBranchByName(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst wasAdopted = branchId !== undefined;\n\n\t\tif (branchId) {\n\t\t\tif (inputs.parentName) {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" — parentName \"${inputs.parentName}\" is ignored for adopted branches (existing lineage preserved).`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpulumi.log.info(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" (${branchId})`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tconst body: { branch: { name: string; parent_id?: string } } = {\n\t\t\t\tbranch: { name: inputs.name },\n\t\t\t};\n\n\t\t\tif (inputs.parentName) {\n\t\t\t\tconst parentId = await findBranchByName(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.parentName,\n\t\t\t\t);\n\n\t\t\t\tif (!parentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Neon parent branch \"${inputs.parentName}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tbody.branch.parent_id = parentId;\n\t\t\t}\n\n\t\t\tconst result = await client.post<BranchCreateResponse>(\n\t\t\t\t`/projects/${inputs.projectId}/branches`,\n\t\t\t\tbody,\n\t\t\t);\n\n\t\t\tbranchId = result.branch.id;\n\t\t}\n\n\t\treturn { id: branchId, outs: { ...inputs, wasAdopted } };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonBranchOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst result = await client.get<BranchResponse>(\n\t\t\t`/projects/${props.projectId}/branches/${id}`,\n\t\t);\n\n\t\t// parentName is preserved from prior state (props): the Neon API does not expose\n\t\t// a branch's parent on GET /branches/:id, so it cannot be re-derived here.\n\t\treturn {\n\t\t\tid: result.branch.id,\n\t\t\tprops: { ...props, name: result.branch.name },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the branch only if we created it. Adopted branches (and state written\n\t * before `wasAdopted` existed) are left untouched.\n\t */\n\tasync delete(id: string, props: NeonBranchOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Neon branch \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(`/projects/${props.projectId}/branches/${id}`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(`Failed to delete Neon branch (may already be deleted)`);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonBranchOutputs,\n\t\tnews: NeonBranchInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.parentName !== news.parentName) {\n\t\t\treplaces.push(\"parentName\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || olds.name !== news.name,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonBranchResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly branchId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\t/** Name of the parent branch; omit for project-root branches. */\n\t\t\tparentName?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonBranchResourceProvider(), name, { ...args }, opts);\n\t}\n}\n\n/** Options type for NeonBranch — replaces Pulumi's native `provider` field. */\ntype NeonBranchOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n};\n\n/** Args for NeonBranch. */\nexport interface NeonBranchArgs {\n\t/** Branch display name. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of the parent branch to branch from (copy-on-write). Omit to\n\t * branch from the project root (Neon default branch).\n\t */\n\tparent?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Neon branch with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * // Root branch (Neon default parent)\n * const production = new NeonBranch(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Copy-on-write branch from production\n * const staging = new NeonBranch(\"staging\", {\n * name: \"staging\",\n * parent: \"production\",\n * }, { provider, project });\n * ```\n */\nexport class NeonBranch extends pulumi.ComponentResource {\n\t/** Neon branch ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonBranchArgs, opts: NeonBranchOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Branch\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonBranchResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tparentName: args.parent,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.id;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;AA+DA,eAAe,iBACd,QACA,WACA,MAC8B;CAO9B,QAFc,MAJO,OAAO,IAC3B,aAAa,UAAU,UACxB,GAEqB,SAAS,MAAM,MAAM,EAAE,SAAS,IAE1C,GAAG;AACf;;;;;;;;;AAUA,IAAa,6BAAb,MAEA;CACC,MAAM,OAAO,QAAgE;EAC5E,MAAM,SAAS,IAAIA,+BAAW,OAAO,MAAM;EAE3C,IAAI,WAAW,MAAM,iBACpB,QACA,OAAO,WACP,OAAO,IACR;EAEA,MAAM,aAAa,aAAa;EAEhC,IAAI,UACH,IAAI,OAAO,YACV,eAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,kBAAkB,OAAO,WAAW,gEACnF;OAEA,eAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,KAAK,SAAS,EAC7D;OAEK;GACN,MAAM,OAAyD,EAC9D,QAAQ,EAAE,MAAM,OAAO,KAAK,EAC7B;GAEA,IAAI,OAAO,YAAY;IACtB,MAAM,WAAW,MAAM,iBACtB,QACA,OAAO,WACP,OAAO,UACR;IAEA,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,uBAAuB,OAAO,WAAW,yBAAyB,OAAO,WAC1E;IAGD,KAAK,OAAO,YAAY;GACzB;GAOA,YAAW,MALU,OAAO,KAC3B,aAAa,OAAO,UAAU,YAC9B,IACD,GAEkB,OAAO;EAC1B;EAEA,OAAO;GAAE,IAAI;GAAU,MAAM;IAAE,GAAG;IAAQ;GAAW;EAAE;CACxD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFFA,+BAAW,MAAM,MAEV,EAAE,IAC3B,aAAa,MAAM,UAAU,YAAY,IAC1C;EAIA,OAAO;GACN,IAAI,OAAO,OAAO;GAClB,OAAO;IAAE,GAAG;IAAO,MAAM,OAAO,OAAO;GAAK;EAC7C;CACD;;;;;CAMA,MAAM,OAAO,IAAY,OAAyC;EACjE,IAAI,MAAM,eAAe,OAAO;GAC/B,eAAO,IAAI,KACV,gBAAgB,MAAM,KAAK,qDAC5B;GAEA;EACD;EAEA,MAAM,SAAS,IAAIA,+BAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OAAO,aAAa,MAAM,UAAU,YAAY,IAAI;EAClE,QAAQ;GACP,eAAO,IAAI,KAAK,uDAAuD;EACxE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,eAAe,KAAK,YAC5B,SAAS,KAAK,YAAY;EAG3B,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,KAAK,SAAS,KAAK;GACnD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,qBAAN,cAAiCC,eAAO,QAAQ,SAAS;CAGxD,YACC,MACA,MAOA,MACC;EACD,MAAM,IAAI,2BAA2B,GAAG,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI;CAChE;AACD;;;;;;;;;;;;;;;;;;AAwCA,IAAa,aAAb,cAAgCA,eAAO,kBAAkB;CAIxD,YAAY,MAAc,MAAsB,MAAyB;EACxE,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,0BAA0B,MAAM,CAAC,GAAG,UAAU;EAEpD,MAAM,WAAW,IAAI,mBACpB,GAAG,KAAK,YACR;GACC,QAAQ,SAAS;GACjB,WAAW,QAAQ;GACnB,MAAM,KAAK;GACX,YAAY,KAAK;EAClB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
|
|
1
|
+
{"version":3,"file":"branch.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/branch.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon branch dynamic provider. */\nexport interface NeonBranchInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID (e.g. `\"quiet-forest-69719462\"`). */\n\tprojectId: string;\n\n\t/** Branch display name (e.g. `\"production\"`, `\"development\"`). */\n\tname: string;\n\n\t/**\n\t * Parent branch NAME. Resolved to `parent_id` inside `create()` via\n\t * `findBranchByName`. Omit for project-root branches.\n\t */\n\tparentName?: string;\n}\n\n/** Persisted state for the Neon branch. */\ninterface NeonBranchOutputs extends NeonBranchInputs {}\n\n/** Neon API response for a branch. */\ninterface BranchResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for branch creation. */\ninterface BranchCreateResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for listing branches. */\ninterface BranchListResponse {\n\tbranches: Array<{\n\t\tid: string;\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Finds an existing Neon branch by name within a project.\n */\nasync function findBranchByName(\n\tclient: NeonClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.get<BranchListResponse>(\n\t\t`/projects/${projectId}/branches`,\n\t);\n\n\tconst match = result.branches.find((b) => b.name === name);\n\n\treturn match?.id;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon branches.\n *\n * Uses adopt-or-create on `create()`: finds an existing branch by name\n * before creating a new one. When `parentName` is supplied, resolves it\n * to a `parent_id` and includes it in the POST body.\n */\n/** @internal Exported only for unit testing; not part of the public API surface. */\nexport class NeonBranchResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tlet branchId = await findBranchByName(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (branchId) {\n\t\t\tif (inputs.parentName) {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" — parentName \"${inputs.parentName}\" is ignored for adopted branches (existing lineage preserved).`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpulumi.log.info(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" (${branchId})`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tconst body: { branch: { name: string; parent_id?: string } } = {\n\t\t\t\tbranch: { name: inputs.name },\n\t\t\t};\n\n\t\t\tif (inputs.parentName) {\n\t\t\t\tconst parentId = await findBranchByName(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.parentName,\n\t\t\t\t);\n\n\t\t\t\tif (!parentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Neon parent branch \"${inputs.parentName}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tbody.branch.parent_id = parentId;\n\t\t\t}\n\n\t\t\tconst result = await client.post<BranchCreateResponse>(\n\t\t\t\t`/projects/${inputs.projectId}/branches`,\n\t\t\t\tbody,\n\t\t\t);\n\n\t\t\tbranchId = result.branch.id;\n\t\t}\n\n\t\treturn { id: branchId, outs: { ...inputs } };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonBranchOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst result = await client.get<BranchResponse>(\n\t\t\t`/projects/${props.projectId}/branches/${id}`,\n\t\t);\n\n\t\t// parentName is preserved from prior state (props): the Neon API does not expose\n\t\t// a branch's parent on GET /branches/:id, so it cannot be re-derived here.\n\t\treturn {\n\t\t\tid: result.branch.id,\n\t\t\tprops: { ...props, name: result.branch.name },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the branch. Protection of shared/production branches is the consumer's\n\t * responsibility via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(id: string, props: NeonBranchOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(`/projects/${props.projectId}/branches/${id}`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(`Failed to delete Neon branch (may already be deleted)`);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonBranchOutputs,\n\t\tnews: NeonBranchInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.parentName !== news.parentName) {\n\t\t\treplaces.push(\"parentName\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || olds.name !== news.name,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonBranchResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly branchId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\t/** Name of the parent branch; omit for project-root branches. */\n\t\t\tparentName?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonBranchResourceProvider(), name, { ...args }, opts);\n\t}\n}\n\n/** Options type for NeonBranch — replaces Pulumi's native `provider` field. */\ntype NeonBranchOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n};\n\n/** Args for NeonBranch. */\nexport interface NeonBranchArgs {\n\t/** Branch display name. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of the parent branch to branch from (copy-on-write). Omit to\n\t * branch from the project root (Neon default branch).\n\t */\n\tparent?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Neon branch with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * // Root branch (Neon default parent)\n * const production = new NeonBranch(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Copy-on-write branch from production\n * const staging = new NeonBranch(\"staging\", {\n * name: \"staging\",\n * parent: \"production\",\n * }, { provider, project });\n * ```\n */\nexport class NeonBranch extends pulumi.ComponentResource {\n\t/** Neon branch ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonBranchArgs, opts: NeonBranchOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Branch\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonBranchResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tparentName: args.parent,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.id;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;;;AAuDA,eAAe,iBACd,QACA,WACA,MAC8B;CAO9B,QAFc,MAJO,OAAO,IAC3B,aAAa,UAAU,UACxB,GAEqB,SAAS,MAAM,MAAM,EAAE,SAAS,IAE1C,GAAG;AACf;;;;;;;;;AAUA,IAAa,6BAAb,MAEA;CACC,MAAM,OAAO,QAAgE;EAC5E,MAAM,SAAS,IAAIA,+BAAW,OAAO,MAAM;EAE3C,IAAI,WAAW,MAAM,iBACpB,QACA,OAAO,WACP,OAAO,IACR;EAEA,IAAI,UACH,IAAI,OAAO,YACV,eAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,kBAAkB,OAAO,WAAW,gEACnF;OAEA,eAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,KAAK,SAAS,EAC7D;OAEK;GACN,MAAM,OAAyD,EAC9D,QAAQ,EAAE,MAAM,OAAO,KAAK,EAC7B;GAEA,IAAI,OAAO,YAAY;IACtB,MAAM,WAAW,MAAM,iBACtB,QACA,OAAO,WACP,OAAO,UACR;IAEA,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,uBAAuB,OAAO,WAAW,yBAAyB,OAAO,WAC1E;IAGD,KAAK,OAAO,YAAY;GACzB;GAOA,YAAW,MALU,OAAO,KAC3B,aAAa,OAAO,UAAU,YAC9B,IACD,GAEkB,OAAO;EAC1B;EAEA,OAAO;GAAE,IAAI;GAAU,MAAM,EAAE,GAAG,OAAO;EAAE;CAC5C;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFFA,+BAAW,MAAM,MAEV,EAAE,IAC3B,aAAa,MAAM,UAAU,YAAY,IAC1C;EAIA,OAAO;GACN,IAAI,OAAO,OAAO;GAClB,OAAO;IAAE,GAAG;IAAO,MAAM,OAAO,OAAO;GAAK;EAC7C;CACD;;;;;CAMA,MAAM,OAAO,IAAY,OAAyC;EACjE,MAAM,SAAS,IAAIA,+BAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OAAO,aAAa,MAAM,UAAU,YAAY,IAAI;EAClE,QAAQ;GACP,eAAO,IAAI,KAAK,uDAAuD;EACxE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,eAAe,KAAK,YAC5B,SAAS,KAAK,YAAY;EAG3B,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,KAAK,SAAS,KAAK;GACnD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,qBAAN,cAAiCC,eAAO,QAAQ,SAAS;CAGxD,YACC,MACA,MAOA,MACC;EACD,MAAM,IAAI,2BAA2B,GAAG,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI;CAChE;AACD;;;;;;;;;;;;;;;;;;AAwCA,IAAa,aAAb,cAAgCA,eAAO,kBAAkB;CAIxD,YAAY,MAAc,MAAsB,MAAyB;EACxE,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,0BAA0B,MAAM,CAAC,GAAG,UAAU;EAEpD,MAAM,WAAW,IAAI,mBACpB,GAAG,KAAK,YACR;GACC,QAAQ,SAAS;GACjB,WAAW,QAAQ;GACnB,MAAM,KAAK;GACX,YAAY,KAAK;EAClB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
|
package/dist/neon/branch.d.cts
CHANGED
|
@@ -19,15 +19,7 @@ interface NeonBranchInputs {
|
|
|
19
19
|
parentName?: string;
|
|
20
20
|
}
|
|
21
21
|
/** Persisted state for the Neon branch. */
|
|
22
|
-
interface NeonBranchOutputs extends NeonBranchInputs {
|
|
23
|
-
/**
|
|
24
|
-
* Whether this branch already existed and was adopted (vs. created by us).
|
|
25
|
-
* `delete()` only removes branches we created. Absent on state written before
|
|
26
|
-
* this field existed, which is treated as adopted (the safe, non-destructive
|
|
27
|
-
* default) so a feature env's destroy never deletes the adopted production branch.
|
|
28
|
-
*/
|
|
29
|
-
wasAdopted?: boolean;
|
|
30
|
-
}
|
|
22
|
+
interface NeonBranchOutputs extends NeonBranchInputs {}
|
|
31
23
|
/**
|
|
32
24
|
* Dynamic provider implementing CRUD for Neon branches.
|
|
33
25
|
*
|
|
@@ -40,8 +32,8 @@ declare class NeonBranchResourceProvider implements pulumi.dynamic.ResourceProvi
|
|
|
40
32
|
create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
41
33
|
read(id: string, props: NeonBranchOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
42
34
|
/**
|
|
43
|
-
* Deletes the branch
|
|
44
|
-
*
|
|
35
|
+
* Deletes the branch. Protection of shared/production branches is the consumer's
|
|
36
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
45
37
|
*/
|
|
46
38
|
delete(id: string, props: NeonBranchOutputs): Promise<void>;
|
|
47
39
|
diff(_id: string, olds: NeonBranchOutputs, news: NeonBranchInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.d.cts","names":[],"sources":["../../src/neon/branch.ts"],"mappings":";;;;;;;UAMiB,gBAAA;;EAEhB,MAAA;EAFgC;EAKhC,SAAA;EALgC;EAQhC,IAAA;EAHA;;;;EASA,UAAA;AAAA;;UAIS,iBAAA,SAA0B,gBAAgB;
|
|
1
|
+
{"version":3,"file":"branch.d.cts","names":[],"sources":["../../src/neon/branch.ts"],"mappings":";;;;;;;UAMiB,gBAAA;;EAEhB,MAAA;EAFgC;EAKhC,SAAA;EALgC;EAQhC,IAAA;EAHA;;;;EASA,UAAA;AAAA;;UAIS,iBAAA,SAA0B,gBAAgB;AAAA;AAqDpD;;;;;;;cAAa,0BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CAAO,MAAA,EAAQ,gBAAA,GAAmB,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAmDzD,IAAA,CACL,EAAA,UACA,KAAA,EAAO,iBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAmB0B;;;;EAA9C,MAAA,CAAO,EAAA,UAAY,KAAA,EAAO,iBAAA,GAAoB,OAAA;EAU9C,IAAA,CACL,GAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,gBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAuCtB,iBAAA,GAAoB,IAAA,CAAK,MAAA,CAAO,wBAAA;EAhIlB,mCAkIlB,QAAA,EAAU,YAAA,EAhIJ;EAmIN,OAAA,EAAS,WAAA;AAAA;;UAIO,cAAA;EAvIuC;EAyIvD,IAAA,EAAM,MAAA,CAAO,KAAA;EAtFP;;;;EA4FN,MAAA,GAAS,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;cAoBT,UAAA,SAAmB,MAAA,CAAO,iBAAA;EA5EF;EAAA,SA8EpB,EAAA,EAAI,MAAA,CAAO,MAAA;cAEf,IAAA,UAAc,IAAA,EAAM,cAAA,EAAgB,IAAA,EAAM,iBAAA;AAAA"}
|
package/dist/neon/branch.d.mts
CHANGED
|
@@ -19,15 +19,7 @@ interface NeonBranchInputs {
|
|
|
19
19
|
parentName?: string;
|
|
20
20
|
}
|
|
21
21
|
/** Persisted state for the Neon branch. */
|
|
22
|
-
interface NeonBranchOutputs extends NeonBranchInputs {
|
|
23
|
-
/**
|
|
24
|
-
* Whether this branch already existed and was adopted (vs. created by us).
|
|
25
|
-
* `delete()` only removes branches we created. Absent on state written before
|
|
26
|
-
* this field existed, which is treated as adopted (the safe, non-destructive
|
|
27
|
-
* default) so a feature env's destroy never deletes the adopted production branch.
|
|
28
|
-
*/
|
|
29
|
-
wasAdopted?: boolean;
|
|
30
|
-
}
|
|
22
|
+
interface NeonBranchOutputs extends NeonBranchInputs {}
|
|
31
23
|
/**
|
|
32
24
|
* Dynamic provider implementing CRUD for Neon branches.
|
|
33
25
|
*
|
|
@@ -40,8 +32,8 @@ declare class NeonBranchResourceProvider implements pulumi.dynamic.ResourceProvi
|
|
|
40
32
|
create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
41
33
|
read(id: string, props: NeonBranchOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
42
34
|
/**
|
|
43
|
-
* Deletes the branch
|
|
44
|
-
*
|
|
35
|
+
* Deletes the branch. Protection of shared/production branches is the consumer's
|
|
36
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
45
37
|
*/
|
|
46
38
|
delete(id: string, props: NeonBranchOutputs): Promise<void>;
|
|
47
39
|
diff(_id: string, olds: NeonBranchOutputs, news: NeonBranchInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.d.mts","names":[],"sources":["../../src/neon/branch.ts"],"mappings":";;;;;;;UAMiB,gBAAA;;EAEhB,MAAA;EAFgC;EAKhC,SAAA;EALgC;EAQhC,IAAA;EAHA;;;;EASA,UAAA;AAAA;;UAIS,iBAAA,SAA0B,gBAAgB;
|
|
1
|
+
{"version":3,"file":"branch.d.mts","names":[],"sources":["../../src/neon/branch.ts"],"mappings":";;;;;;;UAMiB,gBAAA;;EAEhB,MAAA;EAFgC;EAKhC,SAAA;EALgC;EAQhC,IAAA;EAHA;;;;EASA,UAAA;AAAA;;UAIS,iBAAA,SAA0B,gBAAgB;AAAA;AAqDpD;;;;;;;cAAa,0BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CAAO,MAAA,EAAQ,gBAAA,GAAmB,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAmDzD,IAAA,CACL,EAAA,UACA,KAAA,EAAO,iBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAmB0B;;;;EAA9C,MAAA,CAAO,EAAA,UAAY,KAAA,EAAO,iBAAA,GAAoB,OAAA;EAU9C,IAAA,CACL,GAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,gBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAuCtB,iBAAA,GAAoB,IAAA,CAAK,MAAA,CAAO,wBAAA;EAhIlB,mCAkIlB,QAAA,EAAU,YAAA,EAhIJ;EAmIN,OAAA,EAAS,WAAA;AAAA;;UAIO,cAAA;EAvIuC;EAyIvD,IAAA,EAAM,MAAA,CAAO,KAAA;EAtFP;;;;EA4FN,MAAA,GAAS,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;cAoBT,UAAA,SAAmB,MAAA,CAAO,iBAAA;EA5EF;EAAA,SA8EpB,EAAA,EAAI,MAAA,CAAO,MAAA;cAEf,IAAA,UAAc,IAAA,EAAM,cAAA,EAAgB,IAAA,EAAM,iBAAA;AAAA"}
|
package/dist/neon/branch.mjs
CHANGED
|
@@ -21,7 +21,6 @@ var NeonBranchResourceProvider = class {
|
|
|
21
21
|
async create(inputs) {
|
|
22
22
|
const client = new NeonClient(inputs.apiKey);
|
|
23
23
|
let branchId = await findBranchByName(client, inputs.projectId, inputs.name);
|
|
24
|
-
const wasAdopted = branchId !== void 0;
|
|
25
24
|
if (branchId) if (inputs.parentName) pulumi.log.warn(`Adopting existing Neon branch "${inputs.name}" — parentName "${inputs.parentName}" is ignored for adopted branches (existing lineage preserved).`);
|
|
26
25
|
else pulumi.log.info(`Adopting existing Neon branch "${inputs.name}" (${branchId})`);
|
|
27
26
|
else {
|
|
@@ -35,10 +34,7 @@ var NeonBranchResourceProvider = class {
|
|
|
35
34
|
}
|
|
36
35
|
return {
|
|
37
36
|
id: branchId,
|
|
38
|
-
outs: {
|
|
39
|
-
...inputs,
|
|
40
|
-
wasAdopted
|
|
41
|
-
}
|
|
37
|
+
outs: { ...inputs }
|
|
42
38
|
};
|
|
43
39
|
}
|
|
44
40
|
async read(id, props) {
|
|
@@ -52,14 +48,10 @@ var NeonBranchResourceProvider = class {
|
|
|
52
48
|
};
|
|
53
49
|
}
|
|
54
50
|
/**
|
|
55
|
-
* Deletes the branch
|
|
56
|
-
*
|
|
51
|
+
* Deletes the branch. Protection of shared/production branches is the consumer's
|
|
52
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
57
53
|
*/
|
|
58
54
|
async delete(id, props) {
|
|
59
|
-
if (props.wasAdopted !== false) {
|
|
60
|
-
pulumi.log.warn(`Neon branch "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
55
|
const client = new NeonClient(props.apiKey);
|
|
64
56
|
try {
|
|
65
57
|
await client.delete(`/projects/${props.projectId}/branches/${id}`);
|
package/dist/neon/branch.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.mjs","names":[],"sources":["../../src/neon/branch.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon branch dynamic provider. */\nexport interface NeonBranchInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID (e.g. `\"quiet-forest-69719462\"`). */\n\tprojectId: string;\n\n\t/** Branch display name (e.g. `\"production\"`, `\"development\"`). */\n\tname: string;\n\n\t/**\n\t * Parent branch NAME. Resolved to `parent_id` inside `create()` via\n\t * `findBranchByName`. Omit for project-root branches.\n\t */\n\tparentName?: string;\n}\n\n/** Persisted state for the Neon branch. */\ninterface NeonBranchOutputs extends NeonBranchInputs {\n\t/**\n\t * Whether this branch already existed and was adopted (vs. created by us).\n\t * `delete()` only removes branches we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive\n\t * default) so a feature env's destroy never deletes the adopted production branch.\n\t */\n\twasAdopted?: boolean;\n}\n\n/** Neon API response for a branch. */\ninterface BranchResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for branch creation. */\ninterface BranchCreateResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for listing branches. */\ninterface BranchListResponse {\n\tbranches: Array<{\n\t\tid: string;\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Finds an existing Neon branch by name within a project.\n */\nasync function findBranchByName(\n\tclient: NeonClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.get<BranchListResponse>(\n\t\t`/projects/${projectId}/branches`,\n\t);\n\n\tconst match = result.branches.find((b) => b.name === name);\n\n\treturn match?.id;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon branches.\n *\n * Uses adopt-or-create on `create()`: finds an existing branch by name\n * before creating a new one. When `parentName` is supplied, resolves it\n * to a `parent_id` and includes it in the POST body.\n */\n/** @internal Exported only for unit testing; not part of the public API surface. */\nexport class NeonBranchResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tlet branchId = await findBranchByName(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst wasAdopted = branchId !== undefined;\n\n\t\tif (branchId) {\n\t\t\tif (inputs.parentName) {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" — parentName \"${inputs.parentName}\" is ignored for adopted branches (existing lineage preserved).`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpulumi.log.info(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" (${branchId})`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tconst body: { branch: { name: string; parent_id?: string } } = {\n\t\t\t\tbranch: { name: inputs.name },\n\t\t\t};\n\n\t\t\tif (inputs.parentName) {\n\t\t\t\tconst parentId = await findBranchByName(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.parentName,\n\t\t\t\t);\n\n\t\t\t\tif (!parentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Neon parent branch \"${inputs.parentName}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tbody.branch.parent_id = parentId;\n\t\t\t}\n\n\t\t\tconst result = await client.post<BranchCreateResponse>(\n\t\t\t\t`/projects/${inputs.projectId}/branches`,\n\t\t\t\tbody,\n\t\t\t);\n\n\t\t\tbranchId = result.branch.id;\n\t\t}\n\n\t\treturn { id: branchId, outs: { ...inputs, wasAdopted } };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonBranchOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst result = await client.get<BranchResponse>(\n\t\t\t`/projects/${props.projectId}/branches/${id}`,\n\t\t);\n\n\t\t// parentName is preserved from prior state (props): the Neon API does not expose\n\t\t// a branch's parent on GET /branches/:id, so it cannot be re-derived here.\n\t\treturn {\n\t\t\tid: result.branch.id,\n\t\t\tprops: { ...props, name: result.branch.name },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the branch only if we created it. Adopted branches (and state written\n\t * before `wasAdopted` existed) are left untouched.\n\t */\n\tasync delete(id: string, props: NeonBranchOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Neon branch \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(`/projects/${props.projectId}/branches/${id}`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(`Failed to delete Neon branch (may already be deleted)`);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonBranchOutputs,\n\t\tnews: NeonBranchInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.parentName !== news.parentName) {\n\t\t\treplaces.push(\"parentName\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || olds.name !== news.name,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonBranchResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly branchId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\t/** Name of the parent branch; omit for project-root branches. */\n\t\t\tparentName?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonBranchResourceProvider(), name, { ...args }, opts);\n\t}\n}\n\n/** Options type for NeonBranch — replaces Pulumi's native `provider` field. */\ntype NeonBranchOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n};\n\n/** Args for NeonBranch. */\nexport interface NeonBranchArgs {\n\t/** Branch display name. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of the parent branch to branch from (copy-on-write). Omit to\n\t * branch from the project root (Neon default branch).\n\t */\n\tparent?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Neon branch with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * // Root branch (Neon default parent)\n * const production = new NeonBranch(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Copy-on-write branch from production\n * const staging = new NeonBranch(\"staging\", {\n * name: \"staging\",\n * parent: \"production\",\n * }, { provider, project });\n * ```\n */\nexport class NeonBranch extends pulumi.ComponentResource {\n\t/** Neon branch ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonBranchArgs, opts: NeonBranchOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Branch\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonBranchResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tparentName: args.parent,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.id;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;AA+DA,eAAe,iBACd,QACA,WACA,MAC8B;CAO9B,QAFc,MAJO,OAAO,IAC3B,aAAa,UAAU,UACxB,GAEqB,SAAS,MAAM,MAAM,EAAE,SAAS,IAE1C,GAAG;AACf;;;;;;;;;AAUA,IAAa,6BAAb,MAEA;CACC,MAAM,OAAO,QAAgE;EAC5E,MAAM,SAAS,IAAI,WAAW,OAAO,MAAM;EAE3C,IAAI,WAAW,MAAM,iBACpB,QACA,OAAO,WACP,OAAO,IACR;EAEA,MAAM,aAAa,aAAa;EAEhC,IAAI,UACH,IAAI,OAAO,YACV,OAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,kBAAkB,OAAO,WAAW,gEACnF;OAEA,OAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,KAAK,SAAS,EAC7D;OAEK;GACN,MAAM,OAAyD,EAC9D,QAAQ,EAAE,MAAM,OAAO,KAAK,EAC7B;GAEA,IAAI,OAAO,YAAY;IACtB,MAAM,WAAW,MAAM,iBACtB,QACA,OAAO,WACP,OAAO,UACR;IAEA,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,uBAAuB,OAAO,WAAW,yBAAyB,OAAO,WAC1E;IAGD,KAAK,OAAO,YAAY;GACzB;GAOA,YAAW,MALU,OAAO,KAC3B,aAAa,OAAO,UAAU,YAC9B,IACD,GAEkB,OAAO;EAC1B;EAEA,OAAO;GAAE,IAAI;GAAU,MAAM;IAAE,GAAG;IAAQ;GAAW;EAAE;CACxD;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFF,WAAW,MAAM,MAEV,EAAE,IAC3B,aAAa,MAAM,UAAU,YAAY,IAC1C;EAIA,OAAO;GACN,IAAI,OAAO,OAAO;GAClB,OAAO;IAAE,GAAG;IAAO,MAAM,OAAO,OAAO;GAAK;EAC7C;CACD;;;;;CAMA,MAAM,OAAO,IAAY,OAAyC;EACjE,IAAI,MAAM,eAAe,OAAO;GAC/B,OAAO,IAAI,KACV,gBAAgB,MAAM,KAAK,qDAC5B;GAEA;EACD;EAEA,MAAM,SAAS,IAAI,WAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OAAO,aAAa,MAAM,UAAU,YAAY,IAAI;EAClE,QAAQ;GACP,OAAO,IAAI,KAAK,uDAAuD;EACxE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,eAAe,KAAK,YAC5B,SAAS,KAAK,YAAY;EAG3B,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,KAAK,SAAS,KAAK;GACnD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,qBAAN,cAAiC,OAAO,QAAQ,SAAS;CAGxD,YACC,MACA,MAOA,MACC;EACD,MAAM,IAAI,2BAA2B,GAAG,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI;CAChE;AACD;;;;;;;;;;;;;;;;;;AAwCA,IAAa,aAAb,cAAgC,OAAO,kBAAkB;CAIxD,YAAY,MAAc,MAAsB,MAAyB;EACxE,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,0BAA0B,MAAM,CAAC,GAAG,UAAU;EAEpD,MAAM,WAAW,IAAI,mBACpB,GAAG,KAAK,YACR;GACC,QAAQ,SAAS;GACjB,WAAW,QAAQ;GACnB,MAAM,KAAK;GACX,YAAY,KAAK;EAClB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
|
|
1
|
+
{"version":3,"file":"branch.mjs","names":[],"sources":["../../src/neon/branch.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client\";\nimport type { NeonProject } from \"./project\";\nimport type { NeonProvider } from \"./provider\";\n\n/** Resolved inputs for the Neon branch dynamic provider. */\nexport interface NeonBranchInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID (e.g. `\"quiet-forest-69719462\"`). */\n\tprojectId: string;\n\n\t/** Branch display name (e.g. `\"production\"`, `\"development\"`). */\n\tname: string;\n\n\t/**\n\t * Parent branch NAME. Resolved to `parent_id` inside `create()` via\n\t * `findBranchByName`. Omit for project-root branches.\n\t */\n\tparentName?: string;\n}\n\n/** Persisted state for the Neon branch. */\ninterface NeonBranchOutputs extends NeonBranchInputs {}\n\n/** Neon API response for a branch. */\ninterface BranchResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for branch creation. */\ninterface BranchCreateResponse {\n\tbranch: {\n\t\tid: string;\n\t\tname: string;\n\t\tproject_id: string;\n\t};\n}\n\n/** Neon API response for listing branches. */\ninterface BranchListResponse {\n\tbranches: Array<{\n\t\tid: string;\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Finds an existing Neon branch by name within a project.\n */\nasync function findBranchByName(\n\tclient: NeonClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.get<BranchListResponse>(\n\t\t`/projects/${projectId}/branches`,\n\t);\n\n\tconst match = result.branches.find((b) => b.name === name);\n\n\treturn match?.id;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon branches.\n *\n * Uses adopt-or-create on `create()`: finds an existing branch by name\n * before creating a new one. When `parentName` is supplied, resolves it\n * to a `parent_id` and includes it in the POST body.\n */\n/** @internal Exported only for unit testing; not part of the public API surface. */\nexport class NeonBranchResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(inputs: NeonBranchInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tlet branchId = await findBranchByName(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (branchId) {\n\t\t\tif (inputs.parentName) {\n\t\t\t\tpulumi.log.warn(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" — parentName \"${inputs.parentName}\" is ignored for adopted branches (existing lineage preserved).`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpulumi.log.info(\n\t\t\t\t\t`Adopting existing Neon branch \"${inputs.name}\" (${branchId})`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tconst body: { branch: { name: string; parent_id?: string } } = {\n\t\t\t\tbranch: { name: inputs.name },\n\t\t\t};\n\n\t\t\tif (inputs.parentName) {\n\t\t\t\tconst parentId = await findBranchByName(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.parentName,\n\t\t\t\t);\n\n\t\t\t\tif (!parentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Neon parent branch \"${inputs.parentName}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tbody.branch.parent_id = parentId;\n\t\t\t}\n\n\t\t\tconst result = await client.post<BranchCreateResponse>(\n\t\t\t\t`/projects/${inputs.projectId}/branches`,\n\t\t\t\tbody,\n\t\t\t);\n\n\t\t\tbranchId = result.branch.id;\n\t\t}\n\n\t\treturn { id: branchId, outs: { ...inputs } };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonBranchOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst result = await client.get<BranchResponse>(\n\t\t\t`/projects/${props.projectId}/branches/${id}`,\n\t\t);\n\n\t\t// parentName is preserved from prior state (props): the Neon API does not expose\n\t\t// a branch's parent on GET /branches/:id, so it cannot be re-derived here.\n\t\treturn {\n\t\t\tid: result.branch.id,\n\t\t\tprops: { ...props, name: result.branch.name },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the branch. Protection of shared/production branches is the consumer's\n\t * responsibility via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(id: string, props: NeonBranchOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(`/projects/${props.projectId}/branches/${id}`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(`Failed to delete Neon branch (may already be deleted)`);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonBranchOutputs,\n\t\tnews: NeonBranchInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.parentName !== news.parentName) {\n\t\t\treplaces.push(\"parentName\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || olds.name !== news.name,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass NeonBranchResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly branchId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\tapiKey: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\t/** Name of the parent branch; omit for project-root branches. */\n\t\t\tparentName?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonBranchResourceProvider(), name, { ...args }, opts);\n\t}\n}\n\n/** Options type for NeonBranch — replaces Pulumi's native `provider` field. */\ntype NeonBranchOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Neon authentication context. */\n\tprovider: NeonProvider;\n\n\t/** Neon project context. */\n\tproject: NeonProject;\n};\n\n/** Args for NeonBranch. */\nexport interface NeonBranchArgs {\n\t/** Branch display name. */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of the parent branch to branch from (copy-on-write). Omit to\n\t * branch from the project root (Neon default branch).\n\t */\n\tparent?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Neon branch with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * // Root branch (Neon default parent)\n * const production = new NeonBranch(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Copy-on-write branch from production\n * const staging = new NeonBranch(\"staging\", {\n * name: \"staging\",\n * parent: \"production\",\n * }, { provider, project });\n * ```\n */\nexport class NeonBranch extends pulumi.ComponentResource {\n\t/** Neon branch ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(name: string, args: NeonBranchArgs, opts: NeonBranchOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:neon:Branch\", name, {}, pulumiOpts);\n\n\t\tconst resource = new NeonBranchResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\tapiKey: provider.apiKey,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tparentName: args.parent,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.id;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":";;;;;;;;AAuDA,eAAe,iBACd,QACA,WACA,MAC8B;CAO9B,QAFc,MAJO,OAAO,IAC3B,aAAa,UAAU,UACxB,GAEqB,SAAS,MAAM,MAAM,EAAE,SAAS,IAE1C,GAAG;AACf;;;;;;;;;AAUA,IAAa,6BAAb,MAEA;CACC,MAAM,OAAO,QAAgE;EAC5E,MAAM,SAAS,IAAI,WAAW,OAAO,MAAM;EAE3C,IAAI,WAAW,MAAM,iBACpB,QACA,OAAO,WACP,OAAO,IACR;EAEA,IAAI,UACH,IAAI,OAAO,YACV,OAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,kBAAkB,OAAO,WAAW,gEACnF;OAEA,OAAO,IAAI,KACV,kCAAkC,OAAO,KAAK,KAAK,SAAS,EAC7D;OAEK;GACN,MAAM,OAAyD,EAC9D,QAAQ,EAAE,MAAM,OAAO,KAAK,EAC7B;GAEA,IAAI,OAAO,YAAY;IACtB,MAAM,WAAW,MAAM,iBACtB,QACA,OAAO,WACP,OAAO,UACR;IAEA,IAAI,CAAC,UACJ,MAAM,IAAI,MACT,uBAAuB,OAAO,WAAW,yBAAyB,OAAO,WAC1E;IAGD,KAAK,OAAO,YAAY;GACzB;GAOA,YAAW,MALU,OAAO,KAC3B,aAAa,OAAO,UAAU,YAC9B,IACD,GAEkB,OAAO;EAC1B;EAEA,OAAO;GAAE,IAAI;GAAU,MAAM,EAAE,GAAG,OAAO;EAAE;CAC5C;CAEA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,SAAS,MAAM,IAFF,WAAW,MAAM,MAEV,EAAE,IAC3B,aAAa,MAAM,UAAU,YAAY,IAC1C;EAIA,OAAO;GACN,IAAI,OAAO,OAAO;GAClB,OAAO;IAAE,GAAG;IAAO,MAAM,OAAO,OAAO;GAAK;EAC7C;CACD;;;;;CAMA,MAAM,OAAO,IAAY,OAAyC;EACjE,MAAM,SAAS,IAAI,WAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OAAO,aAAa,MAAM,UAAU,YAAY,IAAI;EAClE,QAAQ;GACP,OAAO,IAAI,KAAK,uDAAuD;EACxE;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,eAAe,KAAK,YAC5B,SAAS,KAAK,YAAY;EAG3B,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,KAAK,SAAS,KAAK;GACnD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,qBAAN,cAAiC,OAAO,QAAQ,SAAS;CAGxD,YACC,MACA,MAOA,MACC;EACD,MAAM,IAAI,2BAA2B,GAAG,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI;CAChE;AACD;;;;;;;;;;;;;;;;;;AAwCA,IAAa,aAAb,cAAgC,OAAO,kBAAkB;CAIxD,YAAY,MAAc,MAAsB,MAAyB;EACxE,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,0BAA0B,MAAM,CAAC,GAAG,UAAU;EAEpD,MAAM,WAAW,IAAI,mBACpB,GAAG,KAAK,YACR;GACC,QAAQ,SAAS;GACjB,WAAW,QAAQ;GACnB,MAAM,KAAK;GACX,YAAY,KAAK;EAClB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EAEnB,KAAK,gBAAgB,EAAE,IAAI,KAAK,GAAG,CAAC;CACrC;AACD"}
|
|
@@ -53,7 +53,6 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
53
53
|
async create(inputs) {
|
|
54
54
|
const client = new require_railway_client.RailwayClient(inputs.token);
|
|
55
55
|
let environmentId = await findEnvironmentId(client, inputs.projectId, inputs.name);
|
|
56
|
-
const wasAdopted = environmentId !== void 0;
|
|
57
56
|
if (environmentId) _pulumi_pulumi.log.info(`Adopting existing Railway environment "${inputs.name}" (${environmentId})`);
|
|
58
57
|
else {
|
|
59
58
|
let sourceEnvironmentId;
|
|
@@ -71,8 +70,7 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
71
70
|
}
|
|
72
71
|
const outs = {
|
|
73
72
|
...inputs,
|
|
74
|
-
environmentId
|
|
75
|
-
wasAdopted
|
|
73
|
+
environmentId
|
|
76
74
|
};
|
|
77
75
|
return {
|
|
78
76
|
id: environmentId,
|
|
@@ -90,25 +88,20 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
90
88
|
}
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
|
-
async update(_id,
|
|
91
|
+
async update(_id, _olds, news) {
|
|
94
92
|
const environmentId = await findEnvironmentId(new require_railway_client.RailwayClient(news.token), news.projectId, news.name);
|
|
95
93
|
if (!environmentId) throw new Error(`Railway environment "${news.name}" not found in project ${news.projectId}`);
|
|
96
94
|
return { outs: {
|
|
97
95
|
...news,
|
|
98
|
-
environmentId
|
|
99
|
-
wasAdopted: olds.wasAdopted
|
|
96
|
+
environmentId
|
|
100
97
|
} };
|
|
101
98
|
}
|
|
102
99
|
/**
|
|
103
|
-
* Deletes the environment
|
|
104
|
-
*
|
|
105
|
-
*
|
|
100
|
+
* Deletes the environment (which cascades its per-environment service instances).
|
|
101
|
+
* Protection of the shared production environment is the consumer's responsibility
|
|
102
|
+
* via the `protect` resource option, not provider logic.
|
|
106
103
|
*/
|
|
107
104
|
async delete(_id, props) {
|
|
108
|
-
if (props.wasAdopted !== false) {
|
|
109
|
-
_pulumi_pulumi.log.warn(`Railway environment "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
105
|
const client = new require_railway_client.RailwayClient(props.token);
|
|
113
106
|
try {
|
|
114
107
|
await client.query(ENVIRONMENT_DELETE_MUTATION, { id: props.environmentId });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/environment.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/** Resolved inputs for the Railway environment dynamic provider. */\ninterface RailwayEnvironmentInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: string;\n\n\t/** Name of an existing environment to fork from when creating a new one. */\n\tsource?: string;\n}\n\n/** Persisted state for the Railway environment. */\ninterface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {\n\t/** Railway-assigned environment UUID. */\n\tenvironmentId: string;\n\n\t/**\n\t * Whether this environment already existed and was adopted (vs. created by us).\n\t * `delete()` only removes environments we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive default).\n\t */\n\twasAdopted?: boolean;\n}\n\nconst ENVIRONMENT_CREATE_MUTATION = `\n mutation EnvironmentCreate($input: EnvironmentCreateInput!) {\n environmentCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst ENVIRONMENT_DELETE_MUTATION = `\n mutation($id: String!) { environmentDelete(id: $id) }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\n/**\n * Queries a project's environments and resolves the ID for the given name.\n */\nasync function findEnvironmentId(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst match = result.project.environments.edges.find(\n\t\t(edge) => edge.node.name === name,\n\t);\n\n\treturn match?.node.id;\n}\n\n/**\n * Dynamic provider that resolves or creates a Railway environment by name.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayEnvironmentResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Adopts an existing Railway environment when found by name, or creates a new\n\t * one (optionally forked from a source environment) when not found.\n\t *\n\t * @param inputs Resolved provider inputs including token, projectId, name, and optional source.\n\t * @returns Pulumi dynamic create result with the environment UUID as the resource ID.\n\t * @throws {Error} When `source` is provided but cannot be resolved to an environment ID.\n\t */\n\tasync create(\n\t\tinputs: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tlet environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst wasAdopted = environmentId !== undefined;\n\n\t\tif (environmentId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tlet sourceEnvironmentId: string | undefined;\n\n\t\t\tif (inputs.source) {\n\t\t\t\tsourceEnvironmentId = await findEnvironmentId(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.source,\n\t\t\t\t);\n\n\t\t\t\tif (!sourceEnvironmentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Railway source environment \"${inputs.source}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tenvironmentCreate: { id: string; name: string };\n\t\t\t}>(ENVIRONMENT_CREATE_MUTATION, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\t// skipInitialDeploys: hold deploys so a forked env doesn't run with inherited\n\t\t\t\t\t// source/production variables before our RailwayVariable resources overwrite them.\n\t\t\t\t\tskipInitialDeploys: true,\n\t\t\t\t\t...(sourceEnvironmentId ? { sourceEnvironmentId } : {}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tenvironmentId = result.environmentCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t}\n\n\t\tconst outs: RailwayEnvironmentOutputs = {\n\t\t\t...inputs,\n\t\t\tenvironmentId,\n\t\t\twasAdopted,\n\t\t};\n\n\t\treturn { id: environmentId, outs };\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayEnvironmentOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${props.name}\" not found during refresh`,\n\t\t\t);\n\t\t}\n\n\t\treturn { id: environmentId, props: { ...props, environmentId } };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tnews.projectId,\n\t\t\tnews.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${news.name}\" not found in project ${news.projectId}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\touts: { ...news, environmentId, wasAdopted: olds.wasAdopted },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the environment only if we created it. Adopted environments (and\n\t * state written before `wasAdopted` existed) are left untouched, so a feature\n\t * env's destroy never removes the shared production environment.\n\t */\n\tasync delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Railway environment \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(ENVIRONMENT_DELETE_MUTATION, {\n\t\t\t\tid: props.environmentId,\n\t\t\t});\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Deleted Railway environment \"${props.name}\" (${props.environmentId})`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway environment \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayEnvironmentResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly environmentId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayEnvironmentResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tenvironmentId: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayEnvironment — replaces Pulumi's native `provider` field. */\ntype RailwayEnvironmentOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context to resolve the environment from. */\n\tproject: RailwayProject;\n};\n\n/** Args for RailwayEnvironment. */\nexport interface RailwayEnvironmentArgs {\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of an existing environment to fork from when this environment is created.\n\t * Evaluated only at creation time — changing `source` after the environment exists\n\t * has no effect (an existing environment is never re-forked).\n\t */\n\tsource?: pulumi.Input<string>;\n}\n\n/**\n * Resolves or creates a Railway environment by name within a project.\n *\n * When the named environment already exists it is adopted (no mutation).\n * When it does not exist it is created — optionally forked from a source\n * environment so the new env inherits service instances and variables.\n *\n * @example\n * ```typescript\n * // Adopt or create \"production\" (no source)\n * const production = new RailwayEnvironment(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Adopt or create \"staging\", forked from \"production\"\n * const staging = new RailwayEnvironment(\"staging\", {\n * name: \"staging\",\n * source: \"production\",\n * }, { provider, project });\n *\n * // Use environmentId downstream\n * const service = new RailwayService(\"api\", { name: \"api\" }, {\n * provider, project, environment: staging,\n * });\n * ```\n */\nexport class RailwayEnvironment extends pulumi.ComponentResource {\n\t/** Railway environment UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayEnvironmentArgs,\n\t\topts: RailwayEnvironmentOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Environment\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayEnvironmentResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tsource: args.source,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.environmentId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"environment.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/environment.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/** Resolved inputs for the Railway environment dynamic provider. */\ninterface RailwayEnvironmentInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: string;\n\n\t/** Name of an existing environment to fork from when creating a new one. */\n\tsource?: string;\n}\n\n/** Persisted state for the Railway environment. */\ninterface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {\n\t/** Railway-assigned environment UUID. */\n\tenvironmentId: string;\n}\n\nconst ENVIRONMENT_CREATE_MUTATION = `\n mutation EnvironmentCreate($input: EnvironmentCreateInput!) {\n environmentCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst ENVIRONMENT_DELETE_MUTATION = `\n mutation($id: String!) { environmentDelete(id: $id) }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\n/**\n * Queries a project's environments and resolves the ID for the given name.\n */\nasync function findEnvironmentId(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst match = result.project.environments.edges.find(\n\t\t(edge) => edge.node.name === name,\n\t);\n\n\treturn match?.node.id;\n}\n\n/**\n * Dynamic provider that resolves or creates a Railway environment by name.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayEnvironmentResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Adopts an existing Railway environment when found by name, or creates a new\n\t * one (optionally forked from a source environment) when not found.\n\t *\n\t * @param inputs Resolved provider inputs including token, projectId, name, and optional source.\n\t * @returns Pulumi dynamic create result with the environment UUID as the resource ID.\n\t * @throws {Error} When `source` is provided but cannot be resolved to an environment ID.\n\t */\n\tasync create(\n\t\tinputs: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tlet environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (environmentId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tlet sourceEnvironmentId: string | undefined;\n\n\t\t\tif (inputs.source) {\n\t\t\t\tsourceEnvironmentId = await findEnvironmentId(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.source,\n\t\t\t\t);\n\n\t\t\t\tif (!sourceEnvironmentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Railway source environment \"${inputs.source}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tenvironmentCreate: { id: string; name: string };\n\t\t\t}>(ENVIRONMENT_CREATE_MUTATION, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\t// skipInitialDeploys: hold deploys so a forked env doesn't run with inherited\n\t\t\t\t\t// source/production variables before our RailwayVariable resources overwrite them.\n\t\t\t\t\tskipInitialDeploys: true,\n\t\t\t\t\t...(sourceEnvironmentId ? { sourceEnvironmentId } : {}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tenvironmentId = result.environmentCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t}\n\n\t\tconst outs: RailwayEnvironmentOutputs = { ...inputs, environmentId };\n\n\t\treturn { id: environmentId, outs };\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayEnvironmentOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${props.name}\" not found during refresh`,\n\t\t\t);\n\t\t}\n\n\t\treturn { id: environmentId, props: { ...props, environmentId } };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tnews.projectId,\n\t\t\tnews.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${news.name}\" not found in project ${news.projectId}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, environmentId } };\n\t}\n\n\t/**\n\t * Deletes the environment (which cascades its per-environment service instances).\n\t * Protection of the shared production environment is the consumer's responsibility\n\t * via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(ENVIRONMENT_DELETE_MUTATION, {\n\t\t\t\tid: props.environmentId,\n\t\t\t});\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Deleted Railway environment \"${props.name}\" (${props.environmentId})`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway environment \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayEnvironmentResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly environmentId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayEnvironmentResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tenvironmentId: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayEnvironment — replaces Pulumi's native `provider` field. */\ntype RailwayEnvironmentOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context to resolve the environment from. */\n\tproject: RailwayProject;\n};\n\n/** Args for RailwayEnvironment. */\nexport interface RailwayEnvironmentArgs {\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of an existing environment to fork from when this environment is created.\n\t * Evaluated only at creation time — changing `source` after the environment exists\n\t * has no effect (an existing environment is never re-forked).\n\t */\n\tsource?: pulumi.Input<string>;\n}\n\n/**\n * Resolves or creates a Railway environment by name within a project.\n *\n * When the named environment already exists it is adopted (no mutation).\n * When it does not exist it is created — optionally forked from a source\n * environment so the new env inherits service instances and variables.\n *\n * @example\n * ```typescript\n * // Adopt or create \"production\" (no source)\n * const production = new RailwayEnvironment(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Adopt or create \"staging\", forked from \"production\"\n * const staging = new RailwayEnvironment(\"staging\", {\n * name: \"staging\",\n * source: \"production\",\n * }, { provider, project });\n *\n * // Use environmentId downstream\n * const service = new RailwayService(\"api\", { name: \"api\" }, {\n * provider, project, environment: staging,\n * });\n * ```\n */\nexport class RailwayEnvironment extends pulumi.ComponentResource {\n\t/** Railway environment UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayEnvironmentArgs,\n\t\topts: RailwayEnvironmentOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Environment\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayEnvironmentResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tsource: args.source,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.environmentId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -19,12 +19,6 @@ interface RailwayEnvironmentInputs {
|
|
|
19
19
|
interface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {
|
|
20
20
|
/** Railway-assigned environment UUID. */
|
|
21
21
|
environmentId: string;
|
|
22
|
-
/**
|
|
23
|
-
* Whether this environment already existed and was adopted (vs. created by us).
|
|
24
|
-
* `delete()` only removes environments we created. Absent on state written before
|
|
25
|
-
* this field existed, which is treated as adopted (the safe, non-destructive default).
|
|
26
|
-
*/
|
|
27
|
-
wasAdopted?: boolean;
|
|
28
22
|
}
|
|
29
23
|
/**
|
|
30
24
|
* Dynamic provider that resolves or creates a Railway environment by name.
|
|
@@ -42,11 +36,11 @@ declare class RailwayEnvironmentResourceProvider implements pulumi.dynamic.Resou
|
|
|
42
36
|
*/
|
|
43
37
|
create(inputs: RailwayEnvironmentInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
44
38
|
read(_id: string, props: RailwayEnvironmentOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
45
|
-
update(_id: string,
|
|
39
|
+
update(_id: string, _olds: RailwayEnvironmentOutputs, news: RailwayEnvironmentInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
46
40
|
/**
|
|
47
|
-
* Deletes the environment
|
|
48
|
-
*
|
|
49
|
-
*
|
|
41
|
+
* Deletes the environment (which cascades its per-environment service instances).
|
|
42
|
+
* Protection of the shared production environment is the consumer's responsibility
|
|
43
|
+
* via the `protect` resource option, not provider logic.
|
|
50
44
|
*/
|
|
51
45
|
delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void>;
|
|
52
46
|
diff(_id: string, olds: RailwayEnvironmentOutputs, news: RailwayEnvironmentInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.cts","names":[],"sources":["../../src/railway/environment.ts"],"mappings":";;;;;;;UAMU,wBAAA;;EAET,KAAA;EAFiC;EAKjC,SAAA;EALiC;EAQjC,IAAA;EAHA;EAMA,MAAA;AAAA;;UAIS,yBAAA,SAAkC,wBAAwB;EAA1D;EAET,
|
|
1
|
+
{"version":3,"file":"environment.d.cts","names":[],"sources":["../../src/railway/environment.ts"],"mappings":";;;;;;;UAMU,wBAAA;;EAET,KAAA;EAFiC;EAKjC,SAAA;EALiC;EAQjC,IAAA;EAHA;EAMA,MAAA;AAAA;;UAIS,yBAAA,SAAkC,wBAAwB;EAA1D;EAET,aAAa;AAAA;;AAAA;AA2Dd;;;cAAa,kCAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAYf;;;;;;;;EAFL,MAAA,CACL,MAAA,EAAQ,wBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAuDpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,yBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAkBpB,MAAA,CACL,GAAA,UACA,KAAA,EAAO,yBAAA,EACP,IAAA,EAAM,wBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA2CnB;;;;;EApBD,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,yBAAA,GAA4B,OAAA;EAkBvD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,yBAAA,EACN,IAAA,EAAM,wBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA8CtB,yBAAA,GAA4B,IAAA,CAChC,MAAA,CAAO,wBAAA;EA9KD,sCAkLN,QAAA,EAAU,eAAA,EAjLT;EAoLD,OAAA,EAAS,cAAA;AAAA;;UAIO,sBAAA;EAhIV;EAkIN,IAAA,EAAM,MAAA,CAAO,KAAA;EAhIL;;;;;EAuIR,MAAA,GAAS,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAnEe;cAgGxB,kBAAA,SAA2B,MAAA,CAAO,iBAAA;EAlDjB;EAAA,SAoDb,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,sBAAA,EACN,IAAA,EAAM,yBAAA;AAAA"}
|
|
@@ -19,12 +19,6 @@ interface RailwayEnvironmentInputs {
|
|
|
19
19
|
interface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {
|
|
20
20
|
/** Railway-assigned environment UUID. */
|
|
21
21
|
environmentId: string;
|
|
22
|
-
/**
|
|
23
|
-
* Whether this environment already existed and was adopted (vs. created by us).
|
|
24
|
-
* `delete()` only removes environments we created. Absent on state written before
|
|
25
|
-
* this field existed, which is treated as adopted (the safe, non-destructive default).
|
|
26
|
-
*/
|
|
27
|
-
wasAdopted?: boolean;
|
|
28
22
|
}
|
|
29
23
|
/**
|
|
30
24
|
* Dynamic provider that resolves or creates a Railway environment by name.
|
|
@@ -42,11 +36,11 @@ declare class RailwayEnvironmentResourceProvider implements pulumi.dynamic.Resou
|
|
|
42
36
|
*/
|
|
43
37
|
create(inputs: RailwayEnvironmentInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
44
38
|
read(_id: string, props: RailwayEnvironmentOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
45
|
-
update(_id: string,
|
|
39
|
+
update(_id: string, _olds: RailwayEnvironmentOutputs, news: RailwayEnvironmentInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
46
40
|
/**
|
|
47
|
-
* Deletes the environment
|
|
48
|
-
*
|
|
49
|
-
*
|
|
41
|
+
* Deletes the environment (which cascades its per-environment service instances).
|
|
42
|
+
* Protection of the shared production environment is the consumer's responsibility
|
|
43
|
+
* via the `protect` resource option, not provider logic.
|
|
50
44
|
*/
|
|
51
45
|
delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void>;
|
|
52
46
|
diff(_id: string, olds: RailwayEnvironmentOutputs, news: RailwayEnvironmentInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.mts","names":[],"sources":["../../src/railway/environment.ts"],"mappings":";;;;;;;UAMU,wBAAA;;EAET,KAAA;EAFiC;EAKjC,SAAA;EALiC;EAQjC,IAAA;EAHA;EAMA,MAAA;AAAA;;UAIS,yBAAA,SAAkC,wBAAwB;EAA1D;EAET,
|
|
1
|
+
{"version":3,"file":"environment.d.mts","names":[],"sources":["../../src/railway/environment.ts"],"mappings":";;;;;;;UAMU,wBAAA;;EAET,KAAA;EAFiC;EAKjC,SAAA;EALiC;EAQjC,IAAA;EAHA;EAMA,MAAA;AAAA;;UAIS,yBAAA,SAAkC,wBAAwB;EAA1D;EAET,aAAa;AAAA;;AAAA;AA2Dd;;;cAAa,kCAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAYf;;;;;;;;EAFL,MAAA,CACL,MAAA,EAAQ,wBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAuDpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,yBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAkBpB,MAAA,CACL,GAAA,UACA,KAAA,EAAO,yBAAA,EACP,IAAA,EAAM,wBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA2CnB;;;;;EApBD,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,yBAAA,GAA4B,OAAA;EAkBvD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,yBAAA,EACN,IAAA,EAAM,wBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA8CtB,yBAAA,GAA4B,IAAA,CAChC,MAAA,CAAO,wBAAA;EA9KD,sCAkLN,QAAA,EAAU,eAAA,EAjLT;EAoLD,OAAA,EAAS,cAAA;AAAA;;UAIO,sBAAA;EAhIV;EAkIN,IAAA,EAAM,MAAA,CAAO,KAAA;EAhIL;;;;;EAuIR,MAAA,GAAS,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAnEe;cAgGxB,kBAAA,SAA2B,MAAA,CAAO,iBAAA;EAlDjB;EAAA,SAoDb,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,sBAAA,EACN,IAAA,EAAM,yBAAA;AAAA"}
|
|
@@ -51,7 +51,6 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
51
51
|
async create(inputs) {
|
|
52
52
|
const client = new RailwayClient(inputs.token);
|
|
53
53
|
let environmentId = await findEnvironmentId(client, inputs.projectId, inputs.name);
|
|
54
|
-
const wasAdopted = environmentId !== void 0;
|
|
55
54
|
if (environmentId) pulumi.log.info(`Adopting existing Railway environment "${inputs.name}" (${environmentId})`);
|
|
56
55
|
else {
|
|
57
56
|
let sourceEnvironmentId;
|
|
@@ -69,8 +68,7 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
69
68
|
}
|
|
70
69
|
const outs = {
|
|
71
70
|
...inputs,
|
|
72
|
-
environmentId
|
|
73
|
-
wasAdopted
|
|
71
|
+
environmentId
|
|
74
72
|
};
|
|
75
73
|
return {
|
|
76
74
|
id: environmentId,
|
|
@@ -88,25 +86,20 @@ var RailwayEnvironmentResourceProvider = class {
|
|
|
88
86
|
}
|
|
89
87
|
};
|
|
90
88
|
}
|
|
91
|
-
async update(_id,
|
|
89
|
+
async update(_id, _olds, news) {
|
|
92
90
|
const environmentId = await findEnvironmentId(new RailwayClient(news.token), news.projectId, news.name);
|
|
93
91
|
if (!environmentId) throw new Error(`Railway environment "${news.name}" not found in project ${news.projectId}`);
|
|
94
92
|
return { outs: {
|
|
95
93
|
...news,
|
|
96
|
-
environmentId
|
|
97
|
-
wasAdopted: olds.wasAdopted
|
|
94
|
+
environmentId
|
|
98
95
|
} };
|
|
99
96
|
}
|
|
100
97
|
/**
|
|
101
|
-
* Deletes the environment
|
|
102
|
-
*
|
|
103
|
-
*
|
|
98
|
+
* Deletes the environment (which cascades its per-environment service instances).
|
|
99
|
+
* Protection of the shared production environment is the consumer's responsibility
|
|
100
|
+
* via the `protect` resource option, not provider logic.
|
|
104
101
|
*/
|
|
105
102
|
async delete(_id, props) {
|
|
106
|
-
if (props.wasAdopted !== false) {
|
|
107
|
-
pulumi.log.warn(`Railway environment "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
103
|
const client = new RailwayClient(props.token);
|
|
111
104
|
try {
|
|
112
105
|
await client.query(ENVIRONMENT_DELETE_MUTATION, { id: props.environmentId });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.mjs","names":[],"sources":["../../src/railway/environment.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/** Resolved inputs for the Railway environment dynamic provider. */\ninterface RailwayEnvironmentInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: string;\n\n\t/** Name of an existing environment to fork from when creating a new one. */\n\tsource?: string;\n}\n\n/** Persisted state for the Railway environment. */\ninterface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {\n\t/** Railway-assigned environment UUID. */\n\tenvironmentId: string;\n\n\t/**\n\t * Whether this environment already existed and was adopted (vs. created by us).\n\t * `delete()` only removes environments we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive default).\n\t */\n\twasAdopted?: boolean;\n}\n\nconst ENVIRONMENT_CREATE_MUTATION = `\n mutation EnvironmentCreate($input: EnvironmentCreateInput!) {\n environmentCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst ENVIRONMENT_DELETE_MUTATION = `\n mutation($id: String!) { environmentDelete(id: $id) }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\n/**\n * Queries a project's environments and resolves the ID for the given name.\n */\nasync function findEnvironmentId(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst match = result.project.environments.edges.find(\n\t\t(edge) => edge.node.name === name,\n\t);\n\n\treturn match?.node.id;\n}\n\n/**\n * Dynamic provider that resolves or creates a Railway environment by name.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayEnvironmentResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Adopts an existing Railway environment when found by name, or creates a new\n\t * one (optionally forked from a source environment) when not found.\n\t *\n\t * @param inputs Resolved provider inputs including token, projectId, name, and optional source.\n\t * @returns Pulumi dynamic create result with the environment UUID as the resource ID.\n\t * @throws {Error} When `source` is provided but cannot be resolved to an environment ID.\n\t */\n\tasync create(\n\t\tinputs: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tlet environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst wasAdopted = environmentId !== undefined;\n\n\t\tif (environmentId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tlet sourceEnvironmentId: string | undefined;\n\n\t\t\tif (inputs.source) {\n\t\t\t\tsourceEnvironmentId = await findEnvironmentId(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.source,\n\t\t\t\t);\n\n\t\t\t\tif (!sourceEnvironmentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Railway source environment \"${inputs.source}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tenvironmentCreate: { id: string; name: string };\n\t\t\t}>(ENVIRONMENT_CREATE_MUTATION, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\t// skipInitialDeploys: hold deploys so a forked env doesn't run with inherited\n\t\t\t\t\t// source/production variables before our RailwayVariable resources overwrite them.\n\t\t\t\t\tskipInitialDeploys: true,\n\t\t\t\t\t...(sourceEnvironmentId ? { sourceEnvironmentId } : {}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tenvironmentId = result.environmentCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t}\n\n\t\tconst outs: RailwayEnvironmentOutputs = {\n\t\t\t...inputs,\n\t\t\tenvironmentId,\n\t\t\twasAdopted,\n\t\t};\n\n\t\treturn { id: environmentId, outs };\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayEnvironmentOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${props.name}\" not found during refresh`,\n\t\t\t);\n\t\t}\n\n\t\treturn { id: environmentId, props: { ...props, environmentId } };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tnews.projectId,\n\t\t\tnews.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${news.name}\" not found in project ${news.projectId}`,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\touts: { ...news, environmentId, wasAdopted: olds.wasAdopted },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the environment only if we created it. Adopted environments (and\n\t * state written before `wasAdopted` existed) are left untouched, so a feature\n\t * env's destroy never removes the shared production environment.\n\t */\n\tasync delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Railway environment \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(ENVIRONMENT_DELETE_MUTATION, {\n\t\t\t\tid: props.environmentId,\n\t\t\t});\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Deleted Railway environment \"${props.name}\" (${props.environmentId})`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway environment \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayEnvironmentResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly environmentId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayEnvironmentResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tenvironmentId: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayEnvironment — replaces Pulumi's native `provider` field. */\ntype RailwayEnvironmentOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context to resolve the environment from. */\n\tproject: RailwayProject;\n};\n\n/** Args for RailwayEnvironment. */\nexport interface RailwayEnvironmentArgs {\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of an existing environment to fork from when this environment is created.\n\t * Evaluated only at creation time — changing `source` after the environment exists\n\t * has no effect (an existing environment is never re-forked).\n\t */\n\tsource?: pulumi.Input<string>;\n}\n\n/**\n * Resolves or creates a Railway environment by name within a project.\n *\n * When the named environment already exists it is adopted (no mutation).\n * When it does not exist it is created — optionally forked from a source\n * environment so the new env inherits service instances and variables.\n *\n * @example\n * ```typescript\n * // Adopt or create \"production\" (no source)\n * const production = new RailwayEnvironment(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Adopt or create \"staging\", forked from \"production\"\n * const staging = new RailwayEnvironment(\"staging\", {\n * name: \"staging\",\n * source: \"production\",\n * }, { provider, project });\n *\n * // Use environmentId downstream\n * const service = new RailwayService(\"api\", { name: \"api\" }, {\n * provider, project, environment: staging,\n * });\n * ```\n */\nexport class RailwayEnvironment extends pulumi.ComponentResource {\n\t/** Railway environment UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayEnvironmentArgs,\n\t\topts: RailwayEnvironmentOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Environment\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayEnvironmentResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tsource: args.source,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.environmentId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"environment.mjs","names":[],"sources":["../../src/railway/environment.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/** Resolved inputs for the Railway environment dynamic provider. */\ninterface RailwayEnvironmentInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: string;\n\n\t/** Name of an existing environment to fork from when creating a new one. */\n\tsource?: string;\n}\n\n/** Persisted state for the Railway environment. */\ninterface RailwayEnvironmentOutputs extends RailwayEnvironmentInputs {\n\t/** Railway-assigned environment UUID. */\n\tenvironmentId: string;\n}\n\nconst ENVIRONMENT_CREATE_MUTATION = `\n mutation EnvironmentCreate($input: EnvironmentCreateInput!) {\n environmentCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst ENVIRONMENT_DELETE_MUTATION = `\n mutation($id: String!) { environmentDelete(id: $id) }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\n/**\n * Queries a project's environments and resolves the ID for the given name.\n */\nasync function findEnvironmentId(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tname: string,\n): Promise<string | undefined> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst match = result.project.environments.edges.find(\n\t\t(edge) => edge.node.name === name,\n\t);\n\n\treturn match?.node.id;\n}\n\n/**\n * Dynamic provider that resolves or creates a Railway environment by name.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayEnvironmentResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Adopts an existing Railway environment when found by name, or creates a new\n\t * one (optionally forked from a source environment) when not found.\n\t *\n\t * @param inputs Resolved provider inputs including token, projectId, name, and optional source.\n\t * @returns Pulumi dynamic create result with the environment UUID as the resource ID.\n\t * @throws {Error} When `source` is provided but cannot be resolved to an environment ID.\n\t */\n\tasync create(\n\t\tinputs: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tlet environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (environmentId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tlet sourceEnvironmentId: string | undefined;\n\n\t\t\tif (inputs.source) {\n\t\t\t\tsourceEnvironmentId = await findEnvironmentId(\n\t\t\t\t\tclient,\n\t\t\t\t\tinputs.projectId,\n\t\t\t\t\tinputs.source,\n\t\t\t\t);\n\n\t\t\t\tif (!sourceEnvironmentId) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Railway source environment \"${inputs.source}\" not found in project ${inputs.projectId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tenvironmentCreate: { id: string; name: string };\n\t\t\t}>(ENVIRONMENT_CREATE_MUTATION, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\t// skipInitialDeploys: hold deploys so a forked env doesn't run with inherited\n\t\t\t\t\t// source/production variables before our RailwayVariable resources overwrite them.\n\t\t\t\t\tskipInitialDeploys: true,\n\t\t\t\t\t...(sourceEnvironmentId ? { sourceEnvironmentId } : {}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tenvironmentId = result.environmentCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway environment \"${inputs.name}\" (${environmentId})`,\n\t\t\t);\n\t\t}\n\n\t\tconst outs: RailwayEnvironmentOutputs = { ...inputs, environmentId };\n\n\t\treturn { id: environmentId, outs };\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayEnvironmentOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${props.name}\" not found during refresh`,\n\t\t\t);\n\t\t}\n\n\t\treturn { id: environmentId, props: { ...props, environmentId } };\n\t}\n\n\tasync update(\n\t\t_id: string,\n\t\t_olds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tconst environmentId = await findEnvironmentId(\n\t\t\tclient,\n\t\t\tnews.projectId,\n\t\t\tnews.name,\n\t\t);\n\n\t\tif (!environmentId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Railway environment \"${news.name}\" not found in project ${news.projectId}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, environmentId } };\n\t}\n\n\t/**\n\t * Deletes the environment (which cascades its per-environment service instances).\n\t * Protection of the shared production environment is the consumer's responsibility\n\t * via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(_id: string, props: RailwayEnvironmentOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(ENVIRONMENT_DELETE_MUTATION, {\n\t\t\t\tid: props.environmentId,\n\t\t\t});\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Deleted Railway environment \"${props.name}\" (${props.environmentId})`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway environment \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayEnvironmentOutputs,\n\t\tnews: RailwayEnvironmentInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayEnvironmentResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly environmentId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayEnvironmentResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tenvironmentId: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayEnvironment — replaces Pulumi's native `provider` field. */\ntype RailwayEnvironmentOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context to resolve the environment from. */\n\tproject: RailwayProject;\n};\n\n/** Args for RailwayEnvironment. */\nexport interface RailwayEnvironmentArgs {\n\t/** Environment display name (e.g. `\"production\"`, `\"staging\"`). */\n\tname: pulumi.Input<string>;\n\n\t/**\n\t * Name of an existing environment to fork from when this environment is created.\n\t * Evaluated only at creation time — changing `source` after the environment exists\n\t * has no effect (an existing environment is never re-forked).\n\t */\n\tsource?: pulumi.Input<string>;\n}\n\n/**\n * Resolves or creates a Railway environment by name within a project.\n *\n * When the named environment already exists it is adopted (no mutation).\n * When it does not exist it is created — optionally forked from a source\n * environment so the new env inherits service instances and variables.\n *\n * @example\n * ```typescript\n * // Adopt or create \"production\" (no source)\n * const production = new RailwayEnvironment(\"production\", {\n * name: \"production\",\n * }, { provider, project });\n *\n * // Adopt or create \"staging\", forked from \"production\"\n * const staging = new RailwayEnvironment(\"staging\", {\n * name: \"staging\",\n * source: \"production\",\n * }, { provider, project });\n *\n * // Use environmentId downstream\n * const service = new RailwayService(\"api\", { name: \"api\" }, {\n * provider, project, environment: staging,\n * });\n * ```\n */\nexport class RailwayEnvironment extends pulumi.ComponentResource {\n\t/** Railway environment UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayEnvironmentArgs,\n\t\topts: RailwayEnvironmentOptions,\n\t) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Environment\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayEnvironmentResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tname: args.name,\n\t\t\t\tsource: args.source,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.environmentId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
package/dist/vercel/project.cjs
CHANGED
|
@@ -145,7 +145,7 @@ function buildProjectBody(inputs) {
|
|
|
145
145
|
*
|
|
146
146
|
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
147
147
|
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
148
|
-
* `delete()` removes
|
|
148
|
+
* `delete()` removes the project; protect shared projects via the `protect` option.
|
|
149
149
|
*
|
|
150
150
|
* @internal Exported only for unit testing; not part of the public API surface.
|
|
151
151
|
*/
|
|
@@ -171,8 +171,7 @@ var VercelProjectResourceProvider = class {
|
|
|
171
171
|
}
|
|
172
172
|
const outs = {
|
|
173
173
|
...inputs,
|
|
174
|
-
projectId
|
|
175
|
-
wasAdopted: existing !== null
|
|
174
|
+
projectId
|
|
176
175
|
};
|
|
177
176
|
return {
|
|
178
177
|
id: projectId,
|
|
@@ -191,7 +190,7 @@ var VercelProjectResourceProvider = class {
|
|
|
191
190
|
}
|
|
192
191
|
};
|
|
193
192
|
}
|
|
194
|
-
async update(id,
|
|
193
|
+
async update(id, _olds, news) {
|
|
195
194
|
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`, {
|
|
196
195
|
method: "PATCH",
|
|
197
196
|
headers: {
|
|
@@ -203,20 +202,14 @@ var VercelProjectResourceProvider = class {
|
|
|
203
202
|
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
204
203
|
return { outs: {
|
|
205
204
|
...news,
|
|
206
|
-
projectId: id
|
|
207
|
-
wasAdopted: olds.wasAdopted
|
|
205
|
+
projectId: id
|
|
208
206
|
} };
|
|
209
207
|
}
|
|
210
208
|
/**
|
|
211
|
-
* Deletes the project
|
|
212
|
-
*
|
|
213
|
-
* predate the stack.
|
|
209
|
+
* Deletes the project. Protection of shared/production projects is the consumer's
|
|
210
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
214
211
|
*/
|
|
215
212
|
async delete(id, props) {
|
|
216
|
-
if (props.wasAdopted !== false) {
|
|
217
|
-
_pulumi_pulumi.log.warn(`Vercel project "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
213
|
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`, {
|
|
221
214
|
method: "DELETE",
|
|
222
215
|
headers: { Authorization: `Bearer ${props.token}` }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.cjs","names":["pulumi"],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/**\n * Authoritative list of Vercel framework preset slugs.\n * Consumers may use this array for validation or UI rendering.\n * Source of truth: @vercel/frameworks (run `bun run script` to regenerate).\n * When Vercel adds a new framework, update this array and release a new version.\n */\nexport const VERCEL_FRAMEWORKS = [\n\t// Full-stack & React\n\t\"blitzjs\",\n\t\"nextjs\",\n\t\"gatsby\",\n\t\"remix\",\n\t\"react-router\",\n\t\"astro\",\n\t\"preact\",\n\t\"solidstart-1\",\n\t\"solidstart\",\n\t\"create-react-app\",\n\t\"ionic-react\",\n\t\"tanstack-start\",\n\t\"redwoodjs\",\n\t\"hydrogen\",\n\t// Vue ecosystem\n\t\"vue\",\n\t\"nuxtjs\",\n\t\"vitepress\",\n\t\"vuepress\",\n\t\"gridsome\",\n\t\"saber\",\n\t// Svelte ecosystem\n\t\"svelte\",\n\t\"sveltekit\",\n\t\"sveltekit-1\",\n\t\"sapper\",\n\t// Angular ecosystem\n\t\"angular\",\n\t\"ionic-angular\",\n\t\"scully\",\n\t// Static site generators\n\t\"hexo\",\n\t\"eleventy\",\n\t\"docusaurus-2\",\n\t\"docusaurus\",\n\t\"hugo\",\n\t\"jekyll\",\n\t\"brunch\",\n\t\"middleman\",\n\t\"zola\",\n\t// UI / component tools\n\t\"storybook\",\n\t\"stencil\",\n\t\"dojo\",\n\t\"ember\",\n\t\"polymer\",\n\t// Build tools\n\t\"vite\",\n\t\"parcel\",\n\t// CMS\n\t\"sanity-v3\",\n\t\"sanity\",\n\t// Node.js back-ends\n\t\"nitro\",\n\t\"hono\",\n\t\"express\",\n\t\"h3\",\n\t\"koa\",\n\t\"nestjs\",\n\t\"elysia\",\n\t\"fastify\",\n\t// Python\n\t\"fastapi\",\n\t\"flask\",\n\t\"fasthtml\",\n\t\"django\",\n\t// Other languages\n\t\"ash\",\n\t\"axum\",\n\t\"actix-web\",\n\t\"ruby\",\n\t\"rust\",\n\t\"go\",\n\t\"python\",\n\t\"node\",\n\t// Misc\n\t\"xmcp\",\n\t\"umijs\",\n\t\"mastra\",\n\t\"services\",\n] as const;\n\n/**\n * Vercel framework preset slug. Derived from {@link VERCEL_FRAMEWORKS} — single source of truth.\n * When Vercel adds a new framework, update {@link VERCEL_FRAMEWORKS} and release a new version.\n */\nexport type VercelFramework = (typeof VERCEL_FRAMEWORKS)[number];\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset. */\n\tframework?: VercelFramework;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n\n\t/**\n\t * Whether this project already existed and was adopted (vs. created by us).\n\t * `delete()` only removes projects we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive default).\n\t */\n\twasAdopted?: boolean;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/** A single entry from `GET /v9/projects/{id}/domains`. */\ninterface VercelDomainEntry {\n\tname: string;\n\tverified: boolean;\n\tredirect: string | null;\n\tgitBranch: string | null;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Picks a project's production domain from its domain list, mirroring how Vercel\n * derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch\n * domain, preferring a custom domain over the `*.vercel.app` default. Returns a\n * full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty\n * (e.g. a freshly created project whose domain has not yet propagated).\n *\n * @param domains Domain entries from `GET /v9/projects/{id}/domains`\n * @param name Project name, used for the `<name>.vercel.app` fallback\n * @returns The production URL, e.g. `https://app.example.com`\n * @example\n * ```typescript\n * pickProductionDomain(\n * [{ name: \"x.vercel.app\", verified: true, redirect: null, gitBranch: null },\n * { name: \"app.example.com\", verified: true, redirect: null, gitBranch: null }],\n * \"x\",\n * ); // => \"https://app.example.com\"\n * ```\n */\nexport function pickProductionDomain(\n\tdomains: VercelDomainEntry[],\n\tname: string,\n): string {\n\tconst production = domains.filter(\n\t\t(domain) =>\n\t\t\tdomain.verified && domain.redirect === null && domain.gitBranch === null,\n\t);\n\n\tconst custom = production.find(\n\t\t(domain) => !domain.name.endsWith(\".vercel.app\"),\n\t);\n\n\tconst fallback = production.find((domain) =>\n\t\tdomain.name.endsWith(\".vercel.app\"),\n\t);\n\n\treturn `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;\n}\n\n/**\n * Fetches a project's production URL from the Vercel domains API.\n * Throws on API failure — a wrong URL would silently misconfigure the app.\n */\nasync function fetchProductionUrl(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n\tname: string,\n): Promise<string> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching domains for \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\tconst { domains = [] } = (await response.json()) as {\n\t\tdomains?: VercelDomainEntry[];\n\t};\n\n\treturn pickProductionDomain(domains, name);\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\n\t\tbody.framework = inputs.framework;\n\t}\n\n\tif (inputs.rootDirectory !== undefined) {\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\t}\n\n\tif (inputs.buildCommand !== undefined) {\n\t\tbody.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.installCommand !== undefined) {\n\t\tbody.installCommand = inputs.installCommand;\n\t}\n\n\tif (inputs.outputDirectory !== undefined) {\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\t}\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * `delete()` removes only projects this resource created (see `wasAdopted`).\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = {\n\t\t\t...inputs,\n\t\t\tprojectId,\n\t\t\twasAdopted: existing !== null,\n\t\t};\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id, wasAdopted: olds.wasAdopted } };\n\t}\n\n\t/**\n\t * Deletes the project only if we created it. Adopted projects (and state written\n\t * before `wasAdopted` existed) are left untouched, protecting projects that\n\t * predate the stack.\n\t */\n\tasync delete(id: string, props: VercelProjectOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Vercel project \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"DELETE\",\n\t\t\t\theaders: { Authorization: `Bearer ${props.token}` },\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok && response.status !== 404) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error deleting project \"${props.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\tpulumi.log.info(`Deleted Vercel project \"${props.name}\" (${id})`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<VercelFramework>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset. */\n\tframework?: pulumi.Input<VercelFramework>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * // The app's own URL comes from the project, not from config or a derived name.\n * variables: { NEXTAUTH_URL: project.url },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/**\n\t * The project's production URL (with `https://`), e.g. `https://app.example.com`.\n\t * Resolves to the custom production domain when one is attached, otherwise the\n\t * `<name>.vercel.app` default — the source of truth for the app's own URL.\n\t */\n\tpublic readonly url: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\t// The production URL is fetched fresh from Vercel on every run (not persisted as\n\t\t// dynamic-resource state), so it is always current and resolves correctly on any\n\t\t// `up` without recreating the project. `unsecret` because a public domain is not\n\t\t// sensitive (only the token used to fetch it is) — keeping it out of the secret\n\t\t// serialization that feeds downstream Variable resources.\n\t\tthis.url = pulumi.unsecret(\n\t\t\tpulumi\n\t\t\t\t.all([\n\t\t\t\t\tthis.id,\n\t\t\t\t\tprovider.token,\n\t\t\t\t\tprovider.teamId,\n\t\t\t\t\tpulumi.output(args.name),\n\t\t\t\t])\n\t\t\t\t.apply(([id, token, teamId, projectName]) =>\n\t\t\t\t\tfetchProductionUrl(token, teamId, id, projectName),\n\t\t\t\t),\n\t\t);\n\n\t\tthis.registerOutputs({ id: this.id, url: this.url });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"project.cjs","names":["pulumi"],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/**\n * Authoritative list of Vercel framework preset slugs.\n * Consumers may use this array for validation or UI rendering.\n * Source of truth: @vercel/frameworks (run `bun run script` to regenerate).\n * When Vercel adds a new framework, update this array and release a new version.\n */\nexport const VERCEL_FRAMEWORKS = [\n\t// Full-stack & React\n\t\"blitzjs\",\n\t\"nextjs\",\n\t\"gatsby\",\n\t\"remix\",\n\t\"react-router\",\n\t\"astro\",\n\t\"preact\",\n\t\"solidstart-1\",\n\t\"solidstart\",\n\t\"create-react-app\",\n\t\"ionic-react\",\n\t\"tanstack-start\",\n\t\"redwoodjs\",\n\t\"hydrogen\",\n\t// Vue ecosystem\n\t\"vue\",\n\t\"nuxtjs\",\n\t\"vitepress\",\n\t\"vuepress\",\n\t\"gridsome\",\n\t\"saber\",\n\t// Svelte ecosystem\n\t\"svelte\",\n\t\"sveltekit\",\n\t\"sveltekit-1\",\n\t\"sapper\",\n\t// Angular ecosystem\n\t\"angular\",\n\t\"ionic-angular\",\n\t\"scully\",\n\t// Static site generators\n\t\"hexo\",\n\t\"eleventy\",\n\t\"docusaurus-2\",\n\t\"docusaurus\",\n\t\"hugo\",\n\t\"jekyll\",\n\t\"brunch\",\n\t\"middleman\",\n\t\"zola\",\n\t// UI / component tools\n\t\"storybook\",\n\t\"stencil\",\n\t\"dojo\",\n\t\"ember\",\n\t\"polymer\",\n\t// Build tools\n\t\"vite\",\n\t\"parcel\",\n\t// CMS\n\t\"sanity-v3\",\n\t\"sanity\",\n\t// Node.js back-ends\n\t\"nitro\",\n\t\"hono\",\n\t\"express\",\n\t\"h3\",\n\t\"koa\",\n\t\"nestjs\",\n\t\"elysia\",\n\t\"fastify\",\n\t// Python\n\t\"fastapi\",\n\t\"flask\",\n\t\"fasthtml\",\n\t\"django\",\n\t// Other languages\n\t\"ash\",\n\t\"axum\",\n\t\"actix-web\",\n\t\"ruby\",\n\t\"rust\",\n\t\"go\",\n\t\"python\",\n\t\"node\",\n\t// Misc\n\t\"xmcp\",\n\t\"umijs\",\n\t\"mastra\",\n\t\"services\",\n] as const;\n\n/**\n * Vercel framework preset slug. Derived from {@link VERCEL_FRAMEWORKS} — single source of truth.\n * When Vercel adds a new framework, update {@link VERCEL_FRAMEWORKS} and release a new version.\n */\nexport type VercelFramework = (typeof VERCEL_FRAMEWORKS)[number];\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset. */\n\tframework?: VercelFramework;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/** A single entry from `GET /v9/projects/{id}/domains`. */\ninterface VercelDomainEntry {\n\tname: string;\n\tverified: boolean;\n\tredirect: string | null;\n\tgitBranch: string | null;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Picks a project's production domain from its domain list, mirroring how Vercel\n * derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch\n * domain, preferring a custom domain over the `*.vercel.app` default. Returns a\n * full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty\n * (e.g. a freshly created project whose domain has not yet propagated).\n *\n * @param domains Domain entries from `GET /v9/projects/{id}/domains`\n * @param name Project name, used for the `<name>.vercel.app` fallback\n * @returns The production URL, e.g. `https://app.example.com`\n * @example\n * ```typescript\n * pickProductionDomain(\n * [{ name: \"x.vercel.app\", verified: true, redirect: null, gitBranch: null },\n * { name: \"app.example.com\", verified: true, redirect: null, gitBranch: null }],\n * \"x\",\n * ); // => \"https://app.example.com\"\n * ```\n */\nexport function pickProductionDomain(\n\tdomains: VercelDomainEntry[],\n\tname: string,\n): string {\n\tconst production = domains.filter(\n\t\t(domain) =>\n\t\t\tdomain.verified && domain.redirect === null && domain.gitBranch === null,\n\t);\n\n\tconst custom = production.find(\n\t\t(domain) => !domain.name.endsWith(\".vercel.app\"),\n\t);\n\n\tconst fallback = production.find((domain) =>\n\t\tdomain.name.endsWith(\".vercel.app\"),\n\t);\n\n\treturn `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;\n}\n\n/**\n * Fetches a project's production URL from the Vercel domains API.\n * Throws on API failure — a wrong URL would silently misconfigure the app.\n */\nasync function fetchProductionUrl(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n\tname: string,\n): Promise<string> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching domains for \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\tconst { domains = [] } = (await response.json()) as {\n\t\tdomains?: VercelDomainEntry[];\n\t};\n\n\treturn pickProductionDomain(domains, name);\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\n\t\tbody.framework = inputs.framework;\n\t}\n\n\tif (inputs.rootDirectory !== undefined) {\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\t}\n\n\tif (inputs.buildCommand !== undefined) {\n\t\tbody.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.installCommand !== undefined) {\n\t\tbody.installCommand = inputs.installCommand;\n\t}\n\n\tif (inputs.outputDirectory !== undefined) {\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\t}\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * `delete()` removes the project; protect shared projects via the `protect` option.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId };\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id } };\n\t}\n\n\t/**\n\t * Deletes the project. Protection of shared/production projects is the consumer's\n\t * responsibility via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(id: string, props: VercelProjectOutputs): Promise<void> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"DELETE\",\n\t\t\t\theaders: { Authorization: `Bearer ${props.token}` },\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok && response.status !== 404) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error deleting project \"${props.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\tpulumi.log.info(`Deleted Vercel project \"${props.name}\" (${id})`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<VercelFramework>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset. */\n\tframework?: pulumi.Input<VercelFramework>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * // The app's own URL comes from the project, not from config or a derived name.\n * variables: { NEXTAUTH_URL: project.url },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/**\n\t * The project's production URL (with `https://`), e.g. `https://app.example.com`.\n\t * Resolves to the custom production domain when one is attached, otherwise the\n\t * `<name>.vercel.app` default — the source of truth for the app's own URL.\n\t */\n\tpublic readonly url: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\t// The production URL is fetched fresh from Vercel on every run (not persisted as\n\t\t// dynamic-resource state), so it is always current and resolves correctly on any\n\t\t// `up` without recreating the project. `unsecret` because a public domain is not\n\t\t// sensitive (only the token used to fetch it is) — keeping it out of the secret\n\t\t// serialization that feeds downstream Variable resources.\n\t\tthis.url = pulumi.unsecret(\n\t\t\tpulumi\n\t\t\t\t.all([\n\t\t\t\t\tthis.id,\n\t\t\t\t\tprovider.token,\n\t\t\t\t\tprovider.teamId,\n\t\t\t\t\tpulumi.output(args.name),\n\t\t\t\t])\n\t\t\t\t.apply(([id, token, teamId, projectName]) =>\n\t\t\t\t\tfetchProductionUrl(token, teamId, id, projectName),\n\t\t\t\t),\n\t\t);\n\n\t\tthis.registerOutputs({ id: this.id, url: this.url });\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -38,12 +38,6 @@ interface VercelProjectInputs {
|
|
|
38
38
|
interface VercelProjectOutputs extends VercelProjectInputs {
|
|
39
39
|
/** Vercel-assigned project ID. */
|
|
40
40
|
projectId: string;
|
|
41
|
-
/**
|
|
42
|
-
* Whether this project already existed and was adopted (vs. created by us).
|
|
43
|
-
* `delete()` only removes projects we created. Absent on state written before
|
|
44
|
-
* this field existed, which is treated as adopted (the safe, non-destructive default).
|
|
45
|
-
*/
|
|
46
|
-
wasAdopted?: boolean;
|
|
47
41
|
}
|
|
48
42
|
/** A single entry from `GET /v9/projects/{id}/domains`. */
|
|
49
43
|
interface VercelDomainEntry {
|
|
@@ -77,18 +71,17 @@ declare function pickProductionDomain(domains: VercelDomainEntry[], name: string
|
|
|
77
71
|
*
|
|
78
72
|
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
79
73
|
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
80
|
-
* `delete()` removes
|
|
74
|
+
* `delete()` removes the project; protect shared projects via the `protect` option.
|
|
81
75
|
*
|
|
82
76
|
* @internal Exported only for unit testing; not part of the public API surface.
|
|
83
77
|
*/
|
|
84
78
|
declare class VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {
|
|
85
79
|
create(inputs: VercelProjectInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
86
80
|
read(id: string, props: VercelProjectOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
87
|
-
update(id: string,
|
|
81
|
+
update(id: string, _olds: VercelProjectOutputs, news: VercelProjectInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
88
82
|
/**
|
|
89
|
-
* Deletes the project
|
|
90
|
-
*
|
|
91
|
-
* predate the stack.
|
|
83
|
+
* Deletes the project. Protection of shared/production projects is the consumer's
|
|
84
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
92
85
|
*/
|
|
93
86
|
delete(id: string, props: VercelProjectOutputs): Promise<void>;
|
|
94
87
|
diff(_id: string, olds: VercelProjectOutputs, news: VercelProjectInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.cts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;;;AAWA;;;cAAa,iBAAA;AAkFH;AAMV;;;AANU,KAME,eAAA,WAA0B,iBAAiB;AAAA;AAAA,UAGtC,mBAAA;EAAmB;EAEnC,KAAA;EAS2B;EAN3B,MAAA;EAAA;EAGA,IAAA;EAGA;EAAA,SAAA,GAAY,eAAe;EAG3B;EAAA,aAAA;EAMA;EAHA,YAAA;EAMe;EAHf,cAAA;EAOS;EAJT,eAAA;AAAA;;UAIS,oBAAA,SAA6B,mBAAmB;
|
|
1
|
+
{"version":3,"file":"project.d.cts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;;;AAWA;;;cAAa,iBAAA;AAkFH;AAMV;;;AANU,KAME,eAAA,WAA0B,iBAAiB;AAAA;AAAA,UAGtC,mBAAA;EAAmB;EAEnC,KAAA;EAS2B;EAN3B,MAAA;EAAA;EAGA,IAAA;EAGA;EAAA,SAAA,GAAY,eAAe;EAG3B;EAAA,aAAA;EAMA;EAHA,YAAA;EAMe;EAHf,cAAA;EAOS;EAJT,eAAA;AAAA;;UAIS,oBAAA,SAA6B,mBAAmB;EAYhD;EAVT,SAAS;AAAA;;UAUA,iBAAA;EACT,IAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;AAAA;AAiDD;;;;;;;;AAEa;AAuFb;;;;;;;;;;AAzFA,iBAAgB,oBAAA,CACf,OAAA,EAAS,iBAAiB,IAC1B,IAAA;;;;;;;;;;cAuFY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAgDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAapB,MAAA,CACL,EAAA,UACA,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAxEA;;;;EAkGpB,MAAA,CAAO,EAAA,UAAY,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAkBjD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA2DtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EA/HN,qCAmID,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EArIE;EAuIlB,IAAA,EAAM,MAAA,CAAO,KAAA;EA1HP;EA6HN,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,eAAA;EA3HjB;EA8HR,aAAA,GAAgB,MAAA,CAAO,KAAA;EA7HhB;EAgIP,YAAA,GAAe,MAAA,CAAO,KAAA;EA/HnB;EAkIH,cAAA,GAAiB,MAAA,CAAO,KAAA;EAlIN;EAqIlB,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;AArFW;AA6BpC;;;;;;cAiFY,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA9CjB;EAAA,SAgDR,EAAA,EAAI,MAAA,CAAO,MAAA;EApD3B;;;;;EAAA,SA2DgB,GAAA,EAAK,MAAA,CAAO,MAAA;cAG3B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -38,12 +38,6 @@ interface VercelProjectInputs {
|
|
|
38
38
|
interface VercelProjectOutputs extends VercelProjectInputs {
|
|
39
39
|
/** Vercel-assigned project ID. */
|
|
40
40
|
projectId: string;
|
|
41
|
-
/**
|
|
42
|
-
* Whether this project already existed and was adopted (vs. created by us).
|
|
43
|
-
* `delete()` only removes projects we created. Absent on state written before
|
|
44
|
-
* this field existed, which is treated as adopted (the safe, non-destructive default).
|
|
45
|
-
*/
|
|
46
|
-
wasAdopted?: boolean;
|
|
47
41
|
}
|
|
48
42
|
/** A single entry from `GET /v9/projects/{id}/domains`. */
|
|
49
43
|
interface VercelDomainEntry {
|
|
@@ -77,18 +71,17 @@ declare function pickProductionDomain(domains: VercelDomainEntry[], name: string
|
|
|
77
71
|
*
|
|
78
72
|
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
79
73
|
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
80
|
-
* `delete()` removes
|
|
74
|
+
* `delete()` removes the project; protect shared projects via the `protect` option.
|
|
81
75
|
*
|
|
82
76
|
* @internal Exported only for unit testing; not part of the public API surface.
|
|
83
77
|
*/
|
|
84
78
|
declare class VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {
|
|
85
79
|
create(inputs: VercelProjectInputs): Promise<pulumi.dynamic.CreateResult>;
|
|
86
80
|
read(id: string, props: VercelProjectOutputs): Promise<pulumi.dynamic.ReadResult>;
|
|
87
|
-
update(id: string,
|
|
81
|
+
update(id: string, _olds: VercelProjectOutputs, news: VercelProjectInputs): Promise<pulumi.dynamic.UpdateResult>;
|
|
88
82
|
/**
|
|
89
|
-
* Deletes the project
|
|
90
|
-
*
|
|
91
|
-
* predate the stack.
|
|
83
|
+
* Deletes the project. Protection of shared/production projects is the consumer's
|
|
84
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
92
85
|
*/
|
|
93
86
|
delete(id: string, props: VercelProjectOutputs): Promise<void>;
|
|
94
87
|
diff(_id: string, olds: VercelProjectOutputs, news: VercelProjectInputs): Promise<pulumi.dynamic.DiffResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.mts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;;;AAWA;;;cAAa,iBAAA;AAkFH;AAMV;;;AANU,KAME,eAAA,WAA0B,iBAAiB;AAAA;AAAA,UAGtC,mBAAA;EAAmB;EAEnC,KAAA;EAS2B;EAN3B,MAAA;EAAA;EAGA,IAAA;EAGA;EAAA,SAAA,GAAY,eAAe;EAG3B;EAAA,aAAA;EAMA;EAHA,YAAA;EAMe;EAHf,cAAA;EAOS;EAJT,eAAA;AAAA;;UAIS,oBAAA,SAA6B,mBAAmB;
|
|
1
|
+
{"version":3,"file":"project.d.mts","names":[],"sources":["../../src/vercel/project.ts"],"mappings":";;;;;;;;AAWA;;;cAAa,iBAAA;AAkFH;AAMV;;;AANU,KAME,eAAA,WAA0B,iBAAiB;AAAA;AAAA,UAGtC,mBAAA;EAAmB;EAEnC,KAAA;EAS2B;EAN3B,MAAA;EAAA;EAGA,IAAA;EAGA;EAAA,SAAA,GAAY,eAAe;EAG3B;EAAA,aAAA;EAMA;EAHA,YAAA;EAMe;EAHf,cAAA;EAOS;EAJT,eAAA;AAAA;;UAIS,oBAAA,SAA6B,mBAAmB;EAYhD;EAVT,SAAS;AAAA;;UAUA,iBAAA;EACT,IAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;AAAA;AAiDD;;;;;;;;AAEa;AAuFb;;;;;;;;;;AAzFA,iBAAgB,oBAAA,CACf,OAAA,EAAS,iBAAiB,IAC1B,IAAA;;;;;;;;;;cAuFY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAgDpB,IAAA,CACL,EAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAapB,MAAA,CACL,EAAA,UACA,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAxEA;;;;EAkGpB,MAAA,CAAO,EAAA,UAAY,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAkBjD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA2DtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EA/HN,qCAmID,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EArIE;EAuIlB,IAAA,EAAM,MAAA,CAAO,KAAA;EA1HP;EA6HN,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,eAAA;EA3HjB;EA8HR,aAAA,GAAgB,MAAA,CAAO,KAAA;EA7HhB;EAgIP,YAAA,GAAe,MAAA,CAAO,KAAA;EA/HnB;EAkIH,cAAA,GAAiB,MAAA,CAAO,KAAA;EAlIN;EAqIlB,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;AArFW;AA6BpC;;;;;;cAiFY,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA9CjB;EAAA,SAgDR,EAAA,EAAI,MAAA,CAAO,MAAA;EApD3B;;;;;EAAA,SA2DgB,GAAA,EAAK,MAAA,CAAO,MAAA;cAG3B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
package/dist/vercel/project.mjs
CHANGED
|
@@ -143,7 +143,7 @@ function buildProjectBody(inputs) {
|
|
|
143
143
|
*
|
|
144
144
|
* On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts
|
|
145
145
|
* the existing project. If 404, creates a new one via `POST /v9/projects`.
|
|
146
|
-
* `delete()` removes
|
|
146
|
+
* `delete()` removes the project; protect shared projects via the `protect` option.
|
|
147
147
|
*
|
|
148
148
|
* @internal Exported only for unit testing; not part of the public API surface.
|
|
149
149
|
*/
|
|
@@ -169,8 +169,7 @@ var VercelProjectResourceProvider = class {
|
|
|
169
169
|
}
|
|
170
170
|
const outs = {
|
|
171
171
|
...inputs,
|
|
172
|
-
projectId
|
|
173
|
-
wasAdopted: existing !== null
|
|
172
|
+
projectId
|
|
174
173
|
};
|
|
175
174
|
return {
|
|
176
175
|
id: projectId,
|
|
@@ -189,7 +188,7 @@ var VercelProjectResourceProvider = class {
|
|
|
189
188
|
}
|
|
190
189
|
};
|
|
191
190
|
}
|
|
192
|
-
async update(id,
|
|
191
|
+
async update(id, _olds, news) {
|
|
193
192
|
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`, {
|
|
194
193
|
method: "PATCH",
|
|
195
194
|
headers: {
|
|
@@ -201,20 +200,14 @@ var VercelProjectResourceProvider = class {
|
|
|
201
200
|
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
202
201
|
return { outs: {
|
|
203
202
|
...news,
|
|
204
|
-
projectId: id
|
|
205
|
-
wasAdopted: olds.wasAdopted
|
|
203
|
+
projectId: id
|
|
206
204
|
} };
|
|
207
205
|
}
|
|
208
206
|
/**
|
|
209
|
-
* Deletes the project
|
|
210
|
-
*
|
|
211
|
-
* predate the stack.
|
|
207
|
+
* Deletes the project. Protection of shared/production projects is the consumer's
|
|
208
|
+
* responsibility via the `protect` resource option, not provider logic.
|
|
212
209
|
*/
|
|
213
210
|
async delete(id, props) {
|
|
214
|
-
if (props.wasAdopted !== false) {
|
|
215
|
-
pulumi.log.warn(`Vercel project "${props.name}" deletion skipped — adopted (not created by Pulumi)`);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
211
|
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`, {
|
|
219
212
|
method: "DELETE",
|
|
220
213
|
headers: { Authorization: `Bearer ${props.token}` }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.mjs","names":[],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/**\n * Authoritative list of Vercel framework preset slugs.\n * Consumers may use this array for validation or UI rendering.\n * Source of truth: @vercel/frameworks (run `bun run script` to regenerate).\n * When Vercel adds a new framework, update this array and release a new version.\n */\nexport const VERCEL_FRAMEWORKS = [\n\t// Full-stack & React\n\t\"blitzjs\",\n\t\"nextjs\",\n\t\"gatsby\",\n\t\"remix\",\n\t\"react-router\",\n\t\"astro\",\n\t\"preact\",\n\t\"solidstart-1\",\n\t\"solidstart\",\n\t\"create-react-app\",\n\t\"ionic-react\",\n\t\"tanstack-start\",\n\t\"redwoodjs\",\n\t\"hydrogen\",\n\t// Vue ecosystem\n\t\"vue\",\n\t\"nuxtjs\",\n\t\"vitepress\",\n\t\"vuepress\",\n\t\"gridsome\",\n\t\"saber\",\n\t// Svelte ecosystem\n\t\"svelte\",\n\t\"sveltekit\",\n\t\"sveltekit-1\",\n\t\"sapper\",\n\t// Angular ecosystem\n\t\"angular\",\n\t\"ionic-angular\",\n\t\"scully\",\n\t// Static site generators\n\t\"hexo\",\n\t\"eleventy\",\n\t\"docusaurus-2\",\n\t\"docusaurus\",\n\t\"hugo\",\n\t\"jekyll\",\n\t\"brunch\",\n\t\"middleman\",\n\t\"zola\",\n\t// UI / component tools\n\t\"storybook\",\n\t\"stencil\",\n\t\"dojo\",\n\t\"ember\",\n\t\"polymer\",\n\t// Build tools\n\t\"vite\",\n\t\"parcel\",\n\t// CMS\n\t\"sanity-v3\",\n\t\"sanity\",\n\t// Node.js back-ends\n\t\"nitro\",\n\t\"hono\",\n\t\"express\",\n\t\"h3\",\n\t\"koa\",\n\t\"nestjs\",\n\t\"elysia\",\n\t\"fastify\",\n\t// Python\n\t\"fastapi\",\n\t\"flask\",\n\t\"fasthtml\",\n\t\"django\",\n\t// Other languages\n\t\"ash\",\n\t\"axum\",\n\t\"actix-web\",\n\t\"ruby\",\n\t\"rust\",\n\t\"go\",\n\t\"python\",\n\t\"node\",\n\t// Misc\n\t\"xmcp\",\n\t\"umijs\",\n\t\"mastra\",\n\t\"services\",\n] as const;\n\n/**\n * Vercel framework preset slug. Derived from {@link VERCEL_FRAMEWORKS} — single source of truth.\n * When Vercel adds a new framework, update {@link VERCEL_FRAMEWORKS} and release a new version.\n */\nexport type VercelFramework = (typeof VERCEL_FRAMEWORKS)[number];\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset. */\n\tframework?: VercelFramework;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n\n\t/**\n\t * Whether this project already existed and was adopted (vs. created by us).\n\t * `delete()` only removes projects we created. Absent on state written before\n\t * this field existed, which is treated as adopted (the safe, non-destructive default).\n\t */\n\twasAdopted?: boolean;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/** A single entry from `GET /v9/projects/{id}/domains`. */\ninterface VercelDomainEntry {\n\tname: string;\n\tverified: boolean;\n\tredirect: string | null;\n\tgitBranch: string | null;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Picks a project's production domain from its domain list, mirroring how Vercel\n * derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch\n * domain, preferring a custom domain over the `*.vercel.app` default. Returns a\n * full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty\n * (e.g. a freshly created project whose domain has not yet propagated).\n *\n * @param domains Domain entries from `GET /v9/projects/{id}/domains`\n * @param name Project name, used for the `<name>.vercel.app` fallback\n * @returns The production URL, e.g. `https://app.example.com`\n * @example\n * ```typescript\n * pickProductionDomain(\n * [{ name: \"x.vercel.app\", verified: true, redirect: null, gitBranch: null },\n * { name: \"app.example.com\", verified: true, redirect: null, gitBranch: null }],\n * \"x\",\n * ); // => \"https://app.example.com\"\n * ```\n */\nexport function pickProductionDomain(\n\tdomains: VercelDomainEntry[],\n\tname: string,\n): string {\n\tconst production = domains.filter(\n\t\t(domain) =>\n\t\t\tdomain.verified && domain.redirect === null && domain.gitBranch === null,\n\t);\n\n\tconst custom = production.find(\n\t\t(domain) => !domain.name.endsWith(\".vercel.app\"),\n\t);\n\n\tconst fallback = production.find((domain) =>\n\t\tdomain.name.endsWith(\".vercel.app\"),\n\t);\n\n\treturn `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;\n}\n\n/**\n * Fetches a project's production URL from the Vercel domains API.\n * Throws on API failure — a wrong URL would silently misconfigure the app.\n */\nasync function fetchProductionUrl(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n\tname: string,\n): Promise<string> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching domains for \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\tconst { domains = [] } = (await response.json()) as {\n\t\tdomains?: VercelDomainEntry[];\n\t};\n\n\treturn pickProductionDomain(domains, name);\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\n\t\tbody.framework = inputs.framework;\n\t}\n\n\tif (inputs.rootDirectory !== undefined) {\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\t}\n\n\tif (inputs.buildCommand !== undefined) {\n\t\tbody.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.installCommand !== undefined) {\n\t\tbody.installCommand = inputs.installCommand;\n\t}\n\n\tif (inputs.outputDirectory !== undefined) {\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\t}\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * `delete()` removes only projects this resource created (see `wasAdopted`).\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = {\n\t\t\t...inputs,\n\t\t\tprojectId,\n\t\t\twasAdopted: existing !== null,\n\t\t};\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id, wasAdopted: olds.wasAdopted } };\n\t}\n\n\t/**\n\t * Deletes the project only if we created it. Adopted projects (and state written\n\t * before `wasAdopted` existed) are left untouched, protecting projects that\n\t * predate the stack.\n\t */\n\tasync delete(id: string, props: VercelProjectOutputs): Promise<void> {\n\t\tif (props.wasAdopted !== false) {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Vercel project \"${props.name}\" deletion skipped — adopted (not created by Pulumi)`,\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"DELETE\",\n\t\t\t\theaders: { Authorization: `Bearer ${props.token}` },\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok && response.status !== 404) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error deleting project \"${props.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\tpulumi.log.info(`Deleted Vercel project \"${props.name}\" (${id})`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<VercelFramework>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset. */\n\tframework?: pulumi.Input<VercelFramework>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * // The app's own URL comes from the project, not from config or a derived name.\n * variables: { NEXTAUTH_URL: project.url },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/**\n\t * The project's production URL (with `https://`), e.g. `https://app.example.com`.\n\t * Resolves to the custom production domain when one is attached, otherwise the\n\t * `<name>.vercel.app` default — the source of truth for the app's own URL.\n\t */\n\tpublic readonly url: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\t// The production URL is fetched fresh from Vercel on every run (not persisted as\n\t\t// dynamic-resource state), so it is always current and resolves correctly on any\n\t\t// `up` without recreating the project. `unsecret` because a public domain is not\n\t\t// sensitive (only the token used to fetch it is) — keeping it out of the secret\n\t\t// serialization that feeds downstream Variable resources.\n\t\tthis.url = pulumi.unsecret(\n\t\t\tpulumi\n\t\t\t\t.all([\n\t\t\t\t\tthis.id,\n\t\t\t\t\tprovider.token,\n\t\t\t\t\tprovider.teamId,\n\t\t\t\t\tpulumi.output(args.name),\n\t\t\t\t])\n\t\t\t\t.apply(([id, token, teamId, projectName]) =>\n\t\t\t\t\tfetchProductionUrl(token, teamId, id, projectName),\n\t\t\t\t),\n\t\t);\n\n\t\tthis.registerOutputs({ id: this.id, url: this.url });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"project.mjs","names":[],"sources":["../../src/vercel/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport type { VercelProvider } from \"./provider\";\n\nconst VERCEL_API_URL = \"https://api.vercel.com\";\n\n/**\n * Authoritative list of Vercel framework preset slugs.\n * Consumers may use this array for validation or UI rendering.\n * Source of truth: @vercel/frameworks (run `bun run script` to regenerate).\n * When Vercel adds a new framework, update this array and release a new version.\n */\nexport const VERCEL_FRAMEWORKS = [\n\t// Full-stack & React\n\t\"blitzjs\",\n\t\"nextjs\",\n\t\"gatsby\",\n\t\"remix\",\n\t\"react-router\",\n\t\"astro\",\n\t\"preact\",\n\t\"solidstart-1\",\n\t\"solidstart\",\n\t\"create-react-app\",\n\t\"ionic-react\",\n\t\"tanstack-start\",\n\t\"redwoodjs\",\n\t\"hydrogen\",\n\t// Vue ecosystem\n\t\"vue\",\n\t\"nuxtjs\",\n\t\"vitepress\",\n\t\"vuepress\",\n\t\"gridsome\",\n\t\"saber\",\n\t// Svelte ecosystem\n\t\"svelte\",\n\t\"sveltekit\",\n\t\"sveltekit-1\",\n\t\"sapper\",\n\t// Angular ecosystem\n\t\"angular\",\n\t\"ionic-angular\",\n\t\"scully\",\n\t// Static site generators\n\t\"hexo\",\n\t\"eleventy\",\n\t\"docusaurus-2\",\n\t\"docusaurus\",\n\t\"hugo\",\n\t\"jekyll\",\n\t\"brunch\",\n\t\"middleman\",\n\t\"zola\",\n\t// UI / component tools\n\t\"storybook\",\n\t\"stencil\",\n\t\"dojo\",\n\t\"ember\",\n\t\"polymer\",\n\t// Build tools\n\t\"vite\",\n\t\"parcel\",\n\t// CMS\n\t\"sanity-v3\",\n\t\"sanity\",\n\t// Node.js back-ends\n\t\"nitro\",\n\t\"hono\",\n\t\"express\",\n\t\"h3\",\n\t\"koa\",\n\t\"nestjs\",\n\t\"elysia\",\n\t\"fastify\",\n\t// Python\n\t\"fastapi\",\n\t\"flask\",\n\t\"fasthtml\",\n\t\"django\",\n\t// Other languages\n\t\"ash\",\n\t\"axum\",\n\t\"actix-web\",\n\t\"ruby\",\n\t\"rust\",\n\t\"go\",\n\t\"python\",\n\t\"node\",\n\t// Misc\n\t\"xmcp\",\n\t\"umijs\",\n\t\"mastra\",\n\t\"services\",\n] as const;\n\n/**\n * Vercel framework preset slug. Derived from {@link VERCEL_FRAMEWORKS} — single source of truth.\n * When Vercel adds a new framework, update {@link VERCEL_FRAMEWORKS} and release a new version.\n */\nexport type VercelFramework = (typeof VERCEL_FRAMEWORKS)[number];\n\n/** Resolved inputs for the Vercel project dynamic provider. */\nexport interface VercelProjectInputs {\n\t/** Vercel API bearer token. */\n\ttoken: string;\n\n\t/** Vercel team/org ID. */\n\tteamId: string;\n\n\t/** Project name. */\n\tname: string;\n\n\t/** Framework preset. */\n\tframework?: VercelFramework;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: string;\n\n\t/** Custom build command. */\n\tbuildCommand?: string;\n\n\t/** Custom install command. */\n\tinstallCommand?: string;\n\n\t/** Custom output directory. */\n\toutputDirectory?: string;\n}\n\n/** Persisted state for the Vercel project. */\ninterface VercelProjectOutputs extends VercelProjectInputs {\n\t/** Vercel-assigned project ID. */\n\tprojectId: string;\n}\n\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/** A single entry from `GET /v9/projects/{id}/domains`. */\ninterface VercelDomainEntry {\n\tname: string;\n\tverified: boolean;\n\tredirect: string | null;\n\tgitBranch: string | null;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Picks a project's production domain from its domain list, mirroring how Vercel\n * derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch\n * domain, preferring a custom domain over the `*.vercel.app` default. Returns a\n * full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty\n * (e.g. a freshly created project whose domain has not yet propagated).\n *\n * @param domains Domain entries from `GET /v9/projects/{id}/domains`\n * @param name Project name, used for the `<name>.vercel.app` fallback\n * @returns The production URL, e.g. `https://app.example.com`\n * @example\n * ```typescript\n * pickProductionDomain(\n * [{ name: \"x.vercel.app\", verified: true, redirect: null, gitBranch: null },\n * { name: \"app.example.com\", verified: true, redirect: null, gitBranch: null }],\n * \"x\",\n * ); // => \"https://app.example.com\"\n * ```\n */\nexport function pickProductionDomain(\n\tdomains: VercelDomainEntry[],\n\tname: string,\n): string {\n\tconst production = domains.filter(\n\t\t(domain) =>\n\t\t\tdomain.verified && domain.redirect === null && domain.gitBranch === null,\n\t);\n\n\tconst custom = production.find(\n\t\t(domain) => !domain.name.endsWith(\".vercel.app\"),\n\t);\n\n\tconst fallback = production.find((domain) =>\n\t\tdomain.name.endsWith(\".vercel.app\"),\n\t);\n\n\treturn `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;\n}\n\n/**\n * Fetches a project's production URL from the Vercel domains API.\n * Throws on API failure — a wrong URL would silently misconfigure the app.\n */\nasync function fetchProductionUrl(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n\tname: string,\n): Promise<string> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching domains for \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\tconst { domains = [] } = (await response.json()) as {\n\t\tdomains?: VercelDomainEntry[];\n\t};\n\n\treturn pickProductionDomain(domains, name);\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\n\t\tbody.framework = inputs.framework;\n\t}\n\n\tif (inputs.rootDirectory !== undefined) {\n\t\tbody.rootDirectory = inputs.rootDirectory;\n\t}\n\n\tif (inputs.buildCommand !== undefined) {\n\t\tbody.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.installCommand !== undefined) {\n\t\tbody.installCommand = inputs.installCommand;\n\t}\n\n\tif (inputs.outputDirectory !== undefined) {\n\t\tbody.outputDirectory = inputs.outputDirectory;\n\t}\n\n\treturn body;\n}\n\n/**\n * Dynamic provider implementing adopt-or-create for Vercel projects.\n *\n * On `create()`, calls `GET /v9/projects/{name}?teamId=…`. If found, adopts\n * the existing project. If 404, creates a new one via `POST /v9/projects`.\n * `delete()` removes the project; protect shared projects via the `protect` option.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class VercelProjectResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst existing = await fetchProject(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tlet projectId: string;\n\n\t\tif (existing) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopting existing Vercel project \"${inputs.name}\" (${existing.id})`,\n\t\t\t);\n\n\t\t\tprojectId = existing.id;\n\t\t} else {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Vercel project \"${inputs.name}\" not found — creating...`,\n\t\t\t);\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${VERCEL_API_URL}/v9/projects?teamId=${inputs.teamId}`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${inputs.token}`,\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(buildProjectBody(inputs)),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Vercel API error creating project \"${inputs.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst created = (await response.json()) as VercelProjectResponse;\n\n\t\t\tprojectId = created.id;\n\t\t}\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId };\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: VercelProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst project = await fetchProject(props.token, props.teamId, id);\n\n\t\tif (!project) {\n\t\t\tthrow new Error(`Vercel project \"${id}\" not found during refresh`);\n\t\t}\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: { ...props, name: project.name, projectId: project.id },\n\t\t};\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${id}?teamId=${news.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"PATCH\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${news.token}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(buildProjectBody(news)),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error updating project \"${id}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\treturn { outs: { ...news, projectId: id } };\n\t}\n\n\t/**\n\t * Deletes the project. Protection of shared/production projects is the consumer's\n\t * responsibility via the `protect` resource option, not provider logic.\n\t */\n\tasync delete(id: string, props: VercelProjectOutputs): Promise<void> {\n\t\tconst response = await fetch(\n\t\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(id)}?teamId=${props.teamId}`,\n\t\t\t{\n\t\t\t\tmethod: \"DELETE\",\n\t\t\t\theaders: { Authorization: `Bearer ${props.token}` },\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok && response.status !== 404) {\n\t\t\tthrow new Error(\n\t\t\t\t`Vercel API error deleting project \"${props.name}\" (${response.status}): ${await response.text()}`,\n\t\t\t);\n\t\t}\n\n\t\tpulumi.log.info(`Deleted Vercel project \"${props.name}\" (${id})`);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<VercelFramework>;\n\t\t\trootDirectory?: pulumi.Input<string>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tinstallCommand?: pulumi.Input<string>;\n\t\t\toutputDirectory?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew VercelProjectResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, projectId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for VercelProject — replaces Pulumi's native `provider` field. */\ntype VercelProjectOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n};\n\n/** Args for VercelProject. */\nexport interface VercelProjectArgs {\n\t/** Project name. Used for both adoption lookup and display name. */\n\tname: pulumi.Input<string>;\n\n\t/** Framework preset. */\n\tframework?: pulumi.Input<VercelFramework>;\n\n\t/** Relative path to the project root within a monorepo (e.g. `\"apps/nexus\"`). */\n\trootDirectory?: pulumi.Input<string>;\n\n\t/** Custom build command. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Custom install command. */\n\tinstallCommand?: pulumi.Input<string>;\n\n\t/** Custom output directory. */\n\toutputDirectory?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Vercel project with adopt-or-create semantics.\n *\n * On first `pulumi up`, looks up the project by name. If it already exists,\n * the resource adopts it. If not, a new project is created. Deletion is a\n * no-op to protect production projects.\n *\n * @example\n * ```typescript\n * const project = new VercelProject(\"nexus\", {\n * name: \"nexus\",\n * framework: \"nextjs\",\n * rootDirectory: \"apps/nexus\",\n * }, { provider });\n *\n * new VercelVariable(\"nexus-vars\", {\n * projectId: project.id,\n * // The app's own URL comes from the project, not from config or a derived name.\n * variables: { NEXTAUTH_URL: project.url },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\t/**\n\t * The project's production URL (with `https://`), e.g. `https://app.example.com`.\n\t * Resolves to the custom production domain when one is attached, otherwise the\n\t * `<name>.vercel.app` default — the source of truth for the app's own URL.\n\t */\n\tpublic readonly url: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\t// The production URL is fetched fresh from Vercel on every run (not persisted as\n\t\t// dynamic-resource state), so it is always current and resolves correctly on any\n\t\t// `up` without recreating the project. `unsecret` because a public domain is not\n\t\t// sensitive (only the token used to fetch it is) — keeping it out of the secret\n\t\t// serialization that feeds downstream Variable resources.\n\t\tthis.url = pulumi.unsecret(\n\t\t\tpulumi\n\t\t\t\t.all([\n\t\t\t\t\tthis.id,\n\t\t\t\t\tprovider.token,\n\t\t\t\t\tprovider.teamId,\n\t\t\t\t\tpulumi.output(args.name),\n\t\t\t\t])\n\t\t\t\t.apply(([id, token, teamId, projectName]) =>\n\t\t\t\t\tfetchProductionUrl(token, teamId, id, projectName),\n\t\t\t\t),\n\t\t);\n\n\t\tthis.registerOutputs({ id: this.id, url: this.url });\n\t}\n}\n"],"mappings":"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"}
|