@anthusai/papyrus 5.1.0 → 5.3.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 (35) hide show
  1. package/amplify/data/schema.js +2 -0
  2. package/amplify/data/schema.ts +2 -0
  3. package/amplify/functions/monitoring-view/handler.py +11 -0
  4. package/amplify/functions/monitoring-view/resource.js +24 -0
  5. package/amplify/functions/monitoring-view/resource.ts +34 -0
  6. package/amplify/functions/monitoring-view/schema.js +16 -0
  7. package/amplify/functions/monitoring-view/schema.ts +18 -0
  8. package/amplify/functions/monitoring-view/wire.js +37 -0
  9. package/amplify/functions/monitoring-view/wire.ts +47 -0
  10. package/amplify/publication/redirect-and-index.js +49 -0
  11. package/amplify/publication/redirect-rules.js +26 -10
  12. package/amplify/publication/redirect-rules.ts +36 -10
  13. package/amplify/publication/s3-projection.js +4 -2
  14. package/amplify/publication/s3-projection.ts +4 -2
  15. package/amplify/site-backend.js +4 -0
  16. package/amplify/site-backend.ts +4 -0
  17. package/components/monitoring-overview-view.tsx +240 -0
  18. package/components/monitoring-panel.tsx +40 -0
  19. package/components/newsroom-organizer.css +17 -0
  20. package/components/newsroom-references-view.tsx +14 -14
  21. package/components/publication-cyclotron-panel.tsx +10 -10
  22. package/components/reference-add-entry.tsx +75 -0
  23. package/components/reference-curation-controls.tsx +30 -21
  24. package/components/reference-cyclotron-priming.tsx +4 -4
  25. package/components/reference-cyclotron-suggestion.tsx +1 -1
  26. package/components/reference-priming.tsx +14 -10
  27. package/components/reference-section-suggestion.tsx +1 -1
  28. package/components/topic-steering-workspace.tsx +13 -2
  29. package/lib/category-repository.ts +1 -1
  30. package/lib/monitoring-client.ts +29 -0
  31. package/lib/monitoring-overview-fixture.ts +51 -0
  32. package/lib/monitoring-overview.ts +144 -0
  33. package/lib/relevance-status.ts +2 -2
  34. package/package.json +8 -2
  35. package/routes.manifest.json +1 -1
@@ -2,6 +2,7 @@ import { a } from "@aws-amplify/backend";
2
2
  import { assignmentAction } from "../functions/assignment-action/resource.js";
3
3
  import { categoryAction } from "../functions/category-action/resource.js";
4
4
  import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema.js";
5
+ import { monitoringViewSchemaDefinitions } from "../functions/monitoring-view/schema.js";
5
6
  import { contentActions } from "../functions/content-actions/resource.js";
6
7
  import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
7
8
  import { manageUserRole } from "../functions/manage-user-role/resource.js";
@@ -2001,6 +2002,7 @@ const baseDefinitions = {
2001
2002
  allow.groups(contentWriteGroups),
2002
2003
  ]),
2003
2004
  ...editorialChecksSchemaDefinitions,
2005
+ ...monitoringViewSchemaDefinitions,
2004
2006
  };
2005
2007
  export const schema = a.schema(baseDefinitions).authorization((allow) => [
2006
2008
  allow.resource(assignmentAction).to(["query", "mutate"]),
@@ -2,6 +2,7 @@ import { type ClientSchema, a } from "@aws-amplify/backend";
2
2
  import { assignmentAction } from "../functions/assignment-action/resource";
3
3
  import { categoryAction } from "../functions/category-action/resource";
4
4
  import { editorialChecksSchemaDefinitions } from "../functions/editorial-checks/schema";
5
+ import { monitoringViewSchemaDefinitions } from "../functions/monitoring-view/schema";
5
6
  import { contentActions } from "../functions/content-actions/resource";
6
7
  import { knowledgeQuery } from "../functions/knowledge-query/resource";
7
8
  import { manageUserRole } from "../functions/manage-user-role/resource";
@@ -2121,6 +2122,7 @@ const baseDefinitions = {
2121
2122
  allow.groups(contentWriteGroups),
2122
2123
  ]),
2123
2124
  ...editorialChecksSchemaDefinitions,
2125
+ ...monitoringViewSchemaDefinitions,
2124
2126
  };
2125
2127
 
2126
2128
  export const schema = a.schema(baseDefinitions).authorization((allow) => [
@@ -0,0 +1,11 @@
1
+ """The monitoring-view function: `monitoringOverview` for editors and admins, read-only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from papyrus_content.monitoring_view import handle
8
+
9
+
10
+ def handler(event: dict[str, Any], _context: Any = None) -> dict[str, Any]:
11
+ return handle(event)
@@ -0,0 +1,24 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import { pythonLambdaCode } from "../shared/python-bundle.js";
5
+ /** AppSync gives a resolver 30 seconds; a read of one small table needs a few. */
6
+ export const MONITORING_VIEW_TIMEOUT_SECONDS = 15;
7
+ /** stdlib plus the runtime's boto3: the bundle is `papyrus_content` and nothing else. */
8
+ export const monitoringView = defineFunction((scope) => new Function(scope, "papyrus-monitoring-view", {
9
+ runtime: Runtime.PYTHON_3_12,
10
+ architecture: Architecture.ARM_64,
11
+ handler: "handler.handler",
12
+ timeout: Duration.seconds(MONITORING_VIEW_TIMEOUT_SECONDS),
13
+ memorySize: 256,
14
+ code: pythonLambdaCode({ functionDir: "monitoring-view", modules: ["papyrus_content"], noDeps: true }, [
15
+ "bash",
16
+ "-c",
17
+ [
18
+ "set -euo pipefail",
19
+ "mkdir -p /asset-output/papyrus_content",
20
+ "cp amplify/functions/monitoring-view/handler.py /asset-output/handler.py",
21
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
22
+ ].join(" && "),
23
+ ]),
24
+ }), { resourceGroupName: "data" });
@@ -0,0 +1,34 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import type { Construct } from "constructs";
5
+ import { pythonLambdaCode } from "../shared/python-bundle";
6
+
7
+ /** AppSync gives a resolver 30 seconds; a read of one small table needs a few. */
8
+ export const MONITORING_VIEW_TIMEOUT_SECONDS = 15;
9
+
10
+ /** stdlib plus the runtime's boto3: the bundle is `papyrus_content` and nothing else. */
11
+ export const monitoringView = defineFunction(
12
+ (scope: Construct) =>
13
+ new Function(scope, "papyrus-monitoring-view", {
14
+ runtime: Runtime.PYTHON_3_12,
15
+ architecture: Architecture.ARM_64,
16
+ handler: "handler.handler",
17
+ timeout: Duration.seconds(MONITORING_VIEW_TIMEOUT_SECONDS),
18
+ memorySize: 256,
19
+ code: pythonLambdaCode(
20
+ { functionDir: "monitoring-view", modules: ["papyrus_content"], noDeps: true },
21
+ [
22
+ "bash",
23
+ "-c",
24
+ [
25
+ "set -euo pipefail",
26
+ "mkdir -p /asset-output/papyrus_content",
27
+ "cp amplify/functions/monitoring-view/handler.py /asset-output/handler.py",
28
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
29
+ ].join(" && "),
30
+ ],
31
+ ),
32
+ }),
33
+ { resourceGroupName: "data" },
34
+ );
@@ -0,0 +1,16 @@
1
+ import { a } from "@aws-amplify/backend";
2
+ import { monitoringView } from "./resource.js";
3
+ const editors = ["editor", "admin"];
4
+ /**
5
+ * Core to every site's data API. The Monitoring page reads the shared monitoring pipeline through
6
+ * this one query. AppSync admits editors and admins only; the function checks the groups again,
7
+ * and returns the whole shared pipeline to admins alone.
8
+ */
9
+ export const monitoringViewSchemaDefinitions = {
10
+ monitoringOverview: a
11
+ .query()
12
+ .arguments({ scope: a.string() })
13
+ .returns(a.json())
14
+ .authorization((allow) => [allow.groups(editors)])
15
+ .handler(a.handler.function(monitoringView)),
16
+ };
@@ -0,0 +1,18 @@
1
+ import { a } from "@aws-amplify/backend";
2
+ import { monitoringView } from "./resource";
3
+
4
+ const editors = ["editor", "admin"];
5
+
6
+ /**
7
+ * Core to every site's data API. The Monitoring page reads the shared monitoring pipeline through
8
+ * this one query. AppSync admits editors and admins only; the function checks the groups again,
9
+ * and returns the whole shared pipeline to admins alone.
10
+ */
11
+ export const monitoringViewSchemaDefinitions = {
12
+ monitoringOverview: a
13
+ .query()
14
+ .arguments({ scope: a.string() })
15
+ .returns(a.json())
16
+ .authorization((allow) => [allow.groups(editors)])
17
+ .handler(a.handler.function(monitoringView)),
18
+ };
@@ -0,0 +1,37 @@
1
+ import { Stack } from "aws-cdk-lib";
2
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
3
+ /** The monitoring stack writes the table's name here (infra/monitoring/papyrus-monitoring.yaml). */
4
+ export const MONITORING_TABLE_PARAMETER = "/papyrus/monitoring/table-name";
5
+ /** Reads only: a single item or a key-range query. No Scan, no writes, no other table. */
6
+ export const MONITORING_VIEW_TABLE_ACTIONS = ["dynamodb:GetItem", "dynamodb:Query"];
7
+ /**
8
+ * The monitoring table is auto-named by CloudFormation (`papyrus-monitoring-MonitoringTable-<id>`), so
9
+ * the grant is the stack's table name prefix, not a wildcard on the account.
10
+ */
11
+ export function monitoringViewTableArns(region, account) {
12
+ const table = `arn:aws:dynamodb:${region}:${account}:table/papyrus-monitoring-MonitoringTable-*`;
13
+ return [table, `${table}/index/routing`];
14
+ }
15
+ export function monitoringViewParameterArn(region, account) {
16
+ return `arn:aws:ssm:${region}:${account}:parameter${MONITORING_TABLE_PARAMETER}`;
17
+ }
18
+ /**
19
+ * Environment and least-privilege IAM for the monitoring-view function. A site outside the monitoring
20
+ * account simply finds no parameter and the page says monitoring is not configured.
21
+ */
22
+ export function wireMonitoringView(backend) {
23
+ const lambda = backend.monitoringView.resources.lambda;
24
+ const stack = Stack.of(lambda);
25
+ lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl);
26
+ lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
27
+ lambda.addToRolePolicy(new PolicyStatement({
28
+ effect: Effect.ALLOW,
29
+ actions: ["ssm:GetParameter"],
30
+ resources: [monitoringViewParameterArn(stack.region, stack.account)],
31
+ }));
32
+ lambda.addToRolePolicy(new PolicyStatement({
33
+ effect: Effect.ALLOW,
34
+ actions: MONITORING_VIEW_TABLE_ACTIONS,
35
+ resources: monitoringViewTableArns(stack.region, stack.account),
36
+ }));
37
+ }
@@ -0,0 +1,47 @@
1
+ import { Stack } from "aws-cdk-lib";
2
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
3
+ import type { Function as LambdaFunction } from "aws-cdk-lib/aws-lambda";
4
+
5
+ /** The monitoring stack writes the table's name here (infra/monitoring/papyrus-monitoring.yaml). */
6
+ export const MONITORING_TABLE_PARAMETER = "/papyrus/monitoring/table-name";
7
+
8
+ /** Reads only: a single item or a key-range query. No Scan, no writes, no other table. */
9
+ export const MONITORING_VIEW_TABLE_ACTIONS = ["dynamodb:GetItem", "dynamodb:Query"];
10
+
11
+ /**
12
+ * The monitoring table is auto-named by CloudFormation (`papyrus-monitoring-MonitoringTable-<id>`), so
13
+ * the grant is the stack's table name prefix, not a wildcard on the account.
14
+ */
15
+ export function monitoringViewTableArns(region: string, account: string): string[] {
16
+ const table = `arn:aws:dynamodb:${region}:${account}:table/papyrus-monitoring-MonitoringTable-*`;
17
+ return [table, `${table}/index/routing`];
18
+ }
19
+
20
+ export function monitoringViewParameterArn(region: string, account: string): string {
21
+ return `arn:aws:ssm:${region}:${account}:parameter${MONITORING_TABLE_PARAMETER}`;
22
+ }
23
+
24
+ /**
25
+ * Environment and least-privilege IAM for the monitoring-view function. A site outside the monitoring
26
+ * account simply finds no parameter and the page says monitoring is not configured.
27
+ */
28
+ export function wireMonitoringView(backend: any): void {
29
+ const lambda = backend.monitoringView.resources.lambda as LambdaFunction;
30
+ const stack = Stack.of(lambda);
31
+ lambda.addEnvironment("PAPYRUS_GRAPHQL_ENDPOINT", backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl as string);
32
+ lambda.addEnvironment("PAPYRUS_MONITORING_TABLE_PARAMETER", MONITORING_TABLE_PARAMETER);
33
+ lambda.addToRolePolicy(
34
+ new PolicyStatement({
35
+ effect: Effect.ALLOW,
36
+ actions: ["ssm:GetParameter"],
37
+ resources: [monitoringViewParameterArn(stack.region, stack.account)],
38
+ }),
39
+ );
40
+ lambda.addToRolePolicy(
41
+ new PolicyStatement({
42
+ effect: Effect.ALLOW,
43
+ actions: MONITORING_VIEW_TABLE_ACTIONS,
44
+ resources: monitoringViewTableArns(stack.region, stack.account),
45
+ }),
46
+ );
47
+ }
@@ -45,6 +45,49 @@ function lookUpPatternRedirect(uri) {
45
45
  return null;
46
46
  }
47
47
 
48
+ // Content negotiation for agents: when the site publishes Markdown twins (standard front end, agents.markdown), a request
49
+ // whose Accept header prefers text/markdown over text/html is rewritten to the .md object before the cache lookup. The
50
+ // flag is spliced in by redirect-rules.ts at deploy time.
51
+ const negotiateMarkdown = /*PAPYRUS_NEGOTIATE_MARKDOWN*/false/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/;
52
+
53
+ function qualityOf(accept, mediaType) {
54
+ let quality = 0;
55
+ const parts = accept.toLowerCase().split(",");
56
+ for (let index = 0; index < parts.length; index += 1) {
57
+ const fields = parts[index].split(";");
58
+ if (fields[0].trim() !== mediaType) {
59
+ continue;
60
+ }
61
+ quality = 1;
62
+ for (let field = 1; field < fields.length; field += 1) {
63
+ const pair = fields[field].trim();
64
+ if (pair.indexOf("q=") === 0) {
65
+ quality = Number(pair.substring(2));
66
+ if (isNaN(quality)) {
67
+ quality = 0;
68
+ }
69
+ }
70
+ }
71
+ }
72
+ return quality;
73
+ }
74
+
75
+ function prefersMarkdown(request) {
76
+ const header = request.headers && request.headers.accept;
77
+ if (!header || !header.value) {
78
+ return false;
79
+ }
80
+ const markdown = qualityOf(header.value, "text/markdown");
81
+ return markdown > 0 && markdown > qualityOf(header.value, "text/html");
82
+ }
83
+
84
+ function markdownTwin(uri) {
85
+ if (uri === "/404.html" || uri.length < 6 || uri.substring(uri.length - 5) !== ".html") {
86
+ return null;
87
+ }
88
+ return uri.substring(0, uri.length - 5) + ".md";
89
+ }
90
+
48
91
  async function handler(event) {
49
92
  const request = event.request;
50
93
  const redirectTarget = await lookUpRedirectTarget(request.uri);
@@ -64,5 +107,11 @@ async function handler(event) {
64
107
  };
65
108
  }
66
109
  request.uri = rewriteToIndexDocument(request.uri);
110
+ if (negotiateMarkdown && prefersMarkdown(request)) {
111
+ const twin = markdownTwin(request.uri);
112
+ if (twin !== null) {
113
+ request.uri = twin;
114
+ }
115
+ }
67
116
  return request;
68
117
  }
@@ -18,23 +18,39 @@ export function edgeRulesFromSettings(settings) {
18
18
  }
19
19
  return rules;
20
20
  }
21
- export function readEdgeRules(staticDir, baseDirectory = process.cwd()) {
21
+ /** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
22
+ export function negotiatesMarkdown(settings) {
23
+ return settings.site !== undefined && settings.agents?.markdown !== false;
24
+ }
25
+ export function readEdgeConfig(staticDir, baseDirectory = process.cwd()) {
22
26
  if (staticDir === undefined)
23
- return [];
27
+ return { rules: [], negotiateMarkdown: false };
24
28
  const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
25
29
  if (!existsSync(settingsPath))
26
- return [];
27
- return edgeRulesFromSettings(JSON.parse(readFileSync(settingsPath, "utf8")));
30
+ return { rules: [], negotiateMarkdown: false };
31
+ const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
32
+ return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
28
33
  }
29
- /** The CloudFront Function source with the site's pattern rules spliced in; the source unchanged when there are none. */
30
- export function compileRedirectFunctionSource(source, rules) {
34
+ export function readEdgeRules(staticDir, baseDirectory = process.cwd()) {
35
+ return readEdgeConfig(staticDir, baseDirectory).rules;
36
+ }
37
+ const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
38
+ const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
39
+ /** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
40
+ export function compileRedirectFunctionSource(source, rules, options = {}) {
31
41
  const start = source.indexOf(START_MARKER);
32
42
  const end = source.indexOf(END_MARKER);
33
43
  if (start === -1 || end === -1 || end < start) {
34
44
  throw new Error("redirect function source has no pattern-rule markers");
35
45
  }
36
- if (rules.length === 0)
37
- return source;
38
- const literal = JSON.stringify(rules);
39
- return source.slice(0, start + START_MARKER.length) + literal + source.slice(end);
46
+ let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
47
+ if (options.negotiateMarkdown === true) {
48
+ const from = compiled.indexOf(NEGOTIATE_START);
49
+ const to = compiled.indexOf(NEGOTIATE_END);
50
+ if (from === -1 || to === -1 || to < from) {
51
+ throw new Error("redirect function source has no markdown negotiation markers");
52
+ }
53
+ compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
54
+ }
55
+ return compiled;
40
56
  }
@@ -8,7 +8,7 @@ const START_MARKER = "/*PAPYRUS_PATTERN_RULES*/";
8
8
  const END_MARKER = "/*END_PAPYRUS_PATTERN_RULES*/";
9
9
 
10
10
  type ProjectionRedirects = { patterns?: { pattern: string; to: string; status?: number; except?: string[] }[] };
11
- type ProjectionSettings = { urls?: { style?: string }; redirects?: ProjectionRedirects };
11
+ type ProjectionSettings = { site?: unknown; urls?: { style?: string }; redirects?: ProjectionRedirects; agents?: { markdown?: boolean } };
12
12
 
13
13
  /**
14
14
  * The pattern rules a site declares in projection.json: its `redirects.patterns`, plus, for
@@ -27,21 +27,47 @@ export function edgeRulesFromSettings(settings: ProjectionSettings): EdgeRedirec
27
27
  return rules;
28
28
  }
29
29
 
30
- export function readEdgeRules(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeRedirectRule[] {
31
- if (staticDir === undefined) return [];
30
+ /** A standard-front-end site publishes Markdown twins unless it turns `agents.markdown` off. */
31
+ export function negotiatesMarkdown(settings: ProjectionSettings): boolean {
32
+ return settings.site !== undefined && settings.agents?.markdown !== false;
33
+ }
34
+
35
+ export type EdgeConfig = { rules: EdgeRedirectRule[]; negotiateMarkdown: boolean };
36
+
37
+ export function readEdgeConfig(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeConfig {
38
+ if (staticDir === undefined) return { rules: [], negotiateMarkdown: false };
32
39
  const settingsPath = resolve(baseDirectory, staticDir, "projection.json");
33
- if (!existsSync(settingsPath)) return [];
34
- return edgeRulesFromSettings(JSON.parse(readFileSync(settingsPath, "utf8")) as ProjectionSettings);
40
+ if (!existsSync(settingsPath)) return { rules: [], negotiateMarkdown: false };
41
+ const settings = JSON.parse(readFileSync(settingsPath, "utf8")) as ProjectionSettings;
42
+ return { rules: edgeRulesFromSettings(settings), negotiateMarkdown: negotiatesMarkdown(settings) };
35
43
  }
36
44
 
37
- /** The CloudFront Function source with the site's pattern rules spliced in; the source unchanged when there are none. */
38
- export function compileRedirectFunctionSource(source: string, rules: EdgeRedirectRule[]): string {
45
+ export function readEdgeRules(staticDir: string | undefined, baseDirectory: string = process.cwd()): EdgeRedirectRule[] {
46
+ return readEdgeConfig(staticDir, baseDirectory).rules;
47
+ }
48
+
49
+ const NEGOTIATE_START = "/*PAPYRUS_NEGOTIATE_MARKDOWN*/";
50
+ const NEGOTIATE_END = "/*END_PAPYRUS_NEGOTIATE_MARKDOWN*/";
51
+
52
+ /** The CloudFront Function source with the site's pattern rules and Markdown negotiation flag spliced in. */
53
+ export function compileRedirectFunctionSource(
54
+ source: string,
55
+ rules: EdgeRedirectRule[],
56
+ options: { negotiateMarkdown?: boolean } = {},
57
+ ): string {
39
58
  const start = source.indexOf(START_MARKER);
40
59
  const end = source.indexOf(END_MARKER);
41
60
  if (start === -1 || end === -1 || end < start) {
42
61
  throw new Error("redirect function source has no pattern-rule markers");
43
62
  }
44
- if (rules.length === 0) return source;
45
- const literal = JSON.stringify(rules);
46
- return source.slice(0, start + START_MARKER.length) + literal + source.slice(end);
63
+ let compiled = rules.length === 0 ? source : source.slice(0, start + START_MARKER.length) + JSON.stringify(rules) + source.slice(end);
64
+ if (options.negotiateMarkdown === true) {
65
+ const from = compiled.indexOf(NEGOTIATE_START);
66
+ const to = compiled.indexOf(NEGOTIATE_END);
67
+ if (from === -1 || to === -1 || to < from) {
68
+ throw new Error("redirect function source has no markdown negotiation markers");
69
+ }
70
+ compiled = compiled.slice(0, from + NEGOTIATE_START.length) + "true" + compiled.slice(to);
71
+ }
72
+ return compiled;
47
73
  }
@@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url";
13
13
  import { assertValidPublicationConfig } from "../site-backend-config.js";
14
14
  import { addRenditionsFunction } from "./renditions-function.js";
15
15
  import { RENDITIONS_FUNCTION_NAME_ENV } from "./renditions-settings.js";
16
- import { compileRedirectFunctionSource, readEdgeRules } from "./redirect-rules.js";
16
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules.js";
17
17
  import { addInvalidationPathAlarm } from "./invalidation-alarm.js";
18
18
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings.js";
19
19
  const redirectFunctionSourcePath = resolve(dirname(fileURLToPath(import.meta.url)), "redirect-and-index.js");
@@ -72,7 +72,9 @@ export function addS3ProjectionPublication(scope, props, media) {
72
72
  const keyValueStore = new cloudfront.KeyValueStore(scope, "RedirectsKeyValueStore");
73
73
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
74
74
  runtime: cloudfront.FunctionRuntime.JS_2_0,
75
- code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeRules(props.staticDir))),
75
+ code: cloudfront.FunctionCode.fromInline(compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeConfig(props.staticDir).rules, {
76
+ negotiateMarkdown: readEdgeConfig(props.staticDir).negotiateMarkdown,
77
+ })),
76
78
  keyValueStore,
77
79
  });
78
80
  const deadLetterQueue = new sqs.Queue(scope, "ProjectionDeadLetterQueue", {
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  import { assertValidPublicationConfig, type PapyrusPublicationConfig } from "../site-backend-config";
17
17
  import { addRenditionsFunction, type RenditionsFunction } from "./renditions-function";
18
18
  import { RENDITIONS_FUNCTION_NAME_ENV, type RenditionsPlan } from "./renditions-settings";
19
- import { compileRedirectFunctionSource, readEdgeRules } from "./redirect-rules";
19
+ import { compileRedirectFunctionSource, readEdgeConfig } from "./redirect-rules";
20
20
  import { addInvalidationPathAlarm } from "./invalidation-alarm";
21
21
  import { distributionIdFromArn, s3ProjectionEnvironment, s3ProjectionPolicyStatements } from "./s3-projection-settings";
22
22
 
@@ -115,7 +115,9 @@ export function addS3ProjectionPublication(
115
115
  const redirectFunction = new cloudfront.Function(scope, "RedirectAndIndexFunction", {
116
116
  runtime: cloudfront.FunctionRuntime.JS_2_0,
117
117
  code: cloudfront.FunctionCode.fromInline(
118
- compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeRules(props.staticDir)),
118
+ compileRedirectFunctionSource(readFileSync(redirectFunctionSourcePath, "utf8"), readEdgeConfig(props.staticDir).rules, {
119
+ negotiateMarkdown: readEdgeConfig(props.staticDir).negotiateMarkdown,
120
+ }),
119
121
  ),
120
122
  keyValueStore,
121
123
  });
@@ -21,6 +21,8 @@ import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRe
21
21
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client.js";
22
22
  import { defineSiteData } from "./data/resource.js";
23
23
  import { editorialChecks } from "./functions/editorial-checks/resource.js";
24
+ import { monitoringView } from "./functions/monitoring-view/resource.js";
25
+ import { wireMonitoringView } from "./functions/monitoring-view/wire.js";
24
26
  import { wireEditorialChecks } from "./functions/editorial-checks/wire.js";
25
27
  import { resolvePlugins } from "./plugins/registry.js";
26
28
  import { wirePlugins } from "./plugins/wire.js";
@@ -95,6 +97,7 @@ export function defineSiteBackend(papyrusSite) {
95
97
  data: defineSiteData(pluginDefinitions),
96
98
  knowledgeQuery,
97
99
  manageUserRole,
100
+ monitoringView,
98
101
  modelAttachmentUpload,
99
102
  newsroomSummary,
100
103
  procedureAction,
@@ -598,6 +601,7 @@ export function defineSiteBackend(papyrusSite) {
598
601
  },
599
602
  });
600
603
  wireEditorialChecks(backend);
604
+ wireMonitoringView(backend);
601
605
  wirePlugins(backend, plugins, contentActionsLambda);
602
606
  return backend;
603
607
  }
@@ -28,6 +28,8 @@ import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth, siteAuthRe
28
28
  import { PROSPECTUS_SIGN_IN_CALLBACK_PATH, addProspectusGoogleOnlyClient } from "./auth/prospectus-google-client";
29
29
  import { defineSiteData } from "./data/resource";
30
30
  import { editorialChecks } from "./functions/editorial-checks/resource";
31
+ import { monitoringView } from "./functions/monitoring-view/resource";
32
+ import { wireMonitoringView } from "./functions/monitoring-view/wire";
31
33
  import { wireEditorialChecks } from "./functions/editorial-checks/wire";
32
34
  import { resolvePlugins } from "./plugins/registry";
33
35
  import { wirePlugins } from "./plugins/wire";
@@ -116,6 +118,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
116
118
  data: defineSiteData(pluginDefinitions),
117
119
  knowledgeQuery,
118
120
  manageUserRole,
121
+ monitoringView,
119
122
  modelAttachmentUpload,
120
123
  newsroomSummary,
121
124
  procedureAction,
@@ -741,6 +744,7 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
741
744
  },
742
745
  });
743
746
  wireEditorialChecks(backend);
747
+ wireMonitoringView(backend);
744
748
  wirePlugins(backend, plugins, contentActionsLambda);
745
749
 
746
750
  return backend;