@embeddable.com/sdk-core 3.9.1 → 3.9.3

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.
@@ -0,0 +1,9 @@
1
+ export declare const ERROR_LOG_FILE: string;
2
+ interface LogErrorParams {
3
+ command: string;
4
+ breadcrumbs: string[];
5
+ error: unknown;
6
+ }
7
+ export declare function initLogger(command: string): Promise<void>;
8
+ export declare function logError({ command, breadcrumbs, error, }: LogErrorParams): Promise<void>;
9
+ export {};
package/lib/utils.d.ts CHANGED
@@ -20,4 +20,4 @@ export declare const removeBuildSuccessFlag: () => Promise<void>;
20
20
  * Check if the build was successful
21
21
  */
22
22
  export declare const checkBuildSuccess: () => Promise<boolean>;
23
- export declare const getPackageVersion: (packageName: string) => any;
23
+ export declare const getSDKVersions: () => Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/sdk-core",
3
- "version": "3.9.1",
3
+ "version": "3.9.3",
4
4
  "description": "Core Embeddable SDK module responsible for web-components bundling and publishing.",
5
5
  "keywords": [
6
6
  "embeddable",
package/src/build.test.ts CHANGED
@@ -5,11 +5,17 @@ import buildTypes from "./buildTypes";
5
5
  import provideConfig from "./provideConfig";
6
6
  import generate from "./generate";
7
7
  import cleanup from "./cleanup";
8
+ import { initLogger, logError } from "./logger";
8
9
 
9
10
  // @ts-ignore
10
11
  import reportErrorToRollbar from "./rollbar.mjs";
11
12
  import { storeBuildSuccessFlag } from "./utils";
12
13
 
14
+ vi.mock("./logger", () => ({
15
+ initLogger: vi.fn().mockResolvedValue(undefined),
16
+ logError: vi.fn().mockResolvedValue(undefined),
17
+ }));
18
+
13
19
  const mockPlugin = {
14
20
  validate: vi.fn(),
15
21
  build: vi.fn(),
package/src/build.ts CHANGED
@@ -11,10 +11,15 @@ import {
11
11
  removeBuildSuccessFlag,
12
12
  storeBuildSuccessFlag,
13
13
  } from "./utils";
14
+ import { initLogger, logError } from "./logger";
14
15
 
15
16
  export default async () => {
17
+ await initLogger("build");
18
+ const breadcrumbs: string[] = [];
19
+
16
20
  try {
17
21
  checkNodeVersion();
22
+ breadcrumbs.push("checkNodeVersion");
18
23
  removeBuildSuccessFlag();
19
24
 
20
25
  const config = await provideConfig();
@@ -28,16 +33,22 @@ export default async () => {
28
33
  for (const getPlugin of config.plugins) {
29
34
  const plugin = getPlugin();
30
35
 
36
+ breadcrumbs.push(`${plugin.pluginName}: validate`);
31
37
  await plugin.validate(config);
38
+ breadcrumbs.push(`${plugin.pluginName}: build`);
32
39
  await plugin.build(config);
40
+ breadcrumbs.push(`${plugin.pluginName}: cleanup`);
33
41
  await plugin.cleanup(config);
34
42
  }
35
43
 
36
44
  // NOTE: likely this will be called inside the loop above if we decide to support clients with mixed frameworks simultaneously.
45
+ breadcrumbs.push("generate");
37
46
  await generate(config, "sdk-react");
47
+ breadcrumbs.push("cleanup");
38
48
  await cleanup(config);
39
49
  await storeBuildSuccessFlag();
40
50
  } catch (error: any) {
51
+ await logError({ command: "build", breadcrumbs, error });
41
52
  await reportErrorToRollbar(error);
42
53
  console.log(error);
43
54
  process.exit(1);
package/src/cleanup.ts CHANGED
@@ -1,7 +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
+ import { getSDKVersions } from "./utils";
5
5
 
6
6
  export default async (ctx: any) => {
7
7
  await extractBuild(ctx);
@@ -26,24 +26,7 @@ export async function createManifest({
26
26
  editorsMetaFileName,
27
27
  stencilWrapperFileName,
28
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
- );
29
+ const sdkVersions = getSDKVersions();
47
30
  // identify user's package manager and its version
48
31
  let packageManager = "npm";
49
32
  if (process.env.npm_config_user_agent?.includes("yarn")) {
package/src/dev.test.ts CHANGED
@@ -9,6 +9,7 @@ import { checkNodeVersion } from "./utils";
9
9
  import { createManifest } from "./cleanup";
10
10
  import prepare from "./prepare";
11
11
  import { WebSocketServer } from "ws";
12
+ import { initLogger, logError } from "./logger";
12
13
 
13
14
  // Mock dependencies
14
15
  vi.mock("./buildTypes", () => ({ default: vi.fn() }));
@@ -31,6 +32,10 @@ vi.mock("./cleanup", () => ({ createManifest: vi.fn() }));
31
32
  vi.mock("node:http", () => ({
32
33
  createServer: vi.fn(() => ({ listen: vi.fn() })),
33
34
  }));
35
+ vi.mock("./logger", () => ({
36
+ initLogger: vi.fn(),
37
+ logError: vi.fn(),
38
+ }));
34
39
 
35
40
  const mockConfig = {
36
41
  client: {
@@ -100,4 +105,24 @@ describe("dev command", () => {
100
105
 
101
106
  await expect.poll(() => chokidar.watch).toBeCalledTimes(2);
102
107
  });
108
+
109
+ it("should log errors and exit on failure", async () => {
110
+ const error = new Error("Test error");
111
+ const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
112
+ throw new Error("process.exit");
113
+ });
114
+
115
+ vi.mocked(checkNodeVersion).mockImplementation(() => {
116
+ throw error;
117
+ });
118
+
119
+ await expect(dev()).rejects.toThrow("process.exit");
120
+
121
+ expect(logError).toHaveBeenCalledWith({
122
+ command: "dev",
123
+ breadcrumbs: ["run dev"],
124
+ error,
125
+ });
126
+ expect(exitSpy).toHaveBeenCalledWith(1);
127
+ });
103
128
  });
package/src/dev.ts CHANGED
@@ -28,6 +28,7 @@ import { createManifest } from "./cleanup";
28
28
  import { selectWorkspace } from "./workspaceUtils";
29
29
  import * as fs from "fs";
30
30
  const minimist = require("minimist");
31
+ import { initLogger, logError } from "./logger";
31
32
 
32
33
  const oraP = import("ora");
33
34
  let wss: WSServer;
@@ -69,130 +70,152 @@ const chokidarWatchOptions = {
69
70
  };
70
71
 
71
72
  export default async () => {
72
- checkNodeVersion();
73
- addToGitingore();
73
+ await initLogger("dev");
74
+ const breadcrumbs: string[] = [];
74
75
 
75
- ora = (await oraP).default;
76
-
77
- process.on("warning", (e) => console.warn(e.stack));
76
+ try {
77
+ breadcrumbs.push("run dev");
78
+ checkNodeVersion();
79
+ addToGitingore();
78
80
 
79
- const logger = createNodeLogger();
80
- const sys = createNodeSys({ process });
81
+ ora = (await oraP).default;
81
82
 
82
- const defaultConfig = await provideConfig();
83
+ process.on("warning", (e) => console.warn(e.stack));
83
84
 
84
- const buildDir = path.resolve(defaultConfig.client.rootDir, BUILD_DEV_DIR);
85
+ const logger = createNodeLogger();
86
+ const sys = createNodeSys({ process });
85
87
 
86
- const config = {
87
- ...defaultConfig,
88
- dev: {
89
- watch: true,
90
- logger,
91
- sys,
92
- },
93
- client: {
94
- ...defaultConfig.client,
95
- buildDir,
96
- componentDir: path.resolve(buildDir, "component"),
97
- stencilBuild: path.resolve(buildDir, "dist", "embeddable-wrapper"),
98
- tmpDir: path.resolve(defaultConfig.client.rootDir, ".embeddable-dev-tmp"),
99
- },
100
- };
88
+ const defaultConfig = await provideConfig();
101
89
 
102
- await prepare(config);
90
+ const buildDir = path.resolve(defaultConfig.client.rootDir, BUILD_DEV_DIR);
103
91
 
104
- const finalhandler = require("finalhandler");
105
- const serveStatic = require("serve-static");
92
+ const config = {
93
+ ...defaultConfig,
94
+ dev: {
95
+ watch: true,
96
+ logger,
97
+ sys,
98
+ },
99
+ client: {
100
+ ...defaultConfig.client,
101
+ buildDir,
102
+ componentDir: path.resolve(buildDir, "component"),
103
+ stencilBuild: path.resolve(buildDir, "dist", "embeddable-wrapper"),
104
+ tmpDir: path.resolve(
105
+ defaultConfig.client.rootDir,
106
+ ".embeddable-dev-tmp",
107
+ ),
108
+ },
109
+ };
106
110
 
107
- const serve = serveStatic(config.client.buildDir);
111
+ breadcrumbs.push("prepare config");
112
+ await prepare(config);
108
113
 
109
- const workspacePreparation = ora("Preparing workspace...").start();
114
+ const finalhandler = require("finalhandler");
115
+ const serveStatic = require("serve-static");
110
116
 
111
- try {
112
- previewWorkspace = await getPreviewWorkspace(workspacePreparation, config);
113
- } catch (e: any) {
114
- workspacePreparation.fail(
115
- e.response?.data?.errorMessage || "Unknown error: " + e.message,
116
- );
117
- process.exit(1);
118
- }
117
+ const serve = serveStatic(config.client.buildDir);
119
118
 
120
- workspacePreparation.succeed("Workspace is ready");
119
+ const workspacePreparation = ora("Preparing workspace...").start();
121
120
 
122
- const server = http.createServer(
123
- (request: IncomingMessage, res: ServerResponse) => {
124
- res.setHeader("Access-Control-Allow-Origin", "*");
125
- res.setHeader(
126
- "Access-Control-Allow-Methods",
127
- "GET, POST, PUT, DELETE, OPTIONS",
121
+ breadcrumbs.push("get preview workspace");
122
+ try {
123
+ previewWorkspace = await getPreviewWorkspace(
124
+ workspacePreparation,
125
+ config,
128
126
  );
129
- res.setHeader(
130
- "Access-Control-Allow-Headers",
131
- "Content-Type, Authorization",
132
- );
133
-
134
- if (request.method === "OPTIONS") {
135
- // Respond to OPTIONS requests with just the CORS headers and a 200 status code
136
- res.writeHead(200);
137
- res.end();
138
- return;
139
- }
140
-
141
- const done = finalhandler(request, res);
142
-
143
- if (request.url?.endsWith(GLOBAL_CSS)) {
144
- res.writeHead(200, { "Content-Type": "text/css" });
145
- res.end(fs.readFileSync(config.client.globalCss));
146
- return;
147
- }
148
-
149
- serve(request, res, done);
150
- },
151
- );
152
-
153
- wss = new WebSocketServer({ server });
154
-
155
- server.listen(SERVER_PORT, async () => {
156
- const watchers: Array<RollupWatcher | FSWatcher> = [];
157
- if (sys?.onProcessInterrupt) {
158
- sys.onProcessInterrupt(
159
- async () => await onClose(server, sys, watchers, config),
127
+ } catch (e: any) {
128
+ workspacePreparation.fail(
129
+ e.response?.data?.errorMessage || "Unknown error: " + e.message,
160
130
  );
131
+ process.exit(1);
161
132
  }
162
133
 
163
- await createManifest({
164
- ctx: {
165
- ...config,
166
- client: {
167
- ...config.client,
168
- tmpDir: buildDir,
169
- },
134
+ workspacePreparation.succeed("Workspace is ready");
135
+
136
+ const server = http.createServer(
137
+ (request: IncomingMessage, res: ServerResponse) => {
138
+ res.setHeader("Access-Control-Allow-Origin", "*");
139
+ res.setHeader(
140
+ "Access-Control-Allow-Methods",
141
+ "GET, POST, PUT, DELETE, OPTIONS",
142
+ );
143
+ res.setHeader(
144
+ "Access-Control-Allow-Headers",
145
+ "Content-Type, Authorization",
146
+ );
147
+
148
+ if (request.method === "OPTIONS") {
149
+ // Respond to OPTIONS requests with just the CORS headers and a 200 status code
150
+ res.writeHead(200);
151
+ res.end();
152
+ return;
153
+ }
154
+
155
+ const done = finalhandler(request, res);
156
+
157
+ try {
158
+ if (request.url?.endsWith(GLOBAL_CSS)) {
159
+ res.writeHead(200, { "Content-Type": "text/css" });
160
+ res.end(fs.readFileSync(config.client.globalCss));
161
+ return;
162
+ }
163
+ } catch {}
164
+
165
+ serve(request, res, done);
170
166
  },
171
- typesFileName: "embeddable-types.js",
172
- stencilWrapperFileName: "embeddable-wrapper.js",
173
- metaFileName: "embeddable-components-meta.js",
174
- editorsMetaFileName: "embeddable-editors-meta.js",
175
- });
176
-
177
- await sendDataModelsAndSecurityContextsChanges(config);
167
+ );
178
168
 
179
- for (const getPlugin of config.plugins) {
180
- const plugin = getPlugin();
169
+ wss = new WebSocketServer({ server });
181
170
 
182
- await plugin.validate(config);
183
- const watcher = await plugin.build(config);
171
+ server.listen(SERVER_PORT, async () => {
172
+ const watchers: Array<RollupWatcher | FSWatcher> = [];
173
+ if (sys?.onProcessInterrupt) {
174
+ sys.onProcessInterrupt(
175
+ async () => await onClose(server, sys, watchers, config),
176
+ );
177
+ }
184
178
 
185
- await configureWatcher(watcher, config);
186
- watchers.push(watcher);
187
- }
179
+ breadcrumbs.push("create manifest");
180
+ await createManifest({
181
+ ctx: {
182
+ ...config,
183
+ client: {
184
+ ...config.client,
185
+ tmpDir: buildDir,
186
+ },
187
+ },
188
+ typesFileName: "embeddable-types.js",
189
+ stencilWrapperFileName: "embeddable-wrapper.js",
190
+ metaFileName: "embeddable-components-meta.js",
191
+ editorsMetaFileName: "embeddable-editors-meta.js",
192
+ });
193
+
194
+ await sendDataModelsAndSecurityContextsChanges(config);
195
+
196
+ for (const getPlugin of config.plugins) {
197
+ const plugin = getPlugin();
198
+
199
+ breadcrumbs.push("validate plugin");
200
+ await plugin.validate(config);
201
+ breadcrumbs.push("build plugin");
202
+ const watcher = await plugin.build(config);
203
+ breadcrumbs.push("configure watcher");
204
+ await configureWatcher(watcher, config);
205
+ watchers.push(watcher);
206
+ }
188
207
 
189
- const dataModelAndSecurityContextWatch =
190
- dataModelAndSecurityContextWatcher(config);
208
+ const dataModelAndSecurityContextWatch =
209
+ dataModelAndSecurityContextWatcher(config);
191
210
 
192
- const customGlobalCssWatch = globalCssWatcher(config);
193
- watchers.push(dataModelAndSecurityContextWatch);
194
- watchers.push(customGlobalCssWatch);
195
- });
211
+ const customGlobalCssWatch = globalCssWatcher(config);
212
+ watchers.push(dataModelAndSecurityContextWatch);
213
+ watchers.push(customGlobalCssWatch);
214
+ });
215
+ } catch (error: any) {
216
+ await logError({ command: "dev", breadcrumbs, error });
217
+ process.exit(1);
218
+ }
196
219
  };
197
220
 
198
221
  const configureWatcher = async (watcher: RollupWatcher, ctx: any) => {
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2
+ import { initLogger, logError, ERROR_LOG_FILE } from "./logger";
3
+ import * as fs from "node:fs/promises";
4
+
5
+ vi.mock("fs/promises", () => ({
6
+ readFile: vi.fn(),
7
+ appendFile: vi.fn(),
8
+ access: vi.fn(),
9
+ mkdir: vi.fn(),
10
+ }));
11
+
12
+ describe("Logger", () => {
13
+ describe("initLogger", () => {
14
+ it("should create log directory if it does not exist", async () => {
15
+ await initLogger("test");
16
+ expect(fs.mkdir).toHaveBeenCalledWith(
17
+ expect.stringContaining(".embeddable/logs"),
18
+ { recursive: true },
19
+ );
20
+ });
21
+
22
+ it("should handle errors when creating log directory", async () => {
23
+ const consoleErrorSpy = vi
24
+ .spyOn(console, "error")
25
+ .mockImplementation(() => {});
26
+ vi.mocked(fs.mkdir).mockRejectedValueOnce(
27
+ new Error("Failed to create directory"),
28
+ );
29
+
30
+ await initLogger("test");
31
+
32
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
33
+ "Failed to create log directory:",
34
+ expect.any(Error),
35
+ );
36
+ });
37
+ });
38
+
39
+ describe("logError", () => {
40
+ it("should append error message to log file", async () => {
41
+ const error = new Error("Test error");
42
+ await logError({
43
+ command: "test",
44
+ breadcrumbs: ["step1", "step2"],
45
+ error,
46
+ });
47
+
48
+ expect(fs.appendFile).toHaveBeenCalledWith(
49
+ ERROR_LOG_FILE,
50
+ expect.stringContaining(
51
+ "Command: test\nBreadcrumbs: step1 > step2\nError: Error: Test error",
52
+ ),
53
+ );
54
+ });
55
+
56
+ it("should handle non-Error objects", async () => {
57
+ await logError({
58
+ command: "test",
59
+ breadcrumbs: ["step1"],
60
+ error: "String error",
61
+ });
62
+
63
+ expect(fs.appendFile).toHaveBeenCalledWith(
64
+ ERROR_LOG_FILE,
65
+ expect.stringContaining(
66
+ "Command: test\nBreadcrumbs: step1\nError: String error",
67
+ ),
68
+ );
69
+ });
70
+
71
+ it("should log to console when error occurs", async () => {
72
+ const consoleErrorSpy = vi
73
+ .spyOn(console, "error")
74
+ .mockImplementation(() => {});
75
+ await logError({ command: "test", breadcrumbs: [], error: "Test error" });
76
+
77
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
78
+ expect.stringContaining("An error occurred during test"),
79
+ );
80
+ });
81
+
82
+ it("should handle errors when writing to log file", async () => {
83
+ const consoleErrorSpy = vi
84
+ .spyOn(console, "error")
85
+ .mockImplementation(() => {});
86
+ vi.mocked(fs.appendFile).mockRejectedValueOnce(
87
+ new Error("Failed to write"),
88
+ );
89
+
90
+ await logError({ command: "test", breadcrumbs: [], error: "Test error" });
91
+
92
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
93
+ "Failed to write to log file:",
94
+ expect.any(Error),
95
+ );
96
+ });
97
+ });
98
+ });
package/src/logger.ts ADDED
@@ -0,0 +1,116 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { getSDKVersions } from "./utils";
4
+
5
+ const LOG_DIR = path.join(process.cwd(), ".embeddable", "logs");
6
+ export const ERROR_LOG_FILE = path.join(LOG_DIR, "error.log");
7
+ const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5 MB
8
+ const MAX_LOG_FILES = 5;
9
+
10
+ interface LogEntry {
11
+ timestamp: string;
12
+ command: string;
13
+ breadcrumbs: string[];
14
+ error: string;
15
+ }
16
+
17
+ interface LogErrorParams {
18
+ command: string;
19
+ breadcrumbs: string[];
20
+ error: unknown;
21
+ }
22
+
23
+ export async function initLogger(command: string) {
24
+ try {
25
+ await fs.mkdir(LOG_DIR, { recursive: true });
26
+ } catch (error) {
27
+ console.error("Failed to create log directory:", error);
28
+ }
29
+
30
+ setupGlobalErrorHandlers(command);
31
+ }
32
+
33
+ export async function logError({
34
+ command,
35
+ breadcrumbs,
36
+ error,
37
+ }: LogErrorParams) {
38
+ const sdkVersions = getSDKVersions();
39
+ const logEntry: LogEntry = {
40
+ timestamp: new Date().toISOString(),
41
+ command,
42
+ breadcrumbs,
43
+ error:
44
+ error instanceof Error
45
+ ? `${error.name}: ${error.message}\n${error.stack}`
46
+ : String(error),
47
+ };
48
+
49
+ const logMessage = `
50
+ [${logEntry.timestamp}] Command: ${logEntry.command}
51
+ Breadcrumbs: ${logEntry.breadcrumbs.join(" > ")}
52
+ Error: ${logEntry.error}
53
+ OS: ${process.platform}
54
+ Node: ${process.version}
55
+ SDK Versions: ${JSON.stringify(sdkVersions, null, 2)}
56
+ ----------------------------------------
57
+ `;
58
+
59
+ try {
60
+ await rotateLogIfNeeded();
61
+ await fs.appendFile(ERROR_LOG_FILE, logMessage);
62
+ console.error(
63
+ `An error occurred during ${command}. Check the log file for details: ${ERROR_LOG_FILE}`,
64
+ );
65
+ } catch (error) {
66
+ console.error("Failed to write to log file:", error);
67
+ }
68
+ }
69
+
70
+ async function rotateLogIfNeeded() {
71
+ try {
72
+ const stats = await fs.stat(ERROR_LOG_FILE);
73
+ if (stats.size < MAX_LOG_SIZE) {
74
+ return;
75
+ }
76
+
77
+ for (let i = MAX_LOG_FILES - 1; i > 0; i--) {
78
+ const oldFile = `${ERROR_LOG_FILE}.${i}`;
79
+ const newFile = `${ERROR_LOG_FILE}.${i + 1}`;
80
+ try {
81
+ await fs.rename(oldFile, newFile);
82
+ } catch (error) {
83
+ // Ignore error if file doesn't exist
84
+ }
85
+ }
86
+
87
+ await fs.rename(ERROR_LOG_FILE, `${ERROR_LOG_FILE}.1`);
88
+ await fs.writeFile(ERROR_LOG_FILE, ""); // Create a new empty log file
89
+ } catch (error: any) {
90
+ if (error.code !== "ENOENT") {
91
+ console.error("Error rotating log file:", error);
92
+ }
93
+ }
94
+ }
95
+
96
+ function setupGlobalErrorHandlers(command: string) {
97
+ process.on("uncaughtException", async (error) => {
98
+ await logError({ command, breadcrumbs: ["uncaughtException"], error });
99
+ console.error(
100
+ "An uncaught error occurred. Check the log file for details.",
101
+ );
102
+ process.exit(1);
103
+ });
104
+
105
+ process.on("unhandledRejection", async (reason) => {
106
+ await logError({
107
+ command,
108
+ breadcrumbs: ["unhandledRejection"],
109
+ error: reason as Error | string,
110
+ });
111
+ console.error(
112
+ "An unhandled rejection occurred. Check the log file for details.",
113
+ );
114
+ process.exit(1);
115
+ });
116
+ }
package/src/login.test.ts CHANGED
@@ -8,6 +8,11 @@ import { CREDENTIALS_DIR, CREDENTIALS_FILE } from "./credentials";
8
8
  import { http, HttpResponse } from "msw";
9
9
  import { AxiosError } from "axios";
10
10
 
11
+ vi.mock("./logger", () => ({
12
+ initLogger: vi.fn(),
13
+ logError: vi.fn(),
14
+ }));
15
+
11
16
  vi.mock("fs/promises", () => ({
12
17
  readFile: vi.fn(),
13
18
  writeFile: vi.fn(),
package/src/login.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import axios from "axios";
3
3
  import provideConfig from "./provideConfig";
4
+ import { initLogger, logError } from "./logger";
4
5
 
5
6
  // @ts-ignore
6
7
  import reportErrorToRollbar from "./rollbar.mjs";
@@ -9,14 +10,18 @@ const oraP = import("ora");
9
10
  const openP = import("open");
10
11
 
11
12
  export default async () => {
13
+ await initLogger("login");
14
+ const breadcrumbs: string[] = [];
12
15
  const ora = (await oraP).default;
13
16
  const authenticationSpinner = ora("Waiting for code verification...").start();
14
17
 
15
18
  try {
16
19
  const open = (await openP).default;
17
20
  const config = await provideConfig();
21
+ breadcrumbs.push("provideConfig");
18
22
 
19
23
  await resolveFiles();
24
+ breadcrumbs.push("resolveFiles");
20
25
 
21
26
  const deviceCodePayload = {
22
27
  client_id: config.authClientId,
@@ -70,8 +75,9 @@ export default async () => {
70
75
  await sleep(deviceCodeResponse.data["interval"] * 1000);
71
76
  }
72
77
  }
73
- } catch (error) {
78
+ } catch (error: unknown) {
74
79
  authenticationSpinner.fail("Authentication failed. Please try again.");
80
+ await logError({ command: "login", breadcrumbs, error });
75
81
  await reportErrorToRollbar(error);
76
82
  console.log(error);
77
83