@infracraft/pulumi 1.16.5 → 1.16.6
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/commands/deploy.cjs +4 -2
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.d.cts +3 -1
- package/dist/commands/deploy.d.cts.map +1 -1
- package/dist/commands/deploy.d.mts +3 -1
- package/dist/commands/deploy.d.mts.map +1 -1
- package/dist/commands/deploy.mjs +5 -4
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/fly/deploy.cjs +4 -6
- package/dist/fly/deploy.cjs.map +1 -1
- package/dist/fly/deploy.d.cts +2 -0
- package/dist/fly/deploy.d.cts.map +1 -1
- package/dist/fly/deploy.d.mts +2 -0
- package/dist/fly/deploy.d.mts.map +1 -1
- package/dist/fly/deploy.mjs +5 -7
- package/dist/fly/deploy.mjs.map +1 -1
- package/dist/railway/deploy.cjs +14 -5
- package/dist/railway/deploy.cjs.map +1 -1
- package/dist/railway/deploy.d.cts +2 -0
- package/dist/railway/deploy.d.cts.map +1 -1
- package/dist/railway/deploy.d.mts +2 -0
- package/dist/railway/deploy.d.mts.map +1 -1
- package/dist/railway/deploy.mjs +14 -5
- package/dist/railway/deploy.mjs.map +1 -1
- package/dist/sandbox.cjs +43 -10
- package/dist/sandbox.cjs.map +1 -1
- package/dist/sandbox.d.cts +30 -9
- package/dist/sandbox.d.cts.map +1 -1
- package/dist/sandbox.d.mts +30 -9
- package/dist/sandbox.d.mts.map +1 -1
- package/dist/sandbox.mjs +43 -11
- package/dist/sandbox.mjs.map +1 -1
- package/package.json +1 -1
package/dist/commands/deploy.cjs
CHANGED
|
@@ -24,10 +24,11 @@ function createDeployCommand(args, opts) {
|
|
|
24
24
|
const sandbox = deps.some(require_sandbox.isDeploySandbox);
|
|
25
25
|
const gitGuard = deps.some(require_git_guard.isGitGuard);
|
|
26
26
|
if (gitGuard && !sandbox) throw new Error(`[infracraft] ${args.name}: GitGuard has no effect without a DeploySandbox in dependsOn`);
|
|
27
|
+
let mode = "NONE";
|
|
28
|
+
if (sandbox) mode = gitGuard ? "STUB" : "ORIGINAL";
|
|
27
29
|
const env = _pulumi_pulumi.getStack();
|
|
28
30
|
const create = _pulumi_pulumi.output(args.cli).apply((cli) => require_sandbox.buildSandboxScript({
|
|
29
|
-
|
|
30
|
-
gitGuard,
|
|
31
|
+
mode,
|
|
31
32
|
appName: args.name,
|
|
32
33
|
env,
|
|
33
34
|
excludePaths: args.excludePaths,
|
|
@@ -47,4 +48,5 @@ function createDeployCommand(args, opts) {
|
|
|
47
48
|
|
|
48
49
|
//#endregion
|
|
49
50
|
exports.createDeployCommand = createDeployCommand;
|
|
51
|
+
exports.dependsOnList = dependsOnList;
|
|
50
52
|
//# sourceMappingURL=deploy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["isDeploySandbox","isGitGuard","pulumi","buildSandboxScript","command"],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["// src/commands/deploy.ts\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { isGitGuard } from \"../git-guard\";\nimport { buildSandboxScript, isDeploySandbox } from \"../sandbox\";\n\nexport interface CreateDeployCommandArgs {\n\t/** Resource name; the child command is `<name>` and the sandbox dir uses it. */\n\tname: string;\n\t/** Fully-formed platform deploy command (may be an Output, e.g. Railway). */\n\tcli: pulumi.Input<string>;\n\t/** Redeploy triggers (source/env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Upload-scoping excludes (applied only in stub mode). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Env passed to the command (secrets that survive preview; not the inlined ones). */\n\tenvironment?: Record<string, pulumi.Input<string>>;\n}\n\nexport interface CreateDeployCommandResult {\n\tcommand: command.local.Command;\n\t/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */\n\tdeploymentUrl: pulumi.Output<string>;\n}\n\n/** Reads a `dependsOn` opt into a flat array of resource instances. */\
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["isDeploySandbox","isGitGuard","pulumi","buildSandboxScript","command"],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["// src/commands/deploy.ts\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { isGitGuard } from \"../git-guard\";\nimport { buildSandboxScript, isDeploySandbox, SandboxMode } from \"../sandbox\";\n\nexport interface CreateDeployCommandArgs {\n\t/** Resource name; the child command is `<name>` and the sandbox dir uses it. */\n\tname: string;\n\t/** Fully-formed platform deploy command (may be an Output, e.g. Railway). */\n\tcli: pulumi.Input<string>;\n\t/** Redeploy triggers (source/env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Upload-scoping excludes (applied only in stub mode). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Env passed to the command (secrets that survive preview; not the inlined ones). */\n\tenvironment?: Record<string, pulumi.Input<string>>;\n}\n\nexport interface CreateDeployCommandResult {\n\tcommand: command.local.Command;\n\t/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */\n\tdeploymentUrl: pulumi.Output<string>;\n}\n\n/** Reads a `dependsOn` opt into a flat array of resource instances. */\nexport function dependsOnList(\n\topts: pulumi.ComponentResourceOptions,\n): unknown[] {\n\tconst dep = opts.dependsOn;\n\n\tif (Array.isArray(dep)) {\n\t\treturn dep;\n\t}\n\n\treturn dep ? [dep] : [];\n}\n\n/**\n * Builds a sandboxed deploy command. Inspects `opts.dependsOn` by brand:\n * a DeploySandbox → isolate; a GitGuard → stub `.git`; GitGuard alone → throw.\n * The platform deploy resources call ONLY this; they never touch the sandbox.\n */\nexport function createDeployCommand(\n\targs: CreateDeployCommandArgs,\n\topts: pulumi.ComponentResourceOptions,\n): CreateDeployCommandResult {\n\tconst deps = dependsOnList(opts);\n\tconst sandbox = deps.some(isDeploySandbox);\n\tconst gitGuard = deps.some(isGitGuard);\n\n\tif (gitGuard && !sandbox) {\n\t\tthrow new Error(\n\t\t\t`[infracraft] ${args.name}: GitGuard has no effect without a DeploySandbox in dependsOn`,\n\t\t);\n\t}\n\n\tlet mode = SandboxMode.NONE;\n\n\tif (sandbox) {\n\t\tmode = gitGuard ? SandboxMode.STUB : SandboxMode.ORIGINAL;\n\t}\n\n\tconst env = pulumi.getStack();\n\n\tconst create = pulumi.output(args.cli).apply((cli) =>\n\t\tbuildSandboxScript({\n\t\t\tmode,\n\t\t\tappName: args.name,\n\t\t\tenv,\n\t\t\texcludePaths: args.excludePaths,\n\t\t\tsetup: args.setup,\n\t\t\tcli,\n\t\t}),\n\t);\n\n\tconst cmd = new command.local.Command(\n\t\targs.name,\n\t\t{ create, triggers: args.triggers, environment: args.environment },\n\t\topts,\n\t);\n\n\tconst deploymentUrl = cmd.stdout.apply(\n\t\t(out) => out.trim().split(\"\\n\").pop() ?? \"\",\n\t);\n\n\treturn { command: cmd, deploymentUrl };\n}\n"],"mappings":";;;;;;;;;;;AA6BA,SAAgB,cACf,MACY;CACZ,MAAM,MAAM,KAAK;CAEjB,IAAI,MAAM,QAAQ,GAAG,GACpB,OAAO;CAGR,OAAO,MAAM,CAAC,GAAG,IAAI,CAAC;AACvB;;;;;;AAOA,SAAgB,oBACf,MACA,MAC4B;CAC5B,MAAM,OAAO,cAAc,IAAI;CAC/B,MAAM,UAAU,KAAK,KAAKA,+BAAe;CACzC,MAAM,WAAW,KAAK,KAAKC,4BAAU;CAErC,IAAI,YAAY,CAAC,SAChB,MAAM,IAAI,MACT,gBAAgB,KAAK,KAAK,8DAC3B;CAGD,IAAI;CAEJ,IAAI,SACH,OAAO;CAGR,MAAM,MAAMC,eAAO,SAAS;CAE5B,MAAM,SAASA,eAAO,OAAO,KAAK,GAAG,EAAE,OAAO,QAC7CC,mCAAmB;EAClB;EACA,SAAS,KAAK;EACd;EACA,cAAc,KAAK;EACnB,OAAO,KAAK;EACZ;CACD,CAAC,CACF;CAEA,MAAM,MAAM,IAAIC,gBAAQ,MAAM,QAC7B,KAAK,MACL;EAAE;EAAQ,UAAU,KAAK;EAAU,aAAa,KAAK;CAAY,GACjE,IACD;CAMA,OAAO;EAAE,SAAS;EAAK,eAJD,IAAI,OAAO,OAC/B,QAAQ,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,KAAK,EAGP;CAAE;AACtC"}
|
|
@@ -22,6 +22,8 @@ interface CreateDeployCommandResult {
|
|
|
22
22
|
/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */
|
|
23
23
|
deploymentUrl: pulumi.Output<string>;
|
|
24
24
|
}
|
|
25
|
+
/** Reads a `dependsOn` opt into a flat array of resource instances. */
|
|
26
|
+
declare function dependsOnList(opts: pulumi.ComponentResourceOptions): unknown[];
|
|
25
27
|
/**
|
|
26
28
|
* Builds a sandboxed deploy command. Inspects `opts.dependsOn` by brand:
|
|
27
29
|
* a DeploySandbox → isolate; a GitGuard → stub `.git`; GitGuard alone → throw.
|
|
@@ -29,5 +31,5 @@ interface CreateDeployCommandResult {
|
|
|
29
31
|
*/
|
|
30
32
|
declare function createDeployCommand(args: CreateDeployCommandArgs, opts: pulumi.ComponentResourceOptions): CreateDeployCommandResult;
|
|
31
33
|
//#endregion
|
|
32
|
-
export { CreateDeployCommandArgs, CreateDeployCommandResult, createDeployCommand };
|
|
34
|
+
export { CreateDeployCommandArgs, CreateDeployCommandResult, createDeployCommand, dependsOnList };
|
|
33
35
|
//# sourceMappingURL=deploy.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/commands/deploy.ts"],"mappings":";;;;;UAOiB,uBAAA;;EAEhB,IAAA;EAFgB;EAIhB,GAAA,EAAK,MAAA,CAAO,KAAA;;EAEZ,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;EAEvB,YAAA;EAI6B;EAF7B,KAAA;EAEoB;EAApB,WAAA,GAAc,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAAA,UAGpB,yBAAA;EAChB,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,OAAA;EAZX;EAcZ,aAAA,EAAe,MAAA,CAAO,MAAM;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/commands/deploy.ts"],"mappings":";;;;;UAOiB,uBAAA;;EAEhB,IAAA;EAFgB;EAIhB,GAAA,EAAK,MAAA,CAAO,KAAA;;EAEZ,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;EAEvB,YAAA;EAI6B;EAF7B,KAAA;EAEoB;EAApB,WAAA,GAAc,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAAA,UAGpB,yBAAA;EAChB,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,OAAA;EAZX;EAcZ,aAAA,EAAe,MAAA,CAAO,MAAM;AAAA;;iBAIb,aAAA,CACf,IAAA,EAAM,MAAA,CAAO,wBAAwB;;;;;;iBAgBtB,mBAAA,CACf,IAAA,EAAM,uBAAA,EACN,IAAA,EAAM,MAAA,CAAO,wBAAA,GACX,yBAAA"}
|
|
@@ -22,6 +22,8 @@ interface CreateDeployCommandResult {
|
|
|
22
22
|
/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */
|
|
23
23
|
deploymentUrl: pulumi.Output<string>;
|
|
24
24
|
}
|
|
25
|
+
/** Reads a `dependsOn` opt into a flat array of resource instances. */
|
|
26
|
+
declare function dependsOnList(opts: pulumi.ComponentResourceOptions): unknown[];
|
|
25
27
|
/**
|
|
26
28
|
* Builds a sandboxed deploy command. Inspects `opts.dependsOn` by brand:
|
|
27
29
|
* a DeploySandbox → isolate; a GitGuard → stub `.git`; GitGuard alone → throw.
|
|
@@ -29,5 +31,5 @@ interface CreateDeployCommandResult {
|
|
|
29
31
|
*/
|
|
30
32
|
declare function createDeployCommand(args: CreateDeployCommandArgs, opts: pulumi.ComponentResourceOptions): CreateDeployCommandResult;
|
|
31
33
|
//#endregion
|
|
32
|
-
export { CreateDeployCommandArgs, CreateDeployCommandResult, createDeployCommand };
|
|
34
|
+
export { CreateDeployCommandArgs, CreateDeployCommandResult, createDeployCommand, dependsOnList };
|
|
33
35
|
//# sourceMappingURL=deploy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/commands/deploy.ts"],"mappings":";;;;;UAOiB,uBAAA;;EAEhB,IAAA;EAFgB;EAIhB,GAAA,EAAK,MAAA,CAAO,KAAA;;EAEZ,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;EAEvB,YAAA;EAI6B;EAF7B,KAAA;EAEoB;EAApB,WAAA,GAAc,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAAA,UAGpB,yBAAA;EAChB,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,OAAA;EAZX;EAcZ,aAAA,EAAe,MAAA,CAAO,MAAM;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/commands/deploy.ts"],"mappings":";;;;;UAOiB,uBAAA;;EAEhB,IAAA;EAFgB;EAIhB,GAAA,EAAK,MAAA,CAAO,KAAA;;EAEZ,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAAP;EAEvB,YAAA;EAI6B;EAF7B,KAAA;EAEoB;EAApB,WAAA,GAAc,MAAA,SAAe,MAAA,CAAO,KAAA;AAAA;AAAA,UAGpB,yBAAA;EAChB,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,OAAA;EAZX;EAcZ,aAAA,EAAe,MAAA,CAAO,MAAM;AAAA;;iBAIb,aAAA,CACf,IAAA,EAAM,MAAA,CAAO,wBAAwB;;;;;;iBAgBtB,mBAAA,CACf,IAAA,EAAM,uBAAA,EACN,IAAA,EAAM,MAAA,CAAO,wBAAA,GACX,yBAAA"}
|
package/dist/commands/deploy.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
2
|
import { isGitGuard } from "../git-guard.mjs";
|
|
3
|
-
import { buildSandboxScript, isDeploySandbox } from "../sandbox.mjs";
|
|
3
|
+
import { SandboxMode, buildSandboxScript, isDeploySandbox } from "../sandbox.mjs";
|
|
4
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
5
5
|
import * as command from "@pulumi/command";
|
|
6
6
|
|
|
@@ -21,10 +21,11 @@ function createDeployCommand(args, opts) {
|
|
|
21
21
|
const sandbox = deps.some(isDeploySandbox);
|
|
22
22
|
const gitGuard = deps.some(isGitGuard);
|
|
23
23
|
if (gitGuard && !sandbox) throw new Error(`[infracraft] ${args.name}: GitGuard has no effect without a DeploySandbox in dependsOn`);
|
|
24
|
+
let mode = "NONE";
|
|
25
|
+
if (sandbox) mode = gitGuard ? "STUB" : "ORIGINAL";
|
|
24
26
|
const env = pulumi.getStack();
|
|
25
27
|
const create = pulumi.output(args.cli).apply((cli) => buildSandboxScript({
|
|
26
|
-
|
|
27
|
-
gitGuard,
|
|
28
|
+
mode,
|
|
28
29
|
appName: args.name,
|
|
29
30
|
env,
|
|
30
31
|
excludePaths: args.excludePaths,
|
|
@@ -43,5 +44,5 @@ function createDeployCommand(args, opts) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
//#endregion
|
|
46
|
-
export { createDeployCommand };
|
|
47
|
+
export { createDeployCommand, dependsOnList };
|
|
47
48
|
//# sourceMappingURL=deploy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["// src/commands/deploy.ts\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { isGitGuard } from \"../git-guard\";\nimport { buildSandboxScript, isDeploySandbox } from \"../sandbox\";\n\nexport interface CreateDeployCommandArgs {\n\t/** Resource name; the child command is `<name>` and the sandbox dir uses it. */\n\tname: string;\n\t/** Fully-formed platform deploy command (may be an Output, e.g. Railway). */\n\tcli: pulumi.Input<string>;\n\t/** Redeploy triggers (source/env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Upload-scoping excludes (applied only in stub mode). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Env passed to the command (secrets that survive preview; not the inlined ones). */\n\tenvironment?: Record<string, pulumi.Input<string>>;\n}\n\nexport interface CreateDeployCommandResult {\n\tcommand: command.local.Command;\n\t/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */\n\tdeploymentUrl: pulumi.Output<string>;\n}\n\n/** Reads a `dependsOn` opt into a flat array of resource instances. */\
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["// src/commands/deploy.ts\nimport * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { isGitGuard } from \"../git-guard\";\nimport { buildSandboxScript, isDeploySandbox, SandboxMode } from \"../sandbox\";\n\nexport interface CreateDeployCommandArgs {\n\t/** Resource name; the child command is `<name>` and the sandbox dir uses it. */\n\tname: string;\n\t/** Fully-formed platform deploy command (may be an Output, e.g. Railway). */\n\tcli: pulumi.Input<string>;\n\t/** Redeploy triggers (source/env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Upload-scoping excludes (applied only in stub mode). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Env passed to the command (secrets that survive preview; not the inlined ones). */\n\tenvironment?: Record<string, pulumi.Input<string>>;\n}\n\nexport interface CreateDeployCommandResult {\n\tcommand: command.local.Command;\n\t/** The deploy CLI's final stdout line (the production URL for Vercel/Fly). */\n\tdeploymentUrl: pulumi.Output<string>;\n}\n\n/** Reads a `dependsOn` opt into a flat array of resource instances. */\nexport function dependsOnList(\n\topts: pulumi.ComponentResourceOptions,\n): unknown[] {\n\tconst dep = opts.dependsOn;\n\n\tif (Array.isArray(dep)) {\n\t\treturn dep;\n\t}\n\n\treturn dep ? [dep] : [];\n}\n\n/**\n * Builds a sandboxed deploy command. Inspects `opts.dependsOn` by brand:\n * a DeploySandbox → isolate; a GitGuard → stub `.git`; GitGuard alone → throw.\n * The platform deploy resources call ONLY this; they never touch the sandbox.\n */\nexport function createDeployCommand(\n\targs: CreateDeployCommandArgs,\n\topts: pulumi.ComponentResourceOptions,\n): CreateDeployCommandResult {\n\tconst deps = dependsOnList(opts);\n\tconst sandbox = deps.some(isDeploySandbox);\n\tconst gitGuard = deps.some(isGitGuard);\n\n\tif (gitGuard && !sandbox) {\n\t\tthrow new Error(\n\t\t\t`[infracraft] ${args.name}: GitGuard has no effect without a DeploySandbox in dependsOn`,\n\t\t);\n\t}\n\n\tlet mode = SandboxMode.NONE;\n\n\tif (sandbox) {\n\t\tmode = gitGuard ? SandboxMode.STUB : SandboxMode.ORIGINAL;\n\t}\n\n\tconst env = pulumi.getStack();\n\n\tconst create = pulumi.output(args.cli).apply((cli) =>\n\t\tbuildSandboxScript({\n\t\t\tmode,\n\t\t\tappName: args.name,\n\t\t\tenv,\n\t\t\texcludePaths: args.excludePaths,\n\t\t\tsetup: args.setup,\n\t\t\tcli,\n\t\t}),\n\t);\n\n\tconst cmd = new command.local.Command(\n\t\targs.name,\n\t\t{ create, triggers: args.triggers, environment: args.environment },\n\t\topts,\n\t);\n\n\tconst deploymentUrl = cmd.stdout.apply(\n\t\t(out) => out.trim().split(\"\\n\").pop() ?? \"\",\n\t);\n\n\treturn { command: cmd, deploymentUrl };\n}\n"],"mappings":";;;;;;;;AA6BA,SAAgB,cACf,MACY;CACZ,MAAM,MAAM,KAAK;CAEjB,IAAI,MAAM,QAAQ,GAAG,GACpB,OAAO;CAGR,OAAO,MAAM,CAAC,GAAG,IAAI,CAAC;AACvB;;;;;;AAOA,SAAgB,oBACf,MACA,MAC4B;CAC5B,MAAM,OAAO,cAAc,IAAI;CAC/B,MAAM,UAAU,KAAK,KAAK,eAAe;CACzC,MAAM,WAAW,KAAK,KAAK,UAAU;CAErC,IAAI,YAAY,CAAC,SAChB,MAAM,IAAI,MACT,gBAAgB,KAAK,KAAK,8DAC3B;CAGD,IAAI;CAEJ,IAAI,SACH,OAAO;CAGR,MAAM,MAAM,OAAO,SAAS;CAE5B,MAAM,SAAS,OAAO,OAAO,KAAK,GAAG,EAAE,OAAO,QAC7C,mBAAmB;EAClB;EACA,SAAS,KAAK;EACd;EACA,cAAc,KAAK;EACnB,OAAO,KAAK;EACZ;CACD,CAAC,CACF;CAEA,MAAM,MAAM,IAAI,QAAQ,MAAM,QAC7B,KAAK,MACL;EAAE;EAAQ,UAAU,KAAK;EAAU,aAAa,KAAK;CAAY,GACjE,IACD;CAMA,OAAO;EAAE,SAAS;EAAK,eAJD,IAAI,OAAO,OAC/B,QAAQ,IAAI,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,KAAK,EAGP;CAAE;AACtC"}
|
package/dist/fly/deploy.cjs
CHANGED
|
@@ -22,11 +22,8 @@ var FlyDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
22
22
|
const setup = `mkdir -p .fly && printf '%s' "$FLY_TOML_CONTENT" > ${configPath}`;
|
|
23
23
|
const cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;
|
|
24
24
|
const triggers = _pulumi_pulumi.output(args.triggers).apply((values) => [...values, tomlContent]);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
else if (pulumiOpts.dependsOn) consumerDeps = [pulumiOpts.dependsOn];
|
|
28
|
-
else consumerDeps = [];
|
|
29
|
-
require_commands_deploy.createDeployCommand({
|
|
25
|
+
const consumerDeps = require_commands_deploy.dependsOnList(pulumiOpts);
|
|
26
|
+
const { deploymentUrl } = require_commands_deploy.createDeployCommand({
|
|
30
27
|
name,
|
|
31
28
|
cli,
|
|
32
29
|
triggers,
|
|
@@ -40,7 +37,8 @@ var FlyDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
40
37
|
parent: this,
|
|
41
38
|
dependsOn: [app, ...consumerDeps]
|
|
42
39
|
});
|
|
43
|
-
this.
|
|
40
|
+
this.deploymentUrl = deploymentUrl;
|
|
41
|
+
this.registerOutputs({ deploymentUrl: this.deploymentUrl });
|
|
44
42
|
}
|
|
45
43
|
};
|
|
46
44
|
|
package/dist/fly/deploy.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["pulumi","generateFlyToml"],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["// src/fly/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\tprovider: FlyProvider;\n\tapp: FlyApp;\n};\n\nexport interface FlyDeployArgs {\n\t/** fly.toml configuration. `config.app` must equal the FlyApp name. */\n\tconfig: FlyTomlConfig;\n\t/** Redeploy triggers; the generated toml content is appended automatically. */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** `fly deploy --wait-timeout` seconds (default 300). */\n\twaitTimeout?: number;\n\t/** `fly deploy --release-command-timeout` seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only` from a generated fly.toml.\n * Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml content arrives via FLY_TOML_CONTENT (avoids shell escaping).\n\t\tconst setup = `mkdir -p .fly && printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`;\n\t\tconst cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["pulumi","generateFlyToml","dependsOnList","createDeployCommand"],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["// src/fly/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand, dependsOnList } from \"../commands/deploy\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\tprovider: FlyProvider;\n\tapp: FlyApp;\n};\n\nexport interface FlyDeployArgs {\n\t/** fly.toml configuration. `config.app` must equal the FlyApp name. */\n\tconfig: FlyTomlConfig;\n\t/** Redeploy triggers; the generated toml content is appended automatically. */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** `fly deploy --wait-timeout` seconds (default 300). */\n\twaitTimeout?: number;\n\t/** `fly deploy --release-command-timeout` seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only` from a generated fly.toml.\n * Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\t/** The deploy CLI's final stdout line (the Fly app URL when emitted). */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml content arrives via FLY_TOML_CONTENT (avoids shell escaping).\n\t\tconst setup = `mkdir -p .fly && printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`;\n\t\tconst cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\t// Keep the `app` ordering anchor first; append the consumer's deps\n\t\t// (DeploySandbox / GitGuard) using the seam's shared normaliser.\n\t\tconst consumerDeps = dependsOnList(pulumiOpts) as pulumi.Resource[];\n\n\t\tconst { deploymentUrl } = createDeployCommand(\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\tcli,\n\t\t\t\ttriggers,\n\t\t\t\tsetup,\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ ...pulumiOpts, parent: this, dependsOn: [app, ...consumerDeps] },\n\t\t);\n\n\t\tthis.deploymentUrl = deploymentUrl;\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+BA,eAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAcC,iCAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAGlD,MAAM,QAAQ,sDAAsD;EACpE,MAAM,MAAM,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EAEhJ,MAAM,WAAWD,eACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAI5C,MAAM,eAAeE,sCAAc,UAAU;EAE7C,MAAM,EAAE,kBAAkBC,4CACzB;GACC;GACA;GACA;GACA;GACA,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,GAAG;GAAY,QAAQ;GAAM,WAAW,CAAC,KAAK,GAAG,YAAY;EAAE,CAClE;EAEA,KAAK,gBAAgB;EAErB,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|
package/dist/fly/deploy.d.cts
CHANGED
|
@@ -26,6 +26,8 @@ interface FlyDeployArgs {
|
|
|
26
26
|
* Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).
|
|
27
27
|
*/
|
|
28
28
|
declare class FlyDeploy extends pulumi.ComponentResource {
|
|
29
|
+
/** The deploy CLI's final stdout line (the Fly app URL when emitted). */
|
|
30
|
+
readonly deploymentUrl: pulumi.Output<string>;
|
|
29
31
|
constructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions);
|
|
30
32
|
}
|
|
31
33
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;EACnC,QAAA,EAAU,WAAA;EACV,GAAA,EAAK,MAAA;AAAA;AAAA,UAGW,aAAA;;EAEhB,MAAA,EAAQ,aAAA;EAPe;EASvB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPzB;EASL,WAAA;EATW;EAWX,qBAAA;EAb4B;EAe5B,gBAAA;AAAA;;;;;cAOY,SAAA,SAAkB,MAAA,CAAO,iBAAA;
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;EACnC,QAAA,EAAU,WAAA;EACV,GAAA,EAAK,MAAA;AAAA;AAAA,UAGW,aAAA;;EAEhB,MAAA,EAAQ,aAAA;EAPe;EASvB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPzB;EASL,WAAA;EATW;EAWX,qBAAA;EAb4B;EAe5B,gBAAA;AAAA;;;;;cAOY,SAAA,SAAkB,MAAA,CAAO,iBAAA;EAjBrB;EAAA,SAmBA,aAAA,EAAe,MAAA,CAAO,MAAA;cAE1B,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
|
package/dist/fly/deploy.d.mts
CHANGED
|
@@ -26,6 +26,8 @@ interface FlyDeployArgs {
|
|
|
26
26
|
* Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).
|
|
27
27
|
*/
|
|
28
28
|
declare class FlyDeploy extends pulumi.ComponentResource {
|
|
29
|
+
/** The deploy CLI's final stdout line (the Fly app URL when emitted). */
|
|
30
|
+
readonly deploymentUrl: pulumi.Output<string>;
|
|
29
31
|
constructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions);
|
|
30
32
|
}
|
|
31
33
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;EACnC,QAAA,EAAU,WAAA;EACV,GAAA,EAAK,MAAA;AAAA;AAAA,UAGW,aAAA;;EAEhB,MAAA,EAAQ,aAAA;EAPe;EASvB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPzB;EASL,WAAA;EATW;EAWX,qBAAA;EAb4B;EAe5B,gBAAA;AAAA;;;;;cAOY,SAAA,SAAkB,MAAA,CAAO,iBAAA;
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/fly/deploy.ts"],"mappings":";;;;;;;KAQK,gBAAA,GAAmB,IAAA,CAAK,MAAA,CAAO,wBAAA;EACnC,QAAA,EAAU,WAAA;EACV,GAAA,EAAK,MAAA;AAAA;AAAA,UAGW,aAAA;;EAEhB,MAAA,EAAQ,aAAA;EAPe;EASvB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPzB;EASL,WAAA;EATW;EAWX,qBAAA;EAb4B;EAe5B,gBAAA;AAAA;;;;;cAOY,SAAA,SAAkB,MAAA,CAAO,iBAAA;EAjBrB;EAAA,SAmBA,aAAA,EAAe,MAAA,CAAO,MAAA;cAE1B,IAAA,UAAc,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,gBAAA;AAAA"}
|
package/dist/fly/deploy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { createDeployCommand } from "../commands/deploy.mjs";
|
|
2
|
+
import { createDeployCommand, dependsOnList } from "../commands/deploy.mjs";
|
|
3
3
|
import { generateFlyToml } from "./toml.mjs";
|
|
4
4
|
import * as pulumi from "@pulumi/pulumi";
|
|
5
5
|
|
|
@@ -20,11 +20,8 @@ var FlyDeploy = class extends pulumi.ComponentResource {
|
|
|
20
20
|
const setup = `mkdir -p .fly && printf '%s' "$FLY_TOML_CONTENT" > ${configPath}`;
|
|
21
21
|
const cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;
|
|
22
22
|
const triggers = pulumi.output(args.triggers).apply((values) => [...values, tomlContent]);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else if (pulumiOpts.dependsOn) consumerDeps = [pulumiOpts.dependsOn];
|
|
26
|
-
else consumerDeps = [];
|
|
27
|
-
createDeployCommand({
|
|
23
|
+
const consumerDeps = dependsOnList(pulumiOpts);
|
|
24
|
+
const { deploymentUrl } = createDeployCommand({
|
|
28
25
|
name,
|
|
29
26
|
cli,
|
|
30
27
|
triggers,
|
|
@@ -38,7 +35,8 @@ var FlyDeploy = class extends pulumi.ComponentResource {
|
|
|
38
35
|
parent: this,
|
|
39
36
|
dependsOn: [app, ...consumerDeps]
|
|
40
37
|
});
|
|
41
|
-
this.
|
|
38
|
+
this.deploymentUrl = deploymentUrl;
|
|
39
|
+
this.registerOutputs({ deploymentUrl: this.deploymentUrl });
|
|
42
40
|
}
|
|
43
41
|
};
|
|
44
42
|
|
package/dist/fly/deploy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["// src/fly/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\tprovider: FlyProvider;\n\tapp: FlyApp;\n};\n\nexport interface FlyDeployArgs {\n\t/** fly.toml configuration. `config.app` must equal the FlyApp name. */\n\tconfig: FlyTomlConfig;\n\t/** Redeploy triggers; the generated toml content is appended automatically. */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** `fly deploy --wait-timeout` seconds (default 300). */\n\twaitTimeout?: number;\n\t/** `fly deploy --release-command-timeout` seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only` from a generated fly.toml.\n * Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml content arrives via FLY_TOML_CONTENT (avoids shell escaping).\n\t\tconst setup = `mkdir -p .fly && printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`;\n\t\tconst cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/fly/deploy.ts"],"sourcesContent":["// src/fly/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand, dependsOnList } from \"../commands/deploy\";\nimport type { FlyApp } from \"./app\";\nimport type { FlyProvider } from \"./provider\";\nimport { type FlyTomlConfig, generateFlyToml } from \"./toml\";\n\ntype FlyDeployOptions = Omit<pulumi.ComponentResourceOptions, \"provider\"> & {\n\tprovider: FlyProvider;\n\tapp: FlyApp;\n};\n\nexport interface FlyDeployArgs {\n\t/** fly.toml configuration. `config.app` must equal the FlyApp name. */\n\tconfig: FlyTomlConfig;\n\t/** Redeploy triggers; the generated toml content is appended automatically. */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** `fly deploy --wait-timeout` seconds (default 300). */\n\twaitTimeout?: number;\n\t/** `fly deploy --release-command-timeout` seconds (default 600). */\n\treleaseCommandTimeout?: number;\n\t/** `fly deploy --ha` (default false). */\n\thighAvailability?: boolean;\n}\n\n/**\n * Deploys a Fly app via `fly deploy --remote-only` from a generated fly.toml.\n * Isolation/git are the seam's job (list a `DeploySandbox`, optionally a `GitGuard`).\n */\nexport class FlyDeploy extends pulumi.ComponentResource {\n\t/** The deploy CLI's final stdout line (the Fly app URL when emitted). */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(name: string, args: FlyDeployArgs, opts: FlyDeployOptions) {\n\t\tconst { provider, app, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:fly:Deploy\", name, {}, pulumiOpts);\n\n\t\tconst tomlContent = generateFlyToml(args.config);\n\t\tconst configPath = `.fly/${args.config.app}.toml`;\n\n\t\tconst waitTimeout = args.waitTimeout ?? 300;\n\t\tconst releaseCommandTimeout = args.releaseCommandTimeout ?? 600;\n\t\tconst highAvailability = args.highAvailability ?? false;\n\n\t\t// The toml content arrives via FLY_TOML_CONTENT (avoids shell escaping).\n\t\tconst setup = `mkdir -p .fly && printf '%s' \"$FLY_TOML_CONTENT\" > ${configPath}`;\n\t\tconst cli = `fly deploy --config ${configPath} --remote-only --ha=${highAvailability} --wait-timeout ${waitTimeout} --release-command-timeout ${releaseCommandTimeout}`;\n\n\t\tconst triggers = pulumi\n\t\t\t.output(args.triggers)\n\t\t\t.apply((values) => [...values, tomlContent]);\n\n\t\t// Keep the `app` ordering anchor first; append the consumer's deps\n\t\t// (DeploySandbox / GitGuard) using the seam's shared normaliser.\n\t\tconst consumerDeps = dependsOnList(pulumiOpts) as pulumi.Resource[];\n\n\t\tconst { deploymentUrl } = createDeployCommand(\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\tcli,\n\t\t\t\ttriggers,\n\t\t\t\tsetup,\n\t\t\t\tenvironment: {\n\t\t\t\t\tFLY_API_TOKEN: provider.token,\n\t\t\t\t\tFLY_TOML_CONTENT: tomlContent,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ ...pulumiOpts, parent: this, dependsOn: [app, ...consumerDeps] },\n\t\t);\n\n\t\tthis.deploymentUrl = deploymentUrl;\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;;;AA8BA,IAAa,YAAb,cAA+B,OAAO,kBAAkB;CAIvD,YAAY,MAAc,MAAqB,MAAwB;EACtE,MAAM,EAAE,UAAU,KAAK,GAAG,eAAe;EAEzC,MAAM,yBAAyB,MAAM,CAAC,GAAG,UAAU;EAEnD,MAAM,cAAc,gBAAgB,KAAK,MAAM;EAC/C,MAAM,aAAa,QAAQ,KAAK,OAAO,IAAI;EAE3C,MAAM,cAAc,KAAK,eAAe;EACxC,MAAM,wBAAwB,KAAK,yBAAyB;EAC5D,MAAM,mBAAmB,KAAK,oBAAoB;EAGlD,MAAM,QAAQ,sDAAsD;EACpE,MAAM,MAAM,uBAAuB,WAAW,sBAAsB,iBAAiB,kBAAkB,YAAY,6BAA6B;EAEhJ,MAAM,WAAW,OACf,OAAO,KAAK,QAAQ,EACpB,OAAO,WAAW,CAAC,GAAG,QAAQ,WAAW,CAAC;EAI5C,MAAM,eAAe,cAAc,UAAU;EAE7C,MAAM,EAAE,kBAAkB,oBACzB;GACC;GACA;GACA;GACA;GACA,aAAa;IACZ,eAAe,SAAS;IACxB,kBAAkB;GACnB;EACD,GACA;GAAE,GAAG;GAAY,QAAQ;GAAM,WAAW,CAAC,KAAK,GAAG,YAAY;EAAE,CAClE;EAEA,KAAK,gBAAgB;EAErB,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|
package/dist/railway/deploy.cjs
CHANGED
|
@@ -10,8 +10,16 @@ _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
|
10
10
|
* the Railway GraphQL API to a terminal status and fails the resource on
|
|
11
11
|
* FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it
|
|
12
12
|
* uses ONLY double quotes and reads inputs from IC_* env vars.
|
|
13
|
+
*
|
|
14
|
+
* Deployment-id resolution filters by `createdAt >= IC_SINCE` (the epoch captured
|
|
15
|
+
* just before `railway up`, minus a clock-skew buffer) and picks the newest such
|
|
16
|
+
* deployment. This prevents latching onto the PREVIOUS deployment when Railway's
|
|
17
|
+
* API has not yet registered the new one (a plain `first:1` race). SLEEPING (the
|
|
18
|
+
* service deployed then scaled to zero) is treated as success; SKIPPED (the
|
|
19
|
+
* deploy was superseded) is non-blocking; an unresolvable id (upload already
|
|
20
|
+
* succeeded) does not block the release.
|
|
13
21
|
*/
|
|
14
|
-
const DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const u="https://backboard.railway.app/graphql/v2";const q=(query,variables)=>fetch(u,{method:"POST",headers:{"Project-Access-Token":t,"Content-Type":"application/json"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));(async()=>{let id;for(let i=0;i<
|
|
22
|
+
const DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const since=Number(process.env.IC_SINCE||0)-120000;const u="https://backboard.railway.app/graphql/v2";const q=(query,variables)=>fetch(u,{method:"POST",headers:{"Project-Access-Token":t,"Content-Type":"application/json"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));const ok={SUCCESS:1,SLEEPING:1};const bad={FAILED:1,CRASHED:1,REMOVED:1};(async()=>{let id;for(let i=0;i<12&&!id;i++){const d=await q("query($p:String!,$e:String!,$s:String!){deployments(first:10,input:{projectId:$p,environmentId:$e,serviceId:$s}){edges{node{id status createdAt}}}}",{p,e,s});const g=(d&&d.data&&d.data.deployments&&d.data.deployments.edges)||[];const fresh=g.map(x=>x.node).filter(n=>n&&n.createdAt&&Date.parse(n.createdAt)>=since).sort((a,b)=>Date.parse(b.createdAt)-Date.parse(a.createdAt));if(fresh[0])id=fresh[0].id;if(!id)await sl(5000);}if(!id){console.error("[infracraft] could not resolve a Railway deployment newer than this run; upload succeeded, not blocking");process.exit(0);}for(let i=0;i<120;i++){const r=await q("query($d:String!){deployment(id:$d){status}}",{d:id});const st=r&&r.data&&r.data.deployment&&r.data.deployment.status;if(st)console.error("[infracraft] railway deployment "+id+" status="+st);if(st&&ok[st])process.exit(0);if(st==="SKIPPED"){console.error("[infracraft] railway deployment "+id+" SKIPPED (superseded) — not blocking");process.exit(0);}if(st&&bad[st]){const l=await q("query($d:String!){deploymentLogs(deploymentId:$d,limit:60){message}}",{d:id});const lg=(l&&l.data&&l.data.deploymentLogs)||[];for(const x of lg)console.error(" "+(x.message||""));console.error("[infracraft] railway deployment "+id+" "+st+" — failing the Pulumi resource");process.exit(1);}await sl(10000);}console.error("[infracraft] timed out waiting for Railway deployment "+id);process.exit(1);})();`;
|
|
15
23
|
/**
|
|
16
24
|
* Deploys a Railway service and waits for a terminal status. Isolation/git are the
|
|
17
25
|
* seam's job (list a `DeploySandbox` and optionally a `GitGuard` in `dependsOn`).
|
|
@@ -26,9 +34,9 @@ var RailwayDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
26
34
|
constructor(name, args, opts) {
|
|
27
35
|
const { provider, project, environment, service, projectToken, ...pulumiOpts } = opts;
|
|
28
36
|
super("infracraft:railway:Deploy", name, {}, pulumiOpts);
|
|
29
|
-
const cli = _pulumi_pulumi.interpolate`RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ "$EXIT" -ne 0 ]; then exit "$EXIT"; fi; if [ -n "$INFRACRAFT_SKIP_DEPLOY_WAIT" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} node -e '${DEPLOY_WAIT_SCRIPT}'`;
|
|
30
|
-
const setup = args.railpackConfig ? `printf '${JSON.stringify(args.railpackConfig).replace(/'/g, "\\'")}' > railpack.json` : void 0;
|
|
31
|
-
require_commands_deploy.createDeployCommand({
|
|
37
|
+
const cli = _pulumi_pulumi.interpolate`IC_SINCE=$(node -e "process.stdout.write(String(Date.now()))"); RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ "$EXIT" -ne 0 ]; then exit "$EXIT"; fi; if [ -n "$INFRACRAFT_SKIP_DEPLOY_WAIT" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} IC_SINCE=$IC_SINCE node -e '${DEPLOY_WAIT_SCRIPT}'`;
|
|
38
|
+
const setup = args.railpackConfig ? `printf '%s' '${JSON.stringify(args.railpackConfig).replace(/'/g, "'\\''")}' > railpack.json` : void 0;
|
|
39
|
+
const { deploymentUrl } = require_commands_deploy.createDeployCommand({
|
|
32
40
|
name,
|
|
33
41
|
cli,
|
|
34
42
|
triggers: args.triggers,
|
|
@@ -38,7 +46,8 @@ var RailwayDeploy = class extends _pulumi_pulumi.ComponentResource {
|
|
|
38
46
|
parent: this,
|
|
39
47
|
...pulumiOpts
|
|
40
48
|
});
|
|
41
|
-
this.
|
|
49
|
+
this.deploymentUrl = deploymentUrl;
|
|
50
|
+
this.registerOutputs({ deploymentUrl: this.deploymentUrl });
|
|
42
51
|
}
|
|
43
52
|
};
|
|
44
53
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["pulumi"],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["// src/railway/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport { RailwayBuilder, type RailwayService } from \"./service\";\n\nexport interface RailwayDeployConfig {\n\tbuilder?: RailwayBuilder;\n\tstartCommand?: string;\n\tpreDeployCommand?: string;\n}\n\nexport interface RailwayDeployArgs {\n\t/** Redeploy triggers (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Paths excluded from the upload when running with `DeploySandbox` + `GitGuard`. */\n\texcludePaths?: string[];\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\ntype RailwayDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\tprovider: RailwayProvider;\n\tproject: RailwayProject;\n\tenvironment: RailwayEnvironment;\n\tservice: RailwayService;\n\t/** Environment-scoped Railway deploy token (provision via RailwayProjectToken). */\n\tprojectToken: pulumi.Input<string>;\n};\n\n/**\n * The deploy-wait poller. `railway up --ci` only blocks until upload; this polls\n * the Railway GraphQL API to a terminal status and fails the resource on\n * FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it\n * uses ONLY double quotes and reads inputs from IC_* env vars.\n */\nconst DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const u=\"https://backboard.railway.app/graphql/v2\";const q=(query,variables)=>fetch(u,{method:\"POST\",headers:{\"Project-Access-Token\":t,\"Content-Type\":\"application/json\"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));(async()=>{let id;for(let i=0;i<
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["pulumi","createDeployCommand"],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["// src/railway/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport { RailwayBuilder, type RailwayService } from \"./service\";\n\nexport interface RailwayDeployConfig {\n\tbuilder?: RailwayBuilder;\n\tstartCommand?: string;\n\tpreDeployCommand?: string;\n}\n\nexport interface RailwayDeployArgs {\n\t/** Redeploy triggers (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Paths excluded from the upload when running with `DeploySandbox` + `GitGuard`. */\n\texcludePaths?: string[];\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\ntype RailwayDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\tprovider: RailwayProvider;\n\tproject: RailwayProject;\n\tenvironment: RailwayEnvironment;\n\tservice: RailwayService;\n\t/** Environment-scoped Railway deploy token (provision via RailwayProjectToken). */\n\tprojectToken: pulumi.Input<string>;\n};\n\n/**\n * The deploy-wait poller. `railway up --ci` only blocks until upload; this polls\n * the Railway GraphQL API to a terminal status and fails the resource on\n * FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it\n * uses ONLY double quotes and reads inputs from IC_* env vars.\n *\n * Deployment-id resolution filters by `createdAt >= IC_SINCE` (the epoch captured\n * just before `railway up`, minus a clock-skew buffer) and picks the newest such\n * deployment. This prevents latching onto the PREVIOUS deployment when Railway's\n * API has not yet registered the new one (a plain `first:1` race). SLEEPING (the\n * service deployed then scaled to zero) is treated as success; SKIPPED (the\n * deploy was superseded) is non-blocking; an unresolvable id (upload already\n * succeeded) does not block the release.\n */\nconst DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const since=Number(process.env.IC_SINCE||0)-120000;const u=\"https://backboard.railway.app/graphql/v2\";const q=(query,variables)=>fetch(u,{method:\"POST\",headers:{\"Project-Access-Token\":t,\"Content-Type\":\"application/json\"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));const ok={SUCCESS:1,SLEEPING:1};const bad={FAILED:1,CRASHED:1,REMOVED:1};(async()=>{let id;for(let i=0;i<12&&!id;i++){const d=await q(\"query($p:String!,$e:String!,$s:String!){deployments(first:10,input:{projectId:$p,environmentId:$e,serviceId:$s}){edges{node{id status createdAt}}}}\",{p,e,s});const g=(d&&d.data&&d.data.deployments&&d.data.deployments.edges)||[];const fresh=g.map(x=>x.node).filter(n=>n&&n.createdAt&&Date.parse(n.createdAt)>=since).sort((a,b)=>Date.parse(b.createdAt)-Date.parse(a.createdAt));if(fresh[0])id=fresh[0].id;if(!id)await sl(5000);}if(!id){console.error(\"[infracraft] could not resolve a Railway deployment newer than this run; upload succeeded, not blocking\");process.exit(0);}for(let i=0;i<120;i++){const r=await q(\"query($d:String!){deployment(id:$d){status}}\",{d:id});const st=r&&r.data&&r.data.deployment&&r.data.deployment.status;if(st)console.error(\"[infracraft] railway deployment \"+id+\" status=\"+st);if(st&&ok[st])process.exit(0);if(st===\"SKIPPED\"){console.error(\"[infracraft] railway deployment \"+id+\" SKIPPED (superseded) — not blocking\");process.exit(0);}if(st&&bad[st]){const l=await q(\"query($d:String!){deploymentLogs(deploymentId:$d,limit:60){message}}\",{d:id});const lg=(l&&l.data&&l.data.deploymentLogs)||[];for(const x of lg)console.error(\" \"+(x.message||\"\"));console.error(\"[infracraft] railway deployment \"+id+\" \"+st+\" — failing the Pulumi resource\");process.exit(1);}await sl(10000);}console.error(\"[infracraft] timed out waiting for Railway deployment \"+id);process.exit(1);})();`;\n\n/**\n * Deploys a Railway service and waits for a terminal status. Isolation/git are the\n * seam's job (list a `DeploySandbox` and optionally a `GitGuard` in `dependsOn`).\n *\n * @example\n * ```typescript\n * new RailwayDeploy(\"mesh\", { triggers: [sourceHash], railpackConfig: { apt: [\"libatomic1\"] } },\n * { provider, project, environment, service, projectToken: token.token, dependsOn: [sandbox, gitGuard] });\n * ```\n */\nexport class RailwayDeploy extends pulumi.ComponentResource {\n\t/** The deploy CLI's final stdout line (Railway service URL when emitted). */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDeployArgs,\n\t\topts: RailwayDeployOptions,\n\t) {\n\t\tconst {\n\t\t\tprovider,\n\t\t\tproject,\n\t\t\tenvironment,\n\t\t\tservice,\n\t\t\tprojectToken,\n\t\t\t...pulumiOpts\n\t\t} = opts;\n\n\t\tsuper(\"infracraft:railway:Deploy\", name, {}, pulumiOpts);\n\n\t\t// Token is inlined (not via the env map): it is an unknown secret at preview,\n\t\t// and an unknown secret in `environment` makes `pulumi preview` fail.\n\t\t// IC_SINCE is captured just before `railway up` so the poller can tell the\n\t\t// new deployment apart from the previous one by createdAt.\n\t\tconst cli = pulumi.interpolate`IC_SINCE=$(node -e \"process.stdout.write(String(Date.now()))\"); RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ \"$EXIT\" -ne 0 ]; then exit \"$EXIT\"; fi; if [ -n \"$INFRACRAFT_SKIP_DEPLOY_WAIT\" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} IC_SINCE=$IC_SINCE node -e '${DEPLOY_WAIT_SCRIPT}'`;\n\n\t\t// `printf '%s'` (not a bare format string) so railpack values containing %\n\t\t// are literal; the JSON is single-quote-escaped the POSIX way (' -> '\\'').\n\t\tconst setup = args.railpackConfig\n\t\t\t? `printf '%s' '${JSON.stringify(args.railpackConfig).replace(/'/g, \"'\\\\''\")}' > railpack.json`\n\t\t\t: undefined;\n\n\t\tconst { deploymentUrl } = createDeployCommand(\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\tcli,\n\t\t\t\ttriggers: args.triggers,\n\t\t\t\texcludePaths: args.excludePaths,\n\t\t\t\tsetup,\n\t\t\t},\n\t\t\t{ parent: this, ...pulumiOpts },\n\t\t);\n\n\t\tthis.deploymentUrl = deploymentUrl;\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkDA,MAAM,qBAAqB;;;;;;;;;;;AAY3B,IAAa,gBAAb,cAAmCA,eAAO,kBAAkB;CAI3D,YACC,MACA,MACA,MACC;EACD,MAAM,EACL,UACA,SACA,aACA,SACA,cACA,GAAG,eACA;EAEJ,MAAM,6BAA6B,MAAM,CAAC,GAAG,UAAU;EAMvD,MAAM,MAAM,eAAO,WAAW,iFAAiF,aAAa,6BAA6B,QAAQ,GAAG,aAAa,QAAQ,GAAG,iBAAiB,YAAY,GAAG,4HAA4H,aAAa,WAAW,QAAQ,GAAG,UAAU,YAAY,GAAG,UAAU,QAAQ,GAAG,+BAA+B,mBAAmB;EAI3d,MAAM,QAAQ,KAAK,iBAChB,gBAAgB,KAAK,UAAU,KAAK,cAAc,EAAE,QAAQ,MAAM,OAAO,EAAE,qBAC3E;EAEH,MAAM,EAAE,kBAAkBC,4CACzB;GACC;GACA;GACA,UAAU,KAAK;GACf,cAAc,KAAK;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,GAAG;EAAW,CAC/B;EAEA,KAAK,gBAAgB;EAErB,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|
|
@@ -37,6 +37,8 @@ type RailwayDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> &
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
declare class RailwayDeploy extends pulumi.ComponentResource {
|
|
40
|
+
/** The deploy CLI's final stdout line (Railway service URL when emitted). */
|
|
41
|
+
readonly deploymentUrl: pulumi.Output<string>;
|
|
40
42
|
constructor(name: string, args: RailwayDeployArgs, opts: RailwayDeployOptions);
|
|
41
43
|
}
|
|
42
44
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;;;;UASiB,mBAAA;EAChB,OAAA,GAAU,cAAc;EACxB,YAAA;EACA,gBAAA;AAAA;AAAA,UAGgB,iBAAA;EALQ;EAOxB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPpB;EASV,YAAA;EAPA;EASA,cAAA,GAAiB,MAAA;AAAA;AAAA,KAGb,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAGP,QAAA,EAAU,eAAA;EACV,OAAA,EAAS,cAAA;EACT,WAAA,EAAa,kBAAA;EACb,OAAA,EAAS,cAAA,EAVQ;EAYjB,YAAA,EAAc,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAZE;
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;;;;UASiB,mBAAA;EAChB,OAAA,GAAU,cAAc;EACxB,YAAA;EACA,gBAAA;AAAA;AAAA,UAGgB,iBAAA;EALQ;EAOxB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPpB;EASV,YAAA;EAPA;EASA,cAAA,GAAiB,MAAA;AAAA;AAAA,KAGb,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAGP,QAAA,EAAU,eAAA;EACV,OAAA,EAAS,cAAA;EACT,WAAA,EAAa,kBAAA;EACb,OAAA,EAAS,cAAA,EAVQ;EAYjB,YAAA,EAAc,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAZE;cAyCX,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAtCjB;EAAA,SAwCR,aAAA,EAAe,MAAA,CAAO,MAAA;cAGrC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -37,6 +37,8 @@ type RailwayDeployOptions = Omit<pulumi.ComponentResourceOptions, "provider"> &
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
declare class RailwayDeploy extends pulumi.ComponentResource {
|
|
40
|
+
/** The deploy CLI's final stdout line (Railway service URL when emitted). */
|
|
41
|
+
readonly deploymentUrl: pulumi.Output<string>;
|
|
40
42
|
constructor(name: string, args: RailwayDeployArgs, opts: RailwayDeployOptions);
|
|
41
43
|
}
|
|
42
44
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;;;;UASiB,mBAAA;EAChB,OAAA,GAAU,cAAc;EACxB,YAAA;EACA,gBAAA;AAAA;AAAA,UAGgB,iBAAA;EALQ;EAOxB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPpB;EASV,YAAA;EAPA;EASA,cAAA,GAAiB,MAAA;AAAA;AAAA,KAGb,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAGP,QAAA,EAAU,eAAA;EACV,OAAA,EAAS,cAAA;EACT,WAAA,EAAa,kBAAA;EACb,OAAA,EAAS,cAAA,EAVQ;EAYjB,YAAA,EAAc,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAZE;
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;;;;UASiB,mBAAA;EAChB,OAAA,GAAU,cAAc;EACxB,YAAA;EACA,gBAAA;AAAA;AAAA,UAGgB,iBAAA;EALQ;EAOxB,QAAA,EAAU,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA;EAPpB;EASV,YAAA;EAPA;EASA,cAAA,GAAiB,MAAA;AAAA;AAAA,KAGb,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EAGP,QAAA,EAAU,eAAA;EACV,OAAA,EAAS,cAAA;EACT,WAAA,EAAa,kBAAA;EACb,OAAA,EAAS,cAAA,EAVQ;EAYjB,YAAA,EAAc,MAAA,CAAO,KAAA;AAAA;;;;;;;;;;AAZE;cAyCX,aAAA,SAAsB,MAAA,CAAO,iBAAA;EAtCjB;EAAA,SAwCR,aAAA,EAAe,MAAA,CAAO,MAAA;cAGrC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
package/dist/railway/deploy.mjs
CHANGED
|
@@ -8,8 +8,16 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
8
8
|
* the Railway GraphQL API to a terminal status and fails the resource on
|
|
9
9
|
* FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it
|
|
10
10
|
* uses ONLY double quotes and reads inputs from IC_* env vars.
|
|
11
|
+
*
|
|
12
|
+
* Deployment-id resolution filters by `createdAt >= IC_SINCE` (the epoch captured
|
|
13
|
+
* just before `railway up`, minus a clock-skew buffer) and picks the newest such
|
|
14
|
+
* deployment. This prevents latching onto the PREVIOUS deployment when Railway's
|
|
15
|
+
* API has not yet registered the new one (a plain `first:1` race). SLEEPING (the
|
|
16
|
+
* service deployed then scaled to zero) is treated as success; SKIPPED (the
|
|
17
|
+
* deploy was superseded) is non-blocking; an unresolvable id (upload already
|
|
18
|
+
* succeeded) does not block the release.
|
|
11
19
|
*/
|
|
12
|
-
const DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const u="https://backboard.railway.app/graphql/v2";const q=(query,variables)=>fetch(u,{method:"POST",headers:{"Project-Access-Token":t,"Content-Type":"application/json"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));(async()=>{let id;for(let i=0;i<
|
|
20
|
+
const DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const since=Number(process.env.IC_SINCE||0)-120000;const u="https://backboard.railway.app/graphql/v2";const q=(query,variables)=>fetch(u,{method:"POST",headers:{"Project-Access-Token":t,"Content-Type":"application/json"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));const ok={SUCCESS:1,SLEEPING:1};const bad={FAILED:1,CRASHED:1,REMOVED:1};(async()=>{let id;for(let i=0;i<12&&!id;i++){const d=await q("query($p:String!,$e:String!,$s:String!){deployments(first:10,input:{projectId:$p,environmentId:$e,serviceId:$s}){edges{node{id status createdAt}}}}",{p,e,s});const g=(d&&d.data&&d.data.deployments&&d.data.deployments.edges)||[];const fresh=g.map(x=>x.node).filter(n=>n&&n.createdAt&&Date.parse(n.createdAt)>=since).sort((a,b)=>Date.parse(b.createdAt)-Date.parse(a.createdAt));if(fresh[0])id=fresh[0].id;if(!id)await sl(5000);}if(!id){console.error("[infracraft] could not resolve a Railway deployment newer than this run; upload succeeded, not blocking");process.exit(0);}for(let i=0;i<120;i++){const r=await q("query($d:String!){deployment(id:$d){status}}",{d:id});const st=r&&r.data&&r.data.deployment&&r.data.deployment.status;if(st)console.error("[infracraft] railway deployment "+id+" status="+st);if(st&&ok[st])process.exit(0);if(st==="SKIPPED"){console.error("[infracraft] railway deployment "+id+" SKIPPED (superseded) — not blocking");process.exit(0);}if(st&&bad[st]){const l=await q("query($d:String!){deploymentLogs(deploymentId:$d,limit:60){message}}",{d:id});const lg=(l&&l.data&&l.data.deploymentLogs)||[];for(const x of lg)console.error(" "+(x.message||""));console.error("[infracraft] railway deployment "+id+" "+st+" — failing the Pulumi resource");process.exit(1);}await sl(10000);}console.error("[infracraft] timed out waiting for Railway deployment "+id);process.exit(1);})();`;
|
|
13
21
|
/**
|
|
14
22
|
* Deploys a Railway service and waits for a terminal status. Isolation/git are the
|
|
15
23
|
* seam's job (list a `DeploySandbox` and optionally a `GitGuard` in `dependsOn`).
|
|
@@ -24,9 +32,9 @@ var RailwayDeploy = class extends pulumi.ComponentResource {
|
|
|
24
32
|
constructor(name, args, opts) {
|
|
25
33
|
const { provider, project, environment, service, projectToken, ...pulumiOpts } = opts;
|
|
26
34
|
super("infracraft:railway:Deploy", name, {}, pulumiOpts);
|
|
27
|
-
const cli = pulumi.interpolate`RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ "$EXIT" -ne 0 ]; then exit "$EXIT"; fi; if [ -n "$INFRACRAFT_SKIP_DEPLOY_WAIT" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} node -e '${DEPLOY_WAIT_SCRIPT}'`;
|
|
28
|
-
const setup = args.railpackConfig ? `printf '${JSON.stringify(args.railpackConfig).replace(/'/g, "\\'")}' > railpack.json` : void 0;
|
|
29
|
-
createDeployCommand({
|
|
35
|
+
const cli = pulumi.interpolate`IC_SINCE=$(node -e "process.stdout.write(String(Date.now()))"); RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ "$EXIT" -ne 0 ]; then exit "$EXIT"; fi; if [ -n "$INFRACRAFT_SKIP_DEPLOY_WAIT" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} IC_SINCE=$IC_SINCE node -e '${DEPLOY_WAIT_SCRIPT}'`;
|
|
36
|
+
const setup = args.railpackConfig ? `printf '%s' '${JSON.stringify(args.railpackConfig).replace(/'/g, "'\\''")}' > railpack.json` : void 0;
|
|
37
|
+
const { deploymentUrl } = createDeployCommand({
|
|
30
38
|
name,
|
|
31
39
|
cli,
|
|
32
40
|
triggers: args.triggers,
|
|
@@ -36,7 +44,8 @@ var RailwayDeploy = class extends pulumi.ComponentResource {
|
|
|
36
44
|
parent: this,
|
|
37
45
|
...pulumiOpts
|
|
38
46
|
});
|
|
39
|
-
this.
|
|
47
|
+
this.deploymentUrl = deploymentUrl;
|
|
48
|
+
this.registerOutputs({ deploymentUrl: this.deploymentUrl });
|
|
40
49
|
}
|
|
41
50
|
};
|
|
42
51
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["// src/railway/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport { RailwayBuilder, type RailwayService } from \"./service\";\n\nexport interface RailwayDeployConfig {\n\tbuilder?: RailwayBuilder;\n\tstartCommand?: string;\n\tpreDeployCommand?: string;\n}\n\nexport interface RailwayDeployArgs {\n\t/** Redeploy triggers (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Paths excluded from the upload when running with `DeploySandbox` + `GitGuard`. */\n\texcludePaths?: string[];\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\ntype RailwayDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\tprovider: RailwayProvider;\n\tproject: RailwayProject;\n\tenvironment: RailwayEnvironment;\n\tservice: RailwayService;\n\t/** Environment-scoped Railway deploy token (provision via RailwayProjectToken). */\n\tprojectToken: pulumi.Input<string>;\n};\n\n/**\n * The deploy-wait poller. `railway up --ci` only blocks until upload; this polls\n * the Railway GraphQL API to a terminal status and fails the resource on\n * FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it\n * uses ONLY double quotes and reads inputs from IC_* env vars.\n */\nconst DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const u=\"https://backboard.railway.app/graphql/v2\";const q=(query,variables)=>fetch(u,{method:\"POST\",headers:{\"Project-Access-Token\":t,\"Content-Type\":\"application/json\"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));(async()=>{let id;for(let i=0;i<
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["// src/railway/deploy.ts (replace entire file)\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport { createDeployCommand } from \"../commands/deploy\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport { RailwayBuilder, type RailwayService } from \"./service\";\n\nexport interface RailwayDeployConfig {\n\tbuilder?: RailwayBuilder;\n\tstartCommand?: string;\n\tpreDeployCommand?: string;\n}\n\nexport interface RailwayDeployArgs {\n\t/** Redeploy triggers (e.g. source hashes, env hashes). */\n\ttriggers: pulumi.Input<pulumi.Input<string>[]>;\n\t/** Paths excluded from the upload when running with `DeploySandbox` + `GitGuard`. */\n\texcludePaths?: string[];\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\ntype RailwayDeployOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\tprovider: RailwayProvider;\n\tproject: RailwayProject;\n\tenvironment: RailwayEnvironment;\n\tservice: RailwayService;\n\t/** Environment-scoped Railway deploy token (provision via RailwayProjectToken). */\n\tprojectToken: pulumi.Input<string>;\n};\n\n/**\n * The deploy-wait poller. `railway up --ci` only blocks until upload; this polls\n * the Railway GraphQL API to a terminal status and fails the resource on\n * FAILED/CRASHED/REMOVED. Runs under `node -e`, single-quoted in the shell, so it\n * uses ONLY double quotes and reads inputs from IC_* env vars.\n *\n * Deployment-id resolution filters by `createdAt >= IC_SINCE` (the epoch captured\n * just before `railway up`, minus a clock-skew buffer) and picks the newest such\n * deployment. This prevents latching onto the PREVIOUS deployment when Railway's\n * API has not yet registered the new one (a plain `first:1` race). SLEEPING (the\n * service deployed then scaled to zero) is treated as success; SKIPPED (the\n * deploy was superseded) is non-blocking; an unresolvable id (upload already\n * succeeded) does not block the release.\n */\nconst DEPLOY_WAIT_SCRIPT = `const t=process.env.IC_TOK,p=process.env.IC_PROJ,e=process.env.IC_ENV,s=process.env.IC_SVC;const since=Number(process.env.IC_SINCE||0)-120000;const u=\"https://backboard.railway.app/graphql/v2\";const q=(query,variables)=>fetch(u,{method:\"POST\",headers:{\"Project-Access-Token\":t,\"Content-Type\":\"application/json\"},body:JSON.stringify({query,variables})}).then(r=>r.json()).catch(()=>({}));const sl=ms=>new Promise(r=>setTimeout(r,ms));const ok={SUCCESS:1,SLEEPING:1};const bad={FAILED:1,CRASHED:1,REMOVED:1};(async()=>{let id;for(let i=0;i<12&&!id;i++){const d=await q(\"query($p:String!,$e:String!,$s:String!){deployments(first:10,input:{projectId:$p,environmentId:$e,serviceId:$s}){edges{node{id status createdAt}}}}\",{p,e,s});const g=(d&&d.data&&d.data.deployments&&d.data.deployments.edges)||[];const fresh=g.map(x=>x.node).filter(n=>n&&n.createdAt&&Date.parse(n.createdAt)>=since).sort((a,b)=>Date.parse(b.createdAt)-Date.parse(a.createdAt));if(fresh[0])id=fresh[0].id;if(!id)await sl(5000);}if(!id){console.error(\"[infracraft] could not resolve a Railway deployment newer than this run; upload succeeded, not blocking\");process.exit(0);}for(let i=0;i<120;i++){const r=await q(\"query($d:String!){deployment(id:$d){status}}\",{d:id});const st=r&&r.data&&r.data.deployment&&r.data.deployment.status;if(st)console.error(\"[infracraft] railway deployment \"+id+\" status=\"+st);if(st&&ok[st])process.exit(0);if(st===\"SKIPPED\"){console.error(\"[infracraft] railway deployment \"+id+\" SKIPPED (superseded) — not blocking\");process.exit(0);}if(st&&bad[st]){const l=await q(\"query($d:String!){deploymentLogs(deploymentId:$d,limit:60){message}}\",{d:id});const lg=(l&&l.data&&l.data.deploymentLogs)||[];for(const x of lg)console.error(\" \"+(x.message||\"\"));console.error(\"[infracraft] railway deployment \"+id+\" \"+st+\" — failing the Pulumi resource\");process.exit(1);}await sl(10000);}console.error(\"[infracraft] timed out waiting for Railway deployment \"+id);process.exit(1);})();`;\n\n/**\n * Deploys a Railway service and waits for a terminal status. Isolation/git are the\n * seam's job (list a `DeploySandbox` and optionally a `GitGuard` in `dependsOn`).\n *\n * @example\n * ```typescript\n * new RailwayDeploy(\"mesh\", { triggers: [sourceHash], railpackConfig: { apt: [\"libatomic1\"] } },\n * { provider, project, environment, service, projectToken: token.token, dependsOn: [sandbox, gitGuard] });\n * ```\n */\nexport class RailwayDeploy extends pulumi.ComponentResource {\n\t/** The deploy CLI's final stdout line (Railway service URL when emitted). */\n\tpublic readonly deploymentUrl: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDeployArgs,\n\t\topts: RailwayDeployOptions,\n\t) {\n\t\tconst {\n\t\t\tprovider,\n\t\t\tproject,\n\t\t\tenvironment,\n\t\t\tservice,\n\t\t\tprojectToken,\n\t\t\t...pulumiOpts\n\t\t} = opts;\n\n\t\tsuper(\"infracraft:railway:Deploy\", name, {}, pulumiOpts);\n\n\t\t// Token is inlined (not via the env map): it is an unknown secret at preview,\n\t\t// and an unknown secret in `environment` makes `pulumi preview` fail.\n\t\t// IC_SINCE is captured just before `railway up` so the poller can tell the\n\t\t// new deployment apart from the previous one by createdAt.\n\t\tconst cli = pulumi.interpolate`IC_SINCE=$(node -e \"process.stdout.write(String(Date.now()))\"); RAILWAY_TOKEN=${projectToken} railway up --ci --project ${project.id} --service ${service.id} --environment ${environment.id}; EXIT=$?; if [ \"$EXIT\" -ne 0 ]; then exit \"$EXIT\"; fi; if [ -n \"$INFRACRAFT_SKIP_DEPLOY_WAIT\" ]; then exit 0; fi; IC_TOK=${projectToken} IC_PROJ=${project.id} IC_ENV=${environment.id} IC_SVC=${service.id} IC_SINCE=$IC_SINCE node -e '${DEPLOY_WAIT_SCRIPT}'`;\n\n\t\t// `printf '%s'` (not a bare format string) so railpack values containing %\n\t\t// are literal; the JSON is single-quote-escaped the POSIX way (' -> '\\'').\n\t\tconst setup = args.railpackConfig\n\t\t\t? `printf '%s' '${JSON.stringify(args.railpackConfig).replace(/'/g, \"'\\\\''\")}' > railpack.json`\n\t\t\t: undefined;\n\n\t\tconst { deploymentUrl } = createDeployCommand(\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\tcli,\n\t\t\t\ttriggers: args.triggers,\n\t\t\t\texcludePaths: args.excludePaths,\n\t\t\t\tsetup,\n\t\t\t},\n\t\t\t{ parent: this, ...pulumiOpts },\n\t\t);\n\n\t\tthis.deploymentUrl = deploymentUrl;\n\n\t\tthis.registerOutputs({ deploymentUrl: this.deploymentUrl });\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkDA,MAAM,qBAAqB;;;;;;;;;;;AAY3B,IAAa,gBAAb,cAAmC,OAAO,kBAAkB;CAI3D,YACC,MACA,MACA,MACC;EACD,MAAM,EACL,UACA,SACA,aACA,SACA,cACA,GAAG,eACA;EAEJ,MAAM,6BAA6B,MAAM,CAAC,GAAG,UAAU;EAMvD,MAAM,MAAM,OAAO,WAAW,iFAAiF,aAAa,6BAA6B,QAAQ,GAAG,aAAa,QAAQ,GAAG,iBAAiB,YAAY,GAAG,4HAA4H,aAAa,WAAW,QAAQ,GAAG,UAAU,YAAY,GAAG,UAAU,QAAQ,GAAG,+BAA+B,mBAAmB;EAI3d,MAAM,QAAQ,KAAK,iBAChB,gBAAgB,KAAK,UAAU,KAAK,cAAc,EAAE,QAAQ,MAAM,OAAO,EAAE,qBAC3E;EAEH,MAAM,EAAE,kBAAkB,oBACzB;GACC;GACA;GACA,UAAU,KAAK;GACf,cAAc,KAAK;GACnB;EACD,GACA;GAAE,QAAQ;GAAM,GAAG;EAAW,CAC/B;EAEA,KAAK,gBAAgB;EAErB,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,CAAC;CAC3D;AACD"}
|
package/dist/sandbox.cjs
CHANGED
|
@@ -13,8 +13,8 @@ function escapeAwkRegex(path) {
|
|
|
13
13
|
return path.replace(/[.[\]{}()*+?^$|\\/]/g, "\\$&");
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Builds the shell filter applied to
|
|
17
|
-
*
|
|
16
|
+
* Builds the shell filter applied to the `git ls-files` list before
|
|
17
|
+
* `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps
|
|
18
18
|
* `apps/<x>/package.json` (the monorepo workspace graph needs it during build);
|
|
19
19
|
* any other entry drops the path and its subtree. Returns `cat` (passthrough)
|
|
20
20
|
* when nothing is excluded. Uses awk so it is portable across macOS and Linux.
|
|
@@ -30,27 +30,59 @@ function buildSandboxFileFilter(excludePaths = []) {
|
|
|
30
30
|
/** Root of the per-deploy sandbox tree. The DeploySandbox resource GCs this. */
|
|
31
31
|
const SANDBOX_ROOT = "/tmp/infracraft";
|
|
32
32
|
/**
|
|
33
|
+
* How a deploy's working copy is isolated. A closed set, so it is an enum rather
|
|
34
|
+
* than a pair of booleans: the seam derives it from `dependsOn` presence
|
|
35
|
+
* (DeploySandbox / GitGuard), which makes the impossible "git-guard without a
|
|
36
|
+
* sandbox" state unrepresentable here.
|
|
37
|
+
*/
|
|
38
|
+
let SandboxMode = /* @__PURE__ */ function(SandboxMode) {
|
|
39
|
+
/** No isolation — run the CLI in the live working tree. */
|
|
40
|
+
SandboxMode["NONE"] = "NONE";
|
|
41
|
+
/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */
|
|
42
|
+
SandboxMode["ORIGINAL"] = "ORIGINAL";
|
|
43
|
+
/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */
|
|
44
|
+
SandboxMode["STUB"] = "STUB";
|
|
45
|
+
return SandboxMode;
|
|
46
|
+
}({});
|
|
47
|
+
/**
|
|
33
48
|
* Builds the shell for a deploy's `command.local.Command.create`. See
|
|
34
49
|
* docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.
|
|
50
|
+
*
|
|
51
|
+
* Hardening: every script runs under POSIX `set -e` so any failed step aborts
|
|
52
|
+
* instead of silently letting the platform CLI run against partial state.
|
|
53
|
+
* `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which
|
|
54
|
+
* may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing
|
|
55
|
+
* `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits
|
|
56
|
+
* 0), the copy is staged through intermediate list files rather than a pipe. The
|
|
57
|
+
* `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear
|
|
58
|
+
* error messages.
|
|
35
59
|
*/
|
|
36
60
|
function buildSandboxScript(options) {
|
|
37
|
-
const {
|
|
38
|
-
const head = `REPO=$(git rev-parse --show-toplevel)`;
|
|
61
|
+
const { mode, appName, env, excludePaths, setup, cli } = options;
|
|
62
|
+
const head = `set -e; REPO=$(git rev-parse --show-toplevel) || { echo "[infracraft] not inside a git repository" >&2; exit 1; }`;
|
|
39
63
|
const runSetupAndCli = [setup, cli].filter(Boolean).join("; ");
|
|
40
|
-
if (
|
|
64
|
+
if (mode === "NONE") return `${head}; cd "$REPO"; ${runSetupAndCli}`;
|
|
41
65
|
const makeSandbox = [
|
|
42
66
|
`PROJECT=$(basename "$REPO")`,
|
|
43
|
-
`SANDBOX=$(mktemp -d "${SANDBOX_ROOT}/$PROJECT-${env ? `${env}-${appName}` : appName}.XXXXXX")`,
|
|
67
|
+
`SANDBOX=$(mktemp -d "${SANDBOX_ROOT}/$PROJECT-${env ? `${env}-${appName}` : appName}.XXXXXX") || { echo "[infracraft] mktemp failed" >&2; exit 1; }`,
|
|
44
68
|
`trap 'rm -rf "$SANDBOX"' EXIT`
|
|
45
69
|
].join("; ");
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
const copy = [
|
|
71
|
+
`git -C "$REPO" ls-files > "$SANDBOX/.ic-ls"`,
|
|
72
|
+
`${mode === "STUB" ? buildSandboxFileFilter(excludePaths) : "cat"} "$SANDBOX/.ic-ls" > "$SANDBOX/.ic-lsf"`,
|
|
73
|
+
`rsync -a --files-from="$SANDBOX/.ic-lsf" "$REPO"/ "$SANDBOX"/`,
|
|
74
|
+
`rm -f "$SANDBOX/.ic-ls" "$SANDBOX/.ic-lsf"`
|
|
75
|
+
].join("; ");
|
|
76
|
+
if (mode === "STUB") return `${head}; ${makeSandbox}; ${copy}; cd "\$SANDBOX"; git init -q && git add -A; ${runSetupAndCli}`;
|
|
77
|
+
return `${head}; ${makeSandbox}; ${copy}; cp -c -R "\$REPO/.git" "\$SANDBOX/.git" 2>/dev/null || cp -R "\$REPO/.git" "\$SANDBOX/.git"; cd "$SANDBOX"; ${runSetupAndCli}`;
|
|
48
78
|
}
|
|
49
79
|
/** Cross-bundle brand: `instanceof` is unreliable when the seam and the resource
|
|
50
80
|
* come from different built entries, so the seam detects sandboxes by this. */
|
|
51
81
|
const DEPLOY_SANDBOX_BRAND = Symbol.for("@infracraft/pulumi/DeploySandbox");
|
|
52
|
-
/** Sweep sandboxes orphaned by a hard-killed run (older than this).
|
|
53
|
-
|
|
82
|
+
/** Sweep sandboxes orphaned by a hard-killed run (older than this). Kept well
|
|
83
|
+
* above the worst-case deploy time (~30 min) but low enough that a SIGKILL
|
|
84
|
+
* orphan does not linger for a day accumulating in /tmp. */
|
|
85
|
+
const STALE_SANDBOX_MS = 10800 * 1e3;
|
|
54
86
|
/**
|
|
55
87
|
* Isolation marker + workspace lifecycle. Listing it in a deploy's `dependsOn`
|
|
56
88
|
* makes that deploy run in an isolated `/tmp/infracraft` copy. Carries no config;
|
|
@@ -93,6 +125,7 @@ function isDeploySandbox(value) {
|
|
|
93
125
|
|
|
94
126
|
//#endregion
|
|
95
127
|
exports.DeploySandbox = DeploySandbox;
|
|
128
|
+
exports.SandboxMode = SandboxMode;
|
|
96
129
|
exports.buildSandboxFileFilter = buildSandboxFileFilter;
|
|
97
130
|
exports.buildSandboxScript = buildSandboxScript;
|
|
98
131
|
exports.isDeploySandbox = isDeploySandbox;
|
package/dist/sandbox.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.cjs","names":["pulumi","fs","path"],"sources":["../src/sandbox.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Escapes a path literal for embedding inside an awk ERE. */\nfunction escapeAwkRegex(path: string): string {\n\treturn path.replace(/[.[\\]{}()*+?^$|\\\\/]/g, \"\\\\$&\");\n}\n\n/**\n * Builds the shell filter applied to
|
|
1
|
+
{"version":3,"file":"sandbox.cjs","names":["pulumi","fs","path"],"sources":["../src/sandbox.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Escapes a path literal for embedding inside an awk ERE. */\nfunction escapeAwkRegex(path: string): string {\n\treturn path.replace(/[.[\\]{}()*+?^$|\\\\/]/g, \"\\\\$&\");\n}\n\n/**\n * Builds the shell filter applied to the `git ls-files` list before\n * `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps\n * `apps/<x>/package.json` (the monorepo workspace graph needs it during build);\n * any other entry drops the path and its subtree. Returns `cat` (passthrough)\n * when nothing is excluded. Uses awk so it is portable across macOS and Linux.\n */\nexport function buildSandboxFileFilter(excludePaths: string[] = []): string {\n\tif (excludePaths.length === 0) {\n\t\treturn \"cat\";\n\t}\n\n\tconst clauses = excludePaths.map((entry) => {\n\t\tconst escaped = escapeAwkRegex(entry);\n\n\t\tif (entry.startsWith(\"apps/\")) {\n\t\t\treturn `!(/^${escaped}\\\\// && !/^${escaped}\\\\/package\\\\.json$/)`;\n\t\t}\n\n\t\treturn `!/^${escaped}(\\\\/|$)/`;\n\t});\n\n\treturn `awk '${clauses.join(\" && \")}'`;\n}\n\n/** Root of the per-deploy sandbox tree. The DeploySandbox resource GCs this. */\nconst SANDBOX_ROOT = \"/tmp/infracraft\";\n\n/**\n * How a deploy's working copy is isolated. A closed set, so it is an enum rather\n * than a pair of booleans: the seam derives it from `dependsOn` presence\n * (DeploySandbox / GitGuard), which makes the impossible \"git-guard without a\n * sandbox\" state unrepresentable here.\n */\nexport enum SandboxMode {\n\t/** No isolation — run the CLI in the live working tree. */\n\tNONE = \"NONE\",\n\t/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */\n\tORIGINAL = \"ORIGINAL\",\n\t/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */\n\tSTUB = \"STUB\",\n}\n\ninterface SandboxScriptOptions {\n\t/** How to isolate the working copy (see {@link SandboxMode}). */\n\tmode: SandboxMode;\n\t/** Resource-derived name, used in the sandbox dir prefix. */\n\tappName: string;\n\t/** Stack/environment name, prefixed to the sandbox dir so leftovers and\n\t * concurrent deploys are identifiable (e.g. `mlm-nvr-staging-nexus.XXXX`). */\n\tenv?: string;\n\t/** Upload-scoping excludes; applied only in STUB mode (see design spec). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Fully-formed platform deploy command (its exit code becomes the script's). */\n\tcli: string;\n}\n\n/**\n * Builds the shell for a deploy's `command.local.Command.create`. See\n * docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.\n *\n * Hardening: every script runs under POSIX `set -e` so any failed step aborts\n * instead of silently letting the platform CLI run against partial state.\n * `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which\n * may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing\n * `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits\n * 0), the copy is staged through intermediate list files rather than a pipe. The\n * `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear\n * error messages.\n */\nexport function buildSandboxScript(options: SandboxScriptOptions): string {\n\tconst { mode, appName, env, excludePaths, setup, cli } = options;\n\n\tconst head = `set -e; REPO=$(git rev-parse --show-toplevel) || { echo \"[infracraft] not inside a git repository\" >&2; exit 1; }`;\n\tconst runSetupAndCli = [setup, cli].filter(Boolean).join(\"; \");\n\n\tif (mode === SandboxMode.NONE) {\n\t\treturn `${head}; cd \"$REPO\"; ${runSetupAndCli}`;\n\t}\n\n\t// Prefix the dir with the project (repo folder name) and env so leftovers and\n\t// concurrent deploys — even across repos and stacks — are identifiable at a\n\t// glance (e.g. `mlm-nvr-production-railway-deploy-mesh.XXXX`).\n\tconst dirPrefix = env ? `${env}-${appName}` : appName;\n\n\tconst makeSandbox = [\n\t\t`PROJECT=$(basename \"$REPO\")`,\n\t\t`SANDBOX=$(mktemp -d \"${SANDBOX_ROOT}/$PROJECT-${dirPrefix}.XXXXXX\") || { echo \"[infracraft] mktemp failed\" >&2; exit 1; }`,\n\t\t`trap 'rm -rf \"$SANDBOX\"' EXIT`,\n\t].join(\"; \");\n\n\t// Copy tracked files via intermediate list files (no pipe — see hardening note).\n\t// STUB filters by excludePaths; ORIGINAL copies the full tracked tree (`cat`).\n\tconst filter =\n\t\tmode === SandboxMode.STUB ? buildSandboxFileFilter(excludePaths) : \"cat\";\n\n\tconst copy = [\n\t\t`git -C \"$REPO\" ls-files > \"$SANDBOX/.ic-ls\"`,\n\t\t`${filter} \"$SANDBOX/.ic-ls\" > \"$SANDBOX/.ic-lsf\"`,\n\t\t`rsync -a --files-from=\"$SANDBOX/.ic-lsf\" \"$REPO\"/ \"$SANDBOX\"/`,\n\t\t`rm -f \"$SANDBOX/.ic-ls\" \"$SANDBOX/.ic-lsf\"`,\n\t].join(\"; \");\n\n\tif (mode === SandboxMode.STUB) {\n\t\t// Metadata-free stub: unborn HEAD, no commit SHA / author reaches the platform.\n\t\tconst stubGit = `cd \"$SANDBOX\"; git init -q && git add -A`;\n\n\t\treturn `${head}; ${makeSandbox}; ${copy}; ${stubGit}; ${runSetupAndCli}`;\n\t}\n\n\t// ORIGINAL: real `.git`, CoW-copied (plain-copy fallback on non-CoW filesystems).\n\tconst copyGit =\n\t\t`cp -c -R \"$REPO/.git\" \"$SANDBOX/.git\" 2>/dev/null || ` +\n\t\t`cp -R \"$REPO/.git\" \"$SANDBOX/.git\"`;\n\n\treturn `${head}; ${makeSandbox}; ${copy}; ${copyGit}; cd \"$SANDBOX\"; ${runSetupAndCli}`;\n}\n\n/** Cross-bundle brand: `instanceof` is unreliable when the seam and the resource\n * come from different built entries, so the seam detects sandboxes by this. */\nconst DEPLOY_SANDBOX_BRAND = Symbol.for(\"@infracraft/pulumi/DeploySandbox\");\n\n/** Sweep sandboxes orphaned by a hard-killed run (older than this). Kept well\n * above the worst-case deploy time (~30 min) but low enough that a SIGKILL\n * orphan does not linger for a day accumulating in /tmp. */\nconst STALE_SANDBOX_MS = 3 * 60 * 60 * 1000;\n\n/**\n * Isolation marker + workspace lifecycle. Listing it in a deploy's `dependsOn`\n * makes that deploy run in an isolated `/tmp/infracraft` copy. Carries no config;\n * the repo root is derived at runtime by the deploy command.\n */\nexport class DeploySandbox extends pulumi.ComponentResource {\n\tconstructor(name: string, opts?: pulumi.ComponentResourceOptions) {\n\t\tsuper(\"infracraft:sandbox:DeploySandbox\", name, {}, opts);\n\n\t\t(this as Record<symbol, unknown>)[DEPLOY_SANDBOX_BRAND] = true;\n\n\t\tif (!pulumi.runtime.isDryRun()) {\n\t\t\tthis.prepareWorkspace();\n\t\t}\n\n\t\tthis.registerOutputs({});\n\t}\n\n\t/** mkdir the workspace root and GC stale sandboxes (best-effort). Sandboxes are\n\t * flat under the root (`/tmp/infracraft/<project>-<env>-<app>.XXXX`), so this\n\t * sweeps any entry older than the stale threshold. */\n\tprivate prepareWorkspace(): void {\n\t\tfs.mkdirSync(SANDBOX_ROOT, { recursive: true });\n\n\t\tlet entries: string[] = [];\n\n\t\ttry {\n\t\t\tentries = fs.readdirSync(SANDBOX_ROOT) as string[];\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = Date.now();\n\n\t\tfor (const entry of entries) {\n\t\t\tconst full = path.join(SANDBOX_ROOT, entry);\n\n\t\t\ttry {\n\t\t\t\tif (now - fs.statSync(full).mtimeMs > STALE_SANDBOX_MS) {\n\t\t\t\t\tfs.rmSync(full, { recursive: true, force: true });\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Racing with an in-flight deploy's cleanup — ignore.\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Bundle-safe check for a `DeploySandbox` in a `dependsOn` array. */\nexport function isDeploySandbox(value: unknown): value is DeploySandbox {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as Record<symbol, unknown>)[DEPLOY_SANDBOX_BRAND] === true\n\t);\n}\n"],"mappings":";;;;;;;;;;;AAKA,SAAS,eAAe,MAAsB;CAC7C,OAAO,KAAK,QAAQ,wBAAwB,MAAM;AACnD;;;;;;;;AASA,SAAgB,uBAAuB,eAAyB,CAAC,GAAW;CAC3E,IAAI,aAAa,WAAW,GAC3B,OAAO;CAaR,OAAO,QAVS,aAAa,KAAK,UAAU;EAC3C,MAAM,UAAU,eAAe,KAAK;EAEpC,IAAI,MAAM,WAAW,OAAO,GAC3B,OAAO,OAAO,QAAQ,aAAa,QAAQ;EAG5C,OAAO,MAAM,QAAQ;CACtB,CAEqB,EAAE,KAAK,MAAM,EAAE;AACrC;;AAGA,MAAM,eAAe;;;;;;;AAQrB,IAAY,cAAL;;CAEN;;CAEA;;CAEA;;AACD;;;;;;;;;;;;;;AA+BA,SAAgB,mBAAmB,SAAuC;CACzE,MAAM,EAAE,MAAM,SAAS,KAAK,cAAc,OAAO,QAAQ;CAEzD,MAAM,OAAO;CACb,MAAM,iBAAiB,CAAC,OAAO,GAAG,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;CAE7D,IAAI,iBACH,OAAO,GAAG,KAAK,gBAAgB;CAQhC,MAAM,cAAc;EACnB;EACA,wBAAwB,aAAa,YAJpB,MAAM,GAAG,IAAI,GAAG,YAAY,QAIc;EAC3D;CACD,EAAE,KAAK,IAAI;CAOX,MAAM,OAAO;EACZ;EACA,GAJA,kBAA4B,uBAAuB,YAAY,IAAI,MAIzD;EACV;EACA;CACD,EAAE,KAAK,IAAI;CAEX,IAAI,iBAIH,OAAO,GAAG,KAAK,IAAI,YAAY,IAAI,KAAK,+CAAgB;CAQzD,OAAO,GAAG,KAAK,IAAI,YAAY,IAAI,KAAK,gHAA+B;AACxE;;;AAIA,MAAM,uBAAuB,OAAO,IAAI,kCAAkC;;;;AAK1E,MAAM,mBAAmB,QAAc;;;;;;AAOvC,IAAa,gBAAb,cAAmCA,eAAO,kBAAkB;CAC3D,YAAY,MAAc,MAAwC;EACjE,MAAM,oCAAoC,MAAM,CAAC,GAAG,IAAI;EAExD,AAAC,KAAiC,wBAAwB;EAE1D,IAAI,CAACA,eAAO,QAAQ,SAAS,GAC5B,KAAK,iBAAiB;EAGvB,KAAK,gBAAgB,CAAC,CAAC;CACxB;;;;CAKA,AAAQ,mBAAyB;EAChC,QAAG,UAAU,cAAc,EAAE,WAAW,KAAK,CAAC;EAE9C,IAAI,UAAoB,CAAC;EAEzB,IAAI;GACH,UAAUC,QAAG,YAAY,YAAY;EACtC,QAAQ;GACP;EACD;EAEA,MAAM,MAAM,KAAK,IAAI;EAErB,KAAK,MAAM,SAAS,SAAS;GAC5B,MAAM,OAAOC,UAAK,KAAK,cAAc,KAAK;GAE1C,IAAI;IACH,IAAI,MAAMD,QAAG,SAAS,IAAI,EAAE,UAAU,kBACrC,QAAG,OAAO,MAAM;KAAE,WAAW;KAAM,OAAO;IAAK,CAAC;GAElD,QAAQ,CAER;EACD;CACD;AACD;;AAGA,SAAgB,gBAAgB,OAAwC;CACvE,OACC,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,0BAA0B;AAE/D"}
|
package/dist/sandbox.d.cts
CHANGED
|
@@ -3,24 +3,36 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
3
3
|
|
|
4
4
|
//#region src/sandbox.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Builds the shell filter applied to
|
|
7
|
-
*
|
|
6
|
+
* Builds the shell filter applied to the `git ls-files` list before
|
|
7
|
+
* `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps
|
|
8
8
|
* `apps/<x>/package.json` (the monorepo workspace graph needs it during build);
|
|
9
9
|
* any other entry drops the path and its subtree. Returns `cat` (passthrough)
|
|
10
10
|
* when nothing is excluded. Uses awk so it is portable across macOS and Linux.
|
|
11
11
|
*/
|
|
12
12
|
declare function buildSandboxFileFilter(excludePaths?: string[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* How a deploy's working copy is isolated. A closed set, so it is an enum rather
|
|
15
|
+
* than a pair of booleans: the seam derives it from `dependsOn` presence
|
|
16
|
+
* (DeploySandbox / GitGuard), which makes the impossible "git-guard without a
|
|
17
|
+
* sandbox" state unrepresentable here.
|
|
18
|
+
*/
|
|
19
|
+
declare enum SandboxMode {
|
|
20
|
+
/** No isolation — run the CLI in the live working tree. */
|
|
21
|
+
NONE = "NONE",
|
|
22
|
+
/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */
|
|
23
|
+
ORIGINAL = "ORIGINAL",
|
|
24
|
+
/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */
|
|
25
|
+
STUB = "STUB"
|
|
26
|
+
}
|
|
13
27
|
interface SandboxScriptOptions {
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/** Whether the sandbox `.git` is a metadata-free stub (true) or the real one. */
|
|
17
|
-
gitGuard: boolean;
|
|
28
|
+
/** How to isolate the working copy (see {@link SandboxMode}). */
|
|
29
|
+
mode: SandboxMode;
|
|
18
30
|
/** Resource-derived name, used in the sandbox dir prefix. */
|
|
19
31
|
appName: string;
|
|
20
32
|
/** Stack/environment name, prefixed to the sandbox dir so leftovers and
|
|
21
|
-
* concurrent deploys are identifiable (e.g. `
|
|
33
|
+
* concurrent deploys are identifiable (e.g. `mlm-nvr-staging-nexus.XXXX`). */
|
|
22
34
|
env?: string;
|
|
23
|
-
/** Upload-scoping excludes; applied only in
|
|
35
|
+
/** Upload-scoping excludes; applied only in STUB mode (see design spec). */
|
|
24
36
|
excludePaths?: string[];
|
|
25
37
|
/** Shell run in the working dir before `cli` (e.g. write railpack.json). */
|
|
26
38
|
setup?: string;
|
|
@@ -30,6 +42,15 @@ interface SandboxScriptOptions {
|
|
|
30
42
|
/**
|
|
31
43
|
* Builds the shell for a deploy's `command.local.Command.create`. See
|
|
32
44
|
* docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.
|
|
45
|
+
*
|
|
46
|
+
* Hardening: every script runs under POSIX `set -e` so any failed step aborts
|
|
47
|
+
* instead of silently letting the platform CLI run against partial state.
|
|
48
|
+
* `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which
|
|
49
|
+
* may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing
|
|
50
|
+
* `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits
|
|
51
|
+
* 0), the copy is staged through intermediate list files rather than a pipe. The
|
|
52
|
+
* `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear
|
|
53
|
+
* error messages.
|
|
33
54
|
*/
|
|
34
55
|
declare function buildSandboxScript(options: SandboxScriptOptions): string;
|
|
35
56
|
/**
|
|
@@ -47,5 +68,5 @@ declare class DeploySandbox extends pulumi.ComponentResource {
|
|
|
47
68
|
/** Bundle-safe check for a `DeploySandbox` in a `dependsOn` array. */
|
|
48
69
|
declare function isDeploySandbox(value: unknown): value is DeploySandbox;
|
|
49
70
|
//#endregion
|
|
50
|
-
export { DeploySandbox, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
71
|
+
export { DeploySandbox, SandboxMode, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
51
72
|
//# sourceMappingURL=sandbox.d.cts.map
|
package/dist/sandbox.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.cts","names":[],"sources":["../src/sandbox.ts"],"mappings":";;;;;;;AAgBA;;;;iBAAgB,sBAAA,CAAuB,YAA2B;AAAA,
|
|
1
|
+
{"version":3,"file":"sandbox.d.cts","names":[],"sources":["../src/sandbox.ts"],"mappings":";;;;;;;AAgBA;;;;iBAAgB,sBAAA,CAAuB,YAA2B;AA2BlE;;;;;;AAAA,aAAY,WAAA;EAMP;EAJJ,IAAA;EAOS;EALT,QAAA;;EAEA,IAAA;AAAA;AAAA,UAGS,oBAAA;EAIT;EAFA,IAAA,EAAM,WAAW;EAOjB;EALA,OAAA;EASA;;EANA,GAAA;EAsBe;EApBf,YAAA;;EAEA,KAAA;EAkB+D;EAhB/D,GAAA;AAAA;;;;;;;;;;;;AA8FwB;AA4BzB;iBA1GgB,kBAAA,CAAmB,OAA6B,EAApB,oBAAoB;;;;;;cA8DnD,aAAA,SAAsB,MAAA,CAAO,iBAAiB;cAC9C,IAAA,UAAc,IAAA,GAAO,MAAA,CAAO,wBAAA;;;;UAehC,gBAAA;AAAA;;iBA4BO,eAAA,CAAgB,KAAA,YAAiB,KAAA,IAAS,aAAa"}
|
package/dist/sandbox.d.mts
CHANGED
|
@@ -3,24 +3,36 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
3
3
|
|
|
4
4
|
//#region src/sandbox.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Builds the shell filter applied to
|
|
7
|
-
*
|
|
6
|
+
* Builds the shell filter applied to the `git ls-files` list before
|
|
7
|
+
* `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps
|
|
8
8
|
* `apps/<x>/package.json` (the monorepo workspace graph needs it during build);
|
|
9
9
|
* any other entry drops the path and its subtree. Returns `cat` (passthrough)
|
|
10
10
|
* when nothing is excluded. Uses awk so it is portable across macOS and Linux.
|
|
11
11
|
*/
|
|
12
12
|
declare function buildSandboxFileFilter(excludePaths?: string[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* How a deploy's working copy is isolated. A closed set, so it is an enum rather
|
|
15
|
+
* than a pair of booleans: the seam derives it from `dependsOn` presence
|
|
16
|
+
* (DeploySandbox / GitGuard), which makes the impossible "git-guard without a
|
|
17
|
+
* sandbox" state unrepresentable here.
|
|
18
|
+
*/
|
|
19
|
+
declare enum SandboxMode {
|
|
20
|
+
/** No isolation — run the CLI in the live working tree. */
|
|
21
|
+
NONE = "NONE",
|
|
22
|
+
/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */
|
|
23
|
+
ORIGINAL = "ORIGINAL",
|
|
24
|
+
/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */
|
|
25
|
+
STUB = "STUB"
|
|
26
|
+
}
|
|
13
27
|
interface SandboxScriptOptions {
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/** Whether the sandbox `.git` is a metadata-free stub (true) or the real one. */
|
|
17
|
-
gitGuard: boolean;
|
|
28
|
+
/** How to isolate the working copy (see {@link SandboxMode}). */
|
|
29
|
+
mode: SandboxMode;
|
|
18
30
|
/** Resource-derived name, used in the sandbox dir prefix. */
|
|
19
31
|
appName: string;
|
|
20
32
|
/** Stack/environment name, prefixed to the sandbox dir so leftovers and
|
|
21
|
-
* concurrent deploys are identifiable (e.g. `
|
|
33
|
+
* concurrent deploys are identifiable (e.g. `mlm-nvr-staging-nexus.XXXX`). */
|
|
22
34
|
env?: string;
|
|
23
|
-
/** Upload-scoping excludes; applied only in
|
|
35
|
+
/** Upload-scoping excludes; applied only in STUB mode (see design spec). */
|
|
24
36
|
excludePaths?: string[];
|
|
25
37
|
/** Shell run in the working dir before `cli` (e.g. write railpack.json). */
|
|
26
38
|
setup?: string;
|
|
@@ -30,6 +42,15 @@ interface SandboxScriptOptions {
|
|
|
30
42
|
/**
|
|
31
43
|
* Builds the shell for a deploy's `command.local.Command.create`. See
|
|
32
44
|
* docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.
|
|
45
|
+
*
|
|
46
|
+
* Hardening: every script runs under POSIX `set -e` so any failed step aborts
|
|
47
|
+
* instead of silently letting the platform CLI run against partial state.
|
|
48
|
+
* `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which
|
|
49
|
+
* may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing
|
|
50
|
+
* `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits
|
|
51
|
+
* 0), the copy is staged through intermediate list files rather than a pipe. The
|
|
52
|
+
* `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear
|
|
53
|
+
* error messages.
|
|
33
54
|
*/
|
|
34
55
|
declare function buildSandboxScript(options: SandboxScriptOptions): string;
|
|
35
56
|
/**
|
|
@@ -47,5 +68,5 @@ declare class DeploySandbox extends pulumi.ComponentResource {
|
|
|
47
68
|
/** Bundle-safe check for a `DeploySandbox` in a `dependsOn` array. */
|
|
48
69
|
declare function isDeploySandbox(value: unknown): value is DeploySandbox;
|
|
49
70
|
//#endregion
|
|
50
|
-
export { DeploySandbox, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
71
|
+
export { DeploySandbox, SandboxMode, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
51
72
|
//# sourceMappingURL=sandbox.d.mts.map
|
package/dist/sandbox.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.mts","names":[],"sources":["../src/sandbox.ts"],"mappings":";;;;;;;AAgBA;;;;iBAAgB,sBAAA,CAAuB,YAA2B;AAAA,
|
|
1
|
+
{"version":3,"file":"sandbox.d.mts","names":[],"sources":["../src/sandbox.ts"],"mappings":";;;;;;;AAgBA;;;;iBAAgB,sBAAA,CAAuB,YAA2B;AA2BlE;;;;;;AAAA,aAAY,WAAA;EAMP;EAJJ,IAAA;EAOS;EALT,QAAA;;EAEA,IAAA;AAAA;AAAA,UAGS,oBAAA;EAIT;EAFA,IAAA,EAAM,WAAW;EAOjB;EALA,OAAA;EASA;;EANA,GAAA;EAsBe;EApBf,YAAA;;EAEA,KAAA;EAkB+D;EAhB/D,GAAA;AAAA;;;;;;;;;;;;AA8FwB;AA4BzB;iBA1GgB,kBAAA,CAAmB,OAA6B,EAApB,oBAAoB;;;;;;cA8DnD,aAAA,SAAsB,MAAA,CAAO,iBAAiB;cAC9C,IAAA,UAAc,IAAA,GAAO,MAAA,CAAO,wBAAA;;;;UAehC,gBAAA;AAAA;;iBA4BO,eAAA,CAAgB,KAAA,YAAiB,KAAA,IAAS,aAAa"}
|
package/dist/sandbox.mjs
CHANGED
|
@@ -9,8 +9,8 @@ function escapeAwkRegex(path) {
|
|
|
9
9
|
return path.replace(/[.[\]{}()*+?^$|\\/]/g, "\\$&");
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Builds the shell filter applied to
|
|
13
|
-
*
|
|
12
|
+
* Builds the shell filter applied to the `git ls-files` list before
|
|
13
|
+
* `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps
|
|
14
14
|
* `apps/<x>/package.json` (the monorepo workspace graph needs it during build);
|
|
15
15
|
* any other entry drops the path and its subtree. Returns `cat` (passthrough)
|
|
16
16
|
* when nothing is excluded. Uses awk so it is portable across macOS and Linux.
|
|
@@ -26,27 +26,59 @@ function buildSandboxFileFilter(excludePaths = []) {
|
|
|
26
26
|
/** Root of the per-deploy sandbox tree. The DeploySandbox resource GCs this. */
|
|
27
27
|
const SANDBOX_ROOT = "/tmp/infracraft";
|
|
28
28
|
/**
|
|
29
|
+
* How a deploy's working copy is isolated. A closed set, so it is an enum rather
|
|
30
|
+
* than a pair of booleans: the seam derives it from `dependsOn` presence
|
|
31
|
+
* (DeploySandbox / GitGuard), which makes the impossible "git-guard without a
|
|
32
|
+
* sandbox" state unrepresentable here.
|
|
33
|
+
*/
|
|
34
|
+
let SandboxMode = /* @__PURE__ */ function(SandboxMode) {
|
|
35
|
+
/** No isolation — run the CLI in the live working tree. */
|
|
36
|
+
SandboxMode["NONE"] = "NONE";
|
|
37
|
+
/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */
|
|
38
|
+
SandboxMode["ORIGINAL"] = "ORIGINAL";
|
|
39
|
+
/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */
|
|
40
|
+
SandboxMode["STUB"] = "STUB";
|
|
41
|
+
return SandboxMode;
|
|
42
|
+
}({});
|
|
43
|
+
/**
|
|
29
44
|
* Builds the shell for a deploy's `command.local.Command.create`. See
|
|
30
45
|
* docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.
|
|
46
|
+
*
|
|
47
|
+
* Hardening: every script runs under POSIX `set -e` so any failed step aborts
|
|
48
|
+
* instead of silently letting the platform CLI run against partial state.
|
|
49
|
+
* `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which
|
|
50
|
+
* may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing
|
|
51
|
+
* `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits
|
|
52
|
+
* 0), the copy is staged through intermediate list files rather than a pipe. The
|
|
53
|
+
* `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear
|
|
54
|
+
* error messages.
|
|
31
55
|
*/
|
|
32
56
|
function buildSandboxScript(options) {
|
|
33
|
-
const {
|
|
34
|
-
const head = `REPO=$(git rev-parse --show-toplevel)`;
|
|
57
|
+
const { mode, appName, env, excludePaths, setup, cli } = options;
|
|
58
|
+
const head = `set -e; REPO=$(git rev-parse --show-toplevel) || { echo "[infracraft] not inside a git repository" >&2; exit 1; }`;
|
|
35
59
|
const runSetupAndCli = [setup, cli].filter(Boolean).join("; ");
|
|
36
|
-
if (
|
|
60
|
+
if (mode === "NONE") return `${head}; cd "$REPO"; ${runSetupAndCli}`;
|
|
37
61
|
const makeSandbox = [
|
|
38
62
|
`PROJECT=$(basename "$REPO")`,
|
|
39
|
-
`SANDBOX=$(mktemp -d "${SANDBOX_ROOT}/$PROJECT-${env ? `${env}-${appName}` : appName}.XXXXXX")`,
|
|
63
|
+
`SANDBOX=$(mktemp -d "${SANDBOX_ROOT}/$PROJECT-${env ? `${env}-${appName}` : appName}.XXXXXX") || { echo "[infracraft] mktemp failed" >&2; exit 1; }`,
|
|
40
64
|
`trap 'rm -rf "$SANDBOX"' EXIT`
|
|
41
65
|
].join("; ");
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
const copy = [
|
|
67
|
+
`git -C "$REPO" ls-files > "$SANDBOX/.ic-ls"`,
|
|
68
|
+
`${mode === "STUB" ? buildSandboxFileFilter(excludePaths) : "cat"} "$SANDBOX/.ic-ls" > "$SANDBOX/.ic-lsf"`,
|
|
69
|
+
`rsync -a --files-from="$SANDBOX/.ic-lsf" "$REPO"/ "$SANDBOX"/`,
|
|
70
|
+
`rm -f "$SANDBOX/.ic-ls" "$SANDBOX/.ic-lsf"`
|
|
71
|
+
].join("; ");
|
|
72
|
+
if (mode === "STUB") return `${head}; ${makeSandbox}; ${copy}; cd "\$SANDBOX"; git init -q && git add -A; ${runSetupAndCli}`;
|
|
73
|
+
return `${head}; ${makeSandbox}; ${copy}; cp -c -R "\$REPO/.git" "\$SANDBOX/.git" 2>/dev/null || cp -R "\$REPO/.git" "\$SANDBOX/.git"; cd "$SANDBOX"; ${runSetupAndCli}`;
|
|
44
74
|
}
|
|
45
75
|
/** Cross-bundle brand: `instanceof` is unreliable when the seam and the resource
|
|
46
76
|
* come from different built entries, so the seam detects sandboxes by this. */
|
|
47
77
|
const DEPLOY_SANDBOX_BRAND = Symbol.for("@infracraft/pulumi/DeploySandbox");
|
|
48
|
-
/** Sweep sandboxes orphaned by a hard-killed run (older than this).
|
|
49
|
-
|
|
78
|
+
/** Sweep sandboxes orphaned by a hard-killed run (older than this). Kept well
|
|
79
|
+
* above the worst-case deploy time (~30 min) but low enough that a SIGKILL
|
|
80
|
+
* orphan does not linger for a day accumulating in /tmp. */
|
|
81
|
+
const STALE_SANDBOX_MS = 10800 * 1e3;
|
|
50
82
|
/**
|
|
51
83
|
* Isolation marker + workspace lifecycle. Listing it in a deploy's `dependsOn`
|
|
52
84
|
* makes that deploy run in an isolated `/tmp/infracraft` copy. Carries no config;
|
|
@@ -88,5 +120,5 @@ function isDeploySandbox(value) {
|
|
|
88
120
|
}
|
|
89
121
|
|
|
90
122
|
//#endregion
|
|
91
|
-
export { DeploySandbox, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
123
|
+
export { DeploySandbox, SandboxMode, buildSandboxFileFilter, buildSandboxScript, isDeploySandbox };
|
|
92
124
|
//# sourceMappingURL=sandbox.mjs.map
|
package/dist/sandbox.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.mjs","names":[],"sources":["../src/sandbox.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Escapes a path literal for embedding inside an awk ERE. */\nfunction escapeAwkRegex(path: string): string {\n\treturn path.replace(/[.[\\]{}()*+?^$|\\\\/]/g, \"\\\\$&\");\n}\n\n/**\n * Builds the shell filter applied to
|
|
1
|
+
{"version":3,"file":"sandbox.mjs","names":[],"sources":["../src/sandbox.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Escapes a path literal for embedding inside an awk ERE. */\nfunction escapeAwkRegex(path: string): string {\n\treturn path.replace(/[.[\\]{}()*+?^$|\\\\/]/g, \"\\\\$&\");\n}\n\n/**\n * Builds the shell filter applied to the `git ls-files` list before\n * `rsync --files-from`. For `apps/<x>` it drops the app's files but keeps\n * `apps/<x>/package.json` (the monorepo workspace graph needs it during build);\n * any other entry drops the path and its subtree. Returns `cat` (passthrough)\n * when nothing is excluded. Uses awk so it is portable across macOS and Linux.\n */\nexport function buildSandboxFileFilter(excludePaths: string[] = []): string {\n\tif (excludePaths.length === 0) {\n\t\treturn \"cat\";\n\t}\n\n\tconst clauses = excludePaths.map((entry) => {\n\t\tconst escaped = escapeAwkRegex(entry);\n\n\t\tif (entry.startsWith(\"apps/\")) {\n\t\t\treturn `!(/^${escaped}\\\\// && !/^${escaped}\\\\/package\\\\.json$/)`;\n\t\t}\n\n\t\treturn `!/^${escaped}(\\\\/|$)/`;\n\t});\n\n\treturn `awk '${clauses.join(\" && \")}'`;\n}\n\n/** Root of the per-deploy sandbox tree. The DeploySandbox resource GCs this. */\nconst SANDBOX_ROOT = \"/tmp/infracraft\";\n\n/**\n * How a deploy's working copy is isolated. A closed set, so it is an enum rather\n * than a pair of booleans: the seam derives it from `dependsOn` presence\n * (DeploySandbox / GitGuard), which makes the impossible \"git-guard without a\n * sandbox\" state unrepresentable here.\n */\nexport enum SandboxMode {\n\t/** No isolation — run the CLI in the live working tree. */\n\tNONE = \"NONE\",\n\t/** Isolated copy carrying the repo's REAL `.git` (real commit/author exposed). */\n\tORIGINAL = \"ORIGINAL\",\n\t/** Isolated copy with a metadata-free stub `.git` (no commit SHA / author). */\n\tSTUB = \"STUB\",\n}\n\ninterface SandboxScriptOptions {\n\t/** How to isolate the working copy (see {@link SandboxMode}). */\n\tmode: SandboxMode;\n\t/** Resource-derived name, used in the sandbox dir prefix. */\n\tappName: string;\n\t/** Stack/environment name, prefixed to the sandbox dir so leftovers and\n\t * concurrent deploys are identifiable (e.g. `mlm-nvr-staging-nexus.XXXX`). */\n\tenv?: string;\n\t/** Upload-scoping excludes; applied only in STUB mode (see design spec). */\n\texcludePaths?: string[];\n\t/** Shell run in the working dir before `cli` (e.g. write railpack.json). */\n\tsetup?: string;\n\t/** Fully-formed platform deploy command (its exit code becomes the script's). */\n\tcli: string;\n}\n\n/**\n * Builds the shell for a deploy's `command.local.Command.create`. See\n * docs/superpowers/specs/2026-06-05-deploy-sandbox-design.md for the modes.\n *\n * Hardening: every script runs under POSIX `set -e` so any failed step aborts\n * instead of silently letting the platform CLI run against partial state.\n * `pipefail` is intentionally NOT used — the command runs under `/bin/sh`, which\n * may be `dash` (pipefail is a bash/ksh/zsh extension). To still catch a failing\n * `git ls-files` (a pipe would mask it, since an `rsync` of an empty list exits\n * 0), the copy is staged through intermediate list files rather than a pipe. The\n * `REPO` and `SANDBOX` command substitutions are explicitly guarded with clear\n * error messages.\n */\nexport function buildSandboxScript(options: SandboxScriptOptions): string {\n\tconst { mode, appName, env, excludePaths, setup, cli } = options;\n\n\tconst head = `set -e; REPO=$(git rev-parse --show-toplevel) || { echo \"[infracraft] not inside a git repository\" >&2; exit 1; }`;\n\tconst runSetupAndCli = [setup, cli].filter(Boolean).join(\"; \");\n\n\tif (mode === SandboxMode.NONE) {\n\t\treturn `${head}; cd \"$REPO\"; ${runSetupAndCli}`;\n\t}\n\n\t// Prefix the dir with the project (repo folder name) and env so leftovers and\n\t// concurrent deploys — even across repos and stacks — are identifiable at a\n\t// glance (e.g. `mlm-nvr-production-railway-deploy-mesh.XXXX`).\n\tconst dirPrefix = env ? `${env}-${appName}` : appName;\n\n\tconst makeSandbox = [\n\t\t`PROJECT=$(basename \"$REPO\")`,\n\t\t`SANDBOX=$(mktemp -d \"${SANDBOX_ROOT}/$PROJECT-${dirPrefix}.XXXXXX\") || { echo \"[infracraft] mktemp failed\" >&2; exit 1; }`,\n\t\t`trap 'rm -rf \"$SANDBOX\"' EXIT`,\n\t].join(\"; \");\n\n\t// Copy tracked files via intermediate list files (no pipe — see hardening note).\n\t// STUB filters by excludePaths; ORIGINAL copies the full tracked tree (`cat`).\n\tconst filter =\n\t\tmode === SandboxMode.STUB ? buildSandboxFileFilter(excludePaths) : \"cat\";\n\n\tconst copy = [\n\t\t`git -C \"$REPO\" ls-files > \"$SANDBOX/.ic-ls\"`,\n\t\t`${filter} \"$SANDBOX/.ic-ls\" > \"$SANDBOX/.ic-lsf\"`,\n\t\t`rsync -a --files-from=\"$SANDBOX/.ic-lsf\" \"$REPO\"/ \"$SANDBOX\"/`,\n\t\t`rm -f \"$SANDBOX/.ic-ls\" \"$SANDBOX/.ic-lsf\"`,\n\t].join(\"; \");\n\n\tif (mode === SandboxMode.STUB) {\n\t\t// Metadata-free stub: unborn HEAD, no commit SHA / author reaches the platform.\n\t\tconst stubGit = `cd \"$SANDBOX\"; git init -q && git add -A`;\n\n\t\treturn `${head}; ${makeSandbox}; ${copy}; ${stubGit}; ${runSetupAndCli}`;\n\t}\n\n\t// ORIGINAL: real `.git`, CoW-copied (plain-copy fallback on non-CoW filesystems).\n\tconst copyGit =\n\t\t`cp -c -R \"$REPO/.git\" \"$SANDBOX/.git\" 2>/dev/null || ` +\n\t\t`cp -R \"$REPO/.git\" \"$SANDBOX/.git\"`;\n\n\treturn `${head}; ${makeSandbox}; ${copy}; ${copyGit}; cd \"$SANDBOX\"; ${runSetupAndCli}`;\n}\n\n/** Cross-bundle brand: `instanceof` is unreliable when the seam and the resource\n * come from different built entries, so the seam detects sandboxes by this. */\nconst DEPLOY_SANDBOX_BRAND = Symbol.for(\"@infracraft/pulumi/DeploySandbox\");\n\n/** Sweep sandboxes orphaned by a hard-killed run (older than this). Kept well\n * above the worst-case deploy time (~30 min) but low enough that a SIGKILL\n * orphan does not linger for a day accumulating in /tmp. */\nconst STALE_SANDBOX_MS = 3 * 60 * 60 * 1000;\n\n/**\n * Isolation marker + workspace lifecycle. Listing it in a deploy's `dependsOn`\n * makes that deploy run in an isolated `/tmp/infracraft` copy. Carries no config;\n * the repo root is derived at runtime by the deploy command.\n */\nexport class DeploySandbox extends pulumi.ComponentResource {\n\tconstructor(name: string, opts?: pulumi.ComponentResourceOptions) {\n\t\tsuper(\"infracraft:sandbox:DeploySandbox\", name, {}, opts);\n\n\t\t(this as Record<symbol, unknown>)[DEPLOY_SANDBOX_BRAND] = true;\n\n\t\tif (!pulumi.runtime.isDryRun()) {\n\t\t\tthis.prepareWorkspace();\n\t\t}\n\n\t\tthis.registerOutputs({});\n\t}\n\n\t/** mkdir the workspace root and GC stale sandboxes (best-effort). Sandboxes are\n\t * flat under the root (`/tmp/infracraft/<project>-<env>-<app>.XXXX`), so this\n\t * sweeps any entry older than the stale threshold. */\n\tprivate prepareWorkspace(): void {\n\t\tfs.mkdirSync(SANDBOX_ROOT, { recursive: true });\n\n\t\tlet entries: string[] = [];\n\n\t\ttry {\n\t\t\tentries = fs.readdirSync(SANDBOX_ROOT) as string[];\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = Date.now();\n\n\t\tfor (const entry of entries) {\n\t\t\tconst full = path.join(SANDBOX_ROOT, entry);\n\n\t\t\ttry {\n\t\t\t\tif (now - fs.statSync(full).mtimeMs > STALE_SANDBOX_MS) {\n\t\t\t\t\tfs.rmSync(full, { recursive: true, force: true });\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Racing with an in-flight deploy's cleanup — ignore.\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Bundle-safe check for a `DeploySandbox` in a `dependsOn` array. */\nexport function isDeploySandbox(value: unknown): value is DeploySandbox {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t(value as Record<symbol, unknown>)[DEPLOY_SANDBOX_BRAND] === true\n\t);\n}\n"],"mappings":";;;;;;;AAKA,SAAS,eAAe,MAAsB;CAC7C,OAAO,KAAK,QAAQ,wBAAwB,MAAM;AACnD;;;;;;;;AASA,SAAgB,uBAAuB,eAAyB,CAAC,GAAW;CAC3E,IAAI,aAAa,WAAW,GAC3B,OAAO;CAaR,OAAO,QAVS,aAAa,KAAK,UAAU;EAC3C,MAAM,UAAU,eAAe,KAAK;EAEpC,IAAI,MAAM,WAAW,OAAO,GAC3B,OAAO,OAAO,QAAQ,aAAa,QAAQ;EAG5C,OAAO,MAAM,QAAQ;CACtB,CAEqB,EAAE,KAAK,MAAM,EAAE;AACrC;;AAGA,MAAM,eAAe;;;;;;;AAQrB,IAAY,cAAL;;CAEN;;CAEA;;CAEA;;AACD;;;;;;;;;;;;;;AA+BA,SAAgB,mBAAmB,SAAuC;CACzE,MAAM,EAAE,MAAM,SAAS,KAAK,cAAc,OAAO,QAAQ;CAEzD,MAAM,OAAO;CACb,MAAM,iBAAiB,CAAC,OAAO,GAAG,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;CAE7D,IAAI,iBACH,OAAO,GAAG,KAAK,gBAAgB;CAQhC,MAAM,cAAc;EACnB;EACA,wBAAwB,aAAa,YAJpB,MAAM,GAAG,IAAI,GAAG,YAAY,QAIc;EAC3D;CACD,EAAE,KAAK,IAAI;CAOX,MAAM,OAAO;EACZ;EACA,GAJA,kBAA4B,uBAAuB,YAAY,IAAI,MAIzD;EACV;EACA;CACD,EAAE,KAAK,IAAI;CAEX,IAAI,iBAIH,OAAO,GAAG,KAAK,IAAI,YAAY,IAAI,KAAK,+CAAgB;CAQzD,OAAO,GAAG,KAAK,IAAI,YAAY,IAAI,KAAK,gHAA+B;AACxE;;;AAIA,MAAM,uBAAuB,OAAO,IAAI,kCAAkC;;;;AAK1E,MAAM,mBAAmB,QAAc;;;;;;AAOvC,IAAa,gBAAb,cAAmC,OAAO,kBAAkB;CAC3D,YAAY,MAAc,MAAwC;EACjE,MAAM,oCAAoC,MAAM,CAAC,GAAG,IAAI;EAExD,AAAC,KAAiC,wBAAwB;EAE1D,IAAI,CAAC,OAAO,QAAQ,SAAS,GAC5B,KAAK,iBAAiB;EAGvB,KAAK,gBAAgB,CAAC,CAAC;CACxB;;;;CAKA,AAAQ,mBAAyB;EAChC,GAAG,UAAU,cAAc,EAAE,WAAW,KAAK,CAAC;EAE9C,IAAI,UAAoB,CAAC;EAEzB,IAAI;GACH,UAAU,GAAG,YAAY,YAAY;EACtC,QAAQ;GACP;EACD;EAEA,MAAM,MAAM,KAAK,IAAI;EAErB,KAAK,MAAM,SAAS,SAAS;GAC5B,MAAM,OAAO,KAAK,KAAK,cAAc,KAAK;GAE1C,IAAI;IACH,IAAI,MAAM,GAAG,SAAS,IAAI,EAAE,UAAU,kBACrC,GAAG,OAAO,MAAM;KAAE,WAAW;KAAM,OAAO;IAAK,CAAC;GAElD,QAAQ,CAER;EACD;CACD;AACD;;AAGA,SAAgB,gBAAgB,OAAwC;CACvE,OACC,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,0BAA0B;AAE/D"}
|