feat: publish Toolbox 0.18.0 with sixteen new tools

This commit is contained in:
2026-09-01 03:02:08 +02:00
parent d2d0c9098d
commit 08a01d843a
15 changed files with 508 additions and 137 deletions
+58 -15
View File
@@ -22,7 +22,39 @@ const nginxConfig = readFileSync(
);
const releaseLock = JSON.parse(
readFileSync(path.join(projectRoot, 'release', 'toolbox.lock.json'), 'utf8')
) as { releaseVersion: string };
) as { releaseVersion: string; apps: Array<{ target: string }> };
const developmentCatalogue = JSON.parse(
readFileSync(path.join(projectRoot, 'public', 'toolbox.catalog.json'), 'utf8')
) as { apps: Array<{ manifest: string; enabled: boolean }> };
const expectedAppTargets = [
'pdf',
'xslt',
'onenote',
'av',
'regex',
'svg',
'auth',
'sudoku',
'colour',
'office',
'image',
'file',
'epub',
'archive',
'privacy',
'crypto',
'barcode',
'network',
'geo',
'helper',
'rand',
'data',
'diff',
'time',
'text',
'unicode',
] as const;
const immutableCacheControl = 'public, max-age=31536000, immutable';
const immutableUriPatterns = Array.from(
@@ -85,18 +117,7 @@ describe('production deployment', () => {
expect(containerfile).toContain(
'COPY --from=release --chown=101:101 /tmp/toolbox/'
);
for (const app of [
'pdf',
'xslt',
'onenote',
'av',
'regex',
'svg',
'auth',
'sudoku',
'colour',
'office',
]) {
for (const app of expectedAppTargets) {
expect(containerfile).toContain(
`test -f /tmp/toolbox/apps/${app}/toolbox-app.json`
);
@@ -104,6 +125,18 @@ describe('production deployment', () => {
expect(containerfile).toMatch(/\nUSER 101:101\nEXPOSE 8080\n/u);
});
it('keeps the development catalogue aligned with all release targets', () => {
expect(releaseLock.apps.map(({ target }) => target)).toEqual(
expectedAppTargets
);
expect(developmentCatalogue.apps).toEqual(
expectedAppTargets.map((target) => ({
manifest: `./apps/${target}/toolbox-app.json`,
enabled: true,
}))
);
});
it('uses only the requested external Traefik network and redirects HTTP to HTTPS', () => {
expect(compose).toContain('dockerfile: Containerfile.release');
expect(compose).toContain('traefik.enable: "true"');
@@ -169,9 +202,9 @@ describe('production deployment', () => {
expect(compose).not.toContain('addideas-auth-stripprefix');
});
it('grants camera capture only to the isolated authentication hostname', () => {
it('grants camera only to the isolated authentication host and barcode path', () => {
expect(nginxConfig).toMatch(
/map \$host \$toolbox_permissions_policy\s*\{\s*default "camera=\(\), microphone=\(\), geolocation=\(\), payment=\(\), usb=\(\)";\s*"auth\.toolbox\.add-ideas\.de" "camera=\(self\), microphone=\(\), geolocation=\(\), payment=\(\), usb=\(\)";\s*\}/mu
/map "\$host:\$uri" \$toolbox_permissions_policy\s*\{\s*default "camera=\(\), microphone=\(\), geolocation=\(\), payment=\(\), usb=\(\)";\s*"~\^auth\\\.toolbox\\\.add-ideas\\\.de:" "camera=\(self\), microphone=\(\), geolocation=\(\), payment=\(\), usb=\(\)";\s*"~\^\[\^:\]\+:\/apps\/barcode\(\?:\/\|\$\)" "camera=\(self\), microphone=\(\), geolocation=\(\), payment=\(\), usb=\(\)";\s*\}/mu
);
expect(nginxConfig).toContain(
'add_header Permissions-Policy $toolbox_permissions_policy always;'
@@ -181,6 +214,16 @@ describe('production deployment', () => {
).toHaveLength(1);
});
it('allows RANDOM.ORG only for the explicitly opt-in Random Tools path', () => {
expect(nginxConfig).toMatch(
/map \$uri \$toolbox_connect_sources\s*\{\s*default "'self'";\s*"~\^\/apps\/rand\(\?:\/\|\$\)" "'self' https:\/\/www\.random\.org";\s*\}/mu
);
expect(nginxConfig).toMatch(
/^\s*add_header Content-Security-Policy "[^"]*connect-src \$toolbox_connect_sources;[^"]*" always;$/mu
);
expect(nginxConfig.match(/https:\/\/www\.random\.org/gu)).toHaveLength(1);
});
it('quotes nginx regular expressions that contain brace quantifiers', () => {
expect(nginxConfig).toContain(
'"~^/(?:.*/)?assets/.*-[A-Za-z0-9_-]{8,}\\.[^/]+$"'