glashjs 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -179,7 +179,8 @@ export default defineConfig({
179
179
  outDir: '.glash/out',
180
180
  stylesheets: ['/app.css'],
181
181
  offline: true,
182
- animatedFavicon: true,
182
+ favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
183
+ animatedFavicon: false,
183
184
  dataPrefixes: ['/api/', '/auth/', '/rest/', '/live', '/stream'],
184
185
  });
185
186
  ```
package/bin/glash.mjs CHANGED
@@ -56,6 +56,11 @@ function glashMark() {
56
56
  ];
57
57
  }
58
58
 
59
+ function faviconLabel(cfg) {
60
+ const source = String(cfg.favicon || 'glash_favicon.svg');
61
+ return source.split(/[\\/]/).pop() || 'glash_favicon.svg';
62
+ }
63
+
59
64
  // LAN IPv4 addresses, so the dev server prints a Network URL you can open from
60
65
  // your phone or another device on the same network.
61
66
  function lanAddresses() {
@@ -79,6 +84,7 @@ async function serve(dev) {
79
84
  console.log('');
80
85
  for (const line of glashMark()) console.log(line);
81
86
  console.log(` glashjs ${dev ? 'dev' : 'serve'} — "${cfg.name}"`);
87
+ console.log(` favicon ${faviconLabel(cfg)} -> /favicon.svg`);
82
88
  if (port !== preferredPort) console.log(` Port ${preferredPort} is in use; using ${port} instead.`);
83
89
  console.log(` ${pages} page route(s), ${apis} api route(s)`);
84
90
  routes.forEach((r) => console.log(` ${r.isApi ? 'api ' : 'page'} ${r.pattern}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glashjs",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "glashjs — The Postgres-native full-stack framework for builders who want to ship without DevOps. Framework, hosting, database, auth, and deploy in one GlashDB-native runtime.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.mjs CHANGED
