redkite 0.1.9 → 0.1.10

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 (127) hide show
  1. package/README.md +131 -11
  2. package/dist/build.d.ts +1 -0
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +7 -1
  5. package/dist/build.js.map +1 -1
  6. package/dist/cli/agent.js +2 -2
  7. package/dist/cli/agent.js.map +1 -1
  8. package/dist/cli/config.d.ts +1 -0
  9. package/dist/cli/config.d.ts.map +1 -1
  10. package/dist/cli/config.js +67 -14
  11. package/dist/cli/config.js.map +1 -1
  12. package/dist/cli/dotenv.d.ts +2 -0
  13. package/dist/cli/dotenv.d.ts.map +1 -0
  14. package/dist/cli/dotenv.js +30 -0
  15. package/dist/cli/dotenv.js.map +1 -0
  16. package/dist/cli/index.d.ts.map +1 -1
  17. package/dist/cli/index.js +11 -5
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/screen.d.ts +10 -3
  20. package/dist/cli/screen.d.ts.map +1 -1
  21. package/dist/cli/screen.js +153 -36
  22. package/dist/cli/screen.js.map +1 -1
  23. package/dist/cli/viewer.d.ts.map +1 -1
  24. package/dist/cli/viewer.js +36 -4
  25. package/dist/cli/viewer.js.map +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +8 -0
  28. package/dist/config.js.map +1 -1
  29. package/dist/deploy.d.ts.map +1 -1
  30. package/dist/deploy.js +12 -2
  31. package/dist/deploy.js.map +1 -1
  32. package/dist/docker.d.ts +1 -0
  33. package/dist/docker.d.ts.map +1 -1
  34. package/dist/docker.js +3 -0
  35. package/dist/docker.js.map +1 -1
  36. package/dist/dockerfile.d.ts +1 -0
  37. package/dist/dockerfile.d.ts.map +1 -1
  38. package/dist/dockerfile.js +16 -3
  39. package/dist/dockerfile.js.map +1 -1
  40. package/dist/environment.d.ts +2 -0
  41. package/dist/environment.d.ts.map +1 -1
  42. package/dist/environment.js +56 -1
  43. package/dist/environment.js.map +1 -1
  44. package/dist/index.d.ts +2 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +2 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/log.d.ts +1 -0
  49. package/dist/log.d.ts.map +1 -1
  50. package/dist/log.js +1 -0
  51. package/dist/log.js.map +1 -1
  52. package/dist/pipeline.d.ts.map +1 -1
  53. package/dist/pipeline.js +3 -0
  54. package/dist/pipeline.js.map +1 -1
  55. package/dist/plugins/bitwarden.d.ts.map +1 -1
  56. package/dist/plugins/bitwarden.js +25 -6
  57. package/dist/plugins/bitwarden.js.map +1 -1
  58. package/dist/presets/nextApp.d.ts.map +1 -1
  59. package/dist/presets/nextApp.js +5 -4
  60. package/dist/presets/nextApp.js.map +1 -1
  61. package/dist/presets/nodeApp.d.ts.map +1 -1
  62. package/dist/presets/nodeApp.js +15 -6
  63. package/dist/presets/nodeApp.js.map +1 -1
  64. package/dist/secrets/manager.d.ts +9 -0
  65. package/dist/secrets/manager.d.ts.map +1 -0
  66. package/dist/secrets/manager.js +130 -0
  67. package/dist/secrets/manager.js.map +1 -0
  68. package/dist/secrets/store.d.ts.map +1 -1
  69. package/dist/secrets/store.js +46 -12
  70. package/dist/secrets/store.js.map +1 -1
  71. package/dist/services/ensure.js +1 -1
  72. package/dist/services/ensure.js.map +1 -1
  73. package/dist/services/index.d.ts +2 -1
  74. package/dist/services/index.d.ts.map +1 -1
  75. package/dist/services/index.js +11 -0
  76. package/dist/services/index.js.map +1 -1
  77. package/dist/services/planned.d.ts +0 -1
  78. package/dist/services/planned.d.ts.map +1 -1
  79. package/dist/services/planned.js +3 -12
  80. package/dist/services/planned.js.map +1 -1
  81. package/dist/services/proxy.d.ts +7 -0
  82. package/dist/services/proxy.d.ts.map +1 -0
  83. package/dist/services/proxy.js +103 -0
  84. package/dist/services/proxy.js.map +1 -0
  85. package/dist/source.d.ts +1 -0
  86. package/dist/source.d.ts.map +1 -1
  87. package/dist/source.js +5 -5
  88. package/dist/source.js.map +1 -1
  89. package/dist/steps.d.ts +1 -6
  90. package/dist/steps.d.ts.map +1 -1
  91. package/dist/steps.js +3 -3
  92. package/dist/steps.js.map +1 -1
  93. package/dist/types.d.ts +7 -2
  94. package/dist/types.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/src/build.ts +10 -1
  97. package/src/cli/agent.ts +2 -2
  98. package/src/cli/config.ts +77 -13
  99. package/src/cli/dotenv.ts +35 -0
  100. package/src/cli/index.ts +11 -5
  101. package/src/cli/screen.ts +193 -43
  102. package/src/cli/viewer.ts +41 -4
  103. package/src/config.ts +11 -0
  104. package/src/deploy.ts +14 -2
  105. package/src/docker.ts +5 -0
  106. package/src/dockerfile.ts +23 -3
  107. package/src/environment.ts +71 -1
  108. package/src/index.ts +2 -1
  109. package/src/log.ts +4 -0
  110. package/src/pipeline.ts +4 -0
  111. package/src/plugins/bitwarden.ts +38 -11
  112. package/src/presets/nextApp.ts +5 -4
  113. package/src/presets/nodeApp.ts +15 -6
  114. package/src/secrets/manager.ts +172 -0
  115. package/src/secrets/store.ts +51 -16
  116. package/src/services/ensure.ts +1 -1
  117. package/src/services/index.ts +13 -1
  118. package/src/services/planned.ts +3 -13
  119. package/src/services/proxy.ts +126 -0
  120. package/src/source.ts +13 -4
  121. package/src/steps.ts +7 -7
  122. package/src/types.ts +19 -4
  123. package/dist/nginx.d.ts +0 -4
  124. package/dist/nginx.d.ts.map +0 -1
  125. package/dist/nginx.js +0 -53
  126. package/dist/nginx.js.map +0 -1
  127. package/src/nginx.ts +0 -63
