@symbo.ls/brender 3.7.6 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/env.js CHANGED
@@ -71,10 +71,24 @@ const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html
71
71
  if (!window.sessionStorage) window.sessionStorage = createStorage();
72
72
  if (!globalThis.localStorage) globalThis.localStorage = window.localStorage;
73
73
  if (!globalThis.sessionStorage) globalThis.sessionStorage = window.sessionStorage;
74
+ if (!window.MutationObserver) {
75
+ window.MutationObserver = class MutationObserver {
76
+ constructor() {
77
+ }
78
+ observe() {
79
+ }
80
+ disconnect() {
81
+ }
82
+ takeRecords() {
83
+ return [];
84
+ }
85
+ };
86
+ }
74
87
  globalThis.window = window;
75
88
  globalThis.document = document;
76
89
  globalThis.Node = window.Node || globalThis.Node;
77
90
  globalThis.HTMLElement = window.HTMLElement || globalThis.HTMLElement;
91
+ globalThis.MutationObserver = window.MutationObserver;
78
92
  globalThis.Window = window.constructor;
79
93
  return { window, document };
80
94
  };
@@ -207,7 +207,8 @@ const fetchSSRTranslations = async (data) => {
207
207
  if (result.translations) {
208
208
  translations[lang] = result.translations;
209
209
  }
210
- } catch {
210
+ } catch (e) {
211
+ console.warn("[brender] SSR translation fetch failed:", e.message);
211
212
  }
212
213
  })
213
214
  );
@@ -40,6 +40,7 @@ var import_path = require("path");
40
40
  var import_fs = require("fs");
41
41
  var import_os = require("os");
42
42
  var import_crypto = require("crypto");
43
+ var import_module = require("module");
43
44
  var import_env = require("./env.js");
44
45
  var import_keys = require("./keys.js");
45
46
  var import_metadata = require("./metadata.js");
@@ -121,71 +122,224 @@ const safeJsonReplacer = () => {
121
122
  return value;
122
123
  };
123
124
  };
125
+ const _brenderRequire = (0, import_module.createRequire)(import_meta.url);
126
+ const detectWorkspace = () => {
127
+ const brenderDir = (0, import_fs.realpathSync)(new URL(".", import_meta.url).pathname);
128
+ const monorepoRoot = (0, import_path.resolve)(brenderDir, "../..");
129
+ const isMonorepo = (0, import_fs.existsSync)((0, import_path.resolve)(monorepoRoot, "packages", "smbls", "src", "createDomql.js"));
130
+ if (isMonorepo) {
131
+ return { isMonorepo: true, monorepoRoot };
132
+ }
133
+ let smblsRoot;
134
+ try {
135
+ const smblsPkg = _brenderRequire.resolve("smbls/package.json");
136
+ smblsRoot = (0, import_path.dirname)(smblsPkg);
137
+ } catch {
138
+ let dir2 = brenderDir;
139
+ while (dir2 !== (0, import_path.dirname)(dir2)) {
140
+ const candidate = (0, import_path.resolve)(dir2, "node_modules", "smbls");
141
+ if ((0, import_fs.existsSync)((0, import_path.resolve)(candidate, "package.json"))) {
142
+ smblsRoot = candidate;
143
+ break;
144
+ }
145
+ dir2 = (0, import_path.dirname)(dir2);
146
+ }
147
+ }
148
+ let _smblsRequire = _brenderRequire;
149
+ if (smblsRoot) {
150
+ try {
151
+ _smblsRequire = (0, import_module.createRequire)((0, import_path.resolve)(smblsRoot, "package.json"));
152
+ } catch {
153
+ }
154
+ }
155
+ let projectRoot;
156
+ let dir = process.cwd();
157
+ while (dir !== (0, import_path.dirname)(dir)) {
158
+ if ((0, import_fs.existsSync)((0, import_path.resolve)(dir, "package.json"))) {
159
+ projectRoot = dir;
160
+ break;
161
+ }
162
+ dir = (0, import_path.dirname)(dir);
163
+ }
164
+ let _projectRequire = _smblsRequire;
165
+ if (projectRoot) {
166
+ try {
167
+ _projectRequire = (0, import_module.createRequire)((0, import_path.resolve)(projectRoot, "package.json"));
168
+ } catch {
169
+ }
170
+ }
171
+ return { isMonorepo: false, smblsRoot, brenderDir, projectRoot, _smblsRequire, _projectRequire };
172
+ };
173
+ const tryRequireResolve = (ws, specifier) => {
174
+ const requireFns = ws.isMonorepo ? [_brenderRequire] : [ws._smblsRequire, ws._projectRequire, _brenderRequire].filter(Boolean);
175
+ for (const req of requireFns) {
176
+ try {
177
+ return req.resolve(specifier);
178
+ } catch {
179
+ }
180
+ }
181
+ return null;
182
+ };
183
+ const resolvePackagePath = (ws, pkgName, ...subpath) => {
184
+ if (ws.isMonorepo) {
185
+ return (0, import_path.resolve)(ws.monorepoRoot, "packages", pkgName, ...subpath);
186
+ }
187
+ const pkgJson = tryRequireResolve(ws, `${pkgName}/package.json`);
188
+ if (pkgJson) return (0, import_path.resolve)((0, import_path.dirname)(pkgJson), ...subpath);
189
+ return null;
190
+ };
191
+ const resolvePluginPath = (ws, pluginName, ...subpath) => {
192
+ if (ws.isMonorepo) {
193
+ return (0, import_path.resolve)(ws.monorepoRoot, "plugins", pluginName, ...subpath);
194
+ }
195
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pluginName}/package.json`);
196
+ if (pkgJson) return (0, import_path.resolve)((0, import_path.dirname)(pkgJson), ...subpath);
197
+ return null;
198
+ };
199
+ const resolveSymbolsPackage = (ws, pkg, ...subpath) => {
200
+ if (ws.isMonorepo) {
201
+ for (const dir of ["packages", "plugins"]) {
202
+ const src = (0, import_path.resolve)(ws.monorepoRoot, dir, pkg, ...subpath);
203
+ if ((0, import_fs.existsSync)(src)) return src;
204
+ }
205
+ return null;
206
+ }
207
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pkg}/package.json`);
208
+ if (pkgJson) return (0, import_path.resolve)((0, import_path.dirname)(pkgJson), ...subpath);
209
+ return null;
210
+ };
211
+ const resolveDomqlPackage = (ws, pkg, ...subpath) => {
212
+ if (ws.isMonorepo) {
213
+ return (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, ...subpath);
214
+ }
215
+ const pkgJson = tryRequireResolve(ws, `@domql/${pkg}/package.json`);
216
+ if (pkgJson) return (0, import_path.resolve)((0, import_path.dirname)(pkgJson), ...subpath);
217
+ return null;
218
+ };
124
219
  let _cachedCreateDomql = null;
