@olenbetong/appframe-vite 6.15.0 → 6.16.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.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resolves the app's entry module. Apps have no `index.html`: the dev server fetches the
3
+ * article's HTML from Synergi and injects a `<script type="module">` pointing at this file,
4
+ * and the production build uses it as the single rolldown input.
5
+ *
6
+ * `relative` is a POSIX path from the root, usable as a glob pattern (`optimizeDeps.entries`
7
+ * takes globs, and an absolute Windows path would be read as one with escapes in it).
8
+ * The last candidate is returned even when it does not exist, so a missing entry fails
9
+ * with a resolve error that names the file instead of a vaguer one later on.
10
+ */
11
+ export declare function getAppEntry(root?: string): {
12
+ absolute: string;
13
+ relative: string;
14
+ url: string;
15
+ };
@@ -0,0 +1,19 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ /** The entry file candidates, in the order they are tried, relative to the app root. */
4
+ const ENTRY_CANDIDATES = ["src/index.tsx", "src/index.ts", "src/index.js"];
5
+ /**
6
+ * Resolves the app's entry module. Apps have no `index.html`: the dev server fetches the
7
+ * article's HTML from Synergi and injects a `<script type="module">` pointing at this file,
8
+ * and the production build uses it as the single rolldown input.
9
+ *
10
+ * `relative` is a POSIX path from the root, usable as a glob pattern (`optimizeDeps.entries`
11
+ * takes globs, and an absolute Windows path would be read as one with escapes in it).
12
+ * The last candidate is returned even when it does not exist, so a missing entry fails
13
+ * with a resolve error that names the file instead of a vaguer one later on.
14
+ */
15
+ export function getAppEntry(root = process.cwd()) {
16
+ let found = ENTRY_CANDIDATES.find((candidate) => existsSync(resolve(root, candidate)));
17
+ let relative = found ?? ENTRY_CANDIDATES[ENTRY_CANDIDATES.length - 1];
18
+ return { absolute: resolve(root, relative), relative, url: `/${relative}` };
19
+ }
@@ -19,6 +19,8 @@ export const GLOBAL_EXPORTS = [
19
19
  "Procedure",
20
20
  "ProcedureAPI",
21
21
  "SortOrder",
22
+ "UploadManager",
23
+ "UploadTask",
22
24
  "generateApiDataHandler",
23
25
  "generateApiDataObject",
24
26
  "getDefaultClient",
package/lib/build.js CHANGED
@@ -1,8 +1,8 @@
1
- import { existsSync } from "node:fs";
2
1
  import { readdir, readFile, writeFile } from "node:fs/promises";
3
2
  import { dirname, resolve } from "node:path";
4
3
  import { visualizer } from "rollup-plugin-visualizer";
5
4
  import { esmExternalRequirePlugin } from "vite";
5
+ import { getAppEntry } from "./appEntry.js";
6
6
  import { importJson } from "./importJson.js";
7
7
  /**
8
8
  * The bare specifiers the 2026 site templates resolve through their import map to the
@@ -202,16 +202,9 @@ export async function addAppframeBuildConfig(config) {
202
202
  sourcemap: true,
203
203
  };
204
204
  config.plugins = config.plugins ?? [];
205
- let entry = resolve(process.cwd(), "./src/index.tsx");
206
- if (!existsSync(resolve(entry))) {
207
- entry = resolve(process.cwd(), "./src/index.ts");
208
- }
209
- if (!existsSync(resolve(entry))) {
210
- entry = resolve(process.cwd(), "./src/index.js");
211
- }
212
205
  config.appType = "custom";
213
206
  config.build.rolldownOptions = {
214
- input: { main: entry },
207
+ input: { main: getAppEntry().absolute },
215
208
  plugins: [
216
209
  // Cast visualizer plugin to any to avoid Rollup type version clashes
217
210
  visualizer({ filename: "./dist/stats.html", gzipSize: true }),
@@ -182,6 +182,10 @@ function dataObjectToEntry(datasource, fields, outputDir, warnings) {
182
182
  if (sortFields.length > 0) {
183
183
  entry.sortOrder = sortFields.map((f) => {
184
184
  let direction = (f.AscDesc ?? "asc").toLowerCase();
185
+ // Keep the null placement of AscNullsLast / DescNullsFirst
186
+ if (direction === "ascnullslast" || direction === "descnullsfirst") {
187
+ return { field: f.FieldName, direction };
188
+ }
185
189
  return direction.startsWith("desc") ? { field: f.FieldName, direction: "desc" } : { field: f.FieldName };
186
190
  });
187
191
  }
package/lib/devServer.js CHANGED
@@ -5,6 +5,7 @@ import { AuthenticationError, authenticate, loginHint, resolveCredentials } from
5
5
  import { Client } from "@olenbetong/appframe-data";
6
6
  import dotenv from "dotenv";
7
7
  import { JSDOM } from "jsdom";
8
+ import { getAppEntry } from "./appEntry.js";
8
9
  import { importJson } from "./importJson.js";
9
10
  import { getStringCache } from "./localization.js";
10
11
  import { diagnoseServerResponse } from "./utils.js";
@@ -68,16 +69,7 @@ export function getProxyRoutes() {
68
69
  * @returns
69
70
  */
70
71
  async function transformArticleHtml(html) {
71
- let entry;
72
- if (existsSync(resolve(process.cwd(), "./src/index.tsx"))) {
73
- entry = "/src/index.tsx";
74
- }
75
- else if (existsSync(resolve(process.cwd(), "./src/index.ts"))) {
76
- entry = "/src/index.ts";
77
- }
78
- else {
79
- entry = "/src/index.js";
80
- }
72
+ let entry = getAppEntry().url;
81
73
  let article = appframe.article?.id;
82
74
  let { hostname } = await getAppHostname();
83
75
  let dom = new JSDOM(html);
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import bodyParser from "body-parser";
4
4
  import { watch } from "chokidar";
5
+ import { getAppEntry } from "./appEntry.js";
5
6
  import { appframeDataGlobal } from "./appframeDataGlobal.js";
6
7
  import { addAppframeBuildConfig, PLATFORM_EXTERNALS, PLATFORM_GLOBALS, PLATFORM_UPSTREAM, platformManifest, usesPlatform, } from "./build.js";
7
8
  import { generateFromConfig } from "./cli-resources-generate.js";
@@ -184,6 +185,19 @@ export default function appframe(options = {}) {
184
185
  ...config.server,
185
186
  proxy,
186
187
  };
188
+ // Apps have no index.html, so the dependency scanner Vite runs at startup has
189
+ // nothing to crawl (it globs "**/*.html") and skips pre-bundling altogether. Every
190
+ // dependency is then discovered when a module first imports it: the ones behind
191
+ // the initial page load are batched, but a lazy route that pulls in a new one
192
+ // re-runs the optimizer and forces a full reload, which lands back on the page
193
+ // you navigated from. Crawling from the entry module finds the lazy imports up
194
+ // front, so the deps are ready before the browser asks for them.
195
+ if (!config.optimizeDeps?.entries) {
196
+ config.optimizeDeps = {
197
+ ...config.optimizeDeps,
198
+ entries: [getAppEntry().relative],
199
+ };
200
+ }
187
201
  }
188
202
  return config;
189
203
  },
