@tothalex/nulljs 0.0.53 → 0.0.57
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/cli.js +2553 -0
- package/package.json +8 -5
- package/src/cli.ts +0 -24
- package/src/commands/config.ts +0 -130
- package/src/commands/deploy.ts +0 -219
- package/src/commands/dev.ts +0 -10
- package/src/commands/host.ts +0 -330
- package/src/commands/index.ts +0 -6
- package/src/commands/secret.ts +0 -387
- package/src/commands/status.ts +0 -41
- package/src/components/DeployAnimation.tsx +0 -92
- package/src/components/DeploymentLogsPane.tsx +0 -79
- package/src/components/Header.tsx +0 -57
- package/src/components/HelpModal.tsx +0 -64
- package/src/components/SystemLogsPane.tsx +0 -78
- package/src/config/index.ts +0 -181
- package/src/lib/bundle/external.ts +0 -23
- package/src/lib/bundle/function.ts +0 -125
- package/src/lib/bundle/index.ts +0 -5
- package/src/lib/bundle/react.ts +0 -149
- package/src/lib/bundle/types.ts +0 -4
- package/src/lib/deploy.ts +0 -103
- package/src/lib/server.ts +0 -160
- package/src/lib/vite.ts +0 -120
- package/src/lib/watcher.ts +0 -274
- package/src/ui.tsx +0 -363
- package/tsconfig.json +0 -30
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tothalex/nulljs",
|
|
3
|
-
"module": "
|
|
4
|
-
"version": "0.0.
|
|
3
|
+
"module": "dist/index.js",
|
|
4
|
+
"version": "0.0.57",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"nulljs": "./
|
|
7
|
+
"nulljs": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"dev": "bun run src/cli.ts dev"
|
|
10
|
+
"dev": "bun run src/cli.ts dev",
|
|
11
|
+
"build:cli": "bun build src/cli.ts --outdir dist --target bun --external @clack/prompts --external @opentui/core --external @opentui/react --external @tailwindcss/vite --external @vitejs/plugin-react --external chalk --external commander --external react --external vite --external lightningcss --external fsevents"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
14
|
"@types/bun": "latest",
|
|
@@ -21,9 +22,11 @@
|
|
|
21
22
|
"@tothalex/nulljs-linux-arm64": "^0.0.79",
|
|
22
23
|
"@tothalex/nulljs-linux-x64": "^0.0.79"
|
|
23
24
|
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
24
28
|
"dependencies": {
|
|
25
29
|
"@clack/prompts": "^0.11.0",
|
|
26
|
-
"@nulljs/api": "workspace:*",
|
|
27
30
|
"@opentui/core": "^0.1.62",
|
|
28
31
|
"@opentui/react": "^0.1.62",
|
|
29
32
|
"@tailwindcss/vite": "^4.1.18",
|
package/src/cli.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { Command } from 'commander'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
registerDevCommand,
|
|
6
|
-
registerDeployCommand,
|
|
7
|
-
registerConfigCommand,
|
|
8
|
-
registerStatusCommand,
|
|
9
|
-
registerSecretCommand,
|
|
10
|
-
registerHostCommand
|
|
11
|
-
} from './commands'
|
|
12
|
-
|
|
13
|
-
const program = new Command()
|
|
14
|
-
|
|
15
|
-
program.name('nulljs').description('NullJS CLI').version('0.0.49')
|
|
16
|
-
|
|
17
|
-
registerDevCommand(program)
|
|
18
|
-
registerDeployCommand(program)
|
|
19
|
-
registerConfigCommand(program)
|
|
20
|
-
registerStatusCommand(program)
|
|
21
|
-
registerSecretCommand(program)
|
|
22
|
-
registerHostCommand(program)
|
|
23
|
-
|
|
24
|
-
program.parse()
|
package/src/commands/config.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import * as p from '@clack/prompts'
|
|
4
|
-
|
|
5
|
-
import { createConfig, listConfigs, useConfig } from '../config'
|
|
6
|
-
|
|
7
|
-
export const registerConfigCommand = (program: Command) => {
|
|
8
|
-
program
|
|
9
|
-
.command('config')
|
|
10
|
-
.description('Configuration management')
|
|
11
|
-
.addCommand(
|
|
12
|
-
new Command('new')
|
|
13
|
-
.description('Create a new configuration')
|
|
14
|
-
.argument('[name]', 'Configuration name (e.g., prod, staging)')
|
|
15
|
-
.argument('[api]', 'API endpoint (e.g., https://api.example.com)')
|
|
16
|
-
.action(async (name?: string, api?: string) => {
|
|
17
|
-
let configName = name
|
|
18
|
-
let configApi = api
|
|
19
|
-
|
|
20
|
-
// Interactive mode if no arguments provided
|
|
21
|
-
if (!configName || !configApi) {
|
|
22
|
-
p.intro(chalk.cyan('Create new configuration'))
|
|
23
|
-
|
|
24
|
-
if (!configName) {
|
|
25
|
-
const nameInput = await p.text({
|
|
26
|
-
message: 'Configuration name',
|
|
27
|
-
placeholder: 'e.g., prod, staging',
|
|
28
|
-
validate: (value) => {
|
|
29
|
-
if (!value.trim()) return 'Name is required'
|
|
30
|
-
if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
31
|
-
return 'Name can only contain letters, numbers, hyphens and underscores'
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
if (p.isCancel(nameInput)) {
|
|
37
|
-
p.cancel('Configuration cancelled')
|
|
38
|
-
process.exit(0)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
configName = nameInput as string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!configApi) {
|
|
45
|
-
const apiInput = await p.text({
|
|
46
|
-
message: 'API endpoint',
|
|
47
|
-
placeholder: 'e.g., https://api.example.com',
|
|
48
|
-
validate: (value) => {
|
|
49
|
-
if (!value.trim()) return 'API endpoint is required'
|
|
50
|
-
try {
|
|
51
|
-
new URL(value)
|
|
52
|
-
} catch {
|
|
53
|
-
return 'Please enter a valid URL'
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (p.isCancel(apiInput)) {
|
|
59
|
-
p.cancel('Configuration cancelled')
|
|
60
|
-
process.exit(0)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
configApi = apiInput as string
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
await createConfig(configName, configApi)
|
|
68
|
-
})
|
|
69
|
-
)
|
|
70
|
-
.addCommand(
|
|
71
|
-
new Command('list').description('List all configurations').action(() => {
|
|
72
|
-
const result = listConfigs()
|
|
73
|
-
|
|
74
|
-
if (!result || result.configs.length === 0) {
|
|
75
|
-
console.log(chalk.yellow('No configurations found.'))
|
|
76
|
-
console.log(chalk.gray(' Run "nulljs dev" to create a dev config, or'))
|
|
77
|
-
console.log(chalk.gray(' Run "nulljs config new <name> <api>" to create a new config'))
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log(chalk.bold('Configurations:'))
|
|
82
|
-
result.configs.forEach((config) => {
|
|
83
|
-
const isActive = config.name === result.current
|
|
84
|
-
const marker = isActive ? chalk.green('●') : chalk.gray('○')
|
|
85
|
-
const name = isActive ? chalk.green(config.name) : config.name
|
|
86
|
-
const suffix = isActive ? chalk.green(' (active)') : ''
|
|
87
|
-
console.log(` ${marker} ${name}${suffix}`)
|
|
88
|
-
console.log(chalk.gray(` API: ${config.api}`))
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
)
|
|
92
|
-
.addCommand(
|
|
93
|
-
new Command('use')
|
|
94
|
-
.description('Switch to a configuration')
|
|
95
|
-
.argument('[name]', 'Configuration name')
|
|
96
|
-
.action(async (name?: string) => {
|
|
97
|
-
let configName = name
|
|
98
|
-
|
|
99
|
-
if (!configName) {
|
|
100
|
-
const configList = listConfigs()
|
|
101
|
-
|
|
102
|
-
if (!configList || configList.configs.length === 0) {
|
|
103
|
-
console.log(chalk.yellow('No configurations found.'))
|
|
104
|
-
console.log(chalk.gray(' Run "nulljs dev" to create a dev config, or'))
|
|
105
|
-
console.log(chalk.gray(' Run "nulljs config new" to create a new config'))
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const selected = await p.select({
|
|
110
|
-
message: 'Select config to use',
|
|
111
|
-
options: configList.configs.map((c) => ({
|
|
112
|
-
value: c.name,
|
|
113
|
-
label: c.name,
|
|
114
|
-
hint: c.name === configList.current ? 'current' : c.api
|
|
115
|
-
})),
|
|
116
|
-
initialValue: configList.current
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
if (p.isCancel(selected)) {
|
|
120
|
-
p.cancel('Cancelled')
|
|
121
|
-
process.exit(0)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
configName = selected as string
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
useConfig(configName)
|
|
128
|
-
})
|
|
129
|
-
)
|
|
130
|
-
}
|
package/src/commands/deploy.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import { basename, resolve, join } from 'path'
|
|
4
|
-
import { existsSync } from 'fs'
|
|
5
|
-
import { readdir } from 'fs/promises'
|
|
6
|
-
import * as p from '@clack/prompts'
|
|
7
|
-
|
|
8
|
-
import { loadPrivateKey, readLocalConfig, getConfig, listConfigs, type Config } from '../config'
|
|
9
|
-
import { isReact } from '../lib/bundle'
|
|
10
|
-
import { deployFunction, deployReact, clearDeployCache } from '../lib/deploy'
|
|
11
|
-
|
|
12
|
-
const FUNCTION_DIRS = ['api', 'cron', 'event']
|
|
13
|
-
|
|
14
|
-
const selectConfig = async (): Promise<Config | null> => {
|
|
15
|
-
const configList = listConfigs()
|
|
16
|
-
|
|
17
|
-
if (!configList || configList.configs.length === 0) {
|
|
18
|
-
return null
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// If only one config, use it directly
|
|
22
|
-
if (configList.configs.length === 1) {
|
|
23
|
-
return configList.configs[0] ?? null
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const selected = await p.select({
|
|
27
|
-
message: 'Select config to deploy to',
|
|
28
|
-
options: configList.configs.map((c) => ({
|
|
29
|
-
value: c.name,
|
|
30
|
-
label: c.name,
|
|
31
|
-
hint: c.name === configList.current ? 'current' : c.api
|
|
32
|
-
})),
|
|
33
|
-
initialValue: configList.current
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
if (p.isCancel(selected)) {
|
|
37
|
-
p.cancel('Deployment cancelled')
|
|
38
|
-
process.exit(0)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return getConfig(selected as string)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const findAllDeployables = async (srcPath: string) => {
|
|
45
|
-
const functions: string[] = []
|
|
46
|
-
|
|
47
|
-
// Find functions in src/function/{api,cron,event}/
|
|
48
|
-
const functionsPath = join(srcPath, 'function')
|
|
49
|
-
for (const dir of FUNCTION_DIRS) {
|
|
50
|
-
const dirPath = join(functionsPath, dir)
|
|
51
|
-
try {
|
|
52
|
-
const items = await readdir(dirPath, { withFileTypes: true })
|
|
53
|
-
for (const item of items) {
|
|
54
|
-
if (item.isFile() && (item.name.endsWith('.ts') || item.name.endsWith('.tsx'))) {
|
|
55
|
-
functions.push(join(dirPath, item.name))
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch {
|
|
59
|
-
// Directory doesn't exist
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// React entrypoint is always src/index.tsx
|
|
64
|
-
const reactEntry = join(srcPath, 'index.tsx')
|
|
65
|
-
const hasReact = existsSync(reactEntry)
|
|
66
|
-
|
|
67
|
-
return { functions, reactEntry: hasReact ? reactEntry : null }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const deployAll = async (config: Config, options: { force?: boolean }) => {
|
|
71
|
-
const srcPath = join(process.cwd(), 'src')
|
|
72
|
-
|
|
73
|
-
if (!existsSync(srcPath)) {
|
|
74
|
-
console.error(chalk.red('✗ No src directory found'))
|
|
75
|
-
process.exit(1)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { functions, reactEntry } = await findAllDeployables(srcPath)
|
|
79
|
-
const total = functions.length + (reactEntry ? 1 : 0)
|
|
80
|
-
|
|
81
|
-
if (total === 0) {
|
|
82
|
-
console.log(chalk.yellow('No functions or React app found to deploy'))
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (options.force) {
|
|
87
|
-
clearDeployCache()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const privateKey = await loadPrivateKey(config)
|
|
91
|
-
let successful = 0
|
|
92
|
-
let failed = 0
|
|
93
|
-
let skipped = 0
|
|
94
|
-
|
|
95
|
-
console.log(chalk.cyan(`Deploying ${total} file(s) to ${config.name}...\n`))
|
|
96
|
-
|
|
97
|
-
// Deploy functions
|
|
98
|
-
for (const filePath of functions) {
|
|
99
|
-
const fileName = basename(filePath)
|
|
100
|
-
try {
|
|
101
|
-
console.log(chalk.yellow('Bundling ') + chalk.bold(fileName))
|
|
102
|
-
const result = await deployFunction(filePath, privateKey, config.api)
|
|
103
|
-
if (result.skipped) {
|
|
104
|
-
console.log(chalk.gray('– Skipped ') + chalk.bold(fileName) + chalk.gray(' (unchanged)'))
|
|
105
|
-
skipped++
|
|
106
|
-
} else {
|
|
107
|
-
console.log(chalk.green('✓ Deployed ') + chalk.bold(fileName))
|
|
108
|
-
successful++
|
|
109
|
-
}
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.error(chalk.red('✗ Failed ') + chalk.bold(fileName) + chalk.red(`: ${error instanceof Error ? error.message : error}`))
|
|
112
|
-
failed++
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Deploy React app (src/index.tsx)
|
|
117
|
-
if (reactEntry) {
|
|
118
|
-
const fileName = basename(reactEntry)
|
|
119
|
-
try {
|
|
120
|
-
console.log(chalk.yellow('Bundling React SPA ') + chalk.bold(fileName))
|
|
121
|
-
const result = await deployReact(reactEntry, privateKey, config.api)
|
|
122
|
-
if (result.skipped) {
|
|
123
|
-
console.log(chalk.gray('– Skipped ') + chalk.bold(fileName) + chalk.gray(' (unchanged)'))
|
|
124
|
-
skipped++
|
|
125
|
-
} else {
|
|
126
|
-
console.log(chalk.green('✓ Deployed ') + chalk.bold(fileName))
|
|
127
|
-
successful++
|
|
128
|
-
}
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(chalk.red('✗ Failed ') + chalk.bold(fileName) + chalk.red(`: ${error instanceof Error ? error.message : error}`))
|
|
131
|
-
failed++
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log('')
|
|
136
|
-
if (failed > 0) {
|
|
137
|
-
console.log(chalk.red(`Deployment completed with errors: ${successful} deployed, ${skipped} skipped, ${failed} failed`))
|
|
138
|
-
process.exit(1)
|
|
139
|
-
} else {
|
|
140
|
-
console.log(chalk.green(`Deployment completed: ${successful} deployed, ${skipped} skipped`))
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const registerDeployCommand = (program: Command) => {
|
|
145
|
-
program
|
|
146
|
-
.command('deploy')
|
|
147
|
-
.description('Bundle and deploy functions and React SPAs')
|
|
148
|
-
.argument('[file]', 'Path to a specific file (deploys all if omitted)')
|
|
149
|
-
.option('-e, --env <name>', 'Use a specific config environment')
|
|
150
|
-
.option('-f, --force', 'Force deploy even if unchanged')
|
|
151
|
-
.action(async (file: string | undefined, options: { env?: string; force?: boolean }) => {
|
|
152
|
-
let config: Config | null
|
|
153
|
-
|
|
154
|
-
if (options.env) {
|
|
155
|
-
// Use specified config
|
|
156
|
-
config = getConfig(options.env)
|
|
157
|
-
if (!config) {
|
|
158
|
-
console.error(chalk.red(`✗ Config "${options.env}" not found.`))
|
|
159
|
-
console.error(chalk.gray(' Run "nulljs config list" to see available configs'))
|
|
160
|
-
process.exit(1)
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
// Interactive picker
|
|
164
|
-
config = await selectConfig()
|
|
165
|
-
if (!config) {
|
|
166
|
-
console.error(chalk.red('✗ No configurations found.'))
|
|
167
|
-
console.error(chalk.gray(' Run "nulljs dev" first to initialize the project'))
|
|
168
|
-
process.exit(1)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// If no file specified, deploy all
|
|
173
|
-
if (!file) {
|
|
174
|
-
await deployAll(config, options)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Single file deployment
|
|
179
|
-
const filePath = resolve(file)
|
|
180
|
-
|
|
181
|
-
if (!existsSync(filePath)) {
|
|
182
|
-
console.error(chalk.red(`✗ File not found: ${filePath}`))
|
|
183
|
-
process.exit(1)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (options.force) {
|
|
187
|
-
clearDeployCache()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const fileName = basename(filePath)
|
|
191
|
-
const privateKey = await loadPrivateKey(config)
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
if (isReact(filePath)) {
|
|
195
|
-
console.log(chalk.yellow('Bundling React SPA ') + chalk.bold(fileName))
|
|
196
|
-
const result = await deployReact(filePath, privateKey, config.api)
|
|
197
|
-
if (result.skipped) {
|
|
198
|
-
console.log(chalk.gray('– Skipped ') + chalk.bold(fileName) + chalk.gray(' (unchanged)'))
|
|
199
|
-
} else {
|
|
200
|
-
console.log(chalk.green('✓ Deployed ') + chalk.bold(fileName) + chalk.gray(` to ${config.name}`))
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
console.log(chalk.yellow('Bundling ') + chalk.bold(fileName))
|
|
204
|
-
const result = await deployFunction(filePath, privateKey, config.api)
|
|
205
|
-
if (result.skipped) {
|
|
206
|
-
console.log(chalk.gray('– Skipped ') + chalk.bold(fileName) + chalk.gray(' (unchanged)'))
|
|
207
|
-
} else {
|
|
208
|
-
console.log(chalk.green('✓ Deployed ') + chalk.bold(fileName) + chalk.gray(` to ${config.name}`))
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.error(
|
|
213
|
-
chalk.red('✗ Deployment failed:'),
|
|
214
|
-
error instanceof Error ? error.message : error
|
|
215
|
-
)
|
|
216
|
-
process.exit(1)
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
}
|
package/src/commands/dev.ts
DELETED