mekong-cli 1.0.0 → 1.1.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.
- package/bin/mekong-cli.js +10 -0
- package/lib/init.js +410 -0
- package/package.json +1 -1
package/bin/mekong-cli.js
CHANGED
|
@@ -118,6 +118,16 @@ function parseArgs(argv) {
|
|
|
118
118
|
// Main
|
|
119
119
|
// ---------------------------------------------------------------------------
|
|
120
120
|
async function main() {
|
|
121
|
+
// init subcommand
|
|
122
|
+
if (process.argv[2] === 'init') {
|
|
123
|
+
const { runInit } = require('../lib/init.js')
|
|
124
|
+
runInit().catch(err => {
|
|
125
|
+
process.stderr.write(`${RED}mekong-cli init: ${err.message}${RESET}\n`)
|
|
126
|
+
process.exit(1)
|
|
127
|
+
})
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
const opts = parseArgs(process.argv);
|
|
122
132
|
|
|
123
133
|
if (opts.help) {
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const readline = require('readline')
|
|
7
|
+
|
|
8
|
+
const BOLD = '\x1b[1m'
|
|
9
|
+
const DIM = '\x1b[2m'
|
|
10
|
+
const CYAN = '\x1b[36m'
|
|
11
|
+
const GREEN = '\x1b[32m'
|
|
12
|
+
const YELLOW = '\x1b[33m'
|
|
13
|
+
const RED = '\x1b[31m'
|
|
14
|
+
const RESET = '\x1b[0m'
|
|
15
|
+
const CHECK = '\x1b[32m✓\x1b[0m'
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Framework tables
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const FRAMEWORKS = [
|
|
21
|
+
{ deps: ['next'], name: 'Next.js', cmd: 'next dev', port: 3000 },
|
|
22
|
+
{ deps: ['nuxt', 'nuxt3', 'nuxt-edge'], name: 'Nuxt', cmd: 'nuxt dev', port: 3000 },
|
|
23
|
+
{ deps: ['vite'], name: 'Vite', cmd: 'vite', port: 5173 },
|
|
24
|
+
{ deps: ['react-scripts'], name: 'CRA', cmd: 'react-scripts start', port: 3000 },
|
|
25
|
+
{ deps: ['@angular/core'], name: 'Angular', cmd: 'ng serve', port: 4200 },
|
|
26
|
+
{ deps: ['@sveltejs/kit'], name: 'SvelteKit', cmd: 'vite dev', port: 5173 },
|
|
27
|
+
{ deps: ['svelte'], name: 'Svelte', cmd: 'vite', port: 5173 },
|
|
28
|
+
{ deps: ['astro'], name: 'Astro', cmd: 'astro dev', port: 4321 },
|
|
29
|
+
{ deps: ['gatsby'], name: 'Gatsby', cmd: 'gatsby develop', port: 8000 },
|
|
30
|
+
{ deps: ['remix', '@remix-run/react'], name: 'Remix', cmd: 'remix dev', port: 3000 },
|
|
31
|
+
{ deps: ['@remix-run/dev'], name: 'Remix', cmd: 'remix dev', port: 3000 },
|
|
32
|
+
{ deps: ['express'], name: 'Express', cmd: 'node server.js', port: 3000 },
|
|
33
|
+
{ deps: ['fastify'], name: 'Fastify', cmd: 'node server.js', port: 3000 },
|
|
34
|
+
{ deps: ['hono', '@hono/node-server'], name: 'Hono', cmd: 'node server.js', port: 3000 },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const PY_FRAMEWORKS = [
|
|
38
|
+
{ pkg: 'fastapi', name: 'FastAPI', cmd: 'uvicorn main:app --reload', port: 8000 },
|
|
39
|
+
{ pkg: 'flask', name: 'Flask', cmd: 'flask run', port: 5000 },
|
|
40
|
+
{ pkg: 'django', name: 'Django', cmd: 'python manage.py runserver', port: 8000 },
|
|
41
|
+
{ pkg: 'starlette', name: 'Starlette', cmd: 'uvicorn main:app --reload', port: 8000 },
|
|
42
|
+
{ pkg: 'tornado', name: 'Tornado', cmd: 'python main.py', port: 8888 },
|
|
43
|
+
{ pkg: 'sanic', name: 'Sanic', cmd: 'sanic main.app', port: 8000 },
|
|
44
|
+
{ pkg: 'litestar', name: 'Litestar', cmd: 'uvicorn main:app --reload', port: 8000 },
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Input helpers
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
// Read all stdin lines upfront when stdin is not a TTY (piped/test mode),
|
|
52
|
+
// so readline auto-close on EOF does not swallow buffered answers.
|
|
53
|
+
function readAllStdinLines() {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const lines = []
|
|
56
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false })
|
|
57
|
+
rl.on('line', (l) => lines.push(l))
|
|
58
|
+
rl.on('close', () => resolve(lines))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Build an `ask(question)` function. In non-TTY (piped) mode, pre-reads all
|
|
63
|
+
// stdin lines into a queue so readline close-on-EOF doesn't eat answers.
|
|
64
|
+
// Returns { ask, close } where close() tears down the readline interface if any.
|
|
65
|
+
async function makeAsker() {
|
|
66
|
+
if (!process.stdin.isTTY) {
|
|
67
|
+
const lines = await readAllStdinLines()
|
|
68
|
+
let idx = 0
|
|
69
|
+
function ask(question) {
|
|
70
|
+
const answer = idx < lines.length ? lines[idx++] : ''
|
|
71
|
+
process.stdout.write(question + answer + '\n')
|
|
72
|
+
return Promise.resolve(answer)
|
|
73
|
+
}
|
|
74
|
+
return { ask, close: () => {} }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Interactive TTY
|
|
78
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
79
|
+
function ask(question) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
rl.question(question, (answer) => resolve(answer))
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
return { ask, close: () => rl.close() }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Filesystem helpers
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
function fileExists(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
fs.accessSync(filePath, fs.constants.F_OK)
|
|
93
|
+
return true
|
|
94
|
+
} catch {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Node.js detection
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
function detectNode(cwd) {
|
|
103
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
104
|
+
let pkg
|
|
105
|
+
try {
|
|
106
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
107
|
+
} catch (err) {
|
|
108
|
+
throw new Error(`Failed to parse package.json: ${err.message}`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allDeps = Object.assign({}, pkg.dependencies || {}, pkg.devDependencies || {})
|
|
112
|
+
|
|
113
|
+
let detected = null
|
|
114
|
+
for (const fw of FRAMEWORKS) {
|
|
115
|
+
if (fw.deps.some((d) => allDeps[d] !== undefined)) {
|
|
116
|
+
detected = { name: fw.name, cmd: fw.cmd, port: fw.port }
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Try to extract port from scripts.dev if it has --port N
|
|
122
|
+
const devScript = (pkg.scripts && pkg.scripts.dev) || null
|
|
123
|
+
if (devScript) {
|
|
124
|
+
const portMatch = devScript.match(/--port[=\s]+(\d+)/)
|
|
125
|
+
if (portMatch) {
|
|
126
|
+
const extractedPort = parseInt(portMatch[1], 10)
|
|
127
|
+
if (detected) {
|
|
128
|
+
detected.port = extractedPort
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// If no framework detected but dev script exists, use it as the command
|
|
132
|
+
if (!detected) {
|
|
133
|
+
detected = { name: null, cmd: devScript, port: 3000 }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { detected, pkg, pkgPath }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Python detection
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
function detectPython(cwd) {
|
|
144
|
+
// Django via manage.py
|
|
145
|
+
if (fileExists(path.join(cwd, 'manage.py'))) {
|
|
146
|
+
return { name: 'Django', cmd: 'python manage.py runserver', port: 8000 }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const packages = new Set()
|
|
150
|
+
|
|
151
|
+
// requirements.txt
|
|
152
|
+
const reqPath = path.join(cwd, 'requirements.txt')
|
|
153
|
+
if (fileExists(reqPath)) {
|
|
154
|
+
const lines = fs.readFileSync(reqPath, 'utf8').split('\n')
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
const clean = line.trim().split(/[>=<![\s]/)[0].toLowerCase()
|
|
157
|
+
if (clean) packages.add(clean)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// pyproject.toml — scan [project] dependencies section
|
|
162
|
+
const pyprojectPath = path.join(cwd, 'pyproject.toml')
|
|
163
|
+
if (fileExists(pyprojectPath)) {
|
|
164
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8')
|
|
165
|
+
const lines = content.split('\n')
|
|
166
|
+
let inDeps = false
|
|
167
|
+
for (const line of lines) {
|
|
168
|
+
const trimmed = line.trim()
|
|
169
|
+
if (trimmed === '[project]') { inDeps = false }
|
|
170
|
+
if (inDeps) {
|
|
171
|
+
if (trimmed.startsWith('[') && trimmed !== '[project.dependencies]') { inDeps = false; continue }
|
|
172
|
+
const clean = trimmed.replace(/^["']/, '').split(/[>=<![\s"']/)[0].toLowerCase()
|
|
173
|
+
if (clean && !clean.startsWith('#')) packages.add(clean)
|
|
174
|
+
}
|
|
175
|
+
if (trimmed === 'dependencies' || trimmed === '[project.dependencies]' ||
|
|
176
|
+
(trimmed.startsWith('dependencies') && trimmed.includes('='))) {
|
|
177
|
+
inDeps = true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const fw of PY_FRAMEWORKS) {
|
|
183
|
+
if (packages.has(fw.pkg)) {
|
|
184
|
+
return { name: fw.name, cmd: fw.cmd, port: fw.port }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Inject Node.js (package.json scripts)
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
async function injectNode(pkgPath, pkg, cmd, port, ask) {
|
|
195
|
+
if (pkg.scripts && pkg.scripts['dev:tunnel']) {
|
|
196
|
+
const answer = await ask(`${YELLOW}dev:tunnel already exists. Overwrite? (Y/n):${RESET} `)
|
|
197
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
198
|
+
process.stdout.write(`Skipped. No changes made.\n`)
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!pkg.scripts) pkg.scripts = {}
|
|
204
|
+
pkg.scripts['dev:tunnel'] = `mekong-cli --with "${cmd}" --port ${port}`
|
|
205
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Inject Python (Makefile)
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
function injectPython(cwd, cmd, port) {
|
|
213
|
+
const makefilePath = path.join(cwd, 'Makefile')
|
|
214
|
+
const target = `dev-tunnel:\n\tmekong ${cmd} --port ${port}\n`
|
|
215
|
+
|
|
216
|
+
if (fileExists(makefilePath)) {
|
|
217
|
+
const existing = fs.readFileSync(makefilePath, 'utf8')
|
|
218
|
+
fs.writeFileSync(makefilePath, existing.trimEnd() + '\n\n' + target, 'utf8')
|
|
219
|
+
} else {
|
|
220
|
+
fs.writeFileSync(makefilePath, target, 'utf8')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
process.stdout.write(`Also run directly: ${CYAN}mekong ${cmd} --port ${port}${RESET}\n`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Prompt for manual command + port
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
async function promptManual(ask) {
|
|
230
|
+
const cmd = await ask(`Enter dev server command: `)
|
|
231
|
+
const portStr = await ask(`Enter local port: `)
|
|
232
|
+
const port = parseInt(portStr.trim(), 10)
|
|
233
|
+
if (isNaN(port)) throw new Error(`Invalid port: ${portStr.trim()}`)
|
|
234
|
+
return { cmd: cmd.trim(), port }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Configure Node.js ecosystem
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
async function configureNode(cwd, ask) {
|
|
241
|
+
let { detected, pkg, pkgPath } = detectNode(cwd)
|
|
242
|
+
let cmd, port, frameworkName
|
|
243
|
+
|
|
244
|
+
if (detected) {
|
|
245
|
+
frameworkName = detected.name || 'custom'
|
|
246
|
+
cmd = detected.cmd
|
|
247
|
+
port = detected.port
|
|
248
|
+
|
|
249
|
+
const label = detected.name ? detected.name : 'project'
|
|
250
|
+
process.stdout.write(`\nDetected: ${BOLD}${label}${RESET} on port ${CYAN}${port}${RESET}\n`)
|
|
251
|
+
process.stdout.write(`Command: ${DIM}${cmd}${RESET}\n\n`)
|
|
252
|
+
process.stdout.write(`Will add to package.json:\n`)
|
|
253
|
+
process.stdout.write(` ${CYAN}"dev:tunnel": "mekong-cli --with \\"${cmd}\\" --port ${port}"${RESET}\n\n`)
|
|
254
|
+
|
|
255
|
+
const answer = await ask(`Confirm? (Y/n): `)
|
|
256
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
257
|
+
const manual = await promptManual(ask)
|
|
258
|
+
cmd = manual.cmd
|
|
259
|
+
port = manual.port
|
|
260
|
+
frameworkName = 'custom'
|
|
261
|
+
|
|
262
|
+
process.stdout.write(`\nWill add to package.json:\n`)
|
|
263
|
+
process.stdout.write(` ${CYAN}"dev:tunnel": "mekong-cli --with \\"${cmd}\\" --port ${port}"${RESET}\n\n`)
|
|
264
|
+
const confirm2 = await ask(`Confirm? (Y/n): `)
|
|
265
|
+
if (confirm2.trim().toLowerCase() === 'n') {
|
|
266
|
+
process.stdout.write(`Skipped. No changes made.\n`)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
process.stdout.write(`\n${YELLOW}No known Node.js framework detected.${RESET}\n`)
|
|
272
|
+
const manual = await promptManual(ask)
|
|
273
|
+
cmd = manual.cmd
|
|
274
|
+
port = manual.port
|
|
275
|
+
frameworkName = 'custom'
|
|
276
|
+
|
|
277
|
+
process.stdout.write(`\nWill add to package.json:\n`)
|
|
278
|
+
process.stdout.write(` ${CYAN}"dev:tunnel": "mekong-cli --with \\"${cmd}\\" --port ${port}"${RESET}\n\n`)
|
|
279
|
+
const confirm2 = await ask(`Confirm? (Y/n): `)
|
|
280
|
+
if (confirm2.trim().toLowerCase() === 'n') {
|
|
281
|
+
process.stdout.write(`Skipped. No changes made.\n`)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const wrote = await injectNode(pkgPath, pkg, cmd, port, ask)
|
|
287
|
+
if (!wrote) return
|
|
288
|
+
|
|
289
|
+
const label = detected && detected.name ? detected.name : frameworkName
|
|
290
|
+
process.stdout.write(`\n${CHECK} Done! mekong-cli is set up for ${BOLD}${label}${RESET}\n\n`)
|
|
291
|
+
process.stdout.write(`Run your tunnel:\n`)
|
|
292
|
+
process.stdout.write(` ${CYAN}npm run dev:tunnel${RESET}\n\n`)
|
|
293
|
+
process.stdout.write(`What it does:\n`)
|
|
294
|
+
process.stdout.write(` 1. Starts: ${DIM}${cmd}${RESET} (port ${port})\n`)
|
|
295
|
+
process.stdout.write(` 2. Waits for port ${port} to open\n`)
|
|
296
|
+
process.stdout.write(` 3. Opens a Mekong tunnel\n`)
|
|
297
|
+
process.stdout.write(` 4. Prints your public URL\n\n`)
|
|
298
|
+
process.stdout.write(`Make sure mekong binary is installed:\n`)
|
|
299
|
+
process.stdout.write(` ${CYAN}https://github.com/MuyleangIng/MekongTunnel/releases/latest${RESET}\n`)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Configure Python ecosystem
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
async function configurePython(cwd, ask) {
|
|
306
|
+
let detected = detectPython(cwd)
|
|
307
|
+
let cmd, port, frameworkName
|
|
308
|
+
|
|
309
|
+
if (detected) {
|
|
310
|
+
frameworkName = detected.name
|
|
311
|
+
cmd = detected.cmd
|
|
312
|
+
port = detected.port
|
|
313
|
+
|
|
314
|
+
process.stdout.write(`\nDetected: ${BOLD}${detected.name}${RESET} on port ${CYAN}${port}${RESET}\n`)
|
|
315
|
+
process.stdout.write(`Command: ${DIM}${cmd}${RESET}\n\n`)
|
|
316
|
+
process.stdout.write(`Will add to Makefile:\n`)
|
|
317
|
+
process.stdout.write(` ${CYAN}dev-tunnel:${RESET}\n`)
|
|
318
|
+
process.stdout.write(` ${CYAN} mekong ${cmd} --port ${port}${RESET}\n\n`)
|
|
319
|
+
|
|
320
|
+
const answer = await ask(`Confirm? (Y/n): `)
|
|
321
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
322
|
+
const manual = await promptManual(ask)
|
|
323
|
+
cmd = manual.cmd
|
|
324
|
+
port = manual.port
|
|
325
|
+
frameworkName = 'custom'
|
|
326
|
+
|
|
327
|
+
process.stdout.write(`\nWill add to Makefile:\n`)
|
|
328
|
+
process.stdout.write(` ${CYAN}dev-tunnel:${RESET}\n`)
|
|
329
|
+
process.stdout.write(` ${CYAN} mekong ${cmd} --port ${port}${RESET}\n\n`)
|
|
330
|
+
const confirm2 = await ask(`Confirm? (Y/n): `)
|
|
331
|
+
if (confirm2.trim().toLowerCase() === 'n') {
|
|
332
|
+
process.stdout.write(`Skipped. No changes made.\n`)
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
process.stdout.write(`\n${YELLOW}No known Python framework detected.${RESET}\n`)
|
|
338
|
+
const manual = await promptManual(ask)
|
|
339
|
+
cmd = manual.cmd
|
|
340
|
+
port = manual.port
|
|
341
|
+
frameworkName = 'custom'
|
|
342
|
+
|
|
343
|
+
process.stdout.write(`\nWill add to Makefile:\n`)
|
|
344
|
+
process.stdout.write(` ${CYAN}dev-tunnel:${RESET}\n`)
|
|
345
|
+
process.stdout.write(` ${CYAN} mekong ${cmd} --port ${port}${RESET}\n\n`)
|
|
346
|
+
const confirm2 = await ask(`Confirm? (Y/n): `)
|
|
347
|
+
if (confirm2.trim().toLowerCase() === 'n') {
|
|
348
|
+
process.stdout.write(`Skipped. No changes made.\n`)
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
injectPython(cwd, cmd, port)
|
|
354
|
+
|
|
355
|
+
process.stdout.write(`\n${CHECK} Done! mekong-tunnel is set up for ${BOLD}${frameworkName}${RESET}\n\n`)
|
|
356
|
+
process.stdout.write(`Run your tunnel:\n`)
|
|
357
|
+
process.stdout.write(` ${CYAN}make dev-tunnel${RESET}\n`)
|
|
358
|
+
process.stdout.write(` (or) ${CYAN}mekong ${cmd} --port ${port}${RESET}\n\n`)
|
|
359
|
+
process.stdout.write(`Make sure mekong binary is installed:\n`)
|
|
360
|
+
process.stdout.write(` ${CYAN}https://github.com/MuyleangIng/MekongTunnel/releases/latest${RESET}\n`)
|
|
361
|
+
process.stdout.write(` ${CYAN}pip install mekong-tunnel${RESET}\n`)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Main exported function
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
async function runInit() {
|
|
368
|
+
const cwd = process.cwd()
|
|
369
|
+
|
|
370
|
+
const hasPkg = fileExists(path.join(cwd, 'package.json'))
|
|
371
|
+
const hasPyProj = fileExists(path.join(cwd, 'pyproject.toml'))
|
|
372
|
+
const hasReqs = fileExists(path.join(cwd, 'requirements.txt'))
|
|
373
|
+
const hasManage = fileExists(path.join(cwd, 'manage.py'))
|
|
374
|
+
const hasPipfile = fileExists(path.join(cwd, 'Pipfile'))
|
|
375
|
+
|
|
376
|
+
const isNode = hasPkg
|
|
377
|
+
const isPython = hasPyProj || hasReqs || hasManage || hasPipfile
|
|
378
|
+
|
|
379
|
+
if (!isNode && !isPython) {
|
|
380
|
+
process.stderr.write(`${RED}No supported project found in current directory.${RESET}\n`)
|
|
381
|
+
process.exit(1)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const { ask, close } = await makeAsker()
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
if (isNode && isPython) {
|
|
388
|
+
process.stdout.write(`\nBoth ${BOLD}Node.js${RESET} and ${BOLD}Python${RESET} projects detected.\n`)
|
|
389
|
+
const answer = await ask(`Configure which? (Node.js / Python / Both) [Node.js]: `)
|
|
390
|
+
const choice = answer.trim().toLowerCase()
|
|
391
|
+
|
|
392
|
+
if (choice === 'python') {
|
|
393
|
+
await configurePython(cwd, ask)
|
|
394
|
+
} else if (choice === 'both') {
|
|
395
|
+
await configureNode(cwd, ask)
|
|
396
|
+
await configurePython(cwd, ask)
|
|
397
|
+
} else {
|
|
398
|
+
await configureNode(cwd, ask)
|
|
399
|
+
}
|
|
400
|
+
} else if (isNode) {
|
|
401
|
+
await configureNode(cwd, ask)
|
|
402
|
+
} else {
|
|
403
|
+
await configurePython(cwd, ask)
|
|
404
|
+
}
|
|
405
|
+
} finally {
|
|
406
|
+
close()
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = { runInit }
|