@embeddable.com/sdk-core 4.2.0-next.0 → 4.3.0-next.0

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.
@@ -0,0 +1,26 @@
1
+ import { ServerResponse } from "http";
2
+ /**
3
+ * Wraps a ServerResponse object to prevent setting the Content-Length header.
4
+ */
5
+ export declare function preventContentLength(res: ServerResponse): void;
6
+ export declare function createWatcherLock(): {
7
+ lock(): void;
8
+ unlock(): void;
9
+ waitUntilFree(): Promise<void>;
10
+ };
11
+ export declare const delay: (ms: number) => Promise<unknown>;
12
+ /**
13
+ * This function waits until a file stabilizes, meaning its size does not change for a certain number of attempts
14
+ * and the content ends with a specific expected tail.
15
+ * It uses stream reading to check the file's content, the same wait serve-static uses.
16
+ * This will help to prevent when serve-static serves a file that is still being written to.
17
+ * One of the issues, related to this, is "Constructor for "embeddable-component#undefined" was not found", that we saw quite often in the past.
18
+ * @param filePath
19
+ * @param expectedTail
20
+ * @param maxAttempts
21
+ * @param stableCount
22
+ */
23
+ export declare function waitUntilFileStable(filePath: string, expectedTail: string, { maxAttempts, requiredStableCount, }?: {
24
+ maxAttempts?: number;
25
+ requiredStableCount?: number;
26
+ }): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "4.2.0-next.0",
3
+ "version": "4.3.0-next.0",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
package/src/dev.test.ts CHANGED
@@ -5,7 +5,7 @@ import provideConfig from "./provideConfig";
5
5
  import buildGlobalHooks from "./buildGlobalHooks";
6
6
  import { getToken } from "./login";
7
7
  import * as chokidar from "chokidar";
8
- import generate from "./generate";
8
+ import generate, { generateDTS } from "./generate";
9
9
  import buildTypes from "./buildTypes";
10
10
  import validate from "./validate";
11
11
  import { findFiles } from "@embeddable.com/sdk-utils";
@@ -15,6 +15,9 @@ import dev, {
15
15
  globalHookWatcher,
16
16
  openDevWorkspacePage,
17
17
  sendBuildChanges,
18
+ onWebComponentBuildFinish,
19
+ waitForStableHmrFiles,
20
+ resetStateForTesting,
18
21
  } from "./dev";
19
22
  import login from "./login";
20
23
  import { checkNodeVersion } from "./utils";
@@ -41,14 +44,18 @@ vi.mock("./buildTypes", () => ({
41
44
  }));
42
45
  vi.mock("./buildGlobalHooks", () => ({ default: vi.fn() }));
43
46
  vi.mock("./prepare", () => ({ default: vi.fn(), removeIfExists: vi.fn() }));
44
- vi.mock("./generate", () => ({ default: vi.fn() }));
47
+ vi.mock("./generate", () => ({
48
+ default: vi.fn(),
49
+ generateDTS: vi.fn(),
50
+ triggerWebComponentRebuild: vi.fn(),
51
+ }));
45
52
  vi.mock("./provideConfig", () => ({ default: vi.fn() }));
46
53
  vi.mock("@stencil/core/sys/node", () => ({
47
54
  createNodeLogger: vi.fn(),
48
55
  createNodeSys: vi.fn(),
49
56
  }));
50
57
  vi.mock("open", () => ({ default: vi.fn() }));
