@jonit-dev/night-watch-cli 1.7.96 → 1.7.97
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/cli.js +251 -555
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1599,17 +1599,14 @@ var init_github_graphql = __esm({
|
|
|
1599
1599
|
}
|
|
1600
1600
|
});
|
|
1601
1601
|
|
|
1602
|
-
// ../core/dist/board/providers/github-projects.js
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
var init_github_projects = __esm({
|
|
1607
|
-
"../core/dist/board/providers/github-projects.js"() {
|
|
1602
|
+
// ../core/dist/board/providers/github-projects-base.js
|
|
1603
|
+
var GitHubProjectsBase;
|
|
1604
|
+
var init_github_projects_base = __esm({
|
|
1605
|
+
"../core/dist/board/providers/github-projects-base.js"() {
|
|
1608
1606
|
"use strict";
|
|
1609
1607
|
init_types2();
|
|
1610
1608
|
init_github_graphql();
|
|
1611
|
-
|
|
1612
|
-
GitHubProjectsProvider = class {
|
|
1609
|
+
GitHubProjectsBase = class {
|
|
1613
1610
|
config;
|
|
1614
1611
|
cwd;
|
|
1615
1612
|
cachedProjectId = null;
|
|
@@ -1617,15 +1614,19 @@ var init_github_projects = __esm({
|
|
|
1617
1614
|
cachedOptionIds = /* @__PURE__ */ new Map();
|
|
1618
1615
|
cachedOwner = null;
|
|
1619
1616
|
cachedRepositoryId = null;
|
|
1617
|
+
cachedRepoNameWithOwner = null;
|
|
1620
1618
|
constructor(config, cwd) {
|
|
1621
1619
|
this.config = config;
|
|
1622
1620
|
this.cwd = cwd;
|
|
1623
1621
|
}
|
|
1624
1622
|
// -------------------------------------------------------------------------
|
|
1625
|
-
//
|
|
1623
|
+
// Repo resolution
|
|
1626
1624
|
// -------------------------------------------------------------------------
|
|
1627
1625
|
async getRepo() {
|
|
1628
|
-
|
|
1626
|
+
if (this.cachedRepoNameWithOwner)
|
|
1627
|
+
return this.cachedRepoNameWithOwner;
|
|
1628
|
+
this.cachedRepoNameWithOwner = this.config.repo ?? await getRepoNwo(this.cwd);
|
|
1629
|
+
return this.cachedRepoNameWithOwner;
|
|
1629
1630
|
}
|
|
1630
1631
|
async getRepoParts() {
|
|
1631
1632
|
const repo = await this.getRepo();
|
|
@@ -1635,131 +1636,102 @@ var init_github_projects = __esm({
|
|
|
1635
1636
|
}
|
|
1636
1637
|
return { owner, name };
|
|
1637
1638
|
}
|
|
1639
|
+
normalizeRepoName(repo) {
|
|
1640
|
+
return repo.trim().toLowerCase();
|
|
1641
|
+
}
|
|
1642
|
+
isCurrentRepoItem(content, repo) {
|
|
1643
|
+
const repoNameWithOwner = content?.repository?.nameWithOwner;
|
|
1644
|
+
if (!repoNameWithOwner)
|
|
1645
|
+
return true;
|
|
1646
|
+
return this.normalizeRepoName(repoNameWithOwner) === this.normalizeRepoName(repo);
|
|
1647
|
+
}
|
|
1638
1648
|
async getRepoOwnerLogin() {
|
|
1639
1649
|
return (await this.getRepoParts()).owner;
|
|
1640
1650
|
}
|
|
1641
1651
|
async getRepoOwner() {
|
|
1642
|
-
if (this.cachedOwner && this.cachedRepositoryId)
|
|
1652
|
+
if (this.cachedOwner && this.cachedRepositoryId)
|
|
1643
1653
|
return this.cachedOwner;
|
|
1644
|
-
}
|
|
1645
1654
|
const { owner, name } = await this.getRepoParts();
|
|
1646
1655
|
const data = await graphql(`query ResolveRepoOwner($owner: String!, $name: String!) {
|
|
1647
1656
|
repository(owner: $owner, name: $name) {
|
|
1648
1657
|
id
|
|
1649
|
-
owner {
|
|
1650
|
-
__typename
|
|
1651
|
-
id
|
|
1652
|
-
login
|
|
1653
|
-
}
|
|
1658
|
+
owner { __typename id login }
|
|
1654
1659
|
}
|
|
1655
1660
|
}`, { owner, name }, this.cwd);
|
|
1656
|
-
if (!data.repository)
|
|
1661
|
+
if (!data.repository)
|
|
1657
1662
|
throw new Error(`Repository ${owner}/${name} not found.`);
|
|
1658
|
-
}
|
|
1659
1663
|
const ownerNode = data.repository.owner;
|
|
1660
1664
|
if (!ownerNode || ownerNode.__typename !== "User" && ownerNode.__typename !== "Organization") {
|
|
1661
1665
|
throw new Error(`Failed to resolve repository owner for ${owner}/${name}.`);
|
|
1662
1666
|
}
|
|
1663
1667
|
this.cachedRepositoryId = data.repository.id;
|
|
1664
|
-
this.cachedOwner = {
|
|
1665
|
-
id: ownerNode.id,
|
|
1666
|
-
login: ownerNode.login,
|
|
1667
|
-
type: ownerNode.__typename
|
|
1668
|
-
};
|
|
1668
|
+
this.cachedOwner = { id: ownerNode.id, login: ownerNode.login, type: ownerNode.__typename };
|
|
1669
1669
|
return this.cachedOwner;
|
|
1670
1670
|
}
|
|
1671
1671
|
async getRepositoryNodeId() {
|
|
1672
|
-
if (this.cachedRepositoryId)
|
|
1672
|
+
if (this.cachedRepositoryId)
|
|
1673
1673
|
return this.cachedRepositoryId;
|
|
1674
|
-
}
|
|
1675
1674
|
await this.getRepoOwner();
|
|
1676
1675
|
if (!this.cachedRepositoryId) {
|
|
1677
1676
|
throw new Error(`Failed to resolve repository ID for ${await this.getRepo()}.`);
|
|
1678
1677
|
}
|
|
1679
1678
|
return this.cachedRepositoryId;
|
|
1680
1679
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1680
|
+
// -------------------------------------------------------------------------
|
|
1681
|
+
// Project discovery & caching
|
|
1682
|
+
// -------------------------------------------------------------------------
|
|
1683
|
+
async resolveProjectNode(projectNumber) {
|
|
1684
|
+
const ownerLogins = /* @__PURE__ */ new Set([await this.getRepoOwnerLogin()]);
|
|
1683
1685
|
try {
|
|
1684
|
-
await
|
|
1685
|
-
|
|
1686
|
-
repository {
|
|
1687
|
-
id
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
}`, { projectId, repositoryId }, this.cwd);
|
|
1691
|
-
} catch (err) {
|
|
1692
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1693
|
-
const normalized = message.toLowerCase();
|
|
1694
|
-
if (normalized.includes("already") && normalized.includes("project")) {
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
throw err;
|
|
1686
|
+
ownerLogins.add(await getViewerLogin(this.cwd));
|
|
1687
|
+
} catch {
|
|
1698
1688
|
}
|
|
1689
|
+
for (const login of ownerLogins) {
|
|
1690
|
+
const node = await this.fetchProjectNode(login, projectNumber);
|
|
1691
|
+
if (node)
|
|
1692
|
+
return node;
|
|
1693
|
+
}
|
|
1694
|
+
return null;
|
|
1699
1695
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
id
|
|
1707
|
-
options {
|
|
1708
|
-
id
|
|
1709
|
-
name
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1696
|
+
/** Try user query first, fall back to org query. */
|
|
1697
|
+
async fetchProjectNode(login, projectNumber) {
|
|
1698
|
+
try {
|
|
1699
|
+
const userData = await graphql(`query GetProject($login: String!, $number: Int!) {
|
|
1700
|
+
user(login: $login) {
|
|
1701
|
+
projectV2(number: $number) { id number title url }
|
|
1713
1702
|
}
|
|
1703
|
+
}`, { login, number: projectNumber }, this.cwd);
|
|
1704
|
+
if (userData.user?.projectV2)
|
|
1705
|
+
return userData.user.projectV2;
|
|
1706
|
+
} catch {
|
|
1714
1707
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1708
|
+
try {
|
|
1709
|
+
const orgData = await graphql(`query GetOrgProject($login: String!, $number: Int!) {
|
|
1710
|
+
organization(login: $login) {
|
|
1711
|
+
projectV2(number: $number) { id number title url }
|
|
1712
|
+
}
|
|
1713
|
+
}`, { login, number: projectNumber }, this.cwd);
|
|
1714
|
+
if (orgData.organization?.projectV2)
|
|
1715
|
+
return orgData.organization.projectV2;
|
|
1716
|
+
} catch {
|
|
1719
1717
|
}
|
|
1720
|
-
return
|
|
1721
|
-
fieldId: field.id,
|
|
1722
|
-
optionIds: new Map(field.options.map((o) => [o.name, o.id]))
|
|
1723
|
-
};
|
|
1718
|
+
return null;
|
|
1724
1719
|
}
|
|
1725
|
-
/**
|
|
1726
|
-
* Fetch and cache the project node ID, Status field ID, and option IDs.
|
|
1727
|
-
* Throws if the project cannot be found or has no Status field.
|
|
1728
|
-
*/
|
|
1729
1720
|
async ensureProjectCache() {
|
|
1730
1721
|
if (this.cachedProjectId !== null && this.cachedFieldId !== null && this.cachedOptionIds.size > 0) {
|
|
1731
|
-
return {
|
|
1732
|
-
projectId: this.cachedProjectId,
|
|
1733
|
-
fieldId: this.cachedFieldId,
|
|
1734
|
-
optionIds: this.cachedOptionIds
|
|
1735
|
-
};
|
|
1722
|
+
return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
|
|
1736
1723
|
}
|
|
1737
1724
|
if (this.cachedProjectId !== null) {
|
|
1738
1725
|
const statusField2 = await this.fetchStatusField(this.cachedProjectId);
|
|
1739
1726
|
this.cachedFieldId = statusField2.fieldId;
|
|
1740
1727
|
this.cachedOptionIds = statusField2.optionIds;
|
|
1741
|
-
return {
|
|
1742
|
-
projectId: this.cachedProjectId,
|
|
1743
|
-
fieldId: this.cachedFieldId,
|
|
1744
|
-
optionIds: this.cachedOptionIds
|
|
1745
|
-
};
|
|
1728
|
+
return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
|
|
1746
1729
|
}
|
|
1747
1730
|
const projectNumber = this.config.projectNumber;
|
|
1748
1731
|
if (!projectNumber) {
|
|
1749
1732
|
throw new Error("No projectNumber configured. Run `night-watch board setup` first.");
|
|
1750
1733
|
}
|
|
1751
|
-
const
|
|
1752
|
-
try {
|
|
1753
|
-
ownerLogins.add(await getViewerLogin(this.cwd));
|
|
1754
|
-
} catch {
|
|
1755
|
-
}
|
|
1756
|
-
let projectNode = null;
|
|
1757
|
-
for (const login of ownerLogins) {
|
|
1758
|
-
projectNode = await this.fetchProjectNode(login, projectNumber);
|
|
1759
|
-
if (projectNode) {
|
|
1760
|
-
break;
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1734
|
+
const projectNode = await this.resolveProjectNode(projectNumber);
|
|
1763
1735
|
if (!projectNode) {
|
|
1764
1736
|
throw new Error(`GitHub Project #${projectNumber} not found for repository owner "${await this.getRepoOwnerLogin()}".`);
|
|
1765
1737
|
}
|
|
@@ -1767,129 +1739,92 @@ var init_github_projects = __esm({
|
|
|
1767
1739
|
const statusField = await this.fetchStatusField(projectNode.id);
|
|
1768
1740
|
this.cachedFieldId = statusField.fieldId;
|
|
1769
1741
|
this.cachedOptionIds = statusField.optionIds;
|
|
1770
|
-
return {
|
|
1771
|
-
projectId: this.cachedProjectId,
|
|
1772
|
-
fieldId: this.cachedFieldId,
|
|
1773
|
-
optionIds: this.cachedOptionIds
|
|
1774
|
-
};
|
|
1742
|
+
return { projectId: this.cachedProjectId, fieldId: this.cachedFieldId, optionIds: this.cachedOptionIds };
|
|
1775
1743
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1744
|
+
// -------------------------------------------------------------------------
|
|
1745
|
+
// Status field management
|
|
1746
|
+
// -------------------------------------------------------------------------
|
|
1747
|
+
async fetchStatusField(projectId) {
|
|
1748
|
+
const fieldData = await graphql(`query GetStatusField($projectId: ID!) {
|
|
1749
|
+
node(id: $projectId) {
|
|
1750
|
+
... on ProjectV2 {
|
|
1751
|
+
field(name: "Status") {
|
|
1752
|
+
... on ProjectV2SingleSelectField {
|
|
1753
|
+
id
|
|
1754
|
+
options { id name }
|
|
1755
|
+
}
|
|
1786
1756
|
}
|
|
1787
1757
|
}
|
|
1788
|
-
}`, { login, number: projectNumber }, this.cwd);
|
|
1789
|
-
if (userData.user?.projectV2) {
|
|
1790
|
-
return userData.user.projectV2;
|
|
1791
|
-
}
|
|
1792
|
-
} catch {
|
|
1793
1758
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1759
|
+
}`, { projectId }, this.cwd);
|
|
1760
|
+
const field = fieldData.node?.field;
|
|
1761
|
+
if (!field) {
|
|
1762
|
+
throw new Error(`Status field not found on project ${projectId}. Run \`night-watch board setup\` to create it.`);
|
|
1763
|
+
}
|
|
1764
|
+
return { fieldId: field.id, optionIds: new Map(field.options.map((o) => [o.name, o.id])) };
|
|
1765
|
+
}
|
|
1766
|
+
async ensureStatusColumns(projectId) {
|
|
1767
|
+
const fieldData = await graphql(`query GetStatusField($projectId: ID!) {
|
|
1768
|
+
node(id: $projectId) {
|
|
1769
|
+
... on ProjectV2 {
|
|
1770
|
+
field(name: "Status") {
|
|
1771
|
+
... on ProjectV2SingleSelectField {
|
|
1772
|
+
id
|
|
1773
|
+
options { id name }
|
|
1774
|
+
}
|
|
1802
1775
|
}
|
|
1803
1776
|
}
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1777
|
+
}
|
|
1778
|
+
}`, { projectId }, this.cwd);
|
|
1779
|
+
const field = fieldData.node?.field;
|
|
1780
|
+
if (!field)
|
|
1781
|
+
return;
|
|
1782
|
+
const existing = new Set(field.options.map((o) => o.name));
|
|
1783
|
+
const required = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
1784
|
+
if (required.every((n) => existing.has(n)))
|
|
1785
|
+
return;
|
|
1786
|
+
await graphql(`mutation UpdateField($fieldId: ID!) {
|
|
1787
|
+
updateProjectV2Field(input: {
|
|
1788
|
+
fieldId: $fieldId
|
|
1789
|
+
singleSelectOptions: [
|
|
1790
|
+
{ name: "Draft", color: GRAY, description: "" }
|
|
1791
|
+
{ name: "Ready", color: BLUE, description: "" }
|
|
1792
|
+
{ name: "In Progress", color: YELLOW, description: "" }
|
|
1793
|
+
{ name: "Review", color: ORANGE, description: "" }
|
|
1794
|
+
{ name: "Done", color: GREEN, description: "" }
|
|
1795
|
+
]
|
|
1796
|
+
}) {
|
|
1797
|
+
projectV2Field {
|
|
1798
|
+
... on ProjectV2SingleSelectField { id options { id name } }
|
|
1807
1799
|
}
|
|
1808
|
-
} catch {
|
|
1809
1800
|
}
|
|
1810
|
-
|
|
1801
|
+
}`, { fieldId: field.id }, this.cwd);
|
|
1811
1802
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
if (!content || content.number === void 0) {
|
|
1819
|
-
return null;
|
|
1820
|
-
}
|
|
1821
|
-
let column = null;
|
|
1822
|
-
for (const fv of item.fieldValues.nodes) {
|
|
1823
|
-
if (fv.field?.name === "Status" && fv.name) {
|
|
1824
|
-
const candidate = fv.name;
|
|
1825
|
-
if (BOARD_COLUMNS.includes(candidate)) {
|
|
1826
|
-
column = candidate;
|
|
1827
|
-
}
|
|
1803
|
+
async linkProjectToRepository(projectId) {
|
|
1804
|
+
const repositoryId = await this.getRepositoryNodeId();
|
|
1805
|
+
try {
|
|
1806
|
+
await graphql(`mutation LinkProjectToRepository($projectId: ID!, $repositoryId: ID!) {
|
|
1807
|
+
linkProjectV2ToRepository(input: { projectId: $projectId, repositoryId: $repositoryId }) {
|
|
1808
|
+
repository { id }
|
|
1828
1809
|
}
|
|
1810
|
+
}`, { projectId, repositoryId }, this.cwd);
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1813
|
+
if (message.toLowerCase().includes("already") && message.toLowerCase().includes("project"))
|
|
1814
|
+
return;
|
|
1815
|
+
throw err;
|
|
1829
1816
|
}
|
|
1830
|
-
return {
|
|
1831
|
-
id: content.id ?? item.id,
|
|
1832
|
-
number: content.number,
|
|
1833
|
-
title: content.title ?? "",
|
|
1834
|
-
body: content.body ?? "",
|
|
1835
|
-
url: content.url ?? "",
|
|
1836
|
-
column,
|
|
1837
|
-
labels: content.labels?.nodes.map((l) => l.name) ?? [],
|
|
1838
|
-
assignees: content.assignees?.nodes.map((a) => a.login) ?? []
|
|
1839
|
-
};
|
|
1840
1817
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
* accumulating every item node so callers never see a truncated board.
|
|
1846
|
-
*/
|
|
1847
|
-
async fetchAllProjectItems(projectId) {
|
|
1818
|
+
// -------------------------------------------------------------------------
|
|
1819
|
+
// Project items
|
|
1820
|
+
// -------------------------------------------------------------------------
|
|
1821
|
+
async paginateProjectItems(query, projectId) {
|
|
1848
1822
|
const allNodes = [];
|
|
1849
1823
|
let cursor = null;
|
|
1850
|
-
const query = `query GetProjectItems($projectId: ID!, $cursor: String) {
|
|
1851
|
-
node(id: $projectId) {
|
|
1852
|
-
... on ProjectV2 {
|
|
1853
|
-
items(first: 100, after: $cursor) {
|
|
1854
|
-
pageInfo {
|
|
1855
|
-
hasNextPage
|
|
1856
|
-
endCursor
|
|
1857
|
-
}
|
|
1858
|
-
nodes {
|
|
1859
|
-
id
|
|
1860
|
-
content {
|
|
1861
|
-
... on Issue {
|
|
1862
|
-
number
|
|
1863
|
-
title
|
|
1864
|
-
body
|
|
1865
|
-
url
|
|
1866
|
-
id
|
|
1867
|
-
labels(first: 10) { nodes { name } }
|
|
1868
|
-
assignees(first: 10) { nodes { login } }
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
fieldValues(first: 10) {
|
|
1872
|
-
nodes {
|
|
1873
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1874
|
-
name
|
|
1875
|
-
field {
|
|
1876
|
-
... on ProjectV2SingleSelectField {
|
|
1877
|
-
name
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
}`;
|
|
1888
1824
|
do {
|
|
1889
1825
|
const variables = { projectId };
|
|
1890
|
-
if (cursor !== null)
|
|
1826
|
+
if (cursor !== null)
|
|
1891
1827
|
variables.cursor = cursor;
|
|
1892
|
-
}
|
|
1893
1828
|
const data = await graphql(query, variables, this.cwd);
|
|
1894
1829
|
const page = data.node.items;
|
|
1895
1830
|
allNodes.push(...page.nodes);
|
|
@@ -1897,37 +1832,27 @@ var init_github_projects = __esm({
|
|
|
1897
1832
|
} while (cursor !== null);
|
|
1898
1833
|
return allNodes;
|
|
1899
1834
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
}
|
|
1916
|
-
nodes {
|
|
1917
|
-
id
|
|
1918
|
-
content {
|
|
1919
|
-
... on Issue {
|
|
1920
|
-
number
|
|
1835
|
+
async fetchAllProjectItems(projectId) {
|
|
1836
|
+
return this.paginateProjectItems(`query GetProjectItems($projectId: ID!, $cursor: String) {
|
|
1837
|
+
node(id: $projectId) {
|
|
1838
|
+
... on ProjectV2 {
|
|
1839
|
+
items(first: 100, after: $cursor) {
|
|
1840
|
+
pageInfo { hasNextPage endCursor }
|
|
1841
|
+
nodes {
|
|
1842
|
+
id
|
|
1843
|
+
content {
|
|
1844
|
+
... on Issue {
|
|
1845
|
+
number title body url id
|
|
1846
|
+
repository { nameWithOwner }
|
|
1847
|
+
labels(first: 10) { nodes { name } }
|
|
1848
|
+
assignees(first: 10) { nodes { login } }
|
|
1849
|
+
}
|
|
1921
1850
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
field {
|
|
1928
|
-
... on ProjectV2SingleSelectField {
|
|
1929
|
-
name
|
|
1930
|
-
}
|
|
1851
|
+
fieldValues(first: 10) {
|
|
1852
|
+
nodes {
|
|
1853
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1854
|
+
name
|
|
1855
|
+
field { ... on ProjectV2SingleSelectField { name } }
|
|
1931
1856
|
}
|
|
1932
1857
|
}
|
|
1933
1858
|
}
|
|
@@ -1935,44 +1860,61 @@ var init_github_projects = __esm({
|
|
|
1935
1860
|
}
|
|
1936
1861
|
}
|
|
1937
1862
|
}
|
|
1863
|
+
}`, projectId);
|
|
1938
1864
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1865
|
+
parseItem(item, repo) {
|
|
1866
|
+
const content = item.content;
|
|
1867
|
+
if (!content || content.number === void 0)
|
|
1868
|
+
return null;
|
|
1869
|
+
if (!this.isCurrentRepoItem(content, repo))
|
|
1870
|
+
return null;
|
|
1871
|
+
let column = null;
|
|
1872
|
+
for (const fv of item.fieldValues.nodes) {
|
|
1873
|
+
if (fv.field?.name === "Status" && fv.name) {
|
|
1874
|
+
const candidate = fv.name;
|
|
1875
|
+
if (BOARD_COLUMNS.includes(candidate))
|
|
1876
|
+
column = candidate;
|
|
1944
1877
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1878
|
+
}
|
|
1879
|
+
return {
|
|
1880
|
+
id: content.id ?? item.id,
|
|
1881
|
+
number: content.number,
|
|
1882
|
+
title: content.title ?? "",
|
|
1883
|
+
body: content.body ?? "",
|
|
1884
|
+
url: content.url ?? "",
|
|
1885
|
+
column,
|
|
1886
|
+
labels: content.labels?.nodes.map((l) => l.name) ?? [],
|
|
1887
|
+
assignees: content.assignees?.nodes.map((a) => a.login) ?? []
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
async setItemStatus(projectId, itemId, fieldId, optionId) {
|
|
1891
|
+
await graphql(`mutation UpdateItemField($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
1892
|
+
updateProjectV2ItemFieldValue(input: {
|
|
1893
|
+
projectId: $projectId
|
|
1894
|
+
itemId: $itemId
|
|
1895
|
+
fieldId: $fieldId
|
|
1896
|
+
value: { singleSelectOptionId: $optionId }
|
|
1897
|
+
}) {
|
|
1898
|
+
projectV2Item { id }
|
|
1899
|
+
}
|
|
1900
|
+
}`, { projectId, itemId, fieldId, optionId }, this.cwd);
|
|
1951
1901
|
}
|
|
1952
1902
|
// -------------------------------------------------------------------------
|
|
1953
|
-
//
|
|
1903
|
+
// Project listing
|
|
1954
1904
|
// -------------------------------------------------------------------------
|
|
1955
|
-
/**
|
|
1956
|
-
* Find an existing project by title among the repository owner's first 50 projects.
|
|
1957
|
-
* Returns null if not found.
|
|
1958
|
-
*/
|
|
1959
1905
|
async findExistingProject(owner, title) {
|
|
1960
1906
|
try {
|
|
1961
1907
|
if (owner.type === "User") {
|
|
1962
1908
|
const data2 = await graphql(`query ListUserProjects($login: String!) {
|
|
1963
1909
|
user(login: $login) {
|
|
1964
|
-
projectsV2(first: 50) {
|
|
1965
|
-
nodes { id number title url }
|
|
1966
|
-
}
|
|
1910
|
+
projectsV2(first: 50) { nodes { id number title url } }
|
|
1967
1911
|
}
|
|
1968
1912
|
}`, { login: owner.login }, this.cwd);
|
|
1969
1913
|
return data2.user?.projectsV2.nodes.find((p) => p.title === title) ?? null;
|
|
1970
1914
|
}
|
|
1971
1915
|
const data = await graphql(`query ListOrgProjects($login: String!) {
|
|
1972
1916
|
organization(login: $login) {
|
|
1973
|
-
projectsV2(first: 50) {
|
|
1974
|
-
nodes { id number title url }
|
|
1975
|
-
}
|
|
1917
|
+
projectsV2(first: 50) { nodes { id number title url } }
|
|
1976
1918
|
}
|
|
1977
1919
|
}`, { login: owner.login }, this.cwd);
|
|
1978
1920
|
return data.organization?.projectsV2.nodes.find((p) => p.title === title) ?? null;
|
|
@@ -1980,63 +1922,22 @@ var init_github_projects = __esm({
|
|
|
1980
1922
|
return null;
|
|
1981
1923
|
}
|
|
1982
1924
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
}`, { projectId }, this.cwd);
|
|
2000
|
-
const field = fieldData.node?.field;
|
|
2001
|
-
if (!field)
|
|
2002
|
-
return;
|
|
2003
|
-
const existing = new Set(field.options.map((o) => o.name));
|
|
2004
|
-
const required = ["Draft", "Ready", "In Progress", "Review", "Done"];
|
|
2005
|
-
const missing = required.filter((n) => !existing.has(n));
|
|
2006
|
-
if (missing.length === 0)
|
|
2007
|
-
return;
|
|
2008
|
-
const colorMap = {
|
|
2009
|
-
Draft: "GRAY",
|
|
2010
|
-
Ready: "BLUE",
|
|
2011
|
-
"In Progress": "YELLOW",
|
|
2012
|
-
Review: "ORANGE",
|
|
2013
|
-
Done: "GREEN"
|
|
2014
|
-
};
|
|
2015
|
-
const allOptions = required.map((name) => ({
|
|
2016
|
-
name,
|
|
2017
|
-
color: colorMap[name],
|
|
2018
|
-
description: ""
|
|
2019
|
-
}));
|
|
2020
|
-
await graphql(`mutation UpdateField($fieldId: ID!) {
|
|
2021
|
-
updateProjectV2Field(input: {
|
|
2022
|
-
fieldId: $fieldId,
|
|
2023
|
-
singleSelectOptions: [
|
|
2024
|
-
{ name: "Draft", color: GRAY, description: "" },
|
|
2025
|
-
{ name: "Ready", color: BLUE, description: "" },
|
|
2026
|
-
{ name: "In Progress", color: YELLOW, description: "" },
|
|
2027
|
-
{ name: "Review", color: ORANGE, description: "" },
|
|
2028
|
-
{ name: "Done", color: GREEN, description: "" }
|
|
2029
|
-
]
|
|
2030
|
-
}) {
|
|
2031
|
-
projectV2Field {
|
|
2032
|
-
... on ProjectV2SingleSelectField {
|
|
2033
|
-
id
|
|
2034
|
-
options { id name }
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
}`, { fieldId: field.id, allOptions }, this.cwd);
|
|
2039
|
-
}
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// ../core/dist/board/providers/github-projects.js
|
|
1930
|
+
import { execFile as execFile2 } from "child_process";
|
|
1931
|
+
import { promisify as promisify2 } from "util";
|
|
1932
|
+
var execFileAsync2, GitHubProjectsProvider;
|
|
1933
|
+
var init_github_projects = __esm({
|
|
1934
|
+
"../core/dist/board/providers/github-projects.js"() {
|
|
1935
|
+
"use strict";
|
|
1936
|
+
init_types2();
|
|
1937
|
+
init_github_graphql();
|
|
1938
|
+
init_github_projects_base();
|
|
1939
|
+
execFileAsync2 = promisify2(execFile2);
|
|
1940
|
+
GitHubProjectsProvider = class extends GitHubProjectsBase {
|
|
2040
1941
|
async setupBoard(title) {
|
|
2041
1942
|
const owner = await this.getRepoOwner();
|
|
2042
1943
|
const existing = await this.findExistingProject(owner, title);
|
|
@@ -2048,12 +1949,7 @@ var init_github_projects = __esm({
|
|
|
2048
1949
|
}
|
|
2049
1950
|
const createData = await graphql(`mutation CreateProject($ownerId: ID!, $title: String!) {
|
|
2050
1951
|
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
2051
|
-
projectV2 {
|
|
2052
|
-
id
|
|
2053
|
-
number
|
|
2054
|
-
url
|
|
2055
|
-
title
|
|
2056
|
-
}
|
|
1952
|
+
projectV2 { id number url title }
|
|
2057
1953
|
}
|
|
2058
1954
|
}`, { ownerId: owner.id, title }, this.cwd);
|
|
2059
1955
|
const project = createData.createProjectV2.projectV2;
|
|
@@ -2069,27 +1965,23 @@ var init_github_projects = __esm({
|
|
|
2069
1965
|
this.cachedOptionIds = refreshed.optionIds;
|
|
2070
1966
|
} catch (err) {
|
|
2071
1967
|
const message = err instanceof Error ? err.message : String(err);
|
|
2072
|
-
if (!message.includes("Status field not found"))
|
|
1968
|
+
if (!message.includes("Status field not found"))
|
|
2073
1969
|
throw err;
|
|
2074
|
-
}
|
|
2075
1970
|
const createFieldData = await graphql(`mutation CreateStatusField($projectId: ID!) {
|
|
2076
1971
|
createProjectV2Field(input: {
|
|
2077
|
-
projectId: $projectId
|
|
2078
|
-
dataType: SINGLE_SELECT
|
|
2079
|
-
name: "Status"
|
|
1972
|
+
projectId: $projectId
|
|
1973
|
+
dataType: SINGLE_SELECT
|
|
1974
|
+
name: "Status"
|
|
2080
1975
|
singleSelectOptions: [
|
|
2081
|
-
{ name: "Draft", color: GRAY, description: "" }
|
|
2082
|
-
{ name: "Ready", color: BLUE, description: "" }
|
|
2083
|
-
{ name: "In Progress", color: YELLOW, description: "" }
|
|
2084
|
-
{ name: "Review", color: ORANGE, description: "" }
|
|
1976
|
+
{ name: "Draft", color: GRAY, description: "" }
|
|
1977
|
+
{ name: "Ready", color: BLUE, description: "" }
|
|
1978
|
+
{ name: "In Progress", color: YELLOW, description: "" }
|
|
1979
|
+
{ name: "Review", color: ORANGE, description: "" }
|
|
2085
1980
|
{ name: "Done", color: GREEN, description: "" }
|
|
2086
1981
|
]
|
|
2087
1982
|
}) {
|
|
2088
1983
|
projectV2Field {
|
|
2089
|
-
... on ProjectV2SingleSelectField {
|
|
2090
|
-
id
|
|
2091
|
-
options { id name }
|
|
2092
|
-
}
|
|
1984
|
+
... on ProjectV2SingleSelectField { id options { id name } }
|
|
2093
1985
|
}
|
|
2094
1986
|
}
|
|
2095
1987
|
}`, { projectId: project.id }, this.cwd);
|
|
@@ -2101,25 +1993,12 @@ var init_github_projects = __esm({
|
|
|
2101
1993
|
}
|
|
2102
1994
|
async getBoard() {
|
|
2103
1995
|
const projectNumber = this.config.projectNumber;
|
|
2104
|
-
if (!projectNumber)
|
|
1996
|
+
if (!projectNumber)
|
|
2105
1997
|
return null;
|
|
2106
|
-
}
|
|
2107
1998
|
try {
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
ownerLogins.add(await getViewerLogin(this.cwd));
|
|
2111
|
-
} catch {
|
|
2112
|
-
}
|
|
2113
|
-
let node = null;
|
|
2114
|
-
for (const login of ownerLogins) {
|
|
2115
|
-
node = await this.fetchProjectNode(login, projectNumber);
|
|
2116
|
-
if (node) {
|
|
2117
|
-
break;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
if (!node) {
|
|
1999
|
+
const node = await this.resolveProjectNode(projectNumber);
|
|
2000
|
+
if (!node)
|
|
2121
2001
|
return null;
|
|
2122
|
-
}
|
|
2123
2002
|
return { id: node.id, number: node.number, title: node.title, url: node.url };
|
|
2124
2003
|
} catch {
|
|
2125
2004
|
return null;
|
|
@@ -2127,73 +2006,36 @@ var init_github_projects = __esm({
|
|
|
2127
2006
|
}
|
|
2128
2007
|
async getColumns() {
|
|
2129
2008
|
const { fieldId, optionIds } = await this.ensureProjectCache();
|
|
2130
|
-
return BOARD_COLUMNS.map((name) => ({
|
|
2131
|
-
id: optionIds.get(name) ?? fieldId,
|
|
2132
|
-
name
|
|
2133
|
-
}));
|
|
2009
|
+
return BOARD_COLUMNS.map((name) => ({ id: optionIds.get(name) ?? fieldId, name }));
|
|
2134
2010
|
}
|
|
2135
2011
|
async createIssue(input) {
|
|
2136
2012
|
const repo = await this.getRepo();
|
|
2137
2013
|
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2138
|
-
const issueArgs = [
|
|
2139
|
-
"issue",
|
|
2140
|
-
"create",
|
|
2141
|
-
"--title",
|
|
2142
|
-
input.title,
|
|
2143
|
-
"--body",
|
|
2144
|
-
input.body,
|
|
2145
|
-
"--repo",
|
|
2146
|
-
repo
|
|
2147
|
-
];
|
|
2014
|
+
const issueArgs = ["issue", "create", "--title", input.title, "--body", input.body, "--repo", repo];
|
|
2148
2015
|
if (input.labels && input.labels.length > 0) {
|
|
2149
2016
|
issueArgs.push("--label", input.labels.join(","));
|
|
2150
2017
|
}
|
|
2151
|
-
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, {
|
|
2152
|
-
cwd: this.cwd,
|
|
2153
|
-
encoding: "utf-8"
|
|
2154
|
-
});
|
|
2018
|
+
const { stdout: issueUrlRaw } = await execFileAsync2("gh", issueArgs, { cwd: this.cwd, encoding: "utf-8" });
|
|
2155
2019
|
const issueUrl = issueUrlRaw.trim();
|
|
2156
2020
|
const issueNumber = parseInt(issueUrl.split("/").pop() ?? "", 10);
|
|
2157
|
-
if (!issueNumber)
|
|
2021
|
+
if (!issueNumber)
|
|
2158
2022
|
throw new Error(`Failed to parse issue number from URL: ${issueUrl}`);
|
|
2159
|
-
}
|
|
2160
2023
|
const [owner, repoName] = repo.split("/");
|
|
2161
2024
|
const { stdout: nodeIdRaw } = await execFileAsync2("gh", ["api", `repos/${owner}/${repoName}/issues/${issueNumber}`, "--jq", ".node_id"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2162
|
-
const
|
|
2163
|
-
const issueJson = { number: issueNumber, id: nodeIdOutput, url: issueUrl };
|
|
2025
|
+
const issueJson = { number: issueNumber, id: nodeIdRaw.trim(), url: issueUrl };
|
|
2164
2026
|
const addData = await graphql(`mutation AddProjectItem($projectId: ID!, $contentId: ID!) {
|
|
2165
2027
|
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
2166
|
-
item {
|
|
2167
|
-
id
|
|
2168
|
-
}
|
|
2028
|
+
item { id }
|
|
2169
2029
|
}
|
|
2170
2030
|
}`, { projectId, contentId: issueJson.id }, this.cwd);
|
|
2171
2031
|
const itemId = addData.addProjectV2ItemById.item.id;
|
|
2172
2032
|
const targetColumn = input.column ?? "Draft";
|
|
2173
2033
|
const optionId = optionIds.get(targetColumn);
|
|
2174
|
-
if (optionId)
|
|
2175
|
-
await
|
|
2176
|
-
$projectId: ID!,
|
|
2177
|
-
$itemId: ID!,
|
|
2178
|
-
$fieldId: ID!,
|
|
2179
|
-
$optionId: String!
|
|
2180
|
-
) {
|
|
2181
|
-
updateProjectV2ItemFieldValue(input: {
|
|
2182
|
-
projectId: $projectId,
|
|
2183
|
-
itemId: $itemId,
|
|
2184
|
-
fieldId: $fieldId,
|
|
2185
|
-
value: { singleSelectOptionId: $optionId }
|
|
2186
|
-
}) {
|
|
2187
|
-
projectV2Item {
|
|
2188
|
-
id
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
}`, { projectId, itemId, fieldId, optionId }, this.cwd);
|
|
2192
|
-
}
|
|
2034
|
+
if (optionId)
|
|
2035
|
+
await this.setItemStatus(projectId, itemId, fieldId, optionId);
|
|
2193
2036
|
const fullIssue = await this.getIssue(issueJson.number);
|
|
2194
|
-
if (fullIssue)
|
|
2037
|
+
if (fullIssue)
|
|
2195
2038
|
return { ...fullIssue, column: targetColumn };
|
|
2196
|
-
}
|
|
2197
2039
|
return {
|
|
2198
2040
|
id: issueJson.id,
|
|
2199
2041
|
number: issueJson.number,
|
|
@@ -2209,26 +2051,16 @@ var init_github_projects = __esm({
|
|
|
2209
2051
|
const repo = await this.getRepo();
|
|
2210
2052
|
let rawIssue;
|
|
2211
2053
|
try {
|
|
2212
|
-
const { stdout: output } = await execFileAsync2("gh", [
|
|
2213
|
-
"issue",
|
|
2214
|
-
"view",
|
|
2215
|
-
String(issueNumber),
|
|
2216
|
-
"--repo",
|
|
2217
|
-
repo,
|
|
2218
|
-
"--json",
|
|
2219
|
-
"number,title,body,url,id,labels,assignees"
|
|
2220
|
-
], { cwd: this.cwd, encoding: "utf-8" });
|
|
2054
|
+
const { stdout: output } = await execFileAsync2("gh", ["issue", "view", String(issueNumber), "--repo", repo, "--json", "number,title,body,url,id,labels,assignees"], { cwd: this.cwd, encoding: "utf-8" });
|
|
2221
2055
|
rawIssue = JSON.parse(output);
|
|
2222
2056
|
} catch {
|
|
2223
2057
|
return null;
|
|
2224
2058
|
}
|
|
2225
2059
|
let column = null;
|
|
2226
2060
|
try {
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
if (match) {
|
|
2061
|
+
const match = (await this.getAllIssues()).find((i) => i.number === issueNumber);
|
|
2062
|
+
if (match)
|
|
2230
2063
|
column = match.column;
|
|
2231
|
-
}
|
|
2232
2064
|
} catch {
|
|
2233
2065
|
}
|
|
2234
2066
|
return {
|
|
@@ -2243,53 +2075,36 @@ var init_github_projects = __esm({
|
|
|
2243
2075
|
};
|
|
2244
2076
|
}
|
|
2245
2077
|
async getIssuesByColumn(column) {
|
|
2246
|
-
|
|
2247
|
-
return all.filter((issue) => issue.column === column);
|
|
2078
|
+
return (await this.getAllIssues()).filter((issue) => issue.column === column);
|
|
2248
2079
|
}
|
|
2249
2080
|
async getAllIssues() {
|
|
2081
|
+
const repo = await this.getRepo();
|
|
2250
2082
|
const { projectId } = await this.ensureProjectCache();
|
|
2251
|
-
const allNodes = await this.fetchAllProjectItems(projectId);
|
|
2252
2083
|
const results = [];
|
|
2253
|
-
for (const item of
|
|
2254
|
-
const parsed = this.parseItem(item);
|
|
2255
|
-
if (parsed)
|
|
2084
|
+
for (const item of await this.fetchAllProjectItems(projectId)) {
|
|
2085
|
+
const parsed = this.parseItem(item, repo);
|
|
2086
|
+
if (parsed)
|
|
2256
2087
|
results.push(parsed);
|
|
2257
|
-
}
|
|
2258
2088
|
}
|
|
2259
2089
|
return results;
|
|
2260
2090
|
}
|
|
2261
2091
|
async moveIssue(issueNumber, targetColumn) {
|
|
2092
|
+
const repo = await this.getRepo();
|
|
2262
2093
|
const { projectId, fieldId, optionIds } = await this.ensureProjectCache();
|
|
2263
|
-
const
|
|
2264
|
-
|
|
2265
|
-
if (!itemNode) {
|
|
2094
|
+
const itemNode = (await this.fetchAllProjectItems(projectId)).find((n) => n.content?.number === issueNumber && this.isCurrentRepoItem(n.content, repo));
|
|
2095
|
+
if (!itemNode)
|
|
2266
2096
|
throw new Error(`Issue #${issueNumber} not found on the project board.`);
|
|
2267
|
-
}
|
|
2268
2097
|
const optionId = optionIds.get(targetColumn);
|
|
2269
|
-
if (!optionId)
|
|
2098
|
+
if (!optionId)
|
|
2270
2099
|
throw new Error(`Column "${targetColumn}" not found on the project board.`);
|
|
2271
|
-
|
|
2272
|
-
await graphql(`mutation UpdateItemField(
|
|
2273
|
-
$projectId: ID!,
|
|
2274
|
-
$itemId: ID!,
|
|
2275
|
-
$fieldId: ID!,
|
|
2276
|
-
$optionId: String!
|
|
2277
|
-
) {
|
|
2278
|
-
updateProjectV2ItemFieldValue(input: {
|
|
2279
|
-
projectId: $projectId,
|
|
2280
|
-
itemId: $itemId,
|
|
2281
|
-
fieldId: $fieldId,
|
|
2282
|
-
value: { singleSelectOptionId: $optionId }
|
|
2283
|
-
}) {
|
|
2284
|
-
projectV2Item {
|
|
2285
|
-
id
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}`, { projectId, itemId: itemNode.id, fieldId, optionId }, this.cwd);
|
|
2100
|
+
await this.setItemStatus(projectId, itemNode.id, fieldId, optionId);
|
|
2289
2101
|
}
|
|
2290
2102
|
async closeIssue(issueNumber) {
|
|
2291
2103
|
const repo = await this.getRepo();
|
|
2292
|
-
await execFileAsync2("gh", ["issue", "close", String(issueNumber), "--repo", repo], {
|
|
2104
|
+
await execFileAsync2("gh", ["issue", "close", String(issueNumber), "--repo", repo], {
|
|
2105
|
+
cwd: this.cwd,
|
|
2106
|
+
encoding: "utf-8"
|
|
2107
|
+
});
|
|
2293
2108
|
}
|
|
2294
2109
|
async commentOnIssue(issueNumber, body) {
|
|
2295
2110
|
const repo = await this.getRepo();
|
|
@@ -2889,123 +2704,6 @@ var init_json_state_migrator = __esm({
|
|
|
2889
2704
|
}
|
|
2890
2705
|
});
|
|
2891
2706
|
|
|
2892
|
-
// ../core/dist/utils/avatar-generator.js
|
|
2893
|
-
function extractOutputUrl(output) {
|
|
2894
|
-
if (!output)
|
|
2895
|
-
return null;
|
|
2896
|
-
return Array.isArray(output) ? output[0] ?? null : output;
|
|
2897
|
-
}
|
|
2898
|
-
function buildAvatarPrompt(personaName, role) {
|
|
2899
|
-
const nameKey = personaName.toLowerCase();
|
|
2900
|
-
const personaDescription = PERSONA_PORTRAITS[nameKey];
|
|
2901
|
-
if (personaDescription) {
|
|
2902
|
-
return `Professional headshot portrait photo of a ${personaDescription}, photorealistic, clean soft neutral background, natural diffused window lighting, shot at f/2.8, shallow depth of field, looking directly at camera, candid professional headshot style, no retouching artifacts, natural skin texture`;
|
|
2903
|
-
}
|
|
2904
|
-
const lower = role.toLowerCase();
|
|
2905
|
-
let descriptor;
|
|
2906
|
-
if (lower.includes("security")) {
|
|
2907
|
-
descriptor = "sharp-eyed cybersecurity professional with a serious and analytical expression, wearing a dark blazer";
|
|
2908
|
-
} else if (lower.includes("architect") || lower.includes("tech lead") || lower.includes("lead")) {
|
|
2909
|
-
descriptor = "confident senior software architect with a composed and thoughtful expression, business casual attire";
|
|
2910
|
-
} else if (lower.includes("qa") || lower.includes("quality")) {
|
|
2911
|
-
descriptor = "meticulous quality engineer with a focused and detail-oriented expression, smart casual attire";
|
|
2912
|
-
} else if (lower.includes("implement") || lower.includes("developer") || lower.includes("engineer")) {
|
|
2913
|
-
descriptor = "software developer with a creative and approachable expression, casual tech attire";
|
|
2914
|
-
} else if (lower.includes("product") || lower.includes("manager")) {
|
|
2915
|
-
descriptor = "product manager with a strategic and empathetic expression, business professional attire";
|
|
2916
|
-
} else if (lower.includes("design")) {
|
|
2917
|
-
descriptor = "UX/UI designer with a creative and innovative expression, modern stylish attire";
|
|
2918
|
-
} else {
|
|
2919
|
-
descriptor = "professional software team member with a friendly and competent expression, smart casual attire";
|
|
2920
|
-
}
|
|
2921
|
-
return `Professional headshot portrait photo of a ${descriptor}, photorealistic, clean soft neutral background, natural diffused window lighting, shot at f/2.8, shallow depth of field, looking directly at camera, candid professional headshot style, no retouching artifacts, natural skin texture`;
|
|
2922
|
-
}
|
|
2923
|
-
function sleep(ms) {
|
|
2924
|
-
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
2925
|
-
}
|
|
2926
|
-
async function generatePersonaAvatar(personaName, personaRole, apiToken) {
|
|
2927
|
-
const prompt2 = buildAvatarPrompt(personaName, personaRole);
|
|
2928
|
-
console.log(`[avatar] Generating avatar for ${personaName} (${personaRole})`);
|
|
2929
|
-
const createRes = await fetch(`https://api.replicate.com/v1/models/${REPLICATE_MODEL}/predictions`, {
|
|
2930
|
-
method: "POST",
|
|
2931
|
-
headers: {
|
|
2932
|
-
Authorization: `Bearer ${apiToken}`,
|
|
2933
|
-
"Content-Type": "application/json",
|
|
2934
|
-
Prefer: "wait"
|
|
2935
|
-
},
|
|
2936
|
-
body: JSON.stringify({
|
|
2937
|
-
input: {
|
|
2938
|
-
prompt: prompt2,
|
|
2939
|
-
aspect_ratio: "1:1",
|
|
2940
|
-
output_format: "webp",
|
|
2941
|
-
output_quality: 85
|
|
2942
|
-
}
|
|
2943
|
-
})
|
|
2944
|
-
});
|
|
2945
|
-
if (!createRes.ok) {
|
|
2946
|
-
const body = await createRes.text();
|
|
2947
|
-
throw new Error(`Replicate create failed (${createRes.status}): ${body}`);
|
|
2948
|
-
}
|
|
2949
|
-
let prediction = await createRes.json();
|
|
2950
|
-
if (prediction.status === "succeeded") {
|
|
2951
|
-
const url = extractOutputUrl(prediction.output);
|
|
2952
|
-
console.log(`[avatar] Generated for ${personaName}: ${url}`);
|
|
2953
|
-
return url;
|
|
2954
|
-
}
|
|
2955
|
-
for (let i = 0; i < MAX_POLLS; i++) {
|
|
2956
|
-
await sleep(POLL_INTERVAL_MS);
|
|
2957
|
-
const pollRes = await fetch(`https://api.replicate.com/v1/predictions/${prediction.id}`, { headers: { Authorization: `Bearer ${apiToken}` } });
|
|
2958
|
-
if (!pollRes.ok)
|
|
2959
|
-
continue;
|
|
2960
|
-
prediction = await pollRes.json();
|
|
2961
|
-
if (prediction.status === "succeeded") {
|
|
2962
|
-
const url = extractOutputUrl(prediction.output);
|
|
2963
|
-
console.log(`[avatar] Generated for ${personaName}: ${url}`);
|
|
2964
|
-
return url;
|
|
2965
|
-
}
|
|
2966
|
-
if (prediction.status === "failed" || prediction.status === "canceled") {
|
|
2967
|
-
throw new Error(`Replicate prediction ${prediction.status}: ${prediction.error ?? "unknown error"}`);
|
|
2968
|
-
}
|
|
2969
|
-
console.log(`[avatar] Polling\u2026 status=${prediction.status} (${i + 1}/${MAX_POLLS})`);
|
|
2970
|
-
}
|
|
2971
|
-
throw new Error("Replicate avatar generation timed out after 3 minutes");
|
|
2972
|
-
}
|
|
2973
|
-
var REPLICATE_MODEL, POLL_INTERVAL_MS, MAX_POLLS, PERSONA_PORTRAITS;
|
|
2974
|
-
var init_avatar_generator = __esm({
|
|
2975
|
-
"../core/dist/utils/avatar-generator.js"() {
|
|
2976
|
-
"use strict";
|
|
2977
|
-
REPLICATE_MODEL = "black-forest-labs/flux-1.1-pro";
|
|
2978
|
-
POLL_INTERVAL_MS = 3e3;
|
|
2979
|
-
MAX_POLLS = 60;
|
|
2980
|
-
PERSONA_PORTRAITS = {
|
|
2981
|
-
maya: [
|
|
2982
|
-
"South Asian woman in her late 20s with sharp dark eyes and straight black hair pulled back in a low ponytail",
|
|
2983
|
-
"wearing a dark charcoal blazer over a plain black turtleneck",
|
|
2984
|
-
"expression is focused and perceptive \u2014 the look of someone who notices details others miss",
|
|
2985
|
-
"minimal jewelry, small silver stud earrings"
|
|
2986
|
-
].join(", "),
|
|
2987
|
-
carlos: [
|
|
2988
|
-
"Hispanic man in his mid-30s with short dark wavy hair and a neatly trimmed beard",
|
|
2989
|
-
"wearing a navy blue henley shirt with sleeves slightly pushed up",
|
|
2990
|
-
"expression is calm and confident \u2014 easy authority, like someone used to making decisions",
|
|
2991
|
-
"slight smile that reads more thoughtful than warm"
|
|
2992
|
-
].join(", "),
|
|
2993
|
-
priya: [
|
|
2994
|
-
"Indian woman in her early 30s with shoulder-length dark brown hair with subtle highlights",
|
|
2995
|
-
"wearing a soft olive green cardigan over a white crew-neck t-shirt",
|
|
2996
|
-
"expression is alert and curious \u2014 bright eyes, slight head tilt, like she just thought of something interesting",
|
|
2997
|
-
"round tortoiseshell glasses"
|
|
2998
|
-
].join(", "),
|
|
2999
|
-
dev: [
|
|
3000
|
-
"East Asian man in his late 20s with short textured black hair styled casually",
|
|
3001
|
-
"wearing a heather gray crewneck sweatshirt",
|
|
3002
|
-
"expression is friendly and approachable \u2014 relaxed confidence, like someone in the middle of good work",
|
|
3003
|
-
"clean-shaven, natural look"
|
|
3004
|
-
].join(", ")
|
|
3005
|
-
};
|
|
3006
|
-
}
|
|
3007
|
-
});
|
|
3008
|
-
|
|
3009
2707
|
// ../core/dist/utils/logger.js
|
|
3010
2708
|
import fs4 from "fs";
|
|
3011
2709
|
import os2 from "os";
|
|
@@ -3761,7 +3459,7 @@ function getLockFilePaths(projectDir) {
|
|
|
3761
3459
|
reviewer: `${LOCK_FILE_PREFIX}pr-reviewer-${runtimeKey}.lock`
|
|
3762
3460
|
};
|
|
3763
3461
|
}
|
|
3764
|
-
function
|
|
3462
|
+
function sleep(ms) {
|
|
3765
3463
|
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
3766
3464
|
}
|
|
3767
3465
|
async function cancelProcess(processType, lockPath, force = false) {
|
|
@@ -3803,7 +3501,7 @@ async function cancelProcess(processType, lockPath, force = false) {
|
|
|
3803
3501
|
message: `Failed to send SIGTERM to ${processType} (PID ${pid}): ${errorMessage}`
|
|
3804
3502
|
};
|
|
3805
3503
|
}
|
|
3806
|
-
await
|
|
3504
|
+
await sleep(3e3);
|
|
3807
3505
|
if (!isProcessRunning(pid)) {
|
|
3808
3506
|
try {
|
|
3809
3507
|
fs7.unlinkSync(lockPath);
|
|
@@ -3823,7 +3521,7 @@ async function cancelProcess(processType, lockPath, force = false) {
|
|
|
3823
3521
|
message: `Failed to send SIGKILL to ${processType} (PID ${pid}): ${errorMessage}`
|
|
3824
3522
|
};
|
|
3825
3523
|
}
|
|
3826
|
-
await
|
|
3524
|
+
await sleep(500);
|
|
3827
3525
|
if (!isProcessRunning(pid)) {
|
|
3828
3526
|
try {
|
|
3829
3527
|
fs7.unlinkSync(lockPath);
|
|
@@ -7032,7 +6730,6 @@ __export(dist_exports, {
|
|
|
7032
6730
|
formatTelegramPayload: () => formatTelegramPayload,
|
|
7033
6731
|
generateItemHash: () => generateItemHash,
|
|
7034
6732
|
generateMarker: () => generateMarker,
|
|
7035
|
-
generatePersonaAvatar: () => generatePersonaAvatar,
|
|
7036
6733
|
getBranchTipTimestamp: () => getBranchTipTimestamp,
|
|
7037
6734
|
getCrontabInfo: () => getCrontabInfo,
|
|
7038
6735
|
getDb: () => getDb,
|
|
@@ -7132,7 +6829,7 @@ __export(dist_exports, {
|
|
|
7132
6829
|
scanRoadmap: () => scanRoadmap,
|
|
7133
6830
|
sendNotifications: () => sendNotifications,
|
|
7134
6831
|
sendWebhook: () => sendWebhook,
|
|
7135
|
-
sleep: () =>
|
|
6832
|
+
sleep: () => sleep,
|
|
7136
6833
|
sliceNextItem: () => sliceNextItem,
|
|
7137
6834
|
sliceRoadmapItem: () => sliceRoadmapItem,
|
|
7138
6835
|
slugify: () => slugify,
|
|
@@ -7166,7 +6863,6 @@ var init_dist = __esm({
|
|
|
7166
6863
|
init_migrations();
|
|
7167
6864
|
init_json_state_migrator();
|
|
7168
6865
|
init_container();
|
|
7169
|
-
init_avatar_generator();
|
|
7170
6866
|
init_logger();
|
|
7171
6867
|
init_cancel();
|
|
7172
6868
|
init_checks();
|
|
@@ -14362,7 +14058,7 @@ async function promptConfirmation(prompt2) {
|
|
|
14362
14058
|
});
|
|
14363
14059
|
});
|
|
14364
14060
|
}
|
|
14365
|
-
function
|
|
14061
|
+
function sleep2(ms) {
|
|
14366
14062
|
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14367
14063
|
}
|
|
14368
14064
|
function isProcessRunning3(pid) {
|
|
@@ -14419,7 +14115,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14419
14115
|
};
|
|
14420
14116
|
}
|
|
14421
14117
|
info(`Sent SIGTERM to ${processType} (PID ${pid}), waiting 3 seconds...`);
|
|
14422
|
-
await
|
|
14118
|
+
await sleep2(3e3);
|
|
14423
14119
|
if (!isProcessRunning3(pid)) {
|
|
14424
14120
|
try {
|
|
14425
14121
|
fs34.unlinkSync(lockPath);
|
|
@@ -14454,7 +14150,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
14454
14150
|
message: `Failed to send SIGKILL to ${processType} (PID ${pid}): ${errorMessage}`
|
|
14455
14151
|
};
|
|
14456
14152
|
}
|
|
14457
|
-
await
|
|
14153
|
+
await sleep2(500);
|
|
14458
14154
|
if (!isProcessRunning3(pid)) {
|
|
14459
14155
|
try {
|
|
14460
14156
|
fs34.unlinkSync(lockPath);
|