jettypod 4.4.118 → 4.4.121
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/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- package/docs/bdd-guidance.md +0 -390
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof Scenario Runner — spawns cucumber-js and streams BDD results
|
|
3
|
+
* in real time to the proof dashboard.
|
|
4
|
+
*
|
|
5
|
+
* Uses Tauri IPC (spawn_process) when available, simulated behavior otherwise.
|
|
6
|
+
* Parses cucumber-js message format output for step-by-step progress.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { invoke, listen, isTauri } from './tauri';
|
|
10
|
+
|
|
11
|
+
// ─── Types ───────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface ScenarioStep {
|
|
14
|
+
keyword: string;
|
|
15
|
+
text: string;
|
|
16
|
+
status: 'passed' | 'running' | 'pending' | 'failed' | 'skipped';
|
|
17
|
+
duration?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Scenario {
|
|
21
|
+
title: string;
|
|
22
|
+
status: 'passed' | 'running' | 'pending' | 'failed';
|
|
23
|
+
steps: ScenarioStep[];
|
|
24
|
+
duration?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScenarioRunnerStatus {
|
|
28
|
+
scenarios: Scenario[];
|
|
29
|
+
logs: string[];
|
|
30
|
+
state: 'idle' | 'running' | 'complete' | 'failed';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ScenarioRunnerCallback = (status: ScenarioRunnerStatus) => void;
|
|
34
|
+
|
|
35
|
+
// ─── Simulated scenario data ─────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const SIMULATED_SCENARIOS: { title: string; steps: { keyword: string; text: string }[] }[] = [
|
|
38
|
+
{
|
|
39
|
+
title: 'User can reach proof dashboard from kanban card',
|
|
40
|
+
steps: [
|
|
41
|
+
{ keyword: 'Given', text: 'I am viewing the kanban board' },
|
|
42
|
+
{ keyword: 'And', text: 'there is a work item that is ready for review' },
|
|
43
|
+
{ keyword: 'When', text: 'I click the QA button on that card' },
|
|
44
|
+
{ keyword: 'Then', text: 'I am on the proof dashboard for that work item' },
|
|
45
|
+
{ keyword: 'And', text: 'I see the service health strip' },
|
|
46
|
+
{ keyword: 'And', text: 'I see the scenario list' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Services auto-launch when proof starts',
|
|
51
|
+
steps: [
|
|
52
|
+
{ keyword: 'Given', text: 'I am on the proof dashboard for a work item' },
|
|
53
|
+
{ keyword: 'When', text: 'I click the Run Proof button' },
|
|
54
|
+
{ keyword: 'Then', text: 'required services start in dependency order' },
|
|
55
|
+
{ keyword: 'And', text: 'each service shows a health indicator' },
|
|
56
|
+
{ keyword: 'And', text: 'the proof waits until all services are healthy' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'BDD scenarios stream results in real time',
|
|
61
|
+
steps: [
|
|
62
|
+
{ keyword: 'Given', text: 'I am on the proof dashboard for a work item' },
|
|
63
|
+
{ keyword: 'And', text: 'all required services are healthy' },
|
|
64
|
+
{ keyword: 'When', text: 'the proof run begins' },
|
|
65
|
+
{ keyword: 'Then', text: 'each scenario appears in the scenario list' },
|
|
66
|
+
{ keyword: 'And', text: 'each step streams its status as it executes' },
|
|
67
|
+
{ keyword: 'And', text: 'the progress bar updates with each completed step' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Inspector tabs show proof details',
|
|
72
|
+
steps: [
|
|
73
|
+
{ keyword: 'Given', text: 'a proof run is in progress' },
|
|
74
|
+
{ keyword: 'When', text: 'I switch between inspector tabs' },
|
|
75
|
+
{ keyword: 'Then', text: 'the Flow tab shows chronological event traces' },
|
|
76
|
+
{ keyword: 'And', text: 'the Scenarios tab shows step-by-step BDD progress' },
|
|
77
|
+
{ keyword: 'And', text: 'the API tab shows HTTP and IPC calls with latency' },
|
|
78
|
+
{ keyword: 'And', text: 'the DB tab shows table mutations with before and after values' },
|
|
79
|
+
{ keyword: 'And', text: 'the Logs tab shows raw service output' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
// ─── Scenario Runner ─────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export class ScenarioRunner {
|
|
87
|
+
private scenarios: Scenario[] = [];
|
|
88
|
+
private logs: string[] = [];
|
|
89
|
+
private state: ScenarioRunnerStatus['state'] = 'idle';
|
|
90
|
+
private onUpdate: ScenarioRunnerCallback;
|
|
91
|
+
private unlisteners: (() => void)[] = [];
|
|
92
|
+
private pid: number | null = null;
|
|
93
|
+
private outputBuffer = '';
|
|
94
|
+
|
|
95
|
+
constructor(onUpdate: ScenarioRunnerCallback) {
|
|
96
|
+
this.onUpdate = onUpdate;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getStatus(): ScenarioRunnerStatus {
|
|
100
|
+
return {
|
|
101
|
+
scenarios: this.scenarios.map(s => ({ ...s, steps: [...s.steps] })),
|
|
102
|
+
logs: [...this.logs],
|
|
103
|
+
state: this.state,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async run(featureFile: string | null): Promise<void> {
|
|
108
|
+
this.state = 'running';
|
|
109
|
+
this.scenarios = [];
|
|
110
|
+
this.logs = [];
|
|
111
|
+
this.notify();
|
|
112
|
+
|
|
113
|
+
if (featureFile && isTauri()) {
|
|
114
|
+
await this.runReal(featureFile);
|
|
115
|
+
} else if (featureFile && !isTauri()) {
|
|
116
|
+
// Outside Tauri with a real feature file — can't spawn processes,
|
|
117
|
+
// fall back to simulated run
|
|
118
|
+
this.addLog(`Feature file: ${featureFile} (simulated — no Tauri IPC)`);
|
|
119
|
+
await this.runSimulated();
|
|
120
|
+
} else {
|
|
121
|
+
// No feature file — simulated run
|
|
122
|
+
this.addLog('No feature file linked to this work item — running simulated scenarios');
|
|
123
|
+
await this.runSimulated();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async stop(): Promise<void> {
|
|
128
|
+
if (this.pid && isTauri()) {
|
|
129
|
+
try {
|
|
130
|
+
await invoke('kill_process', { pid: this.pid });
|
|
131
|
+
} catch { /* best effort */ }
|
|
132
|
+
}
|
|
133
|
+
this.state = 'idle';
|
|
134
|
+
this.pid = null;
|
|
135
|
+
this.notify();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
destroy(): void {
|
|
139
|
+
for (const unlisten of this.unlisteners) {
|
|
140
|
+
unlisten();
|
|
141
|
+
}
|
|
142
|
+
this.unlisteners = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── Real Tauri IPC implementation ──────────────────────
|
|
146
|
+
|
|
147
|
+
private async runReal(featureFile: string): Promise<void> {
|
|
148
|
+
// Listen for process output to parse cucumber messages
|
|
149
|
+
const outputUnlisten = await listen<{ pid: number; lines: string[] }>(
|
|
150
|
+
'process-output-batch',
|
|
151
|
+
(event) => {
|
|
152
|
+
if (event.payload.pid !== this.pid) return;
|
|
153
|
+
|
|
154
|
+
for (const line of event.payload.lines) {
|
|
155
|
+
this.addLog(line);
|
|
156
|
+
this.outputBuffer += line;
|
|
157
|
+
this.parseCucumberOutput();
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
this.unlisteners.push(outputUnlisten);
|
|
162
|
+
|
|
163
|
+
const exitUnlisten = await listen<{ pid: number; code: number }>(
|
|
164
|
+
'process-exit',
|
|
165
|
+
(event) => {
|
|
166
|
+
if (event.payload.pid !== this.pid) return;
|
|
167
|
+
this.state = event.payload.code === 0 ? 'complete' : 'failed';
|
|
168
|
+
this.pid = null;
|
|
169
|
+
this.notify();
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
this.unlisteners.push(exitUnlisten);
|
|
173
|
+
|
|
174
|
+
// Spawn cucumber-js with message format for streaming
|
|
175
|
+
this.pid = await invoke<number>('spawn_process', {
|
|
176
|
+
command: 'npx',
|
|
177
|
+
args: ['cucumber-js', featureFile, '--format', 'message'],
|
|
178
|
+
label: 'cucumber-bdd',
|
|
179
|
+
kind: 'DevServer',
|
|
180
|
+
cwd: null,
|
|
181
|
+
envVars: {},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private parseCucumberOutput(): void {
|
|
186
|
+
// Parse newline-delimited JSON messages from cucumber
|
|
187
|
+
const lines = this.outputBuffer.split('\n');
|
|
188
|
+
this.outputBuffer = lines.pop() || ''; // Keep incomplete last line
|
|
189
|
+
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
const trimmed = line.trim();
|
|
192
|
+
if (!trimmed || !trimmed.startsWith('{')) continue;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const msg = JSON.parse(trimmed);
|
|
196
|
+
this.handleCucumberMessage(msg);
|
|
197
|
+
} catch {
|
|
198
|
+
// Not valid JSON, skip
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private handleCucumberMessage(msg: Record<string, unknown>): void {
|
|
204
|
+
// Cucumber message format events
|
|
205
|
+
if (msg.pickle) {
|
|
206
|
+
const pickle = msg.pickle as { name: string; steps: { text: string; keyword?: string }[] };
|
|
207
|
+
this.scenarios.push({
|
|
208
|
+
title: pickle.name,
|
|
209
|
+
status: 'pending',
|
|
210
|
+
steps: pickle.steps.map(s => ({
|
|
211
|
+
keyword: (s.keyword || 'Step').trim(),
|
|
212
|
+
text: s.text,
|
|
213
|
+
status: 'pending' as const,
|
|
214
|
+
})),
|
|
215
|
+
});
|
|
216
|
+
this.notify();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (msg.testCaseStarted) {
|
|
220
|
+
// Mark the next pending scenario as running
|
|
221
|
+
const pendingScenario = this.scenarios.find(s => s.status === 'pending');
|
|
222
|
+
if (pendingScenario) {
|
|
223
|
+
pendingScenario.status = 'running';
|
|
224
|
+
if (pendingScenario.steps.length > 0) {
|
|
225
|
+
pendingScenario.steps[0].status = 'running';
|
|
226
|
+
}
|
|
227
|
+
this.notify();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (msg.testStepFinished) {
|
|
232
|
+
const result = msg.testStepFinished as { testStepResult: { status: string; duration?: { nanos: number } } };
|
|
233
|
+
const runningScenario = this.scenarios.find(s => s.status === 'running');
|
|
234
|
+
if (runningScenario) {
|
|
235
|
+
const runningStep = runningScenario.steps.find(s => s.status === 'running');
|
|
236
|
+
if (runningStep) {
|
|
237
|
+
const status = result.testStepResult.status.toLowerCase();
|
|
238
|
+
runningStep.status = (status === 'passed' ? 'passed' : 'failed') as ScenarioStep['status'];
|
|
239
|
+
if (result.testStepResult.duration) {
|
|
240
|
+
runningStep.duration = Math.round(result.testStepResult.duration.nanos / 1_000_000);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (runningStep.status === 'failed') {
|
|
244
|
+
// Skip all remaining pending steps in this scenario
|
|
245
|
+
for (const step of runningScenario.steps) {
|
|
246
|
+
if (step.status === 'pending') {
|
|
247
|
+
step.status = 'skipped';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
// Mark next pending step as running
|
|
252
|
+
const nextPending = runningScenario.steps.find(s => s.status === 'pending');
|
|
253
|
+
if (nextPending) {
|
|
254
|
+
nextPending.status = 'running';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
this.notify();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (msg.testCaseFinished) {
|
|
263
|
+
const runningScenario = this.scenarios.find(s => s.status === 'running');
|
|
264
|
+
if (runningScenario) {
|
|
265
|
+
const allPassed = runningScenario.steps.every(s => s.status === 'passed');
|
|
266
|
+
runningScenario.status = allPassed ? 'passed' : 'failed';
|
|
267
|
+
|
|
268
|
+
// Calculate total duration
|
|
269
|
+
const totalDuration = runningScenario.steps.reduce(
|
|
270
|
+
(acc, s) => acc + (s.duration || 0), 0
|
|
271
|
+
);
|
|
272
|
+
runningScenario.duration = totalDuration;
|
|
273
|
+
this.notify();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── Simulated implementation (dev mode) ────────────────
|
|
279
|
+
|
|
280
|
+
private async runSimulated(): Promise<void> {
|
|
281
|
+
// Create all scenarios in pending state first
|
|
282
|
+
this.scenarios = SIMULATED_SCENARIOS.map(s => ({
|
|
283
|
+
title: s.title,
|
|
284
|
+
status: 'pending' as const,
|
|
285
|
+
steps: s.steps.map(step => ({
|
|
286
|
+
...step,
|
|
287
|
+
status: 'pending' as const,
|
|
288
|
+
})),
|
|
289
|
+
}));
|
|
290
|
+
this.notify();
|
|
291
|
+
|
|
292
|
+
// Run through each scenario
|
|
293
|
+
for (const scenario of this.scenarios) {
|
|
294
|
+
scenario.status = 'running';
|
|
295
|
+
this.notify();
|
|
296
|
+
|
|
297
|
+
const scenarioStart = Date.now();
|
|
298
|
+
|
|
299
|
+
let scenarioFailed = false;
|
|
300
|
+
|
|
301
|
+
for (const step of scenario.steps) {
|
|
302
|
+
if (scenarioFailed) {
|
|
303
|
+
step.status = 'skipped';
|
|
304
|
+
this.addLog(` - ${step.keyword} ${step.text} (skipped)`);
|
|
305
|
+
this.notify();
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
step.status = 'running';
|
|
310
|
+
this.notify();
|
|
311
|
+
|
|
312
|
+
// Simulate step execution (100-400ms per step)
|
|
313
|
+
const stepStart = Date.now();
|
|
314
|
+
const delay = 100 + Math.random() * 300;
|
|
315
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
316
|
+
|
|
317
|
+
step.status = 'passed';
|
|
318
|
+
step.duration = Date.now() - stepStart;
|
|
319
|
+
this.addLog(` ✓ ${step.keyword} ${step.text} (${step.duration}ms)`);
|
|
320
|
+
this.notify();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
scenario.status = scenarioFailed ? 'failed' : 'passed';
|
|
324
|
+
scenario.duration = Date.now() - scenarioStart;
|
|
325
|
+
this.addLog(`${scenarioFailed ? '✗' : '✓'} Scenario: ${scenario.title} (${scenario.duration}ms)`);
|
|
326
|
+
this.notify();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.state = 'complete';
|
|
330
|
+
this.notify();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
private addLog(line: string): void {
|
|
336
|
+
this.logs.push(line);
|
|
337
|
+
// Keep last 500 lines
|
|
338
|
+
if (this.logs.length > 500) {
|
|
339
|
+
this.logs = this.logs.slice(-500);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private notify(): void {
|
|
344
|
+
this.onUpdate(this.getStatus());
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -99,14 +99,36 @@ function runMigrations(betterDb) {
|
|
|
99
99
|
)
|
|
100
100
|
`);
|
|
101
101
|
|
|
102
|
-
//
|
|
103
|
-
|
|
102
|
+
// Create _meta table for schema versioning
|
|
103
|
+
betterDb.exec(`
|
|
104
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
105
|
+
key TEXT PRIMARY KEY,
|
|
106
|
+
value TEXT
|
|
107
|
+
)
|
|
108
|
+
`);
|
|
104
109
|
|
|
105
110
|
// Load migration files
|
|
106
111
|
const files = fs.readdirSync(migrationsDir)
|
|
107
112
|
.filter(f => f.endsWith('.js') && f !== 'index.js' && !f.endsWith('.test.js'))
|
|
108
113
|
.sort();
|
|
109
114
|
|
|
115
|
+
const knownMigrationCount = files.length;
|
|
116
|
+
|
|
117
|
+
// Check schema version for forward-compatibility
|
|
118
|
+
const versionRow = betterDb.prepare('SELECT value FROM _meta WHERE key = ?').get('schema_version');
|
|
119
|
+
const currentVersion = versionRow ? parseInt(versionRow.value, 10) : 0;
|
|
120
|
+
|
|
121
|
+
if (currentVersion > knownMigrationCount) {
|
|
122
|
+
console.warn(
|
|
123
|
+
`⚠️ Database schema version (${currentVersion}) is newer than this version of JettyPod supports (${knownMigrationCount}).` +
|
|
124
|
+
'\n Please update JettyPod to avoid compatibility issues.'
|
|
125
|
+
);
|
|
126
|
+
return; // Don't run migrations — the DB is from a newer version
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get already-applied migrations
|
|
130
|
+
const applied = betterDb.prepare('SELECT id FROM migrations').all().map(r => r.id);
|
|
131
|
+
|
|
110
132
|
// Create shim for callback-based migration API
|
|
111
133
|
const shim = createDbShim(betterDb);
|
|
112
134
|
|
|
@@ -140,6 +162,9 @@ function runMigrations(betterDb) {
|
|
|
140
162
|
throw err;
|
|
141
163
|
}
|
|
142
164
|
}
|
|
165
|
+
|
|
166
|
+
// Update schema version to reflect current state
|
|
167
|
+
betterDb.prepare('INSERT OR REPLACE INTO _meta (key, value) VALUES (?, ?)').run('schema_version', String(knownMigrationCount));
|
|
143
168
|
}
|
|
144
169
|
|
|
145
170
|
module.exports = { runMigrations };
|