glashjs 0.14.0 → 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.0",
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": {
@@ -1,8 +1,7 @@
1
1
  // glashjs animated favicon
2
2
  // ---------------------------------------------------------------------------
3
- // Ships an animated favicon with every build. Two reliable modes:
4
- // - animated SVG (SMIL) -> set once; browsers that support it animate the
5
- // tab icon natively (the default glash mark).
3
+ // Ships an optional animated/favicon runtime with every build. Two reliable modes:
4
+ // - SVG -> set once; the default is the official glash mark.
6
5
  // - frame cycling -> a tiny runtime swaps the <link rel=icon> href
7
6
  // across frames on an interval. Works everywhere,
8
7
  // and pauses while the tab is hidden (no wasted CPU).
@@ -47,7 +46,7 @@ if (CONFIG.auto) startGlashFavicon();
47
46
  /**
48
47
  * Resolve + emit the animated favicon for a build.
49
48
  * cfg.animatedFavicon may be:
50
- * true -> the bundled default glash animated mark
49
+ * true -> the bundled default glash mark
51
50
  * "<path>" -> a project animated SVG/GIF
52
51
  * { frames:[...], fps, auto } -> frame-cycling animation
53
52
  */
@@ -59,14 +58,14 @@ export async function generateAnimatedFavicon(outDir, cfg, root, log = () => {})
59
58
 
