From c7c9752384835d209109d4571befe8b6d8dc03a3 Mon Sep 17 00:00:00 2001 From: Tommy Keswick Date: Tue, 1 Sep 2026 16:40:34 -0700 Subject: [PATCH 1/2] Generate src/version.js from codemeta.json instead of tracking it The CDN bundle reports version 0.0.12, released 2025-07-17, while codemeta.json says 0.0.16, released 2026-01-28. That string is in production JavaScript today, four releases and seven months out of date. The cause is a gap in the tooling rather than anyone forgetting. cmt can only write to the repository root, so the Makefile generates version.js there and moves it into src/. Nothing runs that except a person typing `make`, and `deno task build` used to write its own bundle over the top of it. tools/version.js writes the file from inputs already in the repository: version codemeta.json "version" releaseDate codemeta.json "datePublished" releaseHash git rev-parse --short HEAD licenseText LICENSE.txt, or LICENSE Output is byte-identical to cmt's, verified against the committed file. This is not a replacement for cmt -- it covers the one file cmt cannot write to the right place, and cmt keeps README.md and CITATION.cff. `deno task build` runs it first, so a fresh clone needs Deno and nothing else, and the file cannot drift because it no longer exists between builds. It is gitignored for the same reason: it is build output, and it was tracked before, so without the ignore the next `git add -A` would restore the stale copy. `deno task release` becomes an alias for `deno task build` rather than a second copy of the same commands. The two had already drifted -- release omitted the version step, so on a clean checkout it would bundle against a file that does not exist. Two definitions of one build is what allowed that, and it is the same drift that let release zips and local builds disagree. The task CI exercises is the one that stays correct; nothing ever ran release. Co-Authored-By: Claude Opus 5 --- .gitignore | 6 +++++ deno.json | 5 ++-- src/version.js | 21 --------------- tools/generate-version.js | 57 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 23 deletions(-) delete mode 100644 src/version.js create mode 100755 tools/generate-version.js diff --git a/.gitignore b/.gitignore index 0f8a8f1..c9e96e8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,12 @@ bin/ dist/ scratch/ +# Generated from codemeta.json by `deno task generate-version`, which +# `deno task build` runs first. It was tracked previously, which is why the +# CDN bundle reported 0.0.12 while codemeta.json said 0.0.16 -- nothing +# regenerated it. +/src/version.js + # The site is assembled by CI and uploaded as an artifact, never committed. /_site/ diff --git a/deno.json b/deno.json index e73060a..4ce6a3b 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,8 @@ { "tasks": { - "build": "deno bundle --platform browser --outdir=./dist src/*.js ; deno bundle --platform browser --output=dist/cl-web-components.js mod.js", - "release": "deno bundle --platform=browser --outdir=./dist src/*.js ; deno bundle --platform=browser --output=dist/cl-web-components.js mod.js" + "generate-version": "deno run --allow-read --allow-write --allow-run tools/generate-version.js", + "build": "deno task generate-version && deno bundle --platform browser --outdir=./dist src/*.js ; deno bundle --platform browser --output=dist/cl-web-components.js mod.js", + "release": "deno task build" }, "imports": { "@std/assert": "jsr:@std/assert@^1.0.11", diff --git a/src/version.js b/src/version.js deleted file mode 100644 index 1e39bb8..0000000 --- a/src/version.js +++ /dev/null @@ -1,21 +0,0 @@ -// CL-web-components version and license information. - -export const version = '0.0.12', -releaseDate = '2025-07-17', -releaseHash = '9729d57', -licenseText = ` - -Copyright (c) 2025, Caltech -All rights not granted herein are expressly reserved by Caltech. - -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. - -`; \ No newline at end of file diff --git a/tools/generate-version.js b/tools/generate-version.js new file mode 100755 index 0000000..4b30611 --- /dev/null +++ b/tools/generate-version.js @@ -0,0 +1,57 @@ +// Generates src/version.js from codemeta.json. This script is a build tool; +// src/version.js is its output and is not committed. +// +// This is the same output CMTools' version.js generator produces, without +// requiring cmt to be installed. Nothing here is specific to this project: +// any Deno project with a codemeta.json and a license file can use it +// unchanged. +// +// version codemeta.json "version" +// releaseDate codemeta.json "datePublished" +// releaseHash git rev-parse --short HEAD +// licenseText LICENSE.txt, or LICENSE +// +// Run via `deno task generate-version`; `deno task build` depends on it. + +const OUT = "src/version.js"; + +const meta = JSON.parse(await Deno.readTextFile("codemeta.json")); + +let licenseText = ""; +for (const name of ["LICENSE.txt", "LICENSE"]) { + try { + licenseText = await Deno.readTextFile(name); + break; + } catch (err) { + if (!(err instanceof Deno.errors.NotFound)) throw err; + } +} +if (licenseText === "") { + console.error("no LICENSE.txt or LICENSE found"); + Deno.exit(1); +} + +// The text is interpolated into a template literal. +const escaped = licenseText.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + .replace(/\$\{/g, "\\${"); + +const git = new Deno.Command("git", { + args: ["rev-parse", "--short", "HEAD"], +}).outputSync(); +if (!git.success) { + console.error("git rev-parse failed"); + Deno.exit(1); +} +const releaseHash = new TextDecoder().decode(git.stdout).trim(); + +const src = `// ${meta.name} version and license information. + +export const version = '${meta.version}', +releaseDate = '${meta.datePublished}', +releaseHash = '${releaseHash}', +licenseText = \` +${escaped} +\`;`; + +await Deno.writeTextFile(OUT, src); +console.log(`${OUT}: ${meta.version} ${meta.datePublished} ${releaseHash}`); From aaa69ee6405d194eb75e9cd5e382e4b1a2984807 Mon Sep 17 00:00:00 2001 From: Tommy Keswick Date: Wed, 2 Sep 2026 09:09:56 -0700 Subject: [PATCH 2/2] Record why the version generator is vendored ADR-0004. The generator has to live somewhere, and the options that would share it across projects are all blocked or out of scope: cmt cannot write to src/, metadatatools is the wrong domain and its only version is yanked, raw GitHub URLs are served as text/plain and Deno refuses them, and publishing a package to JSR is not something we are taking on now. Vendoring is a placeholder. The script is deliberately project-agnostic so that replacing it costs one line in deno.json. --- ...04-vendor-the-version-generator-for-now.md | 110 ++++++++++++++++++ tools/generate-version.js | 3 + 2 files changed, 113 insertions(+) create mode 100644 docs/decisions/0004-vendor-the-version-generator-for-now.md diff --git a/docs/decisions/0004-vendor-the-version-generator-for-now.md b/docs/decisions/0004-vendor-the-version-generator-for-now.md new file mode 100644 index 0000000..e2ed425 --- /dev/null +++ b/docs/decisions/0004-vendor-the-version-generator-for-now.md @@ -0,0 +1,110 @@ +# 4. Vendor the version generator until it can be shared + +- Status: accepted +- Date: 2026-09-02 + +## Context and Problem Statement + +[ADR-0003](0003-separate-sources-from-generated-files.md) established that build +output is not committed. `src/version.js` is build output that was committed +anyway, and it went stale: it reported `0.0.12` while `codemeta.json` said +`0.0.16`, so that was the version served from the CDN for over a year. + +It went stale because nothing wrote it. `cmt` is the generator, and the +Makefile invoked it: + +```make +version.js: .FORCE + cmt codemeta.json version.js +``` + +That writes the repository root. The bundler reads `src/version.js`. The two +have been different files since the `src/` reorganization in July 2025, and +the target cannot be corrected in place, because `cmt` treats the output +filename as the format identifier (`const format = outputName`) — so +`cmt codemeta.json src/version.js` exits with `unsupported format`. + +So the file must be generated by something, and the question is only where +that something lives. It is not specific to this project: any Deno project +with a `codemeta.json` and a license file needs the same thing. + +## Decision + +Vendor it. `tools/generate-version.js` is a 57-line script in this repository, +run by `deno task generate-version`, which `deno task build` depends on. + +**This is a placeholder for a shared package**, not a preferred design. The +script is deliberately project-agnostic so that moving it costs one line. + +## Considered Options + +1. Keep using `cmt` via the Makefile +2. Fix `cmt` to write subdirectories, and publish it to JSR +3. Add the function to `@caltechlibrary/metadatatools` +4. Reference a script by raw GitHub URL +5. Publish a new package to JSR from `caltechlibrary/workflows` +6. Vendor a script in this repository + +## Decision Outcome + +**Chosen: option 6, with option 5 the most likely successor.** + +### Option 1: keep using `cmt` — rejected + +It cannot write to `src/`. This is the defect being fixed, not an alternative +to it. It also puts a compiled binary, installed via a script, on the critical +path of every contributor's first build. + +### Option 2: fix and publish `cmt` — rejected for now + +The best long-term answer, and the least duplicative: `cmt` stays canonical +and every Deno repository benefits. But it is two changes to a tool this +repository does not own, and the JSR half has unexplained history — see +option 3. + +### Option 3: add it to `metadatatools` — rejected + +Wrong domain and unavailable. The package is scholarly identifier validation +(`doi.ts`, `arxiv.ts`, `isbn.ts`, `orcid.ts`, `ror.ts`), not build tooling. +It has one published version, `0.0.6`, and it is yanked; `latest` is `null`. +CMTools imports that yanked version. + +### Option 4: raw GitHub URL — rejected + +Does not work. `raw.githubusercontent.com` serves `text/plain; charset=utf-8`, +which Deno refuses to load as a module. + +### Option 5: a new JSR package — deferred + +Viable. A `deno/` directory in `caltechlibrary/workflows`, published to the +existing `@caltechlibrary` scope via OIDC, consumed as +`deno run -A jsr:@caltechlibrary/...`. A contributor would never clone the +workflows repository — the module arrives the same way `@std/csv` already +does. Deferred because standing up JSR publishing is out of scope right now. + +### Option 6: vendor it — chosen + +Fixes the defect today with no new infrastructure and no dependency on +decisions owned by others. Twelve repositories in the organization have a +`deno.json`, but only this one has the stale-version defect, so the cost of +duplication is currently theoretical. + +## Consequences + +Good: + +- A fresh clone builds a correct version with only `deno` installed. No `cmt`, + no `make`. +- Nothing about the offline story changes. `src/textarea-csv.js` already + imports `jsr:@std/csv`, so a cold cache has always needed the network. + +Bad, and accepted: + +- It is a copy. If another Deno repository adopts it, there are two, and they + will drift. +- Replacing it means remembering this decision, which is why it is written + here rather than in the pull request. + +## More Information + +- The delta to option 5 is one line in `deno.json` and deleting one file. diff --git a/tools/generate-version.js b/tools/generate-version.js index 4b30611..78da849 100755 --- a/tools/generate-version.js +++ b/tools/generate-version.js @@ -12,6 +12,9 @@ // licenseText LICENSE.txt, or LICENSE // // Run via `deno task generate-version`; `deno task build` depends on it. +// +// This script is vendored, not shared. See +// docs/decisions/0004-vendor-the-version-generator-for-now.md const OUT = "src/version.js";