instaserve 1.0.21 → 1.1.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/README.md ADDED
@@ -0,0 +1,176 @@
1
+ <div align="center">
2
+ <h1 style="color: #3b82f6; margin: 10px 0 5px;">Instaserve</h1>
3
+ <p style="color: #6b7280; margin: 0;">Instant web stack for Node.js</p>
4
+
5
+ <p>
6
+ <img src="https://img.shields.io/npm/v/instaserve" alt="npm version" />
7
+ <img src="https://img.shields.io/bundlephobia/minzip/instaserve" alt="bundle size" />
8
+ </p>
9
+ </div>
10
+
11
+ ## Usage
12
+
13
+ <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); margin: 15px 0;">
14
+ <pre style="margin: 0;"><code>npx instaserve [options]
15
+ npx instaserve generate-routes</code></pre>
16
+ </div>
17
+
18
+ ## Commands
19
+
20
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
21
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">generate-routes</code>
22
+ <span>Create a sample routes.js file in the current directory</span>
23
+ </div>
24
+
25
+ ## Options
26
+
27
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
28
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-port &lt;number&gt;</code>
29
+ <span>Port to listen on (default: 3000)</span>
30
+ </div>
31
+
32
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
33
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-ip &lt;address&gt;</code>
34
+ <span>IP address to bind to (default: 127.0.0.1)</span>
35
+ </div>
36
+
37
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
38
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-public &lt;path&gt;</code>
39
+ <span>Public directory path (default: ./public)</span>
40
+ </div>
41
+
42
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
43
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-api &lt;file&gt;</code>
44
+ <span>Path to routes file (default: ./routes.js)</span>
45
+ </div>
46
+
47
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
48
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-secure</code>
49
+ <span>Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</span>
50
+ </div>
51
+
52
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
53
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-help</code>
54
+ <span>Show help message</span>
55
+ </div>
56
+
57
+ ## HTTPS Support
58
+
59
+ Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:
60
+
61
+ 1. **Generate certificates:**
62
+ ```bash
63
+ ./generate-certs.sh
64
+ ```
65
+ This creates `cert.pem` and `key.pem` files and adds them to your system's trust store.
66
+
67
+ 2. **Run with HTTPS:**
68
+ ```bash
69
+ npx instaserve -secure
70
+ ```
71
+
72
+ The certificate generation script:
73
+ - Creates a self-signed certificate valid for 365 days
74
+ - Automatically adds the certificate to your system trust store (macOS/Linux)
75
+ - Prevents browser security warnings
76
+
77
+ ## Routes
78
+
79
+ The routes file (`routes.js` by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.
80
+
81
+ ### Generating a Routes File
82
+
83
+ To create a sample `routes.js` file with example routes and middleware:
84
+
85
+ ```bash
86
+ npx instaserve generate-routes
87
+ ```
88
+
89
+ This creates a `routes.js` file in the current directory with example code. If the file already exists, the command will fail to prevent overwriting.
90
+
91
+ ### Routes File Validation
92
+
93
+ Instaserve validates routes files on startup:
94
+ - If `-api` is specified and the file doesn't exist, the server will fail to start
95
+ - The routes file must export a default object
96
+ - All route handlers must be functions
97
+ - Invalid routes files will cause the server to exit with an error message
98
+
99
+ ### Basic Route Example
100
+
101
+ ```javascript
102
+ export default {
103
+ // Handle GET /hello
104
+ hello: (req, res, data) => {
105
+ return { message: 'Hello World' }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### Special Routes (Middleware)
111
+
112
+ Routes starting with `_` are middleware functions that run on **every request** before the main route handler. They are useful for:
113
+
114
+ - Logging requests
115
+ - Authentication
116
+ - Request modification
117
+ - Response headers
118
+
119
+ Middleware functions can:
120
+ - Return `false` to continue to the next middleware or main route
121
+ - Return a truthy value to stop processing and use that as the response
122
+ - Modify the request or response objects
123
+
124
+ #### Middleware Example
125
+
126
+ ```javascript
127
+ export default {
128
+ // Log every request
129
+ _log: (req, res, data) => {
130
+ console.log(`${req.method} ${req.url}`)
131
+ return false // Continue processing
132
+ },
133
+
134
+ // Block unauthorized requests
135
+ _auth: (req, res, data) => {
136
+ if (!data.token) {
137
+ res.writeHead(401)
138
+ return 'Unauthorized'
139
+ }
140
+ return false // Continue if authorized
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Route Parameters
146
+
147
+ Each route function receives:
148
+ - `req` - The HTTP request object
149
+ - `res` - The HTTP response object
150
+ - `data` - Combined data from:
151
+ - POST body (if JSON)
152
+ - URL query parameters
153
+ - Form data
154
+
155
+ ### Example Routes File
156
+
157
+ ```javascript
158
+ // routes.js
159
+ export default {
160
+ // Middleware example
161
+ _debug: (req, res, data) => {
162
+ console.log('Request:', req.url)
163
+ return false // Continue to next route
164
+ },
165
+
166
+ // API endpoint
167
+ api: (req, res, data) => {
168
+ return { status: 'ok', data }
169
+ },
170
+
171
+ // Error handling
172
+ testerror: () => {
173
+ throw new Error('Test error')
174
+ }
175
+ }
176
+ ```
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+
3
+ echo "Generating self-signed certificates for Instaserve..."
4
+
5
+ # Create openssl config file
6
+ cat > openssl.conf << EOF
7
+ [req]
8
+ distinguished_name = req_distinguished_name
9
+ req_extensions = v3_req
10
+ prompt = no
11
+
12
+ [req_distinguished_name]
13
+ C = US
14
+ ST = State
15
+ L = City
16
+ O = Instaserve
17
+ OU = Development
18
+ CN = localhost
19
+
20
+ [v3_req]
21
+ basicConstraints = CA:FALSE
22
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment
23
+ extendedKeyUsage = serverAuth, clientAuth
24
+ subjectAltName = @alt_names
25
+
26
+ [alt_names]
27
+ DNS.1 = localhost
28
+ DNS.2 = *.localhost
29
+ IP.1 = 127.0.0.1
30
+ IP.2 = ::1
31
+ EOF
32
+
33
+ # Generate private key
34
+ openssl genrsa -out key.pem 2048
35
+
36
+ # Generate certificate with config
37
+ openssl req -new -x509 -key key.pem -out cert.pem -days 365 -config openssl.conf -extensions v3_req
38
+
39
+ # Clean up config
40
+ rm openssl.conf
41
+
42
+ echo "Certificates generated: cert.pem and key.pem"
43
+
44
+ # Add to system trust store (macOS)
45
+ if [[ "$OSTYPE" == "darwin"* ]]; then
46
+ echo "Adding certificate to macOS trust store..."
47
+ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem
48
+ echo "Certificate added to macOS trust store"
49
+ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
50
+ echo "Adding certificate to Linux trust store..."
51
+ sudo cp cert.pem /usr/local/share/ca-certificates/instaserve.crt
52
+ sudo update-ca-certificates
53
+ echo "Certificate added to Linux trust store"
54
+ else
55
+ echo "Please manually add cert.pem to your system's trust store"
56
+ fi
57
+
58
+ echo "HTTPS certificates ready! Use 'npx instaserve -secure' to enable HTTPS"
59
+ echo "Note: You may still see browser warnings for self-signed certificates"
package/instaserve ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk'
4
+ import fs from 'node:fs'
5
+
6
+ console.log(chalk.cyan('\nInstaserve - Instant Web Stack\n'))
7
+ console.log(chalk.yellow('Usage:'))
8
+ console.log(chalk.green(' npx instaserve [options]'))
9
+ console.log(chalk.green(' npx instaserve generate-routes\n'))
10
+ console.log(chalk.yellow('Commands:'))
11
+ console.log(chalk.green(' generate-routes') + ' Create a sample routes.js file in the current directory\n')
12
+ console.log(chalk.yellow('Options:'))
13
+ console.log(chalk.green(' -port <number>') + ' Port to listen on (default: 3000)')
14
+ console.log(chalk.green(' -ip <address>') + ' IP address to bind to (default: 127.0.0.1)')
15
+ console.log(chalk.green(' -public <path>') + ' Public directory path (default: ./public)')
16
+ console.log(chalk.green(' -api <file>') + ' Path to routes file (default: ./routes.js)')
17
+ console.log(chalk.green(' -secure') + ' Enable HTTPS (requires cert.pem and key.pem: run generate-certs.sh)')
18
+ console.log(chalk.green(' -help') + ' Show this help message\n')
19
+
20
+ if (process.argv.includes('-help')) {
21
+ process.exit(0)
22
+ }
23
+
24
+ const args = process.argv.slice(2)
25
+
26
+ // Handle generate-routes command
27
+ if (args[0] === 'generate-routes') {
28
+ const routesFile = './routes.js'
29
+ if (fs.existsSync(routesFile)) {
30
+ console.error(chalk.red(`Error: ${routesFile} already exists`))
31
+ process.exit(1)
32
+ }
33
+
34
+ const sampleRoutes = `export default {
35
+ // Middleware functions (prefixed with _) run on every request
36
+ // Return false to continue processing, or a value to use as response
37
+
38
+ // Example: Log all requests
39
+ _log: (req, res, data) => {
40
+ console.log(\`\${req.method} \${req.url}\`)
41
+ return false // Continue to next middleware or route
42
+ },
43
+
44
+ // Example: Basic authentication (commented out)
45
+ // _auth: (req, res, data) => {
46
+ // if (!data.token) {
47
+ // res.writeHead(401)
48
+ // return 'Unauthorized'
49
+ // }
50
+ // return false // Continue if authorized
51
+ // },
52
+
53
+ // Regular route handlers
54
+ hello: (req, res, data) => {
55
+ return { message: 'Hello World' }
56
+ },
57
+
58
+ api: (req, res, data) => {
59
+ return { message: 'API endpoint', data }
60
+ }
61
+ }
62
+ `
63
+
64
+ fs.writeFileSync(routesFile, sampleRoutes)
65
+ console.log(chalk.green(`✓ Created ${routesFile}`))
66
+ process.exit(0)
67
+ }
68
+
69
+ import server from './module.mjs'
70
+
71
+ const params = {}
72
+ for (let i = 0; i < args.length; i++) {
73
+ const arg = args[i]
74
+ if (arg.startsWith('-')) {
75
+ const key = arg.slice(1)
76
+ const nextArg = args[i + 1]
77
+ if (nextArg && !nextArg.startsWith('-')) {
78
+ params[key] = nextArg
79
+ i++ // Skip the next argument since we used it
80
+ } else {
81
+ params[key] = true // Boolean flag
82
+ }
83
+ }
84
+ }
85
+
86
+ // Load routes file
87
+ let routes = {}
88
+ const routesFile = params.api || './routes.js'
89
+ const routesFileSpecified = !!params.api
90
+
91
+ if (routesFileSpecified && !fs.existsSync(routesFile)) {
92
+ console.error(chalk.red(`Error: Routes file "${routesFile}" does not exist`))
93
+ process.exit(1)
94
+ }
95
+
96
+ if (fs.existsSync(routesFile)) {
97
+ try {
98
+ const imported = await import(routesFile)
99
+ routes = imported.default || imported
100
+
101
+ if (!routes || typeof routes !== 'object' || Array.isArray(routes)) {
102
+ console.error(chalk.red(`Error: Routes file "${routesFile}" must export a default object`))
103
+ process.exit(1)
104
+ }
105
+
106
+ for (const [key, handler] of Object.entries(routes)) {
107
+ if (typeof handler !== 'function') {
108
+ console.error(chalk.red(`Error: Route "${key}" in "${routesFile}" must be a function`))
109
+ process.exit(1)
110
+ }
111
+ }
112
+ } catch (e) {
113
+ console.error(chalk.red(`Error: Could not load routes file "${routesFile}": ${e.message}`))
114
+ process.exit(1)
115
+ }
116
+ }
117
+
118
+ server(routes)
package/module.mjs ADDED
@@ -0,0 +1,136 @@
1
+ import http from 'node:http'
2
+ import https from 'node:https'
3
+ import fs from 'node:fs'
4
+
5
+ const args = process.argv.slice(2)
6
+ const params = {}
7
+ for (let i = 0; i < args.length; i++) {
8
+ const arg = args[i]
9
+ if (arg.startsWith('-')) {
10
+ const key = arg.slice(1)
11
+ const nextArg = args[i + 1]
12
+ if (nextArg && !nextArg.startsWith('-')) {
13
+ params[key] = nextArg
14
+ i++ // Skip the next argument since we used it
15
+ } else {
16
+ params[key] = true // Boolean flag
17
+ }
18
+ }
19
+ }
20
+
21
+ function public_file(r, s) {
22
+ if (r.url == '/') r.url = '/index.html'
23
+ const fn = `${params.public || './public'}${r.url.replace(/\.\./g, '')}`
24
+ if (fs.existsSync(fn)) {
25
+ const content = fs.readFileSync(fn, 'utf-8')
26
+ if (fn.match(/.js$/)) {
27
+ s.writeHead(200, { 'Content-Type': 'application/javascript' })
28
+ } else {
29
+ s.writeHead(200)
30
+ }
31
+ s.end(content)
32
+ return true // Indicate file was served
33
+ }
34
+ return false // Indicate no file was served
35
+ }
36
+
37
+ export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1') {
38
+ const publicDir = params.public || './public'
39
+ if (publicDir.includes('..')) {
40
+ throw new Error('Public directory path cannot contain ".."')
41
+ }
42
+ if (!fs.existsSync(publicDir)) {
43
+ throw new Error(`Public directory "${publicDir}" does not exist`)
44
+ }
45
+
46
+ const requestHandler = async (r, s) => {
47
+ let sdata = '', rrurl = r.url || ''
48
+ let responseSent = false
49
+
50
+ r.on('data', (s) => sdata += s.toString().trim())
51
+ r.on('end', (x) => {
52
+ try {
53
+ // Compose data object
54
+ const data = sdata ? JSON.parse(sdata) : {}
55
+ const qs = rrurl.split('?')
56
+ if(qs && qs[1]) {
57
+ const o = JSON.parse('{"' + decodeURI(qs[1].replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}')
58
+ Object.assign(data, o)
59
+ }
60
+
61
+ const midware = Object.keys(routes)
62
+ .filter((k) => k.startsWith('_'))
63
+ .find((k) => {
64
+ const result = routes[k](r, s, data)
65
+ if (result && !responseSent) {
66
+ responseSent = true
67
+ s.end(typeof result === 'string' ? result : JSON.stringify(result))
68
+ }
69
+ return result
70
+ })
71
+
72
+ // Response closed by middleware
73
+ if(responseSent || s.writableEnded) return
74
+
75
+ // Try to serve public file
76
+ if(public_file(r, s)) {
77
+ responseSent = true
78
+ return
79
+ }
80
+
81
+ const url = rrurl.split('/')[1].split('?')[0]
82
+ if (routes[url]) {
83
+ const resp = routes[url](r, s, data)
84
+ if (!responseSent && !s.writableEnded) {
85
+ responseSent = true
86
+ s.end(typeof resp === 'string' ? resp:JSON.stringify(resp))
87
+ }
88
+ return
89
+ }
90
+
91
+ if (!responseSent && !s.writableEnded) {
92
+ responseSent = true
93
+ s.writeHead(404);
94
+ s.end();
95
+ }
96
+ } catch (e) {
97
+ console.error(e.stack)
98
+ if (!responseSent && !s.writableEnded) {
99
+ responseSent = true
100
+ s.writeHead(500).end()
101
+ }
102
+ }
103
+ })
104
+ }
105
+
106
+ let server
107
+ if (params.secure) {
108
+ const certPath = './cert.pem'
109
+ const keyPath = './key.pem'
110
+
111
+ if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
112
+ throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
113
+ }
114
+
115
+ const options = {
116
+ cert: fs.readFileSync(certPath),
117
+ key: fs.readFileSync(keyPath)
118
+ }
119
+
120
+ server = https.createServer(options, requestHandler)
121
+ } else {
122
+ server = http.createServer(requestHandler)
123
+ }
124
+
125
+ server.listen(port || 3000, ip || '')
126
+
127
+ const protocol = params.secure ? 'https' : 'http'
128
+ console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${Object.keys(routes).length > 0 ? `using routes: ${Object.keys(routes)}` : 'not using routes'}`)
129
+
130
+ return {
131
+ routes: routes,
132
+ port: port,
133
+ server: server,
134
+ stop: () => { server.close(); return true }
135
+ }
136
+ }
package/package.json CHANGED
@@ -1,5 +1,14 @@
1
- {
2
- "name": "instaserve",
3
- "version": "1.0.21",
4
- "bin": "./index.mjs"
5
- }
1
+ {
2
+ "name": "instaserve",
3
+ "version": "1.1.2",
4
+ "description": "Instant web stack",
5
+ "main": "module.mjs",
6
+ "bin": "./instaserve",
7
+ "scripts": {
8
+ "start": "node instaserve"
9
+ },
10
+ "type": "module",
11
+ "dependencies": {
12
+ "chalk": "^5.6.2"
13
+ }
14
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * This file defines the API routes and middleware for the Instaserve web server.
3
+ *
4
+ * The file exports a default object containing route handlers and middleware functions.
5
+ *
6
+ * Middleware functions (prefixed with _):
7
+ * - _debug: Logs request method, URL and data to console
8
+ * - _returnfalsy: Example middleware that returns true to stop request processing
9
+ * - _example: Demonstrates middleware behavior with console log
10
+ * - _end: Example of early response termination (commented out)
11
+ *
12
+ * Route handlers:
13
+ * - api: Returns a string response with the request data
14
+ * - testerror: Throws an error for testing error handling
15
+ * - testdata: Example route that returns the context parameter
16
+ *
17
+ * Each route handler receives:
18
+ * - req: HTTP request object
19
+ * - s: HTTP response object
20
+ * - data: Combined request data (POST body, query params, form data)
21
+ */
22
+
23
+ export default {
24
+ // Middleware functions (prefixed with _) run on every request
25
+ // Return false to continue processing, or a value to use as response
26
+
27
+ // Example: Log all requests
28
+ _log: (req, res, data) => {
29
+ console.log(`${req.method} ${req.url}`)
30
+ return false // Continue to next middleware or route
31
+ },
32
+
33
+ /**** Example: Basic authentication
34
+ _auth: (req, res, data) => {
35
+ if (!data.token) {
36
+ res.writeHead(401)
37
+ return 'Unauthorized'
38
+ }
39
+ return false // Continue if authorized
40
+ }, */
41
+
42
+ // Regular route handlers
43
+ hello: (req, res, data) => {
44
+ return { message: 'Hello World' }
45
+ },
46
+
47
+ _debug: ({method, url}, s, data) => { console.log(method, url, data) },
48
+ _example: (r, s) => console.log('in routes.mjs, returning a truthy value (above) will stop the chain'),
49
+ // _returnfalsy: (r, s) => { return true }, // Commented out - interferes with static file serving
50
+
51
+ api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
52
+ testerror: () => { throw new Error('this from testerror') },
53
+ testdata: (r, s, c, d) => c
54
+ }
package/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log(1)