Skip to content

Commit c37cc78

Browse files
committed
List the build tool and every bundle on the skill page
/h5p/skill/ wrote out the full address of each of the skill's files but none of the files its step 3 fetches where npm cannot be reached, so a chat with the skill's text alone had to put a bundle's address together from its type, which a fetch tool may refuse. The page now lists h5p-author.mjs and every bundle /h5p/bundles/ serves, each at its full address as its link and its text, named by the author package's contentTypes, the list writeBundles writes from. The SKILL.md test no longer expects the type-file address 0.9.0 names, so the pin can move to the next release without changing it. Closes #17
1 parent 2e2ae0d commit c37cc78

4 files changed

Lines changed: 56 additions & 9 deletions

File tree

‎e2e/skill.spec.ts‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { expect, test } from '@playwright/test'
55

66
/*
77
* /h5p/skill/ as a chat that cannot install the h5p-author skill reads it: a page that writes out
8-
* the full address of every file, since a chat's fetch tool may open only addresses it has seen
9-
* whole; each file there as the pinned author package carries it; and every address SKILL.md gives,
10-
* relative to itself or written out, served by this site.
8+
* the full address of every file, and of the build tool and every bundle in /h5p/bundles/, since a
9+
* chat's fetch tool may open only addresses it has seen whole; each file there as the pinned author
10+
* package carries it; and every address SKILL.md gives, relative to itself or written out, served
11+
* by this site.
1112
*/
1213

1314
const SITE = 'https://missing-elements.github.io'
@@ -38,6 +39,17 @@ test('the page writes out the full address of every file of the skill, as its li
3839
expect([...listing.files].sort()).toEqual(files)
3940
})
4041

