@bardioc/create-bardioc-app 0.5.4 → 0.5.6

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 (87) hide show
  1. package/README.md +3 -5
  2. package/bin/create.mjs +2 -4
  3. package/package.json +5 -5
  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 +4 -2
  8. package/templates/angular/_gitignore +1 -0
  9. package/templates/angular/package.json +2 -4
  10. package/templates/angular/public/runtime-env.js +1 -1
  11. package/templates/angular/scripts/dev-auth-proxy.mjs +10 -84
  12. package/templates/angular/scripts/sync-runtime-env.mjs +17 -36
  13. package/templates/angular/src/app/app.component.ts +2 -2
  14. package/templates/angular/src/app/bardioc-bridge.ts +1 -1
  15. package/templates/angular/src/index.html +2 -2
  16. package/templates/angular/src/runtime-env.ts +1 -1
  17. package/templates/angular/src/styles.css +84 -80
  18. package/templates/angular/src/vite-env.d.ts +3 -3
  19. package/templates/angular/tsconfig.json +1 -0
  20. package/templates/manifest.json +2 -10
  21. package/templates/nextjs/.env.example +4 -2
  22. package/templates/nextjs/_gitignore +1 -0
  23. package/templates/nextjs/app/globals.css +31 -28
  24. package/templates/nextjs/app/layout.tsx +2 -0
  25. package/templates/nextjs/app/page.tsx +8 -5
  26. package/templates/nextjs/eslint.config.mjs +4 -0
  27. package/templates/nextjs/global.d.ts +1 -1
  28. package/templates/nextjs/next.config.ts +7 -26
  29. package/templates/nextjs/package.json +8 -3
  30. package/templates/nextjs/prettier.config.mjs +1 -0
  31. package/templates/nextjs/scripts/dev-auth-proxy.mjs +17 -0
  32. package/templates/nextjs/tailwind.config.ts +1 -4
  33. package/templates/nextjs/tsconfig.json +3 -18
  34. package/templates/preact/eslint.config.mjs +3 -0
  35. package/templates/preact/index.html +2 -2
  36. package/templates/preact/package.json +8 -3
  37. package/templates/preact/prettier.config.mjs +1 -0
  38. package/templates/preact/src/App.tsx +6 -5
  39. package/templates/preact/src/index.css +31 -28
  40. package/templates/preact/src/vite-env.d.ts +2 -2
  41. package/templates/preact/tsconfig.json +4 -10
  42. package/templates/preact/vite.config.ts +6 -1
  43. package/templates/solid/eslint.config.mjs +3 -0
  44. package/templates/solid/index.html +2 -2
  45. package/templates/solid/package.json +8 -4
  46. package/templates/solid/prettier.config.mjs +1 -0
  47. package/templates/solid/src/App.tsx +7 -6
  48. package/templates/solid/src/bardioc-sdk.tsx +18 -18
  49. package/templates/solid/src/index.css +31 -28
  50. package/templates/solid/src/vite-env.d.ts +2 -2
  51. package/templates/solid/tsconfig.json +4 -9
  52. package/templates/solid/vite.config.ts +7 -2
  53. package/templates/svelte/_gitignore +1 -0
  54. package/templates/svelte/eslint.config.mjs +3 -0
  55. package/templates/svelte/index.html +2 -2
  56. package/templates/svelte/package.json +8 -3
  57. package/templates/svelte/prettier.config.mjs +1 -0
  58. package/templates/svelte/src/App.svelte +1 -1
  59. package/templates/svelte/src/index.css +31 -28
  60. package/templates/svelte/src/main.ts +1 -1
  61. package/templates/svelte/src/vite-env.d.ts +2 -2
  62. package/templates/svelte/tsconfig.json +4 -4
  63. package/templates/svelte/vite.config.ts +6 -1
  64. package/templates/vite/CLAUDE.md +11 -8
  65. package/templates/vite/eslint.config.mjs +3 -0
  66. package/templates/vite/index.html +2 -2
  67. package/templates/vite/package.json +9 -5
  68. package/templates/vite/prettier.config.mjs +1 -0
  69. package/templates/vite/src/climate/climate-data.ts +13 -6
  70. package/templates/vite/src/climate/conditions-chart.tsx +6 -8
  71. package/templates/vite/src/events/live-events-view.tsx +1 -3
  72. package/templates/vite/src/graph/requests.ts +2 -1
  73. package/templates/vite/src/i18n.tsx +7 -9
  74. package/templates/vite/src/shell/header.tsx +1 -2
  75. package/templates/vite/tsconfig.json +4 -10
  76. package/templates/vite/vite.config.ts +7 -2
  77. package/templates/vue/_gitignore +1 -0
  78. package/templates/vue/eslint.config.mjs +3 -0
  79. package/templates/vue/index.html +2 -2
  80. package/templates/vue/package.json +8 -3
  81. package/templates/vue/prettier.config.mjs +1 -0
  82. package/templates/vue/src/App.vue +26 -10
  83. package/templates/vue/src/bardioc-sdk.ts +11 -11
  84. package/templates/vue/src/index.css +31 -28
  85. package/templates/vue/src/vite-env.d.ts +2 -2
  86. package/templates/vue/tsconfig.json +5 -9
  87. package/templates/vue/vite.config.ts +7 -2