@@ -169,6 +169,13 @@ export function parseYamlConfig(fileContent) {
169
169
  // ---------------------------------------------------------------------------
170
170
  // Code generation helpers
171
171
  // ---------------------------------------------------------------------------
172
+ /**
173
+ * A field name as an object key in generated code, quoted when it is not a valid identifier,
174
+ * such as a view column named `EU-Neste`.
175
+ */
176
+ function propertyKey(name) {
177
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
178
+ }
172
179
  /**
173
180
  * Convert Appframe (SQL) data types to typescript/field definition types
174
181
  */
@@ -394,7 +401,7 @@ export function getDataObjectDefinition(name, viewDefinition, options) {
394
401
  type += " | null";
395
402
  }
396
403
  }
397
- fieldTypes += ` ${field.name}: ${type};\n`;
404
+ fieldTypes += ` ${propertyKey(field.name)}: ${type};\n`;
398
405
  }
399
406
  types = `export type ${typeName} = {
400
407
  ${fieldTypes}}`;
@@ -465,7 +472,7 @@ ${fieldTypes}}`;
465
472
  order = "DescNullsFirst";
466
473
  break;
467
474
  }
468
- return `{ ${field}: ${sortPrefix}${order} }`;
475
+ return `{ ${propertyKey(field)}: ${sortPrefix}${order} }`;
469
476
  })
470
477
  .join(", ");
471
478
  parametersOption.push(`sortOrder: [${sortOrder}]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-vite",
3
- "version": "6.15.0",
3
+ "version": "6.16.0",
4
4
  "description": "Tools to use and deploy Vite applications to Appframe",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -30,24 +30,24 @@
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
32
  "@olenbetong/appframe-auth": "^0.1.0",
33
- "@olenbetong/appframe-data": "^1.8.2",
33
+ "@olenbetong/appframe-data": "^1.8.3",
34
34
  "body-parser": "^2.3.0",
35
35
  "chalk": "6.0.1",
36
36
  "chokidar": "^5.0.0",
37
37
  "commander": "15.0.0",
38
- "dotenv": "^18.0.4",
38
+ "dotenv": "^18.0.8",
39
39
  "fuzzy": "^0.1.3",
40
- "inquirer": "^14.2.2",
40
+ "inquirer": "^14.2.4",
41
41
  "inquirer-autocomplete-standalone": "^0.8.1",
42
- "jsdom": "30.1.1",
42
+ "jsdom": "30.1.3",
43
43
  "rollup-plugin-visualizer": "^7.1.1",
44
44
  "yaml": "^2.9.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/jsdom": "^30.0.0",
48
- "@types/node": "26.6.3",
48
+ "@types/node": "26.6.5",
49
49
  "typescript": "7.0.2",
50
- "vite": "8.3.1"
50
+ "vite": "8.3.5"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": ">=8.1.5"