@base44-preview/cli 0.0.17-pr.104.e7dd3a1 → 0.0.17-pr.108.0a81bed
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 +142 -62
- package/dist/{index.js → cli/index.js} +8 -873
- package/package.json +8 -7
- package/bin/dev.js +0 -12
- package/bin/run.js +0 -12
- /package/dist/{templates → cli/templates}/backend-and-client/.nvmrc +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/README.md +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/base44/app.jsonc.ejs +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/base44/config.jsonc.ejs +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/base44/entities/task.jsonc +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/components.json +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/index.html +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/jsconfig.json +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/package.json +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/postcss.config.js +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/App.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/api/base44Client.js.ejs +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/components/Base44Logo.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/components/ui/button.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/components/ui/checkbox.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/components/ui/input.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/index.css +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/src/main.jsx +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/tailwind.config.js +0 -0
- /package/dist/{templates → cli/templates}/backend-and-client/vite.config.js +0 -0
- /package/dist/{templates → cli/templates}/backend-only/base44/app.jsonc.ejs +0 -0
- /package/dist/{templates → cli/templates}/backend-only/base44/config.jsonc.ejs +0 -0
- /package/dist/{templates → cli/templates}/templates.json +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
|
|
3
4
|
import childProcess, { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
@@ -5818,97 +5819,6 @@ function handleTupleResult(result, final, index) {
|
|
|
5818
5819
|
if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
|
|
5819
5820
|
final.value[index] = result.value;
|
|
5820
5821
|
}
|
|
5821
|
-
const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
5822
|
-
$ZodType.init(inst, def);
|
|
5823
|
-
inst._zod.parse = (payload, ctx) => {
|
|
5824
|
-
const input = payload.value;
|
|
5825
|
-
if (!isPlainObject$1(input)) {
|
|
5826
|
-
payload.issues.push({
|
|
5827
|
-
expected: "record",
|
|
5828
|
-
code: "invalid_type",
|
|
5829
|
-
input,
|
|
5830
|
-
inst
|
|
5831
|
-
});
|
|
5832
|
-
return payload;
|
|
5833
|
-
}
|
|
5834
|
-
const proms = [];
|
|
5835
|
-
const values = def.keyType._zod.values;
|
|
5836
|
-
if (values) {
|
|
5837
|
-
payload.value = {};
|
|
5838
|
-
const recordKeys = /* @__PURE__ */ new Set();
|
|
5839
|
-
for (const key of values) if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
|
|
5840
|
-
recordKeys.add(typeof key === "number" ? key.toString() : key);
|
|
5841
|
-
const result = def.valueType._zod.run({
|
|
5842
|
-
value: input[key],
|
|
5843
|
-
issues: []
|
|
5844
|
-
}, ctx);
|
|
5845
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5846
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5847
|
-
payload.value[key] = result$1.value;
|
|
5848
|
-
}));
|
|
5849
|
-
else {
|
|
5850
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5851
|
-
payload.value[key] = result.value;
|
|
5852
|
-
}
|
|
5853
|
-
}
|
|
5854
|
-
let unrecognized;
|
|
5855
|
-
for (const key in input) if (!recordKeys.has(key)) {
|
|
5856
|
-
unrecognized = unrecognized ?? [];
|
|
5857
|
-
unrecognized.push(key);
|
|
5858
|
-
}
|
|
5859
|
-
if (unrecognized && unrecognized.length > 0) payload.issues.push({
|
|
5860
|
-
code: "unrecognized_keys",
|
|
5861
|
-
input,
|
|
5862
|
-
inst,
|
|
5863
|
-
keys: unrecognized
|
|
5864
|
-
});
|
|
5865
|
-
} else {
|
|
5866
|
-
payload.value = {};
|
|
5867
|
-
for (const key of Reflect.ownKeys(input)) {
|
|
5868
|
-
if (key === "__proto__") continue;
|
|
5869
|
-
let keyResult = def.keyType._zod.run({
|
|
5870
|
-
value: key,
|
|
5871
|
-
issues: []
|
|
5872
|
-
}, ctx);
|
|
5873
|
-
if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5874
|
-
if (typeof key === "string" && number$1.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number")) {
|
|
5875
|
-
const retryResult = def.keyType._zod.run({
|
|
5876
|
-
value: Number(key),
|
|
5877
|
-
issues: []
|
|
5878
|
-
}, ctx);
|
|
5879
|
-
if (retryResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
|
|
5880
|
-
if (retryResult.issues.length === 0) keyResult = retryResult;
|
|
5881
|
-
}
|
|
5882
|
-
if (keyResult.issues.length) {
|
|
5883
|
-
if (def.mode === "loose") payload.value[key] = input[key];
|
|
5884
|
-
else payload.issues.push({
|
|
5885
|
-
code: "invalid_key",
|
|
5886
|
-
origin: "record",
|
|
5887
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
5888
|
-
input: key,
|
|
5889
|
-
path: [key],
|
|
5890
|
-
inst
|
|
5891
|
-
});
|
|
5892
|
-
continue;
|
|
5893
|
-
}
|
|
5894
|
-
const result = def.valueType._zod.run({
|
|
5895
|
-
value: input[key],
|
|
5896
|
-
issues: []
|
|
5897
|
-
}, ctx);
|
|
5898
|
-
if (result instanceof Promise) proms.push(result.then((result$1) => {
|
|
5899
|
-
if (result$1.issues.length) payload.issues.push(...prefixIssues(key, result$1.issues));
|
|
5900
|
-
payload.value[keyResult.value] = result$1.value;
|
|
5901
|
-
}));
|
|
5902
|
-
else {
|
|
5903
|
-
if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));
|
|
5904
|
-
payload.value[keyResult.value] = result.value;
|
|
5905
|
-
}
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
|
-
if (proms.length) return Promise.all(proms).then(() => payload);
|
|
5909
|
-
return payload;
|
|
5910
|
-
};
|
|
5911
|
-
});
|
|
5912
5822
|
const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
5913
5823
|
$ZodType.init(inst, def);
|
|
5914
5824
|
const values = getEnumValues(def.entries);
|
|
@@ -7199,39 +7109,6 @@ const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
7199
7109
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
7200
7110
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
7201
7111
|
};
|
|
7202
|
-
const recordProcessor = (schema, ctx, _json, params) => {
|
|
7203
|
-
const json = _json;
|
|
7204
|
-
const def = schema._zod.def;
|
|
7205
|
-
json.type = "object";
|
|
7206
|
-
const keyType = def.keyType;
|
|
7207
|
-
const patterns = keyType._zod.bag?.patterns;
|
|
7208
|
-
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
7209
|
-
const valueSchema = process$2(def.valueType, ctx, {
|
|
7210
|
-
...params,
|
|
7211
|
-
path: [
|
|
7212
|
-
...params.path,
|
|
7213
|
-
"patternProperties",
|
|
7214
|
-
"*"
|
|
7215
|
-
]
|
|
7216
|
-
});
|
|
7217
|
-
json.patternProperties = {};
|
|
7218
|
-
for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
|
|
7219
|
-
} else {
|
|
7220
|
-
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$2(def.keyType, ctx, {
|
|
7221
|
-
...params,
|
|
7222
|
-
path: [...params.path, "propertyNames"]
|
|
7223
|
-
});
|
|
7224
|
-
json.additionalProperties = process$2(def.valueType, ctx, {
|
|
7225
|
-
...params,
|
|
7226
|
-
path: [...params.path, "additionalProperties"]
|
|
7227
|
-
});
|
|
7228
|
-
}
|
|
7229
|
-
const keyValues = keyType._zod.values;
|
|
7230
|
-
if (keyValues) {
|
|
7231
|
-
const validKeyValues = [...keyValues].filter((v$1) => typeof v$1 === "string" || typeof v$1 === "number");
|
|
7232
|
-
if (validKeyValues.length > 0) json.required = validKeyValues;
|
|
7233
|
-
}
|
|
7234
|
-
};
|
|
7235
7112
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
7236
7113
|
const def = schema._zod.def;
|
|
7237
7114
|
const inner = process$2(def.innerType, ctx, params);
|
|
@@ -7761,21 +7638,6 @@ function tuple(items, _paramsOrRest, _params) {
|
|
|
7761
7638
|
...normalizeParams(params)
|
|
7762
7639
|
});
|
|
7763
7640
|
}
|
|
7764
|
-
const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
7765
|
-
$ZodRecord.init(inst, def);
|
|
7766
|
-
ZodType.init(inst, def);
|
|
7767
|
-
inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);
|
|
7768
|
-
inst.keyType = def.keyType;
|
|
7769
|
-
inst.valueType = def.valueType;
|
|
7770
|
-
});
|
|
7771
|
-
function record(keyType, valueType, params) {
|
|
7772
|
-
return new ZodRecord({
|
|
7773
|
-
type: "record",
|
|
7774
|
-
keyType,
|
|
7775
|
-
valueType,
|
|
7776
|
-
...normalizeParams(params)
|
|
7777
|
-
});
|
|
7778
|
-
}
|
|
7779
7641
|
const ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
7780
7642
|
$ZodEnum.init(inst, def);
|
|
7781
7643
|
ZodType.init(inst, def);
|
|
@@ -8038,19 +7900,6 @@ var AuthValidationError = class extends Error {
|
|
|
8038
7900
|
this.name = "AuthValidationError";
|
|
8039
7901
|
}
|
|
8040
7902
|
};
|
|
8041
|
-
var ConnectorApiError = class extends Error {
|
|
8042
|
-
constructor(message, cause) {
|
|
8043
|
-
super(message);
|
|
8044
|
-
this.cause = cause;
|
|
8045
|
-
this.name = "ConnectorApiError";
|
|
8046
|
-
}
|
|
8047
|
-
};
|
|
8048
|
-
var ConnectorValidationError = class extends Error {
|
|
8049
|
-
constructor(message) {
|
|
8050
|
-
super(message);
|
|
8051
|
-
this.name = "ConnectorValidationError";
|
|
8052
|
-
}
|
|
8053
|
-
};
|
|
8054
7903
|
|
|
8055
7904
|
//#endregion
|
|
8056
7905
|
//#region src/core/consts.ts
|
|
@@ -16848,13 +16697,10 @@ const SiteConfigSchema = object({
|
|
|
16848
16697
|
outputDirectory: string().optional(),
|
|
16849
16698
|
installCommand: string().optional()
|
|
16850
16699
|
});
|
|
16851
|
-
const ConnectorConfigSchema = object({ scopes: array(string()).optional() });
|
|
16852
|
-
const ConnectorsConfigSchema = record(string(), ConnectorConfigSchema);
|
|
16853
16700
|
const ProjectConfigSchema = object({
|
|
16854
16701
|
name: string().min(1, "App name cannot be empty"),
|
|
16855
16702
|
description: string().optional(),
|
|
16856
16703
|
site: SiteConfigSchema.optional(),
|
|
16857
|
-
connectors: ConnectorsConfigSchema.optional(),
|
|
16858
16704
|
entitiesDir: string().optional().default("entities"),
|
|
16859
16705
|
functionsDir: string().optional().default("functions")
|
|
16860
16706
|
});
|
|
@@ -30740,21 +30586,6 @@ async function getUserInfo(accessToken) {
|
|
|
30740
30586
|
return result.data;
|
|
30741
30587
|
}
|
|
30742
30588
|
|
|
30743
|
-
//#endregion
|
|
30744
|
-
//#region src/cli/errors.ts
|
|
30745
|
-
/**
|
|
30746
|
-
* Error thrown to signal a controlled CLI exit with a specific exit code.
|
|
30747
|
-
* This allows proper error propagation without calling process.exit() directly,
|
|
30748
|
-
* making the code more testable and maintaining a single exit point.
|
|
30749
|
-
*/
|
|
30750
|
-
var CLIExitError = class extends Error {
|
|
30751
|
-
constructor(code$1) {
|
|
30752
|
-
super(`CLI exited with code ${code$1}`);
|
|
30753
|
-
this.code = code$1;
|
|
30754
|
-
this.name = "CLIExitError";
|
|
30755
|
-
}
|
|
30756
|
-
};
|
|
30757
|
-
|
|
30758
30589
|
//#endregion
|
|
30759
30590
|
//#region node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
30760
30591
|
const ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -31175,10 +31006,7 @@ const theme = {
|
|
|
31175
31006
|
base44OrangeBackground: source_default.bgHex("#E86B3C"),
|
|
31176
31007
|
shinyOrange: source_default.hex("#FFD700"),
|
|
31177
31008
|
links: source_default.hex("#00D4FF"),
|
|
31178
|
-
white: source_default.white
|
|
31179
|
-
success: source_default.green,
|
|
31180
|
-
warning: source_default.yellow,
|
|
31181
|
-
error: source_default.red
|
|
31009
|
+
white: source_default.white
|
|
31182
31010
|
},
|
|
31183
31011
|
styles: {
|
|
31184
31012
|
header: source_default.dim,
|
|
@@ -31324,7 +31152,7 @@ async function runCommand(commandFn, options) {
|
|
|
31324
31152
|
} catch (e$1) {
|
|
31325
31153
|
if (e$1 instanceof Error) M.error(e$1.stack ?? e$1.message);
|
|
31326
31154
|
else M.error(String(e$1));
|
|
31327
|
-
|
|
31155
|
+
process.exit(1);
|
|
31328
31156
|
}
|
|
31329
31157
|
}
|
|
31330
31158
|
|
|
@@ -38203,13 +38031,6 @@ var require_lodash = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
38203
38031
|
//#region src/cli/commands/project/create.ts
|
|
38204
38032
|
var import_lodash = /* @__PURE__ */ __toESM(require_lodash(), 1);
|
|
38205
38033
|
const DEFAULT_TEMPLATE_ID = "backend-only";
|
|
38206
|
-
const SUPPORTED_AGENTS = [{
|
|
38207
|
-
value: "cursor",
|
|
38208
|
-
label: "Cursor"
|
|
38209
|
-
}, {
|
|
38210
|
-
value: "claude-code",
|
|
38211
|
-
label: "Claude Code"
|
|
38212
|
-
}];
|
|
38213
38034
|
async function getTemplateById(templateId) {
|
|
38214
38035
|
const templates = await listTemplates();
|
|
38215
38036
|
const template = templates.find((t) => t.id === templateId);
|
|
@@ -38272,7 +38093,6 @@ async function createInteractive(options) {
|
|
|
38272
38093
|
description: result.description || void 0,
|
|
38273
38094
|
projectPath: result.projectPath,
|
|
38274
38095
|
deploy: options.deploy,
|
|
38275
|
-
skills: options.skills,
|
|
38276
38096
|
isInteractive: true
|
|
38277
38097
|
});
|
|
38278
38098
|
}
|
|
@@ -38283,11 +38103,10 @@ async function createNonInteractive(options) {
|
|
|
38283
38103
|
description: options.description,
|
|
38284
38104
|
projectPath: options.path,
|
|
38285
38105
|
deploy: options.deploy,
|
|
38286
|
-
skills: options.skills,
|
|
38287
38106
|
isInteractive: false
|
|
38288
38107
|
});
|
|
38289
38108
|
}
|
|
38290
|
-
async function executeCreate({ template, name: rawName, description, projectPath, deploy,
|
|
38109
|
+
async function executeCreate({ template, name: rawName, description, projectPath, deploy, isInteractive }) {
|
|
38291
38110
|
const name$1 = rawName.trim();
|
|
38292
38111
|
const resolvedPath = resolve(projectPath);
|
|
38293
38112
|
const { projectId } = await runTask("Setting up your project...", async () => {
|
|
@@ -38347,45 +38166,12 @@ async function executeCreate({ template, name: rawName, description, projectPath
|
|
|
38347
38166
|
finalAppUrl = appUrl;
|
|
38348
38167
|
}
|
|
38349
38168
|
}
|
|
38350
|
-
let selectedAgents = [];
|
|
38351
|
-
if (isInteractive) {
|
|
38352
|
-
const result = await fe({
|
|
38353
|
-
message: "Add AI agent skills? (Select agents to configure)",
|
|
38354
|
-
options: SUPPORTED_AGENTS,
|
|
38355
|
-
initialValues: SUPPORTED_AGENTS.map((agent) => agent.value),
|
|
38356
|
-
required: false
|
|
38357
|
-
});
|
|
38358
|
-
if (!pD(result)) selectedAgents = result;
|
|
38359
|
-
} else if (skills) selectedAgents = SUPPORTED_AGENTS.map((agent) => agent.value);
|
|
38360
|
-
if (selectedAgents.length > 0) {
|
|
38361
|
-
const agentArgs = selectedAgents.flatMap((agent) => ["-a", agent]);
|
|
38362
|
-
M.step(`Installing skills for: ${selectedAgents.join(", ")}`);
|
|
38363
|
-
await runTask(`Installing skills for: ${selectedAgents.join(", ")}`, async () => {
|
|
38364
|
-
await execa("npx", [
|
|
38365
|
-
"-y",
|
|
38366
|
-
"add-skill",
|
|
38367
|
-
"base44/skills",
|
|
38368
|
-
"-y",
|
|
38369
|
-
"-s",
|
|
38370
|
-
"base44-cli",
|
|
38371
|
-
"-s",
|
|
38372
|
-
"base44-sdk",
|
|
38373
|
-
...agentArgs
|
|
38374
|
-
], {
|
|
38375
|
-
cwd: resolvedPath,
|
|
38376
|
-
stdio: "inherit"
|
|
38377
|
-
});
|
|
38378
|
-
}, {
|
|
38379
|
-
successMessage: theme.colors.base44Orange("AI agent skills added successfully"),
|
|
38380
|
-
errorMessage: "Failed to add AI agent skills - you can add them later with: npx add-skill base44/skills"
|
|
38381
|
-
});
|
|
38382
|
-
}
|
|
38383
38169
|
M.message(`${theme.styles.header("Project")}: ${theme.colors.base44Orange(name$1)}`);
|
|
38384
38170
|
M.message(`${theme.styles.header("Dashboard")}: ${theme.colors.links(getDashboardUrl(projectId))}`);
|
|
38385
38171
|
if (finalAppUrl) M.message(`${theme.styles.header("Site")}: ${theme.colors.links(finalAppUrl)}`);
|
|
38386
38172
|
return { outroMessage: "Your project is set up and ready to use" };
|
|
38387
38173
|
}
|
|
38388
|
-
const createCommand = new Command("create").description("Create a new Base44 project").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-p, --path <path>", "Path where to create the project").option("-t, --template <id>", "Template ID (e.g., backend-only, backend-and-client)").option("--deploy", "Build and deploy the site").
|
|
38174
|
+
const createCommand = new Command("create").description("Create a new Base44 project").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-p, --path <path>", "Path where to create the project").option("-t, --template <id>", "Template ID (e.g., backend-only, backend-and-client)").option("--deploy", "Build and deploy the site").hook("preAction", validateNonInteractiveFlags$1).action(async (options) => {
|
|
38389
38175
|
await chooseCreate(options);
|
|
38390
38176
|
});
|
|
38391
38177
|
|
|
@@ -39109,663 +38895,12 @@ const siteDeployCommand = new Command("site").description("Manage site deploymen
|
|
|
39109
38895
|
await runCommand(() => deployAction(options), { requireAuth: true });
|
|
39110
38896
|
}));
|
|
39111
38897
|
|
|
39112
|
-
//#endregion
|
|
39113
|
-
//#region src/core/connectors/schema.ts
|
|
39114
|
-
/**
|
|
39115
|
-
* Response from POST /api/apps/{app_id}/external-auth/initiate
|
|
39116
|
-
*/
|
|
39117
|
-
const InitiateResponseSchema = object({
|
|
39118
|
-
redirect_url: string().nullish(),
|
|
39119
|
-
connection_id: string().nullish(),
|
|
39120
|
-
already_authorized: boolean().nullish(),
|
|
39121
|
-
other_user_email: string().nullish(),
|
|
39122
|
-
error: string().nullish()
|
|
39123
|
-
});
|
|
39124
|
-
/**
|
|
39125
|
-
* Response from GET /api/apps/{app_id}/external-auth/status
|
|
39126
|
-
*/
|
|
39127
|
-
const StatusResponseSchema = object({
|
|
39128
|
-
status: _enum([
|
|
39129
|
-
"ACTIVE",
|
|
39130
|
-
"PENDING",
|
|
39131
|
-
"FAILED"
|
|
39132
|
-
]),
|
|
39133
|
-
account_email: string().nullish(),
|
|
39134
|
-
error: string().nullish()
|
|
39135
|
-
}).transform((data) => ({
|
|
39136
|
-
status: data.status,
|
|
39137
|
-
accountEmail: data.account_email,
|
|
39138
|
-
error: data.error
|
|
39139
|
-
}));
|
|
39140
|
-
/**
|
|
39141
|
-
* A connected integration from the list endpoint
|
|
39142
|
-
*/
|
|
39143
|
-
const ConnectorSchema = object({
|
|
39144
|
-
integration_type: string(),
|
|
39145
|
-
status: string(),
|
|
39146
|
-
connected_at: string().nullish(),
|
|
39147
|
-
account_info: object({
|
|
39148
|
-
email: string().nullish(),
|
|
39149
|
-
name: string().nullish()
|
|
39150
|
-
}).nullish()
|
|
39151
|
-
}).transform((data) => ({
|
|
39152
|
-
integrationType: data.integration_type,
|
|
39153
|
-
status: data.status,
|
|
39154
|
-
connectedAt: data.connected_at,
|
|
39155
|
-
accountInfo: data.account_info
|
|
39156
|
-
}));
|
|
39157
|
-
/**
|
|
39158
|
-
* Response from GET /api/apps/{app_id}/external-auth/list
|
|
39159
|
-
*/
|
|
39160
|
-
const ListResponseSchema = object({ integrations: array(ConnectorSchema) });
|
|
39161
|
-
/**
|
|
39162
|
-
* Generic API error response
|
|
39163
|
-
*/
|
|
39164
|
-
const ApiErrorSchema = object({
|
|
39165
|
-
error: string(),
|
|
39166
|
-
detail: string().nullish()
|
|
39167
|
-
});
|
|
39168
|
-
|
|
39169
|
-
//#endregion
|
|
39170
|
-
//#region src/core/connectors/api.ts
|
|
39171
|
-
/**
|
|
39172
|
-
* Initiates OAuth flow for a connector integration.
|
|
39173
|
-
* Returns a redirect URL to open in the browser.
|
|
39174
|
-
*/
|
|
39175
|
-
async function initiateOAuth(integrationType, scopes = null) {
|
|
39176
|
-
const response = await getAppClient().post("external-auth/initiate", {
|
|
39177
|
-
json: {
|
|
39178
|
-
integration_type: integrationType,
|
|
39179
|
-
scopes
|
|
39180
|
-
},
|
|
39181
|
-
throwHttpErrors: false
|
|
39182
|
-
});
|
|
39183
|
-
const json = await response.json();
|
|
39184
|
-
if (!response.ok) {
|
|
39185
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39186
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39187
|
-
throw new ConnectorApiError(`Failed to initiate OAuth: ${response.status} ${response.statusText}`);
|
|
39188
|
-
}
|
|
39189
|
-
const result = InitiateResponseSchema.safeParse(json);
|
|
39190
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid initiate response from server: ${result.error.message}`);
|
|
39191
|
-
return result.data;
|
|
39192
|
-
}
|
|
39193
|
-
/**
|
|
39194
|
-
* Checks the status of an OAuth connection attempt.
|
|
39195
|
-
*/
|
|
39196
|
-
async function checkOAuthStatus(integrationType, connectionId) {
|
|
39197
|
-
const response = await getAppClient().get("external-auth/status", {
|
|
39198
|
-
searchParams: {
|
|
39199
|
-
integration_type: integrationType,
|
|
39200
|
-
connection_id: connectionId
|
|
39201
|
-
},
|
|
39202
|
-
throwHttpErrors: false
|
|
39203
|
-
});
|
|
39204
|
-
const json = await response.json();
|
|
39205
|
-
if (!response.ok) {
|
|
39206
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39207
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39208
|
-
throw new ConnectorApiError(`Failed to check OAuth status: ${response.status} ${response.statusText}`);
|
|
39209
|
-
}
|
|
39210
|
-
const result = StatusResponseSchema.safeParse(json);
|
|
39211
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid status response from server: ${result.error.message}`);
|
|
39212
|
-
return result.data;
|
|
39213
|
-
}
|
|
39214
|
-
/**
|
|
39215
|
-
* Lists all connected integrations for the current app.
|
|
39216
|
-
*/
|
|
39217
|
-
async function listConnectors() {
|
|
39218
|
-
const response = await getAppClient().get("external-auth/list", { throwHttpErrors: false });
|
|
39219
|
-
const json = await response.json();
|
|
39220
|
-
if (!response.ok) {
|
|
39221
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39222
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39223
|
-
throw new ConnectorApiError(`Failed to list connectors: ${response.status} ${response.statusText}`);
|
|
39224
|
-
}
|
|
39225
|
-
const result = ListResponseSchema.safeParse(json);
|
|
39226
|
-
if (!result.success) throw new ConnectorValidationError(`Invalid list response from server: ${result.error.message}`);
|
|
39227
|
-
return result.data.integrations;
|
|
39228
|
-
}
|
|
39229
|
-
/**
|
|
39230
|
-
* Disconnects (soft delete) a connector integration.
|
|
39231
|
-
*/
|
|
39232
|
-
async function disconnectConnector(integrationType) {
|
|
39233
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}`, { throwHttpErrors: false });
|
|
39234
|
-
if (!response.ok) {
|
|
39235
|
-
const json = await response.json();
|
|
39236
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39237
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39238
|
-
throw new ConnectorApiError(`Failed to disconnect connector: ${response.status} ${response.statusText}`);
|
|
39239
|
-
}
|
|
39240
|
-
}
|
|
39241
|
-
/**
|
|
39242
|
-
* Removes (hard delete) a connector integration.
|
|
39243
|
-
* This permanently removes the connector and cannot be undone.
|
|
39244
|
-
*/
|
|
39245
|
-
async function removeConnector(integrationType) {
|
|
39246
|
-
const response = await getAppClient().delete(`external-auth/integrations/${integrationType}/remove`, { throwHttpErrors: false });
|
|
39247
|
-
if (!response.ok) {
|
|
39248
|
-
const json = await response.json();
|
|
39249
|
-
const errorResult = ApiErrorSchema.safeParse(json);
|
|
39250
|
-
if (errorResult.success) throw new ConnectorApiError(errorResult.data.error);
|
|
39251
|
-
throw new ConnectorApiError(`Failed to remove connector: ${response.status} ${response.statusText}`);
|
|
39252
|
-
}
|
|
39253
|
-
}
|
|
39254
|
-
|
|
39255
|
-
//#endregion
|
|
39256
|
-
//#region src/core/connectors/constants.ts
|
|
39257
|
-
/**
|
|
39258
|
-
* OAuth polling configuration
|
|
39259
|
-
*/
|
|
39260
|
-
const OAUTH_POLL_INTERVAL_MS = 2e3;
|
|
39261
|
-
const OAUTH_POLL_TIMEOUT_MS = 300 * 1e3;
|
|
39262
|
-
/**
|
|
39263
|
-
* Supported OAuth connector integrations.
|
|
39264
|
-
* Based on apper/backend/app/external_auth/models/constants.py
|
|
39265
|
-
*/
|
|
39266
|
-
const SUPPORTED_INTEGRATIONS = [
|
|
39267
|
-
"googlecalendar",
|
|
39268
|
-
"googledrive",
|
|
39269
|
-
"gmail",
|
|
39270
|
-
"googlesheets",
|
|
39271
|
-
"googledocs",
|
|
39272
|
-
"googleslides",
|
|
39273
|
-
"slack",
|
|
39274
|
-
"notion",
|
|
39275
|
-
"salesforce",
|
|
39276
|
-
"hubspot",
|
|
39277
|
-
"linkedin",
|
|
39278
|
-
"tiktok"
|
|
39279
|
-
];
|
|
39280
|
-
/**
|
|
39281
|
-
* Display names for integrations (for CLI output)
|
|
39282
|
-
*/
|
|
39283
|
-
const INTEGRATION_DISPLAY_NAMES = {
|
|
39284
|
-
googlecalendar: "Google Calendar",
|
|
39285
|
-
googledrive: "Google Drive",
|
|
39286
|
-
gmail: "Gmail",
|
|
39287
|
-
googlesheets: "Google Sheets",
|
|
39288
|
-
googledocs: "Google Docs",
|
|
39289
|
-
googleslides: "Google Slides",
|
|
39290
|
-
slack: "Slack",
|
|
39291
|
-
notion: "Notion",
|
|
39292
|
-
salesforce: "Salesforce",
|
|
39293
|
-
hubspot: "HubSpot",
|
|
39294
|
-
linkedin: "LinkedIn",
|
|
39295
|
-
tiktok: "TikTok"
|
|
39296
|
-
};
|
|
39297
|
-
function isValidIntegration(type) {
|
|
39298
|
-
return SUPPORTED_INTEGRATIONS.includes(type);
|
|
39299
|
-
}
|
|
39300
|
-
function getIntegrationDisplayName(type) {
|
|
39301
|
-
if (isValidIntegration(type)) return INTEGRATION_DISPLAY_NAMES[type];
|
|
39302
|
-
return type;
|
|
39303
|
-
}
|
|
39304
|
-
|
|
39305
|
-
//#endregion
|
|
39306
|
-
//#region src/core/connectors/oauth.ts
|
|
39307
|
-
/**
|
|
39308
|
-
* Polls for OAuth completion status.
|
|
39309
|
-
* Returns when status becomes ACTIVE or FAILED, or times out.
|
|
39310
|
-
*/
|
|
39311
|
-
async function waitForOAuthCompletion(integrationType, connectionId, options) {
|
|
39312
|
-
let accountEmail;
|
|
39313
|
-
let error;
|
|
39314
|
-
try {
|
|
39315
|
-
await pWaitFor(async () => {
|
|
39316
|
-
const status = await checkOAuthStatus(integrationType, connectionId);
|
|
39317
|
-
if (status.status === "ACTIVE") {
|
|
39318
|
-
accountEmail = status.accountEmail ?? void 0;
|
|
39319
|
-
return true;
|
|
39320
|
-
}
|
|
39321
|
-
if (status.status === "FAILED") {
|
|
39322
|
-
error = status.error || "Authorization failed";
|
|
39323
|
-
throw new Error(error);
|
|
39324
|
-
}
|
|
39325
|
-
options?.onPending?.();
|
|
39326
|
-
return false;
|
|
39327
|
-
}, {
|
|
39328
|
-
interval: OAUTH_POLL_INTERVAL_MS,
|
|
39329
|
-
timeout: OAUTH_POLL_TIMEOUT_MS
|
|
39330
|
-
});
|
|
39331
|
-
return {
|
|
39332
|
-
success: true,
|
|
39333
|
-
accountEmail
|
|
39334
|
-
};
|
|
39335
|
-
} catch (err) {
|
|
39336
|
-
if (err instanceof Error && err.message.includes("timed out")) return {
|
|
39337
|
-
success: false,
|
|
39338
|
-
error: "Authorization timed out. Please try again."
|
|
39339
|
-
};
|
|
39340
|
-
return {
|
|
39341
|
-
success: false,
|
|
39342
|
-
error: error || (err instanceof Error ? err.message : "Unknown error")
|
|
39343
|
-
};
|
|
39344
|
-
}
|
|
39345
|
-
}
|
|
39346
|
-
|
|
39347
|
-
//#endregion
|
|
39348
|
-
//#region src/core/connectors/config.ts
|
|
39349
|
-
/**
|
|
39350
|
-
* Read all connectors from the project config file
|
|
39351
|
-
*/
|
|
39352
|
-
async function readLocalConnectors(projectRoot) {
|
|
39353
|
-
const found = await findProjectRoot(projectRoot);
|
|
39354
|
-
if (!found) return [];
|
|
39355
|
-
const connectorsData = (await readJsonFile(found.configPath)).connectors;
|
|
39356
|
-
if (!connectorsData) return [];
|
|
39357
|
-
const connectors = [];
|
|
39358
|
-
for (const [type, config$1] of Object.entries(connectorsData)) {
|
|
39359
|
-
if (!isValidIntegration(type)) throw new Error(`Unknown connector type: ${type}`);
|
|
39360
|
-
connectors.push({
|
|
39361
|
-
type,
|
|
39362
|
-
scopes: config$1.scopes
|
|
39363
|
-
});
|
|
39364
|
-
}
|
|
39365
|
-
return connectors;
|
|
39366
|
-
}
|
|
39367
|
-
/**
|
|
39368
|
-
* Write connectors to the project config file
|
|
39369
|
-
*/
|
|
39370
|
-
async function writeLocalConnectors(connectors, projectRoot) {
|
|
39371
|
-
const found = await findProjectRoot(projectRoot);
|
|
39372
|
-
if (!found) throw new Error("Project config not found. Run this command from a Base44 project directory.");
|
|
39373
|
-
const existingConfig = await readJsonFile(found.configPath);
|
|
39374
|
-
const connectorsData = {};
|
|
39375
|
-
for (const connector of connectors) connectorsData[connector.type] = { ...connector.scopes && { scopes: connector.scopes } };
|
|
39376
|
-
const updatedConfig = {
|
|
39377
|
-
...existingConfig,
|
|
39378
|
-
connectors: Object.keys(connectorsData).length > 0 ? connectorsData : void 0
|
|
39379
|
-
};
|
|
39380
|
-
if (!updatedConfig.connectors) delete updatedConfig.connectors;
|
|
39381
|
-
await writeJsonFile(found.configPath, updatedConfig);
|
|
39382
|
-
return found.configPath;
|
|
39383
|
-
}
|
|
39384
|
-
/**
|
|
39385
|
-
* Add a connector to the project config file
|
|
39386
|
-
*/
|
|
39387
|
-
async function addLocalConnector(type, scopes, projectRoot) {
|
|
39388
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39389
|
-
const existing = connectors.find((c$1) => c$1.type === type);
|
|
39390
|
-
if (existing) {
|
|
39391
|
-
if (scopes) existing.scopes = scopes;
|
|
39392
|
-
} else connectors.push({
|
|
39393
|
-
type,
|
|
39394
|
-
scopes
|
|
39395
|
-
});
|
|
39396
|
-
return await writeLocalConnectors(connectors, projectRoot);
|
|
39397
|
-
}
|
|
39398
|
-
/**
|
|
39399
|
-
* Remove a connector from the project config file
|
|
39400
|
-
*/
|
|
39401
|
-
async function removeLocalConnector(type, projectRoot) {
|
|
39402
|
-
const connectors = await readLocalConnectors(projectRoot);
|
|
39403
|
-
const filtered = connectors.filter((c$1) => c$1.type !== type);
|
|
39404
|
-
if (filtered.length === connectors.length) return null;
|
|
39405
|
-
return await writeLocalConnectors(filtered, projectRoot);
|
|
39406
|
-
}
|
|
39407
|
-
|
|
39408
|
-
//#endregion
|
|
39409
|
-
//#region src/core/connectors/state.ts
|
|
39410
|
-
/**
|
|
39411
|
-
* Fetches both local config and backend connectors in parallel.
|
|
39412
|
-
* Gracefully handles failures by returning empty arrays.
|
|
39413
|
-
*/
|
|
39414
|
-
async function fetchConnectorState() {
|
|
39415
|
-
const [local, backend] = await Promise.all([readLocalConnectors().catch(() => []), listConnectors().catch(() => [])]);
|
|
39416
|
-
return {
|
|
39417
|
-
local,
|
|
39418
|
-
backend
|
|
39419
|
-
};
|
|
39420
|
-
}
|
|
39421
|
-
|
|
39422
|
-
//#endregion
|
|
39423
|
-
//#region src/cli/commands/connectors/add.ts
|
|
39424
|
-
function validateIntegrationType(type) {
|
|
39425
|
-
if (!isValidIntegration(type)) {
|
|
39426
|
-
const supportedList = SUPPORTED_INTEGRATIONS.join(", ");
|
|
39427
|
-
throw new Error(`Unsupported connector: ${type}\nSupported connectors: ${supportedList}`);
|
|
39428
|
-
}
|
|
39429
|
-
return type;
|
|
39430
|
-
}
|
|
39431
|
-
async function promptForIntegrationType() {
|
|
39432
|
-
const selected = await ve({
|
|
39433
|
-
message: "Select an integration to connect:",
|
|
39434
|
-
options: SUPPORTED_INTEGRATIONS.map((type) => ({
|
|
39435
|
-
value: type,
|
|
39436
|
-
label: getIntegrationDisplayName(type)
|
|
39437
|
-
}))
|
|
39438
|
-
});
|
|
39439
|
-
if (pD(selected)) {
|
|
39440
|
-
xe("Operation cancelled.");
|
|
39441
|
-
process.exit(0);
|
|
39442
|
-
}
|
|
39443
|
-
return selected;
|
|
39444
|
-
}
|
|
39445
|
-
async function addConnector(integrationType) {
|
|
39446
|
-
const selectedType = integrationType ? validateIntegrationType(integrationType) : await promptForIntegrationType();
|
|
39447
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39448
|
-
const result = await runTask(`Initiating ${displayName} connection...`, async (updateMessage) => {
|
|
39449
|
-
const initiateResponse = await initiateOAuth(selectedType);
|
|
39450
|
-
if (initiateResponse.already_authorized) {
|
|
39451
|
-
await addLocalConnector(selectedType);
|
|
39452
|
-
return { alreadyAuthorized: true };
|
|
39453
|
-
}
|
|
39454
|
-
if (initiateResponse.error === "different_user" && initiateResponse.other_user_email) throw new Error(`This app is already connected to ${displayName} by ${initiateResponse.other_user_email}`);
|
|
39455
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) throw new Error("Invalid response from server: missing redirect URL or connection ID");
|
|
39456
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirect_url)}`);
|
|
39457
|
-
updateMessage("Waiting for authorization...");
|
|
39458
|
-
const oauthResult = await waitForOAuthCompletion(selectedType, initiateResponse.connection_id);
|
|
39459
|
-
if (!oauthResult.success) throw new Error(oauthResult.error || "Authorization failed");
|
|
39460
|
-
await addLocalConnector(selectedType);
|
|
39461
|
-
return {
|
|
39462
|
-
alreadyAuthorized: false,
|
|
39463
|
-
accountEmail: oauthResult.accountEmail
|
|
39464
|
-
};
|
|
39465
|
-
}, {
|
|
39466
|
-
successMessage: "Authorization completed!",
|
|
39467
|
-
errorMessage: `Failed to connect ${displayName}`
|
|
39468
|
-
});
|
|
39469
|
-
if (result.alreadyAuthorized) return { outroMessage: `Already connected to ${theme.styles.bold(displayName)} (added to config)` };
|
|
39470
|
-
const accountInfo = result.accountEmail ? ` as ${theme.styles.bold(result.accountEmail)}` : "";
|
|
39471
|
-
return { outroMessage: `Successfully connected to ${theme.styles.bold(displayName)}${accountInfo}` };
|
|
39472
|
-
}
|
|
39473
|
-
const connectorsAddCommand = new Command("add").argument("[type]", "Integration type (e.g., slack, notion, googlecalendar)").description("Connect an OAuth integration").action(async (type) => {
|
|
39474
|
-
await runCommand(() => addConnector(type), {
|
|
39475
|
-
requireAuth: true,
|
|
39476
|
-
requireAppConfig: true
|
|
39477
|
-
});
|
|
39478
|
-
});
|
|
39479
|
-
|
|
39480
|
-
//#endregion
|
|
39481
|
-
//#region src/cli/commands/connectors/list.ts
|
|
39482
|
-
function mergeConnectors(local, backend) {
|
|
39483
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39484
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39485
|
-
type: connector.type,
|
|
39486
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39487
|
-
inLocal: true,
|
|
39488
|
-
inBackend: false
|
|
39489
|
-
});
|
|
39490
|
-
for (const connector of backend) {
|
|
39491
|
-
const existing = merged.get(connector.integrationType);
|
|
39492
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39493
|
-
if (existing) {
|
|
39494
|
-
existing.inBackend = true;
|
|
39495
|
-
existing.status = connector.status;
|
|
39496
|
-
existing.accountEmail = accountEmail;
|
|
39497
|
-
} else merged.set(connector.integrationType, {
|
|
39498
|
-
type: connector.integrationType,
|
|
39499
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39500
|
-
inLocal: false,
|
|
39501
|
-
inBackend: true,
|
|
39502
|
-
status: connector.status,
|
|
39503
|
-
accountEmail
|
|
39504
|
-
});
|
|
39505
|
-
}
|
|
39506
|
-
return Array.from(merged.values());
|
|
39507
|
-
}
|
|
39508
|
-
function formatConnectorLine(connector) {
|
|
39509
|
-
const { displayName, inLocal, inBackend, status, accountEmail } = connector;
|
|
39510
|
-
const isConnected$1 = inBackend && status?.toLowerCase() === "active";
|
|
39511
|
-
const isPending = inLocal && !inBackend;
|
|
39512
|
-
const isOrphaned = inBackend && !inLocal;
|
|
39513
|
-
let bullet;
|
|
39514
|
-
let statusText = "";
|
|
39515
|
-
if (isConnected$1) {
|
|
39516
|
-
bullet = theme.colors.success("●");
|
|
39517
|
-
if (accountEmail) statusText = ` - ${accountEmail}`;
|
|
39518
|
-
} else if (isPending) {
|
|
39519
|
-
bullet = theme.colors.warning("○");
|
|
39520
|
-
statusText = theme.styles.dim(" (not connected)");
|
|
39521
|
-
} else if (isOrphaned) {
|
|
39522
|
-
bullet = theme.colors.error("○");
|
|
39523
|
-
statusText = theme.styles.dim(" (not in local config)");
|
|
39524
|
-
} else {
|
|
39525
|
-
bullet = theme.colors.error("○");
|
|
39526
|
-
statusText = theme.styles.dim(` (${status || "disconnected"})`);
|
|
39527
|
-
}
|
|
39528
|
-
return `${bullet} ${displayName}${statusText}`;
|
|
39529
|
-
}
|
|
39530
|
-
async function listConnectorsCommand() {
|
|
39531
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Fetching connectors...", fetchConnectorState, {
|
|
39532
|
-
successMessage: "Connectors loaded",
|
|
39533
|
-
errorMessage: "Failed to fetch connectors"
|
|
39534
|
-
});
|
|
39535
|
-
const merged = mergeConnectors(localConnectors, backendConnectors);
|
|
39536
|
-
if (merged.length === 0) {
|
|
39537
|
-
M.info("No connectors configured for this app.");
|
|
39538
|
-
M.info(`Run ${theme.styles.bold("base44 connectors add")} to connect an integration.`);
|
|
39539
|
-
return { outroMessage: "" };
|
|
39540
|
-
}
|
|
39541
|
-
console.log();
|
|
39542
|
-
for (const connector of merged) console.log(formatConnectorLine(connector));
|
|
39543
|
-
console.log();
|
|
39544
|
-
const connected = merged.filter((c$1) => c$1.inBackend && c$1.status?.toLowerCase() === "active").length;
|
|
39545
|
-
const pending = merged.filter((c$1) => c$1.inLocal && !c$1.inBackend).length;
|
|
39546
|
-
let summary = `${connected} connected`;
|
|
39547
|
-
if (pending > 0) {
|
|
39548
|
-
summary += `, ${pending} pending`;
|
|
39549
|
-
M.info(`Run ${theme.styles.bold("base44 connectors push")} to connect pending integrations.`);
|
|
39550
|
-
}
|
|
39551
|
-
return { outroMessage: summary };
|
|
39552
|
-
}
|
|
39553
|
-
const connectorsListCommand = new Command("list").description("List all connected OAuth integrations").action(async () => {
|
|
39554
|
-
await runCommand(listConnectorsCommand, {
|
|
39555
|
-
requireAuth: true,
|
|
39556
|
-
requireAppConfig: true
|
|
39557
|
-
});
|
|
39558
|
-
});
|
|
39559
|
-
|
|
39560
|
-
//#endregion
|
|
39561
|
-
//#region src/cli/commands/connectors/push.ts
|
|
39562
|
-
function findPendingConnectors(local, backend) {
|
|
39563
|
-
const connectedTypes = new Set(backend.filter((c$1) => c$1.status.toLowerCase() === "active").map((c$1) => c$1.integrationType));
|
|
39564
|
-
return local.filter((c$1) => !connectedTypes.has(c$1.type)).map((c$1) => ({
|
|
39565
|
-
type: c$1.type,
|
|
39566
|
-
displayName: getIntegrationDisplayName(c$1.type),
|
|
39567
|
-
scopes: c$1.scopes
|
|
39568
|
-
}));
|
|
39569
|
-
}
|
|
39570
|
-
function findOrphanedConnectors(local, backend) {
|
|
39571
|
-
const localTypes = new Set(local.map((c$1) => c$1.type));
|
|
39572
|
-
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) => ({
|
|
39573
|
-
type: c$1.integrationType,
|
|
39574
|
-
displayName: getIntegrationDisplayName(c$1.integrationType),
|
|
39575
|
-
accountEmail: (c$1.accountInfo?.email || c$1.accountInfo?.name) ?? void 0
|
|
39576
|
-
}));
|
|
39577
|
-
}
|
|
39578
|
-
async function connectSingleConnector(connector) {
|
|
39579
|
-
const { type, displayName, scopes } = connector;
|
|
39580
|
-
return await runTask(`Initiating ${displayName} connection...`, async (updateMessage) => {
|
|
39581
|
-
const initiateResponse = await initiateOAuth(type, scopes || null);
|
|
39582
|
-
if (initiateResponse.already_authorized) return { success: true };
|
|
39583
|
-
if (initiateResponse.error === "different_user") return {
|
|
39584
|
-
success: false,
|
|
39585
|
-
error: `Already connected by ${initiateResponse.other_user_email}`
|
|
39586
|
-
};
|
|
39587
|
-
if (!initiateResponse.redirect_url || !initiateResponse.connection_id) return {
|
|
39588
|
-
success: false,
|
|
39589
|
-
error: "Invalid response from server"
|
|
39590
|
-
};
|
|
39591
|
-
M.info(`Please authorize ${displayName} at:\n${theme.colors.links(initiateResponse.redirect_url)}`);
|
|
39592
|
-
updateMessage("Waiting for authorization...");
|
|
39593
|
-
return await waitForOAuthCompletion(type, initiateResponse.connection_id);
|
|
39594
|
-
}, {
|
|
39595
|
-
successMessage: "Authorization completed!",
|
|
39596
|
-
errorMessage: "Authorization failed"
|
|
39597
|
-
});
|
|
39598
|
-
}
|
|
39599
|
-
async function pushConnectorsCommand() {
|
|
39600
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Checking connector status...", fetchConnectorState, {
|
|
39601
|
-
successMessage: "Status checked",
|
|
39602
|
-
errorMessage: "Failed to check status"
|
|
39603
|
-
});
|
|
39604
|
-
const pending = findPendingConnectors(localConnectors, backendConnectors);
|
|
39605
|
-
const orphaned = findOrphanedConnectors(localConnectors, backendConnectors);
|
|
39606
|
-
if (pending.length === 0 && orphaned.length === 0) return { outroMessage: "All connectors are in sync" };
|
|
39607
|
-
console.log();
|
|
39608
|
-
if (pending.length > 0) {
|
|
39609
|
-
M.info(`${pending.length} connector${pending.length === 1 ? "" : "s"} to connect:`);
|
|
39610
|
-
for (const c$1 of pending) console.log(` ${theme.colors.success("+")} ${c$1.displayName}`);
|
|
39611
|
-
}
|
|
39612
|
-
if (orphaned.length > 0) {
|
|
39613
|
-
M.info(`${orphaned.length} connector${orphaned.length === 1 ? "" : "s"} to remove:`);
|
|
39614
|
-
for (const c$1 of orphaned) {
|
|
39615
|
-
const accountInfo = c$1.accountEmail ? ` (${c$1.accountEmail})` : "";
|
|
39616
|
-
console.log(` ${theme.colors.error("-")} ${c$1.displayName}${accountInfo}`);
|
|
39617
|
-
}
|
|
39618
|
-
}
|
|
39619
|
-
console.log();
|
|
39620
|
-
const totalChanges = pending.length + orphaned.length;
|
|
39621
|
-
const shouldProceed = await ye({
|
|
39622
|
-
message: `Apply ${totalChanges} change${totalChanges === 1 ? "" : "s"}?`,
|
|
39623
|
-
initialValue: true
|
|
39624
|
-
});
|
|
39625
|
-
if (pD(shouldProceed) || !shouldProceed) return { outroMessage: "Cancelled" };
|
|
39626
|
-
let connected = 0;
|
|
39627
|
-
let removed = 0;
|
|
39628
|
-
let failed = 0;
|
|
39629
|
-
for (const connector of orphaned) try {
|
|
39630
|
-
await disconnectConnector(connector.type);
|
|
39631
|
-
M.success(`Removed ${connector.displayName}`);
|
|
39632
|
-
removed++;
|
|
39633
|
-
} catch (err) {
|
|
39634
|
-
M.error(`Failed to remove ${connector.displayName}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
39635
|
-
failed++;
|
|
39636
|
-
}
|
|
39637
|
-
for (const connector of pending) {
|
|
39638
|
-
console.log();
|
|
39639
|
-
const result = await connectSingleConnector(connector);
|
|
39640
|
-
if (result.success) {
|
|
39641
|
-
const accountInfo = result.accountEmail ? ` as ${result.accountEmail}` : "";
|
|
39642
|
-
M.success(`${connector.displayName} connected${accountInfo}`);
|
|
39643
|
-
connected++;
|
|
39644
|
-
} else {
|
|
39645
|
-
M.error(`${connector.displayName} failed: ${result.error}`);
|
|
39646
|
-
failed++;
|
|
39647
|
-
}
|
|
39648
|
-
}
|
|
39649
|
-
console.log();
|
|
39650
|
-
const parts = [];
|
|
39651
|
-
if (connected > 0) parts.push(`${connected} connected`);
|
|
39652
|
-
if (removed > 0) parts.push(`${removed} removed`);
|
|
39653
|
-
if (failed > 0) parts.push(`${failed} failed`);
|
|
39654
|
-
return { outroMessage: parts.join(", ") };
|
|
39655
|
-
}
|
|
39656
|
-
const connectorsPushCommand = new Command("push").description("Sync connectors with backend (connect new, remove missing)").action(async () => {
|
|
39657
|
-
await runCommand(pushConnectorsCommand, {
|
|
39658
|
-
requireAuth: true,
|
|
39659
|
-
requireAppConfig: true
|
|
39660
|
-
});
|
|
39661
|
-
});
|
|
39662
|
-
|
|
39663
|
-
//#endregion
|
|
39664
|
-
//#region src/cli/commands/connectors/remove.ts
|
|
39665
|
-
function mergeConnectorsForRemoval(local, backend) {
|
|
39666
|
-
const merged = /* @__PURE__ */ new Map();
|
|
39667
|
-
for (const connector of local) merged.set(connector.type, {
|
|
39668
|
-
type: connector.type,
|
|
39669
|
-
displayName: getIntegrationDisplayName(connector.type),
|
|
39670
|
-
inLocal: true,
|
|
39671
|
-
inBackend: false
|
|
39672
|
-
});
|
|
39673
|
-
for (const connector of backend) {
|
|
39674
|
-
if (!isValidIntegration(connector.integrationType)) continue;
|
|
39675
|
-
const existing = merged.get(connector.integrationType);
|
|
39676
|
-
const accountEmail = (connector.accountInfo?.email || connector.accountInfo?.name) ?? void 0;
|
|
39677
|
-
if (existing) {
|
|
39678
|
-
existing.inBackend = true;
|
|
39679
|
-
existing.accountEmail = accountEmail;
|
|
39680
|
-
} else merged.set(connector.integrationType, {
|
|
39681
|
-
type: connector.integrationType,
|
|
39682
|
-
displayName: getIntegrationDisplayName(connector.integrationType),
|
|
39683
|
-
inLocal: false,
|
|
39684
|
-
inBackend: true,
|
|
39685
|
-
accountEmail
|
|
39686
|
-
});
|
|
39687
|
-
}
|
|
39688
|
-
return Array.from(merged.values());
|
|
39689
|
-
}
|
|
39690
|
-
function validateConnectorType(type, merged) {
|
|
39691
|
-
if (!isValidIntegration(type)) throw new Error(`Invalid connector type: ${type}`);
|
|
39692
|
-
const connector = merged.find((c$1) => c$1.type === type);
|
|
39693
|
-
if (!connector) throw new Error(`No ${getIntegrationDisplayName(type)} connector found`);
|
|
39694
|
-
return {
|
|
39695
|
-
type,
|
|
39696
|
-
connector
|
|
39697
|
-
};
|
|
39698
|
-
}
|
|
39699
|
-
async function promptForConnectorToRemove(connectors) {
|
|
39700
|
-
const selected = await ve({
|
|
39701
|
-
message: "Select a connector to remove:",
|
|
39702
|
-
options: connectors.map((c$1) => {
|
|
39703
|
-
let label = c$1.displayName;
|
|
39704
|
-
if (c$1.accountEmail) label += ` (${c$1.accountEmail})`;
|
|
39705
|
-
else if (c$1.inLocal && !c$1.inBackend) label += " (not connected)";
|
|
39706
|
-
return {
|
|
39707
|
-
value: c$1.type,
|
|
39708
|
-
label
|
|
39709
|
-
};
|
|
39710
|
-
})
|
|
39711
|
-
});
|
|
39712
|
-
if (pD(selected)) {
|
|
39713
|
-
xe("Operation cancelled.");
|
|
39714
|
-
process.exit(0);
|
|
39715
|
-
}
|
|
39716
|
-
return {
|
|
39717
|
-
type: selected,
|
|
39718
|
-
connector: connectors.find((c$1) => c$1.type === selected)
|
|
39719
|
-
};
|
|
39720
|
-
}
|
|
39721
|
-
async function removeConnectorCommand(integrationType, options = {}) {
|
|
39722
|
-
const isHardDelete = options.hard === true;
|
|
39723
|
-
const { local: localConnectors, backend: backendConnectors } = await runTask("Fetching connectors...", fetchConnectorState, {
|
|
39724
|
-
successMessage: "Connectors loaded",
|
|
39725
|
-
errorMessage: "Failed to fetch connectors"
|
|
39726
|
-
});
|
|
39727
|
-
const merged = mergeConnectorsForRemoval(localConnectors, backendConnectors);
|
|
39728
|
-
if (merged.length === 0) return { outroMessage: "No connectors to remove" };
|
|
39729
|
-
const { type: selectedType, connector: selectedConnector } = integrationType ? validateConnectorType(integrationType, merged) : await promptForConnectorToRemove(merged);
|
|
39730
|
-
const displayName = getIntegrationDisplayName(selectedType);
|
|
39731
|
-
const accountInfo = selectedConnector.accountEmail ? ` (${selectedConnector.accountEmail})` : "";
|
|
39732
|
-
if (!options.yes) {
|
|
39733
|
-
const shouldRemove = await ye({
|
|
39734
|
-
message: `${isHardDelete ? "Permanently remove" : "Remove"} ${displayName}${accountInfo}?`,
|
|
39735
|
-
initialValue: false
|
|
39736
|
-
});
|
|
39737
|
-
if (pD(shouldRemove) || !shouldRemove) {
|
|
39738
|
-
xe("Operation cancelled.");
|
|
39739
|
-
process.exit(0);
|
|
39740
|
-
}
|
|
39741
|
-
}
|
|
39742
|
-
await runTask(`Removing ${displayName}...`, async () => {
|
|
39743
|
-
if (selectedConnector.inBackend) if (isHardDelete) await removeConnector(selectedType);
|
|
39744
|
-
else await disconnectConnector(selectedType);
|
|
39745
|
-
await removeLocalConnector(selectedType);
|
|
39746
|
-
}, {
|
|
39747
|
-
successMessage: `${displayName} removed`,
|
|
39748
|
-
errorMessage: `Failed to remove ${displayName}`
|
|
39749
|
-
});
|
|
39750
|
-
return { outroMessage: `Successfully removed ${theme.styles.bold(displayName)}` };
|
|
39751
|
-
}
|
|
39752
|
-
const connectorsRemoveCommand = new Command("remove").argument("[type]", "Integration type to remove (e.g., slack, notion)").option("--hard", "Permanently remove the connector (cannot be undone)").option("-y, --yes", "Skip confirmation prompt").description("Remove an OAuth integration").action(async (type, options) => {
|
|
39753
|
-
await runCommand(() => removeConnectorCommand(type, options), {
|
|
39754
|
-
requireAuth: true,
|
|
39755
|
-
requireAppConfig: true
|
|
39756
|
-
});
|
|
39757
|
-
});
|
|
39758
|
-
|
|
39759
|
-
//#endregion
|
|
39760
|
-
//#region src/cli/commands/connectors/index.ts
|
|
39761
|
-
const connectorsCommand = new Command("connectors").description("Manage OAuth connectors").addCommand(connectorsAddCommand).addCommand(connectorsListCommand).addCommand(connectorsPushCommand).addCommand(connectorsRemoveCommand);
|
|
39762
|
-
|
|
39763
38898
|
//#endregion
|
|
39764
38899
|
//#region package.json
|
|
39765
38900
|
var version = "0.0.17";
|
|
39766
38901
|
|
|
39767
38902
|
//#endregion
|
|
39768
|
-
//#region src/cli/
|
|
38903
|
+
//#region src/cli/index.ts
|
|
39769
38904
|
const program = new Command();
|
|
39770
38905
|
program.name("base44").description("Base44 CLI - Unified interface for managing Base44 applications").version(version);
|
|
39771
38906
|
program.configureHelp({ sortSubcommands: true });
|
|
@@ -39779,7 +38914,7 @@ program.addCommand(linkCommand);
|
|
|
39779
38914
|
program.addCommand(entitiesPushCommand);
|
|
39780
38915
|
program.addCommand(functionsDeployCommand);
|
|
39781
38916
|
program.addCommand(siteDeployCommand);
|
|
39782
|
-
program.
|
|
38917
|
+
program.parse();
|
|
39783
38918
|
|
|
39784
38919
|
//#endregion
|
|
39785
|
-
export {
|
|
38920
|
+
export { };
|