@embeddable.com/sdk-core 3.3.1 → 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/lib/login.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  declare const _default: () => Promise<void>;
2
2
  export default _default;
3
3
  export declare function getToken(): Promise<any>;
4
+ export declare function resolveFiles(): Promise<void>;
package/lib/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const checkNodeVersion: () => Promise<void>;
1
+ export declare const checkNodeVersion: () => Promise<boolean>;
2
2
  /**
3
3
  * Get the value of a process argument by key
4
4
  * Example: getArgumentByKey("--email") or getArgumentByKey(["--email", "-e"])
@@ -6,6 +6,7 @@ export declare const checkNodeVersion: () => Promise<void>;
6
6
  * @returns
7
7
  */
8
8
  export declare const getArgumentByKey: (key: string | string[]) => string | undefined;
9
+ export declare const SUCCESS_FLAG_FILE: string;
9
10
  /**
10
11
  * Store a flag in the credentials directory to indicate a successful build
11
12
  * This is used to determine if the build was successful or not
package/lib/validate.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  declare const _default: (ctx: any, exitIfInvalid?: boolean) => Promise<boolean>;
2
2
  export default _default;
3
3
  export declare function dataModelsValidation(filesList: [string, string][]): Promise<string[]>;
4
+ export declare function securityContextValidation(filesList: [string, string][]): Promise<string[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "3.3.1",
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",
@@ -16,7 +16,9 @@
16
16
  "directory": "packages/core-sdk"
17
17
  },
18
18
  "scripts": {
19
- "build": "rollup -c"
19
+ "build": "rollup -c",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest"
20
22
  },
21
23
  "author": "Embeddable.com <engineering@embeddable.com>",
22
24
  "files": [
@@ -49,7 +51,7 @@
49
51
  "ora": "^8.0.1",
50
52
  "serve-static": "^1.15.0",
51
53
  "sorcery": "^0.11.0",
52
- "vite": "^4.5.0",
54
+ "vite": "^5.3.2",
53
55
  "ws": "^8.17.0",
54
56
  "yaml": "^2.3.3"
55
57
  },
@@ -0,0 +1,96 @@
1
+ import build from "./build";
2
+ import prepare from "./prepare";
3
+ import validate from "./validate";
4
+ import buildTypes from "./buildTypes";
5
+ import provideConfig from "./provideConfig";
6
+ import generate from "./generate";
7
+ import cleanup from "./cleanup";
8
+
9
+ // @ts-ignore
10
+ import reportErrorToRollbar from "./rollbar.mjs";
11
+ import { storeBuildSuccessFlag } from "./utils";
12
+
13
+ const mockPlugin = {
14
+ validate: vi.fn(),
15
+ build: vi.fn(),
16
+ cleanup: vi.fn(),
17
+ };
18
+
19
+ vi.mock("./provideConfig", () => ({
20
+ default: vi.fn(),
21
+ }));
22
+
23
+ vi.mock("./buildTypes", () => ({
24
+ default: vi.fn(),
25
+ }));
26
+
27
+ vi.mock("./rollbar.mjs", () => ({
28
+ default: vi.fn(),
29
+ }));
30
+
31
+ vi.mock("./validate", () => ({
32
+ default: vi.fn(),
33
+ }));
34
+
35
+ vi.mock("./prepare", () => ({
36
+ default: vi.fn(),
37
+ }));
38
+
39
+ vi.mock("./generate", () => ({
40
+ default: vi.fn(),
41
+ }));
42
+
43
+ vi.mock("./cleanup", () => ({
44
+ default: vi.fn(),
45
+ }));
46
+
47
+ vi.mock("./utils", async () => {
48
+ const actual = await vi.importActual("./utils");
49
+ return {
50
+ ...actual,
51
+ storeBuildSuccessFlag: vi.fn(),
52
+ };
53
+ });
54
+
55
+ const config = {
56
+ plugins: [() => mockPlugin],
57
+ };
58
+
59
+ describe("build", () => {
60
+ beforeEach(() => {
61
+ vi.mocked(provideConfig).mockResolvedValue(config);
62
+ });
63
+
64
+ it("should call all the necessary functions", async () => {
65
+ await build();
66
+
67
+ expect(validate).toHaveBeenCalledWith(config);
68
+ expect(prepare).toHaveBeenCalledWith(config);
69
+ expect(buildTypes).toHaveBeenCalledWith(config);
70
+
71
+ // Plugin
72
+ expect(mockPlugin.validate).toHaveBeenCalledWith(config);
73
+ expect(mockPlugin.build).toHaveBeenCalledWith(config);
74
+ expect(mockPlugin.cleanup).toHaveBeenCalledWith(config);
75
+
76
+ expect(generate).toHaveBeenCalledWith(config, "sdk-react");
77
+
78
+ expect(cleanup).toHaveBeenCalledWith(config);
79
+ expect(storeBuildSuccessFlag).toHaveBeenCalled();
80
+ });
81
+
82
+ it("should call reportErrorToRollbar and exit if an error occurs", async () => {
83
+ vi.spyOn(process, "exit").mockImplementation(() => null as never);
84
+ vi.spyOn(console, "log").mockImplementation(() => undefined);
85
+ const error = new Error("test error");
86
+
87
+ vi.mocked(validate).mockRejectedValue(error);
88
+ vi.mocked(reportErrorToRollbar).mockResolvedValue(undefined);
89
+
90
+ await build();
91
+
92
+ expect(reportErrorToRollbar).toHaveBeenCalledWith(error);
93
+ expect(console.log).toHaveBeenCalledWith(error);
94
+ expect(process.exit).toHaveBeenCalledWith(1);
95
+ });
96
+ });
@@ -0,0 +1,98 @@
1
+ import * as fs from "node:fs/promises";
2
+ import buildTypes, { EMB_TYPE_FILE_REGEX } from "./buildTypes";
3
+ import * as path from "node:path";
4
+ import { build } from "vite";
5
+ import { findFiles, getContentHash } from "@embeddable.com/sdk-utils";
6
+
7
+ const config = {
8
+ client: {
9
+ srcDir: "src",
10
+ rootDir: "root",
11
+ buildDir: "build",
12
+ },
13
+ outputOptions: {
14
+ typesEntryPointFilename: "typesEntryPointFilename",
15
+ },
16
+ };
17
+
18
+ const startMock = {
19
+ succeed: vi.fn(),
20
+ fail: vi.fn(),
21
+ };
22
+
23
+ vi.mock("ora", () => ({
24
+ default: () => ({
25
+ start: vi.fn().mockImplementation(() => startMock),
26
+ info: vi.fn(),
27
+ fail: vi.fn(),
28
+ }),
29
+ }));
30
+
31
+ vi.mock("@embeddable.com/sdk-utils", () => ({
32
+ findFiles: vi.fn(),
33
+ getContentHash: vi.fn(),
34
+ }));
35
+
36
+ vi.mock("node:path", () => ({
37
+ resolve: vi.fn(),
38
+ relative: vi.fn(),
39
+ }));
40
+
41
+ vi.mock("node:fs/promises", () => ({
42
+ writeFile: vi.fn(),
43
+ rm: vi.fn(),
44
+ readFile: vi.fn(),
45
+ rename: vi.fn(),
46
+ }));
47
+
48
+ vi.mock("vite", () => ({
49
+ build: vi.fn(),
50
+ }));
51
+
52
+ describe("buildTypes", () => {
53
+ beforeEach(() => {
54
+ vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]);
55
+ vi.mocked(path.relative).mockReturnValue("relativePath");
56
+ vi.mocked(path.resolve).mockReturnValue("resolvedPath");
57
+ vi.mocked(fs.readFile).mockResolvedValue("fileContent");
58
+ });
59
+
60
+ it("should build types", async () => {
61
+ await buildTypes(config);
62
+
63
+ expect(findFiles).toHaveBeenCalledWith("src", EMB_TYPE_FILE_REGEX);
64
+
65
+ expect(build).toHaveBeenCalledWith({
66
+ build: {
67
+ emptyOutDir: false,
68
+ lib: {
69
+ entry: "resolvedPath",
70
+ fileName: "embeddable-types",
71
+ formats: ["es"],
72
+ },
73
+ outDir: "build",
74
+ },
75
+ logLevel: "error",
76
+ });
77
+
78
+ expect(fs.readFile).toHaveBeenCalledWith("resolvedPath", "utf8");
79
+ expect(getContentHash).toHaveBeenCalledWith("fileContent");
80
+
81
+ expect(startMock.succeed).toHaveBeenCalledWith("Types built completed");
82
+
83
+ expect(fs.writeFile).toHaveBeenCalledWith(
84
+ "resolvedPath",
85
+ `import '../relativePath';
86
+ import '../relativePath';`,
87
+ );
88
+
89
+ expect(fs.rm).toHaveBeenCalledWith("resolvedPath");
90
+ });
91
+
92
+ it("should not add hash to the file name when watch is enabled", async () => {
93
+ await buildTypes({ ...config, dev: { watch: true } });
94
+
95
+ expect(getContentHash).not.toHaveBeenCalled();
96
+ expect(fs.rename).not.toHaveBeenCalled();
97
+ });
98
+ });
package/src/buildTypes.ts CHANGED
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import * as vite from "vite";
4
4
  const oraP = import("ora");
5
- import { findFiles } from "@embeddable.com/sdk-utils";
5
+ import { findFiles, getContentHash } from "@embeddable.com/sdk-utils";
6
6
 
7
7
  export const EMB_TYPE_FILE_REGEX = /^(.*)\.type\.emb\.[jt]s$/;
8
8
  export const EMB_OPTIONS_FILE_REGEX = /^(.*)\.options\.emb\.[jt]s$/;
@@ -48,28 +48,36 @@ async function generate(ctx: any) {
48
48
  }
49
49
 
50
50
  async function build(ctx: any) {
51
+ const typesFilePath = path.resolve(
52
+ ctx.client.buildDir,
53
+ ctx.outputOptions.typesEntryPointFilename,
54
+ );
55
+
51
56
  await vite.build({
52
57
  logLevel: "error",
53
58
  build: {
54
59
  emptyOutDir: false,
55
60
  lib: {
56
- entry: path.resolve(
57
- ctx.client.buildDir,
58
- ctx.outputOptions.typesEntryPointFilename,
59
- ),
61
+ entry: typesFilePath,
60
62
  formats: ["es"],
61
63
  fileName: "embeddable-types",
62
64
  },
63
- rollupOptions: ctx.dev?.watch
64
- ? undefined
65
- : {
66
- output: {
67
- entryFileNames: "embeddable-types-[hash].js",
68
- },
69
- },
70
65
  outDir: ctx.client.buildDir,
71
66
  },
72
67
  });
68
+
69
+ if (!ctx.dev?.watch) {
70
+ const fileContent = await fs.readFile(typesFilePath, "utf8");
71
+
72
+ const fileHash = getContentHash(fileContent);
73
+
74
+ const fileName = `embeddable-types-${fileHash}.js`;
75
+
76
+ await fs.rename(
77
+ path.resolve(ctx.client.buildDir, "embeddable-types.js"),
78
+ path.resolve(ctx.client.buildDir, fileName),
79
+ );
80
+ }
73
81
  }
74
82
 
75
83
  async function cleanup(ctx: any) {
@@ -0,0 +1,85 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { createManifest } from "./cleanup";
4
+ import { findFiles } from "@embeddable.com/sdk-utils";
5
+
6
+ const ctx = {
7
+ client: {
8
+ tmpDir: "tmpDir",
9
+ stencilBuild: "stencilBuild",
10
+ buildDir: "buildDir",
11
+ },
12
+ };
13
+
14
+ vi.mock("node:fs/promises", () => ({
15
+ writeFile: vi.fn(),
16
+ rename: vi.fn(),
17
+ }));
18
+
19
+ vi.mock("@embeddable.com/sdk-utils", () => ({
20
+ findFiles: vi.fn(),
21
+ }));
22
+
23
+ vi.mock("node:path", async () => {
24
+ const actual = await vi.importActual("node:path");
25
+ return {
26
+ ...actual,
27
+ resolve: vi.fn(),
28
+ basename: vi.fn(),
29
+ join: vi.fn(),
30
+ };
31
+ });
32
+
33
+ describe("cleanup", () => {
34
+ describe("createManifest", () => {
35
+ beforeEach(() => {
36
+ vi.mocked(fs.writeFile).mockImplementation(async () => undefined);
37
+ vi.mocked(fs.rename).mockImplementation(async () => undefined);
38
+ vi.mocked(findFiles).mockResolvedValue([["", ""]]);
39
+ vi.mocked(path.basename).mockReturnValue("basename");
40
+ vi.mocked(path.join).mockImplementation((...args) => args.join("/"));
41
+
42
+ Object.defineProperties(process, {
43
+ platform: {
44
+ value: "darwin",
45
+ },
46
+ version: {
47
+ value: "v18.20.2",
48
+ },
49
+ env: {
50
+ value: {
51
+ npm_config_user_agent: "npm/10.7.0",
52
+ },
53
+ },
54
+ });
55
+ });
56
+ it("should create manifest", async () => {
57
+ await createManifest({
58
+ ctx,
59
+ typesFileName: "typesFileName",
60
+ metaFileName: "metaFileName",
61
+ editorsMetaFileName: "editorsMetaFileName",
62
+ stencilWrapperFileName: "stencilWrapperFileName",
63
+ });
64
+
65
+ expect(fs.writeFile).toHaveBeenCalledWith(
66
+ "tmpDir/embeddable-manifest.json",
67
+ JSON.stringify({
68
+ entryFiles: {
69
+ "embeddable-types.js": "typesFileName",
70
+ "embeddable-components-meta.js": "metaFileName",
71
+ "embeddable-editors-meta.js": "editorsMetaFileName",
72
+ "embeddable-wrapper.esm.js": "stencilWrapperFileName",
73
+ },
74
+ metadata: {
75
+ nodeVersion: "v18.20.2",
76
+ platform: "darwin",
77
+ sdkVersions: {},
78
+ packageManager: "npm",
79
+ packageManagerVersion: "10.7.0",
80
+ },
81
+ }),
82
+ );
83
+ });
84
+ });
85
+ });
package/src/cleanup.ts CHANGED
@@ -19,7 +19,7 @@ type ManifestArgs = {
19
19
  stencilWrapperFileName: string;
20
20
  };
21
21
 
22
- async function createManifest({
22
+ export async function createManifest({
23
23
  ctx,
24
24
  typesFileName,
25
25
  metaFileName,
@@ -81,36 +81,44 @@ async function createManifest({
81
81
  }
82
82
 
83
83
  async function extractBuild(ctx: any) {
84
- const [[, stencilWrapperFilePath]] = await findFiles(
84
+ const stencilBuildFiles = await findFiles(
85
85
  ctx.client.stencilBuild,
86
86
  /embeddable-wrapper.esm-[a-z0-9]+\.js/,
87
87
  );
88
88
 
89
+ const [[, stencilWrapperFilePath]] = stencilBuildFiles || [];
90
+
89
91
  const stencilWrapperFileName = path.basename(stencilWrapperFilePath);
90
92
  await fs.rename(
91
93
  path.resolve(ctx.client.buildDir, ctx.client.stencilBuild),
92
94
  ctx.client.tmpDir,
93
95
  );
94
96
 
95
- const [[, typesFilePath]] = await findFiles(
97
+ const typesBuildFiles = await findFiles(
96
98
  ctx.client.buildDir,
97
99
  /embeddable-types-[a-z0-9]+\.js/,
98
100
  );
99
101
 
102
+ const [[, typesFilePath]] = typesBuildFiles || [];
103
+
100
104
  const typesFileName = path.basename(typesFilePath);
101
105
  await fs.rename(typesFilePath, path.join(ctx.client.tmpDir, typesFileName));
102
106
 
103
- const [[, metaFilePath]] = await findFiles(
107
+ const metaBuildFiles = await findFiles(
104
108
  ctx.client.buildDir,
105
109
  /embeddable-components-meta-[a-z0-9]+\.js/,
106
110
  );
111
+
112
+ const [[, metaFilePath]] = metaBuildFiles || [];
107
113
  const metaFileName = path.basename(metaFilePath);
108
114
  await fs.rename(metaFilePath, path.join(ctx.client.tmpDir, metaFileName));
109
115
 
110
- const [[, editorsMetaFilePath]] = await findFiles(
116
+ const editorsMetaBuildFiles = await findFiles(
111
117
  ctx.client.buildDir,
112
118
  /embeddable-editors-meta-[a-z0-9]+\.js/,
113
119
  );
120
+
121
+ const [[, editorsMetaFilePath]] = editorsMetaBuildFiles || [];
114
122
  const editorsMetaFileName = path.basename(editorsMetaFilePath);
115
123
  await fs.rename(
116
124
  editorsMetaFilePath,
@@ -0,0 +1,121 @@
1
+ import { server } from "./../../../mocks/server";
2
+ import login, { resolveFiles, getToken } from "./login";
3
+ import { vi } from "vitest";
4
+ import { pathToFileURL } from "node:url";
5
+ import { existsSync } from "node:fs";
6
+ import { mkdir, access, writeFile, readFile } from "fs/promises";
7
+ import { CREDENTIALS_DIR, CREDENTIALS_FILE } from "./credentials";
8
+ import { http, HttpResponse } from "msw";
9
+ import { AxiosError } from "axios";
10
+
11
+ vi.mock("fs/promises", () => ({
12
+ readFile: vi.fn(),
13
+ writeFile: vi.fn(),
14
+ access: vi.fn(),
15
+ mkdir: vi.fn(),
16
+ }));
17
+
18
+ const startMock = {
19
+ succeed: vi.fn(),
20
+ fail: vi.fn(),
21
+ };
22
+
23
+ vi.mock("ora", () => ({
24
+ default: () => ({
25
+ start: vi.fn().mockImplementation(() => startMock),
26
+ info: vi.fn(),
27
+ }),
28
+ }));
29
+
30
+ vi.mock("./rollbar.mjs", () => ({
31
+ default: vi.fn(),
32
+ }));
33
+
34
+ vi.mock("open", () => ({
35
+ default: vi.fn(),
36
+ }));
37
+
38
+ vi.mock("node:fs", () => ({
39
+ existsSync: vi.fn(),
40
+ }));
41
+
42
+ vi.mock("node:url", () => ({
43
+ pathToFileURL: vi.fn(),
44
+ }));
45
+
46
+ vi.mock(`${process.cwd()}/embeddable.config.ts`, () => ({
47
+ default: {
48
+ authDomain: "test-domain.com",
49
+ },
50
+ }));
51
+
52
+ describe("login", () => {
53
+ beforeEach(async () => {
54
+ vi.mocked(readFile).mockImplementation(async () =>
55
+ Buffer.from(`{"access_token":"mocked-token"}`),
56
+ );
57
+
58
+ vi.mocked(access).mockImplementation(async () => {
59
+ throw new Error();
60
+ });
61
+
62
+ vi.mocked(mkdir).mockImplementation(async () => "mocked");
63
+
64
+ vi.mocked(writeFile).mockImplementation(async () => undefined);
65
+
66
+ vi.mocked(existsSync).mockImplementation(() => true);
67
+ vi.mocked(pathToFileURL).mockImplementation(
68
+ () => new URL("mocked-config-path"),
69
+ );
70
+ });
71
+
72
+ vi.mock("./reportErrorToRollbar", () => vi.fn());
73
+ vi.mock("./sleep", () => vi.fn());
74
+
75
+ it("should resolve files", async () => {
76
+ await resolveFiles();
77
+
78
+ expect(access).toHaveBeenCalledWith(CREDENTIALS_DIR);
79
+ expect(mkdir).toHaveBeenCalledWith(CREDENTIALS_DIR);
80
+ expect(writeFile).toHaveBeenCalledWith(CREDENTIALS_FILE, "");
81
+ });
82
+
83
+ it("should get token", async () => {
84
+ const token = await getToken();
85
+
86
+ expect(token).toBe("mocked-token");
87
+ });
88
+
89
+ it("should login by saving token in the credentials file", async () => {
90
+ await login();
91
+ expect(startMock.succeed).toHaveBeenCalledWith(
92
+ "You are successfully authenticated now!",
93
+ );
94
+
95
+ expect(writeFile).toHaveBeenCalledWith(
96
+ CREDENTIALS_FILE,
97
+ JSON.stringify({ access_token: "mocked-token " }),
98
+ );
99
+ });
100
+
101
+ it("should fail to login when the response returns 500 error", async () => {
102
+ vi.spyOn(console, "log").mockImplementation(() => undefined);
103
+ vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
104
+
105
+ server.use(
106
+ http.post("**/oauth/device/code", () => {
107
+ return new HttpResponse(null, { status: 500 });
108
+ }),
109
+ );
110
+
111
+ await login();
112
+
113
+ expect(startMock.fail).toHaveBeenCalledWith(
114
+ "Authentication failed. Please try again.",
115
+ );
116
+
117
+ expect(console.log).toHaveBeenCalledWith(
118
+ new AxiosError("Request failed with status code 500"),
119
+ );
120
+ });
121
+ });
package/src/login.ts CHANGED
@@ -94,7 +94,7 @@ function sleep(ms: number) {
94
94
  return new Promise((res) => setTimeout(res, ms));
95
95
  }
