@tothalex/nulljs 0.0.48 → 0.0.54

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.
Files changed (42) hide show
  1. package/package.json +25 -32
  2. package/scripts/install-server.js +0 -199
  3. package/src/commands/api.ts +0 -16
  4. package/src/commands/auth.ts +0 -54
  5. package/src/commands/create.ts +0 -43
  6. package/src/commands/deploy.ts +0 -160
  7. package/src/commands/dev/function/index.ts +0 -221
  8. package/src/commands/dev/function/utils.ts +0 -99
  9. package/src/commands/dev/index.tsx +0 -126
  10. package/src/commands/dev/logging-manager.ts +0 -87
  11. package/src/commands/dev/server/index.ts +0 -48
  12. package/src/commands/dev/server/utils.ts +0 -37
  13. package/src/commands/dev/ui/components/scroll-area.tsx +0 -141
  14. package/src/commands/dev/ui/components/tab-bar.tsx +0 -67
  15. package/src/commands/dev/ui/index.tsx +0 -71
  16. package/src/commands/dev/ui/logging-context.tsx +0 -76
  17. package/src/commands/dev/ui/tabs/functions-tab.tsx +0 -35
  18. package/src/commands/dev/ui/tabs/server-tab.tsx +0 -36
  19. package/src/commands/dev/ui/tabs/vite-tab.tsx +0 -35
  20. package/src/commands/dev/ui/use-logging.tsx +0 -34
  21. package/src/commands/dev/vite/index.ts +0 -54
  22. package/src/commands/dev/vite/utils.ts +0 -71
  23. package/src/commands/host.ts +0 -339
  24. package/src/commands/index.ts +0 -8
  25. package/src/commands/profile.ts +0 -189
  26. package/src/commands/secret.ts +0 -79
  27. package/src/index.ts +0 -346
  28. package/src/lib/api.ts +0 -189
  29. package/src/lib/bundle/external.ts +0 -23
  30. package/src/lib/bundle/function/index.ts +0 -46
  31. package/src/lib/bundle/index.ts +0 -2
  32. package/src/lib/bundle/react/index.ts +0 -2
  33. package/src/lib/bundle/react/spa.ts +0 -77
  34. package/src/lib/bundle/react/ssr/client.ts +0 -93
  35. package/src/lib/bundle/react/ssr/config.ts +0 -77
  36. package/src/lib/bundle/react/ssr/index.ts +0 -4
  37. package/src/lib/bundle/react/ssr/props.ts +0 -71
  38. package/src/lib/bundle/react/ssr/server.ts +0 -83
  39. package/src/lib/bundle/types.ts +0 -4
  40. package/src/lib/config.ts +0 -347
  41. package/src/lib/deployment.ts +0 -244
  42. 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
- }