pingcode-cli-unofficial 1.8.3 → 1.9.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.
@@ -978,7 +978,7 @@ var require_command = __commonJS({
978
978
  "use strict";
979
979
  var EventEmitter = __require("events").EventEmitter;
980
980
  var childProcess = __require("child_process");
981
- var path8 = __require("path");
981
+ var path9 = __require("path");
982
982
  var fs = __require("fs");
983
983
  var process2 = __require("process");
984
984
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -1911,9 +1911,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1911
1911
  let launchWithNode = false;
1912
1912
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1913
1913
  function findFile(baseDir, baseName) {
1914
- const localBin = path8.resolve(baseDir, baseName);
1914
+ const localBin = path9.resolve(baseDir, baseName);
1915
1915
  if (fs.existsSync(localBin)) return localBin;
1916
- if (sourceExt.includes(path8.extname(baseName))) return void 0;
1916
+ if (sourceExt.includes(path9.extname(baseName))) return void 0;
1917
1917
  const foundExt = sourceExt.find(
1918
1918
  (ext) => fs.existsSync(`${localBin}${ext}`)
1919
1919
  );
@@ -1931,17 +1931,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
1931
1931
  } catch (err) {
1932
1932
  resolvedScriptPath = this._scriptPath;
1933
1933
  }
1934
- executableDir = path8.resolve(
1935
- path8.dirname(resolvedScriptPath),
1934
+ executableDir = path9.resolve(
1935
+ path9.dirname(resolvedScriptPath),
1936
1936
  executableDir
1937
1937
  );
1938
1938
  }
1939
1939
  if (executableDir) {
1940
1940
  let localFile = findFile(executableDir, executableFile);
1941
1941
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1942
- const legacyName = path8.basename(
1942
+ const legacyName = path9.basename(
1943
1943
  this._scriptPath,
1944
- path8.extname(this._scriptPath)
1944
+ path9.extname(this._scriptPath)
1945
1945
  );
1946
1946
  if (legacyName !== this._name) {
1947
1947
  localFile = findFile(
@@ -1952,7 +1952,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1952
1952
  }
1953
1953
  executableFile = localFile || executableFile;
1954
1954
  }
1955
- launchWithNode = sourceExt.includes(path8.extname(executableFile));
1955
+ launchWithNode = sourceExt.includes(path9.extname(executableFile));
1956
1956
  let proc;
1957
1957
  if (process2.platform !== "win32") {
1958
1958
  if (launchWithNode) {
@@ -2792,7 +2792,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2792
2792
  * @return {Command}
2793
2793
  */
2794
2794
  nameFromFilename(filename) {
2795
- this._name = path8.basename(filename, path8.extname(filename));
2795
+ this._name = path9.basename(filename, path9.extname(filename));
2796
2796
  return this;
2797
2797
  }
2798
2798
  /**
@@ -2806,9 +2806,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2806
2806
  * @param {string} [path]
2807
2807
  * @return {(string|null|Command)}
2808
2808
  */
2809
- executableDir(path9) {
2810
- if (path9 === void 0) return this._executableDir;
2811
- this._executableDir = path9;
2809
+ executableDir(path10) {
2810
+ if (path10 === void 0) return this._executableDir;
2811
+ this._executableDir = path10;
2812
2812
  return this;
2813
2813
  }
2814
2814
  /**
@@ -3425,7 +3425,7 @@ var VERSION;
3425
3425
  var init_version = __esm({
3426
3426
  "src/version.ts"() {
3427
3427
  "use strict";
3428
- VERSION = "1.8.3";
3428
+ VERSION = "1.9.0";
3429
3429
  }
3430
3430
  });
3431
3431
 
@@ -4475,9 +4475,9 @@ var init_catalog = __esm({
4475
4475
  function isMutating(method) {
4476
4476
  return MUTATING.has(method.toUpperCase());
4477
4477
  }
4478
- function buildUrl(apiBase, path8, query) {
4478
+ function buildUrl(apiBase, path9, query) {
4479
4479
  const base = apiBase.replace(/\/+$/, "");
4480
- const relative = path8.startsWith("/") ? path8 : `/${path8}`;
4480
+ const relative = path9.startsWith("/") ? path9 : `/${path9}`;
4481
4481
  const url = new URL(`${base}${relative}`);
4482
4482
  if (query !== void 0) {
4483
4483
  for (const [key, value] of Object.entries(query)) {
@@ -5301,12 +5301,12 @@ function normalizeEnvelope(raw, requested) {
5301
5301
  values
5302
5302
  };
5303
5303
  }
5304
- async function fetchPage(ctx, path8, query = {}, page = {}) {
5304
+ async function fetchPage(ctx, path9, query = {}, page = {}) {
5305
5305
  const pageIndex = validatePageIndex(page.pageIndex ?? 0);
5306
5306
  const pageSize = validatePageSize(page.pageSize ?? DEFAULT_PAGE_SIZE);
5307
5307
  const raw = await request(ctx, {
5308
5308
  method: "GET",
5309
- path: path8,
5309
+ path: path9,
5310
5310
  query: { ...query, page_index: pageIndex, page_size: pageSize }
5311
5311
  });
5312
5312
  return normalizeEnvelope(raw, { pageIndex, pageSize });
@@ -5339,8 +5339,8 @@ async function* walkPages(ctx, fetchOne, options, label) {
5339
5339
  pageIndex += 1;
5340
5340
  }
5341
5341
  }
5342
- async function* paginate(ctx, path8, query = {}, options = {}) {
5343
- yield* walkPages(ctx, (page) => fetchPage(ctx, path8, query, page), options, "GET-list");
5342
+ async function* paginate(ctx, path9, query = {}, options = {}) {
5343
+ yield* walkPages(ctx, (page) => fetchPage(ctx, path9, query, page), options, "GET-list");
5344
5344
  }
5345
5345
  function asReadContext(ctx) {
5346
5346
  return ctx.dryRun ? { ...ctx, dryRun: false } : ctx;
@@ -5356,20 +5356,20 @@ function buildSearchBody(payload, page) {
5356
5356
  body.page_size = page.pageSize;
5357
5357
  return { mode: "query", payload: body };
5358
5358
  }
5359
- async function fetchSearchPage(ctx, path8, payload = {}, page = {}) {
5359
+ async function fetchSearchPage(ctx, path9, payload = {}, page = {}) {
5360
5360
  const pageIndex = validatePageIndex(page.pageIndex ?? 0);
5361
5361
  const pageSize = validatePageSize(page.pageSize ?? DEFAULT_PAGE_SIZE);
5362
5362
  const raw = await request(asReadContext(ctx), {
5363
5363
  method: "POST",
5364
- path: path8,
5364
+ path: path9,
5365
5365
  body: buildSearchBody(payload, { pageIndex, pageSize })
5366
5366
  });
5367
5367
  return normalizeEnvelope(raw, { pageIndex, pageSize });
5368
5368
  }
5369
- async function* searchPaginate(ctx, path8, payload = {}, options = {}) {
5369
+ async function* searchPaginate(ctx, path9, payload = {}, options = {}) {
5370
5370
  yield* walkPages(
5371
5371
  ctx,
5372
- (page) => fetchSearchPage(ctx, path8, payload, page),
5372
+ (page) => fetchSearchPage(ctx, path9, payload, page),
5373
5373
  options,
5374
5374
  "search"
5375
5375
  );
@@ -7087,9 +7087,9 @@ async function loadSuiteTree(ctx, path_, query) {
7087
7087
  }
7088
7088
  const candidates = [];
7089
7089
  for (const [id, node] of nodes) {
7090
- const path8 = suitePath(nodes, id);
7091
- const candidate = { id, name: node.name, path: path8 };
7092
- if (path8 !== void 0 && path8 !== node.name) candidate.aliases = [path8];
7090
+ const path9 = suitePath(nodes, id);
7091
+ const candidate = { id, name: node.name, path: path9 };
7092
+ if (path9 !== void 0 && path9 !== node.name) candidate.aliases = [path9];
7093
7093
  candidates.push(candidate);
7094
7094
  }
7095
7095
  return candidates;
@@ -7861,29 +7861,29 @@ endpoints; a wiki page and a code branch are the two with no recovery path at al
7861
7861
  }
7862
7862
  }
7863
7863
  async function runVerb(method, pathArgument, flags, self) {
7864
- const { candidates, path: path8 } = resolveEntry2(method, pathArgument);
7864
+ const { candidates, path: path9 } = resolveEntry2(method, pathArgument);
7865
7865
  const { ctx } = contextFor(self);
7866
7866
  refuseUserTokenEndpoint(ctx, candidates);
7867
7867
  const query = parseQueryFlags(flags.query);
7868
- refuseUnconfirmedDelete(method, path8, query, flags);
7868
+ refuseUnconfirmedDelete(method, path9, query, flags);
7869
7869
  const body = await readBodyFlags(flags);
7870
7870
  const entry = chooseEntry(candidates, query, body);
7871
7871
  const paging = readPagingFor(entry, flags);
7872
7872
  try {
7873
- await send(ctx, entry, path8, query, body, paging);
7873
+ await send(ctx, entry, path9, query, body, paging);
7874
7874
  } catch (error) {
7875
7875
  if (error instanceof PermissionError) errLine(paint.dim(declaredScopeLine(entry)));
7876
7876
  throw error;
7877
7877
  }
7878
7878
  }
7879
- async function send(ctx, entry, path8, query, body, paging) {
7879
+ async function send(ctx, entry, path9, query, body, paging) {
7880
7880
  if (entry.paged === "search") {
7881
- await sendSearch(ctx, path8, body, paging);
7881
+ await sendSearch(ctx, path9, body, paging);
7882
7882
  return;
7883
7883
  }
7884
7884
  if (entry.paged === "query" && paging.all) {
7885
7885
  const values = await collect(
7886
- paginate(ctx, path8, query, {
7886
+ paginate(ctx, path9, query, {
7887
7887
  pageSize: paging.pageSize,
7888
7888
  startPage: paging.pageIndex,
7889
7889
  limit: paging.limit
@@ -7894,17 +7894,17 @@ async function send(ctx, entry, path8, query, body, paging) {
7894
7894
  }
7895
7895
  const raw = await request(ctx, {
7896
7896
  method: entry.method,
7897
- path: path8,
7897
+ path: path9,
7898
7898
  query: paging.requested ? { ...query, page_index: paging.pageIndex, page_size: paging.pageSize } : query,
7899
7899
  ...body === void 0 ? {} : { body }
7900
7900
  });
7901
7901
  printResponse(raw);
7902
7902
  }
7903
- async function sendSearch(ctx, path8, body, paging) {
7903
+ async function sendSearch(ctx, path9, body, paging) {
7904
7904
  const payload = searchPayloadOf(body);
7905
7905
  if (paging.all) {
7906
7906
  const values = await collect(
7907
- searchPaginate(ctx, path8, payload, {
7907
+ searchPaginate(ctx, path9, payload, {
7908
7908
  pageSize: paging.pageSize,
7909
7909
  startPage: paging.pageIndex,
7910
7910
  limit: paging.limit
@@ -7913,7 +7913,7 @@ async function sendSearch(ctx, path8, body, paging) {
7913
7913
  printJson({ values, count: values.length, all: true });
7914
7914
  return;
7915
7915
  }
7916
- const page = await fetchSearchPage(ctx, path8, payload, {
7916
+ const page = await fetchSearchPage(ctx, path9, payload, {
7917
7917
  pageIndex: paging.pageIndex,
7918
7918
  pageSize: paging.pageSize
7919
7919
  });
@@ -7947,8 +7947,8 @@ function resolveEntry2(method, pathArgument) {
7947
7947
  hint: `drop everything from "?" and pass the parameters as --query key=value (repeatable), so they are serialised the same way every other command does it`
7948
7948
  });
7949
7949
  }
7950
- const path8 = normalizePath(raw);
7951
- const unfilled = unfilledPathParams(path8);
7950
+ const path9 = normalizePath(raw);
7951
+ const unfilled = unfilledPathParams(path9);
7952
7952
  if (unfilled.length > 0) {
7953
7953
  throw new UsageError(
7954
7954
  `the path still contains the placeholder${unfilled.length > 1 ? "s" : ""} ${unfilled.map((name) => `{${name}}`).join(", ")}`,
@@ -7957,32 +7957,32 @@ function resolveEntry2(method, pathArgument) {
7957
7957
  }
7958
7958
  );
7959
7959
  }
7960
- const onPath = matchPath(path8);
7961
- if (onPath.length === 0) throw unknownPath(path8);
7960
+ const onPath = matchPath(path9);
7961
+ if (onPath.length === 0) throw unknownPath(path9);
7962
7962
  const candidates = onPath.filter((candidate) => candidate.method === method);
7963
7963
  if (candidates.length === 0) {
7964
- const supported = methodsFor(path8);
7965
- throw new UsageError(`${method} ${path8} is not a documented endpoint`, {
7964
+ const supported = methodsFor(path9);
7965
+ throw new UsageError(`${method} ${path9} is not a documented endpoint`, {
7966
7966
  hint: `that path supports ${supported.join(", ")} \u2014 this API is missing several symmetric operations on purpose (there is no project delete, no sprint delete, and nothing in \u4EA7\u54C1\u7BA1\u7406 can be deleted at all)`
7967
7967
  });
7968
7968
  }
7969
- return { candidates, path: path8 };
7969
+ return { candidates, path: path9 };
7970
7970
  }
7971
- function unknownPath(path8) {
7972
- if (segmentsOf2(path8).includes("authorize")) {
7971
+ function unknownPath(path9) {
7972
+ if (segmentsOf2(path9).includes("authorize")) {
7973
7973
  return new UsageError(
7974
- `${path8} is the browser authorization page of the OAuth2 authorization-code flow, not a REST endpoint: it renders HTML for a human to click and redirects, it returns no JSON, and it is not under /v1`,
7974
+ `${path9} is the browser authorization page of the OAuth2 authorization-code flow, not a REST endpoint: it renders HTML for a human to click and redirects, it returns no JSON, and it is not under /v1`,
7975
7975
  { hint: AUTHORIZE_HINT }
7976
7976
  );
7977
7977
  }
7978
- const suggestions = nearestPaths(path8);
7979
- const hint = suggestions.length === 0 ? `no documented path has ${segmentsOf2(path8).length} segments like this one \u2014 search the catalog with \`pingcode api list --search <text>\`` : `did you mean ${suggestions.join(" , ")} ? \u2014 or search with \`pingcode api list --search <text>\``;
7980
- return new UsageError(`${path8} is not in the endpoint catalog (459 documented v1 endpoints)`, {
7978
+ const suggestions = nearestPaths(path9);
7979
+ const hint = suggestions.length === 0 ? `no documented path has ${segmentsOf2(path9).length} segments like this one \u2014 search the catalog with \`pingcode api list --search <text>\`` : `did you mean ${suggestions.join(" , ")} ? \u2014 or search with \`pingcode api list --search <text>\``;
7980
+ return new UsageError(`${path9} is not in the endpoint catalog (459 documented v1 endpoints)`, {
7981
7981
  hint
7982
7982
  });
7983
7983
  }
7984
- function nearestPaths(path8) {
7985
- const actual = segmentsOf2(path8);
7984
+ function nearestPaths(path9) {
7985
+ const actual = segmentsOf2(path9);
7986
7986
  const scored = /* @__PURE__ */ new Map();
7987
7987
  for (const entry of CATALOG2) {
7988
7988
  const template = segmentsOf2(entry.path);
@@ -8011,8 +8011,8 @@ function editDistance(a, b) {
8011
8011
  }
8012
8012
  return previous[b.length] ?? Math.max(a.length, b.length);
8013
8013
  }
8014
- function segmentsOf2(path8) {
8015
- return path8.split("/").filter((segment) => segment !== "");
8014
+ function segmentsOf2(path9) {
8015
+ return path9.split("/").filter((segment) => segment !== "");
8016
8016
  }
8017
8017
  function isPlaceholder2(segment) {
8018
8018
  return segment.startsWith("{") && segment.endsWith("}");
@@ -8029,9 +8029,9 @@ function refuseUserTokenEndpoint(ctx, candidates) {
8029
8029
  }
8030
8030
  );
8031
8031
  }
8032
- function refuseUnconfirmedDelete(method, path8, query, flags) {
8032
+ function refuseUnconfirmedDelete(method, path9, query, flags) {
8033
8033
  if (method !== "DELETE" || flags.yes === true) return;
8034
- throw new UsageError(`refusing to send DELETE ${path8}${displayQuery(query)} without --yes`, {
8034
+ throw new UsageError(`refusing to send DELETE ${path9}${displayQuery(query)} without --yes`, {
8035
8035
  hint: "re-run with --yes to send it, or with --yes --dry-run to print the full request plan without sending anything"
8036
8036
  });
8037
8037
  }
@@ -8193,22 +8193,22 @@ function registerDescribe(api) {
8193
8193
  "\nAn id is exact; a method + path is matched through the same wildcard rules the\nexecutor uses. GET /v1/auth/token is the one path three entries share (the three\ngrants), so address those by id.\n--json prints the catalog entry verbatim.\n"
8194
8194
  );
8195
8195
  addGlobalOptions(command, { hidden: true }).action(
8196
- (idOrMethod, path8, _flags, self) => {
8196
+ (idOrMethod, path9, _flags, self) => {
8197
8197
  const { ctx } = contextFor(self);
8198
- const entry = lookupForDescribe(idOrMethod, path8);
8198
+ const entry = lookupForDescribe(idOrMethod, path9);
8199
8199
  printEntry(entry, modeOf(ctx));
8200
8200
  }
8201
8201
  );
8202
8202
  }
8203
- function lookupForDescribe(idOrMethod, path8) {
8204
- if (path8 !== void 0) {
8203
+ function lookupForDescribe(idOrMethod, path9) {
8204
+ if (path9 !== void 0) {
8205
8205
  const method = idOrMethod.trim().toUpperCase();
8206
8206
  if (!METHODS.includes(method)) {
8207
8207
  throw new UsageError(`"${idOrMethod}" is not an HTTP method`, {
8208
8208
  hint: `the methods are ${METHODS.join(", ")} \u2014 or pass a single catalog id instead`
8209
8209
  });
8210
8210
  }
8211
- const normalized = normalizePath(path8.trim());
8211
+ const normalized = normalizePath(path9.trim());
8212
8212
  const matches = matchPath(normalized).filter((entry2) => entry2.method === method);
8213
8213
  const entry = matches[0];
8214
8214
  if (entry === void 0) {
@@ -8385,26 +8385,26 @@ function parseRefList(raw) {
8385
8385
  function parseProperties(raw) {
8386
8386
  return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : void 0;
8387
8387
  }
8388
- async function fetchPageOf(ctx, path8, query, page, parse) {
8389
- const raw = await fetchPage(ctx, path8, query, page);
8388
+ async function fetchPageOf(ctx, path9, query, page, parse) {
8389
+ const raw = await fetchPage(ctx, path9, query, page);
8390
8390
  return { ...raw, values: raw.values.map(parse) };
8391
8391
  }
8392
- async function* iterateOf(ctx, path8, query, options, parse) {
8393
- for await (const raw of paginate(ctx, path8, query, options)) {
8392
+ async function* iterateOf(ctx, path9, query, options, parse) {
8393
+ for await (const raw of paginate(ctx, path9, query, options)) {
8394
8394
  yield parse(raw);
8395
8395
  }
8396
8396
  }
8397
- async function listAllOf(ctx, path8, query, parse, options = {}) {
8397
+ async function listAllOf(ctx, path9, query, parse, options = {}) {
8398
8398
  return await collect(
8399
- iterateOf(ctx, path8, query, { pageSize: 100, limit: 1e3, ...options }, parse)
8399
+ iterateOf(ctx, path9, query, { pageSize: 100, limit: 1e3, ...options }, parse)
8400
8400
  );
8401
8401
  }
8402
- async function fetchSearchPageOf(ctx, path8, payload, page, parse) {
8403
- const raw = await fetchSearchPage(ctx, path8, payload, page);
8402
+ async function fetchSearchPageOf(ctx, path9, payload, page, parse) {
8403
+ const raw = await fetchSearchPage(ctx, path9, payload, page);
8404
8404
  return { ...raw, values: raw.values.map(parse) };
8405
8405
  }
8406
- async function* iterateSearchOf(ctx, path8, payload, options, parse) {
8407
- for await (const raw of searchPaginate(ctx, path8, payload, options)) {
8406
+ async function* iterateSearchOf(ctx, path9, payload, options, parse) {
8407
+ for await (const raw of searchPaginate(ctx, path9, payload, options)) {
8408
8408
  yield parse(raw);
8409
8409
  }
8410
8410
  }
@@ -12936,13 +12936,13 @@ var init_projectMember = __esm({
12936
12936
 
12937
12937
  // src/cli/commands/_shared/bulkEntries.ts
12938
12938
  async function readBulkEntries(source, options) {
12939
- const path8 = source.file?.trim() ?? "";
12940
- if (path8 === "") {
12939
+ const path9 = source.file?.trim() ?? "";
12940
+ if (path9 === "") {
12941
12941
  throw new UsageError("--file <path|-> is required", {
12942
12942
  hint: "pass a JSON array of entries, or - to read it from stdin. Each entry needs name, start and end; project and assignee may be shared with --project / --assignee"
12943
12943
  });
12944
12944
  }
12945
- const document = path8 === "-" ? await readJsonStdin() : parseJsonDocument(await readTextFile(path8, "--file"), `--file ${path8}`);
12945
+ const document = path9 === "-" ? await readJsonStdin() : parseJsonDocument(await readTextFile(path9, "--file"), `--file ${path9}`);
12946
12946
  const list = unwrap(document, options.wrapperKey);
12947
12947
  if (list.length === 0) {
12948
12948
  throw new UsageError("--file contained no entries", {
@@ -17614,21 +17614,9 @@ function skillTargets(env = process.env) {
17614
17614
  }
17615
17615
  ];
17616
17616
  }
17617
- function installDir(env = process.env) {
17618
- if (process.platform === "win32") {
17619
- const base = env["LOCALAPPDATA"];
17620
- const root3 = base !== void 0 && base !== "" ? base : path4.join(os2.homedir(), "AppData", "Local");
17621
- return path4.join(root3, APP_NAME);
17622
- }
17623
- const xdg = env["XDG_DATA_HOME"];
17624
- const root2 = xdg !== void 0 && xdg !== "" ? xdg : path4.join(os2.homedir(), ".local", "share");
17625
- return path4.join(root2, APP_NAME);
17626
- }
17627
- var APP_NAME;
17628
17617
  var init_paths = __esm({
17629
17618
  "src/core/paths.ts"() {
17630
17619
  "use strict";
17631
- APP_NAME = "pingcode-cli";
17632
17620
  }
17633
17621
  });
17634
17622
 
@@ -17639,18 +17627,21 @@ import {
17639
17627
  mkdirSync as mkdirSync3,
17640
17628
  readFileSync as readFileSync3,
17641
17629
  readdirSync,
17642
- renameSync as renameSync2,
17643
17630
  rmSync as rmSync3,
17644
17631
  statSync as statSync2,
17645
17632
  utimesSync,
17646
17633
  writeFileSync as writeFileSync3
17647
17634
  } from "fs";
17648
17635
  import { execFileSync } from "child_process";
17649
- import os3 from "os";
17650
17636
  import path5 from "path";
17651
- import { gunzipSync } from "zlib";
17637
+ import { fileURLToPath } from "url";
17652
17638
  function defaultExec(file, args) {
17653
- return execFileSync(file, args, { encoding: "utf8" });
17639
+ const isWindowsBatch = /\.(cmd|bat)$/i.test(file);
17640
+ return execFileSync(file, args, {
17641
+ encoding: "utf8",
17642
+ stdio: ["inherit", "pipe", "pipe"],
17643
+ ...isWindowsBatch ? { shell: true } : {}
17644
+ });
17654
17645
  }
17655
17646
  function defaultFetch(input, init) {
17656
17647
  return init === void 0 ? globalThis.fetch(input) : globalThis.fetch(input, init);
@@ -17711,180 +17702,82 @@ async function fetchLatestInfo(fetchFn = defaultFetch) {
17711
17702
  }
17712
17703
  return { version: latest, tarballUrl };
17713
17704
  }
17714
- async function downloadTarball(url, fetchFn = defaultFetch) {
17715
- let response;
17716
- try {
17717
- response = await fetchFn(url, { signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) });
17718
- } catch (error) {
17719
- throw new TransportError(`failed to download tarball: ${errorMessage2(error)}`, {
17720
- cause: error
17721
- });
17722
- }
17723
- if (!response.ok) {
17705
+ function resolveNpm() {
17706
+ const binDir = path5.dirname(process.execPath);
17707
+ const candidates = process.platform === "win32" ? [path5.join(binDir, "npm.cmd"), path5.join(binDir, "npm")] : [path5.join(binDir, "npm")];
17708
+ return candidates.find(existsSync2);
17709
+ }
17710
+ async function installViaNpm(exec, version) {
17711
+ const npm = resolveNpm();
17712
+ if (npm === void 0) {
17724
17713
  throw new TransportError(
17725
- `tarball download returned HTTP ${response.status}`,
17726
- { status: response.status }
17714
+ `cannot update: no npm binary found next to the node running this command (tried ${path5.join(path5.dirname(process.execPath), "npm")})`,
17715
+ { hint: `install Node.js, then re-run this command` }
17727
17716
  );
17728
17717
  }
17729
- if (response.body === null) {
17730
- throw new TransportError("tarball download returned empty body");
17731
- }
17732
- const chunks = [];
17733
- const reader = response.body.getReader();
17734
- const MAX_SIZE = 50 * 1024 * 1024;
17735
- let total = 0;
17736
- while (true) {
17737
- const { done, value } = await reader.read();
17738
- if (done) break;
17739
- total += value.length;
17740
- if (total > MAX_SIZE) {
17741
- throw new TransportError(`tarball exceeds maximum size of ${MAX_SIZE} bytes`);
17742
- }
17743
- chunks.push(value);
17744
- }
17745
- return Buffer.concat(chunks);
17746
- }
17747
- function extractTarball(buffer, destDir) {
17748
- const gzipped = gunzipSync(buffer);
17749
- return extractTar(gzipped, destDir);
17750
- }
17751
- function extractTar(tarBuffer, destDir) {
17752
- const resolvedDest = path5.resolve(destDir);
17753
- mkdirSync3(resolvedDest, { recursive: true });
17754
- const extracted = [];
17755
- let offset = 0;
17756
- while (offset < tarBuffer.length) {
17757
- if (offset + 512 > tarBuffer.length) break;
17758
- const headerBlock = tarBuffer.subarray(offset, offset + 512);
17759
- if (headerBlock.every((b) => b === 0)) {
17760
- break;
17761
- }
17762
- const name = readTarString(headerBlock, 0, 100);
17763
- const typeflag = tarBuffer[offset + 156];
17764
- const size = readTarNumber(headerBlock, 124, 12);
17765
- offset += 512;
17766
- const dataBlocks = Math.ceil(size / 512);
17767
- const dataOffset = offset;
17768
- if (typeflag === "5".charCodeAt(0)) {
17769
- const cleanName = stripPackagePrefix(name);
17770
- if (cleanName) {
17771
- const dest = path5.resolve(resolvedDest, cleanName);
17772
- if (dest.startsWith(resolvedDest + path5.sep) || dest === resolvedDest) {
17773
- mkdirSync3(dest, { recursive: true });
17774
- }
17775
- }
17776
- } else if (typeflag === "0".charCodeAt(0) || typeflag === 0) {
17777
- const cleanName = stripPackagePrefix(name);
17778
- if (cleanName) {
17779
- const data = tarBuffer.subarray(dataOffset, dataOffset + size);
17780
- const dest = path5.resolve(resolvedDest, cleanName);
17781
- if (dest.startsWith(resolvedDest + path5.sep)) {
17782
- mkdirSync3(path5.dirname(dest), { recursive: true });
17783
- writeFileSync3(dest, data);
17784
- extracted.push(cleanName);
17785
- }
17786
- }
17787
- }
17788
- offset += dataBlocks * 512;
17789
- }
17790
- return extracted;
17791
- }
17792
- function stripPackagePrefix(name) {
17793
- if (name.startsWith("package/")) {
17794
- return name.slice("package/".length);
17795
- }
17796
- return name;
17797
- }
17798
- function readTarString(buf, start, maxLen) {
17799
- const end = buf.indexOf(0, start);
17800
- const actualEnd = end < 0 ? start + maxLen : end;
17801
- return buf.subarray(start, actualEnd).toString("utf8").trim();
17802
- }
17803
- function readTarNumber(buf, start, maxLen) {
17804
- const str2 = readTarString(buf, start, maxLen);
17805
- if (str2.charCodeAt(0) === 128) {
17806
- return 0;
17807
- }
17808
- return parseInt(str2, 8) || 0;
17809
- }
17810
- async function atomicReplace(current, staging) {
17811
- const backup = `${current}.backup`;
17812
- if (existsSync2(backup)) {
17813
- rmSync3(backup, { recursive: true, force: true });
17814
- }
17815
- const isNested = staging.startsWith(`${current}${path5.sep}`);
17816
- const incoming = isNested ? `${current}.incoming` : staging;
17817
- if (isNested) {
17818
- if (existsSync2(incoming)) rmSync3(incoming, { recursive: true, force: true });
17819
- try {
17820
- renameSync2(staging, incoming);
17821
- } catch (error) {
17822
- throw new TransportError(
17823
- `failed to move staging aside: ${errorMessage2(error)}`,
17824
- { cause: error }
17825
- );
17826
- }
17827
- }
17828
- if (existsSync2(current)) {
17829
- try {
17830
- renameSync2(current, backup);
17831
- } catch (error) {
17832
- if (isNested && existsSync2(incoming)) {
17833
- try {
17834
- renameSync2(incoming, staging);
17835
- } catch {
17836
- }
17837
- }
17838
- throw new TransportError(
17839
- `failed to back up current install: ${errorMessage2(error)}`,
17840
- { cause: error }
17841
- );
17842
- }
17843
- }
17718
+ const spec = `${PACKAGE_NAME2}@${version}`;
17719
+ let output;
17844
17720
  try {
17845
- renameSync2(incoming, current);
17721
+ output = exec(npm, ["install", "--global", spec]);
17846
17722
  } catch (error) {
17847
- try {
17848
- if (existsSync2(backup)) renameSync2(backup, current);
17849
- } catch (restoreError) {
17850
- throw new TransportError(
17851
- `CRITICAL: update failed AND backup restore failed. Restore manually: mv "${backup}" "${current}". Original error: ${errorMessage2(error)}. Restore error: ${errorMessage2(restoreError)}`,
17852
- { cause: error }
17853
- );
17854
- }
17723
+ const detail = npmOutputOf(error);
17855
17724
  throw new TransportError(
17856
- `failed to install update (backup restored): ${errorMessage2(error)}`,
17725
+ `npm install --global ${spec} failed (exit ${exitStatusOf(error)}): ${errorMessage2(error)}`,
17857
17726
  {
17858
- hint: `if needed, restore manually: mv "${backup}" "${current}"`,
17727
+ hint: detail === "" ? `npm printed nothing; try running manually: ${npm} install --global ${spec}` : detail,
17859
17728
  cause: error
17860
17729
  }
17861
17730
  );
17862
17731
  }
17863
- }
17864
- function cleanStaging(stagingDir) {
17865
- if (existsSync2(stagingDir)) {
17866
- rmSync3(stagingDir, { recursive: true, force: true });
17732
+ const trimmed = output.trim();
17733
+ if (trimmed !== "") {
17734
+ process.stderr.write(`${output.endsWith("\n") ? output : `${output}
17735
+ `}`);
17736
+ }
17737
+ const installed = readInstalledVersion();
17738
+ if (installed !== version) {
17739
+ throw new TransportError(
17740
+ `npm install --global ${spec} exited 0 but the installed version is ${installed ?? "unreadable"}, expected ${version}`,
17741
+ { hint: `verify with: ${npm} list --global --depth=0 ${PACKAGE_NAME2}` }
17742
+ );
17867
17743
  }
17868
17744
  }
17869
- function validateStaging(stagingDir) {
17870
- const bin = path5.join(stagingDir, "dist", "bin", "pingcode.js");
17871
- return existsSync2(bin);
17745
+ function readInstalledVersion() {
17746
+ try {
17747
+ const raw = readFileSync3(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8");
17748
+ const parsed = JSON.parse(raw);
17749
+ if (typeof parsed !== "object" || parsed === null) return void 0;
17750
+ const version = parsed.version;
17751
+ return typeof version === "string" ? version : void 0;
17752
+ } catch {
17753
+ return void 0;
17754
+ }
17872
17755
  }
17873
- function writeBufferToFile(destPath, buffer) {
17874
- writeFileSync3(destPath, buffer);
17756
+ function exitStatusOf(error) {
17757
+ if (typeof error === "object" && error !== null && "status" in error) {
17758
+ const status = error.status;
17759
+ if (typeof status === "number") return String(status);
17760
+ if (typeof status === "string" && status !== "") return status;
17761
+ }
17762
+ return "unknown";
17875
17763
  }
17876
- function ensureDir(dirPath) {
17877
- mkdirSync3(dirPath, { recursive: true });
17764
+ function npmOutputOf(error) {
17765
+ if (typeof error !== "object" || error === null) return "";
17766
+ const e = error;
17767
+ return [e.stderr, e.stdout].filter((part) => part !== void 0 && part !== null).map((part) => String(part).trim()).filter((part) => part !== "").join("\n");
17878
17768
  }
17879
17769
  function removeFile(filePath) {
17880
17770
  try {
17881
- rmSync3(filePath, { recursive: true, force: true });
17771
+ rmSync3(filePath, { force: true });
17882
17772
  } catch {
17883
17773
  }
17884
17774
  }
17885
17775
  function dirExists(dirPath) {
17886
17776
  return existsSync2(dirPath);
17887
17777
  }
17778
+ function packageSkillDir() {
17779
+ return fileURLToPath(new URL("../../skills/pingcode", import.meta.url));
17780
+ }
17888
17781
  async function syncSkills(sourceDir, targets) {
17889
17782
  const written = [];
17890
17783
  const payload = [];
@@ -17913,43 +17806,6 @@ async function syncSkills(sourceDir, targets) {
17913
17806
  }
17914
17807
  return written;
17915
17808
  }
17916
- function verifyBundle(dir, exec, expectedVersion) {
17917
- const bin = path5.join(dir, "dist", "bin", "pingcode.js");
17918
- let reported;
17919
- try {
17920
- reported = exec("node", [bin, "--version"]).trim();
17921
- } catch (error) {
17922
- throw new TransportError(
17923
- `failed to verify new installation: ${errorMessage2(error)}`,
17924
- {
17925
- hint: `try running manually: node "${bin}" --version`,
17926
- cause: error
17927
- }
17928
- );
17929
- }
17930
- if (reported !== expectedVersion) {
17931
- throw new TransportError(
17932
- `installed bundle reports version ${reported}, expected ${expectedVersion}`,
17933
- { hint: `try running manually: node "${bin}" --version` }
17934
- );
17935
- }
17936
- return reported;
17937
- }
17938
- function restoreBackup(current) {
17939
- const backup = `${current}.backup`;
17940
- rmSync3(current, { recursive: true, force: true });
17941
- try {
17942
- renameSync2(backup, current);
17943
- } catch (error) {
17944
- throw new TransportError(
17945
- `failed to restore the previous install: ${errorMessage2(error)}`,
17946
- {
17947
- hint: `restore manually: mv "${backup}" "${current}"`,
17948
- cause: error
17949
- }
17950
- );
17951
- }
17952
- }
17953
17809
  function acquireLock(dir) {
17954
17810
  const lockPath = path5.join(dir, LOCK_FILENAME);
17955
17811
  try {
@@ -18023,7 +17879,6 @@ function removeHint(dir) {
18023
17879
  removeFile(path5.join(dir, HINT_FILENAME));
18024
17880
  }
18025
17881
  async function runAutoUpdate(env = process.env, fetchFn = defaultFetch, exec = defaultExec) {
18026
- const dir = installDir(env);
18027
17882
  const stateDir = configDir(env);
18028
17883
  try {
18029
17884
  touchCooldown(stateDir);
@@ -18049,44 +17904,16 @@ async function runAutoUpdate(env = process.env, fetchFn = defaultFetch, exec = d
18049
17904
  return { status: "up-to-date" };
18050
17905
  }
18051
17906
  const newVersion = info.version;
18052
- const tarballBuffer = await downloadTarball(info.tarballUrl, fetchFn);
18053
- const stagingDir = path5.join(dir, ".staging");
18054
- const tmpTarball = path5.join(os3.tmpdir(), `pingcode-cli-${newVersion}.tgz`);
17907
+ await installViaNpm(exec, newVersion);
17908
+ const skillSource = packageSkillDir();
17909
+ if (dirExists(skillSource)) {
17910
+ await syncSkills(skillSource, skillTargets(env));
17911
+ }
18055
17912
  try {
18056
- writeFileSync3(tmpTarball, tarballBuffer);
18057
- cleanStaging(stagingDir);
18058
- mkdirSync3(stagingDir, { recursive: true });
18059
- extractTarball(tarballBuffer, stagingDir);
18060
- if (!validateStaging(stagingDir)) {
18061
- cleanStaging(stagingDir);
18062
- throw new TransportError("invalid tarball: dist/bin/pingcode.js not found");
18063
- }
18064
- try {
18065
- verifyBundle(stagingDir, exec, newVersion);
18066
- } catch (error) {
18067
- cleanStaging(stagingDir);
18068
- throw error;
18069
- }
18070
- await atomicReplace(dir, stagingDir);
18071
- const skillSource = path5.join(dir, "skills", "pingcode");
18072
- if (dirExists(skillSource)) {
18073
- await syncSkills(skillSource, skillTargets(env));
18074
- }
18075
- try {
18076
- verifyBundle(dir, exec, newVersion);
18077
- } catch (error) {
18078
- restoreBackup(dir);
18079
- throw error;
18080
- }
18081
- removeFile(`${dir}.backup`);
18082
- try {
18083
- removeHint(stateDir);
18084
- } catch {
18085
- }
18086
- return { status: "updated", version: newVersion };
18087
- } finally {
18088
- removeFile(tmpTarball);
17913
+ removeHint(stateDir);
17914
+ } catch {
18089
17915
  }
17916
+ return { status: "updated", version: newVersion };
18090
17917
  } catch (error) {
18091
17918
  if (info !== void 0) {
18092
17919
  try {
@@ -18121,10 +17948,9 @@ var init_update = __esm({
18121
17948
 
18122
17949
  // src/cli/commands/selfUpdate.ts
18123
17950
  import { execFileSync as execFileSync2 } from "child_process";
18124
- import os4 from "os";
18125
17951
  import path6 from "path";
18126
17952
  function registerSelfUpdateCommands(program2) {
18127
- const cmd = program2.command("self-update").description("update the CLI to the latest npm-published version").option("--check-only", "check for updates without downloading").option("--force", "force update even if already up to date");
17953
+ const cmd = program2.command("self-update").description("update the CLI to the latest npm-published version").option("--check-only", "check for updates without installing").option("--force", "force update even if already up to date");
18128
17954
  addGlobalOptions(cmd).action(async (flags, command) => {
18129
17955
  await runSelfUpdate(flags, command);
18130
17956
  });
@@ -18173,73 +17999,45 @@ async function runSelfUpdate(flags, command) {
18173
17999
  }
18174
18000
  const oldVersion = VERSION;
18175
18001
  const newVersion = info.version;
18176
- const tarballBuffer = await downloadTarball(info.tarballUrl);
18177
- const tarballName = path6.basename(new URL(info.tarballUrl).pathname);
18178
- const install = installDir();
18179
- const stagingDir = path6.join(install, ".staging");
18180
18002
  if (ctx.dryRun) {
18181
18003
  printDryRunPlan({
18182
18004
  oldVersion,
18183
18005
  newVersion,
18184
- assetName: tarballName,
18006
+ assetName: path6.basename(new URL(info.tarballUrl).pathname),
18185
18007
  downloadUrl: info.tarballUrl,
18186
- install,
18187
- stagingDir,
18188
18008
  json: mode.json
18189
18009
  });
18190
18010
  return;
18191
18011
  }
18192
- errLine(paint.dim(`Downloading ${tarballName}...`));
18193
- errLine(paint.dim("Extracting to staging..."));
18194
- const tmpTarball = path6.join(os4.tmpdir(), `pingcode-cli-${newVersion}.tgz`);
18012
+ errLine(paint.dim(`Installing v${newVersion}...`));
18195
18013
  try {
18196
- writeBufferToFile(tmpTarball, tarballBuffer);
18197
- cleanStaging(stagingDir);
18198
- ensureDir(stagingDir);
18199
- extractTarball(tarballBuffer, stagingDir);
18200
- if (!validateStaging(stagingDir)) {
18201
- cleanStaging(stagingDir);
18202
- throw new TransportError(
18203
- "invalid tarball: dist/bin/pingcode.js not found"
18204
- );
18205
- }
18206
- errLine(paint.dim("Verifying..."));
18207
- const exec = (file, args) => String(execFileSync2(file, args, { encoding: "utf8" }));
18208
- verifyBundle(stagingDir, exec, newVersion);
18209
- errLine(paint.dim(`Installing v${newVersion}...`));
18210
- await atomicReplace(install, stagingDir);
18211
- const skillSource = path6.join(install, "skills", "pingcode");
18212
- if (dirExists(skillSource)) {
18213
- errLine(paint.dim("Syncing skills..."));
18214
- await syncSkills(skillSource, skillTargets());
18215
- }
18216
- let verified;
18217
- try {
18218
- verified = verifyBundle(install, exec, newVersion);
18219
- } catch (error) {
18220
- restoreBackup(install);
18221
- throw new TransportError(
18222
- `installed bundle failed verification: ${error instanceof Error ? error.message : String(error)}`,
18223
- {
18224
- hint: `restore manually: mv "${install}.backup" "${install}"`,
18225
- cause: error
18226
- }
18227
- );
18228
- }
18229
- removeFile(`${install}.backup`);
18230
- if (mode.json) {
18231
- printJson({
18232
- status: "updated",
18233
- previous_version: oldVersion,
18234
- new_version: verified
18235
- });
18236
- } else {
18237
- errLine(paint.green(`updated v${oldVersion} \u2192 v${verified}`));
18014
+ await installViaNpm(cliExec, newVersion);
18015
+ } catch (error) {
18016
+ if (!mode.json) {
18017
+ errLine(paint.red(`update failed: ${errorMessageOf(error)}`));
18018
+ const hint = error instanceof TransportError ? error.hint : void 0;
18019
+ if (hint !== void 0 && hint !== "") errLine(paint.dim(` ${hint}`));
18238
18020
  }
18239
- } finally {
18240
- removeFile(tmpTarball);
18021
+ throw error;
18022
+ }
18023
+ const skillSource = packageSkillDir();
18024
+ if (dirExists(skillSource)) {
18025
+ errLine(paint.dim("Syncing skills..."));
18026
+ await syncSkills(skillSource, skillTargets());
18027
+ }
18028
+ if (mode.json) {
18029
+ printJson({
18030
+ status: "updated",
18031
+ previous_version: oldVersion,
18032
+ new_version: newVersion
18033
+ });
18034
+ } else {
18035
+ errLine(paint.green(`updated v${oldVersion} \u2192 v${newVersion}`));
18241
18036
  }
18242
18037
  }
18038
+ function errorMessageOf(error) {
18039
+ return error instanceof Error ? error.message : String(error);
18040
+ }
18243
18041
  function printCheckResult(check, json) {
18244
18042
  if (json) {
18245
18043
  printJson({
@@ -18272,8 +18070,6 @@ function printDryRunPlan(plan) {
18272
18070
  target_version: plan.newVersion,
18273
18071
  asset: plan.assetName,
18274
18072
  download_url: plan.downloadUrl,
18275
- install_dir: plan.install,
18276
- staging_dir: plan.stagingDir,
18277
18073
  skill_targets: skillTargets().map((t) => t.dir)
18278
18074
  });
18279
18075
  return;
@@ -18282,10 +18078,9 @@ function printDryRunPlan(plan) {
18282
18078
  errLine(` current: v${plan.oldVersion}`);
18283
18079
  errLine(` target: v${plan.newVersion}`);
18284
18080
  errLine(` asset: ${plan.assetName}`);
18285
- errLine(` install: ${plan.install}`);
18286
- errLine(` staging: ${plan.stagingDir}`);
18287
18081
  errLine(` skills: ${skillTargets().map((t) => t.dir).join(", ")}`);
18288
18082
  }
18083
+ var cliExec;
18289
18084
  var init_selfUpdate = __esm({
18290
18085
  "src/cli/commands/selfUpdate.ts"() {
18291
18086
  "use strict";
@@ -18293,11 +18088,11 @@ var init_selfUpdate = __esm({
18293
18088
  init_update_check();
18294
18089
  init_update();
18295
18090
  init_errors();
18296
- init_update();
18297
18091
  init_common();
18298
18092
  init_output();
18299
18093
  init_globals();
18300
18094
  init_paths();
18095
+ cliExec = (file, args) => execFileSync2(file, args, { encoding: "utf8" });
18301
18096
  }
18302
18097
  });
18303
18098
 
@@ -18466,6 +18261,10 @@ var init_skill_ops = __esm({
18466
18261
  });
18467
18262
 
18468
18263
  // src/cli/commands/skill.ts
18264
+ import path8 from "path";
18265
+ function packageSkillRoot() {
18266
+ return path8.dirname(path8.dirname(packageSkillDir()));
18267
+ }
18469
18268
  function registerSkillCommands(program2) {
18470
18269
  const skill = program2.command("skill").description("manage the pingcode skill across agent targets");
18471
18270
  skill.command("list").description("show where the skill is installed").action(async (flags) => {
@@ -18528,7 +18327,7 @@ async function runInstall(flags) {
18528
18327
  const { ctx } = contextFor(command);
18529
18328
  const mode = modeOf(ctx);
18530
18329
  const targets = resolveTargets(flags);
18531
- const sourceRoot = installDir();
18330
+ const sourceRoot = packageSkillRoot();
18532
18331
  const results = installSkill(sourceRoot, targets, flags.force ?? false);
18533
18332
  renderResults(results, mode);
18534
18333
  }
@@ -18545,7 +18344,7 @@ async function runUpdate16(flags) {
18545
18344
  const { ctx } = contextFor(command);
18546
18345
  const mode = modeOf(ctx);
18547
18346
  const targets = resolveTargets(flags);
18548
- const sourceRoot = installDir();
18347
+ const sourceRoot = packageSkillRoot();
18549
18348
  const results = installSkill(sourceRoot, targets, true);
18550
18349
  renderResults(results, mode);
18551
18350
  }
@@ -18604,6 +18403,7 @@ var init_skill = __esm({
18604
18403
  "src/cli/commands/skill.ts"() {
18605
18404
  "use strict";
18606
18405
  init_paths();
18406
+ init_update();
18607
18407
  init_skill_ops();
18608
18408
  init_globals();
18609
18409
  init_output();
@@ -19081,11 +18881,11 @@ var init_libraries = __esm({
19081
18881
 
19082
18882
  // src/cli/commands/testhub/entries.ts
19083
18883
  async function readEntryFile(source, schema, hint) {
19084
- const path8 = source.file?.trim() ?? "";
19085
- if (path8 === "") {
18884
+ const path9 = source.file?.trim() ?? "";
18885
+ if (path9 === "") {
19086
18886
  throw new UsageError("--file <path|-> is required", { hint });
19087
18887
  }
19088
- const document = path8 === "-" ? await readJsonStdin() : parseJsonDocument(await readTextFile(path8, "--file"), `--file ${path8}`);
18888
+ const document = path9 === "-" ? await readJsonStdin() : parseJsonDocument(await readTextFile(path9, "--file"), `--file ${path9}`);
19089
18889
  const list = unwrap2(document, schema.wrapperKey);
19090
18890
  if (list.length === 0) {
19091
18891
  throw new UsageError("--file contained no entries", {