125
220
  const bundleCreateDomql = async () => {
126
221
  if (_cachedCreateDomql) return _cachedCreateDomql;
127
- const brenderDir = (0, import_fs.realpathSync)(new URL(".", import_meta.url).pathname);
128
- const monorepoRoot = (0, import_path.resolve)(brenderDir, "../..");
129
- const entry = (0, import_path.resolve)(monorepoRoot, "packages", "smbls", "src", "createDomql.js");
222
+ const ws = detectWorkspace();
223
+ let entry;
224
+ if (ws.isMonorepo) {
225
+ entry = (0, import_path.resolve)(ws.monorepoRoot, "packages", "smbls", "src", "createDomql.js");
226
+ } else if (ws.smblsRoot) {
227
+ const srcEntry = (0, import_path.resolve)(ws.smblsRoot, "src", "createDomql.js");
228
+ const distEntry = (0, import_path.resolve)(ws.smblsRoot, "dist", "esm", "src", "createDomql.js");
229
+ entry = (0, import_fs.existsSync)(srcEntry) ? srcEntry : distEntry;
230
+ }
231
+ if (!entry || !(0, import_fs.existsSync)(entry)) {
232
+ throw new Error(`brender: cannot find createDomql.js (isMonorepo=${ws.isMonorepo}, entry=${entry})`);
233
+ }
130
234
  const esbuild = await import("esbuild");
131
235
  const outFile = (0, import_path.join)((0, import_os.tmpdir)(), `br_createDomql_${(0, import_crypto.randomBytes)(6).toString("hex")}.mjs`);
236
+ const tryResolve = (base) => {
237
+ if (!base) return null;
238
+ const src = (0, import_path.resolve)(base, "src", "index.js");
239
+ if ((0, import_fs.existsSync)(src)) return src;
240
+ const idx = (0, import_path.resolve)(base, "index.js");
241
+ if ((0, import_fs.existsSync)(idx)) return idx;
242
+ return null;
243
+ };
132
244
  const workspacePlugin = {
133
245
  name: "workspace-resolve",
134
246
  setup(build) {
135
247
  build.onResolve({ filter: /^smbls/ }, (args) => {
136
248
  const subpath = args.path.replace(/^smbls\/?/, "");
249
+ const smblsBase = ws.isMonorepo ? (0, import_path.resolve)(ws.monorepoRoot, "packages", "smbls") : ws.smblsRoot;
250
+ if (!smblsBase) return;
137
251
  if (!subpath) {
138
- const src = (0, import_path.resolve)(monorepoRoot, "packages", "smbls", "src", "index.js");
139
- if ((0, import_fs.existsSync)(src)) return { path: src };
252
+ const r = tryResolve(smblsBase);
253
+ if (r) return { path: r };
254
+ return;
140
255
  }
141
- const full = (0, import_path.resolve)(monorepoRoot, "packages", "smbls", subpath);
256
+ const full = (0, import_path.resolve)(smblsBase, subpath);
142
257
  if ((0, import_fs.existsSync)(full)) return { path: full };
143
258
  if ((0, import_fs.existsSync)(full + ".js")) return { path: full + ".js" };
144
259
  const idx = (0, import_path.resolve)(full, "index.js");
145
260
  if ((0, import_fs.existsSync)(idx)) return { path: idx };
146
261
  });
147
262
  build.onResolve({ filter: /^domql$/ }, (args) => {
148
- const src = (0, import_path.resolve)(monorepoRoot, "packages", "domql", "src", "index.js");
149
- if ((0, import_fs.existsSync)(src)) return { path: src };
150
- const dist = (0, import_path.resolve)(monorepoRoot, "packages", "domql", "index.js");
151
- if ((0, import_fs.existsSync)(dist)) return { path: dist };
263
+ if (ws.isMonorepo) {
264
+ const src = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "src", "index.js");
265
+ if ((0, import_fs.existsSync)(src)) return { path: src };
266
+ const dist = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "index.js");
267
+ if ((0, import_fs.existsSync)(dist)) return { path: dist };
268
+ } else {
269
+ try {
270
+ const pkgJson = _require.resolve("domql/package.json");
271
+ const r = tryResolve((0, import_path.dirname)(pkgJson));
272
+ if (r) return { path: r };
273
+ } catch {
274
+ }
275
+ }
152
276
  });
153
277
  build.onResolve({ filter: /^@symbo\.ls\// }, (args) => {
154
278
  const pkg = args.path.replace("@symbo.ls/", "");
155
279
  if (pkg === "sync") return { path: "sync-stub", namespace: "brender-stub" };
156
- for (const dir of ["packages", "plugins"]) {
157
- const src = (0, import_path.resolve)(monorepoRoot, dir, pkg, "src", "index.js");
158
- if ((0, import_fs.existsSync)(src)) return { path: src };
159
- const dist = (0, import_path.resolve)(monorepoRoot, dir, pkg, "index.js");
160
- if ((0, import_fs.existsSync)(dist)) return { path: dist };
280
+ if (ws.isMonorepo) {
281
+ for (const dir of ["packages", "plugins"]) {
282
+ const src = (0, import_path.resolve)(ws.monorepoRoot, dir, pkg, "src", "index.js");
283
+ if ((0, import_fs.existsSync)(src)) return { path: src };
284
+ const dist = (0, import_path.resolve)(ws.monorepoRoot, dir, pkg, "index.js");
285
+ if ((0, import_fs.existsSync)(dist)) return { path: dist };
286
+ }
287
+ const blank = (0, import_path.resolve)(ws.monorepoRoot, "packages", "default-config", "blank", "index.js");
288
+ if (pkg === "default-config" && (0, import_fs.existsSync)(blank)) return { path: blank };
289
+ } else {
290
+ const resolved = resolveSymbolsPackage(ws, pkg, "src", "index.js");
291
+ if (resolved && (0, import_fs.existsSync)(resolved)) return { path: resolved };
292
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, "index.js");
293
+ if (resolvedIdx && (0, import_fs.existsSync)(resolvedIdx)) return { path: resolvedIdx };
294
+ if (pkg === "default-config") {
295
+ const blank = resolveSymbolsPackage(ws, "default-config", "blank", "index.js");
296
+ if (blank && (0, import_fs.existsSync)(blank)) return { path: blank };
297
+ }
161
298
  }
162
- const blank = (0, import_path.resolve)(monorepoRoot, "packages", "default-config", "blank", "index.js");
163
- if (pkg === "default-config" && (0, import_fs.existsSync)(blank)) return { path: blank };
164
299
  });
165
300
  build.onResolve({ filter: /^@domql\// }, (args) => {
166
301
  const pkg = args.path.replace("@domql/", "");
167
- const src = (0, import_path.resolve)(monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
168
- if ((0, import_fs.existsSync)(src)) return { path: src };
169
- const dist = (0, import_path.resolve)(monorepoRoot, "packages", "domql", "packages", pkg, "index.js");
170
- if ((0, import_fs.existsSync)(dist)) return { path: dist };
302
+ if (ws.isMonorepo) {
303
+ const src = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
304
+ if ((0, import_fs.existsSync)(src)) return { path: src };
305
+ const dist = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, "index.js");
306
+ if ((0, import_fs.existsSync)(dist)) return { path: dist };
307
+ } else {
308
+ const resolved = resolveDomqlPackage(ws, pkg, "src", "index.js");
309
+ if (resolved && (0, import_fs.existsSync)(resolved)) return { path: resolved };
310
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, "index.js");
311
+ if (resolvedIdx && (0, import_fs.existsSync)(resolvedIdx)) return { path: resolvedIdx };
312
+ }
171
313
  });
172
314
  build.onResolve({ filter: /^css-in-props/ }, (args) => {
173
- const base = (0, import_path.resolve)(monorepoRoot, "packages", "css-in-props");
315
+ let base;
316
+ if (ws.isMonorepo) {
317
+ base = (0, import_path.resolve)(ws.monorepoRoot, "packages", "css-in-props");
318
+ } else {
319
+ const pkgJson = tryRequireResolve(ws, "css-in-props/package.json");
320
+ if (pkgJson) base = (0, import_path.dirname)(pkgJson);
321
+ }
322
+ if (!base) return;
174
323
  const subpath = args.path.replace(/^css-in-props\/?/, "");
175
324
  if (subpath) {
176
325
  const full = (0, import_path.resolve)(base, subpath);
177
- const idx2 = (0, import_path.resolve)(full, "index.js");
178
- if ((0, import_fs.existsSync)(idx2)) return { path: idx2 };
326
+ const idx = (0, import_path.resolve)(full, "index.js");
327
+ if ((0, import_fs.existsSync)(idx)) return { path: idx };
179
328
  if ((0, import_fs.existsSync)(full + ".js")) return { path: full + ".js" };
180
329
  if ((0, import_fs.existsSync)(full)) return { path: full };
181
330
  }
182
- const src = (0, import_path.resolve)(base, "src", "index.js");
183
- if ((0, import_fs.existsSync)(src)) return { path: src };
184
- const idx = (0, import_path.resolve)(base, "index.js");
185
- if ((0, import_fs.existsSync)(idx)) return { path: idx };
331
+ const r = tryResolve(base);
332
+ if (r) return { path: r };
186
333
  });
