@lernapps/tooling 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/archetypes/shared/playwright.ts +5 -0
- package/dist/cli.mjs +39 -20
- package/dist/playwright.mjs +1 -0
- package/dist/skills/lernapps-cli/references/platform-rules.md +1 -0
- package/dist/skills/lernapps-explainer/SKILL.md +1 -1
- package/dist/skills/lernapps-explainer/references/blocks.md +2 -0
- package/dist/skills/lernapps-explainer/references/components.md +1 -1
- package/dist/skills/lernapps-explainer/references/rules.md +1 -1
- package/dist/skills/lernapps-explainer-design/SKILL.md +2 -1
- package/dist/skills/lernapps-interactive/SKILL.md +1 -1
- package/dist/skills/lernapps-interactive-design/SKILL.md +2 -1
- package/dist/skills/lernapps-quiz/SKILL.md +3 -2
- package/dist/skills/lernapps-quiz-design/SKILL.md +2 -1
- package/generator/app/.github/workflows/pages.yml +6 -1
- package/generator/create.ts +57 -23
- package/guidance/plan-template.md +3 -1
- package/package.json +2 -2
- package/skills/archetypes/lernapps-explainer/SKILL.md +1 -1
- package/skills/archetypes/lernapps-explainer/references/blocks.md +2 -0
- package/skills/archetypes/lernapps-explainer/references/components.md +1 -1
- package/skills/archetypes/lernapps-explainer/references/rules.md +1 -1
- package/skills/archetypes/lernapps-explainer-design/SKILL.md +2 -1
- package/skills/archetypes/lernapps-interactive/SKILL.md +1 -1
- package/skills/archetypes/lernapps-interactive-design/SKILL.md +2 -1
- package/skills/archetypes/lernapps-quiz/SKILL.md +3 -2
- package/skills/archetypes/lernapps-quiz-design/SKILL.md +2 -1
- package/skills/tooling/lernapps-cli/references/platform-rules.md +1 -0
|
@@ -32,6 +32,11 @@ const base: PlaywrightTestConfig = {
|
|
|
32
32
|
// another number of workers: the tooling's own tests set 1, because they run many apps' checks at the same time.
|
|
33
33
|
fullyParallel: true,
|
|
34
34
|
workers: Number(process.env["LERNAPPS_PLAYWRIGHT_WORKERS"]) || "75%",
|
|
35
|
+
// On CI (a small shared runner) a single interaction can now and then wait past the timeout under load
|
|
36
|
+
// (lernapps/app-templates#125): a failed test runs up to twice more there. One that passes on a retry counts as
|
|
37
|
+
// "flaky" in the output, not as passed, so it stays visible; a test that fails every time still fails the check.
|
|
38
|
+
// Locally no retry, so that a test that only sometimes passes shows up while the app is written.
|
|
39
|
+
retries: process.env["CI"] ? 2 : 0,
|
|
35
40
|
reporter: [["line"]],
|
|
36
41
|
// LERNAPPS_CHROMIUM: an installed Chromium that lernapps check starts where Playwright's own is missing
|
|
37
42
|
// (check/visit.ts)
|
package/dist/cli.mjs
CHANGED
|
@@ -1432,8 +1432,11 @@ const CREATE_USAGE = `Usage: lernapps create --archetype <name> [<dir>] [--plan
|
|
|
1432
1432
|
Writes a new app from the archetype's template into <dir> (default: the current folder), which must be empty
|
|
1433
1433
|
except for .git, the first version's plan .vibe/plans/01-<short name>.md and the skills that
|
|
1434
1434
|
lernapps init synced (.agents/skills/, .claude/skills/, skills-lock.json, and the package.json, package-lock.json and
|
|
1435
|
-
node_modules init wrote).
|
|
1436
|
-
|
|
1435
|
+
node_modules init wrote). From the installed package @lernapps/app-templates, the version @lernapps/tooling depends
|
|
1436
|
+
on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
|
|
1437
|
+
lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
|
|
1438
|
+
(scaffold/<name>/), which the planned content replaces. The template folder itself is the example app: it stays in
|
|
1439
|
+
node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version.
|
|
1437
1440
|
|
|
1438
1441
|
It needs the plan of the first version, confirmed by the creator: started from the plan template as
|
|
1439
1442
|
.vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
|
|
@@ -1445,7 +1448,7 @@ Run it in the folder lernapps init prepared, from the tooling init installed:
|
|
|
1445
1448
|
It needs git, and the npm registry for the npm install that follows. The app then depends on the version of
|
|
1446
1449
|
@lernapps/tooling that created it, and its workflows use the actions of that version's tag.
|
|
1447
1450
|
|
|
1448
|
-
It writes, besides the
|
|
1451
|
+
It writes, besides the scaffold: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
|
|
1449
1452
|
installs the git hooks; for an explainer and an interactive app also vue-tsc and typescript, with which lernapps
|
|
1450
1453
|
check type-checks its Vue components; for an interactive app also @doctc/arc42, which checks its architecture),
|
|
1451
1454
|
for an interactive app its architecture docs/arc42/ (the generic part and the interactive extension of app-templates,
|
|
@@ -1497,7 +1500,8 @@ function templatesPackage(dir) {
|
|
|
1497
1500
|
return {
|
|
1498
1501
|
root,
|
|
1499
1502
|
version: manifest.version ?? "0.0.0",
|
|
1500
|
-
archetypes: [...manifest.lernapps?.archetypes ?? []].sort()
|
|
1503
|
+
archetypes: [...manifest.lernapps?.archetypes ?? []].sort(),
|
|
1504
|
+
scaffold: manifest.lernapps?.scaffold ?? {}
|
|
1501
1505
|
};
|
|
1502
1506
|
}
|
|
1503
1507
|
/** The version of this package. */
|
|
@@ -1545,6 +1549,27 @@ function git(args, cwd) {
|
|
|
1545
1549
|
}
|
|
1546
1550
|
/** A package name from the folder's name: lower case, letters, digits and hyphens. */
|
|
1547
1551
|
const packageName = (dir) => basename(dir).toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "app";
|
|
1552
|
+
/**
|
|
1553
|
+
* The scaffold of the archetype into `dir` (decision dec-scaffold of app-templates): the paths of its template folder
|
|
1554
|
+
* that lernapps.scaffold.<archetype> of the package lists (the entry, the configuration, the e2e entry), then the start
|
|
1555
|
+
* content scaffold/<archetype>/, a placeholder the creator replaces. The rest of the template folder is the example
|
|
1556
|
+
* app; it stays in node_modules/@lernapps/app-templates/<archetype>/ for the creator's assistant to look up.
|
|
1557
|
+
*/
|
|
1558
|
+
function writeScaffold(templates, archetype, dir) {
|
|
1559
|
+
const paths = templates.scaffold[archetype];
|
|
1560
|
+
if (paths === void 0 || paths.length === 0) throw new CreateError(`${templates.root} (version ${templates.version}) names no scaffold for ${archetype} (lernapps.scaffold in its package.json): use the version of ${RUNTIME} this tooling depends on`);
|
|
1561
|
+
const template = join(templates.root, archetype);
|
|
1562
|
+
for (const path of paths) {
|
|
1563
|
+
const shipped = basename(path) === ".gitignore" ? join(template, dirname(path), "_gitignore") : void 0;
|
|
1564
|
+
const source = shipped !== void 0 && existsSync(shipped) ? shipped : join(template, path);
|
|
1565
|
+
if (!existsSync(source)) throw new CreateError(`${template} has no ${path}, which its scaffold lists`);
|
|
1566
|
+
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
1567
|
+
cpSync(source, join(dir, path));
|
|
1568
|
+
}
|
|
1569
|
+
const start = join(templates.root, "scaffold", archetype);
|
|
1570
|
+
if (!existsSync(start)) throw new CreateError(`${templates.root} has no start content scaffold/${archetype}/`);
|
|
1571
|
+
cpSync(start, dir, { recursive: true });
|
|
1572
|
+
}
|
|
1548
1573
|
/** This package, as an app depends on it. */
|
|
1549
1574
|
const TOOLING = "@lernapps/tooling";
|
|
1550
1575
|
/**
|
|
@@ -1698,18 +1723,9 @@ function create(args) {
|
|
|
1698
1723
|
const initialised = existsSync(join(dir, "package.json")) ? readJson(join(dir, "package.json")) : void 0;
|
|
1699
1724
|
const initialLock = existsSync(join(dir, "package-lock.json")) ? readFileSync(join(dir, "package-lock.json"), "utf8") : void 0;
|
|
1700
1725
|
mkdirSync(dir, { recursive: true });
|
|
1701
|
-
|
|
1702
|
-
rmSync(join(dir, "package-lock.json"), { force: true });
|
|
1726
|
+
writeScaffold(templates, archetype, dir);
|
|
1703
1727
|
if (initialLock !== void 0) writeFileSync(join(dir, "package-lock.json"), initialLock);
|
|
1704
|
-
if (existsSync(join(dir, "_gitignore"))) renameSync(join(dir, "_gitignore"), join(dir, ".gitignore"));
|
|
1705
|
-
rmSync(join(dir, "README.md"), { force: true });
|
|
1706
|
-
rmSync(join(dir, ARCHITECTURE), {
|
|
1707
|
-
recursive: true,
|
|
1708
|
-
force: true
|
|
1709
|
-
});
|
|
1710
|
-
if (existsSync(join(dir, "docs")) && readdirSync(join(dir, "docs")).length === 0) rmSync(join(dir, "docs"), { recursive: true });
|
|
1711
1728
|
if (architecture) writeArchitecture(dir, templates.root, archetype, templates.version);
|
|
1712
|
-
else rmSync(join(dir, ".arc42ignore"), { force: true });
|
|
1713
1729
|
const app = readJson(join(dir, "package.json"));
|
|
1714
1730
|
const name = initialised?.lernapps?.init === true && typeof initialised.name === "string" ? initialised.name : packageName(dir);
|
|
1715
1731
|
app.name = name;
|
|
@@ -1717,12 +1733,15 @@ function create(args) {
|
|
|
1717
1733
|
...app.scripts,
|
|
1718
1734
|
prepare: "lernapps hooks"
|
|
1719
1735
|
};
|
|
1720
|
-
if (app.devDependencies?.["@lernapps/app-templates"] !== void 0) {
|
|
1721
|
-
app.devDependencies
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1736
|
+
if (app.devDependencies?.["@lernapps/app-templates"] !== void 0) app.devDependencies = {
|
|
1737
|
+
...app.devDependencies,
|
|
1738
|
+
[RUNTIME]: `^${templates.version}`
|
|
1739
|
+
};
|
|
1740
|
+
const overrides = app["overrides"];
|
|
1741
|
+
if (typeof overrides === "object" && overrides !== null && "@lernapps/app-templates" in overrides) {
|
|
1742
|
+
const kept = Object.fromEntries(Object.entries(overrides).filter(([name]) => name !== RUNTIME));
|
|
1743
|
+
if (Object.keys(kept).length > 0) app["overrides"] = kept;
|
|
1744
|
+
else delete app["overrides"];
|
|
1726
1745
|
}
|
|
1727
1746
|
const tooling = values.tooling ?? `^${ownVersion()}`;
|
|
1728
1747
|
app.devDependencies = {
|
package/dist/playwright.mjs
CHANGED
|
@@ -147,6 +147,7 @@ severity: error
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
- Texts for learners, teachers, parents: German, plain (short sentences, active voice, no jargon).
|
|
150
|
+
- Never English only: an app in English is German and English, German first.
|
|
150
151
|
- More languages only where the archetype supports them (explainer, quiz: `languages`, German first); each declared
|
|
151
152
|
language complete, no other.
|
|
152
153
|
- Texts out of code: in the content (explainer YAML, quiz bank) or message files (`src/messages/de.json`, shown
|
|
@@ -8,7 +8,7 @@ license: MIT
|
|
|
8
8
|
|
|
9
9
|
- You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
|
|
10
10
|
exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
|
|
11
|
-
- `create` writes
|
|
11
|
+
- `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
|
|
12
12
|
- Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`. Start every YAML file with
|
|
13
13
|
`# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`
|
|
14
14
|
(one `../` less in `src/app.yaml`).
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
## Languages
|
|
70
70
|
|
|
71
71
|
- `languages` in `src/app.yaml`, German first: `[de]` or `[de, en]` (runtime texts: de, en).
|
|
72
|
+
- Never English only: with `[de]` the pages and the runtime's texts are German, so English strings mix both.
|
|
73
|
+
English wanted → `[de, en]`.
|
|
72
74
|
- A string = German, enough only while German is the only language. Else every text a map:
|
|
73
75
|
`title: { de: Die drei Stände, en: The three estates }`.
|
|
74
76
|
- Build fails on a missing translation (`the translation is missing; write the text as a map: { de: …, en: … }`)
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
`show(answer, task, props, lang)` (answer as text). An input of `params` that is no placeholder shows as
|
|
48
48
|
"(verdeckt)": the tutor knows it, the child does not. One `template` per block: prefer one kind of task per block;
|
|
49
49
|
else give the sentence that changes as an input (`{Aufgabe}`). `about.answer` names what the child does and "Prüfen". The build checks them against `check` for every number; rule `vue-component-tutor-table`. Model:
|
|
50
|
-
`ProzentFeld` of the template
|
|
50
|
+
`ProzentFeld` of the template, in `node_modules/@lernapps/app-templates/explainer/src/components/`.
|
|
51
51
|
- More: rule `vue-component-exercise-contract`.
|
|
52
52
|
|
|
53
53
|
## Rendering
|
|
@@ -194,7 +194,7 @@ severity: hint
|
|
|
194
194
|
- The tutor page then has the task once, the mistakes as F1, F2 … and a table of every number (inputs, solution,
|
|
195
195
|
each mistake), like `generated`. Without them: each number's `describe` as far as 64 kB allow, and the build's hint
|
|
196
196
|
names the block.
|
|
197
|
-
- Example: `src/components/ProzentFeld.ts
|
|
197
|
+
- Example: `node_modules/@lernapps/app-templates/explainer/src/components/ProzentFeld.ts`, the template's.
|
|
198
198
|
|
|
199
199
|
## `vue-component-tested`: A test per component
|
|
200
200
|
|
|
@@ -44,7 +44,8 @@ license: MIT
|
|
|
44
44
|
|
|
45
45
|
## Decide
|
|
46
46
|
|
|
47
|
-
- Languages: German; English as a second language is supported. Each declared language complete.
|
|
47
|
+
- Languages: German; English as a second language is supported. Each declared language complete. Never English
|
|
48
|
+
only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
|
|
48
49
|
- The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
|
|
49
50
|
|
|
50
51
|
## Gotchas
|
|
@@ -7,7 +7,7 @@ license: MIT
|
|
|
7
7
|
# Interactive app
|
|
8
8
|
|
|
9
9
|
- Runtime: `@lernapps/app-templates` (`runtime/interactive/`); leave it untouched.
|
|
10
|
-
- `create` writes a
|
|
10
|
+
- `create` writes a placeholder: a counter as core, component and playthrough. Replace it with the planned parts. The complete example, a typing game, is in `node_modules/@lernapps/app-templates/interactive/`: look things up there, never copy it.
|
|
11
11
|
|
|
12
12
|
## What you write
|
|
13
13
|
|
|
@@ -39,7 +39,8 @@ license: MIT
|
|
|
39
39
|
- Accessibility: WCAG 2.1 AA and 360 px are the default. A deviation the idea needs (a time limit) goes into the
|
|
40
40
|
entry's `limits`, in German.
|
|
41
41
|
- Look and feel: as the creator chose in Explore.
|
|
42
|
-
- Languages: German; each declared language complete; learner texts in message files, never in code.
|
|
42
|
+
- Languages: German; each declared language complete; learner texts in message files, never in code. Never English
|
|
43
|
+
only: German comes first. English wanted → German and English.
|
|
43
44
|
|
|
44
45
|
## Gotchas
|
|
45
46
|
|
|
@@ -9,7 +9,7 @@ license: MIT
|
|
|
9
9
|
- You write `src/quiz.json` and, for the content of its questions, the image files in `src/images/`. Engine, question
|
|
10
10
|
types, feedback, scoring, order, accessibility and the no-JS page come from the runtime `@lernapps/app-templates`.
|
|
11
11
|
- Schema: `node_modules/@lernapps/app-templates/runtime/quiz/quiz.v1.schema.json`.
|
|
12
|
-
- `create` writes the example bank "Naturwunder der Welt"
|
|
12
|
+
- `create` writes a placeholder bank with one question. Replace it with the planned questions. The complete example, the bank "Naturwunder der Welt", is in `node_modules/@lernapps/app-templates/quiz/`: look things up there, never copy it.
|
|
13
13
|
- The fields of each question type and how to write good questions: skill `lernapps-content-questions`.
|
|
14
14
|
|
|
15
15
|
## Gotchas
|
|
@@ -50,7 +50,8 @@ license: MIT
|
|
|
50
50
|
- German; more with `"languages": ["de", "en"]`, German first (runtime texts: de, en).
|
|
51
51
|
- Then every learner text is a map with each language: title, intro, source titles; per question text, options,
|
|
52
52
|
`info`, items, pairs, unit, explanation, link titles.
|
|
53
|
-
- Without `languages`: German strings.
|
|
53
|
+
- Without `languages`: German strings. The page is German then (`lang="de"`, the runtime's own texts such as
|
|
54
|
+
"Frage 1 von 5"); English strings give a page that mixes both. Never English only: English wanted → `[de, en]`.
|
|
54
55
|
- Link `url`: one address, or a map per language (own Wikipedia).
|
|
55
56
|
- Build errors, one line each, e.g.
|
|
56
57
|
`questions[1].explanation (en) (question "totes-meer"): the translation is missing; write the text as a map: { de: …, en: … }`;
|
|
@@ -42,7 +42,8 @@ license: MIT
|
|
|
42
42
|
|
|
43
43
|
## Decide
|
|
44
44
|
|
|
45
|
-
- Languages: German; English as a second language is supported. Each declared language complete.
|
|
45
|
+
- Languages: German; English as a second language is supported. Each declared language complete. Never English
|
|
46
|
+
only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
|
|
46
47
|
- Keep the last score on the device (`rememberLastResult`) or nothing. Keeping it → the entry says
|
|
47
48
|
`fitness.storage: device`.
|
|
48
49
|
- Order: shuffled per seed; a link with `?seed=42` gives every learner the same order.
|
|
@@ -15,7 +15,9 @@ name: Pages
|
|
|
15
15
|
# paths filter: a check skipped by a paths filter stays "Pending" and blocks a pull request that requires it. GitHub
|
|
16
16
|
# may start no run for a push that creates main from commits it already has (the first publish): then start it by
|
|
17
17
|
# hand, Actions → Pages → Run workflow on main (gh workflow run Pages --ref main), as the plan says; a run started by
|
|
18
|
-
# hand checks and publishes everything.
|
|
18
|
+
# hand checks and publishes everything. A push to main whose commit has the same tree as the pull request that the job
|
|
19
|
+
# `app` checked already (the pull request was up to date with main when it was merged) takes that run's bundle and
|
|
20
|
+
# report instead of checking again.
|
|
19
21
|
on:
|
|
20
22
|
push:
|
|
21
23
|
branches: [main]
|
|
@@ -34,6 +36,9 @@ jobs:
|
|
|
34
36
|
app:
|
|
35
37
|
if: github.event.action != 'closed'
|
|
36
38
|
runs-on: ubuntu-latest
|
|
39
|
+
permissions:
|
|
40
|
+
contents: read
|
|
41
|
+
actions: read # on main: find the run of the pull request that checked the same tree, and take its result
|
|
37
42
|
steps:
|
|
38
43
|
- uses: lernapps/tooling/actions/app-check@{{tooling}}
|
|
39
44
|
deploy:
|
package/generator/create.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// The generator, `lernapps create --archetype <name> [<dir>]`: writes a new app into an empty folder, once the creator
|
|
2
2
|
// has confirmed the first version's plan (.vibe/plans/01-<short name>.md, in the phase code, for this archetype). It
|
|
3
|
-
//
|
|
4
|
-
// an exact version)
|
|
5
|
-
//
|
|
3
|
+
// writes the archetype's scaffold out of the installed package @lernapps/app-templates (a dependency of this package,
|
|
4
|
+
// at an exact version): the paths of the template folder its lernapps.scaffold lists and the placeholder in
|
|
5
|
+
// scaffold/<archetype>/, not the example app, which stays in node_modules for the assistant to look up. It makes the
|
|
6
|
+
// app depend on that same version of the runtime, so template and runtime always match. Then it writes what every app has: AGENTS.md, the
|
|
6
7
|
// plan, the content guide (an app-local skill), the workflows, LICENSE, an issue form for content errors and the
|
|
7
8
|
// Renovate configuration (generator/app/); an interactive app also its architecture (generator/architecture.ts). The
|
|
8
9
|
// skills `lernapps init` synced into the folder stay, and are committed with the app; `lernapps update` replaces them
|
|
@@ -26,7 +27,7 @@ import { createRequire } from "node:module";
|
|
|
26
27
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
27
28
|
import { fileURLToPath } from "node:url";
|
|
28
29
|
import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
|
|
29
|
-
import {
|
|
30
|
+
import { ARCHITECTURE_ARCHETYPES, writeArchitecture } from "./architecture.ts";
|
|
30
31
|
import { LEGACY_PLAN, MIGRATED_PLAN, PLAN_NAME, PLANS, planFrontMatter, plans } from "../src/plan.ts";
|
|
31
32
|
import { UsageError } from "../src/usage.ts";
|
|
32
33
|
|
|
@@ -44,8 +45,11 @@ export const CREATE_USAGE = `Usage: lernapps create --archetype <name> [<dir>] [
|
|
|
44
45
|
Writes a new app from the archetype's template into <dir> (default: the current folder), which must be empty
|
|
45
46
|
except for .git, the first version's plan .vibe/plans/01-<short name>.md and the skills that
|
|
46
47
|
lernapps init synced (.agents/skills/, .claude/skills/, skills-lock.json, and the package.json, package-lock.json and
|
|
47
|
-
node_modules init wrote).
|
|
48
|
-
|
|
48
|
+
node_modules init wrote). From the installed package @lernapps/app-templates, the version @lernapps/tooling depends
|
|
49
|
+
on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
|
|
50
|
+
lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
|
|
51
|
+
(scaffold/<name>/), which the planned content replaces. The template folder itself is the example app: it stays in
|
|
52
|
+
node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version.
|
|
49
53
|
|
|
50
54
|
It needs the plan of the first version, confirmed by the creator: started from the plan template as
|
|
51
55
|
.vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
|
|
@@ -57,7 +61,7 @@ Run it in the folder lernapps init prepared, from the tooling init installed:
|
|
|
57
61
|
It needs git, and the npm registry for the npm install that follows. The app then depends on the version of
|
|
58
62
|
@lernapps/tooling that created it, and its workflows use the actions of that version's tag.
|
|
59
63
|
|
|
60
|
-
It writes, besides the
|
|
64
|
+
It writes, besides the scaffold: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
|
|
61
65
|
installs the git hooks; for an explainer and an interactive app also vue-tsc and typescript, with which lernapps
|
|
62
66
|
check type-checks its Vue components; for an interactive app also @doctc/arc42, which checks its architecture),
|
|
63
67
|
for an interactive app its architecture docs/arc42/ (the generic part and the interactive extension of app-templates,
|
|
@@ -96,7 +100,7 @@ export interface Manifest {
|
|
|
96
100
|
version?: string;
|
|
97
101
|
devDependencies?: Record<string, string>;
|
|
98
102
|
scripts?: Record<string, string>;
|
|
99
|
-
lernapps?: { archetypes?: string[]; init?: boolean };
|
|
103
|
+
lernapps?: { archetypes?: string[]; scaffold?: Record<string, string[]>; init?: boolean };
|
|
100
104
|
[key: string]: unknown;
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -108,6 +112,8 @@ export interface Templates {
|
|
|
108
112
|
root: string;
|
|
109
113
|
version: string;
|
|
110
114
|
archetypes: string[];
|
|
115
|
+
/** Per archetype, the paths of its template folder a new app gets (lernapps.scaffold of the package). */
|
|
116
|
+
scaffold: Record<string, string[]>;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
/** The runtime package, which also carries the templates. */
|
|
@@ -133,7 +139,12 @@ export function templatesPackage(dir?: string): Templates {
|
|
|
133
139
|
throw new CreateError(`${root} is no package ${RUNTIME}: it has no package.json`);
|
|
134
140
|
}
|
|
135
141
|
const manifest = readJson(join(root, "package.json"));
|
|
136
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
root,
|
|
144
|
+
version: manifest.version ?? "0.0.0",
|
|
145
|
+
archetypes: [...(manifest.lernapps?.archetypes ?? [])].sort(),
|
|
146
|
+
scaffold: manifest.lernapps?.scaffold ?? {},
|
|
147
|
+
};
|
|
137
148
|
}
|
|
138
149
|
|
|
139
150
|
/** The archetypes of the installed templates. */
|
|
@@ -202,6 +213,34 @@ const packageName = (dir: string) =>
|
|
|
202
213
|
.replace(/[^a-z0-9]+/g, "-")
|
|
203
214
|
.replace(/^-+|-+$/g, "") || "app";
|
|
204
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The scaffold of the archetype into `dir` (decision dec-scaffold of app-templates): the paths of its template folder
|
|
218
|
+
* that lernapps.scaffold.<archetype> of the package lists (the entry, the configuration, the e2e entry), then the start
|
|
219
|
+
* content scaffold/<archetype>/, a placeholder the creator replaces. The rest of the template folder is the example
|
|
220
|
+
* app; it stays in node_modules/@lernapps/app-templates/<archetype>/ for the creator's assistant to look up.
|
|
221
|
+
*/
|
|
222
|
+
export function writeScaffold(templates: Templates, archetype: string, dir: string): void {
|
|
223
|
+
const paths = templates.scaffold[archetype];
|
|
224
|
+
if (paths === undefined || paths.length === 0) {
|
|
225
|
+
throw new CreateError(
|
|
226
|
+
`${templates.root} (version ${templates.version}) names no scaffold for ${archetype} (lernapps.scaffold in its package.json): use the version of ${RUNTIME} this tooling depends on`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const template = join(templates.root, archetype);
|
|
230
|
+
for (const path of paths) {
|
|
231
|
+
// npm never ships a .gitignore: the package carries it as _gitignore (app-templates, scripts/pack-dotfiles.ts);
|
|
232
|
+
// a clone of the repository has the .gitignore itself
|
|
233
|
+
const shipped = basename(path) === ".gitignore" ? join(template, dirname(path), "_gitignore") : undefined;
|
|
234
|
+
const source = shipped !== undefined && existsSync(shipped) ? shipped : join(template, path);
|
|
235
|
+
if (!existsSync(source)) throw new CreateError(`${template} has no ${path}, which its scaffold lists`);
|
|
236
|
+
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
237
|
+
cpSync(source, join(dir, path));
|
|
238
|
+
}
|
|
239
|
+
const start = join(templates.root, "scaffold", archetype);
|
|
240
|
+
if (!existsSync(start)) throw new CreateError(`${templates.root} has no start content scaffold/${archetype}/`);
|
|
241
|
+
cpSync(start, dir, { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
|
|
205
244
|
/** This package, as an app depends on it. */
|
|
206
245
|
export const TOOLING = "@lernapps/tooling";
|
|
207
246
|
|
|
@@ -399,21 +438,9 @@ export function create(args: string[]): number {
|
|
|
399
438
|
? readFileSync(join(dir, "package-lock.json"), "utf8")
|
|
400
439
|
: undefined;
|
|
401
440
|
mkdirSync(dir, { recursive: true });
|
|
402
|
-
|
|
403
|
-
rmSync(join(dir, "package-lock.json"), { force: true }); // the template's lock, of another name and tooling
|
|
441
|
+
writeScaffold(templates, archetype, dir);
|
|
404
442
|
if (initialLock !== undefined) writeFileSync(join(dir, "package-lock.json"), initialLock);
|
|
405
|
-
// npm never ships a .gitignore: the package carries it as _gitignore (app-templates, scripts/pack-dotfiles.ts)
|
|
406
|
-
if (existsSync(join(dir, "_gitignore"))) renameSync(join(dir, "_gitignore"), join(dir, ".gitignore"));
|
|
407
|
-
// the template's README says what the archetype can do, for choosing one; it is no part of the app
|
|
408
|
-
rmSync(join(dir, "README.md"), { force: true });
|
|
409
|
-
// the template folder's architecture is the extension of app-templates' own: an interactive app gets the
|
|
410
|
-
// combination as its reference, the other archetypes none
|
|
411
|
-
rmSync(join(dir, ARCHITECTURE), { recursive: true, force: true });
|
|
412
|
-
if (existsSync(join(dir, "docs")) && readdirSync(join(dir, "docs")).length === 0) {
|
|
413
|
-
rmSync(join(dir, "docs"), { recursive: true });
|
|
414
|
-
}
|
|
415
443
|
if (architecture) writeArchitecture(dir, templates.root, archetype, templates.version);
|
|
416
|
-
else rmSync(join(dir, ".arc42ignore"), { force: true });
|
|
417
444
|
|
|
418
445
|
const app = readJson(join(dir, "package.json"));
|
|
419
446
|
// the short name the creator gave `lernapps init` stays the app's name; without init, the folder names it
|
|
@@ -427,7 +454,14 @@ export function create(args: string[]): number {
|
|
|
427
454
|
// version of the runtime it was copied from
|
|
428
455
|
if (app.devDependencies?.[RUNTIME] !== undefined) {
|
|
429
456
|
app.devDependencies = { ...app.devDependencies, [RUNTIME]: `^${templates.version}` };
|
|
430
|
-
|
|
457
|
+
}
|
|
458
|
+
// the template keeps its checkout's runtime with an override (file:..), which npm would otherwise swap for the
|
|
459
|
+
// version this tooling depends on; the app has no checkout to point at
|
|
460
|
+
const overrides = app["overrides"];
|
|
461
|
+
if (typeof overrides === "object" && overrides !== null && RUNTIME in overrides) {
|
|
462
|
+
const kept = Object.fromEntries(Object.entries(overrides).filter(([name]) => name !== RUNTIME));
|
|
463
|
+
if (Object.keys(kept).length > 0) app["overrides"] = kept;
|
|
464
|
+
else delete app["overrides"];
|
|
431
465
|
}
|
|
432
466
|
// the tooling the app depends on: the one named, else this version (init may have installed it already)
|
|
433
467
|
const tooling = values.tooling ?? `^${ownVersion()}`;
|
|
@@ -101,7 +101,9 @@ questions. -->
|
|
|
101
101
|
research, a data format).
|
|
102
102
|
- Amount: how much content per level or chapter in the first version (e.g. "one text per school year"); what comes
|
|
103
103
|
later.
|
|
104
|
-
- Languages: German only, or
|
|
104
|
+
- Languages: German only, or German and English (texts, code). Never English only: an app without German still has
|
|
105
|
+
German pages and runtime texts, so it mixes both. The creator wants English → offer German and English,
|
|
106
|
+
both complete, and say why.
|
|
105
107
|
- Look and feel: calm like a worksheet, or like a game (colours, praise, effects); dark mode.
|
|
106
108
|
- Terms: the creator's words for the main things in the app, used everywhere.
|
|
107
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lernapps/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Shared tooling of the lernapps.net apps and sites: the CLI lernapps, site actions, Renovate preset, guidance for building apps; documentation at https://lernapps.net/tooling/",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"arc42": "arc42 --dir docs/arc42"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@lernapps/app-templates": "0.
|
|
110
|
+
"@lernapps/app-templates": "0.2.1",
|
|
111
111
|
"axe-core": "4.14.0",
|
|
112
112
|
"playwright": "1.64.1",
|
|
113
113
|
"skills": "1.7.4",
|
|
@@ -8,7 +8,7 @@ license: MIT
|
|
|
8
8
|
|
|
9
9
|
- You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
|
|
10
10
|
exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
|
|
11
|
-
- `create` writes
|
|
11
|
+
- `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
|
|
12
12
|
- Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`. Start every YAML file with
|
|
13
13
|
`# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`
|
|
14
14
|
(one `../` less in `src/app.yaml`).
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
## Languages
|
|
70
70
|
|
|
71
71
|
- `languages` in `src/app.yaml`, German first: `[de]` or `[de, en]` (runtime texts: de, en).
|
|
72
|
+
- Never English only: with `[de]` the pages and the runtime's texts are German, so English strings mix both.
|
|
73
|
+
English wanted → `[de, en]`.
|
|
72
74
|
- A string = German, enough only while German is the only language. Else every text a map:
|
|
73
75
|
`title: { de: Die drei Stände, en: The three estates }`.
|
|
74
76
|
- Build fails on a missing translation (`the translation is missing; write the text as a map: { de: …, en: … }`)
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
`show(answer, task, props, lang)` (answer as text). An input of `params` that is no placeholder shows as
|
|
48
48
|
"(verdeckt)": the tutor knows it, the child does not. One `template` per block: prefer one kind of task per block;
|
|
49
49
|
else give the sentence that changes as an input (`{Aufgabe}`). `about.answer` names what the child does and "Prüfen". The build checks them against `check` for every number; rule `vue-component-tutor-table`. Model:
|
|
50
|
-
`ProzentFeld` of the template
|
|
50
|
+
`ProzentFeld` of the template, in `node_modules/@lernapps/app-templates/explainer/src/components/`.
|
|
51
51
|
- More: rule `vue-component-exercise-contract`.
|
|
52
52
|
|
|
53
53
|
## Rendering
|
|
@@ -194,7 +194,7 @@ severity: hint
|
|
|
194
194
|
- The tutor page then has the task once, the mistakes as F1, F2 … and a table of every number (inputs, solution,
|
|
195
195
|
each mistake), like `generated`. Without them: each number's `describe` as far as 64 kB allow, and the build's hint
|
|
196
196
|
names the block.
|
|
197
|
-
- Example: `src/components/ProzentFeld.ts
|
|
197
|
+
- Example: `node_modules/@lernapps/app-templates/explainer/src/components/ProzentFeld.ts`, the template's.
|
|
198
198
|
|
|
199
199
|
## `vue-component-tested`: A test per component
|
|
200
200
|
|
|
@@ -44,7 +44,8 @@ license: MIT
|
|
|
44
44
|
|
|
45
45
|
## Decide
|
|
46
46
|
|
|
47
|
-
- Languages: German; English as a second language is supported. Each declared language complete.
|
|
47
|
+
- Languages: German; English as a second language is supported. Each declared language complete. Never English
|
|
48
|
+
only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
|
|
48
49
|
- The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
|
|
49
50
|
|
|
50
51
|
## Gotchas
|
|
@@ -7,7 +7,7 @@ license: MIT
|
|
|
7
7
|
# Interactive app
|
|
8
8
|
|
|
9
9
|
- Runtime: `@lernapps/app-templates` (`runtime/interactive/`); leave it untouched.
|
|
10
|
-
- `create` writes a
|
|
10
|
+
- `create` writes a placeholder: a counter as core, component and playthrough. Replace it with the planned parts. The complete example, a typing game, is in `node_modules/@lernapps/app-templates/interactive/`: look things up there, never copy it.
|
|
11
11
|
|
|
12
12
|
## What you write
|
|
13
13
|
|
|
@@ -39,7 +39,8 @@ license: MIT
|
|
|
39
39
|
- Accessibility: WCAG 2.1 AA and 360 px are the default. A deviation the idea needs (a time limit) goes into the
|
|
40
40
|
entry's `limits`, in German.
|
|
41
41
|
- Look and feel: as the creator chose in Explore.
|
|
42
|
-
- Languages: German; each declared language complete; learner texts in message files, never in code.
|
|
42
|
+
- Languages: German; each declared language complete; learner texts in message files, never in code. Never English
|
|
43
|
+
only: German comes first. English wanted → German and English.
|
|
43
44
|
|
|
44
45
|
## Gotchas
|
|
45
46
|
|
|
@@ -9,7 +9,7 @@ license: MIT
|
|
|
9
9
|
- You write `src/quiz.json` and, for the content of its questions, the image files in `src/images/`. Engine, question
|
|
10
10
|
types, feedback, scoring, order, accessibility and the no-JS page come from the runtime `@lernapps/app-templates`.
|
|
11
11
|
- Schema: `node_modules/@lernapps/app-templates/runtime/quiz/quiz.v1.schema.json`.
|
|
12
|
-
- `create` writes the example bank "Naturwunder der Welt"
|
|
12
|
+
- `create` writes a placeholder bank with one question. Replace it with the planned questions. The complete example, the bank "Naturwunder der Welt", is in `node_modules/@lernapps/app-templates/quiz/`: look things up there, never copy it.
|
|
13
13
|
- The fields of each question type and how to write good questions: skill `lernapps-content-questions`.
|
|
14
14
|
|
|
15
15
|
## Gotchas
|
|
@@ -50,7 +50,8 @@ license: MIT
|
|
|
50
50
|
- German; more with `"languages": ["de", "en"]`, German first (runtime texts: de, en).
|
|
51
51
|
- Then every learner text is a map with each language: title, intro, source titles; per question text, options,
|
|
52
52
|
`info`, items, pairs, unit, explanation, link titles.
|
|
53
|
-
- Without `languages`: German strings.
|
|
53
|
+
- Without `languages`: German strings. The page is German then (`lang="de"`, the runtime's own texts such as
|
|
54
|
+
"Frage 1 von 5"); English strings give a page that mixes both. Never English only: English wanted → `[de, en]`.
|
|
54
55
|
- Link `url`: one address, or a map per language (own Wikipedia).
|
|
55
56
|
- Build errors, one line each, e.g.
|
|
56
57
|
`questions[1].explanation (en) (question "totes-meer"): the translation is missing; write the text as a map: { de: …, en: … }`;
|
|
@@ -42,7 +42,8 @@ license: MIT
|
|
|
42
42
|
|
|
43
43
|
## Decide
|
|
44
44
|
|
|
45
|
-
- Languages: German; English as a second language is supported. Each declared language complete.
|
|
45
|
+
- Languages: German; English as a second language is supported. Each declared language complete. Never English
|
|
46
|
+
only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
|
|
46
47
|
- Keep the last score on the device (`rememberLastResult`) or nothing. Keeping it → the entry says
|
|
47
48
|
`fitness.storage: device`.
|
|
48
49
|
- Order: shuffled per seed; a link with `?seed=42` gives every learner the same order.
|
|
@@ -147,6 +147,7 @@ severity: error
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
- Texts for learners, teachers, parents: German, plain (short sentences, active voice, no jargon).
|
|
150
|
+
- Never English only: an app in English is German and English, German first.
|
|
150
151
|
- More languages only where the archetype supports them (explainer, quiz: `languages`, German first); each declared
|
|
151
152
|
language complete, no other.
|
|
152
153
|
- Texts out of code: in the content (explainer YAML, quiz bank) or message files (`src/messages/de.json`, shown
|