@embeddable.com/sdk-core 4.1.12-next.1 → 4.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "4.1.12-next.1",
3
+ "version": "4.2.0-next.0",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
@@ -36,7 +36,7 @@
36
36
  "embeddable": "bin/embeddable"
37
37
  },
38
38
  "engines": {
39
- "node": ">=20.0.0"
39
+ "node": ">=20.1.0"
40
40
  },
41
41
  "license": "MIT",
42
42
  "dependencies": {
@@ -45,7 +45,7 @@
45
45
  "@inquirer/prompts": "^7.2.1",
46
46
  "@stencil/core": "^4.23.0",
47
47
  "@swc-node/register": "^1.10.9",
48
- "archiver": "^7.0.1",
48
+ "yazl": "^2.5.1",
49
49
  "axios": "^1.7.9",
50
50
  "chokidar": "^4.0.3",
51
51
  "dotenv": "^16.4.7",
@@ -66,8 +66,16 @@
66
66
  "prettier --write"
67
67
  ]
68
68
  },
69
+ "peerDependencies": {
70
+ "typescript": ">=5.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "typescript": {
74
+ "optional": true
75
+ }
76
+ },
69
77
  "devDependencies": {
70
- "@types/archiver": "^6.0.3",
78
+ "@types/yazl": "^2.4.5",
71
79
  "@types/finalhandler": "^1.2.3",
72
80
  "@types/minimist": "^1.2.5",
73
81
  "@types/serve-static": "^1.15.7",
package/src/push.test.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import push, { buildArchive } from "./push";
2
2
  import provideConfig from "./provideConfig";
3
3
  import { fileFromPath } from "formdata-node/file-from-path";
4
- import archiver from "archiver";
4
+ import * as path from "path";
5
+ import yazl from "yazl";
5
6
  import * as fs from "node:fs/promises";
6
7
  import * as fsSync from "node:fs";
7
8
  import { findFiles } from "@embeddable.com/sdk-utils";
@@ -53,6 +54,9 @@ vi.mock("node:fs/promises", () => ({
53
54
 
54
55
  vi.mock("node:fs", () => ({
55
56
  createWriteStream: vi.fn(),
57
+ readdirSync: vi.fn().mockReturnValue([]),
58
+ statSync: vi.fn().mockReturnValue({ isFile: () => true }),
59
+ existsSync: vi.fn().mockReturnValue(true),
56
60
  }));
57
61
 
58
62
  vi.mock("./provideConfig", () => ({
@@ -73,9 +77,13 @@ vi.mock("@embeddable.com/sdk-utils", () => ({
73
77
  findFiles: vi.fn(),
74
78
  }));
75
79
 
76
- vi.mock("archiver", () => ({
80
+ const { zipRef } = vi.hoisted(() => ({
81
+ zipRef: { current: null as any },
82
+ }));
83
+
84
+ vi.mock("yazl", () => ({
77
85
  default: {
78
- create: vi.fn(),
86
+ ZipFile: vi.fn(function () { return zipRef.current; }),
79
87
  },
80
88
  }));
81
89
 
@@ -96,11 +104,10 @@ const config = {
96
104
  };
97
105
 
98
106
  describe("push", () => {
99
- const archiveMock = {
100
- finalize: vi.fn(),
101
- pipe: vi.fn(),
102
- directory: vi.fn(),
103
- file: vi.fn(),
107
+ const zipMock = {
108
+ addFile: vi.fn(),
109
+ end: vi.fn(),
110
+ outputStream: { pipe: vi.fn() },
104
111
  };
105
112
 
106
113
  beforeEach(() => {
@@ -121,7 +128,9 @@ describe("push", () => {
121
128
  vi.mocked(fileFromPath).mockReturnValue(
122
129
  new Blob([new ArrayBuffer(8)]) as any
123
130
  );
124
- vi.mocked(archiver.create).mockReturnValue(archiveMock as any);
131
+ zipRef.current = zipMock;
132
+ vi.mocked(fsSync.readdirSync).mockReturnValue([]);
133
+ vi.mocked(fsSync.existsSync).mockReturnValue(true);
125
134
 
126
135
  vi.mocked(fsSync.createWriteStream).mockReturnValue({
127
136
  on: (event: string, cb: () => void) => {
@@ -142,19 +151,19 @@ describe("push", () => {
142
151
 
143
152
  expect(fs.access).toHaveBeenCalledWith(config.client.buildDir);
144
153
 
145
- expect(archiver.create).toHaveBeenCalledWith("zip", {
146
- zlib: { level: 9 },
147
- });
154
+ expect(yazl.ZipFile).toHaveBeenCalled();
148
155
  expect(fsSync.createWriteStream).toHaveBeenCalledWith(
149
156
  config.client.archiveFile
150
157
  );
151
158
 
152
- expect(archiveMock.pipe).toHaveBeenCalled();
153
- expect(archiveMock.file).toHaveBeenCalledWith("src/custom-canvas.css", {
154
- name: "global.css",
155
- });
156
- expect(archiveMock.directory).toHaveBeenCalledWith("buildDir", false);
157
- expect(archiveMock.finalize).toHaveBeenCalled();
159
+ expect(zipMock.outputStream.pipe).toHaveBeenCalled();
160
+ expect(zipMock.addFile).toHaveBeenCalledWith(
161
+ "src/custom-canvas.css", "global.css", expect.objectContaining({ compress: true })
162
+ );
163
+ expect(fsSync.readdirSync).toHaveBeenCalledWith(
164
+ "buildDir", expect.objectContaining({ recursive: true })
165
+ );
166
+ expect(zipMock.end).toHaveBeenCalled();
158
167
  // after publishing the file gets removed
159
168
  expect(fs.rm).toHaveBeenCalledWith(config.client.archiveFile);
160
169
 
@@ -230,13 +239,12 @@ describe("push", () => {
230
239
  });
231
240
 
232
241
  it("should only include component files when pushModels is false", async () => {
233
- const mockArchiver = {
234
- finalize: vi.fn(),
235
- pipe: vi.fn(),
236
- directory: vi.fn(),
237
- file: vi.fn(),
242
+ const localZipMock = {
243
+ addFile: vi.fn(),
244
+ end: vi.fn(),
245
+ outputStream: { pipe: vi.fn() },
238
246
  };
239
- vi.mocked(archiver.create).mockReturnValue(mockArchiver as any);
247
+ zipRef.current = localZipMock;
240
248
 
241
249
  vi.mocked(provideConfig).mockResolvedValue({
242
250
  ...config,
@@ -246,13 +254,14 @@ describe("push", () => {
246
254
 
247
255
  await push();
248
256
 
249
- // Should include component build directory
250
- expect(mockArchiver.directory).toHaveBeenCalled();
251
- // Should not include model files (except global.css which is part of components)
252
- expect(mockArchiver.file).toHaveBeenCalledTimes(2);
253
- expect(mockArchiver.file).toHaveBeenCalledWith(expect.anything(), {
254
- name: "global.css",
255
- });
257
+ // Should read component build directory
258
+ expect(fsSync.readdirSync).toHaveBeenCalledWith(
259
+ "buildDir", expect.objectContaining({ recursive: true })
260
+ );
261
+ // Should include global.css and client context files
262
+ expect(localZipMock.addFile).toHaveBeenCalledWith(
263
+ expect.anything(), "global.css", expect.objectContaining({ compress: true })
264
+ );
256
265
  });
257
266
  });
258
267
 
@@ -398,29 +407,24 @@ describe("push", () => {
398
407
  });
399
408
 
400
409
  describe("buildArchive", () => {
401
- type MockArchiver = {
402
- finalize: ReturnType<typeof vi.fn>;
403
- pipe: ReturnType<typeof vi.fn>;
404
- directory: ReturnType<typeof vi.fn>;
405
- file: ReturnType<typeof vi.fn>;
410
+ type MockZip = {
411
+ addFile: ReturnType<typeof vi.fn>;
412
+ end: ReturnType<typeof vi.fn>;
413
+ outputStream: { pipe: ReturnType<typeof vi.fn> };
406
414
  };
407
415
 
408
- let mockArchiver: MockArchiver;
409
- let mockOra: {
410
- start: ReturnType<typeof vi.fn>;
411
- succeed: ReturnType<typeof vi.fn>;
412
- fail: ReturnType<typeof vi.fn>;
413
- };
416
+ let mockZipLocal: MockZip;
414
417
 
415
418
  beforeEach(() => {
416
- mockArchiver = {
417
- finalize: vi.fn(),
418
- pipe: vi.fn(),
419
- directory: vi.fn(),
420
- file: vi.fn(),
419
+ mockZipLocal = {
420
+ addFile: vi.fn(),
421
+ end: vi.fn(),
422
+ outputStream: { pipe: vi.fn() },
421
423
  };
422
424
 
423
- vi.mocked(archiver.create).mockReturnValue(mockArchiver as any);
425
+ zipRef.current = mockZipLocal;
426
+ vi.mocked(fsSync.readdirSync).mockReturnValue([]);
427
+ vi.mocked(fsSync.existsSync).mockReturnValue(true);
424
428
  vi.mocked(findFiles).mockResolvedValue([]);
425
429
  });
426
430
 
@@ -447,43 +451,38 @@ describe("push", () => {
447
451
 
448
452
  await buildArchive(testConfig);
449
453
 
450
- // Should include component build directory
451
- expect(mockArchiver.directory).toHaveBeenCalledWith(
454
+ // Should read component build directory
455
+ expect(fsSync.readdirSync).toHaveBeenCalledWith(
452
456
  testConfig.client.buildDir,
453
- false
457
+ expect.objectContaining({ recursive: true })
454
458
  );
455
459
  // Should include global.css
456
- expect(mockArchiver.file).toHaveBeenCalledWith(
460
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
457
461
  testConfig.client.customCanvasCss,
458
- {
459
- name: "global.css",
460
- }
462
+ "global.css",
463
+ expect.objectContaining({ compress: true })
461
464
  );
462
465
  // Should include all model files
463
- expect(mockArchiver.file).toHaveBeenCalledWith(
466
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
464
467
  "/path/to/model1.cube.yml",
465
- {
466
- name: "model1.cube.yml",
467
- }
468
+ "model1.cube.yml",
469
+ expect.objectContaining({ compress: true })
468
470
  );
469
- expect(mockArchiver.file).toHaveBeenCalledWith(
471
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
470
472
  "/path/to/model2.cube.yaml",
471
- {
472
- name: "model2.cube.yaml",
473
- }
473
+ "model2.cube.yaml",
474
+ expect.objectContaining({ compress: true })
474
475
  );
475
476
  // Should include all preset files
476
- expect(mockArchiver.file).toHaveBeenCalledWith(
477
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
477
478
  "/path/to/context1.sc.yml",
478
- {
479
- name: "context1.sc.yml",
480
- }
479
+ "context1.sc.yml",
480
+ expect.objectContaining({ compress: true })
481
481
  );
482
- expect(mockArchiver.file).toHaveBeenCalledWith(
482
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
483
483
  "/path/to/context2.cc.yml",
484
- {
485
- name: "context2.cc.yml",
486
- }
484
+ "context2.cc.yml",
485
+ expect.objectContaining({ compress: true })
487
486
  );
488
487
  });
489
488
 
@@ -500,17 +499,16 @@ describe("push", () => {
500
499
 
501
500
  await buildArchive(testConfig);
502
501
 
503
- // Should include component build directory
504
- expect(mockArchiver.directory).toHaveBeenCalledWith(
502
+ // Should read component build directory
503
+ expect(fsSync.readdirSync).toHaveBeenCalledWith(
505
504
  testConfig.client.buildDir,
506
- false
505
+ expect.objectContaining({ recursive: true })
507
506
  );
508
507
  // Should include global.css
509
- expect(mockArchiver.file).toHaveBeenCalledWith(
508
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
510
509
  testConfig.client.customCanvasCss,
511
- {
512
- name: "global.css",
513
- }
510
+ "global.css",
511
+ expect.objectContaining({ compress: true })
514
512
  );
515
513
  // Should only find client context files
516
514
  expect(findFiles).toHaveBeenCalledOnce();
@@ -541,6 +539,82 @@ describe("push", () => {
541
539
  );
542
540
  });
543
541
 
542
+ it("should add directory files to zip with correct relative paths", async () => {
543
+ vi.mocked(fsSync.readdirSync).mockReturnValue([
544
+ "index.js",
545
+ "components/widget.js",
546
+ ] as any);
547
+ vi.mocked(fsSync.statSync).mockReturnValue({ isFile: () => true } as any);
548
+
549
+ const testConfig = {
550
+ ...config,
551
+ pushModels: false,
552
+ pushComponents: true,
553
+ client: {
554
+ ...config.client,
555
+ srcDir: "/src",
556
+ },
557
+ } as ResolvedEmbeddableConfig;
558
+
559
+ await buildArchive(testConfig);
560
+
561
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
562
+ path.join("buildDir", "index.js"),
563
+ "index.js",
564
+ expect.objectContaining({ compress: true })
565
+ );
566
+ expect(mockZipLocal.addFile).toHaveBeenCalledWith(
567
+ path.join("buildDir", "components/widget.js"),
568
+ "components/widget.js",
569
+ expect.objectContaining({ compress: true })
570
+ );
571
+ });
572
+
573
+ it("should skip directories when adding directory contents to zip", async () => {
574
+ vi.mocked(fsSync.readdirSync).mockReturnValue(["components"] as any);
575
+ vi.mocked(fsSync.statSync).mockReturnValue({ isFile: () => false } as any);
576
+
577
+ const testConfig = {
578
+ ...config,
579
+ pushModels: false,
580
+ pushComponents: true,
581
+ client: {
582
+ ...config.client,
583
+ srcDir: "/src",
584
+ },
585
+ } as ResolvedEmbeddableConfig;
586
+
587
+ await buildArchive(testConfig);
588
+
589
+ const addFileCalls = mockZipLocal.addFile.mock.calls;
590
+ const dirCall = addFileCalls.find(
591
+ (call: any) => call[1] === "components"
592
+ );
593
+ expect(dirCall).toBeUndefined();
594
+ });
595
+
596
+ it("should skip customCanvasCss when file does not exist", async () => {
597
+ vi.mocked(fsSync.existsSync).mockReturnValue(false);
598
+
599
+ const testConfig = {
600
+ ...config,
601
+ pushModels: false,
602
+ pushComponents: true,
603
+ client: {
604
+ ...config.client,
605
+ srcDir: "/src",
606
+ },
607
+ } as ResolvedEmbeddableConfig;
608
+
609
+ await buildArchive(testConfig);
610
+
611
+ const addFileCalls = mockZipLocal.addFile.mock.calls;
612
+ const cssCall = addFileCalls.find(
613
+ (call: any) => call[1] === "global.css"
614
+ );
615
+ expect(cssCall).toBeUndefined();
616
+ });
617
+
544
618
  it("should use srcDir as fallback when modelsSrc/presetsSrc are not defined", async () => {
545
619
  const testConfig = {
546
620
  ...config,
package/src/push.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as fsSync from "node:fs";
3
- import archiver from "archiver";
3
+ import yazl from "yazl";
4
4
  import axios, {AxiosResponse} from "axios";
5
5
  import ora, { Ora } from "ora";
6
6
  import { initLogger, logError } from "./logger";
@@ -231,34 +231,41 @@ export async function archive(args: {
231
231
  isDev: boolean;
232
232
  }) {
233
233
  const { ctx, filesList, isDev } = args;
234
- const output = fsSync.createWriteStream(ctx.client.archiveFile);
234
+ const zip = new yazl.ZipFile();
235
235
 
236
- const archive = archiver.create("zip", {
237
- zlib: { level: 9 },
238
- });
239
-
240
- archive.pipe(output);
241
236
  if (!isDev) {
242
- archive.directory(ctx.client.buildDir, false);
237
+ addDirectoryToZip(zip, ctx.client.buildDir);
243
238
  // NOTE: for backward compatibility, keep the file name as global.css
244
- archive.file(ctx.client.customCanvasCss, {
245
- name: "global.css",
246
- });
239
+ if (fsSync.existsSync(ctx.client.customCanvasCss)) {
240
+ zip.addFile(ctx.client.customCanvasCss, "global.css", { compress: true });
241
+ }
247
242
  }
248
243
 
249
- for (const fileData of filesList) {
250
- archive.file(fileData[1], {
251
- name: fileData[0],
252
- });
244
+ for (const [name, filePath] of filesList) {
245
+ zip.addFile(filePath, name, { compress: true });
253
246
  }
254
247
 
255
- await archive.finalize();
248
+ zip.end();
256
249
 
257
- return new Promise<void>((resolve: any, _reject) => {
258
- output.on("close", () => resolve());
250
+ return new Promise<void>((resolve, reject) => {
251
+ const output = fsSync.createWriteStream(ctx.client.archiveFile);
252
+ zip.outputStream.pipe(output);
253
+ output.on("close", resolve);
254
+ output.on("error", reject);
259
255
  });
260
256
  }
261
257
 
258
+ function addDirectoryToZip(zip: yazl.ZipFile, dir: string) {
259
+ const entries = fsSync.readdirSync(dir, { recursive: true });
260
+ for (const entry of entries) {
261
+ const relativePath = String(entry);
262
+ const fullPath = path.join(dir, relativePath);
263
+ if (fsSync.statSync(fullPath).isFile()) {
264
+ zip.addFile(fullPath, relativePath, { compress: true });
265
+ }
266
+ }
267
+ }
268
+
262
269
  export async function createFormData(
263
270
  filePath: string,
264
271
  metadata: Record<string, any>,