187
334
  build.onResolve({ filter: /^@emotion\// }, (args) => {
188
- const nm = (0, import_path.resolve)(monorepoRoot, "node_modules", args.path);
335
+ let nm;
336
+ if (ws.isMonorepo) {
337
+ nm = (0, import_path.resolve)(ws.monorepoRoot, "node_modules", args.path);
338
+ } else {
339
+ const pkgJson = tryRequireResolve(ws, `${args.path}/package.json`);
340
+ if (!pkgJson) return;
341
+ nm = (0, import_path.dirname)(pkgJson);
342
+ }
189
343
  if ((0, import_fs.existsSync)(nm)) {
190
344
  const pkg = (0, import_path.resolve)(nm, "package.json");
191
345
  if ((0, import_fs.existsSync)(pkg)) {
@@ -258,7 +412,11 @@ const bundleCreateDomql = async () => {
258
412
  write: true,
259
413
  logLevel: "warning",
260
414
  plugins: [workspacePlugin],
261
- nodePaths: [(0, import_path.resolve)(monorepoRoot, "node_modules")],
415
+ nodePaths: ws.isMonorepo ? [(0, import_path.resolve)(ws.monorepoRoot, "node_modules")] : [
416
+ ...ws.smblsRoot ? [(0, import_path.resolve)(ws.smblsRoot, "node_modules")] : [],
417
+ ...ws.projectRoot ? [(0, import_path.resolve)(ws.projectRoot, "node_modules")] : [],
418
+ ...ws.smblsRoot ? [(0, import_path.resolve)(ws.smblsRoot, "..", "..", "node_modules")] : []
419
+ ].filter((p) => (0, import_fs.existsSync)(p)),
262
420
  supported: { "import-attributes": false },
263
421
  external: [
264
422
  "fs",
@@ -412,7 +570,8 @@ const render = async (data, options = {}) => {
412
570
  if (prefetch) {
413
571
  try {
414
572
  ssrTranslations = await (0, import_prefetch.fetchSSRTranslations)(data);
415
- } catch {
573
+ } catch (e) {
574
+ console.warn("[brender] SSR translation fetch failed:", e.message);
416
575
  }
417
576
  }
418
577
  if (_prevLocPrefetch !== void 0) globalThis.location = _prevLocPrefetch;
@@ -638,26 +797,43 @@ const generateGlobalCSS = async (ds, config) => {
638
797
  globalThis.__BR_GLOBAL_CSS__ = result
639
798
  export default result
640
799
  `);
641
- const brenderDir = new URL(".", import_meta.url).pathname;
642
- const monorepoRoot = (0, import_path.resolve)(brenderDir, "../..");
800
+ const ws = detectWorkspace();
643
801
  const workspacePlugin = {
644
802
  name: "workspace-resolve",
645
803
  setup(build) {
646
804
  build.onResolve({ filter: /^@symbo\.ls\// }, (args) => {
647
805
  const pkg = args.path.replace("@symbo.ls/", "");
648
- for (const dir of ["packages", "plugins"]) {
649
- const src = (0, import_path.resolve)(monorepoRoot, dir, pkg, "src", "index.js");
650
- if (existsSync2(src)) return { path: src };
651
- const dist = (0, import_path.resolve)(monorepoRoot, dir, pkg, "index.js");
652
- if (existsSync2(dist)) return { path: dist };
806
+ if (ws.isMonorepo) {
807
+ for (const dir of ["packages", "plugins"]) {
808
+ const src = (0, import_path.resolve)(ws.monorepoRoot, dir, pkg, "src", "index.js");
809
+ if (existsSync2(src)) return { path: src };
810
+ const dist = (0, import_path.resolve)(ws.monorepoRoot, dir, pkg, "index.js");
811
+ if (existsSync2(dist)) return { path: dist };
812
+ }
813
+ const blank = (0, import_path.resolve)(ws.monorepoRoot, "packages", "default-config", "blank", "index.js");
814
+ if (pkg === "default-config" && existsSync2(blank)) return { path: blank };
815
+ } else {
816
+ const resolved = resolveSymbolsPackage(ws, pkg, "src", "index.js");
817
+ if (resolved && existsSync2(resolved)) return { path: resolved };
818
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, "index.js");
819
+ if (resolvedIdx && existsSync2(resolvedIdx)) return { path: resolvedIdx };
820
+ if (pkg === "default-config") {
821
+ const blank = resolveSymbolsPackage(ws, "default-config", "blank", "index.js");
822
+ if (blank && existsSync2(blank)) return { path: blank };
823
+ }
653
824
  }
654
- const blank = (0, import_path.resolve)(monorepoRoot, "packages", "default-config", "blank", "index.js");
655
- if (pkg === "default-config" && existsSync2(blank)) return { path: blank };
656
825
  });
657
826
  build.onResolve({ filter: /^@domql\// }, (args) => {
658
827
  const pkg = args.path.replace("@domql/", "");
659
- const src = (0, import_path.resolve)(monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
660
- if (existsSync2(src)) return { path: src };
828
+ if (ws.isMonorepo) {
829
+ const src = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
830
+ if (existsSync2(src)) return { path: src };
831
+ } else {
832
+ const resolved = resolveDomqlPackage(ws, pkg, "src", "index.js");
833
+ if (resolved && existsSync2(resolved)) return { path: resolved };
834
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, "index.js");
835
+ if (resolvedIdx && existsSync2(resolvedIdx)) return { path: resolvedIdx };
836
+ }
661
837
  });
662
838
  }
663
839
  };
@@ -670,6 +846,11 @@ const generateGlobalCSS = async (ds, config) => {
670
846
  write: true,
671
847
  logLevel: "silent",
672
848
  plugins: [workspacePlugin],
849
+ nodePaths: ws.isMonorepo ? [(0, import_path.resolve)(ws.monorepoRoot, "node_modules")] : [
850
+ ...ws.smblsRoot ? [(0, import_path.resolve)(ws.smblsRoot, "node_modules")] : [],
851
+ ...ws.projectRoot ? [(0, import_path.resolve)(ws.projectRoot, "node_modules")] : [],
852
+ ...ws.smblsRoot ? [(0, import_path.resolve)(ws.smblsRoot, "..", "..", "node_modules")] : []
853
+ ].filter((p) => existsSync2(p)),
673
854
  external: ["fs", "path", "os", "crypto", "url", "http", "https", "stream", "util", "events", "buffer", "child_process", "worker_threads", "net", "tls", "dns", "dgram", "zlib", "assert", "querystring", "string_decoder", "readline", "perf_hooks", "async_hooks", "v8", "vm", "cluster", "inspector", "module", "process", "tty", "color-contrast-checker"]
674
855
  });
675
856
  const mod = await import(`file://${tmpOut}`);
package/dist/esm/env.js CHANGED
@@ -49,10 +49,24 @@ const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html
49
49
  if (!window.sessionStorage) window.sessionStorage = createStorage();
50
50
  if (!globalThis.localStorage) globalThis.localStorage = window.localStorage;
51
51
  if (!globalThis.sessionStorage) globalThis.sessionStorage = window.sessionStorage;
52
+ if (!window.MutationObserver) {
53
+ window.MutationObserver = class MutationObserver {
54
+ constructor() {
55
+ }
56
+ observe() {
57
+ }
58
+ disconnect() {
59
+ }
60
+ takeRecords() {
61
+ return [];
62
+ }
63
+ };
64
+ }
52
65
  globalThis.window = window;
53
66
  globalThis.document = document;
54
67
  globalThis.Node = window.Node || globalThis.Node;
55
68
  globalThis.HTMLElement = window.HTMLElement || globalThis.HTMLElement;
69
+ globalThis.MutationObserver = window.MutationObserver;
56
70
  globalThis.Window = window.constructor;
57
71
  return { window, document };
58
72
  };
@@ -173,7 +173,8 @@ const fetchSSRTranslations = async (data) => {
173
173
  if (result.translations) {
174
174
  translations[lang] = result.translations;
175
175
  }
176
- } catch {
176
+ } catch (e) {
177
+ console.warn("[brender] SSR translation fetch failed:", e.message);
177
178
  }
178
179
  })
179
180
  );
@@ -1,7 +1,8 @@
1
- import { resolve, join } from "path";
1
+ import { resolve, join, dirname } from "path";
2
2
  import { existsSync, writeFileSync, unlinkSync, readFileSync, realpathSync } from "fs";
3
3
  import { tmpdir } from "os";
4
4
  import { randomBytes } from "crypto";
5
+ import { createRequire } from "module";
5
6
  import { createEnv } from "./env.js";
6
7
  import { resetKeys, assignKeys, mapKeysToElements } from "./keys.js";
7
8
  import { extractMetadata, generateHeadHtml } from "./metadata.js";
@@ -82,71 +83,224 @@ const safeJsonReplacer = () => {
82
83
  return value;
83
84
  };
84
85
  };
86
+ const _brenderRequire = createRequire(import.meta.url);
87
+ const detectWorkspace = () => {
88
+ const brenderDir = realpathSync(new URL(".", import.meta.url).pathname);
89
+ const monorepoRoot = resolve(brenderDir, "../..");
90
+ const isMonorepo = existsSync(resolve(monorepoRoot, "packages", "smbls", "src", "createDomql.js"));
91
+ if (isMonorepo) {
92
+ return { isMonorepo: true, monorepoRoot };
93
+ }
94
+ let smblsRoot;
95
+ try {
96
+ const smblsPkg = _brenderRequire.resolve("smbls/package.json");
97
+ smblsRoot = dirname(smblsPkg);
98
+ } catch {
99
+ let dir2 = brenderDir;
100
+ while (dir2 !== dirname(dir2)) {
101
+ const candidate = resolve(dir2, "node_modules", "smbls");
102
+ if (existsSync(resolve(candidate, "package.json"))) {
103
+ smblsRoot = candidate;
104
+ break;
105
+ }
106
+ dir2 = dirname(dir2);
107
+ }
108
+ }
109
+ let _smblsRequire = _brenderRequire;
110
+ if (smblsRoot) {
111
+ try {
112
+ _smblsRequire = createRequire(resolve(smblsRoot, "package.json"));
113
+ } catch {
114
+ }
115
+ }
116
+ let projectRoot;
117
+ let dir = process.cwd();
118
+ while (dir !== dirname(dir)) {
119
+ if (existsSync(resolve(dir, "package.json"))) {
120
+ projectRoot = dir;
121
+ break;
122
+ }
123
+ dir = dirname(dir);
124
+ }
125
+ let _projectRequire = _smblsRequire;
126
+ if (projectRoot) {
127
+ try {
128
+ _projectRequire = createRequire(resolve(projectRoot, "package.json"));
129
+ } catch {
130
+ }
131
+ }
132
+ return { isMonorepo: false, smblsRoot, brenderDir, projectRoot, _smblsRequire, _projectRequire };
133
+ };
134
+ const tryRequireResolve = (ws, specifier) => {
135
+ const requireFns = ws.isMonorepo ? [_brenderRequire] : [ws._smblsRequire, ws._projectRequire, _brenderRequire].filter(Boolean);
136
+ for (const req of requireFns) {
137
+ try {
138
+ return req.resolve(specifier);
139
+ } catch {
140
+ }
141
+ }
142
+ return null;
143
+ };
144
+ const resolvePackagePath = (ws, pkgName, ...subpath) => {
145
+ if (ws.isMonorepo) {
146
+ return resolve(ws.monorepoRoot, "packages", pkgName, ...subpath);
147
+ }
148
+ const pkgJson = tryRequireResolve(ws, `${pkgName}/package.json`);
149
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath);
150
+ return null;
151
+ };
152
+ const resolvePluginPath = (ws, pluginName, ...subpath) => {
153
+ if (ws.isMonorepo) {
154
+ return resolve(ws.monorepoRoot, "plugins", pluginName, ...subpath);
155
+ }
156
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pluginName}/package.json`);
157
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath);
158
+ return null;
159
+ };
160
+ const resolveSymbolsPackage = (ws, pkg, ...subpath) => {
161
+ if (ws.isMonorepo) {
162
+ for (const dir of ["packages", "plugins"]) {
163
+ const src = resolve(ws.monorepoRoot, dir, pkg, ...subpath);
164
+ if (existsSync(src)) return src;
165
+ }
166
+ return null;
167
+ }
168
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pkg}/package.json`);
169
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath);
170
+ return null;
171
+ };
172
+ const resolveDomqlPackage = (ws, pkg, ...subpath) => {
173
+ if (ws.isMonorepo) {
174
+ return resolve(ws.monorepoRoot, "packages", "domql", "packages", pkg, ...subpath);
175
+ }
176
+ const pkgJson = tryRequireResolve(ws, `@domql/${pkg}/package.json`);
177
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath);
178
+ return null;
179
+ };
85
180
  let _cachedCreateDomql = null;
