@bardioc/create-bardioc-app 0.5.5 → 0.5.8

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 (79) hide show
  1. package/README.md +1 -3
  2. package/bin/create.mjs +2 -4
  3. package/package.json +1 -1
  4. package/templates/_base/README.md +17 -3
  5. package/templates/_base/scripts/stamp-manifest.mjs +1 -1
  6. package/templates/_opt-link/scripts/link-source.mjs +7 -5
  7. package/templates/angular/.env.example +7 -2
  8. package/templates/angular/_gitignore +1 -0
  9. package/templates/angular/package.json +2 -4
  10. package/templates/angular/scripts/dev-env.mjs +41 -0
  11. package/templates/angular/scripts/dev.mjs +27 -0
  12. package/templates/angular/scripts/sync-runtime-env.mjs +3 -48
  13. package/templates/angular/src/app/app.component.ts +6 -6
  14. package/templates/angular/src/styles.css +4 -0
  15. package/templates/angular/tsconfig.json +1 -0
  16. package/templates/nextjs/.env.example +7 -2
  17. package/templates/nextjs/_gitignore +1 -0
  18. package/templates/nextjs/app/layout.tsx +2 -0
  19. package/templates/nextjs/app/page.tsx +13 -7
  20. package/templates/nextjs/eslint.config.mjs +4 -0
  21. package/templates/nextjs/next.config.ts +2 -21
  22. package/templates/nextjs/package.json +8 -3
  23. package/templates/nextjs/prettier.config.mjs +1 -0
  24. package/templates/nextjs/scripts/dev-env.mjs +41 -0
  25. package/templates/nextjs/scripts/dev.mjs +27 -0
  26. package/templates/nextjs/tsconfig.json +3 -18
  27. package/templates/preact/.env.example +3 -0
  28. package/templates/preact/eslint.config.mjs +3 -0
  29. package/templates/preact/package.json +8 -3
  30. package/templates/preact/prettier.config.mjs +1 -0
  31. package/templates/preact/src/App.tsx +6 -6
  32. package/templates/preact/tsconfig.json +4 -10
  33. package/templates/preact/vite.config.ts +9 -1
  34. package/templates/solid/.env.example +3 -0
  35. package/templates/solid/eslint.config.mjs +3 -0
  36. package/templates/solid/package.json +8 -4
  37. package/templates/solid/prettier.config.mjs +1 -0
  38. package/templates/solid/src/App.tsx +7 -7
  39. package/templates/solid/tsconfig.json +4 -9
  40. package/templates/solid/vite.config.ts +10 -2
  41. package/templates/svelte/.env.example +3 -0
  42. package/templates/svelte/_gitignore +1 -0
  43. package/templates/svelte/eslint.config.mjs +3 -0
  44. package/templates/svelte/package.json +8 -3
  45. package/templates/svelte/prettier.config.mjs +1 -0
  46. package/templates/svelte/src/App.svelte +6 -6
  47. package/templates/svelte/tsconfig.json +4 -4
  48. package/templates/svelte/vite.config.ts +9 -1
  49. package/templates/vite/.env.example +3 -0
  50. package/templates/vite/CLAUDE.md +40 -8
  51. package/templates/vite/eslint.config.mjs +3 -0
  52. package/templates/vite/package.json +9 -3
  53. package/templates/vite/prettier.config.mjs +1 -0
  54. package/templates/vite/src/auth/onboarding.tsx +4 -4
  55. package/templates/vite/src/climate/climate-data.ts +13 -6
  56. package/templates/vite/src/climate/conditions-chart.tsx +2 -2
  57. package/templates/vite/src/climate/overview-view.tsx +1 -1
  58. package/templates/vite/src/climate/stations-view.tsx +3 -3
  59. package/templates/vite/src/climate/use-climate.ts +2 -2
  60. package/templates/vite/src/events/live-events-view.tsx +2 -2
  61. package/templates/vite/src/graph/explorer-view.tsx +4 -4
  62. package/templates/vite/src/graph/requests.ts +2 -1
  63. package/templates/vite/src/locales/de.json +4 -4
  64. package/templates/vite/src/locales/en.json +44 -44
  65. package/templates/vite/src/profile/profile-view.tsx +5 -5
  66. package/templates/vite/src/shell/about-dialog.tsx +3 -3
  67. package/templates/vite/src/shell/app-shell.tsx +16 -12
  68. package/templates/vite/src/shell/header.tsx +24 -24
  69. package/templates/vite/tsconfig.json +4 -10
  70. package/templates/vite/vite.config.ts +10 -2
  71. package/templates/vue/.env.example +3 -0
  72. package/templates/vue/_gitignore +1 -0
  73. package/templates/vue/eslint.config.mjs +3 -0
  74. package/templates/vue/package.json +8 -3
  75. package/templates/vue/prettier.config.mjs +1 -0
  76. package/templates/vue/src/App.vue +6 -6
  77. package/templates/vue/tsconfig.json +5 -9
  78. package/templates/vue/vite.config.ts +10 -2
  79. package/templates/angular/scripts/dev-auth-proxy.mjs +0 -91
