Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/fruity-ears-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Adds `wrangler check do-migration` to check if there are any pending DO migrations for a worker.
2 changes: 1 addition & 1 deletion packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import type { FormData } from "undici";
import type { Mock } from "vitest";

vi.mock("command-exists");
vi.mock("../check/commands", async (importOriginal) => {
vi.mock("../check/startup", async (importOriginal) => {
return {
...(await importOriginal()),
analyseBundle() {
Expand Down
242 changes: 242 additions & 0 deletions packages/wrangler/src/__tests__/do-migration-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockConsoleMethods } from "./helpers/mock-console";
import { useMockIsTTY } from "./helpers/mock-istty";
import { mockLegacyScriptData } from "./helpers/mock-legacy-script";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import { writeWranglerConfig } from "./helpers/write-wrangler-config";

describe("wrangler check do-migrations", () => {
mockAccountId();
mockApiToken();
runInTempDir();
const { setIsTTY } = useMockIsTTY();
const std = mockConsoleMethods();

beforeEach(() => {
setIsTTY(true);
});

test("it can list a pending DO migration", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v1" }],
});

await runWrangler("check do-migrations");
expect(std.out).toMatchInlineSnapshot(`
"
⛅️ wrangler x.x.x
──────────────────
┌─┬─┬─┬─┐
│ New Classes │ New SQLite Classes │ Renamed Classes │ Deleted Classes │
├─┼─┼─┼─┤
│ SomeOtherClass │ │ │ │
└─┴─┴─┴─┘"
`);
});

test("it can list multiple pending DO migrations", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMESQLITENAME", class_name: "SomeSQLiteClass" },
{ name: "THATOTHERNAME", class_name: "ThatOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
{
tag: "v3",
new_classes: ["ThisOtherClass"],
new_sqlite_classes: ["SomeSQLiteClass"],
},
{
tag: "v4",
deleted_classes: ["SomeClass", "SomeOtherClass"],
renamed_classes: [{ from: "ThisOtherClass", to: "ThatOtherClass" }],
},
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v2" }],
});

await runWrangler("check do-migrations");
expect(std.out).toMatchInlineSnapshot(`
"
⛅️ wrangler x.x.x
──────────────────
┌─┬─┬─┬─┐
│ New Classes │ New SQLite Classes │ Renamed Classes │ Deleted Classes │
├─┼─┼─┼─┤
│ ThisOtherClass │ SomeSQLiteClass │ │ │
├─┼─┼─┼─┤
│ │ │ ThisOtherClass to ThatOtherClass │ SomeClass │
│ │ │ │ SomeOtherClass │
└─┴─┴─┴─┘"
`);
});

test("it can no pending DO migrations", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMESQLITENAME", class_name: "SomeSQLiteClass" },
{ name: "THATOTHERNAME", class_name: "ThatOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
{
tag: "v3",
new_classes: ["ThisOtherClass"],
new_sqlite_classes: ["SomeSQLiteClass"],
},
{
tag: "v4",
deleted_classes: ["SomeClass", "SomeOtherClass"],
renamed_classes: [{ from: "ThisOtherClass", to: "ThatOtherClass" }],
},
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v4" }],
});

await runWrangler("check do-migrations");
expect(std.out).toMatchInlineSnapshot(`
"
⛅️ wrangler x.x.x
──────────────────
No DO migrations pending for this worker"
`);
});

test("it can list a pending DO migration (json)", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v1" }],
});

await runWrangler("check do-migrations --json");
expect(std.out).toMatchInlineSnapshot(`
"[
{
\\"new_classes\\": [
\\"SomeOtherClass\\"
]
}
]"
`);
});

test("it can list multiple pending DO migrations (json)", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMESQLITENAME", class_name: "SomeSQLiteClass" },
{ name: "THATOTHERNAME", class_name: "ThatOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
{
tag: "v3",
new_classes: ["ThisOtherClass"],
new_sqlite_classes: ["SomeSQLiteClass"],
},
{
tag: "v4",
deleted_classes: ["SomeClass", "SomeOtherClass"],
renamed_classes: [{ from: "ThisOtherClass", to: "ThatOtherClass" }],
},
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v2" }],
});

await runWrangler("check do-migrations --json");
expect(std.out).toMatchInlineSnapshot(`
"[
{
\\"new_classes\\": [
\\"ThisOtherClass\\"
],
\\"new_sqlite_classes\\": [
\\"SomeSQLiteClass\\"
]
},
{
\\"deleted_classes\\": [
\\"SomeClass\\",
\\"SomeOtherClass\\"
],
\\"renamed_classes\\": [
{
\\"from\\": \\"ThisOtherClass\\",
\\"to\\": \\"ThatOtherClass\\"
}
]
}
]"
`);
});

