@se-studio/contentful-cms 3.15.6 → 3.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.
- package/CHANGELOG.md +18 -0
- package/HOSTED.md +18 -0
- package/dist/commands/host-pack-publish.d.ts +23 -0
- package/dist/commands/host-pack-publish.d.ts.map +1 -0
- package/dist/commands/host-pack-publish.js +84 -0
- package/dist/commands/host-pack-publish.js.map +1 -0
- package/dist/commands/host.d.ts.map +1 -1
- package/dist/commands/host.js +2 -0
- package/dist/commands/host.js.map +1 -1
- package/dist/config/hosted.d.ts +14 -0
- package/dist/config/hosted.d.ts.map +1 -1
- package/dist/config/hosted.js +54 -0
- package/dist/config/hosted.js.map +1 -1
- package/dist/contentful/cma-user-agent.d.ts +1 -1
- package/dist/editor-pack/generate.d.ts +2 -0
- package/dist/editor-pack/generate.d.ts.map +1 -1
- package/dist/editor-pack/generate.js +1 -0
- package/dist/editor-pack/generate.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/hosted/index.d.ts +4 -2
- package/dist/hosted/index.d.ts.map +1 -1
- package/dist/hosted/index.js +4 -2
- package/dist/hosted/index.js.map +1 -1
- package/dist/hosted/next/context.d.ts +6 -1
- package/dist/hosted/next/context.d.ts.map +1 -1
- package/dist/hosted/next/context.js +18 -2
- package/dist/hosted/next/context.js.map +1 -1
- package/dist/hosted/next/editor-pack-path.d.ts +4 -0
- package/dist/hosted/next/editor-pack-path.d.ts.map +1 -1
- package/dist/hosted/next/editor-pack-path.js +16 -0
- package/dist/hosted/next/editor-pack-path.js.map +1 -1
- package/dist/hosted/next/routes/customer-index.d.ts.map +1 -1
- package/dist/hosted/next/routes/customer-index.js +20 -3
- package/dist/hosted/next/routes/customer-index.js.map +1 -1
- package/dist/hosted/next/routes/customer-resource.d.ts +1 -1
- package/dist/hosted/next/routes/customer-resource.d.ts.map +1 -1
- package/dist/hosted/next/routes/customer-resource.js +13 -3
- package/dist/hosted/next/routes/customer-resource.js.map +1 -1
- package/dist/hosted/next/routes/health.d.ts.map +1 -1
- package/dist/hosted/next/routes/health.js +77 -15
- package/dist/hosted/next/routes/health.js.map +1 -1
- package/dist/hosted/next/routes/link-contentful.js +1 -1
- package/dist/hosted/next/routes/link-contentful.js.map +1 -1
- package/dist/hosted/next/routes/mcp.d.ts +6 -1
- package/dist/hosted/next/routes/mcp.d.ts.map +1 -1
- package/dist/hosted/next/routes/mcp.js +50 -22
- package/dist/hosted/next/routes/mcp.js.map +1 -1
- package/dist/hosted/next/routes/oauth-authorization-server.d.ts +1 -1
- package/dist/hosted/next/routes/oauth-authorization-server.d.ts.map +1 -1
- package/dist/hosted/next/routes/oauth-authorization-server.js +2 -2
- package/dist/hosted/next/routes/oauth-authorization-server.js.map +1 -1
- package/dist/hosted/next/routes/oauth-authorize.js +1 -1
- package/dist/hosted/next/routes/oauth-authorize.js.map +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource-path.d.ts +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource-path.d.ts.map +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource-path.js +2 -2
- package/dist/hosted/next/routes/oauth-protected-resource-path.js.map +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource.d.ts +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource.d.ts.map +1 -1
- package/dist/hosted/next/routes/oauth-protected-resource.js +2 -2
- package/dist/hosted/next/routes/oauth-protected-resource.js.map +1 -1
- package/dist/hosted/next/routes/oauth-register.js +1 -1
- package/dist/hosted/next/routes/oauth-register.js.map +1 -1
- package/dist/hosted/next/routes/oauth-token.js +1 -1
- package/dist/hosted/next/routes/oauth-token.js.map +1 -1
- package/dist/hosted/oauth/auth.d.ts +1 -1
- package/dist/hosted/oauth/auth.d.ts.map +1 -1
- package/dist/hosted/oauth/auth.js +3 -3
- package/dist/hosted/oauth/auth.js.map +1 -1
- package/dist/hosted/oauth/config.d.ts +14 -4
- package/dist/hosted/oauth/config.d.ts.map +1 -1
- package/dist/hosted/oauth/config.js +44 -8
- package/dist/hosted/oauth/config.js.map +1 -1
- package/dist/hosted/oauth/handlers.d.ts +5 -3
- package/dist/hosted/oauth/handlers.d.ts.map +1 -1
- package/dist/hosted/oauth/handlers.js +15 -10
- package/dist/hosted/oauth/handlers.js.map +1 -1
- package/dist/hosted/pack-fetch.d.ts +29 -0
- package/dist/hosted/pack-fetch.d.ts.map +1 -0
- package/dist/hosted/pack-fetch.js +180 -0
- package/dist/hosted/pack-fetch.js.map +1 -0
- package/dist/hosted/tenant-registry.d.ts +68 -0
- package/dist/hosted/tenant-registry.d.ts.map +1 -0
- package/dist/hosted/tenant-registry.js +167 -0
- package/dist/hosted/tenant-registry.js.map +1 -0
- package/docs/editor-tasks/publish-handoff.md +10 -0
- package/package.json +1 -1
- package/skills/core/SKILL.md +11 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Opt-in Option C multi-tenant Host routing (shared cms-edit host). */
|
|
3
|
+
export declare function isMultiTenantHostedMode(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Non-secret tenant map for Option C shared cms-edit hosts.
|
|
6
|
+
* Secrets stay in Vercel env under `${secretEnvPrefix}_*`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const TenantRegistryEntrySchema: z.ZodObject<{
|
|
9
|
+
hostname: z.ZodString;
|
|
10
|
+
projectKey: z.ZodString;
|
|
11
|
+
spaceId: z.ZodString;
|
|
12
|
+
environment: z.ZodDefault<z.ZodString>;
|
|
13
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
14
|
+
mediaTenant: z.ZodOptional<z.ZodString>;
|
|
15
|
+
publicOrigin: z.ZodString;
|
|
16
|
+
packRepo: z.ZodString;
|
|
17
|
+
packTag: z.ZodString;
|
|
18
|
+
packAssetName: z.ZodOptional<z.ZodString>;
|
|
19
|
+
secretEnvPrefix: z.ZodString;
|
|
20
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
21
|
+
coreChannel: z.ZodOptional<z.ZodString>;
|
|
22
|
+
devBaseUrl: z.ZodOptional<z.ZodString>;
|
|
23
|
+
productionSiteUrl: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type TenantRegistryEntry = z.infer<typeof TenantRegistryEntrySchema>;
|
|
26
|
+
export declare const TenantRegistrySchema: z.ZodObject<{
|
|
27
|
+
version: z.ZodDefault<z.ZodLiteral<1>>;
|
|
28
|
+
tenants: z.ZodArray<z.ZodObject<{
|
|
29
|
+
hostname: z.ZodString;
|
|
30
|
+
projectKey: z.ZodString;
|
|
31
|
+
spaceId: z.ZodString;
|
|
32
|
+
environment: z.ZodDefault<z.ZodString>;
|
|
33
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
34
|
+
mediaTenant: z.ZodOptional<z.ZodString>;
|
|
35
|
+
publicOrigin: z.ZodString;
|
|
36
|
+
packRepo: z.ZodString;
|
|
37
|
+
packTag: z.ZodString;
|
|
38
|
+
packAssetName: z.ZodOptional<z.ZodString>;
|
|
39
|
+
secretEnvPrefix: z.ZodString;
|
|
40
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
41
|
+
coreChannel: z.ZodOptional<z.ZodString>;
|
|
42
|
+
devBaseUrl: z.ZodOptional<z.ZodString>;
|
|
43
|
+
productionSiteUrl: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
export type TenantRegistry = z.infer<typeof TenantRegistrySchema>;
|
|
47
|
+
export declare class TenantRegistryError extends Error {
|
|
48
|
+
constructor(message: string);
|
|
49
|
+
}
|
|
50
|
+
export declare function parseTenantRegistry(raw: unknown): TenantRegistry;
|
|
51
|
+
export declare function normalizeHostname(hostname: string): string;
|
|
52
|
+
/** Strip port from Host header values. */
|
|
53
|
+
export declare function hostnameFromHostHeader(hostHeader: string | null): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Staging override: `x-cms-edit-host` header, then `?host=`, then `Host`.
|
|
56
|
+
* Never trust override unless multi-tenant mode is on (caller enforces).
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveRequestHostname(request: Request): string | null;
|
|
59
|
+
export declare function findTenantByHostname(registry: TenantRegistry, hostname: string): TenantRegistryEntry | undefined;
|
|
60
|
+
/** Clear process-level registry cache (tests). */
|
|
61
|
+
export declare function clearTenantRegistryCache(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Load registry from `CMS_EDIT_TENANT_REGISTRY_JSON` or `CMS_EDIT_TENANT_REGISTRY_PATH`.
|
|
64
|
+
* Returns null when neither is set (single-tenant hosts).
|
|
65
|
+
*/
|
|
66
|
+
export declare function loadTenantRegistryFromEnv(): TenantRegistry | null;
|
|
67
|
+
export declare function readTenantSecret(prefix: string, suffix: string): string | undefined;
|
|
68
|
+
//# sourceMappingURL=tenant-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-registry.d.ts","sourceRoot":"","sources":["../../src/hosted/tenant-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wEAAwE;AACxE,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;iBAmCpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;iBAG/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAuBhE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,0CAA0C;AAC1C,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAU/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAetE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,MAAM,GACf,mBAAmB,GAAG,SAAS,CAGjC;AAID,kDAAkD;AAClD,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,cAAc,GAAG,IAAI,CA+BjE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAInF"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/** Opt-in Option C multi-tenant Host routing (shared cms-edit host). */
|
|
4
|
+
export function isMultiTenantHostedMode() {
|
|
5
|
+
const flag = process.env['CMS_EDIT_MULTI_TENANT']?.trim().toLowerCase();
|
|
6
|
+
return flag === '1' || flag === 'true' || flag === 'yes';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Non-secret tenant map for Option C shared cms-edit hosts.
|
|
10
|
+
* Secrets stay in Vercel env under `${secretEnvPrefix}_*`.
|
|
11
|
+
*/
|
|
12
|
+
export const TenantRegistryEntrySchema = z.object({
|
|
13
|
+
/** Request Host (lowercase), e.g. `eagle.content.se.studio`. */
|
|
14
|
+
hostname: z.string().min(1),
|
|
15
|
+
projectKey: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.regex(/^[a-z0-9-]+$/, 'projectKey must be lowercase alphanumeric with hyphens'),
|
|
19
|
+
spaceId: z.string().min(1),
|
|
20
|
+
environment: z.string().min(1).default('master'),
|
|
21
|
+
defaultLocale: z.string().min(1).optional(),
|
|
22
|
+
mediaTenant: z.string().min(1).optional(),
|
|
23
|
+
/** Canonical public origin for OAuth / MCP resource URLs (no trailing slash). */
|
|
24
|
+
publicOrigin: z.string().url(),
|
|
25
|
+
/** GitHub `owner/repo` that publishes editor-pack Release artefacts. */
|
|
26
|
+
packRepo: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1)
|
|
29
|
+
.regex(/^[^/]+\/[^/]+$/, 'packRepo must be owner/repo'),
|
|
30
|
+
/** Release tag, e.g. `cms-edit-pack-eagle-v1.0.0`. */
|
|
31
|
+
packTag: z.string().min(1),
|
|
32
|
+
/** Asset filename on the Release (default: `editor-pack.tar.gz`). */
|
|
33
|
+
packAssetName: z.string().min(1).optional(),
|
|
34
|
+
/**
|
|
35
|
+
* Env prefix for tenant secrets, e.g. `CMS_EDIT_TENANT_EAGLE`.
|
|
36
|
+
* Reads `${prefix}_CONTENTFUL_PREVIEW_ACCESS_TOKEN`, etc.
|
|
37
|
+
*/
|
|
38
|
+
secretEnvPrefix: z
|
|
39
|
+
.string()
|
|
40
|
+
.min(1)
|
|
41
|
+
.regex(/^[A-Z][A-Z0-9_]*$/, 'secretEnvPrefix must be UPPER_SNAKE'),
|
|
42
|
+
displayName: z.string().min(1).optional(),
|
|
43
|
+
/** Optional canary pin for shared-host channels. */
|
|
44
|
+
coreChannel: z.string().min(1).optional(),
|
|
45
|
+
devBaseUrl: z.string().url().optional(),
|
|
46
|
+
productionSiteUrl: z.string().url().optional(),
|
|
47
|
+
});
|
|
48
|
+
export const TenantRegistrySchema = z.object({
|
|
49
|
+
version: z.literal(1).default(1),
|
|
50
|
+
tenants: z.array(TenantRegistryEntrySchema).min(1),
|
|
51
|
+
});
|
|
52
|
+
export class TenantRegistryError extends Error {
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'TenantRegistryError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function parseTenantRegistry(raw) {
|
|
59
|
+
const parsed = TenantRegistrySchema.safeParse(raw);
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
throw new TenantRegistryError(`Invalid tenant registry: ${parsed.error.issues.map((i) => i.message).join('; ')}`);
|
|
62
|
+
}
|
|
63
|
+
const hostnames = new Set();
|
|
64
|
+
for (const tenant of parsed.data.tenants) {
|
|
65
|
+
const host = normalizeHostname(tenant.hostname);
|
|
66
|
+
if (hostnames.has(host)) {
|
|
67
|
+
throw new TenantRegistryError(`Duplicate tenant hostname: ${host}`);
|
|
68
|
+
}
|
|
69
|
+
hostnames.add(host);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...parsed.data,
|
|
73
|
+
tenants: parsed.data.tenants.map((t) => ({
|
|
74
|
+
...t,
|
|
75
|
+
hostname: normalizeHostname(t.hostname),
|
|
76
|
+
publicOrigin: t.publicOrigin.replace(/\/$/, ''),
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function normalizeHostname(hostname) {
|
|
81
|
+
return hostname.trim().toLowerCase().replace(/\.$/, '');
|
|
82
|
+
}
|
|
83
|
+
/** Strip port from Host header values. */
|
|
84
|
+
export function hostnameFromHostHeader(hostHeader) {
|
|
85
|
+
if (!hostHeader?.trim())
|
|
86
|
+
return null;
|
|
87
|
+
const raw = hostHeader.trim().toLowerCase();
|
|
88
|
+
// IPv6 [::1]:port — rare for cms-edit; treat whole string if brackets present.
|
|
89
|
+
if (raw.startsWith('[')) {
|
|
90
|
+
const end = raw.indexOf(']');
|
|
91
|
+
return end === -1 ? normalizeHostname(raw) : normalizeHostname(raw.slice(0, end + 1));
|
|
92
|
+
}
|
|
93
|
+
const withoutPort = raw.includes(':') ? (raw.split(':')[0] ?? raw) : raw;
|
|
94
|
+
return normalizeHostname(withoutPort);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Staging override: `x-cms-edit-host` header, then `?host=`, then `Host`.
|
|
98
|
+
* Never trust override unless multi-tenant mode is on (caller enforces).
|
|
99
|
+
*/
|
|
100
|
+
export function resolveRequestHostname(request) {
|
|
101
|
+
const override = request.headers.get('x-cms-edit-host')?.trim();
|
|
102
|
+
if (override) {
|
|
103
|
+
return hostnameFromHostHeader(override);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(request.url);
|
|
107
|
+
const queryHost = url.searchParams.get('host')?.trim();
|
|
108
|
+
if (queryHost) {
|
|
109
|
+
return hostnameFromHostHeader(queryHost);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// ignore malformed URL
|
|
114
|
+
}
|
|
115
|
+
return hostnameFromHostHeader(request.headers.get('host'));
|
|
116
|
+
}
|
|
117
|
+
export function findTenantByHostname(registry, hostname) {
|
|
118
|
+
const normalized = normalizeHostname(hostname);
|
|
119
|
+
return registry.tenants.find((t) => t.hostname === normalized);
|
|
120
|
+
}
|
|
121
|
+
let cachedRegistry;
|
|
122
|
+
/** Clear process-level registry cache (tests). */
|
|
123
|
+
export function clearTenantRegistryCache() {
|
|
124
|
+
cachedRegistry = undefined;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Load registry from `CMS_EDIT_TENANT_REGISTRY_JSON` or `CMS_EDIT_TENANT_REGISTRY_PATH`.
|
|
128
|
+
* Returns null when neither is set (single-tenant hosts).
|
|
129
|
+
*/
|
|
130
|
+
export function loadTenantRegistryFromEnv() {
|
|
131
|
+
if (cachedRegistry !== undefined) {
|
|
132
|
+
return cachedRegistry;
|
|
133
|
+
}
|
|
134
|
+
const json = process.env['CMS_EDIT_TENANT_REGISTRY_JSON']?.trim();
|
|
135
|
+
if (json) {
|
|
136
|
+
try {
|
|
137
|
+
cachedRegistry = parseTenantRegistry(JSON.parse(json));
|
|
138
|
+
return cachedRegistry;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
+
throw new TenantRegistryError(`CMS_EDIT_TENANT_REGISTRY_JSON: ${message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const filePath = process.env['CMS_EDIT_TENANT_REGISTRY_PATH']?.trim();
|
|
146
|
+
if (filePath) {
|
|
147
|
+
try {
|
|
148
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
149
|
+
cachedRegistry = parseTenantRegistry(JSON.parse(raw));
|
|
150
|
+
return cachedRegistry;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof TenantRegistryError)
|
|
154
|
+
throw err;
|
|
155
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
156
|
+
throw new TenantRegistryError(`CMS_EDIT_TENANT_REGISTRY_PATH: ${message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
cachedRegistry = null;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
export function readTenantSecret(prefix, suffix) {
|
|
163
|
+
const key = `${prefix}_${suffix}`;
|
|
164
|
+
const value = process.env[key]?.trim();
|
|
165
|
+
return value || undefined;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=tenant-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-registry.js","sourceRoot":"","sources":["../../src/hosted/tenant-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,3 +42,13 @@ The signed-in Contentful user must have the matching role permission. AI Editor
|
|
|
42
42
|
1. Do not apply a plan unless the user has said yes for that plan id.
|
|
43
43
|
2. Do not claim the site is updated until apply succeeds.
|
|
44
44
|
3. There is no scheduled publish.
|
|
45
|
+
|
|
46
|
+
## Before a production promote
|
|
47
|
+
|
|
48
|
+
Publishing in Contentful is not the same as a git promote. Before merging integration → production when new or changed entries are involved:
|
|
49
|
+
|
|
50
|
+
1. List every page, **template**, **person** / person-type, and other linked entries the live pages render through.
|
|
51
|
+
2. Confirm each is published in the production environment with **no pending drafts** (CDA vs CPA, or `check-publish-state`).
|
|
52
|
+
3. Record the result on the promote PR.
|
|
53
|
+
|
|
54
|
+
Shared templates and person entries are easy to leave draft-only while the page looks fine in preview — that is what broke Brightline provider headshots (7 Oct 2026). Full rules: se-core-product `docs/RELEASE_VERIFICATION.md`.
|
package/package.json
CHANGED
package/skills/core/SKILL.md
CHANGED
|
@@ -867,6 +867,17 @@ cms-edit index dump --reference-only # Full catalog snapshot in termina
|
|
|
867
867
|
|
|
868
868
|
If someone else also changed an entry, ask all versus mine and `publish choose`. The Contentful role must allow it. Unpublish, archive, and revalidate use the same plan-then-yes rule. Do not apply a plan on your own.
|
|
869
869
|
|
|
870
|
+
### Before a production promote
|
|
871
|
+
|
|
872
|
+
When pages that use **templates**, **person** entries, or other linked content are about to go live, verify publish state **before** merging the promote (release verification rule 1):
|
|
873
|
+
|
|
874
|
+
1. List the page(s), linked **template**(s), **person** / person-type entries, and any other content stack pieces the live render walks.
|
|
875
|
+
2. Confirm each is published in the production environment with no pending drafts — compare CDA (Delivery) vs CPA (Preview), or use `@se-studio/site-check/publish-state` / `check-publish-state`.
|
|
876
|
+
3. Record results on the promote PR. A Nick override never skips this check.
|
|
877
|
+
4. Do not assume publishing the page alone published a shared template or person — those often need their own publish (and template webhooks refresh pages that use them).
|
|
878
|
+
|
|
879
|
+
See `packages/skills/references/agent-session/release-verification.md` and `docs/editor-tasks/publish-handoff.md` (in contentful-cms).
|
|
880
|
+
|
|
870
881
|
## Concurrent Sessions
|
|
871
882
|
|
|
872
883
|
Use `--session <name>` or `CONTENTFUL_CMS_SESSION=<name>` to isolate parallel workflows.
|