@kolatts/pncli 1.12.0 → 1.13.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/{chunk-SLQOSGZJ.js → chunk-BNT4SEZD.js} +81 -7
- package/dist/chunk-BNT4SEZD.js.map +1 -0
- package/dist/cli.js +354 -131
- package/dist/cli.js.map +1 -1
- package/dist/{http-5GDQYH62.js → http-BNCZJQTD.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-SLQOSGZJ.js.map +0 -1
- /package/dist/{http-5GDQYH62.js.map → http-BNCZJQTD.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -18,8 +18,9 @@ import {
|
|
|
18
18
|
setGlobalOptions,
|
|
19
19
|
setGlobalUser,
|
|
20
20
|
success,
|
|
21
|
-
warn
|
|
22
|
-
|
|
21
|
+
warn,
|
|
22
|
+
writeRawOutput
|
|
23
|
+
} from "./chunk-BNT4SEZD.js";
|
|
23
24
|
import {
|
|
24
25
|
AdoCoreClient
|
|
25
26
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -276,21 +277,30 @@ function parseBranchReportOutput(raw, branchName, opts) {
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
if (current) commits.push(current);
|
|
279
|
-
const
|
|
280
|
-
const
|
|
280
|
+
const isAbsoluteDate = (s) => /^\d{4}-\d{2}-\d{2}/.test(s);
|
|
281
|
+
const sinceDate = opts.since && isAbsoluteDate(opts.since) ? opts.since.slice(0, 10) : null;
|
|
282
|
+
const untilDate = opts.until && isAbsoluteDate(opts.until) ? opts.until.slice(0, 10) : null;
|
|
283
|
+
const filteredCommits = sinceDate || untilDate ? commits.filter((c) => {
|
|
284
|
+
const d = c.date.slice(0, 10);
|
|
285
|
+
if (sinceDate && d < sinceDate) return false;
|
|
286
|
+
if (untilDate && d > untilDate) return false;
|
|
287
|
+
return true;
|
|
288
|
+
}) : commits;
|
|
289
|
+
const totalInsertions = filteredCommits.reduce((sum, c) => sum + c.insertions, 0);
|
|
290
|
+
const totalDeletions = filteredCommits.reduce((sum, c) => sum + c.deletions, 0);
|
|
281
291
|
return {
|
|
282
292
|
branch: branchName,
|
|
283
293
|
since: opts.since ?? null,
|
|
284
294
|
until: opts.until ?? null,
|
|
285
|
-
commits,
|
|
286
|
-
weeks: buildWeeklyBreakdown(
|
|
287
|
-
byAuthor: buildByAuthor(
|
|
295
|
+
commits: filteredCommits,
|
|
296
|
+
weeks: buildWeeklyBreakdown(filteredCommits),
|
|
297
|
+
byAuthor: buildByAuthor(filteredCommits),
|
|
288
298
|
summary: {
|
|
289
|
-
commitCount:
|
|
299
|
+
commitCount: filteredCommits.length,
|
|
290
300
|
totalInsertions,
|
|
291
301
|
totalDeletions,
|
|
292
302
|
totalNetLines: totalInsertions - totalDeletions,
|
|
293
|
-
totalFilesChanged:
|
|
303
|
+
totalFilesChanged: filteredCommits.reduce((sum, c) => sum + c.filesChanged, 0)
|
|
294
304
|
}
|
|
295
305
|
};
|
|
296
306
|
}
|
|
@@ -411,34 +421,34 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
|
|
|
411
421
|
} catch {
|
|
412
422
|
baseHostname = adoBaseUrl.replace(/\/$/, "").replace(/^https?:\/\//, "").split(/[:/]/)[0].toLowerCase();
|
|
413
423
|
}
|
|
414
|
-
let
|
|
424
|
+
let path8 = remoteUrl;
|
|
415
425
|
let remoteHostname = "";
|
|
416
|
-
const sshProtoMatch =
|
|
426
|
+
const sshProtoMatch = path8.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
|
|
417
427
|
if (sshProtoMatch) {
|
|
418
428
|
remoteHostname = sshProtoMatch[1].toLowerCase();
|
|
419
|
-
|
|
429
|
+
path8 = sshProtoMatch[2];
|
|
420
430
|
} else {
|
|
421
|
-
const gitAtMatch =
|
|
431
|
+
const gitAtMatch = path8.match(/^git@([^:]+)(?::\d+)?:(.*)/);
|
|
422
432
|
if (gitAtMatch) {
|
|
423
433
|
remoteHostname = gitAtMatch[1].toLowerCase();
|
|
424
|
-
|
|
434
|
+
path8 = "/" + gitAtMatch[2];
|
|
425
435
|
} else {
|
|
426
|
-
const httpsMatch =
|
|
436
|
+
const httpsMatch = path8.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
|
|
427
437
|
if (httpsMatch) {
|
|
428
438
|
remoteHostname = httpsMatch[1].toLowerCase();
|
|
429
|
-
|
|
439
|
+
path8 = httpsMatch[2];
|
|
430
440
|
}
|
|
431
441
|
}
|
|
432
442
|
}
|
|
433
443
|
if (!remoteHostname || remoteHostname !== baseHostname) return null;
|
|
434
|
-
|
|
435
|
-
const gitIdx =
|
|
436
|
-
const sshIdx =
|
|
444
|
+
path8 = path8.replace(/\.git$/, "");
|
|
445
|
+
const gitIdx = path8.indexOf("/_git/");
|
|
446
|
+
const sshIdx = path8.indexOf("/_ssh/");
|
|
437
447
|
const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
|
|
438
448
|
if (markerIdx === -1) return null;
|
|
439
|
-
const repo = decodeURIComponent(
|
|
449
|
+
const repo = decodeURIComponent(path8.slice(markerIdx + 6));
|
|
440
450
|
if (!repo) return null;
|
|
441
|
-
const before =
|
|
451
|
+
const before = path8.slice(1, markerIdx);
|
|
442
452
|
const parts = before.split("/").filter(Boolean);
|
|
443
453
|
if (parts.length < 2) return null;
|
|
444
454
|
const project = decodeURIComponent(parts[parts.length - 1]);
|
|
@@ -742,7 +752,7 @@ function registerGitCommands(program2) {
|
|
|
742
752
|
until: opts.until
|
|
743
753
|
});
|
|
744
754
|
if (opts.csv) {
|
|
745
|
-
|
|
755
|
+
writeRawOutput(formatBranchReportCsv(report));
|
|
746
756
|
} else {
|
|
747
757
|
success(report, "git", "report", start);
|
|
748
758
|
}
|
|
@@ -783,9 +793,12 @@ function registerGitCommands(program2) {
|
|
|
783
793
|
}
|
|
784
794
|
|
|
785
795
|
// src/services/jira/commands.ts
|
|
786
|
-
import { readFileSync } from "fs";
|
|
796
|
+
import { readFileSync as readFileSync2, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
797
|
+
import path from "path";
|
|
787
798
|
|
|
788
799
|
// src/services/jira/client.ts
|
|
800
|
+
import { readFileSync } from "fs";
|
|
801
|
+
import { basename, extname } from "path";
|
|
789
802
|
var API2 = "/rest/api/2";
|
|
790
803
|
var JiraClient = class {
|
|
791
804
|
constructor(http) {
|
|
@@ -925,6 +938,36 @@ var JiraClient = class {
|
|
|
925
938
|
}
|
|
926
939
|
return result;
|
|
927
940
|
}
|
|
941
|
+
async addLabels(key, labels) {
|
|
942
|
+
await this.http.jira(`${API2}/issue/${key}`, {
|
|
943
|
+
method: "PUT",
|
|
944
|
+
body: { update: { labels: labels.map((l) => ({ add: l })) } }
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
async removeLabels(key, labels) {
|
|
948
|
+
await this.http.jira(`${API2}/issue/${key}`, {
|
|
949
|
+
method: "PUT",
|
|
950
|
+
body: { update: { labels: labels.map((l) => ({ remove: l })) } }
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
async uploadAttachment(key, filePath) {
|
|
954
|
+
const fileContent = readFileSync(filePath);
|
|
955
|
+
const fileName = basename(filePath);
|
|
956
|
+
const mimeType = guessMimeType(filePath);
|
|
957
|
+
const formData = new FormData();
|
|
958
|
+
formData.append("file", new Blob([fileContent], { type: mimeType }), fileName);
|
|
959
|
+
return this.http.jiraUpload(`${API2}/issue/${key}/attachments`, formData);
|
|
960
|
+
}
|
|
961
|
+
async listAttachments(key) {
|
|
962
|
+
const result = await this.http.jira(
|
|
963
|
+
`${API2}/issue/${key}`,
|
|
964
|
+
{ params: { fields: "attachment" } }
|
|
965
|
+
);
|
|
966
|
+
return result.fields.attachment ?? [];
|
|
967
|
+
}
|
|
968
|
+
async downloadAttachment(contentUrl) {
|
|
969
|
+
return this.http.jiraBuffer(contentUrl);
|
|
970
|
+
}
|
|
928
971
|
async assignIssue(key, accountId) {
|
|
929
972
|
await this.http.jira(`${API2}/issue/${key}/assignee`, {
|
|
930
973
|
method: "PUT",
|
|
@@ -953,6 +996,33 @@ var JiraClient = class {
|
|
|
953
996
|
});
|
|
954
997
|
}
|
|
955
998
|
};
|
|
999
|
+
function guessMimeType(filePath) {
|
|
1000
|
+
const ext = extname(filePath).toLowerCase();
|
|
1001
|
+
const map = {
|
|
1002
|
+
".jpg": "image/jpeg",
|
|
1003
|
+
".jpeg": "image/jpeg",
|
|
1004
|
+
".png": "image/png",
|
|
1005
|
+
".gif": "image/gif",
|
|
1006
|
+
".svg": "image/svg+xml",
|
|
1007
|
+
".pdf": "application/pdf",
|
|
1008
|
+
".txt": "text/plain",
|
|
1009
|
+
".log": "text/plain",
|
|
1010
|
+
".csv": "text/csv",
|
|
1011
|
+
".json": "application/json",
|
|
1012
|
+
".xml": "application/xml",
|
|
1013
|
+
".zip": "application/zip",
|
|
1014
|
+
".tar": "application/x-tar",
|
|
1015
|
+
".gz": "application/gzip",
|
|
1016
|
+
".md": "text/markdown",
|
|
1017
|
+
".html": "text/html",
|
|
1018
|
+
".htm": "text/html",
|
|
1019
|
+
".doc": "application/msword",
|
|
1020
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1021
|
+
".xls": "application/vnd.ms-excel",
|
|
1022
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
1023
|
+
};
|
|
1024
|
+
return map[ext] ?? "application/octet-stream";
|
|
1025
|
+
}
|
|
956
1026
|
function schemaToPncliType(schema) {
|
|
957
1027
|
if (!schema) return void 0;
|
|
958
1028
|
const custom = schema.custom ?? "";
|
|
@@ -1191,6 +1261,28 @@ function registerJiraCommands(program2) {
|
|
|
1191
1261
|
fail(err, "jira", "search", start);
|
|
1192
1262
|
}
|
|
1193
1263
|
});
|
|
1264
|
+
jira.command("add-label").description("Add one or more labels to a Jira issue (non-destructive)").requiredOption("--key <issue-key>", "Issue key").requiredOption("--labels <labels>", "Comma-separated labels to add").action(async (opts) => {
|
|
1265
|
+
const start = Date.now();
|
|
1266
|
+
try {
|
|
1267
|
+
const client = getClient(program2);
|
|
1268
|
+
const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1269
|
+
await client.addLabels(opts.key, labels);
|
|
1270
|
+
success({ updated: opts.key, labelsAdded: labels }, "jira", "add-label", start);
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
fail(err, "jira", "add-label", start);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
jira.command("remove-label").description("Remove one or more labels from a Jira issue (non-destructive)").requiredOption("--key <issue-key>", "Issue key").requiredOption("--labels <labels>", "Comma-separated labels to remove").action(async (opts) => {
|
|
1276
|
+
const start = Date.now();
|
|
1277
|
+
try {
|
|
1278
|
+
const client = getClient(program2);
|
|
1279
|
+
const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1280
|
+
await client.removeLabels(opts.key, labels);
|
|
1281
|
+
success({ updated: opts.key, labelsRemoved: labels }, "jira", "remove-label", start);
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
fail(err, "jira", "remove-label", start);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1194
1286
|
jira.command("assign").description("Assign a Jira issue to a user").requiredOption("--key <issue-key>", "Issue key").requiredOption("--assignee <accountId>", "Assignee account ID").action(async (opts) => {
|
|
1195
1287
|
const start = Date.now();
|
|
1196
1288
|
try {
|
|
@@ -1211,6 +1303,44 @@ function registerJiraCommands(program2) {
|
|
|
1211
1303
|
fail(err, "jira", "link-issue", start);
|
|
1212
1304
|
}
|
|
1213
1305
|
});
|
|
1306
|
+
jira.command("add-attachment").description("Upload a file as an attachment to a Jira issue").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").requiredOption("--file <path>", "Path to the file to upload").action(async (opts) => {
|
|
1307
|
+
const start = Date.now();
|
|
1308
|
+
try {
|
|
1309
|
+
if (!existsSync(opts.file)) throw new PncliError(`File not found: ${opts.file}`, 1);
|
|
1310
|
+
const client = getClient(program2);
|
|
1311
|
+
const data = await client.uploadAttachment(opts.key, opts.file);
|
|
1312
|
+
success(data, "jira", "add-attachment", start);
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
fail(err, "jira", "add-attachment", start);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
jira.command("list-attachments").description("List attachments on a Jira issue").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").action(async (opts) => {
|
|
1318
|
+
const start = Date.now();
|
|
1319
|
+
try {
|
|
1320
|
+
const client = getClient(program2);
|
|
1321
|
+
const data = await client.listAttachments(opts.key);
|
|
1322
|
+
success(data, "jira", "list-attachments", start);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
fail(err, "jira", "list-attachments", start);
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
jira.command("download-attachment").description("Download a Jira issue attachment to .pncli/ (or --dir)").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").requiredOption("--attachment-id <id>", "Attachment ID from list-attachments output").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
|
|
1328
|
+
const start = Date.now();
|
|
1329
|
+
try {
|
|
1330
|
+
const client = getClient(program2);
|
|
1331
|
+
const attachments = await client.listAttachments(opts.key);
|
|
1332
|
+
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
1333
|
+
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
1334
|
+
const outDir = opts.dir ?? path.join(process.cwd(), ".pncli");
|
|
1335
|
+
mkdirSync(outDir, { recursive: true });
|
|
1336
|
+
const outPath = path.join(outDir, path.basename(attachment.filename));
|
|
1337
|
+
const buffer = await client.downloadAttachment(attachment.content);
|
|
1338
|
+
writeFileSync(outPath, buffer);
|
|
1339
|
+
success({ saved: outPath, filename: attachment.filename, size: buffer.length }, "jira", "download-attachment", start);
|
|
1340
|
+
} catch (err) {
|
|
1341
|
+
fail(err, "jira", "download-attachment", start);
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1214
1344
|
jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").option("--project <key>", "Project key \u2014 fetches allowedValues for select fields via createmeta (requires --discover)").option("--issue-type <type>", "Filter allowedValues to a specific issue type (requires --project)").action(async (opts) => {
|
|
1215
1345
|
const start = Date.now();
|
|
1216
1346
|
try {
|
|
@@ -1244,7 +1374,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
|
|
|
1244
1374
|
const filePath = value.slice(1);
|
|
1245
1375
|
let raw;
|
|
1246
1376
|
try {
|
|
1247
|
-
raw =
|
|
1377
|
+
raw = readFileSync2(filePath, "utf8").trim();
|
|
1248
1378
|
} catch (e) {
|
|
1249
1379
|
throw new PncliError(`Cannot read file "${filePath}": ${e.message}`, 1);
|
|
1250
1380
|
}
|
|
@@ -1262,7 +1392,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
|
|
|
1262
1392
|
function parseFieldsFile(filePath, fieldMap) {
|
|
1263
1393
|
let raw;
|
|
1264
1394
|
try {
|
|
1265
|
-
raw =
|
|
1395
|
+
raw = readFileSync2(filePath, "utf8");
|
|
1266
1396
|
} catch (e) {
|
|
1267
1397
|
throw new PncliError(`Cannot read fields file "${filePath}": ${e.message}`, 1);
|
|
1268
1398
|
}
|
|
@@ -1562,9 +1692,9 @@ var ConfluenceClient = class {
|
|
|
1562
1692
|
const initialStart = opts.start ?? 0;
|
|
1563
1693
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
1564
1694
|
return this.http.confluence(`${API3}/content`, {
|
|
1565
|
-
params: { spaceKey, type: "page", expand: "version", start: initialStart + start, limit
|
|
1695
|
+
params: { spaceKey, type: "page", expand: "version", start: initialStart + start, limit }
|
|
1566
1696
|
});
|
|
1567
|
-
});
|
|
1697
|
+
}, opts.limit);
|
|
1568
1698
|
}
|
|
1569
1699
|
async getPageChildren(id) {
|
|
1570
1700
|
return this.http.confluencePaginate(async (start, limit) => {
|
|
@@ -1667,11 +1797,11 @@ var ConfluenceClient = class {
|
|
|
1667
1797
|
return this.http.confluence(`${API3}/space`, {
|
|
1668
1798
|
params: {
|
|
1669
1799
|
start: initialStart + start,
|
|
1670
|
-
limit
|
|
1800
|
+
limit,
|
|
1671
1801
|
...opts.type ? { type: opts.type } : {}
|
|
1672
1802
|
}
|
|
1673
1803
|
});
|
|
1674
|
-
});
|
|
1804
|
+
}, opts.limit);
|
|
1675
1805
|
}
|
|
1676
1806
|
async getSpace(key) {
|
|
1677
1807
|
return this.http.confluence(`${API3}/space/${key}`, {
|
|
@@ -1718,7 +1848,7 @@ function registerConfluenceCommands(program2) {
|
|
|
1718
1848
|
fail(err, "confluence", "get-page-by-title", start);
|
|
1719
1849
|
}
|
|
1720
1850
|
});
|
|
1721
|
-
confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max
|
|
1851
|
+
confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max total pages to return (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
|
|
1722
1852
|
const start = Date.now();
|
|
1723
1853
|
try {
|
|
1724
1854
|
const client = getClient3(program2);
|
|
@@ -1850,7 +1980,7 @@ function registerConfluenceCommands(program2) {
|
|
|
1850
1980
|
fail(err, "confluence", "remove-label", start);
|
|
1851
1981
|
}
|
|
1852
1982
|
});
|
|
1853
|
-
confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max
|
|
1983
|
+
confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max total spaces to return (default: all)").action(async (opts) => {
|
|
1854
1984
|
const start = Date.now();
|
|
1855
1985
|
try {
|
|
1856
1986
|
const client = getClient3(program2);
|
|
@@ -2426,23 +2556,23 @@ import { Option } from "commander";
|
|
|
2426
2556
|
|
|
2427
2557
|
// src/services/deps/parsers/index.ts
|
|
2428
2558
|
import fs from "fs";
|
|
2429
|
-
import
|
|
2559
|
+
import path5 from "path";
|
|
2430
2560
|
import { execSync as execSync3, execFileSync as execFileSync2 } from "child_process";
|
|
2431
2561
|
|
|
2432
2562
|
// src/services/deps/parsers/npm.ts
|
|
2433
|
-
import
|
|
2563
|
+
import path2 from "path";
|
|
2434
2564
|
function findNpmManifests(files) {
|
|
2435
2565
|
const lockFiles = new Set(
|
|
2436
2566
|
files.filter((f) => {
|
|
2437
|
-
const base =
|
|
2567
|
+
const base = path2.basename(f);
|
|
2438
2568
|
return base === "package-lock.json" || base === "yarn.lock" || base === "pnpm-lock.yaml";
|
|
2439
2569
|
})
|
|
2440
2570
|
);
|
|
2441
2571
|
const manifests = [];
|
|
2442
2572
|
for (const file of files) {
|
|
2443
|
-
if (
|
|
2573
|
+
if (path2.basename(file) !== "package.json") continue;
|
|
2444
2574
|
if (file.split("/").includes("node_modules")) continue;
|
|
2445
|
-
const dir =
|
|
2575
|
+
const dir = path2.dirname(file);
|
|
2446
2576
|
let lockFile;
|
|
2447
2577
|
for (const lf of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
|
|
2448
2578
|
const candidate = dir === "." ? lf : `${dir}/${lf}`;
|
|
@@ -2457,7 +2587,7 @@ function findNpmManifests(files) {
|
|
|
2457
2587
|
}
|
|
2458
2588
|
function parseNpmPackages(content, manifest, opts, lockContent) {
|
|
2459
2589
|
if (lockContent && manifest.lockFile) {
|
|
2460
|
-
const base =
|
|
2590
|
+
const base = path2.basename(manifest.lockFile);
|
|
2461
2591
|
if (base === "package-lock.json") {
|
|
2462
2592
|
const pkgs = parsePackageLock(lockContent, manifest.lockFile, opts);
|
|
2463
2593
|
if (pkgs.length > 0) return pkgs;
|
|
@@ -2605,26 +2735,26 @@ function parsePnpmLock(content, lockFilePath, opts, packageJsonContent) {
|
|
|
2605
2735
|
}
|
|
2606
2736
|
|
|
2607
2737
|
// src/services/deps/parsers/nuget.ts
|
|
2608
|
-
import
|
|
2738
|
+
import path3 from "path";
|
|
2609
2739
|
var PROJ_EXTENSIONS = /* @__PURE__ */ new Set([".csproj", ".fsproj", ".vbproj"]);
|
|
2610
2740
|
function findNugetManifests(files) {
|
|
2611
2741
|
const lockFiles = /* @__PURE__ */ new Map();
|
|
2612
2742
|
for (const f of files) {
|
|
2613
|
-
if (
|
|
2614
|
-
lockFiles.set(
|
|
2743
|
+
if (path3.basename(f) === "packages.lock.json") {
|
|
2744
|
+
lockFiles.set(path3.dirname(f), f);
|
|
2615
2745
|
}
|
|
2616
2746
|
}
|
|
2617
2747
|
const manifests = [];
|
|
2618
2748
|
for (const file of files) {
|
|
2619
|
-
const base =
|
|
2620
|
-
const ext =
|
|
2749
|
+
const base = path3.basename(file);
|
|
2750
|
+
const ext = path3.extname(file);
|
|
2621
2751
|
if (PROJ_EXTENSIONS.has(ext) || base === "packages.config") {
|
|
2622
|
-
const dir =
|
|
2752
|
+
const dir = path3.dirname(file);
|
|
2623
2753
|
const lockFile = lockFiles.get(dir);
|
|
2624
2754
|
manifests.push({ file, ecosystem: "nuget", lockFile });
|
|
2625
2755
|
}
|
|
2626
2756
|
if (base === "Directory.Packages.props") {
|
|
2627
|
-
const dir =
|
|
2757
|
+
const dir = path3.dirname(file);
|
|
2628
2758
|
manifests.push({ file, ecosystem: "nuget", lockFile: lockFiles.get(dir) });
|
|
2629
2759
|
}
|
|
2630
2760
|
}
|
|
@@ -2643,7 +2773,7 @@ function findNugetManifests(files) {
|
|
|
2643
2773
|
return deduped;
|
|
2644
2774
|
}
|
|
2645
2775
|
function parseNugetPackages(content, manifest, opts, lockContent, propsContent) {
|
|
2646
|
-
const base =
|
|
2776
|
+
const base = path3.basename(manifest.file);
|
|
2647
2777
|
if (lockContent) {
|
|
2648
2778
|
return parsePackagesLock(lockContent, manifest.lockFile ?? manifest.file, opts);
|
|
2649
2779
|
}
|
|
@@ -2764,14 +2894,14 @@ function parsePackagesLock(content, filePath, opts) {
|
|
|
2764
2894
|
}
|
|
2765
2895
|
|
|
2766
2896
|
// src/services/deps/parsers/maven.ts
|
|
2767
|
-
import
|
|
2897
|
+
import path4 from "path";
|
|
2768
2898
|
function findMavenManifests(files) {
|
|
2769
|
-
const lockFiles = new Set(files.filter((f) =>
|
|
2899
|
+
const lockFiles = new Set(files.filter((f) => path4.basename(f) === "gradle.lockfile"));
|
|
2770
2900
|
const manifests = [];
|
|
2771
2901
|
for (const file of files) {
|
|
2772
|
-
const base =
|
|
2902
|
+
const base = path4.basename(file);
|
|
2773
2903
|
if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
|
|
2774
|
-
const dir =
|
|
2904
|
+
const dir = path4.dirname(file);
|
|
2775
2905
|
const lockCandidate = dir === "." ? "gradle.lockfile" : `${dir}/gradle.lockfile`;
|
|
2776
2906
|
const lockFile = lockFiles.has(lockCandidate) ? lockCandidate : void 0;
|
|
2777
2907
|
manifests.push({ file, ecosystem: "maven", lockFile });
|
|
@@ -2780,7 +2910,7 @@ function findMavenManifests(files) {
|
|
|
2780
2910
|
return manifests;
|
|
2781
2911
|
}
|
|
2782
2912
|
function parseMavenPackages(content, manifest, opts, lockContent) {
|
|
2783
|
-
const base =
|
|
2913
|
+
const base = path4.basename(manifest.file);
|
|
2784
2914
|
if (lockContent && manifest.lockFile) {
|
|
2785
2915
|
return parseGradleLock(lockContent, manifest.lockFile, opts);
|
|
2786
2916
|
}
|
|
@@ -2941,7 +3071,7 @@ function getRepoFiles(repoRoot) {
|
|
|
2941
3071
|
}
|
|
2942
3072
|
function readFile(repoRoot, relPath) {
|
|
2943
3073
|
try {
|
|
2944
|
-
return fs.readFileSync(
|
|
3074
|
+
return fs.readFileSync(path5.join(repoRoot, relPath), "utf8");
|
|
2945
3075
|
} catch {
|
|
2946
3076
|
return null;
|
|
2947
3077
|
}
|
|
@@ -2959,13 +3089,13 @@ function readFileAtRef(repoRoot, ref, relPath) {
|
|
|
2959
3089
|
}
|
|
2960
3090
|
}
|
|
2961
3091
|
function findPropsContent(manifestFile, readFn) {
|
|
2962
|
-
let dir =
|
|
3092
|
+
let dir = path5.dirname(manifestFile);
|
|
2963
3093
|
for (let depth = 0; depth < 10; depth++) {
|
|
2964
3094
|
const candidate = dir === "." ? "Directory.Packages.props" : `${dir}/Directory.Packages.props`;
|
|
2965
3095
|
const content = readFn(candidate);
|
|
2966
3096
|
if (content) return content;
|
|
2967
3097
|
if (dir === ".") break;
|
|
2968
|
-
dir =
|
|
3098
|
+
dir = path5.dirname(dir);
|
|
2969
3099
|
}
|
|
2970
3100
|
return null;
|
|
2971
3101
|
}
|
|
@@ -4243,6 +4373,24 @@ var AdoWorkClient = class {
|
|
|
4243
4373
|
{ method: "POST", body: { text } }
|
|
4244
4374
|
);
|
|
4245
4375
|
}
|
|
4376
|
+
async addTags(collection, id, tags) {
|
|
4377
|
+
const item = await this.getWorkItem(collection, id);
|
|
4378
|
+
const existing = item.fields["System.Tags"] ?? "";
|
|
4379
|
+
const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
|
|
4380
|
+
const lowerSet = new Set(current.map((t) => t.toLowerCase()));
|
|
4381
|
+
const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
|
|
4382
|
+
const merged = [...current, ...toAdd];
|
|
4383
|
+
const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
|
|
4384
|
+
return this.updateWorkItem(collection, id, patch);
|
|
4385
|
+
}
|
|
4386
|
+
async removeTags(collection, id, tags) {
|
|
4387
|
+
const item = await this.getWorkItem(collection, id);
|
|
4388
|
+
const existing = item.fields["System.Tags"] ?? "";
|
|
4389
|
+
const lower = new Set(tags.map((t) => t.toLowerCase()));
|
|
4390
|
+
const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
|
|
4391
|
+
const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
|
|
4392
|
+
return this.updateWorkItem(collection, id, patch);
|
|
4393
|
+
}
|
|
4246
4394
|
async listWorkItemTypes(collection, project) {
|
|
4247
4395
|
const result = await this.http.ado(
|
|
4248
4396
|
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/workitemtypes?api-version=${API6}`
|
|
@@ -4268,6 +4416,24 @@ var AdoWorkClient = class {
|
|
|
4268
4416
|
);
|
|
4269
4417
|
return result.value ?? [];
|
|
4270
4418
|
}
|
|
4419
|
+
async listAttachments(collection, workItemId) {
|
|
4420
|
+
const item = await this.getWorkItem(collection, workItemId);
|
|
4421
|
+
return (item.relations ?? []).filter((r) => r.rel === "AttachedFile").map((r) => {
|
|
4422
|
+
const attrs = r.attributes ?? {};
|
|
4423
|
+
return {
|
|
4424
|
+
id: new URL(r.url).pathname.split("/").pop() ?? "",
|
|
4425
|
+
name: String(attrs["name"] ?? ""),
|
|
4426
|
+
url: r.url,
|
|
4427
|
+
comment: attrs["comment"] !== void 0 ? String(attrs["comment"]) : void 0,
|
|
4428
|
+
resourceSize: typeof attrs["resourceSize"] === "number" ? attrs["resourceSize"] : void 0,
|
|
4429
|
+
resourceCreatedDate: attrs["resourceCreatedDate"] !== void 0 ? String(attrs["resourceCreatedDate"]) : void 0,
|
|
4430
|
+
resourceModifiedDate: attrs["resourceModifiedDate"] !== void 0 ? String(attrs["resourceModifiedDate"]) : void 0
|
|
4431
|
+
};
|
|
4432
|
+
});
|
|
4433
|
+
}
|
|
4434
|
+
async downloadAttachment(absoluteUrl) {
|
|
4435
|
+
return this.http.adoBuffer(absoluteUrl);
|
|
4436
|
+
}
|
|
4271
4437
|
};
|
|
4272
4438
|
|
|
4273
4439
|
// src/services/ado/discovery.ts
|
|
@@ -4441,8 +4607,8 @@ function registerConfigCommands(program2) {
|
|
|
4441
4607
|
if (cfg.ado.baseUrl) {
|
|
4442
4608
|
try {
|
|
4443
4609
|
const collection = cfg.defaults.ado?.collection;
|
|
4444
|
-
const
|
|
4445
|
-
await http.ado(
|
|
4610
|
+
const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
4611
|
+
await http.ado(path8);
|
|
4446
4612
|
results.ado = { ok: true, message: "connected" };
|
|
4447
4613
|
} catch (err) {
|
|
4448
4614
|
results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -4614,8 +4780,8 @@ function registerConfigCommands(program2) {
|
|
|
4614
4780
|
} else {
|
|
4615
4781
|
try {
|
|
4616
4782
|
const collection = cfg.defaults.ado?.collection;
|
|
4617
|
-
const
|
|
4618
|
-
await http.ado(
|
|
4783
|
+
const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
4784
|
+
await http.ado(path8, { timeoutMs: 1e4 });
|
|
4619
4785
|
results.ado = { status: "valid", message: "ok" };
|
|
4620
4786
|
} catch (err) {
|
|
4621
4787
|
results.ado = categorize2(err);
|
|
@@ -5777,6 +5943,8 @@ function registerAdoProjectCommands(ado) {
|
|
|
5777
5943
|
}
|
|
5778
5944
|
|
|
5779
5945
|
// src/services/ado/commands/work.ts
|
|
5946
|
+
import fs3 from "fs";
|
|
5947
|
+
import path6 from "path";
|
|
5780
5948
|
function registerAdoWorkCommands(ado) {
|
|
5781
5949
|
const work = ado.command("work").description("Azure DevOps work item operations");
|
|
5782
5950
|
work.command("get").description("Get a work item by ID").requiredOption("--id <n>", "Work item ID").action(async (opts) => {
|
|
@@ -5917,6 +6085,55 @@ function registerAdoWorkCommands(ado) {
|
|
|
5917
6085
|
fail(err, "ado", "work-add-comment", start);
|
|
5918
6086
|
}
|
|
5919
6087
|
});
|
|
6088
|
+
work.command("add-tag").description("Add one or more tags to a work item (non-destructive)").requiredOption("--id <n>", "Work item ID").requiredOption("--tags <tags>", "Semicolon- or comma-separated tags to add").action(async (opts) => {
|
|
6089
|
+
const start = Date.now();
|
|
6090
|
+
try {
|
|
6091
|
+
const { collection, workClient } = getAdoContext(ado);
|
|
6092
|
+
const tags = opts.tags.split(/[;,]/).map((t) => t.trim()).filter(Boolean);
|
|
6093
|
+
const data = await workClient.addTags(collection, parseInt(opts.id, 10), tags);
|
|
6094
|
+
success(data, "ado", "work-add-tag", start);
|
|
6095
|
+
} catch (err) {
|
|
6096
|
+
fail(err, "ado", "work-add-tag", start);
|
|
6097
|
+
}
|
|
6098
|
+
});
|
|
6099
|
+
work.command("remove-tag").description("Remove one or more tags from a work item (non-destructive)").requiredOption("--id <n>", "Work item ID").requiredOption("--tags <tags>", "Semicolon- or comma-separated tags to remove").action(async (opts) => {
|
|
6100
|
+
const start = Date.now();
|
|
6101
|
+
try {
|
|
6102
|
+
const { collection, workClient } = getAdoContext(ado);
|
|
6103
|
+
const tags = opts.tags.split(/[;,]/).map((t) => t.trim()).filter(Boolean);
|
|
6104
|
+
const data = await workClient.removeTags(collection, parseInt(opts.id, 10), tags);
|
|
6105
|
+
success(data, "ado", "work-remove-tag", start);
|
|
6106
|
+
} catch (err) {
|
|
6107
|
+
fail(err, "ado", "work-remove-tag", start);
|
|
6108
|
+
}
|
|
6109
|
+
});
|
|
6110
|
+
work.command("list-attachments").description("List attachments on a work item").requiredOption("--id <n>", "Work item ID").action(async (opts) => {
|
|
6111
|
+
const start = Date.now();
|
|
6112
|
+
try {
|
|
6113
|
+
const { collection, workClient } = getAdoContext(ado);
|
|
6114
|
+
const data = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
6115
|
+
success(data, "ado", "work-list-attachments", start);
|
|
6116
|
+
} catch (err) {
|
|
6117
|
+
fail(err, "ado", "work-list-attachments", start);
|
|
6118
|
+
}
|
|
6119
|
+
});
|
|
6120
|
+
work.command("download-attachment").description("Download a work item attachment to .pncli/ (or --dir)").requiredOption("--id <n>", "Work item ID").requiredOption("--attachment-id <guid>", "Attachment ID from work list-attachments output").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
|
|
6121
|
+
const start = Date.now();
|
|
6122
|
+
try {
|
|
6123
|
+
const { collection, workClient } = getAdoContext(ado);
|
|
6124
|
+
const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
6125
|
+
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
6126
|
+
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
6127
|
+
const outDir = opts.dir ?? path6.join(process.cwd(), ".pncli");
|
|
6128
|
+
fs3.mkdirSync(outDir, { recursive: true });
|
|
6129
|
+
const outPath = path6.join(outDir, path6.basename(attachment.name));
|
|
6130
|
+
const buffer = await workClient.downloadAttachment(attachment.url);
|
|
6131
|
+
fs3.writeFileSync(outPath, buffer);
|
|
6132
|
+
success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
|
|
6133
|
+
} catch (err) {
|
|
6134
|
+
fail(err, "ado", "work-download-attachment", start);
|
|
6135
|
+
}
|
|
6136
|
+
});
|
|
5920
6137
|
work.command("types").description("List work item types in the project").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered types to ~/.pncli/config.json").action(async (opts) => {
|
|
5921
6138
|
const start = Date.now();
|
|
5922
6139
|
try {
|
|
@@ -5925,10 +6142,10 @@ function registerAdoWorkCommands(ado) {
|
|
|
5925
6142
|
const types = await discoverTypes(workClient, collection, project);
|
|
5926
6143
|
if (opts.save) {
|
|
5927
6144
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
5928
|
-
const
|
|
5929
|
-
const existing = JSON.parse(
|
|
6145
|
+
const fs5 = await import("fs");
|
|
6146
|
+
const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
5930
6147
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
5931
|
-
|
|
6148
|
+
fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
5932
6149
|
warn(`Saved ${types.length} types to ${globalConfigPath}`);
|
|
5933
6150
|
}
|
|
5934
6151
|
success(types, "ado", "work-types", start);
|
|
@@ -5957,11 +6174,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
5957
6174
|
) : allFields;
|
|
5958
6175
|
if (opts.save) {
|
|
5959
6176
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
5960
|
-
const
|
|
5961
|
-
const existing = JSON.parse(
|
|
6177
|
+
const fs5 = await import("fs");
|
|
6178
|
+
const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
5962
6179
|
const aliases = buildDefaultAliases(allFields);
|
|
5963
6180
|
existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
|
|
5964
|
-
|
|
6181
|
+
fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
5965
6182
|
warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
|
|
5966
6183
|
}
|
|
5967
6184
|
success(fields, "ado", "work-fields", start);
|
|
@@ -6242,7 +6459,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
6242
6459
|
const config = lc({ configPath: globalOpts.config });
|
|
6243
6460
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
6244
6461
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
6245
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6462
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-BNCZJQTD.js");
|
|
6246
6463
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
6247
6464
|
const coreClient = new AdoCoreClient2(http);
|
|
6248
6465
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6643,18 +6860,18 @@ var ArtifactoryClient = class {
|
|
|
6643
6860
|
}
|
|
6644
6861
|
});
|
|
6645
6862
|
}
|
|
6646
|
-
async getStorageInfo(repo,
|
|
6647
|
-
const cleanPath =
|
|
6863
|
+
async getStorageInfo(repo, path8) {
|
|
6864
|
+
const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
|
|
6648
6865
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
6649
6866
|
}
|
|
6650
|
-
async getProperties(repo,
|
|
6651
|
-
const cleanPath =
|
|
6867
|
+
async getProperties(repo, path8) {
|
|
6868
|
+
const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
|
|
6652
6869
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
6653
6870
|
params: { properties: "" }
|
|
6654
6871
|
});
|
|
6655
6872
|
}
|
|
6656
|
-
async setProperties(repo,
|
|
6657
|
-
const cleanPath =
|
|
6873
|
+
async setProperties(repo, path8, properties, opts = {}) {
|
|
6874
|
+
const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
|
|
6658
6875
|
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
6659
6876
|
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
6660
6877
|
method: "PUT",
|
|
@@ -6736,27 +6953,27 @@ function registerArtifactoryCommands(program2) {
|
|
|
6736
6953
|
fail(err, "artifactory", "repos", start);
|
|
6737
6954
|
}
|
|
6738
6955
|
});
|
|
6739
|
-
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo,
|
|
6956
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path8) => {
|
|
6740
6957
|
const start = Date.now();
|
|
6741
6958
|
try {
|
|
6742
6959
|
const client = getClient7(program2);
|
|
6743
|
-
const data = await client.getStorageInfo(repo,
|
|
6960
|
+
const data = await client.getStorageInfo(repo, path8);
|
|
6744
6961
|
success(data, "artifactory", "artifact-info", start);
|
|
6745
6962
|
} catch (err) {
|
|
6746
6963
|
fail(err, "artifactory", "artifact-info", start);
|
|
6747
6964
|
}
|
|
6748
6965
|
});
|
|
6749
|
-
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo,
|
|
6966
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path8) => {
|
|
6750
6967
|
const start = Date.now();
|
|
6751
6968
|
try {
|
|
6752
6969
|
const client = getClient7(program2);
|
|
6753
|
-
const data = await client.getProperties(repo,
|
|
6970
|
+
const data = await client.getProperties(repo, path8);
|
|
6754
6971
|
success(data, "artifactory", "properties-get", start);
|
|
6755
6972
|
} catch (err) {
|
|
6756
6973
|
fail(err, "artifactory", "properties-get", start);
|
|
6757
6974
|
}
|
|
6758
6975
|
});
|
|
6759
|
-
art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo,
|
|
6976
|
+
art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path8, keyvals, opts) => {
|
|
6760
6977
|
const start = Date.now();
|
|
6761
6978
|
try {
|
|
6762
6979
|
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
@@ -6767,8 +6984,8 @@ function registerArtifactoryCommands(program2) {
|
|
|
6767
6984
|
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
6768
6985
|
}
|
|
6769
6986
|
const client = getClient7(program2);
|
|
6770
|
-
await client.setProperties(repo,
|
|
6771
|
-
success({ repo, path:
|
|
6987
|
+
await client.setProperties(repo, path8, properties, { recursive: opts.recursive });
|
|
6988
|
+
success({ repo, path: path8, properties }, "artifactory", "properties-set", start);
|
|
6772
6989
|
} catch (err) {
|
|
6773
6990
|
fail(err, "artifactory", "properties-set", start);
|
|
6774
6991
|
}
|
|
@@ -7372,8 +7589,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
7372
7589
|
const startTime = Date.now();
|
|
7373
7590
|
try {
|
|
7374
7591
|
const http = getHttpClient(program2);
|
|
7375
|
-
const
|
|
7376
|
-
const data = await http.sonatypeiq(
|
|
7592
|
+
const path8 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
|
|
7593
|
+
const data = await http.sonatypeiq(path8);
|
|
7377
7594
|
success(data, "sonatypeiq", "policies list", startTime);
|
|
7378
7595
|
} catch (err) {
|
|
7379
7596
|
fail(err, "sonatypeiq", "policies list", startTime);
|
|
@@ -7382,8 +7599,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
7382
7599
|
}
|
|
7383
7600
|
|
|
7384
7601
|
// src/services/skills/commands.ts
|
|
7385
|
-
import
|
|
7386
|
-
import
|
|
7602
|
+
import fs4 from "fs";
|
|
7603
|
+
import path7 from "path";
|
|
7387
7604
|
import os from "os";
|
|
7388
7605
|
import { fileURLToPath } from "url";
|
|
7389
7606
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -7409,16 +7626,16 @@ function repoNameFromUrl(url) {
|
|
|
7409
7626
|
}
|
|
7410
7627
|
}
|
|
7411
7628
|
function defaultMarketplacePath(url) {
|
|
7412
|
-
return
|
|
7629
|
+
return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
7413
7630
|
}
|
|
7414
7631
|
var AGENT_PATHS = {
|
|
7415
|
-
"github-copilot": { project: ".agents/skills", user:
|
|
7416
|
-
"claude-code": { project: ".claude/skills", user:
|
|
7632
|
+
"github-copilot": { project: ".agents/skills", user: path7.join(os.homedir(), ".copilot/skills") },
|
|
7633
|
+
"claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
|
|
7417
7634
|
};
|
|
7418
7635
|
function getBundledSkillsDir() {
|
|
7419
7636
|
try {
|
|
7420
7637
|
const thisFile = fileURLToPath(import.meta.url);
|
|
7421
|
-
return
|
|
7638
|
+
return path7.resolve(path7.dirname(thisFile), "..", "skills");
|
|
7422
7639
|
} catch {
|
|
7423
7640
|
return "";
|
|
7424
7641
|
}
|
|
@@ -7430,22 +7647,22 @@ function registerSkillsCommands(program2) {
|
|
|
7430
7647
|
try {
|
|
7431
7648
|
let targetDir;
|
|
7432
7649
|
if (opts.target) {
|
|
7433
|
-
targetDir =
|
|
7650
|
+
targetDir = path7.resolve(opts.target);
|
|
7434
7651
|
} else {
|
|
7435
7652
|
const agentConfig = AGENT_PATHS[opts.agent];
|
|
7436
7653
|
if (!agentConfig) {
|
|
7437
7654
|
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
7438
7655
|
}
|
|
7439
7656
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
7440
|
-
targetDir =
|
|
7657
|
+
targetDir = path7.resolve(scopePath);
|
|
7441
7658
|
}
|
|
7442
|
-
const resolvedTarget =
|
|
7659
|
+
const resolvedTarget = path7.resolve(targetDir);
|
|
7443
7660
|
const bundledDir = getBundledSkillsDir();
|
|
7444
7661
|
let skillDirs = [];
|
|
7445
7662
|
if (bundledDir !== "") {
|
|
7446
7663
|
try {
|
|
7447
|
-
skillDirs =
|
|
7448
|
-
(name) =>
|
|
7664
|
+
skillDirs = fs4.readdirSync(bundledDir).filter(
|
|
7665
|
+
(name) => fs4.existsSync(path7.join(bundledDir, name, "SKILL.md"))
|
|
7449
7666
|
);
|
|
7450
7667
|
} catch {
|
|
7451
7668
|
}
|
|
@@ -7456,25 +7673,25 @@ function registerSkillsCommands(program2) {
|
|
|
7456
7673
|
const installed = [];
|
|
7457
7674
|
const failed = [];
|
|
7458
7675
|
for (const skillName of skillDirs) {
|
|
7459
|
-
const existingDir =
|
|
7460
|
-
if (!existingDir.startsWith(resolvedTarget +
|
|
7461
|
-
if (
|
|
7462
|
-
|
|
7676
|
+
const existingDir = path7.resolve(targetDir, skillName);
|
|
7677
|
+
if (!existingDir.startsWith(resolvedTarget + path7.sep)) continue;
|
|
7678
|
+
if (fs4.existsSync(existingDir)) {
|
|
7679
|
+
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
7463
7680
|
}
|
|
7464
7681
|
}
|
|
7465
7682
|
warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
|
|
7466
7683
|
for (const skillName of skillDirs) {
|
|
7467
|
-
const skillDir =
|
|
7468
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
7684
|
+
const skillDir = path7.resolve(targetDir, skillName);
|
|
7685
|
+
if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
|
|
7469
7686
|
failed.push(skillName);
|
|
7470
7687
|
continue;
|
|
7471
7688
|
}
|
|
7472
7689
|
try {
|
|
7473
|
-
const mdFiles =
|
|
7474
|
-
|
|
7690
|
+
const mdFiles = fs4.readdirSync(path7.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7691
|
+
fs4.mkdirSync(skillDir, { recursive: true });
|
|
7475
7692
|
for (const mdFile of mdFiles) {
|
|
7476
|
-
const content =
|
|
7477
|
-
|
|
7693
|
+
const content = fs4.readFileSync(path7.join(bundledDir, skillName, mdFile), "utf8");
|
|
7694
|
+
fs4.writeFileSync(path7.join(skillDir, mdFile), content, "utf8");
|
|
7478
7695
|
}
|
|
7479
7696
|
installed.push(skillName);
|
|
7480
7697
|
} catch {
|
|
@@ -7503,25 +7720,25 @@ function registerSkillsCommands(program2) {
|
|
|
7503
7720
|
try {
|
|
7504
7721
|
let targetDir;
|
|
7505
7722
|
if (opts.target) {
|
|
7506
|
-
targetDir =
|
|
7723
|
+
targetDir = path7.resolve(opts.target);
|
|
7507
7724
|
} else {
|
|
7508
7725
|
const agentConfig = AGENT_PATHS[opts.agent];
|
|
7509
7726
|
if (!agentConfig) {
|
|
7510
7727
|
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
7511
7728
|
}
|
|
7512
7729
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
7513
|
-
targetDir =
|
|
7730
|
+
targetDir = path7.resolve(scopePath);
|
|
7514
7731
|
}
|
|
7515
|
-
if (!
|
|
7732
|
+
if (!fs4.existsSync(targetDir)) {
|
|
7516
7733
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
7517
7734
|
return;
|
|
7518
7735
|
}
|
|
7519
|
-
const skillDirs =
|
|
7520
|
-
const skillPath =
|
|
7521
|
-
return
|
|
7736
|
+
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
7737
|
+
const skillPath = path7.join(targetDir, name, "SKILL.md");
|
|
7738
|
+
return fs4.existsSync(skillPath);
|
|
7522
7739
|
});
|
|
7523
7740
|
const skillsList = skillDirs.map((name) => {
|
|
7524
|
-
const content =
|
|
7741
|
+
const content = fs4.readFileSync(path7.join(targetDir, name, "SKILL.md"), "utf8");
|
|
7525
7742
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7526
7743
|
const data = {};
|
|
7527
7744
|
const metadata = {};
|
|
@@ -7563,9 +7780,9 @@ function registerSkillsCommands(program2) {
|
|
|
7563
7780
|
marketplace.command("setup").description("Clone a skills marketplace repo and register it in global config").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").action(async (url, localPath, opts) => {
|
|
7564
7781
|
const start = Date.now();
|
|
7565
7782
|
try {
|
|
7566
|
-
const resolvedPath =
|
|
7567
|
-
const hasGit =
|
|
7568
|
-
if (
|
|
7783
|
+
const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
|
|
7784
|
+
const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
|
|
7785
|
+
if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
|
|
7569
7786
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
7570
7787
|
}
|
|
7571
7788
|
if (hasGit) {
|
|
@@ -7599,7 +7816,7 @@ function registerSkillsCommands(program2) {
|
|
|
7599
7816
|
const configPath = getGlobalConfigPath();
|
|
7600
7817
|
const globalConfig = loadJsonFile(configPath) ?? {};
|
|
7601
7818
|
const marketplacePath = globalConfig.marketplace?.localPath;
|
|
7602
|
-
if (!marketplacePath || !
|
|
7819
|
+
if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
|
|
7603
7820
|
throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
|
|
7604
7821
|
}
|
|
7605
7822
|
warn("Pulling latest marketplace content...");
|
|
@@ -7636,13 +7853,13 @@ function registerSkillsCommands(program2) {
|
|
|
7636
7853
|
if (pluginChoices.length === 0) {
|
|
7637
7854
|
throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7638
7855
|
}
|
|
7639
|
-
const targetDir = opts.claude ?
|
|
7856
|
+
const targetDir = opts.claude ? path7.join(os.homedir(), ".claude", "skills") : path7.join(os.homedir(), ".agents", "skills");
|
|
7640
7857
|
if (plugin === "all") {
|
|
7641
7858
|
const results = {};
|
|
7642
7859
|
let totalInstalled = 0;
|
|
7643
7860
|
for (const pluginChoice of pluginChoices) {
|
|
7644
7861
|
const skillsSrc2 = resolveSkillsSrc(marketplacePath, pluginChoice.name);
|
|
7645
|
-
if (!
|
|
7862
|
+
if (!fs4.existsSync(skillsSrc2)) {
|
|
7646
7863
|
results[pluginChoice.name] = { installed: [], failed: [] };
|
|
7647
7864
|
warn(`No skills directory found for plugin "${pluginChoice.name}" \u2014 skipping.`);
|
|
7648
7865
|
continue;
|
|
@@ -7650,6 +7867,9 @@ function registerSkillsCommands(program2) {
|
|
|
7650
7867
|
const { installed: installed2, failed: failed2 } = copyPluginSkills(skillsSrc2, targetDir);
|
|
7651
7868
|
results[pluginChoice.name] = { installed: installed2, failed: failed2 };
|
|
7652
7869
|
totalInstalled += installed2.length;
|
|
7870
|
+
for (const skill of installed2) {
|
|
7871
|
+
warn(` ${skill}: ${path7.join(skillsSrc2, skill)} \u2192 ${path7.join(targetDir, skill)}`);
|
|
7872
|
+
}
|
|
7653
7873
|
if (failed2.length > 0) {
|
|
7654
7874
|
warn(`Skipped ${failed2.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed2.join(", ")}`);
|
|
7655
7875
|
}
|
|
@@ -7678,10 +7898,13 @@ function registerSkillsCommands(program2) {
|
|
|
7678
7898
|
});
|
|
7679
7899
|
}
|
|
7680
7900
|
const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
|
|
7681
|
-
if (!
|
|
7901
|
+
if (!fs4.existsSync(skillsSrc)) {
|
|
7682
7902
|
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
7683
7903
|
}
|
|
7684
7904
|
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
7905
|
+
for (const skill of installed) {
|
|
7906
|
+
warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
|
|
7907
|
+
}
|
|
7685
7908
|
if (failed.length > 0) {
|
|
7686
7909
|
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
7687
7910
|
}
|
|
@@ -7699,46 +7922,46 @@ function registerSkillsCommands(program2) {
|
|
|
7699
7922
|
});
|
|
7700
7923
|
}
|
|
7701
7924
|
function resolvePluginChoices(marketplacePath) {
|
|
7702
|
-
const marketplaceJsonPath =
|
|
7703
|
-
if (
|
|
7925
|
+
const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
7926
|
+
if (fs4.existsSync(marketplaceJsonPath)) {
|
|
7704
7927
|
try {
|
|
7705
|
-
const meta = JSON.parse(
|
|
7928
|
+
const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
|
|
7706
7929
|
if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
|
|
7707
7930
|
return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
|
|
7708
7931
|
}
|
|
7709
7932
|
} catch {
|
|
7710
7933
|
}
|
|
7711
7934
|
}
|
|
7712
|
-
const pluginsDir =
|
|
7713
|
-
if (
|
|
7714
|
-
return
|
|
7935
|
+
const pluginsDir = path7.join(marketplacePath, "plugins");
|
|
7936
|
+
if (fs4.existsSync(pluginsDir)) {
|
|
7937
|
+
return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path7.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
7715
7938
|
}
|
|
7716
7939
|
return [];
|
|
7717
7940
|
}
|
|
7718
7941
|
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
7719
|
-
const pluginsBase =
|
|
7720
|
-
const skillsSrc =
|
|
7721
|
-
if (!skillsSrc.startsWith(pluginsBase +
|
|
7942
|
+
const pluginsBase = path7.resolve(marketplacePath, "plugins");
|
|
7943
|
+
const skillsSrc = path7.resolve(pluginsBase, selectedPlugin, "skills");
|
|
7944
|
+
if (!skillsSrc.startsWith(pluginsBase + path7.sep)) {
|
|
7722
7945
|
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
7723
7946
|
}
|
|
7724
7947
|
return skillsSrc;
|
|
7725
7948
|
}
|
|
7726
7949
|
function copyPluginSkills(skillsSrc, targetDir) {
|
|
7727
|
-
|
|
7728
|
-
const resolvedTarget =
|
|
7729
|
-
const skillNames =
|
|
7730
|
-
(name) =>
|
|
7950
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
7951
|
+
const resolvedTarget = path7.resolve(targetDir);
|
|
7952
|
+
const skillNames = fs4.readdirSync(skillsSrc).filter(
|
|
7953
|
+
(name) => fs4.statSync(path7.join(skillsSrc, name)).isDirectory()
|
|
7731
7954
|
);
|
|
7732
7955
|
const installed = [];
|
|
7733
7956
|
const failed = [];
|
|
7734
7957
|
for (const skillName of skillNames) {
|
|
7735
|
-
const dest =
|
|
7736
|
-
if (!dest.startsWith(resolvedTarget +
|
|
7958
|
+
const dest = path7.resolve(targetDir, skillName);
|
|
7959
|
+
if (!dest.startsWith(resolvedTarget + path7.sep)) {
|
|
7737
7960
|
failed.push(skillName);
|
|
7738
7961
|
continue;
|
|
7739
7962
|
}
|
|
7740
|
-
|
|
7741
|
-
|
|
7963
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
7964
|
+
fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
|
|
7742
7965
|
installed.push(skillName);
|
|
7743
7966
|
}
|
|
7744
7967
|
return { installed, failed };
|