@realvendex/pi-ci 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/schemas.d.ts +21 -0
- package/dist/schemas.js +25 -0
- package/dist/templates.d.ts +17 -0
- package/dist/templates.js +126 -0
- package/dist/tools/ci_generate.d.ts +21 -0
- package/dist/tools/ci_generate.js +48 -0
- package/dist/tools/ci_init.d.ts +19 -0
- package/dist/tools/ci_init.js +55 -0
- package/dist/tools/ci_validate.d.ts +18 -0
- package/dist/tools/ci_validate.js +153 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# pi-ci
|
|
2
|
+
|
|
3
|
+
> Pi-native GitHub Actions workflow generator for pi.dev extensions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:@realvendex/pi-ci
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What It Does
|
|
12
|
+
|
|
13
|
+
Generate GitHub Actions workflow YAML files pre-configured for pi.dev extension development. Supports three templates (minimal, default, full) with pre-wired jobs for vitest, biome, tsc, npm publish, and GitHub Release.
|
|
14
|
+
|
|
15
|
+
## Tools
|
|
16
|
+
|
|
17
|
+
### `ci_generate`
|
|
18
|
+
|
|
19
|
+
Generate a GitHub Actions CI workflow YAML file.
|
|
20
|
+
|
|
21
|
+
**Parameters:**
|
|
22
|
+
- `template` (string, optional) — `'minimal'`, `'default'`, or `'full'` (default: `'default'`)
|
|
23
|
+
- `packageName` (string, optional) — Package name for npm publish step
|
|
24
|
+
- `nodeVersion` (string, optional) — Node.js version (default: `'22'`)
|
|
25
|
+
- `includePublish` (boolean, optional) — Include npm publish job (default: false)
|
|
26
|
+
- `includeRelease` (boolean, optional) — Include GitHub Release job (default: false)
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
```
|
|
30
|
+
Use the ci_generate tool with template="full" packageName="@realvendex/my-ext"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `ci_validate`
|
|
34
|
+
|
|
35
|
+
Validate an existing workflow YAML against pi.dev conventions.
|
|
36
|
+
|
|
37
|
+
**Parameters:**
|
|
38
|
+
- `yaml` (string, required) — GitHub Actions workflow YAML content to validate
|
|
39
|
+
- `strict` (boolean, optional) — Enable strict pi.dev convention checks (default: false)
|
|
40
|
+
|
|
41
|
+
**Example:**
|
|
42
|
+
```
|
|
43
|
+
Use the ci_validate tool with yaml="<your workflow YAML>" strict=true
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `ci_init`
|
|
47
|
+
|
|
48
|
+
Scaffold a `.github/workflows/` directory with a generated CI workflow.
|
|
49
|
+
|
|
50
|
+
**Parameters:**
|
|
51
|
+
- `template` (string, optional) — `'minimal'`, `'default'`, or `'full'` (default: `'default'`)
|
|
52
|
+
- `packageName` (string, optional) — Package name for npm publish step
|
|
53
|
+
- `dryRun` (boolean, optional) — Preview without writing files (default: false)
|
|
54
|
+
|
|
55
|
+
**Example:**
|
|
56
|
+
```
|
|
57
|
+
Use the ci_init tool with template="default" dryRun=true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Templates
|
|
61
|
+
|
|
62
|
+
| Template | Jobs | Use Case |
|
|
63
|
+
|-----------|--------------------------------|-----------------------------|
|
|
64
|
+
| minimal | CI only | Simple projects |
|
|
65
|
+
| default | CI + optional publish | Standard extensions |
|
|
66
|
+
| full | CI + publish + GitHub Release | Full release automation |
|
|
67
|
+
|
|
68
|
+
## Pre-wired Jobs
|
|
69
|
+
|
|
70
|
+
- **ci** — Runs on every push/PR: `biome check`, `tsc --noEmit`, `vitest run`
|
|
71
|
+
- **publish** — Publishes to npm on tag push (requires `NPM_TOKEN` secret)
|
|
72
|
+
- **release** — Creates GitHub Release on tag push
|
|
73
|
+
|
|
74
|
+
## Resources
|
|
75
|
+
|
|
76
|
+
- [npm](https://www.npmjs.com/package/@realvendex/pi-ci)
|
|
77
|
+
- [GitHub](https://github.com/ZachDreamZ/pi-ci)
|
|
78
|
+
- [pi.dev](https://pi.dev/packages/@realvendex/pi-ci)
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ciGenerateDef, ciGenerateExecute } from "./tools/ci_generate.js";
|
|
2
|
+
import { ciInitDef, ciInitExecute } from "./tools/ci_init.js";
|
|
3
|
+
import { ciValidateDef, ciValidateExecute } from "./tools/ci_validate.js";
|
|
4
|
+
export default function register(pi) {
|
|
5
|
+
pi.registerTool({
|
|
6
|
+
...ciGenerateDef,
|
|
7
|
+
execute: ciGenerateExecute,
|
|
8
|
+
});
|
|
9
|
+
pi.registerTool({
|
|
10
|
+
...ciValidateDef,
|
|
11
|
+
execute: ciValidateExecute,
|
|
12
|
+
});
|
|
13
|
+
pi.registerTool({
|
|
14
|
+
...ciInitDef,
|
|
15
|
+
execute: ciInitExecute,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
export declare const CiGenerateTemplate: Type.TUnion<[Type.TLiteral<"default">, Type.TLiteral<"minimal">, Type.TLiteral<"full">]>;
|
|
3
|
+
export declare const CiGenerateOptions: Type.TObject<{
|
|
4
|
+
template: Type.TOptional<Type.TUnion<[Type.TLiteral<"default">, Type.TLiteral<"minimal">, Type.TLiteral<"full">]>>;
|
|
5
|
+
packageName: Type.TOptional<Type.TString>;
|
|
6
|
+
nodeVersion: Type.TOptional<Type.TString>;
|
|
7
|
+
includePublish: Type.TOptional<Type.TBoolean>;
|
|
8
|
+
includeRelease: Type.TOptional<Type.TBoolean>;
|
|
9
|
+
}>;
|
|
10
|
+
export type CiGenerateInput = Static<typeof CiGenerateOptions>;
|
|
11
|
+
export declare const CiValidateOptions: Type.TObject<{
|
|
12
|
+
yaml: Type.TString;
|
|
13
|
+
strict: Type.TOptional<Type.TBoolean>;
|
|
14
|
+
}>;
|
|
15
|
+
export type CiValidateInput = Static<typeof CiValidateOptions>;
|
|
16
|
+
export declare const CiInitOptions: Type.TObject<{
|
|
17
|
+
template: Type.TOptional<Type.TUnion<[Type.TLiteral<"default">, Type.TLiteral<"minimal">, Type.TLiteral<"full">]>>;
|
|
18
|
+
packageName: Type.TOptional<Type.TString>;
|
|
19
|
+
dryRun: Type.TOptional<Type.TBoolean>;
|
|
20
|
+
}>;
|
|
21
|
+
export type CiInitInput = Static<typeof CiInitOptions>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
// ── ci_generate ──
|
|
3
|
+
export const CiGenerateTemplate = Type.Union([
|
|
4
|
+
Type.Literal("default"),
|
|
5
|
+
Type.Literal("minimal"),
|
|
6
|
+
Type.Literal("full"),
|
|
7
|
+
]);
|
|
8
|
+
export const CiGenerateOptions = Type.Object({
|
|
9
|
+
template: Type.Optional(CiGenerateTemplate),
|
|
10
|
+
packageName: Type.Optional(Type.String({ description: "Package name for npm publish step" })),
|
|
11
|
+
nodeVersion: Type.Optional(Type.String({ description: "Node.js version (default: 22)" })),
|
|
12
|
+
includePublish: Type.Optional(Type.Boolean({ description: "Include npm publish job (default: false)" })),
|
|
13
|
+
includeRelease: Type.Optional(Type.Boolean({ description: "Include GitHub Release job (default: false)" })),
|
|
14
|
+
});
|
|
15
|
+
// ── ci_validate ──
|
|
16
|
+
export const CiValidateOptions = Type.Object({
|
|
17
|
+
yaml: Type.String({ description: "GitHub Actions workflow YAML content to validate" }),
|
|
18
|
+
strict: Type.Optional(Type.Boolean({ description: "Enable strict pi.dev convention checks (default: false)" })),
|
|
19
|
+
});
|
|
20
|
+
// ── ci_init ──
|
|
21
|
+
export const CiInitOptions = Type.Object({
|
|
22
|
+
template: Type.Optional(CiGenerateTemplate),
|
|
23
|
+
packageName: Type.Optional(Type.String({ description: "Package name for npm publish step" })),
|
|
24
|
+
dryRun: Type.Optional(Type.Boolean({ description: "Preview generated YAML without writing files (default: false)" })),
|
|
25
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow YAML template generation for pi.dev extensions.
|
|
3
|
+
* Generates GitHub Actions workflow files pre-configured for pi.dev standards.
|
|
4
|
+
*/
|
|
5
|
+
export interface WorkflowConfig {
|
|
6
|
+
packageName?: string;
|
|
7
|
+
nodeVersion?: string;
|
|
8
|
+
includePublish?: boolean;
|
|
9
|
+
includeRelease?: boolean;
|
|
10
|
+
}
|
|
11
|
+
type TemplateName = "default" | "minimal" | "full";
|
|
12
|
+
export declare function generateWorkflow(template: TemplateName, config: WorkflowConfig): string;
|
|
13
|
+
export declare function listTemplates(): Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
}>;
|
|
17
|
+
export type { TemplateName };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow YAML template generation for pi.dev extensions.
|
|
3
|
+
* Generates GitHub Actions workflow files pre-configured for pi.dev standards.
|
|
4
|
+
*/
|
|
5
|
+
// ── Template definitions ──
|
|
6
|
+
function ciJob(config) {
|
|
7
|
+
const node = config.nodeVersion ?? "22";
|
|
8
|
+
return ` runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
node-version: [${node}]
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: \${{ matrix.node-version }}
|
|
17
|
+
registry-url: https://registry.npmjs.org
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npx biome check .
|
|
20
|
+
- run: npx tsc --noEmit
|
|
21
|
+
- run: npx vitest run`;
|
|
22
|
+
}
|
|
23
|
+
function publishJob(config) {
|
|
24
|
+
const _pkg = config.packageName ?? "@realvendex/pi-extension";
|
|
25
|
+
return ` needs: ci
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-node@v4
|
|
31
|
+
with:
|
|
32
|
+
node-version: 22
|
|
33
|
+
registry-url: https://registry.npmjs.org
|
|
34
|
+
- run: npm ci
|
|
35
|
+
- run: npm run build
|
|
36
|
+
- run: npm publish --access public
|
|
37
|
+
env:
|
|
38
|
+
NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}`;
|
|
39
|
+
}
|
|
40
|
+
function releaseJob() {
|
|
41
|
+
return ` needs: ci
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
44
|
+
permissions:
|
|
45
|
+
contents: write
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
- uses: softprops/action-gh-release@v2
|
|
49
|
+
with:
|
|
50
|
+
generate_release_notes: true`;
|
|
51
|
+
}
|
|
52
|
+
// ── Template generators ──
|
|
53
|
+
function minimalTemplate(config) {
|
|
54
|
+
return `name: CI
|
|
55
|
+
on:
|
|
56
|
+
push:
|
|
57
|
+
branches: [main]
|
|
58
|
+
pull_request:
|
|
59
|
+
branches: [main]
|
|
60
|
+
|
|
61
|
+
jobs:
|
|
62
|
+
ci:
|
|
63
|
+
${ciJob(config)}`;
|
|
64
|
+
}
|
|
65
|
+
function defaultTemplate(config) {
|
|
66
|
+
let yaml = `name: CI
|
|
67
|
+
on:
|
|
68
|
+
push:
|
|
69
|
+
branches: [main]
|
|
70
|
+
pull_request:
|
|
71
|
+
branches: [main]
|
|
72
|
+
|
|
73
|
+
jobs:
|
|
74
|
+
ci:
|
|
75
|
+
${ciJob(config)}`;
|
|
76
|
+
if (config.includePublish) {
|
|
77
|
+
yaml += `
|
|
78
|
+
|
|
79
|
+
publish:
|
|
80
|
+
${publishJob(config)}`;
|
|
81
|
+
}
|
|
82
|
+
return yaml;
|
|
83
|
+
}
|
|
84
|
+
function fullTemplate(config) {
|
|
85
|
+
let yaml = `name: CI/CD
|
|
86
|
+
on:
|
|
87
|
+
push:
|
|
88
|
+
branches: [main]
|
|
89
|
+
tags: [v*]
|
|
90
|
+
pull_request:
|
|
91
|
+
branches: [main]
|
|
92
|
+
|
|
93
|
+
jobs:
|
|
94
|
+
ci:
|
|
95
|
+
${ciJob(config)}
|
|
96
|
+
|
|
97
|
+
publish:
|
|
98
|
+
${publishJob(config)}`;
|
|
99
|
+
if (config.includeRelease !== false) {
|
|
100
|
+
yaml += `
|
|
101
|
+
|
|
102
|
+
release:
|
|
103
|
+
${releaseJob()}`;
|
|
104
|
+
}
|
|
105
|
+
return yaml;
|
|
106
|
+
}
|
|
107
|
+
// ── Public API ──
|
|
108
|
+
const TEMPLATES = {
|
|
109
|
+
minimal: minimalTemplate,
|
|
110
|
+
default: defaultTemplate,
|
|
111
|
+
full: fullTemplate,
|
|
112
|
+
};
|
|
113
|
+
export function generateWorkflow(template, config) {
|
|
114
|
+
const generator = TEMPLATES[template];
|
|
115
|
+
if (!generator) {
|
|
116
|
+
throw new Error(`Unknown template: ${template}. Available: ${Object.keys(TEMPLATES).join(", ")}`);
|
|
117
|
+
}
|
|
118
|
+
return generator(config);
|
|
119
|
+
}
|
|
120
|
+
export function listTemplates() {
|
|
121
|
+
return [
|
|
122
|
+
{ name: "minimal", description: "Single CI job: lint, typecheck, test" },
|
|
123
|
+
{ name: "default", description: "CI job + optional publish on tag push" },
|
|
124
|
+
{ name: "full", description: "CI + publish + GitHub Release on tag push" },
|
|
125
|
+
];
|
|
126
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { type CiGenerateInput } from "../schemas.js";
|
|
3
|
+
export declare const ciGenerateDef: {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Type.TObject<{
|
|
8
|
+
template: Type.TOptional<Type.TUnion<[Type.TLiteral<"default">, Type.TLiteral<"minimal">, Type.TLiteral<"full">]>>;
|
|
9
|
+
packageName: Type.TOptional<Type.TString>;
|
|
10
|
+
nodeVersion: Type.TOptional<Type.TString>;
|
|
11
|
+
includePublish: Type.TOptional<Type.TBoolean>;
|
|
12
|
+
includeRelease: Type.TOptional<Type.TBoolean>;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
export declare function ciGenerateExecute(_toolCallId: string, params: CiGenerateInput): Promise<{
|
|
16
|
+
content: Array<{
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}>;
|
|
20
|
+
details: Record<string, unknown>;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CiGenerateOptions } from "../schemas.js";
|
|
2
|
+
import { generateWorkflow, listTemplates } from "../templates.js";
|
|
3
|
+
export const ciGenerateDef = {
|
|
4
|
+
name: "ci_generate",
|
|
5
|
+
label: "Generate CI Workflow",
|
|
6
|
+
description: "Generate a GitHub Actions workflow YAML file for a pi.dev extension. Produces CI/CD workflow with test (vitest), lint (biome), typecheck (tsc), and optional publish/release jobs.",
|
|
7
|
+
parameters: CiGenerateOptions,
|
|
8
|
+
};
|
|
9
|
+
export async function ciGenerateExecute(_toolCallId, params) {
|
|
10
|
+
const template = params.template ?? "default";
|
|
11
|
+
const config = {
|
|
12
|
+
packageName: params.packageName,
|
|
13
|
+
nodeVersion: params.nodeVersion,
|
|
14
|
+
includePublish: params.includePublish,
|
|
15
|
+
includeRelease: params.includeRelease,
|
|
16
|
+
};
|
|
17
|
+
const yaml = generateWorkflow(template, config);
|
|
18
|
+
const templates = listTemplates();
|
|
19
|
+
const templateInfo = templates.find((t) => t.name === template);
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: yaml }],
|
|
22
|
+
details: {
|
|
23
|
+
template,
|
|
24
|
+
templateDescription: templateInfo?.description ?? "",
|
|
25
|
+
jobs: extractJobNames(yaml),
|
|
26
|
+
nodeVersion: config.nodeVersion ?? "22",
|
|
27
|
+
includePublish: config.includePublish ?? false,
|
|
28
|
+
includeRelease: config.includeRelease ?? template === "full",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function extractJobNames(yaml) {
|
|
33
|
+
const jobs = [];
|
|
34
|
+
const lines = yaml.split("\n");
|
|
35
|
+
let inJobs = false;
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (line.trim() === "jobs:") {
|
|
38
|
+
inJobs = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (inJobs && line.match(/^ {2}\w/)) {
|
|
42
|
+
const match = line.match(/^ {2}(\w[\w-]*):/);
|
|
43
|
+
if (match)
|
|
44
|
+
jobs.push(match[1]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return jobs;
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { type CiInitInput } from "../schemas.js";
|
|
3
|
+
export declare const ciInitDef: {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Type.TObject<{
|
|
8
|
+
template: Type.TOptional<Type.TUnion<[Type.TLiteral<"default">, Type.TLiteral<"minimal">, Type.TLiteral<"full">]>>;
|
|
9
|
+
packageName: Type.TOptional<Type.TString>;
|
|
10
|
+
dryRun: Type.TOptional<Type.TBoolean>;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export declare function ciInitExecute(_toolCallId: string, params: CiInitInput): Promise<{
|
|
14
|
+
content: Array<{
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}>;
|
|
18
|
+
details: Record<string, unknown>;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CiInitOptions } from "../schemas.js";
|
|
2
|
+
import { generateWorkflow } from "../templates.js";
|
|
3
|
+
export const ciInitDef = {
|
|
4
|
+
name: "ci_init",
|
|
5
|
+
label: "Initialize CI Workflow",
|
|
6
|
+
description: "Scaffold a .github/workflows/ directory with a generated CI workflow YAML. Supports dry-run mode to preview without writing files.",
|
|
7
|
+
parameters: CiInitOptions,
|
|
8
|
+
};
|
|
9
|
+
export async function ciInitExecute(_toolCallId, params) {
|
|
10
|
+
const template = params.template ?? "default";
|
|
11
|
+
const config = {
|
|
12
|
+
packageName: params.packageName,
|
|
13
|
+
includePublish: template === "full" || template === "default",
|
|
14
|
+
includeRelease: template === "full",
|
|
15
|
+
};
|
|
16
|
+
const yaml = generateWorkflow(template, config);
|
|
17
|
+
const dryRun = params.dryRun ?? false;
|
|
18
|
+
const workflowPath = ".github/workflows/ci.yml";
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: `# Dry Run — Preview\n\nWould write to: ${workflowPath}\n\n---\n\n${yaml}`,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
details: {
|
|
28
|
+
dryRun: true,
|
|
29
|
+
path: workflowPath,
|
|
30
|
+
template,
|
|
31
|
+
yaml,
|
|
32
|
+
written: false,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// In real mode we return the path + content for the caller to write.
|
|
37
|
+
// Tools in pi.dev don't have filesystem access by default — we provide
|
|
38
|
+
// the content and let the extension host write it.
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `# CI Workflow Ready\n\nWrite the following to \`${workflowPath}\`:\n\n\`\`\`yaml\n${yaml}\n\`\`\``,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
details: {
|
|
47
|
+
dryRun: false,
|
|
48
|
+
path: workflowPath,
|
|
49
|
+
template,
|
|
50
|
+
yaml,
|
|
51
|
+
written: false,
|
|
52
|
+
instruction: `Create directory .github/workflows/ and write the YAML content to ${workflowPath}`,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { type CiValidateInput } from "../schemas.js";
|
|
3
|
+
export declare const ciValidateDef: {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Type.TObject<{
|
|
8
|
+
yaml: Type.TString;
|
|
9
|
+
strict: Type.TOptional<Type.TBoolean>;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
export declare function ciValidateExecute(_toolCallId: string, params: CiValidateInput): Promise<{
|
|
13
|
+
content: Array<{
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
}>;
|
|
17
|
+
details: Record<string, unknown>;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { CiValidateOptions } from "../schemas.js";
|
|
2
|
+
export const ciValidateDef = {
|
|
3
|
+
name: "ci_validate",
|
|
4
|
+
label: "Validate CI Workflow",
|
|
5
|
+
description: "Validate an existing GitHub Actions workflow YAML. Checks structure, required jobs, triggers, and pi.dev-specific conventions (Node version, npm scope, etc.).",
|
|
6
|
+
parameters: CiValidateOptions,
|
|
7
|
+
};
|
|
8
|
+
export async function ciValidateExecute(_toolCallId, params) {
|
|
9
|
+
const issues = validateWorkflow(params.yaml, params.strict ?? false);
|
|
10
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
11
|
+
const warnings = issues.filter((i) => i.level === "warning");
|
|
12
|
+
const infos = issues.filter((i) => i.level === "info");
|
|
13
|
+
const report = buildReport(issues, errors.length === 0);
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: "text", text: report }],
|
|
16
|
+
details: {
|
|
17
|
+
valid: errors.length === 0,
|
|
18
|
+
errors: errors.length,
|
|
19
|
+
warnings: warnings.length,
|
|
20
|
+
infos: infos.length,
|
|
21
|
+
issues,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function validateWorkflow(yaml, strict) {
|
|
26
|
+
const issues = [];
|
|
27
|
+
const lines = yaml.split("\n");
|
|
28
|
+
// ── Structure checks ──
|
|
29
|
+
const hasOnTrigger = lines.some((l) => /^\s*on\s*:/.test(l) && !/runs-on/.test(l));
|
|
30
|
+
if (!hasOnTrigger) {
|
|
31
|
+
issues.push({ level: "error", message: "Missing 'on:' trigger section" });
|
|
32
|
+
}
|
|
33
|
+
if (!yaml.includes("jobs:")) {
|
|
34
|
+
issues.push({ level: "error", message: "Missing 'jobs:' section" });
|
|
35
|
+
}
|
|
36
|
+
// ── Job checks ──
|
|
37
|
+
const hasCi = yaml.includes("ci:");
|
|
38
|
+
const hasTest = yaml.includes("vitest") || yaml.includes("jest") || yaml.includes("test:");
|
|
39
|
+
const hasLint = yaml.includes("biome") || yaml.includes("eslint") || yaml.includes("lint:");
|
|
40
|
+
const hasTypecheck = yaml.includes("tsc") || yaml.includes("typecheck");
|
|
41
|
+
if (!hasCi) {
|
|
42
|
+
issues.push({ level: "warning", message: "No 'ci' job found — consider adding a main CI job" });
|
|
43
|
+
}
|
|
44
|
+
if (hasCi && !hasTest) {
|
|
45
|
+
issues.push({ level: "warning", message: "CI job does not run tests (expected vitest)" });
|
|
46
|
+
}
|
|
47
|
+
if (hasCi && !hasLint) {
|
|
48
|
+
issues.push({ level: "warning", message: "CI job does not run linting (expected biome)" });
|
|
49
|
+
}
|
|
50
|
+
if (hasCi && !hasTypecheck) {
|
|
51
|
+
issues.push({
|
|
52
|
+
level: "warning",
|
|
53
|
+
message: "CI job does not run type checking (expected tsc --noEmit)",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ── Node version ──
|
|
57
|
+
const nodeVersionMatch = yaml.match(/node-version:\s*\[?(\d+)/);
|
|
58
|
+
if (nodeVersionMatch) {
|
|
59
|
+
const version = Number.parseInt(nodeVersionMatch[1], 10);
|
|
60
|
+
if (version < 20) {
|
|
61
|
+
issues.push({
|
|
62
|
+
level: "error",
|
|
63
|
+
message: `Node.js version ${version} is below minimum (20). pi.dev requires Node 20+`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (version < 22 && strict) {
|
|
67
|
+
issues.push({
|
|
68
|
+
level: "warning",
|
|
69
|
+
message: `Node.js version ${version} — pi.dev recommends Node 22 LTS`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
issues.push({
|
|
75
|
+
level: "warning",
|
|
76
|
+
message: "No Node.js version specified — pi.dev recommends Node 22 LTS",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ── Trigger checks ──
|
|
80
|
+
if (!yaml.includes("push:")) {
|
|
81
|
+
issues.push({ level: "info", message: "No push trigger — CI only runs on PRs" });
|
|
82
|
+
}
|
|
83
|
+
if (!yaml.includes("pull_request:")) {
|
|
84
|
+
issues.push({ level: "info", message: "No pull_request trigger — CI only runs on push" });
|
|
85
|
+
}
|
|
86
|
+
// ── Strict checks ──
|
|
87
|
+
if (strict) {
|
|
88
|
+
if (!yaml.includes("registry-url")) {
|
|
89
|
+
issues.push({
|
|
90
|
+
level: "warning",
|
|
91
|
+
message: "Missing registry-url in setup-node (needed for npm publish)",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (!yaml.includes("npm ci")) {
|
|
95
|
+
issues.push({
|
|
96
|
+
level: "warning",
|
|
97
|
+
message: "Using 'npm install' instead of 'npm ci' — ci is preferred in CI",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (yaml.includes("npm install") && !yaml.includes("npm ci")) {
|
|
101
|
+
issues.push({
|
|
102
|
+
level: "error",
|
|
103
|
+
message: "Use 'npm ci' instead of 'npm install' for reproducible builds",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (yaml.includes("actions/checkout@v3") || yaml.includes("actions/setup-node@v3")) {
|
|
107
|
+
issues.push({
|
|
108
|
+
level: "warning",
|
|
109
|
+
message: "Using v3 actions — v4 is current (faster, smaller)",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ── Security ──
|
|
114
|
+
if (yaml.includes("secrets.NPM_TOKEN") && !yaml.includes("refs/tags")) {
|
|
115
|
+
issues.push({
|
|
116
|
+
level: "warning",
|
|
117
|
+
message: "NPM_TOKEN available on all pushes — restrict to tag pushes for safety",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (issues.length === 0) {
|
|
121
|
+
issues.push({ level: "info", message: "No issues found — workflow looks good!" });
|
|
122
|
+
}
|
|
123
|
+
return issues;
|
|
124
|
+
}
|
|
125
|
+
function buildReport(issues, valid) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
lines.push("# Workflow Validation Report");
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(`Status: ${valid ? "✅ VALID" : "❌ INVALID"}`);
|
|
130
|
+
lines.push("");
|
|
131
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
132
|
+
const warnings = issues.filter((i) => i.level === "warning");
|
|
133
|
+
const infos = issues.filter((i) => i.level === "info");
|
|
134
|
+
if (errors.length > 0) {
|
|
135
|
+
lines.push(`## Errors (${errors.length})`);
|
|
136
|
+
for (const e of errors)
|
|
137
|
+
lines.push(` ❌ ${e.message}`);
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
if (warnings.length > 0) {
|
|
141
|
+
lines.push(`## Warnings (${warnings.length})`);
|
|
142
|
+
for (const w of warnings)
|
|
143
|
+
lines.push(` ⚠️ ${w.message}`);
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
if (infos.length > 0) {
|
|
147
|
+
lines.push(`## Info (${infos.length})`);
|
|
148
|
+
for (const i of infos)
|
|
149
|
+
lines.push(` ℹ️ ${i.message}`);
|
|
150
|
+
lines.push("");
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@realvendex/pi-ci",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pi-native GitHub Actions workflow generator for pi.dev extensions — pre-configured test, lint, build, and publish jobs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist", "README.md"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"lint": "biome check .",
|
|
13
|
+
"format": "biome check --write ."
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi",
|
|
18
|
+
"pi-coding-agent",
|
|
19
|
+
"ci-cd",
|
|
20
|
+
"github-actions",
|
|
21
|
+
"workflow",
|
|
22
|
+
"automation",
|
|
23
|
+
"devops"
|
|
24
|
+
],
|
|
25
|
+
"author": "realvendex",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@earendil-works/pi-ai": "*",
|
|
29
|
+
"@earendil-works/pi-agent-core": "*",
|
|
30
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
31
|
+
"@earendil-works/pi-tui": "*",
|
|
32
|
+
"typebox": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "^1.9.0",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
37
|
+
"typescript": "^5.5.0",
|
|
38
|
+
"typebox": "^1.0.0",
|
|
39
|
+
"vitest": "^2.0.0"
|
|
40
|
+
},
|
|
41
|
+
"pi": {
|
|
42
|
+
"extensions": ["./dist"]
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/ZachDreamZ/pi-ci.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/ZachDreamZ/pi-ci/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/ZachDreamZ/pi-ci#readme"
|
|
52
|
+
}
|