@embeddable.com/sdk-core 4.4.0-next.0 → 4.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "4.4.0-next.0",
3
+ "version": "4.4.0-next.1",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
@@ -109,9 +109,14 @@ export type ResolvedEmbeddableConfig = {
109
109
  buildName: string;
110
110
  componentsEntryPointFilename: string;
111
111
  };
112
+ pluginFlags?: PluginFlags;
112
113
  };
113
114
  };
114
115
 
116
+ export type PluginFlags = {
117
+ supportsOnComponentReadyHook: boolean;
118
+ }
119
+
115
120
  const REGION_CONFIGS = {
116
121
  EU: {
117
122
  pushBaseUrl: "https://api.eu.embeddable.com",
package/src/dev.test.ts CHANGED
@@ -1103,6 +1103,34 @@ describe("dev command", () => {
1103
1103
 
1104
1104
  process.argv = originalArgv;
1105
1105
  });
1106
+
1107
+ it("should include pushEmbeddables in the dev-workspace request body when true", async () => {
1108
+ vi.mocked(provideConfig).mockResolvedValue({
1109
+ ...mockConfig,
1110
+ pushEmbeddables: true,
1111
+ } as unknown as ResolvedEmbeddableConfig);
1112
+
1113
+ await dev();
1114
+
1115
+ const devWorkspaceCall = vi.mocked(axios.post).mock.calls.find(
1116
+ (call) => String(call[0]).includes("dev-workspace"),
1117
+ );
1118
+ expect(devWorkspaceCall?.[1]).toMatchObject({ pushEmbeddables: true });
1119
+ });
1120
+
1121
+ it("should include pushEmbeddables in the dev-workspace request body when false", async () => {
1122
+ vi.mocked(provideConfig).mockResolvedValue({
1123
+ ...mockConfig,
1124
+ pushEmbeddables: false,
1125
+ } as unknown as ResolvedEmbeddableConfig);
1126
+
1127
+ await dev();
1128
+
1129
+ const devWorkspaceCall = vi.mocked(axios.post).mock.calls.find(
1130
+ (call) => String(call[0]).includes("dev-workspace"),
1131
+ );
1132
+ expect(devWorkspaceCall?.[1]).toMatchObject({ pushEmbeddables: false });
1133
+ });
1106
1134
  });
1107
1135
 
