@aglyn/plugins-redirects 1.0.0-beta.244 → 1.0.0-beta.246
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 +2 -0
- package/package.json +11 -11
- package/src/lib/declarations.console-server.d.ts +8 -0
- package/src/lib/declarations.console-server.js +27 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/model/redirect-draft-content.d.ts +79 -0
- package/src/lib/model/redirect-draft-content.js +187 -0
- package/src/lib/model/redirect-draft-content.js.map +1 -0
- package/src/lib/model/redirects-record-routes.d.ts +30 -0
- package/src/lib/model/redirects-record-routes.js +46 -0
- package/src/lib/model/redirects-record-routes.js.map +1 -0
- package/src/lib/plugin.js +4 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/redirect-ai-capability.d.ts +47 -0
- package/src/lib/server/redirect-ai-capability.js +128 -0
- package/src/lib/server/redirect-ai-capability.js.map +1 -0
- package/src/lib/server/redirect-ai-writer.console.d.ts +18 -0
- package/src/lib/server/redirect-ai-writer.console.js +71 -0
- package/src/lib/server/redirect-ai-writer.console.js.map +1 -0
- package/src/lib/server/redirect-ai-writer.d.ts +46 -0
- package/src/lib/server/redirect-ai-writer.js +333 -0
- package/src/lib/server/redirect-ai-writer.js.map +1 -0
- package/src/lib/transfer/redirects-transfer.d.ts +34 -0
- package/src/lib/transfer/redirects-transfer.js +5 -5
- package/src/lib/transfer/redirects-transfer.js.map +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ Peer dependencies: `react`, `@mui/material`, `firebase`, and `firebase-admin` fo
|
|
|
16
16
|
|
|
17
17
|
**Console** (`registerRedirectsConsole`, the `console` registrar): a `Redirects` nav item at `/redirects`, gated by the `redirects` entitlement. The page is code-split and loads when opened.
|
|
18
18
|
|
|
19
|
+
**Console server declarations** (`registerRedirectsConsoleServerDeclarations`, the `consoleServerDeclarations` registrar): the server half of the redirects import and export, and the `redirect` writer on the resource-drafts seam with the `redirect` build operation an Aglyn AI build uses to create, update or remove a site's redirect rule. The writer makes the redirects page's checks (publishing role, the `redirects` feature, `redirectsPerHost` on a create, lockdown, validation, duplicates and loops), keeps a published page's path unless the item says it was asked for, and stamps an off-site destination with the member. A rule has no draft state, so it is live once written. The console's `redirect` record route opens the Redirects page.
|
|
20
|
+
|
|
19
21
|
**The rule model**, exported from `.`:
|
|
20
22
|
|
|
21
23
|
- `HostRedirect`: a rule's `source`, `destination`, `statusCode` (`REDIRECT_STATUS_CODES`), `kind` (`REDIRECT_KINDS`: `exact`, `prefix`, `regex`), `priority` and `enabled`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-redirects",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.246",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
36
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
37
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.246",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.246",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.246",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.246",
|
|
32
|
+
"@aglyn/shared-util-email": "1.0.0-beta.246",
|
|
33
|
+
"@aglyn/shared-util-http": "1.0.0-beta.246",
|
|
34
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.246",
|
|
35
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.246",
|
|
36
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.246",
|
|
37
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.246",
|
|
38
38
|
"@swc/helpers": "0.5.23"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import { type PluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
|
|
18
|
+
/**
|
|
19
|
+
* The `redirect` writer as boot registers it (AGL-3745): the check is pure
|
|
20
|
+
* and answers at once, from the content's own rules; the hooks that read or
|
|
21
|
+
* write load the Admin SDK half on first use, which checks again against the
|
|
22
|
+
* site.
|
|
23
|
+
*/
|
|
24
|
+
export declare const lazyRedirectAiWriter: PluginResourceDraftWriter;
|
|
17
25
|
/**
|
|
18
26
|
* The redirects plugin's CONSOLE-ONLY server declarations, named under
|
|
19
27
|
* `consoleServerDeclarations` in `plugins.config.json` and run at the
|
|
@@ -16,11 +16,33 @@
|
|
|
16
16
|
*/ // The registry's own module, not the `@aglyn/aglyn/server` barrel: boot needs
|
|
17
17
|
// one registry, not the whole server surface.
|
|
18
18
|
import { registerPluginTransferResource } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
|
|
19
|
+
import { registerPluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
|
|
19
20
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
21
|
+
import { REDIRECT_DRAFT_RESOURCE, checkRedirectDraftContent } from "./model/redirect-draft-content.js";
|
|
22
|
+
import { registerRedirectAiCapability } from "./server/redirect-ai-capability.js";
|
|
20
23
|
import { REDIRECTS_ALIAS_DICTIONARIES, REDIRECTS_DEFAULT_POLICY, REDIRECTS_MATCH_KEYS, REDIRECTS_TRANSFER_KEY, redirectValuesEqual, redirectsTransferCatalog } from "./transfer/redirects-transfer-fields.js";
|
|
21
24
|
/** The resource that reads and writes, loaded with the first import or export. */ async function load() {
|
|
22
25
|
return (await import("./transfer/redirects-transfer-console.js")).redirectsTransferResource();
|
|
23
26
|
}
|
|
27
|
+
let writerLoading = null;
|
|
28
|
+
/** The `redirect` writer over the Admin SDK, loaded on its first read or write. */ function redirectWriter() {
|
|
29
|
+
writerLoading != null ? writerLoading : writerLoading = import("./server/redirect-ai-writer.console.js").then(({ redirectAiWriter })=>redirectAiWriter()).catch((error)=>{
|
|
30
|
+
writerLoading = null;
|
|
31
|
+
throw error;
|
|
32
|
+
});
|
|
33
|
+
return writerLoading;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The `redirect` writer as boot registers it (AGL-3745): the check is pure
|
|
37
|
+
* and answers at once, from the content's own rules; the hooks that read or
|
|
38
|
+
* write load the Admin SDK half on first use, which checks again against the
|
|
39
|
+
* site.
|
|
40
|
+
*/ export const lazyRedirectAiWriter = {
|
|
41
|
+
check: (content)=>checkRedirectDraftContent(content),
|
|
42
|
+
refusal: async (context)=>(await redirectWriter()).refusal(context),
|
|
43
|
+
read: async (context)=>(await redirectWriter()).read(context),
|
|
44
|
+
write: async (request)=>(await redirectWriter()).write(request)
|
|
45
|
+
};
|
|
24
46
|
/**
|
|
25
47
|
* The redirects plugin's CONSOLE-ONLY server declarations, named under
|
|
26
48
|
* `consoleServerDeclarations` in `plugins.config.json` and run at the
|
|
@@ -64,6 +86,11 @@ import { REDIRECTS_ALIAS_DICTIONARIES, REDIRECTS_DEFAULT_POLICY, REDIRECTS_MATCH
|
|
|
64
86
|
registerPluginTransferResource(REDIRECTS_TRANSFER_KEY, resource, {
|
|
65
87
|
pluginId: BUNDLE_ID
|
|
66
88
|
});
|
|
89
|
+
registerPluginResourceDraftWriter(REDIRECT_DRAFT_RESOURCE, lazyRedirectAiWriter, {
|
|
90
|
+
pluginId: BUNDLE_ID
|
|
91
|
+
});
|
|
92
|
+
// …and the operation an AI build plans for it, which that writer executes.
|
|
93
|
+
registerRedirectAiCapability();
|
|
67
94
|
}
|
|
68
95
|
|
|
69
96
|
//# sourceMappingURL=declarations.console-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/declarations.console-server.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// The registry's own module, not the `@aglyn/aglyn/server` barrel: boot needs\n// one registry, not the whole server surface.\nimport {\n registerPluginTransferResource,\n type PluginTransferResource,\n type TransferRecordsHooks,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport {\n REDIRECTS_ALIAS_DICTIONARIES,\n REDIRECTS_DEFAULT_POLICY,\n REDIRECTS_MATCH_KEYS,\n REDIRECTS_TRANSFER_KEY,\n redirectValuesEqual,\n redirectsTransferCatalog,\n} from './transfer/redirects-transfer-fields'\n\n/** The resource that reads and writes, loaded with the first import or export. */\nasync function load(): Promise<TransferRecordsHooks> {\n return (await import('./transfer/redirects-transfer-console')).redirectsTransferResource() as TransferRecordsHooks\n}\n\n/**\n * The redirects plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * The `redirects` transfer resource: a site's redirect rules, imported and\n * exported through the data-transfer jobs, written through the same checks\n * as the redirects page and the create route (`transfer/redirects-transfer.ts`).\n * The catalog, the match keys, the alias dictionaries, the starting policy\n * and the value comparator are answered from here; every hook that reads or writes loads the Admin SDK with its first\n * call, so boot pays for none of it.\n */\nexport function registerRedirectsConsoleServerDeclarations(): void {\n const resource: PluginTransferResource = {\n fields: () => redirectsTransferCatalog(),\n matchKeys: REDIRECTS_MATCH_KEYS,\n aliases: REDIRECTS_ALIAS_DICTIONARIES,\n // A rule already on the site takes the file's values unless the person\n // chooses otherwise; the review compares values as the page stores them.\n defaultPolicy: REDIRECTS_DEFAULT_POLICY,\n valuesEqual: redirectValuesEqual,\n match: async (ctx, input) => {\n const hooks = await load()\n if (!hooks.match) throw new Error('The redirects resource has no matching.')\n return hooks.match(ctx, input)\n },\n count: async (ctx, options) => (await load()).count?.(ctx, options) ?? 0,\n readPage: async (ctx, cursor, fieldIds, options) => (await load()).readPage(ctx, cursor, fieldIds, options),\n lookup: async (ctx, requests) => (await load()).lookup(ctx, requests),\n plan: async (ctx, input) => {\n const hooks = await load()\n if (!hooks.plan) throw new Error('The redirects resource has no plan.')\n return hooks.plan(ctx, input)\n },\n apply: async (ctx, chunk, writer) => (await load()).apply(ctx, chunk, writer),\n revert: async (ctx, snapshot, decisions) => (await load()).revert(ctx, snapshot, decisions),\n }\n registerPluginTransferResource(REDIRECTS_TRANSFER_KEY, resource, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerPluginTransferResource","BUNDLE_ID","REDIRECTS_ALIAS_DICTIONARIES","REDIRECTS_DEFAULT_POLICY","REDIRECTS_MATCH_KEYS","REDIRECTS_TRANSFER_KEY","redirectValuesEqual","redirectsTransferCatalog","load","redirectsTransferResource","registerRedirectsConsoleServerDeclarations","resource","fields","matchKeys","aliases","defaultPolicy","valuesEqual","match","ctx","input","hooks","Error","count","options","readPage","cursor","fieldIds","lookup","requests","plan","apply","chunk","writer","revert","snapshot","decisions","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,8CAA8C;AAC9C,SACEA,8BAA8B,QAGzB,wDAAuD;AAC9D,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SACEC,4BAA4B,EAC5BC,wBAAwB,EACxBC,oBAAoB,EACpBC,sBAAsB,EACtBC,mBAAmB,EACnBC,wBAAwB,QACnB,0CAAsC;AAE7C,gFAAgF,GAChF,eAAeC;IACb,OAAO,AAAC,CAAA,MAAM,MAAM,CAAC,2CAAuC,EAAGC,yBAAyB;AAC1F;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC;IACd,MAAMC,WAAmC;QACvCC,QAAQ,
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/declarations.console-server.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// The registry's own module, not the `@aglyn/aglyn/server` barrel: boot needs\n// one registry, not the whole server surface.\nimport {\n registerPluginTransferResource,\n type PluginTransferResource,\n type TransferRecordsHooks,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport {\n registerPluginResourceDraftWriter,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { REDIRECT_DRAFT_RESOURCE, checkRedirectDraftContent } from './model/redirect-draft-content'\nimport { registerRedirectAiCapability } from './server/redirect-ai-capability'\nimport {\n REDIRECTS_ALIAS_DICTIONARIES,\n REDIRECTS_DEFAULT_POLICY,\n REDIRECTS_MATCH_KEYS,\n REDIRECTS_TRANSFER_KEY,\n redirectValuesEqual,\n redirectsTransferCatalog,\n} from './transfer/redirects-transfer-fields'\n\n/** The resource that reads and writes, loaded with the first import or export. */\nasync function load(): Promise<TransferRecordsHooks> {\n return (await import('./transfer/redirects-transfer-console')).redirectsTransferResource() as TransferRecordsHooks\n}\n\nlet writerLoading: Promise<PluginResourceDraftWriter> | null = null\n\n/** The `redirect` writer over the Admin SDK, loaded on its first read or write. */\nfunction redirectWriter(): Promise<PluginResourceDraftWriter> {\n writerLoading ??= import('./server/redirect-ai-writer.console')\n .then(({ redirectAiWriter }) => redirectAiWriter())\n .catch((error: unknown) => {\n writerLoading = null\n throw error\n })\n return writerLoading\n}\n\n/**\n * The `redirect` writer as boot registers it (AGL-3745): the check is pure\n * and answers at once, from the content's own rules; the hooks that read or\n * write load the Admin SDK half on first use, which checks again against the\n * site.\n */\nexport const lazyRedirectAiWriter: PluginResourceDraftWriter = {\n check: (content) => checkRedirectDraftContent(content),\n refusal: async (context) => (await redirectWriter()).refusal(context),\n read: async (context) => (await redirectWriter()).read(context),\n write: async (request) => (await redirectWriter()).write(request),\n}\n\n/**\n * The redirects plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * The `redirects` transfer resource: a site's redirect rules, imported and\n * exported through the data-transfer jobs, written through the same checks\n * as the redirects page and the create route (`transfer/redirects-transfer.ts`).\n * The catalog, the match keys, the alias dictionaries, the starting policy\n * and the value comparator are answered from here; every hook that reads or writes loads the Admin SDK with its first\n * call, so boot pays for none of it.\n */\nexport function registerRedirectsConsoleServerDeclarations(): void {\n const resource: PluginTransferResource = {\n fields: () => redirectsTransferCatalog(),\n matchKeys: REDIRECTS_MATCH_KEYS,\n aliases: REDIRECTS_ALIAS_DICTIONARIES,\n // A rule already on the site takes the file's values unless the person\n // chooses otherwise; the review compares values as the page stores them.\n defaultPolicy: REDIRECTS_DEFAULT_POLICY,\n valuesEqual: redirectValuesEqual,\n match: async (ctx, input) => {\n const hooks = await load()\n if (!hooks.match) throw new Error('The redirects resource has no matching.')\n return hooks.match(ctx, input)\n },\n count: async (ctx, options) => (await load()).count?.(ctx, options) ?? 0,\n readPage: async (ctx, cursor, fieldIds, options) => (await load()).readPage(ctx, cursor, fieldIds, options),\n lookup: async (ctx, requests) => (await load()).lookup(ctx, requests),\n plan: async (ctx, input) => {\n const hooks = await load()\n if (!hooks.plan) throw new Error('The redirects resource has no plan.')\n return hooks.plan(ctx, input)\n },\n apply: async (ctx, chunk, writer) => (await load()).apply(ctx, chunk, writer),\n revert: async (ctx, snapshot, decisions) => (await load()).revert(ctx, snapshot, decisions),\n }\n registerPluginTransferResource(REDIRECTS_TRANSFER_KEY, resource, { pluginId: BUNDLE_ID })\n registerPluginResourceDraftWriter(REDIRECT_DRAFT_RESOURCE, lazyRedirectAiWriter, { pluginId: BUNDLE_ID })\n // …and the operation an AI build plans for it, which that writer executes.\n registerRedirectAiCapability()\n}\n"],"names":["registerPluginTransferResource","registerPluginResourceDraftWriter","BUNDLE_ID","REDIRECT_DRAFT_RESOURCE","checkRedirectDraftContent","registerRedirectAiCapability","REDIRECTS_ALIAS_DICTIONARIES","REDIRECTS_DEFAULT_POLICY","REDIRECTS_MATCH_KEYS","REDIRECTS_TRANSFER_KEY","redirectValuesEqual","redirectsTransferCatalog","load","redirectsTransferResource","writerLoading","redirectWriter","then","redirectAiWriter","catch","error","lazyRedirectAiWriter","check","content","refusal","context","read","write","request","registerRedirectsConsoleServerDeclarations","resource","fields","matchKeys","aliases","defaultPolicy","valuesEqual","match","ctx","input","hooks","Error","count","options","readPage","cursor","fieldIds","lookup","requests","plan","apply","chunk","writer","revert","snapshot","decisions","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,8CAA8C;AAC9C,SACEA,8BAA8B,QAGzB,wDAAuD;AAC9D,SACEC,iCAAiC,QAE5B,qDAAoD;AAC3D,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,uBAAuB,EAAEC,yBAAyB,QAAQ,oCAAgC;AACnG,SAASC,4BAA4B,QAAQ,qCAAiC;AAC9E,SACEC,4BAA4B,EAC5BC,wBAAwB,EACxBC,oBAAoB,EACpBC,sBAAsB,EACtBC,mBAAmB,EACnBC,wBAAwB,QACnB,0CAAsC;AAE7C,gFAAgF,GAChF,eAAeC;IACb,OAAO,AAAC,CAAA,MAAM,MAAM,CAAC,2CAAuC,EAAGC,yBAAyB;AAC1F;AAEA,IAAIC,gBAA2D;AAE/D,iFAAiF,GACjF,SAASC;IACPD,wBAAAA,gBAAAA,gBAAkB,MAAM,CAAC,0CACtBE,IAAI,CAAC,CAAC,EAAEC,gBAAgB,EAAE,GAAKA,oBAC/BC,KAAK,CAAC,CAACC;QACNL,gBAAgB;QAChB,MAAMK;IACR;IACF,OAAOL;AACT;AAEA;;;;;CAKC,GACD,OAAO,MAAMM,uBAAkD;IAC7DC,OAAO,CAACC,UAAYlB,0BAA0BkB;IAC9CC,SAAS,OAAOC,UAAY,AAAC,CAAA,MAAMT,gBAAe,EAAGQ,OAAO,CAACC;IAC7DC,MAAM,OAAOD,UAAY,AAAC,CAAA,MAAMT,gBAAe,EAAGU,IAAI,CAACD;IACvDE,OAAO,OAAOC,UAAY,AAAC,CAAA,MAAMZ,gBAAe,EAAGW,KAAK,CAACC;AAC3D,EAAC;AAED;;;;;;;;;;;CAWC,GACD,OAAO,SAASC;IACd,MAAMC,WAAmC;QACvCC,QAAQ,IAAMnB;QACdoB,WAAWvB;QACXwB,SAAS1B;QACT,uEAAuE;QACvE,yEAAyE;QACzE2B,eAAe1B;QACf2B,aAAaxB;QACbyB,OAAO,OAAOC,KAAKC;YACjB,MAAMC,QAAQ,MAAM1B;YACpB,IAAI,CAAC0B,MAAMH,KAAK,EAAE,MAAM,IAAII,MAAM;YAClC,OAAOD,MAAMH,KAAK,CAACC,KAAKC;QAC1B;QACAG,OAAO,OAAOJ,KAAKK;;gBAAY,QAAC;4BAAD,SAAA,CAAC,QAAA,MAAM7B,QAAQ4B,KAAK,qBAApB,YAAC,OAAsBJ,KAAKK,2BAAY;;QACvEC,UAAU,OAAON,KAAKO,QAAQC,UAAUH,UAAY,AAAC,CAAA,MAAM7B,MAAK,EAAG8B,QAAQ,CAACN,KAAKO,QAAQC,UAAUH;QACnGI,QAAQ,OAAOT,KAAKU,WAAa,AAAC,CAAA,MAAMlC,MAAK,EAAGiC,MAAM,CAACT,KAAKU;QAC5DC,MAAM,OAAOX,KAAKC;YAChB,MAAMC,QAAQ,MAAM1B;YACpB,IAAI,CAAC0B,MAAMS,IAAI,EAAE,MAAM,IAAIR,MAAM;YACjC,OAAOD,MAAMS,IAAI,CAACX,KAAKC;QACzB;QACAW,OAAO,OAAOZ,KAAKa,OAAOC,SAAW,AAAC,CAAA,MAAMtC,MAAK,EAAGoC,KAAK,CAACZ,KAAKa,OAAOC;QACtEC,QAAQ,OAAOf,KAAKgB,UAAUC,YAAc,AAAC,CAAA,MAAMzC,MAAK,EAAGuC,MAAM,CAACf,KAAKgB,UAAUC;IACnF;IACArD,+BAA+BS,wBAAwBoB,UAAU;QAAEyB,UAAUpD;IAAU;IACvFD,kCAAkCE,yBAAyBiB,sBAAsB;QAAEkC,UAAUpD;IAAU;IACvG,2EAA2E;IAC3EG;AACF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
import { REDIRECT_KINDS, REDIRECT_STATUS_CODES } from './redirects';
|
|
18
|
+
/**
|
|
19
|
+
* A REDIRECT RULE CHANGE ANOTHER PLUGIN ASKS FOR (AGL-3745): the content the
|
|
20
|
+
* `redirect` writer takes on the core's resource-drafts seam, and its pure
|
|
21
|
+
* check. Pure and light, so the console's boot answers `check` without the
|
|
22
|
+
* Admin SDK; the writer (`server/redirect-ai-writer.ts`) reads it again and
|
|
23
|
+
* makes every check that needs the site.
|
|
24
|
+
*
|
|
25
|
+
* One change per item, named by `action`:
|
|
26
|
+
*
|
|
27
|
+
* - `create` a rule from `source` to `destination`, with the page's
|
|
28
|
+
* defaults where a field is not given: exact, 302, priority 100, on;
|
|
29
|
+
* - `update` the live rule at `source` (of `kind`, when the site has rules
|
|
30
|
+
* of two modes at that path): any of `newSource`, `destination`,
|
|
31
|
+
* `statusCode`, `priority`, `enabled`;
|
|
32
|
+
* - `remove` the live rule at `source`, as the page's Delete does.
|
|
33
|
+
*
|
|
34
|
+
* Only the rule's own fields (`HostRedirect`) are read, plus
|
|
35
|
+
* `replacesLivePage`: a from-path the site publishes a page at is refused
|
|
36
|
+
* unless the person asked for that page to be redirected. The provenance
|
|
37
|
+
* stamp on an off-site destination is never content: the writer stamps the
|
|
38
|
+
* member the change is made for.
|
|
39
|
+
*/
|
|
40
|
+
/** The resource the writer is registered under: the create route's kind. */
|
|
41
|
+
export declare const REDIRECT_DRAFT_RESOURCE = "redirect";
|
|
42
|
+
export declare const REDIRECT_DRAFT_ACTIONS: readonly ["create", "update", "remove"];
|
|
43
|
+
export type RedirectDraftAction = (typeof REDIRECT_DRAFT_ACTIONS)[number];
|
|
44
|
+
/** The longest from-path the source normalizer keeps. */
|
|
45
|
+
export declare const REDIRECT_DRAFT_SOURCE_MAX = 500;
|
|
46
|
+
/** The longest destination the destination normalizer keeps. */
|
|
47
|
+
export declare const REDIRECT_DRAFT_DESTINATION_MAX = 1000;
|
|
48
|
+
/** The content, read. Fields the item did not give are absent. */
|
|
49
|
+
export interface RedirectDraftContent {
|
|
50
|
+
action: RedirectDraftAction;
|
|
51
|
+
source: string;
|
|
52
|
+
kind?: (typeof REDIRECT_KINDS)[number];
|
|
53
|
+
destination?: string;
|
|
54
|
+
newSource?: string;
|
|
55
|
+
statusCode?: (typeof REDIRECT_STATUS_CODES)[number];
|
|
56
|
+
priority?: number;
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
replacesLivePage?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export type RedirectDraftContentRead = {
|
|
61
|
+
ok: true;
|
|
62
|
+
value: RedirectDraftContent;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
problems: string[];
|
|
66
|
+
};
|
|
67
|
+
/** The content as the writer reads it, or every problem that stops it. Pure. */
|
|
68
|
+
export declare function readRedirectDraftContent(content: Readonly<Record<string, unknown>>): RedirectDraftContentRead;
|
|
69
|
+
/**
|
|
70
|
+
* The seam's `check`: whether the content is a change the writer would try,
|
|
71
|
+
* with what it says about it. Pure. A change is live once written.
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkRedirectDraftContent(content: Readonly<Record<string, unknown>>): {
|
|
74
|
+
ok: true;
|
|
75
|
+
facts: Readonly<Record<string, unknown>>;
|
|
76
|
+
} | {
|
|
77
|
+
ok: false;
|
|
78
|
+
problems: string[];
|
|
79
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { REDIRECT_KINDS, REDIRECT_STATUS_CODES, normalizeRedirectDestination, normalizeRedirectSource, validateRedirectRule } from "./redirects.js";
|
|
18
|
+
/**
|
|
19
|
+
* A REDIRECT RULE CHANGE ANOTHER PLUGIN ASKS FOR (AGL-3745): the content the
|
|
20
|
+
* `redirect` writer takes on the core's resource-drafts seam, and its pure
|
|
21
|
+
* check. Pure and light, so the console's boot answers `check` without the
|
|
22
|
+
* Admin SDK; the writer (`server/redirect-ai-writer.ts`) reads it again and
|
|
23
|
+
* makes every check that needs the site.
|
|
24
|
+
*
|
|
25
|
+
* One change per item, named by `action`:
|
|
26
|
+
*
|
|
27
|
+
* - `create` a rule from `source` to `destination`, with the page's
|
|
28
|
+
* defaults where a field is not given: exact, 302, priority 100, on;
|
|
29
|
+
* - `update` the live rule at `source` (of `kind`, when the site has rules
|
|
30
|
+
* of two modes at that path): any of `newSource`, `destination`,
|
|
31
|
+
* `statusCode`, `priority`, `enabled`;
|
|
32
|
+
* - `remove` the live rule at `source`, as the page's Delete does.
|
|
33
|
+
*
|
|
34
|
+
* Only the rule's own fields (`HostRedirect`) are read, plus
|
|
35
|
+
* `replacesLivePage`: a from-path the site publishes a page at is refused
|
|
36
|
+
* unless the person asked for that page to be redirected. The provenance
|
|
37
|
+
* stamp on an off-site destination is never content: the writer stamps the
|
|
38
|
+
* member the change is made for.
|
|
39
|
+
*/ /** The resource the writer is registered under: the create route's kind. */ export const REDIRECT_DRAFT_RESOURCE = 'redirect';
|
|
40
|
+
export const REDIRECT_DRAFT_ACTIONS = [
|
|
41
|
+
'create',
|
|
42
|
+
'update',
|
|
43
|
+
'remove'
|
|
44
|
+
];
|
|
45
|
+
/** The longest from-path the source normalizer keeps. */ export const REDIRECT_DRAFT_SOURCE_MAX = 500;
|
|
46
|
+
/** The longest destination the destination normalizer keeps. */ export const REDIRECT_DRAFT_DESTINATION_MAX = 1000;
|
|
47
|
+
const KEYS = new Set([
|
|
48
|
+
'action',
|
|
49
|
+
'source',
|
|
50
|
+
'kind',
|
|
51
|
+
'destination',
|
|
52
|
+
'newSource',
|
|
53
|
+
'statusCode',
|
|
54
|
+
'priority',
|
|
55
|
+
'enabled',
|
|
56
|
+
'replacesLivePage'
|
|
57
|
+
]);
|
|
58
|
+
/** The fields an update may change, and a remove may not carry. */ const CHANGES = [
|
|
59
|
+
'newSource',
|
|
60
|
+
'destination',
|
|
61
|
+
'statusCode',
|
|
62
|
+
'priority',
|
|
63
|
+
'enabled'
|
|
64
|
+
];
|
|
65
|
+
function given(value) {
|
|
66
|
+
return value !== undefined && value !== null && value !== '';
|
|
67
|
+
}
|
|
68
|
+
/** A status code as a number: a build's arguments carry it as text. */ function statusCodeOf(value) {
|
|
69
|
+
const code = typeof value === 'string' && /^\d{3}$/.test(value.trim()) ? Number(value) : value;
|
|
70
|
+
return REDIRECT_STATUS_CODES.includes(code) ? code : null;
|
|
71
|
+
}
|
|
72
|
+
/** The content as the writer reads it, or every problem that stops it. Pure. */ export function readRedirectDraftContent(content) {
|
|
73
|
+
var _value_kind;
|
|
74
|
+
const problems = [];
|
|
75
|
+
for (const key of Object.keys(content)){
|
|
76
|
+
if (!KEYS.has(key)) problems.push(`"${key}" is not a redirect field`);
|
|
77
|
+
}
|
|
78
|
+
const action = content['action'];
|
|
79
|
+
if (!REDIRECT_DRAFT_ACTIONS.includes(action)) {
|
|
80
|
+
problems.push('Say whether the redirect is created, updated or removed');
|
|
81
|
+
}
|
|
82
|
+
const source = typeof content['source'] === 'string' ? content['source'].trim() : '';
|
|
83
|
+
if (!source) problems.push('Enter a site path like /old-page');
|
|
84
|
+
const value = {
|
|
85
|
+
action: action,
|
|
86
|
+
source
|
|
87
|
+
};
|
|
88
|
+
if (given(content['kind'])) {
|
|
89
|
+
if (REDIRECT_KINDS.includes(content['kind'])) {
|
|
90
|
+
value.kind = content['kind'];
|
|
91
|
+
} else problems.push('Unknown match mode');
|
|
92
|
+
}
|
|
93
|
+
for (const key of [
|
|
94
|
+
'destination',
|
|
95
|
+
'newSource'
|
|
96
|
+
]){
|
|
97
|
+
if (!given(content[key])) continue;
|
|
98
|
+
if (typeof content[key] === 'string') value[key] = content[key].trim();
|
|
99
|
+
else problems.push(`"${key}" is text`);
|
|
100
|
+
}
|
|
101
|
+
if (given(content['statusCode'])) {
|
|
102
|
+
const code = statusCodeOf(content['statusCode']);
|
|
103
|
+
if (code === null) problems.push('Status codes are 301, 302, 307 or 308');
|
|
104
|
+
else value.statusCode = code;
|
|
105
|
+
}
|
|
106
|
+
if (given(content['priority'])) {
|
|
107
|
+
const priority = content['priority'];
|
|
108
|
+
if (typeof priority === 'number' && Number.isInteger(priority)) value.priority = priority;
|
|
109
|
+
else problems.push('The priority is a whole number');
|
|
110
|
+
}
|
|
111
|
+
for (const key of [
|
|
112
|
+
'enabled',
|
|
113
|
+
'replacesLivePage'
|
|
114
|
+
]){
|
|
115
|
+
if (content[key] === undefined || content[key] === null) continue;
|
|
116
|
+
if (typeof content[key] === 'boolean') value[key] = content[key];
|
|
117
|
+
else problems.push(`"${key}" is true or false`);
|
|
118
|
+
}
|
|
119
|
+
if (value.action === 'create') {
|
|
120
|
+
if (!value.destination) problems.push('Destinations are internal paths or https:// URLs');
|
|
121
|
+
if (value.newSource !== undefined) problems.push('A new redirect has no "newSource": its from-path is "source"');
|
|
122
|
+
} else if (value.action === 'update') {
|
|
123
|
+
if (!CHANGES.some((key)=>value[key] !== undefined)) {
|
|
124
|
+
problems.push('Say what changes: the from-path, the destination, the status code, the priority or whether it is on');
|
|
125
|
+
}
|
|
126
|
+
} else if (value.action === 'remove') {
|
|
127
|
+
if (CHANGES.some((key)=>value[key] !== undefined)) {
|
|
128
|
+
problems.push('Removing a redirect names only its from-path and, where needed, its match mode');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// The page's own rules on what the content gives: a path or pattern the
|
|
132
|
+
// matcher can use, and a destination it can serve that wears no other
|
|
133
|
+
// company's address. The site's own domains are the writer's to add.
|
|
134
|
+
const kind = (_value_kind = value.kind) != null ? _value_kind : 'exact';
|
|
135
|
+
if (source && kind !== 'regex' && !normalizeRedirectSource(source)) {
|
|
136
|
+
problems.push('Enter a site path like /old-page');
|
|
137
|
+
}
|
|
138
|
+
const written = value.action === 'create' ? source : value.newSource;
|
|
139
|
+
if (written) {
|
|
140
|
+
const problem = validateRedirectRule({
|
|
141
|
+
kind,
|
|
142
|
+
source: written,
|
|
143
|
+
destination: '/'
|
|
144
|
+
});
|
|
145
|
+
if (problem) problems.push(problem);
|
|
146
|
+
}
|
|
147
|
+
if (value.destination) {
|
|
148
|
+
const problem = normalizeRedirectDestination(value.destination) ? validateRedirectRule({
|
|
149
|
+
kind: 'exact',
|
|
150
|
+
source: '/',
|
|
151
|
+
destination: value.destination
|
|
152
|
+
}) : 'Destinations are internal paths or https:// URLs';
|
|
153
|
+
if (problem) problems.push(problem);
|
|
154
|
+
}
|
|
155
|
+
if (problems.length) return {
|
|
156
|
+
ok: false,
|
|
157
|
+
problems: [
|
|
158
|
+
...new Set(problems)
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
value
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The seam's `check`: whether the content is a change the writer would try,
|
|
168
|
+
* with what it says about it. Pure. A change is live once written.
|
|
169
|
+
*/ export function checkRedirectDraftContent(content) {
|
|
170
|
+
const read = readRedirectDraftContent(content);
|
|
171
|
+
if (read.ok === false) return read;
|
|
172
|
+
const { action, source, destination, kind } = read.value;
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
facts: _extends({
|
|
176
|
+
action,
|
|
177
|
+
source
|
|
178
|
+
}, destination ? {
|
|
179
|
+
destination
|
|
180
|
+
} : {}, {
|
|
181
|
+
kind: kind != null ? kind : 'exact',
|
|
182
|
+
live: true
|
|
183
|
+
})
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=redirect-draft-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/redirects/src/lib/model/redirect-draft-content.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\nimport {\n REDIRECT_KINDS,\n REDIRECT_STATUS_CODES,\n normalizeRedirectDestination,\n normalizeRedirectSource,\n validateRedirectRule,\n} from './redirects'\n\n/**\n * A REDIRECT RULE CHANGE ANOTHER PLUGIN ASKS FOR (AGL-3745): the content the\n * `redirect` writer takes on the core's resource-drafts seam, and its pure\n * check. Pure and light, so the console's boot answers `check` without the\n * Admin SDK; the writer (`server/redirect-ai-writer.ts`) reads it again and\n * makes every check that needs the site.\n *\n * One change per item, named by `action`:\n *\n * - `create` a rule from `source` to `destination`, with the page's\n * defaults where a field is not given: exact, 302, priority 100, on;\n * - `update` the live rule at `source` (of `kind`, when the site has rules\n * of two modes at that path): any of `newSource`, `destination`,\n * `statusCode`, `priority`, `enabled`;\n * - `remove` the live rule at `source`, as the page's Delete does.\n *\n * Only the rule's own fields (`HostRedirect`) are read, plus\n * `replacesLivePage`: a from-path the site publishes a page at is refused\n * unless the person asked for that page to be redirected. The provenance\n * stamp on an off-site destination is never content: the writer stamps the\n * member the change is made for.\n */\n\n/** The resource the writer is registered under: the create route's kind. */\nexport const REDIRECT_DRAFT_RESOURCE = 'redirect'\n\nexport const REDIRECT_DRAFT_ACTIONS = ['create', 'update', 'remove'] as const\nexport type RedirectDraftAction = (typeof REDIRECT_DRAFT_ACTIONS)[number]\n\n/** The longest from-path the source normalizer keeps. */\nexport const REDIRECT_DRAFT_SOURCE_MAX = 500\n/** The longest destination the destination normalizer keeps. */\nexport const REDIRECT_DRAFT_DESTINATION_MAX = 1000\n\n/** The content, read. Fields the item did not give are absent. */\nexport interface RedirectDraftContent {\n action: RedirectDraftAction\n source: string\n kind?: (typeof REDIRECT_KINDS)[number]\n destination?: string\n newSource?: string\n statusCode?: (typeof REDIRECT_STATUS_CODES)[number]\n priority?: number\n enabled?: boolean\n replacesLivePage?: boolean\n}\n\nexport type RedirectDraftContentRead =\n | { ok: true; value: RedirectDraftContent }\n | { ok: false; problems: string[] }\n\nconst KEYS = new Set([\n 'action',\n 'source',\n 'kind',\n 'destination',\n 'newSource',\n 'statusCode',\n 'priority',\n 'enabled',\n 'replacesLivePage',\n])\n\n/** The fields an update may change, and a remove may not carry. */\nconst CHANGES = ['newSource', 'destination', 'statusCode', 'priority', 'enabled'] as const\n\nfunction given(value: unknown): boolean {\n return value !== undefined && value !== null && value !== ''\n}\n\n/** A status code as a number: a build's arguments carry it as text. */\nfunction statusCodeOf(value: unknown): number | null {\n const code = typeof value === 'string' && /^\\d{3}$/.test(value.trim()) ? Number(value) : value\n return (REDIRECT_STATUS_CODES as readonly unknown[]).includes(code) ? (code as number) : null\n}\n\n/** The content as the writer reads it, or every problem that stops it. Pure. */\nexport function readRedirectDraftContent(\n content: Readonly<Record<string, unknown>>,\n): RedirectDraftContentRead {\n const problems: string[] = []\n for (const key of Object.keys(content)) {\n if (!KEYS.has(key)) problems.push(`\"${key}\" is not a redirect field`)\n }\n const action = content['action']\n if (!(REDIRECT_DRAFT_ACTIONS as readonly unknown[]).includes(action)) {\n problems.push('Say whether the redirect is created, updated or removed')\n }\n const source = typeof content['source'] === 'string' ? content['source'].trim() : ''\n if (!source) problems.push('Enter a site path like /old-page')\n const value: RedirectDraftContent = { action: action as RedirectDraftAction, source }\n\n if (given(content['kind'])) {\n if ((REDIRECT_KINDS as readonly unknown[]).includes(content['kind'])) {\n value.kind = content['kind'] as RedirectDraftContent['kind']\n } else problems.push('Unknown match mode')\n }\n for (const key of ['destination', 'newSource'] as const) {\n if (!given(content[key])) continue\n if (typeof content[key] === 'string') value[key] = (content[key] as string).trim()\n else problems.push(`\"${key}\" is text`)\n }\n if (given(content['statusCode'])) {\n const code = statusCodeOf(content['statusCode'])\n if (code === null) problems.push('Status codes are 301, 302, 307 or 308')\n else value.statusCode = code as RedirectDraftContent['statusCode']\n }\n if (given(content['priority'])) {\n const priority = content['priority']\n if (typeof priority === 'number' && Number.isInteger(priority)) value.priority = priority\n else problems.push('The priority is a whole number')\n }\n for (const key of ['enabled', 'replacesLivePage'] as const) {\n if (content[key] === undefined || content[key] === null) continue\n if (typeof content[key] === 'boolean') value[key] = content[key] as boolean\n else problems.push(`\"${key}\" is true or false`)\n }\n\n if (value.action === 'create') {\n if (!value.destination) problems.push('Destinations are internal paths or https:// URLs')\n if (value.newSource !== undefined) problems.push('A new redirect has no \"newSource\": its from-path is \"source\"')\n } else if (value.action === 'update') {\n if (!CHANGES.some((key) => value[key] !== undefined)) {\n problems.push('Say what changes: the from-path, the destination, the status code, the priority or whether it is on')\n }\n } else if (value.action === 'remove') {\n if (CHANGES.some((key) => value[key] !== undefined)) {\n problems.push('Removing a redirect names only its from-path and, where needed, its match mode')\n }\n }\n\n // The page's own rules on what the content gives: a path or pattern the\n // matcher can use, and a destination it can serve that wears no other\n // company's address. The site's own domains are the writer's to add.\n const kind = value.kind ?? 'exact'\n if (source && kind !== 'regex' && !normalizeRedirectSource(source)) {\n problems.push('Enter a site path like /old-page')\n }\n const written = value.action === 'create' ? source : value.newSource\n if (written) {\n const problem = validateRedirectRule({ kind, source: written, destination: '/' })\n if (problem) problems.push(problem)\n }\n if (value.destination) {\n const problem = normalizeRedirectDestination(value.destination)\n ? validateRedirectRule({ kind: 'exact', source: '/', destination: value.destination })\n : 'Destinations are internal paths or https:// URLs'\n if (problem) problems.push(problem)\n }\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n return { ok: true, value }\n}\n\n/**\n * The seam's `check`: whether the content is a change the writer would try,\n * with what it says about it. Pure. A change is live once written.\n */\nexport function checkRedirectDraftContent(\n content: Readonly<Record<string, unknown>>,\n): { ok: true; facts: Readonly<Record<string, unknown>> } | { ok: false; problems: string[] } {\n const read = readRedirectDraftContent(content)\n if (read.ok === false) return read\n const { action, source, destination, kind } = read.value\n return {\n ok: true,\n facts: { action, source, ...(destination ? { destination } : {}), kind: kind ?? 'exact', live: true },\n }\n}\n"],"names":["REDIRECT_KINDS","REDIRECT_STATUS_CODES","normalizeRedirectDestination","normalizeRedirectSource","validateRedirectRule","REDIRECT_DRAFT_RESOURCE","REDIRECT_DRAFT_ACTIONS","REDIRECT_DRAFT_SOURCE_MAX","REDIRECT_DRAFT_DESTINATION_MAX","KEYS","Set","CHANGES","given","value","undefined","statusCodeOf","code","test","trim","Number","includes","readRedirectDraftContent","content","problems","key","Object","keys","has","push","action","source","kind","statusCode","priority","isInteger","destination","newSource","some","written","problem","length","ok","checkRedirectDraftContent","read","facts","live"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
* Where a redirect rule is read, published for every other surface
|
|
19
|
+
* (AGL-3745).
|
|
20
|
+
*
|
|
21
|
+
* The AI plugin's job drawer opens a rule an AI build changed by asking the
|
|
22
|
+
* record-route registry for `redirect`, and gets text instead of a link where
|
|
23
|
+
* this plugin is not loaded. A rule is a site's, so only a site has an
|
|
24
|
+
* address; it is edited on the Redirects page, so the record answers the list
|
|
25
|
+
* a person finds it on.
|
|
26
|
+
*/
|
|
27
|
+
/** The nav slug the shell resolves the Redirects page by: the nav item's `/redirects`. */
|
|
28
|
+
export declare const REDIRECTS_SLUG = "redirects";
|
|
29
|
+
/** Called from the console registrar; the owner is named so a spec calling it directly registers the same way. */
|
|
30
|
+
export declare function registerRedirectsRecordRoutes(): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
*/ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
|
|
17
|
+
import { registerPluginRecordRoute } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
18
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
19
|
+
import { REDIRECT_DRAFT_RESOURCE } from "./redirect-draft-content.js";
|
|
20
|
+
/**
|
|
21
|
+
* Where a redirect rule is read, published for every other surface
|
|
22
|
+
* (AGL-3745).
|
|
23
|
+
*
|
|
24
|
+
* The AI plugin's job drawer opens a rule an AI build changed by asking the
|
|
25
|
+
* record-route registry for `redirect`, and gets text instead of a link where
|
|
26
|
+
* this plugin is not loaded. A rule is a site's, so only a site has an
|
|
27
|
+
* address; it is edited on the Redirects page, so the record answers the list
|
|
28
|
+
* a person finds it on.
|
|
29
|
+
*/ /** The nav slug the shell resolves the Redirects page by: the nav item's `/redirects`. */ export const REDIRECTS_SLUG = 'redirects';
|
|
30
|
+
function redirectsPage(context) {
|
|
31
|
+
return context.host ? buildRoute(Route.HOST_PLUGIN, {
|
|
32
|
+
orgSlug: context.orgSlug,
|
|
33
|
+
host: context.host,
|
|
34
|
+
pluginSlug: REDIRECTS_SLUG
|
|
35
|
+
}) : null;
|
|
36
|
+
}
|
|
37
|
+
/** Called from the console registrar; the owner is named so a spec calling it directly registers the same way. */ export function registerRedirectsRecordRoutes() {
|
|
38
|
+
registerPluginRecordRoute(REDIRECT_DRAFT_RESOURCE, {
|
|
39
|
+
list: redirectsPage,
|
|
40
|
+
record: redirectsPage
|
|
41
|
+
}, {
|
|
42
|
+
pluginId: BUNDLE_ID
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=redirects-record-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/redirects/src/lib/model/redirects-record-routes.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\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n registerPluginRecordRoute,\n type PluginRecordRouteContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { REDIRECT_DRAFT_RESOURCE } from './redirect-draft-content'\n\n/**\n * Where a redirect rule is read, published for every other surface\n * (AGL-3745).\n *\n * The AI plugin's job drawer opens a rule an AI build changed by asking the\n * record-route registry for `redirect`, and gets text instead of a link where\n * this plugin is not loaded. A rule is a site's, so only a site has an\n * address; it is edited on the Redirects page, so the record answers the list\n * a person finds it on.\n */\n\n/** The nav slug the shell resolves the Redirects page by: the nav item's `/redirects`. */\nexport const REDIRECTS_SLUG = 'redirects'\n\nfunction redirectsPage(context: PluginRecordRouteContext): string | null {\n return context.host\n ? buildRoute(Route.HOST_PLUGIN, { orgSlug: context.orgSlug, host: context.host, pluginSlug: REDIRECTS_SLUG })\n : null\n}\n\n/** Called from the console registrar; the owner is named so a spec calling it directly registers the same way. */\nexport function registerRedirectsRecordRoutes(): void {\n registerPluginRecordRoute(\n REDIRECT_DRAFT_RESOURCE,\n { list: redirectsPage, record: redirectsPage },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","REDIRECT_DRAFT_RESOURCE","REDIRECTS_SLUG","redirectsPage","context","host","HOST_PLUGIN","orgSlug","pluginSlug","registerRedirectsRecordRoutes","list","record","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,yBAAyB,QAEpB,mDAAkD;AACzD,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,uBAAuB,QAAQ,8BAA0B;AAElE;;;;;;;;;CASC,GAED,wFAAwF,GACxF,OAAO,MAAMC,iBAAiB,YAAW;AAEzC,SAASC,cAAcC,OAAiC;IACtD,OAAOA,QAAQC,IAAI,GACfR,WAAWC,MAAMQ,WAAW,EAAE;QAAEC,SAASH,QAAQG,OAAO;QAAEF,MAAMD,QAAQC,IAAI;QAAEG,YAAYN;IAAe,KACzG;AACN;AAEA,gHAAgH,GAChH,OAAO,SAASO;IACdV,0BACEE,yBACA;QAAES,MAAMP;QAAeQ,QAAQR;IAAc,GAC7C;QAAES,UAAUZ;IAAU;AAE1B"}
|
package/src/lib/plugin.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerPluginTransferResourceUi } from "@aglyn/aglyn/plugin-manager/pl
|
|
|
18
18
|
import { mdiSignDirection } from "@aglyn/shared-data-mdi";
|
|
19
19
|
import { lazy } from "react";
|
|
20
20
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
21
|
+
import { registerRedirectsRecordRoutes } from "./model/redirects-record-routes.js";
|
|
21
22
|
import { REDIRECTS_SEARCH_SOURCES } from "./model/redirects-search-sources.js";
|
|
22
23
|
import { REDIRECTS_TRANSFER_KEY } from "./transfer/redirects-transfer-fields.js";
|
|
23
24
|
/** Code-split: the Redirects console page only loads when opened. */ const RedirectsConsolePage = lazy(()=>import("./components/redirects-console-page.js"));
|
|
@@ -54,6 +55,9 @@ import { REDIRECTS_TRANSFER_KEY } from "./transfer/redirects-transfer-fields.js"
|
|
|
54
55
|
}
|
|
55
56
|
]
|
|
56
57
|
});
|
|
58
|
+
// Where a rule opens, for a surface that names one — the AI plugin's job
|
|
59
|
+
// drawer opening a rule a build changed (AGL-3745).
|
|
60
|
+
registerRedirectsRecordRoutes();
|
|
57
61
|
// The client half of the `redirects` transfer resource: how the import
|
|
58
62
|
// wizard and the export dialog name it. The server half is registered from
|
|
59
63
|
// `declarations.console-server.ts`.
|
package/src/lib/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/plugin.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\nimport * as Aglyn from '@aglyn/aglyn'\nimport { registerPluginTransferResourceUi } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { mdiSignDirection } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { REDIRECTS_SEARCH_SOURCES } from './model/redirects-search-sources'\nimport { REDIRECTS_TRANSFER_KEY } from './transfer/redirects-transfer-fields'\n\n/** Code-split: the Redirects console page only loads when opened. */\nconst RedirectsConsolePage = lazy(\n () => import('./components/redirects-console-page'),\n)\n\n/**\n * Redirects feature plugin (AGL-395). Console-only — redirects render on\n * published sites through server enforcement (ISR), not a canvas component,\n * so there is no UI bundle to register in the tenant/besigner. The console\n * half declares the Redirects nav + page through the ConsoleExtension\n * registry, gated by the `redirects` entitlement.\n */\nexport function registerRedirectsConsole(): void {\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Redirects',\n featureFlag: 'redirects',\n searchSources: REDIRECTS_SEARCH_SOURCES,\n navItems: [\n {\n label: 'Redirects',\n href: '/redirects',\n // Reuse the existing release-flag nav-tab so staff-preview gating is\n // unchanged now that the tab comes from the plugin (AGL-395).\n navTabId: 'nav-tab-redirects',\n icon: { path: mdiSignDirection.path },\n header: {\n title: 'Redirects',\n icon: { path: mdiSignDirection.path },\n docsTopic: 'redirects',\n },\n Component: RedirectsConsolePage,\n },\n ],\n })\n // The client half of the `redirects` transfer resource: how the import\n // wizard and the export dialog name it. The server half is registered from\n // `declarations.console-server.ts`.\n registerPluginTransferResourceUi(\n REDIRECTS_TRANSFER_KEY,\n { label: 'Redirects', icon: { path: mdiSignDirection.path } },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["Aglyn","registerPluginTransferResourceUi","mdiSignDirection","lazy","BUNDLE_ID","REDIRECTS_SEARCH_SOURCES","REDIRECTS_TRANSFER_KEY","RedirectsConsolePage","registerRedirectsConsole","registerConsoleExtension","pluginId","displayName","featureFlag","searchSources","navItems","label","href","navTabId","icon","path","header","title","docsTopic","Component"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,gCAAgC,QAAQ,wDAAuD;AACxG,SAASC,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,wBAAwB,QAAQ,sCAAkC;AAC3E,SAASC,sBAAsB,QAAQ,0CAAsC;AAE7E,mEAAmE,GACnE,MAAMC,
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/redirects/src/lib/plugin.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\nimport * as Aglyn from '@aglyn/aglyn'\nimport { registerPluginTransferResourceUi } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { mdiSignDirection } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerRedirectsRecordRoutes } from './model/redirects-record-routes'\nimport { REDIRECTS_SEARCH_SOURCES } from './model/redirects-search-sources'\nimport { REDIRECTS_TRANSFER_KEY } from './transfer/redirects-transfer-fields'\n\n/** Code-split: the Redirects console page only loads when opened. */\nconst RedirectsConsolePage = lazy(\n () => import('./components/redirects-console-page'),\n)\n\n/**\n * Redirects feature plugin (AGL-395). Console-only — redirects render on\n * published sites through server enforcement (ISR), not a canvas component,\n * so there is no UI bundle to register in the tenant/besigner. The console\n * half declares the Redirects nav + page through the ConsoleExtension\n * registry, gated by the `redirects` entitlement.\n */\nexport function registerRedirectsConsole(): void {\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Redirects',\n featureFlag: 'redirects',\n searchSources: REDIRECTS_SEARCH_SOURCES,\n navItems: [\n {\n label: 'Redirects',\n href: '/redirects',\n // Reuse the existing release-flag nav-tab so staff-preview gating is\n // unchanged now that the tab comes from the plugin (AGL-395).\n navTabId: 'nav-tab-redirects',\n icon: { path: mdiSignDirection.path },\n header: {\n title: 'Redirects',\n icon: { path: mdiSignDirection.path },\n docsTopic: 'redirects',\n },\n Component: RedirectsConsolePage,\n },\n ],\n })\n // Where a rule opens, for a surface that names one — the AI plugin's job\n // drawer opening a rule a build changed (AGL-3745).\n registerRedirectsRecordRoutes()\n // The client half of the `redirects` transfer resource: how the import\n // wizard and the export dialog name it. The server half is registered from\n // `declarations.console-server.ts`.\n registerPluginTransferResourceUi(\n REDIRECTS_TRANSFER_KEY,\n { label: 'Redirects', icon: { path: mdiSignDirection.path } },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["Aglyn","registerPluginTransferResourceUi","mdiSignDirection","lazy","BUNDLE_ID","registerRedirectsRecordRoutes","REDIRECTS_SEARCH_SOURCES","REDIRECTS_TRANSFER_KEY","RedirectsConsolePage","registerRedirectsConsole","registerConsoleExtension","pluginId","displayName","featureFlag","searchSources","navItems","label","href","navTabId","icon","path","header","title","docsTopic","Component"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,gCAAgC,QAAQ,wDAAuD;AACxG,SAASC,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,6BAA6B,QAAQ,qCAAiC;AAC/E,SAASC,wBAAwB,QAAQ,sCAAkC;AAC3E,SAASC,sBAAsB,QAAQ,0CAAsC;AAE7E,mEAAmE,GACnE,MAAMC,uBAAuBL,KAC3B,IAAM,MAAM,CAAC;AAGf;;;;;;CAMC,GACD,OAAO,SAASM;IACdT,MAAMU,wBAAwB,CAAC;QAC7BC,UAAUP;QACVQ,aAAa;QACbC,aAAa;QACbC,eAAeR;QACfS,UAAU;YACR;gBACEC,OAAO;gBACPC,MAAM;gBACN,qEAAqE;gBACrE,8DAA8D;gBAC9DC,UAAU;gBACVC,MAAM;oBAAEC,MAAMlB,iBAAiBkB,IAAI;gBAAC;gBACpCC,QAAQ;oBACNC,OAAO;oBACPH,MAAM;wBAAEC,MAAMlB,iBAAiBkB,IAAI;oBAAC;oBACpCG,WAAW;gBACb;gBACAC,WAAWhB;YACb;SACD;IACH;IACA,yEAAyE;IACzE,oDAAoD;IACpDH;IACA,uEAAuE;IACvE,2EAA2E;IAC3E,oCAAoC;IACpCJ,iCACEM,wBACA;QAAES,OAAO;QAAaG,MAAM;YAAEC,MAAMlB,iBAAiBkB,IAAI;QAAC;IAAE,GAC5D;QAAET,UAAUP;IAAU;AAE1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
import { type PluginAiCapability, type PluginAiCapabilityArgs } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
|
|
18
|
+
/**
|
|
19
|
+
* WHAT AN AI BUILD CAN DO WITH REDIRECTS (AGL-3745): create, change and remove
|
|
20
|
+
* a site's redirect rules — "redirect /old-menu to /menu", "send /shop to my
|
|
21
|
+
* store page", "stop redirecting /promo".
|
|
22
|
+
*
|
|
23
|
+
* The capability the AI plugin's build planner offers; its item is written by
|
|
24
|
+
* this plugin's `redirect` writer (`redirect-ai-writer.ts`), so every rule is
|
|
25
|
+
* the redirects page's: the publishing role, the `redirects` feature, the
|
|
26
|
+
* `redirectsPerHost` allowance on a create, lockdown, the page's validation,
|
|
27
|
+
* duplicates and loops, and a published page's path kept unless the person
|
|
28
|
+
* asked. No model runs: the planner fills the arguments and they ARE the
|
|
29
|
+
* change, so an item costs no credits.
|
|
30
|
+
*
|
|
31
|
+
* Free has no redirects (`redirectsPerHost: 0`, no `redirects` feature), so a
|
|
32
|
+
* Free workspace's build is never offered one. No page block depends on it.
|
|
33
|
+
*
|
|
34
|
+
* A rule is live once written (`live`): a redirect has no draft state, and the
|
|
35
|
+
* console's own Save writes it live. The build's plan card is the person's
|
|
36
|
+
* agreement to it, so the intent says so.
|
|
37
|
+
*/
|
|
38
|
+
/** The operation's name in a build plan. */
|
|
39
|
+
export declare const REDIRECT_AI_OP = "redirect";
|
|
40
|
+
/** The writer's content for an item: its arguments, the status code read as the number the rule stores. */
|
|
41
|
+
export declare function redirectDraftContentFromArgs(args: PluginAiCapabilityArgs): Readonly<Record<string, unknown>>;
|
|
42
|
+
export declare const redirectAiCapability: PluginAiCapability;
|
|
43
|
+
/**
|
|
44
|
+
* Registers the capability; the console's server declarations call it beside
|
|
45
|
+
* the writer, since only the console runs AI jobs (AGL-3026). Idempotent.
|
|
46
|
+
*/
|
|
47
|
+
export declare function registerRedirectAiCapability(): void;
|