@programinglive/dev-workflow-mcp-server 1.7.10 ā 1.7.11
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.example +11 -0
- package/CHANGELOG.md +17 -0
- package/README.md +43 -2
- package/db/adapters/mysql.js +11 -4
- package/docs/product/PRD.md +5 -1
- package/docs/release-notes/RELEASE_NOTES.md +9 -0
- package/index.js +2 -0
- package/package.json +3 -2
- package/tests/db-adapters.test.js +108 -0
- package/tests/env-support.test.js +46 -0
- package/tests/workflowState.test.js +16 -1
- package/verify-db-mysql.js +92 -0
package/.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Database Configuration
|
|
2
|
+
# Supported types: sqlite, mysql, postgres
|
|
3
|
+
DEV_WORKFLOW_DB_TYPE=sqlite
|
|
4
|
+
|
|
5
|
+
# Connection URLs
|
|
6
|
+
# MySQL Example: mysql://user:password@localhost:3306/database
|
|
7
|
+
# Postgres Example: postgres://user:password@localhost:5432/database
|
|
8
|
+
DEV_WORKFLOW_DB_URL=
|
|
9
|
+
|
|
10
|
+
# Optional: Override user ID
|
|
11
|
+
# DEV_WORKFLOW_USER_ID=my-custom-user-id
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.7.11](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.10...v1.7.11) (2025-12-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### š Documentation
|
|
9
|
+
|
|
10
|
+
* fix version status in PRD ([fa7912b](https://github.com/programinglive/dev-workflow-mcp-server/commit/fa7912ba3354a88ce943fbc9a0f34f4bd1936e34))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⨠Features
|
|
14
|
+
|
|
15
|
+
* implement .env support and fix mysql date persistence ([096797c](https://github.com/programinglive/dev-workflow-mcp-server/commit/096797c689813f44106a027cfa36e90d3da4bfb4))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### ā
Tests
|
|
19
|
+
|
|
20
|
+
* comprehensive DB adapter tests, env support, and warning fixes ([44814ff](https://github.com/programinglive/dev-workflow-mcp-server/commit/44814ffcc0a1efbc8e89948de22c3b196834b390))
|
|
21
|
+
|
|
5
22
|
### [1.7.10](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.9...v1.7.10) (2025-12-16)
|
|
6
23
|
|
|
7
24
|
|
package/README.md
CHANGED
|
@@ -183,9 +183,23 @@ See [Antigravity Getting Started](./docs/antigravity/GETTING-STARTED.md) for det
|
|
|
183
183
|
|
|
184
184
|
## āļø Configuration
|
|
185
185
|
|
|
186
|
-
### Database
|
|
186
|
+
### Database Setup
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
The server supports **SQLite** (default), **MySQL**, and **PostgreSQL**.
|
|
189
|
+
|
|
190
|
+
#### Configuration via `.env`
|
|
191
|
+
1. Copy `.env.example` to `.env`:
|
|
192
|
+
```bash
|
|
193
|
+
cp .env.example .env
|
|
194
|
+
```
|
|
195
|
+
2. Edit `.env` with your settings:
|
|
196
|
+
```ini
|
|
197
|
+
DEV_WORKFLOW_DB_TYPE=mysql
|
|
198
|
+
DEV_WORKFLOW_DB_URL=mysql://user:pass@localhost:3306/db
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Configuration via Environment Variables
|
|
202
|
+
Alternatively, export variables directly:.
|
|
189
203
|
|
|
190
204
|
| Variable | Description | Default |
|
|
191
205
|
|----------|-------------|---------|
|
|
@@ -252,6 +266,33 @@ Add the environment variable in your Windsurf MCP settings:
|
|
|
252
266
|
```
|
|
253
267
|
|
|
254
268
|
|
|
269
|
+
|
|
270
|
+
### ā
Testing
|
|
271
|
+
|
|
272
|
+
The project uses Node.js native test runner (`node --test`).
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
This runs:
|
|
279
|
+
- Unit tests for workflow logic
|
|
280
|
+
- Integration tests for `.env` configuration
|
|
281
|
+
- DB Adapter tests (SQLite always; MySQL/Postgres if configured)
|
|
282
|
+
|
|
283
|
+
#### Testing Database Adapters
|
|
284
|
+
To verify MySQL or PostgreSQL adapters, run tests with the environment variable set:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Test MySQL
|
|
288
|
+
export DEV_WORKFLOW_DB_URL="mysql://root:pass@localhost:3306/test_db"
|
|
289
|
+
node --test tests/db-adapters.test.js
|
|
290
|
+
|
|
291
|
+
# Test PostgreSQL
|
|
292
|
+
export DEV_WORKFLOW_DB_URL="postgres://postgres:pass@localhost:5432/test_db"
|
|
293
|
+
node --test tests/db-adapters.test.js
|
|
294
|
+
```
|
|
295
|
+
|
|
255
296
|
### Scripts
|
|
256
297
|
|
|
257
298
|
- `npm run build` - Bundle the source into `dist/index.mjs` for distribution
|
package/db/adapters/mysql.js
CHANGED
|
@@ -75,6 +75,13 @@ export class MysqlAdapter extends DbAdapter {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
formatDate(date) {
|
|
79
|
+
// Ensure we have a string in 'YYYY-MM-DD HH:mm:ss' format
|
|
80
|
+
const d = date ? new Date(date) : new Date();
|
|
81
|
+
// slice(0, 19) gives YYYY-MM-DDTHH:mm:ss, then replace T with space
|
|
82
|
+
return d.toISOString().slice(0, 19).replace('T', ' ');
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
async insertHistoryEntry(userId, projectPath, entry) {
|
|
79
86
|
await this.connect();
|
|
80
87
|
const normalizedProjectPath = projectPath || '';
|
|
@@ -98,7 +105,7 @@ export class MysqlAdapter extends DbAdapter {
|
|
|
98
105
|
entry.taskDescription,
|
|
99
106
|
entry.taskType,
|
|
100
107
|
entry.commitMessage,
|
|
101
|
-
entry.timestamp
|
|
108
|
+
this.formatDate(entry.timestamp),
|
|
102
109
|
entry.forced ? 1 : 0,
|
|
103
110
|
entry.forceReason || null,
|
|
104
111
|
entry.dropped ? 1 : 0,
|
|
@@ -150,9 +157,9 @@ export class MysqlAdapter extends DbAdapter {
|
|
|
150
157
|
normalizedProjectPath,
|
|
151
158
|
rows.length,
|
|
152
159
|
JSON.stringify(taskTypes),
|
|
153
|
-
lastActive,
|
|
160
|
+
this.formatDate(lastActive),
|
|
154
161
|
JSON.stringify(recentTasks),
|
|
155
|
-
new Date()
|
|
162
|
+
this.formatDate(new Date())
|
|
156
163
|
]);
|
|
157
164
|
}
|
|
158
165
|
|
|
@@ -270,7 +277,7 @@ export class MysqlAdapter extends DbAdapter {
|
|
|
270
277
|
userId,
|
|
271
278
|
normalizedProjectPath,
|
|
272
279
|
JSON.stringify(state),
|
|
273
|
-
new Date()
|
|
280
|
+
this.formatDate(new Date())
|
|
274
281
|
]);
|
|
275
282
|
}
|
|
276
283
|
|
package/docs/product/PRD.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- **Product**: Development Workflow MCP Server
|
|
5
5
|
- **Prepared by**: Cascade (AI assistant)
|
|
6
6
|
- **Date**: 2025-12-16
|
|
7
|
-
- **Status**: Live v1.
|
|
7
|
+
- **Status**: Live v1.7.10
|
|
8
8
|
|
|
9
9
|
## 2. Problem Statement
|
|
10
10
|
Developers need structured guidance to maintain disciplined workflows across projects. Existing tooling lacks enforcement of sequential best practices (testing, documentation, release hygiene) and offers limited visibility into historical task execution.
|
|
@@ -38,6 +38,10 @@ Developers need structured guidance to maintain disciplined workflows across pro
|
|
|
38
38
|
- Guard release commands to ensure prerequisites are satisfied.
|
|
39
39
|
- Support `run_full_workflow` for scripted execution of all steps with validation.
|
|
40
40
|
- Web dashboard displays version dynamically from `package.json` via `/api/version` endpoint to stay in sync after releases.
|
|
41
|
+
- [x] **Consolidated CI/CD**: Unified release and publish workflows.
|
|
42
|
+
- [x] **Multi-Database Support**: SQLite, MySQL, and PostgreSQL adapters implemented and verified.
|
|
43
|
+
- [x] **Environment Configuration**: `.env` file support added.
|
|
44
|
+
- [x] **Comprehensive Testing**: Automated tests for DB adapters and environment loading.
|
|
41
45
|
- **Multi-Database Support**: Abstract the storage layer to support:
|
|
42
46
|
- SQLite (default/local)
|
|
43
47
|
- MySQL
|
|
@@ -4,6 +4,7 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Notes |
|
|
6
6
|
|---------|------|-------|
|
|
7
|
+
| 1.7.10 | 2025-12-16 | update release notes and finalize ci config (92ac78a) |
|
|
7
8
|
| 1.7.9 | 2025-12-16 | **ci:** apply official npm trusted publishing config (9256583) |
|
|
8
9
|
| 1.7.8 | 2025-12-16 | **ci:** remove all token config for pure OIDC publishing (d4759cc) |
|
|
9
10
|
| 1.7.7 | 2025-12-16 | **ci:** remove duplicates and apply robust trusted publishing config (eedb661) |
|
|
@@ -31,6 +32,14 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
|
|
36
|
+
## 1.7.10 ā š§¹ Chores
|
|
37
|
+
|
|
38
|
+
Released on **2025-12-16**.
|
|
39
|
+
|
|
40
|
+
- update release notes and finalize ci config (92ac78a)
|
|
41
|
+
- update PRD and release notes for v1.7.9 (024b3c2)
|
|
42
|
+
|
|
34
43
|
## 1.7.9 ā š Bug Fixes
|
|
35
44
|
|
|
36
45
|
Released on **2025-12-16**.
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dev": "node --watch index.js",
|
|
18
18
|
"start": "node index.js",
|
|
19
19
|
"local": "node index.js",
|
|
20
|
-
"test": "node --test",
|
|
20
|
+
"test": "node --no-deprecation --test",
|
|
21
21
|
"check": "node --check index.js",
|
|
22
22
|
"prepare": "husky",
|
|
23
23
|
"postinstall": "node install.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
45
|
"better-sqlite3": "^11.8.0",
|
|
46
|
+
"dotenv": "^17.2.3",
|
|
46
47
|
"esbuild": "^0.25.0",
|
|
47
48
|
"get-port": "^7.1.0",
|
|
48
49
|
"marked": "^12.0.2",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
|
|
8
|
+
import { SqliteAdapter } from "../db/adapters/sqlite.js";
|
|
9
|
+
import { MysqlAdapter } from "../db/adapters/mysql.js";
|
|
10
|
+
import { PostgresAdapter } from "../db/adapters/postgres.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Helper to run common adapter tests
|
|
14
|
+
async function runGenericAdapterTests(t, adapter, testUserId, testProject) {
|
|
15
|
+
await t.test("connects and migrates", async () => {
|
|
16
|
+
await adapter.connect();
|
|
17
|
+
// No error means success
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await t.test("inserts and retrieves history", async () => {
|
|
21
|
+
const entry = {
|
|
22
|
+
taskDescription: "Test Task",
|
|
23
|
+
taskType: "feature",
|
|
24
|
+
commitMessage: "feat: test",
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
await adapter.insertHistoryEntry(testUserId, testProject, entry);
|
|
28
|
+
|
|
29
|
+
const history = await adapter.getHistoryForUser(testUserId, testProject, { pageSize: 1 });
|
|
30
|
+
assert.equal(history.entries.length, 1);
|
|
31
|
+
assert.equal(history.entries[0].task_description, "Test Task");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await t.test("saves and retrieves state", async () => {
|
|
35
|
+
const state = { phase: "testing", count: 123 };
|
|
36
|
+
await adapter.saveState(testUserId, testProject, state);
|
|
37
|
+
const retrieved = await adapter.getState(testUserId, testProject);
|
|
38
|
+
assert.deepEqual(retrieved, state);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await t.test("history summary aggregation", async () => {
|
|
42
|
+
// already inserted one entry
|
|
43
|
+
const summary = await adapter.getHistorySummary(testUserId, testProject, { frequency: 'daily' });
|
|
44
|
+
assert.ok(summary.length > 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await t.test("project summary update", async () => {
|
|
48
|
+
const summary = await adapter.getSummaryForUser(testUserId, testProject);
|
|
49
|
+
assert.ok(summary);
|
|
50
|
+
assert.ok(summary.totalTasks >= 1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test("Database Adapters", async (t) => {
|
|
55
|
+
// 1. SQLite (Always Test)
|
|
56
|
+
await t.test("SqliteAdapter", async (t) => {
|
|
57
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sqlite-test-"));
|
|
58
|
+
const dbPath = path.join(tmpDir, "test.db");
|
|
59
|
+
|
|
60
|
+
// Convert to file URI for connection string if needed, or just pass path
|
|
61
|
+
// The adapter expects a connection URL for SQLite usually: file:///path/to/db or just path?
|
|
62
|
+
// Looking at sqlite.js: uri: this.config.connectionUrl.replace("sqlite://", "")
|
|
63
|
+
// So we invoke it with sqlite://<path>
|
|
64
|
+
const connectionUrl = `sqlite://${dbPath}`;
|
|
65
|
+
|
|
66
|
+
const adapter = new SqliteAdapter({ connectionUrl });
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await runGenericAdapterTests(t, adapter, "user-sqlite", tmpDir);
|
|
70
|
+
} finally {
|
|
71
|
+
if (adapter.db) adapter.db.close();
|
|
72
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 2. MySQL (Conditional)
|
|
77
|
+
// Check specific test variable OR generic one if it maps to mysql
|
|
78
|
+
const mysqlUrl = process.env.TEST_MYSQL_URL ||
|
|
79
|
+
(process.env.DEV_WORKFLOW_DB_URL && process.env.DEV_WORKFLOW_DB_URL.startsWith("mysql") ? process.env.DEV_WORKFLOW_DB_URL : null);
|
|
80
|
+
|
|
81
|
+
await t.test("MysqlAdapter", { skip: !mysqlUrl }, async (t) => {
|
|
82
|
+
const adapter = new MysqlAdapter({ connectionUrl: mysqlUrl });
|
|
83
|
+
try {
|
|
84
|
+
// We use a random user ID to avoid clashing with verification script data
|
|
85
|
+
// although verification cleaned up mostly.
|
|
86
|
+
const testId = `test-mysql-${Date.now()}`;
|
|
87
|
+
const testProj = process.cwd();
|
|
88
|
+
await runGenericAdapterTests(t, adapter, testId, testProj);
|
|
89
|
+
} finally {
|
|
90
|
+
if (adapter.pool) await adapter.pool.end();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 3. PostgreSQL (Conditional)
|
|
95
|
+
const pgUrl = process.env.TEST_POSTGRES_URL ||
|
|
96
|
+
(process.env.DEV_WORKFLOW_DB_URL && process.env.DEV_WORKFLOW_DB_URL.startsWith("postgres") ? process.env.DEV_WORKFLOW_DB_URL : null);
|
|
97
|
+
|
|
98
|
+
await t.test("PostgresAdapter", { skip: !pgUrl }, async (t) => {
|
|
99
|
+
const adapter = new PostgresAdapter({ connectionUrl: pgUrl });
|
|
100
|
+
try {
|
|
101
|
+
const testId = `test-pg-${Date.now()}`;
|
|
102
|
+
const testProj = process.cwd();
|
|
103
|
+
await runGenericAdapterTests(t, adapter, testId, testProj);
|
|
104
|
+
} finally {
|
|
105
|
+
if (adapter.pool) await adapter.pool.end();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { exec } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
test("Environment Configuration", async (t) => {
|
|
13
|
+
await t.test("loads variables from .env file", async () => {
|
|
14
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "env-test-"));
|
|
15
|
+
const originalCwd = process.cwd();
|
|
16
|
+
const indexJsPath = path.resolve(originalCwd, "index.js");
|
|
17
|
+
const indexUrl = pathToFileURL(indexJsPath).href;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Create a .env file
|
|
21
|
+
await fs.writeFile(path.join(tmpDir, ".env"), "TEST_ENV_VAR=loaded_successfully");
|
|
22
|
+
|
|
23
|
+
// Verify node can run and load it via index.js import
|
|
24
|
+
// We import index.js to trigger the 'dotenv/config' import.
|
|
25
|
+
// Since index.js starts a server if run directly, we just import it.
|
|
26
|
+
// However, we need to run a script in the NEW CWD.
|
|
27
|
+
|
|
28
|
+
const script = `
|
|
29
|
+
import '${indexUrl}';
|
|
30
|
+
console.log("VAR:" + process.env.TEST_ENV_VAR);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const scriptPath = path.join(tmpDir, "check_env.js");
|
|
35
|
+
await fs.writeFile(scriptPath, script);
|
|
36
|
+
|
|
37
|
+
// Execute in tmpDir
|
|
38
|
+
const { stdout } = await execAsync(`node check_env.js`, { cwd: tmpDir });
|
|
39
|
+
|
|
40
|
+
assert.ok(stdout.includes("VAR:loaded_successfully"), "Output should contain loaded variable");
|
|
41
|
+
|
|
42
|
+
} finally {
|
|
43
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -14,6 +14,13 @@ import { constants as fsConstants } from "node:fs";
|
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
|
|
18
|
+
// Isolate these tests from the real database configuration
|
|
19
|
+
delete process.env.DEV_WORKFLOW_DB_TYPE;
|
|
20
|
+
delete process.env.DEV_WORKFLOW_DB_URL;
|
|
21
|
+
delete process.env.TEST_MYSQL_URL;
|
|
22
|
+
delete process.env.TEST_POSTGRES_URL;
|
|
23
|
+
|
|
17
24
|
import {
|
|
18
25
|
WorkflowState,
|
|
19
26
|
getNextStep,
|
|
@@ -50,7 +57,9 @@ test("resolveStateFile prefers current working directory when multiple project m
|
|
|
50
57
|
const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
|
|
51
58
|
await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
|
|
52
59
|
|
|
53
|
-
await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module" }));
|
|
60
|
+
await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
|
|
61
|
+
await mkdir(path.join(moduleDir, "db"), { recursive: true });
|
|
62
|
+
await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
|
|
54
63
|
await writeFile(path.join(projectA, "package.json"), JSON.stringify({ name: "project-a" }));
|
|
55
64
|
await writeFile(path.join(projectB, "package.json"), JSON.stringify({ name: "project-b" }));
|
|
56
65
|
|
|
@@ -101,6 +110,9 @@ test("WorkflowState mirrors state file to compatibility locations", async () =>
|
|
|
101
110
|
await mkdir(path.join(tempRoot, "dist"), { recursive: true });
|
|
102
111
|
await mkdir(stateDir, { recursive: true });
|
|
103
112
|
await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
|
|
113
|
+
await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
|
|
114
|
+
await mkdir(path.join(moduleDir, "db"), { recursive: true });
|
|
115
|
+
await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
|
|
104
116
|
await writeFile(path.join(projectRoot, "package.json"), JSON.stringify({ name: "temp" }));
|
|
105
117
|
|
|
106
118
|
const stateFile = path.join(stateDir, "workflow-state.json");
|
|
@@ -129,6 +141,9 @@ test("WorkflowState avoids filesystem root when no project markers found", async
|
|
|
129
141
|
|
|
130
142
|
const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
|
|
131
143
|
await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
|
|
144
|
+
await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module", type: "module" }));
|
|
145
|
+
await mkdir(path.join(moduleDir, "db"), { recursive: true });
|
|
146
|
+
await writeFile(path.join(moduleDir, "db", "index.js"), 'export function getDbAdapter() { return { connect: async () => {}, saveState: async () => {}, getState: async () => null }; } export async function insertHistoryEntry() {} export async function updateSummaryForUser() {} export async function saveState() {} export async function getState() {} export async function getSummaryForUser() { return { totalTasks: 0, taskTypes: {} }; } export async function getHistoryForUser() { return { entries: [], total: 0 }; }');
|
|
132
147
|
|
|
133
148
|
const moduleUrl = pathToFileURL(path.join(moduleDir, "workflow-state.js"));
|
|
134
149
|
const { WorkflowState: TempWorkflowState } = await import(moduleUrl.href);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
|
|
2
|
+
import { MysqlAdapter } from "./db/adapters/mysql.js";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
|
|
5
|
+
const CONNECTION_URL = process.env.DEV_WORKFLOW_DB_URL;
|
|
6
|
+
|
|
7
|
+
if (!CONNECTION_URL) {
|
|
8
|
+
console.error("ā Error: DEV_WORKFLOW_DB_URL environment variable is required.");
|
|
9
|
+
console.error("Usage: DEV_WORKFLOW_DB_URL='mysql://user:password@host:port/database' node verify-db-mysql.js");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TEST_USER = "test-verification-user";
|
|
14
|
+
const TEST_PROJECT = resolve(process.cwd());
|
|
15
|
+
|
|
16
|
+
async function runVerification() {
|
|
17
|
+
console.log("š Starting MySQL Verification...");
|
|
18
|
+
console.log(` User ID: ${TEST_USER}`);
|
|
19
|
+
console.log(` Project: ${TEST_PROJECT}`);
|
|
20
|
+
console.log(` URL: ${CONNECTION_URL.replace(/:[^:@]+@/, ":****@")}`);
|
|
21
|
+
|
|
22
|
+
const adapter = new MysqlAdapter({
|
|
23
|
+
connectionUrl: CONNECTION_URL
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// 1. Connect & Migrate
|
|
28
|
+
console.log("\n1. Connecting and Migrating...");
|
|
29
|
+
await adapter.connect();
|
|
30
|
+
console.log(" ā
Connected to MySQL");
|
|
31
|
+
|
|
32
|
+
// 2. Clear previous test data (optional, but good for cleanliness)
|
|
33
|
+
// We won't strictly clear to avoid wiping valid data if user reuses IDs,
|
|
34
|
+
// but the test user should be unique enough.
|
|
35
|
+
|
|
36
|
+
// 3. Insert History
|
|
37
|
+
console.log("\n3. Testing History Insertion...");
|
|
38
|
+
const entry = {
|
|
39
|
+
taskDescription: "Verification Task",
|
|
40
|
+
taskType: "test",
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
commitMessage: "test commit"
|
|
43
|
+
};
|
|
44
|
+
await adapter.insertHistoryEntry(TEST_USER, TEST_PROJECT, entry);
|
|
45
|
+
console.log(" ā
Inserted history entry");
|
|
46
|
+
|
|
47
|
+
// 4. Verify History Retrieval
|
|
48
|
+
console.log("\n4. Verifying History Retrieval...");
|
|
49
|
+
const history = await adapter.getHistoryForUser(TEST_USER, TEST_PROJECT, { pageSize: 1 });
|
|
50
|
+
const savedEntry = history.entries[0];
|
|
51
|
+
|
|
52
|
+
if (savedEntry && savedEntry.task_description === entry.taskDescription) {
|
|
53
|
+
console.log(" ā
Retrieved history entry matches");
|
|
54
|
+
} else {
|
|
55
|
+
console.error(" ā Retrieved entry mismatch:", savedEntry);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 5. Test State Persistence
|
|
60
|
+
console.log("\n5. Testing State Persistence...");
|
|
61
|
+
const state = {
|
|
62
|
+
currentPhase: "verification",
|
|
63
|
+
verified: true,
|
|
64
|
+
random: Math.random()
|
|
65
|
+
};
|
|
66
|
+
await adapter.saveState(TEST_USER, TEST_PROJECT, state);
|
|
67
|
+
console.log(" ā
Saved state");
|
|
68
|
+
|
|
69
|
+
// 6. Verify State Retrieval
|
|
70
|
+
console.log("\n6. Verifying State Retrieval...");
|
|
71
|
+
const retrievedState = await adapter.getState(TEST_USER, TEST_PROJECT);
|
|
72
|
+
if (retrievedState && retrievedState.random === state.random) {
|
|
73
|
+
console.log(" ā
Retrieved state matches");
|
|
74
|
+
} else {
|
|
75
|
+
console.error(" ā Retrieved state mismatch:", retrievedState);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log("\nš MySQL Verification SUCCESS!");
|
|
80
|
+
process.exit(0);
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("\nā Verification FAILED:", error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
} finally {
|
|
86
|
+
if (adapter.pool) {
|
|
87
|
+
await adapter.pool.end();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
runVerification();
|