@infracraft/pulumi 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/vercel/deploy.cjs +73 -4
- package/dist/vercel/deploy.cjs.map +1 -1
- package/dist/vercel/deploy.d.cts +52 -4
- package/dist/vercel/deploy.d.cts.map +1 -1
- package/dist/vercel/deploy.d.mts +52 -4
- package/dist/vercel/deploy.d.mts.map +1 -1
- package/dist/vercel/deploy.mjs +72 -5
- package/dist/vercel/deploy.mjs.map +1 -1
- package/package.json +1 -1
package/dist/vercel/deploy.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
+
const require_git_guard = require('../git-guard.cjs');
|
|
3
4
|
const require_stable_dir = require('../stable-dir.cjs');
|
|
4
5
|
let _pulumi_command = require("@pulumi/command");
|
|
5
6
|
_pulumi_command = require_chunk.__toESM(_pulumi_command, 1);
|
|
@@ -7,21 +8,87 @@ let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
|
7
8
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
8
9
|
|
|
9
10
|
//#region src/vercel/deploy.ts
|
|
11
|
+
/** mkdir-based lock serializing the brief window when `.vercelignore` is written. */
|
|
12
|
+
const LOCK_DIR = "/tmp/.vercel-upload-lock";
|
|
13
|
+
/** Where a committed `.vercelignore` is parked while the engine owns the file. */
|
|
14
|
+
const IGNORE_BACKUP = ".vercelignore.infracraft-bak";
|
|
10
15
|
/**
|
|
11
|
-
*
|
|
16
|
+
* Patterns the engine ALWAYS writes to `.vercelignore`, independent of `excludePaths`.
|
|
17
|
+
*
|
|
18
|
+
* `gitGuard` hides the real `.git` by renaming it to {@link GUARD_DIR} (or a
|
|
19
|
+
* {@link LEGACY_GUARD_DIRS} name) for the duration of a deploy, leaving a stub `.git`.
|
|
20
|
+
* Vercel default-ignores `.git` but NOT the renamed guard dir, and — unlike the Railway
|
|
21
|
+
* CLI — Vercel never reads `.gitignore` (where gitGuard also records the guard dir).
|
|
22
|
+
* Without this exclusion the renamed real `.git`, with its 100MB+ pack files, is
|
|
23
|
+
* uploaded and trips Vercel's 100MB-per-file limit. The transient `.vercelignore*` (the
|
|
24
|
+
* generated file and its {@link IGNORE_BACKUP}) is excluded too, so the engine's own
|
|
25
|
+
* scratch files never ship.
|
|
26
|
+
*/
|
|
27
|
+
const ALWAYS_IGNORE = [
|
|
28
|
+
require_git_guard.GUARD_DIR,
|
|
29
|
+
...require_git_guard.LEGACY_GUARD_DIRS,
|
|
30
|
+
".vercelignore*"
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Builds the newline-joined `.vercelignore` body for the deploy.
|
|
34
|
+
*
|
|
35
|
+
* Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's
|
|
36
|
+
* `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'
|
|
37
|
+
* code is dropped while their `package.json` stays for the build's dependency graph.
|
|
38
|
+
*
|
|
39
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
40
|
+
* @returns The ignore body, one pattern per line
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* buildVercelIgnore(["apps/mesh", "docs"]);
|
|
44
|
+
* // ".git-infracraft-pulumi-guard\n…\napps/mesh/**\n!apps/mesh/package.json\ndocs"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function buildVercelIgnore(excludePaths) {
|
|
48
|
+
const excludeLines = (excludePaths ?? []).map((entry) => entry.startsWith("apps/") ? `${entry}/**\n!${entry}/package.json` : entry);
|
|
49
|
+
return [...ALWAYS_IGNORE, ...excludeLines].join("\n");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.
|
|
53
|
+
*
|
|
54
|
+
* The engine acquires a mkdir lock, parks any committed `.vercelignore` at
|
|
55
|
+
* {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\n`
|
|
56
|
+
* sequences), runs the deploy, and — in a background timer so a slow remote build does
|
|
57
|
+
* not block sibling deploys — restores the parked file and releases the lock. The
|
|
58
|
+
* deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource
|
|
59
|
+
* fails when the deploy fails.
|
|
60
|
+
*
|
|
61
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
62
|
+
* @returns A single-line shell command (no raw newlines)
|
|
63
|
+
*/
|
|
64
|
+
function buildVercelDeployCommand(excludePaths) {
|
|
65
|
+
return `while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; if [ -f .vercelignore ]; then mv .vercelignore ${IGNORE_BACKUP}; fi; printf '${buildVercelIgnore(excludePaths).replace(/\n/g, "\\n")}\\n' > .vercelignore; { sleep 8; ${`rm -f .vercelignore; [ -f ${IGNORE_BACKUP} ] && mv ${IGNORE_BACKUP} .vercelignore`}; rmdir ${LOCK_DIR} 2>/dev/null; } & vercel deploy --prod --yes; EXIT=$?; wait; exit $EXIT`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes`, generating a
|
|
69
|
+
* `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.
|
|
12
70
|
*
|
|
13
71
|
* Triggers on source hash (computed from the app directory) and env content hash
|
|
14
72
|
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
15
73
|
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
16
74
|
* and a redeploy is triggered.
|
|
17
75
|
*
|
|
76
|
+
* The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock
|
|
77
|
+
* serializes the brief window in which `.vercelignore` must be consistent, the file is
|
|
78
|
+
* written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a
|
|
79
|
+
* background timer restores the repository and releases the lock so concurrent deploys
|
|
80
|
+
* stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored
|
|
81
|
+
* afterward, so this never destroys a consumer's file — and because a committed
|
|
82
|
+
* `.vercelignore` is git-tracked, a hard-killed run is recoverable with
|
|
83
|
+
* `git checkout .vercelignore`.
|
|
84
|
+
*
|
|
18
85
|
* @example
|
|
19
86
|
* ```typescript
|
|
20
87
|
* new VercelDeploy("nexus-deploy", {
|
|
21
88
|
* projectId: vercelProject.id,
|
|
22
|
-
* rootDirectory: "apps/nexus",
|
|
23
89
|
* monorepoRoot,
|
|
24
|
-
*
|
|
90
|
+
* triggers: [sourceHash, envHash],
|
|
91
|
+
* excludePaths: ["apps/mesh", "docs"],
|
|
25
92
|
* }, { provider });
|
|
26
93
|
* ```
|
|
27
94
|
*/
|
|
@@ -32,7 +99,7 @@ var VercelDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
32
99
|
const projectId = project ? project.id : args.projectId;
|
|
33
100
|
if (!projectId) throw new Error("VercelDeploy: either `args.projectId` or `opts.project` must be provided");
|
|
34
101
|
const deployCmd = new _pulumi_command.local.Command(`${name}-deploy`, {
|
|
35
|
-
create:
|
|
102
|
+
create: buildVercelDeployCommand(args.excludePaths),
|
|
36
103
|
triggers: args.triggers,
|
|
37
104
|
dir: require_stable_dir.stableDir(args.monorepoRoot),
|
|
38
105
|
environment: {
|
|
@@ -48,4 +115,6 @@ var VercelDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
48
115
|
|
|
49
116
|
//#endregion
|
|
50
117
|
exports.VercelDeploy = VercelDeploy;
|
|
118
|
+
exports.buildVercelDeployCommand = buildVercelDeployCommand;
|
|
119
|
+
exports.buildVercelIgnore = buildVercelIgnore;
|
|
51
120
|
//# sourceMappingURL=deploy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["pulumi","command","stableDir"],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { stableDir } from \"../stable-dir\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/**\n\t * Absolute path to the monorepo root (working directory for `vercel deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes`
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["GUARD_DIR","LEGACY_GUARD_DIRS","pulumi","command","stableDir"],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { GUARD_DIR, LEGACY_GUARD_DIRS } from \"../git-guard\";\nimport { stableDir } from \"../stable-dir\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/**\n\t * Absolute path to the monorepo root (working directory for `vercel deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/**\n\t * Paths to exclude from the upload via a generated `.vercelignore`, mirroring\n\t * `RailwayDeployArgs.excludePaths`. An `apps/<name>` entry excludes that app's\n\t * code but keeps its `package.json`, so the workspace graph still resolves during\n\t * the monorepo build. The gitGuard guard dir is always excluded regardless of this\n\t * list — see {@link buildVercelIgnore}.\n\t */\n\texcludePaths?: string[];\n}\n\n/** mkdir-based lock serializing the brief window when `.vercelignore` is written. */\nconst LOCK_DIR = \"/tmp/.vercel-upload-lock\";\n\n/** Where a committed `.vercelignore` is parked while the engine owns the file. */\nconst IGNORE_BACKUP = \".vercelignore.infracraft-bak\";\n\n/**\n * Patterns the engine ALWAYS writes to `.vercelignore`, independent of `excludePaths`.\n *\n * `gitGuard` hides the real `.git` by renaming it to {@link GUARD_DIR} (or a\n * {@link LEGACY_GUARD_DIRS} name) for the duration of a deploy, leaving a stub `.git`.\n * Vercel default-ignores `.git` but NOT the renamed guard dir, and — unlike the Railway\n * CLI — Vercel never reads `.gitignore` (where gitGuard also records the guard dir).\n * Without this exclusion the renamed real `.git`, with its 100MB+ pack files, is\n * uploaded and trips Vercel's 100MB-per-file limit. The transient `.vercelignore*` (the\n * generated file and its {@link IGNORE_BACKUP}) is excluded too, so the engine's own\n * scratch files never ship.\n */\nconst ALWAYS_IGNORE: readonly string[] = [\n\tGUARD_DIR,\n\t...LEGACY_GUARD_DIRS,\n\t\".vercelignore*\",\n];\n\n/**\n * Builds the newline-joined `.vercelignore` body for the deploy.\n *\n * Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's\n * `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'\n * code is dropped while their `package.json` stays for the build's dependency graph.\n *\n * @param excludePaths Consumer-supplied paths to exclude (optional)\n * @returns The ignore body, one pattern per line\n * @example\n * ```typescript\n * buildVercelIgnore([\"apps/mesh\", \"docs\"]);\n * // \".git-infracraft-pulumi-guard\\n…\\napps/mesh/**\\n!apps/mesh/package.json\\ndocs\"\n * ```\n */\nexport function buildVercelIgnore(excludePaths?: string[]): string {\n\tconst excludeLines = (excludePaths ?? []).map((entry) =>\n\t\tentry.startsWith(\"apps/\") ? `${entry}/**\\n!${entry}/package.json` : entry,\n\t);\n\n\treturn [...ALWAYS_IGNORE, ...excludeLines].join(\"\\n\");\n}\n\n/**\n * Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.\n *\n * The engine acquires a mkdir lock, parks any committed `.vercelignore` at\n * {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\\n`\n * sequences), runs the deploy, and — in a background timer so a slow remote build does\n * not block sibling deploys — restores the parked file and releases the lock. The\n * deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource\n * fails when the deploy fails.\n *\n * @param excludePaths Consumer-supplied paths to exclude (optional)\n * @returns A single-line shell command (no raw newlines)\n */\nexport function buildVercelDeployCommand(excludePaths?: string[]): string {\n\tconst ignoreBody = buildVercelIgnore(excludePaths).replace(/\\n/g, \"\\\\n\");\n\n\tconst restore = `rm -f .vercelignore; [ -f ${IGNORE_BACKUP} ] && mv ${IGNORE_BACKUP} .vercelignore`;\n\n\treturn (\n\t\t`while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; ` +\n\t\t`if [ -f .vercelignore ]; then mv .vercelignore ${IGNORE_BACKUP}; fi; ` +\n\t\t`printf '${ignoreBody}\\\\n' > .vercelignore; ` +\n\t\t`{ sleep 8; ${restore}; rmdir ${LOCK_DIR} 2>/dev/null; } & ` +\n\t\t`vercel deploy --prod --yes; EXIT=$?; wait; exit $EXIT`\n\t);\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes`, generating a\n * `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.\n *\n * Triggers on source hash (computed from the app directory) and env content hash\n * (from `VercelVariable.contentHash`). When an env value changes — whether from\n * code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes\n * and a redeploy is triggered.\n *\n * The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock\n * serializes the brief window in which `.vercelignore` must be consistent, the file is\n * written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a\n * background timer restores the repository and releases the lock so concurrent deploys\n * stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored\n * afterward, so this never destroys a consumer's file — and because a committed\n * `.vercelignore` is git-tracked, a hard-killed run is recoverable with\n * `git checkout .vercelignore`.\n *\n * @example\n * ```typescript\n * new VercelDeploy(\"nexus-deploy\", {\n * projectId: vercelProject.id,\n * monorepoRoot,\n * triggers: [sourceHash, envHash],\n * excludePaths: [\"apps/mesh\", \"docs\"],\n * }, { provider });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\t/**\n\t * The production deployment URL printed by `vercel deploy` (its final stdout line).\n\t * Surfaces the deployed link in stack outputs after `pulumi up` without visiting the dashboard.\n\t */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(name: string, args: VercelDeployArgs, opts: VercelDeployOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelDeploy: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst deployCmd = new command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: buildVercelDeployCommand(args.excludePaths),\n\t\t\t\ttriggers: args.triggers,\n\t\t\t\tdir: stableDir(args.monorepoRoot),\n\t\t\t\tenvironment: {\n\t\t\t\t\tVERCEL_TOKEN: provider.token,\n\t\t\t\t\tVERCEL_ORG_ID: provider.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: projectId,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.deploymentUrl = deployCmd.stdout.apply(\n\t\t\t(out) => out.trim().split(\"\\n\").pop() ?? \"\",\n\t\t);\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AAgDA,MAAM,WAAW;;AAGjB,MAAM,gBAAgB;;;;;;;;;;;;;AActB,MAAM,gBAAmC;CACxCA;CACA,GAAGC;CACH;AACD;;;;;;;;;;;;;;;;AAiBA,SAAgB,kBAAkB,cAAiC;CAClE,MAAM,gBAAgB,gBAAgB,CAAC,GAAG,KAAK,UAC9C,MAAM,WAAW,OAAO,IAAI,GAAG,MAAM,QAAQ,MAAM,iBAAiB,KACrE;CAEA,OAAO,CAAC,GAAG,eAAe,GAAG,YAAY,EAAE,KAAK,IAAI;AACrD;;;;;;;;;;;;;;AAeA,SAAgB,yBAAyB,cAAiC;CAKzE,OACC,iBAAiB,SAAS,iFACwB,cAAc,gBAN9C,kBAAkB,YAAY,EAAE,QAAQ,OAAO,KAO7C,EAAE,mCACR,6BAN8B,cAAc,WAAW,cAAc,gBAM7D,UAAU,SAAS;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,eAAb,cAAkCC,eAAO,kBAAkB;CAO1D,YAAY,MAAc,MAAwB,MAA2B;EAC5E,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,4BAA4B,MAAM,CAAC,GAAG,UAAU;EAEtD,MAAM,YAAY,UACf,QAAQ,KACP,KAAK;EAET,IAAI,CAAC,WACJ,MAAM,IAAI,MACT,0EACD;EAGD,MAAM,YAAY,IAAIC,gBAAQ,MAAM,QACnC,GAAG,KAAK,UACR;GACC,QAAQ,yBAAyB,KAAK,YAAY;GAClD,UAAU,KAAK;GACf,KAAKC,6BAAU,KAAK,YAAY;GAChC,aAAa;IACZ,cAAc,SAAS;IACvB,eAAe,SAAS;IACxB,mBAAmB;GACpB;EACD,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,gBAAgB,UAAU,OAAO,OACpC,QAAQ,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,KAAK,EAC1C;EAEA,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|
package/dist/vercel/deploy.d.cts
CHANGED
|
@@ -28,22 +28,70 @@ interface VercelDeployArgs {
|
|
|
28
28
|
monorepoRoot: string;
|
|
29
29
|
/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */
|
|
30
30
|
triggers: pulumi.Input<pulumi.Input<string>[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Paths to exclude from the upload via a generated `.vercelignore`, mirroring
|
|
33
|
+
* `RailwayDeployArgs.excludePaths`. An `apps/<name>` entry excludes that app's
|
|
34
|
+
* code but keeps its `package.json`, so the workspace graph still resolves during
|
|
35
|
+
* the monorepo build. The gitGuard guard dir is always excluded regardless of this
|
|
36
|
+
* list — see {@link buildVercelIgnore}.
|
|
37
|
+
*/
|
|
38
|
+
excludePaths?: string[];
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
|
-
*
|
|
41
|
+
* Builds the newline-joined `.vercelignore` body for the deploy.
|
|
42
|
+
*
|
|
43
|
+
* Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's
|
|
44
|
+
* `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'
|
|
45
|
+
* code is dropped while their `package.json` stays for the build's dependency graph.
|
|
46
|
+
*
|
|
47
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
48
|
+
* @returns The ignore body, one pattern per line
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* buildVercelIgnore(["apps/mesh", "docs"]);
|
|
52
|
+
* // ".git-infracraft-pulumi-guard\n…\napps/mesh/**\n!apps/mesh/package.json\ndocs"
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function buildVercelIgnore(excludePaths?: string[]): string;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.
|
|
58
|
+
*
|
|
59
|
+
* The engine acquires a mkdir lock, parks any committed `.vercelignore` at
|
|
60
|
+
* {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\n`
|
|
61
|
+
* sequences), runs the deploy, and — in a background timer so a slow remote build does
|
|
62
|
+
* not block sibling deploys — restores the parked file and releases the lock. The
|
|
63
|
+
* deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource
|
|
64
|
+
* fails when the deploy fails.
|
|
65
|
+
*
|
|
66
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
67
|
+
* @returns A single-line shell command (no raw newlines)
|
|
68
|
+
*/
|
|
69
|
+
declare function buildVercelDeployCommand(excludePaths?: string[]): string;
|
|
70
|
+
/**
|
|
71
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes`, generating a
|
|
72
|
+
* `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.
|
|
34
73
|
*
|
|
35
74
|
* Triggers on source hash (computed from the app directory) and env content hash
|
|
36
75
|
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
37
76
|
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
38
77
|
* and a redeploy is triggered.
|
|
39
78
|
*
|
|
79
|
+
* The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock
|
|
80
|
+
* serializes the brief window in which `.vercelignore` must be consistent, the file is
|
|
81
|
+
* written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a
|
|
82
|
+
* background timer restores the repository and releases the lock so concurrent deploys
|
|
83
|
+
* stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored
|
|
84
|
+
* afterward, so this never destroys a consumer's file — and because a committed
|
|
85
|
+
* `.vercelignore` is git-tracked, a hard-killed run is recoverable with
|
|
86
|
+
* `git checkout .vercelignore`.
|
|
87
|
+
*
|
|
40
88
|
* @example
|
|
41
89
|
* ```typescript
|
|
42
90
|
* new VercelDeploy("nexus-deploy", {
|
|
43
91
|
* projectId: vercelProject.id,
|
|
44
|
-
* rootDirectory: "apps/nexus",
|
|
45
92
|
* monorepoRoot,
|
|
46
|
-
*
|
|
93
|
+
* triggers: [sourceHash, envHash],
|
|
94
|
+
* excludePaths: ["apps/mesh", "docs"],
|
|
47
95
|
* }, { provider });
|
|
48
96
|
* ```
|
|
49
97
|
*/
|
|
@@ -56,5 +104,5 @@ declare class VercelDeploy extends pulumi.ComponentResource {
|
|
|
56
104
|
constructor(name: string, args: VercelDeployArgs, opts: VercelDeployOptions);
|
|
57
105
|
}
|
|
58
106
|
//#endregion
|
|
59
|
-
export { VercelDeploy, VercelDeployArgs };
|
|
107
|
+
export { VercelDeploy, VercelDeployArgs, buildVercelDeployCommand, buildVercelIgnore };
|
|
60
108
|
//# sourceMappingURL=deploy.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;KAQK,mBAAA,GAAsB,IAAA,CAAK,MAAA,CAAO,wBAAA;uCAEtC,QAAA,EAAU,cAAA;EAFa;;;;EAQvB,OAAA,GAAU,aAAA;AAAA;;UAIM,gBAAA;EAZU;;;;EAiB1B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;;;AAAuB;AAIxB;EAYC,YAAA;;EAGA,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;;;;;;;EASvB,YAAA;AAAA;;;;;;;AAAY;AA0Cb;;;;AAAyD;AAqBzD;;;iBArBgB,iBAAA,CAAkB,YAAuB;AAqBO;AA0ChE;;;;;;;;;;;;AA1CgE,iBAAhD,wBAAA,CAAyB,YAAuB;;;;;;;;;AAiDY;;;;;;;;;;;;;;;;;;;;cAP/D,YAAA,SAAqB,MAAA,CAAO,iBAAA;;;;;WAKxB,aAAA,EAAe,MAAA,CAAO,MAAA;cAE1B,IAAA,UAAc,IAAA,EAAM,gBAAA,EAAkB,IAAA,EAAM,mBAAA;AAAA"}
|
package/dist/vercel/deploy.d.mts
CHANGED
|
@@ -28,22 +28,70 @@ interface VercelDeployArgs {
|
|
|
28
28
|
monorepoRoot: string;
|
|
29
29
|
/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */
|
|
30
30
|
triggers: pulumi.Input<pulumi.Input<string>[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Paths to exclude from the upload via a generated `.vercelignore`, mirroring
|
|
33
|
+
* `RailwayDeployArgs.excludePaths`. An `apps/<name>` entry excludes that app's
|
|
34
|
+
* code but keeps its `package.json`, so the workspace graph still resolves during
|
|
35
|
+
* the monorepo build. The gitGuard guard dir is always excluded regardless of this
|
|
36
|
+
* list — see {@link buildVercelIgnore}.
|
|
37
|
+
*/
|
|
38
|
+
excludePaths?: string[];
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
|
-
*
|
|
41
|
+
* Builds the newline-joined `.vercelignore` body for the deploy.
|
|
42
|
+
*
|
|
43
|
+
* Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's
|
|
44
|
+
* `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'
|
|
45
|
+
* code is dropped while their `package.json` stays for the build's dependency graph.
|
|
46
|
+
*
|
|
47
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
48
|
+
* @returns The ignore body, one pattern per line
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* buildVercelIgnore(["apps/mesh", "docs"]);
|
|
52
|
+
* // ".git-infracraft-pulumi-guard\n…\napps/mesh/**\n!apps/mesh/package.json\ndocs"
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function buildVercelIgnore(excludePaths?: string[]): string;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.
|
|
58
|
+
*
|
|
59
|
+
* The engine acquires a mkdir lock, parks any committed `.vercelignore` at
|
|
60
|
+
* {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\n`
|
|
61
|
+
* sequences), runs the deploy, and — in a background timer so a slow remote build does
|
|
62
|
+
* not block sibling deploys — restores the parked file and releases the lock. The
|
|
63
|
+
* deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource
|
|
64
|
+
* fails when the deploy fails.
|
|
65
|
+
*
|
|
66
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
67
|
+
* @returns A single-line shell command (no raw newlines)
|
|
68
|
+
*/
|
|
69
|
+
declare function buildVercelDeployCommand(excludePaths?: string[]): string;
|
|
70
|
+
/**
|
|
71
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes`, generating a
|
|
72
|
+
* `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.
|
|
34
73
|
*
|
|
35
74
|
* Triggers on source hash (computed from the app directory) and env content hash
|
|
36
75
|
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
37
76
|
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
38
77
|
* and a redeploy is triggered.
|
|
39
78
|
*
|
|
79
|
+
* The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock
|
|
80
|
+
* serializes the brief window in which `.vercelignore` must be consistent, the file is
|
|
81
|
+
* written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a
|
|
82
|
+
* background timer restores the repository and releases the lock so concurrent deploys
|
|
83
|
+
* stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored
|
|
84
|
+
* afterward, so this never destroys a consumer's file — and because a committed
|
|
85
|
+
* `.vercelignore` is git-tracked, a hard-killed run is recoverable with
|
|
86
|
+
* `git checkout .vercelignore`.
|
|
87
|
+
*
|
|
40
88
|
* @example
|
|
41
89
|
* ```typescript
|
|
42
90
|
* new VercelDeploy("nexus-deploy", {
|
|
43
91
|
* projectId: vercelProject.id,
|
|
44
|
-
* rootDirectory: "apps/nexus",
|
|
45
92
|
* monorepoRoot,
|
|
46
|
-
*
|
|
93
|
+
* triggers: [sourceHash, envHash],
|
|
94
|
+
* excludePaths: ["apps/mesh", "docs"],
|
|
47
95
|
* }, { provider });
|
|
48
96
|
* ```
|
|
49
97
|
*/
|
|
@@ -56,5 +104,5 @@ declare class VercelDeploy extends pulumi.ComponentResource {
|
|
|
56
104
|
constructor(name: string, args: VercelDeployArgs, opts: VercelDeployOptions);
|
|
57
105
|
}
|
|
58
106
|
//#endregion
|
|
59
|
-
export { VercelDeploy, VercelDeployArgs };
|
|
107
|
+
export { VercelDeploy, VercelDeployArgs, buildVercelDeployCommand, buildVercelIgnore };
|
|
60
108
|
//# sourceMappingURL=deploy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/vercel/deploy.ts"],"mappings":";;;;;;;KAQK,mBAAA,GAAsB,IAAA,CAAK,MAAA,CAAO,wBAAA;uCAEtC,QAAA,EAAU,cAAA;EAFa;;;;EAQvB,OAAA,GAAU,aAAA;AAAA;;UAIM,gBAAA;EAZU;;;;EAiB1B,SAAA,GAAY,MAAA,CAAO,KAAA;EATnB;;;AAAuB;AAIxB;EAYC,YAAA;;EAGA,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;;;;;;;EASvB,YAAA;AAAA;;;;;;;AAAY;AA0Cb;;;;AAAyD;AAqBzD;;;iBArBgB,iBAAA,CAAkB,YAAuB;AAqBO;AA0ChE;;;;;;;;;;;;AA1CgE,iBAAhD,wBAAA,CAAyB,YAAuB;;;;;;;;;AAiDY;;;;;;;;;;;;;;;;;;;;cAP/D,YAAA,SAAqB,MAAA,CAAO,iBAAA;;;;;WAKxB,aAAA,EAAe,MAAA,CAAO,MAAA;cAE1B,IAAA,UAAc,IAAA,EAAM,gBAAA,EAAkB,IAAA,EAAM,mBAAA;AAAA"}
|
package/dist/vercel/deploy.mjs
CHANGED
|
@@ -1,24 +1,91 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
+
import { GUARD_DIR, LEGACY_GUARD_DIRS } from "../git-guard.mjs";
|
|
2
3
|
import { stableDir } from "../stable-dir.mjs";
|
|
3
4
|
import * as command from "@pulumi/command";
|
|
4
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
5
6
|
|
|
6
7
|
//#region src/vercel/deploy.ts
|
|
8
|
+
/** mkdir-based lock serializing the brief window when `.vercelignore` is written. */
|
|
9
|
+
const LOCK_DIR = "/tmp/.vercel-upload-lock";
|
|
10
|
+
/** Where a committed `.vercelignore` is parked while the engine owns the file. */
|
|
11
|
+
const IGNORE_BACKUP = ".vercelignore.infracraft-bak";
|
|
7
12
|
/**
|
|
8
|
-
*
|
|
13
|
+
* Patterns the engine ALWAYS writes to `.vercelignore`, independent of `excludePaths`.
|
|
14
|
+
*
|
|
15
|
+
* `gitGuard` hides the real `.git` by renaming it to {@link GUARD_DIR} (or a
|
|
16
|
+
* {@link LEGACY_GUARD_DIRS} name) for the duration of a deploy, leaving a stub `.git`.
|
|
17
|
+
* Vercel default-ignores `.git` but NOT the renamed guard dir, and — unlike the Railway
|
|
18
|
+
* CLI — Vercel never reads `.gitignore` (where gitGuard also records the guard dir).
|
|
19
|
+
* Without this exclusion the renamed real `.git`, with its 100MB+ pack files, is
|
|
20
|
+
* uploaded and trips Vercel's 100MB-per-file limit. The transient `.vercelignore*` (the
|
|
21
|
+
* generated file and its {@link IGNORE_BACKUP}) is excluded too, so the engine's own
|
|
22
|
+
* scratch files never ship.
|
|
23
|
+
*/
|
|
24
|
+
const ALWAYS_IGNORE = [
|
|
25
|
+
GUARD_DIR,
|
|
26
|
+
...LEGACY_GUARD_DIRS,
|
|
27
|
+
".vercelignore*"
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Builds the newline-joined `.vercelignore` body for the deploy.
|
|
31
|
+
*
|
|
32
|
+
* Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's
|
|
33
|
+
* `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'
|
|
34
|
+
* code is dropped while their `package.json` stays for the build's dependency graph.
|
|
35
|
+
*
|
|
36
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
37
|
+
* @returns The ignore body, one pattern per line
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* buildVercelIgnore(["apps/mesh", "docs"]);
|
|
41
|
+
* // ".git-infracraft-pulumi-guard\n…\napps/mesh/**\n!apps/mesh/package.json\ndocs"
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
function buildVercelIgnore(excludePaths) {
|
|
45
|
+
const excludeLines = (excludePaths ?? []).map((entry) => entry.startsWith("apps/") ? `${entry}/**\n!${entry}/package.json` : entry);
|
|
46
|
+
return [...ALWAYS_IGNORE, ...excludeLines].join("\n");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.
|
|
50
|
+
*
|
|
51
|
+
* The engine acquires a mkdir lock, parks any committed `.vercelignore` at
|
|
52
|
+
* {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\n`
|
|
53
|
+
* sequences), runs the deploy, and — in a background timer so a slow remote build does
|
|
54
|
+
* not block sibling deploys — restores the parked file and releases the lock. The
|
|
55
|
+
* deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource
|
|
56
|
+
* fails when the deploy fails.
|
|
57
|
+
*
|
|
58
|
+
* @param excludePaths Consumer-supplied paths to exclude (optional)
|
|
59
|
+
* @returns A single-line shell command (no raw newlines)
|
|
60
|
+
*/
|
|
61
|
+
function buildVercelDeployCommand(excludePaths) {
|
|
62
|
+
return `while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; if [ -f .vercelignore ]; then mv .vercelignore ${IGNORE_BACKUP}; fi; printf '${buildVercelIgnore(excludePaths).replace(/\n/g, "\\n")}\\n' > .vercelignore; { sleep 8; ${`rm -f .vercelignore; [ -f ${IGNORE_BACKUP} ] && mv ${IGNORE_BACKUP} .vercelignore`}; rmdir ${LOCK_DIR} 2>/dev/null; } & vercel deploy --prod --yes; EXIT=$?; wait; exit $EXIT`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deploys a Vercel project via `vercel deploy --prod --yes`, generating a
|
|
66
|
+
* `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.
|
|
9
67
|
*
|
|
10
68
|
* Triggers on source hash (computed from the app directory) and env content hash
|
|
11
69
|
* (from `VercelVariable.contentHash`). When an env value changes — whether from
|
|
12
70
|
* code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes
|
|
13
71
|
* and a redeploy is triggered.
|
|
14
72
|
*
|
|
73
|
+
* The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock
|
|
74
|
+
* serializes the brief window in which `.vercelignore` must be consistent, the file is
|
|
75
|
+
* written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a
|
|
76
|
+
* background timer restores the repository and releases the lock so concurrent deploys
|
|
77
|
+
* stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored
|
|
78
|
+
* afterward, so this never destroys a consumer's file — and because a committed
|
|
79
|
+
* `.vercelignore` is git-tracked, a hard-killed run is recoverable with
|
|
80
|
+
* `git checkout .vercelignore`.
|
|
81
|
+
*
|
|
15
82
|
* @example
|
|
16
83
|
* ```typescript
|
|
17
84
|
* new VercelDeploy("nexus-deploy", {
|
|
18
85
|
* projectId: vercelProject.id,
|
|
19
|
-
* rootDirectory: "apps/nexus",
|
|
20
86
|
* monorepoRoot,
|
|
21
|
-
*
|
|
87
|
+
* triggers: [sourceHash, envHash],
|
|
88
|
+
* excludePaths: ["apps/mesh", "docs"],
|
|
22
89
|
* }, { provider });
|
|
23
90
|
* ```
|
|
24
91
|
*/
|
|
@@ -29,7 +96,7 @@ var VercelDeploy = class extends pulumi.ComponentResource {
|
|
|
29
96
|
const projectId = project ? project.id : args.projectId;
|
|
30
97
|
if (!projectId) throw new Error("VercelDeploy: either `args.projectId` or `opts.project` must be provided");
|
|
31
98
|
const deployCmd = new command.local.Command(`${name}-deploy`, {
|
|
32
|
-
create:
|
|
99
|
+
create: buildVercelDeployCommand(args.excludePaths),
|
|
33
100
|
triggers: args.triggers,
|
|
34
101
|
dir: stableDir(args.monorepoRoot),
|
|
35
102
|
environment: {
|
|
@@ -44,5 +111,5 @@ var VercelDeploy = class extends pulumi.ComponentResource {
|
|
|
44
111
|
};
|
|
45
112
|
|
|
46
113
|
//#endregion
|
|
47
|
-
export { VercelDeploy };
|
|
114
|
+
export { VercelDeploy, buildVercelDeployCommand, buildVercelIgnore };
|
|
48
115
|
//# sourceMappingURL=deploy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { stableDir } from \"../stable-dir\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/**\n\t * Absolute path to the monorepo root (working directory for `vercel deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes`
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/vercel/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\nimport { GUARD_DIR, LEGACY_GUARD_DIRS } from \"../git-guard\";\nimport { stableDir } from \"../stable-dir\";\nimport type { VercelProject } from \"./project\";\nimport type { VercelProvider } from \"./provider\";\n\n/** Options type for VercelDeploy — replaces Pulumi's native `provider` field. */\ntype VercelDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\t/** Vercel authentication context. */\n\tprovider: VercelProvider;\n\n\t/**\n\t * VercelProject resource to source the project ID from.\n\t * When provided, `args.projectId` is optional and ignored if both are given.\n\t */\n\tproject?: VercelProject;\n};\n\n/** Args for VercelDeploy. */\nexport interface VercelDeployArgs {\n\t/**\n\t * Vercel project ID.\n\t * Required when `opts.project` is not provided.\n\t */\n\tprojectId?: pulumi.Input<string>;\n\n\t/**\n\t * Absolute path to the monorepo root (working directory for `vercel deploy`).\n\t * Stored relative to the Pulumi program directory so the command stays stable\n\t * across machines and CI (see {@link stableDir}).\n\t */\n\tmonorepoRoot: string;\n\n\t/** Values that trigger a redeploy when changed (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\n\t/**\n\t * Paths to exclude from the upload via a generated `.vercelignore`, mirroring\n\t * `RailwayDeployArgs.excludePaths`. An `apps/<name>` entry excludes that app's\n\t * code but keeps its `package.json`, so the workspace graph still resolves during\n\t * the monorepo build. The gitGuard guard dir is always excluded regardless of this\n\t * list — see {@link buildVercelIgnore}.\n\t */\n\texcludePaths?: string[];\n}\n\n/** mkdir-based lock serializing the brief window when `.vercelignore` is written. */\nconst LOCK_DIR = \"/tmp/.vercel-upload-lock\";\n\n/** Where a committed `.vercelignore` is parked while the engine owns the file. */\nconst IGNORE_BACKUP = \".vercelignore.infracraft-bak\";\n\n/**\n * Patterns the engine ALWAYS writes to `.vercelignore`, independent of `excludePaths`.\n *\n * `gitGuard` hides the real `.git` by renaming it to {@link GUARD_DIR} (or a\n * {@link LEGACY_GUARD_DIRS} name) for the duration of a deploy, leaving a stub `.git`.\n * Vercel default-ignores `.git` but NOT the renamed guard dir, and — unlike the Railway\n * CLI — Vercel never reads `.gitignore` (where gitGuard also records the guard dir).\n * Without this exclusion the renamed real `.git`, with its 100MB+ pack files, is\n * uploaded and trips Vercel's 100MB-per-file limit. The transient `.vercelignore*` (the\n * generated file and its {@link IGNORE_BACKUP}) is excluded too, so the engine's own\n * scratch files never ship.\n */\nconst ALWAYS_IGNORE: readonly string[] = [\n\tGUARD_DIR,\n\t...LEGACY_GUARD_DIRS,\n\t\".vercelignore*\",\n];\n\n/**\n * Builds the newline-joined `.vercelignore` body for the deploy.\n *\n * Prepends {@link ALWAYS_IGNORE} (the guard dir and scratch files) to the consumer's\n * `excludePaths`, applying the workspace-preserving `apps/<name>` rule so other apps'\n * code is dropped while their `package.json` stays for the build's dependency graph.\n *\n * @param excludePaths Consumer-supplied paths to exclude (optional)\n * @returns The ignore body, one pattern per line\n * @example\n * ```typescript\n * buildVercelIgnore([\"apps/mesh\", \"docs\"]);\n * // \".git-infracraft-pulumi-guard\\n…\\napps/mesh/**\\n!apps/mesh/package.json\\ndocs\"\n * ```\n */\nexport function buildVercelIgnore(excludePaths?: string[]): string {\n\tconst excludeLines = (excludePaths ?? []).map((entry) =>\n\t\tentry.startsWith(\"apps/\") ? `${entry}/**\\n!${entry}/package.json` : entry,\n\t);\n\n\treturn [...ALWAYS_IGNORE, ...excludeLines].join(\"\\n\");\n}\n\n/**\n * Builds the `create` shell command that wraps `vercel deploy` in the ignore engine.\n *\n * The engine acquires a mkdir lock, parks any committed `.vercelignore` at\n * {@link IGNORE_BACKUP}, writes the generated body (`printf` expands the encoded `\\n`\n * sequences), runs the deploy, and — in a background timer so a slow remote build does\n * not block sibling deploys — restores the parked file and releases the lock. The\n * deploy's exit status is captured into `EXIT` before `wait`, so the Pulumi resource\n * fails when the deploy fails.\n *\n * @param excludePaths Consumer-supplied paths to exclude (optional)\n * @returns A single-line shell command (no raw newlines)\n */\nexport function buildVercelDeployCommand(excludePaths?: string[]): string {\n\tconst ignoreBody = buildVercelIgnore(excludePaths).replace(/\\n/g, \"\\\\n\");\n\n\tconst restore = `rm -f .vercelignore; [ -f ${IGNORE_BACKUP} ] && mv ${IGNORE_BACKUP} .vercelignore`;\n\n\treturn (\n\t\t`while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; ` +\n\t\t`if [ -f .vercelignore ]; then mv .vercelignore ${IGNORE_BACKUP}; fi; ` +\n\t\t`printf '${ignoreBody}\\\\n' > .vercelignore; ` +\n\t\t`{ sleep 8; ${restore}; rmdir ${LOCK_DIR} 2>/dev/null; } & ` +\n\t\t`vercel deploy --prod --yes; EXIT=$?; wait; exit $EXIT`\n\t);\n}\n\n/**\n * Deploys a Vercel project via `vercel deploy --prod --yes`, generating a\n * `.vercelignore` around the upload — the same exclude engine as `RailwayDeploy`.\n *\n * Triggers on source hash (computed from the app directory) and env content hash\n * (from `VercelVariable.contentHash`). When an env value changes — whether from\n * code, a new mesh URL, or a drift fix after `pulumi refresh` — the hash changes\n * and a redeploy is triggered.\n *\n * The upload is wrapped in an ignore engine mirroring Railway's: a mkdir lock\n * serializes the brief window in which `.vercelignore` must be consistent, the file is\n * written (gitGuard guard dir + {@link VercelDeployArgs.excludePaths}), then a\n * background timer restores the repository and releases the lock so concurrent deploys\n * stream. Any committed `.vercelignore` is parked at {@link IGNORE_BACKUP} and restored\n * afterward, so this never destroys a consumer's file — and because a committed\n * `.vercelignore` is git-tracked, a hard-killed run is recoverable with\n * `git checkout .vercelignore`.\n *\n * @example\n * ```typescript\n * new VercelDeploy(\"nexus-deploy\", {\n * projectId: vercelProject.id,\n * monorepoRoot,\n * triggers: [sourceHash, envHash],\n * excludePaths: [\"apps/mesh\", \"docs\"],\n * }, { provider });\n * ```\n */\nexport class VercelDeploy extends pulumi.ComponentResource {\n\t/**\n\t * The production deployment URL printed by `vercel deploy` (its final stdout line).\n\t * Surfaces the deployed link in stack outputs after `pulumi up` without visiting the dashboard.\n\t */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(name: string, args: VercelDeployArgs, opts: VercelDeployOptions) {\n\t\tconst { provider, project, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:vercel:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst projectId = project\n\t\t\t? project.id\n\t\t\t: (args.projectId as pulumi.Input<string>);\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t\"VercelDeploy: either `args.projectId` or `opts.project` must be provided\",\n\t\t\t);\n\t\t}\n\n\t\tconst deployCmd = new command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: buildVercelDeployCommand(args.excludePaths),\n\t\t\t\ttriggers: args.triggers,\n\t\t\t\tdir: stableDir(args.monorepoRoot),\n\t\t\t\tenvironment: {\n\t\t\t\t\tVERCEL_TOKEN: provider.token,\n\t\t\t\t\tVERCEL_ORG_ID: provider.teamId,\n\t\t\t\t\tVERCEL_PROJECT_ID: projectId,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.deploymentUrl = deployCmd.stdout.apply(\n\t\t\t(out) => out.trim().split(\"\\n\").pop() ?? \"\",\n\t\t);\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;AAgDA,MAAM,WAAW;;AAGjB,MAAM,gBAAgB;;;;;;;;;;;;;AActB,MAAM,gBAAmC;CACxC;CACA,GAAG;CACH;AACD;;;;;;;;;;;;;;;;AAiBA,SAAgB,kBAAkB,cAAiC;CAClE,MAAM,gBAAgB,gBAAgB,CAAC,GAAG,KAAK,UAC9C,MAAM,WAAW,OAAO,IAAI,GAAG,MAAM,QAAQ,MAAM,iBAAiB,KACrE;CAEA,OAAO,CAAC,GAAG,eAAe,GAAG,YAAY,EAAE,KAAK,IAAI;AACrD;;;;;;;;;;;;;;AAeA,SAAgB,yBAAyB,cAAiC;CAKzE,OACC,iBAAiB,SAAS,iFACwB,cAAc,gBAN9C,kBAAkB,YAAY,EAAE,QAAQ,OAAO,KAO7C,EAAE,mCACR,6BAN8B,cAAc,WAAW,cAAc,gBAM7D,UAAU,SAAS;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,eAAb,cAAkC,OAAO,kBAAkB;CAO1D,YAAY,MAAc,MAAwB,MAA2B;EAC5E,MAAM,EAAE,UAAU,SAAS,GAAG,eAAe;EAE7C,MAAM,4BAA4B,MAAM,CAAC,GAAG,UAAU;EAEtD,MAAM,YAAY,UACf,QAAQ,KACP,KAAK;EAET,IAAI,CAAC,WACJ,MAAM,IAAI,MACT,0EACD;EAGD,MAAM,YAAY,IAAI,QAAQ,MAAM,QACnC,GAAG,KAAK,UACR;GACC,QAAQ,yBAAyB,KAAK,YAAY;GAClD,UAAU,KAAK;GACf,KAAK,UAAU,KAAK,YAAY;GAChC,aAAa;IACZ,cAAc,SAAS;IACvB,eAAe,SAAS;IACxB,mBAAmB;GACpB;EACD,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,gBAAgB,UAAU,OAAO,OACpC,QAAQ,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,KAAK,EAC1C;EAEA,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|