@infracraft/pulumi 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vercel/project.cjs
CHANGED
|
@@ -167,11 +167,9 @@ var VercelProjectResourceProvider = class {
|
|
|
167
167
|
if (!response.ok) throw new Error(`Vercel API error creating project "${inputs.name}" (${response.status}): ${await response.text()}`);
|
|
168
168
|
projectId = (await response.json()).id;
|
|
169
169
|
}
|
|
170
|
-
const productionUrl = await fetchProductionUrl(inputs.token, inputs.teamId, projectId, inputs.name);
|
|
171
170
|
const outs = {
|
|
172
171
|
...inputs,
|
|
173
|
-
projectId
|
|
174
|
-
productionUrl
|
|
172
|
+
projectId
|
|
175
173
|
};
|
|
176
174
|
return {
|
|
177
175
|
id: projectId,
|
|
@@ -181,14 +179,12 @@ var VercelProjectResourceProvider = class {
|
|
|
181
179
|
async read(id, props) {
|
|
182
180
|
const project = await fetchProject(props.token, props.teamId, id);
|
|
183
181
|
if (!project) throw new Error(`Vercel project "${id}" not found during refresh`);
|
|
184
|
-
const productionUrl = await fetchProductionUrl(props.token, props.teamId, id, project.name);
|
|
185
182
|
return {
|
|
186
183
|
id: project.id,
|
|
187
184
|
props: {
|
|
188
185
|
...props,
|
|
189
186
|
name: project.name,
|
|
190
|
-
projectId: project.id
|
|
191
|
-
productionUrl
|
|
187
|
+
projectId: project.id
|
|
192
188
|
}
|
|
193
189
|
};
|
|
194
190
|
}
|
|
@@ -202,11 +198,9 @@ var VercelProjectResourceProvider = class {
|
|
|
202
198
|
body: JSON.stringify(buildProjectBody(news))
|
|
203
199
|
});
|
|
204
200
|
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
205
|
-
const productionUrl = await fetchProductionUrl(news.token, news.teamId, id, news.name);
|
|
206
201
|
return { outs: {
|
|
207
202
|
...news,
|
|
208
|
-
projectId: id
|
|
209
|
-
productionUrl
|
|
203
|
+
projectId: id
|
|
210
204
|
} };
|
|
211
205
|
}
|
|
212
206
|
async delete() {
|
|
@@ -236,8 +230,7 @@ var VercelProjectResource = class extends _pulumi_pulumi.dynamic.Resource {
|
|
|
236
230
|
constructor(name, args, opts) {
|
|
237
231
|
super(new VercelProjectResourceProvider(), name, {
|
|
238
232
|
...args,
|
|
239
|
-
projectId: void 0
|
|
240
|
-
productionUrl: void 0
|
|
233
|
+
projectId: void 0
|
|
241
234
|
}, opts);
|
|
242
235
|
}
|
|
243
236
|
};
|
|
@@ -273,7 +266,12 @@ var VercelProject = class extends _pulumi_pulumi.ComponentResource {
|
|
|
273
266
|
...args
|
|
274
267
|
}, { parent: this });
|
|
275
268
|
this.id = resource.projectId;
|
|
276
|
-
this.url =
|
|
269
|
+
this.url = _pulumi_pulumi.unsecret(_pulumi_pulumi.all([
|
|
270
|
+
this.id,
|
|
271
|
+
provider.token,
|
|
272
|
+
provider.teamId,
|
|
273
|
+
_pulumi_pulumi.output(args.name)
|
|
274
|
+
]).apply(([id, token, teamId, projectName]) => fetchProductionUrl(token, teamId, id, projectName)));
|
|
277
275
|
this.registerOutputs({
|
|
278
276
|
id: this.id,
|
|
279
277
|
url: this.url
|
|
@@ -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 * The project's production URL (with `https://`), mirroring Vercel's\n\t * `VERCEL_PROJECT_PRODUCTION_URL`: the custom production domain if one is\n\t * attached, otherwise the `<name>.vercel.app` default.\n\t */\n\tproductionUrl: 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\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 * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {\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 productionUrl = await fetchProductionUrl(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tprojectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId, productionUrl };\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\tconst productionUrl = await fetchProductionUrl(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tid,\n\t\t\tproject.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: project.name,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tproductionUrl,\n\t\t\t},\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\tconst productionUrl = await fetchProductionUrl(\n\t\t\tnews.token,\n\t\t\tnews.teamId,\n\t\t\tid,\n\t\t\tnews.name,\n\t\t);\n\n\t\treturn { outs: { ...news, projectId: id, productionUrl } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\tpublic declare readonly productionUrl: 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, productionUrl: 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\t\tthis.url = resource.productionUrl;\n\n\t\tthis.registerOutputs({ id: this.id, url: this.url });\n\t}\n}\n"],"mappings":";;;;;;AAGA,MAAM,iBAAiB;;;;;;;AAQvB,MAAa,oBAAoB;CAEhC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;AACD;;;;;AAkEA,eAAe,aACd,OACA,QACA,UACwC;CACxC,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,mBAAmB,QAAQ,EAAE,UAAU,UACxE,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;CAEA,IAAI,SAAS,WAAW,KACvB,OAAO;CAGR,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,sCAAsC,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAC9F;CAGD,OAAQ,MAAM,SAAS,KAAK;AAC7B;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBACf,SACA,MACS;CACT,MAAM,aAAa,QAAQ,QACzB,WACA,OAAO,YAAY,OAAO,aAAa,QAAQ,OAAO,cAAc,IACtE;CAEA,MAAM,SAAS,WAAW,MACxB,WAAW,CAAC,OAAO,KAAK,SAAS,aAAa,CAChD;CACA,MAAM,WAAW,WAAW,MAAM,WACjC,OAAO,KAAK,SAAS,aAAa,CACnC;CAEA,OAAO,WAAW,QAAQ,QAAQ,UAAU,QAAQ,GAAG,KAAK;AAC7D;;;;;AAMA,eAAe,mBACd,OACA,QACA,UACA,MACkB;CAClB,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,mBAAmB,QAAQ,EAAE,kBAAkB,UAChF,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE,CACjD;CAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,0CAA0C,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GAClG;CAGD,MAAM,EAAE,UAAU,CAAC,MAAO,MAAM,SAAS,KAAK;CAI9C,OAAO,qBAAqB,SAAS,IAAI;AAC1C;;;;;AAMA,SAAS,iBACR,QACyB;CACzB,MAAM,OAA+B,EAAE,MAAM,OAAO,KAAK;CAEzD,IAAI,OAAO,cAAc,QACxB,KAAK,YAAY,OAAO;CAGzB,IAAI,OAAO,kBAAkB,QAC5B,KAAK,gBAAgB,OAAO;CAG7B,IAAI,OAAO,iBAAiB,QAC3B,KAAK,eAAe,OAAO;CAG5B,IAAI,OAAO,mBAAmB,QAC7B,KAAK,iBAAiB,OAAO;CAG9B,IAAI,OAAO,oBAAoB,QAC9B,KAAK,kBAAkB,OAAO;CAG/B,OAAO;AACR;;;;;;;;AASA,IAAM,gCAAN,MAA+E;CAC9E,MAAM,OACL,QACuC;EACvC,MAAM,WAAW,MAAM,aACtB,OAAO,OACP,OAAO,QACP,OAAO,IACR;EAEA,IAAI;EAEJ,IAAI,UAAU;GACb,eAAO,IAAI,KACV,qCAAqC,OAAO,KAAK,KAAK,SAAS,GAAG,EACnE;GAEA,YAAY,SAAS;EACtB,OAAO;GACN,eAAO,IAAI,KACV,mBAAmB,OAAO,KAAK,0BAChC;GAEA,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,sBAAsB,OAAO,UAC/C;IACC,QAAQ;IACR,SAAS;KACR,eAAe,UAAU,OAAO;KAChC,gBAAgB;IACjB;IACA,MAAM,KAAK,UAAU,iBAAiB,MAAM,CAAC;GAC9C,CACD;GAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,sCAAsC,OAAO,KAAK,KAAK,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GACjG;GAKD,aAAY,MAFW,SAAS,KAAK,GAEjB;EACrB;EAEA,MAAM,gBAAgB,MAAM,mBAC3B,OAAO,OACP,OAAO,QACP,WACA,OAAO,IACR;EAEA,MAAM,OAA6B;GAAE,GAAG;GAAQ;GAAW;EAAc;EAEzE,OAAO;GAAE,IAAI;GAAW;EAAK;CAC9B;CAEA,MAAM,KACL,IACA,OACqC;EACrC,MAAM,UAAU,MAAM,aAAa,MAAM,OAAO,MAAM,QAAQ,EAAE;EAEhE,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,mBAAmB,GAAG,2BAA2B;EAGlE,MAAM,gBAAgB,MAAM,mBAC3B,MAAM,OACN,MAAM,QACN,IACA,QAAQ,IACT;EAEA,OAAO;GACN,IAAI,QAAQ;GACZ,OAAO;IACN,GAAG;IACH,MAAM,QAAQ;IACd,WAAW,QAAQ;IACnB;GACD;EACD;CACD;CAEA,MAAM,OACL,IACA,OACA,MACuC;EACvC,MAAM,WAAW,MAAM,MACtB,GAAG,eAAe,eAAe,GAAG,UAAU,KAAK,UACnD;GACC,QAAQ;GACR,SAAS;IACR,eAAe,UAAU,KAAK;IAC9B,gBAAgB;GACjB;GACA,MAAM,KAAK,UAAU,iBAAiB,IAAI,CAAC;EAC5C,CACD;EAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MACT,sCAAsC,GAAG,KAAK,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GACxF;EAGD,MAAM,gBAAgB,MAAM,mBAC3B,KAAK,OACL,KAAK,QACL,IACA,KAAK,IACN;EAEA,OAAO,EAAE,MAAM;GAAE,GAAG;GAAM,WAAW;GAAI;EAAc,EAAE;CAC1D;CAEA,MAAM,SAAwB;EAC7B,eAAO,IAAI,KACV,sEACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAC5B,MAAM,UAAoB,CAAC;EAE3B,IAAI,KAAK,WAAW,KAAK,QACxB,SAAS,KAAK,QAAQ;EAYvB,KAAK,MAAM,SAAS;GARnB;GACA;GACA;GACA;GACA;GACA;EAGiC,GACjC,IAAI,KAAK,WAAW,KAAK,QACxB,QAAQ,KAAK,KAAK;EAIpB,OAAO;GACN,SAAS,SAAS,SAAS,KAAK,QAAQ,SAAS;GACjD;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,wBAAN,cAAoCA,eAAO,QAAQ,SAAS;CAI3D,YACC,MACA,MAUA,MACC;EACD,MACC,IAAI,8BAA8B,GAClC,MACA;GAAE,GAAG;GAAM,WAAW;GAAW,eAAe;EAAU,GAC1D,IACD;CACD;AACD;;;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,gBAAb,cAAmCA,eAAO,kBAAkB;CAW3D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,GAAG,eAAe;EAEpC,MAAM,6BAA6B,MAAM,CAAC,GAAG,UAAU;EAEvD,MAAM,WAAW,IAAI,sBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,GAAG;EACJ,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,KAAK,SAAS;EACnB,KAAK,MAAM,SAAS;EAEpB,KAAK,gBAAgB;GAAE,IAAI,KAAK;GAAI,KAAK,KAAK;EAAI,CAAC;CACpD;AACD"}
|
|
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\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 * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {\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\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<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([this.id, provider.token, provider.teamId, pulumi.output(args.name)])\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 +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;
|
|
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;EAmBS;EAhBT,eAAA;AAAA;;UAgBS,iBAAA;EACT,IAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;AAAA;AAiDD;;;;;;;;AAEa;AAeZ;;;;;;;;;;AAjBD,iBAAgB,oBAAA,CACf,OAAA,EAAS,iBAAiB,IAC1B,IAAA;;KAsPI,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAIiB,qCAAxB,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EAEV;EAAN,IAAA,EAAM,MAAA,CAAO,KAAA;EAGD;EAAZ,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,eAAA;EAMV;EAHf,aAAA,GAAgB,MAAA,CAAO,KAAA;EASL;EANlB,YAAA,GAAe,MAAA,CAAO,KAAA;EAMQ;EAH9B,cAAA,GAAiB,MAAA,CAAO,KAAA;EAZlB;EAeN,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;AAAK;AAyB/B;;;;;cAAa,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAclC;EAAA,SAZS,EAAA,EAAI,MAAA,CAAO,MAAA;EAF+B;;;;;EAAA,SAS1C,GAAA,EAAK,MAAA,CAAO,MAAA;cAG3B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -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;
|
|
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;EAmBS;EAhBT,eAAA;AAAA;;UAgBS,iBAAA;EACT,IAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;AAAA;AAiDD;;;;;;;;AAEa;AAeZ;;;;;;;;;;AAjBD,iBAAgB,oBAAA,CACf,OAAA,EAAS,iBAAiB,IAC1B,IAAA;;KAsPI,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAIiB,qCAAxB,QAAA,EAAU,cAAA;AAAA;;UAIM,iBAAA;EAEV;EAAN,IAAA,EAAM,MAAA,CAAO,KAAA;EAGD;EAAZ,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,eAAA;EAMV;EAHf,aAAA,GAAgB,MAAA,CAAO,KAAA;EASL;EANlB,YAAA,GAAe,MAAA,CAAO,KAAA;EAMQ;EAH9B,cAAA,GAAiB,MAAA,CAAO,KAAA;EAZlB;EAeN,eAAA,GAAkB,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;;;;;;;;AAAK;AAyB/B;;;;;cAAa,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAclC;EAAA,SAZS,EAAA,EAAI,MAAA,CAAO,MAAA;EAF+B;;;;;EAAA,SAS1C,GAAA,EAAK,MAAA,CAAO,MAAA;cAG3B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
package/dist/vercel/project.mjs
CHANGED
|
@@ -165,11 +165,9 @@ var VercelProjectResourceProvider = class {
|
|
|
165
165
|
if (!response.ok) throw new Error(`Vercel API error creating project "${inputs.name}" (${response.status}): ${await response.text()}`);
|
|
166
166
|
projectId = (await response.json()).id;
|
|
167
167
|
}
|
|
168
|
-
const productionUrl = await fetchProductionUrl(inputs.token, inputs.teamId, projectId, inputs.name);
|
|
169
168
|
const outs = {
|
|
170
169
|
...inputs,
|
|
171
|
-
projectId
|
|
172
|
-
productionUrl
|
|
170
|
+
projectId
|
|
173
171
|
};
|
|
174
172
|
return {
|
|
175
173
|
id: projectId,
|
|
@@ -179,14 +177,12 @@ var VercelProjectResourceProvider = class {
|
|
|
179
177
|
async read(id, props) {
|
|
180
178
|
const project = await fetchProject(props.token, props.teamId, id);
|
|
181
179
|
if (!project) throw new Error(`Vercel project "${id}" not found during refresh`);
|
|
182
|
-
const productionUrl = await fetchProductionUrl(props.token, props.teamId, id, project.name);
|
|
183
180
|
return {
|
|
184
181
|
id: project.id,
|
|
185
182
|
props: {
|
|
186
183
|
...props,
|
|
187
184
|
name: project.name,
|
|
188
|
-
projectId: project.id
|
|
189
|
-
productionUrl
|
|
185
|
+
projectId: project.id
|
|
190
186
|
}
|
|
191
187
|
};
|
|
192
188
|
}
|
|
@@ -200,11 +196,9 @@ var VercelProjectResourceProvider = class {
|
|
|
200
196
|
body: JSON.stringify(buildProjectBody(news))
|
|
201
197
|
});
|
|
202
198
|
if (!response.ok) throw new Error(`Vercel API error updating project "${id}" (${response.status}): ${await response.text()}`);
|
|
203
|
-
const productionUrl = await fetchProductionUrl(news.token, news.teamId, id, news.name);
|
|
204
199
|
return { outs: {
|
|
205
200
|
...news,
|
|
206
|
-
projectId: id
|
|
207
|
-
productionUrl
|
|
201
|
+
projectId: id
|
|
208
202
|
} };
|
|
209
203
|
}
|
|
210
204
|
async delete() {
|
|
@@ -234,8 +228,7 @@ var VercelProjectResource = class extends pulumi.dynamic.Resource {
|
|
|
234
228
|
constructor(name, args, opts) {
|
|
235
229
|
super(new VercelProjectResourceProvider(), name, {
|
|
236
230
|
...args,
|
|
237
|
-
projectId: void 0
|
|
238
|
-
productionUrl: void 0
|
|
231
|
+
projectId: void 0
|
|
239
232
|
}, opts);
|
|
240
233
|
}
|
|
241
234
|
};
|
|
@@ -271,7 +264,12 @@ var VercelProject = class extends pulumi.ComponentResource {
|
|
|
271
264
|
...args
|
|
272
265
|
}, { parent: this });
|
|
273
266
|
this.id = resource.projectId;
|
|
274
|
-
this.url =
|
|
267
|
+
this.url = pulumi.unsecret(pulumi.all([
|
|
268
|
+
this.id,
|
|
269
|
+
provider.token,
|
|
270
|
+
provider.teamId,
|
|
271
|
+
pulumi.output(args.name)
|
|
272
|
+
]).apply(([id, token, teamId, projectName]) => fetchProductionUrl(token, teamId, id, projectName)));
|
|
275
273
|
this.registerOutputs({
|
|
276
274
|
id: this.id,
|
|
277
275
|
url: this.url
|
|
@@ -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 * The project's production URL (with `https://`), mirroring Vercel's\n\t * `VERCEL_PROJECT_PRODUCTION_URL`: the custom production domain if one is\n\t * attached, otherwise the `<name>.vercel.app` default.\n\t */\n\tproductionUrl: 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\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 * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {\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 productionUrl = await fetchProductionUrl(\n\t\t\tinputs.token,\n\t\t\tinputs.teamId,\n\t\t\tprojectId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tconst outs: VercelProjectOutputs = { ...inputs, projectId, productionUrl };\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\tconst productionUrl = await fetchProductionUrl(\n\t\t\tprops.token,\n\t\t\tprops.teamId,\n\t\t\tid,\n\t\t\tproject.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: project.id,\n\t\t\tprops: {\n\t\t\t\t...props,\n\t\t\t\tname: project.name,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tproductionUrl,\n\t\t\t},\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\tconst productionUrl = await fetchProductionUrl(\n\t\t\tnews.token,\n\t\t\tnews.teamId,\n\t\t\tid,\n\t\t\tnews.name,\n\t\t);\n\n\t\treturn { outs: { ...news, projectId: id, productionUrl } };\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\tpublic declare readonly productionUrl: 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, productionUrl: 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\t\tthis.url = resource.productionUrl;\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\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 * Deletion is a no-op to protect production projects.\n */\nclass VercelProjectResourceProvider implements pulumi.dynamic.ResourceProvider {\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\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Vercel project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: VercelProjectOutputs,\n\t\tnews: VercelProjectInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.teamId !== news.teamId) {\n\t\t\treplaces.push(\"teamId\");\n\t\t}\n\n\t\tconst updatableFields = [\n\t\t\t\"name\",\n\t\t\t\"framework\",\n\t\t\t\"rootDirectory\",\n\t\t\t\"buildCommand\",\n\t\t\t\"installCommand\",\n\t\t\t\"outputDirectory\",\n\t\t] as const;\n\n\t\tfor (const field of updatableFields) {\n\t\t\tif (olds[field] !== news[field]) {\n\t\t\t\tchanges.push(field);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass VercelProjectResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tteamId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\tframework?: pulumi.Input<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([this.id, provider.token, provider.teamId, pulumi.output(args.name)])\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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"}
|