@embeddable.com/sdk-core 3.9.4 → 3.9.6

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/push.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export declare const YAML_OR_JS_FILES: RegExp;
1
+ export declare const CUBE_FILES: RegExp;
2
+ export declare const PRESET_FILES: RegExp;
2
3
  declare const _default: () => Promise<void>;
3
4
  export default _default;
4
5
  export declare function archive(ctx: any, yamlFiles: [string, string][], isDev?: boolean): Promise<unknown>;
package/lib/validate.d.ts CHANGED
@@ -2,3 +2,4 @@ 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
4
  export declare function securityContextValidation(filesList: [string, string][]): Promise<string[]>;
5
+ export declare function clientContextValidation(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.9.4",
3
+ "version": "3.9.6",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
@@ -25,6 +25,7 @@ const baseConfigArgs = {
25
25
  rollbarAccessToken: "rollbarAccessToken",
26
26
  previewBaseUrl: "previewBaseUrl",
27
27
  modelsSrc: "modelsSrc",
28
+ presetsSrc: "presetsSrc",
28
29
  };
29
30
 
30
31
  describe("defineConfig", () => {
@@ -55,6 +56,7 @@ describe("defineConfig", () => {
55
56
  "errorFallbackComponent": "/embeddable-sdk/packages/core-sdk",
56
57
  "globalCss": "/embeddable-sdk/packages/core-sdk",
57
58
  "modelsSrc": "/embeddable-sdk/packages/core-sdk",
59
+ "presetsSrc": "/embeddable-sdk/packages/core-sdk",
58
60
  "rollupOptions": {},
59
61
  "rootDir": "/embeddable-sdk/packages/core-sdk",
60
62
  "srcDir": "/embeddable-sdk/packages/core-sdk",
@@ -19,6 +19,7 @@ export type EmbeddableConfig = {
19
19
  previewBaseUrl?: string;
20
20
  componentsSrc?: string;
21
21
  modelsSrc?: string;
22
+ presetsSrc?: string;
22
23
  globalCss?: string;
23
24
  viteConfig?: {
24
25
  resolve?: {
@@ -39,6 +40,7 @@ export default ({
39
40
  rollbarAccessToken,
40
41
  previewBaseUrl,
41
42
  modelsSrc = "src",
43
+ presetsSrc = "src",
42
44
  componentsSrc = "src",
43
45
  globalCss = "src/global.css",
44
46
  viteConfig = {},
@@ -65,6 +67,14 @@ export default ({
65
67
  }
66
68
  }
67
69
 
70
+ if (presetsSrc && !path.isAbsolute(presetsSrc)) {
71
+ presetsSrc = path.resolve(clientRoot, presetsSrc);
72
+
73
+ if (!existsSync(presetsSrc)) {
74
+ throw new Error(`presetsSrc directory ${presetsSrc} does not exist`);
75
+ }
76
+ }
77
+
68
78
  return {
69
79
  core: {
70
80
  rootDir: coreRoot,
@@ -75,6 +85,7 @@ export default ({
75
85
  rootDir: clientRoot,
76
86
  srcDir: path.resolve(clientRoot, componentsSrc),
77
87
  modelsSrc: modelsSrc ? path.resolve(clientRoot, modelsSrc) : undefined,
88
+ presetsSrc: presetsSrc ? path.resolve(clientRoot, presetsSrc) : undefined,
78
89
  buildDir: path.resolve(clientRoot, ".embeddable-build"),
79
90
  tmpDir: path.resolve(clientRoot, ".embeddable-tmp"),
80
91
  globalCss: path.resolve(clientRoot, globalCss),
package/src/dev.test.ts CHANGED
@@ -46,6 +46,7 @@ const mockConfig = {
46
46
  tmpDir: "/mock/root/.embeddable-dev-tmp",
47
47
  globalCss: "/mock/root/global.css",
48
48
  modelsSrc: "/mock/root/models",
49
+ presetsSrc: "/mock/root/presets",
49
50
  },
50
51
  plugins: [],
51
52
  previewBaseUrl: "http://preview.example.com",
package/src/dev.ts CHANGED
@@ -21,7 +21,7 @@ import { FSWatcher } from "chokidar";
21
21
  import { getToken, default as login } from "./login";
22
22
  import axios from "axios";
23
23
  import { findFiles } from "@embeddable.com/sdk-utils";
24
- import { archive, YAML_OR_JS_FILES, sendBuild } from "./push";
24
+ import { archive, PRESET_FILES, CUBE_FILES, sendBuild } from "./push";
25
25
  import validate from "./validate";
26
26
  import { checkNodeVersion } from "./utils";
27
27
  import { createManifest } from "./cleanup";
@@ -191,7 +191,7 @@ export default async () => {
191
191
  editorsMetaFileName: "embeddable-editors-meta.js",
192
192
  });
193
193
 
194
- await sendDataModelsAndSecurityContextsChanges(config);
194
+ await sendDataModelsAndContextsChanges(config);
195
195
 
196
196
  for (const getPlugin of config.plugins) {
197
197
  const plugin = getPlugin();
@@ -274,11 +274,14 @@ const onBundleBuildEnd = async (ctx: any) => {
274
274
 
275
275
  const dataModelAndSecurityContextWatcher = (ctx: any): FSWatcher => {
276
276
  const fsWatcher = chokidar.watch(
277
- [path.resolve(ctx.client.modelsSrc, "**/*.{cube,sc}.{yaml,yml,js}")],
277
+ [
278
+ path.resolve(ctx.client.modelsSrc, "**/*.{cube}.{yaml,yml,js}"),
279
+ path.resolve(ctx.client.presetsSrc, "**/*.{sc,cc}.{yaml,yml}"),
280
+ ],
278
281
  chokidarWatchOptions,
279
282
  );
280
283
  fsWatcher.on("all", async () => {
281
- await sendDataModelsAndSecurityContextsChanges(ctx);
284
+ await sendDataModelsAndContextsChanges(ctx);
282
285
  });
283
286
 
284
287
  return fsWatcher;
@@ -294,7 +297,7 @@ const globalCssWatcher = (ctx: any): FSWatcher => {
294
297
  return fsWatcher;
295
298
  };
296
299
 
297
- const sendDataModelsAndSecurityContextsChanges = async (ctx: any) => {
300
+ const sendDataModelsAndContextsChanges = async (ctx: any) => {
298
301
  sendMessage("dataModelsAndOrSecurityContextUpdateStart");
299
302
  const isValid = await validate(ctx, false);
300
303
  if (isValid) {
@@ -302,7 +305,13 @@ const sendDataModelsAndSecurityContextsChanges = async (ctx: any) => {
302
305
  const sending = ora(
303
306
  "Synchronising data models and/or security contexts...",
304
307
  ).start();
305
- const filesList = await findFiles(ctx.client.modelsSrc, YAML_OR_JS_FILES);
308
+ const cubeFilesList = await findFiles(ctx.client.modelsSrc, CUBE_FILES);
309
+ const contextFilesList = await findFiles(
310
+ ctx.client.presetsSrc,
311
+ PRESET_FILES,
312
+ );
313
+
314
+ const filesList = [...cubeFilesList, ...contextFilesList];
306
315
 
307
316
  // add manifest to the archive
308
317
  filesList.push([
package/src/push.ts CHANGED
@@ -15,8 +15,11 @@ import { getToken } from "./login";
15
15
  import { checkBuildSuccess, checkNodeVersion, getArgumentByKey } from "./utils";
16
16
  import { selectWorkspace } from "./workspaceUtils";
17
17
 
18
- // grab .cube.yml|js and .sc.yml|js files
19
- export const YAML_OR_JS_FILES = /^(.*)\.(cube|sc)\.(ya?ml|js)$/;
18
+ // grab cube files
19
+ export const CUBE_FILES = /^(.*)\.cube\.(ya?ml|js)$/;
20
+
21
+ // grab security context and client context files
22
+ export const PRESET_FILES = /^(.*)\.(sc|cc)\.ya?ml$/;
20
23
 
21
24
  let ora: any;
22
25
  export default async () => {
@@ -134,12 +137,17 @@ async function verify(ctx: any) {
134
137
  async function buildArchive(config: any) {
135
138
  const spinnerArchive = ora("Building...").start();
136
139
 
137
- const filesList = await findFiles(
140
+ const cubeFilesList = await findFiles(
138
141
  config.client.modelsSrc || config.client.srcDir,
139
- YAML_OR_JS_FILES,
142
+ CUBE_FILES,
143
+ );
144
+
145
+ const contextFilesList = await findFiles(
146
+ config.client.presetsSrc || config.client.srcDir,
147
+ PRESET_FILES,
140
148
  );
141
149
 
142
- await archive(config, filesList);
150
+ await archive(config, [...cubeFilesList, ...contextFilesList]);
143
151
  return spinnerArchive.succeed("Bundling completed");
144
152
  }
145
153
 
@@ -1,4 +1,8 @@
1
- import { dataModelsValidation, securityContextValidation } from "./validate";
1
+ import {
2
+ clientContextValidation,
3
+ dataModelsValidation,
4
+ securityContextValidation,
5
+ } from "./validate";
2
6
  import * as fs from "node:fs/promises";
3
7
 
4
8
  const startMock = {
@@ -35,6 +39,11 @@ const securityContextYaml = `
35
39
  country: United States
36
40
  environment: default`;
37
41
 
42
+ const clientContextYaml = `
43
+ - name: blue
44
+ clientContext:
45
+ color: blue`;
46
+
38
47
  vi.mock("ora", () => ({
39
48
  default: () => ({
40
49
  start: vi.fn().mockImplementation(() => startMock),
@@ -121,4 +130,30 @@ describe("validate", () => {
121
130
  ]);
122
131
  });
123
132
  });
133
+
134
+ describe("clientContextValidation", () => {
135
+ it("should return an empty array if the client context is valid", async () => {
136
+ vi.mocked(fs.readFile).mockImplementation(async () => {
137
+ return clientContextYaml;
138
+ });
139
+ const filesList: [string, string][] = [
140
+ ["valid-client-context.json", "path/to/file"],
141
+ ];
142
+ const result = await clientContextValidation(filesList);
143
+ expect(result).toEqual([]);
144
+ });
145
+
146
+ it("should return an array of error messages if the client context is invalid", async () => {
147
+ vi.mocked(fs.readFile).mockImplementation(async () => {
148
+ return `${clientContextYaml} ${clientContextYaml}`;
149
+ });
150
+ const filesList: [string, string][] = [
151
+ ["invalid-client-context.json", "path/to/file"],
152
+ ];
153
+ const result = await clientContextValidation(filesList);
154
+ expect(result).toEqual([
155
+ 'path/to/file: client context with name "blue" already exists',
156
+ ]);
157
+ });
158
+ });
124
159
  });
package/src/validate.ts CHANGED
@@ -6,6 +6,7 @@ import { checkNodeVersion } from "./utils";
6
6
 
7
7
  const CUBE_YAML_FILE_REGEX = /^(.*)\.cube\.ya?ml$/;
8
8
  const SECURITY_CONTEXT_FILE_REGEX = /^(.*)\.sc\.ya?ml$/;
9
+ const CLIENT_CONTEXT_FILE_REGEX = /^(.*)\.cc\.ya?ml$/;
9
10
 
10
11
  export default async (ctx: any, exitIfInvalid = true) => {
11
12
  checkNodeVersion();
@@ -13,13 +14,20 @@ export default async (ctx: any, exitIfInvalid = true) => {
13
14
 
14
15
  const spinnerValidate = ora("Data model validation...").start();
15
16
 
16
- const filesList = await findFiles(ctx.client.srcDir, CUBE_YAML_FILE_REGEX);
17
- const securityContextFilesList = await findFiles(
17
+ const cubeFilesList = await findFiles(
18
18
  ctx.client.modelsSrc || ctx.client.srcDir,
19
+ CUBE_YAML_FILE_REGEX,
20
+ );
21
+ const securityContextFilesList = await findFiles(
22
+ ctx.client.presetsSrc || ctx.client.srcDir,
19
23
  SECURITY_CONTEXT_FILE_REGEX,
20
24
  );
25
+ const clientContextFilesList = await findFiles(
26
+ ctx.client.presetsSrc || ctx.client.srcDir,
27
+ CLIENT_CONTEXT_FILE_REGEX,
28
+ );
21
29
 
22
- const dataModelErrors = await dataModelsValidation(filesList);
30
+ const dataModelErrors = await dataModelsValidation(cubeFilesList);
23
31
 
24
32
  if (dataModelErrors.length) {
25
33
  spinnerValidate.fail("One or more cube.yaml files are invalid:");
@@ -39,6 +47,10 @@ export default async (ctx: any, exitIfInvalid = true) => {
39
47
  securityContextFilesList,
40
48
  );
41
49
 
50
+ const clientContextErrors = await clientContextValidation(
51
+ clientContextFilesList,
52
+ );
53
+
42
54
  if (securityContextErrors.length) {
43
55
  spinnerValidate.fail("One or more security context files are invalid:");
44
56
 
@@ -51,7 +63,23 @@ export default async (ctx: any, exitIfInvalid = true) => {
51
63
  }
52
64
  }
53
65
 
54
- return dataModelErrors.length === 0 && securityContextErrors.length === 0;
66
+ if (clientContextErrors.length) {
67
+ spinnerValidate.fail("One or more client context files are invalid:");
68
+
69
+ clientContextErrors.forEach((errorMessage) =>
70
+ spinnerValidate.info(errorMessage),
71
+ );
72
+
73
+ if (exitIfInvalid) {
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ return (
79
+ dataModelErrors.length === 0 &&
80
+ securityContextErrors.length === 0 &&
81
+ clientContextErrors.length === 0
82
+ );
55
83
  };
56
84
 
57
85
  export async function dataModelsValidation(filesList: [string, string][]) {
@@ -118,6 +146,36 @@ export async function securityContextValidation(filesList: [string, string][]) {
118
146
  return errors;
119
147
  }
120
148
 
149
+ export async function clientContextValidation(filesList: [string, string][]) {
150
+ const errors: string[] = [];
151
+
152
+ const nameSet = new Set<string>();
153
+ for (const [_, filePath] of filesList) {
154
+ const fileContentRaw = await fs.readFile(filePath, "utf8");
155
+
156
+ const cube = YAML.parse(fileContentRaw);
157
+
158
+ cube.forEach((item: { name: string }) => {
159
+ if (nameSet.has(item.name)) {
160
+ errors.push(
161
+ `${filePath}: client context with name "${item.name}" already exists`,
162
+ );
163
+ } else {
164
+ nameSet.add(item.name);
165
+ }
166
+ });
167
+
168
+ const safeParse = clientContextSchema.safeParse(cube);
169
+ if (!safeParse.success) {
170
+ errorFormatter(safeParse.error.issues).forEach((error) => {
171
+ errors.push(`${filePath}: ${error}`);
172
+ });
173
+ }
174
+ }
175
+
176
+ return errors;
177
+ }
178
+
121
179
  enum MeasureTypeEnum {
122
180
  string = "string",
123
181
  time = "time",
@@ -194,3 +252,10 @@ const securityContextSchema = z.array(
194
252
  securityContext: z.object({}), // can be any object
195
253
  }),
196
254
  );
255
+
256
+ const clientContextSchema = z.array(
257
+ z.object({
258
+ name: z.string(),
259
+ clientContext: z.object({}), // can be any object
260
+ }),
261
+ );
@@ -13,6 +13,7 @@ export class EmbeddableComponent {
13
13
 
14
14
  @Prop() componentName: string;
15
15
  @Prop() props: string = '{}';
16
+ @Prop() clientContext: string = '{}';
16
17
 
17
18
  @Event({
18
19
  eventName: 'componentLoaded',
@@ -21,6 +22,7 @@ export class EmbeddableComponent {
21
22
  bubbles: true,
22
23
  }) componentDidLoadEvent: EventEmitter<any>;
23
24
 
25
+ @Watch('clientContext')
24
26
  @Watch('componentName')
25
27
  watchComponentName() {
26
28
  this.handlePops();
@@ -77,7 +79,8 @@ export class EmbeddableComponent {
77
79
  render(
78
80
  this.rootElement,
79
81
  this.componentName,
80
- JSON.parse(this.props)
82
+ JSON.parse(this.props),
83
+ JSON.parse(this.clientContext)
81
84
  );
82
85
  }
83
86
  }