@usecds/server 0.0.0-stage → 0.1.1

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.
Files changed (72) hide show
  1. package/LICENSE +201 -0
  2. package/dist/core/content-report.d.ts +37 -0
  3. package/dist/core/content-report.js +306 -0
  4. package/dist/core/content-report.js.map +1 -0
  5. package/dist/core/content-types.d.ts +40 -0
  6. package/dist/core/content-types.js +27 -0
  7. package/dist/core/content-types.js.map +1 -0
  8. package/dist/core/jsonld.d.ts +33 -0
  9. package/dist/core/jsonld.js +94 -0
  10. package/dist/core/jsonld.js.map +1 -0
  11. package/dist/core/media-metadata.d.ts +20 -0
  12. package/dist/core/media-metadata.js +53 -0
  13. package/dist/core/media-metadata.js.map +1 -0
  14. package/dist/core/media-names.d.ts +9 -0
  15. package/dist/core/media-names.js +58 -0
  16. package/dist/core/media-names.js.map +1 -0
  17. package/dist/core/menu.d.ts +33 -0
  18. package/dist/core/menu.js +89 -0
  19. package/dist/core/menu.js.map +1 -0
  20. package/dist/core/publisher.d.ts +77 -0
  21. package/dist/core/publisher.js +263 -0
  22. package/dist/core/publisher.js.map +1 -0
  23. package/dist/core/report.d.ts +54 -0
  24. package/dist/core/report.js +132 -0
  25. package/dist/core/report.js.map +1 -0
  26. package/dist/core/settings.d.ts +12 -0
  27. package/dist/core/settings.js +40 -0
  28. package/dist/core/settings.js.map +1 -0
  29. package/dist/core/site.d.ts +37 -0
  30. package/dist/core/site.js +96 -0
  31. package/dist/core/site.js.map +1 -0
  32. package/dist/core/storage-report.d.ts +44 -0
  33. package/dist/core/storage-report.js +112 -0
  34. package/dist/core/storage-report.js.map +1 -0
  35. package/dist/core/targets.d.ts +63 -0
  36. package/dist/core/targets.js +213 -0
  37. package/dist/core/targets.js.map +1 -0
  38. package/dist/core/translations.d.ts +41 -0
  39. package/dist/core/translations.js +125 -0
  40. package/dist/core/translations.js.map +1 -0
  41. package/dist/index.d.ts +18 -0
  42. package/dist/index.js +19 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/sources/fixture.d.ts +9 -0
  45. package/dist/sources/fixture.js +79 -0
  46. package/dist/sources/fixture.js.map +1 -0
  47. package/dist/storage/filesystem.d.ts +23 -0
  48. package/dist/storage/filesystem.js +125 -0
  49. package/dist/storage/filesystem.js.map +1 -0
  50. package/dist/types.d.ts +223 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/utils.d.ts +2 -0
  54. package/dist/utils.js +17 -0
  55. package/dist/utils.js.map +1 -0
  56. package/dist/validation.d.ts +23 -0
  57. package/dist/validation.js +102 -0
  58. package/dist/validation.js.map +1 -0
  59. package/package.json +28 -3
  60. package/schemas/v1/block.json +31 -0
  61. package/schemas/v1/channel-manifest.json +23 -0
  62. package/schemas/v1/collection.json +89 -0
  63. package/schemas/v1/jsonld.json +19 -0
  64. package/schemas/v1/language.json +12 -0
  65. package/schemas/v1/media-metadata.json +41 -0
  66. package/schemas/v1/menu.json +21 -0
  67. package/schemas/v1/page.json +12 -0
  68. package/schemas/v1/release-manifest.json +82 -0
  69. package/schemas/v1/route.json +24 -0
  70. package/schemas/v1/site.json +21 -0
  71. package/schemas/v1/target.json +98 -0
  72. package/README.md +0 -3