86
181
  const bundleCreateDomql = async () => {
87
182
  if (_cachedCreateDomql) return _cachedCreateDomql;
88
- const brenderDir = realpathSync(new URL(".", import.meta.url).pathname);
89
- const monorepoRoot = resolve(brenderDir, "../..");
90
- const entry = resolve(monorepoRoot, "packages", "smbls", "src", "createDomql.js");
183
+ const ws = detectWorkspace();
184
+ let entry;
185
+ if (ws.isMonorepo) {
186
+ entry = resolve(ws.monorepoRoot, "packages", "smbls", "src", "createDomql.js");
187
+ } else if (ws.smblsRoot) {
188
+ const srcEntry = resolve(ws.smblsRoot, "src", "createDomql.js");
189
+ const distEntry = resolve(ws.smblsRoot, "dist", "esm", "src", "createDomql.js");
190
+ entry = existsSync(srcEntry) ? srcEntry : distEntry;
191
+ }
192
+ if (!entry || !existsSync(entry)) {
193
+ throw new Error(`brender: cannot find createDomql.js (isMonorepo=${ws.isMonorepo}, entry=${entry})`);
194
+ }
91
195
  const esbuild = await import("esbuild");
92
196
  const outFile = join(tmpdir(), `br_createDomql_${randomBytes(6).toString("hex")}.mjs`);
197
+ const tryResolve = (base) => {
198
+ if (!base) return null;
199
+ const src = resolve(base, "src", "index.js");
200
+ if (existsSync(src)) return src;
201
+ const idx = resolve(base, "index.js");
202
+ if (existsSync(idx)) return idx;
203
+ return null;
204
+ };
93
205
  const workspacePlugin = {
94
206
  name: "workspace-resolve",
95
207
  setup(build) {
96
208
  build.onResolve({ filter: /^smbls/ }, (args) => {
97
209
  const subpath = args.path.replace(/^smbls\/?/, "");
210
+ const smblsBase = ws.isMonorepo ? resolve(ws.monorepoRoot, "packages", "smbls") : ws.smblsRoot;
211
+ if (!smblsBase) return;
98
212
  if (!subpath) {
99
- const src = resolve(monorepoRoot, "packages", "smbls", "src", "index.js");
100
- if (existsSync(src)) return { path: src };
213
+ const r = tryResolve(smblsBase);
214
+ if (r) return { path: r };
215
+ return;
101
216
  }
102
- const full = resolve(monorepoRoot, "packages", "smbls", subpath);
217
+ const full = resolve(smblsBase, subpath);
103
218
  if (existsSync(full)) return { path: full };
104
219
  if (existsSync(full + ".js")) return { path: full + ".js" };
105
220
  const idx = resolve(full, "index.js");
106
221
  if (existsSync(idx)) return { path: idx };
107
222
  });
108
223
  build.onResolve({ filter: /^domql$/ }, (args) => {
109
- const src = resolve(monorepoRoot, "packages", "domql", "src", "index.js");
110
- if (existsSync(src)) return { path: src };
111
- const dist = resolve(monorepoRoot, "packages", "domql", "index.js");
112
- if (existsSync(dist)) return { path: dist };
224
+ if (ws.isMonorepo) {
225
+ const src = resolve(ws.monorepoRoot, "packages", "domql", "src", "index.js");
226
+ if (existsSync(src)) return { path: src };
227
+ const dist = resolve(ws.monorepoRoot, "packages", "domql", "index.js");
228
+ if (existsSync(dist)) return { path: dist };
229
+ } else {
230
+ try {
231
+ const pkgJson = _require.resolve("domql/package.json");
232
+ const r = tryResolve(dirname(pkgJson));
233
+ if (r) return { path: r };
234
+ } catch {
235
+ }
236
+ }
113
237
  });
114
238
  build.onResolve({ filter: /^@symbo\.ls\// }, (args) => {
115
239
  const pkg = args.path.replace("@symbo.ls/", "");
116
240
  if (pkg === "sync") return { path: "sync-stub", namespace: "brender-stub" };
117
- for (const dir of ["packages", "plugins"]) {
118
- const src = resolve(monorepoRoot, dir, pkg, "src", "index.js");
119
- if (existsSync(src)) return { path: src };
120
- const dist = resolve(monorepoRoot, dir, pkg, "index.js");
121
- if (existsSync(dist)) return { path: dist };
241
+ if (ws.isMonorepo) {
242
+ for (const dir of ["packages", "plugins"]) {
243
+ const src = resolve(ws.monorepoRoot, dir, pkg, "src", "index.js");
244
+ if (existsSync(src)) return { path: src };
245
+ const dist = resolve(ws.monorepoRoot, dir, pkg, "index.js");
246
+ if (existsSync(dist)) return { path: dist };
247
+ }
248
+ const blank = resolve(ws.monorepoRoot, "packages", "default-config", "blank", "index.js");
249
+ if (pkg === "default-config" && existsSync(blank)) return { path: blank };
250
+ } else {
251
+ const resolved = resolveSymbolsPackage(ws, pkg, "src", "index.js");
252
+ if (resolved && existsSync(resolved)) return { path: resolved };
253
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, "index.js");
254
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx };
255
+ if (pkg === "default-config") {
256
+ const blank = resolveSymbolsPackage(ws, "default-config", "blank", "index.js");
257
+ if (blank && existsSync(blank)) return { path: blank };
258
+ }
122
259
  }
123
- const blank = resolve(monorepoRoot, "packages", "default-config", "blank", "index.js");
124
- if (pkg === "default-config" && existsSync(blank)) return { path: blank };
125
260
  });
126
261
  build.onResolve({ filter: /^@domql\// }, (args) => {
127
262
  const pkg = args.path.replace("@domql/", "");
128
- const src = resolve(monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
129
- if (existsSync(src)) return { path: src };
130
- const dist = resolve(monorepoRoot, "packages", "domql", "packages", pkg, "index.js");
131
- if (existsSync(dist)) return { path: dist };
263
+ if (ws.isMonorepo) {
264
+ const src = resolve(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
265
+ if (existsSync(src)) return { path: src };
266
+ const dist = resolve(ws.monorepoRoot, "packages", "domql", "packages", pkg, "index.js");
267
+ if (existsSync(dist)) return { path: dist };
268
+ } else {
269
+ const resolved = resolveDomqlPackage(ws, pkg, "src", "index.js");
270
+ if (resolved && existsSync(resolved)) return { path: resolved };
271
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, "index.js");
272
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx };
273
+ }
132
274
  });
133
275
  build.onResolve({ filter: /^css-in-props/ }, (args) => {
134
- const base = resolve(monorepoRoot, "packages", "css-in-props");
276
+ let base;
277
+ if (ws.isMonorepo) {
278
+ base = resolve(ws.monorepoRoot, "packages", "css-in-props");
279
+ } else {
280
+ const pkgJson = tryRequireResolve(ws, "css-in-props/package.json");
281
+ if (pkgJson) base = dirname(pkgJson);
282
+ }
283
+ if (!base) return;
135
284
  const subpath = args.path.replace(/^css-in-props\/?/, "");
136
285
  if (subpath) {
137
286
  const full = resolve(base, subpath);
138
- const idx2 = resolve(full, "index.js");
139
- if (existsSync(idx2)) return { path: idx2 };
287
+ const idx = resolve(full, "index.js");
288
+ if (existsSync(idx)) return { path: idx };
140
289
  if (existsSync(full + ".js")) return { path: full + ".js" };
141
290
  if (existsSync(full)) return { path: full };
142
291
  }
143
- const src = resolve(base, "src", "index.js");
144
- if (existsSync(src)) return { path: src };
145
- const idx = resolve(base, "index.js");
146
- if (existsSync(idx)) return { path: idx };
292
+ const r = tryResolve(base);
293
+ if (r) return { path: r };
147
294
  });
148
295
  build.onResolve({ filter: /^@emotion\// }, (args) => {
149
- const nm = resolve(monorepoRoot, "node_modules", args.path);
296
+ let nm;
297
+ if (ws.isMonorepo) {
298
+ nm = resolve(ws.monorepoRoot, "node_modules", args.path);
299
+ } else {
300
+ const pkgJson = tryRequireResolve(ws, `${args.path}/package.json`);
301
+ if (!pkgJson) return;
302
+ nm = dirname(pkgJson);
303
+ }
150
304
  if (existsSync(nm)) {
151
305
  const pkg = resolve(nm, "package.json");
152
306
  if (existsSync(pkg)) {
@@ -219,7 +373,11 @@ const bundleCreateDomql = async () => {
219
373
  write: true,
220
374
  logLevel: "warning",
221
375
  plugins: [workspacePlugin],
222
- nodePaths: [resolve(monorepoRoot, "node_modules")],
376
+ nodePaths: ws.isMonorepo ? [resolve(ws.monorepoRoot, "node_modules")] : [
377
+ ...ws.smblsRoot ? [resolve(ws.smblsRoot, "node_modules")] : [],
378
+ ...ws.projectRoot ? [resolve(ws.projectRoot, "node_modules")] : [],
379
+ ...ws.smblsRoot ? [resolve(ws.smblsRoot, "..", "..", "node_modules")] : []
380
+ ].filter((p) => existsSync(p)),
223
381
  supported: { "import-attributes": false },
224
382
  external: [
225
383
  "fs",
@@ -373,7 +531,8 @@ const render = async (data, options = {}) => {
373
531
  if (prefetch) {
374
532
  try {
375
533
  ssrTranslations = await fetchSSRTranslations(data);
376
- } catch {
534
+ } catch (e) {
535
+ console.warn("[brender] SSR translation fetch failed:", e.message);
377
536
  }
378
537
  }
379
538
  if (_prevLocPrefetch !== void 0) globalThis.location = _prevLocPrefetch;
@@ -599,26 +758,43 @@ const generateGlobalCSS = async (ds, config) => {
599
758
  globalThis.__BR_GLOBAL_CSS__ = result
600
759
  export default result
601
760
  `);
602
- const brenderDir = new URL(".", import.meta.url).pathname;
603
- const monorepoRoot = resolve(brenderDir, "../..");
761
+ const ws = detectWorkspace();
604
762
  const workspacePlugin = {
605
763
  name: "workspace-resolve",
606
764
  setup(build) {
607
765
  build.onResolve({ filter: /^@symbo\.ls\// }, (args) => {
608
766
  const pkg = args.path.replace("@symbo.ls/", "");
609
- for (const dir of ["packages", "plugins"]) {
610
- const src = resolve(monorepoRoot, dir, pkg, "src", "index.js");
611
- if (existsSync2(src)) return { path: src };
612
- const dist = resolve(monorepoRoot, dir, pkg, "index.js");
613
- if (existsSync2(dist)) return { path: dist };
767
+ if (ws.isMonorepo) {
768
+ for (const dir of ["packages", "plugins"]) {
769
+ const src = resolve(ws.monorepoRoot, dir, pkg, "src", "index.js");
770
+ if (existsSync2(src)) return { path: src };
771
+ const dist = resolve(ws.monorepoRoot, dir, pkg, "index.js");
772
+ if (existsSync2(dist)) return { path: dist };
773
+ }
774
+ const blank = resolve(ws.monorepoRoot, "packages", "default-config", "blank", "index.js");
775
+ if (pkg === "default-config" && existsSync2(blank)) return { path: blank };
776
+ } else {
777
+ const resolved = resolveSymbolsPackage(ws, pkg, "src", "index.js");
778
+ if (resolved && existsSync2(resolved)) return { path: resolved };
779
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, "index.js");
780
+ if (resolvedIdx && existsSync2(resolvedIdx)) return { path: resolvedIdx };
781
+ if (pkg === "default-config") {
782
+ const blank = resolveSymbolsPackage(ws, "default-config", "blank", "index.js");
783
+ if (blank && existsSync2(blank)) return { path: blank };
784
+ }
614
785
  }
615
- const blank = resolve(monorepoRoot, "packages", "default-config", "blank", "index.js");
616
- if (pkg === "default-config" && existsSync2(blank)) return { path: blank };
617
786
  });
618
787
  build.onResolve({ filter: /^@domql\// }, (args) => {
619
788
  const pkg = args.path.replace("@domql/", "");
620
- const src = resolve(monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
621
- if (existsSync2(src)) return { path: src };
789
+ if (ws.isMonorepo) {
790
+ const src = resolve(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
791
+ if (existsSync2(src)) return { path: src };
792
+ } else {
793
+ const resolved = resolveDomqlPackage(ws, pkg, "src", "index.js");
794
+ if (resolved && existsSync2(resolved)) return { path: resolved };
795
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, "index.js");
796
+ if (resolvedIdx && existsSync2(resolvedIdx)) return { path: resolvedIdx };
797
+ }
622
798
  });
623
799
  }
624
800
  };
@@ -631,6 +807,11 @@ const generateGlobalCSS = async (ds, config) => {
631
807
  write: true,
632
808
  logLevel: "silent",
633
809
  plugins: [workspacePlugin],
810
+ nodePaths: ws.isMonorepo ? [resolve(ws.monorepoRoot, "node_modules")] : [
811
+ ...ws.smblsRoot ? [resolve(ws.smblsRoot, "node_modules")] : [],
812
+ ...ws.projectRoot ? [resolve(ws.projectRoot, "node_modules")] : [],
813
+ ...ws.smblsRoot ? [resolve(ws.smblsRoot, "..", "..", "node_modules")] : []
814
+ ].filter((p) => existsSync2(p)),
634
815
  external: ["fs", "path", "os", "crypto", "url", "http", "https", "stream", "util", "events", "buffer", "child_process", "worker_threads", "net", "tls", "dns", "dgram", "zlib", "assert", "querystring", "string_decoder", "readline", "perf_hooks", "async_hooks", "v8", "vm", "cluster", "inspector", "module", "process", "tty", "color-contrast-checker"]
635
816
  });
636
817
  const mod = await import(`file://${tmpOut}`);
package/env.js CHANGED
@@ -48,12 +48,23 @@ export const createEnv = (html = '<!DOCTYPE html><html><head></head><body></body
48
48
  if (!globalThis.localStorage) globalThis.localStorage = window.localStorage
49
49
  if (!globalThis.sessionStorage) globalThis.sessionStorage = window.sessionStorage
50
50
 
51
+ // Stub MutationObserver for SSR (not provided by linkedom)
52
+ if (!window.MutationObserver) {
53
+ window.MutationObserver = class MutationObserver {
54
+ constructor () {}
55
+ observe () {}
56
+ disconnect () {}
57
+ takeRecords () { return [] }
58
+ }
59
+ }
60
+
51
61
  // Expose linkedom constructors on globalThis so @domql/utils isDOMNode
52
62
  // can use instanceof checks (it reads from globalThis.Node, etc.)
53
63
  globalThis.window = window
54
64
  globalThis.document = document
55
65
  globalThis.Node = window.Node || globalThis.Node
56
66
  globalThis.HTMLElement = window.HTMLElement || globalThis.HTMLElement
67
+ globalThis.MutationObserver = window.MutationObserver
57
68
  globalThis.Window = window.constructor
58
69
 
59
70
  return { window, document }
package/load.js CHANGED
@@ -38,9 +38,9 @@ const bundleAndImport = async (entryPath) => {
38
38
  return mod
39
39
  } catch {
40
40
  // Fallback: try raw import
41
- try { return await import(entryPath) } catch { return null }
41
+ try { return await import(entryPath) } catch { return null } // fallback: module not found via this resolver, trying next
42
42
  } finally {
43
- try { unlinkSync(outFile) } catch {}
43
+ try { unlinkSync(outFile) } catch {} // cleanup: ignore if temp file already removed
44
44
  }
45
45
  }
46
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/brender",
3
- "version": "3.7.6",
3
+ "version": "3.8.1",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "module": "./dist/esm/index.js",
@@ -36,7 +36,7 @@
36
36
  "dev:rita": "node examples/serve-rita.js"
37
37
  },
38
38
  "dependencies": {
39
- "@symbo.ls/helmet": "^3.7.6",
39
+ "@symbo.ls/helmet": "^3.8.1",
40
40
  "linkedom": "^0.16.8"
41
41
  },
42
42
  "devDependencies": {
package/prefetch.js CHANGED
@@ -278,7 +278,9 @@ export const fetchSSRTranslations = async (data) => {
278
278
  if (result.translations) {
279
279
  translations[lang] = result.translations
280
280
  }
281
- } catch {}
281
+ } catch (e) {
282
+ console.warn('[brender] SSR translation fetch failed:', e.message)
283
+ }
282
284
  })
283
285
  )
284
286
 
package/render.js CHANGED
@@ -1,7 +1,8 @@
1
- import { resolve, join } from 'path'
1
+ import { resolve, join, dirname } from 'path'
2
2
  import { existsSync, writeFileSync, unlinkSync, readFileSync, realpathSync } from 'fs'
3
3
  import { tmpdir } from 'os'
4
4
  import { randomBytes } from 'crypto'
5
+ import { createRequire } from 'module'
5
6
  import { createEnv } from './env.js'
6
7
  import { resetKeys, assignKeys, mapKeysToElements } from './keys.js'
7
8
  import { extractMetadata, generateHeadHtml } from './metadata.js'
@@ -100,6 +101,126 @@ const safeJsonReplacer = () => {
100
101
  }
101
102
  }
102
103
 
104
+ // ── Workspace detection ──────────────────────────────────────────────────────
105
+ // Detect whether brender is running inside the monorepo or as an installed
106
+ // npm package, and resolve paths accordingly.
107
+ const _brenderRequire = createRequire(import.meta.url)
108
+
109
+ const detectWorkspace = () => {
110
+ const brenderDir = realpathSync(new URL('.', import.meta.url).pathname)
111
+ const monorepoRoot = resolve(brenderDir, '../..')
112
+
113
+ // Check if monorepo layout exists (packages/smbls/src/createDomql.js)
114
+ const isMonorepo = existsSync(resolve(monorepoRoot, 'packages', 'smbls', 'src', 'createDomql.js'))
115
+
116
+ if (isMonorepo) {
117
+ return { isMonorepo: true, monorepoRoot }
118
+ }
119
+
120
+ // npm install context — resolve packages from node_modules
121
+ // Find the smbls package root via require.resolve
122
+ let smblsRoot
123
+ try {
124
+ const smblsPkg = _brenderRequire.resolve('smbls/package.json')
125
+ smblsRoot = dirname(smblsPkg)
126
+ } catch {
127
+ // Fallback: walk up from brenderDir looking for node_modules/smbls
128
+ let dir = brenderDir
129
+ while (dir !== dirname(dir)) {
130
+ const candidate = resolve(dir, 'node_modules', 'smbls')
131
+ if (existsSync(resolve(candidate, 'package.json'))) {
132
+ smblsRoot = candidate
133
+ break
134
+ }
135
+ dir = dirname(dir)
136
+ }
137
+ }
138
+
139
+ // Create a require function from smbls root so we can resolve its dependencies
140
+ // (e.g. @symbo.ls/scratch, @symbo.ls/default-config which are deps of smbls, not brender)
141
+ let _smblsRequire = _brenderRequire
142
+ if (smblsRoot) {
143
+ try {
144
+ _smblsRequire = createRequire(resolve(smblsRoot, 'package.json'))
145
+ } catch {} // fallback: module not found via this resolver, trying next
146
+ }
147
+
148
+ // Also find the project root (where the user's package.json is)
149
+ let projectRoot
150
+ let dir = process.cwd()
151
+ while (dir !== dirname(dir)) {
152
+ if (existsSync(resolve(dir, 'package.json'))) {
153
+ projectRoot = dir
154
+ break
155
+ }
156
+ dir = dirname(dir)
157
+ }
158
+
159
+ // Create a require from project root for hoisted deps
160
+ let _projectRequire = _smblsRequire
161
+ if (projectRoot) {
162
+ try {
163
+ _projectRequire = createRequire(resolve(projectRoot, 'package.json'))
164
+ } catch {} // fallback: module not found via this resolver, trying next
165
+ }
166
+
167
+ return { isMonorepo: false, smblsRoot, brenderDir, projectRoot, _smblsRequire, _projectRequire }
168
+ }
169
+
170
+ // Try multiple require functions to resolve a package (brender scope, smbls scope, project scope)
171
+ const tryRequireResolve = (ws, specifier) => {
172
+ const requireFns = ws.isMonorepo
173
+ ? [_brenderRequire]
174
+ : [ws._smblsRequire, ws._projectRequire, _brenderRequire].filter(Boolean)
175
+ for (const req of requireFns) {
176
+ try {
177
+ return req.resolve(specifier)
178
+ } catch {} // fallback: module not found via this resolver, trying next
179
+ }
180
+ return null
181
+ }
182
+
183
+ // Resolve a package path — monorepo layout or node_modules
184
+ const resolvePackagePath = (ws, pkgName, ...subpath) => {
185
+ if (ws.isMonorepo) {
186
+ return resolve(ws.monorepoRoot, 'packages', pkgName, ...subpath)
187
+ }
188
+ const pkgJson = tryRequireResolve(ws, `${pkgName}/package.json`)
189
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath)
190
+ return null
191
+ }
192
+
193
+ const resolvePluginPath = (ws, pluginName, ...subpath) => {
194
+ if (ws.isMonorepo) {
195
+ return resolve(ws.monorepoRoot, 'plugins', pluginName, ...subpath)
196
+ }
197
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pluginName}/package.json`)
198
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath)
199
+ return null
200
+ }
201
+
202
+ const resolveSymbolsPackage = (ws, pkg, ...subpath) => {
203
+ if (ws.isMonorepo) {
204
+ for (const dir of ['packages', 'plugins']) {
205
+ const src = resolve(ws.monorepoRoot, dir, pkg, ...subpath)
206
+ if (existsSync(src)) return src
207
+ }
208
+ return null
209
+ }
210
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pkg}/package.json`)
211
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath)
212
+ return null
213
+ }
214
+
215
+ const resolveDomqlPackage = (ws, pkg, ...subpath) => {
216
+ if (ws.isMonorepo) {
217
+ return resolve(ws.monorepoRoot, 'packages', 'domql', 'packages', pkg, ...subpath)
218
+ }
219
+ const pkgJson = tryRequireResolve(ws, `@domql/${pkg}/package.json`)
220
+ if (pkgJson) return resolve(dirname(pkgJson), ...subpath)
221
+ return null
222
+ }
223
+
103
224
  // ── Bundled import of createDomqlElement ──────────────────────────────────────
104
225
  // The smbls source tree uses extensionless/directory imports that Node.js ESM
105
226
  // cannot resolve natively. We bundle createDomql.js with esbuild (once, cached)
@@ -109,24 +230,52 @@ let _cachedCreateDomql = null
109
230
  const bundleCreateDomql = async () => {
110
231
  if (_cachedCreateDomql) return _cachedCreateDomql
111
232
 
112
- const brenderDir = realpathSync(new URL('.', import.meta.url).pathname)
113
- const monorepoRoot = resolve(brenderDir, '../..')
114
- const entry = resolve(monorepoRoot, 'packages', 'smbls', 'src', 'createDomql.js')
233
+ const ws = detectWorkspace()
234
+
235
+ // Resolve entry point
236
+ let entry
237
+ if (ws.isMonorepo) {
238
+ entry = resolve(ws.monorepoRoot, 'packages', 'smbls', 'src', 'createDomql.js')
239
+ } else if (ws.smblsRoot) {
240
+ // Prefer src/ (shipped in smbls package), fall back to dist
241
+ const srcEntry = resolve(ws.smblsRoot, 'src', 'createDomql.js')
242
+ const distEntry = resolve(ws.smblsRoot, 'dist', 'esm', 'src', 'createDomql.js')
243
+ entry = existsSync(srcEntry) ? srcEntry : distEntry
244
+ }
245
+
246
+ if (!entry || !existsSync(entry)) {
247
+ throw new Error(`brender: cannot find createDomql.js (isMonorepo=${ws.isMonorepo}, entry=${entry})`)
248
+ }
115
249
 
116
250
  const esbuild = await import('esbuild')
117
251
  const outFile = join(tmpdir(), `br_createDomql_${randomBytes(6).toString('hex')}.mjs`)
118
252
 
253
+ // Helper to find a file trying src/ then root index.js
254
+ const tryResolve = (base) => {
255
+ if (!base) return null
256
+ const src = resolve(base, 'src', 'index.js')
257
+ if (existsSync(src)) return src
258
+ const idx = resolve(base, 'index.js')
259
+ if (existsSync(idx)) return idx
260
+ return null
261
+ }
262
+
119
263
  const workspacePlugin = {
120
264
  name: 'workspace-resolve',
121
265
  setup (build) {
122
266
  // Resolve smbls bare import
123
267
  build.onResolve({ filter: /^smbls/ }, (args) => {
124
268
  const subpath = args.path.replace(/^smbls\/?/, '')
269
+ const smblsBase = ws.isMonorepo
270
+ ? resolve(ws.monorepoRoot, 'packages', 'smbls')
271
+ : ws.smblsRoot
272
+ if (!smblsBase) return
125
273
  if (!subpath) {
126
- const src = resolve(monorepoRoot, 'packages', 'smbls', 'src', 'index.js')
127
- if (existsSync(src)) return { path: src }
274
+ const r = tryResolve(smblsBase)
275
+ if (r) return { path: r }
276
+ return
128
277
  }
129
- const full = resolve(monorepoRoot, 'packages', 'smbls', subpath)
278
+ const full = resolve(smblsBase, subpath)
130
279
  if (existsSync(full)) return { path: full }
131
280
  if (existsSync(full + '.js')) return { path: full + '.js' }
132
281
  const idx = resolve(full, 'index.js')
@@ -134,35 +283,69 @@ const bundleCreateDomql = async () => {
134
283
  })
135
284
  // Resolve domql bare import
136
285
  build.onResolve({ filter: /^domql$/ }, (args) => {
137
- const src = resolve(monorepoRoot, 'packages', 'domql', 'src', 'index.js')
138
- if (existsSync(src)) return { path: src }
139
- const dist = resolve(monorepoRoot, 'packages', 'domql', 'index.js')
140
- if (existsSync(dist)) return { path: dist }
286
+ if (ws.isMonorepo) {
287
+ const src = resolve(ws.monorepoRoot, 'packages', 'domql', 'src', 'index.js')
288
+ if (existsSync(src)) return { path: src }
289
+ const dist = resolve(ws.monorepoRoot, 'packages', 'domql', 'index.js')
290
+ if (existsSync(dist)) return { path: dist }
291
+ } else {
292
+ try {
293
+ const pkgJson = _require.resolve('domql/package.json')
294
+ const r = tryResolve(dirname(pkgJson))
295
+ if (r) return { path: r }
296
+ } catch {} // fallback: module not found via this resolver, trying next
297
+ }
141
298
  })
142
299
  // Resolve @symbo.ls/* packages (skip sync — stubbed above)
143
300
  build.onResolve({ filter: /^@symbo\.ls\// }, args => {
144
301
  const pkg = args.path.replace('@symbo.ls/', '')
145
302
  if (pkg === 'sync') return { path: 'sync-stub', namespace: 'brender-stub' }
146
- for (const dir of ['packages', 'plugins']) {
147
- const src = resolve(monorepoRoot, dir, pkg, 'src', 'index.js')
148
- if (existsSync(src)) return { path: src }
149
- const dist = resolve(monorepoRoot, dir, pkg, 'index.js')
150
- if (existsSync(dist)) return { path: dist }
303
+ if (ws.isMonorepo) {
304
+ for (const dir of ['packages', 'plugins']) {
305
+ const src = resolve(ws.monorepoRoot, dir, pkg, 'src', 'index.js')
306
+ if (existsSync(src)) return { path: src }
307
+ const dist = resolve(ws.monorepoRoot, dir, pkg, 'index.js')
308
+ if (existsSync(dist)) return { path: dist }
309
+ }
310
+ const blank = resolve(ws.monorepoRoot, 'packages', 'default-config', 'blank', 'index.js')
311
+ if (pkg === 'default-config' && existsSync(blank)) return { path: blank }
312
+ } else {
313
+ const resolved = resolveSymbolsPackage(ws, pkg, 'src', 'index.js')
314
+ if (resolved && existsSync(resolved)) return { path: resolved }
315
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, 'index.js')
316
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx }
317
+ // default-config blank
318
+ if (pkg === 'default-config') {
319
+ const blank = resolveSymbolsPackage(ws, 'default-config', 'blank', 'index.js')
320
+ if (blank && existsSync(blank)) return { path: blank }
321
+ }
151
322
  }
152
- const blank = resolve(monorepoRoot, 'packages', 'default-config', 'blank', 'index.js')
153
- if (pkg === 'default-config' && existsSync(blank)) return { path: blank }
154
323
  })
155
324
  // Resolve @domql/* packages
156
325
  build.onResolve({ filter: /^@domql\// }, args => {
157
326
  const pkg = args.path.replace('@domql/', '')
158
- const src = resolve(monorepoRoot, 'packages', 'domql', 'packages', pkg, 'src', 'index.js')
159
- if (existsSync(src)) return { path: src }
160
- const dist = resolve(monorepoRoot, 'packages', 'domql', 'packages', pkg, 'index.js')
161
- if (existsSync(dist)) return { path: dist }
327
+ if (ws.isMonorepo) {
328
+ const src = resolve(ws.monorepoRoot, 'packages', 'domql', 'packages', pkg, 'src', 'index.js')
329
+ if (existsSync(src)) return { path: src }
330
+ const dist = resolve(ws.monorepoRoot, 'packages', 'domql', 'packages', pkg, 'index.js')
331
+ if (existsSync(dist)) return { path: dist }
332
+ } else {
333
+ const resolved = resolveDomqlPackage(ws, pkg, 'src', 'index.js')
334
+ if (resolved && existsSync(resolved)) return { path: resolved }
335
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, 'index.js')
336
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx }
337
+ }
162
338
  })
163
339
  // Resolve css-in-props
164
340
  build.onResolve({ filter: /^css-in-props/ }, args => {
165
- const base = resolve(monorepoRoot, 'packages', 'css-in-props')
341
+ let base
342
+ if (ws.isMonorepo) {
343
+ base = resolve(ws.monorepoRoot, 'packages', 'css-in-props')
344
+ } else {
345
+ const pkgJson = tryRequireResolve(ws, 'css-in-props/package.json')
346
+ if (pkgJson) base = dirname(pkgJson)
347
+ }
348
+ if (!base) return
166
349
  const subpath = args.path.replace(/^css-in-props\/?/, '')
167
350
  if (subpath) {
168
351
  const full = resolve(base, subpath)
@@ -171,14 +354,19 @@ const bundleCreateDomql = async () => {
171
354
  if (existsSync(full + '.js')) return { path: full + '.js' }
172
355
  if (existsSync(full)) return { path: full }
173
356
  }
174
- const src = resolve(base, 'src', 'index.js')
175
- if (existsSync(src)) return { path: src }
176
- const idx = resolve(base, 'index.js')
177
- if (existsSync(idx)) return { path: idx }
357
+ const r = tryResolve(base)
358
+ if (r) return { path: r }
178
359
  })
179
- // Resolve @emotion/* from monorepo node_modules
360
+ // Resolve @emotion/* from node_modules
180
361
  build.onResolve({ filter: /^@emotion\// }, args => {
181
- const nm = resolve(monorepoRoot, 'node_modules', args.path)
362
+ let nm
363
+ if (ws.isMonorepo) {
364
+ nm = resolve(ws.monorepoRoot, 'node_modules', args.path)
365
+ } else {
366
+ const pkgJson = tryRequireResolve(ws, `${args.path}/package.json`)
367
+ if (!pkgJson) return
368
+ nm = dirname(pkgJson)
369
+ }
182
370
  if (existsSync(nm)) {
183
371
  const pkg = resolve(nm, 'package.json')
184
372
  if (existsSync(pkg)) {
@@ -186,7 +374,7 @@ const bundleCreateDomql = async () => {
186
374
  const p = JSON.parse(readFileSync(pkg, 'utf8'))
187
375
  const main = p.module || p.main || 'dist/emotion-css.esm.js'
188
376
  return { path: resolve(nm, main) }
189
- } catch {}
377
+ } catch {} // expected: value is not a valid JSON
190
378
  }
191
379
  return { path: nm }
192
380
  }
@@ -263,7 +451,13 @@ const bundleCreateDomql = async () => {
263
451
  write: true,
264
452
  logLevel: 'warning',
265
453
  plugins: [workspacePlugin],
266
- nodePaths: [resolve(monorepoRoot, 'node_modules')],
454
+ nodePaths: ws.isMonorepo
455
+ ? [resolve(ws.monorepoRoot, 'node_modules')]
456
+ : [
457
+ ...(ws.smblsRoot ? [resolve(ws.smblsRoot, 'node_modules')] : []),
458
+ ...(ws.projectRoot ? [resolve(ws.projectRoot, 'node_modules')] : []),
459
+ ...(ws.smblsRoot ? [resolve(ws.smblsRoot, '..', '..', 'node_modules')] : [])
460
+ ].filter(p => existsSync(p)),
267
461
  supported: { 'import-attributes': false },
268
462
  external: [
269
463
  'fs', 'path', 'os', 'crypto', 'url', 'http', 'https', 'stream',
@@ -276,7 +470,7 @@ const bundleCreateDomql = async () => {
276
470
  })
277
471
 
278
472
  const mod = await import(`file://${outFile}`)
279
- try { unlinkSync(outFile) } catch {}
473
+ try { unlinkSync(outFile) } catch {} // cleanup: ignore if temp file already removed
280
474
 
281
475
  _cachedCreateDomql = mod
282
476
  return mod
@@ -422,7 +616,9 @@ export const render = async (data, options = {}) => {
422
616
  if (prefetch) {
423
617
  try {
424
618
  ssrTranslations = await fetchSSRTranslations(data)
425
- } catch {}
619
+ } catch (e) {
620
+ console.warn('[brender] SSR translation fetch failed:', e.message)
621
+ }
426
622
  }
427
623
 
428
624
  // Restore location/window before createEnv sets them properly
@@ -754,10 +950,8 @@ const generateGlobalCSS = async (ds, config) => {
754
950
  export default result
755
951
  `)
756
952
 
757
- // Resolve the monorepo root from the brender plugin location
758
- // so esbuild can find @symbo.ls/* packages
759
- const brenderDir = new URL('.', import.meta.url).pathname
760
- const monorepoRoot = resolve(brenderDir, '../..')
953
+ // Detect workspace layout (monorepo vs npm install)
954
+ const ws = detectWorkspace()
761
955
 
762
956
  // Workspace resolve plugin: maps @symbo.ls/* and @domql/* to source paths
763
957
  const workspacePlugin = {
@@ -765,21 +959,37 @@ const generateGlobalCSS = async (ds, config) => {
765
959
  setup (build) {
766
960
  build.onResolve({ filter: /^@symbo\.ls\// }, args => {
767
961
  const pkg = args.path.replace('@symbo.ls/', '')
768
- // Try packages/ then plugins/
769
- for (const dir of ['packages', 'plugins']) {
770
- const src = resolve(monorepoRoot, dir, pkg, 'src', 'index.js')
771
- if (existsSync(src)) return { path: src }
772
- const dist = resolve(monorepoRoot, dir, pkg, 'index.js')
773
- if (existsSync(dist)) return { path: dist }
962
+ if (ws.isMonorepo) {
963
+ for (const dir of ['packages', 'plugins']) {
964
+ const src = resolve(ws.monorepoRoot, dir, pkg, 'src', 'index.js')
965
+ if (existsSync(src)) return { path: src }
966
+ const dist = resolve(ws.monorepoRoot, dir, pkg, 'index.js')
967
+ if (existsSync(dist)) return { path: dist }
968
+ }
969
+ const blank = resolve(ws.monorepoRoot, 'packages', 'default-config', 'blank', 'index.js')
970
+ if (pkg === 'default-config' && existsSync(blank)) return { path: blank }
971
+ } else {
972
+ const resolved = resolveSymbolsPackage(ws, pkg, 'src', 'index.js')
973
+ if (resolved && existsSync(resolved)) return { path: resolved }
974
+ const resolvedIdx = resolveSymbolsPackage(ws, pkg, 'index.js')
975
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx }
976
+ if (pkg === 'default-config') {
977
+ const blank = resolveSymbolsPackage(ws, 'default-config', 'blank', 'index.js')
978
+ if (blank && existsSync(blank)) return { path: blank }
979
+ }
774
980
  }
775
- // default-config special case
776
- const blank = resolve(monorepoRoot, 'packages', 'default-config', 'blank', 'index.js')
777
- if (pkg === 'default-config' && existsSync(blank)) return { path: blank }
778
981
  })
779
982
  build.onResolve({ filter: /^@domql\// }, args => {
780
983
  const pkg = args.path.replace('@domql/', '')
781
- const src = resolve(monorepoRoot, 'packages', 'domql', 'packages', pkg, 'src', 'index.js')
782
- if (existsSync(src)) return { path: src }
984
+ if (ws.isMonorepo) {
985
+ const src = resolve(ws.monorepoRoot, 'packages', 'domql', 'packages', pkg, 'src', 'index.js')
986
+ if (existsSync(src)) return { path: src }
987
+ } else {
988
+ const resolved = resolveDomqlPackage(ws, pkg, 'src', 'index.js')
989
+ if (resolved && existsSync(resolved)) return { path: resolved }
990
+ const resolvedIdx = resolveDomqlPackage(ws, pkg, 'index.js')
991
+ if (resolvedIdx && existsSync(resolvedIdx)) return { path: resolvedIdx }
992
+ }
783
993
  })
784
994
  }
785
995
  }
@@ -793,13 +1003,20 @@ const generateGlobalCSS = async (ds, config) => {
793
1003
  write: true,
794
1004
  logLevel: 'silent',
795
1005
  plugins: [workspacePlugin],
1006
+ nodePaths: ws.isMonorepo
1007
+ ? [resolve(ws.monorepoRoot, 'node_modules')]
1008
+ : [
1009
+ ...(ws.smblsRoot ? [resolve(ws.smblsRoot, 'node_modules')] : []),
1010
+ ...(ws.projectRoot ? [resolve(ws.projectRoot, 'node_modules')] : []),
1011
+ ...(ws.smblsRoot ? [resolve(ws.smblsRoot, '..', '..', 'node_modules')] : [])
1012
+ ].filter(p => existsSync(p)),
796
1013
  external: ['fs', 'path', 'os', 'crypto', 'url', 'http', 'https', 'stream', 'util', 'events', 'buffer', 'child_process', 'worker_threads', 'net', 'tls', 'dns', 'dgram', 'zlib', 'assert', 'querystring', 'string_decoder', 'readline', 'perf_hooks', 'async_hooks', 'v8', 'vm', 'cluster', 'inspector', 'module', 'process', 'tty', 'color-contrast-checker']
797
1014
  })
798
1015
 
799
1016
  const mod = await import(`file://${tmpOut}`)
800
1017
  const data = mod.default || {}
801
- try { unlinkSync(tmpEntry) } catch {}
802
- try { unlinkSync(tmpOut) } catch {}
1018
+ try { unlinkSync(tmpEntry) } catch {} // cleanup: ignore if temp file already removed
1019
+ try { unlinkSync(tmpOut) } catch {} // cleanup: ignore if temp file already removed
803
1020
 
804
1021
  const cssVars = data.CSS_VARS || {}
805
1022
  const cssMediaVars = data.CSS_MEDIA_VARS || {}