Skip to content

Commit e765082

Browse files
authored
docs: restore stable Registryctl install URL (#638)
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
1 parent 9dd2844 commit e765082

15 files changed

Lines changed: 209 additions & 51 deletions

‎crates/registryctl/README.md‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@
33
`registryctl` is the adopter CLI for authoring, testing, reviewing, developing,
44
and packaging Registry Stack projects.
55

6-
Install a pinned release without cloning the repository:
6+
Download the latest released installer without cloning the repository:
77

88
```sh
9-
tag=vX.Y.Z
10-
curl -fsSLO "https://github.com/registrystack/registry-stack/releases/download/${tag}/registryctl-${tag}-install.sh"
11-
bash "./registryctl-${tag}-install.sh"
9+
curl -fsSLo registryctl-install.sh https://docs.registrystack.org/install.sh
1210
```
1311

14-
The quick installer trusts GitHub and TLS, then verifies downloaded artifacts
15-
against the release checksums. Use [`release/VERIFY.md`](../../release/VERIFY.md)
16-
when you need to authenticate release signatures and provenance before
17-
installation.
12+
The stable URL serves the installer from the latest promoted Registry Stack
13+
documentation release. The quick installer trusts Registry Docs hosting,
14+
GitHub, and TLS, then verifies downloaded artifacts against the release
15+
checksums. The separate download and execution steps preserve download
16+
failures and let you inspect the installer's tag-frozen `verify_url` before
17+
running it. Use [`release/VERIFY.md`](../../release/VERIFY.md) when you need to
18+
authenticate release signatures and provenance before installation.
19+
20+
Run the installer after applying the verification policy you need:
21+
22+
```sh
23+
bash registryctl-install.sh
24+
```
1825

1926
## Newcomer workflow
2027

‎docs/site/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ npm-debug.log*
99
# Pulled product docs (build artifacts, regenerated by scripts/sync-repo-docs.mjs)
1010
src/content/docs/products/
1111
public/products/
12+
public/install.sh
1213
.repo-docs-cache/
1314
.archive-bundles/
1415
.release-docsets/

‎docs/site/.markdownlint-cli2.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ config:
2929
- LegendList
3030
- HomeLanding
3131
- QuickstartMeta
32+
- RegistryctlInstallCommand
3233
- ProjectStarterSequence
3334
- ProjectWorkspaceJourneys
3435
- AuthoringConfigurationReference

‎docs/site/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"smoke:deployment": "node scripts/smoke-docs-deployment.mjs",
1818
"archive:snapshot": "node scripts/archive-lock.mjs snapshot",
1919
"preview": "astro preview",
20-
"generate": "node scripts/generate-data.mjs && node scripts/generate-project-starters.mjs && node scripts/generate-registryctl-example-overlays.mjs && node scripts/generate-authoring-reference.mjs && node scripts/generate-diagnostic-references.mjs && node scripts/fetch-openapi.mjs && node scripts/sync-repo-docs.mjs && node scripts/generate-sidebar.mjs",
21-
"generate:archive": "node scripts/generate-data.mjs && node scripts/generate-registryctl-example-overlays.mjs && node scripts/fetch-openapi.mjs && node scripts/sync-repo-docs.mjs && node scripts/generate-sidebar.mjs",
20+
"generate": "node scripts/generate-data.mjs && node scripts/generate-project-starters.mjs && node scripts/generate-registryctl-installer.mjs && node scripts/generate-registryctl-example-overlays.mjs && node scripts/generate-authoring-reference.mjs && node scripts/generate-diagnostic-references.mjs && node scripts/fetch-openapi.mjs && node scripts/sync-repo-docs.mjs && node scripts/generate-sidebar.mjs",
21+
"generate:archive": "node scripts/generate-data.mjs && node scripts/generate-registryctl-installer.mjs && node scripts/generate-registryctl-example-overlays.mjs && node scripts/fetch-openapi.mjs && node scripts/sync-repo-docs.mjs && node scripts/generate-sidebar.mjs",
2222
"pretest": "cargo --version",
2323
"test": "node --test \"scripts/**/*.test.mjs\"",
2424
"check:llms:built": "node scripts/check-llms.mjs",

‎docs/site/scripts/check-tutorial.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@ done
174174
# inside `sh` fences. Bump the expected count when you intentionally add or
175175
# remove a documented command.
176176
REGISTRYCTL_TUTORIALS=(
177-
"author-registry-project:74"
177+
"author-registry-project:69"
178178
"configure-project-script-adapter:49"
179-
"publish-spreadsheet-secured-registry-api:20"
179+
"publish-spreadsheet-secured-registry-api:15"
180180
"use-your-spreadsheet:9"
181181
"verify-opencrvs-claims:45"
182182
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
2+
import { dirname, resolve } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
const scriptDir = dirname(fileURLToPath(import.meta.url));
6+
const siteRoot = resolve(scriptDir, '..');
7+
const repoRoot = resolve(siteRoot, '../..');
8+
const canonicalInstaller = resolve(repoRoot, 'crates/registryctl/install.sh');
9+
const publicInstaller = resolve(siteRoot, 'public/install.sh');
10+
11+
export async function generateRegistryctlInstaller({
12+
source = canonicalInstaller,
13+
destination = publicInstaller,
14+
docset = process.env.DOCS_DOCSET,
15+
} = {}) {
16+
let contents = await readFile(source, 'utf8');
17+
if (!contents.startsWith('#!/usr/bin/env bash')) {
18+
throw new Error(`registryctl installer has an unexpected interpreter: ${source}`);
19+
}
20+
if (/^v\d+\.\d+\.\d+$/.test(docset ?? '')) {
21+
const versionPattern = /^default_version="v\d+\.\d+\.\d+"$/gm;
22+
const matches = contents.match(versionPattern) ?? [];
23+
if (matches.length !== 1) {
24+
throw new Error(`registryctl installer must declare one default_version: ${source}`);
25+
}
26+
contents = contents.replace(versionPattern, `default_version="${docset}"`);
27+
}
28+
29+
await mkdir(dirname(destination), { recursive: true });
30+
const temporary = `${destination}.${process.pid}.tmp`;
31+
try {
32+
await writeFile(temporary, contents, { flag: 'wx', mode: 0o755 });
33+
await rename(temporary, destination);
34+
} catch (error) {
35+
await unlink(temporary).catch(() => {});
36+
throw error;
37+
}
38+
39+
return contents;
40+
}
41+
42+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
43+
await generateRegistryctlInstaller();
44+
console.log('Generated public Registryctl installer -> public/install.sh');
45+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import assert from 'node:assert/strict';
2+
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
3+
import { tmpdir } from 'node:os';
4+
import { dirname, resolve } from 'node:path';
5+
import test from 'node:test';
6+
import { fileURLToPath } from 'node:url';
7+
8+
import { generateRegistryctlInstaller } from './generate-registryctl-installer.mjs';
9+
10+
const siteRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11+
const repoRoot = resolve(siteRoot, '../..');
12+
13+
test('publishes the canonical Registryctl installer byte for byte for current docs', async (t) => {
14+
const root = await mkdtemp(resolve(tmpdir(), 'registryctl-public-installer-'));
15+
t.after(() => rm(root, { recursive: true, force: true }));
16+
const source = resolve(repoRoot, 'crates/registryctl/install.sh');
17+
const destination = resolve(root, 'public/install.sh');
18+
19+
await generateRegistryctlInstaller({ source, destination });
20+
21+
assert.equal(await readFile(destination, 'utf8'), await readFile(source, 'utf8'));
22+
assert.notEqual((await stat(destination)).mode & 0o111, 0);
23+
});
24+
25+
test('binds a release-docset installer to that exact version', async (t) => {
26+
const root = await mkdtemp(resolve(tmpdir(), 'registryctl-release-installer-'));
27+
t.after(() => rm(root, { recursive: true, force: true }));
28+
const source = resolve(root, 'install.sh');
29+
const destination = resolve(root, 'public/install.sh');
30+
await writeFile(source, '#!/usr/bin/env bash\ndefault_version="v0.16.3"\necho "$default_version"\n');
31+
32+
await generateRegistryctlInstaller({ source, destination, docset: 'v0.17.0' });
33+
34+
assert.equal(
35+
await readFile(destination, 'utf8'),
36+
'#!/usr/bin/env bash\ndefault_version="v0.17.0"\necho "$default_version"\n',
37+
);
38+
assert.notEqual((await stat(destination)).mode & 0o111, 0);
39+
});
40+
41+
test('current and archived docs generation publish the stable installer URL', async () => {
42+
const packageJson = JSON.parse(await readFile(resolve(siteRoot, 'package.json'), 'utf8'));
43+
const generator = 'node scripts/generate-registryctl-installer.mjs';
44+
45+
assert.match(packageJson.scripts.generate, new RegExp(generator.replaceAll('.', '\\.')));
46+
assert.match(packageJson.scripts['generate:archive'], new RegExp(generator.replaceAll('.', '\\.')));
47+
});

‎docs/site/scripts/page-markdown.test.mjs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ test('raw body content is included verbatim', () => {
7878
assert.ok(out.includes(body), 'body content not found in output');
7979
});
8080

81+
test('expands the Registryctl installer component for machine-readable Markdown', () => {
82+
const body = 'Install:\n\n<RegistryctlInstallCommand />\n\nThen continue.';
83+
const current = buildPageMarkdown('Title', undefined, body);
84+
const archived = buildPageMarkdown('Title', undefined, body, '/v/0.17.0/');
85+
86+
assert.match(
87+
current,
88+
/curl -fsSLo registryctl-install\.sh https:\/\/docs\.registrystack\.org\/install\.sh/,
89+
);
90+
assert.match(
91+
archived,
92+
/curl -fsSLo registryctl-install\.sh https:\/\/docs\.registrystack\.org\/v\/0\.17\.0\/install\.sh/,
93+
);
94+
assert.doesNotMatch(current, /RegistryctlInstallCommand/);
95+
assert.doesNotMatch(archived, /RegistryctlInstallCommand/);
96+
});
97+
98+
test('per-page Markdown route binds component expansion to the build base path', () => {
99+
const routeSource = readFileSync(routePath, 'utf8');
100+
assert.match(routeSource, /entry\.body \?\? '',\s*import\.meta\.env\.BASE_URL,/);
101+
});
102+
81103
test('full output structure: header + title + description + body', () => {
82104
const out = buildPageMarkdown('My Page', 'Summary here.', 'Page body.');
83105
const lines = out.split('\n');

‎docs/site/scripts/registryctl-tutorial.test.mjs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,19 +488,23 @@ test('current reader pages keep public starters and current command roots', () =
488488
read('src/content/docs/tutorials/verify-opencrvs-claims.mdx'),
489489
];
490490
const currentText = pages.join('\n');
491+
const installerCommand = read('src/components/RegistryctlInstallCommand.astro');
491492

492-
assert.match(currentText, /tag="v<major>\.<minor>\.<patch>"/);
493-
assert.match(currentText, /registryctl-\$\{tag\}-install\.sh/);
493+
assert.match(currentText, /RegistryctlInstallCommand/);
494+
assert.match(installerCommand, /import\.meta\.env\.BASE_URL/);
495+
assert.match(installerCommand, /curl -fsSLo registryctl-install\.sh/);
496+
assert.match(currentText, /bash registryctl-install\.sh/);
497+
assert.doesNotMatch(installerCommand, /\| bash/);
494498
assert.match(currentText, /release\/VERIFY\.md/);
495-
assert.match(currentText, /quick installation path trusts GitHub and TLS/);
499+
assert.match(currentText, /quick installation path trusts Registry Docs hosting, GitHub, and TLS/);
496500
assert.match(currentText, /--template http/);
497501
assert.match(currentText, /--template spreadsheet/);
498502
assert.match(currentText, /registryctl dev smoke/);
499503
assert.match(currentText, /registryctl check/);
500504
assert.match(currentText, /registryctl build/);
501505
assert.doesNotMatch(
502506
currentText,
503-
/registryctl (?:preflight|start|stop|restart|smoke|add notary)|init --from|test --live|Bruno/,
507+
/tag="v<major>|installer=|curl --proto|releases\/download\/.*install\.sh|bash "\$installer"|registryctl (?:preflight|start|stop|restart|smoke|add notary)|init --from|test --live|Bruno/,
504508
);
505509
assert.doesNotMatch(currentText, /TODO:|Evidence:/);
506510
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
const installerUrl = new URL(
3+
`${import.meta.env.BASE_URL}install.sh`,
4+
'https://docs.registrystack.org',
5+
).href;
6+
const command = `curl -fsSLo registryctl-install.sh ${installerUrl}`;
7+
---
8+
9+
<pre><code class="language-sh">{command}</code></pre>

0 commit comments

Comments
 (0)