@olenbetong/appframe-vite 6.8.1 → 6.10.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 +41 -23
- package/lib/appframeDataGlobal.d.ts +15 -0
- package/lib/appframeDataGlobal.js +2 -2
- package/lib/build.d.ts +40 -1
- package/lib/build.js +142 -5
- package/lib/index.d.ts +3 -2
- package/lib/index.js +48 -16
- package/lib/resourceGenerate.d.ts +2 -0
- package/lib/resourceGenerate.js +1 -1
- package/lib/resourcesConfig.d.ts +6 -0
- package/lib/resourcesConfig.js +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -68,12 +68,28 @@ 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
|
-
"
|
|
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 platform version the app was built alongside (the workspace's
|
|
87
|
+
`packages/synergi-platform`) and the versions it compiled against; `scripts/app-deploy.ts` pins
|
|
88
|
+
the article to that platform version (its `PlatformHead` and `PlatformStyles` blocks) and
|
|
89
|
+
refuses to deploy when the article is not on a 2026 template, that version is not served on the
|
|
90
|
+
target site, or its manifest carries other versions than the app (override with
|
|
91
|
+
`--allow-platform-mismatch`).
|
|
92
|
+
|
|
77
93
|
## What it does (at a glance)
|
|
78
94
|
|
|
79
95
|
- Dev server
|
|
@@ -86,7 +102,7 @@ This package reads `package.json.appframe` to know what to proxy and how to buil
|
|
|
86
102
|
- Writes filenames to Appframe paths, using the article ID, e.g.:
|
|
87
103
|
- `file/article/script/<ARTICLE_ID>/main.[hash].min.js`
|
|
88
104
|
- `file/article/style/<ARTICLE_ID>/style.[hash].min.css`
|
|
89
|
-
-
|
|
105
|
+
- 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
106
|
- Adds a Rollup visualizer report at `dist/stats.html`.
|
|
91
107
|
- Resolve
|
|
92
108
|
- Adds the alias `~/` to `/src/` so imports like `~/components/Button` resolve to `src/components/Button`.
|
|
@@ -240,27 +256,29 @@ procedures:
|
|
|
240
256
|
|
|
241
257
|
**Supported fields per entry:**
|
|
242
258
|
|
|
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)
|
|
259
|
+
| Field | Type | Description |
|
|
260
|
+
| ------------- | -------------------- | ------------------------------------------------------------------------------- |
|
|
261
|
+
| `id` | `string` | Variable name used in generated code (e.g. `dsAccountGroups`) |
|
|
262
|
+
| `resource` | `string` | Database object ID (e.g. `atbv_Accounting_SubsidiaryLedgerGroups`) |
|
|
263
|
+
| `output` | `string` | Output file path relative to project root |
|
|
264
|
+
| `global` | `boolean` | Use `af.data.generateApiDataObject` / `new af.ProcedureAPI` globals |
|
|
265
|
+
| `types` | `boolean` | Emit TypeScript type definitions |
|
|
266
|
+
| `permissions` | `string` | Permissions: I = insert, U = update, D = delete (e.g. `IUD`) |
|
|
267
|
+
| `maxRecords` | `number` | Max records to fetch (default `50`; `-1` for all) |
|
|
268
|
+
| `sortOrder` | `string \| string[]` | Sort order, e.g. `Created:Desc` or `[Created:Desc, Name]` |
|
|
269
|
+
| `master` | `string` | Master data object name (or `name:importPath`) |
|
|
270
|
+
| `linkFields` | `string \| string[]` | Fields linking child to master |
|
|
271
|
+
| `expose` | `boolean \| string` | Expose on `af.article.dataObjects` / `af.article.procedures` |
|
|
272
|
+
| `dynamic` | `boolean` | Enable dynamic loading |
|
|
273
|
+
| `unique` | `string` | Unique table name for update/delete |
|
|
274
|
+
| `overrides` | `string \| string[]` | Type overrides, e.g. `MyField:string[]` |
|
|
275
|
+
| `distinct` | `boolean` | Fetch distinct rows |
|
|
276
|
+
| `aggregates` | `string \| string[]` | Aggregate bindings, e.g. `Qty:SUM` |
|
|
277
|
+
| `groupBy` | `string \| string[]` | Group-by fields |
|
|
278
|
+
| `where` | `string` | Initial where clause |
|
|
279
|
+
| `fields` | `string \| string[]` | Fields to include (all if omitted) |
|
|
280
|
+
| `transaction` | `boolean` | Procedures only. `false` stops the framework wrapping the call in a transaction |
|
|
281
|
+
| `timeout` | `number` | Procedures only. Milliseconds before the call is abandoned (default `30000`) |
|
|
264
282
|
|
|
265
283
|
A top-level `server` key can override the hostname (defaults to `appframe.proxy.hostname` from `package.json`).
|
|
266
284
|
|
|
@@ -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,41 @@
|
|
|
1
|
-
import type
|
|
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
|
+
/**
|
|
26
|
+
* The `@olenbetong/synergi-platform` version the app was built alongside - the one its
|
|
27
|
+
* article is pinned to on deploy. Missing when the build found no platform package.
|
|
28
|
+
*/
|
|
29
|
+
platform?: string;
|
|
30
|
+
/** The package versions the app resolved at build time. */
|
|
31
|
+
upstream: Record<string, string>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Writes `dist/platform.json` after a platform build: the platform version the app was built
|
|
35
|
+
* alongside and the versions of the platform packages it compiled against. The deploy pins the
|
|
36
|
+
* article to that platform version (its `PlatformHead` block) and compares the versions with the
|
|
37
|
+
* manifest that version serves on the target site, since an app compiled against one React and
|
|
38
|
+
* run on another is a runtime error, not a build error.
|
|
39
|
+
*/
|
|
40
|
+
export declare function platformManifest(): Plugin;
|
|
2
41
|
export declare function addAppframeBuildConfig(config: UserConfig): Promise<UserConfig>;
|
package/lib/build.js
CHANGED
|
@@ -1,7 +1,130 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, 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
|
+
* The platform package's version as the app sees it: its own copy under node_modules when it
|
|
74
|
+
* depends on `@olenbetong/synergi-platform`, otherwise the workspace's `packages/synergi-platform`
|
|
75
|
+
* found by walking up from the app - every app lives in the monorepo next to it.
|
|
76
|
+
*/
|
|
77
|
+
async function platformVersion(cwd) {
|
|
78
|
+
let candidates = [resolve(cwd, "node_modules", "@olenbetong", "synergi-platform", "package.json")];
|
|
79
|
+
let dir = cwd;
|
|
80
|
+
while (true) {
|
|
81
|
+
candidates.push(resolve(dir, "packages", "synergi-platform", "package.json"));
|
|
82
|
+
let parent = dirname(dir);
|
|
83
|
+
if (parent === dir)
|
|
84
|
+
break;
|
|
85
|
+
dir = parent;
|
|
86
|
+
}
|
|
87
|
+
for (let file of candidates) {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(await readFile(file, "utf8")).version;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Not here; try the next candidate.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Writes `dist/platform.json` after a platform build: the platform version the app was built
|
|
99
|
+
* alongside and the versions of the platform packages it compiled against. The deploy pins the
|
|
100
|
+
* article to that platform version (its `PlatformHead` block) and compares the versions with the
|
|
101
|
+
* manifest that version serves on the target site, since an app compiled against one React and
|
|
102
|
+
* run on another is a runtime error, not a build error.
|
|
103
|
+
*/
|
|
104
|
+
export function platformManifest() {
|
|
105
|
+
return {
|
|
106
|
+
name: "appframe:platform-manifest",
|
|
107
|
+
apply: "build",
|
|
108
|
+
async writeBundle() {
|
|
109
|
+
let manifest = { platform: await platformVersion(process.cwd()), upstream: {} };
|
|
110
|
+
if (manifest.platform === undefined) {
|
|
111
|
+
delete manifest.platform;
|
|
112
|
+
this.warn("No @olenbetong/synergi-platform package found; dist/platform.json records no platform version and the app cannot be deployed to a platform article.");
|
|
113
|
+
}
|
|
114
|
+
for (let name of PLATFORM_UPSTREAM) {
|
|
115
|
+
let file = resolve(process.cwd(), "node_modules", name, "package.json");
|
|
116
|
+
try {
|
|
117
|
+
manifest.upstream[name] = JSON.parse(await readFile(file, "utf8")).version;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Not every app depends on every platform package (an app without a grid has no
|
|
121
|
+
// appframe-ds, say); only what the app resolved is recorded and checked.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await writeFile(resolve(process.cwd(), "dist", "platform.json"), `${JSON.stringify(manifest, null, "\t")}\n`);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
5
128
|
export async function addAppframeBuildConfig(config) {
|
|
6
129
|
let appPkg = await importJson("./package.json", true);
|
|
7
130
|
let { appframe } = appPkg;
|
|
@@ -22,9 +145,21 @@ export async function addAppframeBuildConfig(config) {
|
|
|
22
145
|
config.appType = "custom";
|
|
23
146
|
config.build.rolldownOptions = {
|
|
24
147
|
input: { main: entry },
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
148
|
+
plugins: [
|
|
149
|
+
// Cast visualizer plugin to any to avoid Rollup type version clashes
|
|
150
|
+
visualizer({ filename: "./dist/stats.html", gzipSize: true }),
|
|
151
|
+
// The platform set stays external through this plugin rather than the top-level
|
|
152
|
+
// `external`: CommonJS dependencies in the app (MUI X, exceljs) `require("react")`, and
|
|
153
|
+
// only the plugin turns those requires into imports the import map can resolve. A
|
|
154
|
+
// plain external leaves a `require` shim that throws in the browser. The plugin must
|
|
155
|
+
// own the list, so the externals are not repeated below.
|
|
156
|
+
...(usesPlatform(appframe) ? [esmExternalRequirePlugin({ external: PLATFORM_EXTERNALS })] : []),
|
|
157
|
+
],
|
|
158
|
+
external: usesPlatform(appframe)
|
|
159
|
+
? []
|
|
160
|
+
: appframe.build.externals !== false
|
|
161
|
+
? ["react", "react-dom", "react-dom/client"]
|
|
162
|
+
: [],
|
|
28
163
|
treeshake: {
|
|
29
164
|
/*
|
|
30
165
|
* `@digdir/designsystemet-react` does not declare `sideEffects: false`, so the
|
|
@@ -39,7 +174,9 @@ export async function addAppframeBuildConfig(config) {
|
|
|
39
174
|
moduleSideEffects: (id) => !/[\\/]node_modules[\\/].*designsystemet-react[\\/]/.test(id.replace(/\?.*$/, "")),
|
|
40
175
|
},
|
|
41
176
|
output: {
|
|
42
|
-
|
|
177
|
+
// Platform builds keep the bare specifiers (ESM, resolved by the import map); the old
|
|
178
|
+
// externals mode maps them to window globals instead.
|
|
179
|
+
globals: !usesPlatform(appframe) && appframe.build.externals !== false
|
|
43
180
|
? {
|
|
44
181
|
react: "React",
|
|
45
182
|
"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";
|
|
@@ -21,19 +22,27 @@ let lastHostname;
|
|
|
21
22
|
let watcher;
|
|
22
23
|
let resourcesWatcher = null;
|
|
23
24
|
let resourcesDebounce = null;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Restarts the dev server when the app's package.json changes its hostname. Started with the
|
|
27
|
+
* dev server, never at import: a watcher keeps node alive, and the deploy and release scripts
|
|
28
|
+
* import this package for its exports without ever running a server.
|
|
29
|
+
*/
|
|
30
|
+
function watchAppPackage() {
|
|
31
|
+
watcher?.close();
|
|
32
|
+
try {
|
|
33
|
+
let appPkgUrl = `file://${process.cwd()}/package.json`;
|
|
34
|
+
watcher = watch(appPkgUrl).on("all", async () => {
|
|
35
|
+
if (server) {
|
|
36
|
+
let { hostname } = await getLoginInfo();
|
|
37
|
+
if (lastHostname !== hostname) {
|
|
38
|
+
server.restart(false);
|
|
39
|
+
}
|
|
31
40
|
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.log(createLogMessage(`failed to watch package.json: ${error.message}`, { type: "warn" }));
|
|
45
|
+
}
|
|
37
46
|
}
|
|
38
47
|
const jsonParser = bodyParser.json();
|
|
39
48
|
export default function appframe(options = {}) {
|
|
@@ -120,10 +129,10 @@ export default function appframe(options = {}) {
|
|
|
120
129
|
},
|
|
121
130
|
};
|
|
122
131
|
if (command === "build") {
|
|
123
|
-
watcher.close();
|
|
124
132
|
await addAppframeBuildConfig(config);
|
|
125
133
|
}
|
|
126
134
|
else {
|
|
135
|
+
watchAppPackage();
|
|
127
136
|
let proxy = config.server?.proxy ?? {};
|
|
128
137
|
let routes = getProxyRoutes();
|
|
129
138
|
let { hostname, username, password } = await getLoginInfo();
|
|
@@ -240,7 +249,30 @@ export default function appframe(options = {}) {
|
|
|
240
249
|
};
|
|
241
250
|
},
|
|
242
251
|
};
|
|
243
|
-
|
|
252
|
+
let plugins = [plugin, publishSafeOutput()];
|
|
253
|
+
let platform = usesPlatform(readAppframeConfig());
|
|
254
|
+
if (platform) {
|
|
255
|
+
plugins.push(platformManifest());
|
|
256
|
+
}
|
|
257
|
+
if (dataGlobal) {
|
|
258
|
+
// An app built against the platform leaves `@olenbetong/appframe-data` as a bare
|
|
259
|
+
// specifier for the import map, so the global redirect only applies to the dev server
|
|
260
|
+
// (which has no import map) and to builds that bundle their own runtime.
|
|
261
|
+
plugins.unshift({
|
|
262
|
+
...appframeDataGlobal(),
|
|
263
|
+
apply: (_config, env) => env.command === "serve" || !platform,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return plugins;
|
|
267
|
+
}
|
|
268
|
+
/** The `appframe` section of the app's package.json, read synchronously at plugin creation. */
|
|
269
|
+
function readAppframeConfig() {
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")).appframe;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
244
276
|
}
|
|
245
|
-
export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, publishSafeOutput };
|
|
277
|
+
export { addAppframeBuildConfig, appframeDataGlobal, createDevMiddleware, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, publishSafeOutput, usesPlatform, };
|
|
246
278
|
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;
|
package/lib/resourceGenerate.js
CHANGED
|
@@ -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("");
|
package/lib/resourcesConfig.d.ts
CHANGED
|
@@ -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) */
|
package/lib/resourcesConfig.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "6.10.0",
|
|
4
4
|
"description": "Tools to use and deploy Vite applications to Appframe",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
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.
|
|
43
|
-
"@olenbetong/appframe-data": "1.7.
|
|
42
|
+
"yaml": "^2.9.1",
|
|
43
|
+
"@olenbetong/appframe-data": "1.7.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/jsdom": "^30.0.0",
|
|
47
47
|
"@types/node": "26.5.1",
|
|
48
48
|
"typescript": "7.0.2",
|
|
49
|
-
"vite": "8.
|
|
49
|
+
"vite": "8.3.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"vite": ">=8.1.5"
|