@bardioc/create-bardioc-app 0.5.8 → 0.6.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/package.json +1 -1
- package/templates/_base/public/app-manifest.json +1 -2
- package/templates/angular/package.json +1 -1
- package/templates/angular/scripts/dev-env.mjs +31 -10
- package/templates/angular/scripts/dev.mjs +37 -8
- package/templates/angular/src/app/app.component.ts +42 -24
- package/templates/angular/src/app/bardioc.token.ts +2 -2
- package/templates/angular/src/main.ts +5 -8
- package/templates/nextjs/app/page.tsx +8 -14
- package/templates/nextjs/package.json +1 -1
- package/templates/nextjs/scripts/dev-env.mjs +31 -10
- package/templates/nextjs/scripts/dev.mjs +37 -8
- package/templates/preact/package.json +1 -1
- package/templates/preact/src/App.tsx +5 -5
- package/templates/preact/src/main.tsx +3 -9
- package/templates/solid/package.json +1 -1
- package/templates/solid/src/App.tsx +5 -5
- package/templates/solid/src/bardioc-sdk.tsx +11 -23
- package/templates/solid/src/main.tsx +5 -9
- package/templates/svelte/package.json +1 -1
- package/templates/svelte/src/App.svelte +7 -7
- package/templates/svelte/src/main.ts +5 -11
- package/templates/vite/package.json +1 -1
- package/templates/vite/public/app-manifest.json +0 -1
- package/templates/vite/src/auth/onboarding.tsx +4 -2
- package/templates/vite/src/climate/stations-view.tsx +1 -8
- package/templates/vite/src/climate/use-climate.ts +6 -6
- package/templates/vite/src/events/live-events-view.tsx +7 -9
- package/templates/vite/src/graph/explorer-view.tsx +4 -11
- package/templates/vite/src/graph/requests.ts +16 -19
- package/templates/vite/src/locales/de.json +5 -6
- package/templates/vite/src/locales/en.json +6 -7
- package/templates/vite/src/main.tsx +3 -9
- package/templates/vite/src/profile/profile-view.tsx +1 -8
- package/templates/vite/src/profile/use-profile.ts +3 -3
- package/templates/vite/src/shell/about-dialog.tsx +2 -8
- package/templates/vite/src/shell/app-shell.tsx +48 -11
- package/templates/vue/package.json +1 -1
- package/templates/vue/src/App.vue +7 -7
- package/templates/vue/src/bardioc-sdk.ts +6 -15
- package/templates/vue/src/main.ts +5 -10
- package/templates/angular/src/app/bardioc-bridge.ts +0 -5
package/package.json
CHANGED
|
@@ -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.
|
|
31
|
+
"@bardioc/app-sdk": "^1.0.1",
|
|
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
|
-
//
|
|
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
|
|
12
|
-
// here
|
|
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 (
|
|
27
|
-
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
13
|
-
|
|
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
|
|
18
|
-
|
|
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 {
|
|
5
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
53
|
-
|
|
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
|
|
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
|
|
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">{{
|
|
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
|
|
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.
|
|
117
|
+
if (this.running() || !this.bardioc) return;
|
|
104
118
|
this.running.set(true);
|
|
105
119
|
|
|
106
|
-
const request = '
|
|
120
|
+
const request = 'os.profile.get()';
|
|
107
121
|
this.result.set({ status: 'loading', request });
|
|
108
122
|
|
|
109
123
|
const startTime = Date.now();
|
|
110
|
-
this.
|
|
124
|
+
this.notify('Getting user profile…', 'info');
|
|
111
125
|
|
|
112
126
|
try {
|
|
113
|
-
const data = await this.
|
|
127
|
+
const data = await this.bardioc.os.profile.get();
|
|
114
128
|
const duration = Date.now() - startTime;
|
|
115
|
-
this.
|
|
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.
|
|
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.
|
|
128
|
-
this.
|
|
141
|
+
if (!this.bardioc) return;
|
|
142
|
+
this.notify('Hello from {{DISPLAY_NAME}}.', 'info');
|
|
129
143
|
this.result.set({
|
|
130
144
|
status: 'success',
|
|
131
|
-
request: '
|
|
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 {
|
|
2
|
+
import type { Bardioc } from '@bardioc/app-sdk';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const BARDIOC = new InjectionToken<Bardioc>('BARDIOC');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { connect } from '@bardioc/app-sdk';
|
|
1
2
|
import {
|
|
2
|
-
|
|
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 {
|
|
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,7 @@ const useSdkProvider = insideIframe || devStandalone;
|
|
|
24
24
|
|
|
25
25
|
async function bootstrap() {
|
|
26
26
|
if (devStandalone) {
|
|
27
|
-
|
|
27
|
+
installDevHost({
|
|
28
28
|
apiBaseUrl: `http://127.0.0.1:${proxyPort}/rest`,
|
|
29
29
|
});
|
|
30
30
|
}
|
|
@@ -34,10 +34,7 @@ async function bootstrap() {
|
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
if (useSdkProvider) {
|
|
37
|
-
providers.push({
|
|
38
|
-
provide: BARDIOC_BRIDGE,
|
|
39
|
-
useValue: createBardiocBridge(),
|
|
40
|
-
});
|
|
37
|
+
providers.push({ provide: BARDIOC, useValue: await connect() });
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
bootstrapApplication(AppComponent, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
|
@@ -10,13 +10,11 @@ type ResultState =
|
|
|
10
10
|
| { status: 'success'; request: string; data: unknown }
|
|
11
11
|
| { status: 'error'; request: string; message: string };
|
|
12
12
|
|
|
13
|
-
const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME?.trim();
|
|
14
|
-
|
|
15
13
|
// The port the app falls back to when it is not being served over HTTP (LOCALHOST_PORT moves it).
|
|
16
14
|
const DEFAULT_PORT = Number('{{PORT}}');
|
|
17
15
|
|
|
18
16
|
function SdkDemo() {
|
|
19
|
-
const
|
|
17
|
+
const bardioc = useSdk();
|
|
20
18
|
const notify = useNotify();
|
|
21
19
|
const [result, setResult] = useState<ResultState>({ status: 'idle' });
|
|
22
20
|
const [running, setRunning] = useState(false);
|
|
@@ -25,14 +23,14 @@ function SdkDemo() {
|
|
|
25
23
|
if (running) return;
|
|
26
24
|
setRunning(true);
|
|
27
25
|
|
|
28
|
-
const request = '
|
|
26
|
+
const request = 'os.profile.get()';
|
|
29
27
|
setResult({ status: 'loading', request });
|
|
30
28
|
|
|
31
29
|
const startTime = Date.now();
|
|
32
30
|
notify('Getting user profile…', 'info');
|
|
33
31
|
|
|
34
32
|
try {
|
|
35
|
-
const data = await
|
|
33
|
+
const data = await bardioc.os.profile.get();
|
|
36
34
|
|
|
37
35
|
const duration = Date.now() - startTime;
|
|
38
36
|
notify(`Profile loaded (${duration}ms).`, 'success');
|
|
@@ -50,7 +48,7 @@ function SdkDemo() {
|
|
|
50
48
|
notify('Hello from {{DISPLAY_NAME}}.', 'info');
|
|
51
49
|
setResult({
|
|
52
50
|
status: 'success',
|
|
53
|
-
request: '
|
|
51
|
+
request: 'ui.notify',
|
|
54
52
|
data: { ok: true, message: 'Notification sent to OS' },
|
|
55
53
|
});
|
|
56
54
|
}
|
|
@@ -106,7 +104,7 @@ function SdkDemo() {
|
|
|
106
104
|
<div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
|
|
107
105
|
{result.request}
|
|
108
106
|
</div>
|
|
109
|
-
<div className="text-destructive
|
|
107
|
+
<div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
|
|
110
108
|
</div>
|
|
111
109
|
)}
|
|
112
110
|
{result.status === 'success' && (
|
|
@@ -141,23 +139,19 @@ function PreviewShell() {
|
|
|
141
139
|
export default function Page() {
|
|
142
140
|
const useSdkProvider = isInsideIframe() || isDevStandalone();
|
|
143
141
|
|
|
144
|
-
if (!APP_NAME) {
|
|
145
|
-
throw new Error('Missing NEXT_PUBLIC_APP_NAME for runtime');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
142
|
useEffect(() => {
|
|
149
143
|
if (isDevStandalone()) {
|
|
150
144
|
// scripts/dev.mjs runs the dev-auth proxy on the app port + 100, so read the port the page
|
|
151
145
|
// is actually served on rather than the one baked in at scaffold time.
|
|
152
146
|
const appPort = Number(window.location.port) || DEFAULT_PORT;
|
|
153
|
-
|
|
147
|
+
installDevHost({ apiBaseUrl: `http://127.0.0.1:${appPort + 100}/rest` });
|
|
154
148
|
}
|
|
155
149
|
}, []);
|
|
156
150
|
|
|
157
151
|
return (
|
|
158
152
|
<div className="bg-background text-foreground @container/app min-h-screen">
|
|
159
153
|
{useSdkProvider ? (
|
|
160
|
-
<AppSdkProvider
|
|
154
|
+
<AppSdkProvider>
|
|
161
155
|
<SdkDemo />
|
|
162
156
|
</AppSdkProvider>
|
|
163
157
|
) : (
|
|
@@ -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
|
-
//
|
|
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
|
|
12
|
-
// here
|
|
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 (
|
|
27
|
-
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
13
|
-
|
|
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
|
|
18
|
-
|
|
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
|
+
}
|
|
@@ -9,7 +9,7 @@ type ResultState =
|
|
|
9
9
|
| { status: 'error'; request: string; message: string };
|
|
10
10
|
|
|
11
11
|
function SdkDemo() {
|
|
12
|
-
const
|
|
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 = '
|
|
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
|
|
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: '
|
|
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
|
|
102
|
+
<div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
|
|
103
103
|
</div>
|
|
104
104
|
)}
|
|
105
105
|
{result.status === 'success' && (
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
5
5
|
import './index.css';
|
|
6
6
|
|
|
7
|
-
const APP_NAME = import.meta.env.VITE_APP_NAME?.trim();
|
|
8
|
-
|
|
9
|
-
if (!APP_NAME) {
|
|
10
|
-
throw new Error('Missing VITE_APP_NAME for runtime');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
const insideIframe = isInsideIframe();
|
|
14
8
|
const devStandalone = isDevStandalone();
|
|
15
9
|
const useSdkProvider = insideIframe || devStandalone;
|
|
16
10
|
|
|
17
11
|
if (devStandalone) {
|
|
18
|
-
|
|
12
|
+
installDevHost();
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
render(
|
|
22
16
|
useSdkProvider ? (
|
|
23
|
-
<AppSdkProvider
|
|
17
|
+
<AppSdkProvider>
|
|
24
18
|
<App />
|
|
25
19
|
</AppSdkProvider>
|
|
26
20
|
) : (
|