@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.
Files changed (99) hide show
  1. package/.agent/workflows/dev-workflow-bugfix.md +250 -250
  2. package/.agent/workflows/dev-workflow-feature.md +201 -201
  3. package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
  4. package/.agent/workflows/dev-workflow-refactor.md +238 -238
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
  7. package/.github/pull_request_template.md +22 -22
  8. package/.github/workflows/npm-publish.yml +50 -47
  9. package/.husky/commit-msg +0 -0
  10. package/CHANGELOG.md +750 -491
  11. package/CODE_OF_CONDUCT.md +79 -79
  12. package/CONTRIBUTING.md +60 -60
  13. package/LICENSE +21 -21
  14. package/README.md +632 -609
  15. package/SECURITY.md +20 -20
  16. package/db/index.js +204 -204
  17. package/docs/NETLIFY_DEPLOYMENT.md +125 -0
  18. package/docs/PLESK_DEPLOYMENT.md +170 -0
  19. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -0
  20. package/docs/antigravity/EXAMPLES.md +535 -535
  21. package/docs/antigravity/GETTING-STARTED.md +292 -292
  22. package/docs/antigravity/TROUBLESHOOTING.md +742 -742
  23. package/docs/antigravity/WALKTHROUGH.md +25 -25
  24. package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
  25. package/docs/product/PRD.md +67 -64
  26. package/docs/release-notes/RELEASE_NOTES.md +226 -132
  27. package/docs/web-dashboard.md +175 -163
  28. package/exec.js +49 -49
  29. package/index.js +279 -279
  30. package/install.js +73 -73
  31. package/package.json +11 -9
  32. package/release-wrapper.js +192 -157
  33. package/scripts/build-docs.js +63 -63
  34. package/scripts/hello-world.js +1 -1
  35. package/scripts/release.cjs +252 -252
  36. package/scripts/update-release-notes.cjs +178 -182
  37. package/server.json +23 -0
  38. package/tailwind.config.js +8 -10
  39. package/tests/build.test.js +83 -48
  40. package/tests/exec.test.js +35 -35
  41. package/tests/handlers.test.js +1058 -980
  42. package/tests/hello-world.test.js +13 -13
  43. package/tests/mcpRegistry.test.js +65 -0
  44. package/tests/release-wrapper.test.js +107 -105
  45. package/tests/workflowState.test.js +505 -502
  46. package/tools/definitions.js +332 -332
  47. package/tools/handlers.js +1019 -1019
  48. package/vite.config.js +43 -43
  49. package/vite.web.config.js +23 -0
  50. package/web/README.md +36 -0
  51. package/web/app/docs/page.tsx +202 -0
  52. package/web/app/favicon.ico +0 -0
  53. package/web/app/globals.css +91 -0
  54. package/web/app/layout.tsx +36 -0
  55. package/web/app/page.tsx +15 -0
  56. package/web/components/ApiReference.tsx +20 -0
  57. package/web/components/Features.tsx +61 -0
  58. package/web/components/Footer.tsx +9 -0
  59. package/web/components/Hero.tsx +96 -0
  60. package/web/components/History.tsx +143 -0
  61. package/web/components/Navbar.tsx +64 -0
  62. package/web/components/Terminal.tsx +114 -0
  63. package/web/eslint.config.mjs +18 -0
  64. package/web/lib/db.js +131 -0
  65. package/web/lib/docs.js +73 -0
  66. package/web/netlify.toml +3 -0
  67. package/web/next.config.ts +12 -0
  68. package/web/package-lock.json +6560 -0
  69. package/web/package.json +28 -0
  70. package/web/postcss.config.mjs +9 -0
  71. package/web/public/assets/android-chrome-192x192.png +0 -0
  72. package/web/public/assets/android-chrome-512x512.png +0 -0
  73. package/web/public/assets/apple-touch-icon.png +0 -0
  74. package/web/public/assets/favicon-16x16.png +0 -0
  75. package/web/public/assets/favicon-32x32.png +0 -0
  76. package/web/public/assets/favicon.ico +0 -0
  77. package/web/public/assets/favicon.png +0 -0
  78. package/web/public/assets/logo.png +0 -0
  79. package/web/public/assets/og-preview.png +0 -0
  80. package/web/public/assets/site.webmanifest +1 -0
  81. package/web/public/file.svg +1 -0
  82. package/web/public/globe.svg +1 -0
  83. package/web/public/next.svg +1 -0
  84. package/web/public/vercel.svg +1 -0
  85. package/web/public/window.svg +1 -0
  86. package/web/server.js +31 -159
  87. package/web/tailwind.config.ts +19 -0
  88. package/web/tsconfig.json +42 -0
  89. package/workflow-state.js +612 -626
  90. package/dist/docs/EXAMPLES.html +0 -411
  91. package/dist/docs/GETTING-STARTED.html +0 -233
  92. package/dist/docs/README.html +0 -535
  93. package/dist/docs/RELEASE_NOTES.html +0 -167
  94. package/dist/docs/TROUBLESHOOTING.html +0 -542
  95. package/dist/docs/WORKFLOW-TEMPLATES.html +0 -591
  96. package/dist/docs/web-dashboard.html +0 -158
  97. package/web/public/index.html +0 -1046
  98. package/web/public/input.css +0 -3
  99. 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)