-
-
Notifications
You must be signed in to change notification settings - Fork 3
Fix package manifest validation and typing #25
Conversation
e7a03a7
to
3213774
Compare
3213774
to
86ec0ec
Compare
src/package-operations.ts
Outdated
export function validatePackageManifest( | ||
manifest: Partial<PackageManifest>, | ||
export function validatePackageManifest< | ||
T extends Partial<PackageManifest>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remain a bit confused about why these generic parameters are necessary. It seems that there are two types of manifests that we want to validate: a monorepo manifest, and a non-monorepo manifest. Why do we need two generic parameters, and to pass in which fields to validate? This all seems a bit... overly complicated? Maybe I'm missing something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was complicated by the fact that there's effectively three different types of package manifests, going from subset to superset.
The first is the root manifest, which could be either a normal or a monorepo package.json
file. There, we only need to validate the version
. For the polyrepo case, we also need to validate name
. For the monorepo case, we need to also validate private
and workspaces
. On top of that, deeper in the monorepo workflow, we need to validate version
and name
for the manifests of the monorepo's packages.
Rather than keeping a single generic function around, I split the validation logic out of getPackageManifest
and created separate validation function for all the different cases. Most of these new functions use a single generic, but it's a convenient to take in e.g. a Partial
manifest object and return it with e.g. one field correctly type.
I think this is probably better, because the generic validation function kind of sucked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of what I was missing is that you were aiming to perform the absolute minimum validation necessary. I would have probably used just a single validation function that validated name
and version
, and also conditionally validated workspaces
and private
if they existed (and that private
was true
if workspaces
was present). That'd give us one PackageManifest
type. Then a simple check that workspaces
existed would give us a second type, MonorepoPackageManifest
.
But skipping the name validation for the root does make that a bit more challenging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But skipping the name validation for the root does make that a bit more challenging.
Yeah, validating the name
would require that we mandate that monorepo root manifests have a placeholder name field, which seems kind of bad.
src/package-operations.ts
Outdated
@@ -313,101 +320,132 @@ function getUpdatedDependencyField( | |||
* @param fieldsToValidate - The manifest fields that will be validated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description requires updating, now that the validation isn't being done here.
src/package-operations.ts
Outdated
export function validatePackageManifest( | ||
manifest: Partial<PackageManifest>, | ||
export function validatePackageManifestVersion< | ||
T extends Partial<PackageManifest> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: A JSDoc description of this would be helpful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same goes for the other generics introduced in this PR
src/package-operations.ts
Outdated
@@ -31,9 +31,12 @@ export enum FieldNames { | |||
export interface PackageManifest | |||
extends Partial<Record<PackageDependencyFields, Record<string, string>>> { | |||
readonly [FieldNames.Name]: string; | |||
readonly [FieldNames.Private]?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove these? These are valid properties of a package manifest. Removing these forces you to use MonorepoPackageManifest
for any situation where you might receive either type of manifest (e.g. in getManifestErrorMessagePrefix
), which is semantically confusing because it wrongly implies that a non-monorepo manifest isn't expected.
src/package-operations.ts
Outdated
@@ -31,9 +31,12 @@ export enum FieldNames { | |||
export interface PackageManifest | |||
extends Partial<Record<PackageDependencyFields, Record<string, string>>> { | |||
readonly [FieldNames.Name]: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MonorepoPackageManifest
is used without validating the name, yet the type declares that the name exists. This mistake is missed by TypeScript because of the as MonorepoPackageManifest
cast.
If you really want to skip validating the name, this should be made optional.
src/package-operations.ts
Outdated
}`, | ||
); | ||
} | ||
return manifest as MonorepoPackageManifest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast can be avoided by using type guards for the conditions above.
src/update.ts
Outdated
@@ -110,7 +119,7 @@ async function updateMonorepo( | |||
newVersion: string, | |||
versionDiff: SemverReleaseType, | |||
rootManifest: Pick< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this use the MonorepoPackageManifest
type directly instead, rather than picking just those two properties?
I think I've addressed all outstanding comments in 51be3ed. The type guards are more verbose, but we avoid casting using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
This PR improves the validation and type checking of package manifest objects. Previously, we were casting package manifest objects and using
Partial
needlessly. Now, thevalidatePackageManifest
function acts as a type guard by returning the unmodified package manifest object, but with correct types.As part of this improved validation,
readJsonFile
has been renamed toreadJsonObjectFile
and rewritten to error if the parsed JSON is anything other than a plain object, as opposed to just a falsy value.