@jsenv/monorepo 0.0.2 → 0.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/monorepo",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Helpers to manage packages in a monorepo",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "@jsenv/urls": "2.2.1",
38
38
  "@jsenv/filesystem": "4.3.2",
39
- "@jsenv/package-publish": "1.10.3",
40
- "@jsenv/log": "3.4.1",
39
+ "@jsenv/package-publish": "1.10.4",
40
+ "@jsenv/log": "3.4.2",
41
41
  "semver": "7.5.4"
42
42
  }
43
43
  }
package/readme.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  Helpers to manage multiple packages from a single repository. For example when using [NPM workspaces](https://docs.npmjs.com/cli/v8/using-npm/workspaces).
4
4
 
5
- ## Updating a package
5
+ This packages helps to perform 2 tasks that are a bit painful to do "by hand" inside a monorepo: "publish a new version" and "upgrade dependencies".
6
6
 
7
- Updating a package in a workspace by hand is time consuming and error prone. Let's see it with a basic example where a workspace contains two packages and you make a change to one of them.
7
+ ## Publish a new version
8
+
9
+ Publishing a new version of a package in a monorepo by hand is time consuming and error prone. It's because you have to ensure packages versions are properly updated according to their inter-dependency. Let's see it with a basic example where a monorepo contains two packages and you make a change to one of them.
8
10
 
9
11
  _packages/main/package.json:_
10
12
 
@@ -51,7 +53,7 @@ At this point you are supposed to update "packages/main/package.json" like this:
51
53
  }
52
54
  ```
53
55
 
54
- In a workspace with many packages this is hard to do correctly and time consuming. You can automate the painful part as follows:
56
+ In a monorepo with many packages this is hard to do correctly and time consuming. You can automate the painful part as follows:
55
57
 
56
58
  1. Run _syncPackagesVersions_
57
59
  2. Review changes with a tool like "git diff"
@@ -62,7 +64,7 @@ In a workspace with many packages this is hard to do correctly and time consumin
62
64
  _syncPackagesVersions_ is an async function ensuring versions in all package.json are in sync for all packages in the workspace. It update versions in "dependencies", "devDependencies" and increase "version" if needed. This ensure all versions are in sync before publishing.
63
65
 
64
66
  ```js
65
- import { syncPackagesVersions } from "@jsenv/package-workspace";
67
+ import { syncPackagesVersions } from "@jsenv/monorepo";
66
68
 
