@base44-preview/cli 0.0.24-pr.146.e5ee77a → 0.0.25-pr.104.089a20e

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +364 -11
  3. package/package.json +8 -2
package/README.md CHANGED
@@ -53,6 +53,8 @@ The CLI will guide you through project setup. For step-by-step tutorials, see th
53
53
  | [`entities push`](https://docs.base44.com/developers/references/cli/commands/entities-push) | Push local entity schemas to Base44 |
54
54
  | [`functions deploy`](https://docs.base44.com/developers/references/cli/commands/functions-deploy) | Deploy local functions to Base44 |
55
55
  | [`site deploy`](https://docs.base44.com/developers/references/cli/commands/site-deploy) | Deploy built site files to Base44 hosting |
56
+ | `connectors add [type]` | Connect an OAuth integration |
57
+ | `connectors remove [type]` | Disconnect an integration |
56
58
 
57
59
 
58
60
  <!--| [`eject`](https://docs.base44.com/developers/references/cli/commands/eject) | Create a Base44 backend project from an existing Base44 app | -->
package/dist/index.js CHANGED
@@ -16889,6 +16889,11 @@ async function writeAgents(agentsDir, remoteAgents) {
16889
16889
  //#endregion
16890
16890
  //#region src/core/resources/agent/api.ts
16891
16891
  async function pushAgents(agents) {
16892
+ if (agents.length === 0) return {
16893
+ created: [],
16894
+ updated: [],
16895
+ deleted: []
16896
+ };
16892
16897
  const response = await getAppClient().put("agent-configs", {
16893
16898
  json: agents,
16894
16899
  throwHttpErrors: false
@@ -30612,25 +30617,27 @@ async function createArchive(pathToArchive, targetArchivePath) {
30612
30617
  * Checks if there are any resources to deploy in the project.
30613
30618
  *
30614
30619
  * @param projectData - The project configuration and resources
30615
- * @returns true if there are entities, functions, or a configured site to deploy
30620
+ * @returns true if there are entities, functions, agents, or a configured site to deploy
30616
30621
  */
30617
30622
  function hasResourcesToDeploy(projectData) {
30618
- const { project, entities, functions } = projectData;
30623
+ const { project, entities, functions, agents } = projectData;
30619
30624
  const hasSite = Boolean(project.site?.outputDirectory);
30620
30625
  const hasEntities = entities.length > 0;
30621
30626
  const hasFunctions = functions.length > 0;
30622
- return hasEntities || hasFunctions || hasSite;
30627
+ const hasAgents = agents.length > 0;
30628
+ return hasEntities || hasFunctions || hasAgents || hasSite;
30623
30629
  }
30624
30630
  /**
30625
- * Deploys all project resources (entities, functions, and site) to Base44.
30631
+ * Deploys all project resources (entities, functions, agents, and site) to Base44.
30626
30632
  *
30627
30633
  * @param projectData - The project configuration and resources to deploy
30628
30634
  * @returns The deployment result including app URL if site was deployed
30629
30635
  */
30630
30636
  async function deployAll(projectData) {
30631
- const { project, entities, functions } = projectData;
30637
+ const { project, entities, functions, agents } = projectData;
30632
30638
  await entityResource.push(entities);
30633
30639
  await functionResource.push(functions);
30640
+ await agentResource.push(agents);
30634
30641
  if (project.site?.outputDirectory) {
30635
30642
  const { appUrl } = await deploySite(resolve(project.root, project.site.outputDirectory));
30636
30643
  return { appUrl };
@@ -30642,11 +30649,31 @@ async function deployAll(projectData) {
30642
30649
  //#region src/core/project/app-config.ts
30643
30650
  let cache = null;
30644
30651
  /**
30652
+ * Load app config from BASE44_CLI_TEST_OVERRIDES env var.
30653
+ * @returns true if override was applied, false otherwise
30654
+ */
30655
+ function loadFromTestOverrides() {
30656
+ const overrides = process.env.BASE44_CLI_TEST_OVERRIDES;
30657
+ if (!overrides) return false;
30658
+ try {
30659
+ const data = JSON.parse(overrides);
30660
+ if (data.appConfig?.id && data.appConfig?.projectRoot) {
30661
+ cache = {
30662
+ id: data.appConfig.id,
30663
+ projectRoot: data.appConfig.projectRoot
30664
+ };
30665
+ return true;
30666
+ }
30667
+ } catch {}
30668
+ return false;
30669
+ }
30670
+ /**
30645
30671
  * Initialize app config by reading from .app.jsonc.
30646
30672
  * Must be called before using getAppConfig().
30647
30673
  * @throws Error if no project found or .app.jsonc missing
30648
30674
  */
30649
30675
  async function initAppConfig() {
30676
+ if (loadFromTestOverrides()) return;
30650
30677
  if (cache) return;
30651
30678
  const projectRoot = await findProjectRoot();
30652
30679
  if (!projectRoot) throw new Error("No Base44 project found. Run this command from a project directory with a config.jsonc file.");
@@ -31278,7 +31305,10 @@ const theme = {
31278
31305
  base44OrangeBackground: source_default.bgHex("#E86B3C"),
31279
31306
  shinyOrange: source_default.hex("#FFD700"),
31280
31307
  links: source_default.hex("#00D4FF"),
31281
- white: source_default.white
31308
+ white: source_default.white,
31309
+ success: source_default.green,
31310
+ warning: source_default.yellow,
31311
+ error: source_default.red
31282
31312
  },
31283
31313
  styles: {
31284
31314
  header: source_default.dim,
@@ -31595,7 +31625,8 @@ const logoutCommand = new Command("logout").description("Logout from current dev
31595
31625
  async function pushEntitiesAction() {
31596
31626
  const { entities } = await readProjectConfig();
31597
31627
  if (entities.length === 0) return { outroMessage: "No entities found in project" };
31598
- M.info(`Found ${entities.length} entities to push`);
31628
+ const entityNames = entities.map((e$1) => e$1.name).join(", ");
31629
+ M.info(`Found ${entities.length} entities to push: ${entityNames}`);
31599
31630
  const result = await runTask("Pushing entities to Base44", async () => {
31600
31631
  return await pushEntities(entities);
31601
31632
  }, {
@@ -39058,7 +39089,7 @@ var open_default = open;
39058
39089
  //#region src/cli/commands/project/dashboard.ts
39059
39090
  async function openDashboard() {
39060
39091
  const dashboardUrl = getDashboardUrl();
39061
- await open_default(dashboardUrl);
39092
+ if (!process.env.CI) await open_default(dashboardUrl);
39062
39093
  return { outroMessage: `Dashboard opened at ${dashboardUrl}` };
39063
39094
  }
39064
39095
  const dashboardCommand = new Command("dashboard").description("Open the app dashboard in your browser").action(async () => {
@@ -39070,10 +39101,11 @@ const dashboardCommand = new Command("dashboard").description("Open the app dash
39070
39101
  async function deployAction$1(options) {
39071
39102
  const projectData = await readProjectConfig();
39072
39103
  if (!hasResourcesToDeploy(projectData)) return { outroMessage: "No resources found to deploy" };
39073
- const { project, entities, functions } = projectData;
39104
+ const { project, entities, functions, agents } = projectData;
39074
39105
  const summaryLines = [];
39075
39106
  if (entities.length > 0) summaryLines.push(` - ${entities.length} ${entities.length === 1 ? "entity" : "entities"}`);
39076
39107
  if (functions.length > 0) summaryLines.push(` - ${functions.length} ${functions.length === 1 ? "function" : "functions"}`);
39108
+ if (agents.length > 0) summaryLines.push(` - ${agents.length} ${agents.length === 1 ? "agent" : "agents"}`);
39077
39109
  if (project.site?.outputDirectory) summaryLines.push(` - Site from ${project.site.outputDirectory}`);
39078
39110
  if (!options.yes) {
39079
39111
  M.warn(`This will update your Base44 app with:\n${summaryLines.join("\n")}`);
@@ -39090,7 +39122,7 @@ async function deployAction$1(options) {
39090
39122
  if (result.appUrl) M.message(`${theme.styles.header("App URL")}: ${theme.colors.links(result.appUrl)}`);
39091
39123
  return { outroMessage: "App deployed successfully" };
39092
39124
  }
39093
- const deployCommand = new Command("deploy").description("Deploy all project resources (entities, functions, and site)").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
39125
+ const deployCommand = new Command("deploy").description("Deploy all project resources (entities, functions, agents, and site)").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
39094
39126
  await runCommand(() => deployAction$1(options), { requireAuth: true });
39095
39127
  });
39096
39128
 
@@ -39235,9 +39267,329 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
39235
39267
  await runCommand(() => deployAction(options), { requireAuth: true });
39236
39268
  }));
39237
39269
 
39270
+ //#endregion
39271
+ //#region src/core/connectors/schema.ts
39272
+ /**
39273
+ * Response from POST /api/apps/{app_id}/external-auth/initiate
39274
+ */
39275
+ const InitiateResponseSchema = object({
39276
+ redirect_url: string().nullish(),
39277
+ connection_id: string().nullish(),
39278
+ already_authorized: boolean().nullish(),
39279
+ other_user_email: string().nullish(),
39280
+ error: string().nullish()
39281
+ }).transform((data) => ({
39282
+ redirectUrl: data.redirect_url,
39283
+ connectionId: data.connection_id,
39284
+ alreadyAuthorized: data.already_authorized,
39285
+ otherUserEmail: data.other_user_email,
39286
+ error: data.error
39287
+ }));
39288
+ /**
39289
+ * Response from GET /api/apps/{app_id}/external-auth/status
39290
+ */
39291
+ const StatusResponseSchema = object({
39292
+ status: _enum([
39293
+ "ACTIVE",
39294
+ "PENDING",
39295
+ "FAILED"
39296
+ ]),
39297
+ account_email: string().nullish(),
39298
+ error: string().nullish()
39299
+ }).transform((data) => ({
39300
+ status: data.status,
39301
+ accountEmail: data.account_email,
39302
+ error: data.error
39303
+ }));
39304
+ /**
39305
+ * A connected integration from the list endpoint
39306
+ */
39307
+ const ConnectorSchema = object({
39308
+ integration_type: string(),
39309
+ status: string(),
39310
+ connected_at: string().nullish(),
39311
+ account_info: object({
39312
+ email: string().nullish(),
39313
+ name: string().nullish()
39314
+ }).nullish()
39315
+ }).transform((data) => ({
39316
+ integrationType: data.integration_type,
39317
+ status: data.status,
39318
+ connectedAt: data.connected_at,
39319
+ accountInfo: data.account_info
39320
+ }));
39321
+ /**
39322
+ * Response from GET /api/apps/{app_id}/external-auth/list
39323
+ */
39324
+ const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
39325
+
39326
+ //#endregion
39327
+ //#region src/core/connectors/api.ts
39328
+ /**
39329
+ * Initiates OAuth flow for a connector integration.
39330
+ * Returns a redirect URL to open in the browser.
39331
+ */
39332
+ async function initiateOAuth(integrationType, scopes = null) {
39333
+ const json = await (await getAppClient().post("external-auth/initiate", { json: {
39334
+ integration_type: integrationType,
39335
+ scopes
39336
+ } })).json();
39337
+ return InitiateResponseSchema.parse(json);
39338
+ }
39339
+ /**
39340
+ * Checks the status of an OAuth connection attempt.
39341
+ */
39342
+ async function checkOAuthStatus(integrationType, connectionId) {
39343
+ const json = await (await getAppClient().get("external-auth/status", { searchParams: {
39344
+ integration_type: integrationType,
39345
+ connection_id: connectionId
39346
+ } })).json();
39347
+ return StatusResponseSchema.parse(json);
39348
+ }
39349
+ /**
39350
+ * Lists all connected integrations for the current app.
39351
+ */
39352
+ async function listConnectors() {
39353
+ const json = await (await getAppClient().get("external-auth/list")).json();
39354
+ return ListResponseSchema.parse(json).integrations;
39355
+ }
39356
+ /**
39357
+ * Disconnects (soft delete) a connector integration.
39358
+ */
39359
+ async function disconnectConnector(integrationType) {
39360
+ await getAppClient().delete(`external-auth/integrations/${integrationType}`);
39361
+ }
39362
+ /**
39363
+ * Removes (hard delete) a connector integration.
39364
+ * This permanently removes the connector and cannot be undone.
39365
+ */
39366
+ async function removeConnector(integrationType) {
39367
+ await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`);
39368
+ }
39369
+
39370
+ //#endregion
39371
+ //#region src/core/connectors/consts.ts
39372
+ /**
39373
+ * Supported OAuth connector integrations.
39374
+ * Based on apper/backend/app/external_auth/models/constants.py
39375
+ */
39376
+ const SUPPORTED_INTEGRATIONS = [
39377
+ "googlecalendar",
39378
+ "googledrive",
39379
+ "gmail",
39380
+ "googlesheets",
39381
+ "googledocs",
39382
+ "googleslides",
39383
+ "slack",
39384
+ "notion",
39385
+ "salesforce",
39386
+ "hubspot",
39387
+ "linkedin",
39388
+ "tiktok"
39389
+ ];
39390
+ /**
39391
+ * Display names for integrations (for CLI output)
39392
+ */
39393
+ const INTEGRATION_DISPLAY_NAMES = {
39394
+ googlecalendar: "Google Calendar",
39395
+ googledrive: "Google Drive",
39396
+ gmail: "Gmail",
39397
+ googlesheets: "Google Sheets",
39398
+ googledocs: "Google Docs",
39399
+ googleslides: "Google Slides",
39400
+ slack: "Slack",
39401
+ notion: "Notion",
39402
+ salesforce: "Salesforce",
39403
+ hubspot: "HubSpot",
39404
+ linkedin: "LinkedIn",
39405
+ tiktok: "TikTok"
39406
+ };
39407
+ function isValidIntegration(type) {
39408
+ return SUPPORTED_INTEGRATIONS.includes(type);
39409
+ }
39410
+ function getIntegrationDisplayName(type) {
39411
+ return INTEGRATION_DISPLAY_NAMES[type] ?? type;
39412
+ }
39413
+
39414
+ //#endregion
39415
+ //#region src/cli/commands/connectors/utils.ts
39416
+ const OAUTH_POLL_INTERVAL_MS = 2e3;
39417
+ const OAUTH_POLL_TIMEOUT_MS = 300 * 1e3;
39418
+ /**
39419
+ * Polls for OAuth completion status.
39420
+ * Returns when status becomes ACTIVE or FAILED, or times out.
39421
+ */
39422
+ async function waitForOAuthCompletion(integrationType, connectionId, options) {
39423
+ let accountEmail;
39424
+ let error;
39425
+ try {
39426
+ await pWaitFor(async () => {
39427
+ const status = await checkOAuthStatus(integrationType, connectionId);
39428
+ if (status.status === "ACTIVE") {
39429
+ accountEmail = status.accountEmail ?? void 0;
39430
+ return true;
39431
+ }
39432
+ if (status.status === "FAILED") {
39433
+ error = status.error || "Authorization failed";
39434
+ throw new Error(error);
39435
+ }
39436
+ options?.onPending?.();
39437
+ return false;
39438
+ }, {
39439
+ interval: OAUTH_POLL_INTERVAL_MS,
39440
+ timeout: OAUTH_POLL_TIMEOUT_MS
39441
+ });
39442
+ return {
39443
+ success: true,
39444
+ accountEmail
39445
+ };
39446
+ } catch (err) {
39447
+ if (err instanceof Error && err.message.includes("timed out")) return {
39448
+ success: false,
39449
+ error: "Authorization timed out. Please try again."
39450
+ };
39451
+ return {
39452
+ success: false,
39453
+ error: error || (err instanceof Error ? err.message : "Unknown error")
39454
+ };
39455
+ }
39456
+ }
39457
+ /**
39458
+ * Asserts that a string is a valid integration type, throwing if not.
39459
+ */
39460
+ function assertValidIntegrationType(type, supportedIntegrations) {
39461
+ if (!supportedIntegrations.includes(type)) {
39462
+ const supportedList = supportedIntegrations.join(", ");
39463
+ throw new Error(`Unsupported connector: ${type}\nSupported connectors: ${supportedList}`);
39464
+ }
39465
+ }
39466
+
39467
+ //#endregion
39468
+ //#region src/cli/commands/connectors/add.ts
39469
+ async function promptForIntegrationType() {
39470
+ const selected = await ve({
39471
+ message: "Select an integration to connect:",
39472
+ options: Object.entries(INTEGRATION_DISPLAY_NAMES).map(([type, displayName]) => ({
39473
+ value: type,
39474
+ label: displayName
39475
+ }))
39476
+ });
39477
+ if (pD(selected)) {
39478
+ xe("Operation cancelled.");
39479
+ process.exit(0);
39480
+ }
39481
+ return selected;
39482
+ }
39483
+ async function pollForOAuthCompletion(integrationType, connectionId) {
39484
+ return await runTask("Waiting for authorization...", async () => {
39485
+ return await waitForOAuthCompletion(integrationType, connectionId);
39486
+ }, {
39487
+ successMessage: "Authorization completed!",
39488
+ errorMessage: "Authorization failed"
39489
+ });
39490
+ }
39491
+ async function addConnector(integrationType) {
39492
+ let selectedType;
39493
+ if (integrationType) {
39494
+ assertValidIntegrationType(integrationType, SUPPORTED_INTEGRATIONS);
39495
+ selectedType = integrationType;
39496
+ } else selectedType = await promptForIntegrationType();
39497
+ const displayName = getIntegrationDisplayName(selectedType);
39498
+ const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
39499
+ return await initiateOAuth(selectedType);
39500
+ }, {
39501
+ successMessage: `${displayName} OAuth initiated`,
39502
+ errorMessage: `Failed to initiate ${displayName} connection`
39503
+ });
39504
+ if (initiateResponse.alreadyAuthorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)}` };
39505
+ if (initiateResponse.error === "different_user" && initiateResponse.otherUserEmail) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.otherUserEmail}`);
39506
+ if (!initiateResponse.redirectUrl || !initiateResponse.connectionId) throw new Error("Invalid response from server: missing redirect URL or connection ID");
39507
+ M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirectUrl)}`);
39508
+ const result = await pollForOAuthCompletion(selectedType, initiateResponse.connectionId);
39509
+ if (!result.success) throw new Error(result.error || "Authorization failed");
39510
+ const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
39511
+ return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
39512
+ }
39513
+ const connectorsAddCommand = new Command("add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
39514
+ await runCommand(() => addConnector(type), {
39515
+ requireAuth: true,
39516
+ requireAppConfig: true
39517
+ });
39518
+ });
39519
+
39520
+ //#endregion
39521
+ //#region src/cli/commands/connectors/remove.ts
39522
+ function mapBackendConnectors(connectors) {
39523
+ return connectors.filter((c$1) => isValidIntegration(c$1.integrationType)).map((c$1) => ({
39524
+ type: c$1.integrationType,
39525
+ displayName: getIntegrationDisplayName(c$1.integrationType),
39526
+ accountEmail: (c$1.accountInfo?.email || c$1.accountInfo?.name) ?? void 0
39527
+ }));
39528
+ }
39529
+ function validateConnectorType(type, connectors) {
39530
+ if (!isValidIntegration(type)) throw new Error(`Invalid connector type: ${type}`);
39531
+ const connector = connectors.find((c$1) => c$1.type === type);
39532
+ if (!connector) throw new Error(`No ${getIntegrationDisplayName(type)} connector found`);
39533
+ return connector;
39534
+ }
39535
+ async function promptForConnectorToRemove(connectors) {
39536
+ const selected = await ve({
39537
+ message: "Select a connector to remove:",
39538
+ options: connectors.map((c$1) => {
39539
+ let label = c$1.displayName;
39540
+ if (c$1.accountEmail) label += ` (${c$1.accountEmail})`;
39541
+ return {
39542
+ value: c$1.type,
39543
+ label
39544
+ };
39545
+ })
39546
+ });
39547
+ if (pD(selected)) {
39548
+ xe("Operation cancelled.");
39549
+ process.exit(0);
39550
+ }
39551
+ return connectors.find((c$1) => c$1.type === selected);
39552
+ }
39553
+ async function removeConnectorCommand(integrationType, options = {}) {
39554
+ const isHardDelete = options.hard === true;
39555
+ const connectors = mapBackendConnectors(await runTask("Fetching connectors...", async () => listConnectors(), {
39556
+ successMessage: "Connectors loaded",
39557
+ errorMessage: "Failed to fetch connectors"
39558
+ }));
39559
+ if (connectors.length === 0) return { outroMessage: "No connectors to remove" };
39560
+ const selectedConnector = integrationType ? validateConnectorType(integrationType, connectors) : await promptForConnectorToRemove(connectors);
39561
+ const displayName = selectedConnector.displayName;
39562
+ const accountInfo = selectedConnector.accountEmail ? ` (${selectedConnector.accountEmail})` : "";
39563
+ if (!options.yes) {
39564
+ const shouldRemove = await ye({ message: `${isHardDelete ? "Permanently remove" : "Remove"} ${displayName}${accountInfo}?` });
39565
+ if (pD(shouldRemove) || !shouldRemove) {
39566
+ xe("Operation cancelled.");
39567
+ process.exit(0);
39568
+ }
39569
+ }
39570
+ await runTask(`Removing ${displayName}...`, async () => {
39571
+ if (isHardDelete) await removeConnector(selectedConnector.type);
39572
+ else await disconnectConnector(selectedConnector.type);
39573
+ }, {
39574
+ successMessage: `${displayName} removed`,
39575
+ errorMessage: `Failed to remove ${displayName}`
39576
+ });
39577
+ return { outroMessage: `Successfully removed ${theme.styles.bold(displayName)}` };
39578
+ }
39579
+ const connectorsRemoveCommand = new Command("remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").option("--hard", "Permanently remove the connector (cannot be undone)").option("-y, --yes", "Skip confirmation prompt").description("Remove an OAuth integration").action(async (type, options) => {
39580
+ await runCommand(() => removeConnectorCommand(type, options), {
39581
+ requireAuth: true,
39582
+ requireAppConfig: true
39583
+ });
39584
+ });
39585
+
39586
+ //#endregion
39587
+ //#region src/cli/commands/connectors/index.ts
39588
+ const connectorsCommand = new Command("connectors").description("Manage OAuth connectors").addCommand(connectorsAddCommand).addCommand(connectorsRemoveCommand);
39589
+
39238
39590
  //#endregion
39239
39591
  //#region package.json
39240
- var version = "0.0.24";
39592
+ var version = "0.0.25";
39241
39593
 
39242
39594
  //#endregion
39243
39595
  //#region src/cli/program.ts
@@ -39255,6 +39607,7 @@ program.addCommand(entitiesPushCommand);
39255
39607
  program.addCommand(agentsCommand);
39256
39608
  program.addCommand(functionsDeployCommand);
39257
39609
  program.addCommand(siteDeployCommand);
39610
+ program.addCommand(connectorsCommand);
39258
39611
 
39259
39612
  //#endregion
39260
39613
  export { CLIExitError, program };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/cli",
3
- "version": "0.0.24-pr.146.e5ee77a",
3
+ "version": "0.0.25-pr.104.089a20e",
4
4
  "description": "Base44 CLI - Unified interface for managing Base44 applications",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "dev": "./bin/dev.js",
17
17
  "start": "./bin/run.js",
18
18
  "clean": "rm -rf dist",
19
- "lint": "eslint src",
19
+ "lint": "eslint src tests",
20
20
  "test": "vitest run",
21
21
  "test:watch": "vitest"
22
22
  },
@@ -52,9 +52,12 @@
52
52
  "json5": "^2.2.3",
53
53
  "ky": "^1.14.2",
54
54
  "lodash.kebabcase": "^4.1.1",
55
+ "msw": "^2.12.7",
55
56
  "open": "^11.0.0",
56
57
  "p-wait-for": "^6.0.0",
58
+ "strip-ansi": "^7.1.2",
57
59
  "tar": "^7.5.4",
60
+ "tmp-promise": "^3.0.3",
58
61
  "tsdown": "^0.12.4",
59
62
  "tsx": "^4.19.2",
60
63
  "typescript": "^5.7.2",
@@ -64,5 +67,8 @@
64
67
  },
65
68
  "engines": {
66
69
  "node": ">=20.19.0"
70
+ },
71
+ "optionalDependencies": {
72
+ "@rollup/rollup-linux-x64-gnu": "^4.56.0"
67
73
  }
68
74
  }