Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/22190-flat-docs-owner-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@objectstack/cli': patch
---

`os build`: a multi-package artifact's flat `src/docs/` is now carried by the package that owns the artifact's manifest, so its boot no longer warns about them

Clause-②: no

A multi-package artifact (one that carries `packages[]`, ADR-0130 D4) keeps its metadata in its package bodies only. `os build` still wrote the stack's own flat `src/docs/*.md` to the artifact's top level. At every boot, the metadata service registered them under the artifact's `manifest.id` and logged `carries N top-level metadata item(s) that none of its N package bodies declare`. The warning told the author to rebuild the artifact, which did not help: in an ADR-0130 layout the app package's source directory (for example `src/sales/`) is not named after the package, so no docs directory the build reads belongs to it.

- **What changes.** When exactly one `packages[]` entry has the artifact's `manifest.id` as its id, `os build` writes the flat docs onto that entry's body, after any docs from that package's own `src/<pkg>/docs/`. The artifact's top level no longer carries them. `composeStacks(…, { manifest: 'preserve' })` always produces this case, because the artifact's `manifest` is one of its inputs' manifests.
- **What stays the same.** The docs are served under the same package id, the doc count is the same, and books resolve the same tree. The doc lint still checks their names against `manifest.namespace`, and the `Collecting package docs` step line prints the same count.
- **What a running instance now reports differently.** These docs are now part of the owning package's installed record (`GET /api/v1/packages`) and are protected like that package's other docs (`lock: 'full'`, resettable), the same as the flat docs of a single-package artifact. Before, they were registered outside any package record and read back as freely editable.
- **When nothing moves.** If no entry has the manifest id, if several do, or if the artifact declares no `manifest.id`, the flat docs stay on the top level as before. A single-package artifact (no `packages[]`) is byte-identical to before.
- **To pick it up.** Rebuild with `os build`. An artifact that was already built keeps its shape, and the warning, until it is rebuilt.
26 changes: 17 additions & 9 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { buildAccessMatrix, diffAccessMatrix } from '@objectstack/lint';
import { runAuthoringRules, splitBySeverity, authoringRulesFor } from '@objectstack/lint';
import { resolveJsxGateManifest, printJsxGateNotices } from '../utils/sdui-manifest.js';
import { preflightDeclaredCapabilities, renderCapabilityMessage } from '../utils/capability-preflight.js';
import { attachPackageDocs, collectAndLintDocs, type DocIssue } from '../utils/collect-docs.js';
import { collectAndLintDocs, placeCollectedDocs, type DocIssue } from '../utils/collect-docs.js';
import { buildRuntimeBundle, cleanupOldRuntimeBundles } from '../utils/build-runtime.js';
import {
printHeader,
Expand Down Expand Up @@ -981,9 +981,6 @@ export default class Compile extends Command {
}

const finalBundle: Record<string, unknown> = { ...(result.data as Record<string, unknown>) };
if (docsResult.docs.length > 0) {
finalBundle.docs = docsResult.docs;
}
// [#18431] Docs read out of `src/<pkg>/docs/` attach to the body of the
// package that owns them — `packages[i].manifest`, ADR-0130 D4 option
// B — and ⛔ never to the top level, which is the maintainer's ruling
Expand All @@ -993,11 +990,22 @@ export default class Compile extends Command {
// `registerMetadataCollections` over `METADATA_ARRAY_KEYS`, which
// carries `docs` — so a doc on a body is served under that package
// and a flattened copy would buy nothing while destroying the
// attribution. `attachPackageDocs` hands back the
// ARGUMENT when it adds nothing, so a stack with no per-package docs
// serializes from the very same references as before.
if (docsResult.packageDocs.length > 0) {
finalBundle.packages = attachPackageDocs(finalBundle.packages, docsResult.packageDocs);
// attribution.
// [#22190] The stack's own flat `src/docs/` follows the same rule one
// step up: on a multi-package artifact it rides the body of the
// package that owns the artifact's manifest, because that artifact's
// top level is no package's body and the metadata door warns about
// every item it finds there. With no `packages[]` the top level IS
// the one package, so it stays there. `placeCollectedDocs` makes both
// choices; it hands back the very `docs` array and `packages` value
// when it moves nothing, so a single-package stack serializes from
// the same references as before.
const placed = placeCollectedDocs(finalBundle, docsResult);
if (placed.docs.length > 0) {
finalBundle.docs = placed.docs;
}
if (placed.packages !== finalBundle.packages) {
finalBundle.packages = placed.packages;
}

// 4b. Bundle handler functions into `<artifactDir>/objectstack-runtime.{hash}.mjs`
Expand Down
Loading
Loading