@jsenv/monorepo 0.4.12 → 0.4.13

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.4.12",
3
+ "version": "0.4.13",
4
4
  "type": "module",
5
5
  "description": "Helpers to manage packages in a monorepo",
6
6
  "repository": {
@@ -23,10 +23,10 @@
23
23
  "/src/"
24
24
  ],
25
25
  "dependencies": {
26
- "@jsenv/filesystem": "4.15.27",
27
- "@jsenv/humanize": "1.8.5",
28
- "@jsenv/package-publish": "1.11.58",
29
- "@jsenv/urls": "2.9.16",
26
+ "@jsenv/filesystem": "4.15.28",
27
+ "@jsenv/humanize": "1.8.6",
28
+ "@jsenv/package-publish": "1.11.59",
29
+ "@jsenv/urls": "2.9.17",
30
30
  "semver": "7.8.5"
31
31
  },
32
32
  "publishConfig": {
@@ -0,0 +1,73 @@
1
+ import { createTaskLog } from "@jsenv/humanize";
2
+ import {
3
+ checkVersionStatusInRegistry,
4
+ VERSION_STATUS,
5
+ } from "@jsenv/package-publish/src/internal/staged_version.js";
6
+ import {
7
+ compareTwoPackageVersions,
8
+ VERSION_COMPARE_RESULTS,
9
+ } from "./compare_two_package_versions.js";
10
+
11
+ /*
12
+ * The workspace packages whose version is not published on the registry, each
13
+ * with its status there: absent, or staged (the registry has it but does not
14
+ * expose it yet)
15
+ */
16
+ export const collectUnpublishedPackages = async ({
17
+ workspacePackages,
18
+ registryLatestVersions,
19
+ registryUrl,
20
+ token,
21
+ }) => {
22
+ const aheadPackageNames = Object.keys(workspacePackages).filter(
23
+ (packageName) => {
24
+ const workspacePackage = workspacePackages[packageName];
25
+ const registryLatestVersion = registryLatestVersions[packageName];
26
+ if (registryLatestVersion === null) {
27
+ return true;
28
+ }
29
+ const result = compareTwoPackageVersions(
30
+ workspacePackage.packageObject.version,
31
+ registryLatestVersion,
32
+ );
33
+ return (
34
+ result === VERSION_COMPARE_RESULTS.GREATER ||
35
+ result === VERSION_COMPARE_RESULTS.DIFF_TAG
36
+ );
37
+ },
38
+ );
39
+ if (aheadPackageNames.length === 0) {
40
+ return [];
41
+ }
42
+
43
+ const statusTask = createTaskLog(`check versions on registry`);
44
+ let packageInfos;
45
+ try {
46
+ packageInfos = await Promise.all(
47
+ aheadPackageNames.map(async (packageName) => {
48
+ const workspacePackage = workspacePackages[packageName];
49
+ const packageVersion = workspacePackage.packageObject.version;
50
+ const versionStatus = await checkVersionStatusInRegistry({
51
+ registryUrl,
52
+ packageName,
53
+ packageVersion,
54
+ token,
55
+ });
56
+ return {
57
+ packageName,
58
+ packageVersion,
59
+ packageSlug: `${packageName}@${packageVersion}`,
60
+ rootDirectoryUrl: new URL("./", workspacePackage.packageUrl),
61
+ versionStatus,
62
+ };
63
+ }),
64
+ );
65
+ statusTask.done();
66
+ } catch (e) {
67
+ statusTask.fail();
68
+ throw e;
69
+ }
70
+ return packageInfos.filter(
71
+ ({ versionStatus }) => versionStatus !== VERSION_STATUS.PUBLISHED,
72
+ );
73
+ };
package/src/main.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { publishPackages } from "./publish_packages.js";
2
2
  export { syncPackagesVersions } from "./sync_packages_versions.js";
3
3
  export { upgradeExternalVersions } from "./upgrade_external_versions.js";
4
+ export { waitForPackagesPublication } from "./wait_for_packages_publication.js";
@@ -1,14 +1,10 @@
1
- import { createLogger, createTaskLog, UNICODE } from "@jsenv/humanize";
1
+ import { createLogger, UNICODE } from "@jsenv/humanize";
2
2
  import { publish } from "@jsenv/package-publish/src/internal/publish.js";
3
3
  import {
4
- checkVersionStatusInRegistry,
5
4
  VERSION_STATUS,
6
5
  waitForStagedVersionToLand,
7
6
  } from "@jsenv/package-publish/src/internal/staged_version.js";
8
- import {
9
- compareTwoPackageVersions,
10
- VERSION_COMPARE_RESULTS,
11
- } from "./internal/compare_two_package_versions.js";
7
+ import { collectUnpublishedPackages } from "./internal/collect_unpublished_packages.js";
12
8
  import { syncPackagesVersions } from "./sync_packages_versions.js";
13
9
 
14
10
  const REGISTRY_URL = "https://registry.npmjs.org";
@@ -16,71 +12,24 @@ const REGISTRY_URL = "https://registry.npmjs.org";
16
12
  export const publishPackages = async ({ directoryUrl, packagesRelations }) => {
17
13
  // the sync check already read the package.json files and the registry;
18
14
  // it hands both back so that publishing does not do it a second time
19
- const syncResult = await ensureVersionsAreInSync({
20
- directoryUrl,
21
- packagesRelations,
22
- });
23
- if (!syncResult) {
24
- return;
25
- }
26
- const { workspacePackages, registryLatestVersions } = syncResult;
27
- const toPublishPackageNames = Object.keys(workspacePackages).filter(
28
- (packageName) => {
29
- const workspacePackage = workspacePackages[packageName];
30
- const registryLatestVersion = registryLatestVersions[packageName];
31
- if (registryLatestVersion === null) {
32
- return true;
33
- }
34
- const result = compareTwoPackageVersions(
35
- workspacePackage.packageObject.version,
36
- registryLatestVersion,
37
- );
38
- return (
39
- result === VERSION_COMPARE_RESULTS.GREATER ||
40
- result === VERSION_COMPARE_RESULTS.DIFF_TAG
41
- );
42
- },
43
- );
44
- if (toPublishPackageNames.length === 0) {
45
- console.log(`${UNICODE.OK} packages are published on registry`);
46
- return;
47
- }
48
-
15
+ const { workspacePackages, registryLatestVersions } =
16
+ await ensureVersionsAreInSync({
17
+ directoryUrl,
18
+ packagesRelations,
19
+ });
49
20
  const token = process.env.NPM_TOKEN;
50
- const statusTask = createTaskLog(`check versions on registry`);
51
- let packageInfos;
52
- try {
53
- packageInfos = await Promise.all(
54
- toPublishPackageNames.map(async (packageName) => {
55
- const workspacePackage = workspacePackages[packageName];
56
- const packageVersion = workspacePackage.packageObject.version;
57
- const versionStatus = await checkVersionStatusInRegistry({
58
- registryUrl: REGISTRY_URL,
59
- packageName,
60
- packageVersion,
61
- token,
62
- });
63
- return {
64
- packageName,
65
- packageVersion,
66
- packageSlug: `${packageName}@${packageVersion}`,
67
- rootDirectoryUrl: new URL("./", workspacePackage.packageUrl),
68
- versionStatus,
69
- };
70
- }),
71
- );
72
- statusTask.done();
73
- } catch (e) {
74
- statusTask.fail();
75
- throw e;
76
- }
77
- const packagesToPublish = packageInfos.filter(
21
+ const unpublishedPackages = await collectUnpublishedPackages({
22
+ workspacePackages,
23
+ registryLatestVersions,
24
+ registryUrl: REGISTRY_URL,
25
+ token,
26
+ });
27
+ const packagesToPublish = unpublishedPackages.filter(
78
28
  ({ versionStatus }) => versionStatus === VERSION_STATUS.ABSENT,
79
29
  );
80
- const stagedPackages = packageInfos.filter(
30
+ const stagedPackages = unpublishedPackages.filter(
81
31
  ({ versionStatus }) => versionStatus === VERSION_STATUS.STAGED,
82
32
  );
83
-
84
33
  if (packagesToPublish.length === 0 && stagedPackages.length === 0) {
85
34
  console.log(`${UNICODE.OK} packages are published on registry`);
86
35
  return;
@@ -107,20 +56,24 @@ export const publishPackages = async ({ directoryUrl, packagesRelations }) => {
107
56
  });
108
57
  }
109
58
  for (const { packageSlug, rootDirectoryUrl } of packagesToPublish) {
110
- await publish({
59
+ const { success } = await publish({
111
60
  logger: createLogger({ logLevel: "info" }),
112
61
  packageSlug,
113
62
  rootDirectoryUrl,
114
63
  registryUrl: REGISTRY_URL,
115
64
  token,
116
65
  });
66
+ if (!success) {
67
+ throw new Error(`failed to publish ${packageSlug}`);
68
+ }
117
69
  }
118
70
  };
119
71
 
120
72
  /*
121
73
  * Publishing while package.json files are not in sync would put on the registry
122
74
  * packages depending on versions that do not exist (or are outdated).
123
- * When versions are not in sync we ask permission to sync them before publishing.
75
+ * Syncing modifies package.json files, a change to review and commit, so it is
76
+ * never done here: the publish fails instead.
124
77
  */
125
78
  const ensureVersionsAreInSync = async ({ directoryUrl, packagesRelations }) => {
126
79
  const {
@@ -136,19 +89,16 @@ const ensureVersionsAreInSync = async ({ directoryUrl, packagesRelations }) => {
136
89
  packagesRelations,
137
90
  });
138
91
  if (outdatedPackageNames.length) {
139
- console.warn(
140
- `${UNICODE.FAILURE} ${outdatedPackageNames.length} packages have a version older than the one published on registry
92
+ throw new Error(
93
+ `${outdatedPackageNames.length} packages have a version older than the one published on registry
141
94
  - ${outdatedPackageNames.join(`
142
95
  - `)}
143
96
  Run "npm run monorepo:sync_versions" and review the changes before publishing`,
144
97
  );
145
- return null;
146
98
  }
147
99
  const outOfSyncCount = versionUpdates.length + dependencyUpdates.length;
148
- if (outOfSyncCount === 0) {
149
- return { workspacePackages, registryLatestVersions };
150
- }
151
- console.warn(`${UNICODE.WARNING} versions are not in sync, ${outOfSyncCount} things must be updated in package.json files
100
+ if (outOfSyncCount) {
101
+ throw new Error(`versions are not in sync, ${outOfSyncCount} things must be updated in package.json files
152
102
  - ${[
153
103
  ...versionUpdates.map(
154
104
  ({ packageName, from, to }) => `${packageName}: ${from} -> ${to}`,
@@ -158,42 +108,8 @@ Run "npm run monorepo:sync_versions" and review the changes before publishing`,
158
108
  `${packageName} -> ${dependencyName}: ${from} -> ${to}`,
159
109
  ),
160
110
  ].join(`
161
- - `)}`);
162
- const confirmed = await confirm(`Sync versions and publish?`);
163
- if (!confirmed) {
164
- console.log(`${UNICODE.INFO} publish aborted, versions are not in sync`);
165
- return null;
166
- }
167
- // the dry run has mutated its own copy of the package objects; sync again on
168
- // freshly read files (but with what the registry already told us) so that the
169
- // updates are actually written and the package objects match the files
170
- const syncResult = await syncPackagesVersions({
171
- directoryUrl,
172
- packagesRelations,
173
- registryLatestVersions,
174
- });
175
- return {
176
- workspacePackages: syncResult.workspacePackages,
177
- registryLatestVersions,
178
- };
179
- };
180
-
181
- const confirm = async (question) => {
182
- if (!process.stdin.isTTY) {
183
- console.warn(
184
- `${UNICODE.FAILURE} cannot ask confirmation (stdin is not a TTY)`,
185
- );
186
- return false;
187
- }
188
- const { createInterface } = await import("node:readline/promises");
189
- const readlineInterface = createInterface({
190
- input: process.stdin,
191
- output: process.stdout,
192
- });
193
- try {
194
- const answer = await readlineInterface.question(`${question} (y/N) `);
195
- return answer.trim().toLowerCase() === "y";
196
- } finally {
197
- readlineInterface.close();
111
+ - `)}
112
+ Run "npm run monorepo:sync_versions" and review the changes before publishing`);
198
113
  }
114
+ return { workspacePackages, registryLatestVersions };
199
115
  };
@@ -74,6 +74,7 @@ Use a tool like "git diff" to see the new versions and ensure this is what you w
74
74
  workspacePackages,
75
75
  registryLatestVersions,
76
76
  outdatedPackageNames,
77
+ toPublishPackageNames,
77
78
  versionUpdates,
78
79
  dependencyUpdates,
79
80
  };
@@ -248,6 +249,7 @@ Use a tool like "git diff" to see the new versions and ensure this is what you w
248
249
  workspacePackages,
249
250
  registryLatestVersions,
250
251
  outdatedPackageNames,
252
+ toPublishPackageNames,
251
253
  versionUpdates,
252
254
  dependencyUpdates,
253
255
  };
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Waits until every workspace package version is published on the registry.
3
+ * Meant for when publishing happens elsewhere (a CI workflow for instance):
4
+ * it tells when the versions found in package.json files can be installed.
5
+ */
6
+
7
+ import { humanizeDuration, UNICODE } from "@jsenv/humanize";
8
+ import { waitForVersionInRegistry } from "@jsenv/package-publish/src/internal/staged_version.js";
9
+ import { collectUnpublishedPackages } from "./internal/collect_unpublished_packages.js";
10
+ import { collectWorkspacePackages } from "./internal/collect_workspace_packages.js";
11
+ import { fetchWorkspaceLatests } from "./internal/fetch_workspace_latests.js";
12
+
13
+ const REGISTRY_URL = "https://registry.npmjs.org";
14
+
15
+ export const waitForPackagesPublication = async ({
16
+ directoryUrl,
17
+ timeout = 15 * 60_000,
18
+ }) => {
19
+ const workspacePackages = await collectWorkspacePackages({ directoryUrl });
20
+ const registryLatestVersions = await fetchWorkspaceLatests(workspacePackages);
21
+ const unpublishedPackages = await collectUnpublishedPackages({
22
+ workspacePackages,
23
+ registryLatestVersions,
24
+ registryUrl: REGISTRY_URL,
25
+ });
26
+ if (unpublishedPackages.length === 0) {
27
+ console.log(`${UNICODE.OK} packages are published on registry`);
28
+ return;
29
+ }
30
+ console.log(`${UNICODE.INFO} ${unpublishedPackages.length} packages not published on registry yet
31
+ - ${unpublishedPackages.map(({ packageSlug }) => packageSlug).join(`
32
+ - `)}`);
33
+ for (const { packageName, packageVersion } of unpublishedPackages) {
34
+ await waitForVersionInRegistry({
35
+ registryUrl: REGISTRY_URL,
36
+ packageName,
37
+ packageVersion,
38
+ timeout,
39
+ timeoutErrorMessage: `${packageName}@${packageVersion} is still not published on ${REGISTRY_URL} after ${humanizeDuration(timeout)}. Check whatever publishes it.`,
40
+ });
41
+ }
42
+ console.log(`${UNICODE.OK} packages are published on registry`);
43
+ };