@embeddable.com/sdk-core 3.13.6 → 3.14.0-next.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/buildGlobalHooks.d.ts +7 -0
- package/lib/buildPackage.d.ts +2 -0
- package/lib/defineConfig.d.ts +42 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.esm.js +469 -285
- package/lib/index.esm.js.map +1 -1
- package/lib/push.d.ts +1 -1
- package/loader/entryPoint.js +9 -1
- package/loader/entryPoint.test.js +1 -0
- package/package.json +4 -3
- package/src/build.test.ts +6 -0
- package/src/build.ts +2 -0
- package/src/buildGlobalHooks.int.test.ts +215 -0
- package/src/buildGlobalHooks.ts +252 -0
- package/src/buildGlobalHooks.unit.test.ts +273 -0
- package/src/buildPackage.test.ts +167 -0
- package/src/buildPackage.ts +53 -0
- package/src/buildTypes.test.ts +108 -6
- package/src/buildTypes.ts +41 -2
- package/src/cleanup.test.ts +8 -1
- package/src/cleanup.ts +32 -1
- package/src/defineConfig.test.ts +3 -0
- package/src/defineConfig.ts +29 -3
- package/src/dev.test.ts +13 -1
- package/src/dev.ts +37 -1
- package/src/generate.test.ts +2 -0
- package/src/generate.ts +24 -13
- package/src/index.ts +2 -1
- package/src/push.ts +17 -17
- package/src/workspaceUtils.ts +5 -1
- package/templates/component.tsx.template +3 -1
- package/templates/embeddableThemeHook.js.template +28 -0
- package/lib/index.js +0 -22492
- package/lib/index.js.map +0 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// buildGlobalHooks.unit.test.ts
|
|
2
|
+
import { describe, it, expect, beforeEach, vi, Mock } from "vitest";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as fsSync from "node:fs";
|
|
5
|
+
import * as vite from "vite";
|
|
6
|
+
import {
|
|
7
|
+
findFiles,
|
|
8
|
+
getContentHash,
|
|
9
|
+
getGlobalHooksMeta,
|
|
10
|
+
getComponentLibraryConfig,
|
|
11
|
+
} from "@embeddable.com/sdk-utils";
|
|
12
|
+
|
|
13
|
+
import buildGlobalHooks from "../src/buildGlobalHooks";
|
|
14
|
+
import { ResolvedEmbeddableConfig } from "./defineConfig";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
// Mock implementations
|
|
18
|
+
vi.mock("node:fs/promises");
|
|
19
|
+
vi.mock("node:fs");
|
|
20
|
+
vi.mock("vite");
|
|
21
|
+
vi.mock("@embeddable.com/sdk-utils", async () => {
|
|
22
|
+
const actual = await vi.importActual<
|
|
23
|
+
typeof import("@embeddable.com/sdk-utils")
|
|
24
|
+
>("@embeddable.com/sdk-utils");
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
findFiles: vi.fn(),
|
|
28
|
+
getContentHash: vi.fn(),
|
|
29
|
+
getGlobalHooksMeta: vi.fn(),
|
|
30
|
+
getComponentLibraryConfig: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const lifecyclePath = path.resolve(
|
|
35
|
+
process.cwd(),
|
|
36
|
+
"fake",
|
|
37
|
+
"root",
|
|
38
|
+
"embeddable.lifecycle.ts",
|
|
39
|
+
);
|
|
40
|
+
const themePath = path.resolve(
|
|
41
|
+
process.cwd(),
|
|
42
|
+
"fake",
|
|
43
|
+
"root",
|
|
44
|
+
"embeddable.theme.ts",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
describe("buildGlobalHooks (Unit Tests)", () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should skip lifecycle building if file doesn't exist", async () => {
|
|
53
|
+
vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => {
|
|
54
|
+
// We want the code to see that /fake/root/embeddable.lifecycle.ts does NOT exist
|
|
55
|
+
if (p === lifecyclePath) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
// Otherwise, default to false
|
|
59
|
+
return false;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Possibly not used, but let's mock anyway
|
|
63
|
+
(findFiles as Mock).mockResolvedValue([]);
|
|
64
|
+
|
|
65
|
+
// The aggregator template might be read if there's some library with a theme
|
|
66
|
+
(fs.readFile as Mock).mockResolvedValue("some template content");
|
|
67
|
+
(getContentHash as Mock).mockReturnValue("abc123");
|
|
68
|
+
|
|
69
|
+
const ctx: ResolvedEmbeddableConfig = {
|
|
70
|
+
client: {
|
|
71
|
+
srcDir: path.resolve(process.cwd(), "fake", "src"),
|
|
72
|
+
buildDir: path.resolve(process.cwd(), "fake", "build"),
|
|
73
|
+
rootDir: path.resolve(process.cwd(), "fake", "root"),
|
|
74
|
+
lifecycleHooksFile: lifecyclePath,
|
|
75
|
+
componentLibraries: [],
|
|
76
|
+
customizationFile: themePath,
|
|
77
|
+
},
|
|
78
|
+
core: {
|
|
79
|
+
templatesDir: "/fake/templates",
|
|
80
|
+
},
|
|
81
|
+
} as any;
|
|
82
|
+
|
|
83
|
+
await buildGlobalHooks(ctx);
|
|
84
|
+
|
|
85
|
+
// Because we skip building the repo lifecycle, no call to vite.build with that entry
|
|
86
|
+
expect(vite.build).not.toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
build: expect.objectContaining({
|
|
89
|
+
lib: expect.objectContaining({
|
|
90
|
+
entry: lifecyclePath,
|
|
91
|
+
}),
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should build lifecycle file if it exists", async () => {
|
|
98
|
+
// Now we say "repo lifecycle does exist"
|
|
99
|
+
vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => {
|
|
100
|
+
// If path is exactly the lifecycle file => true
|
|
101
|
+
return p === lifecyclePath;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Just in case, but not strictly used here
|
|
105
|
+
(findFiles as Mock).mockResolvedValue([]);
|
|
106
|
+
// The aggregator template might or might not be read
|
|
107
|
+
(fs.readFile as Mock).mockResolvedValue("some file content");
|
|
108
|
+
(getContentHash as Mock).mockReturnValue("abc123");
|
|
109
|
+
(fs.rename as Mock).mockResolvedValue(undefined);
|
|
110
|
+
(vite.build as Mock).mockResolvedValue(undefined);
|
|
111
|
+
|
|
112
|
+
const ctx: ResolvedEmbeddableConfig = {
|
|
113
|
+
client: {
|
|
114
|
+
srcDir: path.resolve(process.cwd(), "fake", "src"),
|
|
115
|
+
buildDir: path.resolve(process.cwd(), "fake", "build"),
|
|
116
|
+
rootDir: path.resolve(process.cwd(), "fake", "root"),
|
|
117
|
+
lifecycleHooksFile: lifecyclePath,
|
|
118
|
+
customizationFile: themePath,
|
|
119
|
+
componentLibraries: [],
|
|
120
|
+
},
|
|
121
|
+
core: {
|
|
122
|
+
templatesDir: "/fake/templates",
|
|
123
|
+
},
|
|
124
|
+
} as any;
|
|
125
|
+
|
|
126
|
+
await buildGlobalHooks(ctx);
|
|
127
|
+
|
|
128
|
+
// We expect a call to build the lifecycle
|
|
129
|
+
expect(vite.build).toHaveBeenCalledWith(
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
build: expect.objectContaining({
|
|
132
|
+
lib: expect.objectContaining({
|
|
133
|
+
entry: lifecyclePath,
|
|
134
|
+
fileName: "embeddable-lifecycle",
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should build theme aggregator if libraries exist with themeProvider references", async () => {
|
|
142
|
+
// aggregator template read is guaranteed
|
|
143
|
+
(fs.readFile as Mock).mockResolvedValue("template content");
|
|
144
|
+
(getContentHash as Mock).mockReturnValue("xyz777");
|
|
145
|
+
(fs.rename as Mock).mockResolvedValue(undefined);
|
|
146
|
+
(vite.build as Mock).mockResolvedValue(undefined);
|
|
147
|
+
|
|
148
|
+
// Suppose we skip the lifecycle, but aggregator is still built
|
|
149
|
+
vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => {
|
|
150
|
+
// lifecycle => false
|
|
151
|
+
if (p === lifecyclePath) return false;
|
|
152
|
+
// local theme => true
|
|
153
|
+
if (p === themePath) return true;
|
|
154
|
+
return false;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
(getComponentLibraryConfig as Mock).mockImplementation((cfg: any) => ({
|
|
158
|
+
libraryName: cfg.name,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
// Each library call to getGlobalHooksMeta occurs twice:
|
|
162
|
+
// 1) buildThemeHook -> aggregator
|
|
163
|
+
// 2) buildLifecycleHooks -> but we skip it if lifecycle doesn't exist
|
|
164
|
+
// The code still calls getGlobalHooksMeta for each library in buildLifecycleHooks
|
|
165
|
+
// So we must provide 4 total mock results for 2 libraries => aggregator + lifecycle each.
|
|
166
|
+
|
|
167
|
+
(getGlobalHooksMeta as Mock)
|
|
168
|
+
// aggregator call #1: libA
|
|
169
|
+
.mockResolvedValueOnce({
|
|
170
|
+
themeProvider: "libA-theme.js",
|
|
171
|
+
lifecycleHooks: [],
|
|
172
|
+
})
|
|
173
|
+
// aggregator call #2: libB
|
|
174
|
+
.mockResolvedValueOnce({
|
|
175
|
+
themeProvider: "libB-theme.js",
|
|
176
|
+
lifecycleHooks: [],
|
|
177
|
+
})
|
|
178
|
+
// lifecycle call #1: libA
|
|
179
|
+
.mockResolvedValueOnce({
|
|
180
|
+
themeProvider: "libA-theme.js",
|
|
181
|
+
lifecycleHooks: [],
|
|
182
|
+
})
|
|
183
|
+
// lifecycle call #2: libB
|
|
184
|
+
.mockResolvedValueOnce({
|
|
185
|
+
themeProvider: "libB-theme.js",
|
|
186
|
+
lifecycleHooks: [],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const ctx: ResolvedEmbeddableConfig = {
|
|
190
|
+
client: {
|
|
191
|
+
srcDir: path.resolve(process.cwd(), "fake", "src"),
|
|
192
|
+
buildDir: path.resolve(process.cwd(), "fake", "build"),
|
|
193
|
+
rootDir: path.resolve(process.cwd(), "fake", "root"),
|
|
194
|
+
lifecycleHooksFile: lifecyclePath,
|
|
195
|
+
customizationFile: themePath,
|
|
196
|
+
componentLibraries: [{ name: "libA" }, { name: "libB" }],
|
|
197
|
+
},
|
|
198
|
+
core: {
|
|
199
|
+
templatesDir: "/fake/templates",
|
|
200
|
+
},
|
|
201
|
+
} as any;
|
|
202
|
+
|
|
203
|
+
await buildGlobalHooks(ctx);
|
|
204
|
+
|
|
205
|
+
// aggregator => build with entry = /fake/build/embeddableThemeHook.js
|
|
206
|
+
expect(vite.build).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
build: expect.objectContaining({
|
|
209
|
+
lib: expect.objectContaining({
|
|
210
|
+
entry: expect.stringContaining("embeddableThemeHook.js"),
|
|
211
|
+
fileName: `embeddable-theme-xyz777`, // from getContentHash
|
|
212
|
+
}),
|
|
213
|
+
}),
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should skip aggregator if no library has themeProvider and no local theme", async () => {
|
|
219
|
+
(fs.readFile as Mock).mockResolvedValue("template content");
|
|
220
|
+
(getContentHash as Mock).mockReturnValue("someHash");
|
|
221
|
+
(fs.rename as Mock).mockResolvedValue(undefined);
|
|
222
|
+
(vite.build as Mock).mockResolvedValue(undefined);
|
|
223
|
+
|
|
224
|
+
vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => {
|
|
225
|
+
// Suppose no local theme => false
|
|
226
|
+
if (p === themePath) return false;
|
|
227
|
+
if (p === lifecyclePath) return false;
|
|
228
|
+
return false;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Each library is missing a themeProvider
|
|
232
|
+
(getComponentLibraryConfig as Mock).mockImplementation((cfg: any) => ({
|
|
233
|
+
libraryName: cfg.name,
|
|
234
|
+
}));
|
|
235
|
+
// no theme, so aggregator is skipped
|
|
236
|
+
(getGlobalHooksMeta as Mock).mockResolvedValue({
|
|
237
|
+
themeProvider: null,
|
|
238
|
+
lifecycleHooks: [],
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const ctx: ResolvedEmbeddableConfig = {
|
|
242
|
+
client: {
|
|
243
|
+
srcDir: path.resolve(process.cwd(), "fake", "src"),
|
|
244
|
+
buildDir: path.resolve(process.cwd(), "fake", "build"),
|
|
245
|
+
rootDir: path.resolve(process.cwd(), "fake", "root"),
|
|
246
|
+
lifecycleHooksFile: lifecyclePath,
|
|
247
|
+
customizationFile: themePath,
|
|
248
|
+
componentLibraries: [
|
|
249
|
+
{ name: "libA" }, // no theme
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
core: {
|
|
253
|
+
templatesDir: "/fake/templates",
|
|
254
|
+
},
|
|
255
|
+
} as any;
|
|
256
|
+
|
|
257
|
+
await buildGlobalHooks(ctx);
|
|
258
|
+
|
|
259
|
+
// aggregator not built
|
|
260
|
+
// We do an exact check: "not toHaveBeenCalledWith"
|
|
261
|
+
// But the code might still do a build for the lifecycle if it existed.
|
|
262
|
+
// We said the lifecycle is false => so no build for aggregator
|
|
263
|
+
expect(vite.build).not.toHaveBeenCalledWith(
|
|
264
|
+
expect.objectContaining({
|
|
265
|
+
build: expect.objectContaining({
|
|
266
|
+
lib: expect.objectContaining({
|
|
267
|
+
entry: expect.stringContaining("embeddableThemeHook.js"),
|
|
268
|
+
}),
|
|
269
|
+
}),
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as fsSync from "node:fs";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
import buildPackage from "./buildPackage";
|
|
8
|
+
import buildTypes from "./buildTypes";
|
|
9
|
+
import buildGlobalHooks from "./buildGlobalHooks";
|
|
10
|
+
import provideConfig from "./provideConfig";
|
|
11
|
+
import {
|
|
12
|
+
checkNodeVersion,
|
|
13
|
+
removeBuildSuccessFlag,
|
|
14
|
+
storeBuildSuccessFlag,
|
|
15
|
+
} from "./utils";
|
|
16
|
+
import { initLogger, logError } from "./logger";
|
|
17
|
+
import { ResolvedEmbeddableConfig } from "./defineConfig";
|
|
18
|
+
|
|
19
|
+
// ----------------- MOCKS ----------------- //
|
|
20
|
+
vi.mock("node:fs/promises");
|
|
21
|
+
vi.mock("node:fs");
|
|
22
|
+
|
|
23
|
+
// Mock buildTypes
|
|
24
|
+
vi.mock("./buildTypes", () => ({
|
|
25
|
+
default: vi.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock buildGlobalHooks
|
|
29
|
+
vi.mock("./buildGlobalHooks", () => ({
|
|
30
|
+
default: vi.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock provideConfig
|
|
34
|
+
vi.mock("./provideConfig", () => ({
|
|
35
|
+
default: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock utils
|
|
39
|
+
vi.mock("./utils", async () => {
|
|
40
|
+
const actualUtils =
|
|
41
|
+
await vi.importActual<typeof import("./utils")>("./utils");
|
|
42
|
+
return {
|
|
43
|
+
...actualUtils,
|
|
44
|
+
checkNodeVersion: vi.fn(),
|
|
45
|
+
removeBuildSuccessFlag: vi.fn(),
|
|
46
|
+
storeBuildSuccessFlag: vi.fn(),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Mock logger
|
|
51
|
+
vi.mock("./logger", () => ({
|
|
52
|
+
initLogger: vi.fn().mockResolvedValue(undefined),
|
|
53
|
+
logError: vi.fn().mockResolvedValue(undefined),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// ----------------- TESTS ----------------- //
|
|
57
|
+
describe("buildPackage", () => {
|
|
58
|
+
// We'll create a sample plugin for testing
|
|
59
|
+
const mockPlugin = {
|
|
60
|
+
pluginName: "testPlugin",
|
|
61
|
+
validate: vi.fn(),
|
|
62
|
+
buildPackage: vi.fn(),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const rootDir = path.resolve("fake", "root");
|
|
66
|
+
const buildDir = path.resolve("fake", "build");
|
|
67
|
+
const distDir = path.resolve(rootDir, "dist");
|
|
68
|
+
|
|
69
|
+
// We'll also create a sample config
|
|
70
|
+
const config = {
|
|
71
|
+
client: {
|
|
72
|
+
rootDir,
|
|
73
|
+
buildDir,
|
|
74
|
+
},
|
|
75
|
+
plugins: [() => mockPlugin],
|
|
76
|
+
} as unknown as ResolvedEmbeddableConfig;
|
|
77
|
+
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
// Clear mocks before each test to avoid cross-test interference
|
|
80
|
+
vi.clearAllMocks();
|
|
81
|
+
|
|
82
|
+
// By default, provideConfig will return our sample config
|
|
83
|
+
vi.mocked(provideConfig).mockResolvedValue(config);
|
|
84
|
+
|
|
85
|
+
// Let's ensure fsSync.existsSync returns false by default
|
|
86
|
+
// (so that our prepare() logic tries to mkdir, etc.)
|
|
87
|
+
vi.spyOn(fsSync, "existsSync").mockReturnValue(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should call all main steps in a successful scenario", async () => {
|
|
91
|
+
await buildPackage();
|
|
92
|
+
|
|
93
|
+
// 1. Logger
|
|
94
|
+
expect(initLogger).toHaveBeenCalledWith("package");
|
|
95
|
+
|
|
96
|
+
// 2. checkNodeVersion and removeBuildSuccessFlag
|
|
97
|
+
expect(checkNodeVersion).toHaveBeenCalled();
|
|
98
|
+
expect(removeBuildSuccessFlag).toHaveBeenCalled();
|
|
99
|
+
|
|
100
|
+
// 3. provideConfig -> must be called, returns our config
|
|
101
|
+
expect(provideConfig).toHaveBeenCalled();
|
|
102
|
+
|
|
103
|
+
// 4. prepare() logic -> it checks if buildDir exists, removes if so, then mkdir
|
|
104
|
+
// Because we do cross-platform checks, we confirm the path used is distDir
|
|
105
|
+
expect(fsSync.existsSync).toHaveBeenCalledWith(distDir);
|
|
106
|
+
// Because it returned false, we do not remove
|
|
107
|
+
expect(fs.rm).toHaveBeenCalledTimes(0);
|
|
108
|
+
// Instead we create it
|
|
109
|
+
expect(fs.mkdir).toHaveBeenCalledWith(distDir);
|
|
110
|
+
|
|
111
|
+
// 5. buildTypes & buildGlobalHooks
|
|
112
|
+
expect(buildTypes).toHaveBeenCalledWith(config);
|
|
113
|
+
expect(buildGlobalHooks).toHaveBeenCalledWith(config);
|
|
114
|
+
|
|
115
|
+
// 6. Plugin calls
|
|
116
|
+
expect(mockPlugin.validate).toHaveBeenCalledWith(config);
|
|
117
|
+
expect(mockPlugin.buildPackage).toHaveBeenCalledWith(config);
|
|
118
|
+
|
|
119
|
+
// 7. storeBuildSuccessFlag
|
|
120
|
+
expect(storeBuildSuccessFlag).toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should remove and recreate buildDir if it already exists", async () => {
|
|
124
|
+
// This time let's simulate that buildDir already exists
|
|
125
|
+
vi.mocked(fsSync.existsSync).mockReturnValue(true);
|
|
126
|
+
|
|
127
|
+
await buildPackage();
|
|
128
|
+
|
|
129
|
+
// Because buildDir exists, prepare() should remove it
|
|
130
|
+
expect(fs.rm).toHaveBeenCalledWith(distDir, { recursive: true });
|
|
131
|
+
// Then re-create it
|
|
132
|
+
expect(fs.mkdir).toHaveBeenCalledWith(distDir);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should log an error, call logError, and exit(1) if an error occurs", async () => {
|
|
136
|
+
// We'll simulate an error in removeBuildSuccessFlag
|
|
137
|
+
const error = new Error("test error");
|
|
138
|
+
vi.mocked(removeBuildSuccessFlag).mockRejectedValueOnce(error);
|
|
139
|
+
|
|
140
|
+
// We want to check process.exit(1) is called
|
|
141
|
+
// but calling exit actually kills the process, so we mock it
|
|
142
|
+
const originalConsoleLog = console.log;
|
|
143
|
+
console.log = vi.fn();
|
|
144
|
+
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
145
|
+
throw new Error("process.exit called");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// We expect buildPackage to throw because exit is called
|
|
149
|
+
await expect(buildPackage()).rejects.toThrow("process.exit called");
|
|
150
|
+
|
|
151
|
+
// We also expect that logError has been called with the correct params
|
|
152
|
+
expect(logError).toHaveBeenCalledWith({
|
|
153
|
+
command: "package",
|
|
154
|
+
breadcrumbs: ["checkNodeVersion"], // because it fails after calling checkNodeVersion
|
|
155
|
+
error,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// And the error is printed
|
|
159
|
+
expect(console.log).toHaveBeenCalledWith(error);
|
|
160
|
+
|
|
161
|
+
// Finally, we confirm process.exit(1) was triggered
|
|
162
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
163
|
+
|
|
164
|
+
console.log = originalConsoleLog;
|
|
165
|
+
exitSpy.mockRestore();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import buildTypes from "./buildTypes";
|
|
2
|
+
import buildGlobalHooks from "./buildGlobalHooks";
|
|
3
|
+
import provideConfig from "./provideConfig";
|
|
4
|
+
import {
|
|
5
|
+
checkNodeVersion,
|
|
6
|
+
removeBuildSuccessFlag,
|
|
7
|
+
storeBuildSuccessFlag,
|
|
8
|
+
} from "./utils";
|
|
9
|
+
import { initLogger, logError } from "./logger";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as fsSync from "node:fs";
|
|
13
|
+
|
|
14
|
+
export default async () => {
|
|
15
|
+
await initLogger("package");
|
|
16
|
+
const breadcrumbs: string[] = [];
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const startTime = process.hrtime();
|
|
20
|
+
await checkNodeVersion();
|
|
21
|
+
breadcrumbs.push("checkNodeVersion");
|
|
22
|
+
await removeBuildSuccessFlag();
|
|
23
|
+
|
|
24
|
+
const config = await provideConfig();
|
|
25
|
+
config.client.buildDir = path.resolve(config.client.rootDir, "dist");
|
|
26
|
+
await prepare(config);
|
|
27
|
+
await buildTypes(config);
|
|
28
|
+
await buildGlobalHooks(config);
|
|
29
|
+
|
|
30
|
+
for (const getPlugin of config.plugins) {
|
|
31
|
+
const plugin = getPlugin();
|
|
32
|
+
|
|
33
|
+
breadcrumbs.push(`${plugin.pluginName}: validate`);
|
|
34
|
+
await plugin.validate(config);
|
|
35
|
+
breadcrumbs.push(`${plugin.pluginName}: buildPackage`);
|
|
36
|
+
await plugin.buildPackage(config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Calculating build time in seconds
|
|
40
|
+
config.buildTime = process.hrtime(startTime);
|
|
41
|
+
await storeBuildSuccessFlag();
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
await logError({ command: "package", breadcrumbs, error });
|
|
44
|
+
console.log(error);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const prepare = async (config: any) => {
|
|
50
|
+
if (fsSync.existsSync(config.client.buildDir))
|
|
51
|
+
await fs.rm(config.client.buildDir, { recursive: true });
|
|
52
|
+
await fs.mkdir(config.client.buildDir);
|
|
53
|
+
};
|
package/src/buildTypes.test.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import buildTypes, { EMB_TYPE_FILE_REGEX } from "./buildTypes";
|
|
3
2
|
import * as path from "node:path";
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
4
|
+
import buildTypes, { EMB_TYPE_FILE_REGEX } from "./buildTypes";
|
|
4
5
|
import { build } from "vite";
|
|
5
|
-
import { findFiles, getContentHash } from "@embeddable.com/sdk-utils";
|
|
6
6
|
import { ResolvedEmbeddableConfig } from "./defineConfig";
|
|
7
|
+
import {
|
|
8
|
+
findFiles,
|
|
9
|
+
getContentHash,
|
|
10
|
+
getComponentLibraryConfig,
|
|
11
|
+
} from "@embeddable.com/sdk-utils";
|
|
12
|
+
|
|
13
|
+
import fg from "fast-glob";
|
|
14
|
+
|
|
15
|
+
// The same config you already have
|
|
7
16
|
const config = {
|
|
8
17
|
client: {
|
|
9
18
|
srcDir: "src",
|
|
10
19
|
rootDir: "root",
|
|
11
20
|
buildDir: "build",
|
|
21
|
+
componentLibraries: [],
|
|
12
22
|
},
|
|
13
23
|
outputOptions: {
|
|
14
24
|
typesEntryPointFilename: "typesEntryPointFilename",
|
|
@@ -31,12 +41,20 @@ vi.mock("ora", () => ({
|
|
|
31
41
|
vi.mock("@embeddable.com/sdk-utils", () => ({
|
|
32
42
|
findFiles: vi.fn(),
|
|
33
43
|
getContentHash: vi.fn(),
|
|
44
|
+
getComponentLibraryConfig: vi.fn(),
|
|
34
45
|
}));
|
|
35
46
|
|
|
36
|
-
vi.mock("node:path", () =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
vi.mock("node:path", async () => {
|
|
48
|
+
const actualPath =
|
|
49
|
+
await vi.importActual<typeof import("node:path")>("node:path");
|
|
50
|
+
return {
|
|
51
|
+
...actualPath,
|
|
52
|
+
resolve: vi.fn(),
|
|
53
|
+
relative: vi.fn(),
|
|
54
|
+
join: vi.fn(),
|
|
55
|
+
dirname: vi.fn(),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
40
58
|
|
|
41
59
|
vi.mock("node:fs/promises", () => ({
|
|
42
60
|
writeFile: vi.fn(),
|
|
@@ -49,12 +67,30 @@ vi.mock("vite", () => ({
|
|
|
49
67
|
build: vi.fn(),
|
|
50
68
|
}));
|
|
51
69
|
|
|
70
|
+
vi.mock("fast-glob", () => ({
|
|
71
|
+
default: {
|
|
72
|
+
sync: vi.fn(),
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
|
|
52
76
|
describe("buildTypes", () => {
|
|
53
77
|
beforeEach(() => {
|
|
78
|
+
vi.clearAllMocks();
|
|
79
|
+
|
|
54
80
|
vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]);
|
|
55
81
|
vi.mocked(path.relative).mockReturnValue("relativePath");
|
|
56
82
|
vi.mocked(path.resolve).mockReturnValue("resolvedPath");
|
|
57
83
|
vi.mocked(fs.readFile).mockResolvedValue("fileContent");
|
|
84
|
+
vi.mocked(getContentHash).mockReturnValue("somehash");
|
|
85
|
+
// By default, no libraries => no fast-glob usage
|
|
86
|
+
vi.mocked(fg.sync).mockReturnValue([]);
|
|
87
|
+
|
|
88
|
+
// We also default to returning an empty config from getComponentLibraryConfig
|
|
89
|
+
vi.mocked(getComponentLibraryConfig).mockReturnValue({
|
|
90
|
+
libraryName: "",
|
|
91
|
+
include: [],
|
|
92
|
+
exclude: [],
|
|
93
|
+
});
|
|
58
94
|
});
|
|
59
95
|
|
|
60
96
|
it("should build types", async () => {
|
|
@@ -98,4 +134,70 @@ import '../relativePath';`,
|
|
|
98
134
|
expect(getContentHash).not.toHaveBeenCalled();
|
|
99
135
|
expect(fs.rename).not.toHaveBeenCalled();
|
|
100
136
|
});
|
|
137
|
+
it("should import types from installed libraries if present", async () => {
|
|
138
|
+
const configWithLibrary = {
|
|
139
|
+
...config,
|
|
140
|
+
client: {
|
|
141
|
+
...config.client,
|
|
142
|
+
componentLibraries: [
|
|
143
|
+
{
|
|
144
|
+
/* any library config object */
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
} as unknown as ResolvedEmbeddableConfig;
|
|
149
|
+
|
|
150
|
+
// Mock getComponentLibraryConfig => returns libraryName: "my-lib"
|
|
151
|
+
vi.mocked(getComponentLibraryConfig).mockReturnValue({
|
|
152
|
+
libraryName: "my-lib",
|
|
153
|
+
include: [],
|
|
154
|
+
exclude: [],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
vi.mocked(fg.sync).mockReturnValue([
|
|
158
|
+
"/fake/path/node_modules/my-lib/dist/embeddable-types-abc123.js",
|
|
159
|
+
"/fake/path/node_modules/my-lib/dist/embeddable-types-xyz987.js",
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
// Now run
|
|
163
|
+
await buildTypes(configWithLibrary);
|
|
164
|
+
|
|
165
|
+
const written = vi.mocked(fs.writeFile).mock.calls[0][1];
|
|
166
|
+
|
|
167
|
+
expect(written).toContain(
|
|
168
|
+
`import 'my-lib/dist/embeddable-types-abc123.js';`,
|
|
169
|
+
);
|
|
170
|
+
expect(written).toContain(
|
|
171
|
+
`import 'my-lib/dist/embeddable-types-xyz987.js';`,
|
|
172
|
+
);
|
|
173
|
+
expect(written).toContain(`import '../relativePath';`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should throw if an error occurs when loading a library", async () => {
|
|
177
|
+
const configWithLibrary = {
|
|
178
|
+
...config,
|
|
179
|
+
client: {
|
|
180
|
+
...config.client,
|
|
181
|
+
componentLibraries: [
|
|
182
|
+
{
|
|
183
|
+
/* any library config object */
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
} as unknown as ResolvedEmbeddableConfig;
|
|
188
|
+
|
|
189
|
+
vi.mocked(getComponentLibraryConfig).mockReturnValue({
|
|
190
|
+
libraryName: "my-broken-lib",
|
|
191
|
+
include: [],
|
|
192
|
+
exclude: [],
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
vi.mocked(fg.sync).mockImplementation(() => {
|
|
196
|
+
throw new Error("some library error");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await expect(buildTypes(configWithLibrary)).rejects.toThrow(
|
|
200
|
+
"some library error",
|
|
201
|
+
);
|
|
202
|
+
});
|
|
101
203
|
});
|