@tothalex/nulljs 0.0.48 → 0.0.53
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/package.json +22 -32
- package/src/cli.ts +24 -0
- package/src/commands/config.ts +130 -0
- package/src/commands/deploy.ts +182 -123
- package/src/commands/dev.ts +10 -0
- package/src/commands/host.ts +130 -139
- package/src/commands/index.ts +6 -8
- package/src/commands/secret.ts +364 -56
- package/src/commands/status.ts +41 -0
- package/src/components/DeployAnimation.tsx +92 -0
- package/src/components/DeploymentLogsPane.tsx +79 -0
- package/src/components/Header.tsx +57 -0
- package/src/components/HelpModal.tsx +64 -0
- package/src/components/SystemLogsPane.tsx +78 -0
- package/src/config/index.ts +181 -0
- package/src/lib/bundle/function.ts +125 -0
- package/src/lib/bundle/index.ts +3 -0
- package/src/lib/bundle/react.ts +149 -0
- package/src/lib/deploy.ts +103 -0
- package/src/lib/server.ts +160 -0
- package/src/lib/vite.ts +120 -0
- package/src/lib/watcher.ts +274 -0
- package/src/ui.tsx +363 -0
- package/tsconfig.json +30 -0
- package/scripts/install-server.js +0 -199
- package/src/commands/api.ts +0 -16
- package/src/commands/auth.ts +0 -54
- package/src/commands/create.ts +0 -43
- package/src/commands/dev/function/index.ts +0 -221
- package/src/commands/dev/function/utils.ts +0 -99
- package/src/commands/dev/index.tsx +0 -126
- package/src/commands/dev/logging-manager.ts +0 -87
- package/src/commands/dev/server/index.ts +0 -48
- package/src/commands/dev/server/utils.ts +0 -37
- package/src/commands/dev/ui/components/scroll-area.tsx +0 -141
- package/src/commands/dev/ui/components/tab-bar.tsx +0 -67
- package/src/commands/dev/ui/index.tsx +0 -71
- package/src/commands/dev/ui/logging-context.tsx +0 -76
- package/src/commands/dev/ui/tabs/functions-tab.tsx +0 -35
- package/src/commands/dev/ui/tabs/server-tab.tsx +0 -36
- package/src/commands/dev/ui/tabs/vite-tab.tsx +0 -35
- package/src/commands/dev/ui/use-logging.tsx +0 -34
- package/src/commands/dev/vite/index.ts +0 -54
- package/src/commands/dev/vite/utils.ts +0 -71
- package/src/commands/profile.ts +0 -189
- package/src/index.ts +0 -346
- package/src/lib/api.ts +0 -189
- package/src/lib/bundle/function/index.ts +0 -46
- package/src/lib/bundle/react/index.ts +0 -2
- package/src/lib/bundle/react/spa.ts +0 -77
- package/src/lib/bundle/react/ssr/client.ts +0 -93
- package/src/lib/bundle/react/ssr/config.ts +0 -77
- package/src/lib/bundle/react/ssr/index.ts +0 -4
- package/src/lib/bundle/react/ssr/props.ts +0 -71
- package/src/lib/bundle/react/ssr/server.ts +0 -83
- package/src/lib/config.ts +0 -347
- package/src/lib/deployment.ts +0 -244
- package/src/lib/update-server.ts +0 -262
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs'
|
|
2
|
-
import { resolve } from 'path'
|
|
3
|
-
import chokidar from 'chokidar'
|
|
4
|
-
import chalk from 'chalk'
|
|
5
|
-
import ts from 'typescript'
|
|
6
|
-
|
|
7
|
-
import { deployFunctionAndLog } from './utils'
|
|
8
|
-
|
|
9
|
-
// Track dependencies between files
|
|
10
|
-
const dependencyGraph = new Map<string, Set<string>>()
|
|
11
|
-
const reverseDependencyGraph = new Map<string, Set<string>>()
|
|
12
|
-
|
|
13
|
-
function analyzeDependencies(filePath: string): Set<string> {
|
|
14
|
-
const dependencies = new Set<string>()
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const fs = require('fs')
|
|
18
|
-
const path = require('path')
|
|
19
|
-
|
|
20
|
-
const sourceCode = fs.readFileSync(filePath, 'utf-8')
|
|
21
|
-
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true)
|
|
22
|
-
|
|
23
|
-
function visit(node: ts.Node) {
|
|
24
|
-
if (
|
|
25
|
-
ts.isImportDeclaration(node) &&
|
|
26
|
-
node.moduleSpecifier &&
|
|
27
|
-
ts.isStringLiteral(node.moduleSpecifier)
|
|
28
|
-
) {
|
|
29
|
-
const importPath = node.moduleSpecifier.text
|
|
30
|
-
|
|
31
|
-
// Resolve relative imports
|
|
32
|
-
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
33
|
-
const resolvedPath = path.resolve(path.dirname(filePath), importPath)
|
|
34
|
-
|
|
35
|
-
// Try different extensions
|
|
36
|
-
const extensions = ['.ts', '.js', '.tsx', '.jsx']
|
|
37
|
-
for (const ext of extensions) {
|
|
38
|
-
const fullPath = resolvedPath + ext
|
|
39
|
-
if (fs.existsSync(fullPath)) {
|
|
40
|
-
dependencies.add(fullPath)
|
|
41
|
-
break
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Also try index files
|
|
46
|
-
const indexPath = path.join(resolvedPath, 'index.ts')
|
|
47
|
-
if (fs.existsSync(indexPath)) {
|
|
48
|
-
dependencies.add(indexPath)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
ts.forEachChild(node, visit)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
visit(sourceFile)
|
|
57
|
-
} catch {
|
|
58
|
-
// Silent fallback - dependency tracking is nice-to-have
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return dependencies
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function updateDependencyGraph(filePath: string) {
|
|
65
|
-
// Remove old dependencies
|
|
66
|
-
const oldDeps = dependencyGraph.get(filePath) || new Set()
|
|
67
|
-
oldDeps.forEach((dep) => {
|
|
68
|
-
const reverseDeps = reverseDependencyGraph.get(dep) || new Set()
|
|
69
|
-
reverseDeps.delete(filePath)
|
|
70
|
-
if (reverseDeps.size === 0) {
|
|
71
|
-
reverseDependencyGraph.delete(dep)
|
|
72
|
-
} else {
|
|
73
|
-
reverseDependencyGraph.set(dep, reverseDeps)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Add new dependencies
|
|
78
|
-
const newDeps = analyzeDependencies(filePath)
|
|
79
|
-
dependencyGraph.set(filePath, newDeps)
|
|
80
|
-
|
|
81
|
-
newDeps.forEach((dep) => {
|
|
82
|
-
const reverseDeps = reverseDependencyGraph.get(dep) || new Set()
|
|
83
|
-
reverseDeps.add(filePath)
|
|
84
|
-
reverseDependencyGraph.set(dep, reverseDeps)
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function getAffectedFunctions(changedFile: string, functionDir: string): Set<string> {
|
|
89
|
-
const affected = new Set<string>()
|
|
90
|
-
|
|
91
|
-
// If the changed file is already in function dir, include it
|
|
92
|
-
if (changedFile.startsWith(functionDir) && changedFile.endsWith('.ts')) {
|
|
93
|
-
affected.add(changedFile)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Find all functions that depend on this changed file
|
|
97
|
-
function findDependents(filePath: string, visited = new Set<string>()) {
|
|
98
|
-
if (visited.has(filePath)) return
|
|
99
|
-
visited.add(filePath)
|
|
100
|
-
|
|
101
|
-
const dependents = reverseDependencyGraph.get(filePath) || new Set()
|
|
102
|
-
dependents.forEach((dependent) => {
|
|
103
|
-
if (dependent.startsWith(functionDir) && dependent.endsWith('.ts')) {
|
|
104
|
-
affected.add(dependent)
|
|
105
|
-
}
|
|
106
|
-
findDependents(dependent, visited)
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
findDependents(changedFile)
|
|
111
|
-
return affected
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export const createFunctionWatcher = (props: { srcDir: string; log: (data: string) => void }) => {
|
|
115
|
-
const functionDir = resolve(props.srcDir, 'function')
|
|
116
|
-
|
|
117
|
-
if (!existsSync(functionDir)) {
|
|
118
|
-
props.log(
|
|
119
|
-
chalk.yellow(
|
|
120
|
-
`⚠️ Functions directory not found at ${functionDir}, skipping function watcher.`
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
return null
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
props.log(chalk.cyan(`👀 Watching functions and dependencies in ${props.srcDir}`))
|
|
127
|
-
|
|
128
|
-
// Build initial dependency graph for all TypeScript files
|
|
129
|
-
const buildInitialDependencyGraph = () => {
|
|
130
|
-
const glob = require('glob')
|
|
131
|
-
const tsFiles = glob.sync(`${props.srcDir}/**/*.ts`, {
|
|
132
|
-
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
props.log(chalk.gray(`🔍 Analyzing ${tsFiles.length} files for dependencies...`))
|
|
136
|
-
|
|
137
|
-
tsFiles.forEach((filePath: string) => {
|
|
138
|
-
const absolutePath = resolve(filePath)
|
|
139
|
-
updateDependencyGraph(absolutePath)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
props.log(chalk.gray(`✓ Ready for smart rebuilds`))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Build initial graph
|
|
146
|
-
buildInitialDependencyGraph()
|
|
147
|
-
|
|
148
|
-
return chokidar
|
|
149
|
-
.watch(props.srcDir, {
|
|
150
|
-
ignored: (path) => {
|
|
151
|
-
// Exclude common web/React directories and file types
|
|
152
|
-
const excludePatterns = [
|
|
153
|
-
'node_modules',
|
|
154
|
-
'.git',
|
|
155
|
-
'pages',
|
|
156
|
-
'components',
|
|
157
|
-
'styles',
|
|
158
|
-
'public',
|
|
159
|
-
'assets',
|
|
160
|
-
'dist',
|
|
161
|
-
'build'
|
|
162
|
-
]
|
|
163
|
-
|
|
164
|
-
// Exclude file extensions that aren't relevant to functions
|
|
165
|
-
const excludeExtensions = [
|
|
166
|
-
'.css',
|
|
167
|
-
'.scss',
|
|
168
|
-
'.sass',
|
|
169
|
-
'.less',
|
|
170
|
-
'.jsx',
|
|
171
|
-
'.tsx',
|
|
172
|
-
'.html',
|
|
173
|
-
'.md'
|
|
174
|
-
]
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
excludePatterns.some((pattern) => path.includes(pattern)) ||
|
|
178
|
-
excludeExtensions.some((ext) => path.endsWith(ext))
|
|
179
|
-
)
|
|
180
|
-
},
|
|
181
|
-
persistent: true,
|
|
182
|
-
ignoreInitial: true,
|
|
183
|
-
cwd: process.cwd()
|
|
184
|
-
})
|
|
185
|
-
.on('add', (filePath) => {
|
|
186
|
-
if (filePath.endsWith('.ts')) {
|
|
187
|
-
const absolutePath = resolve(filePath)
|
|
188
|
-
updateDependencyGraph(absolutePath)
|
|
189
|
-
|
|
190
|
-
// Only deploy if it's a function file
|
|
191
|
-
if (absolutePath.startsWith(functionDir)) {
|
|
192
|
-
deployFunctionAndLog(absolutePath, props.log)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
.on('change', (filePath) => {
|
|
197
|
-
if (filePath.endsWith('.ts')) {
|
|
198
|
-
const absolutePath = resolve(filePath)
|
|
199
|
-
updateDependencyGraph(absolutePath)
|
|
200
|
-
|
|
201
|
-
// Find all affected functions and deploy them
|
|
202
|
-
const affectedFunctions = getAffectedFunctions(absolutePath, functionDir)
|
|
203
|
-
|
|
204
|
-
if (affectedFunctions.size > 0) {
|
|
205
|
-
const fileName = require('path').basename(filePath)
|
|
206
|
-
const functionNames = Array.from(affectedFunctions).map((f) =>
|
|
207
|
-
require('path').basename(f, '.ts')
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
props.log(chalk.blue(`${fileName} changed → checking ${functionNames.join(', ')}`))
|
|
211
|
-
|
|
212
|
-
affectedFunctions.forEach((functionPath) => {
|
|
213
|
-
deployFunctionAndLog(functionPath, props.log)
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
.on('error', (error) => {
|
|
219
|
-
props.log(chalk.red('❌ Function watcher error:' + error))
|
|
220
|
-
})
|
|
221
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'fs'
|
|
2
|
-
import { resolve, basename } from 'path'
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import { deployFunction, isTypescript, type DeploymentResult } from '../../../lib/deployment'
|
|
5
|
-
import { createSecretsFromFile } from '../../secret'
|
|
6
|
-
|
|
7
|
-
export const deployFunctionAndLog = async (
|
|
8
|
-
filePath: string,
|
|
9
|
-
log: (data: string) => void,
|
|
10
|
-
force: boolean = false
|
|
11
|
-
): Promise<void> => {
|
|
12
|
-
if (!isTypescript(filePath)) {
|
|
13
|
-
log(chalk.gray(`Skipping non-TypeScript file: ${basename(filePath)}`))
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
// Silent logger for watch mode
|
|
19
|
-
const logger = {
|
|
20
|
-
log: () => {},
|
|
21
|
-
error: (error: any) => log(chalk.red(`Error: ${error}`))
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const result: DeploymentResult = await deployFunction(filePath, logger, force)
|
|
25
|
-
|
|
26
|
-
// Show appropriate status based on actual result
|
|
27
|
-
if (result.error) {
|
|
28
|
-
log(chalk.red(` ${basename(filePath, '.ts')} deployment failed`))
|
|
29
|
-
if (result.error.message.includes('Unable to connect')) {
|
|
30
|
-
log(chalk.yellow(' Is your server running?'))
|
|
31
|
-
} else {
|
|
32
|
-
log(chalk.gray(` ${result.error.message}`))
|
|
33
|
-
}
|
|
34
|
-
} else if (result.cached) {
|
|
35
|
-
log(chalk.gray(` ${basename(filePath, '.ts')} skipped (no changes)`))
|
|
36
|
-
} else if (result.deployed) {
|
|
37
|
-
log(chalk.green(` ${basename(filePath, '.ts')} deployed successfully`))
|
|
38
|
-
}
|
|
39
|
-
} catch (error: any) {
|
|
40
|
-
log(chalk.red(` ✗ ${basename(filePath, '.ts')} failed`))
|
|
41
|
-
log(chalk.gray(` ${error.message || error}`))
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const deployAllFunctions = async (
|
|
46
|
-
srcDir: string,
|
|
47
|
-
log: (data: string) => void
|
|
48
|
-
): Promise<void> => {
|
|
49
|
-
const functionDir = resolve(srcDir, 'function')
|
|
50
|
-
|
|
51
|
-
if (!existsSync(functionDir)) {
|
|
52
|
-
log(chalk.yellow('⚠️ No function directory found'))
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const files = readdirSync(functionDir, { recursive: true })
|
|
58
|
-
const tsFiles = files
|
|
59
|
-
.filter((file): file is string => typeof file === 'string')
|
|
60
|
-
.filter((file) => file.endsWith('.ts'))
|
|
61
|
-
|
|
62
|
-
if (tsFiles.length === 0) {
|
|
63
|
-
log(chalk.yellow('⚠️ No TypeScript functions found to deploy'))
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
log(chalk.blue(`🚀 Manually deploying ${tsFiles.length} functions...`))
|
|
68
|
-
|
|
69
|
-
for (const file of tsFiles) {
|
|
70
|
-
const absolutePath = resolve(functionDir, file)
|
|
71
|
-
await deployFunctionAndLog(absolutePath, log, true)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
log(chalk.green('✅ Manual function deployment complete'))
|
|
75
|
-
} catch (error) {
|
|
76
|
-
log(chalk.red('❌ Error during manual function deployment: ' + error))
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const deploySecrets = async (srcDir: string, log: (data: string) => void): Promise<void> => {
|
|
81
|
-
const secretsPath = resolve(srcDir, '../.secrets')
|
|
82
|
-
|
|
83
|
-
log(chalk.cyan(`🔍 Checking for .secrets file at ${secretsPath}`))
|
|
84
|
-
|
|
85
|
-
if (!existsSync(secretsPath)) {
|
|
86
|
-
log(chalk.yellow('⚠️ No .secrets file found'))
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
log(chalk.blue('🔐 Deploying secrets from .secrets file...' + ' ' + chalk.gray(secretsPath)))
|
|
92
|
-
|
|
93
|
-
await createSecretsFromFile({ filePath: secretsPath, log })
|
|
94
|
-
|
|
95
|
-
log(chalk.green('✅ Secret deployment complete'))
|
|
96
|
-
} catch (error) {
|
|
97
|
-
log(chalk.red('❌ Error during secret deployment: ' + error))
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'path'
|
|
2
|
-
import { exists } from 'node:fs/promises'
|
|
3
|
-
|
|
4
|
-
import { createServer } from './server'
|
|
5
|
-
import { createVite } from './vite'
|
|
6
|
-
import { UI } from './ui/'
|
|
7
|
-
import { render } from 'ink'
|
|
8
|
-
import { LoggingManager } from './logging-manager'
|
|
9
|
-
import type { Tab } from './ui/components/tab-bar'
|
|
10
|
-
import { createFunctionWatcher } from './function'
|
|
11
|
-
|
|
12
|
-
const analyzeProject = async (srcDir: string) => {
|
|
13
|
-
const indexTsxPath = resolve(srcDir, 'index.tsx')
|
|
14
|
-
const functionDirPath = resolve(srcDir, 'function')
|
|
15
|
-
|
|
16
|
-
const hasReactApp = await exists(indexTsxPath)
|
|
17
|
-
const hasFunctions = await exists(functionDirPath)
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
hasReactApp,
|
|
21
|
-
hasFunctions,
|
|
22
|
-
indexTsxPath: hasReactApp ? indexTsxPath : null
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const dev = async (srcPath: string) => {
|
|
27
|
-
const srcDir = resolve(srcPath)
|
|
28
|
-
const loggingManager = new LoggingManager()
|
|
29
|
-
|
|
30
|
-
const { hasFunctions, indexTsxPath } = await analyzeProject(srcDir)
|
|
31
|
-
|
|
32
|
-
const tabs: Tab[] = [
|
|
33
|
-
{ name: 'Server', status: 'loading' },
|
|
34
|
-
{ name: 'Functions', status: 'loading' },
|
|
35
|
-
{ name: 'Vite', status: 'loading' }
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
let viteServer: any = null
|
|
39
|
-
let serverProcess: any = null
|
|
40
|
-
let functionWatcher: any = null
|
|
41
|
-
|
|
42
|
-
if (indexTsxPath) {
|
|
43
|
-
viteServer = await createVite({
|
|
44
|
-
indexTsxPath,
|
|
45
|
-
srcDir,
|
|
46
|
-
log: loggingManager.getLogFunction('vite')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
tabs.find((tab) => tab.name === 'Vite')!.status = 'running'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
serverProcess = createServer({
|
|
53
|
-
log: loggingManager.getLogFunction('server')
|
|
54
|
-
})
|
|
55
|
-
tabs.find((tab) => tab.name === 'Server')!.status = 'running'
|
|
56
|
-
|
|
57
|
-
if (hasFunctions) {
|
|
58
|
-
functionWatcher = createFunctionWatcher({
|
|
59
|
-
srcDir,
|
|
60
|
-
log: loggingManager.getLogFunction('functions')
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
tabs.find((tab) => tab.name === 'Functions')!.status = 'running'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const shutdown = async () => {
|
|
67
|
-
loggingManager.addLog('server', 'info', 'Shutting down services...')
|
|
68
|
-
|
|
69
|
-
const shutdownTasks = []
|
|
70
|
-
|
|
71
|
-
if (viteServer) {
|
|
72
|
-
shutdownTasks.push(
|
|
73
|
-
viteServer
|
|
74
|
-
.close()
|
|
75
|
-
.then(() => {
|
|
76
|
-
loggingManager.addLog('vite', 'info', 'Vite server stopped')
|
|
77
|
-
tabs.find((tab) => tab.name === 'Vite')!.status = 'stopped'
|
|
78
|
-
})
|
|
79
|
-
.catch((error: any) =>
|
|
80
|
-
loggingManager.addLog('vite', 'error', `Error stopping Vite: ${error}`)
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (serverProcess) {
|
|
86
|
-
shutdownTasks.push(
|
|
87
|
-
Promise.resolve()
|
|
88
|
-
.then(() => {
|
|
89
|
-
serverProcess.kill()
|
|
90
|
-
loggingManager.addLog('server', 'info', 'Server process stopped')
|
|
91
|
-
tabs.find((tab) => tab.name === 'Server')!.status = 'stopped'
|
|
92
|
-
})
|
|
93
|
-
.catch((error: any) =>
|
|
94
|
-
loggingManager.addLog('server', 'error', `Error stopping server: ${error}`)
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (functionWatcher) {
|
|
100
|
-
shutdownTasks.push(
|
|
101
|
-
functionWatcher
|
|
102
|
-
.close()
|
|
103
|
-
.then(() => {
|
|
104
|
-
loggingManager.addLog('functions', 'info', 'Function watcher stopped')
|
|
105
|
-
tabs.find((tab) => tab.name === 'Functions')!.status = 'stopped'
|
|
106
|
-
})
|
|
107
|
-
.catch((error: any) =>
|
|
108
|
-
loggingManager.addLog('functions', 'error', `Error stopping function watcher: ${error}`)
|
|
109
|
-
)
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
await Promise.race([
|
|
115
|
-
Promise.all(shutdownTasks),
|
|
116
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Shutdown timeout')), 3000))
|
|
117
|
-
])
|
|
118
|
-
} catch (error) {
|
|
119
|
-
loggingManager.addLog('server', 'warn', `Shutdown timeout or error: ${error}`)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
process.exit(0)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
render(<UI tabs={tabs} loggingManager={loggingManager} onShutdown={shutdown} srcDir={srcDir} />)
|
|
126
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export type LogEntry = {
|
|
2
|
-
id: string
|
|
3
|
-
timestamp: Date
|
|
4
|
-
service: 'server' | 'functions' | 'vite'
|
|
5
|
-
level: 'info' | 'warn' | 'error'
|
|
6
|
-
message: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class LoggingManager {
|
|
10
|
-
private logs: Record<string, LogEntry[]> = {
|
|
11
|
-
server: [],
|
|
12
|
-
functions: [],
|
|
13
|
-
vite: []
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private listeners: Set<() => void> = new Set()
|
|
17
|
-
|
|
18
|
-
addLog(service: LogEntry['service'], level: LogEntry['level'], message: string) {
|
|
19
|
-
const newLog: LogEntry = {
|
|
20
|
-
id: `${service}-${Date.now()}-${Math.random()}`,
|
|
21
|
-
timestamp: new Date(),
|
|
22
|
-
service,
|
|
23
|
-
level,
|
|
24
|
-
message
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.logs[service].push(newLog)
|
|
28
|
-
this.notifyListeners()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getLogs(service?: LogEntry['service']): LogEntry[] {
|
|
32
|
-
if (service) {
|
|
33
|
-
return [...this.logs[service]]
|
|
34
|
-
}
|
|
35
|
-
return Object.values(this.logs).flat()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getAllLogs(): Record<string, LogEntry[]> {
|
|
39
|
-
return {
|
|
40
|
-
server: [...this.logs.server],
|
|
41
|
-
functions: [...this.logs.functions],
|
|
42
|
-
vite: [...this.logs.vite]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
clearLogs(service?: LogEntry['service']) {
|
|
47
|
-
if (service) {
|
|
48
|
-
this.logs[service] = []
|
|
49
|
-
} else {
|
|
50
|
-
this.logs = {
|
|
51
|
-
server: [],
|
|
52
|
-
functions: [],
|
|
53
|
-
vite: []
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
this.notifyListeners()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
subscribe(listener: () => void) {
|
|
60
|
-
this.listeners.add(listener)
|
|
61
|
-
|
|
62
|
-
return () => this.listeners.delete(listener)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private notifyListeners() {
|
|
66
|
-
this.listeners.forEach((listener) => listener())
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
getLogFunction(service: LogEntry['service']) {
|
|
70
|
-
return (data: any) => {
|
|
71
|
-
let message: string
|
|
72
|
-
let level: LogEntry['level'] = 'info'
|
|
73
|
-
|
|
74
|
-
if (typeof data === 'string') {
|
|
75
|
-
message = data
|
|
76
|
-
} else if (data && typeof data === 'object') {
|
|
77
|
-
message = data.message || JSON.stringify(data)
|
|
78
|
-
level = data.level || level
|
|
79
|
-
} else {
|
|
80
|
-
message = String(data)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
this.addLog(service, level, message)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import { spawn } from 'bun'
|
|
3
|
-
import { existsSync } from 'fs'
|
|
4
|
-
|
|
5
|
-
import { buildServerArgs, getServerBinPath } from './utils'
|
|
6
|
-
import { getDevConfig } from '../../../lib/config'
|
|
7
|
-
|
|
8
|
-
export const createServer = (props: { log?: (data: string) => void }) => {
|
|
9
|
-
const serverBinPath = getServerBinPath()
|
|
10
|
-
|
|
11
|
-
if (!existsSync(serverBinPath)) {
|
|
12
|
-
console.log(chalk.yellow('⚠️ Server binary not found ' + serverBinPath))
|
|
13
|
-
console.log(chalk.gray(' Run: bun run postinstall to install the server'))
|
|
14
|
-
return null
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const devConfig = getDevConfig()
|
|
18
|
-
|
|
19
|
-
const args = buildServerArgs(devConfig)
|
|
20
|
-
|
|
21
|
-
const process = spawn([serverBinPath, ...args], {
|
|
22
|
-
stdout: props.log ? 'pipe' : 'inherit',
|
|
23
|
-
stderr: props.log ? 'pipe' : 'inherit'
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (props.log && process.stdout && process.stderr) {
|
|
27
|
-
const decoder = new TextDecoder()
|
|
28
|
-
|
|
29
|
-
const readStream = async (stream: ReadableStream<Uint8Array>) => {
|
|
30
|
-
const reader = stream.getReader()
|
|
31
|
-
while (true) {
|
|
32
|
-
const { done, value } = await reader.read()
|
|
33
|
-
if (done) {
|
|
34
|
-
break
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (props.log) {
|
|
38
|
-
props.log(decoder.decode(value))
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
readStream(process.stdout)
|
|
44
|
-
readStream(process.stderr)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return process
|
|
48
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { resolve, dirname, join } from 'path'
|
|
2
|
-
import { existsSync } from 'fs'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
|
-
|
|
5
|
-
import { getCloudPath, type DevConfig } from '../../../lib/config'
|
|
6
|
-
|
|
7
|
-
export const getServerBinPath = (): string => {
|
|
8
|
-
// Try to find the server binary relative to this module
|
|
9
|
-
// This works both in development and when installed globally
|
|
10
|
-
const currentFile = fileURLToPath(import.meta.url)
|
|
11
|
-
const moduleRoot = resolve(dirname(currentFile), '../../..')
|
|
12
|
-
const path = join(moduleRoot, 'bin', 'server')
|
|
13
|
-
|
|
14
|
-
if (!existsSync(path)) {
|
|
15
|
-
const currentFile = fileURLToPath(import.meta.url)
|
|
16
|
-
const moduleRoot = resolve(dirname(currentFile), '../../../..')
|
|
17
|
-
return join(moduleRoot, 'bin', 'server')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return path
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const buildServerArgs = (config: DevConfig): string[] => {
|
|
24
|
-
const args = ['--dev']
|
|
25
|
-
|
|
26
|
-
args.push('--api-port', config.apiPort.toString())
|
|
27
|
-
args.push('--gateway-port', config.gatewayPort.toString())
|
|
28
|
-
|
|
29
|
-
const cloudPath = getCloudPath()
|
|
30
|
-
args.push('--cloud-path', cloudPath)
|
|
31
|
-
|
|
32
|
-
if (config.publicKey) {
|
|
33
|
-
args.push('--public-key', config.publicKey)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return args
|
|
37
|
-
}
|