@embeddable.com/sdk-core 3.14.6 → 3.14.7-next.1

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/src/dev.ts CHANGED
@@ -53,14 +53,65 @@ let browserWindow: ChildProcess | null = null;
53
53
 
54
54
  let previewWorkspace: string;
55
55
 
56
+ // Build coordination to prevent duplicate plugin builds
57
+ let pluginBuildInProgress = false;
58
+ let pendingPluginBuilds: (() => Promise<void>)[] = [];
59
+
56
60
  const SERVER_PORT = 8926;
57
61
  const BUILD_DEV_DIR = ".embeddable-dev-build";
58
- const GLOBAL_CSS = "/global.css";
62
+ // NOTE: for backward compatibility, keep the file name as global.css
63
+ const CUSTOM_CANVAS_CSS = "/global.css";
59
64
 
60
65
  export const buildWebComponent = async (config: any) => {
61
66
  await generate(config, "sdk-react");
62
67
  };
63
68
 
69
+ const executePluginBuilds = async (
70
+ config: ResolvedEmbeddableConfig,
71
+ watchers: Array<RollupWatcher | FSWatcher>,
72
+ ) => {
73
+ if (pluginBuildInProgress) {
74
+ // If a plugin build is already in progress, queue this one
75
+ return new Promise<void>((resolve) => {
76
+ pendingPluginBuilds.push(async () => {
77
+ await doPluginBuilds(config, watchers);
78
+ resolve();
79
+ });
80
+ });
81
+ } else {
82
+ // Start the plugin build immediately
83
+ await doPluginBuilds(config, watchers);
84
+ }
85
+ };
86
+
87
+ const doPluginBuilds = async (
88
+ config: ResolvedEmbeddableConfig,
89
+ watchers: Array<RollupWatcher | FSWatcher>,
90
+ ) => {
91
+ pluginBuildInProgress = true;
92
+
93
+ try {
94
+ for (const getPlugin of config.plugins) {
95
+ const plugin = getPlugin();
96
+
97
+ await plugin.validate(config);
98
+ const watcher = await plugin.build(config);
99
+ await configureWatcher(watcher as RollupWatcher, config);
100
+ watchers.push(watcher as RollupWatcher);
101
+ }
102
+ } finally {
103
+ pluginBuildInProgress = false;
104
+
105
+ // Process any pending builds
106
+ if (pendingPluginBuilds.length > 0) {
107
+ const nextBuild = pendingPluginBuilds.shift();
108
+ if (nextBuild) {
109
+ await nextBuild();
110
+ }
111
+ }
112
+ }
113
+ };
114
+
64
115
  const addToGitingore = async () => {
65
116
  try {
66
117
  const gitignorePath = path.resolve(process.cwd(), ".gitignore");
@@ -174,9 +225,9 @@ export default async () => {
174
225
  const done = finalhandler(request, res);
175
226
 
176
227
  try {
177
- if (request.url?.endsWith(GLOBAL_CSS)) {
228
+ if (request.url?.endsWith(CUSTOM_CANVAS_CSS)) {
178
229
  res.writeHead(200, { "Content-Type": "text/css" });
179
- res.end(await fs.readFile(config.client.globalCss));
230
+ res.end(await fs.readFile(config.client.customCanvasCss));
180
231
  return;
181
232
  }
182
233
  } catch {}
@@ -214,20 +265,11 @@ export default async () => {
214
265
  await sendBuildChanges(config);
215
266
 
216
267
  if (config.pushComponents) {
217
- for (const getPlugin of config.plugins) {
218
- const plugin = getPlugin();
219
-
220
- breadcrumbs.push("validate plugin");
221
- await plugin.validate(config);
222
- breadcrumbs.push("build plugin");
223
- const watcher = await plugin.build(config);
224
- breadcrumbs.push("configure watcher");
225
- await configureWatcher(watcher as RollupWatcher, config);
226
- watchers.push(watcher as RollupWatcher);
227
- }
268
+ breadcrumbs.push("build plugins with coordination");
269
+ await executePluginBuilds(config, watchers);
228
270
 
229
- const customGlobalCssWatch = globalCssWatcher(config);
230
- watchers.push(customGlobalCssWatch);
271
+ const customCanvasCssWatch = globalCustomCanvasWatcher(config);
272
+ watchers.push(customCanvasCssWatch);
231
273
 
232
274
  if (themeWatcher) {
233
275
  await globalHookWatcher(themeWatcher);
@@ -373,8 +415,13 @@ const cubeSecurityContextAndClientContextWatcher = async (
373
415
  return fsWatcher;
374
416
  };
375
417
 
376
- const globalCssWatcher = (ctx: ResolvedEmbeddableConfig): FSWatcher => {
377
- const fsWatcher = chokidar.watch(ctx.client.globalCss, chokidarWatchOptions);
418
+ const globalCustomCanvasWatcher = (
419
+ ctx: ResolvedEmbeddableConfig,
420
+ ): FSWatcher => {
421
+ const fsWatcher = chokidar.watch(
422
+ ctx.client.customCanvasCss,
423
+ chokidarWatchOptions,
424
+ );
378
425
 
379
426
  fsWatcher.on("all", async () => {
380
427
  sendMessage("globalCssUpdateSuccess");
package/src/push.test.ts CHANGED
@@ -88,7 +88,7 @@ const config = {
88
88
  rootDir: "rootDir",
89
89
  buildDir: "buildDir",
90
90
  archiveFile: "embeddable-build.zip",
91
- globalCss: "src/global.css",
91
+ customCanvasCss: "src/custom-canvas.css",
92
92
  },
93
93
  pushBaseUrl: "http://localhost:3000",
94
94
  previewBaseUrl: "http://localhost:3000",
@@ -108,18 +108,18 @@ describe("push", () => {
108
108
  vi.mocked(checkBuildSuccess).mockResolvedValue(true);
109
109
  vi.mocked(getArgumentByKey).mockReturnValue(undefined);
110
110
  vi.mocked(provideConfig).mockResolvedValue(
111
- config as ResolvedEmbeddableConfig,
111
+ config as ResolvedEmbeddableConfig
112
112
  );
113
113
  vi.mocked(fs.access).mockResolvedValue(undefined);
114
114
  vi.mocked(fs.readFile).mockImplementation(async () =>
115
- Buffer.from(`{"access_token":"mocked-token"}`),
115
+ Buffer.from(`{"access_token":"mocked-token"}`)
116
116
  );
117
117
  vi.mocked(fs.stat).mockResolvedValue({
118
118
  size: 100,
119
119
  } as any);
120
120
  vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]);
121
121
  vi.mocked(fileFromPath).mockReturnValue(
122
- new Blob([new ArrayBuffer(8)]) as any,
122
+ new Blob([new ArrayBuffer(8)]) as any
123
123
  );
124
124
  vi.mocked(archiver.create).mockReturnValue(archiveMock as any);
125
125
 
@@ -146,11 +146,11 @@ describe("push", () => {
146
146
  zlib: { level: 9 },
147
147
  });
148
148
  expect(fsSync.createWriteStream).toHaveBeenCalledWith(
149
- config.client.archiveFile,
149
+ config.client.archiveFile
150
150
  );
151
151
 
152
152
  expect(archiveMock.pipe).toHaveBeenCalled();
153
- expect(archiveMock.file).toHaveBeenCalledWith("src/global.css", {
153
+ expect(archiveMock.file).toHaveBeenCalledWith("src/custom-canvas.css", {
154
154
  name: "global.css",
155
155
  });
156
156
  expect(archiveMock.directory).toHaveBeenCalledWith("buildDir", false);
@@ -159,10 +159,10 @@ describe("push", () => {
159
159
  expect(fs.rm).toHaveBeenCalledWith(config.client.archiveFile);
160
160
 
161
161
  expect(infoMock.info).toHaveBeenCalledWith(
162
- "Publishing to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id...",
162
+ "Publishing to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id..."
163
163
  );
164
164
  expect(infoMock.succeed).toHaveBeenCalledWith(
165
- "Published to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id",
165
+ "Published to mocked-workspace-name using http://localhost:3000/workspace/mocked-workspace-id"
166
166
  );
167
167
  });
168
168
 
@@ -171,7 +171,7 @@ describe("push", () => {
171
171
  server.use(
172
172
  http.get("**/workspace", () => {
173
173
  return HttpResponse.json([]);
174
- }),
174
+ })
175
175
  );
176
176
 
177
177
  await push();
@@ -190,7 +190,7 @@ describe("push", () => {
190
190
  expect(process.exit).toHaveBeenCalledWith(1);
191
191
 
192
192
  expect(console.error).toHaveBeenCalledWith(
193
- "Build failed or not completed. Please run `embeddable:build` first.",
193
+ "Build failed or not completed. Please run `embeddable:build` first."
194
194
  );
195
195
  });
196
196
 
@@ -224,7 +224,7 @@ describe("push", () => {
224
224
  await push();
225
225
 
226
226
  expect(startMock.fail).toHaveBeenCalledWith(
227
- "Cannot push: both pushModels and pushComponents are disabled",
227
+ "Cannot push: both pushModels and pushComponents are disabled"
228
228
  );
229
229
  expect(process.exit).toHaveBeenCalledWith(1);
230
230
  });
@@ -286,7 +286,7 @@ describe("push", () => {
286
286
  await push();
287
287
 
288
288
  expect(startMock.fail).toHaveBeenCalledWith(
289
- "Invalid email provided. Please provide a valid email using --email (-e) flag",
289
+ "Invalid email provided. Please provide a valid email using --email (-e) flag"
290
290
  );
291
291
  expect(process.exit).toHaveBeenCalledWith(1);
292
292
  });
@@ -305,7 +305,7 @@ describe("push", () => {
305
305
  await push();
306
306
 
307
307
  expect(startMock.fail).toHaveBeenCalledWith(
308
- "Invalid email provided. Please provide a valid email using --email (-e) flag",
308
+ "Invalid email provided. Please provide a valid email using --email (-e) flag"
309
309
  );
310
310
  expect(process.exit).toHaveBeenCalledWith(1);
311
311
  });
@@ -355,13 +355,13 @@ describe("push", () => {
355
355
  vi.spyOn(console, "error").mockImplementation(() => undefined);
356
356
  vi.mocked(fs.access).mockRejectedValue(new Error("No such directory"));
357
357
  vi.mocked(provideConfig).mockResolvedValue(
358
- config as ResolvedEmbeddableConfig,
358
+ config as ResolvedEmbeddableConfig
359
359
  );
360
360
 
361
361
  await push();
362
362
 
363
363
  expect(console.error).toHaveBeenCalledWith(
364
- "No embeddable build was produced.",
364
+ "No embeddable build was produced."
365
365
  );
366
366
  expect(process.exit).toHaveBeenCalledWith(1);
367
367
  });
@@ -371,13 +371,13 @@ describe("push", () => {
371
371
  vi.mocked(fs.access).mockResolvedValue(undefined);
372
372
  vi.mocked(fs.readFile).mockResolvedValue(Buffer.from("{}"));
373
373
  vi.mocked(provideConfig).mockResolvedValue(
374
- config as ResolvedEmbeddableConfig,
374
+ config as ResolvedEmbeddableConfig
375
375
  );
376
376
 
377
377
  await push();
378
378
 
379
379
  expect(console.error).toHaveBeenCalledWith(
380
- "Expired token. Please login again.",
380
+ "Expired token. Please login again."
381
381
  );
382
382
  expect(process.exit).toHaveBeenCalledWith(1);
383
383
  });
@@ -387,7 +387,7 @@ describe("push", () => {
387
387
  vi.mocked(provideConfig).mockRejectedValue(testError);
388
388
  vi.mocked(fs.access).mockResolvedValue(undefined);
389
389
  vi.mocked(fs.readFile).mockImplementation(async () =>
390
- Buffer.from(`{"access_token":"mocked-token"}`),
390
+ Buffer.from(`{"access_token":"mocked-token"}`)
391
391
  );
392
392
 
393
393
  await push();
@@ -450,40 +450,40 @@ describe("push", () => {
450
450
  // Should include component build directory
451
451
  expect(mockArchiver.directory).toHaveBeenCalledWith(
452
452
  testConfig.client.buildDir,
453
- false,
453
+ false
454
454
  );
455
455
  // Should include global.css
456
456
  expect(mockArchiver.file).toHaveBeenCalledWith(
457
- testConfig.client.globalCss,
457
+ testConfig.client.customCanvasCss,
458
458
  {
459
459
  name: "global.css",
460
- },
460
+ }
461
461
  );
462
462
  // Should include all model files
463
463
  expect(mockArchiver.file).toHaveBeenCalledWith(
464
464
  "/path/to/model1.cube.yml",
465
465
  {
466
466
  name: "model1.cube.yml",
467
- },
467
+ }
468
468
  );
469
469
  expect(mockArchiver.file).toHaveBeenCalledWith(
470
470
  "/path/to/model2.cube.yaml",
471
471
  {
472
472
  name: "model2.cube.yaml",
473
- },
473
+ }
474
474
  );
475
475
  // Should include all preset files
476
476
  expect(mockArchiver.file).toHaveBeenCalledWith(
477
477
  "/path/to/context1.sc.yml",
478
478
  {
479
479
  name: "context1.sc.yml",
480
- },
480
+ }
481
481
  );
482
482
  expect(mockArchiver.file).toHaveBeenCalledWith(
483
483
  "/path/to/context2.cc.yml",
484
484
  {
485
485
  name: "context2.cc.yml",
486
- },
486
+ }
487
487
  );
488
488
  });
489
489
 
@@ -503,14 +503,14 @@ describe("push", () => {
503
503
  // Should include component build directory
504
504
  expect(mockArchiver.directory).toHaveBeenCalledWith(
505
505
  testConfig.client.buildDir,
506
- false,
506
+ false
507
507
  );
508
508
  // Should include global.css
509
509
  expect(mockArchiver.file).toHaveBeenCalledWith(
510
- testConfig.client.globalCss,
510
+ testConfig.client.customCanvasCss,
511
511
  {
512
512
  name: "global.css",
513
- },
513
+ }
514
514
  );
515
515
  // Should only find client context files
516
516
  expect(findFiles).toHaveBeenCalledOnce();
@@ -533,11 +533,11 @@ describe("push", () => {
533
533
 
534
534
  expect(findFiles).toHaveBeenCalledWith(
535
535
  "/custom/models/path",
536
- expect.any(RegExp),
536
+ expect.any(RegExp)
537
537
  );
538
538
  expect(findFiles).toHaveBeenCalledWith(
539
539
  "/custom/presets/path",
540
- expect.any(RegExp),
540
+ expect.any(RegExp)
541
541
  );
542
542
  });
543
543
 
package/src/push.ts CHANGED
@@ -240,7 +240,8 @@ export async function archive(args: {
240
240
  archive.pipe(output);
241
241
  if (!isDev) {
242
242
  archive.directory(ctx.client.buildDir, false);
243
- archive.file(ctx.client.globalCss, {
243
+ // NOTE: for backward compatibility, keep the file name as global.css
244
+ archive.file(ctx.client.customCanvasCss, {
244
245
  name: "global.css",
245
246
  });
246
247
  }