Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 30x 30x 30x 30x 30x 30x 30x 30x 30x 29x 29x 29x 1x 30x 22x 22x 22x 21x 12x 30x 30x 30x 30x | import { z } from "zod";
import {
examplesManifestSchema,
manifestExampleSchema,
validateManifestReferences,
type ExamplesManifestCore,
} from "../examples/examples-schema";
const SAFE_ID = /^[a-z][a-z0-9-]{0,63}$/;
const v2ManifestExampleSchema = manifestExampleSchema.strict();
export const tutorDescriptorSchema = z.object({
manifest: z.literal("tutor/manifest.yaml"),
}).strict();
const v2ManifestSchema = z.object({
schemaVersion: z.literal(2),
repository: z.string().max(256).optional(),
ref: z.string().max(128).optional(),
examples: z.array(v2ManifestExampleSchema).max(1000),
tutor: tutorDescriptorSchema.optional(),
}).strict();
export const courseContentManifestSchema = z.union([examplesManifestSchema, v2ManifestSchema]);
export type TutorDescriptor = z.infer<typeof tutorDescriptorSchema>;
export const courseContentTopicEntrySchema = z.object({
id: z.string().regex(SAFE_ID),
path: z.string().regex(/^tutor\/topics\/[a-z][a-z0-9-]{0,63}\.yaml$/),
sha256: z.string().regex(/^[a-f0-9]{64}$/i),
}).strict();
export const courseContentStrategyEntrySchema = z.object({
id: z.string().regex(SAFE_ID),
path: z.string().regex(/^tutor\/strategies\/[a-z][a-z0-9-]{0,63}\.yaml$/),
sha256: z.string().regex(/^[a-f0-9]{64}$/i),
}).strict();
const tutorManifestV1Schema = z.object({
schemaVersion: z.literal(1),
defaultStrategy: z.string().regex(SAFE_ID).optional(),
topics: z.array(courseContentTopicEntrySchema).max(64),
strategies: z.array(courseContentStrategyEntrySchema).max(64),
}).strict();
const phaseStrategiesSchema = z.object({
deepen: z.string().regex(SAFE_ID).optional(),
expand: z.string().regex(SAFE_ID).optional(),
}).strict();
const tutorManifestV2Schema = z.object({
schemaVersion: z.literal(2),
defaultStrategy: z.string().regex(SAFE_ID).optional(),
topics: z.array(courseContentTopicEntrySchema).max(64),
strategies: z.array(courseContentStrategyEntrySchema).max(64),
phaseStrategies: phaseStrategiesSchema.optional(),
}).strict();
export const courseContentTutorManifestSchema = z.union([tutorManifestV1Schema, tutorManifestV2Schema]);
export type CourseContentTutorManifest = z.infer<typeof courseContentTutorManifestSchema>;
type InvalidCapability = { readonly status: "invalid"; readonly reason: string };
export type CourseContentValidation = {
readonly examples:
| { readonly status: "valid"; readonly manifest: ExamplesManifestCore }
| InvalidCapability;
readonly tutor:
| { readonly status: "absent" }
| {
readonly status: "valid";
readonly descriptor: TutorDescriptor;
}
| InvalidCapability;
};
export function parseCourseContentManifest(input: unknown): CourseContentValidation {
const raw = asRecord(input);
const rawExamples = Array.isArray(raw?.examples) ? raw.examples : undefined;
const examples = parseExamplesCapability(input, raw, rawExamples);
const hasTutorRoot = raw !== undefined && hasOwn(raw, "tutor");
const tutor = parseTutorCapability(raw, hasTutorRoot);
return { examples, tutor };
}
function parseExamplesCapability(
input: unknown,
raw: Record<string, unknown> | undefined,
rawExamples: unknown[] | undefined,
): CourseContentValidation["examples"] {
const coreInput = raw === undefined ? input : withoutTutorData(raw, rawExamples);
const coreParsed = courseContentCoreSchema.safeParse(coreInput);
if (!coreParsed.success) return { status: "invalid", reason: "External Examples manifest is invalid" };
try {
validateManifestReferences(coreParsed.data);
return { status: "valid", manifest: coreParsed.data };
} catch {
return { status: "invalid", reason: "External Examples snapshot is invalid" };
}
}
function parseTutorCapability(
raw: Record<string, unknown> | undefined,
hasTutorCapability: boolean,
): CourseContentValidation["tutor"] {
if (!hasTutorCapability) return { status: "absent" };
Iif (raw?.schemaVersion !== 2) {
return { status: "invalid", reason: "Tutor capability requires schema version 2" };
}
const descriptor = tutorDescriptorSchema.safeParse(raw.tutor);
if (!descriptor.success) return { status: "invalid", reason: "Tutor descriptor is invalid" };
return { status: "valid", descriptor: descriptor.data };
}
const courseContentCoreSchema = z.union([
examplesManifestSchema,
z.object({
schemaVersion: z.literal(2),
repository: z.string().max(256).optional(),
ref: z.string().max(128).optional(),
examples: z.array(v2ManifestExampleSchema).max(1000),
}).strict(),
]);
function asRecord(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: undefined;
}
function hasOwn(value: Record<string, unknown>, key: string): boolean {
return Reflect.getOwnPropertyDescriptor(value, key) !== undefined;
}
function withoutTutorData(raw: Record<string, unknown>, _rawExamples: unknown[] | undefined): Record<string, unknown> {
const { tutor: _tutor, ...core } = raw;
return core;
}
|