@olenbetong/appframe-vite 6.3.5 → 6.5.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/lib/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Plugin } from "vite";
2
2
  import { addAppframeBuildConfig } from "./build.js";
3
3
  import { createDevMiddleware } from "./devServer.js";
4
+ import { publishSafeOutput } from "./publishSafeOutput.js";
4
5
  export interface AppframePluginOptions {
5
6
  /**
6
7
  * Whether to automatically generate TypeScript types from the Appframe article
@@ -10,6 +11,14 @@ export interface AppframePluginOptions {
10
11
  * @default true
11
12
  */
12
13
  generateTypes?: boolean;
14
+ /**
15
+ * Whether to enable the Appframe devtools panel and toolbar in dev mode.
16
+ * Set to `false` to skip serving the devtools app and injecting the toolbar
17
+ * script (e.g. when running Storybook).
18
+ *
19
+ * @default true
20
+ */
21
+ devtools?: boolean;
13
22
  }
14
- export default function appframe(options?: AppframePluginOptions): Plugin;
15
- export { addAppframeBuildConfig, createDevMiddleware };
23
+ export default function appframe(options?: AppframePluginOptions): Plugin[];
24
+ export { addAppframeBuildConfig, createDevMiddleware, publishSafeOutput };
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@ import { localizeMiddleware } from "./localization.js";
10
10
  import { checkSession, getLastSession, login } from "./proxy.js";
11
11
  import { RESOURCES_CONFIG_FILE } from "./resourcesConfig.js";
12
12
  import { createLogMessage, getServerName } from "./utils.js";
13
+ import { publishSafeOutput } from "./publishSafeOutput.js";
13
14
  let command = "build";
14
15
  let interval;
15
16
  let server;
@@ -33,8 +34,8 @@ catch (error) {
33
34
  }
34
35
  const jsonParser = bodyParser.json();
35
36
  export default function appframe(options = {}) {
36
- let { generateTypes = true } = options;
37
- return {
37
+ let { generateTypes = true, devtools = true } = options;
38
+ let plugin = {
38
39
  name: "appframe",
39
40
  resolveId(source) {
40
41
  if (source.startsWith("/file/") || source.startsWith("/lib/")) {
@@ -178,7 +179,9 @@ export default function appframe(options = {}) {
178
179
  }, 300);
179
180
  });
180
181
  // DevTools panel: serve the devtools app + REST API at /__appframe_devtools__/
181
- _server.middlewares.use("/__appframe_devtools__", createDevtoolsMiddleware(hostname));
182
+ if (devtools) {
183
+ _server.middlewares.use("/__appframe_devtools__", createDevtoolsMiddleware(hostname));
184
+ }
182
185
  _server.middlewares.use(`/api/user/localize/new/${appframe.article.id}`, jsonParser);
183
186
  _server.middlewares.use(`/api/user/localize/new/${appframe.article.id}`, localizeMiddleware);
184
187
  _server.middlewares.use("/data/Logger/LogError", jsonParser);
@@ -197,7 +200,7 @@ export default function appframe(options = {}) {
197
200
  },
198
201
  transformIndexHtml(_html, ctx) {
199
202
  // Only inject the toolbar in dev mode
200
- if (command !== "serve")
203
+ if (command !== "serve" || !devtools)
201
204
  return;
202
205
  // Only inject for article pages (not the devtools app itself)
203
206
  if (ctx.originalUrl?.startsWith("/__appframe_devtools__"))
@@ -211,5 +214,6 @@ export default function appframe(options = {}) {
211
214
  ];
212
215
  },
213
216
  };
217
+ return [plugin, publishSafeOutput()];
214
218
  }