package/README.md CHANGED
@@ -72,9 +72,7 @@ Generated apps can run standalone with the `DEV_SECRET` dev-auth proxy.
72
72
  2. Fill in `VITE_APP_NAME`, `DEV_SECRET`, and `API_BASE_URL`
73
73
  3. Run `npm run dev`
74
74
 
75
- When running outside the host iframe, the Vite plugin proxies `/rest/*` to `API_BASE_URL` with the `X-Dev-Secret` header and `installDevBridge()` handles SDK messages.
76
-
77
- For public testing, run `npm run dev:live` and use the emitted tunnel URL as the app's live URL in the host App Store configuration.
75
+ When running outside the host iframe, the SDK proxies `/rest/*` to `API_BASE_URL` with the `X-Dev-Secret` header and `installDevBridge()` handles SDK messages. Vite templates do this through the `bardiocDevAuth` plugin; the angular and nextjs templates run `scripts/dev.mjs`, which starts it alongside their dev server.
78
76
 
79
77
  ## Options
80
78
 
package/bin/create.mjs CHANGED
@@ -1,14 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console */
2
3
 
3
- import { readFileSync } from 'node:fs';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { getDefaultTemplate, listTemplates, parseArgs, scaffoldApp } from '../src/scaffold.js';
7
7
 
8
8
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
9
9
  const TEMPLATES_DIR = join(__dirname, '..', 'templates');
10
- const packageJsonPath = join(__dirname, '..', 'package.json');
11
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
12
10
 
13
11
  const args = process.argv.slice(2);
14
12
 
