thinkwork-cli 0.12.10 → 0.12.12

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 (2) hide show
  1. package/dist/cli.js +147 -22
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1693,8 +1693,12 @@ async function resolveEnterpriseDeployRequest(options, deps = {}) {
1693
1693
  const repoContext = findEnterpriseDeploymentRepo(cwd);
1694
1694
  const loadDeployment = deps.loadDeployment ?? loadEnterpriseDeployment;
1695
1695
  const stdinIsTty = deps.stdinIsTty ?? Boolean(process.stdin.isTTY);
1696
- const component = options.component ?? "all";
1697
1696
  const inferRepository = deps.inferRepository ?? inferGitHubRepositoryFromRemote;
1697
+ const component = await resolveEnterpriseDeployComponent(
1698
+ options,
1699
+ stdinIsTty,
1700
+ deps
1701
+ );
1698
1702
  const componentCheck = validateEnterpriseDeployComponent(component);
1699
1703
  if (!componentCheck.valid) {
1700
1704
  throw new Error(componentCheck.error);
@@ -1706,13 +1710,13 @@ async function resolveEnterpriseDeployRequest(options, deps = {}) {
1706
1710
  "Customer slug is required for enterprise deploy. Pass --customer <slug>."
1707
1711
  );
1708
1712
  const registry = loadDeployment(customerSlug);
1709
- const stage = options.stage ?? registry?.defaultStage ?? repoContext?.stages[0] ?? (options.bootstrap ? await promptWhenInteractive(
1710
- "Deployment stage:",
1713
+ const stageDefault = registry?.defaultStage ?? repoContext?.stages[0] ?? "dev";
1714
+ const stage = options.stage ?? (options.bootstrap ? await resolveEnterpriseBootstrapStage(
1715
+ registry,
1716
+ repoContext,
1711
1717
  stdinIsTty,
1712
- deps.promptInput,
1713
- "Deployment stage is required for enterprise bootstrap. Pass --stage <name>.",
1714
- "dev"
1715
- ) : "dev");
1718
+ deps
1719
+ ) : await resolveEnterpriseDispatchStage(stageDefault, stdinIsTty, deps));
1716
1720
  const stageCheck = validateStage(stage);
1717
1721
  if (!stageCheck.valid) {
1718
1722
  throw new Error(stageCheck.error);
@@ -1725,12 +1729,19 @@ async function resolveEnterpriseDeployRequest(options, deps = {}) {
1725
1729
  `${customerSlug}/${customerSlug}-thinkwork-deploy`
1726
1730
  );
1727
1731
  const checkoutDir = options.checkoutDir ?? registry?.checkoutDir ?? registry?.targetDir ?? repoContext?.repoRoot ?? join5(resolve3(cwd), `${customerSlug}-thinkwork-deploy`);
1732
+ const runSmokes = await resolveEnterpriseRunSmokes(
1733
+ options,
1734
+ component,
1735
+ stdinIsTty,
1736
+ deps
1737
+ );
1728
1738
  return {
1729
1739
  customerSlug,
1730
1740
  repository,
1731
1741
  checkoutDir: resolve3(checkoutDir),
1732
1742
  stage,
1733
1743
  component,
1744
+ runSmokes,
1734
1745
  bootstrap: options.bootstrap === true,
1735
1746
  wait: options.wait !== false,
1736
1747
  createRepo: options.createRepo === true,
@@ -1835,6 +1846,78 @@ async function runEnterpriseDeploy(options, deps = {}) {
1835
1846
  workflow
1836
1847
  };
1837
1848
  }
1849
+ async function resolveEnterpriseBootstrapStage(registry, repoContext, stdinIsTty, deps) {
1850
+ return registry?.defaultStage ?? repoContext?.stages[0] ?? await promptWhenInteractive(
1851
+ "Deployment stage:",
1852
+ stdinIsTty,
1853
+ deps.promptInput,
1854
+ "Deployment stage is required for enterprise bootstrap. Pass --stage <name>.",
1855
+ "dev"
1856
+ );
1857
+ }
1858
+ async function resolveEnterpriseDispatchStage(defaultValue, stdinIsTty, deps) {
1859
+ if (!stdinIsTty) return defaultValue;
1860
+ return promptWhenInteractive(
1861
+ "Deployment stage:",
1862
+ stdinIsTty,
1863
+ deps.promptInput,
1864
+ "Deployment stage is required for enterprise deploy. Pass --stage <name>.",
1865
+ defaultValue
1866
+ );
1867
+ }
1868
+ async function resolveEnterpriseDeployComponent(options, stdinIsTty, deps) {
1869
+ const defaultValue = options.component ?? "all";
1870
+ if (options.bootstrap || !stdinIsTty) return defaultValue;
1871
+ if (!ENTERPRISE_DEPLOY_COMPONENTS.includes(
1872
+ defaultValue
1873
+ )) {
1874
+ return defaultValue;
1875
+ }
1876
+ return promptSelectWhenInteractive(
1877
+ {
1878
+ message: "Deployment component:",
1879
+ choices: [
1880
+ {
1881
+ name: "all",
1882
+ value: "all",
1883
+ description: "Deploy release artifacts, Terraform, runtimes, overlays, and smokes"
1884
+ },
1885
+ {
1886
+ name: "foundation",
1887
+ value: "foundation",
1888
+ description: "Terraform apply only"
1889
+ },
1890
+ {
1891
+ name: "artifacts",
1892
+ value: "artifacts",
1893
+ description: "Release artifacts, runtime images, and static bundles"
1894
+ },
1895
+ {
1896
+ name: "overlays",
1897
+ value: "overlays",
1898
+ description: "Customer overlays only"
1899
+ },
1900
+ {
1901
+ name: "smokes",
1902
+ value: "smokes",
1903
+ description: "Smoke checks against an existing stage"
1904
+ }
1905
+ ],
1906
+ defaultValue
1907
+ },
1908
+ deps.promptSelect
1909
+ );
1910
+ }
1911
+ async function resolveEnterpriseRunSmokes(options, component, stdinIsTty, deps) {
1912
+ if (options.runSmokes !== void 0) return options.runSmokes;
1913
+ if (options.bootstrap || !stdinIsTty) return true;
1914
+ if (component !== "all" && component !== "smokes") return true;
1915
+ return promptConfirmWhenInteractive(
1916
+ "Run smoke checks after deploy?",
1917
+ true,
1918
+ deps.promptConfirm
1919
+ );
1920
+ }
1838
1921
  async function promptWhenInteractive(message, stdinIsTty, promptInput, nonInteractiveError, defaultValue) {
1839
1922
  if (!stdinIsTty) throw new Error(nonInteractiveError);
1840
1923
  if (promptInput) {
@@ -1845,6 +1928,20 @@ async function promptWhenInteractive(message, stdinIsTty, promptInput, nonIntera
1845
1928
  const value = await input21({ message, default: defaultValue });
1846
1929
  return value.trim() || defaultValue || value;
1847
1930
  }
1931
+ async function promptSelectWhenInteractive(options, promptSelect) {
1932
+ if (promptSelect) return promptSelect(options);
1933
+ const { select: select10 } = await import("@inquirer/prompts");
1934
+ return select10({
1935
+ message: options.message,
1936
+ choices: options.choices,
1937
+ default: options.defaultValue
1938
+ });
1939
+ }
1940
+ async function promptConfirmWhenInteractive(message, defaultValue, promptConfirm) {
1941
+ if (promptConfirm) return promptConfirm(message);
1942
+ const { confirm: confirmPrompt } = await import("@inquirer/prompts");
1943
+ return confirmPrompt({ message, default: defaultValue });
1944
+ }
1848
1945
  function enterpriseBootstrapStages(requestedStage) {
1849
1946
  return [.../* @__PURE__ */ new Set([requestedStage, "prod"])];
1850
1947
  }
@@ -1879,7 +1976,7 @@ async function dispatchEnterpriseWorkflow(request, options, deps, bootstrap) {
1879
1976
  repository: request.repository,
1880
1977
  stage: request.stage,
1881
1978
  component: request.component,
1882
- runSmokes: options.runSmokes ?? true,
1979
+ runSmokes: request.runSmokes,
1883
1980
  wait: request.wait,
1884
1981
  region: bootstrap?.plan.region ?? request.registry?.region
1885
1982
  },
@@ -1985,28 +2082,26 @@ async function runDeployCommand(opts, deps = {}) {
1985
2082
  }
1986
2083
  async function runLocalTerraformDeploy(opts) {
1987
2084
  const startTime = Date.now();
1988
- const stage = await resolveStage({ flag: opts.stage });
2085
+ const initialStage = await resolveStage({ flag: opts.stage });
1989
2086
  const compCheck = validateComponent(opts.component);
1990
2087
  if (!compCheck.valid) {
1991
2088
  printError(compCheck.error);
1992
2089
  process.exit(1);
1993
2090
  }
1994
2091
  const identity = getAwsIdentity();
1995
- printHeader("deploy", stage, identity);
2092
+ printHeader("deploy", initialStage, identity);
1996
2093
  if (!identity) {
1997
2094
  printWarning("Could not resolve AWS identity. Is the AWS CLI configured?");
1998
2095
  }
1999
- if (isProdLike(stage) && !opts.yes) {
2000
- const ok = await confirm(` Stage "${stage}" is production-like. Deploy?`);
2001
- if (!ok) {
2002
- console.log(" Aborted.");
2003
- process.exit(0);
2004
- }
2005
- } else if (!opts.yes) {
2006
- const ok = await confirm(` Deploy to stage "${stage}"?`);
2007
- if (!ok) {
2008
- console.log(" Aborted.");
2009
- process.exit(0);
2096
+ const stage = await confirmLocalDeployStage(initialStage, opts);
2097
+ if (!stage) {
2098
+ console.log(" Aborted.");
2099
+ process.exit(0);
2100
+ }
2101
+ if (stage !== initialStage) {
2102
+ printHeader("deploy", stage, identity);
2103
+ if (!identity) {
2104
+ printWarning("Could not resolve AWS identity. Is the AWS CLI configured?");
2010
2105
  }
2011
2106
  }
2012
2107
  const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
@@ -2031,6 +2126,32 @@ async function runLocalTerraformDeploy(opts) {
2031
2126
  await runPostDeployProbe(stage);
2032
2127
  printSummary("deploy", stage, tiers, startTime);
2033
2128
  }
2129
+ async function confirmLocalDeployStage(initialStage, opts, deps = {}) {
2130
+ if (opts.yes) return initialStage;
2131
+ let stage = initialStage;
2132
+ while (true) {
2133
+ const ok = await (deps.confirm ?? confirm)(deployConfirmMessage(stage));
2134
+ if (ok) return stage;
2135
+ const canPromptForAnotherStage = deps.stdoutIsTty ?? Boolean(process.stdout.isTTY);
2136
+ if (!canPromptForAnotherStage) return null;
2137
+ const nextStage = (await promptAlternativeDeployStage(stage, deps.promptInput)).trim();
2138
+ if (!nextStage) return null;
2139
+ const check = validateStage(nextStage);
2140
+ if (!check.valid) {
2141
+ throw new Error(check.error);
2142
+ }
2143
+ stage = nextStage;
2144
+ }
2145
+ }
2146
+ function deployConfirmMessage(stage) {
2147
+ return isProdLike(stage) ? ` Stage "${stage}" is production-like. Deploy?` : ` Deploy to stage "${stage}"?`;
2148
+ }
2149
+ async function promptAlternativeDeployStage(currentStage, promptInput) {
2150
+ const message = `Deployment stage to deploy instead of "${currentStage}" (blank to abort):`;
2151
+ if (promptInput) return promptInput(message);
2152
+ const { input: input21 } = await import("@inquirer/prompts");
2153
+ return input21({ message });
2154
+ }
2034
2155
  function printEnterpriseDeploySummary(result) {
2035
2156
  const workflow = result.workflow;
2036
2157
  if (result.kind === "bootstrap") {
@@ -2159,7 +2280,11 @@ async function runEnterpriseDestroy(options, deps = {}) {
2159
2280
  "Refusing to destroy an enterprise stage without --yes in a non-interactive session."
2160
2281
  );
2161
2282
  }
2162
- const message = isProdLike(request.stage) ? ` Stage "${request.stage}" is production-like. Destroy the enterprise stage stack for "${request.customerSlug}" via ${request.repository}?` : ` Destroy enterprise stage "${request.stage}" for "${request.customerSlug}" via ${request.repository}?`;
2283
+ const impact = ` This will dispatch operation=destroy to ${request.repository} and permanently remove the "${request.stage}" stage stack for "${request.customerSlug}". Customer-wide bootstrap resources such as the deployment repository, Terraform state bucket, artifact bucket, and OIDC trust are preserved.`;
2284
+ const message = isProdLike(request.stage) ? ` Stage "${request.stage}" is production-like.
2285
+ ${impact}
2286
+ Continue?` : `${impact}
2287
+ Continue?`;
2163
2288
  const ok = await (deps.promptConfirm ?? confirm)(message);
2164
2289
  if (!ok) {
2165
2290
  throw new Error("Enterprise destroy aborted.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkwork-cli",
3
- "version": "0.12.10",
3
+ "version": "0.12.12",
4
4
  "description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",