Some checks failed
Tests / Required tests (pull_request) Has been cancelled
Tests / conformance test (macos-latest, 24) (pull_request) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (pull_request) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (pull_request) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (pull_request) Has been cancelled
Validate Branch Name / PR mergeability (pull_request) Successful in 21s
Changeset Required / PR mergeability (pull_request) Successful in 11s
Default Flip Documentation / PR mergeability (pull_request) Successful in 10s
Dependabot Auto-Merge / auto-merge (pull_request) Has been skipped
Docs Required / PR mergeability (pull_request) Successful in 10s
Mutation Testing / PR mergeability (pull_request) Successful in 11s
Security Scan / PR mergeability (pull_request) Successful in 11s
Tests / PR mergeability (pull_request) Successful in 11s
Tests / Base branch health (pull_request) Successful in 11s
Tests / Detect test scope (pull_request) Successful in 19s
Auto-Close Unsolicited PRs / Reject PRs without a pre-approved issue (pull_request_target) Successful in 9s
Close Draft PRs / Reject draft PRs (pull_request_target) Has been skipped
PR Target Validator / validate-target (pull_request_target) Successful in 15s
PR Template Format / Pull request template format (pull_request_target) Failing after 16s
PR Title Validator / validate-title (pull_request_target) Failing after 15s
Require Issue Link / Issue link required (pull_request_target) Failing after 14s
Validate Branch Name / check-branch (pull_request) Successful in 8s
Changeset Required / changeset-lint (pull_request) Failing after 32s
Default Flip Documentation / default-flip-documentation (pull_request) Successful in 26s
Docs Required / docs-lint (pull_request) Successful in 5m37s
Mutation Testing / Detect changed covered modules (pull_request) Successful in 19s
Security Scan / security (pull_request) Successful in 5m25s
Tests / lint-tests (pull_request) Failing after 1m57s
Tests / plugin-validate (pull_request) Successful in 1m4s
Tests / test (ubuntu-latest, 24, shard 1/3) (pull_request) Failing after 21s
Tests / test (ubuntu-latest, 24, shard 2/3) (pull_request) Failing after 20s
Tests / test (ubuntu-latest, 24, shard 3/3) (pull_request) Failing after 19s
Tests / test (ubuntu-latest, 24) (pull_request) Failing after 20s
Tests / test (inert CI) (pull_request) Has been skipped
Tests / QA loop walk (smell ratchet) (pull_request) Failing after 18s
Mutation Testing / Stryker (${{ matrix.name }}) (pull_request) Has been skipped
Tests / Coverage gate (merged shards) (pull_request) Has been skipped
Tests / Publish emitted-baseline artifact (pull_request) Has been skipped
Mutation Testing / Stryker mutation score (changed files only) (pull_request) Has been skipped
MSD is not published to npm, so /msd-update and the SessionStart update check could never find a release. Releases are now vX.Y.Z git tags plus a stable branch: - check-latest-version reads release tags via git ls-remote against the baked repository URL (latest = highest stable tag, next = incl. -rc) - /msd-update reruns the bootstrap installer pinned to the checked tag and reads the changelog from that tag - bootstrap.sh defaults to the stable branch and supports --local/--global - package identity: changelog URL on stable, manual install via msd.golem15.com - README quickstart and update how-to describe the one-line installer - track the repo-root CLAUDE.md Emitted-Drift-Ack-Growth: update.md — the update flow now explains the git-tag release lookup and pins the bootstrap installer to the checked tag, replacing the npm/npx wording
168 lines
6.8 KiB
JavaScript
168 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
/**
|
|
* Single source for MSD's published-package coordinates (issue #498).
|
|
*
|
|
* `deriveIdentity(pkg)` is the pure core: it turns a parsed package.json into
|
|
* the coordinate record every consumer needs. The generated runtime module
|
|
* `msd-core/bin/lib/package-identity.cjs` bakes those values at build
|
|
* time, because the installed tree carries no package.json with a `.name` —
|
|
* the only ones MSD stages are synthetic `{"type":"commonjs"}` markers, which
|
|
* since #2544 live inside MSD's own directories (`hooks/`, and the native
|
|
* plugin dir) rather than at the config root — so a runtime
|
|
* `require('package.json').name` resolves to `undefined` (the #378 bug this
|
|
* seam retires). Baking from package.json reconciles #378 (renames survive)
|
|
* with #2992 (the value is never an LLM runtime choice).
|
|
*/
|
|
|
|
/**
|
|
* Parse `owner/name` out of a package.json `repository.url`, stripping the
|
|
* `git+` prefix and `.git` suffix npm conventionally adds.
|
|
*/
|
|
function parseRepo(repository) {
|
|
const url = typeof repository === 'string' ? repository : (repository && repository.url) || '';
|
|
const m = url.replace(/^git\+/, '').replace(/\.git$/, '').match(/(github\.com|git\.golem15\.com)[/:]([^/]+\/[^/]+)$/);
|
|
return m ? { host: m[1], slug: m[2] } : { host: '', slug: '' };
|
|
}
|
|
|
|
function parseRepoSlug(repository) {
|
|
return parseRepo(repository).slug;
|
|
}
|
|
|
|
/**
|
|
* Raw-file URL for CHANGELOG.md on the release branch. GitHub serves raw files
|
|
* from a separate host; the golem15 Gitea forge serves them in-place from the
|
|
* `stable` branch, which always points at the latest tagged release.
|
|
*/
|
|
function changelogRawUrlFor(host, slug) {
|
|
if (!slug) return '';
|
|
if (host === 'github.com') return `https://raw.githubusercontent.com/${slug}/main/CHANGELOG.md`;
|
|
return `https://${host}/${slug}/raw/branch/stable/CHANGELOG.md`;
|
|
}
|
|
|
|
/**
|
|
* Pure: turn an npm package name into a filesystem-safe slug for cache filenames.
|
|
* Strips a leading `@`, replaces `/` with `-`, then collapses any run of
|
|
* characters that are NOT `[a-z0-9]` to a single `-`, and trims leading/trailing `-`.
|
|
*/
|
|
function slugifyPackageName(name) {
|
|
if (!name) return '';
|
|
return name
|
|
.replace(/^@/, '')
|
|
.replace(/\//g, '-')
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '');
|
|
}
|
|
|
|
/**
|
|
* Pure: package.json object -> the package identity coordinates.
|
|
*/
|
|
function deriveIdentity(pkg = {}) {
|
|
const packageName = pkg.name || '';
|
|
const binName = pkg.bin ? Object.keys(pkg.bin)[0] || '' : '';
|
|
const { host, slug: repoSlug } = parseRepo(pkg.repository);
|
|
const repoUrl = repoSlug ? `https://${host}/${repoSlug}` : '';
|
|
const changelogRawUrl = changelogRawUrlFor(host, repoSlug);
|
|
const cacheSlug = slugifyPackageName(packageName);
|
|
const updateCacheFileName = cacheSlug ? `msd-update-check-${cacheSlug}.json` : 'msd-update-check.json';
|
|
return { packageName, binName, repoSlug, repoUrl, changelogRawUrl, cacheSlug, updateCacheFileName };
|
|
}
|
|
|
|
/**
|
|
* Pure: format the manual install command. MSD is not published to npm; it is
|
|
* installed by the bootstrap script served at msd.golem15.com
|
|
* (scripts/bootstrap.sh on the `stable` branch). Shape matches the literal the
|
|
* update workflow embeds: `curl -fsSL https://msd.golem15.com | bash -s --
|
|
* [--<runtime>] --<scope>`. The runtime flag is omitted when not supplied.
|
|
*
|
|
* This function is the canonical source — `render()` serializes it verbatim
|
|
* into the generated module, so the runtime copy can never drift from it.
|
|
*/
|
|
function formatManualInstall({ scope, runtime } = {}) {
|
|
const runtimeFlag = runtime ? ` --${runtime}` : '';
|
|
return `curl -fsSL https://msd.golem15.com | bash -s --${runtimeFlag} --${scope}`;
|
|
}
|
|
|
|
const GENERATED_HEADER =
|
|
'// @generated by scripts/generate-package-identity.cjs from package.json — DO NOT EDIT.\n' +
|
|
'// Single source for MSD package coordinates (issue #498). Regenerate with:\n' +
|
|
'// node scripts/generate-package-identity.cjs\n';
|
|
|
|
/**
|
|
* Render the generated runtime module text for a derived identity. Values are
|
|
* baked as literals; `formatManualInstall` is embedded by `.toString()` so the
|
|
* runtime command builder is byte-identical to the tested source above.
|
|
*/
|
|
function render(identity) {
|
|
const { packageName, binName, repoSlug, repoUrl, changelogRawUrl, cacheSlug, updateCacheFileName } = identity;
|
|
const j = (v) => JSON.stringify(v);
|
|
return (
|
|
GENERATED_HEADER +
|
|
"'use strict';\n\n" +
|
|
`const packageName = ${j(packageName)};\n` +
|
|
`const binName = ${j(binName)};\n` +
|
|
`const repoSlug = ${j(repoSlug)};\n` +
|
|
`const repoUrl = ${j(repoUrl)};\n` +
|
|
`const changelogRawUrl = ${j(changelogRawUrl)};\n` +
|
|
`const cacheSlug = ${j(cacheSlug)};\n` +
|
|
`const updateCacheFileName = ${j(updateCacheFileName)};\n\n` +
|
|
`${formatManualInstall.toString()}\n\n` +
|
|
'function manualInstallCommand(opts = {}) {\n' +
|
|
' return formatManualInstall({ scope: opts.scope, runtime: opts.runtime });\n' +
|
|
'}\n\n' +
|
|
'module.exports = Object.freeze({\n' +
|
|
' packageName,\n' +
|
|
' // PACKAGE_NAME: back-compat alias for #516-era consumers. Baked here, so it\n' +
|
|
" // survives the installed tree's synthetic package.json (fixes the #378 undefined).\n" +
|
|
' PACKAGE_NAME: packageName,\n' +
|
|
' binName,\n' +
|
|
' repoSlug,\n' +
|
|
' repoUrl,\n' +
|
|
' changelogRawUrl,\n' +
|
|
' cacheSlug,\n' +
|
|
' updateCacheFileName,\n' +
|
|
' manualInstallCommand,\n' +
|
|
'});\n'
|
|
);
|
|
}
|
|
|
|
function main() {
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const args = process.argv.slice(2);
|
|
const check = args.includes('--check');
|
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
const out = path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'package-identity.cjs');
|
|
const rendered = render(deriveIdentity(pkg));
|
|
if (check) {
|
|
// Compare normalized content so a CRLF checkout (Windows, no .gitattributes
|
|
// eol rule) does not register as stale. Mirrors the in-process check the
|
|
// unit suite used to perform (issue #498) and the convention used by the
|
|
// other gen-* --check generators.
|
|
const norm = (s) => s.replace(/\r\n/g, '\n');
|
|
let committed = '';
|
|
try {
|
|
committed = fs.readFileSync(out, 'utf8');
|
|
} catch (e) {
|
|
if (e.code !== 'ENOENT') throw e;
|
|
}
|
|
if (norm(committed) !== norm(rendered)) {
|
|
process.stderr.write('package-identity.cjs is stale — run: node scripts/generate-package-identity.cjs\n');
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
fs.writeFileSync(out, rendered);
|
|
process.stdout.write(`wrote ${path.relative(path.join(__dirname, '..'), out)}\n`);
|
|
return 0;
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const code = main();
|
|
if (typeof code === 'number' && code !== 0) process.exitCode = code;
|
|
}
|
|
|
|
module.exports = { deriveIdentity, parseRepoSlug, slugifyPackageName, formatManualInstall, render, main };
|