@bardioc/create-bardioc-app 0.5.8 → 0.6.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.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/templates/_base/public/app-manifest.json +1 -2
  3. package/templates/angular/package.json +1 -1
  4. package/templates/angular/scripts/dev-env.mjs +31 -10
  5. package/templates/angular/scripts/dev.mjs +37 -8
  6. package/templates/angular/src/app/app.component.ts +42 -24
  7. package/templates/angular/src/app/bardioc.token.ts +2 -2
  8. package/templates/angular/src/main.ts +6 -8
  9. package/templates/nextjs/app/page.tsx +8 -8
  10. package/templates/nextjs/package.json +1 -1
  11. package/templates/nextjs/scripts/dev-env.mjs +31 -10
  12. package/templates/nextjs/scripts/dev.mjs +37 -8
  13. package/templates/preact/package.json +1 -1
  14. package/templates/preact/src/App.tsx +5 -5
  15. package/templates/preact/src/main.tsx +3 -3
  16. package/templates/solid/package.json +1 -1
  17. package/templates/solid/src/App.tsx +5 -5
  18. package/templates/solid/src/bardioc-sdk.tsx +11 -23
  19. package/templates/solid/src/main.tsx +5 -3
  20. package/templates/svelte/package.json +1 -1
  21. package/templates/svelte/src/App.svelte +7 -7
  22. package/templates/svelte/src/main.ts +5 -5
  23. package/templates/vite/package.json +1 -1
  24. package/templates/vite/public/app-manifest.json +0 -1
  25. package/templates/vite/src/auth/onboarding.tsx +4 -2
  26. package/templates/vite/src/climate/stations-view.tsx +1 -8
  27. package/templates/vite/src/climate/use-climate.ts +6 -6
  28. package/templates/vite/src/events/live-events-view.tsx +7 -9
  29. package/templates/vite/src/graph/explorer-view.tsx +4 -11
  30. package/templates/vite/src/graph/requests.ts +16 -19
  31. package/templates/vite/src/locales/de.json +5 -6
  32. package/templates/vite/src/locales/en.json +6 -7
  33. package/templates/vite/src/main.tsx +3 -3
  34. package/templates/vite/src/profile/profile-view.tsx +1 -8
  35. package/templates/vite/src/profile/use-profile.ts +3 -3
  36. package/templates/vite/src/shell/about-dialog.tsx +2 -8
  37. package/templates/vite/src/shell/app-shell.tsx +48 -11
  38. package/templates/vue/package.json +1 -1
  39. package/templates/vue/src/App.vue +7 -7
  40. package/templates/vue/src/bardioc-sdk.ts +6 -15
  41. package/templates/vue/src/main.ts +5 -4
  42. package/templates/angular/src/app/bardioc-bridge.ts +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bardioc/create-bardioc-app",
3
- "version": "0.5.8",
3
+ "version": "0.6.0",
4
4
  "description": "Scaffold a Bardioc app with framework templates (vite, react, vue, angular, svelte, solid, preact, nextjs)",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -5,6 +5,5 @@
5
5
  "description": "{{DISPLAY_NAME}} — a Bardioc hosted app",
6
6
  "version": "0.0.1",
7
7
  "entry": "index.html",
8
- "icon": "icon.svg",
9
- "permissions": ["notify", "transport"]
8
+ "icon": "icon.svg"
10
9
  }
@@ -28,7 +28,7 @@
28
28
  "@angular/platform-browser": "^19.2.12",
29
29
  "@angular/platform-browser-dynamic": "^19.2.12",
30
30
  "@angular/router": "^19.2.12",
31
- "@bardioc/app-sdk": "^0.5.0",
31
+ "@bardioc/app-sdk": "^1.0.0",
32
32
  "rxjs": "~7.8.2",
33
33
  "tslib": "^2.8.1",
34
34
  "zone.js": "~0.15.1"
@@ -5,11 +5,11 @@ import { join } from 'node:path';
5
5
  const DEFAULT_PORT = {{PORT}};
6
6
 
