@anthusai/papyrus 1.15.0 → 1.16.0

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.
@@ -43,11 +43,22 @@ The stack also creates the `<siteId>-papyrus-authoring` role. The CLI signs
43
43
  * AppSync requests (SigV4) with credentials for that role (SSO, OIDC or any
44
44
  * principal in the account that may assume it); no token is stored anywhere.
45
45
  *
46
- * No repository and no access token are configured: the apps are manual-deploy
47
- * until Amplify's GitHub App is connected once in the console
48
- * (docs/site-hosting.md). The Papyrus backend itself is created inside the CMS
46
+ * Unless `repositoryAccessTokenSecretArn` is set, no repository and no access
47
+ * token are configured: the apps are manual-deploy until Amplify's GitHub App is
48
+ * connected once in the console (docs/site-hosting.md). When it is set, both apps
49
+ * are created against `repository` and read the GitHub token from that Secrets
50
+ * Manager secret through a CloudFormation dynamic reference, so the token never
51
+ * appears in site.json or the synthesized template. The Papyrus backend itself is created inside the CMS
49
52
  * app by `ampx pipeline-deploy` from the production build.
50
53
  */
54
+ function repositoryConnectionProperties(config) {
55
+ if (config.repositoryAccessTokenSecretArn === undefined)
56
+ return {};
57
+ return {
58
+ repository: config.repository,
59
+ accessToken: `{{resolve:secretsmanager:${config.repositoryAccessTokenSecretArn}:SecretString}}`,
60
+ };
61
+ }
51
62
  export class AmplifyAppShellStack extends Stack {
52
63
  cmsAppId;
53
64
  readerAppId;
@@ -114,7 +125,9 @@ export class AmplifyAppShellStack extends Stack {
114
125
  resources: PROSPECTUS_PAPYRUS_SITE_ROUTES.map((route) => `${prospectusAccountsApiStageArn}/${route}`),
115
126
  }));
116
127
  }
128
+ const repositoryConnection = repositoryConnectionProperties(config);
117
129
  const cmsApp = new amplify.CfnApp(this, "App", {
130
+ ...repositoryConnection,
118
131
  name: cmsAppName,
119
132
  description: `Papyrus newsroom CMS for ${config.siteId} (WEB_COMPUTE)`,
120
133
  platform: "WEB_COMPUTE",
@@ -332,6 +345,7 @@ export class AmplifyAppShellStack extends Stack {
332
345
  resources: ["arn:aws:s3:::amplify-*/media/*"],
333
346
  }));