51
- vi.mock("ws", () => ({
58
+ vi.mock("ws", () => ({
52
59
  WebSocketServer: class WebSocketServer {
53
60
  constructor() {}
54
61
  }
@@ -108,10 +115,15 @@ vi.mock("./dev", async (importOriginal) => {
108
115
  };
109
116
  });
110
117
 
111
- // Mock fs/promises module
112
- vi.mock("node:fs/promises", () => ({
113
- readFile: vi.fn(),
114
- appendFile: vi.fn(),
118
+ vi.mock("./utils/dev.utils", () => ({
119
+ createWatcherLock: vi.fn(() => ({
120
+ lock: vi.fn(),
121
+ unlock: vi.fn(),
122
+ waitUntilFree: vi.fn().mockResolvedValue(undefined),
123
+ })),
124
+ delay: vi.fn().mockResolvedValue(undefined),
125
+ preventContentLength: vi.fn(),
126
+ waitUntilFileStable: vi.fn().mockResolvedValue(undefined),
115
127
  }));
116
128
 
117
129
  const mockConfig = {
@@ -140,6 +152,9 @@ describe("dev command", () => {
140
152
  let mockServer: any;
141
153
 
142
154
  beforeEach(async () => {
155
+ // Reset module-level state between tests
156
+ resetStateForTesting();
157
+
143
158
  listenMock = vi.fn();
144
159
  wsMock = {
145
160
  send: vi.fn(),
@@ -192,9 +207,16 @@ describe("dev command", () => {
192
207
  lifecycleWatcher: watcherMock,
193
208
  });
194
209
 
210
+ // Return a proper stencil watcher mock so buildWebComponent does not crash
211
+ // when it tries to call .on() and .start() on the result of generate()
212
+ const stencilWatcherMock = { on: vi.fn(), start: vi.fn(), close: vi.fn() };
213
+ vi.mocked(generate).mockResolvedValue(stencilWatcherMock as any);
214
+
195
215
  vi.mocked(buildWebComponent).mockImplementation(() => Promise.resolve());
196
216
  vi.mocked(validate).mockImplementation(() => Promise.resolve(true));
197
217
 
218
+ vi.mocked(selectWorkspace).mockResolvedValue({ workspaceId: "mock-workspace" });
219
+
198
220
  vi.mocked(findFiles).mockResolvedValue([
199
221
  ["mock-model.json", "/mock/root/models/mock-model.json"],
200
222
  ]);
@@ -340,7 +362,7 @@ describe("dev command", () => {
340
362
  describe("globalHookWatcher", () => {
341
363
  it("should call watcher.on", async () => {
342
364
  const watcher = { on: vi.fn() } as unknown as RollupWatcher;
343
- globalHookWatcher(watcher);
365
+ globalHookWatcher(watcher, "theme");
344
366
 
345
367
  await expect
346
368
  .poll(() => watcher.on)
@@ -1072,25 +1094,30 @@ describe("dev command", () => {
1072
1094
  }),
1073
1095
  } as unknown as RollupWatcher;
1074
1096
 
1075
- await globalHookWatcher(watcher);
1097
+ await globalHookWatcher(watcher, "theme");
1076
1098
 
1077
1099
  // Test change event
1078
1100
  changeHandler!("/path/to/file.ts");
1079
1101
 
1080
- // Test BUNDLE_START event
1102
+ // Test BUNDLE_START event — key="theme" → type "themeBuildStart"
1081
1103
  await eventHandler!({ code: "BUNDLE_START" });
1082
1104
  expect(mockWss.clients[0].send).toHaveBeenCalledWith(
1083
1105
  JSON.stringify({
1084
- type: "componentsBuildStart",
1106
+ type: "themeBuildStart",
1085
1107
  changedFiles: ["/path/to/file.ts"],
1086
1108
  }),
1087
1109
  );
1088
1110
 
1089
- // Test BUNDLE_END event
1111
+ // Test BUNDLE_END event — key="theme" → type "themeBuildSuccess" with a numeric version
1090
1112
  await eventHandler!({ code: "BUNDLE_END" });
1091
- expect(mockWss.clients[0].send).toHaveBeenCalledWith(
1092
- JSON.stringify({ type: "componentsBuildSuccess" }),
1113
+ const sentMessages = mockWss.clients[0].send.mock.calls.map((call: any) =>
1114
+ JSON.parse(call[0]),
1093
1115
  );
1116
+ const bundleEndMsg = sentMessages.find(
1117
+ (msg: any) => msg.type === "themeBuildSuccess",
1118
+ );
1119
+ expect(bundleEndMsg).toBeDefined();
1120
+ expect(typeof bundleEndMsg.version).toBe("number");
1094
1121
 
1095
1122
  // Test ERROR event
1096
1123
  await eventHandler!({
@@ -1927,4 +1954,147 @@ describe("dev command", () => {
1927
1954
  expect(mockPlugin.build).toHaveBeenCalled();
1928
1955
  });
1929
1956
  });
1957
+
1958
+ describe("onWebComponentBuildFinish", () => {
1959
+ it("should open the workspace page on first call (browserWindow is null)", async () => {
1960
+ const mockOpen = await import("open");
1961
+
1962
+ // Initialize wss by running dev() first
1963
+ await dev();
1964
+
1965
+ const buildResult = {
1966
+ hasSuccessfulBuild: true,
1967
+ hmr: undefined,
1968
+ } as any;
1969
+
1970
+ await onWebComponentBuildFinish(buildResult, mockConfig as any);
1971
+
1972
+ expect(mockOpen.default).toHaveBeenCalledWith(
1973
+ `${mockConfig.previewBaseUrl}/workspace/mock-workspace`,
1974
+ );
1975
+ });
1976
+
1977
+ it("should send HMR message when build has HMR updates", async () => {
1978
+ const mockWss = {
1979
+ clients: [{ send: vi.fn() }],
1980
+ on: vi.fn(),
1981
+ close: vi.fn(),
1982
+ };
1983
+ const wsModule = await import("ws");
1984
+ vi.spyOn(wsModule, "WebSocketServer").mockImplementation(function() {
1985
+ return mockWss as any;
1986
+ } as any);
1987
+
1988
+ // open() must return a truthy ChildProcess so browserWindow is set on the first call
1989
+ vi.mocked(open.default).mockResolvedValue({ unref: vi.fn() } as any);
1990
+
1991
+ await dev();
1992
+
1993
+ // First call: browserWindow is null → opens workspace and returns early
1994
+ const firstBuild = { hasSuccessfulBuild: true, hmr: undefined } as any;
1995
+ await onWebComponentBuildFinish(firstBuild, mockConfig as any);
1996
+
1997
+ // Second call with HMR updates — browserWindow is now truthy, proceeds to send
1998
+ const hmrData = {
1999
+ componentsUpdated: ["my-component"],
2000
+ reloadStrategy: "hmr",
2001
+ };
2002
+ const buildResult = {
2003
+ hasSuccessfulBuild: true,
2004
+ hmr: hmrData,
2005
+ } as any;
2006
+
2007
+ await onWebComponentBuildFinish(buildResult, mockConfig as any);
2008
+
2009
+ const sentMessages = mockWss.clients[0].send.mock.calls.map((call: any) =>
2010
+ JSON.parse(call[0]),
2011
+ );
2012
+ expect(sentMessages).toContainEqual(
2013
+ expect.objectContaining({ type: "componentsBuildSuccessHmr" }),
2014
+ );
2015
+ });
2016
+
2017
+ it("should send componentsBuildSuccess when no HMR updates", async () => {
2018
+ const mockWss = {
2019
+ clients: [{ send: vi.fn() }],
2020
+ on: vi.fn(),
2021
+ close: vi.fn(),
2022
+ };
2023
+ const wsModule = await import("ws");
2024
+ vi.spyOn(wsModule, "WebSocketServer").mockImplementation(function() {
2025
+ return mockWss as any;
2026
+ } as any);
2027
+
2028
+ // open() must return a truthy ChildProcess so browserWindow is set on the first call
2029
+ vi.mocked(open.default).mockResolvedValue({ unref: vi.fn() } as any);
2030
+
2031
+ await dev();
2032
+
2033
+ // First call: browserWindow is null → opens workspace and returns early
2034
+ const firstBuild = { hasSuccessfulBuild: true, hmr: undefined } as any;
2035
+ await onWebComponentBuildFinish(firstBuild, mockConfig as any);
2036
+
2037
+ // Second call without HMR — should send componentsBuildSuccess
2038
+ const buildResult = {
2039
+ hasSuccessfulBuild: true,
2040
+ hmr: { componentsUpdated: undefined, reloadStrategy: "page" },
2041
+ } as any;
2042
+
2043
+ await onWebComponentBuildFinish(buildResult, mockConfig as any);
2044
+
2045
+ const sentMessages = mockWss.clients[0].send.mock.calls.map((call: any) =>
2046
+ JSON.parse(call[0]),
2047
+ );
2048
+ expect(sentMessages).toContainEqual({ type: "componentsBuildSuccess" });
2049
+ });
2050
+ });
2051
+
2052
+ describe("waitForStableHmrFiles", () => {
2053
+ it("should resolve immediately when componentGraph is undefined", async () => {
2054
+ await expect(
2055
+ waitForStableHmrFiles(undefined, mockConfig as any),
2056
+ ).resolves.toBeUndefined();
2057
+ });
2058
+
2059
+ it("should resolve immediately when componentGraph is empty", async () => {
2060
+ await expect(
2061
+ waitForStableHmrFiles({}, mockConfig as any),
2062
+ ).resolves.toBeUndefined();
2063
+ });
2064
+
2065
+ it("should wait for embeddable-component files to stabilize", async () => {
2066
+ const { waitUntilFileStable } = await import("./utils/dev.utils");
2067
+
2068
+ const componentGraph = {
2069
+ "my-component": [
2070
+ "embeddable-component-my.js",
2071
+ "other-file.js",
2072
+ ],
2073
+ } as any;
2074
+
2075
+ await waitForStableHmrFiles(componentGraph, mockConfig as any);
2076
+
2077
+ // Only files starting with "embeddable-component" are waited on
2078
+ expect(waitUntilFileStable).toHaveBeenCalledTimes(1);
2079
+ expect(waitUntilFileStable).toHaveBeenCalledWith(
2080
+ expect.stringContaining("embeddable-component-my.js"),
2081
+ "sourceMappingURL",
2082
+ );
2083
+ });
2084
+
2085
+ it("should wait for all embeddable-component files across multiple components", async () => {
2086
+ const { waitUntilFileStable } = await import("./utils/dev.utils");
2087
+ vi.mocked(waitUntilFileStable).mockClear();
2088
+
2089
+ const componentGraph = {
2090
+ "comp-a": ["embeddable-component-a.js"],
2091
+ "comp-b": ["embeddable-component-b.js", "embeddable-component-b-chunk.js"],
2092
+ } as any;
2093
+
2094
+ await waitForStableHmrFiles(componentGraph, mockConfig as any);
2095
+
2096
+ expect(waitUntilFileStable).toHaveBeenCalledTimes(3);
2097
+ });
2098
+ });
1930
2099
  });
2100
+
package/src/dev.ts CHANGED
@@ -3,9 +3,10 @@ import buildTypes, {
3
3
  EMB_TYPE_FILE_REGEX,
4
4
  } from "./buildTypes";
5
5
  import prepare, { removeIfExists } from "./prepare";
6
- import generate from "./generate";
6
+ import generate, {generateDTS, triggerWebComponentRebuild} from "./generate";
7
7
  import open from "open";
8
8
  import provideConfig from "./provideConfig";
9
+ import { CompilerBuildResults, CompilerWatcher } from "@stencil/core/compiler";
9
10
  import {
10
11
  CompilerSystem,
11
12
  createNodeLogger,
@@ -42,6 +43,13 @@ import finalhandler from "finalhandler";
42
43
  import serveStatic from "serve-static";
43
44
  import { ResolvedEmbeddableConfig } from "./defineConfig";
44
45
  import buildGlobalHooks from "./buildGlobalHooks";
46
+ import {
47
+ createWatcherLock,
48
+ delay,
49
+ preventContentLength,
50
+ waitUntilFileStable
51
+ } from "./utils/dev.utils";
52
+ import { BuildResultsComponentGraph } from "@stencil/core/internal";
45
53
 
46
54
  type FSWatcher = chokidar.FSWatcher;
47
55
 
@@ -62,8 +70,30 @@ const BUILD_DEV_DIR = ".embeddable-dev-build";
62
70
  // NOTE: for backward compatibility, keep the file name as global.css
63
71
  const CUSTOM_CANVAS_CSS = "/global.css";
64
72
 
73
+ let stencilWatcher: CompilerWatcher | undefined;
74
+ let isActiveBundleBuild = false;
75
+
76
+ /** We use two steps compilation for embeddable components.
77
+ * 1. Compile *emb.ts files using plugin complier (sdk-react)
78
+ * 2. Compile the web component using Stencil compiler.
79
+ * These compilations can happen in parallel, but we need to ensure that
80
+ * the first step is not started until the second step is finished (if recompilation is needed).
81
+ * We use this lock to lock it before the second step starts and unlock it after the second step is finished.
82
+ * */
83
+ const lock = createWatcherLock();
84
+
65
85
  export const buildWebComponent = async (config: any) => {
66
- await generate(config, "sdk-react");
86
+ // if there is no watcher, then this is the first build. We need to create a watcher
87
+ // otherwise we can just trigger a rebuild
88
+ if (!stencilWatcher) {
89
+ stencilWatcher = (await generate(config, "sdk-react")) as CompilerWatcher;
90
+ stencilWatcher.on("buildFinish", (e) =>
91
+ onWebComponentBuildFinish(e, config),
92
+ );
93
+ stencilWatcher.start();
94
+ } else {
95
+ await triggerWebComponentRebuild(config);
96
+ }
67
97
  };
68
98
 
69
99
  const executePluginBuilds = async (
@@ -175,7 +205,14 @@ export default async () => {
175
205
  breadcrumbs.push("prepare config");
176
206
  await prepare(config);
177
207
 
178
- const serve = serveStatic(config.client.buildDir);
208
+ const serve = serveStatic(config.client.buildDir, {
209
+ setHeaders: (res, path) => {
210
+ if (path.includes("/dist/embeddable-wrapper/")) {
211
+ // Prevent content length for HMR files
212
+ preventContentLength(res);
213
+ }
214
+ },
215
+ });
179
216
 
180
217
  let workspacePreparation = ora("Preparing workspace...").start();
181
218
 
@@ -233,11 +270,36 @@ export default async () => {
233
270
  }
234
271
  } catch {}
235
272
 
273
+ // Last line of defence: wait for the file to be fully written before
274
+ // handing it to serve-static. This catches any race condition between
275
+ // the WS "build success" notification and the actual HTTP request —
276
+ // e.g. when buildFinish fires slightly before Stencil flushes files.
277
+ const urlPath = (request.url ?? "").split("?")[0];
278
+ if (
279
+ urlPath.includes("/dist/embeddable-wrapper/") &&
280
+ urlPath.endsWith(".js")
281
+ ) {
282
+ const filePath = path.resolve(
283
+ config.client.buildDir,
284
+ urlPath.slice(1),
285
+ );
286
+ await waitUntilFileStable(filePath, "sourceMappingURL", {
287
+ maxAttempts: 40, // up to ~2 s; fast in the happy path
288
+ requiredStableCount: 2,
289
+ }).catch(() => {
290
+ // If the check times out we still serve — better a partial file
291
+ // warning in the console than a hung request.
292
+ });
293
+ }
294
+
236
295
  serve(request, res, done);
237
296
  },
238
297
  );
239
298
 
240
299
  const { themeWatcher, lifecycleWatcher } = await buildGlobalHooks(config);
300
+ const dtsOra = ora("Generating component type files...").start();
301
+ await generateDTS(config)
302
+ dtsOra.succeed("Component type files generated");
241
303
 
242
304
  wss = new WebSocketServer({ server });
243
305
  server.listen(SERVER_PORT, async () => {
@@ -276,11 +338,11 @@ export default async () => {
276
338
  watchers.push(customCanvasCssWatch);
277
339
 
278
340
  if (themeWatcher) {
279
- await globalHookWatcher(themeWatcher);
341
+ await globalHookWatcher(themeWatcher, "themeProvider");
280
342
  watchers.push(themeWatcher);
281
343
  }
282
344
  if (lifecycleWatcher) {
283
- await globalHookWatcher(lifecycleWatcher);
345
+ await globalHookWatcher(lifecycleWatcher, "lifecycleHook");
284
346
  watchers.push(lifecycleWatcher);
285
347
  }
286
348
  } else {
@@ -307,31 +369,52 @@ export const configureWatcher = async (
307
369
  });
308
370
 
309
371
  watcher.on("event", async (e) => {
372
+ if (e.code === "START") {
373
+ await lock.waitUntilFree();
374
+ }
310
375
  if (e.code === "BUNDLE_START") {
376
+ isActiveBundleBuild = true;
311
377
  await onBuildStart(ctx);
312
378
  }
313
379
  if (e.code === "BUNDLE_END") {
380
+ lock.lock();
381
+ isActiveBundleBuild = false;
382
+ if (stencilWatcher && shouldRebuildWebComponent()) {
383
+ try {
384
+ await fs.rm(
385
+ path.resolve(ctx.client.buildDir, "dist", "embeddable-wrapper"),
386
+ { recursive: true },
387
+ );
388
+ } catch (error) {
389
+ console.error("Error cleaning up build directory:", error);
390
+ }
391
+ }
314
392
  await onBundleBuildEnd(ctx);
315
393
  changedFiles = [];
316
394
  }
317
395
  if (e.code === "ERROR") {
396
+ lock.unlock();
397
+ isActiveBundleBuild = false;
318
398
  sendMessage("componentsBuildError", { error: e.error?.message });
319
399
  changedFiles = [];
320
400
  }
321
401
  });
322
402
  };
323
403
 
324
- export const globalHookWatcher = async (watcher: RollupWatcher) => {
404
+ export const globalHookWatcher = async (
405
+ watcher: RollupWatcher,
406
+ key: string,
407
+ ) => {
325
408
  watcher.on("change", (path) => {
326
409
  changedFiles.push(path);
327
410
  });
328
411
 
329
412
  watcher.on("event", async (e) => {
330
413
  if (e.code === "BUNDLE_START") {
331
- sendMessage("componentsBuildStart", { changedFiles });
414
+ sendMessage(`${key}BuildStart`, { changedFiles });
332
415
  }
333
416
  if (e.code === "BUNDLE_END") {
334
- sendMessage("componentsBuildSuccess");
417
+ sendMessage(`${key}BuildSuccess`, { version: new Date().getTime() });
335
418
  changedFiles = [];
336
419
  }
337
420
  if (e.code === "ERROR") {
@@ -371,16 +454,15 @@ export const openDevWorkspacePage = async (
371
454
  return await open(`${previewBaseUrl}/workspace/${workspaceId}`);
372
455
  };
373
456
 
457
+ function shouldRebuildWebComponent() {
458
+ return !onlyTypesChanged() || changedFiles.length === 0;
459
+ }
460
+
374
461
  const onBundleBuildEnd = async (ctx: ResolvedEmbeddableConfig) => {
375
- if (!onlyTypesChanged() || changedFiles.length === 0) {
462
+ if (shouldRebuildWebComponent()) {
376
463
  await buildWebComponent(ctx);
377
- }
378
- if (browserWindow == null) {
379
- browserWindow = await openDevWorkspacePage(
380
- ctx.previewBaseUrl,
381
- previewWorkspace,
382
- );
383
464
  } else {
465
+ lock.unlock();
384
466
  sendMessage("componentsBuildSuccess");
385
467
  }
386
468
  };
@@ -514,6 +596,7 @@ const onClose = async (
514
596
  server.close();
515
597
  wss.close();
516
598
  browserWindow?.unref();
599
+ await stencilWatcher?.close();
517
600
  for (const watcher of watchers) {
518
601
  if (watcher.close) {
519
602
  await watcher.close();
@@ -572,3 +655,68 @@ const getPreviewWorkspace = async (
572
655
  }
573
656
  }
574
657
  };
658
+
659
+ export async function onWebComponentBuildFinish(
660
+ e: CompilerBuildResults,
661
+ config: ResolvedEmbeddableConfig,
662
+ ) {
663
+ lock.unlock();
664
+
665
+ if (!browserWindow) {
666
+ browserWindow = await openDevWorkspacePage(
667
+ config.previewBaseUrl,
668
+ previewWorkspace,
669
+ );
670
+ return;
671
+ }
672
+
673
+ await delay(50);
674
+ if (isActiveBundleBuild) {
675
+ return;
676
+ }
677
+
678
+ if (
679
+ e.hasSuccessfulBuild &&
680
+ e.hmr?.componentsUpdated &&
681
+ e.hmr.reloadStrategy === "hmr"
682
+ ) {
683
+ try {
684
+ await waitForStableHmrFiles(e.componentGraph, config);
685
+ } finally {
686
+ sendMessage("componentsBuildSuccessHmr", e.hmr);
687
+ }
688
+ } else {
689
+ sendMessage("componentsBuildSuccess");
690
+ }
691
+ }
692
+
693
+ export async function waitForStableHmrFiles(
694
+ componentGraph: BuildResultsComponentGraph | undefined,
695
+ config: ResolvedEmbeddableConfig,
696
+ ) {
697
+ const promises = [];
698
+
699
+ for (const files of Object.values(componentGraph ?? {})) {
700
+ for (const file of files) {
701
+ if (file.startsWith("embeddable-component")) {
702
+ const fullPath = path.resolve(
703
+ config.client.buildDir,
704
+ "dist",
705
+ "embeddable-wrapper",
706
+ file,
707
+ );
708
+ promises.push(waitUntilFileStable(fullPath, "sourceMappingURL"));
709
+ }
710
+ }
711
+ }
712
+
713
+ await Promise.all(promises);
714
+ }
715
+
716
+ export function resetStateForTesting() {
717
+ stencilWatcher = undefined;
718
+ isActiveBundleBuild = false;
719
+ pluginBuildInProgress = false;
720
+ pendingPluginBuilds = [];
721
+ browserWindow = null;
722
+ }