package/src/source.ts CHANGED
@@ -32,6 +32,9 @@ export type SourceRequest = {
32
32
  ref: Ref;
33
33
  submodules: boolean;
34
34
  detail?: (message: string) => void;
35
+ // Every line git writes. A first clone of a large repository is the slowest
36
+ // part of a build and it used to say nothing at all while it happened
37
+ output?: (line: string) => void;
35
38
  };
36
39
 
37
40
  // Where each kind lives. A tag is peeled, so an annotated one answers with the
@@ -135,7 +138,7 @@ async function clonedSource(
135
138
  ` mkdir -p '${host.cache}/mirrors'`,
136
139
  ` git clone --mirror '${repo}' '${mirror}'`,
137
140
  "fi",
138
- ]);
141
+ ], request.output);
139
142
 
140
143
  const resolved = await run(host, `resolving ${name}`, [
141
144
  `git -C '${mirror}' rev-parse '${REVISION[kind](name)}'`,
@@ -160,7 +163,7 @@ async function clonedSource(
160
163
  // Leaves the submodules alone: they are tracked, and clean only removes
161
164
  // what is not
162
165
  `git -C '${path}' clean -ffdx`,
163
- ]);
166
+ ], request.output);
164
167
 
165
168
  if (request.submodules) {
166
169
  // --remote follows the branch named in .gitmodules rather than the commit
@@ -169,15 +172,21 @@ async function clonedSource(
169
172
  await run(host, "updating submodules", [
170
173
  `git -C '${path}' submodule sync --recursive`,
171
174
  `git -C '${path}' submodule update --init --remote --recursive`,
172
- ]);
175
+ ], request.output);
173
176
  }
174
177
 
175
178
  return { release, tree: path };
176
179
  }
177
180
 