42+
test('writes out the full address of the build tool and of every bundle the site serves, as its link and its text', async ({ page, request }) => {
43+
// The page names the bundles before Vite writes them; the listing is what Vite wrote.
44+
const listing = await (await request.get('/h5p/bundles/index.json')).json() as { tool: string, bundles: Array<{ file: string }> }
45+
expect(listing.bundles.length).toBeGreaterThan(40)
46+
await page.goto('/h5p/skill/')
47+
const links = await page.locator(`main a[href^="${SITE}/h5p/bundles/"]`).evaluateAll((anchors) =>
48+
anchors.map((anchor) => ({ href: anchor.getAttribute('href'), text: anchor.textContent?.trim() })))
49+
expect(links.map(({ href }) => href)).toEqual([listing.tool, ...listing.bundles.map(({ file }) => file)].map((file) => `${SITE}/h5p/bundles/${file}`))
50+
for (const { href, text } of links) expect(text).toBe(href)
51+
})
52+
4153
test('serves each file at its address as the pinned author package carries it', async ({ request }) => {
4254
for (const file of await carried()) {
4355
const response = await request.get(`/h5p/skill/${file}`)
@@ -52,8 +64,10 @@ test('serves every address SKILL.md gives, relative to itself or on this site',
5264
const relativeLinks = [...skill.matchAll(/\]\((?![a-z]+:|#)([^)\s]+)\)/g)].map(([, link]) => new URL(link, here).pathname)
5365
expect(relativeLinks).toEqual(expect.arrayContaining(['/h5p/skill/LICENSE', '/h5p/skill/references/authoring.md']))
5466
// Its addresses on this site, with a content type for the name it leaves to fill in, and no fragment.
67+
// Which they are moves with the release: since h5p-author 0.9.1 it names this page for a type's
68+
// file, where 0.9.0 named /h5p/build/types/<Type>.md (h5p-offline-player#180).
5569
const filled = skill.replace(/<type>/gi, 'H5P.QuestionSet')
5670
const onSite = [...new Set([...filled.matchAll(/https:\/\/missing-elements\.github\.io(\/[^\s`)>"'#]*)/g)].map(([, path]) => path))]
57-
expect(onSite).toEqual(expect.arrayContaining(['/h5p/build/types/H5P.QuestionSet.md', '/h5p/bundles/H5P.QuestionSet.zip', '/h5p/build/']))
71+
expect(onSite).toEqual(expect.arrayContaining(['/h5p/bundles/H5P.QuestionSet.zip', '/h5p/build/']))
5872
for (const path of [...relativeLinks, ...onSite]) expect((await request.get(path)).ok(), path).toBe(true)
5973
})

‎src/_data/skill.js‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { existsSync, readdirSync, readFileSync } from "node:fs";
22
import { join, relative, sep } from "node:path";
3+
import { contentTypes, openPack } from "@missing-elements/h5p-author";
34

45
// The h5p-author skill as the pinned @missing-elements/h5p-author carries it since 0.9.0: its
56
// SKILL.md, licence and references/, which eleventy.config.js copies to /h5p/skill/ as they are.
@@ -14,10 +15,23 @@ const references = readdirSync(`${author}/references`, { recursive: true, withFi
1415
.sort();
1516
const types = references.filter((path) => path.startsWith("references/types/"));
1617

18+
// What a build where npm cannot be reached fetches (SKILL.md's step 3), which vite.config.ts
19+
// writes to /h5p/bundles/ after this page: the command line, and a bundle per content type, named
20+
// by the author package's own list of them, the one its `writeBundles` writes from.
21+
const pack = await openPack();
22+
let bundles;
23+
try {
24+
bundles = (await contentTypes(pack)).map((part) => `${part.library.machineName}.zip`);
25+
} finally {
26+
await pack.close();
27+
}
28+
1729
export default {
1830
version,
1931
// What SKILL.md names and the type files, apart, as the page lists them; `files` is every one.
2032
named: [...references.filter((path) => !types.includes(path)), "LICENSE"],
2133
types,
2234
files: ["SKILL.md", ...references.filter((path) => !types.includes(path)), "LICENSE", ...types],
35+
// In /h5p/bundles/, the tool first.
36+
bundles: ["h5p-author.mjs", ...bundles],
2337
};

‎src/h5p/skill/index.njk‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
---
22
layout: base.njk
33
title: The h5p-author skill · Missing Elements
4-
description: The h5p-author skill's files, each at its full address, for an AI chat that cannot install the skill.
4+
description: The h5p-author skill's files and those its build fetches, each at its full address, for an AI chat that cannot install the skill.
55
# No script but the site's: base.njk writes the policy first in <head>, and the color-scheme script
66
# is added to script-src by its hash at build (eleventy.config.js).
77
csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; object-src 'none'; base-uri 'self'"
88
---
99
{#- Every address is written out whole, as its link's text too: a chat's fetch tool may open only
1010
the addresses it has seen in full, and a relative link, or SKILL.md's <Type>, is none. -#}
1111
{%- set base = site.url + "/h5p/skill/" -%}
12+
{%- set bundlesBase = site.url + "/h5p/bundles/" -%}
1213
<main class="content skill" id="main">
1314
<header class="docs-header">
1415
<a class="brand build-brand" href="/"><img src="/assets/logo.svg" alt="" width="24" height="24">{{ site.name }}</a>
@@ -17,13 +18,15 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; o
1718
{% include "theme-switch.njk" %}
1819
</div>
1920
<p class="lead">
20-
Its files, each at its full address, for an AI chat that cannot install the skill.
21+
Its files and those its build fetches, each at its full address, for an AI chat that cannot
22+
install the skill.
2123
</p>
2224
<p>
2325
Read <code>SKILL.md</code> first and follow its steps. A file it names, such as
2426
<code>references/authoring.md</code> or a content type's <code>references/types/&lt;Type&gt;.md</code>,
25-
is the one listed here under that path. These are the files of h5p-author {{ skill.version }},
26-
the release the build page at <a href="{{ site.url }}/h5p/build/">{{ site.url }}/h5p/build/</a> runs.
27+
is the one listed here under that path, and a bundle its step 3 fetches is listed last. These
28+
are the files of h5p-author {{ skill.version }}, the release the build page at
29+
<a href="{{ site.url }}/h5p/build/">{{ site.url }}/h5p/build/</a> runs.
2730
</p>
2831
<p>
2932
Where skills can be installed, install it instead:
@@ -62,6 +65,21 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; o
6265
</ul>
6366
</section>
6467

68+
<section aria-labelledby="bundles-heading">
69+
<h2 id="bundles-heading">To build where npm cannot be reached</h2>
70+
<p>
71+
The build tool, which runs with Node and nothing installed, and a bundle per content type:
72+
the libraries a build of that type reads. Fetch the tool and the bundle of the source's own
73+
type, as step 3 of <code>SKILL.md</code> says.
74+
</p>
75+
<ul class="skill-files">
76+
{%- for file in skill.bundles %}
77+
<li><a href="{{ bundlesBase }}{{ file }}">{{ bundlesBase }}{{ file }}</a>
78+
{%- if file == "h5p-author.mjs" %}: the build tool{% endif %}</li>
79+
{%- endfor %}
80+
</ul>
81+
</section>
82+
6583
<footer class="docs-footer">
6684
<p>
6785
The skill and <code>@missing-elements/h5p-author</code> are MIT, from

‎vite.config.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import { build, defineConfig, type Plugin } from 'vite'
1818
* - `/h5p/bundles/`, for a chat that reaches no npm registry (see `bundles()`).
1919
*
2020
* `/h5p/skill/`, the h5p-author skill's files and a page of their addresses, is Eleventy's
21-
* (`eleventy.config.js`, `src/h5p/skill/`).
21+
* (`eleventy.config.js`, `src/h5p/skill/`). The page lists the bundles too, written before them:
22+
* it names them from the author package's `contentTypes`, the list `writeBundles` writes from.
2223
*
2324
* Moved here from embed-my.github.io's `/build` and `/bundles/` (#7).
2425
*/

0 commit comments

Comments
 (0)