@salesforce/vite-plugin-webapp-experimental 1.81.0 → 1.82.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.
@@ -19,7 +19,7 @@
19
19
  declare const reactDesignTimeLocatorBabelPlugin: (api: object, options: Record<string, any> | null | undefined, dirname: string) => {
20
20
  name: string;
21
21
  visitor: {
22
- JSXElement(this: import("@babel/core").PluginPass, path: any, state: any): void;
22
+ JSXElement(this: import('@babel/core').PluginPass, path: any, state: any): void;
23
23
  };
24
24
  };
25
25
  export default reactDesignTimeLocatorBabelPlugin;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- import type { Plugin } from "vite";
1
+ import { Plugin } from 'vite';
7
2
  export interface PluginOptions {
8
3
  /** Salesforce org alias */
9
4
  orgAlias?: string;
package/dist/index.js CHANGED
@@ -1,203 +1,312 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
1
  import { loadManifest, getOrgInfo } from "@salesforce/webapp-experimental/app";
7
2
  import { getDesignModeScriptContent } from "@salesforce/webapp-experimental/design";
8
3
  import { createProxyHandler, injectLivePreviewScript } from "@salesforce/webapp-experimental/proxy";
9
- import reactDesignTimeLocatorBabelPlugin from "./babel/reactDesignTimeLocatorBabelPlugin.js";
10
- import { getBasePath, getCodeBuilderBasePath, getPort, getDevServerTarget, DEFAULT_API_VERSION, DEFAULT_PORT, } from "./utils.js";
4
+ import { declare } from "@babel/helper-plugin-utils";
5
+ import { readdirSync } from "node:fs";
6
+ import { basename } from "node:path";
7
+ const reactDesignTimeLocatorBabelPlugin = declare((api) => {
8
+ api.assertVersion(7);
9
+ const t = api.types;
10
+ function isReactFragmentName(nameNode) {
11
+ if (t.isJSXIdentifier(nameNode) && nameNode.name === "Fragment") return true;
12
+ if (t.isJSXMemberExpression(nameNode) && t.isJSXIdentifier(nameNode.object, { name: "React" }) && t.isJSXIdentifier(nameNode.property, { name: "Fragment" })) {
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ function hasAttr(openingElement, name) {
18
+ return openingElement.attributes.some((attr) => {
19
+ return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === name;
20
+ });
21
+ }
22
+ function addAttr(openingElement, name, value) {
23
+ if (hasAttr(openingElement, name)) return;
24
+ openingElement.attributes.push(
25
+ t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(String(value)))
26
+ );
27
+ }
28
+ return {
29
+ name: "babel-plugin-react-design-time-locator",
30
+ visitor: {
31
+ JSXElement(path, state) {
32
+ const openingElement = path.node.openingElement;
33
+ if (isReactFragmentName(openingElement.name)) return;
34
+ if (!t.isJSXIdentifier(openingElement.name) && !t.isJSXMemberExpression(openingElement.name)) {
35
+ return;
36
+ }
37
+ let tagHead = "";
38
+ if (t.isJSXIdentifier(openingElement.name)) {
39
+ tagHead = openingElement.name.name;
40
+ } else if (t.isJSXMemberExpression(openingElement.name)) {
41
+ tagHead = openingElement.name.property?.name ?? "";
42
+ }
43
+ if (!tagHead) return;
44
+ const filename = state?.file?.opts?.filename;
45
+ if (!filename || filename.includes("node_modules")) return;
46
+ const excludePaths = state.opts?.excludePaths ?? [];
47
+ if (excludePaths.some((p) => filename.includes(p))) return;
48
+ const children = path.node.children ?? [];
49
+ const relevantChildren = children.filter((child) => {
50
+ return !(t.isJSXText(child) && child.value.trim() === "");
51
+ });
52
+ let textType = "none";
53
+ if (relevantChildren.length === 1) {
54
+ const child = relevantChildren[0];
55
+ if (t.isJSXText(child)) {
56
+ textType = "static";
57
+ } else if (t.isJSXExpressionContainer(child)) {
58
+ textType = "dynamic";
59
+ } else {
60
+ textType = "element";
61
+ }
62
+ } else if (relevantChildren.length > 1) {
63
+ textType = "mixed";
64
+ }
65
+ const loc = path.node.loc;
66
+ if (loc?.start) {
67
+ const source = `${filename}:${loc.start.line}:${loc.start.column}`;
68
+ addAttr(openingElement, "data-source-file", source);
69
+ }
70
+ addAttr(openingElement, "data-text-type", textType);
71
+ }
72
+ }
73
+ };
74
+ });
75
+ const DEFAULT_PORT = 5173;
76
+ const DEFAULT_API_VERSION = "65.0";
77
+ const DEFAULT_NAMESPACE = "c";
78
+ function getAppName(rootPath) {
79
+ try {
80
+ const files = readdirSync(rootPath);
81
+ const metaFile = files.find((f) => f.endsWith(".webapplication-meta.xml"));
82
+ if (metaFile) {
83
+ return metaFile.replace(".webapplication-meta.xml", "");
84
+ }
85
+ } catch {
86
+ }
87
+ return basename(rootPath);
88
+ }
89
+ function getNamespace() {
90
+ return DEFAULT_NAMESPACE;
91
+ }
92
+ function buildProdBasePath(namespace, appName) {
93
+ return `/lwr/application/ai/${namespace}-${appName}`;
94
+ }
95
+ function getCodeBuilderBasePath(proxyUri, port) {
96
+ try {
97
+ const url = new URL(proxyUri.replace("{{port}}", port.toString()));
98
+ return url.pathname;
99
+ } catch (error) {
100
+ console.error("Failed to parse CODE_BUILDER_FRAMEWORK_PROXY_URI:", error);
101
+ return `/absproxy/${port}`;
102
+ }
103
+ }
104
+ function getBasePath(mode, codeBuilderProxyUrl2, port, rootPath) {
105
+ const isProd = mode === "production";
106
+ if (isProd) {
107
+ const appName = getAppName(rootPath);
108
+ const namespace = getNamespace();
109
+ return buildProdBasePath(namespace, appName);
110
+ }
111
+ if (!codeBuilderProxyUrl2) {
112
+ return "";
113
+ }
114
+ return getCodeBuilderBasePath(codeBuilderProxyUrl2, port);
115
+ }
116
+ function getDevServerTarget(codeBuilderProxyUrl2, port) {
117
+ if (codeBuilderProxyUrl2) {
118
+ return getCodeBuilderBasePath(codeBuilderProxyUrl2, port);
119
+ }
120
+ return `http://localhost:${port}`;
121
+ }
122
+ function getPort() {
123
+ return parseInt(process.env.SF_WEBAPP_PORT || DEFAULT_PORT.toString(), 10);
124
+ }
11
125
  const codeBuilderProxyUrl = process.env.CODE_BUILDER_FRAMEWORK_PROXY_URI;
12
- export default function webappsPlugin(options = {}) {
13
- const proxyOptions = {
14
- debug: options.debug ?? false,
15
- };
16
- let orgInfo;
17
- let manifest;
18
- let proxyHandler;
19
- // Allow tests or callers to force design mode without needing to run the `config` hook.
20
- let designModeEnabled = options.designMode ?? false;
21
- const corePlugin = {
22
- name: "@salesforce/vite-plugin-webapp-experimental:core",
23
- enforce: "pre",
24
- async config(config, env) {
25
- const rootPath = config.root ?? process.cwd();
26
- // Note: At this stage we may not have the correct manifest path yet,
27
- // so we only load the org info to get the API version
28
- let version = DEFAULT_API_VERSION;
29
- try {
30
- orgInfo = await getOrgInfo(options.orgAlias);
31
- version = orgInfo?.apiVersion || DEFAULT_API_VERSION;
32
- if (options.debug) {
33
- console.log(`[webapps-plugin] Using Salesforce API version: ${version}`);
34
- }
35
- }
36
- catch {
37
- version = DEFAULT_API_VERSION;
38
- }
39
- const isCodeBuilder = !!codeBuilderProxyUrl;
40
- const define = {
41
- __SF_API_VERSION__: JSON.stringify(version),
42
- __SF_SERVER_BASE_PATH__: JSON.stringify(""),
43
- };
44
- if (isCodeBuilder && env.mode !== "production") {
45
- const basePath = getCodeBuilderBasePath(codeBuilderProxyUrl, getPort());
46
- define["__SF_SERVER_BASE_PATH__"] = JSON.stringify(basePath);
47
- }
48
- return {
49
- define,
50
- base: getBasePath(env.mode, codeBuilderProxyUrl, getPort(), rootPath),
51
- server: {
52
- port: getPort(),
53
- // Code Builder specific configuration
54
- ...(isCodeBuilder && {
55
- allowedHosts: true,
56
- strictPort: true,
57
- }),
58
- },
59
- };
60
- },
61
- async configResolved(config) {
62
- try {
63
- const rootPath = config.root ?? process.cwd();
64
- manifest = await loadManifest(`${rootPath}/webapplication.json`);
65
- const target = getDevServerTarget(codeBuilderProxyUrl, config.server.port ?? DEFAULT_PORT);
66
- const basePath = getBasePath(config.mode, codeBuilderProxyUrl, getPort(), rootPath);
67
- proxyHandler = createProxyHandler(manifest, orgInfo, target, basePath, proxyOptions);
68
- }
69
- catch (error) {
70
- console.error(`[webapps-plugin] Initialization failed:`, error);
71
- }
72
- },
73
- configureServer(server) {
74
- // Must run BEFORE Vite's internal middlewares (hence not returning a post-hook function)
75
- server.middlewares.use(async (req, res, next) => {
76
- if (proxyHandler) {
77
- try {
78
- await proxyHandler(req, res, next);
79
- }
80
- catch (error) {
81
- console.error("[webapps-plugin] Proxy handler error:", error);
82
- next();
83
- }
84
- }
85
- else {
86
- if (req.url?.startsWith("/services")) {
87
- res.writeHead(503, { "Content-Type": "application/json" });
88
- res.end(JSON.stringify({
89
- error: "SERVICE_UNAVAILABLE",
90
- message: "Proxy not initialized.",
91
- }));
92
- return;
93
- }
94
- next();
95
- }
96
- });
97
- },
98
- async handleHotUpdate({ file, server }) {
99
- if (file.endsWith("webapplication.json")) {
100
- const updatedManifest = await loadManifest(file);
101
- if (updatedManifest) {
102
- manifest = updatedManifest;
103
- const rootPath = server.config.root ?? process.cwd();
104
- const target = getDevServerTarget(codeBuilderProxyUrl, server.config.server.port ?? DEFAULT_PORT);
105
- const basePath = getBasePath(server.config.mode, codeBuilderProxyUrl, getPort(), rootPath);
106
- proxyHandler = createProxyHandler(manifest, orgInfo, target, basePath, proxyOptions);
107
- server.ws.send({
108
- type: "full-reload",
109
- path: "*",
110
- });
111
- }
112
- }
113
- },
114
- };
115
- const designPlugin = {
116
- name: "@salesforce/vite-plugin-webapp-experimental:design",
117
- enforce: "pre",
118
- config(_config, env) {
119
- designModeEnabled = options.designMode ?? env.mode === "design";
120
- },
121
- configureServer(server) {
122
- // Intercepts design-mode script requests before the core proxy middleware.
123
- server.middlewares.use(async (req, res, next) => {
124
- if (!designModeEnabled) {
125
- next();
126
- return;
127
- }
128
- const urlPath = stripUrlQuery(req.url);
129
- if (urlPath === "/_sfdc/design-mode-interactions.js") {
130
- try {
131
- const script = getDesignModeScriptContent();
132
- if (script !== null) {
133
- res.setHeader("Content-Type", "application/javascript");
134
- res.setHeader("Cache-Control", "no-cache");
135
- res.end(script);
136
- return;
137
- }
138
- console.error(`[webapps-plugin] Design mode script not found. Run 'npm run build:design' in @salesforce/webapp-experimental.`);
139
- res.writeHead(404);
140
- res.end("Design mode script not found");
141
- return;
142
- }
143
- catch (error) {
144
- console.error("[webapps-plugin] Error serving design mode script:", error);
145
- res.writeHead(500);
146
- res.end("Error loading design mode script");
147
- return;
148
- }
149
- }
150
- next();
151
- });
152
- },
153
- async transform(code, id) {
154
- if (!designModeEnabled)
155
- return null;
156
- // Strip Vite query string (e.g. ?import, ?v=hash)
157
- const filepath = id.split("?", 1)[0] ?? id;
158
- // Best-effort: only handle React source files where JSX loc info matters.
159
- const isTsx = filepath.endsWith(".tsx");
160
- const isJsx = filepath.endsWith(".jsx");
161
- if (!isTsx && !isJsx)
162
- return null;
163
- if (filepath.includes("node_modules"))
164
- return null;
165
- const excludePaths = options.designModeExcludePaths ?? ["/components/ui/"];
166
- if (excludePaths.some((p) => filepath.includes(p)))
167
- return null;
168
- const { transformAsync } = await import("@babel/core");
169
- const result = await transformAsync(code, {
170
- filename: filepath,
171
- babelrc: false,
172
- configFile: false,
173
- sourceMaps: true,
174
- parserOpts: {
175
- sourceType: "module",
176
- plugins: isTsx ? ["jsx", "typescript"] : ["jsx"],
177
- },
178
- plugins: [[reactDesignTimeLocatorBabelPlugin, { excludePaths }]],
179
- });
180
- if (!result?.code)
181
- return null;
182
- return { code: result.code, map: result.map };
183
- },
184
- transformIndexHtml(html) {
185
- // Inject Live Preview script (error handling, copy/paste, postMessage bridge)
186
- html = injectLivePreviewScript(html);
187
- if (designModeEnabled) {
188
- const designScriptTag = '<script src="/_sfdc/design-mode-interactions.js"></script>';
189
- return html.replace("</body>", `${designScriptTag}\n</body>`);
126
+ function webappsPlugin(options = {}) {
127
+ const proxyOptions = {
128
+ debug: options.debug ?? false
129
+ };
130
+ let orgInfo;
131
+ let manifest;
132
+ let proxyHandler;
133
+ let designModeEnabled = options.designMode ?? false;
134
+ const corePlugin = {
135
+ name: "@salesforce/vite-plugin-webapp-experimental:core",
136
+ enforce: "pre",
137
+ async config(config, env) {
138
+ const rootPath = config.root ?? process.cwd();
139
+ let version = DEFAULT_API_VERSION;
140
+ try {
141
+ orgInfo = await getOrgInfo(options.orgAlias);
142
+ version = orgInfo?.apiVersion || DEFAULT_API_VERSION;
143
+ if (options.debug) {
144
+ console.log(`[webapps-plugin] Using Salesforce API version: ${version}`);
145
+ }
146
+ } catch {
147
+ version = DEFAULT_API_VERSION;
148
+ }
149
+ const isCodeBuilder = !!codeBuilderProxyUrl;
150
+ const define = {
151
+ __SF_API_VERSION__: JSON.stringify(version),
152
+ __SF_SERVER_BASE_PATH__: JSON.stringify("")
153
+ };
154
+ if (isCodeBuilder && env.mode !== "production") {
155
+ const basePath = getCodeBuilderBasePath(codeBuilderProxyUrl, getPort());
156
+ define["__SF_SERVER_BASE_PATH__"] = JSON.stringify(basePath);
157
+ }
158
+ return {
159
+ define,
160
+ base: getBasePath(env.mode, codeBuilderProxyUrl, getPort(), rootPath),
161
+ server: {
162
+ port: getPort(),
163
+ // Code Builder specific configuration
164
+ ...isCodeBuilder && {
165
+ allowedHosts: true,
166
+ strictPort: true
167
+ }
168
+ }
169
+ };
170
+ },
171
+ async configResolved(config) {
172
+ try {
173
+ const rootPath = config.root ?? process.cwd();
174
+ manifest = await loadManifest(`${rootPath}/webapplication.json`);
175
+ const target = getDevServerTarget(codeBuilderProxyUrl, config.server.port ?? DEFAULT_PORT);
176
+ const basePath = getBasePath(config.mode, codeBuilderProxyUrl, getPort(), rootPath);
177
+ proxyHandler = createProxyHandler(manifest, orgInfo, target, basePath, proxyOptions);
178
+ } catch (error) {
179
+ console.error(`[webapps-plugin] Initialization failed:`, error);
180
+ }
181
+ },
182
+ configureServer(server) {
183
+ server.middlewares.use(async (req, res, next) => {
184
+ if (proxyHandler) {
185
+ try {
186
+ await proxyHandler(req, res, next);
187
+ } catch (error) {
188
+ console.error("[webapps-plugin] Proxy handler error:", error);
189
+ next();
190
+ }
191
+ } else {
192
+ if (req.url?.startsWith("/services")) {
193
+ res.writeHead(503, { "Content-Type": "application/json" });
194
+ res.end(
195
+ JSON.stringify({
196
+ error: "SERVICE_UNAVAILABLE",
197
+ message: "Proxy not initialized."
198
+ })
199
+ );
200
+ return;
201
+ }
202
+ next();
203
+ }
204
+ });
205
+ },
206
+ async handleHotUpdate({ file, server }) {
207
+ if (file.endsWith("webapplication.json")) {
208
+ const updatedManifest = await loadManifest(file);
209
+ if (updatedManifest) {
210
+ manifest = updatedManifest;
211
+ const rootPath = server.config.root ?? process.cwd();
212
+ const target = getDevServerTarget(
213
+ codeBuilderProxyUrl,
214
+ server.config.server.port ?? DEFAULT_PORT
215
+ );
216
+ const basePath = getBasePath(
217
+ server.config.mode,
218
+ codeBuilderProxyUrl,
219
+ getPort(),
220
+ rootPath
221
+ );
222
+ proxyHandler = createProxyHandler(manifest, orgInfo, target, basePath, proxyOptions);
223
+ server.ws.send({
224
+ type: "full-reload",
225
+ path: "*"
226
+ });
227
+ }
228
+ }
229
+ }
230
+ };
231
+ const designPlugin = {
232
+ name: "@salesforce/vite-plugin-webapp-experimental:design",
233
+ enforce: "pre",
234
+ config(_config, env) {
235
+ designModeEnabled = options.designMode ?? env.mode === "design";
236
+ },
237
+ configureServer(server) {
238
+ server.middlewares.use(async (req, res, next) => {
239
+ if (!designModeEnabled) {
240
+ next();
241
+ return;
242
+ }
243
+ const urlPath = stripUrlQuery(req.url);
244
+ if (urlPath === "/_sfdc/design-mode-interactions.js") {
245
+ try {
246
+ const script = getDesignModeScriptContent();
247
+ if (script !== null) {
248
+ res.setHeader("Content-Type", "application/javascript");
249
+ res.setHeader("Cache-Control", "no-cache");
250
+ res.end(script);
251
+ return;
190
252
  }
191
- return html;
253
+ console.error(
254
+ `[webapps-plugin] Design mode script not found. Run 'npm run build:design' in @salesforce/webapp-experimental.`
255
+ );
256
+ res.writeHead(404);
257
+ res.end("Design mode script not found");
258
+ return;
259
+ } catch (error) {
260
+ console.error("[webapps-plugin] Error serving design mode script:", error);
261
+ res.writeHead(500);
262
+ res.end("Error loading design mode script");
263
+ return;
264
+ }
265
+ }
266
+ next();
267
+ });
268
+ },
269
+ async transform(code, id) {
270
+ if (!designModeEnabled) return null;
271
+ const filepath = id.split("?", 1)[0] ?? id;
272
+ const isTsx = filepath.endsWith(".tsx");
273
+ const isJsx = filepath.endsWith(".jsx");
274
+ if (!isTsx && !isJsx) return null;
275
+ if (filepath.includes("node_modules")) return null;
276
+ const excludePaths = options.designModeExcludePaths ?? ["/components/ui/"];
277
+ if (excludePaths.some((p) => filepath.includes(p))) return null;
278
+ const { transformAsync } = await import("@babel/core");
279
+ const result = await transformAsync(code, {
280
+ filename: filepath,
281
+ babelrc: false,
282
+ configFile: false,
283
+ sourceMaps: true,
284
+ parserOpts: {
285
+ sourceType: "module",
286
+ plugins: isTsx ? ["jsx", "typescript"] : ["jsx"]
192
287
  },
193
- };
194
- // Design plugin is listed first so its middleware intercepts design-mode
195
- // script requests before the core proxy middleware.
196
- return [designPlugin, corePlugin];
288
+ plugins: [[reactDesignTimeLocatorBabelPlugin, { excludePaths }]]
289
+ });
290
+ if (!result?.code) return null;
291
+ return { code: result.code, map: result.map };
292
+ },
293
+ transformIndexHtml(html) {
294
+ html = injectLivePreviewScript(html);
295
+ if (designModeEnabled) {
296
+ const designScriptTag = '<script src="/_sfdc/design-mode-interactions.js"><\/script>';
297
+ return html.replace("</body>", `${designScriptTag}
298
+ </body>`);
299
+ }
300
+ return html;
301
+ }
302
+ };
303
+ return [designPlugin, corePlugin];
197
304
  }
198
305
  function stripUrlQuery(url) {
199
- if (!url)
200
- return "";
201
- const queryIndex = url.indexOf("?");
202
- return queryIndex === -1 ? url : url.slice(0, queryIndex);
306
+ if (!url) return "";
307
+ const queryIndex = url.indexOf("?");
308
+ return queryIndex === -1 ? url : url.slice(0, queryIndex);
203
309
  }
310
+ export {
311
+ webappsPlugin as default
312
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/vite-plugin-webapp-experimental",
3
3
  "description": "[experimental] Vite plugin for Salesforce Web Applications",
4
- "version": "1.81.0",
4
+ "version": "1.82.0",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -18,9 +18,9 @@
18
18
  "dist"
19
19
  ],
20
20
  "scripts": {
21
- "build": "tsc --build",
22
- "clean": "rm -rf dist tsconfig.tsbuildinfo",
23
- "dev": "tsc --build --watch",
21
+ "build": "vite build",
22
+ "clean": "rm -rf dist",
23
+ "dev": "vite build --watch",
24
24
  "test": "vitest run",
25
25
  "test:watch": "vitest",
26
26
  "test:coverage": "vitest run --coverage"
@@ -28,12 +28,13 @@
28
28
  "dependencies": {
29
29
  "@babel/core": "^7.28.4",
30
30
  "@babel/helper-plugin-utils": "^7.28.3",
31
- "@salesforce/webapp-experimental": "^1.81.0"
31
+ "@salesforce/webapp-experimental": "^1.82.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/babel__core": "^7.20.5",
35
35
  "@types/babel__helper-plugin-utils": "^7.10.3",
36
36
  "vite": "^7.0.0",
37
+ "vite-plugin-dts": "^4.5.4",
37
38
  "vitest": "^4.0.6"
38
39
  },
39
40
  "peerDependencies": {
@@ -1,117 +0,0 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- /* eslint-disable @typescript-eslint/no-explicit-any */
7
- import { declare } from "@babel/helper-plugin-utils";
8
- /**
9
- * React design-time Babel plugin.
10
- *
11
- * Injects DOM-locating and text metadata attributes into HTML JSX elements at compile-time.
12
- *
13
- * Attributes injected (when location is available):
14
- * - data-source-file="<file_path>:<line>:<col>"
15
- *
16
- * Optional text metadata:
17
- * - data-text-type: none | static | dynamic | mixed | element
18
- *
19
- * NOTE: This is a Babel plugin. In this repo it's run via a Vite `transform` hook.
20
- */
21
- const reactDesignTimeLocatorBabelPlugin = declare((api) => {
22
- api.assertVersion(7);
23
- const t = api.types;
24
- function isReactFragmentName(nameNode) {
25
- // <Fragment> or <React.Fragment>
26
- if (t.isJSXIdentifier(nameNode) && nameNode.name === "Fragment")
27
- return true;
28
- if (t.isJSXMemberExpression(nameNode) &&
29
- t.isJSXIdentifier(nameNode.object, { name: "React" }) &&
30
- t.isJSXIdentifier(nameNode.property, { name: "Fragment" })) {
31
- return true;
32
- }
33
- return false;
34
- }
35
- function hasAttr(openingElement, name) {
36
- return openingElement.attributes.some((attr) => {
37
- return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === name;
38
- });
39
- }
40
- function addAttr(openingElement, name, value) {
41
- if (hasAttr(openingElement, name))
42
- return;
43
- openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(String(value))));
44
- }
45
- return {
46
- name: "babel-plugin-react-design-time-locator",
47
- visitor: {
48
- JSXElement(path, state) {
49
- const openingElement = path.node.openingElement;
50
- // Ignore fragments (they don't render a DOM node).
51
- if (isReactFragmentName(openingElement.name))
52
- return;
53
- // Only support common JSX name node types:
54
- // - <div> (JSXIdentifier)
55
- // - <Button> (JSXIdentifier)
56
- // - <React.Suspense> / <Mui.Button> (JSXMemberExpression)
57
- if (!t.isJSXIdentifier(openingElement.name) &&
58
- !t.isJSXMemberExpression(openingElement.name)) {
59
- return;
60
- }
61
- // Decide whether this is a native DOM element or a component usage.
62
- // We tag BOTH:
63
- // - DOM elements are tagged directly (always works)
64
- // - Components are tagged via props (works when the component forwards props, e.g. many MUI components)
65
- let tagHead = "";
66
- if (t.isJSXIdentifier(openingElement.name)) {
67
- tagHead = openingElement.name.name;
68
- }
69
- else if (t.isJSXMemberExpression(openingElement.name)) {
70
- // Member expressions are always components in React (e.g. <Foo.Bar>)
71
- tagHead = openingElement.name.property?.name ?? "";
72
- }
73
- if (!tagHead)
74
- return;
75
- const filename = state?.file?.opts?.filename;
76
- if (!filename || filename.includes("node_modules"))
77
- return;
78
- // Skip files matching excludePaths (e.g. shadcn components/ui/) so their
79
- // internal elements don't get data-source-file. The root element still
80
- // receives it from the usage site via props spread.
81
- const excludePaths = state.opts?.excludePaths ?? [];
82
- if (excludePaths.some((p) => filename.includes(p)))
83
- return;
84
- // Analyze children for text metadata
85
- const children = path.node.children ?? [];
86
- const relevantChildren = children.filter((child) => {
87
- return !(t.isJSXText(child) && child.value.trim() === "");
88
- });
89
- let textType = "none"; // none | static | dynamic | mixed | element
90
- if (relevantChildren.length === 1) {
91
- const child = relevantChildren[0];
92
- if (t.isJSXText(child)) {
93
- textType = "static";
94
- }
95
- else if (t.isJSXExpressionContainer(child)) {
96
- textType = "dynamic";
97
- }
98
- else {
99
- textType = "element";
100
- }
101
- }
102
- else if (relevantChildren.length > 1) {
103
- textType = "mixed";
104
- }
105
- // Inject location info (best-effort)
106
- const loc = path.node.loc;
107
- if (loc?.start) {
108
- const source = `${filename}:${loc.start.line}:${loc.start.column}`;
109
- addAttr(openingElement, "data-source-file", source);
110
- }
111
- // Inject text metadata
112
- addAttr(openingElement, "data-text-type", textType);
113
- },
114
- },
115
- };
116
- });
117
- export default reactDesignTimeLocatorBabelPlugin;
package/dist/utils.js DELETED
@@ -1,106 +0,0 @@
1
- /**
2
- * Copyright (c) 2026, Salesforce, Inc.,
3
- * All rights reserved.
4
- * For full license text, see the LICENSE.txt file
5
- */
6
- import { readdirSync } from "node:fs";
7
- import { basename } from "node:path";
8
- export const DEFAULT_PORT = 5173;
9
- export const DEFAULT_API_VERSION = "65.0";
10
- export const DEFAULT_NAMESPACE = "c";
11
- /**
12
- * Get the app name from a *.webapplication-meta.xml file in the root path.
13
- * Falls back to the directory name if no matching file is found.
14
- *
15
- * @param rootPath - The root directory to search for the webapplication-meta.xml file
16
- * @returns The app name
17
- */
18
- export function getAppName(rootPath) {
19
- try {
20
- const files = readdirSync(rootPath);
21
- const metaFile = files.find((f) => f.endsWith(".webapplication-meta.xml"));
22
- if (metaFile) {
23
- // Extract name from "myapp.webapplication-meta.xml" -> "myapp"
24
- return metaFile.replace(".webapplication-meta.xml", "");
25
- }
26
- }
27
- catch {
28
- // Fall through to directory name
29
- }
30
- return basename(rootPath);
31
- }
32
- /**
33
- * Get the namespace for the app.
34
- * Currently returns the default namespace ("c") since rootPath points to the
35
- * webapp directory (where vite.config lives), not the sfdx project root.
36
- *
37
- * @returns The namespace (currently always "c")
38
- */
39
- export function getNamespace() {
40
- // TODO: In the future, we could traverse up from rootPath to find sfdx-project.json
41
- return DEFAULT_NAMESPACE;
42
- }
43
- /**
44
- * Build the production base path from namespace and app name.
45
- *
46
- * @param namespace - The Salesforce namespace (e.g., "c")
47
- * @param appName - The app name (e.g., "myapp")
48
- * @returns The production base path (e.g., "/lwr/application/ai/c-myapp")
49
- */
50
- export function buildProdBasePath(namespace, appName) {
51
- return `/lwr/application/ai/${namespace}-${appName}`;
52
- }
53
- /**
54
- * Calculate the code builder base path from the proxy URI (CODE_BUILDER_FRAMEWORK_PROXY_URI) and dev server port
55
- * @param proxyUri - The full proxy URI (e.g., https://name.iad.001.sf.code-builder.platform.salesforce.com/absproxy/{{port}})
56
- * @param port - The port number to replace {{port}} with (e.g., "5173")
57
- * @returns The parsed path with port (e.g., /absproxy/5173/)
58
- */
59
- export function getCodeBuilderBasePath(proxyUri, port) {
60
- try {
61
- const url = new URL(proxyUri.replace("{{port}}", port.toString()));
62
- return url.pathname;
63
- }
64
- catch (error) {
65
- console.error("Failed to parse CODE_BUILDER_FRAMEWORK_PROXY_URI:", error);
66
- return `/absproxy/${port}`; // Default code builder proxy path
67
- }
68
- }
69
- /**
70
- * Get the base path for the webapp based on mode and environment.
71
- *
72
- * For production mode, resolves the app name and namespace from the rootPath
73
- * and returns a path like "/lwr/application/ai/{namespace}-{appName}".
74
- *
75
- * For development mode with Code Builder, returns the proxy path.
76
- * For local development, returns an empty string.
77
- *
78
- * @param mode - The Vite mode ("production", "development", etc.)
79
- * @param codeBuilderProxyUrl - The Code Builder proxy URL (if running in Code Builder)
80
- * @param port - The dev server port
81
- * @param rootPath - The project root path (used to resolve app name and namespace)
82
- * @returns The base path
83
- */
84
- export function getBasePath(mode, codeBuilderProxyUrl, port, rootPath) {
85
- const isProd = mode === "production";
86
- if (isProd) {
87
- const appName = getAppName(rootPath);
88
- const namespace = getNamespace();
89
- return buildProdBasePath(namespace, appName);
90
- }
91
- // Development mode
92
- if (!codeBuilderProxyUrl) {
93
- return "";
94
- }
95
- // A4V / Code Builder: extract path from proxy URI and include port
96
- return getCodeBuilderBasePath(codeBuilderProxyUrl, port);
97
- }
98
- export function getDevServerTarget(codeBuilderProxyUrl, port) {
99
- if (codeBuilderProxyUrl) {
100
- return getCodeBuilderBasePath(codeBuilderProxyUrl, port);
101
- }
102
- return `http://localhost:${port}`;
103
- }
104
- export function getPort() {
105
- return parseInt(process.env.SF_WEBAPP_PORT || DEFAULT_PORT.toString(), 10);
106
- }