@casekit/orm2-cli 1.0.6 → 1.0.7

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.
@@ -1,4 +1,3 @@
1
- import { dir } from "console";
2
1
  import { camelCase } from "es-toolkit";
3
2
  import fs from "fs";
4
3
  import path from "path";
@@ -9,11 +8,11 @@ import { loadConfig } from "#util/loadConfig.js";
9
8
  import { prettify } from "#util/prettify.js";
10
9
  export const handler = async (opts) => {
11
10
  const config = await loadConfig(opts);
12
- const modelFile = await generateModelFile(opts, config);
11
+ const modelFile = await generateModelFile(opts.name, config.directory);
13
12
  await createOrOverwriteFile(`${config.directory}/models/${opts.name}.ts`, modelFile, opts.force);
14
13
  const models = fs
15
14
  .readdirSync(`${config.directory}/models`)
16
15
  .filter((f) => f !== "index.ts")
17
16
  .map((f) => camelCase(f.replace(/\.ts$/, "")));
18
- await createOrOverwriteFile(`${config.directory}/models/index.ts`, await prettify(path.join(process.cwd(), `${dir}/models/index.ts`), generateModelsFile(models)), opts.force);
17
+ await createOrOverwriteFile(`${config.directory}/models/index.ts`, await prettify(path.join(process.cwd(), `${config.directory}/models/index.ts`), generateModelsFile(models)), opts.force);
19
18
  };
@@ -4,15 +4,4 @@ export declare const builder: {
4
4
  readonly desc: "Name of the model";
5
5
  readonly demandOption: true;
6
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
7
  };
@@ -4,15 +4,4 @@ export const builder = {
4
4
  desc: "Name of the model",
5
5
  demandOption: true,
6
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
7
  };
@@ -7,17 +7,6 @@ export declare const generateModel: {
7
7
  readonly desc: "Name of the model";
8
8
  readonly demandOption: true;
9
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
10
  };
22
11
  handler: import("../types.js").Handler<{
23
12
  readonly name: {
@@ -25,16 +14,5 @@ export declare const generateModel: {
25
14
  readonly desc: "Name of the model";
26
15
  readonly demandOption: true;
27
16
  };
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
17
  }>;
40
18
  };
