pdlab-cli 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +101 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -149,6 +149,22 @@ API 地址的解析顺序:`--api-url` 参数 > `PDLAB_API_URL` 环境变量 >
149
149
  作者也才能 `post get` 到自己那篇还没过审的稿子。没登录则匿名读公开内容,照常可用。
150
150
  在 CI 里可以只给环境变量,不落配置文件。
151
151
 
152
+ ## 版本更新提示
153
+
154
+ `pdlab` 每次运行都会顺手看一眼本地缓存的版本信息,如果有新版本会在命令结束时打印一行提示到
155
+ **stderr**(不影响 stdout 的 JSON 输出,脚本/agent 该怎么解析还怎么解析):
156
+
157
+ ```
158
+ ※ pdlab-cli 有新版本可用:0.3.0 → 0.4.0
159
+ 运行 npm install -g pdlab-cli@latest 升级(可用 PDLAB_NO_UPDATE_CHECK=1 关闭此提示)
160
+ ```
161
+
162
+ 这个检查本身不会拖慢任何命令:真正去问 npm registry 的动作放在一个独立的后台进程里做,
163
+ 每 24 小时最多问一次,前台命令只读本地缓存(一次磁盘读,微秒级)。离线或连不上 registry
164
+ 时静默跳过,不影响命令本身的执行结果。
165
+
166
+ 不想看到这个提示:设置环境变量 `PDLAB_NO_UPDATE_CHECK=1`(CI/自动化脚本建议直接设置)。
167
+
152
168
  ## 凭证与安全
153
169
 
154
170
  - **凭证怎么来的**:走设备码授权——CLI 生成一次性 code,你在浏览器里确认后服务端才签发。
package/dist/index.js CHANGED
@@ -24,8 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_fs2 = require("fs");
28
- var import_path2 = require("path");
27
+ var import_fs3 = require("fs");
28
+ var import_path3 = require("path");
29
29
  var import_commander2 = require("commander");
30
30
 
31
31
  // src/commands/login.ts
@@ -975,6 +975,9 @@ function exitCodeTable() {
975
975
  }
976
976
  return table;
977
977
  }
