@embeddable.com/sdk-core 3.4.0 → 3.4.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": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
@@ -51,7 +51,7 @@
51
51
  "ora": "^8.0.1",
52
52
  "serve-static": "^1.15.0",
53
53
  "sorcery": "^0.11.0",
54
- "vite": "^5.3.1",
54
+ "vite": "^5.3.2",
55
55
  "ws": "^8.17.0",
56
56
  "yaml": "^2.3.3"
57
57
  },
@@ -0,0 +1,184 @@
1
+ import push from "./push";
2
+ import provideConfig from "./provideConfig";
3
+ import { fileFromPath } from "formdata-node/file-from-path";
4
+ import * as archiver from "archiver";
5
+ import * as fs from "node:fs/promises";
6
+ import * as fsSync from "node:fs";
7
+ import { findFiles } from "@embeddable.com/sdk-utils";
8
+
9
+ // @ts-ignore
10
+ import reportErrorToRollbar from "./rollbar.mjs";
11
+ import { checkBuildSuccess, checkNodeVersion, getArgumentByKey } from "./utils";
12
+ import { server } from "../../../mocks/server";
13
+ import { http, HttpResponse } from "msw";
14
+
15
+ const infoMock = {
16
+ info: vi.fn(),
17
+ succeed: vi.fn(),
18
+ fail: vi.fn(),
19
+ };
20
+
21
+ const startMock = {
22
+ succeed: vi.fn(),
23
+ info: () => infoMock,
24
+ fail: vi.fn(),
25
+ };
26
+
27
+ vi.mock("ora", () => ({
28
+ default: () => ({
29
+ start: vi.fn().mockReturnValue(startMock),
30
+ info: vi.fn(),
31
+ }),
32
+ }));
33
+
34
+ vi.mock("./utils", () => ({
35
+ checkNodeVersion: vi.fn(),
36
+ checkBuildSuccess: vi.fn(),
37
+ getArgumentByKey: vi.fn(),
38
+ }));
39
+
40
+ vi.mock("node:fs/promises", () => ({
41
+ writeFile: vi.fn(),
42
+ readFile: vi.fn(),
43
+ access: vi.fn(),
44
+ rm: vi.fn(),
45
+ }));
46
+
47
+ vi.mock("node:fs", () => ({
48
+ createWriteStream: vi.fn(),
49
+ }));
50
+
51
+ vi.mock("./provideConfig", () => ({
52
+ default: vi.fn(),
53
+ }));
54
+
55
+ vi.mock("./rollbar.mjs", () => ({
56
+ default: vi.fn(),
57
+ }));
58
+
59
+ vi.mock("@embeddable.com/sdk-utils", () => ({
60
+ findFiles: vi.fn(),
61
+ }));
62
+
63
+ vi.mock("archiver", () => ({
64
+ create: vi.fn(),
65
+ }));
66
+
67
+ vi.mock("formdata-node/file-from-path", () => ({
68
+ fileFromPath: vi.fn().mockReturnValue(new Blob([new ArrayBuffer(8)])),
69
+ }));
70
+
71
+ const config = {
72
+ client: {
73
+ rootDir: "rootDir",
74
+ buildDir: "buildDir",
75
+ archiveFile: "embeddable-build.zip",
76
+ },
77
+ pushBaseUrl: "pushBaseUrl",
78
+ previewBaseUrl: "previewBaseUrl",
79
+ };
80
+
81
+ describe("push", () => {
82
+ beforeEach(() => {
83
+ vi.mocked(checkNodeVersion).mockResolvedValue(true);
84
+ vi.mocked(checkBuildSuccess).mockResolvedValue(true);
85
+ vi.mocked(getArgumentByKey).mockReturnValue(undefined);
86
+ vi.mocked(provideConfig).mockResolvedValue(config);
87
+ vi.mocked(fs.access).mockResolvedValue(undefined);
88
+ vi.mocked(fs.readFile).mockImplementation(async () =>
89
+ Buffer.from(`{"access_token":"mocked-token"}`),
90
+ );
91
+
92
+ vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]);
93
+ vi.mocked(fileFromPath).mockReturnValue(
94
+ new Blob([new ArrayBuffer(8)]) as any,
95
+ );
96
+ vi.mocked(archiver.create).mockReturnValue({
97
+ finalize: vi.fn(),
98
+ pipe: vi.fn(),
99
+ directory: vi.fn(),
100
+ file: vi.fn(),
101
+ } as any);
102
+
103
+ vi.mocked(fsSync.createWriteStream).mockReturnValue({
104
+ on: (event: string, cb: () => void) => {
105
+ cb();
106
+ },
107
+ end: vi.fn(),
108
+ } as any);
109
+
110
+ vi.spyOn(process, "exit").mockImplementation(() => null as never);
111
+ });
112
+
113
+ it("should push the build", async () => {
114
+ await push();
115
+
116
+ expect(provideConfig).toHaveBeenCalled();
117
+ expect(checkNodeVersion).toHaveBeenCalled();
118
+ expect(checkBuildSuccess).toHaveBeenCalled();
119
+
120
+ expect(fs.access).toHaveBeenCalledWith(config.client.buildDir);
121
+
122
+ expect(archiver.create).toHaveBeenCalledWith("zip", {
123
+ zlib: { level: 9 },
124
+ });
125
+ expect(fsSync.createWriteStream).toHaveBeenCalledWith(
126
+ config.client.archiveFile,
127
+ );
128
+
129
+ // after publishing the file gets removed
130
+ expect(fs.rm).toHaveBeenCalledWith(config.client.archiveFile);
131
+
132
+ expect(infoMock.info).toHaveBeenCalledWith(
133
+ "Publishing to mocked-workspace-name using previewBaseUrl/workspace/mocked-workspace-id...",
134
+ );
135
+ expect(infoMock.succeed).toHaveBeenCalledWith(
136
+ "Published to mocked-workspace-name using previewBaseUrl/workspace/mocked-workspace-id",
137
+ );
138
+ });
139
+
140
+ it("should fail if there are no workspaces", async () => {
141
+ vi.spyOn(console, "error").mockImplementation(() => undefined);
142
+ server.use(
143
+ http.get("**/workspace", () => {
144
+ return HttpResponse.json([]);
145
+ }),
146
+ );
147
+
148
+ await push();
149
+
150
+ expect(startMock.fail).toHaveBeenCalledWith("No workspaces found");
151
+ expect(process.exit).toHaveBeenCalledWith(1);
152
+ expect(reportErrorToRollbar).toHaveBeenCalled();
153
+ });
154
+
155
+ it("should fail if the build is not successful", async () => {
156
+ vi.mocked(checkBuildSuccess).mockResolvedValue(false);
157
+
158
+ await push();
159
+
160
+ expect(process.exit).toHaveBeenCalledWith(1);
161
+
162
+ expect(console.error).toHaveBeenCalledWith(
163
+ "Build failed or not completed. Please run `embeddable:build` first.",
164
+ );
165
+ });
166
+
167
+ it("should push by api key provided in the arguments", async () => {
168
+ vi.mocked(getArgumentByKey).mockReturnValue("mocked-api-key");
169
+ Object.defineProperties(process, {
170
+ argv: {
171
+ value: [
172
+ "--api-key",
173
+ "mocked-api-key",
174
+ "--email",
175
+ "mocked-email@valid.com",
176
+ ],
177
+ },
178
+ });
179
+
180
+ await push();
181
+
182
+ expect(startMock.succeed).toHaveBeenCalledWith("Published using API key");
183
+ });
184
+ });
package/src/utils.test.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  checkNodeVersion,
4
4
  getArgumentByKey,
