redkite 0.1.6 → 0.1.8

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 (109) hide show
  1. package/README.md +323 -35
  2. package/dist/checks.d.ts.map +1 -1
  3. package/dist/checks.js +4 -0
  4. package/dist/checks.js.map +1 -1
  5. package/dist/cli/agent.d.ts +2 -0
  6. package/dist/cli/agent.d.ts.map +1 -1
  7. package/dist/cli/agent.js +23 -1
  8. package/dist/cli/agent.js.map +1 -1
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +98 -30
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +16 -1
  14. package/dist/config.js.map +1 -1
  15. package/dist/deploy.d.ts +4 -1
  16. package/dist/deploy.d.ts.map +1 -1
  17. package/dist/deploy.js +77 -23
  18. package/dist/deploy.js.map +1 -1
  19. package/dist/dockerfile.js +6 -7
  20. package/dist/dockerfile.js.map +1 -1
  21. package/dist/environment.d.ts +5 -0
  22. package/dist/environment.d.ts.map +1 -0
  23. package/dist/environment.js +20 -0
  24. package/dist/environment.js.map +1 -0
  25. package/dist/host.d.ts +2 -0
  26. package/dist/host.d.ts.map +1 -1
  27. package/dist/host.js +6 -0
  28. package/dist/host.js.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/localHost.d.ts.map +1 -1
  34. package/dist/localHost.js +2 -0
  35. package/dist/localHost.js.map +1 -1
  36. package/dist/plugin.d.ts +15 -0
  37. package/dist/plugin.d.ts.map +1 -0
  38. package/dist/plugin.js +21 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/dist/plugins/bitwarden.d.ts +11 -0
  41. package/dist/plugins/bitwarden.d.ts.map +1 -0
  42. package/dist/plugins/bitwarden.js +40 -0
  43. package/dist/plugins/bitwarden.js.map +1 -0
  44. package/dist/plugins/digitalOcean.d.ts +19 -0
  45. package/dist/plugins/digitalOcean.d.ts.map +1 -0
  46. package/dist/plugins/digitalOcean.js +71 -0
  47. package/dist/plugins/digitalOcean.js.map +1 -0
  48. package/dist/plugins/index.d.ts +5 -0
  49. package/dist/plugins/index.d.ts.map +1 -0
  50. package/dist/plugins/index.js +5 -0
  51. package/dist/plugins/index.js.map +1 -0
  52. package/dist/plugins/rds.d.ts +14 -0
  53. package/dist/plugins/rds.d.ts.map +1 -0
  54. package/dist/plugins/rds.js +86 -0
  55. package/dist/plugins/rds.js.map +1 -0
  56. package/dist/plugins/snapshot.d.ts +7 -0
  57. package/dist/plugins/snapshot.d.ts.map +1 -0
  58. package/dist/plugins/snapshot.js +29 -0
  59. package/dist/plugins/snapshot.js.map +1 -0
  60. package/dist/presets/nodeApp.d.ts +1 -2
  61. package/dist/presets/nodeApp.d.ts.map +1 -1
  62. package/dist/presets/nodeApp.js +0 -1
  63. package/dist/presets/nodeApp.js.map +1 -1
  64. package/dist/recover.d.ts +19 -0
  65. package/dist/recover.d.ts.map +1 -0
  66. package/dist/recover.js +58 -0
  67. package/dist/recover.js.map +1 -0
  68. package/dist/secrets/store.d.ts +5 -2
  69. package/dist/secrets/store.d.ts.map +1 -1
  70. package/dist/secrets/store.js +25 -14
  71. package/dist/secrets/store.js.map +1 -1
  72. package/dist/sshHost.d.ts +2 -0
  73. package/dist/sshHost.d.ts.map +1 -1
  74. package/dist/sshHost.js +29 -11
  75. package/dist/sshHost.js.map +1 -1
  76. package/dist/steps.d.ts +0 -7
  77. package/dist/steps.d.ts.map +1 -1
  78. package/dist/steps.js +6 -3
  79. package/dist/steps.js.map +1 -1
  80. package/dist/types.d.ts +4 -5
  81. package/dist/types.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/checks.ts +4 -0
  84. package/src/cli/agent.ts +28 -1
  85. package/src/cli/index.ts +118 -29
  86. package/src/config.ts +19 -1
  87. package/src/deploy.ts +101 -29
  88. package/src/dockerfile.ts +7 -11
  89. package/src/environment.ts +24 -0
  90. package/src/host.ts +11 -0
  91. package/src/index.ts +4 -1
  92. package/src/localHost.ts +3 -0
  93. package/src/plugin.ts +50 -0
  94. package/src/plugins/bitwarden.ts +57 -0
  95. package/src/plugins/digitalOcean.ts +111 -0
  96. package/src/plugins/index.ts +4 -0
  97. package/src/plugins/rds.ts +126 -0
  98. package/src/plugins/snapshot.ts +44 -0
  99. package/src/presets/nodeApp.ts +1 -3
  100. package/src/recover.ts +90 -0
  101. package/src/secrets/store.ts +39 -20
  102. package/src/sshHost.ts +35 -11
  103. package/src/steps.ts +7 -6
  104. package/src/types.ts +22 -12
  105. package/dist/secrets/bitwarden.d.ts +0 -3
  106. package/dist/secrets/bitwarden.d.ts.map +0 -1
  107. package/dist/secrets/bitwarden.js +0 -6
  108. package/dist/secrets/bitwarden.js.map +0 -1
  109. package/src/secrets/bitwarden.ts +0 -7
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  </h1>
4
4
 
