@plume-press/cli 0.5.0 → 0.7.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/local.mjs CHANGED
@@ -33315,13 +33315,6 @@ function named(value, path) {
33315
33315
  );
33316
33316
  }
33317
33317
  function defineSchema(schema2) {
33318
- return checkSchema(schema2);
33319
- }
33320
- function readStoredSchema(schema2, ignored = () => {
33321
- }) {
33322
- return checkSchema(schema2, ignored);
33323
- }
33324
- function checkSchema(schema2, ignoredPreviewPath) {
33325
33318
  canonicalJson(schema2);
33326
33319
  assert2(object2(schema2), "expected object");
33327
33320
  allowedKeys(schema2, ["collections", "singletons"], "schema");
@@ -33455,16 +33448,11 @@ function checkSchema(schema2, ignoredPreviewPath) {
33455
33448
  entities.add(entityName);
33456
33449
  validateFields2(entity2.fields, entityName, false);
33457
33450
  if ("previewPath" in entity2)
33458
- try {
33459
- validatePreviewPath(
33460
- entityName,
33461
- entity2.previewPath,
33462
- isCollection ? entity2.fields : null
33463
- );
33464
- } catch (error62) {
33465
- if (!ignoredPreviewPath) throw error62;
33466
- ignoredPreviewPath(error62.message);
33467
- }
33451
+ validatePreviewPath(
33452
+ entityName,
33453
+ entity2.previewPath,
33454
+ isCollection ? entity2.fields : null
33455
+ );
33468
33456
  }
33469
33457
  return schema2;
33470
33458
  }
@@ -33502,10 +33490,11 @@ function validatePreviewPath(entity2, path, fields) {
33502
33490
  );
33503
33491
  }
33504
33492
  async function schemaHash(schema2) {
33505
- readStoredSchema(schema2);
33506
- const bytes2 = new TextEncoder().encode(
33507
- canonicalJson(schema2)
33508
- );
33493
+ defineSchema(schema2);
33494
+ return hashSchemaJson(canonicalJson(schema2));
33495
+ }
33496
+ async function hashSchemaJson(json2) {
33497
+ const bytes2 = new TextEncoder().encode(json2);
33509
33498
  const digest = await crypto.subtle.digest("SHA-256", bytes2);
33510
33499
  return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
33511
33500
  }
