diff --git a/.smpte-build.json b/.smpte-build.json index a49fdb3..c46e10f 100644 --- a/.smpte-build.json +++ b/.smpte-build.json @@ -1,3 +1,3 @@ { - "latestEditionTag": "20260319-pub" + "latestEditionTag": null } diff --git a/doc/main.html b/doc/main.html index bb206a0..2b1b5d0 100644 --- a/doc/main.html +++ b/doc/main.html @@ -9,8 +9,8 @@ - - + + @@ -46,6 +46,8 @@ https://doc.smpte-doc.org/ag-07/main/
pubState
- For further explanation of determining the value of the document's pubState, see
+ For further explanation of determining the value of the document's pubState, see
pubStage
- For further explanation of determining the value of the document's pubStage, see
+ For further explanation of determining the value of the document's pubStage, see
The S3 upload step can be skipped by providing the validate argument to the build script.
The build process can be configured using the .smpte-build.json file which is contains a JSON object that
- conforms to the JSON schema at .
The build process MAY be configured using an optional .smpte-build.json file containing a JSON object that
+ conforms to the JSON schema at . When the file is absent, the build derives
+ its configuration from the document metadata and the repository's git tags.
.smpte-build.json..smpte-build.json.If present or not equal to null, the latestEditionTag property contains the Git tag or commit
- that is used when generating a redline against the latest published edition of the document.
If present and not equal to null, the latestEditionTag property overrides the auto-selected
+ base for the published-edition redline with the specified Git tag or commit. When absent or null, the base is
+ auto-selected as described in .
- For further explanation of determining the value of latestEditionTag, see
+ For the complete description of redline outputs produced by the tooling, see .
The tooling automatically produces redline (change-tracked) renderings of the document on every build — pull requests, merges to main, and release publications. Redlines compare the current rendered document against a chosen reference rendering using the htmldiff utility, and the resulting HTML files are deployed to S3 alongside the clean output and bundled into the review zip when applicable.
Three redlines may be produced per build, each with a different reference base:
+ +base-rl.html)Generated against the pull request's target branch (typically main), via the GITHUB_BASE_REF environment variable provided by GitHub Actions on pull request events. Shows what the PR itself changes relative to the branch it will merge into.
Generated: on pull request events. Omitted: on push and release events, where no PR base is defined.
+pub-rl.html)Generated against the most recent prior git tag matching YYYYMMDD-pub — the previous published edition. Shows what has changed since the last edition was published. The reference base may be overridden by setting latestEditionTag in .smpte-build.json — useful when rebuilding an archived edition or pinning to a specific historical release.
This redline satisfies the requirement in for a redline against the published document (the baseline draft) when preparing a revision.
+Generated: on every event. Omitted: when no prior -pub tag exists (e.g., the first edition).
release-rl.html)Generated against the most recent prior git tag matching YYYYMMDD-{stage} for any recognized pubStage (-wd, -cd, -fcd, -dp, or -pub). Shows iterative changes since the last release tag of any kind, supporting draft-to-draft review across the workflow stages defined in .
This redline satisfies the requirement in that markup from the ballot document be provided for comment resolution.
+Generated: when the current document's pubStage is WD, CD, FCD, or DP. Omitted: when pubStage is PUB (a published edition's relevant comparison is to the prior published edition only) or when no prior dated release tag exists.
In all cases, any tag pointing at the HEAD commit is excluded from selection, so a freshly-tagged release does not redline against itself. When the build runs on a pull request, links to all available redline outputs are included in the comment posted to the PR (see ). When the build runs on a release publication, the redline files are bundled into the review zip attached to the release (see ).
meta tags to Document Status
- maps the required content values of pubState, pubStage, and pubConfidential during the various stages of document development. These values represent the naming convention(s) as defined in and flowcharts as defined in
-
| Document Status | -pubStage value |
- pubState value |
- pubConfidential value |
-
|---|---|---|---|
| Initial draft or revision (Working Draft) in:
- Draft Group (DG) - Study Group (SG) - Working Group (WG) |
- WD |
- draft |
- yes |
-
| >> Document moves to TC >> | -|||
| Pre-FCD (Final Committee Draft) review | -WD |
- pub |
- yes |
-
| >> If pre-FCD comments received, back to DG/SG/WG >> | -|||
| Pre-FCD comment resolution ongoing | -CD |
- draft |
- yes |
-
| Pre-FCD comment resolution complete | -CD |
- pub |
- yes |
-
| >> Document moves to TC >> | -|||
| PCD (Public Committee Draft) period | -CD |
- pub |
- no |
-
| PCD comment resolution ongoing (moves back to DG/SG/WG) | -CD |
- draft |
- yes |
-
| PCD comment resolution complete | ->> Back to Pre-FCD (Final Committee Draft) review >> | -||
| (OR) | -|||
| Ready for FCD ballot | -CD |
- pub |
- yes |
-
| FCD ballot period | -CD |
- pub |
- yes |
-
| >> If FCD ballot comments received >> | -|||
| FCD ballot comment resolution ongoing | -FCD |
- draft |
- yes |
-
| FCD ballot comment resolution complete | -FCD |
- pub |
- yes |
-
| Pre-DP (Draft Publication) review | -FCD |
- pub |
- yes |
-
| Pre-DP ballot period | -FCD |
- pub |
- yes |
-
| (ELSE) | -|||
| FCD ballot passes, no comments | -DP |
- pub |
- yes |
-
| DP ballot passes | -DP |
- pub |
- yes |
-
| Ready for ST (Standards Committee) Audit | -DP |
- pub |
- yes |
-
| ST Audit period | -DP |
- pub |
- yes |
-
| ST Audit complete | -PUB |
- draft |
- yes |
-
| Waiting for publication | -PUB |
- draft |
- yes |
-
| Published | -PUB |
- pub |
- no |
-
- See for a sample workflow utilizing these statuses. -
+This annex describes the tooling behavior that auto-drafts GitHub releases. For the document workflow that drives when releases are created and who publishes them, see .
- Whenever the state of the document changes to <meta itemprop="pubState" content="pub" /> on the main branch, then a release shall be created in the GitHub repo.
-
- Both the GitHub release and git tag shall be named YYYYMMDD "-" pubStage. The YYYYMMDD should directly represent the value used in pubDateTime as defined in .
-
- 20230615-fcd or 20230925-pub
-
- The latestEditionTag property in the .smpte-build.json file shall always be equal to the name of the most recent git tag. This will trigger the tooling to create a link for Redline to most recent edition during any PRs and merges to the main branch.
-
- The tooling will automatically generate zip file packages within the release details page, to be used for both :
-See https://github.com/SMPTE/st428-24-private/releases/tag/20241101-dp, where https://github.com/SMPTE/st428-24-private/releases/download/20241101-dp/27c-st-428-24-dp-2024-12-06-pub.zip (ballot zip), and https://github.com/SMPTE/st428-24-private/releases/download/20241101-dp/20241101-dp.zip (publish zip) are available.
- -- See for a sample workflow utilizing releases. -
- +Whenever the state of the document changes to <meta itemprop="pubState" content="pub" /> on the main branch, the tooling automatically creates a draft release in the GitHub repo. A user with appropriate permissions reviews and publishes the draft to finalize the release and trigger production of the associated artifacts.
- Generally, no changes to a document shall ever be made directly on the main branch, which doesn't allow the tooling to create needed redlines. Instead, changes (regardless of the nature of the change) shall always be made on a new branch and managed via a PR (Pull Request). Each PR is then merged to main after approval, as noted below in at the various stages.
-
PRs should have at least (1) approver that is not the person requesting the PR. This can be the DG chair, TC chair, commentor, or secondary editor, depending on nature of the PR. For instance, on ballot state change PRs, this should be the DG or TC chair, and for publication state PRs, the TC chairs. See for more details.
+The GitHub release and the git tag shall be named YYYYMMDD "-" pubStage, where YYYYMMDD represents the value of pubDateTime (as defined in ) and the pubStage suffix is the value of the pubStage meta in lowercase.
20230615-fcd or 20230925-pub
For workflow events that share both pubDateTime and pubStage with the most recent release but represent a distinct release moment, the merge commit message shall include the token [force-release]. The tooling appends a numeric suffix (-2, -3, …) to the tag to disambiguate.
- The below list shows a sample workflow which would be the steps for an Engineering Document going through an initial draft or revision and the balloting process. -
- -Editors and Chairs should be aware of the general guidelines provided in the . -
+To publish a draft release after it has been auto-created:
main called 2023-initial-draft (where "2023" is the current year).pubState () and pubStage () of the main prose element in the 2023-initial-draft branch are set to
- draft and WD, respectively.2023 Initial Draft from the 2023-initial-draft branch on GitHub. 2020115-pubmain called 2023-revision (where "2023" is the current year).pubState () and pubStage () of the main prose element in the 2023-initial-draft branch are set to
- draft and WD, respectively..smpte-build.json is updated in 2023-revision branch to last release tag
- 2020115-pub (see ) 2023 Revision from the 2023-revision branch on GitHub. The tooling with create a redline against the most recent edition of the document.<meta itemprop="pubState" content="pub" />, <meta itemprop="pubStage" content="WD" /> and <meta itemprop="pubDateTime" content="2023-04-15" /> (assumed date of completion).main.main named 20230415-wd with a tag of the same name.main called 20230415-cd1.20230415-cd1 branch to <meta itemprop="pubState" content="draft" /> and <meta itemprop="pubStage" content="CD" />..smpte-build.json is updated in 20230415-cd1 branch to 20230415-wd tag 20230415 CD1 from the 20230415-cd1 branch on GitHub.<meta itemprop="pubDateTime" content="2023-05-15" /> (assumed date of completion).<meta itemprop="pubState" content="pub" />
- pubDateTime remains unchanged from Workflow Step (5). main.main named 20230415-cd1 (or 20230515-cd1 if Workflow Step 7 was used) with a tag of the same name.main with required redlines is sent to the TC for FCD ballot. Skip to Workflow Step (9)main are used for the PCD. After PCD period ends, go back to Workflow Step (6). See for iterative CD naming. main called 2023-revision-fcd-comment-res.2023-revision-fcd-comment-res branch to <meta itemprop="pubState" content="draft" /> and <meta itemprop="pubStage" content="FCD" />..smpte-build.json is updated in 2023-revision-fcd-comment-res branch to 20230415-cd1 (or 20230515-cd1) tag 2023-revision-fcd-comment-res as needed to resolve comments. The tooling creates redline for review against the last edition (release 20230415-cd1 or 20230515-cd1).<meta itemprop="pubState" content="pub" /> and <meta itemprop="pubDateTime" content="2023-07-15" /> (assumed date of completion).main.main named 20230715-fcd with a tag of the same name.main is sent to the TC for pre-DP review.main called 20230415-dp (or 20230715-dp if Workflow Step 5 was used or 20230715-dp if Workflow Step 10 was used).20230415-dp branch to <meta itemprop="pubStage" content="DP" />.smpte-build.json is updated in 20230415-dp branch to 20230415-cd1 tag (or 20230515-cd1)20230415-cd1 or 20230515-cd1 or 20230715-fcd).main.main named 20230415-dp (or 20230515-dp or 20230715-dp) with a tag of the same name.main is sent to DoS for ST audit.main called 20230415-pub (or 20230515-pub or 20230715-pub).20230415-pub branch to <meta itemprop="pubStage" content="pub" />.smpte-build.json is updated in 20230415-pub branch to 2020115-pub tag 20230415-pubmain.main named 20230415-pub (or 20230515-pub or 20230715-pub) with a tag of the same name.20230415-pub.zip (or 20230515-pub.zip or 20230715-pub.zip) generated in the release to use in the https://github.com/SMPTE/document-library.
- https://github.com/SMPTE/{repo}/releases).YYYYMMDD-pubStage tag for the merged document state.Publishing the draft release attaches zip packages to the release details page:
+pub-rl.html) and the most-recent-release redline (release-rl.html). See for redline details and reference-base selection.See https://github.com/SMPTE/st428-24-private/releases/tag/20241101-dp, where https://github.com/SMPTE/st428-24-private/releases/download/20241101-dp/27c-st-428-24-dp-2024-12-06-pub.zip (ballot zip) and https://github.com/SMPTE/st428-24-private/releases/download/20241101-dp/20241101-dp.zip (publish zip) are available.
Redline to most recent edition
\n`; + htmlLinks += `\n`; + } + + if ("releaseRedline" in generatedFiles) { + htmlLinks += `\n`; } if (generatedFiles.reviewZip !== undefined) { @@ -655,6 +684,10 @@ class BuildPaths { this.pubRedlinePath = path.join(this.pubDirPath, this.pubRedlineName); this.pubRedLineRefPath = path.join(this.buildDirPath, this.pubRedlineName); + this.releaseRedlineName = "release-rl.html"; + this.releaseRedlinePath = path.join(this.pubDirPath, this.releaseRedlineName); + this.releaseRedLineRefPath = path.join(this.buildDirPath, this.releaseRedlineName); + this.baseRedlineName = "base-rl.html"; this.baseRedlinePath = path.join(this.pubDirPath, this.baseRedlineName); this.baseRedLineRefPath = path.join(this.buildDirPath, this.baseRedlineName); @@ -671,13 +704,59 @@ class BuildConfig { try { config = JSON.parse(fs.readFileSync(path.join(docDirPath, ".smpte-build.json"))); } catch { - console.log("Could not read the publication config file."); + /* file is optional; auto-derivation is used when it is absent */ } - this.lastEdRef = config.latestEditionTag || null; + this.latestEditionTag = config.latestEditionTag || null; } } +const DATED_TAG_RE = /^\d{8}-(pub|fcd|cd|wd|dp)(-\d+)?$/; +const PUB_TAG_RE = /^\d{8}-pub(-\d+)?$/; + +function getHeadTags() { + try { + return new Set( + child_process.execSync("git tag --points-at HEAD") + .toString() + .split("\n") + .map(t => t.trim()) + .filter(Boolean) + ); + } catch { + return new Set(); + } +} + +function pickLatestTag(globs, filterRe) { + let allTags; + try { + allTags = child_process.execSync(`git tag -l ${globs.map(g => `'${g}'`).join(" ")} --sort=-version:refname`) + .toString() + .split("\n") + .map(t => t.trim()) + .filter(t => filterRe.test(t)); + } catch { + return null; + } + + const headTags = getHeadTags(); + return allTags.find(t => !headTags.has(t)) || null; +} + +function deriveLastEdRef(override) { + if (override !== null && override !== undefined && override !== "") { + return override; + } + return pickLatestTag(["*-pub*"], PUB_TAG_RE); +} + +function deriveLastReleaseRef(pubStage) { + /* a -pub release only needs the redline to the prior published edition */ + if (pubStage === "PUB") return null; + return pickLatestTag(["*-pub*", "*-fcd*", "*-cd*", "*-wd*", "*-dp*"], DATED_TAG_RE); +} + async function main() { /* retrieve build phase */ @@ -802,7 +881,10 @@ async function main() { /* render document */ - Object.assign(generatedFiles, await build(buildPaths, baseRef, config.lastEdRef, docMetadata)); + const lastEdRef = deriveLastEdRef(config.latestEditionTag); + const lastReleaseRef = deriveLastReleaseRef(docMetadata.pubStage); + + Object.assign(generatedFiles, await build(buildPaths, baseRef, lastEdRef, lastReleaseRef, docMetadata)); /* validate rendered document */ diff --git a/scripts/derive-release-tag.mjs b/scripts/derive-release-tag.mjs new file mode 100644 index 0000000..8f812bf --- /dev/null +++ b/scripts/derive-release-tag.mjs @@ -0,0 +1,81 @@ +/* (c) Society of Motion Picture and Television Engineers + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +import * as fs from "fs"; +import process from "process"; +import { argv } from "process"; + +const PUB_STAGES = new Set(["WD", "CD", "FCD", "DP", "PUB"]); + +function extractMeta(html, name) { + const re = new RegExp(`]*itemprop=["']${name}["'][^>]*content=["']([^"']*)["']`, "i"); + const m = html.match(re); + if (m) return m[1]; + const reSwapped = new RegExp(`]*content=["']([^"']*)["'][^>]*itemprop=["']${name}["']`, "i"); + const m2 = html.match(reSwapped); + return m2 ? m2[1] : null; +} + +function emit(tag) { + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tag}\n`); + } + process.stdout.write(`tag=${tag}\n`); +} + +const docPath = argv[2] || "doc/main.html"; + +if (!fs.existsSync(docPath)) { + console.error(`Document not found: ${docPath}`); + process.exit(1); +} + +const html = fs.readFileSync(docPath, "utf8"); +const headEnd = html.search(/<\/head>/i); +const head = headEnd >= 0 ? html.slice(0, headEnd) : html; + +const pubState = extractMeta(head, "pubState"); +const pubDateTime = extractMeta(head, "pubDateTime"); +const pubStage = extractMeta(head, "pubStage"); + +if (pubState !== "pub") { + emit(""); + process.exit(0); +} + +if (pubDateTime === null || !/^\d{4}-\d{2}-\d{2}$/.test(pubDateTime)) { + console.error(`pubDateTime must be a full YYYY-MM-DD date to form a release tag. Got: ${pubDateTime}`); + process.exit(1); +} + +if (pubStage === null || !PUB_STAGES.has(pubStage)) { + console.error(`pubStage must be one of ${[...PUB_STAGES].join(", ")} to form a release tag. Got: ${pubStage}`); + process.exit(1); +} + +const tag = `${pubDateTime.replace(/-/g, "")}-${pubStage.toLowerCase()}`; +emit(tag); diff --git a/workflows/action.yml b/workflows/action.yml index 175a993..47ff715 100644 --- a/workflows/action.yml +++ b/workflows/action.yml @@ -47,6 +47,41 @@ runs: node ${GITHUB_ACTION_PATH}/../scripts/build.mjs deploy cat ./build/vars.txt > "$GITHUB_OUTPUT" + - name: Create draft release if pubState=pub on main + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} + run: | + TAG=$(node ${GITHUB_ACTION_PATH}/../scripts/derive-release-tag.mjs | grep '^tag=' | head -1 | cut -d= -f2) + if [[ -z "$TAG" ]]; then + echo "pubState != pub or metadata incomplete; skipping draft release." + exit 0 + fi + FORCE="" + if [[ "${{ github.event.head_commit.message }}" == *"[force-release]"* ]]; then + FORCE="1" + fi + FINAL_TAG="$TAG" + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then + if [[ -z "$FORCE" ]]; then + echo "Tag ${TAG} already exists; no-op." + exit 0 + fi + FINAL_TAG="" + for i in $(seq 2 99); do + if ! git rev-parse "refs/tags/${TAG}-${i}" >/dev/null 2>&1; then + FINAL_TAG="${TAG}-${i}" + break + fi + done + if [[ -z "$FINAL_TAG" ]]; then + echo "Could not resolve a free suffix for ${TAG} after 99 attempts." >&2 + exit 1 + fi + fi + gh release create "$FINAL_TAG" --draft --title "$FINAL_TAG" --target "$GITHUB_SHA" --generate-notes + - name: Determine which pull request we are on uses: jwalton/gh-find-current-pr@v1 if: github.event_name == 'pull_request'