5
5
  storeBuildSuccessFlag,
6
+ checkBuildSuccess,
6
7
  SUCCESS_FLAG_FILE,
7
8
  } from "./utils";
8
9
 
@@ -96,4 +97,22 @@ describe("utils", () => {
96
97
  await expect(storeBuildSuccessFlag()).rejects.toThrow();
97
98
  });
98
99
  });
100
+
101
+ describe("checkBuildSuccess", () => {
102
+ it("should return true if the success flag file exists", async () => {
103
+ vi.mocked(fs.access).mockResolvedValue(undefined);
104
+
105
+ const result = await checkBuildSuccess();
106
+
107
+ expect(result).toBe(true);
108
+ });
109
+
110
+ it("should return false if the success flag file does not exist", async () => {
111
+ vi.mocked(fs.access).mockRejectedValue(undefined);
112
+
113
+ const result = await checkBuildSuccess();
114
+
115
+ expect(result).toBe(false);
116
+ });
117
+ });
99
118
  });
@@ -20,6 +20,15 @@ const validYaml = `cubes:
20
20
  type: number
21
21
  primary_key: true`;
22
22
 
23
+ const invalidYaml = `${validYaml}
24
+ measures:
25
+ - name: count
26
+ type: count
27
+ title: '# of customers'
28
+ - name: test
29
+ type: number
30
+ sql: {count} / 10.0`;
31
+
23
32
  const securityContextYaml = `
24
33
  - name: Example customer 1
25
34
  securityContext:
@@ -66,6 +75,25 @@ describe("validate", () => {
66
75
  "path/to/file: At least one cubes or views must be defined",
67
76
  ]);
68
77
  });
78
+
79
+ it("should return an array of error messages if the data models parsing fails", async () => {
80
+ vi.mocked(fs.readFile).mockImplementation(async () => {
81
+ return invalidYaml;
82
+ });
83
+ const filesList: [string, string][] = [
84
+ ["invalid-cube.yaml", "path/to/file"],
85
+ ];
86
+ const result = await dataModelsValidation(filesList);
87
+ expect(result).toMatchInlineSnapshot(`
88
+ [
89
+ "path/to/file: Unexpected scalar at node end at line 18, column 22:
90
+
91
+ sql: {count} / 10.0
92
+ ^^^^^^
93
+ ",
94
+ ]
95
+ `);
96
+ });
69
97
  });
70
98
 
71
99
  describe("securityContextValidation", () => {
package/src/validate.ts CHANGED
@@ -60,25 +60,28 @@ export async function dataModelsValidation(filesList: [string, string][]) {
60
60
  for (const [_, filePath] of filesList) {
61
61
  const fileContentRaw = await fs.readFile(filePath, "utf8");
62
62
 
63
- const cube = YAML.parse(fileContentRaw);
64
-
65
- if (!cube?.cubes && !cube?.views) {
66
- return [`${filePath}: At least one cubes or views must be defined`];
67
- }
63
+ try {
64
+ const cube = YAML.parse(fileContentRaw);
65
+ if (!cube?.cubes && !cube?.views) {
66
+ return [`${filePath}: At least one cubes or views must be defined`];
67
+ }
68
68
 
69
- const cubeModelSafeParse = cubeModelSchema.safeParse(cube);
70
- const viewModelSafeParse = viewModelSchema.safeParse(cube);
69
+ const cubeModelSafeParse = cubeModelSchema.safeParse(cube);
70
+ const viewModelSafeParse = viewModelSchema.safeParse(cube);
71
71
 
72
- if (cube.cubes && !cubeModelSafeParse.success) {
73
- errorFormatter(cubeModelSafeParse.error.issues).forEach((error) => {
74
- errors.push(`${filePath}: ${error}`);
75
- });
76
- }
72
+ if (cube.cubes && !cubeModelSafeParse.success) {
73
+ errorFormatter(cubeModelSafeParse.error.issues).forEach((error) => {
74
+ errors.push(`${filePath}: ${error}`);
75
+ });
76
+ }
77
77
 
78
- if (cube.views && !viewModelSafeParse.success) {
79
- errorFormatter(viewModelSafeParse.error.issues).forEach((error) => {
80
- errors.push(`${filePath}: ${error}`);
81
- });
78
+ if (cube.views && !viewModelSafeParse.success) {
79
+ errorFormatter(viewModelSafeParse.error.issues).forEach((error) => {
80
+ errors.push(`${filePath}: ${error}`);
81
+ });
82
+ }
83
+ } catch (e: any) {
84
+ errors.push(`${filePath}: ${e.message}`);
82
85
  }
83
86
  }
84
87