rman 2.11.1 → 2.11.3

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.
@@ -102,11 +102,11 @@ export declare namespace PublishService {
102
102
  * queries it to decide, so it's safe to call any time, including as the plan a bare `rman
103
103
  * publish` shows before asking for confirmation.
104
104
  *
105
- * **Whether a package is private is read from the manifest that would be published** - the one in
106
- * its build directory when it publishes from one, its own otherwise. A build directory with no
107
- * `package.json` is an `'error'`: nothing has been built to publish. A package `private` in its
108
- * source with no `publishConfig` is skipped before that is asked, since it stays private in any
109
- * manifest derived from it. Each remaining package's **dist-tag**
105
+ * **Whether a package is private is decided from the manifest that would be published.** From a
106
+ * build directory that is the one `publish` derives (see `derivePublishManifest`): `private` there
107
+ * only when the source has it and no `publishConfig`. In place it is the source's own. A build
108
+ * directory that is missing or holds nothing but a `package.json` is an `'error'`: nothing has been
109
+ * built to publish. Each remaining package's **dist-tag**
110
110
  * is settled next (`distTagFor`): usually derived from the version itself, `'error'` in the two
111
111
  * cases with nothing to derive - that one is about the invocation rather than the package, so it
112
112
  * is answered before anything touches the network. A package with uncommitted local changes is
@@ -136,10 +136,12 @@ export declare namespace PublishService {
136
136
  * landed on the registry first, and publishing is rare enough (once per release) that the
137
137
  * simplicity is worth more than the parallelism `run` gets from `power-tasks`.
138
138
  *
139
- * If a package fails, every other still-pending entry depending on it (transitively) is marked
140
- * `'error'` too and never attempted - publishing a package whose own new dependency range points
141
- * at a version that never actually reached the registry would hand consumers a broken install.
142
- * A package's own failure doesn't stop unrelated packages elsewhere in the plan, though.
139
+ * If a package fails, every other still-pending entry that **a consumer's install needs it for**
140
+ * (transitively) is marked `'error'` too and never attempted - publishing a package whose own new
141
+ * dependency range points at a version that never actually reached the registry would hand
142
+ * consumers a broken install. A package's own failure doesn't stop unrelated packages elsewhere in
143
+ * the plan, though, nor one that lists it only where an install does not need it (see
144
+ * `consumerNeeds`).
143
145
  */
144
146
  function applyPlan(repository: Repository, plan: Entry[], options?: ApplyOptions): Promise<Entry[]>;
145
147
  }
@@ -127,11 +127,11 @@ export var PublishService;
127
127
  * queries it to decide, so it's safe to call any time, including as the plan a bare `rman
128
128
  * publish` shows before asking for confirmation.
129
129
  *
130
- * **Whether a package is private is read from the manifest that would be published** - the one in
131
- * its build directory when it publishes from one, its own otherwise. A build directory with no
132
- * `package.json` is an `'error'`: nothing has been built to publish. A package `private` in its
133
- * source with no `publishConfig` is skipped before that is asked, since it stays private in any
134
- * manifest derived from it. Each remaining package's **dist-tag**
130
+ * **Whether a package is private is decided from the manifest that would be published.** From a
131
+ * build directory that is the one `publish` derives (see `derivePublishManifest`): `private` there
132
+ * only when the source has it and no `publishConfig`. In place it is the source's own. A build
133
+ * directory that is missing or holds nothing but a `package.json` is an `'error'`: nothing has been
134
+ * built to publish. Each remaining package's **dist-tag**
135
135
  * is settled next (`distTagFor`): usually derived from the version itself, `'error'` in the two
136
136
  * cases with nothing to derive - that one is about the invocation rather than the package, so it
137
137
  * is answered before anything touches the network. A package with uncommitted local changes is
@@ -192,16 +192,16 @@ export var PublishService;
192
192
  else if (privateBySource(pkg)) {
193
193
  entries.set(pkg.name, { package: pkg, version: pkg.version, status: 'skip', reason: 'private package' });
194
194
  }
195
- else if (!fs.existsSync(path.join(resolvePublishDir(pkg, options.contents), 'package.json'))) {
195
+ else if (!hasBuildOutput(pkg, options.contents)) {
196
196
  const dir = path.relative(pkg.dirname, resolvePublishDir(pkg, options.contents)) || '.';
197
197
  entries.set(pkg.name, {
198
198
  package: pkg,
199
199
  version: pkg.version,
200
200
  status: 'error',
201
- reason: `nothing to publish in "${dir}" - it has no package.json; build the package first`,
201
+ reason: `nothing to publish in "${dir}" - it is missing or empty; build the package first`,
202
202
  });
203
203
  }
204
- else if (publishedManifestOf(pkg, options.contents).private) {
204
+ else if (publishesInPlace(pkg, options.contents) && pkg.manifest.raw.private) {
205
205
  entries.set(pkg.name, { package: pkg, version: pkg.version, status: 'skip', reason: 'private package' });
206
206
  }
207
207
  else if (distTag.error) {
@@ -259,10 +259,12 @@ export var PublishService;
259
259
  * landed on the registry first, and publishing is rare enough (once per release) that the
260
260
  * simplicity is worth more than the parallelism `run` gets from `power-tasks`.
261
261
  *
262
- * If a package fails, every other still-pending entry depending on it (transitively) is marked
263
- * `'error'` too and never attempted - publishing a package whose own new dependency range points
264
- * at a version that never actually reached the registry would hand consumers a broken install.
265
- * A package's own failure doesn't stop unrelated packages elsewhere in the plan, though.
262
+ * If a package fails, every other still-pending entry that **a consumer's install needs it for**
263
+ * (transitively) is marked `'error'` too and never attempted - publishing a package whose own new
264
+ * dependency range points at a version that never actually reached the registry would hand
265
+ * consumers a broken install. A package's own failure doesn't stop unrelated packages elsewhere in
266
+ * the plan, though, nor one that lists it only where an install does not need it (see
267
+ * `consumerNeeds`).
266
268
  */
267
269
  async function applyPlan(repository, plan, options = {}) {
268
270
  const packageManager = CiService.resolvePackageManager(repository, options.packageManager);
@@ -279,7 +281,7 @@ export var PublishService;
279
281
  continue;
280
282
  }
281
283
  const pkg = entry.package;
282
- const blocker = pkg.dependencies.find(d => failed.has(d.name));
284
+ const blocker = pkg.dependencies.find(d => failed.has(d.name) && consumerNeeds(pkg, d.name));
283
285
  if (blocker) {
284
286
  failed.add(pkg.name);
285
287
  result.push({ ...entry, status: 'error', reason: `dependency "${blocker.name}" failed to publish` });
@@ -324,27 +326,59 @@ function resolvePublishDir(pkg, contentsOverride) {
324
326
  const rel = (typeof native === 'string' && native) || configured || contentsOverride;
325
327
  return rel ? path.resolve(pkg.dirname, rel) : pkg.dirname;
326
328
  }
327
- /** The manifest `npm publish` will read for `pkg` - its build directory's, or its own when it
328
- * publishes in place. */
329
- /* **Decided from the artifact, not the source**, because the two disagree about `private` and only
330
- * one of them is published. Read before any build, the plan answered `skip - private package` for a
331
- * single-package repository whose source carries `private: true` as a guard against a stray
332
- * `npm publish` - while `derivePublishManifest` was removing that very flag from what it ships.
333
- * Reported from `postgrejs-kysely`: version, tag and GitHub release all went through, and the
334
- * registry got nothing. */
335
- function publishedManifestOf(pkg, contentsOverride) {
336
- const dir = resolvePublishDir(pkg, contentsOverride);
337
- if (path.resolve(dir) === path.resolve(pkg.dirname))
338
- return pkg.manifest.raw;
339
- return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
340
- }
341
- /** `private` in the source with no `publishConfig` - private in every manifest derived from it too,
342
- * so it is known without a build. */
343
- /* Asked first so an unbuilt private package is skipped rather than reported as unbuilt: opra's
344
- * examples inherit `publish.npm.directory` from the shared preset and skip their build. */
329
+ /** `private` in the source with no `publishConfig` - private in every manifest derived from it too. */
330
+ /* **Decided from the manifest `npm publish` reads, which rman writes.** Publishing from a build
331
+ * directory, that is `derivePublishManifest`'s output - written over whatever is there at publish time
332
+ * - so `private` is the source's, kept only without a `publishConfig`. Reported from
333
+ * `postgrejs-kysely`: its source carried `private: true` as a guard against a stray `npm publish`,
334
+ * the plan answered `skip - private package`, and version, tag and GitHub release went through with
335
+ * nothing on the registry.
336
+ *
337
+ * **2.11.1 read `build/package.json` instead, and that file is not what is published.** It is
338
+ * whatever the build left there - the shared preset writes one, but only from the build `after` step
339
+ * a package can replace, and `@opra/common`/`@opra/client` replace it with their own esbuild step. Both
340
+ * had 625 and 52 built files and no `package.json`, the plan answered `error` for both, and opra's
341
+ * release stopped after the version had been pushed.
342
+ *
343
+ * Asked before the build is looked for, so an unbuilt private package is skipped rather than
344
+ * reported as unbuilt: opra's examples inherit `publish.npm.directory` and skip their build. */
345
345
  function privateBySource(pkg) {
346
346
  return !!pkg.manifest.raw.private && !pkg.manifest.raw.publishConfig;
347
347
  }
348
+ /** Whether a consumer installing `pkg` needs `name` to resolve: a `dependencies` entry, or a peer
349
+ * not marked optional. */
350
+ /* **Not every edge blocks a publish**, and blocking on all of them cost a release. A failed publish
351
+ * stops its dependents because their published range would point at a version that is not there -
352
+ * which is only true of what reaches the consumer:
353
+ * - `devDependencies` are removed from the published manifest (`derivePublishManifest`);
354
+ * - an optional peer that cannot be satisfied does not fail an install;
355
+ * - `optionalDependencies` are the ones npm proceeds without "if it cannot be found or fails to
356
+ * install" (npm's own documentation; not measured here).
357
+ * Measured on opra's 1.31.0 release: `@opra/api-ui` failed its first publish, and `@opra/http` -
358
+ * which lists it as an optional peer and a devDependency only - was blocked, taking elastic, mongodb
359
+ * and sqb with it, though none of their installs would have needed api-ui. */
360
+ function consumerNeeds(pkg, name) {
361
+ const raw = pkg.manifest.raw;
362
+ if (raw.dependencies?.[name] !== undefined)
363
+ return true;
364
+ return raw.peerDependencies?.[name] !== undefined && !raw.peerDependenciesMeta?.[name]?.optional;
365
+ }
366
+ /** Whether `pkg` is published from its own directory, whose `package.json` is then the manifest. */
367
+ /* In place, `private` is the source's whatever `publishConfig` says - npm refuses to publish it. */
368
+ function publishesInPlace(pkg, contentsOverride) {
369
+ return path.resolve(resolvePublishDir(pkg, contentsOverride)) === path.resolve(pkg.dirname);
370
+ }
371
+ /** Whether the directory `pkg` is published from holds something to publish - anything besides a
372
+ * `package.json`, which `publish` writes itself. Always true in place. */
373
+ /* Without it `applyPlan` creates the directory, writes the derived manifest and publishes a tarball
374
+ * holding nothing else. Not "has a package.json": that is the build's to write or not (see
375
+ * `privateBySource`). */
376
+ function hasBuildOutput(pkg, contentsOverride) {
377
+ if (publishesInPlace(pkg, contentsOverride))
378
+ return true;
379
+ const dir = resolvePublishDir(pkg, contentsOverride);
380
+ return fs.existsSync(dir) && fs.readdirSync(dir).some(name => name !== 'package.json');
381
+ }
348
382
  /**
349
383
  * Which registry a package's own version is asked about, and published to.
350
384
  *
@@ -286,8 +286,11 @@ function printApplied(applied, plan, label) {
286
286
  return failed;
287
287
  }
288
288
  /** The `logged` convention: printed here, so `runCli`'s catch does not print it a second time. */
289
+ /* On stderr, because under `--json` stdout is one JSON document: measured on opra's release, the
290
+ * failure line printed below the plan and a reader parsing it got `Unexpected non-whitespace
291
+ * character after JSON`. */
289
292
  function logged(message) {
290
- console.log(colors.red(message));
293
+ console.error(colors.red(message));
291
294
  const err = new Error(message);
292
295
  err.logged = true;
293
296
  return err;
package/constants.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.11.1';
1
+ export const version = '2.11.3';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "2.11.1",
4
+ "version": "2.11.3",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {