@salesforce/webapp-template-cli-experimental 1.3.3
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/LICENSE.txt +82 -0
- package/README.md +1464 -0
- package/dist/commands/apply-patches.d.ts +23 -0
- package/dist/commands/apply-patches.d.ts.map +1 -0
- package/dist/commands/apply-patches.js +650 -0
- package/dist/commands/apply-patches.js.map +1 -0
- package/dist/commands/new-app-feature.d.ts +7 -0
- package/dist/commands/new-app-feature.d.ts.map +1 -0
- package/dist/commands/new-app-feature.js +208 -0
- package/dist/commands/new-app-feature.js.map +1 -0
- package/dist/commands/new-app.d.ts +6 -0
- package/dist/commands/new-app.d.ts.map +1 -0
- package/dist/commands/new-app.js +7 -0
- package/dist/commands/new-app.js.map +1 -0
- package/dist/commands/watch-patches.d.ts +4 -0
- package/dist/commands/watch-patches.d.ts.map +1 -0
- package/dist/commands/watch-patches.js +128 -0
- package/dist/commands/watch-patches.js.map +1 -0
- package/dist/core/dependency-resolver.d.ts +40 -0
- package/dist/core/dependency-resolver.d.ts.map +1 -0
- package/dist/core/dependency-resolver.js +126 -0
- package/dist/core/dependency-resolver.js.map +1 -0
- package/dist/core/file-operations.d.ts +55 -0
- package/dist/core/file-operations.d.ts.map +1 -0
- package/dist/core/file-operations.js +350 -0
- package/dist/core/file-operations.js.map +1 -0
- package/dist/core/package-json-merger.d.ts +31 -0
- package/dist/core/package-json-merger.d.ts.map +1 -0
- package/dist/core/package-json-merger.js +149 -0
- package/dist/core/package-json-merger.js.map +1 -0
- package/dist/core/patch-loader.d.ts +18 -0
- package/dist/core/patch-loader.d.ts.map +1 -0
- package/dist/core/patch-loader.js +115 -0
- package/dist/core/patch-loader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/debounce.d.ts +9 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/debounce.js +20 -0
- package/dist/utils/debounce.js.map +1 -0
- package/dist/utils/import-merger.d.ts +17 -0
- package/dist/utils/import-merger.d.ts.map +1 -0
- package/dist/utils/import-merger.js +244 -0
- package/dist/utils/import-merger.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path-mappings.d.ts +94 -0
- package/dist/utils/path-mappings.d.ts.map +1 -0
- package/dist/utils/path-mappings.js +139 -0
- package/dist/utils/path-mappings.js.map +1 -0
- package/dist/utils/paths.d.ts +61 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +178 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/route-merger.d.ts +107 -0
- package/dist/utils/route-merger.d.ts.map +1 -0
- package/dist/utils/route-merger.js +358 -0
- package/dist/utils/route-merger.js.map +1 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +137 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { Project, ArrayLiteralExpression, Node } from "ts-morph";
|
|
8
|
+
import { mergeImports } from "./import-merger.js";
|
|
9
|
+
import * as logger from "./logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Route Merging Strategy: Replace-Matching with Deep Children Merge
|
|
12
|
+
*
|
|
13
|
+
* This function merges React Router routes from a feature into a base app using
|
|
14
|
+
* a "replace-matching" strategy with intelligent children array merging.
|
|
15
|
+
*
|
|
16
|
+
* ## How It Works:
|
|
17
|
+
*
|
|
18
|
+
* 1. **Top-Level Routes:**
|
|
19
|
+
* - Routes with the SAME path → Merge their children arrays
|
|
20
|
+
* - Routes with DIFFERENT paths → Add feature route to result
|
|
21
|
+
*
|
|
22
|
+
* 2. **Children Array Merging (when parent paths match):**
|
|
23
|
+
* - Index routes (`index: true`) → Feature replaces base if both exist
|
|
24
|
+
* - Named routes (`path: 'about'`) → Feature replaces base if paths match
|
|
25
|
+
* - New routes → Added to children array
|
|
26
|
+
* - Preserves base routes not in feature
|
|
27
|
+
*
|
|
28
|
+
* 3. **Route Deletion:**
|
|
29
|
+
* - Routes with path starting with `__delete__` are deletion markers
|
|
30
|
+
* - The route with matching path (without prefix) is removed from result
|
|
31
|
+
* - Example: `{ path: '__delete__new' }` removes the route with `path: 'new'`
|
|
32
|
+
*
|
|
33
|
+
* 4. **Recursion:**
|
|
34
|
+
* - Applies same logic to nested children arrays
|
|
35
|
+
*
|
|
36
|
+
* ## Example:
|
|
37
|
+
*
|
|
38
|
+
* **Base App Routes:**
|
|
39
|
+
* ```typescript
|
|
40
|
+
* [{
|
|
41
|
+
* path: '/',
|
|
42
|
+
* element: <AppLayout />,
|
|
43
|
+
* children: [
|
|
44
|
+
* { index: true, element: <Home /> }
|
|
45
|
+
* ]
|
|
46
|
+
* }]
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* **Feature Routes:**
|
|
50
|
+
* ```typescript
|
|
51
|
+
* [{
|
|
52
|
+
* path: '/',
|
|
53
|
+
* element: <AppLayout />,
|
|
54
|
+
* children: [
|
|
55
|
+
* { path: 'about', element: <About /> },
|
|
56
|
+
* { path: 'contact', element: <Contact /> }
|
|
57
|
+
* ]
|
|
58
|
+
* }]
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* **Merged Result:**
|
|
62
|
+
* ```typescript
|
|
63
|
+
* [{
|
|
64
|
+
* path: '/',
|
|
65
|
+
* element: <AppLayout />, // Uses feature's element
|
|
66
|
+
* children: [
|
|
67
|
+
* { index: true, element: <Home /> }, // Preserved from base
|
|
68
|
+
* { path: 'about', element: <About /> }, // Added from feature
|
|
69
|
+
* { path: 'contact', element: <Contact /> } // Added from feature
|
|
70
|
+
* ]
|
|
71
|
+
* }]
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* ## Deletion Example:
|
|
75
|
+
*
|
|
76
|
+
* **Base/Previous Routes:**
|
|
77
|
+
* ```typescript
|
|
78
|
+
* [{
|
|
79
|
+
* path: '/',
|
|
80
|
+
* children: [
|
|
81
|
+
* { path: 'home', element: <Home /> },
|
|
82
|
+
* { path: 'about', element: <About /> },
|
|
83
|
+
* { path: 'new', element: <New /> }
|
|
84
|
+
* ]
|
|
85
|
+
* }]
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* **Feature Routes (deleting 'new'):**
|
|
89
|
+
* ```typescript
|
|
90
|
+
* [{
|
|
91
|
+
* path: '/',
|
|
92
|
+
* children: [
|
|
93
|
+
* { path: '__delete__new', element: <></> }
|
|
94
|
+
* ]
|
|
95
|
+
* }]
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* **Merged Result:**
|
|
99
|
+
* ```typescript
|
|
100
|
+
* [{
|
|
101
|
+
* path: '/',
|
|
102
|
+
* children: [
|
|
103
|
+
* { path: 'home', element: <Home /> }, // Preserved
|
|
104
|
+
* { path: 'about', element: <About /> } // Preserved
|
|
105
|
+
* // 'new' route removed
|
|
106
|
+
* ]
|
|
107
|
+
* }]
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @param featurePath - Path to the feature's routes.tsx file
|
|
111
|
+
* @param targetPath - Path to the base app's routes.tsx file
|
|
112
|
+
* @returns The merged routes source code
|
|
113
|
+
*/
|
|
114
|
+
export function mergeRoutes(featurePath, targetPath, project) {
|
|
115
|
+
try {
|
|
116
|
+
const proj = project || new Project();
|
|
117
|
+
const targetFile = project
|
|
118
|
+
? proj.getSourceFile(targetPath) || proj.addSourceFileAtPath(path.resolve(targetPath))
|
|
119
|
+
: proj.addSourceFileAtPath(path.resolve(targetPath));
|
|
120
|
+
const featureFile = project
|
|
121
|
+
? proj.getSourceFile(featurePath) || proj.addSourceFileAtPath(path.resolve(featurePath))
|
|
122
|
+
: proj.addSourceFileAtPath(path.resolve(featurePath));
|
|
123
|
+
// Get initial routes arrays to validate they exist
|
|
124
|
+
const initialTargetArray = getRoutesArrayLiteral(targetFile, "routes");
|
|
125
|
+
const initialFeatureArray = getRoutesArrayLiteral(featureFile, "routes");
|
|
126
|
+
if (!initialTargetArray) {
|
|
127
|
+
logger.warning("Could not extract routes array from target file");
|
|
128
|
+
return featureFile.getText();
|
|
129
|
+
}
|
|
130
|
+
if (!initialFeatureArray) {
|
|
131
|
+
logger.warning("Could not extract routes array from feature file");
|
|
132
|
+
return targetFile.getText();
|
|
133
|
+
}
|
|
134
|
+
// Merge imports from feature file into target file
|
|
135
|
+
// Note: This calls organizeImports() which invalidates AST node references
|
|
136
|
+
mergeImports(featureFile, targetFile);
|
|
137
|
+
// Re-get routes arrays after import merging (organizeImports invalidates previous references)
|
|
138
|
+
const targetArray = getRoutesArrayLiteral(targetFile, "routes");
|
|
139
|
+
const featureArray = getRoutesArrayLiteral(featureFile, "routes");
|
|
140
|
+
if (!targetArray || !featureArray) {
|
|
141
|
+
logger.error("Routes array was lost after import merging");
|
|
142
|
+
throw new Error("Failed to re-acquire routes arrays after import merging");
|
|
143
|
+
}
|
|
144
|
+
// Parse arrays to RouteObject[]
|
|
145
|
+
const targetRoutes = parseRoutesFromAST(targetArray);
|
|
146
|
+
const featureRoutes = parseRoutesFromAST(featureArray);
|
|
147
|
+
// Deep merge the routes
|
|
148
|
+
const mergedRoutes = deepMergeRoutes(targetRoutes, featureRoutes);
|
|
149
|
+
// Convert back to code and update the target
|
|
150
|
+
const mergedCode = generateRoutesCode(mergedRoutes);
|
|
151
|
+
targetArray.replaceWithText(mergedCode);
|
|
152
|
+
// Remove default imports that are no longer used in routes (e.g. after __delete__ route)
|
|
153
|
+
removeUnusedRouteImports(targetFile, mergedRoutes);
|
|
154
|
+
return targetFile.getText();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
logger.error(`Route merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
|
+
throw new Error("Failed to merge routes");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get the array literal expression for the routes variable
|
|
163
|
+
*/
|
|
164
|
+
function getRoutesArrayLiteral(file, varName) {
|
|
165
|
+
const variable = file.getVariableDeclaration(varName);
|
|
166
|
+
if (!variable) {
|
|
167
|
+
logger.warning(`Variable "${varName}" not found in ${file.getFilePath()}`);
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const initializer = variable.getInitializer();
|
|
171
|
+
if (!initializer || !ArrayLiteralExpression.isArrayLiteralExpression(initializer)) {
|
|
172
|
+
logger.warning(`Variable "${varName}" is not initialized with an array literal`);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return initializer;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse routes from AST ArrayLiteralExpression to RouteObject[]
|
|
179
|
+
*/
|
|
180
|
+
function parseRoutesFromAST(arrayLiteral) {
|
|
181
|
+
const routes = [];
|
|
182
|
+
for (const element of arrayLiteral.getElements()) {
|
|
183
|
+
if (Node.isObjectLiteralExpression(element)) {
|
|
184
|
+
const route = {};
|
|
185
|
+
for (const prop of element.getProperties()) {
|
|
186
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
187
|
+
const name = prop.getName();
|
|
188
|
+
const initializer = prop.getInitializer();
|
|
189
|
+
if (!initializer)
|
|
190
|
+
continue;
|
|
191
|
+
if (name === "children" && Node.isArrayLiteralExpression(initializer)) {
|
|
192
|
+
// Recursively parse children
|
|
193
|
+
route.children = parseRoutesFromAST(initializer);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Store the raw text for other properties
|
|
197
|
+
route[name] = initializer.getText();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
routes.push(route);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return routes;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Deep merge two route arrays
|
|
208
|
+
* - Routes are matched by path equality
|
|
209
|
+
* - Feature routes replace target routes with the same path
|
|
210
|
+
* - Children arrays are recursively merged
|
|
211
|
+
* - All properties from feature route are preserved
|
|
212
|
+
* - Routes with path starting with '__delete__' are deletion markers
|
|
213
|
+
*/
|
|
214
|
+
function normalizePath(pathStr) {
|
|
215
|
+
return pathStr.replace(/^['"`]|['"`]$/g, "");
|
|
216
|
+
}
|
|
217
|
+
/** Remove the first route with matching path from the array or any nested children. Returns true if removed. */
|
|
218
|
+
function deleteRouteByPath(routes, targetPath) {
|
|
219
|
+
const index = routes.findIndex((r) => {
|
|
220
|
+
if (r.path && typeof r.path === "string") {
|
|
221
|
+
return normalizePath(r.path) === targetPath;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
});
|
|
225
|
+
if (index >= 0) {
|
|
226
|
+
routes.splice(index, 1);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
for (const r of routes) {
|
|
230
|
+
if (r.children && Array.isArray(r.children)) {
|
|
231
|
+
if (deleteRouteByPath(r.children, targetPath))
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
function deepMergeRoutes(targetRoutes, featureRoutes) {
|
|
238
|
+
const result = [...targetRoutes];
|
|
239
|
+
for (const featureRoute of featureRoutes) {
|
|
240
|
+
// Check if this is a deletion marker
|
|
241
|
+
const DELETE_PREFIX = "__delete__";
|
|
242
|
+
if (featureRoute.path && typeof featureRoute.path === "string") {
|
|
243
|
+
// Remove quotes from path string if present
|
|
244
|
+
const pathValue = featureRoute.path.replace(/^['"`]|['"`]$/g, "");
|
|
245
|
+
if (pathValue.startsWith(DELETE_PREFIX)) {
|
|
246
|
+
// This is a deletion marker - remove the route with matching path (current level or nested)
|
|
247
|
+
const targetPath = pathValue.substring(DELETE_PREFIX.length);
|
|
248
|
+
const deleted = deleteRouteByPath(result, targetPath);
|
|
249
|
+
if (deleted) {
|
|
250
|
+
logger.info(`Deleted route with path: ${targetPath}`);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
logger.info(`Skipped deletion of route '${targetPath}' (not found in current routes)`);
|
|
254
|
+
}
|
|
255
|
+
// Don't add the deletion marker to result
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const matchIndex = result.findIndex((targetRoute) => {
|
|
260
|
+
// Match by path if both have paths (normalize for comparison)
|
|
261
|
+
if (featureRoute.path && targetRoute.path) {
|
|
262
|
+
return normalizePath(featureRoute.path) === normalizePath(targetRoute.path);
|
|
263
|
+
}
|
|
264
|
+
// Match index routes
|
|
265
|
+
if (featureRoute.index && targetRoute.index) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
});
|
|
270
|
+
if (matchIndex >= 0) {
|
|
271
|
+
const targetRoute = result[matchIndex];
|
|
272
|
+
// Merge the route, starting with feature route properties
|
|
273
|
+
const mergedRoute = { ...featureRoute };
|
|
274
|
+
// If both have children, recursively merge them
|
|
275
|
+
if (targetRoute.children && featureRoute.children) {
|
|
276
|
+
mergedRoute.children = deepMergeRoutes(targetRoute.children, featureRoute.children);
|
|
277
|
+
}
|
|
278
|
+
else if (targetRoute.children) {
|
|
279
|
+
// Only target has children, preserve them
|
|
280
|
+
mergedRoute.children = targetRoute.children;
|
|
281
|
+
}
|
|
282
|
+
// If only feature has children, they're already in mergedRoute
|
|
283
|
+
result[matchIndex] = mergedRoute;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// No match found, add the new route
|
|
287
|
+
result.push(featureRoute);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Generate TypeScript code from RouteObject[]
|
|
294
|
+
*/
|
|
295
|
+
function generateRoutesCode(routes) {
|
|
296
|
+
if (routes.length === 0) {
|
|
297
|
+
return "[]";
|
|
298
|
+
}
|
|
299
|
+
const routeStrings = routes.map((route) => generateRouteCode(route, 1));
|
|
300
|
+
return `[\n${routeStrings.join(",\n")}\n]`;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Generate code for a single route object
|
|
304
|
+
*/
|
|
305
|
+
function generateRouteCode(route, indentLevel) {
|
|
306
|
+
const indent = " ".repeat(indentLevel);
|
|
307
|
+
const props = [];
|
|
308
|
+
for (const [key, value] of Object.entries(route)) {
|
|
309
|
+
if (key === "children" && Array.isArray(value)) {
|
|
310
|
+
// Recursively generate children
|
|
311
|
+
const childrenCode = value.length > 0
|
|
312
|
+
? `[\n${value.map((child) => generateRouteCode(child, indentLevel + 2)).join(",\n")}\n${indent} ]`
|
|
313
|
+
: "[]";
|
|
314
|
+
props.push(`children: ${childrenCode}`);
|
|
315
|
+
}
|
|
316
|
+
else if (value !== undefined) {
|
|
317
|
+
// For other properties, use the raw text we stored
|
|
318
|
+
props.push(`${key}: ${value}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return `${indent}{\n${props.map((p) => `${indent} ${p}`).join(",\n")}\n${indent}}`;
|
|
322
|
+
}
|
|
323
|
+
/** Collect component names used in route elements (e.g. <Home /> -> Home). */
|
|
324
|
+
function collectUsedComponentNames(routes) {
|
|
325
|
+
const used = new Set();
|
|
326
|
+
function walk(r) {
|
|
327
|
+
for (const route of r) {
|
|
328
|
+
const el = route.element;
|
|
329
|
+
if (typeof el === "string") {
|
|
330
|
+
const matches = el.match(/<(\w+)/g);
|
|
331
|
+
if (matches) {
|
|
332
|
+
for (const m of matches) {
|
|
333
|
+
used.add(m.slice(1)); // strip "<"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (route.children && Array.isArray(route.children)) {
|
|
338
|
+
walk(route.children);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
walk(routes);
|
|
343
|
+
return used;
|
|
344
|
+
}
|
|
345
|
+
/** Remove default imports whose name is not used in the merged routes (e.g. after deleting a route). */
|
|
346
|
+
function removeUnusedRouteImports(targetFile, mergedRoutes) {
|
|
347
|
+
const used = collectUsedComponentNames(mergedRoutes);
|
|
348
|
+
for (const imp of targetFile.getImportDeclarations()) {
|
|
349
|
+
const defaultImport = imp.getDefaultImport();
|
|
350
|
+
if (defaultImport) {
|
|
351
|
+
const name = defaultImport.getText();
|
|
352
|
+
if (!used.has(name)) {
|
|
353
|
+
imp.remove();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=route-merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-merger.js","sourceRoot":"","sources":["../../src/utils/route-merger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare class ValidationError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Get the web application directory path from a base app path
|
|
6
|
+
* Supports both old structure (<basePath>/webapplications/<appName>) and
|
|
7
|
+
* new SFDX structure (<basePath>/src/force-app/main/default/webapplications/<appName>)
|
|
8
|
+
*/
|
|
9
|
+
export declare function getWebApplicationPath(basePath: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get the web application directory path in SFDX structure
|
|
12
|
+
* Structure: <targetDir>/force-app/main/default/webapplications/<appName>
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSfdxWebApplicationPath(targetDir: string, appName: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Validate that a feature path exists and resolve it to an absolute path
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateAndResolveFeaturePath(featurePath: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Validate and resolve an app path (can be relative or absolute)
|
|
21
|
+
* Validates that the web application directory exists with proper structure
|
|
22
|
+
* Returns the absolute path to the base app directory
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateAndResolveAppPath(appPath: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a app or feature name is valid
|
|
27
|
+
* - Must be in kebab-case (lowercase with hyphens)
|
|
28
|
+
* - Only alphanumeric characters and hyphens allowed
|
|
29
|
+
* - Cannot start or end with hyphens
|
|
30
|
+
* - No consecutive hyphens
|
|
31
|
+
* - Cannot be reserved names: "base", "cli"
|
|
32
|
+
* - Max 50 characters
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateAppOrFeatureName(name: string): void;
|
|
35
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AASA,qBAAa,eAAgB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI3B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyB9D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA0BzE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqDjE;AAID;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAyB3D"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, statSync } from "fs";
|
|
7
|
+
import { join, isAbsolute, basename } from "path";
|
|
8
|
+
import { getMonorepoRoot } from "./paths.js";
|
|
9
|
+
export class ValidationError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ValidationError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get the web application directory path from a base app path
|
|
17
|
+
* Supports both old structure (<basePath>/webapplications/<appName>) and
|
|
18
|
+
* new SFDX structure (<basePath>/src/force-app/main/default/webapplications/<appName>)
|
|
19
|
+
*/
|
|
20
|
+
export function getWebApplicationPath(basePath) {
|
|
21
|
+
const appName = basename(basePath);
|
|
22
|
+
// Try SFDX structure first (new)
|
|
23
|
+
const sfdxPath = join(basePath, "src", "force-app", "main", "default", "webapplications", appName);
|
|
24
|
+
if (existsSync(sfdxPath)) {
|
|
25
|
+
return sfdxPath;
|
|
26
|
+
}
|
|
27
|
+
// Fall back to old structure (for backward compatibility during migration)
|
|
28
|
+
const oldPath = join(basePath, "webapplications", appName);
|
|
29
|
+
if (existsSync(oldPath)) {
|
|
30
|
+
return oldPath;
|
|
31
|
+
}
|
|
32
|
+
// If neither exists, return SFDX structure (expected default)
|
|
33
|
+
return sfdxPath;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the web application directory path in SFDX structure
|
|
37
|
+
* Structure: <targetDir>/force-app/main/default/webapplications/<appName>
|
|
38
|
+
*/
|
|
39
|
+
export function getSfdxWebApplicationPath(targetDir, appName) {
|
|
40
|
+
return join(targetDir, "force-app", "main", "default", "webapplications", appName);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate that a feature path exists and resolve it to an absolute path
|
|
44
|
+
*/
|
|
45
|
+
export function validateAndResolveFeaturePath(featurePath) {
|
|
46
|
+
let resolvedPath;
|
|
47
|
+
// If it's an absolute path, use it as-is
|
|
48
|
+
if (isAbsolute(featurePath)) {
|
|
49
|
+
resolvedPath = featurePath;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// If it's relative, resolve from monorepo root
|
|
53
|
+
resolvedPath = join(getMonorepoRoot(), featurePath);
|
|
54
|
+
}
|
|
55
|
+
if (!existsSync(resolvedPath)) {
|
|
56
|
+
throw new ValidationError(`Feature path '${featurePath}' does not exist`);
|
|
57
|
+
}
|
|
58
|
+
if (!statSync(resolvedPath).isDirectory()) {
|
|
59
|
+
throw new ValidationError(`Feature path '${featurePath}' is not a directory`);
|
|
60
|
+
}
|
|
61
|
+
// Check if it has a feature.ts file
|
|
62
|
+
const featureFilePath = join(resolvedPath, "feature.ts");
|
|
63
|
+
if (!existsSync(featureFilePath)) {
|
|
64
|
+
throw new ValidationError(`Feature path '${featurePath}' does not contain a feature.ts file`);
|
|
65
|
+
}
|
|
66
|
+
return resolvedPath;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate and resolve an app path (can be relative or absolute)
|
|
70
|
+
* Validates that the web application directory exists with proper structure
|
|
71
|
+
* Returns the absolute path to the base app directory
|
|
72
|
+
*/
|
|
73
|
+
export function validateAndResolveAppPath(appPath) {
|
|
74
|
+
let resolvedBasePath;
|
|
75
|
+
// If it's an absolute path, use it as-is
|
|
76
|
+
if (isAbsolute(appPath)) {
|
|
77
|
+
resolvedBasePath = appPath;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// If it's relative, resolve from monorepo root
|
|
81
|
+
resolvedBasePath = join(getMonorepoRoot(), appPath);
|
|
82
|
+
}
|
|
83
|
+
if (!existsSync(resolvedBasePath)) {
|
|
84
|
+
throw new ValidationError(`App path '${appPath}' does not exist`);
|
|
85
|
+
}
|
|
86
|
+
if (!statSync(resolvedBasePath).isDirectory()) {
|
|
87
|
+
throw new ValidationError(`App path '${appPath}' is not a directory`);
|
|
88
|
+
}
|
|
89
|
+
// Get the web application directory path
|
|
90
|
+
const webAppPath = getWebApplicationPath(resolvedBasePath);
|
|
91
|
+
if (!existsSync(webAppPath)) {
|
|
92
|
+
const sfdxPath = join(resolvedBasePath, "src", "force-app", "main", "default", "webapplications", basename(resolvedBasePath));
|
|
93
|
+
const oldPath = join(resolvedBasePath, "webapplications", basename(resolvedBasePath));
|
|
94
|
+
throw new ValidationError(`Web application directory does not exist. Expected structure:\n` +
|
|
95
|
+
` SFDX: ${sfdxPath}\n` +
|
|
96
|
+
` Legacy: ${oldPath}`);
|
|
97
|
+
}
|
|
98
|
+
if (!statSync(webAppPath).isDirectory()) {
|
|
99
|
+
throw new ValidationError(`Web application path '${webAppPath}' is not a directory`);
|
|
100
|
+
}
|
|
101
|
+
// Check if it has a package.json
|
|
102
|
+
const packageJsonPath = join(webAppPath, "package.json");
|
|
103
|
+
if (!existsSync(packageJsonPath)) {
|
|
104
|
+
throw new ValidationError(`Web application '${webAppPath}' does not contain a package.json file`);
|
|
105
|
+
}
|
|
106
|
+
return resolvedBasePath;
|
|
107
|
+
}
|
|
108
|
+
const APP_FEATURE_NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
109
|
+
/**
|
|
110
|
+
* Validate that a app or feature name is valid
|
|
111
|
+
* - Must be in kebab-case (lowercase with hyphens)
|
|
112
|
+
* - Only alphanumeric characters and hyphens allowed
|
|
113
|
+
* - Cannot start or end with hyphens
|
|
114
|
+
* - No consecutive hyphens
|
|
115
|
+
* - Cannot be reserved names: "base", "cli"
|
|
116
|
+
* - Max 50 characters
|
|
117
|
+
*/
|
|
118
|
+
export function validateAppOrFeatureName(name) {
|
|
119
|
+
// Check empty
|
|
120
|
+
if (!name || name.trim().length === 0) {
|
|
121
|
+
throw new ValidationError("App or feature name cannot be empty");
|
|
122
|
+
}
|
|
123
|
+
// Check format
|
|
124
|
+
if (!APP_FEATURE_NAME_REGEX.test(name)) {
|
|
125
|
+
throw new ValidationError(`App or feature name '${name}' is invalid. Must be kebab-case (lowercase letters, numbers, and hyphens only).\nExamples: "nav-menu", "user-auth", "dashboard-v2"`);
|
|
126
|
+
}
|
|
127
|
+
// Check reserved names
|
|
128
|
+
const reserved = ["app", "feature"];
|
|
129
|
+
if (reserved.includes(name)) {
|
|
130
|
+
throw new ValidationError(`App or feature name '${name}' is reserved. Please choose a different name.`);
|
|
131
|
+
}
|
|
132
|
+
// Check length
|
|
133
|
+
if (name.length > 50) {
|
|
134
|
+
throw new ValidationError(`App or feature name '${name}' is too long. Maximum 50 characters.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforce/webapp-template-cli-experimental",
|
|
3
|
+
"version": "1.3.3",
|
|
4
|
+
"description": "CLI tool for applying feature patches to base apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"apply-patches": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:ui": "vitest run --ui",
|
|
21
|
+
"test:e2e": "vitest run e2e",
|
|
22
|
+
"test:unit": "vitest run unit",
|
|
23
|
+
"test:coverage": "vitest run --coverage"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"chalk": "^5.3.0",
|
|
27
|
+
"commander": "^12.0.0",
|
|
28
|
+
"fs-extra": "^11.2.0",
|
|
29
|
+
"ts-morph": "^27.0.2",
|
|
30
|
+
"turbowatch": "^2.29.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/fs-extra": "^11.0.4",
|
|
34
|
+
"@types/node": "^24.10.1",
|
|
35
|
+
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"@vitest/coverage-v8": "4.0.17",
|
|
37
|
+
"@vitest/ui": "^4.0.17",
|
|
38
|
+
"react-dom": "^19.2.1",
|
|
39
|
+
"tsx": "^4.19.2",
|
|
40
|
+
"typescript": "~5.9.3",
|
|
41
|
+
"vitest": "^4.0.17"
|
|
42
|
+
},
|
|
43
|
+
"gitHead": "80d16bc3ec3fc1ed55b8059f6389cf89eb464385"
|
|
44
|
+
}
|