@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.
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
@@ -1,141 +0,0 @@
1
- import { Box, type DOMElement, measureElement, useInput } from 'ink'
2
- import { useEffect, useReducer, useRef } from 'react'
3
-
4
- interface ScrollAreaState {
5
- innerHeight: number
6
- height: number
7
- scrollTop: number
8
- }
9
-
10
- type ScrollAreaAction =
11
- | { type: 'SET_INNER_HEIGHT'; innerHeight: number }
12
- | { type: 'SET_HEIGHT'; height: number }
13
- | { type: 'SCROLL_DOWN' }
14
- | { type: 'SCROLL_UP' }
15
- | { type: 'SCROLL_TO_BOTTOM' }
16
- | { type: 'SCROLL_TO_TOP' }
17
-
18
- const reducer = (state: ScrollAreaState, action: ScrollAreaAction) => {
19
- switch (action.type) {
20
- case 'SET_INNER_HEIGHT':
21
- return {
22
- ...state,
23
- innerHeight: action.innerHeight
24
- }
25
- case 'SET_HEIGHT':
26
- return {
27
- ...state,
28
- height: action.height
29
- }
30
-
31
- case 'SCROLL_DOWN':
32
- return {
33
- ...state,
34
- scrollTop: Math.min(
35
- state.innerHeight <= state.height ? 0 : state.innerHeight - state.height,
36
- state.scrollTop + 1
37
- )
38
- }
39
-
40
- case 'SCROLL_UP':
41
- return {
42
- ...state,
43
- scrollTop: Math.max(0, state.scrollTop - 1)
44
- }
45
-
46
- case 'SCROLL_TO_BOTTOM':
47
- return {
48
- ...state,
49
- scrollTop: Math.max(0, state.innerHeight - state.height)
50
- }
51
-
52
- case 'SCROLL_TO_TOP':
53
- return {
54
- ...state,
55
- scrollTop: 0
56
- }
57
-
58
- default:
59
- return state
60
- }
61
- }
62
-
63
- export interface ScrollAreaProps extends React.PropsWithChildren {
64
- height: number
65
- autoScroll?: boolean
66
- onRef?: (scrollToBottom: () => void) => void
67
- onShutdown?: () => Promise<void>
68
- }
69
-
70
- export function ScrollArea({ height, children, autoScroll = false, onRef }: ScrollAreaProps) {
71
- const [state, dispatch] = useReducer(reducer, {
72
- height: height,
73
- scrollTop: 0,
74
- innerHeight: 0
75
- })
76
-
77
- const innerRef = useRef<DOMElement>(null)
78
-
79
- const scrollToBottom = () => {
80
- dispatch({ type: 'SCROLL_TO_BOTTOM' })
81
- }
82
-
83
- useEffect(() => {
84
- if (onRef) {
85
- onRef(scrollToBottom)
86
- }
87
- }, [onRef])
88
-
89
- useEffect(() => {
90
- dispatch({ type: 'SET_HEIGHT', height })
91
- }, [height])
92
-
93
- useEffect(() => {
94
- if (!innerRef.current) return
95
-
96
- const dimensions = measureElement(innerRef.current)
97
-
98
- dispatch({
99
- type: 'SET_INNER_HEIGHT',
100
- innerHeight: dimensions.height
101
- })
102
-
103
- if (autoScroll) {
104
- scrollToBottom()
105
- }
106
- }, [children, autoScroll])
107
-
108
- useInput((input) => {
109
- if (input === 'j') {
110
- dispatch({
111
- type: 'SCROLL_DOWN'
112
- })
113
- }
114
-
115
- if (input === 'k') {
116
- dispatch({
117
- type: 'SCROLL_UP'
118
- })
119
- }
120
-
121
- if (input === 'G') {
122
- dispatch({
123
- type: 'SCROLL_TO_BOTTOM'
124
- })
125
- }
126
-
127
- if (input === 'g') {
128
- dispatch({
129
- type: 'SCROLL_TO_TOP'
130
- })
131
- }
132
- })
133
-
134
- return (
135
- <Box height={height} flexDirection="column" flexGrow={1} overflow="hidden">
136
- <Box ref={innerRef} flexShrink={0} flexDirection="column" marginTop={-state.scrollTop}>
137
- {children}
138
- </Box>
139
- </Box>
140
- )
141
- }
@@ -1,67 +0,0 @@
1
- import React from 'react'
2
- import { Box, Text } from 'ink'
3
-
4
- export type Tab = {
5
- name: string
6
- status?: 'running' | 'stopped' | 'error' | 'loading'
7
- }
8
-
9
- type TabBarProps = {
10
- tabs: Tab[]
11
- activeTabIndex: number
12
- showKeyBindings?: boolean
13
- isAutoScrolling?: boolean
14
- }
15
-
16
- const getStatusSymbol = (status?: Tab['status']): string => {
17
- switch (status) {
18
- case 'running':
19
- return '●'
20
- case 'stopped':
21
- return '○'
22
- case 'error':
23
- return '✗'
24
- case 'loading':
25
- return '⋯'
26
- default:
27
- return '○'
28
- }
29
- }
30
-
31
- export const TabBar: React.FC<TabBarProps> = ({ tabs, activeTabIndex, showKeyBindings = true }) => {
32
- const tabsText =
33
- tabs.length === 0
34
- ? 'No services'
35
- : tabs
36
- .map((tab, index) => {
37
- const statusSymbol = getStatusSymbol(tab.status)
38
- const isActive = index === activeTabIndex
39
- const activeIndicator = isActive ? '►' : ' '
40
-
41
- return `${activeIndicator} ${index + 1}. ${statusSymbol} ${tab.name} `
42
- })
43
- .join('')
44
-
45
- const currentTab = tabs[activeTabIndex]
46
- const functionKeybinding =
47
- currentTab?.name === 'Functions' ? ' | d: deploy all functions | s: deploy secrets' : ''
48
- const keyBindings = showKeyBindings
49
- ? `h/l: switch panels | j/k: scroll | g/G: top/bottom | q: quit${functionKeybinding}`
50
- : ''
51
-
52
- return (
53
- <Box
54
- borderStyle="single"
55
- borderColor="white"
56
- paddingX={1}
57
- justifyContent="space-between"
58
- flexDirection="row">
59
- <Text>{tabsText}</Text>
60
- {showKeyBindings && (
61
- <Text color="gray" dimColor>
62
- {keyBindings}
63
- </Text>
64
- )}
65
- </Box>
66
- )
67
- }
@@ -1,71 +0,0 @@
1
- import { useCallback, useState } from 'react'
2
- import { Box, useStdout, useInput } from 'ink'
3
-
4
- import { TabBar, type Tab } from './components/tab-bar'
5
- import { ServerTab } from './tabs/server-tab'
6
- import { FunctionsTab } from './tabs/functions-tab'
7
- import { ViteTab } from './tabs/vite-tab'
8
- import { LoggingManager } from '../logging-manager'
9
- import { deployAllFunctions, deploySecrets } from '../function/utils'
10
-
11
- type UIProps = {
12
- loggingManager: LoggingManager
13
- onShutdown: () => Promise<void>
14
- tabs: Tab[]
15
- srcDir: string
16
- }
17
-
18
- export const UI: React.FC<UIProps> = ({ loggingManager, onShutdown, tabs, srcDir }) => {
19
- const { stdout } = useStdout()
20
- const [activeTabIndex, setActiveTabIndex] = useState(0)
21
-
22
- const switchToNextService = useCallback(() => {
23
- setActiveTabIndex((prev) => (prev + 1) % tabs.length)
24
- }, [tabs.length])
25
-
26
- const switchToPrevService = useCallback(() => {
27
- setActiveTabIndex((prev) => (prev - 1 + tabs.length) % tabs.length)
28
- }, [tabs.length])
29
-
30
- useInput(async (input, key) => {
31
- if (input === 'l') {
32
- switchToNextService()
33
- }
34
-
35
- if (input === 'h') {
36
- switchToPrevService()
37
- }
38
-
39
- if (input === 'd') {
40
- await deployAllFunctions(srcDir, loggingManager.getLogFunction('functions'))
41
- }
42
-
43
- if (input === 's') {
44
- await deploySecrets(srcDir, loggingManager.getLogFunction('functions'))
45
- }
46
-
47
- if (input === 'q' || (key.ctrl && input === 'c')) {
48
- await onShutdown()
49
- }
50
- })
51
-
52
- const renderActiveTab = () => {
53
- switch (activeTabIndex) {
54
- case 0:
55
- return <ServerTab loggingManager={loggingManager} />
56
- case 1:
57
- return <FunctionsTab loggingManager={loggingManager} />
58
- case 2:
59
- return <ViteTab loggingManager={loggingManager} />
60
- default:
61
- return <ServerTab loggingManager={loggingManager} />
62
- }
63
- }
64
-
65
- return (
66
- <Box flexDirection="column" width="100%" height={stdout?.rows - 1 || 24}>
67
- <TabBar tabs={tabs} activeTabIndex={activeTabIndex} />
68
- {renderActiveTab()}
69
- </Box>
70
- )
71
- }
@@ -1,76 +0,0 @@
1
- import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react'
2
-
3
- export type LogEntry = {
4
- id: string
5
- timestamp: Date
6
- service: 'server' | 'functions' | 'vite'
7
- level: 'info' | 'warn' | 'error'
8
- message: string
9
- }
10
-
11
- type LoggingContextType = {
12
- logs: Record<string, LogEntry[]>
13
- addLog: (service: LogEntry['service'], level: LogEntry['level'], message: string) => void
14
- clearLogs: (service?: LogEntry['service']) => void
15
- }
16
-
17
- const LoggingContext = createContext<LoggingContextType | undefined>(undefined)
18
-
19
- export const useLogging = () => {
20
- const context = useContext(LoggingContext)
21
- if (!context) {
22
- throw new Error('useLogging must be used within a LoggingProvider')
23
- }
24
- return context
25
- }
26
-
27
- type LoggingProviderProps = {
28
- children: ReactNode
29
- }
30
-
31
- export const LoggingProvider: React.FC<LoggingProviderProps> = ({ children }) => {
32
- const [logs, setLogs] = useState<Record<string, LogEntry[]>>({
33
- server: [],
34
- functions: [],
35
- vite: []
36
- })
37
-
38
- const addLog = useCallback(
39
- (service: LogEntry['service'], level: LogEntry['level'], message: string) => {
40
- const newLog: LogEntry = {
41
- id: `${service}-${Date.now()}-${Math.random()}`,
42
- timestamp: new Date(),
43
- service,
44
- level,
45
- message
46
- }
47
-
48
- setLogs((prev) => ({
49
- ...prev,
50
- [service]: [...prev[service], newLog]
51
- }))
52
- },
53
- []
54
- )
55
-
56
- const clearLogs = useCallback((service?: LogEntry['service']) => {
57
- if (service) {
58
- setLogs((prev) => ({
59
- ...prev,
60
- [service]: []
61
- }))
62
- } else {
63
- setLogs({
64
- server: [],
65
- functions: [],
66
- vite: []
67
- })
68
- }
69
- }, [])
70
-
71
- return (
72
- <LoggingContext.Provider value={{ logs, addLog, clearLogs }}>
73
- {children}
74
- </LoggingContext.Provider>
75
- )
76
- }
@@ -1,35 +0,0 @@
1
- import React from 'react'
2
- import { Box, Text, useStdout } from 'ink'
3
- import { ScrollArea } from '../components/scroll-area'
4
- import { LoggingManager } from '../../logging-manager'
5
- import { useLogging } from '../use-logging'
6
-
7
- type FunctionsTabProps = {
8
- loggingManager: LoggingManager
9
- }
10
-
11
- export const FunctionsTab: React.FC<FunctionsTabProps> = ({ loggingManager }) => {
12
- const { stdout } = useStdout()
13
- const { logs } = useLogging(loggingManager, 'functions')
14
-
15
- return (
16
- <ScrollArea height={stdout.rows - 5} autoScroll={true}>
17
- <Box flexDirection="column" padding={1}>
18
- <Text color="green">Functions</Text>
19
- {logs.length === 0 ? (
20
- <Text dimColor>No function logs yet...</Text>
21
- ) : (
22
- <Box flexDirection="column">
23
- {logs.map((log) => (
24
- <Text
25
- key={`function-tab-${log.id}`}
26
- color={log.level === 'error' ? 'red' : log.level === 'warn' ? 'yellow' : 'white'}>
27
- [{log.timestamp.toLocaleTimeString()}] {log.message}
28
- </Text>
29
- ))}
30
- </Box>
31
- )}
32
- </Box>
33
- </ScrollArea>
34
- )
35
- }
@@ -1,36 +0,0 @@
1
- import React from 'react'
2
- import { Box, Text, useStdout } from 'ink'
3
-
4
- import { ScrollArea } from '../components/scroll-area'
5
- import { LoggingManager } from '../../logging-manager'
6
- import { useLogging } from '../use-logging'
7
-
8
- type ServerTabProps = {
9
- loggingManager: LoggingManager
10
- }
11
-
12
- export const ServerTab: React.FC<ServerTabProps> = ({ loggingManager }) => {
13
- const { stdout } = useStdout()
14
- const { logs } = useLogging(loggingManager, 'server')
15
-
16
- return (
17
- <ScrollArea height={stdout.rows - 5} autoScroll={true}>
18
- <Box flexDirection="column" padding={1}>
19
- <Text color="cyan">Server</Text>
20
- {logs.length === 0 ? (
21
- <Text dimColor>No server logs yet...</Text>
22
- ) : (
23
- <Box flexDirection="column">
24
- {logs.map((log) => (
25
- <Text
26
- key={log.id}
27
- color={log.level === 'error' ? 'red' : log.level === 'warn' ? 'yellow' : 'white'}>
28
- [{log.timestamp.toLocaleTimeString()}] {log.message}
29
- </Text>
30
- ))}
31
- </Box>
32
- )}
33
- </Box>
34
- </ScrollArea>
35
- )
36
- }
@@ -1,35 +0,0 @@
1
- import React from 'react'
2
- import { Box, Text, useStdout } from 'ink'
3
- import { ScrollArea } from '../components/scroll-area'
4
- import { LoggingManager } from '../../logging-manager'
5
- import { useLogging } from '../use-logging'
6
-
7
- type ViteTabProps = {
8
- loggingManager: LoggingManager
9
- }
10
-
11
- export const ViteTab: React.FC<ViteTabProps> = ({ loggingManager }) => {
12
- const { stdout } = useStdout()
13
- const { logs } = useLogging(loggingManager, 'vite')
14
-
15
- return (
16
- <ScrollArea height={stdout.rows - 5} autoScroll={true}>
17
- <Box flexDirection="column" padding={1}>
18
- <Text color="yellow">Vite</Text>
19
- {logs.length === 0 ? (
20
- <Text dimColor>No Vite logs yet...</Text>
21
- ) : (
22
- <Box flexDirection="column">
23
- {logs.map((log) => (
24
- <Text
25
- key={log.id}
26
- color={log.level === 'error' ? 'red' : log.level === 'warn' ? 'yellow' : 'white'}>
27
- [{log.timestamp.toLocaleTimeString()}] {log.message}
28
- </Text>
29
- ))}
30
- </Box>
31
- )}
32
- </Box>
33
- </ScrollArea>
34
- )
35
- }
@@ -1,34 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { LoggingManager, type LogEntry } from '../logging-manager'
3
-
4
- export const useLogging = (loggingManager: LoggingManager, service?: LogEntry['service']) => {
5
- const [logs, setLogs] = useState<LogEntry[]>(() => {
6
- return service ? loggingManager.getLogs(service) : loggingManager.getLogs()
7
- })
8
-
9
- const [allLogs, setAllLogs] = useState<Record<string, LogEntry[]>>(() => {
10
- return loggingManager.getAllLogs()
11
- })
12
-
13
- useEffect(() => {
14
- const unsubscribe = loggingManager.subscribe(() => {
15
- if (service) {
16
- setLogs(loggingManager.getLogs(service))
17
- } else {
18
- setLogs(loggingManager.getLogs())
19
- }
20
- setAllLogs(loggingManager.getAllLogs())
21
- })
22
-
23
- return () => {
24
- unsubscribe()
25
- }
26
- }, [loggingManager, service])
27
-
28
- return {
29
- logs,
30
- allLogs,
31
- addLog: loggingManager.addLog.bind(loggingManager),
32
- clearLogs: loggingManager.clearLogs.bind(loggingManager)
33
- }
34
- }
@@ -1,54 +0,0 @@
1
- import { createServer, type LogOptions } from 'vite'
2
- import { dirname, resolve } from 'path'
3
- import react from '@vitejs/plugin-react'
4
- import tailwindcss from '@tailwindcss/vite'
5
- import { writeFile } from 'node:fs/promises'
6
-
7
- import { createTempIndexHtml, createViteLogger } from './utils'
8
- import { getDevConfig } from '../../../lib/config'
9
-
10
- const PORT = 5173
11
-
12
- export const createVite = async (props: {
13
- srcDir: string
14
- indexTsxPath: string
15
- log: (msg: string, level?: string, options?: LogOptions) => void
16
- }) => {
17
- const devConfig = getDevConfig()
18
- const projectDir = dirname(props.srcDir)
19
- const tempIndexPath = resolve(projectDir, 'index.html')
20
-
21
- try {
22
- const indexHtml = createTempIndexHtml(props.indexTsxPath, projectDir)
23
- await writeFile(tempIndexPath, indexHtml)
24
-
25
- const server = await createServer({
26
- root: projectDir,
27
- plugins: [react(), tailwindcss()],
28
- customLogger: createViteLogger(props.log),
29
- server: {
30
- port: PORT,
31
- host: true,
32
- proxy: {
33
- '/api': {
34
- target: `http://localhost:${devConfig.gatewayPort}`,
35
- changeOrigin: true,
36
- secure: false
37
- },
38
- '/assets': {
39
- target: `http://localhost:${devConfig.gatewayPort}`,
40
- changeOrigin: true,
41
- secure: false
42
- }
43
- }
44
- }
45
- })
46
-
47
- await server.listen()
48
- props.log(`Vite server started on http://localhost:${PORT}`)
49
-
50
- return server
51
- } catch (error) {
52
- console.error('Error setting up Vite:', error)
53
- }
54
- }
@@ -1,71 +0,0 @@
1
- import { resolve, basename } from 'path'
2
- import type { Logger, LogOptions } from 'vite'
3
-
4
- export const createViteLogger = (
5
- log: (msg: string, level?: string, options?: LogOptions) => void
6
- ): Logger => {
7
- return {
8
- info: (msg: string, options) => {
9
- log(msg, 'info', options)
10
- },
11
- warn: (msg: string, options) => {
12
- log(msg, 'warn', options)
13
- },
14
- warnOnce: (msg: string, options) => {
15
- log(msg, 'warn', options)
16
- },
17
- error: (msg: string, options) => {
18
- log(msg, 'error', options)
19
- },
20
- clearScreen: () => {
21
- // Not implemented - we don't want to clear screens in our UI
22
- },
23
- hasErrorLogged: () => {
24
- return false // Simple implementation
25
- },
26
- hasWarned: false
27
- }
28
- }
29
-
30
- export const createTempIndexHtml = (componentPath: string, projectRoot: string): string => {
31
- const relativePath = resolve(componentPath).replace(projectRoot + '/', '')
32
- const componentName = basename(componentPath, '.tsx')
33
-
34
- return `<!DOCTYPE html>
35
- <html lang="en">
36
- <head>
37
- <meta charset="UTF-8" />
38
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
39
- <title>${componentName} - Dev Mode</title>
40
- <style>
41
- * {
42
- box-sizing: border-box;
43
- }
44
- body {
45
- margin: 0;
46
- padding: 0;
47
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
48
- }
49
- #root {
50
- min-height: 100vh;
51
- }
52
- </style>
53
- </head>
54
- <body>
55
- <div id="root"></div>
56
- <script type="module">
57
- import React from 'react'
58
- import ReactDOM from 'react-dom/client'
59
- import { Page } from '/${relativePath}'
60
-
61
- const root = ReactDOM.createRoot(document.getElementById('root'))
62
-
63
- root.render(
64
- React.createElement(React.StrictMode, null,
65
- React.createElement(Page)
66
- )
67
- )
68
- </script>
69
- </body>
70
- </html>`
71
- }