@tothalex/nulljs 0.0.47 → 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.
Files changed (58) hide show
  1. package/package.json +22 -32
  2. package/src/cli.ts +24 -0
  3. package/src/commands/config.ts +130 -0
  4. package/src/commands/deploy.ts +182 -123
  5. package/src/commands/dev.ts +10 -0
  6. package/src/commands/host.ts +130 -139
  7. package/src/commands/index.ts +6 -8
  8. package/src/commands/secret.ts +364 -56
  9. package/src/commands/status.ts +41 -0
  10. package/src/components/DeployAnimation.tsx +92 -0
  11. package/src/components/DeploymentLogsPane.tsx +79 -0
  12. package/src/components/Header.tsx +57 -0
  13. package/src/components/HelpModal.tsx +64 -0
  14. package/src/components/SystemLogsPane.tsx +78 -0
  15. package/src/config/index.ts +181 -0
  16. package/src/lib/bundle/function.ts +125 -0
  17. package/src/lib/bundle/index.ts +3 -0
  18. package/src/lib/bundle/react.ts +149 -0
  19. package/src/lib/deploy.ts +103 -0
  20. package/src/lib/server.ts +160 -0
  21. package/src/lib/vite.ts +120 -0
  22. package/src/lib/watcher.ts +274 -0
  23. package/src/ui.tsx +363 -0
  24. package/tsconfig.json +30 -0
  25. package/scripts/install-server.js +0 -199
  26. package/src/commands/api.ts +0 -16
  27. package/src/commands/auth.ts +0 -54
  28. package/src/commands/create.ts +0 -43
  29. package/src/commands/dev/function/index.ts +0 -221
  30. package/src/commands/dev/function/utils.ts +0 -99
  31. package/src/commands/dev/index.tsx +0 -126
  32. package/src/commands/dev/logging-manager.ts +0 -87
  33. package/src/commands/dev/server/index.ts +0 -48
  34. package/src/commands/dev/server/utils.ts +0 -37
  35. package/src/commands/dev/ui/components/scroll-area.tsx +0 -141
  36. package/src/commands/dev/ui/components/tab-bar.tsx +0 -67
  37. package/src/commands/dev/ui/index.tsx +0 -71
  38. package/src/commands/dev/ui/logging-context.tsx +0 -76
  39. package/src/commands/dev/ui/tabs/functions-tab.tsx +0 -35
  40. package/src/commands/dev/ui/tabs/server-tab.tsx +0 -36
  41. package/src/commands/dev/ui/tabs/vite-tab.tsx +0 -35
  42. package/src/commands/dev/ui/use-logging.tsx +0 -34
  43. package/src/commands/dev/vite/index.ts +0 -54
  44. package/src/commands/dev/vite/utils.ts +0 -71
  45. package/src/commands/profile.ts +0 -189
  46. package/src/index.ts +0 -346
  47. package/src/lib/api.ts +0 -189
  48. package/src/lib/bundle/function/index.ts +0 -46
  49. package/src/lib/bundle/react/index.ts +0 -2
  50. package/src/lib/bundle/react/spa.ts +0 -77
  51. package/src/lib/bundle/react/ssr/client.ts +0 -93
  52. package/src/lib/bundle/react/ssr/config.ts +0 -77
  53. package/src/lib/bundle/react/ssr/index.ts +0 -4
  54. package/src/lib/bundle/react/ssr/props.ts +0 -71
  55. package/src/lib/bundle/react/ssr/server.ts +0 -83
  56. package/src/lib/config.ts +0 -347
  57. package/src/lib/deployment.ts +0 -244
  58. package/src/lib/update-server.ts +0 -262
