@odla-ai/chapter 0.33.2 → 0.35.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.
- package/README.md +83 -2
- package/dist/index.cjs +217 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -6
- package/dist/index.d.ts +80 -6
- package/dist/index.js +219 -3
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +160 -10
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +99 -1
- package/dist/worker/index.d.ts +99 -1
- package/dist/worker/index.js +160 -10
- package/dist/worker/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -432,8 +432,11 @@ The document carries the source, exact chapter/environment/Stripe-mode target,
|
|
|
432
432
|
monotonic revision, ordered and available tiers, immutable Stripe Price ids,
|
|
433
433
|
the membership-page renderer document, and explicitly delegated application,
|
|
434
434
|
policy, scheduling, and theme sections. Theme tokens include typography as
|
|
435
|
-
|
|
436
|
-
`--ui-font-
|
|
435
|
+
allowlisted `--ui-*` CSS custom properties such as `--ui-font-display` and
|
|
436
|
+
`--ui-font-sans`. Chapter rejects non-`--ui-*` names, non-finite values,
|
|
437
|
+
oversized token sets, control characters, style-tag breakout, declaration
|
|
438
|
+
breakout, comments, imports, expressions, and URL-bearing values. Application
|
|
439
|
+
aliases such as `--teal` or `--deepocean` remain host-owned CSS.
|
|
437
440
|
|
|
438
441
|
The packaged follower receiver validates the signed request timestamp, sender,
|
|
439
442
|
target, environment, Stripe mode, schema, and SHA-256 digest. It rejects a stale
|
|
@@ -455,6 +458,84 @@ count before asking odla-db to order revision history. A freshly provisioned
|
|
|
455
458
|
follower therefore acknowledges revision 1 immediately instead of depending on
|
|
456
459
|
an ordered query over an empty result set.
|
|
457
460
|
|
|
461
|
+
#### Render active tokens before first paint
|
|
462
|
+
|
|
463
|
+
Put Chapter's marker after the local bootstrap theme and before any application
|
|
464
|
+
boot script in the HTML head:
|
|
465
|
+
|
|
466
|
+
```html
|
|
467
|
+
<link rel="stylesheet" href="/bootstrap-theme.css">
|
|
468
|
+
<!--odla:signup-control-theme-->
|
|
469
|
+
<script type="module" src="/app.js"></script>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
A host route can transform the static asset with the follower-local active row:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
import { chapterWorker, withActiveSignupControlTheme } from "@odla-ai/chapter/worker";
|
|
476
|
+
import { chapter } from "./chapter.config.mjs";
|
|
477
|
+
|
|
478
|
+
export default chapterWorker({
|
|
479
|
+
chapter,
|
|
480
|
+
routes: [async (request, url, env, context) => {
|
|
481
|
+
if (request.method !== "GET" || url.pathname !== "/apply") return null;
|
|
482
|
+
const asset = await env.ASSETS.fetch(new URL("/index.html", url));
|
|
483
|
+
return withActiveSignupControlTheme(asset, {
|
|
484
|
+
db: context.makeDb(env),
|
|
485
|
+
chapterId: chapter.id,
|
|
486
|
+
environment: env.ODLA_ENV,
|
|
487
|
+
// The site's existing deterministic fallback when no revision is active.
|
|
488
|
+
bootstrapTokens: { "--ui-accent": "#096ea8" },
|
|
489
|
+
});
|
|
490
|
+
}],
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
`withActiveSignupControlTheme` reads only the follower database, revalidates the
|
|
495
|
+
stored document and its canonical digest, and injects one bounded style block
|
|
496
|
+
with matching revision/digest metadata. It never calls the controlling hub. A
|
|
497
|
+
missing active row emits the documented `bootstrap` state; a failed local read
|
|
498
|
+
or corrupt active row fails closed instead of looking like absence. The response
|
|
499
|
+
defaults to `Cache-Control: no-store`, carries digest/revision headers, and gets
|
|
500
|
+
an ETag over the complete transformed HTML. This prevents a pre-activation page
|
|
501
|
+
from surviving activation. A host-owned cache may instead call
|
|
502
|
+
`renderActiveSignupControlHtml()` and include
|
|
503
|
+
`result.theme.metadata.cacheKey` in its complete page cache key.
|
|
504
|
+
|
|
505
|
+
For an SSR or static-HTML transform, use the same helper on the generated string:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
import { renderActiveSignupControlHtml } from "@odla-ai/chapter/worker";
|
|
509
|
+
|
|
510
|
+
const result = await renderActiveSignupControlHtml(generatedHtml, {
|
|
511
|
+
db, chapterId: chapter.id, environment,
|
|
512
|
+
bootstrapTokens: { "--ui-accent": "#096ea8" },
|
|
513
|
+
});
|
|
514
|
+
await writeHtml(result.html); // cache with result.theme.metadata.cacheKey
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
After `GET /api/join-config`, hydrate the existing style element rather than
|
|
518
|
+
writing a second theme system. Matching revision/digest metadata produces no DOM
|
|
519
|
+
write and therefore no visual token change:
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import {
|
|
523
|
+
SIGNUP_CONTROL_THEME_STYLE_ID,
|
|
524
|
+
hydrateSignupControlTheme,
|
|
525
|
+
} from "@odla-ai/chapter";
|
|
526
|
+
|
|
527
|
+
const style = document.getElementById(SIGNUP_CONTROL_THEME_STYLE_ID);
|
|
528
|
+
if (style instanceof HTMLStyleElement) {
|
|
529
|
+
hydrateSignupControlTheme(style, joinConfig.signupControl ?? null, {
|
|
530
|
+
bootstrapTokens: { "--ui-accent": "#096ea8" },
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
The host still decides which routes use managed theming and owns any legacy
|
|
536
|
+
token aliases. Keep the marker after bootstrap declarations so the active
|
|
537
|
+
`--ui-*` values win in the initial parsed document.
|
|
538
|
+
|
|
458
539
|
The hub owns a durable outbox: create/verify immutable Stripe Prices first,
|
|
459
540
|
persist the desired revision, retry signed delivery, and mark it active only
|
|
460
541
|
after the exact revision/digest receipt. Version history is for audit and
|
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CHAPTER_PROMOTION_ADAPTER: () => CHAPTER_PROMOTION_ADAPTER,
|
|
23
24
|
DEFAULT_CHAPTER_COPY: () => DEFAULT_CHAPTER_COPY,
|
|
24
25
|
DEFAULT_SHARE_FIELDS: () => DEFAULT_SHARE_FIELDS,
|
|
25
26
|
MEMBERSHIP_PAGE_RENDERER: () => MEMBERSHIP_PAGE_RENDERER,
|
|
26
27
|
SCHEDULING_DEFAULTS: () => SCHEDULING_DEFAULTS,
|
|
27
28
|
SIGNUP_CONTROL_PATH: () => SIGNUP_CONTROL_PATH,
|
|
28
29
|
SIGNUP_CONTROL_SCHEMA_VERSION: () => SIGNUP_CONTROL_SCHEMA_VERSION,
|
|
30
|
+
SIGNUP_CONTROL_THEME_MARKER: () => SIGNUP_CONTROL_THEME_MARKER,
|
|
31
|
+
SIGNUP_CONTROL_THEME_STYLE_ID: () => SIGNUP_CONTROL_THEME_STYLE_ID,
|
|
29
32
|
applicantProfile: () => applicantProfile,
|
|
30
33
|
applicationBookingUpdate: () => applicationBookingUpdate,
|
|
31
34
|
applicationSummary: () => applicationSummary,
|
|
@@ -61,6 +64,7 @@ __export(index_exports, {
|
|
|
61
64
|
defineChapter: () => defineChapter,
|
|
62
65
|
emailGroupFrom: () => emailGroupFrom,
|
|
63
66
|
endForSlot: () => endForSlot,
|
|
67
|
+
exportChapterPromotion: () => exportChapterPromotion,
|
|
64
68
|
findApplicationRef: () => findApplicationRef,
|
|
65
69
|
findTier: () => findTier,
|
|
66
70
|
firstPaymentPatch: () => firstPaymentPatch,
|
|
@@ -68,6 +72,8 @@ __export(index_exports, {
|
|
|
68
72
|
formationFields: () => formationFields,
|
|
69
73
|
getVaultSecret: () => getVaultSecret,
|
|
70
74
|
hasDisclaimerAck: () => hasDisclaimerAck,
|
|
75
|
+
hydrateSignupControlTheme: () => hydrateSignupControlTheme,
|
|
76
|
+
injectSignupControlThemeHead: () => injectSignupControlThemeHead,
|
|
71
77
|
introIdempotencyKey: () => introIdempotencyKey,
|
|
72
78
|
isAdminRole: () => isAdminRole,
|
|
73
79
|
isAlreadySent: () => isAlreadySent,
|
|
@@ -97,6 +103,7 @@ __export(index_exports, {
|
|
|
97
103
|
reconcileMeetings: () => reconcileMeetings,
|
|
98
104
|
refundedPatch: () => refundedPatch,
|
|
99
105
|
render: () => render,
|
|
106
|
+
renderSignupControlThemeHead: () => renderSignupControlThemeHead,
|
|
100
107
|
renderSummary: () => renderSummary,
|
|
101
108
|
renderTemplateBody: () => renderTemplateBody,
|
|
102
109
|
renewalPatch: () => renewalPatch,
|
|
@@ -3024,12 +3031,18 @@ function themeOf(value, errors) {
|
|
|
3024
3031
|
}
|
|
3025
3032
|
unknownKeys(input, /* @__PURE__ */ new Set(["tokens"]), "document.theme", errors);
|
|
3026
3033
|
if (Object.keys(tokens).length > 256) errors.push("document.theme.tokens supports at most 256 entries");
|
|
3034
|
+
let serializedLength = 0;
|
|
3027
3035
|
for (const [key, token] of Object.entries(tokens)) {
|
|
3028
|
-
if (!/^--[a-z0-9-]{
|
|
3029
|
-
|
|
3030
|
-
|
|
3036
|
+
if (!/^--ui-[a-z0-9][a-z0-9-]{0,94}$/.test(key)) {
|
|
3037
|
+
errors.push(`document.theme.tokens.${key} must be an allowlisted --ui-* custom property`);
|
|
3038
|
+
}
|
|
3039
|
+
const rendered = String(token).trim();
|
|
3040
|
+
serializedLength += key.length + rendered.length + 4;
|
|
3041
|
+
if (typeof token !== "string" && typeof token !== "number" || typeof token === "number" && !Number.isFinite(token) || !rendered || rendered.length > 1e3 || /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i.test(rendered)) {
|
|
3042
|
+
errors.push(`document.theme.tokens.${key} must be a bounded breakout-safe CSS value`);
|
|
3031
3043
|
}
|
|
3032
3044
|
}
|
|
3045
|
+
if (serializedLength > 65536) errors.push("document.theme.tokens exceeds the serialized size bound");
|
|
3033
3046
|
return { tokens };
|
|
3034
3047
|
}
|
|
3035
3048
|
function validateSignupControlDocument(input) {
|
|
@@ -3081,6 +3094,116 @@ function validateSignupControlDocument(input) {
|
|
|
3081
3094
|
} };
|
|
3082
3095
|
}
|
|
3083
3096
|
|
|
3097
|
+
// src/signup-control-theme.ts
|
|
3098
|
+
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
3099
|
+
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
3100
|
+
var TOKEN_NAME = /^--ui-[a-z0-9][a-z0-9-]{0,94}$/;
|
|
3101
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
3102
|
+
var UNSAFE_VALUE = /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i;
|
|
3103
|
+
var MAX_TOKENS = 256;
|
|
3104
|
+
var MAX_VALUE_LENGTH = 1e3;
|
|
3105
|
+
var MAX_SERIALIZED_LENGTH = 65536;
|
|
3106
|
+
function validatedTokens(input) {
|
|
3107
|
+
const entries = Object.entries(input ?? {});
|
|
3108
|
+
if (entries.length > MAX_TOKENS) {
|
|
3109
|
+
throw new TypeError(`signup-control theme supports at most ${MAX_TOKENS} tokens`);
|
|
3110
|
+
}
|
|
3111
|
+
const tokens = {};
|
|
3112
|
+
let serializedLength = 0;
|
|
3113
|
+
for (const [name, raw] of entries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
3114
|
+
if (!TOKEN_NAME.test(name)) {
|
|
3115
|
+
throw new TypeError(`signup-control theme token "${name}" must be an allowlisted --ui-* property`);
|
|
3116
|
+
}
|
|
3117
|
+
if (typeof raw !== "string" && typeof raw !== "number" || typeof raw === "number" && !Number.isFinite(raw)) {
|
|
3118
|
+
throw new TypeError(`signup-control theme token "${name}" must be a finite string or number`);
|
|
3119
|
+
}
|
|
3120
|
+
const value = String(raw).trim();
|
|
3121
|
+
if (!value || value.length > MAX_VALUE_LENGTH || UNSAFE_VALUE.test(value)) {
|
|
3122
|
+
throw new TypeError(`signup-control theme token "${name}" has an unsafe or oversized value`);
|
|
3123
|
+
}
|
|
3124
|
+
serializedLength += name.length + value.length + 4;
|
|
3125
|
+
if (serializedLength > MAX_SERIALIZED_LENGTH) {
|
|
3126
|
+
throw new TypeError("signup-control theme exceeds the serialized size bound");
|
|
3127
|
+
}
|
|
3128
|
+
tokens[name] = value;
|
|
3129
|
+
}
|
|
3130
|
+
return tokens;
|
|
3131
|
+
}
|
|
3132
|
+
function cssFor(tokens) {
|
|
3133
|
+
const declarations = Object.entries(tokens).map(([name, value]) => ` ${name}: ${value};`);
|
|
3134
|
+
return declarations.length ? `:root {
|
|
3135
|
+
${declarations.join("\n")}
|
|
3136
|
+
}` : "";
|
|
3137
|
+
}
|
|
3138
|
+
function stateFor(revision, options) {
|
|
3139
|
+
if (!revision) {
|
|
3140
|
+
return {
|
|
3141
|
+
css: cssFor(validatedTokens(options.bootstrapTokens)),
|
|
3142
|
+
metadata: { source: "bootstrap", revision: null, digest: null, cacheKey: "signup-control:bootstrap" }
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !DIGEST.test(revision.digest)) {
|
|
3146
|
+
throw new TypeError("signup-control theme revision metadata is invalid");
|
|
3147
|
+
}
|
|
3148
|
+
return {
|
|
3149
|
+
css: cssFor(validatedTokens(revision.theme?.tokens)),
|
|
3150
|
+
metadata: {
|
|
3151
|
+
source: "active",
|
|
3152
|
+
revision: revision.revision,
|
|
3153
|
+
digest: revision.digest,
|
|
3154
|
+
cacheKey: `signup-control:${revision.digest}`
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
function attributes(metadata) {
|
|
3159
|
+
return [
|
|
3160
|
+
`id="${SIGNUP_CONTROL_THEME_STYLE_ID}"`,
|
|
3161
|
+
`data-signup-control-source="${metadata.source}"`,
|
|
3162
|
+
`data-signup-control-cache-key="${metadata.cacheKey}"`,
|
|
3163
|
+
...metadata.revision === null ? [] : [`data-signup-control-revision="${metadata.revision}"`],
|
|
3164
|
+
...metadata.digest === null ? [] : [`data-signup-control-digest="${metadata.digest}"`]
|
|
3165
|
+
].join(" ");
|
|
3166
|
+
}
|
|
3167
|
+
function renderSignupControlThemeHead(revision, options = {}) {
|
|
3168
|
+
const { css, metadata } = stateFor(revision, options);
|
|
3169
|
+
return { css, metadata, head: `<style ${attributes(metadata)}>${css}</style>` };
|
|
3170
|
+
}
|
|
3171
|
+
function injectSignupControlThemeHead(html, rendered, marker = SIGNUP_CONTROL_THEME_MARKER) {
|
|
3172
|
+
const existing = /<style\b(?=[^>]*\bid\s*=\s*["']odla-signup-control-theme["'])[^>]*>[\s\S]*?<\/style>/gi;
|
|
3173
|
+
const existingBlocks = html.match(existing) ?? [];
|
|
3174
|
+
if (existingBlocks.length > 1) {
|
|
3175
|
+
throw new TypeError("signup-control theme style block must appear at most once");
|
|
3176
|
+
}
|
|
3177
|
+
if (existingBlocks.length === 1) return html.replace(existing, rendered.head);
|
|
3178
|
+
const markerIndex = html.indexOf(marker);
|
|
3179
|
+
if (markerIndex >= 0) {
|
|
3180
|
+
if (html.indexOf(marker, markerIndex + marker.length) >= 0) {
|
|
3181
|
+
throw new TypeError("signup-control theme marker must appear exactly once");
|
|
3182
|
+
}
|
|
3183
|
+
return `${html.slice(0, markerIndex)}${rendered.head}${html.slice(markerIndex + marker.length)}`;
|
|
3184
|
+
}
|
|
3185
|
+
const closingHead = html.search(/<\/head\s*>/i);
|
|
3186
|
+
if (closingHead < 0) throw new TypeError("signup-control theme requires an HTML head");
|
|
3187
|
+
return `${html.slice(0, closingHead)}${rendered.head}${html.slice(closingHead)}`;
|
|
3188
|
+
}
|
|
3189
|
+
function hydrateSignupControlTheme(target, revision, options = {}) {
|
|
3190
|
+
const next = stateFor(revision, options);
|
|
3191
|
+
const currentSource = target.getAttribute("data-signup-control-source");
|
|
3192
|
+
const currentRevision = target.getAttribute("data-signup-control-revision");
|
|
3193
|
+
const currentDigest = target.getAttribute("data-signup-control-digest");
|
|
3194
|
+
if (currentSource === next.metadata.source && currentRevision === (next.metadata.revision === null ? null : String(next.metadata.revision)) && currentDigest === next.metadata.digest) {
|
|
3195
|
+
return false;
|
|
3196
|
+
}
|
|
3197
|
+
target.textContent = next.css;
|
|
3198
|
+
target.setAttribute("data-signup-control-source", next.metadata.source);
|
|
3199
|
+
target.setAttribute("data-signup-control-cache-key", next.metadata.cacheKey);
|
|
3200
|
+
if (next.metadata.revision === null) target.removeAttribute("data-signup-control-revision");
|
|
3201
|
+
else target.setAttribute("data-signup-control-revision", String(next.metadata.revision));
|
|
3202
|
+
if (next.metadata.digest === null) target.removeAttribute("data-signup-control-digest");
|
|
3203
|
+
else target.setAttribute("data-signup-control-digest", next.metadata.digest);
|
|
3204
|
+
return true;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3084
3207
|
// src/membership-markdown.ts
|
|
3085
3208
|
var MAX_BENEFITS2 = 24;
|
|
3086
3209
|
var MAX_MARKDOWN2 = 65536;
|
|
@@ -3200,4 +3323,95 @@ function membershipTierPresentationFromMarkdown(presentation, source) {
|
|
|
3200
3323
|
if (!result.ok) throw new TypeError(`Invalid membership Markdown: ${result.errors.join("; ")}`);
|
|
3201
3324
|
return result.value;
|
|
3202
3325
|
}
|
|
3326
|
+
|
|
3327
|
+
// src/promotion.ts
|
|
3328
|
+
var import_promotion = require("@odla-ai/promotion");
|
|
3329
|
+
var CHAPTER_PROMOTION_ADAPTER = "@odla-ai/chapter/v1";
|
|
3330
|
+
var exclusions = (chapterId, paidTierIds) => [
|
|
3331
|
+
{
|
|
3332
|
+
kind: "chapter.providers",
|
|
3333
|
+
id: chapterId,
|
|
3334
|
+
reason: "provider_identity",
|
|
3335
|
+
paths: [
|
|
3336
|
+
...paidTierIds.map((id2) => `tiers[${id2}].stripePriceId`),
|
|
3337
|
+
"groups.stripePublishableKey",
|
|
3338
|
+
"applications.stripeCustomerId",
|
|
3339
|
+
"applications.stripeSubscriptionId",
|
|
3340
|
+
"meetings.googleEventId"
|
|
3341
|
+
],
|
|
3342
|
+
detail: "Stripe, Clerk, and Google identities are resolved independently in each target environment."
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
kind: "chapter.runtime",
|
|
3346
|
+
id: chapterId,
|
|
3347
|
+
reason: "runtime_state",
|
|
3348
|
+
paths: ["applications.*", "meetings.*", "emailLog.*", "crm_*.*"],
|
|
3349
|
+
detail: "Applicants, meetings, messages, and CRM rows are operational state, not managed signup content."
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
kind: "chapter.access",
|
|
3353
|
+
id: chapterId,
|
|
3354
|
+
reason: "personal_data",
|
|
3355
|
+
paths: ["admins.*", "superAdmins.*", "networkNotes.*"],
|
|
3356
|
+
detail: "People, access lists, and private network notes remain local to the target environment."
|
|
3357
|
+
},
|
|
3358
|
+
{
|
|
3359
|
+
kind: "chapter.history",
|
|
3360
|
+
id: chapterId,
|
|
3361
|
+
reason: "authority_history",
|
|
3362
|
+
paths: ["signupControls[active=false]", "signupControls.*At", "signupControls.environment"],
|
|
3363
|
+
detail: "The source retains immutable delivery history; only selected published content is exported."
|
|
3364
|
+
}
|
|
3365
|
+
];
|
|
3366
|
+
async function exportChapterPromotion(input) {
|
|
3367
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(input.digest)) throw new TypeError("chapter promotion digest is invalid");
|
|
3368
|
+
const checked = validateSignupControlDocument(input.document);
|
|
3369
|
+
if (!checked.ok) throw new TypeError(checked.errors.join("; "));
|
|
3370
|
+
const document = checked.value;
|
|
3371
|
+
if (await signupControlDigest(document) !== input.digest) throw new TypeError("chapter promotion digest does not match its document");
|
|
3372
|
+
const revision = `chapter:${document.target.chapterId}:${document.revision}`;
|
|
3373
|
+
const promotionProvenance = {
|
|
3374
|
+
adapter: CHAPTER_PROMOTION_ADAPTER,
|
|
3375
|
+
sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
|
|
3376
|
+
receiptDigests: [input.digest]
|
|
3377
|
+
};
|
|
3378
|
+
const artifact = (kind, id2, content) => ({
|
|
3379
|
+
kind,
|
|
3380
|
+
id: id2,
|
|
3381
|
+
sourceRevision: revision,
|
|
3382
|
+
provenance: promotionProvenance,
|
|
3383
|
+
content: (0, import_promotion.canonicalPromotionValue)(content)
|
|
3384
|
+
});
|
|
3385
|
+
const chapterId = document.target.chapterId;
|
|
3386
|
+
const artifacts = [
|
|
3387
|
+
artifact("chapter.catalog", chapterId, {
|
|
3388
|
+
chapterId,
|
|
3389
|
+
tiers: document.tiers.map(({ stripePriceId: _providerId, ...tier }) => tier)
|
|
3390
|
+
}),
|
|
3391
|
+
artifact("chapter.membership-page", chapterId, document.membershipPage),
|
|
3392
|
+
...document.application ? [artifact("chapter.application", chapterId, document.application)] : [],
|
|
3393
|
+
...document.policy ? [artifact("chapter.policy", chapterId, document.policy)] : [],
|
|
3394
|
+
...document.scheduling ? [artifact("chapter.scheduling", chapterId, document.scheduling)] : [],
|
|
3395
|
+
...document.theme ? [artifact("chapter.theme", chapterId, document.theme)] : []
|
|
3396
|
+
];
|
|
3397
|
+
return {
|
|
3398
|
+
adapter: CHAPTER_PROMOTION_ADAPTER,
|
|
3399
|
+
artifacts,
|
|
3400
|
+
exclusions: exclusions(chapterId, document.tiers.filter((tier) => tier.stripePriceId).map((tier) => tier.id)),
|
|
3401
|
+
preconditions: [
|
|
3402
|
+
{
|
|
3403
|
+
code: "chapter.signup-control.schema",
|
|
3404
|
+
path: "services.chapter.signupControl.schemaVersion",
|
|
3405
|
+
expected: document.schemaVersion,
|
|
3406
|
+
detail: "The target must support this Chapter signup-control document version."
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
code: "chapter.identity",
|
|
3410
|
+
path: "app.appId",
|
|
3411
|
+
expected: chapterId,
|
|
3412
|
+
detail: "Chapter content can only target the chapter named by the signed source revision."
|
|
3413
|
+
}
|
|
3414
|
+
]
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3203
3417
|
//# sourceMappingURL=index.cjs.map
|