@postxl/generator 0.71.1 → 0.72.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/dist/generator.js CHANGED
@@ -32,13 +32,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  step((generator = generator.apply(thisArg, _arguments || [])).next());
33
33
  });
34
34
  };
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.generate = void 0;
40
+ const assert_1 = __importDefault(require("assert"));
37
41
  const generator_helper_1 = require("@prisma/generator-helper");
38
42
  const internals_1 = require("@prisma/internals");
39
43
  const zod_1 = require("zod");
40
44
  const lock_1 = require("@postxl/lock");
41
45
  const prettier = __importStar(require("@postxl/prettier"));
46
+ const runtime_1 = require("@postxl/runtime");
42
47
  const react_generator_1 = require("./generators/enums/react.generator");
43
48
  const types_generator_1 = require("./generators/enums/types.generator");
44
49
  const module_generator_1 = require("./generators/indices/data/module.generator");
@@ -75,6 +80,7 @@ const service_generator_5 = require("./generators/models/update/service.generato
75
80
  const service_generator_6 = require("./generators/models/view/service.generator");
76
81
  const meta_1 = require("./lib/meta");
77
82
  const types_1 = require("./lib/schema/types");
83
+ const types_2 = require("./lib/types");
78
84
  const client_path_1 = require("./prisma/client-path");
79
85
  const parse_1 = require("./prisma/parse");
80
86
  // NOTE: This needs to match the constant used in the manager package.
@@ -165,7 +171,6 @@ function generate(_a) {
165
171
  if (root == undefined) {
166
172
  throw new Error(`Could not find lock file. Make sure you are running the generator in a project folder.`);
167
173
  }
168
- const gitignore = lock_1.GitIgnoreUtils.getGitignore(root);
169
174
  const generated = new prettier.ExtendedVirtualFS();
170
175
  // Generate Models
171
176
  for (const model of models) {
@@ -282,9 +287,9 @@ function generate(_a) {
282
287
  },
283
288
  },
284
289
  });
