@programinglive/dev-workflow-mcp-server 1.5.0 → 1.5.2
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/.agent/workflows/dev-workflow-bugfix.md +250 -250
- package/.agent/workflows/dev-workflow-feature.md +201 -201
- package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
- package/.agent/workflows/dev-workflow-refactor.md +238 -238
- package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
- package/.github/pull_request_template.md +22 -22
- package/.github/workflows/npm-publish.yml +50 -47
- package/.husky/commit-msg +0 -0
- package/CHANGELOG.md +750 -491
- package/CODE_OF_CONDUCT.md +79 -79
- package/CONTRIBUTING.md +60 -60
- package/LICENSE +21 -21
- package/README.md +632 -609
- package/SECURITY.md +20 -20
- package/db/index.js +204 -204
- package/docs/NETLIFY_DEPLOYMENT.md +125 -0
- package/docs/PLESK_DEPLOYMENT.md +170 -0
- package/docs/WORKFLOW-BEST-PRACTICES.md +58 -0
- package/docs/antigravity/EXAMPLES.md +535 -535
- package/docs/antigravity/GETTING-STARTED.md +292 -292
- package/docs/antigravity/TROUBLESHOOTING.md +742 -742
- package/docs/antigravity/WALKTHROUGH.md +25 -25
- package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
- package/docs/product/PRD.md +67 -64
- package/docs/release-notes/RELEASE_NOTES.md +226 -132
- package/docs/web-dashboard.md +175 -163
- package/exec.js +49 -49
- package/index.js +279 -279
- package/install.js +73 -73
- package/package.json +11 -9
- package/release-wrapper.js +192 -157
- package/scripts/build-docs.js +63 -63
- package/scripts/hello-world.js +1 -1
- package/scripts/release.cjs +252 -252
- package/scripts/update-release-notes.cjs +178 -182
- package/server.json +23 -0
- package/tailwind.config.js +8 -10
- package/tests/build.test.js +83 -48
- package/tests/exec.test.js +35 -35
- package/tests/handlers.test.js +1058 -980
- package/tests/hello-world.test.js +13 -13
- package/tests/mcpRegistry.test.js +65 -0
- package/tests/release-wrapper.test.js +107 -105
- package/tests/workflowState.test.js +505 -502
- package/tools/definitions.js +332 -332
- package/tools/handlers.js +1019 -1019
- package/vite.config.js +43 -43
- package/vite.web.config.js +23 -0
- package/web/README.md +36 -0
- package/web/app/docs/page.tsx +202 -0
- package/web/app/favicon.ico +0 -0
- package/web/app/globals.css +91 -0
- package/web/app/layout.tsx +36 -0
- package/web/app/page.tsx +15 -0
- package/web/components/ApiReference.tsx +20 -0
- package/web/components/Features.tsx +61 -0
- package/web/components/Footer.tsx +9 -0
- package/web/components/Hero.tsx +96 -0
- package/web/components/History.tsx +143 -0
- package/web/components/Navbar.tsx +64 -0
- package/web/components/Terminal.tsx +114 -0
- package/web/eslint.config.mjs +18 -0
- package/web/lib/db.js +131 -0
- package/web/lib/docs.js +73 -0
- package/web/netlify.toml +3 -0
- package/web/next.config.ts +12 -0
- package/web/package-lock.json +6560 -0
- package/web/package.json +28 -0
- package/web/postcss.config.mjs +9 -0
- package/web/public/assets/android-chrome-192x192.png +0 -0
- package/web/public/assets/android-chrome-512x512.png +0 -0
- package/web/public/assets/apple-touch-icon.png +0 -0
- package/web/public/assets/favicon-16x16.png +0 -0
- package/web/public/assets/favicon-32x32.png +0 -0
- package/web/public/assets/favicon.ico +0 -0
- package/web/public/assets/favicon.png +0 -0
- package/web/public/assets/logo.png +0 -0
- package/web/public/assets/og-preview.png +0 -0
- package/web/public/assets/site.webmanifest +1 -0
- package/web/public/file.svg +1 -0
- package/web/public/globe.svg +1 -0
- package/web/public/next.svg +1 -0
- package/web/public/vercel.svg +1 -0
- package/web/public/window.svg +1 -0
- package/web/server.js +31 -159
- package/web/tailwind.config.ts +19 -0
- package/web/tsconfig.json +42 -0
- package/workflow-state.js +612 -626
- package/dist/docs/EXAMPLES.html +0 -411
- package/dist/docs/GETTING-STARTED.html +0 -233
- package/dist/docs/README.html +0 -535
- package/dist/docs/RELEASE_NOTES.html +0 -167
- package/dist/docs/TROUBLESHOOTING.html +0 -542
- package/dist/docs/WORKFLOW-TEMPLATES.html +0 -591
- package/dist/docs/web-dashboard.html +0 -158
- package/web/public/index.html +0 -1046
- package/web/public/input.css +0 -3
- package/web/public/output.css +0 -1
package/SECURITY.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Supported Versions
|
|
4
|
-
|
|
5
|
-
We release patches for security vulnerabilities under new minor or patch versions. Always use the latest release.
|
|
6
|
-
|
|
7
|
-
## Reporting a Vulnerability
|
|
8
|
-
|
|
9
|
-
If you discover a vulnerability, please email `security@programinglive.com` with:
|
|
10
|
-
|
|
11
|
-
- Description of the issue
|
|
12
|
-
- Steps to reproduce
|
|
13
|
-
- Potential impact
|
|
14
|
-
- Suggested remediation (if known)
|
|
15
|
-
|
|
16
|
-
We aim to acknowledge reports within 72 hours and will keep you informed as we work on a fix. Please do not disclose the vulnerability publicly until we have released an update.
|
|
17
|
-
|
|
18
|
-
## Disclosure Policy
|
|
19
|
-
|
|
20
|
-
Once a fix is available, we will publish a security advisory in the repository releases and notify contributors via the README.
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
We release patches for security vulnerabilities under new minor or patch versions. Always use the latest release.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
If you discover a vulnerability, please email `security@programinglive.com` with:
|
|
10
|
+
|
|
11
|
+
- Description of the issue
|
|
12
|
+
- Steps to reproduce
|
|
13
|
+
- Potential impact
|
|
14
|
+
- Suggested remediation (if known)
|
|
15
|
+
|
|
16
|
+
We aim to acknowledge reports within 72 hours and will keep you informed as we work on a fix. Please do not disclose the vulnerability publicly until we have released an update.
|
|
17
|
+
|
|
18
|
+
## Disclosure Policy
|
|
19
|
+
|
|
20
|
+
Once a fix is available, we will publish a security advisory in the repository releases and notify contributors via the README.
|
package/db/index.js
CHANGED
|
@@ -1,204 +1,204 @@
|
|
|
1
|
-
import Database from "better-sqlite3";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
const DB_FILENAME = "dev-workflow.db";
|
|
9
|
-
const FREQUENCY_FORMATS = {
|
|
10
|
-
daily: "%Y-%m-%d",
|
|
11
|
-
monthly: "%Y-%m",
|
|
12
|
-
yearly: "%Y",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function getDbPath() {
|
|
16
|
-
const initCwd = process.env.INIT_CWD || process.cwd();
|
|
17
|
-
let projectRoot = initCwd;
|
|
18
|
-
while (projectRoot !== path.dirname(projectRoot)) {
|
|
19
|
-
if (existsSync(path.join(projectRoot, "package.json"))) break;
|
|
20
|
-
projectRoot = path.dirname(projectRoot);
|
|
21
|
-
}
|
|
22
|
-
return path.join(projectRoot, ".state", DB_FILENAME);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let db;
|
|
26
|
-
export function getDb() {
|
|
27
|
-
if (!db) {
|
|
28
|
-
const dbPath = getDbPath();
|
|
29
|
-
db = new Database(dbPath);
|
|
30
|
-
db.pragma("journal_mode = WAL");
|
|
31
|
-
migrate();
|
|
32
|
-
}
|
|
33
|
-
return db;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function migrate() {
|
|
37
|
-
const createHistory = `
|
|
38
|
-
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
39
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
-
user_id TEXT NOT NULL,
|
|
41
|
-
task_description TEXT NOT NULL,
|
|
42
|
-
task_type TEXT NOT NULL,
|
|
43
|
-
commit_message TEXT,
|
|
44
|
-
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
-
forced INTEGER DEFAULT 0,
|
|
46
|
-
force_reason TEXT,
|
|
47
|
-
dropped INTEGER DEFAULT 0,
|
|
48
|
-
drop_reason TEXT,
|
|
49
|
-
UNIQUE(user_id, timestamp, task_description)
|
|
50
|
-
)
|
|
51
|
-
`;
|
|
52
|
-
const createSummary = `
|
|
53
|
-
CREATE TABLE IF NOT EXISTS project_summary (
|
|
54
|
-
user_id TEXT PRIMARY KEY,
|
|
55
|
-
total_tasks INTEGER DEFAULT 0,
|
|
56
|
-
task_types TEXT DEFAULT '{}',
|
|
57
|
-
last_active DATETIME,
|
|
58
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
59
|
-
recent_tasks TEXT DEFAULT '[]'
|
|
60
|
-
)
|
|
61
|
-
`;
|
|
62
|
-
const idxHistory = `CREATE INDEX IF NOT EXISTS idx_workflow_history_user ON workflow_history(user_id)`;
|
|
63
|
-
const idxSummary = `CREATE INDEX IF NOT EXISTS idx_project_summary_user ON project_summary(user_id)`;
|
|
64
|
-
|
|
65
|
-
db.exec(createHistory);
|
|
66
|
-
db.exec(createSummary);
|
|
67
|
-
db.exec(idxHistory);
|
|
68
|
-
db.exec(idxSummary);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function insertHistoryEntry(userId, entry) {
|
|
72
|
-
const stmt = getDb().prepare(`
|
|
73
|
-
INSERT OR REPLACE INTO workflow_history
|
|
74
|
-
(user_id, task_description, task_type, commit_message, timestamp, forced, force_reason, dropped, drop_reason)
|
|
75
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
-
`);
|
|
77
|
-
stmt.run(
|
|
78
|
-
userId,
|
|
79
|
-
entry.taskDescription,
|
|
80
|
-
entry.taskType,
|
|
81
|
-
entry.commitMessage,
|
|
82
|
-
entry.timestamp || new Date().toISOString(),
|
|
83
|
-
entry.forced ? 1 : 0,
|
|
84
|
-
entry.forceReason || null,
|
|
85
|
-
entry.dropped ? 1 : 0,
|
|
86
|
-
entry.dropReason || null
|
|
87
|
-
);
|
|
88
|
-
updateSummaryForUser(userId);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function updateSummaryForUser(userId) {
|
|
92
|
-
const getHistory = getDb().prepare(`
|
|
93
|
-
SELECT task_type, timestamp, task_description
|
|
94
|
-
FROM workflow_history
|
|
95
|
-
WHERE user_id = ?
|
|
96
|
-
ORDER BY timestamp DESC
|
|
97
|
-
LIMIT 20
|
|
98
|
-
`);
|
|
99
|
-
const rows = getHistory.all(userId);
|
|
100
|
-
|
|
101
|
-
const taskTypes = {};
|
|
102
|
-
for (const row of rows) {
|
|
103
|
-
taskTypes[row.task_type] = (taskTypes[row.task_type] || 0) + 1;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const recentTasks = rows.slice(0, 5).map(r => ({
|
|
107
|
-
description: r.task_description,
|
|
108
|
-
type: r.task_type,
|
|
109
|
-
timestamp: r.timestamp,
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
const lastActive = rows[0]?.timestamp || null;
|
|
113
|
-
|
|
114
|
-
const upsert = getDb().prepare(`
|
|
115
|
-
INSERT OR REPLACE INTO project_summary
|
|
116
|
-
(user_id, total_tasks, task_types, last_active, recent_tasks, updated_at)
|
|
117
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
118
|
-
`);
|
|
119
|
-
upsert.run(
|
|
120
|
-
userId,
|
|
121
|
-
rows.length,
|
|
122
|
-
JSON.stringify(taskTypes),
|
|
123
|
-
lastActive,
|
|
124
|
-
JSON.stringify(recentTasks),
|
|
125
|
-
new Date().toISOString()
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function getSummaryForUser(userId) {
|
|
130
|
-
const stmt = getDb().prepare(`
|
|
131
|
-
SELECT * FROM project_summary WHERE user_id = ?
|
|
132
|
-
`);
|
|
133
|
-
const row = stmt.get(userId);
|
|
134
|
-
if (!row) return null;
|
|
135
|
-
return {
|
|
136
|
-
totalTasks: row.total_tasks,
|
|
137
|
-
taskTypes: JSON.parse(row.task_types),
|
|
138
|
-
lastActive: row.last_active,
|
|
139
|
-
recentTasks: JSON.parse(row.recent_tasks),
|
|
140
|
-
updatedAt: row.updated_at,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function buildHistoryFilters({ startDate, endDate }) {
|
|
145
|
-
const clauses = [];
|
|
146
|
-
const params = [];
|
|
147
|
-
if (startDate) {
|
|
148
|
-
clauses.push("DATE(timestamp) >= DATE(?)");
|
|
149
|
-
params.push(startDate);
|
|
150
|
-
}
|
|
151
|
-
if (endDate) {
|
|
152
|
-
clauses.push("DATE(timestamp) <= DATE(?)");
|
|
153
|
-
params.push(endDate);
|
|
154
|
-
}
|
|
155
|
-
return { clauses, params };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function getHistoryForUser(userId, options = {}) {
|
|
159
|
-
const { page = 1, pageSize = 20, startDate, endDate } = options;
|
|
160
|
-
const sanitizedPage = Math.max(1, Number.isFinite(Number(page)) ? Math.floor(Number(page)) : 1);
|
|
161
|
-
const sanitizedPageSize = Math.min(100, Math.max(1, Number.isFinite(Number(pageSize)) ? Math.floor(Number(pageSize)) : 20));
|
|
162
|
-
const offset = (sanitizedPage - 1) * sanitizedPageSize;
|
|
163
|
-
|
|
164
|
-
const { clauses, params } = buildHistoryFilters({ startDate, endDate });
|
|
165
|
-
const where = ["user_id = ?", ...clauses].join(" AND ");
|
|
166
|
-
const dbInstance = getDb();
|
|
167
|
-
|
|
168
|
-
const countStmt = dbInstance.prepare(`SELECT COUNT(*) as total FROM workflow_history WHERE ${where}`);
|
|
169
|
-
const total = countStmt.get(userId, ...params).total;
|
|
170
|
-
|
|
171
|
-
const rowsStmt = dbInstance.prepare(`
|
|
172
|
-
SELECT *
|
|
173
|
-
FROM workflow_history
|
|
174
|
-
WHERE ${where}
|
|
175
|
-
ORDER BY timestamp DESC
|
|
176
|
-
LIMIT ?
|
|
177
|
-
OFFSET ?
|
|
178
|
-
`);
|
|
179
|
-
const entries = rowsStmt.all(userId, ...params, sanitizedPageSize, offset);
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
entries,
|
|
183
|
-
total,
|
|
184
|
-
page: sanitizedPage,
|
|
185
|
-
pageSize: sanitizedPageSize,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function getHistorySummary(userId, options = {}) {
|
|
190
|
-
const { startDate, endDate, frequency = "daily" } = options;
|
|
191
|
-
const format = FREQUENCY_FORMATS[frequency] || FREQUENCY_FORMATS.daily;
|
|
192
|
-
const { clauses, params } = buildHistoryFilters({ startDate, endDate });
|
|
193
|
-
const where = ["user_id = ?", ...clauses].join(" AND ");
|
|
194
|
-
const stmt = getDb().prepare(`
|
|
195
|
-
SELECT strftime('${format}', timestamp) AS period, COUNT(*) AS count
|
|
196
|
-
FROM workflow_history
|
|
197
|
-
WHERE ${where}
|
|
198
|
-
GROUP BY period
|
|
199
|
-
ORDER BY period DESC
|
|
200
|
-
`);
|
|
201
|
-
return stmt
|
|
202
|
-
.all(userId, ...params)
|
|
203
|
-
.map((row) => ({ period: row.period, count: row.count }));
|
|
204
|
-
}
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const DB_FILENAME = "dev-workflow.db";
|
|
9
|
+
const FREQUENCY_FORMATS = {
|
|
10
|
+
daily: "%Y-%m-%d",
|
|
11
|
+
monthly: "%Y-%m",
|
|
12
|
+
yearly: "%Y",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getDbPath() {
|
|
16
|
+
const initCwd = process.env.INIT_CWD || process.cwd();
|
|
17
|
+
let projectRoot = initCwd;
|
|
18
|
+
while (projectRoot !== path.dirname(projectRoot)) {
|
|
19
|
+
if (existsSync(path.join(projectRoot, "package.json"))) break;
|
|
20
|
+
projectRoot = path.dirname(projectRoot);
|
|
21
|
+
}
|
|
22
|
+
return path.join(projectRoot, ".state", DB_FILENAME);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let db;
|
|
26
|
+
export function getDb() {
|
|
27
|
+
if (!db) {
|
|
28
|
+
const dbPath = getDbPath();
|
|
29
|
+
db = new Database(dbPath);
|
|
30
|
+
db.pragma("journal_mode = WAL");
|
|
31
|
+
migrate();
|
|
32
|
+
}
|
|
33
|
+
return db;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function migrate() {
|
|
37
|
+
const createHistory = `
|
|
38
|
+
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
user_id TEXT NOT NULL,
|
|
41
|
+
task_description TEXT NOT NULL,
|
|
42
|
+
task_type TEXT NOT NULL,
|
|
43
|
+
commit_message TEXT,
|
|
44
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
+
forced INTEGER DEFAULT 0,
|
|
46
|
+
force_reason TEXT,
|
|
47
|
+
dropped INTEGER DEFAULT 0,
|
|
48
|
+
drop_reason TEXT,
|
|
49
|
+
UNIQUE(user_id, timestamp, task_description)
|
|
50
|
+
)
|
|
51
|
+
`;
|
|
52
|
+
const createSummary = `
|
|
53
|
+
CREATE TABLE IF NOT EXISTS project_summary (
|
|
54
|
+
user_id TEXT PRIMARY KEY,
|
|
55
|
+
total_tasks INTEGER DEFAULT 0,
|
|
56
|
+
task_types TEXT DEFAULT '{}',
|
|
57
|
+
last_active DATETIME,
|
|
58
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
59
|
+
recent_tasks TEXT DEFAULT '[]'
|
|
60
|
+
)
|
|
61
|
+
`;
|
|
62
|
+
const idxHistory = `CREATE INDEX IF NOT EXISTS idx_workflow_history_user ON workflow_history(user_id)`;
|
|
63
|
+
const idxSummary = `CREATE INDEX IF NOT EXISTS idx_project_summary_user ON project_summary(user_id)`;
|
|
64
|
+
|
|
65
|
+
db.exec(createHistory);
|
|
66
|
+
db.exec(createSummary);
|
|
67
|
+
db.exec(idxHistory);
|
|
68
|
+
db.exec(idxSummary);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function insertHistoryEntry(userId, entry) {
|
|
72
|
+
const stmt = getDb().prepare(`
|
|
73
|
+
INSERT OR REPLACE INTO workflow_history
|
|
74
|
+
(user_id, task_description, task_type, commit_message, timestamp, forced, force_reason, dropped, drop_reason)
|
|
75
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
+
`);
|
|
77
|
+
stmt.run(
|
|
78
|
+
userId,
|
|
79
|
+
entry.taskDescription,
|
|
80
|
+
entry.taskType,
|
|
81
|
+
entry.commitMessage,
|
|
82
|
+
entry.timestamp || new Date().toISOString(),
|
|
83
|
+
entry.forced ? 1 : 0,
|
|
84
|
+
entry.forceReason || null,
|
|
85
|
+
entry.dropped ? 1 : 0,
|
|
86
|
+
entry.dropReason || null
|
|
87
|
+
);
|
|
88
|
+
updateSummaryForUser(userId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function updateSummaryForUser(userId) {
|
|
92
|
+
const getHistory = getDb().prepare(`
|
|
93
|
+
SELECT task_type, timestamp, task_description
|
|
94
|
+
FROM workflow_history
|
|
95
|
+
WHERE user_id = ?
|
|
96
|
+
ORDER BY timestamp DESC
|
|
97
|
+
LIMIT 20
|
|
98
|
+
`);
|
|
99
|
+
const rows = getHistory.all(userId);
|
|
100
|
+
|
|
101
|
+
const taskTypes = {};
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
taskTypes[row.task_type] = (taskTypes[row.task_type] || 0) + 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const recentTasks = rows.slice(0, 5).map(r => ({
|
|
107
|
+
description: r.task_description,
|
|
108
|
+
type: r.task_type,
|
|
109
|
+
timestamp: r.timestamp,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
const lastActive = rows[0]?.timestamp || null;
|
|
113
|
+
|
|
114
|
+
const upsert = getDb().prepare(`
|
|
115
|
+
INSERT OR REPLACE INTO project_summary
|
|
116
|
+
(user_id, total_tasks, task_types, last_active, recent_tasks, updated_at)
|
|
117
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
118
|
+
`);
|
|
119
|
+
upsert.run(
|
|
120
|
+
userId,
|
|
121
|
+
rows.length,
|
|
122
|
+
JSON.stringify(taskTypes),
|
|
123
|
+
lastActive,
|
|
124
|
+
JSON.stringify(recentTasks),
|
|
125
|
+
new Date().toISOString()
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getSummaryForUser(userId) {
|
|
130
|
+
const stmt = getDb().prepare(`
|
|
131
|
+
SELECT * FROM project_summary WHERE user_id = ?
|
|
132
|
+
`);
|
|
133
|
+
const row = stmt.get(userId);
|
|
134
|
+
if (!row) return null;
|
|
135
|
+
return {
|
|
136
|
+
totalTasks: row.total_tasks,
|
|
137
|
+
taskTypes: JSON.parse(row.task_types),
|
|
138
|
+
lastActive: row.last_active,
|
|
139
|
+
recentTasks: JSON.parse(row.recent_tasks),
|
|
140
|
+
updatedAt: row.updated_at,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildHistoryFilters({ startDate, endDate }) {
|
|
145
|
+
const clauses = [];
|
|
146
|
+
const params = [];
|
|
147
|
+
if (startDate) {
|
|
148
|
+
clauses.push("DATE(timestamp) >= DATE(?)");
|
|
149
|
+
params.push(startDate);
|
|
150
|
+
}
|
|
151
|
+
if (endDate) {
|
|
152
|
+
clauses.push("DATE(timestamp) <= DATE(?)");
|
|
153
|
+
params.push(endDate);
|
|
154
|
+
}
|
|
155
|
+
return { clauses, params };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getHistoryForUser(userId, options = {}) {
|
|
159
|
+
const { page = 1, pageSize = 20, startDate, endDate } = options;
|
|
160
|
+
const sanitizedPage = Math.max(1, Number.isFinite(Number(page)) ? Math.floor(Number(page)) : 1);
|
|
161
|
+
const sanitizedPageSize = Math.min(100, Math.max(1, Number.isFinite(Number(pageSize)) ? Math.floor(Number(pageSize)) : 20));
|
|
162
|
+
const offset = (sanitizedPage - 1) * sanitizedPageSize;
|
|
163
|
+
|
|
164
|
+
const { clauses, params } = buildHistoryFilters({ startDate, endDate });
|
|
165
|
+
const where = ["user_id = ?", ...clauses].join(" AND ");
|
|
166
|
+
const dbInstance = getDb();
|
|
167
|
+
|
|
168
|
+
const countStmt = dbInstance.prepare(`SELECT COUNT(*) as total FROM workflow_history WHERE ${where}`);
|
|
169
|
+
const total = countStmt.get(userId, ...params).total;
|
|
170
|
+
|
|
171
|
+
const rowsStmt = dbInstance.prepare(`
|
|
172
|
+
SELECT *
|
|
173
|
+
FROM workflow_history
|
|
174
|
+
WHERE ${where}
|
|
175
|
+
ORDER BY timestamp DESC
|
|
176
|
+
LIMIT ?
|
|
177
|
+
OFFSET ?
|
|
178
|
+
`);
|
|
179
|
+
const entries = rowsStmt.all(userId, ...params, sanitizedPageSize, offset);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
entries,
|
|
183
|
+
total,
|
|
184
|
+
page: sanitizedPage,
|
|
185
|
+
pageSize: sanitizedPageSize,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getHistorySummary(userId, options = {}) {
|
|
190
|
+
const { startDate, endDate, frequency = "daily" } = options;
|
|
191
|
+
const format = FREQUENCY_FORMATS[frequency] || FREQUENCY_FORMATS.daily;
|
|
192
|
+
const { clauses, params } = buildHistoryFilters({ startDate, endDate });
|
|
193
|
+
const where = ["user_id = ?", ...clauses].join(" AND ");
|
|
194
|
+
const stmt = getDb().prepare(`
|
|
195
|
+
SELECT strftime('${format}', timestamp) AS period, COUNT(*) AS count
|
|
196
|
+
FROM workflow_history
|
|
197
|
+
WHERE ${where}
|
|
198
|
+
GROUP BY period
|
|
199
|
+
ORDER BY period DESC
|
|
200
|
+
`);
|
|
201
|
+
return stmt
|
|
202
|
+
.all(userId, ...params)
|
|
203
|
+
.map((row) => ({ period: row.period, count: row.count }));
|
|
204
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Netlify Deployment Guide
|
|
2
|
+
|
|
3
|
+
Deploy the Dev Workflow web dashboard to Netlify for a public-facing documentation and demo site.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The web dashboard can be deployed to Netlify as a static Next.js application. This deployment mode provides:
|
|
8
|
+
|
|
9
|
+
- **Static documentation pages** - Full access to the `/docs` section
|
|
10
|
+
- **Demo interface** - Showcase the workflow features
|
|
11
|
+
- **API endpoints** - Serverless functions for version and npm download stats
|
|
12
|
+
|
|
13
|
+
> **Note:** Database features (history, summary) return placeholder data in this deployment mode. For full functionality with persistent data, use the [local development server](./web-dashboard.md) or [Plesk deployment](./PLESK_DEPLOYMENT.md).
|
|
14
|
+
|
|
15
|
+
## Live Site
|
|
16
|
+
|
|
17
|
+
The official deployment is available at:
|
|
18
|
+
- **Primary:** https://devworkflow.programinglive.com
|
|
19
|
+
- **Netlify:** https://devworkflow.netlify.app
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- Netlify account (free tier works)
|
|
24
|
+
- Node.js 18+ for local builds
|
|
25
|
+
- Git repository connected to Netlify (optional, for auto-deploy)
|
|
26
|
+
|
|
27
|
+
## Quick Deploy
|
|
28
|
+
|
|
29
|
+
### Option 1: Netlify CLI
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install Netlify CLI
|
|
33
|
+
npm install -g netlify-cli
|
|
34
|
+
|
|
35
|
+
# Login to Netlify
|
|
36
|
+
netlify login
|
|
37
|
+
|
|
38
|
+
# Navigate to web directory
|
|
39
|
+
cd web
|
|
40
|
+
|
|
41
|
+
# Create new site and deploy
|
|
42
|
+
netlify sites:create --name your-site-name
|
|
43
|
+
netlify deploy --prod --build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Option 2: Netlify Dashboard
|
|
47
|
+
|
|
48
|
+
1. Go to [app.netlify.com](https://app.netlify.com)
|
|
49
|
+
2. Click "Add new site" > "Import an existing project"
|
|
50
|
+
3. Connect your GitHub repository
|
|
51
|
+
4. Configure build settings:
|
|
52
|
+
- **Base directory:** `web`
|
|
53
|
+
- **Build command:** `npm run build`
|
|
54
|
+
- **Publish directory:** `.next`
|
|
55
|
+
5. Deploy
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
The `web/netlify.toml` file configures the build:
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
[build]
|
|
63
|
+
command = "npm run build"
|
|
64
|
+
publish = ".next"
|
|
65
|
+
|
|
66
|
+
[[plugins]]
|
|
67
|
+
package = "@netlify/plugin-nextjs"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Custom Domain Setup
|
|
71
|
+
|
|
72
|
+
1. Go to your site's **Domain management** in Netlify
|
|
73
|
+
2. Click **Add a domain**
|
|
74
|
+
3. Enter your subdomain (e.g., `devworkflow.yourdomain.com`)
|
|
75
|
+
4. Add a CNAME record in your DNS provider:
|
|
76
|
+
- **Host:** `devworkflow` (or your subdomain)
|
|
77
|
+
- **Value:** `your-site-name.netlify.app`
|
|
78
|
+
5. Click **Provision certificate** for HTTPS
|
|
79
|
+
|
|
80
|
+
## API Endpoints
|
|
81
|
+
|
|
82
|
+
The following API routes are available as Netlify Functions:
|
|
83
|
+
|
|
84
|
+
| Endpoint | Description |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `/api/version` | Returns current package version |
|
|
87
|
+
| `/api/npm-downloads` | Returns monthly npm download count |
|
|
88
|
+
| `/api/summary` | Returns placeholder summary data |
|
|
89
|
+
| `/api/history` | Returns placeholder history data |
|
|
90
|
+
| `/api/docs` | Returns documentation content |
|
|
91
|
+
|
|
92
|
+
## Limitations
|
|
93
|
+
|
|
94
|
+
When deployed to Netlify (static mode):
|
|
95
|
+
|
|
96
|
+
- **No persistent database** - History and summary return empty/placeholder data
|
|
97
|
+
- **No real-time updates** - Data is static between deployments
|
|
98
|
+
- **Version is build-time** - Update `web/app/api/version/route.ts` before each release
|
|
99
|
+
|
|
100
|
+
## Updating the Deployment
|
|
101
|
+
|
|
102
|
+
### Manual Redeploy
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cd web
|
|
106
|
+
netlify deploy --prod --build
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Auto-Deploy (Recommended)
|
|
110
|
+
|
|
111
|
+
Connect your GitHub repository to Netlify for automatic deployments on push to main branch.
|
|
112
|
+
|
|
113
|
+
## Troubleshooting
|
|
114
|
+
|
|
115
|
+
| Issue | Solution |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| 404 on custom domain | Provision SSL certificate in Netlify dashboard |
|
|
118
|
+
| Build fails | Ensure `web/package.json` has no native dependencies |
|
|
119
|
+
| Functions timeout | Netlify has 10s limit; optimize API routes |
|
|
120
|
+
|
|
121
|
+
## Related
|
|
122
|
+
|
|
123
|
+
- [Web Dashboard Guide](./web-dashboard.md)
|
|
124
|
+
- [Plesk Deployment](./PLESK_DEPLOYMENT.md)
|
|
125
|
+
- [Main README](../README.md)
|