proschi 0.9.0 → 0.10.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/cli.cjs +98 -16
- package/dist/server.cjs +2 -2
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -20267,6 +20267,12 @@ function problemFromFiles(id, files) {
|
|
|
20267
20267
|
};
|
|
20268
20268
|
}
|
|
20269
20269
|
|
|
20270
|
+
// ../frontend/src/practice/lessonBlocks.ts
|
|
20271
|
+
var CALLOUT_TONES = ["tip", "pitfall", "interview", "takeaway"];
|
|
20272
|
+
var RICH_BLOCKS = ["tldr", "callout", "numbers", "deepdive", "quiz"];
|
|
20273
|
+
var FENCE = /^\s*(`{3,})\s*([\w-]*)(?:\s+(.*?))?\s*$/;
|
|
20274
|
+
var closesFence = (line, ticks) => new RegExp(`^\\s*\`{${ticks.length},}\\s*$`).test(line);
|
|
20275
|
+
|
|
20270
20276
|
// ../frontend/src/practice/lesson.ts
|
|
20271
20277
|
var LESSON_HEADINGS = [
|
|
20272
20278
|
"What you'll learn",
|
|
@@ -20279,19 +20285,43 @@ var LESSON_HEADINGS = [
|
|
|
20279
20285
|
"Further reading"
|
|
20280
20286
|
];
|
|
20281
20287
|
var normalize = (heading) => heading.replace(/[‘’]/g, "'").trim();
|
|
20282
|
-
function
|
|
20283
|
-
const
|
|
20284
|
-
|
|
20288
|
+
function scan(source2) {
|
|
20289
|
+
const prose = [];
|
|
20290
|
+
const blocks2 = [];
|
|
20291
|
+
const open = [];
|
|
20292
|
+
let code;
|
|
20285
20293
|
source2.replace(/\r\n?/g, "\n").split("\n").forEach((text, i) => {
|
|
20286
|
-
|
|
20287
|
-
|
|
20294
|
+
const line = i + 1;
|
|
20295
|
+
if (code !== void 0) {
|
|
20296
|
+
if (closesFence(text, code)) code = void 0;
|
|
20297
|
+
return;
|
|
20298
|
+
}
|
|
20299
|
+
const top = open.at(-1);
|
|
20300
|
+
if (top && closesFence(text, top.ticks)) {
|
|
20301
|
+
open.pop();
|
|
20302
|
+
return;
|
|
20303
|
+
}
|
|
20304
|
+
const fence = FENCE.exec(text);
|
|
20305
|
+
if (fence) {
|
|
20306
|
+
const [, ticks, lang, info = ""] = fence;
|
|
20307
|
+
if (top) blocks2.find((b) => b.line === top.line).body.push({ text, line });
|
|
20308
|
+
if (RICH_BLOCKS.includes(lang)) {
|
|
20309
|
+
const block = { lang, info, line, body: [] };
|
|
20310
|
+
blocks2.push(block);
|
|
20311
|
+
open.push({ ...block, ticks });
|
|
20312
|
+
} else code = ticks;
|
|
20313
|
+
return;
|
|
20314
|
+
}
|
|
20315
|
+
if (top) blocks2.find((b) => b.line === top.line).body.push({ text, line });
|
|
20316
|
+
if (top?.lang !== "quiz") prose.push({ text, line, ...top ? { block: blocks2.find((b) => b.line === top.line) } : {} });
|
|
20288
20317
|
});
|
|
20289
|
-
return
|
|
20318
|
+
return { prose, blocks: blocks2 };
|
|
20290
20319
|
}
|
|
20320
|
+
var proseLines = (source2) => scan(source2).prose;
|
|
20291
20321
|
function h2Headings(source2) {
|
|
20292
|
-
return proseLines(source2).map(({ text, line }) => ({ m: /^##\s+(.*?)\s*#*\s*$/.exec(text), line })).filter((h) => h.m !== null).map(({ m, line }) => ({ text: m[1], line }));
|
|
20322
|
+
return proseLines(source2).filter((l) => !l.block).map(({ text, line }) => ({ m: /^##\s+(.*?)\s*#*\s*$/.exec(text), line })).filter((h) => h.m !== null).map(({ m, line }) => ({ text: m[1], line }));
|
|
20293
20323
|
}
|
|
20294
|
-
function lessonIssues(source2) {
|
|
20324
|
+
function lessonIssues(source2, cardIds) {
|
|
20295
20325
|
const issues = [];
|
|
20296
20326
|
if (!source2.trim()) return [{ message: "The lesson is empty" }];
|
|
20297
20327
|
const headings = h2Headings(source2);
|
|
@@ -20310,6 +20340,7 @@ function lessonIssues(source2) {
|
|
|
20310
20340
|
lastName = required;
|
|
20311
20341
|
}
|
|
20312
20342
|
}
|
|
20343
|
+
issues.push(...blockIssues(source2, cardIds));
|
|
20313
20344
|
for (const { text, line } of proseLines(source2)) {
|
|
20314
20345
|
const prose = text.replace(/`[^`]*`/g, "");
|
|
20315
20346
|
for (const m of prose.matchAll(/\]\(([^)\s]*)[^)]*\)/g)) {
|
|
@@ -20318,6 +20349,26 @@ function lessonIssues(source2) {
|
|
|
20318
20349
|
}
|
|
20319
20350
|
return issues;
|
|
20320
20351
|
}
|
|
20352
|
+
function blockIssues(source2, cardIds) {
|
|
20353
|
+
const issues = [];
|
|
20354
|
+
for (const block of scan(source2).blocks) {
|
|
20355
|
+
const { lang, info, line } = block;
|
|
20356
|
+
const tone = info.split(/\s+/)[0];
|
|
20357
|
+
if (lang === "callout" && !CALLOUT_TONES.includes(tone))
|
|
20358
|
+
issues.push({ message: `A callout needs a tone after "callout": ${CALLOUT_TONES.join(", ")}${tone ? `, not "${tone}"` : ""}`, line });
|
|
20359
|
+
if (lang === "deepdive" && !info.trim()) issues.push({ message: 'A deepdive needs a title after "deepdive"', line });
|
|
20360
|
+
if (lang !== "callout" && lang !== "deepdive" && info.trim()) issues.push({ message: `A ${lang} block takes nothing after its name`, line });
|
|
20361
|
+
if (!block.body.some((l) => l.text.trim())) issues.push({ message: `The ${lang} block is empty`, line });
|
|
20362
|
+
for (const { text, line: at } of block.body) {
|
|
20363
|
+
if (lang === "quiz") {
|
|
20364
|
+
for (const id of text.split(/\s+/).filter(Boolean)) if (cardIds && !cardIds.has(id)) issues.push({ message: `The quiz names "${id}", which is not a review card`, line: at });
|
|
20365
|
+
} else if (FENCE.test(text)) continue;
|
|
20366
|
+
else if (/^#{1,2}\s/.test(text)) issues.push({ message: `Headings inside a ${lang} block must be ### or smaller, so the lesson's sections stay its own`, line: at });
|
|
20367
|
+
else if (lang === "numbers" && text.trim() && !/^[^|]+\|\s*\S/.test(text)) issues.push({ message: 'Each line of a numbers block is "value | label"', line: at });
|
|
20368
|
+
}
|
|
20369
|
+
}
|
|
20370
|
+
return issues;
|
|
20371
|
+
}
|
|
20321
20372
|
|
|
20322
20373
|
// ../frontend/src/practice/markdown.ts
|
|
20323
20374
|
function safeHref(href) {
|
|
@@ -20395,7 +20446,6 @@ function delimiterRow(line) {
|
|
|
20395
20446
|
return cells.map((c) => c.startsWith(":") && c.endsWith(":") ? "center" : c.endsWith(":") ? "right" : c.startsWith(":") ? "left" : void 0);
|
|
20396
20447
|
}
|
|
20397
20448
|
var LIST_ITEM = /^\s*(?:([-*+])|(\d+)[.)])\s+(.*)$/;
|
|
20398
|
-
var FENCE = /^\s*```\s*([\w-]*)(?:\s+[\w-]+)*\s*$/;
|
|
20399
20449
|
var QUOTE = /^\s{0,3}>\s?(.*)$/;
|
|
20400
20450
|
function parseMarkdown(source2, slug2 = slugger()) {
|
|
20401
20451
|
const lines = source2.replace(/\r\n?/g, "\n").split("\n");
|
|
@@ -20419,9 +20469,10 @@ function parseMarkdown(source2, slug2 = slugger()) {
|
|
|
20419
20469
|
const fence = FENCE.exec(line);
|
|
20420
20470
|
if (fence) {
|
|
20421
20471
|
flush();
|
|
20472
|
+
const [, ticks, lang, info = ""] = fence;
|
|
20422
20473
|
const body = [];
|
|
20423
|
-
while (++i < lines.length &&
|
|
20424
|
-
blocks2.push({ kind: "code", ...
|
|
20474
|
+
while (++i < lines.length && !closesFence(lines[i], ticks)) body.push(lines[i]);
|
|
20475
|
+
blocks2.push(isRich(lang) ? richBlock(lang, info, body.join("\n"), slug2) : { kind: "code", ...lang ? { lang } : {}, text: body.join("\n") });
|
|
20425
20476
|
continue;
|
|
20426
20477
|
}
|
|
20427
20478
|
if (!line.trim()) {
|
|
@@ -20477,6 +20528,30 @@ function parseMarkdown(source2, slug2 = slugger()) {
|
|
|
20477
20528
|
flush();
|
|
20478
20529
|
return blocks2;
|
|
20479
20530
|
}
|
|
20531
|
+
var isRich = (lang) => RICH_BLOCKS.includes(lang);
|
|
20532
|
+
function richBlock(lang, info, body, slug2) {
|
|
20533
|
+
switch (lang) {
|
|
20534
|
+
case "tldr":
|
|
20535
|
+
return { kind: "tldr", children: parseMarkdown(body, slug2) };
|
|
20536
|
+
case "callout": {
|
|
20537
|
+
const [, tone = "", title = ""] = /^(\S*)\s*(.*)$/.exec(info) ?? [];
|
|
20538
|
+
const known = CALLOUT_TONES.includes(tone) ? tone : "tip";
|
|
20539
|
+
return { kind: "callout", tone: known, ...title ? { title: parseInline(title) } : {}, children: parseMarkdown(body, slug2) };
|
|
20540
|
+
}
|
|
20541
|
+
case "numbers":
|
|
20542
|
+
return {
|
|
20543
|
+
kind: "numbers",
|
|
20544
|
+
items: body.split("\n").filter((l) => l.trim()).map((l) => {
|
|
20545
|
+
const bar = l.indexOf("|");
|
|
20546
|
+
return bar < 0 ? { value: l.trim(), label: [] } : { value: l.slice(0, bar).trim(), label: parseInline(l.slice(bar + 1).trim()) };
|
|
20547
|
+
})
|
|
20548
|
+
};
|
|
20549
|
+
case "deepdive":
|
|
20550
|
+
return { kind: "deepdive", title: parseInline(info || "Deep dive"), children: parseMarkdown(body, slug2) };
|
|
20551
|
+
case "quiz":
|
|
20552
|
+
return { kind: "quiz", ids: body.split(/\s+/).filter(Boolean) };
|
|
20553
|
+
}
|
|
20554
|
+
}
|
|
20480
20555
|
|
|
20481
20556
|
// ../frontend/src/practice/modes/mdItems.ts
|
|
20482
20557
|
function splitAt(lines, level) {
|
|
@@ -21094,7 +21169,7 @@ function validateProblem(problem, engine = defaultEngine, options = {}) {
|
|
|
21094
21169
|
if (!problem.statement.includes(`**${name}**`)) add(PROBLEM_MD, `The statement must name the use case **${name}** in bold`);
|
|
21095
21170
|
if (!problem.solution.includes(`usecase "${name}"`)) add(SOLUTION, `The solution must define usecase "${name}" from the traffic`);
|
|
21096
21171
|
}
|
|
21097
|
-
if (problem.lesson !== void 0) for (const issue of lessonIssues(problem.lesson)) add(LESSON_MD, issue.message, issue.line);
|
|
21172
|
+
if (problem.lesson !== void 0) for (const issue of lessonIssues(problem.lesson, options.cardIds)) add(LESSON_MD, issue.message, issue.line);
|
|
21098
21173
|
const importLine = problem.given.split("\n").findIndex((l) => /^\s*import /.test(l));
|
|
21099
21174
|
if (importLine >= 0) add(GIVEN, "The given must not import anything", importLine + 1);
|
|
21100
21175
|
report(GIVEN, parse(problem.given).diagnostics.filter((d) => d.severity === "error"));
|
|
@@ -21808,7 +21883,7 @@ var FACTOR = 19 / 81;
|
|
|
21808
21883
|
|
|
21809
21884
|
// ../frontend/src/learn/achievements.ts
|
|
21810
21885
|
var TIERS = ["bronze", "silver", "gold"];
|
|
21811
|
-
var ICONS = ["layers", "brain", "flame", "trophy", "check", "zap", "coins", "calculator", "target", "map", "database", "radio", "shield", "star"];
|
|
21886
|
+
var ICONS = ["layers", "brain", "flame", "trophy", "check", "zap", "coins", "calculator", "target", "map", "database", "radio", "shield", "star", "book"];
|
|
21812
21887
|
var RULE_FIELDS = {
|
|
21813
21888
|
reviews: { required: ["min"], optional: [] },
|
|
21814
21889
|
mastered: { required: ["min"], optional: [] },
|
|
@@ -21825,7 +21900,10 @@ var RULE_FIELDS = {
|
|
|
21825
21900
|
"challenge-streak": { required: ["min"], optional: [] },
|
|
21826
21901
|
"game-waves": { required: ["min"], optional: [] },
|
|
21827
21902
|
"game-clears": { required: ["min"], optional: [] },
|
|
21828
|
-
"game-ascension": { required: ["min"], optional: [] }
|
|
21903
|
+
"game-ascension": { required: ["min"], optional: [] },
|
|
21904
|
+
lessons: { required: ["min"], optional: ["stage"] },
|
|
21905
|
+
"stage-lessons": { required: [], optional: [] },
|
|
21906
|
+
"all-lessons": { required: [], optional: [] }
|
|
21829
21907
|
};
|
|
21830
21908
|
var RULE_KINDS = Object.keys(RULE_FIELDS);
|
|
21831
21909
|
var ACHIEVEMENTS_LOCK_FILE = "achievements.lock";
|
|
@@ -21913,6 +21991,10 @@ function checkRule(raw, context, retired = false) {
|
|
|
21913
21991
|
if (rule.stage !== void 0 && (typeof rule.stage !== "string" || !context.stages.includes(rule.stage))) return `"stage" names "${String(rule.stage)}", which is not a roadmap stage`;
|
|
21914
21992
|
if (kind === "game-waves" && rule.min > 24) return '"min" of a game-waves rule is at most 24, the last wave of Endless';
|
|
21915
21993
|
if (kind === "game-ascension" && rule.min > 10) return '"min" of a game-ascension rule is at most 10';
|
|
21994
|
+
if (kind === "lessons") {
|
|
21995
|
+
const reachable = rule.stage ? context.stageProblems?.[rule.stage]?.length ?? Infinity : context.problems.length;
|
|
21996
|
+
if (rule.min > reachable) return `"min" is ${String(rule.min)}, but only ${reachable} lesson(s) can count towards it`;
|
|
21997
|
+
}
|
|
21916
21998
|
if (kind === "solved" || kind === "first-run" || kind === "under-reference") {
|
|
21917
21999
|
const reachable = context.problems.filter((p) => (!rule.difficulty || p.difficulty === rule.difficulty) && (!rule.tag || p.tags.includes(rule.tag))).length;
|
|
21918
22000
|
if (rule.min > reachable) return `"min" is ${String(rule.min)}, but only ${reachable} problem(s) can count towards it`;
|
|
@@ -22094,7 +22176,7 @@ ${ACHIEVEMENTS_USAGE}`);
|
|
|
22094
22176
|
err(`${displayPath3(file)}: not valid JSON: ${e.message}`);
|
|
22095
22177
|
return 1;
|
|
22096
22178
|
}
|
|
22097
|
-
const violations = checkAchievements(raw, { problems: problemsIn(problemsDir), topics, stages: ROADMAP.map((s) => s.id), lock: lock ?? "" }).violations;
|
|
22179
|
+
const violations = checkAchievements(raw, { problems: problemsIn(problemsDir), topics, stages: ROADMAP.map((s) => s.id), stageProblems: Object.fromEntries(ROADMAP.map((s) => [s.id, s.problems])), lock: lock ?? "" }).violations;
|
|
22098
22180
|
if (lock === void 0) {
|
|
22099
22181
|
const fresh = violations.filter((v) => !v.message.startsWith("New achievement"));
|
|
22100
22182
|
violations.length = 0;
|
|
@@ -26196,7 +26278,7 @@ ${IMPORT_USAGE}`);
|
|
|
26196
26278
|
}
|
|
26197
26279
|
|
|
26198
26280
|
// src/cli.ts
|
|
26199
|
-
var VERSION2 = true ? "0.
|
|
26281
|
+
var VERSION2 = true ? "0.10.0" : "dev";
|
|
26200
26282
|
var USAGE = `Usage:
|
|
26201
26283
|
proschi check [--strict] [--format text|github|json] [--openapi <node>=<spec>]... <file|dir>...
|
|
26202
26284
|
proschi parse <file>
|
package/dist/server.cjs
CHANGED
|
@@ -29432,7 +29432,7 @@ function positionAt(text, offset) {
|
|
|
29432
29432
|
|
|
29433
29433
|
// src/server.ts
|
|
29434
29434
|
if (process.argv.includes("--version")) {
|
|
29435
|
-
console.log(true ? "0.
|
|
29435
|
+
console.log(true ? "0.10.0" : "dev");
|
|
29436
29436
|
process.exit(0);
|
|
29437
29437
|
}
|
|
29438
29438
|
var connection = (0, import_node.createConnection)(import_node.ProposedFeatures.all);
|
|
@@ -29473,7 +29473,7 @@ connection.onInitialize(() => ({
|
|
|
29473
29473
|
documentFormattingProvider: true,
|
|
29474
29474
|
codeActionProvider: { codeActionKinds: [import_node.CodeActionKind.QuickFix] }
|
|
29475
29475
|
},
|
|
29476
|
-
serverInfo: { name: "proschi-language-server", version: true ? "0.
|
|
29476
|
+
serverInfo: { name: "proschi-language-server", version: true ? "0.10.0" : "dev" }
|
|
29477
29477
|
}));
|
|
29478
29478
|
function validate(document) {
|
|
29479
29479
|
const analysis = analysisOf(document);
|