@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,23 +1,49 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
-
import { ArnFormat, CfnOutput, Duration, Stack, StackProps } from "aws-cdk-lib";
|
|
2
|
+
import { ArnFormat, CfnOutput, Duration, Fn, Stack, StackProps } from "aws-cdk-lib";
|
|
3
3
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import * as amplify from "aws-cdk-lib/aws-amplify";
|
|
5
5
|
import { GITHUB_OIDC_PROVIDER_HOST } from "./github-oidc-provider";
|
|
6
6
|
import { cmsProductionBuildSpec, cmsStagingBuildSpec, readerBuildSpec } from "./build-specs";
|
|
7
|
-
import { AmplifyAppShellSiteConfig, resolveStagingDomainName } from "./site-config";
|
|
7
|
+
import { AmplifyAppShellSiteConfig, PROSPECTUS_PAPYRUS_SITE_ROUTES, ReaderCustomHeaderRule, hasCmsApp, isStagingEnabled, resolveRedirectSubDomainPrefix, resolveCmsHostName, resolveProspectusAccountsEndpoint, resolveRevalidateSecretParameterName, resolveStackName, resolveStagingDomainName, resolveStoragePreviewPrefix } from "./site-config";
|
|
8
8
|
|
|
9
9
|
function environmentVariables(variables: Record<string, string>): amplify.CfnBranch.EnvironmentVariableProperty[] {
|
|
10
10
|
return Object.entries(variables).map(([name, value]) => ({ name, value }));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function readerCustomHeaders(rules: ReaderCustomHeaderRule[] | undefined): string | undefined {
|
|
14
|
+
if (!rules) return undefined;
|
|
15
|
+
const lines = ["customHeaders:"];
|
|
16
|
+
for (const rule of rules) {
|
|
17
|
+
lines.push(` - pattern: ${JSON.stringify(rule.pattern)}`, " headers:");
|
|
18
|
+
for (const [key, value] of Object.entries(rule.headers)) {
|
|
19
|
+
lines.push(` - key: ${JSON.stringify(key)}`, ` value: ${JSON.stringify(value)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n") + "\n";
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
/**
|
|
14
26
|
* Provisions the Amplify *app shell* of a Papyrus publication from its
|
|
15
27
|
* site.json:
|
|
16
28
|
*
|
|
17
29
|
* - CMS app (WEB_COMPUTE) with branches `main` (production, owns the backend)
|
|
18
30
|
* and `staging` (frontend only, reads the production backend)
|
|
19
|
-
* - for `markus-static` sites, a reader app (WEB) with branch `main
|
|
20
|
-
*
|
|
31
|
+
* - for `markus-static` sites, a reader app (WEB) with branch `main`; a
|
|
32
|
+
* `markus-static` site may omit `cms` to get the reader app alone (no CMS
|
|
33
|
+
* app, no CMS roles, no authoring role)
|
|
34
|
+
* - a domain association per branch/app when a domain is configured (without
|
|
35
|
+
* one, no AWS::Amplify::Domain is created and the default amplifyapp.com
|
|
36
|
+
* URL is used), IAM service and compute roles
|
|
37
|
+
* - `cms.staging: false` omits the staging branch and its domain
|
|
38
|
+
*
|
|
39
|
+
* Google sign-in is always on: the branches never set PAPYRUS_DISABLE_GOOGLE_OAUTH,
|
|
40
|
+
and `cms.cognitoDomainPrefix` becomes the hosted-UI domain
|
|
41
|
+
(PAPYRUS_COGNITO_DOMAIN_PREFIX) only when `cms.applyCognitoDomainPrefix` is true
|
|
42
|
+
(PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX); otherwise Amplify's generated domain is kept. The secrets GOOGLE_CLIENT_ID and
|
|
43
|
+
GOOGLE_CLIENT_SECRET are Amplify backend secrets set in the console, never here.
|
|
44
|
+
The stack also creates the `<siteId>-papyrus-authoring` role. The CLI signs
|
|
45
|
+
* AppSync requests (SigV4) with credentials for that role (SSO, OIDC or any
|
|
46
|
+
* principal in the account that may assume it); no token is stored anywhere.
|
|
21
47
|
*
|
|
22
48
|
* No repository and no access token are configured: the apps are manual-deploy
|
|
23
49
|
* until Amplify's GitHub App is connected once in the console
|
|
@@ -25,14 +51,22 @@ function environmentVariables(variables: Record<string, string>): amplify.CfnBra
|
|
|
25
51
|
* app by `ampx pipeline-deploy` from the production build.
|
|
26
52
|
*/
|
|
27
53
|
export class AmplifyAppShellStack extends Stack {
|
|
28
|
-
public readonly cmsAppId
|
|
54
|
+
public readonly cmsAppId?: string;
|
|
29
55
|
public readonly readerAppId?: string;
|
|
30
56
|
|
|
31
57
|
constructor(scope: Construct, id: string, config: AmplifyAppShellSiteConfig, props?: StackProps) {
|
|
32
58
|
super(scope, id, props);
|
|
33
59
|
|
|
60
|
+
if (!hasCmsApp(config)) {
|
|
61
|
+
if (!config.reader) throw new Error(`site ${config.siteId} has neither a cms section nor a reader`);
|
|
62
|
+
this.readerAppId = this.addReaderApp(config, config.reader);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
34
66
|
const cmsAppName = config.cms.appName ?? `${config.siteId}-cms`;
|
|
67
|
+
const stagingEnabled = isStagingEnabled(config);
|
|
35
68
|
const stagingDomainName = resolveStagingDomainName(config);
|
|
69
|
+
const cmsHostName = resolveCmsHostName(config);
|
|
36
70
|
|
|
37
71
|
const cmsServiceRole = new iam.Role(this, "AmplifyServiceRole", {
|
|
38
72
|
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
@@ -49,6 +83,33 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
49
83
|
],
|
|
50
84
|
});
|
|
51
85
|
|
|
86
|
+
const revalidateSecretParameterName = config.frontend === "pretext" ? resolveRevalidateSecretParameterName(config) : undefined;
|
|
87
|
+
if (revalidateSecretParameterName) {
|
|
88
|
+
computeRole.addToPolicy(
|
|
89
|
+
new iam.PolicyStatement({
|
|
90
|
+
sid: "ReadRevalidateSecret",
|
|
91
|
+
actions: ["ssm:GetParameter"],
|
|
92
|
+
resources: [
|
|
93
|
+
this.formatArn({ service: "ssm", resource: "parameter", resourceName: revalidateSecretParameterName.slice(1), arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
94
|
+
],
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const prospectusAccountsApiStageArn = config.cms.prospectusAccountsApiStageArn;
|
|
100
|
+
const prospectusEnvironment = prospectusAccountsApiStageArn
|
|
101
|
+
? { PAPYRUS_PROSPECTUS_ACCOUNTS_ENDPOINT: resolveProspectusAccountsEndpoint(prospectusAccountsApiStageArn) }
|
|
102
|
+
: {};
|
|
103
|
+
if (prospectusAccountsApiStageArn) {
|
|
104
|
+
computeRole.addToPolicy(
|
|
105
|
+
new iam.PolicyStatement({
|
|
106
|
+
sid: "InvokeProspectusAccountsAsRegisteredSite",
|
|
107
|
+
actions: ["execute-api:Invoke"],
|
|
108
|
+
resources: PROSPECTUS_PAPYRUS_SITE_ROUTES.map((route) => `${prospectusAccountsApiStageArn}/${route}`),
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
52
113
|
const cmsApp = new amplify.CfnApp(this, "App", {
|
|
53
114
|
name: cmsAppName,
|
|
54
115
|
description: `Papyrus newsroom CMS for ${config.siteId} (WEB_COMPUTE)`,
|
|
@@ -57,13 +118,35 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
57
118
|
computeRoleArn: computeRole.roleArn,
|
|
58
119
|
buildSpec: cmsProductionBuildSpec(config),
|
|
59
120
|
jobConfig: config.cms.buildComputeType ? { buildComputeType: config.cms.buildComputeType } : undefined,
|
|
60
|
-
customRules: [
|
|
121
|
+
customRules: [
|
|
122
|
+
...(config.cms.redirects ?? []).map((redirect) => ({
|
|
123
|
+
source: `https://${redirect.source}`,
|
|
124
|
+
target: `https://${resolveCmsHostName(config)}`,
|
|
125
|
+
status: String(redirect.status),
|
|
126
|
+
})),
|
|
127
|
+
{ source: "/<*>", target: "/index.html", status: "404-200" },
|
|
128
|
+
],
|
|
61
129
|
});
|
|
62
130
|
|
|
63
131
|
const stagingMode: Record<string, string> = config.frontend === "markus-static"
|
|
64
132
|
? { PAPYRUS_STAGING_PREVIEW: "static" }
|
|
65
133
|
: { PAPYRUS_CONTENT_SOURCE: "drafts" };
|
|
66
134
|
|
|
135
|
+
const productionOrigin = cmsHostName
|
|
136
|
+
? `https://${cmsHostName}/`
|
|
137
|
+
: Fn.join("", ["https://main.", cmsApp.attrDefaultDomain, "/"]);
|
|
138
|
+
const stagingOrigin = stagingDomainName
|
|
139
|
+
? `https://${stagingDomainName}/`
|
|
140
|
+
: Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain, "/"]);
|
|
141
|
+
const sharedRootDomain = config.cms.domainPrefix !== undefined;
|
|
142
|
+
const defaultOriginsToAllow = [
|
|
143
|
+
...(config.cms.domainName ? [] : [productionOrigin]),
|
|
144
|
+
...(stagingEnabled && !stagingDomainName ? [stagingOrigin] : []),
|
|
145
|
+
];
|
|
146
|
+
const oauthRedirectUrls = defaultOriginsToAllow.length === 0
|
|
147
|
+
? config.cms.environment.PAPYRUS_OAUTH_REDIRECT_URLS
|
|
148
|
+
: Fn.join(",", [config.cms.environment.PAPYRUS_OAUTH_REDIRECT_URLS, ...defaultOriginsToAllow]);
|
|
149
|
+
|
|
67
150
|
const productionBranch = new amplify.CfnBranch(this, "Branch", {
|
|
68
151
|
appId: cmsApp.attrAppId,
|
|
69
152
|
branchName: "main",
|
|
@@ -72,59 +155,147 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
72
155
|
enablePerformanceMode: false,
|
|
73
156
|
environmentVariables: environmentVariables({
|
|
74
157
|
...config.cms.environment,
|
|
158
|
+
PAPYRUS_COGNITO_DOMAIN_PREFIX: config.cms.cognitoDomainPrefix,
|
|
159
|
+
...(config.cms.applyCognitoDomainPrefix ? { PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX: "true" } : {}),
|
|
160
|
+
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
75
161
|
PAPYRUS_SITE_BRAND: config.brand,
|
|
76
162
|
SITE_ENV: "production",
|
|
77
163
|
PAPYRUS_CONTENT_SOURCE: "published",
|
|
164
|
+
...(revalidateSecretParameterName ? { PAPYRUS_REVALIDATE_SECRET_PARAMETER: revalidateSecretParameterName } : {}),
|
|
165
|
+
...prospectusEnvironment,
|
|
78
166
|
}),
|
|
79
167
|
});
|
|
80
168
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
169
|
+
let stagingBranch: amplify.CfnBranch | undefined;
|
|
170
|
+
if (stagingEnabled) {
|
|
171
|
+
stagingBranch = new amplify.CfnBranch(this, "StagingBranch", {
|
|
172
|
+
appId: cmsApp.attrAppId,
|
|
173
|
+
branchName: "staging",
|
|
174
|
+
stage: "BETA",
|
|
175
|
+
enableAutoBuild: true,
|
|
176
|
+
enablePerformanceMode: false,
|
|
177
|
+
buildSpec: cmsStagingBuildSpec(config),
|
|
178
|
+
environmentVariables: environmentVariables({
|
|
179
|
+
...config.cms.environment,
|
|
180
|
+
PAPYRUS_COGNITO_DOMAIN_PREFIX: config.cms.cognitoDomainPrefix,
|
|
181
|
+
...(config.cms.applyCognitoDomainPrefix ? { PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX: "true" } : {}),
|
|
182
|
+
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
183
|
+
PAPYRUS_SITE_BRAND: config.brand,
|
|
184
|
+
SITE_ENV: "staging",
|
|
185
|
+
...stagingMode,
|
|
186
|
+
...prospectusEnvironment,
|
|
187
|
+
NEXT_PUBLIC_PAPYRUS_STAGING_URL: stagingDomainName
|
|
188
|
+
? `https://${stagingDomainName}`
|
|
189
|
+
: Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain]),
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
96
192
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
193
|
+
if (stagingDomainName && !sharedRootDomain) {
|
|
194
|
+
const stagingDomain = new amplify.CfnDomain(this, "StagingDomain", {
|
|
195
|
+
appId: cmsApp.attrAppId,
|
|
196
|
+
domainName: stagingDomainName,
|
|
197
|
+
subDomainSettings: [{ branchName: "staging", prefix: "" }],
|
|
198
|
+
});
|
|
199
|
+
stagingDomain.addResourceDependency(stagingBranch);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
103
202
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
203
|
+
if (config.cms.domainName) {
|
|
204
|
+
const subDomainSettings = sharedRootDomain
|
|
205
|
+
? [
|
|
206
|
+
{ branchName: "main", prefix: config.cms.domainPrefix as string },
|
|
207
|
+
...(config.cms.redirects ?? []).map((redirect) => ({
|
|
208
|
+
branchName: "main",
|
|
209
|
+
prefix: resolveRedirectSubDomainPrefix(config.cms.domainName as string, redirect.source),
|
|
210
|
+
})),
|
|
211
|
+
...(stagingBranch ? [{ branchName: "staging", prefix: config.cms.stagingDomainPrefix as string }] : []),
|
|
212
|
+
]
|
|
213
|
+
: [{ branchName: "main", prefix: "" }];
|
|
214
|
+
const productionDomain = new amplify.CfnDomain(this, "Domain", {
|
|
215
|
+
appId: cmsApp.attrAppId,
|
|
216
|
+
domainName: config.cms.domainName,
|
|
217
|
+
subDomainSettings,
|
|
218
|
+
});
|
|
219
|
+
productionDomain.addResourceDependency(productionBranch);
|
|
220
|
+
if (sharedRootDomain && stagingBranch) {
|
|
221
|
+
productionDomain.addResourceDependency(stagingBranch);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
110
224
|
|
|
111
225
|
this.cmsAppId = cmsApp.attrAppId;
|
|
112
226
|
new CfnOutput(this, "CmsAppId", { value: cmsApp.attrAppId, description: `Amplify app id for ${cmsAppName}` });
|
|
113
|
-
new CfnOutput(this, "CmsOrigin", { value:
|
|
114
|
-
new CfnOutput(this, "
|
|
227
|
+
new CfnOutput(this, "CmsOrigin", { value: productionOrigin });
|
|
228
|
+
new CfnOutput(this, "CmsComputeRoleArn", {
|
|
229
|
+
value: computeRole.roleArn,
|
|
230
|
+
description: `SSR compute role for ${cmsAppName}; the roleArn of its Prospectus site registration`,
|
|
231
|
+
});
|
|
232
|
+
if (stagingEnabled) {
|
|
233
|
+
new CfnOutput(this, "StagingOrigin", { value: stagingOrigin });
|
|
234
|
+
}
|
|
115
235
|
|
|
116
236
|
if (config.reader) {
|
|
117
|
-
this.readerAppId = this.addReaderApp(config, config.reader
|
|
237
|
+
this.readerAppId = this.addReaderApp(config, config.reader);
|
|
118
238
|
}
|
|
119
239
|
|
|
240
|
+
const amplifyAppIds = [cmsApp.attrAppId, ...(this.readerAppId ? [this.readerAppId] : [])];
|
|
241
|
+
this.addAuthoringRole(config, cmsApp.attrAppId, amplifyAppIds);
|
|
242
|
+
|
|
120
243
|
if (config.github) {
|
|
121
|
-
this.addGithubCiRole(config, config.github,
|
|
244
|
+
this.addGithubCiRole(config, config.github, cmsApp.attrAppId, amplifyAppIds);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private authoringStatements(config: AmplifyAppShellSiteConfig, cmsAppId: string, amplifyAppIds: string[]): iam.PolicyStatement[] {
|
|
249
|
+
const mediaBucketArn = `arn:${this.partition}:s3:::amplify-${cmsAppId}-*`;
|
|
250
|
+
return [
|
|
251
|
+
new iam.PolicyStatement({
|
|
252
|
+
sid: "SignAppSyncAuthoringRequests",
|
|
253
|
+
actions: ["appsync:GraphQL"],
|
|
254
|
+
resources: ["Query", "Mutation"].map((typeName) =>
|
|
255
|
+
this.formatArn({ service: "appsync", resource: "apis", resourceName: `*/types/${typeName}/fields/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
256
|
+
),
|
|
257
|
+
}),
|
|
258
|
+
new iam.PolicyStatement({
|
|
259
|
+
sid: "ReadWriteMediaAndPreview",
|
|
260
|
+
actions: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
|
261
|
+
resources: [`${mediaBucketArn}/media/*`, `${mediaBucketArn}/${resolveStoragePreviewPrefix(config)}*`],
|
|
262
|
+
}),
|
|
263
|
+
new iam.PolicyStatement({
|
|
264
|
+
sid: "ListMediaAndPreview",
|
|
265
|
+
actions: ["s3:ListBucket"],
|
|
266
|
+
resources: [mediaBucketArn],
|
|
267
|
+
conditions: { StringLike: { "s3:prefix": ["media/*", `${resolveStoragePreviewPrefix(config)}*`] } },
|
|
268
|
+
}),
|
|
269
|
+
new iam.PolicyStatement({
|
|
270
|
+
sid: "StartAndInspectAmplifyJobs",
|
|
271
|
+
actions: ["amplify:StartJob", "amplify:ListJobs", "amplify:GetJob", "amplify:ListBranches"],
|
|
272
|
+
resources: amplifyAppIds.flatMap((appId) => [
|
|
273
|
+
this.formatArn({ service: "amplify", resource: "apps", resourceName: appId, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
274
|
+
this.formatArn({ service: "amplify", resource: "apps", resourceName: `${appId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
275
|
+
]),
|
|
276
|
+
}),
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private addAuthoringRole(config: AmplifyAppShellSiteConfig, cmsAppId: string, amplifyAppIds: string[]): void {
|
|
281
|
+
const authoringRole = new iam.Role(this, "PapyrusAuthoringRole", {
|
|
282
|
+
roleName: `${config.siteId}-papyrus-authoring`,
|
|
283
|
+
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.`,
|
|
284
|
+
assumedBy: new iam.AccountRootPrincipal(),
|
|
285
|
+
});
|
|
286
|
+
for (const statement of this.authoringStatements(config, cmsAppId, amplifyAppIds)) {
|
|
287
|
+
authoringRole.addToPolicy(statement);
|
|
122
288
|
}
|
|
289
|
+
new CfnOutput(this, "PapyrusAuthoringRoleArn", {
|
|
290
|
+
value: authoringRole.roleArn,
|
|
291
|
+
description: `Role the Papyrus CLI assumes for ${config.siteId} (profile role_arn)`,
|
|
292
|
+
});
|
|
123
293
|
}
|
|
124
294
|
|
|
125
295
|
private addGithubCiRole(
|
|
126
296
|
config: AmplifyAppShellSiteConfig,
|
|
127
297
|
github: NonNullable<AmplifyAppShellSiteConfig["github"]>,
|
|
298
|
+
cmsAppId: string,
|
|
128
299
|
amplifyAppIds: string[],
|
|
129
300
|
): void {
|
|
130
301
|
const provider = iam.OpenIdConnectProvider.fromOpenIdConnectProviderArn(
|
|
@@ -153,26 +324,9 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
153
324
|
}),
|
|
154
325
|
});
|
|
155
326
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
actions: ["amplify:StartJob", "amplify:ListJobs", "amplify:GetJob", "amplify:ListBranches"],
|
|
160
|
-
resources: amplifyAppIds.flatMap((appId) => [
|
|
161
|
-
this.formatArn({ service: "amplify", resource: "apps", resourceName: appId, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
162
|
-
this.formatArn({ service: "amplify", resource: "apps", resourceName: `${appId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
163
|
-
]),
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
ciRole.addToPolicy(
|
|
167
|
-
new iam.PolicyStatement({
|
|
168
|
-
sid: "ReadSiteSecretsForJwtMinting",
|
|
169
|
-
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
170
|
-
resources: [
|
|
171
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/${this.cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
172
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/shared/${this.cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
173
|
-
],
|
|
174
|
-
}),
|
|
175
|
-
);
|
|
327
|
+
for (const statement of this.authoringStatements(config, cmsAppId, amplifyAppIds)) {
|
|
328
|
+
ciRole.addToPolicy(statement);
|
|
329
|
+
}
|
|
176
330
|
ciRole.addToPolicy(
|
|
177
331
|
new iam.PolicyStatement({
|
|
178
332
|
sid: "WhoAmI",
|
|
@@ -186,7 +340,7 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
186
340
|
sid: "DeployThisSitesAppShellStack",
|
|
187
341
|
actions: ["cloudformation:*"],
|
|
188
342
|
resources: [
|
|
189
|
-
this.formatArn({ service: "cloudformation", resource: "stack", resourceName:
|
|
343
|
+
this.formatArn({ service: "cloudformation", resource: "stack", resourceName: `${resolveStackName(config)}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
190
344
|
],
|
|
191
345
|
}),
|
|
192
346
|
);
|
|
@@ -198,25 +352,14 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
198
352
|
private addReaderApp(
|
|
199
353
|
config: AmplifyAppShellSiteConfig,
|
|
200
354
|
reader: NonNullable<AmplifyAppShellSiteConfig["reader"]>,
|
|
201
|
-
cmsAppId: string,
|
|
202
355
|
): string {
|
|
203
356
|
const readerAppName = reader.appName ?? `${config.siteId}-reader`;
|
|
204
357
|
const readerBranchName = reader.branchName ?? "main";
|
|
205
358
|
|
|
206
359
|
const readerServiceRole = new iam.Role(this, "ReaderServiceRole", {
|
|
207
360
|
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
208
|
-
description: `Static reader build role for Amplify app ${readerAppName} (reads
|
|
361
|
+
description: `Static reader build role for Amplify app ${readerAppName} (reads published media only; content is read as a Cognito guest)`,
|
|
209
362
|
});
|
|
210
|
-
readerServiceRole.addToPolicy(
|
|
211
|
-
new iam.PolicyStatement({
|
|
212
|
-
sid: "ReadSiteSecretsForJwtMinting",
|
|
213
|
-
actions: ["ssm:GetParameter", "ssm:GetParameters"],
|
|
214
|
-
resources: [
|
|
215
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/${cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
216
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: `amplify/shared/${cmsAppId}/*`, arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
217
|
-
],
|
|
218
|
-
}),
|
|
219
|
-
);
|
|
220
363
|
readerServiceRole.addToPolicy(
|
|
221
364
|
new iam.PolicyStatement({
|
|
222
365
|
sid: "ReadMediaForExport",
|
|
@@ -231,6 +374,11 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
231
374
|
platform: "WEB",
|
|
232
375
|
iamServiceRole: readerServiceRole.roleArn,
|
|
233
376
|
buildSpec: readerBuildSpec(config),
|
|
377
|
+
customRules: [
|
|
378
|
+
...(reader.redirects ?? []),
|
|
379
|
+
{ source: "/<*>", target: "/404.html", status: "404-200" },
|
|
380
|
+
],
|
|
381
|
+
customHeaders: readerCustomHeaders(reader.customHeaders),
|
|
234
382
|
});
|
|
235
383
|
|
|
236
384
|
const readerBranch = new amplify.CfnBranch(this, "ReaderBranch", {
|
|
@@ -245,15 +393,21 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
245
393
|
}),
|
|
246
394
|
});
|
|
247
395
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
396
|
+
if (reader.domainName) {
|
|
397
|
+
const readerDomain = new amplify.CfnDomain(this, "ReaderDomain", {
|
|
398
|
+
appId: readerApp.attrAppId,
|
|
399
|
+
domainName: reader.domainName,
|
|
400
|
+
subDomainSettings: (reader.domainPrefixes ?? [""]).map((prefix) => ({ branchName: readerBranchName, prefix })),
|
|
401
|
+
});
|
|
402
|
+
readerDomain.addResourceDependency(readerBranch);
|
|
403
|
+
}
|
|
254
404
|
|
|
255
405
|
new CfnOutput(this, "ReaderAppId", { value: readerApp.attrAppId, description: `Amplify app id for ${readerAppName}` });
|
|
256
|
-
new CfnOutput(this, "ReaderOrigin", {
|
|
406
|
+
new CfnOutput(this, "ReaderOrigin", {
|
|
407
|
+
value: reader.domainName
|
|
408
|
+
? `https://${reader.domainName}/`
|
|
409
|
+
: Fn.join("", [`https://${readerBranchName}.`, readerApp.attrDefaultDomain, "/"]),
|
|
410
|
+
});
|
|
257
411
|
return readerApp.attrAppId;
|
|
258
412
|
}
|
|
259
413
|
}
|
package/infra/build-specs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveStoragePreviewPrefix } from "./site-config.js";
|
|
1
|
+
import { hasCmsApp, resolveStoragePreviewPrefix } from "./site-config.js";
|
|
2
2
|
export function papyrusVersionToPep440(version) {
|
|
3
3
|
const match = /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z-]+\.(\d+))?$/.exec(version);
|
|
4
4
|
if (!match)
|
|
@@ -14,6 +14,8 @@ function commandLines(commands, indent) {
|
|
|
14
14
|
}
|
|
15
15
|
const NPM_CLEAN_INSTALL = "npm ci --cache .npm --prefer-offline";
|
|
16
16
|
const FRONTEND_ENVIRONMENT_CAPTURE = "env | grep -e '^PAPYRUS_' -e '^SITE_ENV=' -e '^NEXT_PUBLIC_' >> .env.production";
|
|
17
|
+
const CHECKOUT_AS_PAPYRUS_ROOT = "PAPYRUS_ROOT=\"$PWD\"";
|
|
18
|
+
const RUNTIME_OUTPUTS_COPY = "cp amplify_outputs.json .next/amplify_outputs.json";
|
|
17
19
|
function pythonProvisioningCommands(config) {
|
|
18
20
|
return [
|
|
19
21
|
"export UV_CACHE_DIR=\"$PWD/.uv-cache\"",
|
|
@@ -30,6 +32,9 @@ function reader(config) {
|
|
|
30
32
|
throw new Error(`site ${config.siteId} has no reader app (frontend ${config.frontend})`);
|
|
31
33
|
return config.reader;
|
|
32
34
|
}
|
|
35
|
+
function stagingPreviewFor(config) {
|
|
36
|
+
return config.cms?.stagingPreview ?? config.reader;
|
|
37
|
+
}
|
|
33
38
|
function frontendSection(preBuildCommands, buildCommands, artifactsDirectory, cachePaths) {
|
|
34
39
|
const lines = ["frontend:", " phases:"];
|
|
35
40
|
if (preBuildCommands.length > 0) {
|
|
@@ -50,20 +55,28 @@ export function cmsProductionBuildSpec(config) {
|
|
|
50
55
|
"if [ -z \"${PAPYRUS_CONSOLE_RESPONDER_IMAGE_URI:-}\" ]; then export PAPYRUS_CONSOLE_RESPONDER_ALLOW_LOCAL_BUILD=true; fi",
|
|
51
56
|
"npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID",
|
|
52
57
|
], " "),
|
|
53
|
-
...frontendSection([], [
|
|
58
|
+
...frontendSection([], [
|
|
59
|
+
FRONTEND_ENVIRONMENT_CAPTURE,
|
|
60
|
+
"npx papyrus-app sync && npm run build",
|
|
61
|
+
...(config.frontend === "pretext" ? [RUNTIME_OUTPUTS_COPY] : []),
|
|
62
|
+
], ".next", [".next/cache/**/*", ".npm/**/*"]),
|
|
54
63
|
].join("\n") + "\n";
|
|
55
64
|
}
|
|
56
65
|
export function cmsStagingBuildSpec(config) {
|
|
57
66
|
const preBuild = [NPM_CLEAN_INSTALL, "npx ampx generate outputs --branch main --app-id $AWS_APP_ID"];
|
|
58
67
|
const cachePaths = [".next/cache/**/*", ".npm/**/*"];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
preBuild.push(...pythonProvisioningCommands(config),
|
|
68
|
+
const preview = config.frontend === "markus-static" ? stagingPreviewFor(config) : undefined;
|
|
69
|
+
if (preview !== undefined) {
|
|
70
|
+
preBuild.push(...pythonProvisioningCommands(config), `${CHECKOUT_AS_PAPYRUS_ROOT} papyrus ops content export-published --drafts --out content-export --clean`, preview.buildCommand, `${CHECKOUT_AS_PAPYRUS_ROOT} papyrus ops content upload-preview --dir ${preview.baseDirectory} --prefix ${resolveStoragePreviewPrefix(config)}`);
|
|
62
71
|
cachePaths.push(".uv-cache/**/*");
|
|
63
72
|
}
|
|
64
73
|
return [
|
|
65
74
|
"version: 1",
|
|
66
|
-
...frontendSection(preBuild, [
|
|
75
|
+
...frontendSection(preBuild, [
|
|
76
|
+
FRONTEND_ENVIRONMENT_CAPTURE,
|
|
77
|
+
"npx papyrus-app sync && npm run build",
|
|
78
|
+
RUNTIME_OUTPUTS_COPY,
|
|
79
|
+
], ".next", cachePaths),
|
|
67
80
|
].join("\n") + "\n";
|
|
68
81
|
}
|
|
69
82
|
export function readerBuildSpec(config) {
|
|
@@ -72,12 +85,14 @@ export function readerBuildSpec(config) {
|
|
|
72
85
|
"version: 1",
|
|
73
86
|
...frontendSection([
|
|
74
87
|
...pythonProvisioningCommands(config),
|
|
75
|
-
|
|
76
|
-
"papyrus content export-published --out content-export --clean",
|
|
88
|
+
...(readerConfig.preBuildCommands ?? []),
|
|
89
|
+
...(readerConfig.skipContentExport ? [] : ["papyrus ops content export-published --auth guest --out content-export --clean"]),
|
|
77
90
|
], [readerConfig.buildCommand], readerConfig.baseDirectory, [".uv-cache/**/*"]),
|
|
78
91
|
].join("\n") + "\n";
|
|
79
92
|
}
|
|
80
93
|
export function buildSpecFor(config, app) {
|
|
94
|
+
if (app !== "reader" && !hasCmsApp(config))
|
|
95
|
+
throw new Error(`site ${config.siteId} has no cms app`);
|
|
81
96
|
if (app === "cms-production")
|
|
82
97
|
return cmsProductionBuildSpec(config);
|
|
83
98
|
if (app === "cms-staging")
|
package/infra/build-specs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AmplifyAppShellSiteConfig, resolveStoragePreviewPrefix } from "./site-config";
|
|
1
|
+
import { AmplifyAppShellSiteConfig, hasCmsApp, resolveStoragePreviewPrefix } from "./site-config";
|
|
2
2
|
|
|
3
3
|
export type BuildSpecAppName = "cms-production" | "cms-staging" | "reader";
|
|
4
4
|
|
|
@@ -22,6 +22,10 @@ const NPM_CLEAN_INSTALL = "npm ci --cache .npm --prefer-offline";
|
|
|
22
22
|
const FRONTEND_ENVIRONMENT_CAPTURE =
|
|
23
23
|
"env | grep -e '^PAPYRUS_' -e '^SITE_ENV=' -e '^NEXT_PUBLIC_' >> .env.production";
|
|
24
24
|
|
|
25
|
+
const CHECKOUT_AS_PAPYRUS_ROOT = "PAPYRUS_ROOT=\"$PWD\"";
|
|
26
|
+
|
|
27
|
+
const RUNTIME_OUTPUTS_COPY = "cp amplify_outputs.json .next/amplify_outputs.json";
|
|
28
|
+
|
|
25
29
|
function pythonProvisioningCommands(config: AmplifyAppShellSiteConfig): string[] {
|
|
26
30
|
return [
|
|
27
31
|
"export UV_CACHE_DIR=\"$PWD/.uv-cache\"",
|
|
@@ -39,6 +43,10 @@ function reader(config: AmplifyAppShellSiteConfig): NonNullable<AmplifyAppShellS
|
|
|
39
43
|
return config.reader;
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
function stagingPreviewFor(config: AmplifyAppShellSiteConfig): { buildCommand: string; baseDirectory: string } | undefined {
|
|
47
|
+
return config.cms?.stagingPreview ?? config.reader;
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
function frontendSection(preBuildCommands: string[], buildCommands: string[], artifactsDirectory: string, cachePaths: string[]): string[] {
|
|
43
51
|
const lines = ["frontend:", " phases:"];
|
|
44
52
|
if (preBuildCommands.length > 0) {
|
|
@@ -76,7 +84,11 @@ export function cmsProductionBuildSpec(config: AmplifyAppShellSiteConfig): strin
|
|
|
76
84
|
),
|
|
77
85
|
...frontendSection(
|
|
78
86
|
[],
|
|
79
|
-
[
|
|
87
|
+
[
|
|
88
|
+
FRONTEND_ENVIRONMENT_CAPTURE,
|
|
89
|
+
"npx papyrus-app sync && npm run build",
|
|
90
|
+
...(config.frontend === "pretext" ? [RUNTIME_OUTPUTS_COPY] : []),
|
|
91
|
+
],
|
|
80
92
|
".next",
|
|
81
93
|
[".next/cache/**/*", ".npm/**/*"],
|
|
82
94
|
),
|
|
@@ -86,14 +98,13 @@ export function cmsProductionBuildSpec(config: AmplifyAppShellSiteConfig): strin
|
|
|
86
98
|
export function cmsStagingBuildSpec(config: AmplifyAppShellSiteConfig): string {
|
|
87
99
|
const preBuild = [NPM_CLEAN_INSTALL, "npx ampx generate outputs --branch main --app-id $AWS_APP_ID"];
|
|
88
100
|
const cachePaths = [".next/cache/**/*", ".npm/**/*"];
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
const preview = config.frontend === "markus-static" ? stagingPreviewFor(config) : undefined;
|
|
102
|
+
if (preview !== undefined) {
|
|
91
103
|
preBuild.push(
|
|
92
104
|
...pythonProvisioningCommands(config),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`papyrus content upload-preview --dir ${readerConfig.baseDirectory} --prefix ${resolveStoragePreviewPrefix(config)}`,
|
|
105
|
+
`${CHECKOUT_AS_PAPYRUS_ROOT} papyrus ops content export-published --drafts --out content-export --clean`,
|
|
106
|
+
preview.buildCommand,
|
|
107
|
+
`${CHECKOUT_AS_PAPYRUS_ROOT} papyrus ops content upload-preview --dir ${preview.baseDirectory} --prefix ${resolveStoragePreviewPrefix(config)}`,
|
|
97
108
|
);
|
|
98
109
|
cachePaths.push(".uv-cache/**/*");
|
|
99
110
|
}
|
|
@@ -101,7 +112,11 @@ export function cmsStagingBuildSpec(config: AmplifyAppShellSiteConfig): string {
|
|
|
101
112
|
"version: 1",
|
|
102
113
|
...frontendSection(
|
|
103
114
|
preBuild,
|
|
104
|
-
[
|
|
115
|
+
[
|
|
116
|
+
FRONTEND_ENVIRONMENT_CAPTURE,
|
|
117
|
+
"npx papyrus-app sync && npm run build",
|
|
118
|
+
RUNTIME_OUTPUTS_COPY,
|
|
119
|
+
],
|
|
105
120
|
".next",
|
|
106
121
|
cachePaths,
|
|
107
122
|
),
|
|
@@ -115,8 +130,8 @@ export function readerBuildSpec(config: AmplifyAppShellSiteConfig): string {
|
|
|
115
130
|
...frontendSection(
|
|
116
131
|
[
|
|
117
132
|
...pythonProvisioningCommands(config),
|
|
118
|
-
|
|
119
|
-
"papyrus content export-published --out content-export --clean",
|
|
133
|
+
...(readerConfig.preBuildCommands ?? []),
|
|
134
|
+
...(readerConfig.skipContentExport ? [] : ["papyrus ops content export-published --auth guest --out content-export --clean"]),
|
|
120
135
|
],
|
|
121
136
|
[readerConfig.buildCommand],
|
|
122
137
|
readerConfig.baseDirectory,
|
|
@@ -126,6 +141,7 @@ export function readerBuildSpec(config: AmplifyAppShellSiteConfig): string {
|
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
export function buildSpecFor(config: AmplifyAppShellSiteConfig, app: BuildSpecAppName): string {
|
|
144
|
+
if (app !== "reader" && !hasCmsApp(config)) throw new Error(`site ${config.siteId} has no cms app`);
|
|
129
145
|
if (app === "cms-production") return cmsProductionBuildSpec(config);
|
|
130
146
|
if (app === "cms-staging") return cmsStagingBuildSpec(config);
|
|
131
147
|
return readerBuildSpec(config);
|