@aion0/forge 0.4.2 → 0.4.4
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 +1 -1
- package/RELEASE_NOTES.md +19 -6
- package/app/api/issue-scanner/route.ts +2 -2
- package/app/api/pipelines/route.ts +14 -0
- package/app/api/project-pipelines/route.ts +68 -0
- package/app/icon.png +0 -0
- package/app/login/page.tsx +1 -0
- package/components/Dashboard.tsx +11 -12
- package/components/PipelineEditor.tsx +3 -1
- package/components/PipelineView.tsx +253 -128
- package/components/ProjectDetail.tsx +163 -230
- package/forge-logo.png +0 -0
- package/lib/help-docs/05-pipelines.md +22 -7
- package/lib/help-docs/09-issue-autofix.md +1 -1
- package/lib/init.ts +7 -1
- package/lib/issue-scanner.ts +2 -2
- package/lib/pipeline-scheduler.ts +239 -0
- package/lib/pipeline.ts +43 -87
- package/middleware.ts +2 -1
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
- package/src/core/db/database.ts +24 -0
- package/app/icon.svg +0 -26
package/README.md
CHANGED
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
# Forge v0.4.
|
|
1
|
+
# Forge v0.4.4
|
|
2
2
|
|
|
3
3
|
Released: 2026-03-21
|
|
4
4
|
|
|
5
|
-
## Changes since v0.4.
|
|
5
|
+
## Changes since v0.4.3
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- feat: add scheduled pipeline execution for project bindings
|
|
9
|
+
- feat: pipeline import/delete + editor cancel confirmation
|
|
6
10
|
|
|
7
11
|
### Bug Fixes
|
|
8
|
-
- fix:
|
|
9
|
-
|
|
12
|
+
- fix: embed editor in right panel, button nesting, fetchData error handling
|
|
13
|
+
|
|
14
|
+
### Refactoring
|
|
15
|
+
- refactor: generic project-pipeline bindings replace hardcoded issue scanner
|
|
16
|
+
- refactor: pipeline UI — workflow list in sidebar, project dropdown, per-workflow history
|
|
17
|
+
- refactor: merge issue-auto-fix + pr-review into single issue-fix-and-review pipeline
|
|
10
18
|
|
|
11
19
|
### Documentation
|
|
12
|
-
-
|
|
20
|
+
- feat: pipeline import/delete + editor cancel confirmation
|
|
21
|
+
|
|
22
|
+
### Other
|
|
23
|
+
- fix issue
|
|
24
|
+
- fix issue
|
|
25
|
+
- fix issues for no any issue on github
|
|
13
26
|
|
|
14
27
|
|
|
15
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.
|
|
28
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.3...v0.4.4
|
|
@@ -61,7 +61,7 @@ export async function POST(req: Request) {
|
|
|
61
61
|
const config = getConfig(body.projectPath);
|
|
62
62
|
const projectName = config?.projectName || body.projectName;
|
|
63
63
|
try {
|
|
64
|
-
const pipeline = startPipeline('issue-
|
|
64
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
65
65
|
issue_id: String(body.issueId),
|
|
66
66
|
project: projectName,
|
|
67
67
|
base_branch: config?.baseBranch || body.baseBranch || 'auto-detect',
|
|
@@ -93,7 +93,7 @@ export async function POST(req: Request) {
|
|
|
93
93
|
// Reset the processed record first, then re-create with new pipeline
|
|
94
94
|
resetProcessedIssue(body.projectPath, body.issueId);
|
|
95
95
|
try {
|
|
96
|
-
const pipeline = startPipeline('issue-
|
|
96
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
97
97
|
issue_id: String(body.issueId),
|
|
98
98
|
project: projectName,
|
|
99
99
|
base_branch: config?.baseBranch || 'auto-detect',
|
|
@@ -61,6 +61,20 @@ export async function POST(req: Request) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// Delete workflow
|
|
65
|
+
if (body.action === 'delete-workflow' && body.name) {
|
|
66
|
+
const { existsSync: ex, unlinkSync: ul } = await import('node:fs');
|
|
67
|
+
const filePath = join(FLOWS_DIR, `${body.name}.yaml`);
|
|
68
|
+
const altPath = join(FLOWS_DIR, `${body.name}.yml`);
|
|
69
|
+
const path = ex(filePath) ? filePath : ex(altPath) ? altPath : null;
|
|
70
|
+
if (!path) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
71
|
+
// Check if built-in
|
|
72
|
+
const w = listWorkflows().find(w => w.name === body.name);
|
|
73
|
+
if (w?.builtin) return NextResponse.json({ error: 'Cannot delete built-in workflow' }, { status: 400 });
|
|
74
|
+
ul(path);
|
|
75
|
+
return NextResponse.json({ ok: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
// Start pipeline
|
|
65
79
|
const { workflow, input } = body;
|
|
66
80
|
if (!workflow) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getBindings,
|
|
4
|
+
addBinding,
|
|
5
|
+
removeBinding,
|
|
6
|
+
updateBinding,
|
|
7
|
+
getRuns,
|
|
8
|
+
deleteRun,
|
|
9
|
+
triggerPipeline,
|
|
10
|
+
getNextRunTime,
|
|
11
|
+
} from '@/lib/pipeline-scheduler';
|
|
12
|
+
import { listWorkflows } from '@/lib/pipeline';
|
|
13
|
+
|
|
14
|
+
// GET /api/project-pipelines?project=PATH
|
|
15
|
+
export async function GET(req: Request) {
|
|
16
|
+
const { searchParams } = new URL(req.url);
|
|
17
|
+
const projectPath = searchParams.get('project');
|
|
18
|
+
if (!projectPath) return NextResponse.json({ error: 'project required' }, { status: 400 });
|
|
19
|
+
|
|
20
|
+
const bindings = getBindings(projectPath).map(b => ({
|
|
21
|
+
...b,
|
|
22
|
+
nextRunAt: getNextRunTime(b),
|
|
23
|
+
}));
|
|
24
|
+
const runs = getRuns(projectPath);
|
|
25
|
+
const workflows = listWorkflows().map(w => ({ name: w.name, description: w.description, builtin: w.builtin }));
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({ bindings, runs, workflows });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// POST /api/project-pipelines
|
|
31
|
+
export async function POST(req: Request) {
|
|
32
|
+
const body = await req.json();
|
|
33
|
+
|
|
34
|
+
if (body.action === 'add') {
|
|
35
|
+
const { projectPath, projectName, workflowName, config } = body;
|
|
36
|
+
if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
|
|
37
|
+
addBinding(projectPath, projectName || projectPath.split('/').pop(), workflowName, config);
|
|
38
|
+
return NextResponse.json({ ok: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (body.action === 'remove') {
|
|
42
|
+
removeBinding(body.projectPath, body.workflowName);
|
|
43
|
+
return NextResponse.json({ ok: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (body.action === 'update') {
|
|
47
|
+
updateBinding(body.projectPath, body.workflowName, { enabled: body.enabled, config: body.config });
|
|
48
|
+
return NextResponse.json({ ok: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (body.action === 'trigger') {
|
|
52
|
+
const { projectPath, projectName, workflowName, input } = body;
|
|
53
|
+
if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
|
|
54
|
+
try {
|
|
55
|
+
const result = triggerPipeline(projectPath, projectName || projectPath.split('/').pop(), workflowName, input);
|
|
56
|
+
return NextResponse.json({ ok: true, ...result });
|
|
57
|
+
} catch (e: any) {
|
|
58
|
+
return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (body.action === 'delete-run') {
|
|
63
|
+
deleteRun(body.id);
|
|
64
|
+
return NextResponse.json({ ok: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
68
|
+
}
|
package/app/icon.png
ADDED
|
Binary file
|
package/app/login/page.tsx
CHANGED
|
@@ -38,6 +38,7 @@ export default function LoginPage() {
|
|
|
38
38
|
<div className="min-h-screen flex items-center justify-center">
|
|
39
39
|
<div className="w-80 space-y-6">
|
|
40
40
|
<div className="text-center">
|
|
41
|
+
<img src="/icon.png" alt="Forge" width={48} height={48} className="rounded mx-auto mb-2" />
|
|
41
42
|
<h1 className="text-2xl font-bold text-[var(--text-primary)]">Forge</h1>
|
|
42
43
|
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
|
43
44
|
{isRemote ? 'Remote Access' : 'Local Access'}
|
package/components/Dashboard.tsx
CHANGED
|
@@ -137,18 +137,16 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
137
137
|
}, []);
|
|
138
138
|
|
|
139
139
|
const fetchData = useCallback(async () => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
setUsage(statusData.usage);
|
|
151
|
-
setProjects(projectsData);
|
|
140
|
+
try {
|
|
141
|
+
const [tasksRes, statusRes, projectsRes] = await Promise.all([
|
|
142
|
+
fetch('/api/tasks'),
|
|
143
|
+
fetch('/api/status'),
|
|
144
|
+
fetch('/api/projects'),
|
|
145
|
+
]);
|
|
146
|
+
if (tasksRes.ok) setTasks(await tasksRes.json());
|
|
147
|
+
if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
|
|
148
|
+
if (projectsRes.ok) setProjects(await projectsRes.json());
|
|
149
|
+
} catch {}
|
|
152
150
|
}, []);
|
|
153
151
|
|
|
154
152
|
useEffect(() => {
|
|
@@ -166,6 +164,7 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
166
164
|
{/* Top bar */}
|
|
167
165
|
<header className="h-12 border-b-2 border-[var(--border)] flex items-center justify-between px-4 shrink-0 bg-[var(--bg-secondary)]">
|
|
168
166
|
<div className="flex items-center gap-4">
|
|
167
|
+
<img src="/icon.png" alt="Forge" width={28} height={28} className="rounded" />
|
|
169
168
|
<span className="text-sm font-bold text-[var(--accent)]">Forge</span>
|
|
170
169
|
{versionInfo && (
|
|
171
170
|
<span className="flex items-center gap-1.5">
|
|
@@ -360,7 +360,9 @@ export default function PipelineEditor({ onSave, onClose, initialYaml }: {
|
|
|
360
360
|
Save
|
|
361
361
|
</button>
|
|
362
362
|
<button
|
|
363
|
-
onClick={
|
|
363
|
+
onClick={() => {
|
|
364
|
+
if (confirm('Discard unsaved changes?')) onClose();
|
|
365
|
+
}}
|
|
364
366
|
className="text-xs px-3 py-1 text-gray-400 hover:text-white"
|
|
365
367
|
>
|
|
366
368
|
Cancel
|