@base44-preview/cli 0.0.15-pr.98.72adf3c → 0.0.15-pr.99.e192f83

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 (2) hide show
  1. package/dist/cli/index.js +441 -1
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -4547,6 +4547,7 @@ const string$1 = (params) => {
4547
4547
  };
4548
4548
  const integer = /^-?\d+$/;
4549
4549
  const number$1 = /^-?\d+(?:\.\d+)?$/;
4550
+ const boolean$1 = /^(?:true|false)$/i;
4550
4551
  const lowercase = /^[^A-Z]*$/;
4551
4552
  const uppercase = /^[^a-z]*$/;
4552
4553
 
@@ -5325,6 +5326,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
5325
5326
  $ZodCheckNumberFormat.init(inst, def);
5326
5327
  $ZodNumber.init(inst, def);
5327
5328
  });
5329
+ const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
5330
+ $ZodType.init(inst, def);
5331
+ inst._zod.pattern = boolean$1;
5332
+ inst._zod.parse = (payload, _ctx) => {
5333
+ if (def.coerce) try {
5334
+ payload.value = Boolean(payload.value);
5335
+ } catch (_$2) {}
5336
+ const input = payload.value;
5337
+ if (typeof input === "boolean") return payload;
5338
+ payload.issues.push({
5339
+ expected: "boolean",
5340
+ code: "invalid_type",
5341
+ input,
5342
+ inst
5343
+ });
5344
+ return payload;
5345
+ };
5346
+ });
5328
5347
  const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
5329
5348
  $ZodType.init(inst, def);
5330
5349
  inst._zod.parse = (payload) => payload;
@@ -6379,6 +6398,13 @@ function _int(Class, params) {
6379
6398
  });
6380
6399
  }
6381
6400
  /* @__NO_SIDE_EFFECTS__ */
6401
+ function _boolean(Class, params) {
6402
+ return new Class({
6403
+ type: "boolean",
6404
+ ...normalizeParams(params)
6405
+ });
6406
+ }
6407
+ /* @__NO_SIDE_EFFECTS__ */
6382
6408
  function _unknown(Class) {
6383
6409
  return new Class({ type: "unknown" });
6384
6410
  }
@@ -6949,6 +6975,9 @@ const numberProcessor = (schema, ctx, _json, _params) => {
6949
6975
  }
6950
6976
  if (typeof multipleOf === "number") json.multipleOf = multipleOf;
6951
6977
  };
6978
+ const booleanProcessor = (_schema, _ctx, json, _params) => {
6979
+ json.type = "boolean";
6980
+ };
6952
6981
  const neverProcessor = (_schema, _ctx, json, _params) => {
6953
6982
  json.not = {};
6954
6983
  };
@@ -7472,6 +7501,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
7472
7501
  function int(params) {
7473
7502
  return _int(ZodNumberFormat, params);
7474
7503
  }
7504
+ const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
7505
+ $ZodBoolean.init(inst, def);
7506
+ ZodType.init(inst, def);
7507
+ inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
7508
+ });
7509
+ function boolean(params) {
7510
+ return _boolean(ZodBoolean, params);
7511
+ }
7475
7512
  const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
7476
7513
  $ZodUnknown.init(inst, def);
7477
7514
  ZodType.init(inst, def);
@@ -7863,6 +7900,19 @@ var AuthValidationError = class extends Error {
7863
7900
  this.name = "AuthValidationError";
7864
7901
  }
7865
7902
  };
7903
+ var ConnectorApiError = class extends Error {
7904
+ constructor(message, cause) {
7905
+ super(message);
7906
+ this.cause = cause;
7907
+ this.name = "ConnectorApiError";
7908
+ }
7909
+ };
7910
+ var ConnectorValidationError = class extends Error {
7911
+ constructor(message) {
7912
+ super(message);
7913
+ this.name = "ConnectorValidationError";
7914
+ }
7915
+ };
7866
7916
 
7867
7917
  //#endregion
7868
7918
  //#region src/core/consts.ts
@@ -30955,7 +31005,10 @@ const theme = {
30955
31005
  base44OrangeBackground: source_default.bgHex("#E86B3C"),
30956
31006
  shinyOrange: source_default.hex("#FFD700"),
30957
31007
  links: source_default.hex("#00D4FF"),
30958
- white: source_default.white
31008
+ white: source_default.white,
31009
+ success: source_default.green,
31010
+ warning: source_default.yellow,
31011
+ error: source_default.red
30959
31012
  },
