@nuasite/cli 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -47,6 +47,39 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
+ // ../cms-core/src/asset-paths.ts
51
+ function assetBaseDir(file) {
52
+ const lastSlash = file.lastIndexOf("/");
53
+ return lastSlash >= 0 ? file.slice(0, lastSlash) : "";
54
+ }
55
+ function normalizeSegments(base, rel) {
56
+ const out = base.slice();
57
+ for (const seg of rel.replace(/^\/+/, "").split("/")) {
58
+ if (seg === "" || seg === ".")
59
+ continue;
60
+ if (seg === "..") {
61
+ if (out.length === 0)
62
+ return null;
63
+ out.pop();
64
+ continue;
65
+ }
66
+ out.push(seg);
67
+ }
68
+ return out.join("/");
69
+ }
70
+ function resolveAssetCandidates(baseDir, rel) {
71
+ if (rel.startsWith("/")) {
72
+ const fromRoot = normalizeSegments([], rel);
73
+ if (fromRoot === null || fromRoot === "")
74
+ return [];
75
+ return [`public/${fromRoot}`, fromRoot];
76
+ }
77
+ if (baseDir === undefined)
78
+ return [];
79
+ const joined = normalizeSegments(baseDir.split("/").filter(Boolean), rel);
80
+ return joined === null ? [] : [joined];
81
+ }
82
+
50
83
  // ../../node_modules/yaml/dist/nodes/identity.js