215
- export { addAppframeBuildConfig, createDevMiddleware };
219
+ export { addAppframeBuildConfig, createDevMiddleware, publishSafeOutput };
@@ -0,0 +1,25 @@
1
+ import { type Plugin } from "vite";
2
+ /**
3
+ * Rewrites emitted JavaScript so that no line ever ends with a backslash.
4
+ *
5
+ * Appframe deploys an article by embedding every script verbatim in a generated SQL
6
+ * script, which the target server executes with SQLCMD semantics. There, a backslash
7
+ * at the end of a line is a line-continuation character: both the backslash and the
8
+ * line break are silently removed. A minified bundle containing a template literal
9
+ * such as `` `\\<newline>` `` (produced for the string "\\\n") therefore arrives as
10
+ * `` `\` `` on stage/prod, where the closing backtick has become an escaped backtick.
11
+ * The literal never terminates and the whole chunk fails to parse with a misleading
12
+ * "Unexpected identifier" SyntaxError.
13
+ *
14
+ * The rewrite is semantics-preserving:
15
+ * - An even number of trailing backslashes means they are all escaped backslashes and
16
+ * the line break is a literal character inside a template literal. The line break is
17
+ * re-encoded as `\n` (or `\r\n`) so the physical line ends without a backslash.
18
+ * - An odd number means the last backslash escapes the line break (a line continuation),
19
+ * which contributes nothing to the value, so both are dropped.
20
+ *
21
+ * Because a physical line break is removed, source map line numbers for a rewritten chunk
22
+ * are off by one from that point on. Rewrites are rare (typically a single occurrence deep
23
+ * inside a vendor chunk), so this is accepted and reported as a build warning.
24
+ */
25
+ export declare function publishSafeOutput(): Plugin;
@@ -0,0 +1,106 @@
1
+ import { parseAst } from "vite";
2
+ /**
3
+ * Rewrites emitted JavaScript so that no line ever ends with a backslash.
4
+ *
5
+ * Appframe deploys an article by embedding every script verbatim in a generated SQL
6
+ * script, which the target server executes with SQLCMD semantics. There, a backslash
7
+ * at the end of a line is a line-continuation character: both the backslash and the
8
+ * line break are silently removed. A minified bundle containing a template literal
9
+ * such as `` `\\<newline>` `` (produced for the string "\\\n") therefore arrives as
10
+ * `` `\` `` on stage/prod, where the closing backtick has become an escaped backtick.
11
+ * The literal never terminates and the whole chunk fails to parse with a misleading
12
+ * "Unexpected identifier" SyntaxError.
13
+ *
14
+ * The rewrite is semantics-preserving:
15
+ * - An even number of trailing backslashes means they are all escaped backslashes and
16
+ * the line break is a literal character inside a template literal. The line break is
17
+ * re-encoded as `\n` (or `\r\n`) so the physical line ends without a backslash.
18
+ * - An odd number means the last backslash escapes the line break (a line continuation),
19
+ * which contributes nothing to the value, so both are dropped.
20
+ *
21
+ * Because a physical line break is removed, source map line numbers for a rewritten chunk
22
+ * are off by one from that point on. Rewrites are rare (typically a single occurrence deep
23
+ * inside a vendor chunk), so this is accepted and reported as a build warning.
24
+ */
25
+ export function publishSafeOutput() {
26
+ return {
27
+ name: "appframe:publish-safe-output",
28
+ apply: "build",
29
+ enforce: "post",
30
+ generateBundle(_options, bundle) {
31
+ for (let output of Object.values(bundle)) {
32
+ if (output.type === "asset") {
33
+ if (typeof output.source === "string" && /\\\r?\n/.test(output.source)) {
34
+ this.warn(`${output.fileName} contains a backslash at the end of a line. Appframe's deploy will strip it, ` +
35
+ `which may corrupt the file.`);
36
+ }
37
+ continue;
38
+ }
39
+ let result = rewrite(output.code);
40
+ if (result.rewrites === 0)
41
+ continue;
42
+ if (!isStillValid(output.code, result.code)) {
43
+ this.error(`Failed to make ${output.fileName} deploy-safe: the rewritten chunk no longer parses. ` +
44
+ `It contains a backslash at the end of a line, which Appframe's deploy will strip.`);
45
+ }
46
+ output.code = result.code;
47
+ this.warn(`${output.fileName} had ${result.rewrites} line break(s) escaped so no line ends with a backslash, ` +
48
+ `which Appframe's deploy would strip. Source map line numbers for this chunk are shifted.`);
49
+ }
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Rewrites `code` so that no physical line ends with a backslash, and reports how many
55
+ * line breaks had to be rewritten.
56
+ */
57
+ function rewrite(code) {
58
+ let lines = code.split("\n");
59
+ let out = "";
60
+ let rewrites = 0;
61
+ for (let i = 0; i < lines.length; i++) {
62
+ let isLast = i === lines.length - 1;
63
+ let raw = lines[i];
64
+ let hasCarriageReturn = !isLast && raw.endsWith("\r");
65
+ let content = hasCarriageReturn ? raw.slice(0, -1) : raw;
66
+ let backslashes = 0;
67
+ while (backslashes < content.length && content[content.length - 1 - backslashes] === "\\") {
68
+ backslashes++;
69
+ }
70
+ if (!isLast && backslashes > 0) {
71
+ rewrites++;
72
+ if (backslashes % 2 === 0) {
73
+ out += content + (hasCarriageReturn ? "\\r\\n" : "\\n");
74
+ }
75
+ else {
76
+ out += content.slice(0, -1);
77
+ }
78
+ continue;
79
+ }
80
+ out += content;
81
+ if (!isLast) {
82
+ out += hasCarriageReturn ? "\r\n" : "\n";
83
+ }
84
+ }
85
+ return { code: out, rewrites };
86
+ }
87
+ /**
88
+ * The rewrite is only safe for backslashes inside string and template literals. A line
89
+ * comment ending with a backslash would be merged with the following line, so verify the
90
+ * result still parses whenever the original did.
91
+ */
92
+ function isStillValid(original, rewritten) {
93
+ try {
94
+ parseAst(original);
95
+ }
96
+ catch {
97
+ return true;
98
+ }
99
+ try {
100
+ parseAst(rewritten);
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-vite",
3
- "version": "6.3.5",
3
+ "version": "6.5.0",
4
4
  "description": "Tools to use and deploy Vite applications to Appframe",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/jsdom": "^27.0.0",
48
- "@types/node": "26.1.1",
48
+ "@types/node": "26.1.2",
49
49
  "typescript": "7.0.2",
50
- "vite": "8.1.5"
50
+ "vite": "8.2.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": ">=8.1.5"