@bardioc/app-sdk 0.4.3 → 0.4.5

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
@@ -86,19 +86,10 @@ import {
86
86
  } from '@bardioc/app-sdk';
87
87
 
88
88
  // React hooks
89
- import {
90
- AppSdkProvider,
91
- useSdk,
92
- useNotify,
93
- useSendToKernel,
94
- } from '@bardioc/app-sdk/react';
89
+ import { AppSdkProvider, useSdk, useNotify, useSendToKernel } from '@bardioc/app-sdk/react';
95
90
 
96
91
  // Standalone dev helpers
97
- import {
98
- installDevBridge,
99
- isDevStandalone,
100
- isInsideIframe,
101
- } from '@bardioc/app-sdk/dev';
92
+ import { installDevBridge, isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
102
93
  ```
103
94
 
104
95
  ## Vite plugin
@@ -4,6 +4,8 @@ export interface DevAuthProxyOptions {
4
4
  apiBaseUrl: string;
5
5
  /** Dev secret copied from the backend. */
6
6
  devSecret: string;
7
+ /** App slug stamped as `X-App-ID`, mirroring how the host identifies iframe apps. */
8
+ appId?: string;
7
9
  /** Optional CORS origin (e.g. `*`). */
8
10
  allowOrigin?: string;
9
11
  }
package/dist/dev-proxy.js CHANGED
@@ -12,7 +12,7 @@ export function applyCors(res, allowOrigin) {
12
12
  }
13
13
  res.setHeader('Access-Control-Allow-Origin', allowOrigin);
14
14
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
15
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Scope-ID');
15
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Scope-ID, X-Requested-With');
16
16
  }
17
17
  export function sendJson(res, status, body, options = {}) {
18
18
  applyCors(res, options.allowOrigin);
@@ -33,7 +33,7 @@ function addDevSecretHeader(req, headers, devSecret) {
33
33
  }
34
34
  export function createDevAuthProxyHandler(options) {
35
35
  const apiBaseUrl = normalizeApiBaseUrl(options.apiBaseUrl);
36
- const { devSecret, allowOrigin } = options;
36
+ const { devSecret, allowOrigin, appId } = options;
37
37
  return async (req, res) => {
38
38
  const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
39
39
  if (req.method === 'OPTIONS') {
@@ -54,6 +54,13 @@ export function createDevAuthProxyHandler(options) {
54
54
  headers.set('Content-Type', contentType);
55
55
  }
56
56
  addDevSecretHeader(req, headers, devSecret);
57
+ // The host stamps X-App-ID from its own registration data, never from the app — the
58
+ // dev proxy does the same with the configured app slug.
59
+ if (appId) {
60
+ headers.set('X-App-ID', appId);
61
+ }
62
+ // BFF rejects /bardioc/ calls without this CSRF marker header; stamp it like the host does.
63
+ headers.set('X-Requested-With', 'XMLHttpRequest');
57
64
  // Forward a few useful headers from the browser.
58
65
  const forwardedHeaders = ['accept', 'accept-language', 'x-scope-id'];
59
66
  for (const name of forwardedHeaders) {
package/dist/dev.js CHANGED
@@ -10,12 +10,6 @@ export function isDevStandalone() {
10
10
  const metaEnv = import.meta.env;
11
11
  return Boolean(metaEnv?.DEV) && !isInsideIframe();
12
12
  }
13
- function shouldForwardScope(policy, scopeId) {
14
- if (policy === 'required') {
15
- return true;
16
- }
17
- return policy === 'optional' && scopeId !== undefined;
18
- }
19
13
  export function installDevBridge(options = {}) {
20
14
  const apiBaseUrl = getBaseUrl(options.apiBaseUrl);
21
15
  const debug = options.debug ?? false;
@@ -44,8 +38,17 @@ export function installDevBridge(options = {}) {
44
38
  if (body && payload.contentType) {
45
39
  headers.set('Content-Type', payload.contentType);
46
40
  }
47
- if (shouldForwardScope(payload.scopePolicy, payload.scopeId)) {
48
- headers.set('X-Scope-ID', payload.scopeId ?? '');
41
+ // BFF rejects /bardioc/ calls without this CSRF marker header.
42
+ headers.set('X-Requested-With', 'XMLHttpRequest');
43
+ // Mirror the host bridge: a required scope without an id is rejected locally, and a
44
+ // resolved scope id is always forwarded regardless of policy. Standalone has no
45
+ // instance, so there is no instance-based fallback here.
46
+ const resolvedScopeId = payload.scopeId;
47
+ if (payload.scopePolicy === 'required' && !resolvedScopeId) {
48
+ throw new Error('Scope ID is required');
49
+ }
50
+ if (resolvedScopeId) {
51
+ headers.set('X-Scope-ID', resolvedScopeId);
49
52
  }
50
53
  const url = `${apiBaseUrl}${payload.path}`;
51
54
  log('[transport]', payload.method ?? 'GET', url);
@@ -57,19 +60,24 @@ export function installDevBridge(options = {}) {
57
60
  cache: 'no-store',
58
61
  });
59
62
  const contentType = response.headers.get('content-type') ?? '';
60
- const text = await response.text();
61
63
  if (!response.ok) {
62
- const responsePayload = contentType.includes('application/json')
63
- ? JSON.parse(text)
64
- : text;
64
+ const text = await response.text();
65
+ const responsePayload = contentType.includes('application/json') && text ? JSON.parse(text) : text;
65
66
  const err = new Error(`Transport ${payload.path} failed: ${response.status}`);
66
67
  Object.assign(err, { status: response.status, responsePayload });
67
68
  throw err;
68
69
  }
69
- if (!text) {
70
- return null;
70
+ // Same response typing as the host: JSON is parsed, empty/plain text becomes a string,
71
+ // anything binary (zip, octet-stream, images/svg, …) round-trips as a Blob.
72
+ if (contentType.includes('application/json')) {
73
+ const text = await response.text();
74
+ return text ? JSON.parse(text) : null;
75
+ }
76
+ if (contentType === '' || contentType.startsWith('text/')) {
77
+ const text = await response.text();
78
+ return text || null;
71
79
  }
72
- return contentType.includes('application/json') ? JSON.parse(text) : text;
80
+ return response.blob();
73
81
  }
74
82
  async function handleKernelProxy(payload) {
75
83
  if (payload.kernelType === 'PING') {
package/dist/dot-env.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export declare function readDotEnvFile(filePath: string): Record<string, string>;
2
- export declare function readDotEnvValue(filePath: string, key: string): string | null;
package/dist/dot-env.js CHANGED
@@ -17,6 +17,3 @@ export function readDotEnvFile(filePath) {
17
17
  }
18
18
  return env;
19
19
  }
20
- export function readDotEnvValue(filePath, key) {
21
- return readDotEnvFile(filePath)[key] ?? null;
22
- }
package/dist/react.js CHANGED
@@ -8,7 +8,12 @@ function applyOsConfigToDocument(osConfig) {
8
8
  return;
9
9
  }
10
10
  const root = document.documentElement;
11
- root.classList.toggle('dark', osConfig.theme === 'dark');
11
+ const isDark = osConfig.theme === 'dark';
12
+ root.classList.toggle('dark', isDark);
13
+ // Advertise the resolved theme to the UA so externally-referenced SVGs (app icon.svg
14
+ // via <img>, favicons) theme-switch through @media (prefers-color-scheme). Mirrors
15
+ // webos-host (next-themes enableColorScheme) and Storybook's preview decorator.
16
+ root.style.colorScheme = isDark ? 'dark' : 'light';
12
17
  if (osConfig.language) {
13
18
  root.lang = osConfig.language;
14
19
  }
package/dist/vite.d.ts CHANGED
@@ -1,9 +1,27 @@
1
1
  /** Vite plugin for Bardioc iframe apps. Usage: `bardiocApp({ appName, port })` */
2
- export { bardiocDevAuth, type BardiocDevAuthOptions } from './dev-auth-vite.js';
2
+ import { type DevAuthProxyOptions } from './dev-proxy.js';
3
3
  export interface BardiocAppOptions {
4
4
  appName?: string;
5
5
  port: number;
6
6
  previewPort?: number;
7
7
  }
8
+ type VitePlugin = {
9
+ name: string;
10
+ apply?: 'serve' | 'build';
11
+ enforce?: 'pre' | 'post';
12
+ config?: (config: any, env: {
13
+ command: string;
14
+ }) => any;
15
+ configureServer?: (server: any) => void;
16
+ transformIndexHtml?: () => Promise<any[] | undefined> | any[] | undefined;
17
+ closeBundle?: () => void;
18
+ };
19
+ export interface BardiocDevAuthOptions {
20
+ appName: string;
21
+ /** Optional explicit proxy options. If omitted, DEV_SECRET and API_BASE_URL are read from env. */
22
+ proxy?: DevAuthProxyOptions;
23
+ }
24
+ export declare function bardiocDevAuth(options: BardiocDevAuthOptions): VitePlugin;
8
25
  /** Returns an array of Vite plugins for a Bardioc iframe app. */
9
26
  export declare function bardiocApp(opts: BardiocAppOptions): any[];
27
+ export {};
package/dist/vite.js CHANGED
@@ -3,8 +3,9 @@ import { execSync, spawn } from 'node:child_process';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
  import { resolve, sep } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { createDevAuthProxyHandler } from './dev-proxy.js';
7
+ import { readDotEnvFile } from './dot-env.js';
6
8
  import { MANIFEST_FILENAME, validateManifest } from './manifest.js';
7
- export { bardiocDevAuth } from './dev-auth-vite.js';
8
9
  function hasCommand(cmd) {
9
10
  try {
10
11
  execSync(`which ${cmd}`, { stdio: 'ignore' });
@@ -216,6 +217,82 @@ function manifestCheck() {
216
217
  },
217
218
  };
218
219
  }
220
+ function readRuntimeEnv(server) {
221
+ const envDir = server.config.envDir ?? server.config.root;
222
+ const mode = server.config.mode?.trim();
223
+ const env = {};
224
+ for (const fileName of [
225
+ '.env',
226
+ '.env.local',
227
+ mode ? `.env.${mode}` : null,
228
+ mode ? `.env.${mode}.local` : null,
229
+ ]) {
230
+ if (!fileName)
231
+ continue;
232
+ const filePath = resolve(envDir, fileName);
233
+ if (existsSync(filePath)) {
234
+ Object.assign(env, readDotEnvFile(filePath));
235
+ }
236
+ }
237
+ for (const [key, value] of Object.entries(process.env)) {
238
+ if (typeof value === 'string') {
239
+ env[key] = value;
240
+ }
241
+ }
242
+ return {
243
+ DEV_SECRET: env.DEV_SECRET?.trim(),
244
+ API_BASE_URL: env.API_BASE_URL?.trim().replace(/\/$/, ''),
245
+ };
246
+ }
247
+ export function bardiocDevAuth(options) {
248
+ let proxyOptions = options.proxy ?? null;
249
+ return {
250
+ name: 'bardioc-dev-auth',
251
+ apply: 'serve',
252
+ enforce: 'pre',
253
+ configureServer(server) {
254
+ if (!proxyOptions) {
255
+ const env = readRuntimeEnv(server);
256
+ if (!env.DEV_SECRET || !env.API_BASE_URL) {
257
+ server.config.logger.warn(`[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — disabled for appName="${options.appName}".`);
258
+ return;
259
+ }
260
+ proxyOptions = {
261
+ apiBaseUrl: env.API_BASE_URL,
262
+ devSecret: env.DEV_SECRET,
263
+ appId: options.appName,
264
+ };
265
+ }
266
+ const handler = createDevAuthProxyHandler(proxyOptions);
267
+ const basePath = server.config.base.endsWith('/')
268
+ ? server.config.base.slice(0, -1)
269
+ : server.config.base;
270
+ server.config.logger.info(`[bardioc-dev-auth] proxying /rest/* → ${proxyOptions.apiBaseUrl} for appName="${options.appName}"`);
271
+ server.middlewares.use(async (req, res, next) => {
272
+ const originalUrl = req.url ?? '';
273
+ const parsed = new URL(originalUrl, 'http://localhost');
274
+ const pathname = parsed.pathname;
275
+ const routePath = pathname.startsWith(basePath)
276
+ ? pathname.slice(basePath.length) || '/'
277
+ : pathname;
278
+ if (!routePath.startsWith('/rest/')) {
279
+ next();
280
+ return;
281
+ }
282
+ try {
283
+ req.url = `${routePath}${parsed.search}`;
284
+ await handler(req, res);
285
+ }
286
+ catch (error) {
287
+ next(error);
288
+ }
289
+ finally {
290
+ req.url = originalUrl;
291
+ }
292
+ });
293
+ },
294
+ };
295
+ }
219
296
  /** Returns an array of Vite plugins for a Bardioc iframe app. */
220
297
  export function bardiocApp(opts) {
221
298
  const { port, previewPort } = opts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bardioc/app-sdk",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "SDK for building apps for the Bardioc OS",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -69,16 +69,15 @@
69
69
  }
70
70
  },
71
71
  "devDependencies": {
72
- "@vitest/coverage-v8": "4.1.5",
73
- "@types/node": "^25.6.0",
74
- "@types/react": "^19.2.14",
75
- "@testing-library/jest-dom": "6.9.1",
72
+ "@vitest/coverage-v8": "^4.1.8",
73
+ "@types/node": "^25.9.1",
74
+ "@types/react": "19.1.16",
76
75
  "@testing-library/react": "16.3.2",
77
76
  "jsdom": "29.1.0",
78
- "react": "19.2.5",
79
- "react-dom": "19.2.5",
80
- "typescript": "^5.9.3",
81
- "vitest": "4.1.5"
77
+ "react": "19.2.6",
78
+ "react-dom": "19.2.6",
79
+ "typescript": "6.0.3",
80
+ "vitest": "^4.1.8"
82
81
  },
83
82
  "scripts": {
84
83
  "build": "tsc",
@@ -1,24 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- export type ProxyPayload = {
3
- sessionKey?: string;
4
- hostUrl?: string;
5
- scopeId?: string | null;
6
- scopePolicy?: 'none' | 'optional' | 'required';
7
- path: string;
8
- method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
9
- body?: unknown;
10
- contentType?: string;
11
- };
12
- interface ProxyTransportOptions {
13
- appId: string;
14
- hostUrl: string;
15
- devSessionKey: string;
16
- }
17
- interface SendJsonOptions {
18
- allowOrigin?: string;
19
- }
20
- export declare function readJsonBody<T = unknown>(req: IncomingMessage): Promise<T | null>;
21
- export declare function applyCors(res: ServerResponse, allowOrigin?: string): void;
22
- export declare function sendJson(res: ServerResponse, status: number, body: unknown, options?: SendJsonOptions): void;
23
- export declare function forwardDevAuthProxyRequest(payload: ProxyPayload, options: ProxyTransportOptions): Promise<Response>;
24
- export {};
@@ -1,59 +0,0 @@
1
- export async function readJsonBody(req) {
2
- return new Promise(resolveBody => {
3
- const chunks = [];
4
- req.on('data', (chunk) => chunks.push(chunk));
5
- req.on('end', () => {
6
- const buffer = Buffer.concat(chunks);
7
- if (buffer.length === 0) {
8
- resolveBody(null);
9
- return;
10
- }
11
- try {
12
- resolveBody(JSON.parse(buffer.toString('utf8')));
13
- }
14
- catch {
15
- resolveBody(null);
16
- }
17
- });
18
- req.on('error', () => resolveBody(null));
19
- });
20
- }
21
- export function applyCors(res, allowOrigin) {
22
- if (!allowOrigin) {
23
- return;
24
- }
25
- res.setHeader('Access-Control-Allow-Origin', allowOrigin);
26
- res.setHeader('Access-Control-Allow-Methods', 'POST,OPTIONS');
27
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
28
- }
29
- export function sendJson(res, status, body, options = {}) {
30
- applyCors(res, options.allowOrigin);
31
- res.statusCode = status;
32
- res.setHeader('Content-Type', 'application/json');
33
- res.setHeader('Cache-Control', 'no-store');
34
- res.end(JSON.stringify(body));
35
- }
36
- export async function forwardDevAuthProxyRequest(payload, options) {
37
- const hostUrl = payload.hostUrl ?? options.hostUrl;
38
- const sessionKey = payload.sessionKey ?? options.devSessionKey;
39
- if (!hostUrl || !sessionKey) {
40
- throw new Error('missing_dev_session');
41
- }
42
- return fetch(`${hostUrl.replace(/\/$/, '')}/api/transport`, {
43
- method: 'POST',
44
- headers: {
45
- 'Content-Type': 'application/json',
46
- 'X-Dev-Session-Key': sessionKey,
47
- },
48
- body: JSON.stringify({
49
- appId: options.appId,
50
- path: payload.path,
51
- method: payload.method ?? 'GET',
52
- body: payload.body,
53
- contentType: payload.contentType,
54
- scopePolicy: payload.scopePolicy,
55
- scopeId: payload.scopeId ?? null,
56
- }),
57
- cache: 'no-store',
58
- });
59
- }
@@ -1,30 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { type DevAuthProxyOptions } from './dev-proxy.js';
3
- type VitePlugin = {
4
- name: string;
5
- apply?: 'serve' | 'build';
6
- enforce?: 'pre' | 'post';
7
- configureServer?: (server: ViteDevServerLike) => void;
8
- };
9
- interface ViteDevServerLike {
10
- config: {
11
- root: string;
12
- envDir?: string;
13
- mode?: string;
14
- base: string;
15
- logger: {
16
- warn: (message: string) => void;
17
- info: (message: string) => void;
18
- };
19
- };
20
- middlewares: {
21
- use: (handler: (req: IncomingMessage, res: ServerResponse, next: (error?: unknown) => void) => void) => void;
22
- };
23
- }
24
- export interface BardiocDevAuthOptions {
25
- appName: string;
26
- /** Optional explicit proxy options. If omitted, DEV_SECRET and API_BASE_URL are read from env. */
27
- proxy?: DevAuthProxyOptions;
28
- }
29
- export declare function bardiocDevAuth(options: BardiocDevAuthOptions): VitePlugin;
30
- export {};
@@ -1,79 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { createDevAuthProxyHandler } from './dev-proxy.js';
4
- import { readDotEnvFile } from './dot-env.js';
5
- function readRuntimeEnv(server) {
6
- const envDir = server.config.envDir ?? server.config.root;
7
- const mode = server.config.mode?.trim();
8
- const env = {};
9
- for (const fileName of [
10
- '.env',
11
- '.env.local',
12
- mode ? `.env.${mode}` : null,
13
- mode ? `.env.${mode}.local` : null,
14
- ]) {
15
- if (!fileName)
16
- continue;
17
- const filePath = resolve(envDir, fileName);
18
- if (existsSync(filePath)) {
19
- Object.assign(env, readDotEnvFile(filePath));
20
- }
21
- }
22
- for (const [key, value] of Object.entries(process.env)) {
23
- if (typeof value === 'string') {
24
- env[key] = value;
25
- }
26
- }
27
- return {
28
- DEV_SECRET: env.DEV_SECRET?.trim(),
29
- API_BASE_URL: env.API_BASE_URL?.trim().replace(/\/$/, ''),
30
- };
31
- }
32
- export function bardiocDevAuth(options) {
33
- let proxyOptions = options.proxy ?? null;
34
- return {
35
- name: 'bardioc-dev-auth',
36
- apply: 'serve',
37
- enforce: 'pre',
38
- configureServer(server) {
39
- if (!proxyOptions) {
40
- const env = readRuntimeEnv(server);
41
- if (!env.DEV_SECRET || !env.API_BASE_URL) {
42
- server.config.logger.warn(`[bardioc-dev-auth] DEV_SECRET and API_BASE_URL are required for dev auth — disabled for appName="${options.appName}".`);
43
- return;
44
- }
45
- proxyOptions = {
46
- apiBaseUrl: env.API_BASE_URL,
47
- devSecret: env.DEV_SECRET,
48
- };
49
- }
50
- const handler = createDevAuthProxyHandler(proxyOptions);
51
- const basePath = server.config.base.endsWith('/')
52
- ? server.config.base.slice(0, -1)
53
- : server.config.base;
54
- server.config.logger.info(`[bardioc-dev-auth] proxying /rest/* → ${proxyOptions.apiBaseUrl} for appName="${options.appName}"`);
55
- server.middlewares.use(async (req, res, next) => {
56
- const originalUrl = req.url ?? '';
57
- const parsed = new URL(originalUrl, 'http://localhost');
58
- const pathname = parsed.pathname;
59
- const routePath = pathname.startsWith(basePath)
60
- ? pathname.slice(basePath.length) || '/'
61
- : pathname;
62
- if (!routePath.startsWith('/rest/')) {
63
- next();
64
- return;
65
- }
66
- try {
67
- req.url = `${routePath}${parsed.search}`;
68
- await handler(req, res);
69
- }
70
- catch (error) {
71
- next(error);
72
- }
73
- finally {
74
- req.url = originalUrl;
75
- }
76
- });
77
- },
78
- };
79
- }
@@ -1,34 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- interface CliConfig {
3
- hostUrl?: string;
4
- cliAccessToken?: string;
5
- cliAccessTokenExpiresAt?: number;
6
- }
7
- interface RuntimeEnv {
8
- VITE_ENABLE_DEV_AUTH?: string;
9
- VITE_APP_ID?: string;
10
- DEV_AUTH_HOST_URL?: string;
11
- }
12
- interface DevSessionResponse {
13
- sessionKey: string;
14
- hostUrl: string;
15
- appId: string;
16
- }
17
- interface CliMainRuntime {
18
- argv?: string[];
19
- cwd?: () => string;
20
- env?: NodeJS.ProcessEnv;
21
- platform?: NodeJS.Platform;
22
- spawnCommand?: typeof spawn;
23
- stdout?: Pick<NodeJS.WriteStream, 'write'>;
24
- stderr?: Pick<NodeJS.WriteStream, 'write'>;
25
- exit?: (code: number) => void;
26
- readCliConfig?: () => CliConfig | null;
27
- }
28
- export declare function readLayeredEnv(cwd: string, runtimeEnv?: NodeJS.ProcessEnv): RuntimeEnv;
29
- export declare function readCliConfigFromFile(configPath: string): CliConfig | null;
30
- export declare function normalizeHostUrl(hostUrl: string): string;
31
- export declare function assertCliConfig(config: CliConfig | null, hostUrl: string): asserts config is CliConfig;
32
- export declare function fetchDevSession(hostUrl: string, cliAccessToken: string, appId: string): Promise<DevSessionResponse>;
33
- export declare function main(runtime?: CliMainRuntime): Promise<void>;
34
- export {};
@@ -1,125 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { readDotEnvFile } from './dot-env.js';
7
- export function readLayeredEnv(cwd, runtimeEnv = process.env) {
8
- const env = {};
9
- for (const fileName of ['.env', '.env.local']) {
10
- const filePath = resolve(cwd, fileName);
11
- if (!existsSync(filePath)) {
12
- continue;
13
- }
14
- Object.assign(env, readDotEnvFile(filePath));
15
- }
16
- for (const [key, value] of Object.entries(runtimeEnv)) {
17
- if (typeof value === 'string') {
18
- env[key] = value;
19
- }
20
- }
21
- return env;
22
- }
23
- export function readCliConfigFromFile(configPath) {
24
- if (!existsSync(configPath)) {
25
- return null;
26
- }
27
- const parsed = JSON.parse(readFileSync(configPath, 'utf8'));
28
- return parsed;
29
- }
30
- function readCliConfig() {
31
- return readCliConfigFromFile(resolve(homedir(), '.bardioc', 'config.json'));
32
- }
33
- export function normalizeHostUrl(hostUrl) {
34
- return hostUrl.trim().replace(/\/$/, '');
35
- }
36
- export function assertCliConfig(config, hostUrl) {
37
- if (!config?.hostUrl || !config.cliAccessToken) {
38
- throw new Error('Missing CLI login. Run `bardioc login` first.');
39
- }
40
- if (normalizeHostUrl(config.hostUrl) !== normalizeHostUrl(hostUrl)) {
41
- throw new Error(`CLI login host ${normalizeHostUrl(config.hostUrl)} does not match DEV_AUTH_HOST_URL ${normalizeHostUrl(hostUrl)}.`);
42
- }
43
- if (typeof config.cliAccessTokenExpiresAt === 'number' &&
44
- config.cliAccessTokenExpiresAt > 0 &&
45
- config.cliAccessTokenExpiresAt * 1000 <= Date.now()) {
46
- throw new Error('Stored CLI login is expired. Run `bardioc login` again.');
47
- }
48
- }
49
- export async function fetchDevSession(hostUrl, cliAccessToken, appId) {
50
- const response = await fetch(`${normalizeHostUrl(hostUrl)}/api/auth/dev-session`, {
51
- method: 'POST',
52
- headers: {
53
- 'Content-Type': 'application/json',
54
- Authorization: `Bearer ${cliAccessToken}`,
55
- },
56
- body: JSON.stringify({ appId }),
57
- cache: 'no-store',
58
- });
59
- if (!response.ok) {
60
- const data = (await response.json().catch(() => ({})));
61
- throw new Error(data.error ?? `Dev session failed (${response.status})`);
62
- }
63
- return (await response.json());
64
- }
65
- export async function main(runtime = {}) {
66
- const argv = runtime.argv ?? process.argv;
67
- const currentEnv = runtime.env ?? process.env;
68
- const currentCwd = runtime.cwd ?? (() => process.cwd());
69
- const spawnCommand = runtime.spawnCommand ?? spawn;
70
- const stdout = runtime.stdout ?? process.stdout;
71
- const stderr = runtime.stderr ?? process.stderr;
72
- const exit = runtime.exit ?? process.exit;
73
- const separatorIndex = argv.indexOf('--');
74
- const commandArgs = separatorIndex >= 0 ? argv.slice(separatorIndex + 1) : [];
75
- if (commandArgs.length === 0) {
76
- throw new Error('Missing command after `--`. Example: bardioc-dev-session -- vite');
77
- }
78
- const cwd = currentCwd();
79
- const env = readLayeredEnv(cwd, currentEnv);
80
- const devAuthEnabled = env.VITE_ENABLE_DEV_AUTH === 'true';
81
- const childEnv = { ...currentEnv };
82
- const [command, ...commandRestArgs] = commandArgs;
83
- if (!command) {
84
- throw new Error('Missing command after `--`. Example: bardioc-dev-session -- vite');
85
- }
86
- if (devAuthEnabled) {
87
- const appId = env.VITE_APP_ID?.trim();
88
- const hostUrl = env.DEV_AUTH_HOST_URL?.trim();
89
- if (!appId) {
90
- throw new Error('Missing VITE_APP_ID for standalone dev auth.');
91
- }
92
- if (!hostUrl) {
93
- throw new Error('Missing DEV_AUTH_HOST_URL for standalone dev auth.');
94
- }
95
- const cliConfig = (runtime.readCliConfig ?? readCliConfig)();
96
- assertCliConfig(cliConfig, hostUrl);
97
- const devSession = await fetchDevSession(hostUrl, cliConfig.cliAccessToken, appId);
98
- childEnv.DEV_AUTH_HOST_URL = normalizeHostUrl(devSession.hostUrl);
99
- childEnv.DEV_SESSION_KEY = devSession.sessionKey;
100
- stdout.write(`[bardioc-dev-session] Refreshed dev session for appId="${devSession.appId}" via ${childEnv.DEV_AUTH_HOST_URL}\n`);
101
- }
102
- const child = spawnCommand(command, commandRestArgs, {
103
- stdio: 'inherit',
104
- env: childEnv,
105
- shell: (runtime.platform ?? process.platform) === 'win32',
106
- });
107
- child.on('exit', (code) => {
108
- exit(code ?? 0);
109
- });
110
- child.on('error', (error) => {
111
- stderr.write(`${error.message}\n`);
112
- exit(1);
113
- });
114
- }
115
- const isDirectRun = process.argv[1]
116
- ? realpathSync.native(resolve(process.argv[1])) ===
117
- realpathSync.native(fileURLToPath(import.meta.url))
118
- : false;
119
- if (isDirectRun) {
120
- void main().catch(error => {
121
- const message = error instanceof Error ? error.message : String(error);
122
- process.stderr.write(`[bardioc-dev-session] ${message}\n`);
123
- process.exit(1);
124
- });
125
- }