@@ -25,6 +25,14 @@ export const DEFAULT_CONFIG = {
25
25
  outDir: '.glash/out',
26
26
  themeColor: '#0b0d12',
27
27
  offline: true,
28
+ // Built-in IP auto-translation. true = detect the visitor's country (from the
29
+ // edge geo header, falling back to a public IP service) and, if their native
30
+ // language differs from the page, offer a one-tap in-place translation. The
31
+ // framework serves the runtime (/_glash/i18n.js) and a geo endpoint
32
+ // (/api/_glash/geo) automatically — no route or layout wiring per app.
33
+ // true | false | { auto?: boolean, geoEndpoint?: string }
34
+ // auto: translate immediately instead of prompting.
35
+ i18n: true,
28
36
  // Requests under these prefixes are treated as live/updated data: network-first
29
37
  // in the Service Worker, so offline mode degrades exactly there (no stale live data).
30
38
  dataPrefixes: ['/api/', '/rest/', '/auth/', '/realtime', '/live', '/stream'],
package/src/create.mjs CHANGED
@@ -136,7 +136,8 @@ export default defineConfig({
136
136
  publicDir: 'public',
137
137
  outDir: '.glash/out',
138
138
  offline: true,
139
- animatedFavicon: true,
139
+ favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
140
+ animatedFavicon: false,
140
141
  stylesheets: ${hasCss ? "['/app.css']" : '[]'},
141
142
  dataPrefixes: ['/api/', '/auth/', '/rest/', '/live', '/stream'],
142
143
  security: {
@@ -49,7 +49,7 @@ ${headHtml}
49
49
  `;
50
50
  }
51
51
 
52
- function shellTail({ offline = true, animatedFavicon = false, dev = false, nonce = '' }) {
52
+ function shellTail({ offline = true, animatedFavicon = false, dev = false, nonce = '', i18n = false }) {
53
53
  const n = nonce ? ` nonce="${escapeHtml(nonce)}"` : '';
54
54
  const fav = animatedFavicon
55
55
  ? `<script type="module"${n}>try{const m=await import("/glash-favicon.mjs");m.startGlashFavicon&&m.startGlashFavicon();}catch{}</script>`
@@ -57,6 +57,16 @@ function shellTail({ offline = true, animatedFavicon = false, dev = false, nonce
57
57
  const off = offline
58
58
  ? `<script type="module"${n}>try{const m=await import("/glash-offline.mjs");m.registerGlashOffline&&m.registerGlashOffline();}catch{}</script>`
59
59
  : '';
60
+ // Built-in IP auto-translation: the framework serves /_glash/i18n.js (the
61
+ // dependency-free runtime). It detects the visitor's country, maps it to their
62
+ // native language, and offers a one-tap in-place translation. CSP-safe: a
63
+ // nonce'd module import of a same-origin URL, no inline logic.
64
+ const i18nOpts = i18n && typeof i18n === 'object'
65
+ ? { auto: !!i18n.auto, ...(i18n.geoEndpoint ? { geoEndpoint: i18n.geoEndpoint } : {}) }
66
+ : {};
67
+ const intl = i18n
68
+ ? `<script type="module"${n}>try{const m=await import("/_glash/i18n.js");m.glashAutoTranslate&&m.glashAutoTranslate(${JSON.stringify(i18nOpts)});}catch{}</script>`
69
+ : '';
60
70
  // Dev HMR: a nonce'd inline script (CSP-safe). On a change event it does an
61
71
  // in-place soft refresh (no full reload, keeps scroll/focus/inputs), falling
62
72
  // back to a full reload if the nav runtime hasn't loaded yet.
@@ -65,7 +75,7 @@ function shellTail({ offline = true, animatedFavicon = false, dev = false, nonce
65
75
  : '';
66
76
  // Client-side navigation runtime (external 'self' module, CSP-safe).
67
77
  const nav = '<script type="module" src="/_glash/nav.js"></script>';
68
- return `\n${fav}${off}${hmr}${nav}\n</body>\n</html>`;
78
+ return `\n${fav}${off}${intl}${hmr}${nav}\n</body>\n</html>`;
69
79
  }
70
80
 
71
81
  /**
@@ -6,11 +6,12 @@
6
6
  // service worker, favicons) with Brotli negotiation.
7
7
  // Every response carries the secure-by-default headers.
8
8
  import http from 'node:http';
9
- import { promises as fs, existsSync, statSync, watch, createReadStream } from 'node:fs';
9
+ import { promises as fs, existsSync, statSync, watch, createReadStream, readFileSync } from 'node:fs';
10
10
  import { Transform } from 'node:stream';
11
11
  import { randomBytes } from 'node:crypto';
12
12
  import path from 'node:path';
13
- import { pathToFileURL } from 'node:url';
13
+ import { pathToFileURL, fileURLToPath } from 'node:url';
14
+ import { geoRouteHandler } from '../i18n.mjs';
14
15
  import { discoverRoutes, matchRoute, findMiddleware } from './router.mjs';
15
16
  import { renderDocument, documentParts, renderMeta, escapeHtml } from './html.mjs';
16
17
  import { NAV_CLIENT } from './nav-client.mjs';
@@ -28,6 +29,11 @@ const MIME = {
28
29
  };
29
30
  const mime = (file) => MIME[path.extname(file).toLowerCase()] || 'application/octet-stream';
30
31
 
32
+ // The built-in i18n runtime served at /_glash/i18n.js. i18n.mjs is dependency-free
33
+ // and valid browser ESM (its server-only exports are harmless in the browser), so
34
+ // we serve it raw — single source of truth, works identically in dev and prod.
35
+ const I18N_CLIENT = readFileSync(fileURLToPath(new URL('../i18n.mjs', import.meta.url)), 'utf8');
36
+
31
37
  /** Build the glashjs server. Returns { server, listen, cfg }. */
32
38
  export async function createGlashServer({ root = process.cwd(), dev = false } = {}) {
33
39
  loadEnvFiles(root);