@aglyn/aglyn 1.0.0-beta.211 → 1.0.0-beta.213
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/package.json +11 -11
- package/src/lib/app-utils/docs-index.generated.js +14 -7
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.d.ts +3 -2
- package/src/lib/app-utils/plugin-bundle-checks.js +242 -3
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-abi.d.ts +61 -0
- package/src/lib/app-utils/plugin-host-abi.js +68 -0
- package/src/lib/app-utils/plugin-host-abi.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.d.ts +9 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +143 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -0
- package/src/lib/app-utils/search-engine-verification.d.ts +98 -0
- package/src/lib/app-utils/search-engine-verification.js +140 -0
- package/src/lib/app-utils/search-engine-verification.js.map +1 -0
- package/src/lib/app-utils/site-entity-json-ld.d.ts +9 -1
- package/src/lib/app-utils/site-entity-json-ld.js +25 -7
- package/src/lib/app-utils/site-entity-json-ld.js.map +1 -1
- package/src/lib/app-utils/variables.d.ts +18 -19
- package/src/lib/app-utils/variables.js +24 -23
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/webhook-delivery.js +3 -1
- package/src/lib/app-utils/webhook-delivery.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +24 -0
- package/src/lib/foundation/definitions/platform.types.js +2 -1
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/enabled-plugins.d.ts +6 -0
- package/src/lib/plugin-manager/enabled-plugins.js +5 -0
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +6 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +8 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contributions.d.ts +59 -0
- package/src/lib/plugin-manager/plugin-contributions.js +113 -0
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.d.ts +8 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +101 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -0
- package/src/lib/plugin-manager/realm-plugins.d.ts +3 -2
- package/src/lib/plugin-manager/realm-plugins.js +8 -3
- package/src/lib/plugin-manager/realm-plugins.js.map +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* What the realm-plugin host ABI hands a signed bundle (AGL-420, AGL-3392):
|
|
18
|
+
* the keys of `globalThis.__AGLYN_PLUGIN_HOST__`, which every app composes
|
|
19
|
+
* from its OWN module instances.
|
|
20
|
+
*
|
|
21
|
+
* Two rules decide what is here. What must be ONE instance across the app and
|
|
22
|
+
* a bundle comes from the host, or the bundle splits it silently. And a
|
|
23
|
+
* library the site already runs is never compiled into a bundle a second time
|
|
24
|
+
* — so the host holds it, and the realm build refuses to compile it in:
|
|
25
|
+
*
|
|
26
|
+
* - `React`, `jsxRuntime` — two Reacts cannot render one tree.
|
|
27
|
+
* - `aglyn` — core's REALM PLUGIN SURFACE: the registries a bundle registers
|
|
28
|
+
* into and the vocabulary it registers with.
|
|
29
|
+
* - `mui` — MUI's realm plugin surface: the components a site element is laid
|
|
30
|
+
* out, filled in and printed with, their class names, `useMediaQuery`, and
|
|
31
|
+
* `createSvgIcon` (how an icon from `@mui/icons-material` draws).
|
|
32
|
+
* - `muiStyles` — the part of `@mui/material/styles` an element styles itself
|
|
33
|
+
* with: `styled`, `useTheme`, the color helpers, `css`, `keyframes`,
|
|
34
|
+
* `useColorScheme` — the site's theme and style cache behind them.
|
|
35
|
+
*
|
|
36
|
+
* Each is a short reviewed list of NAMES (`generate-realm-host-exports.mjs`),
|
|
37
|
+
* read member by member and imported STATICALLY by the host module a page
|
|
38
|
+
* loads only when it runs a realm plugin. Measured on the tenant build, both
|
|
39
|
+
* shapes that looked cheaper cost every published page: an `import()` reaching
|
|
40
|
+
* a library module re-cut the chunks every page loads, and a whole module
|
|
41
|
+
* passed as a value (`import * as styles`) kept every one of its exports alive
|
|
42
|
+
* in the page's own copy. Loading per export would also have tied every signed
|
|
43
|
+
* bundle to the host's exact MUI version.
|
|
44
|
+
*
|
|
45
|
+
* One list, read by the apps that compose the host, the realm Rollup configs
|
|
46
|
+
* that compile imports into lookups on it, and the bundle verifier that
|
|
47
|
+
* refuses a lookup on any other key, and a name its module does not hold.
|
|
48
|
+
* Adding a key is additive and keeps `PLUGIN_HOST_ABI_VERSION`; removing or
|
|
49
|
+
* reshaping one is not.
|
|
50
|
+
*/ export const PLUGIN_HOST_ABI_KEYS = [
|
|
51
|
+
'version',
|
|
52
|
+
'React',
|
|
53
|
+
'jsxRuntime',
|
|
54
|
+
'aglyn',
|
|
55
|
+
'mui',
|
|
56
|
+
'muiStyles'
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* The keys that are SURFACES, whose names the verifier checks against what the
|
|
60
|
+
* host holds (`realm-host-surface.generated.ts`).
|
|
61
|
+
*/ export const PLUGIN_HOST_MODULE_KEYS = [
|
|
62
|
+
'aglyn',
|
|
63
|
+
'mui',
|
|
64
|
+
'muiStyles'
|
|
65
|
+
];
|
|
66
|
+
/** The global the host ABI is published on. */ export const PLUGIN_HOST_GLOBAL = '__AGLYN_PLUGIN_HOST__';
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=plugin-host-abi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plugin-host-abi.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What the realm-plugin host ABI hands a signed bundle (AGL-420, AGL-3392):\n * the keys of `globalThis.__AGLYN_PLUGIN_HOST__`, which every app composes\n * from its OWN module instances.\n *\n * Two rules decide what is here. What must be ONE instance across the app and\n * a bundle comes from the host, or the bundle splits it silently. And a\n * library the site already runs is never compiled into a bundle a second time\n * — so the host holds it, and the realm build refuses to compile it in:\n *\n * - `React`, `jsxRuntime` — two Reacts cannot render one tree.\n * - `aglyn` — core's REALM PLUGIN SURFACE: the registries a bundle registers\n * into and the vocabulary it registers with.\n * - `mui` — MUI's realm plugin surface: the components a site element is laid\n * out, filled in and printed with, their class names, `useMediaQuery`, and\n * `createSvgIcon` (how an icon from `@mui/icons-material` draws).\n * - `muiStyles` — the part of `@mui/material/styles` an element styles itself\n * with: `styled`, `useTheme`, the color helpers, `css`, `keyframes`,\n * `useColorScheme` — the site's theme and style cache behind them.\n *\n * Each is a short reviewed list of NAMES (`generate-realm-host-exports.mjs`),\n * read member by member and imported STATICALLY by the host module a page\n * loads only when it runs a realm plugin. Measured on the tenant build, both\n * shapes that looked cheaper cost every published page: an `import()` reaching\n * a library module re-cut the chunks every page loads, and a whole module\n * passed as a value (`import * as styles`) kept every one of its exports alive\n * in the page's own copy. Loading per export would also have tied every signed\n * bundle to the host's exact MUI version.\n *\n * One list, read by the apps that compose the host, the realm Rollup configs\n * that compile imports into lookups on it, and the bundle verifier that\n * refuses a lookup on any other key, and a name its module does not hold.\n * Adding a key is additive and keeps `PLUGIN_HOST_ABI_VERSION`; removing or\n * reshaping one is not.\n */\nexport const PLUGIN_HOST_ABI_KEYS = [\n 'version',\n 'React',\n 'jsxRuntime',\n 'aglyn',\n 'mui',\n 'muiStyles',\n] as const\n\nexport type PluginHostAbiKey = (typeof PLUGIN_HOST_ABI_KEYS)[number]\n\n/**\n * The keys that are SURFACES, whose names the verifier checks against what the\n * host holds (`realm-host-surface.generated.ts`).\n */\nexport const PLUGIN_HOST_MODULE_KEYS = ['aglyn', 'mui', 'muiStyles'] as const\n\nexport type PluginHostModuleKey = (typeof PLUGIN_HOST_MODULE_KEYS)[number]\n\n/** The global the host ABI is published on. */\nexport const PLUGIN_HOST_GLOBAL = '__AGLYN_PLUGIN_HOST__'\n"],"names":["PLUGIN_HOST_ABI_KEYS","PLUGIN_HOST_MODULE_KEYS","PLUGIN_HOST_GLOBAL"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,GACD,OAAO,MAAMA,uBAAuB;IAClC;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAIV;;;CAGC,GACD,OAAO,MAAMC,0BAA0B;IAAC;IAAS;IAAO;CAAY,CAAS;AAI7E,6CAA6C,GAC7C,OAAO,MAAMC,qBAAqB,wBAAuB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit. Regenerate with:
|
|
3
|
+
* node tools/scripts/generate-realm-host-exports.mjs
|
|
4
|
+
*
|
|
5
|
+
* The names each realm host module holds (AGL-3392), for the bundle
|
|
6
|
+
* verifier: a bundle reading any other name from the host is refused,
|
|
7
|
+
* because it would read `undefined` on every site.
|
|
8
|
+
*/
|
|
9
|
+
export declare const REALM_HOST_SURFACE_NAMES: Readonly<Record<'aglyn' | 'mui' | 'muiStyles', ReadonlySet<string>>>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit. Regenerate with:
|
|
3
|
+
* node tools/scripts/generate-realm-host-exports.mjs
|
|
4
|
+
*
|
|
5
|
+
* The names each realm host module holds (AGL-3392), for the bundle
|
|
6
|
+
* verifier: a bundle reading any other name from the host is refused,
|
|
7
|
+
* because it would read `undefined` on every site.
|
|
8
|
+
*/ /* eslint-disable */ export const REALM_HOST_SURFACE_NAMES = {
|
|
9
|
+
aglyn: new Set([
|
|
10
|
+
"Aglyn",
|
|
11
|
+
"CATEGORY_REQUIRED_CAPABILITY",
|
|
12
|
+
"COMPONENT_CATEGORY_ORDER",
|
|
13
|
+
"CONSOLE_STAFF_WIDGET_SLOTS",
|
|
14
|
+
"CONSOLE_WIDGET_SLOTS",
|
|
15
|
+
"ComponentCategory",
|
|
16
|
+
"EnabledPluginsContext",
|
|
17
|
+
"FEATURE_FLAG",
|
|
18
|
+
"FUNCTION_BUILTIN_NAMES",
|
|
19
|
+
"FUNCTION_MAX_OPERATIONS",
|
|
20
|
+
"FUNCTION_TOKEN_PATTERN",
|
|
21
|
+
"FieldComponentType",
|
|
22
|
+
"FieldValidatorType",
|
|
23
|
+
"HOST_VARIABLE_TYPE_LABELS",
|
|
24
|
+
"LinealDirectiveFlag",
|
|
25
|
+
"MISSING_BINDING_LABEL",
|
|
26
|
+
"MUI_BUNDLE_ID",
|
|
27
|
+
"NAME_TOKEN_PATTERN",
|
|
28
|
+
"NodeIdentityContext",
|
|
29
|
+
"NodeType",
|
|
30
|
+
"PREVIEW_REFUSED_STATUS",
|
|
31
|
+
"PREVIEW_WRITE_BLOCKED_EVENT",
|
|
32
|
+
"REUSABLE_COMPONENT_CATEGORY",
|
|
33
|
+
"REUSABLE_EMAIL_BLOCK_CATEGORY",
|
|
34
|
+
"RICH_TEXT_COMMANDS",
|
|
35
|
+
"ScreenLinkContext",
|
|
36
|
+
"SiteContext",
|
|
37
|
+
"VARIABLE_ID_TOKEN_PATTERN",
|
|
38
|
+
"VARIABLE_NAME_PATTERN",
|
|
39
|
+
"aglyn",
|
|
40
|
+
"attachFunctionDefinitions",
|
|
41
|
+
"bindingTokenNeedsDeep",
|
|
42
|
+
"canvas",
|
|
43
|
+
"components",
|
|
44
|
+
"defineUiFeatureBundle",
|
|
45
|
+
"displayBindingTokens",
|
|
46
|
+
"editableBindingTokens",
|
|
47
|
+
"emitter",
|
|
48
|
+
"evaluateExpression",
|
|
49
|
+
"evaluateHostFunction",
|
|
50
|
+
"expressionIdentifiers",
|
|
51
|
+
"formatFunctionIdToken",
|
|
52
|
+
"formatFunctionParameterOptions",
|
|
53
|
+
"formatVariableIdToken",
|
|
54
|
+
"formatVariableValue",
|
|
55
|
+
"functionGlobals",
|
|
56
|
+
"functionReferencedNames",
|
|
57
|
+
"hasBindings",
|
|
58
|
+
"isCategoryCapabilityEnabled",
|
|
59
|
+
"isConsoleStaffWidgetSlot",
|
|
60
|
+
"isFromEnabledPlugin",
|
|
61
|
+
"isPreviewRefusal",
|
|
62
|
+
"isSwitchedOffForRenderedSite",
|
|
63
|
+
"keyById",
|
|
64
|
+
"listConsoleExtensions",
|
|
65
|
+
"listConsoleNavItems",
|
|
66
|
+
"listConsoleOrgNavItems",
|
|
67
|
+
"listConsoleProviders",
|
|
68
|
+
"listConsoleStaffPages",
|
|
69
|
+
"listConsoleWidgets",
|
|
70
|
+
"logger",
|
|
71
|
+
"nodesReferenceBinding",
|
|
72
|
+
"normalizeBindingTokens",
|
|
73
|
+
"parseFunctionParameterOptions",
|
|
74
|
+
"plugins",
|
|
75
|
+
"registerConsoleExtension",
|
|
76
|
+
"resolveBindings",
|
|
77
|
+
"resolveConsoleOrgPluginPage",
|
|
78
|
+
"resolveConsolePluginPage",
|
|
79
|
+
"resolveConsoleStaffPage",
|
|
80
|
+
"resolveNodesBindings",
|
|
81
|
+
"rewriteBindingTokensDeep",
|
|
82
|
+
"textReferencesBinding",
|
|
83
|
+
"unregisterConsoleExtension",
|
|
84
|
+
"useEnabledPlugins",
|
|
85
|
+
"useNodeId",
|
|
86
|
+
"useSite",
|
|
87
|
+
"useSiteFetch"
|
|
88
|
+
]),
|
|
89
|
+
mui: new Set([
|
|
90
|
+
"Alert",
|
|
91
|
+
"Box",
|
|
92
|
+
"Button",
|
|
93
|
+
"Card",
|
|
94
|
+
"CardContent",
|
|
95
|
+
"Checkbox",
|
|
96
|
+
"Chip",
|
|
97
|
+
"CircularProgress",
|
|
98
|
+
"Collapse",
|
|
99
|
+
"Divider",
|
|
100
|
+
"FormControl",
|
|
101
|
+
"FormControlLabel",
|
|
102
|
+
"FormHelperText",
|
|
103
|
+
"IconButton",
|
|
104
|
+
"InputAdornment",
|
|
105
|
+
"InputLabel",
|
|
106
|
+
"Link",
|
|
107
|
+
"List",
|
|
108
|
+
"ListItem",
|
|
109
|
+
"ListItemText",
|
|
110
|
+
"MenuItem",
|
|
111
|
+
"Paper",
|
|
112
|
+
"Radio",
|
|
113
|
+
"RadioGroup",
|
|
114
|
+
"Select",
|
|
115
|
+
"Stack",
|
|
116
|
+
"SvgIcon",
|
|
117
|
+
"Switch",
|
|
118
|
+
"Table",
|
|
119
|
+
"TableBody",
|
|
120
|
+
"TableCell",
|
|
121
|
+
"TableHead",
|
|
122
|
+
"TableRow",
|
|
123
|
+
"TextField",
|
|
124
|
+
"Tooltip",
|
|
125
|
+
"Typography",
|
|
126
|
+
"createSvgIcon",
|
|
127
|
+
"useMediaQuery"
|
|
128
|
+
]),
|
|
129
|
+
muiStyles: new Set([
|
|
130
|
+
"alpha",
|
|
131
|
+
"css",
|
|
132
|
+
"darken",
|
|
133
|
+
"emphasize",
|
|
134
|
+
"getContrastRatio",
|
|
135
|
+
"keyframes",
|
|
136
|
+
"lighten",
|
|
137
|
+
"styled",
|
|
138
|
+
"useColorScheme",
|
|
139
|
+
"useTheme"
|
|
140
|
+
])
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=realm-host-surface.generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/realm-host-surface.generated.ts"],"sourcesContent":["/**\n * GENERATED FILE — do not edit. Regenerate with:\n * node tools/scripts/generate-realm-host-exports.mjs\n *\n * The names each realm host module holds (AGL-3392), for the bundle\n * verifier: a bundle reading any other name from the host is refused,\n * because it would read `undefined` on every site.\n */\n/* eslint-disable */\n\nexport const REALM_HOST_SURFACE_NAMES: Readonly<\n Record<'aglyn' | 'mui' | 'muiStyles', ReadonlySet<string>>\n> = {\n aglyn: new Set([\"Aglyn\",\"CATEGORY_REQUIRED_CAPABILITY\",\"COMPONENT_CATEGORY_ORDER\",\"CONSOLE_STAFF_WIDGET_SLOTS\",\"CONSOLE_WIDGET_SLOTS\",\"ComponentCategory\",\"EnabledPluginsContext\",\"FEATURE_FLAG\",\"FUNCTION_BUILTIN_NAMES\",\"FUNCTION_MAX_OPERATIONS\",\"FUNCTION_TOKEN_PATTERN\",\"FieldComponentType\",\"FieldValidatorType\",\"HOST_VARIABLE_TYPE_LABELS\",\"LinealDirectiveFlag\",\"MISSING_BINDING_LABEL\",\"MUI_BUNDLE_ID\",\"NAME_TOKEN_PATTERN\",\"NodeIdentityContext\",\"NodeType\",\"PREVIEW_REFUSED_STATUS\",\"PREVIEW_WRITE_BLOCKED_EVENT\",\"REUSABLE_COMPONENT_CATEGORY\",\"REUSABLE_EMAIL_BLOCK_CATEGORY\",\"RICH_TEXT_COMMANDS\",\"ScreenLinkContext\",\"SiteContext\",\"VARIABLE_ID_TOKEN_PATTERN\",\"VARIABLE_NAME_PATTERN\",\"aglyn\",\"attachFunctionDefinitions\",\"bindingTokenNeedsDeep\",\"canvas\",\"components\",\"defineUiFeatureBundle\",\"displayBindingTokens\",\"editableBindingTokens\",\"emitter\",\"evaluateExpression\",\"evaluateHostFunction\",\"expressionIdentifiers\",\"formatFunctionIdToken\",\"formatFunctionParameterOptions\",\"formatVariableIdToken\",\"formatVariableValue\",\"functionGlobals\",\"functionReferencedNames\",\"hasBindings\",\"isCategoryCapabilityEnabled\",\"isConsoleStaffWidgetSlot\",\"isFromEnabledPlugin\",\"isPreviewRefusal\",\"isSwitchedOffForRenderedSite\",\"keyById\",\"listConsoleExtensions\",\"listConsoleNavItems\",\"listConsoleOrgNavItems\",\"listConsoleProviders\",\"listConsoleStaffPages\",\"listConsoleWidgets\",\"logger\",\"nodesReferenceBinding\",\"normalizeBindingTokens\",\"parseFunctionParameterOptions\",\"plugins\",\"registerConsoleExtension\",\"resolveBindings\",\"resolveConsoleOrgPluginPage\",\"resolveConsolePluginPage\",\"resolveConsoleStaffPage\",\"resolveNodesBindings\",\"rewriteBindingTokensDeep\",\"textReferencesBinding\",\"unregisterConsoleExtension\",\"useEnabledPlugins\",\"useNodeId\",\"useSite\",\"useSiteFetch\"]),\n mui: new Set([\"Alert\",\"Box\",\"Button\",\"Card\",\"CardContent\",\"Checkbox\",\"Chip\",\"CircularProgress\",\"Collapse\",\"Divider\",\"FormControl\",\"FormControlLabel\",\"FormHelperText\",\"IconButton\",\"InputAdornment\",\"InputLabel\",\"Link\",\"List\",\"ListItem\",\"ListItemText\",\"MenuItem\",\"Paper\",\"Radio\",\"RadioGroup\",\"Select\",\"Stack\",\"SvgIcon\",\"Switch\",\"Table\",\"TableBody\",\"TableCell\",\"TableHead\",\"TableRow\",\"TextField\",\"Tooltip\",\"Typography\",\"createSvgIcon\",\"useMediaQuery\"]),\n muiStyles: new Set([\"alpha\",\"css\",\"darken\",\"emphasize\",\"getContrastRatio\",\"keyframes\",\"lighten\",\"styled\",\"useColorScheme\",\"useTheme\"]),\n}\n"],"names":["REALM_HOST_SURFACE_NAMES","aglyn","Set","mui","muiStyles"],"mappings":"AAAA;;;;;;;CAOC,GACD,kBAAkB,GAElB,OAAO,MAAMA,2BAET;IACFC,OAAO,IAAIC,IAAI;QAAC;QAAQ;QAA+B;QAA2B;QAA6B;QAAuB;QAAoB;QAAwB;QAAe;QAAyB;QAA0B;QAAyB;QAAqB;QAAqB;QAA4B;QAAsB;QAAwB;QAAgB;QAAqB;QAAsB;QAAW;QAAyB;QAA8B;QAA8B;QAAgC;QAAqB;QAAoB;QAAc;QAA4B;QAAwB;QAAQ;QAA4B;QAAwB;QAAS;QAAa;QAAwB;QAAuB;QAAwB;QAAU;QAAqB;QAAuB;QAAwB;QAAwB;QAAiC;QAAwB;QAAsB;QAAkB;QAA0B;QAAc;QAA8B;QAA2B;QAAsB;QAAmB;QAA+B;QAAU;QAAwB;QAAsB;QAAyB;QAAuB;QAAwB;QAAqB;QAAS;QAAwB;QAAyB;QAAgC;QAAU;QAA2B;QAAkB;QAA8B;QAA2B;QAA0B;QAAuB;QAA2B;QAAwB;QAA6B;QAAoB;QAAY;QAAU;KAAe;IACzsDC,KAAK,IAAID,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAO;QAAc;QAAW;QAAO;QAAmB;QAAW;QAAU;QAAc;QAAmB;QAAiB;QAAa;QAAiB;QAAa;QAAO;QAAO;QAAW;QAAe;QAAW;QAAQ;QAAQ;QAAa;QAAS;QAAQ;QAAU;QAAS;QAAQ;QAAY;QAAY;QAAY;QAAW;QAAY;QAAU;QAAa;QAAgB;KAAgB;IAC/bE,WAAW,IAAIF,IAAI;QAAC;QAAQ;QAAM;QAAS;QAAY;QAAmB;QAAY;QAAU;QAAS;QAAiB;KAAW;AACvI,EAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Search engine ownership verification by HTML meta tag (AGL-3399).
|
|
19
|
+
*
|
|
20
|
+
* ## Why this exists
|
|
21
|
+
*
|
|
22
|
+
* Google Search Console and Bing Webmaster Tools each offer three ways to
|
|
23
|
+
* prove a site is yours: a DNS record, an uploaded file, or a meta tag in the
|
|
24
|
+
* home page's head. A site on a platform subdomain has no DNS of its own and
|
|
25
|
+
* no way to upload a file to the origin root, so the meta tag is the only
|
|
26
|
+
* method it can use — and until this existed there was nowhere to put one.
|
|
27
|
+
*
|
|
28
|
+
* ## What is stored, and what is emitted
|
|
29
|
+
*
|
|
30
|
+
* The tools hand out a whole tag —
|
|
31
|
+
* `<meta name="google-site-verification" content="…" />` — and most people
|
|
32
|
+
* paste exactly that. Only the `content` value is stored
|
|
33
|
+
* (`host.seo.verification`), so the tenant always writes the tag itself and
|
|
34
|
+
* never echoes markup a person pasted.
|
|
35
|
+
*
|
|
36
|
+
* The token is validated at BOTH ends with the same pattern: in the console,
|
|
37
|
+
* so a bad paste is refused at the field, and again in the tenant before it
|
|
38
|
+
* reaches the head, so a value that got into the document some other way is
|
|
39
|
+
* dropped rather than rendered.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately NOT re-exported from the `@aglyn/aglyn` root barrel: that
|
|
42
|
+
* barrel is part of the published-page and realm bundles, and the tenant
|
|
43
|
+
* layout and the console settings card each import this by path.
|
|
44
|
+
*/
|
|
45
|
+
/** The engines a site can verify with. */
|
|
46
|
+
export type SearchEngineVerificationEngine = 'google' | 'bing';
|
|
47
|
+
/** `host.seo.verification`, one token per engine. */
|
|
48
|
+
export type SearchEngineVerification = Partial<Record<SearchEngineVerificationEngine, string>>;
|
|
49
|
+
/** The `<meta name>` each engine looks for. */
|
|
50
|
+
export declare const SEARCH_ENGINE_VERIFICATION_META_NAMES: Readonly<Record<SearchEngineVerificationEngine, string>>;
|
|
51
|
+
/** What each engine is called on screen. */
|
|
52
|
+
export declare const SEARCH_ENGINE_VERIFICATION_LABELS: Readonly<Record<SearchEngineVerificationEngine, string>>;
|
|
53
|
+
/**
|
|
54
|
+
* A verification token: letters, digits, `-` and `_`, at most 128.
|
|
55
|
+
*
|
|
56
|
+
* Google's tokens are URL-safe base64 (about 43 characters) and Bing's are 32
|
|
57
|
+
* hex digits; both fit, and nothing that could close an attribute does.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN: RegExp;
|
|
60
|
+
/** What a paste into a verification field reads as. */
|
|
61
|
+
export interface ParsedVerificationInput {
|
|
62
|
+
/** The token — the `content` value of a pasted tag, or the paste itself. */
|
|
63
|
+
token: string;
|
|
64
|
+
/** The `name` of a pasted tag, lower-cased; absent for a bare token. */
|
|
65
|
+
metaName?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Reads a paste as either a bare token or the whole tag the tools hand out.
|
|
69
|
+
*
|
|
70
|
+
* A paste that looks like markup (it has a `<` or a `content=`) is read as a
|
|
71
|
+
* tag and yields its `content` value, `''` when it has none. Anything else is
|
|
72
|
+
* the token itself, trimmed. Never validates — see
|
|
73
|
+
* {@link searchEngineVerificationError}.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseSearchEngineVerificationInput(input: unknown): ParsedVerificationInput;
|
|
76
|
+
/** The token a paste stores as: the tag's `content`, or the trimmed paste. */
|
|
77
|
+
export declare function extractSearchEngineVerificationToken(input: unknown): string;
|
|
78
|
+
/** Whether `value` is a token the tenant will emit. */
|
|
79
|
+
export declare function isSearchEngineVerificationToken(value: unknown): value is string;
|
|
80
|
+
/**
|
|
81
|
+
* The field error for a paste into `engine`'s field, or `undefined` when it
|
|
82
|
+
* is empty or reads as a valid token.
|
|
83
|
+
*
|
|
84
|
+
* A tag for the OTHER engine is named as such: a Bing tag pasted into the
|
|
85
|
+
* Google field holds a perfectly well-formed token, and storing it would make
|
|
86
|
+
* Search Console's Verify fail with nothing on this side saying why.
|
|
87
|
+
*/
|
|
88
|
+
export declare function searchEngineVerificationError(engine: SearchEngineVerificationEngine, input: unknown): string | undefined;
|
|
89
|
+
/** One `<meta>` a published page emits. */
|
|
90
|
+
export interface SearchEngineVerificationMeta {
|
|
91
|
+
name: string;
|
|
92
|
+
content: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The verification `<meta>` tags a published page emits: one per engine
|
|
96
|
+
* whose stored token passes the pattern, none for anything unset or invalid.
|
|
97
|
+
*/
|
|
98
|
+
export declare function searchEngineVerificationMeta(verification: unknown): SearchEngineVerificationMeta[];
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Search engine ownership verification by HTML meta tag (AGL-3399).
|
|
18
|
+
*
|
|
19
|
+
* ## Why this exists
|
|
20
|
+
*
|
|
21
|
+
* Google Search Console and Bing Webmaster Tools each offer three ways to
|
|
22
|
+
* prove a site is yours: a DNS record, an uploaded file, or a meta tag in the
|
|
23
|
+
* home page's head. A site on a platform subdomain has no DNS of its own and
|
|
24
|
+
* no way to upload a file to the origin root, so the meta tag is the only
|
|
25
|
+
* method it can use — and until this existed there was nowhere to put one.
|
|
26
|
+
*
|
|
27
|
+
* ## What is stored, and what is emitted
|
|
28
|
+
*
|
|
29
|
+
* The tools hand out a whole tag —
|
|
30
|
+
* `<meta name="google-site-verification" content="…" />` — and most people
|
|
31
|
+
* paste exactly that. Only the `content` value is stored
|
|
32
|
+
* (`host.seo.verification`), so the tenant always writes the tag itself and
|
|
33
|
+
* never echoes markup a person pasted.
|
|
34
|
+
*
|
|
35
|
+
* The token is validated at BOTH ends with the same pattern: in the console,
|
|
36
|
+
* so a bad paste is refused at the field, and again in the tenant before it
|
|
37
|
+
* reaches the head, so a value that got into the document some other way is
|
|
38
|
+
* dropped rather than rendered.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately NOT re-exported from the `@aglyn/aglyn` root barrel: that
|
|
41
|
+
* barrel is part of the published-page and realm bundles, and the tenant
|
|
42
|
+
* layout and the console settings card each import this by path.
|
|
43
|
+
*/ /** The engines a site can verify with. */ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
44
|
+
/** The `<meta name>` each engine looks for. */ export const SEARCH_ENGINE_VERIFICATION_META_NAMES = {
|
|
45
|
+
google: 'google-site-verification',
|
|
46
|
+
bing: 'msvalidate.01'
|
|
47
|
+
};
|
|
48
|
+
/** What each engine is called on screen. */ export const SEARCH_ENGINE_VERIFICATION_LABELS = {
|
|
49
|
+
google: 'Google Search Console',
|
|
50
|
+
bing: 'Bing Webmaster Tools'
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A verification token: letters, digits, `-` and `_`, at most 128.
|
|
54
|
+
*
|
|
55
|
+
* Google's tokens are URL-safe base64 (about 43 characters) and Bing's are 32
|
|
56
|
+
* hex digits; both fit, and nothing that could close an attribute does.
|
|
57
|
+
*/ export const SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
58
|
+
/** Engines in the order their tags are emitted. */ const ENGINES = [
|
|
59
|
+
'google',
|
|
60
|
+
'bing'
|
|
61
|
+
];
|
|
62
|
+
/** One attribute of a pasted tag, quoted either way or not at all. */ const attribute = (tag, name)=>{
|
|
63
|
+
var _ref, _ref1, _match_;
|
|
64
|
+
const match = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>/]+))`, 'i').exec(tag);
|
|
65
|
+
if (!match) return undefined;
|
|
66
|
+
return ((_ref = (_ref1 = (_match_ = match[1]) != null ? _match_ : match[2]) != null ? _ref1 : match[3]) != null ? _ref : '').trim();
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Reads a paste as either a bare token or the whole tag the tools hand out.
|
|
70
|
+
*
|
|
71
|
+
* A paste that looks like markup (it has a `<` or a `content=`) is read as a
|
|
72
|
+
* tag and yields its `content` value, `''` when it has none. Anything else is
|
|
73
|
+
* the token itself, trimmed. Never validates — see
|
|
74
|
+
* {@link searchEngineVerificationError}.
|
|
75
|
+
*/ export function parseSearchEngineVerificationInput(input) {
|
|
76
|
+
var _attribute;
|
|
77
|
+
var _attribute1;
|
|
78
|
+
if (typeof input !== 'string') return {
|
|
79
|
+
token: ''
|
|
80
|
+
};
|
|
81
|
+
const trimmed = input.trim();
|
|
82
|
+
if (!trimmed.includes('<') && !/\bcontent\s*=/i.test(trimmed)) {
|
|
83
|
+
return {
|
|
84
|
+
token: trimmed
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const metaName = (_attribute1 = attribute(trimmed, 'name')) == null ? void 0 : _attribute1.toLowerCase();
|
|
88
|
+
return _extends({
|
|
89
|
+
token: (_attribute = attribute(trimmed, 'content')) != null ? _attribute : ''
|
|
90
|
+
}, metaName ? {
|
|
91
|
+
metaName
|
|
92
|
+
} : {});
|
|
93
|
+
}
|
|
94
|
+
/** The token a paste stores as: the tag's `content`, or the trimmed paste. */ export function extractSearchEngineVerificationToken(input) {
|
|
95
|
+
return parseSearchEngineVerificationInput(input).token;
|
|
96
|
+
}
|
|
97
|
+
/** Whether `value` is a token the tenant will emit. */ export function isSearchEngineVerificationToken(value) {
|
|
98
|
+
return typeof value === 'string' && SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN.test(value);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The field error for a paste into `engine`'s field, or `undefined` when it
|
|
102
|
+
* is empty or reads as a valid token.
|
|
103
|
+
*
|
|
104
|
+
* A tag for the OTHER engine is named as such: a Bing tag pasted into the
|
|
105
|
+
* Google field holds a perfectly well-formed token, and storing it would make
|
|
106
|
+
* Search Console's Verify fail with nothing on this side saying why.
|
|
107
|
+
*/ export function searchEngineVerificationError(engine, input) {
|
|
108
|
+
if (input == null || typeof input === 'string' && !input.trim()) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const { token, metaName } = parseSearchEngineVerificationInput(input);
|
|
112
|
+
if (metaName && metaName !== SEARCH_ENGINE_VERIFICATION_META_NAMES[engine]) {
|
|
113
|
+
const other = ENGINES.find((candidate)=>SEARCH_ENGINE_VERIFICATION_META_NAMES[candidate] === metaName);
|
|
114
|
+
return other ? `That tag is for ${SEARCH_ENGINE_VERIFICATION_LABELS[other]} — paste it in that field instead` : `That tag isn’t a ${SEARCH_ENGINE_VERIFICATION_LABELS[engine]} verification tag`;
|
|
115
|
+
}
|
|
116
|
+
if (!isSearchEngineVerificationToken(token)) {
|
|
117
|
+
return 'Paste the verification code, or the whole meta tag — the code is ' + 'letters, numbers, - and _ only';
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The verification `<meta>` tags a published page emits: one per engine
|
|
123
|
+
* whose stored token passes the pattern, none for anything unset or invalid.
|
|
124
|
+
*/ export function searchEngineVerificationMeta(verification) {
|
|
125
|
+
if (!verification || typeof verification !== 'object') return [];
|
|
126
|
+
const stored = verification;
|
|
127
|
+
const tags = [];
|
|
128
|
+
for (const engine of ENGINES){
|
|
129
|
+
const token = stored[engine];
|
|
130
|
+
if (isSearchEngineVerificationToken(token)) {
|
|
131
|
+
tags.push({
|
|
132
|
+
name: SEARCH_ENGINE_VERIFICATION_META_NAMES[engine],
|
|
133
|
+
content: token
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return tags;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=search-engine-verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/search-engine-verification.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Search engine ownership verification by HTML meta tag (AGL-3399).\n *\n * ## Why this exists\n *\n * Google Search Console and Bing Webmaster Tools each offer three ways to\n * prove a site is yours: a DNS record, an uploaded file, or a meta tag in the\n * home page's head. A site on a platform subdomain has no DNS of its own and\n * no way to upload a file to the origin root, so the meta tag is the only\n * method it can use — and until this existed there was nowhere to put one.\n *\n * ## What is stored, and what is emitted\n *\n * The tools hand out a whole tag —\n * `<meta name=\"google-site-verification\" content=\"…\" />` — and most people\n * paste exactly that. Only the `content` value is stored\n * (`host.seo.verification`), so the tenant always writes the tag itself and\n * never echoes markup a person pasted.\n *\n * The token is validated at BOTH ends with the same pattern: in the console,\n * so a bad paste is refused at the field, and again in the tenant before it\n * reaches the head, so a value that got into the document some other way is\n * dropped rather than rendered.\n *\n * Deliberately NOT re-exported from the `@aglyn/aglyn` root barrel: that\n * barrel is part of the published-page and realm bundles, and the tenant\n * layout and the console settings card each import this by path.\n */\n\n/** The engines a site can verify with. */\nexport type SearchEngineVerificationEngine = 'google' | 'bing'\n\n/** `host.seo.verification`, one token per engine. */\nexport type SearchEngineVerification = Partial<\n Record<SearchEngineVerificationEngine, string>\n>\n\n/** The `<meta name>` each engine looks for. */\nexport const SEARCH_ENGINE_VERIFICATION_META_NAMES: Readonly<\n Record<SearchEngineVerificationEngine, string>\n> = {\n google: 'google-site-verification',\n bing: 'msvalidate.01',\n}\n\n/** What each engine is called on screen. */\nexport const SEARCH_ENGINE_VERIFICATION_LABELS: Readonly<\n Record<SearchEngineVerificationEngine, string>\n> = {\n google: 'Google Search Console',\n bing: 'Bing Webmaster Tools',\n}\n\n/**\n * A verification token: letters, digits, `-` and `_`, at most 128.\n *\n * Google's tokens are URL-safe base64 (about 43 characters) and Bing's are 32\n * hex digits; both fit, and nothing that could close an attribute does.\n */\nexport const SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,128}$/\n\n/** Engines in the order their tags are emitted. */\nconst ENGINES: readonly SearchEngineVerificationEngine[] = ['google', 'bing']\n\n/** One attribute of a pasted tag, quoted either way or not at all. */\nconst attribute = (tag: string, name: string): string | undefined => {\n const match = new RegExp(\n `\\\\b${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'>/]+))`,\n 'i',\n ).exec(tag)\n if (!match) return undefined\n return (match[1] ?? match[2] ?? match[3] ?? '').trim()\n}\n\n/** What a paste into a verification field reads as. */\nexport interface ParsedVerificationInput {\n /** The token — the `content` value of a pasted tag, or the paste itself. */\n token: string\n /** The `name` of a pasted tag, lower-cased; absent for a bare token. */\n metaName?: string\n}\n\n/**\n * Reads a paste as either a bare token or the whole tag the tools hand out.\n *\n * A paste that looks like markup (it has a `<` or a `content=`) is read as a\n * tag and yields its `content` value, `''` when it has none. Anything else is\n * the token itself, trimmed. Never validates — see\n * {@link searchEngineVerificationError}.\n */\nexport function parseSearchEngineVerificationInput(\n input: unknown,\n): ParsedVerificationInput {\n if (typeof input !== 'string') return { token: '' }\n const trimmed = input.trim()\n if (!trimmed.includes('<') && !/\\bcontent\\s*=/i.test(trimmed)) {\n return { token: trimmed }\n }\n const metaName = attribute(trimmed, 'name')?.toLowerCase()\n return {\n token: attribute(trimmed, 'content') ?? '',\n ...(metaName ? { metaName } : {}),\n }\n}\n\n/** The token a paste stores as: the tag's `content`, or the trimmed paste. */\nexport function extractSearchEngineVerificationToken(input: unknown): string {\n return parseSearchEngineVerificationInput(input).token\n}\n\n/** Whether `value` is a token the tenant will emit. */\nexport function isSearchEngineVerificationToken(\n value: unknown,\n): value is string {\n return (\n typeof value === 'string' &&\n SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN.test(value)\n )\n}\n\n/**\n * The field error for a paste into `engine`'s field, or `undefined` when it\n * is empty or reads as a valid token.\n *\n * A tag for the OTHER engine is named as such: a Bing tag pasted into the\n * Google field holds a perfectly well-formed token, and storing it would make\n * Search Console's Verify fail with nothing on this side saying why.\n */\nexport function searchEngineVerificationError(\n engine: SearchEngineVerificationEngine,\n input: unknown,\n): string | undefined {\n if (input == null || (typeof input === 'string' && !input.trim())) {\n return undefined\n }\n const { token, metaName } = parseSearchEngineVerificationInput(input)\n if (metaName && metaName !== SEARCH_ENGINE_VERIFICATION_META_NAMES[engine]) {\n const other = ENGINES.find(\n (candidate) =>\n SEARCH_ENGINE_VERIFICATION_META_NAMES[candidate] === metaName,\n )\n return other\n ? `That tag is for ${SEARCH_ENGINE_VERIFICATION_LABELS[other]} — paste it in that field instead`\n : `That tag isn’t a ${SEARCH_ENGINE_VERIFICATION_LABELS[engine]} verification tag`\n }\n if (!isSearchEngineVerificationToken(token)) {\n return (\n 'Paste the verification code, or the whole meta tag — the code is ' +\n 'letters, numbers, - and _ only'\n )\n }\n return undefined\n}\n\n/** One `<meta>` a published page emits. */\nexport interface SearchEngineVerificationMeta {\n name: string\n content: string\n}\n\n/**\n * The verification `<meta>` tags a published page emits: one per engine\n * whose stored token passes the pattern, none for anything unset or invalid.\n */\nexport function searchEngineVerificationMeta(\n verification: unknown,\n): SearchEngineVerificationMeta[] {\n if (!verification || typeof verification !== 'object') return []\n const stored = verification as Record<string, unknown>\n const tags: SearchEngineVerificationMeta[] = []\n for (const engine of ENGINES) {\n const token = stored[engine]\n if (isSearchEngineVerificationToken(token)) {\n tags.push({\n name: SEARCH_ENGINE_VERIFICATION_META_NAMES[engine],\n content: token,\n })\n }\n }\n return tags\n}\n"],"names":["SEARCH_ENGINE_VERIFICATION_META_NAMES","google","bing","SEARCH_ENGINE_VERIFICATION_LABELS","SEARCH_ENGINE_VERIFICATION_TOKEN_PATTERN","ENGINES","attribute","tag","name","match","RegExp","exec","undefined","trim","parseSearchEngineVerificationInput","input","token","trimmed","includes","test","metaName","toLowerCase","extractSearchEngineVerificationToken","isSearchEngineVerificationToken","value","searchEngineVerificationError","engine","other","find","candidate","searchEngineVerificationMeta","verification","stored","tags","push","content"],"mappings":"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"}
|
|
@@ -46,8 +46,16 @@ export declare function siteEntitySchemaType(entity: HostSeoEntity | null | unde
|
|
|
46
46
|
* the Setup form rather than a media-picker target, and re-keying it to
|
|
47
47
|
* `image` for a Person would change the publisher block on every page of
|
|
48
48
|
* every site that set one — a separate decision from this one.
|
|
49
|
+
*
|
|
50
|
+
* Given the site's origin, the node carries the standalone entity's `@id`
|
|
51
|
+
* (AGL-3398). Without it a consumer reads this name-only node as a SECOND
|
|
52
|
+
* entity: Google's Rich Results Test listed a site's LocalBusiness twice, the
|
|
53
|
+
* nested one missing telephone, address, price range and image. With it, the
|
|
54
|
+
* two are one entity described in two places.
|
|
49
55
|
*/
|
|
50
|
-
export declare function hostSeoEntityJsonLd(entity: HostSeoEntity | null | undefined
|
|
56
|
+
export declare function hostSeoEntityJsonLd(entity: HostSeoEntity | null | undefined, context?: {
|
|
57
|
+
origin?: string;
|
|
58
|
+
}): Record<string, unknown> | undefined;
|
|
51
59
|
/**
|
|
52
60
|
* The site's publisher as a STANDALONE, fully populated `schema.org` node
|
|
53
61
|
* (AGL-2716).
|
|
@@ -60,13 +60,25 @@ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType,
|
|
|
60
60
|
* the Setup form rather than a media-picker target, and re-keying it to
|
|
61
61
|
* `image` for a Person would change the publisher block on every page of
|
|
62
62
|
* every site that set one — a separate decision from this one.
|
|
63
|
-
|
|
63
|
+
*
|
|
64
|
+
* Given the site's origin, the node carries the standalone entity's `@id`
|
|
65
|
+
* (AGL-3398). Without it a consumer reads this name-only node as a SECOND
|
|
66
|
+
* entity: Google's Rich Results Test listed a site's LocalBusiness twice, the
|
|
67
|
+
* nested one missing telephone, address, price range and image. With it, the
|
|
68
|
+
* two are one entity described in two places.
|
|
69
|
+
*/ export function hostSeoEntityJsonLd(entity, context) {
|
|
70
|
+
var _ref;
|
|
71
|
+
var _context_origin;
|
|
64
72
|
const name = text(entity == null ? void 0 : entity.name, 400);
|
|
65
73
|
if (!name) return undefined;
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
const origin = (_ref = context == null ? void 0 : (_context_origin = context.origin) == null ? void 0 : _context_origin.replace(/\/+$/, '')) != null ? _ref : '';
|
|
75
|
+
return _extends({
|
|
76
|
+
'@type': siteEntitySchemaType(entity)
|
|
77
|
+
}, origin ? {
|
|
78
|
+
'@id': `${origin}/${SITE_ENTITY_FRAGMENT}`
|
|
79
|
+
} : {}, {
|
|
68
80
|
name
|
|
69
|
-
};
|
|
81
|
+
});
|
|
70
82
|
}
|
|
71
83
|
/**
|
|
72
84
|
* The site's publisher as a STANDALONE, fully populated `schema.org` node
|
|
@@ -156,7 +168,8 @@ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType,
|
|
|
156
168
|
};
|
|
157
169
|
const address = Object.entries(addressFields).filter(([, value])=>value);
|
|
158
170
|
const schemaType = siteEntitySchemaType(entity);
|
|
159
|
-
const
|
|
171
|
+
const picture = hostSeoEntityImageJsonLd(entity, context);
|
|
172
|
+
const business = schemaType !== 'Person' && schemaType !== 'Organization' ? localBusinessJsonLd(entity, telephone, picture.logo) : {};
|
|
160
173
|
return _extends({
|
|
161
174
|
'@context': 'https://schema.org',
|
|
162
175
|
'@type': schemaType
|
|
@@ -168,7 +181,7 @@ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType,
|
|
|
168
181
|
description
|
|
169
182
|
} : {}, url ? {
|
|
170
183
|
url
|
|
171
|
-
} : {},
|
|
184
|
+
} : {}, picture, sameAs.length ? {
|
|
172
185
|
sameAs
|
|
173
186
|
} : {}, contactPoint ? {
|
|
174
187
|
contactPoint
|
|
@@ -189,9 +202,12 @@ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType,
|
|
|
189
202
|
* - `areaServed` as named `City` places. A service-area business commonly has
|
|
190
203
|
* no street address to publish, and this is what says where it works.
|
|
191
204
|
* - `openingHoursSpecification` from the hours text, invalid lines dropped.
|
|
205
|
+
* - `image`, the entity logo (AGL-3398): Google recommends a picture of a
|
|
206
|
+
* LocalBusiness and reads `image`, not the Organization-only `logo`, which
|
|
207
|
+
* stays as well.
|
|
192
208
|
*
|
|
193
209
|
* Every one of them is omitted when empty, never published as `[]` or `''`.
|
|
194
|
-
*/ function localBusinessJsonLd(entity, telephone) {
|
|
210
|
+
*/ function localBusinessJsonLd(entity, telephone, logo) {
|
|
195
211
|
const areaServed = normalizeAreaServed(entity == null ? void 0 : entity.areaServed);
|
|
196
212
|
const hours = parseOpeningHours(entity == null ? void 0 : entity.openingHours);
|
|
197
213
|
const priceRange = text(entity == null ? void 0 : entity.priceRange, PRICE_RANGE_MAX_LENGTH);
|
|
@@ -214,6 +230,8 @@ import { PAYMENT_ACCEPTED_MAX_LENGTH, PRICE_RANGE_MAX_LENGTH, localBusinessType,
|
|
|
214
230
|
priceRange
|
|
215
231
|
} : {}, paymentAccepted ? {
|
|
216
232
|
paymentAccepted
|
|
233
|
+
} : {}, logo ? {
|
|
234
|
+
image: logo
|
|
217
235
|
} : {});
|
|
218
236
|
}
|
|
219
237
|
|