@embeddable.com/sdk-core 3.9.2 → 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.2",
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,132 +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
+ try {
77
+ breadcrumbs.push("run dev");
78
+ checkNodeVersion();
79
+ addToGitingore();
76
80
 
77
- process.on("warning", (e) => console.warn(e.stack));
81
+ ora = (await oraP).default;
78
82
 
79
- const logger = createNodeLogger();
80
- const sys = createNodeSys({ process });
83
+ process.on("warning", (e) => console.warn(e.stack));
81
84
 
82
- const defaultConfig = await provideConfig();
85
+ const logger = createNodeLogger();
86
+ const sys = createNodeSys({ process });
83
87
 
84
- const buildDir = path.resolve(defaultConfig.client.rootDir, BUILD_DEV_DIR);
88
+ const defaultConfig = await provideConfig();
85
89
 
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
- };
90
+ const buildDir = path.resolve(defaultConfig.client.rootDir, BUILD_DEV_DIR);
101
91
 
102
- await prepare(config);
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
+ };
103
110
 
104
- const finalhandler = require("finalhandler");
105
- const serveStatic = require("serve-static");
111
+ breadcrumbs.push("prepare config");
112
+ await prepare(config);
106
113
 
107
- const serve = serveStatic(config.client.buildDir);
114
+ const finalhandler = require("finalhandler");
115
+ const serveStatic = require("serve-static");
108
116
 
109
- const workspacePreparation = ora("Preparing workspace...").start();
117
+ const serve = serveStatic(config.client.buildDir);
110
118
 
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
- }
119
+ const workspacePreparation = ora("Preparing workspace...").start();
119
120
 
120
- workspacePreparation.succeed("Workspace is ready");
121
-
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",
127
+ } catch (e: any) {
128
+ workspacePreparation.fail(
129
+ e.response?.data?.errorMessage || "Unknown error: " + e.message,
132
130
  );
131
+ process.exit(1);
132
+ }
133
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
- try {
144
- if (request.url?.endsWith(GLOBAL_CSS)) {
145
- res.writeHead(200, { "Content-Type": "text/css" });
146
- res.end(fs.readFileSync(config.client.globalCss));
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();
147
152
  return;
148
153
  }
149
- } catch {}
150
154
 
151
- serve(request, res, done);
152
- },
153
- );
155
+ const done = finalhandler(request, res);
154
156
 
155
- wss = new WebSocketServer({ server });
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 {}
156
164
 
157
- server.listen(SERVER_PORT, async () => {
158
- const watchers: Array<RollupWatcher | FSWatcher> = [];
159
- if (sys?.onProcessInterrupt) {
160
- sys.onProcessInterrupt(
161
- async () => await onClose(server, sys, watchers, config),
162
- );
163
- }
164
-
165
- await createManifest({
166
- ctx: {
167
- ...config,
168
- client: {
169
- ...config.client,
170
- tmpDir: buildDir,
171
- },
165
+ serve(request, res, done);
172
166
  },
173
- typesFileName: "embeddable-types.js",
174
- stencilWrapperFileName: "embeddable-wrapper.js",
175
- metaFileName: "embeddable-components-meta.js",
176
- editorsMetaFileName: "embeddable-editors-meta.js",
177
- });
178
-
179
- await sendDataModelsAndSecurityContextsChanges(config);
167
+ );
180
168
 
181
- for (const getPlugin of config.plugins) {
182
- const plugin = getPlugin();
169
+ wss = new WebSocketServer({ server });
183
170
 
184
- await plugin.validate(config);
185
- 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
+ }
186
178
 
187
- await configureWatcher(watcher, config);
188
- watchers.push(watcher);
189
- }
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
+ }
190
207
 
191
- const dataModelAndSecurityContextWatch =
192
- dataModelAndSecurityContextWatcher(config);
208
+ const dataModelAndSecurityContextWatch =
209
+ dataModelAndSecurityContextWatcher(config);
193
210
 
194
- const customGlobalCssWatch = globalCssWatcher(config);
195
- watchers.push(dataModelAndSecurityContextWatch);
196
- watchers.push(customGlobalCssWatch);
197
- });
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
+ }
198
219
  };
199
220
 
200
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