test("it can no pending DO migrations (json)", async () => {
writeWranglerConfig({
durable_objects: {
bindings: [
{ name: "SOMESQLITENAME", class_name: "SomeSQLiteClass" },
{ name: "THATOTHERNAME", class_name: "ThatOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
{
tag: "v3",
new_classes: ["ThisOtherClass"],
new_sqlite_classes: ["SomeSQLiteClass"],
},
{
tag: "v4",
deleted_classes: ["SomeClass", "SomeOtherClass"],
renamed_classes: [{ from: "ThisOtherClass", to: "ThatOtherClass" }],
},
],
});
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v4" }],
});

await runWrangler("check do-migrations --json");
expect(std.out).toMatchInlineSnapshot(`"[]"`);
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { FormData } from "undici";
import { describe, expect, it, vi } from "vitest";
import * as checkCommands from "../check/commands";
import * as checkStartupCommand from "../check/startup";
import { logger } from "../logger";
import { ParseError } from "../parse";
import { helpIfErrorIsSizeOrScriptStartup } from "../utils/friendly-validator-errors";
import { mockConsoleMethods } from "./helpers/mock-console";
import { normalizeString } from "./helpers/normalize";
import { runInTempDir } from "./helpers/run-in-tmp";

vi.mock("../check/commands", () => ({ analyseBundle: vi.fn() }));
const mockAnalyseBundle = vi.mocked(checkCommands.analyseBundle);
vi.mock("../check/startup", () => ({ analyseBundle: vi.fn() }));
const mockAnalyseBundle = vi.mocked(checkStartupCommand.analyseBundle);

describe("helpIfErrorIsSizeOrScriptStartup", () => {
const std = mockConsoleMethods();
Expand Down
10 changes: 10 additions & 0 deletions packages/wrangler/src/check/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createNamespace } from "../core/create-command";

export const checkNamespace = createNamespace({
metadata: {
description: "☑︎ Run checks on your Worker",
owner: "Workers: Authoring and Testing",
status: "alpha",
hidden: true,
},
});
90 changes: 90 additions & 0 deletions packages/wrangler/src/check/migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { createCommand, createNamespace } from "../core/create-command";
import { getMigrationsToUpload } from "../durable";
import { UserError } from "../errors";
import { isNonInteractiveOrCI } from "../is-interactive";
import { logger } from "../logger";
import { requireAuth } from "../user";
import { getScriptName } from "../utils/getScriptName";

export const checkNamespace = createNamespace({
metadata: {
description: "🔍 Check pending migrations for your Worker",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: "🔍 Check pending migrations for your Worker",
description: "🔍 Check pending migrations for your Durable Object",

owner: "Workers: Deploy and Config",
status: "alpha",
hidden: true,
},
});

export const checkDOMigrationCommand = createCommand({
args: {
name: {
describe: "Name of the Worker",
type: "string",
requiresArg: true,
},
json: {
describe: "Return output as a clean JSON object",
type: "boolean",
default: false,
},
},
behaviour: {
printBanner: (args) => !args.json,
},
metadata: {
description: "🔍 Check pending migrations for your Worker",
owner: "Workers: Authoring and Testing",
status: "alpha",
},
handler: async function doMigrationHandler(args, { config }) {
const accountId = await requireAuth(config);
const name = getScriptName(args, config);

if (!name) {
throw new UserError(
'You need to provide a name of your worker. Either pass it as a cli arg with `--name <name>` or in your config file as `name = "<name>"`',
{ telemetryMessage: true }
);
}

const migrations = await getMigrationsToUpload(name, {
accountId,
config,
// deprecated fields
useServiceEnvironments: undefined,
env: undefined,
dispatchNamespace: undefined,
});

if (args.json || isNonInteractiveOrCI()) {
logger.json(migrations?.steps ?? []);
return;
}

if (!migrations || migrations.steps.length === 0) {
logger.log("No DO migrations pending for this worker");
} else {
logger.table(
migrations.steps.map((migration) => {
const new_classes = migration.new_classes?.join("\n") ?? "";
const new_sqlite_classes =
migration.new_sqlite_classes?.join("\n") ?? "";
const renamed_classes =
migration.renamed_classes
?.map((renamed_class) => {
return `${renamed_class.from} to ${renamed_class.to}`;
})
.join("\n") ?? "";
const deleted_classes = migration.deleted_classes?.join("\n") ?? "";

return {
"New Classes": new_classes,
"New SQLite Classes": new_sqlite_classes,
"Renamed Classes": renamed_classes,
"Deleted Classes": deleted_classes,
};
})
);
}
},
});
Loading
Loading