@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
package/dist/html.js ADDED
@@ -0,0 +1,31 @@
1
+ import { fromHtml } from 'hast-util-from-html';
2
+ import { minifyWhitespace } from 'hast-util-minify-whitespace';
3
+ import { toHtml } from 'hast-util-to-html';
4
+ import { visit } from 'unist-util-visit';
5
+ const URL_PROPERTIES = ['href', 'src'];
6
+ /** Rewrite asset references and minify a page's tree in place. */
7
+ export function finishTree(tree, options) {
8
+ visit(tree, 'element', (node) => {
9
+ for (const name of URL_PROPERTIES) {
10
+ const value = node.properties[name];
11
+ if (typeof value === 'string' && value.startsWith('/') && !value.startsWith('//')) {
12
+ node.properties[name] = options.assetUrl(value);
13
+ }
14
+ }
15
+ });
16
+ if (options.minify)
17
+ minifyWhitespace(tree);
18
+ }
19
+ /** The same, for an HTML fragment such as a page body in a feed. */
20
+ export function finishFragment(html, options) {
21
+ const tree = fromHtml(html, { fragment: true });
22
+ finishTree(tree, options);
23
+ return toHtml(tree);
24
+ }
25
+ /** Rewrite asset references in, and minify, a rendered page. */
26
+ export function finishHtml(html, options) {
27
+ const tree = fromHtml(html);
28
+ finishTree(tree, options);
29
+ return toHtml(tree);
30
+ }
31
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AASzC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,KAAK,CAAU,CAAC;AAEhD,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,IAAU,EAAE,OAAsB;IAC3D,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE;QACvC,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClF,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,MAAM;QAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAsB;IACjE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAsB;IAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Element, Root } from 'hast';
2
+ import { type ImageOptions, type OptimizedImage } from '@staticforge/assets';
3
+ /** Where image variants are written in the build. */
4
+ export declare const IMAGE_DIR = "_assets/img";
5
+ export interface ImageContext extends ImageOptions {
6
+ sizes: string;
7
+ /** Source file of a root-relative URL in the built site, if it is copied from the project. */
8
+ sourceOf: (urlPath: string) => string | undefined;
9
+ /** Called for each variant to write. */
10
+ emit: (output: string, file: string) => void;
11
+ /** Called when an image cannot be processed. */
12
+ fail: (node: Element, message: string) => void;
13
+ }
14
+ /** Images being processed, by source file, so each is encoded once per build. */
15
+ export type ImageCache = Map<string, Promise<OptimizedImage>>;
16
+ /**
17
+ * Replace content images with responsive `<picture>` elements: modern formats
18
+ * at several widths, the original format as fallback, and width/height so
19
+ * the layout does not shift while images load.
20
+ */
21
+ export declare function optimizeImages(tree: Root, context: ImageContext, cache: ImageCache): Promise<void>;
22
+ //# sourceMappingURL=images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../src/images.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,cAAc,EAGpB,MAAM,qBAAqB,CAAC;AAE7B,qDAAqD;AACrD,eAAO,MAAM,SAAS,gBAAgB,CAAC;AAEvC,MAAM,WAAW,YAAa,SAAQ,YAAY;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,wCAAwC;IACxC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,gDAAgD;IAChD,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;AAO9D;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAkFf"}
package/dist/images.js ADDED
@@ -0,0 +1,95 @@
1
+ import { posix } from 'node:path';
2
+ import { visit } from 'unist-util-visit';
3
+ import { OPTIMIZABLE, optimizeImage, srcsetOf, } from '@staticforge/assets';
4
+ /** Where image variants are written in the build. */
5
+ export const IMAGE_DIR = '_assets/img';
6
+ const stringProperty = (node, name) => {
7
+ const value = node.properties[name];
8
+ return typeof value === 'string' ? value : undefined;
9
+ };
10
+ /**
11
+ * Replace content images with responsive `<picture>` elements: modern formats
12
+ * at several widths, the original format as fallback, and width/height so
13
+ * the layout does not shift while images load.
14
+ */
15
+ export async function optimizeImages(tree, context, cache) {
16
+ const jobs = [];
17
+ visit(tree, 'element', (node, index, parent) => {
18
+ if (node.tagName !== 'img' || parent === undefined || index === undefined)
19
+ return;
20
+ // An author-built <picture> or srcset is left alone.
21
+ if ((parent.type === 'element' && parent.tagName === 'picture') ||
22
+ node.properties.srcSet !== undefined)
23
+ return;
24
+ const src = stringProperty(node, 'src');
25
+ if (src === undefined || !src.startsWith('/') || src.startsWith('//'))
26
+ return;
27
+ const path = src.split(/[?#]/)[0] ?? '';
28
+ if (!OPTIMIZABLE.test(path))
29
+ return;
30
+ const source = context.sourceOf(path);
31
+ if (source !== undefined)
32
+ jobs.push({ node, parent, index, source });
33
+ });
34
+ await Promise.all(jobs.map(async ({ node, parent, index, source }) => {
35
+ let image = cache.get(source);
36
+ if (image === undefined) {
37
+ const stem = posix
38
+ .basename(source)
39
+ .replace(/\.[^.]+$/, '')
40
+ .replace(/[^\w-]+/g, '-');
41
+ image = optimizeImage(source, `${IMAGE_DIR}/${stem}`, context);
42
+ cache.set(source, image);
43
+ }
44
+ let result;
45
+ try {
46
+ result = await image;
47
+ }
48
+ catch (error) {
49
+ context.fail(node, (error instanceof Error ? error.message : String(error)).split('\n')[0] ?? '');
50
+ return;
51
+ }
52
+ for (const variant of [
53
+ ...result.sources.flatMap((s) => s.variants),
54
+ ...result.fallback.variants,
55
+ ]) {
56
+ context.emit(variant.output, variant.file);
57
+ }
58
+ const urlOf = (output) => `/${output}`;
59
+ const largest = result.fallback.variants.at(-1);
60
+ if (largest === undefined)
61
+ return;
62
+ const img = {
63
+ ...node,
64
+ properties: {
65
+ ...node.properties,
66
+ src: urlOf(largest.output),
67
+ srcSet: srcsetOf(result.fallback.variants, urlOf),
68
+ sizes: context.sizes,
69
+ // Keep the author's size; otherwise reserve the aspect ratio.
70
+ ...(node.properties.width === undefined &&
71
+ node.properties.height === undefined && {
72
+ width: largest.width,
73
+ height: Math.round((result.height * largest.width) / result.width),
74
+ }),
75
+ },
76
+ };
77
+ const picture = {
78
+ type: 'element',
79
+ tagName: 'picture',
80
+ properties: {},
81
+ children: [
82
+ ...result.sources.map((s) => ({
83
+ type: 'element',
84
+ tagName: 'source',
85
+ properties: { type: s.type, srcSet: srcsetOf(s.variants, urlOf), sizes: context.sizes },
86
+ children: [],
87
+ })),
88
+ img,
89
+ ],
90
+ ...(node.position && { position: node.position }),
91
+ };
92
+ parent.children[index] = picture;
93
+ }));
94
+ }
95
+ //# sourceMappingURL=images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"images.js","sourceRoot":"","sources":["../src/images.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export * from './build.js';
2
+ export { BuildCache, Memo, hashOf, memoized, stableJson } from './cache.js';
3
+ export * from './feeds.js';
4
+ export * from './project.js';
5
+ export * from './routes.js';
6
+ export * from './scaffold.js';
7
+ export * from './site.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5E,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './build.js';
2
+ export { BuildCache, Memo, hashOf, memoized, stableJson } from './cache.js';
3
+ export * from './feeds.js';
4
+ export * from './project.js';
5
+ export * from './routes.js';
6
+ export * from './scaffold.js';
7
+ export * from './site.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5E,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC"}
package/dist/nav.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { NavItem } from '@staticforge/render';
2
+ export interface NavCandidate {
3
+ url: string;
4
+ title: string;
5
+ nav: {
6
+ order?: number | undefined;
7
+ label?: string | undefined;
8
+ hide: boolean;
9
+ } | undefined;
10
+ }
11
+ /** Top-level pages, plus deeper pages that opt in with `nav.order`. Excludes home and 404. */
12
+ export declare function navEntries(pages: NavCandidate[]): Omit<NavItem, 'current' | 'active'>[];
13
+ /** Mark the current page, and the section containing it. */
14
+ export declare function navFor(entries: Omit<NavItem, 'current' | 'active'>[], pageUrl: string): NavItem[];
15
+ //# sourceMappingURL=nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../src/nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CAC5F;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE,CAQvF;AAED,4DAA4D;AAC5D,wBAAgB,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjG"}
package/dist/nav.js ADDED
@@ -0,0 +1,19 @@
1
+ /** Top-level pages, plus deeper pages that opt in with `nav.order`. Excludes home and 404. */
2
+ export function navEntries(pages) {
3
+ const topLevel = (url) => /^\/[^/]+(\/|\.html)?$/.test(url);
4
+ return pages
5
+ .filter((p) => p.url !== '/' && p.url !== '/404.html' && p.nav?.hide !== true)
6
+ .filter((p) => topLevel(p.url) || p.nav?.order !== undefined)
7
+ .map((p) => ({ title: p.nav?.label ?? p.title, url: p.url, order: p.nav?.order ?? Infinity }))
8
+ .sort((a, b) => a.order - b.order || a.title.localeCompare(b.title))
9
+ .map(({ title, url }) => ({ title, url }));
10
+ }
11
+ /** Mark the current page, and the section containing it. */
12
+ export function navFor(entries, pageUrl) {
13
+ return entries.map((entry) => {
14
+ const current = entry.url === pageUrl;
15
+ const section = entry.url.endsWith('/') ? entry.url : `${entry.url}/`;
16
+ return { ...entry, current, active: current || pageUrl.startsWith(section) };
17
+ });
18
+ }
19
+ //# sourceMappingURL=nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.js","sourceRoot":"","sources":["../src/nav.ts"],"names":[],"mappings":"AAQA,8FAA8F;AAC9F,MAAM,UAAU,UAAU,CAAC,KAAqB;IAC9C,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;SAC7E,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,SAAS,CAAC;SAC5D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC;SAC7F,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SACnE,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,MAAM,CAAC,OAA8C,EAAE,OAAe;IACpF,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;QACtE,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,31 @@
1
+ /** Conventional project directories, relative to the project root. */
2
+ export declare const ProjectDirs: {
3
+ /** Pages (Markdown/HTML) plus co-located files copied alongside them. */
4
+ readonly content: "content";
5
+ /** Copied verbatim to the site root. */
6
+ readonly static: "static";
7
+ /** Copied to /assets/ (processed in a later milestone). */
8
+ readonly assets: "assets";
9
+ /** Project overrides for theme templates. */
10
+ readonly templates: "templates";
11
+ /** JSON/YAML files exposed to templates as `data`. */
12
+ readonly data: "data";
13
+ };
14
+ export declare const PAGE_EXTENSIONS: Set<string>;
15
+ /**
16
+ * Resolve the output directory and refuse anything we must never delete: the
17
+ * project root, anything outside it, or a directory holding project sources.
18
+ */
19
+ export declare function resolveOutDir(rootDir: string, outDir: string): string;
20
+ /**
21
+ * Validate a scratch output directory (used by `sforge dev`): it must be a
22
+ * subdirectory of the OS temp directory, so a rebuild can never delete
23
+ * project files.
24
+ */
25
+ export declare function resolveScratchDir(dir: string): string;
26
+ /**
27
+ * List files under `dir` as POSIX paths relative to it. Skips dot-files,
28
+ * dot-directories and node_modules. Returns [] if the directory is missing.
29
+ */
30
+ export declare function listFiles(dir: string): string[];
31
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAKA,sEAAsE;AACtE,eAAO,MAAM,WAAW;IACtB,yEAAyE;;IAEzE,wCAAwC;;IAExC,2DAA2D;;IAE3D,6CAA6C;;IAE7C,sDAAsD;;CAE9C,CAAC;AAEX,eAAO,MAAM,eAAe,aAAiD,CAAC;AAE9E;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAuBrE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAe/C"}
@@ -0,0 +1,74 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import { ErrorCode, SforgeError } from '@staticforge/core';
5
+ /** Conventional project directories, relative to the project root. */
6
+ export const ProjectDirs = {
7
+ /** Pages (Markdown/HTML) plus co-located files copied alongside them. */
8
+ content: 'content',
9
+ /** Copied verbatim to the site root. */
10
+ static: 'static',
11
+ /** Copied to /assets/ (processed in a later milestone). */
12
+ assets: 'assets',
13
+ /** Project overrides for theme templates. */
14
+ templates: 'templates',
15
+ /** JSON/YAML files exposed to templates as `data`. */
16
+ data: 'data',
17
+ };
18
+ export const PAGE_EXTENSIONS = new Set(['.md', '.markdown', '.html', '.htm']);
19
+ /**
20
+ * Resolve the output directory and refuse anything we must never delete: the
21
+ * project root, anything outside it, or a directory holding project sources.
22
+ */
23
+ export function resolveOutDir(rootDir, outDir) {
24
+ const absolute = isAbsolute(outDir) ? outDir : resolve(rootDir, outDir);
25
+ const rel = relative(rootDir, absolute);
26
+ const unsafe = (reason) => new SforgeError(ErrorCode.UnsafePath, `Refusing to use ${absolute} as the output directory: ${reason}`, {
27
+ hint: 'Set build.outDir to a dedicated directory inside the project, such as "dist".',
28
+ });
29
+ if (rel === '')
30
+ throw unsafe('it is the project root');
31
+ if (rel.startsWith('..') || isAbsolute(rel))
32
+ throw unsafe('it is outside the project');
33
+ const top = rel.split(sep)[0] ?? '';
34
+ if (Object.values(ProjectDirs).includes(top) ||
35
+ top === 'node_modules' ||
36
+ top.startsWith('.')) {
37
+ throw unsafe(`it would overwrite the project's ${top}/ directory`);
38
+ }
39
+ return absolute;
40
+ }
41
+ /**
42
+ * Validate a scratch output directory (used by `sforge dev`): it must be a
43
+ * subdirectory of the OS temp directory, so a rebuild can never delete
44
+ * project files.
45
+ */
46
+ export function resolveScratchDir(dir) {
47
+ const absolute = resolve(dir);
48
+ const rel = relative(tmpdir(), absolute);
49
+ if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
50
+ throw new SforgeError(ErrorCode.UnsafePath, `Scratch output directory ${absolute} is not inside ${tmpdir()}`);
51
+ }
52
+ return absolute;
53
+ }
54
+ /**
55
+ * List files under `dir` as POSIX paths relative to it. Skips dot-files,
56
+ * dot-directories and node_modules. Returns [] if the directory is missing.
57
+ */
58
+ export function listFiles(dir) {
59
+ let entries;
60
+ try {
61
+ entries = readdirSync(dir, { recursive: true, withFileTypes: true });
62
+ }
63
+ catch (error) {
64
+ if (error.code === 'ENOENT')
65
+ return [];
66
+ throw error;
67
+ }
68
+ return entries
69
+ .filter((entry) => entry.isFile())
70
+ .map((entry) => relative(dir, join(entry.parentPath, entry.name)).split(sep).join('/'))
71
+ .filter((path) => !path.split('/').some((part) => part.startsWith('.') || part === 'node_modules'))
72
+ .sort();
73
+ }
74
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3D,sEAAsE;AACtE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,yEAAyE;IACzE,OAAO,EAAE,SAAS;IAClB,wCAAwC;IACxC,MAAM,EAAE,QAAQ;IAChB,2DAA2D;IAC3D,MAAM,EAAE,QAAQ;IAChB,6CAA6C;IAC7C,SAAS,EAAE,WAAW;IACtB,sDAAsD;IACtD,IAAI,EAAE,MAAM;CACJ,CAAC;AAEX,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAE9E;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,MAAc;IAC3D,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAChC,IAAI,WAAW,CACb,SAAS,CAAC,UAAU,EACpB,mBAAmB,QAAQ,6BAA6B,MAAM,EAAE,EAChE;QACE,IAAI,EAAE,+EAA+E;KACtF,CACF,CAAC;IAEJ,IAAI,GAAG,KAAK,EAAE;QAAE,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACvD,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;IACvF,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IACG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtD,GAAG,KAAK,cAAc;QACtB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EACnB,CAAC;QACD,MAAM,MAAM,CAAC,oCAAoC,GAAG,aAAa,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,WAAW,CACnB,SAAS,CAAC,UAAU,EACpB,4BAA4B,QAAQ,kBAAkB,MAAM,EAAE,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAClE,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;SACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SACtF,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,cAAc,CAAC,CAC3F;SACA,IAAI,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Report, ResolveRef } from '@staticforge/content';
2
+ export interface UrlOptions {
3
+ cleanUrls: boolean;
4
+ trailingSlash: 'always' | 'never';
5
+ }
6
+ export interface Route {
7
+ /** Root-relative URL, e.g. "/blog/hello/". */
8
+ url: string;
9
+ /** Output file, POSIX-relative to the output directory. */
10
+ output: string;
11
+ }
12
+ /**
13
+ * Map a page's path inside content/ to its URL and output file.
14
+ *
15
+ * index.md → / index.html
16
+ * about.md → /about/ about/index.html (cleanUrls, trailingSlash: always)
17
+ * → /about about.html (cleanUrls, trailingSlash: never)
18
+ * → /about.html about.html (cleanUrls: false)
19
+ * blog/_index.md → /blog/ blog/index.html
20
+ * 404.md → /404.html 404.html (always, for hosting providers)
21
+ */
22
+ export declare function routeFor(contentPath: string, slug: string | undefined, options: UrlOptions): Route;
23
+ /**
24
+ * Resolve references the way authors write them (relative to the source file,
25
+ * pointing at other sources) to the URLs they have in the built site:
26
+ *
27
+ * other.md#intro → /other/#intro (a page)
28
+ * photo.jpg → /blog/photo.jpg (a file next to the page)
29
+ * ../../assets/logo.svg → /assets/logo.svg
30
+ * ../../static/cv.pdf → /cv.pdf
31
+ *
32
+ * Root-relative URLs are site URLs and stay unchanged, except page sources
33
+ * (e.g. /blog/post.md). Absolute URLs and fragments are untouched.
34
+ */
35
+ export declare function createRefResolver(options: {
36
+ /** Project-relative path of the page holding the references. */
37
+ sourcePath: string;
38
+ /** Page URL by project-relative source path. */
39
+ pageUrls: ReadonlyMap<string, string>;
40
+ report: Report;
41
+ }): ResolveRef;
42
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAG/D,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,QAAQ,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,KAAK;IACpB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,UAAU,GAClB,KAAK,CAiBP;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,UAAU,CAoEb"}
package/dist/routes.js ADDED
@@ -0,0 +1,106 @@
1
+ import { posix } from 'node:path';
2
+ import { PAGE_EXTENSIONS, ProjectDirs } from './project.js';
3
+ /**
4
+ * Map a page's path inside content/ to its URL and output file.
5
+ *
6
+ * index.md → / index.html
7
+ * about.md → /about/ about/index.html (cleanUrls, trailingSlash: always)
8
+ * → /about about.html (cleanUrls, trailingSlash: never)
9
+ * → /about.html about.html (cleanUrls: false)
10
+ * blog/_index.md → /blog/ blog/index.html
11
+ * 404.md → /404.html 404.html (always, for hosting providers)
12
+ */
13
+ export function routeFor(contentPath, slug, options) {
14
+ const segments = contentPath.replace(/\.[^./]+$/, '').split('/');
15
+ let last = segments.pop() ?? 'index';
16
+ const isIndex = last === 'index' || last === '_index';
17
+ if (!isIndex && slug !== undefined)
18
+ last = slug;
19
+ if (segments.length === 0 && last === '404')
20
+ return { url: '/404.html', output: '404.html' };
21
+ const path = (isIndex ? segments : [...segments, last]).join('/');
22
+ if (path === '')
23
+ return { url: '/', output: 'index.html' };
24
+ const encoded = encodeURI(path);
25
+ if (options.cleanUrls && options.trailingSlash === 'never') {
26
+ return { url: `/${encoded}`, output: `${path}.html` };
27
+ }
28
+ if (options.cleanUrls || isIndex)
29
+ return { url: `/${encoded}/`, output: `${path}/index.html` };
30
+ return { url: `/${encoded}.html`, output: `${path}.html` };
31
+ }
32
+ const isPage = (path) => PAGE_EXTENSIONS.has(posix.extname(path).toLowerCase());
33
+ /**
34
+ * Resolve references the way authors write them (relative to the source file,
35
+ * pointing at other sources) to the URLs they have in the built site:
36
+ *
37
+ * other.md#intro → /other/#intro (a page)
38
+ * photo.jpg → /blog/photo.jpg (a file next to the page)
39
+ * ../../assets/logo.svg → /assets/logo.svg
40
+ * ../../static/cv.pdf → /cv.pdf
41
+ *
42
+ * Root-relative URLs are site URLs and stay unchanged, except page sources
43
+ * (e.g. /blog/post.md). Absolute URLs and fragments are untouched.
44
+ */
45
+ export function createRefResolver(options) {
46
+ const { sourcePath, pageUrls, report } = options;
47
+ const contentPrefix = `${ProjectDirs.content}/`;
48
+ return (ref, node) => {
49
+ if (ref === '' ||
50
+ ref.startsWith('#') ||
51
+ ref.startsWith('//') ||
52
+ /^[a-z][a-z\d+.-]*:/i.test(ref)) {
53
+ return undefined;
54
+ }
55
+ const [, rawPath = '', suffix = ''] = /^([^?#]*)(.*)$/s.exec(ref) ?? [];
56
+ if (rawPath === '')
57
+ return undefined;
58
+ let path;
59
+ try {
60
+ path = decodeURI(rawPath);
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ let projectPath;
66
+ if (path.startsWith('/')) {
67
+ if (!isPage(path))
68
+ return undefined;
69
+ projectPath = posix.normalize(contentPrefix + path.slice(1));
70
+ }
71
+ else {
72
+ projectPath = posix.normalize(posix.join(posix.dirname(sourcePath), path));
73
+ }
74
+ const pageUrl = pageUrls.get(projectPath);
75
+ if (pageUrl !== undefined)
76
+ return pageUrl + suffix;
77
+ // A root-relative /x.html that is not a page may be a file from static/;
78
+ // `sforge check` verifies it against the built site.
79
+ if (path.startsWith('/') && !/\.(?:md|markdown)$/i.test(path))
80
+ return undefined;
81
+ if (projectPath.startsWith(contentPrefix)) {
82
+ if (isPage(projectPath)) {
83
+ report({
84
+ ruleId: 'link-broken',
85
+ severity: 'warning',
86
+ message: `Link to ${ref} does not match any page`,
87
+ hint: 'Check the path, or the page may be a draft.',
88
+ }, node);
89
+ return undefined;
90
+ }
91
+ return `/${encodeURI(projectPath.slice(contentPrefix.length))}${suffix}`;
92
+ }
93
+ if (projectPath.startsWith(`${ProjectDirs.assets}/`))
94
+ return `/${encodeURI(projectPath)}${suffix}`;
95
+ if (projectPath.startsWith(`${ProjectDirs.static}/`)) {
96
+ return `/${encodeURI(projectPath.slice(ProjectDirs.static.length + 1))}${suffix}`;
97
+ }
98
+ report({
99
+ ruleId: 'link-outside-site',
100
+ severity: 'warning',
101
+ message: `Reference ${ref} points outside content/, assets/ and static/, so it will not be published`,
102
+ }, node);
103
+ return undefined;
104
+ };
105
+ }
106
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAc5D;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CACtB,WAAmB,EACnB,IAAwB,EACxB,OAAmB;IAEnB,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjE,IAAI,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC;IACtD,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,SAAS;QAAE,IAAI,GAAG,IAAI,CAAC;IAEhD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAE7F,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAE3D,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,aAAa,KAAK,OAAO,EAAE,CAAC;QAC3D,OAAO,EAAE,GAAG,EAAE,IAAI,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,OAAO,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,aAAa,EAAE,CAAC;IAC/F,OAAO,EAAE,GAAG,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAEjG;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAMjC;IACC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,aAAa,GAAG,GAAG,WAAW,CAAC,OAAO,GAAG,CAAC;IAEhD,OAAO,CAAC,GAAW,EAAE,IAAa,EAAE,EAAE;QACpC,IACE,GAAG,KAAK,EAAE;YACV,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YACnB,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YACpB,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,EAC/B,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACxE,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QAErC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,WAAmB,CAAC;QACxB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,SAAS,CAAC;YACpC,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC1C,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,GAAG,MAAM,CAAC;QACnD,yEAAyE;QACzE,qDAAqD;QACrD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAEhF,IAAI,WAAW,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;gBACxB,MAAM,CACJ;oBACE,MAAM,EAAE,aAAa;oBACrB,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,WAAW,GAAG,0BAA0B;oBACjD,IAAI,EAAE,6CAA6C;iBACpD,EACD,IAAI,CACL,CAAC;gBACF,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,OAAO,IAAI,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;QAC3E,CAAC;QACD,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC;YAClD,OAAO,IAAI,SAAS,CAAC,WAAW,CAAC,GAAG,MAAM,EAAE,CAAC;QAC/C,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,OAAO,IAAI,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;QACpF,CAAC;QAED,MAAM,CACJ;YACE,MAAM,EAAE,mBAAmB;YAC3B,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,aAAa,GAAG,4EAA4E;SACtG,EACD,IAAI,CACL,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type UrlOptions } from './routes.js';
2
+ export declare const SCHEMA_PATH = "./node_modules/staticforge/schema/staticforge.schema.json";
3
+ export interface ScaffoldResult {
4
+ rootDir: string;
5
+ /** Project-relative paths written. */
6
+ created: string[];
7
+ /** Project-relative paths left alone because they already existed. */
8
+ skipped: string[];
9
+ }
10
+ export interface InitOptions {
11
+ /** Absolute project directory; created if missing. */
12
+ dir: string;
13
+ title?: string;
14
+ baseUrl?: string;
15
+ language?: string;
16
+ format?: 'ts' | 'json' | 'yaml';
17
+ /** Add a "staging" deploy target for this provider. */
18
+ provider?: 'aws' | 'azure';
19
+ /** AWS region or Azure location for the target. */
20
+ region?: string;
21
+ /** Overwrite existing files. */
22
+ force?: boolean;
23
+ }
24
+ /** Create a new project: config, starter content, a site stylesheet and .gitignore. */
25
+ export declare function initProject(options: InitOptions): ScaffoldResult & {
26
+ configPath: string;
27
+ };
28
+ export interface NewContentOptions {
29
+ rootDir: string;
30
+ kind: 'page' | 'post';
31
+ /** Path inside content/, e.g. "about" or "blog/hello-world". Posts default to blog/. */
32
+ path: string;
33
+ title?: string;
34
+ format?: 'md' | 'html';
35
+ draft?: boolean;
36
+ force?: boolean;
37
+ /** Post date; defaults to today. */
38
+ date?: Date;
39
+ urls: UrlOptions;
40
+ }
41
+ /** Create a page or post with front-matter; returns its file and future URL. */
42
+ export declare function newContent(options: NewContentOptions): {
43
+ file: string;
44
+ url: string;
45
+ };
46
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,UAAU,EAAY,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,WAAW,8DAA8D,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IAC3B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+DD,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAmEzF;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAoCpF"}