@embeddable.com/sdk-core 4.2.0 → 4.3.0-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/lib/dev.d.ts CHANGED
@@ -1,10 +1,15 @@
1
+ import { CompilerBuildResults } from "@stencil/core/compiler";
1
2
  import { RollupWatcher } from "rollup";
2
3
  import { ChildProcess } from "node:child_process";
3
4
  import { ResolvedEmbeddableConfig } from "./defineConfig";
5
+ import { BuildResultsComponentGraph } from "@stencil/core/internal";
4
6
  export declare const buildWebComponent: (config: any) => Promise<void>;
5
7
  declare const _default: () => Promise<void>;
6
8
  export default _default;
7
9
  export declare const configureWatcher: (watcher: RollupWatcher, ctx: ResolvedEmbeddableConfig) => Promise<void>;
8
- export declare const globalHookWatcher: (watcher: RollupWatcher) => Promise<void>;
10
+ export declare const globalHookWatcher: (watcher: RollupWatcher, key: string) => Promise<void>;
9
11
  export declare const openDevWorkspacePage: (previewBaseUrl: string, workspaceId: string) => Promise<ChildProcess>;
10
12
  export declare const sendBuildChanges: (ctx: ResolvedEmbeddableConfig) => Promise<void>;
13
+ export declare function onWebComponentBuildFinish(e: CompilerBuildResults, config: ResolvedEmbeddableConfig): Promise<void>;
14
+ export declare function waitForStableHmrFiles(componentGraph: BuildResultsComponentGraph | undefined, config: ResolvedEmbeddableConfig): Promise<void>;
15
+ export declare function resetStateForTesting(): void;
package/lib/generate.d.ts CHANGED
@@ -1,3 +1,34 @@
1
+ import { CompilerWatcher } from "@stencil/core/compiler";
1
2
  import { PluginName, ResolvedEmbeddableConfig } from "./defineConfig";
2
- declare const _default: (ctx: ResolvedEmbeddableConfig, pluginName: PluginName) => Promise<void>;
3
+ /**
4
+ * Stencil watcher doesnt react on file metadata changes,
5
+ * so we have to change the file content to trigger a rebuild by appending a space character.
6
+ * This constant defines how many times the space character can be appended before the file is truncated back to its original size.
7
+ */
8
+ export declare const TRIGGER_BUILD_ITERATION_LIMIT = 5;
9
+ export declare function resetForTesting(): void;
10
+ /**
11
+ * Triggers a rebuild of a Stencil web component by modifying the `component.tsx` file.
12
+ *
13
+ * This function works by appending a space character to the file, which causes Stencil's watcher
14
+ * to detect a change and rebuild the component. After every TRIGGER_BUILD_ITERATION_LIMIT rebuilds, the file is truncated back
15
+ * to its original size to prevent indefinite growth and reset the internal rebuild counter.
16
+ *
17
+ * Append and truncate are used instead of rewriting the file to ensure minimal I/O overhead and preserve file metadata.
18
+ */
19
+ export declare function triggerWebComponentRebuild(ctx: ResolvedEmbeddableConfig): Promise<void>;
20
+ declare const _default: (ctx: ResolvedEmbeddableConfig, pluginName: PluginName) => Promise<void | CompilerWatcher>;
3
21
  export default _default;
22
+ /**
23
+ * Generates only the d.ts type declaration files using Stencil, without performing a full build.
24
+ * Used in dev mode to pre-generate types before the watcher starts, avoiding a double-build
25
+ * triggered by the watcher reacting to freshly generated d.ts files.
26
+ *
27
+ * Key differences from the default generate function:
28
+ * - Writes an empty style.css stub (no real CSS injection needed for type generation)
29
+ * - Injects a no-op render stub instead of the real render import
30
+ * - Always creates a fresh sys (never reuses ctx.dev?.sys) to avoid watcher interference
31
+ */
32
+ export declare function generateDTS(ctx: ResolvedEmbeddableConfig): Promise<void>;
33
+ export declare function injectCSS(ctx: ResolvedEmbeddableConfig, pluginName: PluginName): Promise<void>;
34
+ export declare function injectBundleRender(ctx: ResolvedEmbeddableConfig, pluginName: PluginName): Promise<void>;
package/lib/index.esm.js CHANGED
@@ -10,7 +10,7 @@ import * as path from 'path';
10
10
  import path__default$1, { basename } from 'path';