@@ -66,7 +64,7 @@ if (!result.success) {
66
64
 
67
65
  console.log(` Done! Next steps:\n`);
68
66
  console.log(` cd ${name}`);
67
+ console.log(` cp .env.example .env`);
69
68
  console.log(` npm install`);
70
69
  console.log(` npm run dev\n`);
71
70
  console.log(` Open http://localhost:${port} to see your app.`);
72
- console.log(` Use "pnpm dev:live" for a public tunnel/live URL.\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bardioc/create-bardioc-app",
3
- "version": "0.5.5",
3
+ "version": "0.5.8",
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,
@@ -14,11 +14,25 @@ npm install
14
14
 
15
15
  ```bash
16
16
  npm run dev # http://localhost:{{PORT}}
17
- npm run dev:live # public tunnel (BARDIOC_TUNNEL=1)
18
17
  ```
19
18
 
20
- `npm run dev` runs plain Vite as a local preview shell — live SDK data comes only when the app is
21
- opened inside the host. See `.env.example` for the required variables.
19
+ `npm run dev` starts the dev server as a local preview shell. Live SDK data comes when the app is
20
+ opened inside the host, or standalone once `DEV_SECRET` and `API_BASE_URL` are set — the SDK then
21
+ proxies `/rest/*` to the BFF (via the `bardiocDevAuth` Vite plugin, or `scripts/dev.mjs`
22
+ in the angular and nextjs templates, which `npm run dev` starts alongside the dev server). See
23
+ `.env.example` for the required variables.
24
+
25
+ ## Lint & format
26
+
27
+ ```bash
28
+ npm run check-types # TypeScript
29
+ npm run lint # ESLint
30
+ npm run format # Prettier (format:check to verify only)
31
+ ```
32
+
33
+ ESLint, Prettier and the `tsconfig.json` base all come from the shared `@bardioc/configs` presets
34
+ (`eslint.config.mjs`, `prettier.config.mjs`), so an app lints exactly like the SDK does. The angular
35
+ template is the exception — Angular 19 pins TypeScript 5.8, which the presets do not support yet.
22
36
 
23
37
  ## Develop against `@bardioc/*` source (optional)
24
38
 
@@ -14,4 +14,4 @@ manifest.version = version;
14
14
  manifest.description = base ? `${base} (v${version})` : `v${version}`;
15
15
 
16
16
  writeFileSync(url, `${JSON.stringify(manifest, null, 2)}\n`);
17
- console.log(`[stamp-manifest] ${version} → ${outDir}/app-manifest.json`);
17
+ process.stdout.write(`[stamp-manifest] ${version} → ${outDir}/app-manifest.json\n`);
@@ -29,7 +29,7 @@ if (argv[0] === '--remove') {
29
29
  const link = `${LINK_DIR}/${name}`;
30
30
  if (!exists(link)) fail(`No link at ${link}`);
31
31
  rmSync(link);
32
- console.log(`✓ unlinked ${name}`);
32
+ process.stdout.write(`✓ unlinked ${name}\n`);
33
33
  syncCatalogs();
34
34
  process.exit(0);
35
35
  }
@@ -53,11 +53,11 @@ mkdirSync(LINK_DIR, { recursive: true });
53
53
  const link = `${LINK_DIR}/${name}`;
54
54
  if (exists(link)) rmSync(link);
55
55
  symlinkSync(abs, link, 'junction');
56
- console.log(`✓ linked ${name} → ${abs}`);
56
+ process.stdout.write(`✓ linked ${name} → ${abs}\n`);
57
57
 
58
58
  syncCatalogs();
59
59
 
60
- console.log('Next: pnpm install');
60
+ process.stdout.write('Next: pnpm install\n');
61
61
 
62
62
  // ── helpers ──────────────────────────────────────────────────────────
63
63
 
@@ -140,9 +140,11 @@ function replaceMarkerBlock(workspace, beginIdx, endIdx, marker) {
140
140
 
141
141
  function logSyncOutcome(sources) {
142
142
  if (sources.length === 1) {
143
- console.log(`✓ synced catalogs from .linked/${sources[0].name} into ${WORKSPACE_FILE}`);
143
+ process.stdout.write(
144
+ `✓ synced catalogs from .linked/${sources[0].name} into ${WORKSPACE_FILE}\n`
145
+ );
144
146
  } else {
145
- console.log(`✓ removed managed catalogs block from ${WORKSPACE_FILE}`);
147
+ process.stdout.write(`✓ removed managed catalogs block from ${WORKSPACE_FILE}\n`);
146
148
  }
147
149
  }
148
150
 
@@ -1,7 +1,12 @@
1
+ # Hosted app slug used by local runtime/dev-auth wiring.
1
2
  VITE_APP_NAME={{APP_NAME}}
2
3
 
3
- # Dev auth proxy secret for local standalone development.
4
+ # Paste the dev secret from the BardiocOS AppStore after registering your app.
5
+ # Used by the dev-auth proxy (scripts/dev.mjs) to sign requests to the BFF.
4
6
  DEV_SECRET=
5
7
 
6
- # Base URL of the Bardioc API the dev auth proxy should forward to.
8
+ # Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
7
9
  API_BASE_URL=
10
+
11
+ # By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
12
+ #LOCALHOST_PORT={{PORT}}
@@ -5,3 +5,4 @@ dist
5
5
  node_modules
6
6
  *.zip
7
7
  .linked/
8
+ .DS_Store
@@ -6,9 +6,7 @@
6
6
  "sync:runtime-env": "node ./scripts/sync-runtime-env.mjs",
7
7
  "ng": "ng",
8
8
  "predev": "npm run sync:runtime-env",
9
- "dev": "node ./scripts/dev-auth-proxy.mjs --app-port {{PORT}} & PROXY_PID=$!; ng serve --port {{PORT}}; EXIT_CODE=$?; kill $PROXY_PID; exit $EXIT_CODE",
10
- "predev:live": "npm run sync:runtime-env",
11
- "dev:live": "node ./scripts/dev-auth-proxy.mjs --app-port {{PORT}} & PROXY_PID=$!; BARDIOC_TUNNEL=1 ng serve --port {{PORT}}; EXIT_CODE=$?; kill $PROXY_PID; exit $EXIT_CODE",
9
+ "dev": "node ./scripts/dev.mjs",
12
10
  "prebuild": "npm run sync:runtime-env",
13
11
  "build": "ng build && node scripts/stamp-manifest.mjs dist/{{APP_NAME}}/browser",
14
12
  "bundle": "ng build && node scripts/stamp-manifest.mjs dist/{{APP_NAME}}/browser && cd dist/{{APP_NAME}}/browser && zip -r ../../../{{APP_NAME}}.zip .",
@@ -30,7 +28,7 @@
30
28
  "@angular/platform-browser": "^19.2.12",
31
29
  "@angular/platform-browser-dynamic": "^19.2.12",
32
30
  "@angular/router": "^19.2.12",
33
- "@bardioc/app-sdk": "latest",
31
+ "@bardioc/app-sdk": "^0.5.0",
34
32
  "rxjs": "~7.8.2",
35
33
  "tslib": "^2.8.1",
36
34
  "zone.js": "~0.15.1"
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ // The port a scaffolded app defaults to. LOCALHOST_PORT overrides it, from .env or the shell.
5
+ const DEFAULT_PORT = {{PORT}};
6
+
7
+ // The dev-auth proxy answers on the app port plus this offset — the same origin the app points
8
+ // installDevBridge at.
9
+ export const PROXY_PORT_OFFSET = 100;
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.
13
+ export function readEnvValue(projectDir, key) {
14
+ let value;
15
+
16
+ for (const fileName of ['.env', '.env.local']) {
17
+ let source;
18
+ try {
19
+ source = readFileSync(join(projectDir, fileName), 'utf8');
20
+ } catch {
21
+ continue;
22
+ }
23
+
24
+ for (const rawLine of source.split(/\r?\n/)) {
25
+ const line = rawLine.trim();
26
+ if (!line.startsWith(`${key}=`)) {
27
+ continue;
28
+ }
29
+ value = line
30
+ .slice(key.length + 1)
31
+ .trim()
32
+ .replace(/^['"]|['"]$/g, '');
33
+ }
34
+ }
35
+
36
+ return process.env[key] ?? value;
37
+ }
38
+
39
+ export function resolveAppPort(projectDir) {
40
+ return Number(readEnvValue(projectDir, 'LOCALHOST_PORT')) || DEFAULT_PORT;
41
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
4
+ import { spawn } from 'node:child_process';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { PROXY_PORT_OFFSET, resolveAppPort } from './dev-env.mjs';
8
+
9
+ const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
+ const port = resolveAppPort(projectDir);
11
+
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 });
16
+
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 });
19
+
20
+ for (const signal of ['SIGINT', 'SIGTERM']) {
21
+ process.on(signal, () => server.kill(signal));
22
+ }
23
+
24
+ server.on('exit', (code, signal) => {
25
+ proxy?.close();
26
+ process.exit(signal ? 1 : (code ?? 0));
27
+ });
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { readEnvValue } from './dev-env.mjs';
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  const projectRoot = resolve(__dirname, '..');
9
- const env = loadEnv(projectRoot);
10
10
 
11
11
  const runtimeEnv = {
12
- VITE_APP_NAME: env.VITE_APP_NAME,
12
+ VITE_APP_NAME: readEnvValue(projectRoot, 'VITE_APP_NAME'),
13
13
  };
14
14
 
15
15
  const outputPath = join(projectRoot, 'public', 'runtime-env.js');
@@ -20,48 +20,3 @@ writeFileSync(
20
20
  'utf8'
21
21
  );
22
22
 
23
- function loadEnv(projectDir) {
24
- const result = {};
25
-
26
- for (const fileName of ['.env', '.env.local']) {
27
- const filePath = join(projectDir, fileName);
28
-
29
- try {
30
- Object.assign(result, parseEnv(readFileSync(filePath, 'utf8')));
31
- } catch {
32
- // Ignore missing env files.
33
- }
34
- }
35
-
36
- return result;
37
- }
38
-
39
- function parseEnv(source) {
40
- const result = {};
41
-
42
- for (const rawLine of source.split(/\r?\n/)) {
43
- const line = rawLine.trim();
44
- if (!line || line.startsWith('#')) {
45
- continue;
46
- }
47
-
48
- const equalsIndex = line.indexOf('=');
49
- if (equalsIndex <= 0) {
50
- continue;
51
- }
52
-
53
- const key = line.slice(0, equalsIndex).trim();
54
- let value = line.slice(equalsIndex + 1).trim();
55
-
56
- if (
57
- (value.startsWith('"') && value.endsWith('"')) ||
58
- (value.startsWith("'") && value.endsWith("'"))
59
- ) {
60
- value = value.slice(1, -1);
61
- }
62
-
63
- result[key] = value;
64
- }
65
-
66
- return result;
67
- }
@@ -15,7 +15,7 @@ type ResultState =
15
15
  standalone: true,
16
16
  imports: [JsonPipe],
17
17
  template: `
18
- <div class="bg-background text-foreground min-h-screen">
18
+ <div class="bg-background text-foreground @container/app min-h-screen">
19
19
  @if (showSdkDemo) {
20
20
  <div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-3 p-4">
21
21
  <div class="flex flex-col gap-1">
@@ -64,7 +64,7 @@ type ResultState =
64
64
  <div class="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
65
65
  {{ resultRequest() }}
66
66
  </div>
67
- <div class="text-destructive whitespace-pre-wrap break-all">{{ resultMessage() }}</div>
67
+ <div class="text-destructive break-all whitespace-pre-wrap">{{ resultMessage() }}</div>
68
68
  </div>
69
69
  }
70
70
  @case ('success') {
@@ -82,7 +82,7 @@ type ResultState =
82
82
  <div class="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
83
83
  <h1 class="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
84
84
  <p class="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
85
- Open this app inside Bardioc OS to use transport requests and send host notifications.
85
+ Open this app inside Bardioc to use transport requests and send host notifications.
86
86
  </p>
87
87
  <div class="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
88
88
  Outside the host iframe this page is only a local preview shell.
@@ -107,12 +107,12 @@ export class AppComponent {
107
107
  this.result.set({ status: 'loading', request });
108
108
 
109
109
  const startTime = Date.now();
110
- this.bridge.notify('Getting user profile', 'info');
110
+ this.bridge.notify('Getting user profile…', 'info');
111
111
 
112
112
  try {
113
113
  const data = await this.bridge.transport.os.profile.get();
114
114
  const duration = Date.now() - startTime;
115
- this.bridge.notify(`Profile loaded (${duration}ms)`, 'success`);
115
+ this.bridge.notify(`Profile loaded (${duration}ms).`, 'success');
116
116
  this.result.set({ status: 'success', request, data });
117
117
  } catch (err) {
118
118
  const message = err instanceof Error ? err.message : String(err);
@@ -125,7 +125,7 @@ export class AppComponent {
125
125
 
126
126
  handleNotify(): void {
127
127
  if (!this.bridge) return;
128
- this.bridge.notify('Hello from {{DISPLAY_NAME}}', 'info');
128
+ this.bridge.notify('Hello from {{DISPLAY_NAME}}.', 'info');
129
129
  this.result.set({
130
130
  status: 'success',
131
131
  request: 'SDK_NOTIFY',
@@ -85,6 +85,10 @@ code {
85
85
  margin: 0;
86
86
  }
87
87
 
88
+ .\@container\/app {
89
+ container: app / inline-size;
90
+ }
91
+
88
92
  .flex {
89
93
  display: flex;
90
94
  }
@@ -7,6 +7,7 @@
7
7
  "noPropertyAccessFromIndexSignature": true,
8
8
  "noImplicitReturns": true,
9
9
  "noFallthroughCasesInSwitch": true,
10
+ "noUncheckedIndexedAccess": true,
10
11
  "skipLibCheck": true,
11
12
  "esModuleInterop": true,
12
13
  "sourceMap": true,
@@ -1,7 +1,12 @@
1
+ # Hosted app slug used by local runtime/dev-auth wiring.
1
2
  NEXT_PUBLIC_APP_NAME={{APP_NAME}}
2
3
 
3
- # Shared secret used for dev auth when proxying API requests through /rest.
4
+ # Paste the dev secret from the BardiocOS AppStore after registering your app.
5
+ # Used by the dev-auth proxy (scripts/dev.mjs) to sign requests to the BFF.
4
6
  DEV_SECRET=
5
7
 
6
- # Base URL of the Bardioc API backend to proxy /rest requests to.
8
+ # Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
7
9
  API_BASE_URL=
10
+
11
+ # By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
12
+ #LOCALHOST_PORT={{PORT}}
@@ -5,3 +5,4 @@ out
5
5
  node_modules
6
6
  *.zip
7
7
  .linked/
8
+ .DS_Store
@@ -9,6 +9,8 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
9
9
  return (
10
10
  <html lang="en" className="dark" style={{ height: '100%', margin: 0 }}>
11
11
  <head>
12
+ {/* Platform font contract — a fixed absolute URL the host serves, not a bundled asset. */}
13
+ {/* eslint-disable-next-line @next/next/no-css-tags */}
12
14
  <link rel="stylesheet" href="/fonts/bardioc-fonts.css" />
13
15
  </head>
14
16
  <body style={{ height: '100%', margin: 0 }}>{children}</body>
@@ -12,6 +12,9 @@ type ResultState =
12
12
 
13
13
  const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME?.trim();
14
14
 
15
+ // The port the app falls back to when it is not being served over HTTP (LOCALHOST_PORT moves it).
16
+ const DEFAULT_PORT = Number('{{PORT}}');
17
+
15
18
  function SdkDemo() {
16
19
  const bridge = useSdk();
17
20
  const notify = useNotify();
@@ -26,13 +29,13 @@ function SdkDemo() {
26
29
  setResult({ status: 'loading', request });
27
30
 
28
31
  const startTime = Date.now();
29
- notify('Getting user profile', 'info');
32
+ notify('Getting user profile…', 'info');
30
33
 
31
34
  try {
32
35
  const data = await bridge.transport.os.profile.get();
33
36
 
34
37
  const duration = Date.now() - startTime;
35
- notify(`Profile loaded (${duration}ms)`, 'success');
38
+ notify(`Profile loaded (${duration}ms).`, 'success');
36
39
  setResult({ status: 'success', request, data });
37
40
  } catch (err) {
38
41
  const message = err instanceof Error ? err.message : String(err);
@@ -44,7 +47,7 @@ function SdkDemo() {
44
47
  }
45
48
 
46
49
  function handleNotify() {
47
- notify('Hello from {{DISPLAY_NAME}}', 'info');
50
+ notify('Hello from {{DISPLAY_NAME}}.', 'info');
48
51
  setResult({
49
52
  status: 'success',
50
53
  request: 'SDK_NOTIFY',
@@ -103,7 +106,7 @@ function SdkDemo() {
103
106
  <div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
104
107
  {result.request}
105
108
  </div>
106
- <div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
109
+ <div className="text-destructive break-all whitespace-pre-wrap">{result.message}</div>
107
110
  </div>
108
111
  )}
109
112
  {result.status === 'success' && (
@@ -126,7 +129,7 @@ function PreviewShell() {
126
129
  <div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
127
130
  <h1 className="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
128
131
  <p className="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
129
- Open this app inside Bardioc OS to use transport requests and send host notifications.
132
+ Open this app inside Bardioc to use transport requests and send host notifications.
130
133
  </p>
131
134
  <div className="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
132
135
  Outside the host iframe this page is only a local preview shell.
@@ -144,12 +147,15 @@ export default function Page() {
144
147
 
145
148
  useEffect(() => {
146
149
  if (isDevStandalone()) {
147
- installDevBridge();
150
+ // scripts/dev.mjs runs the dev-auth proxy on the app port + 100, so read the port the page
151
+ // is actually served on rather than the one baked in at scaffold time.
152
+ const appPort = Number(window.location.port) || DEFAULT_PORT;
153
+ installDevBridge({ apiBaseUrl: `http://127.0.0.1:${appPort + 100}/rest` });
148
154
  }
149
155
  }, []);
150
156
 
151
157
  return (
152
- <div className="bg-background text-foreground min-h-screen">
158
+ <div className="bg-background text-foreground @container/app min-h-screen">
153
159
  {useSdkProvider ? (
154
160
  <AppSdkProvider appId={APP_NAME}>
155
161
  <SdkDemo />
@@ -0,0 +1,4 @@
1
+ import { config } from '@bardioc/configs/eslint/next';
2
+
3
+ // Static export output and Next's generated env types are not sources.
4
+ export default [...config, { ignores: ['out/**', 'next-env.d.ts'] }];
@@ -1,32 +1,13 @@
1
1
  import type { NextConfig } from 'next';
2
2
 
3
- const apiBaseUrl = process.env.API_BASE_URL?.replace(/\/$/, '');
4
- const devSecret = process.env.DEV_SECRET;
5
-
3
+ // Standalone dev auth lives in scripts/dev.mjs (the SDK dev-auth proxy), not here:
4
+ // the BFF needs Origin dropped and X-Requested-With stamped, which rewrites cannot do.
6
5
  const nextConfig: NextConfig = {
7
6
  output: 'export',
8
7
  images: {
9
8
  unoptimized: true,
10
9
  },
11
10
  trailingSlash: true,
12
- async headers() {
13
- if (!devSecret) return [];
14
- return [
15
- {
16
- source: '/rest/:path*',
17
- headers: [{ key: 'X-Dev-Secret', value: devSecret }],
18
- },
19
- ];
20
- },
21
- async rewrites() {
22
- if (!apiBaseUrl) return [];
23
- return [
24
- {
25
- source: '/rest/:path*',
26
- destination: `${apiBaseUrl}/:path*`,
27
- },
28
- ];
29
- },
30
11
  };
31
12
 
32
13
  export default nextConfig;
@@ -3,29 +3,34 @@
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "next dev --port {{PORT}}",
7
- "dev:live": "BARDIOC_TUNNEL=1 next dev --port {{PORT}}",
6
+ "dev": "node ./scripts/dev.mjs",
8
7
  "build": "next build && node scripts/stamp-manifest.mjs out",
9
8
  "bundle": "next build && node scripts/stamp-manifest.mjs out && cd out && zip -r ../{{APP_NAME}}.zip .",
10
9
  "start": "next start",
11
10
  "check-types": "tsc --noEmit",
11
+ "lint": "eslint .",
12
+ "format": "prettier --write .",
13
+ "format:check": "prettier --check .",
12
14
  "changeset": "changeset",
13
15
  "release:status": "changeset status --verbose",
14
16
  "release:version": "changeset version && npm install",
15
17
  "release:publish": "changeset publish"
16
18
  },
17
19
  "dependencies": {
18
- "@bardioc/app-sdk": "latest",
20
+ "@bardioc/app-sdk": "^0.5.0",
19
21
  "next": "^15.3.2",
20
22
  "react": "^19.2.5",
21
23
  "react-dom": "^19.2.5"
22
24
  },
23
25
  "devDependencies": {
26
+ "@bardioc/configs": "0.2.1",
24
27
  "@changesets/cli": "^2.31.0",
25
28
  "@tailwindcss/postcss": "^4.2.4",
26
29
  "@types/node": "^22.10.0",
27
30
  "@types/react": "^19.2.14",
28
31
  "@types/react-dom": "^19.2.3",
32
+ "eslint": "^10.0.0",
33
+ "prettier": "^3.6.2",
29
34
  "tailwindcss": "^4.2.4",
30
35
  "typescript": "^6.0.3"
31
36
  }
@@ -0,0 +1 @@
1
+ export { default } from '@bardioc/configs/prettier';
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ // The port a scaffolded app defaults to. LOCALHOST_PORT overrides it, from .env or the shell.
5
+ const DEFAULT_PORT = {{PORT}};
6
+
7
+ // The dev-auth proxy answers on the app port plus this offset — the same origin the app points
8
+ // installDevBridge at.
9
+ export const PROXY_PORT_OFFSET = 100;
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.
13
+ export function readEnvValue(projectDir, key) {
14
+ let value;
15
+
16
+ for (const fileName of ['.env', '.env.local']) {
17
+ let source;
18
+ try {
19
+ source = readFileSync(join(projectDir, fileName), 'utf8');
20
+ } catch {
21
+ continue;
22
+ }
23
+
24
+ for (const rawLine of source.split(/\r?\n/)) {
25
+ const line = rawLine.trim();
26
+ if (!line.startsWith(`${key}=`)) {
27
+ continue;
28
+ }
29
+ value = line
30
+ .slice(key.length + 1)
31
+ .trim()
32
+ .replace(/^['"]|['"]$/g, '');
33
+ }
34
+ }
35
+
36
+ return process.env[key] ?? value;
37
+ }
38
+
39
+ export function resolveAppPort(projectDir) {
40
+ return Number(readEnvValue(projectDir, 'LOCALHOST_PORT')) || DEFAULT_PORT;
41
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
4
+ import { spawn } from 'node:child_process';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { PROXY_PORT_OFFSET, resolveAppPort } from './dev-env.mjs';
8
+
9
+ const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
+ const port = resolveAppPort(projectDir);
11
+
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 });
16
+
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 });
19
+
20
+ for (const signal of ['SIGINT', 'SIGTERM']) {
21
+ process.on(signal, () => server.kill(signal));
22
+ }
23
+
24
+ server.on('exit', (code, signal) => {
25
+ proxy?.close();
26
+ process.exit(signal ? 1 : (code ?? 0));
27
+ });
@@ -1,23 +1,8 @@
1
1
  {
2
+ "extends": "@bardioc/configs/typescript/nextjs.json",
2
3
  "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "allowJs": true,
6
- "skipLibCheck": true,
7
- "strict": true,
8
- "noEmit": true,
9
- "esModuleInterop": true,
10
- "module": "esnext",
11
- "moduleResolution": "bundler",
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "jsx": "preserve",
15
- "incremental": true,
16
- "plugins": [
17
- {
18
- "name": "next"
19
- }
20
- ],
4
+ "declaration": false,
5
+ "declarationMap": false,
21
6
  "paths": {
22
7
  "@/*": ["./*"]
23
8
  }