@olonjs/cli 3.0.127 → 3.0.128

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.
@@ -1643,7 +1643,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1643
1643
  "@tiptap/extension-link": "^2.11.5",
1644
1644
  "@tiptap/react": "^2.11.5",
1645
1645
  "@tiptap/starter-kit": "^2.11.5",
1646
- "@olonjs/core": "^1.0.116",
1646
+ "@olonjs/core": "^1.0.117",
1647
1647
  "class-variance-authority": "^0.7.1",
1648
1648
  "clsx": "^2.1.1",
1649
1649
  "lucide-react": "^0.474.0",
@@ -2064,7 +2064,7 @@ if (targets.length === 0) {
2064
2064
  console.log(`[bake] Targets: ${targets.map((t) => t.slug).join(', ')}`);
2065
2065
 
2066
2066
  const ssrEntryUrl = pathToFileURL(path.resolve(root, 'dist-ssr/entry-ssg.js')).href;
2067
- const { render, getCss, getPageMeta, getWebMcpBuildState } = await import(ssrEntryUrl);
2067
+ const { render, getCss, getRemoteStylesheets, getPageMeta, getWebMcpBuildState } = await import(ssrEntryUrl);
2068
2068
 
2069
2069
  const template = await fs.readFile(path.resolve(root, 'dist/index.html'), 'utf-8');
2070
2070
  const hasCommentMarker = template.includes('<!--app-html-->');
@@ -2075,6 +2075,9 @@ if (!hasCommentMarker && !hasRootDivMarker) {
2075
2075
 
2076
2076
  const inlinedCss = getCss();
2077
2077
  const styleTag = `<style data-bake="inline">${inlinedCss}</style>`;
2078
+ const remoteStylesheetTags = getRemoteStylesheets()
2079
+ .map((href) => `<link rel="stylesheet" href="${escapeHtmlAttribute(href)}">`)
2080
+ .join('\n ');
2078
2081
  const webMcpBuildState = getWebMcpBuildState();
2079
2082
 
2080
2083
  for (const { slug } of targets) {
@@ -2148,7 +2151,7 @@ for (const { slug, out, depth } of targets) {
2148
2151
  ].join('\n ');
2149
2152
 
2150
2153
  let bakedHtml = fixedTemplate
2151
- .replace('</head>', ` ${styleTag}\n ${contractLinks}\n</head>`)
2154
+ .replace('</head>', ` ${remoteStylesheetTags ? `${remoteStylesheetTags}\n ` : ''}${styleTag}\n ${contractLinks}\n</head>`)
2152
2155
  .replace(/<title>.*?<\/title>/, `<title>${safeTitle}</title>\n ${metaTags}`);
2153
2156
 
2154
2157
  if (hasCommentMarker) {
@@ -2917,6 +2920,42 @@ function buildThemeFontVarsCss(input: unknown): string {
2917
2920
  return `:root{--theme-font-primary:${primary};--theme-font-serif:${serif};--theme-font-mono:${mono};}`;
2918
2921
  }
2919
2922
 
2923
+ const REMOTE_CSS_LINK_ATTR = 'data-jp-tenant-remote-css';
2924
+
2925
+ function isRemoteStylesheetHref(value: string): boolean {
2926
+ return /^https?:\/\//i.test(value.trim());
2927
+ }
2928
+
2929
+ function extractLeadingRemoteCssImports(cssText: string): { hrefs: string[]; rest: string } {
2930
+ const hrefs = new Set<string>();
2931
+ const leadingTriviaPattern = /^(?:\s+|\/\*[\s\S]*?\*\/)*/;
2932
+ const importPattern =
2933
+ /^@import\s+url\(\s*(?:'([^']+)'|"([^"]+)"|([^'")\s][^)]*))\s*\)\s*([^;]*);/i;
2934
+ let rest = cssText;
2935
+
2936
+ for (;;) {
2937
+ const trivia = rest.match(leadingTriviaPattern);
2938
+ if (trivia && trivia[0]) {
2939
+ rest = rest.slice(trivia[0].length);
2940
+ }
2941
+
2942
+ const match = rest.match(importPattern);
2943
+ if (!match) break;
2944
+
2945
+ const href = (match[1] ?? match[2] ?? match[3] ?? '').trim();
2946
+ const trailingDirectives = (match[4] ?? '').trim();
2947
+
2948
+ if (!isRemoteStylesheetHref(href) || trailingDirectives.length > 0) {
2949
+ break;
2950
+ }
2951
+
2952
+ hrefs.add(href);
2953
+ rest = rest.slice(match[0].length);
2954
+ }
2955
+
2956
+ return { hrefs: Array.from(hrefs), rest };
2957
+ }
2958
+
2920
2959
  function setTenantPreviewReady(ready: boolean): void {
2921
2960
  if (typeof window !== 'undefined') {
2922
2961
  (window as Window & { __TENANT_PREVIEW_READY__?: boolean }).__TENANT_PREVIEW_READY__ = ready;
@@ -3331,6 +3370,39 @@ function App() {
3331
3370
  void runCloudSave(pendingCloudSave.current, false);
3332
3371
  }, [runCloudSave]);
3333
3372
 
3373
+ const tenantCssParts = useMemo(() => extractLeadingRemoteCssImports(tenantCss), [tenantCss]);
3374
+ const resolvedTenantCss = useMemo(
3375
+ () => [buildThemeFontVarsCss(themeConfig), tenantCssParts.rest].filter(Boolean).join('\n'),
3376
+ [tenantCssParts],
3377
+ );
3378
+
3379
+ useEffect(() => {
3380
+ if (typeof document === 'undefined') return undefined;
3381
+
3382
+ const createdLinks: HTMLLinkElement[] = [];
3383
+
3384
+ tenantCssParts.hrefs.forEach((href) => {
3385
+ const existing = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
3386
+ (link) => (link as HTMLLinkElement).href === href,
3387
+ ) as HTMLLinkElement | undefined;
3388
+ if (existing) return;
3389
+
3390
+ const link = document.createElement('link');
3391
+ link.rel = 'stylesheet';
3392
+ link.href = href;
3393
+ link.setAttribute(REMOTE_CSS_LINK_ATTR, href);
3394
+ document.head.appendChild(link);
3395
+ createdLinks.push(link);
3396
+ });
3397
+
3398
+ return () => {
3399
+ createdLinks.forEach((link) => {
3400
+ if (link.getAttribute(REMOTE_CSS_LINK_ATTR) !== link.href) return;
3401
+ if (link.parentNode) link.parentNode.removeChild(link);
3402
+ });
3403
+ };
3404
+ }, [tenantCssParts]);
3405
+
3334
3406
  const config: JsonPagesConfig = {
3335
3407
  tenantId: TENANT_ID,
3336
3408
  basePath: APP_BASE_PATH,
@@ -3341,7 +3413,7 @@ function App() {
3341
3413
  themeConfig,
3342
3414
  menuConfig,
3343
3415
  refDocuments,
3344
- themeCss: { tenant: `${buildThemeFontVarsCss(themeConfig)}\n${tenantCss}` },
3416
+ themeCss: { tenant: resolvedTenantCss },
3345
3417
  addSection: addSectionConfig,
3346
3418
  webmcp: {
3347
3419
  enabled: true,
@@ -8044,6 +8116,39 @@ function buildThemeCssFromSot(theme: ThemeConfig): string {
8044
8116
  return `:root{${serialized}}`;
8045
8117
  }
8046
8118
 
8119
+ function isRemoteStylesheetHref(value: string): boolean {
8120
+ return /^https?:\/\//i.test(value.trim());
8121
+ }
8122
+
8123
+ function extractLeadingRemoteCssImports(cssText: string): { hrefs: string[]; rest: string } {
8124
+ const hrefs = new Set<string>();
8125
+ const leadingTriviaPattern = /^(?:\s+|\/\*[\s\S]*?\*\/)*/;
8126
+ const importPattern =
8127
+ /^@import(?:\s+url\(\s*(?:'([^']+)'|"([^"]+)"|([^'")\s][^)]*))\s*\)|\s*(['"])([^'"]+)\4)\s*([^;]*);/i;
8128
+ let rest = cssText;
8129
+
8130
+ for (;;) {
8131
+ const trivia = rest.match(leadingTriviaPattern);
8132
+ if (trivia && trivia[0]) {
8133
+ rest = rest.slice(trivia[0].length);
8134
+ }
8135
+
8136
+ const match = rest.match(importPattern);
8137
+ if (!match) break;
8138
+
8139
+ const href = (match[1] ?? match[2] ?? match[3] ?? match[5] ?? '').trim();
8140
+ const trailingDirectives = (match[6] ?? '').trim();
8141
+ if (!isRemoteStylesheetHref(href) || trailingDirectives.length > 0) {
8142
+ break;
8143
+ }
8144
+
8145
+ hrefs.add(href);
8146
+ rest = rest.slice(match[0].length);
8147
+ }
8148
+
8149
+ return { hrefs: Array.from(hrefs), rest };
8150
+ }
8151
+
8047
8152
  function resolveTenantId(): string {
8048
8153
  const site: Record<string, unknown> = isRecord(siteConfig) ? siteConfig : {};
8049
8154
  const identityRaw = site['identity'];
@@ -8099,8 +8204,13 @@ export function render(slug: string): string {
8099
8204
 
8100
8205
  export function getCss(): string {
8101
8206
  const themeCss = buildThemeCssFromSot(themeConfig);
8102
- if (!themeCss) return tenantCss;
8103
- return `${themeCss}\n${tenantCss}`;
8207
+ const { rest } = extractLeadingRemoteCssImports(tenantCss);
8208
+ if (!themeCss) return rest;
8209
+ return `${themeCss}\n${rest}`;
8210
+ }
8211
+
8212
+ export function getRemoteStylesheets(): string[] {
8213
+ return extractLeadingRemoteCssImports(tenantCss).hrefs;
8104
8214
  }
8105
8215
 
8106
8216
  export function getPageMeta(slug: string): { title: string; description: string } {
@@ -8127,6 +8237,7 @@ export function getWebMcpBuildState(): {
8127
8237
  }
8128
8238
 
8129
8239
  END_OF_FILE_CONTENT
8240
+ # SKIP: src/entry-ssg.tsx:Zone.Identifier is binary and cannot be embedded as text.
8130
8241
  echo "Creating src/fonts.css..."
8131
8242
  cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
8132
8243
  @import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap');
@@ -1643,7 +1643,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1643
1643
  "@tiptap/extension-link": "^2.11.5",
1644
1644
  "@tiptap/react": "^2.11.5",
1645
1645
  "@tiptap/starter-kit": "^2.11.5",
1646
- "@olonjs/core": "^1.0.116",
1646
+ "@olonjs/core": "^1.0.117",
1647
1647
  "class-variance-authority": "^0.7.1",
1648
1648
  "clsx": "^2.1.1",
1649
1649
  "lucide-react": "^0.474.0",
@@ -2064,7 +2064,7 @@ if (targets.length === 0) {
2064
2064
  console.log(`[bake] Targets: ${targets.map((t) => t.slug).join(', ')}`);
2065
2065
 
2066
2066
  const ssrEntryUrl = pathToFileURL(path.resolve(root, 'dist-ssr/entry-ssg.js')).href;
2067
- const { render, getCss, getPageMeta, getWebMcpBuildState } = await import(ssrEntryUrl);
2067
+ const { render, getCss, getRemoteStylesheets, getPageMeta, getWebMcpBuildState } = await import(ssrEntryUrl);
2068
2068
 
2069
2069
  const template = await fs.readFile(path.resolve(root, 'dist/index.html'), 'utf-8');
2070
2070
  const hasCommentMarker = template.includes('<!--app-html-->');
@@ -2075,6 +2075,9 @@ if (!hasCommentMarker && !hasRootDivMarker) {
2075
2075
 
2076
2076
  const inlinedCss = getCss();
2077
2077
  const styleTag = `<style data-bake="inline">${inlinedCss}</style>`;
2078
+ const remoteStylesheetTags = getRemoteStylesheets()
2079
+ .map((href) => `<link rel="stylesheet" href="${escapeHtmlAttribute(href)}">`)
2080
+ .join('\n ');
2078
2081
  const webMcpBuildState = getWebMcpBuildState();
2079
2082
 
2080
2083
  for (const { slug } of targets) {
@@ -2148,7 +2151,7 @@ for (const { slug, out, depth } of targets) {
2148
2151
  ].join('\n ');
2149
2152
 
2150
2153
  let bakedHtml = fixedTemplate
2151
- .replace('</head>', ` ${styleTag}\n ${contractLinks}\n</head>`)
2154
+ .replace('</head>', ` ${remoteStylesheetTags ? `${remoteStylesheetTags}\n ` : ''}${styleTag}\n ${contractLinks}\n</head>`)
2152
2155
  .replace(/<title>.*?<\/title>/, `<title>${safeTitle}</title>\n ${metaTags}`);
2153
2156
 
2154
2157
  if (hasCommentMarker) {
@@ -2917,6 +2920,42 @@ function buildThemeFontVarsCss(input: unknown): string {
2917
2920
  return `:root{--theme-font-primary:${primary};--theme-font-serif:${serif};--theme-font-mono:${mono};}`;
2918
2921
  }
2919
2922
 
2923
+ const REMOTE_CSS_LINK_ATTR = 'data-jp-tenant-remote-css';
2924
+
2925
+ function isRemoteStylesheetHref(value: string): boolean {
2926
+ return /^https?:\/\//i.test(value.trim());
2927
+ }
2928
+
2929
+ function extractLeadingRemoteCssImports(cssText: string): { hrefs: string[]; rest: string } {
2930
+ const hrefs = new Set<string>();
2931
+ const leadingTriviaPattern = /^(?:\s+|\/\*[\s\S]*?\*\/)*/;
2932
+ const importPattern =
2933
+ /^@import\s+url\(\s*(?:'([^']+)'|"([^"]+)"|([^'")\s][^)]*))\s*\)\s*([^;]*);/i;
2934
+ let rest = cssText;
2935
+
2936
+ for (;;) {
2937
+ const trivia = rest.match(leadingTriviaPattern);
2938
+ if (trivia && trivia[0]) {
2939
+ rest = rest.slice(trivia[0].length);
2940
+ }
2941
+
2942
+ const match = rest.match(importPattern);
2943
+ if (!match) break;
2944
+
2945
+ const href = (match[1] ?? match[2] ?? match[3] ?? '').trim();
2946
+ const trailingDirectives = (match[4] ?? '').trim();
2947
+
2948
+ if (!isRemoteStylesheetHref(href) || trailingDirectives.length > 0) {
2949
+ break;
2950
+ }
2951
+
2952
+ hrefs.add(href);
2953
+ rest = rest.slice(match[0].length);
2954
+ }
2955
+
2956
+ return { hrefs: Array.from(hrefs), rest };
2957
+ }
2958
+
2920
2959
  function setTenantPreviewReady(ready: boolean): void {
2921
2960
  if (typeof window !== 'undefined') {
2922
2961
  (window as Window & { __TENANT_PREVIEW_READY__?: boolean }).__TENANT_PREVIEW_READY__ = ready;
@@ -3331,6 +3370,39 @@ function App() {
3331
3370
  void runCloudSave(pendingCloudSave.current, false);
3332
3371
  }, [runCloudSave]);
3333
3372
 
3373
+ const tenantCssParts = useMemo(() => extractLeadingRemoteCssImports(tenantCss), [tenantCss]);
3374
+ const resolvedTenantCss = useMemo(
3375
+ () => [buildThemeFontVarsCss(themeConfig), tenantCssParts.rest].filter(Boolean).join('\n'),
3376
+ [tenantCssParts],
3377
+ );
3378
+
3379
+ useEffect(() => {
3380
+ if (typeof document === 'undefined') return undefined;
3381
+
3382
+ const createdLinks: HTMLLinkElement[] = [];
3383
+
3384
+ tenantCssParts.hrefs.forEach((href) => {
3385
+ const existing = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
3386
+ (link) => (link as HTMLLinkElement).href === href,
3387
+ ) as HTMLLinkElement | undefined;
3388
+ if (existing) return;
3389
+
3390
+ const link = document.createElement('link');
3391
+ link.rel = 'stylesheet';
3392
+ link.href = href;
3393
+ link.setAttribute(REMOTE_CSS_LINK_ATTR, href);
3394
+ document.head.appendChild(link);
3395
+ createdLinks.push(link);
3396
+ });
3397
+
3398
+ return () => {
3399
+ createdLinks.forEach((link) => {
3400
+ if (link.getAttribute(REMOTE_CSS_LINK_ATTR) !== link.href) return;
3401
+ if (link.parentNode) link.parentNode.removeChild(link);
3402
+ });
3403
+ };
3404
+ }, [tenantCssParts]);
3405
+
3334
3406
  const config: JsonPagesConfig = {
3335
3407
  tenantId: TENANT_ID,
3336
3408
  basePath: APP_BASE_PATH,
@@ -3341,7 +3413,7 @@ function App() {
3341
3413
  themeConfig,
3342
3414
  menuConfig,
3343
3415
  refDocuments,
3344
- themeCss: { tenant: `${buildThemeFontVarsCss(themeConfig)}\n${tenantCss}` },
3416
+ themeCss: { tenant: resolvedTenantCss },
3345
3417
  addSection: addSectionConfig,
3346
3418
  webmcp: {
3347
3419
  enabled: true,
@@ -8044,6 +8116,39 @@ function buildThemeCssFromSot(theme: ThemeConfig): string {
8044
8116
  return `:root{${serialized}}`;
8045
8117
  }
8046
8118
 
8119
+ function isRemoteStylesheetHref(value: string): boolean {
8120
+ return /^https?:\/\//i.test(value.trim());
8121
+ }
8122
+
8123
+ function extractLeadingRemoteCssImports(cssText: string): { hrefs: string[]; rest: string } {
8124
+ const hrefs = new Set<string>();
8125
+ const leadingTriviaPattern = /^(?:\s+|\/\*[\s\S]*?\*\/)*/;
8126
+ const importPattern =
8127
+ /^@import(?:\s+url\(\s*(?:'([^']+)'|"([^"]+)"|([^'")\s][^)]*))\s*\)|\s*(['"])([^'"]+)\4)\s*([^;]*);/i;
8128
+ let rest = cssText;
8129
+
8130
+ for (;;) {
8131
+ const trivia = rest.match(leadingTriviaPattern);
8132
+ if (trivia && trivia[0]) {
8133
+ rest = rest.slice(trivia[0].length);
8134
+ }
8135
+
8136
+ const match = rest.match(importPattern);
8137
+ if (!match) break;
8138
+
8139
+ const href = (match[1] ?? match[2] ?? match[3] ?? match[5] ?? '').trim();
8140
+ const trailingDirectives = (match[6] ?? '').trim();
8141
+ if (!isRemoteStylesheetHref(href) || trailingDirectives.length > 0) {
8142
+ break;
8143
+ }
8144
+
8145
+ hrefs.add(href);
8146
+ rest = rest.slice(match[0].length);
8147
+ }
8148
+
8149
+ return { hrefs: Array.from(hrefs), rest };
8150
+ }
8151
+
8047
8152
  function resolveTenantId(): string {
8048
8153
  const site: Record<string, unknown> = isRecord(siteConfig) ? siteConfig : {};
8049
8154
  const identityRaw = site['identity'];
@@ -8099,8 +8204,13 @@ export function render(slug: string): string {
8099
8204
 
8100
8205
  export function getCss(): string {
8101
8206
  const themeCss = buildThemeCssFromSot(themeConfig);
8102
- if (!themeCss) return tenantCss;
8103
- return `${themeCss}\n${tenantCss}`;
8207
+ const { rest } = extractLeadingRemoteCssImports(tenantCss);
8208
+ if (!themeCss) return rest;
8209
+ return `${themeCss}\n${rest}`;
8210
+ }
8211
+
8212
+ export function getRemoteStylesheets(): string[] {
8213
+ return extractLeadingRemoteCssImports(tenantCss).hrefs;
8104
8214
  }
8105
8215
 
8106
8216
  export function getPageMeta(slug: string): { title: string; description: string } {
@@ -8127,6 +8237,7 @@ export function getWebMcpBuildState(): {
8127
8237
  }
8128
8238
 
8129
8239
  END_OF_FILE_CONTENT
8240
+ # SKIP: src/entry-ssg.tsx:Zone.Identifier is binary and cannot be embedded as text.
8130
8241
  echo "Creating src/fonts.css..."
8131
8242
  cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
8132
8243
  @import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olonjs/cli",
3
- "version": "3.0.127",
3
+ "version": "3.0.128",
4
4
  "description": "The Sovereign CLI Engine for OlonJS.",
5
5
  "type": "module",
6
6
  "bin": {