@@ -0,0 +1,263 @@
1
+ import path from "path";
2
+ import { deterministicStringify, sha256 } from "../utils.js";
3
+ import { validateCollection, validateReleaseManifest, validateChannelManifest } from "../validation.js";
4
+ import { analyzeStorage } from "./storage-report.js";
5
+ import { analyzeTranslations, inferSourceLocale, toTranslationSummary, DEFAULT_SOURCE_LOCALE } from "./translations.js";
6
+ import { resolveTargets } from "./targets.js";
7
+ import { buildContentReport } from "./content-report.js";
8
+ import { collectMediaUsage, mediaLocales, MEDIA_COLLECTION, validateMediaMetadata } from "./media-metadata.js";
9
+ import { validateJsonLd } from "./jsonld.js";
10
+ import { validateSite } from "./site.js";
11
+ import { validateMenu } from "./menu.js";
12
+ import { validateSettings } from "./settings.js";
13
+ /**
14
+ * Thrown when content fails a target requirement. Nothing has been written to the store.
15
+ * The artifacts (including the content report) are attached for the pipeline.
16
+ */
17
+ export class PublishRequirementsError extends Error {
18
+ artifacts;
19
+ constructor(artifacts) {
20
+ const failed = artifacts.content.issues.filter((i) => i.severity === "requirement");
21
+ super(`Publish failed: ${failed.length} unmet target requirement(s)\n` +
22
+ failed.slice(0, 20).map((i) => ` [${i.target}] ${i.collection ? i.collection + (i.id ? "/" + i.id : "") + ": " : ""}${i.message}`).join("\n"));
23
+ this.artifacts = artifacts;
24
+ this.name = "PublishRequirementsError";
25
+ }
26
+ }
27
+ export class Publisher {
28
+ source;
29
+ store;
30
+ config;
31
+ constructor(source, store, config = {}) {
32
+ this.source = source;
33
+ this.store = store;
34
+ this.config = {
35
+ retentionCount: 3,
36
+ ...config
37
+ };
38
+ }
39
+ /**
40
+ * Compiles the source content and publishes a new release to the object store.
41
+ * Updates the channel manifest as the final atomic action.
42
+ */
43
+ async publish(channel, releaseId, options = {}) {
44
+ // The content's state is the source's as of this moment (or later): previews use it to drop
45
+ // edits saved before it, which the release therefore contains
46
+ const sourceReadAt = new Date().toISOString();
47
+ const sourceCollections = await this.source.getCollections();
48
+ const sourceMedia = await this.source.getMedia();
49
+ const sourceMap = this.source.getSourceMap ? await this.source.getSourceMap() : undefined;
50
+ // Only media referenced by content is published; the rest (and its _media entries) is ignored
51
+ const usage = collectMediaUsage(sourceCollections);
52
+ const rawMedia = sourceMedia.filter((m) => usage.has(m.virtualPath));
53
+ const warnings = mediaWarnings(sourceCollections, sourceMedia, usage);
54
+ const published = new Set(rawMedia.map((m) => m.virtualPath));
55
+ const rawCollections = { ...sourceCollections };
56
+ if (rawCollections[MEDIA_COLLECTION]) {
57
+ rawCollections[MEDIA_COLLECTION] = rawCollections[MEDIA_COLLECTION].filter((m) => published.has(m.id));
58
+ }
59
+ const itemLocales = (collection, item) => collection === MEDIA_COLLECTION ? mediaLocales(usage, item.id) : undefined;
60
+ const collectionsMeta = {};
61
+ const mediaMeta = {};
62
+ const objects = [];
63
+ // 1. Normalize, validate and hash collections (nothing is written before all checks pass)
64
+ for (const [colName, items] of Object.entries(rawCollections)) {
65
+ const normalizedCollection = {
66
+ schemaVersion: 1,
67
+ collection: colName,
68
+ items: items
69
+ };
70
+ // Validate collection against JSON Schema
71
+ validateCollection(normalizedCollection);
72
+ // Serialize deterministically and compute SHA-256 hash
73
+ const serialized = deterministicStringify(normalizedCollection);
74
+ const hash = sha256(serialized);
75
+ objects.push({ hash, serialized });
76
+ // Update release manifest metadata
77
+ collectionsMeta[colName] = {
78
+ hash,
79
+ itemCount: items.length,
80
+ size: Buffer.byteLength(serialized, "utf-8")
81
+ };
82
+ }
83
+ // 2. Hash media
84
+ const mediaHashes = rawMedia.map((item) => sha256(item.content));
85
+ rawMedia.forEach((item, i) => {
86
+ mediaMeta[item.virtualPath] = {
87
+ hash: mediaHashes[i],
88
+ size: item.content.length,
89
+ mimeType: item.mimeType
90
+ };
91
+ });
92
+ // Media metadata must describe media that is actually in the release
93
+ if (rawCollections[MEDIA_COLLECTION]) {
94
+ validateMediaMetadata(rawCollections[MEDIA_COLLECTION], mediaMeta);
95
+ }
96
+ // JSON-LD definitions must be valid and refer to existing collections and definitions
97
+ validateJsonLd(rawCollections);
98
+ // Optional site structure (_routes, _pages, _blocks, _menu, _site, _languages) must be consistent
99
+ validateSite(rawCollections);
100
+ validateMenu(rawCollections);
101
+ validateSettings(rawCollections);
102
+ // 3. Measure translation completeness against the source locale and the channel's current release
103
+ const { locale: sourceLocale, origin } = await this.resolveSourceLocale(options.sourceLocale, rawCollections);
104
+ const previousCollections = await this.readChannelCollections(channel);
105
+ const translations = analyzeTranslations(rawCollections, sourceLocale, origin, previousCollections, itemLocales);
106
+ // 4. Check targets; an unmet requirement fails the build before anything is written
107
+ const resolved = resolveTargets(options.targets ?? []);
108
+ const content = buildContentReport(rawCollections, translations, resolved, {
109
+ sourceMap,
110
+ itemLocales,
111
+ mediaPaths: rawMedia.map((m) => m.virtualPath)
112
+ });
113
+ content.warnings.push(...warnings);
114
+ const artifacts = { translations, content, targets: resolved.effective };
115
+ if (sourceMap) {
116
+ artifacts.sourceMap = { releaseId, ...sourceMap };
117
+ }
118
+ if (content.issues.some((i) => i.severity === "requirement")) {
119
+ throw new PublishRequirementsError(artifacts);
120
+ }
121
+ // 5. Write objects and media to CAS
122
+ for (const { hash, serialized } of objects) {
123
+ await this.store.writeObject(hash, serialized);
124
+ }
125
+ for (const [i, item] of rawMedia.entries()) {
126
+ await this.store.writeMedia(mediaHashes[i], path.extname(item.virtualPath), item.content);
127
+ }
128
+ // 6. Construct and write the Release Manifest
129
+ const releaseManifest = {
130
+ schemaVersion: 1,
131
+ releaseId,
132
+ createdAt: new Date().toISOString(),
133
+ sourceReadAt,
134
+ collections: collectionsMeta,
135
+ media: mediaMeta,
136
+ translations: toTranslationSummary(translations),
137
+ targets: Object.keys(content.targets).sort()
138
+ };
139
+ validateReleaseManifest(releaseManifest);
140
+ await this.store.writeRelease(releaseId, releaseManifest);
141
+ // 7. Update the Channel Manifest (LAST atomic step)
142
+ const channelManifest = {
143
+ schemaVersion: 1,
144
+ channel,
145
+ releaseId,
146
+ updatedAt: releaseManifest.createdAt
147
+ };
148
+ validateChannelManifest(channelManifest);
149
+ await this.store.writeChannelManifest(channel, channelManifest);
150
+ // 8. Clean up old releases if they exceed retention limit
151
+ await this.manageReleaseRetention();
152
+ return { manifest: releaseManifest, artifacts };
153
+ }
154
+ /**
155
+ * Source locale precedence: publish argument → ContentSource → inferred from markers → "en".
156
+ */
157
+ async resolveSourceLocale(argument, collections) {
158
+ if (argument)
159
+ return { locale: argument, origin: "argument" };
160
+ const fromSource = this.source.getSourceLocale ? await this.source.getSourceLocale() : undefined;
161
+ if (fromSource)
162
+ return { locale: fromSource, origin: "source" };
163
+ const inferred = inferSourceLocale(collections);
164
+ if (inferred)
165
+ return { locale: inferred, origin: "inferred" };
166
+ return { locale: DEFAULT_SOURCE_LOCALE, origin: "fallback" };
167
+ }
168
+ /**
169
+ * Reads the collections of the release a channel currently points to, or undefined
170
+ * if the channel, its release or any of its objects are missing.
171
+ */
172
+ async readChannelCollections(channel) {
173
+ const channelManifest = await this.store.readChannelManifest(channel);
174
+ if (!channelManifest)
175
+ return undefined;
176
+ const release = await this.store.readRelease(channelManifest.releaseId);
177
+ if (!release)
178
+ return undefined;
179
+ const collections = {};
180
+ for (const [name, meta] of Object.entries(release.collections)) {
181
+ const content = await this.store.readObject(meta.hash);
182
+ if (content === null)
183
+ return undefined;
184
+ collections[name] = JSON.parse(content).items;
185
+ }
186
+ return collections;
187
+ }
188
+ /**
189
+ * Cleans up old releases that exceed the configured retention limit.
190
+ * Releases that a channel currently points to are never deleted.
191
+ */
192
+ async manageReleaseRetention() {
193
+ const limit = this.config.retentionCount ?? 3;
194
+ const releases = await this.store.listReleases();
195
+ // Sort releases lexicographically (or by timestamp if ids are formatted as ISO timestamps)
196
+ // To be safe, we sort them so latest are at the end, and we delete from the beginning (oldest).
197
+ releases.sort();
198
+ if (releases.length > limit) {
199
+ const pinned = new Set();
200
+ for (const channel of await this.store.listChannels()) {
201
+ const manifest = await this.store.readChannelManifest(channel);
202
+ if (manifest)
203
+ pinned.add(manifest.releaseId);
204
+ }
205
+ const toDelete = releases.slice(0, releases.length - limit);
206
+ for (const relId of toDelete) {
207
+ if (pinned.has(relId))
208
+ continue;
209
+ await this.store.deleteRelease(relId);
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Reports per-release storage usage, diffs between releases, channel pointers
215
+ * and orphaned files. Read-only.
216
+ */
217
+ async analyzeStorage() {
218
+ return analyzeStorage(this.store);
219
+ }
220
+ /**
221
+ * Deletes all objects/media not referenced by any retained release.
222
+ * Deletes exactly the orphans listed in the returned report.
223
+ */
224
+ async garbageCollect(options = {}) {
225
+ const dryRun = options.dryRun ?? false;
226
+ const report = await analyzeStorage(this.store);
227
+ let deletedObjects = 0;
228
+ let deletedMedia = 0;
229
+ for (const orphan of report.orphans.files) {
230
+ if (orphan.kind === "object") {
231
+ if (!dryRun)
232
+ await this.store.deleteObject(orphan.hash);
233
+ deletedObjects++;
234
+ }
235
+ else {
236
+ if (!dryRun)
237
+ await this.store.deleteMedia(orphan.file.slice("media/".length));
238
+ deletedMedia++;
239
+ }
240
+ }
241
+ return { dryRun, deletedObjects, deletedMedia, freedBytes: report.orphans.bytes, report };
242
+ }
243
+ }
244
+ // Reports media the publisher leaves out, and references to media the source doesn't provide
245
+ function mediaWarnings(collections, media, usage) {
246
+ const warnings = [];
247
+ const provided = new Set(media.map((m) => m.virtualPath));
248
+ for (const path of provided) {
249
+ if (!usage.has(path))
250
+ warnings.push(`Media ${path} is not referenced by any item and was not published`);
251
+ }
252
+ for (const item of collections[MEDIA_COLLECTION] ?? []) {
253
+ if (!provided.has(item.id) || !usage.has(item.id)) {
254
+ warnings.push(`_media entry ${item.id} was ignored because the media isn't published`);
255
+ }
256
+ }
257
+ for (const path of usage.keys()) {
258
+ if (!provided.has(path))
259
+ warnings.push(`Media ${path} is referenced but not provided by the source`);
260
+ }
261
+ return warnings;
262
+ }
263
+ //# sourceMappingURL=publisher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../src/core/publisher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { ReleaseManifest, TranslationCounts } from "../types.js";
2
+ import { ContentIssue, TargetResult } from "./content-report.js";
3
+ import { PublishArtifacts } from "./publisher.js";
4
+ import { SourceLocaleOrigin } from "./translations.js";
5
+ export interface PublishReport {
6
+ schemaVersion: 1;
7
+ status: "published" | "failed";
8
+ channel: string;
9
+ releaseId: string;
10
+ createdAt: string;
11
+ error?: string;
12
+ summary: {
13
+ requirements: number;
14
+ recommendations: number;
15
+ warnings: number;
16
+ targets: {
17
+ satisfied: number;
18
+ total: number;
19
+ };
20
+ translations: TranslationCounts;
21
+ };
22
+ release?: {
23
+ collections: number;
24
+ items: number;
25
+ media: number;
26
+ };
27
+ targets: Record<string, TargetResult>;
28
+ translations: {
29
+ sourceLocale: string;
30
+ sourceLocaleOrigin: SourceLocaleOrigin;
31
+ locales: Record<string, TranslationCounts>;
32
+ collections: Record<string, Record<string, TranslationCounts>>;
33
+ itemsWithoutSource: {
34
+ collection: string;
35
+ id: string;
36
+ }[];
37
+ };
38
+ issues: ContentIssue[];
39
+ warnings: string[];
40
+ }
41
+ /**
42
+ * Builds the report for a publish run, successful (with manifest) or failed (with error).
43
+ */
44
+ export declare function createPublishReport(input: {
45
+ channel: string;
46
+ releaseId: string;
47
+ artifacts: PublishArtifacts;
48
+ manifest?: ReleaseManifest;
49
+ error?: Error;
50
+ }): PublishReport;
51
+ /**
52
+ * Renders a report as a self-contained HTML page (no external resources, works offline).
53
+ */
54
+ export declare function renderPublishReportHtml(report: PublishReport): string;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Builds the report for a publish run, successful (with manifest) or failed (with error).
3
+ */
4
+ export function createPublishReport(input) {
5
+ const { content, translations } = input.artifacts;
6
+ const severityOrder = { requirement: 0, recommendation: 1 };
7
+ const issues = [...content.issues].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] ||
8
+ (a.collection ?? "").localeCompare(b.collection ?? "") ||
9
+ (a.id ?? "").localeCompare(b.id ?? "") ||
10
+ (a.locale ?? "").localeCompare(b.locale ?? "") ||
11
+ (a.field ?? "").localeCompare(b.field ?? ""));
12
+ const targets = Object.values(content.targets);
13
+ return {
14
+ schemaVersion: 1,
15
+ status: input.manifest ? "published" : "failed",
16
+ channel: input.channel,
17
+ releaseId: input.releaseId,
18
+ createdAt: new Date().toISOString(),
19
+ ...(input.error ? { error: input.error.message } : {}),
20
+ summary: {
21
+ requirements: issues.filter((i) => i.severity === "requirement").length,
22
+ recommendations: issues.filter((i) => i.severity === "recommendation").length,
23
+ warnings: content.warnings.length,
24
+ targets: { satisfied: targets.filter((t) => t.satisfied).length, total: targets.length },
25
+ translations: translations.overall
26
+ },
27
+ ...(input.manifest
28
+ ? {
29
+ release: {
30
+ collections: Object.keys(input.manifest.collections).length,
31
+ items: Object.values(input.manifest.collections).reduce((n, c) => n + c.itemCount, 0),
32
+ media: Object.keys(input.manifest.media).length
33
+ }
34
+ }
35
+ : {}),
36
+ targets: content.targets,
37
+ translations: {
38
+ sourceLocale: translations.sourceLocale,
39
+ sourceLocaleOrigin: translations.sourceLocaleOrigin,
40
+ locales: translations.locales,
41
+ collections: translations.collections,
42
+ itemsWithoutSource: translations.itemsWithoutSource
43
+ },
44
+ issues,
45
+ warnings: content.warnings
46
+ };
47
+ }
48
+ const escape = (value) => String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
49
+ const percent = (c) => (c.expected === 0 ? "–" : `${Math.floor((c.translated / c.expected) * 1000) / 10}%`);
50
+ /**
51
+ * Renders a report as a self-contained HTML page (no external resources, works offline).
52
+ */
53
+ export function renderPublishReportHtml(report) {
54
+ const s = report.summary;
55
+ const locales = Object.keys(report.translations.locales);
56
+ const card = (label, value, tone = "") => `<div class="card ${tone}"><div class="value">${value}</div><div class="label">${label}</div></div>`;
57
+ const targetRows = Object.entries(report.targets).map(([id, t]) => `<tr><td><code>${escape(id)}</code></td><td><span class="badge ${t.satisfied ? "ok" : "bad"}">${t.satisfied ? "satisfied" : "failed"}</span></td><td class="num">${t.requirements}</td><td class="num">${t.recommendations}</td></tr>`).join("");
58
+ const translationRows = Object.entries(report.translations.collections).map(([collection, byLocale]) => `<tr><td><code>${escape(collection)}</code></td>${locales.map((l) => {
59
+ const c = byLocale[l];
60
+ return `<td class="num">${c ? `${percent(c)} <small>${c.translated}/${c.expected}${c.stale ? ` · ${c.stale} stale` : ""}${c.machine ? ` · ${c.machine} machine` : ""}</small>` : "–"}</td>`;
61
+ }).join("")}</tr>`).join("");
62
+ const issueRows = report.issues.map((i) => `<tr class="${i.severity}"><td><span class="badge ${i.severity === "requirement" ? "bad" : "warn"}">${i.severity}</span></td><td>${escape(i.target ?? "")}</td><td><code>${escape([i.collection, i.id].filter(Boolean).join(" / "))}</code></td><td>${escape(i.locale ?? "")}</td><td><code>${escape(i.field ?? "")}</code></td><td class="nowrap">${escape(i.issue)}</td><td>${escape(i.message)}</td><td>${i.source ? `<a href="${escape(i.source)}">open</a>` : ""}</td></tr>`).join("");
63
+ return `<!DOCTYPE html>
64
+ <html lang="en">
65
+ <head>
66
+ <meta charset="UTF-8">
67
+ <meta name="viewport" content="width=device-width, initial-scale=1">
68
+ <title>Publish report · ${escape(report.releaseId)}</title>
69
+ <style>
70
+ :root { --bg: #f8fafc; --panel: #ffffff; --text: #0f172a; --muted: #64748b; --line: #e2e8f0; --ok: #047857; --bad: #b91c1c; --warn: #b45309; }
71
+ @media (prefers-color-scheme: dark) { :root { --bg: #020617; --panel: #0f172a; --text: #e2e8f0; --muted: #94a3b8; --line: #1e293b; --ok: #34d399; --bad: #f87171; --warn: #fbbf24; } }
72
+ * { box-sizing: border-box; }
73
+ body { margin: 0; padding: 32px 16px; background: var(--bg); color: var(--text); font: 14px/1.5 system-ui, sans-serif; }
74
+ main { max-width: 1200px; margin: 0 auto; }
75
+ h1 { font-size: 22px; margin: 0 0 4px; } h2 { font-size: 16px; margin: 32px 0 12px; }
76
+ .meta { color: var(--muted); }
77
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-top: 20px; }
78
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; }
79
+ .card .value { font-size: 22px; font-weight: 700; } .card .label { color: var(--muted); font-size: 12px; }
80
+ .card.bad .value { color: var(--bad); } .card.warn .value { color: var(--warn); } .card.ok .value { color: var(--ok); }
81
+ .table { overflow-x: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; }
82
+ table { width: 100%; border-collapse: collapse; }
83
+ th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
84
+ th { color: var(--muted); font-size: 12px; font-weight: 600; } tr:last-child td { border-bottom: 0; }
85
+ .num { text-align: right; white-space: nowrap; } .nowrap { white-space: nowrap; } small { color: var(--muted); }
86
+ code { font: 12px ui-monospace, monospace; }
87
+ .badge { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; border: 1px solid currentColor; }
88
+ .badge.ok { color: var(--ok); } .badge.bad { color: var(--bad); } .badge.warn { color: var(--warn); }
89
+ .error { margin-top: 16px; padding: 12px 16px; border: 1px solid var(--bad); border-radius: 12px; color: var(--bad); white-space: pre-wrap; }
90
+ .empty { color: var(--muted); padding: 12px 16px; }
91
+ ul { margin: 0; padding: 12px 16px 12px 32px; }
92
+ a { color: inherit; }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <main>
97
+ <h1>Publish report <span class="badge ${report.status === "published" ? "ok" : "bad"}">${report.status}</span></h1>
98
+ <div class="meta">Release <code>${escape(report.releaseId)}</code> · channel <code>${escape(report.channel)}</code> · ${escape(report.createdAt)} · source locale <code>${escape(report.translations.sourceLocale)}</code> (${escape(report.translations.sourceLocaleOrigin)})</div>
99
+ ${report.error ? `<div class="error">${escape(report.error)}</div>` : ""}
100
+
101
+ <div class="cards">
102
+ ${card("failed requirements", String(s.requirements), s.requirements ? "bad" : "ok")}
103
+ ${card("recommendations", String(s.recommendations), s.recommendations ? "warn" : "ok")}
104
+ ${card("warnings", String(s.warnings), s.warnings ? "warn" : "ok")}
105
+ ${card("targets satisfied", `${s.targets.satisfied}/${s.targets.total}`, s.targets.satisfied === s.targets.total ? "ok" : "bad")}
106
+ ${card("translated", `${percent(s.translations)}`, s.translations.missing || s.translations.stale ? "warn" : "ok")}
107
+ ${report.release ? card("collections · items · media", `${report.release.collections} · ${report.release.items} · ${report.release.media}`) : ""}
108
+ </div>
109
+
110
+ <h2>Targets</h2>
111
+ <div class="table"><table><thead><tr><th>Target</th><th>Status</th><th class="num">Failed requirements</th><th class="num">Recommendations</th></tr></thead><tbody>${targetRows}</tbody></table></div>
112
+
113
+ <h2>Translations (vs. ${escape(report.translations.sourceLocale)})</h2>
114
+ ${locales.length
115
+ ? `<div class="table"><table><thead><tr><th>Collection</th>${locales.map((l) => `<th class="num">${escape(l)}</th>`).join("")}</tr></thead><tbody>${translationRows}</tbody></table></div>`
116
+ : `<div class="table empty">No target locales.</div>`}
117
+
118
+ <h2>Issues (${report.issues.length})</h2>
119
+ ${report.issues.length
120
+ ? `<div class="table"><table><thead><tr><th>Severity</th><th>Target</th><th>Item</th><th>Locale</th><th>Field</th><th>Issue</th><th>Message</th><th>Source</th></tr></thead><tbody>${issueRows}</tbody></table></div>`
121
+ : `<div class="table empty">No issues.</div>`}
122
+
123
+ <h2>Warnings (${report.warnings.length})</h2>
124
+ ${report.warnings.length
125
+ ? `<div class="table"><ul>${report.warnings.map((w) => `<li>${escape(w)}</li>`).join("")}</ul></div>`
126
+ : `<div class="table empty">No warnings.</div>`}
127
+ </main>
128
+ </body>
129
+ </html>
130
+ `;
131
+ }
132
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/core/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { CollectionItem } from "../types.js";
2
+ export declare const SITE_COLLECTION = "_site";
3
+ export declare const LANGUAGES_COLLECTION = "_languages";
4
+ export interface SiteItem extends CollectionItem {
5
+ defaultLanguage?: string;
6
+ }
7
+ /**
8
+ * Checks _site and _languages (each only if present). Throws on the first problem: schema errors,
9
+ * a _site with other than one item, the same language twice, a defaultLanguage that isn't a
10
+ * language, and route paths in a language _languages doesn't list.
11
+ */
12
+ export declare function validateSettings(collections: Record<string, CollectionItem[]>): void;
@@ -0,0 +1,40 @@
1
+ import { validateLanguageItem, validateSiteItem } from "../validation.js";
2
+ import { ROUTES_COLLECTION } from "./site.js";
3
+ // Optional, typed reserved collections: the site's settings and the languages it's published in
4
+ export const SITE_COLLECTION = "_site";
5
+ export const LANGUAGES_COLLECTION = "_languages";
6
+ /**
7
+ * Checks _site and _languages (each only if present). Throws on the first problem: schema errors,
8
+ * a _site with other than one item, the same language twice, a defaultLanguage that isn't a
9
+ * language, and route paths in a language _languages doesn't list.
10
+ */
11
+ export function validateSettings(collections) {
12
+ const languages = collections[LANGUAGES_COLLECTION];
13
+ const keys = new Set();
14
+ for (const language of languages ?? []) {
15
+ validateLanguageItem(language);
16
+ if (keys.has(language.key))
17
+ throw new Error(`Invalid _languages item (${language.id}): language "${language.key}" is listed twice`);
18
+ keys.add(language.key);
19
+ }
20
+ const site = collections[SITE_COLLECTION];
21
+ if (site) {
22
+ if (site.length !== 1)
23
+ throw new Error(`Invalid _site: needs exactly one item, has ${site.length}`);
24
+ const item = site[0];
25
+ validateSiteItem(item);
26
+ if (languages && item.defaultLanguage && !keys.has(item.defaultLanguage)) {
27
+ throw new Error(`Invalid _site item (${item.id}): defaultLanguage "${item.defaultLanguage}" isn't in _languages`);
28
+ }
29
+ }
30
+ if (languages) {
31
+ for (const route of collections[ROUTES_COLLECTION] ?? []) {
32
+ for (const [locale, t] of Object.entries(route.translations)) {
33
+ if (typeof t?.path === "string" && !keys.has(locale)) {
34
+ throw new Error(`Invalid _routes item (${route.id}): has a path in "${locale}", which isn't in _languages`);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/core/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,gGAAgG;AAChG,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAMjD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAA6C;IAC5E,MAAM,SAAS,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,QAAQ,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;QACvC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,EAAE,gBAAgB,QAAQ,CAAC,GAAG,mBAAmB,CAAC,CAAC;QACpI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACpG,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAa,CAAC;QACjC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,SAAS,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YACzE,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,EAAE,uBAAuB,IAAI,CAAC,eAAe,uBAAuB,CAAC,CAAC;QACpH,CAAC;IACH,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC;YACzD,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7D,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACrD,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,EAAE,qBAAqB,MAAM,8BAA8B,CAAC,CAAC;gBAC9G,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { CollectionItem, Reference } from "../types.js";
2
+ export declare const ROUTES_COLLECTION = "_routes";
3
+ export declare const PAGES_COLLECTION = "_pages";
4
+ export declare const BLOCKS_COLLECTION = "_blocks";
5
+ export interface RouteItem extends CollectionItem {
6
+ page?: string;
7
+ redirect?: string;
8
+ status?: 301 | 302;
9
+ }
10
+ export interface PageItem extends CollectionItem {
11
+ blocks?: string[];
12
+ }
13
+ export interface BlockItem extends CollectionItem {
14
+ type: string;
15
+ items?: Reference[];
16
+ source?: {
17
+ collection: string;
18
+ };
19
+ links?: string[];
20
+ settings?: Record<string, unknown>;
21
+ }
22
+ /**
23
+ * Checks _routes, _pages and _blocks (each only if present). Throws on the first problem:
24
+ * schema errors, a route without page or redirect (or both), unknown pages, routes, blocks,
25
+ * collections or items, redirect cycles, a route without any path, the same path twice in a locale,
26
+ * and blocks with both items and source.
27
+ */
28
+ export declare function validateSite(collections: Record<string, CollectionItem[]>): void;
29
+ /**
30
+ * Structure that is valid but probably unintended: pages no route shows, blocks no page uses.
31
+ */
32
+ export declare function siteRecommendations(collections: Record<string, CollectionItem[]>): {
33
+ collection: string;
34
+ id: string;
35
+ issue: string;
36
+ message: string;
37
+ }[];