@infracraft/pulumi 1.6.6 → 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 +48 -2
- package/dist/vercel/project.cjs.map +1 -1
- package/dist/vercel/project.d.cts +36 -2
- package/dist/vercel/project.d.cts.map +1 -1
- package/dist/vercel/project.d.mts +36 -2
- package/dist/vercel/project.d.mts.map +1 -1
- package/dist/vercel/project.mjs +48 -3
- package/dist/vercel/project.mjs.map +1 -1
- package/package.json +3 -3
package/dist/vercel/project.cjs
CHANGED
|
@@ -93,6 +93,41 @@ async function fetchProject(token, teamId, idOrName) {
|
|
|
93
93
|
return await response.json();
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
+
* Picks a project's production domain from its domain list, mirroring how Vercel
|
|
97
|
+
* derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch
|
|
98
|
+
* domain, preferring a custom domain over the `*.vercel.app` default. Returns a
|
|
99
|
+
* full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty
|
|
100
|
+
* (e.g. a freshly created project whose domain has not yet propagated).
|
|
101
|
+
*
|
|
102
|
+
* @param domains Domain entries from `GET /v9/projects/{id}/domains`
|
|
103
|
+
* @param name Project name, used for the `<name>.vercel.app` fallback
|
|
104
|
+
* @returns The production URL, e.g. `https://app.example.com`
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* pickProductionDomain(
|
|
108
|
+
* [{ name: "x.vercel.app", verified: true, redirect: null, gitBranch: null },
|
|
109
|
+
* { name: "app.example.com", verified: true, redirect: null, gitBranch: null }],
|
|
110
|
+
* "x",
|
|
111
|
+
* ); // => "https://app.example.com"
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
function pickProductionDomain(domains, name) {
|
|
115
|
+
const production = domains.filter((domain) => domain.verified && domain.redirect === null && domain.gitBranch === null);
|
|
116
|
+
const custom = production.find((domain) => !domain.name.endsWith(".vercel.app"));
|
|
117
|
+
const fallback = production.find((domain) => domain.name.endsWith(".vercel.app"));
|
|
118
|
+
return `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fetches a project's production URL from the Vercel domains API.
|
|
122
|
+
* Throws on API failure — a wrong URL would silently misconfigure the app.
|
|
123
|
+
*/
|
|
124
|
+
async function fetchProductionUrl(token, teamId, idOrName, name) {
|
|
125
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
126
|
+
if (!response.ok) throw new Error(`Vercel API error fetching domains for "${idOrName}" (${response.status}): ${await response.text()}`);
|
|
127
|
+
const { domains = [] } = await response.json();
|
|
128
|
+
return pickProductionDomain(domains, name);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
96
131
|
* Builds the project body for create / update calls.
|
|
97
132
|
* Only includes defined optional fields.
|
|
98
133
|
*/
|
|
@@ -216,7 +251,8 @@ var VercelProjectResource = class extends _pulumi_pulumi.dynamic.Resource {
|
|
|
216
251
|
*
|
|
217
252
|
* new VercelVariable("nexus-vars", {
|
|
218
253
|
* projectId: project.id,
|
|
219
|
-
*
|
|
254
|
+
* // The app's own URL comes from the project, not from config or a derived name.
|
|
255
|
+
* variables: { NEXTAUTH_URL: project.url },
|
|
220
256
|
* }, { provider });
|
|
221
257
|
* ```
|
|
222
258
|
*/
|
|
@@ -230,11 +266,21 @@ var VercelProject = class extends _pulumi_pulumi.ComponentResource {
|
|
|
230
266
|
...args
|
|
231
267
|
}, { parent: this });
|
|
232
268
|
this.id = resource.projectId;
|
|
233
|
-
this.
|
|
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)));
|
|
275
|
+
this.registerOutputs({
|
|
276
|
+
id: this.id,
|
|
277
|
+
url: this.url
|
|
278
|
+
});
|
|
234
279
|
}
|
|
235
280
|
};
|
|
236
281
|
|
|
237
282
|
//#endregion
|
|
238
283
|
exports.VERCEL_FRAMEWORKS = VERCEL_FRAMEWORKS;
|
|
239
284
|
exports.VercelProject = VercelProject;
|
|
285
|
+
exports.pickProductionDomain = pickProductionDomain;
|
|
240
286
|
//# sourceMappingURL=project.cjs.map
|
|
@@ -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\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\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 * variables: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
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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"}
|
|
@@ -34,6 +34,33 @@ interface VercelProjectInputs {
|
|
|
34
34
|
/** Custom output directory. */
|
|
35
35
|
outputDirectory?: string;
|
|
36
36
|
}
|
|
37
|
+
/** A single entry from `GET /v9/projects/{id}/domains`. */
|
|
38
|
+
interface VercelDomainEntry {
|
|
39
|
+
name: string;
|
|
40
|
+
verified: boolean;
|
|
41
|
+
redirect: string | null;
|
|
42
|
+
gitBranch: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Picks a project's production domain from its domain list, mirroring how Vercel
|
|
46
|
+
* derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch
|
|
47
|
+
* domain, preferring a custom domain over the `*.vercel.app` default. Returns a
|
|
48
|
+
* full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty
|
|
49
|
+
* (e.g. a freshly created project whose domain has not yet propagated).
|
|
50
|
+
*
|
|
51
|
+
* @param domains Domain entries from `GET /v9/projects/{id}/domains`
|
|
52
|
+
* @param name Project name, used for the `<name>.vercel.app` fallback
|
|
53
|
+
* @returns The production URL, e.g. `https://app.example.com`
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* pickProductionDomain(
|
|
57
|
+
* [{ name: "x.vercel.app", verified: true, redirect: null, gitBranch: null },
|
|
58
|
+
* { name: "app.example.com", verified: true, redirect: null, gitBranch: null }],
|
|
59
|
+
* "x",
|
|
60
|
+
* ); // => "https://app.example.com"
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function pickProductionDomain(domains: VercelDomainEntry[], name: string): string;
|
|
37
64
|
/** Options type for VercelProject — replaces Pulumi's native `provider` field. */
|
|
38
65
|
type VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
39
66
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
@@ -70,15 +97,22 @@ interface VercelProjectArgs {
|
|
|
70
97
|
*
|
|
71
98
|
* new VercelVariable("nexus-vars", {
|
|
72
99
|
* projectId: project.id,
|
|
73
|
-
*
|
|
100
|
+
* // The app's own URL comes from the project, not from config or a derived name.
|
|
101
|
+
* variables: { NEXTAUTH_URL: project.url },
|
|
74
102
|
* }, { provider });
|
|
75
103
|
* ```
|
|
76
104
|
*/
|
|
77
105
|
declare class VercelProject extends pulumi.ComponentResource {
|
|
78
106
|
/** Vercel-assigned project ID. */
|
|
79
107
|
readonly id: pulumi.Output<string>;
|
|
108
|
+
/**
|
|
109
|
+
* The project's production URL (with `https://`), e.g. `https://app.example.com`.
|
|
110
|
+
* Resolves to the custom production domain when one is attached, otherwise the
|
|
111
|
+
* `<name>.vercel.app` default — the source of truth for the app's own URL.
|
|
112
|
+
*/
|
|
113
|
+
readonly url: pulumi.Output<string>;
|
|
80
114
|
constructor(name: string, args: VercelProjectArgs, opts: VercelProjectOptions);
|
|
81
115
|
}
|
|
82
116
|
//#endregion
|
|
83
|
-
export { VERCEL_FRAMEWORKS, VercelFramework, VercelProject, VercelProjectArgs, VercelProjectInputs };
|
|
117
|
+
export { VERCEL_FRAMEWORKS, VercelFramework, VercelProject, VercelProjectArgs, VercelProjectInputs, pickProductionDomain };
|
|
84
118
|
//# sourceMappingURL=project.d.cts.map
|
|
@@ -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"}
|
|
@@ -34,6 +34,33 @@ interface VercelProjectInputs {
|
|
|
34
34
|
/** Custom output directory. */
|
|
35
35
|
outputDirectory?: string;
|
|
36
36
|
}
|
|
37
|
+
/** A single entry from `GET /v9/projects/{id}/domains`. */
|
|
38
|
+
interface VercelDomainEntry {
|
|
39
|
+
name: string;
|
|
40
|
+
verified: boolean;
|
|
41
|
+
redirect: string | null;
|
|
42
|
+
gitBranch: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Picks a project's production domain from its domain list, mirroring how Vercel
|
|
46
|
+
* derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch
|
|
47
|
+
* domain, preferring a custom domain over the `*.vercel.app` default. Returns a
|
|
48
|
+
* full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty
|
|
49
|
+
* (e.g. a freshly created project whose domain has not yet propagated).
|
|
50
|
+
*
|
|
51
|
+
* @param domains Domain entries from `GET /v9/projects/{id}/domains`
|
|
52
|
+
* @param name Project name, used for the `<name>.vercel.app` fallback
|
|
53
|
+
* @returns The production URL, e.g. `https://app.example.com`
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* pickProductionDomain(
|
|
57
|
+
* [{ name: "x.vercel.app", verified: true, redirect: null, gitBranch: null },
|
|
58
|
+
* { name: "app.example.com", verified: true, redirect: null, gitBranch: null }],
|
|
59
|
+
* "x",
|
|
60
|
+
* ); // => "https://app.example.com"
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function pickProductionDomain(domains: VercelDomainEntry[], name: string): string;
|
|
37
64
|
/** Options type for VercelProject — replaces Pulumi's native `provider` field. */
|
|
38
65
|
type VercelProjectOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
|
|
39
66
|
/** Vercel authentication context. */provider: VercelProvider;
|
|
@@ -70,15 +97,22 @@ interface VercelProjectArgs {
|
|
|
70
97
|
*
|
|
71
98
|
* new VercelVariable("nexus-vars", {
|
|
72
99
|
* projectId: project.id,
|
|
73
|
-
*
|
|
100
|
+
* // The app's own URL comes from the project, not from config or a derived name.
|
|
101
|
+
* variables: { NEXTAUTH_URL: project.url },
|
|
74
102
|
* }, { provider });
|
|
75
103
|
* ```
|
|
76
104
|
*/
|
|
77
105
|
declare class VercelProject extends pulumi.ComponentResource {
|
|
78
106
|
/** Vercel-assigned project ID. */
|
|
79
107
|
readonly id: pulumi.Output<string>;
|
|
108
|
+
/**
|
|
109
|
+
* The project's production URL (with `https://`), e.g. `https://app.example.com`.
|
|
110
|
+
* Resolves to the custom production domain when one is attached, otherwise the
|
|
111
|
+
* `<name>.vercel.app` default — the source of truth for the app's own URL.
|
|
112
|
+
*/
|
|
113
|
+
readonly url: pulumi.Output<string>;
|
|
80
114
|
constructor(name: string, args: VercelProjectArgs, opts: VercelProjectOptions);
|
|
81
115
|
}
|
|
82
116
|
//#endregion
|
|
83
|
-
export { VERCEL_FRAMEWORKS, VercelFramework, VercelProject, VercelProjectArgs, VercelProjectInputs };
|
|
117
|
+
export { VERCEL_FRAMEWORKS, VercelFramework, VercelProject, VercelProjectArgs, VercelProjectInputs, pickProductionDomain };
|
|
84
118
|
//# sourceMappingURL=project.d.mts.map
|
|
@@ -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
|
@@ -91,6 +91,41 @@ async function fetchProject(token, teamId, idOrName) {
|
|
|
91
91
|
return await response.json();
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
+
* Picks a project's production domain from its domain list, mirroring how Vercel
|
|
95
|
+
* derives `VERCEL_PROJECT_PRODUCTION_URL`: a verified, non-redirect, non-branch
|
|
96
|
+
* domain, preferring a custom domain over the `*.vercel.app` default. Returns a
|
|
97
|
+
* full `https://` URL. Falls back to `<name>.vercel.app` when the list is empty
|
|
98
|
+
* (e.g. a freshly created project whose domain has not yet propagated).
|
|
99
|
+
*
|
|
100
|
+
* @param domains Domain entries from `GET /v9/projects/{id}/domains`
|
|
101
|
+
* @param name Project name, used for the `<name>.vercel.app` fallback
|
|
102
|
+
* @returns The production URL, e.g. `https://app.example.com`
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* pickProductionDomain(
|
|
106
|
+
* [{ name: "x.vercel.app", verified: true, redirect: null, gitBranch: null },
|
|
107
|
+
* { name: "app.example.com", verified: true, redirect: null, gitBranch: null }],
|
|
108
|
+
* "x",
|
|
109
|
+
* ); // => "https://app.example.com"
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
function pickProductionDomain(domains, name) {
|
|
113
|
+
const production = domains.filter((domain) => domain.verified && domain.redirect === null && domain.gitBranch === null);
|
|
114
|
+
const custom = production.find((domain) => !domain.name.endsWith(".vercel.app"));
|
|
115
|
+
const fallback = production.find((domain) => domain.name.endsWith(".vercel.app"));
|
|
116
|
+
return `https://${custom?.name ?? fallback?.name ?? `${name}.vercel.app`}`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Fetches a project's production URL from the Vercel domains API.
|
|
120
|
+
* Throws on API failure — a wrong URL would silently misconfigure the app.
|
|
121
|
+
*/
|
|
122
|
+
async function fetchProductionUrl(token, teamId, idOrName, name) {
|
|
123
|
+
const response = await fetch(`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}/domains?teamId=${teamId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
124
|
+
if (!response.ok) throw new Error(`Vercel API error fetching domains for "${idOrName}" (${response.status}): ${await response.text()}`);
|
|
125
|
+
const { domains = [] } = await response.json();
|
|
126
|
+
return pickProductionDomain(domains, name);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
94
129
|
* Builds the project body for create / update calls.
|
|
95
130
|
* Only includes defined optional fields.
|
|
96
131
|
*/
|
|
@@ -214,7 +249,8 @@ var VercelProjectResource = class extends pulumi.dynamic.Resource {
|
|
|
214
249
|
*
|
|
215
250
|
* new VercelVariable("nexus-vars", {
|
|
216
251
|
* projectId: project.id,
|
|
217
|
-
*
|
|
252
|
+
* // The app's own URL comes from the project, not from config or a derived name.
|
|
253
|
+
* variables: { NEXTAUTH_URL: project.url },
|
|
218
254
|
* }, { provider });
|
|
219
255
|
* ```
|
|
220
256
|
*/
|
|
@@ -228,10 +264,19 @@ var VercelProject = class extends pulumi.ComponentResource {
|
|
|
228
264
|
...args
|
|
229
265
|
}, { parent: this });
|
|
230
266
|
this.id = resource.projectId;
|
|
231
|
-
this.
|
|
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)));
|
|
273
|
+
this.registerOutputs({
|
|
274
|
+
id: this.id,
|
|
275
|
+
url: this.url
|
|
276
|
+
});
|
|
232
277
|
}
|
|
233
278
|
};
|
|
234
279
|
|
|
235
280
|
//#endregion
|
|
236
|
-
export { VERCEL_FRAMEWORKS, VercelProject };
|
|
281
|
+
export { VERCEL_FRAMEWORKS, VercelProject, pickProductionDomain };
|
|
237
282
|
//# sourceMappingURL=project.mjs.map
|
|
@@ -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\n/** Vercel API response shape for a project. */\ninterface VercelProjectResponse {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Fetches a Vercel project by name or ID.\n * Returns `null` if the project is not found (404).\n */\nasync function fetchProject(\n\ttoken: string,\n\tteamId: string,\n\tidOrName: string,\n): Promise<VercelProjectResponse | null> {\n\tconst response = await fetch(\n\t\t`${VERCEL_API_URL}/v9/projects/${encodeURIComponent(idOrName)}?teamId=${teamId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\tif (response.status === 404) {\n\t\treturn null;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`Vercel API error fetching project \"${idOrName}\" (${response.status}): ${await response.text()}`,\n\t\t);\n\t}\n\n\treturn (await response.json()) as VercelProjectResponse;\n}\n\n/**\n * Builds the project body for create / update calls.\n * Only includes defined optional fields.\n */\nfunction buildProjectBody(\n\tinputs: Omit<VercelProjectInputs, \"token\" | \"teamId\">,\n): Record<string, string> {\n\tconst body: Record<string, string> = { name: inputs.name };\n\n\tif (inputs.framework !== undefined) {\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 * variables: { NEXT_PUBLIC_API_URL: meshUrl },\n * }, { provider });\n * ```\n */\nexport class VercelProject extends pulumi.ComponentResource {\n\t/** Vercel-assigned project ID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: VercelProjectArgs,\n\t\topts: VercelProjectOptions,\n\t) {\n\t\tconst { provider, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Project\", name, {}, pulumiOpts);\n\n\t\tconst resource = new VercelProjectResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tteamId: provider.teamId,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.projectId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infracraft/pulumi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"lint": "biome check --write"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@pulumi/
|
|
50
|
-
"@pulumi/
|
|
49
|
+
"@pulumi/command": "^1",
|
|
50
|
+
"@pulumi/pulumi": "^3"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"@pulumi/command": {
|