5
5
  <p align="center">
6
- <a href="https://www.npmjs.com/package/redkite-cd"><img alt="npm version" src="https://img.shields.io/npm/v/redkite-cd.svg"></a>
7
- <a href="https://www.npmjs.com/package/redkite-cd"><img alt="npm downloads" src="https://img.shields.io/npm/dm/redkite-cd.svg"></a>
8
- <a href="https://nodejs.org"><img alt="node" src="https://img.shields.io/node/v/redkite-cd.svg"></a>
9
- <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/redkite-cd.svg"></a>
6
+ <a href="https://www.npmjs.com/package/redkite"><img alt="npm version" src="https://img.shields.io/npm/v/redkite.svg"></a>
7
+ <a href="https://www.npmjs.com/package/redkite"><img alt="npm downloads" src="https://img.shields.io/npm/dm/redkite.svg"></a>
8
+ <a href="https://nodejs.org"><img alt="node" src="https://img.shields.io/node/v/redkite.svg"></a>
9
+ <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/redkite.svg"></a>
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -30,8 +30,10 @@ between deploys.
30
30
  - [Requirements](#requirements)
31
31
  - [Configuration](#configuration)
32
32
  - [Hooks](#hooks)
33
+ - [Plugins](#plugins)
33
34
  - [Verifying a build](#verifying-a-build)
34
35
  - [CLI](#cli)
36
+ - [GitHub Actions](#github-actions)
35
37
  - [How a deploy runs](#how-a-deploy-runs)
36
38
  - [Design](#design)
37
39
  - [Contributing](#contributing)
@@ -40,13 +42,13 @@ between deploys.
40
42
  ## Quick start
41
43
 
42
44
  ```sh
43
- npm install redkite-cd
45
+ npm install redkite
44
46
  ```
45
47
 
46
48
  Add `redkite.config.ts` at the root of your project:
47
49
 
48
50
  ```ts
49
- import { defineDeployment, nextApp, nodeApp, redis } from "redkite-cd";
51
+ import { defineDeployment, nextApp, nodeApp, redis } from "redkite";
50
52
 
51
53
  export default defineDeployment({
52
54
  project: "acme",
@@ -137,7 +139,7 @@ One file each, beside the deployment, named `redkite.<environment>.config.ts`:
137
139
 
138
140
  ```ts
139
141
  // redkite.production.config.ts
140
- import { defineEnvironment } from "redkite-cd";
142
+ import { defineEnvironment } from "redkite";
141
143
 
142
144
  export default defineEnvironment({
143
145
  branch: "main",
@@ -276,7 +278,7 @@ the `branch` an environment names is not consulted at all.
276
278
 
277
279
  The release is the content of the working tree, taken with git's own addressing
278
280
  over a scratch index. It covers what is committed, what is modified and what is
279
- untracked, and honours `.gitignore` which is the same set the build reads. So
281
+ untracked, and honours `.gitignore`, which is the same set the build reads. So
280
282
  an edit you never committed is a new release and gets built, and a change under
281
283
  an ignored `node_modules` is not and does not:
282
284
 
@@ -387,6 +389,25 @@ one lockfile for every package in it, so the install has to see all of them,
387
389
  and `node_modules` is where that put it. An app with its own lockfile in a
388
390
  subdirectory is not covered by `dir` alone.
389
391
 
392
+ ### Reaching the deploy host
393
+
394
+ ```ts
395
+ host: { bastion: "deploy@acme.example", hostKeys: "accept-new" }
396
+ ```
397
+
398
+ The host's key is checked before anything is handed to it. `accept-new`, the
399
+ default, trusts it the first time and refuses it if the key ever changes after
400
+ that: no setup, and nothing to intercept once the first deploy has happened.
401
+ `strict` refuses any host not already in `known_hosts`, which is stronger and
402
+ means putting the key there yourself. `off` checks nothing, for a host whose
403
+ address is handed out again and again and only where nothing on the way to it
404
+ can listen.
405
+
406
+ ssh is never left able to ask a question, whichever of the three it is under: a
407
+ prompt nobody is there to answer is a deploy that hangs rather than one that
408
+ fails. Cloning is separate and always `accept-new`, because the deploy host
409
+ reaches GitHub rather than reaching you.
410
+
390
411
  ### Services
391
412
 
392
413
  Long-lived containers shared by the apps. The proxy is not one of them: apps
@@ -428,13 +449,54 @@ An id is a pointer, not a credential, so it belongs in the config while the
428
449
  credentials arrive at deploy time.
429
450
 
430
451
  ```ts
431
- secrets: bitwarden("00000000-0000-4000-8000-000000000001"),
432
- files: { "/app/service-account.json": bitwarden("...") },
452
+ plugins: [bitwarden()],
453
+ // ...
454
+ secrets: bitwarden.item("00000000-0000-4000-8000-000000000001"),
455
+ files: { "/app/service-account.json": bitwarden.item("...") },
433
456
  ```
434
457
 
435
- A deployment reading from Bitwarden wants `BW_CLIENT_ID`, `BW_CLIENT_SECRET` and
436
- `BW_PASSWORD` in the environment. One that does not needs nothing: redkite only
437
- opens the stores your config actually names.
458
+ The vault is a [plugin](#plugins) like any other, so `bitwarden()` has to be
459
+ registered before `bitwarden.item()` resolves to anything. A deployment that
460
+ names a provider nothing registers is refused before it builds, rather than
461
+ reaching for a vault by name.
462
+
463
+ It unlocks with `BW_KEY`, a session obtained elsewhere, and falls back to
464
+ `BW_CLIENT_ID`, `BW_CLIENT_SECRET` and `BW_PASSWORD` when that is not set. A
465
+ config that would rather name its own variable passes the session in:
466
+ `bitwarden({ secrets: process.env.MY_VAULT_SESSION })`. One that reads no
467
+ secrets at all needs none of them: redkite only opens the stores your config
468
+ actually names.
469
+
470
+ #### Where a secret is, and where it is not
471
+
472
+ **Nothing a vault resolves is ever written into an image.** During the build it
473
+ is mounted as `.env` at the app's root for the length of each step and gone
474
+ after it, so a build can read a credential without shipping one, and no layer
475
+ ever holds it. Copying it in and deleting it later would not do: a deleted file
476
+ is still readable in the layer underneath.
477
+
478
+ At runtime it arrives as the container's own environment, written to the host
479
+ and handed to `docker create --env-file`, so no value appears in an argument
480
+ list and the file goes with the rest of the deploy's scratch. Read it from
481
+ `process.env`; there is no `.env` to read.
482
+
483
+ The same is true of the migrations and checks that run in the builder image:
484
+ they are given `--env-file` too, rather than finding a file the build left
485
+ behind.
486
+
487
+ If you do want one in the image, say so, in a build step where the mount is
488
+ still there:
489
+
490
+ ```ts
491
+ steps: ["yarn build", "cp .env dist/.env"],
492
+ ```
493
+
494
+ `files` is the other way to put a credential in an image on purpose, for the
495
+ ones that have to be a file rather than a variable:
496
+
497
+ ```ts
498
+ files: { "/app/service-account.json": bitwarden.item("...") },
499
+ ```
438
500
 
439
501
  ## Hooks
440
502
 
@@ -468,12 +530,12 @@ config that registers a step at the same point replaces it. That is how one is
468
530
  turned off: put something there that does less.
469
531
 
470
532
  ```ts
471
- import { defineStep } from "redkite-cd";
533
+ import { defineStep } from "redkite";
472
534
 
473
535
  export default defineDeployment({
474
536
  // ...
475
537
  steps: [
476
- defineStep("build:after:sourcemaps", async (built, context) => {
538
+ defineStep("build:after:report", async (built, context) => {
477
539
  for (const app of built.apps) {
478
540
  context.task.detail(`${app.name} at ${app.release.slice(0, 7)}`);
479
541
  }
@@ -513,7 +575,7 @@ A migration is a step like any other. `migrate()` answers with one at
513
575
  the old containers are still serving, and throws before anything retires.
514
576
 
515
577
  ```ts
516
- import { migrate } from "redkite-cd";
578
+ import { migrate } from "redkite";
517
579
 
518
580
  export default defineDeployment({
519
581
  steps: [migrate({ app: "backend", command: "yarn db:migrate" })],
@@ -548,7 +610,7 @@ migrate({ app: "backend", command: "yarn db:migrate", network: "deployment" })
548
610
  `attachment()` answers with the same flags for a step of your own:
549
611
 
550
612
  ```ts
551
- import { attachment, defineStep } from "redkite-cd";
613
+ import { attachment, defineStep } from "redkite";
552
614
 
553
615
  defineStep("swap:before:seed", async (built, context) => {
554
616
  const image = built.apps.find((app) => app.name === "backend")?.builderTag;
@@ -563,37 +625,181 @@ defineStep("swap:before:seed", async (built, context) => {
563
625
  });
564
626
  ```
565
627
 
566
- ### Plugins
628
+ ### Snapshotting a database before a swap
567
629
 
568
- A plugin is a function answering with steps, the way `redis()` answers with a
569
- service spec:
630
+ A migration that goes wrong is the one thing a blue-green swap cannot undo:
631
+ putting the container back does not put the rows back. These take a snapshot
632
+ first, and they sit at `swap:before` so they run while the old containers are
633
+ still serving.
570
634
 
571
635
  ```ts
572
- const slack = (webhook: string) => [
573
- defineStep("swap:before:announce", async (built) => {
574
- await fetch(webhook, { method: "POST", body: `deploying ${built.apps.length} apps` });
575
- return built;
576
- }),
577
- defineStep("cleanup:after:notify", async (finished) => {
578
- await fetch(webhook, { method: "POST", body: `released ${finished.released.join(", ")}` });
579
- return finished;
580
- }),
581
- ];
636
+ steps: [
637
+ // Above the migrate, because steps run in the order they are listed
638
+ rdsSnapshot({ instance: "acme-production", region: "eu-west-1" }),
639
+ migrate({ app: "backend", command: "yarn db:migrate" }),
640
+ ]
582
641
  ```
583
642
 
584
- Read whatever it needs where the config is loaded, so a missing value is a
585
- config that fails rather than a deploy that gets most of the way through:
643
+ `rdsSnapshot` goes through the AWS CLI, which every runner already has with the
644
+ credentials the job was given; signing a request by hand is a page of crypto
645
+ that nothing here could check. It takes an `instance`, or a `cluster` for
646
+ Aurora, and refuses a config naming both. RDS captures the data when the
647
+ snapshot begins rather than when it finishes, so it does not wait by default:
648
+ `wait: true` turns a snapshot that silently failed into a deploy that stops
649
+ before the migration, at the cost of however long the snapshot takes.
586
650
 
587
651
  ```ts
588
- const webhook = process.env.SLACK_WEBHOOK;
589
- if (!webhook) throw new Error("SLACK_WEBHOOK is not set");
652
+ digitalOceanSnapshot({ volume: "e7f8...", tokenFrom: "DIGITALOCEAN_TOKEN" })
653
+ ```
654
+
655
+ DigitalOcean's managed databases have no endpoint that takes a snapshot on
656
+ demand: their backups are automatic and the API only lists them. So what this
657
+ snapshots is the block storage `volume` the data sits on, which for redkite's
658
+ own postgres service is the disk under the container, or the `droplet` when the
659
+ database is the whole machine. A volume snapshot of a running Postgres is crash
660
+ consistent rather than clean, which Postgres is built to survive.
661
+
662
+ The token is named rather than given: a config is committed and a token is not.
663
+ Both plugins check what they can before the run starts, so a missing token or a
664
+ config naming two databases fails while the host is still untouched.
665
+
666
+ ## Plugins
667
+
668
+ A plugin is what a deployment opts into. It adds steps to the run, or teaches
669
+ it to resolve a kind of secret, and **nothing it carries happens until the
670
+ config lists it** in `plugins`. Redkite's own vault is one of these rather than
671
+ something wired in behind them, so the rule has no exceptions to remember.
672
+
673
+ ```ts
674
+ import { bitwarden, defineDeployment, digitalOceanSnapshot, rdsSnapshot } from "redkite";
590
675
 
591
676
  export default defineDeployment({
677
+ project: "acme",
678
+ plugins: [
679
+ bitwarden(),
680
+ rdsSnapshot({ instance: "acme-production", region: "eu-west-1" }),
681
+ ],
592
682
  // ...
593
- steps: [...slack(webhook)],
594
683
  });
595
684
  ```
596
685
 
686
+ `plan` prints what a deployment opted into and where each plugin's steps land,
687
+ so what a run will do is readable without running it:
688
+
689
+ ```
690
+ plugins
691
+ bitwarden 0 steps resolves bitwarden
692
+ rds-snapshot-acme-db 1 step
693
+
694
+ pipeline (deploy)
695
+ setup redkite
696
+ build redkite
697
+ swap:before:snapshot-acme-db rds-snapshot-acme-db
698
+ swap:before:migrate-backend
699
+ swap redkite
700
+ cleanup redkite
701
+ ```
702
+
703
+ A plugin's steps run above the deployment's own, which is why the snapshot
704
+ lands before the migration written under `steps`. Registering the same plugin
705
+ twice is refused, and so is a second plugin claiming a provider another already
706
+ resolves.
707
+
708
+ The ones redkite ships are `bitwarden()`, [`rdsSnapshot()` and
709
+ `digitalOceanSnapshot()`](#snapshotting-a-database-before-a-swap). None of them
710
+ is on unless you say so.
711
+
712
+ ### Writing plugins
713
+
714
+ A plugin is an object with a name, and steps or stores. `definePlugin` is
715
+ identity, but it checks the points where the plugin is written rather than
716
+ where it is registered, so a typo is the plugin's own failure:
717
+
718
+ ```ts
719
+ import { definePlugin, defineStep, type Plugin } from "redkite";
720
+
721
+ export function announce(options: { url: string }): Plugin {
722
+ return definePlugin({
723
+ name: "announce",
724
+ steps: [
725
+ defineStep("swap:after:announce", async (input, context) => {
726
+ if (!input.ok) return input;
727
+
728
+ context.task.detail(`announcing ${input.released.join(", ")}`);
729
+ await fetch(options.url, { method: "POST", body: input.released.join(",") });
730
+
731
+ return input;
732
+ }),
733
+ ],
734
+ });
735
+ }
736
+ ```
737
+
738
+ The input and output types come from the point: a step at `swap:after` is
739
+ handed what the swap answered with, and must answer with the same, which is
740
+ what makes `input.released` typed without annotating anything.
741
+
742
+ A plugin resolving secrets registers a store per provider tag instead. It is
743
+ opened once, and only when a ref actually names that provider:
744
+
745
+ ```ts
746
+ definePlugin({
747
+ name: "onepassword",
748
+ stores: {
749
+ onepassword: async ({ detail }) => {
750
+ detail("unlocking");
751
+ return { read: async (id: string) => await read(id) };
752
+ },
753
+ },
754
+ });
755
+ ```
756
+
757
+ Then a ref pointing at it is `{ provider: "onepassword", id }`, which is what a
758
+ helper like `bitwarden.item()` answers with.
759
+
760
+ ### As a separate package
761
+
762
+ There is nothing to register: a package exports a function, a config imports it
763
+ and lists what it answers with. The one thing to know is that **a plugin has to
764
+ ship JavaScript**. Node strips types from a config file, but it refuses to do
765
+ so for anything under `node_modules`, so a package shipping `.ts` fails to load
766
+ with `Stripping types is currently unsupported for files under node_modules`.
767
+ Compile it and ship `dist`, the way redkite itself does:
768
+
769
+ ```json
770
+ {
771
+ "name": "redkite-plugin-announce",
772
+ "type": "module",
773
+ "main": "./dist/index.js",
774
+ "types": "./dist/index.d.ts",
775
+ "files": ["dist"],
776
+ "scripts": { "build": "tsc index.ts --outDir dist --declaration" },
777
+ "peerDependencies": { "redkite": ">=0.1.7" }
778
+ }
779
+ ```
780
+
781
+ `redkite` belongs in `peerDependencies` rather than `dependencies`: a plugin
782
+ extends the redkite the deployment is already running, and a second copy of it
783
+ would be a second set of types that do not match.
784
+
785
+ ```sh
786
+ npm install redkite-plugin-announce
787
+ ```
788
+
789
+ ```ts
790
+ import { announce } from "redkite-plugin-announce";
791
+
792
+ plugins: [announce({ url: process.env.SLACK_WEBHOOK ?? "" })],
793
+ ```
794
+
795
+ A complete one is in [examples/plugin](examples/plugin). Two things worth
796
+ copying from it: take what the plugin needs as options rather than reading the
797
+ environment at import time, and name the variable a token comes from rather
798
+ than the token itself, so a config stays committable. Anything a plugin can
799
+ check about its own configuration it should check when it is constructed, so a
800
+ mistake is a config that fails to load rather than a deploy that stops with the
801
+ swap ahead of it.
802
+
597
803
  ## Verifying a build
598
804
 
599
805
  `redkite verify` is the same host, the same services and the same images as a
@@ -664,6 +870,8 @@ redkite <command> [environment]
664
870
  plan [environment] Print the derived topology, the pipeline and the nginx
665
871
  deploy [environment] Build, swap, health check, and revert on failure
666
872
  verify [environment] Bring the services up, build, and run each app's checks
873
+ rollback [environment] Put back any app a run moved and did not finish
874
+ down [environment] Stop everything this environment named
667
875
 
668
876
  --config <path> Defaults to redkite.config.ts at the root of the project
669
877
  --local Build the images here and ship them to the host
@@ -767,6 +975,86 @@ the CPU and disk it is using. Nothing will clean up after it but you.
767
975
 
768
976
  The sixth press leaves.
769
977
 
978
+ ## GitHub Actions
979
+
980
+ There is a composite action in this repository. `verify` on a pull request,
981
+ `deploy` on merge:
982
+
983
+ ```yaml
984
+ - uses: actions/checkout@v4
985
+ - uses: JVKdouk/redkite@v1
986
+ with:
987
+ command: deploy
988
+ environment: production
989
+ ssh-key: ${{ secrets.DEPLOY_KEY }}
990
+ env:
991
+ BW_CLIENT_ID: ${{ secrets.BW_CLIENT_ID }}
992
+ BW_CLIENT_SECRET: ${{ secrets.BW_CLIENT_SECRET }}
993
+ BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
994
+ ```
995
+
996
+ Whole workflows are in [examples/actions](examples/actions). The action sets
997
+ Node up, caches `~/.cache/redkite` so the pinned Bitwarden CLI is installed once
998
+ rather than every run, loads the key into an agent for the job, and runs a
999
+ pinned `redkite`.
1000
+
1001
+ The view stands down on its own: it wants a terminal on both stdout and stdin,
1002
+ and a runner has neither, so what a job logs is one line per event.
1003
+
1004
+ A runner's `known_hosts` is empty every time, so the default `accept-new`
1005
+ trusts the deploy host on first sight each run. `hostKeys: "strict"` there means
1006
+ writing the key into `~/.ssh/known_hosts` before the deploy step.
1007
+
1008
+ **The key is only needed where something uses it.** A run that reaches a deploy
1009
+ host, or clones over ssh, wants an agent holding one. A `verify` that builds
1010
+ what the checkout already holds wants nothing: no host to reach, no clone. That
1011
+ is what makes a pull request from a fork's branch testable without handing the
1012
+ job a deploy key.
1013
+
1014
+ **Build on the host, not on the runner.** The obvious move is to point an app at
1015
+ the checkout the job already has, and it is usually the wrong one: a runner is
1016
+ thrown away, so BuildKit starts cold every time and the finished image has to be
1017
+ shipped over ssh. An app that names a `repo` is cloned by the deploy host into a
1018
+ mirror that is already warm, and nothing crosses the wire. Keep `path` for
1019
+ `verify`, where the point is to test the tree in the pull request including what
1020
+ was never pushed.
1021
+
1022
+ **Two runs would race.** Redkite holds no lock, so give the deploy job a
1023
+ `concurrency` group. Leave `cancel-in-progress` false on that one: a deploy
1024
+ interrupted between retiring the old container and starting the new one is the
1025
+ one state nothing downstream can reason about.
1026
+
1027
+ ### Cancelling a run
1028
+
1029
+ A run that is **asked** to stop puts back whatever it moved. Everything from
1030
+ retiring the old container to the health check is one guarded stretch, and an
1031
+ abort anywhere in it reverts the apps that had already moved, on a connection
1032
+ the stop cannot refuse.
1033
+
1034
+ A run that is **killed** never gets that far, which is what a cancelled job is:
1035
+ SIGINT, then the job is torn down after a grace period. So the recovery has to
1036
+ be something a different process can do, reading what the host is in rather
1037
+ than what the dead run remembered. A retired container is the whole signal, and
1038
+ that is what `rollback` looks for:
1039
+
1040
+ ```yaml
1041
+ - if: cancelled()
1042
+ uses: JVKdouk/redkite@v1
1043
+ with:
1044
+ command: rollback
1045
+ environment: production
1046
+ ssh-key: ${{ secrets.DEPLOY_KEY }}
1047
+ ```
1048
+
1049
+ It does nothing when nothing was interrupted, so it is safe to leave in the
1050
+ workflow. `down` is its counterpart for an environment that exists to be built
1051
+ against rather than served from: it stops that environment's containers, the
1052
+ derived proxy included, and stops rather than removes them so the next run
1053
+ adopts them where it left off.
1054
+
1055
+ The build itself is still the one thing a killed job cannot take back with it:
1056
+ it keeps going on the deploy host. Nothing inside the job can change that.
1057
+
770
1058
  ## How a deploy runs
771
1059
 
772
1060
  Everything happens on the deploy host, over a single multiplexed ssh connection
@@ -1 +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"}
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAEA,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;AAiCD,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,QAOzC"}
package/dist/checks.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { envFlags } from "./environment.js";
1
2
  import { appRoot } from "./layout.js";
2
3
  import { quote } from "./shell.js";
3
4
  import { attachment, builderOf } from "./steps.js";
@@ -24,6 +25,9 @@ async function checkApp(app, spec, input, context) {
24
25
  const flags = [
25
26
  "run --rm",
26
27
  ...attachment(spec.network ?? "deployment", context.topology),
28
+ // Before the named settings, so a check can override what the vault says
29
+ // without editing the vault
30
+ ...(await envFlags(app, context)),
27
31
  `--workdir ${appRoot(app.dir)}`,
28
32
  ...settings({ ...app.environment, ...spec.environment }),
29
33
  builderOf(input, app.name),
@@ -1 +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"}
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,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,yEAAyE;QACzE,4BAA4B;QAC5B,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACjC,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"}
@@ -1,2 +1,4 @@
1
+ import type { Deployment } from "../types.js";
2
+ export declare function needsAgent(config: Deployment, environment: string): boolean;
1
3
  export declare function requireAgent(warn: (message: string) => void): string;
2
4
  //# sourceMappingURL=agent.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/cli/agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQ9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAIjE;AAWD,wBAAgB,YAAY,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,UA4B3D"}
package/dist/cli/agent.js CHANGED
@@ -1,6 +1,20 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { environmentOf } from "../config.js";
2
3
  // Cloning private repositories and opening the tunnel both go through the
3
4
  // agent, so a deploy without one fails halfway rather than at the start
5
+ // Asked before one is demanded. A runner has no keys and no way to be prompted
6
+ // for one, and a deployment that reaches no other machine and clones nothing
7
+ // has no use for an agent: insisting on one there is a green run turned red
8
+ export function needsAgent(config, environment) {
9
+ if (environmentOf(config, environment)?.host?.bastion)
10
+ return true;
11
+ return config.apps.some((app) => app.repo !== undefined && overSsh(app.repo));
12
+ }
13
+ // A clone URL that names a transport of its own carries its own credentials.
14
+ // Anything else is the scp-like form, which is ssh and wants a key
15
+ function overSsh(repo) {
16
+ return !/^(https?|git|file):\/\//.test(repo);
17
+ }
4
18
  // A plain writer rather than a Log, because this runs before the view opens:
5
19
  // ssh-add may ask for a passphrase, and it cannot ask through a screen
6
20
  // something else is drawing
@@ -18,7 +32,15 @@ export function requireAgent(warn) {
18
32
  if (pid)
19
33
  process.env["SSH_AGENT_PID"] = pid;
20
34
  // Inherits stdio so a passphrase prompt reaches the person running this
21
- execFileSync("ssh-add", [], { stdio: "inherit" });
35
+ try {
36
+ execFileSync("ssh-add", [], { stdio: "inherit" });
37
+ }
38
+ catch {
39
+ throw new Error("ssh-add found no key to load. This deployment reaches another machine " +
40
+ "or clones over ssh, so it needs an agent holding a key that can. On a " +
41
+ "runner, set one up before this step and leave SSH_AUTH_SOCK in the " +
42
+ "environment");
43
+ }
22
44
  return socket;
23
45
  }
24
46
  //# sourceMappingURL=agent.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/cli/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,0EAA0E;AAC1E,wEAAwE;AAExE,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,MAAkB,EAAE,WAAmB;IAChE,IAAI,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IAEnE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,6EAA6E;AAC7E,mEAAmE;AACnE,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,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,IAAI,CAAC;QACH,YAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,wEAAwE;YACtE,wEAAwE;YACxE,qEAAqE;YACrE,aAAa,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AA8F1D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,YAgBxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,2BASxC;AAuQD,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"}