@antislop/zero-lucid-generator 1.0.0 → 1.1.0

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/README.md CHANGED
@@ -60,10 +60,29 @@ import { schema, zql } from './zero-schema.gen.js'
60
60
  |---|---|---|---|
61
61
  | `modelsSourcePath` | `string` | **required** | Path to your models directory, relative to the config file |
62
62
  | `output` | `string` | `zero-schema.gen.ts` | Output file path, relative to the config file |
63
- | `prettier` | `boolean` | `false` | Format the output with Prettier (must be installed) |
63
+ | `format` | `boolean` | `false` | Format the output with [oxfmt](https://oxc.rs/docs/guide/usage/formatter) (must be installed). Respects your `.oxfmtrc.json`. |
64
64
  | `excludeModels` | `LucidModel[]` | `[]` | Models to exclude from the generated schema |
65
65
  | `columnTypes` | `Record<string, Record<string, string>>` | `{}` | Override the inferred Zero type for specific columns |
66
66
 
67
+ ### Formatting with oxfmt
68
+
69
+ Set `format: true` to have the generated file automatically formatted on every run:
70
+
71
+ ```ts
72
+ const config: Config = {
73
+ modelsSourcePath: './app/models',
74
+ format: true,
75
+ }
76
+ ```
77
+
78
+ Install oxfmt in your project:
79
+
80
+ ```sh
81
+ npm add -D oxfmt
82
+ ```
83
+
84
+ The formatter picks up your `.oxfmtrc.json` / `oxfmt.config.ts` automatically, so the output will always match what your own format scripts produce.
85
+
67
86
  ---
68
87
 
69
88
  ## CLI options
package/dist/index.d.ts CHANGED
@@ -9,7 +9,8 @@ type ConfigInput = {
9
9
  */
10
10
  excludeModels?: (abstract new (...args: any[]) => any)[];
11
11
  output?: string;
12
- prettier?: boolean;
12
+ /** Format the output file with oxfmt. Requires oxfmt to be installed. */
13
+ format?: boolean;
13
14
  columnTypes?: Record<string, Record<string, string>>;
14
15
  };
15
16
 
package/dist/index.js CHANGED
@@ -17,14 +17,14 @@ async function getDefaultExportFromModulePath(modulePath) {
17
17
  try {
18
18
  await fs.access(modulePath);
19
19
  } catch {
20
- throw new Error(`lucid-zero: Module not found at ${modulePath}`);
20
+ throw new Error(`Module not found at ${modulePath}`);
21
21
  }
22
22
  const { tsImport } = await import("tsx/esm/api");
23
23
  const moduleUrl = url.pathToFileURL(modulePath).href;
24
24
  const module = await tsImport(moduleUrl, { parentURL: import.meta.url });
25
25
  const defaultExport = module.default;
26
26
  if (!defaultExport) {
27
- throw new Error(`lucid-zero: Module at ${modulePath} does not have a default export`);
27
+ throw new Error(`Module at ${modulePath} does not have a default export`);
28
28
  }
29
29
  return defaultExport;
30
30
  }
@@ -49,17 +49,17 @@ var Config = class {
49
49
  this.excludeModels = configInput.excludeModels ?? [];
50
50
  this.outputFilePath = path.resolve(configDir, configInput.output ?? DEFAULT_OUTPUT_FILE_PATH);
51
51
  this.tsconfigPath = tsconfigPath ?? path.resolve(configDir, "tsconfig.json");
52
- this.formatOutputFile = configInput.prettier ?? false;
52
+ this.formatOutputFile = configInput.format ?? false;
53
53
  this.columnTypes = configInput.columnTypes ?? {};
54
54
  }
55
55
  async verify() {
56
56
  if (!this.modelsSourcePath) {
57
- throw new Error("lucid-zero: modelsSourcePath is required");
57
+ throw new Error("modelsSourcePath is required");
58
58
  }
59
59
  await this.loadModels();
60
60
  if (this.models.length === 0) {
61
61
  throw new Error(
62
- `lucid-zero: No Lucid models found in modelsSourcePath. Each model file must have a default export that extends BaseModel.`
62
+ `No Lucid models found in modelsSourcePath. Each model file must have a default export that extends BaseModel.`
63
63
  );
64
64
  }
65
65
  this.verifyColumnTypes();
@@ -71,7 +71,7 @@ var Config = class {
71
71
  fileNames = await fs2.readdir(modelsSourceAbsPath);
72
72
  } catch (e) {
73
73
  throw new Error(
74
- `lucid-zero: Could not read modelsSourcePath at ${modelsSourceAbsPath}. Does the directory exist?Error: ${e}`
74
+ `Could not read modelsSourcePath at ${modelsSourceAbsPath}. Does the directory exist?Error: ${e}`
75
75
  );
76
76
  }
77
77
  const tsFileNames = fileNames.filter((f) => f.endsWith(".ts") && !f.endsWith(".d.ts"));
@@ -84,7 +84,7 @@ var Config = class {
84
84
  path.join(modelsSourceAbsPath, fileName)
85
85
  );
86
86
  } catch (err) {
87
- console.warn(`lucid-zero: Failed to import ${fileName}, skipping: ${String(err)}`);
87
+ console.warn(`Failed to import ${fileName}, skipping: ${String(err)}`);
88
88
  return null;
89
89
  }
90
90
  if (!this.isLucidModel(defaultExport)) {
@@ -106,14 +106,14 @@ var Config = class {
106
106
  for (const [modelName, columnOverrides] of Object.entries(this.columnTypes ?? {})) {
107
107
  if (!modelNames.has(modelName)) {
108
108
  throw new Error(
109
- `lucid-zero: columnTypes has unknown model "${modelName}". Known models: ${[...modelNames].join(", ")}`
109
+ `columnTypes has unknown model "${modelName}". Known models: ${[...modelNames].join(", ")}`
110
110
  );
111
111
  }
112
112
  for (const [attributeName, typeString] of Object.entries(columnOverrides)) {
113
113
  const baseTypeName = typeString.match(/^([a-z]+)/)?.[1];
114
114
  if (!baseTypeName || !validZeroTypes.includes(baseTypeName)) {
115
115
  throw new Error(
116
- `lucid-zero: columnTypes override for ${modelName}.${attributeName} has unrecognised type "${typeString}". Expected one of: ${validZeroTypes.join(", ")}`
116
+ `columnTypes override for ${modelName}.${attributeName} has unrecognised type "${typeString}". Expected one of: ${validZeroTypes.join(", ")}`
117
117
  );
118
118
  }
119
119
  }
@@ -137,8 +137,8 @@ var ConfigLoader = class {
137
137
  defaultExport = await getDefaultExportFromModulePath(absoluteConfigPath);
138
138
  } catch (err) {
139
139
  throw new Error(
140
- `lucid-zero: Failed to import config at ${absoluteConfigPath}
141
- Error: ${String(err)}`
140
+ `Failed to import config at ${absoluteConfigPath}
141
+ Error: ${err instanceof Error ? err.message : String(err)}`
142
142
  );
143
143
  }
144
144
  const config = new Config(defaultExport, path.dirname(absoluteConfigPath), opts.tsconfigPath);
@@ -190,12 +190,12 @@ var SchemaTransformer = class {
190
190
  });
191
191
  if (!modelSourceFile) {
192
192
  throw new Error(
193
- `lucid-zero: Could not find source file for model ${model.name} in ${this.project.getSourceFiles().map((sourceFile) => sourceFile.getFilePath()).join(", ")}`
193
+ `Could not find source file for model ${model.name} in ${this.project.getSourceFiles().map((sourceFile) => sourceFile.getFilePath()).join(", ")}`
194
194
  );
195
195
  }
196
196
  const classDeclaration = modelSourceFile.getClass(model.name);
197
197
  if (!classDeclaration) {
198
- throw new Error(`lucid-zero: Could not find class declaration for model ${model.name}`);
198
+ throw new Error(`Could not find class declaration for model ${model.name}`);
199
199
  }
200
200
  let currentClass = classDeclaration;
201
201
  while (currentClass) {
@@ -226,7 +226,7 @@ var SchemaTransformer = class {
226
226
  zeroType = tsTypeToZeroType(tsType);
227
227
  } else {
228
228
  console.warn(
229
- `lucid-zero: Could not infer Zero type for ${model.name}.${attributeName}. Falling back to json(). Add a columnTypes override if this is wrong.`
229
+ `Could not infer Zero type for ${model.name}.${attributeName}. Falling back to json(). Add a columnTypes override if this is wrong.`
230
230
  );
231
231
  zeroType = "json()";
232
232
  }
@@ -247,13 +247,13 @@ var SchemaTransformer = class {
247
247
  relation.boot();
248
248
  } catch (err) {
249
249
  throw new Error(
250
- `lucid-zero: Failed to boot relation "${relationName}" on ${model.name}: ${String(err)}`
250
+ `Failed to boot relation "${relationName}" on ${model.name}: ${String(err)}`
251
251
  );
252
252
  }
253
253
  const relatedModel = relation.relatedModel();
254
254
  if (!allModels.some((m) => m.name === relatedModel.name)) {
255
255
  console.info(
256
- `lucid-zero: Skipping relation "${relationName}" on ${model.name} \u2014 ${relatedModel.name} is not included.`
256
+ `Skipping relation "${relationName}" on ${model.name} \u2014 ${relatedModel.name} is not included.`
257
257
  );
258
258
  continue;
259
259
  }
@@ -285,7 +285,7 @@ var SchemaTransformer = class {
285
285
  case "manyToMany": {
286
286
  if (!relation.pivotTable || !relation.pivotForeignKey || !relation.pivotRelatedForeignKey) {
287
287
  throw new Error(
288
- `lucid-zero: manyToMany relation "${relationName}" on ${model.name} \u2014 pivot table fields could not be resolved after boot().`
288
+ `manyToMany relation "${relationName}" on ${model.name} \u2014 pivot table fields could not be resolved after boot().`
289
289
  );
290
290
  }
291
291
  relationships[relationName] = {
@@ -310,7 +310,7 @@ var SchemaTransformer = class {
310
310
  const throughRel = relation;
311
311
  if (!throughRel.throughModel) {
312
312
  throw new Error(
313
- `lucid-zero: hasManyThrough relation "${relationName}" on ${model.name} \u2014 throughModel could not be resolved after boot().`
313
+ `hasManyThrough relation "${relationName}" on ${model.name} \u2014 throughModel could not be resolved after boot().`
314
314
  );
315
315
  }
316
316
  const throughModel = throughRel.throughModel();
@@ -352,33 +352,47 @@ function tsTypeToZeroType(type) {
352
352
  import fs3 from "fs";
353
353
 
354
354
  // src/formatter.ts
355
- var Formatter = class _Formatter {
356
- static async loadPrettier() {
357
- let prettier;
358
- try {
359
- const { createRequire } = await import("module");
360
- const req = createRequire(process.cwd() + "/package.json");
361
- const prettierPath = req.resolve("prettier");
362
- const { pathToFileURL } = await import("url");
363
- prettier = await import(pathToFileURL(prettierPath).href);
364
- return prettier;
365
- } catch {
366
- return null;
367
- }
355
+ import { spawnSync } from "child_process";
356
+ import { createRequire } from "module";
357
+ import path2 from "path";
358
+ function resolveOxfmtBin() {
359
+ try {
360
+ const req = createRequire(process.cwd() + "/package.json");
361
+ const pkgMain = req.resolve("oxfmt");
362
+ const pkgRoot = path2.join(pkgMain, "..", "..");
363
+ return path2.join(pkgRoot, "bin", "oxfmt");
364
+ } catch {
365
+ return null;
368
366
  }
369
- static async format(code) {
370
- const prettier = await _Formatter.loadPrettier();
371
- if (!prettier) {
372
- console.warn("lucid-zero: prettier not found \u2014 skipping formatting");
367
+ }
368
+ var Formatter = class {
369
+ /**
370
+ * Formats `code` by piping it through the oxfmt CLI with `--stdin-filepath`
371
+ * set to the output file's absolute path. This lets oxfmt walk up the
372
+ * directory tree and discover `.oxfmtrc.json` / `oxfmt.config.ts` in the
373
+ * user's project.
374
+ */
375
+ static async format(code, outputFilePath) {
376
+ const bin = resolveOxfmtBin();
377
+ if (!bin) {
378
+ console.warn("lucid-zero: oxfmt not found \u2014 skipping formatting");
373
379
  return code;
374
380
  }
375
- try {
376
- const options = await prettier.resolveConfig(process.cwd());
377
- return await prettier.format(code, { ...options, parser: "typescript" });
378
- } catch {
379
- console.warn("lucid-zero: prettier formatting failed \u2014 skipping");
381
+ const result = spawnSync(
382
+ process.execPath,
383
+ [bin, `--stdin-filepath=${outputFilePath}`],
384
+ {
385
+ input: code,
386
+ encoding: "utf8",
387
+ cwd: process.cwd()
388
+ }
389
+ );
390
+ if (result.status !== 0) {
391
+ console.warn(`lucid-zero: oxfmt formatting failed \u2014 skipping
392
+ ${result.stderr ?? ""}`);
380
393
  return code;
381
394
  }
395
+ return result.stdout;
382
396
  }
383
397
  };
384
398
 
@@ -395,7 +409,7 @@ var CodeGenerator = class {
395
409
  async generateToOutputFile() {
396
410
  let code = this.generate();
397
411
  if (this.config.formatOutputFile) {
398
- code = await Formatter.format(code);
412
+ code = await Formatter.format(code, this.config.outputFilePath);
399
413
  }
400
414
  fs3.writeFileSync(this.config.outputFilePath, code);
401
415
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antislop/zero-lucid-generator",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Generate Zero schemas from Lucid ORM models",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,13 +19,20 @@
19
19
  "check-types": "tsc --noEmit",
20
20
  "lint": "eslint src tests",
21
21
  "lint:fix": "eslint src tests --fix",
22
- "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
23
- "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\""
22
+ "format": "oxfmt \"src/**/*.ts\" \"tests/**/*.ts\"",
23
+ "format:check": "oxfmt --check \"src/**/*.ts\" \"tests/**/*.ts\""
24
24
  },
25
25
  "bin": {
26
26
  "lucid-zero": "./dist/index.js"
27
27
  },
28
- "keywords": ["zero", "lucid", "adonisjs", "schema", "generator", "rocicorp"],
28
+ "keywords": [
29
+ "zero",
30
+ "lucid",
31
+ "adonisjs",
32
+ "schema",
33
+ "generator",
34
+ "rocicorp"
35
+ ],
29
36
  "author": "antislop",
30
37
  "license": "ISC",
31
38
  "publishConfig": {
@@ -43,8 +50,7 @@
43
50
  "@typescript-eslint/parser": "^8.61.0",
44
51
  "@vitest/coverage-v8": "^4.1.8",
45
52
  "eslint": "^10.4.1",
46
- "eslint-config-prettier": "^10.1.8",
47
- "prettier": "^3.8.4",
53
+ "oxfmt": "^0.54.0",
48
54
  "tsup": "^8.5.1",
49
55
  "tsx": "^4.22.4",
50
56
  "typescript": "^6.0.3",