@anthusai/papyrus 1.0.0-next.43 → 1.0.0-next.45
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/functions/content-actions/handler.py +54 -1
- package/amplify/functions/content-actions/requirements.txt +2 -1
- package/amplify/functions/content-actions/resource.js +6 -1
- package/amplify/functions/content-actions/resource.ts +9 -2
- package/amplify/functions/renditions/handler.py +94 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- package/amplify/functions/shared/python-bundle.js +42 -2
- package/amplify/functions/shared/python-bundle.ts +47 -2
- package/amplify/publication/redirect-and-index.js +37 -0
- package/amplify/publication/renditions-function.js +74 -0
- package/amplify/publication/renditions-function.ts +112 -0
- package/amplify/publication/renditions-settings.js +43 -0
- package/amplify/publication/renditions-settings.ts +50 -0
- package/amplify/publication/s3-projection-settings.js +63 -0
- package/amplify/publication/s3-projection-settings.ts +98 -0
- package/amplify/publication/s3-projection.js +126 -0
- package/amplify/publication/s3-projection.ts +174 -0
- package/amplify/site-backend-config.js +51 -1
- package/amplify/site-backend-config.ts +76 -0
- package/amplify/site-backend.js +27 -3
- package/amplify/site-backend.ts +27 -4
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
|
@@ -22,6 +22,80 @@ export type PapyrusAuthConfig = {
|
|
|
22
22
|
applyCognitoDomainPrefix?: boolean;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
export type S3ProjectionDistributionConfig = {
|
|
26
|
+
create: boolean;
|
|
27
|
+
aliases?: string[];
|
|
28
|
+
certificateArn?: string;
|
|
29
|
+
acm?: { hostedZoneId: string; hostedZoneName: string };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type PapyrusPublicationConfig = {
|
|
33
|
+
kind: "s3-projection";
|
|
34
|
+
/** Site repo path (relative, `.py`) of the SiteProjector bundled into content-actions as `site_projector`. */
|
|
35
|
+
projector?: string;
|
|
36
|
+
/** Site repo directory (relative) bundled into content-actions as `site_static/`. */
|
|
37
|
+
staticDir?: string;
|
|
38
|
+
/** Existing CloudFront distribution that reads the site bucket through OAC. */
|
|
39
|
+
edgeDistributionArn?: string;
|
|
40
|
+
/** Create the distribution (and optionally its certificate) in the site backend. Default: not created. */
|
|
41
|
+
distribution?: S3ProjectionDistributionConfig;
|
|
42
|
+
/** Default true: the site bucket is retained when the stack is deleted. `false` destroys it and its objects. */
|
|
43
|
+
retainBucketOnDelete?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CLOUDFRONT_DISTRIBUTION_ARN = /^arn:aws[a-z-]*:cloudfront::\d{12}:distribution\/[A-Z0-9]+$/;
|
|
47
|
+
|
|
48
|
+
function isRelativePathWithoutParentSegments(value: string): boolean {
|
|
49
|
+
if (value === "" || value.startsWith("/") || value.includes("\\")) return false;
|
|
50
|
+
return !value.split("/").includes("..");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function validatePublicationConfig(publication: unknown): string[] {
|
|
54
|
+
const problems: string[] = [];
|
|
55
|
+
if (typeof publication !== "object" || publication === null) {
|
|
56
|
+
return ["publication must be an object"];
|
|
57
|
+
}
|
|
58
|
+
const config = publication as Partial<PapyrusPublicationConfig> & { kind?: unknown };
|
|
59
|
+
if (config.kind !== "s3-projection") {
|
|
60
|
+
problems.push(`publication.kind must be "s3-projection" (got ${JSON.stringify(config.kind)})`);
|
|
61
|
+
}
|
|
62
|
+
if (config.projector !== undefined) {
|
|
63
|
+
if (typeof config.projector !== "string" || !isRelativePathWithoutParentSegments(config.projector) || !config.projector.endsWith(".py")) {
|
|
64
|
+
problems.push("publication.projector must be a relative .py path without '..'");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (config.staticDir !== undefined) {
|
|
68
|
+
if (typeof config.staticDir !== "string" || !isRelativePathWithoutParentSegments(config.staticDir)) {
|
|
69
|
+
problems.push("publication.staticDir must be a relative path without '..'");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (config.edgeDistributionArn !== undefined && !CLOUDFRONT_DISTRIBUTION_ARN.test(config.edgeDistributionArn)) {
|
|
73
|
+
problems.push("publication.edgeDistributionArn must be a CloudFront distribution ARN (arn:aws:cloudfront::<account>:distribution/<id>)");
|
|
74
|
+
}
|
|
75
|
+
const distribution = config.distribution;
|
|
76
|
+
if (distribution !== undefined) {
|
|
77
|
+
if (config.edgeDistributionArn !== undefined && distribution.create === true) {
|
|
78
|
+
problems.push("publication.edgeDistributionArn and publication.distribution.create are mutually exclusive");
|
|
79
|
+
}
|
|
80
|
+
const aliases = distribution.aliases ?? [];
|
|
81
|
+
if (aliases.length > 0 && distribution.create !== true) {
|
|
82
|
+
problems.push("publication.distribution.aliases require distribution.create to be true");
|
|
83
|
+
}
|
|
84
|
+
if (aliases.length > 0 && !distribution.certificateArn && !distribution.acm) {
|
|
85
|
+
problems.push("publication.distribution.aliases require certificateArn or acm");
|
|
86
|
+
}
|
|
87
|
+
if (distribution.acm !== undefined && (!distribution.acm.hostedZoneId || !distribution.acm.hostedZoneName)) {
|
|
88
|
+
problems.push("publication.distribution.acm requires hostedZoneId and hostedZoneName");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return problems;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function assertValidPublicationConfig(publication: unknown): void {
|
|
95
|
+
const problems = validatePublicationConfig(publication);
|
|
96
|
+
if (problems.length > 0) throw new Error(`Invalid backend.publication: ${problems.join("; ")}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
25
99
|
export type PapyrusSiteBackendConfig = {
|
|
26
100
|
/** Brand id; namespaces account-global resources (S3 Vectors index name). */
|
|
27
101
|
brandId?: string;
|
|
@@ -56,6 +130,8 @@ export type PapyrusSiteBackendConfig = {
|
|
|
56
130
|
/** Let this stack verify the SES domain identity and write its TXT record. Default false: verify once by hand. */
|
|
57
131
|
manageDomainIdentity?: boolean;
|
|
58
132
|
};
|
|
133
|
+
/** Opt-in static projection of published pages into an S3 site bucket. Unset: nothing is created. */
|
|
134
|
+
publication?: PapyrusPublicationConfig;
|
|
59
135
|
features?: {
|
|
60
136
|
consoleResponder?: boolean;
|
|
61
137
|
inboundEmail?: boolean;
|
package/amplify/site-backend.js
CHANGED
|
@@ -7,14 +7,18 @@ import { CfnIndex, CfnVectorBucket, CfnVectorBucketPolicy } from "aws-cdk-lib/aw
|
|
|
7
7
|
import { dirname, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { DEFAULT_RESOLVER_WAVE_SIZE, orderModelStacksInWaves } from "./resolver-waves.js";
|
|
10
|
+
import { addS3ProjectionPublication } from "./publication/s3-projection.js";
|
|
11
|
+
import { planRenditions } from "./publication/renditions-settings.js";
|
|
12
|
+
import { contentActionsPublicationWiring, planS3ProjectionBackend } from "./publication/s3-projection-settings.js";
|
|
10
13
|
import { rebuildTriggerSettings } from "./rebuild-trigger-settings.js";
|
|
14
|
+
import { assertValidPublicationConfig } from "./site-backend-config.js";
|
|
11
15
|
import { revalidateSecretReadStatement } from "./revalidate-secret-policy.js";
|
|
12
16
|
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth } from "./auth/resource.js";
|
|
13
17
|
import { data } from "./data/resource.js";
|
|
14
18
|
import { assignmentAction } from "./functions/assignment-action/resource.js";
|
|
15
19
|
import { categoryAction } from "./functions/category-action/resource.js";
|
|
16
20
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource.js";
|
|
17
|
-
import { contentActions } from "./functions/content-actions/resource.js";
|
|
21
|
+
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource.js";
|
|
18
22
|
import { knowledgeQuery } from "./functions/knowledge-query/resource.js";
|
|
19
23
|
import { manageUserRole } from "./functions/manage-user-role/resource.js";
|
|
20
24
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource.js";
|
|
@@ -62,6 +66,11 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
62
66
|
...baseAuthConfig,
|
|
63
67
|
newsroomBasePath: baseAuthConfig.newsroomBasePath ?? activeBrand?.newsroomBasePath,
|
|
64
68
|
};
|
|
69
|
+
if (site.publication !== undefined)
|
|
70
|
+
assertValidPublicationConfig(site.publication);
|
|
71
|
+
const publicationPlan = planS3ProjectionBackend(site.publication);
|
|
72
|
+
const renditionsPlan = planRenditions(site.publication, process.cwd());
|
|
73
|
+
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
65
74
|
const backend = defineBackend({
|
|
66
75
|
assignmentAction,
|
|
67
76
|
auth: defineSiteAuth(authConfig),
|
|
@@ -217,8 +226,8 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
217
226
|
let storageBackupPlan;
|
|
218
227
|
let storageBackupVault;
|
|
219
228
|
const storageBucketCfn = storageBucket.node.defaultChild;
|
|
220
|
-
if ((enableStorageBackups || enableInboundEmail) && storageBucketCfn) {
|
|
221
|
-
// Backups
|
|
229
|
+
if ((enableStorageBackups || enableInboundEmail || renditionsPlan.enabled) && storageBucketCfn) {
|
|
230
|
+
// Backups, inbound-email intake and upload-time renditions all rely on S3 → EventBridge notifications.
|
|
222
231
|
storageBucketCfn.addPropertyOverride("NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled", true);
|
|
223
232
|
}
|
|
224
233
|
if (enableStorageBackups) {
|
|
@@ -302,6 +311,21 @@ export function defineSiteBackend(papyrusSite) {
|
|
|
302
311
|
if (rebuildSettings.resources.length > 0) {
|
|
303
312
|
contentActionsLambda.addToRolePolicy(new PolicyStatement({ actions: rebuildSettings.actions, resources: rebuildSettings.resources }));
|
|
304
313
|
}
|
|
314
|
+
let s3ProjectionPublication;
|
|
315
|
+
if (site.publication !== undefined) {
|
|
316
|
+
s3ProjectionPublication = addS3ProjectionPublication(backend.createStack("s3-projection"), site.publication, {
|
|
317
|
+
mediaBucket: storageBucket,
|
|
318
|
+
renditions: renditionsPlan,
|
|
319
|
+
});
|
|
320
|
+
contentActionsLambda.node.defaultChild.memorySize = publicationPlan.contentActionsMemorySize;
|
|
321
|
+
}
|
|
322
|
+
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
323
|
+
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
324
|
+
contentActionsLambda.addEnvironment(name, value);
|
|
325
|
+
}
|
|
326
|
+
for (const statement of publicationWiring.policyStatements) {
|
|
327
|
+
contentActionsLambda.addToRolePolicy(statement);
|
|
328
|
+
}
|
|
305
329
|
if ((site.revalidateBaseUrl ?? "").trim() !== "") {
|
|
306
330
|
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_BASE_URL", (site.revalidateBaseUrl ?? "").trim());
|
|
307
331
|
const revalidateSecretParameter = (process.env.PAPYRUS_REVALIDATE_SECRET_PARAMETER ?? "").trim();
|
package/amplify/site-backend.ts
CHANGED
|
@@ -3,21 +3,25 @@ import { Stack } from "aws-cdk-lib";
|
|
|
3
3
|
import type * as backup from "aws-cdk-lib/aws-backup";
|
|
4
4
|
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
5
5
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
6
|
-
import { CfnEventSourceMapping, Function as LambdaFunction, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { CfnEventSourceMapping, CfnFunction, Function as LambdaFunction, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
|
|
7
7
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
8
8
|
import { CfnIndex, CfnVectorBucket, CfnVectorBucketPolicy } from "aws-cdk-lib/aws-s3vectors";
|
|
9
9
|
import type { Construct } from "constructs";
|
|
10
10
|
import { dirname, resolve } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { DEFAULT_RESOLVER_WAVE_SIZE, orderModelStacksInWaves } from "./resolver-waves";
|
|
13
|
+
import { addS3ProjectionPublication, type S3ProjectionPublication } from "./publication/s3-projection";
|
|
14
|
+
import { planRenditions } from "./publication/renditions-settings";
|
|
15
|
+
import { contentActionsPublicationWiring, planS3ProjectionBackend } from "./publication/s3-projection-settings";
|
|
13
16
|
import { rebuildTriggerSettings } from "./rebuild-trigger-settings";
|
|
17
|
+
import { assertValidPublicationConfig } from "./site-backend-config";
|
|
14
18
|
import { revalidateSecretReadStatement } from "./revalidate-secret-policy";
|
|
15
19
|
import { applyCognitoDomainPrefix, authConfigFromEnv, defineSiteAuth } from "./auth/resource";
|
|
16
20
|
import { data } from "./data/resource";
|
|
17
21
|
import { assignmentAction } from "./functions/assignment-action/resource";
|
|
18
22
|
import { categoryAction } from "./functions/category-action/resource";
|
|
19
23
|
import { ConsoleChatResponderStack } from "./functions/console-chat-responder/resource";
|
|
20
|
-
import { contentActions } from "./functions/content-actions/resource";
|
|
24
|
+
import { contentActions, setSiteFilesForContentActionsBundle } from "./functions/content-actions/resource";
|
|
21
25
|
import { knowledgeQuery } from "./functions/knowledge-query/resource";
|
|
22
26
|
import { manageUserRole } from "./functions/manage-user-role/resource";
|
|
23
27
|
import { modelAttachmentUpload } from "./functions/model-attachment-upload/resource";
|
|
@@ -80,6 +84,10 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
80
84
|
...baseAuthConfig,
|
|
81
85
|
newsroomBasePath: baseAuthConfig.newsroomBasePath ?? activeBrand?.newsroomBasePath,
|
|
82
86
|
};
|
|
87
|
+
if (site.publication !== undefined) assertValidPublicationConfig(site.publication);
|
|
88
|
+
const publicationPlan = planS3ProjectionBackend(site.publication);
|
|
89
|
+
const renditionsPlan = planRenditions(site.publication, process.cwd());
|
|
90
|
+
setSiteFilesForContentActionsBundle(publicationPlan.enabled ? publicationPlan.siteFiles : undefined);
|
|
83
91
|
const backend = defineBackend({
|
|
84
92
|
assignmentAction,
|
|
85
93
|
auth: defineSiteAuth(authConfig),
|
|
@@ -279,8 +287,8 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
279
287
|
let storageBackupVault: backup.BackupVault | undefined;
|
|
280
288
|
|
|
281
289
|
const storageBucketCfn = storageBucket.node.defaultChild as s3.CfnBucket | undefined;
|
|
282
|
-
if ((enableStorageBackups || enableInboundEmail) && storageBucketCfn) {
|
|
283
|
-
// Backups
|
|
290
|
+
if ((enableStorageBackups || enableInboundEmail || renditionsPlan.enabled) && storageBucketCfn) {
|
|
291
|
+
// Backups, inbound-email intake and upload-time renditions all rely on S3 → EventBridge notifications.
|
|
284
292
|
storageBucketCfn.addPropertyOverride(
|
|
285
293
|
"NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled",
|
|
286
294
|
true,
|
|
@@ -388,6 +396,21 @@ export function defineSiteBackend(papyrusSite: PapyrusSite) {
|
|
|
388
396
|
new PolicyStatement({ actions: rebuildSettings.actions, resources: rebuildSettings.resources }),
|
|
389
397
|
);
|
|
390
398
|
}
|
|
399
|
+
let s3ProjectionPublication: S3ProjectionPublication | undefined;
|
|
400
|
+
if (site.publication !== undefined) {
|
|
401
|
+
s3ProjectionPublication = addS3ProjectionPublication(backend.createStack("s3-projection"), site.publication, {
|
|
402
|
+
mediaBucket: storageBucket,
|
|
403
|
+
renditions: renditionsPlan,
|
|
404
|
+
});
|
|
405
|
+
(contentActionsLambda.node.defaultChild as CfnFunction).memorySize = publicationPlan.contentActionsMemorySize;
|
|
406
|
+
}
|
|
407
|
+
const publicationWiring = contentActionsPublicationWiring(s3ProjectionPublication);
|
|
408
|
+
for (const [name, value] of Object.entries(publicationWiring.environment)) {
|
|
409
|
+
contentActionsLambda.addEnvironment(name, value);
|
|
410
|
+
}
|
|
411
|
+
for (const statement of publicationWiring.policyStatements) {
|
|
412
|
+
contentActionsLambda.addToRolePolicy(statement);
|
|
413
|
+
}
|
|
391
414
|
if ((site.revalidateBaseUrl ?? "").trim() !== "") {
|
|
392
415
|
backend.contentActions.addEnvironment("PAPYRUS_REVALIDATE_BASE_URL", (site.revalidateBaseUrl ?? "").trim());
|
|
393
416
|
const revalidateSecretParameter = (process.env.PAPYRUS_REVALIDATE_SECRET_PARAMETER ?? "").trim();
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED