@base44-preview/cli 0.0.15-pr.104.eb5a84d → 0.0.15-pr.106.8dd9e6e
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/README.md +0 -54
- package/dist/cli/index.js +1 -849
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,60 +70,6 @@ base44 deploy
|
|
|
70
70
|
|---------|-------------|
|
|
71
71
|
| `base44 site deploy` | Deploy built site files to Base44 hosting |
|
|
72
72
|
|
|
73
|
-
### Connectors
|
|
74
|
-
|
|
75
|
-
Manage OAuth integrations to connect your app with external services. Connectors are stored in the `connectors` property of your project's `config.jsonc` file.
|
|
76
|
-
|
|
77
|
-
| Command | Description |
|
|
78
|
-
|---------|-------------|
|
|
79
|
-
| `base44 connectors add [type]` | Add and connect an OAuth integration |
|
|
80
|
-
| `base44 connectors list` | List all connectors (local and connected) |
|
|
81
|
-
| `base44 connectors push` | Sync connectors with backend (connect new, remove missing) |
|
|
82
|
-
| `base44 connectors remove [type]` | Remove an integration |
|
|
83
|
-
| `base44 connectors remove [type] --hard` | Permanently remove an integration |
|
|
84
|
-
|
|
85
|
-
**Supported integrations:** Slack, Google Calendar, Google Drive, Gmail, Google Sheets, Google Docs, Google Slides, Notion, Salesforce, HubSpot, LinkedIn, TikTok
|
|
86
|
-
|
|
87
|
-
**Example workflow:**
|
|
88
|
-
```bash
|
|
89
|
-
# Add a connector interactively (saves to config.jsonc and opens OAuth)
|
|
90
|
-
base44 connectors add slack
|
|
91
|
-
|
|
92
|
-
# List connectors showing local vs connected status
|
|
93
|
-
base44 connectors list
|
|
94
|
-
# Output:
|
|
95
|
-
# ● Slack - user@example.com
|
|
96
|
-
# ○ Google Calendar (not connected)
|
|
97
|
-
|
|
98
|
-
# Sync all connectors (connects new, removes missing from config)
|
|
99
|
-
base44 connectors push
|
|
100
|
-
# Output:
|
|
101
|
-
# 1 connector to connect:
|
|
102
|
-
# + Google Calendar
|
|
103
|
-
# 1 connector to remove:
|
|
104
|
-
# - Notion (user@example.com)
|
|
105
|
-
# Apply 2 changes? (Y/n)
|
|
106
|
-
|
|
107
|
-
# Remove a single connector
|
|
108
|
-
base44 connectors remove slack
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**Configuration** (`base44/config.jsonc`):
|
|
112
|
-
```jsonc
|
|
113
|
-
{
|
|
114
|
-
"name": "my-app",
|
|
115
|
-
"connectors": {
|
|
116
|
-
"slack": {},
|
|
117
|
-
"googlecalendar": { "scopes": ["calendar.readonly"] }
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Once connected, use the SDK's `connectors.getAccessToken()` to retrieve tokens:
|
|
123
|
-
```javascript
|
|
124
|
-
const token = await base44.connectors.getAccessToken("slack");
|
|
125
|
-
```
|
|
126
|
-
|
|
127
73
|
## Configuration
|
|
128
74
|
|
|
129
75
|
### Project Configuration
|
package/dist/cli/index.js
CHANGED
|
@@ -4547,7 +4547,6 @@ 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;
|
|
4551
4550
|
const lowercase = /^[^A-Z]*$/;
|
|
4552
4551
|
const uppercase = /^[^a-z]*$/;
|
|
4553
4552
|
|
|
@@ -5326,24 +5325,6 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
|
|
|
5326
5325
|
$ZodCheckNumberFormat.init(inst, def);
|
|
5327
5326
|
$ZodNumber.init(inst, def);
|
|
5328
5327
|
});
|
|
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
|
-
});
|
|
5347
5328
|
const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
5348
5329
|
$ZodType.init(inst, def);
|
|
5349
5330
|
inst._zod.parse = (payload) => payload;
|
|
@@ -5819,97 +5800,6 @@ function handleTupleResult(result, final, index) {
|
|
|
5819
5800
|
if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
|
|
5820
5801
|
final.value[index] = result.value;
|
|
5821
5802
|
}
|
|
5822
|
-
const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
5823
|
-
$ZodType.init(inst, def);
|
|
5824
|
-
inst._zod.parse = (payload, ctx) => {
|
|
5825
|
-
const input = payload.value;
|
|
5826
|
-
if (!isPlainObject$1(input)) {
|
|
5827
|
-
payload.issues.push({
|
|
5828
|
-
expected: "record",
|
|
5829
|
-
code: "invalid_type",
|
|
5830
|
-
input,
|
|
5831
|
-
inst
|
|
5832
|
-
});
|
|
5833
|
-
return payload;
|
|
5834
|
-
}
|
|
5835
|
-
const proms = [];
|
|
5836
|
-
const values = def.keyType._zod.values;
|
|
5837
|
-
if (values) {
|
|
5838
|
-
payload.value = {};
|
|
5839
|
-
const recordKeys = /* @__PURE__ */ new Set();
|
|
5840
|
-
for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
5841
|
-
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
5842
|
-
const result = def.valueType._zod.run({
|
|
5843
|
-
value: input[key],
|
|
5844
|
-
issues: []
|
|
5845
|
-
}, ctx);
|
|
5846
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5847
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5848
|
-
payload.value[key] = result$1.value;
|
|
5849
|
-
}));
|
|
5850
|
-
else {
|
|
5851
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5852
|
-
payload.value[key] = result.value;
|
|
5853
|
-
}
|
|
5854
|
-
}
|
|
5855
|
-
let unrecognized;
|
|
5856
|
-
for (const key in input) if (!recordKeys.has(key)) {
|
|
5857
|
-
unrecognized = unrecognized ?? [];
|
|
5858
|
-
unrecognized.push(key);
|
|
5859
|
-
}
|
|
5860
|
-
if (unrecognized && unrecognized.length > 0) payload.issues.push({
|
|
5861
|
-
code: "unrecognized_keys",
|
|
5862
|
-
input,
|
|
5863
|
-
inst,
|
|
5864
|
-
keys: unrecognized
|
|
5865
|
-
});
|
|
5866
|
-
} else {
|
|
5867
|
-
payload.value = {};
|
|
5868
|
-
for (const key of Reflect.ownKeys(input)) {
|
|
5869
|
-
if (key === "__proto__") continue;
|
|
5870
|
-
let keyResult = def.keyType._zod.run({
|
|
5871
|
-
value: key,
|
|
5872
|
-
issues: []
|
|
5873
|
-
}, ctx);
|
|
5874
|
-
if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5875
|
-
if (typeof key === "string" && number$1.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number")) {
|
|
5876
|
-
const retryResult = def.keyType._zod.run({
|
|
5877
|
-
value: Number(key),
|
|
5878
|
-
issues: []
|
|
5879
|
-
}, ctx);
|
|
5880
|
-
if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5881
|
-
if (retryResult.issues.length === 0) keyResult = retryResult;
|
|
5882
|
-
}
|
|
5883
|
-
if (keyResult.issues.length) {
|
|
5884
|
-
if (def.mode === "loose") payload.value[key] = input[key];
|
|
5885
|
-
else payload.issues.push({
|
|
5886
|
-
code: "invalid_key",
|
|
5887
|
-
origin: "record",
|
|
5888
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
5889
|
-
input: key,
|
|
5890
|
-
path: [key],
|
|
5891
|
-
inst
|
|
5892
|
-
});
|
|
5893
|
-
continue;
|
|
5894
|
-
}
|
|
5895
|
-
const result = def.valueType._zod.run({
|
|
5896
|
-
value: input[key],
|
|
5897
|
-
issues: []
|
|
5898
|
-
}, ctx);
|
|
5899
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5900
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5901
|
-
payload.value[keyResult.value] = result$1.value;
|
|
5902
|
-
}));
|
|
5903
|
-
else {
|
|
5904
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5905
|
-
payload.value[keyResult.value] = result.value;
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
if (proms.length) return Promise.all(proms).then(() => payload);
|
|
5910
|
-
return payload;
|
|
5911
|
-
};
|
|
5912
|
-
});
|
|
5913
5803
|
const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
5914
5804
|
$ZodType.init(inst, def);
|
|
5915
5805
|
const values = getEnumValues(def.entries);
|
|
@@ -6489,13 +6379,6 @@ function _int(Class, params) {
|
|
|
6489
6379
|
});
|
|
6490
6380
|
}
|
|
6491
6381
|
/* @__NO_SIDE_EFFECTS__ */
|
|
6492
|
-
function _boolean(Class, params) {
|
|
6493
|
-
return new Class({
|
|
6494
|
-
type: "boolean",
|
|
6495
|
-
...normalizeParams(params)
|
|
6496
|
-
});
|
|
6497
|
-
}
|
|
6498
|
-
/* @__NO_SIDE_EFFECTS__ */
|
|
6499
6382
|
function _unknown(Class) {
|
|
6500
6383
|
return new Class({ type: "unknown" });
|
|
6501
6384
|
}
|
|
@@ -7066,9 +6949,6 @@ const numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
7066
6949
|
}
|
|
7067
6950
|
if (typeof multipleOf === "number") json.multipleOf = multipleOf;
|
|
7068
6951
|
};
|
|
7069
|
-
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
7070
|
-
json.type = "boolean";
|
|
7071
|
-
};
|
|
7072
6952
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
7073
6953
|
json.not = {};
|
|
7074
6954
|
};
|
|
@@ -7200,39 +7080,6 @@ const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
7200
7080
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
7201
7081
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
7202
7082
|
};
|
|
7203
|
-
const recordProcessor = (schema, ctx, _json, params) => {
|
|
7204
|
-
const json = _json;
|
|
7205
|
-
const def = schema._zod.def;
|
|
7206
|
-
json.type = "object";
|
|
7207
|
-
const keyType = def.keyType;
|
|
7208
|
-
const patterns = keyType._zod.bag?.patterns;
|
|
7209
|
-
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
7210
|
-
const valueSchema = process$2(def.valueType, ctx, {
|
|
7211
|
-
...params,
|
|
7212
|
-
path: [
|
|
7213
|
-
...params.path,
|
|
7214
|
-
"patternProperties",
|
|
7215
|
-
"*"
|
|
7216
|
-
]
|
|
7217
|
-
});
|
|
7218
|
-
json.patternProperties = {};
|
|
7219
|
-
for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
|
|
7220
|
-
} else {
|
|
7221
|
-
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$2(def.keyType, ctx, {
|
|
7222
|
-
...params,
|
|
7223
|
-
path: [...params.path, "propertyNames"]
|
|
7224
|
-
});
|
|
7225
|
-
json.additionalProperties = process$2(def.valueType, ctx, {
|
|
7226
|
-
...params,
|
|
7227
|
-
path: [...params.path, "additionalProperties"]
|
|
7228
|
-
});
|
|
7229
|
-
}
|
|
7230
|
-
const keyValues = keyType._zod.values;
|
|
7231
|
-
if (keyValues) {
|
|
7232
|
-
const validKeyValues = [...keyValues].filter((v$1) => typeof v$1 === "string" || typeof v$1 === "number");
|
|
7233
|
-
if (validKeyValues.length > 0) json.required = validKeyValues;
|
|
7234
|
-
}
|
|
7235
|
-
};
|
|
7236
7083
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
7237
7084
|
const def = schema._zod.def;
|
|
7238
7085
|
const inner = process$2(def.innerType, ctx, params);
|
|
@@ -7625,14 +7472,6 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
|
|
|
7625
7472
|
function int(params) {
|
|
7626
7473
|
return _int(ZodNumberFormat, params);
|
|
7627
7474
|
}
|
|
7628
|
-
const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
7629
|
-
$ZodBoolean.init(inst, def);
|
|
7630
|
-
ZodType.init(inst, def);
|
|
7631
|
-
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
7632
|
-
});
|
|
7633
|
-
function boolean(params) {
|
|
7634
|
-
return _boolean(ZodBoolean, params);
|
|
7635
|
-
}
|
|
7636
7475
|
const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
7637
7476
|
$ZodUnknown.init(inst, def);
|
|
7638
7477
|
ZodType.init(inst, def);
|
|
@@ -7762,21 +7601,6 @@ function tuple(items, _paramsOrRest, _params) {
|
|
|
7762
7601
|
...normalizeParams(params)
|
|
7763
7602
|
});
|
|
7764
7603
|
}
|
|
7765
|
-
const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
7766
|
-
$ZodRecord.init(inst, def);
|
|
7767
|
-
ZodType.init(inst, def);
|
|
7768
|
-
inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);
|
|
7769
|
-
inst.keyType = def.keyType;
|
|
7770
|
-
inst.valueType = def.valueType;
|
|
7771
|
-
});
|
|
7772
|
-
function record(keyType, valueType, params) {
|
|
7773
|
-
return new ZodRecord({
|
|
7774
|
-
type: "record",
|
|
7775
|
-
keyType,
|
|
7776
|
-
valueType,
|
|
7777
|
-
...normalizeParams(params)
|
|
7778
|
-
});
|
|
7779
|
-
}
|
|
7780
7604
|
const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
7781
7605
|
$ZodEnum.init(inst, def);
|
|
7782
7606
|
ZodType.init(inst, def);
|
|
@@ -8039,19 +7863,6 @@ var AuthValidationError = class extends Error {
|
|
|
8039
7863
|
this.name = "AuthValidationError";
|
|
8040
7864
|
}
|
|
8041
7865
|
};
|
|
8042
|
-
var ConnectorApiError = class extends Error {
|
|
8043
|
-
constructor(message, cause) {
|
|
8044
|
-
super(message);
|
|
8045
|
-
this.cause = cause;
|
|
8046
|
-
this.name = "ConnectorApiError";
|
|
8047
|
-
}
|
|
8048
|
-
};
|
|
8049
|
-
var ConnectorValidationError = class extends Error {
|
|
8050
|
-
constructor(message) {
|
|
8051
|
-
super(message);
|
|
8052
|
-
this.name = "ConnectorValidationError";
|
|
8053
|
-
}
|
|
8054
|
-
};
|
|
8055
7866
|
|
|
8056
7867
|
//#endregion
|
|
8057
7868
|
//#region src/core/consts.ts
|
|
@@ -16849,13 +16660,10 @@ const SiteConfigSchema = object({
|
|
|
16849
16660
|
outputDirectory: string().optional(),
|
|
16850
16661
|
installCommand: string().optional()
|
|
16851
16662
|
});
|
|
16852
|
-
const ConnectorConfigSchema = object({ scopes: array(string()).optional() });
|
|
16853
|
-
const ConnectorsConfigSchema = record(string(), ConnectorConfigSchema);
|
|
16854
16663
|
const ProjectConfigSchema = object({
|
|
16855
16664
|
name: string().min(1, "App name cannot be empty"),
|
|
16856
16665
|
description: string().optional(),
|
|
16857
16666
|
site: SiteConfigSchema.optional(),
|
|
16858
|
-
connectors: ConnectorsConfigSchema.optional(),
|
|
16859
16667
|
entitiesDir: string().optional().default("entities"),
|
|
16860
16668
|
functionsDir: string().optional().default("functions")
|
|
16861
16669
|
});
|
|
@@ -31147,10 +30955,7 @@ const theme = {
|
|
|
31147
30955
|
base44OrangeBackground: source_default.bgHex("#E86B3C"),
|
|
31148
30956
|
shinyOrange: source_default.hex("#FFD700"),
|
|
31149
30957
|
links: source_default.hex("#00D4FF"),
|
|
31150
|
-
white: source_default.white
|
|
31151
|
-
success: source_default.green,
|
|
31152
|
-
warning: source_default.yellow,
|
|
31153
|
-
error: source_default.red
|
|
30958
|
+
white: source_default.white
|
|
31154
30959
|
},
|
|
31155
30960
|
styles: {
|
|
31156
30961
|
header: source_default.dim,
|
|
@@ -38984,658 +38789,6 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
|
|
|
38984
38789
|
await runCommand(() => deployAction(options), { requireAuth: true });
|
|
38985
38790
|
}));
|
|
38986
38791
|
|
|
38987
|
-
//#endregion
|
|
38988
|
-
//#region src/core/connectors/schema.ts
|
|
38989
|
-
/**
|
|
38990
|
-
* Response from POST /api/apps/{app_id}/external-auth/initiate
|
|
38991
|
-
*/
|
|
38992
|
-
const InitiateResponseSchema = object({
|
|
38993
|
-
redirect_url: string().nullish(),
|
|
38994
|
-
connection_id: string().nullish(),
|
|
38995
|
-
already_authorized: boolean().nullish(),
|
|
38996
|
-
other_user_email: string().nullish(),
|
|
38997
|
-
error: string().nullish()
|
|
38998
|
-
});
|
|
38999
|
-
/**
|
|
39000
|
-
* Response from GET /api/apps/{app_id}/external-auth/status
|
|
39001
|
-
*/
|
|
39002
|
-
const StatusResponseSchema = object({
|
|
39003
|
-
status: _enum([
|
|
39004
|
-
"ACTIVE",
|
|
39005
|
-
"PENDING",
|
|
39006
|
-
"FAILED"
|
|
39007
|
-
]),
|
|
39008
|
-
account_email: string().nullish(),
|
|
39009
|
-
error: string().nullish()
|
|
39010
|
-
}).transform((data) => ({
|
|
39011
|
-
status: data.status,
|
|
39012
|
-
accountEmail: data.account_email,
|
|
39013
|
-
error: data.error
|
|
39014
|
-
}));
|
|
39015
|
-
/**
|
|
39016
|
-
* A connected integration from the list endpoint
|
|
39017
|
-
*/
|
|
39018
|
-
const ConnectorSchema = object({
|
|
39019
|
-
integration_type: string(),
|
|
39020
|
-
status: string(),
|
|
39021
|
-
connected_at: string().nullish(),
|
|
39022
|
-
account_info: object({
|
|
39023
|
-
email: string().nullish(),
|
|
39024
|
-
name: string().nullish()
|
|
39025
|
-
}).nullish()
|
|
39026
|
-
}).transform((data) => ({
|
|
39027
|
-
integrationType: data.integration_type,
|
|
39028
|
-
status: data.status,
|
|
39029
|
-
connectedAt: data.connected_at,
|
|
39030
|
-
accountInfo: data.account_info
|
|
39031
|
-
}));
|
|
39032
|
-
/**
|
|
39033
|
-
* Response from GET /api/apps/{app_id}/external-auth/list
|
|
39034
|
-
*/
|
|
39035
|
-
const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
|
|
39036
|
-
/**
|
|
39037
|
-
* Generic API error response
|
|
39038
|
-
*/
|
|
39039
|
-
const ApiErrorSchema = object({
|
|
39040
|
-
error: string(),
|
|
39041
|
-
detail: string().nullish()
|
|
39042
|
-
});
|
|
39043
|
-
|
|
39044
|
-
//#endregion
|
|
39045
|
-
//#region src/core/connectors/api.ts
|
|
39046
|
-
/**
|
|
39047
|
-
* Initiates OAuth flow for a connector integration.
|
|
39048
|
-
* Returns a redirect URL to open in the browser.
|
|
39049
|
-
*/
|
|
39050
|
-
async function initiateOAuth(integrationType, scopes = null) {
|
|
39051
|
-
const response = await getAppClient().post("external-auth/initiate", {
|
|
39052
|
-
json: {
|
|
39053
|
-
integration_type: integrationType,
|
|
39054
|
-
scopes
|
|
39055
|
-
},
|
|
39056
|
-
throwHttpErrors: false
|
|
39057
|
-
});
|
|
39058
|
-
const json = await response.json();
|
|
39059
|
-
if (!response.ok) {
|
|
39060
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39061
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39062
|
-
throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
|
|
39063
|
-
}
|
|
39064
|
-
const result = InitiateResponseSchema.safeParse(json);
|
|
39065
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
|
|
39066
|
-
return result.data;
|
|
39067
|
-
}
|
|
39068
|
-
/**
|
|
39069
|
-
* Checks the status of an OAuth connection attempt.
|
|
39070
|
-
*/
|
|
39071
|
-
async function checkOAuthStatus(integrationType, connectionId) {
|
|
39072
|
-
const response = await getAppClient().get("external-auth/status", {
|
|
39073
|
-
searchParams: {
|
|
39074
|
-
integration_type: integrationType,
|
|
39075
|
-
connection_id: connectionId
|
|
39076
|
-
},
|
|
39077
|
-
throwHttpErrors: false
|
|
39078
|
-
});
|
|
39079
|
-
const json = await response.json();
|
|
39080
|
-
if (!response.ok) {
|
|
39081
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39082
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39083
|
-
throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
|
|
39084
|
-
}
|
|
39085
|
-
const result = StatusResponseSchema.safeParse(json);
|
|
39086
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
|
|
39087
|
-
return result.data;
|
|
39088
|
-
}
|
|
39089
|
-
/**
|
|
39090
|
-
* Lists all connected integrations for the current app.
|
|
39091
|
-
*/
|
|
39092
|
-
async function listConnectors() {
|
|
39093
|
-
const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
|
|
39094
|
-
const json = await response.json();
|
|
39095
|
-
if (!response.ok) {
|
|
39096
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39097
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39098
|
-
throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
|
|
39099
|
-
}
|
|
39100
|
-
const result = ListResponseSchema.safeParse(json);
|
|
39101
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
|
|
39102
|
-
return result.data.integrations;
|
|
39103
|
-
}
|
|
39104
|
-
/**
|
|
39105
|
-
* Disconnects (soft delete) a connector integration.
|
|
39106
|
-
*/
|
|
39107
|
-
async function disconnectConnector(integrationType) {
|
|
39108
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
|
|
39109
|
-
if (!response.ok) {
|
|
39110
|
-
const json = await response.json();
|
|
39111
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39112
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39113
|
-
throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
|
|
39114
|
-
}
|
|
39115
|
-
}
|
|
39116
|
-
/**
|
|
39117
|
-
* Removes (hard delete) a connector integration.
|
|
39118
|
-
* This permanently removes the connector and cannot be undone.
|
|
39119
|
-
*/
|
|
39120
|
-
async function removeConnector(integrationType) {
|
|
39121
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`, { throwHttpErrors: false });
|
|
39122
|
-
if (!response.ok) {
|
|
39123
|
-
const json = await response.json();
|
|
39124
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39125
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39126
|
-
throw new ConnectorApiError(`Failed to remove connector: ${response.status} ${response.statusText}`);
|
|
39127
|
-
}
|
|
39128
|
-
}
|
|
39129
|
-
|
|
39130
|
-
//#endregion
|
|
39131
|
-
//#region src/core/connectors/constants.ts
|
|
39132
|
-
/**
|
|
39133
|
-
* Supported OAuth connector integrations.
|
|
39134
|
-
* Based on apper/backend/app/external_auth/models/constants.py
|
|
39135
|
-
*/
|
|
39136
|
-
const SUPPORTED_INTEGRATIONS = [
|
|
39137
|
-
"googlecalendar",
|
|
39138
|
-
"googledrive",
|
|
39139
|
-
"gmail",
|
|
39140
|
-
"googlesheets",
|
|
39141
|
-
"googledocs",
|
|
39142
|
-
"googleslides",
|
|
39143
|
-
"slack",
|
|
39144
|
-
"notion",
|
|
39145
|
-
"salesforce",
|
|
39146
|
-
"hubspot",
|
|
39147
|
-
"linkedin",
|
|
39148
|
-
"tiktok"
|
|
39149
|
-
];
|
|
39150
|
-
/**
|
|
39151
|
-
* Display names for integrations (for CLI output)
|
|
39152
|
-
*/
|
|
39153
|
-
const INTEGRATION_DISPLAY_NAMES = {
|
|
39154
|
-
googlecalendar: "Google Calendar",
|
|
39155
|
-
googledrive: "Google Drive",
|
|
39156
|
-
gmail: "Gmail",
|
|
39157
|
-
googlesheets: "Google Sheets",
|
|
39158
|
-
googledocs: "Google Docs",
|
|
39159
|
-
googleslides: "Google Slides",
|
|
39160
|
-
slack: "Slack",
|
|
39161
|
-
notion: "Notion",
|
|
39162
|
-
salesforce: "Salesforce",
|
|
39163
|
-
hubspot: "HubSpot",
|
|
39164
|
-
linkedin: "LinkedIn",
|
|
39165
|
-
tiktok: "TikTok"
|
|
39166
|
-
};
|
|
39167
|
-
function isValidIntegration(type) {
|
|
39168
|
-
return SUPPORTED_INTEGRATIONS.includes(type);
|
|
39169
|
-
}
|
|
39170
|
-
function getIntegrationDisplayName(type) {
|
|
39171
|
-
if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
|
|
39172
|
-
return type;
|
|
39173
|
-
}
|
|
39174
|
-
|
|
39175
|
-
//#endregion
|
|
39176
|
-
//#region src/core/connectors/config.ts
|
|
39177
|
-
/**
|
|
39178
|
-
* Read all connectors from the project config file
|
|
39179
|
-
*/
|
|
39180
|
-
async function readLocalConnectors(projectRoot) {
|
|
39181
|
-
const found = await findProjectRoot(projectRoot);
|
|
39182
|
-
if (!found) return [];
|
|
39183
|
-
const connectorsData = (await readJsonFile(found.configPath)).connectors;
|
|
39184
|
-
if (!connectorsData) return [];
|
|
39185
|
-
const connectors = [];
|
|
39186
|
-
for (const [type, config$1] of Object.entries(connectorsData)) {
|
|
39187
|
-
if (!isValidIntegration(type)) throw new Error(`Unknown connector type: ${type}`);
|
|
39188
|
-
connectors.push({
|
|
39189
|
-
type,
|
|
39190
|
-
scopes: config$1.scopes
|
|
39191
|
-
});
|
|
39192
|
-
}
|
|
39193
|
-
return connectors;
|
|
39194
|
-
}
|
|
39195
|
-
/**
|
|
39196
|
-
* Write connectors to the project config file
|
|
39197
|
-
*/
|
|
39198
|
-
async function writeLocalConnectors(connectors, projectRoot) {
|
|
39199
|
-
const found = await findProjectRoot(projectRoot);
|
|
39200
|
-
if (!found) throw new Error("Project config not found. Run this command from a Base44 project directory.");
|
|
39201
|
-
const existingConfig = await readJsonFile(found.configPath);
|
|
39202
|
-
const connectorsData = {};
|
|
39203
|
-
for (const connector of connectors) connectorsData[connector.type] = { ...connector.scopes && { scopes: connector.scopes } };
|
|
39204
|
-
const updatedConfig = {
|
|
39205
|
-
...existingConfig,
|
|
39206
|
-
connectors: Object.keys(connectorsData).length > 0 ? connectorsData : void 0
|
|
39207
|
-
};
|
|
39208
|
-
if (!updatedConfig.connectors) delete updatedConfig.connectors;
|
|
39209
|
-
await writeJsonFile(found.configPath, updatedConfig);
|
|
39210
|
-
return found.configPath;
|
|
39211
|
-
}
|
|
39212
|
-
/**
|
|
39213
|
-
* Add a connector to the project config file
|
|
39214
|
-
*/
|
|
39215
|
-
async function addLocalConnector(type, scopes, projectRoot) {
|
|
39216
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39217
|
-
const existing = connectors.find((c$1) => c$1.type === type);
|
|
39218
|
-
if (existing) {
|
|
39219
|
-
if (scopes) existing.scopes = scopes;
|
|
39220
|
-
} else connectors.push({
|
|
39221
|
-
type,
|
|
39222
|
-
scopes
|
|
39223
|
-
});
|
|
39224
|
-
return await writeLocalConnectors(connectors, projectRoot);
|
|
39225
|
-
}
|
|
39226
|
-
/**
|
|
39227
|
-
* Remove a connector from the project config file
|
|
39228
|
-
*/
|
|
39229
|
-
async function removeLocalConnector(type, projectRoot) {
|
|
39230
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39231
|
-
const filtered = connectors.filter((c$1) => c$1.type !== type);
|
|
39232
|
-
if (filtered.length === connectors.length) return null;
|
|
39233
|
-
return await writeLocalConnectors(filtered, projectRoot);
|
|
39234
|
-
}
|
|
39235
|
-
|
|
39236
|
-
//#endregion
|
|
39237
|
-
//#region src/cli/commands/connectors/add.ts
|
|
39238
|
-
const POLL_INTERVAL_MS$1 = 2e3;
|
|
39239
|
-
const POLL_TIMEOUT_MS$1 = 300 * 1e3;
|
|
39240
|
-
async function promptForIntegrationType() {
|
|
39241
|
-
const selected = await ve({
|
|
39242
|
-
message: "Select an integration to connect:",
|
|
39243
|
-
options: SUPPORTED_INTEGRATIONS.map((type) => ({
|
|
39244
|
-
value: type,
|
|
39245
|
-
label: getIntegrationDisplayName(type)
|
|
39246
|
-
}))
|
|
39247
|
-
});
|
|
39248
|
-
if (pD(selected)) return null;
|
|
39249
|
-
return selected;
|
|
39250
|
-
}
|
|
39251
|
-
async function waitForOAuthCompletion(integrationType, connectionId) {
|
|
39252
|
-
let accountEmail;
|
|
39253
|
-
let error;
|
|
39254
|
-
try {
|
|
39255
|
-
await runTask("Waiting for authorization...", async (updateMessage) => {
|
|
39256
|
-
await pWaitFor(async () => {
|
|
39257
|
-
const status = await checkOAuthStatus(integrationType, connectionId);
|
|
39258
|
-
if (status.status === "ACTIVE") {
|
|
39259
|
-
accountEmail = status.accountEmail ?? void 0;
|
|
39260
|
-
return true;
|
|
39261
|
-
}
|
|
39262
|
-
if (status.status === "FAILED") {
|
|
39263
|
-
error = status.error || "Authorization failed";
|
|
39264
|
-
throw new Error(error);
|
|
39265
|
-
}
|
|
39266
|
-
updateMessage("Waiting for authorization in browser...");
|
|
39267
|
-
return false;
|
|
39268
|
-
}, {
|
|
39269
|
-
interval: POLL_INTERVAL_MS$1,
|
|
39270
|
-
timeout: POLL_TIMEOUT_MS$1
|
|
39271
|
-
});
|
|
39272
|
-
}, {
|
|
39273
|
-
successMessage: "Authorization completed!",
|
|
39274
|
-
errorMessage: "Authorization failed"
|
|
39275
|
-
});
|
|
39276
|
-
return {
|
|
39277
|
-
success: true,
|
|
39278
|
-
accountEmail
|
|
39279
|
-
};
|
|
39280
|
-
} catch (err) {
|
|
39281
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39282
|
-
success: false,
|
|
39283
|
-
error: "Authorization timed out. Please try again."
|
|
39284
|
-
};
|
|
39285
|
-
return {
|
|
39286
|
-
success: false,
|
|
39287
|
-
error: error || (err instanceof Error ? err.message : "Unknown error")
|
|
39288
|
-
};
|
|
39289
|
-
}
|
|
39290
|
-
}
|
|
39291
|
-
async function addConnector(integrationType) {
|
|
39292
|
-
let selectedType;
|
|
39293
|
-
if (!integrationType) {
|
|
39294
|
-
const prompted = await promptForIntegrationType();
|
|
39295
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39296
|
-
selectedType = prompted;
|
|
39297
|
-
} else {
|
|
39298
|
-
if (!isValidIntegration(integrationType)) {
|
|
39299
|
-
const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
|
|
39300
|
-
throw new Error(`Unsupported connector: ${integrationType}\nSupported connectors: ${supportedList}`);
|
|
39301
|
-
}
|
|
39302
|
-
selectedType = integrationType;
|
|
39303
|
-
}
|
|
39304
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39305
|
-
const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
|
|
39306
|
-
return await initiateOAuth(selectedType);
|
|
39307
|
-
}, {
|
|
39308
|
-
successMessage: `${displayName} OAuth initiated`,
|
|
39309
|
-
errorMessage: `Failed to initiate ${displayName} connection`
|
|
39310
|
-
});
|
|
39311
|
-
if (initiateResponse.already_authorized) {
|
|
39312
|
-
await addLocalConnector(selectedType);
|
|
39313
|
-
return { outroMessage: `Already connected to ${theme.styles.bold(displayName)} (added to config)` };
|
|
39314
|
-
}
|
|
39315
|
-
if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
|
|
39316
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
|
|
39317
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirect_url)}`);
|
|
39318
|
-
const result = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
|
|
39319
|
-
if (!result.success) throw new Error(result.error || "Authorization failed");
|
|
39320
|
-
await addLocalConnector(selectedType);
|
|
39321
|
-
const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
|
|
39322
|
-
return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
|
|
39323
|
-
}
|
|
39324
|
-
const connectorsAddCommand = new Command("add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
|
|
39325
|
-
await runCommand(() => addConnector(type), {
|
|
39326
|
-
requireAuth: true,
|
|
39327
|
-
requireAppConfig: true
|
|
39328
|
-
});
|
|
39329
|
-
});
|
|
39330
|
-
|
|
39331
|
-
//#endregion
|
|
39332
|
-
//#region src/cli/commands/connectors/list.ts
|
|
39333
|
-
function mergeConnectors(local, backend) {
|
|
39334
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39335
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39336
|
-
type: connector.type,
|
|
39337
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39338
|
-
inLocal: true,
|
|
39339
|
-
inBackend: false
|
|
39340
|
-
});
|
|
39341
|
-
for (const connector of backend) {
|
|
39342
|
-
const existing = merged.get(connector.integrationType);
|
|
39343
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39344
|
-
if (existing) {
|
|
39345
|
-
existing.inBackend = true;
|
|
39346
|
-
existing.status = connector.status;
|
|
39347
|
-
existing.accountEmail = accountEmail;
|
|
39348
|
-
} else merged.set(connector.integrationType, {
|
|
39349
|
-
type: connector.integrationType,
|
|
39350
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39351
|
-
inLocal: false,
|
|
39352
|
-
inBackend: true,
|
|
39353
|
-
status: connector.status,
|
|
39354
|
-
accountEmail
|
|
39355
|
-
});
|
|
39356
|
-
}
|
|
39357
|
-
return Array.from(merged.values());
|
|
39358
|
-
}
|
|
39359
|
-
function formatConnectorLine(connector) {
|
|
39360
|
-
const { displayName, inLocal, inBackend, status, accountEmail } = connector;
|
|
39361
|
-
const isConnected$1 = inBackend && status?.toLowerCase() === "active";
|
|
39362
|
-
const isPending = inLocal && !inBackend;
|
|
39363
|
-
const isOrphaned = inBackend && !inLocal;
|
|
39364
|
-
let bullet;
|
|
39365
|
-
let statusText = "";
|
|
39366
|
-
if (isConnected$1) {
|
|
39367
|
-
bullet = theme.colors.success("●");
|
|
39368
|
-
if (accountEmail) statusText = ` - ${accountEmail}`;
|
|
39369
|
-
} else if (isPending) {
|
|
39370
|
-
bullet = theme.colors.warning("○");
|
|
39371
|
-
statusText = theme.styles.dim(" (not connected)");
|
|
39372
|
-
} else if (isOrphaned) {
|
|
39373
|
-
bullet = theme.colors.error("○");
|
|
39374
|
-
statusText = theme.styles.dim(" (not in local config)");
|
|
39375
|
-
} else {
|
|
39376
|
-
bullet = theme.colors.error("○");
|
|
39377
|
-
statusText = theme.styles.dim(` (${status || "disconnected"})`);
|
|
39378
|
-
}
|
|
39379
|
-
return `${bullet} ${displayName}${statusText}`;
|
|
39380
|
-
}
|
|
39381
|
-
async function listConnectorsCommand() {
|
|
39382
|
-
const [localConnectors, backendConnectors] = await runTask("Fetching connectors...", async () => {
|
|
39383
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39384
|
-
return [local, backend];
|
|
39385
|
-
}, {
|
|
39386
|
-
successMessage: "Connectors loaded",
|
|
39387
|
-
errorMessage: "Failed to fetch connectors"
|
|
39388
|
-
});
|
|
39389
|
-
const merged = mergeConnectors(localConnectors, backendConnectors);
|
|
39390
|
-
if (merged.length === 0) {
|
|
39391
|
-
M.info("No connectors configured for this app.");
|
|
39392
|
-
M.info(`Run ${theme.styles.bold("base44 connectors add")} to connect an integration.`);
|
|
39393
|
-
return { outroMessage: "" };
|
|
39394
|
-
}
|
|
39395
|
-
console.log();
|
|
39396
|
-
for (const connector of merged) console.log(formatConnectorLine(connector));
|
|
39397
|
-
console.log();
|
|
39398
|
-
const connected = merged.filter((c$1) => c$1.inBackend && c$1.status?.toLowerCase() === "active").length;
|
|
39399
|
-
const pending = merged.filter((c$1) => c$1.inLocal && !c$1.inBackend).length;
|
|
39400
|
-
let summary = `${connected} connected`;
|
|
39401
|
-
if (pending > 0) {
|
|
39402
|
-
summary += `, ${pending} pending`;
|
|
39403
|
-
M.info(`Run ${theme.styles.bold("base44 connectors push")} to connect pending integrations.`);
|
|
39404
|
-
}
|
|
39405
|
-
return { outroMessage: summary };
|
|
39406
|
-
}
|
|
39407
|
-
const connectorsListCommand = new Command("list").description("List all connected OAuth integrations").action(async () => {
|
|
39408
|
-
await runCommand(listConnectorsCommand, {
|
|
39409
|
-
requireAuth: true,
|
|
39410
|
-
requireAppConfig: true
|
|
39411
|
-
});
|
|
39412
|
-
});
|
|
39413
|
-
|
|
39414
|
-
//#endregion
|
|
39415
|
-
//#region src/cli/commands/connectors/push.ts
|
|
39416
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
39417
|
-
const POLL_TIMEOUT_MS = 300 * 1e3;
|
|
39418
|
-
function findPendingConnectors(local, backend) {
|
|
39419
|
-
const connectedTypes = new Set(backend.filter((c$1) => c$1.status.toLowerCase() === "active").map((c$1) => c$1.integrationType));
|
|
39420
|
-
return local.filter((c$1) => !connectedTypes.has(c$1.type)).map((c$1) => ({
|
|
39421
|
-
type: c$1.type,
|
|
39422
|
-
displayName: getIntegrationDisplayName(c$1.type),
|
|
39423
|
-
scopes: c$1.scopes
|
|
39424
|
-
}));
|
|
39425
|
-
}
|
|
39426
|
-
function findOrphanedConnectors(local, backend) {
|
|
39427
|
-
const localTypes = new Set(local.map((c$1) => c$1.type));
|
|
39428
|
-
return backend.filter((c$1) => c$1.status.toLowerCase() === "active").filter((c$1) => !localTypes.has(c$1.integrationType)).filter((c$1) => isValidIntegration(c$1.integrationType)).map((c$1) => ({
|
|
39429
|
-
type: c$1.integrationType,
|
|
39430
|
-
displayName: getIntegrationDisplayName(c$1.integrationType),
|
|
39431
|
-
accountEmail: (c$1.accountInfo?.email || c$1.accountInfo?.name) ?? void 0
|
|
39432
|
-
}));
|
|
39433
|
-
}
|
|
39434
|
-
async function connectSingleConnector(connector) {
|
|
39435
|
-
const { type, displayName, scopes } = connector;
|
|
39436
|
-
const initiateResponse = await initiateOAuth(type, scopes || null);
|
|
39437
|
-
if (initiateResponse.already_authorized) return { success: true };
|
|
39438
|
-
if (initiateResponse.error === "different_user") return {
|
|
39439
|
-
success: false,
|
|
39440
|
-
error: `Already connected by ${initiateResponse.other_user_email}`
|
|
39441
|
-
};
|
|
39442
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) return {
|
|
39443
|
-
success: false,
|
|
39444
|
-
error: "Invalid response from server"
|
|
39445
|
-
};
|
|
39446
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirect_url)}`);
|
|
39447
|
-
let accountEmail;
|
|
39448
|
-
try {
|
|
39449
|
-
await pWaitFor(async () => {
|
|
39450
|
-
const status = await checkOAuthStatus(type, initiateResponse.connection_id);
|
|
39451
|
-
if (status.status === "ACTIVE") {
|
|
39452
|
-
accountEmail = status.accountEmail ?? void 0;
|
|
39453
|
-
return true;
|
|
39454
|
-
}
|
|
39455
|
-
if (status.status === "FAILED") throw new Error(status.error || "Authorization failed");
|
|
39456
|
-
return false;
|
|
39457
|
-
}, {
|
|
39458
|
-
interval: POLL_INTERVAL_MS,
|
|
39459
|
-
timeout: POLL_TIMEOUT_MS
|
|
39460
|
-
});
|
|
39461
|
-
return {
|
|
39462
|
-
success: true,
|
|
39463
|
-
accountEmail
|
|
39464
|
-
};
|
|
39465
|
-
} catch (err) {
|
|
39466
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39467
|
-
success: false,
|
|
39468
|
-
error: "Authorization timed out"
|
|
39469
|
-
};
|
|
39470
|
-
return {
|
|
39471
|
-
success: false,
|
|
39472
|
-
error: err instanceof Error ? err.message : "Unknown error"
|
|
39473
|
-
};
|
|
39474
|
-
}
|
|
39475
|
-
}
|
|
39476
|
-
async function pushConnectorsCommand() {
|
|
39477
|
-
const [localConnectors, backendConnectors] = await runTask("Checking connector status...", async () => {
|
|
39478
|
-
const [local, backend] = await Promise.all([readLocalConnectors(), listConnectors().catch(() => [])]);
|
|
39479
|
-
return [local, backend];
|
|
39480
|
-
}, {
|
|
39481
|
-
successMessage: "Status checked",
|
|
39482
|
-
errorMessage: "Failed to check status"
|
|
39483
|
-
});
|
|
39484
|
-
const pending = findPendingConnectors(localConnectors, backendConnectors);
|
|
39485
|
-
const orphaned = findOrphanedConnectors(localConnectors, backendConnectors);
|
|
39486
|
-
if (pending.length === 0 && orphaned.length === 0) return { outroMessage: "All connectors are in sync" };
|
|
39487
|
-
console.log();
|
|
39488
|
-
if (pending.length > 0) {
|
|
39489
|
-
M.info(`${pending.length} connector${pending.length === 1 ? "" : "s"} to connect:`);
|
|
39490
|
-
for (const c$1 of pending) console.log(` ${theme.colors.success("+")} ${c$1.displayName}`);
|
|
39491
|
-
}
|
|
39492
|
-
if (orphaned.length > 0) {
|
|
39493
|
-
M.info(`${orphaned.length} connector${orphaned.length === 1 ? "" : "s"} to remove:`);
|
|
39494
|
-
for (const c$1 of orphaned) {
|
|
39495
|
-
const accountInfo = c$1.accountEmail ? ` (${c$1.accountEmail})` : "";
|
|
39496
|
-
console.log(` ${theme.colors.error("-")} ${c$1.displayName}${accountInfo}`);
|
|
39497
|
-
}
|
|
39498
|
-
}
|
|
39499
|
-
console.log();
|
|
39500
|
-
const totalChanges = pending.length + orphaned.length;
|
|
39501
|
-
const shouldProceed = await ye({
|
|
39502
|
-
message: `Apply ${totalChanges} change${totalChanges === 1 ? "" : "s"}?`,
|
|
39503
|
-
initialValue: true
|
|
39504
|
-
});
|
|
39505
|
-
if (pD(shouldProceed) || !shouldProceed) return { outroMessage: "Cancelled" };
|
|
39506
|
-
let connected = 0;
|
|
39507
|
-
let removed = 0;
|
|
39508
|
-
let failed = 0;
|
|
39509
|
-
for (const connector of orphaned) try {
|
|
39510
|
-
await disconnectConnector(connector.type);
|
|
39511
|
-
M.success(`Removed ${connector.displayName}`);
|
|
39512
|
-
removed++;
|
|
39513
|
-
} catch (err) {
|
|
39514
|
-
M.error(`Failed to remove ${connector.displayName}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
39515
|
-
failed++;
|
|
39516
|
-
}
|
|
39517
|
-
for (const connector of pending) {
|
|
39518
|
-
console.log();
|
|
39519
|
-
M.info(`Connecting ${theme.styles.bold(connector.displayName)}...`);
|
|
39520
|
-
const result = await connectSingleConnector(connector);
|
|
39521
|
-
if (result.success) {
|
|
39522
|
-
const accountInfo = result.accountEmail ? ` as ${result.accountEmail}` : "";
|
|
39523
|
-
M.success(`${connector.displayName} connected${accountInfo}`);
|
|
39524
|
-
connected++;
|
|
39525
|
-
} else {
|
|
39526
|
-
M.error(`${connector.displayName} failed: ${result.error}`);
|
|
39527
|
-
failed++;
|
|
39528
|
-
}
|
|
39529
|
-
}
|
|
39530
|
-
console.log();
|
|
39531
|
-
const parts = [];
|
|
39532
|
-
if (connected > 0) parts.push(`${connected} connected`);
|
|
39533
|
-
if (removed > 0) parts.push(`${removed} removed`);
|
|
39534
|
-
if (failed > 0) parts.push(`${failed} failed`);
|
|
39535
|
-
return { outroMessage: parts.join(", ") };
|
|
39536
|
-
}
|
|
39537
|
-
const connectorsPushCommand = new Command("push").description("Sync connectors with backend (connect new, remove missing)").action(async () => {
|
|
39538
|
-
await runCommand(pushConnectorsCommand, {
|
|
39539
|
-
requireAuth: true,
|
|
39540
|
-
requireAppConfig: true
|
|
39541
|
-
});
|
|
39542
|
-
});
|
|
39543
|
-
|
|
39544
|
-
//#endregion
|
|
39545
|
-
//#region src/cli/commands/connectors/remove.ts
|
|
39546
|
-
function mergeConnectorsForRemoval(local, backend) {
|
|
39547
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39548
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39549
|
-
type: connector.type,
|
|
39550
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39551
|
-
inLocal: true,
|
|
39552
|
-
inBackend: false
|
|
39553
|
-
});
|
|
39554
|
-
for (const connector of backend) {
|
|
39555
|
-
if (!isValidIntegration(connector.integrationType)) continue;
|
|
39556
|
-
const existing = merged.get(connector.integrationType);
|
|
39557
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39558
|
-
if (existing) {
|
|
39559
|
-
existing.inBackend = true;
|
|
39560
|
-
existing.accountEmail = accountEmail;
|
|
39561
|
-
} else merged.set(connector.integrationType, {
|
|
39562
|
-
type: connector.integrationType,
|
|
39563
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39564
|
-
inLocal: false,
|
|
39565
|
-
inBackend: true,
|
|
39566
|
-
accountEmail
|
|
39567
|
-
});
|
|
39568
|
-
}
|
|
39569
|
-
return Array.from(merged.values());
|
|
39570
|
-
}
|
|
39571
|
-
async function promptForConnectorToRemove(connectors) {
|
|
39572
|
-
const selected = await ve({
|
|
39573
|
-
message: "Select a connector to remove:",
|
|
39574
|
-
options: connectors.map((c$1) => {
|
|
39575
|
-
let label = c$1.displayName;
|
|
39576
|
-
if (c$1.accountEmail) label += ` (${c$1.accountEmail})`;
|
|
39577
|
-
else if (c$1.inLocal && !c$1.inBackend) label += " (not connected)";
|
|
39578
|
-
return {
|
|
39579
|
-
value: c$1.type,
|
|
39580
|
-
label
|
|
39581
|
-
};
|
|
39582
|
-
})
|
|
39583
|
-
});
|
|
39584
|
-
if (pD(selected)) return null;
|
|
39585
|
-
return selected;
|
|
39586
|
-
}
|
|
39587
|
-
async function removeConnectorCommand(integrationType, options = {}) {
|
|
39588
|
-
const isHardDelete = options.hard === true;
|
|
39589
|
-
const [localConnectors, backendConnectors] = await runTask("Fetching connectors...", async () => {
|
|
39590
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39591
|
-
return [local, backend];
|
|
39592
|
-
}, {
|
|
39593
|
-
successMessage: "Connectors loaded",
|
|
39594
|
-
errorMessage: "Failed to fetch connectors"
|
|
39595
|
-
});
|
|
39596
|
-
const merged = mergeConnectorsForRemoval(localConnectors, backendConnectors);
|
|
39597
|
-
if (merged.length === 0) return { outroMessage: "No connectors to remove" };
|
|
39598
|
-
let selectedType;
|
|
39599
|
-
let selectedConnector;
|
|
39600
|
-
if (!integrationType) {
|
|
39601
|
-
const prompted = await promptForConnectorToRemove(merged);
|
|
39602
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39603
|
-
selectedType = prompted;
|
|
39604
|
-
selectedConnector = merged.find((c$1) => c$1.type === selectedType);
|
|
39605
|
-
} else {
|
|
39606
|
-
if (!isValidIntegration(integrationType)) throw new Error(`Invalid connector type: ${integrationType}`);
|
|
39607
|
-
selectedConnector = merged.find((c$1) => c$1.type === integrationType);
|
|
39608
|
-
if (!selectedConnector) throw new Error(`No ${getIntegrationDisplayName(integrationType)} connector found`);
|
|
39609
|
-
selectedType = integrationType;
|
|
39610
|
-
}
|
|
39611
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39612
|
-
const accountInfo = selectedConnector?.accountEmail ? ` (${selectedConnector.accountEmail})` : "";
|
|
39613
|
-
const shouldRemove = await ye({
|
|
39614
|
-
message: `${isHardDelete ? "Permanently remove" : "Remove"} ${displayName}${accountInfo}?`,
|
|
39615
|
-
initialValue: false
|
|
39616
|
-
});
|
|
39617
|
-
if (pD(shouldRemove) || !shouldRemove) return { outroMessage: "Cancelled" };
|
|
39618
|
-
await runTask(isHardDelete ? `Removing ${displayName}...` : `Removing ${displayName}...`, async () => {
|
|
39619
|
-
if (selectedConnector?.inBackend) if (isHardDelete) await removeConnector(selectedType);
|
|
39620
|
-
else await disconnectConnector(selectedType);
|
|
39621
|
-
await removeLocalConnector(selectedType);
|
|
39622
|
-
}, {
|
|
39623
|
-
successMessage: `${displayName} removed`,
|
|
39624
|
-
errorMessage: `Failed to remove ${displayName}`
|
|
39625
|
-
});
|
|
39626
|
-
return { outroMessage: `Successfully removed ${theme.styles.bold(displayName)}` };
|
|
39627
|
-
}
|
|
39628
|
-
const connectorsRemoveCommand = new Command("remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").option("--hard", "Permanently remove the connector (cannot be undone)").description("Remove an OAuth integration").action(async (type, options) => {
|
|
39629
|
-
await runCommand(() => removeConnectorCommand(type, options), {
|
|
39630
|
-
requireAuth: true,
|
|
39631
|
-
requireAppConfig: true
|
|
39632
|
-
});
|
|
39633
|
-
});
|
|
39634
|
-
|
|
39635
|
-
//#endregion
|
|
39636
|
-
//#region src/cli/commands/connectors/index.ts
|
|
39637
|
-
const connectorsCommand = new Command("connectors").description("Manage OAuth connectors").addCommand(connectorsAddCommand).addCommand(connectorsListCommand).addCommand(connectorsPushCommand).addCommand(connectorsRemoveCommand);
|
|
39638
|
-
|
|
39639
38792
|
//#endregion
|
|
39640
38793
|
//#region package.json
|
|
39641
38794
|
var version = "0.0.15";
|
|
@@ -39655,7 +38808,6 @@ program.addCommand(linkCommand);
|
|
|
39655
38808
|
program.addCommand(entitiesPushCommand);
|
|
39656
38809
|
program.addCommand(functionsDeployCommand);
|
|
39657
38810
|
program.addCommand(siteDeployCommand);
|
|
39658
|
-
program.addCommand(connectorsCommand);
|
|
39659
38811
|
program.parse();
|
|
39660
38812
|
|
|
39661
38813
|
//#endregion
|
package/package.json
CHANGED