@arc-e-tect/api-only-publisher 0.17.0 → 0.18.1

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/README.adoc CHANGED
@@ -6,7 +6,7 @@
6
6
  :source-highlighter: rouge
7
7
  // The released version of this component: its snippets use it. The release
8
8
  // workflow updates it; do not change it by hand.
9
- :api-only-publisher-version: 0.17.0
9
+ :api-only-publisher-version: 0.18.1
10
10
 
11
11
  [NOTE]
12
12
  .About this documentation
@@ -691,7 +691,8 @@ Configured portfolio: paths=none, location=portfolios
691
691
  |`file`
692
692
  |A local directory, laid out as `<directory>/<target>/<version>/`.
693
693
  Not a distribution mechanism between repositories: it makes the pipeline testable end to end with no infrastructure, lets a library share one build with the projects that implement it, and is a local-iteration escape hatch.
694
- Like a registry, it never replaces a version it holds with other documents (<<idempotent,Publishing the same version again §>>); with `clean: true` only the version being published is kept, for a directory that is a build output rather than a record of releases.
694
+ Like a registry, it never replaces a version it holds with other documents (<<idempotent,Publishing the same version again §>>).
695
+ With `clean: true` the directory is a build output rather than a record of releases: only the version being published is kept, and it holds the last build, so a version built again with other documents replaces it.
695
696
 
696
697
  |`maven`
697
698
  |A path publishes into a repository layout on disk; an `http(s)` URL deploys to a remote repository with an HTTP `PUT` per file, and keeps its `maven-metadata.xml` (<<maven-remote,Publishing to a remote Maven repository §>>).
@@ -776,7 +777,8 @@ What happens when the destination already has the version:
776
777
  |The same documents: nothing is written or sent, and the log says `already published, identical`, or `with the same documents` when the copy came from another commit.
777
778
  Other documents: the version is not published to that channel, and the run warns, naming the version -- a published version is never replaced, so publish the change as a new version (<<channel-problems,When a channel cannot take a version §>>).
778
779
  A copy that cannot be read, so there is nothing to compare, is treated as one with other documents, saying why it `cannot be compared`.
779
- `file` compares the `manifest.json` in the version's directory, and with `clean: true` removes the other versions, never this one.
780
+ `file` compares the `manifest.json` in the version's directory.
781
+ With `clean: true`, a build output, it replaces a version with other documents instead, logging `Replaced`, and removes the other versions; what guards a release there is the check before `publish` (<<version-guard,A changed target needs a new version §>>), and a subscriber's lockfile after it.
780
782
  `maven` and `gitlab-package` read back every file before uploading any, so a run interrupted half-way is completed by the next one; a GitLab registry that allows duplicate packages, as it does by default, would otherwise replace a version.
781
783
  A version completed beside an archive from another commit gets that archive's `manifest.json`, so the two agree on where the version came from; a POM carries no documents, and one already there is left as it is.
782
784
  `github-release` reads the manifest in the asset the release has, and never overwrites one.