978
+ function isInternalCommand(command) {
979
+ return command.name().startsWith("__");
980
+ }
978
981
  function serialize(command) {
979
982
  const meta = getMeta(command);
980
983
  return {
@@ -984,7 +987,7 @@ function serialize(command) {
984
987
  arguments: command.registeredArguments.map((a) => ({ name: a.name(), required: a.required })),
985
988
  options: command.options.map((o) => ({ flags: o.flags, description: o.description, required: o.mandatory })),
986
989
  examples: meta.examples ?? [],
987
- subcommands: command.commands.map(serialize)
990
+ subcommands: command.commands.filter((c) => !isInternalCommand(c)).map(serialize)
988
991
  };
989
992
  }
990
993
  function registerSchemaCommand(program2) {
@@ -993,7 +996,7 @@ function registerSchemaCommand(program2) {
993
996
  JSON.stringify(
994
997
  {
995
998
  exitCodes: exitCodeTable(),
996
- commands: program2.commands.map(serialize)
999
+ commands: program2.commands.filter((c) => !isInternalCommand(c)).map(serialize)
997
1000
  },
998
1001
  null,
999
1002
  2
@@ -1002,8 +1005,96 @@ function registerSchemaCommand(program2) {
1002
1005
  });
1003
1006
  }
1004
1007
 
1008
+ // src/update-check.ts
1009
+ var import_fs2 = require("fs");
1010
+ var import_child_process = require("child_process");
1011
+ var import_path2 = require("path");
1012
+ var CACHE_FILE = (0, import_path2.join)(CONFIG_DIR, "update-check.json");
1013
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1014
+ var FETCH_TIMEOUT_MS = 3e3;
1015
+ function readUpdateCache() {
1016
+ if (!(0, import_fs2.existsSync)(CACHE_FILE)) return { lastCheckedAt: 0 };
1017
+ try {
1018
+ return JSON.parse((0, import_fs2.readFileSync)(CACHE_FILE, "utf8"));
1019
+ } catch {
1020
+ return { lastCheckedAt: 0 };
1021
+ }
1022
+ }
1023
+ function writeUpdateCache(cache) {
1024
+ if (!(0, import_fs2.existsSync)(CONFIG_DIR)) (0, import_fs2.mkdirSync)(CONFIG_DIR, { recursive: true, mode: 448 });
1025
+ (0, import_fs2.writeFileSync)(CACHE_FILE, JSON.stringify(cache, null, 2));
1026
+ }
1027
+ function parseVersion(version) {
1028
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
1029
+ if (!match) return null;
1030
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
1031
+ }
1032
+ function isNewerVersion(candidate, current) {
1033
+ const a = parseVersion(candidate);
1034
+ const b = parseVersion(current);
1035
+ if (!a || !b) return false;
1036
+ for (let i = 0; i < 3; i++) {
1037
+ if (a[i] !== b[i]) return a[i] > b[i];
1038
+ }
1039
+ return false;
1040
+ }
1041
+ async function fetchLatestVersion(packageName) {
1042
+ const controller = new AbortController();
1043
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
1044
+ try {
1045
+ const res = await fetch(`https://registry.npmjs.org/-/package/${packageName}/dist-tags`, {
1046
+ signal: controller.signal
1047
+ });
1048
+ if (!res.ok) return null;
1049
+ const data = await res.json();
1050
+ return data.latest ?? null;
1051
+ } catch {
1052
+ return null;
1053
+ } finally {
1054
+ clearTimeout(timer);
1055
+ }
1056
+ }
1057
+ async function refreshUpdateCache(packageName) {
1058
+ const latestVersion = await fetchLatestVersion(packageName);
1059
+ const previous = readUpdateCache();
1060
+ writeUpdateCache({
1061
+ lastCheckedAt: Date.now(),
1062
+ latestVersion: latestVersion ?? previous.latestVersion
1063
+ });
1064
+ }
1065
+ function spawnBackgroundRefresh(packageName) {
1066
+ try {
1067
+ const entry = process.argv[1];
1068
+ if (!entry) return;
1069
+ const child = (0, import_child_process.spawn)(process.execPath, [entry, "__update-check-worker", packageName], {
1070
+ detached: true,
1071
+ stdio: "ignore"
1072
+ });
1073
+ child.unref();
1074
+ } catch {
1075
+ }
1076
+ }
1077
+ function notifyIfUpdateAvailable(currentVersion, packageName) {
1078
+ if (process.env.PDLAB_NO_UPDATE_CHECK) return;
1079
+ try {
1080
+ const cache = readUpdateCache();
1081
+ if (cache.latestVersion && isNewerVersion(cache.latestVersion, currentVersion)) {
1082
+ console.error(
1083
+ `
1084
+ \u203B ${packageName} \u6709\u65B0\u7248\u672C\u53EF\u7528\uFF1A${currentVersion} \u2192 ${cache.latestVersion}
1085
+ \u8FD0\u884C npm install -g ${packageName}@latest \u5347\u7EA7\uFF08\u53EF\u7528 PDLAB_NO_UPDATE_CHECK=1 \u5173\u95ED\u6B64\u63D0\u793A\uFF09
1086
+ `
1087
+ );
1088
+ }
1089
+ if (Date.now() - cache.lastCheckedAt > CHECK_INTERVAL_MS) {
1090
+ spawnBackgroundRefresh(packageName);
1091
+ }
1092
+ } catch {
1093
+ }
1094
+ }
1095
+
1005
1096
  // src/index.ts
1006
- var pkg = JSON.parse((0, import_fs2.readFileSync)((0, import_path2.join)(__dirname, "../package.json"), "utf8"));
1097
+ var pkg = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)(__dirname, "../package.json"), "utf8"));
1007
1098
  var program = new import_commander2.Command();
1008
1099
  program.name("pdlab").description("1836 \u5F00\u6E90\u5171\u5EFA\u5E73\u53F0\u547D\u4EE4\u884C\u5DE5\u5177\u2014\u2014\u662F\u73B0\u6709 REST API \u7684\u8584\u5C01\u88C5\u3002\u7ED9 agent/\u811A\u672C\u7528\uFF1Apdlab schema \u4E00\u6B21\u6027\u5217\u51FA\u5168\u90E8\u547D\u4EE4\u3001\u53C2\u6570\u4E0E\u9000\u51FA\u7801\u542B\u4E49\u3002").version(pkg.version);
1009
1100
  program.exitOverride();
@@ -1018,6 +1109,11 @@ registerProjectCommand(program);
1018
1109
  registerPostCommand(program);
1019
1110
  registerTaxonomyCommand(program);
1020
1111
  registerSchemaCommand(program);
1112
+ program.command("__update-check-worker <packageName>", { hidden: true }).action(async (packageName) => {
1113
+ await refreshUpdateCache(packageName);
1114
+ process.exit(0);
1115
+ });
1116
+ notifyIfUpdateAvailable(pkg.version, "pdlab-cli");
1021
1117
  program.parseAsync(process.argv).catch((error) => {
1022
1118
  if (error instanceof import_commander2.CommanderError && error.exitCode === 0) {
1023
1119
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdlab-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "1836 开源共建平台(problem-driven-lab)的命令行工具:项目、内容的增删改查,浏览器授权登录",
6
6
  "keywords": [