@casekit/orm2-cli 0.0.0-20250322230249

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.
Files changed (71) hide show
  1. package/build/cli.d.ts +1 -0
  2. package/build/cli.js +20 -0
  3. package/build/commands/db-drop/handler.d.ts +3 -0
  4. package/build/commands/db-drop/handler.js +19 -0
  5. package/build/commands/db-drop/options.d.ts +1 -0
  6. package/build/commands/db-drop/options.js +1 -0
  7. package/build/commands/db-drop.d.ts +6 -0
  8. package/build/commands/db-drop.js +8 -0
  9. package/build/commands/db-drop.test.d.ts +1 -0
  10. package/build/commands/db-drop.test.js +21 -0
  11. package/build/commands/db-pull/handler.d.ts +3 -0
  12. package/build/commands/db-pull/handler.js +20 -0
  13. package/build/commands/db-pull/options.d.ts +8 -0
  14. package/build/commands/db-pull/options.js +8 -0
  15. package/build/commands/db-pull.d.ts +20 -0
  16. package/build/commands/db-pull.js +8 -0
  17. package/build/commands/db-push/handler.d.ts +3 -0
  18. package/build/commands/db-push/handler.js +18 -0
  19. package/build/commands/db-push/options.d.ts +2 -0
  20. package/build/commands/db-push/options.js +1 -0
  21. package/build/commands/db-push.d.ts +6 -0
  22. package/build/commands/db-push.js +8 -0
  23. package/build/commands/db-push.test.d.ts +1 -0
  24. package/build/commands/db-push.test.js +21 -0
  25. package/build/commands/generate-model/handler.d.ts +3 -0
  26. package/build/commands/generate-model/handler.js +10 -0
  27. package/build/commands/generate-model/options.d.ts +18 -0
  28. package/build/commands/generate-model/options.js +18 -0
  29. package/build/commands/generate-model/util/generateModelFile.d.ts +3 -0
  30. package/build/commands/generate-model/util/generateModelFile.js +59 -0
  31. package/build/commands/generate-model/util/generateModelFile.test.d.ts +1 -0
  32. package/build/commands/generate-model/util/generateModelFile.test.js +67 -0
  33. package/build/commands/generate-model/util/regenerateModelsFile.d.ts +2 -0
  34. package/build/commands/generate-model/util/regenerateModelsFile.js +50 -0
  35. package/build/commands/generate-model.d.ts +40 -0
  36. package/build/commands/generate-model.js +8 -0
  37. package/build/commands/generate-model.test.d.ts +1 -0
  38. package/build/commands/generate-model.test.js +55 -0
  39. package/build/commands/init/handler.d.ts +3 -0
  40. package/build/commands/init/handler.js +20 -0
  41. package/build/commands/init/options.d.ts +12 -0
  42. package/build/commands/init/options.js +12 -0
  43. package/build/commands/init/util/generateConfigFile.d.ts +1 -0
  44. package/build/commands/init/util/generateConfigFile.js +12 -0
  45. package/build/commands/init/util/generateDbFile.d.ts +1 -0
  46. package/build/commands/init/util/generateDbFile.js +61 -0
  47. package/build/commands/init/util/generateModelsFile.d.ts +1 -0
  48. package/build/commands/init/util/generateModelsFile.js +4 -0
  49. package/build/commands/init.d.ts +28 -0
  50. package/build/commands/init.js +8 -0
  51. package/build/commands/init.test.d.ts +1 -0
  52. package/build/commands/init.test.js +112 -0
  53. package/build/index.d.ts +1 -0
  54. package/build/index.js +1 -0
  55. package/build/options.d.ts +12 -0
  56. package/build/options.js +12 -0
  57. package/build/test/setup.d.ts +1 -0
  58. package/build/test/setup.js +31 -0
  59. package/build/types.d.ts +19 -0
  60. package/build/types.js +1 -0
  61. package/build/util/createOrOverwriteFile.d.ts +1 -0
  62. package/build/util/createOrOverwriteFile.js +17 -0
  63. package/build/util/loadConfig.d.ts +3 -0
  64. package/build/util/loadConfig.js +14 -0
  65. package/build/util/prettify.d.ts +1 -0
  66. package/build/util/prettify.js +8 -0
  67. package/build/util/usesDevServer.d.ts +1 -0
  68. package/build/util/usesDevServer.js +10 -0
  69. package/build/util/usesDevServer.test.d.ts +1 -0
  70. package/build/util/usesDevServer.test.js +54 -0
  71. package/package.json +71 -0
