@infracraft/pulumi 1.14.0 → 1.15.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.
@@ -3,6 +3,8 @@ const require_chunk = require('./chunk-BVYJZCqc.cjs');
3
3
  let node_child_process = require("node:child_process");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs, 1);
6
+ let node_os = require("node:os");
7
+ node_os = require_chunk.__toESM(node_os, 1);
6
8
  let node_path = require("node:path");
7
9
  node_path = require_chunk.__toESM(node_path, 1);
8
10
  let _pulumi_command = require("@pulumi/command");
@@ -93,6 +95,7 @@ function recoverStaleGuard(monorepoRoot) {
93
95
  for (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {
94
96
  const guardPath = node_path.join(monorepoRoot, dirName);
95
97
  if (!node_fs.existsSync(guardPath)) continue;
98
+ setGuardImmutable(guardPath, false);
96
99
  if (node_fs.existsSync(gitPath)) node_fs.rmSync(gitPath, {
97
100
  recursive: true,
98
101
  force: true
@@ -108,6 +111,12 @@ function recoverStaleGuard(monorepoRoot) {
108
111
  * repository, so `git ls-files` still reports the tracked files (deploy tools
109
112
  * enumerate them) while none of the history is exposed.
110
113
  *
114
+ * On success it also writes an agent-facing `README.md` and an
115
+ * `infracraft.lock` into the stub (so a coding agent that stumbles onto
116
+ * the unusual state leaves it alone instead of "fixing" it), and best-effort
117
+ * marks the moved-aside real history immutable via {@link setGuardImmutable} so
118
+ * it cannot be accidentally restored while the deploy is in progress.
119
+ *
111
120
  * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`
112
121
  * to hide, and refuses to overwrite an existing guard dir (which would destroy
113
122
  * the real history captured by an earlier run).
@@ -132,6 +141,8 @@ function hideGit(monorepoRoot) {
132
141
  node_fs.renameSync(guardPath, gitPath);
133
142
  throw new Error(`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`);
134
143
  }
144
+ writeStubMetadata(gitPath, GUARD_DIR);
145
+ setGuardImmutable(guardPath, true);
135
146
  }
136
147
  /**
137
148
  * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when
@@ -143,6 +154,7 @@ function restoreGit(monorepoRoot) {
143
154
  if (!node_fs.existsSync(guardPath)) return;
144
155
  const gitPath = node_path.join(monorepoRoot, ".git");
145
156
  try {
157
+ setGuardImmutable(guardPath, false);
146
158
  if (node_fs.existsSync(gitPath)) node_fs.rmSync(gitPath, {
147
159
  recursive: true,
148
160
  force: true
@@ -158,13 +170,121 @@ function ensureGitignore(gitignorePath) {
158
170
  const newline = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
159
171
  node_fs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\n`);
160
172
  }
173
+ /**
174
+ * Basename of the human/agent-facing explainer written into the stub `.git`.
175
+ */
176
+ const STUB_README_NAME = "README.md";
177
+ /**
178
+ * Basename of the lock file written into the stub `.git`. Its presence — with a
179
+ * live `pid` — marks an in-progress deploy that owns the guard.
180
+ */
181
+ const LOCK_FILE_NAME = "infracraft.lock";
182
+ /**
183
+ * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.
184
+ *
185
+ * An automated coding agent that trips over the unusual git state (an unborn
186
+ * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)
187
+ * should read this and leave the repository alone instead of "fixing" it
188
+ * mid-deploy.
189
+ *
190
+ * @param guardDir - Basename of the sibling dir holding the real, locked history
191
+ */
192
+ function buildStubReadme(guardDir) {
193
+ return `# Managed STUB \`.git\` — do not modify or restore it
194
+
195
+ This is **not** the real repository. It is a lightweight stub created
196
+ automatically by [\`@infracraft/pulumi\`](https://github.com/andredezzy/infracraft)
197
+ (the \`git-guard\`) while a deploy (\`pulumi up\` / \`vercel deploy\`) is in progress.
198
+
199
+ ## Why it exists
200
+ Deploy tools would otherwise ingest the full git history. The guard moves the real
201
+ \`.git\` aside to \`../${guardDir}\` and leaves this stub, copying only the index so
202
+ \`git ls-files\` still lists tracked files for the deploy tool.
203
+
204
+ ## It restores itself — automatically
205
+ When the deploy finishes (success or failure) the guard discards this stub and
206
+ moves the real \`.git\` back. **You do not need to do anything.**
207
+
208
+ ## DO NOT try to "fix" this
209
+ While the deploy runs, the real history in \`../${guardDir}\` is **locked**
210
+ (filesystem-immutable, best-effort). Do not run \`git init\` / \`reset\` / \`checkout\`
211
+ here, do not \`rm -rf .git\`, and do not \`mv ../${guardDir} .git\` — the move is
212
+ intentionally blocked and will fail.
213
+
214
+ Symptoms you can safely ignore: an unborn \`main\` branch; \`git ls-files\` works but
215
+ \`git log\` / \`git diff\` fail with \`unable to read <sha>\` (the objects live in the
216
+ locked guard dir).
217
+
218
+ ## If a deploy crashed and this looks stuck
219
+ The guard self-heals on the next deploy. Only if you are certain no deploy is
220
+ running, recover manually from the repo root:
221
+
222
+ \`\`\`sh
223
+ chflags -R nouchg "../${guardDir}" 2>/dev/null || chattr -R -i "../${guardDir}" 2>/dev/null || true
224
+ rm -rf .git && mv "../${guardDir}" .git
225
+ \`\`\`
226
+
227
+ See \`./${LOCK_FILE_NAME}\` for the owning process id and start time.
228
+ `;
229
+ }
230
+ /**
231
+ * Writes the stub explainer and the deploy lock into the freshly created stub
232
+ * `.git`. Best-effort: the stub already satisfies deploy tools without these
233
+ * files, so a write failure here must never abort an otherwise-successful hide.
234
+ */
235
+ function writeStubMetadata(gitPath, guardDir) {
236
+ try {
237
+ node_fs.writeFileSync(node_path.join(gitPath, STUB_README_NAME), buildStubReadme(guardDir));
238
+ const lock = {
239
+ tool: "@infracraft/pulumi git-guard",
240
+ pid: process.pid,
241
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
242
+ host: node_os.hostname(),
243
+ guardDir,
244
+ note: "Deploy in progress. The real .git is moved aside and locked. Do not restore it manually; it restores automatically when the deploy finishes."
245
+ };
246
+ node_fs.writeFileSync(node_path.join(gitPath, LOCK_FILE_NAME), `${JSON.stringify(lock, null, 2)}\n`);
247
+ } catch (error) {
248
+ console.error(`[git-guard] Failed to write stub metadata: ${String(error)}`);
249
+ }
250
+ }
251
+ /**
252
+ * Best-effort filesystem lock on the moved-aside real history so it cannot be
253
+ * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable
254
+ * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently
255
+ * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —
256
+ * the lock is a guard rail, never a deploy blocker. The legitimate restore and
257
+ * recover paths call this with `immutable = false` before moving the dir back.
258
+ *
259
+ * @param guardPath - Absolute path to the guard dir holding the real history
260
+ * @param immutable - `true` to lock, `false` to unlock
261
+ */
262
+ function setGuardImmutable(guardPath, immutable) {
263
+ if (!node_fs.existsSync(guardPath)) return;
264
+ try {
265
+ if (process.platform === "darwin") (0, node_child_process.execFileSync)("chflags", [
266
+ "-R",
267
+ immutable ? "uchg" : "nouchg",
268
+ guardPath
269
+ ], { stdio: "ignore" });
270
+ else if (process.platform === "linux") (0, node_child_process.execFileSync)("chattr", [
271
+ "-R",
272
+ immutable ? "+i" : "-i",
273
+ guardPath
274
+ ], { stdio: "ignore" });
275
+ } catch {}
276
+ }
161
277
 
162
278
  //#endregion
163
279
  exports.GUARD_DIR = GUARD_DIR;
164
280
  exports.LEGACY_GUARD_DIRS = LEGACY_GUARD_DIRS;
281
+ exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
282
+ exports.STUB_README_NAME = STUB_README_NAME;
283
+ exports.buildStubReadme = buildStubReadme;
165
284
  exports.ensureGitignore = ensureGitignore;
166
285
  exports.gitGuard = gitGuard;
167
286
  exports.hideGit = hideGit;
168
287
  exports.recoverStaleGuard = recoverStaleGuard;
169
288
  exports.restoreGit = restoreGit;
289
+ exports.setGuardImmutable = setGuardImmutable;
170
290
  //# sourceMappingURL=git-guard.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-guard.cjs","names":["path","runtime","command","fs"],"sources":["../src/git-guard.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport { runtime } from \"@pulumi/pulumi\";\n\n/**\n * Name of the directory the real `.git` is moved to while deploys run.\n *\n * This value changed from `.git-infrakit-pulumi-guard` to\n * `.git-infracraft-pulumi-guard`. A guard dir left under the old name by a crash\n * under a previous release is still recovered automatically (see\n * {@link LEGACY_GUARD_DIRS} and {@link recoverStaleGuard}), so the rename needs no\n * manual migration. Consumers that imported this constant to locate the guard dir\n * themselves should also account for {@link LEGACY_GUARD_DIRS}.\n */\nexport const GUARD_DIR = \".git-infracraft-pulumi-guard\";\n\n/**\n * Guard-dir names written by older releases. They are recovered on startup so a\n * repository left in a hidden state by a crash under a previous version of this\n * package self-heals on the next run.\n */\nexport const LEGACY_GUARD_DIRS = [\".git-infrakit-pulumi-guard\"];\n\ninterface GitGuardResult {\n\thide: command.local.Command;\n}\n\n/** Absolute monorepo roots already hidden in this process — keeps `gitGuard` idempotent. */\nconst guardedRoots = new Set<string>();\n\n/**\n * Hides the real `.git` so deploy tools (e.g. `vercel deploy`) do not ingest the\n * full git history during a `pulumi up`, then restores it once every dependent\n * deploy has finished.\n *\n * Hiding is performed **synchronously, in-process, on every invocation** — not\n * through a state-tracked `command.local.Command`. A command's `create` only\n * runs on first creation or when its `triggers` change, so a stack that had\n * already recorded the hide command would skip it on every subsequent `up`,\n * leaving the real `.git` exposed during deploys. Hiding is a host-side,\n * run-scoped side effect, not desired-state infrastructure, so it belongs in the\n * program lifecycle rather than the resource graph.\n *\n * Guarantees:\n * - `.git` is hidden before any resource is registered (and therefore before any\n * dependent deploy command runs), on every `up`.\n * - `.git` is restored on success and on failure. On success the language host\n * stays alive until every resource operation completes, so the `exit` handler\n * fires after the deploys; on failure it may fire as soon as the event loop\n * drains, but a failed deploy has already invalidated the update and restore\n * only renames a local directory, so restoring early is harmless.\n * - A guard dir left behind by a hard-killed run (`kill -9`, OOM) is recovered on\n * the next invocation, with no loss of real history.\n * - `pulumi preview` (dry run) moves nothing: it executes no deploy commands.\n *\n * @param monorepoRoot - Absolute path to the repository root containing `.git`\n * @returns `{ hide }` — a backward-compatible dependency anchor for `dependsOn`\n */\nexport function gitGuard(monorepoRoot: string): GitGuardResult {\n\tensureGitignore(path.join(monorepoRoot, \".gitignore\"));\n\n\tconst root = path.resolve(monorepoRoot);\n\n\t// Guard each root at most once per process. `pulumi preview` (dry run) runs the\n\t// program but executes no deploy commands, so it skips the move entirely —\n\t// preview never touches `.git`.\n\tif (!guardedRoots.has(root) && !runtime.isDryRun()) {\n\t\t// Self-heal first: a guard dir present at startup is the fingerprint of a\n\t\t// previous run that was killed before it could restore. Doing this only on\n\t\t// `up` (not preview) keeps preview side-effect free; skipping it on a repeat\n\t\t// call avoids treating the guard this process just created as a stale one.\n\t\trecoverStaleGuard(monorepoRoot);\n\n\t\thideGit(monorepoRoot);\n\t\tguardedRoots.add(root);\n\n\t\tconst restore = (): void => restoreGit(monorepoRoot);\n\n\t\tprocess.on(\"exit\", restore);\n\n\t\tprocess.on(\"SIGINT\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\n\t\tprocess.on(\"SIGTERM\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\n\t// Backward-compatible dependency anchor. The real hide already ran\n\t// synchronously above — before any resource was registered — so consumers'\n\t// existing `dependsOn: [hide]` keeps compiling and still orders deploys after\n\t// the guard, without depending on a command whose static trigger skipped\n\t// re-runs.\n\t//\n\t// Upgrade note: the previous release built this resource with\n\t// `triggers: [stableDir(...)]`. Dropping `triggers` makes the first `pulumi up`\n\t// after upgrading REPLACE `git-guard-hide` once. The replace is harmless — there\n\t// is no delete step and the new create is a bare echo.\n\tconst hide = new command.local.Command(\"git-guard-hide\", {\n\t\tcreate: 'echo \"[git-guard] .git hidden in-process by @infracraft/pulumi\"',\n\t});\n\n\treturn { hide };\n}\n\n/**\n * Restores a guard dir left behind by a previously killed run.\n *\n * A guard dir only exists if a prior run moved the real `.git` aside and never\n * restored it. The current `.git` — if present — is the throwaway stub that\n * `git init` created and is safe to discard; the real history lives in the guard\n * dir and is moved back into place. Legacy guard names are recovered too, which\n * migrates a crashed older stack onto the current layout.\n *\n * @returns `true` if a stale guard dir was recovered, otherwise `false`\n */\nexport function recoverStaleGuard(monorepoRoot: string): boolean {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\tfor (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {\n\t\tconst guardPath = path.join(monorepoRoot, dirName);\n\n\t\tif (!fs.existsSync(guardPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Moves the real `.git` to the guard dir and leaves a lightweight stub in its\n * place. The stub is a fresh `git init` whose index is copied from the real\n * repository, so `git ls-files` still reports the tracked files (deploy tools\n * enumerate them) while none of the history is exposed.\n *\n * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`\n * to hide, and refuses to overwrite an existing guard dir (which would destroy\n * the real history captured by an earlier run).\n */\nexport function hideGit(monorepoRoot: string): void {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(gitPath) || fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tfs.renameSync(gitPath, guardPath);\n\n\ttry {\n\t\texecFileSync(\"git\", [\"init\", \"--quiet\"], {\n\t\t\tcwd: monorepoRoot,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\n\t\tconst realIndex = path.join(guardPath, \"index\");\n\n\t\tif (fs.existsSync(realIndex)) {\n\t\t\tfs.copyFileSync(realIndex, path.join(gitPath, \"index\"));\n\t\t}\n\t} catch (error) {\n\t\t// Re-expose the real `.git` so a failed stub creation never leaves the\n\t\t// repository without git metadata.\n\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\tthrow new Error(\n\t\t\t`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`,\n\t\t);\n\t}\n}\n\n/**\n * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when\n * there is nothing hidden. Failures are reported, not thrown, so a restore on\n * process exit can never crash the host — the manual recovery command is logged.\n */\nexport function restoreGit(monorepoRoot: string): void {\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\ttry {\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\t} catch {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to restore .git. Run manually: rm -rf ${gitPath} && mv ${guardPath} ${gitPath}`,\n\t\t);\n\t}\n}\n\nexport function ensureGitignore(gitignorePath: string): void {\n\tconst content = fs.existsSync(gitignorePath)\n\t\t? fs.readFileSync(gitignorePath, \"utf-8\")\n\t\t: \"\";\n\n\tif (content.includes(GUARD_DIR)) {\n\t\treturn;\n\t}\n\n\tconst newline = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n\n\tfs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\\n`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"git-guard.cjs","names":["path","runtime","command","fs","os"],"sources":["../src/git-guard.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport { runtime } from \"@pulumi/pulumi\";\n\n/**\n * Name of the directory the real `.git` is moved to while deploys run.\n *\n * This value changed from `.git-infrakit-pulumi-guard` to\n * `.git-infracraft-pulumi-guard`. A guard dir left under the old name by a crash\n * under a previous release is still recovered automatically (see\n * {@link LEGACY_GUARD_DIRS} and {@link recoverStaleGuard}), so the rename needs no\n * manual migration. Consumers that imported this constant to locate the guard dir\n * themselves should also account for {@link LEGACY_GUARD_DIRS}.\n */\nexport const GUARD_DIR = \".git-infracraft-pulumi-guard\";\n\n/**\n * Guard-dir names written by older releases. They are recovered on startup so a\n * repository left in a hidden state by a crash under a previous version of this\n * package self-heals on the next run.\n */\nexport const LEGACY_GUARD_DIRS = [\".git-infrakit-pulumi-guard\"];\n\ninterface GitGuardResult {\n\thide: command.local.Command;\n}\n\n/** Absolute monorepo roots already hidden in this process — keeps `gitGuard` idempotent. */\nconst guardedRoots = new Set<string>();\n\n/**\n * Hides the real `.git` so deploy tools (e.g. `vercel deploy`) do not ingest the\n * full git history during a `pulumi up`, then restores it once every dependent\n * deploy has finished.\n *\n * Hiding is performed **synchronously, in-process, on every invocation** — not\n * through a state-tracked `command.local.Command`. A command's `create` only\n * runs on first creation or when its `triggers` change, so a stack that had\n * already recorded the hide command would skip it on every subsequent `up`,\n * leaving the real `.git` exposed during deploys. Hiding is a host-side,\n * run-scoped side effect, not desired-state infrastructure, so it belongs in the\n * program lifecycle rather than the resource graph.\n *\n * Guarantees:\n * - `.git` is hidden before any resource is registered (and therefore before any\n * dependent deploy command runs), on every `up`.\n * - `.git` is restored on success and on failure. On success the language host\n * stays alive until every resource operation completes, so the `exit` handler\n * fires after the deploys; on failure it may fire as soon as the event loop\n * drains, but a failed deploy has already invalidated the update and restore\n * only renames a local directory, so restoring early is harmless.\n * - A guard dir left behind by a hard-killed run (`kill -9`, OOM) is recovered on\n * the next invocation, with no loss of real history.\n * - `pulumi preview` (dry run) moves nothing: it executes no deploy commands.\n *\n * @param monorepoRoot - Absolute path to the repository root containing `.git`\n * @returns `{ hide }` — a backward-compatible dependency anchor for `dependsOn`\n */\nexport function gitGuard(monorepoRoot: string): GitGuardResult {\n\tensureGitignore(path.join(monorepoRoot, \".gitignore\"));\n\n\tconst root = path.resolve(monorepoRoot);\n\n\t// Guard each root at most once per process. `pulumi preview` (dry run) runs the\n\t// program but executes no deploy commands, so it skips the move entirely —\n\t// preview never touches `.git`.\n\tif (!guardedRoots.has(root) && !runtime.isDryRun()) {\n\t\t// Self-heal first: a guard dir present at startup is the fingerprint of a\n\t\t// previous run that was killed before it could restore. Doing this only on\n\t\t// `up` (not preview) keeps preview side-effect free; skipping it on a repeat\n\t\t// call avoids treating the guard this process just created as a stale one.\n\t\trecoverStaleGuard(monorepoRoot);\n\n\t\thideGit(monorepoRoot);\n\t\tguardedRoots.add(root);\n\n\t\tconst restore = (): void => restoreGit(monorepoRoot);\n\n\t\tprocess.on(\"exit\", restore);\n\n\t\tprocess.on(\"SIGINT\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\n\t\tprocess.on(\"SIGTERM\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\n\t// Backward-compatible dependency anchor. The real hide already ran\n\t// synchronously above — before any resource was registered — so consumers'\n\t// existing `dependsOn: [hide]` keeps compiling and still orders deploys after\n\t// the guard, without depending on a command whose static trigger skipped\n\t// re-runs.\n\t//\n\t// Upgrade note: the previous release built this resource with\n\t// `triggers: [stableDir(...)]`. Dropping `triggers` makes the first `pulumi up`\n\t// after upgrading REPLACE `git-guard-hide` once. The replace is harmless — there\n\t// is no delete step and the new create is a bare echo.\n\tconst hide = new command.local.Command(\"git-guard-hide\", {\n\t\tcreate: 'echo \"[git-guard] .git hidden in-process by @infracraft/pulumi\"',\n\t});\n\n\treturn { hide };\n}\n\n/**\n * Restores a guard dir left behind by a previously killed run.\n *\n * A guard dir only exists if a prior run moved the real `.git` aside and never\n * restored it. The current `.git` — if present — is the throwaway stub that\n * `git init` created and is safe to discard; the real history lives in the guard\n * dir and is moved back into place. Legacy guard names are recovered too, which\n * migrates a crashed older stack onto the current layout.\n *\n * @returns `true` if a stale guard dir was recovered, otherwise `false`\n */\nexport function recoverStaleGuard(monorepoRoot: string): boolean {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\tfor (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {\n\t\tconst guardPath = path.join(monorepoRoot, dirName);\n\n\t\tif (!fs.existsSync(guardPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// A crashed run may have left the guard dir immutable; lift the lock so the\n\t\t// recovery rename can proceed.\n\t\tsetGuardImmutable(guardPath, false);\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Moves the real `.git` to the guard dir and leaves a lightweight stub in its\n * place. The stub is a fresh `git init` whose index is copied from the real\n * repository, so `git ls-files` still reports the tracked files (deploy tools\n * enumerate them) while none of the history is exposed.\n *\n * On success it also writes an agent-facing `README.md` and an\n * `infracraft.lock` into the stub (so a coding agent that stumbles onto\n * the unusual state leaves it alone instead of \"fixing\" it), and best-effort\n * marks the moved-aside real history immutable via {@link setGuardImmutable} so\n * it cannot be accidentally restored while the deploy is in progress.\n *\n * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`\n * to hide, and refuses to overwrite an existing guard dir (which would destroy\n * the real history captured by an earlier run).\n */\nexport function hideGit(monorepoRoot: string): void {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(gitPath) || fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tfs.renameSync(gitPath, guardPath);\n\n\ttry {\n\t\texecFileSync(\"git\", [\"init\", \"--quiet\"], {\n\t\t\tcwd: monorepoRoot,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\n\t\tconst realIndex = path.join(guardPath, \"index\");\n\n\t\tif (fs.existsSync(realIndex)) {\n\t\t\tfs.copyFileSync(realIndex, path.join(gitPath, \"index\"));\n\t\t}\n\t} catch (error) {\n\t\t// Re-expose the real `.git` so a failed stub creation never leaves the\n\t\t// repository without git metadata.\n\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\tthrow new Error(\n\t\t\t`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`,\n\t\t);\n\t}\n\n\twriteStubMetadata(gitPath, GUARD_DIR);\n\n\tsetGuardImmutable(guardPath, true);\n}\n\n/**\n * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when\n * there is nothing hidden. Failures are reported, not thrown, so a restore on\n * process exit can never crash the host — the manual recovery command is logged.\n */\nexport function restoreGit(monorepoRoot: string): void {\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\ttry {\n\t\t// Lift the immutability lock before moving the real history back, otherwise\n\t\t// the rename fails on a guard dir that hideGit marked immutable.\n\t\tsetGuardImmutable(guardPath, false);\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\t} catch {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to restore .git. Run manually: rm -rf ${gitPath} && mv ${guardPath} ${gitPath}`,\n\t\t);\n\t}\n}\n\nexport function ensureGitignore(gitignorePath: string): void {\n\tconst content = fs.existsSync(gitignorePath)\n\t\t? fs.readFileSync(gitignorePath, \"utf-8\")\n\t\t: \"\";\n\n\tif (content.includes(GUARD_DIR)) {\n\t\treturn;\n\t}\n\n\tconst newline = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n\n\tfs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\\n`);\n}\n\n/**\n * Basename of the human/agent-facing explainer written into the stub `.git`.\n */\nexport const STUB_README_NAME = \"README.md\";\n\n/**\n * Basename of the lock file written into the stub `.git`. Its presence — with a\n * live `pid` — marks an in-progress deploy that owns the guard.\n */\nexport const LOCK_FILE_NAME = \"infracraft.lock\";\n\n/**\n * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.\n *\n * An automated coding agent that trips over the unusual git state (an unborn\n * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)\n * should read this and leave the repository alone instead of \"fixing\" it\n * mid-deploy.\n *\n * @param guardDir - Basename of the sibling dir holding the real, locked history\n */\nexport function buildStubReadme(guardDir: string): string {\n\treturn `# Managed STUB \\`.git\\` — do not modify or restore it\n\nThis is **not** the real repository. It is a lightweight stub created\nautomatically by [\\`@infracraft/pulumi\\`](https://github.com/andredezzy/infracraft)\n(the \\`git-guard\\`) while a deploy (\\`pulumi up\\` / \\`vercel deploy\\`) is in progress.\n\n## Why it exists\nDeploy tools would otherwise ingest the full git history. The guard moves the real\n\\`.git\\` aside to \\`../${guardDir}\\` and leaves this stub, copying only the index so\n\\`git ls-files\\` still lists tracked files for the deploy tool.\n\n## It restores itself — automatically\nWhen the deploy finishes (success or failure) the guard discards this stub and\nmoves the real \\`.git\\` back. **You do not need to do anything.**\n\n## DO NOT try to \"fix\" this\nWhile the deploy runs, the real history in \\`../${guardDir}\\` is **locked**\n(filesystem-immutable, best-effort). Do not run \\`git init\\` / \\`reset\\` / \\`checkout\\`\nhere, do not \\`rm -rf .git\\`, and do not \\`mv ../${guardDir} .git\\` — the move is\nintentionally blocked and will fail.\n\nSymptoms you can safely ignore: an unborn \\`main\\` branch; \\`git ls-files\\` works but\n\\`git log\\` / \\`git diff\\` fail with \\`unable to read <sha>\\` (the objects live in the\nlocked guard dir).\n\n## If a deploy crashed and this looks stuck\nThe guard self-heals on the next deploy. Only if you are certain no deploy is\nrunning, recover manually from the repo root:\n\n\\`\\`\\`sh\nchflags -R nouchg \"../${guardDir}\" 2>/dev/null || chattr -R -i \"../${guardDir}\" 2>/dev/null || true\nrm -rf .git && mv \"../${guardDir}\" .git\n\\`\\`\\`\n\nSee \\`./${LOCK_FILE_NAME}\\` for the owning process id and start time.\n`;\n}\n\n/**\n * Writes the stub explainer and the deploy lock into the freshly created stub\n * `.git`. Best-effort: the stub already satisfies deploy tools without these\n * files, so a write failure here must never abort an otherwise-successful hide.\n */\nfunction writeStubMetadata(gitPath: string, guardDir: string): void {\n\ttry {\n\t\tfs.writeFileSync(\n\t\t\tpath.join(gitPath, STUB_README_NAME),\n\t\t\tbuildStubReadme(guardDir),\n\t\t);\n\n\t\tconst lock = {\n\t\t\ttool: \"@infracraft/pulumi git-guard\",\n\t\t\tpid: process.pid,\n\t\t\tstartedAt: new Date().toISOString(),\n\t\t\thost: os.hostname(),\n\t\t\tguardDir,\n\t\t\tnote: \"Deploy in progress. The real .git is moved aside and locked. Do not restore it manually; it restores automatically when the deploy finishes.\",\n\t\t};\n\n\t\tfs.writeFileSync(\n\t\t\tpath.join(gitPath, LOCK_FILE_NAME),\n\t\t\t`${JSON.stringify(lock, null, 2)}\\n`,\n\t\t);\n\t} catch (error) {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to write stub metadata: ${String(error)}`,\n\t\t);\n\t}\n}\n\n/**\n * Best-effort filesystem lock on the moved-aside real history so it cannot be\n * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable\n * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently\n * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —\n * the lock is a guard rail, never a deploy blocker. The legitimate restore and\n * recover paths call this with `immutable = false` before moving the dir back.\n *\n * @param guardPath - Absolute path to the guard dir holding the real history\n * @param immutable - `true` to lock, `false` to unlock\n */\nexport function setGuardImmutable(guardPath: string, immutable: boolean): void {\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (process.platform === \"darwin\") {\n\t\t\texecFileSync(\n\t\t\t\t\"chflags\",\n\t\t\t\t[\"-R\", immutable ? \"uchg\" : \"nouchg\", guardPath],\n\t\t\t\t{ stdio: \"ignore\" },\n\t\t\t);\n\t\t} else if (process.platform === \"linux\") {\n\t\t\texecFileSync(\"chattr\", [\"-R\", immutable ? \"+i\" : \"-i\", guardPath], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t});\n\t\t}\n\t} catch {\n\t\t// Lacking the privilege to set the flag (common in CI) must never break the\n\t\t// guard — the lock is best-effort hardening, not a hard dependency.\n\t}\n}\n"],"mappings":"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"}
@@ -69,6 +69,12 @@ declare function recoverStaleGuard(monorepoRoot: string): boolean;
69
69
  * repository, so `git ls-files` still reports the tracked files (deploy tools
70
70
  * enumerate them) while none of the history is exposed.
71
71
  *
72
+ * On success it also writes an agent-facing `README.md` and an
73
+ * `infracraft.lock` into the stub (so a coding agent that stumbles onto
74
+ * the unusual state leaves it alone instead of "fixing" it), and best-effort
75
+ * marks the moved-aside real history immutable via {@link setGuardImmutable} so
76
+ * it cannot be accidentally restored while the deploy is in progress.
77
+ *
72
78
  * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`
73
79
  * to hide, and refuses to overwrite an existing guard dir (which would destroy
74
80
  * the real history captured by an earlier run).
@@ -81,6 +87,38 @@ declare function hideGit(monorepoRoot: string): void;
81
87
  */
82
88
  declare function restoreGit(monorepoRoot: string): void;
83
89
  declare function ensureGitignore(gitignorePath: string): void;
90
+ /**
91
+ * Basename of the human/agent-facing explainer written into the stub `.git`.
92
+ */
93
+ declare const STUB_README_NAME = "README.md";
94
+ /**
95
+ * Basename of the lock file written into the stub `.git`. Its presence — with a
96
+ * live `pid` — marks an in-progress deploy that owns the guard.
97
+ */
98
+ declare const LOCK_FILE_NAME = "infracraft.lock";
99
+ /**
100
+ * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.
101
+ *
102
+ * An automated coding agent that trips over the unusual git state (an unborn
103
+ * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)
104
+ * should read this and leave the repository alone instead of "fixing" it
105
+ * mid-deploy.
106
+ *
107
+ * @param guardDir - Basename of the sibling dir holding the real, locked history
108
+ */
109
+ declare function buildStubReadme(guardDir: string): string;
110
+ /**
111
+ * Best-effort filesystem lock on the moved-aside real history so it cannot be
112
+ * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable
113
+ * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently
114
+ * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —
115
+ * the lock is a guard rail, never a deploy blocker. The legitimate restore and
116
+ * recover paths call this with `immutable = false` before moving the dir back.
117
+ *
118
+ * @param guardPath - Absolute path to the guard dir holding the real history
119
+ * @param immutable - `true` to lock, `false` to unlock
120
+ */
121
+ declare function setGuardImmutable(guardPath: string, immutable: boolean): void;
84
122
  //#endregion
85
- export { GUARD_DIR, LEGACY_GUARD_DIRS, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit };
123
+ export { GUARD_DIR, LEGACY_GUARD_DIRS, LOCK_FILE_NAME, STUB_README_NAME, buildStubReadme, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit, setGuardImmutable };
86
124
  //# sourceMappingURL=git-guard.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-guard.d.cts","names":[],"sources":["../src/git-guard.ts"],"mappings":";;;;;;;AAgBA;;;;AAAsB;AAOtB;;cAPa,SAAA;;AAOkD;AAAC;;;cAAnD,iBAAA;AAAA,UAEH,cAAA;EACT,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,OAAO;AAAA;;;AAAA;AAkC5B;;;;AAA8D;AA6D9D;;;;AAAsD;AAgCtD;;;;AAA4C;AAsC5C;;;;AAA+C;AAsB/C;;;;AAAqD;iBAzJrC,QAAA,CAAS,YAAA,WAAuB,cAAc;;;;;;;;;;;;iBA6D9C,iBAAA,CAAkB,YAAoB;;;;;;;;;;;iBAgCtC,OAAA,CAAQ,YAAoB;;;;;;iBAsC5B,UAAA,CAAW,YAAoB;AAAA,iBAsB/B,eAAA,CAAgB,aAAqB"}
1
+ {"version":3,"file":"git-guard.d.cts","names":[],"sources":["../src/git-guard.ts"],"mappings":";;;;;;;AAiBA;;;;AAAsB;AAOtB;;cAPa,SAAA;;AAOkD;AAAC;;;cAAnD,iBAAA;AAAA,UAEH,cAAA;EACT,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,OAAO;AAAA;;;AAAA;AAkC5B;;;;AAA8D;AA6D9D;;;;AAAsD;AA0CtD;;;;AAA4C;AA0C5C;;;;AAA+C;AA0B/C;;;;AAAqD;iBA3KrC,QAAA,CAAS,YAAA,WAAuB,cAAc;;;;AA4LjC;AAM7B;;;;AAA2B;AAY3B;;iBAjJgB,iBAAA,CAAkB,YAAoB;;AAiJN;AAkFhD;;;;AAAuE;;;;;;;;;;iBAzLvD,OAAA,CAAQ,YAAoB;;;;;;iBA0C5B,UAAA,CAAW,YAAoB;AAAA,iBA0B/B,eAAA,CAAgB,aAAqB;;;;cAiBxC,gBAAA;;;;;cAMA,cAAA;;;;;;;;;;;iBAYG,eAAA,CAAgB,QAAgB;;;;;;;;;;;;iBAkFhC,iBAAA,CAAkB,SAAA,UAAmB,SAAkB"}
@@ -69,6 +69,12 @@ declare function recoverStaleGuard(monorepoRoot: string): boolean;
69
69
  * repository, so `git ls-files` still reports the tracked files (deploy tools
70
70
  * enumerate them) while none of the history is exposed.
71
71
  *
72
+ * On success it also writes an agent-facing `README.md` and an
73
+ * `infracraft.lock` into the stub (so a coding agent that stumbles onto
74
+ * the unusual state leaves it alone instead of "fixing" it), and best-effort
75
+ * marks the moved-aside real history immutable via {@link setGuardImmutable} so
76
+ * it cannot be accidentally restored while the deploy is in progress.
77
+ *
72
78
  * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`
73
79
  * to hide, and refuses to overwrite an existing guard dir (which would destroy
74
80
  * the real history captured by an earlier run).
@@ -81,6 +87,38 @@ declare function hideGit(monorepoRoot: string): void;
81
87
  */
82
88
  declare function restoreGit(monorepoRoot: string): void;
83
89
  declare function ensureGitignore(gitignorePath: string): void;
90
+ /**
91
+ * Basename of the human/agent-facing explainer written into the stub `.git`.
92
+ */
93
+ declare const STUB_README_NAME = "README.md";
94
+ /**
95
+ * Basename of the lock file written into the stub `.git`. Its presence — with a
96
+ * live `pid` — marks an in-progress deploy that owns the guard.
97
+ */
98
+ declare const LOCK_FILE_NAME = "infracraft.lock";
99
+ /**
100
+ * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.
101
+ *
102
+ * An automated coding agent that trips over the unusual git state (an unborn
103
+ * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)
104
+ * should read this and leave the repository alone instead of "fixing" it
105
+ * mid-deploy.
106
+ *
107
+ * @param guardDir - Basename of the sibling dir holding the real, locked history
108
+ */
109
+ declare function buildStubReadme(guardDir: string): string;
110
+ /**
111
+ * Best-effort filesystem lock on the moved-aside real history so it cannot be
112
+ * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable
113
+ * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently
114
+ * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —
115
+ * the lock is a guard rail, never a deploy blocker. The legitimate restore and
116
+ * recover paths call this with `immutable = false` before moving the dir back.
117
+ *
118
+ * @param guardPath - Absolute path to the guard dir holding the real history
119
+ * @param immutable - `true` to lock, `false` to unlock
120
+ */
121
+ declare function setGuardImmutable(guardPath: string, immutable: boolean): void;
84
122
  //#endregion
85
- export { GUARD_DIR, LEGACY_GUARD_DIRS, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit };
123
+ export { GUARD_DIR, LEGACY_GUARD_DIRS, LOCK_FILE_NAME, STUB_README_NAME, buildStubReadme, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit, setGuardImmutable };
86
124
  //# sourceMappingURL=git-guard.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-guard.d.mts","names":[],"sources":["../src/git-guard.ts"],"mappings":";;;;;;;AAgBA;;;;AAAsB;AAOtB;;cAPa,SAAA;;AAOkD;AAAC;;;cAAnD,iBAAA;AAAA,UAEH,cAAA;EACT,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,OAAO;AAAA;;;AAAA;AAkC5B;;;;AAA8D;AA6D9D;;;;AAAsD;AAgCtD;;;;AAA4C;AAsC5C;;;;AAA+C;AAsB/C;;;;AAAqD;iBAzJrC,QAAA,CAAS,YAAA,WAAuB,cAAc;;;;;;;;;;;;iBA6D9C,iBAAA,CAAkB,YAAoB;;;;;;;;;;;iBAgCtC,OAAA,CAAQ,YAAoB;;;;;;iBAsC5B,UAAA,CAAW,YAAoB;AAAA,iBAsB/B,eAAA,CAAgB,aAAqB"}
1
+ {"version":3,"file":"git-guard.d.mts","names":[],"sources":["../src/git-guard.ts"],"mappings":";;;;;;;AAiBA;;;;AAAsB;AAOtB;;cAPa,SAAA;;AAOkD;AAAC;;;cAAnD,iBAAA;AAAA,UAEH,cAAA;EACT,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,OAAO;AAAA;;;AAAA;AAkC5B;;;;AAA8D;AA6D9D;;;;AAAsD;AA0CtD;;;;AAA4C;AA0C5C;;;;AAA+C;AA0B/C;;;;AAAqD;iBA3KrC,QAAA,CAAS,YAAA,WAAuB,cAAc;;;;AA4LjC;AAM7B;;;;AAA2B;AAY3B;;iBAjJgB,iBAAA,CAAkB,YAAoB;;AAiJN;AAkFhD;;;;AAAuE;;;;;;;;;;iBAzLvD,OAAA,CAAQ,YAAoB;;;;;;iBA0C5B,UAAA,CAAW,YAAoB;AAAA,iBA0B/B,eAAA,CAAgB,aAAqB;;;;cAiBxC,gBAAA;;;;;cAMA,cAAA;;;;;;;;;;;iBAYG,eAAA,CAAgB,QAAgB;;;;;;;;;;;;iBAkFhC,iBAAA,CAAkB,SAAA,UAAmB,SAAkB"}
@@ -1,6 +1,7 @@
1
1
  import { t as __name } from "./chunk-OPjESj5l.mjs";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import * as fs from "node:fs";
4
+ import * as os from "node:os";
4
5
  import * as path from "node:path";
5
6
  import * as command from "@pulumi/command";
6
7
  import { runtime } from "@pulumi/pulumi";
@@ -89,6 +90,7 @@ function recoverStaleGuard(monorepoRoot) {
89
90
  for (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {
90
91
  const guardPath = path.join(monorepoRoot, dirName);
91
92
  if (!fs.existsSync(guardPath)) continue;
93
+ setGuardImmutable(guardPath, false);
92
94
  if (fs.existsSync(gitPath)) fs.rmSync(gitPath, {
93
95
  recursive: true,
94
96
  force: true
@@ -104,6 +106,12 @@ function recoverStaleGuard(monorepoRoot) {
104
106
  * repository, so `git ls-files` still reports the tracked files (deploy tools
105
107
  * enumerate them) while none of the history is exposed.
106
108
  *
109
+ * On success it also writes an agent-facing `README.md` and an
110
+ * `infracraft.lock` into the stub (so a coding agent that stumbles onto
111
+ * the unusual state leaves it alone instead of "fixing" it), and best-effort
112
+ * marks the moved-aside real history immutable via {@link setGuardImmutable} so
113
+ * it cannot be accidentally restored while the deploy is in progress.
114
+ *
107
115
  * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`
108
116
  * to hide, and refuses to overwrite an existing guard dir (which would destroy
109
117
  * the real history captured by an earlier run).
@@ -128,6 +136,8 @@ function hideGit(monorepoRoot) {
128
136
  fs.renameSync(guardPath, gitPath);
129
137
  throw new Error(`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`);
130
138
  }
139
+ writeStubMetadata(gitPath, GUARD_DIR);
140
+ setGuardImmutable(guardPath, true);
131
141
  }
132
142
  /**
133
143
  * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when
@@ -139,6 +149,7 @@ function restoreGit(monorepoRoot) {
139
149
  if (!fs.existsSync(guardPath)) return;
140
150
  const gitPath = path.join(monorepoRoot, ".git");
141
151
  try {
152
+ setGuardImmutable(guardPath, false);
142
153
  if (fs.existsSync(gitPath)) fs.rmSync(gitPath, {
143
154
  recursive: true,
144
155
  force: true
@@ -154,7 +165,111 @@ function ensureGitignore(gitignorePath) {
154
165
  const newline = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
155
166
  fs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\n`);
156
167
  }
168
+ /**
169
+ * Basename of the human/agent-facing explainer written into the stub `.git`.
170
+ */
171
+ const STUB_README_NAME = "README.md";
172
+ /**
173
+ * Basename of the lock file written into the stub `.git`. Its presence — with a
174
+ * live `pid` — marks an in-progress deploy that owns the guard.
175
+ */
176
+ const LOCK_FILE_NAME = "infracraft.lock";
177
+ /**
178
+ * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.
179
+ *
180
+ * An automated coding agent that trips over the unusual git state (an unborn
181
+ * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)
182
+ * should read this and leave the repository alone instead of "fixing" it
183
+ * mid-deploy.
184
+ *
185
+ * @param guardDir - Basename of the sibling dir holding the real, locked history
186
+ */
187
+ function buildStubReadme(guardDir) {
188
+ return `# Managed STUB \`.git\` — do not modify or restore it
189
+
190
+ This is **not** the real repository. It is a lightweight stub created
191
+ automatically by [\`@infracraft/pulumi\`](https://github.com/andredezzy/infracraft)
192
+ (the \`git-guard\`) while a deploy (\`pulumi up\` / \`vercel deploy\`) is in progress.
193
+
194
+ ## Why it exists
195
+ Deploy tools would otherwise ingest the full git history. The guard moves the real
196
+ \`.git\` aside to \`../${guardDir}\` and leaves this stub, copying only the index so
197
+ \`git ls-files\` still lists tracked files for the deploy tool.
198
+
199
+ ## It restores itself — automatically
200
+ When the deploy finishes (success or failure) the guard discards this stub and
201
+ moves the real \`.git\` back. **You do not need to do anything.**
202
+
203
+ ## DO NOT try to "fix" this
204
+ While the deploy runs, the real history in \`../${guardDir}\` is **locked**
205
+ (filesystem-immutable, best-effort). Do not run \`git init\` / \`reset\` / \`checkout\`
206
+ here, do not \`rm -rf .git\`, and do not \`mv ../${guardDir} .git\` — the move is
207
+ intentionally blocked and will fail.
208
+
209
+ Symptoms you can safely ignore: an unborn \`main\` branch; \`git ls-files\` works but
210
+ \`git log\` / \`git diff\` fail with \`unable to read <sha>\` (the objects live in the
211
+ locked guard dir).
212
+
213
+ ## If a deploy crashed and this looks stuck
214
+ The guard self-heals on the next deploy. Only if you are certain no deploy is
215
+ running, recover manually from the repo root:
216
+
217
+ \`\`\`sh
218
+ chflags -R nouchg "../${guardDir}" 2>/dev/null || chattr -R -i "../${guardDir}" 2>/dev/null || true
219
+ rm -rf .git && mv "../${guardDir}" .git
220
+ \`\`\`
221
+
222
+ See \`./${LOCK_FILE_NAME}\` for the owning process id and start time.
223
+ `;
224
+ }
225
+ /**
226
+ * Writes the stub explainer and the deploy lock into the freshly created stub
227
+ * `.git`. Best-effort: the stub already satisfies deploy tools without these
228
+ * files, so a write failure here must never abort an otherwise-successful hide.
229
+ */
230
+ function writeStubMetadata(gitPath, guardDir) {
231
+ try {
232
+ fs.writeFileSync(path.join(gitPath, STUB_README_NAME), buildStubReadme(guardDir));
233
+ const lock = {
234
+ tool: "@infracraft/pulumi git-guard",
235
+ pid: process.pid,
236
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
237
+ host: os.hostname(),
238
+ guardDir,
239
+ note: "Deploy in progress. The real .git is moved aside and locked. Do not restore it manually; it restores automatically when the deploy finishes."
240
+ };
241
+ fs.writeFileSync(path.join(gitPath, LOCK_FILE_NAME), `${JSON.stringify(lock, null, 2)}\n`);
242
+ } catch (error) {
243
+ console.error(`[git-guard] Failed to write stub metadata: ${String(error)}`);
244
+ }
245
+ }
246
+ /**
247
+ * Best-effort filesystem lock on the moved-aside real history so it cannot be
248
+ * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable
249
+ * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently
250
+ * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —
251
+ * the lock is a guard rail, never a deploy blocker. The legitimate restore and
252
+ * recover paths call this with `immutable = false` before moving the dir back.
253
+ *
254
+ * @param guardPath - Absolute path to the guard dir holding the real history
255
+ * @param immutable - `true` to lock, `false` to unlock
256
+ */
257
+ function setGuardImmutable(guardPath, immutable) {
258
+ if (!fs.existsSync(guardPath)) return;
259
+ try {
260
+ if (process.platform === "darwin") execFileSync("chflags", [
261
+ "-R",
262
+ immutable ? "uchg" : "nouchg",
263
+ guardPath
264
+ ], { stdio: "ignore" });
265
+ else if (process.platform === "linux") execFileSync("chattr", [
266
+ "-R",
267
+ immutable ? "+i" : "-i",
268
+ guardPath
269
+ ], { stdio: "ignore" });
270
+ } catch {}
271
+ }
157
272
 
158
273
  //#endregion
159
- export { GUARD_DIR, LEGACY_GUARD_DIRS, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit };
274
+ export { GUARD_DIR, LEGACY_GUARD_DIRS, LOCK_FILE_NAME, STUB_README_NAME, buildStubReadme, ensureGitignore, gitGuard, hideGit, recoverStaleGuard, restoreGit, setGuardImmutable };
160
275
  //# sourceMappingURL=git-guard.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-guard.mjs","names":[],"sources":["../src/git-guard.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport { runtime } from \"@pulumi/pulumi\";\n\n/**\n * Name of the directory the real `.git` is moved to while deploys run.\n *\n * This value changed from `.git-infrakit-pulumi-guard` to\n * `.git-infracraft-pulumi-guard`. A guard dir left under the old name by a crash\n * under a previous release is still recovered automatically (see\n * {@link LEGACY_GUARD_DIRS} and {@link recoverStaleGuard}), so the rename needs no\n * manual migration. Consumers that imported this constant to locate the guard dir\n * themselves should also account for {@link LEGACY_GUARD_DIRS}.\n */\nexport const GUARD_DIR = \".git-infracraft-pulumi-guard\";\n\n/**\n * Guard-dir names written by older releases. They are recovered on startup so a\n * repository left in a hidden state by a crash under a previous version of this\n * package self-heals on the next run.\n */\nexport const LEGACY_GUARD_DIRS = [\".git-infrakit-pulumi-guard\"];\n\ninterface GitGuardResult {\n\thide: command.local.Command;\n}\n\n/** Absolute monorepo roots already hidden in this process — keeps `gitGuard` idempotent. */\nconst guardedRoots = new Set<string>();\n\n/**\n * Hides the real `.git` so deploy tools (e.g. `vercel deploy`) do not ingest the\n * full git history during a `pulumi up`, then restores it once every dependent\n * deploy has finished.\n *\n * Hiding is performed **synchronously, in-process, on every invocation** — not\n * through a state-tracked `command.local.Command`. A command's `create` only\n * runs on first creation or when its `triggers` change, so a stack that had\n * already recorded the hide command would skip it on every subsequent `up`,\n * leaving the real `.git` exposed during deploys. Hiding is a host-side,\n * run-scoped side effect, not desired-state infrastructure, so it belongs in the\n * program lifecycle rather than the resource graph.\n *\n * Guarantees:\n * - `.git` is hidden before any resource is registered (and therefore before any\n * dependent deploy command runs), on every `up`.\n * - `.git` is restored on success and on failure. On success the language host\n * stays alive until every resource operation completes, so the `exit` handler\n * fires after the deploys; on failure it may fire as soon as the event loop\n * drains, but a failed deploy has already invalidated the update and restore\n * only renames a local directory, so restoring early is harmless.\n * - A guard dir left behind by a hard-killed run (`kill -9`, OOM) is recovered on\n * the next invocation, with no loss of real history.\n * - `pulumi preview` (dry run) moves nothing: it executes no deploy commands.\n *\n * @param monorepoRoot - Absolute path to the repository root containing `.git`\n * @returns `{ hide }` — a backward-compatible dependency anchor for `dependsOn`\n */\nexport function gitGuard(monorepoRoot: string): GitGuardResult {\n\tensureGitignore(path.join(monorepoRoot, \".gitignore\"));\n\n\tconst root = path.resolve(monorepoRoot);\n\n\t// Guard each root at most once per process. `pulumi preview` (dry run) runs the\n\t// program but executes no deploy commands, so it skips the move entirely —\n\t// preview never touches `.git`.\n\tif (!guardedRoots.has(root) && !runtime.isDryRun()) {\n\t\t// Self-heal first: a guard dir present at startup is the fingerprint of a\n\t\t// previous run that was killed before it could restore. Doing this only on\n\t\t// `up` (not preview) keeps preview side-effect free; skipping it on a repeat\n\t\t// call avoids treating the guard this process just created as a stale one.\n\t\trecoverStaleGuard(monorepoRoot);\n\n\t\thideGit(monorepoRoot);\n\t\tguardedRoots.add(root);\n\n\t\tconst restore = (): void => restoreGit(monorepoRoot);\n\n\t\tprocess.on(\"exit\", restore);\n\n\t\tprocess.on(\"SIGINT\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\n\t\tprocess.on(\"SIGTERM\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\n\t// Backward-compatible dependency anchor. The real hide already ran\n\t// synchronously above — before any resource was registered — so consumers'\n\t// existing `dependsOn: [hide]` keeps compiling and still orders deploys after\n\t// the guard, without depending on a command whose static trigger skipped\n\t// re-runs.\n\t//\n\t// Upgrade note: the previous release built this resource with\n\t// `triggers: [stableDir(...)]`. Dropping `triggers` makes the first `pulumi up`\n\t// after upgrading REPLACE `git-guard-hide` once. The replace is harmless — there\n\t// is no delete step and the new create is a bare echo.\n\tconst hide = new command.local.Command(\"git-guard-hide\", {\n\t\tcreate: 'echo \"[git-guard] .git hidden in-process by @infracraft/pulumi\"',\n\t});\n\n\treturn { hide };\n}\n\n/**\n * Restores a guard dir left behind by a previously killed run.\n *\n * A guard dir only exists if a prior run moved the real `.git` aside and never\n * restored it. The current `.git` — if present — is the throwaway stub that\n * `git init` created and is safe to discard; the real history lives in the guard\n * dir and is moved back into place. Legacy guard names are recovered too, which\n * migrates a crashed older stack onto the current layout.\n *\n * @returns `true` if a stale guard dir was recovered, otherwise `false`\n */\nexport function recoverStaleGuard(monorepoRoot: string): boolean {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\tfor (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {\n\t\tconst guardPath = path.join(monorepoRoot, dirName);\n\n\t\tif (!fs.existsSync(guardPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Moves the real `.git` to the guard dir and leaves a lightweight stub in its\n * place. The stub is a fresh `git init` whose index is copied from the real\n * repository, so `git ls-files` still reports the tracked files (deploy tools\n * enumerate them) while none of the history is exposed.\n *\n * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`\n * to hide, and refuses to overwrite an existing guard dir (which would destroy\n * the real history captured by an earlier run).\n */\nexport function hideGit(monorepoRoot: string): void {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(gitPath) || fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tfs.renameSync(gitPath, guardPath);\n\n\ttry {\n\t\texecFileSync(\"git\", [\"init\", \"--quiet\"], {\n\t\t\tcwd: monorepoRoot,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\n\t\tconst realIndex = path.join(guardPath, \"index\");\n\n\t\tif (fs.existsSync(realIndex)) {\n\t\t\tfs.copyFileSync(realIndex, path.join(gitPath, \"index\"));\n\t\t}\n\t} catch (error) {\n\t\t// Re-expose the real `.git` so a failed stub creation never leaves the\n\t\t// repository without git metadata.\n\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\tthrow new Error(\n\t\t\t`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`,\n\t\t);\n\t}\n}\n\n/**\n * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when\n * there is nothing hidden. Failures are reported, not thrown, so a restore on\n * process exit can never crash the host — the manual recovery command is logged.\n */\nexport function restoreGit(monorepoRoot: string): void {\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\ttry {\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\t} catch {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to restore .git. Run manually: rm -rf ${gitPath} && mv ${guardPath} ${gitPath}`,\n\t\t);\n\t}\n}\n\nexport function ensureGitignore(gitignorePath: string): void {\n\tconst content = fs.existsSync(gitignorePath)\n\t\t? fs.readFileSync(gitignorePath, \"utf-8\")\n\t\t: \"\";\n\n\tif (content.includes(GUARD_DIR)) {\n\t\treturn;\n\t}\n\n\tconst newline = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n\n\tfs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,MAAa,YAAY;;;;;;AAOzB,MAAa,oBAAoB,CAAC,4BAA4B;;AAO9D,MAAM,+BAAe,IAAI,IAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BrC,SAAgB,SAAS,cAAsC;CAC9D,gBAAgB,KAAK,KAAK,cAAc,YAAY,CAAC;CAErD,MAAM,OAAO,KAAK,QAAQ,YAAY;CAKtC,IAAI,CAAC,aAAa,IAAI,IAAI,KAAK,CAAC,QAAQ,SAAS,GAAG;EAKnD,kBAAkB,YAAY;EAE9B,QAAQ,YAAY;EACpB,aAAa,IAAI,IAAI;EAErB,MAAM,gBAAsB,WAAW,YAAY;EAEnD,QAAQ,GAAG,QAAQ,OAAO;EAE1B,QAAQ,GAAG,gBAAgB;GAC1B,QAAQ;GACR,QAAQ,KAAK,CAAC;EACf,CAAC;EAED,QAAQ,GAAG,iBAAiB;GAC3B,QAAQ;GACR,QAAQ,KAAK,CAAC;EACf,CAAC;CACF;CAgBA,OAAO,EAAE,UAJQ,QAAQ,MAAM,QAAQ,kBAAkB,EACxD,QAAQ,oEACT,CAEY,EAAE;AACf;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,cAA+B;CAChE,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM;CAE9C,KAAK,MAAM,WAAW,CAAC,WAAW,GAAG,iBAAiB,GAAG;EACxD,MAAM,YAAY,KAAK,KAAK,cAAc,OAAO;EAEjD,IAAI,CAAC,GAAG,WAAW,SAAS,GAC3B;EAGD,IAAI,GAAG,WAAW,OAAO,GACxB,GAAG,OAAO,SAAS;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EAGpD,GAAG,WAAW,WAAW,OAAO;EAEhC,OAAO;CACR;CAEA,OAAO;AACR;;;;;;;;;;;AAYA,SAAgB,QAAQ,cAA4B;CACnD,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM;CAC9C,MAAM,YAAY,KAAK,KAAK,cAAc,SAAS;CAEnD,IAAI,CAAC,GAAG,WAAW,OAAO,KAAK,GAAG,WAAW,SAAS,GACrD;CAGD,GAAG,WAAW,SAAS,SAAS;CAEhC,IAAI;EACH,aAAa,OAAO,CAAC,QAAQ,SAAS,GAAG;GACxC,KAAK;GACL,OAAO;EACR,CAAC;EAED,MAAM,YAAY,KAAK,KAAK,WAAW,OAAO;EAE9C,IAAI,GAAG,WAAW,SAAS,GAC1B,GAAG,aAAa,WAAW,KAAK,KAAK,SAAS,OAAO,CAAC;CAExD,SAAS,OAAO;EAGf,GAAG,OAAO,SAAS;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACnD,GAAG,WAAW,WAAW,OAAO;EAEhC,MAAM,IAAI,MACT,6CAA6C,aAAa,IAAI,OAAO,KAAK,GAC3E;CACD;AACD;;;;;;AAOA,SAAgB,WAAW,cAA4B;CACtD,MAAM,YAAY,KAAK,KAAK,cAAc,SAAS;CAEnD,IAAI,CAAC,GAAG,WAAW,SAAS,GAC3B;CAGD,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM;CAE9C,IAAI;EACH,IAAI,GAAG,WAAW,OAAO,GACxB,GAAG,OAAO,SAAS;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EAGpD,GAAG,WAAW,WAAW,OAAO;CACjC,QAAQ;EACP,QAAQ,MACP,4DAA4D,QAAQ,SAAS,UAAU,GAAG,SAC3F;CACD;AACD;AAEA,SAAgB,gBAAgB,eAA6B;CAC5D,MAAM,UAAU,GAAG,WAAW,aAAa,IACxC,GAAG,aAAa,eAAe,OAAO,IACtC;CAEH,IAAI,QAAQ,uCAAkB,GAC7B;CAGD,MAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,QAAQ,SAAS,IAAI,IAAI,OAAO;CAEvE,GAAG,eAAe,eAAe,GAAG,UAAU,UAAU,GAAG;AAC5D"}
1
+ {"version":3,"file":"git-guard.mjs","names":[],"sources":["../src/git-guard.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport * as command from \"@pulumi/command\";\nimport { runtime } from \"@pulumi/pulumi\";\n\n/**\n * Name of the directory the real `.git` is moved to while deploys run.\n *\n * This value changed from `.git-infrakit-pulumi-guard` to\n * `.git-infracraft-pulumi-guard`. A guard dir left under the old name by a crash\n * under a previous release is still recovered automatically (see\n * {@link LEGACY_GUARD_DIRS} and {@link recoverStaleGuard}), so the rename needs no\n * manual migration. Consumers that imported this constant to locate the guard dir\n * themselves should also account for {@link LEGACY_GUARD_DIRS}.\n */\nexport const GUARD_DIR = \".git-infracraft-pulumi-guard\";\n\n/**\n * Guard-dir names written by older releases. They are recovered on startup so a\n * repository left in a hidden state by a crash under a previous version of this\n * package self-heals on the next run.\n */\nexport const LEGACY_GUARD_DIRS = [\".git-infrakit-pulumi-guard\"];\n\ninterface GitGuardResult {\n\thide: command.local.Command;\n}\n\n/** Absolute monorepo roots already hidden in this process — keeps `gitGuard` idempotent. */\nconst guardedRoots = new Set<string>();\n\n/**\n * Hides the real `.git` so deploy tools (e.g. `vercel deploy`) do not ingest the\n * full git history during a `pulumi up`, then restores it once every dependent\n * deploy has finished.\n *\n * Hiding is performed **synchronously, in-process, on every invocation** — not\n * through a state-tracked `command.local.Command`. A command's `create` only\n * runs on first creation or when its `triggers` change, so a stack that had\n * already recorded the hide command would skip it on every subsequent `up`,\n * leaving the real `.git` exposed during deploys. Hiding is a host-side,\n * run-scoped side effect, not desired-state infrastructure, so it belongs in the\n * program lifecycle rather than the resource graph.\n *\n * Guarantees:\n * - `.git` is hidden before any resource is registered (and therefore before any\n * dependent deploy command runs), on every `up`.\n * - `.git` is restored on success and on failure. On success the language host\n * stays alive until every resource operation completes, so the `exit` handler\n * fires after the deploys; on failure it may fire as soon as the event loop\n * drains, but a failed deploy has already invalidated the update and restore\n * only renames a local directory, so restoring early is harmless.\n * - A guard dir left behind by a hard-killed run (`kill -9`, OOM) is recovered on\n * the next invocation, with no loss of real history.\n * - `pulumi preview` (dry run) moves nothing: it executes no deploy commands.\n *\n * @param monorepoRoot - Absolute path to the repository root containing `.git`\n * @returns `{ hide }` — a backward-compatible dependency anchor for `dependsOn`\n */\nexport function gitGuard(monorepoRoot: string): GitGuardResult {\n\tensureGitignore(path.join(monorepoRoot, \".gitignore\"));\n\n\tconst root = path.resolve(monorepoRoot);\n\n\t// Guard each root at most once per process. `pulumi preview` (dry run) runs the\n\t// program but executes no deploy commands, so it skips the move entirely —\n\t// preview never touches `.git`.\n\tif (!guardedRoots.has(root) && !runtime.isDryRun()) {\n\t\t// Self-heal first: a guard dir present at startup is the fingerprint of a\n\t\t// previous run that was killed before it could restore. Doing this only on\n\t\t// `up` (not preview) keeps preview side-effect free; skipping it on a repeat\n\t\t// call avoids treating the guard this process just created as a stale one.\n\t\trecoverStaleGuard(monorepoRoot);\n\n\t\thideGit(monorepoRoot);\n\t\tguardedRoots.add(root);\n\n\t\tconst restore = (): void => restoreGit(monorepoRoot);\n\n\t\tprocess.on(\"exit\", restore);\n\n\t\tprocess.on(\"SIGINT\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\n\t\tprocess.on(\"SIGTERM\", () => {\n\t\t\trestore();\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\n\t// Backward-compatible dependency anchor. The real hide already ran\n\t// synchronously above — before any resource was registered — so consumers'\n\t// existing `dependsOn: [hide]` keeps compiling and still orders deploys after\n\t// the guard, without depending on a command whose static trigger skipped\n\t// re-runs.\n\t//\n\t// Upgrade note: the previous release built this resource with\n\t// `triggers: [stableDir(...)]`. Dropping `triggers` makes the first `pulumi up`\n\t// after upgrading REPLACE `git-guard-hide` once. The replace is harmless — there\n\t// is no delete step and the new create is a bare echo.\n\tconst hide = new command.local.Command(\"git-guard-hide\", {\n\t\tcreate: 'echo \"[git-guard] .git hidden in-process by @infracraft/pulumi\"',\n\t});\n\n\treturn { hide };\n}\n\n/**\n * Restores a guard dir left behind by a previously killed run.\n *\n * A guard dir only exists if a prior run moved the real `.git` aside and never\n * restored it. The current `.git` — if present — is the throwaway stub that\n * `git init` created and is safe to discard; the real history lives in the guard\n * dir and is moved back into place. Legacy guard names are recovered too, which\n * migrates a crashed older stack onto the current layout.\n *\n * @returns `true` if a stale guard dir was recovered, otherwise `false`\n */\nexport function recoverStaleGuard(monorepoRoot: string): boolean {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\tfor (const dirName of [GUARD_DIR, ...LEGACY_GUARD_DIRS]) {\n\t\tconst guardPath = path.join(monorepoRoot, dirName);\n\n\t\tif (!fs.existsSync(guardPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// A crashed run may have left the guard dir immutable; lift the lock so the\n\t\t// recovery rename can proceed.\n\t\tsetGuardImmutable(guardPath, false);\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Moves the real `.git` to the guard dir and leaves a lightweight stub in its\n * place. The stub is a fresh `git init` whose index is copied from the real\n * repository, so `git ls-files` still reports the tracked files (deploy tools\n * enumerate them) while none of the history is exposed.\n *\n * On success it also writes an agent-facing `README.md` and an\n * `infracraft.lock` into the stub (so a coding agent that stumbles onto\n * the unusual state leaves it alone instead of \"fixing\" it), and best-effort\n * marks the moved-aside real history immutable via {@link setGuardImmutable} so\n * it cannot be accidentally restored while the deploy is in progress.\n *\n * Idempotent and safe to call on every `up`: it no-ops when there is no `.git`\n * to hide, and refuses to overwrite an existing guard dir (which would destroy\n * the real history captured by an earlier run).\n */\nexport function hideGit(monorepoRoot: string): void {\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(gitPath) || fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tfs.renameSync(gitPath, guardPath);\n\n\ttry {\n\t\texecFileSync(\"git\", [\"init\", \"--quiet\"], {\n\t\t\tcwd: monorepoRoot,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\n\t\tconst realIndex = path.join(guardPath, \"index\");\n\n\t\tif (fs.existsSync(realIndex)) {\n\t\t\tfs.copyFileSync(realIndex, path.join(gitPath, \"index\"));\n\t\t}\n\t} catch (error) {\n\t\t// Re-expose the real `.git` so a failed stub creation never leaves the\n\t\t// repository without git metadata.\n\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\tfs.renameSync(guardPath, gitPath);\n\n\t\tthrow new Error(\n\t\t\t`[git-guard] Failed to create stub .git in ${monorepoRoot}: ${String(error)}`,\n\t\t);\n\t}\n\n\twriteStubMetadata(gitPath, GUARD_DIR);\n\n\tsetGuardImmutable(guardPath, true);\n}\n\n/**\n * Moves the real `.git` back from the guard dir, discarding the stub. No-ops when\n * there is nothing hidden. Failures are reported, not thrown, so a restore on\n * process exit can never crash the host — the manual recovery command is logged.\n */\nexport function restoreGit(monorepoRoot: string): void {\n\tconst guardPath = path.join(monorepoRoot, GUARD_DIR);\n\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\tconst gitPath = path.join(monorepoRoot, \".git\");\n\n\ttry {\n\t\t// Lift the immutability lock before moving the real history back, otherwise\n\t\t// the rename fails on a guard dir that hideGit marked immutable.\n\t\tsetGuardImmutable(guardPath, false);\n\n\t\tif (fs.existsSync(gitPath)) {\n\t\t\tfs.rmSync(gitPath, { recursive: true, force: true });\n\t\t}\n\n\t\tfs.renameSync(guardPath, gitPath);\n\t} catch {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to restore .git. Run manually: rm -rf ${gitPath} && mv ${guardPath} ${gitPath}`,\n\t\t);\n\t}\n}\n\nexport function ensureGitignore(gitignorePath: string): void {\n\tconst content = fs.existsSync(gitignorePath)\n\t\t? fs.readFileSync(gitignorePath, \"utf-8\")\n\t\t: \"\";\n\n\tif (content.includes(GUARD_DIR)) {\n\t\treturn;\n\t}\n\n\tconst newline = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n\n\tfs.appendFileSync(gitignorePath, `${newline}${GUARD_DIR}\\n`);\n}\n\n/**\n * Basename of the human/agent-facing explainer written into the stub `.git`.\n */\nexport const STUB_README_NAME = \"README.md\";\n\n/**\n * Basename of the lock file written into the stub `.git`. Its presence — with a\n * live `pid` — marks an in-progress deploy that owns the guard.\n */\nexport const LOCK_FILE_NAME = \"infracraft.lock\";\n\n/**\n * Builds the agent-facing explainer placed at `.git/README.md` inside the stub.\n *\n * An automated coding agent that trips over the unusual git state (an unborn\n * `main`; `git ls-files` works but `git log` fails with `unable to read <sha>`)\n * should read this and leave the repository alone instead of \"fixing\" it\n * mid-deploy.\n *\n * @param guardDir - Basename of the sibling dir holding the real, locked history\n */\nexport function buildStubReadme(guardDir: string): string {\n\treturn `# Managed STUB \\`.git\\` — do not modify or restore it\n\nThis is **not** the real repository. It is a lightweight stub created\nautomatically by [\\`@infracraft/pulumi\\`](https://github.com/andredezzy/infracraft)\n(the \\`git-guard\\`) while a deploy (\\`pulumi up\\` / \\`vercel deploy\\`) is in progress.\n\n## Why it exists\nDeploy tools would otherwise ingest the full git history. The guard moves the real\n\\`.git\\` aside to \\`../${guardDir}\\` and leaves this stub, copying only the index so\n\\`git ls-files\\` still lists tracked files for the deploy tool.\n\n## It restores itself — automatically\nWhen the deploy finishes (success or failure) the guard discards this stub and\nmoves the real \\`.git\\` back. **You do not need to do anything.**\n\n## DO NOT try to \"fix\" this\nWhile the deploy runs, the real history in \\`../${guardDir}\\` is **locked**\n(filesystem-immutable, best-effort). Do not run \\`git init\\` / \\`reset\\` / \\`checkout\\`\nhere, do not \\`rm -rf .git\\`, and do not \\`mv ../${guardDir} .git\\` — the move is\nintentionally blocked and will fail.\n\nSymptoms you can safely ignore: an unborn \\`main\\` branch; \\`git ls-files\\` works but\n\\`git log\\` / \\`git diff\\` fail with \\`unable to read <sha>\\` (the objects live in the\nlocked guard dir).\n\n## If a deploy crashed and this looks stuck\nThe guard self-heals on the next deploy. Only if you are certain no deploy is\nrunning, recover manually from the repo root:\n\n\\`\\`\\`sh\nchflags -R nouchg \"../${guardDir}\" 2>/dev/null || chattr -R -i \"../${guardDir}\" 2>/dev/null || true\nrm -rf .git && mv \"../${guardDir}\" .git\n\\`\\`\\`\n\nSee \\`./${LOCK_FILE_NAME}\\` for the owning process id and start time.\n`;\n}\n\n/**\n * Writes the stub explainer and the deploy lock into the freshly created stub\n * `.git`. Best-effort: the stub already satisfies deploy tools without these\n * files, so a write failure here must never abort an otherwise-successful hide.\n */\nfunction writeStubMetadata(gitPath: string, guardDir: string): void {\n\ttry {\n\t\tfs.writeFileSync(\n\t\t\tpath.join(gitPath, STUB_README_NAME),\n\t\t\tbuildStubReadme(guardDir),\n\t\t);\n\n\t\tconst lock = {\n\t\t\ttool: \"@infracraft/pulumi git-guard\",\n\t\t\tpid: process.pid,\n\t\t\tstartedAt: new Date().toISOString(),\n\t\t\thost: os.hostname(),\n\t\t\tguardDir,\n\t\t\tnote: \"Deploy in progress. The real .git is moved aside and locked. Do not restore it manually; it restores automatically when the deploy finishes.\",\n\t\t};\n\n\t\tfs.writeFileSync(\n\t\t\tpath.join(gitPath, LOCK_FILE_NAME),\n\t\t\t`${JSON.stringify(lock, null, 2)}\\n`,\n\t\t);\n\t} catch (error) {\n\t\tconsole.error(\n\t\t\t`[git-guard] Failed to write stub metadata: ${String(error)}`,\n\t\t);\n\t}\n}\n\n/**\n * Best-effort filesystem lock on the moved-aside real history so it cannot be\n * accidentally restored mid-deploy (e.g. a manual `mv` fails on an immutable\n * dir). Uses `chflags uchg` on macOS and `chattr +i` on Linux, and silently\n * no-ops where the flag is unsupported or not permitted (e.g. unprivileged CI) —\n * the lock is a guard rail, never a deploy blocker. The legitimate restore and\n * recover paths call this with `immutable = false` before moving the dir back.\n *\n * @param guardPath - Absolute path to the guard dir holding the real history\n * @param immutable - `true` to lock, `false` to unlock\n */\nexport function setGuardImmutable(guardPath: string, immutable: boolean): void {\n\tif (!fs.existsSync(guardPath)) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tif (process.platform === \"darwin\") {\n\t\t\texecFileSync(\n\t\t\t\t\"chflags\",\n\t\t\t\t[\"-R\", immutable ? \"uchg\" : \"nouchg\", guardPath],\n\t\t\t\t{ stdio: \"ignore\" },\n\t\t\t);\n\t\t} else if (process.platform === \"linux\") {\n\t\t\texecFileSync(\"chattr\", [\"-R\", immutable ? \"+i\" : \"-i\", guardPath], {\n\t\t\t\tstdio: \"ignore\",\n\t\t\t});\n\t\t}\n\t} catch {\n\t\t// Lacking the privilege to set the flag (common in CI) must never break the\n\t\t// guard — the lock is best-effort hardening, not a hard dependency.\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.14.0",
3
+ "version": "1.15.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {