@tutti-os/nexight-devops 0.5.3 → 0.6.0

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.
package/dist/cli.js CHANGED
@@ -213,7 +213,7 @@ var tuttiComputerPath = (computerId) => computerId ? `${TUTTI_COMPUTER_API_BASE_
213
213
  var runtimeLogRecallCollectionPath = (computerId) => `${tuttiComputerPath(computerId)}/runtime-log-recalls`;
214
214
  var runtimeLogRecallPath = (computerId, taskId) => `${runtimeLogRecallCollectionPath(computerId)}/${encodeURIComponent(taskId)}`;
215
215
  var runtimeLogRecallDownloadPath = (computerId, taskId) => `${runtimeLogRecallPath(computerId, taskId)}:create-download-url`;
216
- var appendQuery = (path2, values) => {
216
+ var appendQuery = (path, values) => {
217
217
  const query = new URLSearchParams();
218
218
  for (const [key, value] of Object.entries(values)) {
219
219
  if (value !== void 0 && value !== "") {
@@ -221,7 +221,7 @@ var appendQuery = (path2, values) => {
221
221
  }
222
222
  }
223
223
  const encoded = query.toString();
224
- return encoded ? `${path2}?${encoded}` : path2;
224
+ return encoded ? `${path}?${encoded}` : path;
225
225
  };
226
226
  var defaultSleep = async (durationMs) => new Promise((resolveSleep) => {
227
227
  setTimeout(resolveSleep, durationMs);
@@ -357,7 +357,7 @@ var AdminApi = class {
357
357
  this.runtime = runtime;
358
358
  this.fetchImpl = fetchImpl;
359
359
  }
360
- async request(method, path2, body) {
360
+ async request(method, path, body) {
361
361
  const headers = {
362
362
  accept: "application/json"
363
363
  };
@@ -369,7 +369,7 @@ var AdminApi = class {
369
369
  headers["content-type"] = "application/json";
370
370
  payload = JSON.stringify(body);
371
371
  }
372
- const response = await this.fetchImpl(`${this.runtime.apiOrigin}${path2}`, {
372
+ const response = await this.fetchImpl(`${this.runtime.apiOrigin}${path}`, {
373
373
  method,
374
374
  headers,
375
375
  body: payload
@@ -385,11 +385,11 @@ var AdminApi = class {
385
385
  }
386
386
  return parsed;
387
387
  }
388
- get(path2) {
389
- return this.request("GET", path2);
388
+ get(path) {
389
+ return this.request("GET", path);
390
390
  }
391
- post(path2, body) {
392
- return this.request("POST", path2, body);
391
+ post(path, body) {
392
+ return this.request("POST", path, body);
393
393
  }
394
394
  inspectToken(token) {
395
395
  return this.post(ADMIN_TOKEN_INSPECT_PATH, token ? { token_value: token } : {});
@@ -1616,8 +1616,8 @@ var createDataFinderSdkTransport = (options) => {
1616
1616
  options.baseUrl
1617
1617
  );
1618
1618
  return {
1619
- request: async (path2, requestOptions) => {
1620
- const response = await client.dataFinder(path2, {
1619
+ request: async (path, requestOptions) => {
1620
+ const response = await client.dataFinder(path, {
1621
1621
  ...requestOptions,
1622
1622
  ...requestOptions.body === void 0 ? {} : {
1623
1623
  body: typeof requestOptions.body === "string" ? requestOptions.body : JSON.stringify(requestOptions.body)
@@ -1666,11 +1666,11 @@ var DataFinderClient = class {
1666
1666
  get pathScope() {
1667
1667
  return this.publicConfig.pathMode === "project" ? `projects/${this.publicConfig.projectId}` : this.publicConfig.appId;
1668
1668
  }
1669
- async request(path2, options = {}, label = `DataFinder request ${path2}`) {
1670
- if (!path2.startsWith("/openapi/")) {
1669
+ async request(path, options = {}, label = `DataFinder request ${path}`) {
1670
+ if (!path.startsWith("/openapi/")) {
1671
1671
  throw new Error("DataFinder request path must start with /openapi/");
1672
1672
  }
1673
- const response = await this.transport.request(path2, options);
1673
+ const response = await this.transport.request(path, options);
1674
1674
  let payload;
1675
1675
  try {
1676
1676
  payload = JSON.parse(response.text);
@@ -2082,8 +2082,198 @@ var registerDataFinderCommand = (program) => {
2082
2082
  registerDataFinderQueryCommand(datafinder);
2083
2083
  };
2084
2084
 
2085
- // src/diagnostics/shared-agent.ts
2086
- import { createHash } from "crypto";
2085
+ // src/commands/doctor.ts
2086
+ var registerDoctorCommand = (program) => {
2087
+ program.command("doctor").description("Run a local readiness check for the CLI shell").action(() => {
2088
+ logger.step("Running basic readiness checks");
2089
+ logger.success("nexight-devops is ready");
2090
+ });
2091
+ };
2092
+
2093
+ // src/lib/datafinder/types.ts
2094
+ var DEFAULT_DATAFINDER_BASE_URL = "https://analytics.volcengineapi.com";
2095
+ var DEFAULT_DATAFINDER_TIMEZONE = "Asia/Shanghai";
2096
+
2097
+ // src/locales/zh-CN.ts
2098
+ var zhCN = {
2099
+ common: {
2100
+ continue: "\u7EE7\u7EED",
2101
+ skip: "\u8DF3\u8FC7"
2102
+ },
2103
+ errors: {
2104
+ missingAws: "\u672A\u68C0\u6D4B\u5230 aws\uFF0C\u8BF7\u5148\u5B8C\u6210 AWS CLI \u5B89\u88C5\u3002",
2105
+ missingGh: "\u672A\u68C0\u6D4B\u5230 gh\uFF0C\u8BF7\u5148\u5B8C\u6210 GitHub CLI \u5B89\u88C5\u3002"
2106
+ },
2107
+ onboard: {
2108
+ adminLoginSucceeded: "\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u6210\u529F",
2109
+ adminSection: "\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u8BBE\u7F6E",
2110
+ adminTokenRequired: "\u7BA1\u7406\u540E\u53F0 Token \u4E0D\u80FD\u4E3A\u7A7A",
2111
+ awsSection: "AWS CLI \u8BBE\u7F6E",
2112
+ complete: "Onboard \u5B8C\u6210",
2113
+ chooseAwsProfile: "\u8BF7\u9009\u62E9\u9ED8\u8BA4 AWS Profile",
2114
+ chooseLocale: "\u8BF7\u9009\u62E9\u547D\u4EE4\u884C\u8BED\u8A00",
2115
+ datafinderAppPathMode: "\u5E94\u7528\u8DEF\u5F84\uFF08\u4F20\u7EDF SaaS / \u79C1\u6709\u5316\uFF09",
2116
+ datafinderCredentialAction: "\u5DF2\u914D\u7F6E DataFinder \u51ED\u8BC1\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
2117
+ datafinderKeepCredentials: "\u4FDD\u7559\u73B0\u6709\u51ED\u8BC1",
2118
+ datafinderProjectPathMode: "\u9879\u76EE\u8DEF\u5F84\uFF08SaaS \u4E91\u539F\u751F\uFF0C\u63A8\u8350\uFF09",
2119
+ datafinderReplaceCredentials: "\u91CD\u65B0\u8F93\u5165\u51ED\u8BC1",
2120
+ datafinderSection: "DataFinder OpenAPI \u8BBE\u7F6E",
2121
+ datafinderSucceeded: "DataFinder \u8FDE\u63A5\u6210\u529F\uFF1A{dashboardName}\uFF0C\u5171 {reportCount} \u4E2A\u62A5\u8868",
2122
+ enterGhToken: "\u8BF7\u8F93\u5165 GitHub Token",
2123
+ enterAdminToken: "\u8BF7\u8F93\u5165 Tutti \u7BA1\u7406\u540E\u53F0 Token",
2124
+ enterDataFinderAk: "\u8BF7\u8F93\u5165 DataFinder AccessKeyId",
2125
+ enterDataFinderAppId: "\u8BF7\u8F93\u5165 DataFinder \u5E94\u7528 ID",
2126
+ enterDataFinderDashboardId: "\u8BF7\u8F93\u5165\u9ED8\u8BA4 DataFinder \u770B\u677F ID",
2127
+ enterDataFinderProjectId: "\u8BF7\u8F93\u5165 DataFinder \u9879\u76EE ID",
2128
+ enterDataFinderSk: "\u8BF7\u8F93\u5165 DataFinder SecretAccessKey",
2129
+ enterDataFinderTimezone: "\u8BF7\u8F93\u5165 DataFinder \u67E5\u8BE2\u65F6\u533A",
2130
+ enterDataFinderUrl: "\u8BF7\u8F93\u5165 DataFinder OpenAPI \u5730\u5740",
2131
+ ghInstallDeferred: "\u672A\u5B8C\u6210 gh \u5B89\u88C5\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u8FD0\u884C nexight-devops onboard --set-mode gh",
2132
+ ghLoginToken: "\u4F7F\u7528 Token",
2133
+ ghLoginWeb: "\u6D4F\u89C8\u5668\u767B\u5F55\uFF08\u63A8\u8350\uFF09",
2134
+ ghSection: "GitHub CLI \u8BBE\u7F6E",
2135
+ installAwsPrompt: "\u672A\u68C0\u6D4B\u5230 aws\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
2136
+ installBrewFailed: "Homebrew \u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u91CD\u8BD5\u3002",
2137
+ installBrewPrompt: "\u672A\u68C0\u6D4B\u5230 Homebrew\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
2138
+ installGhPrompt: "\u672A\u68C0\u6D4B\u5230 gh\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
2139
+ installManualPrefix: "\u8BF7\u5148\u5B89\u88C5 Homebrew\uFF0C\u518D\u6267\u884C",
2140
+ selectDataFinderPathMode: "\u8BF7\u9009\u62E9 DataFinder OpenAPI \u8DEF\u5F84\u6A21\u5F0F",
2141
+ selectGhLoginMode: "\u8BF7\u9009\u62E9 GitHub \u767B\u5F55\u65B9\u5F0F",
2142
+ skipGhSetup: "\u8DF3\u8FC7 GitHub CLI \u8BBE\u7F6E",
2143
+ skipAdminSetup: "\u8DF3\u8FC7\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u8BBE\u7F6E",
2144
+ skipAwsSetup: "\u8DF3\u8FC7 AWS CLI \u8BBE\u7F6E",
2145
+ skipDataFinderSetup: "\u8DF3\u8FC7 DataFinder OpenAPI \u8BBE\u7F6E",
2146
+ awsInstallDeferred: "\u672A\u5B8C\u6210 AWS CLI \u5B89\u88C5\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u8FD0\u884C nexight-devops onboard --set-mode aws",
2147
+ title: "\u5F00\u59CB\u521D\u59CB\u5316 nexight-devops"
2148
+ },
2149
+ release: {
2150
+ common: {
2151
+ approvalIssueUrl: "\u5BA1\u6279\u5355\u5730\u5740: {approvalIssueUrl}",
2152
+ candidateTag: "\u5019\u9009\u7248\u672C: {candidateTag}",
2153
+ controlRunUrl: "\u63A7\u5236\u9762\u8FD0\u884C\u5730\u5740: {controlRunUrl}",
2154
+ releaseBranch: "\u53D1\u5E03\u5206\u652F: {releaseBranch}",
2155
+ releaseProfile: "\u53D1\u5E03 Profile: {releaseProfile}",
2156
+ targetRepo: "\u76EE\u6807\u4ED3\u5E93: {targetRepo}"
2157
+ },
2158
+ approve: {
2159
+ approvalIssueMissing: "\u672A\u627E\u5230\u53EF\u5BA1\u6279\u7684\u53D1\u5E03\u5355\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u76EE\u6807\u4ED3\u5E93\u6216\u68C0\u67E5\u63A7\u5236\u9762\u72B6\u6001\u3002",
2160
+ approvalWorkflowRunUrl: "\u5BA1\u6279\u5DE5\u4F5C\u6D41\u5730\u5740: {approvalWorkflowRunUrl}",
2161
+ cancelled: "\u5DF2\u53D6\u6D88 release approve\u3002",
2162
+ commenting: "\u6B63\u5728\u5BF9\u53D1\u5E03\u5355 #{issueNumber} \u6267\u884C /release-approve",
2163
+ closeIssueWarning: "\u5BA1\u6279\u5355 #{issueNumber} \u81EA\u52A8\u5173\u95ED\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u5173\u95ED\u3002",
2164
+ confirm: "\u786E\u8BA4\u5BF9\u53D1\u5E03\u5355 #{issueNumber}\uFF08{candidateTag} / {releaseBranch}\uFF09\u6267\u884C /release-approve\uFF1F",
2165
+ feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u6B63\u5F0F\u53D1\u7248\u5DF2\u5F00\u59CB\uFF0C\u8BF7\u7ACB\u5373\u5173\u6CE8\u98DE\u4E66\u7FA4\uFF0C\u6301\u7EED\u8DDF\u8FDB\u53D1\u5E03\u72B6\u6001\u3002",
2166
+ noPendingIssues: "\u5F53\u524D\u6CA1\u6709\u5F85\u5BA1\u6279\u7684\u53D1\u5E03\u5355\uFF0C\u76EE\u6807\u4ED3\u5E93 {targetRepo} \u6682\u65E0\u53EF\u5904\u7406\u9879\u3002",
2167
+ selectApprovalIssue: "\u8BF7\u9009\u62E9\u8981\u5BA1\u6279\u7684\u53D1\u5E03\u5355",
2168
+ selectTarget: "\u8BF7\u9009\u62E9\u8981\u5BA1\u6279\u7684\u4ED3\u5E93",
2169
+ success: "release approve \u5DF2\u5B8C\u6210\uFF0C\u6B63\u5F0F\u53D1\u7248\u5DF2\u5F00\u59CB",
2170
+ workflowFailed: "\u5BA1\u6279\u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u5BA1\u6279\u5DE5\u4F5C\u6D41\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {approvalWorkflowRunUrl}"
2171
+ },
2172
+ hotfix: {
2173
+ allowUnmergedHotfix: "\u662F\u5426\u5141\u8BB8 release \u5206\u652F\u4E0A\u7684\u8865\u4E01\u5C1A\u672A\u88AB main \u5438\u6536\u65F6\u76F4\u63A5\u53D1\u8D77 hotfix\uFF1F",
2174
+ approvalIssueAmbiguous: "\u68C0\u6D4B\u5230\u591A\u4E2A\u65B0\u7684 hotfix \u5BA1\u6279\u5355\uFF0C\u8BF7\u68C0\u67E5\u4EE5\u4E0B\u5019\u9009\u9879: {candidateIssueUrls}\uFF1B\u63A7\u5236\u9762\u8FD0\u884C: {controlRunUrl}",
2175
+ approvalIssueMissing: "\u672A\u627E\u5230\u65B0\u521B\u5EFA\u7684 hotfix \u5BA1\u6279\u5355\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}",
2176
+ cancelled: "\u5DF2\u53D6\u6D88 release hotfix\u3002",
2177
+ confirm: "\u786E\u8BA4\u5BF9 {repoName} \u53D1\u8D77 hotfix\uFF0C\u5206\u652F {releaseBranch}\uFF1F",
2178
+ dispatching: "\u6B63\u5728\u51C6\u5907 hotfix: {repoName}",
2179
+ enterReleaseBranch: "\u8BF7\u8F93\u5165 hotfix \u7684 release \u5206\u652F\uFF08\u5FC5\u987B\u4EE5 release/ \u5F00\u5934\uFF09",
2180
+ feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u81EA\u6D4B\u53D1\u7248\u901A\u77E5\u5DF2\u53D1\u9001\u5230\u98DE\u4E66\u7FA4\uFF0C\u8BF7\u5173\u6CE8\u98DE\u4E66\u7FA4\u5E76\u5B8C\u6210\u9A8C\u8BC1\uFF0C\u968F\u540E\u6267\u884C nexight-devops release approve\u3002",
2181
+ invalidReleaseBranch: "release \u5206\u652F\u5FC5\u987B\u662F release/<name> \u683C\u5F0F\u3002",
2182
+ nextSteps: "\u4E0B\u4E00\u6B65\uFF1A\u5B8C\u6210\u9A8C\u8BC1\u540E\uFF0C\u6267\u884C nexight-devops release approve\u3002",
2183
+ overrideMode: "Override \u6A21\u5F0F: {overrideMode}",
2184
+ overrideModeDisabled: "\u672A\u5F00\u542F",
2185
+ overrideModeEnabled: "\u5DF2\u5F00\u542F",
2186
+ overrideReason: "\u8BF7\u8F93\u5165 release \u5206\u652F\u4E0A\u7684\u8865\u4E01\u5C1A\u672A\u88AB main \u5438\u6536\u65F6\u7EE7\u7EED hotfix \u7684\u539F\u56E0",
2187
+ overrideReasonRequired: "\u5F00\u542F release \u5206\u652F\u8865\u4E01 override \u65F6\u5FC5\u987B\u586B\u5199\u539F\u56E0\u3002",
2188
+ workflowFailureReason: "\u5931\u8D25\u539F\u56E0: {failureMessage}",
2189
+ selectTarget: "\u8BF7\u9009\u62E9\u8981\u6267\u884C hotfix \u7684\u4ED3\u5E93",
2190
+ success: "release hotfix \u5DF2\u5B8C\u6210",
2191
+ waiting: "\u63A7\u5236\u9762 hotfix \u5DE5\u4F5C\u6D41\u5DF2\u89E6\u53D1\uFF0C\u6B63\u5728\u7B49\u5F85\u5B8C\u6210: {repoName}",
2192
+ workflowFailed: "controlled-hotfix-release \u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}"
2193
+ },
2194
+ prepare: {
2195
+ approvalIssueAmbiguous: "\u68C0\u6D4B\u5230\u591A\u4E2A\u65B0\u7684\u53D1\u5E03\u5BA1\u6279\u5355\uFF0C\u8BF7\u68C0\u67E5\u4EE5\u4E0B\u5019\u9009\u9879: {candidateIssueUrls}\uFF1B\u63A7\u5236\u9762\u8FD0\u884C: {controlRunUrl}",
2196
+ approvalIssueMissing: "\u672A\u627E\u5230\u65B0\u521B\u5EFA\u7684\u53D1\u5E03\u5BA1\u6279\u5355\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}",
2197
+ cancelled: "\u5DF2\u53D6\u6D88 release prepare\u3002",
2198
+ confirm: "\u786E\u8BA4\u5BF9 {repoName} \u6267\u884C release prepare\uFF1F",
2199
+ dispatching: "\u6B63\u5728\u51C6\u5907\u53D1\u5E03: {repoName}",
2200
+ feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u81EA\u6D4B\u53D1\u7248\u901A\u77E5\u5DF2\u53D1\u9001\u5230\u98DE\u4E66\u7FA4\uFF0C\u8BF7\u5173\u6CE8\u98DE\u4E66\u7FA4\u5E76\u5B8C\u6210\u9A8C\u8BC1\uFF0C\u968F\u540E\u6267\u884C nexight-devops release approve\u3002",
2201
+ nextSteps: "\u4E0B\u4E00\u6B65\uFF1A\u5B8C\u6210\u9A8C\u8BC1\u540E\uFF0C\u6267\u884C nexight-devops release approve\u3002",
2202
+ selectTarget: "\u8BF7\u9009\u62E9\u8981\u51C6\u5907\u53D1\u5E03\u7684\u4ED3\u5E93",
2203
+ success: "release prepare \u5DF2\u5B8C\u6210",
2204
+ waiting: "\u63A7\u5236\u9762\u53D1\u5E03\u5DE5\u4F5C\u6D41\u5DF2\u89E6\u53D1\uFF0C\u6B63\u5728\u7B49\u5F85\u5B8C\u6210: {repoName}",
2205
+ workflowFailed: "controlled-release \u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}"
2206
+ }
2207
+ },
2208
+ ppeLane: {
2209
+ destroy: {
2210
+ atLeastOneRepo: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u4ED3\u5E93",
2211
+ blankLane: "PPE lane \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
2212
+ cancelled: "\u5DF2\u53D6\u6D88\u9500\u6BC1",
2213
+ confirmLane: "\u8FD9\u662F\u9500\u6BC1\u64CD\u4F5C\uFF0C\u8BF7\u518D\u6B21\u8F93\u5165 {lane} \u786E\u8BA4",
2214
+ enterLane: "\u8BF7\u8F93\u5165\u9700\u8981\u79FB\u9664\u7684 PPE lane \u540D\u79F0",
2215
+ failed: "\u4E00\u4E2A\u6216\u591A\u4E2A PPE lane \u9500\u6BC1\u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25",
2216
+ noRecords: "\u8BE5 ppe-lane \u6CA1\u67E5\u8BE2\u5230\u4EFB\u4F55\u90E8\u7F72\u8BB0\u5F55",
2217
+ running: "\u6B63\u5728\u9500\u6BC1 lane {lane}",
2218
+ selectRepos: "\u8BF7\u9009\u62E9\u9700\u8981\u9500\u6BC1\u7684\u4ED3\u5E93",
2219
+ skippedReason: "Cloud Map \u4E2D\u672A\u627E\u5230\u90E8\u7F72\u8BB0\u5F55",
2220
+ warning: "\u5C06\u9500\u6BC1\u4EE5\u4E0B\u4ED3\u5E93: {repos}"
2221
+ }
2222
+ },
2223
+ versionUpdate: {
2224
+ alreadyLatest: "\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C {currentVersion}\u3002",
2225
+ available: "\u68C0\u6D4B\u5230\u65B0\u7248\u672C {latestVersion}\uFF0C\u5F53\u524D\u7248\u672C\u4E3A {currentVersion}\u3002",
2226
+ checkingRegistry: "\u6B63\u5728\u68C0\u67E5\u7248\u672C\u4FE1\u606F",
2227
+ detectingInstallSource: "\u6B63\u5728\u68C0\u6D4B\u5B89\u88C5\u6765\u6E90",
2228
+ majorRequired: "\u68C0\u6D4B\u5230\u5927\u7248\u672C\u66F4\u65B0 {latestVersion}\uFF0C\u5F53\u524D\u7248\u672C {currentVersion} \u5FC5\u987B\u5148\u5347\u7EA7\u540E\u518D\u7EE7\u7EED\u3002",
2229
+ manualUpgrade: "\u65E0\u6CD5\u8BC6\u522B\u5F53\u524D\u5B89\u88C5\u6765\u6E90\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\u4EE5\u4E0B\u4EFB\u4E00\u5347\u7EA7\u547D\u4EE4\uFF1A\n{commands}",
2230
+ optionalUpgradeFailed: "\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF0C\u672C\u6B21\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u624B\u52A8\u6267\u884C\uFF1A{command}",
2231
+ registryCheckFailed: "\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002",
2232
+ registryCheckFailedManual: "\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
2233
+ rerun: "\u5347\u7EA7\u5B8C\u6210\u540E\u8BF7\u91CD\u65B0\u6267\u884C\u521A\u624D\u7684\u547D\u4EE4\u3002",
2234
+ selectAction: "\u68C0\u6D4B\u5230\u53EF\u7528\u66F4\u65B0\uFF0C\u662F\u5426\u73B0\u5728\u5347\u7EA7\uFF1F",
2235
+ skipThisRun: "\u8DF3\u8FC7\u672C\u6B21",
2236
+ skippedForRun: "\u5DF2\u8DF3\u8FC7\u672C\u6B21\u5347\u7EA7\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002",
2237
+ upgradeFailed: "\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF0C\u8BF7\u5148\u624B\u52A8\u6267\u884C\uFF1A{command}",
2238
+ upgradeNow: "\u7ACB\u5373\u5347\u7EA7",
2239
+ upgradeSucceeded: "CLI \u5DF2\u66F4\u65B0\u5230\u6700\u65B0\u7248\u672C {latestVersion}\u3002",
2240
+ upgrading: "\u6B63\u5728\u6267\u884C\u5347\u7EA7\u547D\u4EE4\uFF1A{command}"
2241
+ }
2242
+ };
2243
+
2244
+ // src/lib/i18n.ts
2245
+ var locales = {
2246
+ "zh-CN": zhCN
2247
+ };
2248
+ var getNestedValue = (source, key) => {
2249
+ const value = key.split(".").reduce((current, part) => {
2250
+ if (current && typeof current === "object" && part in current) {
2251
+ return current[part];
2252
+ }
2253
+ return void 0;
2254
+ }, source);
2255
+ return typeof value === "string" ? value : key;
2256
+ };
2257
+ var interpolate = (message, values) => {
2258
+ if (!values) {
2259
+ return message;
2260
+ }
2261
+ return message.replace(/\{(\w+)\}/g, (placeholder, key) => {
2262
+ const value = values[key];
2263
+ if (value === void 0) {
2264
+ return placeholder;
2265
+ }
2266
+ return String(value);
2267
+ });
2268
+ };
2269
+ var createI18n = (locale) => {
2270
+ const messages = locales[locale];
2271
+ return (key, values) => {
2272
+ const messageFromLocale = messages ? getNestedValue(messages, key) : key;
2273
+ const message = messageFromLocale === key ? getNestedValue(zhCN, key) : messageFromLocale;
2274
+ return interpolate(message, values);
2275
+ };
2276
+ };
2087
2277
 
2088
2278
  // src/lib/shell.ts
2089
2279
  import { execa } from "execa";
@@ -2099,598 +2289,435 @@ var runCommand = async (command, args = [], options = {}) => {
2099
2289
  };
2100
2290
  };
2101
2291
 
2102
- // src/lib/cloudwatch-logs.ts
2103
- var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
2104
- var DEFAULT_POLL_INTERVAL_MS = 750;
2105
- var parseAwsJson = (raw, operation) => {
2106
- try {
2107
- return JSON.parse(raw);
2108
- } catch {
2109
- throw new CliError(`AWS ${operation} returned invalid JSON`, 1);
2110
- }
2111
- };
2112
- var runAwsLogs = async (run, args, operation) => {
2113
- const result = await run("aws", args);
2114
- if (result.exitCode !== 0) {
2115
- throw new CliError(result.stderr.trim() || `AWS ${operation} failed`, 1);
2116
- }
2117
- return result.stdout;
2292
+ // src/commands/onboard.ts
2293
+ var onboardModes = ["all", "admin", "aws", "datafinder", "gh", "locale"];
2294
+ var resolveBrewCommand = async () => {
2295
+ const result = await runCommand("/bin/bash", [
2296
+ "-lc",
2297
+ "command -v brew || { [ -x /opt/homebrew/bin/brew ] && printf /opt/homebrew/bin/brew; } || { [ -x /usr/local/bin/brew ] && printf /usr/local/bin/brew; }"
2298
+ ]);
2299
+ const brewPath = result.stdout.trim();
2300
+ return brewPath ? brewPath : null;
2118
2301
  };
2119
- var queryRows = (rows = []) => rows.map(
2120
- (row) => Object.fromEntries(
2121
- row.flatMap(
2122
- (field2) => field2.field === void 0 || field2.value === void 0 ? [] : [[field2.field, field2.value]]
2123
- )
2124
- )
2125
- );
2126
- var queryStats = (stats = {}) => ({
2127
- bytesScanned: stats.bytesScanned ?? 0,
2128
- logGroupsScanned: stats.logGroupsScanned ?? 0,
2129
- recordsMatched: stats.recordsMatched ?? 0,
2130
- recordsScanned: stats.recordsScanned ?? 0
2131
- });
2132
- var epochSeconds = (value) => String(Math.floor(value.getTime() / 1e3));
2133
- var defaultSleep2 = async (durationMs) => new Promise((resolve5) => {
2134
- setTimeout(resolve5, durationMs);
2135
- });
2136
- var queryCloudWatchLogs = async (input2, dependencies = {}) => {
2137
- const run = dependencies.run ?? runCommand;
2138
- const now = dependencies.now ?? Date.now;
2139
- const sleep2 = dependencies.sleep ?? defaultSleep2;
2140
- const timeoutMs = input2.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS;
2141
- const pollIntervalMs = input2.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2142
- const limit = Math.max(1, Math.min(1e4, Math.floor(input2.limit ?? 500)));
2143
- const startedAt = now();
2144
- const startReply = parseAwsJson(
2145
- await runAwsLogs(
2146
- run,
2147
- [
2148
- "logs",
2149
- "start-query",
2150
- "--log-group-name",
2151
- input2.logGroup,
2152
- "--start-time",
2153
- epochSeconds(input2.startTime),
2154
- "--end-time",
2155
- epochSeconds(input2.endTime),
2156
- "--query-string",
2157
- input2.query,
2158
- "--limit",
2159
- String(limit),
2160
- "--profile",
2161
- input2.profile,
2162
- "--region",
2163
- input2.region,
2164
- "--output",
2165
- "json"
2166
- ],
2167
- "Logs Insights start-query"
2168
- ),
2169
- "Logs Insights start-query"
2302
+ var installHomebrew = async () => {
2303
+ const result = await runCommand(
2304
+ "/bin/bash",
2305
+ [
2306
+ "-lc",
2307
+ '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
2308
+ ],
2309
+ { stdio: "inherit" }
2170
2310
  );
2171
- const queryId = startReply.queryId?.trim();
2172
- if (!queryId) {
2173
- throw new CliError("AWS Logs Insights start-query returned no query ID", 1);
2174
- }
2175
- while (now() - startedAt <= timeoutMs) {
2176
- const reply = parseAwsJson(
2177
- await runAwsLogs(
2178
- run,
2179
- [
2180
- "logs",
2181
- "get-query-results",
2182
- "--query-id",
2183
- queryId,
2184
- "--profile",
2185
- input2.profile,
2186
- "--region",
2187
- input2.region,
2188
- "--output",
2189
- "json"
2190
- ],
2191
- "Logs Insights get-query-results"
2192
- ),
2193
- "Logs Insights get-query-results"
2194
- );
2195
- const status = reply.status ?? "Unknown";
2196
- if (status === "Complete") {
2197
- return {
2198
- queryId,
2199
- rows: queryRows(reply.results),
2200
- stats: queryStats(reply.statistics)
2201
- };
2311
+ return result.exitCode === 0;
2312
+ };
2313
+ var ensureBrewPackage = async (packageName, t) => {
2314
+ let brewCommand = await resolveBrewCommand();
2315
+ if (!brewCommand) {
2316
+ const shouldInstallBrew = await ui.confirm({
2317
+ message: t("onboard.installBrewPrompt"),
2318
+ default: true
2319
+ });
2320
+ if (!shouldInstallBrew) {
2321
+ logger.warn(`${t("onboard.installManualPrefix")} brew install ${packageName}`);
2322
+ return false;
2202
2323
  }
2203
- if (status !== "Running" && status !== "Scheduled") {
2204
- throw new CliError(`AWS Logs Insights query ${queryId} ended with status ${status}`, 1);
2324
+ if (!await installHomebrew()) {
2325
+ logger.warn(t("onboard.installBrewFailed"));
2326
+ return false;
2327
+ }
2328
+ brewCommand = await resolveBrewCommand();
2329
+ if (!brewCommand) {
2330
+ logger.warn(t("onboard.installBrewFailed"));
2331
+ return false;
2205
2332
  }
2206
- await sleep2(pollIntervalMs);
2207
2333
  }
2208
- await run("aws", [
2209
- "logs",
2210
- "stop-query",
2211
- "--query-id",
2212
- queryId,
2213
- "--profile",
2214
- input2.profile,
2215
- "--region",
2216
- input2.region,
2217
- "--output",
2218
- "json"
2219
- ]);
2220
- throw new CliError(`AWS Logs Insights query ${queryId} exceeded ${timeoutMs}ms`, 1);
2334
+ const installResult = await runCommand(brewCommand, ["install", packageName], { stdio: "inherit" });
2335
+ return installResult.exitCode === 0;
2221
2336
  };
2222
-
2223
- // src/diagnostics/shared-agent.ts
2224
- var SHARED_AGENT_PLAYBOOK_ID = "shared-agent-connectivity";
2225
- var SHARED_AGENT_PLAYBOOK_VERSION = "1.0.0";
2226
- var DEFAULT_TSH_LOG_GROUP = "/aws/containerinsights/nexight-ojo-us-east-1/application";
2227
- var DEFAULT_TSH_NAMESPACE = "tsh";
2228
- var DEFAULT_TSH_CONTAINER = "tsh-server";
2229
- var DEFAULT_DIAGNOSIS_WINDOW_MINUTES = 30;
2230
- var MAX_DIAGNOSIS_WINDOW_MINUTES = 24 * 60;
2231
- var MAX_IDENTIFIER_LENGTH = 256;
2232
- var QUERY_LIMIT = 500;
2233
- var RESULT_EVENT_LIMIT = 50;
2234
- var PRODUCT_DEVICE_ID = /^(?:dev_[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/;
2235
- var PRODUCT_AUTHORITY_ID = /^deva_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
2236
- var SAFE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
2237
- var asString2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
2238
- var asBoolean2 = (value) => typeof value === "boolean" ? value : void 0;
2239
- var asNumber = (value) => {
2240
- if (typeof value === "number" && Number.isFinite(value)) {
2241
- return value;
2242
- }
2243
- if (typeof value === "string" && value.trim()) {
2244
- const parsed = Number(value);
2245
- return Number.isFinite(parsed) ? parsed : void 0;
2246
- }
2247
- return void 0;
2248
- };
2249
- var timestampValue = (row) => {
2250
- const value = row["@timestamp"];
2251
- const normalized = value && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(value) ? `${value.replace(" ", "T")}Z` : value;
2252
- const parsed = normalized ? new Date(normalized) : void 0;
2253
- return parsed && !Number.isNaN(parsed.getTime()) ? parsed.toISOString() : value || (/* @__PURE__ */ new Date(0)).toISOString();
2254
- };
2255
- var newestFirst = (events) => events.sort((left, right) => Date.parse(right.timestamp) - Date.parse(left.timestamp));
2256
- var validateDiagnosticIdentifier = (label, value) => {
2257
- const normalized = value.trim();
2258
- if (!normalized || normalized.length > MAX_IDENTIFIER_LENGTH || !SAFE_IDENTIFIER.test(normalized)) {
2259
- throw new CliError(`${label} must use 1-${MAX_IDENTIFIER_LENGTH} letters, digits, '.', '_', ':', or '-'`, 1);
2260
- }
2261
- return normalized;
2262
- };
2263
- var parseDiagnosisAnchor = (value) => {
2264
- const normalized = value.trim();
2265
- if (!/(?:z|[+-]\d{2}:\d{2})$/i.test(normalized)) {
2266
- throw new CliError("--at must include an explicit timezone offset or Z", 1);
2267
- }
2268
- const parsed = new Date(normalized);
2269
- if (Number.isNaN(parsed.getTime())) {
2270
- throw new CliError(`Invalid --at timestamp: ${value}`, 1);
2271
- }
2272
- return parsed;
2273
- };
2274
- var resolveDiagnosisWindow = (anchor, windowMinutes = DEFAULT_DIAGNOSIS_WINDOW_MINUTES, now = /* @__PURE__ */ new Date()) => {
2275
- if (!Number.isInteger(windowMinutes) || windowMinutes < 5 || windowMinutes > MAX_DIAGNOSIS_WINDOW_MINUTES) {
2276
- throw new CliError(`--window-minutes must be an integer between 5 and ${MAX_DIAGNOSIS_WINDOW_MINUTES}`, 1);
2337
+ var ensureGhInstalled = async (t) => {
2338
+ const ghVersion = await runCommand("gh", ["--version"]);
2339
+ if (ghVersion.exitCode === 0) {
2340
+ return true;
2277
2341
  }
2278
- if (anchor.getTime() > now.getTime() + 5 * 6e4) {
2279
- throw new CliError("--at cannot be more than five minutes in the future", 1);
2342
+ if (process.platform === "darwin") {
2343
+ const shouldInstall = await ui.confirm({ message: t("onboard.installGhPrompt"), default: true });
2344
+ if (shouldInstall) {
2345
+ return ensureBrewPackage("gh", t);
2346
+ }
2280
2347
  }
2281
- const halfWindowMs = windowMinutes * 6e4 / 2;
2282
- return {
2283
- from: new Date(anchor.getTime() - halfWindowMs),
2284
- to: new Date(Math.min(anchor.getTime() + halfWindowMs, now.getTime()))
2285
- };
2348
+ logger.warn(t("onboard.ghInstallDeferred"));
2349
+ return false;
2286
2350
  };
2287
- var regexEscape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replaceAll("/", "\\/");
2288
- var jsonFieldFilter = (field2, value) => {
2289
- const encoded = JSON.stringify(value);
2290
- return `diagnosticPayload like /"${field2}"\\s*:\\s*"${regexEscape(encoded.slice(1, -1))}"/`;
2291
- };
2292
- var baseDiagnosticQuery = (namespace, container, prefix) => [
2293
- "fields @timestamp, log",
2294
- `| filter kubernetes.namespace_name = ${JSON.stringify(namespace)}`,
2295
- `| filter kubernetes.container_name = ${JSON.stringify(container)}`,
2296
- `| filter log like /diagnostic_prefix=${regexEscape(prefix)}/`,
2297
- "| parse log /diagnostic_payload=(?<diagnosticPayload>\\{.*\\})$/",
2298
- "| filter ispresent(diagnosticPayload)"
2299
- ];
2300
- var buildDescriptorQuery = (input2) => {
2301
- const commands = baseDiagnosticQuery(input2.namespace, input2.container, "RELAY_DESCRIPTOR_DIAGNOSTIC");
2302
- commands.push(`| filter ${jsonFieldFilter("roomId", input2.roomId)}`);
2303
- if (input2.sharedAgentId) {
2304
- commands.push(`| filter ${jsonFieldFilter("sharedAgentId", input2.sharedAgentId)}`);
2305
- }
2306
- commands.push("| display @timestamp, diagnosticPayload", "| sort @timestamp desc", `| limit ${QUERY_LIMIT}`);
2307
- return commands.join("\n");
2308
- };
2309
- var buildHTTPFailureQuery = (input2) => {
2310
- const commands = baseDiagnosticQuery(input2.namespace, input2.container, "HTTP_FAILURE_DIAGNOSTIC");
2311
- const filters = [jsonFieldFilter("roomId", input2.roomId)];
2312
- if (input2.ownerDeviceId) {
2313
- filters.push(jsonFieldFilter("deviceId", input2.ownerDeviceId));
2314
- }
2315
- if (input2.authorityId) {
2316
- filters.push(`diagnosticPayload like /"urlPath"\\s*:\\s*"[^"]*${regexEscape(input2.authorityId)}[^"]*"/`);
2317
- }
2318
- commands.push(`| filter (${filters.join(" or ")})`);
2319
- commands.push("| display @timestamp, diagnosticPayload", "| sort @timestamp desc", `| limit ${QUERY_LIMIT}`);
2320
- return commands.join("\n");
2321
- };
2322
- var buildOwnerLeaseQuery = (input2) => {
2323
- const refs = [...new Set(input2.refs.filter(Boolean))];
2324
- if (refs.length === 0) {
2325
- throw new CliError("Owner lease query requires at least one pseudonymous device or authority reference", 1);
2326
- }
2327
- return [
2328
- "fields @timestamp, log",
2329
- `| filter kubernetes.namespace_name = ${JSON.stringify(input2.namespace)}`,
2330
- `| filter kubernetes.container_name = ${JSON.stringify(input2.container)}`,
2331
- "| filter log like /shared-agent owner lease observation accepted/",
2332
- `| filter (${refs.map((ref) => `log like /${regexEscape(ref)}/`).join(" or ")})`,
2333
- "| display @timestamp, log",
2334
- "| sort @timestamp desc",
2335
- `| limit ${QUERY_LIMIT}`
2336
- ].join("\n");
2337
- };
2338
- var parseDiagnosticPayload = (row) => {
2339
- const payload = row.diagnosticPayload;
2340
- if (!payload) {
2341
- return void 0;
2342
- }
2343
- try {
2344
- const parsed = JSON.parse(payload);
2345
- return parsed && typeof parsed === "object" ? parsed : void 0;
2346
- } catch {
2347
- return void 0;
2351
+ var ensureAwsInstalled = async (t) => {
2352
+ const awsVersion = await runCommand("aws", ["--version"]);
2353
+ if (awsVersion.exitCode === 0) {
2354
+ return true;
2348
2355
  }
2349
- };
2350
- var parseDescriptorEvidence = (rows) => newestFirst(
2351
- rows.flatMap((row) => {
2352
- const payload = parseDiagnosticPayload(row);
2353
- const outcome = asString2(payload?.outcome);
2354
- const roomId = asString2(payload?.roomId);
2355
- const sharedAgentId = asString2(payload?.sharedAgentId);
2356
- if (!payload || payload.prefix !== "RELAY_DESCRIPTOR_DIAGNOSTIC" || !outcome || !roomId || !sharedAgentId) {
2357
- return [];
2358
- }
2359
- return [
2360
- {
2361
- timestamp: timestampValue(row),
2362
- outcome,
2363
- channel: asString2(payload.channel) ?? "unknown",
2364
- roomId,
2365
- sharedAgentId,
2366
- ...asString2(payload.ownerUserId) ? { ownerUserId: asString2(payload.ownerUserId) } : {},
2367
- ...asString2(payload.ownerDeviceId) ? { ownerDeviceId: asString2(payload.ownerDeviceId) } : {},
2368
- ...asString2(payload.reason) ? { reason: asString2(payload.reason) } : {},
2369
- ...asBoolean2(payload.directEligible) !== void 0 ? { directEligible: asBoolean2(payload.directEligible) } : {}
2370
- }
2371
- ];
2372
- })
2373
- ).slice(0, RESULT_EVENT_LIMIT);
2374
- var extractLogValue = (log, field2) => {
2375
- const escapedField = regexEscape(field2);
2376
- const jsonMatch = log.match(new RegExp(`"${escapedField}"\\s*:\\s*(?:"([^"]*)"|([^,}\\s]+))`));
2377
- if (jsonMatch) {
2378
- return (jsonMatch[1] ?? jsonMatch[2])?.trim();
2379
- }
2380
- const keyValueMatch = log.match(new RegExp(`(?:^|\\s)${escapedField}=(?:"([^"]*)"|([^\\s]+))`));
2381
- return (keyValueMatch?.[1] ?? keyValueMatch?.[2])?.trim();
2382
- };
2383
- var parseOwnerLeaseEvidence = (rows) => newestFirst(
2384
- rows.flatMap((row) => {
2385
- const log = row.log ?? "";
2386
- const outcome = extractLogValue(log, "outcome");
2387
- const reasonCode = extractLogValue(log, "reason_code");
2388
- if (!log.includes("shared-agent owner lease observation accepted") || !outcome || !reasonCode) {
2389
- return [];
2390
- }
2391
- const retryable = extractLogValue(log, "retryable");
2392
- return [
2393
- {
2394
- timestamp: timestampValue(row),
2395
- outcome,
2396
- reasonCode,
2397
- clientVersion: extractLogValue(log, "client_version") ?? "unknown",
2398
- leaseStateBefore: extractLogValue(log, "lease_state_before") ?? "unknown",
2399
- leaseStateAfter: extractLogValue(log, "lease_state_after") ?? "unknown",
2400
- ...extractLogValue(log, "observation_id") ? { observationId: extractLogValue(log, "observation_id") } : {},
2401
- ...extractLogValue(log, "owner_device_ref") ? { ownerDeviceRef: extractLogValue(log, "owner_device_ref") } : {},
2402
- ...extractLogValue(log, "authority_ref") ? { authorityRef: extractLogValue(log, "authority_ref") } : {},
2403
- ...retryable === "true" || retryable === "false" ? { retryable: retryable === "true" } : {}
2404
- }
2405
- ];
2406
- })
2407
- ).slice(0, RESULT_EVENT_LIMIT);
2408
- var parseHTTPFailureEvidence = (rows) => newestFirst(
2409
- rows.flatMap((row) => {
2410
- const payload = parseDiagnosticPayload(row);
2411
- if (!payload || payload.prefix !== "HTTP_FAILURE_DIAGNOSTIC") {
2412
- return [];
2356
+ if (process.platform === "darwin") {
2357
+ const shouldInstall = await ui.confirm({ message: t("onboard.installAwsPrompt"), default: true });
2358
+ if (shouldInstall) {
2359
+ return ensureBrewPackage("awscli", t);
2413
2360
  }
2414
- return [
2415
- {
2416
- timestamp: timestampValue(row),
2417
- ...asNumber(payload.statusCode) !== void 0 ? { statusCode: asNumber(payload.statusCode) } : {},
2418
- ...asString2(payload.route) ? { route: asString2(payload.route) } : {},
2419
- ...asString2(payload.urlPath) ? { urlPath: asString2(payload.urlPath) } : {},
2420
- ...asString2(payload.reason) ? { reason: asString2(payload.reason) } : {},
2421
- ...asString2(payload.requestId) ? { requestId: asString2(payload.requestId) } : {},
2422
- ...asString2(payload.roomId) ? { roomId: asString2(payload.roomId) } : {},
2423
- ...asString2(payload.deviceId) ? { deviceId: asString2(payload.deviceId) } : {},
2424
- ...asString2(payload.clientVersion) ? { clientVersion: asString2(payload.clientVersion) } : {}
2425
- }
2426
- ];
2427
- })
2428
- ).slice(0, RESULT_EVENT_LIMIT);
2429
- var ownerGatewayRef = (kind, value) => {
2430
- const normalized = value?.trim().toLowerCase();
2431
- if (!normalized) {
2432
- return void 0;
2433
- }
2434
- const eligible = kind === "device" ? PRODUCT_DEVICE_ID.test(normalized) : PRODUCT_AUTHORITY_ID.test(normalized);
2435
- if (!eligible) {
2436
- return void 0;
2437
- }
2438
- return createHash("sha256").update(["shared-agent-owner-gateway/v1", kind, normalized].join("\0")).digest("hex").slice(0, 24);
2439
- };
2440
- var ownerDeviceRef = (deviceId) => ownerGatewayRef("device", deviceId);
2441
- var ownerAuthorityRef = (authorityId) => ownerGatewayRef("authority", authorityId);
2442
- var attemptQuery = async (name, queryLogs, input2) => {
2443
- try {
2444
- const result = await queryLogs(input2);
2445
- return { result, report: { name, status: "complete", queryId: result.queryId, stats: result.stats } };
2446
- } catch (error) {
2447
- return { report: { name, status: "failed", error: toErrorMessage(error) } };
2448
2361
  }
2362
+ logger.warn(t("onboard.awsInstallDeferred"));
2363
+ return false;
2449
2364
  };
2450
- var classify = (input2) => {
2451
- const latestDescriptor = input2.descriptors[0];
2452
- const latestLease = input2.leases[0];
2453
- if (latestLease && ["failed", "rejected"].includes(latestLease.outcome) && (!latestDescriptor || Date.parse(latestLease.timestamp) >= Date.parse(latestDescriptor.timestamp))) {
2454
- return {
2455
- code: "OWNER_LEASE_UNHEALTHY",
2456
- confidence: "high",
2457
- summary: `Owner \u7EED\u7EA6\u4E3A ${latestLease.outcome}/${latestLease.reasonCode}\uFF0C\u63A7\u5236\u9762\u5DF2\u6536\u5230\u5F02\u5E38\u7EED\u7EA6\u8BC1\u636E\u3002`
2458
- };
2459
- }
2460
- if (latestDescriptor?.outcome === "authority_offline") {
2461
- if (latestLease?.outcome === "recovered" && Date.parse(latestLease.timestamp) >= Date.parse(latestDescriptor.timestamp)) {
2462
- return {
2463
- code: "OWNER_RECOVERED",
2464
- confidence: "high",
2465
- summary: "Descriptor \u8BF7\u6C42\u9047\u5230 Owner \u79BB\u7EBF\uFF0C\u968F\u540E\u670D\u52A1\u7AEF\u89C2\u5BDF\u5230 Owner \u7EED\u7EA6\u6062\u590D\u3002"
2466
- };
2467
- }
2468
- return {
2469
- code: "OWNER_OFFLINE",
2470
- confidence: "high",
2471
- summary: "\u63A7\u5236\u9762\u660E\u786E\u8FD4\u56DE authority_offline\uFF0COwner Presence lease \u5728\u8BF7\u6C42\u65F6\u4E0D\u53EF\u7528\u3002"
2472
- };
2365
+ var runGhSetup = async (t) => {
2366
+ if (!await ensureGhInstalled(t)) {
2367
+ return false;
2473
2368
  }
2474
- if (latestDescriptor?.outcome === "service_error") {
2475
- return {
2476
- code: "CONTROL_PLANE_FAILURE",
2477
- confidence: "high",
2478
- summary: "Descriptor \u7B7E\u53D1\u53D1\u751F\u670D\u52A1\u7AEF\u9519\u8BEF\uFF0C\u9700\u8981\u6309 requestId \u6216\u76F8\u90BB\u670D\u52A1\u7AEF\u65E5\u5FD7\u7EE7\u7EED\u5B9A\u4F4D\u3002"
2479
- };
2369
+ const authStatus = await runCommand("gh", ["auth", "status"]);
2370
+ if (authStatus.exitCode === 0) {
2371
+ return true;
2480
2372
  }
2481
- if (latestDescriptor?.outcome === "rejected") {
2482
- return {
2483
- code: "DESCRIPTOR_REJECTED",
2484
- confidence: "high",
2485
- summary: `Descriptor \u8BF7\u6C42\u88AB\u62D2\u7EDD${latestDescriptor.reason ? `\uFF1A${latestDescriptor.reason}` : "\u3002"}`
2486
- };
2373
+ const loginMode = await ui.select({
2374
+ message: t("onboard.selectGhLoginMode"),
2375
+ choices: [
2376
+ { value: "web", name: t("onboard.ghLoginWeb") },
2377
+ { value: "token", name: t("onboard.ghLoginToken") }
2378
+ ]
2379
+ });
2380
+ if (loginMode === "web") {
2381
+ await runCommand("gh", ["auth", "login", "--web"], { stdio: "inherit" });
2382
+ } else {
2383
+ const token = await ui.password({ message: t("onboard.enterGhToken") });
2384
+ await runCommand("gh", ["auth", "login", "--with-token"], {
2385
+ input: token
2386
+ });
2487
2387
  }
2488
- if (latestDescriptor?.outcome === "issued") {
2388
+ return (await runCommand("gh", ["auth", "status"])).exitCode === 0;
2389
+ };
2390
+ var runAwsSetup = async (defaultProfile, t) => {
2391
+ if (!await ensureAwsInstalled(t)) {
2489
2392
  return {
2490
- code: "DESCRIPTOR_ISSUED",
2491
- confidence: latestLease ? "high" : "medium",
2492
- summary: latestLease ? "\u63A7\u5236\u9762\u5DF2\u7B7E\u53D1 descriptor\uFF0C\u4E14\u5B58\u5728\u5BF9\u5E94 Owner \u7EED\u7EA6\u8BC1\u636E\uFF1B\u540E\u7EED\u5E94\u68C0\u67E5\u5BA2\u6237\u7AEF\u5230 Device Gateway \u7684\u8FDE\u63A5\u9636\u6BB5\u3002" : "\u63A7\u5236\u9762\u5DF2\u7B7E\u53D1 descriptor\uFF0C\u4F46\u5F53\u524D\u7A97\u53E3\u6CA1\u6709\u5BF9\u5E94 Owner \u7EED\u7EA6\u89C2\u6D4B\uFF0C\u540E\u7EED\u5E94\u68C0\u67E5 Device Gateway \u8FDE\u63A5\u9636\u6BB5\u3002"
2393
+ profile: defaultProfile,
2394
+ configured: false
2493
2395
  };
2494
2396
  }
2495
- if (latestLease && ["succeeded", "recovered"].includes(latestLease.outcome)) {
2496
- return {
2497
- code: "OWNER_RENEWING_NO_DESCRIPTOR",
2498
- confidence: "medium",
2499
- summary: "Owner \u5728\u5F53\u524D\u7A97\u53E3\u6709\u5065\u5EB7\u7EED\u7EA6\uFF0C\u4F46\u6CA1\u6709\u627E\u5230\u8BE5\u623F\u95F4\u7684 descriptor \u8BF7\u6C42\u8BC1\u636E\u3002"
2500
- };
2397
+ const profile = await ui.input({
2398
+ message: t("onboard.chooseAwsProfile"),
2399
+ default: defaultProfile
2400
+ });
2401
+ const listProfiles = await runCommand("aws", ["configure", "list-profiles"]);
2402
+ const profiles = listProfiles.stdout.split("\n").map((item) => item.trim()).filter(Boolean);
2403
+ if (!profiles.includes(profile)) {
2404
+ await runCommand("aws", ["configure", "sso", "--profile", profile], { stdio: "inherit" });
2501
2405
  }
2502
- if (input2.failures.length > 0) {
2406
+ const loginResult = await runCommand("aws", ["sso", "login", "--profile", profile], { stdio: "inherit" });
2407
+ if (loginResult.exitCode !== 0) {
2503
2408
  return {
2504
- code: "HTTP_FAILURE_ONLY",
2505
- confidence: "medium",
2506
- summary: "\u53EA\u627E\u5230\u76F8\u5173 HTTP \u5931\u8D25\uFF0C\u672A\u627E\u5230 descriptor \u6216 Owner \u7EED\u7EA6\u8BC1\u636E\u3002"
2409
+ profile,
2410
+ configured: false
2507
2411
  };
2508
2412
  }
2413
+ const identityResult = await runCommand("aws", [
2414
+ "sts",
2415
+ "get-caller-identity",
2416
+ "--profile",
2417
+ profile,
2418
+ "--output",
2419
+ "json"
2420
+ ]);
2509
2421
  return {
2510
- code: "INCONCLUSIVE",
2511
- confidence: "low",
2512
- summary: "\u5F53\u524D\u65F6\u95F4\u7A97\u6CA1\u6709\u8DB3\u591F\u8BC1\u636E\u5224\u65AD\u5171\u4EAB Agent \u8FDE\u63A5\u5931\u8D25\u9636\u6BB5\u3002"
2422
+ profile,
2423
+ configured: identityResult.exitCode === 0
2513
2424
  };
2514
2425
  };
2515
- var diagnoseSharedAgent = async (rawInput, dependencies = {}) => {
2516
- const now = dependencies.now?.() ?? /* @__PURE__ */ new Date();
2517
- const queryLogs = dependencies.queryLogs ?? queryCloudWatchLogs;
2518
- const namespace = validateDiagnosticIdentifier("namespace", rawInput.namespace ?? DEFAULT_TSH_NAMESPACE);
2519
- const container = validateDiagnosticIdentifier("container", rawInput.container ?? DEFAULT_TSH_CONTAINER);
2520
- const roomId = validateDiagnosticIdentifier("room ID", rawInput.roomId);
2521
- const sharedAgentId = rawInput.sharedAgentId ? validateDiagnosticIdentifier("shared Agent ID", rawInput.sharedAgentId) : void 0;
2522
- const providedOwnerDeviceId = rawInput.ownerDeviceId ? validateDiagnosticIdentifier("Owner device ID", rawInput.ownerDeviceId).toLowerCase() : void 0;
2523
- const authorityId = rawInput.authorityId ? validateDiagnosticIdentifier("Authority ID", rawInput.authorityId).toLowerCase() : void 0;
2524
- const window = resolveDiagnosisWindow(rawInput.anchor, rawInput.windowMinutes, now);
2525
- const commonQueryInput = {
2526
- startTime: window.from,
2527
- endTime: window.to,
2528
- logGroup: rawInput.logGroup,
2529
- profile: rawInput.profile,
2530
- region: rawInput.region,
2531
- limit: QUERY_LIMIT
2532
- };
2533
- const descriptorAttempt = await attemptQuery("descriptor", queryLogs, {
2534
- ...commonQueryInput,
2535
- query: buildDescriptorQuery({ namespace, container, roomId, sharedAgentId })
2536
- });
2537
- const descriptors = parseDescriptorEvidence(descriptorAttempt.result?.rows ?? []);
2538
- const descriptorWithOwner = descriptors.find((event) => event.ownerDeviceId || event.ownerUserId);
2539
- const ownerDeviceId = providedOwnerDeviceId ?? descriptorWithOwner?.ownerDeviceId?.toLowerCase();
2540
- const ownerUserId = descriptorWithOwner?.ownerUserId;
2541
- const deviceRef = ownerDeviceId ? ownerDeviceRef(ownerDeviceId) : void 0;
2542
- const authorityRef = authorityId ? ownerAuthorityRef(authorityId) : void 0;
2543
- const refs = [deviceRef, authorityRef].filter((value) => Boolean(value));
2544
- const httpAttemptPromise = attemptQuery("http-failure", queryLogs, {
2545
- ...commonQueryInput,
2546
- query: buildHTTPFailureQuery({ namespace, container, roomId, ownerDeviceId, authorityId })
2547
- });
2548
- const leaseAttemptPromise = refs.length > 0 ? attemptQuery("owner-lease", queryLogs, {
2549
- ...commonQueryInput,
2550
- query: buildOwnerLeaseQuery({ namespace, container, refs })
2551
- }) : Promise.resolve({ report: { name: "owner-lease", status: "skipped" } });
2552
- const [httpAttempt, leaseAttempt] = await Promise.all([httpAttemptPromise, leaseAttemptPromise]);
2553
- const failures = parseHTTPFailureEvidence(httpAttempt.result?.rows ?? []);
2554
- const leases = parseOwnerLeaseEvidence(leaseAttempt.result?.rows ?? []);
2555
- const queryReports = [descriptorAttempt.report, httpAttempt.report, leaseAttempt.report];
2556
- if (!queryReports.some((report) => report.status === "complete")) {
2557
- throw new CliError(
2558
- `All Shared Agent evidence queries failed: ${queryReports.filter((report) => report.status === "failed").map((report) => `${report.name}: ${report.error ?? "unknown error"}`).join("; ")}`,
2559
- 1
2560
- );
2561
- }
2562
- const warnings = queryReports.flatMap(
2563
- (report) => report.status === "failed" ? [`${report.name} query failed: ${report.error ?? "unknown error"}`] : []
2564
- );
2565
- if (ownerDeviceId && !deviceRef) {
2566
- warnings.push("Owner device ID is not a canonical product ID, so server-side pseudonymous lease events cannot be joined.");
2567
- }
2568
- if (authorityId && !authorityRef) {
2569
- warnings.push("Authority ID is not canonical, so server-side pseudonymous lease events cannot be joined.");
2570
- }
2571
- const ownerRenewalFailure = failures.find(
2572
- (failure) => `${failure.route ?? ""} ${failure.urlPath ?? ""}`.includes("/lease/renew")
2573
- );
2574
- const ownerClientVersion = leases[0]?.clientVersion ?? ownerRenewalFailure?.clientVersion;
2575
- const missingEvidence = [];
2576
- if (descriptors.length === 0) {
2577
- missingEvidence.push("No RELAY_DESCRIPTOR_DIAGNOSTIC event matched this room and time window.");
2578
- }
2579
- if (!ownerDeviceId) {
2580
- missingEvidence.push("Owner device identity was not supplied and no issued descriptor exposed it.");
2581
- }
2582
- if (leaseAttempt.report.status === "skipped") {
2583
- missingEvidence.push("Owner lease evidence could not be queried without a canonical Owner device or Authority ID.");
2584
- } else if (leases.length === 0) {
2585
- missingEvidence.push("No sampled Owner lease observation matched the derived pseudonymous reference.");
2426
+ var runAdminSetup = async (config, options, t) => {
2427
+ const apiOrigin = normalizeAdminApiOrigin(options.adminUrl ?? config.admin?.apiOrigin);
2428
+ const token = (await ui.password({ message: t("onboard.enterAdminToken") })).trim();
2429
+ if (!token) {
2430
+ throw new CliError(t("onboard.adminTokenRequired"), 1);
2586
2431
  }
2587
- return {
2588
- schemaVersion: 1,
2589
- playbook: { id: SHARED_AGENT_PLAYBOOK_ID, version: SHARED_AGENT_PLAYBOOK_VERSION },
2590
- generatedAt: now.toISOString(),
2591
- backend: {
2592
- type: "aws-cloudwatch",
2593
- account: rawInput.account,
2594
- profile: rawInput.profile,
2595
- region: rawInput.region,
2596
- logGroup: rawInput.logGroup
2597
- },
2598
- subject: { roomId, ...sharedAgentId ? { sharedAgentId } : {} },
2599
- window: {
2600
- anchor: rawInput.anchor.toISOString(),
2601
- from: window.from.toISOString(),
2602
- to: window.to.toISOString()
2603
- },
2604
- verdict: classify({ descriptors, failures, leases }),
2605
- owner: {
2606
- ...ownerUserId ? { userId: ownerUserId } : {},
2607
- ...ownerDeviceId ? { deviceId: ownerDeviceId } : {},
2608
- ...deviceRef ? { deviceRef } : {},
2609
- ...ownerClientVersion ? { clientVersion: ownerClientVersion } : {}
2610
- },
2611
- descriptor: {
2612
- attemptCount: descriptors.length,
2613
- events: descriptors,
2614
- ...descriptors[0] ? { latest: descriptors[0] } : {}
2615
- },
2616
- ownerLease: {
2617
- observationCount: leases.length,
2618
- events: leases,
2619
- ...leases[0] ? { latest: leases[0] } : {}
2620
- },
2621
- httpFailures: { count: failures.length, events: failures },
2622
- queryReports,
2623
- partial: queryReports.some((report) => report.status === "failed"),
2624
- warnings,
2625
- missingEvidence
2432
+ await new AdminApi({ apiOrigin, token }).inspectToken(token);
2433
+ config.admin = {
2434
+ apiOrigin,
2435
+ token
2626
2436
  };
2437
+ logger.success(t("onboard.adminLoginSucceeded"));
2627
2438
  };
2628
-
2629
- // src/lib/aws.ts
2630
- var parseJson = (raw) => JSON.parse(raw);
2631
- var isSsoAuthFailure = (stderr) => /(token has expired|token.*expired|sso|authorization pending|login)/i.test(stderr);
2632
- var runAws = async (args) => {
2633
- const result = await runCommand("aws", args);
2634
- if (result.exitCode !== 0) {
2635
- throw new CliError(result.stderr || `AWS command failed: aws ${args.join(" ")}`, 1);
2439
+ var inputWithDefault = async (message, value) => (await ui.input({
2440
+ message,
2441
+ ...value ? { default: value } : {}
2442
+ })).trim();
2443
+ var requiredDataFinderValue = (value, label) => {
2444
+ if (!value) {
2445
+ throw new CliError(`DataFinder ${label} \u4E0D\u80FD\u4E3A\u7A7A`, 1);
2636
2446
  }
2637
- return result.stdout;
2447
+ return value;
2638
2448
  };
2639
- var ensureAwsAuthenticated = async (profile) => {
2640
- const identityArgs = ["sts", "get-caller-identity", "--profile", profile, "--output", "json"];
2641
- const firstAttempt = await runCommand("aws", identityArgs);
2642
- if (firstAttempt.exitCode === 0) {
2643
- return;
2644
- }
2645
- if (!isSsoAuthFailure(firstAttempt.stderr)) {
2646
- throw new CliError(firstAttempt.stderr || `AWS authentication failed for profile ${profile}`, 1);
2647
- }
2648
- const loginResult = await runCommand("aws", ["sso", "login", "--profile", profile], {
2649
- stdio: "inherit"
2650
- });
2651
- if (loginResult.exitCode !== 0) {
2652
- throw new CliError(`AWS SSO login failed for profile ${profile}`, 1);
2449
+ var runDataFinderSetup = async (config, options, t) => {
2450
+ const previous = config.datafinder;
2451
+ let accessKeyId = previous?.accessKeyId ?? "";
2452
+ let secretAccessKey = previous?.secretAccessKey ?? "";
2453
+ if (previous?.accessKeyId && previous.secretAccessKey) {
2454
+ const credentialAction = await ui.select({
2455
+ message: t("onboard.datafinderCredentialAction"),
2456
+ choices: [
2457
+ { value: "keep", name: t("onboard.datafinderKeepCredentials") },
2458
+ { value: "replace", name: t("onboard.datafinderReplaceCredentials") }
2459
+ ]
2460
+ });
2461
+ if (credentialAction === "replace") {
2462
+ accessKeyId = "";
2463
+ secretAccessKey = "";
2464
+ }
2653
2465
  }
2654
- const secondAttempt = await runCommand("aws", identityArgs);
2655
- if (secondAttempt.exitCode !== 0) {
2656
- throw new CliError(`AWS authentication is still invalid for profile ${profile}`, 1);
2466
+ if (!accessKeyId || !secretAccessKey) {
2467
+ accessKeyId = (await ui.password({ message: t("onboard.enterDataFinderAk") })).trim();
2468
+ secretAccessKey = (await ui.password({ message: t("onboard.enterDataFinderSk") })).trim();
2657
2469
  }
2658
- };
2659
- var getAwsCallerIdentity = async (profile) => {
2660
- const identity = parseJson(
2661
- await runAws(["sts", "get-caller-identity", "--profile", profile, "--output", "json"])
2470
+ const pathMode = parseDataFinderPathMode(
2471
+ options.datafinderPathMode ?? await ui.select({
2472
+ message: t("onboard.selectDataFinderPathMode"),
2473
+ choices: [
2474
+ { value: "project", name: t("onboard.datafinderProjectPathMode") },
2475
+ { value: "app", name: t("onboard.datafinderAppPathMode") }
2476
+ ],
2477
+ default: previous?.pathMode ?? "project"
2478
+ })
2662
2479
  );
2663
- return {
2664
- account: identity.Account,
2665
- arn: identity.Arn,
2666
- userId: identity.UserId
2667
- };
2668
- };
2669
- var listLaneInstances = async (input2) => {
2670
- const service = parseJson(
2671
- await runAws([
2672
- "servicediscovery",
2673
- "get-service",
2674
- "--id",
2675
- input2.serviceId,
2676
- "--region",
2677
- input2.region,
2678
- "--profile",
2679
- input2.profile,
2680
- "--output",
2681
- "json"
2682
- ])
2683
- ).Service;
2684
- const namespace = parseJson(
2685
- await runAws([
2686
- "servicediscovery",
2687
- "get-namespace",
2688
- "--id",
2689
- service.NamespaceId,
2690
- "--region",
2691
- input2.region,
2692
- "--profile",
2693
- input2.profile,
2480
+ const candidate = {
2481
+ accessKeyId: requiredDataFinderValue(accessKeyId, "AccessKeyId"),
2482
+ appId: requiredDataFinderValue(
2483
+ await inputWithDefault(
2484
+ t("onboard.enterDataFinderAppId"),
2485
+ options.datafinderAppId ?? previous?.appId
2486
+ ),
2487
+ "appId"
2488
+ ),
2489
+ baseUrl: normalizeDataFinderBaseUrl(
2490
+ requiredDataFinderValue(
2491
+ await inputWithDefault(
2492
+ t("onboard.enterDataFinderUrl"),
2493
+ options.datafinderUrl ?? previous?.baseUrl ?? DEFAULT_DATAFINDER_BASE_URL
2494
+ ),
2495
+ "baseUrl"
2496
+ )
2497
+ ),
2498
+ defaultDashboardId: requiredDataFinderValue(
2499
+ await inputWithDefault(
2500
+ t("onboard.enterDataFinderDashboardId"),
2501
+ options.datafinderDashboardId ?? previous?.defaultDashboardId
2502
+ ),
2503
+ "defaultDashboardId"
2504
+ ),
2505
+ pathMode,
2506
+ projectId: requiredDataFinderValue(
2507
+ await inputWithDefault(
2508
+ t("onboard.enterDataFinderProjectId"),
2509
+ options.datafinderProjectId ?? previous?.projectId
2510
+ ),
2511
+ "projectId"
2512
+ ),
2513
+ secretAccessKey: requiredDataFinderValue(secretAccessKey, "SecretAccessKey"),
2514
+ timezone: requiredDataFinderValue(
2515
+ await inputWithDefault(
2516
+ t("onboard.enterDataFinderTimezone"),
2517
+ previous?.timezone ?? DEFAULT_DATAFINDER_TIMEZONE
2518
+ ),
2519
+ "timezone"
2520
+ )
2521
+ };
2522
+ const summary = await new DataFinderClient(candidate).checkConnection();
2523
+ config.datafinder = toStoredDataFinderConfig(candidate);
2524
+ config.setup.datafinderConfigured = true;
2525
+ logger.success(
2526
+ t("onboard.datafinderSucceeded", {
2527
+ dashboardName: summary.dashboardName ?? summary.dashboardId,
2528
+ reportCount: summary.reportCount
2529
+ })
2530
+ );
2531
+ };
2532
+ var runOnboard = async (options) => {
2533
+ const config = await loadConfig();
2534
+ const t = createI18n(config.locale);
2535
+ const mode = options.setMode ?? "all";
2536
+ if (!onboardModes.includes(mode)) {
2537
+ throw new CliError(`Invalid onboard mode: ${mode}`, 1);
2538
+ }
2539
+ if (mode === "all" || mode === "locale") {
2540
+ const locale = await ui.select({
2541
+ message: t("onboard.chooseLocale"),
2542
+ choices: [
2543
+ { value: "zh-CN", name: "\u4E2D\u6587" },
2544
+ { value: "skip", name: t("common.skip") }
2545
+ ]
2546
+ });
2547
+ if (locale !== "skip") {
2548
+ config.locale = locale;
2549
+ }
2550
+ config.setup.localeConfigured = true;
2551
+ }
2552
+ if (mode === "all" || mode === "admin") {
2553
+ const action = mode === "admin" ? "continue" : await ui.select({
2554
+ message: t("onboard.adminSection"),
2555
+ choices: [
2556
+ { value: "continue", name: t("common.continue") },
2557
+ { value: "skip", name: t("common.skip") }
2558
+ ]
2559
+ });
2560
+ if (action === "continue") {
2561
+ await runAdminSetup(config, options, t);
2562
+ } else {
2563
+ logger.info(t("onboard.skipAdminSetup"));
2564
+ }
2565
+ }
2566
+ if (mode === "all" || mode === "datafinder") {
2567
+ const action = mode === "datafinder" ? "continue" : await ui.select({
2568
+ message: t("onboard.datafinderSection"),
2569
+ choices: [
2570
+ { value: "continue", name: t("common.continue") },
2571
+ { value: "skip", name: t("common.skip") }
2572
+ ]
2573
+ });
2574
+ if (action === "continue") {
2575
+ await runDataFinderSetup(config, options, t);
2576
+ } else {
2577
+ logger.info(t("onboard.skipDataFinderSetup"));
2578
+ }
2579
+ }
2580
+ if (mode === "all" || mode === "gh") {
2581
+ const action = mode === "gh" ? "continue" : await ui.select({
2582
+ message: t("onboard.ghSection"),
2583
+ choices: [
2584
+ { value: "continue", name: t("common.continue") },
2585
+ { value: "skip", name: t("common.skip") }
2586
+ ]
2587
+ });
2588
+ if (action === "continue") {
2589
+ config.setup.ghConfigured = await runGhSetup(t);
2590
+ } else {
2591
+ logger.info(t("onboard.skipGhSetup"));
2592
+ config.setup.ghConfigured = true;
2593
+ }
2594
+ }
2595
+ if (mode === "all" || mode === "aws") {
2596
+ const action = mode === "aws" ? "continue" : await ui.select({
2597
+ message: t("onboard.awsSection"),
2598
+ choices: [
2599
+ { value: "continue", name: t("common.continue") },
2600
+ { value: "skip", name: t("common.skip") }
2601
+ ]
2602
+ });
2603
+ if (action === "continue") {
2604
+ const result = await runAwsSetup(config.defaultAwsProfile, t);
2605
+ config.setup.awsConfigured = result.configured;
2606
+ if (result.configured) {
2607
+ config.defaultAwsProfile = result.profile;
2608
+ }
2609
+ } else {
2610
+ logger.info(t("onboard.skipAwsSetup"));
2611
+ config.setup.awsConfigured = true;
2612
+ }
2613
+ }
2614
+ await saveConfig(config);
2615
+ logger.success(t("onboard.complete"));
2616
+ };
2617
+ var registerOnboardCommand = (program) => {
2618
+ program.command("onboard").description("Install or configure local prerequisites").option("--set-mode <mode>", "setup only one flow: all|admin|aws|datafinder|gh|locale").option("--admin-url <url>", "management API URL or admin.tutti.sh page URL").option("--datafinder-url <url>", "DataFinder OpenAPI origin").option("--datafinder-project-id <id>", "DataFinder project ID").option("--datafinder-app-id <id>", "DataFinder application ID").option("--datafinder-dashboard-id <id>", "default DataFinder dashboard ID").option("--datafinder-path-mode <mode>", "DataFinder API path mode: project|app").action((options) => runOnboard(options));
2619
+ };
2620
+
2621
+ // src/constants/ppe-lane-repos.ts
2622
+ var ppeLaneRepos = [
2623
+ {
2624
+ key: "tutti-server",
2625
+ label: "tutti-server",
2626
+ repoFullName: "tutti-lab/tutti-server",
2627
+ deployWorkflowFile: "deploy-lane.yml",
2628
+ destroyWorkflowFile: "destroy-lane.yml",
2629
+ defaultRef: "main",
2630
+ defaultDestroySelection: false,
2631
+ cloudMapServiceId: "srv-izinli73rwxpf2dr",
2632
+ serviceName: "tutti-server"
2633
+ },
2634
+ {
2635
+ key: "zk-admin-server",
2636
+ label: "zk-admin-server",
2637
+ repoFullName: "zk-org-test/zk-admin-server",
2638
+ deployWorkflowFile: "deploy-lane.yml",
2639
+ destroyWorkflowFile: "destroy-lane.yml",
2640
+ defaultRef: "main",
2641
+ cloudMapServiceId: "srv-s4fn35h7nen4u5ci",
2642
+ serviceName: "admin"
2643
+ },
2644
+ {
2645
+ key: "account",
2646
+ label: "account",
2647
+ repoFullName: "zk-org-test/account",
2648
+ deployWorkflowFile: "deploy-lane.yml",
2649
+ destroyWorkflowFile: "destroy-lane.yml",
2650
+ defaultRef: "main",
2651
+ cloudMapServiceId: "srv-j2newpni247wsn7h",
2652
+ serviceName: "account"
2653
+ },
2654
+ {
2655
+ key: "zk-websocket",
2656
+ label: "zk-websocket",
2657
+ repoFullName: "zk-org-test/zk-websocket",
2658
+ deployWorkflowFile: "deploy-lane.yml",
2659
+ destroyWorkflowFile: "destroy-lane.yml",
2660
+ defaultRef: "main",
2661
+ cloudMapServiceId: "srv-34fi45edgao7tp2r",
2662
+ serviceName: "zk-websocket"
2663
+ }
2664
+ ];
2665
+
2666
+ // src/lib/aws.ts
2667
+ var parseJson = (raw) => JSON.parse(raw);
2668
+ var isSsoAuthFailure = (stderr) => /(token has expired|token.*expired|sso|authorization pending|login)/i.test(stderr);
2669
+ var runAws = async (args) => {
2670
+ const result = await runCommand("aws", args);
2671
+ if (result.exitCode !== 0) {
2672
+ throw new CliError(result.stderr || `AWS command failed: aws ${args.join(" ")}`, 1);
2673
+ }
2674
+ return result.stdout;
2675
+ };
2676
+ var ensureAwsAuthenticated = async (profile) => {
2677
+ const identityArgs = ["sts", "get-caller-identity", "--profile", profile, "--output", "json"];
2678
+ const firstAttempt = await runCommand("aws", identityArgs);
2679
+ if (firstAttempt.exitCode === 0) {
2680
+ return;
2681
+ }
2682
+ if (!isSsoAuthFailure(firstAttempt.stderr)) {
2683
+ throw new CliError(firstAttempt.stderr || `AWS authentication failed for profile ${profile}`, 1);
2684
+ }
2685
+ const loginResult = await runCommand("aws", ["sso", "login", "--profile", profile], {
2686
+ stdio: "inherit"
2687
+ });
2688
+ if (loginResult.exitCode !== 0) {
2689
+ throw new CliError(`AWS SSO login failed for profile ${profile}`, 1);
2690
+ }
2691
+ const secondAttempt = await runCommand("aws", identityArgs);
2692
+ if (secondAttempt.exitCode !== 0) {
2693
+ throw new CliError(`AWS authentication is still invalid for profile ${profile}`, 1);
2694
+ }
2695
+ };
2696
+ var listLaneInstances = async (input2) => {
2697
+ const service = parseJson(
2698
+ await runAws([
2699
+ "servicediscovery",
2700
+ "get-service",
2701
+ "--id",
2702
+ input2.serviceId,
2703
+ "--region",
2704
+ input2.region,
2705
+ "--profile",
2706
+ input2.profile,
2707
+ "--output",
2708
+ "json"
2709
+ ])
2710
+ ).Service;
2711
+ const namespace = parseJson(
2712
+ await runAws([
2713
+ "servicediscovery",
2714
+ "get-namespace",
2715
+ "--id",
2716
+ service.NamespaceId,
2717
+ "--region",
2718
+ input2.region,
2719
+ "--profile",
2720
+ input2.profile,
2694
2721
  "--output",
2695
2722
  "json"
2696
2723
  ])
@@ -2722,11 +2749,6 @@ var listLaneInstances = async (input2) => {
2722
2749
  };
2723
2750
  };
2724
2751
 
2725
- // src/lib/github.ts
2726
- import { mkdtemp as mkdtemp2, readFile as readFile2, rm } from "fs/promises";
2727
- import os from "os";
2728
- import path from "path";
2729
-
2730
2752
  // src/constants/release-targets.ts
2731
2753
  var CONTROL_REPO_FULL_NAME = "zk-org-test/github-action";
2732
2754
  var CONTROL_RELEASE_PREPARE_WORKFLOW_FILE = "controlled-release.yml";
@@ -2740,30 +2762,6 @@ var releaseTargets = [
2740
2762
  repoFullName: "zk-org-test/account",
2741
2763
  defaultReleaseProfile: "production"
2742
2764
  },
2743
- {
2744
- key: "design-project",
2745
- label: "design-project",
2746
- repoFullName: "zk-org-test/design-project",
2747
- defaultReleaseProfile: "production"
2748
- },
2749
- {
2750
- key: "zk-web",
2751
- label: "zk-web",
2752
- repoFullName: "zk-org-test/zk-web",
2753
- defaultReleaseProfile: "production"
2754
- },
2755
- {
2756
- key: "zk-agent-engine",
2757
- label: "zk-agent-engine",
2758
- repoFullName: "zk-org-test/zk-agent-engine",
2759
- defaultReleaseProfile: "production"
2760
- },
2761
- {
2762
- key: "zk-commerce",
2763
- label: "zk-commerce",
2764
- repoFullName: "zk-org-test/zk-commerce",
2765
- defaultReleaseProfile: "production"
2766
- },
2767
2765
  {
2768
2766
  key: "zk-admin-server",
2769
2767
  label: "zk-admin-server",
@@ -2946,17 +2944,6 @@ var ensureGhAuthenticated = async () => {
2946
2944
  throw new CliError("GitHub CLI authentication is required", 1);
2947
2945
  }
2948
2946
  };
2949
- var getCurrentGitHubLogin = async () => {
2950
- const result = await runCommand("gh", ["api", "user", "--jq", ".login"]);
2951
- if (result.exitCode !== 0) {
2952
- throw new CliError("Failed to resolve current GitHub login", 1);
2953
- }
2954
- const login = result.stdout.trim();
2955
- if (!login) {
2956
- throw new CliError("Failed to resolve current GitHub login", 1);
2957
- }
2958
- return login;
2959
- };
2960
2947
  var resolveBranchForRepo = async (input2) => {
2961
2948
  const result = await runCommand("gh", [
2962
2949
  "api",
@@ -3003,32 +2990,6 @@ var dispatchControlWorkflow = async (input2) => {
3003
2990
  throw new CliError(`Failed to dispatch ${input2.workflowFile} for ${CONTROL_REPO_FULL_NAME}`, 1);
3004
2991
  }
3005
2992
  };
3006
- var dispatchCanvasStagingVersionWorkflow = async (input2) => {
3007
- const args = [
3008
- "api",
3009
- `repos/${input2.repoFullName}/actions/workflows/${input2.workflowFile}/dispatches`,
3010
- "--method",
3011
- "POST",
3012
- "-f",
3013
- `ref=${input2.workflowRef}`,
3014
- "-f",
3015
- `inputs[target_ref]=${input2.targetRef}`
3016
- ];
3017
- if (input2.versionMessage) {
3018
- args.push("-f", `inputs[version_message]=${input2.versionMessage}`);
3019
- }
3020
- if (input2.versionTag) {
3021
- args.push("-f", `inputs[version_tag]=${input2.versionTag}`);
3022
- }
3023
- const result = await runCommand("gh", args);
3024
- if (result.exitCode !== 0) {
3025
- const detail = getCommandFailureDetail(result.stderr, result.stdout);
3026
- throw new CliError(
3027
- detail ? `Failed to dispatch ${input2.workflowFile} for ${input2.repoFullName}: ${detail}` : `Failed to dispatch ${input2.workflowFile} for ${input2.repoFullName}`,
3028
- 1
3029
- );
3030
- }
3031
- };
3032
2993
  var dispatchWorkflow = async (input2) => {
3033
2994
  const result = await runCommand("gh", [
3034
2995
  "api",
@@ -3066,50 +3027,6 @@ var listOpenIssueIds = async (repoFullName) => {
3066
3027
  }
3067
3028
  return JSON.parse(result.stdout).map((issue) => issue.id);
3068
3029
  };
3069
- var parseCanvasStagingVersionArtifact = (payload) => {
3070
- const parsed = JSON.parse(payload);
3071
- if (!parsed.deployment_id || !parsed.version_id || !parsed.override_header || !parsed.worker_name || !parsed.workflow_run_url) {
3072
- throw new CliError("Failed to parse canvas staging version artifact metadata", 1);
3073
- }
3074
- return {
3075
- deploymentId: String(parsed.deployment_id),
3076
- overrideHeader: String(parsed.override_header),
3077
- previewAliasUrl: parsed.preview_alias_url ? String(parsed.preview_alias_url) : null,
3078
- previewUrl: parsed.preview_url ? String(parsed.preview_url) : null,
3079
- versionId: String(parsed.version_id),
3080
- versionMessage: String(parsed.version_message ?? ""),
3081
- versionTag: String(parsed.version_tag ?? ""),
3082
- workerName: String(parsed.worker_name),
3083
- workflowRunUrl: String(parsed.workflow_run_url)
3084
- };
3085
- };
3086
- var fetchCanvasStagingVersionArtifact = async (input2) => {
3087
- const tempDir = await mkdtemp2(path.join(os.tmpdir(), "nexight-devops-canvas-"));
3088
- try {
3089
- const downloadResult = await runCommand("gh", [
3090
- "run",
3091
- "download",
3092
- String(input2.runId),
3093
- "--repo",
3094
- input2.repoFullName,
3095
- "--name",
3096
- input2.artifactName,
3097
- "--dir",
3098
- tempDir
3099
- ]);
3100
- if (downloadResult.exitCode !== 0) {
3101
- const detail = getCommandFailureDetail(downloadResult.stderr, downloadResult.stdout);
3102
- throw new CliError(
3103
- detail ? `Failed to download artifact ${input2.artifactName} for run ${input2.runId}: ${detail}` : `Failed to download artifact ${input2.artifactName} for run ${input2.runId}`,
3104
- 1
3105
- );
3106
- }
3107
- const payload = await readFile2(path.join(tempDir, input2.artifactFileName), "utf8");
3108
- return parseCanvasStagingVersionArtifact(payload);
3109
- } finally {
3110
- await rm(tempDir, { recursive: true, force: true });
3111
- }
3112
- };
3113
3030
  var listControlledReleaseIssues = async (state) => {
3114
3031
  const result = await runCommand("gh", [
3115
3032
  "api",
@@ -3306,780 +3223,63 @@ var pollWorkflowRun = async (input2) => {
3306
3223
  try {
3307
3224
  runs = await listWorkflowRuns(input2);
3308
3225
  break;
3309
- } catch (error) {
3310
- if (!(error instanceof CliError) || !isTransientWorkflowRunReadFailure(error.message) || readAttempt === TRANSIENT_WORKFLOW_RUN_READ_ATTEMPTS - 1) {
3311
- throw error;
3312
- }
3313
- input2.onUpdate?.(trackedRunId === null ? { status: "pending" } : { runId: trackedRunId, status: "pending" });
3314
- await sleep(intervalMs);
3315
- }
3316
- }
3317
- if (!runs) {
3318
- throw new CliError(`Timed out waiting for workflow run in ${input2.repoFullName}`, 1);
3319
- }
3320
- if (trackedRunId === null) {
3321
- const candidates = runs.filter((run) => !knownRunIds.has(run.id)).filter((run) => matchesWorkflowRunCorrelation(run, input2.correlation));
3322
- if (candidates.length > 1) {
3323
- throw new CliError(`Ambiguous workflow run selection for ${input2.repoFullName}`, 1);
3324
- }
3325
- if (candidates.length === 0) {
3326
- input2.onUpdate?.({ status: "pending" });
3327
- await sleep(intervalMs);
3328
- continue;
3329
- }
3330
- trackedRunId = candidates[0]?.id ?? null;
3331
- }
3332
- const matchedRun = runs.find((run) => run.id === trackedRunId);
3333
- if (!matchedRun) {
3334
- input2.onUpdate?.({ runId: trackedRunId, status: "pending" });
3335
- await sleep(intervalMs);
3336
- continue;
3337
- }
3338
- input2.onUpdate?.({
3339
- runId: trackedRunId,
3340
- htmlUrl: matchedRun.html_url,
3341
- status: matchedRun.status === "completed" ? "completed" : "in_progress"
3342
- });
3343
- if (matchedRun.status === "completed") {
3344
- const completion = {
3345
- conclusion: matchedRun.conclusion ?? "unknown",
3346
- htmlUrl: matchedRun.html_url,
3347
- runId: matchedRun.id,
3348
- status: matchedRun.status
3349
- };
3350
- if (completion.conclusion !== "success") {
3351
- const failureMessage = await fetchWorkflowRunFailureMessage({
3352
- repoFullName: input2.repoFullName,
3353
- runId: matchedRun.id
3354
- });
3355
- if (failureMessage) {
3356
- completion.failureMessage = failureMessage;
3357
- }
3358
- }
3359
- return completion;
3360
- }
3361
- await sleep(intervalMs);
3362
- }
3363
- throw new CliError(`Timed out waiting for workflow run in ${input2.repoFullName}`, 1);
3364
- };
3365
-
3366
- // src/lib/prerequisites.ts
3367
- var ensureCommandInstalled = async (command, installHint) => {
3368
- const result = await runCommand(command, ["--version"]);
3369
- if (result.exitCode !== 0) {
3370
- throw new CliError(installHint, 1);
3371
- }
3372
- };
3373
- var ensurePrerequisites = async (input2) => {
3374
- const config = await loadConfig();
3375
- if (input2.needsGh) {
3376
- await ensureCommandInstalled("gh", "gh is required. Run nexight-devops onboard --set-mode gh");
3377
- await ensureGhAuthenticated();
3378
- }
3379
- if (input2.needsAws) {
3380
- await ensureCommandInstalled("aws", "aws is required. Run nexight-devops onboard --set-mode aws");
3381
- await ensureAwsAuthenticated(config.defaultAwsProfile);
3382
- }
3383
- return config;
3384
- };
3385
-
3386
- // src/commands/diagnose/shared-agent.ts
3387
- var EXPECTED_PRODUCTION_ACCOUNT = "250509935467";
3388
- var DEFAULT_REGION = "us-east-1";
3389
- var parseWindowMinutes = (value) => {
3390
- if (!/^\d+$/.test(value.trim())) {
3391
- throw new CliError("--window-minutes must be an integer", 1);
3392
- }
3393
- return Number(value);
3394
- };
3395
- var formatBytes = (bytes) => {
3396
- if (bytes < 1024) {
3397
- return `${bytes} B`;
3398
- }
3399
- if (bytes < 1024 * 1024) {
3400
- return `${(bytes / 1024).toFixed(1)} KiB`;
3401
- }
3402
- return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
3403
- };
3404
- var printSharedAgentDiagnosis = (result) => {
3405
- logger.info(`\u7ED3\u8BBA [${result.verdict.code}/${result.verdict.confidence}] ${result.verdict.summary}`);
3406
- logger.step(`\u65F6\u95F4\u7A97 ${result.window.from} \u2192 ${result.window.to}`);
3407
- const ownerParts = [
3408
- result.owner.userId ? `user=${result.owner.userId}` : void 0,
3409
- result.owner.deviceId ? `device=${result.owner.deviceId}` : void 0,
3410
- result.owner.clientVersion ? `version=${result.owner.clientVersion}` : void 0
3411
- ].filter(Boolean);
3412
- logger.step(`Owner ${ownerParts.length > 0 ? ownerParts.join(" | ") : "\u672A\u8BC6\u522B"}`);
3413
- const descriptor = result.descriptor.latest;
3414
- logger.step(
3415
- descriptor ? `Descriptor ${descriptor.timestamp} | ${descriptor.channel}/${descriptor.outcome}${descriptor.reason ? ` | ${descriptor.reason}` : ""}` : "Descriptor \u5F53\u524D\u65F6\u95F4\u7A97\u65E0\u8BC1\u636E"
3416
- );
3417
- const lease = result.ownerLease.latest;
3418
- logger.step(
3419
- lease ? `Owner lease ${lease.timestamp} | ${lease.outcome}/${lease.reasonCode} | version=${lease.clientVersion}` : "Owner lease \u5F53\u524D\u65F6\u95F4\u7A97\u65E0\u53EF\u5173\u8054\u89C2\u6D4B"
3420
- );
3421
- logger.step(`HTTP failures ${result.httpFailures.count}`);
3422
- result.queryReports.forEach((report) => {
3423
- if (report.status === "complete" && report.stats) {
3424
- logger.step(
3425
- `${report.name} query | matched=${report.stats.recordsMatched} scanned=${report.stats.recordsScanned} bytes=${formatBytes(
3426
- report.stats.bytesScanned
3427
- )}`
3428
- );
3429
- }
3430
- });
3431
- result.warnings.forEach((warning) => logger.warn(warning));
3432
- result.missingEvidence.forEach((missing) => logger.warn(`\u7F3A\u5931\u8BC1\u636E: ${missing}`));
3433
- };
3434
- var runSharedAgentDiagnosis = async (options) => {
3435
- const config = await loadConfig();
3436
- const profile = options.profile?.trim() || config.defaultAwsProfile;
3437
- await ensureCommandInstalled("aws", "aws is required. Run nexight-devops onboard --set-mode aws");
3438
- await ensureAwsAuthenticated(profile);
3439
- const identity = await getAwsCallerIdentity(profile);
3440
- if (identity.account !== EXPECTED_PRODUCTION_ACCOUNT) {
3441
- throw new CliError(
3442
- `AWS profile ${profile} is authenticated to account ${identity.account}; expected ${EXPECTED_PRODUCTION_ACCOUNT}`,
3443
- 1
3444
- );
3445
- }
3446
- const run = () => diagnoseSharedAgent({
3447
- account: identity.account,
3448
- profile,
3449
- region: options.region,
3450
- logGroup: options.logGroup,
3451
- namespace: options.namespace,
3452
- roomId: options.roomId,
3453
- sharedAgentId: options.sharedAgentId,
3454
- ownerDeviceId: options.ownerDeviceId,
3455
- authorityId: options.authorityId,
3456
- anchor: parseDiagnosisAnchor(options.at),
3457
- windowMinutes: parseWindowMinutes(options.windowMinutes)
3458
- });
3459
- const result = options.json ? await run() : await withProgressTask(
3460
- {
3461
- initialText: "\u6B63\u5728\u67E5\u8BE2\u5171\u4EAB Agent \u63A7\u5236\u9762\u8BC1\u636E",
3462
- heartbeatText: (elapsedMs) => `\u6B63\u5728\u67E5\u8BE2\u5171\u4EAB Agent \u63A7\u5236\u9762\u8BC1\u636E\uFF08${Math.floor(elapsedMs / 1e3)}s\uFF09`
3463
- },
3464
- run
3465
- );
3466
- if (options.json) {
3467
- console.log(JSON.stringify(result, null, 2));
3468
- } else {
3469
- printSharedAgentDiagnosis(result);
3470
- }
3471
- return result;
3472
- };
3473
- var registerSharedAgentDiagnosisCommand = (program) => {
3474
- program.command("shared-agent").description("Diagnose Shared Agent connectivity from bounded production CloudWatch evidence").requiredOption("--room-id <roomId>", "room ID from the user report or requester diagnostics").requiredOption("--at <timestamp>", "incident timestamp with timezone, for example 2026-08-12T12:00:00+08:00").option("--shared-agent-id <sharedAgentId>", "narrow descriptor evidence to one Shared Agent").option("--owner-device-id <ownerDeviceId>", "known Owner device ID, used to join lease evidence").option("--authority-id <authorityId>", "known Device Authority ID, used to join lease evidence").option("--window-minutes <minutes>", "bounded window centered on --at", String(DEFAULT_DIAGNOSIS_WINDOW_MINUTES)).option("--profile <profile>", "AWS CLI profile; defaults to the configured nexight-devops profile").option("--region <region>", "AWS region", DEFAULT_REGION).option("--log-group <logGroup>", "CloudWatch application log group", DEFAULT_TSH_LOG_GROUP).option("--namespace <namespace>", "Kubernetes namespace", DEFAULT_TSH_NAMESPACE).option("--json", "print stable machine-readable JSON without CLI decorations").action(async (options) => {
3475
- await runSharedAgentDiagnosis(options);
3476
- });
3477
- };
3478
-
3479
- // src/commands/diagnose/index.ts
3480
- var registerDiagnoseCommand = (program) => {
3481
- const diagnose = program.command("diagnose").description("Run deterministic incident diagnosis playbooks");
3482
- registerSharedAgentDiagnosisCommand(diagnose);
3483
- };
3484
-
3485
- // src/commands/doctor.ts
3486
- var registerDoctorCommand = (program) => {
3487
- program.command("doctor").description("Run a local readiness check for the CLI shell").action(() => {
3488
- logger.step("Running basic readiness checks");
3489
- logger.success("nexight-devops is ready");
3490
- });
3491
- };
3492
-
3493
- // src/lib/datafinder/types.ts
3494
- var DEFAULT_DATAFINDER_BASE_URL = "https://analytics.volcengineapi.com";
3495
- var DEFAULT_DATAFINDER_TIMEZONE = "Asia/Shanghai";
3496
-
3497
- // src/locales/zh-CN.ts
3498
- var zhCN = {
3499
- common: {
3500
- continue: "\u7EE7\u7EED",
3501
- skip: "\u8DF3\u8FC7"
3502
- },
3503
- errors: {
3504
- missingAws: "\u672A\u68C0\u6D4B\u5230 aws\uFF0C\u8BF7\u5148\u5B8C\u6210 AWS CLI \u5B89\u88C5\u3002",
3505
- missingGh: "\u672A\u68C0\u6D4B\u5230 gh\uFF0C\u8BF7\u5148\u5B8C\u6210 GitHub CLI \u5B89\u88C5\u3002"
3506
- },
3507
- onboard: {
3508
- adminLoginSucceeded: "\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u6210\u529F",
3509
- adminSection: "\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u8BBE\u7F6E",
3510
- adminTokenRequired: "\u7BA1\u7406\u540E\u53F0 Token \u4E0D\u80FD\u4E3A\u7A7A",
3511
- awsSection: "AWS CLI \u8BBE\u7F6E",
3512
- complete: "Onboard \u5B8C\u6210",
3513
- chooseAwsProfile: "\u8BF7\u9009\u62E9\u9ED8\u8BA4 AWS Profile",
3514
- chooseLocale: "\u8BF7\u9009\u62E9\u547D\u4EE4\u884C\u8BED\u8A00",
3515
- datafinderAppPathMode: "\u5E94\u7528\u8DEF\u5F84\uFF08\u4F20\u7EDF SaaS / \u79C1\u6709\u5316\uFF09",
3516
- datafinderCredentialAction: "\u5DF2\u914D\u7F6E DataFinder \u51ED\u8BC1\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
3517
- datafinderKeepCredentials: "\u4FDD\u7559\u73B0\u6709\u51ED\u8BC1",
3518
- datafinderProjectPathMode: "\u9879\u76EE\u8DEF\u5F84\uFF08SaaS \u4E91\u539F\u751F\uFF0C\u63A8\u8350\uFF09",
3519
- datafinderReplaceCredentials: "\u91CD\u65B0\u8F93\u5165\u51ED\u8BC1",
3520
- datafinderSection: "DataFinder OpenAPI \u8BBE\u7F6E",
3521
- datafinderSucceeded: "DataFinder \u8FDE\u63A5\u6210\u529F\uFF1A{dashboardName}\uFF0C\u5171 {reportCount} \u4E2A\u62A5\u8868",
3522
- enterGhToken: "\u8BF7\u8F93\u5165 GitHub Token",
3523
- enterAdminToken: "\u8BF7\u8F93\u5165 Tutti \u7BA1\u7406\u540E\u53F0 Token",
3524
- enterDataFinderAk: "\u8BF7\u8F93\u5165 DataFinder AccessKeyId",
3525
- enterDataFinderAppId: "\u8BF7\u8F93\u5165 DataFinder \u5E94\u7528 ID",
3526
- enterDataFinderDashboardId: "\u8BF7\u8F93\u5165\u9ED8\u8BA4 DataFinder \u770B\u677F ID",
3527
- enterDataFinderProjectId: "\u8BF7\u8F93\u5165 DataFinder \u9879\u76EE ID",
3528
- enterDataFinderSk: "\u8BF7\u8F93\u5165 DataFinder SecretAccessKey",
3529
- enterDataFinderTimezone: "\u8BF7\u8F93\u5165 DataFinder \u67E5\u8BE2\u65F6\u533A",
3530
- enterDataFinderUrl: "\u8BF7\u8F93\u5165 DataFinder OpenAPI \u5730\u5740",
3531
- ghInstallDeferred: "\u672A\u5B8C\u6210 gh \u5B89\u88C5\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u8FD0\u884C nexight-devops onboard --set-mode gh",
3532
- ghLoginToken: "\u4F7F\u7528 Token",
3533
- ghLoginWeb: "\u6D4F\u89C8\u5668\u767B\u5F55\uFF08\u63A8\u8350\uFF09",
3534
- ghSection: "GitHub CLI \u8BBE\u7F6E",
3535
- installAwsPrompt: "\u672A\u68C0\u6D4B\u5230 aws\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
3536
- installBrewFailed: "Homebrew \u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u91CD\u8BD5\u3002",
3537
- installBrewPrompt: "\u672A\u68C0\u6D4B\u5230 Homebrew\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
3538
- installGhPrompt: "\u672A\u68C0\u6D4B\u5230 gh\uFF0C\u662F\u5426\u73B0\u5728\u5C1D\u8BD5\u5B89\u88C5\uFF1F",
3539
- installManualPrefix: "\u8BF7\u5148\u5B89\u88C5 Homebrew\uFF0C\u518D\u6267\u884C",
3540
- selectDataFinderPathMode: "\u8BF7\u9009\u62E9 DataFinder OpenAPI \u8DEF\u5F84\u6A21\u5F0F",
3541
- selectGhLoginMode: "\u8BF7\u9009\u62E9 GitHub \u767B\u5F55\u65B9\u5F0F",
3542
- skipGhSetup: "\u8DF3\u8FC7 GitHub CLI \u8BBE\u7F6E",
3543
- skipAdminSetup: "\u8DF3\u8FC7\u7BA1\u7406\u540E\u53F0\u767B\u5F55\u8BBE\u7F6E",
3544
- skipAwsSetup: "\u8DF3\u8FC7 AWS CLI \u8BBE\u7F6E",
3545
- skipDataFinderSetup: "\u8DF3\u8FC7 DataFinder OpenAPI \u8BBE\u7F6E",
3546
- awsInstallDeferred: "\u672A\u5B8C\u6210 AWS CLI \u5B89\u88C5\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u8FD0\u884C nexight-devops onboard --set-mode aws",
3547
- title: "\u5F00\u59CB\u521D\u59CB\u5316 nexight-devops"
3548
- },
3549
- release: {
3550
- common: {
3551
- approvalIssueUrl: "\u5BA1\u6279\u5355\u5730\u5740: {approvalIssueUrl}",
3552
- candidateTag: "\u5019\u9009\u7248\u672C: {candidateTag}",
3553
- controlRunUrl: "\u63A7\u5236\u9762\u8FD0\u884C\u5730\u5740: {controlRunUrl}",
3554
- releaseBranch: "\u53D1\u5E03\u5206\u652F: {releaseBranch}",
3555
- releaseProfile: "\u53D1\u5E03 Profile: {releaseProfile}",
3556
- targetRepo: "\u76EE\u6807\u4ED3\u5E93: {targetRepo}"
3557
- },
3558
- approve: {
3559
- approvalIssueMissing: "\u672A\u627E\u5230\u53EF\u5BA1\u6279\u7684\u53D1\u5E03\u5355\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u76EE\u6807\u4ED3\u5E93\u6216\u68C0\u67E5\u63A7\u5236\u9762\u72B6\u6001\u3002",
3560
- approvalWorkflowRunUrl: "\u5BA1\u6279\u5DE5\u4F5C\u6D41\u5730\u5740: {approvalWorkflowRunUrl}",
3561
- cancelled: "\u5DF2\u53D6\u6D88 release approve\u3002",
3562
- commenting: "\u6B63\u5728\u5BF9\u53D1\u5E03\u5355 #{issueNumber} \u6267\u884C /release-approve",
3563
- closeIssueWarning: "\u5BA1\u6279\u5355 #{issueNumber} \u81EA\u52A8\u5173\u95ED\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u5173\u95ED\u3002",
3564
- confirm: "\u786E\u8BA4\u5BF9\u53D1\u5E03\u5355 #{issueNumber}\uFF08{candidateTag} / {releaseBranch}\uFF09\u6267\u884C /release-approve\uFF1F",
3565
- feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u6B63\u5F0F\u53D1\u7248\u5DF2\u5F00\u59CB\uFF0C\u8BF7\u7ACB\u5373\u5173\u6CE8\u98DE\u4E66\u7FA4\uFF0C\u6301\u7EED\u8DDF\u8FDB\u53D1\u5E03\u72B6\u6001\u3002",
3566
- noPendingIssues: "\u5F53\u524D\u6CA1\u6709\u5F85\u5BA1\u6279\u7684\u53D1\u5E03\u5355\uFF0C\u76EE\u6807\u4ED3\u5E93 {targetRepo} \u6682\u65E0\u53EF\u5904\u7406\u9879\u3002",
3567
- selectApprovalIssue: "\u8BF7\u9009\u62E9\u8981\u5BA1\u6279\u7684\u53D1\u5E03\u5355",
3568
- selectTarget: "\u8BF7\u9009\u62E9\u8981\u5BA1\u6279\u7684\u4ED3\u5E93",
3569
- success: "release approve \u5DF2\u5B8C\u6210\uFF0C\u6B63\u5F0F\u53D1\u7248\u5DF2\u5F00\u59CB",
3570
- workflowFailed: "\u5BA1\u6279\u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u5BA1\u6279\u5DE5\u4F5C\u6D41\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {approvalWorkflowRunUrl}"
3571
- },
3572
- hotfix: {
3573
- allowUnmergedHotfix: "\u662F\u5426\u5141\u8BB8 release \u5206\u652F\u4E0A\u7684\u8865\u4E01\u5C1A\u672A\u88AB main \u5438\u6536\u65F6\u76F4\u63A5\u53D1\u8D77 hotfix\uFF1F",
3574
- approvalIssueAmbiguous: "\u68C0\u6D4B\u5230\u591A\u4E2A\u65B0\u7684 hotfix \u5BA1\u6279\u5355\uFF0C\u8BF7\u68C0\u67E5\u4EE5\u4E0B\u5019\u9009\u9879: {candidateIssueUrls}\uFF1B\u63A7\u5236\u9762\u8FD0\u884C: {controlRunUrl}",
3575
- approvalIssueMissing: "\u672A\u627E\u5230\u65B0\u521B\u5EFA\u7684 hotfix \u5BA1\u6279\u5355\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}",
3576
- cancelled: "\u5DF2\u53D6\u6D88 release hotfix\u3002",
3577
- confirm: "\u786E\u8BA4\u5BF9 {repoName} \u53D1\u8D77 hotfix\uFF0C\u5206\u652F {releaseBranch}\uFF1F",
3578
- dispatching: "\u6B63\u5728\u51C6\u5907 hotfix: {repoName}",
3579
- enterReleaseBranch: "\u8BF7\u8F93\u5165 hotfix \u7684 release \u5206\u652F\uFF08\u5FC5\u987B\u4EE5 release/ \u5F00\u5934\uFF09",
3580
- feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u81EA\u6D4B\u53D1\u7248\u901A\u77E5\u5DF2\u53D1\u9001\u5230\u98DE\u4E66\u7FA4\uFF0C\u8BF7\u5173\u6CE8\u98DE\u4E66\u7FA4\u5E76\u5B8C\u6210\u9A8C\u8BC1\uFF0C\u968F\u540E\u6267\u884C nexight-devops release approve\u3002",
3581
- invalidReleaseBranch: "release \u5206\u652F\u5FC5\u987B\u662F release/<name> \u683C\u5F0F\u3002",
3582
- nextSteps: "\u4E0B\u4E00\u6B65\uFF1A\u5B8C\u6210\u9A8C\u8BC1\u540E\uFF0C\u6267\u884C nexight-devops release approve\u3002",
3583
- overrideMode: "Override \u6A21\u5F0F: {overrideMode}",
3584
- overrideModeDisabled: "\u672A\u5F00\u542F",
3585
- overrideModeEnabled: "\u5DF2\u5F00\u542F",
3586
- overrideReason: "\u8BF7\u8F93\u5165 release \u5206\u652F\u4E0A\u7684\u8865\u4E01\u5C1A\u672A\u88AB main \u5438\u6536\u65F6\u7EE7\u7EED hotfix \u7684\u539F\u56E0",
3587
- overrideReasonRequired: "\u5F00\u542F release \u5206\u652F\u8865\u4E01 override \u65F6\u5FC5\u987B\u586B\u5199\u539F\u56E0\u3002",
3588
- workflowFailureReason: "\u5931\u8D25\u539F\u56E0: {failureMessage}",
3589
- selectTarget: "\u8BF7\u9009\u62E9\u8981\u6267\u884C hotfix \u7684\u4ED3\u5E93",
3590
- success: "release hotfix \u5DF2\u5B8C\u6210",
3591
- waiting: "\u63A7\u5236\u9762 hotfix \u5DE5\u4F5C\u6D41\u5DF2\u89E6\u53D1\uFF0C\u6B63\u5728\u7B49\u5F85\u5B8C\u6210: {repoName}",
3592
- workflowFailed: "controlled-hotfix-release \u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}"
3593
- },
3594
- prepare: {
3595
- approvalIssueAmbiguous: "\u68C0\u6D4B\u5230\u591A\u4E2A\u65B0\u7684\u53D1\u5E03\u5BA1\u6279\u5355\uFF0C\u8BF7\u68C0\u67E5\u4EE5\u4E0B\u5019\u9009\u9879: {candidateIssueUrls}\uFF1B\u63A7\u5236\u9762\u8FD0\u884C: {controlRunUrl}",
3596
- approvalIssueMissing: "\u672A\u627E\u5230\u65B0\u521B\u5EFA\u7684\u53D1\u5E03\u5BA1\u6279\u5355\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}",
3597
- cancelled: "\u5DF2\u53D6\u6D88 release prepare\u3002",
3598
- confirm: "\u786E\u8BA4\u5BF9 {repoName} \u6267\u884C release prepare\uFF1F",
3599
- dispatching: "\u6B63\u5728\u51C6\u5907\u53D1\u5E03: {repoName}",
3600
- feishuReminder: "\u98DE\u4E66\u63D0\u9192\uFF1A\u81EA\u6D4B\u53D1\u7248\u901A\u77E5\u5DF2\u53D1\u9001\u5230\u98DE\u4E66\u7FA4\uFF0C\u8BF7\u5173\u6CE8\u98DE\u4E66\u7FA4\u5E76\u5B8C\u6210\u9A8C\u8BC1\uFF0C\u968F\u540E\u6267\u884C nexight-devops release approve\u3002",
3601
- nextSteps: "\u4E0B\u4E00\u6B65\uFF1A\u5B8C\u6210\u9A8C\u8BC1\u540E\uFF0C\u6267\u884C nexight-devops release approve\u3002",
3602
- selectTarget: "\u8BF7\u9009\u62E9\u8981\u51C6\u5907\u53D1\u5E03\u7684\u4ED3\u5E93",
3603
- success: "release prepare \u5DF2\u5B8C\u6210",
3604
- waiting: "\u63A7\u5236\u9762\u53D1\u5E03\u5DE5\u4F5C\u6D41\u5DF2\u89E6\u53D1\uFF0C\u6B63\u5728\u7B49\u5F85\u5B8C\u6210: {repoName}",
3605
- workflowFailed: "controlled-release \u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u6253\u5F00\u63A7\u5236\u9762\u8FD0\u884C\u67E5\u770B\u8BE6\u60C5: {controlRunUrl}"
3606
- }
3607
- },
3608
- ppeLane: {
3609
- destroy: {
3610
- atLeastOneRepo: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u4ED3\u5E93",
3611
- blankLane: "PPE lane \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
3612
- cancelled: "\u5DF2\u53D6\u6D88\u9500\u6BC1",
3613
- confirmLane: "\u8FD9\u662F\u9500\u6BC1\u64CD\u4F5C\uFF0C\u8BF7\u518D\u6B21\u8F93\u5165 {lane} \u786E\u8BA4",
3614
- enterLane: "\u8BF7\u8F93\u5165\u9700\u8981\u79FB\u9664\u7684 PPE lane \u540D\u79F0",
3615
- failed: "\u4E00\u4E2A\u6216\u591A\u4E2A PPE lane \u9500\u6BC1\u5DE5\u4F5C\u6D41\u6267\u884C\u5931\u8D25",
3616
- noRecords: "\u8BE5 ppe-lane \u6CA1\u67E5\u8BE2\u5230\u4EFB\u4F55\u90E8\u7F72\u8BB0\u5F55",
3617
- running: "\u6B63\u5728\u9500\u6BC1 lane {lane}",
3618
- selectRepos: "\u8BF7\u9009\u62E9\u9700\u8981\u9500\u6BC1\u7684\u4ED3\u5E93",
3619
- skippedReason: "Cloud Map \u4E2D\u672A\u627E\u5230\u90E8\u7F72\u8BB0\u5F55",
3620
- warning: "\u5C06\u9500\u6BC1\u4EE5\u4E0B\u4ED3\u5E93: {repos}"
3621
- }
3622
- },
3623
- versionUpdate: {
3624
- alreadyLatest: "\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C {currentVersion}\u3002",
3625
- available: "\u68C0\u6D4B\u5230\u65B0\u7248\u672C {latestVersion}\uFF0C\u5F53\u524D\u7248\u672C\u4E3A {currentVersion}\u3002",
3626
- checkingRegistry: "\u6B63\u5728\u68C0\u67E5\u7248\u672C\u4FE1\u606F",
3627
- detectingInstallSource: "\u6B63\u5728\u68C0\u6D4B\u5B89\u88C5\u6765\u6E90",
3628
- majorRequired: "\u68C0\u6D4B\u5230\u5927\u7248\u672C\u66F4\u65B0 {latestVersion}\uFF0C\u5F53\u524D\u7248\u672C {currentVersion} \u5FC5\u987B\u5148\u5347\u7EA7\u540E\u518D\u7EE7\u7EED\u3002",
3629
- manualUpgrade: "\u65E0\u6CD5\u8BC6\u522B\u5F53\u524D\u5B89\u88C5\u6765\u6E90\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\u4EE5\u4E0B\u4EFB\u4E00\u5347\u7EA7\u547D\u4EE4\uFF1A\n{commands}",
3630
- optionalUpgradeFailed: "\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF0C\u672C\u6B21\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C\u3002\u4F60\u53EF\u4EE5\u7A0D\u540E\u624B\u52A8\u6267\u884C\uFF1A{command}",
3631
- registryCheckFailed: "\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002",
3632
- registryCheckFailedManual: "\u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
3633
- rerun: "\u5347\u7EA7\u5B8C\u6210\u540E\u8BF7\u91CD\u65B0\u6267\u884C\u521A\u624D\u7684\u547D\u4EE4\u3002",
3634
- selectAction: "\u68C0\u6D4B\u5230\u53EF\u7528\u66F4\u65B0\uFF0C\u662F\u5426\u73B0\u5728\u5347\u7EA7\uFF1F",
3635
- skipThisRun: "\u8DF3\u8FC7\u672C\u6B21",
3636
- skippedForRun: "\u5DF2\u8DF3\u8FC7\u672C\u6B21\u5347\u7EA7\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002",
3637
- upgradeFailed: "\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF0C\u8BF7\u5148\u624B\u52A8\u6267\u884C\uFF1A{command}",
3638
- upgradeNow: "\u7ACB\u5373\u5347\u7EA7",
3639
- upgradeSucceeded: "CLI \u5DF2\u66F4\u65B0\u5230\u6700\u65B0\u7248\u672C {latestVersion}\u3002",
3640
- upgrading: "\u6B63\u5728\u6267\u884C\u5347\u7EA7\u547D\u4EE4\uFF1A{command}"
3641
- }
3642
- };
3643
-
3644
- // src/lib/i18n.ts
3645
- var locales = {
3646
- "zh-CN": zhCN
3647
- };
3648
- var getNestedValue = (source, key) => {
3649
- const value = key.split(".").reduce((current, part) => {
3650
- if (current && typeof current === "object" && part in current) {
3651
- return current[part];
3652
- }
3653
- return void 0;
3654
- }, source);
3655
- return typeof value === "string" ? value : key;
3656
- };
3657
- var interpolate = (message, values) => {
3658
- if (!values) {
3659
- return message;
3660
- }
3661
- return message.replace(/\{(\w+)\}/g, (placeholder, key) => {
3662
- const value = values[key];
3663
- if (value === void 0) {
3664
- return placeholder;
3665
- }
3666
- return String(value);
3667
- });
3668
- };
3669
- var createI18n = (locale) => {
3670
- const messages = locales[locale];
3671
- return (key, values) => {
3672
- const messageFromLocale = messages ? getNestedValue(messages, key) : key;
3673
- const message = messageFromLocale === key ? getNestedValue(zhCN, key) : messageFromLocale;
3674
- return interpolate(message, values);
3675
- };
3676
- };
3677
-
3678
- // src/commands/onboard.ts
3679
- var onboardModes = ["all", "admin", "aws", "datafinder", "gh", "locale"];
3680
- var resolveBrewCommand = async () => {
3681
- const result = await runCommand("/bin/bash", [
3682
- "-lc",
3683
- "command -v brew || { [ -x /opt/homebrew/bin/brew ] && printf /opt/homebrew/bin/brew; } || { [ -x /usr/local/bin/brew ] && printf /usr/local/bin/brew; }"
3684
- ]);
3685
- const brewPath = result.stdout.trim();
3686
- return brewPath ? brewPath : null;
3687
- };
3688
- var installHomebrew = async () => {
3689
- const result = await runCommand(
3690
- "/bin/bash",
3691
- [
3692
- "-lc",
3693
- '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
3694
- ],
3695
- { stdio: "inherit" }
3696
- );
3697
- return result.exitCode === 0;
3698
- };
3699
- var ensureBrewPackage = async (packageName, t) => {
3700
- let brewCommand = await resolveBrewCommand();
3701
- if (!brewCommand) {
3702
- const shouldInstallBrew = await ui.confirm({
3703
- message: t("onboard.installBrewPrompt"),
3704
- default: true
3705
- });
3706
- if (!shouldInstallBrew) {
3707
- logger.warn(`${t("onboard.installManualPrefix")} brew install ${packageName}`);
3708
- return false;
3709
- }
3710
- if (!await installHomebrew()) {
3711
- logger.warn(t("onboard.installBrewFailed"));
3712
- return false;
3713
- }
3714
- brewCommand = await resolveBrewCommand();
3715
- if (!brewCommand) {
3716
- logger.warn(t("onboard.installBrewFailed"));
3717
- return false;
3718
- }
3719
- }
3720
- const installResult = await runCommand(brewCommand, ["install", packageName], { stdio: "inherit" });
3721
- return installResult.exitCode === 0;
3722
- };
3723
- var ensureGhInstalled = async (t) => {
3724
- const ghVersion = await runCommand("gh", ["--version"]);
3725
- if (ghVersion.exitCode === 0) {
3726
- return true;
3727
- }
3728
- if (process.platform === "darwin") {
3729
- const shouldInstall = await ui.confirm({ message: t("onboard.installGhPrompt"), default: true });
3730
- if (shouldInstall) {
3731
- return ensureBrewPackage("gh", t);
3732
- }
3733
- }
3734
- logger.warn(t("onboard.ghInstallDeferred"));
3735
- return false;
3736
- };
3737
- var ensureAwsInstalled = async (t) => {
3738
- const awsVersion = await runCommand("aws", ["--version"]);
3739
- if (awsVersion.exitCode === 0) {
3740
- return true;
3741
- }
3742
- if (process.platform === "darwin") {
3743
- const shouldInstall = await ui.confirm({ message: t("onboard.installAwsPrompt"), default: true });
3744
- if (shouldInstall) {
3745
- return ensureBrewPackage("awscli", t);
3746
- }
3747
- }
3748
- logger.warn(t("onboard.awsInstallDeferred"));
3749
- return false;
3750
- };
3751
- var runGhSetup = async (t) => {
3752
- if (!await ensureGhInstalled(t)) {
3753
- return false;
3754
- }
3755
- const authStatus = await runCommand("gh", ["auth", "status"]);
3756
- if (authStatus.exitCode === 0) {
3757
- return true;
3758
- }
3759
- const loginMode = await ui.select({
3760
- message: t("onboard.selectGhLoginMode"),
3761
- choices: [
3762
- { value: "web", name: t("onboard.ghLoginWeb") },
3763
- { value: "token", name: t("onboard.ghLoginToken") }
3764
- ]
3765
- });
3766
- if (loginMode === "web") {
3767
- await runCommand("gh", ["auth", "login", "--web"], { stdio: "inherit" });
3768
- } else {
3769
- const token = await ui.password({ message: t("onboard.enterGhToken") });
3770
- await runCommand("gh", ["auth", "login", "--with-token"], {
3771
- input: token
3772
- });
3773
- }
3774
- return (await runCommand("gh", ["auth", "status"])).exitCode === 0;
3775
- };
3776
- var runAwsSetup = async (defaultProfile, t) => {
3777
- if (!await ensureAwsInstalled(t)) {
3778
- return {
3779
- profile: defaultProfile,
3780
- configured: false
3781
- };
3782
- }
3783
- const profile = await ui.input({
3784
- message: t("onboard.chooseAwsProfile"),
3785
- default: defaultProfile
3786
- });
3787
- const listProfiles = await runCommand("aws", ["configure", "list-profiles"]);
3788
- const profiles = listProfiles.stdout.split("\n").map((item) => item.trim()).filter(Boolean);
3789
- if (!profiles.includes(profile)) {
3790
- await runCommand("aws", ["configure", "sso", "--profile", profile], { stdio: "inherit" });
3791
- }
3792
- const loginResult = await runCommand("aws", ["sso", "login", "--profile", profile], { stdio: "inherit" });
3793
- if (loginResult.exitCode !== 0) {
3794
- return {
3795
- profile,
3796
- configured: false
3797
- };
3798
- }
3799
- const identityResult = await runCommand("aws", [
3800
- "sts",
3801
- "get-caller-identity",
3802
- "--profile",
3803
- profile,
3804
- "--output",
3805
- "json"
3806
- ]);
3807
- return {
3808
- profile,
3809
- configured: identityResult.exitCode === 0
3810
- };
3811
- };
3812
- var runAdminSetup = async (config, options, t) => {
3813
- const apiOrigin = normalizeAdminApiOrigin(options.adminUrl ?? config.admin?.apiOrigin);
3814
- const token = (await ui.password({ message: t("onboard.enterAdminToken") })).trim();
3815
- if (!token) {
3816
- throw new CliError(t("onboard.adminTokenRequired"), 1);
3817
- }
3818
- await new AdminApi({ apiOrigin, token }).inspectToken(token);
3819
- config.admin = {
3820
- apiOrigin,
3821
- token
3822
- };
3823
- logger.success(t("onboard.adminLoginSucceeded"));
3824
- };
3825
- var inputWithDefault = async (message, value) => (await ui.input({
3826
- message,
3827
- ...value ? { default: value } : {}
3828
- })).trim();
3829
- var requiredDataFinderValue = (value, label) => {
3830
- if (!value) {
3831
- throw new CliError(`DataFinder ${label} \u4E0D\u80FD\u4E3A\u7A7A`, 1);
3832
- }
3833
- return value;
3834
- };
3835
- var runDataFinderSetup = async (config, options, t) => {
3836
- const previous = config.datafinder;
3837
- let accessKeyId = previous?.accessKeyId ?? "";
3838
- let secretAccessKey = previous?.secretAccessKey ?? "";
3839
- if (previous?.accessKeyId && previous.secretAccessKey) {
3840
- const credentialAction = await ui.select({
3841
- message: t("onboard.datafinderCredentialAction"),
3842
- choices: [
3843
- { value: "keep", name: t("onboard.datafinderKeepCredentials") },
3844
- { value: "replace", name: t("onboard.datafinderReplaceCredentials") }
3845
- ]
3846
- });
3847
- if (credentialAction === "replace") {
3848
- accessKeyId = "";
3849
- secretAccessKey = "";
3850
- }
3851
- }
3852
- if (!accessKeyId || !secretAccessKey) {
3853
- accessKeyId = (await ui.password({ message: t("onboard.enterDataFinderAk") })).trim();
3854
- secretAccessKey = (await ui.password({ message: t("onboard.enterDataFinderSk") })).trim();
3855
- }
3856
- const pathMode = parseDataFinderPathMode(
3857
- options.datafinderPathMode ?? await ui.select({
3858
- message: t("onboard.selectDataFinderPathMode"),
3859
- choices: [
3860
- { value: "project", name: t("onboard.datafinderProjectPathMode") },
3861
- { value: "app", name: t("onboard.datafinderAppPathMode") }
3862
- ],
3863
- default: previous?.pathMode ?? "project"
3864
- })
3865
- );
3866
- const candidate = {
3867
- accessKeyId: requiredDataFinderValue(accessKeyId, "AccessKeyId"),
3868
- appId: requiredDataFinderValue(
3869
- await inputWithDefault(
3870
- t("onboard.enterDataFinderAppId"),
3871
- options.datafinderAppId ?? previous?.appId
3872
- ),
3873
- "appId"
3874
- ),
3875
- baseUrl: normalizeDataFinderBaseUrl(
3876
- requiredDataFinderValue(
3877
- await inputWithDefault(
3878
- t("onboard.enterDataFinderUrl"),
3879
- options.datafinderUrl ?? previous?.baseUrl ?? DEFAULT_DATAFINDER_BASE_URL
3880
- ),
3881
- "baseUrl"
3882
- )
3883
- ),
3884
- defaultDashboardId: requiredDataFinderValue(
3885
- await inputWithDefault(
3886
- t("onboard.enterDataFinderDashboardId"),
3887
- options.datafinderDashboardId ?? previous?.defaultDashboardId
3888
- ),
3889
- "defaultDashboardId"
3890
- ),
3891
- pathMode,
3892
- projectId: requiredDataFinderValue(
3893
- await inputWithDefault(
3894
- t("onboard.enterDataFinderProjectId"),
3895
- options.datafinderProjectId ?? previous?.projectId
3896
- ),
3897
- "projectId"
3898
- ),
3899
- secretAccessKey: requiredDataFinderValue(secretAccessKey, "SecretAccessKey"),
3900
- timezone: requiredDataFinderValue(
3901
- await inputWithDefault(
3902
- t("onboard.enterDataFinderTimezone"),
3903
- previous?.timezone ?? DEFAULT_DATAFINDER_TIMEZONE
3904
- ),
3905
- "timezone"
3906
- )
3907
- };
3908
- const summary = await new DataFinderClient(candidate).checkConnection();
3909
- config.datafinder = toStoredDataFinderConfig(candidate);
3910
- config.setup.datafinderConfigured = true;
3911
- logger.success(
3912
- t("onboard.datafinderSucceeded", {
3913
- dashboardName: summary.dashboardName ?? summary.dashboardId,
3914
- reportCount: summary.reportCount
3915
- })
3916
- );
3917
- };
3918
- var runOnboard = async (options) => {
3919
- const config = await loadConfig();
3920
- const t = createI18n(config.locale);
3921
- const mode = options.setMode ?? "all";
3922
- if (!onboardModes.includes(mode)) {
3923
- throw new CliError(`Invalid onboard mode: ${mode}`, 1);
3924
- }
3925
- if (mode === "all" || mode === "locale") {
3926
- const locale = await ui.select({
3927
- message: t("onboard.chooseLocale"),
3928
- choices: [
3929
- { value: "zh-CN", name: "\u4E2D\u6587" },
3930
- { value: "skip", name: t("common.skip") }
3931
- ]
3932
- });
3933
- if (locale !== "skip") {
3934
- config.locale = locale;
3226
+ } catch (error) {
3227
+ if (!(error instanceof CliError) || !isTransientWorkflowRunReadFailure(error.message) || readAttempt === TRANSIENT_WORKFLOW_RUN_READ_ATTEMPTS - 1) {
3228
+ throw error;
3229
+ }
3230
+ input2.onUpdate?.(trackedRunId === null ? { status: "pending" } : { runId: trackedRunId, status: "pending" });
3231
+ await sleep(intervalMs);
3232
+ }
3935
3233
  }
3936
- config.setup.localeConfigured = true;
3937
- }
3938
- if (mode === "all" || mode === "admin") {
3939
- const action = mode === "admin" ? "continue" : await ui.select({
3940
- message: t("onboard.adminSection"),
3941
- choices: [
3942
- { value: "continue", name: t("common.continue") },
3943
- { value: "skip", name: t("common.skip") }
3944
- ]
3945
- });
3946
- if (action === "continue") {
3947
- await runAdminSetup(config, options, t);
3948
- } else {
3949
- logger.info(t("onboard.skipAdminSetup"));
3234
+ if (!runs) {
3235
+ throw new CliError(`Timed out waiting for workflow run in ${input2.repoFullName}`, 1);
3950
3236
  }
3951
- }
3952
- if (mode === "all" || mode === "datafinder") {
3953
- const action = mode === "datafinder" ? "continue" : await ui.select({
3954
- message: t("onboard.datafinderSection"),
3955
- choices: [
3956
- { value: "continue", name: t("common.continue") },
3957
- { value: "skip", name: t("common.skip") }
3958
- ]
3959
- });
3960
- if (action === "continue") {
3961
- await runDataFinderSetup(config, options, t);
3962
- } else {
3963
- logger.info(t("onboard.skipDataFinderSetup"));
3237
+ if (trackedRunId === null) {
3238
+ const candidates = runs.filter((run) => !knownRunIds.has(run.id)).filter((run) => matchesWorkflowRunCorrelation(run, input2.correlation));
3239
+ if (candidates.length > 1) {
3240
+ throw new CliError(`Ambiguous workflow run selection for ${input2.repoFullName}`, 1);
3241
+ }
3242
+ if (candidates.length === 0) {
3243
+ input2.onUpdate?.({ status: "pending" });
3244
+ await sleep(intervalMs);
3245
+ continue;
3246
+ }
3247
+ trackedRunId = candidates[0]?.id ?? null;
3964
3248
  }
3965
- }
3966
- if (mode === "all" || mode === "gh") {
3967
- const action = mode === "gh" ? "continue" : await ui.select({
3968
- message: t("onboard.ghSection"),
3969
- choices: [
3970
- { value: "continue", name: t("common.continue") },
3971
- { value: "skip", name: t("common.skip") }
3972
- ]
3973
- });
3974
- if (action === "continue") {
3975
- config.setup.ghConfigured = await runGhSetup(t);
3976
- } else {
3977
- logger.info(t("onboard.skipGhSetup"));
3978
- config.setup.ghConfigured = true;
3249
+ const matchedRun = runs.find((run) => run.id === trackedRunId);
3250
+ if (!matchedRun) {
3251
+ input2.onUpdate?.({ runId: trackedRunId, status: "pending" });
3252
+ await sleep(intervalMs);
3253
+ continue;
3979
3254
  }
3980
- }
3981
- if (mode === "all" || mode === "aws") {
3982
- const action = mode === "aws" ? "continue" : await ui.select({
3983
- message: t("onboard.awsSection"),
3984
- choices: [
3985
- { value: "continue", name: t("common.continue") },
3986
- { value: "skip", name: t("common.skip") }
3987
- ]
3255
+ input2.onUpdate?.({
3256
+ runId: trackedRunId,
3257
+ htmlUrl: matchedRun.html_url,
3258
+ status: matchedRun.status === "completed" ? "completed" : "in_progress"
3988
3259
  });
3989
- if (action === "continue") {
3990
- const result = await runAwsSetup(config.defaultAwsProfile, t);
3991
- config.setup.awsConfigured = result.configured;
3992
- if (result.configured) {
3993
- config.defaultAwsProfile = result.profile;
3260
+ if (matchedRun.status === "completed") {
3261
+ const completion = {
3262
+ conclusion: matchedRun.conclusion ?? "unknown",
3263
+ htmlUrl: matchedRun.html_url,
3264
+ runId: matchedRun.id,
3265
+ status: matchedRun.status
3266
+ };
3267
+ if (completion.conclusion !== "success") {
3268
+ const failureMessage = await fetchWorkflowRunFailureMessage({
3269
+ repoFullName: input2.repoFullName,
3270
+ runId: matchedRun.id
3271
+ });
3272
+ if (failureMessage) {
3273
+ completion.failureMessage = failureMessage;
3274
+ }
3994
3275
  }
3995
- } else {
3996
- logger.info(t("onboard.skipAwsSetup"));
3997
- config.setup.awsConfigured = true;
3276
+ return completion;
3998
3277
  }
3278
+ await sleep(intervalMs);
3999
3279
  }
4000
- await saveConfig(config);
4001
- logger.success(t("onboard.complete"));
4002
- };
4003
- var registerOnboardCommand = (program) => {
4004
- program.command("onboard").description("Install or configure local prerequisites").option("--set-mode <mode>", "setup only one flow: all|admin|aws|datafinder|gh|locale").option("--admin-url <url>", "management API URL or admin.tutti.sh page URL").option("--datafinder-url <url>", "DataFinder OpenAPI origin").option("--datafinder-project-id <id>", "DataFinder project ID").option("--datafinder-app-id <id>", "DataFinder application ID").option("--datafinder-dashboard-id <id>", "default DataFinder dashboard ID").option("--datafinder-path-mode <mode>", "DataFinder API path mode: project|app").action((options) => runOnboard(options));
4005
- };
4006
-
4007
- // src/constants/ppe-lane-frontend-targets.ts
4008
- var ppeLaneCanvasTarget = {
4009
- key: "zk-web/canvas",
4010
- label: "zk-web/canvas",
4011
- repoFullName: "zk-org-test/zk-web",
4012
- workflowFile: "upload-canvas-staging-version.yml",
4013
- defaultWorkflowRef: "main",
4014
- artifactName: "canvas-staging-version",
4015
- artifactFileName: "canvas-staging-version.json",
4016
- workerName: "zk-design-web-staging"
3280
+ throw new CliError(`Timed out waiting for workflow run in ${input2.repoFullName}`, 1);
4017
3281
  };
4018
3282
 
4019
- // src/constants/ppe-lane-repos.ts
4020
- var ppeLaneRepos = [
4021
- {
4022
- key: "zk-agent-engine",
4023
- label: "zk-agent-engine",
4024
- repoFullName: "zk-org-test/zk-agent-engine",
4025
- deployWorkflowFile: "deploy-lane.yml",
4026
- destroyWorkflowFile: "destroy-lane.yml",
4027
- defaultRef: "main",
4028
- cloudMapServiceId: "srv-gg2qso5pnqgykn7h",
4029
- serviceName: "agent-engine"
4030
- },
4031
- {
4032
- key: "zk-commerce",
4033
- label: "zk-commerce",
4034
- repoFullName: "zk-org-test/zk-commerce",
4035
- deployWorkflowFile: "deploy-lane.yml",
4036
- destroyWorkflowFile: "destroy-lane.yml",
4037
- defaultRef: "main",
4038
- cloudMapServiceId: "srv-tfl3t6duyoehwedi",
4039
- serviceName: "zk-commerce"
4040
- },
4041
- {
4042
- key: "zk-admin-server",
4043
- label: "zk-admin-server",
4044
- repoFullName: "zk-org-test/zk-admin-server",
4045
- deployWorkflowFile: "deploy-lane.yml",
4046
- destroyWorkflowFile: "destroy-lane.yml",
4047
- defaultRef: "main",
4048
- cloudMapServiceId: "srv-s4fn35h7nen4u5ci",
4049
- serviceName: "admin"
4050
- },
4051
- {
4052
- key: "design-project",
4053
- label: "design-project",
4054
- repoFullName: "zk-org-test/design-project",
4055
- deployWorkflowFile: "deploy-lane.yml",
4056
- destroyWorkflowFile: "destroy-lane.yml",
4057
- defaultRef: "main",
4058
- cloudMapServiceId: "srv-bjtdvtcxg2clgs6m",
4059
- serviceName: "design-project"
4060
- },
4061
- {
4062
- key: "account",
4063
- label: "account",
4064
- repoFullName: "zk-org-test/account",
4065
- deployWorkflowFile: "deploy-lane.yml",
4066
- destroyWorkflowFile: "destroy-lane.yml",
4067
- defaultRef: "main",
4068
- cloudMapServiceId: "srv-j2newpni247wsn7h",
4069
- serviceName: "account"
4070
- },
4071
- {
4072
- key: "zk-websocket",
4073
- label: "zk-websocket",
4074
- repoFullName: "zk-org-test/zk-websocket",
4075
- deployWorkflowFile: "deploy-lane.yml",
4076
- destroyWorkflowFile: "destroy-lane.yml",
4077
- defaultRef: "main",
4078
- cloudMapServiceId: "srv-34fi45edgao7tp2r",
4079
- serviceName: "zk-websocket"
4080
- }
4081
- ];
4082
-
4083
3283
  // src/lib/ppe-lane.ts
4084
3284
  var PPE_LANE_MAX_LENGTH = 38;
4085
3285
  var PPE_LANE_PATTERN = /^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?$/;
@@ -4100,48 +3300,50 @@ var validatePpeLaneName = (value) => {
4100
3300
  return lane;
4101
3301
  };
4102
3302
 
3303
+ // src/lib/prerequisites.ts
3304
+ var ensureCommandInstalled = async (command, installHint) => {
3305
+ const result = await runCommand(command, ["--version"]);
3306
+ if (result.exitCode !== 0) {
3307
+ throw new CliError(installHint, 1);
3308
+ }
3309
+ };
3310
+ var ensurePrerequisites = async (input2) => {
3311
+ const config = await loadConfig();
3312
+ if (input2.needsGh) {
3313
+ await ensureCommandInstalled("gh", "gh is required. Run nexight-devops onboard --set-mode gh");
3314
+ await ensureGhAuthenticated();
3315
+ }
3316
+ if (input2.needsAws) {
3317
+ await ensureCommandInstalled("aws", "aws is required. Run nexight-devops onboard --set-mode aws");
3318
+ await ensureAwsAuthenticated(config.defaultAwsProfile);
3319
+ }
3320
+ return config;
3321
+ };
3322
+
4103
3323
  // src/commands/ppe-lane/deploy.ts
4104
3324
  var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
4105
3325
  var renderAggregateProgress = (lane, selectedTargets, repoStates) => renderWorkflowProgressPanel({
4106
3326
  items: selectedTargets,
4107
3327
  states: repoStates,
4108
- title: lane ? `\u6B63\u5728\u53D1\u5E03 lane ${lane}` : "\u6B63\u5728\u53D1\u5E03\u6240\u9009\u76EE\u6807"
3328
+ title: `\u6B63\u5728\u53D1\u5E03 lane ${lane}`
4109
3329
  });
4110
3330
  var runPpeLaneDeploy = async () => {
4111
3331
  const requestedRef = await ui.input({ message: "\u8BF7\u8F93\u5165\u76EE\u6807\u5206\u652F", default: "main" });
4112
- const deployTargetChoices = [
4113
- ...ppeLaneRepos.map((repo) => ({ value: repo.key, name: repo.label })),
4114
- { value: ppeLaneCanvasTarget.key, name: ppeLaneCanvasTarget.label }
4115
- ];
4116
3332
  const selectedKeys = await ui.checkbox({
4117
- message: "\u8BF7\u9009\u62E9\u9700\u8981\u53D1\u5E03\u7684\u76EE\u6807",
4118
- choices: deployTargetChoices
3333
+ message: "\u8BF7\u9009\u62E9\u9700\u8981\u53D1\u5E03\u7684\u4ED3\u5E93",
3334
+ choices: ppeLaneRepos.map((repo) => ({ value: repo.key, name: repo.label }))
4119
3335
  });
4120
- if (selectedKeys.length === 0) {
4121
- throw new CliError("At least one deploy target must be selected", 1);
4122
- }
4123
3336
  const selectedRepos = ppeLaneRepos.filter((repo) => selectedKeys.includes(repo.key));
4124
- const shouldDeployCanvas = selectedKeys.includes(ppeLaneCanvasTarget.key);
4125
- const lane = selectedRepos.length > 0 ? validatePpeLaneName(await ui.input({ message: "\u8BF7\u8F93\u5165 PPE lane \u540D\u79F0" })) : void 0;
4126
- const selectedTargets = [
4127
- ...selectedRepos.map((repo) => ({
4128
- defaultRef: repo.defaultRef,
4129
- key: repo.key,
4130
- kind: "backend",
4131
- repoFullName: repo.repoFullName,
4132
- workflowFile: repo.deployWorkflowFile
4133
- })),
4134
- ...shouldDeployCanvas ? [
4135
- {
4136
- defaultRef: ppeLaneCanvasTarget.defaultWorkflowRef,
4137
- key: ppeLaneCanvasTarget.key,
4138
- kind: "canvas",
4139
- repoFullName: ppeLaneCanvasTarget.repoFullName,
4140
- workflowFile: ppeLaneCanvasTarget.workflowFile
4141
- }
4142
- ] : []
4143
- ];
4144
- const config = await ensurePrerequisites({ needsAws: selectedRepos.length > 0, needsGh: true });
3337
+ if (selectedRepos.length === 0) {
3338
+ throw new CliError("At least one deploy repository must be selected", 1);
3339
+ }
3340
+ const lane = validatePpeLaneName(await ui.input({ message: "\u8BF7\u8F93\u5165 PPE lane \u540D\u79F0" }));
3341
+ const selectedTargets = selectedRepos.map((repo) => ({
3342
+ key: repo.key,
3343
+ repoFullName: repo.repoFullName,
3344
+ workflowFile: repo.deployWorkflowFile
3345
+ }));
3346
+ const config = await ensurePrerequisites({ needsAws: true, needsGh: true });
4145
3347
  const branchResults = await Promise.all(
4146
3348
  selectedRepos.map(
4147
3349
  (repo) => resolveBranchForRepo({
@@ -4166,12 +3368,11 @@ var runPpeLaneDeploy = async () => {
4166
3368
  const confirmationLines = [
4167
3369
  "\u786E\u8BA4\u5F00\u59CB\u90E8\u7F72\uFF1F",
4168
3370
  ` \u5206\u652F: ${requestedRef}`,
3371
+ ` lane: ${lane}`,
3372
+ "",
4169
3373
  " \u5DF2\u9009\u62E9\u76EE\u6807:",
4170
- ...selectedKeys.map((key) => ` - ${key}`)
3374
+ ...selectedRepos.map((repo) => ` - ${repo.key}`)
4171
3375
  ];
4172
- if (lane) {
4173
- confirmationLines.splice(2, 0, ` lane: ${lane}`, "");
4174
- }
4175
3376
  const confirmed = await ui.confirm({
4176
3377
  message: confirmationLines.join("\n"),
4177
3378
  default: true
@@ -4180,11 +3381,10 @@ var runPpeLaneDeploy = async () => {
4180
3381
  logger.warn("\u5DF2\u53D6\u6D88\u53D1\u5E03");
4181
3382
  return;
4182
3383
  }
4183
- const githubLogin = shouldDeployCanvas ? await getCurrentGitHubLogin() : void 0;
4184
3384
  const repoStates = /* @__PURE__ */ new Map();
4185
3385
  const results = await withProgressTask(
4186
3386
  {
4187
- initialText: lane ? `\u6B63\u5728\u53D1\u5E03 lane ${lane}` : "\u6B63\u5728\u53D1\u5E03\u6240\u9009\u76EE\u6807",
3387
+ initialText: `\u6B63\u5728\u53D1\u5E03 lane ${lane}`,
4188
3388
  heartbeatMs: 1e3,
4189
3389
  heartbeatText: () => renderAggregateProgress(lane, selectedTargets, repoStates)
4190
3390
  },
@@ -4193,7 +3393,6 @@ var runPpeLaneDeploy = async () => {
4193
3393
  selectedTargets.map(async (target) => {
4194
3394
  const samples = await Promise.resolve(
4195
3395
  listSuccessfulWorkflowRunDurations({
4196
- ...target.kind === "canvas" ? { ref: target.defaultRef } : {},
4197
3396
  repoFullName: target.repoFullName,
4198
3397
  workflowFile: target.workflowFile
4199
3398
  })
@@ -4285,130 +3484,7 @@ var runPpeLaneDeploy = async () => {
4285
3484
  };
4286
3485
  }
4287
3486
  };
4288
- const deployCanvasTarget = async () => {
4289
- const currentState = repoStates.get(ppeLaneCanvasTarget.key) ?? { estimateMs: null, status: "idle" };
4290
- const targetRef = requestedRef;
4291
- const dispatchCreatedAt = (/* @__PURE__ */ new Date()).toISOString();
4292
- try {
4293
- repoStates.set(ppeLaneCanvasTarget.key, {
4294
- ...currentState,
4295
- conclusion: void 0,
4296
- startedAt: Date.now(),
4297
- status: "queued"
4298
- });
4299
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4300
- const knownRunIds = await listWorkflowRunIds({
4301
- ref: ppeLaneCanvasTarget.defaultWorkflowRef,
4302
- repoFullName: ppeLaneCanvasTarget.repoFullName,
4303
- workflowFile: ppeLaneCanvasTarget.workflowFile
4304
- });
4305
- await dispatchCanvasStagingVersionWorkflow({
4306
- repoFullName: ppeLaneCanvasTarget.repoFullName,
4307
- targetRef,
4308
- workflowFile: ppeLaneCanvasTarget.workflowFile,
4309
- workflowRef: ppeLaneCanvasTarget.defaultWorkflowRef
4310
- });
4311
- logger.step(`${ppeLaneCanvasTarget.key} -> queued`);
4312
- const run = await pollWorkflowRun({
4313
- correlation: {
4314
- actorLogin: githubLogin,
4315
- createdAt: dispatchCreatedAt
4316
- },
4317
- knownRunIds,
4318
- ref: ppeLaneCanvasTarget.defaultWorkflowRef,
4319
- repoFullName: ppeLaneCanvasTarget.repoFullName,
4320
- workflowFile: ppeLaneCanvasTarget.workflowFile,
4321
- onUpdate: (update) => {
4322
- const nextState = repoStates.get(ppeLaneCanvasTarget.key) ?? currentState;
4323
- const runUrl = "htmlUrl" in update ? update.htmlUrl : nextState.runUrl;
4324
- repoStates.set(ppeLaneCanvasTarget.key, {
4325
- ...nextState,
4326
- conclusion: void 0,
4327
- detail: update.status === "completed" ? "downloading artifact" : update.status === "pending" ? "waiting for workflow" : void 0,
4328
- runUrl,
4329
- startedAt: nextState.startedAt ?? Date.now(),
4330
- status: update.status === "completed" ? "running" : update.status === "pending" ? "queued" : "running"
4331
- });
4332
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4333
- }
4334
- });
4335
- logger.step(`${ppeLaneCanvasTarget.key} -> ${run.conclusion}`);
4336
- if (run.conclusion !== "success") {
4337
- repoStates.set(ppeLaneCanvasTarget.key, {
4338
- ...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
4339
- conclusion: "failure",
4340
- detail: run.failureMessage ?? "workflow failed",
4341
- runUrl: run.htmlUrl,
4342
- startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
4343
- status: "completed"
4344
- });
4345
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4346
- return {
4347
- key: ppeLaneCanvasTarget.key,
4348
- ref: targetRef,
4349
- runUrl: run.htmlUrl,
4350
- status: run.status,
4351
- conclusion: run.conclusion,
4352
- failureDetail: run.failureMessage ?? "workflow failed"
4353
- };
4354
- }
4355
- repoStates.set(ppeLaneCanvasTarget.key, {
4356
- ...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
4357
- conclusion: void 0,
4358
- detail: "downloading artifact",
4359
- runUrl: run.htmlUrl,
4360
- startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
4361
- status: "running"
4362
- });
4363
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4364
- const artifact = await fetchCanvasStagingVersionArtifact({
4365
- artifactFileName: ppeLaneCanvasTarget.artifactFileName,
4366
- artifactName: ppeLaneCanvasTarget.artifactName,
4367
- repoFullName: ppeLaneCanvasTarget.repoFullName,
4368
- runId: run.runId
4369
- });
4370
- repoStates.set(ppeLaneCanvasTarget.key, {
4371
- ...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
4372
- conclusion: "success",
4373
- detail: void 0,
4374
- runUrl: artifact.workflowRunUrl,
4375
- startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
4376
- status: "completed"
4377
- });
4378
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4379
- return {
4380
- key: ppeLaneCanvasTarget.key,
4381
- ref: targetRef,
4382
- artifact,
4383
- runUrl: artifact.workflowRunUrl,
4384
- status: run.status,
4385
- conclusion: "success"
4386
- };
4387
- } catch (error) {
4388
- const failureState = repoStates.get(ppeLaneCanvasTarget.key) ?? currentState;
4389
- repoStates.set(ppeLaneCanvasTarget.key, {
4390
- ...failureState,
4391
- conclusion: "failure",
4392
- detail: getErrorMessage(error),
4393
- runUrl: failureState.runUrl,
4394
- startedAt: failureState.startedAt ?? Date.now(),
4395
- status: "completed"
4396
- });
4397
- setText(renderAggregateProgress(lane, selectedTargets, repoStates));
4398
- return {
4399
- key: ppeLaneCanvasTarget.key,
4400
- ref: targetRef,
4401
- runUrl: failureState.runUrl ?? "",
4402
- status: "completed",
4403
- conclusion: "failure",
4404
- failureDetail: getErrorMessage(error)
4405
- };
4406
- }
4407
- };
4408
- return Promise.all([
4409
- ...selectedRepos.map((repo, index) => deployBackendTarget(repo, index)),
4410
- ...shouldDeployCanvas ? [deployCanvasTarget()] : []
4411
- ]);
3487
+ return Promise.all(selectedRepos.map((repo, index) => deployBackendTarget(repo, index)));
4412
3488
  }
4413
3489
  );
4414
3490
  results.forEach((result) => {
@@ -4416,28 +3492,14 @@ var runPpeLaneDeploy = async () => {
4416
3492
  `${result.key} | ${result.ref} | ${result.conclusion} | ${result.runUrl}${result.failureDetail ? ` | ${result.failureDetail}` : ""}`
4417
3493
  );
4418
3494
  });
4419
- const canvasResult = results.find((result) => result.key === ppeLaneCanvasTarget.key && result.conclusion === "success");
4420
- if (canvasResult?.artifact) {
4421
- logger.info(`canvas version id: ${canvasResult.artifact.versionId}`);
4422
- logger.info(`canvas workflow: ${canvasResult.artifact.workflowRunUrl}`);
4423
- logger.info(`override header: ${canvasResult.artifact.overrideHeader}`);
4424
- if (canvasResult.artifact.previewUrl) {
4425
- logger.info(`preview url: ${canvasResult.artifact.previewUrl}`);
4426
- }
4427
- if (canvasResult.artifact.previewAliasUrl) {
4428
- logger.info(`preview alias url: ${canvasResult.artifact.previewAliasUrl}`);
4429
- }
4430
- }
4431
3495
  if (results.some((result) => result.conclusion !== "success")) {
4432
3496
  throw new Error("One or more PPE lane deployments failed");
4433
3497
  }
4434
- logger.success(lane ? `PPE lane ${lane} \u53D1\u5E03\u5B8C\u6210` : "\u6240\u9009\u76EE\u6807\u53D1\u5E03\u5B8C\u6210");
4435
- if (selectedRepos.length > 0) {
4436
- logger.info("==================== \u91CD\u8981\u63D0\u793A ====================");
4437
- logger.info(`\u540E\u7EED\u8BF7\u6C42\u5982\u9700\u547D\u4E2D lane ${lane}\uFF0C\u8BF7\u6DFB\u52A0\u8BF7\u6C42\u5934\uFF1A`);
4438
- logger.info(`x-zk-ppe-lane: ${lane}`);
4439
- logger.info("=================================================");
4440
- }
3498
+ logger.success(`PPE lane ${lane} \u53D1\u5E03\u5B8C\u6210`);
3499
+ logger.info("==================== \u91CD\u8981\u63D0\u793A ====================");
3500
+ logger.info(`\u540E\u7EED\u8BF7\u6C42\u5982\u9700\u547D\u4E2D lane ${lane}\uFF0C\u8BF7\u6DFB\u52A0\u8BF7\u6C42\u5934\uFF1A`);
3501
+ logger.info(`x-zk-ppe-lane: ${lane}`);
3502
+ logger.info("=================================================");
4441
3503
  };
4442
3504
  var registerPpeLaneDeployCommand = (program) => {
4443
3505
  program.command("deploy").description("Deploy selected repositories to a PPE lane; route traffic with x-zk-ppe-lane").addHelpText(
@@ -4463,7 +3525,11 @@ var runPpeLaneDestroy = async () => {
4463
3525
  const lane = validatePpeLaneName(await ui.input({ message: t("ppeLane.destroy.enterLane") }));
4464
3526
  const selectedKeys = await ui.checkbox({
4465
3527
  message: t("ppeLane.destroy.selectRepos"),
4466
- choices: ppeLaneRepos.map((repo) => ({ value: repo.key, name: repo.label, checked: true }))
3528
+ choices: ppeLaneRepos.map((repo) => ({
3529
+ value: repo.key,
3530
+ name: repo.label,
3531
+ checked: repo.defaultDestroySelection !== false
3532
+ }))
4467
3533
  });
4468
3534
  if (selectedKeys.length === 0) {
4469
3535
  throw new CliError(t("ppeLane.destroy.atLeastOneRepo"), 1);
@@ -5082,10 +4148,18 @@ var registerReleasePrepareCommand = (program) => {
5082
4148
  };
5083
4149
 
5084
4150
  // src/commands/release/rollback.ts
5085
- import { mkdtemp as mkdtemp3, readFile as readFile3, rm as rm2 } from "fs/promises";
4151
+ import { mkdtemp as mkdtemp2, readFile as readFile2, rm } from "fs/promises";
5086
4152
  import { tmpdir } from "os";
5087
4153
  import { join as join5 } from "path";
5088
4154
 
4155
+ // src/constants/rollback-targets.ts
4156
+ var tuttiServerRollbackTarget = {
4157
+ key: "tutti-server",
4158
+ label: "tutti-server",
4159
+ repoFullName: "tutti-lab/tutti-server"
4160
+ };
4161
+ var rollbackTargets = [tuttiServerRollbackTarget];
4162
+
5089
4163
  // src/commands/release/rollback-local.ts
5090
4164
  var CLUSTER = "arn:aws:eks:us-east-1:250509935467:cluster/nexight-ojo-us-east-1";
5091
4165
  var REPOSITORY = "250509935467.dkr.ecr.us-east-1.amazonaws.com/nexight/tutti/tutti-server";
@@ -5173,7 +4247,7 @@ var fetchLocalRollbackCatalog = async () => {
5173
4247
  };
5174
4248
 
5175
4249
  // src/commands/release/rollback.ts
5176
- var REPO = "tutti-lab/tutti-server";
4250
+ var REPO = tuttiServerRollbackTarget.repoFullName;
5177
4251
  var WORKFLOW = "rollback.yml";
5178
4252
  var SHA256 = /^sha256:[0-9a-f]{64}$/;
5179
4253
  var SINGAPORE_TIME = new Intl.DateTimeFormat("sv-SE", {
@@ -5254,15 +4328,15 @@ var dispatch = async (operation, inputs = {}) => {
5254
4328
  if (run.conclusion !== "success") {
5255
4329
  let state = "";
5256
4330
  if (operation === "execute") {
5257
- const dir = await mkdtemp3(join5(tmpdir(), "tutti-rollback-state-"));
4331
+ const dir = await mkdtemp2(join5(tmpdir(), "tutti-rollback-state-"));
5258
4332
  try {
5259
4333
  await gh(["run", "download", String(run.runId), "--repo", REPO, "--name", "rollback-state", "--dir", dir]);
5260
4334
  state = `
5261
- ${await readFile3(join5(dir, "rollback-state.txt"), "utf8")}`;
4335
+ ${await readFile2(join5(dir, "rollback-state.txt"), "utf8")}`;
5262
4336
  } catch {
5263
4337
  state = "\nActual production state is available in the workflow logs.";
5264
4338
  } finally {
5265
- await rm2(dir, { recursive: true, force: true });
4339
+ await rm(dir, { recursive: true, force: true });
5266
4340
  }
5267
4341
  }
5268
4342
  throw new CliError(`Workflow failed: ${run.failureMessage ?? run.conclusion}
@@ -5274,12 +4348,12 @@ ${run.htmlUrl}${state}`, 1);
5274
4348
  }
5275
4349
  };
5276
4350
  var fetchCatalog = async (runId) => {
5277
- const dir = await mkdtemp3(join5(tmpdir(), "tutti-rollback-"));
4351
+ const dir = await mkdtemp2(join5(tmpdir(), "tutti-rollback-"));
5278
4352
  try {
5279
4353
  await gh(["run", "download", String(runId), "--repo", REPO, "--name", "rollback-catalog", "--dir", dir]);
5280
- return parseRollbackCatalog(await readFile3(join5(dir, "rollback-catalog.json"), "utf8"));
4354
+ return parseRollbackCatalog(await readFile2(join5(dir, "rollback-catalog.json"), "utf8"));
5281
4355
  } finally {
5282
- await rm2(dir, { recursive: true, force: true });
4356
+ await rm(dir, { recursive: true, force: true });
5283
4357
  }
5284
4358
  };
5285
4359
  var readCatalog = async () => {
@@ -5341,8 +4415,12 @@ var runTuttiRollback = async (options) => {
5341
4415
  logger.info(run.htmlUrl);
5342
4416
  };
5343
4417
  var registerReleaseRollbackCommand = (program) => {
5344
- program.command("rollback").description("Restore a recent production image").argument("<repository>", "Currently supported: tutti-server").option("--revision <number>", "Select a recent Helm revision without the chooser").option("--yes", "Skip the final confirmation").action(async (repository, options) => {
5345
- if (repository !== "tutti-server") {
4418
+ program.command("rollback").description("Restore a recent production image").argument("[repository]", "Rollback target; omit to choose interactively").option("--revision <number>", "Select a recent Helm revision without the chooser").option("--yes", "Skip the final confirmation").action(async (repository, options) => {
4419
+ const selected = repository ?? await ui.select({
4420
+ message: "Select a repository to roll back",
4421
+ choices: rollbackTargets.map((target) => ({ name: target.label, value: target.key }))
4422
+ });
4423
+ if (selected !== tuttiServerRollbackTarget.key) {
5346
4424
  throw new CliError("Only tutti-server is currently supported", 1);
5347
4425
  }
5348
4426
  await runTuttiRollback(options);
@@ -5756,7 +4834,6 @@ var registerUpdateVersionCommand = (program) => {
5756
4834
  var registerCommands = (program) => {
5757
4835
  registerAdminCommand(program);
5758
4836
  registerDataFinderCommand(program);
5759
- registerDiagnoseCommand(program);
5760
4837
  registerDoctorCommand(program);
5761
4838
  registerOnboardCommand(program);
5762
4839
  registerPpeLaneCommand(program);