60
59
  if (setting === true || typeof setting === 'string') {
61
60
  const src = setting === true
62
- ? fileURLToPath(new URL('../../templates/favicon-animated.svg', import.meta.url))
61
+ ? fileURLToPath(new URL('../../templates/glash_favicon.svg', import.meta.url))
63
62
  : path.resolve(root, setting);
64
63
  const out = 'favicon-animated' + path.extname(src);
65
64
  try {
66
65
  await fs.copyFile(src, path.join(outDir, out));
67
66
  } catch {
68
- log(` ! animated favicon source not found (${setting}) — using bundled default`);
69
- await fs.copyFile(fileURLToPath(new URL('../../templates/favicon-animated.svg', import.meta.url)), path.join(outDir, 'favicon-animated.svg'));
67
+ log(` ! animated favicon source not found (${setting}) — using bundled glash_favicon.svg`);
68
+ await fs.copyFile(fileURLToPath(new URL('../../templates/glash_favicon.svg', import.meta.url)), path.join(outDir, 'favicon-animated.svg'));
70
69
  baked.animated = '/favicon-animated.svg';
71
70
  }
72
71
  baked.animated = baked.animated || '/' + out;
package/src/build.mjs CHANGED
@@ -115,7 +115,7 @@ export async function build({ root = process.cwd(), log = console.log } = {}) {
115
115
  log(` saved ${t.savedPercent}% (${kb(t.originalBytes - t.optimizedBytes)})`);
116
116
  log(` routes ${routesBuilt.compiled} JSX route(s) precompiled (no runtime esbuild in prod)`);
117
117
  log(` offline ${cfg.offline ? `${offline.precached} files precached (works offline after first visit)` : 'disabled'}`);
118
- log(` favicon static glashdb logo${animated.enabled ? ' + animated (glash-favicon.mjs)' : ''}`);
118
+ log(` favicon glash_favicon.svg${animated.enabled ? ' + animated (glash-favicon.mjs)' : ''}`);
119
119
  log(` version ${version}`);
120
120
  log(` security strict CSP + ${Object.keys(securityHeaders(cfg.security)).length} headers\n`);
121
121
 
@@ -126,7 +126,7 @@ export async function build({ root = process.cwd(), log = console.log } = {}) {
126
126
  // framework. Try the configured path first, then fall back to that bundled logo
127
127
  // so the glashdb favicon shows out-of-the-box even before any override.
128
128
  async function copyFavicon(cfg, root, outDir, log) {
129
- const bundled = path.resolve(fileURLToPath(new URL('../templates/favicon.svg', import.meta.url)));
129
+ const bundled = path.resolve(fileURLToPath(new URL('../templates/glash_favicon.svg', import.meta.url)));
130
130
  const candidates = [path.resolve(root, cfg.favicon), bundled];
131
131
  for (const src of candidates) {
132
132
  try {
@@ -134,7 +134,7 @@ async function copyFavicon(cfg, root, outDir, log) {
134
134
  return;
135
135
  } catch { /* try next */ }
136
136
  }
137
- log(` ! favicon not found (looked in ${cfg.favicon}, bundled glashdb logo) — preview favicon will be missing`);
137
+ log(` ! favicon not found (looked in ${cfg.favicon}, bundled glash_favicon.svg) — preview favicon will be missing`);
138
138
  }
139
139
 
140
140
  function kb(bytes) {
package/src/config.mjs CHANGED
@@ -11,11 +11,11 @@ export const DEFAULT_CONFIG = {
11
11
  shortName: undefined,
12
12
  // The preview favicon defaults to the official glashdb logo shipped with the
13
13
  // framework; override with your own path relative to the project root.
14
- favicon: 'node_modules/glashjs/templates/favicon.svg',
14
+ favicon: 'node_modules/glashjs/templates/glash_favicon.svg',
15
15
  // Animated favicon: true = bundled animated glash mark, a path to your own
16
16
  // animated SVG/GIF, or { frames: ['/f0.svg', ...], fps: 8 } to cycle frames.
17
17
  // false disables it. Emits `glash-favicon.mjs` — call startGlashFavicon() once.
18
- animatedFavicon: true,
18
+ animatedFavicon: false,
19
19
  publicDir: 'public',
20
20
  // File-based routes (pages + api/) served by `glash dev` / `glash serve`.
21
21
  routesDir: 'routes',
@@ -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 = true, 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 = true, 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 = true, 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);
@@ -0,0 +1,79 @@
1
+ <svg width="508" height="508" viewBox="0 0 508 508" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g filter="url(#filter0_ii_12189_2)">
3
+ <rect x="43.9863" y="34.4604" width="420.999" height="420.999" rx="102.477" fill="url(#paint0_linear_12189_2)"/>
4
+ </g>
5
+ <rect x="45.1251" y="35.5992" width="418.722" height="418.722" rx="101.338" stroke="#D4D4D4" stroke-width="2.27759"/>
6
+ <g opacity="0.6" filter="url(#filter1_f_12189_2)" style="mix-blend-mode:overlay">
7
+ <circle cx="253.556" cy="253.556" r="119.602" fill="url(#paint1_linear_12189_2)"/>
8
+ <circle cx="253.556" cy="253.556" r="118.463" stroke="#D4D4D4" stroke-width="2.27759"/>
9
+ </g>
10
+ <mask id="mask0_12189_2" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="43" y="34" width="422" height="422">
11
+ <rect x="45.1241" y="35.5997" width="418.722" height="418.722" rx="101.338" fill="url(#paint2_linear_12189_2)" stroke="#D4D4D4" stroke-width="2.27759"/>
12
+ </mask>
13
+ <g mask="url(#mask0_12189_2)">
14
+ <g filter="url(#filter2_f_12189_2)">
15
+ <ellipse cx="259.269" cy="23.0167" rx="215.284" ry="122.507" fill="url(#paint3_linear_12189_2)"/>
16
+ <path d="M259.269 -98.3521C318.557 -98.3521 372.177 -84.6747 410.935 -62.6196C449.725 -40.5457 473.414 -10.2249 473.414 23.0171C473.414 56.2589 449.725 86.579 410.935 108.653C372.177 130.708 318.557 144.385 259.269 144.385C199.98 144.385 146.361 130.708 107.604 108.653C68.813 86.579 45.1242 56.2589 45.124 23.0171C45.124 -10.2249 68.8128 -40.5457 107.604 -62.6196C146.361 -84.6747 199.98 -98.352 259.269 -98.3521Z" stroke="#D4D4D4" stroke-width="2.27759"/>
17
+ </g>
18
+ <g filter="url(#filter3_f_12189_2)" style="mix-blend-mode:plus-lighter">
19
+ <ellipse cx="254.486" cy="29.677" rx="47.8408" ry="81.3294" fill="url(#paint4_linear_12189_2)"/>
20
+ <path d="M254.486 -50.5137C267.129 -50.5137 278.78 -41.7943 287.333 -27.2539C295.871 -12.7399 301.188 7.3854 301.188 29.6768C301.188 51.9682 295.871 72.0943 287.333 86.6084C278.78 101.149 267.129 109.867 254.486 109.867C241.844 109.867 230.193 101.149 221.64 86.6084C213.102 72.0943 207.784 51.9682 207.784 29.6768C207.784 7.3854 213.102 -12.7399 221.64 -27.2539C230.193 -41.7943 241.844 -50.5137 254.486 -50.5137Z" stroke="#D4D4D4" stroke-width="2.27759"/>
21
+ </g>
22
+ </g>
23
+ <path d="M336.671 312.331L239.265 374.956L164.548 342.247L261.906 279.621L336.671 312.331Z" fill="#FEFFFF"/>
24
+ <path d="M264.17 166.655C286.185 166.655 304.057 184.575 304.057 206.591C304.057 213.479 302.371 219.983 299.047 225.956C292.014 238.626 278.67 246.478 264.17 246.478C258.148 246.478 252.367 245.178 246.972 242.624C233.146 236.025 224.234 221.91 224.234 206.591C224.234 184.575 242.155 166.655 264.17 166.655ZM264.17 128.116C220.814 128.116 185.695 163.235 185.695 206.591C185.695 237.807 203.953 264.784 230.352 277.406C240.565 282.319 252.03 285.017 264.122 285.017C293.604 285.017 319.328 268.734 332.672 244.648C338.983 233.375 342.548 220.416 342.548 206.591C342.548 163.283 307.43 128.116 264.122 128.116H264.17Z" fill="#FEFFFF"/>
25
+ <defs>
26
+ <filter id="filter0_ii_12189_2" x="43.9863" y="34.4604" width="420.999" height="449.704" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
27
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
28
+ <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
29
+ <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
30
+ <feOffset/>
31
+ <feGaussianBlur stdDeviation="19.1363"/>
32
+ <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
33
+ <feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.24 0"/>
34
+ <feBlend mode="normal" in2="shape" result="effect1_innerShadow_12189_2"/>
35
+ <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
36
+ <feOffset dy="28.7045"/>
37
+ <feGaussianBlur stdDeviation="19.1363"/>
38
+ <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
39
+ <feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 0"/>
40
+ <feBlend mode="normal" in2="effect1_innerShadow_12189_2" result="effect2_innerShadow_12189_2"/>
41
+ </filter>
42
+ <filter id="filter1_f_12189_2" x="-0.000152588" y="-0.000152588" width="507.113" height="507.113" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
43
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
44
+ <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
45
+ <feGaussianBlur stdDeviation="66.9771" result="effect1_foregroundBlur_12189_2"/>
46
+ </filter>
47
+ <filter id="filter2_f_12189_2" x="-109.105" y="-252.581" width="736.749" height="551.196" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
48
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
49
+ <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
50
+ <feGaussianBlur stdDeviation="76.5453" result="effect1_foregroundBlur_12189_2"/>
51
+ </filter>
52
+ <filter id="filter3_f_12189_2" x="149.237" y="-109.061" width="210.5" height="277.477" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
53
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
54
+ <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
55
+ <feGaussianBlur stdDeviation="28.7045" result="effect1_foregroundBlur_12189_2"/>
56
+ </filter>
57
+ <linearGradient id="paint0_linear_12189_2" x1="254.486" y1="34.4604" x2="254.486" y2="455.46" gradientUnits="userSpaceOnUse">
58
+ <stop stop-opacity="0.3"/>
59
+ <stop offset="1" stop-opacity="0.6"/>
60
+ </linearGradient>
61
+ <linearGradient id="paint1_linear_12189_2" x1="253.556" y1="133.954" x2="253.556" y2="373.158" gradientUnits="userSpaceOnUse">
62
+ <stop stop-opacity="0"/>
63
+ <stop offset="1" stop-opacity="0.6"/>
64
+ </linearGradient>
65
+ <linearGradient id="paint2_linear_12189_2" x1="254.485" y1="34.4609" x2="254.485" y2="455.46" gradientUnits="userSpaceOnUse">
66
+ <stop stop-color="#4B4B4B"/>
67
+ <stop offset="1" stop-color="#AFAFAF"/>
68
+ </linearGradient>
69
+ <linearGradient id="paint3_linear_12189_2" x1="272.501" y1="142.813" x2="267.703" y2="-48.6569" gradientUnits="userSpaceOnUse">
70
+ <stop stop-color="#D8D8D8" stop-opacity="0.05"/>
71
+ <stop offset="0.61" stop-color="#B2B2B2" stop-opacity="0.05"/>
72
+ <stop offset="0.98" stop-color="#404040"/>
73
+ </linearGradient>
74
+ <linearGradient id="paint4_linear_12189_2" x1="254.486" y1="-51.6523" x2="254.486" y2="111.006" gradientUnits="userSpaceOnUse">
75
+ <stop stop-opacity="0"/>
76
+ <stop offset="1" stop-opacity="0.6"/>
77
+ </linearGradient>
78
+ </defs>
79
+ </svg>