@lernapps/tooling 0.3.1 → 0.5.0
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/dist/check/rules/competencies-on-map.mjs +85 -0
- package/dist/check/rules/one-competency-per-page.mjs +18 -0
- package/dist/check/rules/prerequisites-linked.mjs +38 -0
- package/dist/cli.mjs +519 -56
- package/dist/explainer-map-DW4XCcY0.mjs +124 -0
- package/dist/skills/lernapps-app/SKILL.md +2 -1
- package/dist/skills/lernapps-cli/SKILL.md +13 -4
- package/dist/skills/lernapps-content-questions/SKILL.md +2 -0
- package/dist/skills/lernapps-content-questions/references/question-types.md +2 -1
- package/dist/skills/lernapps-content-research/SKILL.md +41 -5
- package/dist/skills/lernapps-exercise-patterns/references/mathematics.md +25 -1
- package/dist/skills/lernapps-exercise-patterns/references/physics.md +1 -0
- package/dist/skills/lernapps-explainer/SKILL.md +10 -4
- package/dist/skills/lernapps-explainer/references/blocks.md +7 -3
- package/dist/skills/lernapps-explainer/references/exercises.md +6 -0
- package/dist/skills/lernapps-explainer/references/rules.md +29 -0
- package/dist/skills/lernapps-explainer/references/structure.md +62 -0
- package/dist/skills/lernapps-explainer-design/SKILL.md +46 -2
- package/dist/skills/lernapps-explainer-design/references/rules.md +1 -0
- package/dist/skills/lernapps-explainer-self-assessment/SKILL.md +1 -0
- package/dist/skills/lernapps-listing/SKILL.md +9 -0
- package/generator/create.ts +39 -10
- package/generator/init.ts +89 -19
- package/generator/update.ts +53 -8
- package/guidance/increment-template.md +13 -5
- package/guidance/plan-template.md +25 -11
- package/guidance/workflow-rules.md +3 -1
- package/package.json +3 -3
- package/skills/archetypes/lernapps-explainer/SKILL.md +10 -4
- package/skills/archetypes/lernapps-explainer/references/blocks.md +7 -3
- package/skills/archetypes/lernapps-explainer/references/exercises.md +6 -0
- package/skills/archetypes/lernapps-explainer/references/rules.md +29 -0
- package/skills/archetypes/lernapps-explainer/references/structure.md +62 -0
- package/skills/archetypes/lernapps-explainer-design/SKILL.md +46 -2
- package/skills/archetypes/lernapps-explainer-design/references/rules.md +1 -0
- package/skills/archetypes/lernapps-explainer-self-assessment/SKILL.md +1 -0
- package/skills/content/lernapps-content-questions/SKILL.md +2 -0
- package/skills/content/lernapps-content-questions/references/question-types.md +2 -1
- package/skills/content/lernapps-content-research/SKILL.md +41 -5
- package/skills/content/lernapps-exercise-patterns/references/mathematics.md +25 -1
- package/skills/content/lernapps-exercise-patterns/references/physics.md +1 -0
- package/skills/process/lernapps-app/SKILL.md +2 -1
- package/skills/process/lernapps-listing/SKILL.md +9 -0
- package/skills/tooling/lernapps-cli/SKILL.md +13 -4
package/generator/create.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
// Renovate configuration (generator/app/); an interactive app also its architecture (generator/architecture.ts). The
|
|
9
9
|
// skills `lernapps init` synced into the folder stay, and are committed with the app; `lernapps update` replaces them
|
|
10
10
|
// with those of the tooling it moves the app to.
|
|
11
|
-
// To move to a new app-templates: set the exact version of @lernapps/app-templates in package.json
|
|
12
|
-
//
|
|
11
|
+
// To move to a new app-templates: set the exact version of @lernapps/app-templates in package.json, or a commit
|
|
12
|
+
// (github:lernapps/app-templates#<commit>, which Renovate moves) until a release has what the tooling needs; the
|
|
13
|
+
// generator's tests then run on that version. An app gets the same: ^ the version, or the same commit (runtimeSpec).
|
|
13
14
|
import { spawnSync } from "node:child_process";
|
|
14
15
|
import {
|
|
15
16
|
cpSync,
|
|
@@ -49,7 +50,8 @@ node_modules init wrote). From the installed package @lernapps/app-templates, th
|
|
|
49
50
|
on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
|
|
50
51
|
lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
|
|
51
52
|
(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
|
|
53
|
+
node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version (or
|
|
54
|
+
the same commit, while @lernapps/tooling depends on an unreleased one).
|
|
53
55
|
|
|
54
56
|
It needs the plan of the first version, confirmed by the creator: started from the plan template as
|
|
55
57
|
.vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
|
|
@@ -74,7 +76,10 @@ publish action to the repo's GitHub Pages), an issue form for content errors, LI
|
|
|
74
76
|
Renovate preset of lernapps). The installed skills and skills-lock.json are committed with the app, like the
|
|
75
77
|
content guide; lernapps update replaces the lernapps-* skills.
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
It installs nothing and commits nothing. Its last lines say what to do next, in this order: git init
|
|
80
|
+
--initial-branch=work (only if <dir> is not a repository yet; lernapps init made one), npm install (installs the
|
|
81
|
+
runtime and the git hooks), git add -A and git commit (the first commit, on work; the hooks check it), then follow
|
|
82
|
+
the plan.
|
|
78
83
|
|
|
79
84
|
Exit codes: 0 when the app is written, 1 when it cannot be (the message says why), 2 on a usage error.
|
|
80
85
|
|
|
@@ -84,8 +89,8 @@ Options:
|
|
|
84
89
|
its file name <NN>-<short name>.md, else 01-plan.md (default: the one in <dir>)
|
|
85
90
|
--templates <dir> the package @lernapps/app-templates to copy from: its folder, or a clone of its repository
|
|
86
91
|
(default: the environment variable LERNAPPS_TEMPLATES, else the installed package)
|
|
87
|
-
--tooling <spec> the npm spec of @lernapps/tooling the app depends on (default:
|
|
88
|
-
@lernapps/tooling)
|
|
92
|
+
--tooling <spec> the npm spec of @lernapps/tooling the app depends on (default: the one lernapps init
|
|
93
|
+
installed, else ^ the version of this @lernapps/tooling)
|
|
89
94
|
-h, --help print this help
|
|
90
95
|
`;
|
|
91
96
|
|
|
@@ -98,6 +103,7 @@ export const fromPackage = (...path: string[]) => join(packageRoot, ...path);
|
|
|
98
103
|
export interface Manifest {
|
|
99
104
|
name?: string;
|
|
100
105
|
version?: string;
|
|
106
|
+
dependencies?: Record<string, string>;
|
|
101
107
|
devDependencies?: Record<string, string>;
|
|
102
108
|
scripts?: Record<string, string>;
|
|
103
109
|
lernapps?: { archetypes?: string[]; scaffold?: Record<string, string[]>; init?: boolean };
|
|
@@ -147,6 +153,19 @@ export function templatesPackage(dir?: string): Templates {
|
|
|
147
153
|
};
|
|
148
154
|
}
|
|
149
155
|
|
|
156
|
+
/** The version of a package built from a commit, not released: its package.json in the repository says 0.0.0. */
|
|
157
|
+
const UNRELEASED = "0.0.0";
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The spec of the runtime an app depends on, for templates of `version`: ^ that version; for templates built from a
|
|
161
|
+
* commit (version 0.0.0, which the registry does not have) the spec this tooling depends on, the same commit
|
|
162
|
+
* (github:lernapps/app-templates#<commit>), so that template and runtime match.
|
|
163
|
+
*/
|
|
164
|
+
export function runtimeSpec(version: string): string {
|
|
165
|
+
if (version !== UNRELEASED) return `^${version}`;
|
|
166
|
+
return readJson(fromPackage("package.json")).dependencies?.[RUNTIME] ?? `^${version}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
150
169
|
/** The archetypes of the installed templates. */
|
|
151
170
|
export const archetypes = (): string[] => templatesPackage().archetypes;
|
|
152
171
|
|
|
@@ -241,6 +260,9 @@ export function writeScaffold(templates: Templates, archetype: string, dir: stri
|
|
|
241
260
|
cpSync(start, dir, { recursive: true });
|
|
242
261
|
}
|
|
243
262
|
|
|
263
|
+
/** The branch the plan works on (rule 8 of the plan template). */
|
|
264
|
+
export const WORK_BRANCH = "work";
|
|
265
|
+
|
|
244
266
|
/** This package, as an app depends on it. */
|
|
245
267
|
export const TOOLING = "@lernapps/tooling";
|
|
246
268
|
|
|
@@ -453,7 +475,7 @@ export function create(args: string[]): number {
|
|
|
453
475
|
// the template uses the runtime of its own checkout (file:.., installed as a copy through .npmrc); the app the
|
|
454
476
|
// version of the runtime it was copied from
|
|
455
477
|
if (app.devDependencies?.[RUNTIME] !== undefined) {
|
|
456
|
-
app.devDependencies = { ...app.devDependencies, [RUNTIME]:
|
|
478
|
+
app.devDependencies = { ...app.devDependencies, [RUNTIME]: runtimeSpec(templates.version) };
|
|
457
479
|
}
|
|
458
480
|
// the template keeps its checkout's runtime with an override (file:..), which npm would otherwise swap for the
|
|
459
481
|
// version this tooling depends on; the app has no checkout to point at
|
|
@@ -463,8 +485,9 @@ export function create(args: string[]): number {
|
|
|
463
485
|
if (Object.keys(kept).length > 0) app["overrides"] = kept;
|
|
464
486
|
else delete app["overrides"];
|
|
465
487
|
}
|
|
466
|
-
// the tooling the app depends on: the one named, else
|
|
467
|
-
|
|
488
|
+
// the tooling the app depends on: the one named, else the one init installed (maybe a local one, --tooling of
|
|
489
|
+
// init), else this version
|
|
490
|
+
const tooling = values.tooling ?? initialised?.devDependencies?.[TOOLING] ?? `^${ownVersion()}`;
|
|
468
491
|
app.devDependencies = {
|
|
469
492
|
...app.devDependencies,
|
|
470
493
|
...ARCHETYPE_DEV_DEPENDENCIES[archetype],
|
|
@@ -490,7 +513,13 @@ export function create(args: string[]): number {
|
|
|
490
513
|
`lernapps create: the ${archetype} app ${name} in ${dir} (app-templates ${templates.version})`,
|
|
491
514
|
...guide.map((line) => `Content guide: ${line}`),
|
|
492
515
|
...(moved ? [`Plan: moved ${LEGACY_PLAN} to ${relative(dir, plan.target)}`] : []),
|
|
493
|
-
|
|
516
|
+
"Next, in this order (create installs and commits nothing):",
|
|
517
|
+
...[
|
|
518
|
+
...(existsSync(join(dir, ".git")) ? [] : [`git init --initial-branch=${WORK_BRANCH}`]),
|
|
519
|
+
"npm install (the runtime and the git hooks)",
|
|
520
|
+
"git add -A, then git commit (the first commit; the hooks check it)",
|
|
521
|
+
`follow ${relative(dir, plan.target)}`,
|
|
522
|
+
].map((step, index) => ` ${index + 1}. ${step}`),
|
|
494
523
|
"",
|
|
495
524
|
].join("\n"),
|
|
496
525
|
);
|
package/generator/init.ts
CHANGED
|
@@ -6,25 +6,28 @@
|
|
|
6
6
|
// plan has chosen one: `skills --archetype <name>` then adds that archetype's. `lernapps create` later takes over the
|
|
7
7
|
// package.json (the marker lernapps.init) and the installed packages.
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
11
12
|
import { dirname, join, resolve } from "node:path";
|
|
12
13
|
import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
|
|
13
14
|
import { UsageError } from "../src/usage.ts";
|
|
14
15
|
import { PLANS } from "../src/plan.ts";
|
|
15
|
-
import { CreateError, fromPackage, ownVersion, readJson, TOOLING, writeJson } from "./create.ts";
|
|
16
|
+
import { CreateError, fromPackage, ownVersion, readJson, TOOLING, WORK_BRANCH, writeJson } from "./create.ts";
|
|
16
17
|
|
|
17
18
|
/** The options of `lernapps init`, as node:util parseArgs takes them. */
|
|
18
19
|
export const INIT_OPTIONS = {
|
|
19
20
|
addon: { type: "string" },
|
|
21
|
+
tooling: { type: "string" },
|
|
20
22
|
help: { type: "boolean", short: "h", default: false },
|
|
21
23
|
} as const satisfies ParseArgsOptionsConfig;
|
|
22
24
|
|
|
23
|
-
export const INIT_USAGE = `Usage: lernapps init <short name> [--addon <package>]
|
|
25
|
+
export const INIT_USAGE = `Usage: lernapps init <short name> [--addon <package>] [--tooling <spec>]
|
|
24
26
|
|
|
25
27
|
Starts an app in the current folder, which must be empty except for .git. It needs git, Node and the npm registry; nothing
|
|
26
28
|
else. In order:
|
|
27
|
-
git repository on the branch work, which the plan names
|
|
29
|
+
git repository on the branch work, which the plan names; no commit. A repository without commits
|
|
30
|
+
switches to work; one with commits stays, and init warns: git switch -c work
|
|
28
31
|
package.json names the folder, depends on @lernapps/tooling (this version) and the add-on
|
|
29
32
|
npm install installs them
|
|
30
33
|
.vibe/plans/01-<short name>.md
|
|
@@ -36,6 +39,12 @@ Then follow the plan: it says what to do, and when to wait for the creator.
|
|
|
36
39
|
|
|
37
40
|
Options:
|
|
38
41
|
--addon <package> a package that extends the tooling, e.g. @lernapps/practice: its plan template and its skills
|
|
42
|
+
--tooling <spec> for maintainers: the npm spec of @lernapps/tooling to install instead of this version, e.g.
|
|
43
|
+
file:/path/to/lernapps-tooling-0.0.0.tgz or github:lernapps/tooling#<commit>; give the same
|
|
44
|
+
to lernapps create. Without it, a tooling of version 0.0.0 (a local build, not on the
|
|
45
|
+
registry) is packed with npm pack into a temporary folder and installed from that tarball
|
|
46
|
+
when its folder is a git checkout (file:<tarball>), with its dependencies, as from the
|
|
47
|
+
registry
|
|
39
48
|
-h, --help print this help
|
|
40
49
|
`;
|
|
41
50
|
|
|
@@ -62,22 +71,45 @@ const SKILLS_BIN = () => {
|
|
|
62
71
|
return join(dirname(manifest), bin?.["skills"] ?? "bin/cli.mjs");
|
|
63
72
|
};
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
export { WORK_BRANCH };
|
|
75
|
+
|
|
76
|
+
/** A git command in `dir`; a git that cannot run is an error of init. */
|
|
77
|
+
function git(dir: string, args: string[]) {
|
|
78
|
+
const result = spawnSync("git", args, { cwd: dir, encoding: "utf8" });
|
|
79
|
+
if (result.error) throw new CreateError(`git cannot run: ${result.error.message}. Install git, then run init again`);
|
|
80
|
+
return { ok: result.status === 0, out: `${result.stdout}${result.stderr}`.trim() };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** What `ensureRepository` did: a line for the output, and a warning when the plan's branch is not checked out. */
|
|
84
|
+
export interface Repository {
|
|
85
|
+
done?: string;
|
|
86
|
+
warning?: string;
|
|
87
|
+
}
|
|
67
88
|
|
|
68
89
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
90
|
+
* Puts the folder on the branch `work`, which the plan names: `create` and the hooks need a repository, and the plan's
|
|
91
|
+
* first rule is to work there. No repository yet → `git init` on `work`. A repository without commits (a fresh
|
|
92
|
+
* `git init` on master or main, a clone of an empty GitHub repo) → its unborn branch becomes `work`: nothing is lost,
|
|
93
|
+
* since there is nothing on it. A repository with commits on another branch is left as it is, with a warning that
|
|
94
|
+
* names the command. No commit.
|
|
71
95
|
*/
|
|
72
|
-
export function ensureRepository(dir: string):
|
|
73
|
-
if (existsSync(join(dir, ".git")))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
96
|
+
export function ensureRepository(dir: string): Repository {
|
|
97
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
98
|
+
const result = git(dir, ["init", "--quiet", `--initial-branch=${WORK_BRANCH}`]);
|
|
99
|
+
if (!result.ok) throw new CreateError(`git init failed: ${result.out}`);
|
|
100
|
+
return { done: `a git repository on the branch ${WORK_BRANCH}` };
|
|
101
|
+
}
|
|
102
|
+
const branch = git(dir, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
103
|
+
if (branch.ok && branch.out === WORK_BRANCH) return {};
|
|
104
|
+
if (!git(dir, ["rev-parse", "--verify", "--quiet", "HEAD"]).ok) {
|
|
105
|
+
const result = git(dir, ["symbolic-ref", "HEAD", `refs/heads/${WORK_BRANCH}`]);
|
|
106
|
+
if (!result.ok) throw new CreateError(`git could not switch to the branch ${WORK_BRANCH}: ${result.out}`);
|
|
107
|
+
return { done: `the repository had no commit yet: now on the branch ${WORK_BRANCH}` };
|
|
108
|
+
}
|
|
109
|
+
const where = branch.ok ? `on the branch ${branch.out}` : "on no branch (detached HEAD)";
|
|
110
|
+
return {
|
|
111
|
+
warning: `the repository has commits and is ${where}, not on ${WORK_BRANCH}, which the plan works on. Run next: git switch -c ${WORK_BRANCH}`,
|
|
112
|
+
};
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
/** The names of the skills of the archetypes, with the archetype each belongs to (lernapps-<archetype>[-…]). */
|
|
@@ -121,6 +153,42 @@ function syncSkills(dir: string, exclude: string[]): void {
|
|
|
121
153
|
);
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
/** The version of a build that is not on the registry: package.json of the repository says 0.0.0. */
|
|
157
|
+
const LOCAL_VERSION = "0.0.0";
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The tarball of the package in `root`, packed by npm pack (with its prepare script, so the build is current) into a
|
|
161
|
+
* new temporary folder.
|
|
162
|
+
*/
|
|
163
|
+
export function packBuild(root: string): string {
|
|
164
|
+
const out = mkdtempSync(join(tmpdir(), "lernapps-tooling-"));
|
|
165
|
+
run("npm", ["pack", "--silent", "--pack-destination", out], (message) => new CreateError(message), root);
|
|
166
|
+
const tarball = readdirSync(out).find((name) => name.endsWith(".tgz"));
|
|
167
|
+
if (tarball === undefined) throw new CreateError(`npm pack in ${root} wrote no tarball into ${out}`);
|
|
168
|
+
return join(out, tarball);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The spec of @lernapps/tooling that init installs: the one given, else this version. A local build (version 0.0.0,
|
|
173
|
+
* which the registry does not have) is packed when its folder is a git checkout and installed from the tarball, else
|
|
174
|
+
* init says to name one with --tooling. Not the folder itself (file:<folder>): npm links it, so the app's
|
|
175
|
+
* node_modules lacks the tooling's dependencies (Playwright for the end-to-end tests, @lernapps/app-templates with
|
|
176
|
+
* the templates and their READMEs); from a tarball they are installed into the app, as from the registry.
|
|
177
|
+
*/
|
|
178
|
+
export function toolingSpec(
|
|
179
|
+
given: string | undefined,
|
|
180
|
+
version = ownVersion(),
|
|
181
|
+
root = fromPackage(),
|
|
182
|
+
pack: (root: string) => string = packBuild,
|
|
183
|
+
): string {
|
|
184
|
+
if (given !== undefined && given !== "") return given;
|
|
185
|
+
if (version !== LOCAL_VERSION) return version;
|
|
186
|
+
if (existsSync(join(root, ".git"))) return `file:${pack(resolve(root))}`;
|
|
187
|
+
throw new CreateError(
|
|
188
|
+
`this @lernapps/tooling is a local build (version ${LOCAL_VERSION}), which the npm registry does not have: name the tooling to install with --tooling <spec>, e.g. file:/path/to/lernapps-tooling-${LOCAL_VERSION}.tgz (npm pack) or github:lernapps/tooling#<commit>, and give the same to lernapps create`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
124
192
|
export function init(args: string[]): number {
|
|
125
193
|
const { values, positionals } = parseArgs({ args, options: INIT_OPTIONS, strict: true, allowPositionals: true });
|
|
126
194
|
if (values.help) {
|
|
@@ -139,6 +207,7 @@ export function init(args: string[]): number {
|
|
|
139
207
|
);
|
|
140
208
|
}
|
|
141
209
|
const addon = values.addon;
|
|
210
|
+
const tooling = toolingSpec(values.tooling);
|
|
142
211
|
const repository = ensureRepository(dir);
|
|
143
212
|
writeJson(join(dir, "package.json"), {
|
|
144
213
|
name,
|
|
@@ -149,7 +218,7 @@ export function init(args: string[]): number {
|
|
|
149
218
|
const fail = (message: string) => new CreateError(message);
|
|
150
219
|
run(
|
|
151
220
|
"npm",
|
|
152
|
-
["install", "--save-dev", "--no-audit", "--no-fund", `${TOOLING}@${
|
|
221
|
+
["install", "--save-dev", "--no-audit", "--no-fund", `${TOOLING}@${tooling}`, ...(addon ? [addon] : [])],
|
|
153
222
|
fail,
|
|
154
223
|
);
|
|
155
224
|
|
|
@@ -167,8 +236,9 @@ export function init(args: string[]): number {
|
|
|
167
236
|
dir,
|
|
168
237
|
archetypeSkills().map((skill) => skill.name),
|
|
169
238
|
);
|
|
239
|
+
if (repository.warning !== undefined) process.stderr.write(`lernapps init: warning: ${repository.warning}\n`);
|
|
170
240
|
process.stdout.write(
|
|
171
|
-
`lernapps init: ${relative(dir, target)} from ${relativeTemplate(template, dir)}; the skills of the packages are installed${repository ? `; ${repository}` : ""}\nNext: read ${relative(dir, target)} completely and follow it\n`,
|
|
241
|
+
`lernapps init: ${relative(dir, target)} from ${relativeTemplate(template, dir)}; the skills of the packages are installed${repository.done ? `; ${repository.done}` : ""}\nNext: ${repository.warning ? `git switch -c ${WORK_BRANCH}, then ` : ""}read ${relative(dir, target)} completely and follow it\n`,
|
|
172
242
|
);
|
|
173
243
|
return 0;
|
|
174
244
|
}
|
package/generator/update.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// them replaced with versions.
|
|
8
8
|
// It never moves the runtime back: a newer version (Renovate moves the runtime too) stays. An app made before the
|
|
9
9
|
// generator wrote the content guide gets its skeleton (generator/create.ts), and one made before the plans had a
|
|
10
|
-
// folder gets its plan .vibe/plan.md moved to .vibe/plans/01-first-version.md (src/plan.ts).
|
|
10
|
+
// folder gets its plan .vibe/plan.md moved to .vibe/plans/01-first-version.md (src/plan.ts). An explainer in format 1 gets `format: 2` and `strand: none` in src/app.yaml where the runtime knows
|
|
11
|
+
// format 2. The app's lernapps-*
|
|
11
12
|
// skills, which the app commits, are replaced with those of this tooling (its skills/), in the layout of
|
|
12
13
|
// `npx skills add`; the app's own skills (app-content, any other) stay as they are. An interactive app's architecture
|
|
13
14
|
// is replaced outside its app sections, which stay as they are (generator/architecture.ts).
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
ownVersion,
|
|
41
42
|
readJson,
|
|
42
43
|
RUNTIME,
|
|
44
|
+
runtimeSpec,
|
|
43
45
|
SKILLS_LOCK,
|
|
44
46
|
templatesPackage,
|
|
45
47
|
TOOLING,
|
|
@@ -57,13 +59,16 @@ export const UPDATE_USAGE = `Usage: lernapps update
|
|
|
57
59
|
Moves the app in the current folder to the versions of this @lernapps/tooling, then runs npm install. npm update
|
|
58
60
|
moves the app within its version ranges; this moves the ranges, and what npm does not know:
|
|
59
61
|
@lernapps/app-templates to the version this tooling depends on: the runtime its tests ran with. A newer version
|
|
60
|
-
(e.g. from Renovate) is kept. A git commit, as apps once pinned, becomes a version
|
|
62
|
+
(e.g. from Renovate) is kept. A git commit, as apps once pinned, becomes a version; while
|
|
63
|
+
this tooling depends on a commit (an unreleased runtime), the app moves to that commit
|
|
61
64
|
@lernapps/tooling to the version of this tooling (unchanged when run from the app)
|
|
62
65
|
.github/workflows/ the actions of lernapps/tooling at the tag of that version
|
|
63
66
|
.agents/skills/app-content/SKILL.md
|
|
64
67
|
the content guide's skeleton, where the app has none yet (and its link for Claude Code)
|
|
65
68
|
.vibe/plan.md the plan of an app made before the plans had a folder: moved to
|
|
66
69
|
.vibe/plans/01-first-version.md, the first of its plans (one per piece of work)
|
|
70
|
+
src/app.yaml of an explainer in format 1, when the runtime knows format 2: adds format: 2 and
|
|
71
|
+
strand: none (the app is not on the learning map; its pages need no competencies)
|
|
67
72
|
.agents/skills/lernapps-*, .claude/skills/lernapps-*, skills-lock.json
|
|
68
73
|
replaced with the skills of this tooling, as npx skills add lays them out: those of
|
|
69
74
|
every phase, and of the archetypes only those of the current plan's archetype; every
|
|
@@ -98,9 +103,16 @@ function lowest(range: string): [number, number, number] | undefined {
|
|
|
98
103
|
const older = (a: [number, number, number], b: [number, number, number]) =>
|
|
99
104
|
a[0] !== b[0] ? a[0] < b[0] : a[1] !== b[1] ? a[1] < b[1] : a[2] < b[2];
|
|
100
105
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The next range of a dependency at `version` (target: ^ that version, or the spec given, e.g. a git commit of an
|
|
108
|
+
* unreleased runtime), and what the output says about it.
|
|
109
|
+
*/
|
|
110
|
+
export function nextRange(
|
|
111
|
+
name: string,
|
|
112
|
+
current: string | undefined,
|
|
113
|
+
version: string,
|
|
114
|
+
target = `^${version}`,
|
|
115
|
+
): { spec: string; line: string } {
|
|
104
116
|
// a local copy (a folder or a tarball of the package, as an app under development uses) is the creator's choice
|
|
105
117
|
if (current !== undefined && /^(file:|link:|\.{0,2}\/)/.test(current)) {
|
|
106
118
|
return { spec: current, line: `${name}: kept ${current}, a local copy` };
|
|
@@ -108,8 +120,9 @@ export function nextRange(name: string, current: string | undefined, version: st
|
|
|
108
120
|
const now = current === undefined ? undefined : lowest(current);
|
|
109
121
|
const next = lowest(version);
|
|
110
122
|
if (current === target) return { spec: current, line: `${name}: unchanged (${current})` };
|
|
111
|
-
// a newer version than this tooling's stays: it must never move back
|
|
112
|
-
|
|
123
|
+
// a newer version than this tooling's stays: it must never move back; a commit (no version to compare) is where the
|
|
124
|
+
// tooling's tests ran, so the app moves to it
|
|
125
|
+
if (lowest(target) !== undefined && now !== undefined && next !== undefined && !older(now, next)) {
|
|
113
126
|
return { spec: current ?? target, line: `${name}: kept ${current}, not moved to ${target}: it is not older` };
|
|
114
127
|
}
|
|
115
128
|
return { spec: target, line: `${name}: ${current ?? "none"} → ${target}` };
|
|
@@ -148,6 +161,35 @@ function migratePlan(): string[] {
|
|
|
148
161
|
];
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
/** The explainer's app file, and the schema file that tells the runtime knows format 2 (learning map). */
|
|
165
|
+
const EXPLAINER_APP = join("src", "app.yaml");
|
|
166
|
+
const EXPLAINER_V2_SCHEMA = join("runtime", "explainer", "explainer.v2.schema.json");
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Moves an explainer from format 1 to format 2 (the learning map): adds `format: 2` and `strand: none` to
|
|
170
|
+
* src/app.yaml when the app has neither, and points its schema comment to explainer.v2.schema.json. `strand: none`
|
|
171
|
+
* keeps the app off the map, so its pages need no competencies; the creator may name a strand later (skill
|
|
172
|
+
* lernapps-content-research). Only where the runtime of this tooling has the v2 schema: an older runtime does not
|
|
173
|
+
* know the fields. An app that is no explainer, or already has a `format` or a `strand`, stays as it is. What it did,
|
|
174
|
+
* as lines of the output.
|
|
175
|
+
*/
|
|
176
|
+
export function migrateExplainer(templatesRoot: string): string[] {
|
|
177
|
+
if (!existsSync(EXPLAINER_APP) || !existsSync(join(templatesRoot, EXPLAINER_V2_SCHEMA))) return [];
|
|
178
|
+
const text = readFileSync(EXPLAINER_APP, "utf8");
|
|
179
|
+
const top = (key: string) => new RegExp(`^${key}:`, "m").test(text);
|
|
180
|
+
if (!top("chapters") || top("format") || top("strand")) return [];
|
|
181
|
+
const schema = /^(# yaml-language-server: \$schema=.*)explainer\.v1\.schema\.json$/m;
|
|
182
|
+
const fields = "format: 2\nstrand: none\n";
|
|
183
|
+
let next: string;
|
|
184
|
+
if (schema.test(text))
|
|
185
|
+
next = text.replace(schema, (_all, head: string) => `${head}explainer.v2.schema.json\n${fields.trimEnd()}`);
|
|
186
|
+
else next = fields + text;
|
|
187
|
+
writeFileSync(EXPLAINER_APP, next);
|
|
188
|
+
return [
|
|
189
|
+
`explainer: ${EXPLAINER_APP} moved to format 2: added format: 2 and strand: none (not on the learning map; pages need no competencies). To join the map, pick a strand of https://lernapps.net/apps/map/ and give every page competencies`,
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
/**
|
|
152
194
|
* The skills of this tooling, as it ships them (skills/<group>/<name>/), and as the app keeps them (flat, by name).
|
|
153
195
|
* Every app has those of the groups process, tooling and content; of the group archetypes only those of its own
|
|
@@ -371,7 +413,7 @@ export function update(args: string[]): number {
|
|
|
371
413
|
const notMoved: string[] = [];
|
|
372
414
|
const runtime = devDependencies[RUNTIME];
|
|
373
415
|
if (runtime !== undefined) {
|
|
374
|
-
const next = nextRange(RUNTIME, runtime, templates.version);
|
|
416
|
+
const next = nextRange(RUNTIME, runtime, templates.version, runtimeSpec(templates.version));
|
|
375
417
|
if (next.spec !== runtime) specs.push(`${RUNTIME}@${next.spec}`);
|
|
376
418
|
lines.push(next.line);
|
|
377
419
|
}
|
|
@@ -404,6 +446,8 @@ export function update(args: string[]): number {
|
|
|
404
446
|
lines.push(...guide.map((line) => `content guide: ${line}`));
|
|
405
447
|
const plan = migratePlan();
|
|
406
448
|
lines.push(...plan);
|
|
449
|
+
const explainer = migrateExplainer(templates.root);
|
|
450
|
+
lines.push(...explainer);
|
|
407
451
|
const skills = [...replaceSkills(), ...unignoreSkills()];
|
|
408
452
|
lines.push(...skills);
|
|
409
453
|
const architecture = updateArchitecture();
|
|
@@ -421,6 +465,7 @@ export function update(args: string[]): number {
|
|
|
421
465
|
if (skillsMoved) files.push(SKILLS_LOCK);
|
|
422
466
|
if (skills.some((line) => line.includes(".gitignore"))) files.push(".gitignore");
|
|
423
467
|
if (architecture.lines.length > 0) files.push(`${ARCHITECTURE}/`);
|
|
468
|
+
if (explainer.length > 0) files.push(EXPLAINER_APP);
|
|
424
469
|
if (plan.some((line) => line.startsWith("plan: moved"))) files.push(".vibe/ (git add -A .vibe, to keep the move)");
|
|
425
470
|
process.stdout.write(
|
|
426
471
|
`Next: npx --no -- lernapps check, then commit ${files.slice(0, -1).join(", ")} and ${files.at(-1) ?? ""}\n`,
|
|
@@ -45,7 +45,7 @@ a correction.
|
|
|
45
45
|
|
|
46
46
|
## Notes
|
|
47
47
|
|
|
48
|
-
<!-- Curriculum orientation, new competencies by chapter, didactic approach, scope, sources and licences, open
|
|
48
|
+
<!-- Curriculum orientation, new competencies by chapter (ids of the strand), didactic approach, scope, sources and licences, open
|
|
49
49
|
questions. -->
|
|
50
50
|
|
|
51
51
|
## Explore
|
|
@@ -68,12 +68,19 @@ questions. -->
|
|
|
68
68
|
"(guess)". Topics: which competencies, chapters or rounds, for which school years; hurdles and examples;
|
|
69
69
|
amount; any change of look and feel or terms.
|
|
70
70
|
- [ ] Load skill `lernapps-content-research`. Research what the guide's curriculum and sources do not cover. Under
|
|
71
|
-
"Notes": curriculum orientation, new competencies by chapter as "Ich kann …", verified sources.
|
|
71
|
+
"Notes": curriculum orientation, new competencies by chapter as "Ich kann …", verified sources. The app keeps its
|
|
72
|
+
strand; with one, the new competencies are only those of that strand in the learning map (ids), as skill
|
|
73
|
+
`lernapps-content-research` says, an explainer as a rule one page each; competencies that build on each other (the
|
|
74
|
+
map's `requires`) share a chapter, in that order. None in the map → propose it to the
|
|
75
|
+
catalog. `strand: none` and a strand fits → recommend it.
|
|
72
76
|
- [ ] Under "Notes": the didactic approach per competency and the scope (in, out, data range).
|
|
73
77
|
- [ ] A kind of content the guide does not cover (a new page structure, screen, component or kind of exercise) →
|
|
74
78
|
load skills `lernapps-<archetype>-design` and `lernapps-exercise-patterns`. Else skip this task.
|
|
75
|
-
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, new competencies by chapter
|
|
76
|
-
|
|
79
|
+
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, new competencies by chapter (with their
|
|
80
|
+
ids), didactic
|
|
81
|
+
approach, scope, every "(guess)", sources and how you checked them, what is unchecked. With a strand, the map
|
|
82
|
+
decides the structure: new competencies that build on each other (its `requires`) share a chapter, in that
|
|
83
|
+
order; no curriculum search is needed for it. Stop and wait.
|
|
77
84
|
Changes → change, ask again.
|
|
78
85
|
- [ ] Set `phase: plan`: change the value line (`grep '^phase:'` prints `phase: plan`).
|
|
79
86
|
|
|
@@ -110,7 +117,8 @@ questions. -->
|
|
|
110
117
|
### Content method
|
|
111
118
|
|
|
112
119
|
<!-- One line each: the guide's patterns, figures and components used, and where; anything new, what it does and
|
|
113
|
-
cannot
|
|
120
|
+
cannot; an explainer on the map: per new page its prerequisites the app does not practise, and which get a recap
|
|
121
|
+
page, which a link. Something new goes into the guide in the slice that adds it. -->
|
|
114
122
|
|
|
115
123
|
### Completed
|
|
116
124
|
|
|
@@ -42,7 +42,7 @@ For you, the assistant.
|
|
|
42
42
|
|
|
43
43
|
## Notes
|
|
44
44
|
|
|
45
|
-
<!--
|
|
45
|
+
<!-- Strand (<subject>/<strand> or none), curriculum orientation, competencies by chapter (ids), didactic approach, scope, sources and licences, open
|
|
46
46
|
questions. -->
|
|
47
47
|
|
|
48
48
|
## Explore
|
|
@@ -59,7 +59,7 @@ questions. -->
|
|
|
59
59
|
|
|
60
60
|
- [ ] The skills of the phases are in `.agents/skills/` (`npx --yes @lernapps/tooling init <short name>` put them there
|
|
61
61
|
and wrote this plan; the app commits them). `.agents/skills/lernapps-app` missing → stop and tell the creator
|
|
62
|
-
the folder was not started with `init`.
|
|
62
|
+
the folder was not started with `init`. `git branch --show-current` prints `work`; else `git switch -c work`.
|
|
63
63
|
- [ ] Load skill `lernapps-app`. Tell the creator in two sentences what a lernapps app is and where it will live: at
|
|
64
64
|
their own address, free, public, and once listed as a copy on lernapps.net. The technical part (GitHub) comes at the end, and you do it.
|
|
65
65
|
- [ ] Interview: ask the topics "Goal" and "Learners" of "Answers".
|
|
@@ -74,7 +74,11 @@ questions. -->
|
|
|
74
74
|
which. No answer → ask once more; still none → write your assumption with "(guess)". Done when every topic
|
|
75
75
|
has an answer or a guess.
|
|
76
76
|
- [ ] Load skill `lernapps-content-research`. Research as it says. Under "Notes": curriculum orientation,
|
|
77
|
-
competencies by chapter as "Ich kann …", verified sources. No competencies → not done.
|
|
77
|
+
competencies by chapter as "Ich kann …", verified sources. No competencies → not done. First the learning map as
|
|
78
|
+
skill `lernapps-content-research` says: recommend a strand that fits (`<subject>/<strand>`), else `none`; with a
|
|
79
|
+
strand, only its competencies, by their ids from the map. Write both under "Notes", and how they map onto
|
|
80
|
+
chapters and pages (an explainer: as a rule one page per competency; competencies that build on each other, by
|
|
81
|
+
the map's `requires`, share a chapter, in that order).
|
|
78
82
|
- [ ] Under "Notes", one line per competency: how learners get there (derive, not only read off; e.g. a tree diagram
|
|
79
83
|
shows how a probability comes about), which misconception the feedback catches.
|
|
80
84
|
- [ ] Under "Notes", the scope: what is in, what is out and why, the data range (e.g. "all main-group elements",
|
|
@@ -82,8 +86,10 @@ questions. -->
|
|
|
82
86
|
- [ ] Fetch https://lernapps.net/apps/schemas/entry.v1.schema.json and read each property's description. Ask what
|
|
83
87
|
helps teachers and parents find the app. Fill "Entry draft". Search words: one or two words each, as people
|
|
84
88
|
type them.
|
|
85
|
-
- [ ] **Checkpoint:** research. Show in plain words: curriculum orientation, competencies by
|
|
86
|
-
approach, scope, every "(guess)", sources and how you
|
|
89
|
+
- [ ] **Checkpoint:** research. Show in plain words: the strand or why none, curriculum orientation, competencies by
|
|
90
|
+
chapter (with their ids) and their pages, didactic approach, scope, every "(guess)", sources and how you
|
|
91
|
+
checked them, what is unchecked, archetype and reason. With a strand, the map decides the structure: competencies
|
|
92
|
+
that build on each other (its `requires`) share a chapter, in that order; no curriculum search is needed for it.
|
|
87
93
|
Stop and wait. Changes → change, ask again.
|
|
88
94
|
- [ ] Set `phase: plan`: change the value line (`grep '^phase:'` prints `phase: plan`).
|
|
89
95
|
|
|
@@ -132,11 +138,12 @@ questions. -->
|
|
|
132
138
|
- [ ] Map the content as skill `lernapps-<archetype>-design` says; reread it if it is no longer in view.
|
|
133
139
|
- [ ] Choose a pattern per competency as skill `lernapps-exercise-patterns` says (loaded in Explore; reread it if it
|
|
134
140
|
is no longer in view). Write "Content method".
|
|
135
|
-
- [ ] Write the Code tasks under "Code": pages or screens, their content, what each shows. Each ends in a commit
|
|
141
|
+
- [ ] Write the Code tasks under "Code": pages or screens, their content, what each shows. Each ends in a commit:
|
|
142
|
+
one task, one commit. Steps too small for a commit of their own → one task.
|
|
136
143
|
After the first task that shows real content: the task "Show the creator a first look (`early-preview`)".
|
|
137
144
|
- [ ] Each screen, component or interactive exercise: a sketch (static SVG or image, or the steps with what
|
|
138
145
|
learners see after each). Words alone are not enough. Keep sketch files in `.vibe/sketches/`, which `create` leaves alone.
|
|
139
|
-
- [ ] Show the creator, short: goal, competencies by chapter, entry draft, tasks, sketches; the content method in
|
|
146
|
+
- [ ] Show the creator, short: goal, competencies by chapter (chapters along the map's `requires`), entry draft, tasks, sketches; the content method in
|
|
140
147
|
plain words (what learners see and do, what it cannot do). At most 2 questions.
|
|
141
148
|
- [ ] **Checkpoint:** confirm the plan and the content method, with the sketches shown. Stop and wait for an
|
|
142
149
|
explicit yes. Changes → change, ask again. No `create` before the yes.
|
|
@@ -145,7 +152,8 @@ questions. -->
|
|
|
145
152
|
### Content method
|
|
146
153
|
|
|
147
154
|
<!-- One line each: exercise patterns and where; figures and how they are made; data files; each component, what it
|
|
148
|
-
checks and cannot; how a later agent adds a chapter.
|
|
155
|
+
checks and cannot; how a later agent adds a chapter. An explainer on the map: per page its prerequisites (`requires`)
|
|
156
|
+
the app does not practise, and which get a recap page, which a link. Becomes the content guide in Code. -->
|
|
149
157
|
|
|
150
158
|
### Completed
|
|
151
159
|
|
|
@@ -153,7 +161,8 @@ checks and cannot; how a later agent adds a chapter. Becomes the content guide i
|
|
|
153
161
|
|
|
154
162
|
<!--
|
|
155
163
|
- Build the app from the archetype and the confirmed plan, locally on `work`. Work on your own.
|
|
156
|
-
- Commit after each task
|
|
164
|
+
- Commit after each task, literally: one task, one commit, its box ticked in it; the hooks check. Never one commit
|
|
165
|
+
for several tasks: join them into one task in the plan first. A check fails → skill `lernapps-cli`. Fix from the message; never
|
|
157
166
|
bypass a hook.
|
|
158
167
|
- Undecidable → choose, mark "(guess)" under "Key decisions", go on.
|
|
159
168
|
- Creator asks for a change → first say your reading in one sentence ("Ich nehme alle 44 Hauptgruppenelemente
|
|
@@ -168,10 +177,15 @@ checks and cannot; how a later agent adds a chapter. Becomes the content guide i
|
|
|
168
177
|
- [ ] Load skill `lernapps-cli`. Create the app in this plan's folder as it says. `create` keeps this plan. Never
|
|
169
178
|
write the app by hand.
|
|
170
179
|
- [ ] Fill the content guide `.agents/skills/app-content/SKILL.md` from "Notes" and "Content method"
|
|
171
|
-
(`app-content-guide`).
|
|
172
|
-
|
|
180
|
+
(`app-content-guide`). Keep its nine headings and fill each: Curriculum, Competencies, Content structure,
|
|
181
|
+
Terminology, Exercise patterns, Figures, Sources, Adding a chapter or round, Rules learned from mistakes
|
|
182
|
+
("None yet." until the first). `grep -c "FILL IN" .agents/skills/app-content/SKILL.md` prints 0. Commit it
|
|
183
|
+
before any content.
|
|
173
184
|
- [ ] Load skill `lernapps-<archetype>` and the content guide. Questions with options and feedback: also skill
|
|
174
185
|
`lernapps-content-questions`.
|
|
186
|
+
- [ ] An explainer: decide on `scope` as `references/structure.md` of skill `lernapps-explainer` says. Needed with an
|
|
187
|
+
`open-task` or the creator's material: then `scope` in `src/app.yaml` and on every page
|
|
188
|
+
(`explainer-scope-matches`). Write the choice under "Key decisions".
|
|
175
189
|
- [ ] <the tasks written in Plan; after each, commit on `work`; after the first with real content, show the
|
|
176
190
|
creator a first look (`early-preview`) and go on>
|
|
177
191
|
- [ ] The content guide matches what was built (`app-content-matches`).
|
|
@@ -87,7 +87,9 @@ scope: tooling
|
|
|
87
87
|
severity: error
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
- Keep `.agents/skills/app-content/SKILL.md` with its fixed headings
|
|
90
|
+
- Keep `.agents/skills/app-content/SKILL.md` with its fixed headings, in this order, each filled: Curriculum,
|
|
91
|
+
Competencies, Content structure, Terminology, Exercise patterns, Figures, Sources, Adding a chapter or round,
|
|
92
|
+
Rules learned from mistakes ("None yet." until the first rule).
|
|
91
93
|
- After the plan is confirmed and before content: replace every placeholder ("FILL IN") with concrete instructions
|
|
92
94
|
for writing new content: files, ids, data files, an example of this app. Not how the app works, no general advice.
|
|
93
95
|
- A content decision changes (pattern, figure type, source rule) → change the guide in the same commit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lernapps/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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": "
|
|
110
|
+
"@lernapps/app-templates": "github:lernapps/app-templates#4fdc214e60ce4523d8233d3ed9b37641601b2fa9",
|
|
111
111
|
"axe-core": "4.14.0",
|
|
112
112
|
"playwright": "1.64.1",
|
|
113
113
|
"skills": "1.7.4",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@doctc/arc42": "0.38.0",
|
|
118
|
-
"@lernapps/site": "github:lernapps/lernapps.github.io#
|
|
118
|
+
"@lernapps/site": "github:lernapps/lernapps.github.io#0f6257d3dffcbf69ffe962d0b80bd7298a62de02",
|
|
119
119
|
"@types/node": "26.6.5",
|
|
120
120
|
"ajv": "8.20.0",
|
|
121
121
|
"vite-plus": "1.4.0"
|