@@ -29,24 +29,19 @@ describe("orm generate model", () => {
29
29
  `,
30
30
  }, "/project");
31
31
  });
32
- test("it generates a model file merging the template and provided fields", async () => {
32
+ test("it generates a model file and updates the index", async () => {
33
33
  vi.spyOn(prompts, "confirm").mockResolvedValue(true);
34
34
  vi.spyOn(process, "cwd").mockReturnValue("/project");
35
35
  await yargs()
36
36
  .options(globalOptions)
37
37
  .command(generateModel)
38
- .parseAsync("model --name post --fields title:text content:text");
38
+ .parseAsync("model --name post");
39
39
  const modelFile = vol.readFileSync("./app/db.server/models/post.ts", "utf8");
40
40
  expect(modelFile.trim()).toEqual(unindent `
41
- import { type ModelDefinition, sql } from "@casekit/orm2";
41
+ import type { ModelDefinition } from "@casekit/orm2";
42
42
 
43
43
  export const post = {
44
- fields: {
45
- id: { type: "uuid", primaryKey: true },
46
- createdAt: { type: "timestamp", default: sql\`now()\` },
47
- title: { type: "text" },
48
- content: { type: "text" },
49
- },
44
+ fields: {},
50
45
  } as const satisfies ModelDefinition;
51
46
  `);
52
47
  const indexFile = vol.readFileSync("./app/db.server/models/index.ts", "utf8");
@@ -1,3 +1 @@
1
- import type { CommandOptions, OrmCLIConfig } from "#types.js";
2
- import type { builder } from "../commands/generate-model/options.js";
3
- export declare const generateModelFile: (opts: Pick<CommandOptions<typeof builder>, "name" | "config" | "template" | "fields">, { directory }: Pick<OrmCLIConfig, "directory">) => Promise<string>;
1
+ export declare const generateModelFile: (name: string, directory: string) => Promise<string>;
@@ -1,112 +1,11 @@
1
- import { camelCase } from "es-toolkit";
2
- import jscodeshift from "jscodeshift";
3
- import fs from "node:fs";
4
1
  import path from "node:path";
5
2
  import { prettify } from "#util/prettify.js";
6
- export const generateModelFile = async (opts, { directory }) => {
7
- const config = fs.readFileSync(opts.config, "utf-8");
8
- const j = jscodeshift.withParser("ts");
9
- const root = j(config);
10
- // Find the template in the config
11
- const templatePath = root
12
- .find(j.ExportDefaultDeclaration)
13
- .find(j.ObjectExpression)
14
- .find(j.ObjectProperty, {
15
- key: { type: "Identifier", name: "generate" },
16
- })
17
- .find(j.ObjectProperty, {
18
- key: { type: "Identifier", name: "templates" },
19
- })
20
- .find(j.ObjectProperty, {
21
- key: { type: "Identifier", name: opts.template },
22
- });
23
- let modelDefinition;
24
- if (templatePath.length > 0) {
25
- // Get the template node directly
26
- modelDefinition = templatePath.find(j.ObjectExpression).get().node;
27
- }
28
- else {
29
- console.warn(`Template "${opts.template}" not found.`);
30
- // Create a minimal model definition with just fields
31
- modelDefinition = j.objectExpression([
32
- j.objectProperty(j.identifier("fields"), j.objectExpression([])),
33
- ]);
34
- }
35
- // Find or create the fields property
36
- let fieldsProperty = modelDefinition.properties.find((prop) => prop.type === "ObjectProperty" &&
37
- prop.key.type === "Identifier" &&
38
- prop.key.name === "fields");
39
- if (!fieldsProperty) {
40
- fieldsProperty = j.objectProperty(j.identifier("fields"), j.objectExpression([]));
41
- modelDefinition.properties.push(fieldsProperty);
42
- }
43
- const fieldsObject = fieldsProperty.value;
44
- // Collect existing fields from template by converting them to source code
45
- const templateFieldsCode = new Map();
46
- for (const prop of fieldsObject.properties) {
47
- if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
48
- // Extract the field code by converting the property to source
49
- const fieldCode = j(prop.value).toSource({ quote: "double" });
50
- templateFieldsCode.set(prop.key.name, fieldCode);
51
- }
52
- }
53
- // Process command-line fields - they override template fields but should be added at the end
54
- const newFields = new Map();
55
- for (const field of opts.fields) {
56
- const [originalName, type] = field.split(":");
57
- if (!originalName || !type) {
58
- console.error("Invalid field definition:", field);
59
- process.exit(1);
60
- }
61
- const camelCasedName = camelCase(originalName);
62
- // Build a code string for the new field
63
- let fieldCode = `{ type: "${type}" }`;
64
- if (camelCasedName !== originalName) {
65
- fieldCode = `{ column: "${originalName}", type: "${type}" }`;
66
- }
67
- // Remove from template if it exists, add to new fields
68
- templateFieldsCode.delete(camelCasedName);
69
- newFields.set(camelCasedName, fieldCode);
70
- }
71
- // Merge: template fields first (excluding overridden ones), then new fields
72
- const allFieldsCode = new Map([...templateFieldsCode, ...newFields]);
73
- // Rebuild the fields object with all properties
74
- const newFieldsProps = [];
75
- for (const [name, code] of allFieldsCode.entries()) {
76
- // Parse the field code back into an AST node
77
- const parsed = j(`const x = ${code}`);
78
- const value = parsed.find(j.VariableDeclarator).get("init")
79
- .node;
80
- newFieldsProps.push(j.objectProperty(j.identifier(name), value));
81
- }
82
- fieldsObject.properties = newFieldsProps;
83
- // Check if sql import is needed by traversing the AST
84
- const hasSqlLiteral = j(modelDefinition).find(j.TaggedTemplateExpression, {
85
- tag: { type: "Identifier", name: "sql" },
86
- }).length > 0;
87
- // Build the import statement by parsing the import code string
88
- // to ensure proper TypeScript type-only import syntax
89
- let importCode;
90
- if (hasSqlLiteral) {
91
- // import { type ModelDefinition, sql }
92
- importCode =
93
- 'import { type ModelDefinition, sql } from "@casekit/orm2";';
94
- }
95
- else {
96
- // import type { ModelDefinition }
97
- importCode = 'import type { ModelDefinition } from "@casekit/orm2";';
98
- }
99
- const importDeclaration = j(importCode)
100
- .find(j.ImportDeclaration)
101
- .get().node;
102
- // Build the export statement with type assertion
103
- const exportDeclaration = j.exportNamedDeclaration(j.variableDeclaration("const", [
104
- j.variableDeclarator(j.identifier(opts.name), j.tsSatisfiesExpression(j.tsAsExpression(modelDefinition, j.tsTypeReference(j.identifier("const"))), j.tsTypeReference(j.identifier("ModelDefinition")))),
105
- ]));
106
- const program = j.program([importDeclaration, exportDeclaration]);
107
- const result = j(program).toSource({
108
- quote: "double",
109
- objectCurlySpacing: false,
110
- });
111
- return await prettify(path.join(process.cwd(), directory), result);
3
+ export const generateModelFile = async (name, directory) => {
4
+ const code = `import type { ModelDefinition } from "@casekit/orm2";
5
+
6
+ export const ${name} = {
7
+ fields: {},
8
+ } as const satisfies ModelDefinition;
9
+ `;
10
+ return await prettify(path.join(process.cwd(), directory), code);
112
11
  };
@@ -4,64 +4,14 @@ import { unindent } from "@casekit/unindent";
4
4
  import { generateModelFile } from "./generateModelFile.js";
5
5
  describe("generateModelFile", () => {
6
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
- }, { directory: "./app/db.server" });
40
- expect(result.trim()).toBe(unindent `
41
- import { type 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
- `);
7
+ vol.fromJSON({}, "/project");
51
8
  });
52
- test("works if the template does not exist", async () => {
53
- const result = await generateModelFile({
54
- config: "orm.config.ts",
55
- name: "foo",
56
- template: "wrong",
57
- fields: [],
58
- }, {
59
- directory: "./app/db.server",
60
- });
9
+ test("generates an empty model definition", async () => {
10
+ const result = await generateModelFile("book", "./app/db.server");
61
11
  expect(result.trim()).toBe(unindent `
62
12
  import type { ModelDefinition } from "@casekit/orm2";
63
13
 
64
- export const foo = {
14
+ export const book = {
65
15
  fields: {},
66
16
  } as const satisfies ModelDefinition;
67
17
  `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@casekit/orm2-cli",
3
3
  "description": "",
4
- "version": "1.0.6",
4
+ "version": "1.0.7",
5
5
  "author": "",
6
6
  "bin": {
7
7
  "orm": "./build/cli.js"
@@ -14,19 +14,17 @@
14
14
  "camelcase": "^8.0.0",
15
15
  "dotenv": "^16.5.0",
16
16
  "es-toolkit": "^1.39.3",
17
- "jscodeshift": "^17.3.0",
18
17
  "pluralize": "^8.0.0",
19
18
  "tsx": "^4.20.3",
20
19
  "yargs": "^18.0.0",
21
- "@casekit/orm2": "1.0.6",
22
- "@casekit/sql": "1.0.6",
23
- "@casekit/toolbox": "1.0.6",
24
- "@casekit/orm2-migrate": "1.0.6"
20
+ "@casekit/orm2-migrate": "1.0.7",
21
+ "@casekit/sql": "1.0.7",
22
+ "@casekit/orm2": "1.0.7",
23
+ "@casekit/toolbox": "1.0.7"
25
24
  },
26
25
  "devDependencies": {
27
26
  "@trivago/prettier-plugin-sort-imports": "^5.2.2",
28
27
  "@types/byline": "^4.2.36",
29
- "@types/jscodeshift": "^17.3.0",
30
28
  "@types/node": "^24.0.3",
31
29
  "@types/pg": "^8.15.4",
32
30
  "@types/pluralize": "^0.0.33",
@@ -39,9 +37,9 @@
39
37
  "vite-tsconfig-paths": "^5.1.4",
40
38
  "vitest": "^3.2.4",
41
39
  "zod": "^4.0.17",
42
- "@casekit/prettier-config": "1.0.6",
43
- "@casekit/orm2-fixtures": "1.0.6",
44
- "@casekit/tsconfig": "1.0.6"
40
+ "@casekit/orm2-fixtures": "1.0.7",
41
+ "@casekit/tsconfig": "1.0.7",
42
+ "@casekit/prettier-config": "1.0.7"
45
43
  },
46
44
  "exports": {
47
45
  ".": "./build/index.js"