create-surf-app 1.0.0-alpha.3 → 1.0.0-alpha.4
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/dist/{chunk-NZOL5GFZ.js → chunk-BV3XIPFM.js} +1 -39
- package/dist/cli.js +1 -7
- package/dist/index.js +1 -1
- package/dist/templates/default/backend/.env.example +2 -0
- package/dist/templates/default/backend/package.json +3 -3
- package/dist/templates/default/backend/scripts/check-env.js +40 -0
- package/dist/templates/default/frontend/.env.example +3 -0
- package/dist/templates/default/frontend/package.json +2 -2
- package/dist/templates/default/frontend/scripts/check-env.cjs +51 -0
- package/dist/templates/default/frontend/vite.config.ts +6 -22
- package/dist/templates/nextjs/.env.example +3 -0
- package/dist/templates/nextjs/package.json +2 -2
- package/dist/templates/nextjs/scripts/check-env.js +12 -5
- package/package.json +1 -1
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
import os from "os";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
6
|
-
var DEFAULT_BACKEND_PORT = "3001";
|
|
7
5
|
var VALID_TEMPLATES = ["vite", "nextjs"];
|
|
8
6
|
async function createSurfApp({
|
|
9
7
|
projectName = ".",
|
|
10
|
-
backendPort = process.env.BACKEND_PORT || DEFAULT_BACKEND_PORT,
|
|
11
|
-
previewBase = process.env.BASE_PATH,
|
|
12
8
|
template: templateArg,
|
|
13
9
|
logger = console.log
|
|
14
10
|
} = {}) {
|
|
15
11
|
const root = path.resolve(projectName);
|
|
16
12
|
const name = path.basename(root);
|
|
17
13
|
const template = validateTemplate(templateArg);
|
|
18
|
-
const validatedBackendPort = validatePort("backend", backendPort);
|
|
19
14
|
const templateDir = resolveTemplateDir(template);
|
|
20
15
|
logger(`
|
|
21
16
|
Creating Surf app (${template}) in ${root}
|
|
@@ -23,7 +18,6 @@ async function createSurfApp({
|
|
|
23
18
|
fs.mkdirSync(root, { recursive: true });
|
|
24
19
|
copyDir(templateDir, root, root, logger);
|
|
25
20
|
if (template === "nextjs") {
|
|
26
|
-
writeNextjsEnvFile(root, validatedBackendPort, previewBase);
|
|
27
21
|
finalizePackageName(root, name);
|
|
28
22
|
logger(`
|
|
29
23
|
Done! Next steps:
|
|
@@ -35,7 +29,6 @@ Done! Next steps:
|
|
|
35
29
|
Open http://localhost:3000
|
|
36
30
|
`);
|
|
37
31
|
} else {
|
|
38
|
-
writeEnvFiles(root, validatedBackendPort, previewBase);
|
|
39
32
|
logger(`
|
|
40
33
|
Done! Next steps:
|
|
41
34
|
|
|
@@ -87,43 +80,12 @@ function copyDir(src, dest, root, logger) {
|
|
|
87
80
|
logger(` ${path.relative(root, destPath)}`);
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
|
-
function validatePort(label, value) {
|
|
91
|
-
const port = Number.parseInt(value, 10);
|
|
92
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
93
|
-
throw new Error(`Invalid ${label} port: ${value}`);
|
|
94
|
-
}
|
|
95
|
-
return String(port);
|
|
96
|
-
}
|
|
97
83
|
function finalizePackageName(root, projectName) {
|
|
98
84
|
const pkgPath = path.join(root, "package.json");
|
|
99
85
|
if (!fs.existsSync(pkgPath)) return;
|
|
100
86
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
101
87
|
pkg.name = projectName.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "surf-app";
|
|
102
|
-
fs.writeFileSync(pkgPath,
|
|
103
|
-
}
|
|
104
|
-
function writeNextjsEnvFile(root, port, basePath) {
|
|
105
|
-
const envPath = path.join(root, ".env");
|
|
106
|
-
const envContent = [
|
|
107
|
-
`FRONTEND_PORT=${port}`,
|
|
108
|
-
`BASE_PATH=${basePath || ""}`,
|
|
109
|
-
"SURF_API_KEY="
|
|
110
|
-
].join(os.EOL);
|
|
111
|
-
fs.writeFileSync(envPath, `${envContent}${os.EOL}`);
|
|
112
|
-
}
|
|
113
|
-
function writeEnvFiles(root, backendPort, previewBase) {
|
|
114
|
-
const backendEnvPath = path.join(root, "backend", ".env");
|
|
115
|
-
const frontendEnvPath = path.join(root, "frontend", ".env");
|
|
116
|
-
const backendEnv = [
|
|
117
|
-
`BACKEND_PORT=${backendPort}`,
|
|
118
|
-
"SURF_API_KEY="
|
|
119
|
-
].join(os.EOL);
|
|
120
|
-
fs.writeFileSync(backendEnvPath, `${backendEnv}${os.EOL}`);
|
|
121
|
-
const frontendEnv = [
|
|
122
|
-
"FRONTEND_PORT=5173",
|
|
123
|
-
`BACKEND_PORT=${backendPort}`,
|
|
124
|
-
`BASE_PATH=${previewBase || ""}`
|
|
125
|
-
].join(os.EOL);
|
|
126
|
-
fs.writeFileSync(frontendEnvPath, `${frontendEnv}${os.EOL}`);
|
|
88
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
127
89
|
}
|
|
128
90
|
|
|
129
91
|
export {
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createSurfApp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BV3XIPFM.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
8
|
-
"--frontend-port",
|
|
9
|
-
"--backend-port",
|
|
10
|
-
"--preview-base",
|
|
11
8
|
"--template"
|
|
12
9
|
]);
|
|
13
10
|
function getFlag(args, name) {
|
|
@@ -35,9 +32,6 @@ function parseCliArgs(args) {
|
|
|
35
32
|
}
|
|
36
33
|
return {
|
|
37
34
|
projectName: positionalArgs[0] || ".",
|
|
38
|
-
frontendPort: getFlag(args, "--frontend-port"),
|
|
39
|
-
backendPort: getFlag(args, "--backend-port"),
|
|
40
|
-
previewBase: getFlag(args, "--preview-base"),
|
|
41
35
|
template: getFlag(args, "--template")
|
|
42
36
|
};
|
|
43
37
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"name": "backend",
|
|
3
3
|
"private": true,
|
|
4
4
|
"scripts": {
|
|
5
|
-
"start": "node
|
|
6
|
-
"dev": "node
|
|
5
|
+
"start": "node scripts/check-env.js node server.js",
|
|
6
|
+
"dev": "node scripts/check-env.js node --watch server.js"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@surf-ai/sdk": "1.0.0-alpha.
|
|
9
|
+
"@surf-ai/sdk": "1.0.0-alpha.4",
|
|
10
10
|
"express": "4.22.1"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates required env vars before running a command.
|
|
3
|
+
* Loads .env if it exists (optional convenience), then checks vars.
|
|
4
|
+
*
|
|
5
|
+
* Dev/start: BACKEND_PORT, SURF_API_KEY
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('node:fs')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
const { execSync } = require('node:child_process')
|
|
10
|
+
|
|
11
|
+
// Load .env if it exists (convenience — env vars can come from anywhere)
|
|
12
|
+
const envPath = path.join(process.cwd(), '.env')
|
|
13
|
+
if (fs.existsSync(envPath)) {
|
|
14
|
+
for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
|
|
15
|
+
const trimmed = line.trim()
|
|
16
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
17
|
+
const eq = trimmed.indexOf('=')
|
|
18
|
+
if (eq < 0) continue
|
|
19
|
+
const key = trimmed.slice(0, eq)
|
|
20
|
+
const val = trimmed.slice(eq + 1)
|
|
21
|
+
if (!process.env[key]) process.env[key] = val
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2)
|
|
26
|
+
|
|
27
|
+
const required = ['BACKEND_PORT', 'SURF_API_KEY']
|
|
28
|
+
const missing = required.filter(k => !process.env[k])
|
|
29
|
+
|
|
30
|
+
if (missing.length > 0) {
|
|
31
|
+
console.error(`\n❌ Missing required env vars: ${missing.join(', ')}`)
|
|
32
|
+
console.error(` Set them in your environment or copy .env.example to .env\n`)
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
execSync(args.join(' '), { stdio: 'inherit', env: process.env })
|
|
38
|
+
} catch (e) {
|
|
39
|
+
process.exit(e.status || 1)
|
|
40
|
+
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "vite",
|
|
8
|
-
"build": "npm run build:client && npm run build:server",
|
|
7
|
+
"dev": "node scripts/check-env.cjs vite",
|
|
8
|
+
"build": "node scripts/check-env.cjs npm run build:client && npm run build:server",
|
|
9
9
|
"build:client": "vite build --outDir dist/client",
|
|
10
10
|
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
|
|
11
11
|
"lint": "eslint .",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates required env vars before running a command.
|
|
3
|
+
* Loads .env manually, checks vars, then execs the actual command.
|
|
4
|
+
*
|
|
5
|
+
* Build: BASE_PATH (defined, can be empty), BACKEND_PORT
|
|
6
|
+
* Dev: FRONTEND_PORT, BACKEND_PORT, BASE_PATH
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('node:fs')
|
|
9
|
+
const path = require('node:path')
|
|
10
|
+
const { execSync } = require('node:child_process')
|
|
11
|
+
|
|
12
|
+
// Load .env manually (Vite doesn't load non-VITE_ vars into process.env)
|
|
13
|
+
const envPath = path.join(process.cwd(), '.env')
|
|
14
|
+
if (fs.existsSync(envPath)) {
|
|
15
|
+
for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
|
|
16
|
+
const trimmed = line.trim()
|
|
17
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
18
|
+
const eq = trimmed.indexOf('=')
|
|
19
|
+
if (eq < 0) continue
|
|
20
|
+
const key = trimmed.slice(0, eq)
|
|
21
|
+
const val = trimmed.slice(eq + 1)
|
|
22
|
+
if (!process.env[key]) process.env[key] = val
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const args = process.argv.slice(2)
|
|
27
|
+
const isBuild = args.some(a => a.includes('build'))
|
|
28
|
+
|
|
29
|
+
// Vars that must be non-empty
|
|
30
|
+
const requiredNonEmpty = isBuild
|
|
31
|
+
? ['BACKEND_PORT']
|
|
32
|
+
: ['FRONTEND_PORT', 'BACKEND_PORT']
|
|
33
|
+
|
|
34
|
+
// Vars that must be defined (empty is ok — BASE_PATH="" means root)
|
|
35
|
+
const requiredDefined = ['BASE_PATH']
|
|
36
|
+
|
|
37
|
+
const missingNonEmpty = requiredNonEmpty.filter(k => !process.env[k])
|
|
38
|
+
const missingDefined = requiredDefined.filter(k => process.env[k] === undefined)
|
|
39
|
+
const missing = [...missingNonEmpty, ...missingDefined]
|
|
40
|
+
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
console.error(`\n❌ Missing required env vars: ${missing.join(', ')}`)
|
|
43
|
+
console.error(` Set them in your environment or copy .env.example to .env\n`)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
execSync(args.join(' '), { stdio: 'inherit', env: process.env })
|
|
49
|
+
} catch (e) {
|
|
50
|
+
process.exit(e.status || 1)
|
|
51
|
+
}
|
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import { defineConfig
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
3
|
import react from '@vitejs/plugin-react'
|
|
4
4
|
import tailwindcss from '@tailwindcss/vite'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
env
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const value = env[name]
|
|
11
|
-
const port = Number.parseInt(value || '', 10)
|
|
12
|
-
if (!Number.isInteger(port)) {
|
|
13
|
-
throw new Error(`Missing required ${name} in frontend/.env`)
|
|
14
|
-
}
|
|
15
|
-
return port
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default defineConfig(({ mode }) => {
|
|
19
|
-
const env = loadEnv(mode, process.cwd(), '')
|
|
20
|
-
const frontendPort = readRequiredPort(env, 'FRONTEND_PORT')
|
|
21
|
-
const backendPort = readRequiredPort(env, 'BACKEND_PORT')
|
|
22
|
-
const base = env.BASE_PATH || './'
|
|
6
|
+
export default defineConfig(() => {
|
|
7
|
+
const frontendPort = Number.parseInt(process.env.FRONTEND_PORT || '', 10)
|
|
8
|
+
const backendPort = Number.parseInt(process.env.BACKEND_PORT || '', 10)
|
|
9
|
+
const base = process.env.BASE_PATH || './'
|
|
23
10
|
const hasAbsBase = base.startsWith('/')
|
|
24
11
|
const apiBasePrefix = hasAbsBase ? base.replace(/\/$/, '') : ''
|
|
25
12
|
|
|
@@ -35,11 +22,10 @@ export default defineConfig(({ mode }) => {
|
|
|
35
22
|
plugins: [react(), tailwindcss()],
|
|
36
23
|
server: {
|
|
37
24
|
host: '0.0.0.0',
|
|
38
|
-
port: frontendPort,
|
|
25
|
+
port: frontendPort || undefined,
|
|
39
26
|
proxy: {
|
|
40
27
|
[`${apiBasePrefix}/api`]: backendProxy,
|
|
41
28
|
},
|
|
42
|
-
// Keep the HMR socket under the preview base path.
|
|
43
29
|
hmr: {
|
|
44
30
|
path: 'ws/vite-hmr',
|
|
45
31
|
},
|
|
@@ -51,8 +37,6 @@ export default defineConfig(({ mode }) => {
|
|
|
51
37
|
dedupe: ['react', 'react-dom'],
|
|
52
38
|
preserveSymlinks: true,
|
|
53
39
|
},
|
|
54
|
-
// Pre-bundle the deps touched during the initial boot path so cold starts
|
|
55
|
-
// do not race Vite's lazy dependency optimizer.
|
|
56
40
|
optimizeDeps: {
|
|
57
41
|
include: [
|
|
58
42
|
'react',
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"type-check": "tsc --noEmit --incremental"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@surf-ai/sdk": "1.0.0-alpha.
|
|
12
|
+
"@surf-ai/sdk": "1.0.0-alpha.4",
|
|
13
13
|
"@surf-ai/theme": "latest",
|
|
14
|
-
"next": "15.
|
|
14
|
+
"next": "15.5.14",
|
|
15
15
|
"react": "19.2.4",
|
|
16
16
|
"react-dom": "19.2.4",
|
|
17
17
|
"drizzle-orm": "0.44.2",
|
|
@@ -26,14 +26,21 @@ if (fs.existsSync(envPath)) {
|
|
|
26
26
|
const args = process.argv.slice(2)
|
|
27
27
|
const isBuild = args.includes('build')
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Vars that must be non-empty
|
|
30
|
+
const requiredNonEmpty = isBuild
|
|
31
|
+
? []
|
|
32
|
+
: ['FRONTEND_PORT', 'SURF_API_KEY']
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
// Vars that must be defined (empty is ok — e.g. BASE_PATH="" means root)
|
|
35
|
+
const requiredDefined = ['BASE_PATH']
|
|
36
|
+
|
|
37
|
+
const missingNonEmpty = requiredNonEmpty.filter(k => !process.env[k])
|
|
38
|
+
const missingDefined = requiredDefined.filter(k => process.env[k] === undefined)
|
|
39
|
+
const missing = [...missingNonEmpty, ...missingDefined]
|
|
34
40
|
|
|
35
41
|
if (missing.length > 0) {
|
|
36
|
-
console.error(`\n❌ Missing required env vars
|
|
42
|
+
console.error(`\n❌ Missing required env vars: ${missing.join(', ')}`)
|
|
43
|
+
console.error(` Set them in your environment or copy .env.example to .env\n`)
|
|
37
44
|
process.exit(1)
|
|
38
45
|
}
|
|
39
46
|
|