51
84
  var require_identity = __commonJS((exports) => {
52
85
  var ALIAS = Symbol.for("yaml.alias");
@@ -7011,6 +7044,504 @@ var require_dist = __commonJS((exports) => {
7011
7044
  exports.visitAsync = visit.visitAsync;
7012
7045
  });
7013
7046
 
7047
+ // ../cms-core/src/check-entries.ts
7048
+ import path from "path";
7049
+ function isPlainObject(value) {
7050
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
7051
+ }
7052
+ async function collectEntryFiles(fs, collection, base) {
7053
+ const pattern = collection.loaderPattern ?? "**/*.{md,mdx,json,yaml,yml}";
7054
+ const files = await fs.glob(`${base}/${pattern}`);
7055
+ return files.filter((file) => !path.basename(file).startsWith("_")).sort();
7056
+ }
7057
+ function parseEntry(file, raw) {
7058
+ const extension = path.extname(file);
7059
+ if (DATA_EXTENSIONS.has(extension)) {
7060
+ try {
7061
+ const parsed = extension === ".json" ? JSON.parse(raw) : import_yaml.default.parse(raw);
7062
+ return { frontmatter: isPlainObject(parsed) ? parsed : {} };
7063
+ } catch (error) {
7064
+ return { error: error instanceof Error ? error.message.split(`
7065
+ `)[0] : String(error) };
7066
+ }
7067
+ }
7068
+ const trimmed = raw.trimStart();
7069
+ if (!trimmed.startsWith("---"))
7070
+ return { frontmatter: {} };
7071
+ const lines = trimmed.split(`
7072
+ `);
7073
+ const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
7074
+ if (end === -1)
7075
+ return { error: "frontmatter is never closed (missing the second `---`)" };
7076
+ try {
7077
+ const parsed = import_yaml.default.parse(lines.slice(1, end).join(`
7078
+ `).trim());
7079
+ return { frontmatter: isPlainObject(parsed) ? parsed : {} };
7080
+ } catch (error) {
7081
+ return { error: error instanceof Error ? error.message.split(`
7082
+ `)[0] : String(error) };
7083
+ }
7084
+ }
7085
+ function collectionKind(collection, loaderPattern) {
7086
+ const extensions = collection.entries.map((entry) => path.extname(entry.file));
7087
+ if (extensions.length > 0)
7088
+ return extensions.some((extension) => extension === ".md" || extension === ".mdx") ? "markdown" : "data";
7089
+ return loaderPattern === undefined || /\bmdx?\b/.test(loaderPattern) ? "markdown" : "data";
7090
+ }
7091
+ async function loadCollections(fs, config) {
7092
+ const loaded = new Map;
7093
+ for (const [name, collection] of config) {
7094
+ const base = normalizeBase(collection.loaderBase ?? `src/content/${name}`);
7095
+ if (!await fs.exists(base)) {
7096
+ loaded.set(name, { name, base, exists: false, entries: [] });
7097
+ continue;
7098
+ }
7099
+ const entries = [];
7100
+ for (const file of await collectEntryFiles(fs, collection, base)) {
7101
+ const parsed = parseEntry(file, await fs.readFile(file));
7102
+ entries.push({
7103
+ file,
7104
+ stem: entryStem(file),
7105
+ ..."error" in parsed ? { parseError: parsed.error } : { frontmatter: parsed.frontmatter }
7106
+ });
7107
+ }
7108
+ loaded.set(name, { name, base, exists: true, entries });
7109
+ }
7110
+ return loaded;
7111
+ }
7112
+ var import_yaml, DATA_EXTENSIONS, normalizeBase = (base) => path.normalize(base).replace(/^\.\//, "").replace(/\/+$/, ""), entryStem = (file) => path.basename(file).replace(/\.(md|mdx|json|ya?ml)$/, "");
7113
+ var init_check_entries = __esm(() => {
7114
+ import_yaml = __toESM(require_dist(), 1);
7115
+ DATA_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
7116
+ });
7117
+
7118
+ // ../cms-core/src/schema-port.ts
7119
+ function describeIssue(issue) {
7120
+ if (issue.path.length === 0)
7121
+ return { message: issue.message };
7122
+ const field = issue.path.join(".");
7123
+ return { field, message: `${field}: ${issue.message}` };
7124
+ }
7125
+ function schemaFor(schemas, collection) {
7126
+ return Object.hasOwn(schemas, collection) ? schemas[collection] : undefined;
7127
+ }
7128
+
7129
+ // ../cms-core/src/check-live.ts
7130
+ async function checkAgainstSchemas(input) {
7131
+ const findings = [];
7132
+ for (const collection of input.collections.values()) {
7133
+ const schema = schemaFor(input.schemas, collection.name);
7134
+ if (!schema)
7135
+ continue;
7136
+ for (const entry of collection.entries) {
7137
+ if (entry.frontmatter === undefined)
7138
+ continue;
7139
+ let result;
7140
+ try {
7141
+ result = await schema.safeParse(entry.frontmatter);
7142
+ } catch (error) {
7143
+ findings.push({
7144
+ severity: "error",
7145
+ code: "entry/schema-rejected",
7146
+ file: entry.file,
7147
+ message: `The collection schema threw while validating this entry: ${error instanceof Error ? error.message : String(error)}`
7148
+ });
7149
+ continue;
7150
+ }
7151
+ if (result.success)
7152
+ continue;
7153
+ for (const issue of result.issues) {
7154
+ findings.push({ severity: "error", code: "entry/schema-rejected", file: entry.file, ...describeIssue(issue) });
7155
+ }
7156
+ }
7157
+ }
7158
+ return findings;
7159
+ }
7160
+ var init_check_live = () => {};
7161
+
7162
+ // ../cms-core/src/schema-probe.ts
7163
+ function mutated(point, mutation) {
7164
+ let copy;
7165
+ try {
7166
+ copy = structuredClone(point.record);
7167
+ } catch {
7168
+ return null;
7169
+ }
7170
+ let parent = copy;
7171
+ for (const segment of point.path.slice(0, -1)) {
7172
+ if (!isContainer(parent))
7173
+ return null;
7174
+ parent = parent[segment];
7175
+ }
7176
+ const last = point.path.at(-1);
7177
+ if (last === undefined || !isContainer(parent))
7178
+ return null;
7179
+ if ("remove" in mutation)
7180
+ delete parent[last];
7181
+ else
7182
+ parent[last] = mutation.set;
7183
+ return copy;
7184
+ }
7185
+ async function probe(schema, point, mutation) {
7186
+ const value = mutated(point, mutation);
7187
+ if (value === null)
7188
+ return "unknown";
7189
+ try {
7190
+ const result = await schema.safeParse(value);
7191
+ if (result.success)
7192
+ return "accepted";
7193
+ const here = result.issues.some((issue) => point.path.every((segment, index) => issue.path[index] === segment));
7194
+ return here ? "rejected-here" : "rejected-elsewhere";
7195
+ } catch {
7196
+ return "unknown";
7197
+ }
7198
+ }
7199
+ async function governsPath(schema, point) {
7200
+ for (const value of GOVERNANCE_PROBES) {
7201
+ if (await probe(schema, point, { set: value }) === "rejected-here")
7202
+ return true;
7203
+ }
7204
+ return false;
7205
+ }
7206
+ async function acceptsMissing(schema, point) {
7207
+ return await probe(schema, point, { remove: true }) === "accepted";
7208
+ }
7209
+ async function rejectsValueAt(schema, point, value) {
7210
+ return await probe(schema, point, { set: value }) === "rejected-here";
7211
+ }
7212
+ async function firstAcceptedEntry(schema, entries) {
7213
+ for (const entry of entries) {
7214
+ if (!entry.frontmatter)
7215
+ continue;
7216
+ try {
7217
+ const result = await schema.safeParse(entry.frontmatter);
7218
+ if (result.success)
7219
+ return entry.frontmatter;
7220
+ } catch {}
7221
+ }
7222
+ return;
7223
+ }
7224
+ var pointAt = (seed, field) => ({ record: seed, path: [field] }), GOVERNANCE_PROBES, isContainer = (value) => typeof value === "object" && value !== null;
7225
+ var init_schema_probe = __esm(() => {
7226
+ GOVERNANCE_PROBES = [{ __nuaProbe: true }, ["__nua_probe__"], 918273.456, null];
7227
+ });
7228
+
7229
+ // ../cms-core/src/check-shape.ts
7230
+ async function requiredDrift(name, field, seed, schemas) {
7231
+ const schema = schemaFor(schemas, name);
7232
+ if (!schema || !field.required || field.layout?.hidden)
7233
+ return null;
7234
+ const point = pointAt(seed, field.name);
7235
+ if (!await acceptsMissing(schema, point))
7236
+ return null;
7237
+ if (!await governsPath(schema, point))
7238
+ return null;
7239
+ return {
7240
+ severity: "warning",
7241
+ code: "cms/required-drift",
7242
+ file: CONFIG_FILE,
7243
+ field: `${name}.${field.name}`,
7244
+ message: `The editor treats "${name}.${field.name}" as required and refuses to save it blank, but the schema accepts an entry without it.`,
7245
+ hint: "Either the schema should require it, or the config should show the parser that it is optional. Wrapping a field schema in a helper call (`field.image(z.string().optional())`) or reaching it through an import hides the `.optional()` from the parser, which then reads the field as required \u2014 put the wrapper outside, as `field.image(z.string()).optional()`."
7246
+ };
7247
+ }
7248
+ async function degradedField(name, field, seed, input) {
7249
+ const schema = schemaFor(input.schemas, name);
7250
+ if (!schema || field.type !== undefined || field.layout?.hidden)
7251
+ return null;
7252
+ if (anyEntryHasValue(input.collections.get(name)?.entries ?? [], field.name))
7253
+ return null;
7254
+ const point = pointAt(seed, field.name);
7255
+ for (const text of TEXT_PROBES) {
7256
+ if (!await rejectsValueAt(schema, point, text))
7257
+ return null;
7258
+ }
7259
+ return {
7260
+ severity: "error",
7261
+ code: "cms/field-degraded",
7262
+ file: CONFIG_FILE,
7263
+ field: `${name}.${field.name}`,
7264
+ message: `The editor will render "${name}.${field.name}" as a plain text input \u2014 the parser pinned no type and no entry supplies one \u2014 and the schema rejects text there.`,
7265
+ hint: "Write the field's type chain inline in the content config (`n.number()`, `n.date()`, `n.enum([\u2026])`). A type reached through an import, a helper with arguments, or a modifier applied after the object is built is invisible to the parser, which then leaves the editor guessing."
7266
+ };
7267
+ }
7268
+ async function checkFieldShapes(input) {
7269
+ const findings = [];
7270
+ for (const [name, collection] of input.config) {
7271
+ const schema = schemaFor(input.schemas, name);
7272
+ if (!schema || collection.fields.length === 0)
7273
+ continue;
7274
+ const seed = await firstAcceptedEntry(schema, input.collections.get(name)?.entries ?? []);
7275
+ if (!seed)
7276
+ continue;
7277
+ for (const field of collection.fields) {
7278
+ const drift = await requiredDrift(name, field, seed, input.schemas);
7279
+ if (drift)
7280
+ findings.push(drift);
7281
+ const degraded = await degradedField(name, field, seed, input);
7282
+ if (degraded)
7283
+ findings.push(degraded);
7284
+ }
7285
+ }
7286
+ return findings;
7287
+ }
7288
+ var CONFIG_FILE = "src/content.config.ts", TEXT_PROBES, anyEntryHasValue = (entries, field) => entries.some((entry) => entry.frontmatter !== undefined && entry.frontmatter[field] !== undefined);
7289
+ var init_check_shape = __esm(() => {
7290
+ init_schema_probe();
7291
+ TEXT_PROBES = ["", "nua", "https://nua.test/probe", "2024-01-02", "123"];
7292
+ });
7293
+
7294
+ // ../cms-types/src/field-values.ts
7295
+ function isBlankFieldValue(value) {
7296
+ return value === undefined || value === null || value === "";
7297
+ }
7298
+ function defaultValueForNewEntry(field, today = () => new Date) {
7299
+ if (field.defaultValue !== undefined)
7300
+ return field.defaultValue;
7301
+ switch (field.type) {
7302
+ case "boolean":
7303
+ return false;
7304
+ case "number":
7305
+ return 0;
7306
+ case "array":
7307
+ return [];
7308
+ case "date":
7309
+ return today().toISOString().split("T")[0];
7310
+ default:
7311
+ return "";
7312
+ }
7313
+ }
7314
+ function newRepeaterItem(fields, today) {
7315
+ const item = {};
7316
+ for (const field of fields) {
7317
+ if (!field.required || field.hidden)
7318
+ continue;
7319
+ item[field.name] = defaultValueForNewEntry(field, today);
7320
+ }
7321
+ return item;
7322
+ }
7323
+ function isDeclaredDerived(field) {
7324
+ return field.derivedDeclared === true || field.layout?.derivedFrom !== undefined;
7325
+ }
7326
+ function blankRequiredFields(fields, frontmatter) {
7327
+ return fields.filter((field) => field.required && !(field.hidden ?? field.layout?.hidden) && !isDeclaredDerived(field) && isBlankFieldValue(frontmatter[field.name])).map((field) => field.name);
7328
+ }
7329
+ // ../cms-types/src/index.ts
7330
+ function isFieldType(value) {
7331
+ return FIELD_TYPES.includes(value);
7332
+ }
7333
+ function isDerivedTransform(value) {
7334
+ return DERIVED_TRANSFORMS.includes(value);
7335
+ }
7336
+ var FIELD_TYPES, DERIVED_TRANSFORMS;
7337
+ var init_src = __esm(() => {
7338
+ FIELD_TYPES = [
7339
+ "text",
7340
+ "textarea",
7341
+ "markdown",
7342
+ "date",
7343
+ "datetime",
7344
+ "time",
7345
+ "year",
7346
+ "month",
7347
+ "boolean",
7348
+ "number",
7349
+ "image",
7350
+ "file",
7351
+ "url",
7352
+ "email",
7353
+ "tel",
7354
+ "color",
7355
+ "select",
7356
+ "array",
7357
+ "object",
7358
+ "reference"
7359
+ ];
7360
+ DERIVED_TRANSFORMS = ["slugifyHref", "slug", "copy"];
7361
+ });
7362
+
7363
+ // ../cms-core/src/editor-write-model.ts
7364
+ function newEntryFrontmatter(fields, today) {
7365
+ const frontmatter = {};
7366
+ for (const field of fields) {
7367
+ if (field.name === "title" || field.hidden)
7368
+ continue;
7369
+ frontmatter[field.name] = defaultValueForNewEntry(field, today);
7370
+ }
7371
+ return frontmatter;
7372
+ }
7373
+ function applyCreateRouteFields(frontmatter, kind, today = () => new Date) {
7374
+ const title = "Untitled";
7375
+ if (kind === "data")
7376
+ return { title, ...frontmatter };
7377
+ return { title, date: today().toISOString().split("T")[0], ...frontmatter };
7378
+ }
7379
+ function omitEmptyOnCreate(frontmatter) {
7380
+ const out = {};
7381
+ for (const [key, value] of Object.entries(frontmatter)) {
7382
+ if (value !== undefined && value !== "")
7383
+ out[key] = value;
7384
+ }
7385
+ return out;
7386
+ }
7387
+ var init_editor_write_model = __esm(() => {
7388
+ init_src();
7389
+ init_src();
7390
+ });
7391
+
7392
+ // ../cms-core/src/check-write.ts
7393
+ async function parseWrite(schema, value) {
7394
+ try {
7395
+ const result = await schema.safeParse(value);
7396
+ return { issues: result.success ? [] : result.issues };
7397
+ } catch (error) {
7398
+ return { threw: errorText(error) };
7399
+ }
7400
+ }
7401
+ function valueAt(record, path2) {
7402
+ let current = record;
7403
+ for (const segment of path2) {
7404
+ if (Array.isArray(current)) {
7405
+ const index = typeof segment === "number" ? segment : Number(segment);
7406
+ if (!Number.isInteger(index) || index < 0 || index >= current.length)
7407
+ return { present: false };
7408
+ current = current[index];
7409
+ continue;
7410
+ }
7411
+ if (!isPlainObject(current) || !Object.hasOwn(current, String(segment)))
7412
+ return { present: false };
7413
+ current = current[String(segment)];
7414
+ }
7415
+ return current === undefined ? { present: false } : { present: true, value: current };
7416
+ }
7417
+ function hintFor(action, found) {
7418
+ if (action === "create") {
7419
+ if (!found.present) {
7420
+ return "The create form leaves an untouched field out of the write entirely. `.optional()` or `.default(\u2026)` accepts a missing key; `.nullable()` does not \u2014 it accepts `null`, not absence.";
7421
+ }
7422
+ return `The create form starts this field at ${JSON.stringify(found.value)} and nothing makes the editor change it before saving. Either the schema accepts that value, or mark the field \`hidden\` so the write omits it and let \`.default(\u2026)\` supply one.`;
7423
+ }
7424
+ if (!found.present) {
7425
+ return 'The appended item does not carry this key \u2014 "+ Add" seeds only the item fields the config declares required. Declaring it required there is what makes the editor seed it; if it is not required, `.optional()` or `.default(\u2026)` says so to the schema, and `.nullable()` does not \u2014 it accepts `null`, not absence.';
7426
+ }
7427
+ return `The appended item carries ${JSON.stringify(found.value)} for this key, and it is written the moment "+ Add" is clicked. \`.default(\u2026)\` on the inner field is the way out: the parser then reads the field as optional, so the item leaves the key out and the schema supplies the value itself.`;
7428
+ }
7429
+ function rejectionFinding(action, issue, kind, written) {
7430
+ const { field, message } = describeIssue(issue);
7431
+ const finding = {
7432
+ severity: "error",
7433
+ code: "cms/empty-write",
7434
+ file: CONFIG_FILE2,
7435
+ message: `${action} produces a write the schema rejects. ${message}`
7436
+ };
7437
+ if (field !== undefined)
7438
+ finding.field = field;
7439
+ finding.hint = hintFor(kind, valueAt(written, issue.path));
7440
+ return finding;
7441
+ }
7442
+ function blankItemsFor(field, current, today) {
7443
+ const itemFields = field.fields ?? [];
7444
+ if (itemFields.length > 0) {
7445
+ return [newRepeaterItem(itemFields.map((item) => ({ ...toWriteModelField(item), required: item.required })), today)];
7446
+ }
7447
+ if (current.length === 0)
7448
+ return [{}, { name: "" }];
7449
+ const first = current[0];
7450
+ const template = isPlainObject(first) ? Object.fromEntries(Object.keys(first).map((key) => [key, ""])) : {};
7451
+ return [{}, template];
7452
+ }
7453
+ async function checkEditorWrites(input) {
7454
+ const findings = [];
7455
+ const seen = new Set;
7456
+ const report = (key, finding) => {
7457
+ const identity = `${key} ${finding.field ?? ""}`;
7458
+ if (seen.has(identity))
7459
+ return;
7460
+ seen.add(identity);
7461
+ findings.push(finding);
7462
+ };
7463
+ for (const [name, collection] of input.config) {
7464
+ const schema = schemaFor(input.schemas, name);
7465
+ if (!schema)
7466
+ continue;
7467
+ const createKey = `${name} create`;
7468
+ if (collection.fields.length === 0) {
7469
+ report(createKey, {
7470
+ severity: "warning",
7471
+ code: "cms/empty-write-unchecked",
7472
+ file: CONFIG_FILE2,
7473
+ field: name,
7474
+ message: `Could not check what creating a new entry in "${name}" writes: its schema is not readable from the content config, so there are no fields to predict the write from.`,
7475
+ hint: "Write the field types inline in the content config rather than importing the shape from another module \u2014 the prediction reads the config source, and a schema assembled elsewhere leaves it nothing to read."
7476
+ });
7477
+ continue;
7478
+ }
7479
+ const loaded = input.collections.get(name);
7480
+ const entries = loaded?.entries ?? [];
7481
+ const kind = loaded ? collectionKind(loaded, collection.loaderPattern) : "markdown";
7482
+ const created = applyCreateRouteFields(omitEmptyOnCreate(newEntryFrontmatter(collection.fields.map(toWriteModelField), input.today)), kind, input.today);
7483
+ const guarded = new Set(blankRequiredFields(collection.fields, created));
7484
+ const createAction = `Creating a new entry in "${name}"`;
7485
+ const createVerdict = await parseWrite(schema, created);
7486
+ if ("threw" in createVerdict) {
7487
+ report(createKey, throwFinding(createAction, createVerdict.threw));
7488
+ } else {
7489
+ for (const issue of createVerdict.issues) {
7490
+ const top = issue.path[0];
7491
+ if (typeof top === "string" && guarded.has(top))
7492
+ continue;
7493
+ report(createKey, rejectionFinding(createAction, issue, "create", created));
7494
+ }
7495
+ }
7496
+ const repeaters = collection.fields.filter((field) => field.type === "array" && field.itemType === "object");
7497
+ if (repeaters.length === 0)
7498
+ continue;
7499
+ const seed = await firstAcceptedEntry(schema, entries);
7500
+ for (const field of repeaters) {
7501
+ const key = `${name} add:${field.name}`;
7502
+ if (!seed) {
7503
+ const hasEntries = entries.some((entry) => entry.frontmatter !== undefined);
7504
+ const reason = hasEntries ? "no existing entry passes the schema, so there is no valid record to add an item to" : "the collection has no entry to add an item to";
7505
+ report(key, {
7506
+ severity: "warning",
7507
+ code: "cms/empty-write-unchecked",
7508
+ file: CONFIG_FILE2,
7509
+ field: field.name,
7510
+ message: `Could not check what "+ Add" on "${name}.${field.name}" writes: ${reason}.`,
7511
+ hint: hasEntries ? `Fix the \`entry/schema-rejected\` findings in "${name}" and run this again \u2014 the prediction needs one entry the schema accepts.` : `Add one entry to "${name}" and run this again.`
7512
+ });
7513
+ continue;
7514
+ }
7515
+ const current = seed[field.name];
7516
+ const existing = Array.isArray(current) ? current : [];
7517
+ const action = `Clicking "+ Add" on "${name}.${field.name}"`;
7518
+ for (const item of blankItemsFor(field, existing, input.today)) {
7519
+ const written = { ...seed, [field.name]: [...existing, item] };
7520
+ const verdict = await parseWrite(schema, written);
7521
+ if ("threw" in verdict) {
7522
+ report(key, throwFinding(action, verdict.threw));
7523
+ continue;
7524
+ }
7525
+ for (const issue of verdict.issues)
7526
+ report(key, rejectionFinding(action, issue, "add", written));
7527
+ }
7528
+ }
7529
+ }
7530
+ return findings;
7531
+ }
7532
+ var CONFIG_FILE2 = "src/content.config.ts", toWriteModelField = (field) => ({ name: field.name, type: field.type, hidden: field.layout?.hidden }), errorText = (error) => error instanceof Error ? error.message : String(error), throwFinding = (action, threw) => ({
7533
+ severity: "error",
7534
+ code: "cms/empty-write",
7535
+ file: CONFIG_FILE2,
7536
+ message: `${action} produces a write the schema throws on: ${threw}`,
7537
+ hint: "A schema that throws instead of returning an issue is usually a refinement reading a field this write does not carry. Guard it against a missing value \u2014 `astro sync` runs the same schema over the same record."
7538
+ });
7539
+ var init_check_write = __esm(() => {
7540
+ init_check_entries();
7541
+ init_editor_write_model();
7542
+ init_schema_probe();
7543
+ });
7544
+
7014
7545
  // ../../node_modules/@babel/parser/lib/index.js
7015
7546
  var require_lib = __commonJS((exports) => {
7016
7547
  Object.defineProperty(exports, "__esModule", {
@@ -21794,40 +22325,16 @@ var require_lib = __commonJS((exports) => {
21794
22325
  exports.tokTypes = tokTypes;
21795
22326
  });
21796
22327
 
21797
- // ../cms-types/src/index.ts
21798
- function isFieldType(value) {
21799
- return FIELD_TYPES.includes(value);
21800
- }
21801
- var FIELD_TYPES;
21802
- var init_src = __esm(() => {
21803
- FIELD_TYPES = [
21804
- "text",
21805
- "textarea",
21806
- "markdown",
21807
- "date",
21808
- "datetime",
21809
- "time",
21810
- "year",
21811
- "month",
21812
- "boolean",
21813
- "number",
21814
- "image",
21815
- "file",
21816
- "url",
21817
- "email",
21818
- "tel",
21819
- "color",
21820
- "select",
21821
- "array",
21822
- "object",
21823
- "reference"
21824
- ];
21825
- });
21826
-
21827
22328
  // ../cms-core/src/content-config-ast.ts
21828
22329
  function resolveExpression(node, bindings, visited = new Set) {
21829
22330
  let current = node;
21830
- while (current.type === "Identifier") {
22331
+ for (;; ) {
22332
+ if (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression" || current.type === "TSTypeAssertion") {
22333
+ current = current.expression;
22334
+ continue;
22335
+ }
22336
+ if (current.type !== "Identifier")
22337
+ return current;
21831
22338
  if (visited.has(current.name))
21832
22339
  return current;
21833
22340
  visited.add(current.name);
@@ -21836,7 +22343,80 @@ function resolveExpression(node, bindings, visited = new Set) {
21836
22343
  return current;
21837
22344
  current = next;
21838
22345
  }
21839
- return current;
22346
+ }
22347
+ function isAstNode(value) {
22348
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
22349
+ }
22350
+ function walkNodes(node, visit) {
22351
+ visit(node);
22352
+ const children = Object.values(node);
22353
+ for (const child of children) {
22354
+ if (Array.isArray(child)) {
22355
+ for (const item of child) {
22356
+ if (isAstNode(item))
22357
+ walkNodes(item, visit);
22358
+ }
22359
+ } else if (isAstNode(child)) {
22360
+ walkNodes(child, visit);
22361
+ }
22362
+ }
22363
+ }
22364
+ function declaredPatternNames(node, record) {
22365
+ switch (node.type) {
22366
+ case "Identifier":
22367
+ record(node.name);
22368
+ return;
22369
+ case "ObjectPattern":
22370
+ for (const prop of node.properties)
22371
+ declaredPatternNames(prop.type === "RestElement" ? prop.argument : prop.value, record);
22372
+ return;
22373
+ case "ArrayPattern":
22374
+ for (const el of node.elements) {
22375
+ if (el)
22376
+ declaredPatternNames(el, record);
22377
+ }
22378
+ return;
22379
+ case "AssignmentPattern":
22380
+ declaredPatternNames(node.left, record);
22381
+ return;
22382
+ case "RestElement":
22383
+ declaredPatternNames(node.argument, record);
22384
+ return;
22385
+ }
22386
+ }
22387
+ function shadowedNames(ast) {
22388
+ const seen = new Set;
22389
+ const shadowed = new Set;
22390
+ const record = (name) => {
22391
+ if (seen.has(name))
22392
+ shadowed.add(name);
22393
+ seen.add(name);
22394
+ };
22395
+ walkNodes(ast, (node) => {
22396
+ switch (node.type) {
22397
+ case "VariableDeclarator":
22398
+ declaredPatternNames(node.id, record);
22399
+ return;
22400
+ case "FunctionDeclaration":
22401
+ case "FunctionExpression":
22402
+ case "ArrowFunctionExpression":
22403
+ for (const param of node.params)
22404
+ declaredPatternNames(param, record);
22405
+ if (node.type !== "ArrowFunctionExpression" && node.id)
22406
+ record(node.id.name);
22407
+ return;
22408
+ case "ClassDeclaration":
22409
+ if (node.id)
22410
+ record(node.id.name);
22411
+ return;
22412
+ case "ImportSpecifier":
22413
+ case "ImportDefaultSpecifier":
22414
+ case "ImportNamespaceSpecifier":
22415
+ record(node.local.name);
22416
+ return;
22417
+ }
22418
+ });
22419
+ return shadowed;
21840
22420
  }
21841
22421
  function parseSource(source) {
21842
22422
  try {
@@ -21916,6 +22496,8 @@ function parseConfigSource(source, _sourcePath) {
21916
22496
  }
21917
22497
  }
21918
22498
  }
22499
+ for (const name of shadowedNames(ast))
22500
+ bindings.delete(name);
21919
22501
  const collectionObjects = new Map(inlineCollections);
21920
22502
  for (const [varName, collectionName] of exportMap) {
21921
22503
  const decl = collectionDecls.get(varName);
@@ -21971,9 +22553,12 @@ function parseConfigSource(source, _sourcePath) {
21971
22553
  });
21972
22554
  continue;
21973
22555
  }
22556
+ const fields = parseSchemaFields(schemaObject, bindings);
22557
+ checkDerivedFromDeclarations(collectionName, fields);
21974
22558
  result.set(collectionName, {
21975
22559
  name: collectionName,
21976
- fields: parseSchemaFields(schemaObject, bindings),
22560
+ fields,
22561
+ ...hasSkippedMembers(schemaObject) ? { partialFields: true } : {},
21977
22562
  loaderPattern,
21978
22563
  loaderBase,
21979
22564
  layout,
@@ -22032,6 +22617,15 @@ function parseLayoutSection(node) {
22032
22617
  return null;
22033
22618
  return collapsed ? { title, fields, collapsed } : { title, fields };
22034
22619
  }
22620
+ function readEnumOptions(node) {
22621
+ const options = [];
22622
+ for (const el of node.elements) {
22623
+ if (el?.type !== "StringLiteral")
22624
+ return null;
22625
+ options.push(el.value);
22626
+ }
22627
+ return options.length > 0 ? options : null;
22628
+ }
22035
22629
  function stringArray(node) {
22036
22630
  const out = [];
22037
22631
  for (const el of node.elements) {
@@ -22218,7 +22812,10 @@ function parseSchemaFields(schemaObject, bindings) {
22218
22812
  }
22219
22813
  return fields;
22220
22814
  }
22221
- function analyzeFieldExpression(node, field, bindings) {
22815
+ function hasSkippedMembers(schemaObject) {
22816
+ return schemaObject.properties.some((prop) => prop.type !== "ObjectProperty" || !propertyKeyName(prop.key));
22817
+ }
22818
+ function analyzeFieldExpression(node, field, bindings, helpers = new Set) {
22222
22819
  let current = resolveExpression(node, bindings);
22223
22820
  while (current) {
22224
22821
  if (current.type !== "CallExpression")
@@ -22227,10 +22824,30 @@ function analyzeFieldExpression(node, field, bindings) {
22227
22824
  analyzeBaseCall(current, field, bindings);
22228
22825
  return;
22229
22826
  }
22827
+ if (current.callee.type === "Identifier") {
22828
+ const returned = sameFileHelperReturn(current, bindings, helpers);
22829
+ if (returned)
22830
+ analyzeFieldExpression(returned, field, bindings, helpers);
22831
+ return;
22832
+ }
22230
22833
  if (current.callee.type !== "MemberExpression")
22231
22834
  return;
22232
22835
  const property = current.callee.property;
22233
22836
  const methodName = property.type === "Identifier" ? property.name : "";
22837
+ if (methodName === "array") {
22838
+ const item = { name: "__item__", required: true };
22839
+ analyzeFieldExpression(current.callee.object, item, bindings, helpers);
22840
+ if (item.reference) {
22841
+ field.reference = { target: item.reference.target, isArray: true };
22842
+ return;
22843
+ }
22844
+ field.type = "array";
22845
+ if (item.type)
22846
+ field.itemType = item.type;
22847
+ if (item.fields)
22848
+ field.fields = item.fields;
22849
+ return;
22850
+ }
22234
22851
  if (WRAPPER_METHODS.has(methodName)) {
22235
22852
  field.required = false;
22236
22853
  } else if (methodName === "orderBy") {
@@ -22241,6 +22858,25 @@ function analyzeFieldExpression(node, field, bindings) {
22241
22858
  current = resolveExpression(current.callee.object, bindings);
22242
22859
  }
22243
22860
  }
22861
+ function sameFileHelperReturn(call, bindings, seen) {
22862
+ if (call.callee.type !== "Identifier" || call.arguments.length > 0)
22863
+ return null;
22864
+ if (seen.has(call.callee.name))
22865
+ return null;
22866
+ seen.add(call.callee.name);
22867
+ const fn = resolveExpression(call.callee, bindings);
22868
+ if (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression")
22869
+ return null;
22870
+ if (fn.params.length > 0)
22871
+ return null;
22872
+ if (fn.body.type !== "BlockStatement")
22873
+ return fn.body;
22874
+ for (const stmt of fn.body.body) {
22875
+ if (stmt.type === "ReturnStatement" && stmt.argument)
22876
+ return stmt.argument;
22877
+ }
22878
+ return null;
22879
+ }
22244
22880
  function isBaseCall(node) {
22245
22881
  const callee = node.callee;
22246
22882
  if (callee.type === "Identifier") {
@@ -22287,18 +22923,16 @@ function analyzeBaseCall(node, field, bindings) {
22287
22923
  }
22288
22924
  return;
22289
22925
  }
22926
+ if ((ns === "z" || ns === "n") && fn === "boolean") {
22927
+ field.type = "boolean";
22928
+ return;
22929
+ }
22290
22930
  if ((ns === "z" || ns === "n") && fn === "enum") {
22291
- const arg = node.arguments[0];
22292
- if (arg?.type === "ArrayExpression") {
22293
- const options = [];
22294
- for (const el of arg.elements) {
22295
- if (el?.type === "StringLiteral")
22296
- options.push(el.value);
22297
- }
22298
- if (options.length > 0) {
22299
- field.type = "select";
22300
- field.options = options;
22301
- }
22931
+ const arg = node.arguments[0] ? resolveExpression(node.arguments[0], bindings) : undefined;
22932
+ const options = arg?.type === "ArrayExpression" ? readEnumOptions(arg) : null;
22933
+ if (options) {
22934
+ field.type = "select";
22935
+ field.options = options;
22302
22936
  }
22303
22937
  const hintsArg = node.arguments[1];
22304
22938
  if (hintsArg?.type === "ObjectExpression") {
@@ -22421,10 +23055,64 @@ function parseFieldLayoutFromObject(obj) {
22421
23055
  if (value.type === "BooleanLiteral")
22422
23056
  layout.hidden = value.value;
22423
23057
  break;
23058
+ case "derivedFrom":
23059
+ parseDerivedFromValue(value, layout);
23060
+ break;
22424
23061
  }
22425
23062
  }
22426
23063
  return Object.keys(layout).length > 0 ? layout : undefined;
22427
23064
  }
23065
+ function parseDerivedFromValue(value, layout) {
23066
+ if (value.type === "StringLiteral") {
23067
+ if (value.value)
23068
+ layout.derivedFrom = value.value;
23069
+ return;
23070
+ }
23071
+ if (value.type !== "ObjectExpression")
23072
+ return;
23073
+ let field;
23074
+ let transform;
23075
+ for (const prop of value.properties) {
23076
+ if (prop.type !== "ObjectProperty")
23077
+ continue;
23078
+ const key = propertyKeyName(prop.key);
23079
+ if (prop.value.type !== "StringLiteral")
23080
+ continue;
23081
+ if (key === "field")
23082
+ field = prop.value.value;
23083
+ else if (key === "transform" && isDerivedTransform(prop.value.value))
23084
+ transform = prop.value.value;
23085
+ }
23086
+ if (!field)
23087
+ return;
23088
+ layout.derivedFrom = field;
23089
+ if (transform)
23090
+ layout.derivedTransform = transform;
23091
+ }
23092
+ function checkDerivedFromDeclarations(collectionName, fields) {
23093
+ const siblingNames = new Set(fields.map((f) => f.name));
23094
+ for (const field of fields) {
23095
+ const source = field.layout?.derivedFrom;
23096
+ if (source !== undefined && !siblingNames.has(source)) {
23097
+ console.warn(`[cms] collection "${collectionName}": field "${field.name}" derives from "${source}", which is not a field of this collection` + ` \u2014 the field is hidden and nothing is ever computed for it`);
23098
+ }
23099
+ dropNestedDerivedFrom(collectionName, field.name, field.fields);
23100
+ }
23101
+ }
23102
+ function dropNestedDerivedFrom(collectionName, path2, fields) {
23103
+ if (!fields)
23104
+ return;
23105
+ for (const field of fields) {
23106
+ const fieldPath = `${path2}.${field.name}`;
23107
+ const layout = field.layout;
23108
+ if (layout?.derivedFrom !== undefined) {
23109
+ console.warn(`[cms] collection "${collectionName}": field "${fieldPath}" declares \`derivedFrom\` on a nested field, which is not supported` + ` \u2014 only top-level fields derive, so the declaration is ignored and the field stays editable`);
23110
+ delete layout.derivedFrom;
23111
+ delete layout.derivedTransform;
23112
+ }
23113
+ dropNestedDerivedFrom(collectionName, fieldPath, field.fields);
23114
+ }
23115
+ }
22428
23116
  var import_parser, FIELD_HELPER_TYPES, VALID_HINT_KEYS, WRAPPER_METHODS;
22429
23117
  var init_content_config_ast = __esm(() => {
22430
23118
  init_src();
@@ -22459,11 +23147,44 @@ var init_content_config_ast = __esm(() => {
22459
23147
  WRAPPER_METHODS = new Set(["optional", "nullable", "nullish", "default"]);
22460
23148
  });
22461
23149
 
22462
- // ../cms-core/src/check.ts
22463
- import path from "path";
22464
- function isPlainObject(value) {
22465
- return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
23150
+ // ../cms-core/src/shared.ts
23151
+ function pathSegmentValue(raw) {
23152
+ if (typeof raw === "string")
23153
+ return raw;
23154
+ if (typeof raw === "number" || typeof raw === "boolean")
23155
+ return String(raw);
23156
+ if (raw instanceof Date)
23157
+ return raw.toISOString().slice(0, 10);
23158
+ return;
23159
+ }
23160
+ function computePathnameFromSpec(spec, data) {
23161
+ const segments = [];
23162
+ for (const seg of spec) {
23163
+ if ("literal" in seg) {
23164
+ segments.push(seg.literal);
23165
+ continue;
23166
+ }
23167
+ const value = pathSegmentValue(data[seg.field]);
23168
+ if (value === undefined)
23169
+ return;
23170
+ segments.push(seg.map && Object.hasOwn(seg.map, value) ? seg.map[value] : value);
23171
+ }
23172
+ const path2 = ("/" + segments.join("/")).replace(/\/{2,}/g, "/").replace(/\/+$/, "");
23173
+ return path2 === "" ? "/" : path2;
23174
+ }
23175
+ function resolvePathnameFromSpec(def, data) {
23176
+ if (!def.pathname)
23177
+ return;
23178
+ return computePathnameFromSpec(def.pathname, data ?? {});
22466
23179
  }
23180
+ function slugifyHref(text) {
23181
+ return "/" + text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
23182
+ }
23183
+ var init_shared = __esm(() => {
23184
+ init_src();
23185
+ });
23186
+
23187
+ // ../cms-core/src/check.ts
22467
23188
  function typeMismatch(field, value) {
22468
23189
  switch (field.type) {
22469
23190
  case "number":
@@ -22505,40 +23226,86 @@ function typeMismatch(field, value) {
22505
23226
  return null;
22506
23227
  }
22507
23228
  }
22508
- async function collectEntryFiles(fs, collection, base) {
22509
- const pattern = collection.loaderPattern ?? "**/*.{md,mdx,json,yaml,yml}";
22510
- const files = await fs.glob(`${base}/${pattern}`);
22511
- return files.filter((file) => !path.basename(file).startsWith("_")).sort();
23229
+ function projectAssetPath(value) {
23230
+ if (value === "" || value.startsWith("//"))
23231
+ return null;
23232
+ if (/^[a-z][a-z\d+\-.]*:/i.test(value))
23233
+ return null;
23234
+ if (/^[~@#]/.test(value))
23235
+ return null;
23236
+ const withoutQuery = value.split(/[?#]/)[0] ?? "";
23237
+ return withoutQuery === "" ? null : withoutQuery;
22512
23238
  }
22513
- function parseEntry(file, raw) {
22514
- const extension = path.extname(file);
22515
- if (DATA_EXTENSIONS.has(extension)) {
22516
- try {
22517
- const parsed = extension === ".json" ? JSON.parse(raw) : import_yaml.default.parse(raw);
22518
- return { frontmatter: isPlainObject(parsed) ? parsed : {} };
22519
- } catch (error) {
22520
- return { error: error instanceof Error ? error.message.split(`
22521
- `)[0] : String(error) };
23239
+ async function checkAssets(fs, fields, data, file, prefix, findings) {
23240
+ for (const field of fields) {
23241
+ const value = data[field.name];
23242
+ if (value === undefined || value === null)
23243
+ continue;
23244
+ const label = prefix === "" ? field.name : `${prefix}.${field.name}`;
23245
+ if (field.type === "object" && field.fields && isPlainObject(value)) {
23246
+ await checkAssets(fs, field.fields, value, file, label, findings);
23247
+ continue;
23248
+ }
23249
+ if (field.type === "array" && field.itemType === "object" && field.fields && Array.isArray(value)) {
23250
+ for (const [index, item] of value.entries()) {
23251
+ if (isPlainObject(item))
23252
+ await checkAssets(fs, field.fields, item, file, `${label}[${index}]`, findings);
23253
+ }
23254
+ continue;
23255
+ }
23256
+ if (field.type !== "image" || typeof value !== "string")
23257
+ continue;
23258
+ const assetPath = projectAssetPath(value);
23259
+ if (assetPath === null)
23260
+ continue;
23261
+ const candidates = resolveAssetCandidates(assetBaseDir(file), assetPath);
23262
+ if (candidates.length === 0)
23263
+ continue;
23264
+ let found = false;
23265
+ for (const candidate of candidates) {
23266
+ if (await fs.exists(candidate)) {
23267
+ found = true;
23268
+ break;
23269
+ }
22522
23270
  }
23271
+ if (found)
23272
+ continue;
23273
+ findings.push({
23274
+ severity: "warning",
23275
+ code: "entry/missing-asset",
23276
+ file,
23277
+ field: label,
23278
+ message: `${label}: "${value}" points at no file (tried ${candidates.join(", ")}).`
23279
+ });
22523
23280
  }
22524
- const trimmed = raw.trimStart();
22525
- if (!trimmed.startsWith("---"))
22526
- return { frontmatter: {} };
22527
- const lines = trimmed.split(`
22528
- `);
22529
- const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
22530
- if (end === -1)
22531
- return { error: "frontmatter is never closed (missing the second `---`)" };
22532
- try {
22533
- const parsed = import_yaml.default.parse(lines.slice(1, end).join(`
22534
- `).trim());
22535
- return { frontmatter: isPlainObject(parsed) ? parsed : {} };
22536
- } catch (error) {
22537
- return { error: error instanceof Error ? error.message.split(`
22538
- `)[0] : String(error) };
23281
+ }
23282
+ function pathnameCollisions(collection, entries) {
23283
+ if (!collection.pathname)
23284
+ return [];
23285
+ const findings = [];
23286
+ const firstAt = new Map;
23287
+ for (const entry of entries) {
23288
+ if (!entry.frontmatter)
23289
+ continue;
23290
+ const pathname = computePathnameFromSpec(collection.pathname, entry.frontmatter);
23291
+ if (pathname === undefined)
23292
+ continue;
23293
+ const first = firstAt.get(pathname);
23294
+ if (!first) {
23295
+ firstAt.set(pathname, entry);
23296
+ continue;
23297
+ }
23298
+ findings.push({
23299
+ severity: "warning",
23300
+ code: "entry/pathname-collision",
23301
+ file: entry.file,
23302
+ field: collection.name,
23303
+ message: `The pathname rule maps this entry and ${first.file} to ${pathname} \u2014 they collide on one page.`
23304
+ });
22539
23305
  }
23306
+ return findings;
22540
23307
  }
22541
- async function checkContent(fs) {
23308
+ async function checkContent(fs, options = {}) {
22542
23309
  const findings = [];
22543
23310
  const config = await parseContentConfig(fs, new Map);
22544
23311
  if (config.size === 0) {
@@ -22550,51 +23317,47 @@ async function checkContent(fs) {
22550
23317
  });
22551
23318
  return { findings, collections: 0, entries: 0 };
22552
23319
  }
22553
- const bases = new Map;
22554
- const files = new Map;
23320
+ const collections = await loadCollections(fs, config);
22555
23321
  const stems = new Map;
22556
- for (const [name, collection] of config) {
22557
- const base = normalizeBase(collection.loaderBase ?? `src/content/${name}`);
22558
- bases.set(name, base);
22559
- if (!await fs.exists(base)) {
23322
+ for (const [name, loaded] of collections) {
23323
+ stems.set(name, new Set(loaded.entries.map((entry) => entry.stem)));
23324
+ if (!loaded.exists) {
22560
23325
  findings.push({
22561
23326
  severity: "error",
22562
23327
  code: "config/missing-dir",
22563
23328
  file: "src/content.config.ts",
22564
23329
  field: name,
22565
- message: `Collection "${name}" loads from ${base}/, which does not exist.`
23330
+ message: `Collection "${name}" loads from ${loaded.base}/, which does not exist.`
22566
23331
  });
22567
- files.set(name, []);
22568
- stems.set(name, new Set);
22569
23332
  continue;
22570
23333
  }
22571
- const collectionFiles = await collectEntryFiles(fs, collection, base);
22572
- files.set(name, collectionFiles);
22573
- stems.set(name, new Set(collectionFiles.map(entryStem)));
22574
- if (collectionFiles.length === 0) {
23334
+ if (loaded.entries.length === 0) {
22575
23335
  findings.push({
22576
23336
  severity: "warning",
22577
23337
  code: "config/empty-collection",
22578
- file: base,
23338
+ file: loaded.base,
22579
23339
  field: name,
22580
- message: `Collection "${name}" matched no entries under ${base}/.`
23340
+ message: `Collection "${name}" matched no entries under ${loaded.base}/.`
22581
23341
  });
22582
23342
  }
22583
23343
  }
22584
23344
  let entries = 0;
22585
23345
  for (const [name, collection] of config) {
22586
23346
  const typedFields = collection.fields.filter((field) => field.type !== undefined || field.required || field.reference);
22587
- for (const file of files.get(name) ?? []) {
23347
+ const declared = new Set(collection.fields.map((field) => field.name));
23348
+ const loadedEntries = collections.get(name)?.entries ?? [];
23349
+ const schemaJudgesEntries = options.schemas !== undefined && schemaFor(options.schemas, name) !== undefined;
23350
+ for (const entry of loadedEntries) {
22588
23351
  entries++;
22589
- const parsed = parseEntry(file, await fs.readFile(file));
22590
- if ("error" in parsed) {
22591
- findings.push({ severity: "error", code: "entry/syntax", file, message: `Frontmatter does not parse: ${parsed.error}` });
23352
+ const file = entry.file;
23353
+ if (entry.frontmatter === undefined) {
23354
+ findings.push({ severity: "error", code: "entry/syntax", file, message: `Frontmatter does not parse: ${entry.parseError}` });
22592
23355
  continue;
22593
23356
  }
22594
23357
  for (const field of typedFields) {
22595
- const value = parsed.frontmatter[field.name];
23358
+ const value = entry.frontmatter[field.name];
22596
23359
  if (value === undefined || value === null) {
22597
- if (field.required) {
23360
+ if (field.required && !schemaJudgesEntries) {
22598
23361
  findings.push({
22599
23362
  severity: "error",
22600
23363
  code: "entry/missing-required",
@@ -22605,7 +23368,7 @@ async function checkContent(fs) {
22605
23368
  }
22606
23369
  continue;
22607
23370
  }
22608
- const mismatch = field.type === undefined ? null : typeMismatch(field, value);
23371
+ const mismatch = field.type === undefined || schemaJudgesEntries ? null : typeMismatch(field, value);
22609
23372
  if (mismatch) {
22610
23373
  findings.push({ severity: "error", code: "entry/field-type", file, field: field.name, message: `${field.name}: ${mismatch}` });
22611
23374
  continue;
@@ -22629,7 +23392,26 @@ async function checkContent(fs) {
22629
23392
  });
22630
23393
  }
22631
23394
  }
23395
+ await checkAssets(fs, collection.fields, entry.frontmatter, file, "", findings);
23396
+ if (declared.size > 0 && !collection.partialFields) {
23397
+ for (const key of Object.keys(entry.frontmatter)) {
23398
+ if (declared.has(key))
23399
+ continue;
23400
+ findings.push({
23401
+ severity: "warning",
23402
+ code: "entry/unknown-key",
23403
+ file,
23404
+ field: key,
23405
+ message: `"${key}" is not a field of "${name}" \u2014 nothing reads it.`
23406
+ });
23407
+ }
23408
+ }
22632
23409
  }
23410
+ findings.push(...pathnameCollisions(collection, loadedEntries));
23411
+ }
23412
+ if (options.schemas) {
23413
+ const input = { config, collections, schemas: options.schemas };
23414
+ findings.push(...await checkAgainstSchemas(input), ...await checkEditorWrites(input), ...await checkFieldShapes(input));
22633
23415
  }
22634
23416
  return { findings, collections: config.size, entries };
22635
23417
  }
@@ -22647,15 +23429,19 @@ function formatCheckReport(report) {
22647
23429
  const lines = [];
22648
23430
  for (const [file, findings] of byFile) {
22649
23431
  lines.push(file);
23432
+ let lastHint;
22650
23433
  for (const finding of findings) {
22651
23434
  const at = finding.line === undefined ? "" : `${finding.line}:${finding.column ?? 0}`;
22652
23435
  lines.push(` ${at.padEnd(8)}${(finding.severity === "error" ? "error" : "warn").padEnd(6)} ${finding.message} [${finding.code}]`);
23436
+ if (finding.hint !== undefined && finding.hint !== lastHint)
23437
+ lines.push(` ${" ".repeat(14)}\u2192 ${finding.hint}`);
23438
+ lastHint = finding.hint;
22653
23439
  }
22654
23440
  }
22655
23441
  return lines.join(`
22656
23442
  `);
22657
23443
  }
22658
- var import_yaml, DATA_EXTENSIONS, normalizeBase = (base) => path.normalize(base).replace(/^\.\//, "").replace(/\/+$/, ""), entryStem = (file) => path.basename(file).replace(/\.(md|mdx|json|ya?ml)$/, ""), typeName = (value) => {
23444
+ var typeName = (value) => {
22659
23445
  if (value === null)
22660
23446
  return "null";
22661
23447
  if (Array.isArray(value))
@@ -22665,45 +23451,14 @@ var import_yaml, DATA_EXTENSIONS, normalizeBase = (base) => path.normalize(base)
22665
23451
  return typeof value;
22666
23452
  };
22667
23453
  var init_check = __esm(() => {
23454
+ init_check_entries();
23455
+ init_check_live();
23456
+ init_check_shape();
23457
+ init_check_write();
22668
23458
  init_content_config_ast();
22669
- import_yaml = __toESM(require_dist(), 1);
22670
- DATA_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
23459
+ init_shared();
22671
23460
  });
22672
23461
 
22673
- // ../cms-core/src/shared.ts
22674
- function pathSegmentValue(raw) {
22675
- if (typeof raw === "string")
22676
- return raw;
22677
- if (typeof raw === "number" || typeof raw === "boolean")
22678
- return String(raw);
22679
- if (raw instanceof Date)
22680
- return raw.toISOString().slice(0, 10);
22681
- return;
22682
- }
22683
- function computePathnameFromSpec(spec, data) {
22684
- const segments = [];
22685
- for (const seg of spec) {
22686
- if ("literal" in seg) {
22687
- segments.push(seg.literal);
22688
- continue;
22689
- }
22690
- const value = pathSegmentValue(data[seg.field]);
22691
- if (value === undefined)
22692
- return;
22693
- segments.push(seg.map && Object.hasOwn(seg.map, value) ? seg.map[value] : value);
22694
- }
22695
- const path2 = ("/" + segments.join("/")).replace(/\/{2,}/g, "/").replace(/\/+$/, "");
22696
- return path2 === "" ? "/" : path2;
22697
- }
22698
- function resolvePathnameFromSpec(def, data) {
22699
- if (!def.pathname)
22700
- return;
22701
- return computePathnameFromSpec(def.pathname, data ?? {});
22702
- }
22703
- function slugifyHref(text) {
22704
- return "/" + text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
22705
- }
22706
-
22707
23462
  // ../cms-core/src/collection-scanner.ts
22708
23463
  import path2 from "path";
22709
23464
  function normalizeFieldName(name) {
@@ -23116,6 +23871,14 @@ function applyParsedFieldLayout(field, pf) {
23116
23871
  field.order = layout.order;
23117
23872
  if (layout.sidebar)
23118
23873
  field.position = "sidebar";
23874
+ if (layout.derivedFrom) {
23875
+ field.derivedFrom = layout.derivedFrom;
23876
+ field.derivedDeclared = true;
23877
+ if (layout.derivedTransform)
23878
+ field.derivedTransform = layout.derivedTransform;
23879
+ field.hidden = layout.hidden ?? true;
23880
+ return;
23881
+ }
23119
23882
  if (layout.hidden)
23120
23883
  field.hidden = true;
23121
23884
  }
@@ -23496,6 +24259,7 @@ function warnOnPathnameCollisions(collections) {
23496
24259
  var import_yaml2, DATE_PATTERN, URL_PATTERN, IMAGE_EXTENSIONS, MAX_SELECT_OPTIONS = 10, TEXTAREA_MIN_LENGTH = 200, SIDEBAR_FIELD_NAMES, DIRECTIVE_PATTERN, FREE_TEXT_FIELD_NAMES, PUBLISH_TOGGLE_NAMES, PUBLISH_DATE_NAMES, TITLE_FALLBACK_FIELDS, FRONTMATTER_PATTERN, MAX_NESTED_FIELD_DEPTH = 16, HREF_SUFFIXES, WHOLE_DIRECTORY_PATTERN;
23497
24260
  var init_collection_scanner = __esm(() => {
23498
24261
  init_content_config_ast();
24262
+ init_shared();
23499
24263
  import_yaml2 = __toESM(require_dist(), 1);
23500
24264
  DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
23501
24265
  URL_PATTERN = /^(https?:\/\/|\/)/;
@@ -23575,12 +24339,16 @@ var import_yaml3;
23575
24339
  var init_entry_ops = __esm(() => {
23576
24340
  init_collection_scanner();
23577
24341
  init_content_config_ast();
24342
+ init_editor_write_model();
23578
24343
  init_local();
24344
+ init_shared();
23579
24345
  import_yaml3 = __toESM(require_dist(), 1);
23580
24346
  });
23581
24347
 
23582
24348
  // ../cms-core/src/handlers/page-ops.ts
23583
- var init_page_ops = () => {};
24349
+ var init_page_ops = __esm(() => {
24350
+ init_shared();
24351
+ });
23584
24352
  // ../cms-core/src/core.ts
23585
24353
  var init_core = __esm(() => {
23586
24354
  init_collection_scanner();
@@ -23817,14 +24585,21 @@ var init_project_config_ast = __esm(() => {
23817
24585
  // ../cms-core/src/index.ts
23818
24586
  var init_src2 = __esm(() => {
23819
24587
  init_check();
24588
+ init_check_entries();
24589
+ init_check_live();
24590
+ init_check_shape();
24591
+ init_check_write();
23820
24592
  init_collection_scanner();
23821
24593
  init_component_registry();
23822
24594
  init_content_config_ast();
23823
24595
  init_core();
24596
+ init_editor_write_model();
23824
24597
  init_node_fs();
23825
24598
  init_entry_ops();
23826
24599
  init_media();
23827
24600
  init_project_config_ast();
24601
+ init_schema_probe();
24602
+ init_shared();
23828
24603
  });
23829
24604
 
23830
24605
  // ../../node_modules/@astrojs/compiler/dist/chunk-W5DTLHV4.js
@@ -24621,45 +25396,307 @@ var init_check_code = __esm(() => {
24621
25396
  IGNORED = ["node_modules", "dist", ".astro", ".git", ".output", ".vercel", ".netlify"];
24622
25397
  });
24623
25398
 
25399
+ // src/live-schema.ts
25400
+ import { existsSync, realpathSync } from "fs";
25401
+ import path5 from "path";
25402
+ function reasonOf(error, fallback = "unknown error") {
25403
+ try {
25404
+ if (typeof error === "string" && error !== "")
25405
+ return error;
25406
+ if (error instanceof Error && error.message !== "")
25407
+ return error.message;
25408
+ if (isRecord(error) && typeof error.message === "string" && error.message !== "")
25409
+ return error.message;
25410
+ } catch {}
25411
+ return fallback;
25412
+ }
25413
+ function findConfig(projectRoot) {
25414
+ for (const name of CONFIG_NAMES) {
25415
+ const candidate = path5.join(projectRoot, name);
25416
+ if (existsSync(candidate))
25417
+ return candidate;
25418
+ }
25419
+ return null;
25420
+ }
25421
+ function projectAstroDir(projectRoot) {
25422
+ let dir = projectRoot;
25423
+ while (true) {
25424
+ const candidate = path5.join(dir, "node_modules", "astro");
25425
+ if (existsSync(path5.join(candidate, "package.json")))
25426
+ return candidate;
25427
+ const parent = path5.dirname(dir);
25428
+ if (parent === dir)
25429
+ return null;
25430
+ dir = parent;
25431
+ }
25432
+ }
25433
+ function zodEntryOf(module) {
25434
+ if (!isRecord(module))
25435
+ return null;
25436
+ const namespace = module.z;
25437
+ if (!isRecord(namespace))
25438
+ return null;
25439
+ const string = namespace.string;
25440
+ const object = namespace.object;
25441
+ const union = namespace.union;
25442
+ const any = namespace.any;
25443
+ if (typeof string !== "function" || typeof object !== "function" || typeof union !== "function" || typeof any !== "function")
25444
+ return null;
25445
+ return {
25446
+ namespace,
25447
+ any: () => Reflect.apply(any, namespace, []),
25448
+ reference: () => {
25449
+ const text = () => Reflect.apply(string, namespace, []);
25450
+ const lookup = (key) => Reflect.apply(object, namespace, [{ [key]: text(), collection: text() }]);
25451
+ return Reflect.apply(union, namespace, [[text(), lookup("id"), lookup("slug")]]);
25452
+ }
25453
+ };
25454
+ }
25455
+ function installContentStub(zodPath, zod) {
25456
+ Bun.plugin({
25457
+ name: "astro-content-stub",
25458
+ setup(build) {
25459
+ build.module("astro:content", () => ({
25460
+ loader: "object",
25461
+ exports: {
25462
+ defineCollection: (collection) => collection,
25463
+ reference: () => zod.reference(),
25464
+ z: zod.namespace
25465
+ }
25466
+ }));
25467
+ }
25468
+ });
25469
+ stubbedZod = zodPath;
25470
+ }
25471
+ function parseMethod(schema) {
25472
+ if (!isRecord(schema))
25473
+ return null;
25474
+ for (const name of ["safeParseAsync", "safeParse"]) {
25475
+ const method = schema[name];
25476
+ if (typeof method === "function")
25477
+ return (value) => Reflect.apply(method, schema, [value]);
25478
+ }
25479
+ return null;
25480
+ }
25481
+ function dedupe(issues) {
25482
+ const seen = new Set;
25483
+ const unique = [];
25484
+ for (const issue of issues) {
25485
+ const key = `${issue.path.join("\x00")}\x01${issue.message}`;
25486
+ if (seen.has(key))
25487
+ continue;
25488
+ seen.add(key);
25489
+ unique.push(issue);
25490
+ }
25491
+ return unique;
25492
+ }
25493
+ function unionIssues(raw) {
25494
+ return asArray(raw.errors ?? raw.unionErrors).flatMap((branch) => asArray(isRecord(branch) ? branch.issues ?? branch.errors : branch).flatMap(toIssues));
25495
+ }
25496
+ function toIssues(raw) {
25497
+ if (!isRecord(raw))
25498
+ return [{ path: [], message: reasonOf(raw, "invalid value") }];
25499
+ const base = asArray(raw.path).map((segment) => typeof segment === "number" ? segment : String(segment));
25500
+ const branches = raw.code === "invalid_union" ? unionIssues(raw) : [];
25501
+ if (branches.length > 0)
25502
+ return dedupe(branches.map((issue) => ({ path: [...base, ...issue.path], message: issue.message })));
25503
+ return [{ path: base, message: typeof raw.message === "string" ? raw.message : "invalid value" }];
25504
+ }
25505
+ function normalizeIssues(error) {
25506
+ const raw = isRecord(error) ? asArray(error.issues ?? error.errors) : [];
25507
+ if (raw.length > 0)
25508
+ return raw.flatMap(toIssues);
25509
+ return [{ path: [], message: reasonOf(error, "the schema rejected this value without saying why") }];
25510
+ }
25511
+ function liveSchema(parse3) {
25512
+ return {
25513
+ async safeParse(value) {
25514
+ try {
25515
+ const result = await parse3(value);
25516
+ if (!isRecord(result))
25517
+ return { success: false, issues: [{ path: [], message: "the schema returned no parse result" }] };
25518
+ if (result.success === true)
25519
+ return { success: true };
25520
+ return { success: false, issues: normalizeIssues(result.error) };
25521
+ } catch (error) {
25522
+ return { success: false, issues: [{ path: [], message: reasonOf(error, "the schema threw while parsing this entry") }] };
25523
+ }
25524
+ }
25525
+ };
25526
+ }
25527
+ async function collectSchemas(module, zod, configName) {
25528
+ const collections = isRecord(module) ? module.collections : undefined;
25529
+ if (!isRecord(collections))
25530
+ return { skipped: `${configName} does not export a \`collections\` object.` };
25531
+ const schemas = {};
25532
+ for (const [name, definition] of Object.entries(collections)) {
25533
+ if (!isRecord(definition))
25534
+ continue;
25535
+ const factory = definition.schema;
25536
+ if (factory === undefined)
25537
+ continue;
25538
+ let resolved;
25539
+ try {
25540
+ resolved = typeof factory === "function" ? await Reflect.apply(factory, definition, [{ image: () => zod.any() }]) : factory;
25541
+ } catch (error) {
25542
+ return { skipped: `The schema of collection "${name}" in ${configName} threw while being built: ${reasonOf(error)}` };
25543
+ }
25544
+ const parse3 = parseMethod(resolved);
25545
+ if (!parse3) {
25546
+ return { skipped: `The schema of collection "${name}" in ${configName} has no safeParse method \u2014 is it a zod schema?` };
25547
+ }
25548
+ schemas[name] = liveSchema(parse3);
25549
+ }
25550
+ return { schemas };
25551
+ }
25552
+ async function loadLiveSchemas(projectRoot) {
25553
+ const root = path5.resolve(projectRoot);
25554
+ const configPath = findConfig(root);
25555
+ if (!configPath) {
25556
+ return { skipped: `No content config in ${root} \u2014 expected src/content.config.ts or src/content/config.ts.` };
25557
+ }
25558
+ const configName = path5.relative(root, configPath);
25559
+ const astroDir = projectAstroDir(root);
25560
+ if (!astroDir) {
25561
+ return { skipped: `No node_modules/astro above ${root} \u2014 install the project's dependencies and run this again.` };
25562
+ }
25563
+ let zodPath;
25564
+ try {
25565
+ zodPath = realOf(Bun.resolveSync("astro/zod", root));
25566
+ } catch (error) {
25567
+ return { skipped: `Could not resolve astro/zod from ${root}: ${reasonOf(error)}` };
25568
+ }
25569
+ if (!contains(astroDir, zodPath)) {
25570
+ return {
25571
+ skipped: `astro/zod resolved to ${zodPath}, outside the project's own ${astroDir} \u2014 reinstall the project's dependencies and run this again.`
25572
+ };
25573
+ }
25574
+ let zodModule;
25575
+ try {
25576
+ zodModule = await import(zodPath);
25577
+ } catch (error) {
25578
+ return { skipped: `Loading astro/zod from ${root} failed: ${reasonOf(error)}` };
25579
+ }
25580
+ let zod;
25581
+ try {
25582
+ zod = zodEntryOf(zodModule);
25583
+ } catch {
25584
+ zod = null;
25585
+ }
25586
+ if (!zod) {
25587
+ return { skipped: `astro/zod in ${root} does not export a usable \`z\` \u2014 the installed Astro version may be unsupported.` };
25588
+ }
25589
+ if (stubbedZod === null) {
25590
+ try {
25591
+ installContentStub(zodPath, zod);
25592
+ } catch (error) {
25593
+ return { skipped: `Installing the astro:content stub failed: ${reasonOf(error)}` };
25594
+ }
25595
+ } else if (stubbedZod !== zodPath) {
25596
+ return {
25597
+ skipped: `Live schemas were already loaded against ${stubbedZod}; the astro:content stub is process-global, so ${root} needs a run of its own.`
25598
+ };
25599
+ }
25600
+ let module;
25601
+ try {
25602
+ module = await import(configPath);
25603
+ } catch (error) {
25604
+ return { skipped: `${configName} failed to load: ${reasonOf(error)}` };
25605
+ }
25606
+ try {
25607
+ return await collectSchemas(module, zod, configName);
25608
+ } catch (error) {
25609
+ return { skipped: `Reading the collections in ${configName} failed: ${reasonOf(error)}` };
25610
+ }
25611
+ }
25612
+ var CONFIG_NAMES, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), asArray = (value) => Array.isArray(value) ? value : [], realOf = (target) => {
25613
+ try {
25614
+ return realpathSync(target);
25615
+ } catch {
25616
+ return target;
25617
+ }
25618
+ }, contains = (dir, file) => {
25619
+ const relative = path5.relative(realOf(dir), realOf(file));
25620
+ return relative !== "" && !relative.startsWith("..") && !path5.isAbsolute(relative);
25621
+ }, stubbedZod = null;
25622
+ var init_live_schema = __esm(() => {
25623
+ CONFIG_NAMES = [
25624
+ "src/content.config.mjs",
25625
+ "src/content.config.js",
25626
+ "src/content.config.mts",
25627
+ "src/content.config.ts",
25628
+ "src/content/config.ts",
25629
+ "src/content/config.js",
25630
+ "src/content/config.mjs",
25631
+ "src/content/config.mts"
25632
+ ];
25633
+ });
25634
+
24624
25635
  // src/check.ts
24625
25636
  var exports_check = {};
24626
25637
  __export(exports_check, {
25638
+ parseCheckArgs: () => parseCheckArgs,
24627
25639
  check: () => check,
24628
25640
  astroRoots: () => astroRoots
24629
25641
  });
24630
- import { existsSync, readdirSync } from "fs";
24631
- import path5 from "path";
25642
+ import { existsSync as existsSync2, readdirSync } from "fs";
25643
+ import path6 from "path";
25644
+ function parseCheckArgs(args, cwd) {
25645
+ const contentOnly = args.includes("--content-only");
25646
+ return {
25647
+ cwd,
25648
+ json: args.includes("--json"),
25649
+ strict: args.includes("--strict"),
25650
+ contentOnly,
25651
+ live: !contentOnly && !args.includes("--no-live")
25652
+ };
25653
+ }
24632
25654
  function astroRoots(cwd) {
24633
25655
  if (hasAstroConfig(cwd))
24634
25656
  return ["."];
24635
- const packagesDir = path5.join(cwd, "packages");
24636
- if (!existsSync(packagesDir))
25657
+ const packagesDir = path6.join(cwd, "packages");
25658
+ if (!existsSync2(packagesDir))
24637
25659
  return [];
24638
- return readdirSync(packagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().filter((name) => hasAstroConfig(path5.join(packagesDir, name))).map((name) => path5.join("packages", name));
25660
+ return readdirSync(packagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().filter((name) => hasAstroConfig(path6.join(packagesDir, name))).map((name) => path6.join("packages", name));
24639
25661
  }
24640
25662
  function prefixed(report, root) {
24641
25663
  if (root === ".")
24642
25664
  return report;
24643
25665
  return {
24644
25666
  ...report,
24645
- findings: report.findings.map((finding) => ({ ...finding, file: path5.join(root, finding.file) }))
25667
+ findings: report.findings.map((finding) => ({ ...finding, file: path6.join(root, finding.file) }))
24646
25668
  };
24647
25669
  }
25670
+ async function liveSchemasFor(options, roots) {
25671
+ if (options.contentOnly) {
25672
+ return { skipped: "--content-only was passed, and reading the real schemas means executing the project's content config." };
25673
+ }
25674
+ if (!options.live)
25675
+ return { skipped: "--no-live was passed." };
25676
+ const first = roots[0];
25677
+ if (roots.length !== 1 || first === undefined) {
25678
+ const where = roots.join(", ");
25679
+ return { skipped: `They need exactly one Astro project; this run covers ${roots.length} (${where}). Check each one from its own directory.` };
25680
+ }
25681
+ return loadLiveSchemas(path6.join(options.cwd, first));
25682
+ }
24648
25683
  async function check(options) {
24649
25684
  const roots = astroRoots(options.cwd);
24650
25685
  if (roots.length === 0) {
24651
25686
  console.error(`No astro.config.* found in ${options.cwd} or its packages/*. Run this in an Astro project.`);
24652
25687
  return 1;
24653
25688
  }
25689
+ const live = await liveSchemasFor(options, roots);
25690
+ const schemas = "schemas" in live ? live.schemas : undefined;
24654
25691
  const reports = await Promise.all(roots.map(async (root) => {
24655
- const absolute = path5.join(options.cwd, root);
24656
- const content = prefixed(await checkContent(createNodeFs(absolute)), root);
25692
+ const absolute = path6.join(options.cwd, root);
25693
+ const content = prefixed(await checkContent(createNodeFs(absolute), schemas ? { schemas } : {}), root);
24657
25694
  if (options.contentOnly)
24658
25695
  return { ...content, files: 0 };
24659
25696
  const code = await checkCode(absolute);
24660
25697
  return {
24661
25698
  ...content,
24662
- findings: [...content.findings, ...code.findings.map((finding) => ({ ...finding, file: path5.join(root, finding.file) }))],
25699
+ findings: [...content.findings, ...code.findings.map((finding) => ({ ...finding, file: path6.join(root, finding.file) }))],
24663
25700
  files: code.files
24664
25701
  };
24665
25702
  }));
@@ -24672,7 +25709,7 @@ async function check(options) {
24672
25709
  const errors = report.findings.filter((finding) => finding.severity === "error").length;
24673
25710
  const warnings = report.findings.length - errors;
24674
25711
  if (options.json) {
24675
- console.log(JSON.stringify({ ...report, roots }, null, 2));
25712
+ console.log(JSON.stringify({ ...report, roots, liveSchemas: "schemas" in live ? "ran" : live.skipped }, null, 2));
24676
25713
  } else {
24677
25714
  const formatted = formatCheckReport(report);
24678
25715
  if (formatted)
@@ -24682,23 +25719,26 @@ async function check(options) {
24682
25719
  const code = options.contentOnly ? "" : `, ${sourceFiles} source file(s)`;
24683
25720
  const counts = `${report.collections} collection(s), ${report.entries} entries${code}${where}`;
24684
25721
  console.log(errors === 0 && warnings === 0 ? `${counts} \u2014 no problems found` : `${counts} \u2014 ${errors} error(s), ${warnings} warning(s)`);
25722
+ if ("skipped" in live)
25723
+ console.log(`Live schema rules did not run: ${live.skipped}`);
24685
25724
  }
24686
25725
  return errors > 0 || options.strict && warnings > 0 ? 1 : 0;
24687
25726
  }
24688
- var ASTRO_CONFIG_NAMES, hasAstroConfig = (dir) => ASTRO_CONFIG_NAMES.some((name) => existsSync(path5.join(dir, name)));
25727
+ var ASTRO_CONFIG_NAMES, hasAstroConfig = (dir) => ASTRO_CONFIG_NAMES.some((name) => existsSync2(path6.join(dir, name)));
24689
25728
  var init_check2 = __esm(() => {
24690
25729
  init_src2();
24691
25730
  init_check_code();
25731
+ init_live_schema();
24692
25732
  ASTRO_CONFIG_NAMES = ["astro.config.ts", "astro.config.mts", "astro.config.mjs", "astro.config.js"];
24693
25733
  });
24694
25734
 
24695
25735
  // src/utils.ts
24696
- import { existsSync as existsSync2 } from "fs";
25736
+ import { existsSync as existsSync3 } from "fs";
24697
25737
  import { join } from "path";
24698
25738
  function findAstroConfig(cwd = process.cwd()) {
24699
- for (const name of CONFIG_NAMES) {
25739
+ for (const name of CONFIG_NAMES2) {
24700
25740
  const p = join(cwd, name);
24701
- if (existsSync2(p))
25741
+ if (existsSync3(p))
24702
25742
  return p;
24703
25743
  }
24704
25744
  return null;
@@ -24795,9 +25835,9 @@ function assembleConfig(imports, body) {
24795
25835
  `;
24796
25836
  return result;
24797
25837
  }
24798
- var CONFIG_NAMES;
25838
+ var CONFIG_NAMES2;
24799
25839
  var init_utils = __esm(() => {
24800
- CONFIG_NAMES = [
25840
+ CONFIG_NAMES2 = [
24801
25841
  "astro.config.ts",
24802
25842
  "astro.config.mts",
24803
25843
  "astro.config.mjs",
@@ -24815,7 +25855,7 @@ __export(exports_init, {
24815
25855
  detectNuaManagedImports: () => detectNuaManagedImports,
24816
25856
  cleanEmptyStructures: () => cleanEmptyStructures
24817
25857
  });
24818
- import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
25858
+ import { existsSync as existsSync4, readFileSync, writeFileSync } from "fs";
24819
25859
  import { basename, join as join2 } from "path";
24820
25860
  function detectNuaManagedImports(content) {
24821
25861
  const managed = new Map;
@@ -24959,7 +25999,7 @@ async function init({ cwd = process.cwd(), dryRun = false, yes = false } = {}) {
24959
25999
  process.exit(1);
24960
26000
  }
24961
26001
  const pkgPath = join2(cwd, "package.json");
24962
- if (!existsSync3(pkgPath)) {
26002
+ if (!existsSync4(pkgPath)) {
24963
26003
  console.error("No package.json found.");
24964
26004
  process.exit(1);
24965
26005
  }
@@ -25037,7 +26077,7 @@ __export(exports_clean, {
25037
26077
  detectDisabledFeatures: () => detectDisabledFeatures,
25038
26078
  clean: () => clean
25039
26079
  });
25040
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
26080
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
25041
26081
  import { basename as basename2, join as join3 } from "path";
25042
26082
  function detectDisabledFeatures(content) {
25043
26083
  const disabled = new Set;
@@ -25146,7 +26186,7 @@ function transformPackageJson2(pkg, disabled, usedRuntimePackages = []) {
25146
26186
  }
25147
26187
  function scanForNuasiteUsage(cwd) {
25148
26188
  const srcDir = join3(cwd, "src");
25149
- if (!existsSync4(srcDir))
26189
+ if (!existsSync5(srcDir))
25150
26190
  return [];
25151
26191
  const results = [];
25152
26192
  try {
@@ -25181,7 +26221,7 @@ async function clean({ cwd = process.cwd(), dryRun = false, yes = false } = {})
25181
26221
  return;
25182
26222
  }
25183
26223
  const pkgPath = join3(cwd, "package.json");
25184
- if (!existsSync4(pkgPath)) {
26224
+ if (!existsSync5(pkgPath)) {
25185
26225
  console.error("No package.json found.");
25186
26226
  process.exit(1);
25187
26227
  }
@@ -25289,7 +26329,7 @@ var init_config = () => {};
25289
26329
 
25290
26330
  // ../cms/src/component-registry.ts
25291
26331
  import fs2 from "fs/promises";
25292
- import path6 from "path";
26332
+ import path7 from "path";
25293
26333
 
25294
26334
  class ComponentRegistry {
25295
26335
  components = new Map;
@@ -25299,7 +26339,7 @@ class ComponentRegistry {
25299
26339
  }
25300
26340
  async scan() {
25301
26341
  for (const dir of this.componentDirs) {
25302
- const fullPath = path6.join(getProjectRoot(), dir);
26342
+ const fullPath = path7.join(getProjectRoot(), dir);
25303
26343
  try {
25304
26344
  await this.scanDirectory(fullPath, dir);
25305
26345
  } catch {}
@@ -25314,8 +26354,8 @@ class ComponentRegistry {
25314
26354
  async scanDirectory(dir, relativePath) {
25315
26355
  const entries = await fs2.readdir(dir, { withFileTypes: true });
25316
26356
  for (const entry of entries) {
25317
- const fullPath = path6.join(dir, entry.name);
25318
- const relPath = path6.join(relativePath, entry.name);
26357
+ const fullPath = path7.join(dir, entry.name);
26358
+ const relPath = path7.join(relativePath, entry.name);
25319
26359
  if (entry.isDirectory()) {
25320
26360
  await this.scanDirectory(fullPath, relPath);
25321
26361
  } else if (entry.isFile() && entry.name.endsWith(".astro")) {
@@ -25326,7 +26366,7 @@ class ComponentRegistry {
25326
26366
  async parseComponent(filePath, relativePath) {
25327
26367
  try {
25328
26368
  const content = await fs2.readFile(filePath, "utf-8");
25329
- const componentName = path6.basename(filePath, ".astro");
26369
+ const componentName = path7.basename(filePath, ".astro");
25330
26370
  const props = await this.extractProps(content);
25331
26371
  const slots = this.extractSlots(content);
25332
26372
  const description = this.extractDescription(content);
@@ -25581,7 +26621,7 @@ function slugify2(text) {
25581
26621
 
25582
26622
  // ../cms/src/utils.ts
25583
26623
  import { createHash } from "crypto";
25584
- import path7 from "path";
26624
+ import path8 from "path";
25585
26625
  function normalizePagePath(url) {
25586
26626
  let pathname;
25587
26627
  try {
@@ -25634,21 +26674,21 @@ function escapeReplacement(str) {
25634
26674
  return str.replace(/\$/g, "$$$$");
25635
26675
  }
25636
26676
  function resolveSourcePath(sourcePath) {
25637
- return path7.isAbsolute(sourcePath) ? sourcePath : path7.join(getProjectRoot(), sourcePath);
26677
+ return path8.isAbsolute(sourcePath) ? sourcePath : path8.join(getProjectRoot(), sourcePath);
25638
26678
  }
25639
26679
  function resolveAndValidatePath(filePath) {
25640
26680
  const projectRoot = getProjectRoot();
25641
- const resolvedRoot = path7.resolve(projectRoot);
26681
+ const resolvedRoot = path8.resolve(projectRoot);
25642
26682
  const isAbsoluteFs = filePath.startsWith(resolvedRoot);
25643
26683
  const normalizedPath = !isAbsoluteFs && filePath.startsWith("/") ? filePath.slice(1) : filePath;
25644
- const fullPath = path7.isAbsolute(normalizedPath) ? path7.resolve(normalizedPath) : path7.resolve(projectRoot, normalizedPath);
25645
- if (!fullPath.startsWith(resolvedRoot + path7.sep) && fullPath !== resolvedRoot) {
26684
+ const fullPath = path8.isAbsolute(normalizedPath) ? path8.resolve(normalizedPath) : path8.resolve(projectRoot, normalizedPath);
26685
+ if (!fullPath.startsWith(resolvedRoot + path8.sep) && fullPath !== resolvedRoot) {
25646
26686
  throw new Error(`Path traversal detected: ${filePath}`);
25647
26687
  }
25648
26688
  return fullPath;
25649
26689
  }
25650
26690
  async function acquireFileLock(filePath) {
25651
- const key = path7.resolve(filePath);
26691
+ const key = path8.resolve(filePath);
25652
26692
  while (fileLocks.has(key)) {
25653
26693
  await fileLocks.get(key);
25654
26694
  }
@@ -25666,8 +26706,8 @@ function escapeHtml2(text) {
25666
26706
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
25667
26707
  }
25668
26708
  function relativeImportPath2(fromFile, toFile) {
25669
- const fromDir = path7.dirname(fromFile);
25670
- let rel = path7.relative(fromDir, toFile).split(path7.sep).join("/");
26709
+ const fromDir = path8.dirname(fromFile);
26710
+ let rel = path8.relative(fromDir, toFile).split(path8.sep).join("/");
25671
26711
  if (!rel.startsWith("."))
25672
26712
  rel = `./${rel}`;
25673
26713
  return rel;
@@ -25680,7 +26720,7 @@ var init_utils2 = __esm(() => {
25680
26720
 
25681
26721
  // ../cms/src/handlers/component-ops.ts
25682
26722
  import fs3 from "fs/promises";
25683
- import path8 from "path";
26723
+ import path9 from "path";
25684
26724
  async function handleInsertComponent(request, manifestWriter) {
25685
26725
  const { position, referenceComponentId, componentName, props, meta } = request;
25686
26726
  if (!meta?.url) {
@@ -25898,7 +26938,7 @@ function getComponentOccurrenceIndex(manifest, referenceComponent) {
25898
26938
  async function findComponentInvocationFile(projectRoot, pageUrl, manifest, referenceComponent) {
25899
26939
  if (referenceComponent.invocationSourcePath) {
25900
26940
  const filePath = normalizeFilePath(referenceComponent.invocationSourcePath);
25901
- const fullPath = path8.resolve(projectRoot, filePath);
26941
+ const fullPath = path9.resolve(projectRoot, filePath);
25902
26942
  try {
25903
26943
  const content = await fs3.readFile(fullPath, "utf-8");
25904
26944
  const lines = content.split(`
@@ -25912,7 +26952,7 @@ async function findComponentInvocationFile(projectRoot, pageUrl, manifest, refer
25912
26952
  const candidates = getPageFileCandidates(pageUrl);
25913
26953
  const occurrenceIndex = getComponentOccurrenceIndex(manifest, referenceComponent);
25914
26954
  for (const candidate of candidates) {
25915
- const fullPath = path8.resolve(projectRoot, candidate);
26955
+ const fullPath = path9.resolve(projectRoot, candidate);
25916
26956
  try {
25917
26957
  const content = await fs3.readFile(fullPath, "utf-8");
25918
26958
  const lines = content.split(`
@@ -25972,7 +27012,7 @@ function getIndentation(line) {
25972
27012
  function normalizeFilePath(p) {
25973
27013
  if (!p.startsWith("/"))
25974
27014
  return p;
25975
- const projectRoot = path8.resolve(getProjectRoot());
27015
+ const projectRoot = path9.resolve(getProjectRoot());
25976
27016
  if (p.startsWith(projectRoot))
25977
27017
  return p;
25978
27018
  return p.slice(1);
@@ -26380,45 +27420,45 @@ var init_array_ops = __esm(() => {
26380
27420
  // ../cms/src/astro-image-paths.ts
26381
27421
  import { createHash as createHash2 } from "crypto";
26382
27422
  import fs5 from "fs/promises";
26383
- import path9 from "path";
27423
+ import path10 from "path";
26384
27424
  function isHugoStyleEntry(entryAbsPath) {
26385
- return HUGO_INDEX_RE.test(path9.basename(entryAbsPath));
27425
+ return HUGO_INDEX_RE.test(path10.basename(entryAbsPath));
26386
27426
  }
26387
27427
  function shortContentHash(buf) {
26388
27428
  return createHash2("sha256").update(buf).digest("hex").slice(0, 8);
26389
27429
  }
26390
27430
  async function pickAstroImageTarget(args) {
26391
- const entryDir = path9.dirname(args.entryAbsPath);
27431
+ const entryDir = path10.dirname(args.entryAbsPath);
26392
27432
  const isHugoStyle = isHugoStyleEntry(args.entryAbsPath);
26393
- const safeFilename = path9.basename(args.originalFilename);
27433
+ const safeFilename = path10.basename(args.originalFilename);
26394
27434
  if (!safeFilename || safeFilename === "." || safeFilename === "..") {
26395
27435
  throw new Error(`Invalid filename: ${args.originalFilename}`);
26396
27436
  }
26397
27437
  const baseName = isHugoStyle ? safeFilename : `${args.slug}-${safeFilename}`;
26398
- const baseAbs = path9.join(entryDir, baseName);
27438
+ const baseAbs = path10.join(entryDir, baseName);
26399
27439
  if (await isFreeOrMatching(baseAbs, args.compareBuffer)) {
26400
27440
  return { absPath: baseAbs, relPath: `./${baseName}` };
26401
27441
  }
26402
27442
  const candidateAbs = await pickHashedSibling(entryDir, baseName, args.compareBuffer);
26403
- return { absPath: candidateAbs, relPath: `./${path9.basename(candidateAbs)}` };
27443
+ return { absPath: candidateAbs, relPath: `./${path10.basename(candidateAbs)}` };
26404
27444
  }
26405
27445
  async function pickSiblingTarget(dir, filename, buf) {
26406
- const safe = path9.basename(filename);
27446
+ const safe = path10.basename(filename);
26407
27447
  if (!safe || safe === "." || safe === "..") {
26408
27448
  throw new Error(`Invalid filename: ${filename}`);
26409
27449
  }
26410
- const baseAbs = path9.join(dir, safe);
27450
+ const baseAbs = path10.join(dir, safe);
26411
27451
  if (await isFreeOrMatching(baseAbs, buf))
26412
27452
  return baseAbs;
26413
27453
  return pickHashedSibling(dir, safe, buf);
26414
27454
  }
26415
27455
  async function pickHashedSibling(dir, baseName, buf) {
26416
27456
  const hash = shortContentHash(buf);
26417
- const ext = path9.extname(baseName);
26418
- const stem = path9.basename(baseName, ext);
27457
+ const ext = path10.extname(baseName);
27458
+ const stem = path10.basename(baseName, ext);
26419
27459
  for (let attempt = 0;attempt < 5; attempt++) {
26420
27460
  const suffix = attempt === 0 ? hash : `${hash}-${attempt}`;
26421
- const candidateAbs = path9.join(dir, `${stem}-${suffix}${ext}`);
27461
+ const candidateAbs = path10.join(dir, `${stem}-${suffix}${ext}`);
26422
27462
  if (await isFreeOrMatching(candidateAbs, buf))
26423
27463
  return candidateAbs;
26424
27464
  }
@@ -26442,7 +27482,7 @@ var init_astro_image_paths = __esm(() => {
26442
27482
 
26443
27483
  // ../cms/src/handlers/astro-image-upload.ts
26444
27484
  import fs6 from "fs/promises";
26445
- import path10 from "path";
27485
+ import path11 from "path";
26446
27486
  async function tryAstroImageUpload(args) {
26447
27487
  const { collection, entry, field } = args.context;
26448
27488
  if (!collection || !entry || !field)
@@ -26464,12 +27504,12 @@ async function tryAstroImageUpload(args) {
26464
27504
  originalFilename: args.originalFilename,
26465
27505
  compareBuffer: args.fileBuffer
26466
27506
  });
26467
- await fs6.mkdir(path10.dirname(target.absPath), { recursive: true });
27507
+ await fs6.mkdir(path11.dirname(target.absPath), { recursive: true });
26468
27508
  await fs6.writeFile(target.absPath, args.fileBuffer);
26469
27509
  return {
26470
27510
  success: true,
26471
27511
  url: target.relPath,
26472
- filename: path10.basename(target.absPath)
27512
+ filename: path11.basename(target.absPath)
26473
27513
  };
26474
27514
  }
26475
27515
  var init_astro_image_upload = __esm(() => {
@@ -26661,7 +27701,7 @@ var require_he = __commonJS((exports, module) => {
26661
27701
  var has = function(object2, propertyName) {
26662
27702
  return hasOwnProperty2.call(object2, propertyName);
26663
27703
  };
26664
- var contains = function(array, value) {
27704
+ var contains2 = function(array, value) {
26665
27705
  var index = -1;
26666
27706
  var length = array.length;
26667
27707
  while (++index < length) {
@@ -26696,7 +27736,7 @@ var require_he = __commonJS((exports, module) => {
26696
27736
  }
26697
27737
  return decodeMapNumeric[codePoint];
26698
27738
  }
26699
- if (strict && contains(invalidReferenceCodePoints, codePoint)) {
27739
+ if (strict && contains2(invalidReferenceCodePoints, codePoint)) {
26700
27740
  parseError("disallowed character reference");
26701
27741
  }
26702
27742
  if (codePoint > 65535) {
@@ -30184,7 +31224,7 @@ var require_filters = __commonJS((exports) => {
30184
31224
  exports.filters = {
30185
31225
  contains: function(next, text, _a) {
30186
31226
  var adapter = _a.adapter;
30187
- return function contains(elem) {
31227
+ return function contains2(elem) {
30188
31228
  return next(elem) && adapter.getText(elem).includes(text);
30189
31229
  };
30190
31230
  },
@@ -32297,7 +33337,7 @@ var init_snippet_utils = __esm(() => {
32297
33337
 
32298
33338
  // ../cms/src/handlers/source-writer.ts
32299
33339
  import fs8 from "fs/promises";
32300
- import path11 from "path";
33340
+ import path12 from "path";
32301
33341
  async function handleUpdate(request, manifestWriter) {
32302
33342
  const { changes, meta } = request;
32303
33343
  const errors = [];
@@ -32334,7 +33374,7 @@ async function handleUpdate(request, manifestWriter) {
32334
33374
  }
32335
33375
  if (appliedCount > 0 && newContent !== currentContent) {
32336
33376
  for (const op of fileOps) {
32337
- await fs8.mkdir(path11.dirname(op.target), { recursive: true });
33377
+ await fs8.mkdir(path12.dirname(op.target), { recursive: true });
32338
33378
  await fs8.writeFile(op.target, op.bytes);
32339
33379
  }
32340
33380
  await fs8.writeFile(fullPath, newContent, "utf-8");
@@ -32600,8 +33640,8 @@ async function tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, o
32600
33640
  const resolved = await resolveNewSrcBytes(newSrc, originUrl);
32601
33641
  if (!resolved)
32602
33642
  return null;
32603
- const originalAssetAbs = path11.resolve(path11.dirname(absFilePath), importInfo.source);
32604
- const targetAbs = await pickSiblingTarget(path11.dirname(originalAssetAbs), resolved.filename, resolved.bytes);
33643
+ const originalAssetAbs = path12.resolve(path12.dirname(absFilePath), importInfo.source);
33644
+ const targetAbs = await pickSiblingTarget(path12.dirname(originalAssetAbs), resolved.filename, resolved.bytes);
32605
33645
  const newRelImport = relativeImportPath2(absFilePath, targetAbs);
32606
33646
  const newContent = content.slice(0, importInfo.sourceStart) + newRelImport + content.slice(importInfo.sourceEnd);
32607
33647
  return {
@@ -32611,8 +33651,8 @@ async function tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, o
32611
33651
  };
32612
33652
  }
32613
33653
  async function resolveNewSrcBytes(newSrc, originUrl) {
32614
- const filenameFromPath = (p) => path11.basename(p.split("?")[0] ?? p);
32615
- const diskPath = newSrc.startsWith("/src/") ? path11.join(getProjectRoot(), newSrc.slice(1)) : newSrc.startsWith("/") && !newSrc.startsWith("//") ? path11.join(getProjectRoot(), "public", newSrc.replace(/^\/+/, "")) : null;
33654
+ const filenameFromPath = (p) => path12.basename(p.split("?")[0] ?? p);
33655
+ const diskPath = newSrc.startsWith("/src/") ? path12.join(getProjectRoot(), newSrc.slice(1)) : newSrc.startsWith("/") && !newSrc.startsWith("//") ? path12.join(getProjectRoot(), "public", newSrc.replace(/^\/+/, "")) : null;
32616
33656
  if (diskPath) {
32617
33657
  try {
32618
33658
  return { bytes: await fs8.readFile(diskPath), filename: filenameFromPath(newSrc) };
@@ -33160,7 +34200,7 @@ var init_source_writer = __esm(() => {
33160
34200
  });
33161
34201
 
33162
34202
  // ../cms/src/handlers/api-routes.ts
33163
- import path12 from "path";
34203
+ import path13 from "path";
33164
34204
  function requireMedia(ctx) {
33165
34205
  if (!ctx.mediaAdapter) {
33166
34206
  sendError(ctx.res, "Media storage not configured", 501);
@@ -33323,7 +34363,7 @@ var init_api_routes = __esm(() => {
33323
34363
  sendJson(res, { success: false, error: `Invalid content path: ${body.filePath}` }, 400);
33324
34364
  return;
33325
34365
  }
33326
- const fullPath = path12.resolve(getProjectRoot(), body.filePath.replace(/^\//, ""));
34366
+ const fullPath = path13.resolve(getProjectRoot(), body.filePath.replace(/^\//, ""));
33327
34367
  expectedDeletions.add(fullPath);
33328
34368
  const result = await core.deleteEntry(entry.collection, entry.slug);
33329
34369
  if (result.success) {
@@ -33430,7 +34470,7 @@ var init_api_routes = __esm(() => {
33430
34470
  const body = await parseJsonBody(req);
33431
34471
  const result = await core.deletePage(body);
33432
34472
  if (result.success && result.filePath) {
33433
- expectedDeletions.add(path12.resolve(getProjectRoot(), result.filePath));
34473
+ expectedDeletions.add(path13.resolve(getProjectRoot(), result.filePath));
33434
34474
  }
33435
34475
  if (result.success && body.createRedirect && body.redirectTo) {
33436
34476
  await core.addRedirect({ source: body.pagePath, destination: body.redirectTo, statusCode: 307 });
@@ -33535,7 +34575,7 @@ var init_color_patterns = __esm(() => {
33535
34575
 
33536
34576
  // ../cms/src/tailwind-colors.ts
33537
34577
  import fs9 from "fs/promises";
33538
- import path13 from "path";
34578
+ import path14 from "path";
33539
34579
  async function parseTailwindConfig(projectRoot = getProjectRoot()) {
33540
34580
  const cssFiles = [
33541
34581
  "src/styles/global.css",
@@ -33549,7 +34589,7 @@ async function parseTailwindConfig(projectRoot = getProjectRoot()) {
33549
34589
  ];
33550
34590
  let customColors = [];
33551
34591
  for (const cssFile of cssFiles) {
33552
- const fullPath = path13.join(projectRoot, cssFile);
34592
+ const fullPath = path14.join(projectRoot, cssFile);
33553
34593
  try {
33554
34594
  const content = await fs9.readFile(fullPath, "utf-8");
33555
34595
  customColors = extractColorsFromCss(content);
@@ -33622,7 +34662,7 @@ async function parseTextStyles(projectRoot = getProjectRoot()) {
33622
34662
  ];
33623
34663
  let customTextStyles = {};
33624
34664
  for (const cssFile of cssFiles) {
33625
- const fullPath = path13.join(projectRoot, cssFile);
34665
+ const fullPath = path14.join(projectRoot, cssFile);
33626
34666
  try {
33627
34667
  const content = await fs9.readFile(fullPath, "utf-8");
33628
34668
  customTextStyles = extractTextStylesFromCss(content);
@@ -34090,7 +35130,7 @@ var init_local_admin = __esm(() => {
34090
35130
 
34091
35131
  // ../cms/src/manifest-writer.ts
34092
35132
  import fs10 from "fs/promises";
34093
- import path14 from "path";
35133
+ import path15 from "path";
34094
35134
 
34095
35135
  class ManifestWriter {
34096
35136
  globalManifest;
@@ -34161,10 +35201,10 @@ class ManifestWriter {
34161
35201
  }
34162
35202
  getPageManifestPath(pagePath) {
34163
35203
  if (pagePath === "/" || pagePath === "") {
34164
- return path14.join(this.outDir, "index.json");
35204
+ return path15.join(this.outDir, "index.json");
34165
35205
  }
34166
35206
  const cleanPath = pagePath.replace(/^\//, "");
34167
- return path14.join(this.outDir, `${cleanPath}.json`);
35207
+ return path15.join(this.outDir, `${cleanPath}.json`);
34168
35208
  }
34169
35209
  addPage(pagePath, entries, components, collection, seo) {
34170
35210
  this.pageManifests.set(pagePath, { entries, components, collection, seo });
@@ -34190,7 +35230,7 @@ class ManifestWriter {
34190
35230
  }
34191
35231
  async writePageManifest(pagePath, entries, components, collection, seo) {
34192
35232
  const manifestPath = this.getPageManifestPath(pagePath);
34193
- const manifestDir = path14.dirname(manifestPath);
35233
+ const manifestDir = path15.dirname(manifestPath);
34194
35234
  await fs10.mkdir(manifestDir, { recursive: true });
34195
35235
  const metadata = {
34196
35236
  version: MANIFEST_VERSION,
@@ -34246,7 +35286,7 @@ class ManifestWriter {
34246
35286
  }
34247
35287
  }
34248
35288
  if (this.outDir) {
34249
- const globalManifestPath = path14.join(this.outDir, this.manifestFile);
35289
+ const globalManifestPath = path15.join(this.outDir, this.manifestFile);
34250
35290
  const globalSettings = {
34251
35291
  componentDefinitions: this.componentDefinitions,
34252
35292
  pages
@@ -34858,7 +35898,7 @@ var init_field_types = __esm(() => {
34858
35898
 
34859
35899
  // ../cms/src/migrate-astro-image.ts
34860
35900
  import fs11 from "fs/promises";
34861
- import path15 from "path";
35901
+ import path16 from "path";
34862
35902
  async function migrateAstroImages(options = {}) {
34863
35903
  const projectRoot = options.projectRoot ?? getProjectRoot();
34864
35904
  const dryRun = options.dryRun ?? false;
@@ -34870,7 +35910,7 @@ async function migrateAstroImages(options = {}) {
34870
35910
  if (astroFields.length === 0 || !def.entries)
34871
35911
  continue;
34872
35912
  for (const entry of def.entries) {
34873
- const entryAbs = path15.isAbsolute(entry.sourcePath) ? entry.sourcePath : path15.join(projectRoot, entry.sourcePath);
35913
+ const entryAbs = path16.isAbsolute(entry.sourcePath) ? entry.sourcePath : path16.join(projectRoot, entry.sourcePath);
34874
35914
  let raw;
34875
35915
  try {
34876
35916
  raw = await fs11.readFile(entryAbs, "utf-8");
@@ -34885,7 +35925,7 @@ async function migrateAstroImages(options = {}) {
34885
35925
  }
34886
35926
  const [fullFm, fmStart, yamlBody, fmEnd] = fmMatch;
34887
35927
  const doc2 = import_yaml7.parseDocument(yamlBody);
34888
- let mutated = false;
35928
+ let mutated2 = false;
34889
35929
  for (const field of astroFields) {
34890
35930
  const current = doc2.get(field.name);
34891
35931
  if (typeof current !== "string") {
@@ -34895,7 +35935,7 @@ async function migrateAstroImages(options = {}) {
34895
35935
  }
34896
35936
  if (!current.startsWith("/") || current.startsWith("//"))
34897
35937
  continue;
34898
- const sourceAbs = path15.join(projectRoot, "public", current.replace(/^\/+/, ""));
35938
+ const sourceAbs = path16.join(projectRoot, "public", current.replace(/^\/+/, ""));
34899
35939
  let sourceBuf;
34900
35940
  try {
34901
35941
  sourceBuf = await fs11.readFile(sourceAbs);
@@ -34906,15 +35946,15 @@ async function migrateAstroImages(options = {}) {
34906
35946
  const target = await pickAstroImageTarget({
34907
35947
  entryAbsPath: entryAbs,
34908
35948
  slug: entry.slug,
34909
- originalFilename: path15.basename(current),
35949
+ originalFilename: path16.basename(current),
34910
35950
  compareBuffer: sourceBuf
34911
35951
  });
34912
35952
  if (!dryRun) {
34913
- await fs11.mkdir(path15.dirname(target.absPath), { recursive: true });
35953
+ await fs11.mkdir(path16.dirname(target.absPath), { recursive: true });
34914
35954
  await fs11.writeFile(target.absPath, sourceBuf);
34915
35955
  }
34916
35956
  doc2.set(field.name, target.relPath);
34917
- mutated = true;
35957
+ mutated2 = true;
34918
35958
  migrations.push({
34919
35959
  entrySourcePath: entry.sourcePath,
34920
35960
  fieldName: field.name,
@@ -34924,7 +35964,7 @@ async function migrateAstroImages(options = {}) {
34924
35964
  copiedTo: target.absPath
34925
35965
  });
34926
35966
  }
34927
- if (mutated && !dryRun) {
35967
+ if (mutated2 && !dryRun) {
34928
35968
  const newYaml = doc2.toString().replace(/\n$/, "");
34929
35969
  const newRaw = raw.replace(fullFm, `${fmStart}${newYaml}${fmEnd}`);
34930
35970
  await fs11.writeFile(entryAbs, newRaw, "utf-8");
@@ -34973,7 +36013,7 @@ var exports_migrate = {};
34973
36013
  __export(exports_migrate, {
34974
36014
  migrate: () => migrate
34975
36015
  });
34976
- import path16 from "path";
36016
+ import path17 from "path";
34977
36017
  async function migrate(args) {
34978
36018
  if (args.target !== "astro-image") {
34979
36019
  console.error(`Unknown migrate target: ${args.target}`);
@@ -34992,7 +36032,7 @@ async function migrate(args) {
34992
36032
  for (const m2 of result.migrations) {
34993
36033
  console.log(` ${m2.entrySourcePath}`);
34994
36034
  console.log(` ${m2.fieldName}: ${m2.originalValue} \u2192 ${m2.newValue}`);
34995
- console.log(` copy: ${path16.relative(process.cwd(), m2.copiedFrom)} \u2192 ${path16.relative(process.cwd(), m2.copiedTo)}`);
36035
+ console.log(` copy: ${path17.relative(process.cwd(), m2.copiedFrom)} \u2192 ${path17.relative(process.cwd(), m2.copiedTo)}`);
34996
36036
  }
34997
36037
  }
34998
36038
  if (result.skipped.length > 0) {
@@ -35034,7 +36074,7 @@ Commands:`);
35034
36074
  console.log(" build Run astro build with the Nua defaults");
35035
36075
  console.log(" dev Run astro dev with the Nua defaults");
35036
36076
  console.log(" preview Run astro preview with the Nua defaults");
35037
- console.log(" check Validate collections and parse the source (no build). --json, --strict, --content-only");
36077
+ console.log(" check Validate collections and parse the source (no build). --json, --strict, --content-only, --no-live");
35038
36078
  console.log(" init Convert a standard Astro project to use Nua");
35039
36079
  console.log(" clean Eject to a standard Astro project (remove @nuasite/* deps)");
35040
36080
  console.log(" migrate <target> Run a content migration. Targets: astro-image");
@@ -35048,13 +36088,8 @@ if (command && ["build", "dev", "preview"].includes(command)) {
35048
36088
  } else {
35049
36089
  switch (command) {
35050
36090
  case "check": {
35051
- const { check: check2 } = await Promise.resolve().then(() => (init_check2(), exports_check));
35052
- process.exit(await check2({
35053
- cwd: process.cwd(),
35054
- json: args.includes("--json"),
35055
- strict: args.includes("--strict"),
35056
- contentOnly: args.includes("--content-only")
35057
- }));
36091
+ const { check: check2, parseCheckArgs: parseCheckArgs2 } = await Promise.resolve().then(() => (init_check2(), exports_check));
36092
+ process.exit(await check2(parseCheckArgs2(args, process.cwd())));
35058
36093
  break;
35059
36094
  }
35060
36095
  case "init": {