@happyvertical/projects 0.80.0 → 0.80.1
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/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +396 -446
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (existsSync(metaSrc)) {
|
|
18
|
-
copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
19
|
-
}
|
|
5
|
+
//#region src/cli/claude-context.ts
|
|
6
|
+
/**
|
|
7
|
+
* CLI script to install agent context for @happyvertical/projects
|
|
8
|
+
* Run the published context installer binary for this package.
|
|
9
|
+
*/
|
|
10
|
+
var pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
11
|
+
var targetDir = join(process.cwd(), ".claude");
|
|
12
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
13
|
+
var pkgName = "projects";
|
|
14
|
+
var agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
15
|
+
var metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
16
|
+
if (existsSync(agentMdSrc)) copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
17
|
+
if (existsSync(metaSrc)) copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
20
18
|
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
21
|
-
//#
|
|
19
|
+
//#endregion
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=claude-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-context.js","sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/projects\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'projects';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"
|
|
1
|
+
{"version":3,"file":"claude-context.js","names":[],"sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/projects\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'projects';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"mappings":";;;;;;;;;AAUA,IAAM,UAAU,KADA,QAAQ,cAAc,OAAO,KAAK,GAAG,CAChC,GAAS,OAAO;AACrC,IAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAG1C,IAAM,UAAU;AAChB,IAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,IAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GACvB,aAAa,YAAY,KAAK,WAAW,QAAQ,QAAQ,IAAI,CAAC;AAGhE,IAAI,WAAW,OAAO,GACpB,aAAa,SAAS,KAAK,WAAW,QAAQ,QAAQ,WAAW,CAAC;AAGpE,QAAQ,IAAI,8BAA8B,QAAQ,qBAAqB"}
|
package/dist/index.js
CHANGED
|
@@ -1,138 +1,170 @@
|
|
|
1
1
|
import { GraphQLClient } from "@happyvertical/graphql";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/errors.ts
|
|
15
|
+
/**
|
|
16
|
+
* Project error handling
|
|
17
|
+
*/
|
|
18
|
+
var ProjectErrorCode = /* @__PURE__ */ function(ProjectErrorCode) {
|
|
19
|
+
ProjectErrorCode["NOT_FOUND"] = "NOT_FOUND";
|
|
20
|
+
ProjectErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
21
|
+
ProjectErrorCode["INVALID_STATUS"] = "INVALID_STATUS";
|
|
22
|
+
ProjectErrorCode["INVALID_FIELD"] = "INVALID_FIELD";
|
|
23
|
+
ProjectErrorCode["ITEM_NOT_IN_PROJECT"] = "ITEM_NOT_IN_PROJECT";
|
|
24
|
+
ProjectErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
|
|
25
|
+
ProjectErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
26
|
+
ProjectErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
27
|
+
ProjectErrorCode["UNKNOWN"] = "UNKNOWN";
|
|
28
|
+
return ProjectErrorCode;
|
|
29
|
+
}({});
|
|
30
|
+
var ProjectError = class ProjectError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
statusCode;
|
|
33
|
+
response;
|
|
34
|
+
constructor(message, code, statusCode, response) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.statusCode = statusCode;
|
|
38
|
+
this.response = response;
|
|
39
|
+
this.name = "ProjectError";
|
|
40
|
+
Error.captureStackTrace(this, this.constructor);
|
|
41
|
+
}
|
|
42
|
+
static fromHTTPStatus(statusCode, message, response) {
|
|
43
|
+
let code;
|
|
44
|
+
switch (statusCode) {
|
|
45
|
+
case 404:
|
|
46
|
+
code = "NOT_FOUND";
|
|
47
|
+
break;
|
|
48
|
+
case 401:
|
|
49
|
+
code = "UNAUTHORIZED";
|
|
50
|
+
break;
|
|
51
|
+
case 403:
|
|
52
|
+
code = "UNAUTHORIZED";
|
|
53
|
+
break;
|
|
54
|
+
case 429:
|
|
55
|
+
code = "RATE_LIMITED";
|
|
56
|
+
break;
|
|
57
|
+
case 422:
|
|
58
|
+
code = "VALIDATION_ERROR";
|
|
59
|
+
break;
|
|
60
|
+
default: code = "UNKNOWN";
|
|
61
|
+
}
|
|
62
|
+
return new ProjectError(message, code, statusCode, response);
|
|
63
|
+
}
|
|
64
|
+
static networkError(message, cause) {
|
|
65
|
+
return new ProjectError(message, "NETWORK_ERROR", void 0, cause);
|
|
66
|
+
}
|
|
67
|
+
isRetryable() {
|
|
68
|
+
return this.code === "RATE_LIMITED" || this.code === "NETWORK_ERROR";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/factory.ts
|
|
73
|
+
/**
|
|
74
|
+
* Project factory function
|
|
75
|
+
*
|
|
76
|
+
* Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* Check if value is already a project instance
|
|
80
|
+
*/
|
|
58
81
|
function isProjectInstance(value) {
|
|
59
|
-
|
|
82
|
+
return value !== null && typeof value === "object" && "getProject" in value && "addItem" in value && "updateItemStatus" in value && typeof value.getProject === "function";
|
|
60
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Get a project management client instance
|
|
86
|
+
*
|
|
87
|
+
* This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.
|
|
88
|
+
* It can accept either a configuration object or an existing project instance.
|
|
89
|
+
*
|
|
90
|
+
* @param options - Project configuration or existing project instance
|
|
91
|
+
* @returns Promise resolving to project interface
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { getProject } from '@happyvertical/projects';
|
|
96
|
+
*
|
|
97
|
+
* // Create a GitHub Projects client
|
|
98
|
+
* const project = await getProject({
|
|
99
|
+
* type: 'github',
|
|
100
|
+
* owner: 'happyvertical',
|
|
101
|
+
* projectId: 'PVT_kwDOB9Y8ns4A8-TY',
|
|
102
|
+
* token: process.env.GITHUB_TOKEN,
|
|
103
|
+
* statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
|
|
104
|
+
* statusOptions: {
|
|
105
|
+
* 'New': 'option-id-1',
|
|
106
|
+
* 'Backlog': 'option-id-2',
|
|
107
|
+
* 'In Progress': 'option-id-3',
|
|
108
|
+
* 'Done': 'option-id-4'
|
|
109
|
+
* }
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* // Use the client
|
|
113
|
+
* await project.addItem(issueNodeId);
|
|
114
|
+
* await project.updateItemStatus(itemId, 'In Progress');
|
|
115
|
+
*
|
|
116
|
+
* // Pass existing instance (returns it unchanged)
|
|
117
|
+
* const sameProject = await getProject(project);
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
61
120
|
async function getProject(options) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
if (!options.token) {
|
|
78
|
-
throw new ProjectError(
|
|
79
|
-
"Authentication token is required",
|
|
80
|
-
ProjectErrorCode.UNAUTHORIZED
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
switch (options.type) {
|
|
84
|
-
case "github": {
|
|
85
|
-
const { GitHubProject: GitHubProject2 } = await Promise.resolve().then(() => index);
|
|
86
|
-
return new GitHubProject2(options);
|
|
87
|
-
}
|
|
88
|
-
case "jira":
|
|
89
|
-
throw new ProjectError(
|
|
90
|
-
"Jira support not yet implemented",
|
|
91
|
-
ProjectErrorCode.UNKNOWN
|
|
92
|
-
);
|
|
93
|
-
case "zenhub":
|
|
94
|
-
throw new ProjectError(
|
|
95
|
-
"ZenHub support not yet implemented",
|
|
96
|
-
ProjectErrorCode.UNKNOWN
|
|
97
|
-
);
|
|
98
|
-
case "linear":
|
|
99
|
-
throw new ProjectError(
|
|
100
|
-
"Linear support not yet implemented",
|
|
101
|
-
ProjectErrorCode.UNKNOWN
|
|
102
|
-
);
|
|
103
|
-
default:
|
|
104
|
-
throw new ProjectError(
|
|
105
|
-
`Unsupported project type: ${options.type}`,
|
|
106
|
-
ProjectErrorCode.VALIDATION_ERROR
|
|
107
|
-
);
|
|
108
|
-
}
|
|
121
|
+
if (isProjectInstance(options)) return options;
|
|
122
|
+
if (!options.type) throw new ProjectError("Project type is required", ProjectErrorCode.VALIDATION_ERROR);
|
|
123
|
+
if (!options.projectId) throw new ProjectError("Project ID is required", ProjectErrorCode.VALIDATION_ERROR);
|
|
124
|
+
if (!options.token) throw new ProjectError("Authentication token is required", ProjectErrorCode.UNAUTHORIZED);
|
|
125
|
+
switch (options.type) {
|
|
126
|
+
case "github": {
|
|
127
|
+
const { GitHubProject } = await Promise.resolve().then(() => github_exports);
|
|
128
|
+
return new GitHubProject(options);
|
|
129
|
+
}
|
|
130
|
+
case "jira": throw new ProjectError("Jira support not yet implemented", ProjectErrorCode.UNKNOWN);
|
|
131
|
+
case "zenhub": throw new ProjectError("ZenHub support not yet implemented", ProjectErrorCode.UNKNOWN);
|
|
132
|
+
case "linear": throw new ProjectError("Linear support not yet implemented", ProjectErrorCode.UNKNOWN);
|
|
133
|
+
default: throw new ProjectError(`Unsupported project type: ${options.type}`, ProjectErrorCode.VALIDATION_ERROR);
|
|
134
|
+
}
|
|
109
135
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/github/index.ts
|
|
138
|
+
/**
|
|
139
|
+
* GitHub Projects V2 implementation
|
|
140
|
+
*/
|
|
141
|
+
var github_exports = /* @__PURE__ */ __exportAll({ GitHubProject: () => GitHubProject });
|
|
142
|
+
var GITHUB_DEFAULT_ITEMS_LIMIT = 100;
|
|
143
|
+
var GITHUB_ITEMS_PAGE_SIZE = 20;
|
|
144
|
+
/**
|
|
145
|
+
* GitHub Projects V2 implementation
|
|
146
|
+
*/
|
|
147
|
+
var GitHubProject = class {
|
|
148
|
+
graphql;
|
|
149
|
+
projectId;
|
|
150
|
+
owner;
|
|
151
|
+
repo;
|
|
152
|
+
statusFieldId;
|
|
153
|
+
statusOptions;
|
|
154
|
+
constructor(config) {
|
|
155
|
+
if (config.type !== "github") throw new Error("Invalid config type for GitHubProject");
|
|
156
|
+
this.graphql = new GraphQLClient({
|
|
157
|
+
endpoint: "https://api.github.com/graphql",
|
|
158
|
+
token: config.token
|
|
159
|
+
});
|
|
160
|
+
this.projectId = config.projectId;
|
|
161
|
+
this.owner = config.owner;
|
|
162
|
+
this.repo = config.repo;
|
|
163
|
+
this.statusFieldId = config.statusFieldId;
|
|
164
|
+
this.statusOptions = config.statusOptions;
|
|
165
|
+
}
|
|
166
|
+
async getProject() {
|
|
167
|
+
const data = await this.graphql.query(`
|
|
136
168
|
query($projectId: ID!) {
|
|
137
169
|
node(id: $projectId) {
|
|
138
170
|
... on ProjectV2 {
|
|
@@ -171,51 +203,45 @@ class GitHubProject {
|
|
|
171
203
|
}
|
|
172
204
|
}
|
|
173
205
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*
|
|
214
|
-
* @param contentId - GitHub node ID for the issue or pull request
|
|
215
|
-
* @returns The created provider-neutral project item
|
|
216
|
-
*/
|
|
217
|
-
async addItem(contentId) {
|
|
218
|
-
const mutation = `
|
|
206
|
+
`, { projectId: this.projectId });
|
|
207
|
+
const statusField = data.node.fields.nodes.find((f) => f.name === "Status" && f.options);
|
|
208
|
+
return {
|
|
209
|
+
id: data.node.id,
|
|
210
|
+
title: data.node.title,
|
|
211
|
+
description: data.node.shortDescription,
|
|
212
|
+
owner: data.node.owner.login,
|
|
213
|
+
url: data.node.url,
|
|
214
|
+
statuses: statusField?.options ? statusField.options.map((opt, index) => ({
|
|
215
|
+
id: opt.id,
|
|
216
|
+
name: opt.name,
|
|
217
|
+
description: opt.description,
|
|
218
|
+
color: opt.color,
|
|
219
|
+
order: index
|
|
220
|
+
})) : [],
|
|
221
|
+
fields: data.node.fields.nodes.map((f) => ({
|
|
222
|
+
id: f.id,
|
|
223
|
+
name: f.name,
|
|
224
|
+
type: this.mapFieldType(f.dataType),
|
|
225
|
+
options: f.options?.map((opt) => ({
|
|
226
|
+
id: opt.id,
|
|
227
|
+
name: opt.name,
|
|
228
|
+
description: opt.description,
|
|
229
|
+
color: opt.color
|
|
230
|
+
}))
|
|
231
|
+
}))
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Add an issue or pull request to the configured GitHub project.
|
|
236
|
+
*
|
|
237
|
+
* The mutation result contains item identity and content type only. Use
|
|
238
|
+
* {@link listItems} when content metadata, status, or fields are required.
|
|
239
|
+
*
|
|
240
|
+
* @param contentId - GitHub node ID for the issue or pull request
|
|
241
|
+
* @returns The created provider-neutral project item
|
|
242
|
+
*/
|
|
243
|
+
async addItem(contentId) {
|
|
244
|
+
const data = await this.graphql.mutate(`
|
|
219
245
|
mutation($projectId: ID!, $contentId: ID!) {
|
|
220
246
|
addProjectV2ItemById(input: {
|
|
221
247
|
projectId: $projectId
|
|
@@ -236,20 +262,19 @@ class GitHubProject {
|
|
|
236
262
|
}
|
|
237
263
|
}
|
|
238
264
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const mutation = `
|
|
265
|
+
`, {
|
|
266
|
+
projectId: this.projectId,
|
|
267
|
+
contentId
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
id: data.addProjectV2ItemById.item.id,
|
|
271
|
+
contentId: data.addProjectV2ItemById.item.content.id,
|
|
272
|
+
fields: {},
|
|
273
|
+
type: data.addProjectV2ItemById.item.content.typename
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async removeItem(itemId) {
|
|
277
|
+
await this.graphql.mutate(`
|
|
253
278
|
mutation($projectId: ID!, $itemId: ID!) {
|
|
254
279
|
deleteProjectV2Item(input: {
|
|
255
280
|
projectId: $projectId
|
|
@@ -258,23 +283,22 @@ class GitHubProject {
|
|
|
258
283
|
deletedItemId
|
|
259
284
|
}
|
|
260
285
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const query = `
|
|
286
|
+
`, {
|
|
287
|
+
projectId: this.projectId,
|
|
288
|
+
itemId
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get one GitHub Projects V2 item by its project-item ID.
|
|
293
|
+
*
|
|
294
|
+
* This identity lookup does not hydrate content metadata, status, or fields.
|
|
295
|
+
* Use {@link listItems} for the enriched board-item representation.
|
|
296
|
+
*
|
|
297
|
+
* @param itemId - GitHub project-item node ID
|
|
298
|
+
* @returns The item, or `null` when it or its content is inaccessible
|
|
299
|
+
*/
|
|
300
|
+
async getItem(itemId) {
|
|
301
|
+
const data = await this.graphql.query(`
|
|
278
302
|
query($itemId: ID!) {
|
|
279
303
|
node(id: $itemId) {
|
|
280
304
|
... on ProjectV2Item {
|
|
@@ -296,29 +320,26 @@ class GitHubProject {
|
|
|
296
320
|
}
|
|
297
321
|
}
|
|
298
322
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
*/
|
|
320
|
-
async listItems(filters) {
|
|
321
|
-
const query = `
|
|
323
|
+
`, { itemId });
|
|
324
|
+
if (!data.node?.content?.id || !data.node.content.typename) return null;
|
|
325
|
+
return {
|
|
326
|
+
id: data.node.id,
|
|
327
|
+
contentId: data.node.content.id,
|
|
328
|
+
fields: {},
|
|
329
|
+
type: data.node.content.typename
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* List GitHub Projects V2 items after an optional cursor.
|
|
334
|
+
*
|
|
335
|
+
* Deleted or inaccessible content is skipped, and the result defaults to at
|
|
336
|
+
* most 100 accessible items to preserve the adapter's historical limit.
|
|
337
|
+
*
|
|
338
|
+
* @param filters - Optional cursor and maximum result count
|
|
339
|
+
* @returns Provider-neutral project items with content metadata and fields
|
|
340
|
+
*/
|
|
341
|
+
async listItems(filters) {
|
|
342
|
+
const query = `
|
|
322
343
|
query($projectId: ID!, $first: Int!, $after: String) {
|
|
323
344
|
node(id: $projectId) {
|
|
324
345
|
... on ProjectV2 {
|
|
@@ -508,171 +529,111 @@ class GitHubProject {
|
|
|
508
529
|
name
|
|
509
530
|
}
|
|
510
531
|
`;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
(pullRequest) => pullRequest ? [pullRequest.url] : []
|
|
617
|
-
);
|
|
618
|
-
case "ProjectV2ItemFieldRepositoryValue":
|
|
619
|
-
return value.repository?.nameWithOwner;
|
|
620
|
-
case "ProjectV2ItemFieldReviewerValue":
|
|
621
|
-
return value.reviewers?.nodes.flatMap((reviewer) => {
|
|
622
|
-
const name = reviewer?.login ?? reviewer?.slug;
|
|
623
|
-
return name ? [name] : [];
|
|
624
|
-
});
|
|
625
|
-
case "ProjectV2ItemFieldSingleSelectValue":
|
|
626
|
-
return value.name;
|
|
627
|
-
case "ProjectV2ItemFieldTextValue":
|
|
628
|
-
return value.text;
|
|
629
|
-
case "ProjectV2ItemFieldUserValue":
|
|
630
|
-
return value.users?.nodes.flatMap((user) => user ? [user.login] : []);
|
|
631
|
-
case "ProjectV2ItemIssueFieldValue": {
|
|
632
|
-
const issueValue = value.issueFieldValue;
|
|
633
|
-
switch (issueValue?.typename) {
|
|
634
|
-
case "IssueFieldDateValue":
|
|
635
|
-
return issueValue.dateValue;
|
|
636
|
-
case "IssueFieldMultiSelectValue":
|
|
637
|
-
return issueValue.multiSelectOptions?.map((option) => option.name);
|
|
638
|
-
case "IssueFieldNumberValue":
|
|
639
|
-
return issueValue.numberValue;
|
|
640
|
-
case "IssueFieldSingleSelectValue":
|
|
641
|
-
return issueValue.name ?? issueValue.singleSelectValue;
|
|
642
|
-
case "IssueFieldTextValue":
|
|
643
|
-
return issueValue.textValue;
|
|
644
|
-
default:
|
|
645
|
-
return void 0;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
default:
|
|
649
|
-
return void 0;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
// Status Management
|
|
653
|
-
async updateItemStatus(itemId, status) {
|
|
654
|
-
if (!this.statusFieldId || !this.statusOptions) {
|
|
655
|
-
throw new ProjectError(
|
|
656
|
-
"Status field configuration is required. Provide statusFieldId and statusOptions in config.",
|
|
657
|
-
ProjectErrorCode.INVALID_STATUS
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
const statusOptionId = this.statusOptions[status];
|
|
661
|
-
if (!statusOptionId) {
|
|
662
|
-
throw new ProjectError(
|
|
663
|
-
`Status "${status}" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(", ")}`,
|
|
664
|
-
ProjectErrorCode.INVALID_STATUS
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
await this.updateItemField(itemId, this.statusFieldId, statusOptionId);
|
|
668
|
-
}
|
|
669
|
-
async listStatuses() {
|
|
670
|
-
const project = await this.getProject();
|
|
671
|
-
return project.statuses;
|
|
672
|
-
}
|
|
673
|
-
// Field Management
|
|
674
|
-
async updateItemField(itemId, fieldId, value) {
|
|
675
|
-
const mutation = `
|
|
532
|
+
const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;
|
|
533
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.floor(requestedLimit)) : GITHUB_DEFAULT_ITEMS_LIMIT;
|
|
534
|
+
if (limit === 0) return [];
|
|
535
|
+
const items = [];
|
|
536
|
+
let after = filters?.cursor;
|
|
537
|
+
let fillPage = false;
|
|
538
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
539
|
+
while (items.length < limit) {
|
|
540
|
+
const remaining = limit - items.length;
|
|
541
|
+
const first = fillPage ? GITHUB_ITEMS_PAGE_SIZE : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);
|
|
542
|
+
const data = await this.graphql.query(query, {
|
|
543
|
+
projectId: this.projectId,
|
|
544
|
+
first,
|
|
545
|
+
after
|
|
546
|
+
});
|
|
547
|
+
if (!data.node) throw new ProjectError(`GitHub project ${this.projectId} was not found or is inaccessible`, ProjectErrorCode.NOT_FOUND);
|
|
548
|
+
const connection = data.node.items;
|
|
549
|
+
let skippedContent = false;
|
|
550
|
+
for (const node of connection.nodes) {
|
|
551
|
+
if (!node) {
|
|
552
|
+
skippedContent = true;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const item = this.mapProjectItem(node);
|
|
556
|
+
if (item) items.push(item);
|
|
557
|
+
else skippedContent = true;
|
|
558
|
+
if (items.length >= limit) break;
|
|
559
|
+
}
|
|
560
|
+
fillPage = skippedContent;
|
|
561
|
+
const { endCursor, hasNextPage } = connection.pageInfo;
|
|
562
|
+
if (!hasNextPage || !endCursor || endCursor === after || seenCursors.has(endCursor)) break;
|
|
563
|
+
seenCursors.add(endCursor);
|
|
564
|
+
after = endCursor;
|
|
565
|
+
}
|
|
566
|
+
return items;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Convert one GitHub Projects V2 item into the provider-neutral item shape.
|
|
570
|
+
*/
|
|
571
|
+
mapProjectItem(item) {
|
|
572
|
+
const { content } = item;
|
|
573
|
+
if (!content) return null;
|
|
574
|
+
const fieldEntries = [];
|
|
575
|
+
let status;
|
|
576
|
+
for (const fieldValue of item.fieldValues.nodes) {
|
|
577
|
+
if (!fieldValue?.field) continue;
|
|
578
|
+
const value = this.mapProjectFieldValue(fieldValue);
|
|
579
|
+
if (value !== void 0) fieldEntries.push([fieldValue.field.name, value]);
|
|
580
|
+
if ((this.statusFieldId ? fieldValue.field.id === this.statusFieldId : fieldValue.field.name.toLowerCase() === "status") && typeof value === "string") status = value;
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
id: item.id,
|
|
584
|
+
contentId: content.id,
|
|
585
|
+
title: content.title,
|
|
586
|
+
url: content.url ?? null,
|
|
587
|
+
assignees: content.assignees.nodes.flatMap((assignee) => assignee ? [assignee.login] : []),
|
|
588
|
+
status,
|
|
589
|
+
fields: Object.fromEntries(fieldEntries),
|
|
590
|
+
type: content.typename
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Convert GitHub's field-value union into stable scalar or string-array values.
|
|
595
|
+
*/
|
|
596
|
+
mapProjectFieldValue(value) {
|
|
597
|
+
switch (value.typename) {
|
|
598
|
+
case "ProjectV2ItemFieldDateValue": return value.date;
|
|
599
|
+
case "ProjectV2ItemFieldIterationValue": return value.title;
|
|
600
|
+
case "ProjectV2ItemFieldLabelValue": return value.labels?.nodes.flatMap((label) => label ? [label.name] : []);
|
|
601
|
+
case "ProjectV2ItemFieldMilestoneValue": return value.milestone?.title;
|
|
602
|
+
case "ProjectV2ItemFieldNumberValue": return value.number;
|
|
603
|
+
case "ProjectV2ItemFieldPullRequestValue": return value.pullRequests?.nodes.flatMap((pullRequest) => pullRequest ? [pullRequest.url] : []);
|
|
604
|
+
case "ProjectV2ItemFieldRepositoryValue": return value.repository?.nameWithOwner;
|
|
605
|
+
case "ProjectV2ItemFieldReviewerValue": return value.reviewers?.nodes.flatMap((reviewer) => {
|
|
606
|
+
const name = reviewer?.login ?? reviewer?.slug;
|
|
607
|
+
return name ? [name] : [];
|
|
608
|
+
});
|
|
609
|
+
case "ProjectV2ItemFieldSingleSelectValue": return value.name;
|
|
610
|
+
case "ProjectV2ItemFieldTextValue": return value.text;
|
|
611
|
+
case "ProjectV2ItemFieldUserValue": return value.users?.nodes.flatMap((user) => user ? [user.login] : []);
|
|
612
|
+
case "ProjectV2ItemIssueFieldValue": {
|
|
613
|
+
const issueValue = value.issueFieldValue;
|
|
614
|
+
switch (issueValue?.typename) {
|
|
615
|
+
case "IssueFieldDateValue": return issueValue.dateValue;
|
|
616
|
+
case "IssueFieldMultiSelectValue": return issueValue.multiSelectOptions?.map((option) => option.name);
|
|
617
|
+
case "IssueFieldNumberValue": return issueValue.numberValue;
|
|
618
|
+
case "IssueFieldSingleSelectValue": return issueValue.name ?? issueValue.singleSelectValue;
|
|
619
|
+
case "IssueFieldTextValue": return issueValue.textValue;
|
|
620
|
+
default: return;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
default: return;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async updateItemStatus(itemId, status) {
|
|
627
|
+
if (!this.statusFieldId || !this.statusOptions) throw new ProjectError("Status field configuration is required. Provide statusFieldId and statusOptions in config.", ProjectErrorCode.INVALID_STATUS);
|
|
628
|
+
const statusOptionId = this.statusOptions[status];
|
|
629
|
+
if (!statusOptionId) throw new ProjectError(`Status "${status}" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(", ")}`, ProjectErrorCode.INVALID_STATUS);
|
|
630
|
+
await this.updateItemField(itemId, this.statusFieldId, statusOptionId);
|
|
631
|
+
}
|
|
632
|
+
async listStatuses() {
|
|
633
|
+
return (await this.getProject()).statuses;
|
|
634
|
+
}
|
|
635
|
+
async updateItemField(itemId, fieldId, value) {
|
|
636
|
+
await this.graphql.mutate(`
|
|
676
637
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {
|
|
677
638
|
updateProjectV2ItemFieldValue(input: {
|
|
678
639
|
projectId: $projectId
|
|
@@ -685,56 +646,45 @@ class GitHubProject {
|
|
|
685
646
|
}
|
|
686
647
|
}
|
|
687
648
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
case "single_select":
|
|
712
|
-
return "single_select";
|
|
713
|
-
case "iteration":
|
|
714
|
-
return "iteration";
|
|
715
|
-
default:
|
|
716
|
-
return "text";
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
721
|
-
__proto__: null,
|
|
722
|
-
GitHubProject
|
|
723
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
724
|
-
const KANBAN_STATUSES = [
|
|
725
|
-
"New",
|
|
726
|
-
"Backlog",
|
|
727
|
-
"Planning",
|
|
728
|
-
"Ready",
|
|
729
|
-
"In Progress",
|
|
730
|
-
"Review",
|
|
731
|
-
"Done"
|
|
732
|
-
];
|
|
733
|
-
export {
|
|
734
|
-
GitHubProject,
|
|
735
|
-
KANBAN_STATUSES,
|
|
736
|
-
ProjectError,
|
|
737
|
-
ProjectErrorCode,
|
|
738
|
-
getProject
|
|
649
|
+
`, {
|
|
650
|
+
projectId: this.projectId,
|
|
651
|
+
itemId,
|
|
652
|
+
fieldId,
|
|
653
|
+
value: { singleSelectOptionId: value }
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async listFields() {
|
|
657
|
+
return (await this.getProject()).fields;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Map GitHub field type to our standard types
|
|
661
|
+
*/
|
|
662
|
+
mapFieldType(dataType) {
|
|
663
|
+
switch (dataType.toLowerCase()) {
|
|
664
|
+
case "text": return "text";
|
|
665
|
+
case "number": return "number";
|
|
666
|
+
case "date": return "date";
|
|
667
|
+
case "single_select": return "single_select";
|
|
668
|
+
case "iteration": return "iteration";
|
|
669
|
+
default: return "text";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
739
672
|
};
|
|
740
|
-
//#
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/types.ts
|
|
675
|
+
/**
|
|
676
|
+
* Kanban status names for 6-lane structure
|
|
677
|
+
*/
|
|
678
|
+
var KANBAN_STATUSES = [
|
|
679
|
+
"New",
|
|
680
|
+
"Backlog",
|
|
681
|
+
"Planning",
|
|
682
|
+
"Ready",
|
|
683
|
+
"In Progress",
|
|
684
|
+
"Review",
|
|
685
|
+
"Done"
|
|
686
|
+
];
|
|
687
|
+
//#endregion
|
|
688
|
+
export { GitHubProject, KANBAN_STATUSES, ProjectError, ProjectErrorCode, getProject };
|
|
689
|
+
|
|
690
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\nconst GITHUB_DEFAULT_ITEMS_LIMIT = 100;\nconst GITHUB_ITEMS_PAGE_SIZE = 20;\n\ninterface GitHubProjectItemReference {\n typename: ProjectItem['type'];\n id: string;\n}\n\ninterface GitHubProjectItemContent extends GitHubProjectItemReference {\n title: string;\n url?: string;\n assignees: {\n nodes: Array<{ login: string } | null>;\n };\n}\n\ninterface GitHubProjectFieldValue {\n typename: string;\n field?: {\n id: string;\n name: string;\n };\n date?: string | null;\n title?: string;\n labels?: {\n nodes: Array<{ name: string } | null>;\n } | null;\n milestone?: {\n title: string;\n } | null;\n number?: number | null;\n pullRequests?: {\n nodes: Array<{ url: string } | null>;\n } | null;\n repository?: {\n nameWithOwner: string;\n } | null;\n reviewers?: {\n nodes: Array<{\n login?: string;\n slug?: string;\n } | null>;\n } | null;\n name?: string | null;\n text?: string | null;\n users?: {\n nodes: Array<{ login: string } | null>;\n } | null;\n issueFieldValue?: {\n typename: string;\n name?: string;\n dateValue?: string;\n multiSelectOptions?: Array<{ name: string }>;\n numberValue?: number;\n singleSelectValue?: string;\n textValue?: string;\n } | null;\n}\n\ninterface GitHubProjectItemNode {\n id: string;\n content: GitHubProjectItemContent | null;\n fieldValues: {\n nodes: Array<GitHubProjectFieldValue | null>;\n };\n}\n\ninterface GitHubProjectItemsPage {\n node: {\n items: {\n nodes: Array<GitHubProjectItemNode | null>;\n pageInfo: {\n hasNextPage: boolean;\n endCursor: string | null;\n };\n };\n } | null;\n}\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n /**\n * Add an issue or pull request to the configured GitHub project.\n *\n * The mutation result contains item identity and content type only. Use\n * {@link listItems} when content metadata, status, or fields are required.\n *\n * @param contentId - GitHub node ID for the issue or pull request\n * @returns The created provider-neutral project item\n */\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: GitHubProjectItemReference;\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: data.addProjectV2ItemById.item.content.typename,\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n /**\n * Get one GitHub Projects V2 item by its project-item ID.\n *\n * This identity lookup does not hydrate content metadata, status, or fields.\n * Use {@link listItems} for the enriched board-item representation.\n *\n * @param itemId - GitHub project-item node ID\n * @returns The item, or `null` when it or its content is inaccessible\n */\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n ... on DraftIssue {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: Partial<GitHubProjectItemReference> | null;\n } | null;\n };\n\n if (!data.node?.content?.id || !data.node.content.typename) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: data.node.content.typename,\n };\n }\n\n /**\n * List GitHub Projects V2 items after an optional cursor.\n *\n * Deleted or inaccessible content is skipped, and the result defaults to at\n * most 100 accessible items to preserve the adapter's historical limit.\n *\n * @param filters - Optional cursor and maximum result count\n * @returns Provider-neutral project items with content metadata and fields\n */\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on PullRequest {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on DraftIssue {\n id\n typename: __typename\n title\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n }\n fieldValues(first: 50) {\n nodes {\n typename: __typename\n # GitHub Projects have at most 50 fields. A 20-item page and\n # four 100-node nested connections stay below GitHub's\n # 500,000-node query limit while avoiding partial values.\n ... on ProjectV2ItemFieldDateValue {\n field {\n ...ProjectField\n }\n date\n }\n ... on ProjectV2ItemFieldIterationValue {\n field {\n ...ProjectField\n }\n title\n }\n ... on ProjectV2ItemFieldLabelValue {\n field {\n ...ProjectField\n }\n labels(first: 100) {\n nodes {\n name\n }\n }\n }\n ... on ProjectV2ItemFieldMilestoneValue {\n field {\n ...ProjectField\n }\n milestone {\n title\n }\n }\n ... on ProjectV2ItemFieldNumberValue {\n field {\n ...ProjectField\n }\n number\n }\n ... on ProjectV2ItemFieldPullRequestValue {\n field {\n ...ProjectField\n }\n pullRequests(first: 100) {\n nodes {\n url\n }\n }\n }\n ... on ProjectV2ItemFieldRepositoryValue {\n field {\n ...ProjectField\n }\n repository {\n nameWithOwner\n }\n }\n ... on ProjectV2ItemFieldReviewerValue {\n field {\n ...ProjectField\n }\n reviewers(first: 100) {\n nodes {\n ... on Bot {\n login\n }\n ... on EnterpriseTeam {\n slug\n }\n ... on Mannequin {\n login\n }\n ... on Team {\n slug\n }\n ... on User {\n login\n }\n }\n }\n }\n ... on ProjectV2ItemFieldSingleSelectValue {\n field {\n ...ProjectField\n }\n name\n }\n ... on ProjectV2ItemFieldTextValue {\n field {\n ...ProjectField\n }\n text\n }\n ... on ProjectV2ItemFieldUserValue {\n field {\n ...ProjectField\n }\n users(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on ProjectV2ItemIssueFieldValue {\n field {\n ...ProjectField\n }\n issueFieldValue {\n typename: __typename\n ... on IssueFieldDateValue {\n dateValue: value\n }\n ... on IssueFieldMultiSelectValue {\n multiSelectOptions: options {\n name\n }\n }\n ... on IssueFieldNumberValue {\n numberValue: value\n }\n ... on IssueFieldSingleSelectValue {\n name\n singleSelectValue: value\n }\n ... on IssueFieldTextValue {\n textValue: value\n }\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n\n fragment ProjectField on ProjectV2FieldCommon {\n id\n name\n }\n `;\n\n const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;\n const limit = Number.isFinite(requestedLimit)\n ? Math.max(0, Math.floor(requestedLimit))\n : GITHUB_DEFAULT_ITEMS_LIMIT;\n if (limit === 0) {\n return [];\n }\n\n const items: ProjectItem[] = [];\n let after = filters?.cursor;\n let fillPage = false;\n const seenCursors = new Set<string>();\n\n while (items.length < limit) {\n const remaining = limit - items.length;\n const first = fillPage\n ? GITHUB_ITEMS_PAGE_SIZE\n : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);\n const data = await this.graphql.query<GitHubProjectItemsPage>(query, {\n projectId: this.projectId,\n first,\n after,\n });\n if (!data.node) {\n throw new ProjectError(\n `GitHub project ${this.projectId} was not found or is inaccessible`,\n ProjectErrorCode.NOT_FOUND,\n );\n }\n const connection = data.node.items;\n let skippedContent = false;\n\n for (const node of connection.nodes) {\n if (!node) {\n skippedContent = true;\n continue;\n }\n const item = this.mapProjectItem(node);\n if (item) {\n items.push(item);\n } else {\n skippedContent = true;\n }\n if (items.length >= limit) {\n break;\n }\n }\n fillPage = skippedContent;\n\n const { endCursor, hasNextPage } = connection.pageInfo;\n if (\n !hasNextPage ||\n !endCursor ||\n endCursor === after ||\n seenCursors.has(endCursor)\n ) {\n break;\n }\n seenCursors.add(endCursor);\n after = endCursor;\n }\n\n return items;\n }\n\n /**\n * Convert one GitHub Projects V2 item into the provider-neutral item shape.\n */\n private mapProjectItem(item: GitHubProjectItemNode): ProjectItem | null {\n const { content } = item;\n if (!content) {\n return null;\n }\n\n const fieldEntries: Array<[string, unknown]> = [];\n let status: string | undefined;\n\n for (const fieldValue of item.fieldValues.nodes) {\n if (!fieldValue?.field) {\n continue;\n }\n const value = this.mapProjectFieldValue(fieldValue);\n if (value !== undefined) {\n fieldEntries.push([fieldValue.field.name, value]);\n }\n\n const isStatusField = this.statusFieldId\n ? fieldValue.field.id === this.statusFieldId\n : fieldValue.field.name.toLowerCase() === 'status';\n if (isStatusField && typeof value === 'string') {\n status = value;\n }\n }\n\n return {\n id: item.id,\n contentId: content.id,\n title: content.title,\n url: content.url ?? null,\n assignees: content.assignees.nodes.flatMap((assignee) =>\n assignee ? [assignee.login] : [],\n ),\n status,\n fields: Object.fromEntries(fieldEntries),\n type: content.typename,\n };\n }\n\n /**\n * Convert GitHub's field-value union into stable scalar or string-array values.\n */\n private mapProjectFieldValue(value: GitHubProjectFieldValue): unknown {\n switch (value.typename) {\n case 'ProjectV2ItemFieldDateValue':\n return value.date;\n case 'ProjectV2ItemFieldIterationValue':\n return value.title;\n case 'ProjectV2ItemFieldLabelValue':\n return value.labels?.nodes.flatMap((label) =>\n label ? [label.name] : [],\n );\n case 'ProjectV2ItemFieldMilestoneValue':\n return value.milestone?.title;\n case 'ProjectV2ItemFieldNumberValue':\n return value.number;\n case 'ProjectV2ItemFieldPullRequestValue':\n return value.pullRequests?.nodes.flatMap((pullRequest) =>\n pullRequest ? [pullRequest.url] : [],\n );\n case 'ProjectV2ItemFieldRepositoryValue':\n return value.repository?.nameWithOwner;\n case 'ProjectV2ItemFieldReviewerValue':\n return value.reviewers?.nodes.flatMap((reviewer) => {\n const name = reviewer?.login ?? reviewer?.slug;\n return name ? [name] : [];\n });\n case 'ProjectV2ItemFieldSingleSelectValue':\n return value.name;\n case 'ProjectV2ItemFieldTextValue':\n return value.text;\n case 'ProjectV2ItemFieldUserValue':\n return value.users?.nodes.flatMap((user) => (user ? [user.login] : []));\n case 'ProjectV2ItemIssueFieldValue': {\n const issueValue = value.issueFieldValue;\n switch (issueValue?.typename) {\n case 'IssueFieldDateValue':\n return issueValue.dateValue;\n case 'IssueFieldMultiSelectValue':\n return issueValue.multiSelectOptions?.map((option) => option.name);\n case 'IssueFieldNumberValue':\n return issueValue.numberValue;\n case 'IssueFieldSingleSelectValue':\n return issueValue.name ?? issueValue.singleSelectValue;\n case 'IssueFieldTextValue':\n return issueValue.textValue;\n default:\n return undefined;\n }\n }\n default:\n return undefined;\n }\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\n/**\n * A provider-neutral item on a project board.\n */\nexport interface ProjectItem {\n /** Provider project-item ID. */\n id: string;\n /** Provider content-node ID for the issue, pull request, or draft issue. */\n contentId: string;\n /** Content title when returned by the provider. */\n title?: string;\n /** Content URL, or `null` when the content has no provider URL. */\n url?: string | null;\n /** Provider usernames assigned to the content. */\n assignees?: string[];\n /** Selected status name from the configured or canonical status field. */\n status?: string;\n /** Provider field values keyed by field name. */\n fields: Record<string, unknown>;\n /** Kind of content represented by the project item. */\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\n/**\n * Options for reading project items.\n */\nexport interface ItemFilters {\n /** Return items in this status when supported by the provider. */\n status?: string;\n /** Return items assigned to these provider usernames when supported. */\n assignees?: string[];\n /** Return items with these labels when supported. */\n labels?: string[];\n /** Maximum number of items to return. Defaults to 100; zero returns none. */\n limit?: number;\n /** Provider cursor after which item pagination begins. */\n cursor?: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"names":["ProjectErrorCode","GitHubProject","index"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\nconst GITHUB_DEFAULT_ITEMS_LIMIT = 100;\nconst GITHUB_ITEMS_PAGE_SIZE = 20;\n\ninterface GitHubProjectItemReference {\n typename: ProjectItem['type'];\n id: string;\n}\n\ninterface GitHubProjectItemContent extends GitHubProjectItemReference {\n title: string;\n url?: string;\n assignees: {\n nodes: Array<{ login: string } | null>;\n };\n}\n\ninterface GitHubProjectFieldValue {\n typename: string;\n field?: {\n id: string;\n name: string;\n };\n date?: string | null;\n title?: string;\n labels?: {\n nodes: Array<{ name: string } | null>;\n } | null;\n milestone?: {\n title: string;\n } | null;\n number?: number | null;\n pullRequests?: {\n nodes: Array<{ url: string } | null>;\n } | null;\n repository?: {\n nameWithOwner: string;\n } | null;\n reviewers?: {\n nodes: Array<{\n login?: string;\n slug?: string;\n } | null>;\n } | null;\n name?: string | null;\n text?: string | null;\n users?: {\n nodes: Array<{ login: string } | null>;\n } | null;\n issueFieldValue?: {\n typename: string;\n name?: string;\n dateValue?: string;\n multiSelectOptions?: Array<{ name: string }>;\n numberValue?: number;\n singleSelectValue?: string;\n textValue?: string;\n } | null;\n}\n\ninterface GitHubProjectItemNode {\n id: string;\n content: GitHubProjectItemContent | null;\n fieldValues: {\n nodes: Array<GitHubProjectFieldValue | null>;\n };\n}\n\ninterface GitHubProjectItemsPage {\n node: {\n items: {\n nodes: Array<GitHubProjectItemNode | null>;\n pageInfo: {\n hasNextPage: boolean;\n endCursor: string | null;\n };\n };\n } | null;\n}\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n /**\n * Add an issue or pull request to the configured GitHub project.\n *\n * The mutation result contains item identity and content type only. Use\n * {@link listItems} when content metadata, status, or fields are required.\n *\n * @param contentId - GitHub node ID for the issue or pull request\n * @returns The created provider-neutral project item\n */\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: GitHubProjectItemReference;\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: data.addProjectV2ItemById.item.content.typename,\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n /**\n * Get one GitHub Projects V2 item by its project-item ID.\n *\n * This identity lookup does not hydrate content metadata, status, or fields.\n * Use {@link listItems} for the enriched board-item representation.\n *\n * @param itemId - GitHub project-item node ID\n * @returns The item, or `null` when it or its content is inaccessible\n */\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n ... on DraftIssue {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: Partial<GitHubProjectItemReference> | null;\n } | null;\n };\n\n if (!data.node?.content?.id || !data.node.content.typename) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: data.node.content.typename,\n };\n }\n\n /**\n * List GitHub Projects V2 items after an optional cursor.\n *\n * Deleted or inaccessible content is skipped, and the result defaults to at\n * most 100 accessible items to preserve the adapter's historical limit.\n *\n * @param filters - Optional cursor and maximum result count\n * @returns Provider-neutral project items with content metadata and fields\n */\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on PullRequest {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on DraftIssue {\n id\n typename: __typename\n title\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n }\n fieldValues(first: 50) {\n nodes {\n typename: __typename\n # GitHub Projects have at most 50 fields. A 20-item page and\n # four 100-node nested connections stay below GitHub's\n # 500,000-node query limit while avoiding partial values.\n ... on ProjectV2ItemFieldDateValue {\n field {\n ...ProjectField\n }\n date\n }\n ... on ProjectV2ItemFieldIterationValue {\n field {\n ...ProjectField\n }\n title\n }\n ... on ProjectV2ItemFieldLabelValue {\n field {\n ...ProjectField\n }\n labels(first: 100) {\n nodes {\n name\n }\n }\n }\n ... on ProjectV2ItemFieldMilestoneValue {\n field {\n ...ProjectField\n }\n milestone {\n title\n }\n }\n ... on ProjectV2ItemFieldNumberValue {\n field {\n ...ProjectField\n }\n number\n }\n ... on ProjectV2ItemFieldPullRequestValue {\n field {\n ...ProjectField\n }\n pullRequests(first: 100) {\n nodes {\n url\n }\n }\n }\n ... on ProjectV2ItemFieldRepositoryValue {\n field {\n ...ProjectField\n }\n repository {\n nameWithOwner\n }\n }\n ... on ProjectV2ItemFieldReviewerValue {\n field {\n ...ProjectField\n }\n reviewers(first: 100) {\n nodes {\n ... on Bot {\n login\n }\n ... on EnterpriseTeam {\n slug\n }\n ... on Mannequin {\n login\n }\n ... on Team {\n slug\n }\n ... on User {\n login\n }\n }\n }\n }\n ... on ProjectV2ItemFieldSingleSelectValue {\n field {\n ...ProjectField\n }\n name\n }\n ... on ProjectV2ItemFieldTextValue {\n field {\n ...ProjectField\n }\n text\n }\n ... on ProjectV2ItemFieldUserValue {\n field {\n ...ProjectField\n }\n users(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on ProjectV2ItemIssueFieldValue {\n field {\n ...ProjectField\n }\n issueFieldValue {\n typename: __typename\n ... on IssueFieldDateValue {\n dateValue: value\n }\n ... on IssueFieldMultiSelectValue {\n multiSelectOptions: options {\n name\n }\n }\n ... on IssueFieldNumberValue {\n numberValue: value\n }\n ... on IssueFieldSingleSelectValue {\n name\n singleSelectValue: value\n }\n ... on IssueFieldTextValue {\n textValue: value\n }\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n\n fragment ProjectField on ProjectV2FieldCommon {\n id\n name\n }\n `;\n\n const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;\n const limit = Number.isFinite(requestedLimit)\n ? Math.max(0, Math.floor(requestedLimit))\n : GITHUB_DEFAULT_ITEMS_LIMIT;\n if (limit === 0) {\n return [];\n }\n\n const items: ProjectItem[] = [];\n let after = filters?.cursor;\n let fillPage = false;\n const seenCursors = new Set<string>();\n\n while (items.length < limit) {\n const remaining = limit - items.length;\n const first = fillPage\n ? GITHUB_ITEMS_PAGE_SIZE\n : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);\n const data = await this.graphql.query<GitHubProjectItemsPage>(query, {\n projectId: this.projectId,\n first,\n after,\n });\n if (!data.node) {\n throw new ProjectError(\n `GitHub project ${this.projectId} was not found or is inaccessible`,\n ProjectErrorCode.NOT_FOUND,\n );\n }\n const connection = data.node.items;\n let skippedContent = false;\n\n for (const node of connection.nodes) {\n if (!node) {\n skippedContent = true;\n continue;\n }\n const item = this.mapProjectItem(node);\n if (item) {\n items.push(item);\n } else {\n skippedContent = true;\n }\n if (items.length >= limit) {\n break;\n }\n }\n fillPage = skippedContent;\n\n const { endCursor, hasNextPage } = connection.pageInfo;\n if (\n !hasNextPage ||\n !endCursor ||\n endCursor === after ||\n seenCursors.has(endCursor)\n ) {\n break;\n }\n seenCursors.add(endCursor);\n after = endCursor;\n }\n\n return items;\n }\n\n /**\n * Convert one GitHub Projects V2 item into the provider-neutral item shape.\n */\n private mapProjectItem(item: GitHubProjectItemNode): ProjectItem | null {\n const { content } = item;\n if (!content) {\n return null;\n }\n\n const fieldEntries: Array<[string, unknown]> = [];\n let status: string | undefined;\n\n for (const fieldValue of item.fieldValues.nodes) {\n if (!fieldValue?.field) {\n continue;\n }\n const value = this.mapProjectFieldValue(fieldValue);\n if (value !== undefined) {\n fieldEntries.push([fieldValue.field.name, value]);\n }\n\n const isStatusField = this.statusFieldId\n ? fieldValue.field.id === this.statusFieldId\n : fieldValue.field.name.toLowerCase() === 'status';\n if (isStatusField && typeof value === 'string') {\n status = value;\n }\n }\n\n return {\n id: item.id,\n contentId: content.id,\n title: content.title,\n url: content.url ?? null,\n assignees: content.assignees.nodes.flatMap((assignee) =>\n assignee ? [assignee.login] : [],\n ),\n status,\n fields: Object.fromEntries(fieldEntries),\n type: content.typename,\n };\n }\n\n /**\n * Convert GitHub's field-value union into stable scalar or string-array values.\n */\n private mapProjectFieldValue(value: GitHubProjectFieldValue): unknown {\n switch (value.typename) {\n case 'ProjectV2ItemFieldDateValue':\n return value.date;\n case 'ProjectV2ItemFieldIterationValue':\n return value.title;\n case 'ProjectV2ItemFieldLabelValue':\n return value.labels?.nodes.flatMap((label) =>\n label ? [label.name] : [],\n );\n case 'ProjectV2ItemFieldMilestoneValue':\n return value.milestone?.title;\n case 'ProjectV2ItemFieldNumberValue':\n return value.number;\n case 'ProjectV2ItemFieldPullRequestValue':\n return value.pullRequests?.nodes.flatMap((pullRequest) =>\n pullRequest ? [pullRequest.url] : [],\n );\n case 'ProjectV2ItemFieldRepositoryValue':\n return value.repository?.nameWithOwner;\n case 'ProjectV2ItemFieldReviewerValue':\n return value.reviewers?.nodes.flatMap((reviewer) => {\n const name = reviewer?.login ?? reviewer?.slug;\n return name ? [name] : [];\n });\n case 'ProjectV2ItemFieldSingleSelectValue':\n return value.name;\n case 'ProjectV2ItemFieldTextValue':\n return value.text;\n case 'ProjectV2ItemFieldUserValue':\n return value.users?.nodes.flatMap((user) => (user ? [user.login] : []));\n case 'ProjectV2ItemIssueFieldValue': {\n const issueValue = value.issueFieldValue;\n switch (issueValue?.typename) {\n case 'IssueFieldDateValue':\n return issueValue.dateValue;\n case 'IssueFieldMultiSelectValue':\n return issueValue.multiSelectOptions?.map((option) => option.name);\n case 'IssueFieldNumberValue':\n return issueValue.numberValue;\n case 'IssueFieldSingleSelectValue':\n return issueValue.name ?? issueValue.singleSelectValue;\n case 'IssueFieldTextValue':\n return issueValue.textValue;\n default:\n return undefined;\n }\n }\n default:\n return undefined;\n }\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\n/**\n * A provider-neutral item on a project board.\n */\nexport interface ProjectItem {\n /** Provider project-item ID. */\n id: string;\n /** Provider content-node ID for the issue, pull request, or draft issue. */\n contentId: string;\n /** Content title when returned by the provider. */\n title?: string;\n /** Content URL, or `null` when the content has no provider URL. */\n url?: string | null;\n /** Provider usernames assigned to the content. */\n assignees?: string[];\n /** Selected status name from the configured or canonical status field. */\n status?: string;\n /** Provider field values keyed by field name. */\n fields: Record<string, unknown>;\n /** Kind of content represented by the project item. */\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\n/**\n * Options for reading project items.\n */\nexport interface ItemFilters {\n /** Return items in this status when supported by the provider. */\n status?: string;\n /** Return items assigned to these provider usernames when supported. */\n assignees?: string[];\n /** Return items with these labels when supported. */\n labels?: string[];\n /** Maximum number of items to return. Defaults to 100; zero returns none. */\n limit?: number;\n /** Provider cursor after which item pagination begins. */\n cursor?: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/projects",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.1",
|
|
4
4
|
"description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
"have-projects-context": "./dist/cli/claude-context.js"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@happyvertical/graphql": "0.80.
|
|
19
|
-
"@happyvertical/repos": "0.80.
|
|
18
|
+
"@happyvertical/graphql": "0.80.1",
|
|
19
|
+
"@happyvertical/repos": "0.80.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "25.0.10",
|
|
23
|
-
"typescript": "
|
|
24
|
-
"vite": "
|
|
23
|
+
"typescript": "5.9.3",
|
|
24
|
+
"vite": "8.1.4",
|
|
25
25
|
"vite-plugin-dts": "4.5.4",
|
|
26
|
-
"vitest": "4.1.
|
|
26
|
+
"vitest": "4.1.10"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|