@sybelblue/prairielearn-navigator 1.0.1 → 1.1.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/README.md CHANGED
@@ -127,3 +127,29 @@ the pull request. Run it from the repository root so annotation paths resolve:
127
127
  ```
128
128
 
129
129
  Requires Node.js 22 or newer.
130
+
131
+ ## Library API
132
+
133
+ The package also exposes a typed, headless resolver for tools that need the
134
+ files used by each question without starting VS Code or invoking the CLI:
135
+
136
+ ```ts
137
+ import { createQuestionFileResolver } from "@sybelblue/prairielearn-navigator";
138
+
139
+ const resolver = createQuestionFileResolver({
140
+ courseRoot: "/path/to/course",
141
+ questions: [
142
+ { qid: "vectors/addition", directory: "questions/vectors/addition" },
143
+ ],
144
+ });
145
+
146
+ const initial = await resolver.resolveAll();
147
+ const changed = await resolver.update(["clientFilesCourse/plots.js"]);
148
+ ```
149
+
150
+ `resolveAll()` returns a `Map` from QID to a `QuestionFileResolution` with
151
+ course-relative file paths, `question-local`, `template-local`, or
152
+ `course-level` provenance, and diagnostics for dependencies that cannot be
153
+ resolved statically. `update()` maintains the dependency graph and returns
154
+ only questions affected by the supplied course-relative paths. The resolver
155
+ does not watch the filesystem; callers decide how and when to batch changes.
@@ -0,0 +1,46 @@
1
+ import { Diagnostic } from "./diagnostic";
2
+ import { PlVersion } from "./plVersion";
3
+ import { RuleId } from "./rules/registry";
4
+ /** Looked up in the course root, next to infoCourse.json. */
5
+ export declare const configFileName = ".pl-navigator.jsonc";
6
+ type RuleSetting = "off" | "warning" | "error";
7
+ /** A glob and the directory it is relative to. */
8
+ interface Exclude {
9
+ base: string;
10
+ glob: string;
11
+ }
12
+ export interface NavigatorConfig {
13
+ plVersion: PlVersion;
14
+ /** Absolute. */
15
+ schemaCacheDir: string;
16
+ rules: Partial<Record<RuleId, RuleSetting>>;
17
+ /** Files never checked. Every source adds to this rather than replacing it. */
18
+ excludes: Exclude[];
19
+ }
20
+ /** Whether `file` matches one of the config's `excludes`. */
21
+ export declare function isExcluded(config: NavigatorConfig, file: string): boolean;
22
+ /** Same keys as the config file; from CLI flags or VS Code settings. */
23
+ export type ConfigOverrides = Partial<Record<keyof NavigatorConfig, unknown>>;
24
+ /** A config problem, located in `file` when it came from one. */
25
+ export type ConfigProblem = Diagnostic & {
26
+ file?: string;
27
+ };
28
+ export interface LoadedConfig {
29
+ config: NavigatorConfig;
30
+ /** The config file that was read, if any. */
31
+ file?: string;
32
+ problems: ConfigProblem[];
33
+ }
34
+ /**
35
+ * Reads `<courseRoot>/.pl-navigator.jsonc` (or `configPath`) and applies
36
+ * `overrides` on top. Invalid values are reported and replaced by defaults;
37
+ * this never throws.
38
+ */
39
+ export declare function loadConfig(courseRoot: string | null, options?: {
40
+ configPath?: string;
41
+ overrides?: ConfigOverrides;
42
+ /** What relative paths in `overrides` are relative to (default: cwd). */
43
+ overridesBaseDir?: string;
44
+ defaults?: Partial<NavigatorConfig>;
45
+ }): LoadedConfig;
46
+ export {};
@@ -0,0 +1,7 @@
1
+ import { CourseId } from "../common";
2
+ /**
3
+ * Walks up from `filePath` to the nearest directory containing
4
+ * `infoCourse.json`. Returns that directory (the course id), or null.
5
+ * Pass the same `cache` across calls to look up each directory only once.
6
+ */
7
+ export declare function findCourseRoot(filePath: string, cache?: Map<string, CourseId | null>): CourseId | null;
@@ -0,0 +1,14 @@
1
+ /** Globs for each kind of PrairieLearn JSON file, relative to the course root. */
2
+ export declare const courseFiles: {
3
+ readonly course: "infoCourse.json";
4
+ readonly courseInstance: "courseInstances/**/infoCourseInstance.json";
5
+ readonly assessment: "courseInstances/**/assessments/**/infoAssessment.json";
6
+ readonly question: "questions/**/info.json";
7
+ readonly element: "elements/*/info.json";
8
+ readonly elementExtension: "elementExtensions/*/*/info.json";
9
+ };
10
+ export declare const questionHtml = "questions/**/question.html";
11
+ /** Matches a posix path relative to the course root against a glob. */
12
+ export declare function matchesGlob(relPath: string, glob: string): boolean;
13
+ /** `filePath` relative to `courseRoot`, with forward slashes. */
14
+ export declare function courseRelativePath(courseRoot: string, filePath: string): string;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Editor-agnostic diagnostic shared by the VS Code extension and the CLI.
3
+ * Ranges are character offsets into the checked text; adapters convert them
4
+ * to their own position types.
5
+ */
6
+ type Severity = "error" | "warning";
7
+ interface RelatedInfo {
8
+ path: string;
9
+ message: string;
10
+ }
11
+ export interface Diagnostic {
12
+ startOffset: number;
13
+ endOffset: number;
14
+ message: string;
15
+ severity: Severity;
16
+ code?: string;
17
+ related?: RelatedInfo[];
18
+ }
19
+ /** 1-based line/column for a character offset. */
20
+ export declare function offsetToPosition(text: string, offset: number): {
21
+ line: number;
22
+ column: number;
23
+ };
24
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A minimal scanner for PrairieLearn element tags in question.html. It finds
3
+ * opening tags and their attributes with offsets; it is not a full HTML
4
+ * parser, but it skips comments and handles quoted values containing ">".
5
+ */
6
+ interface HtmlAttribute {
7
+ value: string;
8
+ /** Offsets of the value, excluding quotes. */
9
+ startOffset: number;
10
+ endOffset: number;
11
+ }
12
+ export interface HtmlElement {
13
+ startOffset: number;
14
+ endOffset: number;
15
+ /** Lowercased attribute names. */
16
+ attributes: Map<string, HtmlAttribute>;
17
+ }
18
+ /** Opening tags of `tagName` (case-insensitive), in document order. */
19
+ export declare function findElements(text: string, tagName: string): HtmlElement[];
20
+ export {};
@@ -0,0 +1,32 @@
1
+ import { JSONPath, Node } from "jsonc-parser";
2
+ /** A parsed JSON-with-comments document that keeps offsets for every node. */
3
+ export interface JsonDoc {
4
+ /** Undefined when the text has no parseable root value. */
5
+ value: unknown;
6
+ tree: Node | undefined;
7
+ errors: {
8
+ offset: number;
9
+ length: number;
10
+ message: string;
11
+ }[];
12
+ }
13
+ export declare function parseJsonDoc(text: string): JsonDoc;
14
+ /**
15
+ * Syntax errors under strict JSON, as PrairieLearn parses course files
16
+ * (JSON.parse): comments and trailing commas are errors.
17
+ */
18
+ export declare function strictJsonErrors(text: string): JsonDoc["errors"];
19
+ /** Parses JSON with comments, returning undefined if it has syntax errors. */
20
+ export declare function parseJsonc(text: string): unknown;
21
+ /**
22
+ * Offsets of the value at `path` (or of its property name, with `key`),
23
+ * falling back to its nearest existing ancestor, then to the document start.
24
+ */
25
+ export declare function rangeOf(doc: JsonDoc, path: JSONPath, options?: {
26
+ key?: boolean;
27
+ }): {
28
+ startOffset: number;
29
+ endOffset: number;
30
+ };
31
+ /** Converts a JSON pointer ("/a/0/b") into a path (["a", 0, "b"]). */
32
+ export declare function pointerToPath(pointer: string): JSONPath;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PrairieLearn has no semver releases: it deploys continuously from `master`.
3
+ * A PL version is therefore identified by a date (the state of `master` at the
4
+ * end of that UTC day), a full commit sha, or "latest".
5
+ */
6
+ export type IsoDate = string;
7
+ export type PlVersion = "latest" | IsoDate | string;
8
+ /**
9
+ * A space-separated conjunction of date comparisons, or "*".
10
+ * e.g. ">=2023-05-01 <2025-04-17"
11
+ */
12
+ export type VersionRange = string;
13
+ export declare function isCommitSha(s: string): boolean;
14
+ /** Returns an error message if `s` is not a valid PL version. */
15
+ export declare function validatePlVersion(s: unknown): string | undefined;
16
+ export declare function today(): IsoDate;
17
+ /**
18
+ * The date used to select version-ranged rules. A sha's commit date is not
19
+ * known offline, so a pinned sha selects rules as if it were current.
20
+ */
21
+ export declare function versionDate(version: PlVersion): IsoDate;
22
+ /** Whether `date` satisfies every comparator in `range`. */
23
+ export declare function matchesRange(range: VersionRange, date: IsoDate): boolean;
@@ -0,0 +1,30 @@
1
+ export type QuestionFileProvenance = "question-local" | "template-local" | "course-level";
2
+ export interface ResolvedQuestionFile {
3
+ /** Course-relative path with forward slashes. */
4
+ path: string;
5
+ provenance: QuestionFileProvenance;
6
+ }
7
+ export interface QuestionFileResolution {
8
+ files: ResolvedQuestionFile[];
9
+ /** False when at least one dependency could not be resolved statically. */
10
+ complete: boolean;
11
+ diagnostics: string[];
12
+ }
13
+ export interface QuestionFileDescriptor {
14
+ qid: string;
15
+ /** Course-relative question directory with forward slashes. */
16
+ directory: string;
17
+ }
18
+ export interface QuestionFileResolverOptions {
19
+ courseRoot: string;
20
+ questions: readonly QuestionFileDescriptor[];
21
+ }
22
+ export interface QuestionFileResolutionUpdate {
23
+ affectedQids: string[];
24
+ resolutions: Map<string, QuestionFileResolution>;
25
+ }
26
+ export interface QuestionFileResolver {
27
+ resolveAll(): Promise<Map<string, QuestionFileResolution>>;
28
+ update(changedPaths: readonly string[], questions?: readonly QuestionFileDescriptor[]): Promise<QuestionFileResolutionUpdate>;
29
+ }
30
+ export declare function createQuestionFileResolver(options: QuestionFileResolverOptions): QuestionFileResolver;
@@ -0,0 +1,10 @@
1
+ import { QuestionId } from "../common";
2
+ export type QuestionPaths = {
3
+ dir: string;
4
+ infoJson: string;
5
+ questionHtml: string;
6
+ serverPy: string;
7
+ };
8
+ export declare function questionFilePathsFromId(questionId: QuestionId): QuestionPaths & {
9
+ strict(): Partial<QuestionPaths>;
10
+ };
@@ -0,0 +1,58 @@
1
+ import { IsoDate } from "./plVersion";
2
+ import { FileRef } from "./references/extract";
3
+ /** One place a course file references a target. */
4
+ export interface Use {
5
+ /** The file holding the reference. */
6
+ file: string;
7
+ ref: FileRef;
8
+ /** 1-based, like offsetToPosition. */
9
+ start: {
10
+ line: number;
11
+ column: number;
12
+ };
13
+ end: {
14
+ line: number;
15
+ column: number;
16
+ };
17
+ }
18
+ /**
19
+ * Reverse index of every file reference in one course: which files
20
+ * reference a given target. Updated one file at a time.
21
+ */
22
+ export declare class ReferenceIndex {
23
+ readonly courseRoot: string;
24
+ private plDate;
25
+ private usesByFile;
26
+ private filesByTarget;
27
+ /** How many targets lie under each directory, to answer directory events quickly. */
28
+ private targetsUnder;
29
+ constructor(courseRoot: string, plDate: IsoDate);
30
+ /** Whether `file` can hold references at all. */
31
+ holdsReferences(file: string): boolean;
32
+ /** Re-reads the references in `file` from `text`. */
33
+ update(file: string, text: string): void;
34
+ remove(file: string): void;
35
+ /** Every file holding references. */
36
+ files(): string[];
37
+ /** References made by `file`. */
38
+ usesIn(file: string): Use[];
39
+ /**
40
+ * References to `target`. A reference to a directory (a question, or a
41
+ * serverFilesCourse folder) also counts as a use of every file inside it.
42
+ */
43
+ usesOf(target: string): Use[];
44
+ private countUnder;
45
+ /**
46
+ * Files whose references can be affected by `changed` (a created, edited,
47
+ * or deleted path): references to it, to a directory containing it, or,
48
+ * when it is a directory, to anything inside it.
49
+ */
50
+ filesAffectedBy(changed: string): string[];
51
+ }
52
+ /** Indexes every reference-holding file in a course from disk. */
53
+ export declare function indexCourse(courseRoot: string, plDate: IsoDate): ReferenceIndex;
54
+ /**
55
+ * Like indexCourse, but reads files asynchronously in batches, yielding
56
+ * between them, so a large course does not block an editor.
57
+ */
58
+ export declare function indexCourseAsync(courseRoot: string, plDate: IsoDate, batchSize?: number): Promise<ReferenceIndex>;
@@ -0,0 +1,13 @@
1
+ /** Directories a reference can be relative to. */
2
+ export type BaseName = "fileDir" | "clientFilesQuestion" | "clientFilesCourse" | "serverFilesCourse" | "courseExtensions" | "questions";
3
+ export interface RefContext {
4
+ courseRoot: string;
5
+ /** Directory containing the file being read. */
6
+ fileDir: string;
7
+ }
8
+ export declare function baseDir(base: BaseName, ctx: RefContext): string;
9
+ /**
10
+ * `base/value`, or null if that escapes `base`. Naming `base` itself counts
11
+ * as escaping unless `allowBase` (a directory may be the base; a file not).
12
+ */
13
+ export declare function inside(base: string, value: string, allowBase?: boolean): string | null;
@@ -0,0 +1,34 @@
1
+ import { JsonDoc } from "../json";
2
+ import { IsoDate } from "../plVersion";
3
+ import { RefContext } from "./bases";
4
+ import { RefSpec } from "./specs";
5
+ export interface FileRef {
6
+ spec: RefSpec;
7
+ value: string;
8
+ /** Offsets of the value, excluding quotes. */
9
+ startOffset: number;
10
+ endOffset: number;
11
+ /**
12
+ * Canonical absolute path the value names: the file, or for a question
13
+ * its directory. Null when the reference is invalid (see `problem`).
14
+ */
15
+ target: string | null;
16
+ /** Why the reference is invalid regardless of the file system. */
17
+ problem?: string;
18
+ }
19
+ /** The file references are read from; `doc` is only read for JSON files. */
20
+ interface SourceFile {
21
+ text: string;
22
+ readonly doc: JsonDoc;
23
+ }
24
+ /** A SourceFile that parses its JSON on first use. */
25
+ export declare function sourceOf(text: string): SourceFile;
26
+ /** Whether any reference spec reads this course-relative path. */
27
+ export declare function holdsReferences(relPath: string): boolean;
28
+ /** All file references in a course file, in document order. */
29
+ export declare function findFileRefs(source: SourceFile, relPath: string, plDate: IsoDate, ctx: RefContext, filter?: (spec: RefSpec) => boolean): FileRef[];
30
+ /** The file reference whose value contains `offset`, if any. */
31
+ export declare function fileRefAt(refs: FileRef[], offset: number): FileRef | undefined;
32
+ /** The file to open for a reference: a question opens its html, else its info.json. */
33
+ export declare function openTarget(ref: FileRef): string | null;
34
+ export {};
@@ -0,0 +1,272 @@
1
+ import { VersionRange } from "../plVersion";
2
+ import { BaseName } from "./bases";
3
+ /**
4
+ * Every place a course file names another file that must exist. Each spec
5
+ * drives, from this one table:
6
+ * - an existence rule (`rule`, registered in rules/registry.ts),
7
+ * - editor jump-to-file, and
8
+ * - "uses" (the reverse index in ../referenceIndex.ts).
9
+ *
10
+ * Deliberately not listed:
11
+ * - nodeModules* / core* dependencies: files on the PrairieLearn server.
12
+ * - workspaceOptions.gradedFiles, and `file-name` on pl-image-capture,
13
+ * pl-file-editor, pl-file-upload, pl-rich-text-editor: student submissions.
14
+ * - type="dynamic" pl-figure / pl-file-download: generated by server.py.
15
+ */
16
+ /** Matches a property key or array index; "*" matches any key or index. */
17
+ export type PathSegment = string | RegExp;
18
+ /**
19
+ * How an element's `directory` attribute picks the base directory.
20
+ * `named` maps attribute values to bases; with `relative`, any other value
21
+ * is a path relative to the question directory.
22
+ */
23
+ interface DirectoryAttr {
24
+ default: string;
25
+ named: Readonly<Record<string, BaseName>>;
26
+ relative?: true;
27
+ }
28
+ interface JsonSource {
29
+ json: readonly PathSegment[];
30
+ base: BaseName;
31
+ }
32
+ interface HtmlSource {
33
+ tag: string;
34
+ attr: string;
35
+ /** Without one, values are relative to `base`. */
36
+ directory?: DirectoryAttr;
37
+ base?: BaseName;
38
+ /** Skip elements with type="dynamic" (generated files). */
39
+ staticOnly?: true;
40
+ }
41
+ /**
42
+ * What must exist: a file, a directory, either, or a question (a directory
43
+ * under questions/ with info.json and, usually, question.html).
44
+ */
45
+ export type TargetKind = "file" | "fileOrDir" | "question";
46
+ interface BaseSpec<Rule extends string> {
47
+ /** Stable id, used in tests and for debugging. */
48
+ id: string;
49
+ range: VersionRange;
50
+ /** Glob, relative to the course root, of files holding the reference. */
51
+ files: string;
52
+ rule: Rule;
53
+ target: TargetKind;
54
+ /** Severity of a missing target; defaults to error. */
55
+ severity?: "warning";
56
+ }
57
+ export type RefSpec<Rule extends string = string> = BaseSpec<Rule> & ({
58
+ source: "json";
59
+ json: JsonSource;
60
+ } | {
61
+ source: "html";
62
+ html: HtmlSource;
63
+ });
64
+ export declare const refSpecs: readonly [{
65
+ readonly source: "json";
66
+ readonly json: {
67
+ readonly json: readonly PathSegment[];
68
+ readonly base: BaseName;
69
+ };
70
+ readonly id: "question.dependencies.clientFilesCourse";
71
+ readonly range: "*";
72
+ readonly files: "questions/**/info.json";
73
+ readonly rule: "client-files-course-exist";
74
+ readonly target: "file";
75
+ }, {
76
+ readonly source: "json";
77
+ readonly json: {
78
+ readonly json: readonly PathSegment[];
79
+ readonly base: BaseName;
80
+ };
81
+ readonly id: "question.dependencies.clientFilesQuestion";
82
+ readonly range: "*";
83
+ readonly files: "questions/**/info.json";
84
+ readonly rule: "client-files-question-exist";
85
+ readonly target: "file";
86
+ }, {
87
+ readonly source: "json";
88
+ readonly json: {
89
+ readonly json: readonly PathSegment[];
90
+ readonly base: BaseName;
91
+ };
92
+ readonly id: "question.clientFiles";
93
+ readonly range: "*";
94
+ readonly files: "questions/**/info.json";
95
+ readonly rule: "client-files-question-exist";
96
+ readonly target: "file";
97
+ readonly severity: "warning";
98
+ }, {
99
+ readonly source: "json";
100
+ readonly json: {
101
+ readonly json: readonly PathSegment[];
102
+ readonly base: BaseName;
103
+ };
104
+ readonly id: "question.externalGradingOptions.serverFilesCourse";
105
+ readonly range: "*";
106
+ readonly files: "questions/**/info.json";
107
+ readonly rule: "server-files-course-exist";
108
+ readonly target: "fileOrDir";
109
+ }, {
110
+ readonly source: "json";
111
+ readonly json: {
112
+ readonly json: readonly PathSegment[];
113
+ readonly base: BaseName;
114
+ };
115
+ readonly id: "question.template";
116
+ readonly range: "*";
117
+ readonly files: "questions/**/info.json";
118
+ readonly rule: "question-exists";
119
+ readonly target: "question";
120
+ }, ...({
121
+ readonly source: "json";
122
+ readonly json: {
123
+ readonly json: readonly PathSegment[];
124
+ readonly base: BaseName;
125
+ };
126
+ readonly id: "elements/*/info.json.controller" | "elementExtensions/*/*/info.json.controller";
127
+ readonly range: "*";
128
+ readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
129
+ readonly rule: "element-files-exist";
130
+ readonly target: "file";
131
+ } | {
132
+ readonly source: "json";
133
+ readonly json: {
134
+ readonly json: readonly PathSegment[];
135
+ readonly base: BaseName;
136
+ };
137
+ readonly id: "elements/*/info.json.dependencies.element" | "elementExtensions/*/*/info.json.dependencies.element";
138
+ readonly range: "*";
139
+ readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
140
+ readonly rule: "element-files-exist";
141
+ readonly target: "file";
142
+ } | {
143
+ readonly source: "json";
144
+ readonly json: {
145
+ readonly json: readonly PathSegment[];
146
+ readonly base: BaseName;
147
+ };
148
+ readonly id: "elements/*/info.json.dynamicDependencies.element" | "elementExtensions/*/*/info.json.dynamicDependencies.element";
149
+ readonly range: "*";
150
+ readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
151
+ readonly rule: "element-files-exist";
152
+ readonly target: "file";
153
+ } | {
154
+ readonly source: "json";
155
+ readonly json: {
156
+ readonly json: readonly PathSegment[];
157
+ readonly base: BaseName;
158
+ };
159
+ readonly id: "elements/*/info.json.dependencies.clientFilesCourse" | "elementExtensions/*/*/info.json.dependencies.clientFilesCourse";
160
+ readonly range: "*";
161
+ readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
162
+ readonly rule: "client-files-course-exist";
163
+ readonly target: "file";
164
+ } | {
165
+ readonly source: "json";
166
+ readonly json: {
167
+ readonly json: readonly PathSegment[];
168
+ readonly base: BaseName;
169
+ };
170
+ readonly id: "elements/*/info.json.dynamicDependencies.clientFilesCourse" | "elementExtensions/*/*/info.json.dynamicDependencies.clientFilesCourse";
171
+ readonly range: "*";
172
+ readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
173
+ readonly rule: "client-files-course-exist";
174
+ readonly target: "file";
175
+ })[], {
176
+ readonly source: "json";
177
+ readonly json: {
178
+ readonly json: readonly PathSegment[];
179
+ readonly base: BaseName;
180
+ };
181
+ readonly id: "assessment.question";
182
+ readonly range: "*";
183
+ readonly files: "courseInstances/**/assessments/**/infoAssessment.json";
184
+ readonly rule: "question-exists";
185
+ readonly target: "question";
186
+ }, {
187
+ readonly source: "json";
188
+ readonly json: {
189
+ readonly json: readonly PathSegment[];
190
+ readonly base: BaseName;
191
+ };
192
+ readonly id: "assessment.alternative";
193
+ readonly range: "*";
194
+ readonly files: "courseInstances/**/assessments/**/infoAssessment.json";
195
+ readonly rule: "question-exists";
196
+ readonly target: "question";
197
+ }, {
198
+ readonly source: "html";
199
+ readonly html: HtmlSource;
200
+ readonly id: "pl-figure";
201
+ readonly range: "*";
202
+ readonly files: "questions/**/question.html";
203
+ readonly rule: "pl-figure-file-exist";
204
+ readonly target: "file";
205
+ }, {
206
+ readonly source: "html";
207
+ readonly html: HtmlSource;
208
+ readonly id: "pl-file-download";
209
+ readonly range: "*";
210
+ readonly files: "questions/**/question.html";
211
+ readonly rule: "pl-file-download-file-exist";
212
+ readonly target: "file";
213
+ }, {
214
+ readonly source: "html";
215
+ readonly html: HtmlSource;
216
+ readonly id: "pl-code";
217
+ readonly range: "*";
218
+ readonly files: "questions/**/question.html";
219
+ readonly rule: "pl-code-file-exist";
220
+ readonly target: "fileOrDir";
221
+ }, {
222
+ readonly source: "html";
223
+ readonly html: HtmlSource;
224
+ readonly id: "pl-file-editor";
225
+ readonly range: "*";
226
+ readonly files: "questions/**/question.html";
227
+ readonly rule: "pl-file-editor-file-exist";
228
+ readonly target: "file";
229
+ }, {
230
+ readonly source: "html";
231
+ readonly html: HtmlSource;
232
+ readonly id: "pl-graph";
233
+ readonly range: "*";
234
+ readonly files: "questions/**/question.html";
235
+ readonly rule: "pl-graph-file-exist";
236
+ readonly target: "file";
237
+ }, {
238
+ readonly source: "html";
239
+ readonly html: HtmlSource;
240
+ readonly id: "pl-rich-text-editor";
241
+ readonly range: "*";
242
+ readonly files: "questions/**/question.html";
243
+ readonly rule: "pl-rich-text-editor-file-exist";
244
+ readonly target: "file";
245
+ }, {
246
+ readonly source: "html";
247
+ readonly html: HtmlSource;
248
+ readonly id: "pl-excalidraw";
249
+ readonly range: "*";
250
+ readonly files: "questions/**/question.html";
251
+ readonly rule: "pl-excalidraw-file-exist";
252
+ readonly target: "file";
253
+ }, {
254
+ readonly source: "html";
255
+ readonly html: HtmlSource;
256
+ readonly id: "pl-xss-safe";
257
+ readonly range: "*";
258
+ readonly files: "questions/**/question.html";
259
+ readonly rule: "pl-xss-safe-file-exist";
260
+ readonly target: "fileOrDir";
261
+ }, {
262
+ readonly source: "html";
263
+ readonly html: HtmlSource;
264
+ readonly id: "pl-template";
265
+ readonly range: "*";
266
+ readonly files: "questions/**/question.html";
267
+ readonly rule: "pl-template-file-exist";
268
+ readonly target: "file";
269
+ }];
270
+ /** Ids of the existence rules generated from `refSpecs`. */
271
+ export type RefRuleId = (typeof refSpecs)[number]["rule"];
272
+ export {};
@@ -0,0 +1,14 @@
1
+ import { SchemaName } from "../schemas";
2
+ import { RuleImpl } from "./types";
3
+ /**
4
+ * Reports JSON syntax errors. PrairieLearn reads course JSON with JSON.parse,
5
+ * so comments and trailing commas fail its sync. (Other rules still read
6
+ * such files tolerantly.)
7
+ */
8
+ export declare const jsonSyntax: RuleImpl;
9
+ /** Validates the file against PrairieLearn's JSON schema for the PL version. */
10
+ export declare function schemaRule(name: SchemaName): RuleImpl;
11
+ /** Reports every reference governed by `rule` whose target is missing or invalid. */
12
+ export declare function referencesExist(rule: string): RuleImpl;
13
+ /** Warns on every occurrence of a question used more than once in a file. */
14
+ export declare const duplicateQuestionIds: RuleImpl;
@@ -0,0 +1,26 @@
1
+ import { RefRuleId } from "../references/specs";
2
+ import { RuleEntry } from "./types";
3
+ /**
4
+ * Rules that are not about file references, by id. Each entry is
5
+ * `[plVersionRange, courseRelativeGlob, impl]`, and for a given file the
6
+ * first entry whose range and glob both match is the one that runs.
7
+ *
8
+ * To change a rule for newer PL versions, add an entry with a narrower
9
+ * range above the existing one, e.g.
10
+ * [">=2026-01-01", courseFiles.question, newImpl],
11
+ * ["*", courseFiles.question, oldImpl],
12
+ */
13
+ declare const otherRules: {
14
+ readonly "json-syntax": (readonly ["*", "infoCourse.json" | "courseInstances/**/infoCourseInstance.json" | "courseInstances/**/assessments/**/infoAssessment.json" | "questions/**/info.json" | "elements/*/info.json" | "elementExtensions/*/*/info.json", import("./types").RuleImpl])[];
15
+ readonly schema: readonly [readonly ["*", "infoCourse.json", import("./types").RuleImpl], readonly ["*", "courseInstances/**/infoCourseInstance.json", import("./types").RuleImpl], readonly ["*", "courseInstances/**/assessments/**/infoAssessment.json", import("./types").RuleImpl], readonly ["*", "questions/**/info.json", import("./types").RuleImpl], readonly ["*", "elements/*/info.json", import("./types").RuleImpl], readonly ["*", "elementExtensions/*/*/info.json", import("./types").RuleImpl]];
16
+ readonly "duplicate-question-id": readonly [readonly ["*", "courseInstances/**/assessments/**/infoAssessment.json", import("./types").RuleImpl]];
17
+ };
18
+ /**
19
+ * Every validation rule, by id. Ids appear in diagnostic codes as
20
+ * `prairielearn-navigator/<id>` and are the keys of the `rules` setting.
21
+ */
22
+ export declare const rules: Record<RuleId, readonly RuleEntry[]>;
23
+ export type RuleId = keyof typeof otherRules | RefRuleId;
24
+ export declare function isRuleId(id: string): id is RuleId;
25
+ export declare function ruleDiagnosticCode(id: RuleId): string;
26
+ export {};
@@ -0,0 +1,18 @@
1
+ import { NavigatorConfig } from "../config";
2
+ import { Diagnostic } from "../diagnostic";
3
+ import { IsoDate } from "../plVersion";
4
+ import { SchemaStore } from "../schemas";
5
+ import { RuleId } from "./registry";
6
+ import { RuleImpl, RuleTable } from "./types";
7
+ /** The implementation of each rule that applies to `relPath` at `plDate`. */
8
+ export declare function selectRules<Id extends string = RuleId>(relPath: string, plDate: IsoDate, table?: RuleTable): [Id, RuleImpl][];
9
+ /** Globs, relative to a course root, of every file some rule applies to. */
10
+ export declare function checkableGlobs(): string[];
11
+ /** Whether any rule applies to this course-relative path, at any version. */
12
+ export declare function isCheckable(relPath: string): boolean;
13
+ /** Runs every applicable, enabled rule on one file of a course. */
14
+ export declare function runRules(filePath: string, text: string, options: {
15
+ courseRoot: string;
16
+ config: NavigatorConfig;
17
+ schemas: SchemaStore;
18
+ }): Promise<Diagnostic[]>;
@@ -0,0 +1,29 @@
1
+ import { Diagnostic } from "../diagnostic";
2
+ import { JsonDoc } from "../json";
3
+ import { IsoDate, PlVersion, VersionRange } from "../plVersion";
4
+ import { SchemaStore } from "../schemas";
5
+ /** Everything a rule may inspect about the file being checked. */
6
+ export interface RuleContext {
7
+ courseRoot: string;
8
+ filePath: string;
9
+ /** Posix path relative to the course root, as matched by rule globs. */
10
+ relPath: string;
11
+ text: string;
12
+ doc: JsonDoc;
13
+ plVersion: PlVersion;
14
+ /** The date `plVersion` stands for, used to select version-ranged rules. */
15
+ plDate: IsoDate;
16
+ schemas: SchemaStore;
17
+ }
18
+ export type RuleImpl = (ctx: RuleContext) => Diagnostic[] | Promise<Diagnostic[]>;
19
+ /**
20
+ * One implementation of a rule for PL versions in `range` and files matching
21
+ * `files` (a glob relative to the course root).
22
+ */
23
+ export type RuleEntry = readonly [range: VersionRange, files: string, impl: RuleImpl];
24
+ /**
25
+ * Rule id -> ordered entries. For each rule, the first entry whose range and
26
+ * glob both match the file is the one that runs; put newer-version entries
27
+ * above older ones.
28
+ */
29
+ export type RuleTable = Record<string, readonly RuleEntry[]>;
@@ -0,0 +1,51 @@
1
+ import { ValidateFunction } from "ajv";
2
+ import { PlVersion } from "./plVersion";
3
+ export type SchemaName = "infoCourse" | "infoCourseInstance" | "infoAssessment" | "infoQuestion" | "infoElementCourse" | "infoElementExtension";
4
+ export interface LoadedSchema {
5
+ validate: ValidateFunction;
6
+ label: string;
7
+ }
8
+ type Fetch = (url: string, init?: {
9
+ headers?: Record<string, string>;
10
+ }) => Promise<{
11
+ ok: boolean;
12
+ status: number;
13
+ text(): Promise<string>;
14
+ json(): Promise<unknown>;
15
+ }>;
16
+ export interface SchemaStoreOptions {
17
+ cacheDir: string;
18
+ /** Injected for tests; defaults to the global fetch. */
19
+ fetch?: Fetch;
20
+ /** Raises the GitHub API rate limit for date versions. */
21
+ githubToken?: string;
22
+ }
23
+ /**
24
+ * Fetches PrairieLearn's JSON schemas for a given PL version and caches them
25
+ * on disk as `<cacheDir>/<sha | master-YYYY-MM-DD>/<name>.json`.
26
+ *
27
+ * Failures never throw: `get` resolves to undefined and the reason is
28
+ * recorded once in `problems`, so callers can report it a single time.
29
+ */
30
+ export declare class SchemaStore {
31
+ private readonly options;
32
+ readonly problems: Set<string>;
33
+ private readonly fetch;
34
+ private readonly loading;
35
+ private readonly resolving;
36
+ constructor(options: SchemaStoreOptions);
37
+ get cacheDir(): string;
38
+ get(name: SchemaName, version: PlVersion): Promise<LoadedSchema | undefined>;
39
+ private load;
40
+ private resolve;
41
+ private resolveUncached;
42
+ /** Last commit on master at or before the end of `date` (UTC). */
43
+ private lookupSha;
44
+ private schemaText;
45
+ private writeCache;
46
+ private pruneStaleLatest;
47
+ private get refsFile();
48
+ private readRefs;
49
+ private writeRefs;
50
+ }
51
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{createRequire as e}from"node:module";const t=e(import.meta.url);var n={211(e,t,n){function r(e){return 32===e||9===e}function s(e){return 10===e||13===e}function o(e){return e>=48&&e<=57}var i;n.d(t,{findNodeAtLocation:()=>g,parse:()=>d,parseTree:()=>h,printParseErrorCode:()=>y}),function(e){e[e.lineFeed=10]="lineFeed",e[e.carriageReturn=13]="carriageReturn",e[e.space=32]="space",e[e._0=48]="_0",e[e._1=49]="_1",e[e._2=50]="_2",e[e._3=51]="_3",e[e._4=52]="_4",e[e._5=53]="_5",e[e._6=54]="_6",e[e._7=55]="_7",e[e._8=56]="_8",e[e._9=57]="_9",e[e.a=97]="a",e[e.b=98]="b",e[e.c=99]="c",e[e.d=100]="d",e[e.e=101]="e",e[e.f=102]="f",e[e.g=103]="g",e[e.h=104]="h",e[e.i=105]="i",e[e.j=106]="j",e[e.k=107]="k",e[e.l=108]="l",e[e.m=109]="m",e[e.n=110]="n",e[e.o=111]="o",e[e.p=112]="p",e[e.q=113]="q",e[e.r=114]="r",e[e.s=115]="s",e[e.t=116]="t",e[e.u=117]="u",e[e.v=118]="v",e[e.w=119]="w",e[e.x=120]="x",e[e.y=121]="y",e[e.z=122]="z",e[e.A=65]="A",e[e.B=66]="B",e[e.C=67]="C",e[e.D=68]="D",e[e.E=69]="E",e[e.F=70]="F",e[e.G=71]="G",e[e.H=72]="H",e[e.I=73]="I",e[e.J=74]="J",e[e.K=75]="K",e[e.L=76]="L",e[e.M=77]="M",e[e.N=78]="N",e[e.O=79]="O",e[e.P=80]="P",e[e.Q=81]="Q",e[e.R=82]="R",e[e.S=83]="S",e[e.T=84]="T",e[e.U=85]="U",e[e.V=86]="V",e[e.W=87]="W",e[e.X=88]="X",e[e.Y=89]="Y",e[e.Z=90]="Z",e[e.asterisk=42]="asterisk",e[e.backslash=92]="backslash",e[e.closeBrace=125]="closeBrace",e[e.closeBracket=93]="closeBracket",e[e.colon=58]="colon",e[e.comma=44]="comma",e[e.dot=46]="dot",e[e.doubleQuote=34]="doubleQuote",e[e.minus=45]="minus",e[e.openBrace=123]="openBrace",e[e.openBracket=91]="openBracket",e[e.plus=43]="plus",e[e.slash=47]="slash",e[e.formFeed=12]="formFeed",e[e.tab=9]="tab"}(i||(i={})),new Array(20).fill(0).map((e,t)=>" ".repeat(t));const a=200;var l,u,c;function f(e,t,n=l.DEFAULT){const i=function(e,t=!1){const n=e.length;let i=0,a="",l=0,u=16,c=0,f=0,p=0,d=0,h=0;function g(t,n){let r=0,s=0;for(;r<t||!n;){let t=e.charCodeAt(i);if(t>=48&&t<=57)s=16*s+t-48;else if(t>=65&&t<=70)s=16*s+t-65+10;else{if(!(t>=97&&t<=102))break;s=16*s+t-97+10}i++,r++}return r<t&&(s=-1),s}function m(){if(a="",h=0,l=i,f=c,d=p,i>=n)return l=n,u=17;let t=e.charCodeAt(i);if(r(t)){do{i++,a+=String.fromCharCode(t),t=e.charCodeAt(i)}while(r(t));return u=15}if(s(t))return i++,a+=String.fromCharCode(t),13===t&&10===e.charCodeAt(i)&&(i++,a+="\n"),c++,p=i,u=14;switch(t){case 123:return i++,u=1;case 125:return i++,u=2;case 91:return i++,u=3;case 93:return i++,u=4;case 58:return i++,u=6;case 44:return i++,u=5;case 34:return i++,a=function(){let t="",r=i;for(;;){if(i>=n){t+=e.substring(r,i),h=2;break}const o=e.charCodeAt(i);if(34===o){t+=e.substring(r,i),i++;break}if(92!==o){if(o>=0&&o<=31){if(s(o)){t+=e.substring(r,i),h=2;break}h=6}i++}else{if(t+=e.substring(r,i),i++,i>=n){h=2;break}switch(e.charCodeAt(i++)){case 34:t+='"';break;case 92:t+="\\";break;case 47:t+="/";break;case 98:t+="\b";break;case 102:t+="\f";break;case 110:t+="\n";break;case 114:t+="\r";break;case 116:t+="\t";break;case 117:const e=g(4,!0);e>=0?t+=String.fromCharCode(e):h=4;break;default:h=5}r=i}}return t}(),u=10;case 47:const r=i-1;if(47===e.charCodeAt(i+1)){for(i+=2;i<n&&!s(e.charCodeAt(i));)i++;return a=e.substring(r,i),u=12}if(42===e.charCodeAt(i+1)){i+=2;const t=n-1;let o=!1;for(;i<t;){const t=e.charCodeAt(i);if(42===t&&47===e.charCodeAt(i+1)){i+=2,o=!0;break}i++,s(t)&&(13===t&&10===e.charCodeAt(i)&&i++,c++,p=i)}return o||(i++,h=1),a=e.substring(r,i),u=13}return a+=String.fromCharCode(t),i++,u=16;case 45:if(a+=String.fromCharCode(t),i++,i===n||!o(e.charCodeAt(i)))return u=16;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return a+=function(){let t=i;if(48===e.charCodeAt(i))i++;else for(i++;i<e.length&&o(e.charCodeAt(i));)i++;if(i<e.length&&46===e.charCodeAt(i)){if(i++,!(i<e.length&&o(e.charCodeAt(i))))return h=3,e.substring(t,i);for(i++;i<e.length&&o(e.charCodeAt(i));)i++}let n=i;if(i<e.length&&(69===e.charCodeAt(i)||101===e.charCodeAt(i)))if(i++,(i<e.length&&43===e.charCodeAt(i)||45===e.charCodeAt(i))&&i++,i<e.length&&o(e.charCodeAt(i))){for(i++;i<e.length&&o(e.charCodeAt(i));)i++;n=i}else h=3;return e.substring(t,n)}(),u=11;default:for(;i<n&&y(t);)i++,t=e.charCodeAt(i);if(l!==i){switch(a=e.substring(l,i),a){case"true":return u=8;case"false":return u=9;case"null":return u=7}return u=16}return a+=String.fromCharCode(t),i++,u=16}}function y(e){if(r(e)||s(e))return!1;switch(e){case 125:case 93:case 123:case 91:case 34:case 58:case 44:case 47:return!1}return!0}return{setPosition:function(e){i=e,a="",l=0,u=16,h=0},getPosition:()=>i,scan:t?function(){let e;do{e=m()}while(e>=12&&e<=15);return e}:m,getToken:()=>u,getTokenValue:()=>a,getTokenOffset:()=>l,getTokenLength:()=>i-l,getTokenStartLine:()=>f,getTokenStartCharacter:()=>l-d,getTokenError:()=>h}}(e,!1),a=[];let u=0;function c(e){return e?t=>0===u&&e(t,i.getTokenOffset(),i.getTokenLength(),i.getTokenStartLine(),i.getTokenStartCharacter()):()=>!0}function f(e){return e?t=>0===u&&e(t,i.getTokenOffset(),i.getTokenLength(),i.getTokenStartLine(),i.getTokenStartCharacter(),()=>a.slice()):()=>!0}function p(e){return e?()=>{u>0?u++:!1===e(i.getTokenOffset(),i.getTokenLength(),i.getTokenStartLine(),i.getTokenStartCharacter(),()=>a.slice())&&(u=1)}:()=>!0}function d(e){return e?()=>{u>0&&u--,0===u&&e(i.getTokenOffset(),i.getTokenLength(),i.getTokenStartLine(),i.getTokenStartCharacter())}:()=>!0}const h=p(t.onObjectBegin),g=f(t.onObjectProperty),m=d(t.onObjectEnd),y=p(t.onArrayBegin),v=d(t.onArrayEnd),b=f(t.onLiteralValue),_=c(t.onSeparator),O=(A=t.onComment)?()=>0===u&&A(i.getTokenOffset(),i.getTokenLength(),i.getTokenStartLine(),i.getTokenStartCharacter()):()=>!0,C=c(t.onError);var A;const x=n&&n.disallowComments,E=n&&n.allowTrailingComma;function R(){for(;;){const e=i.scan();switch(i.getTokenError()){case 4:k(14);break;case 5:k(15);break;case 3:k(13);break;case 1:x||k(11);break;case 2:k(12);break;case 6:k(16)}switch(e){case 12:case 13:x?k(10):O();break;case 16:k(1);break;case 15:case 14:break;default:return e}}}function k(e,t=[],n=[]){if(C(e),t.length+n.length>0){let e=i.getToken();for(;17!==e;){if(-1!==t.indexOf(e)){R();break}if(-1!==n.indexOf(e))break;e=R()}}}function $(e){const t=i.getTokenValue();return e?b(t):(g(t),a.push(t)),R(),!0}function S(){return 10!==i.getToken()?(k(3,[],[2,5]),!1):($(!1),6===i.getToken()?(_(":"),R(),w()||k(4,[],[2,5])):k(5,[],[2,5]),a.pop(),!0)}function w(){switch(i.getToken()){case 3:return function(){y(),R();let e=!0,t=!1;for(;4!==i.getToken()&&17!==i.getToken();){if(5===i.getToken()){if(t||k(4,[],[]),_(","),R(),4===i.getToken()&&E)break}else t&&k(6,[],[]);e?(a.push(0),e=!1):a[a.length-1]++,w()||k(4,[],[4,5]),t=!0}return v(),e||a.pop(),4!==i.getToken()?k(8,[4],[]):R(),!0}();case 1:return function(){h(),R();let e=!1;for(;2!==i.getToken()&&17!==i.getToken();){if(5===i.getToken()){if(e||k(4,[],[]),_(","),R(),2===i.getToken()&&E)break}else e&&k(6,[],[]);S()||k(4,[],[2,5]),e=!0}return m(),2!==i.getToken()?k(7,[2],[]):R(),!0}();case 10:return $(!0);default:return function(){switch(i.getToken()){case 11:const e=i.getTokenValue();let t=Number(e);isNaN(t)&&(k(2),t=0),b(t);break;case 7:b(null);break;case 8:b(!0);break;case 9:b(!1);break;default:return!1}return R(),!0}()}}return R(),17===i.getToken()?!!n.allowEmptyContent||(k(4,[],[]),!1):w()?(17!==i.getToken()&&k(9,[],[]),!0):(k(4,[],[]),!1)}function p(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"object":return e?Array.isArray(e)?"array":"object":"null";default:return"null"}}new Array(a).fill(0).map((e,t)=>"\n"+" ".repeat(t)),new Array(a).fill(0).map((e,t)=>"\r"+" ".repeat(t)),new Array(a).fill(0).map((e,t)=>"\r\n"+" ".repeat(t)),new Array(a).fill(0).map((e,t)=>"\n"+"\t".repeat(t)),new Array(a).fill(0).map((e,t)=>"\r"+"\t".repeat(t)),new Array(a).fill(0).map((e,t)=>"\r\n"+"\t".repeat(t)),function(e){e.DEFAULT={allowTrailingComma:!1}}(l||(l={})),function(e){e[e.None=0]="None",e[e.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=2]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",e[e.InvalidUnicode=4]="InvalidUnicode",e[e.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",e[e.InvalidCharacter=6]="InvalidCharacter"}(u||(u={})),function(e){e[e.OpenBraceToken=1]="OpenBraceToken",e[e.CloseBraceToken=2]="CloseBraceToken",e[e.OpenBracketToken=3]="OpenBracketToken",e[e.CloseBracketToken=4]="CloseBracketToken",e[e.CommaToken=5]="CommaToken",e[e.ColonToken=6]="ColonToken",e[e.NullKeyword=7]="NullKeyword",e[e.TrueKeyword=8]="TrueKeyword",e[e.FalseKeyword=9]="FalseKeyword",e[e.StringLiteral=10]="StringLiteral",e[e.NumericLiteral=11]="NumericLiteral",e[e.LineCommentTrivia=12]="LineCommentTrivia",e[e.BlockCommentTrivia=13]="BlockCommentTrivia",e[e.LineBreakTrivia=14]="LineBreakTrivia",e[e.Trivia=15]="Trivia",e[e.Unknown=16]="Unknown",e[e.EOF=17]="EOF"}(c||(c={}));const d=function(e,t=[],n=l.DEFAULT){let r=null,s=[];const o=[];function i(e){Array.isArray(s)?s.push(e):null!==r&&(s[r]=e)}return f(e,{onObjectBegin:()=>{const e={};i(e),o.push(s),s=e,r=null},onObjectProperty:e=>{r=e},onObjectEnd:()=>{s=o.pop()},onArrayBegin:()=>{const e=[];i(e),o.push(s),s=e,r=null},onArrayEnd:()=>{s=o.pop()},onLiteralValue:i,onError:(e,n,r)=>{t.push({error:e,offset:n,length:r})}},n),s[0]},h=function(e,t=[],n=l.DEFAULT){let r={type:"array",offset:-1,length:-1,children:[],parent:void 0};function s(e){"property"===r.type&&(r.length=e-r.offset,r=r.parent)}function o(e){return r.children.push(e),e}f(e,{onObjectBegin:e=>{r=o({type:"object",offset:e,length:-1,parent:r,children:[]})},onObjectProperty:(e,t,n)=>{r=o({type:"property",offset:t,length:-1,parent:r,children:[]}),r.children.push({type:"string",value:e,offset:t,length:n,parent:r})},onObjectEnd:(e,t)=>{s(e+t),r.length=e+t-r.offset,r=r.parent,s(e+t)},onArrayBegin:(e,t)=>{r=o({type:"array",offset:e,length:-1,parent:r,children:[]})},onArrayEnd:(e,t)=>{r.length=e+t-r.offset,r=r.parent,s(e+t)},onLiteralValue:(e,t,n)=>{o({type:p(e),offset:t,length:n,parent:r,value:e}),s(t+n)},onSeparator:(e,t,n)=>{"property"===r.type&&(":"===e?r.colonOffset=t:","===e&&s(t))},onError:(e,n,r)=>{t.push({error:e,offset:n,length:r})}},n);const i=r.children[0];return i&&delete i.parent,i},g=function(e,t){if(!e)return;let n=e;for(let e of t)if("string"==typeof e){if("object"!==n.type||!Array.isArray(n.children))return;let t=!1;for(const r of n.children)if(Array.isArray(r.children)&&r.children[0].value===e&&2===r.children.length){n=r.children[1],t=!0;break}if(!t)return}else{const t=e;if("array"!==n.type||t<0||!Array.isArray(n.children)||t>=n.children.length)return;n=n.children[t]}return n};var m;function y(e){switch(e){case 1:return"InvalidSymbol";case 2:return"InvalidNumberFormat";case 3:return"PropertyNameExpected";case 4:return"ValueExpected";case 5:return"ColonExpected";case 6:return"CommaExpected";case 7:return"CloseBraceExpected";case 8:return"CloseBracketExpected";case 9:return"EndOfFileExpected";case 10:return"InvalidCommentToken";case 11:return"UnexpectedEndOfComment";case 12:return"UnexpectedEndOfString";case 13:return"UnexpectedEndOfNumber";case 14:return"InvalidUnicode";case 15:return"InvalidEscapeCharacter";case 16:return"InvalidCharacter"}return"<unknown ParseErrorCode>"}!function(e){e[e.InvalidSymbol=1]="InvalidSymbol",e[e.InvalidNumberFormat=2]="InvalidNumberFormat",e[e.PropertyNameExpected=3]="PropertyNameExpected",e[e.ValueExpected=4]="ValueExpected",e[e.ColonExpected=5]="ColonExpected",e[e.CommaExpected=6]="CommaExpected",e[e.CloseBraceExpected=7]="CloseBraceExpected",e[e.CloseBracketExpected=8]="CloseBracketExpected",e[e.EndOfFileExpected=9]="EndOfFileExpected",e[e.InvalidCommentToken=10]="InvalidCommentToken",e[e.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=12]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",e[e.InvalidUnicode=14]="InvalidUnicode",e[e.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",e[e.InvalidCharacter=16]="InvalidCharacter"}(m||(m={}))},157(e,t,n){const r=n(675),s=n(280);function o(e,t,n=!1){return!t||null!==t.windows&&void 0!==t.windows||(t={...t,windows:s.isWindows()}),r(e,t,n)}Object.assign(o,r),e.exports=o},940(e){const t="\\\\/",n=`[^${t}]`,r="\\.",s="\\/",o="[^/]",i=`(?:${s}|$)`,a=`(?:^|${s})`,l=`${r}{1,2}${i}`,u={DOT_LITERAL:r,PLUS_LITERAL:"\\+",QMARK_LITERAL:"\\?",SLASH_LITERAL:s,ONE_CHAR:"(?=.)",QMARK:o,END_ANCHOR:i,DOTS_SLASH:l,NO_DOT:`(?!${r})`,NO_DOTS:`(?!${a}${l})`,NO_DOT_SLASH:`(?!${r}{0,1}${i})`,NO_DOTS_SLASH:`(?!${l})`,QMARK_NO_DOT:`[^.${s}]`,STAR:`${o}*?`,START_ANCHOR:a,SEP:"/"},c={...u,SLASH_LITERAL:`[${t}]`,QMARK:n,STAR:`${n}*?`,DOTS_SLASH:`${r}{1,2}(?:[${t}]|$)`,NO_DOT:`(?!${r})`,NO_DOTS:`(?!(?:^|[${t}])${r}{1,2}(?:[${t}]|$))`,NO_DOT_SLASH:`(?!${r}{0,1}(?:[${t}]|$))`,NO_DOTS_SLASH:`(?!${r}{1,2}(?:[${t}]|$))`,QMARK_NO_DOT:`[^.${t}]`,START_ANCHOR:`(?:^|[${t}])`,END_ANCHOR:`(?:[${t}]|$)`,SEP:"\\"};e.exports={DEFAULT_MAX_EXTGLOB_RECURSION:0,MAX_LENGTH:65536,POSIX_REGEX_SOURCE:{__proto__:null,alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"},REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{__proto__:null,"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,extglobChars:e=>({"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}),globChars:e=>!0===e?c:u}},154(e,t,n){const r=n(940),s=n(280),{MAX_LENGTH:o,POSIX_REGEX_SOURCE:i,REGEX_NON_SPECIAL_CHARS:a,REGEX_SPECIAL_CHARS_BACKREF:l,REPLACEMENTS:u}=r,c=(e,t)=>{if("function"==typeof t.expandRange)return t.expandRange(...e,t);e.sort();const n=`[${e.join("-")}]`;try{new RegExp(n)}catch(t){return e.map(e=>s.escapeRegex(e)).join("..")}return n},f=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,p=e=>{const t=[];let n=0,r=0,s=0,o="",i=!1;for(const a of e)if(!0!==i)if("\\"!==a)if('"'!==a){if(0===s)if("["===a)n++;else if("]"===a&&n>0)n--;else if(0===n)if("("===a)r++;else if(")"===a&&r>0)r--;else if("|"===a&&0===r){t.push(o),o="";continue}o+=a}else s=1===s?0:1,o+=a;else o+=a,i=!0;else o+=a,i=!1;return t.push(o),t},d=e=>{let t=e.trim(),n=!0;for(;!0===n;)n=!1,/^@\([^\\()[\]{}|]+\)$/.test(t)&&(t=t.slice(2,-1),n=!0);if((e=>{let t=!1;for(const n of e)if(!0!==t)if("\\"!==n){if(/[?*+@!()[\]{}]/.test(n))return!1}else t=!0;else t=!1;return!0})(t))return t.replace(/\\(.)/g,"$1")},h=(e,t=!0)=>{if("+"!==e[0]&&"*"!==e[0]||"("!==e[1])return;let n=0,r=0,s=0,o=!1;for(let i=1;i<e.length;i++){const a=e[i];if(!0!==o)if("\\"!==a)if('"'!==a){if(1!==s)if("["!==a){if("]"===a&&n>0)n--;else if(!(n>0))if("("!==a){if(")"===a&&(r--,0===r)){if(!0===t&&i!==e.length-1)return;return{type:e[0],body:e.slice(2,i),end:i}}}else r++}else n++}else s=1===s?0:1;else o=!0;else o=!1}},g=e=>{let t=0;const n=[];for(;t<e.length;){const r=h(e.slice(t),!1);if(!r||"*"!==r.type)return;const s=p(r.body).map(e=>e.trim());if(1!==s.length)return;const o=d(s[0]);if(!o||1!==o.length)return;n.push(o),t+=r.end+1}if(!(n.length<1))return n},m=e=>{let t=0,n=e.trim(),r=h(n);for(;r;)t++,n=r.body.trim(),r=h(n);return t},y=(e,t)=>{if("string"!=typeof e)throw new TypeError("Expected a string");e=u[e]||e;const n={...t},h="number"==typeof n.maxLength?Math.min(o,n.maxLength):o;let v=e.length;if(v>h)throw new SyntaxError(`Input length: ${v}, exceeds maximum allowed length: ${h}`);const b={type:"bos",value:"",output:n.prepend||""},_=[b],O=n.capture?"":"?:",C=r.globChars(n.windows),A=r.extglobChars(C),{DOT_LITERAL:x,PLUS_LITERAL:E,SLASH_LITERAL:R,ONE_CHAR:k,DOTS_SLASH:$,NO_DOT:S,NO_DOT_SLASH:w,NO_DOTS_SLASH:T,QMARK:j,QMARK_NO_DOT:L,STAR:H,START_ANCHOR:F}=C,D=e=>`(${O}(?:(?!${F}${e.dot?$:x}).)*?)`,P=n.dot?"":S,N=n.dot?j:L;let q=!0===n.bash?D(n):H;n.capture&&(q=`(${q})`),"boolean"==typeof n.noext&&(n.noextglob=n.noext);const I={input:e,index:-1,start:0,dot:!0===n.dot,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:_};e=s.removePrefix(e,I),v=e.length;const M=[],B=[],U=[];let G,Q=b;const K=()=>I.index===v-1,X=I.peek=(t=1)=>e[I.index+t],J=I.advance=()=>e[++I.index]||"",V=()=>e.slice(I.index+1),W=(e="",t=0)=>{I.consumed+=e,I.index+=t},Y=e=>{I.output+=null!=e.output?e.output:e.value,W(e.value)},z=()=>{let e=1;for(;"!"===X()&&("("!==X(2)||"?"===X(3));)J(),I.start++,e++;return e%2!=0&&(I.negated=!0,I.start++,!0)},Z=e=>{I[e]++,U.push(e)},ee=e=>{I[e]--,U.pop()},te=e=>{if("globstar"===Q.type){const t=I.braces>0&&("comma"===e.type||"brace"===e.type),n=!0===e.extglob||M.length&&("pipe"===e.type||"paren"===e.type);"slash"===e.type||"paren"===e.type||t||n||(I.output=I.output.slice(0,-Q.output.length),Q.type="star",Q.value="*",Q.output=q,I.output+=Q.output)}if(M.length&&"paren"!==e.type&&(M[M.length-1].inner+=e.value),(e.value||e.output)&&Y(e),Q&&"text"===Q.type&&"text"===e.type)return Q.output=(Q.output||Q.value)+e.value,void(Q.value+=e.value);e.prev=Q,_.push(e),Q=e},ne=(e,t)=>{const r={...A[t],conditions:1,inner:""};r.prev=Q,r.parens=I.parens,r.output=I.output,r.startIndex=I.index,r.tokensIndex=_.length;const s=(n.capture?"(":"")+r.open;Z("parens"),te({type:e,value:t,output:I.output?"":k}),te({type:"paren",extglob:!0,value:J(),output:s}),M.push(r)},re=o=>{const i=e.slice(o.startIndex,I.index+1),a=((e,t)=>{if(!1===t.maxExtglobRecursion)return{risky:!1};const n="number"==typeof t.maxExtglobRecursion?t.maxExtglobRecursion:r.DEFAULT_MAX_EXTGLOB_RECURSION,o=p(e).map(e=>e.trim());if(o.length>1&&(o.some(e=>""===e)||o.some(e=>/^[*?]+$/.test(e))||(e=>{const t=e.map(d).filter(Boolean);for(let e=0;e<t.length;e++)for(let n=e+1;n<t.length;n++){const r=t[e],s=t[n],o=r[0];if(o&&r===o.repeat(r.length)&&s===o.repeat(s.length)&&(r===s||r.startsWith(s)||s.startsWith(r)))return!0}return!1})(o)))return{risky:!0};const i=[];let a=!1,l=!0;for(const e of o){const t=g(e);if(t){a=!0,i.push(...t);continue}const r=d(e);if(r&&1===r.length)i.push(r);else if(l=!1,m(e)>n)return{risky:!0}}return a?l?{risky:!0,safeOutput:(u=[...new Set(i)],`${1===u.length?s.escapeRegex(u[0]):`[${u.map(e=>s.escapeRegex(e)).join("")}]`}*`)}:{risky:!0}:{risky:!1};var u})(e.slice(o.startIndex+2,I.index),n);if(("plus"===o.type||"star"===o.type)&&a.risky){const e=a.safeOutput?(o.output?"":k)+(n.capture?`(${a.safeOutput})`:a.safeOutput):void 0,t=_[o.tokensIndex];t.type="text",t.value=i,t.output=e||s.escapeRegex(i);for(let e=o.tokensIndex+1;e<_.length;e++)_[e].value="",_[e].output="",delete _[e].suffix;return I.output=o.output+t.output,I.backtrack=!0,te({type:"paren",extglob:!0,value:G,output:""}),void ee("parens")}let l,u=o.close+(n.capture?")":"");if("negate"===o.type){let e=q;if(o.inner&&o.inner.length>1&&o.inner.includes("/")&&(e=D(n)),(e!==q||K()||/^\)+$/.test(V()))&&(u=o.close=`)$))${e}`),o.inner.includes("*")&&(l=V())&&/^\.[^\\/.]+$/.test(l)){const n=y(l,{...t,fastpaths:!1}).output;u=o.close=`)${n})${e})`}"bos"===o.prev.type&&(I.negatedExtglob=!0)}te({type:"paren",extglob:!0,value:G,output:u}),ee("parens")};if(!1!==n.fastpaths&&!/(^[*!]|[/()[\]{}"])/.test(e)){let r=!1,o=e.replace(l,(e,t,n,s,o,i)=>"\\"===s?(r=!0,e):"?"===s?t?t+s+(o?j.repeat(o.length):""):0===i?N+(o?j.repeat(o.length):""):j.repeat(n.length):"."===s?x.repeat(n.length):"*"===s?t?t+s+(o?q:""):q:t?e:`\\${e}`);return!0===r&&(o=!0===n.unescape?o.replace(/\\/g,""):o.replace(/\\+/g,e=>e.length%2==0?"\\\\":e?"\\":"")),o===e&&!0===n.contains?(I.output=e,I):(I.output=s.wrapOutput(o,I,t),I)}for(;!K();){if(G=J(),"\0"===G)continue;if("\\"===G){const e=X();if("/"===e&&!0!==n.bash)continue;if("."===e||";"===e)continue;if(!e){G+="\\",te({type:"text",value:G});continue}const t=/^\\+/.exec(V());let r=0;if(t&&t[0].length>2&&(r=t[0].length,I.index+=r,r%2!=0&&(G+="\\")),!0===n.unescape?G=J():G+=J(),0===I.brackets){te({type:"text",value:G});continue}}if(I.brackets>0&&("]"!==G||"["===Q.value||"[^"===Q.value)){if(!1!==n.posix&&":"===G){const e=Q.value.slice(1);if(e.includes("[")&&(Q.posix=!0,e.includes(":"))){const e=Q.value.lastIndexOf("["),t=Q.value.slice(0,e),n=Q.value.slice(e+2),r=i[n];if(r){Q.value=t+r,I.backtrack=!0,J(),b.output||1!==_.indexOf(Q)||(b.output=k);continue}}}("["===G&&":"!==X()||"-"===G&&"]"===X())&&(G=`\\${G}`),"]"!==G||"["!==Q.value&&"[^"!==Q.value||(G=`\\${G}`),!0===n.posix&&"!"===G&&"["===Q.value&&(G="^"),Q.value+=G,Y({value:G});continue}if(1===I.quotes&&'"'!==G){G=s.escapeRegex(G),Q.value+=G,Y({value:G});continue}if('"'===G){I.quotes=1===I.quotes?0:1,!0===n.keepQuotes&&te({type:"text",value:G});continue}if("("===G){Z("parens"),te({type:"paren",value:G});continue}if(")"===G){if(0===I.parens&&!0===n.strictBrackets)throw new SyntaxError(f("opening","("));const e=M[M.length-1];if(e&&I.parens===e.parens+1){re(M.pop());continue}te({type:"paren",value:G,output:I.parens?")":"\\)"}),ee("parens");continue}if("["===G){if(!0!==n.nobracket&&V().includes("]"))Z("brackets");else{if(!0!==n.nobracket&&!0===n.strictBrackets)throw new SyntaxError(f("closing","]"));G=`\\${G}`}te({type:"bracket",value:G});continue}if("]"===G){if(!0===n.nobracket||Q&&"bracket"===Q.type&&1===Q.value.length){te({type:"text",value:G,output:`\\${G}`});continue}if(0===I.brackets){if(!0===n.strictBrackets)throw new SyntaxError(f("opening","["));te({type:"text",value:G,output:`\\${G}`});continue}ee("brackets");const e=Q.value.slice(1);if(!0===Q.posix||"^"!==e[0]||e.includes("/")||(G=`/${G}`),Q.value+=G,Y({value:G}),!1===n.literalBrackets||s.hasRegexChars(e))continue;const t=s.escapeRegex(Q.value);if(I.output=I.output.slice(0,-Q.value.length),!0===n.literalBrackets){I.output+=t,Q.value=t;continue}Q.value=`(${O}${t}|${Q.value})`,I.output+=Q.value;continue}if("{"===G&&!0!==n.nobrace){Z("braces");const e={type:"brace",value:G,output:"(",outputIndex:I.output.length,tokensIndex:I.tokens.length};B.push(e),te(e);continue}if("}"===G){const e=B[B.length-1];if(!0===n.nobrace||!e){te({type:"text",value:G,output:G});continue}let t=")";if(!0===e.dots){const e=_.slice(),r=[];for(let t=e.length-1;t>=0&&(_.pop(),"brace"!==e[t].type);t--)"dots"!==e[t].type&&r.unshift(e[t].value);t=c(r,n),I.backtrack=!0}if(!0!==e.comma&&!0!==e.dots){const n=I.output.slice(0,e.outputIndex),r=I.tokens.slice(e.tokensIndex);e.value=e.output="\\{",G=t="\\}",I.output=n;for(const e of r)I.output+=e.output||e.value}te({type:"brace",value:G,output:t}),ee("braces"),B.pop();continue}if("|"===G){M.length>0&&M[M.length-1].conditions++,te({type:"text",value:G});continue}if(","===G){let e=G;const t=B[B.length-1];t&&"braces"===U[U.length-1]&&(t.comma=!0,e="|"),te({type:"comma",value:G,output:e});continue}if("/"===G){if("dot"===Q.type&&I.index===I.start+1){I.start=I.index+1,I.consumed="",I.output="",_.pop(),Q=b;continue}te({type:"slash",value:G,output:R});continue}if("."===G){if(I.braces>0&&"dot"===Q.type){"."===Q.value&&(Q.output=x);const e=B[B.length-1];Q.type="dots",Q.output+=G,Q.value+=G,e.dots=!0;continue}if(I.braces+I.parens===0&&"bos"!==Q.type&&"slash"!==Q.type){te({type:"text",value:G,output:x});continue}te({type:"dot",value:G,output:x});continue}if("?"===G){if((!Q||"("!==Q.value)&&!0!==n.noextglob&&"("===X()&&"?"!==X(2)){ne("qmark",G);continue}if(Q&&"paren"===Q.type){const e=X();let t=G;("("===Q.value&&!/[!=<:]/.test(e)||"<"===e&&!/<([!=]|\w+>)/.test(V()))&&(t=`\\${G}`),te({type:"text",value:G,output:t});continue}if(!0!==n.dot&&("slash"===Q.type||"bos"===Q.type)){te({type:"qmark",value:G,output:L});continue}te({type:"qmark",value:G,output:j});continue}if("!"===G){if(!0!==n.noextglob&&"("===X()&&("?"!==X(2)||!/[!=<:]/.test(X(3)))){ne("negate",G);continue}if(!0!==n.nonegate&&0===I.index){z();continue}}if("+"===G){if(!0!==n.noextglob&&"("===X()&&"?"!==X(2)){ne("plus",G);continue}if(Q&&"("===Q.value||!1===n.regex){te({type:"plus",value:G,output:E});continue}if(Q&&("bracket"===Q.type||"paren"===Q.type||"brace"===Q.type)||I.parens>0){te({type:"plus",value:G});continue}te({type:"plus",value:E});continue}if("@"===G){if(!0!==n.noextglob&&"("===X()&&"?"!==X(2)){te({type:"at",extglob:!0,value:G,output:""});continue}te({type:"text",value:G});continue}if("*"!==G){"$"!==G&&"^"!==G||(G=`\\${G}`);const e=a.exec(V());e&&(G+=e[0],I.index+=e[0].length),te({type:"text",value:G});continue}if(Q&&("globstar"===Q.type||!0===Q.star)){Q.type="star",Q.star=!0,Q.value+=G,Q.output=q,I.backtrack=!0,I.globstar=!0,W(G);continue}let t=V();if(!0!==n.noextglob&&/^\([^?]/.test(t)){ne("star",G);continue}if("star"===Q.type){if(!0===n.noglobstar){W(G);continue}const r=Q.prev,s=r.prev,o="slash"===r.type||"bos"===r.type,i=s&&("star"===s.type||"globstar"===s.type);if(!0===n.bash&&(!o||t[0]&&"/"!==t[0])){te({type:"star",value:G,output:""});continue}const a=I.braces>0&&("comma"===r.type||"brace"===r.type),l=M.length&&("pipe"===r.type||"paren"===r.type);if(!o&&"paren"!==r.type&&!a&&!l){te({type:"star",value:G,output:""});continue}for(;"/**"===t.slice(0,3);){const n=e[I.index+4];if(n&&"/"!==n)break;t=t.slice(3),W("/**",3)}const u=K()||I.parens>0&&t===")".repeat(I.parens)&&!M.some(e=>"negate"===e.type);if("bos"===r.type&&K()){Q.type="globstar",Q.value+=G,Q.output=D(n),I.output=Q.output,I.globstar=!0,W(G);continue}if("slash"===r.type&&"bos"!==r.prev.type&&!i&&u){I.output=I.output.slice(0,-(r.output+Q.output).length),r.output=`(?:${r.output}`,Q.type="globstar",Q.output=D(n)+(n.strictSlashes?")":"|$)"),Q.value+=G,I.globstar=!0,I.output+=r.output+Q.output,W(G);continue}if("slash"===r.type&&"bos"!==r.prev.type&&"/"===t[0]){const e=void 0!==t[1]?"|$":"";I.output=I.output.slice(0,-(r.output+Q.output).length),r.output=`(?:${r.output}`,Q.type="globstar",Q.output=`${D(n)}${R}|${R}${e})`,Q.value+=G,I.output+=r.output+Q.output,I.globstar=!0,W(G+J()),te({type:"slash",value:"/",output:""});continue}if("bos"===r.type&&"/"===t[0]){Q.type="globstar",Q.value+=G,Q.output=`(?:^|${R}|${D(n)}${R})`,I.output=Q.output,I.globstar=!0,W(G+J()),te({type:"slash",value:"/",output:""});continue}I.output=I.output.slice(0,-Q.output.length),Q.type="globstar",Q.output=D(n),Q.value+=G,I.output+=Q.output,I.globstar=!0,W(G);continue}const r={type:"star",value:G,output:q};!0!==n.bash?!Q||"bracket"!==Q.type&&"paren"!==Q.type||!0!==n.regex?(I.index!==I.start&&"slash"!==Q.type&&"dot"!==Q.type||("dot"===Q.type?(I.output+=w,Q.output+=w):!0===n.dot?(I.output+=T,Q.output+=T):(I.output+=P,Q.output+=P),"*"!==X()&&(I.output+=k,Q.output+=k)),te(r)):(r.output=G,te(r)):(r.output=".*?","bos"!==Q.type&&"slash"!==Q.type||(r.output=P+r.output),te(r))}for(;I.brackets>0;){if(!0===n.strictBrackets)throw new SyntaxError(f("closing","]"));I.output=s.escapeLast(I.output,"["),ee("brackets")}for(;I.parens>0;){if(!0===n.strictBrackets)throw new SyntaxError(f("closing",")"));I.output=s.escapeLast(I.output,"("),ee("parens")}for(;I.braces>0;){if(!0===n.strictBrackets)throw new SyntaxError(f("closing","}"));I.output=s.escapeLast(I.output,"{"),ee("braces")}if(!0===n.strictSlashes||"star"!==Q.type&&"bracket"!==Q.type||te({type:"maybe_slash",value:"",output:`${R}?`}),!0===I.backtrack){I.output="";for(const e of I.tokens)I.output+=null!=e.output?e.output:e.value,e.suffix&&(I.output+=e.suffix)}return I};y.fastpaths=(e,t)=>{const n={...t},i="number"==typeof n.maxLength?Math.min(o,n.maxLength):o,a=e.length;if(a>i)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${i}`);e=u[e]||e;const{DOT_LITERAL:l,SLASH_LITERAL:c,ONE_CHAR:f,DOTS_SLASH:p,NO_DOT:d,NO_DOTS:h,NO_DOTS_SLASH:g,STAR:m,START_ANCHOR:y}=r.globChars(n.windows),v=n.dot?h:d,b=n.dot?g:d,_=n.capture?"":"?:";let O=!0===n.bash?".*?":m;n.capture&&(O=`(${O})`);const C=e=>!0===e.noglobstar?O:`(${_}(?:(?!${y}${e.dot?p:l}).)*?)`,A=e=>{switch(e){case"*":return`${v}${f}${O}`;case".*":return`${l}${f}${O}`;case"*.*":return`${v}${O}${l}${f}${O}`;case"*/*":return`${v}${O}${c}${f}${b}${O}`;case"**":return v+C(n);case"**/*":return`(?:${v}${C(n)}${c})?${b}${f}${O}`;case"**/*.*":return`(?:${v}${C(n)}${c})?${b}${O}${l}${f}${O}`;case"**/.*":return`(?:${v}${C(n)}${c})?${l}${f}${O}`;default:{const t=/^(.*?)\.(\w+)$/.exec(e);if(!t)return;const n=A(t[1]);if(!n)return;return n+l+t[2]}}},x=s.removePrefix(e,{negated:!1,prefix:""});let E=A(x);return E&&!0!==n.strictSlashes&&(E+=`${c}?`),E},e.exports=y},675(e,t,n){const r=n(608),s=n(154),o=n(280),i=n(940),a=(e,t,n=!1)=>{if(Array.isArray(e)){const r=e.map(e=>a(e,t,n)),s=e=>{for(const t of r){const n=t(e);if(n)return n}return!1};return s}const r=(s=e)&&"object"==typeof s&&!Array.isArray(s)&&e.tokens&&e.input;var s;if(""===e||"string"!=typeof e&&!r)throw new TypeError("Expected pattern to be a non-empty string");const o=t||{},i=o.windows,l=r?a.compileRe(e,t):a.makeRe(e,t,!1,!0),u=l.state;delete l.state;let c=()=>!1;if(o.ignore){const e={...t,ignore:null,onMatch:null,onResult:null};c=a(o.ignore,e,n)}const f=(n,r=!1)=>{const{isMatch:s,match:f,output:p}=a.test(n,l,t,{glob:e,posix:i}),d={glob:e,state:u,regex:l,posix:i,input:n,output:p,match:f,isMatch:s};return"function"==typeof o.onResult&&o.onResult(d),!1===s?(d.isMatch=!1,!!r&&d):c(n)?("function"==typeof o.onIgnore&&o.onIgnore(d),d.isMatch=!1,!!r&&d):("function"==typeof o.onMatch&&o.onMatch(d),!r||d)};return n&&(f.state=u),f};a.test=(e,t,n,{glob:r,posix:s}={})=>{if("string"!=typeof e)throw new TypeError("Expected input to be a string");if(""===e)return{isMatch:!1,output:""};const i=n||{},l=i.format||(s?o.toPosixSlashes:null);let u=e===r,c=u&&l?l(e):e;return!1===u&&(c=l?l(e):e,u=c===r),!1!==u&&!0!==i.capture||(u=!0===i.matchBase||!0===i.basename?a.matchBase(e,t,n,s):t.exec(c)),{isMatch:Boolean(u),match:u,output:c}},a.matchBase=(e,t,n,r=n&&n.windows)=>(t instanceof RegExp?t:a.makeRe(t,n)).test(o.basename(e,{windows:r})),a.isMatch=(e,t,n)=>a(t,n)(e),a.parse=(e,t)=>Array.isArray(e)?e.map(e=>a.parse(e,t)):s(e,{...t,fastpaths:!1}),a.scan=(e,t)=>r(e,t),a.compileRe=(e,t,n=!1,r=!1)=>{if(!0===n)return e.output;const s=t||{},o=s.contains?"":"^",i=s.contains?"":"$";let l=`${o}(?:${e.output})${i}`;e&&!0===e.negated&&(l=`^(?!${l}).*$`);const u=a.toRegex(l,t);return!0===r&&(u.state=e),u},a.makeRe=(e,t={},n=!1,r=!1)=>{if(!e||"string"!=typeof e)throw new TypeError("Expected a non-empty string");let o={negated:!1,fastpaths:!0};return!1===t.fastpaths||"."!==e[0]&&"*"!==e[0]||(o.output=s.fastpaths(e,t)),o.output||(o=s(e,t)),a.compileRe(o,t,n,r)},a.toRegex=(e,t)=>{try{const n=t||{};return new RegExp(e,n.flags||(n.nocase?"i":""))}catch(e){if(t&&!0===t.debug)throw e;return/$^/}},a.constants=i,e.exports=a},608(e,t,n){const r=n(280),{CHAR_ASTERISK:s,CHAR_AT:o,CHAR_BACKWARD_SLASH:i,CHAR_COMMA:a,CHAR_DOT:l,CHAR_EXCLAMATION_MARK:u,CHAR_FORWARD_SLASH:c,CHAR_LEFT_CURLY_BRACE:f,CHAR_LEFT_PARENTHESES:p,CHAR_LEFT_SQUARE_BRACKET:d,CHAR_PLUS:h,CHAR_QUESTION_MARK:g,CHAR_RIGHT_CURLY_BRACE:m,CHAR_RIGHT_PARENTHESES:y,CHAR_RIGHT_SQUARE_BRACKET:v}=n(940),b=e=>e===c||e===i,_=e=>{!0!==e.isPrefix&&(e.depth=e.isGlobstar?1/0:1)};e.exports=(e,t)=>{const n=t||{},O=e.length-1,C=!0===n.parts||!0===n.tokens||!0===n.scanToEnd,A=[],x=[],E=[];let R,k,$=e,S=-1,w=0,T=0,j=!1,L=!1,H=!1,F=!1,D=!1,P=!1,N=!1,q=!1,I=!1,M=!1,B=0,U={value:"",depth:0,isGlob:!1};const G=()=>S>=O,Q=()=>$.charCodeAt(S+1),K=()=>(R=k,$.charCodeAt(++S));for(;S<O;){let e;if(k=K(),k!==i){if(!0===P||k===f){for(B++;!0!==G()&&(k=K());)if(k!==i)if(k!==f){if(!0!==P&&k===l&&(k=K())===l){if(j=U.isBrace=!0,H=U.isGlob=!0,M=!0,!0===C)continue;break}if(!0!==P&&k===a){if(j=U.isBrace=!0,H=U.isGlob=!0,M=!0,!0===C)continue;break}if(k===m&&(B--,0===B)){P=!1,j=U.isBrace=!0,M=!0;break}}else B++;else N=U.backslashes=!0,K();if(!0===C)continue;break}if(k!==c){if(!0!==n.noext&&!0==(k===h||k===o||k===s||k===g||k===u)&&Q()===p){if(H=U.isGlob=!0,F=U.isExtglob=!0,M=!0,k===u&&S===w&&(I=!0),!0===C){let e=0;for(;!0!==G()&&(k=K());)if(k!==i)if(k!==p){if(k===y&&0===--e){M=!0;break}}else e++;else N=U.backslashes=!0,K();continue}break}if(k===s){if(R===s&&(D=U.isGlobstar=!0),H=U.isGlob=!0,M=!0,!0===C)continue;break}if(k===g){if(H=U.isGlob=!0,M=!0,!0===C)continue;break}if(k===d){for(;!0!==G()&&(e=K());)if(e!==i){if(e===v){L=U.isBracket=!0,H=U.isGlob=!0,M=!0;break}}else N=U.backslashes=!0,K();if(!0===C)continue;break}if(!0===n.nonegate||k!==u||S!==w){if(!0!==n.noparen&&k===p){if(H=U.isGlob=!0,!0===C){let e=1;for(;!0!==G()&&(k=K());)if(k!==i)if(k!==p){if(k===y&&0===--e){M=!0;break}}else e++;else N=U.backslashes=!0,K();continue}break}if(!0===H){if(M=!0,!0===C)continue;break}}else q=U.negated=!0,w++}else{if(A.push(S),x.push(U),U={value:"",depth:0,isGlob:!1},!0===M)continue;if(R===l&&S===w+1){w+=2;continue}T=S+1}}else N=U.backslashes=!0,k=K(),k===f&&(P=!0)}!0===n.noext&&(F=!1,H=!1);let X=$,J="",V="";w>0&&(J=$.slice(0,w),$=$.slice(w),T-=w),X&&!0===H&&T>0?(X=$.slice(0,T),V=$.slice(T)):!0===H?(X="",V=$):X=$,X&&""!==X&&"/"!==X&&X!==$&&b(X.charCodeAt(X.length-1))&&(X=X.slice(0,-1)),!0===n.unescape&&(V&&(V=r.removeBackslashes(V)),X&&!0===N&&(X=r.removeBackslashes(X)));const W={prefix:J,input:e,start:w,base:X,glob:V,isBrace:j,isBracket:L,isGlob:H,isExtglob:F,isGlobstar:D,negated:q,negatedExtglob:I};if(!0===n.tokens&&(W.maxDepth=0,b(k)||x.push(U),W.tokens=x),!0===n.parts||!0===n.tokens){let t;for(let r=0;r<A.length;r++){const s=void 0!==t?t+1:w,o=A[r],i=e.slice(s,o);n.tokens&&(0===r&&0!==w?(x[r].isPrefix=!0,x[r].value=J):x[r].value=i,_(x[r]),W.maxDepth+=x[r].depth),o>=w&&(E.push(i),t=o)}const r=void 0!==t?t+1:w,s=e.slice(r);E.push(s),n.tokens&&t&&t+1<e.length&&(x[x.length-1].value=s,_(x[x.length-1]),W.maxDepth+=x[x.length-1].depth),W.slashes=A,W.parts=E}return W}},280(e,t,n){const{REGEX_BACKSLASH:r,REGEX_REMOVE_BACKSLASH:s,REGEX_SPECIAL_CHARS:o,REGEX_SPECIAL_CHARS_GLOBAL:i}=n(940);t.isObject=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),t.hasRegexChars=e=>o.test(e),t.isRegexChar=e=>1===e.length&&t.hasRegexChars(e),t.escapeRegex=e=>e.replace(i,"\\$1"),t.toPosixSlashes=e=>e.replace(r,"/"),t.isWindows=()=>{if("undefined"!=typeof navigator&&navigator.platform){const e=navigator.platform.toLowerCase();return"win32"===e||"windows"===e}return!("undefined"==typeof process||!process.platform)&&"win32"===process.platform},t.removeBackslashes=e=>e.replace(s,e=>"\\"===e?"":e),t.escapeLast=(e,n,r)=>{const s=e.lastIndexOf(n,r);return-1===s?e:"\\"===e[s-1]?t.escapeLast(e,n,s-1):`${e.slice(0,s)}\\${e.slice(s)}`},t.removePrefix=(e,t={})=>{let n=e;return n.startsWith("./")&&(n=n.slice(2),t.prefix="./"),n},t.wrapOutput=(e,t={},n={})=>{let r=`${n.contains?"":"^"}(?:${e})${n.contains?"":"$"}`;return!0===t.negated&&(r=`(?:^(?!${r}).*$)`),r},t.basename=(e,{windows:t}={})=>{const n=e.split(t?/[\\/]/:"/"),r=n[n.length-1];return""===r?n[n.length-2]:r}},738(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.configFileName=void 0,t.isExcluded=function(e,t){return e.excludes.some(({base:e,glob:n})=>{const r=u.relative(e,t);return!r.startsWith("..")&&!u.isAbsolute(r)&&(0,c.matchesGlob)(r.split(u.sep).join("/"),n)})},t.loadConfig=function(e,n={}){const r={plVersion:"latest",schemaCacheDir:g(),rules:{},excludes:[],...n.defaults},s=[],o=n.configPath??(e?u.join(e,t.configFileName):void 0);let i;if(o&&a.existsSync(o)){i=u.resolve(o);const e=(0,f.parseJsonDoc)(a.readFileSync(i,"utf-8"));for(const t of e.errors)s.push({file:i,startOffset:t.offset,endOffset:t.offset+t.length,message:`invalid JSON: ${t.message}`,severity:"error"});m(r,e.value,u.dirname(i),(t,n,r="error")=>s.push({file:i,...(0,f.rangeOf)(e,n,{key:"warning"===r}),message:t,severity:r}))}else n.configPath&&s.push({startOffset:0,endOffset:0,message:`config file not found: ${n.configPath}`,severity:"error"});return n.overrides&&m(r,n.overrides,n.overridesBaseDir??process.cwd(),(e,t,n="error")=>s.push({startOffset:0,endOffset:0,message:e,severity:n})),{config:r,file:i,problems:s}};const a=i(n(24)),l=i(n(161)),u=i(n(760)),c=n(554),f=n(758),p=n(702),d=n(731);t.configFileName=".pl-navigator.jsonc";const h=["off","warning","error"];function g(){const e=process.env.XDG_CACHE_HOME||u.join(l.homedir(),".cache");return u.join(e,"prairielearn-navigator","schemas")}function m(e,t,n,r){const s=(e,t)=>r(e,t,"warning");if(void 0!==t)if("object"!=typeof t||null===t||Array.isArray(t))r("config must be an object",[]);else for(const[o,i]of Object.entries(t))if(void 0!==i)switch(o){case"$schema":break;case"plVersion":{const t=(0,p.validatePlVersion)(i);t?r(t,[o]):e.plVersion=i;break}case"schemaCacheDir":"string"!=typeof i||""===i?r("schemaCacheDir must be a non-empty string",[o]):e.schemaCacheDir=u.resolve(n,y(i));break;case"excludes":!Array.isArray(i)||i.some(e=>"string"!=typeof e||""===e)?r("excludes must be an array of glob patterns",[o]):e.excludes=[...e.excludes,...i.map(e=>({base:n,glob:e.replace(/^\.\//,"")}))];break;case"rules":if("object"!=typeof i||null===i||Array.isArray(i)){r('rules must be an object of rule id to "off" | "warning" | "error"',[o]);break}for(const[t,n]of Object.entries(i))(0,d.isRuleId)(t)?"string"==typeof n&&h.includes(n)?e.rules={...e.rules,[t]:n}:r(`rule "${t}" must be "off", "warning", or "error"`,[o,t]):s(`unknown rule "${t}"`,[o,t]);break;default:s(`unknown config key "${o}"`,[o])}}function y(e){return"~"===e||e.startsWith("~/")?u.join(l.homedir(),e.slice(1)):e}},554(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t}),a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.questionHtml=t.courseFiles=void 0,t.matchesGlob=function(e,t){let n=c.get(t);return n||(n=(0,u.default)(t,{dot:!0}),c.set(t,n)),n(e)},t.courseRelativePath=function(e,t){return l.relative(e,t).split(l.sep).join("/")};const l=i(n(760)),u=a(n(157));t.courseFiles={course:"infoCourse.json",courseInstance:"courseInstances/**/infoCourseInstance.json",assessment:"courseInstances/**/assessments/**/infoAssessment.json",question:"questions/**/info.json",element:"elements/*/info.json",elementExtension:"elementExtensions/*/*/info.json"},t.questionHtml="questions/**/question.html";const c=new Map},13(e,t){t.findElements=function(e,t){const n=function(e){return e.replace(/<!--[\s\S]*?(?:-->|$)/g,e=>e.replace(/[^\n]/g," "))}(e),o=n.toLowerCase(),i=`<${t.toLowerCase()}`,a=[];for(let e=o.indexOf(i);-1!==e;e=o.indexOf(i,e+1)){const t=e+i.length;if(!/[\s/>]/.test(n[t]??""))continue;const o=r(n,t);if(-1===o)break;a.push({startOffset:e,endOffset:o+1,attributes:s(n.slice(t,o),t)}),e=o}return a};const n=/([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;function r(e,t){let n;for(let r=t;r<e.length;r++){const t=e[r];if(n)t===n&&(n=void 0);else if('"'===t||"'"===t)n=t;else if(">"===t)return r}return-1}function s(e,t){const r=new Map;for(const s of e.matchAll(n)){const e=s[1].toLowerCase();if(r.has(e))continue;const n=s[2]??s[3]??s[4]??"",o=void 0!==s[2]||void 0!==s[3],i=t+s.index+s[0].length-(o?1:0);r.set(e,{value:n,startOffset:i-n.length,endOffset:i})}return r}},758(e,t,n){t.parseJsonDoc=s,t.strictJsonErrors=function(e){const t=[];return(0,r.parseTree)(e,t,{allowTrailingComma:!1,disallowComments:!0}),t.map(e=>({offset:e.offset,length:e.length,message:(0,r.printParseErrorCode)(e.error)}))},t.parseJsonc=function(e){const t=s(e);return 0===t.errors.length?t.value:void 0},t.rangeOf=function(e,t,n={}){if(e.tree)for(let s=t.length;s>=0;s--){let o=(0,r.findNodeAtLocation)(e.tree,t.slice(0,s));if(o&&n.key&&s===t.length&&"property"===o.parent?.type&&(o=o.parent.children?.[0]??o),o)return{startOffset:o.offset,endOffset:o.offset+o.length}}return{startOffset:0,endOffset:0}},t.pointerToPath=function(e){return""===e?[]:e.slice(1).split("/").map(e=>e.replace(/~1/g,"/").replace(/~0/g,"~")).map(e=>/^\d+$/.test(e)?Number(e):e)};const r=n(211);function s(e){const t=[],n={allowTrailingComma:!0,disallowComments:!1},s=(0,r.parseTree)(e,t,n);return{value:s?(0,r.parse)(e,[],n):void 0,tree:s,errors:t.map(e=>({offset:e.offset,length:e.length,message:(0,r.printParseErrorCode)(e.error)}))}}},702(e,t){t.validatePlVersion=function(e){return"string"!=typeof e?"plVersion must be a string":"latest"===e||o(e)||function(e){return r.test(e)}(e)?void 0:`invalid plVersion "${e}": expected "latest", a date (YYYY-MM-DD), or a 40-character commit sha`},t.versionDate=function(e){return o(e)?e:(new Date).toISOString().slice(0,10)},t.matchesRange=function(e,t){return e.trim().split(/\s+/).every(n=>{if("*"===n)return!0;const r=s.exec(n);if(!r)throw new Error(`invalid version range "${e}"`);const[,o="=",i]=r;switch(o){case">=":return t>=i;case"<=":return t<=i;case">":return t>i;case"<":return t<i;default:return t===i}})};const n=/^\d{4}-\d{2}-\d{2}$/,r=/^[0-9a-f]{40}$/,s=/^(>=|<=|>|<|=)?(\d{4}-\d{2}-\d{2})$/;function o(e){return n.test(e)&&!Number.isNaN(Date.parse(e))}},208(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.createQuestionFileResolver=function(e){return new v(e)};const a=i(n(24)),l=i(n(760)),u=n(738),c=n(554),f=n(13),p=n(758),d=n(702),h=n(392),g=n(633),m=new Set([".git","__pycache__","node_modules"]),y=/^(?:\.DS_Store|\.#.*|.*~|.*\.swp|.*\.swx|.*\.tmp|.*\.py[co])$/;class v{courseRoot;questions=new Map;resolutions=new Map;config;configDiagnostics;constructor(e){this.courseRoot=l.resolve(e.courseRoot),this.setQuestions(e.questions);const t=(0,u.loadConfig)(this.courseRoot);this.config=t.config,this.configDiagnostics=t.problems.map(e=>e.file?`${this.relative(e.file)}: ${e.message}`:e.message)}async resolveAll(){const e=[...this.questions.keys()].sort();return await this.resolveQids(e),this.publicResolutions(e)}async update(e,t){const n=new Map(this.questions),r=this.templateDescendants(),s=new Set;if(t){this.setQuestions(t);for(const[e,t]of n){const n=this.questions.get(e);if(!n||n.directory!==t.directory){s.add(e);for(const t of r.get(e)??[])s.add(t)}}for(const e of this.questions.keys())if(!n.has(e)){s.add(e);for(const t of r.get(e)??[])s.add(t)}}const o=e.map(e=>{const t=b(e);if(!t)throw new Error(`Invalid changed path: ${e}`);return t});if(o.includes(".pl-navigator.jsonc")){const e=(0,u.loadConfig)(this.courseRoot);this.config=e.config,this.configDiagnostics=e.problems.map(e=>e.file?`${this.relative(e.file)}: ${e.message}`:e.message);for(const e of this.questions.keys())s.add(e)}else for(const e of o){for(const[t,n]of this.questions)if(_(n.directory,e)){s.add(t);for(const e of r.get(t)??[])s.add(e)}for(const[t,n]of this.resolutions)n.dependencies.some(t=>"directory"===t.kind?_(t.path,e):t.path===e)&&s.add(t)}for(const e of[...this.resolutions.keys()])this.questions.has(e)||this.resolutions.delete(e);const i=[...s].filter(e=>this.questions.has(e)).sort();return await this.resolveQids(i),{affectedQids:i,resolutions:this.publicResolutions(i)}}async resolveQids(e){await Promise.all(e.map(async e=>{try{this.resolutions.set(e,await this.resolveQuestion(e))}catch(t){this.resolutions.set(e,{files:[],complete:!1,diagnostics:[`Resolver failure: ${this.errorMessage(t)}`],dependencies:[],templates:[]})}}))}async resolveQuestion(e){const t=new Map,n=[...this.configDiagnostics],r=[],s=new Set,o=new Set,i=async(e,u)=>{if(o.has(e))return void n.push(`Template cycle: ${[...o,e].join(" -> ")}`);o.add(e);const c=this.questions.get(e);if(!c)return void n.push(`Missing template question: ${e}`);const f=this.absolute(c.directory);r.push({kind:"directory",path:c.directory}),await this.collectDirectory(f,u,t,n);for(const e of["info.json","question.html"]){const u=l.join(f,e),c=this.relative(u);if(!a.existsSync(u))continue;r.push({kind:"file",path:c});const g=await a.promises.readFile(u,"utf-8");"info.json"===e&&(0,p.parseJsonDoc)(g).errors.length>0&&n.push(`${c}: invalid JSON; dependency resolution is partial`);const m=(0,h.findFileRefs)((0,h.sourceOf)(g),c,(0,d.versionDate)(this.config.plVersion),{courseRoot:this.courseRoot,fileDir:f},t=>t.files===`questions/**/${e}`);this.dynamicHtmlDiagnostics(c,g,n);for(const e of m)if(e.value.includes("{{"))n.push(`${c}: dynamic reference cannot be resolved (${e.spec.id})`);else{if("question"===e.spec.target){if(!e.target){n.push(`${c}: ${e.problem??`invalid template ${e.value}`} (${e.spec.id})`);continue}const t=this.relativeOrNull(e.target),r=t?[...this.questions.values()].find(e=>e.directory===t)?.qid??e.value:e.value;if(s.add(r),o.has(r)){n.push(`Template cycle: ${[...o,r].join(" -> ")}`);continue}await i(r,"template-local");continue}await this.resolveReference(e,c,t,n,r)}}};await i(e,"question-local");const u=[...t].map(([e,t])=>({path:e,provenance:t})).sort((e,t)=>e.path.localeCompare(t.path)||e.provenance.localeCompare(t.provenance)),c=[...new Set(n)].sort();return{files:u,complete:0===c.length,diagnostics:c,dependencies:O(r),templates:[...s].sort()}}async resolveReference(e,t,n,r,s){if(!e.target)return void r.push(`${t}: ${e.problem??`invalid reference ${e.value}`} (${e.spec.id})`);const o=this.relativeOrNull(e.target);if(!o)return void r.push(`${t}: reference escapes the course (${e.spec.id})`);if(this.ignored(o))return;let i;s.push({kind:"fileOrDir"===e.spec.target?"directory":"file",path:o});try{i=await a.promises.lstat(e.target)}catch{return void r.push(`${t}: file not found: ${o} (${e.spec.id})`)}if(!i.isSymbolicLink())return i.isDirectory()?"fileOrDir"!==e.spec.target?void r.push(`${t}: expected a file: ${o} (${e.spec.id})`):void await this.collectDirectory(e.target,"course-level",n,r):void(i.isFile()?n.set(o,this.provenanceFor(o,n.get(o))):r.push(`${t}: unsupported file type: ${o} (${e.spec.id})`));await this.collectSymlink(e.target,"course-level",n,r)}async collectDirectory(e,t,n,r){let s;try{s=await a.promises.readdir(e,{withFileTypes:!0})}catch(t){const n=this.relativeOrNull(e)??"outside course";return void r.push(`${n}: could not read directory: ${this.errorMessage(t)}`)}s.sort((e,t)=>e.name.localeCompare(t.name));for(const o of s){const s=l.join(e,o.name),i=this.relativeOrNull(s);i&&!this.ignored(i)&&(o.isDirectory()?await this.collectDirectory(s,t,n,r):o.isFile()?n.set(i,this.provenanceFor(i,n.get(i),t)):o.isSymbolicLink()&&await this.collectSymlink(s,t,n,r))}}async collectSymlink(e,t,n,r){const s=this.relative(e);let o;try{o=await a.promises.realpath(e)}catch(e){return void r.push(`${s}: broken symbolic link: ${this.errorMessage(e)}`)}if(!this.relativeOrNull(o))return void r.push(`${s}: symbolic link escapes the course`);const i=await a.promises.stat(o);i.isDirectory()?r.push(`${s}: symbolic-link directories are not followed`):i.isFile()?n.set(s,this.provenanceFor(s,n.get(s),t)):r.push(`${s}: symbolic link has an unsupported target`)}dynamicHtmlDiagnostics(e,t,n){for(const r of g.refSpecs)if("html"===r.source&&(0,c.matchesGlob)(e,r.files))for(const s of(0,f.findElements)(t,r.html.tag)){const t=s.attributes.get(r.html.attr)?.value,o=s.attributes.get("directory")?.value,i=s.attributes.get("type")?.value;[t,o,i].some(e=>e?.includes("{{"))&&n.push(`${e}: dynamic reference cannot be resolved (${r.id})`)}}templateDescendants(){const e=new Map;for(const[t,n]of this.resolutions)for(const r of n.templates){const n=e.get(r)??new Set;n.add(t),e.set(r,n)}const t=new Map,n=new Set([...this.questions.keys(),...e.keys()]);for(const r of n){const n=new Set,s=[...e.get(r)??[]];for(;s.length>0;){const t=s.pop();n.has(t)||(n.add(t),s.push(...e.get(t)??[]))}t.set(r,n)}return t}setQuestions(e){const t=new Map;for(const n of e){const e=b(n.directory);if(!n.qid||!e||!e.startsWith("questions/"))throw new Error(`Invalid question descriptor: ${n.qid||"<empty>"}`);if(t.has(n.qid))throw new Error(`Duplicate QID: ${n.qid}`);t.set(n.qid,{qid:n.qid,directory:e})}this.questions=t}publicResolutions(e){const t=new Map;for(const n of e){const e=this.resolutions.get(n);e&&t.set(n,{files:e.files.map(e=>({...e})),complete:e.complete,diagnostics:[...e.diagnostics]})}return t}ignored(e){const t=e.split("/");return t.some(e=>m.has(e))||y.test(t.at(-1)??"")||(0,u.isExcluded)(this.config,this.absolute(e))}provenanceFor(e,t,n="course-level"){return"question-local"===t||"question-local"===n?"question-local":"template-local"===t||"template-local"===n?"template-local":n}absolute(e){return l.join(this.courseRoot,...e.split("/"))}relative(e){return(0,c.courseRelativePath)(this.courseRoot,e)}relativeOrNull(e){return b(this.relative(l.resolve(e)))}errorMessage(e){return function(e){return e instanceof Error?e.message:String(e)}(e).split(this.courseRoot).join(".")}}function b(e){if(e.includes("\\"))return null;const t=l.posix.normalize(e);return!t||t!==e||"."===t||".."===t||t.startsWith("../")||l.posix.isAbsolute(t)?null:t}function _(e,t){return t===e||t.startsWith(`${e}/`)}function O(e){const t=new Set;return e.filter(e=>{const n=`${e.kind}:${e.path}`;return!t.has(n)&&(t.add(n),!0)})}},634(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.questionFilePathsFromId=function(e){const t=l.join(e.courseId,"questions",e.localId),n=l.join(t,"info.json"),r=l.join(t,"question.html"),s=l.join(t,"server.py");return{dir:t,infoJson:n,questionHtml:r,serverPy:s,strict(){const e={};return a.existsSync(t)&&(e.dir=t),a.existsSync(n)&&(e.infoJson=n),a.existsSync(r)&&(e.questionHtml=r),a.existsSync(s)&&(e.serverPy=s),e}}};const a=i(n(24)),l=i(n(760))},835(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.baseDir=function(e,t){switch(e){case"fileDir":return t.fileDir;case"clientFilesQuestion":return a.join(t.fileDir,"clientFilesQuestion");case"clientFilesCourse":return a.join(t.courseRoot,"clientFilesCourse");case"serverFilesCourse":return a.join(t.courseRoot,"serverFilesCourse");case"courseExtensions":return a.join(t.courseRoot,"elementExtensions");case"questions":return a.join(t.courseRoot,"questions")}},t.inside=function(e,t,n=!1){const r=a.resolve(e,t),s=a.relative(e,r);return""===s?n?r:null:s.startsWith("..")||a.isAbsolute(s)?null:r};const a=i(n(760))},392(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.sourceOf=function(e){let t;return{text:e,get doc(){return t??=(0,f.parseJsonDoc)(e)}}},t.holdsReferences=function(e){return h.refSpecs.some(t=>(0,u.matchesGlob)(e,t.files))},t.findFileRefs=function(e,t,n,r,s=()=>!0){const o=[];for(const i of h.refSpecs)if(s(i)&&(0,u.matchesGlob)(t,i.files)&&(0,p.matchesRange)(i.range,n))if("json"===i.source){const t=e.doc.tree;t&&m(i,i.json.json,i.json.base,t,r,o)}else for(const t of(0,c.findElements)(e.text,i.html.tag)){const e=v(i,t,r);e&&o.push(e)}return o.sort((e,t)=>e.startOffset-t.startOffset)},t.fileRefAt=function(e,t){return e.find(e=>e.startOffset-1<=t&&t<=e.endOffset+1)},t.openTarget=function(e){if(null===e.target||"question"!==e.spec.target)return e.target;const t=l.join(e.target,"question.html");return a.existsSync(t)?t:l.join(e.target,"info.json")};const a=i(n(24)),l=i(n(760)),u=n(554),c=n(13),f=n(758),p=n(702),d=n(835),h=n(633);function g(e,t){return"*"===e||(e instanceof RegExp?"string"==typeof t&&e.test(t):e===String(t))}function m(e,t,n,r,s,o,i=0){if(i===t.length){if("string"===r.type){const t=r.value;o.push({spec:e,value:t,startOffset:r.offset+1,endOffset:r.offset+r.length-1,...b(e,t,(0,d.baseDir)(n,s))})}return}const a=t[i];if("object"===r.type)for(const l of r.children??[]){const[r,u]=l.children??[];r&&u&&g(a,r.value)&&m(e,t,n,u,s,o,i+1)}else"array"===r.type&&(r.children??[]).forEach((r,l)=>{g(a,l)&&m(e,t,n,r,s,o,i+1)})}const y=e=>e.includes("{{");function v(e,t,n){const{attr:r,directory:s,base:o,staticOnly:i}=e.html,a=t.attributes.get(r),l=t.attributes.get("type")?.value??"static",u=t.attributes.get("directory"),c=u?.value??s?.default??"";if(!a||[a.value,l,c].some(y)||i&&"static"!==l.trim().toLowerCase())return;const f={spec:e,value:a.value,startOffset:a.startOffset,endOffset:a.endOffset};let p;if(s?Object.hasOwn(s.named,c)?p=(0,d.baseDir)(s.named[c],n):s.relative&&(p=(0,d.inside)(n.fileDir,c,!0)??void 0):p=(0,d.baseDir)(o??"fileDir",n),!p){const t=Object.keys(s?.named??{}).map(e=>`"${e}"`),n=t.length>2?`one of ${t.join(", ")}`:t.join(" or ");return{...f,value:c,startOffset:u?.startOffset??f.startOffset,endOffset:u?.endOffset??f.endOffset,target:null,problem:s?.relative?`${e.html.tag} directory "${c}" must be inside the question directory`:`invalid ${e.html.tag} directory "${c}": must be ${n}`}}return{...f,...b(e,a.value,p,c)}}function b(e,t,n,r=("json"===e.source?e.json.base:"")){const s=(0,d.inside)(n,t);return s?{target:s}:{target:null,problem:`"${t}" must be a path inside ${"question"===e.target?"questions/":"fileDir"===r||"."===r||""===r?"its directory":`${r}/`}`}}},633(e,t,n){t.refSpecs=void 0;const r=n(554),s=(e,t)=>({source:"json",json:{json:e,base:t}}),o=e=>({source:"html",html:e}),i=/^clientFilesCourse(Styles|Scripts)$/,a=[r.courseFiles.element,r.courseFiles.elementExtension],l={default:".",named:{serverFilesCourse:"serverFilesCourse",clientFilesCourse:"clientFilesCourse"},relative:!0},u={default:"clientFilesQuestion",named:{clientFilesQuestion:"clientFilesQuestion",clientFilesCourse:"clientFilesCourse"}};t.refSpecs=[{id:"question.dependencies.clientFilesCourse",range:"*",files:r.courseFiles.question,rule:"client-files-course-exist",target:"file",...s(["dependencies",i,"*"],"clientFilesCourse")},{id:"question.dependencies.clientFilesQuestion",range:"*",files:r.courseFiles.question,rule:"client-files-question-exist",target:"file",...s(["dependencies",/^clientFilesQuestion(Styles|Scripts)$/,"*"],"clientFilesQuestion")},{id:"question.clientFiles",range:"*",files:r.courseFiles.question,rule:"client-files-question-exist",target:"file",severity:"warning",...s(["clientFiles","*"],"fileDir")},{id:"question.externalGradingOptions.serverFilesCourse",range:"*",files:r.courseFiles.question,rule:"server-files-course-exist",target:"fileOrDir",...s(["externalGradingOptions","serverFilesCourse","*"],"serverFilesCourse")},{id:"question.template",range:"*",files:r.courseFiles.question,rule:"question-exists",target:"question",...s(["template"],"questions")},...a.flatMap(e=>[{id:`${e}.controller`,range:"*",files:e,rule:"element-files-exist",target:"file",...s(["controller"],"fileDir")},{id:`${e}.dependencies.element`,range:"*",files:e,rule:"element-files-exist",target:"file",...s(["dependencies",/^(element|extension)(Styles|Scripts)$/,"*"],"fileDir")},{id:`${e}.dynamicDependencies.element`,range:"*",files:e,rule:"element-files-exist",target:"file",...s(["dynamicDependencies",/^(element|extension)Scripts$/,"*"],"fileDir")},{id:`${e}.dependencies.clientFilesCourse`,range:"*",files:e,rule:"client-files-course-exist",target:"file",...s(["dependencies",i,"*"],"clientFilesCourse")},{id:`${e}.dynamicDependencies.clientFilesCourse`,range:"*",files:e,rule:"client-files-course-exist",target:"file",...s(["dynamicDependencies","clientFilesCourseScripts","*"],"clientFilesCourse")}]),{id:"assessment.question",range:"*",files:r.courseFiles.assessment,rule:"question-exists",target:"question",...s(["zones","*","questions","*","id"],"questions")},{id:"assessment.alternative",range:"*",files:r.courseFiles.assessment,rule:"question-exists",target:"question",...s(["zones","*","questions","*","alternatives","*","id"],"questions")},{id:"pl-figure",range:"*",files:r.questionHtml,rule:"pl-figure-file-exist",target:"file",...o({tag:"pl-figure",attr:"file-name",directory:u,staticOnly:!0})},{id:"pl-file-download",range:"*",files:r.questionHtml,rule:"pl-file-download-file-exist",target:"file",...o({tag:"pl-file-download",attr:"file-name",directory:u,staticOnly:!0})},{id:"pl-code",range:"*",files:r.questionHtml,rule:"pl-code-file-exist",target:"fileOrDir",...o({tag:"pl-code",attr:"source-file-name",directory:l})},{id:"pl-file-editor",range:"*",files:r.questionHtml,rule:"pl-file-editor-file-exist",target:"file",...o({tag:"pl-file-editor",attr:"source-file-name",directory:l})},{id:"pl-graph",range:"*",files:r.questionHtml,rule:"pl-graph-file-exist",target:"file",...o({tag:"pl-graph",attr:"source-file-name",directory:l})},{id:"pl-rich-text-editor",range:"*",files:r.questionHtml,rule:"pl-rich-text-editor-file-exist",target:"file",...o({tag:"pl-rich-text-editor",attr:"source-file-name",directory:l})},{id:"pl-excalidraw",range:"*",files:r.questionHtml,rule:"pl-excalidraw-file-exist",target:"file",...o({tag:"pl-excalidraw",attr:"source-file-name",directory:{default:".",named:{".":"fileDir",clientFilesQuestion:"clientFilesQuestion",clientFilesCourse:"clientFilesCourse",serverFilesCourse:"serverFilesCourse"}}})},{id:"pl-xss-safe",range:"*",files:r.questionHtml,rule:"pl-xss-safe-file-exist",target:"fileOrDir",...o({tag:"pl-xss-safe",attr:"source-file-name",base:"fileDir"})},{id:"pl-template",range:"*",files:r.questionHtml,rule:"pl-template-file-exist",target:"file",...o({tag:"pl-template",attr:"file-name",directory:{default:"serverFilesCourse",named:{question:"fileDir",clientFilesQuestion:"clientFilesQuestion",clientFilesCourse:"clientFilesCourse",serverFilesCourse:"serverFilesCourse",courseExtensions:"courseExtensions"}}})}]},369(e,t,n){var r,s=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var s=Object.getOwnPropertyDescriptor(t,n);s&&!("get"in s?!t.__esModule:s.writable||s.configurable)||(s={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,s)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),i=0;i<n.length;i++)"default"!==n[i]&&s(t,e,n[i]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0}),t.duplicateQuestionIds=t.jsonSyntax=void 0,t.schemaRule=function(e){return async({doc:t,schemas:n,plVersion:r})=>{if(t.errors.length>0||void 0===t.value)return[];const s=await n.get(e,r);if(!s||s.validate(t.value))return[];const o=new Set,i=[];for(const e of s.validate.errors??[]){let n=(0,u.pointerToPath)(e.instancePath),r=`${e.instancePath||"root"} ${e.message}`,a="error";if("additionalProperties"===e.keyword){const t=String(e.params.additionalProperty);n=[...n,t],r=`unknown property "${t}"`,a="warning"}else"enum"===e.keyword&&(r+=`: ${e.params.allowedValues.map(e=>JSON.stringify(e)).join(", ")}`);const l=`${n.join("/")}\0${r}`;if(o.has(l))continue;o.add(l);let c=(0,u.rangeOf)(t,n);"required"===e.keyword&&(c={startOffset:c.startOffset,endOffset:c.startOffset+1}),i.push({...c,message:`${r} (PL schema ${s.label})`,severity:a})}return i}},t.referencesExist=function(e){return t=>{const n=[];for(const r of p(t,t=>t.rule===e)){const e={startOffset:r.startOffset,endOffset:r.endOffset},s=r.spec.severity??"error";if(null===r.target)n.push({...e,message:r.problem??`invalid path "${r.value}"`,severity:"error"});else if("question"===r.spec.target){const o=h(r,t.courseRoot);o&&n.push({...e,...o,severity:s})}else d(r.target,r.spec.target)||n.push({...e,message:`${"file"===r.spec.target?"file":"file or directory"} not found: ${l.relative(t.courseRoot,r.target)}`,severity:s,related:[{path:r.target,message:"Expected location of file"}]})}return n}};const a=i(n(24)),l=i(n(760)),u=n(758),c=n(634),f=n(392);function p(e,t){const{relPath:n,plDate:r,courseRoot:s,filePath:o}=e;return(0,f.findFileRefs)(e,n,r,{courseRoot:s,fileDir:l.dirname(o)},t)}function d(e,t){const n=a.statSync(e,{throwIfNoEntry:!1});return"file"===t?!!n?.isFile():!!n}function h(e,t){const n=(0,c.questionFilePathsFromId)({courseId:t,localId:e.value}),r=n.strict();if(!r.dir)return{message:`missing question: expected question directory ${n.dir}`,related:[{path:n.infoJson,message:"Expected location of info.json"}]};if(!r.infoJson)return{message:"incomplete question: missing required JSON file",related:[{path:n.infoJson,message:"Expected location of info.json"}]};if(!r.questionHtml){const e=(0,u.parseJsonc)(a.readFileSync(r.infoJson,"utf8"));if("string"!=typeof e?.options?.text)return{message:"incomplete question: missing required html file",related:[{path:n.questionHtml,message:"Expected location of question.html"}]}}}t.jsonSyntax=({text:e})=>{const t=new Set,n=[];for(const r of(0,u.strictJsonErrors)(e)){if(t.has(r.offset))continue;t.add(r.offset);const s=/,\s*$/.exec(e.slice(0,r.offset)),o=s&&"InvalidCommentToken"!==r.message?{startOffset:s.index,endOffset:s.index+1}:{startOffset:r.offset,endOffset:r.offset+Math.max(1,r.length)};n.push({...o,message:"InvalidCommentToken"===r.message?"comments are not allowed: PrairieLearn reads course JSON with JSON.parse":s?"trailing commas are not allowed: PrairieLearn reads course JSON with JSON.parse":`invalid JSON: ${r.message}`,severity:"error"})}return n},t.duplicateQuestionIds=e=>{const t=new Map;for(const n of p(e,e=>"question"===e.target))t.set(n.value,[...t.get(n.value)??[],n]);return[...t.values()].filter(e=>e.length>1).flatMap(e=>e.map(t=>({startOffset:t.startOffset,endOffset:t.endOffset,message:`Duplicate question ID: "${t.value}" appears ${e.length} times`,severity:"warning"})))}},731(e,t,n){t.aO=void 0,t.isRuleId=function(e){return Object.hasOwn(t.aO,e)};const r=n(554),s=n(633),o=n(369),i={"json-syntax":Object.values(r.courseFiles).map(e=>["*",e,o.jsonSyntax]),schema:[["*",r.courseFiles.course,(0,o.schemaRule)("infoCourse")],["*",r.courseFiles.courseInstance,(0,o.schemaRule)("infoCourseInstance")],["*",r.courseFiles.assessment,(0,o.schemaRule)("infoAssessment")],["*",r.courseFiles.question,(0,o.schemaRule)("infoQuestion")],["*",r.courseFiles.element,(0,o.schemaRule)("infoElementCourse")],["*",r.courseFiles.elementExtension,(0,o.schemaRule)("infoElementExtension")]],"duplicate-question-id":[["*",r.courseFiles.assessment,o.duplicateQuestionIds]]};t.aO={...i,...function(){const e={};for(const t of s.refSpecs){const n=e[t.rule]??=[];n.some(([,e])=>e===t.files)||n.push(["*",t.files,(0,o.referencesExist)(t.rule)])}return e}()}},24(e){e.exports=t("node:fs")},161(e){e.exports=t("node:os")},760(e){e.exports=t("node:path")}};const r={};function s(e){const t=r[e];if(void 0!==t)return t.exports;const o=r[e]={exports:{}};return n[e].call(o.exports,o,o.exports,s),o.exports}s.d=(e,t)=>{if(Array.isArray(t))for(var n=0;n<t.length;){var r=t[n++],o=t[n++];s.o(e,r)?0===o&&n++:0===o?Object.defineProperty(e,r,{enumerable:!0,value:t[n++]}):Object.defineProperty(e,r,{enumerable:!0,get:o})}else for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.o=(e,t)=>Object.hasOwn(e,t);let o={};(()=>{let e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.createQuestionFileResolver=void 0;var t=s(208);Object.defineProperty(e,"createQuestionFileResolver",{enumerable:!0,get:function(){return t.createQuestionFileResolver}})})();const i=o.__esModule,a=o.createQuestionFileResolver;export{i as __esModule,a as createQuestionFileResolver,o as default};
@@ -0,0 +1 @@
1
+ export { createQuestionFileResolver, type QuestionFileDescriptor, type QuestionFileProvenance, type QuestionFileResolution, type QuestionFileResolutionUpdate, type QuestionFileResolver, type QuestionFileResolverOptions, type ResolvedQuestionFile, } from "./core/questionFileResolver";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybelblue/prairielearn-navigator",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Check PrairieLearn courses for missing, incomplete, and duplicate questions",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,11 +8,18 @@
8
8
  "url": "git+https://github.com/SybelBlue/prairielearn-navigator.git",
9
9
  "directory": "packages/cli"
10
10
  },
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/core/questionFileResolver.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
11
18
  "bin": {
12
- "prairielearn-navigator": "dist/cli.js"
19
+ "prairielearn-navigator": "dist/cli.cjs"
13
20
  },
14
21
  "files": [
15
- "dist/cli.js"
22
+ "dist"
16
23
  ],
17
24
  "engines": {
18
25
  "node": ">=22"
@@ -27,7 +34,7 @@
27
34
  "access": "public"
28
35
  },
29
36
  "scripts": {
30
- "prepack": "npm --prefix ../.. run build:cli && cp ../../LICENSE .",
37
+ "prepack": "npm --prefix ../.. run build:npm && cp ../../LICENSE .",
31
38
  "prepublishOnly": "npm --prefix ../.. run lint && npm --prefix ../.. run test:cli"
32
39
  }
33
40
  }
File without changes