@@ -33645,7 +33634,10 @@ function validateRecord(schema2, name, data, options2) {
33645
33634
  const entity2 = [...schema2.collections, ...schema2.singletons ?? []].find(
33646
33635
  (candidate) => candidate.name === name
33647
33636
  );
33648
- if (!entity2)
33637
+ return validateRecordFields(entity2?.fields, name, data, options2);
33638
+ }
33639
+ function validateRecordFields(fields, name, data, options2) {
33640
+ if (!fields)
33649
33641
  return [
33650
33642
  {
33651
33643
  path: "",
@@ -33666,7 +33658,7 @@ function validateRecord(schema2, name, data, options2) {
33666
33658
  }
33667
33659
  const issues = [];
33668
33660
  validateFields(
33669
- entity2.fields,
33661
+ fields,
33670
33662
  data,
33671
33663
  "",
33672
33664
  options2.publication,
@@ -34222,23 +34214,6 @@ async function safePath(cwd, create2) {
34222
34214
  );
34223
34215
  return path;
34224
34216
  }
34225
- function addTimestamps(db) {
34226
- const missing = () => !db.prepare("PRAGMA table_info(local_records)").all().some((column) => column.name === "created_at");
34227
- if (!missing() && !db.prepare(
34228
- "SELECT 1 FROM local_records WHERE created_at IS NULL OR updated_at IS NULL LIMIT 1"
34229
- ).get())
34230
- return;
34231
- transaction(db, () => {
34232
- if (missing()) {
34233
- db.exec("ALTER TABLE local_records ADD COLUMN created_at INTEGER");
34234
- db.exec("ALTER TABLE local_records ADD COLUMN updated_at INTEGER");
34235
- }
34236
- const now = Date.now();
34237
- db.prepare(
34238
- "UPDATE local_records SET created_at = coalesce(created_at, ?), updated_at = coalesce(updated_at, created_at, ?) WHERE created_at IS NULL OR updated_at IS NULL"
34239
- ).run(now, now);
34240
- });
34241
- }
34242
34217
  function meta3(db) {
34243
34218
  return db.prepare("SELECT schema, revision FROM local_meta WHERE id = 1").get();
34244
34219
  }
@@ -34444,7 +34419,6 @@ async function open2(path, expected) {
34444
34419
  db.exec("PRAGMA busy_timeout = 5000");
34445
34420
  const stored = meta3(db);
34446
34421
  if (!stored) throw new Error("Local content database is not initialized.");
34447
- addTimestamps(db);
34448
34422
  let schema2 = defineSchema(JSON.parse(stored.schema));
34449
34423
  if (expected && canonicalJson(schema2) !== canonicalJson(expected))
34450
34424
  throw new Error(
@@ -34633,7 +34607,7 @@ async function openLocalStore(input2) {
34633
34607
  "CREATE TABLE local_meta (id INTEGER PRIMARY KEY CHECK(id = 1), schema TEXT NOT NULL, revision INTEGER NOT NULL)"
34634
34608
  );
34635
34609
  db.exec(
34636
- "CREATE TABLE local_records (collection TEXT NOT NULL, record_id TEXT NOT NULL, data TEXT NOT NULL, visibility TEXT NOT NULL CHECK(visibility IN ('draft', 'public')), created_at INTEGER, updated_at INTEGER, PRIMARY KEY(collection, record_id))"
34610
+ "CREATE TABLE local_records (collection TEXT NOT NULL, record_id TEXT NOT NULL, data TEXT NOT NULL, visibility TEXT NOT NULL CHECK(visibility IN ('draft', 'public')), created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, PRIMARY KEY(collection, record_id))"
34637
34611
  );
34638
34612
  db.prepare(
34639
34613
  "INSERT INTO local_meta (id, schema, revision) VALUES (1, ?, 0)"
package/dist/schema.d.ts CHANGED
@@ -187,11 +187,6 @@ export type FieldIssue = {
187
187
  export declare function canonicalJson(value: JsonValue): string;
188
188
  /** Schema configuration is data only. Executable validators/transforms are unsupported. */
189
189
  export declare function defineSchema<const S extends SchemaDefinition>(schema: S): S;
190
- /** A schema stored with content, which an older Plume accepted: checked like
191
- * `defineSchema`, except that a `previewPath` this version's grammar rejects is reported
192
- * to `ignored` and left inert (`resolvePreviewPath` gives it no path), so that content
193
- * stays readable. Defining or registering a schema stays strict. */
194
- export declare function readStoredSchema(schema: unknown, ignored?: (problem: string) => void): SchemaDefinition;
195
190
  /** A path on the Site's own website: one leading `/`, no backslash, whitespace or control
196
191
  * character, at most 2048 characters, and still a path of the same site once a URL parser
197
192
  * normalises it (`/./`, `..`), so no browser or parser reads it as another host. */
@@ -201,9 +196,17 @@ export declare function isLocalPath(path: unknown): path is string;
201
196
  * Item ID (even when a Field is named `id`), `{<field>}` that text or slug Field's value,
202
197
  * each trimmed and URL-encoded. The result is always an `isLocalPath`. */
203
198
  export declare function resolvePreviewPath(entity: Collection | Singleton, recordId: string, data: JsonValue | undefined): string | null;
204
- /** Hashes a schema as stored: a `previewPath` an older Plume accepted doesn't change or
205
- * block its identity (see `readStoredSchema`). */
199
+ /** Hashes a valid schema using its canonical JSON representation. */
206
200
  export declare function schemaHash(schema: SchemaDefinition): Promise<string>;
201
+ /** Owns an immutable schema snapshot for validating many records without rechecking
202
+ * the schema. Mutating the source cannot change the snapshot or its cached hash. */
203
+ export declare function createSchemaValidator<const S extends SchemaDefinition>(source: S): {
204
+ schema: S;
205
+ hash: () => Promise<string>;
206
+ validateRecord: (name: string, data: unknown, options: {
207
+ publication: boolean;
208
+ }) => FieldIssue[];
209
+ };
207
210
  /** Validates shape only; richText HTML requires sanitization at the write/render boundary.
208
211
  * Slug uniqueness, reference targets and media `accept` need the whole content and are
209
212
  * checked where it is available (save, preparation, local store). */
package/dist/schema.mjs CHANGED
@@ -83,13 +83,6 @@ function named(value, path) {
83
83
  );
84
84
  }
85
85
  function defineSchema(schema) {
86
- return checkSchema(schema);
87
- }
88
- function readStoredSchema(schema, ignored = () => {
89
- }) {
90
- return checkSchema(schema, ignored);
91
- }
92
- function checkSchema(schema, ignoredPreviewPath) {
93
86
  canonicalJson(schema);
94
87
  assert(object(schema), "expected object");
95
88
  allowedKeys(schema, ["collections", "singletons"], "schema");
@@ -223,16 +216,11 @@ function checkSchema(schema, ignoredPreviewPath) {
223
216
  entities.add(entityName);
224
217
  validateFields2(entity.fields, entityName, false);
225
218
  if ("previewPath" in entity)
226
- try {
227
- validatePreviewPath(
228
- entityName,
229
- entity.previewPath,
230
- isCollection ? entity.fields : null
231
- );
232
- } catch (error) {
233
- if (!ignoredPreviewPath) throw error;
234
- ignoredPreviewPath(error.message);
235
- }
219
+ validatePreviewPath(
220
+ entityName,
221
+ entity.previewPath,
222
+ isCollection ? entity.fields : null
223
+ );
236
224
  }
237
225
  return schema;
238
226
  }
@@ -296,13 +284,36 @@ function resolvePreviewPath(entity, recordId, data) {
296
284
  return !missing && isLocalPath(path) ? path : null;
297
285
  }
298
286
  async function schemaHash(schema) {
299
- readStoredSchema(schema);
300
- const bytes = new TextEncoder().encode(
301
- canonicalJson(schema)
302
- );
287
+ defineSchema(schema);
288
+ return hashSchemaJson(canonicalJson(schema));
289
+ }
290
+ async function hashSchemaJson(json) {
291
+ const bytes = new TextEncoder().encode(json);
303
292
  const digest = await crypto.subtle.digest("SHA-256", bytes);
304
293
  return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
305
294
  }
295
+ function createSchemaValidator(source) {
296
+ const json = canonicalJson(source);
297
+ const schema = defineSchema(JSON.parse(json));
298
+ const freeze = (value) => {
299
+ for (const child of Object.values(value))
300
+ if (child !== null && typeof child === "object") freeze(child);
301
+ Object.freeze(value);
302
+ };
303
+ freeze(schema);
304
+ const entities = [...schema.collections, ...schema.singletons ?? []];
305
+ let hash;
306
+ return {
307
+ schema,
308
+ hash: () => hash ??= hashSchemaJson(json),
309
+ validateRecord: (name, data, options2) => validateRecordFields(
310
+ entities.find((entity) => entity.name === name)?.fields,
311
+ name,
312
+ data,
313
+ options2
314
+ )
315
+ };
316
+ }
306
317
  var unset = (field, record) => !Object.hasOwn(record, field.name) || record[field.name] === null && !field.required;
307
318
  function validateFields(fields, value, prefix, publication, report) {
308
319
  if (!object(value)) {
@@ -439,7 +450,10 @@ function validateRecord(schema, name, data, options2) {
439
450
  const entity = [...schema.collections, ...schema.singletons ?? []].find(
440
451
  (candidate) => candidate.name === name
441
452
  );
442
- if (!entity)
453
+ return validateRecordFields(entity?.fields, name, data, options2);
454
+ }
455
+ function validateRecordFields(fields, name, data, options2) {
456
+ if (!fields)
443
457
  return [
444
458
  {
445
459
  path: "",
@@ -460,7 +474,7 @@ function validateRecord(schema, name, data, options2) {
460
474
  }
461
475
  const issues = [];
462
476
  validateFields(
463
- entity.fields,
477
+ fields,
464
478
  data,
465
479
  "",
466
480
  options2.publication,
@@ -570,11 +584,11 @@ function validDate(value) {
570
584
  }
571
585
  export {
572
586
  canonicalJson,
587
+ createSchemaValidator,
573
588
  defineSchema,
574
589
  dropMissingReferences,
575
590
  isLocalPath,
576
591
  mediaAccepted,
577
- readStoredSchema,
578
592
  referenceIds,
579
593
  resolvePreviewPath,
580
594
  schemaHash,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plume-press/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "The plume command: set up a website's Plume CMS and check its builds.",
5
5
  "homepage": "https://app.plume.press",
6
6
  "license": "MIT",
@@ -32,7 +32,8 @@
32
32
  "THIRD_PARTY_NOTICES.md"
33
33
  ],
34
34
  "dependencies": {
35
- "jiti": "2.7.0"
35
+ "jiti": "2.7.0",
36
+ "yazl": "3.3.1"
36
37
  },
37
38
  "publishConfig": {
38
39
  "access": "public"