-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrun.js
152 lines (129 loc) · 4.66 KB
/
run.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
const subprocess = require('child_process')
const RED = 1, GREEN = 2, YELLOW = 3, BLUE = 4, MAGENTA = 5, CYAN = 6, WHITE = 7, GRAY = 60
const PRODUCTION = (process.argv[2] ?? process.env['ENV'] ?? '').startsWith('prod')
if (PRODUCTION) {
process.env['ENV'] = 'production'
process.env['NODE_ENV'] = 'production'
}
// Go into the dir of run.js
process.chdir(__dirname)
// Detect if already in Poetry virtual environment
function findPython() {
if (process.env.PYTHON) return process.env.PYTHON
if (process.env.POETRY_ACTIVE === '1') return 'python'
const exes = ["py -3.11", "python3.11", "python", "python3"]
for (const exe of exes) {
const cmd = exe.split(' ');
const child = subprocess.spawnSync(cmd[0], [...cmd.slice(1), '-V'], { encoding: 'utf8' });
if (child.status === 0 && child.stdout.includes('3.11.')) {
return `${exe} -m poetry run python`
}
}
fatal("Could not find Python 3.11, pleases install it.")
}
// TODO: some windows users might need a py -3.11 prefix
const PYTHON_PATH = findPython()
const CADDY_PATH = process.env.CADDY || 'caddy'
const createSubLogger = log => (level, msg) => log(prefix(level, {
error: RED,
warn: YELLOW,
info: MAGENTA,
debug: CYAN,
}[level], 5), '| ', msg)
function checkVersion(cmd, version, options = {}) {
const cmdParts = cmd.split(' ')
const child = subprocess.spawnSync(cmdParts[0], cmdParts.slice(1), { encoding: 'utf8', ...options });
if (child.status !== 0) {
fatal(`The program "${cmd}" failed with status code "${child.status}"`)
}
return child.stdout.includes(version)
}
const nodeVersion = process.versions.node.split('.').map(parseInt)
if (nodeVersion[0] < 18) {
fatal(`Invalid version (${process.version}) of NodeJS. Please use NodeJS v18 or newer.`)
}
if (!checkVersion(`${PYTHON_PATH} -V`, '3.11.', { cwd: 'backend' })) {
fatal("Incorrect version of Python. Please use Python 3.11")
}
if (!PRODUCTION) {
log('Running development server...')
spawn(prefix('Django', RED), `${PYTHON_PATH} manage.py runserver`, {
callback: djangoLog,
cwd: 'backend',
})
spawn(prefix('Vite', GREEN), 'npm start --silent', {
cwd: 'frontend',
})
spawn(prefix('Caddy', YELLOW), `${CADDY_PATH} run`, {callback: caddyLog})
} else {
log('Running production test server...')
spawn(prefix('Django', RED), `${PYTHON_PATH} manage.py runserver`, {
callback: djangoLog,
cwd: 'backend',
})
spawn(prefix('Caddy', YELLOW), `${CADDY_PATH} run --config Caddyfile.prod`, {
callback: caddyLog,
})
}
// Special logger for Caddy
function caddyLog(line) {
try {
const data = JSON.parse(line)
let {level, ts, logger, msg, ...rest} = data
if (Object.keys(rest).length > 0)
rest = color(JSON.stringify(rest), GRAY) //util.inspect(rest, {colors: true, depth: 10, breakLength: Infinity})
else
rest = ''
const colors = {error: RED, warn: YELLOW, info: MAGENTA}
level = prefix(level, colors[level] ?? WHITE, 5)
logger = logger ? color(logger, CYAN) + ' ' : ''
return `${level} | ${logger}${msg} ${rest}`
} catch (err) {
if (err instanceof SyntaxError)
return line
else
throw err
}
}
// Indicate the server is ready after Django starts
let ready = false
function djangoLog(line) {
if (!ready) {
let pfx = prefix('General', BLUE)
ready = true // TODO: maybe check vite too, larger projects might take longer to build
return (line + '\n'
+ `${pfx} | \n`
+ `${pfx} | Running at ${color(`http://localhost:${PRODUCTION ? '80' : '8080'}/`, YELLOW)}\n`
+ `${pfx} | `)
}
}
function prefix(str, col, len=7) {
return color(str.padEnd(len), col)
}
function color(str, color) {
return `\x1b[${30 + color}m${str}\x1b[0m`
}
function log(...args) {
console.log(`${prefix('General', BLUE)} |`, ...args)
}
function fatal(...args) {
console.log(`${prefix('Error', RED)} |`, ...args)
process.exit(1)
}
function spawn(name, cmd, options={}) {
cmd = cmd.split(' ')
const proc = subprocess.spawn(cmd[0], cmd.slice(1), { shell: true, stdio: 'pipe', ...options })
let data = '';
for (let stream of [proc.stdout, proc.stderr]) {
stream.on('data', chunk => {
data += chunk
const lines = data.split('\n')
data = lines.pop()
for (let line of lines) {
if (options.callback)
line = options.callback(line) ?? line
console.log(`${name} | ${line}`)
}
})
}
}