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
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
// Test results database layer
|
|
2
|
-
// Ingests cucumber-results.json into SQLite with upsert semantics
|
|
3
|
-
// so results survive git operations and file resets.
|
|
4
|
-
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
|
-
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
|
-
const { runMigrations } = require('./run-migrations');
|
|
12
|
-
|
|
13
|
-
export interface TestResultRow {
|
|
14
|
-
scenario_name: string;
|
|
15
|
-
feature_file: string;
|
|
16
|
-
status: 'passed' | 'failed' | 'pending';
|
|
17
|
-
duration_ms: number;
|
|
18
|
-
error_message: string | null;
|
|
19
|
-
failed_step: string | null;
|
|
20
|
-
run_at: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface TestRunRow {
|
|
24
|
-
id: number;
|
|
25
|
-
run_at: string;
|
|
26
|
-
total_scenarios: number;
|
|
27
|
-
passed: number;
|
|
28
|
-
failed: number;
|
|
29
|
-
pending: number;
|
|
30
|
-
duration_ms: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// --- Database connection (reuses existing singleton pattern) ---
|
|
34
|
-
|
|
35
|
-
let cachedDb: Database.Database | null = null;
|
|
36
|
-
let cachedDbPath: string | null = null;
|
|
37
|
-
|
|
38
|
-
function getProjectRoot(): string {
|
|
39
|
-
if (process.env.JETTYPOD_PROJECT_PATH) {
|
|
40
|
-
return process.env.JETTYPOD_PROJECT_PATH;
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error('Not in a git repository and JETTYPOD_PROJECT_PATH not set');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getDb(): Database.Database {
|
|
50
|
-
const dbPath = path.join(getProjectRoot(), '.jettypod', 'work.db');
|
|
51
|
-
|
|
52
|
-
if (cachedDb && cachedDbPath !== dbPath) {
|
|
53
|
-
cachedDb.close();
|
|
54
|
-
cachedDb = null;
|
|
55
|
-
cachedDbPath = null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!cachedDb) {
|
|
59
|
-
cachedDb = new Database(dbPath);
|
|
60
|
-
cachedDbPath = dbPath;
|
|
61
|
-
cachedDb.pragma('journal_mode = WAL');
|
|
62
|
-
cachedDb.pragma('foreign_keys = ON');
|
|
63
|
-
try {
|
|
64
|
-
runMigrations(cachedDb);
|
|
65
|
-
} catch (err) {
|
|
66
|
-
console.error('[test-results-db] Failed to run migrations on', dbPath, err);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return cachedDb;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// --- Ingestion ---
|
|
73
|
-
|
|
74
|
-
interface CucumberStep {
|
|
75
|
-
keyword?: string;
|
|
76
|
-
name?: string;
|
|
77
|
-
result?: {
|
|
78
|
-
status: string;
|
|
79
|
-
duration?: number;
|
|
80
|
-
error_message?: string;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
interface CucumberElement {
|
|
85
|
-
type: string;
|
|
86
|
-
name: string;
|
|
87
|
-
steps?: CucumberStep[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
interface CucumberFeature {
|
|
91
|
-
uri: string;
|
|
92
|
-
elements?: CucumberElement[];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Ingest cucumber-results.json into SQLite.
|
|
97
|
-
* Uses upsert semantics: each scenario's latest result is kept,
|
|
98
|
-
* unrelated scenarios are not affected.
|
|
99
|
-
*
|
|
100
|
-
* @param jsonPath - Path to cucumber-results.json (defaults to project root)
|
|
101
|
-
* @returns Number of scenarios ingested, or 0 if file doesn't exist
|
|
102
|
-
*/
|
|
103
|
-
export function ingestCucumberResults(jsonPath?: string): number {
|
|
104
|
-
const projectRoot = getProjectRoot();
|
|
105
|
-
const resultsPath = jsonPath || path.join(projectRoot, 'cucumber-results.json');
|
|
106
|
-
|
|
107
|
-
if (!fs.existsSync(resultsPath)) {
|
|
108
|
-
return 0;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let cucumberJson: CucumberFeature[];
|
|
112
|
-
try {
|
|
113
|
-
const raw = fs.readFileSync(resultsPath, 'utf-8');
|
|
114
|
-
cucumberJson = JSON.parse(raw);
|
|
115
|
-
} catch {
|
|
116
|
-
return 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!Array.isArray(cucumberJson) || cucumberJson.length === 0) {
|
|
120
|
-
return 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return ingestCucumberJson(cucumberJson);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Ingest parsed cucumber JSON data into SQLite.
|
|
128
|
-
* Separated from file reading for testability.
|
|
129
|
-
*/
|
|
130
|
-
export function ingestCucumberJson(cucumberJson: CucumberFeature[]): number {
|
|
131
|
-
const db = getDb();
|
|
132
|
-
|
|
133
|
-
// Parse scenarios from cucumber JSON
|
|
134
|
-
const scenarios: {
|
|
135
|
-
feature_file: string;
|
|
136
|
-
scenario_name: string;
|
|
137
|
-
status: 'passed' | 'failed' | 'pending';
|
|
138
|
-
duration_ms: number;
|
|
139
|
-
error_message: string | null;
|
|
140
|
-
failed_step: string | null;
|
|
141
|
-
}[] = [];
|
|
142
|
-
|
|
143
|
-
for (const feature of cucumberJson) {
|
|
144
|
-
for (const element of feature.elements || []) {
|
|
145
|
-
if (element.type !== 'scenario') continue;
|
|
146
|
-
|
|
147
|
-
const steps = element.steps || [];
|
|
148
|
-
const failed = steps.some(s => s.result?.status === 'failed');
|
|
149
|
-
const pending = steps.some(s =>
|
|
150
|
-
s.result?.status === 'pending' || s.result?.status === 'undefined'
|
|
151
|
-
);
|
|
152
|
-
const totalDuration = steps.reduce(
|
|
153
|
-
(sum, s) => sum + (s.result?.duration || 0), 0
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
let errorMessage: string | null = null;
|
|
157
|
-
let failedStepName: string | null = null;
|
|
158
|
-
if (failed) {
|
|
159
|
-
const failedStepObj = steps.find(s => s.result?.status === 'failed');
|
|
160
|
-
errorMessage = failedStepObj?.result?.error_message || null;
|
|
161
|
-
failedStepName = failedStepObj
|
|
162
|
-
? `${failedStepObj.keyword || ''}${failedStepObj.name || ''}`.trim()
|
|
163
|
-
: null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
scenarios.push({
|
|
167
|
-
feature_file: feature.uri,
|
|
168
|
-
scenario_name: element.name,
|
|
169
|
-
status: failed ? 'failed' : pending ? 'pending' : 'passed',
|
|
170
|
-
duration_ms: Math.round(totalDuration / 1000000), // nanoseconds → ms
|
|
171
|
-
error_message: errorMessage,
|
|
172
|
-
failed_step: failedStepName,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (scenarios.length === 0) {
|
|
178
|
-
return 0;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Write in a transaction for atomicity
|
|
182
|
-
const insertRun = db.prepare(`
|
|
183
|
-
INSERT INTO test_runs (run_at, total_scenarios, passed, failed, pending, duration_ms)
|
|
184
|
-
VALUES (datetime('now'), ?, ?, ?, ?, ?)
|
|
185
|
-
`);
|
|
186
|
-
|
|
187
|
-
const upsertScenario = db.prepare(`
|
|
188
|
-
INSERT INTO test_scenarios (feature_file, scenario_name)
|
|
189
|
-
VALUES (?, ?)
|
|
190
|
-
ON CONFLICT(feature_file, scenario_name) DO NOTHING
|
|
191
|
-
`);
|
|
192
|
-
|
|
193
|
-
const getScenarioId = db.prepare(`
|
|
194
|
-
SELECT id FROM test_scenarios WHERE feature_file = ? AND scenario_name = ?
|
|
195
|
-
`);
|
|
196
|
-
|
|
197
|
-
const insertResult = db.prepare(`
|
|
198
|
-
INSERT INTO test_results (test_run_id, scenario_id, status, duration_ms, error_message, failed_step, run_at)
|
|
199
|
-
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
200
|
-
`);
|
|
201
|
-
|
|
202
|
-
const transaction = db.transaction(() => {
|
|
203
|
-
const passed = scenarios.filter(s => s.status === 'passed').length;
|
|
204
|
-
const failed = scenarios.filter(s => s.status === 'failed').length;
|
|
205
|
-
const pending = scenarios.filter(s => s.status === 'pending').length;
|
|
206
|
-
const totalDuration = scenarios.reduce((sum, s) => sum + s.duration_ms, 0);
|
|
207
|
-
|
|
208
|
-
const runResult = insertRun.run(scenarios.length, passed, failed, pending, totalDuration);
|
|
209
|
-
const runId = runResult.lastInsertRowid as number;
|
|
210
|
-
|
|
211
|
-
for (const scenario of scenarios) {
|
|
212
|
-
upsertScenario.run(scenario.feature_file, scenario.scenario_name);
|
|
213
|
-
const row = getScenarioId.get(scenario.feature_file, scenario.scenario_name) as { id: number };
|
|
214
|
-
insertResult.run(
|
|
215
|
-
runId,
|
|
216
|
-
row.id,
|
|
217
|
-
scenario.status,
|
|
218
|
-
scenario.duration_ms,
|
|
219
|
-
scenario.error_message,
|
|
220
|
-
scenario.failed_step
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return scenarios.length;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return transaction();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// --- Query functions ---
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Get the latest result for each scenario.
|
|
234
|
-
* Returns one row per scenario with its most recent test result.
|
|
235
|
-
*/
|
|
236
|
-
export function getLatestResults(): TestResultRow[] {
|
|
237
|
-
const db = getDb();
|
|
238
|
-
return db.prepare(`
|
|
239
|
-
SELECT
|
|
240
|
-
ts.scenario_name,
|
|
241
|
-
ts.feature_file,
|
|
242
|
-
tr.status,
|
|
243
|
-
tr.duration_ms,
|
|
244
|
-
tr.error_message,
|
|
245
|
-
tr.failed_step,
|
|
246
|
-
tr.run_at
|
|
247
|
-
FROM test_results tr
|
|
248
|
-
INNER JOIN test_scenarios ts ON tr.scenario_id = ts.id
|
|
249
|
-
WHERE tr.id = (
|
|
250
|
-
SELECT tr2.id FROM test_results tr2
|
|
251
|
-
WHERE tr2.scenario_id = tr.scenario_id
|
|
252
|
-
ORDER BY tr2.run_at DESC
|
|
253
|
-
LIMIT 1
|
|
254
|
-
)
|
|
255
|
-
ORDER BY ts.feature_file, ts.scenario_name
|
|
256
|
-
`).all() as TestResultRow[];
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Get the latest result for a specific scenario by name.
|
|
261
|
-
*/
|
|
262
|
-
export function getLatestResultForScenario(scenarioName: string): TestResultRow | null {
|
|
263
|
-
const db = getDb();
|
|
264
|
-
const row = db.prepare(`
|
|
265
|
-
SELECT
|
|
266
|
-
ts.scenario_name,
|
|
267
|
-
ts.feature_file,
|
|
268
|
-
tr.status,
|
|
269
|
-
tr.duration_ms,
|
|
270
|
-
tr.error_message,
|
|
271
|
-
tr.failed_step,
|
|
272
|
-
tr.run_at
|
|
273
|
-
FROM test_results tr
|
|
274
|
-
INNER JOIN test_scenarios ts ON tr.scenario_id = ts.id
|
|
275
|
-
WHERE ts.scenario_name = ?
|
|
276
|
-
ORDER BY tr.run_at DESC
|
|
277
|
-
LIMIT 1
|
|
278
|
-
`).get(scenarioName) as TestResultRow | undefined;
|
|
279
|
-
return row || null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Get the most recent test run metadata.
|
|
284
|
-
*/
|
|
285
|
-
export function getLatestTestRun(): TestRunRow | null {
|
|
286
|
-
const db = getDb();
|
|
287
|
-
const row = db.prepare(`
|
|
288
|
-
SELECT id, run_at, total_scenarios, passed, failed, pending, duration_ms
|
|
289
|
-
FROM test_runs
|
|
290
|
-
ORDER BY run_at DESC
|
|
291
|
-
LIMIT 1
|
|
292
|
-
`).get() as TestRunRow | undefined;
|
|
293
|
-
return row || null;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get all test runs (for history display).
|
|
298
|
-
*/
|
|
299
|
-
export function getTestRuns(limit: number = 50): TestRunRow[] {
|
|
300
|
-
const db = getDb();
|
|
301
|
-
return db.prepare(`
|
|
302
|
-
SELECT id, run_at, total_scenarios, passed, failed, pending, duration_ms
|
|
303
|
-
FROM test_runs
|
|
304
|
-
ORDER BY run_at DESC
|
|
305
|
-
LIMIT ?
|
|
306
|
-
`).all(limit) as TestRunRow[];
|
|
307
|
-
}
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
// Test data layer for the test dashboard
|
|
2
|
-
// Reads BDD test structure from feature files and results from SQLite
|
|
3
|
-
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
import { getLatestResults, getLatestTestRun } from './test-results-db';
|
|
8
|
-
|
|
9
|
-
// Types for test data structure
|
|
10
|
-
export interface TestScenario {
|
|
11
|
-
id: string;
|
|
12
|
-
title: string;
|
|
13
|
-
status: 'pass' | 'fail' | 'pending';
|
|
14
|
-
duration: string;
|
|
15
|
-
lastRun: string | null;
|
|
16
|
-
error?: string;
|
|
17
|
-
failedStep?: string;
|
|
18
|
-
steps: string[];
|
|
19
|
-
undefinedSteps: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface TestFeature {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
description: string;
|
|
26
|
-
featureFile: string;
|
|
27
|
-
scenarios: TestScenario[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface TestEpic {
|
|
31
|
-
id: string;
|
|
32
|
-
title: string;
|
|
33
|
-
healthBadge: { passing: number; failing: number };
|
|
34
|
-
features: TestFeature[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface TestSummary {
|
|
38
|
-
total: number;
|
|
39
|
-
passing: number;
|
|
40
|
-
failing: number;
|
|
41
|
-
pending: number;
|
|
42
|
-
lastRun: string | null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface TestDashboardData {
|
|
46
|
-
summary: TestSummary;
|
|
47
|
-
epics: TestEpic[];
|
|
48
|
-
standaloneFeatures: TestFeature[];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getProjectRoot(): string {
|
|
52
|
-
if (process.env.JETTYPOD_PROJECT_PATH) {
|
|
53
|
-
return process.env.JETTYPOD_PROJECT_PATH;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
|
|
57
|
-
} catch {
|
|
58
|
-
throw new Error('Not in a git repository and JETTYPOD_PROJECT_PATH not set');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Parse a .feature file to extract scenarios with steps and description
|
|
63
|
-
function parseFeatureFile(filePath: string): { title: string; description: string; scenarios: { name: string; steps: string[] }[] } {
|
|
64
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
65
|
-
const lines = content.split('\n');
|
|
66
|
-
|
|
67
|
-
let title = '';
|
|
68
|
-
const descriptionLines: string[] = [];
|
|
69
|
-
const scenarios: { name: string; steps: string[] }[] = [];
|
|
70
|
-
let inDescription = false;
|
|
71
|
-
let currentScenario: { name: string; steps: string[] } | null = null;
|
|
72
|
-
|
|
73
|
-
for (const line of lines) {
|
|
74
|
-
const trimmed = line.trim();
|
|
75
|
-
if (trimmed.startsWith('Feature:')) {
|
|
76
|
-
title = trimmed.replace('Feature:', '').trim();
|
|
77
|
-
inDescription = true;
|
|
78
|
-
} else if (trimmed.startsWith('Scenario:')) {
|
|
79
|
-
inDescription = false;
|
|
80
|
-
if (currentScenario) scenarios.push(currentScenario);
|
|
81
|
-
currentScenario = { name: trimmed.replace('Scenario:', '').trim(), steps: [] };
|
|
82
|
-
} else if (currentScenario && (trimmed.startsWith('Given ') || trimmed.startsWith('When ') || trimmed.startsWith('Then ') || trimmed.startsWith('And ') || trimmed.startsWith('But '))) {
|
|
83
|
-
currentScenario.steps.push(trimmed);
|
|
84
|
-
} else if (inDescription && trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('@')) {
|
|
85
|
-
descriptionLines.push(trimmed);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (currentScenario) scenarios.push(currentScenario);
|
|
89
|
-
|
|
90
|
-
return { title, description: descriptionLines.join(' ').trim(), scenarios };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Try to read Cucumber JSON results if available
|
|
94
|
-
function getCucumberResults(projectRoot: string): Map<string, { status: string; duration: number; error?: string; failedStep?: string }> | null {
|
|
95
|
-
const resultsPath = path.join(projectRoot, 'cucumber-results.json');
|
|
96
|
-
if (!fs.existsSync(resultsPath)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf-8'));
|
|
102
|
-
const scenarioResults = new Map<string, { status: string; duration: number; error?: string; failedStep?: string }>();
|
|
103
|
-
|
|
104
|
-
for (const feature of results) {
|
|
105
|
-
for (const element of feature.elements || []) {
|
|
106
|
-
if (element.type === 'scenario') {
|
|
107
|
-
const steps = element.steps || [];
|
|
108
|
-
const failed = steps.some((s: { result?: { status: string } }) => s.result?.status === 'failed');
|
|
109
|
-
const pending = steps.some((s: { result?: { status: string } }) => s.result?.status === 'pending' || s.result?.status === 'undefined');
|
|
110
|
-
const totalDuration = steps.reduce((sum: number, s: { result?: { duration: number } }) => sum + (s.result?.duration || 0), 0);
|
|
111
|
-
|
|
112
|
-
let error: string | undefined;
|
|
113
|
-
let failedStepName: string | undefined;
|
|
114
|
-
if (failed) {
|
|
115
|
-
const failedStepObj = steps.find((s: { result?: { status: string; error_message?: string }; keyword?: string; name?: string }) => s.result?.status === 'failed');
|
|
116
|
-
error = failedStepObj?.result?.error_message;
|
|
117
|
-
failedStepName = failedStepObj ? `${failedStepObj.keyword}${failedStepObj.name}`.trim() : undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
scenarioResults.set(element.name, {
|
|
121
|
-
status: failed ? 'failed' : pending ? 'pending' : 'passed',
|
|
122
|
-
duration: totalDuration / 1000000, // Convert nanoseconds to milliseconds
|
|
123
|
-
error,
|
|
124
|
-
failedStep: failedStepName,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return scenarioResults;
|
|
131
|
-
} catch {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Get all feature files from the project
|
|
137
|
-
function getFeatureFiles(projectRoot: string): string[] {
|
|
138
|
-
const featuresDir = path.join(projectRoot, 'features');
|
|
139
|
-
if (!fs.existsSync(featuresDir)) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const files: string[] = [];
|
|
144
|
-
const entries = fs.readdirSync(featuresDir, { withFileTypes: true });
|
|
145
|
-
|
|
146
|
-
for (const entry of entries) {
|
|
147
|
-
if (entry.isFile() && entry.name.endsWith('.feature')) {
|
|
148
|
-
files.push(path.join(featuresDir, entry.name));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return files;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Run cucumber dry-run to detect undefined steps (exported for separate async endpoint)
|
|
156
|
-
export function getUndefinedSteps(projectRoot?: string): Record<string, string[]> {
|
|
157
|
-
const root = projectRoot || getProjectRoot();
|
|
158
|
-
const undefinedMap: Record<string, string[]> = {};
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const result = execSync(
|
|
162
|
-
'NODE_ENV=test npx cucumber-js --dry-run --format json 2>/dev/null',
|
|
163
|
-
{ cwd: root, encoding: 'utf-8', timeout: 30000 }
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
const parsed = JSON.parse(result);
|
|
167
|
-
for (const feature of parsed) {
|
|
168
|
-
for (const element of (feature.elements || [])) {
|
|
169
|
-
if (element.type === 'scenario') {
|
|
170
|
-
const steps = element.steps || [];
|
|
171
|
-
const undefinedSteps: string[] = [];
|
|
172
|
-
for (const step of steps) {
|
|
173
|
-
if (step.result?.status === 'undefined') {
|
|
174
|
-
undefinedSteps.push(`${step.keyword}${step.name}`.trim());
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (undefinedSteps.length > 0) {
|
|
178
|
-
undefinedMap[element.name] = undefinedSteps;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
} catch (err: unknown) {
|
|
184
|
-
const error = err as { killed?: boolean; stderr?: string; message?: string };
|
|
185
|
-
if (error.killed) {
|
|
186
|
-
console.warn('[getUndefinedSteps] dry-run timed out');
|
|
187
|
-
} else if (error.stderr && error.stderr.includes('ambiguous')) {
|
|
188
|
-
console.warn('[getUndefinedSteps] ambiguous step definitions detected');
|
|
189
|
-
} else {
|
|
190
|
-
console.warn('[getUndefinedSteps] dry-run failed:', error.message || 'unknown error');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return undefinedMap;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Main function to get test dashboard data
|
|
198
|
-
// Reads scenario structure from feature files, results from SQLite database
|
|
199
|
-
export function getTestDashboardData(): TestDashboardData {
|
|
200
|
-
const projectRoot = getProjectRoot();
|
|
201
|
-
const featureFiles = getFeatureFiles(projectRoot);
|
|
202
|
-
|
|
203
|
-
// Build a map of scenario name → latest DB result
|
|
204
|
-
let dbResults: Map<string, { status: string; duration_ms: number; error_message: string | null; failed_step: string | null; run_at: string }>;
|
|
205
|
-
let lastRun: string | null = null;
|
|
206
|
-
try {
|
|
207
|
-
const rows = getLatestResults();
|
|
208
|
-
dbResults = new Map(rows.map(r => [r.scenario_name, r]));
|
|
209
|
-
const latestRun = getLatestTestRun();
|
|
210
|
-
lastRun = latestRun?.run_at || null;
|
|
211
|
-
} catch {
|
|
212
|
-
// DB not available (tables not created yet) — fall back to empty
|
|
213
|
-
dbResults = new Map();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const features: TestFeature[] = [];
|
|
217
|
-
let totalTests = 0;
|
|
218
|
-
let passingTests = 0;
|
|
219
|
-
let failingTests = 0;
|
|
220
|
-
let pendingTests = 0;
|
|
221
|
-
|
|
222
|
-
for (const filePath of featureFiles) {
|
|
223
|
-
const { title, description, scenarios: parsedScenarios } = parseFeatureFile(filePath);
|
|
224
|
-
const featureId = path.basename(filePath, '.feature');
|
|
225
|
-
|
|
226
|
-
const scenarios: TestScenario[] = parsedScenarios.map((scenario, index) => {
|
|
227
|
-
const result = dbResults.get(scenario.name);
|
|
228
|
-
let status: 'pass' | 'fail' | 'pending' = 'pending';
|
|
229
|
-
let duration = '0s';
|
|
230
|
-
let error: string | undefined;
|
|
231
|
-
let failedStep: string | undefined;
|
|
232
|
-
|
|
233
|
-
if (result) {
|
|
234
|
-
status = result.status === 'passed' ? 'pass' : result.status === 'failed' ? 'fail' : 'pending';
|
|
235
|
-
duration = result.duration_ms < 1000 ? `${Math.round(result.duration_ms)}ms` : `${(result.duration_ms / 1000).toFixed(1)}s`;
|
|
236
|
-
error = result.error_message || undefined;
|
|
237
|
-
failedStep = result.failed_step || undefined;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
totalTests++;
|
|
241
|
-
if (status === 'pass') passingTests++;
|
|
242
|
-
else if (status === 'fail') failingTests++;
|
|
243
|
-
else pendingTests++;
|
|
244
|
-
|
|
245
|
-
const undefinedSteps: string[] = [];
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
id: `${featureId}-${index}`,
|
|
249
|
-
title: scenario.name,
|
|
250
|
-
status,
|
|
251
|
-
duration,
|
|
252
|
-
lastRun: result?.run_at || null,
|
|
253
|
-
error,
|
|
254
|
-
failedStep,
|
|
255
|
-
steps: scenario.steps,
|
|
256
|
-
undefinedSteps,
|
|
257
|
-
};
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
features.push({
|
|
261
|
-
id: featureId,
|
|
262
|
-
title,
|
|
263
|
-
description,
|
|
264
|
-
featureFile: path.relative(projectRoot, filePath),
|
|
265
|
-
scenarios,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const summary: TestSummary = {
|
|
270
|
-
total: totalTests,
|
|
271
|
-
passing: passingTests,
|
|
272
|
-
failing: failingTests,
|
|
273
|
-
pending: pendingTests,
|
|
274
|
-
lastRun,
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
return {
|
|
278
|
-
summary,
|
|
279
|
-
epics: [],
|
|
280
|
-
standaloneFeatures: features,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
/** @type {import('next').NextConfig} */
|
|
4
|
-
const nextConfig = {
|
|
5
|
-
// Externalize modules with native bindings or dynamic requires
|
|
6
|
-
// - better-sqlite3: Used by dashboard's lib/db.ts (sync)
|
|
7
|
-
// - sqlite3: Used by lib/database.js (async) via worktree-facade
|
|
8
|
-
serverExternalPackages: ['better-sqlite3', 'sqlite3'],
|
|
9
|
-
|
|
10
|
-
// Set monorepo root for correct file tracing
|
|
11
|
-
outputFileTracingRoot: path.resolve(process.cwd(), '../..'),
|
|
12
|
-
|
|
13
|
-
// Note: Turbopack disabled - it can't handle dynamic requires in lib/migrations/index.js
|
|
14
|
-
// The webpack externals below properly externalize the jettypod lib chain
|
|
15
|
-
|
|
16
|
-
webpack: (config, { isServer }) => {
|
|
17
|
-
if (isServer) {
|
|
18
|
-
config.externals = config.externals || [];
|
|
19
|
-
config.externals.push(({ request }, callback) => {
|
|
20
|
-
// Externalize worktree-facade with RUNTIME path resolution.
|
|
21
|
-
// Uses 'var' external type so the path expression evaluates at runtime,
|
|
22
|
-
// not at build time (which would bake in the build machine's absolute path).
|
|
23
|
-
// Packaged app: JETTYPOD_RESOURCES_PATH/bin/lib/<module>
|
|
24
|
-
// Dev mode: process.cwd()/../../lib/<module>
|
|
25
|
-
if (request && request.includes('lib/worktree-facade')) {
|
|
26
|
-
const moduleName = request.split('lib/')[1];
|
|
27
|
-
return callback(null,
|
|
28
|
-
`var require(process.env.JETTYPOD_RESOURCES_PATH ` +
|
|
29
|
-
`? require('path').join(process.env.JETTYPOD_RESOURCES_PATH, 'bin', 'lib', '${moduleName}') ` +
|
|
30
|
-
`: require('path').resolve(process.cwd(), '../../lib', '${moduleName}'))`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
// Externalize run-migrations.js with RUNTIME path resolution.
|
|
34
|
-
// It uses dynamic require() to load migration files, which webpack
|
|
35
|
-
// replaces with a dead stub if bundled.
|
|
36
|
-
// In both packaged and dev: process.cwd() is the dashboard dir,
|
|
37
|
-
// and run-migrations.js is at lib/run-migrations.js relative to it.
|
|
38
|
-
if (request && request.includes('run-migrations')) {
|
|
39
|
-
return callback(null,
|
|
40
|
-
`var require(require('path').join(process.cwd(), 'lib', 'run-migrations'))`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
callback(undefined);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
return config;
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
module.exports = nextConfig;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|