@olenbetong/appframe-vite 6.8.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,12 +68,25 @@ This package reads `package.json.appframe` to know what to proxy and how to buil
68
68
  "routes": ["^/custom/.*"], // Optional: extra proxied routes
69
69
  },
70
70
  "build": {
71
- "externals": true, // Map React and ReactDOM to globals
71
+ "platform": true, // Import React and the af packages from the site's platform bundle
72
+ "externals": true, // Legacy: map React and ReactDOM to globals
72
73
  },
73
74
  },
74
75
  }
75
76
  ```
76
77
 
78
+ ### `build.platform`
79
+
80
+ Apps on the 2026 site templates (`ob.2026.application`) set `"platform": true`. The production
81
+ build then leaves `react`, `react/jsx-runtime`, `react/jsx-dev-runtime`, `react-dom`,
82
+ `react-dom/client`, `@olenbetong/appframe-core` and `@olenbetong/appframe-data` as bare imports,
83
+ which the template's import map resolves to `@olenbetong/synergi-platform` — one cached React
84
+ per page, shared with the site chrome. The `af.data` global redirect (`dataGlobal`) then only
85
+ applies to the dev server, which has no import map. The build also writes `dist/platform.json`
86
+ with the versions the app compiled against; `scripts/app-deploy.ts` refuses to deploy when the
87
+ target article renders no import map or its platform carries other versions (override with
88
+ `--allow-platform-mismatch`).
89
+
77
90
  ## What it does (at a glance)
78
91
 
79
92
  - Dev server
@@ -86,7 +99,7 @@ This package reads `package.json.appframe` to know what to proxy and how to buil
86
99
  - Writes filenames to Appframe paths, using the article ID, e.g.:
87
100
  - `file/article/script/<ARTICLE_ID>/main.[hash].min.js`
88
101
  - `file/article/style/<ARTICLE_ID>/style.[hash].min.css`
89
- - Optionally treats `react` and `react-dom` as externals and maps them to `React` and `ReactDOM` globals.
102
+ - With `build.platform`, leaves React and the af packages as bare imports for the template's import map and writes `dist/platform.json`; the legacy `build.externals` maps `react` and `react-dom` to `React` and `ReactDOM` globals instead.
90
103
  - Adds a Rollup visualizer report at `dist/stats.html`.
91
104
  - Resolve
92
105
  - Adds the alias `~/` to `/src/` so imports like `~/components/Button` resolve to `src/components/Button`.
@@ -240,27 +253,29 @@ procedures:
240
253
 
241
254
  **Supported fields per entry:**
242
255
 
243
- | Field | Type | Description |
244
- | ------------- | -------------------- | ------------------------------------------------------------------- |
245
- | `id` | `string` | Variable name used in generated code (e.g. `dsAccountGroups`) |
246
- | `resource` | `string` | Database object ID (e.g. `atbv_Accounting_SubsidiaryLedgerGroups`) |
247
- | `output` | `string` | Output file path relative to project root |
248
- | `global` | `boolean` | Use `af.data.generateApiDataObject` / `new af.ProcedureAPI` globals |
249
- | `types` | `boolean` | Emit TypeScript type definitions |
250
- | `permissions` | `string` | Permissions: I = insert, U = update, D = delete (e.g. `IUD`) |
251
- | `maxRecords` | `number` | Max records to fetch (default `50`; `-1` for all) |
252
- | `sortOrder` | `string \| string[]` | Sort order, e.g. `Created:Desc` or `[Created:Desc, Name]` |
253
- | `master` | `string` | Master data object name (or `name:importPath`) |
254
- | `linkFields` | `string \| string[]` | Fields linking child to master |
255
- | `expose` | `boolean \| string` | Expose on `af.article.dataObjects` / `af.article.procedures` |
256
- | `dynamic` | `boolean` | Enable dynamic loading |
257
- | `unique` | `string` | Unique table name for update/delete |
258
- | `overrides` | `string \| string[]` | Type overrides, e.g. `MyField:string[]` |
259
- | `distinct` | `boolean` | Fetch distinct rows |
260
- | `aggregates` | `string \| string[]` | Aggregate bindings, e.g. `Qty:SUM` |
261
- | `groupBy` | `string \| string[]` | Group-by fields |
262
- | `where` | `string` | Initial where clause |
263
- | `fields` | `string \| string[]` | Fields to include (all if omitted) |
256
+ | Field | Type | Description |
257
+ | ------------- | -------------------- | ------------------------------------------------------------------------------- |
258
+ | `id` | `string` | Variable name used in generated code (e.g. `dsAccountGroups`) |
259
+ | `resource` | `string` | Database object ID (e.g. `atbv_Accounting_SubsidiaryLedgerGroups`) |
260
+ | `output` | `string` | Output file path relative to project root |
261
+ | `global` | `boolean` | Use `af.data.generateApiDataObject` / `new af.ProcedureAPI` globals |
262
+ | `types` | `boolean` | Emit TypeScript type definitions |
263
+ | `permissions` | `string` | Permissions: I = insert, U = update, D = delete (e.g. `IUD`) |
264
+ | `maxRecords` | `number` | Max records to fetch (default `50`; `-1` for all) |
265
+ | `sortOrder` | `string \| string[]` | Sort order, e.g. `Created:Desc` or `[Created:Desc, Name]` |
266
+ | `master` | `string` | Master data object name (or `name:importPath`) |
267
+ | `linkFields` | `string \| string[]` | Fields linking child to master |
268
+ | `expose` | `boolean \| string` | Expose on `af.article.dataObjects` / `af.article.procedures` |
269
+ | `dynamic` | `boolean` | Enable dynamic loading |
270
+ | `unique` | `string` | Unique table name for update/delete |
271
+ | `overrides` | `string \| string[]` | Type overrides, e.g. `MyField:string[]` |
272
+ | `distinct` | `boolean` | Fetch distinct rows |
273
+ | `aggregates` | `string \| string[]` | Aggregate bindings, e.g. `Qty:SUM` |
274
+ | `groupBy` | `string \| string[]` | Group-by fields |
275
+ | `where` | `string` | Initial where clause |
276
+ | `fields` | `string \| string[]` | Fields to include (all if omitted) |
277
+ | `transaction` | `boolean` | Procedures only. `false` stops the framework wrapping the call in a transaction |
278
+ | `timeout` | `number` | Procedures only. Milliseconds before the call is abandoned (default `30000`) |
264
279
 
265
280
  A top-level `server` key can override the hostname (defaults to `appframe.proxy.hostname` from `package.json`).
266
281
 
@@ -1,4 +1,19 @@
1
1
  import type { Plugin } from "vite";
2
+ /**
3
+ * Names the framework puts on the `af.data` global. These are the exports shared by
4
+ * `bundle-entries/browser.esm.ts` and `bundle-entries/browser-compat.esm.ts` — which of
5
+ * the two a page loads depends on the user's UserMode, so only names present in *both*
6
+ * are safe to read off the global.
7
+ */
8
+ export declare const GLOBAL_EXPORTS: string[];
9
+ /**
10
+ * Helpers the `af.data` bundles do not expose, mapped to the module they live in. These
11
+ * are pure functions whose only dependencies on the rest of the package are types, so
12
+ * bundling them creates no second client, data object or event bus — the state that must
13
+ * stay single-instance still comes from the global. Move an entry up to GLOBAL_EXPORTS
14
+ * once the browser bundle entries export it and the new af.data has been deployed.
15
+ */
16
+ export declare const LOCAL_EXPORTS: Record<string, string[]>;
2
17
  /**
3
18
  * Redirects imports of `@olenbetong/appframe-data` to the `af.data` global the article
4
19
  * already loads.
@@ -7,7 +7,7 @@ const VIRTUAL_ID = "\0appframe-data-global";
7
7
  * the two a page loads depends on the user's UserMode, so only names present in *both*
8
8
  * are safe to read off the global.
9
9
  */
10
- const GLOBAL_EXPORTS = [
10
+ export const GLOBAL_EXPORTS = [
11
11
  "Client",
12
12
  "DataHandler",
13
13
  "DataObject",
@@ -32,7 +32,7 @@ const GLOBAL_EXPORTS = [
32
32
  * stay single-instance still comes from the global. Move an entry up to GLOBAL_EXPORTS
33
33
  * once the browser bundle entries export it and the new af.data has been deployed.
34
34
  */
35
- const LOCAL_EXPORTS = {
35
+ export const LOCAL_EXPORTS = {
36
36
  "formDataToObject.js": ["formDataToDataObjectRecord", "formDataToObject", "formDataToProcedureParameters"],
37
37
  "exportDataObject.js": ["exportOptionsFromDataObject", "getDataObjectReportRequest", "openDataObjectReport"],
38
38
  "DataHandler.js": ["isRequestError"],
package/lib/build.d.ts CHANGED
@@ -1,2 +1,35 @@
1
- import type { UserConfig } from "vite";
1
+ import { type Plugin, type UserConfig } from "vite";
2
+ /**
3
+ * The bare specifiers the 2026 site templates resolve through their import map to the
4
+ * `@olenbetong/synergi-platform` bundle: one React per page, and the `af` packages as
5
+ * shims over the globals the article loads. An app built with `appframe.build.platform`
6
+ * leaves these external and so must run on a template that renders the import map.
7
+ */
8
+ export declare const PLATFORM_EXTERNALS: string[];
9
+ /**
10
+ * The IIFE global each platform specifier is published under (`buildConfig.entries` in the
11
+ * platform package), for builds that cannot use the import map.
12
+ */
13
+ export declare const PLATFORM_GLOBALS: Record<string, string>;
14
+ export declare function usesPlatform(appframe: {
15
+ build?: {
16
+ platform?: boolean;
17
+ };
18
+ } | undefined): boolean;
19
+ /**
20
+ * The packages whose version the platform manifest records; an app must match the served
21
+ * platform on every one it has installed.
22
+ */
23
+ export declare const PLATFORM_UPSTREAM: string[];
24
+ export type PlatformManifest = {
25
+ /** The package versions the app resolved at build time. */
26
+ upstream: Record<string, string>;
27
+ };
28
+ /**
29
+ * Writes `dist/platform.json` after a platform build: the versions of the platform packages the
30
+ * app was compiled against. The deploy script compares it with the manifest of the platform the
31
+ * target site serves, since an app compiled against one React and run on another is a runtime
32
+ * error, not a build error.
33
+ */
34
+ export declare function platformManifest(): Plugin;
2
35
  export declare function addAppframeBuildConfig(config: UserConfig): Promise<UserConfig>;
package/lib/build.js CHANGED
@@ -1,7 +1,100 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { resolve } from "node:path";
3
4
  import { visualizer } from "rollup-plugin-visualizer";
5
+ import { esmExternalRequirePlugin } from "vite";
4
6
  import { importJson } from "./importJson.js";
7
+ /**
8
+ * The bare specifiers the 2026 site templates resolve through their import map to the
9
+ * `@olenbetong/synergi-platform` bundle: one React per page, and the `af` packages as
10
+ * shims over the globals the article loads. An app built with `appframe.build.platform`
11
+ * leaves these external and so must run on a template that renders the import map.
12
+ */
13
+ export const PLATFORM_EXTERNALS = [
14
+ "react",
15
+ "react/jsx-runtime",
16
+ "react/jsx-dev-runtime",
17
+ "react-dom",
18
+ "react-dom/client",
19
+ "react-router",
20
+ "@digdir/designsystemet-web",
21
+ "@digdir/designsystemet-react",
22
+ "@olenbetong/appframe-core",
23
+ "@olenbetong/appframe-data",
24
+ "@olenbetong/appframe-react",
25
+ "@olenbetong/appframe-ds",
26
+ "@olenbetong/appframe-ds/grid",
27
+ "@olenbetong/appframe-ds/mdi",
28
+ "@olenbetong/appframe-ds/progress",
29
+ "@olenbetong/appframe-ds/report-downloader",
30
+ ];
31
+ /**
32
+ * The IIFE global each platform specifier is published under (`buildConfig.entries` in the
33
+ * platform package), for builds that cannot use the import map.
34
+ */
35
+ export const PLATFORM_GLOBALS = {
36
+ react: "React",
37
+ "react/jsx-runtime": "ReactJSXRuntime",
38
+ "react/jsx-dev-runtime": "ReactJSXDevRuntime",
39
+ "react-dom": "ReactDOM",
40
+ "react-dom/client": "ReactDOMClient",
41
+ "react-router": "ReactRouter",
42
+ "@digdir/designsystemet-web": "Designsystemet.web",
43
+ "@digdir/designsystemet-react": "Designsystemet.react",
44
+ "@olenbetong/appframe-core": "SynergiPlatform.appframeCore",
45
+ "@olenbetong/appframe-data": "SynergiPlatform.appframeData",
46
+ "@olenbetong/appframe-react": "SynergiPlatform.appframeReact",
47
+ "@olenbetong/appframe-ds": "SynergiPlatform.appframeDs",
48
+ "@olenbetong/appframe-ds/grid": "SynergiPlatform.appframeDsGrid",
49
+ "@olenbetong/appframe-ds/mdi": "SynergiPlatform.appframeDsMdi",
50
+ "@olenbetong/appframe-ds/progress": "SynergiPlatform.appframeDsProgress",
51
+ "@olenbetong/appframe-ds/report-downloader": "SynergiPlatform.appframeDsReportDownloader",
52
+ };
53
+ export function usesPlatform(appframe) {
54
+ return appframe?.build?.platform === true;
55
+ }
56
+ /**
57
+ * The packages whose version the platform manifest records; an app must match the served
58
+ * platform on every one it has installed.
59
+ */
60
+ export const PLATFORM_UPSTREAM = [
61
+ "react",
62
+ "react-dom",
63
+ "react-router",
64
+ "@digdir/designsystemet-react",
65
+ "@digdir/designsystemet-web",
66
+ "@digdir/designsystemet-css",
67
+ "@olenbetong/appframe-core",
68
+ "@olenbetong/appframe-data",
69
+ "@olenbetong/appframe-react",
70
+ "@olenbetong/appframe-ds",
71
+ ];
72
+ /**
73
+ * Writes `dist/platform.json` after a platform build: the versions of the platform packages the
74
+ * app was compiled against. The deploy script compares it with the manifest of the platform the
75
+ * target site serves, since an app compiled against one React and run on another is a runtime
76
+ * error, not a build error.
77
+ */
78
+ export function platformManifest() {
79
+ return {
80
+ name: "appframe:platform-manifest",
81
+ apply: "build",
82
+ async writeBundle() {
83
+ let manifest = { upstream: {} };
84
+ for (let name of PLATFORM_UPSTREAM) {
85
+ let file = resolve(process.cwd(), "node_modules", name, "package.json");
86
+ try {
87
+ manifest.upstream[name] = JSON.parse(await readFile(file, "utf8")).version;
88
+ }
89
+ catch {
90
+ // Not every app depends on every platform package (an app without a grid has no
91
+ // appframe-ds, say); only what the app resolved is recorded and checked.
92
+ }
93
+ }
94
+ await writeFile(resolve(process.cwd(), "dist", "platform.json"), `${JSON.stringify(manifest, null, "\t")}\n`);
95
+ },
96
+ };
97
+ }
5
98
  export async function addAppframeBuildConfig(config) {
6
99
  let appPkg = await importJson("./package.json", true);
7
100
  let { appframe } = appPkg;
@@ -22,9 +115,21 @@ export async function addAppframeBuildConfig(config) {
22
115
  config.appType = "custom";
23
116
  config.build.rolldownOptions = {
24
117
  input: { main: entry },
25
- // Cast visualizer plugin to any to avoid Rollup type version clashes
26
- plugins: [visualizer({ filename: "./dist/stats.html", gzipSize: true })],
27
- external: appframe.build.externals !== false ? ["react", "react-dom", "react-dom/client"] : [],
118
+ plugins: [
119
+ // Cast visualizer plugin to any to avoid Rollup type version clashes
120
+ visualizer({ filename: "./dist/stats.html", gzipSize: true }),
121
+ // The platform set stays external through this plugin rather than the top-level
122
+ // `external`: CommonJS dependencies in the app (MUI X, exceljs) `require("react")`, and
123
+ // only the plugin turns those requires into imports the import map can resolve. A
124
+ // plain external leaves a `require` shim that throws in the browser. The plugin must
125
+ // own the list, so the externals are not repeated below.
126
+ ...(usesPlatform(appframe) ? [esmExternalRequirePlugin({ external: PLATFORM_EXTERNALS })] : []),
127
+ ],
128
+ external: usesPlatform(appframe)
129
+ ? []
130
+ : appframe.build.externals !== false
131
+ ? ["react", "react-dom", "react-dom/client"]
132
+ : [],
28
133
  treeshake: {
29
134
  /*
30
135
  * `@digdir/designsystemet-react` does not declare `sideEffects: false`, so the
@@ -39,7 +144,9 @@ export async function addAppframeBuildConfig(config) {
39
144
  moduleSideEffects: (id) => !/[\\/]node_modules[\\/].*designsystemet-react[\\/]/.test(id.replace(/\?.*$/, "")),
40
145
  },
41
146
  output: {
42
- globals: appframe.build.externals !== false
147
+ // Platform builds keep the bare specifiers (ESM, resolved by the import map); the old
148
+ // externals mode maps them to window globals instead.
149
+ globals: !usesPlatform(appframe) && appframe.build.externals !== false
43
150
  ? {
44
151
  react: "React",
45
152
  "react-dom": "ReactDOM",
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Plugin } from "vite";
2
2
  import { appframeDataGlobal } from "./appframeDataGlobal.js";
3
- import { addAppframeBuildConfig } from "./build.js";
3
+ import { addAppframeBuildConfig, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, usesPlatform } from "./build.js";
4
4
  import { createDevMiddleware } from "./devServer.js";
5
5
  import { publishSafeOutput } from "./publishSafeOutput.js";
6
6
  export interface AppframePluginOptions {
@@ -32,6 +32,7 @@ export interface AppframePluginOptions {
32
32
  dataGlobal?: boolean;
33
33
  }
34
34
  export default function appframe(options?: AppframePluginOptions): Plugin[];
35
- export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, publishSafeOutput };
35
+ export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, publishSafeOutput, usesPlatform, };
36
+ export type { PlatformManifest } from "./build.js";
36
37
  export { DEVTOOLS_API_VERSION, type DevtoolsDiscovery, type DevtoolsServerInfo } from "./devtoolsServer.js";
37
38
  export type { JobOptions, ReleaseEvent, ReleaseScriptEvent, ReleaseSnapshot, ReleaseStep } from "./releaseServer.js";
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { resolve } from "node:path";
2
3
  import bodyParser from "body-parser";
3
4
  import { watch } from "chokidar";
4
5
  import { appframeDataGlobal } from "./appframeDataGlobal.js";
5
- import { addAppframeBuildConfig } from "./build.js";
6
+ import { addAppframeBuildConfig, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, usesPlatform, } from "./build.js";
6
7
  import { generateFromConfig } from "./cli-resources-generate.js";
7
8
  import { createDevMiddleware, getLoginInfo, getProxyRoutes } from "./devServer.js";
8
9
  import { createDevtoolsMiddleware } from "./devtoolsServer.js";
@@ -240,7 +241,30 @@ export default function appframe(options = {}) {
240
241
  };
241
242
  },
242
243
  };
243
- return [...(dataGlobal ? [appframeDataGlobal()] : []), plugin, publishSafeOutput()];
244
+ let plugins = [plugin, publishSafeOutput()];
245
+ let platform = usesPlatform(readAppframeConfig());
246
+ if (platform) {
247
+ plugins.push(platformManifest());
248
+ }
249
+ if (dataGlobal) {
250
+ // An app built against the platform leaves `@olenbetong/appframe-data` as a bare
251
+ // specifier for the import map, so the global redirect only applies to the dev server
252
+ // (which has no import map) and to builds that bundle their own runtime.
253
+ plugins.unshift({
254
+ ...appframeDataGlobal(),
255
+ apply: (_config, env) => env.command === "serve" || !platform,
256
+ });
257
+ }
258
+ return plugins;
244
259
  }
245
- export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, publishSafeOutput };
260
+ /** The `appframe` section of the app's package.json, read synchronously at plugin creation. */
261
+ function readAppframeConfig() {
262
+ try {
263
+ return JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")).appframe;
264
+ }
265
+ catch {
266
+ return undefined;
267
+ }
268
+ }
269
+ export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, publishSafeOutput, usesPlatform, };
246
270
  export { DEVTOOLS_API_VERSION } from "./devtoolsServer.js";
@@ -9,6 +9,8 @@ export declare function writeGeneratedFile(filePath: string, content: string): P
9
9
  export type CLIOptions = {
10
10
  /** Procedures only. `false` stops the framework wrapping the call in a transaction. */
11
11
  transaction?: boolean;
12
+ /** Procedures only. Milliseconds before the call is abandoned; the generated default is 30000. */
13
+ timeout?: number;
12
14
  server: string;
13
15
  types?: boolean;
14
16
  global: boolean;
@@ -305,7 +305,7 @@ export function getProcedureDefinition(name, procDefinition, options) {
305
305
  output.push(`export const ${procName} = new ${options.global ? "af." : ""}ProcedureAPI${options.types ? `<${paramTypeName}, ${options.typesJsonReturnType ?? "{ Table?: unknown[] }"}>` : ""}({
306
306
  procedureId: "${name}",
307
307
  parameters: ${JSON.stringify(parameters, null, 2)},
308
- timeout: 30000${options.transaction === false ? ",\n transaction: false" : ""}
308
+ timeout: ${options.timeout ?? 30000}${options.transaction === false ? ",\n transaction: false" : ""}
309
309
  });`);
310
310
  if (options.expose) {
311
311
  output.push("");
@@ -37,6 +37,12 @@ export type ResourceEntry = {
37
37
  * remote write a distributed one, which fails without MSDTC configured between the servers.
38
38
  */
39
39
  transaction?: boolean;
40
+ /**
41
+ * Procedures only. Milliseconds the call may take before it is abandoned, client and server
42
+ * alike (default `30000`). For a procedure that reads across a linked server or scans a large
43
+ * table, where a preview alone can take a minute.
44
+ */
45
+ timeout?: number;
40
46
  /** Permissions to set: I = insert, U = update, D = delete (e.g. `IUD`) */
41
47
  permissions?: string;
42
48
  /** Maximum records to fetch (default `50`; use `-1` for all) */
@@ -98,6 +98,7 @@ export function entryToCLIOptions(entry, hostname) {
98
98
  dynamic: entry.dynamic ?? false,
99
99
  types: entry.types,
100
100
  transaction: entry.transaction,
101
+ timeout: entry.timeout,
101
102
  maxRecords: entry.maxRecords !== undefined ? String(entry.maxRecords) : "50",
102
103
  sortOrder: sortOrderToString(entry.sortOrder),
103
104
  permissions: entry.permissions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-vite",
3
- "version": "6.8.0",
3
+ "version": "6.9.0",
4
4
  "description": "Tools to use and deploy Vite applications to Appframe",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -35,18 +35,18 @@
35
35
  "commander": "15.0.0",
36
36
  "dotenv": "^17.4.2",
37
37
  "fuzzy": "^0.1.3",
38
- "inquirer": "^14.2.0",
38
+ "inquirer": "^14.2.2",
39
39
  "inquirer-autocomplete-standalone": "^0.8.1",
40
40
  "jsdom": "30.0.1",
41
41
  "rollup-plugin-visualizer": "^7.1.1",
42
- "yaml": "^2.9.0",
43
- "@olenbetong/appframe-data": "1.7.0"
42
+ "yaml": "^2.9.1",
43
+ "@olenbetong/appframe-data": "1.7.2"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/jsdom": "^27.0.0",
47
- "@types/node": "26.4.0",
46
+ "@types/jsdom": "^30.0.0",
47
+ "@types/node": "26.5.1",
48
48
  "typescript": "7.0.2",
49
- "vite": "8.2.2"
49
+ "vite": "8.3.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "vite": ">=8.1.5"