@arc-e-tect/api-only-publisher 0.6.0 → 0.6.2

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.6.0
9
+ :api-only-publisher-version: 0.6.2
10
10
 
11
11
  image:https://github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-API/actions/workflows/nvd-cache-refresh.yml/badge.svg[Vulnerability Scan,link=https://github.com/Arc-E-Tect/SoftwareEngineeringDoneRight-API/actions/workflows/nvd-cache-refresh.yml]
12
12
  image:https://img.shields.io/npm/v/@arc-e-tect/api-only-publisher[npm,link=https://www.npmjs.com/package/@arc-e-tect/api-only-publisher]
@@ -199,6 +199,7 @@ An OpenAPI document's components each carry the path of the fragment they came f
199
199
  A `publish: false` target keeps the version its source declares.
200
200
 
201
201
  . **Lint** the finished document, so a broken fragment surfaces against the target it actually affects.
202
+ `build` prints the linter's output only when the lint fails, and writes no report; to see warnings, and to write each document's report under `reports.lint`, run `lint`.
202
203
 
203
204
  [#versions]
204
205
  == Versions
@@ -295,7 +296,7 @@ Declare the members and the aggregate's own identity:
295
296
  portfolio:
296
297
  publish: false
297
298
  asyncapi:
298
- bundle: portfolio_asyncapi_structure.yaml
299
+ bundle: bundles/portfolio_asyncapi_structure.yaml
299
300
  aggregate:
300
301
  - orders
301
302
  - payments
@@ -369,20 +370,25 @@ Adapting the tool to another project means editing configuration.
369
370
 
370
371
  [source,yaml]
371
372
  ----
373
+ # The version of this file's own format -- which keys apionly.yaml has and what
374
+ # they mean -- not of any API. This Publisher understands 1, and refuses others.
372
375
  schemaVersion: 1
373
376
 
374
377
  sources:
375
378
  root: specs # staged wholesale; the kinds share fragments
376
- openapi: openapi
379
+ openapi: openapi # each kind's directory under root; bundle paths start here
377
380
  asyncapi: asyncapi
378
381
 
379
382
  defaults:
380
383
  openapi:
381
384
  lint: .redocly.yaml
385
+ # The file name of every built OpenAPI document: build/dist/<target>/openapi.yaml.
386
+ # The Subscriber looks for exactly this name.
382
387
  outputName: openapi.yaml
383
388
  fragmentPaths: true # x-fragment-path on every component; the default
384
389
  asyncapi:
385
- outputName: asyncapi.yaml
390
+ outputName: asyncapi.yaml # as for OpenAPI: build/dist/<target>/asyncapi.yaml
391
+ fragmentPaths: true # x-fragment-path on every fragment; the default
386
392
  placeholders:
387
393
  strict: true # an unresolved {{token}} fails the build
388
394
 
@@ -408,17 +414,36 @@ targets:
408
414
  # versionFile: versions/orders.properties
409
415
  # default: orders.bundle.properties beside the first bundle root
410
416
  openapi:
411
- bundle: bundles/orders.yaml
417
+ # The bundle root the document is built from, relative to specs/openapi/.
418
+ # It is built into build/dist/orders/openapi.yaml, named by outputName.
419
+ bundle: bundles/orders_openapi_structure.yaml
412
420
  asyncapi:
413
- bundle: orders_asyncapi.yaml
421
+ # Relative to specs/asyncapi/; built into build/dist/orders/asyncapi.yaml.
422
+ bundle: bundles/orders_asyncapi_structure.yaml
414
423
  portfolio:
415
424
  publish: false # built and linted, never published
416
425
  openapi:
417
- bundle: bundles/portfolio.yaml
426
+ bundle: bundles/portfolio_openapi_structure.yaml
418
427
  ----
419
428
 
420
429
  `targets` is the single source of truth for what gets built.
421
430
 
431
+ A target's `bundle` and a kind's `outputName` are the two ends of one build: the bundle root is what a document is built *from*, in the library, and `outputName` is what it is built *to*, in `<build.dist>/<target>/`.
432
+ Every target's document of one kind has the same `outputName`; keep the defaults, `openapi.yaml` and `asyncapi.yaml`, because the Subscriber looks for exactly those names.
433
+
434
+ [#bundle-roots]
435
+ === Where bundle roots go
436
+
437
+ Put every bundle root in a `bundles/` directory of its kind's source directory, named `<target>_<kind>_structure.yaml`: `specs/openapi/bundles/orders_openapi_structure.yaml` and `specs/asyncapi/bundles/orders_asyncapi_structure.yaml`.
438
+ The `init` scaffold lays out its OpenAPI bundle root this way.
439
+
440
+ * A bundle root is a table of contents rather than a fragment, and a `bundles/` directory keeps the two apart.
441
+ * The name says which target and which kind, so the two roots of one target are never confused, in the tree or in a review.
442
+ * The version file's default place is beside the target's first bundle root (<<versions,Versions §>>), so it lands in `bundles/` too.
443
+
444
+ The Publisher does not require it: a bundle path is any path relative to the kind's source directory.
445
+ A bundle root moved into or out of `bundles/` needs its relative `$ref`s adjusted by one `../`, since they resolve against the file's own directory; the built document is otherwise the same.
446
+
422
447
  [#pinning-the-bundlers]
423
448
  === Pinning the bundlers
424
449
 
@@ -463,6 +488,11 @@ Any other YAML file under the source root counts.
463
488
  [#version-stamping]
464
489
  === Version stamping edits one scalar
465
490
 
491
+ Stamping sets `info.version` in the *built* document, `<build.dist>/<target>/<outputName>`, after bundling.
492
+ The value it replaces comes from the bundle root's `info` block, written in the bundle root or `$ref`'d from a fragment, which the bundler puts into the document: every bundle root therefore needs an `info` block with a `version`, conventionally `0.0.0`.
493
+ The `init` scaffold's bundle root refers to `specs/openapi/shared/info.yaml`, which declares `version: 0.0.0`.
494
+ The version that replaces it is the target's version file's (<<versions,Versions §>>).
495
+
466
496
  `info.version` is located structurally and that one scalar is spliced.
467
497
  It is deliberately not a re-serialisation: re-emitting a document reformats everything around the edit, because the AsyncAPI CLI wraps long descriptions at a width no YAML emitter reproduces.
468
498
  Splicing keeps every other byte exactly as the bundler wrote it.
@@ -492,12 +522,14 @@ It is how the API-Only TranscriberJ ties generated code to the fragment it came
492
522
  It is on by default, for both specification kinds.
493
523
  `defaults.openapi.fragmentPaths: false` and `defaults.asyncapi.fragmentPaths: false` turn it off, each for its own kind.
494
524
 
495
- How it is done::
525
+ How it is done, for OpenAPI::
496
526
  The bundler is not told anything; it is shown stamped fragments, in copies of the staged tree under `<build.staging>/fragment-paths/<target>/`.
497
527
  The first bundle is built with every fragment stamped, to learn which fragments become components.
498
528
  The second, the one that is published, is built with only those stamped.
499
529
  So a document differs from one built with `fragmentPaths: false` by exactly one line per component, and in no other byte.
500
530
  The cost is a second bundler run per OpenAPI target.
531
+ +
532
+ An AsyncAPI target is stamped differently, in one run and with every fragment stamped, because its bundler inlines rather than hoists: <<fragment-paths-asyncapi,AsyncAPI, where the bundler inlines rather than hoists §>> describes it.
501
533
 
502
534
  What stops the build::
503
535
  A fragment that writes `x-fragment-path` itself: the Publisher sets that key and nothing else may.
@@ -540,7 +572,9 @@ A stamp in the built document that names no file in the library, which would mea
540
572
  [#fragment-path-versions]
541
573
  ==== What a change to `x-fragment-path` means for a contract's version
542
574
 
543
- Semantic versioning describes the contract, so the version follows from what a change does to the contract's promises:
575
+ Semantic versioning describes the contract, so the version follows from what a change does to the contract's promises.
576
+ The Publisher does not decide it: it stamps whatever the target's version file declares.
577
+ Choosing the next version is the API designer's responsibility, and this table is their guideline:
544
578
 
545
579
  [cols="2,1,3", options="header"]
546
580
  |===
@@ -594,11 +628,11 @@ In order of what usually needs changing:
594
628
  |The lint rules
595
629
  |`defaults.openapi.lint`
596
630
 
597
- |Whether components carry `x-fragment-path`
598
- |`defaults.openapi.fragmentPaths`, defaulting to `true`
631
+ |Whether built documents carry `x-fragment-path`
632
+ |`defaults.openapi.fragmentPaths` and `defaults.asyncapi.fragmentPaths`, each defaulting to `true`
599
633
 
600
634
  |Where lint reports go
601
- |`reports.lint`, defaulting to `build/reports/lint/<target>/<kind>.txt`
635
+ |`reports.lint`, a directory, defaulting to `build/reports/lint`: each document's report is `<target>/<kind>.txt` in it, beside `unreferenced.txt`
602
636
 
603
637
  |Where built documents go
604
638
  |`build.dist`
@@ -623,6 +657,14 @@ A library with no `{{token}}` anywhere simply has nothing substituted.
623
657
  The tool does not police them.
624
658
  `changed` tells a release which targets a commit altered, and every manifest records the closure hash of what was published, so the hooks a diff gate would need already exist, but the gate itself is deliberately not built.
625
659
 
660
+ [IMPORTANT]
661
+ .The API designer defines the contract's version, not the Publisher
662
+ ====
663
+ The Publisher reads a target's version from its version file and stamps it; it refuses a version that is not a semantic release version, and nothing else.
664
+ It never works out a version from what changed, and never checks that a version matches the change.
665
+ Whether a change is major, minor or patch is the API designer's decision, and so is its consequence: raising the version in the target's version file.
666
+ ====
667
+
626
668
  A breaking change is declared where it is made: the major version in the target's version file changes in the same commit as the fragments it breaks.
627
669
  A specification library should write down its own policy for what counts as breaking.
628
670
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arc-e-tect/api-only-publisher",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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/config.js CHANGED
@@ -161,8 +161,9 @@ function load(configPath) {
161
161
  requireString(this.sources[kind], `sources.${kind}`),
162
162
  this.targets[target][kind].bundle);
163
163
  },
164
- // Whether built documents of this kind carry x-fragment-path on each
165
- // component. On unless turned off; OpenAPI only.
164
+ // Whether built documents of this kind carry x-fragment-path: on each
165
+ // component of an OpenAPI document, on each fragment of an AsyncAPI one.
166
+ // On unless turned off, for both kinds.
166
167
  fragmentPaths(kind) {
167
168
  if (kind !== "openapi" && kind !== "asyncapi") return false;
168
169
  const configured = (this.defaults[kind] || {}).fragmentPaths;