@gaoding/cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/postinstall.js +30 -3
- package/dist/src/bootstrap/create-runtime.js +5 -1
- package/dist/src/features/update/update-notification.js +2 -0
- package/dist/src/features/update/update-service.js +9 -0
- package/dist/src/platform/installation-owner.js +81 -0
- package/package.json +4 -4
- package/skills/gd-cli/references/update.md +2 -0
package/dist/bin/postinstall.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { loadBundledSkills } from "../src/features/skill/bundled-skills.js";
|
|
4
4
|
import { syncBundledSkills } from "../src/features/skill/installer.js";
|
|
5
|
+
import { markWorkBuddyInstallation, readInstallationOwner } from "../src/platform/installation-owner.js";
|
|
5
6
|
export function isSupportedGlobalLifecycle(env) {
|
|
6
7
|
const userAgent = env.npm_config_user_agent ?? "";
|
|
7
8
|
if (env.npm_config_global === "true") {
|
|
@@ -19,8 +20,31 @@ export async function runPostinstall(options = {}) {
|
|
|
19
20
|
if (!isSupportedGlobalLifecycle(env))
|
|
20
21
|
return;
|
|
21
22
|
const writeError = options.writeError ?? ((text) => process.stderr.write(text));
|
|
23
|
+
let skills;
|
|
24
|
+
try {
|
|
25
|
+
skills = (options.loadSkills ?? loadBundledSkills)(options.startFile ?? fileURLToPath(import.meta.url));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (env.GD_CLI_INSTALL_OWNER === "workbuddy") {
|
|
29
|
+
throw new Error("无法确认当前安装的 package。");
|
|
30
|
+
}
|
|
31
|
+
writeError(`警告: ${oneLineMessage(error)}\n`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const primarySkill = skills.find((skill) => skill.skill === "gd-cli");
|
|
35
|
+
if (!primarySkill)
|
|
36
|
+
throw new Error("Bundled gd-cli Skill is missing.");
|
|
37
|
+
const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
|
|
38
|
+
if (env.GD_CLI_INSTALL_OWNER === "workbuddy") {
|
|
39
|
+
markWorkBuddyInstallation(packageRoot);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const owner = readInstallationOwner(packageRoot);
|
|
43
|
+
if (owner === "workbuddy")
|
|
44
|
+
return;
|
|
45
|
+
if (owner === "invalid")
|
|
46
|
+
throw new Error("无法确认当前安装的托管信息。");
|
|
22
47
|
try {
|
|
23
|
-
const skills = (options.loadSkills ?? loadBundledSkills)(options.startFile ?? fileURLToPath(import.meta.url));
|
|
24
48
|
await (options.syncSkills ?? syncBundledSkills)(skills, {
|
|
25
49
|
env,
|
|
26
50
|
...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
|
|
@@ -38,5 +62,8 @@ const entryPath = process.argv[1];
|
|
|
38
62
|
const isDirectEntry = entryPath !== undefined
|
|
39
63
|
&& resolve(entryPath) === fileURLToPath(import.meta.url);
|
|
40
64
|
if (isDirectEntry || process.env.npm_lifecycle_event === "postinstall") {
|
|
41
|
-
void runPostinstall()
|
|
65
|
+
void runPostinstall().catch((error) => {
|
|
66
|
+
process.stderr.write(`${oneLineMessage(error)}\n`);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
});
|
|
42
69
|
}
|
|
@@ -37,6 +37,7 @@ import { createUpdateNotificationService } from "../features/update/update-notif
|
|
|
37
37
|
import { classifyTelemetryError } from "../cli/errors.js";
|
|
38
38
|
import { createTelemetryInvocation } from "../telemetry/invocation.js";
|
|
39
39
|
import { createSlsTelemetrySink } from "../telemetry/sls-sink.js";
|
|
40
|
+
import { readInstallationOwner } from "../platform/installation-owner.js";
|
|
40
41
|
const productionEndpoints = {
|
|
41
42
|
orgApi: new URL("https://gdcli.gaoding.com/api"),
|
|
42
43
|
agentApi: new URL("https://gdcli.gaoding.com/api"),
|
|
@@ -51,6 +52,8 @@ export function createProductionRuntime(options = {}) {
|
|
|
51
52
|
const primarySkill = bundledSkills.find((skill) => skill.skill === "gd-cli");
|
|
52
53
|
if (!primarySkill)
|
|
53
54
|
throw new Error("Bundled gd-cli Skill is missing.");
|
|
55
|
+
const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
|
|
56
|
+
const installationOwner = readInstallationOwner(packageRoot);
|
|
54
57
|
const endpoints = options.endpoints ?? productionEndpoints;
|
|
55
58
|
const fetch = options.fetch ?? globalThis.fetch;
|
|
56
59
|
const now = options.now ?? (() => new Date());
|
|
@@ -233,13 +236,13 @@ export function createProductionRuntime(options = {}) {
|
|
|
233
236
|
});
|
|
234
237
|
const update = {
|
|
235
238
|
run(signal) {
|
|
236
|
-
const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
|
|
237
239
|
return createUpdateService({
|
|
238
240
|
currentVersion: primarySkill.version,
|
|
239
241
|
packageRoot,
|
|
240
242
|
bundledSkills,
|
|
241
243
|
fetch,
|
|
242
244
|
telemetry,
|
|
245
|
+
installationOwner,
|
|
243
246
|
...(process.argv[1] ? { binaryPath: process.argv[1] } : {}),
|
|
244
247
|
...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
|
|
245
248
|
}).run(signal);
|
|
@@ -249,6 +252,7 @@ export function createProductionRuntime(options = {}) {
|
|
|
249
252
|
currentVersion: primarySkill.version,
|
|
250
253
|
fetch,
|
|
251
254
|
now,
|
|
255
|
+
installationOwner,
|
|
252
256
|
...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {}),
|
|
253
257
|
...(options.env ? { env: options.env } : {}),
|
|
254
258
|
notify: (message) => { presenter.notice(message); }
|
|
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { gt } from "semver";
|
|
5
5
|
import { syncBundledSkills, verifyBundledSkills } from "../skill/installer.js";
|
|
6
6
|
import { fetchLatestVersion } from "./latest-version.js";
|
|
7
|
+
import { readInstallationOwner } from "../../platform/installation-owner.js";
|
|
7
8
|
export class UpdateError extends Error {
|
|
8
9
|
code;
|
|
9
10
|
nextSteps;
|
|
@@ -57,6 +58,14 @@ export function createUpdateService(options) {
|
|
|
57
58
|
return {
|
|
58
59
|
async run(signal) {
|
|
59
60
|
signal.throwIfAborted();
|
|
61
|
+
const owner = options.installationOwner
|
|
62
|
+
?? readInstallationOwner(options.packageRoot);
|
|
63
|
+
if (owner === "workbuddy") {
|
|
64
|
+
throw new UpdateError("UPDATE_FAILED", "此 gd-cli 由 WorkBuddy 连接器管理。", ["请在 WorkBuddy 中更新「稿定」连接器。"]);
|
|
65
|
+
}
|
|
66
|
+
if (owner === "invalid") {
|
|
67
|
+
throw new UpdateError("UPDATE_FAILED", "无法确认当前安装的托管信息。", ["请通过原安装渠道重新安装 gd-cli。"]);
|
|
68
|
+
}
|
|
60
69
|
const latest = await options.telemetry.stage("registry", async () => {
|
|
61
70
|
try {
|
|
62
71
|
const result = await fetchLatestVersion({ fetch: fetchPackage, signal });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, linkSync, lstatSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const markerFilename = ".gd-cli-installation.json";
|
|
5
|
+
export function readInstallationOwner(packageRoot) {
|
|
6
|
+
const path = join(packageRoot, markerFilename);
|
|
7
|
+
try {
|
|
8
|
+
if (!lstatSync(path).isFile())
|
|
9
|
+
return "invalid";
|
|
10
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
11
|
+
if (!isRecord(value))
|
|
12
|
+
return "invalid";
|
|
13
|
+
const keys = Object.keys(value);
|
|
14
|
+
return keys.length === 1 && keys[0] === "owner" && value.owner === "workbuddy"
|
|
15
|
+
? "workbuddy"
|
|
16
|
+
: "invalid";
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return isMissing(error) ? "user" : "invalid";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function markWorkBuddyInstallation(packageRoot) {
|
|
23
|
+
const packageMetadata = parsePackageMetadata(packageRoot);
|
|
24
|
+
if (packageMetadata.name !== "@gaoding/cli") {
|
|
25
|
+
throw new Error("无法确认当前安装的 package。");
|
|
26
|
+
}
|
|
27
|
+
const markerPath = join(packageRoot, markerFilename);
|
|
28
|
+
const current = readInstallationOwner(packageRoot);
|
|
29
|
+
if (current === "workbuddy")
|
|
30
|
+
return;
|
|
31
|
+
if (current === "invalid")
|
|
32
|
+
throw new Error("无法确认当前安装的托管信息。");
|
|
33
|
+
const temporaryPath = join(packageRoot, `.${markerFilename}.${randomUUID()}.tmp`);
|
|
34
|
+
let descriptor;
|
|
35
|
+
try {
|
|
36
|
+
descriptor = openSync(temporaryPath, "wx", 0o600);
|
|
37
|
+
writeFileSync(descriptor, `${JSON.stringify({ owner: "workbuddy" }, null, 2)}\n`, "utf8");
|
|
38
|
+
closeSync(descriptor);
|
|
39
|
+
descriptor = undefined;
|
|
40
|
+
linkSync(temporaryPath, markerPath);
|
|
41
|
+
unlinkSync(temporaryPath);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (descriptor !== undefined) {
|
|
45
|
+
try {
|
|
46
|
+
closeSync(descriptor);
|
|
47
|
+
}
|
|
48
|
+
catch { /* best effort */ }
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
unlinkSync(temporaryPath);
|
|
52
|
+
}
|
|
53
|
+
catch { /* best effort */ }
|
|
54
|
+
if (isExists(error)) {
|
|
55
|
+
if (readInstallationOwner(packageRoot) === "workbuddy")
|
|
56
|
+
return;
|
|
57
|
+
throw new Error("无法确认当前安装的托管信息。");
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function parsePackageMetadata(packageRoot) {
|
|
63
|
+
try {
|
|
64
|
+
const value = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
65
|
+
if (!isRecord(value))
|
|
66
|
+
throw new Error("invalid package metadata");
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new Error("无法确认当前安装的 package。");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function isRecord(value) {
|
|
74
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
function isMissing(error) {
|
|
77
|
+
return isRecord(error) && error.code === "ENOENT";
|
|
78
|
+
}
|
|
79
|
+
function isExists(error) {
|
|
80
|
+
return isRecord(error) && error.code === "EEXIST";
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaoding/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Gaoding command-line interface for agents and people.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"vitest": "4.1.10"
|
|
56
56
|
},
|
|
57
57
|
"gaodingRelease": {
|
|
58
|
-
"sourceCommit": "
|
|
59
|
-
"pipelineId": "
|
|
60
|
-
"pipelineUrl": "https://git.intra.gaoding.com/gtt/gaoding-cli/-/pipelines/
|
|
58
|
+
"sourceCommit": "24e9f75334bea21d5d5fed4797a346c81b37c0b1",
|
|
59
|
+
"pipelineId": "173862",
|
|
60
|
+
"pipelineUrl": "https://git.intra.gaoding.com/gtt/gaoding-cli/-/pipelines/173862"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -10,4 +10,6 @@ gd-cli update
|
|
|
10
10
|
|
|
11
11
|
正常使用 CLI 时,版本可用性最多每小时检查一次;存在新版本时,同一本地自然日最多在 stderr 提醒一次。看到提示后执行 `gd-cli update`。自动检查不会更新 CLI,也不会检查或修复 Agent Skill;`CI=true` 时跳过。
|
|
12
12
|
|
|
13
|
+
如果 `gd-cli update` 提示当前安装由 WorkBuddy 连接器管理,请在 WorkBuddy 中更新「稿定」连接器。不要尝试绕过该安装渠道单独升级 CLI 或同步 Agent Skill;连接器会一起管理并验收匹配版本的 CLI 与 Skill。
|
|
14
|
+
|
|
13
15
|
命令不接收参数或 `--json`。不支持自动更新的安装来源会给出明确的 npm、pnpm 全局安装命令。若 CLI 已更新但 Agent Skill 验收失败,保留已更新版本,按 stderr 的下一步修复后重新执行 `gd-cli update`。
|