1108
1136
  describe("addToGitignore", () => {
package/src/dev.ts CHANGED
@@ -661,6 +661,7 @@ const getPreviewWorkspace = async (
661
661
  instanceUrl,
662
662
  pushModels: ctx.pushModels,
663
663
  pushComponents: ctx.pushComponents,
664
+ pushEmbeddables: ctx.pushEmbeddables,
664
665
  },
665
666
  {
666
667
  headers: {
@@ -254,9 +254,9 @@ describe("generateDTS", () => {
254
254
  "embeddable-wrapper.esm.js",
255
255
  ] as any);
256
256
  vi.mocked(path.resolve).mockImplementation((...args) => args.join("/"));
257
- // Template contains both tokens so we can verify replacement
257
+ // Template contains all tokens so we can verify replacement
258
258
  vi.mocked(fs.readFile).mockResolvedValue(
259
- "replace-this-with-component-name {{RENDER_IMPORT}}",
259
+ "replace-this-with-component-name {{RENDER_IMPORT}} {{PLUGIN_FLAGS}}",
260
260
  );
261
261
  vi.mocked(loadConfig).mockResolvedValue({ config: {} } as any);
262
262
  vi.mocked(createCompiler).mockResolvedValue({
@@ -290,6 +290,19 @@ describe("generateDTS", () => {
290
290
  );
291
291
  });
292
292
 
293
+ it("should replace {{PLUGIN_FLAGS}} token with empty pluginFlags", async () => {
294
+ await generateDTS(config as unknown as ResolvedEmbeddableConfig);
295
+
296
+ expect(fs.writeFile).toHaveBeenCalledWith(
297
+ "componentDir/component.tsx",
298
+ expect.stringContaining("const pluginFlags: Partial<PluginFlags> = {}"),
299
+ );
300
+ expect(fs.writeFile).toHaveBeenCalledWith(
301
+ "componentDir/component.tsx",
302
+ expect.not.stringContaining("{{PLUGIN_FLAGS}}"),
303
+ );
304
+ });
305
+
293
306
  it("should call loadConfig with devMode=false and sourceMap=false", async () => {
294
307
  await generateDTS(config as unknown as ResolvedEmbeddableConfig);
295
308
 
@@ -333,7 +346,7 @@ describe("injectBundleRender cross-platform paths", () => {
333
346
 
334
347
  beforeEach(() => {
335
348
  vi.mocked(path.resolve).mockImplementation((...args) => args.join("/"));
336
- vi.mocked(fs.readFile).mockResolvedValue("{{RENDER_IMPORT}}");
349
+ vi.mocked(fs.readFile).mockResolvedValue("{{RENDER_IMPORT}}\n{{PLUGIN_FLAGS}}");
337
350
  });
338
351
 
339
352
  it("should use forward slashes in import when path.relative returns unix path", async () => {
@@ -363,6 +376,55 @@ describe("injectBundleRender cross-platform paths", () => {
363
376
  expect.stringContaining("import render from '../../buildDir/buildName/render.js'"),
364
377
  );
365
378
  });
379
+
380
+ it("should inject pluginFlags from config into component.tsx", async () => {
381
+ vi.mocked(path.relative).mockReturnValue("../../buildDir/buildName");
382
+ const ctxWithPluginFlags = {
383
+ ...ctxWithFileName,
384
+ "sdk-react": {
385
+ ...ctxWithFileName["sdk-react"],
386
+ pluginFlags: { supportsOnComponentReadyHook: true },
387
+ },
388
+ };
389
+
390
+ await injectBundleRender(
391
+ ctxWithPluginFlags as unknown as ResolvedEmbeddableConfig,
392
+ "sdk-react",
393
+ );
394
+
395
+ expect(fs.writeFile).toHaveBeenCalledWith(
396
+ expect.any(String),
397
+ expect.stringContaining('const pluginFlags: Partial<PluginFlags> = {"supportsOnComponentReadyHook":true}'),
398
+ );
399
+ });
400
+
401
+ it("should inject empty pluginFlags when not present in config", async () => {
402
+ vi.mocked(path.relative).mockReturnValue("../../buildDir/buildName");
403
+
404
+ await injectBundleRender(
405
+ ctxWithFileName as unknown as ResolvedEmbeddableConfig,
406
+ "sdk-react",
407
+ );
408
+
409
+ expect(fs.writeFile).toHaveBeenCalledWith(
410
+ expect.any(String),
411
+ expect.stringContaining("const pluginFlags: Partial<PluginFlags> = {}"),
412
+ );
413
+ });
414
+
415
+ it("should not leave {{PLUGIN_FLAGS}} token in output", async () => {
416
+ vi.mocked(path.relative).mockReturnValue("../../buildDir/buildName");
417
+
418
+ await injectBundleRender(
419
+ ctxWithFileName as unknown as ResolvedEmbeddableConfig,
420
+ "sdk-react",
421
+ );
422
+
423
+ expect(fs.writeFile).toHaveBeenCalledWith(
424
+ expect.any(String),
425
+ expect.not.stringContaining("{{PLUGIN_FLAGS}}"),
426
+ );
427
+ });
366
428
  });
367
429
 
368
430
  describe("injectCSS cross-platform paths", () => {
package/src/generate.ts CHANGED
@@ -18,6 +18,7 @@ import type { Logger } from "@stencil/core/internal";
18
18
 
19
19
  const STYLE_IMPORTS_TOKEN = "{{STYLES_IMPORT}}";
20
20
  const RENDER_IMPORT_TOKEN = "{{RENDER_IMPORT}}";
21
+ const PLUGIN_FLAGS_TOKEN = "{{PLUGIN_FLAGS}}";
21
22
 
22
23
  // stencil doesn't support dynamic component tag name, so we need to replace it manually
23
24
  const COMPONENT_TAG_TOKEN = "replace-this-with-component-name";
@@ -166,6 +167,8 @@ export async function injectBundleRender(
166
167
  )
167
168
  .replaceAll("\\", "/");
168
169
  const importStr = `import render from '${importFilePath}/${ctx[pluginName].outputOptions.fileName}';`;
170
+ const pluginFlags = ctx[pluginName].pluginFlags ?? {};
171
+ const pluginFlagsStr = `const pluginFlags: Partial<PluginFlags> = ${JSON.stringify(pluginFlags)}`;
169
172
 
170
173
  let content = await fs.readFile(
171
174
  path.resolve(ctx.core.templatesDir, "component.tsx.template"),
@@ -178,7 +181,7 @@ export async function injectBundleRender(
178
181
 
179
182
  await fs.writeFile(
180
183
  path.resolve(ctx.client.componentDir, "component.tsx"),
181
- content.replace(RENDER_IMPORT_TOKEN, importStr),
184
+ content.replace(RENDER_IMPORT_TOKEN, importStr).replace(PLUGIN_FLAGS_TOKEN, pluginFlagsStr),
182
185
  );
183
186
  }
184
187
 
@@ -199,7 +202,7 @@ async function injectBundleRenderStub(
199
202
  content = content.replace(COMPONENT_TAG_TOKEN, "embeddable-component");
200
203
  await fs.writeFile(
201
204
  path.resolve(ctx.client.componentDir, "component.tsx"),
202
- content.replace(RENDER_IMPORT_TOKEN, stubStr),
205
+ content.replace(RENDER_IMPORT_TOKEN, stubStr).replace(PLUGIN_FLAGS_TOKEN, "const pluginFlags: Partial<PluginFlags> = {}"),
203
206
  );
204
207
  }
205
208
 
package/src/index.ts CHANGED
@@ -4,4 +4,4 @@ export { default as push } from "./push";
4
4
  export { default as dev } from "./dev";
5
5
  export { default as defineConfig } from "./defineConfig";
6
6
  export { default as buildPackage } from "./buildPackage";
7
- export type { ResolvedEmbeddableConfig } from "./defineConfig";
7
+ export type { ResolvedEmbeddableConfig, PluginFlags } from "./defineConfig";
package/src/push.test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import push, { buildArchive, EMBEDDABLE_FILES } from "./push";
1
+ import push, { buildArchive, EMBEDDABLE_FILES, sendBuild, sendBuildByApiKey } from "./push";
2
2
  import provideConfig from "./provideConfig";
3
3
  import { fileFromPath } from "formdata-node/file-from-path";
4
4
  import * as path from "path";
@@ -239,7 +239,6 @@ describe("push", () => {
239
239
  expect(process.exit).toHaveBeenCalledWith(1);
240
240
  });
241
241
 
242
-
243
242
  it("should only include component files when pushModels is false", async () => {
244
243
  const localZipMock = {
245
244
  addFile: vi.fn(),
@@ -747,4 +746,52 @@ describe("push", () => {
747
746
  expect(EMBEDDABLE_FILES.test("my-dashboard.embeddable.ts")).toBe(false);
748
747
  });
749
748
  });
749
+
750
+ describe.each([
751
+ {
752
+ label: "sendBuild",
753
+ endpoint: "**/bundle/:workspaceId/upload",
754
+ invoke: (testConfig: ResolvedEmbeddableConfig) =>
755
+ sendBuild(testConfig, { workspaceId: "test-workspace", token: "test-token" }),
756
+ },
757
+ {
758
+ label: "sendBuildByApiKey",
759
+ endpoint: "**/bundle/upload",
760
+ invoke: (testConfig: ResolvedEmbeddableConfig) =>
761
+ sendBuildByApiKey(testConfig, { apiKey: "test-api-key", email: "test@example.com" }),
762
+ },
763
+ ])("$label", ({ endpoint, invoke }) => {
764
+ async function captureMetadata(pushEmbeddables: boolean) {
765
+ let capturedMetadata: Record<string, any> | undefined;
766
+
767
+ server.use(
768
+ http.post(endpoint, async ({ request }) => {
769
+ const formData = await request.formData();
770
+ const requestBlob = formData.get("request") as Blob;
771
+ capturedMetadata = JSON.parse(await requestBlob.text());
772
+ return HttpResponse.json({ bundleId: "mocked-bundle-id" });
773
+ }),
774
+ );
775
+
776
+ const testConfig = {
777
+ ...config,
778
+ pushEmbeddables,
779
+ region: "us",
780
+ starterEmbeddables: {},
781
+ } as unknown as ResolvedEmbeddableConfig;
782
+
783
+ await invoke(testConfig);
784
+ return capturedMetadata;
785
+ }
786
+
787
+ it("should include pushEmbeddables in form data metadata when true", async () => {
788
+ const metadata = await captureMetadata(true);
789
+ expect(metadata?.pushEmbeddables).toBe(true);
790
+ });
791
+
792
+ it("should include pushEmbeddables in form data metadata when false", async () => {
793
+ const metadata = await captureMetadata(false);
794
+ expect(metadata?.pushEmbeddables).toBe(false);
795
+ });
796
+ });
750
797
  });
package/src/push.ts CHANGED
@@ -313,6 +313,7 @@ export async function sendBuildByApiKey(
313
313
  const form = await createFormData(ctx.client.archiveFile, {
314
314
  pushModels: ctx.pushModels,
315
315
  pushComponents: ctx.pushComponents,
316
+ pushEmbeddables: ctx.pushEmbeddables,
316
317
  starterEmbeddableIds: ctx.starterEmbeddables?.[ctx.region],
317
318
  authorEmail: email,
318
319
  description: message,
@@ -347,6 +348,7 @@ export async function sendBuild(
347
348
  const form = await createFormData(ctx.client.archiveFile, {
348
349
  pushModels: ctx.pushModels,
349
350
  pushComponents: ctx.pushComponents,
351
+ pushEmbeddables: ctx.pushEmbeddables,
350
352
  starterEmbeddableIds: ctx.starterEmbeddables?.[ctx.region],
351
353
  authorEmail: "",
352
354
  description: message,
@@ -1,5 +1,7 @@
1
1
  import { Component, Watch, Host, Prop, Event, EventEmitter, h, Listen } from '@stencil/core';
2
+ import type { PluginFlags } from '@embeddable.com/sdk-core';
2
3
  {{RENDER_IMPORT}}
4
+ {{PLUGIN_FLAGS}}
3
5
 
4
6
  @Component({
5
7
  tag: 'replace-this-with-component-name',
@@ -40,11 +42,13 @@ export class EmbeddableComponent {
40
42
 
41
43
  componentDidLoad() {
42
44
  this.handlePops();
43
- const props = JSON.parse(this.props);
44
- this.componentDidLoadEvent.emit({
45
+ if (!pluginFlags.supportsOnComponentReadyHook) {
46
+ const props = JSON.parse(this.props);
47
+ this.componentDidLoadEvent.emit({
45
48
  componentId: props.componentId,
46
49
  componentName: this.componentName
47
- })
50
+ })
51
+ }
48
52
  }
49
53
 
50
54
  disconnectedCallback() {
@@ -113,7 +117,12 @@ export class EmbeddableComponent {
113
117
  this.componentName,
114
118
  props,
115
119
  JSON.parse(this.clientContext),
116
- this.theme
120
+ this.theme,
121
+ () =>
122
+ this.componentDidLoadEvent.emit({
123
+ componentId: props.componentId,
124
+ componentName: this.componentName
125
+ })
117
126
  );
118
127
  }
119
128
  }