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.
@@ -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, `${JSON.stringify(pkg, null, 2)}${os.EOL}`);
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-NZOL5GFZ.js";
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSurfApp
3
- } from "./chunk-NZOL5GFZ.js";
3
+ } from "./chunk-BV3XIPFM.js";
4
4
  export {
5
5
  createSurfApp
6
6
  };
@@ -0,0 +1,2 @@
1
+ BACKEND_PORT=3001
2
+ SURF_API_KEY=
@@ -2,11 +2,11 @@
2
2
  "name": "backend",
3
3
  "private": true,
4
4
  "scripts": {
5
- "start": "node --env-file=.env server.js",
6
- "dev": "node --env-file=.env --watch server.js"
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.3",
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
+ }
@@ -0,0 +1,3 @@
1
+ FRONTEND_PORT=5173
2
+ BACKEND_PORT=3001
3
+ BASE_PATH=
@@ -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, loadEnv } from 'vite'
2
+ import { defineConfig } from 'vite'
3
3
  import react from '@vitejs/plugin-react'
4
4
  import tailwindcss from '@tailwindcss/vite'
5
5
 
6
- function readRequiredPort(
7
- env: Record<string, string>,
8
- name: 'BACKEND_PORT' | 'FRONTEND_PORT',
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',
@@ -0,0 +1,3 @@
1
+ FRONTEND_PORT=3000
2
+ BASE_PATH=
3
+ SURF_API_KEY=
@@ -9,9 +9,9 @@
9
9
  "type-check": "tsc --noEmit --incremental"
10
10
  },
11
11
  "dependencies": {
12
- "@surf-ai/sdk": "1.0.0-alpha.3",
12
+ "@surf-ai/sdk": "1.0.0-alpha.4",
13
13
  "@surf-ai/theme": "latest",
14
- "next": "15.3.3",
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
- const required = isBuild
30
- ? ['BASE_PATH', 'SURF_API_KEY']
31
- : ['FRONTEND_PORT', 'BASE_PATH', 'SURF_API_KEY']
29
+ // Vars that must be non-empty
30
+ const requiredNonEmpty = isBuild
31
+ ? []
32
+ : ['FRONTEND_PORT', 'SURF_API_KEY']
32
33
 
33
- const missing = required.filter(k => !process.env[k])
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 in .env: ${missing.join(', ')}\n`)
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-surf-app",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Scaffold a Surf app — Vite + React + Express + @surf-ai/sdk",
5
5
  "type": "module",
6
6
  "bin": {