@anthusai/papyrus 2.0.0-next.5 → 2.0.0-next.50
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/data/resource.js +23 -3
- package/amplify/data/resource.ts +25 -3
- package/amplify/data/schema.js +2028 -1979
- package/amplify/data/schema.ts +72 -14
- package/amplify/functions/assignment-action/data-access.js +90 -0
- package/amplify/functions/assignment-action/data-access.ts +89 -0
- package/amplify/functions/assignment-action/handler.ts +1 -1
- package/amplify/functions/content-actions/handler.py +53 -2
- package/amplify/functions/content-actions/requirements.txt +1 -0
- package/amplify/functions/content-actions/resource.js +1 -1
- package/amplify/functions/content-actions/resource.ts +1 -1
- package/amplify/functions/editorial-checks/handler.py +14 -0
- package/amplify/functions/editorial-checks/requirements.txt +1 -0
- package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.js +7 -7
- package/amplify/{plugins/editorial-checks/function → functions/editorial-checks}/resource.ts +7 -7
- package/amplify/functions/editorial-checks/schema.js +121 -0
- package/amplify/{plugins → functions}/editorial-checks/schema.ts +33 -6
- package/amplify/functions/editorial-checks/wire.js +53 -0
- package/amplify/functions/editorial-checks/wire.ts +61 -0
- package/amplify/functions/monitoring-view/handler.py +11 -0
- package/amplify/functions/monitoring-view/resource.js +24 -0
- package/amplify/functions/monitoring-view/resource.ts +34 -0
- package/amplify/functions/monitoring-view/schema.js +16 -0
- package/amplify/functions/monitoring-view/schema.ts +18 -0
- package/amplify/functions/monitoring-view/wire.js +37 -0
- package/amplify/functions/monitoring-view/wire.ts +47 -0
- package/amplify/functions/public-forms/core.js +247 -0
- package/amplify/functions/public-forms/core.ts +310 -0
- package/amplify/functions/public-forms/handler.ts +41 -0
- package/amplify/functions/public-forms/origins.js +35 -0
- package/amplify/functions/public-forms/origins.ts +43 -0
- package/amplify/functions/public-forms/resource.js +9 -0
- package/amplify/functions/public-forms/resource.ts +10 -0
- package/amplify/functions/public-forms/wire.js +25 -0
- package/amplify/functions/public-forms/wire.ts +32 -0
- package/amplify/functions/shared/editorial-context.ts +63 -4
- package/amplify/functions/shared/python-bundle.js +20 -14
- package/amplify/functions/shared/python-bundle.ts +25 -18
- package/amplify/plugins/newsletter/config.js +195 -0
- package/amplify/plugins/newsletter/config.ts +240 -0
- package/amplify/plugins/newsletter/function/handler.py +6 -0
- package/amplify/plugins/newsletter/function/requirements.txt +5 -0
- package/amplify/plugins/newsletter/ops/handler.py +12 -0
- package/amplify/plugins/newsletter/ops/resource.js +27 -0
- package/amplify/plugins/newsletter/ops/resource.ts +37 -0
- package/amplify/plugins/newsletter/papyrus.plugin.js +260 -0
- package/amplify/plugins/newsletter/papyrus.plugin.ts +308 -0
- package/amplify/plugins/newsletter/schema.js +56 -0
- package/amplify/plugins/newsletter/schema.ts +61 -0
- package/amplify/plugins/newsletter/secure-string-parameter.js +107 -0
- package/amplify/plugins/newsletter/secure-string-parameter.ts +120 -0
- package/amplify/plugins/newsletter/sending.js +140 -0
- package/amplify/plugins/newsletter/sending.ts +186 -0
- package/amplify/plugins/reader-steering/config.js +162 -0
- package/amplify/plugins/reader-steering/config.ts +248 -0
- package/amplify/plugins/reader-steering/function/ai_moderation.py +230 -0
- package/amplify/plugins/reader-steering/function/handler.py +1363 -0
- package/amplify/plugins/reader-steering/function/moderation-prompt.md +27 -0
- package/amplify/plugins/reader-steering/function/moderation.py +316 -0
- package/amplify/plugins/reader-steering/function/resource.js +27 -0
- package/amplify/plugins/reader-steering/function/resource.ts +35 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.js +175 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +201 -0
- package/amplify/plugins/reader-steering/schema.js +140 -0
- package/amplify/plugins/reader-steering/schema.ts +142 -0
- package/amplify/plugins/registry.js +4 -2
- package/amplify/plugins/registry.ts +4 -2
- package/amplify/plugins/types.ts +5 -0
- package/amplify/plugins/wire.js +1 -0
- package/amplify/plugins/wire.ts +1 -0
- package/amplify/publication/redirect-and-index.js +105 -0
- package/amplify/publication/redirect-rules.js +73 -0
- package/amplify/publication/redirect-rules.ts +93 -0
- package/amplify/publication/s3-projection.js +7 -1
- package/amplify/publication/s3-projection.ts +9 -1
- package/amplify/site-backend-config.ts +17 -0
- package/amplify/site-backend.js +36 -1
- package/amplify/site-backend.ts +44 -1
- package/app/api/newsletter/confirm/route.ts +13 -0
- package/app/api/newsletter/preferences/route.ts +13 -0
- package/app/api/newsletter/subscribe/route.ts +9 -0
- package/app/api/newsletter/unsubscribe/route.ts +13 -0
- package/app/api/newsroom/curation/context/route.ts +9 -0
- package/app/api/newsroom/curation/priming/route.ts +9 -0
- package/app/api/newsroom/curation/publication/route.ts +9 -0
- package/app/api/reader/comment/delete/route.ts +9 -0
- package/app/api/reader/comment/edit/route.ts +9 -0
- package/app/api/reader/comment/route.ts +9 -0
- package/app/api/reader/handle/route.ts +9 -0
- package/app/api/reader/me/route.ts +9 -0
- package/app/api/reader/rating/route.ts +9 -0
- package/app/api/reader/report/route.ts +9 -0
- package/app/api/reader/retired/route.ts +9 -0
- package/app/api/reader/submit/route.ts +9 -0
- package/app/api/reader/vote/route.ts +9 -0
- package/bin/papyrus-infra.mjs +20 -1
- package/components/editorial-checks-admin-panel.tsx +36 -11
- package/components/moderation-panel.tsx +248 -0
- package/components/monitoring-overview-view.tsx +241 -0
- package/components/monitoring-panel.tsx +40 -0
- package/components/newsletter-panel.tsx +474 -0
- package/components/newsletter-sections.tsx +163 -0
- package/components/newsroom-article-editor.tsx +2 -4
- package/components/newsroom-board-view.tsx +4 -1
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-organizer.css +31 -0
- package/components/newsroom-references-view.tsx +14 -14
- package/components/publication-cyclotron-panel.tsx +15 -10
- package/components/reference-add-entry.tsx +75 -0
- package/components/reference-curation-controls.tsx +31 -22
- package/components/reference-cyclotron-priming.tsx +58 -19
- package/components/reference-cyclotron-suggestion.tsx +1 -1
- package/components/reference-priming.tsx +14 -10
- package/components/reference-section-suggestion.tsx +1 -1
- package/components/topic-steering-workspace.tsx +64 -20
- package/components/use-editorial-checks.ts +12 -10
- package/infra/amplify-app-shell.js +90 -16
- package/infra/amplify-app-shell.ts +102 -18
- package/infra/index.js +1 -0
- package/infra/secure-string-parameter-handler.js +41 -0
- package/infra/secure-string-parameter-handler.ts +41 -0
- package/infra/secure-string-parameter.js +79 -0
- package/infra/secure-string-parameter.ts +91 -0
- package/infra/site-config.js +85 -6
- package/infra/site-config.ts +123 -6
- package/lib/category-repository.ts +11 -9
- package/lib/curation-routes.ts +345 -0
- package/lib/editorial-checks-backend.ts +1 -1
- package/lib/editorial-checks-client.ts +19 -1
- package/lib/editorial-checks-demo.ts +2 -2
- package/lib/editorial-checks-settings.ts +3 -0
- package/lib/editorial-checks.ts +8 -11
- package/lib/editorial-preview.ts +5 -5
- package/lib/monitoring-client.ts +29 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +145 -0
- package/lib/newsletter-api.ts +391 -0
- package/lib/newsletter-client.ts +46 -0
- package/lib/newsletter-fixture.ts +184 -0
- package/lib/newsletter-model.ts +202 -0
- package/lib/newsletter-pages.ts +264 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/prospectus-session.ts +15 -14
- package/lib/prospectus-sign-in-routes.ts +54 -13
- package/lib/prospectus-site-identity.ts +24 -1
- package/lib/reader-api.ts +145 -0
- package/lib/reader-moderation.ts +220 -0
- package/lib/reader-retirement-route.ts +89 -0
- package/lib/reader-subject.md +29 -0
- package/lib/reader-subject.ts +53 -0
- package/lib/relevance-status.ts +2 -2
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +6 -3
- package/lib/site-origin.ts +67 -0
- package/middleware.ts +14 -3
- package/package.json +42 -4
- package/routes.manifest.json +191 -1
- package/amplify/plugins/editorial-checks/function/handler.py +0 -11
- package/amplify/plugins/editorial-checks/function/requirements.txt +0 -1
- package/amplify/plugins/editorial-checks/papyrus.plugin.js +0 -50
- package/amplify/plugins/editorial-checks/papyrus.plugin.ts +0 -63
- package/amplify/plugins/editorial-checks/schema.js +0 -94
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useId, useRef, useState, type KeyboardEvent } from "react";
|
|
4
4
|
import { primingStateText, type PrimingRequestView } from "../lib/relevance-status";
|
|
5
5
|
|
|
6
6
|
export type PrimingFormInput = { url: string; label: string; explanation: string };
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* The "Paste a URL" option of the Add a reference area. The page is read in the
|
|
10
10
|
* background and the outcome is shown beside each added reference.
|
|
11
11
|
*/
|
|
12
12
|
export function ReferenceCyclotronPriming({
|
|
@@ -23,6 +23,19 @@ export function ReferenceCyclotronPriming({
|
|
|
23
23
|
const [explanation, setExplanation] = useState("");
|
|
24
24
|
const [message, setMessage] = useState<string | null>(null);
|
|
25
25
|
|
|
26
|
+
const ratingLabelId = useId();
|
|
27
|
+
const buttons = useRef<Array<HTMLButtonElement | null>>([]);
|
|
28
|
+
|
|
29
|
+
function moveSelection(event: KeyboardEvent<HTMLButtonElement>, index: number) {
|
|
30
|
+
const forward = event.key === "ArrowRight" || event.key === "ArrowDown";
|
|
31
|
+
const backward = event.key === "ArrowLeft" || event.key === "ArrowUp";
|
|
32
|
+
if (!forward && !backward) return;
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
const next = (index + (forward ? 1 : -1) + labels.length) % labels.length;
|
|
35
|
+
setLabel(labels[next]);
|
|
36
|
+
buttons.current[next]?.focus();
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
async function submit() {
|
|
27
40
|
setMessage("Adding…");
|
|
28
41
|
try {
|
|
@@ -36,16 +49,16 @@ export function ReferenceCyclotronPriming({
|
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
return (
|
|
39
|
-
<
|
|
52
|
+
<div className="space-y-2 text-sm" data-reference-cyclotron-priming>
|
|
40
53
|
<form
|
|
41
|
-
className="flex flex-
|
|
54
|
+
className="flex flex-col gap-3"
|
|
42
55
|
onSubmit={(event) => {
|
|
43
56
|
event.preventDefault();
|
|
44
57
|
void submit();
|
|
45
58
|
}}
|
|
46
59
|
>
|
|
47
|
-
<label className="flex
|
|
48
|
-
<span className="text-xs text-muted-foreground">
|
|
60
|
+
<label className="flex flex-col gap-1">
|
|
61
|
+
<span className="text-xs text-muted-foreground">Web page address</span>
|
|
49
62
|
<input
|
|
50
63
|
aria-label="Reference URL"
|
|
51
64
|
onChange={(event) => setUrl(event.target.value)}
|
|
@@ -55,20 +68,46 @@ export function ReferenceCyclotronPriming({
|
|
|
55
68
|
value={url}
|
|
56
69
|
/>
|
|
57
70
|
</label>
|
|
71
|
+
<div className="flex flex-col gap-1">
|
|
72
|
+
<span className="text-xs text-muted-foreground" id={ratingLabelId}>Should Papyrus include or exclude pages like this?</span>
|
|
73
|
+
<div aria-labelledby={ratingLabelId} className="flex flex-wrap gap-2" role="radiogroup">
|
|
74
|
+
{labels.map((choice, index) => {
|
|
75
|
+
const checked = choice === label;
|
|
76
|
+
return (
|
|
77
|
+
<button
|
|
78
|
+
aria-checked={checked}
|
|
79
|
+
className={`min-w-32 flex-1 rounded border px-4 py-3 text-base font-semibold capitalize ${checked ? "border-foreground bg-foreground text-background" : "border-border bg-transparent"}`}
|
|
80
|
+
data-rating-choice={choice}
|
|
81
|
+
key={choice}
|
|
82
|
+
onClick={() => setLabel(choice)}
|
|
83
|
+
onKeyDown={(event) => moveSelection(event, index)}
|
|
84
|
+
ref={(node) => { buttons.current[index] = node; }}
|
|
85
|
+
role="radio"
|
|
86
|
+
tabIndex={checked || (!labels.includes(label) && index === 0) ? 0 : -1}
|
|
87
|
+
type="button"
|
|
88
|
+
>
|
|
89
|
+
{choice}
|
|
90
|
+
</button>
|
|
91
|
+
);
|
|
92
|
+
})}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
58
95
|
<label className="flex flex-col gap-1">
|
|
59
|
-
<span className="text-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
))}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<
|
|
96
|
+
<span className="text-sm font-medium">Why?</span>
|
|
97
|
+
<textarea
|
|
98
|
+
aria-label="Reason"
|
|
99
|
+
className="w-full"
|
|
100
|
+
onChange={(event) => setExplanation(event.target.value)}
|
|
101
|
+
placeholder="Why? A sentence or two helps Papyrus learn what you want."
|
|
102
|
+
rows={4}
|
|
103
|
+
value={explanation}
|
|
104
|
+
/>
|
|
105
|
+
<span className="text-xs text-muted-foreground">Optional, but a short reason helps Papyrus learn what you want.</span>
|
|
69
106
|
</label>
|
|
70
|
-
<
|
|
71
|
-
|
|
107
|
+
<div>
|
|
108
|
+
<button className="cyclotron-review-secondary" type="submit">Add reference</button>
|
|
109
|
+
</div>
|
|
110
|
+
{message ? <p className="text-xs" role="status">{message}</p> : null}
|
|
72
111
|
</form>
|
|
73
112
|
{requests.length ? (
|
|
74
113
|
<ul aria-label="Added references" className="space-y-1 text-xs text-muted-foreground">
|
|
@@ -79,6 +118,6 @@ export function ReferenceCyclotronPriming({
|
|
|
79
118
|
))}
|
|
80
119
|
</ul>
|
|
81
120
|
) : null}
|
|
82
|
-
</
|
|
121
|
+
</div>
|
|
83
122
|
);
|
|
84
123
|
}
|
|
@@ -60,7 +60,7 @@ export function ReferenceSuggestionBlock({
|
|
|
60
60
|
<section aria-label="Suggestion" className="space-y-2 border-y border-foreground py-3" data-reference-suggestion={referenceLineageId}>
|
|
61
61
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-foreground">Suggestion</h3>
|
|
62
62
|
<p className="text-sm text-foreground">
|
|
63
|
-
|
|
63
|
+
Papyrus suggests <span className="font-semibold">{decision.label}</span> ({suggestionConfidenceWords(decision)}).
|
|
64
64
|
</p>
|
|
65
65
|
<p className="text-xs text-foreground" data-reference-suggestion-notice>
|
|
66
66
|
A suggestion never changes the status until an editor acts. This reference is {statusLabel.toLowerCase()}.
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
4
3
|
import { PrimingPanel, type PrimingPanelProps } from "cyclotron/components/priming-panel";
|
|
5
|
-
import { Button } from "./ui/button";
|
|
6
4
|
|
|
7
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The "Import a file" option of the Add a reference area. Papyrus supplies the
|
|
7
|
+
* transport and configuration; Cyclotron owns the preview and label controls.
|
|
8
|
+
* It is only rendered by `ReferenceAddEntry`, never as a section of its own.
|
|
9
|
+
*/
|
|
8
10
|
export function ReferencePriming(props: PrimingPanelProps) {
|
|
9
|
-
const [open, setOpen] = useState(false);
|
|
10
11
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
<PrimingPanel
|
|
13
|
+
{...props}
|
|
14
|
+
renderItem={props.renderItem ?? ((item) => (
|
|
15
|
+
<div className="space-y-2">
|
|
16
|
+
<p className="whitespace-pre-wrap text-sm">{String(item.values.abstract ?? item.values.text ?? "")}</p>
|
|
17
|
+
<p className="break-all text-xs text-muted-foreground">{String(item.values.source_uri ?? "")}</p>
|
|
18
|
+
</div>
|
|
19
|
+
))}
|
|
20
|
+
/>
|
|
17
21
|
);
|
|
18
22
|
}
|
|
@@ -40,7 +40,7 @@ export function SectionSuggestionBlock({
|
|
|
40
40
|
<section aria-label="Suggested Section" className="space-y-2 border-y border-foreground py-3" data-reference-section-suggestion={referenceLineageId}>
|
|
41
41
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-foreground">Suggested Section</h3>
|
|
42
42
|
<p className="text-sm text-foreground">
|
|
43
|
-
|
|
43
|
+
Papyrus suggests <span className="font-semibold">{titleOf(sections, suggestion.sectionId)}</span>
|
|
44
44
|
{suggestion.confidence !== null ? ` (${formatPercentage(suggestion.confidence)})` : ""}.
|
|
45
45
|
</p>
|
|
46
46
|
{others.length ? (
|
|
@@ -14,6 +14,9 @@ import type { ReactNode, RefObject } from "react";
|
|
|
14
14
|
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
|
|
15
15
|
import amplifyOutputs from "papyrus-amplify-outputs";
|
|
16
16
|
import { EditorialChecksAdminPanel } from "./editorial-checks-admin-panel";
|
|
17
|
+
import { MonitoringPanel } from "./monitoring-panel";
|
|
18
|
+
import { ModerationPanel } from "./moderation-panel";
|
|
19
|
+
import { NewsletterPanel } from "./newsletter-panel";
|
|
17
20
|
import { SignupEntriesPanel } from "./signup-entries-panel";
|
|
18
21
|
import { pluginSettings, type PluginOutputs } from "../lib/plugin-settings";
|
|
19
22
|
import { generateClient } from "aws-amplify/data";
|
|
@@ -89,6 +92,7 @@ import { ReferenceSourcePreview } from "./reference-source-preview";
|
|
|
89
92
|
import type { ReaderAuthSnapshot } from "./reader-auth-state";
|
|
90
93
|
import { NewsDeskAccessGate } from "./newsroom-access-gate";
|
|
91
94
|
import { NewsroomOpsOverview } from "./newsroom-ops-overview";
|
|
95
|
+
import { NewsroomCurationBootstrap } from "./newsroom-curation-bootstrap";
|
|
92
96
|
import { NewsroomReferencesView } from "./newsroom-references-view";
|
|
93
97
|
import { NewsroomAssignmentsView } from "./newsroom-assignments-view";
|
|
94
98
|
import { NewsroomTopicsView } from "./newsroom-topics-view";
|
|
@@ -247,7 +251,7 @@ type AssignmentAction = "claim" | "release" | "complete" | "cancel" | "reopen" |
|
|
|
247
251
|
type ReportingPacketReviewDecision = "select" | "merge" | "brief" | "hold" | "kill";
|
|
248
252
|
type AssignmentDeskViewMode = "queue" | "budget";
|
|
249
253
|
type UserRoleAction = "grant" | "revoke";
|
|
250
|
-
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups" | "editorial-checks";
|
|
254
|
+
type AdministrationPanel = "users" | "policies" | "sections" | "procedures" | "delegations" | "signups" | "newsletter" | "moderation" | "editorial-checks" | "monitoring";
|
|
251
255
|
export type NewsDeskTab = "overview" | "desks" | "topics" | "concepts" | "references" | "messages" | "assignments" | "administration" | "search";
|
|
252
256
|
type LexicalRuleScope = "publication" | "corpus" | "classifier" | "category";
|
|
253
257
|
type AnalysisReindexMode = "online-update" | "classifier-retrain" | "scoped-topic-rebuild" | "entity-graph-rebuild" | "generated-analysis-rebuild";
|
|
@@ -879,7 +883,7 @@ function NewsDeskDashboard({
|
|
|
879
883
|
const [mergeSelection, setMergeSelection] = useState<MergeSelection | null>(null);
|
|
880
884
|
const [isPending, startTransition] = useTransition();
|
|
881
885
|
const controlsDisabled = isPending || !canEdit;
|
|
882
|
-
const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel);
|
|
886
|
+
const initialAdministrationPanel = normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo));
|
|
883
887
|
const [administrationPanel, setAdministrationPanel] = useState<AdministrationPanel>(initialAdministrationPanel);
|
|
884
888
|
const [doctrineDrafts, setDoctrineDrafts] = useState<DoctrineEditorState>(() => buildDoctrineEditorState(dashboard.doctrineRecords));
|
|
885
889
|
const [loadedSections, setLoadedSections] = useState<Record<string, boolean>>({
|
|
@@ -1120,8 +1124,8 @@ function NewsDeskDashboard({
|
|
|
1120
1124
|
}, [dashboard.procedureDefinitions, dashboard.procedureRuns, dashboard.procedureVersions]);
|
|
1121
1125
|
|
|
1122
1126
|
useEffect(() => {
|
|
1123
|
-
setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel));
|
|
1124
|
-
}, [initialSelection.panel]);
|
|
1127
|
+
setAdministrationPanel(normalizeAdministrationPanel(initialSelection.panel, Boolean(dashboard.isDemo)));
|
|
1128
|
+
}, [initialSelection.panel, dashboard.isDemo]);
|
|
1125
1129
|
|
|
1126
1130
|
useEffect(() => {
|
|
1127
1131
|
if (activeTab !== "administration") return;
|
|
@@ -3115,6 +3119,7 @@ function NewsDeskDashboard({
|
|
|
3115
3119
|
/>
|
|
3116
3120
|
) : null}
|
|
3117
3121
|
{activeTab === "references" ? (
|
|
3122
|
+
<NewsroomCurationBootstrap enabled={!dashboard.isDemo}>
|
|
3118
3123
|
<NewsroomReferencesView
|
|
3119
3124
|
demo={Boolean(dashboard.isDemo)}
|
|
3120
3125
|
disabled={controlsDisabled}
|
|
@@ -3152,6 +3157,7 @@ function NewsDeskDashboard({
|
|
|
3152
3157
|
referenceAttachments={referenceAttachments}
|
|
3153
3158
|
references={references}
|
|
3154
3159
|
/>
|
|
3160
|
+
</NewsroomCurationBootstrap>
|
|
3155
3161
|
) : null}
|
|
3156
3162
|
{activeTab === "messages" ? (
|
|
3157
3163
|
<MessagesDeskView
|
|
@@ -4318,17 +4324,24 @@ function AdministrationDeskView({
|
|
|
4318
4324
|
<strong>Agent delegations</strong>
|
|
4319
4325
|
<span>Let an agent curate References for you</span>
|
|
4320
4326
|
</Link>
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
>
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4327
|
+
<Link
|
|
4328
|
+
href={administrationPanelHref("editorial-checks", isDemo)}
|
|
4329
|
+
data-news-desk-admin-nav="editorial-checks"
|
|
4330
|
+
data-active={administrationPanel === "editorial-checks" || undefined}
|
|
4331
|
+
onClick={() => onPanelChange("editorial-checks")}
|
|
4332
|
+
>
|
|
4333
|
+
<strong>Editorial checks</strong>
|
|
4334
|
+
<span>Policies the article editor checks drafts against</span>
|
|
4335
|
+
</Link>
|
|
4336
|
+
<Link
|
|
4337
|
+
href={administrationPanelHref("monitoring", isDemo)}
|
|
4338
|
+
data-news-desk-admin-nav="monitoring"
|
|
4339
|
+
data-active={administrationPanel === "monitoring" || undefined}
|
|
4340
|
+
onClick={() => onPanelChange("monitoring")}
|
|
4341
|
+
>
|
|
4342
|
+
<strong>Monitoring</strong>
|
|
4343
|
+
<span>The automated research pipeline, read only</span>
|
|
4344
|
+
</Link>
|
|
4332
4345
|
{signupPluginEnabled() ? (
|
|
4333
4346
|
<Link
|
|
4334
4347
|
href={administrationPanelHref("signups", isDemo)}
|
|
@@ -4340,10 +4353,35 @@ function AdministrationDeskView({
|
|
|
4340
4353
|
<span>Addresses from the site's signup forms</span>
|
|
4341
4354
|
</Link>
|
|
4342
4355
|
) : null}
|
|
4356
|
+
{newsletterPluginEnabled(isDemo) ? (
|
|
4357
|
+
<Link
|
|
4358
|
+
href={administrationPanelHref("newsletter", isDemo)}
|
|
4359
|
+
data-news-desk-admin-nav="newsletter"
|
|
4360
|
+
data-active={administrationPanel === "newsletter" || undefined}
|
|
4361
|
+
onClick={() => onPanelChange("newsletter")}
|
|
4362
|
+
>
|
|
4363
|
+
<strong>Newsletter</strong>
|
|
4364
|
+
<span>Subscribers, issues and delivery for the site's email</span>
|
|
4365
|
+
</Link>
|
|
4366
|
+
) : null}
|
|
4367
|
+
{readerPluginEnabled() ? (
|
|
4368
|
+
<Link
|
|
4369
|
+
href={administrationPanelHref("moderation", isDemo)}
|
|
4370
|
+
data-news-desk-admin-nav="moderation"
|
|
4371
|
+
data-active={administrationPanel === "moderation" || undefined}
|
|
4372
|
+
onClick={() => onPanelChange("moderation")}
|
|
4373
|
+
>
|
|
4374
|
+
<strong>Moderation</strong>
|
|
4375
|
+
<span>Reader submissions and comments</span>
|
|
4376
|
+
</Link>
|
|
4377
|
+
) : null}
|
|
4343
4378
|
</nav>}
|
|
4344
4379
|
<div className="news-desk-settings-panel">
|
|
4345
4380
|
{administrationPanel === "signups" ? <SignupEntriesPanel /> : null}
|
|
4381
|
+
{administrationPanel === "newsletter" ? <NewsletterPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
|
|
4382
|
+
{administrationPanel === "moderation" ? <ModerationPanel /> : null}
|
|
4346
4383
|
{administrationPanel === "editorial-checks" ? <EditorialChecksAdminPanel /> : null}
|
|
4384
|
+
{administrationPanel === "monitoring" ? <MonitoringPanel isAdmin={canManageUsers} isDemo={isDemo} /> : null}
|
|
4347
4385
|
{administrationPanel === "delegations" ? <AdministrationDelegationsPanel disabled={disabled} /> : null}
|
|
4348
4386
|
{administrationPanel === "users" ? (
|
|
4349
4387
|
<AdministrationUsersPanel
|
|
@@ -12807,14 +12845,20 @@ function signupPluginEnabled(): boolean {
|
|
|
12807
12845
|
return pluginSettings(amplifyOutputs as PluginOutputs, "signup") !== null;
|
|
12808
12846
|
}
|
|
12809
12847
|
|
|
12810
|
-
|
|
12811
|
-
|
|
12848
|
+
/** The Newsletter panel exists when the site enabled the plugin; `?demo=1` shows it with fixture data so it can be seen and tested. */
|
|
12849
|
+
function newsletterPluginEnabled(demo = false): boolean {
|
|
12850
|
+
return demo || pluginSettings(amplifyOutputs as PluginOutputs, "newsletter") !== null;
|
|
12851
|
+
}
|
|
12852
|
+
|
|
12853
|
+
function readerPluginEnabled(): boolean {
|
|
12854
|
+
return pluginSettings(amplifyOutputs as PluginOutputs, "reader-steering") !== null;
|
|
12812
12855
|
}
|
|
12813
12856
|
|
|
12814
|
-
function normalizeAdministrationPanel(value: string | null | undefined): AdministrationPanel {
|
|
12815
|
-
if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations") return value;
|
|
12857
|
+
function normalizeAdministrationPanel(value: string | null | undefined, demo = false): AdministrationPanel {
|
|
12858
|
+
if (value === "policies" || value === "sections" || value === "procedures" || value === "delegations" || value === "editorial-checks" || value === "monitoring") return value;
|
|
12816
12859
|
if (value === "signups" && signupPluginEnabled()) return value;
|
|
12817
|
-
if (value === "
|
|
12860
|
+
if (value === "newsletter" && newsletterPluginEnabled(demo)) return value;
|
|
12861
|
+
if (value === "moderation" && readerPluginEnabled()) return value;
|
|
12818
12862
|
return "users";
|
|
12819
12863
|
}
|
|
12820
12864
|
|
|
@@ -27,14 +27,16 @@ export function useEditorialChecks({
|
|
|
27
27
|
section,
|
|
28
28
|
itemId,
|
|
29
29
|
}: {
|
|
30
|
-
/** null
|
|
30
|
+
/** null when there is no backend to check against (a preview). Whether a publication runs checks is its stored setting. */
|
|
31
31
|
backend: EditorialChecksBackend | null;
|
|
32
32
|
body: string | null;
|
|
33
33
|
section: string;
|
|
34
34
|
itemId: string | null;
|
|
35
35
|
}) {
|
|
36
|
-
const
|
|
36
|
+
const available = backend !== null;
|
|
37
37
|
const [settings, setSettings] = useState<EditorialSettings>(DEFAULT_EDITORIAL_SETTINGS);
|
|
38
|
+
const [settingsLoaded, setSettingsLoaded] = useState(false);
|
|
39
|
+
const enabled = available && settings.enabled;
|
|
38
40
|
const [result, setResult] = useState<EditorialCheckResult | null>(null);
|
|
39
41
|
const [checkedBody, setCheckedBody] = useState<string | null>(null);
|
|
40
42
|
const [status, setStatus] = useState<CheckStatus>({ state: "idle" });
|
|
@@ -46,9 +48,9 @@ export function useEditorialChecks({
|
|
|
46
48
|
const sequence = useRef(0);
|
|
47
49
|
|
|
48
50
|
useEffect(() => {
|
|
49
|
-
if (!
|
|
50
|
-
backend?.loadSettings().then(({ settings: loaded }) => setSettings(loaded)).catch(() => undefined);
|
|
51
|
-
}, [
|
|
51
|
+
if (!available) return;
|
|
52
|
+
backend?.loadSettings().then(({ settings: loaded }) => setSettings(loaded)).catch(() => undefined).finally(() => setSettingsLoaded(true));
|
|
53
|
+
}, [available, backend]);
|
|
52
54
|
|
|
53
55
|
useEffect(() => {
|
|
54
56
|
if (!enabled || !itemId) return;
|
|
@@ -57,7 +59,7 @@ export function useEditorialChecks({
|
|
|
57
59
|
|
|
58
60
|
const check = useCallback(async () => {
|
|
59
61
|
const text = latestBody.current;
|
|
60
|
-
if (!backend || text === null || !text.trim()) return;
|
|
62
|
+
if (!backend || !enabled || text === null || !text.trim()) return;
|
|
61
63
|
const run = ++sequence.current;
|
|
62
64
|
setStatus({ state: "checking" });
|
|
63
65
|
try {
|
|
@@ -73,13 +75,13 @@ export function useEditorialChecks({
|
|
|
73
75
|
} catch (error) {
|
|
74
76
|
if (run === sequence.current) setStatus({ state: "error", message: error instanceof Error ? error.message : "The check failed." });
|
|
75
77
|
}
|
|
76
|
-
}, [backend, section]);
|
|
78
|
+
}, [backend, enabled, section]);
|
|
77
79
|
|
|
78
80
|
useEffect(() => {
|
|
79
|
-
if (!enabled || !settings.checkWhileTyping || body === null || body === checkedBody) return;
|
|
81
|
+
if (!enabled || !settingsLoaded || !settings.checkWhileTyping || body === null || body === checkedBody) return;
|
|
80
82
|
const timer = window.setTimeout(() => void check(), CHECK_PAUSE_MS);
|
|
81
83
|
return () => window.clearTimeout(timer);
|
|
82
|
-
}, [body, check, checkedBody, enabled, settings.checkWhileTyping]);
|
|
84
|
+
}, [body, check, checkedBody, enabled, settings.checkWhileTyping, settingsLoaded]);
|
|
83
85
|
|
|
84
86
|
const record = useCallback(
|
|
85
87
|
(finding: EditorialFinding, decision: FeedbackDecision, reason: string | null = null) => {
|
|
@@ -105,7 +107,7 @@ export function useEditorialChecks({
|
|
|
105
107
|
[backend, section],
|
|
106
108
|
);
|
|
107
109
|
|
|
108
|
-
const current = result && checkedBody === body;
|
|
110
|
+
const current = enabled && result && checkedBody === body;
|
|
109
111
|
return {
|
|
110
112
|
enabled,
|
|
111
113
|
settings,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
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
|
-
import
|
|
4
|
+
import { SecureStringParameter } from "./secure-string-parameter.js";
|
|
5
5
|
import { GITHUB_OIDC_PROVIDER_HOST } from "./github-oidc-provider.js";
|
|
6
6
|
import { cmsProductionBuildSpec, cmsStagingBuildSpec, readerBuildSpec } from "./build-specs.js";
|
|
7
|
-
import { PROSPECTUS_PAPYRUS_SITE_ROUTES, hasCmsApp, isStagingEnabled, resolveRedirectSubDomainPrefix, resolveCmsHostName, resolveProspectusAccountsEndpoint, resolveRevalidateSecretParameterName, resolveStackName, resolveStagingDomainName, resolveStoragePreviewPrefix } from "./site-config.js";
|
|
7
|
+
import { PROSPECTUS_PAPYRUS_SITE_ROUTES, hasCmsApp, isStagingEnabled, resolveRedirectSubDomainPrefix, resolveCmsHostName, resolveSiteOrigin, githubOidcSubjects, resolveGithubRepository, resolveProspectusAccountsEndpoint, resolveProspectusSessionSealingParameterName, resolveReaderPollSecretParameterName, resolveRevalidateSecretParameterName, resolveStackName, resolveStagingDomainName, resolveStoragePreviewPrefix } from "./site-config.js";
|
|
8
8
|
function environmentVariables(variables) {
|
|
9
9
|
return Object.entries(variables).map(([name, value]) => ({ name, value }));
|
|
10
10
|
}
|
|
@@ -68,6 +68,9 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
68
68
|
if (!config.reader)
|
|
69
69
|
throw new Error(`site ${config.siteId} has neither a cms section nor a reader`);
|
|
70
70
|
this.readerAppId = this.addReaderApp(config, config.reader);
|
|
71
|
+
if (config.autoDeploy) {
|
|
72
|
+
this.addGithubDeployRole(config, [{ appId: this.readerAppId, branch: config.reader.branchName ?? "main" }]);
|
|
73
|
+
}
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
76
|
const cmsAppName = config.cms.appName ?? `${config.siteId}-cms`;
|
|
@@ -89,33 +92,49 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
89
92
|
});
|
|
90
93
|
const revalidateSecretParameterName = config.frontend === "pretext" ? resolveRevalidateSecretParameterName(config) : undefined;
|
|
91
94
|
if (revalidateSecretParameterName) {
|
|
95
|
+
const revalidateSecret = new SecureStringParameter(this, "RevalidateSecretParameter", {
|
|
96
|
+
parameterName: revalidateSecretParameterName,
|
|
97
|
+
description: `Shared secret that authorizes publishes to call the /api/revalidate route of ${cmsAppName}`,
|
|
98
|
+
});
|
|
92
99
|
computeRole.addToPolicy(new iam.PolicyStatement({
|
|
93
100
|
sid: "ReadRevalidateSecret",
|
|
94
101
|
actions: ["ssm:GetParameter"],
|
|
95
|
-
resources: [
|
|
96
|
-
this.formatArn({ service: "ssm", resource: "parameter", resourceName: revalidateSecretParameterName.slice(1), arnFormat: ArnFormat.SLASH_RESOURCE_NAME }),
|
|
97
|
-
],
|
|
102
|
+
resources: [revalidateSecret.parameterArn],
|
|
98
103
|
}));
|
|
99
104
|
}
|
|
100
105
|
const prospectusAccountsApiStageArn = config.cms.prospectusAccountsApiStageArn;
|
|
101
|
-
const
|
|
102
|
-
? new
|
|
106
|
+
const prospectusSessionSealing = prospectusAccountsApiStageArn
|
|
107
|
+
? new SecureStringParameter(this, "ProspectusSessionSealingParameter", {
|
|
108
|
+
parameterName: resolveProspectusSessionSealingParameterName(config),
|
|
103
109
|
description: `Seals the Prospectus sign-in cookies of ${cmsAppName}; replacing it signs everyone out`,
|
|
104
|
-
generateSecretString: { passwordLength: 64, excludePunctuation: true },
|
|
105
110
|
})
|
|
106
111
|
: undefined;
|
|
107
|
-
const prospectusEnvironment = prospectusAccountsApiStageArn &&
|
|
112
|
+
const prospectusEnvironment = prospectusAccountsApiStageArn && prospectusSessionSealing
|
|
108
113
|
? {
|
|
109
114
|
PAPYRUS_PROSPECTUS_ACCOUNTS_ENDPOINT: resolveProspectusAccountsEndpoint(prospectusAccountsApiStageArn),
|
|
110
|
-
|
|
115
|
+
PAPYRUS_PROSPECTUS_SESSION_SECRET_PARAMETER: prospectusSessionSealing.parameterName,
|
|
111
116
|
PAPYRUS_PROSPECTUS_SITE_PRODUCT: `papyrus-${config.siteId}`,
|
|
112
117
|
}
|
|
113
118
|
: {};
|
|
114
|
-
if (
|
|
119
|
+
if (prospectusSessionSealing) {
|
|
115
120
|
computeRole.addToPolicy(new iam.PolicyStatement({
|
|
116
121
|
sid: "ReadProspectusSessionSealingSecret",
|
|
117
|
-
actions: ["
|
|
118
|
-
resources: [
|
|
122
|
+
actions: ["ssm:GetParameter"],
|
|
123
|
+
resources: [prospectusSessionSealing.parameterArn],
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
if (prospectusAccountsApiStageArn && config.cms.readerRetirementPoll) {
|
|
127
|
+
// Prospectus admits one caller per site, this compute role, so the scheduled snapshot run reaches the retired-subject
|
|
128
|
+
// route through the CMS route /api/reader/retired, which it authenticates with this secret.
|
|
129
|
+
const readerPollSecret = new SecureStringParameter(this, "ReaderPollSecretParameter", {
|
|
130
|
+
parameterName: resolveReaderPollSecretParameterName(config),
|
|
131
|
+
description: `Authorizes the scheduled reader snapshot run to call the retired-subject route of ${cmsAppName}`,
|
|
132
|
+
});
|
|
133
|
+
prospectusEnvironment.PAPYRUS_READER_POLL_SECRET_PARAMETER = readerPollSecret.parameterName;
|
|
134
|
+
computeRole.addToPolicy(new iam.PolicyStatement({
|
|
135
|
+
sid: "ReadReaderPollSecret",
|
|
136
|
+
actions: ["ssm:GetParameter"],
|
|
137
|
+
resources: [readerPollSecret.parameterArn],
|
|
119
138
|
}));
|
|
120
139
|
}
|
|
121
140
|
if (prospectusAccountsApiStageArn) {
|
|
@@ -153,9 +172,15 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
153
172
|
const stagingOrigin = stagingDomainName
|
|
154
173
|
? `https://${stagingDomainName}/`
|
|
155
174
|
: Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain, "/"]);
|
|
175
|
+
// The compute adapter's request URL is https://localhost:3000, so the site names its own public origin.
|
|
176
|
+
// A site served by its own edge (cms.siteOrigin) has no Amplify domain, so the edge host is its origin.
|
|
177
|
+
const siteOrigin = resolveSiteOrigin(config);
|
|
178
|
+
const productionSiteOrigin = cmsHostName ? `https://${cmsHostName}` : siteOrigin ?? Fn.join("", ["https://main.", cmsApp.attrDefaultDomain]);
|
|
179
|
+
const stagingSiteOrigin = stagingDomainName ? `https://${stagingDomainName}` : Fn.join("", ["https://staging.", cmsApp.attrDefaultDomain]);
|
|
156
180
|
const sharedRootDomain = config.cms.domainPrefix !== undefined;
|
|
157
181
|
const defaultOriginsToAllow = [
|
|
158
182
|
...(config.cms.domainName ? [] : [productionOrigin]),
|
|
183
|
+
...(siteOrigin ? [`${siteOrigin}/`] : []),
|
|
159
184
|
...(stagingEnabled && !stagingDomainName ? [stagingOrigin] : []),
|
|
160
185
|
];
|
|
161
186
|
const oauthRedirectUrls = defaultOriginsToAllow.length === 0
|
|
@@ -165,7 +190,8 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
165
190
|
appId: cmsApp.attrAppId,
|
|
166
191
|
branchName: "main",
|
|
167
192
|
stage: "PRODUCTION",
|
|
168
|
-
|
|
193
|
+
// Auto-build off when GitHub Actions starts the job on merge (autoDeploy): nothing may double-build.
|
|
194
|
+
enableAutoBuild: !config.autoDeploy,
|
|
169
195
|
enablePerformanceMode: false,
|
|
170
196
|
environmentVariables: environmentVariables({
|
|
171
197
|
...config.cms.environment,
|
|
@@ -174,6 +200,7 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
174
200
|
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
175
201
|
PAPYRUS_SITE_BRAND: config.brand,
|
|
176
202
|
SITE_ENV: "production",
|
|
203
|
+
PAPYRUS_SITE_ORIGIN: productionSiteOrigin,
|
|
177
204
|
PAPYRUS_CONTENT_SOURCE: "published",
|
|
178
205
|
...(revalidateSecretParameterName ? { PAPYRUS_REVALIDATE_SECRET_PARAMETER: revalidateSecretParameterName } : {}),
|
|
179
206
|
...prospectusEnvironment,
|
|
@@ -195,6 +222,7 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
195
222
|
PAPYRUS_OAUTH_REDIRECT_URLS: oauthRedirectUrls,
|
|
196
223
|
PAPYRUS_SITE_BRAND: config.brand,
|
|
197
224
|
SITE_ENV: "staging",
|
|
225
|
+
PAPYRUS_SITE_ORIGIN: stagingSiteOrigin,
|
|
198
226
|
...stagingMode,
|
|
199
227
|
...prospectusEnvironment,
|
|
200
228
|
NEXT_PUBLIC_PAPYRUS_STAGING_URL: stagingDomainName
|
|
@@ -247,6 +275,12 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
247
275
|
}
|
|
248
276
|
const amplifyAppIds = [cmsApp.attrAppId, ...(this.readerAppId ? [this.readerAppId] : [])];
|
|
249
277
|
this.addAuthoringRole(config, cmsApp.attrAppId, amplifyAppIds);
|
|
278
|
+
if (config.autoDeploy) {
|
|
279
|
+
this.addGithubDeployRole(config, [
|
|
280
|
+
{ appId: cmsApp.attrAppId, branch: "main" },
|
|
281
|
+
...(this.readerAppId && config.reader ? [{ appId: this.readerAppId, branch: config.reader.branchName ?? "main" }] : []),
|
|
282
|
+
]);
|
|
283
|
+
}
|
|
250
284
|
if (config.github) {
|
|
251
285
|
this.addGithubCiRole(config, config.github, cmsApp.attrAppId, amplifyAppIds);
|
|
252
286
|
}
|
|
@@ -294,6 +328,46 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
294
328
|
description: `Role the Papyrus CLI assumes for ${config.siteId} (profile role_arn)`,
|
|
295
329
|
});
|
|
296
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* The merge-to-main deploy role: assumable only by a GitHub Actions run on `main` of the site's own
|
|
333
|
+
* repository (OIDC, no keys), and able to do exactly one thing: start and read Amplify jobs on the
|
|
334
|
+
* production branch of the site's apps.
|
|
335
|
+
*/
|
|
336
|
+
addGithubDeployRole(config, targets) {
|
|
337
|
+
const { owner, repo } = resolveGithubRepository(config);
|
|
338
|
+
const provider = iam.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, "GithubDeployOidcProvider", this.formatArn({
|
|
339
|
+
service: "iam",
|
|
340
|
+
region: "",
|
|
341
|
+
resource: "oidc-provider",
|
|
342
|
+
resourceName: GITHUB_OIDC_PROVIDER_HOST,
|
|
343
|
+
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
|
|
344
|
+
}));
|
|
345
|
+
const deployRole = new iam.Role(this, "GithubDeployRole", {
|
|
346
|
+
roleName: `${config.siteId}-github-deploy`,
|
|
347
|
+
description: `Merge-to-main deploy for ${owner}/${repo}: GitHub OIDC on refs/heads/main only; amplify:StartJob and GetJob on the production branch of the site's apps only`,
|
|
348
|
+
maxSessionDuration: Duration.hours(1),
|
|
349
|
+
assumedBy: new iam.OpenIdConnectPrincipal(provider, {
|
|
350
|
+
StringEquals: {
|
|
351
|
+
[`${GITHUB_OIDC_PROVIDER_HOST}:aud`]: "sts.amazonaws.com",
|
|
352
|
+
[`${GITHUB_OIDC_PROVIDER_HOST}:sub`]: githubOidcSubjects(config, "main"),
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
356
|
+
deployRole.addToPolicy(new iam.PolicyStatement({
|
|
357
|
+
sid: "StartAndReadProductionJobs",
|
|
358
|
+
actions: ["amplify:StartJob", "amplify:GetJob"],
|
|
359
|
+
resources: targets.map((target) => this.formatArn({
|
|
360
|
+
service: "amplify",
|
|
361
|
+
resource: "apps",
|
|
362
|
+
resourceName: `${target.appId}/branches/${target.branch}/jobs/*`,
|
|
363
|
+
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
|
|
364
|
+
})),
|
|
365
|
+
}));
|
|
366
|
+
new CfnOutput(this, "GithubDeployRoleArn", {
|
|
367
|
+
value: deployRole.roleArn,
|
|
368
|
+
description: `Role for the merge-to-main deploy workflow in ${owner}/${repo}`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
297
371
|
addGithubCiRole(config, github, cmsAppId, amplifyAppIds) {
|
|
298
372
|
const provider = iam.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, "GithubOidcProvider", this.formatArn({
|
|
299
373
|
service: "iam",
|
|
@@ -309,7 +383,7 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
309
383
|
assumedBy: new iam.OpenIdConnectPrincipal(provider, {
|
|
310
384
|
StringEquals: { [`${GITHUB_OIDC_PROVIDER_HOST}:aud`]: "sts.amazonaws.com" },
|
|
311
385
|
StringLike: {
|
|
312
|
-
[`${GITHUB_OIDC_PROVIDER_HOST}:sub`]: github.branches.
|
|
386
|
+
[`${GITHUB_OIDC_PROVIDER_HOST}:sub`]: github.branches.flatMap((branch) => githubOidcSubjects(config, branch)),
|
|
313
387
|
},
|
|
314
388
|
}),
|
|
315
389
|
});
|
|
@@ -361,7 +435,7 @@ export class AmplifyAppShellStack extends Stack {
|
|
|
361
435
|
appId: readerApp.attrAppId,
|
|
362
436
|
branchName: readerBranchName,
|
|
363
437
|
stage: "PRODUCTION",
|
|
364
|
-
enableAutoBuild:
|
|
438
|
+
enableAutoBuild: !config.autoDeploy,
|
|
365
439
|
environmentVariables: environmentVariables({
|
|
366
440
|
...(reader.environment ?? {}),
|
|
367
441
|
PAPYRUS_SITE_BRAND: config.brand,
|