@happyvertical/projects 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +352 -0
- package/dist/claude-context.d.ts +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +409 -0
- package/dist/index.js.map +1 -0
- package/metadata.json +31 -0
- package/package.json +57 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @happyvertical/projects
|
|
2
|
+
|
|
3
|
+
<!-- BEGIN AGENT:GENERATED -->
|
|
4
|
+
## Purpose
|
|
5
|
+
Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear
|
|
6
|
+
|
|
7
|
+
## Package Map
|
|
8
|
+
- Package: `@happyvertical/projects`
|
|
9
|
+
- Hierarchy path: `@happyvertical/sdk > packages > projects`
|
|
10
|
+
- Workspace position: `21 of 30` local packages
|
|
11
|
+
- Internal dependencies: `@happyvertical/graphql`, `@happyvertical/repos`
|
|
12
|
+
- Internal dependents: `@happyvertical/github-actions`
|
|
13
|
+
- Knowledge graph files: `AGENT.md`, `metadata.json`, `ecosystem-manifest.json`
|
|
14
|
+
|
|
15
|
+
## Build & Test
|
|
16
|
+
```bash
|
|
17
|
+
pnpm --filter @happyvertical/projects build
|
|
18
|
+
pnpm --filter @happyvertical/projects test
|
|
19
|
+
pnpm --filter @happyvertical/projects clean
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Agent Correction Loops
|
|
23
|
+
- If module resolution or export errors mention a workspace dependency, build the dependency first (`pnpm --filter @happyvertical/graphql build`, `pnpm --filter @happyvertical/repos build`) and then rerun `pnpm --filter @happyvertical/projects build`.
|
|
24
|
+
- If tests or exports fail after API, type, or bundle changes, run `pnpm --filter @happyvertical/projects clean` followed by `pnpm --filter @happyvertical/projects build` and `pnpm --filter @happyvertical/projects test`.
|
|
25
|
+
- If failures span multiple packages or Turborepo ordering looks wrong, run `pnpm build` and `pnpm typecheck` from the repo root before retrying package-scoped commands.
|
|
26
|
+
|
|
27
|
+
## Ecosystem Relationships
|
|
28
|
+
- Provides: Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear
|
|
29
|
+
- Implements: none
|
|
30
|
+
- Requires: @happyvertical/graphql, @happyvertical/repos
|
|
31
|
+
- Stability: stable (Primary package surface is described as implemented and production-oriented.)
|
|
32
|
+
<!-- END AGENT:GENERATED -->
|
|
33
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# @happyvertical/projects
|
|
2
|
+
|
|
3
|
+
Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @happyvertical/projects @happyvertical/repos
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Claude Code Context
|
|
12
|
+
|
|
13
|
+
Install Claude Code context files for AI-assisted development:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx have-projects-context
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This copies the package's `AGENT.md` documentation and `metadata.json` metadata to your project's `.claude/` directory, enabling Claude to provide better assistance when working with this package.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Example
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { getProject } from '@happyvertical/projects';
|
|
27
|
+
import { getRepository } from '@happyvertical/repos';
|
|
28
|
+
|
|
29
|
+
// Get repository to retrieve issue node IDs
|
|
30
|
+
const repo = await getRepository({
|
|
31
|
+
type: 'github',
|
|
32
|
+
owner: 'happyvertical',
|
|
33
|
+
repo: 'sdk',
|
|
34
|
+
token: process.env.GITHUB_TOKEN
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Get project with status field configuration
|
|
38
|
+
const project = await getProject({
|
|
39
|
+
type: 'github',
|
|
40
|
+
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
|
|
41
|
+
token: process.env.GITHUB_TOKEN,
|
|
42
|
+
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
|
|
43
|
+
statusOptions: {
|
|
44
|
+
'New': 'option-id-1',
|
|
45
|
+
'Backlog': 'option-id-2',
|
|
46
|
+
'In Progress': 'option-id-3',
|
|
47
|
+
'Done': 'option-id-4'
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Add issue to project
|
|
52
|
+
const issue = await repo.getIssue(352);
|
|
53
|
+
const item = await project.addItem(issue.id);
|
|
54
|
+
|
|
55
|
+
// Update status
|
|
56
|
+
await project.updateItemStatus(item.id, 'In Progress');
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Kanban Workflow
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// List all items
|
|
63
|
+
const items = await project.listItems();
|
|
64
|
+
|
|
65
|
+
// Get available statuses
|
|
66
|
+
const statuses = await project.listStatuses();
|
|
67
|
+
|
|
68
|
+
// Move item through workflow
|
|
69
|
+
await project.updateItemStatus(itemId, 'Ready');
|
|
70
|
+
await project.updateItemStatus(itemId, 'In Progress');
|
|
71
|
+
await project.updateItemStatus(itemId, 'Review');
|
|
72
|
+
await project.updateItemStatus(itemId, 'Done');
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Custom Field Management
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// List all project fields
|
|
79
|
+
const fields = await project.listFields();
|
|
80
|
+
|
|
81
|
+
// Update custom field
|
|
82
|
+
const priorityField = fields.find(f => f.name === 'Priority');
|
|
83
|
+
if (priorityField) {
|
|
84
|
+
await project.updateItemField(itemId, priorityField.id, 'high-priority-option-id');
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### Factory Function
|
|
91
|
+
|
|
92
|
+
#### `getProject(options)`
|
|
93
|
+
|
|
94
|
+
Creates a project management client instance.
|
|
95
|
+
|
|
96
|
+
**Parameters:**
|
|
97
|
+
- `options: ProjectConfig | IProject` - Project configuration or existing instance
|
|
98
|
+
|
|
99
|
+
**Returns:** `Promise<IProject>`
|
|
100
|
+
|
|
101
|
+
**Example:**
|
|
102
|
+
```typescript
|
|
103
|
+
const project = await getProject({
|
|
104
|
+
type: 'github',
|
|
105
|
+
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
|
|
106
|
+
token: process.env.GITHUB_TOKEN,
|
|
107
|
+
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
|
|
108
|
+
statusOptions: {
|
|
109
|
+
'New': 'option-id-1',
|
|
110
|
+
'Backlog': 'option-id-2',
|
|
111
|
+
'In Progress': 'option-id-3',
|
|
112
|
+
'Done': 'option-id-4'
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### IProject Interface
|
|
118
|
+
|
|
119
|
+
#### Project Information
|
|
120
|
+
|
|
121
|
+
##### `getProject(): Promise<Project>`
|
|
122
|
+
|
|
123
|
+
Get project details including fields and statuses.
|
|
124
|
+
|
|
125
|
+
**Returns:** Project metadata with all fields and status options
|
|
126
|
+
|
|
127
|
+
#### Item Management
|
|
128
|
+
|
|
129
|
+
##### `addItem(contentId: string): Promise<ProjectItem>`
|
|
130
|
+
|
|
131
|
+
Add an issue or pull request to the project.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
- `contentId: string` - Node ID of the issue or PR (from @happyvertical/repos)
|
|
135
|
+
|
|
136
|
+
**Returns:** Created project item
|
|
137
|
+
|
|
138
|
+
##### `removeItem(itemId: string): Promise<void>`
|
|
139
|
+
|
|
140
|
+
Remove an item from the project.
|
|
141
|
+
|
|
142
|
+
**Parameters:**
|
|
143
|
+
- `itemId: string` - Project item ID
|
|
144
|
+
|
|
145
|
+
##### `getItem(itemId: string): Promise<ProjectItem | null>`
|
|
146
|
+
|
|
147
|
+
Get a specific project item.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `itemId: string` - Project item ID
|
|
151
|
+
|
|
152
|
+
**Returns:** Project item or null if not found
|
|
153
|
+
|
|
154
|
+
##### `listItems(filters?: ItemFilters): Promise<ProjectItem[]>`
|
|
155
|
+
|
|
156
|
+
List all items in the project.
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
- `filters?: ItemFilters` - Optional filters (limit, cursor, status)
|
|
160
|
+
|
|
161
|
+
**Returns:** Array of project items
|
|
162
|
+
|
|
163
|
+
#### Status Management
|
|
164
|
+
|
|
165
|
+
##### `updateItemStatus(itemId: string, status: string): Promise<void>`
|
|
166
|
+
|
|
167
|
+
Update the status of a project item.
|
|
168
|
+
|
|
169
|
+
**Parameters:**
|
|
170
|
+
- `itemId: string` - Project item ID
|
|
171
|
+
- `status: string` - Status name (must match statusOptions in config)
|
|
172
|
+
|
|
173
|
+
**Example:**
|
|
174
|
+
```typescript
|
|
175
|
+
await project.updateItemStatus(itemId, 'In Progress');
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
##### `listStatuses(): Promise<Status[]>`
|
|
179
|
+
|
|
180
|
+
Get all available status options.
|
|
181
|
+
|
|
182
|
+
**Returns:** Array of status definitions with IDs, names, colors
|
|
183
|
+
|
|
184
|
+
#### Field Management
|
|
185
|
+
|
|
186
|
+
##### `updateItemField(itemId: string, fieldId: string, value: unknown): Promise<void>`
|
|
187
|
+
|
|
188
|
+
Update a custom field value.
|
|
189
|
+
|
|
190
|
+
**Parameters:**
|
|
191
|
+
- `itemId: string` - Project item ID
|
|
192
|
+
- `fieldId: string` - Field ID
|
|
193
|
+
- `value: unknown` - Field value (type depends on field type)
|
|
194
|
+
|
|
195
|
+
##### `listFields(): Promise<Field[]>`
|
|
196
|
+
|
|
197
|
+
Get all project fields.
|
|
198
|
+
|
|
199
|
+
**Returns:** Array of field definitions
|
|
200
|
+
|
|
201
|
+
## GitHub Projects V2 Setup
|
|
202
|
+
|
|
203
|
+
### Finding Your Project ID
|
|
204
|
+
|
|
205
|
+
1. Navigate to your GitHub Project
|
|
206
|
+
2. In the URL, find the project number: `https://github.com/orgs/ORG/projects/123`
|
|
207
|
+
3. Use GitHub CLI to get the node ID:
|
|
208
|
+
```bash
|
|
209
|
+
gh api graphql -f query='
|
|
210
|
+
query {
|
|
211
|
+
organization(login: "ORG") {
|
|
212
|
+
projectV2(number: 123) {
|
|
213
|
+
id
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
'
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Finding Status Field ID and Options
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
gh api graphql -f query='
|
|
224
|
+
query {
|
|
225
|
+
node(id: "PVT_kwDOB9Y8ns4A8-TY") {
|
|
226
|
+
... on ProjectV2 {
|
|
227
|
+
fields(first: 20) {
|
|
228
|
+
nodes {
|
|
229
|
+
... on ProjectV2SingleSelectField {
|
|
230
|
+
id
|
|
231
|
+
name
|
|
232
|
+
options {
|
|
233
|
+
id
|
|
234
|
+
name
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
'
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Look for the "Status" field and note:
|
|
246
|
+
- Field ID (`PVTSSF_...`)
|
|
247
|
+
- Option IDs for each status
|
|
248
|
+
|
|
249
|
+
### Configuration Example
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const project = await getProject({
|
|
253
|
+
type: 'github',
|
|
254
|
+
projectId: 'PVT_kwDOB9Y8ns4A8-TY', // From step 1
|
|
255
|
+
token: process.env.GITHUB_TOKEN,
|
|
256
|
+
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY', // From step 2
|
|
257
|
+
statusOptions: {
|
|
258
|
+
'New': '47fc9ee4', // Option IDs from step 2
|
|
259
|
+
'Backlog': '4c6e9e0c',
|
|
260
|
+
'Planning': 'f75ad846',
|
|
261
|
+
'Ready': '98236657',
|
|
262
|
+
'In Progress': 'b5f9e3e1',
|
|
263
|
+
'Review': '5e1b3e8f',
|
|
264
|
+
'Done': '3f4e5d6c'
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Standard Kanban Statuses
|
|
270
|
+
|
|
271
|
+
The package exports standard kanban status names:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { KANBAN_STATUSES } from '@happyvertical/projects';
|
|
275
|
+
|
|
276
|
+
// ['New', 'Backlog', 'Planning', 'Ready', 'In Progress', 'Review', 'Done']
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Error Handling
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { ProjectError, ProjectErrorCode } from '@happyvertical/projects';
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await project.updateItemStatus(itemId, 'Invalid Status');
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error instanceof ProjectError) {
|
|
288
|
+
switch (error.code) {
|
|
289
|
+
case ProjectErrorCode.INVALID_STATUS:
|
|
290
|
+
console.error('Status not found:', error.message);
|
|
291
|
+
break;
|
|
292
|
+
case ProjectErrorCode.UNAUTHORIZED:
|
|
293
|
+
console.error('Token lacks permissions');
|
|
294
|
+
break;
|
|
295
|
+
case ProjectErrorCode.RATE_LIMITED:
|
|
296
|
+
console.error('Rate limited, retry after delay');
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Integration with @happyvertical/repos
|
|
304
|
+
|
|
305
|
+
Projects work seamlessly with repositories:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { getRepository } from '@happyvertical/repos';
|
|
309
|
+
import { getProject } from '@happyvertical/projects';
|
|
310
|
+
|
|
311
|
+
const repo = await getRepository({
|
|
312
|
+
type: 'github',
|
|
313
|
+
owner: 'happyvertical',
|
|
314
|
+
repo: 'sdk',
|
|
315
|
+
token: process.env.GITHUB_TOKEN
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const project = await getProject({
|
|
319
|
+
type: 'github',
|
|
320
|
+
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
|
|
321
|
+
token: process.env.GITHUB_TOKEN,
|
|
322
|
+
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
|
|
323
|
+
statusOptions: {
|
|
324
|
+
'New': 'option-id-1',
|
|
325
|
+
'In Progress': 'option-id-2',
|
|
326
|
+
'Done': 'option-id-3'
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Create issue and add to project
|
|
331
|
+
const issue = await repo.createIssue({
|
|
332
|
+
title: 'New feature',
|
|
333
|
+
body: 'Description',
|
|
334
|
+
labels: ['type: feature']
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const item = await project.addItem(issue.id);
|
|
338
|
+
await project.updateItemStatus(item.id, 'In Progress');
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Supported Platforms
|
|
342
|
+
|
|
343
|
+
| Platform | Status | Notes |
|
|
344
|
+
|----------|--------|-------|
|
|
345
|
+
| GitHub Projects V2 | ✅ Complete | Full GraphQL implementation |
|
|
346
|
+
| Jira | 🚧 Planned | Coming soon |
|
|
347
|
+
| ZenHub | 🚧 Planned | Coming soon |
|
|
348
|
+
| Linear | 🚧 Planned | Coming soon |
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, copyFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const Dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const pkgRoot = join(Dirname, "../..");
|
|
7
|
+
const targetDir = join(process.cwd(), ".claude");
|
|
8
|
+
if (!existsSync(targetDir)) {
|
|
9
|
+
mkdirSync(targetDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
const pkgName = "projects";
|
|
12
|
+
const agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
13
|
+
const metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
14
|
+
if (existsSync(agentMdSrc)) {
|
|
15
|
+
copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
16
|
+
}
|
|
17
|
+
if (existsSync(metaSrc)) {
|
|
18
|
+
copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
19
|
+
}
|
|
20
|
+
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
21
|
+
//# sourceMappingURL=claude-context.js.map
|
|
@@ -0,0 +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"],"names":[],"mappings":";;;;AASA,MAAM,UAAU,QAAQ,cAAc,YAAY,GAAG,CAAC;AACtD,MAAM,UAAU,KAAK,SAAS,OAAO;AACrC,MAAM,YAAY,KAAK,QAAQ,IAAA,GAAO,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,YAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AAC1C;AAEA,MAAM,UAAU;AAChB,MAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,MAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GAAG;AAC1B,eAAa,YAAY,KAAK,WAAW,QAAQ,OAAO,KAAK,CAAC;AAChE;AAEA,IAAI,WAAW,OAAO,GAAG;AACvB,eAAa,SAAS,KAAK,WAAW,QAAQ,OAAO,YAAY,CAAC;AACpE;AAEA,QAAQ,IAAI,8BAA8B,OAAO,sBAAsB;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export declare interface Field {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';
|
|
5
|
+
options?: FieldOption[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export declare interface FieldOption {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
color?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get a project management client instance
|
|
17
|
+
*
|
|
18
|
+
* This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.
|
|
19
|
+
* It can accept either a configuration object or an existing project instance.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Project configuration or existing project instance
|
|
22
|
+
* @returns Promise resolving to project interface
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { getProject } from '@happyvertical/projects';
|
|
27
|
+
*
|
|
28
|
+
* // Create a GitHub Projects client
|
|
29
|
+
* const project = await getProject({
|
|
30
|
+
* type: 'github',
|
|
31
|
+
* owner: 'happyvertical',
|
|
32
|
+
* projectId: 'PVT_kwDOB9Y8ns4A8-TY',
|
|
33
|
+
* token: process.env.GITHUB_TOKEN,
|
|
34
|
+
* statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
|
|
35
|
+
* statusOptions: {
|
|
36
|
+
* 'New': 'option-id-1',
|
|
37
|
+
* 'Backlog': 'option-id-2',
|
|
38
|
+
* 'In Progress': 'option-id-3',
|
|
39
|
+
* 'Done': 'option-id-4'
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Use the client
|
|
44
|
+
* await project.addItem(issueNodeId);
|
|
45
|
+
* await project.updateItemStatus(itemId, 'In Progress');
|
|
46
|
+
*
|
|
47
|
+
* // Pass existing instance (returns it unchanged)
|
|
48
|
+
* const sameProject = await getProject(project);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function getProject(options: ProjectConfig | IProject): Promise<IProject>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* GitHub Projects V2 implementation
|
|
55
|
+
*/
|
|
56
|
+
export declare class GitHubProject implements IProject {
|
|
57
|
+
private graphql;
|
|
58
|
+
private projectId;
|
|
59
|
+
private owner?;
|
|
60
|
+
private repo?;
|
|
61
|
+
private statusFieldId?;
|
|
62
|
+
private statusOptions?;
|
|
63
|
+
constructor(config: ProjectConfig);
|
|
64
|
+
getProject(): Promise<Project>;
|
|
65
|
+
addItem(contentId: string): Promise<ProjectItem>;
|
|
66
|
+
removeItem(itemId: string): Promise<void>;
|
|
67
|
+
getItem(itemId: string): Promise<ProjectItem | null>;
|
|
68
|
+
listItems(filters?: ItemFilters): Promise<ProjectItem[]>;
|
|
69
|
+
updateItemStatus(itemId: string, status: string): Promise<void>;
|
|
70
|
+
listStatuses(): Promise<Status[]>;
|
|
71
|
+
updateItemField(itemId: string, fieldId: string, value: unknown): Promise<void>;
|
|
72
|
+
listFields(): Promise<Field[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Map GitHub field type to our standard types
|
|
75
|
+
*/
|
|
76
|
+
private mapFieldType;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Project interface - all project implementations must implement this
|
|
81
|
+
*/
|
|
82
|
+
export declare interface IProject {
|
|
83
|
+
getProject(): Promise<Project>;
|
|
84
|
+
addItem(contentId: string): Promise<ProjectItem>;
|
|
85
|
+
removeItem(itemId: string): Promise<void>;
|
|
86
|
+
getItem(itemId: string): Promise<ProjectItem | null>;
|
|
87
|
+
listItems(filters?: ItemFilters): Promise<ProjectItem[]>;
|
|
88
|
+
updateItemStatus(itemId: string, status: string): Promise<void>;
|
|
89
|
+
listStatuses(): Promise<Status[]>;
|
|
90
|
+
updateItemField(itemId: string, fieldId: string, value: unknown): Promise<void>;
|
|
91
|
+
listFields(): Promise<Field[]>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export declare interface ItemFilters {
|
|
95
|
+
status?: string;
|
|
96
|
+
assignees?: string[];
|
|
97
|
+
labels?: string[];
|
|
98
|
+
limit?: number;
|
|
99
|
+
cursor?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Kanban status names for 6-lane structure
|
|
104
|
+
*/
|
|
105
|
+
export declare const KANBAN_STATUSES: readonly ["New", "Backlog", "Planning", "Ready", "In Progress", "Review", "Done"];
|
|
106
|
+
|
|
107
|
+
export declare type KanbanStatus = (typeof KANBAN_STATUSES)[number];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Core types for project management operations
|
|
111
|
+
*/
|
|
112
|
+
export declare interface Project {
|
|
113
|
+
id: string;
|
|
114
|
+
title: string;
|
|
115
|
+
description?: string;
|
|
116
|
+
owner: string;
|
|
117
|
+
url: string;
|
|
118
|
+
statuses: Status[];
|
|
119
|
+
fields: Field[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Project configuration
|
|
124
|
+
*/
|
|
125
|
+
export declare interface ProjectConfig {
|
|
126
|
+
type: 'github' | 'jira' | 'zenhub' | 'linear';
|
|
127
|
+
projectId: string;
|
|
128
|
+
token: string;
|
|
129
|
+
owner?: string;
|
|
130
|
+
repo?: string;
|
|
131
|
+
statusFieldId?: string;
|
|
132
|
+
statusOptions?: Record<string, string>;
|
|
133
|
+
boardId?: string;
|
|
134
|
+
teamId?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export declare class ProjectError extends Error {
|
|
138
|
+
code: ProjectErrorCode;
|
|
139
|
+
statusCode?: number | undefined;
|
|
140
|
+
response?: unknown | undefined;
|
|
141
|
+
constructor(message: string, code: ProjectErrorCode, statusCode?: number | undefined, response?: unknown | undefined);
|
|
142
|
+
static fromHTTPStatus(statusCode: number, message: string, response?: unknown): ProjectError;
|
|
143
|
+
static networkError(message: string, cause?: Error): ProjectError;
|
|
144
|
+
isRetryable(): boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Project error handling
|
|
149
|
+
*/
|
|
150
|
+
export declare enum ProjectErrorCode {
|
|
151
|
+
NOT_FOUND = "NOT_FOUND",
|
|
152
|
+
UNAUTHORIZED = "UNAUTHORIZED",
|
|
153
|
+
INVALID_STATUS = "INVALID_STATUS",
|
|
154
|
+
INVALID_FIELD = "INVALID_FIELD",
|
|
155
|
+
ITEM_NOT_IN_PROJECT = "ITEM_NOT_IN_PROJECT",
|
|
156
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
157
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
158
|
+
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
159
|
+
UNKNOWN = "UNKNOWN"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export declare interface ProjectItem {
|
|
163
|
+
id: string;
|
|
164
|
+
contentId: string;
|
|
165
|
+
status?: string;
|
|
166
|
+
fields: Record<string, unknown>;
|
|
167
|
+
type: 'Issue' | 'PullRequest' | 'DraftIssue';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export declare interface Status {
|
|
171
|
+
id: string;
|
|
172
|
+
name: string;
|
|
173
|
+
description?: string;
|
|
174
|
+
color?: string;
|
|
175
|
+
order: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { GraphQLClient } from "@happyvertical/graphql";
|
|
2
|
+
var ProjectErrorCode = /* @__PURE__ */ ((ProjectErrorCode2) => {
|
|
3
|
+
ProjectErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
4
|
+
ProjectErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
5
|
+
ProjectErrorCode2["INVALID_STATUS"] = "INVALID_STATUS";
|
|
6
|
+
ProjectErrorCode2["INVALID_FIELD"] = "INVALID_FIELD";
|
|
7
|
+
ProjectErrorCode2["ITEM_NOT_IN_PROJECT"] = "ITEM_NOT_IN_PROJECT";
|
|
8
|
+
ProjectErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
9
|
+
ProjectErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
10
|
+
ProjectErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
11
|
+
ProjectErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
12
|
+
return ProjectErrorCode2;
|
|
13
|
+
})(ProjectErrorCode || {});
|
|
14
|
+
class ProjectError extends Error {
|
|
15
|
+
constructor(message, code, statusCode, response) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.statusCode = statusCode;
|
|
19
|
+
this.response = response;
|
|
20
|
+
this.name = "ProjectError";
|
|
21
|
+
Error.captureStackTrace(this, this.constructor);
|
|
22
|
+
}
|
|
23
|
+
static fromHTTPStatus(statusCode, message, response) {
|
|
24
|
+
let code;
|
|
25
|
+
switch (statusCode) {
|
|
26
|
+
case 404:
|
|
27
|
+
code = "NOT_FOUND";
|
|
28
|
+
break;
|
|
29
|
+
case 401:
|
|
30
|
+
code = "UNAUTHORIZED";
|
|
31
|
+
break;
|
|
32
|
+
case 403:
|
|
33
|
+
code = "UNAUTHORIZED";
|
|
34
|
+
break;
|
|
35
|
+
case 429:
|
|
36
|
+
code = "RATE_LIMITED";
|
|
37
|
+
break;
|
|
38
|
+
case 422:
|
|
39
|
+
code = "VALIDATION_ERROR";
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
code = "UNKNOWN";
|
|
43
|
+
}
|
|
44
|
+
return new ProjectError(message, code, statusCode, response);
|
|
45
|
+
}
|
|
46
|
+
static networkError(message, cause) {
|
|
47
|
+
return new ProjectError(
|
|
48
|
+
message,
|
|
49
|
+
"NETWORK_ERROR",
|
|
50
|
+
void 0,
|
|
51
|
+
cause
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
isRetryable() {
|
|
55
|
+
return this.code === "RATE_LIMITED" || this.code === "NETWORK_ERROR";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isProjectInstance(value) {
|
|
59
|
+
return value !== null && typeof value === "object" && "getProject" in value && "addItem" in value && "updateItemStatus" in value && typeof value.getProject === "function";
|
|
60
|
+
}
|
|
61
|
+
async function getProject(options) {
|
|
62
|
+
if (isProjectInstance(options)) {
|
|
63
|
+
return options;
|
|
64
|
+
}
|
|
65
|
+
if (!options.type) {
|
|
66
|
+
throw new ProjectError(
|
|
67
|
+
"Project type is required",
|
|
68
|
+
ProjectErrorCode.VALIDATION_ERROR
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!options.projectId) {
|
|
72
|
+
throw new ProjectError(
|
|
73
|
+
"Project ID is required",
|
|
74
|
+
ProjectErrorCode.VALIDATION_ERROR
|
|
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
|
+
}
|
|
109
|
+
}
|
|
110
|
+
class GitHubProject {
|
|
111
|
+
graphql;
|
|
112
|
+
projectId;
|
|
113
|
+
owner;
|
|
114
|
+
repo;
|
|
115
|
+
statusFieldId;
|
|
116
|
+
statusOptions;
|
|
117
|
+
constructor(config) {
|
|
118
|
+
if (config.type !== "github") {
|
|
119
|
+
throw new Error("Invalid config type for GitHubProject");
|
|
120
|
+
}
|
|
121
|
+
this.graphql = new GraphQLClient({
|
|
122
|
+
endpoint: "https://api.github.com/graphql",
|
|
123
|
+
token: config.token
|
|
124
|
+
});
|
|
125
|
+
this.projectId = config.projectId;
|
|
126
|
+
this.owner = config.owner;
|
|
127
|
+
this.repo = config.repo;
|
|
128
|
+
this.statusFieldId = config.statusFieldId;
|
|
129
|
+
this.statusOptions = config.statusOptions;
|
|
130
|
+
}
|
|
131
|
+
// Project Info
|
|
132
|
+
async getProject() {
|
|
133
|
+
const query = `
|
|
134
|
+
query($projectId: ID!) {
|
|
135
|
+
node(id: $projectId) {
|
|
136
|
+
... on ProjectV2 {
|
|
137
|
+
id
|
|
138
|
+
title
|
|
139
|
+
shortDescription
|
|
140
|
+
url
|
|
141
|
+
owner {
|
|
142
|
+
... on Organization {
|
|
143
|
+
login
|
|
144
|
+
}
|
|
145
|
+
... on User {
|
|
146
|
+
login
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
fields(first: 20) {
|
|
150
|
+
nodes {
|
|
151
|
+
... on ProjectV2Field {
|
|
152
|
+
id
|
|
153
|
+
name
|
|
154
|
+
dataType
|
|
155
|
+
}
|
|
156
|
+
... on ProjectV2SingleSelectField {
|
|
157
|
+
id
|
|
158
|
+
name
|
|
159
|
+
dataType
|
|
160
|
+
options {
|
|
161
|
+
id
|
|
162
|
+
name
|
|
163
|
+
description
|
|
164
|
+
color
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
const data = await this.graphql.query(query, {
|
|
174
|
+
projectId: this.projectId
|
|
175
|
+
});
|
|
176
|
+
const statusField = data.node.fields.nodes.find(
|
|
177
|
+
(f) => f.name === "Status" && f.options
|
|
178
|
+
);
|
|
179
|
+
return {
|
|
180
|
+
id: data.node.id,
|
|
181
|
+
title: data.node.title,
|
|
182
|
+
description: data.node.shortDescription,
|
|
183
|
+
owner: data.node.owner.login,
|
|
184
|
+
url: data.node.url,
|
|
185
|
+
statuses: statusField?.options ? statusField.options.map((opt, index2) => ({
|
|
186
|
+
id: opt.id,
|
|
187
|
+
name: opt.name,
|
|
188
|
+
description: opt.description,
|
|
189
|
+
color: opt.color,
|
|
190
|
+
order: index2
|
|
191
|
+
})) : [],
|
|
192
|
+
fields: data.node.fields.nodes.map((f) => ({
|
|
193
|
+
id: f.id,
|
|
194
|
+
name: f.name,
|
|
195
|
+
type: this.mapFieldType(f.dataType),
|
|
196
|
+
options: f.options?.map((opt) => ({
|
|
197
|
+
id: opt.id,
|
|
198
|
+
name: opt.name,
|
|
199
|
+
description: opt.description,
|
|
200
|
+
color: opt.color
|
|
201
|
+
}))
|
|
202
|
+
}))
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Items
|
|
206
|
+
async addItem(contentId) {
|
|
207
|
+
const mutation = `
|
|
208
|
+
mutation($projectId: ID!, $contentId: ID!) {
|
|
209
|
+
addProjectV2ItemById(input: {
|
|
210
|
+
projectId: $projectId
|
|
211
|
+
contentId: $contentId
|
|
212
|
+
}) {
|
|
213
|
+
item {
|
|
214
|
+
id
|
|
215
|
+
content {
|
|
216
|
+
... on Issue {
|
|
217
|
+
id
|
|
218
|
+
}
|
|
219
|
+
... on PullRequest {
|
|
220
|
+
id
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
const data = await this.graphql.mutate(mutation, {
|
|
228
|
+
projectId: this.projectId,
|
|
229
|
+
contentId
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
id: data.addProjectV2ItemById.item.id,
|
|
233
|
+
contentId: data.addProjectV2ItemById.item.content.id,
|
|
234
|
+
fields: {},
|
|
235
|
+
type: "Issue"
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async removeItem(itemId) {
|
|
239
|
+
const mutation = `
|
|
240
|
+
mutation($projectId: ID!, $itemId: ID!) {
|
|
241
|
+
deleteProjectV2Item(input: {
|
|
242
|
+
projectId: $projectId
|
|
243
|
+
itemId: $itemId
|
|
244
|
+
}) {
|
|
245
|
+
deletedItemId
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
`;
|
|
249
|
+
await this.graphql.mutate(mutation, {
|
|
250
|
+
projectId: this.projectId,
|
|
251
|
+
itemId
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async getItem(itemId) {
|
|
255
|
+
const query = `
|
|
256
|
+
query($itemId: ID!) {
|
|
257
|
+
node(id: $itemId) {
|
|
258
|
+
... on ProjectV2Item {
|
|
259
|
+
id
|
|
260
|
+
content {
|
|
261
|
+
... on Issue {
|
|
262
|
+
id
|
|
263
|
+
}
|
|
264
|
+
... on PullRequest {
|
|
265
|
+
id
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
const data = await this.graphql.query(query, { itemId });
|
|
273
|
+
if (!data.node) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
id: data.node.id,
|
|
278
|
+
contentId: data.node.content.id,
|
|
279
|
+
fields: {},
|
|
280
|
+
type: "Issue"
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async listItems(filters) {
|
|
284
|
+
const query = `
|
|
285
|
+
query($projectId: ID!, $first: Int!, $after: String) {
|
|
286
|
+
node(id: $projectId) {
|
|
287
|
+
... on ProjectV2 {
|
|
288
|
+
items(first: $first, after: $after) {
|
|
289
|
+
nodes {
|
|
290
|
+
id
|
|
291
|
+
content {
|
|
292
|
+
... on Issue {
|
|
293
|
+
id
|
|
294
|
+
}
|
|
295
|
+
... on PullRequest {
|
|
296
|
+
id
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
pageInfo {
|
|
301
|
+
hasNextPage
|
|
302
|
+
endCursor
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
`;
|
|
309
|
+
const data = await this.graphql.query(query, {
|
|
310
|
+
projectId: this.projectId,
|
|
311
|
+
first: filters?.limit || 100,
|
|
312
|
+
after: filters?.cursor
|
|
313
|
+
});
|
|
314
|
+
return data.node.items.nodes.map((item) => ({
|
|
315
|
+
id: item.id,
|
|
316
|
+
contentId: item.content.id,
|
|
317
|
+
fields: {},
|
|
318
|
+
type: "Issue"
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
// Status Management
|
|
322
|
+
async updateItemStatus(itemId, status) {
|
|
323
|
+
if (!this.statusFieldId || !this.statusOptions) {
|
|
324
|
+
throw new ProjectError(
|
|
325
|
+
"Status field configuration is required. Provide statusFieldId and statusOptions in config.",
|
|
326
|
+
ProjectErrorCode.INVALID_STATUS
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const statusOptionId = this.statusOptions[status];
|
|
330
|
+
if (!statusOptionId) {
|
|
331
|
+
throw new ProjectError(
|
|
332
|
+
`Status "${status}" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(", ")}`,
|
|
333
|
+
ProjectErrorCode.INVALID_STATUS
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
await this.updateItemField(itemId, this.statusFieldId, statusOptionId);
|
|
337
|
+
}
|
|
338
|
+
async listStatuses() {
|
|
339
|
+
const project = await this.getProject();
|
|
340
|
+
return project.statuses;
|
|
341
|
+
}
|
|
342
|
+
// Field Management
|
|
343
|
+
async updateItemField(itemId, fieldId, value) {
|
|
344
|
+
const mutation = `
|
|
345
|
+
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {
|
|
346
|
+
updateProjectV2ItemFieldValue(input: {
|
|
347
|
+
projectId: $projectId
|
|
348
|
+
itemId: $itemId
|
|
349
|
+
fieldId: $fieldId
|
|
350
|
+
value: $value
|
|
351
|
+
}) {
|
|
352
|
+
projectV2Item {
|
|
353
|
+
id
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
`;
|
|
358
|
+
await this.graphql.mutate(mutation, {
|
|
359
|
+
projectId: this.projectId,
|
|
360
|
+
itemId,
|
|
361
|
+
fieldId,
|
|
362
|
+
value: { singleSelectOptionId: value }
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async listFields() {
|
|
366
|
+
const project = await this.getProject();
|
|
367
|
+
return project.fields;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Map GitHub field type to our standard types
|
|
371
|
+
*/
|
|
372
|
+
mapFieldType(dataType) {
|
|
373
|
+
switch (dataType.toLowerCase()) {
|
|
374
|
+
case "text":
|
|
375
|
+
return "text";
|
|
376
|
+
case "number":
|
|
377
|
+
return "number";
|
|
378
|
+
case "date":
|
|
379
|
+
return "date";
|
|
380
|
+
case "single_select":
|
|
381
|
+
return "single_select";
|
|
382
|
+
case "iteration":
|
|
383
|
+
return "iteration";
|
|
384
|
+
default:
|
|
385
|
+
return "text";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
390
|
+
__proto__: null,
|
|
391
|
+
GitHubProject
|
|
392
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
393
|
+
const KANBAN_STATUSES = [
|
|
394
|
+
"New",
|
|
395
|
+
"Backlog",
|
|
396
|
+
"Planning",
|
|
397
|
+
"Ready",
|
|
398
|
+
"In Progress",
|
|
399
|
+
"Review",
|
|
400
|
+
"Done"
|
|
401
|
+
];
|
|
402
|
+
export {
|
|
403
|
+
GitHubProject,
|
|
404
|
+
KANBAN_STATUSES,
|
|
405
|
+
ProjectError,
|
|
406
|
+
ProjectErrorCode,
|
|
407
|
+
getProject
|
|
408
|
+
};
|
|
409
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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\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 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 }\n ... on PullRequest {\n id\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: {\n id: string;\n };\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: 'Issue',\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 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 }\n ... on PullRequest {\n id\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: {\n id: string;\n };\n } | null;\n };\n\n if (!data.node) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: 'Issue',\n };\n }\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 }\n ... on PullRequest {\n id\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n first: filters?.limit || 100,\n after: filters?.cursor,\n })) as {\n node: {\n items: {\n nodes: Array<{\n id: string;\n content: {\n id: string;\n };\n }>;\n };\n };\n };\n\n return data.node.items.nodes.map((item) => ({\n id: item.id,\n contentId: item.content.id,\n fields: {},\n type: 'Issue' as const,\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\nexport interface ProjectItem {\n id: string; // Project item ID\n contentId: string; // Issue/PR node ID\n status?: string;\n fields: Record<string, unknown>;\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\nexport interface ItemFilters {\n status?: string;\n assignees?: string[];\n labels?: string[];\n limit?: number;\n cursor?: string; // For pagination\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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"}
|
package/metadata.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/projects",
|
|
3
|
+
"path": "packages/projects",
|
|
4
|
+
"position": {
|
|
5
|
+
"index": 21,
|
|
6
|
+
"count": 30
|
|
7
|
+
},
|
|
8
|
+
"description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
|
|
9
|
+
"provides": [
|
|
10
|
+
"Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear"
|
|
11
|
+
],
|
|
12
|
+
"implements": [],
|
|
13
|
+
"requires": {
|
|
14
|
+
"workspace": [
|
|
15
|
+
"@happyvertical/graphql",
|
|
16
|
+
"@happyvertical/repos"
|
|
17
|
+
],
|
|
18
|
+
"externalHappyVertical": [],
|
|
19
|
+
"external": []
|
|
20
|
+
},
|
|
21
|
+
"dependents": [
|
|
22
|
+
"@happyvertical/github-actions"
|
|
23
|
+
],
|
|
24
|
+
"stability": {
|
|
25
|
+
"level": "stable",
|
|
26
|
+
"reason": "Primary package surface is described as implemented and production-oriented."
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"projects"
|
|
30
|
+
]
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/projects",
|
|
3
|
+
"version": "0.74.8",
|
|
4
|
+
"description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"have-projects-context": "./dist/cli/claude-context.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@happyvertical/graphql": "0.74.8",
|
|
19
|
+
"@happyvertical/repos": "0.74.8"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "25.0.10",
|
|
23
|
+
"typescript": "^5.9.3",
|
|
24
|
+
"vite": "7.3.2",
|
|
25
|
+
"vite-plugin-dts": "4.5.4",
|
|
26
|
+
"vitest": "4.1.5"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"AGENT.md",
|
|
33
|
+
"metadata.json"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"registry": "https://registry.npmjs.org",
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/happyvertical/sdk.git",
|
|
42
|
+
"directory": "packages/projects"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/happyvertical/sdk/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/happyvertical/sdk/tree/main/packages/projects#readme",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "npx vitest run --passWithNoTests",
|
|
51
|
+
"test:watch": "npx vitest",
|
|
52
|
+
"build": "vite build",
|
|
53
|
+
"build:watch": "vite build --watch",
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"dev": "npm run build:watch & npm run test:watch"
|
|
56
|
+
}
|
|
57
|
+
}
|