@staticforge/build 0.0.0-stage → 0.1.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/build.d.ts +75 -0
  5. package/dist/build.d.ts.map +1 -0
  6. package/dist/build.js +692 -0
  7. package/dist/build.js.map +1 -0
  8. package/dist/cache.d.ts +64 -0
  9. package/dist/cache.d.ts.map +1 -0
  10. package/dist/cache.js +97 -0
  11. package/dist/cache.js.map +1 -0
  12. package/dist/code-assets.d.ts +36 -0
  13. package/dist/code-assets.d.ts.map +1 -0
  14. package/dist/code-assets.js +61 -0
  15. package/dist/code-assets.js.map +1 -0
  16. package/dist/data.d.ts +8 -0
  17. package/dist/data.d.ts.map +1 -0
  18. package/dist/data.js +43 -0
  19. package/dist/data.js.map +1 -0
  20. package/dist/feeds.d.ts +33 -0
  21. package/dist/feeds.d.ts.map +1 -0
  22. package/dist/feeds.js +120 -0
  23. package/dist/feeds.js.map +1 -0
  24. package/dist/html.d.ts +14 -0
  25. package/dist/html.d.ts.map +1 -0
  26. package/dist/html.js +31 -0
  27. package/dist/html.js.map +1 -0
  28. package/dist/images.d.ts +22 -0
  29. package/dist/images.d.ts.map +1 -0
  30. package/dist/images.js +95 -0
  31. package/dist/images.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +8 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/nav.d.ts +15 -0
  37. package/dist/nav.d.ts.map +1 -0
  38. package/dist/nav.js +19 -0
  39. package/dist/nav.js.map +1 -0
  40. package/dist/project.d.ts +31 -0
  41. package/dist/project.d.ts.map +1 -0
  42. package/dist/project.js +74 -0
  43. package/dist/project.js.map +1 -0
  44. package/dist/routes.d.ts +42 -0
  45. package/dist/routes.d.ts.map +1 -0
  46. package/dist/routes.js +106 -0
  47. package/dist/routes.js.map +1 -0
  48. package/dist/scaffold.d.ts +46 -0
  49. package/dist/scaffold.d.ts.map +1 -0
  50. package/dist/scaffold.js +163 -0
  51. package/dist/scaffold.js.map +1 -0
  52. package/dist/site.d.ts +47 -0
  53. package/dist/site.d.ts.map +1 -0
  54. package/dist/site.js +107 -0
  55. package/dist/site.js.map +1 -0
  56. package/dist/tasks.d.ts +86 -0
  57. package/dist/tasks.d.ts.map +1 -0
  58. package/dist/tasks.js +103 -0
  59. package/dist/tasks.js.map +1 -0
  60. package/package.json +48 -3
