@gxp-dev/tools 2.0.56 → 2.0.58

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.
@@ -8,7 +8,6 @@ const path = require("path");
8
8
  const fs = require("fs");
9
9
  const shell = require("shelljs");
10
10
  const archiver = require("archiver");
11
- const { exportCmd } = require("../constants");
12
11
  const { findProjectRoot, resolveGxPaths } = require("../utils");
13
12
 
14
13
  /**
@@ -310,8 +309,6 @@ async function buildCommand(argv) {
310
309
 
311
310
  console.log("🔨 Building plugin...\n");
312
311
 
313
- const envVars = [];
314
-
315
312
  // check if vite.config.js exists locally
316
313
  let viteConfigPath = paths.viteConfigPath;
317
314
  const localViteConfigPath = path.join(projectPath, "vite.config.js");
@@ -320,24 +317,18 @@ async function buildCommand(argv) {
320
317
  console.log(`📁 Using local vite.config.js: ${viteConfigPath}`);
321
318
  }
322
319
 
323
- // Set variables only if not already defined in environment
320
+ // Set environment variables directly on process.env for cross-platform compatibility.
321
+ // Using shell-level "export"/"set" syntax breaks on Windows due to cmd.exe quote parsing.
324
322
  if (!process.env.NODE_LOG_LEVEL) {
325
- envVars.push(
326
- `${exportCmd} NODE_LOG_LEVEL=${argv["node-log-level"] || "error"}`
327
- );
323
+ process.env.NODE_LOG_LEVEL = argv["node-log-level"] || "error";
328
324
  }
329
325
  if (!process.env.COMPONENT_PATH) {
330
- envVars.push(
331
- `${exportCmd} COMPONENT_PATH=${
332
- argv["component-path"] || "./src/Plugin.vue"
333
- }`
334
- );
326
+ process.env.COMPONENT_PATH = argv["component-path"] || "./src/Plugin.vue";
335
327
  }
336
328
 
337
- const command = [
338
- ...envVars,
339
- `npx vite build --config "${viteConfigPath}"`,
340
- ].join(" && ");
329
+ // Normalize path separators to forward slashes for cross-platform shell compatibility
330
+ const normalizedViteConfigPath = viteConfigPath.replace(/\\/g, "/");
331
+ const command = `npx vite build --config "${normalizedViteConfigPath}"`;
341
332
 
342
333
  const result = shell.exec(command);
343
334
 
@@ -9,7 +9,6 @@ const path = require("path");
9
9
  const fs = require("fs");
10
10
  const shell = require("shelljs");
11
11
  const dotenv = require("dotenv");
12
- const { exportCmd } = require("../constants");
13
12
  const {
14
13
  findProjectRoot,
15
14
  resolveGxPaths,
@@ -82,10 +81,13 @@ function getBrowserExtensionConfig(browser, projectPath, paths, options = {}) {
82
81
  return null;
83
82
  }
84
83
 
84
+ // Normalize path separators for cross-platform shell compatibility
85
+ const normalizedScriptPath = scriptPath.replace(/\\/g, "/");
85
86
  return {
86
87
  name: "CHROME",
87
88
  color: "blue",
88
- command: `CHROME_EXTENSION_PATH="${extensionPath}" USE_HTTPS="${useHttps}" NODE_PORT="${port}" node "${scriptPath}"`,
89
+ // Inline KEY=VALUE env syntax doesn't work on Windows; env vars are set on process.env before exec
90
+ command: `node "${normalizedScriptPath}"`,
89
91
  extensionPath,
90
92
  startUrl,
91
93
  };
@@ -200,34 +202,26 @@ function devCommand(argv) {
200
202
  console.log("📦 Using runtime dev files (publish to customize)");
201
203
  }
202
204
 
203
- // Only set environment variables if they're not already set (allows .env to take precedence)
204
- const envVars = [];
205
-
206
- // Set variables only if not already defined in environment
205
+ // Set environment variables directly on process.env for cross-platform compatibility.
206
+ // Using shell-level "export"/"set" syntax breaks on Windows due to cmd.exe quote parsing.
207
207
  if (!process.env.NODE_LOG_LEVEL) {
208
- envVars.push(
209
- `${exportCmd} NODE_LOG_LEVEL=${argv["node-log-level"] || "info"}`
210
- );
208
+ process.env.NODE_LOG_LEVEL = argv["node-log-level"] || "info";
211
209
  }
212
210
  if (!process.env.NODE_PORT) {
213
- envVars.push(`${exportCmd} NODE_PORT=${finalPort}`);
211
+ process.env.NODE_PORT = String(finalPort);
214
212
  }
215
213
  if (!process.env.COMPONENT_PATH) {
216
- envVars.push(
217
- `${exportCmd} COMPONENT_PATH=${
218
- argv["component-path"] || "./src/Plugin.vue"
219
- }`
220
- );
214
+ process.env.COMPONENT_PATH = argv["component-path"] || "./src/Plugin.vue";
221
215
  }
222
216
 
223
217
  // Always set HTTPS-related variables (these are dynamic)
224
- envVars.push(`${exportCmd} USE_HTTPS=${useHttps ? "true" : "false"}`);
225
- envVars.push(`${exportCmd} CERT_PATH=${certPath}`);
226
- envVars.push(`${exportCmd} KEY_PATH=${keyPath}`);
218
+ process.env.USE_HTTPS = useHttps ? "true" : "false";
219
+ process.env.CERT_PATH = certPath;
220
+ process.env.KEY_PATH = keyPath;
227
221
 
228
222
  // Set mock API flag if requested
229
223
  if (withMock) {
230
- envVars.push(`${exportCmd} MOCK_API_ENABLED=true`);
224
+ process.env.MOCK_API_ENABLED = "true";
231
225
  }
232
226
 
233
227
  // Check for browser extension flags
@@ -266,6 +260,11 @@ function devCommand(argv) {
266
260
  }
267
261
  }
268
262
 
263
+ // Set CHROME_EXTENSION_PATH on process.env so launch-chrome.js inherits it
264
+ if (chromeConfig) {
265
+ process.env.CHROME_EXTENSION_PATH = chromeConfig.extensionPath;
266
+ }
267
+
269
268
  // Build the command based on what's requested
270
269
  let command;
271
270
 
@@ -274,18 +273,19 @@ function devCommand(argv) {
274
273
  const names = [];
275
274
  const colors = [];
276
275
 
276
+ // Normalize path separators to forward slashes for cross-platform shell compatibility
277
+ const normalizedViteConfigPath = viteConfigPath.replace(/\\/g, "/");
278
+
277
279
  // Vite is always included
278
- const viteCommand = [
279
- ...envVars,
280
- `npx vite dev --config "${viteConfigPath}"`,
281
- ].join(" && ");
280
+ const viteCommand = `npx vite dev --config "${normalizedViteConfigPath}"`;
282
281
  processes.push(`"${viteCommand}"`);
283
282
  names.push("VITE");
284
283
  colors.push("cyan");
285
284
 
286
285
  // Socket server (on by default, skip if --no-socket or server.js not found)
287
286
  if (serverJsPath) {
288
- processes.push(`"npx nodemon \\"${serverJsPath}\\""`);
287
+ const normalizedServerPath = serverJsPath.replace(/\\/g, "/");
288
+ processes.push(`"npx nodemon \\"${normalizedServerPath}\\""`);
289
289
  names.push("SOCKET");
290
290
  colors.push("green");
291
291
  }
@@ -312,9 +312,7 @@ function devCommand(argv) {
312
312
  )}" --prefix-colors "${colors.join(",")}" ${processes.join(" ")}`;
313
313
  } else {
314
314
  // Just run Vite dev server alone
315
- command = [...envVars, `npx vite dev --config "${viteConfigPath}"`].join(
316
- " && "
317
- );
315
+ command = `npx vite dev --config "${normalizedViteConfigPath}"`;
318
316
  }
319
317
 
320
318
  shell.exec(command);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gxp-dev/tools",
3
- "version": "2.0.56",
3
+ "version": "2.0.58",
4
4
  "description": "Dev tools to create platform plugins",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {
@@ -313,7 +313,55 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
313
313
  console.warn(`[GxP Store] Unknown SOCKET_DRIVER "${socketDriver}", sockets not initialized`);
314
314
  }
315
315
  }
316
+ /**
317
+ * Initialize API operations registry from OpenAPI spec
318
+ */
319
+ async function initializeApiOperations() {
320
+ // Operations are built from OpenAPI spec paths
321
+ // Structure: { [operationId]: { method, path, parameters } }
322
+ try {
323
+ const specUrl = `${apiBaseUrl.value}/api-specs/openapi.json`
324
+ const response = await axios.get(specUrl)
325
+ const spec = response.data
326
+
327
+ const operations = {}
328
+ const httpMethods = ["get", "post", "put", "patch", "delete"]
329
+
330
+ // Parse paths from OpenAPI spec
331
+ if (spec.paths) {
332
+ for (const [path, pathItem] of Object.entries(spec.paths)) {
333
+ for (const method of httpMethods) {
334
+ if (pathItem[method] && pathItem[method].operationId) {
335
+ const operation = pathItem[method]
336
+ const operationId = operation.operationId
337
+
338
+ // Extract path parameters from the path string
339
+ const pathParams = []
340
+ const paramMatches = path.matchAll(/\{([^}]+)\}/g)
341
+ for (const match of paramMatches) {
342
+ pathParams.push(match[1])
343
+ }
344
+
345
+ operations[operationId] = {
346
+ method,
347
+ path,
348
+ parameters: pathParams,
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
316
354
 
355
+ apiOperations.value = operations
356
+ console.log(
357
+ `Loaded ${Object.keys(operations).length} API operations from OpenAPI spec`,
358
+ )
359
+ } catch (error) {
360
+ console.error("Failed to load OpenAPI spec:", error.message)
361
+ // Initialize with empty operations on failure
362
+ apiOperations.value = {}
363
+ }
364
+ }
317
365
  /**
318
366
  * Initialize dependency-based sockets
319
367
  * Called after manifest loads to set up dependency-specific listeners
@@ -329,43 +377,43 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
329
377
  dependency.operations &&
330
378
  Object.keys(dependency.operations).length > 0
331
379
  ) {
332
- Object.keys(dependency.operations).forEach((operation) => {
333
- if (
334
- Object.keys(apiOperations.value[dependency.identifier]).every(
335
- (key) =>
336
- [
337
- "identifier",
338
- "model",
339
- "permissionKey",
340
- "operations",
341
- ].includes(key)
342
- )
343
- ) {
344
- let method = "get";
345
- let path = dependency.operations[operation];
346
- if (path.includes(":")) {
347
- let pathSplit = path.split(":");
348
- method = pathSplit[0];
349
- path = pathSplit[1];
350
- }
351
- path = path.replace(
352
- "{teamSlug}/{projectSlug}",
353
- pluginVars.value.projectId
354
- );
355
- path = path.replace(
356
- `{${dependency.permissionKey}}`,
357
- dependencyList.value[dependency.identifier]
358
- );
359
- if (!apiOperations.value[dependency.identifier]) {
360
- apiOperations.value[dependency.identifier] = {};
361
- }
362
- apiOperations.value[dependency.identifier][operation] = {
363
- method: method,
364
- path: path,
365
- model_key: dependency.permissionKey,
366
- };
367
- }
368
- });
380
+ // Object.keys(dependency.operations).forEach((operation) => {
381
+ // if (
382
+ // Object.keys(apiOperations.value[dependency.identifier]).every(
383
+ // (key) =>
384
+ // [
385
+ // "identifier",
386
+ // "model",
387
+ // "permissionKey",
388
+ // "operations",
389
+ // ].includes(key)
390
+ // )
391
+ // ) {
392
+ // let method = "get";
393
+ // let path = dependency.operations[operation];
394
+ // if (path.includes(":")) {
395
+ // let pathSplit = path.split(":");
396
+ // method = pathSplit[0];
397
+ // path = pathSplit[1];
398
+ // }
399
+ // path = path.replace(
400
+ // "{teamSlug}/{projectSlug}",
401
+ // pluginVars.value.projectId
402
+ // );
403
+ // path = path.replace(
404
+ // `{${dependency.permissionKey}}`,
405
+ // dependencyList.value[dependency.identifier]
406
+ // );
407
+ // if (!apiOperations.value[dependency.identifier]) {
408
+ // apiOperations.value[dependency.identifier] = {};
409
+ // }
410
+ // apiOperations.value[dependency.identifier][operation] = {
411
+ // method: method,
412
+ // path: path,
413
+ // model_key: dependency.permissionKey,
414
+ // };
415
+ // }
416
+ // });
369
417
  }
370
418
  if (dependency.events && Object.keys(dependency.events).length > 0) {
371
419
  // Create socket listeners for each event type
@@ -445,20 +493,133 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
445
493
  throw new Error(`DELETE ${endpoint}: ${error.message}`);
446
494
  }
447
495
  }
448
- async function callApi(operation, identifier, data = {}) {
449
- try {
450
- const operationConfig = apiOperations.value[identifier][operation];
496
+ async function callApi(operationId, identifier, data = {}) {
497
+ // Initialize operations if not done
498
+ if (Object.keys(apiOperations.value).length === 0) {
499
+ await initializeApiOperations()
500
+ }
501
+ let operationConfig = apiOperations.value[operationId]
502
+ if (!operationConfig) {
503
+ operationConfig =
504
+ apiOperations.value["portal.v1.project." + operationId]
451
505
  if (!operationConfig) {
452
- throw new Error(`Operation not found: ${operation}`);
506
+ throw new Error(`Operation not found: portal.v1.${operationId}`)
453
507
  }
454
- const response = await apiClient[operationConfig.method](
455
- operationConfig.path,
456
- data
457
- );
458
- return response.data;
508
+ }
509
+
510
+ const { method, path, parameters } = operationConfig
511
+
512
+ // Build the URL by substituting path parameters
513
+ let resolvedPath = path
514
+
515
+ // Build context parameters from multiple sources:
516
+ // 1. Auto-inject teamSlug and projectSlug from portal context
517
+ // 2. Look up identifier value from dependencyList (if identifier provided)
518
+ // 3. Merge in additional data parameters
519
+
520
+ let projectTeamId = pluginVars.value?.projectId?.split("/")
521
+ if (!projectTeamId || projectTeamId.length !== 2) {
522
+ return []
523
+ }
524
+ let teamSlug = projectTeamId[0]
525
+ let projectSlug = projectTeamId[1]
526
+
527
+ const contextParams = {
528
+ teamSlug: teamSlug,
529
+ projectSlug: projectSlug,
530
+ }
531
+ if (parameters.includes("form") && pluginVars.value?.formId) {
532
+ contextParams["form"] = pluginVars.value?.formId
533
+ }
534
+ // If identifier is provided, look up its value from dependencyList
535
+ // dependencyList stores parent object IDs as { 'identifier': idValue }
536
+ if (identifier !== null && identifier !== undefined) {
537
+ const identifierValue = dependencyList.value?.[identifier]
538
+ if (identifierValue !== undefined) {
539
+ // Add the identifier value using the identifier key as the param name
540
+ // e.g., identifier='form' with dependencyList.form='quiz-123' adds { form: 'quiz-123' }
541
+ contextParams[identifier] = identifierValue
542
+ }
543
+ }
544
+ const parsedData = {}
545
+ for (const key in data) {
546
+ if (data[key] !== undefined && data[key] !== null) {
547
+ if (data[key].toString().startsWith("pluginVars")) {
548
+ const pluginVarKey = data[key].split(".")[1]
549
+
550
+ parsedData[key] = pluginVars.value[pluginVarKey]
551
+ continue
552
+ }
553
+ parsedData[key] = data[key]
554
+ }
555
+ }
556
+
557
+ // Merge in additional data (can override dependencyList values if needed)
558
+ Object.assign(contextParams, parsedData)
559
+
560
+ // Replace path parameters
561
+ for (const param of parameters) {
562
+ const value = contextParams[param]
563
+ if (value === undefined || value === null) {
564
+ throw new Error(
565
+ `Missing required parameter: ${param} for operation ${operationId}`,
566
+ )
567
+ }
568
+ resolvedPath = resolvedPath.replace(
569
+ `{${param}}`,
570
+ encodeURIComponent(value),
571
+ )
572
+ }
573
+
574
+ // Separate path params from body data
575
+ const bodyData = { ...parsedData }
576
+ for (const param of parameters) {
577
+ delete bodyData[param]
578
+ }
579
+ // Also remove identifier from body if it was in data
580
+ if (identifier && bodyData[identifier] !== undefined) {
581
+ delete bodyData[identifier]
582
+ }
583
+
584
+ try {
585
+ let response
586
+ if (method === "get" || method === "delete") {
587
+ // GET/DELETE: params go in query string
588
+ response = await apiClient[method]("/api" + resolvedPath, {
589
+ params: bodyData,
590
+ })
591
+ } else {
592
+ // POST/PUT/PATCH: params go in body
593
+ response = await apiClient[method]("/api" + resolvedPath, bodyData)
594
+ }
595
+ return response.data
459
596
  } catch (error) {
460
- throw new Error(`${method} ${endpoint}: ${error.message}`);
597
+ const message =
598
+ error.response?.data?.message ||
599
+ error.response?.data?.error ||
600
+ error.message
601
+ console.error(
602
+ `API Error [${operationId}]:`,
603
+ message,
604
+ error.response?.data,
605
+ )
606
+ throw new Error(`${method.toUpperCase()} ${resolvedPath}: ${message}`)
461
607
  }
608
+
609
+
610
+ // try {
611
+ // const operationConfig = apiOperations.value[identifier][operation];
612
+ // if (!operationConfig) {
613
+ // throw new Error(`Operation not found: ${operation}`);
614
+ // }
615
+ // const response = await apiClient[operationConfig.method](
616
+ // operationConfig.path,
617
+ // data
618
+ // );
619
+ // return response.data;
620
+ // } catch (error) {
621
+ // throw new Error(`${method} ${endpoint}: ${error.message}`);
622
+ // }
462
623
  }
463
624
 
464
625
  // Utility methods
@@ -563,7 +724,7 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
563
724
  // Initialize sockets SYNCHRONOUSLY when store is created
564
725
  // This ensures sockets is available immediately
565
726
  initializeSockets();
566
-
727
+ initializeApiOperations()
567
728
  // Load manifest ASYNCHRONOUSLY in the background
568
729
  // This allows the store to be used immediately while manifest loads
569
730
  loadManifest();
@@ -332,8 +332,8 @@ export default defineConfig(({ mode }) => {
332
332
  ? {
333
333
  protocol: env.HMR_PROTOCOL || "wss",
334
334
  host: env.HMR_HOST,
335
- port: parseInt(env.HMR_PORT) || 443,
336
- clientPort: parseInt(env.HMR_PORT) || 443,
335
+ port: parseInt(env.HMR_PORT) || parseInt(env.CLIENT_PORT) || parseInt(env.NODE_PORT) || 3060,
336
+ clientPort: parseInt(env.HMR_CLIENT_PORT) || parseInt(env.CLIENT_PORT) || parseInt(env.NODE_PORT) || 3060,
337
337
  }
338
338
  : {
339
339
  clientPort:
@@ -320,7 +320,7 @@ export default defineConfig(({ mode }) => {
320
320
  },
321
321
  build: {
322
322
  lib: {
323
- entry: [env.COMPONENT_PATH || "./src/Plugin.vue"],
323
+ entry: [path.resolve(process.cwd(), env.COMPONENT_PATH || "./src/Plugin.vue")],
324
324
  name: libName,
325
325
  fileName: (format) => `plugin.${format}.js`,
326
326
  formats: ["es"],