@haverstack/eleventy 0.2.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/assets.d.ts +52 -0
  4. package/dist/assets.d.ts.map +1 -0
  5. package/dist/assets.js +148 -0
  6. package/dist/assets.js.map +1 -0
  7. package/dist/check.d.ts +35 -0
  8. package/dist/check.d.ts.map +1 -0
  9. package/dist/check.js +141 -0
  10. package/dist/check.js.map +1 -0
  11. package/dist/cli.d.ts +15 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +122 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/emit.d.ts +72 -0
  16. package/dist/emit.d.ts.map +1 -0
  17. package/dist/emit.js +147 -0
  18. package/dist/emit.js.map +1 -0
  19. package/dist/errors.d.ts +12 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +15 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/feeds.d.ts +24 -0
  24. package/dist/feeds.d.ts.map +1 -0
  25. package/dist/feeds.js +103 -0
  26. package/dist/feeds.js.map +1 -0
  27. package/dist/index.d.ts +94 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +73 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/load.d.ts +86 -0
  32. package/dist/load.d.ts.map +1 -0
  33. package/dist/load.js +258 -0
  34. package/dist/load.js.map +1 -0
  35. package/dist/markdown.d.ts +23 -0
  36. package/dist/markdown.d.ts.map +1 -0
  37. package/dist/markdown.js +81 -0
  38. package/dist/markdown.js.map +1 -0
  39. package/dist/publish.d.ts +45 -0
  40. package/dist/publish.d.ts.map +1 -0
  41. package/dist/publish.js +79 -0
  42. package/dist/publish.js.map +1 -0
  43. package/dist/resolve.d.ts +119 -0
  44. package/dist/resolve.d.ts.map +1 -0
  45. package/dist/resolve.js +411 -0
  46. package/dist/resolve.js.map +1 -0
  47. package/dist/scaffold.d.ts +22 -0
  48. package/dist/scaffold.d.ts.map +1 -0
  49. package/dist/scaffold.js +98 -0
  50. package/dist/scaffold.js.map +1 -0
  51. package/dist/templates.d.ts +34 -0
  52. package/dist/templates.d.ts.map +1 -0
  53. package/dist/templates.js +171 -0
  54. package/dist/templates.js.map +1 -0
  55. package/dist/types.d.ts +96 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +123 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +72 -0
