Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions e2e/skill.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { expect, test } from '@playwright/test'

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

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

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 }) => {
// The page names the bundles before Vite writes them; the listing is what Vite wrote.
const listing = await (await request.get('/h5p/bundles/index.json')).json() as { tool: string, bundles: Array<{ file: string }> }
expect(listing.bundles.length).toBeGreaterThan(40)
await page.goto('/h5p/skill/')
const links = await page.locator(`main a[href^="${SITE}/h5p/bundles/"]`).evaluateAll((anchors) =>
anchors.map((anchor) => ({ href: anchor.getAttribute('href'), text: anchor.textContent?.trim() })))
expect(links.map(({ href }) => href)).toEqual([listing.tool, ...listing.bundles.map(({ file }) => file)].map((file) => `${SITE}/h5p/bundles/${file}`))
for (const { href, text } of links) expect(text).toBe(href)
})

test('serves each file at its address as the pinned author package carries it', async ({ request }) => {
for (const file of await carried()) {
const response = await request.get(`/h5p/skill/${file}`)
Expand All @@ -54,6 +66,9 @@ test('serves every address SKILL.md gives, relative to itself or on this site',
// Its addresses on this site, with a content type for the name it leaves to fill in, and no fragment.
const filled = skill.replace(/<type>/gi, 'H5P.QuestionSet')
const onSite = [...new Set([...filled.matchAll(/https:\/\/missing-elements\.github\.io(\/[^\s`)>"'#]*)/g)].map(([, path]) => path))]
expect(onSite).toEqual(expect.arrayContaining(['/h5p/build/types/H5P.QuestionSet.md', '/h5p/bundles/H5P.QuestionSet.zip', '/h5p/build/']))
expect(onSite).toEqual(expect.arrayContaining(['/h5p/bundles/H5P.QuestionSet.zip', '/h5p/build/']))
Comment thread
alekswebnet marked this conversation as resolved.
Resolved
// A type's file, at an address of its own as h5p-author 0.9.0 gives it, or from this page, which
// writes each one out (the first test), as the release after it does (h5p-offline-player#180).
expect(onSite.some((path) => path === '/h5p/build/types/H5P.QuestionSet.md' || path === '/h5p/skill/'), 'a way to a type\'s file').toBe(true)
for (const path of [...relativeLinks, ...onSite]) expect((await request.get(path)).ok(), path).toBe(true)
})
14 changes: 14 additions & 0 deletions src/_data/skill.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join, relative, sep } from "node:path";
import { contentTypes, openPack } from "@missing-elements/h5p-author";

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

// What a build where npm cannot be reached fetches (SKILL.md's step 3), which vite.config.ts
// writes to /h5p/bundles/ after this page: the command line, and a bundle per content type, named
// by the author package's own list of them, the one its `writeBundles` writes from.
const pack = await openPack();
let bundles;
try {
bundles = (await contentTypes(pack)).map((part) => `${part.library.machineName}.zip`);
} finally {
await pack.close();
}

export default {
version,
// What SKILL.md names and the type files, apart, as the page lists them; `files` is every one.
named: [...references.filter((path) => !types.includes(path)), "LICENSE"],
types,
files: ["SKILL.md", ...references.filter((path) => !types.includes(path)), "LICENSE", ...types],
// In /h5p/bundles/, the tool first.
bundles: ["h5p-author.mjs", ...bundles],
};
26 changes: 22 additions & 4 deletions src/h5p/skill/index.njk
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
---
layout: base.njk
title: The h5p-author skill · Missing Elements
description: The h5p-author skill's files, each at its full address, for an AI chat that cannot install the skill.
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.
# No script but the site's: base.njk writes the policy first in <head>, and the color-scheme script
# is added to script-src by its hash at build (eleventy.config.js).
csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; object-src 'none'; base-uri 'self'"
---
{#- Every address is written out whole, as its link's text too: a chat's fetch tool may open only
the addresses it has seen in full, and a relative link, or SKILL.md's <Type>, is none. -#}
{%- set base = site.url + "/h5p/skill/" -%}
{%- set bundlesBase = site.url + "/h5p/bundles/" -%}
<main class="content skill" id="main">
<header class="docs-header">
<a class="brand build-brand" href="/"><img src="/assets/logo.svg" alt="" width="24" height="24">{{ site.name }}</a>
Expand All @@ -17,13 +18,15 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; o
{% include "theme-switch.njk" %}
</div>
<p class="lead">
Its files, each at its full address, for an AI chat that cannot install the skill.
Its files and those its build fetches, each at its full address, for an AI chat that cannot
install the skill.
</p>
<p>
Read <code>SKILL.md</code> first and follow its steps. A file it names, such as
<code>references/authoring.md</code> or a content type's <code>references/types/&lt;Type&gt;.md</code>,
is the one listed here under that path. These are the files of h5p-author {{ skill.version }},
the release the build page at <a href="{{ site.url }}/h5p/build/">{{ site.url }}/h5p/build/</a> runs.
is the one listed here under that path, and a bundle its step 3 fetches is listed last. These
are the files of h5p-author {{ skill.version }}, the release the build page at
<a href="{{ site.url }}/h5p/build/">{{ site.url }}/h5p/build/</a> runs.
</p>
<p>
Where skills can be installed, install it instead:
Expand Down Expand Up @@ -62,6 +65,21 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; o
</ul>
</section>

<section aria-labelledby="bundles-heading">
<h2 id="bundles-heading">To build where npm cannot be reached</h2>
<p>
The build tool, which runs with Node and nothing installed, and a bundle per content type:
the libraries a build of that type reads. Fetch the tool and the bundle of the source's own
type, as step 3 of <code>SKILL.md</code> says.
</p>
<ul class="skill-files">
{%- for file in skill.bundles %}
<li><a href="{{ bundlesBase }}{{ file }}">{{ bundlesBase }}{{ file }}</a>
{%- if file == "h5p-author.mjs" %}: the build tool{% endif %}</li>
{%- endfor %}
</ul>
</section>

<footer class="docs-footer">
<p>
The skill and <code>@missing-elements/h5p-author</code> are MIT, from
Expand Down
3 changes: 2 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { build, defineConfig, type Plugin } from 'vite'
* - `/h5p/bundles/`, for a chat that reaches no npm registry (see `bundles()`).
*
* `/h5p/skill/`, the h5p-author skill's files and a page of their addresses, is Eleventy's
* (`eleventy.config.js`, `src/h5p/skill/`).
* (`eleventy.config.js`, `src/h5p/skill/`). The page lists the bundles too, written before them:
* it names them from the author package's `contentTypes`, the list `writeBundles` writes from.
*
* Moved here from embed-my.github.io's `/build` and `/bundles/` (#7).
*/
Expand Down
Loading