@caelo-cms/shared 0.10.53 → 0.10.55
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/dist/auto-update.d.ts +5 -2
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +13 -6
- package/dist/auto-update.js.map +1 -1
- package/dist/fonts.d.ts +12 -0
- package/dist/fonts.d.ts.map +1 -1
- package/dist/fonts.js +37 -1
- package/dist/fonts.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/link-refs.d.ts +250 -0
- package/dist/link-refs.d.ts.map +1 -0
- package/dist/link-refs.js +416 -0
- package/dist/link-refs.js.map +1 -0
- package/dist/media.d.ts +1 -1
- package/dist/nested-module-render.d.ts +4 -1
- package/dist/nested-module-render.d.ts.map +1 -1
- package/dist/nested-module-render.js +14 -2
- package/dist/nested-module-render.js.map +1 -1
- package/dist/platform-updates.d.ts +7 -3
- package/dist/platform-updates.d.ts.map +1 -1
- package/dist/platform-updates.js +13 -2
- package/dist/platform-updates.js.map +1 -1
- package/dist/preview-compose.d.ts +10 -0
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +58 -20
- package/dist/preview-compose.js.map +1 -1
- package/dist/raw-text-escape.d.ts +18 -0
- package/dist/raw-text-escape.d.ts.map +1 -0
- package/dist/raw-text-escape.js +40 -0
- package/dist/raw-text-escape.js.map +1 -0
- package/dist/responsive-images.d.ts.map +1 -1
- package/dist/responsive-images.js +3 -3
- package/dist/responsive-images.js.map +1 -1
- package/dist/seo.js +1 -1
- package/dist/seo.js.map +1 -1
- package/dist/template-balance.d.ts +3 -0
- package/dist/template-balance.d.ts.map +1 -0
- package/dist/template-balance.js +214 -0
- package/dist/template-balance.js.map +1 -0
- package/dist/template-engine.d.ts +11 -0
- package/dist/template-engine.d.ts.map +1 -1
- package/dist/template-engine.js +12 -0
- package/dist/template-engine.js.map +1 -1
- package/dist/theme-render.d.ts +2 -2
- package/dist/theme-render.d.ts.map +1 -1
- package/dist/theme-render.js +29 -5
- package/dist/theme-render.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/auto-update.test.ts +32 -1
- package/src/auto-update.ts +13 -6
- package/src/fonts.test.ts +37 -0
- package/src/fonts.ts +38 -1
- package/src/index.ts +2 -0
- package/src/link-refs.test.ts +552 -0
- package/src/link-refs.ts +569 -0
- package/src/nested-module-render.ts +18 -1
- package/src/platform-updates.test.ts +5 -0
- package/src/platform-updates.ts +13 -2
- package/src/preview-compose-deferrals.test.ts +103 -0
- package/src/preview-compose.ts +73 -24
- package/src/raw-text-escape.ts +44 -0
- package/src/responsive-images.test.ts +10 -0
- package/src/responsive-images.ts +6 -3
- package/src/seo.test.ts +7 -0
- package/src/seo.ts +1 -1
- package/src/template-balance.test.ts +97 -0
- package/src/template-balance.ts +216 -0
- package/src/template-engine.ts +23 -0
- package/src/theme-render-sanitize.test.ts +40 -0
- package/src/theme-render.ts +40 -8
- package/src/version.ts +1 -1
package/src/auto-update.ts
CHANGED
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
* the soak period.
|
|
21
21
|
*
|
|
22
22
|
* Rules, in order (maintainer decisions on #696):
|
|
23
|
-
* 1. nothing newer
|
|
23
|
+
* 1. nothing newer → none (there is no "notices off": security
|
|
24
|
+
* releases are always announced, #862);
|
|
24
25
|
* 2. off the stable channel, or the policy only notifies → notify;
|
|
25
26
|
* 3. major → notify (never automatic before 1.0);
|
|
26
27
|
* 4. release information unreadable → blocked (fails loudly, no guess);
|
|
27
|
-
* 5. changes the cloud setup → needs_cloud_shell (never automatic
|
|
28
|
+
* 5. changes the cloud setup → needs_cloud_shell (never automatic, even
|
|
29
|
+
* with `autoConverge` on: that choice is inert until the converge job
|
|
30
|
+
* runs these releases from Caelo, #819);
|
|
28
31
|
* 6. destructive database changes → manual (never automatic);
|
|
29
32
|
* 7. the kind is not switched on (security / patch / minor) → notify;
|
|
30
33
|
* 8. no upgrader on this install → blocked;
|
|
@@ -194,9 +197,6 @@ export function decideAutoUpdate(input: AutoUpdateInput): AutoUpdateDecision {
|
|
|
194
197
|
if (kind === null) {
|
|
195
198
|
return { action: "none", reason: `v${input.installedVersion} is the newest release.` };
|
|
196
199
|
}
|
|
197
|
-
if (policy.mode === "off") {
|
|
198
|
-
return { action: "none", reason: "Update notices are switched off in the update policy." };
|
|
199
|
-
}
|
|
200
200
|
const v = release.version;
|
|
201
201
|
const base = { version: v, kind } as const;
|
|
202
202
|
if (!isStable(v)) {
|
|
@@ -229,10 +229,17 @@ export function decideAutoUpdate(input: AutoUpdateInput): AutoUpdateDecision {
|
|
|
229
229
|
}
|
|
230
230
|
const facts = input.facts;
|
|
231
231
|
if (facts.infraChange === true) {
|
|
232
|
+
// FIXME(issue #819): once the converge job runs these releases from
|
|
233
|
+
// Caelo, `policy.autoConverge` decides here like autoPatch does below.
|
|
234
|
+
// Until then the Owner's "install automatically" choice is stored but
|
|
235
|
+
// has no effect, and the reason says so.
|
|
236
|
+
const ownerChoice = policy.autoConverge
|
|
237
|
+
? " Your setting to install these automatically takes effect once Caelo can run them itself."
|
|
238
|
+
: "";
|
|
232
239
|
return {
|
|
233
240
|
action: "needs_cloud_shell",
|
|
234
241
|
...base,
|
|
235
|
-
reason: `v${v} changes this install's cloud setup, which the admin may not do. It is never installed automatically: the Owner runs it once with their own account in Cloud Shell
|
|
242
|
+
reason: `v${v} changes this install's cloud setup, which the admin may not do. It is never installed automatically: the Owner runs it once with their own account in Cloud Shell.${ownerChoice}`,
|
|
236
243
|
};
|
|
237
244
|
}
|
|
238
245
|
if (facts.contractMigrations) {
|
package/src/fonts.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { describe, expect, it } from "bun:test";
|
|
|
10
10
|
import {
|
|
11
11
|
buildFontFaceCss,
|
|
12
12
|
extractThemeFontRequests,
|
|
13
|
+
fontFaceProblem,
|
|
13
14
|
fontUnresolvableMarker,
|
|
14
15
|
googleFontsCssUrl,
|
|
15
16
|
isSystemFontFamily,
|
|
@@ -155,3 +156,39 @@ describe("fontUnresolvableMarker", () => {
|
|
|
155
156
|
expect(fontUnresolvableMarker("Poppins")).toBe("theme-font-unresolvable:Poppins");
|
|
156
157
|
});
|
|
157
158
|
});
|
|
159
|
+
|
|
160
|
+
describe("buildFontFaceCss descriptor validation", () => {
|
|
161
|
+
const good = {
|
|
162
|
+
family: "Inter",
|
|
163
|
+
style: "normal",
|
|
164
|
+
weight: "400",
|
|
165
|
+
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153",
|
|
166
|
+
publicUrl: "/_assets/fonts/inter/abc123.woff2",
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
it("accepts the descriptor shapes a css2 response carries", () => {
|
|
170
|
+
expect(fontFaceProblem(good)).toBeNull();
|
|
171
|
+
expect(fontFaceProblem({ ...good, style: "italic", weight: "100 900" })).toBeNull();
|
|
172
|
+
expect(fontFaceProblem({ ...good, style: "oblique 0deg 10deg" })).toBeNull();
|
|
173
|
+
expect(fontFaceProblem({ ...good, unicodeRange: null })).toBeNull();
|
|
174
|
+
expect(fontFaceProblem({ ...good, unicodeRange: "U+4??" })).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("refuses descriptors that could close the <style> or add rules, instead of scrubbing them", () => {
|
|
178
|
+
expect(fontFaceProblem({ ...good, style: "normal;}</style><script>" })).toMatch(/font-style/);
|
|
179
|
+
expect(fontFaceProblem({ ...good, weight: "400;}</style>" })).toMatch(/font-weight/);
|
|
180
|
+
expect(fontFaceProblem({ ...good, unicodeRange: "U+0000-00FF;</style>" })).toMatch(
|
|
181
|
+
/unicode-range/,
|
|
182
|
+
);
|
|
183
|
+
expect(fontFaceProblem({ ...good, publicUrl: "/x.woff2)</style>" })).toMatch(/url/);
|
|
184
|
+
expect(() => buildFontFaceCss([{ ...good, weight: "400}</style>" }])).toThrow(
|
|
185
|
+
/invalid font-weight/,
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("writes a family name's < as a CSS escape so it cannot spell </style", () => {
|
|
190
|
+
const css = buildFontFaceCss([{ ...good, family: "Evil</style><script>alert(1)</script>" }]);
|
|
191
|
+
expect(css.toLowerCase()).not.toContain("</style");
|
|
192
|
+
expect(css).toContain("Evil\\3c /style>");
|
|
193
|
+
});
|
|
194
|
+
});
|
package/src/fonts.ts
CHANGED
|
@@ -224,16 +224,53 @@ export function parseFontsCss(cssText: string): ParsedFontFace[] {
|
|
|
224
224
|
return faces;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
const FONT_STYLE_RE = /^(?:normal|italic|oblique(?: +-?\d{1,3}(?:\.\d+)?deg){0,2})$/i;
|
|
228
|
+
const FONT_WEIGHT_RE = /^(?:normal|bold|\d{1,4}(?: +\d{1,4})?)$/i;
|
|
229
|
+
const UNICODE_RANGE_ITEM = "U\\+[0-9A-F?]{1,6}(?:-[0-9A-F]{1,6})?";
|
|
230
|
+
const UNICODE_RANGE_RE = new RegExp(`^${UNICODE_RANGE_ITEM}(?:, *${UNICODE_RANGE_ITEM})*$`, "i");
|
|
231
|
+
/** A same-site asset path: no quotes, parens, whitespace or `<`. */
|
|
232
|
+
const FONT_URL_RE = /^[A-Za-z0-9._~/:%+-]+$/;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Why `face` cannot be emitted as an `@font-face` rule, or null when it can.
|
|
236
|
+
* The descriptors come from a remote fonts-CSS payload, so each one is held
|
|
237
|
+
* to the grammar a real descriptor has instead of being scrubbed: a value
|
|
238
|
+
* outside it could close the surrounding `<style>` or add rules, and a
|
|
239
|
+
* scrubbed value would ship a silently different font.
|
|
240
|
+
*/
|
|
241
|
+
export function fontFaceProblem(face: ResolvedFontFace): string | null {
|
|
242
|
+
if (!FONT_STYLE_RE.test(face.style)) return `font-style ${JSON.stringify(face.style)}`;
|
|
243
|
+
if (!FONT_WEIGHT_RE.test(face.weight)) return `font-weight ${JSON.stringify(face.weight)}`;
|
|
244
|
+
if (face.unicodeRange !== null && !UNICODE_RANGE_RE.test(face.unicodeRange)) {
|
|
245
|
+
return `unicode-range ${JSON.stringify(face.unicodeRange)}`;
|
|
246
|
+
}
|
|
247
|
+
if (!FONT_URL_RE.test(face.publicUrl)) return `url ${JSON.stringify(face.publicUrl)}`;
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
227
251
|
/**
|
|
228
252
|
* Emit self-hosted @font-face CSS. `font-display: swap` always — text
|
|
229
253
|
* must render in the fallback while the woff2 loads.
|
|
254
|
+
*
|
|
255
|
+
* Throws on a face {@link fontFaceProblem} refuses; the resolver checks
|
|
256
|
+
* faces first and reports a bad family as unresolved, so reaching the
|
|
257
|
+
* throw means a caller skipped that check.
|
|
230
258
|
*/
|
|
231
259
|
export function buildFontFaceCss(faces: readonly ResolvedFontFace[]): string {
|
|
232
260
|
return faces
|
|
233
261
|
.map((f) => {
|
|
262
|
+
const problem = fontFaceProblem(f);
|
|
263
|
+
if (problem !== null) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`fonts: refusing the @font-face for ${JSON.stringify(f.family)} — invalid ${problem}. Re-fetch the family or pick another font.`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
// A CSS string; `<` is written as its CSS escape so no family name
|
|
269
|
+
// can spell `</style` in the surrounding HTML.
|
|
270
|
+
const family = JSON.stringify(f.family).replaceAll("<", "\\3c ");
|
|
234
271
|
const range = f.unicodeRange !== null ? `unicode-range:${f.unicodeRange};` : "";
|
|
235
272
|
return (
|
|
236
|
-
`@font-face{font-family:${
|
|
273
|
+
`@font-face{font-family:${family};` +
|
|
237
274
|
`font-style:${f.style};font-weight:${f.weight};font-display:swap;` +
|
|
238
275
|
`src:url(${f.publicUrl}) format('woff2');${range}}`
|
|
239
276
|
);
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from "./genesis-inventory.js";
|
|
|
25
25
|
export * from "./google-models.js";
|
|
26
26
|
export * from "./image-capabilities.js";
|
|
27
27
|
export * from "./interactions.js";
|
|
28
|
+
export * from "./link-refs.js";
|
|
28
29
|
export {
|
|
29
30
|
type LogContext,
|
|
30
31
|
type Logger,
|
|
@@ -47,6 +48,7 @@ export * from "./preview-scanner.js";
|
|
|
47
48
|
export * from "./proposal-status.js";
|
|
48
49
|
export * from "./provisioning-command.js";
|
|
49
50
|
export * from "./publish-live-parity.js";
|
|
51
|
+
export * from "./raw-text-escape.js";
|
|
50
52
|
export * from "./redirect-emit-caddy.js";
|
|
51
53
|
export * from "./redirect-emit-cloudfront.js";
|
|
52
54
|
export * from "./redirect-emit-front-door.js";
|