hyper-pm 0.1.2 → 0.1.4
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/README.md +25 -19
- package/dist/index.cjs +612 -190
- package/dist/main.cjs +612 -190
- package/env.example +5 -0
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -7612,6 +7612,20 @@ var envSchema = external_exports.object({
|
|
|
7612
7612
|
HYPER_PM_AI_API_KEY: external_exports.string().optional(),
|
|
7613
7613
|
/** Optional override for hyper-pm JSONL event `actor` on CLI mutations. */
|
|
7614
7614
|
HYPER_PM_ACTOR: external_exports.string().optional(),
|
|
7615
|
+
/**
|
|
7616
|
+
* Optional `git user.name` for hyper-pm data-branch commits when repo git
|
|
7617
|
+
* identity is unset (falls back after `GIT_AUTHOR_*`, then a built-in default).
|
|
7618
|
+
*/
|
|
7619
|
+
HYPER_PM_GIT_USER_NAME: external_exports.string().optional(),
|
|
7620
|
+
/**
|
|
7621
|
+
* Optional `git user.email` for hyper-pm data-branch commits when repo git
|
|
7622
|
+
* identity is unset (falls back after `GIT_AUTHOR_*`, then a built-in default).
|
|
7623
|
+
*/
|
|
7624
|
+
HYPER_PM_GIT_USER_EMAIL: external_exports.string().optional(),
|
|
7625
|
+
/** Standard Git override for commit author name (optional). */
|
|
7626
|
+
GIT_AUTHOR_NAME: external_exports.string().optional(),
|
|
7627
|
+
/** Standard Git override for commit author email (optional). */
|
|
7628
|
+
GIT_AUTHOR_EMAIL: external_exports.string().optional(),
|
|
7615
7629
|
/**
|
|
7616
7630
|
* Absolute path to the hyper-pm CLI bundle (`main.cjs`) for hyper-pm-mcp and
|
|
7617
7631
|
* other integrations when auto-resolution from the `hyper-pm` package is insufficient.
|
|
@@ -12671,6 +12685,336 @@ var listActiveTicketSummaries = (projection, options) => {
|
|
|
12671
12685
|
});
|
|
12672
12686
|
};
|
|
12673
12687
|
|
|
12688
|
+
// src/lib/github-issue-body.ts
|
|
12689
|
+
var FENCE_JSON_RE = /```json\s*([\s\S]*?)```/i;
|
|
12690
|
+
var parseHyperPmFenceObject = (body) => {
|
|
12691
|
+
const fence = body.match(FENCE_JSON_RE);
|
|
12692
|
+
if (!fence?.[1]) return void 0;
|
|
12693
|
+
try {
|
|
12694
|
+
const data = JSON.parse(fence[1].trim());
|
|
12695
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
12696
|
+
return data;
|
|
12697
|
+
} catch {
|
|
12698
|
+
return void 0;
|
|
12699
|
+
}
|
|
12700
|
+
};
|
|
12701
|
+
var parseHyperPmIdFromIssueBody = (body) => {
|
|
12702
|
+
const meta = parseHyperPmFenceObject(body);
|
|
12703
|
+
if (meta === void 0) return void 0;
|
|
12704
|
+
const id = meta["hyper_pm_id"];
|
|
12705
|
+
return typeof id === "string" ? id : void 0;
|
|
12706
|
+
};
|
|
12707
|
+
var extractDescriptionBeforeFirstFence = (body) => {
|
|
12708
|
+
const fenceIdx = body.indexOf("```");
|
|
12709
|
+
if (fenceIdx === -1) {
|
|
12710
|
+
return body.trim();
|
|
12711
|
+
}
|
|
12712
|
+
return body.slice(0, fenceIdx).trim();
|
|
12713
|
+
};
|
|
12714
|
+
var inboundTicketPlanningPayloadFromFenceMeta = (meta) => {
|
|
12715
|
+
const out = {};
|
|
12716
|
+
if (Object.prototype.hasOwnProperty.call(meta, "priority")) {
|
|
12717
|
+
const v = meta["priority"];
|
|
12718
|
+
if (v === null) {
|
|
12719
|
+
out["priority"] = null;
|
|
12720
|
+
} else if (typeof v === "string") {
|
|
12721
|
+
const p = tryParseTicketPriority(v);
|
|
12722
|
+
if (p !== void 0) {
|
|
12723
|
+
out["priority"] = p;
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12726
|
+
}
|
|
12727
|
+
if (Object.prototype.hasOwnProperty.call(meta, "size")) {
|
|
12728
|
+
const v = meta["size"];
|
|
12729
|
+
if (v === null) {
|
|
12730
|
+
out["size"] = null;
|
|
12731
|
+
} else if (typeof v === "string") {
|
|
12732
|
+
const s = tryParseTicketSize(v);
|
|
12733
|
+
if (s !== void 0) {
|
|
12734
|
+
out["size"] = s;
|
|
12735
|
+
}
|
|
12736
|
+
}
|
|
12737
|
+
}
|
|
12738
|
+
if (Object.prototype.hasOwnProperty.call(meta, "estimate")) {
|
|
12739
|
+
const v = meta["estimate"];
|
|
12740
|
+
if (v === null) {
|
|
12741
|
+
out["estimate"] = null;
|
|
12742
|
+
} else if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
|
|
12743
|
+
out["estimate"] = v;
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
if (Object.prototype.hasOwnProperty.call(meta, "start_work_at")) {
|
|
12747
|
+
const v = meta["start_work_at"];
|
|
12748
|
+
if (v === null) {
|
|
12749
|
+
out["startWorkAt"] = null;
|
|
12750
|
+
} else if (typeof v === "string") {
|
|
12751
|
+
const t = v.trim();
|
|
12752
|
+
if (t !== "" && Number.isFinite(Date.parse(t))) {
|
|
12753
|
+
out["startWorkAt"] = t;
|
|
12754
|
+
}
|
|
12755
|
+
}
|
|
12756
|
+
}
|
|
12757
|
+
if (Object.prototype.hasOwnProperty.call(meta, "target_finish_at")) {
|
|
12758
|
+
const v = meta["target_finish_at"];
|
|
12759
|
+
if (v === null) {
|
|
12760
|
+
out["targetFinishAt"] = null;
|
|
12761
|
+
} else if (typeof v === "string") {
|
|
12762
|
+
const t = v.trim();
|
|
12763
|
+
if (t !== "" && Number.isFinite(Date.parse(t))) {
|
|
12764
|
+
out["targetFinishAt"] = t;
|
|
12765
|
+
}
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12768
|
+
return out;
|
|
12769
|
+
};
|
|
12770
|
+
var buildGithubIssueBody = (params) => {
|
|
12771
|
+
const meta = {
|
|
12772
|
+
hyper_pm_id: params.hyperPmId,
|
|
12773
|
+
type: params.type,
|
|
12774
|
+
parent_ids: params.parentIds
|
|
12775
|
+
};
|
|
12776
|
+
if (params.type === "ticket" && params.ticketPlanning !== void 0) {
|
|
12777
|
+
const p = params.ticketPlanning;
|
|
12778
|
+
if (p.priority !== void 0) {
|
|
12779
|
+
meta.priority = p.priority;
|
|
12780
|
+
}
|
|
12781
|
+
if (p.size !== void 0) {
|
|
12782
|
+
meta.size = p.size;
|
|
12783
|
+
}
|
|
12784
|
+
if (p.estimate !== void 0) {
|
|
12785
|
+
meta.estimate = p.estimate;
|
|
12786
|
+
}
|
|
12787
|
+
if (p.startWorkAt !== void 0) {
|
|
12788
|
+
meta.start_work_at = p.startWorkAt;
|
|
12789
|
+
}
|
|
12790
|
+
if (p.targetFinishAt !== void 0) {
|
|
12791
|
+
meta.target_finish_at = p.targetFinishAt;
|
|
12792
|
+
}
|
|
12793
|
+
}
|
|
12794
|
+
return `${params.description.trim()}
|
|
12795
|
+
|
|
12796
|
+
\`\`\`json
|
|
12797
|
+
${JSON.stringify(meta, null, 2)}
|
|
12798
|
+
\`\`\`
|
|
12799
|
+
`;
|
|
12800
|
+
};
|
|
12801
|
+
var ticketPlanningForGithubIssueBody = (ticket) => {
|
|
12802
|
+
const out = {};
|
|
12803
|
+
if (ticket.priority !== void 0) {
|
|
12804
|
+
out.priority = ticket.priority;
|
|
12805
|
+
}
|
|
12806
|
+
if (ticket.size !== void 0) {
|
|
12807
|
+
out.size = ticket.size;
|
|
12808
|
+
}
|
|
12809
|
+
if (ticket.estimate !== void 0) {
|
|
12810
|
+
out.estimate = ticket.estimate;
|
|
12811
|
+
}
|
|
12812
|
+
if (ticket.startWorkAt !== void 0) {
|
|
12813
|
+
out.startWorkAt = ticket.startWorkAt;
|
|
12814
|
+
}
|
|
12815
|
+
if (ticket.targetFinishAt !== void 0) {
|
|
12816
|
+
out.targetFinishAt = ticket.targetFinishAt;
|
|
12817
|
+
}
|
|
12818
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
12819
|
+
};
|
|
12820
|
+
|
|
12821
|
+
// src/lib/github-issue-labels.ts
|
|
12822
|
+
var RESERVED_LOWER = /* @__PURE__ */ new Set(["hyper-pm", "ticket"]);
|
|
12823
|
+
var GITHUB_LABEL_NAME_MAX_LENGTH = 50;
|
|
12824
|
+
var isReservedHyperPmGithubLabel = (name) => {
|
|
12825
|
+
return RESERVED_LOWER.has(name.trim().toLowerCase());
|
|
12826
|
+
};
|
|
12827
|
+
var labelNameFromGithubLabelEntry = (entry) => {
|
|
12828
|
+
if (typeof entry === "string") {
|
|
12829
|
+
const t = entry.trim();
|
|
12830
|
+
return t === "" ? void 0 : t;
|
|
12831
|
+
}
|
|
12832
|
+
if (typeof entry === "object" && entry !== null && "name" in entry) {
|
|
12833
|
+
const n = entry.name;
|
|
12834
|
+
if (typeof n !== "string") return void 0;
|
|
12835
|
+
const t = n.trim();
|
|
12836
|
+
return t === "" ? void 0 : t;
|
|
12837
|
+
}
|
|
12838
|
+
return void 0;
|
|
12839
|
+
};
|
|
12840
|
+
var ticketLabelsFromGithubIssueLabels = (labels) => {
|
|
12841
|
+
if (!Array.isArray(labels)) {
|
|
12842
|
+
return [];
|
|
12843
|
+
}
|
|
12844
|
+
const raw = [];
|
|
12845
|
+
for (const item of labels) {
|
|
12846
|
+
const n = labelNameFromGithubLabelEntry(item);
|
|
12847
|
+
if (n === void 0) continue;
|
|
12848
|
+
if (isReservedHyperPmGithubLabel(n)) continue;
|
|
12849
|
+
raw.push(n);
|
|
12850
|
+
}
|
|
12851
|
+
return normalizeTicketLabelList(raw);
|
|
12852
|
+
};
|
|
12853
|
+
var mergeOutboundGithubIssueLabelsForTicket = (ticketLabels) => {
|
|
12854
|
+
const base = ["hyper-pm", "ticket"];
|
|
12855
|
+
const seen = new Set(base.map((x) => x.toLowerCase()));
|
|
12856
|
+
const out = [...base];
|
|
12857
|
+
const norm = normalizeTicketLabelList(ticketLabels ?? []);
|
|
12858
|
+
for (const lab of norm) {
|
|
12859
|
+
if (lab.length > GITHUB_LABEL_NAME_MAX_LENGTH) {
|
|
12860
|
+
continue;
|
|
12861
|
+
}
|
|
12862
|
+
const low = lab.toLowerCase();
|
|
12863
|
+
if (seen.has(low)) continue;
|
|
12864
|
+
seen.add(low);
|
|
12865
|
+
out.push(lab);
|
|
12866
|
+
}
|
|
12867
|
+
return out;
|
|
12868
|
+
};
|
|
12869
|
+
|
|
12870
|
+
// src/cli/github-issue-import.ts
|
|
12871
|
+
var collectLinkedGithubIssueNumbers = (projection) => {
|
|
12872
|
+
const out = /* @__PURE__ */ new Set();
|
|
12873
|
+
for (const ticket of projection.tickets.values()) {
|
|
12874
|
+
if (ticket.deleted) continue;
|
|
12875
|
+
const n = ticket.githubIssueNumber;
|
|
12876
|
+
if (n !== void 0 && Number.isFinite(n)) {
|
|
12877
|
+
out.add(n);
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
return out;
|
|
12881
|
+
};
|
|
12882
|
+
var stripHyperPmGithubIssueTitle = (title) => title.replace(/^\[hyper-pm\]\s*/i, "").trim();
|
|
12883
|
+
var tryParseGithubImportListState = (raw) => {
|
|
12884
|
+
if (raw === void 0 || raw === "") return "all";
|
|
12885
|
+
const t = raw.trim().toLowerCase();
|
|
12886
|
+
if (t === "open" || t === "closed" || t === "all") return t;
|
|
12887
|
+
return void 0;
|
|
12888
|
+
};
|
|
12889
|
+
var parseGithubImportIssueNumberSet = (raw) => {
|
|
12890
|
+
if (raw === void 0 || raw.length === 0) return void 0;
|
|
12891
|
+
const out = /* @__PURE__ */ new Set();
|
|
12892
|
+
for (const piece of raw) {
|
|
12893
|
+
for (const token of piece.split(",")) {
|
|
12894
|
+
const t = token.trim();
|
|
12895
|
+
if (t === "") continue;
|
|
12896
|
+
const n = Number.parseInt(t, 10);
|
|
12897
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
12898
|
+
throw new Error(`Invalid --issue value: ${JSON.stringify(token)}`);
|
|
12899
|
+
}
|
|
12900
|
+
out.add(n);
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12903
|
+
if (out.size === 0) {
|
|
12904
|
+
throw new Error("No valid --issue numbers after parsing flags");
|
|
12905
|
+
}
|
|
12906
|
+
return out;
|
|
12907
|
+
};
|
|
12908
|
+
var ticketCreatePlanningFragmentFromFenceMeta = (meta) => {
|
|
12909
|
+
if (meta === void 0) return {};
|
|
12910
|
+
const src = inboundTicketPlanningPayloadFromFenceMeta(meta);
|
|
12911
|
+
const out = {};
|
|
12912
|
+
for (const [k, v] of Object.entries(src)) {
|
|
12913
|
+
if (v !== null && v !== void 0) {
|
|
12914
|
+
out[k] = v;
|
|
12915
|
+
}
|
|
12916
|
+
}
|
|
12917
|
+
return out;
|
|
12918
|
+
};
|
|
12919
|
+
var buildTicketCreatedPayloadBaseFromGithubIssue = (issue) => {
|
|
12920
|
+
const bodyText = issue.body ?? "";
|
|
12921
|
+
const title = stripHyperPmGithubIssueTitle(String(issue.title ?? ""));
|
|
12922
|
+
const desc = extractDescriptionBeforeFirstFence(bodyText);
|
|
12923
|
+
const payload = {
|
|
12924
|
+
title,
|
|
12925
|
+
body: desc
|
|
12926
|
+
};
|
|
12927
|
+
if (issue.state === "closed") {
|
|
12928
|
+
payload["state"] = "closed";
|
|
12929
|
+
}
|
|
12930
|
+
const assignee = assigneeFromGithubIssue(issue);
|
|
12931
|
+
if (assignee !== void 0 && assignee !== "") {
|
|
12932
|
+
payload["assignee"] = assignee;
|
|
12933
|
+
}
|
|
12934
|
+
const labels = ticketLabelsFromGithubIssueLabels(issue.labels);
|
|
12935
|
+
if (labels.length > 0) {
|
|
12936
|
+
payload["labels"] = labels;
|
|
12937
|
+
}
|
|
12938
|
+
const meta = parseHyperPmFenceObject(bodyText);
|
|
12939
|
+
Object.assign(payload, ticketCreatePlanningFragmentFromFenceMeta(meta));
|
|
12940
|
+
return payload;
|
|
12941
|
+
};
|
|
12942
|
+
var classifyGithubIssueForImport = (params) => {
|
|
12943
|
+
const num = params.issue.number;
|
|
12944
|
+
if (!Number.isFinite(num) || num < 1) {
|
|
12945
|
+
return {
|
|
12946
|
+
result: "skip",
|
|
12947
|
+
skip: { issueNumber: 0, reason: "issue_filter" }
|
|
12948
|
+
};
|
|
12949
|
+
}
|
|
12950
|
+
if (params.onlyIssueNumbers !== void 0 && !params.onlyIssueNumbers.has(num)) {
|
|
12951
|
+
return {
|
|
12952
|
+
result: "skip",
|
|
12953
|
+
skip: { issueNumber: num, reason: "issue_filter" }
|
|
12954
|
+
};
|
|
12955
|
+
}
|
|
12956
|
+
if (params.issue.pull_request !== void 0 && params.issue.pull_request !== null) {
|
|
12957
|
+
return {
|
|
12958
|
+
result: "skip",
|
|
12959
|
+
skip: { issueNumber: num, reason: "pull_request" }
|
|
12960
|
+
};
|
|
12961
|
+
}
|
|
12962
|
+
if (params.linkedNumbers.has(num)) {
|
|
12963
|
+
return {
|
|
12964
|
+
result: "skip",
|
|
12965
|
+
skip: { issueNumber: num, reason: "already_linked" }
|
|
12966
|
+
};
|
|
12967
|
+
}
|
|
12968
|
+
const body = params.issue.body ?? "";
|
|
12969
|
+
const hyperPmId = parseHyperPmIdFromIssueBody(body);
|
|
12970
|
+
if (hyperPmId !== void 0 && hyperPmId.trim() !== "") {
|
|
12971
|
+
const row = params.projection.tickets.get(hyperPmId);
|
|
12972
|
+
if (row !== void 0 && !row.deleted) {
|
|
12973
|
+
return {
|
|
12974
|
+
result: "skip",
|
|
12975
|
+
skip: { issueNumber: num, reason: "body_hyper_pm_existing_ticket" }
|
|
12976
|
+
};
|
|
12977
|
+
}
|
|
12978
|
+
return {
|
|
12979
|
+
result: "skip",
|
|
12980
|
+
skip: { issueNumber: num, reason: "body_hyper_pm_orphan_ref" }
|
|
12981
|
+
};
|
|
12982
|
+
}
|
|
12983
|
+
return {
|
|
12984
|
+
result: "candidate",
|
|
12985
|
+
ticketCreatedPayloadBase: buildTicketCreatedPayloadBaseFromGithubIssue(
|
|
12986
|
+
params.issue
|
|
12987
|
+
)
|
|
12988
|
+
};
|
|
12989
|
+
};
|
|
12990
|
+
var partitionGithubIssuesForImport = (params) => {
|
|
12991
|
+
const linkedNumbers = collectLinkedGithubIssueNumbers(params.projection);
|
|
12992
|
+
const candidates = [];
|
|
12993
|
+
const skipped = [];
|
|
12994
|
+
for (const issue of params.issues) {
|
|
12995
|
+
const r = classifyGithubIssueForImport({
|
|
12996
|
+
projection: params.projection,
|
|
12997
|
+
linkedNumbers,
|
|
12998
|
+
onlyIssueNumbers: params.onlyIssueNumbers,
|
|
12999
|
+
issue
|
|
13000
|
+
});
|
|
13001
|
+
if (r.result === "skip") {
|
|
13002
|
+
skipped.push(r.skip);
|
|
13003
|
+
} else {
|
|
13004
|
+
candidates.push({
|
|
13005
|
+
issueNumber: issue.number,
|
|
13006
|
+
ticketCreatedPayloadBase: r.ticketCreatedPayloadBase
|
|
13007
|
+
});
|
|
13008
|
+
}
|
|
13009
|
+
}
|
|
13010
|
+
return { candidates, skipped };
|
|
13011
|
+
};
|
|
13012
|
+
var mergeTicketImportCreatePayload = (ticketId, base, storyId) => {
|
|
13013
|
+
const storyTrimmed = storyId !== void 0 && storyId !== "" ? storyId.trim() : void 0;
|
|
13014
|
+
const storyPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? { storyId: storyTrimmed } : {};
|
|
13015
|
+
return { id: ticketId, ...base, ...storyPayload };
|
|
13016
|
+
};
|
|
13017
|
+
|
|
12674
13018
|
// src/config/hyper-pm-config.ts
|
|
12675
13019
|
var hyperPmConfigSchema = external_exports.object({
|
|
12676
13020
|
schema: external_exports.literal(1),
|
|
@@ -13023,6 +13367,33 @@ var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
|
|
|
13023
13367
|
}
|
|
13024
13368
|
};
|
|
13025
13369
|
|
|
13370
|
+
// src/git/resolve-effective-git-author-for-data-commit.ts
|
|
13371
|
+
var defaultDataCommitName = "hyper-pm";
|
|
13372
|
+
var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
|
|
13373
|
+
var tryReadGitConfigUserName = async (cwd, runGit2) => {
|
|
13374
|
+
try {
|
|
13375
|
+
const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
|
|
13376
|
+
return stdout.trim();
|
|
13377
|
+
} catch {
|
|
13378
|
+
return "";
|
|
13379
|
+
}
|
|
13380
|
+
};
|
|
13381
|
+
var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
|
|
13382
|
+
try {
|
|
13383
|
+
const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
|
|
13384
|
+
return stdout.trim();
|
|
13385
|
+
} catch {
|
|
13386
|
+
return "";
|
|
13387
|
+
}
|
|
13388
|
+
};
|
|
13389
|
+
var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
|
|
13390
|
+
const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
|
|
13391
|
+
const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
|
|
13392
|
+
const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
|
|
13393
|
+
const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
|
|
13394
|
+
return { name, email };
|
|
13395
|
+
};
|
|
13396
|
+
|
|
13026
13397
|
// src/run/commit-data.ts
|
|
13027
13398
|
var maxActorSuffixLen = 60;
|
|
13028
13399
|
var formatDataBranchCommitMessage = (base, actorSuffix) => {
|
|
@@ -13034,15 +13405,55 @@ var formatDataBranchCommitMessage = (base, actorSuffix) => {
|
|
|
13034
13405
|
const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
|
|
13035
13406
|
return `${base} (${suffix})`;
|
|
13036
13407
|
};
|
|
13037
|
-
var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2) => {
|
|
13408
|
+
var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
|
|
13038
13409
|
const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
|
|
13039
13410
|
if (!stdout.trim()) return;
|
|
13040
13411
|
await runGit2(worktreePath, ["add", "."]);
|
|
13041
|
-
|
|
13042
|
-
}
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13412
|
+
const authorEnv = opts?.authorEnv ?? env;
|
|
13413
|
+
const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
|
|
13414
|
+
worktreePath,
|
|
13415
|
+
runGit2,
|
|
13416
|
+
authorEnv
|
|
13417
|
+
);
|
|
13418
|
+
await runGit2(worktreePath, [
|
|
13419
|
+
"-c",
|
|
13420
|
+
`user.name=${name}`,
|
|
13421
|
+
"-c",
|
|
13422
|
+
`user.email=${email}`,
|
|
13423
|
+
"commit",
|
|
13424
|
+
"-m",
|
|
13425
|
+
message
|
|
13426
|
+
]);
|
|
13427
|
+
};
|
|
13428
|
+
|
|
13429
|
+
// src/run/push-data-branch.ts
|
|
13430
|
+
var firstLineFromUnknown = (err) => {
|
|
13431
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
13432
|
+
const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
|
|
13433
|
+
return line.length > 0 ? line : "git error";
|
|
13434
|
+
};
|
|
13435
|
+
var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
|
|
13436
|
+
try {
|
|
13437
|
+
await runGit2(worktreePath, ["remote", "get-url", remote]);
|
|
13438
|
+
} catch (e) {
|
|
13439
|
+
return {
|
|
13440
|
+
status: "skipped_no_remote",
|
|
13441
|
+
detail: firstLineFromUnknown(e)
|
|
13442
|
+
};
|
|
13443
|
+
}
|
|
13444
|
+
try {
|
|
13445
|
+
await runGit2(worktreePath, ["push", "-u", remote, branch]);
|
|
13446
|
+
return { status: "pushed" };
|
|
13447
|
+
} catch (e) {
|
|
13448
|
+
return {
|
|
13449
|
+
status: "failed",
|
|
13450
|
+
detail: firstLineFromUnknown(e)
|
|
13451
|
+
};
|
|
13452
|
+
}
|
|
13453
|
+
};
|
|
13454
|
+
|
|
13455
|
+
// src/storage/append-event.ts
|
|
13456
|
+
var import_promises5 = require("node:fs/promises");
|
|
13046
13457
|
var import_node_path5 = require("node:path");
|
|
13047
13458
|
|
|
13048
13459
|
// src/storage/event-path.ts
|
|
@@ -13755,188 +14166,6 @@ var defaultGithubPrActivitySyncDeps = (params) => ({
|
|
|
13755
14166
|
}
|
|
13756
14167
|
});
|
|
13757
14168
|
|
|
13758
|
-
// src/lib/github-issue-body.ts
|
|
13759
|
-
var FENCE_JSON_RE = /```json\s*([\s\S]*?)```/i;
|
|
13760
|
-
var parseHyperPmFenceObject = (body) => {
|
|
13761
|
-
const fence = body.match(FENCE_JSON_RE);
|
|
13762
|
-
if (!fence?.[1]) return void 0;
|
|
13763
|
-
try {
|
|
13764
|
-
const data = JSON.parse(fence[1].trim());
|
|
13765
|
-
if (typeof data !== "object" || data === null) return void 0;
|
|
13766
|
-
return data;
|
|
13767
|
-
} catch {
|
|
13768
|
-
return void 0;
|
|
13769
|
-
}
|
|
13770
|
-
};
|
|
13771
|
-
var parseHyperPmIdFromIssueBody = (body) => {
|
|
13772
|
-
const meta = parseHyperPmFenceObject(body);
|
|
13773
|
-
if (meta === void 0) return void 0;
|
|
13774
|
-
const id = meta["hyper_pm_id"];
|
|
13775
|
-
return typeof id === "string" ? id : void 0;
|
|
13776
|
-
};
|
|
13777
|
-
var extractDescriptionBeforeFirstFence = (body) => {
|
|
13778
|
-
const fenceIdx = body.indexOf("```");
|
|
13779
|
-
if (fenceIdx === -1) {
|
|
13780
|
-
return body.trim();
|
|
13781
|
-
}
|
|
13782
|
-
return body.slice(0, fenceIdx).trim();
|
|
13783
|
-
};
|
|
13784
|
-
var inboundTicketPlanningPayloadFromFenceMeta = (meta) => {
|
|
13785
|
-
const out = {};
|
|
13786
|
-
if (Object.prototype.hasOwnProperty.call(meta, "priority")) {
|
|
13787
|
-
const v = meta["priority"];
|
|
13788
|
-
if (v === null) {
|
|
13789
|
-
out["priority"] = null;
|
|
13790
|
-
} else if (typeof v === "string") {
|
|
13791
|
-
const p = tryParseTicketPriority(v);
|
|
13792
|
-
if (p !== void 0) {
|
|
13793
|
-
out["priority"] = p;
|
|
13794
|
-
}
|
|
13795
|
-
}
|
|
13796
|
-
}
|
|
13797
|
-
if (Object.prototype.hasOwnProperty.call(meta, "size")) {
|
|
13798
|
-
const v = meta["size"];
|
|
13799
|
-
if (v === null) {
|
|
13800
|
-
out["size"] = null;
|
|
13801
|
-
} else if (typeof v === "string") {
|
|
13802
|
-
const s = tryParseTicketSize(v);
|
|
13803
|
-
if (s !== void 0) {
|
|
13804
|
-
out["size"] = s;
|
|
13805
|
-
}
|
|
13806
|
-
}
|
|
13807
|
-
}
|
|
13808
|
-
if (Object.prototype.hasOwnProperty.call(meta, "estimate")) {
|
|
13809
|
-
const v = meta["estimate"];
|
|
13810
|
-
if (v === null) {
|
|
13811
|
-
out["estimate"] = null;
|
|
13812
|
-
} else if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
|
|
13813
|
-
out["estimate"] = v;
|
|
13814
|
-
}
|
|
13815
|
-
}
|
|
13816
|
-
if (Object.prototype.hasOwnProperty.call(meta, "start_work_at")) {
|
|
13817
|
-
const v = meta["start_work_at"];
|
|
13818
|
-
if (v === null) {
|
|
13819
|
-
out["startWorkAt"] = null;
|
|
13820
|
-
} else if (typeof v === "string") {
|
|
13821
|
-
const t = v.trim();
|
|
13822
|
-
if (t !== "" && Number.isFinite(Date.parse(t))) {
|
|
13823
|
-
out["startWorkAt"] = t;
|
|
13824
|
-
}
|
|
13825
|
-
}
|
|
13826
|
-
}
|
|
13827
|
-
if (Object.prototype.hasOwnProperty.call(meta, "target_finish_at")) {
|
|
13828
|
-
const v = meta["target_finish_at"];
|
|
13829
|
-
if (v === null) {
|
|
13830
|
-
out["targetFinishAt"] = null;
|
|
13831
|
-
} else if (typeof v === "string") {
|
|
13832
|
-
const t = v.trim();
|
|
13833
|
-
if (t !== "" && Number.isFinite(Date.parse(t))) {
|
|
13834
|
-
out["targetFinishAt"] = t;
|
|
13835
|
-
}
|
|
13836
|
-
}
|
|
13837
|
-
}
|
|
13838
|
-
return out;
|
|
13839
|
-
};
|
|
13840
|
-
var buildGithubIssueBody = (params) => {
|
|
13841
|
-
const meta = {
|
|
13842
|
-
hyper_pm_id: params.hyperPmId,
|
|
13843
|
-
type: params.type,
|
|
13844
|
-
parent_ids: params.parentIds
|
|
13845
|
-
};
|
|
13846
|
-
if (params.type === "ticket" && params.ticketPlanning !== void 0) {
|
|
13847
|
-
const p = params.ticketPlanning;
|
|
13848
|
-
if (p.priority !== void 0) {
|
|
13849
|
-
meta.priority = p.priority;
|
|
13850
|
-
}
|
|
13851
|
-
if (p.size !== void 0) {
|
|
13852
|
-
meta.size = p.size;
|
|
13853
|
-
}
|
|
13854
|
-
if (p.estimate !== void 0) {
|
|
13855
|
-
meta.estimate = p.estimate;
|
|
13856
|
-
}
|
|
13857
|
-
if (p.startWorkAt !== void 0) {
|
|
13858
|
-
meta.start_work_at = p.startWorkAt;
|
|
13859
|
-
}
|
|
13860
|
-
if (p.targetFinishAt !== void 0) {
|
|
13861
|
-
meta.target_finish_at = p.targetFinishAt;
|
|
13862
|
-
}
|
|
13863
|
-
}
|
|
13864
|
-
return `${params.description.trim()}
|
|
13865
|
-
|
|
13866
|
-
\`\`\`json
|
|
13867
|
-
${JSON.stringify(meta, null, 2)}
|
|
13868
|
-
\`\`\`
|
|
13869
|
-
`;
|
|
13870
|
-
};
|
|
13871
|
-
var ticketPlanningForGithubIssueBody = (ticket) => {
|
|
13872
|
-
const out = {};
|
|
13873
|
-
if (ticket.priority !== void 0) {
|
|
13874
|
-
out.priority = ticket.priority;
|
|
13875
|
-
}
|
|
13876
|
-
if (ticket.size !== void 0) {
|
|
13877
|
-
out.size = ticket.size;
|
|
13878
|
-
}
|
|
13879
|
-
if (ticket.estimate !== void 0) {
|
|
13880
|
-
out.estimate = ticket.estimate;
|
|
13881
|
-
}
|
|
13882
|
-
if (ticket.startWorkAt !== void 0) {
|
|
13883
|
-
out.startWorkAt = ticket.startWorkAt;
|
|
13884
|
-
}
|
|
13885
|
-
if (ticket.targetFinishAt !== void 0) {
|
|
13886
|
-
out.targetFinishAt = ticket.targetFinishAt;
|
|
13887
|
-
}
|
|
13888
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
13889
|
-
};
|
|
13890
|
-
|
|
13891
|
-
// src/lib/github-issue-labels.ts
|
|
13892
|
-
var RESERVED_LOWER = /* @__PURE__ */ new Set(["hyper-pm", "ticket"]);
|
|
13893
|
-
var GITHUB_LABEL_NAME_MAX_LENGTH = 50;
|
|
13894
|
-
var isReservedHyperPmGithubLabel = (name) => {
|
|
13895
|
-
return RESERVED_LOWER.has(name.trim().toLowerCase());
|
|
13896
|
-
};
|
|
13897
|
-
var labelNameFromGithubLabelEntry = (entry) => {
|
|
13898
|
-
if (typeof entry === "string") {
|
|
13899
|
-
const t = entry.trim();
|
|
13900
|
-
return t === "" ? void 0 : t;
|
|
13901
|
-
}
|
|
13902
|
-
if (typeof entry === "object" && entry !== null && "name" in entry) {
|
|
13903
|
-
const n = entry.name;
|
|
13904
|
-
if (typeof n !== "string") return void 0;
|
|
13905
|
-
const t = n.trim();
|
|
13906
|
-
return t === "" ? void 0 : t;
|
|
13907
|
-
}
|
|
13908
|
-
return void 0;
|
|
13909
|
-
};
|
|
13910
|
-
var ticketLabelsFromGithubIssueLabels = (labels) => {
|
|
13911
|
-
if (!Array.isArray(labels)) {
|
|
13912
|
-
return [];
|
|
13913
|
-
}
|
|
13914
|
-
const raw = [];
|
|
13915
|
-
for (const item of labels) {
|
|
13916
|
-
const n = labelNameFromGithubLabelEntry(item);
|
|
13917
|
-
if (n === void 0) continue;
|
|
13918
|
-
if (isReservedHyperPmGithubLabel(n)) continue;
|
|
13919
|
-
raw.push(n);
|
|
13920
|
-
}
|
|
13921
|
-
return normalizeTicketLabelList(raw);
|
|
13922
|
-
};
|
|
13923
|
-
var mergeOutboundGithubIssueLabelsForTicket = (ticketLabels) => {
|
|
13924
|
-
const base = ["hyper-pm", "ticket"];
|
|
13925
|
-
const seen = new Set(base.map((x) => x.toLowerCase()));
|
|
13926
|
-
const out = [...base];
|
|
13927
|
-
const norm = normalizeTicketLabelList(ticketLabels ?? []);
|
|
13928
|
-
for (const lab of norm) {
|
|
13929
|
-
if (lab.length > GITHUB_LABEL_NAME_MAX_LENGTH) {
|
|
13930
|
-
continue;
|
|
13931
|
-
}
|
|
13932
|
-
const low = lab.toLowerCase();
|
|
13933
|
-
if (seen.has(low)) continue;
|
|
13934
|
-
seen.add(low);
|
|
13935
|
-
out.push(lab);
|
|
13936
|
-
}
|
|
13937
|
-
return out;
|
|
13938
|
-
};
|
|
13939
|
-
|
|
13940
14169
|
// src/sync/github-inbound-actor.ts
|
|
13941
14170
|
var githubInboundActorFromIssue = (issue) => {
|
|
13942
14171
|
const login = issue.user?.login?.trim();
|
|
@@ -15218,7 +15447,174 @@ ${body}`
|
|
|
15218
15447
|
return { id: o.id, deleted: true };
|
|
15219
15448
|
});
|
|
15220
15449
|
});
|
|
15221
|
-
|
|
15450
|
+
ticket.command("import-github").description(
|
|
15451
|
+
"Create local tickets for GitHub issues not yet represented in hyper-pm"
|
|
15452
|
+
).option("--dry-run", "list import candidates without writing events", false).option(
|
|
15453
|
+
"--story <id>",
|
|
15454
|
+
"optional story id applied to every imported ticket (must exist)"
|
|
15455
|
+
).option(
|
|
15456
|
+
"--state <s>",
|
|
15457
|
+
"GitHub list filter: open | closed | all (default all)",
|
|
15458
|
+
"all"
|
|
15459
|
+
).option(
|
|
15460
|
+
"--issue <n>",
|
|
15461
|
+
"only consider these issue numbers (repeatable or comma-separated)",
|
|
15462
|
+
(value, previous) => [...previous, value],
|
|
15463
|
+
[]
|
|
15464
|
+
).action(async function() {
|
|
15465
|
+
const g = readGlobals(this);
|
|
15466
|
+
const o = this.opts();
|
|
15467
|
+
const listState = tryParseGithubImportListState(o.state);
|
|
15468
|
+
if (listState === void 0) {
|
|
15469
|
+
deps.error(
|
|
15470
|
+
`Invalid --state ${JSON.stringify(o.state)} (use open, closed, or all)`
|
|
15471
|
+
);
|
|
15472
|
+
deps.exit(ExitCode.UserError);
|
|
15473
|
+
}
|
|
15474
|
+
let onlyIssueNumbers;
|
|
15475
|
+
try {
|
|
15476
|
+
onlyIssueNumbers = parseGithubImportIssueNumberSet(o.issue);
|
|
15477
|
+
} catch (e) {
|
|
15478
|
+
deps.error(e instanceof Error ? e.message : String(e));
|
|
15479
|
+
deps.exit(ExitCode.UserError);
|
|
15480
|
+
}
|
|
15481
|
+
const repoRoot = await resolveRepoRoot(g.repo);
|
|
15482
|
+
const cfg = await loadMergedConfig(repoRoot, g);
|
|
15483
|
+
if (cfg.issueMapping !== "ticket") {
|
|
15484
|
+
deps.error(
|
|
15485
|
+
'ticket import-github requires config issueMapping "ticket" (other mappings are not supported yet).'
|
|
15486
|
+
);
|
|
15487
|
+
deps.exit(ExitCode.UserError);
|
|
15488
|
+
}
|
|
15489
|
+
const githubToken = await resolveGithubTokenForSync({
|
|
15490
|
+
envToken: env.GITHUB_TOKEN,
|
|
15491
|
+
cwd: repoRoot
|
|
15492
|
+
});
|
|
15493
|
+
if (!githubToken) {
|
|
15494
|
+
deps.error(
|
|
15495
|
+
"GitHub auth required: set GITHUB_TOKEN or run `gh auth login`"
|
|
15496
|
+
);
|
|
15497
|
+
deps.exit(ExitCode.EnvironmentAuth);
|
|
15498
|
+
}
|
|
15499
|
+
const actor = await resolveCliActor({
|
|
15500
|
+
repoRoot,
|
|
15501
|
+
cliActor: g.actor,
|
|
15502
|
+
envActor: env.HYPER_PM_ACTOR
|
|
15503
|
+
});
|
|
15504
|
+
const tmpBase = g.tempDir ?? env.TMPDIR ?? (0, import_node_os2.tmpdir)();
|
|
15505
|
+
const session = await openDataBranchWorktree({
|
|
15506
|
+
repoRoot,
|
|
15507
|
+
dataBranch: cfg.dataBranch,
|
|
15508
|
+
tmpBase,
|
|
15509
|
+
keepWorktree: g.keepWorktree,
|
|
15510
|
+
runGit
|
|
15511
|
+
});
|
|
15512
|
+
try {
|
|
15513
|
+
const lines = await readAllEventLines(session.worktreePath);
|
|
15514
|
+
const proj = replayEvents(lines);
|
|
15515
|
+
const storyRaw = o.story;
|
|
15516
|
+
const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
|
|
15517
|
+
if (storyTrimmed !== void 0 && storyTrimmed !== "") {
|
|
15518
|
+
const storyRow = proj.stories.get(storyTrimmed);
|
|
15519
|
+
if (!storyRow || storyRow.deleted) {
|
|
15520
|
+
deps.error(`Story not found: ${storyTrimmed}`);
|
|
15521
|
+
deps.exit(ExitCode.UserError);
|
|
15522
|
+
}
|
|
15523
|
+
}
|
|
15524
|
+
try {
|
|
15525
|
+
const gitDerivedSlug = await tryReadGithubOwnerRepoSlugFromGit({
|
|
15526
|
+
repoRoot,
|
|
15527
|
+
remote: cfg.remote,
|
|
15528
|
+
runGit
|
|
15529
|
+
});
|
|
15530
|
+
const { owner, repo: repo2 } = resolveGithubRepo(
|
|
15531
|
+
cfg,
|
|
15532
|
+
env.GITHUB_REPO,
|
|
15533
|
+
gitDerivedSlug
|
|
15534
|
+
);
|
|
15535
|
+
const octokit = new Octokit2({ auth: githubToken });
|
|
15536
|
+
const issues = await octokit.paginate(
|
|
15537
|
+
octokit.rest.issues.listForRepo,
|
|
15538
|
+
{
|
|
15539
|
+
owner,
|
|
15540
|
+
repo: repo2,
|
|
15541
|
+
state: listState,
|
|
15542
|
+
per_page: 100
|
|
15543
|
+
}
|
|
15544
|
+
);
|
|
15545
|
+
const { candidates, skipped } = partitionGithubIssuesForImport({
|
|
15546
|
+
projection: proj,
|
|
15547
|
+
issues,
|
|
15548
|
+
onlyIssueNumbers
|
|
15549
|
+
});
|
|
15550
|
+
if (o.dryRun) {
|
|
15551
|
+
deps.log(
|
|
15552
|
+
formatOutput(g.format, {
|
|
15553
|
+
ok: true,
|
|
15554
|
+
dryRun: true,
|
|
15555
|
+
candidates,
|
|
15556
|
+
skipped
|
|
15557
|
+
})
|
|
15558
|
+
);
|
|
15559
|
+
} else {
|
|
15560
|
+
const imported = [];
|
|
15561
|
+
for (const c of candidates) {
|
|
15562
|
+
const ticketId = ulid();
|
|
15563
|
+
const createPayload = mergeTicketImportCreatePayload(
|
|
15564
|
+
ticketId,
|
|
15565
|
+
c.ticketCreatedPayloadBase,
|
|
15566
|
+
storyTrimmed
|
|
15567
|
+
);
|
|
15568
|
+
const createdEvt = makeEvent(
|
|
15569
|
+
"TicketCreated",
|
|
15570
|
+
createPayload,
|
|
15571
|
+
deps.clock,
|
|
15572
|
+
actor
|
|
15573
|
+
);
|
|
15574
|
+
await appendEventLine(
|
|
15575
|
+
session.worktreePath,
|
|
15576
|
+
createdEvt,
|
|
15577
|
+
deps.clock
|
|
15578
|
+
);
|
|
15579
|
+
const linkEvt = makeEvent(
|
|
15580
|
+
"GithubIssueLinked",
|
|
15581
|
+
{ ticketId, issueNumber: c.issueNumber },
|
|
15582
|
+
deps.clock,
|
|
15583
|
+
actor
|
|
15584
|
+
);
|
|
15585
|
+
await appendEventLine(session.worktreePath, linkEvt, deps.clock);
|
|
15586
|
+
imported.push({ ticketId, issueNumber: c.issueNumber });
|
|
15587
|
+
}
|
|
15588
|
+
await commitDataWorktreeIfNeeded(
|
|
15589
|
+
session.worktreePath,
|
|
15590
|
+
formatDataBranchCommitMessage(
|
|
15591
|
+
"hyper-pm: import-github-issues",
|
|
15592
|
+
actor
|
|
15593
|
+
),
|
|
15594
|
+
runGit
|
|
15595
|
+
);
|
|
15596
|
+
deps.log(
|
|
15597
|
+
formatOutput(g.format, {
|
|
15598
|
+
ok: true,
|
|
15599
|
+
imported,
|
|
15600
|
+
skipped
|
|
15601
|
+
})
|
|
15602
|
+
);
|
|
15603
|
+
}
|
|
15604
|
+
} catch (e) {
|
|
15605
|
+
deps.error(e instanceof Error ? e.message : String(e));
|
|
15606
|
+
deps.exit(ExitCode.ExternalApi);
|
|
15607
|
+
}
|
|
15608
|
+
} finally {
|
|
15609
|
+
await session.dispose();
|
|
15610
|
+
}
|
|
15611
|
+
deps.exit(ExitCode.Success);
|
|
15612
|
+
});
|
|
15613
|
+
program2.command("sync").description("GitHub Issues sync").option("--no-github", "skip network sync", false).option(
|
|
15614
|
+
"--skip-push",
|
|
15615
|
+
"after a successful sync, do not push the data branch to the remote",
|
|
15616
|
+
false
|
|
15617
|
+
).action(async function() {
|
|
15222
15618
|
const g = readGlobals(this);
|
|
15223
15619
|
const o = this.opts();
|
|
15224
15620
|
const repoRoot = await resolveRepoRoot(g.repo);
|
|
@@ -15300,7 +15696,33 @@ ${body}`
|
|
|
15300
15696
|
formatDataBranchCommitMessage("hyper-pm: sync", outboundActor),
|
|
15301
15697
|
runGit
|
|
15302
15698
|
);
|
|
15303
|
-
|
|
15699
|
+
let dataBranchPush;
|
|
15700
|
+
let dataBranchPushDetail;
|
|
15701
|
+
if (o.skipPush) {
|
|
15702
|
+
dataBranchPush = "skipped_cli";
|
|
15703
|
+
dataBranchPushDetail = "skip-push";
|
|
15704
|
+
} else {
|
|
15705
|
+
const pushResult = await tryPushDataBranchToRemote(
|
|
15706
|
+
session.worktreePath,
|
|
15707
|
+
cfg.remote,
|
|
15708
|
+
cfg.dataBranch,
|
|
15709
|
+
runGit
|
|
15710
|
+
);
|
|
15711
|
+
dataBranchPush = pushResult.status;
|
|
15712
|
+
dataBranchPushDetail = pushResult.detail;
|
|
15713
|
+
if (pushResult.status === "failed" && pushResult.detail) {
|
|
15714
|
+
deps.error(
|
|
15715
|
+
`hyper-pm: data branch not pushed (${cfg.remote}/${cfg.dataBranch}): ${pushResult.detail}`
|
|
15716
|
+
);
|
|
15717
|
+
}
|
|
15718
|
+
}
|
|
15719
|
+
deps.log(
|
|
15720
|
+
formatOutput(g.format, {
|
|
15721
|
+
ok: true,
|
|
15722
|
+
dataBranchPush,
|
|
15723
|
+
...dataBranchPushDetail !== void 0 ? { dataBranchPushDetail } : {}
|
|
15724
|
+
})
|
|
15725
|
+
);
|
|
15304
15726
|
} catch (e) {
|
|
15305
15727
|
deps.error(e instanceof Error ? e.message : String(e));
|
|
15306
15728
|
deps.exit(ExitCode.ExternalApi);
|