@embeddable.com/sdk-core 3.3.0 → 3.4.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/bin/embeddable +5 -1
- package/lib/cleanup.d.ts +8 -0
- package/lib/index.esm.js +302 -146
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +300 -144
- package/lib/index.js.map +1 -1
- package/lib/login.d.ts +1 -0
- package/lib/utils.d.ts +3 -1
- package/lib/validate.d.ts +1 -0
- package/package.json +5 -3
- package/src/build.test.ts +96 -0
- package/src/buildTypes.test.ts +98 -0
- package/src/buildTypes.ts +20 -12
- package/src/cleanup.test.ts +85 -0
- package/src/cleanup.ts +89 -18
- package/src/login.test.ts +121 -0
- package/src/login.ts +1 -1
- package/src/provideConfig.test.ts +32 -0
- package/src/push.ts +5 -2
- package/src/utils.test.ts +99 -0
- package/src/utils.ts +27 -6
- package/src/validate.test.ts +96 -0
- package/src/validate.ts +2 -15
|
@@ -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
|
+
});
|
package/src/push.ts
CHANGED
|
@@ -43,6 +43,9 @@ export default async () => {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const token = await verify(config);
|
|
46
|
+
spinnerPushing = ora()
|
|
47
|
+
.start()
|
|
48
|
+
.info("No API Key provided. Standard login will be used.");
|
|
46
49
|
|
|
47
50
|
const { workspaceId, name: workspaceName } = await selectWorkspace(
|
|
48
51
|
config,
|
|
@@ -52,9 +55,9 @@ export default async () => {
|
|
|
52
55
|
const workspacePreviewUrl = `${config.previewBaseUrl}/workspace/${workspaceId}`;
|
|
53
56
|
|
|
54
57
|
await buildArchive(config);
|
|
55
|
-
spinnerPushing
|
|
58
|
+
spinnerPushing.info(
|
|
56
59
|
`Publishing to ${workspaceName} using ${workspacePreviewUrl}...`,
|
|
57
|
-
)
|
|
60
|
+
);
|
|
58
61
|
|
|
59
62
|
await sendBuild(config, { workspaceId, token });
|
|
60
63
|
spinnerPushing.succeed(
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import {
|
|
3
|
+
checkNodeVersion,
|
|
4
|
+
getArgumentByKey,
|
|
5
|
+
storeBuildSuccessFlag,
|
|
6
|
+
SUCCESS_FLAG_FILE,
|
|
7
|
+
} from "./utils";
|
|
8
|
+
|
|
9
|
+
const startMock = {
|
|
10
|
+
succeed: vi.fn(),
|
|
11
|
+
fail: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const failMock = vi.fn();
|
|
15
|
+
|
|
16
|
+
vi.mock("ora", () => ({
|
|
17
|
+
default: () => ({
|
|
18
|
+
start: vi.fn().mockImplementation(() => startMock),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
fail: failMock,
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("../package.json", () => ({
|
|
25
|
+
engines: {
|
|
26
|
+
node: "14.x",
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("fs/promises", () => ({
|
|
31
|
+
readFile: vi.fn(),
|
|
32
|
+
writeFile: vi.fn(),
|
|
33
|
+
access: vi.fn(),
|
|
34
|
+
mkdir: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
describe("utils", () => {
|
|
38
|
+
describe("checkNodeVersion", () => {
|
|
39
|
+
it("should return true if the node version is greater than 14", async () => {
|
|
40
|
+
const result = await checkNodeVersion();
|
|
41
|
+
|
|
42
|
+
expect(result).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should fail if the node version is less than 16", async () => {
|
|
46
|
+
Object.defineProperty(process.versions, "node", {
|
|
47
|
+
value: "14.0",
|
|
48
|
+
configurable: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
vi.mock("../package.json", () => ({
|
|
52
|
+
engines: {
|
|
53
|
+
node: "16.x",
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
vi.spyOn(process, "exit").mockImplementation(() => null as never);
|
|
58
|
+
|
|
59
|
+
const result = await checkNodeVersion();
|
|
60
|
+
|
|
61
|
+
expect(failMock).toHaveBeenCalledWith({
|
|
62
|
+
text: "Node version 16.0 or higher is required. You are running 14.0.",
|
|
63
|
+
color: "red",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(result).toBe(undefined);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("getArgumentByKey", () => {
|
|
71
|
+
it("should return the value of the argument", () => {
|
|
72
|
+
process.argv = ["--email", "test@a.com", "--password", "123456"];
|
|
73
|
+
const result = getArgumentByKey("--email");
|
|
74
|
+
|
|
75
|
+
expect(result).toBe("test@a.com");
|
|
76
|
+
|
|
77
|
+
process.argv = ["-e", "test@a.com", "--password", "123456"];
|
|
78
|
+
const result2 = getArgumentByKey(["--email", "-e"]);
|
|
79
|
+
|
|
80
|
+
expect(result2).toBe("test@a.com");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("storeBuildSuccessFlag", () => {
|
|
85
|
+
it("should write a success flag file", async () => {
|
|
86
|
+
await storeBuildSuccessFlag();
|
|
87
|
+
|
|
88
|
+
expect(fs.writeFile).toHaveBeenCalledWith(SUCCESS_FLAG_FILE, "true");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should throw an error if the file write fails", async () => {
|
|
92
|
+
vi.mocked(fs.writeFile).mockImplementation(async () => {
|
|
93
|
+
throw new Error();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await expect(storeBuildSuccessFlag()).rejects.toThrow();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/utils.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
const oraP = import("ora");
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import { CREDENTIALS_DIR } from "./credentials";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
let ora: any;
|
|
6
7
|
export const checkNodeVersion = async () => {
|
|
7
8
|
ora = (await oraP).default;
|
|
8
|
-
ora("Checking node version...");
|
|
9
|
+
const spinner = ora("Checking node version...");
|
|
9
10
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const packageJson = await import("../package.json");
|
|
13
|
+
const {
|
|
14
|
+
engines: { node },
|
|
15
|
+
} = packageJson;
|
|
12
16
|
|
|
13
|
-
const [minMajor, minMinor] =
|
|
17
|
+
const [minMajor, minMinor] = node
|
|
14
18
|
.split(".")
|
|
15
19
|
.map((v: string) => v.replace(/[^\d]/g, ""))
|
|
16
20
|
.map(Number);
|
|
17
21
|
|
|
18
22
|
if (major < minMajor || (major === minMajor && minor < minMinor)) {
|
|
19
|
-
|
|
23
|
+
spinner.fail({
|
|
20
24
|
text: `Node version ${minMajor}.${minMinor} or higher is required. You are running ${major}.${minor}.`,
|
|
21
25
|
color: "red",
|
|
22
|
-
})
|
|
26
|
+
});
|
|
23
27
|
|
|
24
28
|
process.exit(1);
|
|
29
|
+
} else {
|
|
30
|
+
return true;
|
|
25
31
|
}
|
|
26
32
|
};
|
|
27
33
|
|
|
@@ -47,7 +53,7 @@ export const getArgumentByKey = (key: string | string[]) => {
|
|
|
47
53
|
return index !== -1 ? process.argv[index + 1] : undefined;
|
|
48
54
|
};
|
|
49
55
|
|
|
50
|
-
const SUCCESS_FLAG_FILE = `${CREDENTIALS_DIR}/success`;
|
|
56
|
+
export const SUCCESS_FLAG_FILE = `${CREDENTIALS_DIR}/success`;
|
|
51
57
|
/**
|
|
52
58
|
* Store a flag in the credentials directory to indicate a successful build
|
|
53
59
|
* This is used to determine if the build was successful or not
|
|
@@ -82,3 +88,18 @@ export const checkBuildSuccess = async () => {
|
|
|
82
88
|
return false;
|
|
83
89
|
}
|
|
84
90
|
};
|
|
91
|
+
|
|
92
|
+
export const getPackageVersion = (packageName: string) => {
|
|
93
|
+
const packageJsonPath = path.join(
|
|
94
|
+
process.cwd(),
|
|
95
|
+
"node_modules",
|
|
96
|
+
packageName,
|
|
97
|
+
"package.json",
|
|
98
|
+
);
|
|
99
|
+
try {
|
|
100
|
+
const packageJson = require(packageJsonPath);
|
|
101
|
+
return packageJson.version;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { dataModelsValidation, securityContextValidation } from "./validate";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
const startMock = {
|
|
5
|
+
succeed: vi.fn(),
|
|
6
|
+
fail: vi.fn(),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const failMock = vi.fn();
|
|
10
|
+
|
|
11
|
+
const validYaml = `cubes:
|
|
12
|
+
- name: customers
|
|
13
|
+
title: My customers
|
|
14
|
+
data_source: default
|
|
15
|
+
sql_table: public.customers
|
|
16
|
+
|
|
17
|
+
dimensions:
|
|
18
|
+
- name: id
|
|
19
|
+
sql: id
|
|
20
|
+
type: number
|
|
21
|
+
primary_key: true`;
|
|
22
|
+
|
|
23
|
+
const securityContextYaml = `
|
|
24
|
+
- name: Example customer 1
|
|
25
|
+
securityContext:
|
|
26
|
+
country: United States
|
|
27
|
+
environment: default`;
|
|
28
|
+
|
|
29
|
+
vi.mock("ora", () => ({
|
|
30
|
+
default: () => ({
|
|
31
|
+
start: vi.fn().mockImplementation(() => startMock),
|
|
32
|
+
info: vi.fn(),
|
|
33
|
+
fail: failMock,
|
|
34
|
+
}),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock("node:fs/promises", () => ({
|
|
38
|
+
readFile: vi.fn(),
|
|
39
|
+
writeFile: vi.fn(),
|
|
40
|
+
access: vi.fn(),
|
|
41
|
+
mkdir: vi.fn(),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
describe("validate", () => {
|
|
45
|
+
describe("dataModelsValidation", () => {
|
|
46
|
+
it("should return an empty array if the data models are valid", async () => {
|
|
47
|
+
vi.mocked(fs.readFile).mockImplementation(async () => {
|
|
48
|
+
return validYaml;
|
|
49
|
+
});
|
|
50
|
+
const filesList: [string, string][] = [
|
|
51
|
+
["valid-cube.yaml", "path/to/file"],
|
|
52
|
+
];
|
|
53
|
+
const result = await dataModelsValidation(filesList);
|
|
54
|
+
expect(result).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should return an array of error messages if the data models are invalid", async () => {
|
|
58
|
+
vi.mocked(fs.readFile).mockImplementation(async () => {
|
|
59
|
+
return "";
|
|
60
|
+
});
|
|
61
|
+
const filesList: [string, string][] = [
|
|
62
|
+
["invalid-cube.yaml", "path/to/file"],
|
|
63
|
+
];
|
|
64
|
+
const result = await dataModelsValidation(filesList);
|
|
65
|
+
expect(result).toEqual([
|
|
66
|
+
"path/to/file: At least one cubes or views must be defined",
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("securityContextValidation", () => {
|
|
72
|
+
it("should return an empty array if the security context is valid", async () => {
|
|
73
|
+
vi.mocked(fs.readFile).mockImplementation(async () => {
|
|
74
|
+
return securityContextYaml;
|
|
75
|
+
});
|
|
76
|
+
const filesList: [string, string][] = [
|
|
77
|
+
["valid-security-context.json", "path/to/file"],
|
|
78
|
+
];
|
|
79
|
+
const result = await securityContextValidation(filesList);
|
|
80
|
+
expect(result).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should return an array of error messages if the security context is invalid", async () => {
|
|
84
|
+
vi.mocked(fs.readFile).mockImplementation(async () => {
|
|
85
|
+
return `${securityContextYaml} ${securityContextYaml}`;
|
|
86
|
+
});
|
|
87
|
+
const filesList: [string, string][] = [
|
|
88
|
+
["invalid-security-context.json", "path/to/file"],
|
|
89
|
+
];
|
|
90
|
+
const result = await securityContextValidation(filesList);
|
|
91
|
+
expect(result).toEqual([
|
|
92
|
+
'path/to/file: security context with name "Example customer 1" already exists',
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/validate.ts
CHANGED
|
@@ -2,24 +2,11 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as YAML from "yaml";
|
|
3
3
|
import { errorFormatter, findFiles } from "@embeddable.com/sdk-utils";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { checkNodeVersion } from "./utils";
|
|
5
6
|
|
|
6
7
|
const CUBE_YAML_FILE_REGEX = /^(.*)\.cube\.ya?ml$/;
|
|
7
8
|
const SECURITY_CONTEXT_FILE_REGEX = /^(.*)\.sc\.ya?ml$/;
|
|
8
9
|
|
|
9
|
-
const checkNodeVersion = () => {
|
|
10
|
-
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
11
|
-
|
|
12
|
-
const engines = require("../package.json").engines.node;
|
|
13
|
-
|
|
14
|
-
const [minMajor, minMinor] = engines.split(".").map(Number);
|
|
15
|
-
|
|
16
|
-
if (major < minMajor || (major === minMajor && minor < minMinor)) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`Node version ${minMajor}.${minMinor} or higher is required. You are running ${major}.${minor}.`,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
10
|
export default async (ctx: any, exitIfInvalid = true) => {
|
|
24
11
|
checkNodeVersion();
|
|
25
12
|
const ora = (await import("ora")).default;
|
|
@@ -98,7 +85,7 @@ export async function dataModelsValidation(filesList: [string, string][]) {
|
|
|
98
85
|
return errors;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
async function securityContextValidation(filesList: [string, string][]) {
|
|
88
|
+
export async function securityContextValidation(filesList: [string, string][]) {
|
|
102
89
|
const errors: string[] = [];
|
|
103
90
|
|
|
104
91
|
const nameSet = new Set<string>();
|