@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/ui.tsx ADDED
@@ -0,0 +1,363 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { createCliRenderer } from '@opentui/core'
3
+ import { createRoot } from '@opentui/react'
4
+ import {
5
+ getSystemLogs,
6
+ searchDeploymentLogs,
7
+ type LogEntry,
8
+ type SystemLogEntry
9
+ } from '@nulljs/api'
10
+
11
+ import { startServer, type BinarySource } from './lib/server'
12
+ import { startWatcher, forceDeployAll } from './lib/watcher'
13
+ import { startViteServer, stopViteServer } from './lib/vite'
14
+ import { getOrCreateLocalDevConfig, type Config } from './config'
15
+ import { DeploymentLogsPane } from './components/DeploymentLogsPane'
16
+ import { SystemLogsPane } from './components/SystemLogsPane'
17
+ import { DeployAnimation, type DeployedFunction } from './components/DeployAnimation'
18
+ import { Header } from './components/Header'
19
+ import { HelpModal } from './components/HelpModal'
20
+
21
+ type WatcherEvent = {
22
+ type: 'deploy' | 'change'
23
+ deployed?: DeployedFunction[]
24
+ filePath?: string
25
+ success?: boolean
26
+ skipped?: boolean
27
+ error?: string
28
+ affectedCount?: number
29
+ timestamp: Date
30
+ }
31
+
32
+ const App = (props: {
33
+ config: Config
34
+ binarySource: BinarySource
35
+ functionCount: number
36
+ viteRunning: boolean
37
+ onWatcherEvent: (handler: (event: WatcherEvent) => void) => void
38
+ onForceDeploy: () => Promise<void>
39
+ }) => {
40
+ const [error, setError] = useState<string | null>(null)
41
+ const [loading, setLoading] = useState(true)
42
+ const [deploymentLogs, setDeploymentLogs] = useState<LogEntry[]>([])
43
+ const [systemLogs, setSystemLogs] = useState<SystemLogEntry[]>([])
44
+ const [activeTab, setActiveTab] = useState<'deployment' | 'system'>('system')
45
+ const [autoScroll, setAutoScroll] = useState(true)
46
+ const [jumpTrigger, setJumpTrigger] = useState(0)
47
+ const [watcherStatus, setWatcherStatus] = useState<WatcherEvent | null>(null)
48
+ const [showKeyBindings, setShowKeyBindings] = useState(false)
49
+ const [isDeploying, setIsDeploying] = useState(false)
50
+
51
+ useEffect(() => {
52
+ props.onWatcherEvent((event) => {
53
+ console.error('DEBUG: received watcher event:', event.type, event.deployed?.length)
54
+ setWatcherStatus(event)
55
+ })
56
+ }, [props.onWatcherEvent])
57
+
58
+ useEffect(() => {
59
+ const fn = async () => {
60
+ try {
61
+ setLoading(true)
62
+ setError(null)
63
+
64
+ const [deploymentLogsData, systemLogsData] = await Promise.all([
65
+ searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
66
+ getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
67
+ ])
68
+
69
+ setDeploymentLogs(deploymentLogsData)
70
+ setSystemLogs(systemLogsData)
71
+ } catch (err) {
72
+ setError(err instanceof Error ? err.message : String(err))
73
+ console.error('Failed to fetch logs:', err)
74
+ } finally {
75
+ setLoading(false)
76
+ }
77
+ }
78
+
79
+ fn()
80
+ }, [props.config.key.public, props.config.api])
81
+
82
+ // Poll for new logs every 2 seconds
83
+ useEffect(() => {
84
+ const interval = setInterval(async () => {
85
+ if (loading) return
86
+
87
+ try {
88
+ const [newDeploymentLogs, newSystemLogs] = await Promise.all([
89
+ searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
90
+ getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
91
+ ])
92
+
93
+ // Only update if we have new logs
94
+ if (newDeploymentLogs.length > 0) {
95
+ setDeploymentLogs(newDeploymentLogs)
96
+ }
97
+ if (newSystemLogs.length > 0) {
98
+ setSystemLogs(newSystemLogs)
99
+ }
100
+ } catch (err) {
101
+ console.error('Failed to poll for new logs:', err)
102
+ }
103
+ }, 2000)
104
+
105
+ return () => clearInterval(interval)
106
+ }, [loading, props.config.key.public, props.config.api])
107
+
108
+ useEffect(() => {
109
+ const handleKeyPress = (data: Buffer) => {
110
+ const key = data.toString()
111
+
112
+ // Number keys to jump to specific panel
113
+ if (key === '1') {
114
+ setActiveTab('system')
115
+ return
116
+ }
117
+ if (key === '2') {
118
+ setActiveTab('deployment')
119
+ return
120
+ }
121
+
122
+ // Tab key cycles between panels
123
+ if (key === '\t') {
124
+ setActiveTab((prev) => (prev === 'system' ? 'deployment' : 'system'))
125
+ return
126
+ }
127
+
128
+ // h/l for vim-style navigation
129
+ if (key === 'h') {
130
+ setActiveTab('system')
131
+ return
132
+ }
133
+ if (key === 'l') {
134
+ setActiveTab('deployment')
135
+ return
136
+ }
137
+
138
+ // g - jump to top and disable auto-scroll
139
+ if (key === 'g') {
140
+ setAutoScroll(false)
141
+ setJumpTrigger((prev) => prev + 1)
142
+ return
143
+ }
144
+
145
+ // Shift+G - jump to bottom and enable auto-scroll
146
+ if (key === 'G') {
147
+ setAutoScroll(true)
148
+ setJumpTrigger((prev) => prev + 1)
149
+ return
150
+ }
151
+
152
+ // ? - toggle key bindings modal
153
+ if (key === '?') {
154
+ setShowKeyBindings((prev) => !prev)
155
+ return
156
+ }
157
+
158
+ // ESC - close key bindings modal
159
+ if (key === '\x1b') {
160
+ setShowKeyBindings(false)
161
+ return
162
+ }
163
+
164
+ // d - force deploy all
165
+ if (key === 'd' && !isDeploying) {
166
+ setIsDeploying(true)
167
+ props.onForceDeploy().finally(() => {
168
+ setIsDeploying(false)
169
+ })
170
+ return
171
+ }
172
+ }
173
+
174
+ process.stdin.setRawMode(true)
175
+ process.stdin.on('data', handleKeyPress)
176
+
177
+ return () => {
178
+ process.stdin.setRawMode(false)
179
+ process.stdin.off('data', handleKeyPress)
180
+ }
181
+ }, [isDeploying, props.onForceDeploy])
182
+
183
+ if (error) {
184
+ return (
185
+ <box flexGrow={1} flexDirection="column" padding={1}>
186
+ <text>
187
+ <span fg="red">Error: {error}</span>
188
+ </text>
189
+ </box>
190
+ )
191
+ }
192
+
193
+ return (
194
+ <box flexDirection="column">
195
+ <Header
196
+ activeTab={activeTab}
197
+ functionCount={props.functionCount}
198
+ viteRunning={props.viteRunning}
199
+ isDeploying={isDeploying}
200
+ binarySource={props.binarySource}
201
+ />
202
+
203
+ <box paddingTop={1} flexDirection="column">
204
+ {activeTab === 'system' ? (
205
+ <SystemLogsPane
206
+ logs={systemLogs}
207
+ loading={loading}
208
+ autoScroll={autoScroll}
209
+ jumpTrigger={jumpTrigger}
210
+ />
211
+ ) : (
212
+ <DeploymentLogsPane
213
+ logs={deploymentLogs}
214
+ loading={loading}
215
+ autoScroll={autoScroll}
216
+ jumpTrigger={jumpTrigger}
217
+ />
218
+ )}
219
+ {watcherStatus?.type === 'deploy' &&
220
+ watcherStatus.deployed &&
221
+ watcherStatus.deployed.length > 0 && (
222
+ <DeployAnimation
223
+ deployed={watcherStatus.deployed}
224
+ onComplete={() => {
225
+ console.error('DEBUG: animation complete')
226
+ setWatcherStatus(null)
227
+ }}
228
+ />
229
+ )}
230
+ </box>
231
+
232
+ <HelpModal visible={showKeyBindings} />
233
+ </box>
234
+ )
235
+ }
236
+
237
+ const main = async () => {
238
+ let serverInfo: Awaited<ReturnType<typeof startServer>> | null = null
239
+ let viteInfo: Awaited<ReturnType<typeof startViteServer>> | null = null
240
+ let stopWatcher: (() => void) | null = null
241
+ let isCleaningUp = false
242
+
243
+ const cleanup = async (exitAfter = true) => {
244
+ if (isCleaningUp) return
245
+ isCleaningUp = true
246
+
247
+ if (stopWatcher) {
248
+ stopWatcher()
249
+ stopWatcher = null
250
+ }
251
+ if (viteInfo) {
252
+ await stopViteServer(viteInfo)
253
+ viteInfo = null
254
+ }
255
+ if (serverInfo) {
256
+ const pid = serverInfo.process.pid
257
+ serverInfo = null
258
+ try {
259
+ // Kill the server process and its children
260
+ process.kill(pid, 'SIGTERM')
261
+ } catch {
262
+ // Process might already be dead
263
+ }
264
+ }
265
+ if (exitAfter) {
266
+ process.exit(0)
267
+ }
268
+ }
269
+
270
+ process.on('SIGINT', () => cleanup(true))
271
+ process.on('SIGTERM', () => cleanup(true))
272
+ process.on('exit', () => cleanup(false))
273
+
274
+ try {
275
+ serverInfo = await startServer()
276
+
277
+ const devConfig = await getOrCreateLocalDevConfig()
278
+
279
+ const srcPath = process.cwd() + '/src'
280
+
281
+ viteInfo = await startViteServer({
282
+ srcDir: srcPath,
283
+ onLog: (_msg, _level) => {}
284
+ })
285
+
286
+ let watcherEventHandler: ((event: WatcherEvent) => void) | null = null
287
+ let pendingEvents: WatcherEvent[] = []
288
+ let functionCount = 0
289
+
290
+ const sendEvent = (event: WatcherEvent) => {
291
+ if (watcherEventHandler) {
292
+ watcherEventHandler(event)
293
+ } else {
294
+ pendingEvents.push(event)
295
+ }
296
+ }
297
+
298
+ const renderer = await createCliRenderer()
299
+ const root = createRoot(renderer)
300
+
301
+ const handleForceDeploy = async () => {
302
+ const deployResults: DeployedFunction[] = []
303
+
304
+ await forceDeployAll(srcPath, {
305
+ onDeploy: (filePath, success, error) => {
306
+ deployResults.push({ name: filePath, success, error })
307
+ }
308
+ })
309
+
310
+ if (deployResults.length > 0) {
311
+ sendEvent({
312
+ type: 'deploy',
313
+ deployed: deployResults,
314
+ timestamp: new Date()
315
+ })
316
+ }
317
+ }
318
+
319
+ // Start watcher after renderer is ready
320
+ stopWatcher = await startWatcher(srcPath, {
321
+ silent: true,
322
+ onReady: (count) => {
323
+ functionCount = count
324
+ },
325
+ onDeployBatch: (results) => {
326
+ sendEvent({
327
+ type: 'deploy',
328
+ deployed: results.map((r) => ({
329
+ name: r.name,
330
+ success: r.success,
331
+ error: r.error
332
+ })),
333
+ timestamp: new Date()
334
+ })
335
+ }
336
+ })
337
+
338
+ root.render(
339
+ <App
340
+ config={devConfig}
341
+ binarySource={serverInfo.binarySource}
342
+ functionCount={functionCount}
343
+ viteRunning={viteInfo !== null}
344
+ onWatcherEvent={(handler) => {
345
+ watcherEventHandler = handler
346
+ // Flush any pending events
347
+ if (pendingEvents.length > 0) {
348
+ for (const event of pendingEvents) {
349
+ handler(event)
350
+ }
351
+ pendingEvents = []
352
+ }
353
+ }}
354
+ onForceDeploy={handleForceDeploy}
355
+ />
356
+ )
357
+ } catch (error) {
358
+ console.error('Failed to start server:', error)
359
+ cleanup()
360
+ }
361
+ }
362
+
363
+ main()
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "jsxImportSource": "@opentui/react",
10
+ "allowJs": true,
11
+
12
+ // Bundler mode
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": true,
16
+ "noEmit": true,
17
+
18
+ // Best practices
19
+ "strict": true,
20
+ "skipLibCheck": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedIndexedAccess": true,
23
+ "noImplicitOverride": true,
24
+
25
+ // Some stricter flags (disabled by default)
26
+ "noUnusedLocals": false,
27
+ "noUnusedParameters": false,
28
+ "noPropertyAccessFromIndexSignature": false
29
+ }
30
+ }
@@ -1,199 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- const https = require('https')
4
- const fs = require('fs')
5
- const path = require('path')
6
- const { createWriteStream } = require('fs')
7
- const tar = require('tar')
8
-
9
- // --- Configuration ---
10
- const S3_BASE_URL = 'https://nulljs.s3.eu-north-1.amazonaws.com'
11
- const S3_PREFIX = 'releases/'
12
- const DOWNLOAD_BASE_URL = `${S3_BASE_URL}/${S3_PREFIX}`
13
- // ---------------------
14
-
15
- function getPlatformInfo() {
16
- const platform = process.platform
17
- const arch = process.arch
18
-
19
- const platformMap = {
20
- linux: {
21
- x64: 'x86_64-unknown-linux-gnu',
22
- arm64: 'aarch64-unknown-linux-gnu'
23
- },
24
- darwin: {
25
- x64: 'x86_64-apple-darwin',
26
- arm64: 'aarch64-apple-darwin'
27
- }
28
- }
29
-
30
- const target = platformMap[platform]?.[arch]
31
- if (!target) {
32
- throw new Error(`Unsupported platform: ${platform}-${arch}`)
33
- }
34
-
35
- const extension = '.tar.gz'
36
- const binaryName = 'server'
37
-
38
- return { target, extension, binaryName }
39
- }
40
-
41
- // Helper to fetch XML and return response data
42
- async function fetchXml(url) {
43
- return new Promise((resolve, reject) => {
44
- https
45
- .get(url, (res) => {
46
- if (res.statusCode !== 200) {
47
- reject(
48
- new Error(`S3 Request Failed: ${res.statusCode}. Check if public listing is enabled.`)
49
- )
50
- return
51
- }
52
- let data = ''
53
- res.on('data', (chunk) => (data += chunk))
54
- res.on('end', () => resolve(data))
55
- })
56
- .on('error', reject)
57
- })
58
- }
59
-
60
- /**
61
- * Lists the S3 prefix, parses the XML, and determines the latest version tag.
62
- */
63
- async function getLatestVersionFromS3() {
64
- // Query S3 for folders using delimiter and prefix
65
- const listUrl = `${S3_BASE_URL}/?delimiter=/&prefix=${S3_PREFIX}`
66
- const xmlData = await fetchXml(listUrl)
67
-
68
- // Regex to find all CommonPrefixes entries (the version folders)
69
- const prefixRegex = /<Prefix>(releases\/v[^<]+)\/<\/Prefix>/g
70
- const versionFolders = []
71
- let match
72
-
73
- while ((match = prefixRegex.exec(xmlData)) !== null) {
74
- // Extract version part: "releases/v1.0.0/" -> "v1.0.0"
75
- const fullPrefix = match[1]
76
- const version = fullPrefix.substring(S3_PREFIX.length)
77
- versionFolders.push(version)
78
- }
79
-
80
- if (versionFolders.length === 0) {
81
- throw new Error('No version folders found in S3 bucket.')
82
- }
83
-
84
- // Sort versions to find the latest (using semantic versioning logic)
85
- const sortedVersions = versionFolders.sort((a, b) => {
86
- // Strips 'v', splits by '.', and converts to number arrays for comparison
87
- const aParts = a.replace('v', '').split('.').map(Number)
88
- const bParts = b.replace('v', '').split('.').map(Number)
89
-
90
- // Compare major, minor, and patch numbers
91
- for (let i = 0; i < 3; i++) {
92
- if (aParts[i] > bParts[i]) return 1
93
- if (aParts[i] < bParts[i]) return -1
94
- }
95
- return 0
96
- })
97
-
98
- // The latest version is the last one after sorting
99
- return sortedVersions[sortedVersions.length - 1]
100
- }
101
-
102
- async function downloadFile(url, destination) {
103
- return new Promise((resolve, reject) => {
104
- const file = createWriteStream(destination)
105
-
106
- https
107
- .get(url, (response) => {
108
- if (response.statusCode === 302 || response.statusCode === 301) {
109
- // Handle redirect
110
- return downloadFile(response.headers.location, destination).then(resolve).catch(reject)
111
- }
112
-
113
- if (response.statusCode !== 200) {
114
- reject(new Error(`Failed to download: ${response.statusCode}`))
115
- return
116
- }
117
-
118
- response.pipe(file)
119
-
120
- file.on('finish', () => {
121
- file.close()
122
- resolve()
123
- })
124
-
125
- file.on('error', reject)
126
- })
127
- .on('error', reject)
128
- })
129
- }
130
-
131
- async function extractArchive(archivePath, extractPath, binaryName) {
132
- // Extract tar.gz
133
- await tar.x({
134
- file: archivePath,
135
- cwd: extractPath
136
- })
137
-
138
- // Make binary executable
139
- const binaryPath = path.join(extractPath, binaryName)
140
- if (fs.existsSync(binaryPath)) {
141
- fs.chmodSync(binaryPath, '755')
142
- }
143
- }
144
-
145
- async function installServer() {
146
- try {
147
- console.log('📦 Installing nulljs server binary...')
148
-
149
- const { target, extension, binaryName } = getPlatformInfo()
150
- const binDir = path.join(__dirname, '..', 'bin')
151
- const archivePath = path.join(binDir, `server${extension}`)
152
- const binaryPath = path.join(binDir, 'server')
153
-
154
- // Create bin directory
155
- if (!fs.existsSync(binDir)) {
156
- fs.mkdirSync(binDir, { recursive: true })
157
- }
158
-
159
- // Check if binary already exists (optional optimization)
160
- if (fs.existsSync(binaryPath)) {
161
- console.log('✅ Server binary already installed')
162
- return
163
- }
164
-
165
- // 1. Determine Version from S3
166
- console.log('🔍 Checking for latest version on S3...')
167
- const version = await getLatestVersionFromS3()
168
- console.log(` Found version: ${version}`)
169
-
170
- // 2. Construct URL
171
- // URL format: https://.../releases/VERSION/nulljs-server-TARGET.tar.gz
172
- const downloadUrl = `${DOWNLOAD_BASE_URL}${version}/nulljs-server-${target}${extension}`
173
- console.log(` Downloading from: ${downloadUrl}`)
174
-
175
- // 3. Download
176
- await downloadFile(downloadUrl, archivePath)
177
- console.log('✅ Download completed')
178
-
179
- // 4. Extract
180
- console.log('📂 Extracting binary...')
181
- await extractArchive(archivePath, binDir, binaryName)
182
-
183
- // 5. Clean up archive
184
- fs.unlinkSync(archivePath)
185
-
186
- console.log('✅ nulljs server installed successfully')
187
- } catch (error) {
188
- console.error('❌ Failed to install server binary:', error.message)
189
- console.error('⚠️ You may need to build the server manually')
190
- process.exit(0)
191
- }
192
- }
193
-
194
- // Only run if called directly
195
- if (require.main === module) {
196
- installServer()
197
- }
198
-
199
- module.exports = { installServer }
@@ -1,16 +0,0 @@
1
- import chalk from 'chalk'
2
-
3
- import { saveApiUrl } from '../lib/config'
4
-
5
- const setApiUrl = (url: string, profileName?: string) => {
6
- try {
7
- saveApiUrl(url, profileName)
8
- const profileText = profileName ? ` to profile '${profileName}'` : ''
9
- console.log(chalk.green('✓'), `API URL saved${profileText}: ${chalk.blue(url)}`)
10
- } catch (error) {
11
- console.error(chalk.red('✗'), 'Failed to save API URL:', error)
12
- process.exit(1)
13
- }
14
- }
15
-
16
- export { setApiUrl }
@@ -1,54 +0,0 @@
1
- import chalk from 'chalk'
2
- import readline from 'node:readline'
3
-
4
- import { loadConfigWithProfile, saveKeys } from '../lib/config'
5
-
6
- const askQuestion = (query: string): Promise<string> => {
7
- const rl = readline.createInterface({
8
- input: process.stdin,
9
- output: process.stdout
10
- })
11
-
12
- return new Promise((resolve) =>
13
- rl.question(query, (answer) => {
14
- rl.close()
15
- resolve(answer.trim())
16
- })
17
- )
18
- }
19
-
20
- const auth = async (profileName?: string) => {
21
- const config = loadConfigWithProfile()
22
-
23
- if (config.key) {
24
- const answer = await askQuestion(
25
- 'Keys already exist. Are you sure you want to regenerate them? (Y/n): '
26
- )
27
-
28
- if (answer.toLowerCase() === 'n') {
29
- console.log(chalk.green('Public Key:'), chalk.blue(config.key.public))
30
- return
31
- }
32
- }
33
-
34
- const keyPair = await crypto.subtle.generateKey(
35
- {
36
- name: 'Ed25519',
37
- namedCurve: 'Ed25519'
38
- },
39
- true,
40
- ['sign', 'verify']
41
- )
42
-
43
- const privateKeyBuffer = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
44
- const publicKeyBuffer = await crypto.subtle.exportKey('spki', keyPair.publicKey)
45
-
46
- const privateKey = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer)))
47
- const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)))
48
-
49
- saveKeys(privateKey, publicKey, profileName)
50
-
51
- console.log(chalk.green('Public Key:'), chalk.blue(publicKey))
52
- }
53
-
54
- export { auth }
@@ -1,43 +0,0 @@
1
- import chalk from 'chalk'
2
- import { existsSync } from 'node:fs'
3
- import degit from 'degit'
4
- import path from 'node:path'
5
-
6
- const repo = 'tothalex/nulljs-template'
7
-
8
- const modifyName = async (newName: string, packagePath: string) => {
9
- const file = Bun.file(packagePath)
10
- const packageContent = await file.text()
11
- const packageJson = JSON.parse(packageContent)
12
-
13
- packageJson.name = newName
14
-
15
- await Bun.write(packagePath, JSON.stringify(packageJson, null, 2) + '\n')
16
- }
17
-
18
- const create = async (name: string) => {
19
- if (existsSync(name)) {
20
- console.log(chalk.red('Folder already exists: ') + chalk.bgRed(name))
21
- process.exit(0)
22
- }
23
-
24
- const targetDir = path.join(process.cwd(), name)
25
-
26
- try {
27
- const emitter = degit(repo, {
28
- cache: false,
29
- force: true,
30
- verbose: true
31
- })
32
-
33
- await emitter.clone(targetDir)
34
-
35
- await modifyName(name, targetDir + '/package.json')
36
- console.log(chalk.green('Project setup completed successfully!'))
37
- } catch (error) {
38
- console.error(chalk.red('An error occurred during project creation:'))
39
- console.error(error)
40
- }
41
- }
42
-
43
- export { create }