@tutti-os/nexight-devops 0.5.4 → 0.6.1
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/README.md +9 -18
- package/dist/cli.js +834 -1644
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
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 = (
|
|
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 ? `${
|
|
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,
|
|
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}${
|
|
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(
|
|
389
|
-
return this.request("GET",
|
|
388
|
+
get(path) {
|
|
389
|
+
return this.request("GET", path);
|
|
390
390
|
}
|
|
391
|
-
post(
|
|
392
|
-
return this.request("POST",
|
|
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 (
|
|
1620
|
-
const response = await client.dataFinder(
|
|
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(
|
|
1670
|
-
if (!
|
|
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(
|
|
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/
|
|
2086
|
-
|
|
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/
|
|
2103
|
-
var
|
|
2104
|
-
var
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
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
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
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
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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 (
|
|
2204
|
-
|
|
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
|
|
2209
|
-
|
|
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
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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 (
|
|
2279
|
-
|
|
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
|
-
|
|
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
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
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
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
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
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
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
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2491
|
-
|
|
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
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
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
|
-
|
|
2406
|
+
const loginResult = await runCommand("aws", ["sso", "login", "--profile", profile], { stdio: "inherit" });
|
|
2407
|
+
if (loginResult.exitCode !== 0) {
|
|
2503
2408
|
return {
|
|
2504
|
-
|
|
2505
|
-
|
|
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
|
-
|
|
2511
|
-
|
|
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
|
|
2516
|
-
const
|
|
2517
|
-
const
|
|
2518
|
-
|
|
2519
|
-
|
|
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
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
var
|
|
2633
|
-
|
|
2634
|
-
|
|
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
|
|
2447
|
+
return value;
|
|
2638
2448
|
};
|
|
2639
|
-
var
|
|
2640
|
-
const
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
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
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
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
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
"
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
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",
|
|
@@ -3225,6 +3142,9 @@ var matchesWorkflowRunCorrelation = (run, correlation) => {
|
|
|
3225
3142
|
if (correlation.actorLogin && run.actor?.login !== correlation.actorLogin) {
|
|
3226
3143
|
return false;
|
|
3227
3144
|
}
|
|
3145
|
+
if (correlation.runName && run.display_title !== correlation.runName) {
|
|
3146
|
+
return false;
|
|
3147
|
+
}
|
|
3228
3148
|
if (!isRunCreatedAtOrAfter(run.created_at, correlation.createdAt)) {
|
|
3229
3149
|
return false;
|
|
3230
3150
|
}
|
|
@@ -3333,764 +3253,36 @@ var pollWorkflowRun = async (input2) => {
|
|
|
3333
3253
|
if (!matchedRun) {
|
|
3334
3254
|
input2.onUpdate?.({ runId: trackedRunId, status: "pending" });
|
|
3335
3255
|
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;
|
|
3935
|
-
}
|
|
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"));
|
|
3950
|
-
}
|
|
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"));
|
|
3964
|
-
}
|
|
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;
|
|
3256
|
+
continue;
|
|
3979
3257
|
}
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
choices: [
|
|
3985
|
-
{ value: "continue", name: t("common.continue") },
|
|
3986
|
-
{ value: "skip", name: t("common.skip") }
|
|
3987
|
-
]
|
|
3258
|
+
input2.onUpdate?.({
|
|
3259
|
+
runId: trackedRunId,
|
|
3260
|
+
htmlUrl: matchedRun.html_url,
|
|
3261
|
+
status: matchedRun.status === "completed" ? "completed" : "in_progress"
|
|
3988
3262
|
});
|
|
3989
|
-
if (
|
|
3990
|
-
const
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3263
|
+
if (matchedRun.status === "completed") {
|
|
3264
|
+
const completion = {
|
|
3265
|
+
conclusion: matchedRun.conclusion ?? "unknown",
|
|
3266
|
+
htmlUrl: matchedRun.html_url,
|
|
3267
|
+
runId: matchedRun.id,
|
|
3268
|
+
status: matchedRun.status
|
|
3269
|
+
};
|
|
3270
|
+
if (completion.conclusion !== "success") {
|
|
3271
|
+
const failureMessage = await fetchWorkflowRunFailureMessage({
|
|
3272
|
+
repoFullName: input2.repoFullName,
|
|
3273
|
+
runId: matchedRun.id
|
|
3274
|
+
});
|
|
3275
|
+
if (failureMessage) {
|
|
3276
|
+
completion.failureMessage = failureMessage;
|
|
3277
|
+
}
|
|
3994
3278
|
}
|
|
3995
|
-
|
|
3996
|
-
logger.info(t("onboard.skipAwsSetup"));
|
|
3997
|
-
config.setup.awsConfigured = true;
|
|
3279
|
+
return completion;
|
|
3998
3280
|
}
|
|
3281
|
+
await sleep(intervalMs);
|
|
3999
3282
|
}
|
|
4000
|
-
|
|
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"
|
|
3283
|
+
throw new CliError(`Timed out waiting for workflow run in ${input2.repoFullName}`, 1);
|
|
4017
3284
|
};
|
|
4018
3285
|
|
|
4019
|
-
// src/constants/ppe-lane-repos.ts
|
|
4020
|
-
var ppeLaneRepos = [
|
|
4021
|
-
{
|
|
4022
|
-
key: "tutti-server",
|
|
4023
|
-
label: "tutti-server",
|
|
4024
|
-
repoFullName: "tutti-lab/tutti-server",
|
|
4025
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4026
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4027
|
-
defaultRef: "main",
|
|
4028
|
-
defaultDestroySelection: false,
|
|
4029
|
-
cloudMapServiceId: "srv-izinli73rwxpf2dr",
|
|
4030
|
-
serviceName: "tutti-server"
|
|
4031
|
-
},
|
|
4032
|
-
{
|
|
4033
|
-
key: "zk-agent-engine",
|
|
4034
|
-
label: "zk-agent-engine",
|
|
4035
|
-
repoFullName: "zk-org-test/zk-agent-engine",
|
|
4036
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4037
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4038
|
-
defaultRef: "main",
|
|
4039
|
-
cloudMapServiceId: "srv-gg2qso5pnqgykn7h",
|
|
4040
|
-
serviceName: "agent-engine"
|
|
4041
|
-
},
|
|
4042
|
-
{
|
|
4043
|
-
key: "zk-commerce",
|
|
4044
|
-
label: "zk-commerce",
|
|
4045
|
-
repoFullName: "zk-org-test/zk-commerce",
|
|
4046
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4047
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4048
|
-
defaultRef: "main",
|
|
4049
|
-
cloudMapServiceId: "srv-tfl3t6duyoehwedi",
|
|
4050
|
-
serviceName: "zk-commerce"
|
|
4051
|
-
},
|
|
4052
|
-
{
|
|
4053
|
-
key: "zk-admin-server",
|
|
4054
|
-
label: "zk-admin-server",
|
|
4055
|
-
repoFullName: "zk-org-test/zk-admin-server",
|
|
4056
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4057
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4058
|
-
defaultRef: "main",
|
|
4059
|
-
cloudMapServiceId: "srv-s4fn35h7nen4u5ci",
|
|
4060
|
-
serviceName: "admin"
|
|
4061
|
-
},
|
|
4062
|
-
{
|
|
4063
|
-
key: "design-project",
|
|
4064
|
-
label: "design-project",
|
|
4065
|
-
repoFullName: "zk-org-test/design-project",
|
|
4066
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4067
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4068
|
-
defaultRef: "main",
|
|
4069
|
-
cloudMapServiceId: "srv-bjtdvtcxg2clgs6m",
|
|
4070
|
-
serviceName: "design-project"
|
|
4071
|
-
},
|
|
4072
|
-
{
|
|
4073
|
-
key: "account",
|
|
4074
|
-
label: "account",
|
|
4075
|
-
repoFullName: "zk-org-test/account",
|
|
4076
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4077
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4078
|
-
defaultRef: "main",
|
|
4079
|
-
cloudMapServiceId: "srv-j2newpni247wsn7h",
|
|
4080
|
-
serviceName: "account"
|
|
4081
|
-
},
|
|
4082
|
-
{
|
|
4083
|
-
key: "zk-websocket",
|
|
4084
|
-
label: "zk-websocket",
|
|
4085
|
-
repoFullName: "zk-org-test/zk-websocket",
|
|
4086
|
-
deployWorkflowFile: "deploy-lane.yml",
|
|
4087
|
-
destroyWorkflowFile: "destroy-lane.yml",
|
|
4088
|
-
defaultRef: "main",
|
|
4089
|
-
cloudMapServiceId: "srv-34fi45edgao7tp2r",
|
|
4090
|
-
serviceName: "zk-websocket"
|
|
4091
|
-
}
|
|
4092
|
-
];
|
|
4093
|
-
|
|
4094
3286
|
// src/lib/ppe-lane.ts
|
|
4095
3287
|
var PPE_LANE_MAX_LENGTH = 38;
|
|
4096
3288
|
var PPE_LANE_PATTERN = /^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?$/;
|
|
@@ -4111,48 +3303,50 @@ var validatePpeLaneName = (value) => {
|
|
|
4111
3303
|
return lane;
|
|
4112
3304
|
};
|
|
4113
3305
|
|
|
3306
|
+
// src/lib/prerequisites.ts
|
|
3307
|
+
var ensureCommandInstalled = async (command, installHint) => {
|
|
3308
|
+
const result = await runCommand(command, ["--version"]);
|
|
3309
|
+
if (result.exitCode !== 0) {
|
|
3310
|
+
throw new CliError(installHint, 1);
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
var ensurePrerequisites = async (input2) => {
|
|
3314
|
+
const config = await loadConfig();
|
|
3315
|
+
if (input2.needsGh) {
|
|
3316
|
+
await ensureCommandInstalled("gh", "gh is required. Run nexight-devops onboard --set-mode gh");
|
|
3317
|
+
await ensureGhAuthenticated();
|
|
3318
|
+
}
|
|
3319
|
+
if (input2.needsAws) {
|
|
3320
|
+
await ensureCommandInstalled("aws", "aws is required. Run nexight-devops onboard --set-mode aws");
|
|
3321
|
+
await ensureAwsAuthenticated(config.defaultAwsProfile);
|
|
3322
|
+
}
|
|
3323
|
+
return config;
|
|
3324
|
+
};
|
|
3325
|
+
|
|
4114
3326
|
// src/commands/ppe-lane/deploy.ts
|
|
4115
3327
|
var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
4116
3328
|
var renderAggregateProgress = (lane, selectedTargets, repoStates) => renderWorkflowProgressPanel({
|
|
4117
3329
|
items: selectedTargets,
|
|
4118
3330
|
states: repoStates,
|
|
4119
|
-
title:
|
|
3331
|
+
title: `\u6B63\u5728\u53D1\u5E03 lane ${lane}`
|
|
4120
3332
|
});
|
|
4121
3333
|
var runPpeLaneDeploy = async () => {
|
|
4122
3334
|
const requestedRef = await ui.input({ message: "\u8BF7\u8F93\u5165\u76EE\u6807\u5206\u652F", default: "main" });
|
|
4123
|
-
const deployTargetChoices = [
|
|
4124
|
-
...ppeLaneRepos.map((repo) => ({ value: repo.key, name: repo.label })),
|
|
4125
|
-
{ value: ppeLaneCanvasTarget.key, name: ppeLaneCanvasTarget.label }
|
|
4126
|
-
];
|
|
4127
3335
|
const selectedKeys = await ui.checkbox({
|
|
4128
|
-
message: "\u8BF7\u9009\u62E9\u9700\u8981\u53D1\u5E03\u7684\
|
|
4129
|
-
choices:
|
|
3336
|
+
message: "\u8BF7\u9009\u62E9\u9700\u8981\u53D1\u5E03\u7684\u4ED3\u5E93",
|
|
3337
|
+
choices: ppeLaneRepos.map((repo) => ({ value: repo.key, name: repo.label }))
|
|
4130
3338
|
});
|
|
4131
|
-
if (selectedKeys.length === 0) {
|
|
4132
|
-
throw new CliError("At least one deploy target must be selected", 1);
|
|
4133
|
-
}
|
|
4134
3339
|
const selectedRepos = ppeLaneRepos.filter((repo) => selectedKeys.includes(repo.key));
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
...shouldDeployCanvas ? [
|
|
4146
|
-
{
|
|
4147
|
-
defaultRef: ppeLaneCanvasTarget.defaultWorkflowRef,
|
|
4148
|
-
key: ppeLaneCanvasTarget.key,
|
|
4149
|
-
kind: "canvas",
|
|
4150
|
-
repoFullName: ppeLaneCanvasTarget.repoFullName,
|
|
4151
|
-
workflowFile: ppeLaneCanvasTarget.workflowFile
|
|
4152
|
-
}
|
|
4153
|
-
] : []
|
|
4154
|
-
];
|
|
4155
|
-
const config = await ensurePrerequisites({ needsAws: selectedRepos.length > 0, needsGh: true });
|
|
3340
|
+
if (selectedRepos.length === 0) {
|
|
3341
|
+
throw new CliError("At least one deploy repository must be selected", 1);
|
|
3342
|
+
}
|
|
3343
|
+
const lane = validatePpeLaneName(await ui.input({ message: "\u8BF7\u8F93\u5165 PPE lane \u540D\u79F0" }));
|
|
3344
|
+
const selectedTargets = selectedRepos.map((repo) => ({
|
|
3345
|
+
key: repo.key,
|
|
3346
|
+
repoFullName: repo.repoFullName,
|
|
3347
|
+
workflowFile: repo.deployWorkflowFile
|
|
3348
|
+
}));
|
|
3349
|
+
const config = await ensurePrerequisites({ needsAws: true, needsGh: true });
|
|
4156
3350
|
const branchResults = await Promise.all(
|
|
4157
3351
|
selectedRepos.map(
|
|
4158
3352
|
(repo) => resolveBranchForRepo({
|
|
@@ -4177,12 +3371,11 @@ var runPpeLaneDeploy = async () => {
|
|
|
4177
3371
|
const confirmationLines = [
|
|
4178
3372
|
"\u786E\u8BA4\u5F00\u59CB\u90E8\u7F72\uFF1F",
|
|
4179
3373
|
` \u5206\u652F: ${requestedRef}`,
|
|
3374
|
+
` lane: ${lane}`,
|
|
3375
|
+
"",
|
|
4180
3376
|
" \u5DF2\u9009\u62E9\u76EE\u6807:",
|
|
4181
|
-
...
|
|
3377
|
+
...selectedRepos.map((repo) => ` - ${repo.key}`)
|
|
4182
3378
|
];
|
|
4183
|
-
if (lane) {
|
|
4184
|
-
confirmationLines.splice(2, 0, ` lane: ${lane}`, "");
|
|
4185
|
-
}
|
|
4186
3379
|
const confirmed = await ui.confirm({
|
|
4187
3380
|
message: confirmationLines.join("\n"),
|
|
4188
3381
|
default: true
|
|
@@ -4191,11 +3384,10 @@ var runPpeLaneDeploy = async () => {
|
|
|
4191
3384
|
logger.warn("\u5DF2\u53D6\u6D88\u53D1\u5E03");
|
|
4192
3385
|
return;
|
|
4193
3386
|
}
|
|
4194
|
-
const githubLogin = shouldDeployCanvas ? await getCurrentGitHubLogin() : void 0;
|
|
4195
3387
|
const repoStates = /* @__PURE__ */ new Map();
|
|
4196
3388
|
const results = await withProgressTask(
|
|
4197
3389
|
{
|
|
4198
|
-
initialText:
|
|
3390
|
+
initialText: `\u6B63\u5728\u53D1\u5E03 lane ${lane}`,
|
|
4199
3391
|
heartbeatMs: 1e3,
|
|
4200
3392
|
heartbeatText: () => renderAggregateProgress(lane, selectedTargets, repoStates)
|
|
4201
3393
|
},
|
|
@@ -4204,7 +3396,6 @@ var runPpeLaneDeploy = async () => {
|
|
|
4204
3396
|
selectedTargets.map(async (target) => {
|
|
4205
3397
|
const samples = await Promise.resolve(
|
|
4206
3398
|
listSuccessfulWorkflowRunDurations({
|
|
4207
|
-
...target.kind === "canvas" ? { ref: target.defaultRef } : {},
|
|
4208
3399
|
repoFullName: target.repoFullName,
|
|
4209
3400
|
workflowFile: target.workflowFile
|
|
4210
3401
|
})
|
|
@@ -4296,130 +3487,7 @@ var runPpeLaneDeploy = async () => {
|
|
|
4296
3487
|
};
|
|
4297
3488
|
}
|
|
4298
3489
|
};
|
|
4299
|
-
|
|
4300
|
-
const currentState = repoStates.get(ppeLaneCanvasTarget.key) ?? { estimateMs: null, status: "idle" };
|
|
4301
|
-
const targetRef = requestedRef;
|
|
4302
|
-
const dispatchCreatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4303
|
-
try {
|
|
4304
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4305
|
-
...currentState,
|
|
4306
|
-
conclusion: void 0,
|
|
4307
|
-
startedAt: Date.now(),
|
|
4308
|
-
status: "queued"
|
|
4309
|
-
});
|
|
4310
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4311
|
-
const knownRunIds = await listWorkflowRunIds({
|
|
4312
|
-
ref: ppeLaneCanvasTarget.defaultWorkflowRef,
|
|
4313
|
-
repoFullName: ppeLaneCanvasTarget.repoFullName,
|
|
4314
|
-
workflowFile: ppeLaneCanvasTarget.workflowFile
|
|
4315
|
-
});
|
|
4316
|
-
await dispatchCanvasStagingVersionWorkflow({
|
|
4317
|
-
repoFullName: ppeLaneCanvasTarget.repoFullName,
|
|
4318
|
-
targetRef,
|
|
4319
|
-
workflowFile: ppeLaneCanvasTarget.workflowFile,
|
|
4320
|
-
workflowRef: ppeLaneCanvasTarget.defaultWorkflowRef
|
|
4321
|
-
});
|
|
4322
|
-
logger.step(`${ppeLaneCanvasTarget.key} -> queued`);
|
|
4323
|
-
const run = await pollWorkflowRun({
|
|
4324
|
-
correlation: {
|
|
4325
|
-
actorLogin: githubLogin,
|
|
4326
|
-
createdAt: dispatchCreatedAt
|
|
4327
|
-
},
|
|
4328
|
-
knownRunIds,
|
|
4329
|
-
ref: ppeLaneCanvasTarget.defaultWorkflowRef,
|
|
4330
|
-
repoFullName: ppeLaneCanvasTarget.repoFullName,
|
|
4331
|
-
workflowFile: ppeLaneCanvasTarget.workflowFile,
|
|
4332
|
-
onUpdate: (update) => {
|
|
4333
|
-
const nextState = repoStates.get(ppeLaneCanvasTarget.key) ?? currentState;
|
|
4334
|
-
const runUrl = "htmlUrl" in update ? update.htmlUrl : nextState.runUrl;
|
|
4335
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4336
|
-
...nextState,
|
|
4337
|
-
conclusion: void 0,
|
|
4338
|
-
detail: update.status === "completed" ? "downloading artifact" : update.status === "pending" ? "waiting for workflow" : void 0,
|
|
4339
|
-
runUrl,
|
|
4340
|
-
startedAt: nextState.startedAt ?? Date.now(),
|
|
4341
|
-
status: update.status === "completed" ? "running" : update.status === "pending" ? "queued" : "running"
|
|
4342
|
-
});
|
|
4343
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4344
|
-
}
|
|
4345
|
-
});
|
|
4346
|
-
logger.step(`${ppeLaneCanvasTarget.key} -> ${run.conclusion}`);
|
|
4347
|
-
if (run.conclusion !== "success") {
|
|
4348
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4349
|
-
...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
|
|
4350
|
-
conclusion: "failure",
|
|
4351
|
-
detail: run.failureMessage ?? "workflow failed",
|
|
4352
|
-
runUrl: run.htmlUrl,
|
|
4353
|
-
startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
|
|
4354
|
-
status: "completed"
|
|
4355
|
-
});
|
|
4356
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4357
|
-
return {
|
|
4358
|
-
key: ppeLaneCanvasTarget.key,
|
|
4359
|
-
ref: targetRef,
|
|
4360
|
-
runUrl: run.htmlUrl,
|
|
4361
|
-
status: run.status,
|
|
4362
|
-
conclusion: run.conclusion,
|
|
4363
|
-
failureDetail: run.failureMessage ?? "workflow failed"
|
|
4364
|
-
};
|
|
4365
|
-
}
|
|
4366
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4367
|
-
...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
|
|
4368
|
-
conclusion: void 0,
|
|
4369
|
-
detail: "downloading artifact",
|
|
4370
|
-
runUrl: run.htmlUrl,
|
|
4371
|
-
startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
|
|
4372
|
-
status: "running"
|
|
4373
|
-
});
|
|
4374
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4375
|
-
const artifact = await fetchCanvasStagingVersionArtifact({
|
|
4376
|
-
artifactFileName: ppeLaneCanvasTarget.artifactFileName,
|
|
4377
|
-
artifactName: ppeLaneCanvasTarget.artifactName,
|
|
4378
|
-
repoFullName: ppeLaneCanvasTarget.repoFullName,
|
|
4379
|
-
runId: run.runId
|
|
4380
|
-
});
|
|
4381
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4382
|
-
...repoStates.get(ppeLaneCanvasTarget.key) ?? currentState,
|
|
4383
|
-
conclusion: "success",
|
|
4384
|
-
detail: void 0,
|
|
4385
|
-
runUrl: artifact.workflowRunUrl,
|
|
4386
|
-
startedAt: repoStates.get(ppeLaneCanvasTarget.key)?.startedAt ?? Date.now(),
|
|
4387
|
-
status: "completed"
|
|
4388
|
-
});
|
|
4389
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4390
|
-
return {
|
|
4391
|
-
key: ppeLaneCanvasTarget.key,
|
|
4392
|
-
ref: targetRef,
|
|
4393
|
-
artifact,
|
|
4394
|
-
runUrl: artifact.workflowRunUrl,
|
|
4395
|
-
status: run.status,
|
|
4396
|
-
conclusion: "success"
|
|
4397
|
-
};
|
|
4398
|
-
} catch (error) {
|
|
4399
|
-
const failureState = repoStates.get(ppeLaneCanvasTarget.key) ?? currentState;
|
|
4400
|
-
repoStates.set(ppeLaneCanvasTarget.key, {
|
|
4401
|
-
...failureState,
|
|
4402
|
-
conclusion: "failure",
|
|
4403
|
-
detail: getErrorMessage(error),
|
|
4404
|
-
runUrl: failureState.runUrl,
|
|
4405
|
-
startedAt: failureState.startedAt ?? Date.now(),
|
|
4406
|
-
status: "completed"
|
|
4407
|
-
});
|
|
4408
|
-
setText(renderAggregateProgress(lane, selectedTargets, repoStates));
|
|
4409
|
-
return {
|
|
4410
|
-
key: ppeLaneCanvasTarget.key,
|
|
4411
|
-
ref: targetRef,
|
|
4412
|
-
runUrl: failureState.runUrl ?? "",
|
|
4413
|
-
status: "completed",
|
|
4414
|
-
conclusion: "failure",
|
|
4415
|
-
failureDetail: getErrorMessage(error)
|
|
4416
|
-
};
|
|
4417
|
-
}
|
|
4418
|
-
};
|
|
4419
|
-
return Promise.all([
|
|
4420
|
-
...selectedRepos.map((repo, index) => deployBackendTarget(repo, index)),
|
|
4421
|
-
...shouldDeployCanvas ? [deployCanvasTarget()] : []
|
|
4422
|
-
]);
|
|
3490
|
+
return Promise.all(selectedRepos.map((repo, index) => deployBackendTarget(repo, index)));
|
|
4423
3491
|
}
|
|
4424
3492
|
);
|
|
4425
3493
|
results.forEach((result) => {
|
|
@@ -4427,28 +3495,14 @@ var runPpeLaneDeploy = async () => {
|
|
|
4427
3495
|
`${result.key} | ${result.ref} | ${result.conclusion} | ${result.runUrl}${result.failureDetail ? ` | ${result.failureDetail}` : ""}`
|
|
4428
3496
|
);
|
|
4429
3497
|
});
|
|
4430
|
-
const canvasResult = results.find((result) => result.key === ppeLaneCanvasTarget.key && result.conclusion === "success");
|
|
4431
|
-
if (canvasResult?.artifact) {
|
|
4432
|
-
logger.info(`canvas version id: ${canvasResult.artifact.versionId}`);
|
|
4433
|
-
logger.info(`canvas workflow: ${canvasResult.artifact.workflowRunUrl}`);
|
|
4434
|
-
logger.info(`override header: ${canvasResult.artifact.overrideHeader}`);
|
|
4435
|
-
if (canvasResult.artifact.previewUrl) {
|
|
4436
|
-
logger.info(`preview url: ${canvasResult.artifact.previewUrl}`);
|
|
4437
|
-
}
|
|
4438
|
-
if (canvasResult.artifact.previewAliasUrl) {
|
|
4439
|
-
logger.info(`preview alias url: ${canvasResult.artifact.previewAliasUrl}`);
|
|
4440
|
-
}
|
|
4441
|
-
}
|
|
4442
3498
|
if (results.some((result) => result.conclusion !== "success")) {
|
|
4443
3499
|
throw new Error("One or more PPE lane deployments failed");
|
|
4444
3500
|
}
|
|
4445
|
-
logger.success(
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
logger.info("=================================================");
|
|
4451
|
-
}
|
|
3501
|
+
logger.success(`PPE lane ${lane} \u53D1\u5E03\u5B8C\u6210`);
|
|
3502
|
+
logger.info("==================== \u91CD\u8981\u63D0\u793A ====================");
|
|
3503
|
+
logger.info(`\u540E\u7EED\u8BF7\u6C42\u5982\u9700\u547D\u4E2D lane ${lane}\uFF0C\u8BF7\u6DFB\u52A0\u8BF7\u6C42\u5934\uFF1A`);
|
|
3504
|
+
logger.info(`x-zk-ppe-lane: ${lane}`);
|
|
3505
|
+
logger.info("=================================================");
|
|
4452
3506
|
};
|
|
4453
3507
|
var registerPpeLaneDeployCommand = (program) => {
|
|
4454
3508
|
program.command("deploy").description("Deploy selected repositories to a PPE lane; route traffic with x-zk-ppe-lane").addHelpText(
|
|
@@ -5098,8 +4152,8 @@ var registerReleasePrepareCommand = (program) => {
|
|
|
5098
4152
|
|
|
5099
4153
|
// src/commands/release/rollback.ts
|
|
5100
4154
|
import { mkdtemp as mkdtemp3, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
5101
|
-
import { tmpdir } from "os";
|
|
5102
|
-
import { join as
|
|
4155
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4156
|
+
import { join as join6 } from "path";
|
|
5103
4157
|
|
|
5104
4158
|
// src/constants/rollback-targets.ts
|
|
5105
4159
|
var tuttiServerRollbackTarget = {
|
|
@@ -5107,7 +4161,12 @@ var tuttiServerRollbackTarget = {
|
|
|
5107
4161
|
label: "tutti-server",
|
|
5108
4162
|
repoFullName: "tutti-lab/tutti-server"
|
|
5109
4163
|
};
|
|
5110
|
-
var
|
|
4164
|
+
var platformWebRollbackTarget = {
|
|
4165
|
+
key: "platform-web",
|
|
4166
|
+
label: "platform-web",
|
|
4167
|
+
repoFullName: "tutti-lab/tutti"
|
|
4168
|
+
};
|
|
4169
|
+
var rollbackTargets = [tuttiServerRollbackTarget, platformWebRollbackTarget];
|
|
5111
4170
|
|
|
5112
4171
|
// src/commands/release/rollback-local.ts
|
|
5113
4172
|
var CLUSTER = "arn:aws:eks:us-east-1:250509935467:cluster/nexight-ojo-us-east-1";
|
|
@@ -5195,9 +4254,137 @@ var fetchLocalRollbackCatalog = async () => {
|
|
|
5195
4254
|
return buildLocalRollbackCatalog(history, deployment, manifests);
|
|
5196
4255
|
};
|
|
5197
4256
|
|
|
4257
|
+
// src/commands/release/rollback-web.ts
|
|
4258
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4259
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, rm } from "fs/promises";
|
|
4260
|
+
import { tmpdir } from "os";
|
|
4261
|
+
import { join as join5 } from "path";
|
|
4262
|
+
var REPO = platformWebRollbackTarget.repoFullName;
|
|
4263
|
+
var WORKFLOW = "platform-web-rollback.yml";
|
|
4264
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
4265
|
+
var SHA = /^[0-9a-f]{40}$/;
|
|
4266
|
+
var choiceTime = new Intl.DateTimeFormat("sv-SE", { timeZone: "Asia/Singapore", dateStyle: "short", timeStyle: "short" });
|
|
4267
|
+
var validDeployment = (value) => UUID.test(value?.id) && Array.isArray(value.versions) && value.versions.length > 0 && value.versions.every((item) => UUID.test(item.version_id) && Number.isFinite(item.percentage) && item.percentage > 0 && item.percentage <= 100) && Math.abs(value.versions.reduce((sum, item) => sum + item.percentage, 0) - 100) < 1e-3;
|
|
4268
|
+
var parseWebRollbackCatalog = (raw) => {
|
|
4269
|
+
const data = JSON.parse(raw);
|
|
4270
|
+
if (data.schema !== "tutti.web.rollback.catalog.v1" || data.environment !== "production" || !validDeployment(data.currentDeployment) || !Array.isArray(data.candidates) || data.candidates.length > 100) {
|
|
4271
|
+
throw new CliError("Invalid Platform Web rollback catalog", 1);
|
|
4272
|
+
}
|
|
4273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4274
|
+
const active = new Set(data.currentDeployment.versions.map((item) => item.version_id));
|
|
4275
|
+
for (const candidate of data.candidates) {
|
|
4276
|
+
if (!UUID.test(candidate.versionId) || !UUID.test(candidate.deploymentId) || !Number.isFinite(Date.parse(candidate.deployedAt)) || typeof candidate.current !== "boolean" || candidate.current !== active.has(candidate.versionId) || typeof candidate.withdrawn !== "boolean" || typeof candidate.fullTraffic !== "boolean" || !(candidate.sha === null || SHA.test(candidate.sha)) || !(candidate.runId === null || /^\d+$/.test(candidate.runId)) || seen.has(candidate.versionId)) {
|
|
4277
|
+
throw new CliError("Invalid Platform Web deployment in rollback catalog", 1);
|
|
4278
|
+
}
|
|
4279
|
+
seen.add(candidate.versionId);
|
|
4280
|
+
}
|
|
4281
|
+
return data;
|
|
4282
|
+
};
|
|
4283
|
+
var buildWebRollbackChoices = (catalog) => catalog.candidates.filter((candidate) => !candidate.current && !candidate.withdrawn && candidate.fullTraffic).map((candidate) => ({
|
|
4284
|
+
name: `${choiceTime.format(new Date(candidate.deployedAt))} SGT ${candidate.versionId.slice(0, 8)} ${candidate.sha?.slice(0, 12) ?? "SHA unknown"}${candidate.runId ? ` run ${candidate.runId}` : ""}`,
|
|
4285
|
+
value: candidate.versionId
|
|
4286
|
+
}));
|
|
4287
|
+
var gh = async (args) => {
|
|
4288
|
+
const result = await runCommand("gh", args);
|
|
4289
|
+
if (result.exitCode !== 0) throw new CliError(result.stderr.trim() || result.stdout.trim() || "GitHub command failed", 1);
|
|
4290
|
+
return result.stdout;
|
|
4291
|
+
};
|
|
4292
|
+
var dispatch = async (operation, inputs = {}) => {
|
|
4293
|
+
const operationId = randomUUID3();
|
|
4294
|
+
const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4295
|
+
const knownRunIds = await listWorkflowRunIds({ repoFullName: REPO, workflowFile: WORKFLOW, ref: "main" });
|
|
4296
|
+
const fields = { operation, operation_id: operationId, requested_at: requestedAt, ...inputs };
|
|
4297
|
+
await gh(["workflow", "run", WORKFLOW, "--repo", REPO, "--ref", "main", ...Object.entries(fields).flatMap(([key, value]) => ["-f", `${key}=${value}`])]);
|
|
4298
|
+
const progress = ui.spinner(operation === "execute" ? "\u7B49\u5F85 Platform Web \u56DE\u6EDA\u548C\u5165\u53E3\u9A8C\u8BC1..." : "\u8BFB\u53D6 Platform Web \u751F\u4EA7\u72B6\u6001...").start();
|
|
4299
|
+
try {
|
|
4300
|
+
const run = await pollWorkflowRun({
|
|
4301
|
+
knownRunIds,
|
|
4302
|
+
repoFullName: REPO,
|
|
4303
|
+
workflowFile: WORKFLOW,
|
|
4304
|
+
ref: "main",
|
|
4305
|
+
intervalMs: 1e3,
|
|
4306
|
+
maxAttempts: 600,
|
|
4307
|
+
correlation: { runName: `Platform Web Rollback / ${operation} / ${operationId}`, createdAt: requestedAt },
|
|
4308
|
+
onUpdate: (update) => {
|
|
4309
|
+
if ("htmlUrl" in update) progress.text = update.htmlUrl;
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4312
|
+
const directory = await mkdtemp2(join5(tmpdir(), "platform-web-rollback-"));
|
|
4313
|
+
try {
|
|
4314
|
+
try {
|
|
4315
|
+
await gh(["run", "download", String(run.runId), "--repo", REPO, "--name", "platform-web-rollback", "--dir", directory]);
|
|
4316
|
+
} catch (error) {
|
|
4317
|
+
throw new CliError(`Could not retrieve operation outcome; inspect actual production state at ${run.htmlUrl} before retrying: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
4318
|
+
}
|
|
4319
|
+
const result = JSON.parse(await readFile2(join5(directory, "rollback-result.json"), "utf8"));
|
|
4320
|
+
if (result.schema !== "tutti.web.rollback.result.v1" || result.operationId !== operationId || !["success", "failure", "pending_verification"].includes(result.status) || !["unchanged", "switched", "unknown"].includes(result.deployment) || !result.verification || !result.timings || Object.values(result.timings).some((value) => !Number.isFinite(value) || value < 0) || result.actual && !validDeployment(result.actual)) {
|
|
4321
|
+
throw new CliError(`Invalid rollback result: ${run.htmlUrl}`, 1);
|
|
4322
|
+
}
|
|
4323
|
+
if (run.conclusion !== "success" || result.status !== "success") {
|
|
4324
|
+
const state = result.actual ? `${result.actual.id} / ${JSON.stringify(result.actual.versions)}` : "unknown";
|
|
4325
|
+
throw new CliError(`Platform Web operation failed; deployment ${result.deployment}
|
|
4326
|
+
Actual production: ${state}
|
|
4327
|
+
${result.error ?? run.failureMessage ?? run.conclusion}
|
|
4328
|
+
${run.htmlUrl}`, 1);
|
|
4329
|
+
}
|
|
4330
|
+
if (operation === "execute" && (result.deployment !== "switched" || !result.actual || !UUID.test(result.targetVersionId ?? "") || result.actual.versions.length !== 1 || result.actual.versions[0]?.version_id !== result.targetVersionId || result.actual.versions[0]?.percentage !== 100)) {
|
|
4331
|
+
throw new CliError(`Rollback did not confirm target version at 100%: ${run.htmlUrl}`, 1);
|
|
4332
|
+
}
|
|
4333
|
+
const catalog = operation === "list" ? parseWebRollbackCatalog(await readFile2(join5(directory, "rollback-catalog.json"), "utf8")) : void 0;
|
|
4334
|
+
return { run, result, catalog };
|
|
4335
|
+
} catch (error) {
|
|
4336
|
+
if (error instanceof CliError) throw error;
|
|
4337
|
+
throw new CliError(`Could not read actual operation state; inspect ${run.htmlUrl} before retrying: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
4338
|
+
} finally {
|
|
4339
|
+
await rm(directory, { recursive: true, force: true });
|
|
4340
|
+
}
|
|
4341
|
+
} finally {
|
|
4342
|
+
progress.stop();
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
var runPlatformWebRollback = async (options) => {
|
|
4346
|
+
if (options.previous && options.versionId || options.probe && (options.previous || options.versionId)) throw new CliError("Choose only one of --previous, --version-id, or --probe", 1);
|
|
4347
|
+
if (options.versionId && !UUID.test(options.versionId)) throw new CliError("--version-id must be a Worker version UUID", 1);
|
|
4348
|
+
if (options.reason && Buffer.byteLength(options.reason) > 200) throw new CliError("--reason must be at most 200 UTF-8 bytes", 1);
|
|
4349
|
+
await ensurePrerequisites({ needsGh: true });
|
|
4350
|
+
let inputs = { reason: options.reason ?? "operator rollback" };
|
|
4351
|
+
if (options.probe) {
|
|
4352
|
+
const { run: run2, result: result2 } = await dispatch("probe");
|
|
4353
|
+
logger.info(`Read-only production probe: ${JSON.stringify(result2.timings)}`);
|
|
4354
|
+
logger.info(`Verification: ${JSON.stringify(result2.verification)}`);
|
|
4355
|
+
logger.info(run2.htmlUrl);
|
|
4356
|
+
return;
|
|
4357
|
+
}
|
|
4358
|
+
if (options.yes && (options.previous || options.versionId)) {
|
|
4359
|
+
inputs = { ...inputs, previous: String(Boolean(options.previous)), version_id: options.versionId ?? "" };
|
|
4360
|
+
} else {
|
|
4361
|
+
const { catalog } = await dispatch("list", options.versionId ? { version_id: options.versionId } : {});
|
|
4362
|
+
if (!catalog) throw new CliError("Missing Platform Web rollback catalog", 1);
|
|
4363
|
+
const choices = buildWebRollbackChoices(catalog);
|
|
4364
|
+
if (!options.versionId && choices.length === 0) throw new CliError("No eligible recent production Web version is available", 1);
|
|
4365
|
+
const versionId = options.versionId ?? (options.previous ? choices[0]?.value : await ui.select({ message: "Select a production Web version to restore", choices, pageSize: Math.min(choices.length, 15) }));
|
|
4366
|
+
const selected = catalog.candidates.find((candidate) => candidate.versionId === versionId && !candidate.current);
|
|
4367
|
+
if (!selected) throw new CliError("No eligible recent production Web version is available", 1);
|
|
4368
|
+
logger.info(`Current deployment: ${catalog.currentDeployment.id}`);
|
|
4369
|
+
logger.info(`Target version: ${selected.versionId} / SHA ${selected.sha ?? "unknown"}`);
|
|
4370
|
+
if (selected.runId) logger.info(`Published by: https://github.com/${REPO}/actions/runs/${selected.runId}`);
|
|
4371
|
+
if (selected.withdrawn) logger.warn("This version was previously withdrawn by a rollback.");
|
|
4372
|
+
if (!options.yes && !await ui.confirm({ message: "Restore this Web version to production?", default: false })) {
|
|
4373
|
+
logger.warn("Rollback cancelled");
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
inputs = { ...inputs, previous: "false", version_id: selected.versionId, expected_deployment_id: catalog.currentDeployment.id };
|
|
4377
|
+
}
|
|
4378
|
+
const { run, result } = await dispatch("execute", inputs);
|
|
4379
|
+
logger.success(`Platform Web switched to ${result.targetVersionId} at 100% traffic`);
|
|
4380
|
+
logger.info(`Verification: ${JSON.stringify(result.verification)}`);
|
|
4381
|
+
logger.info(`Timing (ms): ${JSON.stringify(result.timings)}`);
|
|
4382
|
+
logger.info(run.htmlUrl);
|
|
4383
|
+
};
|
|
4384
|
+
|
|
5198
4385
|
// src/commands/release/rollback.ts
|
|
5199
|
-
var
|
|
5200
|
-
var
|
|
4386
|
+
var REPO2 = tuttiServerRollbackTarget.repoFullName;
|
|
4387
|
+
var WORKFLOW2 = "rollback.yml";
|
|
5201
4388
|
var SHA256 = /^sha256:[0-9a-f]{64}$/;
|
|
5202
4389
|
var SINGAPORE_TIME = new Intl.DateTimeFormat("sv-SE", {
|
|
5203
4390
|
timeZone: "Asia/Singapore",
|
|
@@ -5221,7 +4408,7 @@ var buildRollbackChoices = (catalog) => catalog.candidates.filter((candidate) =>
|
|
|
5221
4408
|
name: `${displayChoiceTime(candidate.deployedAt)} SGT #${candidate.revision} ${candidate.version}`,
|
|
5222
4409
|
value: candidate.revision
|
|
5223
4410
|
}));
|
|
5224
|
-
var
|
|
4411
|
+
var gh2 = async (args) => {
|
|
5225
4412
|
const result = await runCommand("gh", args);
|
|
5226
4413
|
if (result.exitCode !== 0) {
|
|
5227
4414
|
throw new CliError(result.stderr.trim() || result.stdout.trim() || `gh ${args[0]} failed`, 1);
|
|
@@ -5241,8 +4428,8 @@ var parseRollbackCatalog = (raw) => {
|
|
|
5241
4428
|
return data;
|
|
5242
4429
|
};
|
|
5243
4430
|
var listActiveDeployments = async () => {
|
|
5244
|
-
const runs = await Promise.all(["deploy.yml",
|
|
5245
|
-
const raw = await
|
|
4431
|
+
const runs = await Promise.all(["deploy.yml", WORKFLOW2].map(async (workflow) => {
|
|
4432
|
+
const raw = await gh2(["api", `repos/${REPO2}/actions/workflows/${workflow}/runs?per_page=30`]);
|
|
5246
4433
|
return JSON.parse(raw).workflow_runs;
|
|
5247
4434
|
}));
|
|
5248
4435
|
return runs.flat().filter(
|
|
@@ -5255,19 +4442,19 @@ var ensureNoActiveDeployment = async () => {
|
|
|
5255
4442
|
throw new CliError(`Production deployment is already running: ${active[0]?.html_url}`, 1);
|
|
5256
4443
|
}
|
|
5257
4444
|
};
|
|
5258
|
-
var
|
|
5259
|
-
const knownRunIds = await listWorkflowRunIds({ repoFullName:
|
|
5260
|
-
const args = ["workflow", "run",
|
|
4445
|
+
var dispatch2 = async (operation, inputs = {}) => {
|
|
4446
|
+
const knownRunIds = await listWorkflowRunIds({ repoFullName: REPO2, workflowFile: WORKFLOW2, ref: "main" });
|
|
4447
|
+
const args = ["workflow", "run", WORKFLOW2, "--repo", REPO2, "--ref", "main", "-f", `operation=${operation}`];
|
|
5261
4448
|
for (const [key, value] of Object.entries(inputs)) {
|
|
5262
4449
|
args.push("-f", `${key}=${value}`);
|
|
5263
4450
|
}
|
|
5264
|
-
await
|
|
4451
|
+
await gh2(args);
|
|
5265
4452
|
const progress = ui.spinner(operation === "list" ? "\u8BFB\u53D6\u751F\u4EA7\u90E8\u7F72\u5386\u53F2..." : "\u7B49\u5F85\u751F\u4EA7\u56DE\u6EDA\u548C\u5065\u5EB7\u68C0\u67E5...").start();
|
|
5266
4453
|
try {
|
|
5267
4454
|
const run = await pollWorkflowRun({
|
|
5268
4455
|
knownRunIds,
|
|
5269
|
-
repoFullName:
|
|
5270
|
-
workflowFile:
|
|
4456
|
+
repoFullName: REPO2,
|
|
4457
|
+
workflowFile: WORKFLOW2,
|
|
5271
4458
|
ref: "main",
|
|
5272
4459
|
maxAttempts: operation === "execute" ? 420 : 180,
|
|
5273
4460
|
onUpdate: (update) => {
|
|
@@ -5277,11 +4464,11 @@ var dispatch = async (operation, inputs = {}) => {
|
|
|
5277
4464
|
if (run.conclusion !== "success") {
|
|
5278
4465
|
let state = "";
|
|
5279
4466
|
if (operation === "execute") {
|
|
5280
|
-
const dir = await mkdtemp3(
|
|
4467
|
+
const dir = await mkdtemp3(join6(tmpdir2(), "tutti-rollback-state-"));
|
|
5281
4468
|
try {
|
|
5282
|
-
await
|
|
4469
|
+
await gh2(["run", "download", String(run.runId), "--repo", REPO2, "--name", "rollback-state", "--dir", dir]);
|
|
5283
4470
|
state = `
|
|
5284
|
-
${await readFile3(
|
|
4471
|
+
${await readFile3(join6(dir, "rollback-state.txt"), "utf8")}`;
|
|
5285
4472
|
} catch {
|
|
5286
4473
|
state = "\nActual production state is available in the workflow logs.";
|
|
5287
4474
|
} finally {
|
|
@@ -5297,10 +4484,10 @@ ${run.htmlUrl}${state}`, 1);
|
|
|
5297
4484
|
}
|
|
5298
4485
|
};
|
|
5299
4486
|
var fetchCatalog = async (runId) => {
|
|
5300
|
-
const dir = await mkdtemp3(
|
|
4487
|
+
const dir = await mkdtemp3(join6(tmpdir2(), "tutti-rollback-"));
|
|
5301
4488
|
try {
|
|
5302
|
-
await
|
|
5303
|
-
return parseRollbackCatalog(await readFile3(
|
|
4489
|
+
await gh2(["run", "download", String(runId), "--repo", REPO2, "--name", "rollback-catalog", "--dir", dir]);
|
|
4490
|
+
return parseRollbackCatalog(await readFile3(join6(dir, "rollback-catalog.json"), "utf8"));
|
|
5304
4491
|
} finally {
|
|
5305
4492
|
await rm2(dir, { recursive: true, force: true });
|
|
5306
4493
|
}
|
|
@@ -5314,7 +4501,7 @@ var readCatalog = async () => {
|
|
|
5314
4501
|
progress.stop();
|
|
5315
4502
|
}
|
|
5316
4503
|
logger.warn("\u672C\u673A\u65E0\u6CD5\u5B8C\u6574\u8BFB\u53D6\u751F\u4EA7\u90E8\u7F72\u5386\u53F2\uFF0C\u6539\u7528 GitHub Actions \u67E5\u8BE2\u3002");
|
|
5317
|
-
const listRun = await
|
|
4504
|
+
const listRun = await dispatch2("list");
|
|
5318
4505
|
return fetchCatalog(listRun.runId);
|
|
5319
4506
|
};
|
|
5320
4507
|
var runTuttiRollback = async (options) => {
|
|
@@ -5354,7 +4541,7 @@ var runTuttiRollback = async (options) => {
|
|
|
5354
4541
|
return;
|
|
5355
4542
|
}
|
|
5356
4543
|
await ensureNoActiveDeployment();
|
|
5357
|
-
const run = await
|
|
4544
|
+
const run = await dispatch2("execute", {
|
|
5358
4545
|
revision: String(selected.revision),
|
|
5359
4546
|
digest: selected.digest,
|
|
5360
4547
|
expected_current_digest: catalog.currentDigest,
|
|
@@ -5364,14 +4551,18 @@ var runTuttiRollback = async (options) => {
|
|
|
5364
4551
|
logger.info(run.htmlUrl);
|
|
5365
4552
|
};
|
|
5366
4553
|
var registerReleaseRollbackCommand = (program) => {
|
|
5367
|
-
program.command("rollback").description("Restore a recent production
|
|
4554
|
+
program.command("rollback").description("Restore a recent production deployment").argument("[repository]", "Rollback target; omit to choose interactively").option("--revision <number>", "Select a recent Helm revision without the chooser").option("--previous", "Restore the latest eligible previous Platform Web version").option("--version-id <uuid>", "Select a previously deployed Platform Web Worker version").option("--reason <text>", "Record a Platform Web rollback reason").option("--probe", "Measure a read-only Platform Web production probe").option("--yes", "Skip the final confirmation").action(async (repository, options) => {
|
|
5368
4555
|
const selected = repository ?? await ui.select({
|
|
5369
4556
|
message: "Select a repository to roll back",
|
|
5370
4557
|
choices: rollbackTargets.map((target) => ({ name: target.label, value: target.key }))
|
|
5371
4558
|
});
|
|
5372
|
-
if (selected
|
|
5373
|
-
throw new CliError("
|
|
4559
|
+
if (selected === platformWebRollbackTarget.key) {
|
|
4560
|
+
if (options.revision) throw new CliError("--revision applies only to tutti-server", 1);
|
|
4561
|
+
await runPlatformWebRollback(options);
|
|
4562
|
+
return;
|
|
5374
4563
|
}
|
|
4564
|
+
if (selected !== tuttiServerRollbackTarget.key) throw new CliError(`Unsupported rollback target: ${selected}`, 1);
|
|
4565
|
+
if (options.previous || options.versionId || options.reason || options.probe) throw new CliError("--previous, --version-id, --reason and --probe apply only to platform-web", 1);
|
|
5375
4566
|
await runTuttiRollback(options);
|
|
5376
4567
|
});
|
|
5377
4568
|
};
|
|
@@ -5401,7 +4592,7 @@ var readCliVersion = () => {
|
|
|
5401
4592
|
|
|
5402
4593
|
// src/lib/version-check.ts
|
|
5403
4594
|
import { realpath } from "fs/promises";
|
|
5404
|
-
import { join as
|
|
4595
|
+
import { join as join7, resolve as resolve3, sep } from "path";
|
|
5405
4596
|
var PACKAGE_NAME = "@tutti-os/nexight-devops";
|
|
5406
4597
|
var AUTO_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
5407
4598
|
var DEFAULT_LOCALE = "zh-CN";
|
|
@@ -5470,7 +4661,7 @@ var isInsidePackageRoot = (entryPath, root) => {
|
|
|
5470
4661
|
if (!entryPath || !root) {
|
|
5471
4662
|
return false;
|
|
5472
4663
|
}
|
|
5473
|
-
const packageRoot = resolve3(
|
|
4664
|
+
const packageRoot = resolve3(join7(root, "@tutti-os", "nexight-devops"));
|
|
5474
4665
|
const targetPath = resolve3(entryPath);
|
|
5475
4666
|
return targetPath === packageRoot || targetPath.startsWith(`${packageRoot}${sep}`);
|
|
5476
4667
|
};
|
|
@@ -5480,7 +4671,7 @@ var isInsideBunGlobalBin = (entryPath, bunGlobalBin) => {
|
|
|
5480
4671
|
}
|
|
5481
4672
|
const targetPath = resolve3(entryPath);
|
|
5482
4673
|
const binPath = resolve3(bunGlobalBin);
|
|
5483
|
-
const expectedEntryPath = resolve3(
|
|
4674
|
+
const expectedEntryPath = resolve3(join7(binPath, "nexight-devops"));
|
|
5484
4675
|
return targetPath === expectedEntryPath;
|
|
5485
4676
|
};
|
|
5486
4677
|
var readGlobalPath = async (command, args) => {
|
|
@@ -5783,7 +4974,6 @@ var registerUpdateVersionCommand = (program) => {
|
|
|
5783
4974
|
var registerCommands = (program) => {
|
|
5784
4975
|
registerAdminCommand(program);
|
|
5785
4976
|
registerDataFinderCommand(program);
|
|
5786
|
-
registerDiagnoseCommand(program);
|
|
5787
4977
|
registerDoctorCommand(program);
|
|
5788
4978
|
registerOnboardCommand(program);
|
|
5789
4979
|
registerPpeLaneCommand(program);
|