@embeddable.com/sdk-core 3.8.0 → 3.9.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.
@@ -82,11 +82,13 @@ export const resolve = async (specifier, context, nextResolve) => {
82
82
  ? pathToFileURL(specifier.replace(`${processCwd}/file:/`, "")).href
83
83
  : specifier;
84
84
 
85
- return {
85
+ const resolved = {
86
86
  format: isTS ? "ts" : undefined,
87
87
  url: entryPointPath,
88
88
  shortCircuit: true,
89
89
  };
90
+
91
+ return resolved;
90
92
  }
91
93
 
92
94
  // Determine if this is a package import
@@ -121,13 +123,15 @@ export const resolve = async (specifier, context, nextResolve) => {
121
123
  !resolvedModule.resolvedFileName.includes("/node_modules/") &&
122
124
  EXTENSIONS.includes(resolvedModule.extension)
123
125
  ) {
124
- return {
126
+ const resolved = {
125
127
  format: "ts",
126
128
  url:
127
129
  pathToFileURL(resolvedModule.resolvedFileName).href +
128
130
  `?update=${Date.now()}`,
129
131
  shortCircuit: true,
130
132
  };
133
+
134
+ return resolved;
131
135
  }
132
136
 
133
137
  // Fallback for non-TS files or unresolved modules
@@ -147,11 +151,13 @@ export const load = async (url, context, nextLoad) => {
147
151
 
148
152
  // Compile the code using @swc-node with the customer's tsconfig
149
153
  const compiled = await compile(code, fileURLToPath(url), tsconfig, true);
150
- return {
154
+ const resolved = {
151
155
  format: "module",
152
156
  source: compiled,
153
157
  shortCircuit: true,
154
158
  };
159
+
160
+ return resolved;
155
161
  } else {
156
162
  if (
157
163
  isWindows &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
@@ -41,7 +41,7 @@
41
41
  "@embeddable.com/sdk-utils": "*",
42
42
  "@inquirer/prompts": "^7.0.0",
43
43
  "@stencil/core": "^4.22.0",
44
- "@swc-node/register": "^1.9.0",
44
+ "@swc-node/register": "^1.10.9",
45
45
  "archiver": "^5.3.1",
46
46
  "axios": "^1.7.2",
47
47
  "chokidar": "^3.6.0",
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach, Mock } from "vitest";
2
+ import * as http from "node:http";
3
+ import axios from "axios";
4
+ import provideConfig from "./provideConfig";
5
+ import { getToken } from "./login";
6
+ import * as chokidar from "chokidar";
7
+ import dev from "./dev";
8
+ import { checkNodeVersion } from "./utils";
9
+ import { createManifest } from "./cleanup";
10
+ import prepare from "./prepare";
11
+ import { WebSocketServer } from "ws";
12
+
13
+ // Mock dependencies
14
+ vi.mock("./buildTypes", () => ({ default: vi.fn() }));
15
+ vi.mock("./prepare", () => ({ default: vi.fn(), removeIfExists: vi.fn() }));
16
+ vi.mock("./generate", () => ({ default: vi.fn() }));
17
+ vi.mock("./provideConfig", () => ({ default: vi.fn() }));
18
+ vi.mock("@stencil/core/sys/node", () => ({
19
+ createNodeLogger: vi.fn(),
20
+ createNodeSys: vi.fn(),
21
+ }));
22
+ vi.mock("ws", () => ({ WebSocketServer: vi.fn() }));
23
+ vi.mock("chokidar", () => ({ watch: vi.fn() }));
24
+ vi.mock("./login", () => ({ getToken: vi.fn(), default: vi.fn() }));
25
+ vi.mock("axios", () => ({ default: { get: vi.fn() } }));
26
+ vi.mock("@embeddable.com/sdk-utils", () => ({ findFiles: vi.fn() }));
27
+ vi.mock("./push", () => ({ archive: vi.fn(), sendBuild: vi.fn() }));
28
+ vi.mock("./validate", () => ({ default: vi.fn() }));
29
+ vi.mock("./utils", () => ({ checkNodeVersion: vi.fn() }));
30
+ vi.mock("./cleanup", () => ({ createManifest: vi.fn() }));
31
+ vi.mock("node:http", () => ({
32
+ createServer: vi.fn(() => ({ listen: vi.fn() })),
33
+ }));
34
+
35
+ const mockConfig = {
36
+ client: {
37
+ rootDir: "/mock/root",
38
+ buildDir: "/mock/root/.embeddable-dev-build",
39
+ componentDir: "/mock/root/.embeddable-dev-build/component",
40
+ stencilBuild: "/mock/root/.embeddable-dev-build/dist/embeddable-wrapper",
41
+ tmpDir: "/mock/root/.embeddable-dev-tmp",
42
+ selfServeCustomizationDir: "/mock/root/self-serve",
43
+ modelsSrc: "/mock/root/models",
44
+ },
45
+ plugins: [],
46
+ previewBaseUrl: "http://preview.example.com",
47
+ pushBaseUrl: "http://push.example.com",
48
+ };
49
+
50
+ describe("dev command", () => {
51
+ let listenMock: Mock;
52
+ beforeEach(() => {
53
+ listenMock = vi.fn();
54
+ vi.mocked(http.createServer).mockImplementation(
55
+ () =>
56
+ ({
57
+ listen: listenMock,
58
+ }) as any,
59
+ );
60
+ vi.mocked(WebSocketServer).mockImplementation(() => {
61
+ return {
62
+ clients: [],
63
+ on: vi.fn(),
64
+ } as any;
65
+ });
66
+ vi.mocked(chokidar.watch).mockReturnValue({
67
+ on: vi.fn(),
68
+ } as any);
69
+ // Mock process.on to avoid actually setting up process listeners
70
+ vi.spyOn(process, "on").mockImplementation(() => process);
71
+ vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
72
+
73
+ vi.mocked(provideConfig).mockResolvedValue(mockConfig);
74
+ vi.mocked(getToken).mockResolvedValue("mock-token");
75
+ vi.mocked(axios.get).mockResolvedValue({
76
+ data: "mock-workspace",
77
+ });
78
+ });
79
+
80
+ afterEach(() => {
81
+ vi.restoreAllMocks();
82
+ });
83
+
84
+ it("should set up the development environment", async () => {
85
+ // Run the dev command
86
+ await dev();
87
+
88
+ // Verify that the necessary functions were called
89
+ expect(checkNodeVersion).toHaveBeenCalled();
90
+ expect(prepare).toHaveBeenCalled();
91
+ expect(http.createServer).toHaveBeenCalled();
92
+ expect(WebSocketServer).toHaveBeenCalled();
93
+
94
+ // Verify that the server was set up to listen on the correct port
95
+ expect(listenMock).toHaveBeenCalledWith(8926, expect.any(Function));
96
+
97
+ // Call the listen callback to simulate the server being set up
98
+ listenMock.mock.calls[0][1]();
99
+ expect(createManifest).toHaveBeenCalled();
100
+
101
+ await expect.poll(() => chokidar.watch).toBeCalledTimes(2);
102
+ });
103
+ });
package/src/dev.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  createNodeSys,
12
12
  } from "@stencil/core/sys/node";
13
13
  import { RollupWatcher } from "rollup";
14
+ import * as http from "node:http";
14
15
  import { IncomingMessage, Server, ServerResponse } from "http";
15
16
  import { ChildProcess } from "node:child_process";
16
17
  import { WebSocketServer, Server as WSServer } from "ws";
@@ -57,16 +58,24 @@ const addToGitingore = async () => {
57
58
  }
58
59
  };
59
60
 
61
+ const chokidarWatchOptions = {
62
+ ignoreInitial: true,
63
+ usePolling: false, // Ensure polling is disabled
64
+ awaitWriteFinish: {
65
+ stabilityThreshold: 200,
66
+ pollInterval: 100,
67
+ },
68
+ };
69
+
60
70
  export default async () => {
61
71
  checkNodeVersion();
62
72
  addToGitingore();
63
73
 
64
- const http = require("http");
65
74
  ora = (await oraP).default;
66
75
 
67
76
  process.on("warning", (e) => console.warn(e.stack));
68
77
 
69
- const logger = createNodeLogger({ process });
78
+ const logger = createNodeLogger();
70
79
  const sys = createNodeSys({ process });
71
80
 
72
81
  const defaultConfig = await provideConfig();
@@ -176,6 +185,7 @@ export default async () => {
176
185
 
177
186
  await plugin.validate(config);
178
187
  const watcher = await plugin.build(config);
188
+
179
189
  await configureWatcher(watcher, config);
180
190
  watchers.push(watcher);
181
191
  }
@@ -246,9 +256,7 @@ const onBundleBuildEnd = async (ctx: any) => {
246
256
  const dataModelAndSecurityContextWatcher = (ctx: any): FSWatcher => {
247
257
  const fsWatcher = chokidar.watch(
248
258
  [path.resolve(ctx.client.modelsSrc, "**/*.{cube,sc}.{yaml,yml,js}")],
249
- {
250
- ignoreInitial: true,
251
- },
259
+ chokidarWatchOptions,
252
260
  );
253
261
  fsWatcher.on("all", async () => {
254
262
  await sendDataModelsAndSecurityContextsChanges(ctx);
@@ -263,9 +271,7 @@ const customSelfServeWatcher = (ctx: any): FSWatcher => {
263
271
  path.resolve(ctx.client.selfServeCustomizationDir, "style.css"),
264
272
  path.resolve(ctx.client.selfServeCustomizationDir, "*.svg"),
265
273
  ],
266
- {
267
- ignoreInitial: true,
268
- },
274
+ chokidarWatchOptions,
269
275
  );
270
276
 
271
277
  fsWatcher.on("all", async () => {
@@ -79,7 +79,9 @@ describe("generate", () => {
79
79
  config: {},
80
80
  } as any);
81
81
  vi.mocked(createCompiler).mockResolvedValue({
82
- build: vi.fn(),
82
+ build: vi.fn().mockResolvedValue({
83
+ hasError: false,
84
+ }),
83
85
  destroy: vi.fn(),
84
86
  } as any);
85
87
 
@@ -112,4 +114,50 @@ describe("generate", () => {
112
114
  "stencilBuild/embeddable-wrapper.esm-hash.js",
113
115
  );
114
116
  });
117
+
118
+ it("should generate bundle in dev mode", async () => {
119
+ const ctx = {
120
+ ...config,
121
+ dev: {
122
+ logger: vi.fn(),
123
+ sys: vi.fn(),
124
+ },
125
+ };
126
+
127
+ vi.mocked(fs.readFile).mockResolvedValue(
128
+ "replace-this-with-component-name",
129
+ );
130
+ await generate(ctx, "sdk-react");
131
+
132
+ expect(createCompiler).toHaveBeenCalled();
133
+
134
+ expect(fs.writeFile).toHaveBeenCalledWith(
135
+ "componentDir/component.tsx",
136
+ expect.stringContaining("embeddable-component"),
137
+ );
138
+
139
+ expect(loadConfig).toHaveBeenCalledWith({
140
+ config: {
141
+ configPath: "buildDir/stencil.config.ts",
142
+ devMode: true,
143
+ maxConcurrentWorkers: 8,
144
+ minifyCss: false,
145
+ minifyJs: false,
146
+ namespace: "embeddable-wrapper",
147
+ outputTargets: [
148
+ {
149
+ type: "dist",
150
+ },
151
+ ],
152
+ rootDir: "buildDir",
153
+ sourceMap: false,
154
+ srcDir: "buildDir/component",
155
+ tsconfig: "buildDir/tsconfig.json",
156
+ watchDirs: ["buildDir/buildName"],
157
+ },
158
+ initTsConfig: true,
159
+ logger: expect.any(Function),
160
+ sys: expect.any(Function),
161
+ });
162
+ });
115
163
  });
package/src/generate.ts CHANGED
@@ -96,8 +96,8 @@ async function addComponentTagName(filePath: string, bundleHash: string) {
96
96
  ]);
97
97
  }
98
98
 
99
- async function runStencil(ctx: any) {
100
- const logger = ctx.dev?.logger || createNodeLogger({ process });
99
+ async function runStencil(ctx: any): Promise<void> {
100
+ const logger = ctx.dev?.logger || createNodeLogger();
101
101
  const sys = ctx.dev?.sys || createNodeSys({ process });
102
102
  const devMode = !!ctx.dev;
103
103
 
@@ -115,17 +115,49 @@ async function runStencil(ctx: any) {
115
115
  tsconfig: path.resolve(ctx.client.buildDir, "tsconfig.json"),
116
116
  namespace: "embeddable-wrapper",
117
117
  srcDir: path.resolve(ctx.client.buildDir, "component"),
118
+ sourceMap: !devMode,
119
+ minifyJs: !devMode,
120
+ minifyCss: !devMode,
118
121
  outputTargets: [
119
122
  {
120
123
  type: "dist",
121
124
  },
122
125
  ],
126
+ watchDirs: [
127
+ path.resolve(
128
+ ctx.client.buildDir,
129
+ ctx["sdk-react"].outputOptions.buildName,
130
+ ),
131
+ ],
123
132
  },
124
133
  });
125
134
 
126
135
  const compiler = await createCompiler(validated.config);
136
+ const buildResults = await compiler.build();
137
+
138
+ if (devMode) {
139
+ // Handle process exit to clean up resources
140
+ const cleanUp = async () => {
141
+ await compiler.destroy();
142
+ process.exit(0);
143
+ };
144
+
145
+ process.on("SIGINT", cleanUp);
146
+ process.on("SIGTERM", cleanUp);
147
+ } else {
148
+ if (buildResults.hasError) {
149
+ console.error("Stencil build error:", buildResults.diagnostics);
150
+ throw new Error("Stencil build error");
151
+ } else {
152
+ await handleStencilBuildOutput(ctx);
153
+ }
154
+ await compiler.destroy();
155
+ }
127
156
 
128
- await compiler.build();
157
+ process.chdir(ctx.client.rootDir);
158
+ }
159
+
160
+ async function handleStencilBuildOutput(ctx: any) {
129
161
  const entryFilePath = path.resolve(
130
162
  ctx.client.stencilBuild,
131
163
  "embeddable-wrapper.esm.js",
@@ -149,9 +181,6 @@ async function runStencil(ctx: any) {
149
181
  entryFilePath,
150
182
  path.resolve(ctx.client.stencilBuild, fileName),
151
183
  );
152
-
153
- await compiler.destroy();
154
- process.chdir(ctx.client.rootDir);
155
184
  }
156
185
 
157
186
  async function generateSourceMap(ctx: any, pluginName: string) {