96
96
 
97
- async function resolveFiles() {
97
+ export async function resolveFiles() {
98
98
  try {
99
99
  await fs.access(CREDENTIALS_DIR);
100
100
  } catch (_e) {
@@ -0,0 +1,32 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { existsSync } from "node:fs";
3
+ import provideConfig from "./provideConfig";
4
+ import { vi } from "vitest";
5
+
6
+ vi.mock("node:fs", () => ({
7
+ existsSync: vi.fn(),
8
+ }));
9
+
10
+ vi.mock("node:url", () => ({
11
+ pathToFileURL: vi.fn(),
12
+ }));
13
+
14
+ vi.mock(`${process.cwd()}/embeddable.config.ts`, () => ({
15
+ default: "mocked-config",
16
+ }));
17
+
18
+ describe("provideConfig", () => {
19
+ beforeEach(() => {
20
+ vi.mocked(existsSync).mockImplementation(() => true);
21
+ vi.mocked(pathToFileURL).mockImplementation(
22
+ () => new URL("mocked-config-path"),
23
+ );
24
+ });
25
+
26
+ it("should return the default config when the config file exists", async () => {
27
+ const result = await provideConfig();
28
+
29
+ // Assert the result
30
+ expect(result).toEqual("mocked-config");
31
+ });
32
+ });