@programinglive/dev-workflow-mcp-server 1.7.10 → 1.7.12
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/.dockerignore +60 -0
- package/.env.example +12 -0
- package/.env.production +16 -0
- package/CHANGELOG.md +45 -0
- package/Dockerfile +32 -0
- package/README.md +63 -2
- package/db/adapters/mysql.js +11 -4
- package/db/migrations/002_create_users_table.sql +22 -0
- package/db/migrations/003_create_workflow_history.sql +14 -0
- package/db/migrations/004_fix_workflow_history.sql +3 -0
- package/docker-compose.yml +24 -0
- package/docs/GCP_DEPLOYMENT.md +298 -0
- package/docs/product/PRD.md +5 -1
- package/docs/release-notes/RELEASE_NOTES.md +26 -0
- package/index.js +2 -0
- package/package.json +9 -3
- package/scripts/add-user.js +52 -0
- package/scripts/create-admin.js +47 -0
- package/scripts/deploy-gcloud.ps1 +160 -0
- package/scripts/deploy-gcloud.sh +154 -0
- package/tests/db-adapters.test.js +108 -0
- package/tests/env-support.test.js +46 -0
- package/tests/workflowState.test.js +16 -1
- package/web/__tests__/api/workflow-history.test.js +61 -0
- package/web/app/docs/page.tsx +1 -1
- package/web/app/history/page.tsx +213 -0
- package/web/app/layout.tsx +1 -1
- package/web/app/login/page.tsx +144 -0
- package/web/app/page.tsx +0 -2
- package/web/components/Hero.tsx +1 -1
- package/web/components/History.tsx +63 -16
- package/web/components/Navbar.tsx +111 -9
- package/web/lib/auth.js +75 -0
- package/web/middleware/auth.js +26 -0
- package/web/next.config.ts +4 -0
- package/web/package-lock.json +40 -40
- package/web/package.json +6 -5
- package/web/scripts/smoke-test.js +30 -0
- package/web/server.js +163 -18
|
@@ -2,22 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { Github, Menu, X, Moon, Sun } from "lucide-react";
|
|
5
|
+
import { Github, Menu, X, Moon, Sun, LogOut } from "lucide-react";
|
|
6
6
|
import Image from "next/image";
|
|
7
7
|
|
|
8
8
|
export default function Navbar() {
|
|
9
9
|
const [isOpen, setIsOpen] = useState(false);
|
|
10
10
|
const [theme, setTheme] = useState<"light" | "dark">("dark");
|
|
11
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
12
|
+
const [username, setUsername] = useState<string | null>(null);
|
|
11
13
|
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
// Get theme from localStorage or system preference
|
|
14
16
|
const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
18
|
+
|
|
19
|
+
const initialTheme = savedTheme || (prefersDark ? "dark" : "light");
|
|
20
|
+
setTheme(initialTheme);
|
|
21
|
+
|
|
22
|
+
// Apply the class immediately
|
|
23
|
+
document.documentElement.classList.toggle("dark", initialTheme === "dark");
|
|
24
|
+
|
|
25
|
+
// Check authentication status
|
|
26
|
+
const checkAuth = () => {
|
|
27
|
+
// Check authentication status ONLY if we think we're logged in OR if we just received an update event
|
|
28
|
+
// This prevents 401 errors from cluttering the console for guests
|
|
29
|
+
const mightBeLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
|
|
30
|
+
|
|
31
|
+
if (mightBeLoggedIn) {
|
|
32
|
+
fetch('/api/auth/me')
|
|
33
|
+
.then(res => {
|
|
34
|
+
if (res.ok) {
|
|
35
|
+
return res.json();
|
|
36
|
+
}
|
|
37
|
+
throw new Error('Not authenticated');
|
|
38
|
+
})
|
|
39
|
+
.then(data => {
|
|
40
|
+
setIsAuthenticated(true);
|
|
41
|
+
setUsername(data.user?.username || 'User');
|
|
42
|
+
// Ensure local storage is in sync
|
|
43
|
+
localStorage.setItem('isLoggedIn', 'true');
|
|
44
|
+
})
|
|
45
|
+
.catch(() => {
|
|
46
|
+
// Session invalid/expired
|
|
47
|
+
localStorage.removeItem('isLoggedIn');
|
|
48
|
+
setIsAuthenticated(false);
|
|
49
|
+
setUsername(null);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
setIsAuthenticated(false);
|
|
53
|
+
setUsername(null);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Initial check
|
|
58
|
+
checkAuth();
|
|
59
|
+
|
|
60
|
+
// Listen for auth updates (login/logout/validated session)
|
|
61
|
+
window.addEventListener('authUpdate', checkAuth);
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
window.removeEventListener('authUpdate', checkAuth);
|
|
65
|
+
};
|
|
21
66
|
}, []);
|
|
22
67
|
|
|
23
68
|
const toggleTheme = () => {
|
|
@@ -27,6 +72,19 @@ export default function Navbar() {
|
|
|
27
72
|
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
28
73
|
};
|
|
29
74
|
|
|
75
|
+
const handleLogout = async () => {
|
|
76
|
+
try {
|
|
77
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
78
|
+
localStorage.removeItem('isLoggedIn');
|
|
79
|
+
setIsAuthenticated(false);
|
|
80
|
+
setUsername(null);
|
|
81
|
+
window.dispatchEvent(new Event('authUpdate'));
|
|
82
|
+
window.location.href = '/login';
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Logout error:', error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
30
88
|
return (
|
|
31
89
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-[#0a0a0f]/80 backdrop-blur-lg border-b border-gray-200 dark:border-white/10 transition-colors duration-300">
|
|
32
90
|
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-8">
|
|
@@ -37,7 +95,7 @@ export default function Navbar() {
|
|
|
37
95
|
</Link>
|
|
38
96
|
<div className="hidden md:flex items-center gap-3 lg:gap-6">
|
|
39
97
|
<Link href="/#features" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Features</Link>
|
|
40
|
-
<Link href="
|
|
98
|
+
<Link href="/history" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">History</Link>
|
|
41
99
|
<Link href="/#api" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">API</Link>
|
|
42
100
|
<Link href="/docs" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Docs</Link>
|
|
43
101
|
<button
|
|
@@ -60,6 +118,28 @@ export default function Navbar() {
|
|
|
60
118
|
<Github className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
|
61
119
|
GitHub
|
|
62
120
|
</a>
|
|
121
|
+
{isAuthenticated && (
|
|
122
|
+
<div className="flex items-center gap-4 pl-4 border-l border-gray-200 dark:border-white/10">
|
|
123
|
+
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
124
|
+
{username}
|
|
125
|
+
</span>
|
|
126
|
+
<button
|
|
127
|
+
onClick={handleLogout}
|
|
128
|
+
className="flex items-center gap-1.5 px-3 py-1.5 lg:px-4 lg:py-2 bg-red-100 dark:bg-red-900/50 hover:bg-red-200 dark:hover:bg-red-800/50 rounded-lg transition text-xs lg:text-sm text-red-700 dark:text-red-300"
|
|
129
|
+
>
|
|
130
|
+
<LogOut className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
|
131
|
+
Logout
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
{!isAuthenticated && (
|
|
136
|
+
<Link
|
|
137
|
+
href="/login"
|
|
138
|
+
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition"
|
|
139
|
+
>
|
|
140
|
+
Login
|
|
141
|
+
</Link>
|
|
142
|
+
)}
|
|
63
143
|
</div>
|
|
64
144
|
<div className="md:hidden flex items-center gap-2">
|
|
65
145
|
<button
|
|
@@ -86,8 +166,13 @@ export default function Navbar() {
|
|
|
86
166
|
{isOpen && (
|
|
87
167
|
<div className="md:hidden border-t border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0f] transition-colors duration-300">
|
|
88
168
|
<div className="px-4 py-4 space-y-3">
|
|
169
|
+
{isAuthenticated && (
|
|
170
|
+
<div className="px-3 py-2 text-sm font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-white/10 mb-2">
|
|
171
|
+
Signed in as {username}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
89
174
|
<Link href="/#features" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Features</Link>
|
|
90
|
-
<Link href="
|
|
175
|
+
<Link href="/history" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>History</Link>
|
|
91
176
|
<Link href="/#api" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>API</Link>
|
|
92
177
|
<Link href="/docs" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Docs</Link>
|
|
93
178
|
<a
|
|
@@ -99,6 +184,23 @@ export default function Navbar() {
|
|
|
99
184
|
<Github className="w-4 h-4" />
|
|
100
185
|
GitHub
|
|
101
186
|
</a>
|
|
187
|
+
{isAuthenticated ? (
|
|
188
|
+
<button
|
|
189
|
+
onClick={() => { setIsOpen(false); handleLogout(); }}
|
|
190
|
+
className="w-full text-left flex items-center gap-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 px-2 py-1.5 rounded transition"
|
|
191
|
+
>
|
|
192
|
+
<LogOut className="w-4 h-4" />
|
|
193
|
+
Logout
|
|
194
|
+
</button>
|
|
195
|
+
) : (
|
|
196
|
+
<Link
|
|
197
|
+
href="/login"
|
|
198
|
+
className="block w-full text-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition"
|
|
199
|
+
onClick={() => setIsOpen(false)}
|
|
200
|
+
>
|
|
201
|
+
Login
|
|
202
|
+
</Link>
|
|
203
|
+
)}
|
|
102
204
|
</div>
|
|
103
205
|
</div>
|
|
104
206
|
)}
|
package/web/lib/auth.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import bcrypt from 'bcrypt';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
|
|
4
|
+
const SALT_ROUNDS = 10;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hash a password using bcrypt
|
|
8
|
+
*/
|
|
9
|
+
export async function hashPassword(password) {
|
|
10
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Verify a password against a hash
|
|
15
|
+
*/
|
|
16
|
+
export async function verifyPassword(password, hash) {
|
|
17
|
+
return bcrypt.compare(password, hash);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a new user
|
|
22
|
+
*/
|
|
23
|
+
export async function createUser(pool, username, password, email = null) {
|
|
24
|
+
const passwordHash = await hashPassword(password);
|
|
25
|
+
|
|
26
|
+
const result = await pool.query(
|
|
27
|
+
'INSERT INTO users (username, password_hash, email) VALUES ($1, $2, $3) RETURNING id, username, email, created_at',
|
|
28
|
+
[username, passwordHash, email]
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return result.rows[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Authenticate a user
|
|
36
|
+
*/
|
|
37
|
+
export async function authenticateUser(pool, username, password) {
|
|
38
|
+
const result = await pool.query(
|
|
39
|
+
'SELECT id, username, password_hash, email FROM users WHERE username = $1',
|
|
40
|
+
[username]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (result.rows.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const user = result.rows[0];
|
|
48
|
+
const isValid = await verifyPassword(password, user.password_hash);
|
|
49
|
+
|
|
50
|
+
if (!isValid) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Update last login
|
|
55
|
+
await pool.query(
|
|
56
|
+
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = $1',
|
|
57
|
+
[user.id]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Return user without password hash
|
|
61
|
+
const { password_hash, ...userWithoutPassword } = user;
|
|
62
|
+
return userWithoutPassword;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get user by ID
|
|
67
|
+
*/
|
|
68
|
+
export async function getUserById(pool, id) {
|
|
69
|
+
const result = await pool.query(
|
|
70
|
+
'SELECT id, username, email, created_at, last_login FROM users WHERE id = $1',
|
|
71
|
+
[id]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return result.rows[0] || null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to require authentication
|
|
3
|
+
*/
|
|
4
|
+
export function requireAuth(req, res, next) {
|
|
5
|
+
if (req.session && req.session.userId) {
|
|
6
|
+
return next();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// If it's an API request, return 401
|
|
10
|
+
if (req.path.startsWith('/api/')) {
|
|
11
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Otherwise redirect to login
|
|
15
|
+
res.redirect('/login');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Middleware to redirect if already authenticated
|
|
20
|
+
*/
|
|
21
|
+
export function redirectIfAuthenticated(req, res, next) {
|
|
22
|
+
if (req.session && req.session.userId) {
|
|
23
|
+
return res.redirect('/');
|
|
24
|
+
}
|
|
25
|
+
next();
|
|
26
|
+
}
|
package/web/next.config.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { NextConfig } from "next";
|
|
|
3
3
|
const nextConfig: NextConfig = {
|
|
4
4
|
// Static export for Netlify deployment
|
|
5
5
|
output: 'export',
|
|
6
|
+
// Set turbopack root to silence multi-lockfile warning
|
|
7
|
+
turbopack: {
|
|
8
|
+
root: __dirname,
|
|
9
|
+
},
|
|
6
10
|
// Disable image optimization for static export
|
|
7
11
|
images: {
|
|
8
12
|
unoptimized: true,
|
package/web/package-lock.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"version": "0.1.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"lucide-react": "^0.555.0",
|
|
12
|
-
"next": "16.0.
|
|
12
|
+
"next": "^16.0.10",
|
|
13
13
|
"react": "19.2.0",
|
|
14
14
|
"react-dom": "19.2.0"
|
|
15
15
|
},
|
|
@@ -1038,9 +1038,9 @@
|
|
|
1038
1038
|
}
|
|
1039
1039
|
},
|
|
1040
1040
|
"node_modules/@next/env": {
|
|
1041
|
-
"version": "16.0.
|
|
1042
|
-
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.
|
|
1043
|
-
"integrity": "sha512-
|
|
1041
|
+
"version": "16.0.10",
|
|
1042
|
+
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz",
|
|
1043
|
+
"integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==",
|
|
1044
1044
|
"license": "MIT"
|
|
1045
1045
|
},
|
|
1046
1046
|
"node_modules/@next/eslint-plugin-next": {
|
|
@@ -1054,9 +1054,9 @@
|
|
|
1054
1054
|
}
|
|
1055
1055
|
},
|
|
1056
1056
|
"node_modules/@next/swc-darwin-arm64": {
|
|
1057
|
-
"version": "16.0.
|
|
1058
|
-
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.
|
|
1059
|
-
"integrity": "sha512-
|
|
1057
|
+
"version": "16.0.10",
|
|
1058
|
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.10.tgz",
|
|
1059
|
+
"integrity": "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==",
|
|
1060
1060
|
"cpu": [
|
|
1061
1061
|
"arm64"
|
|
1062
1062
|
],
|
|
@@ -1070,9 +1070,9 @@
|
|
|
1070
1070
|
}
|
|
1071
1071
|
},
|
|
1072
1072
|
"node_modules/@next/swc-darwin-x64": {
|
|
1073
|
-
"version": "16.0.
|
|
1074
|
-
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.
|
|
1075
|
-
"integrity": "sha512-
|
|
1073
|
+
"version": "16.0.10",
|
|
1074
|
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz",
|
|
1075
|
+
"integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==",
|
|
1076
1076
|
"cpu": [
|
|
1077
1077
|
"x64"
|
|
1078
1078
|
],
|
|
@@ -1086,9 +1086,9 @@
|
|
|
1086
1086
|
}
|
|
1087
1087
|
},
|
|
1088
1088
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
|
1089
|
-
"version": "16.0.
|
|
1090
|
-
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.
|
|
1091
|
-
"integrity": "sha512-
|
|
1089
|
+
"version": "16.0.10",
|
|
1090
|
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz",
|
|
1091
|
+
"integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==",
|
|
1092
1092
|
"cpu": [
|
|
1093
1093
|
"arm64"
|
|
1094
1094
|
],
|
|
@@ -1102,9 +1102,9 @@
|
|
|
1102
1102
|
}
|
|
1103
1103
|
},
|
|
1104
1104
|
"node_modules/@next/swc-linux-arm64-musl": {
|
|
1105
|
-
"version": "16.0.
|
|
1106
|
-
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.
|
|
1107
|
-
"integrity": "sha512-
|
|
1105
|
+
"version": "16.0.10",
|
|
1106
|
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz",
|
|
1107
|
+
"integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==",
|
|
1108
1108
|
"cpu": [
|
|
1109
1109
|
"arm64"
|
|
1110
1110
|
],
|
|
@@ -1118,9 +1118,9 @@
|
|
|
1118
1118
|
}
|
|
1119
1119
|
},
|
|
1120
1120
|
"node_modules/@next/swc-linux-x64-gnu": {
|
|
1121
|
-
"version": "16.0.
|
|
1122
|
-
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.
|
|
1123
|
-
"integrity": "sha512-
|
|
1121
|
+
"version": "16.0.10",
|
|
1122
|
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz",
|
|
1123
|
+
"integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==",
|
|
1124
1124
|
"cpu": [
|
|
1125
1125
|
"x64"
|
|
1126
1126
|
],
|
|
@@ -1134,9 +1134,9 @@
|
|
|
1134
1134
|
}
|
|
1135
1135
|
},
|
|
1136
1136
|
"node_modules/@next/swc-linux-x64-musl": {
|
|
1137
|
-
"version": "16.0.
|
|
1138
|
-
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.
|
|
1139
|
-
"integrity": "sha512-
|
|
1137
|
+
"version": "16.0.10",
|
|
1138
|
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz",
|
|
1139
|
+
"integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==",
|
|
1140
1140
|
"cpu": [
|
|
1141
1141
|
"x64"
|
|
1142
1142
|
],
|
|
@@ -1150,9 +1150,9 @@
|
|
|
1150
1150
|
}
|
|
1151
1151
|
},
|
|
1152
1152
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
|
1153
|
-
"version": "16.0.
|
|
1154
|
-
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.
|
|
1155
|
-
"integrity": "sha512-
|
|
1153
|
+
"version": "16.0.10",
|
|
1154
|
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz",
|
|
1155
|
+
"integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==",
|
|
1156
1156
|
"cpu": [
|
|
1157
1157
|
"arm64"
|
|
1158
1158
|
],
|
|
@@ -1166,9 +1166,9 @@
|
|
|
1166
1166
|
}
|
|
1167
1167
|
},
|
|
1168
1168
|
"node_modules/@next/swc-win32-x64-msvc": {
|
|
1169
|
-
"version": "16.0.
|
|
1170
|
-
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.
|
|
1171
|
-
"integrity": "sha512-
|
|
1169
|
+
"version": "16.0.10",
|
|
1170
|
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz",
|
|
1171
|
+
"integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==",
|
|
1172
1172
|
"cpu": [
|
|
1173
1173
|
"x64"
|
|
1174
1174
|
],
|
|
@@ -4652,12 +4652,12 @@
|
|
|
4652
4652
|
"license": "MIT"
|
|
4653
4653
|
},
|
|
4654
4654
|
"node_modules/next": {
|
|
4655
|
-
"version": "16.0.
|
|
4656
|
-
"resolved": "https://registry.npmjs.org/next/-/next-16.0.
|
|
4657
|
-
"integrity": "sha512-
|
|
4655
|
+
"version": "16.0.10",
|
|
4656
|
+
"resolved": "https://registry.npmjs.org/next/-/next-16.0.10.tgz",
|
|
4657
|
+
"integrity": "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==",
|
|
4658
4658
|
"license": "MIT",
|
|
4659
4659
|
"dependencies": {
|
|
4660
|
-
"@next/env": "16.0.
|
|
4660
|
+
"@next/env": "16.0.10",
|
|
4661
4661
|
"@swc/helpers": "0.5.15",
|
|
4662
4662
|
"caniuse-lite": "^1.0.30001579",
|
|
4663
4663
|
"postcss": "8.4.31",
|
|
@@ -4670,14 +4670,14 @@
|
|
|
4670
4670
|
"node": ">=20.9.0"
|
|
4671
4671
|
},
|
|
4672
4672
|
"optionalDependencies": {
|
|
4673
|
-
"@next/swc-darwin-arm64": "16.0.
|
|
4674
|
-
"@next/swc-darwin-x64": "16.0.
|
|
4675
|
-
"@next/swc-linux-arm64-gnu": "16.0.
|
|
4676
|
-
"@next/swc-linux-arm64-musl": "16.0.
|
|
4677
|
-
"@next/swc-linux-x64-gnu": "16.0.
|
|
4678
|
-
"@next/swc-linux-x64-musl": "16.0.
|
|
4679
|
-
"@next/swc-win32-arm64-msvc": "16.0.
|
|
4680
|
-
"@next/swc-win32-x64-msvc": "16.0.
|
|
4673
|
+
"@next/swc-darwin-arm64": "16.0.10",
|
|
4674
|
+
"@next/swc-darwin-x64": "16.0.10",
|
|
4675
|
+
"@next/swc-linux-arm64-gnu": "16.0.10",
|
|
4676
|
+
"@next/swc-linux-arm64-musl": "16.0.10",
|
|
4677
|
+
"@next/swc-linux-x64-gnu": "16.0.10",
|
|
4678
|
+
"@next/swc-linux-x64-musl": "16.0.10",
|
|
4679
|
+
"@next/swc-win32-arm64-msvc": "16.0.10",
|
|
4680
|
+
"@next/swc-win32-x64-msvc": "16.0.10",
|
|
4681
4681
|
"sharp": "^0.34.4"
|
|
4682
4682
|
},
|
|
4683
4683
|
"peerDependencies": {
|
package/web/package.json
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
"name": "web",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
+
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
|
-
"dev": "
|
|
7
|
+
"dev": "node server.js",
|
|
7
8
|
"build": "next build",
|
|
8
|
-
"start": "
|
|
9
|
-
"lint": "
|
|
9
|
+
"start": "NODE_ENV=production node server.js",
|
|
10
|
+
"lint": "next lint"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"lucide-react": "^0.555.0",
|
|
13
|
-
"next": "16.0.
|
|
14
|
+
"next": "^16.0.10",
|
|
14
15
|
"react": "19.2.0",
|
|
15
16
|
"react-dom": "19.2.0"
|
|
16
17
|
},
|
|
@@ -25,4 +26,4 @@
|
|
|
25
26
|
"tailwindcss": "^3.4.18",
|
|
26
27
|
"typescript": "^5"
|
|
27
28
|
}
|
|
28
|
-
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const rootDir = path.join(__dirname, '..');
|
|
8
|
+
|
|
9
|
+
console.log('Running smoke tests...');
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// 1. Verify package.json type: module
|
|
13
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
14
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
15
|
+
if (packageJson.type !== 'module') {
|
|
16
|
+
throw new Error('FAIL: package.json must have "type": "module"');
|
|
17
|
+
}
|
|
18
|
+
console.log('✅ package.json has "type": "module"');
|
|
19
|
+
|
|
20
|
+
// 2. Syntax check server.js
|
|
21
|
+
const serverJsPath = path.join(rootDir, 'server.js');
|
|
22
|
+
execSync(`node -c "${serverJsPath}"`, { stdio: 'inherit' });
|
|
23
|
+
console.log('✅ server.js syntax check passed');
|
|
24
|
+
|
|
25
|
+
console.log('All smoke tests passed!');
|
|
26
|
+
process.exit(0);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('❌ Smoke test failed:', error.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|