package/src/lib/config.ts DELETED
@@ -1,347 +0,0 @@
1
- import {
2
- readFileSync,
3
- writeFileSync,
4
- existsSync,
5
- mkdirSync,
6
- readdirSync,
7
- unlinkSync
8
- } from 'node:fs'
9
- import { join, dirname } from 'node:path'
10
- import chalk from 'chalk'
11
-
12
- type NulljsConfig = {
13
- key?: {
14
- private: string
15
- public: string
16
- }
17
- api?: string
18
- dev?: {
19
- apiPort?: number
20
- gatewayPort?: number
21
- }
22
- }
23
-
24
- const findProjectRoot = (startPath: string = process.cwd()): string => {
25
- let currentPath = startPath
26
- while (currentPath !== dirname(currentPath)) {
27
- if (existsSync(join(currentPath, 'package.json'))) {
28
- return currentPath
29
- }
30
- currentPath = dirname(currentPath)
31
- }
32
- return startPath
33
- }
34
-
35
- export const configPath = join(findProjectRoot(), '.nulljs', 'config.json')
36
- export const profilesDir = join(findProjectRoot(), '.nulljs', 'profiles')
37
- export const activeProfilePath = join(findProjectRoot(), '.nulljs', 'active-profile')
38
-
39
- export const loadConfig = (): NulljsConfig => {
40
- if (!existsSync(configPath)) {
41
- return {}
42
- }
43
-
44
- try {
45
- const configContent = readFileSync(configPath, 'utf8')
46
- return JSON.parse(configContent) as NulljsConfig
47
- } catch (error) {
48
- console.error('Failed to parse config file:', error)
49
- return {}
50
- }
51
- }
52
-
53
- export const saveConfig = (config: NulljsConfig): void => {
54
- try {
55
- // Ensure the directory exists
56
- const configDir = dirname(configPath)
57
- if (!existsSync(configDir)) {
58
- mkdirSync(configDir, { recursive: true })
59
- }
60
-
61
- const configContent = JSON.stringify(config, null, 2)
62
- writeFileSync(configPath, configContent, 'utf8')
63
- } catch (error) {
64
- console.error('Failed to save config file:', error)
65
- throw error
66
- }
67
- }
68
-
69
- export const updateConfig = (partialConfig: Partial<NulljsConfig>): void => {
70
- const currentConfig = loadConfig()
71
- const updatedConfig = { ...currentConfig, ...partialConfig }
72
-
73
- saveConfig(updatedConfig)
74
- }
75
-
76
- // Profile-related functions (must be defined before other functions use them)
77
- export const getActiveProfile = (): string | null => {
78
- if (!existsSync(activeProfilePath)) {
79
- return null
80
- }
81
- try {
82
- return readFileSync(activeProfilePath, 'utf8').trim()
83
- } catch {
84
- return null
85
- }
86
- }
87
-
88
- export const setActiveProfile = (profileName: string): void => {
89
- const configDir = dirname(activeProfilePath)
90
- if (!existsSync(configDir)) {
91
- mkdirSync(configDir, { recursive: true })
92
- }
93
- writeFileSync(activeProfilePath, profileName, 'utf8')
94
- }
95
-
96
- export const getProfilePath = (profileName: string): string => {
97
- return join(profilesDir, `${profileName}.json`)
98
- }
99
-
100
- export const loadProfile = (profileName: string): NulljsConfig => {
101
- const profilePath = getProfilePath(profileName)
102
- if (!existsSync(profilePath)) {
103
- return {}
104
- }
105
-
106
- try {
107
- const profileContent = readFileSync(profilePath, 'utf8')
108
- return JSON.parse(profileContent) as NulljsConfig
109
- } catch (error) {
110
- console.error(`Failed to parse profile '${profileName}':`, error)
111
- return {}
112
- }
113
- }
114
-
115
- export const saveProfile = (profileName: string, config: NulljsConfig): void => {
116
- try {
117
- if (!existsSync(profilesDir)) {
118
- mkdirSync(profilesDir, { recursive: true })
119
- }
120
-
121
- const profilePath = getProfilePath(profileName)
122
- const configContent = JSON.stringify(config, null, 2)
123
- writeFileSync(profilePath, configContent, 'utf8')
124
- } catch (error) {
125
- console.error(`Failed to save profile '${profileName}':`, error)
126
- throw error
127
- }
128
- }
129
-
130
- export const profileExists = (profileName: string): boolean => {
131
- return existsSync(getProfilePath(profileName))
132
- }
133
-
134
- export const loadConfigWithProfile = (): NulljsConfig => {
135
- const activeProfile = getActiveProfile()
136
-
137
- if (activeProfile && profileExists(activeProfile)) {
138
- return loadProfile(activeProfile)
139
- }
140
-
141
- return loadConfig()
142
- }
143
-
144
- export const saveConfigWithProfile = (config: NulljsConfig, profileName?: string): void => {
145
- if (profileName) {
146
- saveProfile(profileName, config)
147
- setActiveProfile(profileName)
148
- } else {
149
- const activeProfile = getActiveProfile()
150
- if (activeProfile && profileExists(activeProfile)) {
151
- saveProfile(activeProfile, config)
152
- } else {
153
- saveConfig(config)
154
- }
155
- }
156
- }
157
-
158
- export const updateConfigWithProfile = (
159
- partialConfig: Partial<NulljsConfig>,
160
- profileName?: string
161
- ): void => {
162
- if (profileName) {
163
- const currentConfig = loadProfile(profileName)
164
- const updatedConfig = { ...currentConfig, ...partialConfig }
165
- saveProfile(profileName, updatedConfig)
166
- setActiveProfile(profileName)
167
- } else {
168
- const activeProfile = getActiveProfile()
169
- if (activeProfile && profileExists(activeProfile)) {
170
- const currentConfig = loadProfile(activeProfile)
171
- const updatedConfig = { ...currentConfig, ...partialConfig }
172
- saveProfile(activeProfile, updatedConfig)
173
- } else {
174
- updateConfig(partialConfig)
175
- }
176
- }
177
- }
178
-
179
- // Functions that use profile-related functions
180
- export const saveKeys = (privateKey: string, publicKey: string, profileName?: string): void => {
181
- updateConfigWithProfile(
182
- {
183
- key: {
184
- private: privateKey,
185
- public: publicKey
186
- }
187
- },
188
- profileName
189
- )
190
- }
191
-
192
- export const saveApiUrl = (apiUrl: string, profileName?: string): void => {
193
- updateConfigWithProfile({ api: apiUrl }, profileName)
194
- }
195
-
196
- export const loadPrivateKey = async () => {
197
- let base64Key = process.env.NULLJS_PRIVATE_KEY
198
-
199
- if (!base64Key) {
200
- const config = loadConfigWithProfile()
201
- if (!config.key?.private) {
202
- throw new Error('Private key not found in config file or environment variable')
203
- }
204
- base64Key = config.key.private
205
- }
206
-
207
- const keyBuffer = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0))
208
-
209
- return await crypto.subtle.importKey(
210
- 'pkcs8',
211
- keyBuffer,
212
- {
213
- name: 'Ed25519',
214
- namedCurve: 'Ed25519'
215
- },
216
- false,
217
- ['sign']
218
- )
219
- }
220
-
221
- export const getApiUrl = (): string => {
222
- const config = loadConfigWithProfile()
223
- return config.api || 'http://localhost:3000/api'
224
- }
225
-
226
- export const API = getApiUrl()
227
-
228
- export type DevConfig = {
229
- apiPort: number
230
- gatewayPort: number
231
- publicKey: string | undefined
232
- }
233
-
234
- export const getDevConfig = () => {
235
- const config = loadConfigWithProfile()
236
- return {
237
- apiPort: config.dev?.apiPort || 3000,
238
- gatewayPort: config.dev?.gatewayPort || 3001,
239
- publicKey: config.key?.public
240
- }
241
- }
242
-
243
- export const getProjectRoot = () => findProjectRoot()
244
-
245
- export const getCloudPath = () => join(findProjectRoot(), '.nulljs')
246
-
247
- export const listProfiles = (): string[] => {
248
- if (!existsSync(profilesDir)) {
249
- return []
250
- }
251
-
252
- try {
253
- return readdirSync(profilesDir)
254
- .filter((file) => file.endsWith('.json'))
255
- .map((file) => file.replace('.json', ''))
256
- } catch {
257
- return []
258
- }
259
- }
260
-
261
- export const deleteProfile = (profileName: string): boolean => {
262
- const profilePath = getProfilePath(profileName)
263
- if (!existsSync(profilePath)) {
264
- return false
265
- }
266
-
267
- try {
268
- unlinkSync(profilePath)
269
-
270
- const activeProfile = getActiveProfile()
271
- if (activeProfile === profileName) {
272
- if (existsSync(activeProfilePath)) {
273
- unlinkSync(activeProfilePath)
274
- }
275
- }
276
-
277
- return true
278
- } catch {
279
- return false
280
- }
281
- }
282
-
283
- export const requireActiveProfile = (): { profileName: string; config: NulljsConfig } => {
284
- const activeProfile = getActiveProfile()
285
-
286
- if (!activeProfile) {
287
- console.error(chalk.red('❌ No active profile selected.'))
288
- console.error(chalk.gray(' Create a profile: nulljs profile create <name>'))
289
- console.error(chalk.gray(' Or switch to existing: nulljs profile use <name>'))
290
- process.exit(1)
291
- }
292
-
293
- if (!profileExists(activeProfile)) {
294
- console.error(chalk.red(`❌ Active profile '${activeProfile}' does not exist.`))
295
- console.error(chalk.gray(' Available profiles:'))
296
- const profiles = listProfiles()
297
- if (profiles.length > 0) {
298
- profiles.forEach((profile) => console.error(chalk.gray(` - ${profile}`)))
299
- console.error(chalk.gray(' Switch to an existing profile: nulljs profile use <name>'))
300
- } else {
301
- console.error(chalk.gray(' Create your first profile: nulljs profile create <name>'))
302
- }
303
- process.exit(1)
304
- }
305
-
306
- const config = loadProfile(activeProfile)
307
- return { profileName: activeProfile, config }
308
- }
309
-
310
- export const requireProfileConfiguration = (
311
- requiredFields: Array<'api' | 'key' | 'dev'> = ['api', 'key']
312
- ): { profileName: string; config: NulljsConfig } => {
313
- const { profileName, config } = requireActiveProfile()
314
-
315
- const missing: string[] = []
316
-
317
- if (requiredFields.includes('api') && !config.api) {
318
- missing.push('API URL')
319
- }
320
-
321
- if (requiredFields.includes('key') && (!config.key?.private || !config.key?.public)) {
322
- missing.push('authentication keys')
323
- }
324
-
325
- if (requiredFields.includes('dev') && !config.dev) {
326
- missing.push('dev configuration')
327
- }
328
-
329
- if (missing.length > 0) {
330
- console.error(chalk.red(`❌ Profile '${profileName}' is missing required configuration:`))
331
- missing.forEach((field) => console.error(chalk.yellow(` - ${field}`)))
332
- console.error('')
333
- console.error(chalk.gray(' Configure your profile:'))
334
- if (missing.includes('API URL')) {
335
- console.error(chalk.gray(' nulljs api <url>'))
336
- }
337
- if (missing.includes('authentication keys')) {
338
- console.error(chalk.gray(' nulljs auth'))
339
- }
340
- if (missing.includes('dev configuration')) {
341
- console.error(chalk.gray(' Check your .nulljs/profiles/<profile>.json file'))
342
- }
343
- process.exit(1)
344
- }
345
-
346
- return { profileName, config }
347
- }
@@ -1,244 +0,0 @@
1
- import { basename } from 'path'
2
- import { existsSync, readFileSync, writeFileSync } from 'fs'
3
- import { join } from 'path'
4
- import { build } from 'vite'
5
- import { createHash } from 'crypto'
6
- import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup'
7
- import chalk from 'chalk'
8
-
9
- import { functionConfig, spaClientConfig, ssrConfigConfig } from './bundle'
10
- import { createDeployment } from './api'
11
- import { getCloudPath } from './config'
12
-
13
- export const isReact = (file: string) => file.endsWith('.tsx')
14
- export const isAsset = (file: string) => file.endsWith('.css')
15
- export const isTypescript = (file: string) => file.endsWith('.ts')
16
- export const isJavaScript = (file: string) => file.endsWith('.js')
17
- export const getFileName = (path: string) => basename(path)
18
-
19
- export const isOutputAsset = (output: OutputAsset | OutputChunk): output is OutputAsset => {
20
- return output.type === 'asset'
21
- }
22
-
23
- export const isOutputChunk = (output: OutputAsset | OutputChunk): output is OutputChunk => {
24
- return output.type === 'chunk'
25
- }
26
-
27
- export type DeploymentType = 'react' | 'function'
28
-
29
- export type Deployment = {
30
- name: string
31
- type: DeploymentType
32
- assets: Array<{
33
- fileName: string
34
- code: string
35
- }>
36
- }
37
-
38
- // Persistent cache utilities
39
- const getCachePath = (): string => {
40
- const nulljsPath = getCloudPath()
41
- return join(nulljsPath, 'deployment-cache.json')
42
- }
43
-
44
- const loadDeploymentCache = (): Map<string, string> => {
45
- const cachePath = getCachePath()
46
-
47
- if (!existsSync(cachePath)) {
48
- return new Map()
49
- }
50
-
51
- try {
52
- const cacheContent = readFileSync(cachePath, 'utf-8')
53
- const cacheObject = JSON.parse(cacheContent)
54
- return new Map(Object.entries(cacheObject))
55
- } catch {
56
- // If cache is corrupted, start fresh
57
- return new Map()
58
- }
59
- }
60
-
61
- const saveDeploymentCache = (cache: Map<string, string>): void => {
62
- const cachePath = getCachePath()
63
- const nulljsPath = getCloudPath()
64
-
65
- // Ensure .nulljs directory exists
66
- if (!existsSync(nulljsPath)) {
67
- require('fs').mkdirSync(nulljsPath, { recursive: true })
68
- }
69
-
70
- const cacheObject = Object.fromEntries(cache)
71
- writeFileSync(cachePath, JSON.stringify(cacheObject, null, 2), 'utf-8')
72
- }
73
-
74
- // Load cache on startup
75
- let deploymentHashCache = loadDeploymentCache()
76
-
77
- const generateDeploymentHash = (deployment: Deployment): string => {
78
- const assetsString = deployment.assets
79
- .map((asset) => `${asset.fileName}:${asset.code}`)
80
- .sort()
81
- .join('|')
82
-
83
- return createHash('sha256').update(assetsString).digest('hex')
84
- }
85
-
86
- const getCachedHash = (deploymentName: string, type: DeploymentType): string | null => {
87
- const cacheKey = `${type}-${deploymentName}`
88
- return deploymentHashCache.get(cacheKey) || null
89
- }
90
-
91
- const saveCachedHash = (deploymentName: string, type: DeploymentType, hash: string): void => {
92
- const cacheKey = `${type}-${deploymentName}`
93
- deploymentHashCache.set(cacheKey, hash)
94
- saveDeploymentCache(deploymentHashCache)
95
- }
96
-
97
- const hasDeploymentChanged = (
98
- deployment: Deployment,
99
- force: boolean = false,
100
- logger: any = console
101
- ): boolean => {
102
- if (force) {
103
- logger.log(`Force flag set for ${deployment.name}, deploying...`)
104
- return true
105
- }
106
-
107
- const currentHash = generateDeploymentHash(deployment)
108
- const cachedHash = getCachedHash(deployment.name, deployment.type)
109
-
110
- if (!cachedHash) {
111
- logger.log(`No cached build found for ${deployment.name}, deploying...`)
112
- return true
113
- }
114
-
115
- const hasChanged = currentHash !== cachedHash
116
-
117
- if (hasChanged) {
118
- logger.log(`Build changed for ${deployment.name}, deploying...`)
119
- } else {
120
- logger.log(`No changes detected for ${deployment.name}, skipping deployment`)
121
- }
122
-
123
- return hasChanged
124
- }
125
-
126
- export type DeploymentResult = {
127
- deployed: boolean
128
- cached: boolean
129
- error?: Error
130
- }
131
-
132
- export const deployFunction = async (
133
- path: string,
134
- logger: any = console,
135
- force: boolean = false
136
- ): Promise<DeploymentResult> => {
137
- const file = basename(path)
138
-
139
- const deployment: Deployment = {
140
- name: file,
141
- type: 'function',
142
- assets: []
143
- }
144
-
145
- try {
146
- const functionBuild = (await build(functionConfig(path))) as RollupOutput
147
-
148
- const handler = functionBuild.output.find(
149
- (output) => output.fileName === 'handler.js'
150
- ) as OutputChunk
151
-
152
- if (!handler) {
153
- logger.log(chalk.yellow(`Handler not found for ${deployment.name}`))
154
- return { deployed: false, cached: false, error: new Error('Handler not found') }
155
- }
156
-
157
- deployment.assets.push({
158
- fileName: handler.fileName,
159
- code: handler.code
160
- })
161
-
162
- if (!hasDeploymentChanged(deployment, force, logger)) {
163
- return { deployed: false, cached: true }
164
- }
165
-
166
- await createDeployment(deployment, logger)
167
-
168
- const hash = generateDeploymentHash(deployment)
169
- saveCachedHash(deployment.name, deployment.type, hash)
170
-
171
- return { deployed: true, cached: false }
172
- } catch (error) {
173
- logger.error ? logger.error(error) : logger.log(`Error: ${error}`)
174
- return { deployed: false, cached: false, error: error as Error }
175
- }
176
- }
177
-
178
- export const deployPage = async (path: string) => {
179
- const file = basename(path)
180
-
181
- const deployment: Deployment = {
182
- name: file,
183
- type: 'react',
184
- assets: []
185
- }
186
-
187
- // const serverBuild = (await build(ssrServerConfig(path))) as RollupOutput
188
- // const server = serverBuild.output.pop() as OutputChunk
189
- //
190
- // deployment.assets.push({
191
- // fileName: 'server.js',
192
- // code: server.code
193
- // })
194
-
195
- const configBuild = (await build(ssrConfigConfig(path))) as RollupOutput
196
- const config = configBuild.output.pop() as OutputChunk
197
-
198
- deployment.assets.push({
199
- fileName: 'config.js',
200
- code: config.code
201
- })
202
-
203
- // const propsBuild = (await build(ssrPropsConfig(path))) as RollupOutput
204
- // const props = propsBuild.output.pop() as OutputChunk
205
- //
206
- // deployment.assets.push({
207
- // fileName: 'props.js',
208
- // code: props.code
209
- // })
210
-
211
- const clientConfig = spaClientConfig(path)
212
- const clientBuild = (await build(clientConfig)) as RollupOutput
213
-
214
- for (const clientAsset of clientBuild.output) {
215
- const fileName = getFileName(clientAsset.fileName)
216
-
217
- if (isOutputChunk(clientAsset)) {
218
- deployment.assets.push({
219
- fileName,
220
- code: clientAsset.code
221
- })
222
- }
223
-
224
- if (isOutputAsset(clientAsset)) {
225
- deployment.assets.push({
226
- fileName,
227
- code: clientAsset.source as string
228
- })
229
- }
230
- }
231
-
232
- if (!hasDeploymentChanged(deployment)) {
233
- return // Skip deployment if no changes
234
- }
235
-
236
- try {
237
- await createDeployment(deployment)
238
- const hash = generateDeploymentHash(deployment)
239
- saveCachedHash(deployment.name, deployment.type, hash)
240
- } catch (error) {
241
- console.error(error)
242
- throw error // Re-throw the error so it propagates to the caller
243
- }
244
- }