@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/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@tothalex/nulljs",
3
- "module": "src/index.tsx",
4
- "version": "0.0.53",
3
+ "module": "dist/index.js",
4
+ "version": "0.0.57",
5
5
  "type": "module",
6
6
  "bin": {
7
- "nulljs": "./src/cli.ts"
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()
@@ -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
- }
@@ -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
- }
@@ -1,10 +0,0 @@
1
- import type { Command } from 'commander'
2
-
3
- export const registerDevCommand = (program: Command) => {
4
- program
5
- .command('dev')
6
- .description('Launch the development renderer')
7
- .action(async () => {
8
- await import('../ui.tsx')
9
- })
10
- }