@gxp-dev/tools 2.0.87 → 2.0.88
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/lib/cli.js +6 -0
- package/bin/lib/commands/dev.js +182 -59
- package/package.json +1 -1
package/bin/lib/cli.js
CHANGED
|
@@ -157,6 +157,12 @@ const cli = yargs
|
|
|
157
157
|
default: false,
|
|
158
158
|
alias: "m",
|
|
159
159
|
},
|
|
160
|
+
json: {
|
|
161
|
+
describe:
|
|
162
|
+
"Emit all dev-server logs as newline-delimited JSON (one record per line) for cloud log collectors",
|
|
163
|
+
type: "boolean",
|
|
164
|
+
default: false,
|
|
165
|
+
},
|
|
160
166
|
},
|
|
161
167
|
devCommand,
|
|
162
168
|
)
|
package/bin/lib/commands/dev.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const path = require("path")
|
|
9
9
|
const fs = require("fs")
|
|
10
|
+
const { spawn } = require("child_process")
|
|
10
11
|
const shell = require("shelljs")
|
|
11
12
|
const dotenv = require("dotenv")
|
|
12
13
|
const {
|
|
@@ -16,6 +17,123 @@ const {
|
|
|
16
17
|
findExistingCertificates,
|
|
17
18
|
} = require("../utils")
|
|
18
19
|
|
|
20
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a logger that either emits NDJSON lines or plain text.
|
|
24
|
+
* In JSON mode, every record is a single line: {timestamp, service, level, message}
|
|
25
|
+
*/
|
|
26
|
+
function createLogger(jsonMode) {
|
|
27
|
+
function emit(level, service, message) {
|
|
28
|
+
if (jsonMode) {
|
|
29
|
+
process.stdout.write(
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
service,
|
|
33
|
+
level,
|
|
34
|
+
message,
|
|
35
|
+
}) + "\n",
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
const stream =
|
|
40
|
+
level === "error" || level === "warn" ? process.stderr : process.stdout
|
|
41
|
+
stream.write(message + "\n")
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
jsonMode,
|
|
45
|
+
info: (message, service = "GXDEV") => emit("info", service, message),
|
|
46
|
+
warn: (message, service = "GXDEV") => emit("warn", service, message),
|
|
47
|
+
error: (message, service = "GXDEV") => emit("error", service, message),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Spawn a service and pipe each line of its stdout/stderr through the logger.
|
|
53
|
+
* Returns the child process.
|
|
54
|
+
*/
|
|
55
|
+
function spawnService(name, command, logger) {
|
|
56
|
+
const child = spawn(command, {
|
|
57
|
+
shell: true,
|
|
58
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
59
|
+
env: process.env,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function pipe(stream, level) {
|
|
63
|
+
let buffer = ""
|
|
64
|
+
stream.setEncoding("utf8")
|
|
65
|
+
stream.on("data", (chunk) => {
|
|
66
|
+
buffer += chunk
|
|
67
|
+
let idx
|
|
68
|
+
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
69
|
+
const line = buffer.slice(0, idx).replace(/\r$/, "")
|
|
70
|
+
buffer = buffer.slice(idx + 1)
|
|
71
|
+
const clean = line.replace(ANSI_REGEX, "")
|
|
72
|
+
if (clean.length > 0) {
|
|
73
|
+
logger[level](clean, name)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
stream.on("end", () => {
|
|
78
|
+
if (buffer.length > 0) {
|
|
79
|
+
const clean = buffer.replace(ANSI_REGEX, "").trim()
|
|
80
|
+
if (clean) {
|
|
81
|
+
logger[level](clean, name)
|
|
82
|
+
}
|
|
83
|
+
buffer = ""
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pipe(child.stdout, "info")
|
|
89
|
+
pipe(child.stderr, "error")
|
|
90
|
+
return child
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run a list of services concurrently, wiring up line-by-line JSON logging
|
|
95
|
+
* and best-effort shutdown when any one exits or the user hits Ctrl+C.
|
|
96
|
+
*/
|
|
97
|
+
function runServicesJson(services, logger) {
|
|
98
|
+
const children = services.map((svc) => {
|
|
99
|
+
logger.info(`starting ${svc.name}: ${svc.command}`, svc.name)
|
|
100
|
+
return { svc, child: spawnService(svc.name, svc.command, logger) }
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
let shuttingDown = false
|
|
104
|
+
function shutdown(code) {
|
|
105
|
+
if (shuttingDown) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
shuttingDown = true
|
|
109
|
+
for (const { child } of children) {
|
|
110
|
+
if (!child.killed && child.exitCode === null) {
|
|
111
|
+
child.kill("SIGTERM")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
process.exit(code ?? 0)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const { svc, child } of children) {
|
|
118
|
+
child.on("exit", (code, signal) => {
|
|
119
|
+
logger.info(
|
|
120
|
+
`${svc.name} exited (code=${code ?? "null"}${
|
|
121
|
+
signal ? `, signal=${signal}` : ""
|
|
122
|
+
})`,
|
|
123
|
+
svc.name,
|
|
124
|
+
)
|
|
125
|
+
shutdown(code ?? 0)
|
|
126
|
+
})
|
|
127
|
+
child.on("error", (err) => {
|
|
128
|
+
logger.error(`${svc.name} failed to spawn: ${err.message}`, svc.name)
|
|
129
|
+
shutdown(1)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
process.on("SIGINT", () => shutdown(130))
|
|
134
|
+
process.on("SIGTERM", () => shutdown(143))
|
|
135
|
+
}
|
|
136
|
+
|
|
19
137
|
/**
|
|
20
138
|
* Get browser extension paths and commands
|
|
21
139
|
* @param {string} browser - "firefox" or "chrome"
|
|
@@ -96,6 +214,7 @@ function getBrowserExtensionConfig(browser, projectPath, paths, options = {}) {
|
|
|
96
214
|
* Development command - starts the dev server
|
|
97
215
|
*/
|
|
98
216
|
function devCommand(argv) {
|
|
217
|
+
const logger = createLogger(!!argv.json)
|
|
99
218
|
const paths = resolveGxPaths()
|
|
100
219
|
const projectPath = findProjectRoot()
|
|
101
220
|
|
|
@@ -105,13 +224,13 @@ function devCommand(argv) {
|
|
|
105
224
|
|
|
106
225
|
// Load .env file into process.env
|
|
107
226
|
if (fs.existsSync(envPath)) {
|
|
108
|
-
|
|
227
|
+
logger.info("📋 Loading environment variables from .env file")
|
|
109
228
|
dotenv.config({ path: envPath })
|
|
110
229
|
} else if (fs.existsSync(envExamplePath)) {
|
|
111
|
-
|
|
230
|
+
logger.info(
|
|
112
231
|
"💡 Tip: Create .env file from .env.example to customize your environment settings",
|
|
113
232
|
)
|
|
114
|
-
|
|
233
|
+
logger.info(" cp .env.example .env")
|
|
115
234
|
}
|
|
116
235
|
|
|
117
236
|
// Check for SSL certificates unless explicitly disabled
|
|
@@ -124,32 +243,32 @@ function devCommand(argv) {
|
|
|
124
243
|
const existingCerts = findExistingCertificates(certsDir)
|
|
125
244
|
|
|
126
245
|
if (!existingCerts) {
|
|
127
|
-
|
|
246
|
+
logger.warn(
|
|
128
247
|
"⚠ SSL certificates not found. Run 'npm run setup-ssl' to enable HTTPS",
|
|
129
248
|
)
|
|
130
|
-
|
|
249
|
+
logger.info("🌐 Starting HTTP development server...")
|
|
131
250
|
useHttps = false
|
|
132
251
|
} else {
|
|
133
|
-
|
|
134
|
-
|
|
252
|
+
logger.info("🔒 Starting HTTPS development server...")
|
|
253
|
+
logger.info(
|
|
135
254
|
`📁 Using certificate: ${path.basename(existingCerts.certPath)}`,
|
|
136
255
|
)
|
|
137
|
-
|
|
256
|
+
logger.info(`🔑 Using key: ${path.basename(existingCerts.keyPath)}`)
|
|
138
257
|
certPath = existingCerts.certPath
|
|
139
258
|
keyPath = existingCerts.keyPath
|
|
140
259
|
}
|
|
141
260
|
} else {
|
|
142
|
-
|
|
261
|
+
logger.info("🌐 Starting HTTP development server...")
|
|
143
262
|
}
|
|
144
263
|
|
|
145
264
|
// Determine final port value (priority: CLI arg > .env > default)
|
|
146
265
|
const finalPort = argv.port || process.env.NODE_PORT || 3000
|
|
147
|
-
|
|
266
|
+
logger.info(`🌐 Development server will start on port: ${finalPort}`)
|
|
148
267
|
|
|
149
268
|
// Check if mock API should be enabled
|
|
150
269
|
const withMock = argv["with-mock"]
|
|
151
270
|
if (withMock) {
|
|
152
|
-
|
|
271
|
+
logger.info("🎭 Mock API will be enabled")
|
|
153
272
|
}
|
|
154
273
|
|
|
155
274
|
// Socket server starts by default unless --no-socket is passed
|
|
@@ -159,15 +278,15 @@ function devCommand(argv) {
|
|
|
159
278
|
// Check for local server.js first, then runtime directory
|
|
160
279
|
const serverJs = resolveFilePath("server.cjs", "", "runtime")
|
|
161
280
|
if (!fs.existsSync(serverJs.path)) {
|
|
162
|
-
|
|
281
|
+
logger.warn("⚠ server.js not found. Skipping Socket.IO server.")
|
|
163
282
|
} else {
|
|
164
283
|
serverJsPath = serverJs.path
|
|
165
|
-
|
|
284
|
+
logger.info(
|
|
166
285
|
`📡 Starting Socket.IO server with nodemon... (${
|
|
167
286
|
serverJs.isLocal ? "local" : "package"
|
|
168
287
|
} version)`,
|
|
169
288
|
)
|
|
170
|
-
|
|
289
|
+
logger.info(`📁 Using: ${serverJsPath}`)
|
|
171
290
|
}
|
|
172
291
|
}
|
|
173
292
|
|
|
@@ -184,16 +303,16 @@ function devCommand(argv) {
|
|
|
184
303
|
fs.existsSync(path.join(projectPath, "vite.extend.mjs"))
|
|
185
304
|
|
|
186
305
|
if (hasLocalIndexHtml) {
|
|
187
|
-
|
|
306
|
+
logger.info("📁 Using local index.html")
|
|
188
307
|
}
|
|
189
308
|
if (hasLocalMainJs) {
|
|
190
|
-
|
|
309
|
+
logger.info("📁 Using local main.js")
|
|
191
310
|
}
|
|
192
311
|
if (hasLocalExtend) {
|
|
193
|
-
|
|
312
|
+
logger.info("🧩 Extending vite config from vite.extend.js")
|
|
194
313
|
}
|
|
195
314
|
if (!hasLocalIndexHtml && !hasLocalMainJs && !hasLocalExtend) {
|
|
196
|
-
|
|
315
|
+
logger.info(
|
|
197
316
|
"📦 Using runtime dev files (create vite.extend.js to customize)",
|
|
198
317
|
)
|
|
199
318
|
}
|
|
@@ -234,11 +353,11 @@ function devCommand(argv) {
|
|
|
234
353
|
port: finalPort,
|
|
235
354
|
})
|
|
236
355
|
if (firefoxConfig) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
356
|
+
logger.info("🦊 Firefox extension will launch with dev server")
|
|
357
|
+
logger.info(`📁 Extension path: ${firefoxConfig.extensionPath}`)
|
|
358
|
+
logger.info(`🌐 Start URL: ${firefoxConfig.startUrl}`)
|
|
240
359
|
} else {
|
|
241
|
-
|
|
360
|
+
logger.warn("⚠️ Firefox extension not found, skipping")
|
|
242
361
|
}
|
|
243
362
|
}
|
|
244
363
|
|
|
@@ -248,11 +367,11 @@ function devCommand(argv) {
|
|
|
248
367
|
port: finalPort,
|
|
249
368
|
})
|
|
250
369
|
if (chromeConfig) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
370
|
+
logger.info("🚀 Chrome extension will launch with dev server")
|
|
371
|
+
logger.info(`📁 Extension path: ${chromeConfig.extensionPath}`)
|
|
372
|
+
logger.info(`🌐 Start URL: ${chromeConfig.startUrl}`)
|
|
254
373
|
} else {
|
|
255
|
-
|
|
374
|
+
logger.warn("⚠️ Chrome extension not found, skipping")
|
|
256
375
|
}
|
|
257
376
|
}
|
|
258
377
|
|
|
@@ -261,54 +380,58 @@ function devCommand(argv) {
|
|
|
261
380
|
process.env.CHROME_EXTENSION_PATH = chromeConfig.extensionPath
|
|
262
381
|
}
|
|
263
382
|
|
|
264
|
-
// Build the command based on what's requested
|
|
265
|
-
let command
|
|
266
|
-
|
|
267
|
-
// Collect all processes to run
|
|
268
|
-
const processes = []
|
|
269
|
-
const names = []
|
|
270
|
-
const colors = []
|
|
271
|
-
|
|
272
383
|
// Normalize path separators to forward slashes for cross-platform shell compatibility
|
|
273
384
|
const normalizedViteConfigPath = viteConfigPath.replace(/\\/g, "/")
|
|
274
385
|
|
|
275
|
-
//
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
386
|
+
// Build the canonical service list (raw commands, no concurrently wrapping)
|
|
387
|
+
const services = []
|
|
388
|
+
services.push({
|
|
389
|
+
name: "VITE",
|
|
390
|
+
color: "cyan",
|
|
391
|
+
command: `npx vite dev --config "${normalizedViteConfigPath}"`,
|
|
392
|
+
})
|
|
280
393
|
|
|
281
|
-
// Socket server (on by default, skip if --no-socket or server.js not found)
|
|
282
394
|
if (serverJsPath) {
|
|
283
395
|
const normalizedServerPath = serverJsPath.replace(/\\/g, "/")
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
396
|
+
services.push({
|
|
397
|
+
name: "SOCKET",
|
|
398
|
+
color: "green",
|
|
399
|
+
command: `npx nodemon "${normalizedServerPath}"`,
|
|
400
|
+
})
|
|
287
401
|
}
|
|
288
402
|
|
|
289
|
-
// Firefox extension (optional)
|
|
290
403
|
if (firefoxConfig) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
404
|
+
services.push({
|
|
405
|
+
name: firefoxConfig.name,
|
|
406
|
+
color: firefoxConfig.color,
|
|
407
|
+
command: firefoxConfig.command,
|
|
408
|
+
})
|
|
294
409
|
}
|
|
295
410
|
|
|
296
|
-
// Chrome extension (optional)
|
|
297
411
|
if (chromeConfig) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
412
|
+
services.push({
|
|
413
|
+
name: chromeConfig.name,
|
|
414
|
+
color: chromeConfig.color,
|
|
415
|
+
command: chromeConfig.command,
|
|
416
|
+
})
|
|
301
417
|
}
|
|
302
418
|
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
419
|
+
// In JSON mode we orchestrate the children ourselves so we can wrap every
|
|
420
|
+
// stdout/stderr line as NDJSON. Concurrently's prefixed output would defeat
|
|
421
|
+
// that. Outside JSON mode, keep the legacy concurrently-based behavior.
|
|
422
|
+
if (logger.jsonMode) {
|
|
423
|
+
runServicesJson(services, logger)
|
|
424
|
+
return
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let command
|
|
428
|
+
if (services.length > 1) {
|
|
429
|
+
const quoted = services.map((s) => `"${s.command}"`).join(" ")
|
|
430
|
+
const names = services.map((s) => s.name).join(",")
|
|
431
|
+
const colors = services.map((s) => s.color).join(",")
|
|
432
|
+
command = `npx concurrently --names "${names}" --prefix-colors "${colors}" ${quoted}`
|
|
309
433
|
} else {
|
|
310
|
-
|
|
311
|
-
command = `npx vite dev --config "${normalizedViteConfigPath}"`
|
|
434
|
+
command = services[0].command
|
|
312
435
|
}
|
|
313
436
|
|
|
314
437
|
shell.exec(command)
|