7
7
  // The dev-auth proxy answers on the app port plus this offset — the same origin the app points
8
- // installDevBridge at.
8
+ // installDevHost at.
9
9
  export const PROXY_PORT_OFFSET = 100;
10
10
 
11
- // Neither Next nor Angular exposes import.meta.env to a node script, so dotenv values are read
12
- // here the way Vite's loadEnv does it: a shell value always wins over the files.
11
+ // Neither Next nor Angular exposes import.meta.env to a node script, so the dotenv files are read
12
+ // here: .env, then .env.local, then the shell, which always wins — the precedence of Vite's loadEnv.
13
13
  export function readEnvValue(projectDir, key) {
14
14
  let value;
15
15
 
@@ -23,19 +23,40 @@ export function readEnvValue(projectDir, key) {
23
23
 
24
24
  for (const rawLine of source.split(/\r?\n/)) {
25
25
  const line = rawLine.trim();
26
- if (!line.startsWith(`${key}=`)) {
27
- continue;
26
+ if (line.startsWith(`${key}=`)) {
27
+ value = parseValue(line.slice(key.length + 1));
28
28
  }
29
- value = line
30
- .slice(key.length + 1)
31
- .trim()
32
- .replace(/^['"]|['"]$/g, '');
33
29
  }
34
30
  }
35
31
 
36
32
  return process.env[key] ?? value;
37
33
  }
38
34
 
35
+ // A quoted value is taken verbatim; an unquoted one ends at a `#` comment, as dotenv reads it.
36
+ function parseValue(raw) {
37
+ const value = raw.trim();
38
+ const quoted = /^(['"`])(.*?)\1/.exec(value);
39
+ return quoted ? quoted[2] : value.split('#')[0].trim();
40
+ }
41
+
39
42
  export function resolveAppPort(projectDir) {
40
- return Number(readEnvValue(projectDir, 'LOCALHOST_PORT')) || DEFAULT_PORT;
43
+ const raw = readEnvValue(projectDir, 'LOCALHOST_PORT');
44
+ if (!raw) {
45
+ return DEFAULT_PORT;
46
+ }
47
+
48
+ const port = Number(raw);
49
+ if (Number.isInteger(port) && port >= 1 && port <= 65535) {
50
+ return port;
51
+ }
52
+
53
+ console.warn(`[dev] LOCALHOST_PORT=${raw} is not a valid port — using ${DEFAULT_PORT}`);
54
+ return DEFAULT_PORT;
55
+ }
56
+
57
+ // The standalone dev-auth contract; null when either value is missing, which disables the proxy.
58
+ export function readDevAuthEnv(projectDir) {
59
+ const devSecret = readEnvValue(projectDir, 'DEV_SECRET')?.trim();
60
+ const apiBaseUrl = readEnvValue(projectDir, 'API_BASE_URL')?.trim().replace(/\/$/, '');
61
+ return devSecret && apiBaseUrl ? { devSecret, apiBaseUrl } : null;
41
62
  }
@@ -1,27 +1,56 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
3
+ import { createDevAuthProxyHandler } from '@bardioc/app-sdk/dev-proxy';
4
4
  import { spawn } from 'node:child_process';
5
+ import { createServer } from 'node:http';
5
6
  import { dirname, resolve } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
- import { PROXY_PORT_OFFSET, resolveAppPort } from './dev-env.mjs';
8
+ import { PROXY_PORT_OFFSET, readDevAuthEnv, resolveAppPort } from './dev-env.mjs';
9
+
10
+ const PROXY_HOST = '127.0.0.1';
8
11
 
9
12
  const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
13
  const port = resolveAppPort(projectDir);
11
14
 
12
- // Angular's dev server cannot host the SDK Vite plugin, so the proxy runs beside it. Without a
13
- // DEV_SECRET / API_BASE_URL it stays unstarted and the dev server runs alone, matching how the
14
- // SDK Vite plugin disables itself.
15
- const proxy = await startDevAuthProxy({ projectDir, port: port + PROXY_PORT_OFFSET });
15
+ // Angular's dev server cannot host the SDK Vite plugin, so the proxy runs beside it.
16
+ const proxy = await startProxy(port + PROXY_PORT_OFFSET);
16
17
 
17
- // shell:true so npm's node_modules/.bin lookup works on Windows too, where the binary is a .cmd.
18
- const server = spawn('ng', ['serve', '--port', String(port)], { stdio: 'inherit', shell: true });
18
+ // One command string: shell:true resolves the .cmd shim on Windows, and passing an args array
19
+ // alongside it is deprecated (DEP0190).
20
+ const server = spawn(`ng serve --port ${port}`, { stdio: 'inherit', shell: true });
19
21
 
20
22
  for (const signal of ['SIGINT', 'SIGTERM']) {
21
23
  process.on(signal, () => server.kill(signal));
22
24
  }
23
25
 
26
+ // Exit only once the dev server has, carrying its exit code.
24
27
  server.on('exit', (code, signal) => {
25
28
  proxy?.close();
26
29
  process.exit(signal ? 1 : (code ?? 0));
27
30
  });
31
+
32
+ // Built from the SDK handler rather than startDevAuthProxy, whose own SIGINT handler exits the
33
+ // process before the dev server has shut down. Without DEV_SECRET / API_BASE_URL the dev server
34
+ // runs alone, matching how the SDK Vite plugin disables itself.
35
+ async function startProxy(proxyPort) {
36
+ const env = readDevAuthEnv(projectDir);
37
+ if (!env) {
38
+ console.warn(
39
+ '[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — proxy disabled'
40
+ );
41
+ return null;
42
+ }
43
+
44
+ const handler = createDevAuthProxyHandler({ ...env, allowOrigin: '*' });
45
+ const proxyServer = createServer((req, res) => void handler(req, res));
46
+
47
+ await new Promise((resolveListen, rejectListen) => {
48
+ proxyServer.once('error', rejectListen);
49
+ proxyServer.listen(proxyPort, PROXY_HOST, resolveListen);
50
+ });
51
+
52
+ console.log(
53
+ `[bardioc-dev-auth] proxying /rest/* → ${env.apiBaseUrl} on http://${PROXY_HOST}:${proxyPort}/rest`
54
+ );
55
+ return proxyServer;
56
+ }
@@ -1,8 +1,8 @@
1
- import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
2
1
  import { JsonPipe } from '@angular/common';
3
2
  import { Component, inject, signal } from '@angular/core';
4
- import { BARDIOC_BRIDGE } from './bardioc.token';
5
- import type { HostBridge } from '@bardioc/app-sdk';
3
+ import type { NotifyLevel } from '@bardioc/app-sdk';
4
+ import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
5
+ import { BARDIOC } from './bardioc.token';
6
6
 
7
7
  type ResultState =
8
8
  | { status: 'idle' }
@@ -27,30 +27,38 @@ type ResultState =
27
27
 
28
28
  <div class="flex flex-wrap gap-2">
29
29
  <button
30
- class="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed"
30
+ class="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50"
31
31
  (click)="runGetProfile()"
32
- [disabled]="running()">
32
+ [disabled]="running()"
33
+ >
33
34
  Get Profile
34
35
  </button>
35
36
  <button
36
- class="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed"
37
+ class="bg-primary text-primary-foreground hover:bg-primary/90 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50"
37
38
  (click)="handleNotify()"
38
- [disabled]="running()">
39
+ [disabled]="running()"
40
+ >
39
41
  Send Notification OS
40
42
  </button>
41
43
  <button
42
44
  class="border-border text-muted-foreground hover:text-foreground rounded border px-3 py-1 text-xs"
43
45
  (click)="clearResult()"
44
- [disabled]="running()">
46
+ [disabled]="running()"
47
+ >
45
48
  Clear
46
49
  </button>
47
50
  </div>
48
51
 
49
- <div class="border-border bg-background flex min-h-0 flex-1 flex-col overflow-hidden rounded border">
52
+ <div
53
+ class="border-border bg-background flex min-h-0 flex-1 flex-col overflow-hidden rounded border"
54
+ >
50
55
  @switch (result().status) {
51
56
  @case ('idle') {
52
- <div class="text-muted-foreground flex h-full items-center justify-center p-3 text-center text-xs leading-5">
53
- Run a query to see results. If the host session is missing, the transport route will return 401.
57
+ <div
58
+ class="text-muted-foreground flex h-full items-center justify-center p-3 text-center text-xs leading-5"
59
+ >
60
+ Run a query to see results. If the host session is missing, the transport route
61
+ will return 401.
54
62
  </div>
55
63
  }
56
64
  @case ('loading') {
@@ -64,15 +72,21 @@ type ResultState =
64
72
  <div class="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
65
73
  {{ resultRequest() }}
66
74
  </div>
67
- <div class="text-destructive break-all whitespace-pre-wrap">{{ resultMessage() }}</div>
75
+ <div class="text-destructive whitespace-pre-wrap break-all">
76
+ {{ resultMessage() }}
77
+ </div>
68
78
  </div>
69
79
  }
70
80
  @case ('success') {
71
81
  <div class="flex h-full flex-col gap-0 overflow-hidden">
72
- <div class="border-border text-muted-foreground shrink-0 border-b px-3 py-1 font-mono text-xs">
82
+ <div
83
+ class="border-border text-muted-foreground shrink-0 border-b px-3 py-1 font-mono text-xs"
84
+ >
73
85
  {{ resultRequest() }}
74
86
  </div>
75
- <pre class="text-foreground flex-1 overflow-auto p-3 text-[11px] leading-5">{{ resultData() | json }}</pre>
87
+ <pre class="text-foreground flex-1 overflow-auto p-3 text-[11px] leading-5">{{
88
+ resultData() | json
89
+ }}</pre>
76
90
  </div>
77
91
  }
78
92
  }
@@ -93,30 +107,30 @@ type ResultState =
93
107
  `,
94
108
  })
95
109
  export class AppComponent {
96
- private bridge = inject(BARDIOC_BRIDGE, { optional: true }) as HostBridge | null;
110
+ private bardioc = inject(BARDIOC, { optional: true });
97
111
 
98
112
  showSdkDemo = isInsideIframe() || isDevStandalone();
99
113
  result = signal<ResultState>({ status: 'idle' });
100
114
  running = signal(false);
101
115
 
102
116
  async runGetProfile(): Promise<void> {
103
- if (this.running() || !this.bridge) return;
117
+ if (this.running() || !this.bardioc) return;
104
118
  this.running.set(true);
105
119
 
106
- const request = 'transport.os.profile.get()';
120
+ const request = 'os.profile.get()';
107
121
  this.result.set({ status: 'loading', request });
108
122
 
109
123
  const startTime = Date.now();
110
- this.bridge.notify('Getting user profile…', 'info');
124
+ this.notify('Getting user profile…', 'info');
111
125
 
112
126
  try {
113
- const data = await this.bridge.transport.os.profile.get();
127
+ const data = await this.bardioc.os.profile.get();
114
128
  const duration = Date.now() - startTime;
115
- this.bridge.notify(`Profile loaded (${duration}ms).`, 'success');
129
+ this.notify(`Profile loaded (${duration}ms).`, 'success');
116
130
  this.result.set({ status: 'success', request, data });
117
131
  } catch (err) {
118
132
  const message = err instanceof Error ? err.message : String(err);
119
- this.bridge.notify(`Failed: ${message}`, 'error');
133
+ this.notify(`Failed: ${message}`, 'error');
120
134
  this.result.set({ status: 'error', request, message });
121
135
  }
122
136
 
@@ -124,15 +138,19 @@ export class AppComponent {
124
138
  }
125
139
 
126
140
  handleNotify(): void {
127
- if (!this.bridge) return;
128
- this.bridge.notify('Hello from {{DISPLAY_NAME}}.', 'info');
141
+ if (!this.bardioc) return;
142
+ this.notify('Hello from {{DISPLAY_NAME}}.', 'info');
129
143
  this.result.set({
130
144
  status: 'success',
131
- request: 'SDK_NOTIFY',
145
+ request: 'ui.notify',
132
146
  data: { ok: true, message: 'Notification sent to OS' },
133
147
  });
134
148
  }
135
149
 
150
+ private notify(message: string, level: NotifyLevel): void {
151
+ this.bardioc?.ui.notify(message, level).catch(console.error);
152
+ }
153
+
136
154
  clearResult(): void {
137
155
  this.result.set({ status: 'idle' });
138
156
  }
@@ -1,4 +1,4 @@
1
1
  import { InjectionToken } from '@angular/core';
2
- import type { HostBridge } from '@bardioc/app-sdk';
2
+ import type { Bardioc } from '@bardioc/app-sdk';
3
3
 
4
- export const BARDIOC_BRIDGE = new InjectionToken<HostBridge>('BARDIOC_BRIDGE');
4
+ export const BARDIOC = new InjectionToken<Bardioc>('BARDIOC');
@@ -1,5 +1,6 @@
1
+ import { connect } from '@bardioc/app-sdk';
1
2
  import {
2
- installDevBridge,
3
+ installDevHost,
3
4
  isDevStandalone,
4
5
  isInsideIframe,
5
6
  } from '@bardioc/app-sdk/dev';
@@ -7,8 +8,7 @@ import { bootstrapApplication } from '@angular/platform-browser';
7
8
  import { provideHttpClient } from '@angular/common/http';
8
9
  import { type EnvironmentProviders, type Provider } from '@angular/core';
9
10
  import { AppComponent } from './app/app.component';
10
- import { createBardiocBridge } from './app/bardioc-bridge';
11
- import { BARDIOC_BRIDGE } from './app/bardioc.token';
11
+ import { BARDIOC } from './app/bardioc.token';
12
12
  import './styles.css';
13
13
 
14
14
  function readAppPort(): number {
@@ -24,7 +24,8 @@ const useSdkProvider = insideIframe || devStandalone;
24
24
 
25
25
  async function bootstrap() {
26
26
  if (devStandalone) {
27
- installDevBridge({
27
+ installDevHost({
28
+ appId: '{{APP_NAME}}',
28
29
  apiBaseUrl: `http://127.0.0.1:${proxyPort}/rest`,
29
30
  });
30
31
  }
@@ -34,10 +35,7 @@ async function bootstrap() {
34
35
  ];
35
36
 
36
37
  if (useSdkProvider) {
37
- providers.push({
38
- provide: BARDIOC_BRIDGE,
39
- useValue: createBardiocBridge(),
40
- });
38
+ providers.push({ provide: BARDIOC, useValue: await connect() });
41
39
  }
42
40
 
43
41
  bootstrapApplication(AppComponent, {
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { installDevBridge, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
3
+ import { installDevHost, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
4
4
  import { AppSdkProvider, useNotify, useSdk } from '@bardioc/app-sdk/react';
5
5
  import { useEffect, useState } from 'react';
6
6
 
@@ -16,7 +16,7 @@ const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME?.trim();
16
16
  const DEFAULT_PORT = Number('{{PORT}}');
17
17
 
18
18
  function SdkDemo() {
19
- const bridge = useSdk();
19
+ const bardioc = useSdk();
20
20
  const notify = useNotify();
21
21
  const [result, setResult] = useState<ResultState>({ status: 'idle' });
22
22
  const [running, setRunning] = useState(false);
@@ -25,14 +25,14 @@ function SdkDemo() {
25
25
  if (running) return;
26
26
  setRunning(true);
27
27
 
28
- const request = 'transport.os.profile.get()';
28
+ const request = 'os.profile.get()';
29
29
  setResult({ status: 'loading', request });
30
30
 
31
31
  const startTime = Date.now();
32
32
  notify('Getting user profile…', 'info');
33
33
 
34
34
  try {
35
- const data = await bridge.transport.os.profile.get();
35
+ const data = await bardioc.os.profile.get();
36
36
 
37
37
  const duration = Date.now() - startTime;
38
38
  notify(`Profile loaded (${duration}ms).`, 'success');
@@ -50,7 +50,7 @@ function SdkDemo() {
50
50
  notify('Hello from {{DISPLAY_NAME}}.', 'info');
51
51
  setResult({
52
52
  status: 'success',
53
- request: 'SDK_NOTIFY',
53
+ request: 'ui.notify',
54
54
  data: { ok: true, message: 'Notification sent to OS' },
55
55
  });
56
56
  }
@@ -106,7 +106,7 @@ function SdkDemo() {
106
106
  <div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
107
107
  {result.request}
108
108
  </div>
109
- <div className="text-destructive break-all whitespace-pre-wrap">{result.message}</div>
109
+ <div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
110
110
  </div>
111
111
  )}
112
112
  {result.status === 'success' && (
@@ -150,14 +150,14 @@ export default function Page() {
150
150
  // scripts/dev.mjs runs the dev-auth proxy on the app port + 100, so read the port the page
151
151
  // is actually served on rather than the one baked in at scaffold time.
152
152
  const appPort = Number(window.location.port) || DEFAULT_PORT;
153
- installDevBridge({ apiBaseUrl: `http://127.0.0.1:${appPort + 100}/rest` });
153
+ installDevHost({ appId: APP_NAME, apiBaseUrl: `http://127.0.0.1:${appPort + 100}/rest` });
154
154
  }
155
155
  }, []);
156
156
 
157
157
  return (
158
158
  <div className="bg-background text-foreground @container/app min-h-screen">
159
159
  {useSdkProvider ? (
160
- <AppSdkProvider appId={APP_NAME}>
160
+ <AppSdkProvider>
161
161
  <SdkDemo />
162
162
  </AppSdkProvider>
163
163
  ) : (
@@ -17,7 +17,7 @@
17
17
  "release:publish": "changeset publish"
18
18
  },
19
19
  "dependencies": {
20
- "@bardioc/app-sdk": "^0.5.0",
20
+ "@bardioc/app-sdk": "^1.0.0",
21
21
  "next": "^15.3.2",
22
22
  "react": "^19.2.5",
23
23
  "react-dom": "^19.2.5"
@@ -5,11 +5,11 @@ import { join } from 'node:path';
5
5
  const DEFAULT_PORT = {{PORT}};
6
6
 
7
7
  // The dev-auth proxy answers on the app port plus this offset — the same origin the app points
8
- // installDevBridge at.
8
+ // installDevHost at.
9
9
  export const PROXY_PORT_OFFSET = 100;
10
10
 
11
- // Neither Next nor Angular exposes import.meta.env to a node script, so dotenv values are read
12
- // here the way Vite's loadEnv does it: a shell value always wins over the files.
11
+ // Neither Next nor Angular exposes import.meta.env to a node script, so the dotenv files are read
12
+ // here: .env, then .env.local, then the shell, which always wins — the precedence of Vite's loadEnv.
13
13
  export function readEnvValue(projectDir, key) {
14
14
  let value;
15
15
 
@@ -23,19 +23,40 @@ export function readEnvValue(projectDir, key) {
23
23
 
24
24
  for (const rawLine of source.split(/\r?\n/)) {
25
25
  const line = rawLine.trim();
26
- if (!line.startsWith(`${key}=`)) {
27
- continue;
26
+ if (line.startsWith(`${key}=`)) {
27
+ value = parseValue(line.slice(key.length + 1));
28
28
  }
29
- value = line
30
- .slice(key.length + 1)
31
- .trim()
32
- .replace(/^['"]|['"]$/g, '');
33
29
  }
34
30
  }
35
31
 
36
32
  return process.env[key] ?? value;
37
33
  }
38
34
 
35
+ // A quoted value is taken verbatim; an unquoted one ends at a `#` comment, as dotenv reads it.
36
+ function parseValue(raw) {
37
+ const value = raw.trim();
38
+ const quoted = /^(['"`])(.*?)\1/.exec(value);
39
+ return quoted ? quoted[2] : value.split('#')[0].trim();
40
+ }
41
+
39
42
  export function resolveAppPort(projectDir) {
40
- return Number(readEnvValue(projectDir, 'LOCALHOST_PORT')) || DEFAULT_PORT;
43
+ const raw = readEnvValue(projectDir, 'LOCALHOST_PORT');
44
+ if (!raw) {
45
+ return DEFAULT_PORT;
46
+ }
47
+
48
+ const port = Number(raw);
49
+ if (Number.isInteger(port) && port >= 1 && port <= 65535) {
50
+ return port;
51
+ }
52
+
53
+ console.warn(`[dev] LOCALHOST_PORT=${raw} is not a valid port — using ${DEFAULT_PORT}`);
54
+ return DEFAULT_PORT;
55
+ }
56
+
57
+ // The standalone dev-auth contract; null when either value is missing, which disables the proxy.
58
+ export function readDevAuthEnv(projectDir) {
59
+ const devSecret = readEnvValue(projectDir, 'DEV_SECRET')?.trim();
60
+ const apiBaseUrl = readEnvValue(projectDir, 'API_BASE_URL')?.trim().replace(/\/$/, '');
61
+ return devSecret && apiBaseUrl ? { devSecret, apiBaseUrl } : null;
41
62
  }
@@ -1,27 +1,56 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
3
+ import { createDevAuthProxyHandler } from '@bardioc/app-sdk/dev-proxy';
4
4
  import { spawn } from 'node:child_process';
5
+ import { createServer } from 'node:http';
5
6
  import { dirname, resolve } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
- import { PROXY_PORT_OFFSET, resolveAppPort } from './dev-env.mjs';
8
+ import { PROXY_PORT_OFFSET, readDevAuthEnv, resolveAppPort } from './dev-env.mjs';
9
+
10
+ const PROXY_HOST = '127.0.0.1';
8
11
 
9
12
  const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
13
  const port = resolveAppPort(projectDir);
11
14
 
12
- // `next dev` cannot host the SDK Vite plugin, so the proxy runs beside it. Without a
13
- // DEV_SECRET / API_BASE_URL it stays unstarted and the dev server runs alone, matching how the
14
- // SDK Vite plugin disables itself.
15
- const proxy = await startDevAuthProxy({ projectDir, port: port + PROXY_PORT_OFFSET });
15
+ // `next dev` cannot host the SDK Vite plugin, so the proxy runs beside it.
16
+ const proxy = await startProxy(port + PROXY_PORT_OFFSET);
16
17
 
17
- // shell:true so npm's node_modules/.bin lookup works on Windows too, where the binary is a .cmd.
18
- const server = spawn('next', ['dev', '--port', String(port)], { stdio: 'inherit', shell: true });
18
+ // One command string: shell:true resolves the .cmd shim on Windows, and passing an args array
19
+ // alongside it is deprecated (DEP0190).
20
+ const server = spawn(`next dev --port ${port}`, { stdio: 'inherit', shell: true });
19
21
 
20
22
  for (const signal of ['SIGINT', 'SIGTERM']) {
21
23
  process.on(signal, () => server.kill(signal));
22
24
  }
23
25
 
26
+ // Exit only once the dev server has, carrying its exit code.
24
27
  server.on('exit', (code, signal) => {
25
28
  proxy?.close();
26
29
  process.exit(signal ? 1 : (code ?? 0));
27
30
  });
31
+
32
+ // Built from the SDK handler rather than startDevAuthProxy, whose own SIGINT handler exits the
33
+ // process before the dev server has shut down. Without DEV_SECRET / API_BASE_URL the dev server
34
+ // runs alone, matching how the SDK Vite plugin disables itself.
35
+ async function startProxy(proxyPort) {
36
+ const env = readDevAuthEnv(projectDir);
37
+ if (!env) {
38
+ console.warn(
39
+ '[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — proxy disabled'
40
+ );
41
+ return null;
42
+ }
43
+
44
+ const handler = createDevAuthProxyHandler({ ...env, allowOrigin: '*' });
45
+ const proxyServer = createServer((req, res) => void handler(req, res));
46
+
47
+ await new Promise((resolveListen, rejectListen) => {
48
+ proxyServer.once('error', rejectListen);
49
+ proxyServer.listen(proxyPort, PROXY_HOST, resolveListen);
50
+ });
51
+
52
+ console.log(
53
+ `[bardioc-dev-auth] proxying /rest/* → ${env.apiBaseUrl} on http://${PROXY_HOST}:${proxyPort}/rest`
54
+ );
55
+ return proxyServer;
56
+ }
@@ -18,7 +18,7 @@
18
18
  "release:publish": "changeset publish"
19
19
  },
20
20
  "dependencies": {
21
- "@bardioc/app-sdk": "^0.5.0",
21
+ "@bardioc/app-sdk": "^1.0.0",
22
22
  "preact": "^10.26.7"
23
23
  },
24
24
  "devDependencies": {
@@ -9,7 +9,7 @@ type ResultState =
9
9
  | { status: 'error'; request: string; message: string };
10
10
 
11
11
  function SdkDemo() {
12
- const bridge = useSdk();
12
+ const bardioc = useSdk();
13
13
  const notify = useNotify();
14
14
  const [result, setResult] = useState<ResultState>({ status: 'idle' });
15
15
  const [running, setRunning] = useState(false);
@@ -18,14 +18,14 @@ function SdkDemo() {
18
18
  if (running) return;
19
19
  setRunning(true);
20
20
 
21
- const request = 'transport.os.profile.get()';
21
+ const request = 'os.profile.get()';
22
22
  setResult({ status: 'loading', request });
23
23
 
24
24
  const startTime = Date.now();
25
25
  notify('Getting user profile…', 'info');
26
26
 
27
27
  try {
28
- const data = await bridge.transport.os.profile.get();
28
+ const data = await bardioc.os.profile.get();
29
29
 
30
30
  const duration = Date.now() - startTime;
31
31
  notify(`Profile loaded (${duration}ms).`, 'success');
@@ -43,7 +43,7 @@ function SdkDemo() {
43
43
  notify('Hello from {{DISPLAY_NAME}}.', 'info');
44
44
  setResult({
45
45
  status: 'success',
46
- request: 'SDK_NOTIFY',
46
+ request: 'ui.notify',
47
47
  data: { ok: true, message: 'Notification sent to OS' },
48
48
  });
49
49
  }
@@ -99,7 +99,7 @@ function SdkDemo() {
99
99
  <div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
100
100
  {result.request}
101
101
  </div>
102
- <div className="text-destructive break-all whitespace-pre-wrap">{result.message}</div>
102
+ <div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
103
103
  </div>
104
104
  )}
105
105
  {result.status === 'success' && (
@@ -1,4 +1,4 @@
1
- import { installDevBridge, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
1
+ import { installDevHost, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
2
2
  import { AppSdkProvider } from '@bardioc/app-sdk/react';
3
3
  import { render } from 'preact';
4
4
  import { App } from './App';
@@ -15,12 +15,12 @@ const devStandalone = isDevStandalone();
15
15
  const useSdkProvider = insideIframe || devStandalone;
16
16
 
17
17
  if (devStandalone) {
18
- installDevBridge({ debug: false });
18
+ installDevHost({ appId: APP_NAME });
19
19
  }
20
20
 
21
21
  render(
22
22
  useSdkProvider ? (
23
- <AppSdkProvider appId={APP_NAME}>
23
+ <AppSdkProvider>
24
24
  <App />
25
25
  </AppSdkProvider>
26
26
  ) : (
@@ -18,7 +18,7 @@
18
18
  "release:publish": "changeset publish"
19
19
  },
20
20
  "dependencies": {
21
- "@bardioc/app-sdk": "^0.5.0",
21
+ "@bardioc/app-sdk": "^1.0.0",
22
22
  "solid-js": "^1.9.7"
23
23
  },
24
24
  "devDependencies": {