@salesforce/webapp-experimental 0.2.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.
Files changed (59) hide show
  1. package/README.md +3 -0
  2. package/dist/api/apex.d.ts +9 -0
  3. package/dist/api/apex.d.ts.map +1 -0
  4. package/dist/api/apex.js +18 -0
  5. package/dist/api/apex.test.d.ts +2 -0
  6. package/dist/api/apex.test.d.ts.map +1 -0
  7. package/dist/api/apex.test.js +61 -0
  8. package/dist/api/clients.d.ts +27 -0
  9. package/dist/api/clients.d.ts.map +1 -0
  10. package/dist/api/clients.js +116 -0
  11. package/dist/api/clients.test.d.ts +2 -0
  12. package/dist/api/clients.test.d.ts.map +1 -0
  13. package/dist/api/clients.test.js +237 -0
  14. package/dist/api/graphql.d.ts +20 -0
  15. package/dist/api/graphql.d.ts.map +1 -0
  16. package/dist/api/graphql.js +20 -0
  17. package/dist/api/graphql.test.d.ts +2 -0
  18. package/dist/api/graphql.test.d.ts.map +1 -0
  19. package/dist/api/graphql.test.js +70 -0
  20. package/dist/api/index.d.ts +7 -0
  21. package/dist/api/index.d.ts.map +1 -0
  22. package/dist/api/index.js +10 -0
  23. package/dist/api/utils/accounts.d.ts +32 -0
  24. package/dist/api/utils/accounts.d.ts.map +1 -0
  25. package/dist/api/utils/accounts.js +45 -0
  26. package/dist/api/utils/records.d.ts +11 -0
  27. package/dist/api/utils/records.d.ts.map +1 -0
  28. package/dist/api/utils/records.js +20 -0
  29. package/dist/api/utils/records.test.d.ts +2 -0
  30. package/dist/api/utils/records.test.d.ts.map +1 -0
  31. package/dist/api/utils/records.test.js +185 -0
  32. package/dist/api/utils/user.d.ts +12 -0
  33. package/dist/api/utils/user.d.ts.map +1 -0
  34. package/dist/api/utils/user.js +23 -0
  35. package/dist/api/utils/user.test.d.ts +2 -0
  36. package/dist/api/utils/user.test.d.ts.map +1 -0
  37. package/dist/api/utils/user.test.js +156 -0
  38. package/dist/app/index.d.ts +5 -0
  39. package/dist/app/index.d.ts.map +1 -0
  40. package/dist/app/index.js +2 -0
  41. package/dist/app/manifest.d.ts +32 -0
  42. package/dist/app/manifest.d.ts.map +1 -0
  43. package/dist/app/manifest.js +46 -0
  44. package/dist/app/org.d.ts +22 -0
  45. package/dist/app/org.d.ts.map +1 -0
  46. package/dist/app/org.js +62 -0
  47. package/dist/index.d.ts +4 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +4 -0
  50. package/dist/proxy/handler.d.ts +23 -0
  51. package/dist/proxy/handler.d.ts.map +1 -0
  52. package/dist/proxy/handler.js +210 -0
  53. package/dist/proxy/index.d.ts +3 -0
  54. package/dist/proxy/index.d.ts.map +1 -0
  55. package/dist/proxy/index.js +1 -0
  56. package/dist/proxy/routing.d.ts +34 -0
  57. package/dist/proxy/routing.d.ts.map +1 -0
  58. package/dist/proxy/routing.js +100 -0
  59. package/package.json +50 -0