285
- const unfixableErrorCount = lintResult.totalErrorCount - lintResult.totalFixableErrorCount;
286
- if (unfixableErrorCount > 0) {
287
- console.log(`LINT: ${unfixableErrorCount} unfixable errors! Please open an issue with your schema!`);
290
+ const unFixableErrorCount = lintResult.totalErrorCount - lintResult.totalFixableErrorCount;
291
+ if (unFixableErrorCount > 0) {
292
+ console.log(`LINT: ${unFixableErrorCount} non-fixable errors! Please open an issue with your schema!`);
288
293
  }
289
294
  const unfixedWarningCount = lintResult.totalWarningCount - lintResult.totalFixableWarningCount;
290
295
  if (unfixedWarningCount > 0) {
@@ -295,17 +300,20 @@ function generate(_a) {
295
300
  console.log(result.message);
296
301
  }
297
302
  }
298
- if (unfixedWarningCount === 0 && unfixableErrorCount === 0) {
303
+ if (unfixedWarningCount === 0 && unFixableErrorCount === 0) {
299
304
  console.log(`LINT: All files linted successfully without warnings and errors!`);
300
305
  }
301
306
  yield generated.formatUTF8Files(prettier.config);
302
307
  // -------------------------------------------------------------------------
303
- const vfs = yield lock_1.VirtualFS.fromLockFile({
304
- root,
305
- namespace: GENERATOR_NAMESPACE,
308
+ const gitignore = lock_1.GitIgnoreUtils.getGitignore(root);
309
+ const lock = yield lock_1.LockFile.fromProjectRoot({ root, namespace: GENERATOR_NAMESPACE });
310
+ const vfs = yield lock_1.VirtualFS.fromDirectory(root, {
306
311
  // NOTE: We start with a clean slate every time we generate so we can detect
307
312
  // which files were not generated in the last run.
308
- opts: { clean: true },
313
+ clean: true,
314
+ // NOTE: We only load files that may be impacted by the generator.
315
+ include: lock.listManagedFiles(),
316
+ ignore: [lock_1.LockFile.LOCKFILE_PATH],
309
317
  });
310
318
  // Merge with existing files.
311
319
  yield vfs.copy(generated, './');
@@ -316,11 +324,103 @@ function generate(_a) {
316
324
  if (FORCE_ENV) {
317
325
  console.debug('Forcing regeneration of all files due to POSTXL_FORCE_REWRITE=true!');
318
326
  }
319
- const changes = yield vfs.flush(process.cwd(), GENERATOR_NAMESPACE, {
320
- force: config.force || FORCE_ENV,
321
- gitIgnoredFiles: gitignore === null || gitignore === void 0 ? void 0 : gitignore.entries,
322
- });
323
- const log = lock_1.ConsoleUtils.getFilesChangelog(changes.filter((c) => c.status !== 'skipped'));
327
+ const isForceMode = FORCE_ENV || config.force;
328
+ const results = yield vfs.flush(root, (_b) => __awaiter(this, [_b], void 0, function* ({ path, disk, file }) {
329
+ const fileLockEntry = lock.get(path);
330
+ switch (fileLockEntry.status) {
331
+ case 'unauthorized':
332
+ // NOTE: We never override files that are not managed by the generator namespace!
333
+ return 'skip';
334
+ case 'managed': {
335
+ // NOTE: File is detached because developer changed the contents or deleted the file.
336
+ if (
337
+ // NOTE: User deleted the file!
338
+ disk === undefined) {
339
+ // NOTE: Unless the file is deleted because of gitignore or we are in force mode, we skip it.
340
+ if ((gitignore === null || gitignore === void 0 ? void 0 : gitignore.entries.has(path)) || isForceMode) {
341
+ return 'write';
342
+ }
343
+ return 'skip';
344
+ }
345
+ // NOTE: The file exists in the lock so we check whether it's managed or detached.
346
+ const diskFileChecksum = yield lock_1.MiniGit.calculateFileChecksum({ path, content: disk.content });
347
+ if (
348
+ // NOTE: File is detached because developer changed the contents of the file.
349
+ fileLockEntry.checksum !== diskFileChecksum) {
350
+ // NOTE: We only skip detached files when we are not in force mode.
351
+ if (isForceMode) {
352
+ return 'write';
353
+ }
354
+ return 'skip';
355
+ }
356
+ // NOTE: File is managed!
357
+ (0, assert_1.default)(fileLockEntry.checksum === diskFileChecksum && disk !== undefined);
358
+ if (file) {
359
+ const newFileChecksum = yield lock_1.MiniGit.calculateFileChecksum({
360
+ path,
361
+ content: file.kind === 'UTF8-FILE' ? file.utf8Content : file.binaryContent,
362
+ });
363
+ // NOTE: We defensively don't override files with matching checksums unless in force mode
364
+ // because checksum is by design not an injection.
365
+ if (newFileChecksum === fileLockEntry.checksum && !isForceMode) {
366
+ return 'skip';
367
+ }
368
+ return 'write';
369
+ }
370
+ // NOTE: File has been deleted.
371
+ return 'write';
372
+ }
373
+ case 'enoent': {
374
+ // NOTE: File has no lockfile entry.
375
+ const is3rdPartFile = disk !== undefined;
376
+ if (is3rdPartFile && !isForceMode) {
377
+ return 'skip';
378
+ }
379
+ return 'write';
380
+ }
381
+ default:
382
+ throw new types_2.ExhaustiveSwitchCheck(fileLockEntry);
383
+ }
384
+ }), { dryRun: false });
385
+ // NOTE: Lastly we update the lockfile with all changes we've flushed.
386
+ for (const change of results) {
387
+ switch (change.status) {
388
+ case 'write': {
389
+ // NOTE: We update the lockfile entries of the files that were changed.
390
+ if (change.disk === undefined) {
391
+ lock.remove(change.path);
392
+ }
393
+ else {
394
+ const r = yield lock.add({ path: change.path, content: change.disk.content });
395
+ // NOTE: Adding a managed file should never fail.
396
+ (0, assert_1.default)(r != null);
397
+ }
398
+ break;
399
+ }
400
+ case 'skip':
401
+ break;
402
+ default:
403
+ throw new types_2.ExhaustiveSwitchCheck(change);
404
+ }
405
+ }
406
+ yield lock.writeToProjectRoot(root, { dryRun: false });
407
+ // NOTE: Lastly we generate the log of the changes.
408
+ const log = lock_1.ConsoleUtils.getFilesChangelog(results.map((result) => {
409
+ if (result.status === 'write') {
410
+ if (result.prevDisk === undefined) {
411
+ return { path: result.path, status: 'new' };
412
+ }
413
+ if (result.disk === undefined) {
414
+ return { path: result.path, status: 'deleted' };
415
+ }
416
+ if (runtime_1.BufferUtils.equals(result.prevDisk.content, result.disk.content) &&
417
+ result.prevDisk.mode === result.disk.mode) {
418
+ return { path: result.path, status: 'unchanged' };
419
+ }
420
+ return { path: result.path, status: 'changed' };
421
+ }
422
+ return { path: result.path, status: 'skipped' };
423
+ }));
324
424
  console.info(log);
325
425
  });
326
426
  }
@@ -26,9 +26,11 @@ function generateImportService({ models, meta }) {
26
26
  [meta.actions.execution.interfaceLocation.import]: [meta.actions.execution.interface],
27
27
  [meta.actions.dispatcher.classLocation.import]: [meta.actions.dispatcher.class],
28
28
  [types.location.path]: [
29
+ (0, types_1.toAnnotatedTypeName)(delta),
29
30
  (0, types_1.toAnnotatedTypeName)(delta_Fields),
30
31
  (0, types_1.toAnnotatedTypeName)(delta_Model.type),
31
- (0, types_1.toAnnotatedTypeName)(delta),
32
+ (0, types_1.toAnnotatedTypeName)(delta_Model.errors.invalidReference.type),
33
+ (0, types_1.toAnnotatedTypeName)(delta_Model.errors.isRequiredDependency.type),
32
34
  ],
33
35
  [decoder.location.path]: [(0, types_1.toAnnotatedTypeName)(decoder.decodedPXLModelDataTypeName)],
34
36
  [converterFunctions.location.path]: [converterFunctions.deltaToBulkMutations],
@@ -266,10 +268,10 @@ export class ${meta.import.importService.name} {
266
268
  /**
267
269
  * Internal helper function that removes undefined values from an array.
268
270
  */
269
- private keepErrors<ModelErrors>(errors: (ModelErrors | undefined)[]): ModelErrors[] {
270
- return errors.filter((error) => error !== undefined) as ModelErrors[]
271
+ private keepErrors<O, I extends O | undefined>(errors: I[]): O[] {
272
+ return errors.filter((error) => error !== undefined) as O[]
271
273
  }
272
-
274
+
273
275
  private validateRequiredFields<
274
276
  Model extends ${dto.genericModel}<ID>,
275
277
  ID extends ${dto.idType},
@@ -492,10 +494,6 @@ function generateDetectDeltaFunction({ model, modelMeta, models, schemaMeta, imp
492
494
  }
493
495
  else if (field.kind === 'relation') {
494
496
  const relatedModelMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
495
- imports.addTypeImport({
496
- from: schemaMeta.import.types.location.path,
497
- items: [delta_Model.errors.invalidReference.type],
498
- });
499
497
  sharedValidations.push(`this.validateReferenceField({
500
498
  item,
501
499
  fieldName: '${fieldName}',
@@ -515,10 +513,6 @@ function generateDetectDeltaFunction({ model, modelMeta, models, schemaMeta, imp
515
513
  !relatedField.isRequired) {
516
514
  continue;
517
515
  }
518
- imports.addTypeImport({
519
- from: schemaMeta.import.types.location.path,
520
- items: [delta_Model.errors.isRequiredDependency.type],
521
- });
522
516
  const relatedModelMeta = (0, meta_1.getModelMetadata)({ model: relatedModel });
523
517
  const relatedFieldName = (0, string_1.toPascalCase)(relatedField.name);
524
518
  deleteValidations.push(`
@@ -566,12 +560,13 @@ private async detect${modelMeta.internalSingularNameCapitalized}Delta(
566
560
  existingItem,
567
561
  properties: [${fieldNames.join(',')}],
568
562
  }),
569
- validateCreate: async ({ item }) =>
563
+ validateCreate: async ({ item }): Promise<${modelMeta.import.delta_Model_Errors}[]> =>
570
564
  this.keepErrors([
571
565
  ${requiredFieldsValidation}
572
566
  ...(await sharedValidations(item)),
573
567
  ]),
574
- validateUpdate: async ({ item }) => this.keepErrors(await sharedValidations(item)),
568
+ validateUpdate: async ({ item }): Promise<${modelMeta.import.delta_Model_Errors}[]> =>
569
+ this.keepErrors(await sharedValidations(item)),
575
570
  validateDelete: ${validateDelete},
576
571
  })
577
572
  }`;
@@ -831,7 +831,7 @@ function generateValidationBlocks({ model }) {
831
831
  function generateIndexBlocks({ model, imports }) {
832
832
  const indexes = model.attributes.index ? [getIndexDefinition({ fieldNames: model.attributes.index, model })] : [];
833
833
  if (indexes.length > 0) {
834
- imports.addTypeImport({ from: (0, types_1.toPackageName)('@postxl/runtime'), items: [(0, types_1.toTypeName)('NestedMap')] });
834
+ imports.addImport({ items: [(0, types_1.toClassName)('NestedMap')], from: (0, types_1.toPackageName)('@postxl/runtime') });
835
835
  }
836
836
  const result = {
837
837
  nestedMapDeclarations: [],
@@ -241,6 +241,9 @@ function parseModel({ dmmfModel, enums, models, config, }) {
241
241
  validation = { type: 'float' };
242
242
  }
243
243
  const _field = Object.assign(Object.assign({ kind: 'scalar', validation }, shared), { isUnique: isUniqueField(dmmfField), isGenerated: isAutoIncrementField(dmmfField), tsTypeName: getTsTypeForScalar(dmmfField) });
244
+ if (_field.tsTypeName === 'boolean' && !_field.isRequired) {
245
+ (0, error_1.throwError)(`${(0, logger_1.highlight)(`${dmmfModel.name}.${fieldName}`)}: Boolean fields cannot be nullable!`);
246
+ }
244
247
  return _field;
245
248
  }
246
249
  if (dmmfField.kind === 'enum') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.71.1",
3
+ "version": "0.72.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {
@@ -19,10 +19,10 @@
19
19
  "@prisma/internals": "5.8.1",
20
20
  "exceljs": "4.3.0",
21
21
  "fast-glob": "3.2.12",
22
- "remeda": "1.9.4",
23
22
  "zod": "3.22.2",
24
- "@postxl/lock": "1.4.2",
25
- "@postxl/prettier": "0.0.2"
23
+ "@postxl/lock": "1.5.0",
24
+ "@postxl/prettier": "0.1.0",
25
+ "@postxl/runtime": "0.1.3"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@jest/globals": "29.7.0",
@@ -40,11 +40,9 @@
40
40
  "peerDependencies": {
41
41
  "prisma": "5.8.1"
42
42
  },
43
- "wallaby": {
44
- "autoDetect": true
45
- },
46
43
  "scripts": {
47
- "test:generators": "./scripts/test.sh",
44
+ "test:generators": "./scripts/test-generators.sh",
45
+ "test:setup": "./scripts/test-setup.sh",
48
46
  "test:jest": "jest",
49
47
  "test:watch": "jest --watch",
50
48
  "test:types": "tsc --noEmit"