Files
better-auth/packages/cli/src/commands/migrate.ts
T
Fraol LemechaGitHubKinfe123Bereket Engidacubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>Alex YangBereket Engida
2915e4c92d feat: add telemetry (#3822)
* feat: telemetry

Co-authored-by: Kinfe123 <kinfishtech@gmail.com>

* chore: remove changeset

* fix: do not generate project id unless telemetry is enabled

* fix: return `isInsiderContainerCached`

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* chore: remove unused utils file

* fix: properly cache generated project id

* feat: interpret empty env vars as false

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix: use nullish coalescing to set fallback

* fix: should be `isInsideContainerCached`

* fix: unique icons + tooltip for telemetry component

* fix: import child process from node

* fix: remove quotes in description

Co-authored-by: Alex Yang <himself65@outlook.com>

* fix: address reviews

Co-authored-by: Alex Yang <himself65@outlook.com>

* chore: refactor

* refactor

* add tests

* cache pkg json

* add cli tracking

* add migrate

* chore fix xi

* skip tet

* update snapshot

* chore: fix typecheck

* Expand telemetry docs: list collected fields, clarify anonymous redaction via getTelemetryAuthConfig, and document CLI events and audit/opt‑out paths.

* docs

* doc cleanup

* fixes

* remove git first commit message

* update docs

---------

Co-authored-by: Kinfe123 <kinfishtech@gmail.com>
Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Alex Yang <himself65@outlook.com>
Co-authored-by: Bereket Engida <Bekacru@gmail.com>
2025-08-10 01:11:56 -07:00

190 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Command } from "commander";
import * as z from "zod/v4";
import { existsSync } from "fs";
import path from "path";
import yoctoSpinner from "yocto-spinner";
import chalk from "chalk";
import prompts from "prompts";
import { logger, createTelemetry, getTelemetryAuthConfig } from "better-auth";
import { getAdapter, getMigrations } from "better-auth/db";
import { getConfig } from "../utils/get-config";
export async function migrateAction(opts: any) {
const options = z
.object({
cwd: z.string(),
config: z.string().optional(),
y: z.boolean().optional(),
yes: z.boolean().optional(),
})
.parse(opts);
const cwd = path.resolve(options.cwd);
if (!existsSync(cwd)) {
logger.error(`The directory "${cwd}" does not exist.`);
process.exit(1);
}
const config = await getConfig({
cwd,
configPath: options.config,
});
if (!config) {
logger.error(
"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.",
);
return;
}
const db = await getAdapter(config);
if (!db) {
logger.error(
"Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter.",
);
process.exit(1);
}
if (db.id !== "kysely") {
if (db.id === "prisma") {
logger.error(
"The migrate command only works with the built-in Kysely adapter. For Prisma, run `npx @better-auth/cli generate` to create the schema, then use Prisma’s migrate or push to apply it.",
);
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: {
outcome: "unsupported_adapter",
adapter: "prisma",
config: getTelemetryAuthConfig(config),
},
});
} catch {}
process.exit(0);
}
if (db.id === "drizzle") {
logger.error(
"The migrate command only works with the built-in Kysely adapter. For Drizzle, run `npx @better-auth/cli generate` to create the schema, then use Drizzle’s migrate or push to apply it.",
);
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: {
outcome: "unsupported_adapter",
adapter: "drizzle",
config: getTelemetryAuthConfig(config),
},
});
} catch {}
process.exit(0);
}
logger.error("Migrate command isn't supported for this adapter.");
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: {
outcome: "unsupported_adapter",
adapter: db.id,
config: getTelemetryAuthConfig(config),
},
});
} catch {}
process.exit(1);
}
const spinner = yoctoSpinner({ text: "preparing migration..." }).start();
const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
if (!toBeAdded.length && !toBeCreated.length) {
spinner.stop();
logger.info("🚀 No migrations needed.");
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: {
outcome: "no_changes",
config: getTelemetryAuthConfig(config),
},
});
} catch {}
process.exit(0);
}
spinner.stop();
logger.info(`🔑 The migration will affect the following:`);
for (const table of [...toBeCreated, ...toBeAdded]) {
console.log(
"->",
chalk.magenta(Object.keys(table.fields).join(", ")),
chalk.white("fields on"),
chalk.yellow(`${table.table}`),
chalk.white("table."),
);
}
if (options.y) {
console.warn("WARNING: --y is deprecated. Consider -y or --yes");
options.yes = true;
}
let migrate = options.yes;
if (!migrate) {
const response = await prompts({
type: "confirm",
name: "migrate",
message: "Are you sure you want to run these migrations?",
initial: false,
});
migrate = response.migrate;
}
if (!migrate) {
logger.info("Migration cancelled.");
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: { outcome: "aborted", config: getTelemetryAuthConfig(config) },
});
} catch {}
process.exit(0);
}
spinner?.start("migrating...");
await runMigrations();
spinner.stop();
logger.info("🚀 migration was completed successfully!");
try {
const telemetry = await createTelemetry(config);
await telemetry.publish({
type: "cli_migrate",
payload: { outcome: "migrated", config: getTelemetryAuthConfig(config) },
});
} catch {}
process.exit(0);
}
export const migrate = new Command("migrate")
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd(),
)
.option(
"--config <config>",
"the path to the configuration file. defaults to the first configuration file found.",
)
.option(
"-y, --yes",
"automatically accept and run migrations without prompting",
false,
)
.option("--y", "(deprecated) same as --yes", false)
.action(migrateAction);