Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/cli/src/cli/commands/automations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Command } from "commander";
import { getAutomationsStatusCommand } from "./status.js";

export function getAutomationsCommand(): Command {
return new Command("automations")
.description("Manage automations")
.addCommand(getAutomationsStatusCommand());
}
61 changes: 61 additions & 0 deletions packages/cli/src/cli/commands/automations/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { Command } from "commander";
import type { CLIContext, RunCommandResult } from "@/cli/types.js";
import { Base44Command, runTask, theme } from "@/cli/utils/index.js";
import { listDeployedFunctions } from "@/core/resources/function/api.js";

function formatAutomationType(automation: {
type: string;
schedule_mode?: string;
schedule_type?: string;
}): string {
if (automation.type === "entity") return "entity";
if (automation.type === "scheduled") {
if (automation.schedule_mode === "one-time") return "scheduled (one-time)";
if (automation.schedule_type === "cron") return "scheduled (cron)";
return "scheduled (simple)";
}
return automation.type;
}

async function statusAction({ log }: CLIContext): Promise<RunCommandResult> {
const { functions } = await runTask(
"Fetching automations...",
async () => listDeployedFunctions(),
{ errorMessage: "Failed to fetch automations" },
);

let totalAutomations = 0;
let functionsWithAutomations = 0;

for (const fn of functions) {
if (fn.automations.length === 0) continue;
functionsWithAutomations++;

for (const automation of fn.automations) {
totalAutomations++;
const active = automation.is_active;
const statusLabel = active
? theme.styles.bold("active")
: theme.styles.dim("disabled");
const typeLabel = formatAutomationType(automation);

log.message(
` ${fn.name} / ${automation.name} ${theme.styles.dim(typeLabel)} ${statusLabel}`,
);
}
}

if (totalAutomations === 0) {
return { outroMessage: "No automations found" };
}

return {
outroMessage: `${totalAutomations} automation${totalAutomations !== 1 ? "s" : ""} across ${functionsWithAutomations} function${functionsWithAutomations !== 1 ? "s" : ""}`,
};
}

export function getAutomationsStatusCommand(): Command {
return new Base44Command("status")
.description("Show status of all automations")
.action(statusAction);
}
26 changes: 26 additions & 0 deletions packages/cli/src/cli/commands/entities/count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Command } from "commander";
import type { CLIContext, RunCommandResult } from "@/cli/types.js";
import { Base44Command, runTask } from "@/cli/utils/index.js";
import { countEntityRecords } from "@/core/resources/entity/data-api.js";

async function countAction(
_ctx: CLIContext,
entityName: string,
): Promise<RunCommandResult> {
const count = await runTask(
`Counting ${entityName} records...`,
async () => countEntityRecords(entityName),
{ errorMessage: `Failed to count ${entityName} records` },
);

return {
outroMessage: `${entityName} has ${count} record${count !== 1 ? "s" : ""}`,
};
}

export function getEntitiesCountCommand(): Command {
return new Base44Command("count")
.description("Count total records for an entity")
.argument("<entityName>", "Name of the entity")
.action(countAction);
}
31 changes: 31 additions & 0 deletions packages/cli/src/cli/commands/entities/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Command } from "commander";
import type { CLIContext, RunCommandResult } from "@/cli/types.js";
import { Base44Command, runTask } from "@/cli/utils/index.js";
import { getEntityRecord } from "@/core/resources/entity/data-api.js";

async function getAction(
_ctx: CLIContext,
entityName: string,
id: string,
): Promise<RunCommandResult> {
const record = await runTask(
`Fetching ${entityName} record...`,
async () => getEntityRecord(entityName, id),
{ errorMessage: `Failed to fetch ${entityName} record "${id}"` },
);

const formatted = JSON.stringify(record, null, 2);

return {
outroMessage: `Fetched ${entityName} record ${id}`,
stdout: `${formatted}\n`,
};
}

export function getEntitiesGetCommand(): Command {
return new Base44Command("get")
.description("Get a single entity record by ID")
.argument("<entityName>", "Name of the entity")
.argument("<id>", "Record ID")
.action(getAction);
}
14 changes: 14 additions & 0 deletions packages/cli/src/cli/commands/entities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Command } from "commander";
import { getEntitiesCountCommand } from "./count.js";
import { getEntitiesGetCommand } from "./get.js";
import { getEntitiesListCommand } from "./list.js";
import { getEntitiesPushCommand } from "./push.js";

export function getEntitiesCommand(): Command {
return new Command("entities")
.description("Manage project entities")
.addCommand(getEntitiesPushCommand())
.addCommand(getEntitiesListCommand())
.addCommand(getEntitiesGetCommand())
.addCommand(getEntitiesCountCommand());
}
133 changes: 133 additions & 0 deletions packages/cli/src/cli/commands/entities/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { Command } from "commander";
import type { CLIContext, RunCommandResult } from "@/cli/types.js";
import { Base44Command, runTask, theme } from "@/cli/utils/index.js";
import { InvalidInputError } from "@/core/errors.js";
import {
type EntityRecord,
type ListEntityRecordsOptions,
listEntityRecords,
} from "@/core/resources/entity/data-api.js";

interface ListOptions {
limit?: string;
sort?: string;
skip?: string;
}

