@aurios/mizzling 1.1.0 → 1.1.2
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/.turbo/turbo-build.log +15 -0
- package/LICENSE +21 -0
- package/dist/chunk-DKDRM5WU.js +2 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +1 -1
- package/package.json +38 -19
- package/src/cli.ts +26 -29
- package/src/commands/drop.ts +51 -51
- package/src/commands/generate.ts +88 -83
- package/src/commands/init.ts +1 -1
- package/src/commands/list.ts +39 -37
- package/src/commands/push.ts +54 -54
- package/src/config.ts +14 -14
- package/src/discovery.ts +33 -25
- package/test/config-expansion.test.ts +164 -0
- package/test/config.test.ts +72 -0
- package/test/define-config-integration.test.ts +49 -0
- package/test/discovery.test.ts +117 -0
- package/test/drop.test.ts +145 -0
- package/test/e2e-env.test.ts +87 -0
- package/test/e2e.test.ts +135 -0
- package/test/generate.test.ts +116 -0
- package/test/init-command.test.ts +78 -0
- package/test/init.test.ts +22 -0
- package/test/interactive.test.ts +96 -0
- package/test/list.test.ts +58 -0
- package/test/mizzling-exports.test.ts +18 -0
- package/test/push.test.ts +88 -0
- package/tsconfig.json +33 -11
- package/tsup.config.ts +5 -5
- package/vitest.config.ts +8 -0
- package/CHANGELOG.md +0 -45
- package/dist/chunk-SNI5EXF5.js +0 -2
package/test/e2e.test.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { expect, test, describe, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { mkdirSync, rmSync, existsSync, readdirSync, writeFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { DynamoDBClient, DeleteTableCommand } from "@aws-sdk/client-dynamodb";
|
|
7
|
+
|
|
8
|
+
const TEMP_DIR = join(tmpdir(), "mizzle-e2e-test-" + Date.now());
|
|
9
|
+
const MIGRATIONS_DIR = join(TEMP_DIR, "migrations");
|
|
10
|
+
const SCHEMA_FILE = join(TEMP_DIR, "schema.ts");
|
|
11
|
+
const CONFIG_FILE = join(TEMP_DIR, "mizzle.config.ts");
|
|
12
|
+
|
|
13
|
+
function runCommand(
|
|
14
|
+
args: string[],
|
|
15
|
+
env: Record<string, string>,
|
|
16
|
+
): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const proc = spawn("bun", args, {
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
env: { ...process.env, ...env },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let stdout = "";
|
|
24
|
+
let stderr = "";
|
|
25
|
+
|
|
26
|
+
proc.stdout.on("data", (data) => (stdout += data.toString()));
|
|
27
|
+
proc.stderr.on("data", (data) => (stderr += data.toString()));
|
|
28
|
+
|
|
29
|
+
proc.on("close", (code) => {
|
|
30
|
+
resolve({ code: code || 0, stdout, stderr });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("CLI End-to-End Migration Lifecycle", () => {
|
|
36
|
+
const projectRoot = process.cwd().includes("packages/mizzling")
|
|
37
|
+
? join(process.cwd(), "../..")
|
|
38
|
+
: process.cwd();
|
|
39
|
+
const mizzleTablePath = join(projectRoot, "packages/mizzle/src/core/table");
|
|
40
|
+
const mizzleColumnsPath = join(projectRoot, "packages/mizzle/src/columns/index");
|
|
41
|
+
const mizzleStrategiesPath = join(projectRoot, "packages/mizzle/src/core/strategies");
|
|
42
|
+
const mizzlingConfigPath = join(projectRoot, "packages/mizzling/src/config");
|
|
43
|
+
const mizzlingCliPath = join(projectRoot, "packages/mizzling/src/cli.ts");
|
|
44
|
+
|
|
45
|
+
beforeAll(() => {
|
|
46
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
47
|
+
|
|
48
|
+
// Create a simple schema
|
|
49
|
+
writeFileSync(
|
|
50
|
+
SCHEMA_FILE,
|
|
51
|
+
`
|
|
52
|
+
import { dynamoTable, dynamoEntity } from "${mizzleTablePath}";
|
|
53
|
+
import { string } from "${mizzleColumnsPath}";
|
|
54
|
+
import { staticKey } from "${mizzleStrategiesPath}";
|
|
55
|
+
|
|
56
|
+
export const testTable = dynamoTable("e2e_test_table", {
|
|
57
|
+
pk: string("pk"),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const testEntity = dynamoEntity(testTable, "Test", { name: string() }, (cols) => ({
|
|
61
|
+
pk: staticKey("FIXED"),
|
|
62
|
+
}));
|
|
63
|
+
`.trim(),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Create config
|
|
67
|
+
writeFileSync(
|
|
68
|
+
CONFIG_FILE,
|
|
69
|
+
`
|
|
70
|
+
import { defineConfig } from "${mizzlingConfigPath}";
|
|
71
|
+
export default defineConfig({
|
|
72
|
+
schema: "${SCHEMA_FILE}",
|
|
73
|
+
out: "${MIGRATIONS_DIR}",
|
|
74
|
+
region: "us-east-1",
|
|
75
|
+
endpoint: "http://localhost:8000"
|
|
76
|
+
});
|
|
77
|
+
`.trim(),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("Full lifecycle: generate -> push -> list -> drop", async () => {
|
|
86
|
+
// 1. Generate
|
|
87
|
+
const {
|
|
88
|
+
code: generateExit,
|
|
89
|
+
stdout: genStdout,
|
|
90
|
+
stderr: genStderr,
|
|
91
|
+
} = await runCommand([mizzlingCliPath, "generate", "--name", "initial"], {
|
|
92
|
+
MIZZLE_CONFIG: CONFIG_FILE,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (generateExit !== 0 || !existsSync(MIGRATIONS_DIR)) {
|
|
96
|
+
console.error("Generate failed or migrations dir missing!");
|
|
97
|
+
console.error("STDOUT:", genStdout);
|
|
98
|
+
console.error("STDERR:", genStderr);
|
|
99
|
+
}
|
|
100
|
+
expect(generateExit).toBe(0);
|
|
101
|
+
expect(existsSync(MIGRATIONS_DIR)).toBe(true);
|
|
102
|
+
expect(readdirSync(MIGRATIONS_DIR).some((f) => f.endsWith("_initial.ts"))).toBe(true);
|
|
103
|
+
|
|
104
|
+
// 2. Push
|
|
105
|
+
const {
|
|
106
|
+
code: pushExit,
|
|
107
|
+
stdout: pushStdout,
|
|
108
|
+
stderr: pushStderr,
|
|
109
|
+
} = await runCommand([mizzlingCliPath, "push", "--yes"], {
|
|
110
|
+
MIZZLE_CONFIG: CONFIG_FILE,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (pushExit !== 0) {
|
|
114
|
+
console.error("Push failed!");
|
|
115
|
+
console.error("STDOUT:", pushStdout);
|
|
116
|
+
console.error("STDERR:", pushStderr);
|
|
117
|
+
}
|
|
118
|
+
expect(pushExit).toBe(0);
|
|
119
|
+
|
|
120
|
+
// 3. List
|
|
121
|
+
const { stdout: listOutput } = await runCommand([mizzlingCliPath, "list"], {
|
|
122
|
+
MIZZLE_CONFIG: CONFIG_FILE,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(listOutput).toContain("e2e_test_table");
|
|
126
|
+
|
|
127
|
+
// 4. Cleanup: Drop the table manually to leave clean state
|
|
128
|
+
const client = new DynamoDBClient({
|
|
129
|
+
region: "us-east-1",
|
|
130
|
+
endpoint: "http://localhost:8000",
|
|
131
|
+
credentials: { accessKeyId: "local", secretAccessKey: "local" },
|
|
132
|
+
});
|
|
133
|
+
await client.send(new DeleteTableCommand({ TableName: "e2e_test_table" }));
|
|
134
|
+
}, 20000); // Higher timeout for spawning
|
|
135
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { generateCommand } from "../src/commands/generate";
|
|
3
|
+
import { PhysicalTable } from "@aurios/mizzle/table";
|
|
4
|
+
import { TABLE_SYMBOLS } from "@repo/shared";
|
|
5
|
+
import { mkdirSync, rmSync, existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { tmpdir } from "os";
|
|
8
|
+
|
|
9
|
+
// Mock Clack
|
|
10
|
+
vi.mock("@clack/prompts", () => ({
|
|
11
|
+
intro: vi.fn(),
|
|
12
|
+
outro: vi.fn(),
|
|
13
|
+
text: vi.fn(() => Promise.resolve("migration")),
|
|
14
|
+
isCancel: vi.fn(() => false),
|
|
15
|
+
cancel: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Helper to create mock table
|
|
19
|
+
const mockTable = (name: string) => {
|
|
20
|
+
const table = new PhysicalTable(name, {
|
|
21
|
+
pk: {
|
|
22
|
+
build: () => ({
|
|
23
|
+
_: { name: "id", type: "string" },
|
|
24
|
+
getDynamoType: () => "S",
|
|
25
|
+
name: "id",
|
|
26
|
+
}),
|
|
27
|
+
} as any,
|
|
28
|
+
});
|
|
29
|
+
table[TABLE_SYMBOLS.TABLE_NAME] = name;
|
|
30
|
+
table[TABLE_SYMBOLS.PARTITION_KEY] = {
|
|
31
|
+
name: "id",
|
|
32
|
+
getDynamoType: () => "S",
|
|
33
|
+
} as any;
|
|
34
|
+
return table;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const TEMP_DIR = join(tmpdir(), "mizzle-generate-test-" + Date.now());
|
|
38
|
+
const MIGRATIONS_DIR = join(TEMP_DIR, "migrations");
|
|
39
|
+
const SCHEMA_FILE = join(TEMP_DIR, "schema.ts");
|
|
40
|
+
|
|
41
|
+
describe("Generate Command", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
44
|
+
mkdirSync(MIGRATIONS_DIR, { recursive: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Mock discoverSchema to return our tables
|
|
52
|
+
const mockDiscover = vi.fn();
|
|
53
|
+
|
|
54
|
+
test("should create initial snapshot and migration if none exist", async () => {
|
|
55
|
+
// Setup
|
|
56
|
+
const tables = [mockTable("users")];
|
|
57
|
+
mockDiscover.mockResolvedValue({ tables, entities: [] });
|
|
58
|
+
|
|
59
|
+
await generateCommand({
|
|
60
|
+
config: { schema: SCHEMA_FILE, out: MIGRATIONS_DIR },
|
|
61
|
+
discoverSchema: mockDiscover, // Dependency injection for testing
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Verify Snapshot
|
|
65
|
+
const snapshotPath = join(MIGRATIONS_DIR, "snapshot.json");
|
|
66
|
+
expect(existsSync(snapshotPath)).toBe(true);
|
|
67
|
+
const snapshot = JSON.parse(readFileSync(snapshotPath, "utf-8"));
|
|
68
|
+
expect(snapshot.tables["users"]).toBeDefined();
|
|
69
|
+
|
|
70
|
+
// Verify Migration Script
|
|
71
|
+
const files = await import("fs").then((fs) => fs.readdirSync(MIGRATIONS_DIR));
|
|
72
|
+
const migrationFile = files.find((f) => f.endsWith(".ts"));
|
|
73
|
+
expect(migrationFile).toBeDefined();
|
|
74
|
+
expect(migrationFile).toMatch(/^0000_.*\.ts$/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("should not create migration if no changes", async () => {
|
|
78
|
+
// Setup: Create snapshot first
|
|
79
|
+
const tables = [mockTable("users")];
|
|
80
|
+
mockDiscover.mockResolvedValue({ tables, entities: [] });
|
|
81
|
+
|
|
82
|
+
// Run once to setup
|
|
83
|
+
await generateCommand({
|
|
84
|
+
config: { schema: SCHEMA_FILE, out: MIGRATIONS_DIR },
|
|
85
|
+
discoverSchema: mockDiscover,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const filesBefore = await import("fs").then((fs) => fs.readdirSync(MIGRATIONS_DIR));
|
|
89
|
+
|
|
90
|
+
// Run again
|
|
91
|
+
await generateCommand({
|
|
92
|
+
config: { schema: SCHEMA_FILE, out: MIGRATIONS_DIR },
|
|
93
|
+
discoverSchema: mockDiscover,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const filesAfter = await import("fs").then((fs) => fs.readdirSync(MIGRATIONS_DIR));
|
|
97
|
+
expect(filesAfter.length).toBe(filesBefore.length);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("should handle errors gracefully", async () => {
|
|
101
|
+
mockDiscover.mockRejectedValue(new Error("Discovery failed"));
|
|
102
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
103
|
+
const processExitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any);
|
|
104
|
+
|
|
105
|
+
await generateCommand({
|
|
106
|
+
config: { schema: SCHEMA_FILE, out: MIGRATIONS_DIR },
|
|
107
|
+
discoverSchema: mockDiscover,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
111
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
112
|
+
|
|
113
|
+
errorSpy.mockRestore();
|
|
114
|
+
processExitSpy.mockRestore();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { initCommand } from "../src/commands/init";
|
|
3
|
+
import { writeFileSync, existsSync, rmSync, mkdirSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import * as prompts from "@clack/prompts";
|
|
7
|
+
|
|
8
|
+
vi.mock("@clack/prompts", () => ({
|
|
9
|
+
intro: vi.fn(),
|
|
10
|
+
outro: vi.fn(),
|
|
11
|
+
text: vi.fn(),
|
|
12
|
+
isCancel: vi.fn((val) => typeof val === "symbol" && val.toString().includes("clack:cancel")),
|
|
13
|
+
note: vi.fn(),
|
|
14
|
+
cancel: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const TEMP_DIR = join(tmpdir(), "mizzle-init-test-" + Date.now());
|
|
18
|
+
|
|
19
|
+
describe("Init Command", () => {
|
|
20
|
+
const originalCwd = process.cwd();
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
24
|
+
process.chdir(TEMP_DIR);
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
process.chdir(originalCwd);
|
|
30
|
+
rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("should create mizzle.config.ts with user input", async () => {
|
|
34
|
+
// Mock prompts
|
|
35
|
+
(prompts.text as unknown as { mockImplementation: (fn: unknown) => void }).mockImplementation(
|
|
36
|
+
async (options: { message: string; initialValue?: string }) => {
|
|
37
|
+
const message = options.message;
|
|
38
|
+
if (message.includes("schema")) return "./src/schema.ts";
|
|
39
|
+
if (message.includes("output")) return "./migrations";
|
|
40
|
+
if (message.includes("region")) return "us-east-1";
|
|
41
|
+
if (message.includes("endpoint")) return "http://localhost:8000";
|
|
42
|
+
return options.initialValue || "";
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
await initCommand();
|
|
47
|
+
|
|
48
|
+
expect(existsSync(join(TEMP_DIR, "mizzle.config.ts"))).toBe(true);
|
|
49
|
+
const content = readFileSync(join(TEMP_DIR, "mizzle.config.ts"), "utf-8");
|
|
50
|
+
expect(content).toContain('schema: "./src/schema.ts"');
|
|
51
|
+
expect(content).toContain('out: "./migrations"');
|
|
52
|
+
expect(content).toContain('region: "us-east-1"');
|
|
53
|
+
expect(content).toContain('endpoint: "http://localhost:8000"');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("should abort if mizzle.config.ts already exists", async () => {
|
|
57
|
+
writeFileSync(join(TEMP_DIR, "mizzle.config.ts"), "existing");
|
|
58
|
+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
59
|
+
|
|
60
|
+
await initCommand();
|
|
61
|
+
|
|
62
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("already exists"));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should abort if user cancels", async () => {
|
|
66
|
+
const CANCEL = Symbol.for("clack:cancel");
|
|
67
|
+
(prompts.text as unknown as { mockResolvedValue: (val: unknown) => void }).mockResolvedValue(
|
|
68
|
+
CANCEL,
|
|
69
|
+
);
|
|
70
|
+
(prompts.isCancel as unknown as { mockReturnValue: (val: unknown) => void }).mockReturnValue(
|
|
71
|
+
true,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await initCommand();
|
|
75
|
+
|
|
76
|
+
expect(existsSync(join(TEMP_DIR, "mizzle.config.ts"))).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
test("cli should show help message", () => {
|
|
6
|
+
const projectRoot = process.cwd().includes("packages/mizzling")
|
|
7
|
+
? join(process.cwd(), "../..")
|
|
8
|
+
: process.cwd();
|
|
9
|
+
const mizzlingCliPath = join(projectRoot, "packages/mizzling/src/cli.ts");
|
|
10
|
+
|
|
11
|
+
const result = spawnSync("bun", [mizzlingCliPath, "--help"], {
|
|
12
|
+
encoding: "utf-8",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Since src/cli.ts doesn't exist, it might fail with a non-zero exit code or error
|
|
16
|
+
expect(result.status).toBe(0);
|
|
17
|
+
expect(result.stdout).toContain("Usage: mizzle [options] [command]");
|
|
18
|
+
expect(result.stdout).toContain("generate");
|
|
19
|
+
expect(result.stdout).toContain("push");
|
|
20
|
+
expect(result.stdout).toContain("list");
|
|
21
|
+
expect(result.stdout).toContain("drop");
|
|
22
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { generateCommand } from "../src/commands/generate";
|
|
3
|
+
import { pushCommand } from "../src/commands/push";
|
|
4
|
+
import { PhysicalTable } from "@aurios/mizzle/table";
|
|
5
|
+
import { TABLE_SYMBOLS } from "@repo/shared";
|
|
6
|
+
import { mkdirSync, rmSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { tmpdir } from "os";
|
|
9
|
+
|
|
10
|
+
// Mocks
|
|
11
|
+
const { mockClack } = vi.hoisted(() => ({
|
|
12
|
+
mockClack: {
|
|
13
|
+
text: vi.fn(() => Promise.resolve("test_migration")),
|
|
14
|
+
confirm: vi.fn(() => Promise.resolve(true)),
|
|
15
|
+
intro: vi.fn(() => {}),
|
|
16
|
+
outro: vi.fn(() => {}),
|
|
17
|
+
spinner: vi.fn(() => ({ start: vi.fn(), stop: vi.fn(), message: vi.fn() })),
|
|
18
|
+
isCancel: vi.fn(() => false),
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("@clack/prompts", () => ({
|
|
23
|
+
text: mockClack.text,
|
|
24
|
+
confirm: mockClack.confirm,
|
|
25
|
+
intro: mockClack.intro,
|
|
26
|
+
outro: mockClack.outro,
|
|
27
|
+
spinner: mockClack.spinner,
|
|
28
|
+
isCancel: mockClack.isCancel,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const mockTable = (name: string) => {
|
|
32
|
+
const table = new PhysicalTable(name, {
|
|
33
|
+
pk: {
|
|
34
|
+
build: () => ({
|
|
35
|
+
_: { name: "id", type: "string" },
|
|
36
|
+
getDynamoType: () => "S",
|
|
37
|
+
name: "id",
|
|
38
|
+
}),
|
|
39
|
+
} as any,
|
|
40
|
+
});
|
|
41
|
+
table[TABLE_SYMBOLS.TABLE_NAME] = name;
|
|
42
|
+
table[TABLE_SYMBOLS.PARTITION_KEY] = {
|
|
43
|
+
name: "id",
|
|
44
|
+
getDynamoType: () => "S",
|
|
45
|
+
} as any;
|
|
46
|
+
return table;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Mock Client
|
|
50
|
+
const createMockClient = () => {
|
|
51
|
+
return {
|
|
52
|
+
send: async () => ({ TableNames: [], Table: undefined }),
|
|
53
|
+
} as unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const TEMP_DIR = join(tmpdir(), "mizzle-interactive-test-" + Date.now());
|
|
57
|
+
|
|
58
|
+
describe("Interactive Commands", () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
mkdirSync(TEMP_DIR, { recursive: true });
|
|
61
|
+
vi.mocked(mockClack.text).mockClear();
|
|
62
|
+
vi.mocked(mockClack.confirm).mockClear();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const mockDiscover = vi.fn();
|
|
70
|
+
|
|
71
|
+
test("generateCommand should ask for migration name", async () => {
|
|
72
|
+
const tables = [mockTable("users")];
|
|
73
|
+
mockDiscover.mockResolvedValue({ tables, entities: [] });
|
|
74
|
+
|
|
75
|
+
await generateCommand({
|
|
76
|
+
config: { schema: "dummy", out: TEMP_DIR } as any,
|
|
77
|
+
discoverSchema: mockDiscover,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(mockClack.text).toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("pushCommand should ask for confirmation", async () => {
|
|
84
|
+
const tables = [mockTable("users")];
|
|
85
|
+
mockDiscover.mockResolvedValue({ tables, entities: [] });
|
|
86
|
+
const client = createMockClient();
|
|
87
|
+
|
|
88
|
+
await pushCommand({
|
|
89
|
+
config: { schema: "dummy", out: TEMP_DIR } as any,
|
|
90
|
+
discoverSchema: mockDiscover,
|
|
91
|
+
client: client as any,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(mockClack.confirm).toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach, vi } from "vitest";
|
|
2
|
+
import { listCommand } from "../src/commands/list";
|
|
3
|
+
import type { IMizzleClient } from "../../mizzle/src/core/client";
|
|
4
|
+
|
|
5
|
+
// Mock Console
|
|
6
|
+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
7
|
+
|
|
8
|
+
// Manual Mock Client
|
|
9
|
+
const createMockClient = (tables: Record<string, unknown>[]) => {
|
|
10
|
+
return {
|
|
11
|
+
send: async (command: { constructor: { name: string }; input?: { TableName: string } }) => {
|
|
12
|
+
const cmdName = command.constructor.name;
|
|
13
|
+
if (cmdName === "ListTablesCommand") {
|
|
14
|
+
return { TableNames: tables.map((t) => t.TableName as string) };
|
|
15
|
+
}
|
|
16
|
+
if (cmdName === "DescribeTableCommand") {
|
|
17
|
+
const tableName = command.input?.TableName;
|
|
18
|
+
const table = tables.find((t) => t.TableName === tableName);
|
|
19
|
+
return { Table: table };
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("List Command", () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
logSpy.mockClear();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("should list remote tables", async () => {
|
|
32
|
+
const mockTables = [
|
|
33
|
+
{
|
|
34
|
+
TableName: "users",
|
|
35
|
+
AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }],
|
|
36
|
+
KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
const client = createMockClient(mockTables) as unknown;
|
|
40
|
+
|
|
41
|
+
await listCommand({
|
|
42
|
+
config: { schema: "dummy", out: "dummy" } as any,
|
|
43
|
+
client: client as any,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining("users"));
|
|
47
|
+
// Basic check for now
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("should handle empty list", async () => {
|
|
51
|
+
const client = createMockClient([]) as unknown;
|
|
52
|
+
await listCommand({
|
|
53
|
+
config: { schema: "dummy", out: "dummy" } as any,
|
|
54
|
+
client: client as any,
|
|
55
|
+
});
|
|
56
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining("No tables found"));
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { defineConfig } from "@aurios/mizzling/index";
|
|
3
|
+
|
|
4
|
+
describe("Mizzling Package Exports", () => {
|
|
5
|
+
it("should export defineConfig", () => {
|
|
6
|
+
expect(defineConfig).toBeDefined();
|
|
7
|
+
expect(typeof defineConfig).toBe("function");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should return config object as is", () => {
|
|
11
|
+
const config = {
|
|
12
|
+
schema: "./schema",
|
|
13
|
+
out: "./migrations",
|
|
14
|
+
};
|
|
15
|
+
const result = defineConfig(config);
|
|
16
|
+
expect(result).toBe(config);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { expect, test, describe, vi } from "vitest";
|
|
2
|
+
import { pushCommand } from "../src/commands/push";
|
|
3
|
+
import { PhysicalTable } from "@aurios/mizzle/table";
|
|
4
|
+
import { TABLE_SYMBOLS } from "@repo/shared";
|
|
5
|
+
import type { IMizzleClient } from "../../mizzle/src/core/client";
|
|
6
|
+
|
|
7
|
+
// Mock Clack
|
|
8
|
+
vi.mock("@clack/prompts", () => ({
|
|
9
|
+
text: vi.fn(() => Promise.resolve("migration")),
|
|
10
|
+
confirm: vi.fn(() => Promise.resolve(true)),
|
|
11
|
+
intro: vi.fn(() => {}),
|
|
12
|
+
outro: vi.fn(() => {}),
|
|
13
|
+
spinner: () => ({ start: () => {}, stop: () => {}, message: () => {} }),
|
|
14
|
+
isCancel: () => false,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock Table
|
|
18
|
+
const mockTable = (name: string) => {
|
|
19
|
+
const table = new PhysicalTable(name, {
|
|
20
|
+
pk: {
|
|
21
|
+
build: () => ({
|
|
22
|
+
_: { name: "id", type: "string" },
|
|
23
|
+
getDynamoType: () => "S",
|
|
24
|
+
name: "id",
|
|
25
|
+
}),
|
|
26
|
+
} as any,
|
|
27
|
+
});
|
|
28
|
+
table[TABLE_SYMBOLS.TABLE_NAME] = name;
|
|
29
|
+
table[TABLE_SYMBOLS.PARTITION_KEY] = {
|
|
30
|
+
name: "id",
|
|
31
|
+
getDynamoType: () => "S",
|
|
32
|
+
} as any;
|
|
33
|
+
return table;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Manual Mock Client
|
|
37
|
+
const createMockClient = () => {
|
|
38
|
+
const sends: unknown[] = [];
|
|
39
|
+
return {
|
|
40
|
+
send: async (command: { constructor: { name: string }; input: Record<string, unknown> }) => {
|
|
41
|
+
sends.push(command);
|
|
42
|
+
const cmdName = command.constructor.name;
|
|
43
|
+
|
|
44
|
+
if (cmdName === "ListTablesCommand") {
|
|
45
|
+
return { TableNames: [] };
|
|
46
|
+
}
|
|
47
|
+
if (cmdName === "CreateTableCommand") {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
},
|
|
52
|
+
// Helper to access captured calls
|
|
53
|
+
_sends: sends,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe("Push Command", () => {
|
|
58
|
+
const mockDiscover = vi.fn();
|
|
59
|
+
|
|
60
|
+
test("should create table if it does not exist in remote", async () => {
|
|
61
|
+
// Setup
|
|
62
|
+
const tables = [mockTable("users")];
|
|
63
|
+
mockDiscover.mockResolvedValue({ tables, entities: [] });
|
|
64
|
+
|
|
65
|
+
const mockClient = createMockClient() as unknown;
|
|
66
|
+
|
|
67
|
+
await pushCommand({
|
|
68
|
+
config: { schema: "dummy", out: "dummy" } as any,
|
|
69
|
+
discoverSchema: mockDiscover,
|
|
70
|
+
client: mockClient as any,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Verify CreateTable was called
|
|
74
|
+
// We look for CreateTableCommand in mockClient._sends
|
|
75
|
+
const createCall = (
|
|
76
|
+
mockClient as {
|
|
77
|
+
_sends: {
|
|
78
|
+
constructor: { name: string };
|
|
79
|
+
input: { TableName: string; KeySchema: unknown };
|
|
80
|
+
}[];
|
|
81
|
+
}
|
|
82
|
+
)._sends.find((cmd) => cmd.constructor.name === "CreateTableCommand");
|
|
83
|
+
|
|
84
|
+
expect(createCall).toBeDefined();
|
|
85
|
+
expect(createCall!.input.TableName).toBe("users");
|
|
86
|
+
expect(createCall!.input.KeySchema).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"extends": "@repo/typescript-config/base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@repo/shared": [
|
|
7
|
+
"./../shared/src/index.ts"
|
|
8
|
+
],
|
|
9
|
+
"@aurios/mizzle": [
|
|
10
|
+
"./../mizzle/src/index.ts"
|
|
11
|
+
],
|
|
12
|
+
"@aurios/mizzle/columns": [
|
|
13
|
+
"./../mizzle/src/columns/index.ts"
|
|
14
|
+
],
|
|
15
|
+
"@aurios/mizzle/table": [
|
|
16
|
+
"./../mizzle/src/core/table.ts"
|
|
17
|
+
],
|
|
18
|
+
"@aurios/mizzle/snapshot": [
|
|
19
|
+
"./../mizzle/src/core/snapshot.ts"
|
|
20
|
+
],
|
|
21
|
+
"@aurios/mizzle/diff": [
|
|
22
|
+
"./../mizzle/src/core/diff.ts"
|
|
23
|
+
],
|
|
24
|
+
"@aurios/mizzle/introspection": [
|
|
25
|
+
"./../mizzle/src/core/introspection.ts"
|
|
26
|
+
],
|
|
27
|
+
"@aurios/mizzle/db": [
|
|
28
|
+
"./../mizzle/src/db.ts"
|
|
29
|
+
],
|
|
30
|
+
"@aurios/mizzle/*": [
|
|
31
|
+
"./../mizzle/src/*"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
13
35
|
}
|
package/tsup.config.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { defineConfig } from
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
entry: [
|
|
5
|
-
format: [
|
|
4
|
+
entry: ["src/cli.ts", "src/index.ts"],
|
|
5
|
+
format: ["esm"],
|
|
6
6
|
clean: true,
|
|
7
|
-
noExternal: [
|
|
7
|
+
noExternal: ["@repo/shared", "mizzle"],
|
|
8
8
|
minify: true,
|
|
9
9
|
banner: {
|
|
10
|
-
js:
|
|
10
|
+
js: "#!/usr/bin/env node",
|
|
11
11
|
},
|
|
12
12
|
});
|