@kolatts/pncli 1.17.0 → 1.18.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/copilot-instructions.md +44 -5
- package/dist/{chunk-FFVQ4PA7.js → chunk-IVUDXPDP.js} +13 -3
- package/dist/chunk-IVUDXPDP.js.map +1 -0
- package/dist/{chunk-XPPEXJBP.js → chunk-WX4ZRHIC.js} +33 -7
- package/dist/chunk-WX4ZRHIC.js.map +1 -0
- package/dist/cli.js +546 -77
- package/dist/cli.js.map +1 -1
- package/dist/{config-S56LADVF.js → config-QN7KDHE4.js} +2 -2
- package/dist/{http-5WRBWEM6.js → http-DDY5DLF6.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +5 -1
- package/skills/pncli/ado.md +19 -1
- package/skills/pncli/bitbucket.md +11 -1
- package/skills/pncli/confluence.md +5 -1
- package/skills/pncli/github.md +11 -1
- package/skills/pncli/jira.md +11 -0
- package/dist/chunk-FFVQ4PA7.js.map +0 -1
- package/dist/chunk-XPPEXJBP.js.map +0 -1
- /package/dist/{config-S56LADVF.js.map → config-QN7KDHE4.js.map} +0 -0
- /package/dist/{http-5WRBWEM6.js.map → http-DDY5DLF6.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
setRepoConfigValue,
|
|
9
9
|
writeGlobalConfig,
|
|
10
10
|
writeRepoConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IVUDXPDP.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCode,
|
|
14
14
|
PncliError,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
success,
|
|
21
21
|
warn,
|
|
22
22
|
writeRawOutput
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WX4ZRHIC.js";
|
|
24
24
|
import {
|
|
25
25
|
AdoCoreClient
|
|
26
26
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -734,6 +734,19 @@ var BitbucketClient = class {
|
|
|
734
734
|
);
|
|
735
735
|
return result.values ?? [];
|
|
736
736
|
}
|
|
737
|
+
async createRepo(opts) {
|
|
738
|
+
return this.http.bitbucket(
|
|
739
|
+
`${API}/projects/${opts.project}/repos`,
|
|
740
|
+
{
|
|
741
|
+
method: "POST",
|
|
742
|
+
body: {
|
|
743
|
+
name: opts.name,
|
|
744
|
+
scmId: "git",
|
|
745
|
+
...opts.description !== void 0 ? { description: opts.description } : {}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
);
|
|
749
|
+
}
|
|
737
750
|
};
|
|
738
751
|
|
|
739
752
|
// src/services/git/commands.ts
|
|
@@ -837,7 +850,7 @@ function registerGitCommands(program2) {
|
|
|
837
850
|
}
|
|
838
851
|
|
|
839
852
|
// src/services/jira/commands.ts
|
|
840
|
-
import { readFileSync as
|
|
853
|
+
import { readFileSync as readFileSync3, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
841
854
|
import path from "path";
|
|
842
855
|
|
|
843
856
|
// src/services/jira/client.ts
|
|
@@ -911,7 +924,7 @@ var JiraClient = class {
|
|
|
911
924
|
if (opts.description) fields.description = opts.description;
|
|
912
925
|
if (opts.priority) fields.priority = { name: opts.priority };
|
|
913
926
|
if (opts.assignee) fields.assignee = { name: opts.assignee };
|
|
914
|
-
if (opts.labels) fields.labels = opts.labels;
|
|
927
|
+
if (opts.labels?.length) fields.labels = opts.labels;
|
|
915
928
|
Object.assign(fields, opts.customFieldValues ?? {});
|
|
916
929
|
await this.http.jira(`${API2}/issue/${key}`, {
|
|
917
930
|
method: "PUT",
|
|
@@ -1207,7 +1220,119 @@ function formatFieldValue(value, type) {
|
|
|
1207
1220
|
}
|
|
1208
1221
|
}
|
|
1209
1222
|
|
|
1223
|
+
// src/services/jira/input-schema.ts
|
|
1224
|
+
var JIRA_INPUT_FILE_SCHEMA = {
|
|
1225
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1226
|
+
title: "pncli Jira issue input file",
|
|
1227
|
+
description: "Shape accepted by --input-file on `pncli jira create-issue` / `update-issue`. CLI flags (--summary, --description, --priority, --field, ...) override matching keys here; overridden keys are reported in meta.overrides.",
|
|
1228
|
+
type: "object",
|
|
1229
|
+
properties: {
|
|
1230
|
+
project: { type: "string", description: "Project key (create-issue only)" },
|
|
1231
|
+
issueType: { type: "string", description: "Issue type name, e.g. Bug, Story, Task (create-issue only)" },
|
|
1232
|
+
fields: {
|
|
1233
|
+
type: "object",
|
|
1234
|
+
description: 'Field dictionary. Built-in keys: summary, description, priority, assignee, labels, parent. Any other key resolves as a custom field: a friendly name registered via `pncli jira fields`, or a raw field id (e.g. customfield_10032) passed through untouched \u2014 custom fields never need to be pre-registered in config. Any string value may be "@path/to/file" to read that field\'s content from a file instead of inlining it (useful for large rich-text fields).',
|
|
1235
|
+
additionalProperties: true
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
additionalProperties: false
|
|
1239
|
+
};
|
|
1240
|
+
var JIRA_INPUT_FILE_EXAMPLE = {
|
|
1241
|
+
project: "PROJ",
|
|
1242
|
+
issueType: "Bug",
|
|
1243
|
+
fields: {
|
|
1244
|
+
summary: "Login broken on Safari",
|
|
1245
|
+
description: "@desc.html",
|
|
1246
|
+
priority: "High",
|
|
1247
|
+
labels: ["frontend", "urgent"],
|
|
1248
|
+
// Raw field id — passed through untouched, no pre-registration needed. A friendly
|
|
1249
|
+
// custom-field name (e.g. "Story Points") also works, but only once it's registered
|
|
1250
|
+
// via `pncli config set jira.customFields` / discovered with `pncli jira fields`.
|
|
1251
|
+
customfield_10032: "@ac.md"
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// src/lib/input.ts
|
|
1256
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1257
|
+
function resolveTextInput(inline, file, optionName) {
|
|
1258
|
+
if (inline !== void 0 && file !== void 0) {
|
|
1259
|
+
throw new PncliError(`Cannot specify both --${optionName} and --${optionName}-file`, 1);
|
|
1260
|
+
}
|
|
1261
|
+
if (file !== void 0) {
|
|
1262
|
+
try {
|
|
1263
|
+
return readFileSync2(file === "-" ? 0 : file, "utf8");
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
const source = file === "-" ? "stdin" : `file "${file}"`;
|
|
1266
|
+
throw new PncliError(`Cannot read ${optionName} ${source}: ${e.message}`, 1);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return inline;
|
|
1270
|
+
}
|
|
1271
|
+
function readJsonInputFile(filePath, optionName = "input-file") {
|
|
1272
|
+
let raw;
|
|
1273
|
+
try {
|
|
1274
|
+
raw = readFileSync2(filePath === "-" ? 0 : filePath, "utf8");
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
const source = filePath === "-" ? "stdin" : `file "${filePath}"`;
|
|
1277
|
+
throw new PncliError(`Cannot read --${optionName} ${source}: ${e.message}`, 1);
|
|
1278
|
+
}
|
|
1279
|
+
try {
|
|
1280
|
+
return JSON.parse(raw);
|
|
1281
|
+
} catch (e) {
|
|
1282
|
+
throw new PncliError(`--${optionName} must contain valid JSON: ${e.message}`, 1);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function resolveAtFileRef(value) {
|
|
1286
|
+
if (typeof value !== "string" || !value.startsWith("@")) return value;
|
|
1287
|
+
const filePath = value.slice(1);
|
|
1288
|
+
try {
|
|
1289
|
+
return readFileSync2(filePath, "utf8");
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
throw new PncliError(`Cannot read referenced file "${filePath}": ${e.message}`, 1);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function mergeWithOverrides(fromJson, fromFlags) {
|
|
1295
|
+
const merged = { ...fromJson };
|
|
1296
|
+
const overrides = [];
|
|
1297
|
+
for (const [key, value] of Object.entries(fromFlags)) {
|
|
1298
|
+
if (value === void 0) continue;
|
|
1299
|
+
if (Object.prototype.hasOwnProperty.call(fromJson, key)) overrides.push(key);
|
|
1300
|
+
merged[key] = value;
|
|
1301
|
+
}
|
|
1302
|
+
return { merged, overrides };
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1210
1305
|
// src/services/jira/commands.ts
|
|
1306
|
+
var BUILTIN_ISSUE_FIELDS = /* @__PURE__ */ new Set(["summary", "description", "priority", "assignee", "labels", "parent"]);
|
|
1307
|
+
function splitFieldsDictionary(fields, fieldMap) {
|
|
1308
|
+
const builtin = {};
|
|
1309
|
+
const custom = {};
|
|
1310
|
+
for (const [rawKey, rawValue] of Object.entries(fields)) {
|
|
1311
|
+
const value = resolveAtFileRef(rawValue);
|
|
1312
|
+
const key = rawKey.toLowerCase();
|
|
1313
|
+
if (BUILTIN_ISSUE_FIELDS.has(key)) {
|
|
1314
|
+
builtin[key] = value;
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
const def = fieldMap.byName.get(key) ?? fieldMap.byId.get(rawKey);
|
|
1318
|
+
if (def) {
|
|
1319
|
+
custom[def.id] = value;
|
|
1320
|
+
} else if (!/\s/.test(rawKey)) {
|
|
1321
|
+
custom[rawKey] = value;
|
|
1322
|
+
} else {
|
|
1323
|
+
throw new PncliError(
|
|
1324
|
+
`Unknown custom field: "${rawKey}". Fields must be registered in config by friendly name or ID. Run: pncli jira fields`,
|
|
1325
|
+
1
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return { builtin, custom };
|
|
1330
|
+
}
|
|
1331
|
+
function normalizeLabels(value) {
|
|
1332
|
+
if (value === void 0) return void 0;
|
|
1333
|
+
if (Array.isArray(value)) return value.map((v) => String(v).trim());
|
|
1334
|
+
return String(value).split(",").map((s) => s.trim()).filter(Boolean);
|
|
1335
|
+
}
|
|
1211
1336
|
function getClient(program2) {
|
|
1212
1337
|
const opts = program2.optsWithGlobals();
|
|
1213
1338
|
const config = loadConfig({ configPath: opts.config });
|
|
@@ -1247,7 +1372,7 @@ function registerJiraCommands(program2) {
|
|
|
1247
1372
|
fail(err, "jira", "get-issue", start);
|
|
1248
1373
|
}
|
|
1249
1374
|
});
|
|
1250
|
-
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").
|
|
1375
|
+
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").option("--summary <text>", "Issue summary (required, unless supplied via --input-file)").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--parent <key>", "Parent issue key \u2014 sets fields.parent in the create payload").option("--field <Name=value>", "Custom field value; use Name=@file.json to read value from file (repeatable)", (val, acc) => [...acc, val], []).option("--fields-file <path>", "Path to a JSON file mapping field names/IDs to their Jira API values").option("--input-file <path>", "JSON file describing the whole issue ({ project, issueType, fields: {...} }); '-' = stdin. CLI flags override matching keys. See: pncli jira schema").action(async (opts) => {
|
|
1251
1376
|
const start = Date.now();
|
|
1252
1377
|
let fieldMap = buildFieldMap([]);
|
|
1253
1378
|
try {
|
|
@@ -1255,35 +1380,87 @@ function registerJiraCommands(program2) {
|
|
|
1255
1380
|
const client = resolved.client;
|
|
1256
1381
|
fieldMap = resolved.fieldMap;
|
|
1257
1382
|
const defaults = getDefaults(program2);
|
|
1258
|
-
const
|
|
1259
|
-
const
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1383
|
+
const jsonInput = opts.inputFile ? readJsonInputFile(opts.inputFile) : void 0;
|
|
1384
|
+
const { builtin: jsonBuiltin, custom: jsonCustom } = jsonInput?.fields ? splitFieldsDictionary(jsonInput.fields, fieldMap) : { builtin: {}, custom: {} };
|
|
1385
|
+
const project = opts.project ?? jsonInput?.project ?? defaults.project;
|
|
1386
|
+
const issueType = opts.type ?? jsonInput?.issueType ?? defaults.issueType ?? "Task";
|
|
1387
|
+
if (!project) throw new PncliError('--project required (or set defaults.jira.project in config, or "project" in --input-file)', 1);
|
|
1388
|
+
const flagBuiltin = {
|
|
1389
|
+
...opts.summary !== void 0 ? { summary: opts.summary } : {},
|
|
1390
|
+
...opts.description !== void 0 ? { description: opts.description } : {},
|
|
1391
|
+
...opts.priority !== void 0 ? { priority: opts.priority } : {},
|
|
1392
|
+
...opts.assignee !== void 0 ? { assignee: opts.assignee } : {},
|
|
1393
|
+
...opts.labels !== void 0 ? { labels: opts.labels } : {},
|
|
1394
|
+
...opts.parent !== void 0 ? { parent: opts.parent } : {}
|
|
1395
|
+
};
|
|
1396
|
+
const { merged: builtinFields, overrides: builtinOverrides } = mergeWithOverrides(jsonBuiltin, flagBuiltin);
|
|
1397
|
+
const summary = builtinFields.summary;
|
|
1398
|
+
if (!summary) throw new PncliError('Must specify --summary or "fields.summary" in --input-file', 1);
|
|
1399
|
+
const priority = builtinFields.priority ?? defaults.priority;
|
|
1263
1400
|
const fileFields = opts.fieldsFile ? parseFieldsFile(opts.fieldsFile, fieldMap) : {};
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1401
|
+
const flagCustom = { ...fileFields, ...parseFieldArgs(opts.field, fieldMap) };
|
|
1402
|
+
const { merged: customFieldValues, overrides: customOverrides } = mergeWithOverrides(jsonCustom, flagCustom);
|
|
1403
|
+
const overrides = [...builtinOverrides, ...customOverrides];
|
|
1404
|
+
if (overrides.length) warn(`--input-file value(s) overridden by CLI flags: ${overrides.join(", ")}`);
|
|
1405
|
+
const data = await client.createIssue({
|
|
1406
|
+
project,
|
|
1407
|
+
issueType,
|
|
1408
|
+
summary,
|
|
1409
|
+
description: builtinFields.description,
|
|
1410
|
+
priority,
|
|
1411
|
+
assignee: builtinFields.assignee,
|
|
1412
|
+
labels: normalizeLabels(builtinFields.labels),
|
|
1413
|
+
parent: builtinFields.parent,
|
|
1414
|
+
customFieldValues
|
|
1415
|
+
});
|
|
1416
|
+
success(data, "jira", "create-issue", start, overrides);
|
|
1267
1417
|
} catch (err) {
|
|
1268
1418
|
fail(translateFieldErrors(err, fieldMap), "jira", "create-issue", start);
|
|
1269
1419
|
}
|
|
1270
1420
|
});
|
|
1271
|
-
jira.command("update-issue").description("Update a Jira issue").requiredOption("--key <issue-key>", "Issue key").option("--summary <text>", "New summary").option("--description <text>", "New description").option("--priority <name>", "New priority").option("--assignee <accountId>", "New assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value; use Name=@file.json to read value from file (repeatable)", (val, acc) => [...acc, val], []).option("--fields-file <path>", "Path to a JSON file mapping field names/IDs to their Jira API values").action(async (opts) => {
|
|
1421
|
+
jira.command("update-issue").description("Update a Jira issue").requiredOption("--key <issue-key>", "Issue key").option("--summary <text>", "New summary").option("--description <text>", "New description").option("--priority <name>", "New priority").option("--assignee <accountId>", "New assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value; use Name=@file.json to read value from file (repeatable)", (val, acc) => [...acc, val], []).option("--fields-file <path>", "Path to a JSON file mapping field names/IDs to their Jira API values").option("--input-file <path>", "JSON file describing fields to update ({ fields: {...} }); '-' = stdin. CLI flags override matching keys. See: pncli jira schema").action(async (opts) => {
|
|
1272
1422
|
const start = Date.now();
|
|
1273
1423
|
let fieldMap = buildFieldMap([]);
|
|
1274
1424
|
try {
|
|
1275
1425
|
const resolved = getClientAndFields(program2);
|
|
1276
1426
|
const client = resolved.client;
|
|
1277
1427
|
fieldMap = resolved.fieldMap;
|
|
1278
|
-
const
|
|
1428
|
+
const jsonInput = opts.inputFile ? readJsonInputFile(opts.inputFile) : void 0;
|
|
1429
|
+
const { builtin: jsonBuiltin, custom: jsonCustom } = jsonInput?.fields ? splitFieldsDictionary(jsonInput.fields, fieldMap) : { builtin: {}, custom: {} };
|
|
1430
|
+
const flagBuiltin = {
|
|
1431
|
+
...opts.summary !== void 0 ? { summary: opts.summary } : {},
|
|
1432
|
+
...opts.description !== void 0 ? { description: opts.description } : {},
|
|
1433
|
+
...opts.priority !== void 0 ? { priority: opts.priority } : {},
|
|
1434
|
+
...opts.assignee !== void 0 ? { assignee: opts.assignee } : {},
|
|
1435
|
+
...opts.labels !== void 0 ? { labels: opts.labels } : {}
|
|
1436
|
+
};
|
|
1437
|
+
const { merged: builtinFields, overrides: builtinOverrides } = mergeWithOverrides(jsonBuiltin, flagBuiltin);
|
|
1279
1438
|
const fileFields = opts.fieldsFile ? parseFieldsFile(opts.fieldsFile, fieldMap) : {};
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1439
|
+
const flagCustom = { ...fileFields, ...parseFieldArgs(opts.field, fieldMap) };
|
|
1440
|
+
const { merged: customFieldValues, overrides: customOverrides } = mergeWithOverrides(jsonCustom, flagCustom);
|
|
1441
|
+
const overrides = [...builtinOverrides, ...customOverrides];
|
|
1442
|
+
if (overrides.length) warn(`--input-file value(s) overridden by CLI flags: ${overrides.join(", ")}`);
|
|
1443
|
+
await client.updateIssue(opts.key, {
|
|
1444
|
+
summary: builtinFields.summary,
|
|
1445
|
+
description: builtinFields.description,
|
|
1446
|
+
priority: builtinFields.priority,
|
|
1447
|
+
assignee: builtinFields.assignee,
|
|
1448
|
+
labels: normalizeLabels(builtinFields.labels),
|
|
1449
|
+
customFieldValues
|
|
1450
|
+
});
|
|
1451
|
+
success({ updated: opts.key }, "jira", "update-issue", start, overrides);
|
|
1283
1452
|
} catch (err) {
|
|
1284
1453
|
fail(translateFieldErrors(err, fieldMap), "jira", "update-issue", start);
|
|
1285
1454
|
}
|
|
1286
1455
|
});
|
|
1456
|
+
jira.command("schema").description("Print the --input-file JSON schema and an example for create-issue/update-issue").option("--example-only", "Print only the runnable example JSON (no envelope) \u2014 pipeable straight into --input-file").action((opts) => {
|
|
1457
|
+
const start = Date.now();
|
|
1458
|
+
if (opts.exampleOnly) {
|
|
1459
|
+
writeRawOutput(JSON.stringify(JIRA_INPUT_FILE_EXAMPLE, null, 2) + "\n");
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
success({ schema: JIRA_INPUT_FILE_SCHEMA, example: JIRA_INPUT_FILE_EXAMPLE }, "jira", "schema", start);
|
|
1463
|
+
});
|
|
1287
1464
|
jira.command("transition-issue").description("Transition a Jira issue to a new status").requiredOption("--key <issue-key>", "Issue key").requiredOption("--transition <name-or-id>", "Transition name or ID").action(async (opts) => {
|
|
1288
1465
|
const start = Date.now();
|
|
1289
1466
|
try {
|
|
@@ -1492,7 +1669,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
|
|
|
1492
1669
|
const filePath = value.slice(1);
|
|
1493
1670
|
let raw;
|
|
1494
1671
|
try {
|
|
1495
|
-
raw =
|
|
1672
|
+
raw = readFileSync3(filePath, "utf8").trim();
|
|
1496
1673
|
} catch (e) {
|
|
1497
1674
|
throw new PncliError(`Cannot read file "${filePath}": ${e.message}`, 1);
|
|
1498
1675
|
}
|
|
@@ -1510,7 +1687,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
|
|
|
1510
1687
|
function parseFieldsFile(filePath, fieldMap) {
|
|
1511
1688
|
let raw;
|
|
1512
1689
|
try {
|
|
1513
|
-
raw =
|
|
1690
|
+
raw = readFileSync3(filePath, "utf8");
|
|
1514
1691
|
} catch (e) {
|
|
1515
1692
|
throw new PncliError(`Cannot read fields file "${filePath}": ${e.message}`, 1);
|
|
1516
1693
|
}
|
|
@@ -1563,6 +1740,24 @@ function getClient2(program2, overrides) {
|
|
|
1563
1740
|
}
|
|
1564
1741
|
function registerBitbucketCommands(program2) {
|
|
1565
1742
|
const bb = program2.command("bitbucket").description("Bitbucket Server operations").option("--project <key>", "Bitbucket project key").option("--repo <slug>", "Bitbucket repository slug");
|
|
1743
|
+
bb.command("create-repo").description("Create a new repository in a project").requiredOption("--name <name>", "Repository name (slug)").option("--description <desc>", "Repository description").option("--project <key>", "Bitbucket project key (overrides parent --project)").action(async (opts) => {
|
|
1744
|
+
const start = Date.now();
|
|
1745
|
+
try {
|
|
1746
|
+
const globalOpts = bb.optsWithGlobals();
|
|
1747
|
+
const config = loadConfig({ configPath: globalOpts.config });
|
|
1748
|
+
const http = createHttpClient(config, Boolean(globalOpts.dryRun));
|
|
1749
|
+
const client = new BitbucketClient(http);
|
|
1750
|
+
const ctx = getGitContext(config);
|
|
1751
|
+
const project = opts.project ?? globalOpts.project ?? ctx?.project ?? config.defaults.bitbucket?.project ?? "";
|
|
1752
|
+
if (!project) {
|
|
1753
|
+
throw new PncliError("Could not determine Bitbucket project. Pass --project, or run pncli config init.", 1);
|
|
1754
|
+
}
|
|
1755
|
+
const data = await client.createRepo({ project, name: opts.name, description: opts.description });
|
|
1756
|
+
success(data, "bitbucket", "create-repo", start);
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
fail(err, "bitbucket", "create-repo", start);
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1566
1761
|
bb.command("list-prs").description("List pull requests").option("--state <state>", "PR state: OPEN|MERGED|DECLINED|ALL", "OPEN").option("--author <username>", "Filter by author username").option("--reviewer <username>", "Filter by reviewer username").action(async (opts) => {
|
|
1567
1762
|
const start = Date.now();
|
|
1568
1763
|
try {
|
|
@@ -1965,6 +2160,32 @@ var GitHubClient = class {
|
|
|
1965
2160
|
}
|
|
1966
2161
|
return results;
|
|
1967
2162
|
}
|
|
2163
|
+
async createIssue(opts) {
|
|
2164
|
+
return this.http.github(
|
|
2165
|
+
`/repos/${opts.owner}/${opts.repo}/issues`,
|
|
2166
|
+
{
|
|
2167
|
+
method: "POST",
|
|
2168
|
+
body: {
|
|
2169
|
+
title: opts.title,
|
|
2170
|
+
...opts.body !== void 0 ? { body: opts.body } : {},
|
|
2171
|
+
...opts.labels?.length ? { labels: opts.labels } : {},
|
|
2172
|
+
...opts.assignees?.length ? { assignees: opts.assignees } : {}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
async createRepo(opts) {
|
|
2178
|
+
const path8 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
|
|
2179
|
+
return this.http.github(path8, {
|
|
2180
|
+
method: "POST",
|
|
2181
|
+
body: {
|
|
2182
|
+
name: opts.name,
|
|
2183
|
+
...opts.description !== void 0 ? { description: opts.description } : {},
|
|
2184
|
+
...opts.private !== void 0 ? { private: opts.private } : {},
|
|
2185
|
+
...opts.autoInit !== void 0 ? { auto_init: opts.autoInit } : {}
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
1968
2189
|
};
|
|
1969
2190
|
|
|
1970
2191
|
// src/services/github/commands.ts
|
|
@@ -1986,6 +2207,50 @@ function getClient3(program2, overrides) {
|
|
|
1986
2207
|
}
|
|
1987
2208
|
function registerGitHubCommands(program2) {
|
|
1988
2209
|
const gh = program2.command("github").description("GitHub repository operations").option("--owner <owner>", "GitHub owner (user or org)").option("--repo <repo>", "GitHub repository name");
|
|
2210
|
+
gh.command("create-repo").description("Create a new repository (personal or org)").requiredOption("--name <name>", "Repository name").option("--description <desc>", "Repository description").option("--private", "Create as a private repository").option("--auto-init", "Initialize with a README").action(async (opts) => {
|
|
2211
|
+
const start = Date.now();
|
|
2212
|
+
try {
|
|
2213
|
+
const globalOpts = gh.optsWithGlobals();
|
|
2214
|
+
const config = loadConfig({ configPath: globalOpts.config });
|
|
2215
|
+
const http = createHttpClient(config, Boolean(globalOpts.dryRun));
|
|
2216
|
+
const client = new GitHubClient(http);
|
|
2217
|
+
const owner = globalOpts.owner ?? "";
|
|
2218
|
+
const data = await client.createRepo({
|
|
2219
|
+
org: owner || void 0,
|
|
2220
|
+
name: opts.name,
|
|
2221
|
+
description: opts.description,
|
|
2222
|
+
private: opts.private,
|
|
2223
|
+
autoInit: opts.autoInit
|
|
2224
|
+
});
|
|
2225
|
+
success(data, "github", "create-repo", start);
|
|
2226
|
+
} catch (err) {
|
|
2227
|
+
fail(err, "github", "create-repo", start);
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
gh.command("create-issue").description("Create a GitHub issue").requiredOption("--title <title>", "Issue title").option("--body <body>", "Issue body (markdown)").option("--body-file <path>", "Read issue body from a file (use - for stdin); overridden by --body").option("--label <label>", "Label to apply (repeatable)", (v, acc) => {
|
|
2231
|
+
acc.push(v);
|
|
2232
|
+
return acc;
|
|
2233
|
+
}, []).option("--assignee <login>", "Assignee login (repeatable)", (v, acc) => {
|
|
2234
|
+
acc.push(v);
|
|
2235
|
+
return acc;
|
|
2236
|
+
}, []).action(async (opts) => {
|
|
2237
|
+
const start = Date.now();
|
|
2238
|
+
try {
|
|
2239
|
+
const { client, owner, repo } = getClient3(gh);
|
|
2240
|
+
const body = opts.body ?? resolveTextInput(void 0, opts.bodyFile, "body");
|
|
2241
|
+
const data = await client.createIssue({
|
|
2242
|
+
owner,
|
|
2243
|
+
repo,
|
|
2244
|
+
title: opts.title,
|
|
2245
|
+
body,
|
|
2246
|
+
labels: opts.label.length ? opts.label : void 0,
|
|
2247
|
+
assignees: opts.assignee.length ? opts.assignee : void 0
|
|
2248
|
+
});
|
|
2249
|
+
success(data, "github", "create-issue", start);
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
fail(err, "github", "create-issue", start);
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
1989
2254
|
gh.command("list-prs").description("List pull requests").addOption(new Option("--state <state>", "PR state").choices(["open", "closed", "all"]).default("open")).option("--head <branch>", "Filter by head branch (user:branch)").option("--base <branch>", "Filter by base branch").action(async (opts) => {
|
|
1990
2255
|
const start = Date.now();
|
|
1991
2256
|
try {
|
|
@@ -2012,18 +2277,19 @@ function registerGitHubCommands(program2) {
|
|
|
2012
2277
|
fail(err, "github", "get-pr", start);
|
|
2013
2278
|
}
|
|
2014
2279
|
});
|
|
2015
|
-
gh.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--head <branch>", "Source branch (or user:branch for forks)").option("--base <branch>", "Target branch (defaults to config or main)").option("--body <body>", "PR description").option("--draft", "Create as draft PR").option("--owner <owner>", "GitHub owner (overrides parent --owner)").option("--repo <repo>", "GitHub repository name (overrides parent --repo)").action(async (opts) => {
|
|
2280
|
+
gh.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--head <branch>", "Source branch (or user:branch for forks)").option("--base <branch>", "Target branch (defaults to config or main)").option("--body <body>", "PR description").option("--body-file <path>", "Read PR description from a file (use - for stdin); overridden by --body").option("--draft", "Create as draft PR").option("--owner <owner>", "GitHub owner (overrides parent --owner)").option("--repo <repo>", "GitHub repository name (overrides parent --repo)").action(async (opts) => {
|
|
2016
2281
|
const start = Date.now();
|
|
2017
2282
|
try {
|
|
2018
2283
|
const { client, owner, repo, config } = getClient3(gh, { owner: opts.owner, repo: opts.repo });
|
|
2019
2284
|
const base = opts.base ?? config.defaults.github?.targetBranch ?? "main";
|
|
2285
|
+
const body = opts.body ?? resolveTextInput(void 0, opts.bodyFile, "body");
|
|
2020
2286
|
const data = await client.createPR({
|
|
2021
2287
|
owner,
|
|
2022
2288
|
repo,
|
|
2023
2289
|
title: opts.title,
|
|
2024
2290
|
head: opts.head,
|
|
2025
2291
|
base,
|
|
2026
|
-
body
|
|
2292
|
+
body,
|
|
2027
2293
|
draft: opts.draft
|
|
2028
2294
|
});
|
|
2029
2295
|
success(data, "github", "create-pr", start);
|
|
@@ -2248,7 +2514,12 @@ function registerGitHubCommands(program2) {
|
|
|
2248
2514
|
});
|
|
2249
2515
|
}
|
|
2250
2516
|
|
|
2517
|
+
// src/services/confluence/commands.ts
|
|
2518
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2519
|
+
|
|
2251
2520
|
// src/services/confluence/client.ts
|
|
2521
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2522
|
+
import { basename as basename2 } from "path";
|
|
2252
2523
|
var API3 = "/rest/api";
|
|
2253
2524
|
var ConfluenceClient = class {
|
|
2254
2525
|
constructor(http) {
|
|
@@ -2393,9 +2664,52 @@ var ConfluenceClient = class {
|
|
|
2393
2664
|
});
|
|
2394
2665
|
});
|
|
2395
2666
|
}
|
|
2667
|
+
async uploadAttachment(pageId, filePath, comment) {
|
|
2668
|
+
const fileContent = readFileSync4(filePath);
|
|
2669
|
+
const fileName = basename2(filePath);
|
|
2670
|
+
const mimeType = guessMimeType(filePath);
|
|
2671
|
+
const formData = new FormData();
|
|
2672
|
+
formData.append("file", new Blob([fileContent], { type: mimeType }), fileName);
|
|
2673
|
+
if (comment) formData.append("comment", comment);
|
|
2674
|
+
const result = await this.http.confluenceUpload(
|
|
2675
|
+
`${API3}/content/${pageId}/child/attachment`,
|
|
2676
|
+
formData
|
|
2677
|
+
);
|
|
2678
|
+
return result.results;
|
|
2679
|
+
}
|
|
2680
|
+
async deleteAttachment(attachmentId) {
|
|
2681
|
+
return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
|
|
2682
|
+
}
|
|
2683
|
+
async getPageHistory(id) {
|
|
2684
|
+
return this.http.confluence(`${API3}/content/${id}/history`);
|
|
2685
|
+
}
|
|
2686
|
+
async convertToStorage(value, fromRepresentation = "markdown") {
|
|
2687
|
+
const result = await this.http.confluence(
|
|
2688
|
+
`${API3}/contentbody/convert/storage`,
|
|
2689
|
+
{
|
|
2690
|
+
method: "POST",
|
|
2691
|
+
body: { value, representation: fromRepresentation }
|
|
2692
|
+
}
|
|
2693
|
+
);
|
|
2694
|
+
return result.value;
|
|
2695
|
+
}
|
|
2396
2696
|
};
|
|
2397
2697
|
|
|
2398
2698
|
// src/services/confluence/commands.ts
|
|
2699
|
+
function xmlParseHint(err, bodyContent, convertedFromMarkdown = false) {
|
|
2700
|
+
if (!(err instanceof Error)) return null;
|
|
2701
|
+
const m = err.message.match(/at \[row,col\]=\{(\d+),(\d+)\}/);
|
|
2702
|
+
if (!m) return null;
|
|
2703
|
+
const row = parseInt(m[1], 10);
|
|
2704
|
+
const col = parseInt(m[2], 10);
|
|
2705
|
+
const lines = bodyContent.split("\n");
|
|
2706
|
+
const line = lines[row - 1];
|
|
2707
|
+
if (!line) return null;
|
|
2708
|
+
const pointer = " ".repeat(Math.max(0, col - 1)) + "^";
|
|
2709
|
+
const suffix = convertedFromMarkdown ? " (converted from Markdown)" : "";
|
|
2710
|
+
return `Offending line ${row}${suffix}: ${line}
|
|
2711
|
+
${pointer}`;
|
|
2712
|
+
}
|
|
2399
2713
|
function getClient4(program2) {
|
|
2400
2714
|
const opts = program2.optsWithGlobals();
|
|
2401
2715
|
const config = loadConfig({ configPath: opts.config });
|
|
@@ -2473,38 +2787,67 @@ function registerConfluenceCommands(program2) {
|
|
|
2473
2787
|
fail(err, "confluence", "search", start);
|
|
2474
2788
|
}
|
|
2475
2789
|
});
|
|
2476
|
-
confluence.command("create-page").description("Create a new Confluence page").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").
|
|
2790
|
+
confluence.command("create-page").description("Create a new Confluence page").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").option("--body <html>", "Page body (storage format HTML or Markdown when --markdown is set)").option("--body-file <path>", "Path to a file containing the page body ('-' = stdin)").option("--markdown", "Convert body from Markdown to storage format via Confluence API").option("--parent-id <id>", "Parent page ID (to nest under a page)").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
2477
2791
|
const start = Date.now();
|
|
2792
|
+
let bodyContent;
|
|
2478
2793
|
try {
|
|
2479
2794
|
const client = getClient4(program2);
|
|
2795
|
+
const rawBody = resolveTextInput(opts.body, opts.bodyFile, "body");
|
|
2796
|
+
if (rawBody === void 0) throw new PncliError("Must specify --body or --body-file", 1);
|
|
2797
|
+
bodyContent = opts.markdown ? await client.convertToStorage(rawBody) : rawBody;
|
|
2798
|
+
const representation = opts.markdown ? "storage" : opts.representation;
|
|
2480
2799
|
const data = await client.createPage({
|
|
2481
2800
|
spaceKey: opts.space,
|
|
2482
2801
|
title: opts.title,
|
|
2483
|
-
body:
|
|
2802
|
+
body: bodyContent,
|
|
2484
2803
|
parentId: opts.parentId,
|
|
2485
|
-
representation
|
|
2804
|
+
representation
|
|
2486
2805
|
});
|
|
2487
2806
|
success(data, "confluence", "create-page", start);
|
|
2488
2807
|
} catch (err) {
|
|
2489
|
-
|
|
2808
|
+
const hint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
|
|
2809
|
+
if (hint) {
|
|
2810
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2811
|
+
const status = err instanceof PncliError ? err.status : 1;
|
|
2812
|
+
const url = err instanceof PncliError ? err.url : void 0;
|
|
2813
|
+
fail(new PncliError(`${msg}
|
|
2814
|
+
|
|
2815
|
+
${hint}`, status, url), "confluence", "create-page", start);
|
|
2816
|
+
} else {
|
|
2817
|
+
fail(err, "confluence", "create-page", start);
|
|
2818
|
+
}
|
|
2490
2819
|
}
|
|
2491
2820
|
});
|
|
2492
|
-
confluence.command("update-page").description("Update a Confluence page (fetches current version automatically)").requiredOption("--id <page-id>", "Page ID").option("--title <title>", "New page title").option("--body <html>", "New page body (storage format HTML)").option("--status <status>", "Page status: current (default) or draft", "current").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
2821
|
+
confluence.command("update-page").description("Update a Confluence page (fetches current version automatically)").requiredOption("--id <page-id>", "Page ID").option("--title <title>", "New page title").option("--body <html>", "New page body (storage format HTML or Markdown when --markdown is set)").option("--body-file <path>", "Path to a file containing the new page body ('-' = stdin)").option("--markdown", "Convert body from Markdown to storage format via Confluence API").option("--status <status>", "Page status: current (default) or draft", "current").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
|
|
2493
2822
|
const start = Date.now();
|
|
2823
|
+
let bodyContent;
|
|
2494
2824
|
try {
|
|
2495
2825
|
const client = getClient4(program2);
|
|
2826
|
+
const rawBody = resolveTextInput(opts.body, opts.bodyFile, "body");
|
|
2827
|
+
bodyContent = rawBody !== void 0 && opts.markdown ? await client.convertToStorage(rawBody) : rawBody;
|
|
2828
|
+
const representation = bodyContent !== void 0 && opts.markdown ? "storage" : opts.representation;
|
|
2496
2829
|
const current = await client.getPage(opts.id, "version");
|
|
2497
2830
|
const nextVersion = current.version.number + 1;
|
|
2498
2831
|
const data = await client.updatePage(opts.id, {
|
|
2499
2832
|
version: nextVersion,
|
|
2500
2833
|
title: opts.title ?? current.title,
|
|
2501
|
-
body:
|
|
2834
|
+
body: bodyContent,
|
|
2502
2835
|
status: opts.status,
|
|
2503
|
-
representation
|
|
2836
|
+
representation
|
|
2504
2837
|
});
|
|
2505
2838
|
success(data, "confluence", "update-page", start);
|
|
2506
2839
|
} catch (err) {
|
|
2507
|
-
|
|
2840
|
+
const hint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
|
|
2841
|
+
if (hint) {
|
|
2842
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2843
|
+
const status = err instanceof PncliError ? err.status : 1;
|
|
2844
|
+
const url = err instanceof PncliError ? err.url : void 0;
|
|
2845
|
+
fail(new PncliError(`${msg}
|
|
2846
|
+
|
|
2847
|
+
${hint}`, status, url), "confluence", "update-page", start);
|
|
2848
|
+
} else {
|
|
2849
|
+
fail(err, "confluence", "update-page", start);
|
|
2850
|
+
}
|
|
2508
2851
|
}
|
|
2509
2852
|
});
|
|
2510
2853
|
confluence.command("delete-page").description("Delete a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
@@ -2591,6 +2934,37 @@ function registerConfluenceCommands(program2) {
|
|
|
2591
2934
|
fail(err, "confluence", "list-attachments", start);
|
|
2592
2935
|
}
|
|
2593
2936
|
});
|
|
2937
|
+
confluence.command("upload-attachment").description("Upload a file as an attachment to a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--file <path>", "Path to the file to upload").option("--comment <text>", "Optional comment to attach to the file version").action(async (opts) => {
|
|
2938
|
+
const start = Date.now();
|
|
2939
|
+
try {
|
|
2940
|
+
if (!existsSync2(opts.file)) throw new PncliError(`File not found: ${opts.file}`, 1);
|
|
2941
|
+
const client = getClient4(program2);
|
|
2942
|
+
const data = await client.uploadAttachment(opts.id, opts.file, opts.comment);
|
|
2943
|
+
success(data, "confluence", "upload-attachment", start);
|
|
2944
|
+
} catch (err) {
|
|
2945
|
+
fail(err, "confluence", "upload-attachment", start);
|
|
2946
|
+
}
|
|
2947
|
+
});
|
|
2948
|
+
confluence.command("delete-attachment").description("Delete an attachment from Confluence by its content ID (moves it to the trash; does not permanently purge it)").requiredOption("--id <attachment-id>", "Attachment content ID").action(async (opts) => {
|
|
2949
|
+
const start = Date.now();
|
|
2950
|
+
try {
|
|
2951
|
+
const client = getClient4(program2);
|
|
2952
|
+
await client.deleteAttachment(opts.id);
|
|
2953
|
+
success({ deleted: opts.id }, "confluence", "delete-attachment", start);
|
|
2954
|
+
} catch (err) {
|
|
2955
|
+
fail(err, "confluence", "delete-attachment", start);
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
confluence.command("get-page-history").description("Get the version history metadata for a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
|
|
2959
|
+
const start = Date.now();
|
|
2960
|
+
try {
|
|
2961
|
+
const client = getClient4(program2);
|
|
2962
|
+
const data = await client.getPageHistory(opts.id);
|
|
2963
|
+
success(data, "confluence", "get-page-history", start);
|
|
2964
|
+
} catch (err) {
|
|
2965
|
+
fail(err, "confluence", "get-page-history", start);
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2594
2968
|
}
|
|
2595
2969
|
|
|
2596
2970
|
// src/services/sonar/client.ts
|
|
@@ -4909,8 +5283,8 @@ import password from "@inquirer/password";
|
|
|
4909
5283
|
import confirm from "@inquirer/confirm";
|
|
4910
5284
|
|
|
4911
5285
|
// src/services/ado/client/work.ts
|
|
4912
|
-
import { readFileSync as
|
|
4913
|
-
import { basename as
|
|
5286
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5287
|
+
import { basename as basename3 } from "path";
|
|
4914
5288
|
var API6 = "7.1";
|
|
4915
5289
|
var API_PREVIEW = "7.1-preview.4";
|
|
4916
5290
|
var AdoWorkClient = class {
|
|
@@ -5025,8 +5399,8 @@ var AdoWorkClient = class {
|
|
|
5025
5399
|
);
|
|
5026
5400
|
}
|
|
5027
5401
|
async uploadAttachment(collection, workItemId, filePath, comment) {
|
|
5028
|
-
const fileContent =
|
|
5029
|
-
const fileName =
|
|
5402
|
+
const fileContent = readFileSync5(filePath);
|
|
5403
|
+
const fileName = basename3(filePath);
|
|
5030
5404
|
const attachment = await this.http.adoUpload(
|
|
5031
5405
|
`/${encodeURIComponent(collection)}/_apis/wit/attachments?fileName=${encodeURIComponent(fileName)}&api-version=${API6}`,
|
|
5032
5406
|
fileContent,
|
|
@@ -5077,32 +5451,32 @@ async function discoverTypes(client, collection, project) {
|
|
|
5077
5451
|
}
|
|
5078
5452
|
return result;
|
|
5079
5453
|
}
|
|
5454
|
+
var ADO_FIELD_ALIAS_CANDIDATES = [
|
|
5455
|
+
["title", "System.Title"],
|
|
5456
|
+
["state", "System.State"],
|
|
5457
|
+
["assignedto", "System.AssignedTo"],
|
|
5458
|
+
["assigned-to", "System.AssignedTo"],
|
|
5459
|
+
["description", "System.Description"],
|
|
5460
|
+
["areapath", "System.AreaPath"],
|
|
5461
|
+
["area-path", "System.AreaPath"],
|
|
5462
|
+
["iterationpath", "System.IterationPath"],
|
|
5463
|
+
["iteration", "System.IterationPath"],
|
|
5464
|
+
["tags", "System.Tags"],
|
|
5465
|
+
["priority", "Microsoft.VSTS.Common.Priority"],
|
|
5466
|
+
["severity", "Microsoft.VSTS.Common.Severity"],
|
|
5467
|
+
["effort", "Microsoft.VSTS.Scheduling.Effort"],
|
|
5468
|
+
["storypoints", "Microsoft.VSTS.Scheduling.StoryPoints"],
|
|
5469
|
+
["story-points", "Microsoft.VSTS.Scheduling.StoryPoints"],
|
|
5470
|
+
["remainingwork", "Microsoft.VSTS.Scheduling.RemainingWork"],
|
|
5471
|
+
["remaining", "Microsoft.VSTS.Scheduling.RemainingWork"],
|
|
5472
|
+
["acceptancecriteria", "Microsoft.VSTS.Common.AcceptanceCriteria"],
|
|
5473
|
+
["reprosteps", "Microsoft.VSTS.TCM.ReproSteps"],
|
|
5474
|
+
["foundinsource", "Microsoft.VSTS.Build.FoundIn"]
|
|
5475
|
+
];
|
|
5080
5476
|
function buildDefaultAliases(fields) {
|
|
5081
5477
|
const refNames = new Set(fields.map((f) => f.referenceName));
|
|
5082
|
-
const candidates = [
|
|
5083
|
-
["title", "System.Title"],
|
|
5084
|
-
["state", "System.State"],
|
|
5085
|
-
["assignedto", "System.AssignedTo"],
|
|
5086
|
-
["assigned-to", "System.AssignedTo"],
|
|
5087
|
-
["description", "System.Description"],
|
|
5088
|
-
["areapath", "System.AreaPath"],
|
|
5089
|
-
["area-path", "System.AreaPath"],
|
|
5090
|
-
["iterationpath", "System.IterationPath"],
|
|
5091
|
-
["iteration", "System.IterationPath"],
|
|
5092
|
-
["tags", "System.Tags"],
|
|
5093
|
-
["priority", "Microsoft.VSTS.Common.Priority"],
|
|
5094
|
-
["severity", "Microsoft.VSTS.Common.Severity"],
|
|
5095
|
-
["effort", "Microsoft.VSTS.Scheduling.Effort"],
|
|
5096
|
-
["storypoints", "Microsoft.VSTS.Scheduling.StoryPoints"],
|
|
5097
|
-
["story-points", "Microsoft.VSTS.Scheduling.StoryPoints"],
|
|
5098
|
-
["remainingwork", "Microsoft.VSTS.Scheduling.RemainingWork"],
|
|
5099
|
-
["remaining", "Microsoft.VSTS.Scheduling.RemainingWork"],
|
|
5100
|
-
["acceptancecriteria", "Microsoft.VSTS.Common.AcceptanceCriteria"],
|
|
5101
|
-
["reprosteps", "Microsoft.VSTS.TCM.ReproSteps"],
|
|
5102
|
-
["foundinsource", "Microsoft.VSTS.Build.FoundIn"]
|
|
5103
|
-
];
|
|
5104
5478
|
const aliases = {};
|
|
5105
|
-
for (const [alias, ref] of
|
|
5479
|
+
for (const [alias, ref] of ADO_FIELD_ALIAS_CANDIDATES) {
|
|
5106
5480
|
if (refNames.has(ref)) {
|
|
5107
5481
|
aliases[alias] = ref;
|
|
5108
5482
|
}
|
|
@@ -6350,6 +6724,12 @@ var AdoGitClient = class {
|
|
|
6350
6724
|
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repoId)}?api-version=${API7}`
|
|
6351
6725
|
);
|
|
6352
6726
|
}
|
|
6727
|
+
async createRepo(collection, project, name) {
|
|
6728
|
+
return this.http.ado(
|
|
6729
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/git/repositories?api-version=${API7}`,
|
|
6730
|
+
{ method: "POST", body: { name } }
|
|
6731
|
+
);
|
|
6732
|
+
}
|
|
6353
6733
|
// ── Pull Requests ─────────────────────────────────────────────────
|
|
6354
6734
|
async listPRs(collection, project, repo, opts = {}) {
|
|
6355
6735
|
return this.http.adoPaginate(async (token) => {
|
|
@@ -6604,14 +6984,34 @@ function parseFieldArgs2(fields) {
|
|
|
6604
6984
|
}
|
|
6605
6985
|
return result;
|
|
6606
6986
|
}
|
|
6987
|
+
var stripSeparators = (s) => s.toLowerCase().replace(/[\s-]+/g, "");
|
|
6988
|
+
var DEFAULT_ADO_FIELD_ALIASES = Object.fromEntries(ADO_FIELD_ALIAS_CANDIDATES);
|
|
6607
6989
|
function buildFieldPatch(fields, fieldAliases) {
|
|
6990
|
+
const aliases = { ...DEFAULT_ADO_FIELD_ALIASES, ...fieldAliases };
|
|
6608
6991
|
return Object.entries(fields).map(([key, value]) => {
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6992
|
+
let refName = aliases[key.toLowerCase()] ?? aliases[key];
|
|
6993
|
+
if (!refName) {
|
|
6994
|
+
const target = stripSeparators(key);
|
|
6995
|
+
const match = Object.entries(aliases).find(([aliasKey]) => stripSeparators(aliasKey) === target);
|
|
6996
|
+
refName = match?.[1];
|
|
6997
|
+
}
|
|
6998
|
+
return { op: "add", path: `/fields/${refName ?? key}`, value };
|
|
6999
|
+
});
|
|
7000
|
+
}
|
|
7001
|
+
var BUILTIN_WORK_FIELD_KEYS = /* @__PURE__ */ new Set(["title", "description", "assignee", "assignedto", "priority"]);
|
|
7002
|
+
function splitWorkFieldsDictionary(fields) {
|
|
7003
|
+
const builtin = {};
|
|
7004
|
+
const custom = {};
|
|
7005
|
+
for (const [rawKey, rawValue] of Object.entries(fields)) {
|
|
7006
|
+
const value = resolveAtFileRef(rawValue);
|
|
7007
|
+
const key = rawKey.toLowerCase().replace(/\s+/g, "");
|
|
7008
|
+
if (BUILTIN_WORK_FIELD_KEYS.has(key)) {
|
|
7009
|
+
builtin[key === "assignedto" ? "assignee" : key] = value;
|
|
7010
|
+
} else {
|
|
7011
|
+
custom[rawKey] = value;
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
7014
|
+
return { builtin, custom };
|
|
6615
7015
|
}
|
|
6616
7016
|
var PR_VOTE = {
|
|
6617
7017
|
APPROVE: 10,
|
|
@@ -6679,6 +7079,35 @@ function registerAdoProjectCommands(ado) {
|
|
|
6679
7079
|
// src/services/ado/commands/work.ts
|
|
6680
7080
|
import fs3 from "fs";
|
|
6681
7081
|
import path6 from "path";
|
|
7082
|
+
|
|
7083
|
+
// src/services/ado/input-schema.ts
|
|
7084
|
+
var ADO_WORK_INPUT_FILE_SCHEMA = {
|
|
7085
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
7086
|
+
title: "pncli Azure DevOps work item input file",
|
|
7087
|
+
description: "Shape accepted by --input-file on `pncli ado work create` / `work update`. CLI flags (--title, --description, --priority, --field, ...) override matching keys here; overridden keys are reported in meta.overrides.",
|
|
7088
|
+
type: "object",
|
|
7089
|
+
properties: {
|
|
7090
|
+
type: { type: "string", description: "Work item type, e.g. Bug, Task, User Story (create only)" },
|
|
7091
|
+
fields: {
|
|
7092
|
+
type: "object",
|
|
7093
|
+
description: 'Field dictionary. Built-in keys (case-insensitive): title, description, assignee, priority. Any other key (e.g. "Acceptance Criteria", or a raw reference name like "Microsoft.VSTS.Common.AcceptanceCriteria" or a custom "MyOrg.SomeField") resolves through the fieldAliases saved by `pncli ado work fields --save`, falling back to the key itself \u2014 custom fields never need to be pre-registered to be usable here. Any string value may be "@path/to/file" to read that field\'s content from a file instead of inlining it (useful for large rich-text fields like Description or Acceptance Criteria).',
|
|
7094
|
+
additionalProperties: true
|
|
7095
|
+
}
|
|
7096
|
+
},
|
|
7097
|
+
additionalProperties: false
|
|
7098
|
+
};
|
|
7099
|
+
var ADO_WORK_INPUT_FILE_EXAMPLE = {
|
|
7100
|
+
type: "Bug",
|
|
7101
|
+
fields: {
|
|
7102
|
+
Title: "Login broken on Safari",
|
|
7103
|
+
Description: "@desc.html",
|
|
7104
|
+
"Acceptance Criteria": "@ac.md",
|
|
7105
|
+
Priority: 2,
|
|
7106
|
+
"MyOrg.SomeCustomField": "x"
|
|
7107
|
+
}
|
|
7108
|
+
};
|
|
7109
|
+
|
|
7110
|
+
// src/services/ado/commands/work.ts
|
|
6682
7111
|
function registerAdoWorkCommands(ado) {
|
|
6683
7112
|
const work = ado.command("work").description("Azure DevOps work item operations");
|
|
6684
7113
|
work.command("get").description("Get a work item by ID").requiredOption("--id <n>", "Work item ID").action(async (opts) => {
|
|
@@ -6691,28 +7120,45 @@ function registerAdoWorkCommands(ado) {
|
|
|
6691
7120
|
fail(err, "ado", "work-get", start);
|
|
6692
7121
|
}
|
|
6693
7122
|
});
|
|
6694
|
-
work.command("create").description("Create a work item").
|
|
7123
|
+
work.command("create").description("Create a work item").option("--type <type>", "Work item type (e.g. Bug, Task, User Story) (required, unless supplied via --input-file)").option("--title <title>", "Work item title (required, unless supplied via --input-file)").option("--description <text>", "Description").option("--assignee <user>", "Assigned to (display name or email)").option("--priority <n>", "Priority (1-4)").option("--parent <id>", "Parent work item ID \u2014 creates a parent link after creation").option("--field <name=value>", "Additional field (repeatable)", (v, acc) => {
|
|
6695
7124
|
acc.push(v);
|
|
6696
7125
|
return acc;
|
|
6697
|
-
}, []).action(async (opts) => {
|
|
7126
|
+
}, []).option("--input-file <path>", "JSON file describing the work item ({ type, fields: {...} }); '-' = stdin. CLI flags override matching keys. See: pncli ado work schema").action(async (opts) => {
|
|
6698
7127
|
const start = Date.now();
|
|
6699
7128
|
try {
|
|
6700
7129
|
const globalOpts = ado.optsWithGlobals();
|
|
6701
7130
|
const config = loadConfig({ configPath: globalOpts.config });
|
|
6702
7131
|
const { collection, project, workClient } = getAdoContext(ado);
|
|
7132
|
+
const jsonInput = opts.inputFile ? readJsonInputFile(opts.inputFile) : void 0;
|
|
7133
|
+
const { builtin: jsonBuiltin, custom: jsonCustom } = jsonInput?.fields ? splitWorkFieldsDictionary(jsonInput.fields) : { builtin: {}, custom: {} };
|
|
7134
|
+
const type = opts.type ?? jsonInput?.type;
|
|
7135
|
+
if (!type) throw new PncliError('--type required (or "type" in --input-file)', 1);
|
|
7136
|
+
const flagBuiltin = {
|
|
7137
|
+
...opts.title !== void 0 ? { title: opts.title } : {},
|
|
7138
|
+
...opts.description !== void 0 ? { description: opts.description } : {},
|
|
7139
|
+
...opts.assignee !== void 0 ? { assignee: opts.assignee } : {},
|
|
7140
|
+
...opts.priority !== void 0 ? { priority: opts.priority } : {}
|
|
7141
|
+
};
|
|
7142
|
+
const { merged: builtinFields, overrides: builtinOverrides } = mergeWithOverrides(jsonBuiltin, flagBuiltin);
|
|
7143
|
+
if (!builtinFields.title) throw new PncliError('--title required (or "fields.Title" in --input-file)', 1);
|
|
7144
|
+
const flagCustom = parseFieldArgs2(opts.field);
|
|
7145
|
+
const { merged: customFields, overrides: customOverrides } = mergeWithOverrides(jsonCustom, flagCustom);
|
|
7146
|
+
const overrides = [...builtinOverrides, ...customOverrides];
|
|
7147
|
+
if (overrides.length) warn(`--input-file value(s) overridden by CLI flags: ${overrides.join(", ")}`);
|
|
7148
|
+
const hasContent = (v) => v !== void 0 && v !== "";
|
|
6703
7149
|
const builtIn = {
|
|
6704
|
-
"System.Title":
|
|
6705
|
-
...
|
|
6706
|
-
...
|
|
6707
|
-
...
|
|
7150
|
+
"System.Title": builtinFields.title,
|
|
7151
|
+
...hasContent(builtinFields.description) ? { "System.Description": builtinFields.description } : {},
|
|
7152
|
+
...hasContent(builtinFields.assignee) ? { "System.AssignedTo": builtinFields.assignee } : {},
|
|
7153
|
+
...hasContent(builtinFields.priority) ? { "Microsoft.VSTS.Common.Priority": builtinFields.priority } : {}
|
|
6708
7154
|
};
|
|
6709
|
-
const extra =
|
|
7155
|
+
const extra = buildFieldPatch(customFields, config.ado.fieldAliases);
|
|
6710
7156
|
const patch = [
|
|
6711
7157
|
...Object.entries(builtIn).map(([k, v]) => ({ op: "add", path: `/fields/${k}`, value: v })),
|
|
6712
7158
|
...extra
|
|
6713
7159
|
];
|
|
6714
|
-
const data = await workClient.createWorkItem(collection, project,
|
|
6715
|
-
success(data, "ado", "work-create", start);
|
|
7160
|
+
const data = await workClient.createWorkItem(collection, project, type, patch);
|
|
7161
|
+
success(data, "ado", "work-create", start, overrides);
|
|
6716
7162
|
if (opts.parent) {
|
|
6717
7163
|
try {
|
|
6718
7164
|
const baseUrl = config.ado.baseUrl?.replace(/\/$/, "");
|
|
@@ -6731,15 +7177,20 @@ function registerAdoWorkCommands(ado) {
|
|
|
6731
7177
|
work.command("update").description("Update a work item field").requiredOption("--id <n>", "Work item ID").option("--field <name=value>", "Field to update (repeatable)", (v, acc) => {
|
|
6732
7178
|
acc.push(v);
|
|
6733
7179
|
return acc;
|
|
6734
|
-
}, []).action(async (opts) => {
|
|
7180
|
+
}, []).option("--input-file <path>", "JSON file describing fields to update ({ fields: {...} }); '-' = stdin. CLI flags override matching keys. See: pncli ado work schema").action(async (opts) => {
|
|
6735
7181
|
const start = Date.now();
|
|
6736
7182
|
try {
|
|
6737
7183
|
const globalOpts = ado.optsWithGlobals();
|
|
6738
7184
|
const config = loadConfig({ configPath: globalOpts.config });
|
|
6739
7185
|
const { collection, workClient } = getAdoContext(ado);
|
|
6740
|
-
const
|
|
7186
|
+
const jsonInput = opts.inputFile ? readJsonInputFile(opts.inputFile) : void 0;
|
|
7187
|
+
const jsonFields = jsonInput?.fields ? Object.fromEntries(Object.entries(jsonInput.fields).map(([k, v]) => [k, resolveAtFileRef(v)])) : {};
|
|
7188
|
+
const flagFields = parseFieldArgs2(opts.field);
|
|
7189
|
+
const { merged, overrides } = mergeWithOverrides(jsonFields, flagFields);
|
|
7190
|
+
if (overrides.length) warn(`--input-file value(s) overridden by CLI flags: ${overrides.join(", ")}`);
|
|
7191
|
+
const patch = buildFieldPatch(merged, config.ado.fieldAliases);
|
|
6741
7192
|
const data = await workClient.updateWorkItem(collection, parseInt(opts.id, 10), patch);
|
|
6742
|
-
success(data, "ado", "work-update", start);
|
|
7193
|
+
success(data, "ado", "work-update", start, overrides);
|
|
6743
7194
|
} catch (err) {
|
|
6744
7195
|
fail(err, "ado", "work-update", start);
|
|
6745
7196
|
}
|
|
@@ -6950,6 +7401,14 @@ function registerAdoWorkCommands(ado) {
|
|
|
6950
7401
|
fail(err, "ado", "work-fields", start);
|
|
6951
7402
|
}
|
|
6952
7403
|
});
|
|
7404
|
+
work.command("schema").description("Print the --input-file JSON schema and an example for work create/update").option("--example-only", "Print only the runnable example JSON (no envelope) \u2014 pipeable straight into --input-file").action((opts) => {
|
|
7405
|
+
const start = Date.now();
|
|
7406
|
+
if (opts.exampleOnly) {
|
|
7407
|
+
writeRawOutput(JSON.stringify(ADO_WORK_INPUT_FILE_EXAMPLE, null, 2) + "\n");
|
|
7408
|
+
return;
|
|
7409
|
+
}
|
|
7410
|
+
success({ schema: ADO_WORK_INPUT_FILE_SCHEMA, example: ADO_WORK_INPUT_FILE_EXAMPLE }, "ado", "work-schema", start);
|
|
7411
|
+
});
|
|
6953
7412
|
}
|
|
6954
7413
|
|
|
6955
7414
|
// src/services/ado/commands/repo.ts
|
|
@@ -6978,6 +7437,16 @@ function registerAdoRepoCommands(ado) {
|
|
|
6978
7437
|
fail(err, "ado", "repo-list", start);
|
|
6979
7438
|
}
|
|
6980
7439
|
});
|
|
7440
|
+
repo.command("create").description("Create a new git repository in the project").requiredOption("--name <name>", "Repository name").action(async (opts) => {
|
|
7441
|
+
const start = Date.now();
|
|
7442
|
+
try {
|
|
7443
|
+
const { collection, project, gitClient } = getAdoContext(ado);
|
|
7444
|
+
const data = await gitClient.createRepo(collection, project, opts.name);
|
|
7445
|
+
success(data, "ado", "repo-create", start);
|
|
7446
|
+
} catch (err) {
|
|
7447
|
+
fail(err, "ado", "repo-create", start);
|
|
7448
|
+
}
|
|
7449
|
+
});
|
|
6981
7450
|
repo.command("get").description("Get a specific repository").action(async () => {
|
|
6982
7451
|
const start = Date.now();
|
|
6983
7452
|
try {
|
|
@@ -7219,11 +7688,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
7219
7688
|
const start = Date.now();
|
|
7220
7689
|
try {
|
|
7221
7690
|
const globalOpts = ado.optsWithGlobals();
|
|
7222
|
-
const { loadConfig: lc } = await import("./config-
|
|
7691
|
+
const { loadConfig: lc } = await import("./config-QN7KDHE4.js");
|
|
7223
7692
|
const config = lc({ configPath: globalOpts.config });
|
|
7224
7693
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7225
7694
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
7226
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
7695
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-DDY5DLF6.js");
|
|
7227
7696
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
7228
7697
|
const coreClient = new AdoCoreClient2(http);
|
|
7229
7698
|
const me = await coreClient.getConnectionData(collection);
|