@ihelpee/prumo-plugin-github-project 0.0.0-stage → 0.18.2

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/LICENSE ADDED
@@ -0,0 +1,51 @@
1
+ Prumo Software License
2
+
3
+ Copyright (c) 2026 IHELPEE TECNOLOGIA LTDA (https://ihelpee.com). All rights reserved.
4
+
5
+ This license applies to Prumo version 0.17.0 and later: the @ihelpee/prumo
6
+ package, its plugins and i18n packages, and their documentation (the
7
+ "Software"). Versions published before 0.17.0 remain under the license each
8
+ one was published with (MIT or Apache-2.0); this license does not change them.
9
+
10
+ 1. Grant. IHELPEE TECNOLOGIA LTDA ("IHELPEE") grants you a free,
11
+ non-exclusive, non-transferable, revocable license to install and run the
12
+ Software, as published by IHELPEE, to check your own projects. Your
13
+ projects may be commercial: a company may use the Software on the code of
14
+ the products it sells.
15
+
16
+ 2. Commercial exploitation is reserved. Only IHELPEE may make money from the
17
+ Software itself. Unless IHELPEE agrees in writing, you may not charge for,
18
+ or receive any payment or other consideration for, the Software or any
19
+ product or service whose value comes mainly from it, such as reselling
20
+ it, bundling it, hosting it, or paid consulting, training, support,
21
+ setup or certification built on it.
22
+
23
+ 3. Restrictions. Unless IHELPEE agrees in writing, you may not:
24
+ a. copy, modify, translate or create derivative works of the Software,
25
+ except copies made by installing and running it;
26
+ b. redistribute, sell, sublicense, rent or publish the Software, alone or
27
+ as part of another product;
28
+ c. offer the Software, or a substantial part of its functionality, to
29
+ third parties as a hosted or managed service;
30
+ d. reverse engineer, decompile or disassemble the Software, except where
31
+ the law allows it despite this restriction;
32
+ e. remove or alter any copyright, trademark or license notice.
33
+
34
+ 4. Trademarks. This license grants no right to the "Prumo" name, logo or
35
+ other marks. See docs/TRADEMARK.md.
36
+
37
+ 5. Termination. This license ends automatically if you breach it. On
38
+ termination you must stop using the Software and delete your copies.
39
+
40
+ 6. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
41
+ KIND, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY, FITNESS FOR A
42
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT.
43
+
44
+ 7. Limitation of liability. TO THE EXTENT PERMITTED BY LAW, IHELPEE IS NOT
45
+ LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF, OR INABILITY TO USE, THE
46
+ SOFTWARE.
47
+
48
+ 8. Governing law. This license is governed by the laws of the Federative
49
+ Republic of Brazil.
50
+
51
+ Other licensing: alexgotardi@gmail.com
package/README.md CHANGED
@@ -1,3 +1,72 @@
1
- # Temporary Holding Version
1
+ # @ihelpee/prumo-plugin-github-project
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Experimental, read-only GitHub Projects v2 and Issues client used by Prumo's pipeline plugin.
4
+ It returns GitHub data in plain typed objects and does not decide whether an issue mutation is
5
+ allowed. The pipeline owns that policy.
6
+
7
+ ## What it provides
8
+
9
+ - `GitHubProjectClient` for GraphQL requests and issue details.
10
+ - `parseIssueRef` for `owner/repo#123` references and GitHub issue URLs.
11
+ - Issue data including title, body, state, assignees, comments, sub-issues and a selected project
12
+ status.
13
+ - Optional project number filtering when an issue belongs to more than one project.
14
+
15
+ This package does not create, edit, close, assign or move issues. It has no retry or pagination
16
+ loop: the current issue-details query reads bounded pages (up to 100 comments, 50 sub-issues and
17
+ 10 project items).
18
+
19
+ ## Installation
20
+
21
+ Install it with Prumo and the pipeline plugin at matching versions:
22
+
23
+ ```bash
24
+ pnpm add @ihelpee/prumo @ihelpee/prumo-plugin-github-project @ihelpee/prumo-plugin-pipeline
25
+ ```
26
+
27
+ ## Use the client
28
+
29
+ Provide a token with access to the target issue and project. The client reads `GH_TOKEN` or
30
+ `GITHUB_TOKEN` when no token is passed explicitly.
31
+
32
+ ```ts
33
+ import {
34
+ GitHubProjectClient,
35
+ parseIssueRef,
36
+ } from '@ihelpee/prumo-plugin-github-project';
37
+
38
+ const client = new GitHubProjectClient({ token: process.env.GH_TOKEN });
39
+ const issue = await client.getIssueDetails(parseIssueRef('owner/repo#123'), 13);
40
+
41
+ console.log(issue.projectStatus, issue.assignees, issue.subIssues);
42
+ ```
43
+
44
+ The `apiUrl` option can point the client at a compatible GitHub API endpoint. Set `locale` to
45
+ `en` or `pt-BR` to localize client-generated diagnostics; English is the default.
46
+
47
+ ## Use with the pipeline plugin
48
+
49
+ Register both plugins in `prumo.config.ts`:
50
+
51
+ ```ts
52
+ import { defineConfig } from '@ihelpee/prumo';
53
+ import githubProject from '@ihelpee/prumo-plugin-github-project';
54
+ import pipeline from '@ihelpee/prumo-plugin-pipeline';
55
+
56
+ export default defineConfig({ plugins: [githubProject, pipeline] });
57
+ ```
58
+
59
+ The pipeline uses this client to read issue and project state before evaluating its own
60
+ preflight policy. See the [pipeline README](https://github.com/ihelpee/prumo/blob/main/packages/plugin-pipeline/README.md)
61
+ for its commands and configuration.
62
+
63
+ ## Errors and access
64
+
65
+ Requests fail with an explicit diagnostic when the token is missing, GitHub returns an HTTP or
66
+ GraphQL error, or the issue cannot be found. Use a token with the least privilege that can read
67
+ the repository issue and the requested project. The client does not log or persist the token.
68
+
69
+ ## Compatibility
70
+
71
+ Use the same release version as `@ihelpee/prumo` and its companion plugins. This package is
72
+ experimental; its API may change between minor releases.
@@ -0,0 +1,19 @@
1
+ import type { SupportedLocale } from '@ihelpee/prumo/core';
2
+ import type { GitHubIssueRef, RawIssueDetails } from './types.js';
3
+ export interface GitHubClientOptions {
4
+ token?: string;
5
+ apiUrl?: string;
6
+ /** Language of error messages; English when absent. */
7
+ locale?: SupportedLocale;
8
+ }
9
+ export declare class GitHubProjectClient {
10
+ private readonly token;
11
+ private readonly apiUrl;
12
+ private readonly locale;
13
+ constructor(options?: GitHubClientOptions);
14
+ hasToken(): boolean;
15
+ private assertToken;
16
+ graphql<T = any>(query: string, variables?: Record<string, unknown>): Promise<T>;
17
+ getIssueDetails(ref: GitHubIssueRef, projectId?: string | number): Promise<RawIssueDetails>;
18
+ }
19
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;gBAEzC,OAAO,GAAE,mBAAwB;IAM7C,QAAQ,IAAI,OAAO;IAInB,OAAO,CAAC,WAAW;IAMb,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAwBpF,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAwHlG"}
package/dist/client.js ADDED
@@ -0,0 +1,141 @@
1
+ import { messages } from './messages.js';
2
+ export class GitHubProjectClient {
3
+ token;
4
+ apiUrl;
5
+ locale;
6
+ constructor(options = {}) {
7
+ this.token = options.token || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || '';
8
+ this.apiUrl = options.apiUrl || 'https://api.github.com';
9
+ this.locale = options.locale;
10
+ }
11
+ hasToken() {
12
+ return Boolean(this.token && this.token.trim().length > 0);
13
+ }
14
+ assertToken() {
15
+ if (!this.hasToken()) {
16
+ throw new Error(messages.t(this.locale, 'tokenMissing'));
17
+ }
18
+ }
19
+ async graphql(query, variables = {}) {
20
+ this.assertToken();
21
+ const res = await fetch(`${this.apiUrl}/graphql`, {
22
+ method: 'POST',
23
+ headers: {
24
+ Authorization: `Bearer ${this.token}`,
25
+ 'Content-Type': 'application/json',
26
+ 'User-Agent': 'prumo-plugin-github-project',
27
+ },
28
+ body: JSON.stringify({ query, variables }),
29
+ });
30
+ if (!res.ok) {
31
+ throw new Error(`GitHub GraphQL HTTP ${res.status}: ${res.statusText}`);
32
+ }
33
+ const payload = (await res.json());
34
+ if (payload.errors && payload.errors.length > 0) {
35
+ throw new Error(`GitHub GraphQL error: ${payload.errors.map((e) => e.message).join('; ')}`);
36
+ }
37
+ return payload.data;
38
+ }
39
+ async getIssueDetails(ref, projectId) {
40
+ this.assertToken();
41
+ const query = `
42
+ query GetIssueDetails($owner: String!, $repo: String!, $number: Int!) {
43
+ repository(owner: $owner, name: $repo) {
44
+ issue(number: $number) {
45
+ number
46
+ title
47
+ body
48
+ state
49
+ assignees(first: 20) {
50
+ nodes {
51
+ login
52
+ }
53
+ }
54
+ comments(first: 100) {
55
+ nodes {
56
+ id
57
+ author {
58
+ login
59
+ }
60
+ body
61
+ createdAt
62
+ }
63
+ }
64
+ projectItems(first: 10) {
65
+ nodes {
66
+ id
67
+ project {
68
+ number
69
+ title
70
+ }
71
+ fieldValues(first: 20) {
72
+ nodes {
73
+ ... on ProjectV2ItemFieldSingleSelectValue {
74
+ name
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ subIssues(first: 50) {
81
+ nodes {
82
+ id
83
+ number
84
+ title
85
+ state
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ `;
92
+ try {
93
+ const data = await this.graphql(query, {
94
+ owner: ref.owner,
95
+ repo: ref.repo,
96
+ number: ref.number,
97
+ });
98
+ const issue = data.repository?.issue;
99
+ if (!issue) {
100
+ throw new Error(messages.t(this.locale, 'issueNotFound', { issue: `${ref.owner}/${ref.repo}#${ref.number}` }));
101
+ }
102
+ let projectStatus = null;
103
+ if (issue.projectItems?.nodes && issue.projectItems.nodes.length > 0) {
104
+ for (const item of issue.projectItems.nodes) {
105
+ if (!projectId || String(item.project?.number) === String(projectId)) {
106
+ const statusVal = item.fieldValues?.nodes?.find((f) => Boolean(f?.name));
107
+ if (statusVal?.name) {
108
+ projectStatus = statusVal.name;
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return {
115
+ owner: ref.owner,
116
+ repo: ref.repo,
117
+ number: issue.number,
118
+ title: issue.title ?? '',
119
+ body: issue.body ?? '',
120
+ state: issue.state ?? 'OPEN',
121
+ assignees: (issue.assignees?.nodes ?? []).map((a) => a.login),
122
+ comments: (issue.comments?.nodes ?? []).map((c) => ({
123
+ id: c.id,
124
+ author: c.author?.login ?? 'ghost',
125
+ body: c.body ?? '',
126
+ createdAt: c.createdAt,
127
+ })),
128
+ subIssues: (issue.subIssues?.nodes ?? []).map((s) => ({
129
+ id: s.id,
130
+ number: s.number,
131
+ title: s.title,
132
+ state: s.state,
133
+ })),
134
+ projectStatus,
135
+ };
136
+ }
137
+ catch (err) {
138
+ throw new Error(messages.t(this.locale, 'loadFailed', { issue: `${ref.owner}/${ref.repo}#${ref.number}`, error: err.message }));
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,8 @@
1
+ export * from './types.js';
2
+ export * from './client.js';
3
+ export declare function activate(): void;
4
+ declare const _default: {
5
+ activate: typeof activate;
6
+ };
7
+ export default _default;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAE5B,wBAAgB,QAAQ,IAAI,IAAI,CAE/B;;;;AAED,wBAEE"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './types.js';
2
+ export * from './client.js';
3
+ export function activate() {
4
+ // Fine client plugin — exposes raw GraphQL/REST queries
5
+ }
6
+ export default {
7
+ activate,
8
+ };
@@ -0,0 +1,3 @@
1
+ /** Error messages. English is canonical; other locales fall back to it per key. */
2
+ export declare const messages: import("@ihelpee/prumo/core").Translator<"tokenMissing" | "issueNotFound" | "loadFailed" | "invalidRef">;
3
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,eAAO,MAAM,QAAQ,0GAcnB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { defineMessages } from '@ihelpee/prumo/core';
2
+ /** Error messages. English is canonical; other locales fall back to it per key. */
3
+ export const messages = defineMessages({
4
+ en: {
5
+ tokenMissing: 'GH_TOKEN or GITHUB_TOKEN is not set. Set the environment variable, or use PIPELINE_FIXTURE=<path> to run offline.',
6
+ issueNotFound: 'Issue {issue} not found on GitHub',
7
+ loadFailed: 'Failed to load GitHub data for {issue}: {error}',
8
+ invalidRef: "Invalid issue reference: '{raw}'. Expected 'owner/repo#123' or a GitHub URL",
9
+ },
10
+ 'pt-BR': {
11
+ tokenMissing: 'GH_TOKEN ou GITHUB_TOKEN ausente no ambiente. Defina a variável de ambiente ou utilize PIPELINE_FIXTURE=<path> para executar offline.',
12
+ issueNotFound: 'Issue {issue} não encontrada no GitHub',
13
+ loadFailed: 'Falha ao carregar dados do GitHub para {issue}: {error}',
14
+ invalidRef: "Referência de issue inválida: '{raw}'. Formato esperado: 'owner/repo#123' ou URL do GitHub",
15
+ },
16
+ });
@@ -0,0 +1,39 @@
1
+ import type { SupportedLocale } from '@ihelpee/prumo/core';
2
+ export interface GitHubIssueRef {
3
+ owner: string;
4
+ repo: string;
5
+ number: number;
6
+ }
7
+ export declare function parseIssueRef(raw: string, locale?: SupportedLocale): GitHubIssueRef;
8
+ export interface RawProjectItem {
9
+ id: string;
10
+ projectId?: string;
11
+ status: string | null;
12
+ title: string;
13
+ updatedAt?: string;
14
+ }
15
+ export interface RawIssueComment {
16
+ id: string;
17
+ author: string;
18
+ body: string;
19
+ createdAt: string;
20
+ }
21
+ export interface RawSubIssue {
22
+ id: string;
23
+ number: number;
24
+ title: string;
25
+ state: string;
26
+ }
27
+ export interface RawIssueDetails {
28
+ owner: string;
29
+ repo: string;
30
+ number: number;
31
+ title: string;
32
+ body: string;
33
+ state: string;
34
+ assignees: string[];
35
+ comments: RawIssueComment[];
36
+ subIssues: RawSubIssue[];
37
+ projectStatus?: string | null;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,cAAc,CAqBnF;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1,21 @@
1
+ import { messages } from './messages.js';
2
+ export function parseIssueRef(raw, locale) {
3
+ const trimmed = raw.trim();
4
+ const match = trimmed.match(/^([^/]+)\/([^#]+)#(\d+)$/);
5
+ if (match) {
6
+ return {
7
+ owner: match[1],
8
+ repo: match[2],
9
+ number: Number.parseInt(match[3], 10),
10
+ };
11
+ }
12
+ const urlMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
13
+ if (urlMatch) {
14
+ return {
15
+ owner: urlMatch[1],
16
+ repo: urlMatch[2],
17
+ number: Number.parseInt(urlMatch[3], 10),
18
+ };
19
+ }
20
+ throw new Error(messages.t(locale, 'invalidRef', { raw }));
21
+ }
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@ihelpee/prumo-plugin-github-project",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.18.2",
4
+ "type": "module",
5
+ "description": "Thin GitHub Project v2 and Issue client: raw GraphQL queries for items, status, comments, sub-issues.",
6
+ "author": "Alex Gotardi <alexgotardi@gmail.com> (https://github.com/alexgotardi)",
7
+ "license": "SEE LICENSE IN LICENSE",
8
+ "homepage": "https://github.com/ihelpee/prumo#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ihelpee/prumo.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/ihelpee/prumo/issues"
15
+ },
16
+ "maturity": "experimental",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "package.json",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "prumo": ">=0.12.0",
35
+ "contributes": {
36
+ "activate": "./dist/index.js",
37
+ "description": "Raw GitHub Project and Issue queries without domain business rules."
38
+ },
39
+ "peerDependencies": {
40
+ "@ihelpee/prumo": "^0.18.2"
41
+ },
42
+ "devDependencies": {
43
+ "@ihelpee/prumo": "0.18.2",
44
+ "@ihelpee/prumo-plugin-typescript": "0.18.2"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.json",
48
+ "verify": "prumo gate --root ."
49
+ }
6
50
  }