@farm.js/search 0.1.0-beta.0 → 0.1.0-beta.1
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/dist/build.d.ts.map +1 -1
- package/dist/build.js +36 -2
- package/dist/config.js +20 -1
- package/package.json +2 -2
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgF1F;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAoB3E"}
|
package/dist/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
1
|
+
import { lstat, mkdir, readFile, readdir, realpath, rm } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import * as pagefind from "pagefind";
|
|
4
4
|
export async function writeSearchIndex(input) {
|
|
@@ -6,6 +6,7 @@ export async function writeSearchIndex(input) {
|
|
|
6
6
|
const basePath = normalizeBasePath(input.basePath);
|
|
7
7
|
const bundlePath = withBasePath(`/${input.options.output}`, basePath);
|
|
8
8
|
const outputPath = resolveOutputPath(publicDir, bundlePath);
|
|
9
|
+
await assertOutputPathInside(publicDir, outputPath);
|
|
9
10
|
const htmlFiles = await collectHtmlFiles(publicDir, outputPath);
|
|
10
11
|
const routes = htmlFiles
|
|
11
12
|
.map((file) => ({
|
|
@@ -163,11 +164,44 @@ async function collectHtmlFiles(root, ignoredDirectory) {
|
|
|
163
164
|
function resolveOutputPath(publicDir, bundlePath) {
|
|
164
165
|
const outputPath = path.resolve(publicDir, `.${bundlePath}`);
|
|
165
166
|
const relative = path.relative(publicDir, outputPath);
|
|
166
|
-
if (!relative ||
|
|
167
|
+
if (!relative ||
|
|
168
|
+
relative === ".." ||
|
|
169
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
170
|
+
path.isAbsolute(relative)) {
|
|
167
171
|
throw new Error("[farm:search] Search output must stay inside the public output directory");
|
|
168
172
|
}
|
|
169
173
|
return outputPath;
|
|
170
174
|
}
|
|
175
|
+
async function assertOutputPathInside(publicDir, outputPath) {
|
|
176
|
+
const realPublicDir = await realpath(publicDir);
|
|
177
|
+
let existingAncestor = outputPath;
|
|
178
|
+
while (true) {
|
|
179
|
+
try {
|
|
180
|
+
existingAncestor = await realpath(existingAncestor);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (error.code !== "ENOENT")
|
|
185
|
+
throw error;
|
|
186
|
+
try {
|
|
187
|
+
await lstat(existingAncestor);
|
|
188
|
+
throw new Error("[farm:search] Search output must stay inside the public output directory, including through symlinks");
|
|
189
|
+
}
|
|
190
|
+
catch (statError) {
|
|
191
|
+
if (statError.code !== "ENOENT")
|
|
192
|
+
throw statError;
|
|
193
|
+
}
|
|
194
|
+
const parent = path.dirname(existingAncestor);
|
|
195
|
+
if (parent === existingAncestor)
|
|
196
|
+
throw error;
|
|
197
|
+
existingAncestor = parent;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const relative = path.relative(realPublicDir, existingAncestor);
|
|
201
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
202
|
+
throw new Error("[farm:search] Search output must stay inside the public output directory, including through symlinks");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
171
205
|
function pagefindError(action, errors) {
|
|
172
206
|
const details = errors.length ? `:\n${errors.map((error) => ` - ${error}`).join("\n")}` : "";
|
|
173
207
|
return new Error(`[farm:search] Pagefind ${action}${details}`);
|
package/dist/config.js
CHANGED
|
@@ -57,11 +57,30 @@ function normalizeOutput(value) {
|
|
|
57
57
|
value.includes("\\") ||
|
|
58
58
|
value.includes("?") ||
|
|
59
59
|
value.includes("#") ||
|
|
60
|
-
segments.some((segment) =>
|
|
60
|
+
segments.some((segment) => !isStableOutputSegment(segment))) {
|
|
61
61
|
throw new TypeError("Search output must stay inside the public output directory");
|
|
62
62
|
}
|
|
63
63
|
return normalized;
|
|
64
64
|
}
|
|
65
|
+
function isStableOutputSegment(segment) {
|
|
66
|
+
if (!segment)
|
|
67
|
+
return false;
|
|
68
|
+
let decoded = segment;
|
|
69
|
+
try {
|
|
70
|
+
decoded = decodeURIComponent(segment);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Malformed escapes remain literal in browser pathnames.
|
|
74
|
+
}
|
|
75
|
+
return (decoded !== "." &&
|
|
76
|
+
decoded !== ".." &&
|
|
77
|
+
!decoded.includes("/") &&
|
|
78
|
+
!decoded.includes("\\") &&
|
|
79
|
+
!Array.from(decoded).some((character) => {
|
|
80
|
+
const code = character.charCodeAt(0);
|
|
81
|
+
return code <= 31 || (code >= 127 && code <= 159);
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
65
84
|
function normalizeLanguage(value) {
|
|
66
85
|
const normalized = normalizeOptionalString(value, "language")?.toLowerCase();
|
|
67
86
|
if (normalized && !/^[a-z]{2}$/.test(normalized)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/search",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
4
|
"description": "Build-time static page search for Farm.js applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"farm.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"pagefind": "^1.5.2",
|
|
40
|
-
"@farm.js/core": "0.1.0-beta.
|
|
40
|
+
"@farm.js/core": "0.1.0-beta.93"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.10.5",
|