package/README.md CHANGED
@@ -8,10 +8,10 @@ Scaffold a Vite app wired for `@bardioc/app-sdk`.
8
8
 
9
9
  ## Install
10
10
 
11
- Configure the `@bardioc` scoped registry and install the CLI globally:
11
+ Install the CLI globally from the public npm registry:
12
12
 
13
13
  ```bash
14
- npm config set @bardioc:registry http://nexus.almato.com/repository/npm-group/ --location=user && npm install -g @bardioc/create-bardioc-app
14
+ npm install -g @bardioc/create-bardioc-app
15
15
  ```
16
16
 
17
17
  If your default npm registry is not `https://registry.npmjs.org/`, set it in your user-level npm config before installing app dependencies.
@@ -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-auth-proxy.mjs`, which `npm run dev` starts 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.4",
3
+ "version": "0.5.6",
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,
@@ -30,10 +30,10 @@
30
30
  "react"
31
31
  ],
32
32
  "devDependencies": {
33
- "@vitest/coverage-v8": "4.1.5",
34
- "@types/node": "^25.6.0",
35
- "typescript": "^6.0.3",
36
- "vitest": "4.1.5"
33
+ "@vitest/coverage-v8": "^4.1.8",
34
+ "@types/node": "^25.9.1",
35
+ "typescript": "6.0.3",
36
+ "vitest": "^4.1.8"
37
37
  },
38
38
  "scripts": {
39
39
  "check-types": "tsc --noEmit",
@@ -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-auth-proxy.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,9 @@
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-auth-proxy.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=
@@ -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-auth-proxy.mjs & PROXY_PID=$!; ng serve --port {{PORT}}; EXIT_CODE=$?; kill $PROXY_PID 2>/dev/null; exit $EXIT_CODE",
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.4.9",
34
32
  "rxjs": "~7.8.2",
35
33
  "tslib": "^2.8.1",
36
34
  "zone.js": "~0.15.1"
@@ -1 +1 @@
1
- window.__BARDIOC_RUNTIME_ENV__ = {};
1
+ window.__BARDIOC_RUNTIME_ENV__ = {};
@@ -1,91 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync } from 'node:fs';
4
- import { createServer } from 'node:http';
5
- import { dirname, join, resolve } from 'node:path';
3
+ import { startDevAuthProxy } from '@bardioc/app-sdk/dev-proxy';
4
+ import { dirname, resolve } from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
7
- import { createDevAuthProxyHandler } from '@bardioc/app-sdk/dev-proxy';
8
6
 
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- const projectRoot = resolve(__dirname, '..');
11
- const appPort = readAppPort();
12
- const proxyPort = appPort + 100;
13
- const env = loadEnv(projectRoot);
7
+ const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
14
8
 
15
- if (!env.DEV_SECRET || !env.API_BASE_URL) {
16
- process.stderr.write('[bardioc-dev-auth-proxy] missing DEV_SECRET or API_BASE_URL, proxy disabled\n');
17
- await new Promise(() => {});
18
- }
19
-
20
- const handler = createDevAuthProxyHandler({
21
- apiBaseUrl: env.API_BASE_URL,
22
- devSecret: env.DEV_SECRET,
23
- allowOrigin: '*',
24
- });
25
-
26
- const server = createServer((req, res) => {
27
- void handler(req, res);
28
- });
29
-
30
- server.listen(proxyPort, '127.0.0.1', () => {
31
- process.stdout.write(`[bardioc-dev-auth-proxy] listening on http://127.0.0.1:${proxyPort}/rest\n`);
32
- });
33
-
34
- for (const signal of ['SIGINT', 'SIGTERM']) {
35
- process.on(signal, () => {
36
- server.close(() => process.exit(0));
37
- });
38
- }
39
-
40
- function readAppPort() {
41
- const flagIndex = process.argv.indexOf('--app-port');
42
- const raw = flagIndex >= 0 ? process.argv[flagIndex + 1] : undefined;
43
- const parsed = Number.parseInt(raw ?? '{{PORT}}', 10);
44
- return Number.isFinite(parsed) ? parsed : {{PORT}};
45
- }
46
-
47
- function loadEnv(projectDir) {
48
- const result = {};
49
-
50
- for (const fileName of ['.env', '.env.local']) {
51
- const filePath = join(projectDir, fileName);
52
-
53
- try {
54
- Object.assign(result, parseEnv(readFileSync(filePath, 'utf8')));
55
- } catch {
56
- // Ignore missing env files.
57
- }
58
- }
59
-
60
- return result;
61
- }
62
-
63
- function parseEnv(source) {
64
- const result = {};
65
-
66
- for (const rawLine of source.split(/\r?\n/)) {
67
- const line = rawLine.trim();
68
- if (!line || line.startsWith('#')) {
69
- continue;
70
- }
71
-
72
- const equalsIndex = line.indexOf('=');
73
- if (equalsIndex <= 0) {
74
- continue;
75
- }
76
-
77
- const key = line.slice(0, equalsIndex).trim();
78
- let value = line.slice(equalsIndex + 1).trim();
79
-
80
- if (
81
- (value.startsWith('"') && value.endsWith('"')) ||
82
- (value.startsWith("'") && value.endsWith("'"))
83
- ) {
84
- value = value.slice(1, -1);
85
- }
86
-
87
- result[key] = value;
88
- }
9
+ // Angular's dev server cannot host the SDK Vite plugin, so the proxy runs beside it
10
+ // on the app port + 100 — the same origin src/main.ts points installDevBridge at.
11
+ const server = await startDevAuthProxy({ projectDir, port: {{PORT}} + 100 });
89
12
 
90
- return result;
13
+ // Without DEV_SECRET / API_BASE_URL there is nothing to serve — exit and let the dev server run
14
+ // on its own, matching how the SDK Vite plugin disables itself.
15
+ if (!server) {
16
+ process.exit(0);
91
17
  }
@@ -6,10 +6,9 @@ import { fileURLToPath } from 'node:url';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
  const projectRoot = resolve(__dirname, '..');
9
- const env = loadEnv(projectRoot);
10
9
 
11
10
  const runtimeEnv = {
12
- VITE_APP_NAME: env.VITE_APP_NAME,
11
+ VITE_APP_NAME: readEnvValue(projectRoot, 'VITE_APP_NAME'),
13
12
  };
14
13
 
15
14
  const outputPath = join(projectRoot, 'public', 'runtime-env.js');
@@ -20,48 +19,30 @@ writeFileSync(
20
19
  'utf8'
21
20
  );
22
21
 
23
- function loadEnv(projectDir) {
24
- const result = {};
22
+ // Angular has no import.meta.env, so the app slug is baked into a runtime script instead.
23
+ function readEnvValue(projectDir, key) {
24
+ let value;
25
25
 
26
26
  for (const fileName of ['.env', '.env.local']) {
27
- const filePath = join(projectDir, fileName);
28
-
27
+ let source;
29
28
  try {
30
- Object.assign(result, parseEnv(readFileSync(filePath, 'utf8')));
29
+ source = readFileSync(join(projectDir, fileName), 'utf8');
31
30
  } 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
31
  continue;
51
32
  }
52
33
 
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);
34
+ for (const rawLine of source.split(/\r?\n/)) {
35
+ const line = rawLine.trim();
36
+ if (!line.startsWith(`${key}=`)) {
37
+ continue;
38
+ }
39
+ value = line
40
+ .slice(key.length + 1)
41
+ .trim()
42
+ .replace(/^['"]|['"]$/g, '');
61
43
  }
62
-
63
- result[key] = value;
64
44
  }
65
45
 
66
- return result;
46
+ // A shell value always wins over the dotenv files, like Vite's env loading.
47
+ return process.env[key] ?? value;
67
48
  }
@@ -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') {
@@ -112,7 +112,7 @@ export class AppComponent {
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);
@@ -1,5 +1,5 @@
1
1
  import { createHostBridge, type HostBridge } from '@bardioc/app-sdk';
2
2
 
3
3
  export function createBardiocBridge(): HostBridge {
4
- return createHostBridge({});
4
+ return createHostBridge({});
5
5
  }
@@ -1,5 +1,5 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark" style="height: 100%; margin: 0">
1
+ <!doctype html>
2
+ <html lang="en" class="dark" style="height: 100%; margin: 0; color-scheme: dark">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <title>{{DISPLAY_NAME}}</title>
@@ -14,4 +14,4 @@ export function readRuntimeEnv(name: string): string | undefined {
14
14
 
15
15
  const trimmed = value.trim();
16
16
  return trimmed.length > 0 ? trimmed : undefined;
17
- }
17
+ }