@@ -0,0 +1,62 @@
1
+ import { Org } from "@salesforce/core";
2
+ /**
3
+ * Get Salesforce org info and authentication details
4
+ *
5
+ * @param orgAlias - Optional org alias or username, uses default org if not provided
6
+ * @returns Promise resolving to org info or null if authentication fails
7
+ */
8
+ export async function getOrgInfo(orgAlias) {
9
+ const org = await createOrg(orgAlias);
10
+ if (!org) {
11
+ return;
12
+ }
13
+ const connection = org.getConnection();
14
+ const authInfo = connection.getAuthInfo();
15
+ const authFields = authInfo.getFields();
16
+ return {
17
+ apiVersion: connection.getApiVersion(),
18
+ orgId: authFields.orgId ?? "",
19
+ instanceUrl: toLightningDomain(connection.instanceUrl),
20
+ username: authFields.username ?? "",
21
+ accessToken: connection.accessToken ?? "",
22
+ orgAlias,
23
+ };
24
+ }
25
+ async function createOrg(orgAlias) {
26
+ try {
27
+ if (!orgAlias) {
28
+ return await Org.create({});
29
+ }
30
+ return await Org.create({ aliasOrUsername: orgAlias });
31
+ }
32
+ catch (error) {
33
+ console.error("Failed to get SF org info:", error);
34
+ }
35
+ }
36
+ /**
37
+ * Refresh Salesforce org authentication
38
+ *
39
+ * @param orgAlias
40
+ */
41
+ export async function refreshOrgAuth(orgAlias) {
42
+ const org = await Org.create({ aliasOrUsername: orgAlias });
43
+ await org.refreshAuth();
44
+ return getOrgInfo(orgAlias);
45
+ }
46
+ function replaceLast(originalString, searchString, replacementString) {
47
+ const lastIndex = originalString.lastIndexOf(searchString);
48
+ if (lastIndex === -1) {
49
+ return originalString;
50
+ }
51
+ const before = originalString.slice(0, lastIndex);
52
+ const after = originalString.slice(lastIndex + searchString.length);
53
+ return before + replacementString + after;
54
+ }
55
+ function toLightningDomain(instanceUrl) {
56
+ if (!instanceUrl.includes(".my.")) {
57
+ return instanceUrl;
58
+ }
59
+ let url = replaceLast(instanceUrl, ".my.", ".lightning.");
60
+ url = replaceLast(url, ".salesforce", ".force");
61
+ return url;
62
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./api/index.js";
2
+ export * from "./app/index.js";
3
+ export * from "./proxy/index.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Browser-safe API clients
2
+ export * from "./api/index.js";
3
+ export * from "./app/index.js";
4
+ export * from "./proxy/index.js";
@@ -0,0 +1,23 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { OrgInfo, WebAppManifest } from "../app/index.js";
3
+ /**
4
+ * Configuration options for the WebApp proxy handler
5
+ */
6
+ export interface ProxyOptions {
7
+ debug?: boolean;
8
+ }
9
+ /**
10
+ * Proxy handler function type
11
+ */
12
+ export type ProxyHandler = (req: IncomingMessage, res: ServerResponse, next?: () => void) => Promise<void>;
13
+ /**
14
+ * Create proxy request handler
15
+ *
16
+ * @param manifest - WebApp manifest configuration
17
+ * @param orgInfo - Salesforce org information
18
+ * @param target - Target URL for dev server forwarding
19
+ * @param options - Proxy configuration options
20
+ * @returns Async request handler function for Node.js HTTP server
21
+ */
22
+ export declare function createProxyHandler(manifest: WebAppManifest, orgInfo?: OrgInfo, target?: string, basePath?: string, options?: ProxyOptions): ProxyHandler;
23
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,YAAY;IAE5B,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,CAAC,EAAE,MAAM,IAAI,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;AA8MnB;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,cAAc,EACxB,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,YAAY,GACpB,YAAY,CAGd"}
@@ -0,0 +1,210 @@
1
+ import { applyTrailingSlash, matchRoute } from "./routing.js";
2
+ import { refreshOrgAuth } from "../app/index.js";
3
+ /**
4
+ * Handles all proxy routing and forwarding for WebApps
5
+ */
6
+ class WebAppProxyHandler {
7
+ manifest;
8
+ orgInfo;
9
+ target;
10
+ basePath;
11
+ options;
12
+ constructor(manifest, orgInfo, target, basePath, options) {
13
+ this.manifest = manifest;
14
+ this.orgInfo = orgInfo;
15
+ this.target = target;
16
+ this.basePath = basePath;
17
+ this.options = options;
18
+ }
19
+ async handle(req, res, next) {
20
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
21
+ let pathname = url.pathname;
22
+ if (this.options?.debug) {
23
+ console.log(`[webapps-proxy] ${req.method} ${pathname}`);
24
+ }
25
+ pathname = applyTrailingSlash(pathname, this.manifest.routing?.trailingSlash);
26
+ const match = matchRoute(pathname, this.basePath, this.manifest.routing?.rewrites, this.manifest.routing?.redirects);
27
+ if (match) {
28
+ if (match.type === "api") {
29
+ await this.handleSalesforceApi(req, res);
30
+ return;
31
+ }
32
+ if (match.type === "redirect" && match.target && match.statusCode) {
33
+ this.handleRedirect(res, match.target, match.statusCode);
34
+ return;
35
+ }
36
+ if (match.type === "rewrite" && match.target) {
37
+ url.pathname = `/${match.target}`.replace(/\/+/g, "/");
38
+ req.url = url.pathname + url.search;
39
+ if (this.options?.debug) {
40
+ console.log(`[webapps-proxy] Rewrite to ${req.url}`);
41
+ }
42
+ }
43
+ }
44
+ if (next) {
45
+ next();
46
+ }
47
+ else {
48
+ await this.forwardToDevServer(req, res);
49
+ }
50
+ }
51
+ handleRedirect(res, location, statusCode) {
52
+ res.writeHead(statusCode, { Location: location });
53
+ res.end();
54
+ }
55
+ async handleSalesforceApi(req, res) {
56
+ try {
57
+ if (!this.orgInfo) {
58
+ res.writeHead(401, { "Content-Type": "application/json" });
59
+ res.end(JSON.stringify({
60
+ error: "NO_ORG_FOUND",
61
+ message: "No default Salesforce org found. Run 'sf org login web --set-default' to authenticate.",
62
+ }));
63
+ return;
64
+ }
65
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
66
+ let pathIndex = url.pathname.indexOf("/lwr/apex/v");
67
+ if (pathIndex === -1) {
68
+ pathIndex = url.pathname.indexOf("/services/data/v");
69
+ }
70
+ const apiPath = url.pathname.substring(pathIndex);
71
+ let targetUrl = `${this.orgInfo.instanceUrl}${apiPath}${url.search}`;
72
+ if (this.options?.debug) {
73
+ console.log(`[webapps-proxy] Forwarding to Salesforce: ${targetUrl}`);
74
+ }
75
+ // Buffer the request body before sending. This allows us to retry requests
76
+ // with the same body in case of authentication failures (403).
77
+ // For GET/HEAD requests, body is undefined.
78
+ const body = req.method !== "GET" && req.method !== "HEAD" ? await getBody(req) : undefined;
79
+ let response = await fetch(targetUrl, {
80
+ method: req.method,
81
+ headers: {
82
+ ...getFilteredHeaders(req.headers),
83
+ Cookie: `sid=${this.orgInfo.accessToken}`,
84
+ Accept: req.headers.accept ?? "application/json",
85
+ // necessary for Apex requests, for which SessionUtil.validateSessionUsage won't accept OAuth token as `sid` cookie
86
+ Authorization: `Bearer ${this.orgInfo.accessToken}`,
87
+ },
88
+ body: body,
89
+ });
90
+ if (response.status === 401 || response.status === 403) {
91
+ console.warn(`[webapps-proxy] Received ${response.status}, refreshing token...`);
92
+ // Use the orgAlias from the current orgInfo to maintain consistency
93
+ const updatedOrgInfo = await refreshOrgAuth(this.orgInfo.orgAlias);
94
+ if (!updatedOrgInfo) {
95
+ throw new Error("Failed to refresh token");
96
+ }
97
+ this.orgInfo = updatedOrgInfo;
98
+ if (this.orgInfo) {
99
+ if (this.options?.debug) {
100
+ console.log("[webapps-proxy] Token refreshed, retrying request");
101
+ }
102
+ // Update target URL with refreshed org info (instance URL may have changed)
103
+ targetUrl = `${this.orgInfo.instanceUrl}${url.pathname}${url.search}`;
104
+ // Retry with the same buffered body
105
+ response = await fetch(targetUrl, {
106
+ method: req.method,
107
+ headers: {
108
+ ...getFilteredHeaders(req.headers),
109
+ Cookie: `sid=${this.orgInfo.accessToken}`,
110
+ Accept: req.headers.accept ?? "application/json",
111
+ },
112
+ body: body,
113
+ });
114
+ }
115
+ }
116
+ await this.sendResponse(res, response);
117
+ }
118
+ catch (error) {
119
+ console.error("[webapps-proxy] Salesforce API request failed:", error);
120
+ res.writeHead(502, { "Content-Type": "application/json" });
121
+ res.end(JSON.stringify({
122
+ error: "GATEWAY_ERROR",
123
+ message: "Failed to forward request to Salesforce",
124
+ }));
125
+ }
126
+ }
127
+ async forwardToDevServer(req, res) {
128
+ try {
129
+ const url = new URL(req.url ?? "/", this.target);
130
+ if (this.options?.debug) {
131
+ console.log(`[webapps-proxy] Forwarding to dev server: ${url.href}`);
132
+ }
133
+ const body = req.method !== "GET" && req.method !== "HEAD" ? await getBody(req) : undefined;
134
+ const response = await fetch(url.href, {
135
+ method: req.method,
136
+ headers: getFilteredHeaders(req.headers),
137
+ body: body,
138
+ });
139
+ await this.sendResponse(res, response);
140
+ }
141
+ catch (error) {
142
+ console.error("[webapps-proxy] Dev server request failed:", error);
143
+ res.writeHead(502, { "Content-Type": "application/json" });
144
+ res.end(JSON.stringify({
145
+ error: "GATEWAY_ERROR",
146
+ message: "Failed to forward request to dev server",
147
+ }));
148
+ }
149
+ }
150
+ async sendResponse(res, response) {
151
+ const headers = {};
152
+ const skipHeaders = new Set(["content-encoding", "content-length", "transfer-encoding"]);
153
+ response.headers.forEach((value, key) => {
154
+ if (!skipHeaders.has(key.toLowerCase())) {
155
+ headers[key] = value;
156
+ }
157
+ });
158
+ res.writeHead(response.status, headers);
159
+ if (response.body) {
160
+ const reader = response.body.getReader();
161
+ while (true) {
162
+ const { done, value } = await reader.read();
163
+ if (done)
164
+ break;
165
+ res.write(value);
166
+ }
167
+ }
168
+ res.end();
169
+ }
170
+ }
171
+ /**
172
+ * Create proxy request handler
173
+ *
174
+ * @param manifest - WebApp manifest configuration
175
+ * @param orgInfo - Salesforce org information
176
+ * @param target - Target URL for dev server forwarding
177
+ * @param options - Proxy configuration options
178
+ * @returns Async request handler function for Node.js HTTP server
179
+ */
180
+ export function createProxyHandler(manifest, orgInfo, target, basePath, options) {
181
+ const handler = new WebAppProxyHandler(manifest, orgInfo, target, basePath, options);
182
+ return (req, res, next) => handler.handle(req, res, next);
183
+ }
184
+ function getFilteredHeaders(headers) {
185
+ const filtered = {};
186
+ const hopByHopHeaders = new Set([
187
+ "connection",
188
+ "keep-alive",
189
+ "proxy-authenticate",
190
+ "proxy-authorization",
191
+ "te",
192
+ "trailer",
193
+ "transfer-encoding",
194
+ "upgrade",
195
+ ]);
196
+ for (const [key, value] of Object.entries(headers)) {
197
+ if (!hopByHopHeaders.has(key.toLowerCase()) && value) {
198
+ filtered[key] = Array.isArray(value) ? value.join(", ") : value;
199
+ }
200
+ }
201
+ return filtered;
202
+ }
203
+ function getBody(req) {
204
+ return new Promise((resolve, reject) => {
205
+ const chunks = [];
206
+ req.on("data", (chunk) => chunks.push(chunk));
207
+ req.on("end", () => resolve(Buffer.concat(chunks)));
208
+ req.on("error", reject);
209
+ });
210
+ }
@@ -0,0 +1,3 @@
1
+ export type { ProxyOptions, ProxyHandler } from "./handler.js";
2
+ export { createProxyHandler } from "./handler.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export { createProxyHandler } from "./handler.js";
@@ -0,0 +1,34 @@
1
+ import type { RedirectRule, RewriteRule } from "../app/index.js";
2
+ export interface RouteMatch {
3
+ type: "rewrite" | "redirect" | "api";
4
+ target?: string;
5
+ statusCode?: number;
6
+ params?: Record<string, string>;
7
+ }
8
+ /**
9
+ * Match URL path against routing rules
10
+ *
11
+ * @param pathname - The URL pathname to match
12
+ * @param rewrites - Optional array of rewrite rules
13
+ * @param redirects - Optional array of redirect rules
14
+ * @returns Route match result indicating the type and target, or null if no match
15
+ */
16
+ export declare function matchRoute(pathname: string, basePath?: string, rewrites?: RewriteRule[], redirects?: RedirectRule[]): RouteMatch | null;
17
+ /**
18
+ * Check if a path matches any of the given glob patterns
19
+ * Supports glob wildcards: * (matches anything except /), ** (matches anything including /), ? (single character)
20
+ *
21
+ * @param path - The path to test
22
+ * @param patterns - Array of glob patterns to match against
23
+ * @returns True if the path matches any pattern, false otherwise
24
+ */
25
+ export declare function matchesPattern(path: string, patterns: string[] | undefined): boolean;
26
+ /**
27
+ * Apply trailing slash rules to pathname
28
+ *
29
+ * @param pathname - The URL pathname
30
+ * @param trailingSlash - Trailing slash handling strategy
31
+ * @returns Modified pathname with trailing slash applied according to rules
32
+ */
33
+ export declare function applyTrailingSlash(pathname: string, trailingSlash?: "always" | "never" | "auto"): string;
34
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/proxy/routing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEjE,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AASD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,WAAW,EAAE,EACxB,SAAS,CAAC,EAAE,YAAY,EAAE,GACxB,UAAU,GAAG,IAAI,CA2DnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAMpF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GACzC,MAAM,CAiBR"}
@@ -0,0 +1,100 @@
1
+ import micromatch from "micromatch";
2
+ import { match } from "path-to-regexp";
3
+ function normalizeRoute(route) {
4
+ let wildcardIndex = 0;
5
+ return route.replace(/\*/g, () => {
6
+ return `{*wildcard${wildcardIndex++}}`;
7
+ });
8
+ }
9
+ /**
10
+ * Match URL path against routing rules
11
+ *
12
+ * @param pathname - The URL pathname to match
13
+ * @param rewrites - Optional array of rewrite rules
14
+ * @param redirects - Optional array of redirect rules
15
+ * @returns Route match result indicating the type and target, or null if no match
16
+ */
17
+ export function matchRoute(pathname, basePath, rewrites, redirects) {
18
+ if (pathname.startsWith(`${basePath || ""}/services/data/v`) ||
19
+ pathname.startsWith(`${basePath || ""}/lwr/apex/v`)) {
20
+ return { type: "api" };
21
+ }
22
+ if (redirects) {
23
+ for (const redirect of redirects) {
24
+ const normalizedRoute = normalizeRoute(redirect.route);
25
+ const matcher = match(normalizedRoute, { decode: decodeURIComponent });
26
+ const result = matcher(pathname);
27
+ if (result) {
28
+ let target = redirect.target;
29
+ const params = result.params;
30
+ for (const [key, value] of Object.entries(params)) {
31
+ if (!key.startsWith("wildcard")) {
32
+ target = target.replace(`:${key}`, value);
33
+ }
34
+ }
35
+ return {
36
+ type: "redirect",
37
+ target,
38
+ statusCode: redirect.statusCode,
39
+ };
40
+ }
41
+ }
42
+ }
43
+ if (rewrites) {
44
+ for (const rewrite of rewrites) {
45
+ const normalizedRoute = normalizeRoute(rewrite.route);
46
+ const matcher = match(normalizedRoute, { decode: decodeURIComponent });
47
+ const result = matcher(pathname);
48
+ if (result) {
49
+ const params = {};
50
+ const matchParams = result.params;
51
+ for (const [key, value] of Object.entries(matchParams)) {
52
+ if (!key.startsWith("wildcard")) {
53
+ params[key] = value;
54
+ }
55
+ }
56
+ return {
57
+ type: "rewrite",
58
+ target: rewrite.target,
59
+ params,
60
+ };
61
+ }
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Check if a path matches any of the given glob patterns
68
+ * Supports glob wildcards: * (matches anything except /), ** (matches anything including /), ? (single character)
69
+ *
70
+ * @param path - The path to test
71
+ * @param patterns - Array of glob patterns to match against
72
+ * @returns True if the path matches any pattern, false otherwise
73
+ */
74
+ export function matchesPattern(path, patterns) {
75
+ if (!patterns || patterns.length === 0) {
76
+ return false;
77
+ }
78
+ return micromatch.isMatch(path, patterns);
79
+ }
80
+ /**
81
+ * Apply trailing slash rules to pathname
82
+ *
83
+ * @param pathname - The URL pathname
84
+ * @param trailingSlash - Trailing slash handling strategy
85
+ * @returns Modified pathname with trailing slash applied according to rules
86
+ */
87
+ export function applyTrailingSlash(pathname, trailingSlash) {
88
+ if (!trailingSlash || trailingSlash === "auto") {
89
+ return pathname;
90
+ }
91
+ const hasTrailingSlash = pathname.endsWith("/");
92
+ const isRoot = pathname === "/";
93
+ if (trailingSlash === "always" && !hasTrailingSlash && !isRoot) {
94
+ return `${pathname}/`;
95
+ }
96
+ if (trailingSlash === "never" && hasTrailingSlash && !isRoot) {
97
+ return pathname.slice(0, -1);
98
+ }
99
+ return pathname;
100
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@salesforce/webapp-experimental",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./api": {
14
+ "types": "./dist/api/index.d.ts",
15
+ "import": "./dist/api/index.js"
16
+ },
17
+ "./app": {
18
+ "types": "./dist/app/index.d.ts",
19
+ "import": "./dist/app/index.js"
20
+ },
21
+ "./proxy": {
22
+ "types": "./dist/proxy/index.d.ts",
23
+ "import": "./dist/proxy/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc --build",
32
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
33
+ "dev": "tsc --build --watch"
34
+ },
35
+ "dependencies": {
36
+ "@conduit-client/salesforce-lightning-service-worker": "^3.7.0",
37
+ "@salesforce/core": "^8.23.4",
38
+ "axios": "^1.7.7",
39
+ "micromatch": "^4.0.8",
40
+ "path-to-regexp": "^8.3.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/micromatch": "^4.0.10",
44
+ "vitest": "^4.0.6"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.0.0"
48
+ },
49
+ "gitHead": "a806910fe9c132ad3cc0ca63302072b0076699b9"
50
+ }