@anthusai/papyrus 2.0.0-next.7 → 2.0.0-next.71
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 +61 -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/{plugins/editorial-checks/function → functions/editorial-checks}/handler.py +3 -3
- 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 +245 -0
- package/amplify/plugins/newsletter/config.ts +305 -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 +263 -0
- package/amplify/plugins/newsletter/papyrus.plugin.ts +311 -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 +180 -0
- package/amplify/plugins/reader-steering/config.ts +280 -0
- package/amplify/plugins/reader-steering/function/ai_moderation.py +246 -0
- package/amplify/plugins/reader-steering/function/handler.py +1554 -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/proposals.py +255 -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 +184 -0
- package/amplify/plugins/reader-steering/papyrus.plugin.ts +210 -0
- package/amplify/plugins/reader-steering/schema.js +204 -0
- package/amplify/plugins/reader-steering/schema.ts +206 -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/projection-lease.js +30 -0
- package/amplify/publication/projection-lease.ts +39 -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 +38 -1
- package/amplify/site-backend.ts +46 -1
- package/app/api/auth/prospectus/sign-in/route.ts +4 -0
- package/app/api/newsletter/confirm/route.ts +13 -0
- package/app/api/newsletter/preferences/route.ts +13 -0
- package/app/api/newsletter/state/route.ts +9 -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/flag/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-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 +86 -6
- package/infra/site-config.ts +129 -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 +418 -0
- package/lib/newsletter-client.ts +46 -0
- package/lib/newsletter-fixture.ts +184 -0
- package/lib/newsletter-model.ts +202 -0
- package/lib/prospectus-session.ts +15 -14
- package/lib/prospectus-sign-in-routes.ts +84 -19
- 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/semantic-graph.ts +2 -0
- 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 -3
- package/routes.manifest.json +215 -2
- package/amplify/plugins/editorial-checks/function/requirements.txt +0 -1
- package/amplify/plugins/editorial-checks/papyrus.plugin.js +0 -69
- package/amplify/plugins/editorial-checks/papyrus.plugin.ts +0 -85
- package/amplify/plugins/editorial-checks/schema.js +0 -94
package/infra/site-config.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type CmsConfig = {
|
|
|
32
32
|
appName?: string;
|
|
33
33
|
domainName?: string;
|
|
34
34
|
domainPrefix?: string;
|
|
35
|
+
/** Public https origin of a site whose host is served by its own edge (no Amplify domain association). */
|
|
36
|
+
siteOrigin?: string;
|
|
35
37
|
staging?: boolean;
|
|
36
38
|
stagingPreview?: StagingPreviewConfig;
|
|
37
39
|
cognitoDomainPrefix: string;
|
|
@@ -42,6 +44,12 @@ export type CmsConfig = {
|
|
|
42
44
|
stagingDomainPrefix?: string;
|
|
43
45
|
redirects?: CmsRedirect[];
|
|
44
46
|
prospectusAccountsApiStageArn?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The site is a public-reader site that polls Prospectus for retired reader subjects. The template then generates a
|
|
49
|
+
* shared secret that the scheduled snapshot run presents to the CMS route `/api/reader/retired` (requires
|
|
50
|
+
* `prospectusAccountsApiStageArn`; docs/reader-steering.md).
|
|
51
|
+
*/
|
|
52
|
+
readerRetirementPoll?: boolean;
|
|
45
53
|
deployStackInBuild?: boolean;
|
|
46
54
|
};
|
|
47
55
|
|
|
@@ -53,6 +61,7 @@ export type AmplifyAppShellSiteConfig = {
|
|
|
53
61
|
frontend: "pretext" | "markus-static";
|
|
54
62
|
hostedZoneId?: string;
|
|
55
63
|
stackName?: string;
|
|
64
|
+
aws?: SiteAwsConfig;
|
|
56
65
|
cms?: CmsConfig;
|
|
57
66
|
reader?: {
|
|
58
67
|
appName?: string;
|
|
@@ -69,20 +78,67 @@ export type AmplifyAppShellSiteConfig = {
|
|
|
69
78
|
};
|
|
70
79
|
papyrusVersion: string;
|
|
71
80
|
storagePreviewPrefix?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The scheduled cloud knowledge run for this publication. The app-shell stack ignores it; it is validated by
|
|
83
|
+
* infra/scheduled-jobs/lib/knowledge-run-config.ts, which owns the schema.
|
|
84
|
+
*/
|
|
85
|
+
knowledgeRun?: unknown;
|
|
86
|
+
/**
|
|
87
|
+
* Merge-to-main deploy from GitHub Actions through OIDC. When true (the default) the stack creates the
|
|
88
|
+
* `<siteId>-github-deploy` role and turns off Amplify's own auto-build on the production branches, so the
|
|
89
|
+
* public repository's workflow is the only trigger. Set false for a site whose repository is private:
|
|
90
|
+
* private repositories get no Actions minutes, so Amplify's own repository-connected auto-build stays on.
|
|
91
|
+
*/
|
|
92
|
+
autoDeploy: boolean;
|
|
72
93
|
github?: {
|
|
73
94
|
owner: string;
|
|
74
95
|
repo: string;
|
|
75
96
|
branches: string[];
|
|
76
97
|
ciCanDeployInfra: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* GitHub's numeric ids of the owner and the repository. Repositories with the immutable OIDC subject
|
|
100
|
+
* (`repo:<owner>@<ownerId>/<repo>@<repoId>:ref:...`, see `gh api repos/<owner>/<repo>/actions/oidc/customization/sub`)
|
|
101
|
+
* present that form instead of `repo:<owner>/<repo>:ref:...`; with both ids set, roles trust both forms.
|
|
102
|
+
*/
|
|
103
|
+
ownerId?: number;
|
|
104
|
+
repoId?: number;
|
|
77
105
|
};
|
|
78
106
|
};
|
|
79
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Where the site's AWS resources live. `bin/app-shell.ts` deploys the stack into exactly this account and
|
|
110
|
+
* region (see `resolveDeployEnvironment`), and `papyrus site google-secrets` asserts `accountId` with STS
|
|
111
|
+
* before it writes anything.
|
|
112
|
+
*/
|
|
113
|
+
export type SiteAwsConfig = {
|
|
114
|
+
profile?: string;
|
|
115
|
+
accountId?: string;
|
|
116
|
+
region?: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
80
119
|
export type CmsSiteConfig = AmplifyAppShellSiteConfig & { cms: CmsConfig };
|
|
81
120
|
|
|
82
121
|
export function hasCmsApp(config: AmplifyAppShellSiteConfig): config is CmsSiteConfig {
|
|
83
122
|
return config.cms !== undefined;
|
|
84
123
|
}
|
|
85
124
|
|
|
125
|
+
export function resolveGithubRepository(config: Pick<AmplifyAppShellSiteConfig, "repository" | "github">): { owner: string; repo: string } {
|
|
126
|
+
if (config.github) return { owner: config.github.owner, repo: config.github.repo };
|
|
127
|
+
const [owner, repo] = config.repository.replace("https://github.com/", "").split("/");
|
|
128
|
+
return { owner, repo };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The OIDC `sub` values a GitHub Actions run on `ref` of the site's repository can present. */
|
|
132
|
+
export function githubOidcSubjects(config: Pick<AmplifyAppShellSiteConfig, "repository" | "github">, ref: string): string[] {
|
|
133
|
+
const { owner, repo } = resolveGithubRepository(config);
|
|
134
|
+
const subjects = [`repo:${owner}/${repo}:ref:refs/heads/${ref}`];
|
|
135
|
+
const { ownerId, repoId } = config.github ?? {};
|
|
136
|
+
if (ownerId !== undefined && repoId !== undefined) {
|
|
137
|
+
subjects.push(`repo:${owner}@${ownerId}/${repo}@${repoId}:ref:refs/heads/${ref}`);
|
|
138
|
+
}
|
|
139
|
+
return subjects;
|
|
140
|
+
}
|
|
141
|
+
|
|
86
142
|
export const DEFAULT_CI_BRANCHES = ["main", "staging"];
|
|
87
143
|
|
|
88
144
|
const SITE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -90,6 +146,7 @@ const REPOSITORY_PATTERN = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_
|
|
|
90
146
|
const SECRETS_MANAGER_SECRET_ARN_PATTERN = /^arn:aws:secretsmanager:[a-z0-9-]+:\d{12}:secret:[A-Za-z0-9/_+=.@-]+$/;
|
|
91
147
|
const HOSTED_ZONE_ID_PATTERN = /^Z[A-Z0-9]+$/;
|
|
92
148
|
const HOST_NAME_PATTERN = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
|
|
149
|
+
const SITE_ORIGIN_PATTERN = /^https:\/\/([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}(:\d{1,5})?$/;
|
|
93
150
|
const DOMAIN_PREFIX_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
94
151
|
const APP_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 _.-]*$/;
|
|
95
152
|
const STACK_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,127}$/;
|
|
@@ -100,7 +157,7 @@ const GITHUB_REPO_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
|
100
157
|
const COGNITO_DOMAIN_PREFIX_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
101
158
|
const COGNITO_RESERVED_WORDS = ["aws", "amazon", "cognito"];
|
|
102
159
|
const PROSPECTUS_ACCOUNTS_API_STAGE_ARN_PATTERN = /^arn:aws:execute-api:[a-z0-9-]+:\d{12}:[a-z0-9]+\/api$/;
|
|
103
|
-
const TEMPLATE_MANAGED_ENVIRONMENT_KEYS = ["PAPYRUS_COGNITO_DOMAIN_PREFIX", "PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX", "PAPYRUS_DISABLE_GOOGLE_OAUTH", "PAPYRUS_REVALIDATE_SECRET_PARAMETER", "PAPYRUS_PROSPECTUS_ACCOUNTS_ENDPOINT", "
|
|
160
|
+
const TEMPLATE_MANAGED_ENVIRONMENT_KEYS = ["PAPYRUS_COGNITO_DOMAIN_PREFIX", "PAPYRUS_APPLY_COGNITO_DOMAIN_PREFIX", "PAPYRUS_DISABLE_GOOGLE_OAUTH", "PAPYRUS_REVALIDATE_SECRET_PARAMETER", "PAPYRUS_PROSPECTUS_ACCOUNTS_ENDPOINT", "PAPYRUS_PROSPECTUS_SESSION_SECRET_PARAMETER", "PAPYRUS_PROSPECTUS_SITE_PRODUCT", "PAPYRUS_READER_POLL_SECRET_PARAMETER", "PAPYRUS_SITE_ORIGIN"];
|
|
104
161
|
|
|
105
162
|
/** Routes Prospectus admits for a registered Papyrus site (Prospectus accounts/papyrus-sites.ts). */
|
|
106
163
|
export const PROSPECTUS_PAPYRUS_SITE_ROUTES = [
|
|
@@ -127,6 +184,14 @@ function originOfRedirectUrl(value: string): string {
|
|
|
127
184
|
}
|
|
128
185
|
}
|
|
129
186
|
|
|
187
|
+
export function resolveProspectusSessionSealingParameterName(config: Pick<AmplifyAppShellSiteConfig, "siteId">): string {
|
|
188
|
+
return `/papyrus/${config.siteId}/prospectus-session-sealing`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function resolveReaderPollSecretParameterName(config: Pick<AmplifyAppShellSiteConfig, "siteId">): string {
|
|
192
|
+
return `/papyrus/${config.siteId}/reader-poll-secret`;
|
|
193
|
+
}
|
|
194
|
+
|
|
130
195
|
export function resolveRevalidateSecretParameterName(config: Pick<AmplifyAppShellSiteConfig, "siteId">): string {
|
|
131
196
|
return `/papyrus/${config.siteId}/revalidate-secret`;
|
|
132
197
|
}
|
|
@@ -251,6 +316,10 @@ export function resolveCmsHostName(config: Pick<CmsSiteConfig, "cms">): string |
|
|
|
251
316
|
return config.cms.domainPrefix ? `${config.cms.domainPrefix}.${config.cms.domainName}` : config.cms.domainName;
|
|
252
317
|
}
|
|
253
318
|
|
|
319
|
+
export function resolveSiteOrigin(config: Pick<CmsSiteConfig, "cms">): string | undefined {
|
|
320
|
+
return config.cms.siteOrigin;
|
|
321
|
+
}
|
|
322
|
+
|
|
254
323
|
export function resolveStagingDomainName(config: Pick<CmsSiteConfig, "cms">): string | undefined {
|
|
255
324
|
if (!isStagingEnabled(config)) return undefined;
|
|
256
325
|
if (config.cms.domainPrefix) {
|
|
@@ -282,7 +351,7 @@ function parseStagingPreview(value: unknown): StagingPreviewConfig | undefined {
|
|
|
282
351
|
|
|
283
352
|
function parseCms(value: unknown): CmsConfig {
|
|
284
353
|
const cmsRecord = requireObject(value, "cms");
|
|
285
|
-
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "deployStackInBuild"], "cms");
|
|
354
|
+
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "siteOrigin", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "readerRetirementPoll", "deployStackInBuild"], "cms");
|
|
286
355
|
const buildComputeType = cmsRecord.buildComputeType;
|
|
287
356
|
if (buildComputeType !== undefined && buildComputeType !== "STANDARD" && buildComputeType !== "STANDARD_8GB") {
|
|
288
357
|
fail("cms.buildComputeType", `must be "STANDARD" or "STANDARD_8GB", got ${JSON.stringify(buildComputeType)}`);
|
|
@@ -293,6 +362,12 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
293
362
|
if (cmsRecord.applyCognitoDomainPrefix !== undefined && typeof cmsRecord.applyCognitoDomainPrefix !== "boolean") {
|
|
294
363
|
fail("cms.applyCognitoDomainPrefix", `must be a boolean, got ${JSON.stringify(cmsRecord.applyCognitoDomainPrefix)}`);
|
|
295
364
|
}
|
|
365
|
+
if (cmsRecord.readerRetirementPoll !== undefined && typeof cmsRecord.readerRetirementPoll !== "boolean") {
|
|
366
|
+
fail("cms.readerRetirementPoll", `must be a boolean, got ${JSON.stringify(cmsRecord.readerRetirementPoll)}`);
|
|
367
|
+
}
|
|
368
|
+
if (cmsRecord.readerRetirementPoll === true && cmsRecord.prospectusAccountsApiStageArn === undefined) {
|
|
369
|
+
fail("cms.readerRetirementPoll", "requires cms.prospectusAccountsApiStageArn: the poll calls Prospectus as the site");
|
|
370
|
+
}
|
|
296
371
|
if (cmsRecord.deployStackInBuild !== undefined && typeof cmsRecord.deployStackInBuild !== "boolean") {
|
|
297
372
|
fail("cms.deployStackInBuild", `must be a boolean, got ${JSON.stringify(cmsRecord.deployStackInBuild)}`);
|
|
298
373
|
}
|
|
@@ -300,6 +375,7 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
300
375
|
appName: optionalString(cmsRecord.appName, "cms.appName", APP_NAME_PATTERN),
|
|
301
376
|
domainName: optionalString(cmsRecord.domainName, "cms.domainName", HOST_NAME_PATTERN, "host name"),
|
|
302
377
|
domainPrefix: optionalString(cmsRecord.domainPrefix, "cms.domainPrefix", DOMAIN_PREFIX_PATTERN, "single DNS label"),
|
|
378
|
+
siteOrigin: optionalString(cmsRecord.siteOrigin, "cms.siteOrigin", SITE_ORIGIN_PATTERN, "https origin with no path, query or trailing slash, such as https://shipped.anth.us"),
|
|
303
379
|
staging: cmsRecord.staging as boolean | undefined,
|
|
304
380
|
cognitoDomainPrefix: requireString(
|
|
305
381
|
cmsRecord.cognitoDomainPrefix,
|
|
@@ -314,6 +390,7 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
314
390
|
stagingDomainPrefix: optionalString(cmsRecord.stagingDomainPrefix, "cms.stagingDomainPrefix", DOMAIN_PREFIX_PATTERN, "single DNS label"),
|
|
315
391
|
redirects: parseRedirects(cmsRecord.redirects),
|
|
316
392
|
deployStackInBuild: cmsRecord.deployStackInBuild as boolean | undefined,
|
|
393
|
+
readerRetirementPoll: cmsRecord.readerRetirementPoll as boolean | undefined,
|
|
317
394
|
stagingPreview: parseStagingPreview(cmsRecord.stagingPreview),
|
|
318
395
|
prospectusAccountsApiStageArn: optionalString(
|
|
319
396
|
cmsRecord.prospectusAccountsApiStageArn,
|
|
@@ -329,10 +406,13 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
329
406
|
}
|
|
330
407
|
for (const key of TEMPLATE_MANAGED_ENVIRONMENT_KEYS) {
|
|
331
408
|
if (key in cms.environment) {
|
|
332
|
-
fail(`cms.environment.${key}`, "is managed by the template: Google sign-in is always on, the Cognito domain comes from cms.cognitoDomainPrefix, the revalidation secret parameter is derived from siteId and the Prospectus endpoint comes from cms.prospectusAccountsApiStageArn");
|
|
409
|
+
fail(`cms.environment.${key}`, "is managed by the template: Google sign-in is always on, the Cognito domain comes from cms.cognitoDomainPrefix, the revalidation secret parameter is derived from siteId and the Prospectus endpoint comes from cms.prospectusAccountsApiStageArn and the public site origin comes from the site host");
|
|
333
410
|
}
|
|
334
411
|
}
|
|
335
412
|
|
|
413
|
+
if (cms.siteOrigin && cms.domainName) {
|
|
414
|
+
fail("cms.siteOrigin", "is for sites whose host is served by their own edge; remove it when cms.domainName is set");
|
|
415
|
+
}
|
|
336
416
|
if (cms.staging === false && cms.stagingDomainName) {
|
|
337
417
|
fail("cms.stagingDomainName", "must not be set when cms.staging is false");
|
|
338
418
|
}
|
|
@@ -395,11 +475,22 @@ function validateOauthRedirectUrls(config: CmsSiteConfig): void {
|
|
|
395
475
|
}
|
|
396
476
|
}
|
|
397
477
|
|
|
478
|
+
function parseAws(value: unknown): SiteAwsConfig | undefined {
|
|
479
|
+
if (value === undefined) return undefined;
|
|
480
|
+
const record = requireObject(value, "aws");
|
|
481
|
+
rejectUnknownKeys(record, ["profile", "accountId", "region"], "aws");
|
|
482
|
+
return {
|
|
483
|
+
profile: optionalString(record.profile, "aws.profile", /^[A-Za-z0-9_.-]+$/, "AWS CLI profile name"),
|
|
484
|
+
accountId: optionalString(record.accountId, "aws.accountId", /^\d{12}$/, "12-digit account id"),
|
|
485
|
+
region: optionalString(record.region, "aws.region", /^[a-z]{2}(-[a-z]+)+-\d$/, "AWS region such as us-east-1"),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
398
489
|
export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
399
490
|
const record = requireObject(raw, "site");
|
|
400
491
|
rejectUnknownKeys(
|
|
401
492
|
record,
|
|
402
|
-
["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"],
|
|
493
|
+
["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "aws", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "knowledgeRun", "autoDeploy", "github"],
|
|
403
494
|
"site",
|
|
404
495
|
);
|
|
405
496
|
|
|
@@ -415,6 +506,7 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
415
506
|
}
|
|
416
507
|
const hostedZoneId = optionalString(record.hostedZoneId, "hostedZoneId", HOSTED_ZONE_ID_PATTERN, "Route 53 zone id");
|
|
417
508
|
const stackName = optionalString(record.stackName, "stackName", STACK_NAME_PATTERN, "CloudFormation stack name");
|
|
509
|
+
const aws = parseAws(record.aws);
|
|
418
510
|
const papyrusVersion = requireString(record.papyrusVersion, "papyrusVersion", PAPYRUS_VERSION_PATTERN, "exact version such as 1.0.0 or 1.0.0-next.1");
|
|
419
511
|
const storagePreviewPrefix = optionalString(record.storagePreviewPrefix, "storagePreviewPrefix", /^[A-Za-z0-9._-]+\/$/, "must end with /");
|
|
420
512
|
|
|
@@ -452,11 +544,14 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
452
544
|
fail("cms.stagingPreview", "must not be set when cms.staging is false");
|
|
453
545
|
}
|
|
454
546
|
|
|
547
|
+
const autoDeploy = record.autoDeploy ?? true;
|
|
548
|
+
if (typeof autoDeploy !== "boolean") fail("autoDeploy", "must be a boolean");
|
|
549
|
+
|
|
455
550
|
let github: AmplifyAppShellSiteConfig["github"];
|
|
456
551
|
if (record.github !== undefined) {
|
|
457
552
|
if (cms === undefined) fail("github", "requires cms (the CI role is scoped to the CMS app)");
|
|
458
553
|
const githubRecord = requireObject(record.github, "github");
|
|
459
|
-
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra"], "github");
|
|
554
|
+
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra", "ownerId", "repoId"], "github");
|
|
460
555
|
const owner = requireString(githubRecord.owner, "github.owner", GITHUB_OWNER_PATTERN, "plain GitHub login");
|
|
461
556
|
const repo = requireString(githubRecord.repo, "github.repo", GITHUB_REPO_PATTERN);
|
|
462
557
|
if (repository !== `https://github.com/${owner}/${repo}`) {
|
|
@@ -476,7 +571,15 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
476
571
|
}
|
|
477
572
|
const ciCanDeployInfra = githubRecord.ciCanDeployInfra ?? false;
|
|
478
573
|
if (typeof ciCanDeployInfra !== "boolean") fail("github.ciCanDeployInfra", "must be a boolean");
|
|
479
|
-
|
|
574
|
+
const optionalId = (value: unknown, name: string): number | undefined => {
|
|
575
|
+
if (value === undefined) return undefined;
|
|
576
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) fail(name, "must be a positive integer (GitHub numeric id)");
|
|
577
|
+
return value as number;
|
|
578
|
+
};
|
|
579
|
+
const ownerId = optionalId(githubRecord.ownerId, "github.ownerId");
|
|
580
|
+
const repoId = optionalId(githubRecord.repoId, "github.repoId");
|
|
581
|
+
if ((ownerId === undefined) !== (repoId === undefined)) fail("github", "ownerId and repoId must be set together");
|
|
582
|
+
github = { owner, repo, branches, ciCanDeployInfra: ciCanDeployInfra as boolean, ...(ownerId !== undefined ? { ownerId, repoId } : {}) };
|
|
480
583
|
}
|
|
481
584
|
|
|
482
585
|
const hasDomain = cms?.domainName !== undefined || reader?.domainName !== undefined;
|
|
@@ -495,14 +598,34 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
495
598
|
frontend,
|
|
496
599
|
hostedZoneId,
|
|
497
600
|
stackName,
|
|
601
|
+
aws,
|
|
498
602
|
cms,
|
|
499
603
|
reader,
|
|
500
604
|
papyrusVersion,
|
|
501
605
|
storagePreviewPrefix,
|
|
606
|
+
autoDeploy: autoDeploy as boolean,
|
|
502
607
|
github,
|
|
608
|
+
...(record.knowledgeRun !== undefined ? { knowledgeRun: record.knowledgeRun } : {}),
|
|
503
609
|
};
|
|
504
610
|
|
|
505
611
|
if (hasCmsApp(config)) validateOauthRedirectUrls(config);
|
|
506
612
|
|
|
507
613
|
return config;
|
|
508
614
|
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* The CDK environment for a site's app-shell stack, taken only from the site's own `aws` block. There is
|
|
618
|
+
* deliberately no default account: a deploy that guesses an account can land in, or fail against, the wrong
|
|
619
|
+
* one.
|
|
620
|
+
*/
|
|
621
|
+
export function resolveDeployEnvironment(config: Pick<AmplifyAppShellSiteConfig, "siteId" | "aws">): { account: string; region: string } {
|
|
622
|
+
const account = config.aws?.accountId;
|
|
623
|
+
const region = config.aws?.region;
|
|
624
|
+
const missing = [account ? "" : "aws.accountId", region ? "" : "aws.region"].filter(Boolean);
|
|
625
|
+
if (!account || !region) {
|
|
626
|
+
throw new Error(
|
|
627
|
+
`site "${config.siteId}" does not say where it deploys: add ${missing.join(" and ")} to its site.json, e.g. "aws": { "accountId": "<12-digit account>", "region": "us-east-1" }. No account is assumed.`,
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return { account, region };
|
|
631
|
+
}
|
|
@@ -200,7 +200,6 @@ export type ProcedureRunRecord = {
|
|
|
200
200
|
newsroomFeedKey?: string | null;
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
const DEFAULT_NEWSROOM_SECTIONS_CONFIG_PATH = path.join(process.cwd(), "corpora", "papyrus-newsroom-sections.yml");
|
|
204
203
|
const NEWSROOM_SECTION_TYPES = new Set<NewsroomSectionType>(["canonical", "floating", "rotating"]);
|
|
205
204
|
type NewsroomSectionSeedRow = Omit<NewsroomSectionRecord, "sortOrder" | "enabled" | "enabledStatus" | "createdAt" | "updatedAt"> & {
|
|
206
205
|
enabled: boolean;
|
|
@@ -690,10 +689,10 @@ export type CategorySteeringDashboard = {
|
|
|
690
689
|
loadError?: string | null;
|
|
691
690
|
};
|
|
692
691
|
|
|
693
|
-
const DEFAULT_ANALYSIS_PROFILES_PATH = path.join(process.cwd(), "corpora", "papyrus-analysis-profiles.yml");
|
|
694
|
-
const DEFAULT_STEERING_CONFIG_PATH = path.join(process.cwd(), "corpora", "papyrus-steering.yml");
|
|
695
692
|
|
|
696
|
-
|
|
693
|
+
/** Analysis profiles the site keeps in a file; a site that configures none (data-driven sites) has none. No default file is looked up. */
|
|
694
|
+
export async function loadAnalysisProfileSummaries(filepath?: string): Promise<AnalysisProfileSummary[]> {
|
|
695
|
+
if (!filepath) return [];
|
|
697
696
|
try {
|
|
698
697
|
const parsed = YAML.parse(fs.readFileSync(filepath, "utf8")) as {
|
|
699
698
|
schemaVersion?: number;
|
|
@@ -722,7 +721,9 @@ export async function loadAnalysisProfileSummaries(filepath = DEFAULT_ANALYSIS_P
|
|
|
722
721
|
}
|
|
723
722
|
}
|
|
724
723
|
|
|
725
|
-
|
|
724
|
+
/** Corpora a site lists in a steering file; no configured file means none. No default file is looked up. */
|
|
725
|
+
export async function loadConfiguredCorpusSummaries(filepath?: string): Promise<CategorySteeringCorpus[]> {
|
|
726
|
+
if (!filepath) return [];
|
|
726
727
|
try {
|
|
727
728
|
const parsed = YAML.parse(fs.readFileSync(filepath, "utf8")) as {
|
|
728
729
|
corpora?: Array<Record<string, unknown>>;
|
|
@@ -818,7 +819,8 @@ function sortCategorys(categorys: CategorySteeringCategory[]): CategorySteeringC
|
|
|
818
819
|
});
|
|
819
820
|
}
|
|
820
821
|
|
|
821
|
-
function defaultNewsroomSections(importedAt: string, sectionsConfigPath
|
|
822
|
+
function defaultNewsroomSections(importedAt: string, sectionsConfigPath?: string): NewsroomSectionRecord[] {
|
|
823
|
+
if (!sectionsConfigPath) return [];
|
|
822
824
|
return loadNewsroomSectionSeedRows(sectionsConfigPath).map((row, index) => ({
|
|
823
825
|
...row,
|
|
824
826
|
enabledStatus: "enabled",
|
|
@@ -828,7 +830,7 @@ function defaultNewsroomSections(importedAt: string, sectionsConfigPath = DEFAUL
|
|
|
828
830
|
}));
|
|
829
831
|
}
|
|
830
832
|
|
|
831
|
-
function loadNewsroomSectionSeedRows(sectionsConfigPath
|
|
833
|
+
function loadNewsroomSectionSeedRows(sectionsConfigPath: string) {
|
|
832
834
|
const cached = newsroomSectionSeedRowsCache.get(sectionsConfigPath);
|
|
833
835
|
if (cached) return cached;
|
|
834
836
|
const parsed = YAML.parse(fs.readFileSync(sectionsConfigPath, "utf8")) as {
|
|
@@ -883,7 +885,7 @@ function normalizeNewsroomSectionSeedRow(
|
|
|
883
885
|
|
|
884
886
|
export function createDemoCategorySteeringDashboard(
|
|
885
887
|
brandId: SiteBrandId = resolveSiteBrandId(),
|
|
886
|
-
newsroomSectionsConfigPath
|
|
888
|
+
newsroomSectionsConfigPath?: string,
|
|
887
889
|
): CategorySteeringDashboard {
|
|
888
890
|
const importedAt = "2026-05-16T12:00:00.000Z";
|
|
889
891
|
const profile = getNewsroomDemoProfile(brandId);
|
|
@@ -1698,7 +1700,7 @@ export function createDemoCategorySteeringDashboard(
|
|
|
1698
1700
|
question: "Should this candidate source become a reference for this publication?",
|
|
1699
1701
|
reviewControl: "thumbs",
|
|
1700
1702
|
reviewReason: "program",
|
|
1701
|
-
reviewDetail: "Every
|
|
1703
|
+
reviewDetail: "Every suggestion is reviewed while suggestions are getting started.",
|
|
1702
1704
|
version: 1,
|
|
1703
1705
|
}),
|
|
1704
1706
|
},
|