67
69
  await syncPackagesVersions({
68
70
  directoryUrl: new URL("./", import.meta.url),
@@ -73,12 +75,12 @@ await syncPackagesVersions({
73
75
 
74
76
  Each package might need to increase their package.json "version" differently. When it's required _syncPackagesVersions_ increases PATCH number ("1.0.3" becomes "1.0.4"). After that it's up to you to review these changes to decide if you keep PATCH increment or want to increment MINOR or MAJOR instead.
75
77
 
76
- ## publishPackages
78
+ ### publishPackages
77
79
 
78
- _publishPackages_ is an async function that will publish all packages in the workspace on NPM. But only the packages that are not already published.
80
+ _publishPackages_ is an async function that will publish all packages in the monorepo on NPM. But only the packages that are not already published.
79
81
 
80
82
  ```js
81
- import { publishPackages } from "@jsenv/package-workspace";
83
+ import { publishPackages } from "@jsenv/monorepo";
82
84
 
83
85
  process.env.NPM_TOKEN = "token_auhtorized_to_publish_on_npm";
84
86
 
@@ -86,3 +88,91 @@ await publishPackages({
86
88
  directoryUrl: new URL("./", import.meta.url),
87
89
  });
88
90
  ```
91
+
92
+ ## Upgrade dependencies
93
+
94
+ As a maintainer of a package with many dependencies you periodically want to check if there is new versions of your dependencies to stay up to date. We'll first see what NPM packages are usually doing and why it's a problem. Then we'll see how to avoid that problem. Finally we'll see how to use a function to upgrade dependencies because it can be a bit time consuming to do by hand.
95
+
96
+ NPM introduced what usage of ^ or "\*" in your _package.json_.
97
+
98
+ ```json
99
+ {
100
+ "dependencies": {
101
+ "foo": "^1.0.0"
102
+ }
103
+ }
104
+ ```
105
+
106
+ But it causes a problem.
107
+
108
+ ### The problem
109
+
110
+ As a result "npm install" auto updates to latest versions if any is found. In the end any npm install can change the behaviour of your code if a new version was published since the last npm install.
111
+
112
+ The sequence of events looks as below
113
+
114
+ ```console
115
+ [7h00] npm install
116
+ [7h01] npm downloads `foo@1.1.0`
117
+ [7h30] `foo@1.2.0` is published on NPM
118
+ [8h00] npm install
119
+ [8h01] npm downloads `foo@1.2.0`
120
+ ```
121
+
122
+ Whenever you or someone else ends up with foo version `1.2.0` it can break the code or lead to different code behaviour. People will loose time trying to understand what's going on only to realize it comes from the new version.
123
+
124
+ ### But package-lock.json fixes that right?
125
+
126
+ _package-lock.json_ fixes that but only if you run `npm ci`.
127
+ And people are still used to start a project using `npm install + npm start`.
128
+
129
+ The problem is that `npm install` does too many things.
130
+ Most of the time you don't want to update your deps. The 2 most common scenarios are:
131
+
132
+ - "I want want to install deps on a fresh project"
133
+ - "I want to ensure my deps are in sync after git pull in a branch"
134
+
135
+ "I want to update all my deps" happens from time to time but is usually not what you had in mind before executing "npm install"
136
+
137
+ Moreover the usage of _package-lock.json_ remains optional.
138
+
139
+ ## How to avoid the problem
140
+
141
+ Use explicit version in the package.json
142
+
143
+ BAD
144
+
145
+ ```json
146
+ {
147
+ "dependencies": {
148
+ "foo": "^1.0.0",
149
+ "bar": "2.*"
150
+ }
151
+ }
152
+ ```
153
+
154
+ GOOD
155
+
156
+ ```json
157
+ {
158
+ "dependencies": {
159
+ "foo": "1.1.3",
160
+ "bar": "2.0.0"
161
+ }
162
+ }
163
+ ```
164
+
165
+ As a result there is no ambiguity on the version being used and we know the exact version in the glimpse of an eye.
166
+ **You control when the version gets updated**
167
+
168
+ Once versions are fixed you can update whenever you want by running "npm outdated" and decide what to update by hand.
169
+
170
+ But inside large codebases with a lot of packages this process takes time, you can use the following function to perform "npm outdated" + update the versions in the package.json
171
+
172
+ ```js
173
+ import { upgradeExternalVersions } from "@jsenv/monorepo";
174
+
175
+ await upgradeExternalVersions({
176
+ directoryUrl: new URL("./", import.meta.url),
177
+ });
178
+ ```
@@ -1,3 +1,14 @@
1
+ /*
2
+ * Try to upgrade all packages that are external to a monorepo.
3
+ * - "external" means a package that is not part of the monorepo
4
+ * - "upgrade" means check if there is a more recent version on NPM registry
5
+ * and if yes, update the version in the package.json
6
+ *
7
+ * Versions declared in "dependencies", "devDependencies"
8
+ *
9
+ * Be sure to check ../readme.md#upgrade-dependencies
10
+ */
11
+
1
12
  import { UNICODE, createTaskLog } from "@jsenv/log";
2
13
  import { fetchLatestInRegistry } from "@jsenv/package-publish/src/internal/fetchLatestInRegistry.js";
3
14
 
@@ -26,6 +37,11 @@ export const upgradeExternalVersions = async ({ directoryUrl }) => {
26
37
  ) {
27
38
  return;
28
39
  }
40
+ // "*" means package accept anything
41
+ // so there is no need to update it, it's always matching the latest version
42
+ if (version === "*") {
43
+ return;
44
+ }
29
45
  const existing = externalPackages[name];
30
46
  if (existing) {
31
47
  externalPackages[name].push({
@@ -45,11 +61,7 @@ export const upgradeExternalVersions = async ({ directoryUrl }) => {
45
61
  for (const internalPackageName of Object.keys(internalPackages)) {
46
62
  const internalPackage = internalPackages[internalPackageName];
47
63
  const internalPackageObject = internalPackage.packageObject;
48
- const {
49
- dependencies = {},
50
- devDependencies = {},
51
- peerDependencies = {},
52
- } = internalPackageObject;
64
+ const { dependencies = {}, devDependencies = {} } = internalPackageObject;
53
65
  const dependencyNames = Object.keys(dependencies);
54
66
  dependencyNames.forEach((dependencyName) => {
55
67
  addExternalPackage({
@@ -65,16 +77,7 @@ export const upgradeExternalVersions = async ({ directoryUrl }) => {
65
77
  internalPackageName,
66
78
  type: "devDependencies",
67
79
  name: devDependencyName,
68
- version: dependencies[devDependencyName],
69
- });
70
- });
71
- const peerDependencyNames = Object.keys(peerDependencies);
72
- peerDependencyNames.forEach((peerDependencyName) => {
73
- addExternalPackage({
74
- internalPackageName,
75
- type: "peerDependencies",
76
- name: peerDependencyName,
77
- version: dependencies[peerDependencyName],
80
+ version: devDependencies[devDependencyName],
78
81
  });
79
82
  });
80
83
  }
@@ -115,7 +118,7 @@ export const upgradeExternalVersions = async ({ directoryUrl }) => {
115
118
  const packageFilesToUpdate = {};
116
119
  const updates = [];
117
120
  for (const externalPackageName of externalPackageNames) {
118
- const externalPackageRefs = externalPackageNames[externalPackageName];
121
+ const externalPackageRefs = externalPackages[externalPackageName];
119
122
  for (const externalPackageRef of externalPackageRefs) {
120
123
  const internalPackageName = externalPackageRef.internalPackageName;
121
124
  const internalPackageDeps =
@@ -151,11 +154,13 @@ export const upgradeExternalVersions = async ({ directoryUrl }) => {
151
154
  internalPackage.updateFile(internalPackage.packageObject);
152
155
  });
153
156
  if (updates.length === 0) {
154
- console.log(`${UNICODE.OK} all versions in package.json files are in sync`);
157
+ console.log(
158
+ `${UNICODE.OK} all versions declared in package.json files are in up-to-date with registry`,
159
+ );
155
160
  } else {
156
161
  console.log(
157
162
  `${UNICODE.INFO} ${updates.length} versions modified in package.json files
158
- Use a tool like "git diff" to review these changes then run "npm install"`,
163
+ Use a tool like "git diff" to review these changes then run "npm install"`,
159
164
  );
160
165
  }
161
166
  return updates;