@anthusai/papyrus 1.0.0-next.9 → 1.0.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/auth/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +20 -5
- package/amplify/auth/resource.ts +27 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -60
- package/amplify/data/schema.ts +47 -60
- package/amplify/functions/category-action/handler.ts +5 -3
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
- package/amplify/functions/console-chat-responder/resource.js +0 -7
- package/amplify/functions/console-chat-responder/resource.ts +0 -8
- package/amplify/functions/console-chat-responder/src/main.rs +0 -9
- package/amplify/functions/content-actions/handler.py +143 -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 +120 -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/functions/shared/reference-curation-metadata.ts +29 -0
- package/amplify/inbound-email/ses-intake.js +86 -0
- package/amplify/inbound-email/ses-intake.ts +110 -0
- package/amplify/plugins/registry.js +19 -0
- package/amplify/plugins/registry.ts +23 -0
- package/amplify/plugins/signup/function/handler.py +200 -0
- package/amplify/plugins/signup/function/resource.js +27 -0
- package/amplify/plugins/signup/function/resource.ts +35 -0
- package/amplify/plugins/signup/papyrus.plugin.js +59 -0
- package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
- package/amplify/plugins/signup/schema.js +40 -0
- package/amplify/plugins/signup/schema.ts +41 -0
- package/amplify/plugins/signup/waf.js +48 -0
- package/amplify/plugins/signup/waf.ts +51 -0
- package/amplify/plugins/types.js +1 -0
- package/amplify/plugins/types.ts +39 -0
- package/amplify/plugins/wire.js +24 -0
- package/amplify/plugins/wire.ts +26 -0
- package/amplify/publication/default-publication.js +18 -0
- package/amplify/publication/default-publication.ts +20 -0
- package/amplify/publication/invalidation-alarm.js +26 -0
- package/amplify/publication/invalidation-alarm.ts +30 -0
- 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 +51 -0
- package/amplify/publication/renditions-settings.ts +58 -0
- package/amplify/publication/reproject-on-deploy.js +73 -0
- package/amplify/publication/reproject-on-deploy.ts +78 -0
- package/amplify/publication/s3-projection-settings.js +67 -0
- package/amplify/publication/s3-projection-settings.ts +101 -0
- package/amplify/publication/s3-projection.js +151 -0
- package/amplify/publication/s3-projection.ts +201 -0
- package/amplify/resolver-waves.js +26 -0
- package/amplify/resolver-waves.ts +34 -0
- package/amplify/revalidate-invalidation-settings.js +30 -0
- package/amplify/revalidate-invalidation-settings.ts +35 -0
- package/amplify/revalidate-secret-policy.js +13 -0
- package/amplify/revalidate-secret-policy.ts +19 -0
- package/amplify/site-backend-config.js +65 -1
- package/amplify/site-backend-config.ts +153 -2
- package/amplify/site-backend-names.js +103 -0
- package/amplify/site-backend-names.ts +166 -0
- package/amplify/site-backend.js +143 -176
- package/amplify/site-backend.ts +181 -201
- package/amplify/storage-backups/construct.js +31 -0
- package/amplify/storage-backups/construct.ts +47 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
- package/app/[year]/[month]/[day]/page.tsx +1 -1
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
- package/app/api/archive/editions/route.ts +1 -1
- package/app/api/cyclotron/priming-proof/route.ts +11 -0
- package/app/api/media/[...path]/route.ts +5 -16
- package/app/api/revalidate/route.ts +15 -7
- package/app/archive/page.tsx +1 -1
- package/app/articles/[slug]/page.tsx +7 -5
- package/app/blog-rhythm.css +278 -0
- package/app/cyclotron/page.tsx +13 -0
- package/app/globals.css +2 -0
- package/app/layout.tsx +16 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
- package/app/newsroom/[section]/page.tsx +6 -3
- package/app/newsroom/layout.tsx +4 -9
- package/app/newsroom/settings/page.tsx +6 -0
- package/app/newsroom/work/page.tsx +10 -0
- package/app/page.tsx +22 -8
- package/app/robots.ts +2 -5
- package/app/tailwind.css +1 -0
- package/components/amplify-client-provider.tsx +5 -22
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +152 -5
- package/components/blog-page-background.tsx +23 -6
- package/components/news-desk-page.tsx +4 -15
- package/components/news-desk-taxonomy-client.ts +90 -3
- package/components/newspaper.tsx +6 -2
- package/components/newsroom-access-gate.tsx +2 -1
- package/components/newsroom-board-view.tsx +99 -0
- package/components/newsroom-client-shell.tsx +12 -0
- package/components/newsroom-curation-provider.tsx +17 -0
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
- package/components/newsroom-newsprint-board.css +48 -0
- package/components/newsroom-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +7 -1
- package/components/newsroom-organizer-shell.tsx +130 -0
- package/components/newsroom-organizer.css +63 -0
- package/components/newsroom-references-view.tsx +92 -8
- package/components/papyrus-console-shell.tsx +4 -2
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/priming-proof-page.tsx +92 -0
- package/components/publication-cyclotron-panel.tsx +57 -0
- package/components/reader-auth-control.tsx +8 -5
- package/components/reference-curation-controls.tsx +77 -0
- package/components/reference-cyclotron-status.tsx +109 -0
- package/components/reference-priming.tsx +18 -0
- package/components/reference-relevance-review.tsx +75 -0
- package/components/settings-page.tsx +2 -1
- package/components/signup-entries-panel.tsx +94 -0
- package/components/topic-steering-workspace.tsx +78 -6
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-near-viewport.ts +30 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +148 -27
- package/infra/amplify-app-shell.ts +160 -31
- package/infra/build-specs.js +23 -7
- package/infra/build-specs.ts +27 -10
- package/infra/site-config.js +248 -36
- package/infra/site-config.ts +313 -51
- package/lib/amplify-outputs-path.ts +5 -1
- package/lib/amplify-server-runtime.ts +36 -1
- package/lib/approved-priming-proof-reports.ts +6 -0
- package/lib/blog-feature-solver.ts +459 -0
- package/lib/blog-rhythm.ts +134 -0
- package/lib/brand-fonts-watch.ts +43 -0
- package/lib/brand-fonts.ts +112 -0
- package/lib/cached-content-repository.ts +12 -0
- package/lib/category-repository.ts +43 -93
- package/lib/content-item-lifecycle.ts +55 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-types.ts +4 -0
- package/lib/curation-client.ts +61 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/graphql-content-repository.ts +56 -22
- package/lib/media-route-response.ts +45 -0
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-base-path.ts +12 -3
- package/lib/newsroom-board.ts +99 -0
- package/lib/newsroom-category-drilldown.ts +3 -2
- package/lib/newsroom-forum-routes.ts +6 -4
- package/lib/newsroom-index-filters.ts +14 -10
- package/lib/newsroom-nav.ts +3 -1
- package/lib/newsroom-organizer-nav.ts +37 -0
- package/lib/newsroom-section-routing.ts +20 -0
- package/lib/oauth-redirect-priority.ts +40 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/plugin-settings.ts +11 -0
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/priming-proof-report.ts +89 -0
- package/lib/prospectus-site-identity.ts +303 -0
- package/lib/publication-cyclotron.ts +74 -0
- package/lib/rate-limit-retry.ts +55 -0
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-cache-lifetime.ts +5 -0
- package/lib/reader-static-params.ts +85 -4
- package/lib/reader-storage-url.ts +9 -12
- package/lib/reference-policy.ts +5 -0
- package/lib/relevance-decisions.ts +103 -0
- package/lib/relevance-status.ts +63 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-prewarm.ts +53 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/robots-policy.ts +14 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/semantic-graph.ts +7 -0
- package/lib/shared-credentials-provider.ts +61 -0
- package/lib/signup-csv.ts +24 -0
- package/lib/site-brand.ts +59 -0
- package/lib/staging-gated-path.ts +23 -0
- package/lib/staging-preview-object.ts +21 -5
- package/lib/test-editor-override.ts +4 -0
- package/lib/video-loading.ts +22 -0
- package/lib/video-script.ts +14 -0
- package/middleware.ts +11 -19
- package/package.json +64 -1
- package/renderers/pretext/article-page.tsx +39 -3
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +15 -102
- package/renderers/pretext/rhythm-article-page.tsx +165 -0
- package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
- package/renderers/pretext/rhythm-shell-style.ts +35 -0
- package/routes.manifest.json +68 -12
- package/src/sync.mjs +30 -6
- package/src/with-papyrus.d.mts +9 -0
- package/src/with-papyrus.mjs +48 -3
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
- package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Token } from "aws-cdk-lib";
|
|
3
|
+
export function sanitizeAwsName(value, maxLength = 50) {
|
|
4
|
+
return value
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
7
|
+
.replace(/-+/g, "-")
|
|
8
|
+
.replace(/^-|-$/g, "")
|
|
9
|
+
.slice(0, maxLength);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Account-global names are immutable in AWS, so they must be built from literal
|
|
13
|
+
* synth-time inputs only. A CDK token (or text that came from one, such as
|
|
14
|
+
* `token-token-17565`) makes the name change on every synth and fail with
|
|
15
|
+
* AlreadyExists, so this throws instead.
|
|
16
|
+
*/
|
|
17
|
+
export function assertLiteralResourceName(label, name) {
|
|
18
|
+
if (Token.isUnresolved(name) || /token|\$\{/i.test(name)) {
|
|
19
|
+
throw new Error(`${label} "${name}" is derived from an unresolved CDK token. Build account-global names from literal site config values only.`);
|
|
20
|
+
}
|
|
21
|
+
return name;
|
|
22
|
+
}
|
|
23
|
+
export function readBooleanEnvFlag(environment, name, defaultValue) {
|
|
24
|
+
const raw = (environment[name] ?? "").trim().toLowerCase();
|
|
25
|
+
if (["1", "true", "yes", "on"].includes(raw))
|
|
26
|
+
return true;
|
|
27
|
+
if (["0", "false", "no", "off"].includes(raw))
|
|
28
|
+
return false;
|
|
29
|
+
return defaultValue;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* True only for the Amplify app that owns the original p.apyr.us backend. That
|
|
33
|
+
* backend keeps its historical account-global names so its live resources are
|
|
34
|
+
* never replaced. Every other site gets brand-scoped names.
|
|
35
|
+
*/
|
|
36
|
+
export function isLegacyProductionApp(identity) {
|
|
37
|
+
return identity.productionAppId !== "" && identity.amplifyAppId === identity.productionAppId;
|
|
38
|
+
}
|
|
39
|
+
export function isLegacyProductionPipeline(identity) {
|
|
40
|
+
return isLegacyProductionApp(identity) && identity.amplifyBranch === "main";
|
|
41
|
+
}
|
|
42
|
+
export function resolveSiteBackendFeatureFlags(features, environment, identity) {
|
|
43
|
+
const legacyPipeline = isLegacyProductionPipeline(identity);
|
|
44
|
+
return {
|
|
45
|
+
consoleResponder: features?.consoleResponder ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_CONSOLE_RESPONDER", true),
|
|
46
|
+
inboundEmail: features?.inboundEmail ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_INBOUND_EMAIL", legacyPipeline),
|
|
47
|
+
slack: features?.slack ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_SLACK", false),
|
|
48
|
+
storageBackups: features?.storageBackups ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_STORAGE_BACKUPS", legacyPipeline),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function deriveKnowledgeVectorIndexName(identity) {
|
|
52
|
+
const name = isLegacyProductionApp(identity)
|
|
53
|
+
? "papyrus-knowledge"
|
|
54
|
+
: `papyrus-knowledge-${sanitizeAwsName(identity.brandId, 40)}`;
|
|
55
|
+
return assertLiteralResourceName("Knowledge vector index name", name);
|
|
56
|
+
}
|
|
57
|
+
export function deriveStorageBackupVaultName(identity, explicitName) {
|
|
58
|
+
const explicit = (explicitName ?? "").trim();
|
|
59
|
+
if (explicit !== "")
|
|
60
|
+
return assertLiteralResourceName("Backup vault name", sanitizeAwsName(explicit));
|
|
61
|
+
if (isLegacyProductionPipeline(identity)) {
|
|
62
|
+
return assertLiteralResourceName("Backup vault name", sanitizeAwsName(`papyrus-${identity.productionAppId}-main-media-backup-vault`));
|
|
63
|
+
}
|
|
64
|
+
const literalSiteKey = [identity.brandId, identity.amplifyAppId, identity.amplifyBranch].join("|");
|
|
65
|
+
const siteHash = createHash("sha256").update(literalSiteKey).digest("hex").slice(0, 8);
|
|
66
|
+
return assertLiteralResourceName("Backup vault name", `papyrus-${sanitizeAwsName(identity.brandId, 20)}-${siteHash}-media-vault`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The `papyrus-site-inbound-` prefix keeps a brand named `p-apyr-us` from
|
|
70
|
+
* colliding with the legacy `papyrus-inbound-p-apyr-us` set.
|
|
71
|
+
* SES allows one ACTIVE receipt rule set per region. A non-legacy site creates
|
|
72
|
+
* its own brand-named rule set and never activates it from CloudFormation.
|
|
73
|
+
*/
|
|
74
|
+
export function deriveReceiptRuleSetName(identity, inboundEmailDomain) {
|
|
75
|
+
const name = isLegacyProductionApp(identity)
|
|
76
|
+
? `papyrus-inbound-${inboundEmailDomain.replace(/\./g, "-")}`
|
|
77
|
+
: `papyrus-site-inbound-${sanitizeAwsName(identity.brandId, 40)}`;
|
|
78
|
+
return assertLiteralResourceName("Receipt rule set name", name);
|
|
79
|
+
}
|
|
80
|
+
/** `undefined` keeps the legacy auto-generated rule name of the live p.apyr.us rule. */
|
|
81
|
+
export function deriveReceiptRuleName(identity) {
|
|
82
|
+
if (isLegacyProductionApp(identity))
|
|
83
|
+
return undefined;
|
|
84
|
+
return assertLiteralResourceName("Receipt rule name", `${sanitizeAwsName(identity.brandId, 40)}-inbound-submissions`);
|
|
85
|
+
}
|
|
86
|
+
export function planInboundEmailSes(identity, inboundEmailEnabled, options) {
|
|
87
|
+
const legacyPipeline = isLegacyProductionPipeline(identity);
|
|
88
|
+
const deployedMainBranch = identity.amplifyAppId !== "" && identity.amplifyBranch === "main";
|
|
89
|
+
const createReceiptRules = inboundEmailEnabled && (legacyPipeline || deployedMainBranch);
|
|
90
|
+
return {
|
|
91
|
+
createReceiptRules,
|
|
92
|
+
activateReceiptRuleSet: createReceiptRules && legacyPipeline,
|
|
93
|
+
manageDomainIdentity: createReceiptRules && (legacyPipeline || options?.manageDomainIdentity === true),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function deriveSiteGlobalResourceNames(identity, inboundEmailDomain) {
|
|
97
|
+
return {
|
|
98
|
+
knowledgeVectorIndexName: deriveKnowledgeVectorIndexName(identity),
|
|
99
|
+
storageBackupVaultName: deriveStorageBackupVaultName(identity),
|
|
100
|
+
receiptRuleSetName: deriveReceiptRuleSetName(identity, inboundEmailDomain),
|
|
101
|
+
receiptRuleName: deriveReceiptRuleName(identity),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Token } from "aws-cdk-lib";
|
|
3
|
+
|
|
4
|
+
export type SiteBackendFeatureFlags = {
|
|
5
|
+
consoleResponder: boolean;
|
|
6
|
+
inboundEmail: boolean;
|
|
7
|
+
slack: boolean;
|
|
8
|
+
storageBackups: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SiteBackendFeatureInput = Partial<SiteBackendFeatureFlags> | undefined;
|
|
12
|
+
|
|
13
|
+
export function sanitizeAwsName(value: string, maxLength = 50): string {
|
|
14
|
+
return value
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
17
|
+
.replace(/-+/g, "-")
|
|
18
|
+
.replace(/^-|-$/g, "")
|
|
19
|
+
.slice(0, maxLength);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Account-global names are immutable in AWS, so they must be built from literal
|
|
24
|
+
* synth-time inputs only. A CDK token (or text that came from one, such as
|
|
25
|
+
* `token-token-17565`) makes the name change on every synth and fail with
|
|
26
|
+
* AlreadyExists, so this throws instead.
|
|
27
|
+
*/
|
|
28
|
+
export function assertLiteralResourceName(label: string, name: string): string {
|
|
29
|
+
if (Token.isUnresolved(name) || /token|\$\{/i.test(name)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`${label} "${name}" is derived from an unresolved CDK token. Build account-global names from literal site config values only.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readBooleanEnvFlag(
|
|
38
|
+
environment: Record<string, string | undefined>,
|
|
39
|
+
name: string,
|
|
40
|
+
defaultValue: boolean,
|
|
41
|
+
): boolean {
|
|
42
|
+
const raw = (environment[name] ?? "").trim().toLowerCase();
|
|
43
|
+
if (["1", "true", "yes", "on"].includes(raw)) return true;
|
|
44
|
+
if (["0", "false", "no", "off"].includes(raw)) return false;
|
|
45
|
+
return defaultValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type SiteBackendIdentity = {
|
|
49
|
+
brandId: string;
|
|
50
|
+
amplifyAppId: string;
|
|
51
|
+
amplifyBranch: string;
|
|
52
|
+
productionAppId: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* True only for the Amplify app that owns the original p.apyr.us backend. That
|
|
57
|
+
* backend keeps its historical account-global names so its live resources are
|
|
58
|
+
* never replaced. Every other site gets brand-scoped names.
|
|
59
|
+
*/
|
|
60
|
+
export function isLegacyProductionApp(identity: SiteBackendIdentity): boolean {
|
|
61
|
+
return identity.productionAppId !== "" && identity.amplifyAppId === identity.productionAppId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isLegacyProductionPipeline(identity: SiteBackendIdentity): boolean {
|
|
65
|
+
return isLegacyProductionApp(identity) && identity.amplifyBranch === "main";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveSiteBackendFeatureFlags(
|
|
69
|
+
features: SiteBackendFeatureInput,
|
|
70
|
+
environment: Record<string, string | undefined>,
|
|
71
|
+
identity: SiteBackendIdentity,
|
|
72
|
+
): SiteBackendFeatureFlags {
|
|
73
|
+
const legacyPipeline = isLegacyProductionPipeline(identity);
|
|
74
|
+
return {
|
|
75
|
+
consoleResponder:
|
|
76
|
+
features?.consoleResponder ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_CONSOLE_RESPONDER", true),
|
|
77
|
+
inboundEmail:
|
|
78
|
+
features?.inboundEmail ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_INBOUND_EMAIL", legacyPipeline),
|
|
79
|
+
slack: features?.slack ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_SLACK", false),
|
|
80
|
+
storageBackups:
|
|
81
|
+
features?.storageBackups ?? readBooleanEnvFlag(environment, "PAPYRUS_ENABLE_STORAGE_BACKUPS", legacyPipeline),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function deriveKnowledgeVectorIndexName(identity: SiteBackendIdentity): string {
|
|
86
|
+
const name = isLegacyProductionApp(identity)
|
|
87
|
+
? "papyrus-knowledge"
|
|
88
|
+
: `papyrus-knowledge-${sanitizeAwsName(identity.brandId, 40)}`;
|
|
89
|
+
return assertLiteralResourceName("Knowledge vector index name", name);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function deriveStorageBackupVaultName(identity: SiteBackendIdentity, explicitName?: string): string {
|
|
93
|
+
const explicit = (explicitName ?? "").trim();
|
|
94
|
+
if (explicit !== "") return assertLiteralResourceName("Backup vault name", sanitizeAwsName(explicit));
|
|
95
|
+
if (isLegacyProductionPipeline(identity)) {
|
|
96
|
+
return assertLiteralResourceName(
|
|
97
|
+
"Backup vault name",
|
|
98
|
+
sanitizeAwsName(`papyrus-${identity.productionAppId}-main-media-backup-vault`),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const literalSiteKey = [identity.brandId, identity.amplifyAppId, identity.amplifyBranch].join("|");
|
|
102
|
+
const siteHash = createHash("sha256").update(literalSiteKey).digest("hex").slice(0, 8);
|
|
103
|
+
return assertLiteralResourceName(
|
|
104
|
+
"Backup vault name",
|
|
105
|
+
`papyrus-${sanitizeAwsName(identity.brandId, 20)}-${siteHash}-media-vault`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The `papyrus-site-inbound-` prefix keeps a brand named `p-apyr-us` from
|
|
111
|
+
* colliding with the legacy `papyrus-inbound-p-apyr-us` set.
|
|
112
|
+
* SES allows one ACTIVE receipt rule set per region. A non-legacy site creates
|
|
113
|
+
* its own brand-named rule set and never activates it from CloudFormation.
|
|
114
|
+
*/
|
|
115
|
+
export function deriveReceiptRuleSetName(identity: SiteBackendIdentity, inboundEmailDomain: string): string {
|
|
116
|
+
const name = isLegacyProductionApp(identity)
|
|
117
|
+
? `papyrus-inbound-${inboundEmailDomain.replace(/\./g, "-")}`
|
|
118
|
+
: `papyrus-site-inbound-${sanitizeAwsName(identity.brandId, 40)}`;
|
|
119
|
+
return assertLiteralResourceName("Receipt rule set name", name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** `undefined` keeps the legacy auto-generated rule name of the live p.apyr.us rule. */
|
|
123
|
+
export function deriveReceiptRuleName(identity: SiteBackendIdentity): string | undefined {
|
|
124
|
+
if (isLegacyProductionApp(identity)) return undefined;
|
|
125
|
+
return assertLiteralResourceName("Receipt rule name", `${sanitizeAwsName(identity.brandId, 40)}-inbound-submissions`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type InboundEmailSesPlan = {
|
|
129
|
+
createReceiptRules: boolean;
|
|
130
|
+
activateReceiptRuleSet: boolean;
|
|
131
|
+
manageDomainIdentity: boolean;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export function planInboundEmailSes(
|
|
135
|
+
identity: SiteBackendIdentity,
|
|
136
|
+
inboundEmailEnabled: boolean,
|
|
137
|
+
options: { manageDomainIdentity?: boolean } | undefined,
|
|
138
|
+
): InboundEmailSesPlan {
|
|
139
|
+
const legacyPipeline = isLegacyProductionPipeline(identity);
|
|
140
|
+
const deployedMainBranch = identity.amplifyAppId !== "" && identity.amplifyBranch === "main";
|
|
141
|
+
const createReceiptRules = inboundEmailEnabled && (legacyPipeline || deployedMainBranch);
|
|
142
|
+
return {
|
|
143
|
+
createReceiptRules,
|
|
144
|
+
activateReceiptRuleSet: createReceiptRules && legacyPipeline,
|
|
145
|
+
manageDomainIdentity: createReceiptRules && (legacyPipeline || options?.manageDomainIdentity === true),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type SiteGlobalResourceNames = {
|
|
150
|
+
knowledgeVectorIndexName: string;
|
|
151
|
+
storageBackupVaultName: string;
|
|
152
|
+
receiptRuleSetName: string;
|
|
153
|
+
receiptRuleName: string | undefined;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export function deriveSiteGlobalResourceNames(
|
|
157
|
+
identity: SiteBackendIdentity,
|
|
158
|
+
inboundEmailDomain: string,
|
|
159
|
+
): SiteGlobalResourceNames {
|
|
160
|
+
return {
|
|
161
|
+
knowledgeVectorIndexName: deriveKnowledgeVectorIndexName(identity),
|
|
162
|
+
storageBackupVaultName: deriveStorageBackupVaultName(identity),
|
|
163
|
+
receiptRuleSetName: deriveReceiptRuleSetName(identity, inboundEmailDomain),
|
|
164
|
+
receiptRuleName: deriveReceiptRuleName(identity),
|
|
165
|
+
};
|
|
166
|
+
}
|