Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tasknotes/model",
"version": "0.3.0-rc.14",
"version": "0.3.0-rc.15",
"description": "TaskNotes model, mapping, validation, recurrence, and operation-planning reference implementation.",
"license": "MIT",
"type": "module",
Expand Down
14 changes: 11 additions & 3 deletions src/mdbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ export interface TaskNotesMdbaseTypePack {
provides: Array<{ id: "tasknotes.task"; version: string; digest: string }>;
}

/**
* Version of the generated implementing type. It equals the published starter
* type's version, so types written by TaskNotes and by the tasknotes.task pack
* agree and pack upgrades do not see a conflicting version change.
*/
export const TASKNOTES_MDBASE_TYPE_VERSION = 4;

/** Version of the generated definition bundle, independent of contract version. */
export const TASKNOTES_MDBASE_PACK_VERSION = "0.3.0-rc.16";
export const TASKNOTES_MDBASE_PACK_VERSION = "0.3.0-rc.17";
export const TASKNOTES_CONTRACT_DIGEST =
"sha256:dde71d0d776aa0069ee627fe3c10b1a20f87ba36929a16790598ebfb4e8ebf04";

Expand Down Expand Up @@ -482,7 +489,8 @@ export function buildTaskNotesMdbaseResources(
"assignees",
mapping.assignees,
{ type: "array", items: { type: "string", minLength: 1, pattern: "\\S" }, uniqueItems: true },
{ links: [{ suffix: "[]" }] }
// Not a v0.2 field, so no legacy nulls to accept; matches the published starter.
{ links: [{ suffix: "[]" }], legacyNullable: false }
);
addField(
"attachments",
Expand Down Expand Up @@ -739,7 +747,7 @@ export function buildTaskNotesMdbaseResources(
const type: Record<string, unknown> = {
kind: "mdbase.type",
name: typeName,
version: 3,
version: TASKNOTES_MDBASE_TYPE_VERSION,
description: "A task managed by TaskNotes.",
match: buildMatch(modelConfig),
schema: {
Expand Down
57 changes: 56 additions & 1 deletion src/starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cloneDefaultModelConfig } from "./defaults";
import { serializeMarkdownDocument } from "./frontmatter";
import {
buildTaskNotesMdbaseResources,
TASKNOTES_MDBASE_TYPE_VERSION,
type TaskNotesMdbaseOptions,
type TaskNotesMdbaseResources,
} from "./mdbase";
Expand All @@ -19,7 +20,57 @@ import { TASKNOTES_SPEC_VERSION, type TaskNotesModelConfig } from "./types";
* here is a new starter version and must be deliberate. The test suite checks
* this output against the published type.
*/
export const TASKNOTES_STARTER_TYPE_VERSION = 4;
export const TASKNOTES_STARTER_TYPE_VERSION = TASKNOTES_MDBASE_TYPE_VERSION;

/**
* Revision of the published starter document (mdbase-contracts
* `types/tasknotes-task/<revision>.md`). A revision that does not change the
* type's data keeps TASKNOTES_STARTER_TYPE_VERSION, so collections that already
* have that version do not see a conflicting version change on upgrade.
*/
export const TASKNOTES_STARTER_REVISION = 5;

/**
* The generator bookkeeping published in the first starter. Collections keep
* their own list (it follows their field mapping), so pack upgrades must not
* change it: the starter always publishes this list.
*/
export const TASKNOTES_STARTER_MANAGED_FIELDS: readonly string[] = [
"attachments",
"blockedBy",
"complete_instances",
"completedDate",
"contexts",
"dateCreated",
"dateModified",
"due",
"googleCalendarEventId",
"googleCalendarExceptionEventId",
"googleCalendarExceptionOriginalScheduled",
"googleCalendarMovedOriginalDates",
"icsEventId",
"id",
"occurrence_date",
"occurrence_future_horizon",
"occurrence_materialization",
"occurrence_next_trigger",
"occurrence_past_horizon",
"occurrence_template",
"priority",
"projects",
"recurrence",
"recurrence_anchor",
"recurrence_parent",
"reminders",
"scheduled",
"skipped_instances",
"status",
"tags",
"tasknotes_manual_order",
"timeEntries",
"timeEstimate",
"title",
];

export const TASKNOTES_STARTER_PROFILES = [
"core-lite",
Expand Down Expand Up @@ -87,6 +138,10 @@ export function buildTaskNotesStarterResources(
throw new Error(`The generated type does not implement tasknotes.task ${TASKNOTES_SPEC_VERSION}.`);
}
type.version = TASKNOTES_STARTER_TYPE_VERSION;
type["x-tasknotes-generator"] = {
...type["x-tasknotes-generator"],
managed_fields: [...TASKNOTES_STARTER_MANAGED_FIELDS],
};
// One shared date schema, so due and scheduled cannot drift apart.
const taskDateSchema = {
anyOf: [
Expand Down
2 changes: 1 addition & 1 deletion test/assignments.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test("ordinary task updates retain unresolved assignments and explicitly clear t
test("the task contract declares assignments as links resolved by the collection", () => {
const resources = buildTaskNotesMdbaseResources();
assert.equal(resources.contract.version, "0.3.0-rc.5");
assert.equal(resources.type.version, 3);
assert.equal(resources.type.version, 4);
assert.equal(resources.taskSchema.properties.assignees.uniqueItems, true);
assert.equal(resources.type.implements[0].fields.assignees, "assignees");
// No target_type: several local types may implement mdbase.person.
Expand Down
22 changes: 16 additions & 6 deletions test/starter.test.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import assert from "node:assert/strict";
import test from "node:test";
import { buildTaskNotesMdbaseTypePack } from "../dist/esm/mdbase.js";
import { buildTaskNotesMdbaseResources, buildTaskNotesMdbaseTypePack } from "../dist/esm/mdbase.js";
import {
buildTaskNotesStarterResources,
TASKNOTES_STARTER_TYPE_VERSION,
} from "../dist/esm/starter.js";

// The published tasknotes.task 0.3.0-rc.16 pack (mdbase-contracts 9cda221e,
// https://mdbase.dev/contracts/packs/tasknotes.task/0.3.0-rc.16.json). If the
// The published tasknotes.task 0.3.0-rc.17 pack
// (https://mdbase.dev/contracts/packs/tasknotes.task/0.3.0-rc.17.json). If the
// starter changes, publish a new starter type version in mdbase-contracts and
// update these digests together; collections upgrade by merging against the
// previous starter, so an unintended change here reaches every collection.
const PUBLISHED_RC16_RESOURCES = {
const PUBLISHED_RC17_RESOURCES = {
"_contracts/tasknotes.task.md":
"sha256:ecadd00700e249940f4244aaa5b5985a96557d13340463ec491dfa5e89b032a4",
"_types/task.md":
"sha256:2b6016c3543c0dfff491cc6262aed3afd4d672360484174042a261acd1d8ff5f",
"sha256:b3cf8dbe8885b331c494d2eeb36abfb052890e06b757877d655c269e666487dc",
"_schemas/tasknotes/tasknotes-task.schema.json":
"sha256:aec95578a093be95cbce0fb500964e7aeaeeacc611624fc2893074637dbd4125",
"_schemas/tasknotes/tasknotes-task-binding.schema.json":
Expand All @@ -26,7 +26,7 @@ test("the starter reproduces the published TaskNotes pack byte for byte", async
const pack = await buildTaskNotesMdbaseTypePack(buildTaskNotesStarterResources());
assert.deepEqual(
Object.fromEntries(pack.manifest.resources.map((resource) => [resource.target, resource.digest])),
PUBLISHED_RC16_RESOURCES,
PUBLISHED_RC17_RESOURCES,
);
});

Expand All @@ -39,3 +39,13 @@ test("the starter keeps cancelled as a skipped status and declares only implemen
assert.equal(binding.status.default_skipped, "cancelled");
assert.deepEqual(binding.profiles, ["core-lite", "recurrence", "materialized-occurrences"]);
});

test("types written for migrated v0.2 collections declare assignees as the starter does", () => {
const starter = buildTaskNotesStarterResources().type;
const legacy = buildTaskNotesMdbaseResources({ legacyCompatibility: true }).type;
assert.equal(legacy.version, starter.version);
assert.deepEqual(
legacy.schema.value.properties.assignees,
starter.schema.value.properties.assignees,
);
});
Loading