30960
31013
  styles: {
30961
31014
  header: source_default.dim,
@@ -38789,6 +38842,390 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
38789
38842
  await runCommand(() => deployAction(options), { requireAuth: true });
38790
38843
  }));
38791
38844
 
38845
+ //#endregion
38846
+ //#region src/core/connectors/schema.ts
38847
+ /**
38848
+ * Response from POST /api/apps/{app_id}/external-auth/initiate
38849
+ */
38850
+ const InitiateResponseSchema = object({
38851
+ redirect_url: string().optional(),
38852
+ connection_id: string().optional(),
38853
+ already_authorized: boolean().optional(),
38854
+ other_user_email: string().optional(),
38855
+ error: string().optional()
38856
+ });
38857
+ /**
38858
+ * Response from GET /api/apps/{app_id}/external-auth/status
38859
+ */
38860
+ const StatusResponseSchema = object({
38861
+ status: _enum([
38862
+ "ACTIVE",
38863
+ "PENDING",
38864
+ "FAILED"
38865
+ ]),
38866
+ account_email: string().optional(),
38867
+ error: string().optional()
38868
+ });
38869
+ /**
38870
+ * A connected integration from the list endpoint
38871
+ */
38872
+ const ConnectorSchema = object({
38873
+ integration_type: string(),
38874
+ status: string(),
38875
+ connected_at: string().optional(),
38876
+ account_info: object({
38877
+ email: string().optional(),
38878
+ name: string().optional()
38879
+ }).optional()
38880
+ }).transform((data) => ({
38881
+ integrationType: data.integration_type,
38882
+ status: data.status,
38883
+ connectedAt: data.connected_at,
38884
+ accountInfo: data.account_info
38885
+ }));
38886
+ /**
38887
+ * Response from GET /api/apps/{app_id}/external-auth/list
38888
+ */
38889
+ const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
38890
+ /**
38891
+ * Generic API error response
38892
+ */
38893
+ const ApiErrorSchema = object({
38894
+ error: string(),
38895
+ detail: string().optional()
38896
+ });
38897
+
38898
+ //#endregion
38899
+ //#region src/core/connectors/api.ts
38900
+ /**
38901
+ * Initiates OAuth flow for a connector integration.
38902
+ * Returns a redirect URL to open in the browser.
38903
+ */
38904
+ async function initiateOAuth(integrationType, scopes = null) {
38905
+ const response = await getAppClient().post("external-auth/initiate", {
38906
+ json: {
38907
+ integration_type: integrationType,
38908
+ scopes
38909
+ },
38910
+ throwHttpErrors: false
38911
+ });
38912
+ const json = await response.json();
38913
+ if (!response.ok) {
38914
+ const errorResult = ApiErrorSchema.safeParse(json);
38915
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38916
+ throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
38917
+ }
38918
+ const result = InitiateResponseSchema.safeParse(json);
38919
+ if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
38920
+ return result.data;
38921
+ }
38922
+ /**
38923
+ * Checks the status of an OAuth connection attempt.
38924
+ */
38925
+ async function checkOAuthStatus(integrationType, connectionId) {
38926
+ const response = await getAppClient().get("external-auth/status", {
38927
+ searchParams: {
38928
+ integration_type: integrationType,
38929
+ connection_id: connectionId
38930
+ },
38931
+ throwHttpErrors: false
38932
+ });
38933
+ const json = await response.json();
38934
+ if (!response.ok) {
38935
+ const errorResult = ApiErrorSchema.safeParse(json);
38936
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38937
+ throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
38938
+ }
38939
+ const result = StatusResponseSchema.safeParse(json);
38940
+ if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
38941
+ return result.data;
38942
+ }
38943
+ /**
38944
+ * Lists all connected integrations for the current app.
38945
+ */
38946
+ async function listConnectors() {
38947
+ const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
38948
+ const json = await response.json();
38949
+ if (!response.ok) {
38950
+ const errorResult = ApiErrorSchema.safeParse(json);
38951
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38952
+ throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
38953
+ }
38954
+ const result = ListResponseSchema.safeParse(json);
38955
+ if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
38956
+ return result.data.integrations;
38957
+ }
38958
+ /**
38959
+ * Disconnects (soft delete) a connector integration.
38960
+ */
38961
+ async function disconnectConnector(integrationType) {
38962
+ const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
38963
+ if (!response.ok) {
38964
+ const json = await response.json();
38965
+ const errorResult = ApiErrorSchema.safeParse(json);
38966
+ if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
38967
+ throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
38968
+ }
38969
+ }
38970
+
38971
+ //#endregion
38972
+ //#region src/core/connectors/constants.ts
38973
+ /**
38974
+ * Supported OAuth connector integrations.
38975
+ * Based on apper/backend/app/external_auth/models/constants.py
38976
+ */
38977
+ const SUPPORTED_INTEGRATIONS = [
38978
+ "googlecalendar",
38979
+ "googledrive",
38980
+ "gmail",
38981
+ "googlesheets",
38982
+ "googledocs",
38983
+ "googleslides",
38984
+ "slack",
38985
+ "notion",
38986
+ "salesforce",
38987
+ "hubspot",
38988
+ "linkedin",
38989
+ "tiktok"
38990
+ ];
38991
+ /**
38992
+ * Display names for integrations (for CLI output)
38993
+ */
38994
+ const INTEGRATION_DISPLAY_NAMES = {
38995
+ googlecalendar: "Google Calendar",
38996
+ googledrive: "Google Drive",
38997
+ gmail: "Gmail",
38998
+ googlesheets: "Google Sheets",
38999
+ googledocs: "Google Docs",
39000
+ googleslides: "Google Slides",
39001
+ slack: "Slack",
39002
+ notion: "Notion",
39003
+ salesforce: "Salesforce",
39004
+ hubspot: "HubSpot",
39005
+ linkedin: "LinkedIn",
39006
+ tiktok: "TikTok"
39007
+ };
39008
+ function isValidIntegration(type) {
39009
+ return SUPPORTED_INTEGRATIONS.includes(type);
39010
+ }
39011
+ function getIntegrationDisplayName(type) {
39012
+ if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
39013
+ return type;
39014
+ }
39015
+
39016
+ //#endregion
39017
+ //#region src/cli/commands/connectors/add.ts
39018
+ const POLL_INTERVAL_MS = 2e3;
39019
+ const POLL_TIMEOUT_MS = 300 * 1e3;
39020
+ async function promptForIntegrationType() {
39021
+ const selected = await ve({
39022
+ message: "Select an integration to connect:",
39023
+ options: SUPPORTED_INTEGRATIONS.map((type) => ({
39024
+ value: type,
39025
+ label: getIntegrationDisplayName(type)
39026
+ }))
39027
+ });
39028
+ if (pD(selected)) return null;
39029
+ return selected;
39030
+ }
39031
+ async function waitForOAuthCompletion(integrationType, connectionId) {
39032
+ let accountEmail;
39033
+ let error;
39034
+ try {
39035
+ await runTask("Waiting for authorization...", async (updateMessage) => {
39036
+ await pWaitFor(async () => {
39037
+ const status = await checkOAuthStatus(integrationType, connectionId);
39038
+ if (status.status === "ACTIVE") {
39039
+ accountEmail = status.account_email;
39040
+ return true;
39041
+ }
39042
+ if (status.status === "FAILED") {
39043
+ error = status.error || "Authorization failed";
39044
+ throw new Error(error);
39045
+ }
39046
+ updateMessage("Waiting for authorization in browser...");
39047
+ return false;
39048
+ }, {
39049
+ interval: POLL_INTERVAL_MS,
39050
+ timeout: POLL_TIMEOUT_MS
39051
+ });
39052
+ }, {
39053
+ successMessage: "Authorization completed!",
39054
+ errorMessage: "Authorization failed"
39055
+ });
39056
+ return {
39057
+ success: true,
39058
+ accountEmail
39059
+ };
39060
+ } catch (err) {
39061
+ if (err instanceof Error && err.message.includes("timed out")) return {
39062
+ success: false,
39063
+ error: "Authorization timed out. Please try again."
39064
+ };
39065
+ return {
39066
+ success: false,
39067
+ error: error || (err instanceof Error ? err.message : "Unknown error")
39068
+ };
39069
+ }
39070
+ }
39071
+ async function addConnector(integrationType) {
39072
+ let selectedType;
39073
+ if (!integrationType) {
39074
+ const prompted = await promptForIntegrationType();
39075
+ if (!prompted) return { outroMessage: "Cancelled" };
39076
+ selectedType = prompted;
39077
+ } else {
39078
+ if (!isValidIntegration(integrationType)) {
39079
+ const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
39080
+ throw new Error(`Unsupported connector: ${integrationType}\nSupported connectors: ${supportedList}`);
39081
+ }
39082
+ selectedType = integrationType;
39083
+ }
39084
+ const displayName = getIntegrationDisplayName(selectedType);
39085
+ const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
39086
+ return await initiateOAuth(selectedType);
39087
+ }, {
39088
+ successMessage: `${displayName} OAuth initiated`,
39089
+ errorMessage: `Failed to initiate ${displayName} connection`
39090
+ });
39091
+ if (initiateResponse.already_authorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)}` };
39092
+ if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
39093
+ if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
39094
+ M.info(`Opening browser for ${displayName} authorization...`);
39095
+ await open_default(initiateResponse.redirect_url);
39096
+ const result = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
39097
+ if (!result.success) throw new Error(result.error || "Authorization failed");
39098
+ const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
39099
+ return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
39100
+ }
39101
+ const connectorsAddCommand = new Command("connectors:add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
39102
+ await runCommand(() => addConnector(type), {
39103
+ requireAuth: true,
39104
+ requireAppConfig: true
39105
+ });
39106
+ });
39107
+
39108
+ //#endregion
39109
+ //#region src/cli/commands/connectors/list.ts
39110
+ function formatDate(dateString) {
39111
+ if (!dateString) return "-";
39112
+ try {
39113
+ return new Date(dateString).toLocaleDateString("en-US", {
39114
+ year: "numeric",
39115
+ month: "short",
39116
+ day: "numeric"
39117
+ });
39118
+ } catch {
39119
+ return dateString;
39120
+ }
39121
+ }
39122
+ function formatStatus(status) {
39123
+ const normalized = status.toLowerCase();
39124
+ if (normalized === "active" || normalized === "connected") return theme.colors.success("● active");
39125
+ if (normalized === "expired") return theme.colors.warning("● expired");
39126
+ if (normalized === "failed" || normalized === "disconnected") return theme.colors.error("● disconnected");
39127
+ return status;
39128
+ }
39129
+ async function listConnectorsCommand() {
39130
+ const connectors = await runTask("Fetching connectors...", async () => {
39131
+ return await listConnectors();
39132
+ }, {
39133
+ successMessage: "Connectors loaded",
39134
+ errorMessage: "Failed to fetch connectors"
39135
+ });
39136
+ if (connectors.length === 0) {
39137
+ M.info("No connectors configured for this app.");
39138
+ M.info(`Run ${theme.styles.bold("base44 connectors:add")} to connect an integration.`);
39139
+ return { outroMessage: "" };
39140
+ }
39141
+ console.log();
39142
+ console.log(theme.styles.bold("Connected Integrations:"));
39143
+ console.log();
39144
+ const headers = [
39145
+ "Type",
39146
+ "Account",
39147
+ "Status",
39148
+ "Connected"
39149
+ ];
39150
+ const colWidths = [
39151
+ 20,
39152
+ 30,
39153
+ 15,
39154
+ 15
39155
+ ];
39156
+ const headerRow = headers.map((h$2, i$1) => h$2.padEnd(colWidths[i$1])).join(" ");
39157
+ console.log(theme.styles.dim(headerRow));
39158
+ console.log(theme.styles.dim("─".repeat(headerRow.length)));
39159
+ for (const connector of connectors) {
39160
+ const type = getIntegrationDisplayName(connector.integrationType).padEnd(colWidths[0]);
39161
+ const account = (connector.accountInfo?.email || connector.accountInfo?.name || "-").padEnd(colWidths[1]);
39162
+ const status = formatStatus(connector.status);
39163
+ const connected = formatDate(connector.connectedAt).padEnd(colWidths[3]);
39164
+ console.log(`${type} ${account} ${status.padEnd(colWidths[2] + 10)} ${connected}`);
39165
+ }
39166
+ console.log();
39167
+ return { outroMessage: `${connectors.length} connector${connectors.length === 1 ? "" : "s"} configured` };
39168
+ }
39169
+ const connectorsListCommand = new Command("connectors:list").description("List all connected OAuth integrations").action(async () => {
39170
+ await runCommand(listConnectorsCommand, {
39171
+ requireAuth: true,
39172
+ requireAppConfig: true
39173
+ });
39174
+ });
39175
+
39176
+ //#endregion
39177
+ //#region src/cli/commands/connectors/remove.ts
39178
+ async function promptForConnectorToRemove(connectors) {
39179
+ const selected = await ve({
39180
+ message: "Select a connector to remove:",
39181
+ options: connectors.map((c$1) => ({
39182
+ value: c$1.integrationType,
39183
+ label: `${getIntegrationDisplayName(c$1.integrationType)}${c$1.accountInfo?.email ? ` (${c$1.accountInfo.email})` : ""}`
39184
+ }))
39185
+ });
39186
+ if (pD(selected)) return null;
39187
+ return selected;
39188
+ }
39189
+ async function removeConnectorCommand(integrationType) {
39190
+ const connectors = await runTask("Fetching connectors...", async () => {
39191
+ return await listConnectors();
39192
+ }, {
39193
+ successMessage: "Connectors loaded",
39194
+ errorMessage: "Failed to fetch connectors"
39195
+ });
39196
+ if (connectors.length === 0) return { outroMessage: "No connectors to remove" };
39197
+ let selectedType;
39198
+ if (!integrationType) {
39199
+ const prompted = await promptForConnectorToRemove(connectors);
39200
+ if (!prompted) return { outroMessage: "Cancelled" };
39201
+ selectedType = prompted;
39202
+ } else {
39203
+ if (!isValidIntegration(integrationType)) throw new Error(`Invalid connector type: ${integrationType}`);
39204
+ if (!connectors.some((c$1) => c$1.integrationType === integrationType)) throw new Error(`No ${getIntegrationDisplayName(integrationType)} connector found for this app`);
39205
+ selectedType = integrationType;
39206
+ }
39207
+ const displayName = getIntegrationDisplayName(selectedType);
39208
+ const connector = connectors.find((c$1) => c$1.integrationType === selectedType);
39209
+ const shouldRemove = await ye({
39210
+ message: `Disconnect ${displayName}${connector?.accountInfo?.email ? ` (${connector.accountInfo.email})` : ""}?`,
39211
+ initialValue: false
39212
+ });
39213
+ if (pD(shouldRemove) || !shouldRemove) return { outroMessage: "Cancelled" };
39214
+ await runTask(`Disconnecting ${displayName}...`, async () => {
39215
+ await disconnectConnector(selectedType);
39216
+ }, {
39217
+ successMessage: `${displayName} disconnected`,
39218
+ errorMessage: `Failed to disconnect ${displayName}`
39219
+ });
39220
+ return { outroMessage: `Successfully disconnected ${theme.styles.bold(displayName)}` };
39221
+ }
39222
+ const connectorsRemoveCommand = new Command("connectors:remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").description("Disconnect an OAuth integration").action(async (type) => {
39223
+ await runCommand(() => removeConnectorCommand(type), {
39224
+ requireAuth: true,
39225
+ requireAppConfig: true
39226
+ });
39227
+ });
39228
+
38792
39229
  //#endregion
38793
39230
  //#region package.json
38794
39231
  var version = "0.0.15";
@@ -38808,6 +39245,9 @@ program.addCommand(linkCommand);
38808
39245
  program.addCommand(entitiesPushCommand);
38809
39246
  program.addCommand(functionsDeployCommand);
38810
39247
  program.addCommand(siteDeployCommand);
39248
+ program.addCommand(connectorsAddCommand);
39249
+ program.addCommand(connectorsListCommand);
39250
+ program.addCommand(connectorsRemoveCommand);
38811
39251
  program.parse();
38812
39252
 
38813
39253
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/cli",
3
- "version": "0.0.15-pr.98.72adf3c",
3
+ "version": "0.0.15-pr.99.e192f83",
4
4
  "description": "Base44 CLI - Unified interface for managing Base44 applications",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",