@anthusai/papyrus 1.0.0-next.8 → 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/bin/papyrus-infra.mjs +3 -3
- 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 +219 -71
- package/infra/amplify-app-shell.ts +231 -77
- package/infra/build-specs.js +23 -8
- package/infra/build-specs.ts +27 -11
- package/infra/site-config.js +277 -34
- package/infra/site-config.ts +340 -43
- 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
|
@@ -1,20 +1,46 @@
|
|
|
1
|
-
import { ArnFormat, CfnOutput, Duration, Stack } from "aws-cdk-lib";
|
|
1
|
+
import { ArnFormat, CfnOutput, Duration, Fn, Stack } from "aws-cdk-lib";
|
|
2
2
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
3
3
|
import * as amplify from "aws-cdk-lib/aws-amplify";
|
|
4
4
|
import { GITHUB_OIDC_PROVIDER_HOST } from "./github-oidc-provider.js";
|
|
5
5
|
import { cmsProductionBuildSpec, cmsStagingBuildSpec, readerBuildSpec } from "./build-specs.js";
|
|
6
|
-
import { resolveStagingDomainName } from "./site-config.js";
|
|
6
|
+
import { PROSPECTUS_PAPYRUS_SITE_ROUTES, hasCmsApp, isStagingEnabled, resolveRedirectSubDomainPrefix, resolveCmsHostName, resolveProspectusAccountsEndpoint, resolveRevalidateSecretParameterName, resolveStackName, resolveStagingDomainName, resolveStoragePreviewPrefix } from "./site-config.js";
|
|
7
7
|
function environmentVariables(variables) {
|
|
8
8
|
return Object.entries(variables).map(([name, value]) => ({ name, value }));
|
|
9
9
|
}
|
|
10
|
+
function readerCustomHeaders(rules) {
|
|
11
|
+
if (!rules)
|
|
12
|
+
return undefined;
|
|
13
|
+
const lines = ["customHeaders:"];
|
|
14
|
+
for (const rule of rules) {
|
|
15
|
+
lines.push(` - pattern: ${JSON.stringify(rule.pattern)}`, " headers:");
|
|
16
|
+
for (const [key, value] of Object.entries(rule.headers)) {
|
|
17
|
+
lines.push(` - key: ${JSON.stringify(key)}`, ` value: ${JSON.stringify(value)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return lines.join("\n") + "\n";
|
|
21
|
+
}
|
|
10
22
|
/**
|
|
11
23
|
* Provisions the Amplify *app shell* of a Papyrus publication from its
|
|
12
24
|
* site.json:
|
|
13
25
|
*
|
|
14
26
|
* - CMS app (WEB_COMPUTE) with branches `main` (production, owns the backend)
|
|
15
27
|
* and `staging` (frontend only, reads the production backend)
|
|
16
|
-
* - for `markus-static` sites, a reader app (WEB) with branch `main
|
|
17
|
-
*
|
|
28
|
+
* - for `markus-static` sites, a reader app (WEB) with branch `main`; a
|
|
29
|
+
* `markus-static` site may omit `cms` to get the reader app alone (no CMS
|
|
30
|
+
* app, no CMS roles, no authoring role)
|
|
31
|
+
* - a domain association per branch/app when a domain is configured (without
|
|
32
|
+
* one, no AWS::Amplify::Domain is created and the default amplifyapp.com
|
|
33
|
+
* URL is used), IAM service and compute roles
|
|
34
|
+
* - `cms.staging: false` omits the staging branch and its domain
|
|
35
|
+
*
|
|
36
|
+
* Google sign-in is always on: the branches never set PAPYRUS_DISABLE_GOOGLE_OAUTH,
|
|
37
|
+
and `cms.cognitoDomainPrefix` becomes the hosted-UI domain
|
|
38
|
+
(PAPYRUS_COGNITO_DOMAIN_PREFIX) only when `cms.applyCognitoDomainPrefix` is true
|
|
39
|
+
(PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX); otherwise Amplify's generated domain is kept. The secrets GOOGLE_CLIENT_ID and
|
|
40
|
+
GOOGLE_CLIENT_SECRET are Amplify backend secrets set in the console, never here.
|
|
41
|
+
The stack also creates the `<siteId>-papyrus-authoring` role. The CLI signs
|
|
42
|
+
* AppSync requests (SigV4) with credentials for that role (SSO, OIDC or any
|
|
43
|
+
* principal in the account that may assume it); no token is stored anywhere.
|
|
18
44
|
*
|
|
19
45
|
* No repository and no access token are configured: the apps are manual-deploy
|
|
20
46
|
* until Amplify's GitHub App is connected once in the console
|
|
@@ -26,8 +52,16 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
26
52
|
readerAppId;
|
|
27
53
|
constructor(scope, id, config, props) {
|
|
28
54
|
super(scope, id, props);
|
|
55
|
+
if (!hasCmsApp(config)) {
|
|
56
|
+
if (!config.reader)
|
|
57
|
+
throw new Error(`site ${config.siteId} has neither a cms section nor a reader`);
|
|
58
|
+
this.readerAppId = this.addReaderApp(config, config.reader);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
29
61
|
const cmsAppName = config.cms.appName ?? `${config.siteId}-cms`;
|
|
62
|
+
const stagingEnabled = isStagingEnabled(config);
|
|
30
63
|
const stagingDomainName = resolveStagingDomainName(config);
|
|
64
|
+
const cmsHostName = resolveCmsHostName(config);
|
|
31
65
|
const cmsServiceRole = new iam.Role(this, "AmplifyServiceRole", {
|
|
32
66
|
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
33
67
|
description: `Backend deploy and staging build service role for Amplify app ${cmsAppName} (AdministratorAccess: documented risk)`,
|
|
@@ -41,6 +75,27 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
41
75
|
iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"),
|
|
42
76
|
],
|
|
43
77
|
});
|
|
78
|
+
const revalidateSecretParameterName = config.frontend === "pretext" ? resolveRevalidateSecretParameterName(config) : undefined;
|
|
79
|
+
if (revalidateSecretParameterName) {
|
|
80
|
+
computeRole.addToPolicy(new iam.PolicyStatement({
|
|
81
|
+
sid: "ReadRevalidateSecret",
|
|
82
|
+
actions: ["ssm:GetParameter"],
|
|
83
|
+
resources: [
|
|
84
|
+
this.formatArn({ service: "ssm", resource: "parameter", resourceName: revalidateSecretParameterName.slice(1), arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
85
|
+
],
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
const prospectusAccountsApiStageArn = config.cms.prospectusAccountsApiStageArn;
|
|
89
|
+
const prospectusEnvironment = prospectusAccountsApiStageArn
|
|
90
|
+
? { PAPYRUS_PROSPECTUS_ACCOUNTS_ENDPOINT: resolveProspectusAccountsEndpoint(prospectusAccountsApiStageArn) }
|
|
91
|
+
: {};
|
|
92
|
+
if (prospectusAccountsApiStageArn) {
|
|
93
|
+
computeRole.addToPolicy(new iam.PolicyStatement({
|
|
94
|
+
sid: "InvokeProspectusAccountsAsRegisteredSite",
|
|
95
|
+
actions: ["execute-api:Invoke"],
|
|
96
|
+
resources: PROSPECTUS_PAPYRUS_SITE_ROUTES.map((route) => `${prospectusAccountsApiStageArn}/${route}`),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
44
99
|
const cmsApp = new amplify.CfnApp(this, "App", {
|
|
45
100
|
name: cmsAppName,
|
|
46
101
|
description: `Papyrus newsroom CMS for ${config.siteId} (WEB_COMPUTE)`,
|
|
@@ -49,11 +104,32 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
49
104
|
computeRoleArn: computeRole.roleArn,
|
|
50
105
|
buildSpec: cmsProductionBuildSpec(config),
|
|
51
106
|
jobConfig: config.cms.buildComputeType ? { buildComputeType: config.cms.buildComputeType } : undefined,
|
|
52
|
-
customRules: [
|
|
107
|
+
customRules: [
|
|
108
|
+
...(config.cms.redirects ?? []).map((redirect) => ({
|
|
109
|
+
source: `https://${redirect.source}`,
|
|
110
|
+
target: `https://${resolveCmsHostName(config)}`,
|
|
111
|
+
status: String(redirect.status),
|
|
112
|
+
})),
|
|
113
|
+
{ source: "/<*>", target: "/index.html", status: "404-200" },
|
|
114
|
+
],
|
|
53
115
|
});
|
|
54
116
|
const stagingMode = config.frontend === "markus-static"
|
|
55
117
|
? { PAPYRUS_STAGING_PREVIEW: "static" }
|
|
56
118
|
: { PAPYRUS_CONTENT_SOURCE: "drafts" };
|
|
119
|
+
const productionOrigin = cmsHostName
|
|
120
|
+
? `https://${cmsHostName}/`
|
|
121
|
+
: Fn.join("", ["https://main.", cmsApp.attrDefaultDomain, "/"]);
|
|
122
|
+
const stagingOrigin = stagingDomainName
|
|
123
|
+
? `https://${stagingDomainName}/`
|
|
124
|
+
: Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain, "/"]);
|
|
125
|
+
const sharedRootDomain = config.cms.domainPrefix !== undefined;
|
|
126
|
+
const defaultOriginsToAllow = [
|
|
127
|
+
...(config.cms.domainName ? [] : [productionOrigin]),
|
|
128
|
+
...(stagingEnabled && !stagingDomainName ? [stagingOrigin] : []),
|
|
129
|
+
];
|
|
130
|
+
const oauthRedirectUrls = defaultOriginsToAllow.length === 0
|
|
131
|
+
? config.cms.environment.PAPYRUS_OAUTH_REDIRECT_URLS
|
|
132
|
+
: Fn.join(",", [config.cms.environment.PAPYRUS_OAUTH_REDIRECT_URLS, ...defaultOriginsToAllow]);
|
|
57
133
|
const productionBranch = new amplify.CfnBranch(this, "Branch", {
|
|
58
134
|
appId: cmsApp.attrAppId,
|
|
59
135
|
branchName: "main",
|
|
@@ -62,50 +138,132 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
62
138
|
enablePerformanceMode: false,
|
|
63
139
|
environmentVariables: environmentVariables({
|
|
64
140
|
...config.cms.environment,
|
|
141
|
+
PAPYRUS_COGNITO_DOMAIN_PREFIX: config.cms.cognitoDomainPrefix,
|
|
142
|
+
...(config.cms.applyCognitoDomainPrefix ? { PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX: "true" } : {}),
|
|
143
|
+
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
65
144
|
PAPYRUS_SITE_BRAND: config.brand,
|
|
66
145
|
SITE_ENV: "production",
|
|
67
146
|
PAPYRUS_CONTENT_SOURCE: "published",
|
|
147
|
+
...(revalidateSecretParameterName ? { PAPYRUS_REVALIDATE_SECRET_PARAMETER: revalidateSecretParameterName } : {}),
|
|
148
|
+
...prospectusEnvironment,
|
|
68
149
|
}),
|
|
69
150
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
151
|
+
let stagingBranch;
|
|
152
|
+
if (stagingEnabled) {
|
|
153
|
+
stagingBranch = new amplify.CfnBranch(this, "StagingBranch", {
|
|
154
|
+
appId: cmsApp.attrAppId,
|
|
155
|
+
branchName: "staging",
|
|
156
|
+
stage: "BETA",
|
|
157
|
+
enableAutoBuild: true,
|
|
158
|
+
enablePerformanceMode: false,
|
|
159
|
+
buildSpec: cmsStagingBuildSpec(config),
|
|
160
|
+
environmentVariables: environmentVariables({
|
|
161
|
+
...config.cms.environment,
|
|
162
|
+
PAPYRUS_COGNITO_DOMAIN_PREFIX: config.cms.cognitoDomainPrefix,
|
|
163
|
+
...(config.cms.applyCognitoDomainPrefix ? { PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX: "true" } : {}),
|
|
164
|
+
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
165
|
+
PAPYRUS_SITE_BRAND: config.brand,
|
|
166
|
+
SITE_ENV: "staging",
|
|
167
|
+
...stagingMode,
|
|
168
|
+
...prospectusEnvironment,
|
|
169
|
+
NEXT_PUBLIC_PAPYRUS_STAGING_URL: stagingDomainName
|
|
170
|
+
? `https://${stagingDomainName}`
|
|
171
|
+
: Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain]),
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
if (stagingDomainName && !sharedRootDomain) {
|
|
175
|
+
const stagingDomain = new amplify.CfnDomain(this, "StagingDomain", {
|
|
176
|
+
appId: cmsApp.attrAppId,
|
|
177
|
+
domainName: stagingDomainName,
|
|
178
|
+
subDomainSettings: [{ branchName: "staging", prefix: "" }],
|
|
179
|
+
});
|
|
180
|
+
stagingDomain.addResourceDependency(stagingBranch);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (config.cms.domainName) {
|
|
184
|
+
const subDomainSettings = sharedRootDomain
|
|
185
|
+
? [
|
|
186
|
+
{ branchName: "main", prefix: config.cms.domainPrefix },
|
|
187
|
+
...(config.cms.redirects ?? []).map((redirect) => ({
|
|
188
|
+
branchName: "main",
|
|
189
|
+
prefix: resolveRedirectSubDomainPrefix(config.cms.domainName, redirect.source),
|
|
190
|
+
})),
|
|
191
|
+
...(stagingBranch ? [{ branchName: "staging", prefix: config.cms.stagingDomainPrefix }] : []),
|
|
192
|
+
]
|
|
193
|
+
: [{ branchName: "main", prefix: "" }];
|
|
194
|
+
const productionDomain = new amplify.CfnDomain(this, "Domain", {
|
|
195
|
+
appId: cmsApp.attrAppId,
|
|
196
|
+
domainName: config.cms.domainName,
|
|
197
|
+
subDomainSettings,
|
|
198
|
+
});
|
|
199
|
+
productionDomain.addResourceDependency(productionBranch);
|
|
200
|
+
if (sharedRootDomain && stagingBranch) {
|
|
201
|
+
productionDomain.addResourceDependency(stagingBranch);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
97
204
|
this.cmsAppId = cmsApp.attrAppId;
|
|
98
205
|
new CfnOutput(this, "CmsAppId", { value: cmsApp.attrAppId, description: `Amplify app id for ${cmsAppName}` });
|
|
99
|
-
new CfnOutput(this, "CmsOrigin", { value:
|
|
100
|
-
new CfnOutput(this, "
|
|
206
|
+
new CfnOutput(this, "CmsOrigin", { value: productionOrigin });
|
|
207
|
+
new CfnOutput(this, "CmsComputeRoleArn", {
|
|
208
|
+
value: computeRole.roleArn,
|
|
209
|
+
description: `SSR compute role for ${cmsAppName}; the roleArn of its Prospectus site registration`,
|
|
210
|
+
});
|
|
211
|
+
if (stagingEnabled) {
|
|
212
|
+
new CfnOutput(this, "StagingOrigin", { value: stagingOrigin });
|
|
213
|
+
}
|
|
101
214
|
if (config.reader) {
|
|
102
|
-
this.readerAppId = this.addReaderApp(config, config.reader
|
|
215
|
+
this.readerAppId = this.addReaderApp(config, config.reader);
|
|
103
216
|
}
|
|
217
|
+
const amplifyAppIds = [cmsApp.attrAppId, ...(this.readerAppId ? [this.readerAppId] : [])];
|
|
218
|
+
this.addAuthoringRole(config, cmsApp.attrAppId, amplifyAppIds);
|
|
104
219
|
if (config.github) {
|
|
105
|
-
this.addGithubCiRole(config, config.github,
|
|
220
|
+
this.addGithubCiRole(config, config.github, cmsApp.attrAppId, amplifyAppIds);
|
|
106
221
|
}
|
|
107
222
|
}
|
|
108
|
-
|
|
223
|
+
authoringStatements(config, cmsAppId, amplifyAppIds) {
|
|
224
|
+
const mediaBucketArn = `arn:${this.partition}:s3:::amplify-${cmsAppId}-*`;
|
|
225
|
+
return [
|
|
226
|
+
new iam.PolicyStatement({
|
|
227
|
+
sid: "SignAppSyncAuthoringRequests",
|
|
228
|
+
actions: ["appsync:GraphQL"],
|
|
229
|
+
resources: ["Query", "Mutation"].map((typeName) => this.formatArn({ service: "appsync", resource: "apis", resourceName: `*/types/${typeName}/fields/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME })),
|
|
230
|
+
}),
|
|
231
|
+
new iam.PolicyStatement({
|
|
232
|
+
sid: "ReadWriteMediaAndPreview",
|
|
233
|
+
actions: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
|
234
|
+
resources: [`${mediaBucketArn}/media/*`, `${mediaBucketArn}/${resolveStoragePreviewPrefix(config)}*`],
|
|
235
|
+
}),
|
|
236
|
+
new iam.PolicyStatement({
|
|
237
|
+
sid: "ListMediaAndPreview",
|
|
238
|
+
actions: ["s3:ListBucket"],
|
|
239
|
+
resources: [mediaBucketArn],
|
|
240
|
+
conditions: { StringLike: { "s3:prefix": ["media/*", `${resolveStoragePreviewPrefix(config)}*`] } },
|
|
241
|
+
}),
|
|
242
|
+
new iam.PolicyStatement({
|
|
243
|
+
sid: "StartAndInspectAmplifyJobs",
|
|
244
|
+
actions: ["amplify:StartJob", "amplify:ListJobs", "amplify:GetJob", "amplify:ListBranches"],
|
|
245
|
+
resources: amplifyAppIds.flatMap((appId) => [
|
|
246
|
+
this.formatArn({ service: "amplify", resource: "apps", resourceName: appId, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
247
|
+
this.formatArn({ service: "amplify", resource: "apps", resourceName: `${appId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
248
|
+
]),
|
|
249
|
+
}),
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
addAuthoringRole(config, cmsAppId, amplifyAppIds) {
|
|
253
|
+
const authoringRole = new iam.Role(this, "PapyrusAuthoringRole", {
|
|
254
|
+
roleName: `${config.siteId}-papyrus-authoring`,
|
|
255
|
+
description: `Papyrus CLI authoring role for ${config.siteId}: AppSync (SigV4), media and preview storage, rebuild jobs. Assumable by principals in this account that are allowed sts:AssumeRole on it.`,
|
|
256
|
+
assumedBy: new iam.AccountRootPrincipal(),
|
|
257
|
+
});
|
|
258
|
+
for (const statement of this.authoringStatements(config, cmsAppId, amplifyAppIds)) {
|
|
259
|
+
authoringRole.addToPolicy(statement);
|
|
260
|
+
}
|
|
261
|
+
new CfnOutput(this, "PapyrusAuthoringRoleArn", {
|
|
262
|
+
value: authoringRole.roleArn,
|
|
263
|
+
description: `Role the Papyrus CLI assumes for ${config.siteId} (profile role_arn)`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
addGithubCiRole(config, github, cmsAppId, amplifyAppIds) {
|
|
109
267
|
const provider = iam.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, "GithubOidcProvider", this.formatArn({
|
|
110
268
|
service: "iam",
|
|
111
269
|
region: "",
|
|
@@ -124,22 +282,9 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
124
282
|
},
|
|
125
283
|
}),
|
|
126
284
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
resources: amplifyAppIds.flatMap((appId) => [
|
|
131
|
-
this.formatArn({ service: "amplify", resource: "apps", resourceName: appId, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
132
|
-
this.formatArn({ service: "amplify", resource: "apps", resourceName: `${appId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
133
|
-
]),
|
|
134
|
-
}));
|
|
135
|
-
ciRole.addToPolicy(new iam.PolicyStatement({
|
|
136
|
-
sid: "ReadSiteSecretsForJwtMinting",
|
|
137
|
-
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
138
|
-
resources: [
|
|
139
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/${this.cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
140
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/shared/${this.cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
141
|
-
],
|
|
142
|
-
}));
|
|
285
|
+
for (const statement of this.authoringStatements(config, cmsAppId, amplifyAppIds)) {
|
|
286
|
+
ciRole.addToPolicy(statement);
|
|
287
|
+
}
|
|
143
288
|
ciRole.addToPolicy(new iam.PolicyStatement({
|
|
144
289
|
sid: "WhoAmI",
|
|
145
290
|
actions: ["sts:GetCallerIdentity"],
|
|
@@ -150,27 +295,19 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
150
295
|
sid: "DeployThisSitesAppShellStack",
|
|
151
296
|
actions: ["cloudformation:*"],
|
|
152
297
|
resources: [
|
|
153
|
-
this.formatArn({ service: "cloudformation", resource: "stack", resourceName:
|
|
298
|
+
this.formatArn({ service: "cloudformation", resource: "stack", resourceName: `${resolveStackName(config)}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
154
299
|
],
|
|
155
300
|
}));
|
|
156
301
|
}
|
|
157
302
|
new CfnOutput(this, "GithubCiRoleArn", { value: ciRole.roleArn, description: `Role for GitHub Actions in ${github.owner}/${github.repo}` });
|
|
158
303
|
}
|
|
159
|
-
addReaderApp(config, reader
|
|
304
|
+
addReaderApp(config, reader) {
|
|
160
305
|
const readerAppName = reader.appName ?? `${config.siteId}-reader`;
|
|
161
306
|
const readerBranchName = reader.branchName ?? "main";
|
|
162
307
|
const readerServiceRole = new iam.Role(this, "ReaderServiceRole", {
|
|
163
308
|
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
164
|
-
description: `Static reader build role for Amplify app ${readerAppName} (reads
|
|
309
|
+
description: `Static reader build role for Amplify app ${readerAppName} (reads published media only; content is read as a Cognito guest)`,
|
|
165
310
|
});
|
|
166
|
-
readerServiceRole.addToPolicy(new iam.PolicyStatement({
|
|
167
|
-
sid: "ReadSiteSecretsForJwtMinting",
|
|
168
|
-
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
169
|
-
resources: [
|
|
170
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/${cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
171
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/shared/${cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
172
|
-
],
|
|
173
|
-
}));
|
|
174
311
|
readerServiceRole.addToPolicy(new iam.PolicyStatement({
|
|
175
312
|
sid: "ReadMediaForExport",
|
|
176
313
|
actions: ["s3:GetObject"],
|
|
@@ -182,6 +319,11 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
182
319
|
platform: "WEB",
|
|
183
320
|
iamServiceRole: readerServiceRole.roleArn,
|
|
184
321
|
buildSpec: readerBuildSpec(config),
|
|
322
|
+
customRules: [
|
|
323
|
+
...(reader.redirects ?? []),
|
|
324
|
+
{ source: "/<*>", target: "/404.html", status: "404-200" },
|
|
325
|
+
],
|
|
326
|
+
customHeaders: readerCustomHeaders(reader.customHeaders),
|
|
185
327
|
});
|
|
186
328
|
const readerBranch = new amplify.CfnBranch(this, "ReaderBranch", {
|
|
187
329
|
appId: readerApp.attrAppId,
|
|
@@ -194,14 +336,20 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
194
336
|
SITE_ENV: "production",
|
|
195
337
|
}),
|
|
196
338
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
339
|
+
if (reader.domainName) {
|
|
340
|
+
const readerDomain = new amplify.CfnDomain(this, "ReaderDomain", {
|
|
341
|
+
appId: readerApp.attrAppId,
|
|
342
|
+
domainName: reader.domainName,
|
|
343
|
+
subDomainSettings: (reader.domainPrefixes ?? [""]).map((prefix) => ({ branchName: readerBranchName, prefix })),
|
|
344
|
+
});
|
|
345
|
+
readerDomain.addResourceDependency(readerBranch);
|
|
346
|
+
}
|
|
203
347
|
new CfnOutput(this, "ReaderAppId", { value: readerApp.attrAppId, description: `Amplify app id for ${readerAppName}` });
|
|
204
|
-
new CfnOutput(this, "ReaderOrigin", {
|
|
348
|
+
new CfnOutput(this, "ReaderOrigin", {
|
|
349
|
+
value: reader.domainName
|
|
350
|
+
? `https://${reader.domainName}/`
|
|
351
|
+
: Fn.join("", [`https://${readerBranchName}.`, readerApp.attrDefaultDomain, "/"]),
|
|
352
|
+
});
|
|
205
353
|
return readerApp.attrAppId;
|
|
206
354
|
}
|
|
207
355
|
}
|