@gxp-dev/tools 2.0.91 → 2.0.93
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/commands/init.js +2 -0
- package/bin/lib/constants.js +12 -0
- package/bin/lib/utils/files.js +32 -0
- package/package.json +1 -1
- package/runtime/stores/gxpPortalConfigStore.js +1 -1
- package/runtime/vite.config.js +149 -1
- package/template/app-manifest.json +1 -0
- package/template/env.example +1 -1
- package/template/theme-layouts/PrivateLayout.vue +10 -0
- package/template/theme-layouts/PublicLayout.vue +10 -0
- package/template/theme-layouts/SystemLayout.vue +10 -0
package/bin/lib/commands/init.js
CHANGED
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
safeCopyFile,
|
|
30
30
|
createPackageJson,
|
|
31
31
|
updateAppManifest,
|
|
32
|
+
ensureBaseFramework,
|
|
32
33
|
installDependencies,
|
|
33
34
|
updateExistingProject,
|
|
34
35
|
ensureMkcertInstalled,
|
|
@@ -721,6 +722,7 @@ async function initCommand(argv) {
|
|
|
721
722
|
console.log("Updating existing project...")
|
|
722
723
|
updateExistingProject(projectPath)
|
|
723
724
|
copyBundleFiles(projectPath, paths, false)
|
|
725
|
+
ensureBaseFramework(projectPath)
|
|
724
726
|
console.log("✅ Project updated!")
|
|
725
727
|
return
|
|
726
728
|
}
|
package/bin/lib/constants.js
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
const isWin = process.platform === "win32"
|
|
9
9
|
const exportCmd = isWin ? "set" : "export"
|
|
10
10
|
|
|
11
|
+
// Base CSS framework loaded by theme-layouts during development.
|
|
12
|
+
// Versioned here so the same value lands in package.json devDependencies
|
|
13
|
+
// AND the app-manifest's baseFramework field (platform reads the manifest
|
|
14
|
+
// to know which CSS framework to provide for the live plugin).
|
|
15
|
+
const TAILWIND_VERSION = "^4.3.0"
|
|
16
|
+
|
|
11
17
|
// Required dependencies for GxP projects
|
|
12
18
|
const REQUIRED_DEPENDENCIES = {
|
|
13
19
|
dotenv: "^16.4.5",
|
|
@@ -23,8 +29,12 @@ const REQUIRED_DEV_DEPENDENCIES = {
|
|
|
23
29
|
vitest: "^4.1.5",
|
|
24
30
|
"@vue/test-utils": "^2.4.6",
|
|
25
31
|
"happy-dom": "^15.11.0",
|
|
32
|
+
tailwindcss: TAILWIND_VERSION,
|
|
33
|
+
"@tailwindcss/vite": TAILWIND_VERSION,
|
|
26
34
|
}
|
|
27
35
|
|
|
36
|
+
const BASE_FRAMEWORK = `tailwindcss@${TAILWIND_VERSION}`
|
|
37
|
+
|
|
28
38
|
// Default scripts for package.json
|
|
29
39
|
const DEFAULT_SCRIPTS = {
|
|
30
40
|
dev: "gxdev dev --cli",
|
|
@@ -131,4 +141,6 @@ module.exports = {
|
|
|
131
141
|
DEFAULT_PORTS,
|
|
132
142
|
PACKAGE_NAME,
|
|
133
143
|
ENVIRONMENT_URLS,
|
|
144
|
+
TAILWIND_VERSION,
|
|
145
|
+
BASE_FRAMEWORK,
|
|
134
146
|
}
|
package/bin/lib/utils/files.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
REQUIRED_DEPENDENCIES,
|
|
12
12
|
REQUIRED_DEV_DEPENDENCIES,
|
|
13
13
|
DEFAULT_SCRIPTS,
|
|
14
|
+
BASE_FRAMEWORK,
|
|
14
15
|
} = require("../constants")
|
|
15
16
|
const { loadGlobalConfig } = require("./paths")
|
|
16
17
|
|
|
@@ -89,6 +90,9 @@ function updateAppManifest(projectPath, projectName, description = "") {
|
|
|
89
90
|
manifest.description = `GxP Plugin: ${projectName}`
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// Tell the platform which base CSS framework the layouts load.
|
|
94
|
+
manifest.baseFramework = BASE_FRAMEWORK
|
|
95
|
+
|
|
92
96
|
// Update strings with project name
|
|
93
97
|
if (manifest.strings && manifest.strings.default) {
|
|
94
98
|
manifest.strings.default.welcome_text = `Welcome to ${projectName}`
|
|
@@ -101,6 +105,33 @@ function updateAppManifest(projectPath, projectName, description = "") {
|
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Ensures app-manifest.json has the current baseFramework value.
|
|
110
|
+
* Used during existing-project updates where the manifest file is not overwritten.
|
|
111
|
+
* @param {string} projectPath - Path to project directory
|
|
112
|
+
*/
|
|
113
|
+
function ensureBaseFramework(projectPath) {
|
|
114
|
+
const manifestPath = path.join(projectPath, "app-manifest.json")
|
|
115
|
+
if (!fs.existsSync(manifestPath)) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"))
|
|
121
|
+
if (manifest.baseFramework === BASE_FRAMEWORK) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
manifest.baseFramework = BASE_FRAMEWORK
|
|
125
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, "\t"))
|
|
126
|
+
console.log(`✓ Set baseFramework in app-manifest.json (${BASE_FRAMEWORK})`)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.warn(
|
|
129
|
+
"⚠ Could not set baseFramework in app-manifest.json:",
|
|
130
|
+
error.message,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
104
135
|
/**
|
|
105
136
|
* Installs npm dependencies
|
|
106
137
|
*/
|
|
@@ -244,6 +275,7 @@ module.exports = {
|
|
|
244
275
|
safeCopyFile,
|
|
245
276
|
createPackageJson,
|
|
246
277
|
updateAppManifest,
|
|
278
|
+
ensureBaseFramework,
|
|
247
279
|
installDependencies,
|
|
248
280
|
updateExistingProject,
|
|
249
281
|
isImageMagickInstalled,
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@ function getApiConfig() {
|
|
|
47
47
|
const projectId = import.meta.env.VITE_API_PROJECT_ID || ""
|
|
48
48
|
const useHttps = import.meta.env.VITE_USE_HTTPS !== "false"
|
|
49
49
|
const nodePort = import.meta.env.VITE_NODE_PORT || "3060"
|
|
50
|
-
const mockPort = import.meta.env.VITE_SOCKET_IO_PORT || "
|
|
50
|
+
const mockPort = import.meta.env.VITE_SOCKET_IO_PORT || "3069"
|
|
51
51
|
|
|
52
52
|
// Check if we're in development mode (Vite dev server)
|
|
53
53
|
const isDev = import.meta.env.DEV
|
package/runtime/vite.config.js
CHANGED
|
@@ -120,6 +120,28 @@ function hasLocalFile(fileName) {
|
|
|
120
120
|
* concatenated, objects (resolve.alias, define, etc.) are merged key-by-key,
|
|
121
121
|
* primitives are overwritten.
|
|
122
122
|
*/
|
|
123
|
+
/**
|
|
124
|
+
* Try to load the `@tailwindcss/vite` plugin from the project's node_modules.
|
|
125
|
+
*
|
|
126
|
+
* Tailwind 4 ships as a dev-only base CSS framework — `tailwindcss` and
|
|
127
|
+
* `@tailwindcss/vite` are declared in the project's devDependencies and
|
|
128
|
+
* imported via `@import "tailwindcss";` inside the theme-layouts (which are
|
|
129
|
+
* dev-only; the production build entry is `src/Plugin.vue`). Loading the
|
|
130
|
+
* Vite plugin here lets that import generate utility CSS without each
|
|
131
|
+
* project having to wire it up in `vite.extend.js`. If the user has removed
|
|
132
|
+
* Tailwind we silently skip — the layouts work without it, just without
|
|
133
|
+
* Tailwind's default styling.
|
|
134
|
+
*/
|
|
135
|
+
async function loadTailwindPlugin() {
|
|
136
|
+
try {
|
|
137
|
+
const mod = await import("@tailwindcss/vite")
|
|
138
|
+
const plugin = mod.default ?? mod
|
|
139
|
+
return typeof plugin === "function" ? plugin() : null
|
|
140
|
+
} catch {
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
async function loadExtensionConfig(ctx, runtimeConfig) {
|
|
124
146
|
const candidates = ["vite.extend.js", "vite.extend.mjs"]
|
|
125
147
|
for (const name of candidates) {
|
|
@@ -188,6 +210,59 @@ export default defineConfig(async (ctx) => {
|
|
|
188
210
|
const runtimeMainFsUrl = toFsUrl("main.js")
|
|
189
211
|
const runtimeLogoFsUrl = toFsUrl("logo.png")
|
|
190
212
|
|
|
213
|
+
// ---------------------------------------------------------------------
|
|
214
|
+
// Live log streaming for the developer hub IDE.
|
|
215
|
+
//
|
|
216
|
+
// The portal's Session page wants to surface gxdev's stdout/stderr (Vite
|
|
217
|
+
// build errors, HMR notices, custom console.log from the project, etc.)
|
|
218
|
+
// in a bottom dock so the developer doesn't have to `kubectl logs` from
|
|
219
|
+
// outside the cluster. We keep a small ring buffer of recent lines and
|
|
220
|
+
// fan them out to any number of SSE subscribers — the buffer is replayed
|
|
221
|
+
// to each new subscriber so they see context from before they connected.
|
|
222
|
+
//
|
|
223
|
+
// We hook process.stdout.write / process.stderr.write rather than
|
|
224
|
+
// Vite's customLogger so we also capture lines emitted by the user's
|
|
225
|
+
// plugins, the runtime mock-api, console.log from server-side code,
|
|
226
|
+
// etc. The patch is a wrapper, not a replacement — the original
|
|
227
|
+
// terminal output keeps working unchanged.
|
|
228
|
+
const LOG_BUFFER_MAX = 500
|
|
229
|
+
const logBuffer = []
|
|
230
|
+
const logSubscribers = new Set()
|
|
231
|
+
|
|
232
|
+
function pushLog(stream, chunk) {
|
|
233
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8")
|
|
234
|
+
if (!text) return
|
|
235
|
+
// Split on newlines so each line is its own SSE event. Trailing
|
|
236
|
+
// empty splits (from `foo\n`) become "" — skip them.
|
|
237
|
+
const lines = text.split(/\r?\n/)
|
|
238
|
+
const ts = Date.now()
|
|
239
|
+
for (const raw of lines) {
|
|
240
|
+
if (!raw) continue
|
|
241
|
+
const entry = { stream, line: raw, ts }
|
|
242
|
+
logBuffer.push(entry)
|
|
243
|
+
if (logBuffer.length > LOG_BUFFER_MAX) logBuffer.shift()
|
|
244
|
+
for (const send of logSubscribers) {
|
|
245
|
+
try {
|
|
246
|
+
send(entry)
|
|
247
|
+
} catch {
|
|
248
|
+
// subscriber errored — drop it on the floor; the
|
|
249
|
+
// `res.on("close")` cleanup handles dead clients.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const _originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
256
|
+
const _originalStderrWrite = process.stderr.write.bind(process.stderr)
|
|
257
|
+
process.stdout.write = function (chunk, ...rest) {
|
|
258
|
+
pushLog("stdout", chunk)
|
|
259
|
+
return _originalStdoutWrite(chunk, ...rest)
|
|
260
|
+
}
|
|
261
|
+
process.stderr.write = function (chunk, ...rest) {
|
|
262
|
+
pushLog("stderr", chunk)
|
|
263
|
+
return _originalStderrWrite(chunk, ...rest)
|
|
264
|
+
}
|
|
265
|
+
|
|
191
266
|
// Create plugin to serve runtime files (index.html and main.js) if no local ones exist
|
|
192
267
|
const runtimeFilesPlugin = {
|
|
193
268
|
name: "runtime-files",
|
|
@@ -216,6 +291,55 @@ export default defineConfig(async (ctx) => {
|
|
|
216
291
|
return null
|
|
217
292
|
},
|
|
218
293
|
configureServer(server) {
|
|
294
|
+
// SSE endpoint that streams gxdev's stdout/stderr to the
|
|
295
|
+
// portal's IDE. CORS is wide open because the portal is on a
|
|
296
|
+
// different origin than the preview pod (dashboard.<env> vs
|
|
297
|
+
// <subdomain>.dev.<env>) and we already gate access at the
|
|
298
|
+
// network layer — the preview URLs are unguessable and live
|
|
299
|
+
// inside the cluster's preview namespace.
|
|
300
|
+
server.middlewares.use("/__logs", (req, res) => {
|
|
301
|
+
if (req.method === "OPTIONS") {
|
|
302
|
+
res.writeHead(204, {
|
|
303
|
+
"Access-Control-Allow-Origin": "*",
|
|
304
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
305
|
+
"Access-Control-Allow-Headers": "*",
|
|
306
|
+
})
|
|
307
|
+
res.end()
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
if (req.method !== "GET") {
|
|
311
|
+
res.writeHead(405)
|
|
312
|
+
res.end()
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
res.writeHead(200, {
|
|
316
|
+
"Content-Type": "text/event-stream",
|
|
317
|
+
"Cache-Control": "no-cache, no-transform",
|
|
318
|
+
Connection: "keep-alive",
|
|
319
|
+
"Access-Control-Allow-Origin": "*",
|
|
320
|
+
"X-Accel-Buffering": "no",
|
|
321
|
+
})
|
|
322
|
+
// Replay buffered lines so the client gets context.
|
|
323
|
+
for (const entry of logBuffer) {
|
|
324
|
+
res.write(`data: ${JSON.stringify(entry)}\n\n`)
|
|
325
|
+
}
|
|
326
|
+
// Heartbeat every 25s to keep the connection alive
|
|
327
|
+
// through any intermediate idle-timeout (GCP LB defaults
|
|
328
|
+
// to 30s; we already bump it to 86400s via
|
|
329
|
+
// GCPBackendPolicy but the heartbeat is cheap insurance).
|
|
330
|
+
const heartbeat = setInterval(() => {
|
|
331
|
+
res.write(`: heartbeat\n\n`)
|
|
332
|
+
}, 25000)
|
|
333
|
+
const send = (entry) => {
|
|
334
|
+
res.write(`data: ${JSON.stringify(entry)}\n\n`)
|
|
335
|
+
}
|
|
336
|
+
logSubscribers.add(send)
|
|
337
|
+
req.on("close", () => {
|
|
338
|
+
clearInterval(heartbeat)
|
|
339
|
+
logSubscribers.delete(send)
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
219
343
|
server.middlewares.use((req, res, next) => {
|
|
220
344
|
// Serve runtime index.html for root requests and SPA navigation requests
|
|
221
345
|
// (unless local index.html is opted in). SPA fallback is required so
|
|
@@ -313,6 +437,12 @@ export default defineConfig(async (ctx) => {
|
|
|
313
437
|
// Determine if HTTPS is enabled
|
|
314
438
|
const useHttps = getHttpsConfig(env) !== undefined
|
|
315
439
|
|
|
440
|
+
// Load Tailwind 4 Vite plugin from the project's node_modules if present.
|
|
441
|
+
const tailwindPlugin = await loadTailwindPlugin()
|
|
442
|
+
if (tailwindPlugin) {
|
|
443
|
+
console.log("🎨 Tailwind: @tailwindcss/vite plugin loaded")
|
|
444
|
+
}
|
|
445
|
+
|
|
316
446
|
// Get API proxy target for non-mock environments
|
|
317
447
|
const apiProxyTarget = getApiProxyTarget(env)
|
|
318
448
|
if (apiProxyTarget) {
|
|
@@ -346,6 +476,8 @@ export default defineConfig(async (ctx) => {
|
|
|
346
476
|
},
|
|
347
477
|
plugins: [
|
|
348
478
|
runtimeFilesPlugin,
|
|
479
|
+
// Tailwind 4 Vite plugin (no-op if @tailwindcss/vite isn't installed).
|
|
480
|
+
...((tailwindPlugin && [tailwindPlugin]) || []),
|
|
349
481
|
// Source tracker must run BEFORE vue() to transform templates before compilation
|
|
350
482
|
...(useSourceTracker ? [gxpSourceTrackerPlugin()] : []),
|
|
351
483
|
vue(),
|
|
@@ -375,8 +507,24 @@ export default defineConfig(async (ctx) => {
|
|
|
375
507
|
exclude: [
|
|
376
508
|
// Consumer install (published toolkit from npm)
|
|
377
509
|
"**/node_modules/@gxp-dev/tools/**",
|
|
378
|
-
// Workspace / `npm link` / self-dev: the runtime source itself
|
|
510
|
+
// Workspace / `npm link` / self-dev: the runtime source itself,
|
|
511
|
+
// resolved via the @gx-runtime alias.
|
|
379
512
|
`${runtimeDir.replace(/\\/g, "/")}/**`,
|
|
513
|
+
// If the toolkit lives at a symlinked path (global install,
|
|
514
|
+
// `npm link`, monorepo workspace), Vite resolves modules to
|
|
515
|
+
// their realpath — match that too so the runtime source is
|
|
516
|
+
// not double-rewritten.
|
|
517
|
+
...(realRuntimeDir !== runtimeDir.replace(/\\/g, "/")
|
|
518
|
+
? [`${realRuntimeDir}/**`]
|
|
519
|
+
: []),
|
|
520
|
+
// `vue-demi` is the Vue 2/3 compat shim that Pinia (and many
|
|
521
|
+
// other libs) pull in transitively. Its `lib/index.mjs` does
|
|
522
|
+
// `export * from "vue"`, which rollup-plugin-external-globals
|
|
523
|
+
// can't rewrite ("Cannot export all properties from an
|
|
524
|
+
// external variable"). Leave it alone — Vite's `dedupe: ["vue"]`
|
|
525
|
+
// already ensures it imports the same Vue instance that we
|
|
526
|
+
// expose on `window.Vue`.
|
|
527
|
+
"**/node_modules/vue-demi/**",
|
|
380
528
|
],
|
|
381
529
|
},
|
|
382
530
|
),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"description": "GxToolkit Plugin",
|
|
5
5
|
"manifest_version": 3,
|
|
6
6
|
"asset_dir": "/src/public",
|
|
7
|
+
"baseFramework": "tailwindcss@^4.3.0",
|
|
7
8
|
"configurationFile": "configuration.json",
|
|
8
9
|
"appInstructionsFile": "app-instructions.md",
|
|
9
10
|
"defaultStylingFile": "default-styling.css",
|
package/template/env.example
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
<slot />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
|
+
<!--
|
|
6
|
+
Tailwind base/utilities for development only. The layouts wrap the plugin in
|
|
7
|
+
the dev runtime but are NOT part of the production build (build entry is
|
|
8
|
+
src/Plugin.vue), so this CSS never ships in dist/. Remove this block if you
|
|
9
|
+
don't want Tailwind defaults applied while developing.
|
|
10
|
+
-->
|
|
11
|
+
<style>
|
|
12
|
+
@import "tailwindcss";
|
|
13
|
+
</style>
|
|
14
|
+
|
|
5
15
|
<style scoped></style>
|
|
6
16
|
|
|
7
17
|
<script setup>
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
<slot />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
|
+
<!--
|
|
6
|
+
Tailwind base/utilities for development only. The layouts wrap the plugin in
|
|
7
|
+
the dev runtime but are NOT part of the production build (build entry is
|
|
8
|
+
src/Plugin.vue), so this CSS never ships in dist/. Remove this block if you
|
|
9
|
+
don't want Tailwind defaults applied while developing.
|
|
10
|
+
-->
|
|
11
|
+
<style>
|
|
12
|
+
@import "tailwindcss";
|
|
13
|
+
</style>
|
|
14
|
+
|
|
5
15
|
<style scoped></style>
|
|
6
16
|
|
|
7
17
|
<script setup>
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
<slot />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
|
+
<!--
|
|
6
|
+
Tailwind base/utilities for development only. The layouts wrap the plugin in
|
|
7
|
+
the dev runtime but are NOT part of the production build (build entry is
|
|
8
|
+
src/Plugin.vue), so this CSS never ships in dist/. Remove this block if you
|
|
9
|
+
don't want Tailwind defaults applied while developing.
|
|
10
|
+
-->
|
|
11
|
+
<style>
|
|
12
|
+
@import "tailwindcss";
|
|
13
|
+
</style>
|
|
14
|
+
|
|
5
15
|
<style scoped></style>
|
|
6
16
|
|
|
7
17
|
<script setup>
|