@anthusai/papyrus 0.0.0 → 1.0.0-next.1
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/LICENSE +21 -0
- package/amplify/auth/resource.js +61 -0
- package/amplify/auth/resource.ts +65 -0
- package/amplify/data/resource.js +2037 -0
- package/amplify/data/resource.ts +2148 -0
- package/amplify/data/schema.js +1850 -0
- package/amplify/data/schema.ts +1953 -0
- package/amplify/functions/assignment-action/handler.ts +1416 -0
- package/amplify/functions/assignment-action/resource.js +8 -0
- package/amplify/functions/assignment-action/resource.ts +9 -0
- package/amplify/functions/category-action/handler.ts +2259 -0
- package/amplify/functions/category-action/resource.js +8 -0
- package/amplify/functions/category-action/resource.ts +9 -0
- package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
- package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
- package/amplify/functions/console-chat-responder/Dockerfile +19 -0
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
- package/amplify/functions/console-chat-responder/resource.js +153 -0
- package/amplify/functions/console-chat-responder/resource.ts +179 -0
- package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
- package/amplify/functions/email-submission-processor/handler.py +46 -0
- package/amplify/functions/email-submission-processor/requirements.txt +2 -0
- package/amplify/functions/email-submission-processor/resource.js +56 -0
- package/amplify/functions/email-submission-processor/resource.ts +69 -0
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
- package/amplify/functions/knowledge-query/handler.py +1 -0
- package/amplify/functions/knowledge-query/requirements.txt +3 -0
- package/amplify/functions/knowledge-query/resource.js +50 -0
- package/amplify/functions/knowledge-query/resource.ts +61 -0
- package/amplify/functions/manage-user-role/handler.ts +799 -0
- package/amplify/functions/manage-user-role/resource.js +8 -0
- package/amplify/functions/manage-user-role/resource.ts +9 -0
- package/amplify/functions/model-attachment-upload/handler.ts +387 -0
- package/amplify/functions/model-attachment-upload/resource.js +8 -0
- package/amplify/functions/model-attachment-upload/resource.ts +9 -0
- package/amplify/functions/newsroom-summary/handler.ts +321 -0
- package/amplify/functions/newsroom-summary/resource.js +8 -0
- package/amplify/functions/newsroom-summary/resource.ts +9 -0
- package/amplify/functions/procedure-action/handler.ts +621 -0
- package/amplify/functions/procedure-action/resource.js +8 -0
- package/amplify/functions/procedure-action/resource.ts +9 -0
- package/amplify/functions/reader-settings/handler.ts +322 -0
- package/amplify/functions/reader-settings/resource.js +8 -0
- package/amplify/functions/reader-settings/resource.ts +9 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
- package/amplify/functions/ses-inbound-receive/resource.js +8 -0
- package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
- package/amplify/functions/shared/amplify-secrets.ts +65 -0
- package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
- package/amplify/functions/shared/email-mime-intake.ts +196 -0
- package/amplify/functions/shared/email-submission.ts +415 -0
- package/amplify/functions/shared/inbound-email-intake.ts +37 -0
- package/amplify/functions/shared/lambda-data-client.ts +21 -0
- package/amplify/functions/shared/model-payloads.ts +186 -0
- package/amplify/functions/shared/python-bundle.js +115 -0
- package/amplify/functions/shared/python-bundle.ts +141 -0
- package/amplify/functions/shared/slack-events.ts +111 -0
- package/amplify/functions/slack-delivery/handler.py +112 -0
- package/amplify/functions/slack-delivery/requirements.txt +2 -0
- package/amplify/functions/slack-delivery/resource.js +38 -0
- package/amplify/functions/slack-delivery/resource.ts +51 -0
- package/amplify/functions/slack-events/handler.ts +155 -0
- package/amplify/functions/slack-events/resource.js +11 -0
- package/amplify/functions/slack-events/resource.ts +12 -0
- package/amplify/inbound-email/stack.js +46 -0
- package/amplify/inbound-email/stack.ts +61 -0
- package/amplify/package.json +3 -0
- package/amplify/site-backend-config.js +1 -0
- package/amplify/site-backend-config.ts +30 -0
- package/amplify/site-backend.js +587 -0
- package/amplify/site-backend.ts +704 -0
- package/amplify/storage/resource.js +18 -0
- package/amplify/storage/resource.ts +19 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
- package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
- package/app/[year]/[month]/[day]/page.tsx +22 -0
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
- package/app/api/archive/editions/route.ts +18 -0
- package/app/api/media/[...path]/route.ts +35 -0
- package/app/api/revalidate/route.ts +94 -0
- package/app/archive/page.tsx +24 -0
- package/app/articles/[slug]/page.tsx +35 -0
- package/app/globals.css +10329 -0
- package/app/layout.tsx +146 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
- package/app/newsroom/[section]/page.tsx +47 -0
- package/app/newsroom/forum/[threadId]/page.tsx +35 -0
- package/app/newsroom/insights/[threadId]/page.tsx +22 -0
- package/app/newsroom/insights/page.tsx +14 -0
- package/app/newsroom/layout.tsx +12 -0
- package/app/newsroom/page.tsx +31 -0
- package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
- package/app/page.tsx +272 -0
- package/app/settings/page.tsx +14 -0
- package/app/tailwind.css +65 -0
- package/bin/papyrus-app.mjs +10 -0
- package/bin/papyrus-infra.mjs +39 -0
- package/components/ai-elements/conversation.tsx +168 -0
- package/components/ai-elements/message.tsx +360 -0
- package/components/ai-elements/model-selector.tsx +30 -0
- package/components/ai-elements/prompt-input.tsx +1461 -0
- package/components/ai-elements/shimmer.tsx +76 -0
- package/components/ai-elements/suggestion.tsx +54 -0
- package/components/ai-elements/tool.tsx +173 -0
- package/components/amplify-client-provider.tsx +140 -0
- package/components/archive-grid.tsx +89 -0
- package/components/archive-shell.tsx +41 -0
- package/components/article-video.tsx +47 -0
- package/components/blog-page-background.tsx +20 -0
- package/components/dev-sandbox-editor-sign-in.tsx +60 -0
- package/components/news-desk-client-provider.tsx +211 -0
- package/components/news-desk-page.tsx +119 -0
- package/components/news-desk-taxonomy-client.ts +3684 -0
- package/components/newspaper.tsx +1929 -0
- package/components/newsroom-assignments-view.tsx +664 -0
- package/components/newsroom-ops-overview.tsx +226 -0
- package/components/newsroom-ops-shell.tsx +319 -0
- package/components/newsroom-references-view.tsx +598 -0
- package/components/newsroom-topics-view.tsx +425 -0
- package/components/papyrus-console-shell.tsx +1912 -0
- package/components/pictogram-figure.tsx +65 -0
- package/components/presentation-footer.tsx +146 -0
- package/components/reader-auth-control.tsx +162 -0
- package/components/reader-auth-state.ts +122 -0
- package/components/reader-settings.ts +332 -0
- package/components/reader-theme-control.tsx +85 -0
- package/components/reference-source-preview.tsx +163 -0
- package/components/settings-page.tsx +239 -0
- package/components/topic-steering-workspace.tsx +18063 -0
- package/components/ui/badge.tsx +30 -0
- package/components/ui/button-group.tsx +87 -0
- package/components/ui/button.tsx +58 -0
- package/components/ui/card.tsx +32 -0
- package/components/ui/command.tsx +194 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/dropdown-menu.tsx +266 -0
- package/components/ui/hover-card.tsx +51 -0
- package/components/ui/input-group.tsx +158 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +16 -0
- package/components/ui/select.tsx +199 -0
- package/components/ui/separator.tsx +23 -0
- package/components/ui/sheet.tsx +92 -0
- package/components/ui/spinner.tsx +10 -0
- package/components/ui/tabs.tsx +113 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components/use-resolved-papyrus-theme.ts +47 -0
- package/infra/amplify-app-shell.js +109 -0
- package/infra/amplify-app-shell.ts +155 -0
- package/infra/index.js +12 -0
- package/lib/amplify-outputs-guard.ts +24 -0
- package/lib/amplify-outputs-path.ts +16 -0
- package/lib/amplify-server-runtime.ts +63 -0
- package/lib/archive-data.ts +30 -0
- package/lib/archive-types.ts +15 -0
- package/lib/articles.ts +320 -0
- package/lib/cached-content-repository.ts +89 -0
- package/lib/category-dashboard.ts +37 -0
- package/lib/category-repository.ts +1907 -0
- package/lib/console-chat-client.ts +1316 -0
- package/lib/console-web-context.ts +118 -0
- package/lib/content-load-timing.ts +23 -0
- package/lib/content-repository.ts +40 -0
- package/lib/content-types.ts +104 -0
- package/lib/define-site.js +3 -0
- package/lib/define-site.ts +21 -0
- package/lib/demo-amplify-outputs.ts +15 -0
- package/lib/dev-sandbox-editor-auth.ts +18 -0
- package/lib/doctrine.ts +128 -0
- package/lib/edition-routes.ts +155 -0
- package/lib/edition-sections.ts +111 -0
- package/lib/empty-edition-layout-plan.ts +109 -0
- package/lib/excerpts.ts +8 -0
- package/lib/graphql-content-repository.ts +986 -0
- package/lib/image-url.ts +10 -0
- package/lib/layout-plan.ts +981 -0
- package/lib/layout-scenarios.ts +564 -0
- package/lib/markus-body.ts +168 -0
- package/lib/markus-ir.ts +235 -0
- package/lib/markus-projection.ts +243 -0
- package/lib/markus-to-article.ts +93 -0
- package/lib/news-desk-session.ts +160 -0
- package/lib/newspaper-layout.ts +3459 -0
- package/lib/newsroom-assignments.ts +173 -0
- package/lib/newsroom-base-path.ts +48 -0
- package/lib/newsroom-card-layout.ts +102 -0
- package/lib/newsroom-category-drilldown.ts +133 -0
- package/lib/newsroom-demo-dashboard.ts +267 -0
- package/lib/newsroom-demo-profile.ts +46 -0
- package/lib/newsroom-forum-routes.ts +153 -0
- package/lib/newsroom-index-filters.ts +202 -0
- package/lib/newsroom-knowledge-query-request.ts +51 -0
- package/lib/newsroom-list-selection.ts +42 -0
- package/lib/newsroom-nav.ts +42 -0
- package/lib/newsroom-references.ts +116 -0
- package/lib/newsroom-sections.ts +52 -0
- package/lib/newsroom-topics.ts +118 -0
- package/lib/papyrus-object-kinds.ts +24 -0
- package/lib/papyrus-web-locations.ts +480 -0
- package/lib/pilobol-sample.ts +167 -0
- package/lib/presentation-footer.ts +92 -0
- package/lib/pretext-layout.ts +179 -0
- package/lib/public-placeholder-config.ts +102 -0
- package/lib/publication-items.ts +181 -0
- package/lib/reader-route-config.ts +1 -0
- package/lib/reader-static-params.ts +56 -0
- package/lib/reader-storage-url.ts +63 -0
- package/lib/reference-display.ts +74 -0
- package/lib/reference-policy.ts +97 -0
- package/lib/reference-source-preview.ts +175 -0
- package/lib/renderer-config.ts +50 -0
- package/lib/renderer.ts +87 -0
- package/lib/reporting-story-budget.ts +638 -0
- package/lib/semantic-graph.ts +554 -0
- package/lib/site-brand-tenant.ts +41 -0
- package/lib/site-brand.ts +179 -0
- package/lib/site-renderer.ts +17 -0
- package/lib/site-stack.ts +195 -0
- package/lib/themed-image.ts +30 -0
- package/lib/utils.ts +6 -0
- package/middleware.ts +78 -0
- package/package.json +296 -1
- package/publications/anth_us/brand.ts +68 -0
- package/publications/anth_us/theme.css +66 -0
- package/publications/papyrus/theme.css +11 -0
- package/publications/pilobol_us/brand.ts +37 -0
- package/publications/pilobol_us/theme.css +140 -0
- package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
- package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
- package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
- package/publications/threat_intelligence/brand.ts +40 -0
- package/publications/threat_intelligence/pictograms/art.tsx +608 -0
- package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
- package/publications/threat_intelligence/pictograms/registry.ts +30 -0
- package/publications/threat_intelligence/pictograms/system.tsx +305 -0
- package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
- package/publications/threat_intelligence/theme.css +1440 -0
- package/renderers/markus/index.ts +25 -0
- package/renderers/markus/stubs.tsx +33 -0
- package/renderers/pretext/article-page.tsx +174 -0
- package/renderers/pretext/index.ts +30 -0
- package/renderers/pretext/layouts.ts +16 -0
- package/renderers/pretext/presentation-shell.tsx +477 -0
- package/routes.manifest.json +182 -0
- package/scripts/fixtures/pilobol-sample.ast.json +234 -0
- package/src/backend.js +1 -0
- package/src/backend.ts +3 -0
- package/src/sync.mjs +87 -0
- package/src/with-papyrus.d.mts +2 -0
- package/src/with-papyrus.mjs +47 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Select as SelectPrimitive } from "@base-ui/react/select"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
const Select = SelectPrimitive.Root
|
|
8
|
+
|
|
9
|
+
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<SelectPrimitive.Group
|
|
12
|
+
data-slot="select-group"
|
|
13
|
+
className={cn("scroll-my-1 p-1", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
|
|
20
|
+
return (
|
|
21
|
+
<SelectPrimitive.Value
|
|
22
|
+
data-slot="select-value"
|
|
23
|
+
className={cn("flex flex-1 text-left", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function SelectTrigger({
|
|
30
|
+
className,
|
|
31
|
+
size = "default",
|
|
32
|
+
children,
|
|
33
|
+
...props
|
|
34
|
+
}: SelectPrimitive.Trigger.Props & {
|
|
35
|
+
size?: "sm" | "default"
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<SelectPrimitive.Trigger
|
|
39
|
+
data-slot="select-trigger"
|
|
40
|
+
data-size={size}
|
|
41
|
+
className={cn(
|
|
42
|
+
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
<SelectPrimitive.Icon
|
|
49
|
+
render={
|
|
50
|
+
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
</SelectPrimitive.Trigger>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function SelectContent({
|
|
58
|
+
className,
|
|
59
|
+
children,
|
|
60
|
+
side = "bottom",
|
|
61
|
+
sideOffset = 4,
|
|
62
|
+
align = "center",
|
|
63
|
+
alignOffset = 0,
|
|
64
|
+
alignItemWithTrigger = true,
|
|
65
|
+
...props
|
|
66
|
+
}: SelectPrimitive.Popup.Props &
|
|
67
|
+
Pick<
|
|
68
|
+
SelectPrimitive.Positioner.Props,
|
|
69
|
+
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
|
|
70
|
+
>) {
|
|
71
|
+
return (
|
|
72
|
+
<SelectPrimitive.Portal>
|
|
73
|
+
<SelectPrimitive.Positioner
|
|
74
|
+
side={side}
|
|
75
|
+
sideOffset={sideOffset}
|
|
76
|
+
align={align}
|
|
77
|
+
alignOffset={alignOffset}
|
|
78
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
79
|
+
className="isolate z-50"
|
|
80
|
+
>
|
|
81
|
+
<SelectPrimitive.Popup
|
|
82
|
+
data-slot="select-content"
|
|
83
|
+
data-align-trigger={alignItemWithTrigger}
|
|
84
|
+
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
<SelectScrollUpButton />
|
|
88
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
89
|
+
<SelectScrollDownButton />
|
|
90
|
+
</SelectPrimitive.Popup>
|
|
91
|
+
</SelectPrimitive.Positioner>
|
|
92
|
+
</SelectPrimitive.Portal>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function SelectLabel({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: SelectPrimitive.GroupLabel.Props) {
|
|
100
|
+
return (
|
|
101
|
+
<SelectPrimitive.GroupLabel
|
|
102
|
+
data-slot="select-label"
|
|
103
|
+
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function SelectItem({
|
|
110
|
+
className,
|
|
111
|
+
children,
|
|
112
|
+
...props
|
|
113
|
+
}: SelectPrimitive.Item.Props) {
|
|
114
|
+
return (
|
|
115
|
+
<SelectPrimitive.Item
|
|
116
|
+
data-slot="select-item"
|
|
117
|
+
className={cn(
|
|
118
|
+
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
|
124
|
+
{children}
|
|
125
|
+
</SelectPrimitive.ItemText>
|
|
126
|
+
<SelectPrimitive.ItemIndicator
|
|
127
|
+
render={
|
|
128
|
+
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
<CheckIcon className="pointer-events-none" />
|
|
132
|
+
</SelectPrimitive.ItemIndicator>
|
|
133
|
+
</SelectPrimitive.Item>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function SelectSeparator({
|
|
138
|
+
className,
|
|
139
|
+
...props
|
|
140
|
+
}: SelectPrimitive.Separator.Props) {
|
|
141
|
+
return (
|
|
142
|
+
<SelectPrimitive.Separator
|
|
143
|
+
data-slot="select-separator"
|
|
144
|
+
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function SelectScrollUpButton({
|
|
151
|
+
className,
|
|
152
|
+
...props
|
|
153
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
|
|
154
|
+
return (
|
|
155
|
+
<SelectPrimitive.ScrollUpArrow
|
|
156
|
+
data-slot="select-scroll-up-button"
|
|
157
|
+
className={cn(
|
|
158
|
+
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
159
|
+
className
|
|
160
|
+
)}
|
|
161
|
+
{...props}
|
|
162
|
+
>
|
|
163
|
+
<ChevronUpIcon
|
|
164
|
+
/>
|
|
165
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function SelectScrollDownButton({
|
|
170
|
+
className,
|
|
171
|
+
...props
|
|
172
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
|
|
173
|
+
return (
|
|
174
|
+
<SelectPrimitive.ScrollDownArrow
|
|
175
|
+
data-slot="select-scroll-down-button"
|
|
176
|
+
className={cn(
|
|
177
|
+
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
178
|
+
className
|
|
179
|
+
)}
|
|
180
|
+
{...props}
|
|
181
|
+
>
|
|
182
|
+
<ChevronDownIcon
|
|
183
|
+
/>
|
|
184
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
Select,
|
|
190
|
+
SelectContent,
|
|
191
|
+
SelectGroup,
|
|
192
|
+
SelectItem,
|
|
193
|
+
SelectLabel,
|
|
194
|
+
SelectScrollDownButton,
|
|
195
|
+
SelectScrollUpButton,
|
|
196
|
+
SelectSeparator,
|
|
197
|
+
SelectTrigger,
|
|
198
|
+
SelectValue,
|
|
199
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Separator({
|
|
6
|
+
className,
|
|
7
|
+
orientation = "horizontal",
|
|
8
|
+
...props
|
|
9
|
+
}: SeparatorPrimitive.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<SeparatorPrimitive
|
|
12
|
+
data-slot="separator"
|
|
13
|
+
orientation={orientation}
|
|
14
|
+
className={cn(
|
|
15
|
+
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Separator }
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
5
|
+
import { XIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
import { Button } from "./button";
|
|
9
|
+
|
|
10
|
+
type SheetSide = "bottom" | "left" | "right";
|
|
11
|
+
|
|
12
|
+
const sideClasses: Record<SheetSide, string> = {
|
|
13
|
+
bottom:
|
|
14
|
+
"inset-x-0 bottom-0 top-auto max-h-[85dvh] w-full rounded-t-2xl border-t data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
|
|
15
|
+
left: "inset-y-0 left-0 h-full w-[min(100vw,20rem)] rounded-r-2xl border-r data-open:slide-in-from-left data-closed:slide-out-to-left",
|
|
16
|
+
right: "inset-y-0 right-0 h-full w-[min(100vw,20rem)] rounded-l-2xl border-l data-open:slide-in-from-right data-closed:slide-out-to-right",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function Sheet({ ...props }: DialogPrimitive.Root.Props) {
|
|
20
|
+
return <DialogPrimitive.Root data-slot="sheet" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function SheetTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
|
24
|
+
return <DialogPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SheetClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
28
|
+
return <DialogPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function SheetContent({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
showCloseButton = true,
|
|
35
|
+
side = "bottom",
|
|
36
|
+
...props
|
|
37
|
+
}: DialogPrimitive.Popup.Props & {
|
|
38
|
+
showCloseButton?: boolean;
|
|
39
|
+
side?: SheetSide;
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<DialogPrimitive.Portal>
|
|
43
|
+
<DialogPrimitive.Backdrop
|
|
44
|
+
className="fixed inset-0 z-50 bg-black/40 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0"
|
|
45
|
+
/>
|
|
46
|
+
<DialogPrimitive.Popup
|
|
47
|
+
className={cn(
|
|
48
|
+
"fixed z-50 grid gap-4 bg-background p-4 text-foreground shadow-lg outline-none duration-200 data-open:animate-in data-closed:animate-out",
|
|
49
|
+
sideClasses[side],
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
{showCloseButton ? (
|
|
56
|
+
<DialogPrimitive.Close
|
|
57
|
+
render={
|
|
58
|
+
<Button className="absolute top-3 right-3" size="icon-sm" variant="ghost" />
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
<XIcon className="size-4" />
|
|
62
|
+
<span className="sr-only">Close</span>
|
|
63
|
+
</DialogPrimitive.Close>
|
|
64
|
+
) : null}
|
|
65
|
+
</DialogPrimitive.Popup>
|
|
66
|
+
</DialogPrimitive.Portal>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
71
|
+
return <div className={cn("flex flex-col gap-1 pr-8", className)} {...props} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function SheetTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
75
|
+
return (
|
|
76
|
+
<DialogPrimitive.Title
|
|
77
|
+
className={cn("text-base font-semibold tracking-tight", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function SheetDescription({ className, ...props }: DialogPrimitive.Description.Props) {
|
|
84
|
+
return (
|
|
85
|
+
<DialogPrimitive.Description
|
|
86
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils"
|
|
2
|
+
import { Loader2Icon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
5
|
+
return (
|
|
6
|
+
<Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
|
7
|
+
)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { Spinner }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
type TabsContextValue = {
|
|
8
|
+
value: string;
|
|
9
|
+
setValue: (value: string) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const TabsContext = React.createContext<TabsContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
function useTabsContext(componentName: string): TabsContextValue {
|
|
15
|
+
const context = React.useContext(TabsContext);
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error(`${componentName} must be used within <Tabs>.`);
|
|
18
|
+
}
|
|
19
|
+
return context;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function Tabs({
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
defaultValue,
|
|
26
|
+
onValueChange,
|
|
27
|
+
value,
|
|
28
|
+
}: React.PropsWithChildren<{
|
|
29
|
+
className?: string;
|
|
30
|
+
defaultValue: string;
|
|
31
|
+
onValueChange?: (value: string) => void;
|
|
32
|
+
value?: string;
|
|
33
|
+
}>) {
|
|
34
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
35
|
+
const activeValue = value ?? uncontrolledValue;
|
|
36
|
+
const setValue = React.useCallback(
|
|
37
|
+
(nextValue: string) => {
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
setUncontrolledValue(nextValue);
|
|
40
|
+
}
|
|
41
|
+
onValueChange?.(nextValue);
|
|
42
|
+
},
|
|
43
|
+
[onValueChange, value],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TabsContext.Provider value={{ value: activeValue, setValue }}>
|
|
48
|
+
<div className={cn("flex flex-col gap-3", className)}>{children}</div>
|
|
49
|
+
</TabsContext.Provider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TabsList({
|
|
54
|
+
children,
|
|
55
|
+
className,
|
|
56
|
+
}: React.PropsWithChildren<{ className?: string }>) {
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
aria-orientation="horizontal"
|
|
60
|
+
className={cn(
|
|
61
|
+
"inline-flex h-9 w-fit items-center rounded-lg bg-muted/50 p-1 text-muted-foreground",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
role="tablist"
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function TabsTrigger({
|
|
72
|
+
children,
|
|
73
|
+
className,
|
|
74
|
+
value,
|
|
75
|
+
}: React.PropsWithChildren<{ className?: string; value: string }>) {
|
|
76
|
+
const context = useTabsContext("TabsTrigger");
|
|
77
|
+
const active = context.value === value;
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
aria-selected={active}
|
|
81
|
+
className={cn(
|
|
82
|
+
"inline-flex min-w-[7rem] items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium transition-all",
|
|
83
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[rgb(var(--ink-solid-rgb)_/_0.18)]",
|
|
84
|
+
active
|
|
85
|
+
? "bg-primary/10 text-foreground shadow-none ring-1 ring-primary/15"
|
|
86
|
+
: "text-muted-foreground hover:bg-primary/5 hover:text-foreground",
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
data-state={active ? "active" : "inactive"}
|
|
90
|
+
onClick={() => context.setValue(value)}
|
|
91
|
+
role="tab"
|
|
92
|
+
type="button"
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</button>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function TabsContent({
|
|
100
|
+
children,
|
|
101
|
+
className,
|
|
102
|
+
value,
|
|
103
|
+
}: React.PropsWithChildren<{ className?: string; value: string }>) {
|
|
104
|
+
const context = useTabsContext("TabsContent");
|
|
105
|
+
if (context.value !== value) return null;
|
|
106
|
+
return (
|
|
107
|
+
<div className={className} data-state="active" role="tabpanel">
|
|
108
|
+
{children}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function TooltipProvider({
|
|
8
|
+
delay = 0,
|
|
9
|
+
...props
|
|
10
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TooltipPrimitive.Provider
|
|
13
|
+
data-slot="tooltip-provider"
|
|
14
|
+
delay={delay}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
|
21
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
|
|
25
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipContent({
|
|
29
|
+
className,
|
|
30
|
+
side = "top",
|
|
31
|
+
sideOffset = 4,
|
|
32
|
+
align = "center",
|
|
33
|
+
alignOffset = 0,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: TooltipPrimitive.Popup.Props &
|
|
37
|
+
Pick<
|
|
38
|
+
TooltipPrimitive.Positioner.Props,
|
|
39
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
40
|
+
>) {
|
|
41
|
+
return (
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<TooltipPrimitive.Positioner
|
|
44
|
+
align={align}
|
|
45
|
+
alignOffset={alignOffset}
|
|
46
|
+
side={side}
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
className="isolate z-50"
|
|
49
|
+
>
|
|
50
|
+
<TooltipPrimitive.Popup
|
|
51
|
+
data-slot="tooltip-content"
|
|
52
|
+
className={cn(
|
|
53
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
|
60
|
+
</TooltipPrimitive.Popup>
|
|
61
|
+
</TooltipPrimitive.Positioner>
|
|
62
|
+
</TooltipPrimitive.Portal>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import type { ResolvedTheme } from "../lib/themed-image";
|
|
5
|
+
|
|
6
|
+
const SETTINGS_EVENT = "papyrus:settings-changed";
|
|
7
|
+
|
|
8
|
+
function readResolvedTheme(): ResolvedTheme {
|
|
9
|
+
if (typeof document === "undefined" || typeof window === "undefined") return "light";
|
|
10
|
+
const theme = document.documentElement.dataset.papyrusTheme;
|
|
11
|
+
if (theme === "dark") return "dark";
|
|
12
|
+
if (theme === "light") return "light";
|
|
13
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useResolvedPapyrusTheme(): ResolvedTheme {
|
|
17
|
+
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>("light");
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
21
|
+
const refresh = () => setResolvedTheme(readResolvedTheme());
|
|
22
|
+
refresh();
|
|
23
|
+
media.addEventListener("change", refresh);
|
|
24
|
+
window.addEventListener(SETTINGS_EVENT, refresh);
|
|
25
|
+
window.addEventListener("storage", refresh);
|
|
26
|
+
const observer = new MutationObserver((mutations) => {
|
|
27
|
+
for (const mutation of mutations) {
|
|
28
|
+
if (mutation.type === "attributes" && mutation.attributeName === "data-papyrus-theme") {
|
|
29
|
+
refresh();
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
observer.observe(document.documentElement, {
|
|
35
|
+
attributes: true,
|
|
36
|
+
attributeFilter: ["data-papyrus-theme"],
|
|
37
|
+
});
|
|
38
|
+
return () => {
|
|
39
|
+
media.removeEventListener("change", refresh);
|
|
40
|
+
window.removeEventListener(SETTINGS_EVENT, refresh);
|
|
41
|
+
window.removeEventListener("storage", refresh);
|
|
42
|
+
observer.disconnect();
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return resolvedTheme;
|
|
47
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Stack, SecretValue } from "aws-cdk-lib";
|
|
2
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
3
|
+
import * as amplify from "aws-cdk-lib/aws-amplify";
|
|
4
|
+
/**
|
|
5
|
+
* Provisions the Amplify *app shell* for a Papyrus publication CMS:
|
|
6
|
+
*
|
|
7
|
+
* - AWS::Amplify::App (WEB_COMPUTE, GitHub repo connection via PAT in Secrets Manager)
|
|
8
|
+
* - AWS::Amplify::Branch (main, with PAPYRUS_SITE_BRAND env vars)
|
|
9
|
+
* - AWS::Amplify::Domain (newsroom.<site>.us on the site's Route 53 zone)
|
|
10
|
+
* - IAM service role (backend deploy) + compute role (SSR rendering)
|
|
11
|
+
*
|
|
12
|
+
* The Papyrus *backend* (AppSync, Storage, Lambda) is NOT created here. It
|
|
13
|
+
* deploys via `ampx pipeline-deploy` from the repo `amplify.yml` once Amplify
|
|
14
|
+
* CI runs a build on the branch. This stack only creates the container the
|
|
15
|
+
* backend deploys into, so the app is never "created manually" in the console.
|
|
16
|
+
*
|
|
17
|
+
* The GitHub access token is read from Secrets Manager by dynamic reference,
|
|
18
|
+
* so the token value never appears in the synthesized template.
|
|
19
|
+
*/
|
|
20
|
+
export class AmplifyAppShellStack extends Stack {
|
|
21
|
+
appId;
|
|
22
|
+
defaultDomain;
|
|
23
|
+
constructor(scope, id, config, props) {
|
|
24
|
+
super(scope, id, props);
|
|
25
|
+
const githubToken = SecretValue.secretsManager(config.githubTokenSecretName).unsafeUnwrap();
|
|
26
|
+
// Service role Amplify assumes to deploy the backend (ampx pipeline-deploy
|
|
27
|
+
// creates AppSync, Storage, Lambda, etc., so it needs broad permissions).
|
|
28
|
+
const serviceRole = new iam.Role(this, "AmplifyServiceRole", {
|
|
29
|
+
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
30
|
+
description: `Backend deploy service role for Amplify app ${config.appName}`,
|
|
31
|
+
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")],
|
|
32
|
+
});
|
|
33
|
+
// WEB_COMPUTE (Next.js SSR) requires a compute role for the rendering server.
|
|
34
|
+
const computeRole = new iam.Role(this, "AmplifyComputeRole", {
|
|
35
|
+
assumedBy: new iam.ServicePrincipal("amplify.amazonaws.com"),
|
|
36
|
+
description: `SSR compute role for Amplify app ${config.appName}`,
|
|
37
|
+
managedPolicies: [
|
|
38
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"),
|
|
39
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
const app = new amplify.CfnApp(this, "App", {
|
|
43
|
+
name: config.appName,
|
|
44
|
+
description: config.description,
|
|
45
|
+
platform: config.platform,
|
|
46
|
+
repository: config.repository,
|
|
47
|
+
accessToken: githubToken,
|
|
48
|
+
iamServiceRole: serviceRole.roleArn,
|
|
49
|
+
computeRoleArn: computeRole.roleArn,
|
|
50
|
+
buildSpec: this.buildSpec(),
|
|
51
|
+
customRules: [{ source: "/<*>", target: "/index.html", status: "404-200" }],
|
|
52
|
+
});
|
|
53
|
+
this.appId = app.attrAppId;
|
|
54
|
+
this.defaultDomain = `${app.attrAppId}.amplifyapp.com`;
|
|
55
|
+
const branch = new amplify.CfnBranch(this, "Branch", {
|
|
56
|
+
appId: app.attrAppId,
|
|
57
|
+
branchName: config.branchName,
|
|
58
|
+
stage: "PRODUCTION",
|
|
59
|
+
enableAutoBuild: config.enableAutoBuild ?? true,
|
|
60
|
+
enablePerformanceMode: false,
|
|
61
|
+
environmentVariables: Object.entries(config.environment).map(([key, value]) => ({
|
|
62
|
+
name: key,
|
|
63
|
+
value,
|
|
64
|
+
})),
|
|
65
|
+
});
|
|
66
|
+
branch.addResourceDependency(app);
|
|
67
|
+
const domain = new amplify.CfnDomain(this, "Domain", {
|
|
68
|
+
appId: app.attrAppId,
|
|
69
|
+
domainName: config.domainName,
|
|
70
|
+
subDomainSettings: [{ branchName: config.branchName, prefix: "" }],
|
|
71
|
+
});
|
|
72
|
+
domain.addResourceDependency(branch);
|
|
73
|
+
this.exportValue(app.attrAppId, { description: `Amplify app id for ${config.appName}` });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build spec mirrors the repo root amplify.yml so the app deploys the full
|
|
77
|
+
* Papyrus backend + Next.js frontend on every build. Inlined because Amplify
|
|
78
|
+
* needs a buildSpec on the app for the first deploy before the repo is cloned.
|
|
79
|
+
*/
|
|
80
|
+
buildSpec() {
|
|
81
|
+
return [
|
|
82
|
+
"version: 1",
|
|
83
|
+
"backend:",
|
|
84
|
+
" phases:",
|
|
85
|
+
" build:",
|
|
86
|
+
" commands:",
|
|
87
|
+
" - npm install --cache .npm --prefer-offline",
|
|
88
|
+
" - |",
|
|
89
|
+
" if [ -z \"${PAPYRUS_CONSOLE_RESPONDER_IMAGE_URI:-}\" ]; then",
|
|
90
|
+
" export PAPYRUS_CONSOLE_RESPONDER_ALLOW_LOCAL_BUILD=true",
|
|
91
|
+
" fi",
|
|
92
|
+
" - npx ampx pipeline-deploy --debug --branch $AWS_BRANCH --app-id $AWS_APP_ID",
|
|
93
|
+
"frontend:",
|
|
94
|
+
" phases:",
|
|
95
|
+
" build:",
|
|
96
|
+
" commands:",
|
|
97
|
+
" - env | grep -e '^PAPYRUS_CONTENT_SOURCE=' -e '^PAPYRUS_EDITION_SLUG=' -e '^PAPYRUS_REVALIDATE_SECRET=' >> .env.production",
|
|
98
|
+
" - npm run build",
|
|
99
|
+
" artifacts:",
|
|
100
|
+
" baseDirectory: .next",
|
|
101
|
+
" files:",
|
|
102
|
+
" - '**/*'",
|
|
103
|
+
" cache:",
|
|
104
|
+
" paths:",
|
|
105
|
+
" - .next/cache/**/*",
|
|
106
|
+
" - .npm/**/*",
|
|
107
|
+
].join("\n");
|
|
108
|
+
}
|
|
109
|
+
}
|