fix(FN-000): backup routine uses npx runfusion.ai; alias exposes fn/fusion
Backup automation now emits `npx runfusion.ai backup --create` so scheduled backups work for users on the zero-install path where `fn` is not on PATH. `runfusion.ai` also exposes `fn` and `fusion` bins, so `npm i -g runfusion.ai` puts them on PATH (npm does not link dep bins globally). Alias only defaults to the dashboard when invoked as `runfusion.ai` / `runfusion`; `fn` / `fusion` forward args verbatim so bare `fn` still prints help. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,25 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// runfusion.ai — tiny alias for @runfusion/fusion.
|
// runfusion.ai — tiny alias for @runfusion/fusion.
|
||||||
// With no args, launches the Fusion dashboard. With args, forwards to the
|
//
|
||||||
// underlying `fn` CLI so `npx runfusion.ai task list` etc. Just Work.
|
// Exposes four bins: runfusion.ai, runfusion, fn, fusion. Installing this
|
||||||
|
// package globally (`npm i -g runfusion.ai`) therefore also puts `fn` and
|
||||||
|
// `fusion` on PATH, even though `@runfusion/fusion` is only a dependency
|
||||||
|
// (npm does not link dep bins globally).
|
||||||
|
//
|
||||||
|
// When invoked as `runfusion.ai` / `runfusion` with no args, defaults to
|
||||||
|
// launching the dashboard. When invoked as `fn` / `fusion`, forwards args
|
||||||
|
// verbatim so behavior matches the main CLI exactly (e.g. bare `fn` prints
|
||||||
|
// help, not `fn dashboard`).
|
||||||
|
|
||||||
|
import { basename } from "node:path";
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
const invokedAs = basename(process.argv[1] || "").replace(/\.(js|cjs|mjs|exe)$/i, "");
|
||||||
|
const isAliasInvocation = invokedAs === "runfusion.ai" || invokedAs === "runfusion";
|
||||||
|
|
||||||
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
if (isAliasInvocation && args.length === 0) {
|
||||||
// No subcommand → default to dashboard. Keep --help passthrough explicit
|
// No subcommand → default to dashboard.
|
||||||
// so users can discover the full CLI surface via `npx runfusion.ai --help`.
|
process.argv = [process.argv[0], process.argv[1], "dashboard"];
|
||||||
if (args.length === 0) {
|
|
||||||
process.argv = [process.argv[0], process.argv[1], "dashboard"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await import("@runfusion/fusion/dist/bin.js");
|
await import("@runfusion/fusion/dist/bin.js");
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"runfusion.ai": "index.js",
|
"runfusion.ai": "index.js",
|
||||||
"runfusion": "index.js"
|
"runfusion": "index.js",
|
||||||
|
"fn": "index.js",
|
||||||
|
"fusion": "index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ describe("syncBackupRoutine", () => {
|
|||||||
expect(routine).toBeDefined();
|
expect(routine).toBeDefined();
|
||||||
expect(routine?.name).toBe("Database Backup");
|
expect(routine?.name).toBe("Database Backup");
|
||||||
expect(routine?.trigger).toEqual({ type: "cron", cronExpression: "0 3 * * *" });
|
expect(routine?.trigger).toEqual({ type: "cron", cronExpression: "0 3 * * *" });
|
||||||
expect(routine?.command).toBe("fn backup --create");
|
expect(routine?.command).toBe("npx runfusion.ai backup --create");
|
||||||
expect(routine?.agentId).toBe("");
|
expect(routine?.agentId).toBe("");
|
||||||
expect(routine?.scope).toBe("project");
|
expect(routine?.scope).toBe("project");
|
||||||
});
|
});
|
||||||
@@ -494,7 +494,7 @@ describe("syncBackupRoutine", () => {
|
|||||||
|
|
||||||
expect(routines).toHaveLength(1);
|
expect(routines).toHaveLength(1);
|
||||||
expect(updated?.trigger).toEqual({ type: "cron", cronExpression: "30 4 * * *" });
|
expect(updated?.trigger).toEqual({ type: "cron", cronExpression: "30 4 * * *" });
|
||||||
expect(updated?.command).toBe("fn backup --create");
|
expect(updated?.command).toBe("npx runfusion.ai backup --create");
|
||||||
expect(updated?.enabled).toBe(true);
|
expect(updated?.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -420,11 +420,11 @@ export async function syncBackupAutomation(
|
|||||||
throw new Error(`Invalid backup schedule: ${schedule}`);
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the backup command
|
// Build the backup command.
|
||||||
// The CLI command will be: fn backup --auto
|
// Uses `npx runfusion.ai` so backups work even when only the zero-install
|
||||||
// The --auto flag indicates this is an automated run (can add special handling if needed)
|
// path (`npx runfusion.ai`) has been used and `fn` is not on PATH.
|
||||||
const command = "fn backup --create";
|
const command = "npx runfusion.ai backup --create";
|
||||||
|
|
||||||
if (existingSchedule) {
|
if (existingSchedule) {
|
||||||
// Update existing schedule
|
// Update existing schedule
|
||||||
return await automationStore.updateSchedule(existingSchedule.id, {
|
return await automationStore.updateSchedule(existingSchedule.id, {
|
||||||
@@ -474,7 +474,7 @@ export async function syncBackupRoutine(
|
|||||||
throw new Error(`Invalid backup schedule: ${schedule}`);
|
throw new Error(`Invalid backup schedule: ${schedule}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = "fn backup --create";
|
const command = "npx runfusion.ai backup --create";
|
||||||
const input = {
|
const input = {
|
||||||
name: BACKUP_SCHEDULE_NAME,
|
name: BACKUP_SCHEDULE_NAME,
|
||||||
description: "Automatic database backup based on project settings",
|
description: "Automatic database backup based on project settings",
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ export function RoutineEditor({ routine, onSubmit, onCancel, scope: formScope, p
|
|||||||
{simpleActionType === "command" ? (
|
{simpleActionType === "command" ? (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="routine-command">Command</label>
|
<label htmlFor="routine-command">Command</label>
|
||||||
<input id="routine-command" type="text" placeholder="e.g. fn backup --create" value={command} onChange={(e) => setCommand(e.target.value)} aria-invalid={!!errors.command} aria-describedby={errors.command ? commandErrorId : undefined} />
|
<input id="routine-command" type="text" placeholder="e.g. npx runfusion.ai backup --create" value={command} onChange={(e) => setCommand(e.target.value)} aria-invalid={!!errors.command} aria-describedby={errors.command ? commandErrorId : undefined} />
|
||||||
{errors.command ? <small id={commandErrorId} className="field-error">{errors.command}</small> : <small>Shell command to execute.</small>}
|
{errors.command ? <small id={commandErrorId} className="field-error">{errors.command}</small> : <small>Shell command to execute.</small>}
|
||||||
</div>
|
</div>
|
||||||
) : simpleActionType === "ai-prompt" ? (
|
) : simpleActionType === "ai-prompt" ? (
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ describe("ScheduledTasksModal", () => {
|
|||||||
|
|
||||||
it("shows routine cards and the new automation button when routines exist", async () => {
|
it("shows routine cards and the new automation button when routines exist", async () => {
|
||||||
mockFetchRoutines.mockResolvedValue([
|
mockFetchRoutines.mockResolvedValue([
|
||||||
makeRoutine({ name: "Database Backup", command: "fn backup --create" }),
|
makeRoutine({ name: "Database Backup", command: "npx runfusion.ai backup --create" }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
render(<ScheduledTasksModal onClose={onClose} addToast={addToast} />);
|
render(<ScheduledTasksModal onClose={onClose} addToast={addToast} />);
|
||||||
@@ -126,7 +126,7 @@ describe("ScheduledTasksModal", () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText("Database Backup")).toBeDefined();
|
expect(screen.getByText("Database Backup")).toBeDefined();
|
||||||
});
|
});
|
||||||
expect(screen.getByText("fn backup --create")).toBeDefined();
|
expect(screen.getByText("npx runfusion.ai backup --create")).toBeDefined();
|
||||||
expect(screen.getByText("New Automation")).toBeDefined();
|
expect(screen.getByText("New Automation")).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user