@anthusai/papyrus 5.18.0 → 5.19.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/amplify/publication/redirect-and-index.js +22 -0
- package/amplify/publication/redirect-rules.js +21 -9
- package/amplify/publication/redirect-rules.ts +25 -11
- package/amplify/publication/s3-projection.js +4 -2
- package/amplify/publication/s3-projection.ts +4 -2
- package/bin/papyrus-infra.mjs +1 -1
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
|
@@ -91,6 +91,22 @@ function markdownTwin(uri) {
|
|
|
91
91
|
return uri.substring(0, uri.length - 5) + ".md";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Directory-style sites keep a page's twin at /x/index.md, so a bare /x.md is rewritten there. The edge cannot test for
|
|
95
|
+
// existence, hence the rule: a last segment <name>.md with a name other than index. A path prefix (a base path) is just
|
|
96
|
+
// part of <path>. The flag is spliced in by redirect-rules.ts at deploy time from urls.style.
|
|
97
|
+
const directoryTwins = /*PAPYRUS_DIRECTORY_TWINS*/false/*END_PAPYRUS_DIRECTORY_TWINS*/;
|
|
98
|
+
|
|
99
|
+
function directoryTwin(uri) {
|
|
100
|
+
if (uri.length < 5 || uri.substring(uri.length - 3) !== ".md") {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const lastSegment = uri.substring(uri.lastIndexOf("/") + 1);
|
|
104
|
+
if (lastSegment === "index.md" || lastSegment === ".md") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return uri.substring(0, uri.length - 3) + "/index.md";
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
async function handler(event) {
|
|
95
111
|
const request = event.request;
|
|
96
112
|
const redirectTarget = await lookUpRedirectTarget(request.uri);
|
|
@@ -110,6 +126,12 @@ async function handler(event) {
|
|
|
110
126
|
};
|
|
111
127
|
}
|
|
112
128
|
request.uri = rewriteToIndexDocument(request.uri);
|
|
129
|
+
if (directoryTwins) {
|
|
130
|
+
const bareTwin = directoryTwin(request.uri);
|
|
131
|
+
if (bareTwin !== null) {
|
|
132
|
+
request.uri = bareTwin;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
113
135
|
if (negotiateMarkdown && prefersMarkdown(request)) {
|
|
114
136
|
const twin = markdownTwin(request.uri);
|
|
115
137
|
if (twin !== null) {
|
|
@@ -27,20 +27,34 @@ export function edgeRulesFromSettings(settings) {
|
|
|
27
27
|
export function negotiatesMarkdown(settings) {
|
|
28
28
|
return settings.site !== undefined && settings.agents?.markdown !== false;
|
|
29
29
|
}
|
|
30
|
+
/** A directory-style site keeps a page's Markdown twin at `/x/index.md`; the edge then serves a bare `/x.md` from there. */
|
|
31
|
+
export function usesDirectoryTwins(settings) {
|
|
32
|
+
return negotiatesMarkdown(settings) && settings.urls?.style === "directory";
|
|
33
|
+
}
|
|
30
34
|
export function readEdgeConfig(staticDir, baseDirectory = process.cwd()) {
|
|
31
35
|
if (staticDir === undefined)
|
|
32
|
-
return { rules: [], negotiateMarkdown: false };
|
|
36
|
+
return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
33
37
|
const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
|
|
34
38
|
if (!existsSync(settingsPath))
|
|
35
|
-
return { rules: [], negotiateMarkdown: false };
|
|
39
|
+
return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
36
40
|
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
37
|
-
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
|
|
41
|
+
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings), directoryTwins: usesDirectoryTwins(settings) };
|
|
38
42
|
}
|
|
39
43
|
export function readEdgeRules(staticDir, baseDirectory = process.cwd()) {
|
|
40
44
|
return readEdgeConfig(staticDir, baseDirectory).rules;
|
|
41
45
|
}
|
|
42
46
|
const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
43
47
|
const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
48
|
+
const TWINS_START = "/*PAPYRUS_DIRECTORY_TWINS*/";
|
|
49
|
+
const TWINS_END = "/*END_PAPYRUS_DIRECTORY_TWINS*/";
|
|
50
|
+
function spliceFlag(source, start, end, label) {
|
|
51
|
+
const from = source.indexOf(start);
|
|
52
|
+
const to = source.indexOf(end);
|
|
53
|
+
if (from === -1 || to === -1 || to < from) {
|
|
54
|
+
throw new Error(`redirect function source has no ${label} markers`);
|
|
55
|
+
}
|
|
56
|
+
return source.slice(0, from + start.length) + "true" + source.slice(to);
|
|
57
|
+
}
|
|
44
58
|
/** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
|
|
45
59
|
export function compileRedirectFunctionSource(source, rules, options = {}) {
|
|
46
60
|
const start = source.indexOf(START_MARKER);
|
|
@@ -50,12 +64,10 @@ export function compileRedirectFunctionSource(source, rules, options = {}) {
|
|
|
50
64
|
}
|
|
51
65
|
let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
|
|
52
66
|
if (options.negotiateMarkdown === true) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
|
|
67
|
+
compiled = spliceFlag(compiled, NEGOTIATE_START, NEGOTIATE_END, "markdown negotiation");
|
|
68
|
+
}
|
|
69
|
+
if (options.directoryTwins === true) {
|
|
70
|
+
compiled = spliceFlag(compiled, TWINS_START, TWINS_END, "directory twin");
|
|
59
71
|
}
|
|
60
72
|
return compiled;
|
|
61
73
|
}
|
|
@@ -38,14 +38,19 @@ export function negotiatesMarkdown(settings: ProjectionSettings): boolean {
|
|
|
38
38
|
return settings.site !== undefined && settings.agents?.markdown !== false;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/** A directory-style site keeps a page's Markdown twin at `/x/index.md`; the edge then serves a bare `/x.md` from there. */
|
|
42
|
+
export function usesDirectoryTwins(settings: ProjectionSettings): boolean {
|
|
43
|
+
return negotiatesMarkdown(settings) && settings.urls?.style === "directory";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type EdgeConfig = { rules: EdgeRedirectRule[]; negotiateMarkdown: boolean; directoryTwins: boolean };
|
|
42
47
|
|
|
43
48
|
export function readEdgeConfig(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeConfig {
|
|
44
|
-
if (staticDir === undefined) return { rules: [], negotiateMarkdown: false };
|
|
49
|
+
if (staticDir === undefined) return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
45
50
|
const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
|
|
46
|
-
if (!existsSync(settingsPath)) return { rules: [], negotiateMarkdown: false };
|
|
51
|
+
if (!existsSync(settingsPath)) return { rules: [], negotiateMarkdown: false, directoryTwins: false };
|
|
47
52
|
const settings = JSON.parse(readFileSync(settingsPath, "utf8")) as ProjectionSettings;
|
|
48
|
-
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
|
|
53
|
+
return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings), directoryTwins: usesDirectoryTwins(settings) };
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export function readEdgeRules(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeRedirectRule[] {
|
|
@@ -54,12 +59,23 @@ export function readEdgeRules(staticDir: string | undefined, baseDirectory: stri
|
|
|
54
59
|
|
|
55
60
|
const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
56
61
|
const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
|
|
62
|
+
const TWINS_START = "/*PAPYRUS_DIRECTORY_TWINS*/";
|
|
63
|
+
const TWINS_END = "/*END_PAPYRUS_DIRECTORY_TWINS*/";
|
|
64
|
+
|
|
65
|
+
function spliceFlag(source: string, start: string, end: string, label: string): string {
|
|
66
|
+
const from = source.indexOf(start);
|
|
67
|
+
const to = source.indexOf(end);
|
|
68
|
+
if (from === -1 || to === -1 || to < from) {
|
|
69
|
+
throw new Error(`redirect function source has no ${label} markers`);
|
|
70
|
+
}
|
|
71
|
+
return source.slice(0, from + start.length) + "true" + source.slice(to);
|
|
72
|
+
}
|
|
57
73
|
|
|
58
74
|
/** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
|
|
59
75
|
export function compileRedirectFunctionSource(
|
|
60
76
|
source: string,
|
|
61
77
|
rules: EdgeRedirectRule[],
|
|
62
|
-
options: { negotiateMarkdown?: boolean } = {},
|
|
78
|
+
options: { negotiateMarkdown?: boolean; directoryTwins?: boolean } = {},
|
|
63
79
|
): string {
|
|
64
80
|
const start = source.indexOf(START_MARKER);
|
|
65
81
|
const end = source.indexOf(END_MARKER);
|
|
@@ -68,12 +84,10 @@ export function compileRedirectFunctionSource(
|
|
|
68
84
|
}
|
|
69
85
|
let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
|
|
70
86
|
if (options.negotiateMarkdown === true) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
|
|
87
|
+
compiled = spliceFlag(compiled, NEGOTIATE_START, NEGOTIATE_END, "markdown negotiation");
|
|
88
|
+
}
|
|
89
|
+
if (options.directoryTwins === true) {
|
|
90
|
+
compiled = spliceFlag(compiled, TWINS_START, TWINS_END, "directory twin");
|
|
77
91
|
}
|
|
78
92
|
return compiled;
|
|
79
93
|
}
|
|
@@ -70,10 +70,12 @@ export function addS3ProjectionPublication(scope, props, media) {
|
|
|
70
70
|
assertValidPublicationConfig({ kind: "s3-projection", ...props });
|
|
71
71
|
const bucket = createSiteBucket(scope, props.retainBucketOnDelete !== false);
|
|
72
72
|
const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
|
|
73
|
+
const edgeConfig = readEdgeConfig(props.staticDir);
|
|
73
74
|
const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
|
|
74
75
|
runtime: cloudfront.FunctionRuntime.JS_2_0,
|
|
75
|
-
code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"),
|
|
76
|
-
negotiateMarkdown:
|
|
76
|
+
code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), edgeConfig.rules, {
|
|
77
|
+
negotiateMarkdown: edgeConfig.negotiateMarkdown,
|
|
78
|
+
directoryTwins: edgeConfig.directoryTwins,
|
|
77
79
|
})),
|
|
78
80
|
keyValueStore,
|
|
79
81
|
});
|
|
@@ -112,11 +112,13 @@ export function addS3ProjectionPublication(
|
|
|
112
112
|
const bucket = createSiteBucket(scope, props.retainBucketOnDelete !== false);
|
|
113
113
|
|
|
114
114
|
const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
|
|
115
|
+
const edgeConfig = readEdgeConfig(props.staticDir);
|
|
115
116
|
const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
|
|
116
117
|
runtime: cloudfront.FunctionRuntime.JS_2_0,
|
|
117
118
|
code: cloudfront.FunctionCode.fromInline(
|
|
118
|
-
compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"),
|
|
119
|
-
negotiateMarkdown:
|
|
119
|
+
compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), edgeConfig.rules, {
|
|
120
|
+
negotiateMarkdown: edgeConfig.negotiateMarkdown,
|
|
121
|
+
directoryTwins: edgeConfig.directoryTwins,
|
|
120
122
|
}),
|
|
121
123
|
),
|
|
122
124
|
keyValueStore,
|
package/bin/papyrus-infra.mjs
CHANGED
|
@@ -53,7 +53,7 @@ if (command === "compile-edge-function") {
|
|
|
53
53
|
const { compileRedirectFunctionSource, readEdgeConfig } = await import("../amplify/publication/redirect-rules.js");
|
|
54
54
|
const functionSource = fs.readFileSync(new URL("../amplify/publication/redirect-and-index.js", import.meta.url), "utf8");
|
|
55
55
|
const config = readEdgeConfig(staticDir);
|
|
56
|
-
const compiled = compileRedirectFunctionSource(functionSource, config.rules, { negotiateMarkdown: config.negotiateMarkdown });
|
|
56
|
+
const compiled = compileRedirectFunctionSource(functionSource, config.rules, { negotiateMarkdown: config.negotiateMarkdown, directoryTwins: config.directoryTwins });
|
|
57
57
|
const out = opt("out");
|
|
58
58
|
if (out) fs.writeFileSync(path.resolve(out), compiled);
|
|
59
59
|
else process.stdout.write(compiled);
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED