@aglyn/aglyn 1.0.0-beta.213 → 1.0.0-beta.215

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.
Files changed (40) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +2 -2
  3. package/src/lib/app-utils/docs-help.generated.js +2 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +16 -9
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/enabled-plugins-context.d.ts +8 -4
  8. package/src/lib/app-utils/enabled-plugins-context.js +9 -4
  9. package/src/lib/app-utils/enabled-plugins-context.js.map +1 -1
  10. package/src/lib/app-utils/host-naming.d.ts +2 -29
  11. package/src/lib/app-utils/host-naming.js +5 -28
  12. package/src/lib/app-utils/host-naming.js.map +1 -1
  13. package/src/lib/app-utils/media-ref.js +1 -1
  14. package/src/lib/app-utils/media-ref.js.map +1 -1
  15. package/src/lib/app-utils/notifications.d.ts +7 -0
  16. package/src/lib/app-utils/notifications.js.map +1 -1
  17. package/src/lib/app-utils/plan-entitlements.js +14 -8
  18. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  19. package/src/lib/app-utils/platform-brand.d.ts +4 -0
  20. package/src/lib/app-utils/platform-brand.js +2 -0
  21. package/src/lib/app-utils/platform-brand.js.map +1 -1
  22. package/src/lib/app-utils/plugin-bundle-checks.js +193 -15
  23. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  24. package/src/lib/app-utils/realm-elements-context.d.ts +17 -0
  25. package/src/lib/app-utils/realm-elements-context.js +35 -0
  26. package/src/lib/app-utils/realm-elements-context.js.map +1 -0
  27. package/src/lib/app-utils/tenant-apex.d.ts +45 -0
  28. package/src/lib/app-utils/tenant-apex.js +48 -0
  29. package/src/lib/app-utils/tenant-apex.js.map +1 -0
  30. package/src/lib/app-utils/wire-nodes.d.ts +64 -0
  31. package/src/lib/app-utils/wire-nodes.js +112 -0
  32. package/src/lib/app-utils/wire-nodes.js.map +1 -0
  33. package/src/lib/foundation/definitions/org-billing.types.d.ts +12 -0
  34. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  35. package/src/lib/plugin-manager/plugin-contributions.d.ts +8 -3
  36. package/src/lib/plugin-manager/plugin-contributions.js +5 -3
  37. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  38. package/src/lib/plugin-manager/realm-plugins.d.ts +70 -0
  39. package/src/lib/plugin-manager/realm-plugins.js +143 -29
  40. package/src/lib/plugin-manager/realm-plugins.js.map +1 -1
@@ -181,7 +181,11 @@ export declare const PLATFORM_POSTAL_ADDRESS: string | null;
181
181
  export interface ResolvedBrandingProfile {
182
182
  productName: string;
183
183
  logoUrl: string | null;
184
+ /** The logo for dark grounds; null means {@link logoUrl} serves both. */
185
+ logoDarkUrl: string | null;
184
186
  faviconUrl: string | null;
187
+ /** The favicon for a dark tab strip; null means {@link faviconUrl} serves both. */
188
+ faviconDarkUrl: string | null;
185
189
  primaryColor: string | null;
186
190
  supportUrl: string | null;
187
191
  /**
@@ -246,7 +246,9 @@ var _clean4;
246
246
  // else's site. The free-tier attribution badge is the one that exists
247
247
  // today, and it carried no mark at all while this was null.
248
248
  logoUrl: PLATFORM_MARK_URL,
249
+ logoDarkUrl: null,
249
250
  faviconUrl: null,
251
+ faviconDarkUrl: null,
250
252
  primaryColor: null,
251
253
  supportUrl: PLATFORM_SUPPORT_URL,
252
254
  homeUrl: PLATFORM_HOME_URL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/platform-brand.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 this deployment calls itself (AGL-2153).\n *\n * the rule for the self-host path: *\"the self hosted owner should not have\n * to edit the source to update aglyn branded items urls or personal\n * identification items, should move these to env vars\"*. AGL-2016 did that for\n * the values where getting it wrong misroutes a legal notice. This is the\n * other half — the product name itself, which was a bare `'Aglyn'` literal in\n * nine places.\n *\n * ## Why one module rather than nine reads\n *\n * The literals were not merely duplicated, they were duplicated across a\n * boundary: `PLATFORM_GENERATOR_NAME` exists here AND as a hand-copied\n * constant in `apps/tenant/middleware.ts`, because the edge bundle cannot\n * import this lib. A single source they both read is what stops the copy from\n * drifting into a deployment that half-renames itself.\n *\n * ## Why a default at all, when operator-identity.ts refuses to have one\n *\n * `operator-identity.ts` sets out the rule and it is the right one: a value\n * whose wrongness is invisible to the operator and visible only off-site must\n * not be guessed. The brand is the opposite case. An operator who does not set\n * it sees \"Aglyn\" in their own browser tab on their own first page load, in\n * their own console. That is a default they can see and correct, which is a\n * default rather than a trap — the same test AGL-2022 applied to the tenant\n * apex.\n *\n * ## Why `NEXT_PUBLIC_`, and why dot notation is load-bearing\n *\n * The brand is printed to the public — that is what it is for — so\n * `NEXT_PUBLIC_` carries no disclosure risk. Next inlines `NEXT_PUBLIC_*` into\n * the browser bundle by textually substituting `process.env.NAME`; the\n * **bracket** form is never substituted and reads `undefined` in the browser\n * (AGL-2037). Every read below is dot notation on purpose.\n *\n * Build-time consequence for self-hosters: these are baked in when the image\n * is built, so they must be set *before* `docker compose build`, not merely\n * before `up`. `docs/SELF_HOSTING.md` says so.\n */\n\n/** A non-empty trimmed string, else undefined — `' '` is a half-finished `.env`. */\nconst clean = (value: unknown): string | undefined => {\n const text = typeof value === 'string' ? value.trim() : ''\n return text.length ? text : undefined\n}\n\n/**\n * The product name, as shown to end users.\n *\n * This is the browser-tab affix, the PWA name, the WebAuthn relying-party name\n * the OS credential prompt displays, the `<meta name=\"generator\">` and\n * `x-powered-by` on published sites, and the platform default `productName` /\n * `fromName` every non-white-label org's surfaces resolve to.\n */\nexport const PLATFORM_BRAND_NAME =\n clean(process.env.NEXT_PUBLIC_PLATFORM_BRAND_NAME) ?? 'Aglyn'\n\n/**\n * The legal entity operating the software's own cloud, for copy that needs a\n * company rather than a product (`BRAND.ORG_NAME_LEGAL`).\n *\n * NOT the same thing as `operatorIdentity().name`, and the distinction matters.\n * This is a *brand* string with a visible default; that one is the identity a\n * statutory notice is addressed to and refuses to default. An operator who\n * sets only this has renamed the product, not published who they are.\n */\nexport const PLATFORM_BRAND_LEGAL_NAME =\n clean(process.env.NEXT_PUBLIC_PLATFORM_BRAND_LEGAL_NAME) ??\n `${PLATFORM_BRAND_NAME} LLC`\n\n/**\n * Where a \"need help?\" line points.\n *\n * Falls through the operator's OWN support address before it falls back to\n * ours, deliberately: an operator who followed the runbook has already set\n * `NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL`, and making them discover a second\n * variable to stop pointing their customers at Aglyn's support desk — which\n * cannot help those customers with anything — is a trap dressed as a default.\n *\n * Read here rather than imported from `operator-identity.ts` so this module\n * stays dependency-free and safe to import from anywhere, including the places\n * that cannot take the operator module's weight.\n *\n * ## Why the last fallback is the CONSOLE and not the marketing site\n *\n * It was `aglyn.com/support`, which answers 404 and has never existed\n * (AGL-3262) — MEASURED against the live site, not inferred: `/support`,\n * `/help` and `/docs` all render the not-found page and none is in the\n * sitemap. A support URL that 404s is worse than none, because it is printed\n * on a receipt beside a charge the reader is already unsure about.\n *\n * `app.aglyn.com/support` is a real page (AGL-3265): it resolves the\n * workspace from the session, so a customer with one lands on its support\n * umbrella and a customer with several picks. That is the whole reason it\n * exists — it is the one support address that needs nothing interpolated\n * into it, which is exactly what this constant is.\n *\n * The console is THIS install's (AGL-3322). `/support` is a console route, so\n * every console serves its own, and `NEXT_PUBLIC_CONSOLE_URL` is required of\n * every self-hosted install — so one that configured neither variable above\n * sends its customers to its own support page rather than to ours, where they\n * have no account. The line is in the footer of every system email the\n * platform sends. `app.aglyn.com` is left only for a build that names no\n * console at all.\n */\nexport const PLATFORM_SUPPORT_URL: string = (() => {\n const configured = clean(process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL)\n if (configured) return configured\n const operatorEmail = clean(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL)\n if (operatorEmail) return `mailto:${operatorEmail}`\n const consoleOrigin = clean(process.env.NEXT_PUBLIC_CONSOLE_URL)?.replace(\n /\\/+$/,\n '',\n )\n return `${consoleOrigin || 'https://app.aglyn.com'}/support`\n})()\n\n/** True when this deployment still answers to Aglyn's own brand. */\nexport const isAglynOperatedBrand = (): boolean =>\n PLATFORM_BRAND_NAME === 'Aglyn'\n\n/**\n * Where the platform's own front door is — the marketing home, not the help\n * desk.\n *\n * ## Why this is not `PLATFORM_SUPPORT_URL`\n *\n * The \"Made with …\" badge on a published free-tier site used to link to\n * `supportUrl`, and on this deployment `supportUrl` falls through to\n * `mailto:` + the operator support address. So the one link the platform gets\n * on every free customer's site — its only organic acquisition surface —\n * opened the visitor's mail client addressed to our support desk. A visitor\n * who liked the site and clicked to find out what built it got a blank email,\n * and support got the misdirected replies.\n *\n * They are genuinely different destinations. `supportUrl` answers \"I am a\n * customer and something is wrong\"; this answers \"what is this, and can I have\n * one\". Keeping one value for both guarantees one of them is wrong.\n *\n * ## Why it is null rather than aglyn.com off-brand\n *\n * A self-hosted deployment that renamed the product but set no home URL has\n * nowhere honest to send that visitor: \"Made with Foo\" pointing at aglyn.com\n * advertises the wrong company on Foo's customers' sites. The badge already\n * renders as a plain label when it has no destination (AGL-2428), which is the\n * correct outcome — so the Aglyn default applies only while this really is the\n * Aglyn-operated brand.\n */\nexport const PLATFORM_HOME_URL: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_HOME_URL) ??\n (isAglynOperatedBrand() ? 'https://aglyn.com' : null)\n\n/**\n * The platform's own logo MARK — the compact, square, no-wordmark form — as a\n * URL an `<img>` on a PUBLISHED SITE can load.\n *\n * Site-relative on purpose: every app that renders it serves\n * `public/_static/…` from its own origin, so one path works on\n * `{sub}.aglyn.app` and on a customer's custom domain alike without knowing\n * either. The white variant is the one that survives the badge's dark pill and\n * an org's `primaryColor` behind it.\n *\n * Null unless this is the Aglyn-operated brand, for the same reason as\n * {@link PLATFORM_HOME_URL}: a renamed deployment must not stamp our mark on\n * its customers' sites. Such an operator sets `NEXT_PUBLIC_PLATFORM_MARK_URL`\n * to their own, or gets a text-only badge.\n */\nexport const PLATFORM_MARK_URL: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_MARK_URL) ??\n (isAglynOperatedBrand()\n ? '/_static/images/brand/aglyn-logo-mark-white.svg'\n : null)\n\n/** An absolute `https://` URL, else undefined — anything else has no origin in an inbox. */\nconst httpsUrl = (value: string | undefined): string | undefined => {\n if (!value) return undefined\n try {\n return new URL(value).protocol === 'https:' ? value : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The platform's logo as its own system email draws it: the header of mail\n * sent under the platform's brand (AGL-3322).\n *\n * ABSOLUTE and https, unlike {@link PLATFORM_MARK_URL}. That mark is a\n * site-relative path because every app that draws it serves the file from its\n * own origin; an inbox has no origin, so a relative path there is a\n * broken-image box. A configured value that is not an absolute `https://` URL\n * is ignored rather than handed on to fail in somebody's inbox.\n *\n * The WORDMARK rather than the square mark: a header is read at a glance, and\n * the wordmark names the sender where the mark would need the reader to\n * already know it.\n *\n * A PNG sized for the slot (AGL-3367): 396×120, three times the 132×40 the\n * header draws it at, about 15 KB, served as a static file off aglyn.com's\n * edge. The master-resolution file behind the media CDN's 60-second cache is\n * four times the bytes and makes the header the last thing in the email to\n * appear. Not an SVG: Gmail and Outlook do not draw SVG in mail, so every\n * recipient on either would get a broken-image box where the logo belongs.\n *\n * Null unless this is the Aglyn-operated brand, for the reason\n * {@link PLATFORM_HOME_URL} is: a renamed deployment must not put our logo at\n * the top of its customers' mail. Such an operator sets\n * `NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL`, or gets its product name as the\n * header, in bold text.\n *\n * Deliberately NOT `PLATFORM_BRANDING_PROFILE.emailLogoUrl`, which stays null.\n * That field is an ORG's email logo: a white-label profile inherits it when\n * the org set none, and a send that carries one is read as an org's own mail.\n * Putting the platform's logo there would stamp it on every white-label org\n * that left the field blank.\n */\nexport const PLATFORM_EMAIL_LOGO_URL: string | null =\n httpsUrl(clean(process.env.NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL)) ??\n (isAglynOperatedBrand()\n ? 'https://aglyn.com/_static/images/brand/aglyn-email-wordmark.png'\n : null)\n\n/**\n * The postal address printed beside {@link PLATFORM_BRAND_LEGAL_NAME} at the\n * foot of the platform's own system email (AGL-3322).\n *\n * A BRAND string, beside the legal name whose line it completes, rather than\n * a field of `operatorIdentity()`, and the difference is the default. The\n * operator identity is what a statutory notice is addressed to and refuses to\n * guess; this is a line in a footer the operator sees in their own mail and\n * can correct, and for the Aglyn-operated brand the right value is known: the\n * registered agent's address the Aglyn marketing site's email footer prints.\n *\n * Null unless this is the Aglyn-operated brand: a renamed deployment must\n * never print our address as its own. Such an operator sets\n * `NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS`, or their footer carries the legal\n * name alone. Line breaks in a configured value are folded into commas,\n * because the footer prints the address on one line.\n */\nexport const PLATFORM_POSTAL_ADDRESS: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS)\n ?.split(/\\s*\\n\\s*/)\n .filter(Boolean)\n .join(', ') ??\n (isAglynOperatedBrand()\n ? 'c/o Northwest Registered Agent, LLC, 5900 Balcones Drive STE 100, Austin, TX 78731'\n : null)\n\n/**\n * A branding profile with every field present — what a branded surface\n * renders. Image/color/domain fields are nullable (Aglyn's own surfaces\n * bake those in rather than carry a URL); the remaining text fields always\n * have a value so callers never string-concatenate `undefined`.\n *\n * `supportUrl` is nullable too, and it is the one field where null carries a\n * decision rather than an absence — see {@link resolveBrandingProfile}. A\n * caller must render NOTHING for it, never a placeholder and never a\n * substitute.\n */\nexport interface ResolvedBrandingProfile {\n productName: string\n logoUrl: string | null\n faviconUrl: string | null\n primaryColor: string | null\n supportUrl: string | null\n /**\n * The brand's own FRONT DOOR — where \"Made with <product>\" sends a visitor\n * who wants to know what built the site they are looking at.\n *\n * Deliberately separate from {@link ResolvedBrandingProfile.supportUrl},\n * which the badge used to borrow: a help-desk address is the wrong answer to\n * \"what is this\", and on this deployment it resolves to a `mailto:`, so the\n * badge opened a blank email instead of a web page.\n *\n * Nullable for the same reason `supportUrl` is — a brand with nowhere to\n * send that visitor gets a plain label, never a substitute destination.\n */\n homeUrl: string | null\n fromName: string\n emailLogoUrl: string | null\n customConsoleDomain: string | null\n}\n\n/**\n * The PLATFORM (non-white-label) brand — the fallback every surface gets when\n * an org lacks the `whiteLabel` entitlement, and the gap-filler for a partial\n * agency profile.\n *\n * It lives beside the literals it is assembled from rather than beside the\n * entitlement gate that hands it out, and that placement is load-bearing\n * rather than tidying. A published tenant page renders the attribution badge\n * from this constant and reads nothing else out of `plan-entitlements.ts`;\n * defining it there put that module's whole plan, quota and entitlement table\n * — the single largest first-party module a visitor could reach — into the\n * first load of every anonymous visitor to every customer site.\n * `plan-entitlements.ts` re-exports it, so every existing caller is unchanged.\n *\n * The name says the PLATFORM's brand rather than Aglyn's because on a\n * self-host install that assertion stops being true: which platform this is,\n * is configuration. `resolveBrandingProfile` is the single resolver EVERY\n * branded surface routes through — console chrome, published-site badge and\n * title, transactional email — and this is its fallback, so all of them get a\n * self-host brand without extending the white-label machinery at all.\n */\nexport const PLATFORM_BRANDING_PROFILE: ResolvedBrandingProfile = {\n productName: PLATFORM_BRAND_NAME,\n // The platform's own surfaces still bake their logo in — this is here for\n // the surfaces that CANNOT, which is every surface rendered on somebody\n // else's site. The free-tier attribution badge is the one that exists\n // today, and it carried no mark at all while this was null.\n logoUrl: PLATFORM_MARK_URL,\n faviconUrl: null,\n primaryColor: null,\n supportUrl: PLATFORM_SUPPORT_URL,\n homeUrl: PLATFORM_HOME_URL,\n fromName: PLATFORM_BRAND_NAME,\n emailLogoUrl: null,\n customConsoleDomain: null,\n}\n"],"names":["clean","httpsUrl","value","text","trim","length","undefined","PLATFORM_BRAND_NAME","process","env","NEXT_PUBLIC_PLATFORM_BRAND_NAME","PLATFORM_BRAND_LEGAL_NAME","NEXT_PUBLIC_PLATFORM_BRAND_LEGAL_NAME","PLATFORM_SUPPORT_URL","configured","NEXT_PUBLIC_PLATFORM_SUPPORT_URL","operatorEmail","NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL","consoleOrigin","NEXT_PUBLIC_CONSOLE_URL","replace","isAglynOperatedBrand","PLATFORM_HOME_URL","NEXT_PUBLIC_PLATFORM_HOME_URL","PLATFORM_MARK_URL","NEXT_PUBLIC_PLATFORM_MARK_URL","URL","protocol","PLATFORM_EMAIL_LOGO_URL","NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL","PLATFORM_POSTAL_ADDRESS","NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS","split","filter","Boolean","join","PLATFORM_BRANDING_PROFILE","productName","logoUrl","faviconUrl","primaryColor","supportUrl","homeUrl","fromName","emailLogoUrl","customConsoleDomain"],"mappings":"IAyEEA,QAYAA,SAkFAA,SAmBAA,SAiDAC;IAuBAD;AAlQF;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCC,GAED,oFAAoF,GACpF,MAAMA,QAAQ,CAACE;IACb,MAAMC,OAAO,OAAOD,UAAU,WAAWA,MAAME,IAAI,KAAK;IACxD,OAAOD,KAAKE,MAAM,GAAGF,OAAOG;AAC9B;AAEA;;;;;;;CAOC,GACD,OAAO,MAAMC,uBACXP,SAAAA,MAAMQ,QAAQC,GAAG,CAACC,+BAA+B,aAAjDV,SAAsD,QAAO;AAE/D;;;;;;;;CAQC,GACD,OAAO,MAAMW,6BACXX,UAAAA,MAAMQ,QAAQC,GAAG,CAACG,qCAAqC,aAAvDZ,UACA,GAAGO,oBAAoB,IAAI,CAAC,CAAA;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,GACD,OAAO,MAAMM,uBAA+B,AAAC,CAAA;QAKrBb;IAJtB,MAAMc,aAAad,MAAMQ,QAAQC,GAAG,CAACM,gCAAgC;IACrE,IAAID,YAAY,OAAOA;IACvB,MAAME,gBAAgBhB,MAAMQ,QAAQC,GAAG,CAACQ,kCAAkC;IAC1E,IAAID,eAAe,OAAO,CAAC,OAAO,EAAEA,eAAe;IACnD,MAAME,iBAAgBlB,SAAAA,MAAMQ,QAAQC,GAAG,CAACU,uBAAuB,sBAAzCnB,OAA4CoB,OAAO,CACvE,QACA;IAEF,OAAO,GAAGF,iBAAiB,wBAAwB,QAAQ,CAAC;AAC9D,CAAA,IAAI;AAEJ,kEAAkE,GAClE,OAAO,MAAMG,uBAAuB,IAClCd,wBAAwB,QAAO;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,MAAMe,qBACXtB,UAAAA,MAAMQ,QAAQC,GAAG,CAACc,6BAA6B,aAA/CvB,UACCqB,yBAAyB,sBAAsB,KAAK;AAEvD;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMG,qBACXxB,UAAAA,MAAMQ,QAAQC,GAAG,CAACgB,6BAA6B,aAA/CzB,UACCqB,yBACG,oDACA,KAAK;AAEX,0FAA0F,GAC1F,MAAMpB,WAAW,CAACC;IAChB,IAAI,CAACA,OAAO,OAAOI;IACnB,IAAI;QACF,OAAO,IAAIoB,IAAIxB,OAAOyB,QAAQ,KAAK,WAAWzB,QAAQI;IACxD,EAAE,eAAM;QACN,OAAOA;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GACD,OAAO,MAAMsB,2BACX3B,YAAAA,SAASD,MAAMQ,QAAQC,GAAG,CAACoB,mCAAmC,cAA9D5B,YACCoB,yBACG,oEACA,KAAK;AAEX;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMS,mCACX9B,UAAAA,MAAMQ,QAAQC,GAAG,CAACsB,mCAAmC,sBAArD/B,QACIgC,KAAK,CAAC,YACPC,MAAM,CAACC,SACPC,IAAI,CAAC,wBACPd,yBACG,uFACA,KAAK;AAqCX;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMe,4BAAqD;IAChEC,aAAa9B;IACb,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,4DAA4D;IAC5D+B,SAASd;IACTe,YAAY;IACZC,cAAc;IACdC,YAAY5B;IACZ6B,SAASpB;IACTqB,UAAUpC;IACVqC,cAAc;IACdC,qBAAqB;AACvB,EAAC"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/platform-brand.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 this deployment calls itself (AGL-2153).\n *\n * the rule for the self-host path: *\"the self hosted owner should not have\n * to edit the source to update aglyn branded items urls or personal\n * identification items, should move these to env vars\"*. AGL-2016 did that for\n * the values where getting it wrong misroutes a legal notice. This is the\n * other half — the product name itself, which was a bare `'Aglyn'` literal in\n * nine places.\n *\n * ## Why one module rather than nine reads\n *\n * The literals were not merely duplicated, they were duplicated across a\n * boundary: `PLATFORM_GENERATOR_NAME` exists here AND as a hand-copied\n * constant in `apps/tenant/middleware.ts`, because the edge bundle cannot\n * import this lib. A single source they both read is what stops the copy from\n * drifting into a deployment that half-renames itself.\n *\n * ## Why a default at all, when operator-identity.ts refuses to have one\n *\n * `operator-identity.ts` sets out the rule and it is the right one: a value\n * whose wrongness is invisible to the operator and visible only off-site must\n * not be guessed. The brand is the opposite case. An operator who does not set\n * it sees \"Aglyn\" in their own browser tab on their own first page load, in\n * their own console. That is a default they can see and correct, which is a\n * default rather than a trap — the same test AGL-2022 applied to the tenant\n * apex.\n *\n * ## Why `NEXT_PUBLIC_`, and why dot notation is load-bearing\n *\n * The brand is printed to the public — that is what it is for — so\n * `NEXT_PUBLIC_` carries no disclosure risk. Next inlines `NEXT_PUBLIC_*` into\n * the browser bundle by textually substituting `process.env.NAME`; the\n * **bracket** form is never substituted and reads `undefined` in the browser\n * (AGL-2037). Every read below is dot notation on purpose.\n *\n * Build-time consequence for self-hosters: these are baked in when the image\n * is built, so they must be set *before* `docker compose build`, not merely\n * before `up`. `docs/SELF_HOSTING.md` says so.\n */\n\n/** A non-empty trimmed string, else undefined — `' '` is a half-finished `.env`. */\nconst clean = (value: unknown): string | undefined => {\n const text = typeof value === 'string' ? value.trim() : ''\n return text.length ? text : undefined\n}\n\n/**\n * The product name, as shown to end users.\n *\n * This is the browser-tab affix, the PWA name, the WebAuthn relying-party name\n * the OS credential prompt displays, the `<meta name=\"generator\">` and\n * `x-powered-by` on published sites, and the platform default `productName` /\n * `fromName` every non-white-label org's surfaces resolve to.\n */\nexport const PLATFORM_BRAND_NAME =\n clean(process.env.NEXT_PUBLIC_PLATFORM_BRAND_NAME) ?? 'Aglyn'\n\n/**\n * The legal entity operating the software's own cloud, for copy that needs a\n * company rather than a product (`BRAND.ORG_NAME_LEGAL`).\n *\n * NOT the same thing as `operatorIdentity().name`, and the distinction matters.\n * This is a *brand* string with a visible default; that one is the identity a\n * statutory notice is addressed to and refuses to default. An operator who\n * sets only this has renamed the product, not published who they are.\n */\nexport const PLATFORM_BRAND_LEGAL_NAME =\n clean(process.env.NEXT_PUBLIC_PLATFORM_BRAND_LEGAL_NAME) ??\n `${PLATFORM_BRAND_NAME} LLC`\n\n/**\n * Where a \"need help?\" line points.\n *\n * Falls through the operator's OWN support address before it falls back to\n * ours, deliberately: an operator who followed the runbook has already set\n * `NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL`, and making them discover a second\n * variable to stop pointing their customers at Aglyn's support desk — which\n * cannot help those customers with anything — is a trap dressed as a default.\n *\n * Read here rather than imported from `operator-identity.ts` so this module\n * stays dependency-free and safe to import from anywhere, including the places\n * that cannot take the operator module's weight.\n *\n * ## Why the last fallback is the CONSOLE and not the marketing site\n *\n * It was `aglyn.com/support`, which answers 404 and has never existed\n * (AGL-3262) — MEASURED against the live site, not inferred: `/support`,\n * `/help` and `/docs` all render the not-found page and none is in the\n * sitemap. A support URL that 404s is worse than none, because it is printed\n * on a receipt beside a charge the reader is already unsure about.\n *\n * `app.aglyn.com/support` is a real page (AGL-3265): it resolves the\n * workspace from the session, so a customer with one lands on its support\n * umbrella and a customer with several picks. That is the whole reason it\n * exists — it is the one support address that needs nothing interpolated\n * into it, which is exactly what this constant is.\n *\n * The console is THIS install's (AGL-3322). `/support` is a console route, so\n * every console serves its own, and `NEXT_PUBLIC_CONSOLE_URL` is required of\n * every self-hosted install — so one that configured neither variable above\n * sends its customers to its own support page rather than to ours, where they\n * have no account. The line is in the footer of every system email the\n * platform sends. `app.aglyn.com` is left only for a build that names no\n * console at all.\n */\nexport const PLATFORM_SUPPORT_URL: string = (() => {\n const configured = clean(process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL)\n if (configured) return configured\n const operatorEmail = clean(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL)\n if (operatorEmail) return `mailto:${operatorEmail}`\n const consoleOrigin = clean(process.env.NEXT_PUBLIC_CONSOLE_URL)?.replace(\n /\\/+$/,\n '',\n )\n return `${consoleOrigin || 'https://app.aglyn.com'}/support`\n})()\n\n/** True when this deployment still answers to Aglyn's own brand. */\nexport const isAglynOperatedBrand = (): boolean =>\n PLATFORM_BRAND_NAME === 'Aglyn'\n\n/**\n * Where the platform's own front door is — the marketing home, not the help\n * desk.\n *\n * ## Why this is not `PLATFORM_SUPPORT_URL`\n *\n * The \"Made with …\" badge on a published free-tier site used to link to\n * `supportUrl`, and on this deployment `supportUrl` falls through to\n * `mailto:` + the operator support address. So the one link the platform gets\n * on every free customer's site — its only organic acquisition surface —\n * opened the visitor's mail client addressed to our support desk. A visitor\n * who liked the site and clicked to find out what built it got a blank email,\n * and support got the misdirected replies.\n *\n * They are genuinely different destinations. `supportUrl` answers \"I am a\n * customer and something is wrong\"; this answers \"what is this, and can I have\n * one\". Keeping one value for both guarantees one of them is wrong.\n *\n * ## Why it is null rather than aglyn.com off-brand\n *\n * A self-hosted deployment that renamed the product but set no home URL has\n * nowhere honest to send that visitor: \"Made with Foo\" pointing at aglyn.com\n * advertises the wrong company on Foo's customers' sites. The badge already\n * renders as a plain label when it has no destination (AGL-2428), which is the\n * correct outcome — so the Aglyn default applies only while this really is the\n * Aglyn-operated brand.\n */\nexport const PLATFORM_HOME_URL: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_HOME_URL) ??\n (isAglynOperatedBrand() ? 'https://aglyn.com' : null)\n\n/**\n * The platform's own logo MARK — the compact, square, no-wordmark form — as a\n * URL an `<img>` on a PUBLISHED SITE can load.\n *\n * Site-relative on purpose: every app that renders it serves\n * `public/_static/…` from its own origin, so one path works on\n * `{sub}.aglyn.app` and on a customer's custom domain alike without knowing\n * either. The white variant is the one that survives the badge's dark pill and\n * an org's `primaryColor` behind it.\n *\n * Null unless this is the Aglyn-operated brand, for the same reason as\n * {@link PLATFORM_HOME_URL}: a renamed deployment must not stamp our mark on\n * its customers' sites. Such an operator sets `NEXT_PUBLIC_PLATFORM_MARK_URL`\n * to their own, or gets a text-only badge.\n */\nexport const PLATFORM_MARK_URL: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_MARK_URL) ??\n (isAglynOperatedBrand()\n ? '/_static/images/brand/aglyn-logo-mark-white.svg'\n : null)\n\n/** An absolute `https://` URL, else undefined — anything else has no origin in an inbox. */\nconst httpsUrl = (value: string | undefined): string | undefined => {\n if (!value) return undefined\n try {\n return new URL(value).protocol === 'https:' ? value : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The platform's logo as its own system email draws it: the header of mail\n * sent under the platform's brand (AGL-3322).\n *\n * ABSOLUTE and https, unlike {@link PLATFORM_MARK_URL}. That mark is a\n * site-relative path because every app that draws it serves the file from its\n * own origin; an inbox has no origin, so a relative path there is a\n * broken-image box. A configured value that is not an absolute `https://` URL\n * is ignored rather than handed on to fail in somebody's inbox.\n *\n * The WORDMARK rather than the square mark: a header is read at a glance, and\n * the wordmark names the sender where the mark would need the reader to\n * already know it.\n *\n * A PNG sized for the slot (AGL-3367): 396×120, three times the 132×40 the\n * header draws it at, about 15 KB, served as a static file off aglyn.com's\n * edge. The master-resolution file behind the media CDN's 60-second cache is\n * four times the bytes and makes the header the last thing in the email to\n * appear. Not an SVG: Gmail and Outlook do not draw SVG in mail, so every\n * recipient on either would get a broken-image box where the logo belongs.\n *\n * Null unless this is the Aglyn-operated brand, for the reason\n * {@link PLATFORM_HOME_URL} is: a renamed deployment must not put our logo at\n * the top of its customers' mail. Such an operator sets\n * `NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL`, or gets its product name as the\n * header, in bold text.\n *\n * Deliberately NOT `PLATFORM_BRANDING_PROFILE.emailLogoUrl`, which stays null.\n * That field is an ORG's email logo: a white-label profile inherits it when\n * the org set none, and a send that carries one is read as an org's own mail.\n * Putting the platform's logo there would stamp it on every white-label org\n * that left the field blank.\n */\nexport const PLATFORM_EMAIL_LOGO_URL: string | null =\n httpsUrl(clean(process.env.NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL)) ??\n (isAglynOperatedBrand()\n ? 'https://aglyn.com/_static/images/brand/aglyn-email-wordmark.png'\n : null)\n\n/**\n * The postal address printed beside {@link PLATFORM_BRAND_LEGAL_NAME} at the\n * foot of the platform's own system email (AGL-3322).\n *\n * A BRAND string, beside the legal name whose line it completes, rather than\n * a field of `operatorIdentity()`, and the difference is the default. The\n * operator identity is what a statutory notice is addressed to and refuses to\n * guess; this is a line in a footer the operator sees in their own mail and\n * can correct, and for the Aglyn-operated brand the right value is known: the\n * registered agent's address the Aglyn marketing site's email footer prints.\n *\n * Null unless this is the Aglyn-operated brand: a renamed deployment must\n * never print our address as its own. Such an operator sets\n * `NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS`, or their footer carries the legal\n * name alone. Line breaks in a configured value are folded into commas,\n * because the footer prints the address on one line.\n */\nexport const PLATFORM_POSTAL_ADDRESS: string | null =\n clean(process.env.NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS)\n ?.split(/\\s*\\n\\s*/)\n .filter(Boolean)\n .join(', ') ??\n (isAglynOperatedBrand()\n ? 'c/o Northwest Registered Agent, LLC, 5900 Balcones Drive STE 100, Austin, TX 78731'\n : null)\n\n/**\n * A branding profile with every field present — what a branded surface\n * renders. Image/color/domain fields are nullable (Aglyn's own surfaces\n * bake those in rather than carry a URL); the remaining text fields always\n * have a value so callers never string-concatenate `undefined`.\n *\n * `supportUrl` is nullable too, and it is the one field where null carries a\n * decision rather than an absence — see {@link resolveBrandingProfile}. A\n * caller must render NOTHING for it, never a placeholder and never a\n * substitute.\n */\nexport interface ResolvedBrandingProfile {\n productName: string\n logoUrl: string | null\n /** The logo for dark grounds; null means {@link logoUrl} serves both. */\n logoDarkUrl: string | null\n faviconUrl: string | null\n /** The favicon for a dark tab strip; null means {@link faviconUrl} serves both. */\n faviconDarkUrl: string | null\n primaryColor: string | null\n supportUrl: string | null\n /**\n * The brand's own FRONT DOOR — where \"Made with <product>\" sends a visitor\n * who wants to know what built the site they are looking at.\n *\n * Deliberately separate from {@link ResolvedBrandingProfile.supportUrl},\n * which the badge used to borrow: a help-desk address is the wrong answer to\n * \"what is this\", and on this deployment it resolves to a `mailto:`, so the\n * badge opened a blank email instead of a web page.\n *\n * Nullable for the same reason `supportUrl` is — a brand with nowhere to\n * send that visitor gets a plain label, never a substitute destination.\n */\n homeUrl: string | null\n fromName: string\n emailLogoUrl: string | null\n customConsoleDomain: string | null\n}\n\n/**\n * The PLATFORM (non-white-label) brand — the fallback every surface gets when\n * an org lacks the `whiteLabel` entitlement, and the gap-filler for a partial\n * agency profile.\n *\n * It lives beside the literals it is assembled from rather than beside the\n * entitlement gate that hands it out, and that placement is load-bearing\n * rather than tidying. A published tenant page renders the attribution badge\n * from this constant and reads nothing else out of `plan-entitlements.ts`;\n * defining it there put that module's whole plan, quota and entitlement table\n * — the single largest first-party module a visitor could reach — into the\n * first load of every anonymous visitor to every customer site.\n * `plan-entitlements.ts` re-exports it, so every existing caller is unchanged.\n *\n * The name says the PLATFORM's brand rather than Aglyn's because on a\n * self-host install that assertion stops being true: which platform this is,\n * is configuration. `resolveBrandingProfile` is the single resolver EVERY\n * branded surface routes through — console chrome, published-site badge and\n * title, transactional email — and this is its fallback, so all of them get a\n * self-host brand without extending the white-label machinery at all.\n */\nexport const PLATFORM_BRANDING_PROFILE: ResolvedBrandingProfile = {\n productName: PLATFORM_BRAND_NAME,\n // The platform's own surfaces still bake their logo in — this is here for\n // the surfaces that CANNOT, which is every surface rendered on somebody\n // else's site. The free-tier attribution badge is the one that exists\n // today, and it carried no mark at all while this was null.\n logoUrl: PLATFORM_MARK_URL,\n logoDarkUrl: null,\n faviconUrl: null,\n faviconDarkUrl: null,\n primaryColor: null,\n supportUrl: PLATFORM_SUPPORT_URL,\n homeUrl: PLATFORM_HOME_URL,\n fromName: PLATFORM_BRAND_NAME,\n emailLogoUrl: null,\n customConsoleDomain: null,\n}\n"],"names":["clean","httpsUrl","value","text","trim","length","undefined","PLATFORM_BRAND_NAME","process","env","NEXT_PUBLIC_PLATFORM_BRAND_NAME","PLATFORM_BRAND_LEGAL_NAME","NEXT_PUBLIC_PLATFORM_BRAND_LEGAL_NAME","PLATFORM_SUPPORT_URL","configured","NEXT_PUBLIC_PLATFORM_SUPPORT_URL","operatorEmail","NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL","consoleOrigin","NEXT_PUBLIC_CONSOLE_URL","replace","isAglynOperatedBrand","PLATFORM_HOME_URL","NEXT_PUBLIC_PLATFORM_HOME_URL","PLATFORM_MARK_URL","NEXT_PUBLIC_PLATFORM_MARK_URL","URL","protocol","PLATFORM_EMAIL_LOGO_URL","NEXT_PUBLIC_PLATFORM_EMAIL_LOGO_URL","PLATFORM_POSTAL_ADDRESS","NEXT_PUBLIC_PLATFORM_POSTAL_ADDRESS","split","filter","Boolean","join","PLATFORM_BRANDING_PROFILE","productName","logoUrl","logoDarkUrl","faviconUrl","faviconDarkUrl","primaryColor","supportUrl","homeUrl","fromName","emailLogoUrl","customConsoleDomain"],"mappings":"IAyEEA,QAYAA,SAkFAA,SAmBAA,SAiDAC;IAuBAD;AAlQF;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCC,GAED,oFAAoF,GACpF,MAAMA,QAAQ,CAACE;IACb,MAAMC,OAAO,OAAOD,UAAU,WAAWA,MAAME,IAAI,KAAK;IACxD,OAAOD,KAAKE,MAAM,GAAGF,OAAOG;AAC9B;AAEA;;;;;;;CAOC,GACD,OAAO,MAAMC,uBACXP,SAAAA,MAAMQ,QAAQC,GAAG,CAACC,+BAA+B,aAAjDV,SAAsD,QAAO;AAE/D;;;;;;;;CAQC,GACD,OAAO,MAAMW,6BACXX,UAAAA,MAAMQ,QAAQC,GAAG,CAACG,qCAAqC,aAAvDZ,UACA,GAAGO,oBAAoB,IAAI,CAAC,CAAA;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,GACD,OAAO,MAAMM,uBAA+B,AAAC,CAAA;QAKrBb;IAJtB,MAAMc,aAAad,MAAMQ,QAAQC,GAAG,CAACM,gCAAgC;IACrE,IAAID,YAAY,OAAOA;IACvB,MAAME,gBAAgBhB,MAAMQ,QAAQC,GAAG,CAACQ,kCAAkC;IAC1E,IAAID,eAAe,OAAO,CAAC,OAAO,EAAEA,eAAe;IACnD,MAAME,iBAAgBlB,SAAAA,MAAMQ,QAAQC,GAAG,CAACU,uBAAuB,sBAAzCnB,OAA4CoB,OAAO,CACvE,QACA;IAEF,OAAO,GAAGF,iBAAiB,wBAAwB,QAAQ,CAAC;AAC9D,CAAA,IAAI;AAEJ,kEAAkE,GAClE,OAAO,MAAMG,uBAAuB,IAClCd,wBAAwB,QAAO;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,MAAMe,qBACXtB,UAAAA,MAAMQ,QAAQC,GAAG,CAACc,6BAA6B,aAA/CvB,UACCqB,yBAAyB,sBAAsB,KAAK;AAEvD;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMG,qBACXxB,UAAAA,MAAMQ,QAAQC,GAAG,CAACgB,6BAA6B,aAA/CzB,UACCqB,yBACG,oDACA,KAAK;AAEX,0FAA0F,GAC1F,MAAMpB,WAAW,CAACC;IAChB,IAAI,CAACA,OAAO,OAAOI;IACnB,IAAI;QACF,OAAO,IAAIoB,IAAIxB,OAAOyB,QAAQ,KAAK,WAAWzB,QAAQI;IACxD,EAAE,eAAM;QACN,OAAOA;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GACD,OAAO,MAAMsB,2BACX3B,YAAAA,SAASD,MAAMQ,QAAQC,GAAG,CAACoB,mCAAmC,cAA9D5B,YACCoB,yBACG,oEACA,KAAK;AAEX;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMS,mCACX9B,UAAAA,MAAMQ,QAAQC,GAAG,CAACsB,mCAAmC,sBAArD/B,QACIgC,KAAK,CAAC,YACPC,MAAM,CAACC,SACPC,IAAI,CAAC,wBACPd,yBACG,uFACA,KAAK;AAyCX;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMe,4BAAqD;IAChEC,aAAa9B;IACb,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,4DAA4D;IAC5D+B,SAASd;IACTe,aAAa;IACbC,YAAY;IACZC,gBAAgB;IAChBC,cAAc;IACdC,YAAY9B;IACZ+B,SAAStB;IACTuB,UAAUtC;IACVuC,cAAc;IACdC,qBAAqB;AACvB,EAAC"}
@@ -643,6 +643,153 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
643
643
  }
644
644
  }
645
645
  }
646
+ const FUNCTION_TYPES = new Set([
647
+ 'FunctionDeclaration',
648
+ 'FunctionExpression',
649
+ 'ArrowFunctionExpression'
650
+ ]);
651
+ /** Nodes that open a scope a name can be declared in. */ const SCOPE_TYPES = new Set([
652
+ 'Program',
653
+ ...FUNCTION_TYPES,
654
+ 'BlockStatement',
655
+ 'StaticBlock',
656
+ 'SwitchStatement',
657
+ 'CatchClause',
658
+ 'ForStatement',
659
+ 'ForInStatement',
660
+ 'ForOfStatement',
661
+ 'ClassExpression'
662
+ ]);
663
+ /** The names a binding pattern declares: `a`, `{ a, b: c }`, `[a, ...b]`, `a = 1`. */ function patternNames(pattern, into) {
664
+ if (!pattern) return;
665
+ switch(pattern.type){
666
+ case 'Identifier':
667
+ into.add(pattern['name']);
668
+ return;
669
+ case 'AssignmentPattern':
670
+ return patternNames(pattern['left'], into);
671
+ case 'RestElement':
672
+ return patternNames(pattern['argument'], into);
673
+ case 'ArrayPattern':
674
+ var _pattern_elements;
675
+ for (const element of (_pattern_elements = pattern['elements']) != null ? _pattern_elements : [])patternNames(element, into);
676
+ return;
677
+ case 'ObjectPattern':
678
+ var _pattern_properties;
679
+ for (const property of (_pattern_properties = pattern['properties']) != null ? _pattern_properties : []){
680
+ patternNames(property.type === 'RestElement' ? property : property['value'], into);
681
+ }
682
+ return;
683
+ }
684
+ }
685
+ /** The declarations a statement list makes itself: `let`/`const`/`class`/`function`. */ function lexicalNames(statements, into) {
686
+ for (const statement of statements){
687
+ if ((statement == null ? void 0 : statement.type) === 'VariableDeclaration' && statement['kind'] !== 'var') {
688
+ var _statement_declarations;
689
+ for (const declarator of (_statement_declarations = statement['declarations']) != null ? _statement_declarations : []){
690
+ patternNames(declarator['id'], into);
691
+ }
692
+ }
693
+ if (((statement == null ? void 0 : statement.type) === 'FunctionDeclaration' || (statement == null ? void 0 : statement.type) === 'ClassDeclaration') && statement['id']) {
694
+ into.add(statement['id']['name']);
695
+ }
696
+ }
697
+ }
698
+ /** Every `var` a function body declares, however deep, short of a nested function. */ function varNames(node, into) {
699
+ if (!node || typeof node !== 'object') return;
700
+ if (FUNCTION_TYPES.has(node.type)) return;
701
+ if (node.type === 'VariableDeclaration' && node['kind'] === 'var') {
702
+ var _node_declarations;
703
+ for (const declarator of (_node_declarations = node['declarations']) != null ? _node_declarations : []){
704
+ patternNames(declarator['id'], into);
705
+ }
706
+ }
707
+ for (const [field, value] of Object.entries(node)){
708
+ if (field === 'type' || !value || typeof value !== 'object') continue;
709
+ if (Array.isArray(value)) for (const child of value)varNames(child, into);
710
+ else if ('type' in value) varNames(value, into);
711
+ }
712
+ }
713
+ /** The names a scope node declares for its own body. */ function declaredNames(scope) {
714
+ const names = new Set();
715
+ if (FUNCTION_TYPES.has(scope.type)) {
716
+ var _scope_params;
717
+ for (const param of (_scope_params = scope['params']) != null ? _scope_params : [])patternNames(param, names);
718
+ if (scope.type === 'FunctionExpression' && scope['id']) {
719
+ names.add(scope['id']['name']);
720
+ }
721
+ const body = scope['body'];
722
+ if ((body == null ? void 0 : body.type) === 'BlockStatement') {
723
+ var _body_body, _body_body1;
724
+ for (const statement of (_body_body = body['body']) != null ? _body_body : [])varNames(statement, names);
725
+ lexicalNames((_body_body1 = body['body']) != null ? _body_body1 : [], names);
726
+ }
727
+ return names;
728
+ }
729
+ switch(scope.type){
730
+ case 'Program':
731
+ case 'BlockStatement':
732
+ case 'StaticBlock':
733
+ var _scope_body;
734
+ lexicalNames((_scope_body = scope['body']) != null ? _scope_body : [], names);
735
+ break;
736
+ case 'SwitchStatement':
737
+ var _scope_cases;
738
+ for (const branch of (_scope_cases = scope['cases']) != null ? _scope_cases : []){
739
+ var _branch_consequent;
740
+ lexicalNames((_branch_consequent = branch['consequent']) != null ? _branch_consequent : [], names);
741
+ }
742
+ break;
743
+ case 'CatchClause':
744
+ patternNames(scope['param'], names);
745
+ break;
746
+ case 'ForStatement':
747
+ case 'ForInStatement':
748
+ case 'ForOfStatement':
749
+ {
750
+ var _scope_init;
751
+ const head = (_scope_init = scope['init']) != null ? _scope_init : scope['left'];
752
+ if ((head == null ? void 0 : head.type) === 'VariableDeclaration' && head['kind'] !== 'var') lexicalNames([
753
+ head
754
+ ], names);
755
+ break;
756
+ }
757
+ case 'ClassExpression':
758
+ if (scope['id']) names.add(scope['id']['name']);
759
+ break;
760
+ }
761
+ return names;
762
+ }
763
+ /**
764
+ * Whether an identifier refers to a variable, rather than naming a property,
765
+ * a key or a label.
766
+ */ function isReference(node, parent) {
767
+ if (!parent) return true;
768
+ switch(parent.type){
769
+ case 'MemberExpression':
770
+ return parent['object'] === node || parent['computed'] === true;
771
+ case 'Property':
772
+ if (parent['key'] === node && parent['computed'] !== true) {
773
+ return parent['shorthand'] === true && parent['value'] === node;
774
+ }
775
+ return true;
776
+ case 'MethodDefinition':
777
+ case 'PropertyDefinition':
778
+ return parent['key'] !== node || parent['computed'] === true;
779
+ case 'LabeledStatement':
780
+ case 'BreakStatement':
781
+ case 'ContinueStatement':
782
+ return false;
783
+ case 'ExportSpecifier':
784
+ return parent['local'] === node;
785
+ case 'ImportSpecifier':
786
+ case 'ImportDefaultSpecifier':
787
+ case 'ImportNamespaceSpecifier':
788
+ return false;
789
+ default:
790
+ return true;
791
+ }
792
+ }
646
793
  /**
647
794
  * What a bundle reads from the host ABI (AGL-3392), read off the bytes.
648
795
  *
@@ -679,15 +826,40 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
679
826
  ]);
680
827
  }
681
828
  });
682
- const within = (node, ancestor)=>{
683
- for(let at = node; at; at = parents.get(at)){
684
- if (at === ancestor) return true;
829
+ // Scope (AGL-3394). A minifier names the host alias `e`, and names every
830
+ // other function's first parameter `e` too, so a name only resolves to the
831
+ // host where no nearer scope declares it again.
832
+ const scopeNames = new Map();
833
+ const namesOf = (scope)=>{
834
+ let found = scopeNames.get(scope);
835
+ if (!found) {
836
+ found = declaredNames(scope);
837
+ scopeNames.set(scope, found);
838
+ }
839
+ return found;
840
+ };
841
+ /** The scope a declarator's name belongs to: its function for `var`, else its block. */ const bindingScope = (declarator)=>{
842
+ const declaration = parents.get(declarator);
843
+ const functionScoped = (declaration == null ? void 0 : declaration['kind']) === 'var';
844
+ for(let at = parents.get(declaration); at; at = parents.get(at)){
845
+ if (functionScoped ? FUNCTION_TYPES.has(at.type) || at.type === 'Program' : SCOPE_TYPES.has(at.type)) {
846
+ return at;
847
+ }
848
+ }
849
+ return null;
850
+ };
851
+ /** Whether `ref` means the binding `name` declared in `scope`. */ const resolvesTo = (ref, name, scope)=>{
852
+ if (!isReference(ref, parents.get(ref))) return false;
853
+ for(let at = parents.get(ref); at; at = parents.get(at)){
854
+ if (at === scope) return true;
855
+ if (SCOPE_TYPES.has(at.type) && namesOf(at).has(name)) return false;
685
856
  }
686
- return false;
857
+ return scope === null;
687
858
  };
688
859
  const isHostGlobal = (node)=>(node == null ? void 0 : node.type) === 'Identifier' && node['name'] === PLUGIN_HOST_GLOBAL || (node == null ? void 0 : node.type) === 'MemberExpression' && isGlobalRef(node['object']) && propertyName(node) === PLUGIN_HOST_GLOBAL;
689
- // Names bound to the host object, each valid everywhere (`scope` null) or
690
- // only inside one function (the `register(host)` parameter).
860
+ // Names bound to the host object, each with the scope it is declared in:
861
+ // a declarator's block or function, or the `register(host)` function for its
862
+ // parameter.
691
863
  const hostNames = new Map();
692
864
  walk(program, (node)=>{
693
865
  var _node_id;
@@ -701,14 +873,20 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
701
873
  init
702
874
  ];
703
875
  if ((id == null ? void 0 : id.type) === 'Identifier' && roots.some(isHostGlobal)) {
704
- hostNames.set(id['name'], null);
876
+ hostNames.set(id['name'], {
877
+ scope: bindingScope(node),
878
+ param: false
879
+ });
705
880
  }
706
881
  }
707
882
  if (node.type === 'FunctionDeclaration' && ((_node_id = node['id']) == null ? void 0 : _node_id['name']) === 'register') {
708
883
  var _node_params;
709
884
  const param = ((_node_params = node['params']) != null ? _node_params : [])[0];
710
885
  if ((param == null ? void 0 : param.type) === 'Identifier' && !hostNames.has(param['name'])) {
711
- hostNames.set(param['name'], node);
886
+ hostNames.set(param['name'], {
887
+ scope: node,
888
+ param: true
889
+ });
712
890
  }
713
891
  }
714
892
  });
@@ -716,10 +894,9 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
716
894
  if (isHostGlobal(node)) return 'global';
717
895
  if ((node == null ? void 0 : node.type) !== 'Identifier') return null;
718
896
  const name = node['name'];
719
- if (!hostNames.has(name)) return null;
720
- const scope = hostNames.get(name);
721
- if (!scope) return 'global';
722
- return within(node, scope) ? 'param' : null;
897
+ const binding = hostNames.get(name);
898
+ if (!binding || !resolvesTo(node, name, binding.scope)) return null;
899
+ return binding.param ? 'param' : 'global';
723
900
  };
724
901
  const modules = new Set(PLUGIN_HOST_MODULE_KEYS);
725
902
  const allowed = new Set(PLUGIN_HOST_ABI_KEYS);
@@ -758,10 +935,11 @@ const BASE64_BLOB = /^[A-Za-z0-9+/\r\n]+={0,2}$/;
758
935
  if ((id == null ? void 0 : id.type) === 'Identifier') {
759
936
  var _identifiers_get;
760
937
  const alias = id['name'];
761
- if (seenAliases.has(alias)) return;
762
- seenAliases.add(alias);
938
+ if (seenAliases.has(parent)) return;
939
+ seenAliases.add(parent);
940
+ const scope = bindingScope(parent);
763
941
  for (const use of (_identifiers_get = identifiers.get(alias)) != null ? _identifiers_get : []){
764
- if (use !== id) useOf(key, use);
942
+ if (use !== id && resolvesTo(use, alias, scope)) useOf(key, use);
765
943
  }
766
944
  return;
767
945
  }