@gxp-dev/tools 2.0.90 → 2.0.92
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 +12 -0
- package/bin/lib/commands/dev.js +4 -1
- package/bin/lib/utils/paths.js +8 -2
- package/package.json +1 -1
- package/runtime/vite.config.js +119 -1
package/bin/lib/cli.js
CHANGED
|
@@ -51,6 +51,18 @@ const cli = yargs
|
|
|
51
51
|
default: false,
|
|
52
52
|
global: true,
|
|
53
53
|
})
|
|
54
|
+
.option("use-global", {
|
|
55
|
+
describe:
|
|
56
|
+
"Force-use the global @gxp-dev/tools install (runtime, vite config, templates) and ignore any local node_modules copy",
|
|
57
|
+
type: "boolean",
|
|
58
|
+
default: false,
|
|
59
|
+
global: true,
|
|
60
|
+
})
|
|
61
|
+
.middleware((argv) => {
|
|
62
|
+
if (argv["use-global"]) {
|
|
63
|
+
process.env.GXDEV_USE_GLOBAL = "true"
|
|
64
|
+
}
|
|
65
|
+
})
|
|
54
66
|
.command(
|
|
55
67
|
"ui",
|
|
56
68
|
"Open the interactive Terminal UI without auto-starting anything",
|
package/bin/lib/commands/dev.js
CHANGED
|
@@ -229,8 +229,11 @@ function devCommand(argv) {
|
|
|
229
229
|
)
|
|
230
230
|
const installLocation =
|
|
231
231
|
paths.packageRoot === localToolkitDir ? "local" : "package"
|
|
232
|
+
const forcedGlobal = process.env.GXDEV_USE_GLOBAL === "true"
|
|
232
233
|
logger.info(
|
|
233
|
-
`📦 Using ${installLocation} toolkit install: ${paths.packageRoot}
|
|
234
|
+
`📦 Using ${installLocation} toolkit install: ${paths.packageRoot}${
|
|
235
|
+
forcedGlobal ? " (forced via --use-global)" : ""
|
|
236
|
+
}`,
|
|
234
237
|
)
|
|
235
238
|
|
|
236
239
|
// Load .env file if it exists for default values
|
package/bin/lib/utils/paths.js
CHANGED
|
@@ -45,9 +45,15 @@ function findProjectRoot() {
|
|
|
45
45
|
function resolveGxPaths() {
|
|
46
46
|
const projectRoot = findProjectRoot()
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Honor --use-global (propagated by cli.js as GXDEV_USE_GLOBAL=true) to
|
|
49
|
+
// force the CLI's own install location, even if a local node_modules copy
|
|
50
|
+
// of @gxp-dev/tools exists. Useful when the local version is stale or
|
|
51
|
+
// being debugged against the globally installed toolkit.
|
|
52
|
+
const forceGlobal = process.env.GXDEV_USE_GLOBAL === "true"
|
|
53
|
+
|
|
54
|
+
// Try local installation first (unless --use-global)
|
|
49
55
|
const localNodeModules = path.join(projectRoot, "node_modules", PACKAGE_NAME)
|
|
50
|
-
if (fs.existsSync(localNodeModules)) {
|
|
56
|
+
if (!forceGlobal && fs.existsSync(localNodeModules)) {
|
|
51
57
|
return {
|
|
52
58
|
gentoPath: path.join(localNodeModules, "bin", getBinaryName()),
|
|
53
59
|
viteConfigPath: path.join(localNodeModules, "runtime", "vite.config.js"),
|
package/package.json
CHANGED
package/runtime/vite.config.js
CHANGED
|
@@ -188,6 +188,59 @@ export default defineConfig(async (ctx) => {
|
|
|
188
188
|
const runtimeMainFsUrl = toFsUrl("main.js")
|
|
189
189
|
const runtimeLogoFsUrl = toFsUrl("logo.png")
|
|
190
190
|
|
|
191
|
+
// ---------------------------------------------------------------------
|
|
192
|
+
// Live log streaming for the developer hub IDE.
|
|
193
|
+
//
|
|
194
|
+
// The portal's Session page wants to surface gxdev's stdout/stderr (Vite
|
|
195
|
+
// build errors, HMR notices, custom console.log from the project, etc.)
|
|
196
|
+
// in a bottom dock so the developer doesn't have to `kubectl logs` from
|
|
197
|
+
// outside the cluster. We keep a small ring buffer of recent lines and
|
|
198
|
+
// fan them out to any number of SSE subscribers — the buffer is replayed
|
|
199
|
+
// to each new subscriber so they see context from before they connected.
|
|
200
|
+
//
|
|
201
|
+
// We hook process.stdout.write / process.stderr.write rather than
|
|
202
|
+
// Vite's customLogger so we also capture lines emitted by the user's
|
|
203
|
+
// plugins, the runtime mock-api, console.log from server-side code,
|
|
204
|
+
// etc. The patch is a wrapper, not a replacement — the original
|
|
205
|
+
// terminal output keeps working unchanged.
|
|
206
|
+
const LOG_BUFFER_MAX = 500
|
|
207
|
+
const logBuffer = []
|
|
208
|
+
const logSubscribers = new Set()
|
|
209
|
+
|
|
210
|
+
function pushLog(stream, chunk) {
|
|
211
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8")
|
|
212
|
+
if (!text) return
|
|
213
|
+
// Split on newlines so each line is its own SSE event. Trailing
|
|
214
|
+
// empty splits (from `foo\n`) become "" — skip them.
|
|
215
|
+
const lines = text.split(/\r?\n/)
|
|
216
|
+
const ts = Date.now()
|
|
217
|
+
for (const raw of lines) {
|
|
218
|
+
if (!raw) continue
|
|
219
|
+
const entry = { stream, line: raw, ts }
|
|
220
|
+
logBuffer.push(entry)
|
|
221
|
+
if (logBuffer.length > LOG_BUFFER_MAX) logBuffer.shift()
|
|
222
|
+
for (const send of logSubscribers) {
|
|
223
|
+
try {
|
|
224
|
+
send(entry)
|
|
225
|
+
} catch {
|
|
226
|
+
// subscriber errored — drop it on the floor; the
|
|
227
|
+
// `res.on("close")` cleanup handles dead clients.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const _originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
234
|
+
const _originalStderrWrite = process.stderr.write.bind(process.stderr)
|
|
235
|
+
process.stdout.write = function (chunk, ...rest) {
|
|
236
|
+
pushLog("stdout", chunk)
|
|
237
|
+
return _originalStdoutWrite(chunk, ...rest)
|
|
238
|
+
}
|
|
239
|
+
process.stderr.write = function (chunk, ...rest) {
|
|
240
|
+
pushLog("stderr", chunk)
|
|
241
|
+
return _originalStderrWrite(chunk, ...rest)
|
|
242
|
+
}
|
|
243
|
+
|
|
191
244
|
// Create plugin to serve runtime files (index.html and main.js) if no local ones exist
|
|
192
245
|
const runtimeFilesPlugin = {
|
|
193
246
|
name: "runtime-files",
|
|
@@ -216,6 +269,55 @@ export default defineConfig(async (ctx) => {
|
|
|
216
269
|
return null
|
|
217
270
|
},
|
|
218
271
|
configureServer(server) {
|
|
272
|
+
// SSE endpoint that streams gxdev's stdout/stderr to the
|
|
273
|
+
// portal's IDE. CORS is wide open because the portal is on a
|
|
274
|
+
// different origin than the preview pod (dashboard.<env> vs
|
|
275
|
+
// <subdomain>.dev.<env>) and we already gate access at the
|
|
276
|
+
// network layer — the preview URLs are unguessable and live
|
|
277
|
+
// inside the cluster's preview namespace.
|
|
278
|
+
server.middlewares.use("/__logs", (req, res) => {
|
|
279
|
+
if (req.method === "OPTIONS") {
|
|
280
|
+
res.writeHead(204, {
|
|
281
|
+
"Access-Control-Allow-Origin": "*",
|
|
282
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
283
|
+
"Access-Control-Allow-Headers": "*",
|
|
284
|
+
})
|
|
285
|
+
res.end()
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (req.method !== "GET") {
|
|
289
|
+
res.writeHead(405)
|
|
290
|
+
res.end()
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
res.writeHead(200, {
|
|
294
|
+
"Content-Type": "text/event-stream",
|
|
295
|
+
"Cache-Control": "no-cache, no-transform",
|
|
296
|
+
Connection: "keep-alive",
|
|
297
|
+
"Access-Control-Allow-Origin": "*",
|
|
298
|
+
"X-Accel-Buffering": "no",
|
|
299
|
+
})
|
|
300
|
+
// Replay buffered lines so the client gets context.
|
|
301
|
+
for (const entry of logBuffer) {
|
|
302
|
+
res.write(`data: ${JSON.stringify(entry)}\n\n`)
|
|
303
|
+
}
|
|
304
|
+
// Heartbeat every 25s to keep the connection alive
|
|
305
|
+
// through any intermediate idle-timeout (GCP LB defaults
|
|
306
|
+
// to 30s; we already bump it to 86400s via
|
|
307
|
+
// GCPBackendPolicy but the heartbeat is cheap insurance).
|
|
308
|
+
const heartbeat = setInterval(() => {
|
|
309
|
+
res.write(`: heartbeat\n\n`)
|
|
310
|
+
}, 25000)
|
|
311
|
+
const send = (entry) => {
|
|
312
|
+
res.write(`data: ${JSON.stringify(entry)}\n\n`)
|
|
313
|
+
}
|
|
314
|
+
logSubscribers.add(send)
|
|
315
|
+
req.on("close", () => {
|
|
316
|
+
clearInterval(heartbeat)
|
|
317
|
+
logSubscribers.delete(send)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
219
321
|
server.middlewares.use((req, res, next) => {
|
|
220
322
|
// Serve runtime index.html for root requests and SPA navigation requests
|
|
221
323
|
// (unless local index.html is opted in). SPA fallback is required so
|
|
@@ -375,8 +477,24 @@ export default defineConfig(async (ctx) => {
|
|
|
375
477
|
exclude: [
|
|
376
478
|
// Consumer install (published toolkit from npm)
|
|
377
479
|
"**/node_modules/@gxp-dev/tools/**",
|
|
378
|
-
// Workspace / `npm link` / self-dev: the runtime source itself
|
|
480
|
+
// Workspace / `npm link` / self-dev: the runtime source itself,
|
|
481
|
+
// resolved via the @gx-runtime alias.
|
|
379
482
|
`${runtimeDir.replace(/\\/g, "/")}/**`,
|
|
483
|
+
// If the toolkit lives at a symlinked path (global install,
|
|
484
|
+
// `npm link`, monorepo workspace), Vite resolves modules to
|
|
485
|
+
// their realpath — match that too so the runtime source is
|
|
486
|
+
// not double-rewritten.
|
|
487
|
+
...(realRuntimeDir !== runtimeDir.replace(/\\/g, "/")
|
|
488
|
+
? [`${realRuntimeDir}/**`]
|
|
489
|
+
: []),
|
|
490
|
+
// `vue-demi` is the Vue 2/3 compat shim that Pinia (and many
|
|
491
|
+
// other libs) pull in transitively. Its `lib/index.mjs` does
|
|
492
|
+
// `export * from "vue"`, which rollup-plugin-external-globals
|
|
493
|
+
// can't rewrite ("Cannot export all properties from an
|
|
494
|
+
// external variable"). Leave it alone — Vite's `dedupe: ["vue"]`
|
|
495
|
+
// already ensures it imports the same Vue instance that we
|
|
496
|
+
// expose on `window.Vue`.
|
|
497
|
+
"**/node_modules/vue-demi/**",
|
|
380
498
|
],
|
|
381
499
|
},
|
|
382
500
|
),
|