@base44-preview/cli 0.0.15-pr.99.a90e72b → 0.0.16-pr.107.c96948c
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 +23 -47
- package/dist/cli/index.js +94 -825
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,9 +43,31 @@ base44 deploy
|
|
|
43
43
|
| Command | Description |
|
|
44
44
|
|---------|-------------|
|
|
45
45
|
| `base44 create` | Create a new Base44 project from a template |
|
|
46
|
-
| `base44 link` | Link
|
|
46
|
+
| `base44 link` | Link a local project to Base44 (create new or link existing) |
|
|
47
47
|
| `base44 dashboard` | Open the app dashboard in your browser |
|
|
48
48
|
|
|
49
|
+
#### Link Command Options
|
|
50
|
+
|
|
51
|
+
The `link` command supports both creating new projects and linking to existing ones:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Interactive mode - choose to create new or link existing
|
|
55
|
+
base44 link
|
|
56
|
+
|
|
57
|
+
# Create a new project (non-interactive)
|
|
58
|
+
base44 link --create --name "my-app" --description "My app description"
|
|
59
|
+
|
|
60
|
+
# Link to an existing project by ID (non-interactive)
|
|
61
|
+
base44 link --projectId <app-id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Option | Description |
|
|
65
|
+
|--------|-------------|
|
|
66
|
+
| `-c, --create` | Create a new project (skip selection prompt) |
|
|
67
|
+
| `-n, --name <name>` | Project name (required with --create) |
|
|
68
|
+
| `-d, --description <desc>` | Project description (optional) |
|
|
69
|
+
| `-p, --projectId <id>` | Link to an existing project by ID (skips selection prompt) |
|
|
70
|
+
|
|
49
71
|
### Deployment
|
|
50
72
|
|
|
51
73
|
| Command | Description |
|
|
@@ -70,52 +92,6 @@ base44 deploy
|
|
|
70
92
|
|---------|-------------|
|
|
71
93
|
| `base44 site deploy` | Deploy built site files to Base44 hosting |
|
|
72
94
|
|
|
73
|
-
### Connectors
|
|
74
|
-
|
|
75
|
-
Manage OAuth integrations to connect your app with external services. Connectors are tracked in a local `connectors.jsonc` file and synced with the backend.
|
|
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` | Connect all pending integrations from local config |
|
|
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 connectors (saves to connectors.jsonc and opens OAuth)
|
|
90
|
-
base44 connectors:add slack
|
|
91
|
-
base44 connectors:add googlecalendar
|
|
92
|
-
|
|
93
|
-
# List connectors showing local vs connected status
|
|
94
|
-
base44 connectors:list
|
|
95
|
-
# Output:
|
|
96
|
-
# ● Slack - user@example.com
|
|
97
|
-
# ○ Google Calendar (not connected)
|
|
98
|
-
|
|
99
|
-
# Connect all pending integrations
|
|
100
|
-
base44 connectors:push
|
|
101
|
-
|
|
102
|
-
# Remove a connector
|
|
103
|
-
base44 connectors:remove slack
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Local configuration** (`base44/connectors.jsonc`):
|
|
107
|
-
```jsonc
|
|
108
|
-
{
|
|
109
|
-
"slack": {},
|
|
110
|
-
"googlecalendar": { "scopes": ["calendar.readonly"] }
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Once connected, use the SDK's `connectors.getAccessToken()` to retrieve tokens:
|
|
115
|
-
```javascript
|
|
116
|
-
const token = await base44.connectors.getAccessToken("slack");
|
|
117
|
-
```
|
|
118
|
-
|
|
119
95
|
## Configuration
|
|
120
96
|
|
|
121
97
|
### Project Configuration
|
package/dist/cli/index.js
CHANGED
|
@@ -5819,97 +5819,6 @@ function handleTupleResult(result, final, index) {
|
|
|
5819
5819
|
if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
|
|
5820
5820
|
final.value[index] = result.value;
|
|
5821
5821
|
}
|
|
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
5822
|
const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
5914
5823
|
$ZodType.init(inst, def);
|
|
5915
5824
|
const values = getEnumValues(def.entries);
|
|
@@ -7200,39 +7109,6 @@ const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
7200
7109
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
7201
7110
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
7202
7111
|
};
|
|
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
7112
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
7237
7113
|
const def = schema._zod.def;
|
|
7238
7114
|
const inner = process$2(def.innerType, ctx, params);
|
|
@@ -7762,21 +7638,6 @@ function tuple(items, _paramsOrRest, _params) {
|
|
|
7762
7638
|
...normalizeParams(params)
|
|
7763
7639
|
});
|
|
7764
7640
|
}
|
|
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
7641
|
const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
7781
7642
|
$ZodEnum.init(inst, def);
|
|
7782
7643
|
ZodType.init(inst, def);
|
|
@@ -8039,19 +7900,6 @@ var AuthValidationError = class extends Error {
|
|
|
8039
7900
|
this.name = "AuthValidationError";
|
|
8040
7901
|
}
|
|
8041
7902
|
};
|
|
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
7903
|
|
|
8056
7904
|
//#endregion
|
|
8057
7905
|
//#region src/core/consts.ts
|
|
@@ -16858,6 +16706,13 @@ const ProjectConfigSchema = object({
|
|
|
16858
16706
|
});
|
|
16859
16707
|
const AppConfigSchema = object({ id: string().min(1, "id cannot be empty") });
|
|
16860
16708
|
const CreateProjectResponseSchema = looseObject({ id: string() });
|
|
16709
|
+
const ProjectSchema = object({
|
|
16710
|
+
id: string(),
|
|
16711
|
+
name: string(),
|
|
16712
|
+
userDescription: string().optional(),
|
|
16713
|
+
isManagedSourceCode: boolean().optional()
|
|
16714
|
+
});
|
|
16715
|
+
const ProjectsResponseSchema = array(ProjectSchema);
|
|
16861
16716
|
|
|
16862
16717
|
//#endregion
|
|
16863
16718
|
//#region src/core/project/config.ts
|
|
@@ -16942,6 +16797,13 @@ async function createProject(projectName, description) {
|
|
|
16942
16797
|
} });
|
|
16943
16798
|
return { projectId: CreateProjectResponseSchema.parse(await response.json()).id };
|
|
16944
16799
|
}
|
|
16800
|
+
async function listProjects() {
|
|
16801
|
+
const response = await base44Client.get(`api/apps`, { searchParams: {
|
|
16802
|
+
"sort": "-updated_date",
|
|
16803
|
+
"fields": "id,name,user_description,is_managed_source_code"
|
|
16804
|
+
} });
|
|
16805
|
+
return ProjectsResponseSchema.parse(await response.json());
|
|
16806
|
+
}
|
|
16945
16807
|
|
|
16946
16808
|
//#endregion
|
|
16947
16809
|
//#region node_modules/ejs/lib/utils.js
|
|
@@ -31144,10 +31006,7 @@ const theme = {
|
|
|
31144
31006
|
base44OrangeBackground: source_default.bgHex("#E86B3C"),
|
|
31145
31007
|
shinyOrange: source_default.hex("#FFD700"),
|
|
31146
31008
|
links: source_default.hex("#00D4FF"),
|
|
31147
|
-
white: source_default.white
|
|
31148
|
-
success: source_default.green,
|
|
31149
|
-
warning: source_default.yellow,
|
|
31150
|
-
error: source_default.red
|
|
31009
|
+
white: source_default.white
|
|
31151
31010
|
},
|
|
31152
31011
|
styles: {
|
|
31153
31012
|
header: source_default.dim,
|
|
@@ -38898,20 +38757,33 @@ const deployCommand = new Command("deploy").description("Deploy all project reso
|
|
|
38898
38757
|
//#endregion
|
|
38899
38758
|
//#region src/cli/commands/project/link.ts
|
|
38900
38759
|
function validateNonInteractiveFlags(command) {
|
|
38901
|
-
const { create: create$1, name: name$1 } = command.opts();
|
|
38760
|
+
const { create: create$1, name: name$1, projectId } = command.opts();
|
|
38761
|
+
if (create$1 && projectId) command.error("--create and --projectId cannot be used together");
|
|
38902
38762
|
if (create$1 && !name$1) command.error("--name is required when using --create");
|
|
38903
38763
|
}
|
|
38904
|
-
async function
|
|
38764
|
+
async function promptForLinkAction() {
|
|
38905
38765
|
const actionOptions = [{
|
|
38906
38766
|
value: "create",
|
|
38907
38767
|
label: "Create a new project",
|
|
38908
38768
|
hint: "Create a new Base44 project and link it"
|
|
38909
38769
|
}];
|
|
38770
|
+
actionOptions.push({
|
|
38771
|
+
value: "choose",
|
|
38772
|
+
label: "Link an existing project",
|
|
38773
|
+
hint: `Choose from one of your available projects previously created by the Base44 CLI`
|
|
38774
|
+
});
|
|
38775
|
+
const action = await ve({
|
|
38776
|
+
message: "How would you like to link this project?",
|
|
38777
|
+
options: actionOptions
|
|
38778
|
+
});
|
|
38779
|
+
if (pD(action)) {
|
|
38780
|
+
xe("Operation cancelled.");
|
|
38781
|
+
process.exit(0);
|
|
38782
|
+
}
|
|
38783
|
+
return action;
|
|
38784
|
+
}
|
|
38785
|
+
async function promptForNewProjectDetails() {
|
|
38910
38786
|
const result = await Ce({
|
|
38911
|
-
action: () => ve({
|
|
38912
|
-
message: "How would you like to link this project?",
|
|
38913
|
-
options: actionOptions
|
|
38914
|
-
}),
|
|
38915
38787
|
name: () => {
|
|
38916
38788
|
return he({
|
|
38917
38789
|
message: "What is the name of your project?",
|
|
@@ -38931,29 +38803,71 @@ async function promptForProjectDetails() {
|
|
|
38931
38803
|
description: result.description ? result.description.trim() : void 0
|
|
38932
38804
|
};
|
|
38933
38805
|
}
|
|
38806
|
+
async function promptForExistingProject(linkableProjects) {
|
|
38807
|
+
const selectedProject = await ve({
|
|
38808
|
+
message: "Choose a project to link",
|
|
38809
|
+
options: linkableProjects.map((project) => ({
|
|
38810
|
+
value: project,
|
|
38811
|
+
label: project.name
|
|
38812
|
+
}))
|
|
38813
|
+
});
|
|
38814
|
+
if (pD(selectedProject)) {
|
|
38815
|
+
xe("Operation cancelled.");
|
|
38816
|
+
process.exit(0);
|
|
38817
|
+
}
|
|
38818
|
+
return selectedProject;
|
|
38819
|
+
}
|
|
38934
38820
|
async function link(options) {
|
|
38935
38821
|
const projectRoot = await findProjectRoot();
|
|
38936
38822
|
if (!projectRoot) throw new Error("No Base44 project found. Run this command from a project directory with a config.jsonc file.");
|
|
38937
38823
|
if (await appConfigExists(projectRoot.root)) throw new Error("Project is already linked. An .app.jsonc file with the appId already exists.");
|
|
38938
|
-
|
|
38939
|
-
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
38946
|
-
|
|
38947
|
-
|
|
38948
|
-
|
|
38949
|
-
|
|
38950
|
-
|
|
38951
|
-
|
|
38952
|
-
|
|
38953
|
-
|
|
38824
|
+
let finalProjectId;
|
|
38825
|
+
const action = options.projectId ? "choose" : options.create ? "create" : await promptForLinkAction();
|
|
38826
|
+
if (action === "choose") {
|
|
38827
|
+
const linkableProjects = (await runTask("Fetching projects...", async () => listProjects(), {
|
|
38828
|
+
successMessage: "Projects fetched",
|
|
38829
|
+
errorMessage: "Failed to fetch projects"
|
|
38830
|
+
})).filter((p$1) => p$1.isManagedSourceCode !== true);
|
|
38831
|
+
if (!linkableProjects.length) return { outroMessage: "No projects available for linking" };
|
|
38832
|
+
let projectId;
|
|
38833
|
+
if (options.projectId) {
|
|
38834
|
+
if (!linkableProjects.find((p$1) => p$1.id === options.projectId)) throw new Error(`Project with ID "${options.projectId}" not found or not available for linking.`);
|
|
38835
|
+
projectId = options.projectId;
|
|
38836
|
+
} else projectId = (await promptForExistingProject(linkableProjects)).id;
|
|
38837
|
+
await runTask("Linking project...", async () => {
|
|
38838
|
+
await writeAppConfig(projectRoot.root, projectId);
|
|
38839
|
+
setAppConfig({
|
|
38840
|
+
id: projectId,
|
|
38841
|
+
projectRoot: projectRoot.root
|
|
38842
|
+
});
|
|
38843
|
+
}, {
|
|
38844
|
+
successMessage: "Project linked successfully",
|
|
38845
|
+
errorMessage: "Failed to link project"
|
|
38846
|
+
});
|
|
38847
|
+
finalProjectId = projectId;
|
|
38848
|
+
}
|
|
38849
|
+
if (action === "create") {
|
|
38850
|
+
const { name: name$1, description } = options.create ? {
|
|
38851
|
+
name: options.name.trim(),
|
|
38852
|
+
description: options.description?.trim()
|
|
38853
|
+
} : await promptForNewProjectDetails();
|
|
38854
|
+
const { projectId } = await runTask("Creating project on Base44...", async () => {
|
|
38855
|
+
return await createProject(name$1, description);
|
|
38856
|
+
}, {
|
|
38857
|
+
successMessage: "Project created successfully",
|
|
38858
|
+
errorMessage: "Failed to create project"
|
|
38859
|
+
});
|
|
38860
|
+
await writeAppConfig(projectRoot.root, projectId);
|
|
38861
|
+
setAppConfig({
|
|
38862
|
+
id: projectId,
|
|
38863
|
+
projectRoot: projectRoot.root
|
|
38864
|
+
});
|
|
38865
|
+
finalProjectId = projectId;
|
|
38866
|
+
}
|
|
38867
|
+
M.message(`${theme.styles.header("Dashboard")}: ${theme.colors.links(getDashboardUrl(finalProjectId))}`);
|
|
38954
38868
|
return { outroMessage: "Project linked" };
|
|
38955
38869
|
}
|
|
38956
|
-
const linkCommand = new Command("link").description("Link a local project to a Base44 project").option("-c, --create", "Create a new project (skip selection prompt)").option("-n, --name <name>", "Project name (required when --create is used)").option("-d, --description <description>", "Project description").hook("preAction", validateNonInteractiveFlags).action(async (options) => {
|
|
38870
|
+
const linkCommand = new Command("link").description("Link a local project to a Base44 project (create new or link existing)").option("-c, --create", "Create a new project (skip selection prompt)").option("-n, --name <name>", "Project name (required when --create is used)").option("-d, --description <description>", "Project description").option("-p, --projectId <id>", "Project ID to link to an existing project (skips selection prompt)").hook("preAction", validateNonInteractiveFlags).action(async (options) => {
|
|
38957
38871
|
await runCommand(() => link(options), {
|
|
38958
38872
|
requireAuth: true,
|
|
38959
38873
|
requireAppConfig: false
|
|
@@ -38981,650 +38895,9 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
|
|
|
38981
38895
|
await runCommand(() => deployAction(options), { requireAuth: true });
|
|
38982
38896
|
}));
|
|
38983
38897
|
|
|
38984
|
-
//#endregion
|
|
38985
|
-
//#region src/core/connectors/schema.ts
|
|
38986
|
-
/**
|
|
38987
|
-
* Response from POST /api/apps/{app_id}/external-auth/initiate
|
|
38988
|
-
*/
|
|
38989
|
-
const InitiateResponseSchema = object({
|
|
38990
|
-
redirect_url: string().optional(),
|
|
38991
|
-
connection_id: string().optional(),
|
|
38992
|
-
already_authorized: boolean().optional(),
|
|
38993
|
-
other_user_email: string().optional(),
|
|
38994
|
-
error: string().optional()
|
|
38995
|
-
});
|
|
38996
|
-
/**
|
|
38997
|
-
* Response from GET /api/apps/{app_id}/external-auth/status
|
|
38998
|
-
*/
|
|
38999
|
-
const StatusResponseSchema = object({
|
|
39000
|
-
status: _enum([
|
|
39001
|
-
"ACTIVE",
|
|
39002
|
-
"PENDING",
|
|
39003
|
-
"FAILED"
|
|
39004
|
-
]),
|
|
39005
|
-
account_email: string().optional(),
|
|
39006
|
-
error: string().optional()
|
|
39007
|
-
}).transform((data) => ({
|
|
39008
|
-
status: data.status,
|
|
39009
|
-
accountEmail: data.account_email,
|
|
39010
|
-
error: data.error
|
|
39011
|
-
}));
|
|
39012
|
-
/**
|
|
39013
|
-
* A connected integration from the list endpoint
|
|
39014
|
-
*/
|
|
39015
|
-
const ConnectorSchema = object({
|
|
39016
|
-
integration_type: string(),
|
|
39017
|
-
status: string(),
|
|
39018
|
-
connected_at: string().optional(),
|
|
39019
|
-
account_info: object({
|
|
39020
|
-
email: string().optional(),
|
|
39021
|
-
name: string().optional()
|
|
39022
|
-
}).optional()
|
|
39023
|
-
}).transform((data) => ({
|
|
39024
|
-
integrationType: data.integration_type,
|
|
39025
|
-
status: data.status,
|
|
39026
|
-
connectedAt: data.connected_at,
|
|
39027
|
-
accountInfo: data.account_info
|
|
39028
|
-
}));
|
|
39029
|
-
/**
|
|
39030
|
-
* Response from GET /api/apps/{app_id}/external-auth/list
|
|
39031
|
-
*/
|
|
39032
|
-
const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
|
|
39033
|
-
/**
|
|
39034
|
-
* Generic API error response
|
|
39035
|
-
*/
|
|
39036
|
-
const ApiErrorSchema = object({
|
|
39037
|
-
error: string(),
|
|
39038
|
-
detail: string().optional()
|
|
39039
|
-
});
|
|
39040
|
-
|
|
39041
|
-
//#endregion
|
|
39042
|
-
//#region src/core/connectors/api.ts
|
|
39043
|
-
/**
|
|
39044
|
-
* Initiates OAuth flow for a connector integration.
|
|
39045
|
-
* Returns a redirect URL to open in the browser.
|
|
39046
|
-
*/
|
|
39047
|
-
async function initiateOAuth(integrationType, scopes = null) {
|
|
39048
|
-
const response = await getAppClient().post("external-auth/initiate", {
|
|
39049
|
-
json: {
|
|
39050
|
-
integration_type: integrationType,
|
|
39051
|
-
scopes
|
|
39052
|
-
},
|
|
39053
|
-
throwHttpErrors: false
|
|
39054
|
-
});
|
|
39055
|
-
const json = await response.json();
|
|
39056
|
-
if (!response.ok) {
|
|
39057
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39058
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39059
|
-
throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
|
|
39060
|
-
}
|
|
39061
|
-
const result = InitiateResponseSchema.safeParse(json);
|
|
39062
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
|
|
39063
|
-
return result.data;
|
|
39064
|
-
}
|
|
39065
|
-
/**
|
|
39066
|
-
* Checks the status of an OAuth connection attempt.
|
|
39067
|
-
*/
|
|
39068
|
-
async function checkOAuthStatus(integrationType, connectionId) {
|
|
39069
|
-
const response = await getAppClient().get("external-auth/status", {
|
|
39070
|
-
searchParams: {
|
|
39071
|
-
integration_type: integrationType,
|
|
39072
|
-
connection_id: connectionId
|
|
39073
|
-
},
|
|
39074
|
-
throwHttpErrors: false
|
|
39075
|
-
});
|
|
39076
|
-
const json = await response.json();
|
|
39077
|
-
if (!response.ok) {
|
|
39078
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39079
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39080
|
-
throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
|
|
39081
|
-
}
|
|
39082
|
-
const result = StatusResponseSchema.safeParse(json);
|
|
39083
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
|
|
39084
|
-
return result.data;
|
|
39085
|
-
}
|
|
39086
|
-
/**
|
|
39087
|
-
* Lists all connected integrations for the current app.
|
|
39088
|
-
*/
|
|
39089
|
-
async function listConnectors() {
|
|
39090
|
-
const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
|
|
39091
|
-
const json = await response.json();
|
|
39092
|
-
if (!response.ok) {
|
|
39093
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39094
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39095
|
-
throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
|
|
39096
|
-
}
|
|
39097
|
-
const result = ListResponseSchema.safeParse(json);
|
|
39098
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
|
|
39099
|
-
return result.data.integrations;
|
|
39100
|
-
}
|
|
39101
|
-
/**
|
|
39102
|
-
* Disconnects (soft delete) a connector integration.
|
|
39103
|
-
*/
|
|
39104
|
-
async function disconnectConnector(integrationType) {
|
|
39105
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
|
|
39106
|
-
if (!response.ok) {
|
|
39107
|
-
const json = await response.json();
|
|
39108
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39109
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39110
|
-
throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
|
|
39111
|
-
}
|
|
39112
|
-
}
|
|
39113
|
-
/**
|
|
39114
|
-
* Removes (hard delete) a connector integration.
|
|
39115
|
-
* This permanently removes the connector and cannot be undone.
|
|
39116
|
-
*/
|
|
39117
|
-
async function removeConnector(integrationType) {
|
|
39118
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`, { throwHttpErrors: false });
|
|
39119
|
-
if (!response.ok) {
|
|
39120
|
-
const json = await response.json();
|
|
39121
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39122
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39123
|
-
throw new ConnectorApiError(`Failed to remove connector: ${response.status} ${response.statusText}`);
|
|
39124
|
-
}
|
|
39125
|
-
}
|
|
39126
|
-
|
|
39127
|
-
//#endregion
|
|
39128
|
-
//#region src/core/connectors/constants.ts
|
|
39129
|
-
/**
|
|
39130
|
-
* Supported OAuth connector integrations.
|
|
39131
|
-
* Based on apper/backend/app/external_auth/models/constants.py
|
|
39132
|
-
*/
|
|
39133
|
-
const SUPPORTED_INTEGRATIONS = [
|
|
39134
|
-
"googlecalendar",
|
|
39135
|
-
"googledrive",
|
|
39136
|
-
"gmail",
|
|
39137
|
-
"googlesheets",
|
|
39138
|
-
"googledocs",
|
|
39139
|
-
"googleslides",
|
|
39140
|
-
"slack",
|
|
39141
|
-
"notion",
|
|
39142
|
-
"salesforce",
|
|
39143
|
-
"hubspot",
|
|
39144
|
-
"linkedin",
|
|
39145
|
-
"tiktok"
|
|
39146
|
-
];
|
|
39147
|
-
/**
|
|
39148
|
-
* Display names for integrations (for CLI output)
|
|
39149
|
-
*/
|
|
39150
|
-
const INTEGRATION_DISPLAY_NAMES = {
|
|
39151
|
-
googlecalendar: "Google Calendar",
|
|
39152
|
-
googledrive: "Google Drive",
|
|
39153
|
-
gmail: "Gmail",
|
|
39154
|
-
googlesheets: "Google Sheets",
|
|
39155
|
-
googledocs: "Google Docs",
|
|
39156
|
-
googleslides: "Google Slides",
|
|
39157
|
-
slack: "Slack",
|
|
39158
|
-
notion: "Notion",
|
|
39159
|
-
salesforce: "Salesforce",
|
|
39160
|
-
hubspot: "HubSpot",
|
|
39161
|
-
linkedin: "LinkedIn",
|
|
39162
|
-
tiktok: "TikTok"
|
|
39163
|
-
};
|
|
39164
|
-
function isValidIntegration(type) {
|
|
39165
|
-
return SUPPORTED_INTEGRATIONS.includes(type);
|
|
39166
|
-
}
|
|
39167
|
-
function getIntegrationDisplayName(type) {
|
|
39168
|
-
if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
|
|
39169
|
-
return type;
|
|
39170
|
-
}
|
|
39171
|
-
|
|
39172
|
-
//#endregion
|
|
39173
|
-
//#region src/core/connectors/config.ts
|
|
39174
|
-
/**
|
|
39175
|
-
* Schema for a single connector configuration
|
|
39176
|
-
*/
|
|
39177
|
-
const ConnectorConfigSchema = object({ scopes: array(string()).optional() });
|
|
39178
|
-
/**
|
|
39179
|
-
* Schema for the connectors.jsonc file
|
|
39180
|
-
*/
|
|
39181
|
-
const ConnectorsFileSchema = record(string(), ConnectorConfigSchema);
|
|
39182
|
-
const CONNECTORS_FILE_PATTERNS = [`${PROJECT_SUBDIR}/connectors.${CONFIG_FILE_EXTENSION_GLOB}`, `connectors.${CONFIG_FILE_EXTENSION_GLOB}`];
|
|
39183
|
-
/**
|
|
39184
|
-
* Find the connectors config file in the project
|
|
39185
|
-
*/
|
|
39186
|
-
async function findConnectorsFile(startPath) {
|
|
39187
|
-
return (await globby(CONNECTORS_FILE_PATTERNS, {
|
|
39188
|
-
cwd: startPath || process.cwd(),
|
|
39189
|
-
absolute: true
|
|
39190
|
-
}))[0] ?? null;
|
|
39191
|
-
}
|
|
39192
|
-
/**
|
|
39193
|
-
* Get the default path for the connectors file
|
|
39194
|
-
*/
|
|
39195
|
-
function getDefaultConnectorsPath(projectRoot) {
|
|
39196
|
-
return join(projectRoot || process.cwd(), PROJECT_SUBDIR, "connectors.jsonc");
|
|
39197
|
-
}
|
|
39198
|
-
/**
|
|
39199
|
-
* Read all connectors from the local config file
|
|
39200
|
-
*/
|
|
39201
|
-
async function readLocalConnectors(projectRoot) {
|
|
39202
|
-
const filePath = await findConnectorsFile(projectRoot);
|
|
39203
|
-
if (!filePath) return [];
|
|
39204
|
-
const parsed = await readJsonFile(filePath);
|
|
39205
|
-
const result = ConnectorsFileSchema.safeParse(parsed);
|
|
39206
|
-
if (!result.success) throw new Error(`Invalid connectors configuration: ${result.error.message}`);
|
|
39207
|
-
const connectors = [];
|
|
39208
|
-
for (const [type, config$1] of Object.entries(result.data)) {
|
|
39209
|
-
if (!isValidIntegration(type)) throw new Error(`Unknown connector type: ${type}`);
|
|
39210
|
-
connectors.push({
|
|
39211
|
-
type,
|
|
39212
|
-
scopes: config$1.scopes
|
|
39213
|
-
});
|
|
39214
|
-
}
|
|
39215
|
-
return connectors;
|
|
39216
|
-
}
|
|
39217
|
-
/**
|
|
39218
|
-
* Write connectors to the local config file
|
|
39219
|
-
*/
|
|
39220
|
-
async function writeLocalConnectors(connectors, projectRoot) {
|
|
39221
|
-
let filePath = await findConnectorsFile(projectRoot);
|
|
39222
|
-
if (!filePath) filePath = getDefaultConnectorsPath(projectRoot);
|
|
39223
|
-
const data = {};
|
|
39224
|
-
for (const connector of connectors) data[connector.type] = { ...connector.scopes && { scopes: connector.scopes } };
|
|
39225
|
-
await writeJsonFile(filePath, data);
|
|
39226
|
-
return filePath;
|
|
39227
|
-
}
|
|
39228
|
-
/**
|
|
39229
|
-
* Add a connector to the local config file
|
|
39230
|
-
*/
|
|
39231
|
-
async function addLocalConnector(type, scopes, projectRoot) {
|
|
39232
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39233
|
-
const existing = connectors.find((c$1) => c$1.type === type);
|
|
39234
|
-
if (existing) {
|
|
39235
|
-
if (scopes) existing.scopes = scopes;
|
|
39236
|
-
} else connectors.push({
|
|
39237
|
-
type,
|
|
39238
|
-
scopes
|
|
39239
|
-
});
|
|
39240
|
-
return await writeLocalConnectors(connectors, projectRoot);
|
|
39241
|
-
}
|
|
39242
|
-
/**
|
|
39243
|
-
* Remove a connector from the local config file
|
|
39244
|
-
*/
|
|
39245
|
-
async function removeLocalConnector(type, projectRoot) {
|
|
39246
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39247
|
-
const filtered = connectors.filter((c$1) => c$1.type !== type);
|
|
39248
|
-
if (filtered.length === connectors.length) return null;
|
|
39249
|
-
return await writeLocalConnectors(filtered, projectRoot);
|
|
39250
|
-
}
|
|
39251
|
-
|
|
39252
|
-
//#endregion
|
|
39253
|
-
//#region src/cli/commands/connectors/add.ts
|
|
39254
|
-
const POLL_INTERVAL_MS$1 = 2e3;
|
|
39255
|
-
const POLL_TIMEOUT_MS$1 = 300 * 1e3;
|
|
39256
|
-
async function promptForIntegrationType() {
|
|
39257
|
-
const selected = await ve({
|
|
39258
|
-
message: "Select an integration to connect:",
|
|
39259
|
-
options: SUPPORTED_INTEGRATIONS.map((type) => ({
|
|
39260
|
-
value: type,
|
|
39261
|
-
label: getIntegrationDisplayName(type)
|
|
39262
|
-
}))
|
|
39263
|
-
});
|
|
39264
|
-
if (pD(selected)) return null;
|
|
39265
|
-
return selected;
|
|
39266
|
-
}
|
|
39267
|
-
async function waitForOAuthCompletion(integrationType, connectionId) {
|
|
39268
|
-
let accountEmail;
|
|
39269
|
-
let error;
|
|
39270
|
-
try {
|
|
39271
|
-
await runTask("Waiting for authorization...", async (updateMessage) => {
|
|
39272
|
-
await pWaitFor(async () => {
|
|
39273
|
-
const status = await checkOAuthStatus(integrationType, connectionId);
|
|
39274
|
-
if (status.status === "ACTIVE") {
|
|
39275
|
-
accountEmail = status.accountEmail;
|
|
39276
|
-
return true;
|
|
39277
|
-
}
|
|
39278
|
-
if (status.status === "FAILED") {
|
|
39279
|
-
error = status.error || "Authorization failed";
|
|
39280
|
-
throw new Error(error);
|
|
39281
|
-
}
|
|
39282
|
-
updateMessage("Waiting for authorization in browser...");
|
|
39283
|
-
return false;
|
|
39284
|
-
}, {
|
|
39285
|
-
interval: POLL_INTERVAL_MS$1,
|
|
39286
|
-
timeout: POLL_TIMEOUT_MS$1
|
|
39287
|
-
});
|
|
39288
|
-
}, {
|
|
39289
|
-
successMessage: "Authorization completed!",
|
|
39290
|
-
errorMessage: "Authorization failed"
|
|
39291
|
-
});
|
|
39292
|
-
return {
|
|
39293
|
-
success: true,
|
|
39294
|
-
accountEmail
|
|
39295
|
-
};
|
|
39296
|
-
} catch (err) {
|
|
39297
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39298
|
-
success: false,
|
|
39299
|
-
error: "Authorization timed out. Please try again."
|
|
39300
|
-
};
|
|
39301
|
-
return {
|
|
39302
|
-
success: false,
|
|
39303
|
-
error: error || (err instanceof Error ? err.message : "Unknown error")
|
|
39304
|
-
};
|
|
39305
|
-
}
|
|
39306
|
-
}
|
|
39307
|
-
async function addConnector(integrationType) {
|
|
39308
|
-
let selectedType;
|
|
39309
|
-
if (!integrationType) {
|
|
39310
|
-
const prompted = await promptForIntegrationType();
|
|
39311
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39312
|
-
selectedType = prompted;
|
|
39313
|
-
} else {
|
|
39314
|
-
if (!isValidIntegration(integrationType)) {
|
|
39315
|
-
const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
|
|
39316
|
-
throw new Error(`Unsupported connector: ${integrationType}\nSupported connectors: ${supportedList}`);
|
|
39317
|
-
}
|
|
39318
|
-
selectedType = integrationType;
|
|
39319
|
-
}
|
|
39320
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39321
|
-
const initiateResponse = await runTask(`Initiating ${displayName} connection...`, async () => {
|
|
39322
|
-
return await initiateOAuth(selectedType);
|
|
39323
|
-
}, {
|
|
39324
|
-
successMessage: `${displayName} OAuth initiated`,
|
|
39325
|
-
errorMessage: `Failed to initiate ${displayName} connection`
|
|
39326
|
-
});
|
|
39327
|
-
if (initiateResponse.already_authorized) {
|
|
39328
|
-
await addLocalConnector(selectedType);
|
|
39329
|
-
return { outroMessage: `Already connected to ${theme.styles.bold(displayName)} (added to connectors.jsonc)` };
|
|
39330
|
-
}
|
|
39331
|
-
if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
|
|
39332
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
|
|
39333
|
-
M.info(`Opening browser for ${displayName} authorization...`);
|
|
39334
|
-
await open_default(initiateResponse.redirect_url);
|
|
39335
|
-
const result = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
|
|
39336
|
-
if (!result.success) throw new Error(result.error || "Authorization failed");
|
|
39337
|
-
await addLocalConnector(selectedType);
|
|
39338
|
-
const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
|
|
39339
|
-
return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
|
|
39340
|
-
}
|
|
39341
|
-
const connectorsAddCommand = new Command("connectors:add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
|
|
39342
|
-
await runCommand(() => addConnector(type), {
|
|
39343
|
-
requireAuth: true,
|
|
39344
|
-
requireAppConfig: true
|
|
39345
|
-
});
|
|
39346
|
-
});
|
|
39347
|
-
|
|
39348
|
-
//#endregion
|
|
39349
|
-
//#region src/cli/commands/connectors/list.ts
|
|
39350
|
-
function mergeConnectors(local, backend) {
|
|
39351
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39352
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39353
|
-
type: connector.type,
|
|
39354
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39355
|
-
inLocal: true,
|
|
39356
|
-
inBackend: false
|
|
39357
|
-
});
|
|
39358
|
-
for (const connector of backend) {
|
|
39359
|
-
const existing = merged.get(connector.integrationType);
|
|
39360
|
-
if (existing) {
|
|
39361
|
-
existing.inBackend = true;
|
|
39362
|
-
existing.status = connector.status;
|
|
39363
|
-
existing.accountEmail = connector.accountInfo?.email || connector.accountInfo?.name;
|
|
39364
|
-
} else merged.set(connector.integrationType, {
|
|
39365
|
-
type: connector.integrationType,
|
|
39366
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39367
|
-
inLocal: false,
|
|
39368
|
-
inBackend: true,
|
|
39369
|
-
status: connector.status,
|
|
39370
|
-
accountEmail: connector.accountInfo?.email || connector.accountInfo?.name
|
|
39371
|
-
});
|
|
39372
|
-
}
|
|
39373
|
-
return Array.from(merged.values());
|
|
39374
|
-
}
|
|
39375
|
-
function formatConnectorLine(connector) {
|
|
39376
|
-
const { displayName, inLocal, inBackend, status, accountEmail } = connector;
|
|
39377
|
-
const isConnected$1 = inBackend && status?.toLowerCase() === "active";
|
|
39378
|
-
const isPending = inLocal && !inBackend;
|
|
39379
|
-
const isOrphaned = inBackend && !inLocal;
|
|
39380
|
-
let bullet;
|
|
39381
|
-
let statusText = "";
|
|
39382
|
-
if (isConnected$1) {
|
|
39383
|
-
bullet = theme.colors.success("●");
|
|
39384
|
-
if (accountEmail) statusText = ` - ${accountEmail}`;
|
|
39385
|
-
} else if (isPending) {
|
|
39386
|
-
bullet = theme.colors.warning("○");
|
|
39387
|
-
statusText = theme.styles.dim(" (not connected)");
|
|
39388
|
-
} else if (isOrphaned) {
|
|
39389
|
-
bullet = theme.colors.error("○");
|
|
39390
|
-
statusText = theme.styles.dim(" (not in local config)");
|
|
39391
|
-
} else {
|
|
39392
|
-
bullet = theme.colors.error("○");
|
|
39393
|
-
statusText = theme.styles.dim(` (${status || "disconnected"})`);
|
|
39394
|
-
}
|
|
39395
|
-
return `${bullet} ${displayName}${statusText}`;
|
|
39396
|
-
}
|
|
39397
|
-
async function listConnectorsCommand() {
|
|
39398
|
-
const [localConnectors, backendConnectors] = await runTask("Fetching connectors...", async () => {
|
|
39399
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39400
|
-
return [local, backend];
|
|
39401
|
-
}, {
|
|
39402
|
-
successMessage: "Connectors loaded",
|
|
39403
|
-
errorMessage: "Failed to fetch connectors"
|
|
39404
|
-
});
|
|
39405
|
-
const merged = mergeConnectors(localConnectors, backendConnectors);
|
|
39406
|
-
if (merged.length === 0) {
|
|
39407
|
-
M.info("No connectors configured for this app.");
|
|
39408
|
-
M.info(`Run ${theme.styles.bold("base44 connectors:add")} to connect an integration.`);
|
|
39409
|
-
return { outroMessage: "" };
|
|
39410
|
-
}
|
|
39411
|
-
console.log();
|
|
39412
|
-
for (const connector of merged) console.log(formatConnectorLine(connector));
|
|
39413
|
-
console.log();
|
|
39414
|
-
const connected = merged.filter((c$1) => c$1.inBackend && c$1.status?.toLowerCase() === "active").length;
|
|
39415
|
-
const pending = merged.filter((c$1) => c$1.inLocal && !c$1.inBackend).length;
|
|
39416
|
-
let summary = `${connected} connected`;
|
|
39417
|
-
if (pending > 0) {
|
|
39418
|
-
summary += `, ${pending} pending`;
|
|
39419
|
-
M.info(`Run ${theme.styles.bold("base44 connectors:push")} to connect pending integrations.`);
|
|
39420
|
-
}
|
|
39421
|
-
return { outroMessage: summary };
|
|
39422
|
-
}
|
|
39423
|
-
const connectorsListCommand = new Command("connectors:list").description("List all connected OAuth integrations").action(async () => {
|
|
39424
|
-
await runCommand(listConnectorsCommand, {
|
|
39425
|
-
requireAuth: true,
|
|
39426
|
-
requireAppConfig: true
|
|
39427
|
-
});
|
|
39428
|
-
});
|
|
39429
|
-
|
|
39430
|
-
//#endregion
|
|
39431
|
-
//#region src/cli/commands/connectors/push.ts
|
|
39432
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
39433
|
-
const POLL_TIMEOUT_MS = 300 * 1e3;
|
|
39434
|
-
function findPendingConnectors(local, backend) {
|
|
39435
|
-
const connectedTypes = new Set(backend.filter((c$1) => c$1.status.toLowerCase() === "active").map((c$1) => c$1.integrationType));
|
|
39436
|
-
return local.filter((c$1) => !connectedTypes.has(c$1.type)).map((c$1) => ({
|
|
39437
|
-
type: c$1.type,
|
|
39438
|
-
displayName: getIntegrationDisplayName(c$1.type),
|
|
39439
|
-
scopes: c$1.scopes
|
|
39440
|
-
}));
|
|
39441
|
-
}
|
|
39442
|
-
async function connectSingleConnector(connector) {
|
|
39443
|
-
const { type, displayName, scopes } = connector;
|
|
39444
|
-
const initiateResponse = await initiateOAuth(type, scopes || null);
|
|
39445
|
-
if (initiateResponse.already_authorized) return { success: true };
|
|
39446
|
-
if (initiateResponse.error === "different_user") return {
|
|
39447
|
-
success: false,
|
|
39448
|
-
error: `Already connected by ${initiateResponse.other_user_email}`
|
|
39449
|
-
};
|
|
39450
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) return {
|
|
39451
|
-
success: false,
|
|
39452
|
-
error: "Invalid response from server"
|
|
39453
|
-
};
|
|
39454
|
-
M.info(`Opening browser for ${displayName} authorization...`);
|
|
39455
|
-
await open_default(initiateResponse.redirect_url);
|
|
39456
|
-
let accountEmail;
|
|
39457
|
-
try {
|
|
39458
|
-
await pWaitFor(async () => {
|
|
39459
|
-
const status = await checkOAuthStatus(type, initiateResponse.connection_id);
|
|
39460
|
-
if (status.status === "ACTIVE") {
|
|
39461
|
-
accountEmail = status.accountEmail;
|
|
39462
|
-
return true;
|
|
39463
|
-
}
|
|
39464
|
-
if (status.status === "FAILED") throw new Error(status.error || "Authorization failed");
|
|
39465
|
-
return false;
|
|
39466
|
-
}, {
|
|
39467
|
-
interval: POLL_INTERVAL_MS,
|
|
39468
|
-
timeout: POLL_TIMEOUT_MS
|
|
39469
|
-
});
|
|
39470
|
-
return {
|
|
39471
|
-
success: true,
|
|
39472
|
-
accountEmail
|
|
39473
|
-
};
|
|
39474
|
-
} catch (err) {
|
|
39475
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39476
|
-
success: false,
|
|
39477
|
-
error: "Authorization timed out"
|
|
39478
|
-
};
|
|
39479
|
-
return {
|
|
39480
|
-
success: false,
|
|
39481
|
-
error: err instanceof Error ? err.message : "Unknown error"
|
|
39482
|
-
};
|
|
39483
|
-
}
|
|
39484
|
-
}
|
|
39485
|
-
async function pushConnectorsCommand() {
|
|
39486
|
-
const [localConnectors, backendConnectors] = await runTask("Checking connector status...", async () => {
|
|
39487
|
-
const [local, backend] = await Promise.all([readLocalConnectors(), listConnectors().catch(() => [])]);
|
|
39488
|
-
return [local, backend];
|
|
39489
|
-
}, {
|
|
39490
|
-
successMessage: "Status checked",
|
|
39491
|
-
errorMessage: "Failed to check status"
|
|
39492
|
-
});
|
|
39493
|
-
if (localConnectors.length === 0) {
|
|
39494
|
-
M.info("No connectors defined in connectors.jsonc");
|
|
39495
|
-
M.info(`Run ${theme.styles.bold("base44 connectors:add")} to add a connector.`);
|
|
39496
|
-
return { outroMessage: "" };
|
|
39497
|
-
}
|
|
39498
|
-
const pending = findPendingConnectors(localConnectors, backendConnectors);
|
|
39499
|
-
if (pending.length === 0) return { outroMessage: "All connectors are already connected" };
|
|
39500
|
-
console.log();
|
|
39501
|
-
M.info(`${pending.length} connector${pending.length === 1 ? "" : "s"} need${pending.length === 1 ? "s" : ""} to be connected:`);
|
|
39502
|
-
for (const c$1 of pending) console.log(` ${theme.colors.warning("○")} ${c$1.displayName}`);
|
|
39503
|
-
console.log();
|
|
39504
|
-
const shouldProceed = await ye({
|
|
39505
|
-
message: `Connect ${pending.length} integration${pending.length === 1 ? "" : "s"}?`,
|
|
39506
|
-
initialValue: true
|
|
39507
|
-
});
|
|
39508
|
-
if (pD(shouldProceed) || !shouldProceed) return { outroMessage: "Cancelled" };
|
|
39509
|
-
let connected = 0;
|
|
39510
|
-
let failed = 0;
|
|
39511
|
-
for (const connector of pending) {
|
|
39512
|
-
console.log();
|
|
39513
|
-
M.info(`Connecting ${theme.styles.bold(connector.displayName)}...`);
|
|
39514
|
-
const result = await connectSingleConnector(connector);
|
|
39515
|
-
if (result.success) {
|
|
39516
|
-
const accountInfo = result.accountEmail ? ` as ${result.accountEmail}` : "";
|
|
39517
|
-
M.success(`${connector.displayName} connected${accountInfo}`);
|
|
39518
|
-
connected++;
|
|
39519
|
-
} else {
|
|
39520
|
-
M.error(`${connector.displayName} failed: ${result.error}`);
|
|
39521
|
-
failed++;
|
|
39522
|
-
}
|
|
39523
|
-
}
|
|
39524
|
-
console.log();
|
|
39525
|
-
if (failed === 0) return { outroMessage: `Successfully connected ${connected} integration${connected === 1 ? "" : "s"}` };
|
|
39526
|
-
return { outroMessage: `Connected ${connected}, failed ${failed}` };
|
|
39527
|
-
}
|
|
39528
|
-
const connectorsPushCommand = new Command("connectors:push").description("Connect all pending integrations from connectors.jsonc").action(async () => {
|
|
39529
|
-
await runCommand(pushConnectorsCommand, {
|
|
39530
|
-
requireAuth: true,
|
|
39531
|
-
requireAppConfig: true
|
|
39532
|
-
});
|
|
39533
|
-
});
|
|
39534
|
-
|
|
39535
|
-
//#endregion
|
|
39536
|
-
//#region src/cli/commands/connectors/remove.ts
|
|
39537
|
-
function mergeConnectorsForRemoval(local, backend) {
|
|
39538
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39539
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39540
|
-
type: connector.type,
|
|
39541
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39542
|
-
inLocal: true,
|
|
39543
|
-
inBackend: false
|
|
39544
|
-
});
|
|
39545
|
-
for (const connector of backend) {
|
|
39546
|
-
if (!isValidIntegration(connector.integrationType)) continue;
|
|
39547
|
-
const existing = merged.get(connector.integrationType);
|
|
39548
|
-
if (existing) {
|
|
39549
|
-
existing.inBackend = true;
|
|
39550
|
-
existing.accountEmail = connector.accountInfo?.email || connector.accountInfo?.name;
|
|
39551
|
-
} else merged.set(connector.integrationType, {
|
|
39552
|
-
type: connector.integrationType,
|
|
39553
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39554
|
-
inLocal: false,
|
|
39555
|
-
inBackend: true,
|
|
39556
|
-
accountEmail: connector.accountInfo?.email || connector.accountInfo?.name
|
|
39557
|
-
});
|
|
39558
|
-
}
|
|
39559
|
-
return Array.from(merged.values());
|
|
39560
|
-
}
|
|
39561
|
-
async function promptForConnectorToRemove(connectors) {
|
|
39562
|
-
const selected = await ve({
|
|
39563
|
-
message: "Select a connector to remove:",
|
|
39564
|
-
options: connectors.map((c$1) => {
|
|
39565
|
-
let label = c$1.displayName;
|
|
39566
|
-
if (c$1.accountEmail) label += ` (${c$1.accountEmail})`;
|
|
39567
|
-
else if (c$1.inLocal && !c$1.inBackend) label += " (not connected)";
|
|
39568
|
-
return {
|
|
39569
|
-
value: c$1.type,
|
|
39570
|
-
label
|
|
39571
|
-
};
|
|
39572
|
-
})
|
|
39573
|
-
});
|
|
39574
|
-
if (pD(selected)) return null;
|
|
39575
|
-
return selected;
|
|
39576
|
-
}
|
|
39577
|
-
async function removeConnectorCommand(integrationType, options = {}) {
|
|
39578
|
-
const isHardDelete = options.hard === true;
|
|
39579
|
-
const [localConnectors, backendConnectors] = await runTask("Fetching connectors...", async () => {
|
|
39580
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39581
|
-
return [local, backend];
|
|
39582
|
-
}, {
|
|
39583
|
-
successMessage: "Connectors loaded",
|
|
39584
|
-
errorMessage: "Failed to fetch connectors"
|
|
39585
|
-
});
|
|
39586
|
-
const merged = mergeConnectorsForRemoval(localConnectors, backendConnectors);
|
|
39587
|
-
if (merged.length === 0) return { outroMessage: "No connectors to remove" };
|
|
39588
|
-
let selectedType;
|
|
39589
|
-
let selectedConnector;
|
|
39590
|
-
if (!integrationType) {
|
|
39591
|
-
const prompted = await promptForConnectorToRemove(merged);
|
|
39592
|
-
if (!prompted) return { outroMessage: "Cancelled" };
|
|
39593
|
-
selectedType = prompted;
|
|
39594
|
-
selectedConnector = merged.find((c$1) => c$1.type === selectedType);
|
|
39595
|
-
} else {
|
|
39596
|
-
if (!isValidIntegration(integrationType)) throw new Error(`Invalid connector type: ${integrationType}`);
|
|
39597
|
-
selectedConnector = merged.find((c$1) => c$1.type === integrationType);
|
|
39598
|
-
if (!selectedConnector) throw new Error(`No ${getIntegrationDisplayName(integrationType)} connector found`);
|
|
39599
|
-
selectedType = integrationType;
|
|
39600
|
-
}
|
|
39601
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39602
|
-
const accountInfo = selectedConnector?.accountEmail ? ` (${selectedConnector.accountEmail})` : "";
|
|
39603
|
-
const shouldRemove = await ye({
|
|
39604
|
-
message: `${isHardDelete ? "Permanently remove" : "Remove"} ${displayName}${accountInfo}?`,
|
|
39605
|
-
initialValue: false
|
|
39606
|
-
});
|
|
39607
|
-
if (pD(shouldRemove) || !shouldRemove) return { outroMessage: "Cancelled" };
|
|
39608
|
-
await runTask(isHardDelete ? `Removing ${displayName}...` : `Removing ${displayName}...`, async () => {
|
|
39609
|
-
if (selectedConnector?.inBackend) if (isHardDelete) await removeConnector(selectedType);
|
|
39610
|
-
else await disconnectConnector(selectedType);
|
|
39611
|
-
await removeLocalConnector(selectedType);
|
|
39612
|
-
}, {
|
|
39613
|
-
successMessage: `${displayName} removed`,
|
|
39614
|
-
errorMessage: `Failed to remove ${displayName}`
|
|
39615
|
-
});
|
|
39616
|
-
return { outroMessage: `Successfully removed ${theme.styles.bold(displayName)}` };
|
|
39617
|
-
}
|
|
39618
|
-
const connectorsRemoveCommand = new Command("connectors: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) => {
|
|
39619
|
-
await runCommand(() => removeConnectorCommand(type, options), {
|
|
39620
|
-
requireAuth: true,
|
|
39621
|
-
requireAppConfig: true
|
|
39622
|
-
});
|
|
39623
|
-
});
|
|
39624
|
-
|
|
39625
38898
|
//#endregion
|
|
39626
38899
|
//#region package.json
|
|
39627
|
-
var version = "0.0.
|
|
38900
|
+
var version = "0.0.16";
|
|
39628
38901
|
|
|
39629
38902
|
//#endregion
|
|
39630
38903
|
//#region src/cli/index.ts
|
|
@@ -39641,10 +38914,6 @@ program.addCommand(linkCommand);
|
|
|
39641
38914
|
program.addCommand(entitiesPushCommand);
|
|
39642
38915
|
program.addCommand(functionsDeployCommand);
|
|
39643
38916
|
program.addCommand(siteDeployCommand);
|
|
39644
|
-
program.addCommand(connectorsAddCommand);
|
|
39645
|
-
program.addCommand(connectorsListCommand);
|
|
39646
|
-
program.addCommand(connectorsPushCommand);
|
|
39647
|
-
program.addCommand(connectorsRemoveCommand);
|
|
39648
38917
|
program.parse();
|
|
39649
38918
|
|
|
39650
38919
|
//#endregion
|
package/package.json
CHANGED