@feasibleone/blong-gogo 1.15.0 → 1.17.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.17.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.16.0...blong-gogo-v1.17.0) (2026-04-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * auto-discover and run blong from a folder with loose handler files ([#120](https://github.com/feasibleone/blong/issues/120)) ([4b407b6](https://github.com/feasibleone/blong/commit/4b407b6d0888eed0bb6cdc8428d82ed15f5152c5))
9
+ * **blong-gogo:** browser compatibility — replace got/tls with ky+undici, add BrowserLog, static tree-shaking split ([#123](https://github.com/feasibleone/blong/issues/123)) ([91b8d1a](https://github.com/feasibleone/blong/commit/91b8d1a159463f9e5a0f4f6d52c4c285989cac43))
10
+ * blong-ui ([9ca1281](https://github.com/feasibleone/blong/commit/9ca1281c0178c69dfc722bc4a5978f649bc1fa0d))
11
+ * improve logging ([5f43391](https://github.com/feasibleone/blong/commit/5f43391a43a99e0dd591401e24c45cd02b3bdba2))
12
+ * simplify api loading ([e6ff342](https://github.com/feasibleone/blong/commit/e6ff34239d7f1f53e3a21d30768ed5ab45042786))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * bin deduplicate ([af41171](https://github.com/feasibleone/blong/commit/af41171c0c4dbb0599f7bbe1c82efc4923fd79c8))
18
+
19
+ ## [1.16.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.15.0...blong-gogo-v1.16.0) (2026-04-01)
20
+
21
+
22
+ ### Features
23
+
24
+ * implement ConfigRuntime for hot configuration reload ([#115](https://github.com/feasibleone/blong/issues/115)) ([61bab7c](https://github.com/feasibleone/blong/commit/61bab7ce8587bf83d72fe80138aaef68943f21c4))
25
+ * unified test and handlers ([#117](https://github.com/feasibleone/blong/issues/117)) ([bf0ed96](https://github.com/feasibleone/blong/commit/bf0ed96c5df3d949fa225dd8a30fc25698a7855a))
26
+
3
27
  ## [1.15.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.14.3...blong-gogo-v1.15.0) (2026-03-29)
4
28
 
5
29
 
package/bin/blong-dev.ts CHANGED
@@ -1,47 +1,8 @@
1
1
  #!/usr/bin/env -S node --watch --inspect
2
2
 
3
3
  import minimist from 'minimist';
4
- import {existsSync} from 'node:fs';
5
- import {basename, resolve} from 'node:path';
6
- import load from '../src/load.ts';
4
+ import {autoRun} from '../src/loadServer.ts';
7
5
 
8
6
  const argv: {_: string[]} = minimist(process.argv.slice(2));
9
- const cwd = process.cwd();
10
- const target = argv._[0];
11
7
 
12
- if (target) {
13
- // Explicit file provided — load it directly (existing behavior)
14
- (await import(resolve(target))).default(load);
15
- } else {
16
- // Auto-detect what to run based on available files in the current directory.
17
- const indexFile = resolve(cwd, 'index.ts');
18
- const serverFile = resolve(cwd, 'server.ts');
19
- const browserFile = resolve(cwd, 'browser.ts');
20
-
21
- if (existsSync(indexFile)) {
22
- (await import(indexFile)).default(load);
23
- } else if (existsSync(serverFile) && existsSync(browserFile)) {
24
- const {default: server} = await import(serverFile);
25
- const {default: browser} = await import(browserFile);
26
- const name = basename(cwd);
27
- const platforms: Awaited<ReturnType<typeof load>>[] = await Promise.all([
28
- load(server, name, name, ['microservice', 'integration', 'dev']),
29
- load(browser, name, name, ['microservice', 'integration', 'dev']),
30
- ]);
31
- for (const platform of platforms) await platform.start();
32
- await platforms[1].test();
33
- if (process.env.CI) for (const platform of platforms) await platform.stop();
34
- } else if (existsSync(serverFile)) {
35
- const {default: server} = await import(serverFile);
36
- const name = basename(cwd);
37
- const platform = await load(server, name, name, ['microservice', 'integration', 'dev']);
38
- await platform.start();
39
- await platform.test();
40
- if (process.env.CI) await platform.stop();
41
- } else {
42
- throw new Error(
43
- `No index.ts, server.ts, or browser.ts found in ${cwd}. ` +
44
- 'Run blong from a suite or realm folder, or provide a file path.',
45
- );
46
- }
47
- }
8
+ await autoRun({cwd: process.cwd(), target: argv._[0]});
package/bin/blong.ts CHANGED
@@ -1,48 +1,8 @@
1
1
  #!/usr/bin/env -S node
2
2
 
3
3
  import minimist from 'minimist';
4
- import { existsSync } from 'node:fs';
5
- import { basename, join, resolve } from 'node:path';
6
- import load from '../src/load.ts';
4
+ import {autoRun} from '../src/loadServer.ts';
7
5
 
8
6
  const argv: {_: string[]} = minimist(process.argv.slice(2));
9
- const cwd = process.cwd();
10
- const target = argv._[0];
11
7
 
12
- if (target) {
13
- // Explicit file provided — load it directly
14
- (await import(resolve(target))).default(load);
15
- } else {
16
- const indexFile = join(cwd, 'index.ts');
17
- const serverFile = join(cwd, 'server.ts');
18
- const browserFile = join(cwd, 'browser.ts');
19
- const name = basename(cwd);
20
-
21
- if (existsSync(indexFile)) {
22
- // index.ts exists — use it directly (suite or realm with a custom runner)
23
- (await import(indexFile)).default(load);
24
- } else if (existsSync(serverFile) && existsSync(browserFile)) {
25
- // Both server.ts and browser.ts — two-platform (suite or realm with browser)
26
- const {default: serverDef} = await import(serverFile);
27
- const {default: browserDef} = await import(browserFile);
28
- const platforms: Awaited<ReturnType<typeof load>>[] = await Promise.all([
29
- load(serverDef, name, name, ['microservice', 'integration', 'dev']),
30
- load(browserDef, name, name, ['microservice', 'integration', 'dev']),
31
- ]);
32
- for (const platform of platforms) await platform.start();
33
- await platforms[1].test();
34
- if (process.env.CI) for (const platform of platforms) await platform.stop();
35
- } else if (existsSync(serverFile)) {
36
- // Only server.ts — suite or realm (loadRealm detects and wraps realms automatically)
37
- const {default: serverDef} = await import(serverFile);
38
- const platform = await load(serverDef, name, name, ['microservice', 'integration', 'dev']);
39
- await platform.start();
40
- await platform.test();
41
- if (process.env.CI) await platform.stop();
42
- } else {
43
- throw new Error(
44
- `No index.ts or server.ts found in ${cwd}. ` +
45
- 'Run blong from a suite or realm folder, or provide a file path.',
46
- );
47
- }
48
- }
8
+ await autoRun({cwd: process.cwd(), target: argv._[0]});
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@feasibleone/blong-gogo",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/feasibleone/blong.git"
6
6
  },
7
7
  "type": "module",
8
8
  "exports": {
9
- ".": "./src/load.ts"
9
+ ".": {
10
+ "browser": "./src/browser.ts",
11
+ "import": "./src/load.ts"
12
+ },
13
+ "./browser": "./src/adapter/browser.ts",
14
+ "./ConfigRuntime.js": "./src/ConfigRuntime.ts"
10
15
  },
11
16
  "bin": {
12
17
  "blong": "./bin/blong.ts",
@@ -39,6 +44,7 @@
39
44
  "glob": "^13.0.6",
40
45
  "got": "^14.6.6",
41
46
  "jose": "^6.2.1",
47
+ "undici": "^8.0.1",
42
48
  "knex": "^3.1.0",
43
49
  "ky": "^1.14.2",
44
50
  "lru-cache": "^11.2.4",
@@ -69,11 +75,16 @@
69
75
  "@rushstack/heft": "^1.2.6",
70
76
  "@rushstack/heft-lint-plugin": "^1.2.6",
71
77
  "@rushstack/heft-typescript-plugin": "^1.3.1",
78
+ "@types/node": "^24",
72
79
  "eslint": "~9.39.2",
80
+ "playwright": "^1.58.2",
81
+ "tap": "^21.6.2",
73
82
  "typescript": "^5.9.3"
74
83
  },
75
84
  "scripts": {
76
85
  "build": "true",
77
- "ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance"
86
+ "browser-check": "node scripts/browser-compat-check.mjs",
87
+ "ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance",
88
+ "ci-unit": "tap src/ConfigRuntime.test.ts src/lib.test.ts --allow-incomplete-coverage"
78
89
  }
79
90
  }
@@ -0,0 +1,144 @@
1
+ import {Internal, type ILog} from '@feasibleone/blong/types';
2
+ import {pino, type Level, type Logger, type LoggerOptions} from 'pino';
3
+
4
+ // ── level constants (pino uses numeric levels matching bunyan) ──────────────
5
+ const TRACE = 10, DEBUG = 20, INFO = 30, WARN = 40, ERROR = 50, FATAL = 60;
6
+
7
+ const nameFromLevel: Record<number, string> = {
8
+ [TRACE]: 'trace', [DEBUG]: 'debug', [INFO]: 'info',
9
+ [WARN]: 'warn', [ERROR]: 'error', [FATAL]: 'fatal',
10
+ };
11
+
12
+ const LEVEL_CSS: Record<string, string> = {
13
+ trace: 'color: grey',
14
+ debug: 'color: blue',
15
+ info: 'color: cyan',
16
+ warn: 'color: magenta',
17
+ error: 'color: red',
18
+ fatal: 'color: red; font-weight: bold',
19
+ };
20
+ const DEFAULT_CSS = {
21
+ def: 'color: black',
22
+ msg: 'color: darkblue',
23
+ service: 'color: darkorange',
24
+ mtid: 'color: Magenta',
25
+ src: 'color: DimGray; font-style: italic; font-size: 0.9em',
26
+ };
27
+
28
+ // Fields written separately; everything else goes into the `details` object
29
+ const SKIP = new Set([
30
+ 'name', 'hostname', 'pid', 'level', 'component', 'msg', 'time', 'v',
31
+ 'src', 'error', 'clientReq', 'clientRes', 'req', 'res',
32
+ '$meta', 'mtid', 'jsException', 'service',
33
+ ]);
34
+
35
+ export interface IBrowserLogConfig {
36
+ level?: Level;
37
+ /** Route each log call to the matching console.warn / console.error etc.
38
+ * instead of always using console.log. Default: false */
39
+ logByLevel?: boolean;
40
+ }
41
+
42
+ function write(rec: Record<string, unknown>, logByLevel: boolean): void {
43
+ const level = rec.level as number;
44
+ const levelKey = nameFromLevel[level] ?? 'info';
45
+ const paddedLevel = levelKey.toUpperCase().padStart(5);
46
+
47
+ // Choose the console method
48
+ let consoleMethod: (...a: unknown[]) => void = console.log; // eslint-disable-line no-console
49
+ if (logByLevel) {
50
+ const mapped = level <= TRACE ? 'debug' : level >= FATAL ? 'error' : levelKey;
51
+ consoleMethod = (typeof (console as Record<string, unknown>)[mapped] === 'function'
52
+ ? (console as Record<string, (...a: unknown[]) => void>)[mapped]
53
+ : console.log); // eslint-disable-line no-console
54
+ }
55
+
56
+ const levelCss =
57
+ level < DEBUG ? LEVEL_CSS.trace :
58
+ level < INFO ? LEVEL_CSS.debug :
59
+ level < WARN ? LEVEL_CSS.info :
60
+ level < ERROR ? LEVEL_CSS.warn :
61
+ level < FATAL ? LEVEL_CSS.error : LEVEL_CSS.fatal;
62
+
63
+ // any fields not in the skip list become the expandable details object
64
+ const details: Record<string, unknown> = {};
65
+ for (const [k, v] of Object.entries(rec)) {
66
+ if (v != null && !SKIP.has(k)) details[k] = v;
67
+ }
68
+ const hasDetails = Object.keys(details).length > 0;
69
+
70
+ const loggerName = rec.childName
71
+ ? `${rec.name}/${rec.childName}`
72
+ : (rec.name as string | undefined) ?? '';
73
+
74
+ const time = rec.time instanceof Date
75
+ ? rec.time.toISOString().slice(11, 23)
76
+ : new Date().toISOString().slice(11, 23);
77
+
78
+ const label =
79
+ (rec.$meta as Record<string, string> | undefined)?.method ??
80
+ (rec.$meta as Record<string, string> | undefined)?.opcode ??
81
+ (rec.msg as string | undefined) ?? '';
82
+
83
+ const fmt = `[%s] %c%s%c %s%c %s: %c%s %c%s${hasDetails ? ' %c%o' : ''}`;
84
+ const args: unknown[] = [
85
+ fmt,
86
+ time,
87
+ levelCss, paddedLevel,
88
+ DEFAULT_CSS.service, rec.service ?? '',
89
+ DEFAULT_CSS.def, loggerName,
90
+ DEFAULT_CSS.mtid, (rec.mtid as string | undefined) ?? '',
91
+ DEFAULT_CSS.msg, label,
92
+ ];
93
+ if (hasDetails) args.push(DEFAULT_CSS.src, details);
94
+
95
+ consoleMethod(...args);
96
+
97
+ if (rec.error && (rec.error as {stack?: string}).stack)
98
+ console.error(rec.error); // eslint-disable-line no-console
99
+ }
100
+
101
+ export default class BrowserLog extends Internal implements ILog {
102
+ #logger: Logger;
103
+ #config: IBrowserLogConfig = {level: 'info', logByLevel: false};
104
+
105
+ public constructor(config: IBrowserLogConfig) {
106
+ super();
107
+ this.merge(this.#config, config);
108
+ const logByLevel = this.#config.logByLevel ?? false;
109
+ this.#logger = pino({
110
+ level: this.#config.level ?? 'info',
111
+ browser: {
112
+ asObject: true,
113
+ write: (rec: Record<string, unknown>) => write(rec, logByLevel),
114
+ },
115
+ });
116
+ }
117
+
118
+ public child<T extends string>(...params: Parameters<Logger<never>['child']>): Logger<T> {
119
+ return this.#logger.child(...params) as Logger<T>;
120
+ }
121
+
122
+ public logger(
123
+ level: LoggerOptions['level'] = this.#config.level,
124
+ bindings: object,
125
+ ): ReturnType<ILog['logger']> {
126
+ const child = this.#logger.child(bindings, {level});
127
+ const result = {trace: null, debug: null, info: null, warn: null, error: null, fatal: null};
128
+ switch (level) {
129
+ case 'trace':
130
+ result.trace = child.trace.bind(child);
131
+ case 'debug': // eslint-disable-line no-fallthrough
132
+ result.debug = child.debug.bind(child);
133
+ case 'info': // eslint-disable-line no-fallthrough
134
+ result.info = child.info.bind(child);
135
+ case 'warn': // eslint-disable-line no-fallthrough
136
+ result.warn = child.warn.bind(child);
137
+ case 'error': // eslint-disable-line no-fallthrough
138
+ result.error = child.error.bind(child);
139
+ case 'fatal': // eslint-disable-line no-fallthrough
140
+ result.fatal = child.fatal.bind(child);
141
+ }
142
+ return result;
143
+ }
144
+ }