@micacms/runtime 0.6.0-rc.2 → 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/app.d.ts.map +1 -1
- package/dist/app.js +5 -0
- package/dist/app.js.map +1 -1
- package/dist/context.d.ts +17 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +62 -18
- package/dist/context.js.map +1 -1
- package/dist/http.d.ts +2 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +5 -0
- package/dist/http.js.map +1 -1
- package/dist/lib/file-type.d.ts +2 -0
- package/dist/lib/file-type.d.ts.map +1 -1
- package/dist/lib/file-type.js +3 -1
- package/dist/lib/file-type.js.map +1 -1
- package/dist/routes/admin-content.d.ts +1 -1
- package/dist/routes/admin-content.d.ts.map +1 -1
- package/dist/routes/admin-content.js +15 -0
- package/dist/routes/admin-content.js.map +1 -1
- package/dist/routes/admin-media.d.ts +5 -3
- package/dist/routes/admin-media.d.ts.map +1 -1
- package/dist/routes/admin-media.js +72 -2
- package/dist/routes/admin-media.js.map +1 -1
- package/dist/routes/admin-staging.d.ts.map +1 -1
- package/dist/routes/admin-staging.js +18 -7
- package/dist/routes/admin-staging.js.map +1 -1
- package/dist/routes/admin-system.d.ts.map +1 -1
- package/dist/routes/admin-system.js +4 -0
- package/dist/routes/admin-system.js.map +1 -1
- package/dist/services/changes.d.ts +19 -4
- package/dist/services/changes.d.ts.map +1 -1
- package/dist/services/changes.js +60 -1
- package/dist/services/changes.js.map +1 -1
- package/dist/services/content-store.d.ts +27 -6
- package/dist/services/content-store.d.ts.map +1 -1
- package/dist/services/content-store.js +231 -18
- package/dist/services/content-store.js.map +1 -1
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/media.js +4 -1
- package/dist/services/media.js.map +1 -1
- package/dist/services/staging.d.ts.map +1 -1
- package/dist/services/staging.js +26 -14
- package/dist/services/staging.js.map +1 -1
- package/dist/workspace/developer.d.ts +2 -1
- package/dist/workspace/developer.d.ts.map +1 -1
- package/dist/workspace/developer.js +19 -4
- package/dist/workspace/developer.js.map +1 -1
- package/dist/workspace/policy.d.ts +1 -1
- package/dist/workspace/policy.d.ts.map +1 -1
- package/dist/workspace/policy.js.map +1 -1
- package/dist/workspace/tree.d.ts +15 -2
- package/dist/workspace/tree.d.ts.map +1 -1
- package/dist/workspace/tree.js +29 -6
- package/dist/workspace/tree.js.map +1 -1
- package/package.json +2 -2
- package/src/app.ts +5 -0
- package/src/context.ts +69 -20
- package/src/http.ts +6 -0
- package/src/lib/file-type.ts +4 -1
- package/src/routes/admin-content.ts +16 -2
- package/src/routes/admin-media.ts +74 -3
- package/src/routes/admin-staging.ts +22 -7
- package/src/routes/admin-system.ts +4 -0
- package/src/services/changes.ts +84 -5
- package/src/services/content-store.ts +243 -25
- package/src/services/media.ts +7 -1
- package/src/services/staging.ts +30 -14
- package/src/workspace/developer.ts +22 -5
- package/src/workspace/policy.ts +1 -1
- package/src/workspace/tree.ts +35 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-store.js","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Collections and singletons over the working tree's files: validation, slugs,\n * entry files and `_order.json`. An entry is its slug and path, Git keeps the\n * history, a save is a draft, and only Publish makes a commit.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport {\n ContentPathError,\n DOCUMENT_EXTENSION,\n INDEX_FILE,\n ORDER_FILE,\n collectionEntryPath,\n entryFiles,\n entryLayout,\n entryTitle,\n isValidSlug,\n collectReferences,\n readCollection,\n readSingleton,\n relativizeMedia,\n resolveSlugField,\n serializeEntry,\n singletonPath,\n slugify,\n uniqueSlug,\n validateFields,\n withDefaults,\n type Collection,\n type ContentEntry,\n type FieldIssue,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nimport { ConflictError, FilesError, type ContentFiles, type FileChange } from \"../workspace/files.js\";\n\nexport class ContentError extends FilesError {\n override name = \"ContentError\";\n}\n\n/** A write to a collection or singleton that this build of Mica cannot interpret. */\nexport class ReadOnlyError extends ContentError {\n override name = \"ReadOnlyError\";\n}\n\nexport class ValidationError extends ContentError {\n override name = \"ValidationError\";\n constructor(\n message: string,\n readonly issues: FieldIssue[],\n ) {\n super(message);\n }\n}\n\nexport interface SaveEntryInput {\n data: Record<string, unknown>;\n /** The entry being replaced. Absent when creating. */\n slug?: string;\n /** The revision `slug` had when the editor opened it. Absent writes unconditionally. */\n expectedRevision?: string | null;\n}\n\nexport interface SavedEntry {\n slug: string;\n title: string;\n /** The entry's revision after this save, for the next save. A file revision, not a commit. */\n revision: string;\n /** True when the save moved the entry's files. */\n renamed: boolean;\n}\n\n/** `readOnly` comes from `readOnlyParts`: its collections and singletons are read, never written. */\nexport function createContentStore(site: Site, workspace: ContentFiles, readOnly: ReadonlyMap<string, string> = new Map()) {\n const requireCollection = (name: string): Collection => {\n const def = site.collections[name];\n if (!def) throw new ContentError(`Unknown collection \"${name}\".`);\n return def;\n };\n\n const requireSingleton = (name: string): Singleton => {\n const def = site.singletons[name];\n if (!def) throw new ContentError(`Unknown singleton \"${name}\".`);\n return def;\n };\n\n const writable = <T>(def: T, key: string): T => {\n const reason = readOnly.get(key);\n if (reason) throw new ReadOnlyError(reason);\n return def;\n };\n\n const validate = (\n def: Collection | Singleton,\n data: unknown,\n what: string,\n ): Record<string, unknown> => {\n const result = validateFields(def.fields, data);\n if (!result.ok) {\n throw new ValidationError(\n `${what} cannot be saved until these fields are correct.`,\n result.issues,\n );\n }\n return result.data as Record<string, unknown>;\n };\n\n /**\n * Deletes for `.mdoc` files not in `keep`, so a cleared rich-text field does\n * not come back on the next read. Media stays: an unreferenced upload is not\n * garbage.\n */\n const staleDocuments = async (base: string, keep: Set<string>): Promise<FileChange[]> => {\n const changes: FileChange[] = [];\n\n for (const item of await workspace.list(base)) {\n if (item.type !== \"file\") continue;\n if (!item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n if (keep.has(item.name)) continue;\n changes.push({ type: \"delete\", path: `${base}/${item.name}` });\n }\n\n return changes;\n };\n\n /**\n * The document's revision after a write. Throws when the file is absent,\n * because an empty token could never match.\n */\n const revisionAfterWrite = async (dataFile: string, folder: string | null): Promise<string> => {\n const revision = await documentRevision(dataFile, folder);\n if (revision === null) {\n throw new ContentError(`Saved, but \"${dataFile}\" is not in the repository afterwards.`);\n }\n return revision;\n };\n\n /** The folder whose files make up one document, or null for a one-file document. */\n const folderOf = (fields: FieldMap, base: string): string | null =>\n entryLayout(fields) === \"directory\" ? base : null;\n\n /**\n * The revision of a whole document: its data file and, in the folder layout,\n * every file of its folder. A change to any one of them makes the next save a\n * conflict, not an overwrite.\n */\n const documentRevision = async (dataFile: string, folder: string | null): Promise<string | null> => {\n const files = await documentFiles(dataFile, folder);\n if (files === null) return null;\n if (folder === null) return files.get(dataFile)!;\n const parts = [...files].map(([path, revision]) => `${path}\\u0000${revision}`);\n return createHash(\"sha256\").update(parts.sort().join(\"\\n\")).digest(\"hex\").slice(0, 16);\n };\n\n /** Every file of a document with its revision, or null when its data file is absent. */\n const documentFiles = async (dataFile: string, folder: string | null): Promise<Map<string, string> | null> => {\n const data = await workspace.revision(dataFile);\n if (data === null) return null;\n const files = new Map([[dataFile, data]]);\n if (folder === null) return files;\n const walk = async (dir: string): Promise<void> => {\n for (const item of await workspace.list(dir)) {\n const child = `${dir}/${item.name}`;\n if (item.type === \"directory\") await walk(child);\n else files.set(child, (await workspace.revision(child)) ?? \"\");\n }\n };\n await walk(folder);\n return files;\n };\n\n /**\n * Refuses when the document changed since the editor read `expected`, and\n * returns every file's revision for `apply`, which checks them all again\n * under the write lock.\n */\n const expectDocument = async (\n expected: string | null | undefined,\n dataFile: string,\n folder: string | null,\n ): Promise<Record<string, string | null> | undefined> => {\n if (expected === undefined) return undefined;\n const files = await documentFiles(dataFile, folder);\n if ((await documentRevision(dataFile, folder)) !== expected) {\n throw new ConflictError(\"This content changed after you opened it. Reload to see the current version.\", [\n dataFile,\n ]);\n }\n return files === null ? { [dataFile]: null } : Object.fromEntries(files);\n };\n\n /**\n * The order file with a renamed slug in its old place, so the entry does not\n * drop to the end of the list.\n */\n const renameInOrder = async (name: string, from: string, to: string): Promise<FileChange | null> => {\n const path = `${name}/${ORDER_FILE}`;\n const raw = await workspace.read(path);\n if (raw === null) return null;\n\n let listed: unknown;\n try {\n listed = JSON.parse(raw);\n } catch {\n // Reading the collection reports a malformed order file; a save need not.\n return null;\n }\n if (!Array.isArray(listed) || !listed.includes(from)) return null;\n\n const order = listed.map((slug: unknown) => (slug === from ? to : slug));\n return { type: \"write\", path, contents: `${JSON.stringify(order, null, 2)}\\n` };\n };\n\n /** Slugs already taken in a collection, so a new one can be made unique. */\n const takenSlugs = async (def: Collection, name: string, except?: string): Promise<string[]> => {\n const entries = await readCollection(workspace, def, name);\n return entries.map((entry) => entry.slug).filter((slug) => slug !== except);\n };\n\n return {\n async list(collectionName: string): Promise<ContentEntry[]> {\n return readCollection(workspace, requireCollection(collectionName), collectionName);\n },\n\n async get(collectionName: string, slug: string): Promise<ContentEntry | null> {\n const entries = await this.list(collectionName);\n return entries.find((entry) => entry.slug === slug) ?? null;\n },\n\n /** The revision an editor must send back with a save of this entry. */\n async revisionOf(collectionName: string, slug: string): Promise<string | null> {\n const def = requireCollection(collectionName);\n return documentRevision(\n dataPath(def, collectionName, slug),\n folderOf(def.fields, collectionEntryPath(def.fields, collectionName, slug)),\n );\n },\n\n /** The revision an editor must send back with a save of this singleton. */\n async revisionOfSingleton(name: string): Promise<string | null> {\n const def = requireSingleton(name);\n return documentRevision(singletonDataPath(def, name), folderOf(def.fields, singletonPath(def.fields, name)));\n },\n\n async getSingleton(name: string): Promise<Record<string, unknown>> {\n const def = requireSingleton(name);\n const data = await readSingleton(workspace, def, name);\n // A singleton with no file yet opens as an empty form, not a 404.\n return withDefaults(def.fields, data);\n },\n\n async save(collectionName: string, input: SaveEntryInput): Promise<SavedEntry> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n const data = validate(def, input.data, \"This entry\");\n\n const slugKey = resolveSlugField(def)!;\n const requested = pickSlug(def, data, input.slug);\n const slug =\n requested === input.slug\n ? requested\n : uniqueSlug(requested, await takenSlugs(def, collectionName, input.slug));\n\n data[slugKey] = slug;\n\n const base = collectionEntryPath(def.fields, collectionName, slug);\n const previousBase =\n input.slug && input.slug !== slug\n ? collectionEntryPath(def.fields, collectionName, input.slug)\n : null;\n\n // The editor uses root-relative media paths; store them relative to the\n // entry (the old directory on a rename).\n const serialized = serializeEntry(\n def.fields,\n mediaForDisk(def.fields, data, previousBase ?? base),\n );\n const files = entryFiles(def.fields, base, serialized);\n\n const changes: FileChange[] = Object.entries(files).map(([path, contents]) => ({\n type: \"write\",\n path,\n contents,\n }));\n\n if (previousBase) {\n changes.push(\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: previousBase }\n : { type: \"delete\", path: previousBase },\n );\n await moveMedia(workspace, previousBase, base, changes);\n const order = await renameInOrder(collectionName, input.slug!, slug);\n if (order) changes.push(order);\n } else if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n const title = entryTitle(def, data);\n await workspace.apply({\n changes,\n expect: await expectDocument(\n input.expectedRevision,\n dataPath(def, collectionName, input.slug ?? slug),\n folderOf(def.fields, previousBase ?? base),\n ),\n });\n\n const revision = await revisionAfterWrite(dataPath(def, collectionName, slug), folderOf(def.fields, base));\n\n return { slug, title, revision, renamed: previousBase !== null };\n },\n\n async remove(\n collectionName: string,\n slug: string,\n options: { expectedRevision?: string | null } = {},\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n // One path segment: an empty slug or `a/b` would delete more than one entry.\n if (slug === \"\" || slug.includes(\"/\") || slug === \".\" || slug === \"..\") {\n throw new ContentPathError(`\"${slug}\" is not an entry of \"${collectionName}\".`);\n }\n const base = collectionEntryPath(def.fields, collectionName, slug);\n\n await workspace.apply({\n changes: [\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: base }\n : { type: \"delete\", path: base },\n ],\n expect: await expectDocument(\n options.expectedRevision,\n dataPath(def, collectionName, slug),\n folderOf(def.fields, base),\n ),\n });\n },\n\n async saveSingleton(\n name: string,\n payload: unknown,\n options: { expectedRevision?: string | null } = {},\n ): Promise<{ revision: string }> {\n const def = writable(requireSingleton(name), `singletons.${name}`);\n const data = validate(def, payload, `The \"${def.label}\" settings`);\n\n const base = singletonPath(def.fields, name);\n const serialized = serializeEntry(def.fields, mediaForDisk(def.fields, data, base));\n\n const changes: FileChange[] = Object.entries(entryFiles(def.fields, base, serialized)).map(\n ([path, contents]) => ({ type: \"write\", path, contents }),\n );\n\n if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n await workspace.apply({\n changes,\n expect: await expectDocument(options.expectedRevision, singletonDataPath(def, name), folderOf(def.fields, base)),\n });\n\n return { revision: await revisionAfterWrite(singletonDataPath(def, name), folderOf(def.fields, base)) };\n },\n\n async reorder(\n collectionName: string,\n slugs: string[],\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n if (!def.sortable) {\n throw new ContentError(`The \"${def.label}\" collection is not manually sortable.`);\n }\n\n await workspace.apply({\n changes: [\n {\n type: \"write\",\n path: `${collectionName}/${ORDER_FILE}`,\n contents: `${JSON.stringify(slugs, null, 2)}\\n`,\n },\n ],\n });\n },\n\n requireCollection,\n requireSingleton,\n\n /** Slug the server would assign, so the editor's URL preview matches it. */\n async suggestSlug(collectionName: string, source: string, except?: string): Promise<string> {\n const def = requireCollection(collectionName);\n return uniqueSlug(slugify(source), await takenSlugs(def, collectionName, except));\n },\n\n /** Entries pointing at `slug`, so the editor can warn before a delete. */\n async findReferences(collectionName: string, slug: string) {\n const results: Array<{ collection: string; slug: string; title: string }> = [];\n\n for (const [name, def] of Object.entries(site.collections)) {\n if (!linksTo(def.fields, collectionName)) continue;\n for (const entry of await readCollection(workspace, def, name)) {\n const points = collectReferences(def.fields, entry.data).some(\n (reference) =>\n reference.kind === \"relation\" && reference.collection === collectionName && reference.slug === slug,\n );\n if (points) {\n results.push({ collection: name, slug: entry.slug, title: entryTitle(def, entry.data) });\n }\n }\n }\n\n return results;\n },\n };\n}\n\nexport type ContentStore = ReturnType<typeof createContentStore>;\n\n/** Whether a field map has a relation to `collection`, nested fields included. */\nfunction linksTo(fields: FieldMap, collection: string): boolean {\n return Object.values(fields).some(\n (field) =>\n (field.kind === \"relation\" && field.collection === collection) ||\n ((field.kind === \"object\" || field.kind === \"repeater\") && linksTo(field.fields, collection)),\n );\n}\n\n/**\n * Media paths as the repository stores them. Only a directory entry has\n * colocated media to relativize.\n */\nfunction mediaForDisk(\n fields: FieldMap,\n data: Record<string, unknown>,\n base: string,\n): Record<string, unknown> {\n return entryLayout(fields) === \"directory\" ? relativizeMedia(fields, data, base) : data;\n}\n\nfunction dataPath(def: Collection, collection: string, slug: string): string {\n const base = collectionEntryPath(def.fields, collection, slug);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\nfunction singletonDataPath(def: Singleton, name: string): string {\n const base = singletonPath(def.fields, name);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\n/** The slug from the payload's slug field, or one derived from the title. */\nfunction pickSlug(\n def: Collection,\n data: Record<string, unknown>,\n current: string | undefined,\n): string {\n const slugKey = resolveSlugField(def)!;\n const fromData = data[slugKey];\n\n const candidate =\n (typeof fromData === \"string\" && fromData.trim()) || slugify(entryTitle(def, data));\n\n if (!candidate) {\n throw new ContentError(\"An entry needs a title or a URL before it can be saved.\");\n }\n\n const normalised = isValidSlug(candidate) ? candidate : slugify(candidate);\n if (!normalised) {\n throw new ContentError(`\"${candidate}\" cannot be turned into a URL. Use letters and numbers.`);\n }\n\n return normalised === current ? current : normalised;\n}\n\n/** Copies colocated media to the renamed entry; the caller deletes the old directory. */\nasync function moveMedia(\n files: ContentFiles,\n from: string,\n to: string,\n changes: FileChange[],\n): Promise<void> {\n for (const item of await files.list(`${from}/media`)) {\n if (item.type !== \"file\") continue;\n\n const bytes = await files.readBinary(`${from}/media/${item.name}`);\n if (bytes === null) continue;\n\n changes.push({ type: \"write\", path: `${to}/media/${item.name}`, contents: bytes });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"content-store.js","sourceRoot":"","sources":["../../src/services/content-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Collections and singletons over the working tree's files: validation, slugs,\n * entry files and `_order.json`. An entry is its slug and path, Git keeps the\n * history, a save is a draft, and only Publish makes a commit.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { posix } from \"node:path\";\n\nimport {\n ContentPathError,\n DOCUMENT_EXTENSION,\n INDEX_FILE,\n ORDER_FILE,\n collectionEntryPath,\n entryFiles,\n entryLayout,\n entryTitle,\n isValidSlug,\n collectReferences,\n readCollection,\n mapMedia,\n readCollectionEntry,\n readSingleton,\n relativizeMedia,\n relinkReferences,\n resolveSlugField,\n resolveTitleField,\n serializeEntry,\n singletonPath,\n slugify,\n uniqueSlug,\n validateFields,\n withDefaults,\n type Collection,\n type ContentEntry,\n type FieldIssue,\n type FieldMap,\n type Singleton,\n type Site,\n} from \"@micacms/core\";\n\nimport { ConflictError, FilesError, type ContentFiles, type FileChange } from \"../workspace/files.js\";\n\nexport class ContentError extends FilesError {\n override name = \"ContentError\";\n}\n\n/** A write to a collection or singleton that this build of Mica cannot interpret. */\nexport class ReadOnlyError extends ContentError {\n override name = \"ReadOnlyError\";\n}\n\nexport class ValidationError extends ContentError {\n override name = \"ValidationError\";\n constructor(\n message: string,\n readonly issues: FieldIssue[],\n ) {\n super(message);\n }\n}\n\nexport interface SaveEntryInput {\n data: Record<string, unknown>;\n /** The entry being replaced. Absent when creating. */\n slug?: string;\n /** The revision `slug` had when the editor opened it. Absent writes unconditionally. */\n expectedRevision?: string | null;\n /** On a URL change: point the relations in other entries and settings at the new URL, in the same write. */\n updateLinks?: boolean;\n}\n\n/** An entry or settings page that links to an entry. `readOnly`: this build cannot change its links. */\nexport type Referrer =\n | { kind: \"entry\"; collection: string; slug: string; title: string; readOnly?: true }\n | { kind: \"singleton\"; name: string; title: string; readOnly?: true };\n\nexport interface SavedEntry {\n slug: string;\n title: string;\n /** The entry's revision after this save, for the next save. A file revision, not a commit. */\n revision: string;\n /** True when the save moved the entry's files. */\n renamed: boolean;\n /** How many other entries and settings pages now link to the new URL. */\n relinked: number;\n}\n\n/** `readOnly` comes from `readOnlyParts`: its collections and singletons are read, never written. */\nexport function createContentStore(site: Site, workspace: ContentFiles, readOnly: ReadonlyMap<string, string> = new Map()) {\n const requireCollection = (name: string): Collection => {\n const def = site.collections[name];\n if (!def) throw new ContentError(`Unknown collection \"${name}\".`);\n return def;\n };\n\n const requireSingleton = (name: string): Singleton => {\n const def = site.singletons[name];\n if (!def) throw new ContentError(`Unknown singleton \"${name}\".`);\n return def;\n };\n\n const writable = <T>(def: T, key: string): T => {\n const reason = readOnly.get(key);\n if (reason) throw new ReadOnlyError(reason);\n return def;\n };\n\n const validate = (\n def: Collection | Singleton,\n data: unknown,\n what: string,\n ): Record<string, unknown> => {\n // Save keeps a half-finished entry; Publish checks `required` (decision \"Publish a selection\").\n const result = validateFields(def.fields, data, { enforceRequired: false });\n if (!result.ok) {\n throw new ValidationError(\n `${what} cannot be saved until these fields are correct.`,\n result.issues,\n );\n }\n return result.data as Record<string, unknown>;\n };\n\n /**\n * Deletes for `.mdoc` files not in `keep`, so a cleared rich-text field does\n * not come back on the next read. Media stays: an unreferenced upload is not\n * garbage.\n */\n const staleDocuments = async (base: string, keep: Set<string>): Promise<FileChange[]> => {\n const changes: FileChange[] = [];\n\n for (const item of await workspace.list(base)) {\n if (item.type !== \"file\") continue;\n if (!item.name.endsWith(DOCUMENT_EXTENSION)) continue;\n if (keep.has(item.name)) continue;\n changes.push({ type: \"delete\", path: `${base}/${item.name}` });\n }\n\n return changes;\n };\n\n /**\n * The document's revision after a write. Throws when the file is absent,\n * because an empty token could never match.\n */\n const revisionAfterWrite = async (dataFile: string, folder: string | null): Promise<string> => {\n const revision = await documentRevision(dataFile, folder);\n if (revision === null) {\n throw new ContentError(`Saved, but \"${dataFile}\" is not in the repository afterwards.`);\n }\n return revision;\n };\n\n /** The folder whose files make up one document, or null for a one-file document. */\n const folderOf = (fields: FieldMap, base: string): string | null =>\n entryLayout(fields) === \"directory\" ? base : null;\n\n /**\n * The revision of a whole document: its data file and, in the folder layout,\n * every file of its folder. A change to any one of them makes the next save a\n * conflict, not an overwrite.\n */\n const documentRevision = async (dataFile: string, folder: string | null): Promise<string | null> => {\n const files = await documentFiles(dataFile, folder);\n if (files === null) return null;\n if (folder === null) return files.get(dataFile)!;\n const parts = [...files].map(([path, revision]) => `${path}\\u0000${revision}`);\n return createHash(\"sha256\").update(parts.sort().join(\"\\n\")).digest(\"hex\").slice(0, 16);\n };\n\n /** Every file of a document with its revision, or null when its data file is absent. */\n const documentFiles = async (dataFile: string, folder: string | null): Promise<Map<string, string> | null> => {\n const data = await workspace.revision(dataFile);\n if (data === null) return null;\n const files = new Map([[dataFile, data]]);\n if (folder === null) return files;\n const walk = async (dir: string): Promise<void> => {\n for (const item of await workspace.list(dir)) {\n const child = `${dir}/${item.name}`;\n if (item.type === \"directory\") await walk(child);\n else files.set(child, (await workspace.revision(child)) ?? \"\");\n }\n };\n await walk(folder);\n return files;\n };\n\n /**\n * Refuses when the document changed since the editor read `expected`, and\n * returns every file's revision for `apply`, which checks them all again\n * under the write lock.\n */\n const expectDocument = async (\n expected: string | null | undefined,\n dataFile: string,\n folder: string | null,\n ): Promise<Record<string, string | null> | undefined> => {\n if (expected === undefined) return undefined;\n const files = await documentFiles(dataFile, folder);\n if ((await documentRevision(dataFile, folder)) !== expected) {\n throw new ConflictError(\"This content changed after you opened it. Reload to see the current version.\", [\n dataFile,\n ]);\n }\n return files === null ? { [dataFile]: null } : Object.fromEntries(files);\n };\n\n /**\n * The order file with a renamed slug in its old place, so the entry does not\n * drop to the end of the list.\n */\n const renameInOrder = async (name: string, from: string, to: string): Promise<FileChange | null> => {\n const path = `${name}/${ORDER_FILE}`;\n const raw = await workspace.read(path);\n if (raw === null) return null;\n\n let listed: unknown;\n try {\n listed = JSON.parse(raw);\n } catch {\n // Reading the collection reports a malformed order file; a save need not.\n return null;\n }\n if (!Array.isArray(listed) || !listed.includes(from)) return null;\n\n const order = listed.map((slug: unknown) => (slug === from ? to : slug));\n return { type: \"write\", path, contents: `${JSON.stringify(order, null, 2)}\\n` };\n };\n\n /** Entries and settings pages whose relations point at `collection`/`slug`, with what a relink needs. */\n const referrers = async (collection: string, slug: string) => {\n const found: Array<{\n referrer: Referrer;\n fields: FieldMap;\n base: string;\n dataFile: string;\n /** Reads the data again, so that a relink starts from what is on disk now. */\n read: () => Promise<Record<string, unknown> | null>;\n }> = [];\n const readOnlyMark = (key: string) => (readOnly.has(key) ? { readOnly: true as const } : {});\n const points = (fields: FieldMap, data: Record<string, unknown>) =>\n collectReferences(fields, data).some(\n (reference) => reference.kind === \"relation\" && reference.collection === collection && reference.slug === slug,\n );\n\n for (const [name, def] of Object.entries(site.collections)) {\n if (!linksTo(def.fields, collection)) continue;\n for (const entry of await readCollection(workspace, def, name)) {\n if (!points(def.fields, entry.data)) continue;\n found.push({\n referrer: {\n kind: \"entry\",\n collection: name,\n slug: entry.slug,\n title: entryTitle(def, entry.data),\n ...readOnlyMark(`collections.${name}`),\n },\n fields: def.fields,\n base: collectionEntryPath(def.fields, name, entry.slug),\n dataFile: dataPath(def, name, entry.slug),\n read: () => readCollectionEntry(workspace, def, name, entry.slug),\n });\n }\n }\n for (const [name, def] of Object.entries(site.singletons)) {\n if (!linksTo(def.fields, collection)) continue;\n const data = await readSingleton(workspace, def, name);\n if (!data || !points(def.fields, data)) continue;\n found.push({\n referrer: { kind: \"singleton\", name, title: def.label, ...readOnlyMark(`singletons.${name}`) },\n fields: def.fields,\n base: singletonPath(def.fields, name),\n dataFile: singletonDataPath(def, name),\n read: () => readSingleton(workspace, def, name),\n });\n }\n return found;\n };\n\n /**\n * Files that a new entry uses from `<collection>/<other slug>/media/`:\n * uploads made before the URL was final. Each gets a free name in the\n * entry's own `media/` folder. From a folder where no entry lives they move;\n * from another entry's folder (a colleague saved that URL first) they are\n * copied, because that entry may use them too.\n */\n const stagedUploads = async (def: Collection, collection: string, data: Record<string, unknown>, base: string) => {\n const moves: Array<{ from: string; to: string; bytes: Uint8Array; keep: boolean }> = [];\n const taken = new Set((await workspace.list(`${base}/media`)).map((item) => item.name));\n for (const reference of collectReferences(def.fields, data)) {\n if (reference.kind !== \"media\") continue;\n const match = /^([^/]+)\\/([^/]+)\\/media\\/([^/]+)$/.exec(reference.path);\n if (!match || match[1] !== collection || `${collection}/${match[2]}` === base) continue;\n if (moves.some((move) => move.from === reference.path)) continue;\n const keep = (await workspace.read(dataPath(def, collection, match[2]!))) !== null;\n const bytes = await workspace.readBinary(reference.path);\n if (bytes === null) continue;\n const extension = match[3]!.includes(\".\") ? match[3]!.slice(match[3]!.lastIndexOf(\".\")) : \"\";\n const stem = match[3]!.slice(0, match[3]!.length - extension.length);\n let name = match[3]!;\n for (let n = 2; taken.has(name); n += 1) name = `${stem}-${n}${extension}`;\n taken.add(name);\n moves.push({ from: reference.path, to: `${base}/media/${name}`, bytes, keep });\n }\n return moves;\n };\n\n /** Slugs already taken in a collection, so a new one can be made unique. */\n const takenSlugs = async (def: Collection, name: string, except?: string): Promise<string[]> => {\n const entries = await readCollection(workspace, def, name);\n return entries.map((entry) => entry.slug).filter((slug) => slug !== except);\n };\n\n return {\n async list(collectionName: string): Promise<ContentEntry[]> {\n return readCollection(workspace, requireCollection(collectionName), collectionName);\n },\n\n async get(collectionName: string, slug: string): Promise<ContentEntry | null> {\n const entries = await this.list(collectionName);\n return entries.find((entry) => entry.slug === slug) ?? null;\n },\n\n /** The revision an editor must send back with a save of this entry. */\n async revisionOf(collectionName: string, slug: string): Promise<string | null> {\n const def = requireCollection(collectionName);\n return documentRevision(\n dataPath(def, collectionName, slug),\n folderOf(def.fields, collectionEntryPath(def.fields, collectionName, slug)),\n );\n },\n\n /** The revision an editor must send back with a save of this singleton. */\n async revisionOfSingleton(name: string): Promise<string | null> {\n const def = requireSingleton(name);\n return documentRevision(singletonDataPath(def, name), folderOf(def.fields, singletonPath(def.fields, name)));\n },\n\n async getSingleton(name: string): Promise<Record<string, unknown>> {\n const def = requireSingleton(name);\n const data = await readSingleton(workspace, def, name);\n // A singleton with no file yet opens as an empty form, not a 404.\n return withDefaults(def.fields, data);\n },\n\n async save(collectionName: string, input: SaveEntryInput): Promise<SavedEntry> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n const data = validate(def, input.data, \"This entry\");\n\n const slugKey = resolveSlugField(def)!;\n const requested = pickSlug(def, data, input.slug);\n const slug =\n requested === input.slug\n ? requested\n : uniqueSlug(requested, await takenSlugs(def, collectionName, input.slug));\n\n data[slugKey] = slug;\n const base = collectionEntryPath(def.fields, collectionName, slug);\n // A new entry's uploads went into the folder of the URL it had then.\n const staged = input.slug === undefined ? await stagedUploads(def, collectionName, data, base) : [];\n if (staged.length > 0) {\n const moved = new Map(staged.map((item) => [item.from, item.to]));\n Object.assign(data, mapMedia(def.fields, data, (value) => (typeof value === \"string\" ? (moved.get(value) ?? value) : value)));\n }\n // An entry that links to itself follows its own new URL.\n const own = previousLinks(input, slug) ? relinkReferences(def.fields, data, collectionName, input.slug!, slug) : null;\n if (own) Object.assign(data, own);\n\n const previousBase =\n input.slug && input.slug !== slug\n ? collectionEntryPath(def.fields, collectionName, input.slug)\n : null;\n\n // The editor uses root-relative media paths; store them relative to the\n // entry (the old directory on a rename).\n const serialized = serializeEntry(\n def.fields,\n mediaForDisk(def.fields, data, previousBase ?? base),\n );\n const files = entryFiles(def.fields, base, serialized);\n\n const changes: FileChange[] = Object.entries(files).map(([path, contents]) => ({\n type: \"write\",\n path,\n contents,\n }));\n for (const item of staged) {\n changes.push({ type: \"write\", path: item.to, contents: item.bytes });\n if (!item.keep) changes.push({ type: \"delete\", path: item.from });\n }\n\n if (previousBase) {\n changes.push(\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: previousBase }\n : { type: \"delete\", path: previousBase },\n );\n await moveMedia(workspace, previousBase, base, changes);\n const order = await renameInOrder(collectionName, input.slug!, slug);\n if (order) changes.push(order);\n } else if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n // Only the data file of each referrer changes; its rich text and media stay as they are.\n // A read-only part is left alone: this build may not write it.\n let relinked = 0;\n const referrerRevisions: Record<string, string | null> = {};\n if (previousLinks(input, slug)) {\n for (const found of await referrers(collectionName, input.slug!)) {\n if (found.referrer.readOnly) continue;\n if (found.referrer.kind === \"entry\" && found.referrer.collection === collectionName && found.referrer.slug === input.slug) continue;\n // The revision before the read: a save in between makes the write refuse, not overwrite it.\n const revision = await workspace.revision(found.dataFile);\n const current = await found.read();\n const next = current && relinkReferences(found.fields, current, collectionName, input.slug!, slug);\n if (!next) continue;\n const written = entryFiles(found.fields, found.base, serializeEntry(found.fields, mediaForDisk(found.fields, next, found.base)));\n changes.push({ type: \"write\", path: found.dataFile, contents: written[found.dataFile]! });\n referrerRevisions[found.dataFile] = revision;\n relinked += 1;\n }\n }\n\n const title = entryTitle(def, data);\n const expected = await expectDocument(\n input.expectedRevision,\n dataPath(def, collectionName, input.slug ?? slug),\n folderOf(def.fields, previousBase ?? base),\n );\n try {\n await workspace.apply({\n changes,\n expect: relinked > 0 ? { ...expected, ...referrerRevisions } : expected,\n });\n } catch (error) {\n if (error instanceof ConflictError && error.paths.some((path) => path in referrerRevisions)) {\n throw new ConflictError(\"An entry or page that links here changed while you saved. Save again.\", error.paths);\n }\n throw error;\n }\n\n const revision = await revisionAfterWrite(dataPath(def, collectionName, slug), folderOf(def.fields, base));\n\n return { slug, title, revision, renamed: previousBase !== null, relinked };\n },\n\n async remove(\n collectionName: string,\n slug: string,\n options: { expectedRevision?: string | null } = {},\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n // One path segment: an empty slug or `a/b` would delete more than one entry.\n if (slug === \"\" || slug.includes(\"/\") || slug === \".\" || slug === \"..\") {\n throw new ContentPathError(`\"${slug}\" is not an entry of \"${collectionName}\".`);\n }\n const base = collectionEntryPath(def.fields, collectionName, slug);\n\n await workspace.apply({\n changes: [\n entryLayout(def.fields) === \"directory\"\n ? { type: \"deleteTree\", path: base }\n : { type: \"delete\", path: base },\n ],\n expect: await expectDocument(\n options.expectedRevision,\n dataPath(def, collectionName, slug),\n folderOf(def.fields, base),\n ),\n });\n },\n\n async saveSingleton(\n name: string,\n payload: unknown,\n options: { expectedRevision?: string | null } = {},\n ): Promise<{ revision: string }> {\n const def = writable(requireSingleton(name), `singletons.${name}`);\n const data = validate(def, payload, `The \"${def.label}\" settings`);\n\n const base = singletonPath(def.fields, name);\n const serialized = serializeEntry(def.fields, mediaForDisk(def.fields, data, base));\n\n const changes: FileChange[] = Object.entries(entryFiles(def.fields, base, serialized)).map(\n ([path, contents]) => ({ type: \"write\", path, contents }),\n );\n\n if (entryLayout(def.fields) === \"directory\") {\n changes.push(...(await staleDocuments(base, new Set(Object.keys(serialized.documents)))));\n }\n\n await workspace.apply({\n changes,\n expect: await expectDocument(options.expectedRevision, singletonDataPath(def, name), folderOf(def.fields, base)),\n });\n\n return { revision: await revisionAfterWrite(singletonDataPath(def, name), folderOf(def.fields, base)) };\n },\n\n async reorder(\n collectionName: string,\n slugs: string[],\n ): Promise<void> {\n const def = writable(requireCollection(collectionName), `collections.${collectionName}`);\n if (!def.sortable) {\n throw new ContentError(`The \"${def.label}\" collection is not manually sortable.`);\n }\n\n await workspace.apply({\n changes: [\n {\n type: \"write\",\n path: `${collectionName}/${ORDER_FILE}`,\n contents: `${JSON.stringify(slugs, null, 2)}\\n`,\n },\n ],\n });\n },\n\n requireCollection,\n requireSingleton,\n\n /** Slug the server would assign, so the editor's URL preview matches it. */\n async suggestSlug(collectionName: string, source: string, except?: string): Promise<string> {\n const def = requireCollection(collectionName);\n return uniqueSlug(slugify(source), await takenSlugs(def, collectionName, except));\n },\n\n /**\n * Where each file is used: in a media field, or as an image or a link in rich\n * text. A file that something uses is not deleted.\n */\n async mediaUses(paths: string[]): Promise<{ uses: Record<string, Referrer[]>; unreadable: string[] }> {\n const wanted = new Set(paths);\n /** Collections and settings that could not be read: a use there cannot be ruled out. */\n const unreadable: string[] = [];\n const uses: Record<string, Referrer[]> = Object.fromEntries(paths.map((path) => [path, []]));\n const check = (referrer: Referrer, fields: FieldMap, data: Record<string, unknown>, folder: string) => {\n const found = new Set<string>();\n for (const reference of collectReferences(fields, data)) {\n if (reference.kind === \"media\" && wanted.has(reference.path)) found.add(reference.path);\n }\n for (const text of richTextValues(fields, data)) {\n for (const target of markdocTargets(text)) {\n const path = resolveTarget(target, folder);\n if (path && wanted.has(path)) found.add(path);\n }\n }\n for (const path of found) uses[path]!.push(referrer);\n };\n\n for (const [name, def] of Object.entries(site.collections)) {\n const entries = await readCollection(workspace, def, name).catch(() => null);\n if (!entries) {\n unreadable.push(def.label);\n continue;\n }\n for (const entry of entries) {\n const base = collectionEntryPath(def.fields, name, entry.slug);\n const folder = entryLayout(def.fields) === \"directory\" ? base : name;\n check({ kind: \"entry\", collection: name, slug: entry.slug, title: entryTitle(def, entry.data) }, def.fields, entry.data, folder);\n }\n }\n for (const [name, def] of Object.entries(site.singletons)) {\n const data = await readSingleton(workspace, def, name).catch(() => undefined);\n if (data === undefined) unreadable.push(def.label);\n if (!data) continue;\n const folder = entryLayout(def.fields) === \"directory\" ? singletonPath(def.fields, name) : \"\";\n check({ kind: \"singleton\", name, title: def.label }, def.fields, data, folder);\n }\n return { uses, unreadable };\n },\n\n /** Entries and settings pages that link to `slug`: the warning before a delete or a URL change. */\n async findReferences(collectionName: string, slug: string): Promise<Referrer[]> {\n requireCollection(collectionName);\n return (await referrers(collectionName, slug)).map((found) => found.referrer);\n },\n };\n}\n\nexport type ContentStore = ReturnType<typeof createContentStore>;\n\n/** Whether a save is a URL change whose links should follow. */\nfunction previousLinks(input: SaveEntryInput, slug: string): boolean {\n return input.updateLinks === true && input.slug !== undefined && input.slug !== slug;\n}\n\n/** Every rich-text value in a payload, nested fields included. */\nfunction richTextValues(fields: FieldMap, data: Record<string, unknown>): string[] {\n const found: string[] = [];\n const isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n const visit = (map: FieldMap, record: Record<string, unknown>) => {\n for (const [key, field] of Object.entries(map)) {\n const value = record[key];\n if (field.kind === \"richText\" && typeof value === \"string\") found.push(value);\n else if (field.kind === \"object\" && isRecord(value)) visit(field.fields, value);\n else if (field.kind === \"repeater\" && Array.isArray(value)) for (const row of value) if (isRecord(row)) visit(field.fields, row);\n }\n };\n visit(fields, data);\n return found;\n}\n\n/**\n * The targets of the images and links in Markdoc: ``,\n * `[Price list](/media/prices.pdf)`, a reference `[x]: media/x.jpg`, and a tag\n * attribute such as `{% figure src=\"media/x.jpg\" %}`.\n */\nfunction markdocTargets(text: string): string[] {\n const inline = [...text.matchAll(/\\]\\(\\s*<?([^)\\s>]+)/g)].map((match) => match[1]!);\n const references = [...text.matchAll(/^\\s{0,3}\\[[^\\]]+\\]:\\s*<?([^\\s>]+)/gm)].map((match) => match[1]!);\n const attributes = [...text.matchAll(/\\{%[^%]*%\\}/g)].flatMap((tag) =>\n [...tag[0].matchAll(/\\b[\\w-]+\\s*=\\s*\"([^\"]+)\"/g)].map((match) => match[1]!),\n );\n return [...inline, ...references, ...attributes];\n}\n\n/** A rich-text target as a content path: from the content root with `/`, else from the document's folder. */\nfunction resolveTarget(target: string, folder: string): string | null {\n if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith(\"#\")) return null;\n let decoded: string;\n try {\n decoded = decodeURI(target.split(/[?#]/)[0]!);\n } catch {\n return null;\n }\n const joined = decoded.startsWith(\"/\") ? decoded.slice(1) : folder === \"\" ? decoded : `${folder}/${decoded}`;\n const normal = posix.normalize(joined);\n return normal.startsWith(\"..\") ? null : normal;\n}\n\n/** Whether a field map has a relation to `collection`, nested fields included. */\nfunction linksTo(fields: FieldMap, collection: string): boolean {\n return Object.values(fields).some(\n (field) =>\n (field.kind === \"relation\" && field.collection === collection) ||\n ((field.kind === \"object\" || field.kind === \"repeater\") && linksTo(field.fields, collection)),\n );\n}\n\n/**\n * Media paths as the repository stores them. Only a directory entry has\n * colocated media to relativize.\n */\nfunction mediaForDisk(\n fields: FieldMap,\n data: Record<string, unknown>,\n base: string,\n): Record<string, unknown> {\n return entryLayout(fields) === \"directory\" ? relativizeMedia(fields, data, base) : data;\n}\n\nfunction dataPath(def: Collection, collection: string, slug: string): string {\n const base = collectionEntryPath(def.fields, collection, slug);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\nfunction singletonDataPath(def: Singleton, name: string): string {\n const base = singletonPath(def.fields, name);\n return entryLayout(def.fields) === \"directory\" ? `${base}/${INDEX_FILE}` : base;\n}\n\n/** The slug from the payload's slug field, or one derived from the title. */\nfunction pickSlug(\n def: Collection,\n data: Record<string, unknown>,\n current: string | undefined,\n): string {\n const slugKey = resolveSlugField(def)!;\n const fromData = data[slugKey];\n\n // Not `entryTitle`, whose \"Untitled\" would give every entry without a title the same URL.\n const titleKey = resolveTitleField(def);\n const title = titleKey ? data[titleKey] : undefined;\n const candidate =\n (typeof fromData === \"string\" && fromData.trim()) || (typeof title === \"string\" ? slugify(title.trim()) : \"\");\n\n if (!candidate) {\n throw new ContentError(\"An entry needs a title or a URL before it can be saved.\");\n }\n\n const normalised = isValidSlug(candidate) ? candidate : slugify(candidate);\n if (!normalised) {\n throw new ContentError(`\"${candidate}\" cannot be turned into a URL. Use letters and numbers.`);\n }\n\n return normalised === current ? current : normalised;\n}\n\n/** Copies colocated media to the renamed entry; the caller deletes the old directory. */\nasync function moveMedia(\n files: ContentFiles,\n from: string,\n to: string,\n changes: FileChange[],\n): Promise<void> {\n for (const item of await files.list(`${from}/media`)) {\n if (item.type !== \"file\") continue;\n\n const bytes = await files.readBinary(`${from}/media/${item.name}`);\n if (bytes === null) continue;\n\n changes.push({ type: \"write\", path: `${to}/media/${item.name}`, contents: bytes });\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAM1D,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB;IAkBrD,sDAAsD;mBACjC;QACnB,IAAI,EAAE,IAAI,CAAC;QACX,oEAAoE;QACpE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC;KACvC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,UAAU,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAM1D,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB;IAkBrD,sDAAsD;mBACjC;QACnB,IAAI,EAAE,IAAI,CAAC;QACX,oEAAoE;QACpE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC;KACvC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,UAAU,CAAA;KAAE,CAAC;IA+CtD;;;OAGG;YACW,OAAO,CAAC,SAAS,EAAE,CAAC;oBAkCZ,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;EASzF;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AA6EjE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
|
package/dist/services/media.js
CHANGED
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { contentPath, ContentPathError, MEDIA_DIR, slugify } from "@micacms/core";
|
|
8
8
|
import { imageSize } from "image-size";
|
|
9
9
|
import { ApiError, badRequest } from "../lib/errors.js";
|
|
10
|
-
import { detectFileType, isAccepted, isImageType, SVG_TYPE } from "../lib/file-type.js";
|
|
10
|
+
import { detectFileType, isAccepted, isImageType, SVG_TYPE, VIDEO_TYPE } from "../lib/file-type.js";
|
|
11
11
|
/** Where uploads go when they are not attached to a particular entry. */
|
|
12
12
|
export const SHARED_MEDIA_DIR = "media";
|
|
13
13
|
export function createMediaService(deps) {
|
|
@@ -40,6 +40,9 @@ export function createMediaService(deps) {
|
|
|
40
40
|
}
|
|
41
41
|
if (detected.mimeType === SVG_TYPE)
|
|
42
42
|
throw refuseSvg();
|
|
43
|
+
if (detected.mimeType === VIDEO_TYPE) {
|
|
44
|
+
throw new ApiError("unsupported_media_type", "Mica does not keep videos: they make the website's files large. Put the video on a video service, and link to it.");
|
|
45
|
+
}
|
|
43
46
|
if (!isAccepted(detected.mimeType, input.accept ?? "any")) {
|
|
44
47
|
throw new ApiError("unsupported_media_type", `A ${input.accept} was expected, but this is a ${detected.mimeType}.`);
|
|
45
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media uploads. Every upload is checked here for size and for its real content\n * type, read from the bytes. An upload is a file in the content repository; its\n * name is slugified and unique in its own directory.\n */\n\nimport path from \"node:path\";\n\nimport { contentPath, ContentPathError, MEDIA_DIR, slugify } from \"@micacms/core\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { imageSize } from \"image-size\";\n\nimport { ApiError, badRequest } from \"../lib/errors.js\";\nimport { detectFileType, isAccepted, isImageType, SVG_TYPE } from \"../lib/file-type.js\";\n\n/** Where uploads go when they are not attached to a particular entry. */\nexport const SHARED_MEDIA_DIR = \"media\";\n\nexport interface MediaServiceDeps {\n files: ContentFiles;\n maxBytes: number;\n}\n\nexport interface MediaFile {\n /** Path relative to the content root: the value a `media()` field holds. */\n path: string;\n filename: string;\n mimeType: string;\n size: number;\n width: number | null;\n height: number | null;\n}\n\nexport function createMediaService(deps: MediaServiceDeps) {\n const { files, maxBytes } = deps;\n\n /** A name that is unique in `directory`. Only there can a collision overwrite a file. */\n const uniqueName = async (directory: string, filename: string): Promise<string> => {\n const taken = new Set(\n (await files.list(directory)).filter((item) => item.type === \"file\").map((item) => item.name),\n );\n\n const extension = path.extname(filename);\n const stem = filename.slice(0, filename.length - extension.length);\n\n let candidate = filename;\n for (let n = 2; taken.has(candidate); n += 1) candidate = `${stem}-${n}${extension}`;\n return candidate;\n };\n\n return {\n /** Reads and checks an upload, without writing it. */\n async prepare(input: {\n file: File;\n /** Directory to place the file in, relative to the content root. */\n directory?: string;\n accept?: \"image\" | \"document\" | \"any\";\n }): Promise<{ file: MediaFile; contents: Uint8Array }> {\n const { file } = input;\n const directory = uploadDirectory(input.directory ?? SHARED_MEDIA_DIR);\n\n if (file.size === 0) throw badRequest(\"The file is empty.\");\n if (file.size > maxBytes) {\n throw new ApiError(\n \"payload_too_large\",\n `The file is ${formatBytes(file.size)}. The limit is ${formatBytes(maxBytes)}.`,\n );\n }\n\n const contents = new Uint8Array(await file.arrayBuffer());\n // From the bytes, never from the browser's claim.\n const detected = detectFileType(contents);\n\n if (!detected) {\n throw new ApiError(\"unsupported_media_type\", \"This file type is not supported.\");\n }\n if (detected.mimeType === SVG_TYPE) throw refuseSvg();\n if (!isAccepted(detected.mimeType, input.accept ?? \"any\")) {\n throw new ApiError(\n \"unsupported_media_type\",\n `A ${input.accept} was expected, but this is a ${detected.mimeType}.`,\n );\n }\n\n const filename = await uniqueName(directory, safeName(file.name, detected.extension));\n\n return {\n contents,\n file: {\n path: `${directory}/${filename}`,\n filename,\n mimeType: detected.mimeType,\n size: contents.byteLength,\n ...dimensions(contents, detected.mimeType),\n },\n };\n },\n\n /**\n * Every media file in the repository. A tree walk is sufficient because\n * content trees are small.\n */\n async list(): Promise<MediaFile[]> {\n const found: MediaFile[] = [];\n\n const walk = async (directory: string): Promise<void> => {\n for (const item of await files.list(directory)) {\n const child = directory === \"\" ? item.name : `${directory}/${item.name}`;\n\n if (item.type === \"directory\") {\n await walk(child);\n continue;\n }\n\n if (!isMediaName(item.name)) continue;\n\n const bytes = await files.readBinary(child);\n if (!bytes) continue;\n\n const mimeType = typeOf(bytes, item.name);\n if (mimeType === SVG_TYPE) continue;\n\n found.push({\n path: child,\n filename: item.name,\n mimeType,\n size: bytes.byteLength,\n ...dimensions(bytes, mimeType),\n });\n }\n };\n\n await walk(\"\");\n return found.sort((a, b) => a.path.localeCompare(b.path));\n },\n\n async read(mediaPath: string): Promise<{ bytes: Uint8Array; mimeType: string } | null> {\n const bytes = await files.readBinary(contentPath(mediaPath));\n if (!bytes) return null;\n\n const mimeType = typeOf(bytes, mediaPath);\n if (mimeType === SVG_TYPE) throw refuseSvg();\n return { bytes, mimeType };\n },\n };\n}\n\nexport type MediaService = ReturnType<typeof createMediaService>;\n\n/** An upload goes into a `media/` folder inside the content root, and nowhere else. */\nfunction uploadDirectory(directory: string): string {\n let clean: string;\n try {\n clean = contentPath(directory);\n } catch (error) {\n if (error instanceof ContentPathError) throw badRequest(error.message);\n throw error;\n }\n if (clean.split(\"/\").at(-1) !== MEDIA_DIR) {\n throw badRequest(`Uploads go into a \"${MEDIA_DIR}\" folder, not \"${directory}\".`);\n }\n return clean;\n}\n\n/** A slugified filename: safe as a path segment and as part of a URL. */\nfunction safeName(original: string, extension: string): string {\n const stem = slugify(path.basename(original).replace(/\\.[^.]*$/, \"\")).slice(0, 80);\n return `${stem || \"upload\"}.${extension}`;\n}\n\n/** Extension to media type, only for formats that byte-sniffing does not identify. */\nconst BY_EXTENSION: Record<string, string> = {\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n txt: \"text/plain\",\n csv: \"text/csv\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n};\n\n/**\n * The bytes win over the extension, except that an SVG name always counts, so a\n * leading comment cannot hide an SVG.\n */\nfunction typeOf(bytes: Uint8Array, name: string): string {\n const byName = mimeFromName(name);\n if (byName === SVG_TYPE) return SVG_TYPE;\n return detectFileType(bytes)?.mimeType ?? byName;\n}\n\nconst refuseSvg = () =>\n new ApiError(\n \"unsupported_media_type\",\n \"Mica does not accept SVG files, because they can contain scripts. Use PNG or WebP instead.\",\n );\n\nfunction mimeFromName(name: string): string {\n const extension = path.extname(name).slice(1).toLowerCase();\n return BY_EXTENSION[extension] ?? \"application/octet-stream\";\n}\n\n/** Files a `media()` field could plausibly point at. */\nfunction isMediaName(name: string): boolean {\n return /\\.(jpe?g|png|webp|avif|gif|tiff?|pdf|docx?|xlsx?|txt|csv)$/i.test(name);\n}\n\nfunction dimensions(\n bytes: Uint8Array,\n mimeType: string,\n): { width: number | null; height: number | null } {\n if (bytes.byteLength === 0 || !isImageType(mimeType)) {\n return { width: null, height: null };\n }\n try {\n const size = imageSize(Buffer.from(bytes));\n return { width: size.width ?? null, height: size.height ?? null };\n } catch {\n // A format image-size cannot read is still a valid upload.\n return { width: null, height: null };\n }\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} kB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media uploads. Every upload is checked here for size and for its real content\n * type, read from the bytes. An upload is a file in the content repository; its\n * name is slugified and unique in its own directory.\n */\n\nimport path from \"node:path\";\n\nimport { contentPath, ContentPathError, MEDIA_DIR, slugify } from \"@micacms/core\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { imageSize } from \"image-size\";\n\nimport { ApiError, badRequest } from \"../lib/errors.js\";\nimport { detectFileType, isAccepted, isImageType, SVG_TYPE, VIDEO_TYPE } from \"../lib/file-type.js\";\n\n/** Where uploads go when they are not attached to a particular entry. */\nexport const SHARED_MEDIA_DIR = \"media\";\n\nexport interface MediaServiceDeps {\n files: ContentFiles;\n maxBytes: number;\n}\n\nexport interface MediaFile {\n /** Path relative to the content root: the value a `media()` field holds. */\n path: string;\n filename: string;\n mimeType: string;\n size: number;\n width: number | null;\n height: number | null;\n}\n\nexport function createMediaService(deps: MediaServiceDeps) {\n const { files, maxBytes } = deps;\n\n /** A name that is unique in `directory`. Only there can a collision overwrite a file. */\n const uniqueName = async (directory: string, filename: string): Promise<string> => {\n const taken = new Set(\n (await files.list(directory)).filter((item) => item.type === \"file\").map((item) => item.name),\n );\n\n const extension = path.extname(filename);\n const stem = filename.slice(0, filename.length - extension.length);\n\n let candidate = filename;\n for (let n = 2; taken.has(candidate); n += 1) candidate = `${stem}-${n}${extension}`;\n return candidate;\n };\n\n return {\n /** Reads and checks an upload, without writing it. */\n async prepare(input: {\n file: File;\n /** Directory to place the file in, relative to the content root. */\n directory?: string;\n accept?: \"image\" | \"document\" | \"any\";\n }): Promise<{ file: MediaFile; contents: Uint8Array }> {\n const { file } = input;\n const directory = uploadDirectory(input.directory ?? SHARED_MEDIA_DIR);\n\n if (file.size === 0) throw badRequest(\"The file is empty.\");\n if (file.size > maxBytes) {\n throw new ApiError(\n \"payload_too_large\",\n `The file is ${formatBytes(file.size)}. The limit is ${formatBytes(maxBytes)}.`,\n );\n }\n\n const contents = new Uint8Array(await file.arrayBuffer());\n // From the bytes, never from the browser's claim.\n const detected = detectFileType(contents);\n\n if (!detected) {\n throw new ApiError(\"unsupported_media_type\", \"This file type is not supported.\");\n }\n if (detected.mimeType === SVG_TYPE) throw refuseSvg();\n if (detected.mimeType === VIDEO_TYPE) {\n throw new ApiError(\n \"unsupported_media_type\",\n \"Mica does not keep videos: they make the website's files large. Put the video on a video service, and link to it.\",\n );\n }\n if (!isAccepted(detected.mimeType, input.accept ?? \"any\")) {\n throw new ApiError(\n \"unsupported_media_type\",\n `A ${input.accept} was expected, but this is a ${detected.mimeType}.`,\n );\n }\n\n const filename = await uniqueName(directory, safeName(file.name, detected.extension));\n\n return {\n contents,\n file: {\n path: `${directory}/${filename}`,\n filename,\n mimeType: detected.mimeType,\n size: contents.byteLength,\n ...dimensions(contents, detected.mimeType),\n },\n };\n },\n\n /**\n * Every media file in the repository. A tree walk is sufficient because\n * content trees are small.\n */\n async list(): Promise<MediaFile[]> {\n const found: MediaFile[] = [];\n\n const walk = async (directory: string): Promise<void> => {\n for (const item of await files.list(directory)) {\n const child = directory === \"\" ? item.name : `${directory}/${item.name}`;\n\n if (item.type === \"directory\") {\n await walk(child);\n continue;\n }\n\n if (!isMediaName(item.name)) continue;\n\n const bytes = await files.readBinary(child);\n if (!bytes) continue;\n\n const mimeType = typeOf(bytes, item.name);\n if (mimeType === SVG_TYPE) continue;\n\n found.push({\n path: child,\n filename: item.name,\n mimeType,\n size: bytes.byteLength,\n ...dimensions(bytes, mimeType),\n });\n }\n };\n\n await walk(\"\");\n return found.sort((a, b) => a.path.localeCompare(b.path));\n },\n\n async read(mediaPath: string): Promise<{ bytes: Uint8Array; mimeType: string } | null> {\n const bytes = await files.readBinary(contentPath(mediaPath));\n if (!bytes) return null;\n\n const mimeType = typeOf(bytes, mediaPath);\n if (mimeType === SVG_TYPE) throw refuseSvg();\n return { bytes, mimeType };\n },\n };\n}\n\nexport type MediaService = ReturnType<typeof createMediaService>;\n\n/** An upload goes into a `media/` folder inside the content root, and nowhere else. */\nfunction uploadDirectory(directory: string): string {\n let clean: string;\n try {\n clean = contentPath(directory);\n } catch (error) {\n if (error instanceof ContentPathError) throw badRequest(error.message);\n throw error;\n }\n if (clean.split(\"/\").at(-1) !== MEDIA_DIR) {\n throw badRequest(`Uploads go into a \"${MEDIA_DIR}\" folder, not \"${directory}\".`);\n }\n return clean;\n}\n\n/** A slugified filename: safe as a path segment and as part of a URL. */\nfunction safeName(original: string, extension: string): string {\n const stem = slugify(path.basename(original).replace(/\\.[^.]*$/, \"\")).slice(0, 80);\n return `${stem || \"upload\"}.${extension}`;\n}\n\n/** Extension to media type, only for formats that byte-sniffing does not identify. */\nconst BY_EXTENSION: Record<string, string> = {\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n txt: \"text/plain\",\n csv: \"text/csv\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n};\n\n/**\n * The bytes win over the extension, except that an SVG name always counts, so a\n * leading comment cannot hide an SVG.\n */\nfunction typeOf(bytes: Uint8Array, name: string): string {\n const byName = mimeFromName(name);\n if (byName === SVG_TYPE) return SVG_TYPE;\n return detectFileType(bytes)?.mimeType ?? byName;\n}\n\nconst refuseSvg = () =>\n new ApiError(\n \"unsupported_media_type\",\n \"Mica does not accept SVG files, because they can contain scripts. Use PNG or WebP instead.\",\n );\n\nfunction mimeFromName(name: string): string {\n const extension = path.extname(name).slice(1).toLowerCase();\n return BY_EXTENSION[extension] ?? \"application/octet-stream\";\n}\n\n/** Files a `media()` field could plausibly point at. */\nfunction isMediaName(name: string): boolean {\n return /\\.(jpe?g|png|webp|avif|gif|tiff?|pdf|docx?|xlsx?|txt|csv)$/i.test(name);\n}\n\nfunction dimensions(\n bytes: Uint8Array,\n mimeType: string,\n): { width: number | null; height: number | null } {\n if (bytes.byteLength === 0 || !isImageType(mimeType)) {\n return { width: null, height: null };\n }\n try {\n const size = imageSize(Buffer.from(bytes));\n return { width: size.width ?? null, height: size.height ?? null };\n } catch {\n // A format image-size cannot read is still a valid upload.\n return { width: null, height: null };\n }\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} kB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAuE,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAI/G,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAWL,KAAK,OAAO,EAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAQL,KAAK,qBAAqB,EAK3B,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,8DAA8D;IAC9D,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,sBAAsB,GAC3B,qBAAqB,CAqQvB"}
|
package/dist/services/staging.js
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* - When the push fails, the commit stays as `committed-pending-push`; a
|
|
10
10
|
* retry pushes and never commits again.
|
|
11
11
|
*/
|
|
12
|
+
import { stat } from "node:fs/promises";
|
|
12
13
|
import { MANIFEST_PATH, memorySource } from "@micacms/core";
|
|
14
|
+
import { within } from "@micacms/core/node";
|
|
13
15
|
import { ApiError, badRequest, notFound } from "../lib/errors.js";
|
|
14
16
|
import { developerPolicy } from "../workspace/developer.js";
|
|
15
17
|
import { GitError } from "../workspace/git.js";
|
|
16
18
|
import { createWorkspaceLocks } from "../workspace/locks.js";
|
|
17
|
-
import {
|
|
19
|
+
import { commitPaths, currentBranch, discardChange, headRevision, headSource, overlaySource, listTreeChanges, locateTree, publicationMessage, unpushedCommits, } from "../workspace/tree.js";
|
|
18
20
|
import { analyseChanges, compareSources, describeProblems, flattenProblems, } from "./changes.js";
|
|
19
21
|
export function createWorkingTreeStagingService(deps) {
|
|
20
22
|
const { model, files, contentRoot, logger } = deps;
|
|
@@ -74,10 +76,17 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
74
76
|
...change,
|
|
75
77
|
revision: change.kind === "delete" ? null : await files.revision(change.path),
|
|
76
78
|
})));
|
|
79
|
+
/** When each file was last saved; a deletion has no file to ask. */
|
|
80
|
+
const withSavedAt = async (changes) => Promise.all(changes.map(async (change) => {
|
|
81
|
+
if (change.kind === "delete")
|
|
82
|
+
return { ...change, savedAt: null };
|
|
83
|
+
const saved = await stat(within(contentRoot, change.path)).catch(() => null);
|
|
84
|
+
return { ...change, savedAt: saved ? saved.mtime.toISOString() : null };
|
|
85
|
+
}));
|
|
77
86
|
/** Refused for the developer's checkout: Mica never fetches or merges there. */
|
|
78
87
|
const managedOnly = (action) => {
|
|
79
88
|
if (action === undefined)
|
|
80
|
-
throw notFound("
|
|
89
|
+
throw notFound("Only a self-hosted Mica keeps its own copy of the website; the local editor leaves Git to you.");
|
|
81
90
|
return action;
|
|
82
91
|
};
|
|
83
92
|
return {
|
|
@@ -90,7 +99,7 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
90
99
|
publish: { enabled: false, reason: tree.reason },
|
|
91
100
|
head: null,
|
|
92
101
|
branch: null,
|
|
93
|
-
|
|
102
|
+
unpushed: null,
|
|
94
103
|
changes: [],
|
|
95
104
|
validation: emptyValidation(),
|
|
96
105
|
sync,
|
|
@@ -100,7 +109,7 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
100
109
|
const analysed = await analyse(tree, await listTreeChanges(tree));
|
|
101
110
|
return {
|
|
102
111
|
...analysed,
|
|
103
|
-
changes: await withRevisions(analysed.changes),
|
|
112
|
+
changes: await withSavedAt(await withRevisions(analysed.changes)),
|
|
104
113
|
head: await headRevision(tree),
|
|
105
114
|
branch: await currentBranch(tree),
|
|
106
115
|
};
|
|
@@ -110,7 +119,8 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
110
119
|
publish: policy.publishAvailability(),
|
|
111
120
|
head,
|
|
112
121
|
branch,
|
|
113
|
-
|
|
122
|
+
// A clone that Mica keeps holds only Mica's commits, and retries its own push.
|
|
123
|
+
unpushed: policy.owner === "developer" ? await unpushedCommits(tree) : null,
|
|
114
124
|
changes,
|
|
115
125
|
validation,
|
|
116
126
|
sync,
|
|
@@ -119,7 +129,7 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
119
129
|
async publish(input) {
|
|
120
130
|
const message = input.message.trim();
|
|
121
131
|
if (!message)
|
|
122
|
-
throw badRequest("
|
|
132
|
+
throw badRequest("Say what changed before you publish.");
|
|
123
133
|
const reviewed = Object.keys(input.paths);
|
|
124
134
|
if (reviewed.length === 0)
|
|
125
135
|
throw badRequest("Choose at least one change to publish.");
|
|
@@ -146,8 +156,7 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
146
156
|
}
|
|
147
157
|
// The model drift gate: validation used the model that this process started with.
|
|
148
158
|
if ((await files.revision(MANIFEST_PATH)) !== deps.schemaRevision) {
|
|
149
|
-
throw new ApiError("conflict", "The content model changed
|
|
150
|
-
"the new model, then review the pending changes and publish again.");
|
|
159
|
+
throw new ApiError("conflict", "The content model changed just now. Review the changes again, then publish.");
|
|
151
160
|
}
|
|
152
161
|
const selected = (await listTreeChanges(tree)).filter((change) => change.path in input.paths);
|
|
153
162
|
// Checked against what the commit will hold: drafts left behind are not on the site.
|
|
@@ -159,15 +168,15 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
170
|
try {
|
|
162
|
-
const revision = await commitPaths(tree, reviewed, message, policy.commitAuthor(input.author));
|
|
163
|
-
logger.info("Published", { revision, files: reviewed.length, owner: policy.owner });
|
|
171
|
+
const revision = await commitPaths(tree, reviewed, publicationMessage(message), policy.commitAuthor(input.author));
|
|
172
|
+
logger.info("Published", { revision, files: reviewed.length, owner: policy.owner, editor: input.editor });
|
|
164
173
|
return revision;
|
|
165
174
|
}
|
|
166
175
|
catch (error) {
|
|
167
176
|
if (error instanceof GitError) {
|
|
168
177
|
// Git's message is the actionable part: no identity, a hook that
|
|
169
178
|
// refused, a rebase in progress.
|
|
170
|
-
throw new ApiError("conflict", `
|
|
179
|
+
throw new ApiError("conflict", `Publishing failed: ${error.message}`);
|
|
171
180
|
}
|
|
172
181
|
throw error;
|
|
173
182
|
}
|
|
@@ -180,11 +189,11 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
180
189
|
return locks.remote.run(async () => {
|
|
181
190
|
const revision = await headRevision(tree);
|
|
182
191
|
if (revision === null)
|
|
183
|
-
throw badRequest("There is
|
|
192
|
+
throw badRequest("There is nothing to send to the website yet.");
|
|
184
193
|
return policy.push(tree, revision);
|
|
185
194
|
});
|
|
186
195
|
},
|
|
187
|
-
async discard(requested) {
|
|
196
|
+
async discard(requested, editor) {
|
|
188
197
|
const tree = await located();
|
|
189
198
|
return locks.tree.write(async () => {
|
|
190
199
|
// Discard is destructive, so it reaches only paths that the change list names.
|
|
@@ -197,6 +206,8 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
197
206
|
await discardChange(tree, change);
|
|
198
207
|
discarded.push(path);
|
|
199
208
|
}
|
|
209
|
+
if (discarded.length > 0)
|
|
210
|
+
logger.info("Discarded", { paths: discarded, editor });
|
|
200
211
|
return { discarded };
|
|
201
212
|
});
|
|
202
213
|
},
|
|
@@ -213,8 +224,9 @@ export function createWorkingTreeStagingService(deps) {
|
|
|
213
224
|
async sync() {
|
|
214
225
|
await managedOnly(policy.sync)();
|
|
215
226
|
},
|
|
216
|
-
async resolve(group, choice) {
|
|
227
|
+
async resolve(group, choice, editor) {
|
|
217
228
|
await managedOnly(policy.resolve)(group, choice);
|
|
229
|
+
logger.info("Conflict resolved", { group, choice, editor });
|
|
218
230
|
},
|
|
219
231
|
async repository() {
|
|
220
232
|
const tree = await locate();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.js","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The staging service on a Git working tree. Autosave has already written the\n * files; Publish commits the reviewed paths.\n *\n * - Every Git operation is limited to an explicit path set, so other work in\n * the same checkout is never swept in.\n * - Only the reviewed files are published, as the editor saw them. Drafts of\n * other entries stay drafts.\n * - When the push fails, the commit stays as `committed-pending-push`; a\n * retry pushes and never commits again.\n */\n\nimport { MANIFEST_PATH, memorySource, type ContentSource, type PublishResult, type Site } from \"@micacms/core\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Logger } from \"../lib/logger.js\";\nimport { developerPolicy } from \"../workspace/developer.js\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { GitError } from \"../workspace/git.js\";\nimport { createWorkspaceLocks, type WorkspaceLocks } from \"../workspace/locks.js\";\nimport type { OwnerPolicy } from \"../workspace/policy.js\";\nimport {\n aheadOfUpstream,\n commitPaths,\n currentBranch,\n discardChange,\n headRevision,\n headSource,\n overlaySource,\n listTreeChanges,\n locateTree,\n type GitTree,\n type TreeChange,\n} from \"../workspace/tree.js\";\nimport {\n analyseChanges,\n compareSources,\n describeProblems,\n flattenProblems,\n type AnnotatedChange,\n type CompareTarget,\n type Comparison,\n type ContentStagingService,\n type PublishInput,\n type RepositoryInfo,\n type StagingStatus,\n type StagingValidation,\n} from \"./changes.js\";\n\nexport interface WorkingTreeStagingDeps {\n model: Site;\n /**\n * The working tree's files without the lock wrapper: this service holds the\n * locks around whole operations.\n */\n files: ContentFiles;\n contentRoot: string;\n schemaRevision: string | null;\n /** From `readOnlyParts`: a change to one of these is never published by this build. */\n readOnly?: ReadonlyMap<string, string>;\n /** `MICA_LOCAL_PUBLISH`, for the default developer policy. */\n publishEnabled: boolean;\n logger: Logger;\n /** Defaults to the developer. */\n policy?: OwnerPolicy;\n /** Shared with the services that write content. */\n locks?: WorkspaceLocks;\n /**\n * Set by a managed clone, whose Git runs in an isolated environment. Without\n * it, the developer's Git finds the tree.\n */\n tree?: GitTree;\n}\n\nexport function createWorkingTreeStagingService(\n deps: WorkingTreeStagingDeps,\n): ContentStagingService {\n const { model, files, contentRoot, logger } = deps;\n const policy = deps.policy ?? developerPolicy({ publishEnabled: deps.publishEnabled, logger });\n const locks = deps.locks ?? createWorkspaceLocks();\n\n /** `source` is what the result holds: the whole working tree, or HEAD with a selection. */\n const analyse = async (tree: GitTree, treeChanges: TreeChange[], source: ContentSource = files) => {\n const { changes, validation } = await analyseChanges(\n model,\n source,\n treeChanges,\n headSource(tree),\n );\n\n // The field validator cannot see a merge conflict: the file holds Git's conflict format.\n for (const change of treeChanges) {\n if (!change.conflicted) continue;\n validation.problems.push({\n path: change.path,\n label: change.path,\n issues: [\n { path: \"\", message: \"This file has a Git merge conflict. Resolve it with your Git tools.\" },\n ],\n });\n }\n // By the first path segment, so that a stray file in a read-only collection is caught too.\n for (const change of changes) {\n const head = change.path.split(\"/\")[0] ?? \"\";\n const reason = deps.readOnly?.get(`collections.${head}`) ?? deps.readOnly?.get(`singletons.${head.replace(/\\.json$/, \"\")}`);\n if (reason) {\n validation.problems.push({ path: change.path, label: change.owner.label, issues: [{ path: \"\", message: reason }] });\n }\n }\n validation.ok = validation.problems.length === 0;\n\n return { changes, validation };\n };\n\n const locate = async (): Promise<GitTree | { reason: string }> => deps.tree ?? locateTree(contentRoot);\n\n const located = async (): Promise<GitTree> => {\n const tree = await locate();\n if (\"reason\" in tree) throw badRequest(tree.reason);\n return tree;\n };\n\n const publishable = async (): Promise<GitTree> => {\n const availability = policy.publishAvailability();\n if (!availability.enabled) throw badRequest(availability.reason);\n return located();\n };\n\n /** The key that keeps an entry's files together: a publication never splits one. */\n const groupOf = (change: AnnotatedChange): string => {\n const owner = change.owner;\n if (owner.kind === \"entry\") return `entry:${owner.collection}/${owner.slug}`;\n if (owner.kind === \"singleton\") return `singleton:${owner.name}`;\n if (owner.kind === \"order\") return `order:${owner.collection}`;\n return `file:${change.path}`;\n };\n\n const withRevisions = async (changes: AnnotatedChange[]): Promise<AnnotatedChange[]> =>\n Promise.all(\n changes.map(async (change) => ({\n ...change,\n revision: change.kind === \"delete\" ? null : await files.revision(change.path),\n })),\n );\n\n /** Refused for the developer's checkout: Mica never fetches or merges there. */\n const managedOnly = <T>(action: T | undefined): T => {\n if (action === undefined) throw notFound(\"This is the developer's checkout; Git there is theirs.\");\n return action;\n };\n\n return {\n async status(): Promise<StagingStatus> {\n const tree = await locate();\n const sync = policy.syncStatus?.() ?? null;\n\n if (\"reason\" in tree) {\n return {\n owner: policy.owner,\n publish: { enabled: false, reason: tree.reason },\n head: null,\n branch: null,\n ahead: null,\n changes: [],\n validation: emptyValidation(),\n sync,\n };\n }\n\n const { changes, validation, head, branch } = await locks.tree.read(async () => {\n const analysed = await analyse(tree, await listTreeChanges(tree));\n return {\n ...analysed,\n changes: await withRevisions(analysed.changes),\n head: await headRevision(tree),\n branch: await currentBranch(tree),\n };\n });\n\n return {\n owner: policy.owner,\n publish: policy.publishAvailability(),\n head,\n branch,\n ahead: await aheadOfUpstream(tree),\n changes,\n validation,\n sync,\n };\n },\n\n async publish(input: PublishInput): Promise<PublishResult> {\n const message = input.message.trim();\n if (!message) throw badRequest(\"A publication needs a commit message.\");\n const reviewed = Object.keys(input.paths);\n if (reviewed.length === 0) throw badRequest(\"Choose at least one change to publish.\");\n\n const tree = await publishable();\n\n // One publication at a time from sync to push: no sync can run in\n // between, and no second commit can stack up.\n return locks.remote.run(async () => {\n await policy.prepare(tree);\n\n const revision = await locks.tree.write(async () => {\n const pending = await withRevisions((await analyse(tree, await listTreeChanges(tree))).changes);\n const byPath = new Map(pending.map((change) => [change.path, change]));\n\n // The review gate refuses a file saved again since the review, a\n // reviewed file that is no longer pending, or another file of a\n // reviewed entry. Drafts of other entries never block a publication.\n const reviewedGroups = new Set(\n reviewed.map((path) => byPath.get(path)).flatMap((change) => (change ? [groupOf(change)] : [])),\n );\n const stale = [\n ...reviewed.filter((path) => byPath.get(path)?.revision !== input.paths[path] || !byPath.has(path)),\n ...pending\n .filter((change) => reviewedGroups.has(groupOf(change)) && !(change.path in input.paths))\n .map((change) => change.path),\n ];\n if (stale.length > 0) {\n throw new ApiError(\n \"conflict\",\n \"Some of these changes were edited after you reviewed them. Review them again, then publish.\",\n );\n }\n\n // The model drift gate: validation used the model that this process started with.\n if ((await files.revision(MANIFEST_PATH)) !== deps.schemaRevision) {\n throw new ApiError(\n \"conflict\",\n \"The content model changed after this server started. Restart Mica to load \" +\n \"the new model, then review the pending changes and publish again.\",\n );\n }\n\n const selected = (await listTreeChanges(tree)).filter((change) => change.path in input.paths);\n // Checked against what the commit will hold: drafts left behind are not on the site.\n const result = overlaySource(headSource(tree), files, selected.map((change) => change.path));\n const { validation } = await analyse(tree, selected, result);\n if (!validation.ok) {\n throw new ApiError(\"validation_failed\", describeProblems(validation.problems), {\n issues: flattenProblems(validation.problems),\n });\n }\n\n try {\n const revision = await commitPaths(tree, reviewed, message, policy.commitAuthor(input.author));\n logger.info(\"Published\", { revision, files: reviewed.length, owner: policy.owner });\n return revision;\n } catch (error) {\n if (error instanceof GitError) {\n // Git's message is the actionable part: no identity, a hook that\n // refused, a rebase in progress.\n throw new ApiError(\"conflict\", `Git refused the commit: ${error.message}`);\n }\n throw error;\n }\n });\n\n return policy.push(tree, revision);\n });\n },\n\n async push(): Promise<PublishResult> {\n const tree = await publishable();\n\n return locks.remote.run(async () => {\n const revision = await headRevision(tree);\n if (revision === null) throw badRequest(\"There is no commit to push yet.\");\n return policy.push(tree, revision);\n });\n },\n\n async discard(requested: string[]): Promise<{ discarded: string[] }> {\n const tree = await located();\n\n return locks.tree.write(async () => {\n // Discard is destructive, so it reaches only paths that the change list names.\n const current = new Map((await listTreeChanges(tree)).map((change) => [change.path, change]));\n\n const discarded: string[] = [];\n for (const path of requested) {\n const change = current.get(path);\n if (!change) continue;\n await discardChange(tree, change);\n discarded.push(path);\n }\n\n return { discarded };\n });\n },\n\n async compare(target: CompareTarget, against: \"head\" | \"origin\" = \"head\"): Promise<Comparison> {\n const tree = await locate();\n if (against === \"origin\" && !policy.upstreamRef) throw badRequest(\"There is no website version to compare with here.\");\n // Without a repository nothing is published, so \"before\" is an empty source.\n const published =\n \"reason\" in tree\n ? memorySource({}, \"unpublished\")\n : headSource(tree, against === \"origin\" ? policy.upstreamRef : \"HEAD\");\n return locks.tree.read(() => compareSources(model, published, files, target));\n },\n\n async sync(): Promise<void> {\n await managedOnly(policy.sync)();\n },\n\n async resolve(group: string, choice: \"mine\" | \"website\"): Promise<void> {\n await managedOnly(policy.resolve)(group, choice);\n },\n\n async repository(): Promise<RepositoryInfo> {\n const tree = await locate();\n const described = (await policy.describe?.()) ?? null;\n return {\n owner: policy.owner,\n branch: \"reason\" in tree ? null : await currentBranch(tree),\n head: \"reason\" in tree ? null : await headRevision(tree),\n url: described?.url ?? null,\n cloneBytes: described?.cloneBytes ?? null,\n sync: policy.syncStatus?.() ?? null,\n };\n },\n };\n}\n\nfunction emptyValidation(): StagingValidation {\n return { ok: true, problems: [] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"staging.js","sourceRoot":"","sources":["../../src/services/staging.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The staging service on a Git working tree. Autosave has already written the\n * files; Publish commits the reviewed paths.\n *\n * - Every Git operation is limited to an explicit path set, so other work in\n * the same checkout is never swept in.\n * - Only the reviewed files are published, as the editor saw them. Drafts of\n * other entries stay drafts.\n * - When the push fails, the commit stays as `committed-pending-push`; a\n * retry pushes and never commits again.\n */\n\nimport { stat } from \"node:fs/promises\";\n\nimport { MANIFEST_PATH, memorySource, type ContentSource, type PublishResult, type Site } from \"@micacms/core\";\nimport { within } from \"@micacms/core/node\";\n\nimport { ApiError, badRequest, notFound } from \"../lib/errors.js\";\nimport type { Logger } from \"../lib/logger.js\";\nimport { developerPolicy } from \"../workspace/developer.js\";\nimport type { ContentFiles } from \"../workspace/files.js\";\nimport { GitError } from \"../workspace/git.js\";\nimport { createWorkspaceLocks, type WorkspaceLocks } from \"../workspace/locks.js\";\nimport type { OwnerPolicy } from \"../workspace/policy.js\";\nimport {\n commitPaths,\n currentBranch,\n discardChange,\n headRevision,\n headSource,\n overlaySource,\n listTreeChanges,\n locateTree,\n publicationMessage,\n unpushedCommits,\n type GitTree,\n type TreeChange,\n} from \"../workspace/tree.js\";\nimport {\n analyseChanges,\n compareSources,\n describeProblems,\n flattenProblems,\n type AnnotatedChange,\n type CompareTarget,\n type Comparison,\n type ContentStagingService,\n type PublishInput,\n type RepositoryInfo,\n type StagingStatus,\n type StagingValidation,\n} from \"./changes.js\";\n\nexport interface WorkingTreeStagingDeps {\n model: Site;\n /**\n * The working tree's files without the lock wrapper: this service holds the\n * locks around whole operations.\n */\n files: ContentFiles;\n contentRoot: string;\n schemaRevision: string | null;\n /** From `readOnlyParts`: a change to one of these is never published by this build. */\n readOnly?: ReadonlyMap<string, string>;\n /** `MICA_LOCAL_PUBLISH`, for the default developer policy. */\n publishEnabled: boolean;\n logger: Logger;\n /** Defaults to the developer. */\n policy?: OwnerPolicy;\n /** Shared with the services that write content. */\n locks?: WorkspaceLocks;\n /**\n * Set by a managed clone, whose Git runs in an isolated environment. Without\n * it, the developer's Git finds the tree.\n */\n tree?: GitTree;\n}\n\nexport function createWorkingTreeStagingService(\n deps: WorkingTreeStagingDeps,\n): ContentStagingService {\n const { model, files, contentRoot, logger } = deps;\n const policy = deps.policy ?? developerPolicy({ publishEnabled: deps.publishEnabled, logger });\n const locks = deps.locks ?? createWorkspaceLocks();\n\n /** `source` is what the result holds: the whole working tree, or HEAD with a selection. */\n const analyse = async (tree: GitTree, treeChanges: TreeChange[], source: ContentSource = files) => {\n const { changes, validation } = await analyseChanges(\n model,\n source,\n treeChanges,\n headSource(tree),\n );\n\n // The field validator cannot see a merge conflict: the file holds Git's conflict format.\n for (const change of treeChanges) {\n if (!change.conflicted) continue;\n validation.problems.push({\n path: change.path,\n label: change.path,\n issues: [\n { path: \"\", message: \"This file has a Git merge conflict. Resolve it with your Git tools.\" },\n ],\n });\n }\n // By the first path segment, so that a stray file in a read-only collection is caught too.\n for (const change of changes) {\n const head = change.path.split(\"/\")[0] ?? \"\";\n const reason = deps.readOnly?.get(`collections.${head}`) ?? deps.readOnly?.get(`singletons.${head.replace(/\\.json$/, \"\")}`);\n if (reason) {\n validation.problems.push({ path: change.path, label: change.owner.label, issues: [{ path: \"\", message: reason }] });\n }\n }\n validation.ok = validation.problems.length === 0;\n\n return { changes, validation };\n };\n\n const locate = async (): Promise<GitTree | { reason: string }> => deps.tree ?? locateTree(contentRoot);\n\n const located = async (): Promise<GitTree> => {\n const tree = await locate();\n if (\"reason\" in tree) throw badRequest(tree.reason);\n return tree;\n };\n\n const publishable = async (): Promise<GitTree> => {\n const availability = policy.publishAvailability();\n if (!availability.enabled) throw badRequest(availability.reason);\n return located();\n };\n\n /** The key that keeps an entry's files together: a publication never splits one. */\n const groupOf = (change: AnnotatedChange): string => {\n const owner = change.owner;\n if (owner.kind === \"entry\") return `entry:${owner.collection}/${owner.slug}`;\n if (owner.kind === \"singleton\") return `singleton:${owner.name}`;\n if (owner.kind === \"order\") return `order:${owner.collection}`;\n return `file:${change.path}`;\n };\n\n const withRevisions = async (changes: AnnotatedChange[]): Promise<AnnotatedChange[]> =>\n Promise.all(\n changes.map(async (change) => ({\n ...change,\n revision: change.kind === \"delete\" ? null : await files.revision(change.path),\n })),\n );\n\n /** When each file was last saved; a deletion has no file to ask. */\n const withSavedAt = async (changes: AnnotatedChange[]): Promise<AnnotatedChange[]> =>\n Promise.all(\n changes.map(async (change) => {\n if (change.kind === \"delete\") return { ...change, savedAt: null };\n const saved = await stat(within(contentRoot, change.path)).catch(() => null);\n return { ...change, savedAt: saved ? saved.mtime.toISOString() : null };\n }),\n );\n\n /** Refused for the developer's checkout: Mica never fetches or merges there. */\n const managedOnly = <T>(action: T | undefined): T => {\n if (action === undefined) throw notFound(\"Only a self-hosted Mica keeps its own copy of the website; the local editor leaves Git to you.\");\n return action;\n };\n\n return {\n async status(): Promise<StagingStatus> {\n const tree = await locate();\n const sync = policy.syncStatus?.() ?? null;\n\n if (\"reason\" in tree) {\n return {\n owner: policy.owner,\n publish: { enabled: false, reason: tree.reason },\n head: null,\n branch: null,\n unpushed: null,\n changes: [],\n validation: emptyValidation(),\n sync,\n };\n }\n\n const { changes, validation, head, branch } = await locks.tree.read(async () => {\n const analysed = await analyse(tree, await listTreeChanges(tree));\n return {\n ...analysed,\n changes: await withSavedAt(await withRevisions(analysed.changes)),\n head: await headRevision(tree),\n branch: await currentBranch(tree),\n };\n });\n\n return {\n owner: policy.owner,\n publish: policy.publishAvailability(),\n head,\n branch,\n // A clone that Mica keeps holds only Mica's commits, and retries its own push.\n unpushed: policy.owner === \"developer\" ? await unpushedCommits(tree) : null,\n changes,\n validation,\n sync,\n };\n },\n\n async publish(input: PublishInput): Promise<PublishResult> {\n const message = input.message.trim();\n if (!message) throw badRequest(\"Say what changed before you publish.\");\n const reviewed = Object.keys(input.paths);\n if (reviewed.length === 0) throw badRequest(\"Choose at least one change to publish.\");\n\n const tree = await publishable();\n\n // One publication at a time from sync to push: no sync can run in\n // between, and no second commit can stack up.\n return locks.remote.run(async () => {\n await policy.prepare(tree);\n\n const revision = await locks.tree.write(async () => {\n const pending = await withRevisions((await analyse(tree, await listTreeChanges(tree))).changes);\n const byPath = new Map(pending.map((change) => [change.path, change]));\n\n // The review gate refuses a file saved again since the review, a\n // reviewed file that is no longer pending, or another file of a\n // reviewed entry. Drafts of other entries never block a publication.\n const reviewedGroups = new Set(\n reviewed.map((path) => byPath.get(path)).flatMap((change) => (change ? [groupOf(change)] : [])),\n );\n const stale = [\n ...reviewed.filter((path) => byPath.get(path)?.revision !== input.paths[path] || !byPath.has(path)),\n ...pending\n .filter((change) => reviewedGroups.has(groupOf(change)) && !(change.path in input.paths))\n .map((change) => change.path),\n ];\n if (stale.length > 0) {\n throw new ApiError(\n \"conflict\",\n \"Some of these changes were edited after you reviewed them. Review them again, then publish.\",\n );\n }\n\n // The model drift gate: validation used the model that this process started with.\n if ((await files.revision(MANIFEST_PATH)) !== deps.schemaRevision) {\n throw new ApiError(\n \"conflict\",\n \"The content model changed just now. Review the changes again, then publish.\",\n );\n }\n\n const selected = (await listTreeChanges(tree)).filter((change) => change.path in input.paths);\n // Checked against what the commit will hold: drafts left behind are not on the site.\n const result = overlaySource(headSource(tree), files, selected.map((change) => change.path));\n const { validation } = await analyse(tree, selected, result);\n if (!validation.ok) {\n throw new ApiError(\"validation_failed\", describeProblems(validation.problems), {\n issues: flattenProblems(validation.problems),\n });\n }\n\n try {\n const revision = await commitPaths(tree, reviewed, publicationMessage(message), policy.commitAuthor(input.author));\n logger.info(\"Published\", { revision, files: reviewed.length, owner: policy.owner, editor: input.editor });\n return revision;\n } catch (error) {\n if (error instanceof GitError) {\n // Git's message is the actionable part: no identity, a hook that\n // refused, a rebase in progress.\n throw new ApiError(\"conflict\", `Publishing failed: ${error.message}`);\n }\n throw error;\n }\n });\n\n return policy.push(tree, revision);\n });\n },\n\n async push(): Promise<PublishResult> {\n const tree = await publishable();\n\n return locks.remote.run(async () => {\n const revision = await headRevision(tree);\n if (revision === null) throw badRequest(\"There is nothing to send to the website yet.\");\n return policy.push(tree, revision);\n });\n },\n\n async discard(requested: string[], editor?: string): Promise<{ discarded: string[] }> {\n const tree = await located();\n\n return locks.tree.write(async () => {\n // Discard is destructive, so it reaches only paths that the change list names.\n const current = new Map((await listTreeChanges(tree)).map((change) => [change.path, change]));\n\n const discarded: string[] = [];\n for (const path of requested) {\n const change = current.get(path);\n if (!change) continue;\n await discardChange(tree, change);\n discarded.push(path);\n }\n\n if (discarded.length > 0) logger.info(\"Discarded\", { paths: discarded, editor });\n return { discarded };\n });\n },\n\n async compare(target: CompareTarget, against: \"head\" | \"origin\" = \"head\"): Promise<Comparison> {\n const tree = await locate();\n if (against === \"origin\" && !policy.upstreamRef) throw badRequest(\"There is no website version to compare with here.\");\n // Without a repository nothing is published, so \"before\" is an empty source.\n const published =\n \"reason\" in tree\n ? memorySource({}, \"unpublished\")\n : headSource(tree, against === \"origin\" ? policy.upstreamRef : \"HEAD\");\n return locks.tree.read(() => compareSources(model, published, files, target));\n },\n\n async sync(): Promise<void> {\n await managedOnly(policy.sync)();\n },\n\n async resolve(group: string, choice: \"mine\" | \"website\", editor?: string): Promise<void> {\n await managedOnly(policy.resolve)(group, choice);\n logger.info(\"Conflict resolved\", { group, choice, editor });\n },\n\n async repository(): Promise<RepositoryInfo> {\n const tree = await locate();\n const described = (await policy.describe?.()) ?? null;\n return {\n owner: policy.owner,\n branch: \"reason\" in tree ? null : await currentBranch(tree),\n head: \"reason\" in tree ? null : await headRevision(tree),\n url: described?.url ?? null,\n cloneBytes: described?.cloneBytes ?? null,\n sync: policy.syncStatus?.() ?? null,\n };\n },\n };\n}\n\nfunction emptyValidation(): StagingValidation {\n return { ok: true, problems: [] };\n}\n"]}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* The policy for the developer's own checkout. Mica never fetches, merges or
|
|
3
3
|
* resets here. Publish pushes with the developer's `git`, credentials and
|
|
4
4
|
* upstream; a new branch without one is pushed to `origin` and tracks it. A
|
|
5
|
-
* failed push keeps the commit.
|
|
5
|
+
* failed push keeps the commit. A push sends only Mica's own commits: other
|
|
6
|
+
* commits that no remote has make Publish refuse.
|
|
6
7
|
*/
|
|
7
8
|
import type { Logger } from "../lib/logger.js";
|
|
8
9
|
import type { OwnerPolicy } from "./policy.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"developer.d.ts","sourceRoot":"","sources":["../../src/workspace/developer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"developer.d.ts","sourceRoot":"","sources":["../../src/workspace/developer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAGpE,MAAM,WAAW,sBAAsB;IACrC,4BAA4B;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,WAAW,CAmC5E"}
|