11
11
  import fg from 'fast-glob';
12
12
  import * as fsSync from 'node:fs';
13
- import { existsSync } from 'node:fs';
13
+ import { existsSync, createReadStream } from 'node:fs';
14
14
  import { createNodeLogger, createNodeSys } from '@stencil/core/sys/node';
15
15
  import { loadConfig, createCompiler } from '@stencil/core/compiler';
16
16
  import * as sorcery from 'sorcery';
@@ -23,7 +23,7 @@ import require$$1 from 'os';
23
23
  import require$$3 from 'http';
24
24
  import require$$4 from 'https';
25
25
  import require$$0$1 from 'url';
26
- import require$$2$1, { createReadStream } from 'fs';
26
+ import require$$2$1, { createReadStream as createReadStream$1 } from 'fs';
27
27
  import axios from 'axios';
28
28
  import yazl from 'yazl';
29
29
  import { select } from '@inquirer/prompts';
@@ -611,18 +611,79 @@ const STYLE_IMPORTS_TOKEN = "{{STYLES_IMPORT}}";
611
611
  const RENDER_IMPORT_TOKEN = "{{RENDER_IMPORT}}";
612
612
  // stencil doesn't support dynamic component tag name, so we need to replace it manually
613
613
  const COMPONENT_TAG_TOKEN = "replace-this-with-component-name";
614
+ let triggeredBuildCount = 0;
615
+ /**
616
+ * Stencil watcher doesnt react on file metadata changes,
617
+ * so we have to change the file content to trigger a rebuild by appending a space character.
618
+ * This constant defines how many times the space character can be appended before the file is truncated back to its original size.
619
+ */
620
+ const TRIGGER_BUILD_ITERATION_LIMIT = 5;
621
+ let originalFileStats = null;
622
+ /**
623
+ * Triggers a rebuild of a Stencil web component by modifying the `component.tsx` file.
624
+ *
625
+ * This function works by appending a space character to the file, which causes Stencil's watcher
626
+ * to detect a change and rebuild the component. After every TRIGGER_BUILD_ITERATION_LIMIT rebuilds, the file is truncated back
627
+ * to its original size to prevent indefinite growth and reset the internal rebuild counter.
628
+ *
629
+ * Append and truncate are used instead of rewriting the file to ensure minimal I/O overhead and preserve file metadata.
630
+ */
631
+ async function triggerWebComponentRebuild(ctx) {
632
+ const filePath = path$1.resolve(ctx.client.componentDir, "component.tsx");
633
+ if (triggeredBuildCount === 0) {
634
+ // store original file stats on the first build
635
+ originalFileStats = await fs.stat(filePath);
636
+ }
637
+ if (triggeredBuildCount === TRIGGER_BUILD_ITERATION_LIMIT && originalFileStats) {
638
+ await fs.truncate(filePath, originalFileStats.size);
639
+ triggeredBuildCount = 0; // reset the counter after resetting the file
640
+ }
641
+ else {
642
+ await fs.appendFile(filePath, " ");
643
+ triggeredBuildCount++;
644
+ }
645
+ }
614
646
  var generate = async (ctx, pluginName) => {
615
647
  await injectCSS(ctx, pluginName);
616
648
  await injectBundleRender(ctx, pluginName);
617
- await runStencil(ctx);
618
- await generateSourceMap(ctx, pluginName);
649
+ const watcher = await runStencil(ctx);
650
+ if (watcher) {
651
+ watcher.on("buildFinish", () => {
652
+ // stencil always changes the working directory to the root of the web component.
653
+ // We need to change it back to the client root directory
654
+ process.chdir(ctx.client.rootDir);
655
+ generateSourceMap(ctx, pluginName);
656
+ });
657
+ }
658
+ else {
659
+ await generateSourceMap(ctx, pluginName);
660
+ }
661
+ return watcher;
619
662
  };
