/
writer.ts
160 lines (136 loc) Β· 3.77 KB
/
writer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { posix } from "../deps/path.ts";
import { emptyDir, ensureDir } from "../deps/fs.ts";
import { concurrent, sha1 } from "./utils.ts";
import { Exception } from "./errors.ts";
import type { Page, StaticFile } from "./filesystem.ts";
import type Logger from "./logger.ts";
export interface Options {
src: string;
dest: string;
logger: Logger;
}
/**
* Class to write the generated pages and static files
* in the dest folder.
*/
export default class Writer {
src: string;
dest: string;
logger: Logger;
#saveCount = 0;
#outputs = new Map<string, [number, string, string]>();
constructor(options: Options) {
this.src = options.src;
this.dest = options.dest;
this.logger = options.logger;
}
/**
* Save the pages in the dest folder
* Returns an array of pages that have been saved
*/
async savePages(pages: Page[]): Promise<Page[]> {
const savedPages: Page[] = [];
++this.#saveCount;
await concurrent(
pages,
async (page) => {
if (await this.savePage(page)) {
savedPages.push(page);
}
},
);
return savedPages;
}
/**
* Save a page in the dest folder
* Returns a boolean indicating if the page has saved
*/
async savePage(page: Page): Promise<boolean> {
// Ignore empty files
if (!page.content) {
return false;
}
const src = page.src.path
? page.src.path + (page.src.ext || "")
: "(generated)";
const dest = page.dest.path + page.dest.ext;
const id = dest.toLowerCase();
const hash = await sha1(page.content);
const previous = this.#outputs.get(id);
this.#outputs.set(id, [this.#saveCount, src, hash]);
if (previous) {
const [previousCount, previousPage, previousHash] = previous;
if (previousCount === this.#saveCount) {
throw new Exception(
"A page will overwrite another page. Use distinct `url` values to resolve the conflict.",
{
page,
previousPage,
destination: dest,
},
);
}
// The page content didn't change
if (previousHash === hash) {
return false;
}
}
this.logger.log(`π₯ ${dest.replace(/index\.html?$/, "")} <dim>${src}</dim>`);
const filename = posix.join(this.dest, dest);
await ensureDir(posix.dirname(filename));
page.content instanceof Uint8Array
? await Deno.writeFile(filename, page.content)
: await Deno.writeTextFile(filename, page.content);
return true;
}
/**
* Copy the static files in the dest folder
*/
async copyFiles(files: StaticFile[]): Promise<StaticFile[]> {
const copyFiles: StaticFile[] = [];
await concurrent(
files,
async (file) => {
if (await this.copyFile(file)) {
copyFiles.push(file);
}
},
);
return copyFiles;
}
/**
* Copy a static file in the dest folder
* Returns a boolean indicating if the file has saved
*/
async copyFile(file: StaticFile): Promise<boolean> {
const { src, dest, saved, removed } = file;
if (saved || removed) {
return false;
}
const pathFrom = posix.join(this.src, src);
const pathTo = posix.join(this.dest, dest);
try {
await ensureDir(posix.dirname(pathTo));
await Deno.copyFile(pathFrom, pathTo);
this.logger.log(`π₯ ${dest} <dim>${src}</dim>`);
file.saved = true;
return true;
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
try {
await Deno.remove(pathTo);
this.logger.log(`β <dim>${dest}</dim>`);
file.removed = true;
} catch {
// Ignored
}
}
}
return false;
}
/** Empty the dest folder */
async clear() {
await emptyDir(this.dest);
this.#outputs.clear();
}
}