@anthusai/papyrus 1.0.0-next.1 → 1.0.0-next.10
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 +32 -0
- package/amplify/data/resource.ts +37 -0
- package/amplify/data/schema.js +32 -0
- package/amplify/data/schema.ts +37 -0
- package/amplify/functions/content-actions/handler.py +149 -0
- package/amplify/functions/content-actions/requirements.txt +4 -0
- package/amplify/functions/content-actions/resource.js +40 -0
- package/amplify/functions/content-actions/resource.ts +54 -0
- package/amplify/functions/shared/python-bundle.js +17 -2
- package/amplify/functions/shared/python-bundle.ts +23 -2
- package/amplify/rebuild-trigger-settings.js +22 -0
- package/amplify/rebuild-trigger-settings.ts +42 -0
- package/amplify/site-backend-config.ts +6 -0
- package/amplify/site-backend.js +28 -0
- package/amplify/site-backend.ts +35 -0
- package/amplify/storage/resource.js +3 -0
- package/amplify/storage/resource.ts +3 -0
- package/app/%5F_preview/[[...path]]/route.ts +8 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +2 -2
- package/app/globals.css +20 -4
- package/app/layout.tsx +8 -12
- package/app/newsroom/articles/[id]/page.tsx +15 -0
- package/app/newsroom/articles/page.tsx +13 -0
- package/app/robots.ts +11 -0
- package/bin/papyrus-app.mjs +12 -2
- package/bin/papyrus-infra.mjs +74 -18
- package/components/article-video.tsx +112 -3
- package/components/markus-ir-preview.tsx +300 -0
- package/components/news-desk-page.tsx +1 -4
- package/components/newsroom-access-gate.tsx +66 -0
- package/components/newsroom-article-editor.tsx +741 -0
- package/components/newsroom-articles-view.tsx +129 -0
- package/components/newsroom-ops-shell.tsx +3 -1
- package/components/staging-banner.tsx +11 -0
- package/components/topic-steering-workspace.tsx +1 -59
- package/infra/amplify-app-shell.js +205 -80
- package/infra/amplify-app-shell.ts +242 -113
- package/infra/build-specs.js +85 -0
- package/infra/build-specs.ts +131 -0
- package/infra/github-oidc-provider.js +24 -0
- package/infra/github-oidc-provider.ts +30 -0
- package/infra/index.js +3 -0
- package/infra/site-config.js +186 -0
- package/infra/site-config.ts +235 -0
- package/lib/cached-content-repository.ts +2 -1
- package/lib/content-actions-client.ts +98 -0
- package/lib/content-actions-demo.ts +190 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-source-context.ts +26 -0
- package/lib/empty-theme.css +1 -0
- package/lib/graphql-content-repository.ts +193 -39
- package/lib/layout-scenarios.ts +8 -8
- package/lib/newsroom-articles.ts +325 -0
- package/lib/newsroom-demo-articles.ts +90 -0
- package/lib/newsroom-media-backend.ts +142 -0
- package/lib/newsroom-media.ts +182 -0
- package/lib/newsroom-nav.ts +2 -0
- package/lib/site-brand-tenant.ts +3 -22
- package/lib/site-brand.ts +57 -42
- package/lib/site-env.ts +52 -0
- package/lib/staging-gate.ts +37 -0
- package/lib/staging-preview-object.ts +98 -0
- package/lib/video-mode.ts +42 -0
- package/lib/video-script.ts +63 -0
- package/middleware.ts +58 -33
- package/package.json +23 -1
- package/renderers/pretext/article-page.tsx +1 -6
- package/renderers/pretext/presentation-shell.tsx +2 -9
- package/routes.manifest.json +27 -1
- package/scripts/videoml/build-browser-bundle.mjs +22 -0
- package/scripts/videoml/build-preview-bundle.mjs +50 -0
- package/scripts/videoml/bundle-common.mjs +129 -0
- package/scripts/videoml/preview/preview-entry.tsx +381 -0
- package/scripts/videoml/preview/preview-stage-fit.ts +28 -0
- package/scripts/videoml/preview/preview-timing.ts +42 -0
- package/scripts/videoml/preview.html +77 -0
- package/scripts/videoml/standard-entry.tsx +46 -0
- package/src/with-papyrus.mjs +15 -2
- package/publications/anth_us/brand.ts +0 -68
- package/publications/anth_us/theme.css +0 -66
- package/publications/pilobol_us/brand.ts +0 -37
- package/publications/pilobol_us/theme.css +0 -140
- package/publications/threat_intelligence/blog-defense/graph.ts +0 -636
- package/publications/threat_intelligence/blog-defense/layout.ts +0 -934
- package/publications/threat_intelligence/blog-defense/page-background.tsx +0 -590
- package/publications/threat_intelligence/brand.ts +0 -40
- package/publications/threat_intelligence/pictograms/art.tsx +0 -608
- package/publications/threat_intelligence/pictograms/figure.tsx +0 -82
- package/publications/threat_intelligence/pictograms/registry.ts +0 -30
- package/publications/threat_intelligence/pictograms/system.tsx +0 -305
- package/publications/threat_intelligence/seed/seed-edition-content.json +0 -629
- package/publications/threat_intelligence/theme.css +0 -1440
- /package/{publications/papyrus/theme.css → app/papyrus-theme.css} +0 -0
package/amplify/data/resource.js
CHANGED
|
@@ -2,6 +2,7 @@ import { a, defineData } from "@aws-amplify/backend";
|
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource.js";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource.js";
|
|
4
4
|
import { graphqlJwtAuthorizer } from "../functions/graphql-jwt-authorizer/resource.js";
|
|
5
|
+
import { contentActions } from "../functions/content-actions/resource.js";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource.js";
|
|
7
8
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource.js";
|
|
@@ -468,6 +469,36 @@ const schema = a.schema({
|
|
|
468
469
|
.returns(a.ref("NewsroomSummary"))
|
|
469
470
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
470
471
|
.handler(a.handler.function(newsroomSummary)),
|
|
472
|
+
deriveMarkus: a
|
|
473
|
+
.query()
|
|
474
|
+
.arguments({ input: a.json().required() })
|
|
475
|
+
.returns(a.json())
|
|
476
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
477
|
+
.handler(a.handler.function(contentActions)),
|
|
478
|
+
saveItemDraft: a
|
|
479
|
+
.mutation()
|
|
480
|
+
.arguments({ input: a.json().required() })
|
|
481
|
+
.returns(a.json())
|
|
482
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
483
|
+
.handler(a.handler.function(contentActions)),
|
|
484
|
+
publishItem: a
|
|
485
|
+
.mutation()
|
|
486
|
+
.arguments({ input: a.json().required() })
|
|
487
|
+
.returns(a.json())
|
|
488
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
489
|
+
.handler(a.handler.function(contentActions)),
|
|
490
|
+
unpublishItem: a
|
|
491
|
+
.mutation()
|
|
492
|
+
.arguments({ input: a.json().required() })
|
|
493
|
+
.returns(a.json())
|
|
494
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
495
|
+
.handler(a.handler.function(contentActions)),
|
|
496
|
+
requestStagingBuild: a
|
|
497
|
+
.mutation()
|
|
498
|
+
.arguments({ input: a.json().required() })
|
|
499
|
+
.returns(a.json())
|
|
500
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
501
|
+
.handler(a.handler.function(contentActions)),
|
|
471
502
|
knowledgeQuery: a
|
|
472
503
|
.query()
|
|
473
504
|
.arguments({
|
|
@@ -2012,6 +2043,7 @@ const schema = a.schema({
|
|
|
2012
2043
|
]),
|
|
2013
2044
|
}).authorization((allow) => [
|
|
2014
2045
|
allow.resource(categoryAction),
|
|
2046
|
+
allow.resource(contentActions),
|
|
2015
2047
|
allow.resource(manageUserRole),
|
|
2016
2048
|
allow.resource(modelAttachmentUpload),
|
|
2017
2049
|
allow.resource(newsroomSummary),
|
package/amplify/data/resource.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
|
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource";
|
|
4
4
|
import { graphqlJwtAuthorizer } from "../functions/graphql-jwt-authorizer/resource";
|
|
5
|
+
import { contentActions } from "../functions/content-actions/resource";
|
|
5
6
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
6
7
|
import { manageUserRole } from "../functions/manage-user-role/resource";
|
|
7
8
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource";
|
|
@@ -508,6 +509,41 @@ const schema = a.schema({
|
|
|
508
509
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
509
510
|
.handler(a.handler.function(newsroomSummary)),
|
|
510
511
|
|
|
512
|
+
deriveMarkus: a
|
|
513
|
+
.query()
|
|
514
|
+
.arguments({ input: a.json().required() })
|
|
515
|
+
.returns(a.json())
|
|
516
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
517
|
+
.handler(a.handler.function(contentActions)),
|
|
518
|
+
|
|
519
|
+
saveItemDraft: a
|
|
520
|
+
.mutation()
|
|
521
|
+
.arguments({ input: a.json().required() })
|
|
522
|
+
.returns(a.json())
|
|
523
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
524
|
+
.handler(a.handler.function(contentActions)),
|
|
525
|
+
|
|
526
|
+
publishItem: a
|
|
527
|
+
.mutation()
|
|
528
|
+
.arguments({ input: a.json().required() })
|
|
529
|
+
.returns(a.json())
|
|
530
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
531
|
+
.handler(a.handler.function(contentActions)),
|
|
532
|
+
|
|
533
|
+
unpublishItem: a
|
|
534
|
+
.mutation()
|
|
535
|
+
.arguments({ input: a.json().required() })
|
|
536
|
+
.returns(a.json())
|
|
537
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
538
|
+
.handler(a.handler.function(contentActions)),
|
|
539
|
+
|
|
540
|
+
requestStagingBuild: a
|
|
541
|
+
.mutation()
|
|
542
|
+
.arguments({ input: a.json().required() })
|
|
543
|
+
.returns(a.json())
|
|
544
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
545
|
+
.handler(a.handler.function(contentActions)),
|
|
546
|
+
|
|
511
547
|
knowledgeQuery: a
|
|
512
548
|
.query()
|
|
513
549
|
.arguments({
|
|
@@ -2120,6 +2156,7 @@ const schema = a.schema({
|
|
|
2120
2156
|
]),
|
|
2121
2157
|
}).authorization((allow) => [
|
|
2122
2158
|
allow.resource(categoryAction),
|
|
2159
|
+
allow.resource(contentActions),
|
|
2123
2160
|
allow.resource(manageUserRole),
|
|
2124
2161
|
allow.resource(modelAttachmentUpload),
|
|
2125
2162
|
allow.resource(newsroomSummary),
|
package/amplify/data/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a } from "@aws-amplify/backend";
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource.js";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource.js";
|
|
4
|
+
import { contentActions } from "../functions/content-actions/resource.js";
|
|
4
5
|
import { knowledgeQuery } from "../functions/knowledge-query/resource.js";
|
|
5
6
|
import { manageUserRole } from "../functions/manage-user-role/resource.js";
|
|
6
7
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource.js";
|
|
@@ -368,6 +369,36 @@ export const schema = a.schema({
|
|
|
368
369
|
.returns(a.ref("NewsroomSummary"))
|
|
369
370
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
370
371
|
.handler(a.handler.function(newsroomSummary)),
|
|
372
|
+
deriveMarkus: a
|
|
373
|
+
.query()
|
|
374
|
+
.arguments({ input: a.json().required() })
|
|
375
|
+
.returns(a.json())
|
|
376
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
377
|
+
.handler(a.handler.function(contentActions)),
|
|
378
|
+
saveItemDraft: a
|
|
379
|
+
.mutation()
|
|
380
|
+
.arguments({ input: a.json().required() })
|
|
381
|
+
.returns(a.json())
|
|
382
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
383
|
+
.handler(a.handler.function(contentActions)),
|
|
384
|
+
publishItem: a
|
|
385
|
+
.mutation()
|
|
386
|
+
.arguments({ input: a.json().required() })
|
|
387
|
+
.returns(a.json())
|
|
388
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
389
|
+
.handler(a.handler.function(contentActions)),
|
|
390
|
+
unpublishItem: a
|
|
391
|
+
.mutation()
|
|
392
|
+
.arguments({ input: a.json().required() })
|
|
393
|
+
.returns(a.json())
|
|
394
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
395
|
+
.handler(a.handler.function(contentActions)),
|
|
396
|
+
requestStagingBuild: a
|
|
397
|
+
.mutation()
|
|
398
|
+
.arguments({ input: a.json().required() })
|
|
399
|
+
.returns(a.json())
|
|
400
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
401
|
+
.handler(a.handler.function(contentActions)),
|
|
371
402
|
knowledgeQuery: a
|
|
372
403
|
.query()
|
|
373
404
|
.arguments({
|
|
@@ -1843,6 +1874,7 @@ export const schema = a.schema({
|
|
|
1843
1874
|
]),
|
|
1844
1875
|
}).authorization((allow) => [
|
|
1845
1876
|
allow.resource(categoryAction),
|
|
1877
|
+
allow.resource(contentActions),
|
|
1846
1878
|
allow.resource(manageUserRole),
|
|
1847
1879
|
allow.resource(modelAttachmentUpload),
|
|
1848
1880
|
allow.resource(newsroomSummary),
|
package/amplify/data/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ClientSchema, a } from "@aws-amplify/backend";
|
|
2
2
|
import { assignmentAction } from "../functions/assignment-action/resource";
|
|
3
3
|
import { categoryAction } from "../functions/category-action/resource";
|
|
4
|
+
import { contentActions } from "../functions/content-actions/resource";
|
|
4
5
|
import { knowledgeQuery } from "../functions/knowledge-query/resource";
|
|
5
6
|
import { manageUserRole } from "../functions/manage-user-role/resource";
|
|
6
7
|
import { modelAttachmentUpload } from "../functions/model-attachment-upload/resource";
|
|
@@ -402,6 +403,41 @@ export const schema = a.schema({
|
|
|
402
403
|
.authorization((allow) => [allow.groups(categoryWriteGroups), allow.custom()])
|
|
403
404
|
.handler(a.handler.function(newsroomSummary)),
|
|
404
405
|
|
|
406
|
+
deriveMarkus: a
|
|
407
|
+
.query()
|
|
408
|
+
.arguments({ input: a.json().required() })
|
|
409
|
+
.returns(a.json())
|
|
410
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
411
|
+
.handler(a.handler.function(contentActions)),
|
|
412
|
+
|
|
413
|
+
saveItemDraft: a
|
|
414
|
+
.mutation()
|
|
415
|
+
.arguments({ input: a.json().required() })
|
|
416
|
+
.returns(a.json())
|
|
417
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
418
|
+
.handler(a.handler.function(contentActions)),
|
|
419
|
+
|
|
420
|
+
publishItem: a
|
|
421
|
+
.mutation()
|
|
422
|
+
.arguments({ input: a.json().required() })
|
|
423
|
+
.returns(a.json())
|
|
424
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
425
|
+
.handler(a.handler.function(contentActions)),
|
|
426
|
+
|
|
427
|
+
unpublishItem: a
|
|
428
|
+
.mutation()
|
|
429
|
+
.arguments({ input: a.json().required() })
|
|
430
|
+
.returns(a.json())
|
|
431
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
432
|
+
.handler(a.handler.function(contentActions)),
|
|
433
|
+
|
|
434
|
+
requestStagingBuild: a
|
|
435
|
+
.mutation()
|
|
436
|
+
.arguments({ input: a.json().required() })
|
|
437
|
+
.returns(a.json())
|
|
438
|
+
.authorization((allow) => [allow.groups(contentWriteGroups)])
|
|
439
|
+
.handler(a.handler.function(contentActions)),
|
|
440
|
+
|
|
405
441
|
knowledgeQuery: a
|
|
406
442
|
.query()
|
|
407
443
|
.arguments({
|
|
@@ -1944,6 +1980,7 @@ export const schema = a.schema({
|
|
|
1944
1980
|
]),
|
|
1945
1981
|
}).authorization((allow) => [
|
|
1946
1982
|
allow.resource(categoryAction),
|
|
1983
|
+
allow.resource(contentActions),
|
|
1947
1984
|
allow.resource(manageUserRole),
|
|
1948
1985
|
allow.resource(modelAttachmentUpload),
|
|
1949
1986
|
allow.resource(newsroomSummary),
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from papyrus_content.graphql_authoring import create_authoring_client
|
|
8
|
+
from papyrus_content.markus_renderer.derive import derive_body
|
|
9
|
+
from papyrus_content.rebuild_trigger import (
|
|
10
|
+
DEFAULT_STAGING_BRANCH,
|
|
11
|
+
reader_target_from_environment,
|
|
12
|
+
trigger_pretext_revalidation,
|
|
13
|
+
trigger_rebuild,
|
|
14
|
+
trigger_staging_build,
|
|
15
|
+
)
|
|
16
|
+
from papyrus_content.publishing import (
|
|
17
|
+
ItemFields,
|
|
18
|
+
PublishError,
|
|
19
|
+
publish_item,
|
|
20
|
+
save_item,
|
|
21
|
+
unpublish_item,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_input(raw: Any) -> dict:
|
|
26
|
+
parsed = json.loads(raw) if isinstance(raw, (str, bytes)) else raw
|
|
27
|
+
if not isinstance(parsed, dict):
|
|
28
|
+
raise PublishError("invalid-input", "The input must be a JSON object.")
|
|
29
|
+
return parsed
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def actor_from_event(event: dict) -> str:
|
|
33
|
+
identity = event.get("identity") or {}
|
|
34
|
+
return identity.get("username") or identity.get("sub") or "unknown"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def failure_response(error: PublishError) -> dict:
|
|
38
|
+
if error.errors:
|
|
39
|
+
errors = [body_error.to_dict() for body_error in error.errors]
|
|
40
|
+
else:
|
|
41
|
+
errors = [{"code": error.code, "message": error.message, "line": None}]
|
|
42
|
+
return {"ok": False, "errors": errors}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def derive_markus_action(arguments: dict) -> dict:
|
|
46
|
+
derivation = derive_body(arguments.get("frontMatterYaml"), arguments.get("bodyMarkus") or "")
|
|
47
|
+
if not derivation.ok:
|
|
48
|
+
return {"ok": False, "errors": [body_error.to_dict() for body_error in derivation.errors]}
|
|
49
|
+
response = {
|
|
50
|
+
"ok": True,
|
|
51
|
+
"bodyIrBytes": len(json.dumps(derivation.body_ir, separators=(",", ":")).encode("utf-8")),
|
|
52
|
+
"errors": [],
|
|
53
|
+
}
|
|
54
|
+
if arguments.get("includeIr") is True:
|
|
55
|
+
response["bodyIr"] = derivation.body_ir
|
|
56
|
+
return response
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def save_item_draft_action(arguments: dict, actor: str) -> dict:
|
|
60
|
+
client, _claims = create_authoring_client()
|
|
61
|
+
fields = ItemFields(
|
|
62
|
+
type=arguments["type"],
|
|
63
|
+
slug=arguments["slug"],
|
|
64
|
+
section=arguments.get("section"),
|
|
65
|
+
front_matter_yaml=arguments.get("frontMatterYaml"),
|
|
66
|
+
body_markus=arguments.get("bodyMarkus") or "",
|
|
67
|
+
aliases=list(arguments.get("aliases") or []),
|
|
68
|
+
id=arguments.get("id"),
|
|
69
|
+
)
|
|
70
|
+
record = save_item(client, fields, actor=actor, expected_content_hash=arguments.get("expectedContentHash"))
|
|
71
|
+
return {
|
|
72
|
+
"ok": True,
|
|
73
|
+
"item": {
|
|
74
|
+
"id": record["id"],
|
|
75
|
+
"contentHash": record["contentHash"],
|
|
76
|
+
"status": record["status"],
|
|
77
|
+
"slug": record["slug"],
|
|
78
|
+
"versionNumber": record.get("versionNumber"),
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def site_triggers_for(client, item_id: str) -> dict:
|
|
84
|
+
triggers: dict = {}
|
|
85
|
+
reader_app_id, reader_branch = reader_target_from_environment()
|
|
86
|
+
revalidate_base_url = (os.environ.get("PAPYRUS_REVALIDATE_BASE_URL") or "").strip()
|
|
87
|
+
if revalidate_base_url:
|
|
88
|
+
try:
|
|
89
|
+
item = client.get_record("Item", item_id) or {}
|
|
90
|
+
slugs = [item["slug"]] if item.get("slug") else []
|
|
91
|
+
revalidated = trigger_pretext_revalidation(slugs, None, base_url=revalidate_base_url)
|
|
92
|
+
triggers["revalidated"] = revalidated if revalidated is not None else {"ok": False, "reason": "not-configured"}
|
|
93
|
+
except Exception as error: # noqa: BLE001 - a trigger failure never fails the publish
|
|
94
|
+
triggers["revalidated"] = {"ok": False, "error": f"{type(error).__name__}: {error}"}
|
|
95
|
+
if reader_app_id:
|
|
96
|
+
triggers["rebuild"] = trigger_rebuild(reader_app_id=reader_app_id, reader_branch=reader_branch)
|
|
97
|
+
return triggers
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def publish_item_action(arguments: dict, actor: str) -> dict:
|
|
101
|
+
client, _claims = create_authoring_client()
|
|
102
|
+
result = publish_item(client, arguments["id"], actor=actor)
|
|
103
|
+
response = {
|
|
104
|
+
"ok": True,
|
|
105
|
+
"changed": result.changed,
|
|
106
|
+
"publishedId": result.published_item_id,
|
|
107
|
+
"versionNumber": result.version_number,
|
|
108
|
+
}
|
|
109
|
+
if result.changed:
|
|
110
|
+
response.update(site_triggers_for(client, arguments["id"]))
|
|
111
|
+
return response
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def unpublish_item_action(arguments: dict, actor: str) -> dict:
|
|
115
|
+
client, _claims = create_authoring_client()
|
|
116
|
+
result = unpublish_item(client, arguments["id"], actor=actor)
|
|
117
|
+
response = {"ok": True, "changed": result.changed}
|
|
118
|
+
if result.changed:
|
|
119
|
+
response.update(site_triggers_for(client, arguments["id"]))
|
|
120
|
+
return response
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def request_staging_build_action() -> dict:
|
|
124
|
+
cms_app_id = (os.environ.get("PAPYRUS_CMS_AMPLIFY_APP_ID") or "").strip()
|
|
125
|
+
if not cms_app_id:
|
|
126
|
+
return {"ok": False, "errors": [{"code": "staging-build-not-configured", "message": "This site has no staging build configured.", "line": None}]}
|
|
127
|
+
staging_branch = (os.environ.get("PAPYRUS_STAGING_BRANCH") or "").strip() or DEFAULT_STAGING_BRANCH
|
|
128
|
+
build = trigger_staging_build(cms_app_id=cms_app_id, staging_branch=staging_branch)
|
|
129
|
+
return {"ok": True, "build": build}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def handler(event, context):
|
|
133
|
+
field = event["fieldName"]
|
|
134
|
+
arguments = parse_input(event["arguments"]["input"])
|
|
135
|
+
actor = actor_from_event(event)
|
|
136
|
+
try:
|
|
137
|
+
if field == "deriveMarkus":
|
|
138
|
+
return derive_markus_action(arguments)
|
|
139
|
+
if field == "saveItemDraft":
|
|
140
|
+
return save_item_draft_action(arguments, actor)
|
|
141
|
+
if field == "publishItem":
|
|
142
|
+
return publish_item_action(arguments, actor)
|
|
143
|
+
if field == "unpublishItem":
|
|
144
|
+
return unpublish_item_action(arguments, actor)
|
|
145
|
+
if field == "requestStagingBuild":
|
|
146
|
+
return request_staging_build_action()
|
|
147
|
+
except PublishError as error:
|
|
148
|
+
return failure_response(error)
|
|
149
|
+
raise ValueError(f"Unsupported field {field}")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineFunction } from "@aws-amplify/backend";
|
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
|
3
|
+
import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { pythonLambdaCode } from "../shared/python-bundle.js";
|
|
8
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = path.resolve(dirname, "../../..");
|
|
10
|
+
export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.5.1", "Pillow>=10,<12"];
|
|
11
|
+
function installCheckoutRequirements(outputDir) {
|
|
12
|
+
const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
|
|
13
|
+
execSync(`python3 -m pip install -r "${requirementsPath}" -t "${outputDir}" --no-cache-dir --platform manylinux2014_aarch64 --implementation cp --python-version 3.12 --only-binary=:all: --ignore-requires-python`, { cwd: projectRoot, stdio: "inherit" });
|
|
14
|
+
}
|
|
15
|
+
export const contentActions = defineFunction((scope) => {
|
|
16
|
+
return new Function(scope, "papyrus-content-actions", {
|
|
17
|
+
runtime: Runtime.PYTHON_3_12,
|
|
18
|
+
architecture: Architecture.ARM_64,
|
|
19
|
+
handler: "handler.handler",
|
|
20
|
+
timeout: Duration.seconds(60),
|
|
21
|
+
memorySize: 1024,
|
|
22
|
+
code: pythonLambdaCode({
|
|
23
|
+
functionDir: "content-actions",
|
|
24
|
+
modules: ["papyrus_content"],
|
|
25
|
+
requirements: contentActionsRequirements,
|
|
26
|
+
noDeps: true,
|
|
27
|
+
checkoutExtras: installCheckoutRequirements,
|
|
28
|
+
}, [
|
|
29
|
+
"bash",
|
|
30
|
+
"-c",
|
|
31
|
+
[
|
|
32
|
+
"set -euo pipefail",
|
|
33
|
+
"mkdir -p /asset-output/papyrus_content",
|
|
34
|
+
"cp amplify/functions/content-actions/handler.py /asset-output/handler.py",
|
|
35
|
+
"cp -R src/papyrus_content/. /asset-output/papyrus_content/",
|
|
36
|
+
"python3 -m pip install -r amplify/functions/content-actions/requirements.txt -t /asset-output --no-cache-dir",
|
|
37
|
+
].join(" && "),
|
|
38
|
+
]),
|
|
39
|
+
});
|
|
40
|
+
}, { resourceGroupName: "data" });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineFunction } from "@aws-amplify/backend";
|
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
|
3
|
+
import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
4
|
+
import { Construct } from "constructs";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { pythonLambdaCode } from "../shared/python-bundle";
|
|
9
|
+
|
|
10
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const projectRoot = path.resolve(dirname, "../../..");
|
|
12
|
+
|
|
13
|
+
export const contentActionsRequirements = ["PyYAML>=6.0.2,<7", "citeproc-py>=0.8.2,<1", "anthus-markus==0.5.1", "Pillow>=10,<12"];
|
|
14
|
+
|
|
15
|
+
function installCheckoutRequirements(outputDir: string): void {
|
|
16
|
+
const requirementsPath = path.join(projectRoot, "amplify/functions/content-actions/requirements.txt");
|
|
17
|
+
execSync(
|
|
18
|
+
`python3 -m pip install -r "${requirementsPath}" -t "${outputDir}" --no-cache-dir --platform manylinux2014_aarch64 --implementation cp --python-version 3.12 --only-binary=:all: --ignore-requires-python`,
|
|
19
|
+
{ cwd: projectRoot, stdio: "inherit" },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const contentActions = defineFunction(
|
|
24
|
+
(scope: Construct) => {
|
|
25
|
+
return new Function(scope, "papyrus-content-actions", {
|
|
26
|
+
runtime: Runtime.PYTHON_3_12,
|
|
27
|
+
architecture: Architecture.ARM_64,
|
|
28
|
+
handler: "handler.handler",
|
|
29
|
+
timeout: Duration.seconds(60),
|
|
30
|
+
memorySize: 1024,
|
|
31
|
+
code: pythonLambdaCode(
|
|
32
|
+
{
|
|
33
|
+
functionDir: "content-actions",
|
|
34
|
+
modules: ["papyrus_content"],
|
|
35
|
+
requirements: contentActionsRequirements,
|
|
36
|
+
noDeps: true,
|
|
37
|
+
checkoutExtras: installCheckoutRequirements,
|
|
38
|
+
},
|
|
39
|
+
[
|
|
40
|
+
"bash",
|
|
41
|
+
"-c",
|
|
42
|
+
[
|
|
43
|
+
"set -euo pipefail",
|
|
44
|
+
"mkdir -p /asset-output/papyrus_content",
|
|
45
|
+
"cp amplify/functions/content-actions/handler.py /asset-output/handler.py",
|
|
46
|
+
"cp -R src/papyrus_content/. /asset-output/papyrus_content/",
|
|
47
|
+
"python3 -m pip install -r amplify/functions/content-actions/requirements.txt -t /asset-output --no-cache-dir",
|
|
48
|
+
].join(" && "),
|
|
49
|
+
],
|
|
50
|
+
),
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
{ resourceGroupName: "data" },
|
|
54
|
+
);
|
|
@@ -58,9 +58,24 @@ function bundleFromPip(outputDir, options) {
|
|
|
58
58
|
// Older pip (e.g. a Python 3.9 build image) checks Requires-Python against the host interpreter, not --python-version.
|
|
59
59
|
"--ignore-requires-python",
|
|
60
60
|
...extra,
|
|
61
|
-
pipSpec(),
|
|
61
|
+
...(options.noDeps ? [] : [pipSpec()]),
|
|
62
62
|
...(options.requirements ?? []),
|
|
63
63
|
], { stdio: "inherit" });
|
|
64
|
+
if (options.noDeps) {
|
|
65
|
+
execFileSync(python, [
|
|
66
|
+
"-m", "pip", "install",
|
|
67
|
+
"--target", outputDir,
|
|
68
|
+
"--no-cache-dir",
|
|
69
|
+
"--no-deps",
|
|
70
|
+
"--platform", "manylinux2014_aarch64",
|
|
71
|
+
"--implementation", "cp",
|
|
72
|
+
"--python-version", "3.12",
|
|
73
|
+
"--only-binary=:all:",
|
|
74
|
+
"--ignore-requires-python",
|
|
75
|
+
...extra,
|
|
76
|
+
pipSpec(),
|
|
77
|
+
], { stdio: "inherit" });
|
|
78
|
+
}
|
|
64
79
|
fs.copyFileSync(path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"), path.join(outputDir, "handler.py"));
|
|
65
80
|
if (options.corpora) {
|
|
66
81
|
const corporaDir = path.join(outputDir, "corpora");
|
|
@@ -74,7 +89,7 @@ function bundleFromPip(outputDir, options) {
|
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
|
-
function bundleFromCheckout(outputDir, options) {
|
|
92
|
+
export function bundleFromCheckout(outputDir, options) {
|
|
78
93
|
fs.copyFileSync(path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"), path.join(outputDir, "handler.py"));
|
|
79
94
|
for (const module of options.modules) {
|
|
80
95
|
fs.mkdirSync(path.join(outputDir, module), { recursive: true });
|
|
@@ -53,6 +53,8 @@ export type PythonBundleOptions = {
|
|
|
53
53
|
checkoutExtras?: (outputDir: string) => void;
|
|
54
54
|
/** Extra pip requirements in package mode, beyond `papyrus-newsroom`. */
|
|
55
55
|
requirements?: string[];
|
|
56
|
+
/** Package mode: install `papyrus-newsroom` with `--no-deps`, then only `requirements`. */
|
|
57
|
+
noDeps?: boolean;
|
|
56
58
|
/** Copy `<cwd>/corpora/*.yml` into the asset (publication-owned steering config). */
|
|
57
59
|
corpora?: boolean;
|
|
58
60
|
};
|
|
@@ -77,11 +79,30 @@ function bundleFromPip(outputDir: string, options: PythonBundleOptions): void {
|
|
|
77
79
|
// Older pip (e.g. a Python 3.9 build image) checks Requires-Python against the host interpreter, not --python-version.
|
|
78
80
|
"--ignore-requires-python",
|
|
79
81
|
...extra,
|
|
80
|
-
pipSpec(),
|
|
82
|
+
...(options.noDeps ? [] : [pipSpec()]),
|
|
81
83
|
...(options.requirements ?? []),
|
|
82
84
|
],
|
|
83
85
|
{ stdio: "inherit" },
|
|
84
86
|
);
|
|
87
|
+
if (options.noDeps) {
|
|
88
|
+
execFileSync(
|
|
89
|
+
python,
|
|
90
|
+
[
|
|
91
|
+
"-m", "pip", "install",
|
|
92
|
+
"--target", outputDir,
|
|
93
|
+
"--no-cache-dir",
|
|
94
|
+
"--no-deps",
|
|
95
|
+
"--platform", "manylinux2014_aarch64",
|
|
96
|
+
"--implementation", "cp",
|
|
97
|
+
"--python-version", "3.12",
|
|
98
|
+
"--only-binary=:all:",
|
|
99
|
+
"--ignore-requires-python",
|
|
100
|
+
...extra,
|
|
101
|
+
pipSpec(),
|
|
102
|
+
],
|
|
103
|
+
{ stdio: "inherit" },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
85
106
|
fs.copyFileSync(
|
|
86
107
|
path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"),
|
|
87
108
|
path.join(outputDir, "handler.py"),
|
|
@@ -98,7 +119,7 @@ function bundleFromPip(outputDir: string, options: PythonBundleOptions): void {
|
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
121
|
|
|
101
|
-
function bundleFromCheckout(outputDir: string, options: PythonBundleOptions): void {
|
|
122
|
+
export function bundleFromCheckout(outputDir: string, options: PythonBundleOptions): void {
|
|
102
123
|
fs.copyFileSync(
|
|
103
124
|
path.join(packageRoot, "amplify/functions", options.functionDir, "handler.py"),
|
|
104
125
|
path.join(outputDir, "handler.py"),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const REBUILD_TRIGGER_ACTIONS = ["amplify:StartJob", "amplify:ListJobs"];
|
|
2
|
+
function branchJobsArn(input, appId, branch) {
|
|
3
|
+
return `arn:aws:amplify:${input.region}:${input.account}:apps/${appId}/branches/${branch}/jobs/*`;
|
|
4
|
+
}
|
|
5
|
+
export function rebuildTriggerSettings(input) {
|
|
6
|
+
const environment = {};
|
|
7
|
+
const resources = [];
|
|
8
|
+
const readerAppId = (input.reader?.amplifyAppId ?? "").trim();
|
|
9
|
+
if (readerAppId !== "") {
|
|
10
|
+
const readerBranch = (input.reader?.branchName ?? "").trim() || "main";
|
|
11
|
+
environment.PAPYRUS_READER_AMPLIFY_APP_ID = readerAppId;
|
|
12
|
+
environment.PAPYRUS_READER_BRANCH = readerBranch;
|
|
13
|
+
resources.push(branchJobsArn(input, readerAppId, readerBranch));
|
|
14
|
+
}
|
|
15
|
+
const cmsAppId = input.cmsAppId.trim();
|
|
16
|
+
if (input.stagingBuildEnabled && cmsAppId !== "") {
|
|
17
|
+
environment.PAPYRUS_CMS_AMPLIFY_APP_ID = cmsAppId;
|
|
18
|
+
environment.PAPYRUS_STAGING_BRANCH = "staging";
|
|
19
|
+
resources.push(branchJobsArn(input, cmsAppId, "staging"));
|
|
20
|
+
}
|
|
21
|
+
return { environment, actions: REBUILD_TRIGGER_ACTIONS, resources };
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment and IAM policy for the content-actions Lambda's site triggers.
|
|
3
|
+
* Pure (no CDK imports) so the policy can be tested without synthesizing.
|
|
4
|
+
*/
|
|
5
|
+
export type RebuildTriggerInput = {
|
|
6
|
+
reader?: { amplifyAppId: string; branchName?: string };
|
|
7
|
+
stagingBuildEnabled: boolean;
|
|
8
|
+
cmsAppId: string;
|
|
9
|
+
region: string;
|
|
10
|
+
account: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type RebuildTriggerSettings = {
|
|
14
|
+
environment: Record<string, string>;
|
|
15
|
+
actions: string[];
|
|
16
|
+
resources: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const REBUILD_TRIGGER_ACTIONS = ["amplify:StartJob", "amplify:ListJobs"];
|
|
20
|
+
|
|
21
|
+
function branchJobsArn(input: RebuildTriggerInput, appId: string, branch: string): string {
|
|
22
|
+
return `arn:aws:amplify:${input.region}:${input.account}:apps/${appId}/branches/${branch}/jobs/*`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rebuildTriggerSettings(input: RebuildTriggerInput): RebuildTriggerSettings {
|
|
26
|
+
const environment: Record<string, string> = {};
|
|
27
|
+
const resources: string[] = [];
|
|
28
|
+
const readerAppId = (input.reader?.amplifyAppId ?? "").trim();
|
|
29
|
+
if (readerAppId !== "") {
|
|
30
|
+
const readerBranch = (input.reader?.branchName ?? "").trim() || "main";
|
|
31
|
+
environment.PAPYRUS_READER_AMPLIFY_APP_ID = readerAppId;
|
|
32
|
+
environment.PAPYRUS_READER_BRANCH = readerBranch;
|
|
33
|
+
resources.push(branchJobsArn(input, readerAppId, readerBranch));
|
|
34
|
+
}
|
|
35
|
+
const cmsAppId = input.cmsAppId.trim();
|
|
36
|
+
if (input.stagingBuildEnabled && cmsAppId !== "") {
|
|
37
|
+
environment.PAPYRUS_CMS_AMPLIFY_APP_ID = cmsAppId;
|
|
38
|
+
environment.PAPYRUS_STAGING_BRANCH = "staging";
|
|
39
|
+
resources.push(branchJobsArn(input, cmsAppId, "staging"));
|
|
40
|
+
}
|
|
41
|
+
return { environment, actions: REBUILD_TRIGGER_ACTIONS, resources };
|
|
42
|
+
}
|
|
@@ -21,6 +21,12 @@ export type PapyrusSiteBackendConfig = {
|
|
|
21
21
|
*/
|
|
22
22
|
productionAppId?: string;
|
|
23
23
|
auth?: PapyrusAuthConfig;
|
|
24
|
+
/** Static reader app (Markus-static sites) that a publish rebuilds through IAM `amplify:StartJob`. */
|
|
25
|
+
reader?: { amplifyAppId: string; branchName?: string };
|
|
26
|
+
/** Lets the newsroom start the `staging` branch build of this CMS app. */
|
|
27
|
+
stagingBuild?: { enabled: boolean };
|
|
28
|
+
/** Base URL of a Pretext reader whose `/api/revalidate` a publish calls (secret `PAPYRUS_REVALIDATE_SECRET`). */
|
|
29
|
+
revalidateBaseUrl?: string;
|
|
24
30
|
features?: {
|
|
25
31
|
consoleResponder?: boolean;
|
|
26
32
|
inboundEmail?: boolean;
|