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,199 @@
1
+ import type { AppSpec, Deployment, ServiceSpec } from "./types.js";
2
+
3
+ import { environmentOf } from "./config.js";
4
+ import { mountFor } from "./layout.js";
5
+
6
+ // Every name and address the deployment uses, derived from the app list. This
7
+ // is the file that replaces the constants block: nothing is chosen by hand, so
8
+ // adding an app cannot collide with an address somebody already picked.
9
+
10
+ const NGINX_OCTET = 20;
11
+ const APP_BLOCK_START = 21;
12
+ // Services sit in their own block, so adding one never moves an app
13
+ const SERVICE_BLOCK_START = 200;
14
+
15
+ export type AppTopology = {
16
+ name: string;
17
+ container: string;
18
+ retired: string;
19
+ failed: string;
20
+ currentAddress: string;
21
+ retiredAddress: string;
22
+ port: number;
23
+ route: string;
24
+ volumes: { volume: string; mountPath: string }[];
25
+ caches: Record<string, string>;
26
+ };
27
+
28
+ export type ServiceTopology = {
29
+ name: string;
30
+ container: string;
31
+ address: string;
32
+ alias?: string;
33
+ volumes: { volume: string; mountPath: string }[];
34
+ };
35
+
36
+ export type Topology = {
37
+ environment: string;
38
+ branch: string;
39
+ network: string;
40
+ subnet: string;
41
+ cidr: string;
42
+ // Absent for an environment written for verify alone, which publishes nothing
43
+ publicPort?: number;
44
+ // Derived, never listed. Apps with routes imply exactly one proxy, and it
45
+ // keeps the address it was given before any of this was derived
46
+ router: ServiceTopology;
47
+ apps: AppTopology[];
48
+ services: ServiceTopology[];
49
+ // Every alias a container must resolve, name to address
50
+ extraHosts: Record<string, string>;
51
+ };
52
+
53
+ export function topologyFor(config: Deployment, environment: string): Topology {
54
+ const env = environmentOf(config, environment);
55
+
56
+ if (!env) {
57
+ const known = Object.keys(config.environments ?? {});
58
+
59
+ // An environment is a file, so having none is a different mistake from
60
+ // asking for one that is not there, and reads as one
61
+ if (known.length === 0) {
62
+ throw new Error(
63
+ `No environments. Each one is a redkite.<name>.config.ts beside the ` +
64
+ `deployment, so ${environment} wants a redkite.${environment}.config.ts`,
65
+ );
66
+ }
67
+
68
+ throw new Error(
69
+ `Unknown environment ${environment}, expected one of ${known.join(", ")}`,
70
+ );
71
+ }
72
+
73
+ const prefix = `${config.project}-${environment}`;
74
+ const address = (octet: number) => `${env.subnet}.${octet}`;
75
+
76
+ const apps = config.apps.map((app, index) =>
77
+ appTopology(app, prefix, environment, address, index),
78
+ );
79
+
80
+ const services = config.services.map((service, index) =>
81
+ serviceTopology(service, prefix, address, index),
82
+ );
83
+
84
+ return {
85
+ environment,
86
+ branch: env.branch,
87
+ network: `${prefix}-network`,
88
+ subnet: env.subnet,
89
+ cidr: `${env.subnet}.0/16`,
90
+ publicPort: env.publicPort,
91
+ router: {
92
+ name: "nginx",
93
+ container: `${prefix}-nginx`,
94
+ address: address(NGINX_OCTET),
95
+ volumes: [],
96
+ },
97
+ apps,
98
+ services,
99
+ extraHosts: extraHosts(apps, services, env.extraHosts),
100
+ };
101
+ }
102
+
103
+ function appTopology(
104
+ app: AppSpec,
105
+ prefix: string,
106
+ environment: string,
107
+ address: (octet: number) => string,
108
+ index: number,
109
+ ): AppTopology {
110
+ const container = `${prefix}-${app.name}`;
111
+ const base = APP_BLOCK_START + index * 2;
112
+
113
+ const volumes = Object.entries(app.volumes ?? {}).map(([name, mountPath]) => ({
114
+ volume: `${container}-${name}`,
115
+ mountPath,
116
+ }));
117
+
118
+ // Deduplicated by where each one mounts: an app that is the whole repository
119
+ // resolves the root and its own node_modules to one path, and two ids for one
120
+ // target would be a second cache nothing ever writes to
121
+ const targets = new Set<string>();
122
+ const caches: Record<string, string> = {};
123
+
124
+ for (const cache of app.build.caches) {
125
+ const target = mountFor(cache, app.dir);
126
+ if (targets.has(target)) continue;
127
+
128
+ targets.add(target);
129
+ caches[cache] = `${app.name}-${environment}-${cache}-cache`;
130
+ }
131
+
132
+ return {
133
+ name: app.name,
134
+ container,
135
+ retired: `retired-${container}`,
136
+ failed: `failed-${container}`,
137
+ // Retired takes the lower slot, so a rollback never renumbers the live one
138
+ retiredAddress: address(base),
139
+ currentAddress: address(base + 1),
140
+ port: app.port,
141
+ route: app.route,
142
+ volumes,
143
+ caches,
144
+ };
145
+ }
146
+
147
+ function serviceTopology(
148
+ service: ServiceSpec,
149
+ prefix: string,
150
+ address: (octet: number) => string,
151
+ index: number,
152
+ ): ServiceTopology {
153
+ const container = `${prefix}-${service.name}`;
154
+
155
+ return {
156
+ name: service.name,
157
+ container,
158
+ // Pinned when a service already exists at a known address
159
+ address: address(service.address ?? SERVICE_BLOCK_START + index),
160
+ alias: service.alias,
161
+ volumes: Object.entries(service.volumes ?? {}).map(([name, mountPath]) => ({
162
+ volume: `${container}-${name}`,
163
+ mountPath,
164
+ })),
165
+ };
166
+ }
167
+
168
+ // Nginx resolves upstreams by container name, apps reach services by alias
169
+ function extraHosts(
170
+ apps: AppTopology[],
171
+ services: ServiceTopology[],
172
+ declared: Record<string, string> = {},
173
+ ) {
174
+ const hosts: Record<string, string> = {};
175
+
176
+ for (const app of apps) {
177
+ hosts[app.container] = app.currentAddress;
178
+ hosts[app.retired] = app.retiredAddress;
179
+ }
180
+
181
+ for (const service of services) {
182
+ if (service.alias) hosts[service.alias] = service.address;
183
+ }
184
+
185
+ // Declared last, but never over a derived one: a name that already resolves
186
+ // to a container in this deployment would send its traffic somewhere else,
187
+ // and the deploy would look like it worked
188
+ for (const [name, address] of Object.entries(declared)) {
189
+ if (name in hosts) {
190
+ throw new Error(
191
+ `extraHosts names ${name}, which this deployment already resolves to ${hosts[name]}`,
192
+ );
193
+ }
194
+
195
+ hosts[name] = address;
196
+ }
197
+
198
+ return hosts;
199
+ }
package/src/types.ts ADDED
@@ -0,0 +1,243 @@
1
+ import type { AnyStep } from "./pipeline.js";
2
+
3
+ // The shapes a deploy config is written against. Nothing here knows about
4
+ // Docker or git, so a config can be planned and asserted on without a host to
5
+ // deploy to.
6
+
7
+ export type Environment = {
8
+ // Where the images are built. "host" is the deploy host, which is where they
9
+ // are needed and costs nothing to move them. "local" builds on this machine
10
+ // and ships the result, for a host too small to compile on. Ignored when the
11
+ // deploy host is already this machine
12
+ buildOn?: "host" | "local";
13
+ // Git ref the apps are built from, the only per-environment source difference
14
+ branch: string;
15
+ // First three octets, the allocator owns the fourth. One /16 per environment
16
+ subnet: string;
17
+ // Port published on the host, the only port a person outside ever types.
18
+ // A verify environment has none: nothing serves in one, so there is no proxy
19
+ // to publish and no traffic to publish it for
20
+ publicPort?: number;
21
+ // The machine the containers run on, and which builds the images. Absent
22
+ // means this one
23
+ host?: DeployHost;
24
+ // Hostname to address, added to every container beside the ones the topology
25
+ // derives. For a database or a legacy service that has no DNS the apps can
26
+ // use, and whose address is what differs between environments
27
+ extraHosts?: Record<string, string>;
28
+ };
29
+
30
+ // Where a step's container is attached. "host" is the deploy host's own stack,
31
+ // which reaches whatever that machine already reaches. "deployment" is the
32
+ // network the apps and services run on, which is what resolves a service by
33
+ // the alias the apps know it by
34
+ export type StepNetwork = "host" | "deployment" | "none" | { named: string };
35
+
36
+ export type DeployHost = {
37
+ // SSH destination, user@address
38
+ bastion: string;
39
+ // Socket path on that machine. Local when absent, deploying to this one
40
+ socket?: string;
41
+ };
42
+
43
+ // One item in a store, named at the point of use rather than through a lookup
44
+ // table. The provider tag is what routes it to a store at deploy time
45
+ export type SecretRef = {
46
+ // Which store resolves this, and the primitive that produced it
47
+ provider: string;
48
+ // The store's own identifier for the item
49
+ id: string;
50
+ };
51
+
52
+ // One entry, or several merged in order. A key set by a later ref wins
53
+ export type SecretRefs = SecretRef | SecretRef[];
54
+
55
+ export type ServiceSpec = {
56
+ // Identifies the service, and becomes part of its container name
57
+ name: string;
58
+ // Image pulled and re-tagged onto the host, pinned rather than floating
59
+ image: string;
60
+ // Hostname other containers reach this service by, added as an extra host
61
+ alias?: string;
62
+ // Whether the host brings it back after a reboot or a crash
63
+ restart?: "always" | "unless-stopped";
64
+ // Container path to the file contents baked into the image at build time
65
+ files?: Record<string, string>;
66
+ // Settings the image reads on start. These are baked into the container, so
67
+ // a credential belongs in secrets rather than here
68
+ environment?: Record<string, string>;
69
+ // Resolved at deploy time and handed over as an env file, so a password
70
+ // reaches the container without being written down in this repository
71
+ secrets?: SecretRefs;
72
+ // Pins the last octet, for a service already running at an address it was
73
+ // given by hand. Drop the pin once the container has been recreated
74
+ address?: number;
75
+ // Volume name to container path. Without one, an image that declares a
76
+ // VOLUME gets an anonymous volume nothing in this file can name again
77
+ volumes?: Record<string, string>;
78
+ };
79
+
80
+ // A directory carried into the runtime image. Optional means the build is
81
+ // allowed not to have produced it, which is a claim about that directory alone
82
+ export type CarryPath = string | { path: string; optional: true };
83
+
84
+ export type BuildSpec = {
85
+ // Which preset produced this, carried for diagnostics rather than dispatch
86
+ preset: string;
87
+ // Fat image the repository is compiled in, with caches mounted
88
+ builderImage: string;
89
+ // Slim image the compiled output is copied into, and which actually ships
90
+ runtimeImage: string;
91
+ // Copied and run before the rest of the source. A commit that does not touch
92
+ // these files reuses the installed dependencies instead of resolving them
93
+ // again, which is the difference between a deploy and a cold build
94
+ dependencies?: {
95
+ files: string[];
96
+ step: string;
97
+ // Root lifecycle scripts removed from the manifest before installing. This
98
+ // layer holds the manifest and the lockfile alone, so a prepare that wants
99
+ // the repository, its hooks or its scripts directory cannot run here
100
+ stripScripts?: string[];
101
+ };
102
+ // Shell commands run in order in the builder. A non-zero exit fails the deploy
103
+ steps: string[];
104
+ // Directory in the builder that becomes the root of the runtime image
105
+ output: string;
106
+ // Extra builder directories copied into the runtime image beside the output.
107
+ // A bare path has to exist, and a build whose output is missing one of these
108
+ // is a build that failed without saying so
109
+ carry: CarryPath[];
110
+ // Whether the output tree keeps the repository's own directory structure.
111
+ // Next's standalone build does: it traces from the workspace root, so an app
112
+ // in a subdirectory arrives under that subdirectory rather than at the top
113
+ keepsLayout?: boolean;
114
+ // Process the runtime image starts, as argv rather than a shell string. It
115
+ // runs at the app's root, so a path relative to that survives dir
116
+ entrypoint: string[];
117
+ // Cache names mounted into the builder, keyed per app and environment so two
118
+ // apps or two environments never share one
119
+ caches: string[];
120
+ // Whether the checkout follows the submodules named in .gitmodules. An empty
121
+ // submodule directory builds a working image with the contents missing
122
+ submodules: boolean;
123
+ // Packages the builder needs before any step runs, typically an SSH client
124
+ aptPackages: string[];
125
+ // Packages the runtime image needs, typically curl for the health probe
126
+ runtimePackages: string[];
127
+ // Shell commands run in the runtime image, for what a package manager cannot
128
+ // install. Above the output copy, so a new commit does not repeat them
129
+ runtimeSteps: string[];
130
+ // Uploads source maps during the build, then deletes them from the image
131
+ sourcemaps?: SourcemapSpec;
132
+ };
133
+
134
+ export type SourcemapSpec = {
135
+ // Which service receives the maps, "sentry" is the only one today
136
+ provider: "sentry";
137
+ // Env var stripped from the shipped file, it is a build-time credential and
138
+ // has no reason to travel inside the image
139
+ stripFromImage: string;
140
+ };
141
+
142
+ // How a build is decided to work. The commands run in the builder image, which
143
+ // is the one holding the test runner and the dev dependencies, so nothing has
144
+ // to be installed to check a release that is already compiled
145
+ export type VerifySpec = {
146
+ // Shell commands run in order. A non-zero exit fails the run, and the first
147
+ // is usually whatever brings the test database to the schema the tests want
148
+ steps: string[];
149
+ // Defaults to the deployment network, which is what resolves a service by
150
+ // the alias the app already uses: postgres answers at postgres
151
+ network?: StepNetwork;
152
+ // Settings the checks need beside the ones the image was built with, a test
153
+ // database url among them
154
+ environment?: Record<string, string>;
155
+ };
156
+
157
+ export type HealthSpec = {
158
+ // Path probed on the container itself, not through the proxy
159
+ path: string;
160
+ // Decides whether the parsed body means healthy. A body that answers but
161
+ // fails this is a retry, not a verdict, the container may still be starting
162
+ expect: (body: Record<string, unknown>) => boolean;
163
+ // Attempts before the deploy reverts. Defaults to 5
164
+ retries?: number;
165
+ // Wait between attempts in milliseconds. Defaults to 5000
166
+ intervalMs?: number;
167
+ // Wait before the first attempt, covering ordinary start-up. Defaults to 10000
168
+ delayMs?: number;
169
+ };
170
+
171
+ export type AppSpec = {
172
+ // Identifies the app. Becomes its container name, cache keys, volume names
173
+ // and nginx upstream, so changing it orphans everything named after it
174
+ name: string;
175
+ // Clone URL, fetched over the forwarded SSH agent rather than with a token.
176
+ // Exactly one of this and path: the source is either cloned or already here
177
+ repo?: string;
178
+ // A directory on this machine, built as it stands rather than cloned. What a
179
+ // CI job already checked out is one, and so is the copy you are editing.
180
+ // Relative to the deployment file. The branch an environment names is not
181
+ // read for one of these: what is on disk is what ships
182
+ path?: string;
183
+ // What of that directory goes into the build, relative to it. Only for a
184
+ // path, and only needed where git cannot answer: a work tree's .gitignore
185
+ // already says it. Given here it wins, which is how a repository is narrowed
186
+ // to the one app inside it
187
+ include?: string[];
188
+ // Nginx location this app answers. "/" is the catch-all, and a mounted route
189
+ // such as "/api/" has its prefix stripped before the request is proxied
190
+ route: string;
191
+ // Port the app listens on inside its container, and which nginx proxies to
192
+ port: number;
193
+ // Environment for the app. Several are merged in the order written, so a
194
+ // shared ref can come first and a per-app one override it
195
+ secrets?: SecretRefs;
196
+ // Where the app sits in the repository, for a monorepo whose root is not it.
197
+ // Build steps and the shipped command run there. The dependency install does
198
+ // not: a workspace lockfile is resolved at the root for every package at once
199
+ dir?: string;
200
+ // How the repository becomes a runnable image
201
+ build: BuildSpec;
202
+ // What has to be true before traffic is allowed to move to the new container
203
+ health: HealthSpec;
204
+ // What has to be true for the build to be worth deploying at all. Only
205
+ // `redkite verify` runs these, so a deploy never pays for them
206
+ verify?: VerifySpec;
207
+ // Volume name to container path, for state that outlives a deploy
208
+ volumes?: Record<string, string>;
209
+ // Plain environment for the running container, for what is configuration
210
+ // rather than a credential. Secrets come from the vault instead
211
+ environment?: Record<string, string>;
212
+ // Container path to the item whose contents land there, for credentials that
213
+ // have to be a file rather than an environment variable
214
+ files?: Record<string, SecretRef>;
215
+ };
216
+
217
+ export type Deployment = {
218
+ // Prefixes every container, network and volume, and separates one project's
219
+ // objects from another's on a shared host
220
+ project: string;
221
+ // Overrides whichever one was selected, for a deployment that has only one
222
+ // and no reason to keep it in a file. Two of them is two files
223
+ environment?: Environment;
224
+ // Filled by the loader from the redkite.<name>.config.ts files beside this
225
+ // one, and from anything package.json names. A deployment does not declare
226
+ // them: the thing that differs between staging and production is a file, not
227
+ // a key several levels down a literal
228
+ environments?: Record<string, Environment>;
229
+ // Largest request body the proxy accepts before answering 413
230
+ maxBodySize?: string;
231
+ // Image the derived proxy runs, pinned rather than floating
232
+ proxyImage?: string;
233
+ // Long-lived containers shared by the apps, not rebuilt on every deploy.
234
+ // The proxy is not one of them: apps with routes imply exactly one, so it is
235
+ // derived rather than listed
236
+ services: ServiceSpec[];
237
+ // The applications. Everything derived, addresses, container names, nginx
238
+ // upstreams and location blocks, is a function of this list
239
+ apps: AppSpec[];
240
+ // Work injected into the run, from this file or from a plugin that answers
241
+ // with some. Addressed by where it runs, so nothing here has to be called
242
+ steps?: AnyStep[];
243
+ };