@@ -2110,8 +2112,9 @@ package:
2110
2112
  [#docker-github-actions]
2111
2113
  ==== Reference pipeline: GitHub Actions
2112
2114
 
2113
- Two jobs: `package` runs the image and hands the files on, `publish` checks them against the hand-off and publishes them to npm, Maven and NuGet on GitHub Packages, and to a GitHub Release, with the job's `GITHUB_TOKEN`.
2114
- Copy it into `.github/workflows/`, then set the digest, the targets and the trigger.
2115
+ Two jobs on a tag: `package` runs the image and hands the files on, `publish` checks them against the hand-off, publishes them to npm, Maven and NuGet on GitHub Packages, and to a GitHub Release, with the job's `GITHUB_TOKEN`, and tags each target's release, `<target>-v<version>`.
2116
+ On a pull request, a third job, `check`, runs the image's `check` with no network, against the merge-base with the pull request's base branch (<<version-guard,A changed target needs a new version §>>).
2117
+ Copy it into `.github/workflows/`, then set the digest, the targets and the trigger, and `TAG_FORMAT` if `versioning.tagFormat` names the tags otherwise.
2115
2118
  The container-job variant above replaces its `package` job.
2116
2119
 
2117
2120
  It is exercised end to end, both variants, by a workflow in this repository that publishes to scratch packages and resolves each one with its ecosystem's own client and with the API-Only Subscriber.
@@ -2123,8 +2126,11 @@ include::pipelines/github-actions.yml[]
2123
2126
 
2124
2127
  What the two steps lose, and how the pipeline makes up for it:
2125
2128
 
2126
- * *Idempotence.* A channel's remote mode leaves a version the registry has alone when it holds the same documents, and refuses it when it does not; `npm publish` or `dotnet nuget push` of an existing version fails instead.
2127
- Each publishing step checks for the version first, compares the bytes, and publishes only what is missing, so a re-run is safe and a real conflict still fails.
2129
+ * *Idempotence.* A channel's remote mode leaves a version the registry has alone when it holds the same documents, and does not replace it when it holds others; `npm publish` or `dotnet nuget push` of an existing version fails instead.
2130
+ Each publishing step checks for the version first, compares the documents its `manifest.json` lists -- not the bytes, which differ by commit -- and publishes only what is missing, so a re-run is safe.
2131
+ A version held with other documents, or a registry that cannot be reached, is a `::warning` on the run, never a failed job, as for the Publisher's own channels (<<channel-problems,When a channel cannot take a version §>>).
2132
+ * *History.* Every job that runs the Publisher checks out the full history and the tags, `fetch-depth: 0`, which `check` and `publish` compare each target's version against.
2133
+ * *Release tags.* The last step creates each target's release tag at the commit it published, unless it exists; a tag it cannot create is a warning.
2128
2134
  * *Maven checksums and metadata.* The local Maven layout holds the artifact, its POM and its manifest, and no `.md5`, `.sha1` or `maven-metadata.xml`.
2129
2135
  `mvn deploy:deploy-file` writes those when it uploads, so the repository ends up as a remote `maven` channel would leave it, and Gradle resolves it.
2130
2136
 
@@ -2132,6 +2138,8 @@ Each publishing step checks for the version first, compares the bytes, and publi
2132
2138
  ==== Reference pipeline: GitLab CI
2133
2139
 
2134
2140
  One job, on a `v*` tag, as the GitHub Actions pipeline: the image builds, lints and publishes the targets to the project's Generic Package Registry with `gitlab-package`, and, where `apionly.yaml` configures it, to the project's Maven registry, both with the job's `CI_JOB_TOKEN`.
2141
+ In a merge request, a `check` job runs the image's `check` against the merge-base with the target branch; both jobs fetch the full history, `GIT_DEPTH: 0`, and the branch they compare with.
2142
+ `gitlab-package` with `release: true` creates each target's release, and with it the tag `<target>-v<version>` that `check` reads; without it, nothing creates that tag, and `check` compares with the merge-base alone.
2135
2143
  `apionly.yaml` needs no GitLab settings for it; the pipeline's header says what to add for the Maven registry.
2136
2144
  Copy it to `.gitlab-ci.yml`, then set the digest and the targets.
2137
2145
  The tag pattern keeps out the tag a GitLab release puts on the commit, `<target>-v<version>`, which would otherwise start the pipeline a second time; a library that tags its releases otherwise changes the rule, and keeps that one out.
@@ -2147,6 +2155,8 @@ include::pipelines/gitlab-ci.yml[]
2147
2155
  link:../docs/guides/publishing-to-gitlab/overview.adoc[Publishing to GitLab →] is the guide: tokens, a self-managed GitLab, and what consumers need.
2148
2156
 
2149
2157
  *The two-step variant*, link:pipelines/gitlab-ci-two-step.yml[`pipelines/gitlab-ci-two-step.yml` →], keeps the image sealed: `package` runs in the image, and one `publish:*` job per registry publishes to the project's package registry with that registry's own client and `CI_JOB_TOKEN`: npm, Maven and NuGet, and the archives to the generic package registry.
2158
+ As in the GitHub Actions pipeline, each compares documents and warns rather than fails, a merge request runs `check`, and a last job, `release:tags`, creates a GitLab release, and with it the tag, for each target's release; a pre-release gets none, as GitLab would make it the latest release.
2159
+ It runs on a `v*` tag, so the tags it creates do not start it again.
2150
2160
  It is validated against GitLab's published CI schema only; no GitLab pipeline has run it yet.
2151
2161
 
2152
2162
  [#docker-mirror-in-pipelines]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arc-e-tect/api-only-publisher",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Builds, packs and publishes API description documents from a library of reusable fragments.",
5
5
  "license": "MIT",
6
6
  "author": "Arc-E-Tect",
package/src/channels.js CHANGED
@@ -34,7 +34,10 @@ function format(template, manifest) {
34
34
  * Not a real distribution mechanism. It exists so the pipeline is testable end
35
35
  * to end before any remote exists, and as a local-iteration escape hatch
36
36
  * afterwards. Like the registries, it never replaces a version: one it holds with
37
- * the same documents is left alone, and one with other documents is refused.
37
+ * the same documents is left alone, and one with other documents is refused --
38
+ * unless `clean: true` declares the directory a build output, not a record of
39
+ * releases, where the last build is what it holds. What guards a release is then
40
+ * the git check before `publish`, and a subscriber's lockfile after it.
38
41
  */
39
42
  function publishFile(archive, manifest, options, log) {
40
43
  // Relative to the library root, not to wherever the CLI happened to be run.
@@ -44,21 +47,29 @@ function publishFile(archive, manifest, options, log) {
44
47
  const manifestDest = path.join(targetDir, "manifest.json");
45
48
  const files = [archiveDest, manifestDest];
46
49
 
50
+ const buildOutput = options.clean === true;
47
51
  const held = fs.existsSync(manifestDest) ? judgeHeld(fs.readFileSync(manifestDest), "json", manifest) : null;
48
- if (held && held.verdict !== "same") {
52
+ const replaced = held && held.verdict !== "same";
53
+ if (replaced && !buildOutput) {
49
54
  throw new ChannelConflict(`${dir} already has ${manifest.target} ${manifest.version}, and it ` +
50
55
  `${held.verdict === "different" ? "is not this one" : `cannot be compared: ${held.reason}`}; ` +
51
56
  "a published version is never replaced, so publish the change as a new version");
52
57
  }
53
- // `clean: true` keeps only the version being published. Used where the
54
- // directory is a build output rather than a record of releases: an older
55
- // version is published again from the commit that declared it.
56
- if (options.clean === true && fs.existsSync(path.join(dir, manifest.target))) {
58
+ // `clean: true` keeps only the version being published, and the last build of it.
59
+ // Used where the directory is a build output rather than a record of releases: an
60
+ // older version is published again from the commit that declared it.
61
+ if (buildOutput && fs.existsSync(path.join(dir, manifest.target))) {
57
62
  for (const version of fs.readdirSync(path.join(dir, manifest.target))) {
58
- if (version !== manifest.version) fs.rmSync(path.join(dir, manifest.target, version), { recursive: true, force: true });
63
+ if (version !== manifest.version || replaced) {
64
+ fs.rmSync(path.join(dir, manifest.target, version), { recursive: true, force: true });
65
+ }
59
66
  }
60
67
  }
61
- if (held && fs.existsSync(archiveDest)) {
68
+ if (replaced) {
69
+ log(`-- Replaced ${manifest.target} ${manifest.version} in ${dir}, which clean: true makes a build output: it held ` +
70
+ (held.verdict === "different" ? "other documents" : `a copy that cannot be compared (${held.reason})`));
71
+ }
72
+ if (held && !replaced && fs.existsSync(archiveDest)) {
62
73
  const identical = fs.readFileSync(archiveDest).equals(fs.readFileSync(archive));
63
74
  log(`-- ${manifest.target} ${manifest.version} is already published, ` +
64
75
  `${identical ? "identical" : "with the same documents"}, in ${dir}; nothing to do`);
@@ -68,7 +79,7 @@ function publishFile(archive, manifest, options, log) {
68
79
  fs.copyFileSync(archive, archiveDest);
69
80
  fs.writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + "\n");
70
81
 
71
- log(`-- Published ${manifest.target} ${manifest.version} to ${archiveDest}`);
82
+ if (!replaced) log(`-- Published ${manifest.target} ${manifest.version} to ${archiveDest}`);
72
83
  return { location: archiveDest, files };
73
84
  }
74
85