spaps 0.1.0 ā 0.2.0
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/README.md +9 -0
- package/bin/spaps.js +43 -16
- package/package.json +5 -2
- package/src/local-server.js +316 -0
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ npx spaps local
|
|
|
12
12
|
|
|
13
13
|
That's it! Your local SPAPS server is running at http://localhost:3300 š
|
|
14
14
|
|
|
15
|
+
⨠**New in v0.2.0**: The `spaps local` command now starts a real server!
|
|
16
|
+
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
17
19
|
```bash
|
|
@@ -28,10 +30,17 @@ Start a local SPAPS development server with:
|
|
|
28
30
|
- Auto-authentication enabled
|
|
29
31
|
- Test users (user/admin/premium)
|
|
30
32
|
- CORS configured for any frontend
|
|
33
|
+
- Mock Stripe endpoints
|
|
34
|
+
- Full auth flow support
|
|
31
35
|
|
|
32
36
|
```bash
|
|
33
37
|
spaps local
|
|
34
38
|
# Server running at http://localhost:3300
|
|
39
|
+
# Documentation at http://localhost:3300/docs
|
|
40
|
+
|
|
41
|
+
# Options:
|
|
42
|
+
spaps local --port 3301 # Use different port
|
|
43
|
+
spaps local --open # Open browser automatically
|
|
35
44
|
```
|
|
36
45
|
|
|
37
46
|
### `spaps init`
|
package/bin/spaps.js
CHANGED
|
@@ -30,24 +30,51 @@ program
|
|
|
30
30
|
.command('local')
|
|
31
31
|
.description('Start local SPAPS server (no API keys required!)')
|
|
32
32
|
.option('-p, --port <port>', 'Port to run on', '3300')
|
|
33
|
-
.
|
|
33
|
+
.option('-o, --open', 'Open browser automatically', false)
|
|
34
|
+
.action(async (options) => {
|
|
34
35
|
console.log(logo);
|
|
35
|
-
console.log(chalk.green('š Starting SPAPS local development server...'));
|
|
36
|
-
console.log(chalk.dim(` Port: ${options.port}`));
|
|
37
|
-
console.log(chalk.dim(' Mode: Local Development (auto-auth enabled)'));
|
|
38
|
-
console.log();
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
try {
|
|
38
|
+
// Import and start the local server
|
|
39
|
+
const LocalServer = require('../src/local-server.js');
|
|
40
|
+
const server = new LocalServer({ port: options.port });
|
|
41
|
+
|
|
42
|
+
await server.start();
|
|
43
|
+
|
|
44
|
+
// Open browser if requested
|
|
45
|
+
if (options.open) {
|
|
46
|
+
const { exec } = require('child_process');
|
|
47
|
+
const url = `http://localhost:${options.port}/docs`;
|
|
48
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
49
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
50
|
+
exec(`${start} ${url}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Keep process running
|
|
54
|
+
process.on('SIGINT', () => {
|
|
55
|
+
console.log(chalk.yellow('\nš Shutting down SPAPS local server...'));
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red('ā Failed to start server:'), error.message);
|
|
61
|
+
|
|
62
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
63
|
+
console.log(chalk.yellow('\nš” Installing dependencies...'));
|
|
64
|
+
const { execSync } = require('child_process');
|
|
65
|
+
try {
|
|
66
|
+
execSync('npm install express cors', {
|
|
67
|
+
cwd: path.join(__dirname, '..'),
|
|
68
|
+
stdio: 'inherit'
|
|
69
|
+
});
|
|
70
|
+
console.log(chalk.green('ā
Dependencies installed! Please run the command again.'));
|
|
71
|
+
} catch (installError) {
|
|
72
|
+
console.error(chalk.red('Failed to install dependencies. Please run: npm install express cors'));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
51
78
|
});
|
|
52
79
|
|
|
53
80
|
// Init command - Initialize SPAPS in existing project
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service CLI - Zero-config local development and project scaffolding",
|
|
5
5
|
"main": "bin/spaps.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,13 +37,16 @@
|
|
|
37
37
|
"commander": "^11.1.0",
|
|
38
38
|
"ora": "^5.4.1",
|
|
39
39
|
"prompts": "^2.4.2",
|
|
40
|
-
"axios": "^1.6.0"
|
|
40
|
+
"axios": "^1.6.0",
|
|
41
|
+
"express": "^4.18.2",
|
|
42
|
+
"cors": "^2.8.5"
|
|
41
43
|
},
|
|
42
44
|
"engines": {
|
|
43
45
|
"node": ">=16.0.0"
|
|
44
46
|
},
|
|
45
47
|
"files": [
|
|
46
48
|
"bin",
|
|
49
|
+
"src",
|
|
47
50
|
"README.md"
|
|
48
51
|
]
|
|
49
52
|
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SPAPS Local Development Server
|
|
5
|
+
* Minimal, zero-config server for local development
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const cors = require('cors');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
class LocalServer {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.port = options.port || process.env.PORT || 3300;
|
|
15
|
+
this.app = express();
|
|
16
|
+
this.setupMiddleware();
|
|
17
|
+
this.setupRoutes();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setupMiddleware() {
|
|
21
|
+
// CORS - allow everything in local mode
|
|
22
|
+
this.app.use(cors({
|
|
23
|
+
origin: true,
|
|
24
|
+
credentials: true,
|
|
25
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
26
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Test-User'],
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Body parsing
|
|
30
|
+
this.app.use(express.json());
|
|
31
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
32
|
+
|
|
33
|
+
// Local mode indicator
|
|
34
|
+
this.app.use((req, res, next) => {
|
|
35
|
+
res.setHeader('X-SPAPS-Mode', 'local-development');
|
|
36
|
+
|
|
37
|
+
// Auto-auth in local mode
|
|
38
|
+
if (!req.headers.authorization && !req.headers['x-api-key']) {
|
|
39
|
+
req.headers['x-api-key'] = 'local-dev-key';
|
|
40
|
+
req.user = {
|
|
41
|
+
id: 'local-user-123',
|
|
42
|
+
email: 'dev@localhost',
|
|
43
|
+
role: req.query._user || req.headers['x-test-user'] || 'user'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Log requests
|
|
48
|
+
console.log(chalk.dim(`${req.method} ${req.path}`));
|
|
49
|
+
next();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setupRoutes() {
|
|
54
|
+
// Health check
|
|
55
|
+
this.app.get('/health', (req, res) => {
|
|
56
|
+
res.json({
|
|
57
|
+
status: 'healthy',
|
|
58
|
+
mode: 'local-development',
|
|
59
|
+
version: '0.2.0',
|
|
60
|
+
timestamp: new Date().toISOString()
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Local mode status
|
|
65
|
+
this.app.get('/health/local-mode', (req, res) => {
|
|
66
|
+
res.json({
|
|
67
|
+
enabled: true,
|
|
68
|
+
environment: 'local-development',
|
|
69
|
+
features: {
|
|
70
|
+
autoAuth: true,
|
|
71
|
+
corsEnabled: true,
|
|
72
|
+
testUsers: ['user', 'admin', 'premium'],
|
|
73
|
+
apiKeyRequired: false
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Mock authentication endpoints
|
|
79
|
+
this.app.post('/api/auth/login', (req, res) => {
|
|
80
|
+
const { email, password } = req.body;
|
|
81
|
+
res.json({
|
|
82
|
+
access_token: 'local-jwt-token-' + Date.now(),
|
|
83
|
+
refresh_token: 'local-refresh-token-' + Date.now(),
|
|
84
|
+
user: {
|
|
85
|
+
id: 'local-user-123',
|
|
86
|
+
email: email || 'dev@localhost',
|
|
87
|
+
role: 'user'
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.app.post('/api/auth/register', (req, res) => {
|
|
93
|
+
const { email, password } = req.body;
|
|
94
|
+
res.json({
|
|
95
|
+
access_token: 'local-jwt-token-' + Date.now(),
|
|
96
|
+
refresh_token: 'local-refresh-token-' + Date.now(),
|
|
97
|
+
user: {
|
|
98
|
+
id: 'local-user-' + Date.now(),
|
|
99
|
+
email: email || 'dev@localhost',
|
|
100
|
+
role: 'user'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this.app.post('/api/auth/wallet-sign-in', (req, res) => {
|
|
106
|
+
const { wallet_address, chain_type } = req.body;
|
|
107
|
+
res.json({
|
|
108
|
+
access_token: 'local-jwt-token-' + Date.now(),
|
|
109
|
+
refresh_token: 'local-refresh-token-' + Date.now(),
|
|
110
|
+
user: {
|
|
111
|
+
id: 'local-wallet-user-123',
|
|
112
|
+
wallet_address,
|
|
113
|
+
chain_type,
|
|
114
|
+
role: 'user'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.app.post('/api/auth/refresh', (req, res) => {
|
|
120
|
+
res.json({
|
|
121
|
+
access_token: 'local-jwt-token-refreshed-' + Date.now(),
|
|
122
|
+
refresh_token: 'local-refresh-token-refreshed-' + Date.now()
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
this.app.post('/api/auth/logout', (req, res) => {
|
|
127
|
+
res.json({ success: true, message: 'Logged out successfully' });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.app.get('/api/auth/user', (req, res) => {
|
|
131
|
+
res.json({
|
|
132
|
+
id: req.user?.id || 'local-user-123',
|
|
133
|
+
email: req.user?.email || 'dev@localhost',
|
|
134
|
+
role: req.user?.role || 'user',
|
|
135
|
+
created_at: new Date().toISOString()
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Mock Stripe endpoints
|
|
140
|
+
this.app.post('/api/stripe/create-checkout-session', (req, res) => {
|
|
141
|
+
res.json({
|
|
142
|
+
sessionId: 'cs_test_local_' + Date.now(),
|
|
143
|
+
url: 'https://checkout.stripe.com/pay/cs_test_local'
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.app.get('/api/stripe/subscription', (req, res) => {
|
|
148
|
+
res.json({
|
|
149
|
+
id: 'sub_local_123',
|
|
150
|
+
status: 'active',
|
|
151
|
+
plan: 'premium',
|
|
152
|
+
current_period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Mock usage endpoints
|
|
157
|
+
this.app.get('/api/usage/balance', (req, res) => {
|
|
158
|
+
res.json({
|
|
159
|
+
balance: 1000,
|
|
160
|
+
currency: 'credits',
|
|
161
|
+
updated_at: new Date().toISOString()
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Documentation endpoint
|
|
166
|
+
this.app.get('/docs', (req, res) => {
|
|
167
|
+
res.send(`
|
|
168
|
+
<!DOCTYPE html>
|
|
169
|
+
<html>
|
|
170
|
+
<head>
|
|
171
|
+
<title>SPAPS Local Mode</title>
|
|
172
|
+
<style>
|
|
173
|
+
body {
|
|
174
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
175
|
+
max-width: 800px;
|
|
176
|
+
margin: 50px auto;
|
|
177
|
+
padding: 20px;
|
|
178
|
+
line-height: 1.6;
|
|
179
|
+
}
|
|
180
|
+
h1 { color: #ff6b6b; }
|
|
181
|
+
code {
|
|
182
|
+
background: #f4f4f4;
|
|
183
|
+
padding: 2px 6px;
|
|
184
|
+
border-radius: 3px;
|
|
185
|
+
}
|
|
186
|
+
pre {
|
|
187
|
+
background: #f4f4f4;
|
|
188
|
+
padding: 15px;
|
|
189
|
+
border-radius: 5px;
|
|
190
|
+
overflow-x: auto;
|
|
191
|
+
}
|
|
192
|
+
.endpoint {
|
|
193
|
+
margin: 20px 0;
|
|
194
|
+
padding: 15px;
|
|
195
|
+
border-left: 3px solid #ff6b6b;
|
|
196
|
+
background: #fff9f9;
|
|
197
|
+
}
|
|
198
|
+
</style>
|
|
199
|
+
</head>
|
|
200
|
+
<body>
|
|
201
|
+
<h1>š SPAPS Local Development Mode</h1>
|
|
202
|
+
<p>You're running in <strong>local development mode</strong>. No API keys required!</p>
|
|
203
|
+
|
|
204
|
+
<h2>Quick Start</h2>
|
|
205
|
+
<pre>
|
|
206
|
+
// No configuration needed!
|
|
207
|
+
const response = await fetch('http://localhost:${this.port}/api/auth/login', {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: JSON.stringify({
|
|
211
|
+
email: 'test@example.com',
|
|
212
|
+
password: 'password'
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
const { access_token, user } = await response.json();
|
|
216
|
+
</pre>
|
|
217
|
+
|
|
218
|
+
<h2>Available Endpoints</h2>
|
|
219
|
+
|
|
220
|
+
<div class="endpoint">
|
|
221
|
+
<strong>GET /health</strong> - Health check
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="endpoint">
|
|
225
|
+
<strong>POST /api/auth/login</strong> - Email/password login
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<div class="endpoint">
|
|
229
|
+
<strong>POST /api/auth/register</strong> - Create account
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div class="endpoint">
|
|
233
|
+
<strong>POST /api/auth/wallet-sign-in</strong> - Wallet authentication
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="endpoint">
|
|
237
|
+
<strong>GET /api/auth/user</strong> - Get current user
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="endpoint">
|
|
241
|
+
<strong>POST /api/stripe/create-checkout-session</strong> - Create payment session
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="endpoint">
|
|
245
|
+
<strong>GET /api/usage/balance</strong> - Check usage balance
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<h2>Test Users</h2>
|
|
249
|
+
<p>Switch between test users by adding <code>?_user=admin</code> or <code>?_user=premium</code> to any request.</p>
|
|
250
|
+
|
|
251
|
+
<h2>Environment</h2>
|
|
252
|
+
<pre>
|
|
253
|
+
Mode: ${process.env.NODE_ENV || 'development'}
|
|
254
|
+
Port: ${this.port}
|
|
255
|
+
Auto-Auth: Enabled
|
|
256
|
+
CORS: Enabled (all origins)
|
|
257
|
+
</pre>
|
|
258
|
+
</body>
|
|
259
|
+
</html>
|
|
260
|
+
`);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Catch-all for unimplemented routes
|
|
264
|
+
this.app.use((req, res) => {
|
|
265
|
+
res.status(404).json({
|
|
266
|
+
error: 'Not found',
|
|
267
|
+
message: `Endpoint ${req.method} ${req.path} not implemented in local mode`,
|
|
268
|
+
suggestion: 'Check /docs for available endpoints'
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
start() {
|
|
274
|
+
return new Promise((resolve, reject) => {
|
|
275
|
+
const server = this.app.listen(this.port, (err) => {
|
|
276
|
+
if (err) {
|
|
277
|
+
reject(err);
|
|
278
|
+
} else {
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(chalk.yellow('š SPAPS Local Development Server'));
|
|
281
|
+
console.log(chalk.green(`⨠Running at: http://localhost:${this.port}`));
|
|
282
|
+
console.log(chalk.blue(`š Documentation: http://localhost:${this.port}/docs`));
|
|
283
|
+
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
|
284
|
+
console.log();
|
|
285
|
+
resolve(server);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Handle errors
|
|
290
|
+
server.on('error', (err) => {
|
|
291
|
+
if (err.code === 'EADDRINUSE') {
|
|
292
|
+
console.error(chalk.red(`ā Port ${this.port} is already in use`));
|
|
293
|
+
console.log(chalk.yellow('š” Try: spaps local --port 3301'));
|
|
294
|
+
} else {
|
|
295
|
+
console.error(chalk.red('ā Server error:'), err.message);
|
|
296
|
+
}
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Export for use in CLI
|
|
304
|
+
module.exports = LocalServer;
|
|
305
|
+
|
|
306
|
+
// Run directly if called as script
|
|
307
|
+
if (require.main === module) {
|
|
308
|
+
const server = new LocalServer();
|
|
309
|
+
server.start().catch(console.error);
|
|
310
|
+
|
|
311
|
+
// Graceful shutdown
|
|
312
|
+
process.on('SIGINT', () => {
|
|
313
|
+
console.log(chalk.yellow('\nš Shutting down...'));
|
|
314
|
+
process.exit(0);
|
|
315
|
+
});
|
|
316
|
+
}
|