function formatRecordRow(record: EntityRecord, fields: string[]): string {
const values = fields.map((field) => {
const value = record[field];
if (value === undefined || value === null) return "-";
if (typeof value === "object") return JSON.stringify(value);
return String(value);
});
return values.join("\t");
}

function pickDisplayFields(records: EntityRecord[]): string[] {
const systemFields = ["id", "created_date"];
if (records.length === 0) return systemFields;

const skipFields = new Set([
"id",
"created_date",
"updated_date",
"created_by",
"created_by_id",
"app_id",
"entity_name",
"is_deleted",
"deleted_date",
"environment",
"_id",
]);

const dataFields: string[] = [];
for (const key of Object.keys(records[0])) {
if (!skipFields.has(key) && dataFields.length < 4) {
dataFields.push(key);
}
}

return [...systemFields, ...dataFields];
}

function validateLimit(limit: string | undefined): void {
if (limit === undefined) return;
const n = Number.parseInt(limit, 10);
if (Number.isNaN(n) || n < 1) {
throw new InvalidInputError(
`Invalid limit: "${limit}". Must be a positive integer.`,
);
}
}

function validateSkip(skip: string | undefined): void {
if (skip === undefined) return;
const n = Number.parseInt(skip, 10);
if (Number.isNaN(n) || n < 0) {
throw new InvalidInputError(
`Invalid skip: "${skip}". Must be a non-negative integer.`,
);
}
}

async function listAction(
{ log }: CLIContext,
entityName: string,
options: ListOptions,
): Promise<RunCommandResult> {
validateLimit(options.limit);
validateSkip(options.skip);

const apiOptions: ListEntityRecordsOptions = {};

if (options.limit) {
apiOptions.limit = Number.parseInt(options.limit, 10);
} else {
apiOptions.limit = 10;
}

if (options.skip) {
apiOptions.skip = Number.parseInt(options.skip, 10);
}

if (options.sort) {
apiOptions.sort = options.sort;
}

const records = await runTask(
`Fetching ${entityName} records...`,
async () => listEntityRecords(entityName, apiOptions),
{ errorMessage: `Failed to fetch ${entityName} records` },
);

if (records.length === 0) {
return { outroMessage: `No records found for ${entityName}` };
}

const fields = pickDisplayFields(records);
const header = fields.join("\t");

log.message(theme.styles.dim(header));
for (const record of records) {
log.message(` ${formatRecordRow(record, fields)}`);
}

return {
outroMessage: `Showing ${records.length} ${entityName} record${records.length !== 1 ? "s" : ""}`,
};
}

export function getEntitiesListCommand(): Command {
return new Base44Command("list")
.description("List entity records")
.argument("<entityName>", "Name of the entity")
.option(
"-n, --limit <n>",
"Maximum number of records to return (default: 10)",
)
.option("--sort <field>", "Field to sort by")
.option("--skip <n>", "Number of records to skip")
.action(listAction);
}
12 changes: 4 additions & 8 deletions packages/cli/src/cli/commands/entities/push.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Command } from "commander";
import type { Command } from "commander";
import type { CLIContext, RunCommandResult } from "@/cli/types.js";
import { Base44Command, runTask } from "@/cli/utils/index.js";
import { readProjectConfig } from "@/core/index.js";
Expand Down Expand Up @@ -42,11 +42,7 @@ async function pushEntitiesAction({
}

export function getEntitiesPushCommand(): Command {
return new Command("entities")
.description("Manage project entities")
.addCommand(
new Base44Command("push")
.description("Push local entities to Base44")
.action(pushEntitiesAction),
);
return new Base44Command("push")
.description("Push local entities to Base44")
.action(pushEntitiesAction);
}
8 changes: 6 additions & 2 deletions packages/cli/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { getAuthCommand } from "@/cli/commands/auth/index.js";
import { getLoginCommand } from "@/cli/commands/auth/login.js";
import { getLogoutCommand } from "@/cli/commands/auth/logout.js";
import { getWhoamiCommand } from "@/cli/commands/auth/whoami.js";
import { getAutomationsCommand } from "@/cli/commands/automations/index.js";
import { getConnectorsCommand } from "@/cli/commands/connectors/index.js";
import { getDashboardCommand } from "@/cli/commands/dashboard/index.js";
import { getEntitiesPushCommand } from "@/cli/commands/entities/push.js";
import { getEntitiesCommand } from "@/cli/commands/entities/index.js";
import { getFunctionsCommand } from "@/cli/commands/functions/index.js";
import { getCreateCommand } from "@/cli/commands/project/create.js";
import { getDeployCommand } from "@/cli/commands/project/deploy.js";
Expand Down Expand Up @@ -56,7 +57,7 @@ export function createProgram(context: CLIContext): Command {
program.addCommand(getEjectCommand());

// Register entities commands
program.addCommand(getEntitiesPushCommand());
program.addCommand(getEntitiesCommand());

// Register agents commands
program.addCommand(getAgentsCommand());
Expand All @@ -79,6 +80,9 @@ export function createProgram(context: CLIContext): Command {
// Register types command
program.addCommand(getTypesCommand());

// Register automations commands
program.addCommand(getAutomationsCommand());

// Register exec command
program.addCommand(getExecCommand());

Expand Down
Loading
Loading