@static-pages/core 5.0.3 → 7.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # CHANGELOG
2
+
3
+ ## 7.0.0-alpha.0
4
+ - New `CreateReader` and `CreateWriter` utilities available as `from` and `to` prop values.
5
+ - Writers now recieve an AsyncIterable as the previous method made too much confusion.
6
+ - Type defintion improvements.
7
+ - Test suite switched from tap to mocha for now.
8
+ - Coverage reports switched from nyc to c8.
9
+
10
+
11
+ ## 6.0.0
12
+ - Adopt JS iterator protocol for the writer callbacks.
13
+ - Removed the `.teardown()` call on the writer when iteration finished. The iterator protocol gives better tools to express these logic.
14
+
15
+
16
+ ## 5.0.3
17
+ - Updated .npmignore to remove unneeded files from package.
18
+
19
+ ## 5.0.2
20
+ - Use Github Actions instead of Travis-CI, replace Build status badge.
21
+
22
+ ## 5.0.1
23
+ - Updated maintenance badge in README.md.
24
+
25
+ ## 5.0.0
26
+ - Added a `.teardown()` call on the writer which is executed when iteration finished.
27
+ - Provided better type definitions.
28
+
29
+
30
+ ## 4.0.0
31
+ - Now types are exported in a namespace.
32
+
33
+
34
+ ## 3.0.4
35
+ - Updated eslint config.
36
+
37
+ ## 3.0.3
38
+ - Added esm tests.
39
+ - Improved build configuration.
40
+
41
+ ## 3.0.2
42
+ - Improved examples.
43
+
44
+ ## 3.0.1
45
+ - Updated docs.
46
+
47
+ ## 3.0.0
48
+ - Removed `variables` option on `Route` type. You can use `.bind()` on the writer to achieve the same functionality.
49
+ - Updated tests.
50
+ - Updated README.md.
51
+
52
+
53
+ ## 2.1.0
54
+ - In addition to the default export, now the main function also gets exported as `staticPages`.
55
+
56
+ ## 2.0.2
57
+ - Updated dependencies.
58
+ - Updated docker image link in README.md.
59
+
60
+ ## 2.0.1
61
+ - Fixed a typo in README.md.
62
+
63
+ ## 2.0.0
64
+ - Made configuration a bit cleaner and predictable by wrapping additional variables in a `variables` key in the `Route` type.
65
+ - Updated tests.
66
+ - Updated docs.
67
+ - Changed some eslint configuration.
68
+
69
+
70
+ ## 1.0.4
71
+ - Improved esmodule compatibility.
72
+ - Simplified syntax of `Data` type.
73
+
74
+ ## 1.0.3
75
+ - Made `isIterable` test a bit cleaner.
76
+
77
+ ## 1.0.2
78
+ - Added eslint support.
79
+
80
+ ## 1.0.1
81
+ - First stable release.
82
+ - Added node12 support.
83
+ - Updated .npmignore to remove unneeded files from package.
84
+ - Updated dependencies.
85
+ - Updated examples in README.md.
86
+ - Added badges to README.md.
87
+
88
+
89
+ ## 0.1.6
90
+ - Updated tests to get 100% coverage.
91
+ - Added test cases when `staticPages()` should throw.
92
+ - Fixed a faulty throws case.
93
+
94
+ ## 0.1.5
95
+ - Added Travis testing and Coveralls coverage badge to README.md.
96
+
97
+ ## 0.1.4
98
+ - Improved README.md.
99
+
100
+ ## 0.1.3
101
+ - Added error handling which should throw when invalid params provided.
102
+
103
+ ## 0.1.2
104
+ - Configured a `prepack` script to automatically test before publish.
105
+
106
+ ## 0.1.1
107
+ - Updated .npmignore to omit /src folder from release.
108
+ - Updated package keywords.
109
+
110
+ ## 0.1.0
111
+ - Initial work in progress release.
package/README.md CHANGED
@@ -1,68 +1,76 @@
1
1
  # Static Pages / Core
2
2
 