334
347
  const readerApp = new amplify.CfnApp(this, "ReaderApp", {
348
+ ...repositoryConnectionProperties(config),
335
349
  name: readerAppName,
336
350
  description: `Papyrus static reader for ${config.siteId} (WEB)`,
337
351
  platform: "WEB",
@@ -46,11 +46,22 @@ The stack also creates the `<siteId>-papyrus-authoring` role. The CLI signs
46
46
  * AppSync requests (SigV4) with credentials for that role (SSO, OIDC or any
47
47
  * principal in the account that may assume it); no token is stored anywhere.
48
48
  *
49
- * No repository and no access token are configured: the apps are manual-deploy
50
- * until Amplify's GitHub App is connected once in the console
51
- * (docs/site-hosting.md). The Papyrus backend itself is created inside the CMS
49
+ * Unless `repositoryAccessTokenSecretArn` is set, no repository and no access
50
+ * token are configured: the apps are manual-deploy until Amplify's GitHub App is
51
+ * connected once in the console (docs/site-hosting.md). When it is set, both apps
52
+ * are created against `repository` and read the GitHub token from that Secrets
53
+ * Manager secret through a CloudFormation dynamic reference, so the token never
54
+ * appears in site.json or the synthesized template. The Papyrus backend itself is created inside the CMS
52
55
  * app by `ampx pipeline-deploy` from the production build.
53
56
  */
57
+ function repositoryConnectionProperties(config: AmplifyAppShellSiteConfig): { repository?: string; accessToken?: string } {
58
+ if (config.repositoryAccessTokenSecretArn === undefined) return {};
59
+ return {
60
+ repository: config.repository,
61
+ accessToken: `{{resolve:secretsmanager:${config.repositoryAccessTokenSecretArn}:SecretString}}`,
62
+ };
63
+ }
64
+
54
65
  export class AmplifyAppShellStack extends Stack {
55
66
  public readonly cmsAppId?: string;
56
67
  public readonly readerAppId?: string;
@@ -130,7 +141,10 @@ export class AmplifyAppShellStack extends Stack {
130
141
  );
131
142
  }
132
143
 
144
+ const repositoryConnection = repositoryConnectionProperties(config);
145
+
133
146
  const cmsApp = new amplify.CfnApp(this, "App", {
147
+ ...repositoryConnection,
134
148
  name: cmsAppName,
135
149
  description: `Papyrus newsroom CMS for ${config.siteId} (WEB_COMPUTE)`,
136
150
  platform: "WEB_COMPUTE",
@@ -389,6 +403,7 @@ export class AmplifyAppShellStack extends Stack {
389
403
  );
390
404
 
391
405
  const readerApp = new amplify.CfnApp(this, "ReaderApp", {
406
+ ...repositoryConnectionProperties(config),
392
407
  name: readerAppName,
393
408
  description: `Papyrus static reader for ${config.siteId} (WEB)`,
394
409
  platform: "WEB",
@@ -5,6 +5,7 @@ export function hasCmsApp(config) {
5
5
  export const DEFAULT_CI_BRANCHES = ["main", "staging"];
6
6
  const SITE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
7
7
  const REPOSITORY_PATTERN = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
8
+ const SECRETS_MANAGER_SECRET_ARN_PATTERN = /^arn:aws:secretsmanager:[a-z0-9-]+:\d{12}:secret:[A-Za-z0-9/_+=.@-]+$/;
8
9
  const HOSTED_ZONE_ID_PATTERN = /^Z[A-Z0-9]+$/;
9
10
  const HOST_NAME_PATTERN = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
10
11
  const DOMAIN_PREFIX_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
@@ -297,9 +298,12 @@ function validateOauthRedirectUrls(config) {
297
298
  }
298
299
  export function parseSiteConfig(raw) {
299
300
  const record = requireObject(raw, "site");
300
- rejectUnknownKeys(record, ["siteId", "repository", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"], "site");
301
+ rejectUnknownKeys(record, ["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"], "site");
301
302
  const siteId = requireString(record.siteId, "siteId", SITE_ID_PATTERN, "kebab-case");
302
303
  const repository = requireString(record.repository, "repository", REPOSITORY_PATTERN, "https://github.com/<org>/<repo>");
304
+ const repositoryAccessTokenSecretArn = record.repositoryAccessTokenSecretArn === undefined
305
+ ? undefined
306
+ : requireString(record.repositoryAccessTokenSecretArn, "repositoryAccessTokenSecretArn", SECRETS_MANAGER_SECRET_ARN_PATTERN, "Secrets Manager secret ARN");
303
307
  const brand = requireString(record.brand, "brand");
304
308
  const frontend = requireString(record.frontend, "frontend");
305
309
  if (frontend !== "pretext" && frontend !== "markus-static") {
@@ -382,6 +386,7 @@ export function parseSiteConfig(raw) {
382
386
  const config = {
383
387
  siteId,
384
388
  repository,
389
+ repositoryAccessTokenSecretArn,
385
390
  brand,
386
391
  frontend,
387
392
  hostedZoneId,
@@ -47,6 +47,7 @@ export type CmsConfig = {
47
47
  export type AmplifyAppShellSiteConfig = {
48
48
  siteId: string;
49
49
  repository: string;
50
+ repositoryAccessTokenSecretArn?: string;
50
51
  brand: string;
51
52
  frontend: "pretext" | "markus-static";
52
53
  hostedZoneId?: string;
@@ -85,6 +86,7 @@ export const DEFAULT_CI_BRANCHES = ["main", "staging"];
85
86
 
86
87
  const SITE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
87
88
  const REPOSITORY_PATTERN = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
89
+ const SECRETS_MANAGER_SECRET_ARN_PATTERN = /^arn:aws:secretsmanager:[a-z0-9-]+:\d{12}:secret:[A-Za-z0-9/_+=.@-]+$/;
88
90
  const HOSTED_ZONE_ID_PATTERN = /^Z[A-Z0-9]+$/;
89
91
  const HOST_NAME_PATTERN = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
90
92
  const DOMAIN_PREFIX_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
@@ -392,12 +394,15 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
392
394
  const record = requireObject(raw, "site");
393
395
  rejectUnknownKeys(
394
396
  record,
395
- ["siteId", "repository", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"],
397
+ ["siteId", "repository", "repositoryAccessTokenSecretArn", "brand", "frontend", "hostedZoneId", "stackName", "cms", "reader", "papyrusVersion", "storagePreviewPrefix", "github"],
396
398
  "site",
397
399
  );
398
400
 
399
401
  const siteId = requireString(record.siteId, "siteId", SITE_ID_PATTERN, "kebab-case");
400
402
  const repository = requireString(record.repository, "repository", REPOSITORY_PATTERN, "https://github.com/<org>/<repo>");
403
+ const repositoryAccessTokenSecretArn = record.repositoryAccessTokenSecretArn === undefined
404
+ ? undefined
405
+ : requireString(record.repositoryAccessTokenSecretArn, "repositoryAccessTokenSecretArn", SECRETS_MANAGER_SECRET_ARN_PATTERN, "Secrets Manager secret ARN");
401
406
  const brand = requireString(record.brand, "brand");
402
407
  const frontend = requireString(record.frontend, "frontend");
403
408
  if (frontend !== "pretext" && frontend !== "markus-static") {
@@ -480,6 +485,7 @@ export function parseSiteConfig(raw: unknown): AmplifyAppShellSiteConfig {
480
485
  const config: AmplifyAppShellSiteConfig = {
481
486
  siteId,
482
487
  repository,
488
+ repositoryAccessTokenSecretArn,
483
489
  brand,
484
490
  frontend,
485
491
  hostedZoneId,
package/package.json CHANGED
@@ -377,7 +377,7 @@
377
377
  "optional": true
378
378
  }
379
379
  },
380
- "version": "1.15.0",
380
+ "version": "1.16.0",
381
381
  "dependencies": {
382
382
  "@aws-amplify/adapter-nextjs": "^1.7.3",
383
383
  "@aws-crypto/sha256-js": "5.2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.15.0",
2
+ "version": "1.16.0",
3
3
  "routes": [
4
4
  {
5
5
  "file": "app/%5F_preview/[[...path]]/route.ts",