Package mutation utilities
Describe the feature
To provide a lighter alternative to the npmcli libraries, we could provide the common mutation utilities and leave out the rest:
Features
- Add/remove dependencies from prod, dev, peer, optional peer
- Partial updates
- Basic normalisation
- Automatically sort dependency lists
Implementation
We can probably also follow a class based approach like npm did:
type DependencyType = 'peer' | 'optionalPeer' | 'dev' | 'prod';
declare class PackageJson {
addDependency(
name: string,
version: string,
type?: DependencyType
): void;
removeDependency(
name: string,
type?: DependencyType
): void;
update(
partial: Partial<PackageJson>
): void;
save(): Promise<void>;
}
declare function loadPackageJson(path: string): Promise<PackageJson>;
internally, save can do basic normalisation:
- sort the dependency lists
- set
nameto be''if it isn't set - set
versionto be''if it isn't set
Given we already have PackageJson (type), we could name this PackageJsonManager or some such thing, and have a convenience constructor/util to new one up from a PackageJson (as that's also what we'd store internally).
Additional information
- [ ] Would you be willing to help implement this feature?
This is now implemented by #240
We might still introduce addDependency and removeDependency utils BTW just have to do more research about ecosystem needs.
Feedbacks more than welcome @MichaelDeBoey what you feel missing with current state.
@pi0 As mentioned in https://github.com/unjs/pkg-types/pull/240#issuecomment-3175361169, these utilities could be a perfect alternative for @npmcli/package-json usage in React Router without much changes to the current code
- https://github.com/remix-run/react-router/blob/a4644d37e4b891486e094479bc7c33dd538dd7d5/packages/react-router-dev/cli/commands.ts
- https://github.com/remix-run/react-router/blob/a4644d37e4b891486e094479bc7c33dd538dd7d5/packages/react-router-dev/config/config.ts
- https://github.com/remix-run/indie-stack/blob/56abb93bf81f635b574d9ca23eed05602699458a/remix.init/index.js
- https://github.com/remix-run/blues-stack/blob/7390646d6f3b6e6cb7ef722e4b61cdbffb997afc/remix.init/index.js
- https://github.com/remix-run/grunge-stack/blob/c25307668648b4944e5cd7815c78c098d67ea322/remix.init/index.js
Adding convenience methods like removePackageJSONScript could replace code like seen in https://github.com/remix-run/grunge-stack/blob/c25307668648b4944e5cd7815c78c098d67ea322/remix.init/index.js#L51-L64
But I would understand that doing pkg.scripts.scriptToRemove = undefined would be the preferred solution.
Apart from normalizing, Utilities for managing dependencies (without involving package managers), particularly, might make real value IMO, like @43081j's initial draft for controlling peer/meta, etc. We could also add some registry-aware features like resolving tags and auto-adding peer deps with a simple fetch to it.
Adding more utils for all the fields might be hard to maintain and add to the package size, while it could be done with simple one-line JS code already.
Currently, we only expose the core utility updatePackage, which allows running any kind of changes in one atomic read/write run and preserving format.
@MichaelDeBoey I think your above example could migrate to something like this. How does it sound to you:
import { updatePackage } from 'pkg-types'
const updatePackageJson = async ({ APP_NAME, packageJson }) => {
await updatePackage('package.json', pkg => {
pkg.scripts["format:repo"] = _repoFormatScript
pkg.name = APP_NAME
})
};
I think your above example could migrate to something like this. How does it sound to you:
import { updatePackage } from 'pkg-types' const updatePackageJson = async ({ APP_NAME, packageJson }) => { await updatePackage('package.json', pkg => { pkg.scripts["format:repo"] = _repoFormatScript pkg.name = APP_NAME }) };
@pi0 Actually, it doesn't, since pkg.scripts["format:repo"] is supposed to be removed.
The way that's currently done is by getting it as _repoFormatScript and putting the rest in scripts due to the rest operator.
After that we overwrite the original pkg.scripts with scripts
So it would actually be
import { updatePackage } from 'pkg-types'
const updatePackageJson = async ({ APP_NAME, packageJson }) =>
updatePackage('package.json', (pkg) => {
{ "format:repo": _repoFormatScript, ...scripts } = pkg.scripts
pkg.scripts = scripts
pkg.name = APP_NAME
});
So the whole destructing thing is what I actually wanted to avoid with a util like removePackageScript.
Having something like @43081j suggested where you could just set it to undefined and it would be removed, would be a nice alternative if you ask me
import { updatePackage } from 'pkg-types'
const updatePackageJson = async ({ APP_NAME, packageJson }) =>
updatePackage('package.json', (pkg) => {
pkg.scripts["format:repo"] = undefined
pkg.name = APP_NAME
});
Ah i might have misread your ref code. Yeah you can totally do that and assign = undefined. During JSON serialization it will be dropped.