178
- async function run(host: Host, what: string, script: string[]) {
181
+ async function run(
182
+ host: Host,
183
+ what: string,
184
+ script: string[],
185
+ output?: (line: string) => void,
186
+ ) {
179
187
  const result = await host.sh(
180
188
  [`export GIT_SSH_COMMAND='${GIT_SSH}'`, "set -e", ...script].join("\n"),
189
+ output,
181
190
  );
182
191
 
183
192
  if (result.code === 0) return result;
package/src/steps.ts CHANGED
@@ -30,10 +30,10 @@ type MigrateOptions = {
30
30
  // already reach whatever the app's environment file points at. A database
31
31
  // this deployment runs as a service is on "deployment" instead
32
32
  network?: StepNetwork;
33
- // Written when the step reached its database through a forward. It runs on
34
- // the deploy host now, which is the machine that bastion named, so nothing
35
- // is forwarded and the deploy refuses a bastion that is anything else
36
- tunnel?: { bastion: string; from: string; alias?: string; port?: number };
33
+ // The machine this migration reaches its database through. The step runs on
34
+ // the deploy host, so a deployment that moved to another one is refused
35
+ // rather than pointed at a database it may not be able to see
36
+ through?: string;
37
37
  };
38
38
 
39
39
  // An ordinary step at an ordinary point. Hung before the swap, it runs while
@@ -78,11 +78,11 @@ function assertReachable(plan: Plan, options: MigrateOptions) {
78
78
  if (!app) throw new Error(`${options.app} names no app in this deployment`);
79
79
 
80
80
  const bastion = environmentOf(plan.config, plan.environment)?.host?.bastion;
81
- const tunnel = options.tunnel;
82
- if (!tunnel || tunnel.bastion === bastion) return;
81
+ const expected = options.through;
82
+ if (!expected || expected === bastion) return;
83
83
 
84
84
  throw new Error(
85
- `${options.app} tunnels its migration through ${tunnel.bastion}, ` +
85
+ `${options.app} migrates through ${expected}, ` +
86
86
  `but ${plan.environment} deploys to ${bastion ?? "this machine"}. ` +
87
87
  "The step runs on the deploy host, so they have to be the same",
88
88
  );
package/src/types.ts CHANGED
@@ -68,6 +68,22 @@ export type SecretRef = {
68
68
  // One entry, or several merged in order. A key set by a later ref wins
69
69
  export type SecretRefs = SecretRef | SecretRef[];
70
70
 
71
+ // The proxy every app's route is resolved by. Written with nginx(), because
72
+ // what redkite derives is the server block and the upstreams and what this
73
+ // says is everything around them
74
+ export type ProxySpec = {
75
+ // Image it runs, pinned rather than floating
76
+ image?: string;
77
+ // Largest request body it accepts before answering 413
78
+ maxBodySize?: string;
79
+ // Lines put inside the server block, above the locations. For what belongs
80
+ // to the whole server: a redirect, a rate limit zone, an error page
81
+ server?: string[];
82
+ // Lines put inside every location, below what redkite sets, so one of these
83
+ // replaces a header rather than being replaced by it
84
+ location?: string[];
85
+ };
86
+
71
87
  export type ServiceSpec = {
72
88
  // Identifies the service, and becomes part of its container name
73
89
  name: string;
@@ -239,10 +255,9 @@ export type Deployment = {
239
255
  // them: the thing that differs between staging and production is a file, not
240
256
  // a key several levels down a literal
241
257
  environments?: Record<string, Environment>;
242
- // Largest request body the proxy accepts before answering 413
243
- maxBodySize?: string;
244
- // Image the derived proxy runs, pinned rather than floating
245
- proxyImage?: string;
258
+ // The derived proxy, which is not something a deployment lists: apps carry
259
+ // routes, routes need something to resolve them, so there is exactly one
260
+ proxy?: ProxySpec;
246
261
  // Long-lived containers shared by the apps, not rebuilt on every deploy.
247
262
  // The proxy is not one of them: apps with routes imply exactly one, so it is
248
263
  // derived rather than listed
package/dist/nginx.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import type { Topology } from "./topology.js";
2
- export declare const LISTEN_PORT = 3000;
3
- export declare function renderNginx(topology: Topology, maxBodySize?: string): string;
4
- //# sourceMappingURL=nginx.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nginx.d.ts","sourceRoot":"","sources":["../src/nginx.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,eAAe,CAAC;AAsB3D,eAAO,MAAM,WAAW,OAAO,CAAC;AAEhC,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,UAoBnE"}
package/dist/nginx.js DELETED
@@ -1,53 +0,0 @@
1
- // The nginx configuration is a function of the app list. Header policy is set
2
- // here once rather than per location, which is what let the two blocks drift
3
- // apart and lose X-Forwarded-For on one of them.
4
- const FORWARDING = [
5
- "proxy_set_header Host $http_host;",
6
- "proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
7
- "proxy_set_header X-Forwarded-Host $http_host;",
8
- "proxy_set_header X-Forwarded-Proto $scheme;",
9
- ];
10
- const FAILOVER = [
11
- "proxy_connect_timeout 2s;",
12
- "proxy_read_timeout 30s;",
13
- "proxy_next_upstream error timeout http_502 http_503;",
14
- "proxy_next_upstream_tries 2;",
15
- ];
16
- // The port the proxy listens on inside its container. The published port maps
17
- // onto this one, so both sides have to agree and only one of them may say it
18
- export const LISTEN_PORT = 3000;
19
- export function renderNginx(topology, maxBodySize) {
20
- const upstreams = topology.apps.map((app) => upstream(app)).join("\n\n");
21
- // Longest route first, so "/api/" is not shadowed by the "/" catch-all
22
- const ordered = [...topology.apps].sort((a, b) => b.route.length - a.route.length);
23
- const locations = ordered.map((app) => location(app)).join("\n\n");
24
- return `resolver 127.0.0.1 valid=5s;
25
-
26
- ${upstreams}
27
-
28
- server {
29
- listen ${LISTEN_PORT};
30
- merge_slashes off;
31
-
32
- client_max_body_size ${maxBodySize ?? "1M"};
33
-
34
- ${locations}
35
- }
36
- `;
37
- }
38
- function upstream(app) {
39
- return `upstream ${app.name} {
40
- server ${app.container}:${app.port} max_fails=1 fail_timeout=2s;
41
- server ${app.retired}:${app.port} backup;
42
- }`;
43
- }
44
- function location(app) {
45
- // A trailing slash on proxy_pass strips the location prefix. The catch-all
46
- // must not carry one, a mounted route must
47
- const target = app.route === "/" ? `http://${app.name}` : `http://${app.name}/`;
48
- const body = [`proxy_pass ${target};`, ...FAILOVER, ...FORWARDING];
49
- return ` location ${app.route} {
50
- ${body.map((line) => ` ${line}`).join("\n")}
51
- }`;
52
- }
53
- //# sourceMappingURL=nginx.js.map
package/dist/nginx.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"nginx.js","sourceRoot":"","sources":["../src/nginx.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,6EAA6E;AAC7E,iDAAiD;AAEjD,MAAM,UAAU,GAAG;IACjB,mCAAmC;IACnC,8DAA8D;IAC9D,+CAA+C;IAC/C,6CAA6C;CAC9C,CAAC;AAEF,MAAM,QAAQ,GAAG;IACf,2BAA2B;IAC3B,yBAAyB;IACzB,sDAAsD;IACtD,8BAA8B;CAC/B,CAAC;AAEF,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAEhC,MAAM,UAAU,WAAW,CAAC,QAAkB,EAAE,WAAoB;IAElE,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzE,uEAAuE;IACvE,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnE,OAAO;;EAEP,SAAS;;;aAGE,WAAW;;;2BAGG,WAAW,IAAI,IAAI;;EAE5C,SAAS;;CAEV,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAC,GAAgB;IAChC,OAAO,YAAY,GAAG,CAAC,IAAI;aAChB,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI;aACzB,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI;EAClC,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAgB;IAChC,2EAA2E;IAC3E,2CAA2C;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,CAAC;IAChF,MAAM,IAAI,GAAG,CAAC,cAAc,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,CAAC,CAAC;IAEnE,OAAO,gBAAgB,GAAG,CAAC,KAAK;EAChC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;MAC5C,CAAC;AACP,CAAC"}
package/src/nginx.ts DELETED
@@ -1,63 +0,0 @@
1
- import type { AppTopology, Topology } from "./topology.js";
2
-
3
- // The nginx configuration is a function of the app list. Header policy is set
4
- // here once rather than per location, which is what let the two blocks drift
5
- // apart and lose X-Forwarded-For on one of them.
6
-
7
- const FORWARDING = [
8
- "proxy_set_header Host $http_host;",
9
- "proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
10
- "proxy_set_header X-Forwarded-Host $http_host;",
11
- "proxy_set_header X-Forwarded-Proto $scheme;",
12
- ];
13
-
14
- const FAILOVER = [
15
- "proxy_connect_timeout 2s;",
16
- "proxy_read_timeout 30s;",
17
- "proxy_next_upstream error timeout http_502 http_503;",
18
- "proxy_next_upstream_tries 2;",
19
- ];
20
-
21
- // The port the proxy listens on inside its container. The published port maps
22
- // onto this one, so both sides have to agree and only one of them may say it
23
- export const LISTEN_PORT = 3000;
24
-
25
- export function renderNginx(topology: Topology, maxBodySize?: string) {
26
-
27
- const upstreams = topology.apps.map((app) => upstream(app)).join("\n\n");
28
- // Longest route first, so "/api/" is not shadowed by the "/" catch-all
29
- const ordered = [...topology.apps].sort((a, b) => b.route.length - a.route.length);
30
- const locations = ordered.map((app) => location(app)).join("\n\n");
31
-
32
- return `resolver 127.0.0.1 valid=5s;
33
-
34
- ${upstreams}
35
-
36
- server {
37
- listen ${LISTEN_PORT};
38
- merge_slashes off;
39
-
40
- client_max_body_size ${maxBodySize ?? "1M"};
41
-
42
- ${locations}
43
- }
44
- `;
45
- }
46
-
47
- function upstream(app: AppTopology) {
48
- return `upstream ${app.name} {
49
- server ${app.container}:${app.port} max_fails=1 fail_timeout=2s;
50
- server ${app.retired}:${app.port} backup;
51
- }`;
52
- }
53
-
54
- function location(app: AppTopology) {
55
- // A trailing slash on proxy_pass strips the location prefix. The catch-all
56
- // must not carry one, a mounted route must
57
- const target = app.route === "/" ? `http://${app.name}` : `http://${app.name}/`;
58
- const body = [`proxy_pass ${target};`, ...FAILOVER, ...FORWARDING];
59
-
60
- return ` location ${app.route} {
61
- ${body.map((line) => ` ${line}`).join("\n")}
62
- }`;
63
- }