@envsync-cloud/deploy-cli 0.6.5 → 0.6.7

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 (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +61 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -40,7 +40,7 @@ bunx @envsync-cloud/deploy-cli <command>
40
40
  ```text
41
41
  envsync-deploy preinstall
42
42
  envsync-deploy setup
43
- envsync-deploy bootstrap [--dry-run]
43
+ envsync-deploy bootstrap [--dry-run] [--force]
44
44
  envsync-deploy deploy [--dry-run]
45
45
  envsync-deploy health [--json]
46
46
  envsync-deploy upgrade [--dry-run]
@@ -71,7 +71,7 @@ Bootstrap infra, migrations, RustFS, and OpenFGA:
71
71
  npx @envsync-cloud/deploy-cli bootstrap
72
72
  ```
73
73
 
74
- `bootstrap` is destructive. It removes the existing EnvSync stack, matching containers, network, and managed volumes before rebuilding, and requires typing `ARE YOU SURE?` to continue.
74
+ `bootstrap` is destructive. It removes the existing EnvSync stack, matching containers, network, and managed volumes before rebuilding, and requires typing `yes` to continue. Use `--force` to bypass the prompt in automation or other non-interactive environments.
75
75
 
76
76
  Deploy the pending API and frontend services:
77
77
 
@@ -106,6 +106,7 @@ Preview mutating commands without changing the host:
106
106
 
107
107
  ```bash
108
108
  npx @envsync-cloud/deploy-cli bootstrap --dry-run
109
+ npx @envsync-cloud/deploy-cli bootstrap --force
109
110
  npx @envsync-cloud/deploy-cli deploy --dry-run
110
111
  ```
111
112
 
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var REQUIRED_BOOTSTRAP_ENV_KEYS = [
48
48
  "OPENFGA_MODEL_ID"
49
49
  ];
50
50
  var SEMVER_VERSION_RE = /^\d+\.\d+\.\d+$/;
51
- var currentOptions = { dryRun: false };
51
+ var currentOptions = { dryRun: false, force: false };
52
52
  function formatShellArg(arg) {
53
53
  if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
54
54
  return JSON.stringify(arg);
@@ -221,7 +221,7 @@ async function ask(question, fallback = "") {
221
221
  }
222
222
  async function askRequired(question) {
223
223
  if (!process.stdin.isTTY) {
224
- throw new Error(`${question} confirmation requires an interactive terminal.`);
224
+ throw new Error("Bootstrap confirmation requires an interactive terminal. Re-run with --force to bypass the prompt.");
225
225
  }
226
226
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
227
227
  return await new Promise((resolve) => {
@@ -635,6 +635,10 @@ function renderTraefikDynamicConfig(config) {
635
635
  " loadBalancer:",
636
636
  " servers:",
637
637
  " - url: http://web_nginx:8080",
638
+ " browser-otlp:",
639
+ " loadBalancer:",
640
+ " servers:",
641
+ ` - url: http://clickstack:${config.services.clickstack_otlp_http_port}`,
638
642
  " routers:",
639
643
  " landing-router:",
640
644
  ` rule: Host(\`${hosts.landing}\`)`,
@@ -646,6 +650,18 @@ function renderTraefikDynamicConfig(config) {
646
650
  " service: web",
647
651
  " entryPoints: [websecure]",
648
652
  " tls: {}",
653
+ " landing-otlp-router:",
654
+ ` rule: Host(\`${hosts.landing}\`) && (PathPrefix(\`/v1/traces\`) || PathPrefix(\`/v1/logs\`) || PathPrefix(\`/v1/metrics\`))`,
655
+ " service: browser-otlp",
656
+ " priority: 100",
657
+ " entryPoints: [websecure]",
658
+ " tls: {}",
659
+ " web-otlp-router:",
660
+ ` rule: Host(\`${hosts.app}\`) && (PathPrefix(\`/v1/traces\`) || PathPrefix(\`/v1/logs\`) || PathPrefix(\`/v1/metrics\`))`,
661
+ " service: browser-otlp",
662
+ " priority: 100",
663
+ " entryPoints: [websecure]",
664
+ " tls: {}",
649
665
  " api-router:",
650
666
  ` rule: Host(\`${hosts.api}\`)`,
651
667
  " service: envsync-api",
@@ -666,15 +682,17 @@ function renderNginxConf(kind) {
666
682
  "}"
667
683
  ].join("\n") + "\n";
668
684
  }
669
- function renderFrontendRuntimeConfig(config) {
685
+ function renderFrontendRuntimeConfig(config, kind) {
670
686
  const hosts = domainMap(config.domain.root_domain);
687
+ const otelEndpoint = kind === "web" ? `https://${hosts.app}` : `https://${hosts.landing}`;
671
688
  return `window.__ENVSYNC_RUNTIME_CONFIG__ = ${JSON.stringify({
672
689
  apiBaseUrl: `https://${hosts.api}`,
673
690
  appBaseUrl: `https://${hosts.app}`,
674
691
  authBaseUrl: `https://${hosts.auth}`,
675
692
  keycloakRealm: config.auth.keycloak_realm,
676
693
  webClientId: config.auth.web_client_id,
677
- apiDocsUrl: `https://${hosts.api}/docs`
694
+ apiDocsUrl: `https://${hosts.api}/docs`,
695
+ otelEndpoint
678
696
  }, null, 2)};
679
697
  `;
680
698
  }
@@ -1090,7 +1108,29 @@ function waitForTcpService(config, label, host, port, timeoutSeconds = 120) {
1090
1108
  throw new Error(`Timed out waiting for ${label} at ${host}:${port}`);
1091
1109
  }
1092
1110
  function waitForHttpService(config, label, url, timeoutSeconds = 120) {
1093
- waitForCommand(config, `${label} HTTP readiness`, "alpine:3.20", `wget -q -O /dev/null ${JSON.stringify(url)}`, timeoutSeconds);
1111
+ if (currentOptions.dryRun) {
1112
+ logDryRun(`Would wait for ${label} at ${url}`);
1113
+ return;
1114
+ }
1115
+ logStep(`Waiting for ${label} on ${url}`);
1116
+ const deadline = Date.now() + timeoutSeconds * 1e3;
1117
+ while (Date.now() < deadline) {
1118
+ if (commandSucceeds("docker", [
1119
+ "run",
1120
+ "--rm",
1121
+ "--network",
1122
+ stackNetworkName(config),
1123
+ "alpine:3.20",
1124
+ "sh",
1125
+ "-lc",
1126
+ `wget -q -O /dev/null ${JSON.stringify(url)}`
1127
+ ])) {
1128
+ logSuccess(`${label} is ready`);
1129
+ return;
1130
+ }
1131
+ sleepSeconds(2);
1132
+ }
1133
+ throw new Error(`Timed out waiting for ${label} at ${url}`);
1094
1134
  }
1095
1135
  function runOpenFgaMigrate(config, runtimeEnv) {
1096
1136
  logStep("Running OpenFGA datastore migrations");
@@ -1307,9 +1347,14 @@ async function confirmBootstrapReset(config) {
1307
1347
  logWarn("Containers: none currently matched");
1308
1348
  }
1309
1349
  logWarn("This removes existing deployment data for the managed EnvSync services.");
1310
- const response = await askRequired(chalk.bold.red('Type "ARE YOU SURE?" to continue:'));
1311
- if (response !== "ARE YOU SURE?") {
1312
- throw new Error("Bootstrap aborted. Confirmation did not match 'ARE YOU SURE?'.");
1350
+ if (currentOptions.force) {
1351
+ logWarn("Skipping confirmation because --force was provided.");
1352
+ logSuccess("Destructive bootstrap reset confirmed");
1353
+ return;
1354
+ }
1355
+ const response = await askRequired(chalk.bold.red('Type "yes" to continue:'));
1356
+ if (response !== "yes") {
1357
+ throw new Error("Bootstrap aborted. Confirmation did not match 'yes'.");
1313
1358
  }
1314
1359
  logSuccess("Destructive bootstrap reset confirmed");
1315
1360
  }
@@ -1480,9 +1525,8 @@ async function cmdBootstrap() {
1480
1525
  assertSwarmManager();
1481
1526
  if (currentOptions.dryRun) {
1482
1527
  logWarn("Dry-run mode: bootstrap reset will be previewed but not executed.");
1483
- } else {
1484
- await confirmBootstrapReset(config);
1485
1528
  }
1529
+ await confirmBootstrapReset(config);
1486
1530
  cleanupBootstrapState(config);
1487
1531
  ensureRepoCheckout(config);
1488
1532
  writeDeployArtifacts(config, nextGenerated);
@@ -1513,7 +1557,7 @@ async function cmdBootstrap() {
1513
1557
  run("docker", ["stack", "deploy", "-c", BOOTSTRAP_STACK_FILE, config.services.stack_name]);
1514
1558
  logSuccess("Runtime bootstrap stack deployed");
1515
1559
  }
1516
- waitForHttpService(config, "keycloak", "http://keycloak:8080/health/ready", 180);
1560
+ waitForHttpService(config, "keycloak management readiness", "http://keycloak:9000/health/ready", 180);
1517
1561
  waitForHttpService(config, "openfga", "http://openfga:8090/stores");
1518
1562
  waitForTcpService(config, "minikms", "minikms", 50051);
1519
1563
  const initResult = runBootstrapInit(config);
@@ -1561,8 +1605,8 @@ async function cmdDeploy() {
1561
1605
  }
1562
1606
  extractStaticBundle(config.images.web, `${RELEASES_ROOT}/web/current`);
1563
1607
  extractStaticBundle(config.images.landing, `${RELEASES_ROOT}/landing/current`);
1564
- writeFileMaybe(`${RELEASES_ROOT}/web/current/runtime-config.js`, renderFrontendRuntimeConfig(config));
1565
- writeFileMaybe(`${RELEASES_ROOT}/landing/current/runtime-config.js`, renderFrontendRuntimeConfig(config));
1608
+ writeFileMaybe(`${RELEASES_ROOT}/web/current/runtime-config.js`, renderFrontendRuntimeConfig(config, "web"));
1609
+ writeFileMaybe(`${RELEASES_ROOT}/landing/current/runtime-config.js`, renderFrontendRuntimeConfig(config, "landing"));
1566
1610
  if (currentOptions.dryRun) {
1567
1611
  logDryRun(`Would deploy full stack for ${config.services.stack_name}`);
1568
1612
  logCommand("docker", ["stack", "deploy", "-c", STACK_FILE, config.services.stack_name]);
@@ -1770,9 +1814,10 @@ async function main() {
1770
1814
  const command = argv[0];
1771
1815
  const args = argv.slice(1);
1772
1816
  currentOptions = {
1773
- dryRun: args.includes("--dry-run")
1817
+ dryRun: args.includes("--dry-run"),
1818
+ force: args.includes("--force")
1774
1819
  };
1775
- const positionals = args.filter((arg) => arg !== "--dry-run");
1820
+ const positionals = args.filter((arg) => arg !== "--dry-run" && arg !== "--force");
1776
1821
  switch (command) {
1777
1822
  case "preinstall":
1778
1823
  await cmdPreinstall();
@@ -1803,7 +1848,7 @@ async function main() {
1803
1848
  break;
1804
1849
  default:
1805
1850
  console.log(
1806
- "Usage: envsync-deploy <preinstall|setup|bootstrap|deploy|health|upgrade|upgrade-deps|backup|restore> [--dry-run]"
1851
+ "Usage: envsync-deploy <preinstall|setup|bootstrap|deploy|health|upgrade|upgrade-deps|backup|restore> [--dry-run] [--force]"
1807
1852
  );
1808
1853
  process.exit(command ? 1 : 0);
1809
1854
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envsync-cloud/deploy-cli",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "CLI for self-hosted EnvSync deployment on Docker Swarm",
5
5
  "type": "module",
6
6
  "bin": {