@superinterface/server 1.0.0 → 1.0.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/bin/index.cjs +9 -0
- package/package.json +15 -12
- package/scripts/cli.ts +45 -19
- package/scripts/commands/run/server.ts +146 -0
- package/scripts/utils/env.ts +15 -5
package/bin/index.cjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superinterface/server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"bin": {
|
|
5
|
+
"superinterface-server": "./bin/index.cjs"
|
|
6
|
+
},
|
|
4
7
|
"exports": {
|
|
5
8
|
"./prisma/enums/*": "./prisma/enums/*",
|
|
6
9
|
"./prisma/*": "./prisma/*",
|
|
@@ -131,6 +134,7 @@
|
|
|
131
134
|
},
|
|
132
135
|
"dependencies": {
|
|
133
136
|
"@anthropic-ai/sdk": "^0.65.0",
|
|
137
|
+
"@clack/prompts": "^0.11.0",
|
|
134
138
|
"@mendable/firecrawl-js": "^1.22.0",
|
|
135
139
|
"@mistralai/mistralai": "^1.10.0",
|
|
136
140
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
@@ -141,18 +145,22 @@
|
|
|
141
145
|
"@upstash/redis": "^1.35.5",
|
|
142
146
|
"@upstash/workflow": "^0.2.20",
|
|
143
147
|
"@vercel/functions": "^3.1.1",
|
|
148
|
+
"commander": "^14.0.1",
|
|
144
149
|
"dayjs": "^1.11.18",
|
|
150
|
+
"dotenv": "^16.4.5",
|
|
145
151
|
"groq-sdk": "^0.33.0",
|
|
146
152
|
"p-iteration": "^1.1.8",
|
|
147
153
|
"prisma-json-types-generator": "^3.6.2",
|
|
148
154
|
"replicate": "^1.2.0",
|
|
149
155
|
"supercompat": "^3.8.0",
|
|
150
|
-
"
|
|
156
|
+
"tsx": "^4.11.0",
|
|
157
|
+
"uuid": "^13.0.0",
|
|
158
|
+
"zod": "^3.23.8"
|
|
151
159
|
},
|
|
152
160
|
"peerDependencies": {
|
|
153
161
|
"next": "15.5.4",
|
|
154
|
-
"react": "19.1.
|
|
155
|
-
"react-dom": "19.1.
|
|
162
|
+
"react": "^19.1.0",
|
|
163
|
+
"react-dom": "^19.1.0"
|
|
156
164
|
},
|
|
157
165
|
"devDependencies": {
|
|
158
166
|
"@eslint/eslintrc": "^3",
|
|
@@ -163,14 +171,9 @@
|
|
|
163
171
|
"eslint-config-next": "15.5.4",
|
|
164
172
|
"next": "15.5.4",
|
|
165
173
|
"prisma": "^6.16.3",
|
|
166
|
-
"react": "19.1.
|
|
167
|
-
"react-dom": "19.1.
|
|
174
|
+
"react": "^19.1.0",
|
|
175
|
+
"react-dom": "^19.1.0",
|
|
168
176
|
"tsc-alias": "^1.8.12",
|
|
169
|
-
"typescript": "^5"
|
|
170
|
-
"@clack/prompts": "^0.11.0",
|
|
171
|
-
"dotenv": "^16.4.5",
|
|
172
|
-
"tsx": "^4.11.0",
|
|
173
|
-
"zod": "^3.23.8",
|
|
174
|
-
"commander": "^14.0.1"
|
|
177
|
+
"typescript": "^5"
|
|
175
178
|
}
|
|
176
179
|
}
|
package/scripts/cli.ts
CHANGED
|
@@ -4,16 +4,29 @@ import type { PrismaClient } from '@prisma/client'
|
|
|
4
4
|
import { Command } from 'commander'
|
|
5
5
|
import { createOrganization } from './commands/organizations/create'
|
|
6
6
|
import { createOrganizationApiKey } from './commands/organizations/api-keys/create'
|
|
7
|
+
import { runServer } from './commands/run/server'
|
|
7
8
|
import { CliError } from './utils/errors'
|
|
8
9
|
import { ensureEnv, ensureDatabaseUrl } from './utils/env'
|
|
9
10
|
|
|
10
11
|
const program = new Command()
|
|
11
|
-
.name('superinterface
|
|
12
|
+
.name('@superinterface/server')
|
|
12
13
|
.description('Administrative helpers for @superinterface/server')
|
|
13
14
|
.showHelpAfterError('(add --help for additional information)')
|
|
14
15
|
|
|
15
16
|
type ActionOptions<Options> = Options & { prisma: PrismaClient }
|
|
16
17
|
|
|
18
|
+
const handleCliError = (error: unknown) => {
|
|
19
|
+
if (error instanceof CliError) {
|
|
20
|
+
console.error(error.message)
|
|
21
|
+
if (error.cause) {
|
|
22
|
+
console.error(error.cause)
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
console.error(error)
|
|
26
|
+
}
|
|
27
|
+
process.exitCode = 1
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
const withAction = <Options extends Record<string, unknown>>(
|
|
18
31
|
action: (options: ActionOptions<Options>) => Promise<void>,
|
|
19
32
|
) => {
|
|
@@ -31,15 +44,7 @@ const withAction = <Options extends Record<string, unknown>>(
|
|
|
31
44
|
|
|
32
45
|
await action(actionOptions)
|
|
33
46
|
} catch (error) {
|
|
34
|
-
|
|
35
|
-
console.error(error.message)
|
|
36
|
-
if (error.cause) {
|
|
37
|
-
console.error(error.cause)
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
console.error(error)
|
|
41
|
-
}
|
|
42
|
-
process.exitCode = 1
|
|
47
|
+
handleCliError(error)
|
|
43
48
|
} finally {
|
|
44
49
|
await prisma?.$disconnect()
|
|
45
50
|
}
|
|
@@ -67,18 +72,39 @@ organizationApiKeys
|
|
|
67
72
|
.option('-n, --name <name>', 'API key display name')
|
|
68
73
|
.action(withAction(createOrganizationApiKey))
|
|
69
74
|
|
|
75
|
+
const run = program.command('run').description('Run local services')
|
|
76
|
+
|
|
77
|
+
run
|
|
78
|
+
.command('server')
|
|
79
|
+
.description('Start the server runtime')
|
|
80
|
+
.option('-r, --runtime <runtime>', 'Runtime driver (default: next)', 'next')
|
|
81
|
+
.option('-p, --port <port>', 'Port to listen on')
|
|
82
|
+
.action(async (options: { runtime?: string; port?: string }) => {
|
|
83
|
+
try {
|
|
84
|
+
const portOption = options.port
|
|
85
|
+
let parsedPort: number | undefined
|
|
86
|
+
|
|
87
|
+
if (typeof portOption === 'string') {
|
|
88
|
+
const port = Number.parseInt(portOption, 10)
|
|
89
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
90
|
+
throw new CliError('Port must be a positive integer.')
|
|
91
|
+
}
|
|
92
|
+
parsedPort = port
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await runServer({
|
|
96
|
+
runtime: options.runtime,
|
|
97
|
+
port: parsedPort,
|
|
98
|
+
})
|
|
99
|
+
} catch (error) {
|
|
100
|
+
handleCliError(error)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
70
104
|
if (process.argv.length <= 2) {
|
|
71
105
|
program.outputHelp()
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
program.parseAsync(process.argv).catch((error) => {
|
|
75
|
-
|
|
76
|
-
console.error(error.message)
|
|
77
|
-
if (error.cause) {
|
|
78
|
-
console.error(error.cause)
|
|
79
|
-
}
|
|
80
|
-
} else {
|
|
81
|
-
console.error(error)
|
|
82
|
-
}
|
|
83
|
-
process.exitCode = 1
|
|
109
|
+
handleCliError(error)
|
|
84
110
|
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { CliError } from '../../utils/errors'
|
|
8
|
+
import { ensureDatabaseUrl, ensureEnv } from '../../utils/env'
|
|
9
|
+
|
|
10
|
+
const supportedRuntimes = ['next'] as const
|
|
11
|
+
|
|
12
|
+
type SupportedRuntime = (typeof supportedRuntimes)[number]
|
|
13
|
+
|
|
14
|
+
export type RunServerOptions = {
|
|
15
|
+
runtime?: string
|
|
16
|
+
port?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isSupportedRuntime = (runtime: string): runtime is SupportedRuntime => {
|
|
20
|
+
return supportedRuntimes.includes(runtime as SupportedRuntime)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const runServer = async ({
|
|
24
|
+
runtime = 'next',
|
|
25
|
+
port,
|
|
26
|
+
}: RunServerOptions) => {
|
|
27
|
+
const normalizedRuntime = runtime.toLowerCase()
|
|
28
|
+
|
|
29
|
+
if (!isSupportedRuntime(normalizedRuntime)) {
|
|
30
|
+
throw new CliError(`Unsupported runtime "${runtime}".`, {
|
|
31
|
+
cause: `Supported runtimes: ${supportedRuntimes.join(', ')}`,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await ensureEnv()
|
|
36
|
+
ensureDatabaseUrl()
|
|
37
|
+
|
|
38
|
+
switch (normalizedRuntime) {
|
|
39
|
+
case 'next':
|
|
40
|
+
await runNextRuntime({ port })
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type RunCommandOptions = {
|
|
46
|
+
label: string
|
|
47
|
+
cwd: string
|
|
48
|
+
env?: NodeJS.ProcessEnv
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const runCommand = async (
|
|
52
|
+
command: string,
|
|
53
|
+
args: string[],
|
|
54
|
+
{ label, cwd, env }: RunCommandOptions,
|
|
55
|
+
) => {
|
|
56
|
+
await new Promise<void>((resolve, reject) => {
|
|
57
|
+
const child = spawn(command, args, {
|
|
58
|
+
cwd,
|
|
59
|
+
stdio: 'inherit',
|
|
60
|
+
env,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const forwardSignal = (signal: NodeJS.Signals) => {
|
|
64
|
+
if (!child.killed) {
|
|
65
|
+
child.kill(signal)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const onSigint = () => forwardSignal('SIGINT')
|
|
70
|
+
const onSigterm = () => forwardSignal('SIGTERM')
|
|
71
|
+
|
|
72
|
+
const cleanup = () => {
|
|
73
|
+
process.off('SIGINT', onSigint)
|
|
74
|
+
process.off('SIGTERM', onSigterm)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.on('SIGINT', onSigint)
|
|
78
|
+
process.on('SIGTERM', onSigterm)
|
|
79
|
+
|
|
80
|
+
child.once('error', (error) => {
|
|
81
|
+
cleanup()
|
|
82
|
+
reject(new CliError(`${label} failed to start.`, { cause: error }))
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
child.once('exit', (code, signal) => {
|
|
86
|
+
cleanup()
|
|
87
|
+
|
|
88
|
+
if (signal) {
|
|
89
|
+
process.kill(process.pid, signal)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof code === 'number' && code !== 0) {
|
|
94
|
+
reject(new CliError(`${label} exited with code ${code}.`))
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
resolve()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type RunNextRuntimeOptions = {
|
|
104
|
+
port?: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const runNextRuntime = async ({ port }: RunNextRuntimeOptions) => {
|
|
108
|
+
const packageRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
|
109
|
+
const require = createRequire(import.meta.url)
|
|
110
|
+
|
|
111
|
+
let nextBinPath: string
|
|
112
|
+
try {
|
|
113
|
+
const nextPackageJson = require.resolve('next/package.json', {
|
|
114
|
+
paths: [packageRoot],
|
|
115
|
+
})
|
|
116
|
+
nextBinPath = path.resolve(path.dirname(nextPackageJson), 'dist/bin/next')
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new CliError('Next runtime is not installed or cannot be resolved.', {
|
|
119
|
+
cause: error,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const environment = {
|
|
124
|
+
...process.env,
|
|
125
|
+
NODE_ENV: process.env.NODE_ENV ?? 'production',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const buildIdPath = path.join(packageRoot, '.next', 'BUILD_ID')
|
|
129
|
+
if (!fs.existsSync(buildIdPath)) {
|
|
130
|
+
throw new CliError(
|
|
131
|
+
'No existing Next build found. Run `next build` before starting.',
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const startArgs = ['start']
|
|
136
|
+
|
|
137
|
+
if (typeof port === 'number') {
|
|
138
|
+
startArgs.push('-p', String(port))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await runCommand(process.execPath, [nextBinPath, ...startArgs], {
|
|
142
|
+
label: 'Next runtime',
|
|
143
|
+
cwd: packageRoot,
|
|
144
|
+
env: environment,
|
|
145
|
+
})
|
|
146
|
+
}
|
package/scripts/utils/env.ts
CHANGED
|
@@ -10,15 +10,25 @@ export const ensureEnv = async () => {
|
|
|
10
10
|
if (envLoaded) return
|
|
11
11
|
|
|
12
12
|
const proc = process as ProcessWithLoadEnvFile
|
|
13
|
+
let handled = false
|
|
13
14
|
|
|
14
15
|
if (typeof proc.loadEnvFile === 'function') {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
try {
|
|
17
|
+
proc.loadEnvFile()
|
|
18
|
+
handled = true
|
|
19
|
+
} catch (error) {
|
|
20
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code
|
|
21
|
+
if (code && code !== 'ENOENT') {
|
|
22
|
+
throw error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!handled) {
|
|
28
|
+
const dotenv = await import('dotenv')
|
|
29
|
+
dotenv.config({ path: '.env' })
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
const dotenv = await import('dotenv')
|
|
21
|
-
dotenv.config({ path: '.env' })
|
|
22
32
|
envLoaded = true
|
|
23
33
|
}
|
|
24
34
|
|