@exreve/exk 1.0.0

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.
@@ -0,0 +1,111 @@
1
+ import fs from 'fs/promises'
2
+ import path from 'path'
3
+
4
+ const toAbsolutePath = (p: string) => path.isAbsolute(p) ? p : path.resolve(p)
5
+
6
+ interface ProjectCreateRequest {
7
+ projectId: string
8
+ name: string
9
+ path: string
10
+ sourcePath?: string
11
+ }
12
+
13
+ interface ProjectInfo {
14
+ projectId: string
15
+ name: string
16
+ path: string
17
+ sourcePath?: string
18
+ exists: boolean
19
+ }
20
+
21
+ export async function createProject(request: ProjectCreateRequest): Promise<{ success: boolean; error?: string; actualPath?: string }> {
22
+ try {
23
+ const { projectId, name, path: projectPath, sourcePath } = request
24
+ const absolutePath = toAbsolutePath(projectPath)
25
+
26
+ // Check if source path exists (if linking)
27
+ if (sourcePath) {
28
+ const absoluteSourcePath = toAbsolutePath(sourcePath)
29
+ try {
30
+ const sourceStat = await fs.stat(absoluteSourcePath)
31
+ if (!sourceStat.isDirectory()) {
32
+ return { success: false, error: 'Source path is not a directory' }
33
+ }
34
+ } catch {
35
+ return { success: false, error: 'Source path does not exist' }
36
+ }
37
+ }
38
+
39
+ // Check if project directory already exists
40
+ try {
41
+ const stat = await fs.stat(absolutePath)
42
+ if (!stat.isDirectory()) {
43
+ return { success: false, error: 'Path exists but is not a directory' }
44
+ }
45
+ } catch {
46
+ // Directory doesn't exist, create it
47
+ try {
48
+ await fs.mkdir(absolutePath, { recursive: true })
49
+ } catch (error: any) {
50
+ return { success: false, error: `Failed to create directory: ${error.message}` }
51
+ }
52
+ }
53
+
54
+ // If linking to source, create symlink or copy (for now, just validate)
55
+ // In a real implementation, you might want to create a symlink or copy files
56
+ if (sourcePath) {
57
+ // For now, we just validate that both paths exist
58
+ // The actual linking can be done by the application logic
59
+ }
60
+
61
+ return { success: true, actualPath: absolutePath }
62
+ } catch (error: any) {
63
+ return { success: false, error: error.message }
64
+ }
65
+ }
66
+
67
+ export async function deleteProject(projectPath: string): Promise<{ success: boolean; error?: string }> {
68
+ try {
69
+ const absolutePath = toAbsolutePath(projectPath)
70
+
71
+ try {
72
+ const stat = await fs.stat(absolutePath)
73
+ if (!stat.isDirectory()) {
74
+ return { success: false, error: 'Path is not a directory' }
75
+ }
76
+ } catch {
77
+ // Directory doesn't exist, consider it deleted
78
+ return { success: true }
79
+ }
80
+
81
+ // Don't delete the directory on device - just succeed
82
+ // The directory will remain on the filesystem
83
+ return { success: true }
84
+ } catch (error: any) {
85
+ return { success: false, error: error.message }
86
+ }
87
+ }
88
+
89
+ export async function getProjectInfo(projectPath: string): Promise<ProjectInfo | null> {
90
+ try {
91
+ const absolutePath = toAbsolutePath(projectPath)
92
+
93
+ try {
94
+ const stat = await fs.stat(absolutePath)
95
+ if (!stat.isDirectory()) {
96
+ return null
97
+ }
98
+
99
+ return {
100
+ projectId: '', // Will be set by caller
101
+ name: path.basename(absolutePath),
102
+ path: absolutePath,
103
+ exists: true
104
+ }
105
+ } catch {
106
+ return null
107
+ }
108
+ } catch {
109
+ return null
110
+ }
111
+ }
@@ -0,0 +1,218 @@
1
+ import type { ProjectApp } from './shared/types'
2
+
3
+ /**
4
+ * Generate TypeScript runner code for an app
5
+ */
6
+ export function generateRunnerCode(app: ProjectApp, projectPath: string): string {
7
+ const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_') // Sanitize app name for filename
8
+ const appType = app.appType || (app.framework?.toLowerCase().includes('react') ||
9
+ app.framework?.toLowerCase().includes('vue') ||
10
+ app.framework?.toLowerCase().includes('angular') ||
11
+ app.framework?.toLowerCase().includes('svelte') ? 'static-frontend' : 'backend')
12
+
13
+ if (appType === 'static-frontend') {
14
+ return generateStaticFrontendRunner(app, projectPath)
15
+ } else {
16
+ return generateBackendRunner(app, projectPath)
17
+ }
18
+ }
19
+
20
+ function generateStaticFrontendRunner(app: ProjectApp, projectPath: string): string {
21
+ const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_') // Sanitize app name for filename
22
+ const port = app.port || 3000
23
+ const buildDir = app.buildDir || 'dist'
24
+ const appDir = app.directory || ''
25
+
26
+ return `import Fastify from 'fastify'
27
+ import fastifyStatic from '@fastify/static'
28
+ import path from 'path'
29
+ import fs from 'fs/promises'
30
+ import { fileURLToPath } from 'url'
31
+ import { dirname } from 'path'
32
+
33
+ const __filename = fileURLToPath(import.meta.url)
34
+ const __dirname = dirname(__filename)
35
+ const PORT = ${port}
36
+ const BUILD_DIR = path.join(__dirname, ${appDir ? `'${appDir}', ` : ''}'${buildDir}')
37
+
38
+ async function start() {
39
+ // Check if build directory exists
40
+ try {
41
+ await fs.access(BUILD_DIR)
42
+ } catch {
43
+ console.error(\`✗ Build directory not found: \${BUILD_DIR}\`)
44
+ console.error(' Please build the project first using: ' + (process.env.BUILD_COMMAND || 'npm run build'))
45
+ process.exit(1)
46
+ }
47
+
48
+ const fastify = Fastify({
49
+ logger: {
50
+ level: 'info',
51
+ transport: {
52
+ target: 'pino-pretty',
53
+ options: {
54
+ translateTime: 'HH:MM:ss Z',
55
+ ignore: 'pid,hostname',
56
+ },
57
+ },
58
+ },
59
+ })
60
+
61
+ // Register static file serving
62
+ await fastify.register(fastifyStatic, {
63
+ root: BUILD_DIR,
64
+ prefix: '/',
65
+ })
66
+
67
+ // Request logging
68
+ fastify.addHook('onRequest', async (request, reply) => {
69
+ const logLine = \`[\${new Date().toISOString()}] \${request.method} \${request.url} - \${reply.statusCode}\\n\`
70
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
71
+ })
72
+
73
+ // Error handling
74
+ fastify.setErrorHandler(async (error, request, reply) => {
75
+ const logLine = \`[\${new Date().toISOString()}] ERROR: \${error.message} - \${request.method} \${request.url}\\n\`
76
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
77
+ reply.status(500).send({ error: 'Internal Server Error' })
78
+ })
79
+
80
+ try {
81
+ await fastify.listen({ port: PORT, host: '0.0.0.0' })
82
+ console.log(\`✓ Static server started on port \${PORT}\`)
83
+ console.log(\` Serving from: \${BUILD_DIR}\`)
84
+ } catch (error: any) {
85
+ console.error(\`✗ Failed to start server:\`, error.message)
86
+ process.exit(1)
87
+ }
88
+ }
89
+
90
+ // Handle graceful shutdown
91
+ process.on('SIGTERM', async () => {
92
+ console.log('Shutting down...')
93
+ await fastify.close()
94
+ process.exit(0)
95
+ })
96
+
97
+ process.on('SIGINT', async () => {
98
+ console.log('Shutting down...')
99
+ await fastify.close()
100
+ process.exit(0)
101
+ })
102
+
103
+ start()
104
+ `
105
+ }
106
+
107
+ function generateBackendRunner(app: ProjectApp, projectPath: string): string {
108
+ const appName = app.name.replace(/[^a-zA-Z0-9_-]/g, '_') // Sanitize app name for filename
109
+ const appDir = app.directory || ''
110
+ const startCommand = app.startCommand
111
+ const envVars = app.env || {}
112
+
113
+ const envString = Object.entries(envVars)
114
+ .map(([key, value]) => ` ${key}: '${value}',`)
115
+ .join('\n')
116
+
117
+ return `import { spawn } from 'child_process'
118
+ import fs from 'fs/promises'
119
+ import path from 'path'
120
+ import { fileURLToPath } from 'url'
121
+ import { dirname } from 'path'
122
+
123
+ const __filename = fileURLToPath(import.meta.url)
124
+ const __dirname = dirname(__filename)
125
+ const APP_DIR = path.join(__dirname, ${appDir ? `'${appDir}'` : 'undefined'})
126
+ const START_COMMAND = '${startCommand}'
127
+
128
+ const env = {
129
+ ...process.env,
130
+ ${envString}
131
+ NODE_ENV: process.env.NODE_ENV || 'development',
132
+ }
133
+
134
+ let childProcess: ReturnType<typeof spawn> | null = null
135
+
136
+ async function start() {
137
+ const workingDir = APP_DIR || process.cwd()
138
+
139
+ console.log(\`Starting backend app: ${app.name}\`)
140
+ console.log(\` Directory: \${workingDir}\`)
141
+ console.log(\` Command: \${START_COMMAND}\`)
142
+
143
+ const isWin = process.platform === 'win32'
144
+ const shell = isWin ? (process.env.COMSPEC || 'cmd.exe') : 'sh'
145
+ const args = isWin ? ['/c', START_COMMAND] : ['-c', START_COMMAND]
146
+ childProcess = spawn(shell, args, {
147
+ cwd: workingDir,
148
+ env,
149
+ stdio: ['ignore', 'pipe', 'pipe'],
150
+ })
151
+
152
+ if (!childProcess.pid) {
153
+ console.error('✗ Failed to spawn process')
154
+ process.exit(1)
155
+ }
156
+
157
+ console.log(\`✓ Backend started with PID: \${childProcess.pid}\`)
158
+
159
+ // Capture stdout
160
+ childProcess.stdout?.on('data', async (data: Buffer) => {
161
+ const output = data.toString()
162
+ const logLine = \`[\${new Date().toISOString()}] [STDOUT] \${output}\`
163
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
164
+ process.stdout.write(output)
165
+ })
166
+
167
+ // Capture stderr
168
+ childProcess.stderr?.on('data', async (data: Buffer) => {
169
+ const output = data.toString()
170
+ const logLine = \`[\${new Date().toISOString()}] [STDERR] \${output}\`
171
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
172
+ process.stderr.write(output)
173
+ })
174
+
175
+ // Handle process exit
176
+ childProcess.on('exit', async (code) => {
177
+ const logLine = \`[\${new Date().toISOString()}] Process exited with code \${code}\\n\`
178
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
179
+ console.log(\`Process exited with code \${code}\`)
180
+ childProcess = null
181
+ })
182
+
183
+ // Handle process errors
184
+ childProcess.on('error', async (error) => {
185
+ const logLine = \`[\${new Date().toISOString()}] Process error: \${error.message}\\n\`
186
+ await fs.appendFile('${appName}_runner.log', logLine, 'utf-8').catch(() => {})
187
+ console.error(\`Process error: \${error.message}\`)
188
+ })
189
+ }
190
+
191
+ // Handle graceful shutdown
192
+ process.on('SIGTERM', async () => {
193
+ console.log('Shutting down...')
194
+ if (childProcess) {
195
+ childProcess.kill('SIGTERM')
196
+ await new Promise(resolve => setTimeout(resolve, 2000))
197
+ if (childProcess) {
198
+ childProcess.kill('SIGKILL')
199
+ }
200
+ }
201
+ process.exit(0)
202
+ })
203
+
204
+ process.on('SIGINT', async () => {
205
+ console.log('Shutting down...')
206
+ if (childProcess) {
207
+ childProcess.kill('SIGTERM')
208
+ await new Promise(resolve => setTimeout(resolve, 2000))
209
+ if (childProcess) {
210
+ childProcess.kill('SIGKILL')
211
+ }
212
+ }
213
+ process.exit(0)
214
+ })
215
+
216
+ start()
217
+ `
218
+ }