redkite 0.1.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.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +819 -0
  3. package/bin/redkite.mjs +12 -0
  4. package/dist/build.d.ts +27 -0
  5. package/dist/build.d.ts.map +1 -0
  6. package/dist/build.js +132 -0
  7. package/dist/build.js.map +1 -0
  8. package/dist/checks.d.ts +4 -0
  9. package/dist/checks.d.ts.map +1 -0
  10. package/dist/checks.js +49 -0
  11. package/dist/checks.js.map +1 -0
  12. package/dist/cli/agent.d.ts +2 -0
  13. package/dist/cli/agent.d.ts.map +1 -0
  14. package/dist/cli/agent.js +24 -0
  15. package/dist/cli/agent.js.map +1 -0
  16. package/dist/cli/config.d.ts +5 -0
  17. package/dist/cli/config.d.ts.map +1 -0
  18. package/dist/cli/config.js +217 -0
  19. package/dist/cli/config.js.map +1 -0
  20. package/dist/cli/index.d.ts +11 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +438 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/log.d.ts +9 -0
  25. package/dist/cli/log.d.ts.map +1 -0
  26. package/dist/cli/log.js +128 -0
  27. package/dist/cli/log.js.map +1 -0
  28. package/dist/cli/screen.d.ts +34 -0
  29. package/dist/cli/screen.d.ts.map +1 -0
  30. package/dist/cli/screen.js +298 -0
  31. package/dist/cli/screen.js.map +1 -0
  32. package/dist/cli/viewer.d.ts +9 -0
  33. package/dist/cli/viewer.d.ts.map +1 -0
  34. package/dist/cli/viewer.js +139 -0
  35. package/dist/cli/viewer.js.map +1 -0
  36. package/dist/config.d.ts +7 -0
  37. package/dist/config.d.ts.map +1 -0
  38. package/dist/config.js +79 -0
  39. package/dist/config.js.map +1 -0
  40. package/dist/deploy.d.ts +19 -0
  41. package/dist/deploy.d.ts.map +1 -0
  42. package/dist/deploy.js +275 -0
  43. package/dist/deploy.js.map +1 -0
  44. package/dist/docker.d.ts +96 -0
  45. package/dist/docker.d.ts.map +1 -0
  46. package/dist/docker.js +325 -0
  47. package/dist/docker.js.map +1 -0
  48. package/dist/dockerfile.d.ts +14 -0
  49. package/dist/dockerfile.d.ts.map +1 -0
  50. package/dist/dockerfile.js +160 -0
  51. package/dist/dockerfile.js.map +1 -0
  52. package/dist/health.d.ts +13 -0
  53. package/dist/health.d.ts.map +1 -0
  54. package/dist/health.js +54 -0
  55. package/dist/health.js.map +1 -0
  56. package/dist/host.d.ts +21 -0
  57. package/dist/host.d.ts.map +1 -0
  58. package/dist/host.js +27 -0
  59. package/dist/host.js.map +1 -0
  60. package/dist/index.d.ts +26 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +26 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/layout.d.ts +5 -0
  65. package/dist/layout.d.ts.map +1 -0
  66. package/dist/layout.js +45 -0
  67. package/dist/layout.js.map +1 -0
  68. package/dist/localHost.d.ts +7 -0
  69. package/dist/localHost.d.ts.map +1 -0
  70. package/dist/localHost.js +32 -0
  71. package/dist/localHost.js.map +1 -0
  72. package/dist/log.d.ts +14 -0
  73. package/dist/log.d.ts.map +1 -0
  74. package/dist/log.js +15 -0
  75. package/dist/log.js.map +1 -0
  76. package/dist/nginx.d.ts +4 -0
  77. package/dist/nginx.d.ts.map +1 -0
  78. package/dist/nginx.js +53 -0
  79. package/dist/nginx.js.map +1 -0
  80. package/dist/pipeline.d.ts +103 -0
  81. package/dist/pipeline.d.ts.map +1 -0
  82. package/dist/pipeline.js +142 -0
  83. package/dist/pipeline.js.map +1 -0
  84. package/dist/presets/index.d.ts +3 -0
  85. package/dist/presets/index.d.ts.map +1 -0
  86. package/dist/presets/index.js +3 -0
  87. package/dist/presets/index.js.map +1 -0
  88. package/dist/presets/nextApp.d.ts +15 -0
  89. package/dist/presets/nextApp.d.ts.map +1 -0
  90. package/dist/presets/nextApp.js +53 -0
  91. package/dist/presets/nextApp.js.map +1 -0
  92. package/dist/presets/nodeApp.d.ts +19 -0
  93. package/dist/presets/nodeApp.d.ts.map +1 -0
  94. package/dist/presets/nodeApp.js +29 -0
  95. package/dist/presets/nodeApp.js.map +1 -0
  96. package/dist/secrets/bitwarden.d.ts +3 -0
  97. package/dist/secrets/bitwarden.d.ts.map +1 -0
  98. package/dist/secrets/bitwarden.js +6 -0
  99. package/dist/secrets/bitwarden.js.map +1 -0
  100. package/dist/secrets/refs.d.ts +9 -0
  101. package/dist/secrets/refs.d.ts.map +1 -0
  102. package/dist/secrets/refs.js +20 -0
  103. package/dist/secrets/refs.js.map +1 -0
  104. package/dist/secrets/store.d.ts +9 -0
  105. package/dist/secrets/store.d.ts.map +1 -0
  106. package/dist/secrets/store.js +104 -0
  107. package/dist/secrets/store.js.map +1 -0
  108. package/dist/services/ensure.d.ts +18 -0
  109. package/dist/services/ensure.d.ts.map +1 -0
  110. package/dist/services/ensure.js +93 -0
  111. package/dist/services/ensure.js.map +1 -0
  112. package/dist/services/index.d.ts +19 -0
  113. package/dist/services/index.d.ts.map +1 -0
  114. package/dist/services/index.js +27 -0
  115. package/dist/services/index.js.map +1 -0
  116. package/dist/services/planned.d.ts +20 -0
  117. package/dist/services/planned.d.ts.map +1 -0
  118. package/dist/services/planned.js +77 -0
  119. package/dist/services/planned.js.map +1 -0
  120. package/dist/shell.d.ts +11 -0
  121. package/dist/shell.d.ts.map +1 -0
  122. package/dist/shell.js +65 -0
  123. package/dist/shell.js.map +1 -0
  124. package/dist/source.d.ts +16 -0
  125. package/dist/source.d.ts.map +1 -0
  126. package/dist/source.js +108 -0
  127. package/dist/source.js.map +1 -0
  128. package/dist/sshHost.d.ts +15 -0
  129. package/dist/sshHost.d.ts.map +1 -0
  130. package/dist/sshHost.js +91 -0
  131. package/dist/sshHost.js.map +1 -0
  132. package/dist/steps.d.ts +27 -0
  133. package/dist/steps.d.ts.map +1 -0
  134. package/dist/steps.js +69 -0
  135. package/dist/steps.js.map +1 -0
  136. package/dist/topology.d.ts +40 -0
  137. package/dist/topology.d.ts.map +1 -0
  138. package/dist/topology.js +113 -0
  139. package/dist/topology.js.map +1 -0
  140. package/dist/types.d.ts +100 -0
  141. package/dist/types.d.ts.map +1 -0
  142. package/dist/types.js +2 -0
  143. package/dist/types.js.map +1 -0
  144. package/package.json +64 -0
  145. package/src/build.ts +236 -0
  146. package/src/checks.ts +65 -0
  147. package/src/cli/agent.ts +28 -0
  148. package/src/cli/config.ts +275 -0
  149. package/src/cli/index.ts +559 -0
  150. package/src/cli/log.ts +168 -0
  151. package/src/cli/screen.ts +409 -0
  152. package/src/cli/viewer.ts +192 -0
  153. package/src/config.ts +90 -0
  154. package/src/deploy.ts +409 -0
  155. package/src/docker.ts +433 -0
  156. package/src/dockerfile.ts +228 -0
  157. package/src/health.ts +83 -0
  158. package/src/host.ts +54 -0
  159. package/src/index.ts +25 -0
  160. package/src/layout.ts +41 -0
  161. package/src/localHost.ts +50 -0
  162. package/src/log.ts +38 -0
  163. package/src/nginx.ts +63 -0
  164. package/src/pipeline.ts +335 -0
  165. package/src/presets/index.ts +2 -0
  166. package/src/presets/nextApp.ts +70 -0
  167. package/src/presets/nodeApp.ts +46 -0
  168. package/src/secrets/bitwarden.ts +7 -0
  169. package/src/secrets/refs.ts +32 -0
  170. package/src/secrets/store.ts +147 -0
  171. package/src/services/ensure.ts +160 -0
  172. package/src/services/index.ts +48 -0
  173. package/src/services/planned.ts +119 -0
  174. package/src/shell.ts +95 -0
  175. package/src/source.ts +171 -0
  176. package/src/sshHost.ts +145 -0
  177. package/src/steps.ts +101 -0
  178. package/src/topology.ts +199 -0
  179. package/src/types.ts +243 -0
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../dist/cli/index.js";
3
+
4
+ // Node warns about a .ts config in a CommonJS package before redkite gets to say
5
+ // the same thing usefully, and its advice, .mjs, is wrong for a TypeScript file
6
+ process.removeAllListeners("warning");
7
+ process.on("warning", (warning) => {
8
+ if (warning.message.startsWith("Failed to load the ES module")) return;
9
+ console.warn(`${warning.name}: ${warning.message}`);
10
+ });
11
+
12
+ await main(process.argv.slice(2));
@@ -0,0 +1,27 @@
1
+ import type { Docker } from "./docker.js";
2
+ import type { Host } from "./host.js";
3
+ import type { AppTopology } from "./topology.js";
4
+ import type { AppSpec } from "./types.js";
5
+ export type BuildContext = {
6
+ host: Host;
7
+ docker: Docker;
8
+ deliver?: {
9
+ host: Host;
10
+ docker: Docker;
11
+ };
12
+ env: string;
13
+ files: Record<string, string>;
14
+ branch: string;
15
+ environment: string;
16
+ detail?: (message: string) => void;
17
+ output?: (line: string) => void;
18
+ };
19
+ export type BuildResult = {
20
+ release: string;
21
+ fingerprint: string;
22
+ tag: string;
23
+ builderTag: string;
24
+ cached: boolean;
25
+ };
26
+ export declare function build(app: AppSpec, topology: AppTopology, context: BuildContext): Promise<BuildResult>;
27
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IAGf,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzC,GAAG,EAAE,MAAM,CAAC;IAEZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAOF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAEhB,WAAW,EAAE,MAAM,CAAC;IAEpB,GAAG,EAAE,MAAM,CAAC;IAIZ,UAAU,EAAE,MAAM,CAAC;IAEnB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAsB,KAAK,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAuFtB"}
package/dist/build.js ADDED
@@ -0,0 +1,132 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { BUILDER_STAGE, renderDockerfile, renderDockerignore } from "./dockerfile.js";
4
+ import { prepareSource } from "./source.js";
5
+ // Bumped when the pipeline changes shape without the config changing. The tag
6
+ // below is what tells a host it already holds an image, and a host that trusts
7
+ // the commit alone serves the last pipeline's output forever
8
+ const PIPELINE = "4";
9
+ export async function build(app, topology, context) {
10
+ const { host, docker } = context;
11
+ const spec = app.build;
12
+ const detail = context.detail ?? (() => { });
13
+ const source = await prepareSource(host, {
14
+ name: topology.container,
15
+ repo: app.repo,
16
+ // Already absolute when the CLI loaded the config, because a path is read
17
+ // against the deployment file rather than wherever this was run
18
+ path: app.path && resolve(app.path),
19
+ include: app.include,
20
+ branch: context.branch,
21
+ submodules: spec.submodules,
22
+ detail,
23
+ });
24
+ const release = source.release;
25
+ const fingerprint = fingerprintOf(app, context, release);
26
+ const tag = `${topology.container}:${release}-${fingerprint}`;
27
+ // Always, rather than where something remembered to ask for it. It costs the
28
+ // export of layers the runtime build produced anyway, and a flag that has to
29
+ // be set before a step can run is a flag that will be missing
30
+ const builderTag = `${topology.container}-builder:${release}-${fingerprint}`;
31
+ // Asked of the daemon that will run the container, not the one that builds.
32
+ // An image this machine holds and the host does not is one still to be sent
33
+ const runner = context.deliver?.docker ?? docker;
34
+ // The whole build is skipped, not just a transfer. Nothing about this commit,
35
+ // this pipeline or these secrets differs from the image already sitting there
36
+ if (await held(runner, tag, builderTag)) {
37
+ detail(`already built at ${release.slice(0, 7)}`);
38
+ await runner.image.retag(tag, topology.container);
39
+ return { release, fingerprint, tag, builderTag, cached: true };
40
+ }
41
+ const secrets = await writeSecrets(app, context, fingerprint);
42
+ const dockerfile = await host.write(`${app.name}.Dockerfile`, renderDockerfile(spec, {
43
+ caches: topology.caches,
44
+ port: topology.port,
45
+ release,
46
+ environment: context.environment,
47
+ envSecret: secrets.env.id,
48
+ fileSecrets: Object.fromEntries(secrets.files.map((file) => [file.path, file.id])),
49
+ dir: app.dir,
50
+ }));
51
+ // BuildKit reads this beside the Dockerfile rather than inside the context,
52
+ // which is what lets the checkout stay a checkout: the repository keeps its
53
+ // own .dockerignore, and .git never enters the build
54
+ await host.write(`${app.name}.Dockerfile.dockerignore`, renderDockerignore(app.include));
55
+ const invocation = {
56
+ context: source.tree,
57
+ dockerfile,
58
+ secrets: Object.fromEntries([secrets.env, ...secrets.files].map((secret) => [secret.id, secret.src])),
59
+ };
60
+ detail(`building ${release.slice(0, 7)}`);
61
+ await docker.image.build({ ...invocation, tags: [tag, topology.container] }, watch(detail, context.output));
62
+ // Every layer of this was just built, so it costs the export alone
63
+ detail("keeping the builder");
64
+ await docker.image.build({ ...invocation, tags: [builderTag], target: BUILDER_STAGE }, watch(detail, context.output));
65
+ await ship(context, [tag, topology.container, builderTag], detail);
66
+ return { release, fingerprint, tag, builderTag, cached: false };
67
+ }
68
+ // One stream, rather than a tarball written here, copied, and read there. The
69
+ // tags travel inside the archive, so nothing has to be named again on arrival
70
+ async function ship(context, tags, detail) {
71
+ const deliver = context.deliver;
72
+ if (!deliver)
73
+ return;
74
+ const named = tags.filter((tag) => tag !== undefined);
75
+ detail(`shipping ${named.length} images`);
76
+ const result = await deliver.host.pipe(`docker save ${named.join(" ")}`, "docker load", context.output);
77
+ if (result.code !== 0) {
78
+ throw new Error(`Could not ship the image: ${result.stderr || result.stdout}`);
79
+ }
80
+ await deliver.docker.track((state) => {
81
+ for (const tag of named)
82
+ state.images.add(tag);
83
+ });
84
+ }
85
+ async function held(docker, tag, builderTag) {
86
+ if (!(await docker.image.exists(tag)))
87
+ return false;
88
+ // A runtime image without the builder that produced it would run the
89
+ // migration from whatever release happened to be tagged last
90
+ return await docker.image.exists(builderTag);
91
+ }
92
+ // The fingerprint is part of every id, because a secret mount is not part of a
93
+ // layer's cache key. Without it a changed environment file is answered with the
94
+ // image that was built from the old one
95
+ async function writeSecrets(app, context, fingerprint) {
96
+ const env = {
97
+ id: `${app.name}-env-${fingerprint}`,
98
+ src: await context.host.write(`${app.name}.env`, context.env),
99
+ };
100
+ const files = await Promise.all(Object.entries(context.files).map(async ([path, contents], index) => ({
101
+ path,
102
+ id: `${app.name}-file-${index}-${fingerprint}`,
103
+ // Named by index, so a container path with slashes in it stays one file
104
+ src: await context.host.write(`${app.name}.file.${index}`, contents),
105
+ })));
106
+ return { env, files };
107
+ }
108
+ // BuildKit names the step it is on in its own progress output, which is a
109
+ // better line to show than anything this file could invent
110
+ const STEP = /^#\d+ \[[^\]]*\] (.+)$/;
111
+ function watch(detail, output) {
112
+ return (line) => {
113
+ output?.(line);
114
+ const step = STEP.exec(line)?.[1];
115
+ if (step)
116
+ detail(step);
117
+ };
118
+ }
119
+ // The commit is one input of several. The pipeline, the environment file and
120
+ // any credential baked in as a file all change the image without touching it
121
+ function fingerprintOf(app, context, release) {
122
+ return createHash("sha256")
123
+ .update(PIPELINE)
124
+ .update(release)
125
+ .update(app.dir ?? "")
126
+ .update(JSON.stringify(app.build))
127
+ .update(context.env)
128
+ .update(JSON.stringify(Object.entries(context.files).sort()))
129
+ .digest("hex")
130
+ .slice(0, 12);
131
+ }
132
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Built, Context, Plan, Released } from "./pipeline.js";
2
+ export declare function runChecks(input: Built, context: Context): Promise<Released>;
3
+ export declare function assertCheckable(plan: Plan): void;
4
+ //# sourceMappingURL=checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAapE,wBAAsB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAajF;AA8BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,QAOzC"}
package/dist/checks.js ADDED
@@ -0,0 +1,49 @@
1
+ import { appRoot } from "./layout.js";
2
+ import { quote } from "./shell.js";
3
+ import { attachment, builderOf } from "./steps.js";
4
+ // What redkite's verify step runs. An app declares the commands that decide
5
+ // whether its build works, and this is the whole of putting them somewhere they
6
+ // can reach the services the deployment brought up.
7
+ //
8
+ // The image is the builder rather than the runtime one. The runtime image holds
9
+ // the compiled output and nothing that compiled it, so the test runner is not
10
+ // in there, and installing one at check time would test a different tree.
11
+ export async function runChecks(input, context) {
12
+ const checked = [];
13
+ for (const app of context.config.apps) {
14
+ if (!app.verify)
15
+ continue;
16
+ await checkApp(app, app.verify, input, context);
17
+ checked.push(app.name);
18
+ }
19
+ // Anything that got here passed: a failing command throws. The lists a swap
20
+ // fills stay empty, because a verify run moves no addresses
21
+ return { ...input, ok: true, released: [], reverted: [], checked };
22
+ }
23
+ async function checkApp(app, spec, input, context) {
24
+ const flags = [
25
+ "run --rm",
26
+ ...attachment(spec.network ?? "deployment", context.topology),
27
+ `--workdir ${appRoot(app.dir)}`,
28
+ ...settings({ ...app.environment, ...spec.environment }),
29
+ builderOf(input, app.name),
30
+ ];
31
+ // In order and one at a time. The first is usually what brings the database
32
+ // to the schema the rest expect, so running them together would race
33
+ for (const command of spec.steps) {
34
+ context.task.detail(`${app.name}: ${command}`);
35
+ await context.docker.runOrThrow([...flags, "sh -c", quote(command)].join(" "), `${app.name} failed ${command}`, context.task.line);
36
+ }
37
+ }
38
+ function settings(environment) {
39
+ return Object.entries(environment).map(([name, value]) => `-e ${name}=${quote(value)}`);
40
+ }
41
+ // Checked before the run starts, so asking for a verify that would check
42
+ // nothing fails without having created a network or built an image
43
+ export function assertCheckable(plan) {
44
+ if (plan.config.apps.some((app) => app.verify))
45
+ return;
46
+ throw new Error("No app declares verify, so this run would check nothing. " +
47
+ "An app is verified by giving it verify: { steps: [...] }");
48
+ }
49
+ //# sourceMappingURL=checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGnD,4EAA4E;AAC5E,gFAAgF;AAChF,oDAAoD;AACpD,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,0EAA0E;AAE1E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAY,EAAE,OAAgB;IAC5D,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,SAAS;QAE1B,MAAM,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,4EAA4E;IAC5E,4DAA4D;IAC5D,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;AACrE,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAY,EAAE,IAAgB,EAAE,KAAY,EAAE,OAAgB;IACpF,MAAM,KAAK,GAAG;QACZ,UAAU;QACV,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,IAAI,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC;QAC7D,aAAa,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC/B,GAAG,QAAQ,CAAC,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACxD,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC;KAC3B,CAAC;IAEF,4EAA4E;IAC5E,qEAAqE;IACrE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QAE/C,MAAM,OAAO,CAAC,MAAM,CAAC,UAAU,CAC7B,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAC7C,GAAG,GAAG,CAAC,IAAI,WAAW,OAAO,EAAE,EAC/B,OAAO,CAAC,IAAI,CAAC,IAAI,CAClB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,WAAmC;IACnD,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,yEAAyE;AACzE,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,IAAU;IACxC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO;IAEvD,MAAM,IAAI,KAAK,CACb,2DAA2D;QACzD,0DAA0D,CAC7D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function requireAgent(warn: (message: string) => void): string;
2
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/cli/agent.ts"],"names":[],"mappings":"AAQA,wBAAgB,YAAY,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,UAmB3D"}
@@ -0,0 +1,24 @@
1
+ import { execFileSync } from "node:child_process";
2
+ // Cloning private repositories and opening the tunnel both go through the
3
+ // agent, so a deploy without one fails halfway rather than at the start
4
+ // A plain writer rather than a Log, because this runs before the view opens:
5
+ // ssh-add may ask for a passphrase, and it cannot ask through a screen
6
+ // something else is drawing
7
+ export function requireAgent(warn) {
8
+ const existing = process.env["SSH_AUTH_SOCK"];
9
+ if (existing)
10
+ return existing;
11
+ warn("No SSH agent, starting one");
12
+ const output = execFileSync("ssh-agent", ["-s"], { encoding: "utf8" });
13
+ const socket = output.match(/SSH_AUTH_SOCK=([^;]+);/)?.[1];
14
+ const pid = output.match(/SSH_AGENT_PID=([^;]+);/)?.[1];
15
+ if (!socket)
16
+ throw new Error("ssh-agent did not report a socket");
17
+ process.env["SSH_AUTH_SOCK"] = socket;
18
+ if (pid)
19
+ process.env["SSH_AGENT_PID"] = pid;
20
+ // Inherits stdio so a passphrase prompt reaches the person running this
21
+ execFileSync("ssh-add", [], { stdio: "inherit" });
22
+ return socket;
23
+ }
24
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/cli/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,0EAA0E;AAC1E,wEAAwE;AAExE,6EAA6E;AAC7E,uEAAuE;AACvE,4BAA4B;AAC5B,MAAM,UAAU,YAAY,CAAC,IAA+B;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC9C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAElE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC;IACtC,IAAI,GAAG;QAAE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,GAAG,CAAC;IAE5C,wEAAwE;IACxE,YAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAElD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Deployment, Environment } from "../types.js";
2
+ export declare function loadConfig(explicit?: string): Promise<Deployment>;
3
+ export declare function discover(from: string): string;
4
+ export declare function loadEnvironments(directory: string): Promise<Record<string, Environment>>;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA2B3D,wBAAsB,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBvE;AA0CD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwB7C;AAGD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,wCAmBvD"}
@@ -0,0 +1,217 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ // Found by convention rather than named on the command line. A repository has
6
+ // one deployment, and requiring the path is a flag nobody ever varies.
7
+ //
8
+ // The environments live beside it, one file each, so the thing that differs
9
+ // between staging and production is a file rather than a key several levels
10
+ // down a literal.
11
+ const EXTENSIONS = ["ts", "mts", "js", "mjs"];
12
+ const CANDIDATES = EXTENSIONS.map((extension) => `redkite.config.${extension}`);
13
+ // redkite.<environment>.config.<extension>, beside the deployment it belongs to
14
+ const PER_ENVIRONMENT = /^redkite\.([a-z0-9-]+)\.config\.(ts|mts|js|mjs)$/;
15
+ // Node strips types itself from 22.18 on, which is what lets redkite ship with
16
+ // no dependencies. These are the ways that can fail on a config it cannot read.
17
+ // A SyntaxError is the same class of failure: a package without "type":
18
+ // "module" makes a .ts file CommonJS, where an import statement is not legal
19
+ const LOADER = new Set([
20
+ "ERR_UNKNOWN_FILE_EXTENSION",
21
+ "ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX",
22
+ "ERR_MODULE_NOT_FOUND",
23
+ "ERR_INVALID_MODULE_SPECIFIER",
24
+ ]);
25
+ export async function loadConfig(explicit) {
26
+ const path = explicit ? resolve(explicit) : discover(process.cwd());
27
+ const config = defaultOf(await load(path), path);
28
+ const beside = await loadEnvironments(dirname(path));
29
+ const named = await loadNamed(manifestOf(explicit ? dirname(path) : process.cwd()));
30
+ for (const name of Object.keys(named)) {
31
+ if (!beside[name])
32
+ continue;
33
+ throw new Error(`${name} is named by package.json and also sits beside the deployment. ` +
34
+ "An environment comes from one place or the other");
35
+ }
36
+ return { ...rooted(config, dirname(path)), environments: { ...beside, ...named } };
37
+ }
38
+ // A source path belongs to the file that named it, not to wherever the command
39
+ // was run. Resolving it here is what lets a deploy from a workspace and one
40
+ // from the root build the same tree
41
+ function rooted(config, directory) {
42
+ if (!config.apps.some((app) => app.path))
43
+ return config;
44
+ return {
45
+ ...config,
46
+ apps: config.apps.map((app) => app.path ? { ...app, path: resolve(directory, app.path) } : app),
47
+ };
48
+ }
49
+ // package.json may name the file each environment lives in, for a repository
50
+ // that keeps them somewhere the naming convention would not find them
51
+ async function loadNamed(manifest) {
52
+ const declared = manifest?.redkite.environments;
53
+ if (!manifest || !declared)
54
+ return {};
55
+ const environments = {};
56
+ for (const [name, where] of Object.entries(declared)) {
57
+ if (typeof where !== "string") {
58
+ throw new Error(`package.json names ${name} as something other than a path`);
59
+ }
60
+ const path = resolve(manifest.root, where);
61
+ if (!existsSync(path)) {
62
+ throw new Error(`package.json points ${name} at ${path}, which is not there`);
63
+ }
64
+ environments[name] = defaultOf(await load(path), path);
65
+ }
66
+ return environments;
67
+ }
68
+ // A deployment is one file at the root of the project, and a deploy is as
69
+ // likely to be run from a workspace inside it as from there
70
+ export function discover(from) {
71
+ let directory = from;
72
+ for (;;) {
73
+ const declared = directoryFrom(directory);
74
+ // Saying where the files are and not putting them there is a mistake worth
75
+ // stopping for, rather than a reason to keep looking further up
76
+ if (declared)
77
+ return found(declared) ?? missing(declared, directory);
78
+ const here = found(directory);
79
+ if (here)
80
+ return here;
81
+ const parent = dirname(directory);
82
+ if (parent === directory)
83
+ break;
84
+ directory = parent;
85
+ }
86
+ throw new Error(`No redkite.config.ts found in ${from} or any directory above it. A deployment ` +
87
+ 'is one file at the root of the project, or wherever package.json\'s ' +
88
+ '"redkite": { "directory": … } says it is');
89
+ }
90
+ // Every environment that lives in a file of its own, keyed by the name in it
91
+ export async function loadEnvironments(directory) {
92
+ const environments = {};
93
+ const seen = {};
94
+ for (const entry of readdirSync(directory).sort()) {
95
+ const name = PER_ENVIRONMENT.exec(entry)?.[1];
96
+ if (!name)
97
+ continue;
98
+ const first = seen[name];
99
+ if (first) {
100
+ throw new Error(`${name} is defined by both ${first} and ${entry}`);
101
+ }
102
+ seen[name] = entry;
103
+ const path = join(directory, entry);
104
+ environments[name] = defaultOf(await load(path), path);
105
+ }
106
+ return environments;
107
+ }
108
+ function found(directory) {
109
+ for (const candidate of CANDIDATES) {
110
+ const path = join(directory, candidate);
111
+ if (existsSync(path))
112
+ return path;
113
+ }
114
+ return undefined;
115
+ }
116
+ function missing(declared, from) {
117
+ throw new Error(`${join(from, "package.json")} points redkite at ${declared}, which holds no ` +
118
+ `redkite.config.${EXTENSIONS.join(", redkite.config.")}`);
119
+ }
120
+ function manifestAt(directory) {
121
+ const path = join(directory, "package.json");
122
+ if (!existsSync(path))
123
+ return undefined;
124
+ try {
125
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
126
+ return parsed.redkite ? { root: directory, redkite: parsed.redkite } : undefined;
127
+ }
128
+ catch {
129
+ // A package.json that does not parse is not this tool's to complain about
130
+ return undefined;
131
+ }
132
+ }
133
+ // The nearest one above wherever this was run, which is the same walk the
134
+ // config itself is found by
135
+ function manifestOf(from) {
136
+ let directory = from;
137
+ for (;;) {
138
+ const found = manifestAt(directory);
139
+ if (found)
140
+ return found;
141
+ const parent = dirname(directory);
142
+ if (parent === directory)
143
+ return undefined;
144
+ directory = parent;
145
+ }
146
+ }
147
+ // package.json says where the deployment files live, for a repository that
148
+ // would rather not keep them at its root
149
+ function directoryFrom(directory) {
150
+ const declared = manifestAt(directory)?.redkite.directory;
151
+ return typeof declared === "string" ? resolve(directory, declared) : undefined;
152
+ }
153
+ function defaultOf(module, path) {
154
+ const found = module;
155
+ // A config inside a CommonJS package transpiles to CommonJS, and Node hands
156
+ // back the whole module.exports as the default, wrapping the real one
157
+ const config = found.default?.default ?? found.default;
158
+ if (config)
159
+ return config;
160
+ throw new Error(`${path} has no default export`);
161
+ }
162
+ async function load(path) {
163
+ const url = pathToFileURL(path).href;
164
+ try {
165
+ return await import(url);
166
+ }
167
+ catch (error) {
168
+ if (!unreadable(error))
169
+ throw error;
170
+ // tsx resolves what Node will not: a path mapped by tsconfig, and the
171
+ // ./thing.js specifier TypeScript writes for a sibling ./thing.ts
172
+ const register = await tsxFrom(dirname(path));
173
+ if (!register)
174
+ throw cannotRead(path, error);
175
+ const unregister = register();
176
+ try {
177
+ return await import(url);
178
+ }
179
+ finally {
180
+ await unregister();
181
+ }
182
+ }
183
+ }
184
+ // Resolved from the project being deployed rather than depended on, so redkite
185
+ // installs as one package and still reads a config that needs more
186
+ async function tsxFrom(directory) {
187
+ try {
188
+ const require = createRequire(join(directory, "redkite.js"));
189
+ const api = (await import(pathToFileURL(require.resolve("tsx/esm/api")).href));
190
+ return api.register;
191
+ }
192
+ catch {
193
+ return undefined;
194
+ }
195
+ }
196
+ function unreadable(error) {
197
+ if (!(error instanceof Error))
198
+ return false;
199
+ if (error instanceof SyntaxError)
200
+ return true;
201
+ return "code" in error && typeof error.code === "string" && LOADER.has(error.code);
202
+ }
203
+ // Node reads TypeScript itself, within limits a config can run into. Each of
204
+ // them has a one line fix, and none of them is obvious from what Node throws
205
+ function cannotRead(path, error) {
206
+ const remedies = [
207
+ ' · A package without "type": "module" makes a .ts file CommonJS, where',
208
+ " an import statement is not legal. Name it redkite.config.mts instead",
209
+ " · Node resolves a relative import by the file it names, so a sibling is",
210
+ " ./thing.ts rather than ./thing.js",
211
+ " · Node reads TypeScript from 22.18 on, and this is " + process.version,
212
+ " · Anything else: add tsx to this project and redkite will read the config",
213
+ " through it",
214
+ ];
215
+ return new Error(`Could not read ${path}\n\n${error.message}\n\n${remedies.join("\n")}`, { cause: error });
216
+ }
217
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Deployment } from "../types.js";
2
+ export declare function positional(argv: string[]): string[];
3
+ export declare function main(argv: string[]): Promise<boolean | void>;
4
+ export declare function stopper(on: {
5
+ abort: () => void;
6
+ say: (message: string) => void;
7
+ signal: (name: "TERM" | "KILL") => void;
8
+ leave: () => void;
9
+ }): () => void;
10
+ export declare function buildingHere(config: Deployment, environment: string): Deployment;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AA2F1D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,YAgBxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,2BASxC;AAkLD,wBAAgB,OAAO,CAAC,EAAE,EAAE;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACxC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,cAwBA;AAkCD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,UAAU,CAehF"}