@casekit/orm-cli 0.0.1-release-candidate
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/build/cli.d.ts +1 -0
- package/build/cli.js +21 -0
- package/build/commands/db-drop/handler.d.ts +3 -0
- package/build/commands/db-drop/handler.js +15 -0
- package/build/commands/db-drop/options.d.ts +1 -0
- package/build/commands/db-drop/options.js +1 -0
- package/build/commands/db-drop.d.ts +6 -0
- package/build/commands/db-drop.js +8 -0
- package/build/commands/db-drop.test.d.ts +1 -0
- package/build/commands/db-drop.test.js +22 -0
- package/build/commands/db-pull/handler.d.ts +3 -0
- package/build/commands/db-pull/handler.js +32 -0
- package/build/commands/db-pull/options.d.ts +13 -0
- package/build/commands/db-pull/options.js +13 -0
- package/build/commands/db-pull/util/relationNames.d.ts +3 -0
- package/build/commands/db-pull/util/relationNames.js +14 -0
- package/build/commands/db-pull/util/relationNames.test.d.ts +1 -0
- package/build/commands/db-pull/util/relationNames.test.js +61 -0
- package/build/commands/db-pull/util/renderDefault.d.ts +3 -0
- package/build/commands/db-pull/util/renderDefault.js +46 -0
- package/build/commands/db-pull/util/renderDefault.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderDefault.test.js +67 -0
- package/build/commands/db-pull/util/renderFieldDefinition.d.ts +2 -0
- package/build/commands/db-pull/util/renderFieldDefinition.js +50 -0
- package/build/commands/db-pull/util/renderFieldDefinition.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderFieldDefinition.test.js +182 -0
- package/build/commands/db-pull/util/renderModel.basic.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderModel.basic.test.js +169 -0
- package/build/commands/db-pull/util/renderModel.constraints.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderModel.constraints.test.js +677 -0
- package/build/commands/db-pull/util/renderModel.d.ts +2 -0
- package/build/commands/db-pull/util/renderModel.defaultValues.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderModel.defaultValues.test.js +518 -0
- package/build/commands/db-pull/util/renderModel.js +88 -0
- package/build/commands/db-pull/util/renderModel.relations.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderModel.relations.test.js +880 -0
- package/build/commands/db-pull/util/renderModel.types.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderModel.types.test.js +703 -0
- package/build/commands/db-pull/util/renderRelations.d.ts +2 -0
- package/build/commands/db-pull/util/renderRelations.js +55 -0
- package/build/commands/db-pull/util/renderRelations.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderRelations.test.js +165 -0
- package/build/commands/db-pull/util/renderType.d.ts +6 -0
- package/build/commands/db-pull/util/renderType.js +55 -0
- package/build/commands/db-pull/util/renderType.test.d.ts +1 -0
- package/build/commands/db-pull/util/renderType.test.js +46 -0
- package/build/commands/db-pull.d.ts +30 -0
- package/build/commands/db-pull.js +8 -0
- package/build/commands/db-pull.test.d.ts +1 -0
- package/build/commands/db-pull.test.js +633 -0
- package/build/commands/db-push/handler.d.ts +3 -0
- package/build/commands/db-push/handler.js +14 -0
- package/build/commands/db-push/options.d.ts +2 -0
- package/build/commands/db-push/options.js +1 -0
- package/build/commands/db-push.d.ts +6 -0
- package/build/commands/db-push.js +8 -0
- package/build/commands/db-push.test.d.ts +1 -0
- package/build/commands/db-push.test.js +21 -0
- package/build/commands/generate-model/handler.d.ts +3 -0
- package/build/commands/generate-model/handler.js +18 -0
- package/build/commands/generate-model/options.d.ts +7 -0
- package/build/commands/generate-model/options.js +7 -0
- package/build/commands/generate-model.d.ts +18 -0
- package/build/commands/generate-model.js +8 -0
- package/build/commands/generate-model.test.d.ts +1 -0
- package/build/commands/generate-model.test.js +58 -0
- package/build/commands/init/handler.d.ts +3 -0
- package/build/commands/init/handler.js +22 -0
- package/build/commands/init/options.d.ts +12 -0
- package/build/commands/init/options.js +12 -0
- package/build/commands/init.d.ts +28 -0
- package/build/commands/init.js +8 -0
- package/build/commands/init.test.d.ts +1 -0
- package/build/commands/init.test.js +125 -0
- package/build/generators/generateConfigFile.d.ts +1 -0
- package/build/generators/generateConfigFile.js +16 -0
- package/build/generators/generateDbFile.d.ts +1 -0
- package/build/generators/generateDbFile.js +37 -0
- package/build/generators/generateModelFile.d.ts +1 -0
- package/build/generators/generateModelFile.js +11 -0
- package/build/generators/generateModelFile.test.d.ts +1 -0
- package/build/generators/generateModelFile.test.js +19 -0
- package/build/generators/generateModelsFile.d.ts +1 -0
- package/build/generators/generateModelsFile.js +8 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/options.d.ts +12 -0
- package/build/options.js +12 -0
- package/build/test/setup.d.ts +1 -0
- package/build/test/setup.js +35 -0
- package/build/types.d.ts +14 -0
- package/build/types.js +1 -0
- package/build/util/createOrOverwriteFile.d.ts +1 -0
- package/build/util/createOrOverwriteFile.js +17 -0
- package/build/util/loadConfig.d.ts +3 -0
- package/build/util/loadConfig.js +21 -0
- package/build/util/prettify.d.ts +1 -0
- package/build/util/prettify.js +8 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
3
|
+
import yargs from "yargs";
|
|
4
|
+
import { dbDrop } from "./db-drop.js";
|
|
5
|
+
import { dbPush } from "./db-push.js";
|
|
6
|
+
describe("db push", () => {
|
|
7
|
+
let db;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
db = new pg.Client();
|
|
10
|
+
await db.connect();
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await db.end();
|
|
14
|
+
});
|
|
15
|
+
test("creates the schema for the models defined in the config", async () => {
|
|
16
|
+
await yargs().command(dbDrop).parseAsync("drop");
|
|
17
|
+
await yargs().command(dbPush).parseAsync("push");
|
|
18
|
+
const result = await db.query("SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'orm';");
|
|
19
|
+
expect(result.rows.length).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { camelCase } from "es-toolkit";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { generateModelFile } from "#generators/generateModelFile.js";
|
|
5
|
+
import { generateModelsFile } from "#generators/generateModelsFile.js";
|
|
6
|
+
import { createOrOverwriteFile } from "#util/createOrOverwriteFile.js";
|
|
7
|
+
import { loadConfig } from "#util/loadConfig.js";
|
|
8
|
+
import { prettify } from "#util/prettify.js";
|
|
9
|
+
export const handler = async (opts) => {
|
|
10
|
+
const config = await loadConfig(opts);
|
|
11
|
+
const modelFile = await generateModelFile(opts.name, config.directory);
|
|
12
|
+
await createOrOverwriteFile(`${config.directory}/models/${opts.name}.ts`, modelFile, opts.force);
|
|
13
|
+
const models = fs
|
|
14
|
+
.readdirSync(`${config.directory}/models`)
|
|
15
|
+
.filter((f) => f !== "index.ts")
|
|
16
|
+
.map((f) => camelCase(f.replace(/\.ts$/, "")));
|
|
17
|
+
await createOrOverwriteFile(`${config.directory}/models/index.ts`, await prettify(path.join(process.cwd(), `${config.directory}/models/index.ts`), generateModelsFile(models)), opts.force);
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const generateModel: {
|
|
2
|
+
command: string;
|
|
3
|
+
desc: string;
|
|
4
|
+
builder: {
|
|
5
|
+
readonly name: {
|
|
6
|
+
readonly type: "string";
|
|
7
|
+
readonly desc: "Name of the model";
|
|
8
|
+
readonly demandOption: true;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
handler: import("../types.js").Handler<{
|
|
12
|
+
readonly name: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly desc: "Name of the model";
|
|
15
|
+
readonly demandOption: true;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as prompts from "@inquirer/prompts";
|
|
2
|
+
import { vol } from "memfs";
|
|
3
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { unindent } from "@casekit/unindent";
|
|
6
|
+
import { globalOptions } from "#options.js";
|
|
7
|
+
import { generateModel } from "./generate-model.js";
|
|
8
|
+
describe("orm generate model", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.spyOn(process, "cwd").mockReturnValue(".");
|
|
11
|
+
vol.fromJSON({
|
|
12
|
+
"app/db.server/models/index.ts": unindent `
|
|
13
|
+
import { user } from "./user";
|
|
14
|
+
|
|
15
|
+
export const models = {
|
|
16
|
+
user
|
|
17
|
+
};
|
|
18
|
+
`,
|
|
19
|
+
"app/db.server/models/user.ts": unindent `
|
|
20
|
+
import { type ModelDefinition, sql } from "@casekit/orm";
|
|
21
|
+
|
|
22
|
+
export const user = {
|
|
23
|
+
fields: {
|
|
24
|
+
id: { type: "uuid", primaryKey: true },
|
|
25
|
+
createdAt: { type: "timestamp", default: sql\`now()\` },
|
|
26
|
+
name: { type: "text" },
|
|
27
|
+
},
|
|
28
|
+
} as const satisfies ModelDefinition;
|
|
29
|
+
`,
|
|
30
|
+
}, "/project");
|
|
31
|
+
});
|
|
32
|
+
test("it generates a model file and updates the index", async () => {
|
|
33
|
+
vi.spyOn(prompts, "confirm").mockResolvedValue(true);
|
|
34
|
+
vi.spyOn(process, "cwd").mockReturnValue("/project");
|
|
35
|
+
await yargs()
|
|
36
|
+
.options(globalOptions)
|
|
37
|
+
.command(generateModel)
|
|
38
|
+
.parseAsync("model --name post");
|
|
39
|
+
const modelFile = vol.readFileSync("./app/db.server/models/post.ts", "utf8");
|
|
40
|
+
expect(modelFile.trim()).toEqual(unindent `
|
|
41
|
+
import type { ModelDefinition } from "@casekit/orm";
|
|
42
|
+
|
|
43
|
+
export const post = {
|
|
44
|
+
fields: {},
|
|
45
|
+
} as const satisfies ModelDefinition;
|
|
46
|
+
`);
|
|
47
|
+
const indexFile = vol.readFileSync("./app/db.server/models/index.ts", "utf8");
|
|
48
|
+
expect(indexFile.trim()).toEqual(unindent `
|
|
49
|
+
import { post } from "./post";
|
|
50
|
+
import { user } from "./user";
|
|
51
|
+
|
|
52
|
+
export const models = {
|
|
53
|
+
post,
|
|
54
|
+
user,
|
|
55
|
+
};
|
|
56
|
+
`);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { input } from "@inquirer/prompts";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { generateConfigFile } from "#generators/generateConfigFile.js";
|
|
5
|
+
import { generateDbFile } from "#generators/generateDbFile.js";
|
|
6
|
+
import { generateModelsFile } from "#generators/generateModelsFile.js";
|
|
7
|
+
import { createOrOverwriteFile } from "#util/createOrOverwriteFile.js";
|
|
8
|
+
import { prettify } from "#util/prettify.js";
|
|
9
|
+
export const handler = async (opts) => {
|
|
10
|
+
const srcDir = ["src", "app", "lib"].find(fs.existsSync) ?? "src";
|
|
11
|
+
const dir = opts.directory ??
|
|
12
|
+
(await input({
|
|
13
|
+
message: "Where do you want to keep your database configuration?",
|
|
14
|
+
default: `./${srcDir}/db.server`,
|
|
15
|
+
}));
|
|
16
|
+
const dbFile = generateDbFile();
|
|
17
|
+
await createOrOverwriteFile(`${dir}/index.ts`, dbFile, opts.force);
|
|
18
|
+
const modelsFile = generateModelsFile([]);
|
|
19
|
+
await createOrOverwriteFile(`${dir}/models/index.ts`, await prettify(path.join(process.cwd(), `${dir}/models/index.ts`), modelsFile), opts.force);
|
|
20
|
+
const configFile = generateConfigFile(dir);
|
|
21
|
+
await createOrOverwriteFile(`orm.config.ts`, configFile, opts.force);
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const builder: {
|
|
2
|
+
readonly force: {
|
|
3
|
+
readonly type: "boolean";
|
|
4
|
+
readonly desc: "Overwrite existing files without asking for confirmation";
|
|
5
|
+
readonly default: false;
|
|
6
|
+
};
|
|
7
|
+
readonly directory: {
|
|
8
|
+
readonly alias: "d";
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly desc: "Location to keep your database configuration";
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const builder = {
|
|
2
|
+
force: {
|
|
3
|
+
type: "boolean",
|
|
4
|
+
desc: "Overwrite existing files without asking for confirmation",
|
|
5
|
+
default: false,
|
|
6
|
+
},
|
|
7
|
+
directory: {
|
|
8
|
+
alias: "d",
|
|
9
|
+
type: "string",
|
|
10
|
+
desc: "Location to keep your database configuration",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const init: {
|
|
2
|
+
command: string;
|
|
3
|
+
desc: string;
|
|
4
|
+
builder: {
|
|
5
|
+
readonly force: {
|
|
6
|
+
readonly type: "boolean";
|
|
7
|
+
readonly desc: "Overwrite existing files without asking for confirmation";
|
|
8
|
+
readonly default: false;
|
|
9
|
+
};
|
|
10
|
+
readonly directory: {
|
|
11
|
+
readonly alias: "d";
|
|
12
|
+
readonly type: "string";
|
|
13
|
+
readonly desc: "Location to keep your database configuration";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
handler: import("../types.js").Handler<{
|
|
17
|
+
readonly force: {
|
|
18
|
+
readonly type: "boolean";
|
|
19
|
+
readonly desc: "Overwrite existing files without asking for confirmation";
|
|
20
|
+
readonly default: false;
|
|
21
|
+
};
|
|
22
|
+
readonly directory: {
|
|
23
|
+
readonly alias: "d";
|
|
24
|
+
readonly type: "string";
|
|
25
|
+
readonly desc: "Location to keep your database configuration";
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as prompts from "@inquirer/prompts";
|
|
2
|
+
import { vol } from "memfs";
|
|
3
|
+
import { describe, expect, test, vi } from "vitest";
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { unindent } from "@casekit/unindent";
|
|
6
|
+
import { init } from "./init.js";
|
|
7
|
+
describe("init", () => {
|
|
8
|
+
test("scaffolding config files", async () => {
|
|
9
|
+
vi.spyOn(prompts, "input").mockResolvedValueOnce("./app/db.server");
|
|
10
|
+
vi.spyOn(prompts, "confirm").mockResolvedValueOnce(true);
|
|
11
|
+
await yargs().command(init).parseAsync("init");
|
|
12
|
+
const dbFile = vol.readFileSync("./app/db.server/index.ts", "utf-8");
|
|
13
|
+
const modelFile = vol.readFileSync("./app/db.server/models/index.ts", "utf-8");
|
|
14
|
+
const configFile = vol.readFileSync("./orm.config.ts", "utf-8");
|
|
15
|
+
expect(dbFile).toEqual(unindent `
|
|
16
|
+
import { type Config, type ModelType, type Orm, orm } from "@casekit/orm";
|
|
17
|
+
import { models } from "./models";
|
|
18
|
+
|
|
19
|
+
const config = {
|
|
20
|
+
models,
|
|
21
|
+
} as const satisfies Config;
|
|
22
|
+
|
|
23
|
+
let db: Orm<typeof config>;
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
// eslint-disable-next-line no-var
|
|
27
|
+
var __db: Orm<typeof config>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// we do this because in development we don't want to restart
|
|
31
|
+
// the server with every change, but we want to make sure we don't
|
|
32
|
+
// create a new connection to the DB with every change either.
|
|
33
|
+
if (process.env.NODE_ENV === "production") {
|
|
34
|
+
db = orm(config);
|
|
35
|
+
await db.connect();
|
|
36
|
+
} else {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
38
|
+
if (!global.__db) {
|
|
39
|
+
global.__db = orm(config);
|
|
40
|
+
await global.__db.connect();
|
|
41
|
+
}
|
|
42
|
+
db = global.__db;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type DB = Orm<typeof config>;
|
|
46
|
+
export type Models = typeof models;
|
|
47
|
+
export type Model<M extends keyof Models> = ModelType<Models[M]>;
|
|
48
|
+
|
|
49
|
+
export { db };
|
|
50
|
+
`);
|
|
51
|
+
expect(modelFile.trim()).toEqual(unindent `
|
|
52
|
+
export const models = {};
|
|
53
|
+
`);
|
|
54
|
+
expect(configFile).toEqual(unindent `
|
|
55
|
+
import { type Config, orm } from "@casekit/orm";
|
|
56
|
+
import type { OrmCLIConfig } from "@casekit/orm-cli";
|
|
57
|
+
|
|
58
|
+
import { models } from "./app/db.server/models";
|
|
59
|
+
|
|
60
|
+
const config = {
|
|
61
|
+
models,
|
|
62
|
+
} as const satisfies Config;
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
db: orm(config),
|
|
66
|
+
directory: "./app/db.server",
|
|
67
|
+
} satisfies OrmCLIConfig;
|
|
68
|
+
`);
|
|
69
|
+
});
|
|
70
|
+
test("optionally overwriting existing files", async () => {
|
|
71
|
+
vol.fromJSON({
|
|
72
|
+
"package.json": JSON.stringify({ dependencies: {} }),
|
|
73
|
+
"app/db.server/models/index.ts": "original",
|
|
74
|
+
"orm.config.ts": "original",
|
|
75
|
+
"app/db.server/index.ts": "original",
|
|
76
|
+
}, "/project");
|
|
77
|
+
vi.spyOn(prompts, "input").mockResolvedValueOnce("./app/db.server");
|
|
78
|
+
vi.spyOn(prompts, "confirm")
|
|
79
|
+
.mockResolvedValueOnce(true)
|
|
80
|
+
.mockResolvedValueOnce(false)
|
|
81
|
+
.mockResolvedValueOnce(false);
|
|
82
|
+
await yargs().command(init).parseAsync("init");
|
|
83
|
+
const dbFile = vol.readFileSync("./app/db.server/index.ts", "utf-8");
|
|
84
|
+
const modelFile = vol.readFileSync("./app/db.server/models/index.ts", "utf-8");
|
|
85
|
+
const configFile = vol.readFileSync("./orm.config.ts", "utf-8");
|
|
86
|
+
expect(dbFile).toEqual(unindent `
|
|
87
|
+
import { type Config, type ModelType, type Orm, orm } from "@casekit/orm";
|
|
88
|
+
import { models } from "./models";
|
|
89
|
+
|
|
90
|
+
const config = {
|
|
91
|
+
models,
|
|
92
|
+
} as const satisfies Config;
|
|
93
|
+
|
|
94
|
+
let db: Orm<typeof config>;
|
|
95
|
+
|
|
96
|
+
declare global {
|
|
97
|
+
// eslint-disable-next-line no-var
|
|
98
|
+
var __db: Orm<typeof config>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// we do this because in development we don't want to restart
|
|
102
|
+
// the server with every change, but we want to make sure we don't
|
|
103
|
+
// create a new connection to the DB with every change either.
|
|
104
|
+
if (process.env.NODE_ENV === "production") {
|
|
105
|
+
db = orm(config);
|
|
106
|
+
await db.connect();
|
|
107
|
+
} else {
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
109
|
+
if (!global.__db) {
|
|
110
|
+
global.__db = orm(config);
|
|
111
|
+
await global.__db.connect();
|
|
112
|
+
}
|
|
113
|
+
db = global.__db;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type DB = Orm<typeof config>;
|
|
117
|
+
export type Models = typeof models;
|
|
118
|
+
export type Model<M extends keyof Models> = ModelType<Models[M]>;
|
|
119
|
+
|
|
120
|
+
export { db };
|
|
121
|
+
`);
|
|
122
|
+
expect(modelFile).toEqual("original");
|
|
123
|
+
expect(configFile).toEqual("original");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateConfigFile: (directory: string) => string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { unindent } from "@casekit/unindent";
|
|
2
|
+
export const generateConfigFile = (directory) => unindent `
|
|
3
|
+
import { type Config, orm } from "@casekit/orm";
|
|
4
|
+
import type { OrmCLIConfig } from "@casekit/orm-cli";
|
|
5
|
+
|
|
6
|
+
import { models } from "./${directory.replace(/^\.\//, "")}/models";
|
|
7
|
+
|
|
8
|
+
const config = {
|
|
9
|
+
models,
|
|
10
|
+
} as const satisfies Config;
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
db: orm(config),
|
|
14
|
+
directory: "${directory}",
|
|
15
|
+
} satisfies OrmCLIConfig;
|
|
16
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateDbFile: () => string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { unindent } from "@casekit/unindent";
|
|
2
|
+
export const generateDbFile = () => unindent `
|
|
3
|
+
import { type Config, type ModelType, type Orm, orm } from "@casekit/orm";
|
|
4
|
+
import { models } from "./models";
|
|
5
|
+
|
|
6
|
+
const config = {
|
|
7
|
+
models,
|
|
8
|
+
} as const satisfies Config;
|
|
9
|
+
|
|
10
|
+
let db: Orm<typeof config>;
|
|
11
|
+
|
|
12
|
+
declare global {
|
|
13
|
+
// eslint-disable-next-line no-var
|
|
14
|
+
var __db: Orm<typeof config>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// we do this because in development we don't want to restart
|
|
18
|
+
// the server with every change, but we want to make sure we don't
|
|
19
|
+
// create a new connection to the DB with every change either.
|
|
20
|
+
if (process.env.NODE_ENV === "production") {
|
|
21
|
+
db = orm(config);
|
|
22
|
+
await db.connect();
|
|
23
|
+
} else {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
25
|
+
if (!global.__db) {
|
|
26
|
+
global.__db = orm(config);
|
|
27
|
+
await global.__db.connect();
|
|
28
|
+
}
|
|
29
|
+
db = global.__db;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DB = Orm<typeof config>;
|
|
33
|
+
export type Models = typeof models;
|
|
34
|
+
export type Model<M extends keyof Models> = ModelType<Models[M]>;
|
|
35
|
+
|
|
36
|
+
export { db };
|
|
37
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateModelFile: (name: string, directory: string) => Promise<string>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { prettify } from "#util/prettify.js";
|
|
3
|
+
export const generateModelFile = async (name, directory) => {
|
|
4
|
+
const code = `import type { ModelDefinition } from "@casekit/orm";
|
|
5
|
+
|
|
6
|
+
export const ${name} = {
|
|
7
|
+
fields: {},
|
|
8
|
+
} as const satisfies ModelDefinition;
|
|
9
|
+
`;
|
|
10
|
+
return await prettify(path.join(process.cwd(), directory), code);
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { vol } from "memfs";
|
|
2
|
+
import { beforeAll, describe, expect, test } from "vitest";
|
|
3
|
+
import { unindent } from "@casekit/unindent";
|
|
4
|
+
import { generateModelFile } from "./generateModelFile.js";
|
|
5
|
+
describe("generateModelFile", () => {
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
vol.fromJSON({}, "/project");
|
|
8
|
+
});
|
|
9
|
+
test("generates an empty model definition", async () => {
|
|
10
|
+
const result = await generateModelFile("book", "./app/db.server");
|
|
11
|
+
expect(result.trim()).toBe(unindent `
|
|
12
|
+
import type { ModelDefinition } from "@casekit/orm";
|
|
13
|
+
|
|
14
|
+
export const book = {
|
|
15
|
+
fields: {},
|
|
16
|
+
} as const satisfies ModelDefinition;
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateModelsFile: (models: string[]) => string;
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OrmCLIConfig } from "./types.js";
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const globalOptions: {
|
|
2
|
+
readonly config: {
|
|
3
|
+
readonly type: "string";
|
|
4
|
+
readonly alias: "c";
|
|
5
|
+
readonly describe: "Path to the orm CLIs configuration file";
|
|
6
|
+
readonly default: "orm.config.ts";
|
|
7
|
+
};
|
|
8
|
+
readonly force: {
|
|
9
|
+
readonly type: "boolean";
|
|
10
|
+
readonly desc: "Skip all prompts, use defaults, and overwrite existing files without asking for confirmation";
|
|
11
|
+
};
|
|
12
|
+
};
|
package/build/options.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const globalOptions = {
|
|
2
|
+
config: {
|
|
3
|
+
type: "string",
|
|
4
|
+
alias: "c",
|
|
5
|
+
describe: "Path to the orm CLIs configuration file",
|
|
6
|
+
default: "orm.config.ts",
|
|
7
|
+
},
|
|
8
|
+
force: {
|
|
9
|
+
type: "boolean",
|
|
10
|
+
desc: "Skip all prompts, use defaults, and overwrite existing files without asking for confirmation",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { vol } from "memfs";
|
|
2
|
+
import pg from "pg";
|
|
3
|
+
import { register } from "tsx/esm/api";
|
|
4
|
+
import { afterAll, afterEach, beforeAll, beforeEach, vi } from "vitest";
|
|
5
|
+
import * as loadConfig from "#util/loadConfig.js";
|
|
6
|
+
vi.mock("fs");
|
|
7
|
+
vi.mock("prettier");
|
|
8
|
+
vi.mock("@inquirer/prompts");
|
|
9
|
+
let unregister;
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
const client = new pg.Client();
|
|
12
|
+
await client.connect();
|
|
13
|
+
await client.query("DROP SCHEMA IF EXISTS orm_cli_test CASCADE");
|
|
14
|
+
unregister = register();
|
|
15
|
+
});
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
await unregister();
|
|
18
|
+
});
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
const path = "./orm.config.ts";
|
|
21
|
+
const { default: config } = await import(path);
|
|
22
|
+
await config.db.connect();
|
|
23
|
+
process.on("exit", async function () {
|
|
24
|
+
await config.db.close();
|
|
25
|
+
});
|
|
26
|
+
vi.spyOn(loadConfig, "loadConfig").mockResolvedValue(config);
|
|
27
|
+
const originalFs = await vi.importActual("fs");
|
|
28
|
+
const ormConfig = originalFs.readFileSync("./src/test/orm.config.ts", "utf8");
|
|
29
|
+
const prettierConfig = originalFs.readFileSync("../../.prettierrc.json", "utf8");
|
|
30
|
+
vol.fromJSON({ "orm.config.ts": ormConfig, ".prettierrc.json": prettierConfig }, "/project");
|
|
31
|
+
vi.spyOn(process, "cwd").mockReturnValue("/project");
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vol.reset();
|
|
35
|
+
});
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ConnectionConfig } from "pg";
|
|
2
|
+
import { ArgumentsCamelCase, InferredOptionTypes, Options } from "yargs";
|
|
3
|
+
import { Orm } from "@casekit/orm";
|
|
4
|
+
import { globalOptions } from "#options.js";
|
|
5
|
+
export interface OrmCLIConfig {
|
|
6
|
+
db: Orm;
|
|
7
|
+
directory: string;
|
|
8
|
+
migrate?: {
|
|
9
|
+
connection?: ConnectionConfig;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export type Builder = Record<string, Options>;
|
|
13
|
+
export type CommandOptions<T extends Builder = Record<string, never>> = ArgumentsCamelCase<InferredOptionTypes<T & typeof globalOptions>>;
|
|
14
|
+
export type Handler<T extends Builder = Record<string, never>> = (opts: ArgumentsCamelCase<InferredOptionTypes<T & typeof globalOptions>>) => void | Promise<void>;
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createOrOverwriteFile: (filePath: string, content: string, force?: boolean) => Promise<string | undefined>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export const createOrOverwriteFile = async (filePath, content, force) => {
|
|
5
|
+
const fullPath = path.join(process.cwd(), filePath);
|
|
6
|
+
if (fs.existsSync(fullPath) && !force) {
|
|
7
|
+
const overwrite = await confirm({
|
|
8
|
+
message: `${filePath} already exists - overwrite it?`,
|
|
9
|
+
default: false,
|
|
10
|
+
});
|
|
11
|
+
if (!overwrite)
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
15
|
+
fs.writeFileSync(fullPath, content);
|
|
16
|
+
return fullPath;
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { register } from "tsx/esm/api";
|
|
4
|
+
export const loadConfig = async (options) => {
|
|
5
|
+
try {
|
|
6
|
+
dotenv.config();
|
|
7
|
+
const unregister = register();
|
|
8
|
+
const { default: config } = await import(path.join(process.cwd(), options.config));
|
|
9
|
+
await unregister();
|
|
10
|
+
const c = "default" in config ? config.default : config;
|
|
11
|
+
await c.db.connect();
|
|
12
|
+
process.on("exit", async function () {
|
|
13
|
+
await c.db.close();
|
|
14
|
+
});
|
|
15
|
+
return c;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
console.error(e instanceof Error ? e.message : e);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const prettify: (path: string, content: string) => Promise<string>;
|