package/dist/load.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @haverstack/eleventy — load phase
3
+ * -------------------------------------------------------
4
+ * Fetch every record a build needs and turn it into in-memory indexes.
5
+ * This phase is mechanical: it resolves the site, walks the change-free
6
+ * structure, and groups records for lookup. Every decision — cascades,
7
+ * collisions, permalinks, collection membership — belongs to resolve.
8
+ *
9
+ * Two disciplines this phase enforces (see docs/design.md § Load):
10
+ *
11
+ * - Every query loops on the cursor to exhaustion. `cursor === null` is
12
+ * the only end-of-results signal; a short or empty page is not. `total`
13
+ * is `null` under a scoped stack, so there is no count to check.
14
+ * - Every query asks for unlisted records. They still build at their
15
+ * URLs, so the build must see them; excluding them from listings is
16
+ * resolve's job. `includeUnlisted` is owner-only, so a build under a
17
+ * scoped credential silently loses them — `unlistedVisible` records
18
+ * whether that happened.
19
+ */
20
+ import { type FileId, type RecordId, type StackClient, type StackRecord } from '@haverstack/core';
21
+ /** The `collection` object a listing-root `page@1` carries. */
22
+ export interface CollectionSpec {
23
+ typeId: string;
24
+ tag?: string;
25
+ order?: string;
26
+ }
27
+ export interface LoadOptions {
28
+ /**
29
+ * The `site@1` record to build, by `handle` or by record id. Omit only
30
+ * on a stack with no `site@1` records — the single-site case.
31
+ */
32
+ site?: string;
33
+ }
34
+ /** `page-meta` sidecars for one described record, split by scope. */
35
+ export interface SidecarSet {
36
+ /** Sidecars with no `for-site` association — the record's default. >1 is a writer error. */
37
+ unscoped: StackRecord[];
38
+ /** Sidecars scoped to the site being built. >1 is a writer error. */
39
+ thisSite: StackRecord[];
40
+ /** Every scoped sidecar by the site it targets — for resolving links to other sites. */
41
+ bySite: Map<RecordId, StackRecord[]>;
42
+ }
43
+ /** A node in the site's page tree. */
44
+ export interface PageNode {
45
+ record: StackRecord;
46
+ parent: PageNode | null;
47
+ children: PageNode[];
48
+ }
49
+ export interface StackIndex {
50
+ /** The site being built, or `null` on a stack with no `site@1` records. */
51
+ site: StackRecord | null;
52
+ /** Every `site@1` record, for cross-site link resolution. */
53
+ sitesById: Map<RecordId, StackRecord>;
54
+ sitesByHandle: Map<string, StackRecord>;
55
+ /**
56
+ * Every record this phase fetched, by id: every site's pages, all menus,
57
+ * sidecars, attachment metadata, the owner, and the listing members that
58
+ * carry a sidecar. Every key of `sidecarsByParent` resolves here.
59
+ */
60
+ byId: Map<RecordId, StackRecord>;
61
+ /** Root pages of the site being built, sorted stably. */
62
+ pageRoots: PageNode[];
63
+ /** Every page in the site's pruned subtree, by id. */
64
+ pagesById: Map<RecordId, PageNode>;
65
+ /** `page-meta` sidecars grouped by the record they describe. */
66
+ sidecarsByParent: Map<RecordId, SidecarSet>;
67
+ /**
68
+ * Raw collection candidates per listing-root page id: every record the
69
+ * root's `collection` query selects, scoped to this site and unlisted
70
+ * included, in query order. Draft/hidden filtering, sorting and
71
+ * permalinks are resolve's.
72
+ */
73
+ membersByListingRoot: Map<RecordId, StackRecord[]>;
74
+ /** The site's menus, in fetch order. */
75
+ menus: StackRecord[];
76
+ /** The site's menus grouped by handle (one-to-many: uniqueness is unenforced). */
77
+ menusByHandle: Map<string, StackRecord[]>;
78
+ /** `_attachment@1` records grouped by fileId, earliest `createdAt` first. */
79
+ attachmentsByFileId: Map<FileId, StackRecord[]>;
80
+ /** The stack owner's `_entity@1` card, or `null`. */
81
+ owner: StackRecord | null;
82
+ /** `false` when the build runs under a credential that cannot see unlisted records. */
83
+ unlistedVisible: boolean;
84
+ }
85
+ export declare function load(stack: StackClient, opts?: LoadOptions): Promise<StackIndex>;
86
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAGL,KAAK,MAAM,EAEX,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAK1B,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,4FAA4F;IAC5F,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,qEAAqE;IACrE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,wFAAwF;IACxF,MAAM,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;CACtC;AAED,sCAAsC;AACtC,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,6DAA6D;IAC7D,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACtC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACxC;;;;OAIG;IACH,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACjC,yDAAyD;IACzD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,sDAAsD;IACtD,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACnC,gEAAgE;IAChE,gBAAgB,EAAE,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,oBAAoB,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;IACnD,wCAAwC;IACxC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,kFAAkF;IAClF,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAC1C,6EAA6E;IAC7E,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAChD,qDAAqD;IACrD,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,uFAAuF;IACvF,eAAe,EAAE,OAAO,CAAC;CAC1B;AAyHD,wBAAsB,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoI1F"}
package/dist/load.js ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * @haverstack/eleventy — load phase
3
+ * -------------------------------------------------------
4
+ * Fetch every record a build needs and turn it into in-memory indexes.
5
+ * This phase is mechanical: it resolves the site, walks the change-free
6
+ * structure, and groups records for lookup. Every decision — cascades,
7
+ * collisions, permalinks, collection membership — belongs to resolve.
8
+ *
9
+ * Two disciplines this phase enforces (see docs/design.md § Load):
10
+ *
11
+ * - Every query loops on the cursor to exhaustion. `cursor === null` is
12
+ * the only end-of-results signal; a short or empty page is not. `total`
13
+ * is `null` under a scoped stack, so there is no count to check.
14
+ * - Every query asks for unlisted records. They still build at their
15
+ * URLs, so the build must see them; excluding them from listings is
16
+ * resolve's job. `includeUnlisted` is owner-only, so a build under a
17
+ * scoped credential silently loses them — `unlistedVisible` records
18
+ * whether that happened.
19
+ */
20
+ import { StackPermissionError, SYSTEM_TYPES, } from '@haverstack/core';
21
+ import { PAGE, SITE } from '@haverstack/commons';
22
+ import { FOR_SITE_LABEL, MENU, PAGE_META, SITE_MEMBERSHIP_LABEL } from './types.js';
23
+ import { HaverstackEleventyError } from './errors.js';
24
+ const QUERY_PAGE_SIZE = 100;
25
+ /** Walk `filter` to exhaustion. See the module comment for why the loop is the only correct form. */
26
+ async function queryAll(stack, filter) {
27
+ const out = [];
28
+ let cursor;
29
+ do {
30
+ const page = await stack.query({ filter, cursor, limit: QUERY_PAGE_SIZE });
31
+ out.push(...page.records);
32
+ cursor = page.cursor ?? undefined;
33
+ } while (cursor);
34
+ return out;
35
+ }
36
+ function resolveSite(sites, want) {
37
+ if (want === undefined) {
38
+ if (sites.length === 0)
39
+ return null;
40
+ const known = sites.map((s) => s.content.handle ?? `(${s.id})`).join(', ');
41
+ throw new HaverstackEleventyError(`This stack has ${sites.length} site@1 record(s); name one with the \`site\` option ` +
42
+ `(handle or record id). Known: ${known}.`);
43
+ }
44
+ const byHandle = sites.filter((s) => s.content.handle === want);
45
+ if (byHandle.length === 1)
46
+ return byHandle[0];
47
+ if (byHandle.length > 1) {
48
+ throw new HaverstackEleventyError(`The \`site\` handle "${want}" resolves to ${byHandle.length} records ` +
49
+ `(${byHandle.map((s) => s.id).join(', ')}). Handle uniqueness is a writer obligation.`);
50
+ }
51
+ const byId = sites.find((s) => s.id === want);
52
+ if (byId)
53
+ return byId;
54
+ throw new HaverstackEleventyError(`No site@1 record with handle or id "${want}".`);
55
+ }
56
+ /**
57
+ * The first ancestor of `startId` that is not itself a page in `rawPages`,
58
+ * or `null` for a page with no parent (or an ancestry cycle). A `site@1`
59
+ * parent is one such stop; so is a parent that was never fetched.
60
+ */
61
+ function terminalAncestor(startId, rawPages) {
62
+ const seen = new Set();
63
+ let current = rawPages.get(startId);
64
+ while (current) {
65
+ if (seen.has(current.id))
66
+ return null;
67
+ seen.add(current.id);
68
+ const parentId = current.parentId;
69
+ if (!parentId)
70
+ return null;
71
+ const parent = rawPages.get(parentId);
72
+ if (!parent)
73
+ return parentId;
74
+ current = parent;
75
+ }
76
+ return null;
77
+ }
78
+ const pageOrder = (a, b) => a.record.createdAt.getTime() - b.record.createdAt.getTime() ||
79
+ (a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0);
80
+ function buildPageTree(kept) {
81
+ const byId = new Map(kept.map((r) => [r.id, { record: r, parent: null, children: [] }]));
82
+ const roots = [];
83
+ for (const node of byId.values()) {
84
+ const parentId = node.record.parentId;
85
+ const parent = parentId ? byId.get(parentId) : undefined;
86
+ if (parent) {
87
+ node.parent = parent;
88
+ parent.children.push(node);
89
+ }
90
+ else {
91
+ roots.push(node);
92
+ }
93
+ }
94
+ roots.sort(pageOrder);
95
+ for (const node of byId.values())
96
+ node.children.sort(pageOrder);
97
+ return { roots, byId };
98
+ }
99
+ function forSiteTargets(record) {
100
+ const out = [];
101
+ for (const a of record.associations ?? []) {
102
+ if (a.kind === 'relationship' && a.label === FOR_SITE_LABEL && a.target.scope === 'record') {
103
+ out.push(a.target.recordId);
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ function groupSidecars(sidecars, siteId) {
109
+ const map = new Map();
110
+ for (const sc of sidecars) {
111
+ if (!sc.parentId)
112
+ continue;
113
+ const set = map.get(sc.parentId) ?? {
114
+ unscoped: [],
115
+ thisSite: [],
116
+ bySite: new Map(),
117
+ };
118
+ const targets = forSiteTargets(sc);
119
+ if (targets.length === 0) {
120
+ set.unscoped.push(sc);
121
+ }
122
+ else {
123
+ for (const target of targets) {
124
+ const list = set.bySite.get(target) ?? [];
125
+ list.push(sc);
126
+ set.bySite.set(target, list);
127
+ }
128
+ if (siteId !== undefined && targets.includes(siteId))
129
+ set.thisSite.push(sc);
130
+ }
131
+ map.set(sc.parentId, set);
132
+ }
133
+ return map;
134
+ }
135
+ export async function load(stack, opts = {}) {
136
+ // Probe includeUnlisted once — it is owner-only, and a scoped stack
137
+ // throws rather than downgrading silently.
138
+ let unlistedVisible = true;
139
+ let siteRecords;
140
+ try {
141
+ siteRecords = await queryAll(stack, { typeId: SITE.id, includeUnlisted: true });
142
+ }
143
+ catch (err) {
144
+ if (!(err instanceof StackPermissionError))
145
+ throw err;
146
+ unlistedVisible = false;
147
+ siteRecords = await queryAll(stack, { typeId: SITE.id });
148
+ }
149
+ const unlisted = unlistedVisible
150
+ ? { includeUnlisted: true }
151
+ : {};
152
+ const site = resolveSite(siteRecords, opts.site);
153
+ const sitesById = new Map(siteRecords.map((s) => [s.id, s]));
154
+ const sitesByHandle = new Map();
155
+ for (const s of siteRecords) {
156
+ const handle = s.content.handle;
157
+ if (typeof handle === 'string' && handle && !sitesByHandle.has(handle)) {
158
+ sitesByHandle.set(handle, s);
159
+ }
160
+ }
161
+ const [allPages, allSidecars, allMenus, allAttachments, owner] = await Promise.all([
162
+ queryAll(stack, { typeId: PAGE.id, ...unlisted }),
163
+ queryAll(stack, { typeId: PAGE_META.id, ...unlisted }),
164
+ queryAll(stack, { typeId: MENU.id, ...(site ? { parentId: site.id } : {}), ...unlisted }),
165
+ queryAll(stack, { baseId: SYSTEM_TYPES.ATTACHMENT, ...unlisted }),
166
+ stack.getOwnerEntity(),
167
+ ]);
168
+ const rawPages = new Map(allPages.map((p) => [p.id, p]));
169
+ const kept = allPages.filter((p) => {
170
+ const terminal = terminalAncestor(p.id, rawPages);
171
+ return site ? terminal === site.id : terminal === null || !sitesById.has(terminal);
172
+ });
173
+ const { roots: pageRoots, byId: pagesById } = buildPageTree(kept);
174
+ const sidecarsByParent = groupSidecars(allSidecars, site?.id);
175
+ // Collection candidates: one query per listing root, scoped to this site
176
+ // by a `relatedTo` clause (omitted on a single-site stack). Query
177
+ // construction only — the draft convention, sort and permalinks that turn
178
+ // candidates into a listing are resolve's. The roots run concurrently:
179
+ // over a remote stack these are the calls that add up.
180
+ const listingNodes = [...pagesById.values()].filter((n) => n.record.content.collection?.typeId);
181
+ const listingMembers = await Promise.all(listingNodes.map((node) => {
182
+ const spec = node.record.content.collection;
183
+ return queryAll(stack, {
184
+ typeId: spec.typeId,
185
+ ...(spec.tag ? { tags: [spec.tag] } : {}),
186
+ ...(site
187
+ ? {
188
+ relatedTo: {
189
+ label: SITE_MEMBERSHIP_LABEL,
190
+ target: { scope: 'record', recordId: site.id },
191
+ },
192
+ }
193
+ : {}),
194
+ ...unlisted,
195
+ });
196
+ }));
197
+ const membersByListingRoot = new Map(listingNodes.map((node, i) => [node.record.id, listingMembers[i]]));
198
+ const menusByHandle = new Map();
199
+ for (const menu of allMenus) {
200
+ const handle = menu.content.handle;
201
+ if (typeof handle !== 'string')
202
+ continue;
203
+ const list = menusByHandle.get(handle) ?? [];
204
+ list.push(menu);
205
+ menusByHandle.set(handle, list);
206
+ }
207
+ const attachmentsByFileId = new Map();
208
+ for (const attachment of allAttachments) {
209
+ const fileId = attachment.content.fileId;
210
+ if (!fileId)
211
+ continue;
212
+ const list = attachmentsByFileId.get(fileId) ?? [];
213
+ list.push(attachment);
214
+ attachmentsByFileId.set(fileId, list);
215
+ }
216
+ for (const list of attachmentsByFileId.values()) {
217
+ list.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
218
+ }
219
+ const byId = new Map();
220
+ for (const record of [
221
+ ...siteRecords,
222
+ ...allPages,
223
+ ...allMenus,
224
+ ...allSidecars,
225
+ ...allAttachments,
226
+ ...[...membersByListingRoot.values()].flat(),
227
+ ]) {
228
+ byId.set(record.id, record);
229
+ }
230
+ if (owner)
231
+ byId.set(owner.id, owner);
232
+ // The records sidecars describe — listing members carrying page-meta —
233
+ // are not bulk-fetched here (resolve's collection queries bring them in).
234
+ // Point-read the few that carry a sidecar so every key in
235
+ // `sidecarsByParent` resolves through `byId`: `check` names them, and a
236
+ // scoped read that returns null just leaves the gap it would have anyway.
237
+ const describedIds = [...sidecarsByParent.keys()].filter((id) => !byId.has(id));
238
+ const described = await Promise.all(describedIds.map((id) => stack.get(id)));
239
+ for (const record of described)
240
+ if (record)
241
+ byId.set(record.id, record);
242
+ return {
243
+ site,
244
+ sitesById,
245
+ sitesByHandle,
246
+ byId,
247
+ pageRoots,
248
+ pagesById,
249
+ sidecarsByParent,
250
+ membersByListingRoot,
251
+ menus: allMenus,
252
+ menusByHandle,
253
+ attachmentsByFileId,
254
+ owner,
255
+ unlistedVisible,
256
+ };
257
+ }
258
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../src/load.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @haverstack/eleventy — body rendering
3
+ * -------------------------------------------------------
4
+ * A plugin-owned markdown-it instance so record bodies render the same
5
+ * regardless of the host's markdown config. Embed substitution runs first
6
+ * (it rewrites link and image targets in the source text); the result is
7
+ * sanitized, because a stack can hold records written by more than one
8
+ * person and sanitizing on render costs little.
9
+ *
10
+ * `format` follows the commons vocabulary: `markdown` when absent, `plain`
11
+ * when set, and any unrecognised value rendered as plain — never a richer
12
+ * format than the record declares. See docs/design.md § Markdown.
13
+ */
14
+ /**
15
+ * Rewrite `](filename)` / `](<filename>)` targets to their staged asset
16
+ * paths. Exposed so a site rendering the raw body itself gets image links
17
+ * that resolve.
18
+ */
19
+ export declare function substituteEmbeds(text: string, embeds: Map<string, string>): string;
20
+ export declare function renderMarkdown(text: string): string;
21
+ export declare function renderPlain(text: string): string;
22
+ export declare function renderBody(text: string, format: unknown, embeds?: Map<string, string>): string;
23
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2CH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CASlF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK9F"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @haverstack/eleventy — body rendering
3
+ * -------------------------------------------------------
4
+ * A plugin-owned markdown-it instance so record bodies render the same
5
+ * regardless of the host's markdown config. Embed substitution runs first
6
+ * (it rewrites link and image targets in the source text); the result is
7
+ * sanitized, because a stack can hold records written by more than one
8
+ * person and sanitizing on render costs little.
9
+ *
10
+ * `format` follows the commons vocabulary: `markdown` when absent, `plain`
11
+ * when set, and any unrecognised value rendered as plain — never a richer
12
+ * format than the record declares. See docs/design.md § Markdown.
13
+ */
14
+ import MarkdownIt from 'markdown-it';
15
+ import sanitizeHtml from 'sanitize-html';
16
+ const md = new MarkdownIt({
17
+ html: true,
18
+ linkify: true,
19
+ typographer: true,
20
+ langPrefix: 'language-',
21
+ });
22
+ const SANITIZE = {
23
+ allowedTags: [
24
+ ...sanitizeHtml.defaults.allowedTags,
25
+ 'img',
26
+ 'figure',
27
+ 'figcaption',
28
+ 'picture',
29
+ 'source',
30
+ 'h1',
31
+ 'h2',
32
+ 'del',
33
+ 'ins',
34
+ 'sup',
35
+ 'sub',
36
+ 'abbr',
37
+ ],
38
+ allowedAttributes: {
39
+ ...sanitizeHtml.defaults.allowedAttributes,
40
+ a: ['href', 'name', 'id', 'target', 'rel'],
41
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
42
+ source: ['src', 'srcset', 'type', 'media', 'sizes'],
43
+ code: ['class'],
44
+ span: ['class'],
45
+ '*': ['id'],
46
+ },
47
+ allowedSchemes: ['http', 'https', 'mailto', 'tel'],
48
+ };
49
+ const escapeHtml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
50
+ /**
51
+ * Rewrite `](filename)` / `](<filename>)` targets to their staged asset
52
+ * paths. Exposed so a site rendering the raw body itself gets image links
53
+ * that resolve.
54
+ */
55
+ export function substituteEmbeds(text, embeds) {
56
+ let out = text;
57
+ for (const [filename, assetPath] of embeds) {
58
+ const literal = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
59
+ out = out
60
+ .replace(new RegExp(`\\]\\(<${literal}>\\)`, 'g'), `](<${assetPath}>)`)
61
+ .replace(new RegExp(`\\]\\(${literal}(\\s|\\))`, 'g'), `](${assetPath}$1`);
62
+ }
63
+ return out;
64
+ }
65
+ export function renderMarkdown(text) {
66
+ return sanitizeHtml(md.render(text), SANITIZE);
67
+ }
68
+ export function renderPlain(text) {
69
+ return text
70
+ .split(/\n{2,}/)
71
+ .map((para) => `<p>${escapeHtml(para).replace(/\n/g, '<br>\n')}</p>`)
72
+ .join('\n');
73
+ }
74
+ export function renderBody(text, format, embeds) {
75
+ const isMarkdown = format === undefined || format === 'markdown';
76
+ if (!isMarkdown)
77
+ return renderPlain(text);
78
+ const source = embeds && embeds.size > 0 ? substituteEmbeds(text, embeds) : text;
79
+ return renderMarkdown(source);
80
+ }
81
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @haverstack/eleventy — the `publish` command
3
+ * -------------------------------------------------------
4
+ * Stamps `article.url` / `post.url` with the canonical location at first
5
+ * publish, so any app can render a "view published" link. This is a
6
+ * write, and it does not belong in a build: a build that mutates what it
7
+ * read cannot run twice safely, against production, or from CI without
8
+ * write credentials. So it is a separate command, run deliberately after
9
+ * a successful build.
10
+ *
11
+ * See docs/design.md § publish.
12
+ */
13
+ import type { Stack } from '@haverstack/core';
14
+ import { type SlugStrategy } from './resolve.js';
15
+ export interface PublishOptions {
16
+ site?: string;
17
+ slugStrategy?: SlugStrategy;
18
+ /** Report what would be written without writing. */
19
+ dryRun?: boolean;
20
+ }
21
+ export interface PublishReport {
22
+ site: string;
23
+ baseUrl: string;
24
+ dryRun: boolean;
25
+ /** `url` was absent and this site stamped it. */
26
+ stamped: {
27
+ recordId: string;
28
+ url: string;
29
+ }[];
30
+ /** `url` already points into this site — nothing to do. */
31
+ alreadyHome: {
32
+ recordId: string;
33
+ storedUrl: string;
34
+ }[];
35
+ /** `url` points at another site — this site renders rel=canonical, does not restamp. */
36
+ canonicalElsewhere: {
37
+ recordId: string;
38
+ storedUrl: string;
39
+ }[];
40
+ /** Members whose type carries no canonical `url` field (photos, bookmarks). */
41
+ skipped: number;
42
+ }
43
+ export declare function runPublish(stack: Stack, opts?: PublishOptions): Promise<PublishReport>;
44
+ export declare function formatPublishReport(report: PublishReport): string;
45
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAQ1D,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,2DAA2D;IAC3D,WAAW,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,wFAAwF;IACxF,kBAAkB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6ChG;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAsBjE"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @haverstack/eleventy — the `publish` command
3
+ * -------------------------------------------------------
4
+ * Stamps `article.url` / `post.url` with the canonical location at first
5
+ * publish, so any app can render a "view published" link. This is a
6
+ * write, and it does not belong in a build: a build that mutates what it
7
+ * read cannot run twice safely, against production, or from CI without
8
+ * write credentials. So it is a separate command, run deliberately after
9
+ * a successful build.
10
+ *
11
+ * See docs/design.md § publish.
12
+ */
13
+ import { ARTICLE, POST } from '@haverstack/commons';
14
+ import { load } from './load.js';
15
+ import { resolve } from './resolve.js';
16
+ import { HaverstackEleventyError } from './errors.js';
17
+ const baseId = (typeId) => typeId.split('@')[0];
18
+ /** Types whose `url` field is the canonical published location (not, say, a bookmark's target). */
19
+ const CANONICAL_URL_TYPES = new Set([baseId(ARTICLE.id), baseId(POST.id)]);
20
+ export async function runPublish(stack, opts = {}) {
21
+ const index = await load(stack, { site: opts.site });
22
+ // A clean resolve is a precondition — publish follows a successful build.
23
+ const resolved = resolve(index, { slugStrategy: opts.slugStrategy ?? 'title', strict: true });
24
+ if (!resolved.site) {
25
+ throw new HaverstackEleventyError('publish needs a site@1 record: canonical URLs are built from its baseUrl.');
26
+ }
27
+ const baseUrl = String(resolved.site.content.baseUrl ?? '').replace(/\/+$/, '');
28
+ if (!baseUrl) {
29
+ throw new HaverstackEleventyError(`Site "${resolved.site.content.handle}" has no baseUrl.`);
30
+ }
31
+ const report = {
32
+ site: resolved.site.content.handle,
33
+ baseUrl,
34
+ dryRun: Boolean(opts.dryRun),
35
+ stamped: [],
36
+ alreadyHome: [],
37
+ canonicalElsewhere: [],
38
+ skipped: 0,
39
+ };
40
+ for (const member of resolved.members) {
41
+ if (!CANONICAL_URL_TYPES.has(baseId(member.record.typeId))) {
42
+ report.skipped++;
43
+ continue;
44
+ }
45
+ const stored = member.record.content.url;
46
+ if (typeof stored === 'string' && stored) {
47
+ if (stored.startsWith(baseUrl))
48
+ report.alreadyHome.push({ recordId: member.record.id, storedUrl: stored });
49
+ else
50
+ report.canonicalElsewhere.push({ recordId: member.record.id, storedUrl: stored });
51
+ continue;
52
+ }
53
+ const url = `${baseUrl}${member.url}`;
54
+ if (!opts.dryRun)
55
+ await stack.update(member.record.id, { url });
56
+ report.stamped.push({ recordId: member.record.id, url });
57
+ }
58
+ return report;
59
+ }
60
+ export function formatPublishReport(report) {
61
+ const lines = [];
62
+ lines.push(`${report.dryRun ? 'Would publish' : 'Published'} canonical URLs for "${report.site}" ` +
63
+ `(${report.baseUrl}).\n`);
64
+ for (const { recordId, url } of report.stamped) {
65
+ lines.push(` ${report.dryRun ? 'would stamp' : 'stamped'} ${recordId} → ${url}`);
66
+ }
67
+ for (const { recordId } of report.alreadyHome) {
68
+ lines.push(` already home ${recordId}`);
69
+ }
70
+ for (const { recordId, storedUrl } of report.canonicalElsewhere) {
71
+ lines.push(` elsewhere ${recordId} (canonical at ${storedUrl})`);
72
+ }
73
+ lines.push(`\n${report.stamped.length} ${report.dryRun ? 'to stamp' : 'stamped'}, ` +
74
+ `${report.alreadyHome.length} already canonical here, ` +
75
+ `${report.canonicalElsewhere.length} canonical elsewhere, ` +
76
+ `${report.skipped} without a canonical url field.`);
77
+ return lines.join('\n');
78
+ }
79
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../src/publish.ts"],"names":[],"mappings":"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"}