@@ -0,0 +1,163 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { basename, dirname, join } from 'node:path';
3
+ import { stringify as toYaml } from 'yaml';
4
+ import { CONFIG_FILE_NAMES, ErrorCode, SforgeError, validateConfig, } from '@staticforge/core';
5
+ import { ProjectDirs } from './project.js';
6
+ import { routeFor } from './routes.js';
7
+ export const SCHEMA_PATH = './node_modules/staticforge/schema/staticforge.schema.json';
8
+ const humanize = (name) => {
9
+ const words = name.replace(/[-_]+/g, ' ').trim();
10
+ return words === '' ? 'My site' : words.charAt(0).toUpperCase() + words.slice(1);
11
+ };
12
+ const slugify = (name) => name
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-+|-+$/g, '') || 'site';
16
+ /** Render a config object as a TypeScript literal with unquoted keys and single quotes. */
17
+ function toTsLiteral(value, indent = '') {
18
+ const next = `${indent} `;
19
+ if (Array.isArray(value)) {
20
+ if (value.length === 0)
21
+ return '[]';
22
+ return `[${value.map((v) => toTsLiteral(v, next)).join(', ')}]`;
23
+ }
24
+ if (value !== null && typeof value === 'object') {
25
+ const entries = Object.entries(value);
26
+ if (entries.length === 0)
27
+ return '{}';
28
+ const lines = entries.map(([k, v]) => {
29
+ const key = /^[A-Za-z_$][\w$]*$/.test(k) ? k : `'${k}'`;
30
+ return `${next}${key}: ${toTsLiteral(v, next)},`;
31
+ });
32
+ return `{\n${lines.join('\n')}\n${indent}}`;
33
+ }
34
+ if (typeof value === 'string')
35
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
36
+ return String(value);
37
+ }
38
+ function configSource(config, format) {
39
+ switch (format) {
40
+ case 'ts':
41
+ return `import { defineConfig } from 'staticforge';\n\nexport default defineConfig(${toTsLiteral(config)});\n`;
42
+ case 'json':
43
+ return `${JSON.stringify({ $schema: SCHEMA_PATH, ...config }, null, 2)}\n`;
44
+ case 'yaml':
45
+ return `# yaml-language-server: $schema=${SCHEMA_PATH}\n${toYaml(config)}`;
46
+ }
47
+ }
48
+ function writeFiles(rootDir, files, force) {
49
+ const result = { rootDir, created: [], skipped: [] };
50
+ for (const [path, contents] of Object.entries(files)) {
51
+ const full = join(rootDir, path);
52
+ if (existsSync(full) && !force) {
53
+ result.skipped.push(path);
54
+ continue;
55
+ }
56
+ mkdirSync(dirname(full), { recursive: true });
57
+ writeFileSync(full, contents);
58
+ result.created.push(path);
59
+ }
60
+ return result;
61
+ }
62
+ /** Create a new project: config, starter content, a site stylesheet and .gitignore. */
63
+ export function initProject(options) {
64
+ const { dir, format = 'ts', force = false } = options;
65
+ const existing = CONFIG_FILE_NAMES.find((name) => existsSync(join(dir, name)));
66
+ if (existing !== undefined && !force) {
67
+ throw new SforgeError(ErrorCode.FileExists, `${join(dir, existing)} already exists`, {
68
+ hint: 'This directory is already a staticforge project. Pass --force to overwrite the starter files.',
69
+ });
70
+ }
71
+ const name = basename(dir);
72
+ const title = options.title ?? humanize(name);
73
+ const config = {
74
+ site: {
75
+ title,
76
+ baseUrl: options.baseUrl ?? 'https://example.com',
77
+ language: options.language ?? 'en',
78
+ },
79
+ styles: ['/assets/css/site.css'],
80
+ };
81
+ if (options.provider === 'aws') {
82
+ config.targets = { staging: { provider: 'aws', region: options.region ?? 'us-east-1' } };
83
+ }
84
+ else if (options.provider === 'azure') {
85
+ config.targets = {
86
+ staging: {
87
+ provider: 'azure',
88
+ resourceGroup: `rg-${slugify(name)}`,
89
+ location: options.region ?? 'westeurope',
90
+ },
91
+ };
92
+ }
93
+ validateConfig(config, 'generated config'); // guard against scaffolding drifting from the schema
94
+ const configName = `staticforge.config.${format}`;
95
+ const files = {
96
+ [configName]: configSource(config, format),
97
+ [`${ProjectDirs.content}/index.md`]: `---
98
+ ${toYaml({ title, description: `Welcome to ${title}.` })}---
99
+
100
+ This site is built with staticforge.
101
+
102
+ ## Next steps
103
+
104
+ - Edit this page in \`content/index.md\`.
105
+ - Add a page with \`sforge new page <name>\`, or a blog post with \`sforge new post <name>\`.
106
+ - Build the site into \`dist/\` with \`sforge build\`.
107
+ `,
108
+ [`${ProjectDirs.content}/about.md`]: `---
109
+ title: About
110
+ description: What this site is about.
111
+ nav:
112
+ order: 1
113
+ ---
114
+
115
+ Tell visitors who you are and what they will find here.
116
+ `,
117
+ [`${ProjectDirs.assets}/css/site.css`]: `/*
118
+ * Site styles, loaded after the theme. Override theme variables here, e.g.:
119
+ *
120
+ * :root { --color-link: #0b6e4f; }
121
+ */
122
+ `,
123
+ '.gitignore': 'node_modules/\ndist/\n.sforge/cache/\n.sforge/state/\n.sforge/iac/**/.terraform/\n',
124
+ };
125
+ const result = writeFiles(dir, files, force);
126
+ return { ...result, configPath: join(dir, configName) };
127
+ }
128
+ /** Create a page or post with front-matter; returns its file and future URL. */
129
+ export function newContent(options) {
130
+ const { kind, format = 'md' } = options;
131
+ let path = options.path.replace(/^\.?\/?(content\/)?/, '').replace(/\.(md|markdown|html?)$/i, '');
132
+ if (kind === 'post' && !path.includes('/'))
133
+ path = `blog/${path}`;
134
+ const segments = path.split('/');
135
+ if (path === '' || segments.some((s) => !/^[a-z0-9][a-z0-9_-]*$/i.test(s))) {
136
+ throw new SforgeError(ErrorCode.Usage, `Invalid content path "${options.path}"`, {
137
+ hint: 'Use letters, digits, "-" and "_" in each segment, e.g. "guides/getting-started".',
138
+ });
139
+ }
140
+ const contentPath = `${path}.${format}`;
141
+ const file = `${ProjectDirs.content}/${contentPath}`;
142
+ const full = join(options.rootDir, file);
143
+ if (existsSync(full) && !options.force) {
144
+ throw new SforgeError(ErrorCode.FileExists, `${file} already exists`, {
145
+ hint: 'Pass --force to overwrite it.',
146
+ });
147
+ }
148
+ const frontMatter = {
149
+ title: options.title ?? humanize(segments.at(-1) ?? path),
150
+ };
151
+ if (kind === 'post') {
152
+ frontMatter.layout = 'post';
153
+ frontMatter.date = (options.date ?? new Date()).toISOString().slice(0, 10);
154
+ frontMatter.tags = [];
155
+ }
156
+ if (options.draft)
157
+ frontMatter.draft = true;
158
+ const body = format === 'md' ? 'Write your content here.\n' : '<p>Write your content here.</p>\n';
159
+ mkdirSync(dirname(full), { recursive: true });
160
+ writeFileSync(full, `---\n${toYaml(frontMatter)}---\n\n${body}`);
161
+ return { file, url: routeFor(contentPath, undefined, options.urls).url };
162
+ }
163
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
package/dist/site.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { PageMeta } from '@staticforge/content';
2
+ import type { SforgeConfig } from '@staticforge/core';
3
+ import type { Breadcrumb, PageSummary, TagRef } from '@staticforge/render';
4
+ import { type Route, type UrlOptions } from './routes.js';
5
+ /** A built page with what site features need to know about it. */
6
+ export interface SitePage {
7
+ source: string;
8
+ url: string;
9
+ title: string;
10
+ meta: PageMeta;
11
+ }
12
+ export interface Collection {
13
+ name: string;
14
+ /** Content-relative directory, e.g. "blog". */
15
+ dir: string;
16
+ /** Source of the collection's index page (`blog/_index.md`), if it has one. */
17
+ index: string | undefined;
18
+ /** Members, sorted. */
19
+ pages: SitePage[];
20
+ perPage: number;
21
+ feed: {
22
+ rss: boolean;
23
+ atom: boolean;
24
+ };
25
+ }
26
+ /** "Static Sites" → "static-sites"; "C++" → "c"; non-Latin letters are kept. */
27
+ export declare function slugify(text: string): string;
28
+ /**
29
+ * Group pages into the configured collections. A collection's members are the
30
+ * pages under its directory, except section index pages.
31
+ */
32
+ export declare function collectionsOf(config: SforgeConfig['collections'], pages: SitePage[]): {
33
+ collections: Collection[];
34
+ problems: string[];
35
+ };
36
+ /** Route of a collection's list page `n` (1-based): /blog/, /blog/page/2/, … */
37
+ export declare function listRoute(dir: string, n: number, urls: UrlOptions): Route;
38
+ /** Route of a tag's page, /tags/<slug>/, or the tag index, /tags/. */
39
+ export declare function tagRoute(slug: string | undefined, urls: UrlOptions): Route;
40
+ export declare function summarize(page: SitePage, baseUrl: string, tagUrl: (slug: string) => string | undefined): PageSummary;
41
+ export declare function tagsOf(names: string[], tagUrl: (slug: string) => string | undefined): TagRef[];
42
+ /**
43
+ * Home, then each ancestor URL that is a page, then the page itself:
44
+ * /guides/setup/ → Home › Guides › Setup.
45
+ */
46
+ export declare function breadcrumbsFor(url: string, titles: ReadonlyMap<string, string>): Breadcrumb[];
47
+ //# sourceMappingURL=site.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAY,MAAM,aAAa,CAAC;AAEpE,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,uBAAuB;IACvB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CACvC;AAID,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5C;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,CAAC,aAAa,CAAC,EACnC,KAAK,EAAE,QAAQ,EAAE,GAChB;IAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA2CnD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK,CAGzE;AAED,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK,CAE1E;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC3C,WAAW,CAUb;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAK9F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,UAAU,EAAE,CAkB7F"}
package/dist/site.js ADDED
@@ -0,0 +1,107 @@
1
+ import { posix } from 'node:path';
2
+ import { ProjectDirs } from './project.js';
3
+ import { routeFor } from './routes.js';
4
+ const INDEX = /(^|\/)_?index\.[^/]+$/;
5
+ /** "Static Sites" → "static-sites"; "C++" → "c"; non-Latin letters are kept. */
6
+ export function slugify(text) {
7
+ return (text
8
+ .normalize('NFKD')
9
+ .replace(/[̀-ͯ]/g, '')
10
+ .toLowerCase()
11
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
12
+ .replace(/^-+|-+$/g, '') || 'tag');
13
+ }
14
+ const time = (date) => date?.getTime() ?? -Infinity;
15
+ /**
16
+ * Group pages into the configured collections. A collection's members are the
17
+ * pages under its directory, except section index pages.
18
+ */
19
+ export function collectionsOf(config, pages) {
20
+ const collections = [];
21
+ const problems = [];
22
+ for (const [name, options] of Object.entries(config)) {
23
+ const dir = posix.normalize(options.dir).replace(/\/+$/, '');
24
+ const prefix = `${ProjectDirs.content}/`;
25
+ if (!dir.startsWith(prefix) && dir !== ProjectDirs.content) {
26
+ problems.push(`Collection "${name}": dir "${options.dir}" must be inside ${ProjectDirs.content}/`);
27
+ continue;
28
+ }
29
+ const contentDir = dir === ProjectDirs.content ? '' : dir.slice(prefix.length);
30
+ const within = (source) => source.startsWith(`${dir}/`);
31
+ const members = pages.filter((p) => within(p.source) && !INDEX.test(p.source));
32
+ const index = pages.find((p) => posix.dirname(p.source) === dir && INDEX.test(p.source))?.source;
33
+ const sorted = [...members].sort((a, b) => {
34
+ switch (options.sortBy) {
35
+ case 'title':
36
+ return a.title.localeCompare(b.title);
37
+ case 'order':
38
+ return ((a.meta.order ?? Infinity) - (b.meta.order ?? Infinity) ||
39
+ a.title.localeCompare(b.title));
40
+ default:
41
+ // Newest first; undated pages last, by title.
42
+ return time(b.meta.date) - time(a.meta.date) || a.title.localeCompare(b.title);
43
+ }
44
+ });
45
+ collections.push({
46
+ name,
47
+ dir: contentDir,
48
+ index,
49
+ pages: sorted,
50
+ perPage: options.perPage,
51
+ feed: options.feed,
52
+ });
53
+ }
54
+ return { collections, problems };
55
+ }
56
+ /** Route of a collection's list page `n` (1-based): /blog/, /blog/page/2/, … */
57
+ export function listRoute(dir, n, urls) {
58
+ const base = dir === '' ? '' : `${dir}/`;
59
+ return routeFor(n === 1 ? `${base}index.md` : `${base}page/${n}.md`, undefined, urls);
60
+ }
61
+ /** Route of a tag's page, /tags/<slug>/, or the tag index, /tags/. */
62
+ export function tagRoute(slug, urls) {
63
+ return routeFor(slug === undefined ? 'tags/index.md' : `tags/${slug}.md`, undefined, urls);
64
+ }
65
+ export function summarize(page, baseUrl, tagUrl) {
66
+ return {
67
+ title: page.title,
68
+ url: page.url,
69
+ absoluteUrl: new URL(page.url, baseUrl).href,
70
+ description: page.meta.description,
71
+ date: page.meta.date,
72
+ updated: page.meta.updated,
73
+ tags: tagsOf(page.meta.tags, tagUrl),
74
+ };
75
+ }
76
+ export function tagsOf(names, tagUrl) {
77
+ return names.map((name) => {
78
+ const slug = slugify(name);
79
+ return { name, slug, url: tagUrl(slug) };
80
+ });
81
+ }
82
+ /**
83
+ * Home, then each ancestor URL that is a page, then the page itself:
84
+ * /guides/setup/ → Home › Guides › Setup.
85
+ */
86
+ export function breadcrumbsFor(url, titles) {
87
+ const crumbs = [];
88
+ const home = titles.get('/');
89
+ if (home !== undefined && url !== '/')
90
+ crumbs.push({ title: home, url: '/', current: false });
91
+ const segments = url
92
+ .replace(/^\/|\/$/g, '')
93
+ .split('/')
94
+ .filter((s) => s !== '');
95
+ for (let i = 1; i < segments.length; i++) {
96
+ const path = `/${segments.slice(0, i).join('/')}`;
97
+ const ancestor = titles.has(`${path}/`) ? `${path}/` : titles.has(path) ? path : undefined;
98
+ if (ancestor !== undefined) {
99
+ crumbs.push({ title: titles.get(ancestor) ?? '', url: ancestor, current: false });
100
+ }
101
+ }
102
+ const title = titles.get(url);
103
+ if (title !== undefined)
104
+ crumbs.push({ title, url, current: true });
105
+ return crumbs;
106
+ }
107
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type { Root } from 'hast';
2
+ import { type ParsedContent, type PreparedSource } from '@staticforge/content';
3
+ import type { Diagnostic } from '@staticforge/core';
4
+ import { type NavItem, type Pagination, type PageContext, type PageSummary, type RenderContext, type TagSummary } from '@staticforge/render';
5
+ /** Below this many pages, work stays in the main thread: starting workers costs more. */
6
+ export declare const MIN_PAGES_FOR_WORKERS = 100;
7
+ export declare const PARSE_TASK: {
8
+ module: string;
9
+ fn: string;
10
+ };
11
+ export declare const RENDER_TASK: {
12
+ module: string;
13
+ fn: string;
14
+ };
15
+ /** Whether a root-relative URL path names an output file or directory index. */
16
+ export declare function outputExists(outputs: {
17
+ has(path: string): boolean;
18
+ }, urlPath: string): boolean;
19
+ /** The final URL of an asset: its fingerprinted name, keeping a query or fragment. */
20
+ export declare function assetUrlFor(urls: ReadonlyMap<string, string>): (url: string) => string;
21
+ export interface ParseContext {
22
+ language: string;
23
+ /** Page URL by project-relative source path. */
24
+ pageUrls: Map<string, string>;
25
+ /** Every output path of the build. */
26
+ outputs: Set<string>;
27
+ /** Whether content images are optimized (their pages then need their trees). */
28
+ optimizeImages: boolean;
29
+ }
30
+ /**
31
+ * A parsed page. Pages the build still changes (documents, and fragments with
32
+ * images to optimize) keep their tree; the others carry their HTML, which is
33
+ * much cheaper to send between threads and safe to cache.
34
+ */
35
+ export type ParsedPage = Omit<ParsedContent, 'tree'> & ({
36
+ tree: Root;
37
+ html?: undefined;
38
+ } | {
39
+ tree?: undefined;
40
+ html: string;
41
+ });
42
+ export interface ParseResult {
43
+ parsed: ParsedPage;
44
+ /** Problems found while resolving references (reported before `parsed.diagnostics`). */
45
+ references: Diagnostic[];
46
+ }
47
+ /** Parse pages to HAST; references resolve against all pages and outputs of the build. */
48
+ export declare function parsePages(input: {
49
+ items: PreparedSource[];
50
+ context: ParseContext;
51
+ }): Promise<ParseResult[]>;
52
+ export interface RenderContextShared {
53
+ /** Distinguishes builds, so workers never reuse a renderer (and its templates) across them. */
54
+ buildId: string;
55
+ theme: string;
56
+ rootDir: string;
57
+ baseUrl: string;
58
+ /** Fingerprinted URL by asset URL. */
59
+ assetUrls: Map<string, string>;
60
+ minify: boolean;
61
+ base: Omit<RenderContext, 'page' | 'content' | 'nav' | 'breadcrumbs'>;
62
+ nav: Omit<NavItem, 'current' | 'active'>[];
63
+ /** Page title by URL, for breadcrumbs. */
64
+ titles: Map<string, string>;
65
+ }
66
+ export interface RenderItem {
67
+ layout: string;
68
+ url: string;
69
+ page: PageContext;
70
+ content: string;
71
+ pagination?: Pagination;
72
+ tag?: TagSummary & {
73
+ items: PageSummary[];
74
+ };
75
+ }
76
+ export type RenderResult = {
77
+ html: string;
78
+ } | {
79
+ error: string;
80
+ };
81
+ /** Render pages with their layouts and finish them (asset URLs, minification). */
82
+ export declare function renderPages(input: {
83
+ items: RenderItem[];
84
+ context: RenderContextShared;
85
+ }): RenderResult[];
86
+ //# sourceMappingURL=tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EAGpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,aAAa,EAElB,KAAK,UAAU,EAGhB,MAAM,qBAAqB,CAAC;AAW7B,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,UAAU;;;CAAgD,CAAC;AACxE,eAAO,MAAM,WAAW;;;CAAiD,CAAC;AAE1E,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAU9F;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAKtF;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,sCAAsC;IACtC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,GAClD,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAW1E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,wFAAwF;IACxF,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED,0FAA0F;AAC1F,wBAAsB,UAAU,CAAC,KAAK,EAAE;IACtC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,YAAY,CAAC;CACvB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAoBzB;AAED,MAAM,WAAW,mBAAmB;IAClC,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,CAAC,CAAC;IACtE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE,CAAC;IAC3C,0CAA0C;IAC1C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,UAAU,GAAG;QAAE,KAAK,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAIhE,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,mBAAmB,CAAC;CAC9B,GAAG,YAAY,EAAE,CAgCjB"}
package/dist/tasks.js ADDED
@@ -0,0 +1,103 @@
1
+ import { posix } from 'node:path';
2
+ import { toHtml } from 'hast-util-to-html';
3
+ import { visit } from 'unist-util-visit';
4
+ import { createReport, parseContent, } from '@staticforge/content';
5
+ import { createRenderer, loadTheme, } from '@staticforge/render';
6
+ import { finishHtml } from './html.js';
7
+ import { navFor } from './nav.js';
8
+ import { createRefResolver } from './routes.js';
9
+ import { breadcrumbsFor } from './site.js';
10
+ /*
11
+ * The per-page work of a build, as functions of plain data, so it can run in
12
+ * worker threads (see `WorkerPool` in @staticforge/core) or in this thread.
13
+ */
14
+ /** Below this many pages, work stays in the main thread: starting workers costs more. */
15
+ export const MIN_PAGES_FOR_WORKERS = 100;
16
+ export const PARSE_TASK = { module: import.meta.url, fn: 'parsePages' };
17
+ export const RENDER_TASK = { module: import.meta.url, fn: 'renderPages' };
18
+ /** Whether a root-relative URL path names an output file or directory index. */
19
+ export function outputExists(outputs, urlPath) {
20
+ let path;
21
+ try {
22
+ path = decodeURI(urlPath.split(/[?#]/)[0] ?? '').replace(/^\//, '');
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ return (outputs.has(path) || outputs.has(posix.join(path, 'index.html')) || outputs.has(`${path}.html`));
28
+ }
29
+ /** The final URL of an asset: its fingerprinted name, keeping a query or fragment. */
30
+ export function assetUrlFor(urls) {
31
+ return (url) => {
32
+ const [, path = url, rest = ''] = /^([^?#]*)(.*)$/s.exec(url) ?? [];
33
+ return (urls.get(path) ?? path) + rest;
34
+ };
35
+ }
36
+ function hasImage(tree) {
37
+ let found = false;
38
+ visit(tree, 'element', (node) => {
39
+ if (node.tagName === 'img')
40
+ found = true;
41
+ return found ? false : undefined;
42
+ });
43
+ return found;
44
+ }
45
+ /** Parse pages to HAST; references resolve against all pages and outputs of the build. */
46
+ export async function parsePages(input) {
47
+ const { language, pageUrls, outputs, optimizeImages } = input.context;
48
+ const fileExists = (urlPath) => outputExists(outputs, urlPath);
49
+ const results = [];
50
+ for (const prepared of input.items) {
51
+ const references = [];
52
+ const report = createReport(prepared.path, references);
53
+ const parsed = await parseContent(prepared, {
54
+ language,
55
+ resolveRef: createRefResolver({ sourcePath: prepared.path, pageUrls, report }),
56
+ fileExists,
57
+ });
58
+ const { tree, ...rest } = parsed;
59
+ const keepTree = parsed.kind === 'document' || (optimizeImages && hasImage(tree));
60
+ results.push({
61
+ parsed: keepTree ? { ...rest, tree } : { ...rest, html: toHtml(tree) },
62
+ references,
63
+ });
64
+ }
65
+ return results;
66
+ }
67
+ let cached;
68
+ /** Render pages with their layouts and finish them (asset URLs, minification). */
69
+ export function renderPages(input) {
70
+ const { context } = input;
71
+ const assetUrl = assetUrlFor(context.assetUrls);
72
+ if (cached?.buildId !== context.buildId) {
73
+ const theme = loadTheme(context.theme, context.rootDir);
74
+ cached = {
75
+ buildId: context.buildId,
76
+ renderer: createRenderer(theme, {
77
+ rootDir: context.rootDir,
78
+ baseUrl: context.baseUrl,
79
+ assetUrl,
80
+ }),
81
+ };
82
+ }
83
+ const { renderer } = cached;
84
+ return input.items.map((item) => {
85
+ let html;
86
+ try {
87
+ html = renderer.render(item.layout, {
88
+ ...context.base,
89
+ page: item.page,
90
+ content: item.content,
91
+ nav: navFor(context.nav, item.url),
92
+ breadcrumbs: breadcrumbsFor(item.url, context.titles),
93
+ ...(item.pagination && { pagination: item.pagination }),
94
+ ...(item.tag && { tag: item.tag }),
95
+ });
96
+ }
97
+ catch (error) {
98
+ return { error: error instanceof Error ? error.message : String(error) };
99
+ }
100
+ return { html: finishHtml(html, { assetUrl, minify: context.minify }) };
101
+ });
102
+ }
103
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@staticforge/build",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Site build pipeline: discovery, URL mapping, rendering and output",
5
+ "keywords": [
6
+ "staticforge"
7
+ ],
8
+ "homepage": "https://github.com/elchanio-76/sforge#readme",
9
+ "bugs": {
10
+ "url": "https://github.com/elchanio-76/sforge/issues"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/elchanio-76/sforge.git",
15
+ "directory": "packages/build"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Eleftherios Chaniotakis",
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "@staticforge/source": "./src/index.ts",
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "engines": {
31
+ "node": "^22.22.0 || >=24.8.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@staticforge/assets": "0.1.0",
38
+ "@staticforge/content": "0.1.0",
39
+ "@staticforge/core": "0.1.0",
40
+ "@staticforge/render": "0.1.0",
41
+ "hast-util-from-html": "^2.0.3",
42
+ "hast-util-minify-whitespace": "^1.0.1",
43
+ "hast-util-to-html": "^9.0.5",
44
+ "hastscript": "^9.0.1",
45
+ "unist-util-visit": "^5.1.0",
46
+ "yaml": "^2.9.1"
47
+ },
48
+ "devDependencies": {
49
+ "@types/hast": "^3.0.5"
50
+ }
6
51
  }