663
+ /**
664
+ * Generates only the d.ts type declaration files using Stencil, without performing a full build.
665
+ * Used in dev mode to pre-generate types before the watcher starts, avoiding a double-build
666
+ * triggered by the watcher reacting to freshly generated d.ts files.
667
+ *
668
+ * Key differences from the default generate function:
669
+ * - Writes an empty style.css stub (no real CSS injection needed for type generation)
670
+ * - Injects a no-op render stub instead of the real render import
671
+ * - Always creates a fresh sys (never reuses ctx.dev?.sys) to avoid watcher interference
672
+ */
673
+ async function generateDTS(ctx) {
674
+ await injectEmptyCSS(ctx);
675
+ await injectBundleRenderStub(ctx);
676
+ await runStencil(ctx, { dtsOnly: true });
677
+ }
620
678
  async function injectCSS(ctx, pluginName) {
621
679
  const CUSTOMER_BUILD = path$1.resolve(ctx.client.buildDir, ctx[pluginName].outputOptions.buildName);
622
680
  const allFiles = await fs.readdir(CUSTOMER_BUILD);
681
+ const importFilePath = path$1
682
+ .relative(ctx.client.componentDir, path$1.resolve(ctx.client.buildDir, ctx[pluginName].outputOptions.buildName))
683
+ .replaceAll("\\", "/");
623
684
  const imports = allFiles
624
685
  .filter((fileName) => fileName.endsWith(".css"))
625
- .map((fileName) => `@import '../../${ctx[pluginName].outputOptions.buildName}/${fileName}';`);
686
+ .map((fileName) => `@import '${importFilePath}/${fileName}';`);
626
687
  const componentLibraries = ctx.client.componentLibraries;
627
688
  for (const componentLibrary of componentLibraries) {
628
689
  const { libraryName } = getComponentLibraryConfig(componentLibrary);
@@ -637,13 +698,25 @@ async function injectCSS(ctx, pluginName) {
637
698
  }
638
699
  async function injectBundleRender(ctx, pluginName) {
639
700
  var _a;
640
- const importStr = `import render from '../../${ctx[pluginName].outputOptions.buildName}/${ctx[pluginName].outputOptions.fileName}';`;
701
+ const importFilePath = path$1
702
+ .relative(ctx.client.componentDir, path$1.resolve(ctx.client.buildDir, ctx[pluginName].outputOptions.buildName))
703
+ .replaceAll("\\", "/");
704
+ const importStr = `import render from '${importFilePath}/${ctx[pluginName].outputOptions.fileName}';`;
641
705
  let content = await fs.readFile(path$1.resolve(ctx.core.templatesDir, "component.tsx.template"), "utf8");
642
706
  if (!!((_a = ctx.dev) === null || _a === void 0 ? void 0 : _a.watch)) {
643
707
  content = content.replace(COMPONENT_TAG_TOKEN, "embeddable-component");
644
708
  }
645
709
  await fs.writeFile(path$1.resolve(ctx.client.componentDir, "component.tsx"), content.replace(RENDER_IMPORT_TOKEN, importStr));
646
710
  }
711
+ async function injectEmptyCSS(ctx) {
712
+ await fs.writeFile(path$1.resolve(ctx.client.componentDir, "style.css"), "");
713
+ }
714
+ async function injectBundleRenderStub(ctx) {
715
+ const stubStr = `const render = () => {};`;
716
+ let content = await fs.readFile(path$1.resolve(ctx.core.templatesDir, "component.tsx.template"), "utf8");
717
+ content = content.replace(COMPONENT_TAG_TOKEN, "embeddable-component");
718
+ await fs.writeFile(path$1.resolve(ctx.client.componentDir, "component.tsx"), content.replace(RENDER_IMPORT_TOKEN, stubStr));
719
+ }
647
720
  async function addComponentTagName(filePath, bundleHash) {
648
721
  // find entry file with a name *.entry.js
649
722
  const entryFiles = await findFiles(path$1.dirname(filePath), /.*\.entry\.js/);
@@ -662,11 +735,18 @@ async function addComponentTagName(filePath, bundleHash) {
662
735
  fs.writeFile(entryFileName[1], newEntryFileContent),
663
736
  ]);
664
737
  }
665
- async function runStencil(ctx) {
738
+ async function runStencil(ctx, options) {
666
739
  var _a, _b, _c;
667
- const logger = ((_a = ctx.dev) === null || _a === void 0 ? void 0 : _a.logger) || createNodeLogger();
668
- const sys = ((_b = ctx.dev) === null || _b === void 0 ? void 0 : _b.sys) || createNodeSys({ process });
669
- const devMode = !!((_c = ctx.dev) === null || _c === void 0 ? void 0 : _c.watch);
740
+ const logger = ((options === null || options === void 0 ? void 0 : options.dtsOnly) ? createNodeLogger() : ((_a = ctx.dev) === null || _a === void 0 ? void 0 : _a.logger) || createNodeLogger());
741
+ const sys = (options === null || options === void 0 ? void 0 : options.dtsOnly) ? createNodeSys({ process }) : (((_b = ctx.dev) === null || _b === void 0 ? void 0 : _b.sys) || createNodeSys({ process }));
742
+ const devMode = !!((_c = ctx.dev) === null || _c === void 0 ? void 0 : _c.watch) && !(options === null || options === void 0 ? void 0 : options.dtsOnly);
743
+ if (options === null || options === void 0 ? void 0 : options.dtsOnly) {
744
+ logger.setLevel("error");
745
+ logger.createTimeSpan = () => ({
746
+ duration: () => 0,
747
+ finish: () => 0,
748
+ });
749
+ }
670
750
  const isWindows = process.platform === "win32";
671
751
  const validated = await loadConfig({
672
752
  initTsConfig: true,
@@ -675,14 +755,16 @@ async function runStencil(ctx) {
675
755
  config: {
676
756
  devMode,
677
757
  maxConcurrentWorkers: isWindows ? 0 : 8, // workers break on windows
758
+ // we will trigger a rebuild by updating the component.tsx file (see triggerBuild function)
759
+ watchIgnoredRegex: [/\.css$/, /\.d\.ts$/, /\.js$/],
678
760
  rootDir: ctx.client.webComponentRoot,
679
761
  configPath: path$1.resolve(ctx.client.webComponentRoot, "stencil.config.ts"),
680
762
  tsconfig: path$1.resolve(ctx.client.webComponentRoot, "tsconfig.json"),
681
763
  namespace: "embeddable-wrapper",
682
764
  srcDir: ctx.client.componentDir,
683
- sourceMap: true, // always generate source maps in both dev and prod
684
- minifyJs: !devMode,
685
- minifyCss: !devMode,
765
+ sourceMap: !(options === null || options === void 0 ? void 0 : options.dtsOnly), // always generate source maps in both dev and prod
766
+ minifyJs: !devMode && !(options === null || options === void 0 ? void 0 : options.dtsOnly),
767
+ minifyCss: !devMode && !(options === null || options === void 0 ? void 0 : options.dtsOnly),
686
768
  outputTargets: [
687
769
  {
688
770
  type: "dist",
@@ -692,17 +774,21 @@ async function runStencil(ctx) {
692
774
  },
693
775
  });
694
776
  const compiler = await createCompiler(validated.config);
777
+ if (devMode) {
778
+ sys.onProcessInterrupt(() => {
779
+ compiler.destroy();
780
+ });
781
+ return await compiler.createWatcher();
782
+ }
695
783
  const buildResults = await compiler.build();
696
- if (!devMode) {
697
- if (buildResults.hasError) {
698
- console.error("Stencil build error:", buildResults.diagnostics);
699
- throw new Error("Stencil build error");
700
- }
701
- else {
702
- await handleStencilBuildOutput(ctx);
703
- }
704
- await compiler.destroy();
784
+ if (buildResults.hasError) {
785
+ console.error("Stencil build error:", buildResults.diagnostics);
786
+ throw new Error("Stencil build error");
705
787
  }
788
+ else {
789
+ await handleStencilBuildOutput(ctx);
790
+ }
791
+ await compiler.destroy();
706
792
  process.chdir(ctx.client.rootDir);
707
793
  }
708
794
  async function handleStencilBuildOutput(ctx) {
@@ -21587,6 +21673,95 @@ function checkAndLogWarnings(response) {
21587
21673
  }
21588
21674
  }
21589
21675
 
21676
+ /**
21677
+ * Wraps a ServerResponse object to prevent setting the Content-Length header.
21678
+ */
21679
+ function preventContentLength(res) {
21680
+ const originalSetHeader = res.setHeader.bind(res);
21681
+ res.setHeader = function (key, value) {
21682
+ if (key.toLowerCase() === "content-length") {
21683
+ return this;
21684
+ }
21685
+ return originalSetHeader(key, value);
21686
+ };
21687
+ }
21688
+ function createWatcherLock() {
21689
+ let locked = false;
21690
+ let waiters = [];
21691
+ return {
21692
+ lock() {
21693
+ if (!locked) {
21694
+ locked = true;
21695
+ }
21696
+ },
21697
+ unlock() {
21698
+ if (locked) {
21699
+ locked = false;
21700
+ waiters.forEach((fn) => fn());
21701
+ waiters = [];
21702
+ }
21703
+ },
21704
+ async waitUntilFree() {
21705
+ if (!locked)
21706
+ return;
21707
+ await new Promise((resolve) => {
21708
+ waiters.push(resolve);
21709
+ });
21710
+ },
21711
+ };
21712
+ }
21713
+ const delay = (ms) => new Promise((res) => setTimeout(res, ms));
21714
+ /**
21715
+ * This function waits until a file stabilizes, meaning its size does not change for a certain number of attempts
21716
+ * and the content ends with a specific expected tail.
21717
+ * It uses stream reading to check the file's content, the same wait serve-static uses.
21718
+ * This will help to prevent when serve-static serves a file that is still being written to.
21719
+ * One of the issues, related to this, is "Constructor for "embeddable-component#undefined" was not found", that we saw quite often in the past.
21720
+ * @param filePath
21721
+ * @param expectedTail
21722
+ * @param maxAttempts
21723
+ * @param stableCount
21724
+ */
21725
+ async function waitUntilFileStable(filePath, expectedTail, { maxAttempts = 100, requiredStableCount = 2, } = {}) {
21726
+ let lastSize = -1;
21727
+ let stableCounter = 0;
21728
+ for (let i = 0; i < maxAttempts; i++) {
21729
+ try {
21730
+ const { size, tailMatches } = await checkFileTail(filePath, expectedTail);
21731
+ if (size === lastSize && size > 0 && tailMatches) {
21732
+ stableCounter++;
21733
+ if (stableCounter >= requiredStableCount) {
21734
+ return;
21735
+ }
21736
+ }
21737
+ else {
21738
+ stableCounter = 0;
21739
+ }
21740
+ lastSize = size;
21741
+ }
21742
+ catch (_a) {
21743
+ }
21744
+ await delay(50);
21745
+ }
21746
+ throw new Error("File did not stabilize");
21747
+ }
21748
+ async function checkFileTail(filePath, expectedTail, tailLength = 500) {
21749
+ const stats = await fs.stat(filePath);
21750
+ const size = stats.size;
21751
+ const start = Math.max(0, size - tailLength);
21752
+ return new Promise((resolve, reject) => {
21753
+ let tailBuffer = "";
21754
+ createReadStream(filePath, { encoding: "utf-8", start })
21755
+ .on("data", (chunk) => {
21756
+ tailBuffer += chunk;
21757
+ })
21758
+ .on("end", () => {
21759
+ resolve({ size, tailMatches: tailBuffer.includes(expectedTail) });
21760
+ })
21761
+ .on("error", reject);
21762
+ });
21763
+ }
21764
+
21590
21765
  dotenv.config();
21591
21766
  let wss;
21592
21767
  let changedFiles = [];
@@ -21599,8 +21774,27 @@ const SERVER_PORT = 8926;
21599
21774
  const BUILD_DEV_DIR = ".embeddable-dev-build";
21600
21775
  // NOTE: for backward compatibility, keep the file name as global.css
21601
21776
  const CUSTOM_CANVAS_CSS = "/global.css";
21777
+ let stencilWatcher;
21778
+ let isActiveBundleBuild = false;
21779
+ /** We use two steps compilation for embeddable components.
21780
+ * 1. Compile *emb.ts files using plugin complier (sdk-react)
21781
+ * 2. Compile the web component using Stencil compiler.
21782
+ * These compilations can happen in parallel, but we need to ensure that
21783
+ * the first step is not started until the second step is finished (if recompilation is needed).
21784
+ * We use this lock to lock it before the second step starts and unlock it after the second step is finished.
21785
+ * */
21786
+ const lock = createWatcherLock();
21602
21787
  const buildWebComponent = async (config) => {
21603
- await generate(config, "sdk-react");
21788
+ // if there is no watcher, then this is the first build. We need to create a watcher
21789
+ // otherwise we can just trigger a rebuild
21790
+ if (!stencilWatcher) {
21791
+ stencilWatcher = (await generate(config, "sdk-react"));
21792
+ stencilWatcher.on("buildFinish", (e) => onWebComponentBuildFinish(e, config));
21793
+ stencilWatcher.start();
21794
+ }
21795
+ else {
21796
+ await triggerWebComponentRebuild(config);
21797
+ }
21604
21798
  };
21605
21799
  const executePluginBuilds = async (config, watchers) => {
21606
21800
  if (pluginBuildInProgress) {
@@ -21690,7 +21884,14 @@ var dev = async () => {
21690
21884
  };
21691
21885
  breadcrumbs.push("prepare config");
21692
21886
  await prepare$1(config);
21693
- const serve = serveStatic(config.client.buildDir);
21887
+ const serve = serveStatic(config.client.buildDir, {
21888
+ setHeaders: (res, path) => {
21889
+ if (path.includes("/dist/embeddable-wrapper/")) {
21890
+ // Prevent content length for HMR files
21891
+ preventContentLength(res);
21892
+ }
21893
+ },
21894
+ });
21694
21895
  let workspacePreparation = ora("Preparing workspace...").start();
21695
21896
  breadcrumbs.push("get preview workspace");
21696
21897
  try {
@@ -21710,7 +21911,7 @@ var dev = async () => {
21710
21911
  }
21711
21912
  workspacePreparation.succeed("Workspace is ready");
21712
21913
  const server = http.createServer(async (request, res) => {
21713
- var _a;
21914
+ var _a, _b;
21714
21915
  res.setHeader("Access-Control-Allow-Origin", "*");
21715
21916
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
21716
21917
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
@@ -21728,10 +21929,29 @@ var dev = async () => {
21728
21929
  return;
21729
21930
  }
21730
21931
  }
21731
- catch (_b) { }
21932
+ catch (_c) { }
21933
+ // Last line of defence: wait for the file to be fully written before
21934
+ // handing it to serve-static. This catches any race condition between
21935
+ // the WS "build success" notification and the actual HTTP request —
21936
+ // e.g. when buildFinish fires slightly before Stencil flushes files.
21937
+ const urlPath = ((_b = request.url) !== null && _b !== void 0 ? _b : "").split("?")[0];
21938
+ if (urlPath.includes("/dist/embeddable-wrapper/") &&
21939
+ urlPath.endsWith(".js")) {
21940
+ const filePath = path.resolve(config.client.buildDir, urlPath.slice(1));
21941
+ await waitUntilFileStable(filePath, "sourceMappingURL", {
21942
+ maxAttempts: 40, // up to ~2 s; fast in the happy path
21943
+ requiredStableCount: 2,
21944
+ }).catch(() => {
21945
+ // If the check times out we still serve — better a partial file
21946
+ // warning in the console than a hung request.
21947
+ });
21948
+ }
21732
21949
  serve(request, res, done);
21733
21950
  });
21734
21951
  const { themeWatcher, lifecycleWatcher } = await buildGlobalHooks(config);
21952
+ const dtsOra = ora("Generating component type files...").start();
21953
+ await generateDTS(config);
21954
+ dtsOra.succeed("Component type files generated");
21735
21955
  wss = new WebSocketServer({ server });
21736
21956
  server.listen(SERVER_PORT, async () => {
21737
21957
  const watchers = [];
@@ -21762,11 +21982,11 @@ var dev = async () => {
21762
21982
  const customCanvasCssWatch = globalCustomCanvasWatcher(config);
21763
21983
  watchers.push(customCanvasCssWatch);
21764
21984
  if (themeWatcher) {
21765
- await globalHookWatcher(themeWatcher);
21985
+ await globalHookWatcher(themeWatcher, "themeProvider");
21766
21986
  watchers.push(themeWatcher);
21767
21987
  }
21768
21988
  if (lifecycleWatcher) {
21769
- await globalHookWatcher(lifecycleWatcher);
21989
+ await globalHookWatcher(lifecycleWatcher, "lifecycleHook");
21770
21990
  watchers.push(lifecycleWatcher);
21771
21991
  }
21772
21992
  }
@@ -21789,30 +22009,46 @@ const configureWatcher = async (watcher, ctx) => {
21789
22009
  });
21790
22010
  watcher.on("event", async (e) => {
21791
22011
  var _a;
22012
+ if (e.code === "START") {
22013
+ await lock.waitUntilFree();
22014
+ }
21792
22015
  if (e.code === "BUNDLE_START") {
22016
+ isActiveBundleBuild = true;
21793
22017
  await onBuildStart(ctx);
21794
22018
  }
21795
22019
  if (e.code === "BUNDLE_END") {
22020
+ lock.lock();
22021
+ isActiveBundleBuild = false;
22022
+ if (stencilWatcher && shouldRebuildWebComponent()) {
22023
+ try {
22024
+ await fs.rm(path.resolve(ctx.client.buildDir, "dist", "embeddable-wrapper"), { recursive: true });
22025
+ }
22026
+ catch (error) {
22027
+ console.error("Error cleaning up build directory:", error);
22028
+ }
22029
+ }
21796
22030
  await onBundleBuildEnd(ctx);
21797
22031
  changedFiles = [];
21798
22032
  }
21799
22033
  if (e.code === "ERROR") {
22034
+ lock.unlock();
22035
+ isActiveBundleBuild = false;
21800
22036
  sendMessage("componentsBuildError", { error: (_a = e.error) === null || _a === void 0 ? void 0 : _a.message });
21801
22037
  changedFiles = [];
21802
22038
  }
21803
22039
  });
21804
22040
  };
21805
- const globalHookWatcher = async (watcher) => {
22041
+ const globalHookWatcher = async (watcher, key) => {
21806
22042
  watcher.on("change", (path) => {
21807
22043
  changedFiles.push(path);
21808
22044
  });
21809
22045
  watcher.on("event", async (e) => {
21810
22046
  var _a;
21811
22047
  if (e.code === "BUNDLE_START") {
21812
- sendMessage("componentsBuildStart", { changedFiles });
22048
+ sendMessage(`${key}BuildStart`, { changedFiles });
21813
22049
  }
21814
22050
  if (e.code === "BUNDLE_END") {
21815
- sendMessage("componentsBuildSuccess");
22051
+ sendMessage(`${key}BuildSuccess`, { version: new Date().getTime() });
21816
22052
  changedFiles = [];
21817
22053
  }
21818
22054
  if (e.code === "ERROR") {
@@ -21841,14 +22077,15 @@ const openDevWorkspacePage = async (previewBaseUrl, workspaceId) => {
21841
22077
  ora(`Preview workspace is available at ${previewBaseUrl}/workspace/${workspaceId}`).info();
21842
22078
  return await open(`${previewBaseUrl}/workspace/${workspaceId}`);
21843
22079
  };
22080
+ function shouldRebuildWebComponent() {
22081
+ return !onlyTypesChanged() || changedFiles.length === 0;
22082
+ }
21844
22083
  const onBundleBuildEnd = async (ctx) => {
21845
- if (!onlyTypesChanged() || changedFiles.length === 0) {
22084
+ if (shouldRebuildWebComponent()) {
21846
22085
  await buildWebComponent(ctx);
21847
22086
  }
21848
- if (browserWindow == null) {
21849
- browserWindow = await openDevWorkspacePage(ctx.previewBaseUrl, previewWorkspace);
21850
- }
21851
22087
  else {
22088
+ lock.unlock();
21852
22089
  sendMessage("componentsBuildSuccess");
21853
22090
  }
21854
22091
  };
@@ -21938,6 +22175,7 @@ const onClose = async (server, sys, watchers, config) => {
21938
22175
  server.close();
21939
22176
  wss.close();
21940
22177
  browserWindow === null || browserWindow === void 0 ? void 0 : browserWindow.unref();
22178
+ await (stencilWatcher === null || stencilWatcher === void 0 ? void 0 : stencilWatcher.close());
21941
22179
  for (const watcher of watchers) {
21942
22180
  if (watcher.close) {
21943
22181
  await watcher.close();
@@ -21986,6 +22224,43 @@ const getPreviewWorkspace = async (startedOra, ctx) => {
21986
22224
  }
21987
22225
  }
21988
22226
  };
22227
+ async function onWebComponentBuildFinish(e, config) {
22228
+ var _a;
22229
+ lock.unlock();
22230
+ if (!browserWindow) {
22231
+ browserWindow = await openDevWorkspacePage(config.previewBaseUrl, previewWorkspace);
22232
+ return;
22233
+ }
22234
+ await delay(50);
22235
+ if (isActiveBundleBuild) {
22236
+ return;
22237
+ }
22238
+ if (e.hasSuccessfulBuild &&
22239
+ ((_a = e.hmr) === null || _a === void 0 ? void 0 : _a.componentsUpdated) &&
22240
+ e.hmr.reloadStrategy === "hmr") {
22241
+ try {
22242
+ await waitForStableHmrFiles(e.componentGraph, config);
22243
+ }
22244
+ finally {
22245
+ sendMessage("componentsBuildSuccessHmr", e.hmr);
22246
+ }
22247
+ }
22248
+ else {
22249
+ sendMessage("componentsBuildSuccess");
22250
+ }
22251
+ }
22252
+ async function waitForStableHmrFiles(componentGraph, config) {
22253
+ const promises = [];
22254
+ for (const files of Object.values(componentGraph !== null && componentGraph !== void 0 ? componentGraph : {})) {
22255
+ for (const file of files) {
22256
+ if (file.startsWith("embeddable-component")) {
22257
+ const fullPath = path.resolve(config.client.buildDir, "dist", "embeddable-wrapper", file);
22258
+ promises.push(waitUntilFileStable(fullPath, "sourceMappingURL"));
22259
+ }
22260
+ }
22261
+ }
22262
+ await Promise.all(promises);
22263
+ }
21989
22264
 
21990
22265
  const REGION_CONFIGS = {
21991
22266
  EU: {
@@ -22530,7 +22805,7 @@ var _FileFromPath = class _FileFromPath {
22530
22805
  );
22531
22806
  }
22532
22807
  if (this.size) {
22533
- yield* createReadStream(__privateGet(this, _path), {
22808
+ yield* createReadStream$1(__privateGet(this, _path), {
22534
22809
  start: __privateGet(this, _start),
22535
22810
  end: __privateGet(this, _start) + this.size - 1
22536
22811
  });