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
package/dist/docker.js ADDED
@@ -0,0 +1,325 @@
1
+ const RUNNING = new Set(["running", "restarting"]);
2
+ // What a container was created from, recorded on the container itself. Docker
3
+ // cannot change a label without recreating, which is exactly when it changes
4
+ export const SPEC_LABEL = "redkite.spec";
5
+ // One command in place of an inspect per object. Every guard in this file used
6
+ // to be its own round trip, and a round trip here is a container exec
7
+ const SNAPSHOT = [
8
+ `ps -a --format '{{.Names}}\t{{.State}}\t{{.Label "${SPEC_LABEL}"}}'`,
9
+ "docker image ls --format '{{.Repository}}:{{.Tag}}'",
10
+ "docker network ls --format '{{.Name}}'",
11
+ ].join("; echo --- ; ");
12
+ export class Docker {
13
+ host;
14
+ network;
15
+ image;
16
+ container;
17
+ // The promise, not the state. Two builds and the infrastructure step all ask
18
+ // for this at once, and caching the result alone lets every one of them miss
19
+ state;
20
+ constructor(host) {
21
+ this.host = host;
22
+ this.network = new DockerNetwork(this);
23
+ this.image = new DockerImage(this);
24
+ this.container = new DockerContainer(this);
25
+ }
26
+ // Read once, then kept current by the mutations below. A deploy owns the
27
+ // host for its duration, so nothing else is moving underneath it
28
+ async snapshot() {
29
+ this.state ??= this.read();
30
+ return await this.state;
31
+ }
32
+ async read() {
33
+ const { stdout } = await this.run(SNAPSHOT);
34
+ const [containers = "", images = "", networks = ""] = stdout.split("---");
35
+ return {
36
+ containers: new Map(lines(containers).map((line) => {
37
+ const [name = "", state = "unknown", spec = ""] = line.split("\t");
38
+ return [name, { state, spec: spec || undefined }];
39
+ })),
40
+ // Both spellings, so a lookup by bare name and one by name:tag both hit
41
+ images: new Set(lines(images).flatMap((line) => [line, line.replace(/:latest$/, "")])),
42
+ networks: new Set(lines(networks)),
43
+ };
44
+ }
45
+ async run(command, onLine) {
46
+ return await this.host.sh(`docker ${command}`, onLine);
47
+ }
48
+ // Called by every mutation, so the snapshot never goes stale
49
+ async track(change) {
50
+ change(await this.snapshot());
51
+ }
52
+ async runOrThrow(command, message, onLine) {
53
+ const result = await this.run(command, onLine);
54
+ if (result.code === 0)
55
+ return result;
56
+ throw new Error(`${message}: ${result.stderr || result.stdout}`);
57
+ }
58
+ }
59
+ class DockerNetwork {
60
+ docker;
61
+ constructor(docker) {
62
+ this.docker = docker;
63
+ }
64
+ async exists(name) {
65
+ return (await this.docker.snapshot()).networks.has(name);
66
+ }
67
+ async create(name, subnet) {
68
+ if (await this.exists(name))
69
+ return false;
70
+ const suffix = subnet ? ` --subnet=${subnet}` : "";
71
+ await this.docker.runOrThrow(`network create ${name}${suffix}`, "Network creation failed");
72
+ await this.docker.track((state) => state.networks.add(name));
73
+ return true;
74
+ }
75
+ async remove(name) {
76
+ if (!(await this.exists(name)))
77
+ return false;
78
+ await this.docker.runOrThrow(`network rm ${name}`, "Network removal failed");
79
+ await this.docker.track((state) => state.networks.delete(name));
80
+ return true;
81
+ }
82
+ // Disconnect can fail because the container was never attached, which is not
83
+ // an error. Connect failing is, the address is what nginx resolves to
84
+ async reconnect(network, container, ip) {
85
+ if (!(await this.docker.container.exists(container)))
86
+ return false;
87
+ await this.docker.run(`network disconnect ${network} ${container}`);
88
+ await this.docker.runOrThrow(`network connect --ip ${ip} ${network} ${container}`, "Network connect failed");
89
+ return true;
90
+ }
91
+ }
92
+ class DockerImage {
93
+ docker;
94
+ constructor(docker) {
95
+ this.docker = docker;
96
+ }
97
+ async exists(name) {
98
+ return (await this.docker.snapshot()).images.has(name);
99
+ }
100
+ // Every version of one image the host is holding. A deploy tags what it built
101
+ // by release, and without this the previous ones are never reclaimed.
102
+ // :latest is not one of them: it is the moving name a container is created
103
+ // from, and reclaiming it leaves the host unable to start the app
104
+ async versionsOf(repository) {
105
+ const images = (await this.docker.snapshot()).images;
106
+ return [...images].filter((name) => name.startsWith(`${repository}:`) && name !== `${repository}:latest`);
107
+ }
108
+ // BuildKit is already inside the daemon that will run the container, so the
109
+ // image it produces never has to be serialised, transferred or loaded
110
+ async build(spec, onLine) {
111
+ const command = [
112
+ "build",
113
+ // Provenance attestations make the result a manifest list, which is a
114
+ // different thing to tag and nothing here consumes them
115
+ "--progress plain --provenance=false --pull",
116
+ `-f ${spec.dockerfile}`,
117
+ ...spec.tags.map((tag) => `-t ${tag}`),
118
+ ...Object.entries(spec.secrets ?? {}).map(([id, path]) => `--secret id=${id},src=${path}`),
119
+ ...(spec.target ? [`--target ${spec.target}`] : []),
120
+ spec.context,
121
+ ].join(" ");
122
+ await this.docker.runOrThrow(command, "Image build failed", onLine);
123
+ await this.docker.track((state) => {
124
+ for (const tag of spec.tags)
125
+ state.images.add(tag);
126
+ });
127
+ return true;
128
+ }
129
+ // A second name for an image that is already local. Costs nothing, and it is
130
+ // what lets the next deploy recognise a commit it already holds
131
+ async retag(from, to) {
132
+ await this.docker.runOrThrow(`tag ${from} ${to}`, "Image tagging failed");
133
+ await this.docker.track((state) => state.images.add(to));
134
+ return true;
135
+ }
136
+ async remove(name) {
137
+ if (!(await this.exists(name)))
138
+ return false;
139
+ await this.docker.runOrThrow(`image remove -f ${name}`, "Image removal failed");
140
+ await this.docker.track((state) => state.images.delete(name));
141
+ return true;
142
+ }
143
+ }
144
+ class DockerContainer {
145
+ docker;
146
+ constructor(docker) {
147
+ this.docker = docker;
148
+ }
149
+ async exists(name) {
150
+ return (await this.docker.snapshot()).containers.has(name);
151
+ }
152
+ async status(name) {
153
+ return (await this.docker.snapshot()).containers.get(name)?.state ?? "none";
154
+ }
155
+ // What the running container was created from. Absent for one redkite did not
156
+ // create, or created before it started recording it
157
+ async specOf(name) {
158
+ return (await this.docker.snapshot()).containers.get(name)?.spec;
159
+ }
160
+ async isRunning(name) {
161
+ return RUNNING.has(await this.status(name));
162
+ }
163
+ async start(name) {
164
+ if (!(await this.exists(name))) {
165
+ throw new Error(`Cannot start ${name}, it does not exist`);
166
+ }
167
+ await this.docker.runOrThrow(`container start ${name}`, "Container start failed");
168
+ await this.docker.track((state) => set(state, name, "running"));
169
+ return true;
170
+ }
171
+ async stop(name) {
172
+ if (!(await this.exists(name)))
173
+ return false;
174
+ await this.docker.runOrThrow(`container stop ${name}`, "Container stop failed");
175
+ await this.docker.track((state) => set(state, name, "exited"));
176
+ return true;
177
+ }
178
+ // Refuses rather than clobbers, a rename onto an existing name would lose
179
+ // whichever container is already there
180
+ async rename(from, to) {
181
+ if (!(await this.exists(from)))
182
+ return false;
183
+ if (await this.exists(to))
184
+ return false;
185
+ await this.docker.runOrThrow(`container rename ${from} ${to}`, "Container rename failed");
186
+ await this.docker.track((state) => {
187
+ const held = state.containers.get(from) ?? { state: "unknown" };
188
+ state.containers.delete(from);
189
+ state.containers.set(to, held);
190
+ });
191
+ return true;
192
+ }
193
+ async remove(name) {
194
+ if (!(await this.exists(name)))
195
+ return false;
196
+ if (await this.isRunning(name)) {
197
+ throw new Error(`Cannot remove ${name} while it is running`);
198
+ }
199
+ await this.docker.runOrThrow(`container rm ${name}`, "Container removal failed");
200
+ await this.docker.track((state) => state.containers.delete(name));
201
+ return true;
202
+ }
203
+ async create(builder) {
204
+ await this.docker.runOrThrow(builder.parse(), "Container create failed");
205
+ await this.docker.track((state) => state.containers.set(builder.named(), {
206
+ state: "created",
207
+ spec: builder.labelled(SPEC_LABEL),
208
+ }));
209
+ return true;
210
+ }
211
+ builder() {
212
+ return new DockerBuilder(this);
213
+ }
214
+ }
215
+ // A state change keeps whatever the container was created from: the label is
216
+ // on the container, and only a recreate can move it
217
+ function set(state, name, next) {
218
+ state.containers.set(name, { ...state.containers.get(name), state: next });
219
+ }
220
+ function lines(block) {
221
+ return block
222
+ .split("\n")
223
+ .map((line) => line.trim())
224
+ .filter(Boolean);
225
+ }
226
+ export class DockerBuilder {
227
+ container;
228
+ _name = "";
229
+ _image = "";
230
+ _hostname;
231
+ _ip;
232
+ _restart;
233
+ _networks = [];
234
+ _volumes = [];
235
+ _hosts = [];
236
+ _ports = [];
237
+ _env = [];
238
+ _labels = new Map();
239
+ _envFile;
240
+ constructor(container) {
241
+ this.container = container;
242
+ }
243
+ name(value) {
244
+ this._name = value;
245
+ return this;
246
+ }
247
+ image(value) {
248
+ this._image = value;
249
+ return this;
250
+ }
251
+ hostname(value) {
252
+ this._hostname = value;
253
+ return this;
254
+ }
255
+ network(value) {
256
+ this._networks.push(value);
257
+ return this;
258
+ }
259
+ volume(from, to) {
260
+ this._volumes.push(`${from}:${to}`);
261
+ return this;
262
+ }
263
+ extraHost(name, ip) {
264
+ this._hosts.push(`${name}:${ip}`);
265
+ return this;
266
+ }
267
+ restart(value) {
268
+ this._restart = value;
269
+ return this;
270
+ }
271
+ ip(value) {
272
+ this._ip = value;
273
+ return this;
274
+ }
275
+ env(name, value) {
276
+ this._env.push(`${name}=${value}`);
277
+ return this;
278
+ }
279
+ // A path on the host rather than the values themselves, so a password is
280
+ // never an argument in the process list or in the shell history
281
+ envFile(path) {
282
+ this._envFile = path;
283
+ return this;
284
+ }
285
+ label(name, value) {
286
+ this._labels.set(name, value);
287
+ return this;
288
+ }
289
+ labelled(name) {
290
+ return this._labels.get(name);
291
+ }
292
+ port(from, to) {
293
+ this._ports.push(`${from}:${to}`);
294
+ return this;
295
+ }
296
+ named() {
297
+ return this._name;
298
+ }
299
+ parse() {
300
+ if (!this._name)
301
+ throw new Error("A container needs a name");
302
+ if (!this._image)
303
+ throw new Error(`${this._name} has no image`);
304
+ // Defaults to the container name. The original emitted "--hostname
305
+ // undefined", because nothing ever called the setter
306
+ const hostname = this._hostname ?? this._name;
307
+ return [
308
+ `container create --name ${this._name} --hostname ${hostname}`,
309
+ ...this._volumes.map((volume) => `-v ${volume}`),
310
+ ...this._networks.map((network) => `--network ${network}`),
311
+ ...this._ports.map((port) => `-p ${port}`),
312
+ ...this._hosts.map((host) => `--add-host ${host}`),
313
+ ...[...this._labels].map(([name, value]) => `--label ${name}=${value}`),
314
+ ...(this._envFile ? [`--env-file ${this._envFile}`] : []),
315
+ ...this._env.map((entry) => `-e ${entry}`),
316
+ ...(this._ip ? [`--ip ${this._ip}`] : []),
317
+ ...(this._restart ? [`--restart ${this._restart}`] : []),
318
+ this._image,
319
+ ].join(" ");
320
+ }
321
+ async create() {
322
+ return await this.container.create(this);
323
+ }
324
+ }
325
+ //# sourceMappingURL=docker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.js","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { BuildSpec } from "./types.js";
2
+ export type DockerfileContext = {
3
+ caches: Record<string, string>;
4
+ port: number;
5
+ release: string;
6
+ environment: string;
7
+ envSecret: string;
8
+ fileSecrets: Record<string, string>;
9
+ dir?: string;
10
+ };
11
+ export declare const BUILDER_STAGE = "builder";
12
+ export declare function renderDockerignore(include?: string[]): string;
13
+ export declare function renderDockerfile(spec: BuildSpec, context: DockerfileContext): string;
14
+ //# sourceMappingURL=dockerfile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dockerfile.d.ts","sourceRoot":"","sources":["../src/dockerfile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAOvD,MAAM,MAAM,iBAAiB,GAAG;IAG9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IAKpB,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAIpC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAIF,eAAO,MAAM,aAAa,YAAY,CAAC;AAMvC,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAMpD;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAQR"}
@@ -0,0 +1,160 @@
1
+ import { appRoot, destinationFor, mountFor, rootedAt } from "./layout.js";
2
+ // Named here because the build stops at this stage to keep an image a
3
+ // step before the swap can run in, and two spellings of it would drift
4
+ export const BUILDER_STAGE = "builder";
5
+ // What BuildKit reads beside the Dockerfile. Without an include the checkout
6
+ // keeps its own rules and only .git is held back. With one, everything is held
7
+ // back and the named paths are let through again, so what the build sees is
8
+ // what the release was taken over
9
+ export function renderDockerignore(include) {
10
+ if (!include)
11
+ return ".git\n**/.git\n";
12
+ // .git after the exemptions, because the last rule to match is the one that
13
+ // decides and an included directory would otherwise carry it back in
14
+ return ["*", ...include.map((path) => `!${path}`), ".git", "**/.git", ""].join("\n");
15
+ }
16
+ export function renderDockerfile(spec, context) {
17
+ return [
18
+ "# syntax=docker/dockerfile:1.7",
19
+ ...builderStage(spec, context),
20
+ "",
21
+ ...runtimeStage(spec, context),
22
+ "",
23
+ ].join("\n");
24
+ }
25
+ function builderStage(spec, context) {
26
+ const mounts = cacheMounts(spec, context);
27
+ const workdir = appRoot(context.dir);
28
+ // The repository root, not the app's own directory: a workspace resolves one
29
+ // lockfile for every package in it, so the install below has to see them all
30
+ const lines = [
31
+ `FROM ${spec.builderImage} AS ${BUILDER_STAGE}`,
32
+ "WORKDIR /app",
33
+ ];
34
+ if (spec.aptPackages.length > 0) {
35
+ lines.push(`RUN apk add --no-cache ${spec.aptPackages.join(" ")}`);
36
+ }
37
+ lines.push(`ENV NODE_ENV=${context.environment}`);
38
+ lines.push("ENV NEXT_TELEMETRY_DISABLED=1");
39
+ if (spec.dependencies)
40
+ lines.push(...dependencyLayer(spec, mounts));
41
+ // Submodules are already in the context: the checkout on the host resolved
42
+ // them, so nothing in the build needs an agent or a .git directory
43
+ lines.push(copy(".", "/app"));
44
+ // Only now, so the steps below read the app's own manifest and write beside it
45
+ if (context.dir)
46
+ lines.push(`WORKDIR ${workdir}`);
47
+ lines.push(`RUN --mount=type=secret,id=${context.envSecret} cp /run/secrets/${context.envSecret} ${workdir}/.env`, `ENV SENTRY_RELEASE=${context.release}`);
48
+ // Unquoted: the shell form hands everything after RUN to sh -c, so a step is
49
+ // written exactly as it would be typed. Quoting it made the whole command one
50
+ // word, and the shell went looking for a program by that name
51
+ for (const step of spec.steps) {
52
+ lines.push(`RUN ${mounts}${step}`);
53
+ }
54
+ if (spec.sourcemaps) {
55
+ // The built .env ships inside the image, and an upload token is build-time
56
+ const token = spec.sourcemaps.stripFromImage;
57
+ lines.push(`RUN sed -i '/^${token}=/d' ${rootedAt(spec.output, context.dir)}/.env`);
58
+ }
59
+ return lines;
60
+ }
61
+ function runtimeStage(spec, context) {
62
+ const output = rootedAt(spec.output, context.dir);
63
+ // Where the app lands inside the tree that was just copied to /app. An output
64
+ // keeping the repository's structure puts it back under dir, and everything
65
+ // else flattens it to the top
66
+ const nested = spec.keepsLayout ? context.dir : undefined;
67
+ const lines = [`FROM ${spec.runtimeImage}`, `WORKDIR ${appRoot(nested)}`];
68
+ if (spec.runtimePackages.length > 0) {
69
+ lines.push(`RUN apk add --no-cache ${spec.runtimePackages.join(" ")}`);
70
+ }
71
+ // Before the output, which changes every commit: what these install is a
72
+ // property of the image, not of the release being deployed
73
+ for (const step of spec.runtimeSteps) {
74
+ lines.push(`RUN ${step}`);
75
+ }
76
+ // The one copy nothing can be allowed to skip: an image without it starts,
77
+ // answers, and serves a 404 for everything the app was supposed to be
78
+ lines.push(copy(output, "/app", BUILDER_STAGE));
79
+ // Directories the output does not contain but the runtime needs, such as a
80
+ // generated client or the static assets a standalone build leaves behind.
81
+ // The two sides are rooted separately: dir is where the app was built, and
82
+ // nested is where the output put it
83
+ for (const entry of spec.carry) {
84
+ const { path, optional } = carried(entry);
85
+ const from = rootedAt(path, context.dir);
86
+ const to = rootedAt(destinationFor(path, spec.output), nested);
87
+ lines.push(optional ? copyOrSkip(from, to, BUILDER_STAGE) : copy(from, to, BUILDER_STAGE));
88
+ }
89
+ for (const [path, secret] of Object.entries(context.fileSecrets)) {
90
+ lines.push(`RUN --mount=type=secret,id=${secret} cp /run/secrets/${secret} ${path}`);
91
+ }
92
+ lines.push(`EXPOSE ${context.port}`, `CMD ${JSON.stringify(spec.entrypoint)}`);
93
+ return lines;
94
+ }
95
+ function carried(entry) {
96
+ if (typeof entry === "string")
97
+ return { path: entry, optional: false };
98
+ return { path: entry.path, optional: entry.optional };
99
+ }
100
+ // A COPY whose source does not exist fails the build, which is what a source
101
+ // the build was supposed to produce should do
102
+ function copy(from, to, stage) {
103
+ return `COPY ${stage ? `--from=${stage} ` : ""}${from} ${to}`;
104
+ }
105
+ // Bracketing the last character makes the source a pattern, and a pattern
106
+ // matching nothing is skipped: the only way a Dockerfile has to say "if it
107
+ // exists". For a source whose absence is a fact about the repository
108
+ function copyOrSkip(from, to, stage) {
109
+ const path = from.replace(/\/+$/, "");
110
+ const last = path.slice(-1);
111
+ // An empty pattern matches nothing and would be skipped in silence, which is
112
+ // the one outcome this whole distinction exists to prevent
113
+ if (!last)
114
+ return copy(from, to, stage);
115
+ return copy(`${path.slice(0, -1)}[${last}]`, to, stage);
116
+ }
117
+ // One mount per cache, repeated on every step, because a Dockerfile scopes a
118
+ // cache to the RUN that asks for it rather than to the stage
119
+ function cacheMounts(spec, context) {
120
+ // Only the caches the topology derived an id for. It drops any whose target
121
+ // another already covers, and two mounts on one target is a build that does
122
+ // not start
123
+ return spec.caches
124
+ .filter((name) => name in context.caches)
125
+ .map((name) => {
126
+ const id = context.caches[name] ?? name;
127
+ return `--mount=type=cache,id=${id},target=${mountFor(name, context.dir)} `;
128
+ })
129
+ .join("");
130
+ }
131
+ function dependencyLayer(spec, mounts) {
132
+ const { files, step, stripScripts = [] } = spec.dependencies ?? {
133
+ files: [],
134
+ step: "",
135
+ };
136
+ // A lockfile the repository does not have is not an error here. The install
137
+ // below is what decides that, and it says so in the package manager's words
138
+ const lines = files.map((file) => copyOrSkip(file, `/app/${file}`));
139
+ // This layer is the manifest and the lockfile, nothing else. A root prepare
140
+ // that installs git hooks or calls into scripts/ has neither
141
+ if (stripScripts.length > 0) {
142
+ lines.push(`RUN node --input-type=commonjs -e ${quote(strip(stripScripts))}`);
143
+ }
144
+ lines.push(`RUN ${mounts}${step}`);
145
+ return lines;
146
+ }
147
+ function strip(names) {
148
+ return [
149
+ 'const fs = require("fs"), path = "/app/package.json";',
150
+ 'const manifest = JSON.parse(fs.readFileSync(path, "utf8"));',
151
+ `for (const name of ${JSON.stringify(names)}) delete manifest.scripts?.[name];`,
152
+ "fs.writeFileSync(path, JSON.stringify(manifest, null, 2));",
153
+ ].join("");
154
+ }
155
+ // Only for a command passed as one argument to another. Single quotes are what
156
+ // the shell strips, so a script containing double quotes survives
157
+ function quote(command) {
158
+ return `'${command.replaceAll("'", `'\\''`)}'`;
159
+ }
160
+ //# sourceMappingURL=dockerfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dockerfile.js","sourceRoot":"","sources":["../src/dockerfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type Log } from "./log.js";
2
+ import type { HealthSpec } from "./types.js";
3
+ export type Probe = (container: string, url: string) => Promise<{
4
+ code: number;
5
+ output: string;
6
+ }>;
7
+ export type HealthDeps = {
8
+ probe: Probe;
9
+ sleep: (ms: number) => Promise<void>;
10
+ log?: Log;
11
+ };
12
+ export declare function healthcheck(container: string, port: number, spec: HealthSpec, deps: HealthDeps): Promise<boolean>;
13
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,GAAG,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS7C,MAAM,MAAM,KAAK,GAAG,CAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,CAAC,EAAE,GAAG,CAAC;CACX,CAAC;AAKF,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC,CA6BlB"}
package/dist/health.js ADDED
@@ -0,0 +1,54 @@
1
+ import { silent } from "./log.js";
2
+ const RETRIES = 10;
3
+ // Ceiling for the backoff, not a fixed wait
4
+ const INTERVAL_MS = 5000;
5
+ // The first probe is free, a container that is already up answers it
6
+ const DELAY_MS = 0;
7
+ const FIRST_BACKOFF_MS = 250;
8
+ // One loop for every app. The two copies it replaces had already drifted: the
9
+ // frontend never incremented its counter on a failed predicate, so a container
10
+ // that answered but never became healthy span until the deploy was killed.
11
+ export async function healthcheck(container, port, spec, deps) {
12
+ const retries = spec.retries ?? RETRIES;
13
+ const ceiling = spec.intervalMs ?? INTERVAL_MS;
14
+ const log = deps.log ?? silent;
15
+ const delay = spec.delayMs ?? DELAY_MS;
16
+ if (delay > 0)
17
+ await deps.sleep(delay);
18
+ let backoff = FIRST_BACKOFF_MS;
19
+ for (let attempt = 1; attempt <= retries; attempt += 1) {
20
+ const url = `localhost:${port}${spec.path}`;
21
+ const { code, output } = await deps.probe(container, url);
22
+ if (code === 0 && passes(spec, output, log, container)) {
23
+ log.done(`${container} is healthy`);
24
+ return true;
25
+ }
26
+ if (attempt === retries)
27
+ break;
28
+ // Doubling from a quarter second means a container that starts quickly
29
+ // costs almost nothing, and one that does not still gets the same budget
30
+ await deps.sleep(backoff);
31
+ backoff = Math.min(backoff * 2, ceiling);
32
+ }
33
+ log.fail(`${container} failed its health check after ${retries} attempts`);
34
+ return false;
35
+ }
36
+ // A body that parses but fails the predicate is a retry, not a verdict. The
37
+ // container may still be warming up, and the caller has a retry budget for it
38
+ function passes(spec, output, log, container) {
39
+ let body;
40
+ try {
41
+ body = JSON.parse(output);
42
+ }
43
+ catch {
44
+ log.warn(`${container} returned a body that is not JSON: ${output}`);
45
+ return false;
46
+ }
47
+ if (typeof body !== "object" || body === null)
48
+ return false;
49
+ if (spec.expect(body))
50
+ return true;
51
+ log.warn(`${container} is not healthy yet: ${output}`);
52
+ return false;
53
+ }
54
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,UAAU,CAAC;AAG5C,MAAM,OAAO,GAAG,EAAE,CAAC;AACnB,4CAA4C;AAC5C,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,qEAAqE;AACrE,MAAM,QAAQ,GAAG,CAAC,CAAC;AACnB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAa7B,8EAA8E;AAC9E,+EAA+E;AAC/E,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAiB,EACjB,IAAY,EACZ,IAAgB,EAChB,IAAgB;IAEhB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,IAAI,WAAW,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IAEvC,IAAI,KAAK,GAAG,CAAC;QAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEvC,IAAI,OAAO,GAAG,gBAAgB,CAAC;IAE/B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,aAAa,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAE1D,IAAI,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,KAAK,OAAO;YAAE,MAAM;QAE/B,uEAAuE;QACvE,yEAAyE;QACzE,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,kCAAkC,OAAO,WAAW,CAAC,CAAC;IAC3E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,SAAS,MAAM,CACb,IAAgB,EAChB,MAAc,EACd,GAAQ,EACR,SAAiB;IAEjB,IAAI,IAAa,CAAC;IAElB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,sCAAsC,MAAM,EAAE,CAAC,CAAC;QACrE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,IAAI,CAAC,MAAM,CAAC,IAA+B,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9D,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,wBAAwB,MAAM,EAAE,CAAC,CAAC;IACvD,OAAO,KAAK,CAAC;AACf,CAAC"}
package/dist/host.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type Result = {
2
+ code: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ };
6
+ export type OnLine = (line: string) => void;
7
+ export type Host = {
8
+ sh(command: string, onLine?: OnLine): Promise<Result>;
9
+ write(name: string, contents: string): Promise<string>;
10
+ readonly directory: string;
11
+ readonly cache: string;
12
+ pipe(local: string, remote: string, onLine?: OnLine): Promise<Result>;
13
+ stop(signal: "TERM" | "KILL"): Promise<number>;
14
+ close?(): Promise<void>;
15
+ };
16
+ export declare function tail(lines: string[]): string;
17
+ export declare function lineReader(onLine: OnLine): {
18
+ push(chunk: string): void;
19
+ flush(): void;
20
+ };
21
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAItE,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5C,MAAM,MAAM,IAAI,GAAG;IAGjB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAGtD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAGvB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAGtE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAMF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,UAEnC;AAGD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;gBAIzB,MAAM;;EAUrB"}