3
- [![Build Status](https://github.com/staticpagesjs/core/actions/workflows/coveralls.yaml/badge.svg)](https://github.com/staticpagesjs/core/actions/workflows/coveralls.yaml)
3
+ [![Build Status](https://github.com/staticpagesjs/core/actions/workflows/build.yaml/badge.svg)](https://github.com/staticpagesjs/core/actions/workflows/build.yaml)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/staticpagesjs/core/badge.svg?branch=master)](https://coveralls.io/github/staticpagesjs/core?branch=master)
5
5
  ![npms.io (quality)](https://img.shields.io/npms-io/quality-score/@static-pages/core?label=quality)
6
- ![Maintenance](https://img.shields.io/maintenance/yes/2023)
7
-
8
- This package contains only the core; this means it does not provide CLI support or readers and writers.
9
- You can import this library to your JS project then add your own controllers, readers and writers.
6
+ ![Maintenance](https://img.shields.io/maintenance/yes/2024)
10
7
 
11
8
  Yet another static pages generator?
12
9
  Yes! Because I browsed the whole jamstack scene, but could not find one which
13
- 1. uses MVC pattern
14
- 2. can read input from any source (YAML, JSON, front-matter style markdowns, database etc.)
15
- 3. can render with any template engine (Twig, ejs, Pug, Mustache etc.)
16
- 4. supports incremental builds
17
- 5. has a flexible CLI tool (see [@static-pages/cli](https://www.npmjs.com/package/@static-pages/cli) on npm)
18
- 6. has a Docker image (see [staticpages/cli](https://hub.docker.com/repository/docker/staticpages/cli) on dockerhub)
19
- 7. written in JS (preferably TypeScript)
20
- 8. easy to extend with JS code
21
- 9. learning and using is easy (Gatsby, Hugo, Jekyll, Eleventy etc. are so cool but harder to learn and configure)
10
+ 1. can read input from any source (YAML, JSON, front-matter style markdowns, database etc.)
11
+ 2. can render with any template engine (Twig, ejs, Pug, Mustache etc.)
12
+ 3. written in JS (preferably TypeScript)
13
+ 4. easy to extend with JS code
14
+ 5. supports incremental builds
15
+ 6. uses MVC pattern
16
+ 7. learning and using is easy (Gatsby, Hugo, Jekyll, Eleventy etc. are so cool but harder to learn and configure)
22
17
 
23
18
  And because I wrote a ton of custom static generators before; I tought I can improve the concepts to a point where its (hopefully) useful for others.
24
19
 
25
20
  ## Where should I use this?
26
- This project targets small and medium sized projects. The rendering process tries to be as fast as possible so its also useful when you need performance.
21
+
22
+ This project targets small and medium sized websites. The rendering process tries to be as fast as possible so its also useful when you need performance.
27
23
 
28
24
  ## Documentation
29
- [Visit the project page.](https://staticpagesjs.github.io/)
25
+
26
+ For detailed information, visit the [project page](https://staticpagesjs.github.io/).
30
27
 
31
28
  ## Usage
32
- __Readers__ provides an iterable list of page data. __Controllers__ can manipulate and extend each data object. __Writers__ render the final output for you.
33
29
 
34
30
  ```js
35
31
  import staticPages from '@static-pages/core';
36
- import markdownReader from '@static-pages/markdown-reader';
37
- import yamlReader from '@static-pages/yaml-reader';
38
- import twigWriter from '@static-pages/twig-writer';
39
-
40
- staticPages({
41
- from: markdownReader({
42
- pattern: "pages/**/*.md"
43
- }),
44
- to: twigWriter({
45
- view: "content.html.twig",
46
- viewsDir: "path/to/views/folder",
47
- outDir: "path/to/output/folder",
48
- }),
49
- controller(data) {
50
- data.timestamp = new Date().toJSON(); // adds a "timestamp" variable
51
- return data; // returning the data is required if you want to send it to the renderer
52
- }
53
- }, {
54
- from: yamlReader({ // assume we have the home page data in yaml format.
55
- pattern: "home/*.yaml" // <-- reads home/en.yaml, home/de.yaml, home/fr.yaml etc.
56
- }),
57
- to: twigWriter({
58
- view: "home.html.twig",
59
- viewsDir: "path/to/views/folder",
60
- outDir: "path/to/output/folder",
61
- }),
32
+ import twig from '@static-pages/twig';
33
+
34
+ // Default options for every `Route` via .with() call.
35
+ const generate = staticPages.with({
36
+ to: {
37
+ render: twig({
38
+ viewsDir: 'path/to/views/folder'
39
+ }),
40
+ },
62
41
  controller(data) {
63
- data.commitHash = yourGetCommitHashFn();
42
+ // adds a 'now' variable to the template context
43
+ data.now = new Date().toJSON();
44
+
45
+ // returning the data is required
64
46
  return data;
65
47
  }
48
+ });
49
+
50
+ // Generate every document type as a page.
51
+ // One route equals one batch of similar pages.
52
+ generate({
53
+ from: {
54
+ cwd: 'pages',
55
+ pattern: '**/*.md',
56
+ },
57
+ }, {
58
+ // Any Iterable or AsyncIterable also accepted eg. an array
59
+ from: [
60
+ { title: 'About', url: 'about', body: 'About us content' },
61
+ { title: 'Privacy', url: 'privacy', body: 'Privacy content' },
62
+ ],
63
+ }, {
64
+ from: {
65
+ cwd: 'home',
66
+ pattern: '*.yaml',
67
+ },
68
+ to: {
69
+ render: twig({
70
+ view: 'home.html.twig',
71
+ viewsDir: 'path/to/views/folder',
72
+ }),
73
+ },
66
74
  })
67
75
  .catch(error => {
68
76
  console.error('Error:', error);
@@ -70,25 +78,150 @@ staticPages({
70
78
  });
71
79
  ```
72
80
 
73
- ## `staticPages(...routes: Route[])`
81
+ ### Notes
82
+
83
+ > The `controller` may return with multiple documents, each will be rendered as a separate page. Alternatively it may return `undefined` to prevent the rendering of the current document.
84
+
85
+ > The `from` parameter can also recieve an `Iterable` or an `AsyncIterable` type!
74
86
 
75
- Each route consists of a `from`, `to` and optionally a `controller` property matching the definition below.
87
+ > The `to` parameter can also recieve a `function` that handles the document rendering and storing!
88
+
89
+
90
+ ## `staticPages(...routes: Route[]): Promise<void>`
91
+
92
+ Each route consists of a `from`, `to` and a `controller` property matching the definition below.
76
93
 
77
94
  ```ts
78
- type Data = Record<string, unknown>;
79
- type Route = {
80
- from: Iterable<Data> | AsyncIterable<Data>;
81
- to: { (data: Data): void | Promise<void>; teardown?(): void | Promise<void>; };
82
- controller?(data: Data): void | Data | Data[] | Promise<void | Data | Data[]>;
83
- };
95
+ interface Route<F, T> {
96
+ from?: Iterable<F> | AsyncIterable<F> | CreateReaderOptions<F>;
97
+ to?: { (data: AsyncIterable<T>): MaybePromise<void>; } | CreateWriterOptions<T>;
98
+ controller?(data: F): MaybePromise<undefined | T | Iterable<T> | AsyncIterable<T>>;
99
+ }
100
+
101
+ type MaybePromise<T> = T | Promise<T>;
102
+
103
+ interface CreateReaderOptions<T> {
104
+ // Handles file operations, defaults to nodejs `fs` module
105
+ fs?: Filesystem;
106
+ // Current working directory
107
+ cwd?: string;
108
+ // File patterns to include
109
+ pattern?: string | string[];
110
+ // File patterns to exclude
111
+ ignore?: string | string[];
112
+ // Callback to parse a file content into an object
113
+ parse?(content: Uint8Array | string, filename: string): MaybePromise<T>;
114
+ // Called on error
115
+ onError?(error: unknown): MaybePromise<void>;
116
+ }
117
+
118
+ interface CreateWriterOptions<T> {
119
+ // Handles file operations, defaults to nodejs `fs` module
120
+ fs?: Filesystem;
121
+ // Current working directory
122
+ cwd?: string;
123
+ // Callback that renders the document into a page
124
+ render?(data: T): MaybePromise<Uint8Array | string>;
125
+ // Callback that retrieves the filename (URL) of a page
126
+ name?(data: T): MaybePromise<string>;
127
+ // Called on error
128
+ onError?(error: unknown): MaybePromise<void>;
129
+ }
130
+
131
+ interface Stats {
132
+ isFile(): boolean;
133
+ isDirectory(): boolean;
134
+ }
135
+
136
+ interface Dirent {
137
+ name: string;
138
+ path: string;
139
+ isFile(): boolean;
140
+ isDirectory(): boolean;
141
+ }
142
+
143
+ interface Filesystem {
144
+ readdir(
145
+ path: string | URL,
146
+ options: {
147
+ encoding: 'utf8';
148
+ withFileTypes: false;
149
+ recursive: boolean;
150
+ },
151
+ callback: (err: Error | null, files: string[]) => void,
152
+ ): void;
153
+
154
+ readdir(
155
+ path: string | URL,
156
+ options: {
157
+ encoding: 'utf8';
158
+ withFileTypes: true;
159
+ recursive: boolean;
160
+ },
161
+ callback: (err: Error | null, files: Dirent[]) => void,
162
+ ): void;
163
+
164
+ readFile(
165
+ path: string | URL,
166
+ options: {
167
+ encoding: 'utf8';
168
+ },
169
+ callback: (err: Error | null, data: string) => void
170
+ ): void;
171
+
172
+ readFile(
173
+ path: string | URL,
174
+ options: null,
175
+ callback: (err: Error | null, data: Uint8Array) => void
176
+ ): void;
177
+
178
+ stat(
179
+ path: string | URL,
180
+ callback: (err: Error | null, stats: Stats) => void
181
+ ): void;
182
+
183
+ mkdir(
184
+ path: string | URL,
185
+ options: {
186
+ recursive: true;
187
+ },
188
+ callback: (err: Error | null, path?: string) => void
189
+ ): void;
190
+
191
+ writeFile(
192
+ path: string | URL,
193
+ data: string | Uint8Array,
194
+ callback: (err: Error | null) => void
195
+ ): void;
196
+ }
84
197
  ```
85
198
 
86
- > Tip: Controllers may return an array of `Data` objects; each will be rendered as a separate page.
87
- > Alternatively it may return `void` to prevent the rendering of the current data object.
199
+ ### `Filesystem` interface
200
+
201
+ When you use the `createReader` and `createWriter` interfaces to read and write documents, you can provide a `Filesystem` implementation. This interface is a minimal subset of the [NodeJS FS API](https://nodejs.org/api/fs.html). By default we use the built-in `node:fs` module.
202
+
203
+ ### `CreateReaderOptions` default parameters
204
+ - `fs`: the nodejs `fs` module
205
+ - `cwd`: `'pages'`
206
+ - `parse`: automatically parse `json`, `yaml`, `yml`, `md` or `markdown` extensions with `yaml` and `gray-matter` packages.
207
+ - `onError`: `(err) => { throw err; }`
208
+
209
+ ### `CreateWriterOptions` default parameters
210
+ - `fs`: the nodejs `fs` module
211
+ - `cwd`: `'public'`
212
+ - `name`: `(data) => data.url`
213
+ - `render`: `(data) => data.content`
214
+ - `onError`: `(err) => { throw err; }`
215
+
216
+
217
+ ## `staticPages.with(defaults: Partial<Route>): { (...routes: Partial<Route>[]): Promise<void>; }`
218
+
219
+ Preconfigures a separate instance of the `staticPages` call with the given default parameters.
220
+ These only works as fallback values, you can override every value later.
221
+
222
+ If a `from` or `to` parameter is a plain object in both defaults and later at the route definition they will be merged (see usage example).
88
223
 
89
- > Tip: To schedule cleanup after writers you can define a `.teardown()` member on the writer call.
90
- > This callback will be run after the last page is processed. If more writers provide the same callback
91
- > its only executed once.
92
224
 
93
225
  ## Missing a feature?
94
- Create an issue describing your needs. If it fits the scope of the project I will implement it or you can implement it your own and submit a pull request.
226
+ Create an issue describing your needs!
227
+ If it fits the scope of the project I will implement it.
@@ -0,0 +1 @@
1
+ export declare function autoparse(content: string | Uint8Array, filename: string): any;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.autoparse = void 0;
7
+ const yaml_1 = require("yaml");
8
+ const gray_matter_1 = __importDefault(require("gray-matter"));
9
+ function autoparse(content, filename) {
10
+ const extension = filename.split('.').pop().toLowerCase();
11
+ switch (extension) {
12
+ case 'json':
13
+ return JSON.parse(content.toString());
14
+ case 'yaml':
15
+ case 'yml':
16
+ return (0, yaml_1.parse)(content.toString());
17
+ case 'md':
18
+ case 'markdown':
19
+ const { data, content: markdownContent } = (0, gray_matter_1.default)(content.toString());
20
+ return { ...data, content: markdownContent };
21
+ }
22
+ throw new Error(`Could not parse document with '${extension}' extension.`);
23
+ }
24
+ exports.autoparse = autoparse;
@@ -0,0 +1,15 @@
1
+ import type { MaybePromise, Filesystem } from './helpers.js';
2
+ export declare namespace createReader {
3
+ interface Options<T> {
4
+ fs?: Filesystem;
5
+ cwd?: string;
6
+ pattern?: string | string[];
7
+ ignore?: string | string[];
8
+ parse?(content: Uint8Array | string, filename: string): MaybePromise<T>;
9
+ onError?(error: unknown): MaybePromise<void>;
10
+ }
11
+ }
12
+ export declare function createReader<T>({ fs, cwd, pattern, ignore, parse, onError, }?: createReader.Options<T>): AsyncGenerator<Awaited<T>, void, unknown>;
13
+ export declare namespace createReader {
14
+ var isOptions: <T>(x: unknown) => x is Options<T>;
15
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createReader = void 0;
7
+ const picomatch_1 = __importDefault(require("picomatch"));
8
+ const autoparse_js_1 = require("./autoparse.js");
9
+ const helpers_js_1 = require("./helpers.js");
10
+ const node_path_1 = require("node:path");
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ async function* createReader({ fs = node_fs_1.default, cwd = 'pages', pattern, ignore, parse = autoparse_js_1.autoparse, onError = (error) => { throw error; }, } = {}) {
13
+ if (!(0, helpers_js_1.isFilesystem)(fs))
14
+ throw new TypeError(`Expected Node FS compatible implementation at 'fs' property.`);
15
+ if (typeof cwd !== 'string')
16
+ throw new TypeError(`Expected 'string', recieved '${(0, helpers_js_1.getType)(cwd)}' at 'cwd' property.`);
17
+ if (!cwd)
18
+ throw new TypeError(`Expected non-empty string at 'cwd'.`);
19
+ if (typeof pattern !== 'undefined' && typeof pattern !== 'string' && !Array.isArray(pattern))
20
+ throw new TypeError(`Expected 'string' or 'string[]', recieved '${(0, helpers_js_1.getType)(pattern)}' at 'pattern' property.`);
21
+ if (typeof ignore !== 'undefined' && typeof ignore !== 'string' && !Array.isArray(ignore))
22
+ throw new TypeError(`Expected 'string' or 'string[]', recieved '${(0, helpers_js_1.getType)(ignore)}' at 'ignore' property.`);
23
+ if (typeof parse !== 'function')
24
+ throw new TypeError(`Expected 'function', recieved '${(0, helpers_js_1.getType)(parse)}' at 'parse' property.`);
25
+ if (typeof onError !== 'function')
26
+ throw new TypeError(`Expected 'function', recieved '${(0, helpers_js_1.getType)(onError)}' at 'onError' property.`);
27
+ let filenames = await new Promise((resolve, reject) => {
28
+ fs.readdir(cwd, { encoding: 'utf8', recursive: true, withFileTypes: true }, (err, files) => {
29
+ if (err)
30
+ reject(err);
31
+ else
32
+ resolve(files
33
+ .filter(x => x.isFile())
34
+ .map(x => (0, node_path_1.join)((0, node_path_1.relative)(cwd, x.path), x.name)));
35
+ });
36
+ });
37
+ if (typeof pattern !== 'undefined' || typeof ignore !== 'undefined') {
38
+ const filteredFilenames = [];
39
+ const isMatch = (0, picomatch_1.default)(pattern !== null && pattern !== void 0 ? pattern : '**/*', { ignore });
40
+ for (const filename of filenames) {
41
+ if (isMatch(filename)) {
42
+ filteredFilenames.push(filename);
43
+ }
44
+ }
45
+ filenames = filteredFilenames;
46
+ }
47
+ for (const filename of filenames) {
48
+ try {
49
+ const content = await new Promise((resolve, reject) => {
50
+ fs.readFile((0, node_path_1.join)(cwd, filename), null, (err, data) => {
51
+ if (err)
52
+ reject(err);
53
+ else
54
+ resolve(data);
55
+ });
56
+ });
57
+ yield await parse(content, filename);
58
+ }
59
+ catch (error) {
60
+ await onError(error);
61
+ }
62
+ }
63
+ }
64
+ exports.createReader = createReader;
65
+ createReader.isOptions = (x) => {
66
+ return x == undefined || (!!x && typeof x === 'object' && !(0, helpers_js_1.isIterable)(x) && !(0, helpers_js_1.isAsyncIterable)(x));
67
+ };
@@ -0,0 +1,14 @@
1
+ import type { MaybePromise, Filesystem } from './helpers.js';
2
+ export declare namespace createWriter {
3
+ interface Options<T> {
4
+ fs?: Filesystem;
5
+ cwd?: string;
6
+ name?(data: T): MaybePromise<string>;
7
+ render?(data: T): MaybePromise<Uint8Array | string>;
8
+ onError?(error: unknown): MaybePromise<void>;
9
+ }
10
+ }
11
+ export declare function createWriter<T>({ fs, cwd, name, render, onError, }?: createWriter.Options<T>): (iterable: Iterable<T> | AsyncIterable<T>) => Promise<void>;
12
+ export declare namespace createWriter {
13
+ var isOptions: <T>(x: unknown) => x is Options<T>;
14
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.createWriter = void 0;
27
+ const helpers_js_1 = require("./helpers.js");
28
+ const node_path_1 = require("node:path");
29
+ const nodeFs = __importStar(require("node:fs"));
30
+ const defaultNamer = (data) => {
31
+ if (!!data && typeof data === 'object' && 'url' in data && typeof data.url === 'string') {
32
+ return data.url.concat('.html');
33
+ }
34
+ throw new Error(`Missing 'url' field in the document.`);
35
+ };
36
+ const defaultRenderer = (data) => {
37
+ if (!!data && typeof data === 'object' && 'content' in data) {
38
+ return '' + data.content;
39
+ }
40
+ throw new Error(`Missing 'content' field in the document.`);
41
+ };
42
+ function createWriter({ fs = nodeFs, cwd = 'public', name = defaultNamer, render = defaultRenderer, onError = (error) => { throw error; }, } = {}) {
43
+ if (!(0, helpers_js_1.isFilesystem)(fs))
44
+ throw new TypeError(`Expected Node FS compatible implementation at 'fs' property.`);
45
+ if (typeof cwd !== 'string')
46
+ throw new TypeError(`Expected 'string', recieved '${(0, helpers_js_1.getType)(cwd)}' at 'cwd' property.`);
47
+ if (!cwd)
48
+ throw new TypeError(`Expected non-empty string at 'cwd'.`);
49
+ if (typeof render !== 'function')
50
+ throw new TypeError(`Expected 'function', recieved '${(0, helpers_js_1.getType)(render)}' at 'render' property.`);
51
+ if (typeof name !== 'function')
52
+ throw new TypeError(`Expected 'function', recieved '${(0, helpers_js_1.getType)(name)}' at 'name' property.`);
53
+ if (typeof onError !== 'function')
54
+ throw new TypeError(`Expected 'function', recieved '${(0, helpers_js_1.getType)(onError)}' at 'onError' property.`);
55
+ return async function (iterable) {
56
+ if (!(0, helpers_js_1.isIterable)(iterable) && !(0, helpers_js_1.isAsyncIterable)(iterable))
57
+ throw new TypeError(`Expected 'Iterable' or 'AsyncIterable' at callback.`);
58
+ for await (const data of iterable) {
59
+ try {
60
+ const filepath = cwd + '/' + await name(data);
61
+ const dirpath = (0, node_path_1.dirname)(filepath);
62
+ await new Promise((resolve, reject) => {
63
+ fs.stat(dirpath, (err, stats) => {
64
+ if (err) {
65
+ fs.mkdir(dirpath, { recursive: true }, (err) => {
66
+ if (err) {
67
+ reject(err);
68
+ }
69
+ else {
70
+ resolve(undefined);
71
+ }
72
+ });
73
+ }
74
+ else {
75
+ resolve(undefined);
76
+ }
77
+ });
78
+ });
79
+ const content = await render(data);
80
+ await new Promise((resolve, reject) => {
81
+ fs.writeFile(filepath, content, (err) => {
82
+ if (err)
83
+ reject(err);
84
+ else
85
+ resolve(undefined);
86
+ });
87
+ });
88
+ }
89
+ catch (error) {
90
+ await onError(error);
91
+ }
92
+ }
93
+ };
94
+ }
95
+ exports.createWriter = createWriter;
96
+ createWriter.isOptions = (x) => {
97
+ return x == undefined || (!!x && typeof x === 'object');
98
+ };
@@ -0,0 +1,37 @@
1
+ export type MaybePromise<T> = T | Promise<T>;
2
+ interface Stats {
3
+ isFile(): boolean;
4
+ isDirectory(): boolean;
5
+ }
6
+ interface Dirent {
7
+ name: string;
8
+ path: string;
9
+ isFile(): boolean;
10
+ isDirectory(): boolean;
11
+ }
12
+ export interface Filesystem {
13
+ readdir(path: string | URL, options: {
14
+ encoding: 'utf8';
15
+ withFileTypes: false;
16
+ recursive: boolean;
17
+ }, callback: (err: Error | null, files: string[]) => void): void;
18
+ readdir(path: string | URL, options: {
19
+ encoding: 'utf8';
20
+ withFileTypes: true;
21
+ recursive: boolean;
22
+ }, callback: (err: Error | null, files: Dirent[]) => void): void;
23
+ readFile(path: string | URL, options: {
24
+ encoding: 'utf8';
25
+ }, callback: (err: Error | null, data: string) => void): void;
26
+ readFile(path: string | URL, options: null, callback: (err: Error | null, data: Uint8Array) => void): void;
27
+ stat(path: string | URL, callback: (err: Error | null, stats: Stats) => void): void;
28
+ mkdir(path: string | URL, options: {
29
+ recursive: true;
30
+ }, callback: (err: Error | null, path?: string) => void): void;
31
+ writeFile(path: string | URL, data: string | Uint8Array, callback: (err: Error | null) => void): void;
32
+ }
33
+ export declare const isFilesystem: (x: unknown) => x is Filesystem;
34
+ export declare const isIterable: <T>(x: unknown) => x is Iterable<T>;
35
+ export declare const isAsyncIterable: <T>(x: unknown) => x is AsyncIterable<T>;
36
+ export declare const getType: (x: unknown) => string;
37
+ export {};
package/cjs/helpers.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getType = exports.isAsyncIterable = exports.isIterable = exports.isFilesystem = void 0;
4
+ const isFilesystem = (x) => !!x && typeof x === 'object' && 'stat' in x && 'mkdir' in x && 'readFile' in x && 'writeFile' in x;
5
+ exports.isFilesystem = isFilesystem;
6
+ const isIterable = (x) => !!x && typeof x === 'object' && Symbol.iterator in x && typeof x[Symbol.iterator] === 'function';
7
+ exports.isIterable = isIterable;
8
+ const isAsyncIterable = (x) => !!x && typeof x === 'object' && Symbol.asyncIterator in x && typeof x[Symbol.asyncIterator] === 'function';
9
+ exports.isAsyncIterable = isAsyncIterable;
10
+ const getType = (x) => typeof x === 'object' ? (x ? (Array.isArray(x) ? 'array' : 'object') : 'null') : typeof x;
11
+ exports.getType = getType;