package/build/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/build/cli.js ADDED
@@ -0,0 +1,20 @@
1
+ import yargs from "yargs";
2
+ import { hideBin } from "yargs/helpers";
3
+ import { dbDrop } from "#commands/db-drop.js";
4
+ import { dbPush } from "#commands/db-push.js";
5
+ import { dbPull } from "./commands/db-pull.js";
6
+ import { generateModel } from "./commands/generate-model.js";
7
+ import { init } from "./commands/init.js";
8
+ import { globalOptions } from "./options.js";
9
+ await yargs(hideBin(process.argv))
10
+ .strict(true)
11
+ .scriptName("orm")
12
+ .options(globalOptions)
13
+ .command("db", "Commands for managing your database", (yargs) => yargs.command(dbDrop).command(dbPush).command(dbPull))
14
+ .command("generate", "Commands for scaffolding files", (yargs) => yargs.command(generateModel))
15
+ .command(init)
16
+ .help()
17
+ .showHelpOnFail(true)
18
+ .demandCommand()
19
+ .recommendCommands()
20
+ .parse();
@@ -0,0 +1,3 @@
1
+ import { Handler } from "#types.js";
2
+ import { builder } from "./options.js";
3
+ export declare const handler: Handler<typeof builder>;
@@ -0,0 +1,19 @@
1
+ import { migrate } from "@casekit/orm2-migrate";
2
+ import { loadConfig } from "#util/loadConfig.js";
3
+ export const handler = async (opts) => {
4
+ const { db } = await loadConfig(opts);
5
+ try {
6
+ console.log("Dropping database schemas");
7
+ await db.connect();
8
+ await migrate.drop(db);
9
+ console.log("✅ Done");
10
+ }
11
+ catch (e) {
12
+ console.error("Failed to drop schemas", e);
13
+ process.exitCode = 1;
14
+ throw e;
15
+ }
16
+ finally {
17
+ await db.close();
18
+ }
19
+ };
@@ -0,0 +1 @@
1
+ export declare const builder: {};
@@ -0,0 +1 @@
1
+ export const builder = {};
@@ -0,0 +1,6 @@
1
+ export declare const dbDrop: {
2
+ command: string;
3
+ desc: string;
4
+ builder: {};
5
+ handler: import("../types.js").Handler<{}>;
6
+ };
@@ -0,0 +1,8 @@
1
+ import { handler } from "./db-drop/handler.js";
2
+ import { builder } from "./db-drop/options.js";
3
+ export const dbDrop = {
4
+ command: "drop",
5
+ desc: "Drop the database schemas used by your models",
6
+ builder,
7
+ handler,
8
+ };
@@ -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 drop", () => {
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("drops the schema(s) for the models defined in the config", async () => {
16
+ await yargs().command(dbPush).parseAsync("push");
17
+ await yargs().command(dbDrop).parseAsync("drop");
18
+ const result = await db.query("SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'orm_cli_test';");
19
+ expect(result.rows.length).toBe(0);
20
+ });
21
+ });
@@ -0,0 +1,3 @@
1
+ import { Handler } from "#types.js";
2
+ import { builder } from "./options.js";
3
+ export declare const handler: Handler<typeof builder>;
@@ -0,0 +1,20 @@
1
+ import { migrate } from "@casekit/orm2-migrate";
2
+ import { loadConfig } from "#util/loadConfig.js";
3
+ export const handler = async (opts) => {
4
+ const { db } = await loadConfig(opts);
5
+ const schema = opts.schema.length > 0 ? opts.schema : [db.config.schema];
6
+ try {
7
+ console.log("Pulling schema from database");
8
+ await db.connect();
9
+ const pulledSchemas = await migrate.pull(db, schema);
10
+ console.log("✅ Done");
11
+ console.log({ pulledSchemas });
12
+ }
13
+ catch (e) {
14
+ console.error("Error pulling schema from database", e);
15
+ process.exitCode = 1;
16
+ }
17
+ finally {
18
+ await db.close();
19
+ }
20
+ };
@@ -0,0 +1,8 @@
1
+ export declare const builder: {
2
+ schema: {
3
+ type: "string";
4
+ desc: string;
5
+ array: true;
6
+ default: never[];
7
+ };
8
+ };
@@ -0,0 +1,8 @@
1
+ export const builder = {
2
+ schema: {
3
+ type: "string",
4
+ desc: "The name of the schema to pull in the database",
5
+ array: true,
6
+ default: [],
7
+ },
8
+ };
@@ -0,0 +1,20 @@
1
+ export declare const dbPull: {
2
+ command: string;
3
+ desc: string;
4
+ builder: {
5
+ schema: {
6
+ type: "string";
7
+ desc: string;
8
+ array: true;
9
+ default: never[];
10
+ };
11
+ };
12
+ handler: import("../types.js").Handler<{
13
+ schema: {
14
+ type: "string";
15
+ desc: string;
16
+ array: true;
17
+ default: never[];
18
+ };
19
+ }>;
20
+ };
@@ -0,0 +1,8 @@
1
+ import { handler } from "./db-pull/handler.js";
2
+ import { builder } from "./db-pull/options.js";
3
+ export const dbPull = {
4
+ command: "pull",
5
+ desc: "Introspect and pull the current schema from the database",
6
+ builder,
7
+ handler,
8
+ };
@@ -0,0 +1,3 @@
1
+ import { Handler } from "#types.js";
2
+ import { builder } from "./options.js";
3
+ export declare const handler: Handler<typeof builder>;
@@ -0,0 +1,18 @@
1
+ import { migrate } from "@casekit/orm2-migrate";
2
+ import { loadConfig } from "#util/loadConfig.js";
3
+ export const handler = async (opts) => {
4
+ const { db } = await loadConfig(opts);
5
+ try {
6
+ await db.connect();
7
+ await migrate.push(db);
8
+ console.log("✅ Done");
9
+ }
10
+ catch (e) {
11
+ console.error("Error pushing schema to database", e);
12
+ process.exitCode = 1;
13
+ throw e;
14
+ }
15
+ finally {
16
+ await db.close();
17
+ }
18
+ };
@@ -0,0 +1,2 @@
1
+ import { Builder } from "#types.js";
2
+ export declare const builder: Builder;
@@ -0,0 +1 @@
1
+ export const builder = {};
@@ -0,0 +1,6 @@
1
+ export declare const dbPush: {
2
+ command: string;
3
+ desc: string;
4
+ builder: import("../types.js").Builder;
5
+ handler: import("../types.js").Handler<import("../types.js").Builder>;
6
+ };
@@ -0,0 +1,8 @@
1
+ import { handler } from "./db-push/handler.js";
2
+ import { builder } from "./db-push/options.js";
3
+ export const dbPush = {
4
+ command: "push",
5
+ desc: "Forcibly push the current schema to the database",
6
+ builder,
7
+ handler,
8
+ };
@@ -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,3 @@
1
+ import { Handler } from "#types.js";
2
+ import { builder } from "./options.js";
3
+ export declare const handler: Handler<typeof builder>;
@@ -0,0 +1,10 @@
1
+ import { createOrOverwriteFile } from "#util/createOrOverwriteFile.js";
2
+ import { loadConfig } from "#util/loadConfig.js";
3
+ import { generateModelFile } from "./util/generateModelFile.js";
4
+ import { regenerateModelsFile } from "./util/regenerateModelsFile.js";
5
+ export const handler = async (opts) => {
6
+ const config = await loadConfig(opts);
7
+ const modelFile = await generateModelFile(opts);
8
+ await createOrOverwriteFile(`${config.directory}/models/${opts.name}.ts`, modelFile, opts.force);
9
+ await regenerateModelsFile(config, opts.name);
10
+ };
@@ -0,0 +1,18 @@
1
+ export declare const builder: {
2
+ readonly name: {
3
+ readonly type: "string";
4
+ readonly desc: "Name of the model";
5
+ readonly demandOption: true;
6
+ };
7
+ readonly template: {
8
+ readonly type: "string";
9
+ readonly desc: "Template to use to generate the model. Pass --template none if you have a default template defined but don't want to use it.";
10
+ readonly default: "default";
11
+ };
12
+ readonly fields: {
13
+ readonly type: "string";
14
+ readonly desc: "Fields to generate for the model, in the format 'name:type name:type'";
15
+ readonly array: true;
16
+ readonly default: readonly [];
17
+ };
18
+ };
@@ -0,0 +1,18 @@
1
+ export const builder = {
2
+ name: {
3
+ type: "string",
4
+ desc: "Name of the model",
5
+ demandOption: true,
6
+ },
7
+ template: {
8
+ type: "string",
9
+ desc: "Template to use to generate the model. Pass --template none if you have a default template defined but don't want to use it.",
10
+ default: "default",
11
+ },
12
+ fields: {
13
+ type: "string",
14
+ desc: "Fields to generate for the model, in the format 'name:type name:type'",
15
+ array: true,
16
+ default: [],
17
+ },
18
+ };
@@ -0,0 +1,3 @@
1
+ import { CommandOptions } from "#types.js";
2
+ import { builder } from "../options.js";
3
+ export declare const generateModelFile: (opts: Pick<CommandOptions<typeof builder>, "name" | "config" | "template" | "fields">) => Promise<string>;
@@ -0,0 +1,59 @@
1
+ import fs from "fs";
2
+ import jscodeshift from "jscodeshift";
3
+ import { prettify } from "#util/prettify.js";
4
+ export const generateModelFile = async (opts) => {
5
+ const config = fs.readFileSync(opts.config, "utf-8");
6
+ const j = jscodeshift.withParser("ts");
7
+ const root = j(config);
8
+ const templateContent = root
9
+ .find(j.ExportDefaultDeclaration)
10
+ .find(j.ObjectExpression)
11
+ .find(j.ObjectProperty, { key: { name: "templates" } })
12
+ .find(j.ObjectExpression)
13
+ .find(j.ObjectProperty, { key: { name: opts.template } })
14
+ .find(j.ObjectExpression);
15
+ if (templateContent.length === 0) {
16
+ if (opts.template !== "default") {
17
+ console.warn(`Template "${opts.template}" not found.`);
18
+ }
19
+ }
20
+ const templateAst = templateContent.length === 0
21
+ ? j("{ fields: {} }")
22
+ : j(templateContent.get().node);
23
+ const fieldsObj = templateAst
24
+ .find(j.ObjectProperty, {
25
+ key: { name: "fields" },
26
+ })
27
+ .find(j.ObjectExpression);
28
+ for (const field of opts.fields) {
29
+ const [name, type] = field.split(":");
30
+ if (!name || !type) {
31
+ console.error("Invalid field definition:", field);
32
+ process.exit(1);
33
+ }
34
+ const existingProperties = fieldsObj.get().node.properties ?? [];
35
+ const filteredProperties = existingProperties.filter((prop) => prop.type === "ObjectProperty" &&
36
+ prop.key.type === "Identifier" &&
37
+ prop.key.name !== name);
38
+ fieldsObj.replaceWith(j.objectExpression([
39
+ ...filteredProperties.map((prop) => j.objectProperty(prop.key, prop.value)),
40
+ j.objectProperty(j.identifier(name), j.objectExpression([
41
+ j.objectProperty(j.identifier("type"), j.stringLiteral(type)),
42
+ ])),
43
+ ]));
44
+ }
45
+ templateAst.find(j.EmptyStatement).remove();
46
+ const usesSqlLiteral = templateAst.find(j.TaggedTemplateExpression).length > 0;
47
+ // we avoid trailing commas and strip newlines so
48
+ // prettier won't break code onto multiple lines unless necessary.
49
+ // trailing commas will be added in again by prettier.
50
+ const definition = templateAst.toSource({
51
+ trailingComma: false,
52
+ lineTerminator: " ",
53
+ });
54
+ const result = `
55
+ import { ModelDefinition ${usesSqlLiteral ? ", sql" : ""} } from "@casekit/orm2";
56
+
57
+ export const ${opts.name} = ${definition} as const satisfies ModelDefinition;`;
58
+ return await prettify(process.cwd(), result);
59
+ };
@@ -0,0 +1,67 @@
1
+ import { vol } from "memfs";
2
+ import { beforeAll, describe, expect, test, vi } from "vitest";
3
+ import { unindent } from "@casekit/unindent";
4
+ import { generateModelFile } from "./generateModelFile.js";
5
+ describe("generateModelFile", () => {
6
+ beforeAll(() => {
7
+ vol.fromJSON({
8
+ "orm.config.ts": unindent `
9
+ import { sql } from "@casekit/orm2";
10
+ import { type OrmCLIConfig } from "@casekit/orm2-cli";
11
+
12
+ export default {
13
+ directory: "./app/db.server",
14
+ generate: {
15
+ templates: {
16
+ default: {
17
+ fields: {
18
+ id: { type: "uuid", primaryKey: true },
19
+ createdAt: { type: "timestamp", default: sql\`now()\` },
20
+ },
21
+ },
22
+ foo: {
23
+ fields: {
24
+ name: { type: "text" },
25
+ },
26
+ },
27
+ },
28
+ },
29
+ } satisfies OrmCLIConfig;
30
+ `,
31
+ }, "/project");
32
+ });
33
+ test("merges fields from the passed `fields` argument, overwriting same-named fields in the template", async () => {
34
+ const result = await generateModelFile({
35
+ config: "orm.config.ts",
36
+ name: "bar",
37
+ template: "default",
38
+ fields: ["id:serial", "name:text"],
39
+ });
40
+ expect(result.trim()).toBe(unindent `
41
+ import { ModelDefinition, sql } from "@casekit/orm2";
42
+
43
+ export const bar = {
44
+ fields: {
45
+ createdAt: { type: "timestamp", default: sql\`now()\` },
46
+ id: { type: "serial" },
47
+ name: { type: "text" },
48
+ },
49
+ } as const satisfies ModelDefinition;
50
+ `);
51
+ });
52
+ test("works if the template does not exist - but logs a warning", async () => {
53
+ const warnSpy = vi.spyOn(console, "warn").mockReturnValue();
54
+ const result = await generateModelFile({
55
+ config: "orm.config.ts",
56
+ name: "foo",
57
+ template: "wrong",
58
+ fields: [],
59
+ });
60
+ expect(warnSpy).toHaveBeenCalledWith(`Template "wrong" not found.`);
61
+ expect(result.trim()).toBe(unindent `
62
+ import { ModelDefinition } from "@casekit/orm2";
63
+
64
+ export const foo = { fields: {} } as const satisfies ModelDefinition;
65
+ `);
66
+ });
67
+ });
@@ -0,0 +1,2 @@
1
+ import { OrmCLIConfig } from "#types.js";
2
+ export declare const regenerateModelsFile: (config: OrmCLIConfig, name: string) => Promise<void>;
@@ -0,0 +1,50 @@
1
+ import fs from "fs";
2
+ import jscodeshift from "jscodeshift";
3
+ import path from "path";
4
+ import { prettify } from "#util/prettify.js";
5
+ export const regenerateModelsFile = async (config, name) => {
6
+ console.log(`- Regenerating models/index.ts file: ${config.directory}`);
7
+ const filename = path.join(config.directory, "models/index.ts");
8
+ const source = fs.readFileSync(path.join(process.cwd(), filename), "utf-8");
9
+ const j = jscodeshift.withParser("ts");
10
+ const root = j(source);
11
+ const modelsExport = root
12
+ .find(j.ExportNamedDeclaration)
13
+ .filter((path) => path.node.declaration?.type === "VariableDeclaration" &&
14
+ path.node.declaration?.declarations[0]?.type ===
15
+ "VariableDeclarator" &&
16
+ path.node.declaration?.declarations[0]?.id.type ===
17
+ "Identifier" &&
18
+ path.node.declaration?.declarations[0]?.id.name === "models" &&
19
+ path.node.declaration?.declarations[0]?.init?.type ===
20
+ "ObjectExpression" &&
21
+ path.node.declaration?.declarations[0]?.init?.type ===
22
+ "ObjectExpression")
23
+ .at(0);
24
+ if (modelsExport.size() === 0) {
25
+ console.log(`- No \`models\` object found in models/index.ts. Cannot inject \${name}\` export.`);
26
+ return;
27
+ }
28
+ const modelsObject = modelsExport.get().node.declaration.declarations[0].init;
29
+ // Add an import for `name` from `./name` if not already imported
30
+ const existingImport = root
31
+ .find(j.ImportDeclaration)
32
+ .filter((path) => path.node.source.value === `./${name}` &&
33
+ !!path.node.specifiers?.some((specifier) => specifier.type === "ImportSpecifier" &&
34
+ specifier.imported.name === name));
35
+ if (existingImport.size() === 0) {
36
+ const newImport = j.importDeclaration([j.importSpecifier(j.identifier(name))], j.literal(`./${name}`));
37
+ root.get().node.program.body.unshift(newImport);
38
+ modelsObject.properties.push(j.property.from({
39
+ key: j.identifier(name),
40
+ value: j.identifier(name),
41
+ shorthand: true,
42
+ kind: "init",
43
+ }));
44
+ console.log(`- Injected export into ${filename}`);
45
+ }
46
+ else {
47
+ console.log("- Model already exported from index file, skipping");
48
+ }
49
+ fs.writeFileSync(path.join(process.cwd(), filename), await prettify(path.join(process.cwd(), filename), root.toSource()));
50
+ };
@@ -0,0 +1,40 @@
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
+ readonly template: {
11
+ readonly type: "string";
12
+ readonly desc: "Template to use to generate the model. Pass --template none if you have a default template defined but don't want to use it.";
13
+ readonly default: "default";
14
+ };
15
+ readonly fields: {
16
+ readonly type: "string";
17
+ readonly desc: "Fields to generate for the model, in the format 'name:type name:type'";
18
+ readonly array: true;
19
+ readonly default: readonly [];
20
+ };
21
+ };
22
+ handler: import("../types.js").Handler<{
23
+ readonly name: {
24
+ readonly type: "string";
25
+ readonly desc: "Name of the model";
26
+ readonly demandOption: true;
27
+ };
28
+ readonly template: {
29
+ readonly type: "string";
30
+ readonly desc: "Template to use to generate the model. Pass --template none if you have a default template defined but don't want to use it.";
31
+ readonly default: "default";
32
+ };
33
+ readonly fields: {
34
+ readonly type: "string";
35
+ readonly desc: "Fields to generate for the model, in the format 'name:type name:type'";
36
+ readonly array: true;
37
+ readonly default: readonly [];
38
+ };
39
+ }>;
40
+ };
@@ -0,0 +1,8 @@
1
+ import { handler } from "./generate-model/handler.js";
2
+ import { builder } from "./generate-model/options.js";
3
+ export const generateModel = {
4
+ command: "model",
5
+ desc: "Generate a skeleton model file",
6
+ builder,
7
+ handler,
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ import * as prompts from "@inquirer/prompts";
2
+ import fs from "fs";
3
+ import { vol } from "memfs";
4
+ import { beforeEach, describe, expect, test, vi } from "vitest";
5
+ import yargs from "yargs";
6
+ import { unindent } from "@casekit/unindent";
7
+ import { globalOptions } from "#options.js";
8
+ import { generateModel } from "./generate-model.js";
9
+ describe("orm generate model", () => {
10
+ beforeEach(() => {
11
+ vi.spyOn(process, "cwd").mockReturnValue(".");
12
+ });
13
+ beforeEach(() => {
14
+ vol.fromJSON({
15
+ "app/db.server/models/index.ts": unindent `
16
+ import { user } from "./user";
17
+
18
+ export const models = {
19
+ user
20
+ };
21
+ `,
22
+ }, "/project");
23
+ });
24
+ test("it generates a model file merging the template and provided fields", async () => {
25
+ vi.spyOn(prompts, "confirm").mockResolvedValue(true);
26
+ vi.spyOn(process, "cwd").mockReturnValue("/project");
27
+ await yargs()
28
+ .options(globalOptions)
29
+ .command(generateModel)
30
+ .parseAsync("model --name post --fields title:text content:text");
31
+ const modelFile = fs.readFileSync("./app/db.server/models/post.ts", "utf8");
32
+ expect(modelFile.trim()).toEqual(unindent `
33
+ import { ModelDefinition, sql } from "@casekit/orm2";
34
+
35
+ export const post = {
36
+ fields: {
37
+ id: { type: "uuid", primaryKey: true },
38
+ createdAt: { type: "timestamp", default: sql\`now()\` },
39
+ title: { type: "text" },
40
+ content: { type: "text" },
41
+ },
42
+ } as const satisfies ModelDefinition;
43
+ `);
44
+ const indexFile = fs.readFileSync("./app/db.server/models/index.ts", "utf8");
45
+ expect(indexFile.trim()).toEqual(unindent `
46
+ import { post } from "./post";
47
+ import { user } from "./user";
48
+
49
+ export const models = {
50
+ user,
51
+ post,
52
+ };
53
+ `);
54
+ });
55
+ });
@@ -0,0 +1,3 @@
1
+ import { Handler } from "#types.js";
2
+ import { builder } from "./options.js";
3
+ export declare const handler: Handler<typeof builder>;
@@ -0,0 +1,20 @@
1
+ import { input } from "@inquirer/prompts";
2
+ import fs from "fs";
3
+ import { createOrOverwriteFile } from "#util/createOrOverwriteFile.js";
4
+ import { generateConfigFile } from "./util/generateConfigFile.js";
5
+ import { generateDbFile } from "./util/generateDbFile.js";
6
+ import { generateModelsFile } from "./util/generateModelsFile.js";
7
+ export const handler = async (opts) => {
8
+ const srcDir = ["src", "app", "lib"].find(fs.existsSync) ?? "src";
9
+ const dir = opts.directory ??
10
+ (await input({
11
+ message: "Where do you want to keep your database configuration?",
12
+ default: `./${srcDir}/db.server`,
13
+ }));
14
+ const dbFile = generateDbFile();
15
+ await createOrOverwriteFile(`${dir}/db.ts`, dbFile, opts.force);
16
+ const modelsFile = generateModelsFile();
17
+ await createOrOverwriteFile(`${dir}/models/index.ts`, modelsFile, opts.force);
18
+ const configFile = generateConfigFile(dir);
19
+ await createOrOverwriteFile(`orm.config.ts`, configFile, opts.force);
20
+ };
@@ -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
+ };