@anthusai/papyrus 2.0.0-next.4 → 2.0.0-next.40
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 +22 -3
- package/amplify/data/resource.ts +24 -3
- package/amplify/data/schema.js +2029 -1967
- package/amplify/data/schema.ts +72 -2
- package/amplify/functions/assignment-action/data-access.js +88 -0
- package/amplify/functions/assignment-action/data-access.ts +87 -0
- package/amplify/functions/content-actions/handler.py +33 -0
- 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/functions/editorial-checks/resource.js +43 -0
- package/amplify/functions/editorial-checks/resource.ts +60 -0
- package/amplify/functions/editorial-checks/schema.js +121 -0
- package/amplify/functions/editorial-checks/schema.ts +123 -0
- 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/python-bundle.js +20 -14
- package/amplify/functions/shared/python-bundle.ts +25 -18
- 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/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/bin/papyrus-infra.mjs +20 -1
- package/components/editorial-checks-admin-panel.tsx +362 -0
- package/components/editorial-finding-card.tsx +162 -0
- package/components/editorial-findings-panel.tsx +91 -0
- package/components/editorial-preview-popover.tsx +103 -0
- package/components/markus-body-editor.tsx +213 -0
- package/components/markus-ir-preview.tsx +147 -6
- package/components/monitoring-overview-view.tsx +241 -0
- package/components/monitoring-panel.tsx +40 -0
- package/components/newsroom-article-editor.tsx +136 -11
- package/components/newsroom-board-view.tsx +6 -7
- package/components/newsroom-curation-bootstrap.tsx +45 -0
- package/components/newsroom-ops-shell.tsx +7 -3
- package/components/newsroom-organizer.css +74 -61
- package/components/newsroom-references-view.tsx +14 -14
- package/components/newsroom-theme-fonts/broadsheet-green.css +10 -0
- package/components/newsroom-theme-fonts/broadsheet-green.tsx +6 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.css +9 -0
- package/components/newsroom-theme-fonts/newspaper-gothic.tsx +6 -0
- package/components/newsroom-theme-fonts/night-desk.css +10 -0
- package/components/newsroom-theme-fonts/night-desk.tsx +6 -0
- package/components/newsroom-theme-fonts/refined-newsprint.css +11 -0
- package/components/newsroom-theme-fonts/refined-newsprint.tsx +6 -0
- package/components/newsroom-theme.css +39 -0
- package/components/newsroom-theme.tsx +33 -0
- 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 +4 -4
- 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 +27 -2
- package/components/use-editorial-checks.ts +133 -0
- package/infra/amplify-app-shell.js +76 -16
- package/infra/amplify-app-shell.ts +86 -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 +75 -6
- package/infra/site-config.ts +106 -6
- package/lib/category-repository.ts +11 -9
- package/lib/curation-routes.ts +345 -0
- package/lib/editorial-checks-backend.ts +16 -0
- package/lib/editorial-checks-client.ts +212 -0
- package/lib/editorial-checks-demo.ts +147 -0
- package/lib/editorial-checks-settings.ts +27 -0
- package/lib/editorial-checks.ts +171 -0
- package/lib/editorial-preview.ts +228 -0
- package/lib/monitoring-client.ts +29 -0
- package/lib/monitoring-overview-fixture.ts +51 -0
- package/lib/monitoring-overview.ts +145 -0
- package/lib/newsroom-board.ts +27 -2
- package/lib/prospectus-session.ts +14 -14
- package/lib/prospectus-sign-in-routes.ts +52 -11
- package/lib/relevance-status.ts +2 -2
- package/lib/site-brand-tenant.ts +11 -6
- package/lib/site-brand.ts +23 -3
- package/lib/site-origin.ts +67 -0
- package/middleware.ts +14 -3
- package/package.json +48 -4
- package/routes.manifest.json +34 -1
- 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 +0 -93
- 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 +0 -93
- package/components/newsroom-newsprint-board.css +0 -7
package/infra/site-config.js
CHANGED
|
@@ -2,12 +2,29 @@ export const READER_REDIRECT_STATUSES = ["301", "302", "200", "404", "404-200"];
|
|
|
2
2
|
export function hasCmsApp(config) {
|
|
3
3
|
return config.cms !== undefined;
|
|
4
4
|
}
|
|
5
|
+
export function resolveGithubRepository(config) {
|
|
6
|
+
if (config.github)
|
|
7
|
+
return { owner: config.github.owner, repo: config.github.repo };
|
|
8
|
+
const [owner, repo] = config.repository.replace("https://github.com/", "").split("/");
|
|
9
|
+
return { owner, repo };
|
|
10
|
+
}
|
|
11
|
+
/** The OIDC `sub` values a GitHub Actions run on `ref` of the site's repository can present. */
|
|
12
|
+
export function githubOidcSubjects(config, ref) {
|
|
13
|
+
const { owner, repo } = resolveGithubRepository(config);
|
|
14
|
+
const subjects = [`repo:${owner}/${repo}:ref:refs/heads/${ref}`];
|
|
15
|
+
const { ownerId, repoId } = config.github ?? {};
|
|
16
|
+
if (ownerId !== undefined && repoId !== undefined) {
|
|
17
|
+
subjects.push(`repo:${owner}@${ownerId}/${repo}@${repoId}:ref:refs/heads/${ref}`);
|
|
18
|
+
}
|
|
19
|
+
return subjects;
|
|
20
|
+
}
|
|
5
21
|
export const DEFAULT_CI_BRANCHES = ["main", "staging"];
|
|
6
22
|
const SITE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
7
23
|
const REPOSITORY_PATTERN = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
8
24
|
const SECRETS_MANAGER_SECRET_ARN_PATTERN = /^arn:aws:secretsmanager:[a-z0-9-]+:\d{12}:secret:[A-Za-z0-9/_+=.@-]+$/;
|
|
9
25
|
const HOSTED_ZONE_ID_PATTERN = /^Z[A-Z0-9]+$/;
|
|
10
26
|
const HOST_NAME_PATTERN = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
|
|
27
|
+
const SITE_ORIGIN_PATTERN = /^https:\/\/([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}(:\d{1,5})?$/;
|
|
11
28
|
const DOMAIN_PREFIX_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
12
29
|
const APP_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 _.-]*$/;
|
|
13
30
|
const STACK_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,127}$/;
|
|
@@ -18,7 +35,7 @@ const GITHUB_REPO_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
|
18
35
|
const COGNITO_DOMAIN_PREFIX_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
19
36
|
const COGNITO_RESERVED_WORDS = ["aws", "amazon", "cognito"];
|
|
20
37
|
const PROSPECTUS_ACCOUNTS_API_STAGE_ARN_PATTERN = /^arn:aws:execute-api:[a-z0-9-]+:\d{12}:[a-z0-9]+\/api$/;
|
|
21
|
-
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", "
|
|
38
|
+
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_SITE_ORIGIN"];
|
|
22
39
|
/** Routes Prospectus admits for a registered Papyrus site (Prospectus accounts/papyrus-sites.ts). */
|
|
23
40
|
export const PROSPECTUS_PAPYRUS_SITE_ROUTES = [
|
|
24
41
|
"GET/v1/identities/me",
|
|
@@ -43,6 +60,9 @@ function originOfRedirectUrl(value) {
|
|
|
43
60
|
return value;
|
|
44
61
|
}
|
|
45
62
|
}
|
|
63
|
+
export function resolveProspectusSessionSealingParameterName(config) {
|
|
64
|
+
return `/papyrus/${config.siteId}/prospectus-session-sealing`;
|
|
65
|
+
}
|
|
46
66
|
export function resolveRevalidateSecretParameterName(config) {
|
|
47
67
|
return `/papyrus/${config.siteId}/revalidate-secret`;
|
|
48
68
|
}
|
|
@@ -168,6 +188,9 @@ export function resolveCmsHostName(config) {
|
|
|
168
188
|
return undefined;
|
|
169
189
|
return config.cms.domainPrefix ? `${config.cms.domainPrefix}.${config.cms.domainName}` : config.cms.domainName;
|
|
170
190
|
}
|
|
191
|
+
export function resolveSiteOrigin(config) {
|
|
192
|
+
return config.cms.siteOrigin;
|
|
193
|
+
}
|
|
171
194
|
export function resolveStagingDomainName(config) {
|
|
172
195
|
if (!isStagingEnabled(config))
|
|
173
196
|
return undefined;
|
|
@@ -199,7 +222,7 @@ function parseStagingPreview(value) {
|
|
|
199
222
|
}
|
|
200
223
|
function parseCms(value) {
|
|
201
224
|
const cmsRecord = requireObject(value, "cms");
|
|
202
|
-
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "deployStackInBuild"], "cms");
|
|
225
|
+
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "siteOrigin", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "deployStackInBuild"], "cms");
|
|
203
226
|
const buildComputeType = cmsRecord.buildComputeType;
|
|
204
227
|
if (buildComputeType !== undefined && buildComputeType !== "STANDARD" && buildComputeType !== "STANDARD_8GB") {
|
|
205
228
|
fail("cms.buildComputeType", `must be "STANDARD" or "STANDARD_8GB", got ${JSON.stringify(buildComputeType)}`);
|
|
@@ -217,6 +240,7 @@ function parseCms(value) {
|
|
|
217
240
|
appName: optionalString(cmsRecord.appName, "cms.appName", APP_NAME_PATTERN),
|
|
218
241
|
domainName: optionalString(cmsRecord.domainName, "cms.domainName", HOST_NAME_PATTERN, "host name"),
|
|
219
242
|
domainPrefix: optionalString(cmsRecord.domainPrefix, "cms.domainPrefix", DOMAIN_PREFIX_PATTERN, "single DNS label"),
|
|
243
|
+
siteOrigin: optionalString(cmsRecord.siteOrigin, "cms.siteOrigin", SITE_ORIGIN_PATTERN, "https origin with no path, query or trailing slash, such as https://shipped.anth.us"),
|
|
220
244
|
staging: cmsRecord.staging,
|
|
221
245
|
cognitoDomainPrefix: requireString(cmsRecord.cognitoDomainPrefix, "cms.cognitoDomainPrefix", COGNITO_DOMAIN_PREFIX_PATTERN, "lowercase letters, digits and hyphens; globally unique per region"),
|
|
222
246
|
applyCognitoDomainPrefix: cmsRecord.applyCognitoDomainPrefix,
|
|
@@ -235,9 +259,12 @@ function parseCms(value) {
|
|
|
235
259
|
}
|
|
236
260
|
for (const key of TEMPLATE_MANAGED_ENVIRONMENT_KEYS) {
|
|
237
261
|
if (key in cms.environment) {
|
|
238
|
-
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");
|
|
262
|
+
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");
|
|
239
263
|
}
|
|
240
264
|
}
|
|
265
|
+
if (cms.siteOrigin && cms.domainName) {
|
|
266
|
+
fail("cms.siteOrigin", "is for sites whose host is served by their own edge; remove it when cms.domainName is set");
|
|
267
|
+
}
|
|
241
268
|
if (cms.staging === false && cms.stagingDomainName) {
|
|
242
269
|
fail("cms.stagingDomainName", "must not be set when cms.staging is false");
|
|
243
270
|
}
|
|
@@ -300,9 +327,20 @@ function validateOauthRedirectUrls(config) {
|
|
|
300
327
|
}
|
|
301
328
|
}
|
|
302
329
|
}
|
|
330
|
+
function parseAws(value) {
|
|
331
|
+
if (value === undefined)
|
|
332
|
+
return undefined;
|
|
333
|
+
const record = requireObject(value, "aws");
|
|
334
|
+
rejectUnknownKeys(record, ["profile", "accountId", "region"], "aws");
|
|
335
|
+
return {
|
|
336
|
+
profile: optionalString(record.profile, "aws.profile", /^[A-Za-z0-9_.-]+$/, "AWS CLI profile name"),
|
|
337
|
+
accountId: optionalString(record.accountId, "aws.accountId", /^\d{12}$/, "12-digit account id"),
|
|
338
|
+
region: optionalString(record.region, "aws.region", /^[a-z]{2}(-[a-z]+)+-\d$/, "AWS region such as us-east-1"),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
303
341
|
export function parseSiteConfig(raw) {
|
|
304
342
|
const record = requireObject(raw, "site");
|
|
305
|
-
rejectUnknownKeys(record, ["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"], "site");
|
|
343
|
+
rejectUnknownKeys(record, ["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "aws", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "autoDeploy", "github"], "site");
|
|
306
344
|
const siteId = requireString(record.siteId, "siteId", SITE_ID_PATTERN, "kebab-case");
|
|
307
345
|
const repository = requireString(record.repository, "repository", REPOSITORY_PATTERN, "https://github.com/<org>/<repo>");
|
|
308
346
|
const repositoryAccessTokenSecretArn = record.repositoryAccessTokenSecretArn === undefined
|
|
@@ -315,6 +353,7 @@ export function parseSiteConfig(raw) {
|
|
|
315
353
|
}
|
|
316
354
|
const hostedZoneId = optionalString(record.hostedZoneId, "hostedZoneId", HOSTED_ZONE_ID_PATTERN, "Route 53 zone id");
|
|
317
355
|
const stackName = optionalString(record.stackName, "stackName", STACK_NAME_PATTERN, "CloudFormation stack name");
|
|
356
|
+
const aws = parseAws(record.aws);
|
|
318
357
|
const papyrusVersion = requireString(record.papyrusVersion, "papyrusVersion", PAPYRUS_VERSION_PATTERN, "exact version such as 1.0.0 or 1.0.0-next.1");
|
|
319
358
|
const storagePreviewPrefix = optionalString(record.storagePreviewPrefix, "storagePreviewPrefix", /^[A-Za-z0-9._-]+\/$/, "must end with /");
|
|
320
359
|
const cms = record.cms === undefined && frontend === "markus-static" ? undefined : parseCms(record.cms);
|
|
@@ -352,12 +391,15 @@ export function parseSiteConfig(raw) {
|
|
|
352
391
|
if (cms?.stagingPreview !== undefined && cms.staging === false) {
|
|
353
392
|
fail("cms.stagingPreview", "must not be set when cms.staging is false");
|
|
354
393
|
}
|
|
394
|
+
const autoDeploy = record.autoDeploy ?? true;
|
|
395
|
+
if (typeof autoDeploy !== "boolean")
|
|
396
|
+
fail("autoDeploy", "must be a boolean");
|
|
355
397
|
let github;
|
|
356
398
|
if (record.github !== undefined) {
|
|
357
399
|
if (cms === undefined)
|
|
358
400
|
fail("github", "requires cms (the CI role is scoped to the CMS app)");
|
|
359
401
|
const githubRecord = requireObject(record.github, "github");
|
|
360
|
-
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra"], "github");
|
|
402
|
+
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra", "ownerId", "repoId"], "github");
|
|
361
403
|
const owner = requireString(githubRecord.owner, "github.owner", GITHUB_OWNER_PATTERN, "plain GitHub login");
|
|
362
404
|
const repo = requireString(githubRecord.repo, "github.repo", GITHUB_REPO_PATTERN);
|
|
363
405
|
if (repository !== `https://github.com/${owner}/${repo}`) {
|
|
@@ -378,7 +420,18 @@ export function parseSiteConfig(raw) {
|
|
|
378
420
|
const ciCanDeployInfra = githubRecord.ciCanDeployInfra ?? false;
|
|
379
421
|
if (typeof ciCanDeployInfra !== "boolean")
|
|
380
422
|
fail("github.ciCanDeployInfra", "must be a boolean");
|
|
381
|
-
|
|
423
|
+
const optionalId = (value, name) => {
|
|
424
|
+
if (value === undefined)
|
|
425
|
+
return undefined;
|
|
426
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
427
|
+
fail(name, "must be a positive integer (GitHub numeric id)");
|
|
428
|
+
return value;
|
|
429
|
+
};
|
|
430
|
+
const ownerId = optionalId(githubRecord.ownerId, "github.ownerId");
|
|
431
|
+
const repoId = optionalId(githubRecord.repoId, "github.repoId");
|
|
432
|
+
if ((ownerId === undefined) !== (repoId === undefined))
|
|
433
|
+
fail("github", "ownerId and repoId must be set together");
|
|
434
|
+
github = { owner, repo, branches, ciCanDeployInfra: ciCanDeployInfra, ...(ownerId !== undefined ? { ownerId, repoId } : {}) };
|
|
382
435
|
}
|
|
383
436
|
const hasDomain = cms?.domainName !== undefined || reader?.domainName !== undefined;
|
|
384
437
|
if (hasDomain && hostedZoneId === undefined) {
|
|
@@ -395,13 +448,29 @@ export function parseSiteConfig(raw) {
|
|
|
395
448
|
frontend,
|
|
396
449
|
hostedZoneId,
|
|
397
450
|
stackName,
|
|
451
|
+
aws,
|
|
398
452
|
cms,
|
|
399
453
|
reader,
|
|
400
454
|
papyrusVersion,
|
|
401
455
|
storagePreviewPrefix,
|
|
456
|
+
autoDeploy: autoDeploy,
|
|
402
457
|
github,
|
|
403
458
|
};
|
|
404
459
|
if (hasCmsApp(config))
|
|
405
460
|
validateOauthRedirectUrls(config);
|
|
406
461
|
return config;
|
|
407
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* The CDK environment for a site's app-shell stack, taken only from the site's own `aws` block. There is
|
|
465
|
+
* deliberately no default account: a deploy that guesses an account can land in, or fail against, the wrong
|
|
466
|
+
* one.
|
|
467
|
+
*/
|
|
468
|
+
export function resolveDeployEnvironment(config) {
|
|
469
|
+
const account = config.aws?.accountId;
|
|
470
|
+
const region = config.aws?.region;
|
|
471
|
+
const missing = [account ? "" : "aws.accountId", region ? "" : "aws.region"].filter(Boolean);
|
|
472
|
+
if (!account || !region) {
|
|
473
|
+
throw new Error(`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.`);
|
|
474
|
+
}
|
|
475
|
+
return { account, region };
|
|
476
|
+
}
|
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;
|
|
@@ -53,6 +55,7 @@ export type AmplifyAppShellSiteConfig = {
|
|
|
53
55
|
frontend: "pretext" | "markus-static";
|
|
54
56
|
hostedZoneId?: string;
|
|
55
57
|
stackName?: string;
|
|
58
|
+
aws?: SiteAwsConfig;
|
|
56
59
|
cms?: CmsConfig;
|
|
57
60
|
reader?: {
|
|
58
61
|
appName?: string;
|
|
@@ -69,20 +72,62 @@ export type AmplifyAppShellSiteConfig = {
|
|
|
69
72
|
};
|
|
70
73
|
papyrusVersion: string;
|
|
71
74
|
storagePreviewPrefix?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Merge-to-main deploy from GitHub Actions through OIDC. When true (the default) the stack creates the
|
|
77
|
+
* `<siteId>-github-deploy` role and turns off Amplify's own auto-build on the production branches, so the
|
|
78
|
+
* public repository's workflow is the only trigger. Set false for a site whose repository is private:
|
|
79
|
+
* private repositories get no Actions minutes, so Amplify's own repository-connected auto-build stays on.
|
|
80
|
+
*/
|
|
81
|
+
autoDeploy: boolean;
|
|
72
82
|
github?: {
|
|
73
83
|
owner: string;
|
|
74
84
|
repo: string;
|
|
75
85
|
branches: string[];
|
|
76
86
|
ciCanDeployInfra: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* GitHub's numeric ids of the owner and the repository. Repositories with the immutable OIDC subject
|
|
89
|
+
* (`repo:<owner>@<ownerId>/<repo>@<repoId>:ref:...`, see `gh api repos/<owner>/<repo>/actions/oidc/customization/sub`)
|
|
90
|
+
* present that form instead of `repo:<owner>/<repo>:ref:...`; with both ids set, roles trust both forms.
|
|
91
|
+
*/
|
|
92
|
+
ownerId?: number;
|
|
93
|
+
repoId?: number;
|
|
77
94
|
};
|
|
78
95
|
};
|
|
79
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Where the site's AWS resources live. `bin/app-shell.ts` deploys the stack into exactly this account and
|
|
99
|
+
* region (see `resolveDeployEnvironment`), and `papyrus site google-secrets` asserts `accountId` with STS
|
|
100
|
+
* before it writes anything.
|
|
101
|
+
*/
|
|
102
|
+
export type SiteAwsConfig = {
|
|
103
|
+
profile?: string;
|
|
104
|
+
accountId?: string;
|
|
105
|
+
region?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
80
108
|
export type CmsSiteConfig = AmplifyAppShellSiteConfig & { cms: CmsConfig };
|
|
81
109
|
|
|
82
110
|
export function hasCmsApp(config: AmplifyAppShellSiteConfig): config is CmsSiteConfig {
|
|
83
111
|
return config.cms !== undefined;
|
|
84
112
|
}
|
|
85
113
|
|
|
114
|
+
export function resolveGithubRepository(config: Pick<AmplifyAppShellSiteConfig, "repository" | "github">): { owner: string; repo: string } {
|
|
115
|
+
if (config.github) return { owner: config.github.owner, repo: config.github.repo };
|
|
116
|
+
const [owner, repo] = config.repository.replace("https://github.com/", "").split("/");
|
|
117
|
+
return { owner, repo };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The OIDC `sub` values a GitHub Actions run on `ref` of the site's repository can present. */
|
|
121
|
+
export function githubOidcSubjects(config: Pick<AmplifyAppShellSiteConfig, "repository" | "github">, ref: string): string[] {
|
|
122
|
+
const { owner, repo } = resolveGithubRepository(config);
|
|
123
|
+
const subjects = [`repo:${owner}/${repo}:ref:refs/heads/${ref}`];
|
|
124
|
+
const { ownerId, repoId } = config.github ?? {};
|
|
125
|
+
if (ownerId !== undefined && repoId !== undefined) {
|
|
126
|
+
subjects.push(`repo:${owner}@${ownerId}/${repo}@${repoId}:ref:refs/heads/${ref}`);
|
|
127
|
+
}
|
|
128
|
+
return subjects;
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
export const DEFAULT_CI_BRANCHES = ["main", "staging"];
|
|
87
132
|
|
|
88
133
|
const SITE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -90,6 +135,7 @@ const REPOSITORY_PATTERN = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_
|
|
|
90
135
|
const SECRETS_MANAGER_SECRET_ARN_PATTERN = /^arn:aws:secretsmanager:[a-z0-9-]+:\d{12}:secret:[A-Za-z0-9/_+=.@-]+$/;
|
|
91
136
|
const HOSTED_ZONE_ID_PATTERN = /^Z[A-Z0-9]+$/;
|
|
92
137
|
const HOST_NAME_PATTERN = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
|
|
138
|
+
const SITE_ORIGIN_PATTERN = /^https:\/\/([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}(:\d{1,5})?$/;
|
|
93
139
|
const DOMAIN_PREFIX_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
94
140
|
const APP_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 _.-]*$/;
|
|
95
141
|
const STACK_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,127}$/;
|
|
@@ -100,7 +146,7 @@ const GITHUB_REPO_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
|
100
146
|
const COGNITO_DOMAIN_PREFIX_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
101
147
|
const COGNITO_RESERVED_WORDS = ["aws", "amazon", "cognito"];
|
|
102
148
|
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", "
|
|
149
|
+
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_SITE_ORIGIN"];
|
|
104
150
|
|
|
105
151
|
/** Routes Prospectus admits for a registered Papyrus site (Prospectus accounts/papyrus-sites.ts). */
|
|
106
152
|
export const PROSPECTUS_PAPYRUS_SITE_ROUTES = [
|
|
@@ -127,6 +173,10 @@ function originOfRedirectUrl(value: string): string {
|
|
|
127
173
|
}
|
|
128
174
|
}
|
|
129
175
|
|
|
176
|
+
export function resolveProspectusSessionSealingParameterName(config: Pick<AmplifyAppShellSiteConfig, "siteId">): string {
|
|
177
|
+
return `/papyrus/${config.siteId}/prospectus-session-sealing`;
|
|
178
|
+
}
|
|
179
|
+
|
|
130
180
|
export function resolveRevalidateSecretParameterName(config: Pick<AmplifyAppShellSiteConfig, "siteId">): string {
|
|
131
181
|
return `/papyrus/${config.siteId}/revalidate-secret`;
|
|
132
182
|
}
|
|
@@ -251,6 +301,10 @@ export function resolveCmsHostName(config: Pick<CmsSiteConfig, "cms">): string |
|
|
|
251
301
|
return config.cms.domainPrefix ? `${config.cms.domainPrefix}.${config.cms.domainName}` : config.cms.domainName;
|
|
252
302
|
}
|
|
253
303
|
|
|
304
|
+
export function resolveSiteOrigin(config: Pick<CmsSiteConfig, "cms">): string | undefined {
|
|
305
|
+
return config.cms.siteOrigin;
|
|
306
|
+
}
|
|
307
|
+
|
|
254
308
|
export function resolveStagingDomainName(config: Pick<CmsSiteConfig, "cms">): string | undefined {
|
|
255
309
|
if (!isStagingEnabled(config)) return undefined;
|
|
256
310
|
if (config.cms.domainPrefix) {
|
|
@@ -282,7 +336,7 @@ function parseStagingPreview(value: unknown): StagingPreviewConfig | undefined {
|
|
|
282
336
|
|
|
283
337
|
function parseCms(value: unknown): CmsConfig {
|
|
284
338
|
const cmsRecord = requireObject(value, "cms");
|
|
285
|
-
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "deployStackInBuild"], "cms");
|
|
339
|
+
rejectUnknownKeys(cmsRecord, ["appName", "domainName", "staging", "cognitoDomainPrefix", "applyCognitoDomainPrefix", "buildComputeType", "environment", "stagingDomainName", "domainPrefix", "siteOrigin", "stagingDomainPrefix", "redirects", "stagingPreview", "prospectusAccountsApiStageArn", "deployStackInBuild"], "cms");
|
|
286
340
|
const buildComputeType = cmsRecord.buildComputeType;
|
|
287
341
|
if (buildComputeType !== undefined && buildComputeType !== "STANDARD" && buildComputeType !== "STANDARD_8GB") {
|
|
288
342
|
fail("cms.buildComputeType", `must be "STANDARD" or "STANDARD_8GB", got ${JSON.stringify(buildComputeType)}`);
|
|
@@ -300,6 +354,7 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
300
354
|
appName: optionalString(cmsRecord.appName, "cms.appName", APP_NAME_PATTERN),
|
|
301
355
|
domainName: optionalString(cmsRecord.domainName, "cms.domainName", HOST_NAME_PATTERN, "host name"),
|
|
302
356
|
domainPrefix: optionalString(cmsRecord.domainPrefix, "cms.domainPrefix", DOMAIN_PREFIX_PATTERN, "single DNS label"),
|
|
357
|
+
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
358
|
staging: cmsRecord.staging as boolean | undefined,
|
|
304
359
|
cognitoDomainPrefix: requireString(
|
|
305
360
|
cmsRecord.cognitoDomainPrefix,
|
|
@@ -329,10 +384,13 @@ function parseCms(value: unknown): CmsConfig {
|
|
|
329
384
|
}
|
|
330
385
|
for (const key of TEMPLATE_MANAGED_ENVIRONMENT_KEYS) {
|
|
331
386
|
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");
|
|
387
|
+
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
388
|
}
|
|
334
389
|
}
|
|
335
390
|
|
|
391
|
+
if (cms.siteOrigin && cms.domainName) {
|
|
392
|
+
fail("cms.siteOrigin", "is for sites whose host is served by their own edge; remove it when cms.domainName is set");
|
|
393
|
+
}
|
|
336
394
|
if (cms.staging === false && cms.stagingDomainName) {
|
|
337
395
|
fail("cms.stagingDomainName", "must not be set when cms.staging is false");
|
|
338
396
|
}
|
|
@@ -395,11 +453,22 @@ function validateOauthRedirectUrls(config: CmsSiteConfig): void {
|
|
|
395
453
|
}
|
|
396
454
|
}
|
|
397
455
|
|
|
456
|
+
function parseAws(value: unknown): SiteAwsConfig | undefined {
|
|
457
|
+
if (value === undefined) return undefined;
|
|
458
|
+
const record = requireObject(value, "aws");
|
|
459
|
+
rejectUnknownKeys(record, ["profile", "accountId", "region"], "aws");
|
|
460
|
+
return {
|
|
461
|
+
profile: optionalString(record.profile, "aws.profile", /^[A-Za-z0-9_.-]+$/, "AWS CLI profile name"),
|
|
462
|
+
accountId: optionalString(record.accountId, "aws.accountId", /^\d{12}$/, "12-digit account id"),
|
|
463
|
+
region: optionalString(record.region, "aws.region", /^[a-z]{2}(-[a-z]+)+-\d$/, "AWS region such as us-east-1"),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
398
467
|
export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
399
468
|
const record = requireObject(raw, "site");
|
|
400
469
|
rejectUnknownKeys(
|
|
401
470
|
record,
|
|
402
|
-
["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"],
|
|
471
|
+
["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "aws", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "autoDeploy", "github"],
|
|
403
472
|
"site",
|
|
404
473
|
);
|
|
405
474
|
|
|
@@ -415,6 +484,7 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
415
484
|
}
|
|
416
485
|
const hostedZoneId = optionalString(record.hostedZoneId, "hostedZoneId", HOSTED_ZONE_ID_PATTERN, "Route 53 zone id");
|
|
417
486
|
const stackName = optionalString(record.stackName, "stackName", STACK_NAME_PATTERN, "CloudFormation stack name");
|
|
487
|
+
const aws = parseAws(record.aws);
|
|
418
488
|
const papyrusVersion = requireString(record.papyrusVersion, "papyrusVersion", PAPYRUS_VERSION_PATTERN, "exact version such as 1.0.0 or 1.0.0-next.1");
|
|
419
489
|
const storagePreviewPrefix = optionalString(record.storagePreviewPrefix, "storagePreviewPrefix", /^[A-Za-z0-9._-]+\/$/, "must end with /");
|
|
420
490
|
|
|
@@ -452,11 +522,14 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
452
522
|
fail("cms.stagingPreview", "must not be set when cms.staging is false");
|
|
453
523
|
}
|
|
454
524
|
|
|
525
|
+
const autoDeploy = record.autoDeploy ?? true;
|
|
526
|
+
if (typeof autoDeploy !== "boolean") fail("autoDeploy", "must be a boolean");
|
|
527
|
+
|
|
455
528
|
let github: AmplifyAppShellSiteConfig["github"];
|
|
456
529
|
if (record.github !== undefined) {
|
|
457
530
|
if (cms === undefined) fail("github", "requires cms (the CI role is scoped to the CMS app)");
|
|
458
531
|
const githubRecord = requireObject(record.github, "github");
|
|
459
|
-
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra"], "github");
|
|
532
|
+
rejectUnknownKeys(githubRecord, ["owner", "repo", "branches", "ciCanDeployInfra", "ownerId", "repoId"], "github");
|
|
460
533
|
const owner = requireString(githubRecord.owner, "github.owner", GITHUB_OWNER_PATTERN, "plain GitHub login");
|
|
461
534
|
const repo = requireString(githubRecord.repo, "github.repo", GITHUB_REPO_PATTERN);
|
|
462
535
|
if (repository !== `https://github.com/${owner}/${repo}`) {
|
|
@@ -476,7 +549,15 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
476
549
|
}
|
|
477
550
|
const ciCanDeployInfra = githubRecord.ciCanDeployInfra ?? false;
|
|
478
551
|
if (typeof ciCanDeployInfra !== "boolean") fail("github.ciCanDeployInfra", "must be a boolean");
|
|
479
|
-
|
|
552
|
+
const optionalId = (value: unknown, name: string): number | undefined => {
|
|
553
|
+
if (value === undefined) return undefined;
|
|
554
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) fail(name, "must be a positive integer (GitHub numeric id)");
|
|
555
|
+
return value as number;
|
|
556
|
+
};
|
|
557
|
+
const ownerId = optionalId(githubRecord.ownerId, "github.ownerId");
|
|
558
|
+
const repoId = optionalId(githubRecord.repoId, "github.repoId");
|
|
559
|
+
if ((ownerId === undefined) !== (repoId === undefined)) fail("github", "ownerId and repoId must be set together");
|
|
560
|
+
github = { owner, repo, branches, ciCanDeployInfra: ciCanDeployInfra as boolean, ...(ownerId !== undefined ? { ownerId, repoId } : {}) };
|
|
480
561
|
}
|
|
481
562
|
|
|
482
563
|
const hasDomain = cms?.domainName !== undefined || reader?.domainName !== undefined;
|
|
@@ -495,10 +576,12 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
495
576
|
frontend,
|
|
496
577
|
hostedZoneId,
|
|
497
578
|
stackName,
|
|
579
|
+
aws,
|
|
498
580
|
cms,
|
|
499
581
|
reader,
|
|
500
582
|
papyrusVersion,
|
|
501
583
|
storagePreviewPrefix,
|
|
584
|
+
autoDeploy: autoDeploy as boolean,
|
|
502
585
|
github,
|
|
503
586
|
};
|
|
504
587
|
|
|
@@ -506,3 +589,20 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
|
|
|
506
589
|
|
|
507
590
|
return config;
|
|
508
591
|
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The CDK environment for a site's app-shell stack, taken only from the site's own `aws` block. There is
|
|
595
|
+
* deliberately no default account: a deploy that guesses an account can land in, or fail against, the wrong
|
|
596
|
+
* one.
|
|
597
|
+
*/
|
|
598
|
+
export function resolveDeployEnvironment(config: Pick<AmplifyAppShellSiteConfig, "siteId" | "aws">): { account: string; region: string } {
|
|
599
|
+
const account = config.aws?.accountId;
|
|
600
|
+
const region = config.aws?.region;
|
|
601
|
+
const missing = [account ? "" : "aws.accountId", region ? "" : "aws.region"].filter(Boolean);
|
|
602
|
+
if (!account || !region) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`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.`,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return { account, region };
|
|
608
|
+
}
|
|
@@ -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
|
},
|