@cubing/dev-config 0.6.2 → 0.6.4

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 (2) hide show
  1. package/bin/package.json.ts +93 -31
  2. package/package.json +1 -1
@@ -155,7 +155,7 @@ try {
155
155
  break;
156
156
  }
157
157
  default:
158
- throw new Error("Invalid command.") as never;
158
+ throw new Error("Invalid subcommand.") as never;
159
159
  }
160
160
  }
161
161
 
@@ -260,8 +260,11 @@ function field<T>(
260
260
  optional?: boolean;
261
261
  additionalChecks?: { [requirementMessage: string]: (t: T) => boolean };
262
262
  skipPrintingSuccess?: boolean;
263
+ mustBePopulatedMessage?: string;
263
264
  },
264
265
  ) {
266
+ const mustBePopulatedMessage = () =>
267
+ options?.mustBePopulatedMessage ?? "Field must be populated.";
265
268
  const { breadcrumbString, maybeValue } = traverse(breadcrumbs);
266
269
  if (!maybeValue) {
267
270
  if (options?.optional) {
@@ -270,7 +273,7 @@ function field<T>(
270
273
  }
271
274
  return;
272
275
  } else {
273
- console.log(`❌ ${breadcrumbString} — Must be present.`);
276
+ console.log(`❌ ${breadcrumbString} — ${mustBePopulatedMessage()}`);
274
277
  exitCode = 1;
275
278
  return;
276
279
  }
@@ -294,9 +297,11 @@ function field<T>(
294
297
  }
295
298
  } else {
296
299
  if (category === "undefined") {
297
- console.log(`❌ ${breadcrumbString} — Must be present.`);
300
+ console.log(`❌ ${breadcrumbString} — ${mustBePopulatedMessage()}.`);
298
301
  } else if (type === "undefined") {
299
- console.log(`❌ ${breadcrumbString} — Present (but must not be).`);
302
+ console.log(
303
+ `❌ ${breadcrumbString} — Field is populated (but must not be).`,
304
+ );
300
305
  } else {
301
306
  if (Array.isArray(type)) {
302
307
  console.log(
@@ -313,7 +318,7 @@ function field<T>(
313
318
  }
314
319
  }
315
320
 
316
- function mustNotBePresent(breadcrumbs: Breadcrumbs) {
321
+ function mustNotBePopulated(breadcrumbs: Breadcrumbs) {
317
322
  const { breadcrumbString, maybeValue } = traverse(breadcrumbs);
318
323
  if (maybeValue) {
319
324
  console.log(`❌ ${breadcrumbString} — Must not be present.`);
@@ -393,6 +398,19 @@ field(["type"], "string", {
393
398
  'Type must be `"module"`.': (type: string) => type === "module",
394
399
  },
395
400
  });
401
+ if ("main" in packageJSON || "types" in packageJSON) {
402
+ field(["main"], "string", {
403
+ mustBePopulatedMessage: 'Must be populated if "types" is populated.',
404
+ });
405
+ field(["types"], "string", {
406
+ mustBePopulatedMessage: 'Must be populated if "main" is populated.',
407
+ });
408
+ } else {
409
+ console.log("☑️ .main");
410
+ console.log("☑️ .types");
411
+ }
412
+ mustNotBePopulated(["module"]);
413
+ mustNotBePopulated(["browser"]);
396
414
  field(["exports"], "object");
397
415
  field(["bin"], "object", { optional: true });
398
416
  field(["dependencies"], "object", { optional: true });
@@ -407,15 +425,6 @@ field(["files"], "array");
407
425
  field(["scripts"], "object");
408
426
  // Set to `"# no-op"` if needed.
409
427
  field(["scripts", "prepublishOnly"], "string");
410
- if ("main" in packageJSON || "types" in packageJSON) {
411
- field(["main"], "string");
412
- field(["types"], "string");
413
- } else {
414
- console.log("☑️ .main");
415
- console.log("☑️ .types");
416
- }
417
- mustNotBePresent(["module"]);
418
- mustNotBePresent(["browser"]);
419
428
 
420
429
  console.log("Checking paths of binaries and exports:");
421
430
 
@@ -485,7 +494,7 @@ function checkPath(
485
494
  break;
486
495
  }
487
496
  default:
488
- throw new Error("Invalid command.") as never;
497
+ throw new Error("Invalid subcommand.") as never;
489
498
  }
490
499
  }
491
500
  }
@@ -500,14 +509,14 @@ function checkPath(
500
509
  // TODO: allow folders (with a required trailing slash)?
501
510
  if (!(await resolvedPath.existsAsFile())) {
502
511
  exitCode = 1;
503
- return `❌ ${breadcrumbString} — Missing — ${value}`;
512
+ return `❌ ${breadcrumbString} — Path is not present on disk. — ${value}`;
504
513
  }
505
514
  if (options.mustBeExecutable) {
506
515
  if (!((await resolvedPath.stat()).mode ^ constants.X_OK)) {
507
516
  // This is not considered fixable because the binary may be the output
508
517
  // of a build process. In that case, the build process is responsible
509
518
  // for marking it as executable.
510
- return `❌ ${breadcrumbString} — Must be executable — ${value}`;
519
+ return `❌ ${breadcrumbString} — File at path must be executable — ${value}`;
511
520
  }
512
521
  }
513
522
  return `✅ ${breadcrumbString} — OK — ${value}`;
@@ -520,14 +529,15 @@ checkPath(["types"], { expectPrefix: ResolutionPrefix.Relative });
520
529
  checkPath(["module"], { expectPrefix: ResolutionPrefix.Relative });
521
530
  checkPath(["browser"], { expectPrefix: ResolutionPrefix.Relative });
522
531
 
523
- if (packageJSON.exports) {
524
- for (const [exportPath, value] of Object.entries(packageJSON.exports)) {
532
+ const { exports } = packageJSON;
533
+ if (exports) {
534
+ for (const [subpath, value] of Object.entries(exports)) {
525
535
  if (!value) {
526
536
  // biome-ignore lint/complexity/noUselessContinue: Explicit control flow.
527
537
  continue;
528
538
  } else if (typeof value === "string") {
529
539
  // TODO: error?
530
- checkPath(["exports", [exportPath]], {
540
+ checkPath(["exports", [subpath]], {
531
541
  expectPrefix: ResolutionPrefix.Relative,
532
542
  });
533
543
  } else if (value === null) {
@@ -542,25 +552,70 @@ if (packageJSON.exports) {
542
552
 
543
553
  checks.push(
544
554
  (async () => {
545
- const { breadcrumbString } = traverse(["exports", [exportPath]]);
555
+ const { breadcrumbString } = traverse(["exports", [subpath]]);
556
+ const fixingLines = [];
546
557
  const orderingErrorLines = [];
547
558
  /**
548
559
  * https://nodejs.org/api/packages.html#conditional-exports
549
560
  */
561
+ let updateKeys = false;
550
562
  if (keys.includes("types")) {
551
563
  if (keys[0] !== "types") {
552
- orderingErrorLines.push(
553
- ` ↪ "types" must be the first export if present.`,
554
- );
564
+ switch (subcommand) {
565
+ case "check": {
566
+ orderingErrorLines.push(
567
+ ` ↪ "types" must be the first export if present — 📝 fixable!`,
568
+ );
569
+ break;
570
+ }
571
+ case "format": {
572
+ fixingLines.push(
573
+ ` ↪ "types" must be the first export if present — 📝 fixing!`,
574
+ );
575
+ keys.splice(keys.indexOf("types"), 1);
576
+ keys.splice(0, 0, "types");
577
+ updateKeys = true;
578
+ break;
579
+ }
580
+ default:
581
+ throw new Error("Invalid subcommand.") as never;
582
+ }
555
583
  }
556
584
  }
557
585
  if (keys.includes("default")) {
558
586
  if (keys.at(-1) !== "default") {
559
- orderingErrorLines.push(
560
- ` ↪ "default" must be the last export if present.`,
561
- );
587
+ switch (subcommand) {
588
+ case "check": {
589
+ orderingErrorLines.push(
590
+ ` ↪ "default" must be the last export if present — 📝 fixable!`,
591
+ );
592
+ break;
593
+ }
594
+ case "format": {
595
+ fixingLines.push(
596
+ ` ↪ "default" must be the last export if present — 📝 fixing!`,
597
+ );
598
+ keys.splice(keys.indexOf("default"), 1);
599
+ keys.push("default");
600
+ updateKeys = true;
601
+ break;
602
+ }
603
+ default:
604
+ throw new Error("Invalid subcommand.") as never;
605
+ }
562
606
  }
563
607
  }
608
+ if (updateKeys) {
609
+ // TODO: avoid type wrangling.
610
+ const newConditionalExports: Record<string, string> = {};
611
+ for (const key of keys) {
612
+ newConditionalExports[key] = (value as Record<string, string>)[
613
+ key
614
+ ];
615
+ }
616
+ (exports as Record<string, Record<string, string>>)[subpath] =
617
+ newConditionalExports;
618
+ }
564
619
  for (const key of keys) {
565
620
  // Note `"require"` is *emphatically not allowed*.
566
621
  if (!["types", "import", "default"].includes(key)) {
@@ -569,19 +624,26 @@ if (packageJSON.exports) {
569
624
  );
570
625
  }
571
626
  }
572
- if (orderingErrorLines) {
573
- exitCode = 0;
627
+ if (orderingErrorLines.length > 0) {
628
+ exitCode = 1;
574
629
  return [
575
630
  `❌ ${breadcrumbString} — Invalid keys:`,
576
631
  ...orderingErrorLines,
577
632
  ].join("\n");
578
633
  } else {
579
- return `✅ ${breadcrumbString} Key set and ordering is okay..`;
634
+ if (fixingLines.length > 0) {
635
+ return [
636
+ `✅ ${breadcrumbString} — Fixing key ordering:`,
637
+ ...fixingLines,
638
+ ].join("\n");
639
+ } else {
640
+ return `✅ ${breadcrumbString} — Key set and ordering is okay.`;
641
+ }
580
642
  }
581
643
  })(),
582
644
  );
583
645
  for (const secondaryKey of keys) {
584
- checkPath(["exports", [exportPath], secondaryKey], {
646
+ checkPath(["exports", [subpath], secondaryKey], {
585
647
  expectPrefix: ResolutionPrefix.Relative,
586
648
  });
587
649
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubing/dev-config",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Common dev configs for projects.",
5
5
  "author": {
6
6
  "name": "Lucas Garron",