@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/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
@@ -19,3 +20,4 @@ export declare const removeBuildSuccessFlag: () => Promise<void>;
19
20
  * Check if the build was successful
20
21
  */
21
22
  export declare const checkBuildSuccess: () => Promise<boolean>;
23
+ export declare const getPackageVersion: (packageName: string) => any;
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.0",
3
+ "version": "3.4.0",
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.1",
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
@@ -1,6 +1,7 @@
1
1
  import { findFiles } from "@embeddable.com/sdk-utils";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
+ import { getPackageVersion } from "./utils";
4
5
 
5
6
  export default async (ctx: any) => {
6
7
  await extractBuild(ctx);
@@ -10,57 +11,127 @@ export default async (ctx: any) => {
10
11
  await moveBuildTOBuildDir(ctx);
11
12
  };
12
13
 
14
+ type ManifestArgs = {
15
+ ctx: any;
16
+ typesFileName: string;
17
+ metaFileName: string;
18
+ editorsMetaFileName: string;
19
+ stencilWrapperFileName: string;
20
+ };
21
+
22
+ export async function createManifest({
23
+ ctx,
24
+ typesFileName,
25
+ metaFileName,
26
+ editorsMetaFileName,
27
+ stencilWrapperFileName,
28
+ }: ManifestArgs) {
29
+ const packageNames = [
30
+ "@embeddable.com/core",
31
+ "@embeddable.com/react",
32
+ "@embeddable.com/sdk-core",
33
+ "@embeddable.com/sdk-react",
34
+ "@embeddable.com/sdk-utils",
35
+ ];
36
+
37
+ const sdkVersions = packageNames.reduce<Record<string, string>>(
38
+ (acc, packageName) => {
39
+ const version = getPackageVersion(packageName);
40
+ if (version) {
41
+ acc[packageName] = version;
42
+ }
43
+ return acc;
44
+ },
45
+ {},
46
+ );
47
+ // identify user's package manager and its version
48
+ let packageManager = "npm";
49
+ if (process.env.npm_config_user_agent?.includes("yarn")) {
50
+ packageManager = "yarn";
51
+ }
52
+ if (process.env.npm_config_user_agent?.includes("pnpm")) {
53
+ packageManager = "pnpm";
54
+ }
55
+
56
+ const packageManagerVersion =
57
+ process.env.npm_config_user_agent?.match(/(\d+\.\d+\.\d+)/)?.[0] ||
58
+ "unknown";
59
+
60
+ // write manifest file with files with hash
61
+ const manifest = {
62
+ entryFiles: {
63
+ "embeddable-types.js": typesFileName,
64
+ "embeddable-components-meta.js": metaFileName,
65
+ "embeddable-editors-meta.js": editorsMetaFileName,
66
+ "embeddable-wrapper.esm.js": stencilWrapperFileName,
67
+ },
68
+ metadata: {
69
+ nodeVersion: process.version,
70
+ platform: process.platform,
71
+ sdkVersions,
72
+ packageManager,
73
+ packageManagerVersion,
74
+ },
75
+ };
76
+
77
+ await fs.writeFile(
78
+ path.join(ctx.client.tmpDir, "embeddable-manifest.json"),
79
+ JSON.stringify(manifest),
80
+ );
81
+ }
82
+
13
83
  async function extractBuild(ctx: any) {
14
- const [[, stencilWrapperFilePath]] = await findFiles(
84
+ const stencilBuildFiles = await findFiles(
15
85
  ctx.client.stencilBuild,
16
86
  /embeddable-wrapper.esm-[a-z0-9]+\.js/,
17
87
  );
18
88
 
89
+ const [[, stencilWrapperFilePath]] = stencilBuildFiles || [];
90
+
19
91
  const stencilWrapperFileName = path.basename(stencilWrapperFilePath);
20
92
  await fs.rename(
21
93
  path.resolve(ctx.client.buildDir, ctx.client.stencilBuild),
22
94
  ctx.client.tmpDir,
23
95
  );
24
96
 
25
- const [[, typesFilePath]] = await findFiles(
97
+ const typesBuildFiles = await findFiles(
26
98
  ctx.client.buildDir,
27
99
  /embeddable-types-[a-z0-9]+\.js/,
28
100
  );
29
101
 
102
+ const [[, typesFilePath]] = typesBuildFiles || [];
103
+
30
104
  const typesFileName = path.basename(typesFilePath);
31
105
  await fs.rename(typesFilePath, path.join(ctx.client.tmpDir, typesFileName));
32
106
 
33
- const [[, metaFilePath]] = await findFiles(
107
+ const metaBuildFiles = await findFiles(
34
108
  ctx.client.buildDir,
35
109
  /embeddable-components-meta-[a-z0-9]+\.js/,
36
110
  );
111
+
112
+ const [[, metaFilePath]] = metaBuildFiles || [];
37
113
  const metaFileName = path.basename(metaFilePath);
38
114
  await fs.rename(metaFilePath, path.join(ctx.client.tmpDir, metaFileName));
39
115
 
40
- const [[, editorsMetaFilePath]] = await findFiles(
116
+ const editorsMetaBuildFiles = await findFiles(
41
117
  ctx.client.buildDir,
42
118
  /embeddable-editors-meta-[a-z0-9]+\.js/,
43
119
  );
120
+
121
+ const [[, editorsMetaFilePath]] = editorsMetaBuildFiles || [];
44
122
  const editorsMetaFileName = path.basename(editorsMetaFilePath);
45
123
  await fs.rename(
46
124
  editorsMetaFilePath,
47
125
  path.join(ctx.client.tmpDir, editorsMetaFileName),
48
126
  );
49
127
 
50
- // write manifest file with files with hash
51
- const manifest = {
52
- entryFiles: {
53
- "embeddable-types.js": typesFileName,
54
- "embeddable-components-meta.js": metaFileName,
55
- "embeddable-editors-meta.js": editorsMetaFileName,
56
- "embeddable-wrapper.esm.js": stencilWrapperFileName,
57
- },
58
- };
59
-
60
- await fs.writeFile(
61
- path.join(ctx.client.tmpDir, "embeddable-manifest.json"),
62
- JSON.stringify(manifest),
63
- );
128
+ await createManifest({
129
+ ctx,
130
+ typesFileName,
131
+ metaFileName,
132
+ editorsMetaFileName,
133
+ stencilWrapperFileName,
134
+ });
64
135
  }
65
136
 
66
137
  async function removeObsoleteDir(dir: string) {
@@ -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) {