@salesforce/angular-plugin-ui-bundle 11.59.2 → 11.62.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/dist/bin/serve.js +79 -35
- package/dist/design-mode-hydrate.d.ts +8 -0
- package/dist/design-mode-hydrate.d.ts.map +1 -0
- package/dist/design-mode-hydrate.js +92 -0
- package/dist/middleware/html.d.ts.map +1 -1
- package/dist/middleware/html.js +50 -21
- package/dist/middleware/proxy.d.ts +2 -0
- package/dist/middleware/proxy.d.ts.map +1 -1
- package/dist/middleware/proxy.js +35 -4
- package/dist/utils.d.ts +22 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +62 -0
- package/package.json +3 -2
package/dist/bin/serve.js
CHANGED
|
@@ -4,16 +4,24 @@
|
|
|
4
4
|
* All rights reserved.
|
|
5
5
|
* For full license text, see the LICENSE.txt file
|
|
6
6
|
*/
|
|
7
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
8
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
9
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
10
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return path;
|
|
14
|
+
};
|
|
7
15
|
/**
|
|
8
16
|
* sf-angular-serve — dev server wrapper for Angular CLI UI Bundles.
|
|
9
17
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
18
|
+
* Runs ng serve with --define (substitutes __SF_API_VERSION__ in Vite's
|
|
19
|
+
* optimizeDeps prebundle — the app-build esbuild plugin can't reach it) and
|
|
20
|
+
* --port (pins SF_UIBUNDLE_PORT for orchestrator discovery).
|
|
13
21
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* The `--design` flag (consumed here, never forwarded to ng) selects the mode:
|
|
23
|
+
* default — spawns ng in a child process.
|
|
24
|
+
* --design — boots ng in-process so the compiler flag applies (see below).
|
|
17
25
|
*/
|
|
18
26
|
import { spawn } from "node:child_process";
|
|
19
27
|
import { createRequire } from "node:module";
|
|
@@ -21,27 +29,23 @@ import { join } from "node:path";
|
|
|
21
29
|
import { pathToFileURL } from "node:url";
|
|
22
30
|
import { createApiVersionPlugin } from "../plugins/api-version.js";
|
|
23
31
|
import { getPort } from "../utils.js";
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
// `--design` is a wrapper-only flag: consumed here, never passed to ng.
|
|
34
|
+
const designMode = process.argv.includes("--design");
|
|
24
35
|
const { version } = await createApiVersionPlugin();
|
|
25
36
|
const port = getPort();
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// shell to strip a quoting layer — a second stringify would bake literal quotes
|
|
29
|
-
// into the value and produce /services/data/v"68.0"/.
|
|
37
|
+
// --define needs valid JS source ("68.0", not 68.0); no shell, so stringify once
|
|
38
|
+
// (a second would bake literal quotes → /services/data/v"68.0"/).
|
|
30
39
|
const defineArg = `__SF_API_VERSION__=${JSON.stringify(version)}`;
|
|
31
|
-
// Resolve
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// Resolve from the consuming app first (process.cwd() = where sf-angular-serve was
|
|
35
|
-
// invoked). This is required in local dev where the plugin is symlinked via `file:`:
|
|
36
|
-
// import.meta.url then points outside the app, so a module-relative resolve would miss
|
|
37
|
-
// the app's @angular/cli. Fall back to module-relative for hoisted published installs.
|
|
40
|
+
// Resolve @angular/cli from the app (cwd) first — the plugin sits outside the app
|
|
41
|
+
// (symlinked in dev, hoisted when published). Fall back to module-relative.
|
|
38
42
|
function resolveNgCli() {
|
|
39
43
|
try {
|
|
40
44
|
const requireFromApp = createRequire(pathToFileURL(join(process.cwd(), "package.json")));
|
|
41
45
|
return requireFromApp.resolve("@angular/cli/bin/ng.js");
|
|
42
46
|
}
|
|
43
47
|
catch {
|
|
44
|
-
return
|
|
48
|
+
return require.resolve("@angular/cli/bin/ng.js");
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
let ngPath;
|
|
@@ -53,20 +57,60 @@ catch {
|
|
|
53
57
|
"Please install it in your project: npm install --save-dev @angular/cli");
|
|
54
58
|
process.exit(1);
|
|
55
59
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
process.on("
|
|
71
|
-
|
|
72
|
-
});
|
|
60
|
+
const ngArgs = ["serve", `--define=${defineArg}`, `--port=${port}`];
|
|
61
|
+
// Default mode: spawn ng in a child process (isolation + signal forwarding).
|
|
62
|
+
function spawnNgServe() {
|
|
63
|
+
const child = spawn(process.execPath, [ngPath, ...ngArgs], {
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
env: { ...process.env, NODE_OPTIONS: "--no-deprecation" },
|
|
66
|
+
});
|
|
67
|
+
child.on("error", (err) => {
|
|
68
|
+
console.error("[sf-angular-serve] Failed to start ng:", err.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
child.on("exit", (code) => {
|
|
72
|
+
process.exit(code ?? 0);
|
|
73
|
+
});
|
|
74
|
+
process.on("SIGINT", () => {
|
|
75
|
+
child.kill("SIGINT");
|
|
76
|
+
});
|
|
77
|
+
process.on("SIGTERM", () => {
|
|
78
|
+
child.kill("SIGTERM");
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Design mode: boot ng in-process. The source-location flag is an in-memory global
|
|
82
|
+
// on @angular/compiler — a spawned child would get a fresh instance with it unset.
|
|
83
|
+
async function bootNgInProcessForDesign() {
|
|
84
|
+
process.env.SF_DESIGN_MODE = "true";
|
|
85
|
+
// Compile on the main thread so the flag below reaches the compiler's isolate.
|
|
86
|
+
process.env.NG_BUILD_PARALLEL_TS = "0";
|
|
87
|
+
// Bake `data-ng-source-location` onto every element (the hydrate script derives
|
|
88
|
+
// data-source-file / data-text-type from it). Resolve @angular/compiler from the
|
|
89
|
+
// app (cwd) to hit the same instance ng compiles with; the wrong copy = no-op.
|
|
90
|
+
try {
|
|
91
|
+
const appRequire = createRequire(pathToFileURL(join(process.cwd(), "package.json")));
|
|
92
|
+
const compiler = appRequire("@angular/compiler");
|
|
93
|
+
compiler.setEnableTemplateSourceLocations(true);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error("[design-mode] failed to enable native source locations:", err);
|
|
97
|
+
}
|
|
98
|
+
// Rewrite argv to what plain `ng serve` would get, then boot. ng's exit code and
|
|
99
|
+
// signals are ours (same process); only the boot itself needs guarding.
|
|
100
|
+
const execPath = process.execPath;
|
|
101
|
+
const scriptPath = process.argv[1] ?? execPath;
|
|
102
|
+
process.argv = [execPath, scriptPath, ...ngArgs];
|
|
103
|
+
try {
|
|
104
|
+
await import(__rewriteRelativeImportExtension(pathToFileURL(ngPath).href));
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error("[sf-angular-serve] Failed to start ng:", err instanceof Error ? err.message : err);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (designMode) {
|
|
112
|
+
await bootNgInProcessForDesign();
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
spawnNgServe();
|
|
116
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/** The function above, serialized to a self-invoking script served verbatim. */
|
|
7
|
+
export declare const DESIGN_MODE_HYDRATE_SCRIPT: string;
|
|
8
|
+
//# sourceMappingURL=design-mode-hydrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-mode-hydrate.d.ts","sourceRoot":"","sources":["../src/design-mode-hydrate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqFH,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,QAAqC,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Client-side hydration for design mode.
|
|
8
|
+
*
|
|
9
|
+
* Reads Angular's native `data-ng-source-location` and re-injects the attributes
|
|
10
|
+
* the design runtime consumes, so ui-design-mode is unchanged:
|
|
11
|
+
* - data-source-file="<path>:<line>:<col>"
|
|
12
|
+
* - data-text-type="none | mixed | element | static"
|
|
13
|
+
*
|
|
14
|
+
* A lone text node is labeled "static" (not "dynamic") so inline editing stays
|
|
15
|
+
* enabled: at runtime static text and `{{ interpolation }}` are indistinguishable.
|
|
16
|
+
*
|
|
17
|
+
* Authored as a normal function; `DESIGN_MODE_HYDRATE_SCRIPT` (below) serializes it
|
|
18
|
+
* to the string proxy.ts serves verbatim — no bundling step, no escaped regexes.
|
|
19
|
+
*/
|
|
20
|
+
function hydrateClient() {
|
|
21
|
+
const NG_ATTR = "data-ng-source-location";
|
|
22
|
+
const SRC_ATTR = "data-source-file";
|
|
23
|
+
const TYPE_ATTR = "data-text-type";
|
|
24
|
+
// "<path>@o:<off>,l:<line>,c:<col>" -> "<path>:<line+1>:<col>" (native line 0-based).
|
|
25
|
+
// Keep the compiler's project-relative path verbatim — the host resolves and
|
|
26
|
+
// path-gates it, so a basename would break nested/same-named templates.
|
|
27
|
+
function toSourceFile(value) {
|
|
28
|
+
const m = /^(.*)@o:\d+,l:(\d+),c:(\d+)$/.exec(value);
|
|
29
|
+
if (!m || !m[1])
|
|
30
|
+
return null;
|
|
31
|
+
const path = m[1];
|
|
32
|
+
const line = parseInt(m[2], 10);
|
|
33
|
+
const col = parseInt(m[3], 10);
|
|
34
|
+
if (!isFinite(line) || !isFinite(col))
|
|
35
|
+
return null;
|
|
36
|
+
return path + ":" + (line + 1) + ":" + col;
|
|
37
|
+
}
|
|
38
|
+
// 0 children -> "none", >1 -> "mixed", 1 element -> "element", 1 text -> "static".
|
|
39
|
+
function classifyTextType(el) {
|
|
40
|
+
const relevant = [];
|
|
41
|
+
for (const n of el.childNodes) {
|
|
42
|
+
if (n.nodeType === 3 && !(n.textContent || "").trim())
|
|
43
|
+
continue;
|
|
44
|
+
relevant.push(n);
|
|
45
|
+
}
|
|
46
|
+
if (relevant.length === 0)
|
|
47
|
+
return "none";
|
|
48
|
+
if (relevant.length > 1)
|
|
49
|
+
return "mixed";
|
|
50
|
+
return relevant[0].nodeType === 1 ? "element" : "static";
|
|
51
|
+
}
|
|
52
|
+
function hydrateEl(el) {
|
|
53
|
+
const raw = el.getAttribute(NG_ATTR);
|
|
54
|
+
if (!raw)
|
|
55
|
+
return;
|
|
56
|
+
el.removeAttribute(NG_ATTR); // consumed; re-hydration passes skip it via the selector
|
|
57
|
+
const src = toSourceFile(raw);
|
|
58
|
+
if (!src)
|
|
59
|
+
return;
|
|
60
|
+
el.setAttribute(SRC_ATTR, src);
|
|
61
|
+
el.setAttribute(TYPE_ATTR, classifyTextType(el));
|
|
62
|
+
}
|
|
63
|
+
function hydrate(root) {
|
|
64
|
+
if (root.nodeType === 1 && root.hasAttribute(NG_ATTR)) {
|
|
65
|
+
hydrateEl(root);
|
|
66
|
+
}
|
|
67
|
+
for (const el of root.querySelectorAll("[" + NG_ATTR + "]")) {
|
|
68
|
+
hydrateEl(el);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function start() {
|
|
72
|
+
hydrate(document);
|
|
73
|
+
// Re-hydrate nodes Angular mounts later (@if / @for); the SRC_ATTR guard makes repeat passes cheap.
|
|
74
|
+
const observer = new MutationObserver((mutations) => {
|
|
75
|
+
for (const mutation of mutations) {
|
|
76
|
+
for (const node of mutation.addedNodes) {
|
|
77
|
+
if (node.nodeType === 1)
|
|
78
|
+
hydrate(node);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
83
|
+
}
|
|
84
|
+
if (document.readyState === "loading") {
|
|
85
|
+
document.addEventListener("DOMContentLoaded", start);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
start();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** The function above, serialized to a self-invoking script served verbatim. */
|
|
92
|
+
export const DESIGN_MODE_HYDRATE_SCRIPT = `(${hydrateClient.toString()})();`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/middleware/html.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/middleware/html.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA4FjE,wBAAsB,oBAAoB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+E/F"}
|
package/dist/middleware/html.js
CHANGED
|
@@ -5,26 +5,24 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* HTML injection middleware for ng serve.
|
|
8
|
-
* Intercepts
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Intercepts SPA navigation responses and injects the Live Preview script,
|
|
9
|
+
* SFDC_ENV global, and base href. Skips static files and /services/ paths.
|
|
10
|
+
* In design mode (SF_DESIGN_MODE) it also injects the runtime <script> tag
|
|
11
|
+
* (proxy.ts serves that script).
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
+
* Runs BEFORE the proxy middleware (wrapping must happen first).
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* createHtmlMiddleware is the res/req plumbing that buffers the response and feeds
|
|
17
|
-
* it through that transform. The transformer is intentionally not part of the
|
|
18
|
-
* package's public API — it is only ever consumed by this middleware.
|
|
15
|
+
* Split into a pure createIndexHtmlTransformer (string→string, unit-testable)
|
|
16
|
+
* and createHtmlMiddleware (req/res plumbing). The transformer is not public API.
|
|
19
17
|
*/
|
|
20
18
|
import { getOrgInfo } from "@salesforce/ui-bundle/app";
|
|
21
19
|
import { injectLivePreviewScript } from "@salesforce/ui-bundle/proxy";
|
|
22
|
-
import {
|
|
20
|
+
import { DESIGN_MODE_HYDRATE_PATH, DESIGN_MODE_SCRIPT_PATH } from "./proxy.js";
|
|
21
|
+
import { getAppName, getCodeBuilderBasePath, getNamespace, getPort } from "../utils.js";
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* always injects — there is no build-time caller to guard against.
|
|
23
|
+
* Builds a pure HTML transform bound to the current env (basePath resolved once
|
|
24
|
+
* from CODE_BUILDER_FRAMEWORK_PROXY_URI or "/"). Only the ng serve middleware
|
|
25
|
+
* calls it, so it always injects.
|
|
28
26
|
*/
|
|
29
27
|
async function createIndexHtmlTransformer(options = {}) {
|
|
30
28
|
const port = getPort();
|
|
@@ -32,7 +30,14 @@ async function createIndexHtmlTransformer(options = {}) {
|
|
|
32
30
|
const isCodeBuilder = !!codeBuilderProxyUrl;
|
|
33
31
|
const basePath = isCodeBuilder ? getCodeBuilderBasePath(codeBuilderProxyUrl, port) : "/";
|
|
34
32
|
const apiPath = basePath;
|
|
35
|
-
//
|
|
33
|
+
// App identity for SFDC_ENV (namespace + appName). Resolved from the app root
|
|
34
|
+
// (cwd — where ng serve runs), matching the server injector so local dev sees
|
|
35
|
+
// the same values a deployed bundle would.
|
|
36
|
+
const rootPath = process.cwd();
|
|
37
|
+
const namespace = getNamespace(rootPath);
|
|
38
|
+
const appName = getAppName(rootPath);
|
|
39
|
+
const designMode = process.env.SF_DESIGN_MODE === "true";
|
|
40
|
+
// Org URL for SFDC_ENV (Lightning Out, etc.)
|
|
36
41
|
let orgUrl;
|
|
37
42
|
try {
|
|
38
43
|
const orgInfo = await getOrgInfo(options.orgAlias);
|
|
@@ -47,10 +52,8 @@ async function createIndexHtmlTransformer(options = {}) {
|
|
|
47
52
|
return (html) => {
|
|
48
53
|
// 1. Live Preview script (enables VS Code extension communication)
|
|
49
54
|
html = injectLivePreviewScript(html);
|
|
50
|
-
// 2.
|
|
51
|
-
// If a base tag exists, replace it; otherwise inject after <head> (or skip if no <head>)
|
|
55
|
+
// 2. <base href>: replace an existing tag, else inject after <head>.
|
|
52
56
|
const baseHref = basePath.endsWith("/") ? basePath : `${basePath}/`;
|
|
53
|
-
// Match any <base ...> tag (single/double quotes, attributes in any order).
|
|
54
57
|
const baseTagRegex = /<base\b[^>]*>/i;
|
|
55
58
|
if (baseTagRegex.test(html)) {
|
|
56
59
|
html = html.replace(baseTagRegex, `<base href="${baseHref}">`);
|
|
@@ -58,12 +61,38 @@ async function createIndexHtmlTransformer(options = {}) {
|
|
|
58
61
|
else if (/<head(\s[^>]*)?>/i.test(html)) {
|
|
59
62
|
html = html.replace(/<head(\s[^>]*)?>/i, (match) => `${match}\n <base href="${baseHref}">`);
|
|
60
63
|
}
|
|
61
|
-
// 3. SFDC_ENV global (basePath + apiPath + orgUrl)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
// 3. SFDC_ENV global (basePath + apiPath + orgUrl + app identity).
|
|
65
|
+
// JSON.stringify escapes quotes/backslashes in every value (e.g. an
|
|
66
|
+
// appName derived from a dir name), so no single field can break the
|
|
67
|
+
// emitted SFDC_ENV object. It does NOT escape `<`, so we also escape it
|
|
68
|
+
// to `<` to prevent a `</script>` in a value from closing this
|
|
69
|
+
// inline <script> element early.
|
|
70
|
+
const sfdcEnv = { basePath, apiPath };
|
|
71
|
+
if (orgUrl)
|
|
72
|
+
sfdcEnv.orgUrl = orgUrl;
|
|
73
|
+
if (namespace)
|
|
74
|
+
sfdcEnv.namespace = namespace;
|
|
75
|
+
if (appName)
|
|
76
|
+
sfdcEnv.appName = appName;
|
|
77
|
+
const sfdcEnvLiteral = JSON.stringify(sfdcEnv).replace(/</g, "\\u003c");
|
|
78
|
+
const sfdcEnvScript = `<script>(function() { globalThis.SFDC_ENV = ${sfdcEnvLiteral}; })();</script>`;
|
|
64
79
|
if (html.includes("</head>")) {
|
|
65
80
|
html = html.replace("</head>", ` ${sfdcEnvScript}\n</head>`);
|
|
66
81
|
}
|
|
82
|
+
// 4. Design-mode scripts, before </body> (else </head>). Hydrate first so
|
|
83
|
+
// its attributes exist before the interactions runtime reads them.
|
|
84
|
+
if (designMode) {
|
|
85
|
+
const hydrateSrc = `${baseHref}${DESIGN_MODE_HYDRATE_PATH.replace(/^\//, "")}`;
|
|
86
|
+
const scriptSrc = `${baseHref}${DESIGN_MODE_SCRIPT_PATH.replace(/^\//, "")}`;
|
|
87
|
+
const designScripts = `<script type="module" src="${hydrateSrc}"></script>\n` +
|
|
88
|
+
` <script type="module" src="${scriptSrc}"></script>`;
|
|
89
|
+
if (html.includes("</body>")) {
|
|
90
|
+
html = html.replace("</body>", ` ${designScripts}\n</body>`);
|
|
91
|
+
}
|
|
92
|
+
else if (html.includes("</head>")) {
|
|
93
|
+
html = html.replace("</head>", ` ${designScripts}\n</head>`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
67
96
|
return html;
|
|
68
97
|
};
|
|
69
98
|
}
|
|
@@ -4,5 +4,7 @@
|
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
6
|
import type { Middleware, SalesforceOptions } from "../types.ts";
|
|
7
|
+
export declare const DESIGN_MODE_SCRIPT_PATH = "/_sfdc/design-mode-interactions.js";
|
|
8
|
+
export declare const DESIGN_MODE_HYDRATE_PATH = "/_sfdc/design-mode-hydrate.js";
|
|
7
9
|
export declare function createProxyMiddleware(options?: SalesforceOptions): Promise<Middleware>;
|
|
8
10
|
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/middleware/proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/middleware/proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkBH,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIjE,eAAO,MAAM,uBAAuB,uCAAuC,CAAC;AAC5E,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAsBxE,wBAAsB,qBAAqB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgGhG"}
|
package/dist/middleware/proxy.js
CHANGED
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Proxy middleware factory.
|
|
8
|
-
* Forwards /services/*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Forwards /services/* to the connected org with auth; watches ui-bundle.json
|
|
9
|
+
* and rebuilds the handler on route changes. Ahead of proxying it serves a few
|
|
10
|
+
* local responses (health check, and in design mode the hydrate + interactions
|
|
11
|
+
* scripts). Runs AFTER the HTML middleware.
|
|
12
12
|
*/
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
14
|
import { loadManifest, getOrgInfo } from "@salesforce/ui-bundle/app";
|
|
15
15
|
import { createProxyHandler } from "@salesforce/ui-bundle/proxy";
|
|
16
|
+
import { getRuntimeScriptContent } from "@salesforce/ui-design-mode/runtime";
|
|
16
17
|
import { watch } from "chokidar";
|
|
18
|
+
import { DESIGN_MODE_HYDRATE_SCRIPT } from "../design-mode-hydrate.js";
|
|
17
19
|
import { getCodeBuilderBasePath, getPort } from "../utils.js";
|
|
20
|
+
// Served paths (html.ts imports these so the two files can't drift).
|
|
21
|
+
export const DESIGN_MODE_SCRIPT_PATH = "/_sfdc/design-mode-interactions.js";
|
|
22
|
+
export const DESIGN_MODE_HYDRATE_PATH = "/_sfdc/design-mode-hydrate.js";
|
|
18
23
|
function buildHandler(manifest, orgInfo, options) {
|
|
19
24
|
const port = getPort();
|
|
20
25
|
const codeBuilderProxyUrl = process.env.CODE_BUILDER_FRAMEWORK_PROXY_URI;
|
|
@@ -35,6 +40,10 @@ export async function createProxyMiddleware(options = {}) {
|
|
|
35
40
|
let cachedOrgInfo;
|
|
36
41
|
let currentHandler;
|
|
37
42
|
const manifestPath = resolve(process.cwd(), "ui-bundle.json");
|
|
43
|
+
// Read the pre-built runtime IIFE once; null = ui-design-mode not built
|
|
44
|
+
// (→ 404 below, server still boots). Inert unless in design mode.
|
|
45
|
+
const designMode = process.env.SF_DESIGN_MODE === "true";
|
|
46
|
+
const designScript = designMode ? getRuntimeScriptContent() : null;
|
|
38
47
|
if (!cachedManifest) {
|
|
39
48
|
cachedManifest = await loadManifest(manifestPath);
|
|
40
49
|
}
|
|
@@ -71,6 +80,28 @@ export async function createProxyMiddleware(options = {}) {
|
|
|
71
80
|
res.end();
|
|
72
81
|
return;
|
|
73
82
|
}
|
|
83
|
+
// Design-mode scripts — serve locally (Cache-Control: no-store so rebuilds are
|
|
84
|
+
// always picked up). endsWith tolerates a Code Builder base-path prefix.
|
|
85
|
+
if (designMode) {
|
|
86
|
+
const url = req.url?.split("?")[0] ?? "/";
|
|
87
|
+
if (url.endsWith(DESIGN_MODE_HYDRATE_PATH)) {
|
|
88
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
89
|
+
res.setHeader("Cache-Control", "no-store");
|
|
90
|
+
res.end(DESIGN_MODE_HYDRATE_SCRIPT);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (url.endsWith(DESIGN_MODE_SCRIPT_PATH)) {
|
|
94
|
+
if (designScript === null) {
|
|
95
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
96
|
+
res.end("design-mode runtime not built");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
100
|
+
res.setHeader("Cache-Control", "no-store");
|
|
101
|
+
res.end(designScript);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
74
105
|
if (currentHandler) {
|
|
75
106
|
try {
|
|
76
107
|
await currentHandler(req, res, next);
|
package/dist/utils.d.ts
CHANGED
|
@@ -5,7 +5,29 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const DEFAULT_API_VERSION = "65.0";
|
|
7
7
|
export declare const DEFAULT_PORT = 5173;
|
|
8
|
+
export declare const DEFAULT_NAMESPACE = "c";
|
|
8
9
|
export declare function getPort(): number;
|
|
9
10
|
/** Extract path from Code Builder proxy URI. Same logic as vite-plugin-ui-bundle. */
|
|
10
11
|
export declare function getCodeBuilderBasePath(proxyUri: string, port: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Get the app name from a *.uibundle-meta.xml file in the root path.
|
|
14
|
+
* Falls back to the directory name if no matching file is found.
|
|
15
|
+
* Same logic as vite-plugin-ui-bundle.
|
|
16
|
+
*
|
|
17
|
+
* @param rootPath - The root directory to search for the uibundle-meta.xml file
|
|
18
|
+
* @returns The app name
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAppName(rootPath: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get the registered namespace for the app by traversing up from the given
|
|
23
|
+
* directory to locate an sfdx-project.json and reading its `namespace` field.
|
|
24
|
+
* Same logic as vite-plugin-ui-bundle.
|
|
25
|
+
*
|
|
26
|
+
* Falls back to the default namespace ("c") when no sfdx-project.json is found,
|
|
27
|
+
* when it declares no (or an empty) namespace, or when it cannot be read/parsed.
|
|
28
|
+
*
|
|
29
|
+
* @param startDir - The directory to start searching from (the UI Bundle root)
|
|
30
|
+
* @returns The registered namespace, or "c" when none is found
|
|
31
|
+
*/
|
|
32
|
+
export declare function getNamespace(startDir: string): string;
|
|
11
33
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAE1C,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ7E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwBrD"}
|
package/dist/utils.js
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { basename, dirname, join } from "node:path";
|
|
6
8
|
export const DEFAULT_API_VERSION = "65.0";
|
|
7
9
|
export const DEFAULT_PORT = 5173;
|
|
10
|
+
export const DEFAULT_NAMESPACE = "c";
|
|
8
11
|
export function getPort() {
|
|
9
12
|
return parseInt(process.env.SF_UIBUNDLE_PORT || DEFAULT_PORT.toString(), 10);
|
|
10
13
|
}
|
|
@@ -19,3 +22,62 @@ export function getCodeBuilderBasePath(proxyUri, port) {
|
|
|
19
22
|
return `/absproxy/${port}`;
|
|
20
23
|
}
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the app name from a *.uibundle-meta.xml file in the root path.
|
|
27
|
+
* Falls back to the directory name if no matching file is found.
|
|
28
|
+
* Same logic as vite-plugin-ui-bundle.
|
|
29
|
+
*
|
|
30
|
+
* @param rootPath - The root directory to search for the uibundle-meta.xml file
|
|
31
|
+
* @returns The app name
|
|
32
|
+
*/
|
|
33
|
+
export function getAppName(rootPath) {
|
|
34
|
+
try {
|
|
35
|
+
const files = readdirSync(rootPath);
|
|
36
|
+
const metaFile = files.find((f) => f.endsWith(".uibundle-meta.xml"));
|
|
37
|
+
if (metaFile) {
|
|
38
|
+
// Extract name from "myapp.uibundle-meta.xml" -> "myapp"
|
|
39
|
+
return metaFile.replace(".uibundle-meta.xml", "");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Fall through to directory name
|
|
44
|
+
}
|
|
45
|
+
return basename(rootPath);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the registered namespace for the app by traversing up from the given
|
|
49
|
+
* directory to locate an sfdx-project.json and reading its `namespace` field.
|
|
50
|
+
* Same logic as vite-plugin-ui-bundle.
|
|
51
|
+
*
|
|
52
|
+
* Falls back to the default namespace ("c") when no sfdx-project.json is found,
|
|
53
|
+
* when it declares no (or an empty) namespace, or when it cannot be read/parsed.
|
|
54
|
+
*
|
|
55
|
+
* @param startDir - The directory to start searching from (the UI Bundle root)
|
|
56
|
+
* @returns The registered namespace, or "c" when none is found
|
|
57
|
+
*/
|
|
58
|
+
export function getNamespace(startDir) {
|
|
59
|
+
let dir = startDir;
|
|
60
|
+
// Walk up until dirname stops changing (the filesystem root).
|
|
61
|
+
for (let parent = dirname(dir);; dir = parent, parent = dirname(dir)) {
|
|
62
|
+
const sfdxProjectPath = join(dir, "sfdx-project.json");
|
|
63
|
+
// The nearest sfdx-project.json is authoritative: once found, resolve from
|
|
64
|
+
// it (or fall back to the default) rather than inheriting an ancestor's
|
|
65
|
+
// namespace.
|
|
66
|
+
if (existsSync(sfdxProjectPath)) {
|
|
67
|
+
try {
|
|
68
|
+
const namespace = JSON.parse(readFileSync(sfdxProjectPath, "utf-8")).namespace;
|
|
69
|
+
if (typeof namespace === "string" && namespace.trim()) {
|
|
70
|
+
return namespace.trim();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Unreadable or invalid JSON: fall through to the default.
|
|
75
|
+
}
|
|
76
|
+
return DEFAULT_NAMESPACE;
|
|
77
|
+
}
|
|
78
|
+
if (parent === dir) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return DEFAULT_NAMESPACE;
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/angular-plugin-ui-bundle",
|
|
3
3
|
"description": "Angular CLI plugin for Salesforce UI Bundles",
|
|
4
|
-
"version": "11.
|
|
4
|
+
"version": "11.62.0",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"test:coverage": "vitest run --coverage"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@salesforce/ui-bundle": "^11.
|
|
32
|
+
"@salesforce/ui-bundle": "^11.62.0",
|
|
33
|
+
"@salesforce/ui-design-mode": "^11.62.0",
|
|
33
34
|
"chokidar": "^4.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|