Skip to content

Commit fa62bc1

Browse files
committed
fix: improve component resolution
1 parent eb409d7 commit fa62bc1

File tree

6 files changed

+117
-32
lines changed

6 files changed

+117
-32
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
"axios": "^1.7.9",
1919
"glob": "^10.4.5",
2020
"lwc": "~8.12.5",
21-
"node-fetch": "^3.3.2"
21+
"node-fetch": "^3.3.2",
22+
"xml2js": "^0.6.2"
2223
},
2324
"devDependencies": {
2425
"@oclif/plugin-command-snapshot": "^5.2.35",
2526
"@salesforce/cli-plugins-testkit": "^5.3.39",
2627
"@salesforce/dev-scripts": "^10.2.11",
2728
"@salesforce/plugin-command-reference": "^3.1.44",
2829
"@types/node-fetch": "^2.6.11",
30+
"@types/xml2js": "^0.4.14",
2931
"eslint-plugin-sf-plugin": "^1.20.15",
3032
"esmock": "^2.6.9",
3133
"oclif": "^4.17.27",

src/commands/lightning/dev/component.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,16 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import fs from 'node:fs';
98
import path from 'node:path';
109
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
11-
import { Messages } from '@salesforce/core';
10+
import { Messages, SfProject } from '@salesforce/core';
1211
import { cmpDev } from '@lwrjs/api';
12+
import { ComponentUtils } from '../../../shared/componentUtils.js';
1313
import { PromptUtils } from '../../../shared/promptUtils.js';
1414

1515
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
1616
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.component');
1717

18-
// TODO support other module directories
19-
const MODULES_DIR = path.resolve(path.join('force-app', 'main', 'default', 'lwc'));
20-
21-
function getDirectories(filePath: string): string[] {
22-
try {
23-
const items = fs.readdirSync(filePath);
24-
25-
const directories = items.filter((item) => fs.statSync(path.join(filePath, item)).isDirectory());
26-
27-
return directories;
28-
} catch (error) {
29-
return [];
30-
}
31-
}
32-
3318
export default class LightningDevComponent extends SfCommand<void> {
3419
public static readonly summary = messages.getMessage('summary');
3520
public static readonly description = messages.getMessage('description');
@@ -47,27 +32,35 @@ export default class LightningDevComponent extends SfCommand<void> {
4732
};
4833

4934
public async run(): Promise<void> {
35+
const project = await SfProject.resolve();
5036
const { flags } = await this.parse(LightningDevComponent);
5137

5238
let name = flags.name;
5339
if (!name) {
54-
const dirs = getDirectories(path.resolve(MODULES_DIR));
40+
const dirs = await ComponentUtils.getComponentPaths(project);
5541
if (!dirs) {
5642
throw new Error(messages.getMessage('error.directory'));
5743
}
5844

59-
const components = dirs.map((dir) => {
60-
const xmlPath = path.resolve(path.join(MODULES_DIR, dir, `${dir}.js-meta.xml`));
61-
const xmlContent = fs.readFileSync(xmlPath, 'utf-8');
62-
const label = xmlContent.match(/<masterLabel>(.*?)<\/masterLabel>/);
63-
const description = xmlContent.match(/<description>(.*?)<\/description>/);
64-
65-
return {
66-
name: dir,
67-
label: label ? label[1] : '',
68-
description: description ? description[1] : '',
69-
};
70-
});
45+
const components = (
46+
await Promise.all(
47+
dirs.map(async (dir) => {
48+
const xml = await ComponentUtils.getComponentMetadata(dir);
49+
if (!xml) {
50+
return undefined;
51+
}
52+
53+
const componentName = path.basename(dir);
54+
const label = ComponentUtils.componentNameToTitleCase(componentName);
55+
56+
return {
57+
name: componentName,
58+
label: xml.LightningComponentBundle.masterLabel ?? label,
59+
description: xml.LightningComponentBundle.description ?? '',
60+
};
61+
})
62+
)
63+
).filter((component) => !!component);
7164

7265
name = await PromptUtils.promptUserToSelectComponent(components);
7366
if (!name) {

src/shared/componentUtils.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import path from 'node:path';
8+
import fs from 'node:fs';
9+
import { SfProject } from '@salesforce/core';
10+
import { glob } from 'glob';
11+
import { parseStringPromise } from 'xml2js';
12+
13+
export type LwcMetadata = {
14+
LightningComponentBundle: {
15+
description?: string;
16+
masterLabel?: string;
17+
};
18+
};
19+
20+
export class ComponentUtils {
21+
public static componentNameToTitleCase(componentName: string): string {
22+
if (!componentName) {
23+
return '';
24+
}
25+
26+
return componentName.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase());
27+
}
28+
29+
public static async getComponentPaths(project: SfProject): Promise<string[]> {
30+
const packageDirs = project.getPackageDirectories();
31+
const namespacePaths = (
32+
await Promise.all(packageDirs.map((dir) => glob(`${dir.fullPath}/**/lwc`, { absolute: true })))
33+
).flat();
34+
35+
return (
36+
await Promise.all(
37+
namespacePaths.map(async (namespacePath): Promise<string[]> => {
38+
const children = await fs.promises.readdir(namespacePath, { withFileTypes: true });
39+
40+
return children
41+
.filter((child) => child.isDirectory())
42+
.map((child) => path.join(child.parentPath, child.name));
43+
})
44+
)
45+
).flat();
46+
}
47+
48+
public static async getComponentMetadata(dirname: string): Promise<LwcMetadata | undefined> {
49+
const componentName = path.basename(dirname);
50+
const metaXmlPath = path.join(dirname, `${componentName}.js-meta.xml`);
51+
if (!fs.existsSync(metaXmlPath)) {
52+
return undefined;
53+
}
54+
55+
const xmlContent = fs.readFileSync(metaXmlPath, 'utf8');
56+
const parsedData = (await parseStringPromise(xmlContent)) as LwcMetadata;
57+
if (!this.isLwcMetadata(parsedData)) {
58+
return undefined;
59+
}
60+
61+
return parsedData;
62+
}
63+
64+
private static isLwcMetadata(obj: unknown): obj is LwcMetadata {
65+
return (obj && typeof obj === 'object' && 'LightningComponentBundle' in obj) === true;
66+
}
67+
}

src/shared/promptUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class PromptUtils {
9292

9393
public static async promptUserToSelectComponent(components: Array<Record<string, string>>): Promise<string> {
9494
const choices = components.map((component) => ({
95-
name: component.label.length > 0 ? component.label : component.name,
95+
name: component.label ?? component.name,
9696
value: component.name,
9797
description: component.description,
9898
}));

test/shared/componentUtils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { expect } from 'chai';
9+
import { ComponentUtils } from '../../src/shared/componentUtils.js';
10+
11+
describe('componentUtils', () => {
12+
it('converts camel case component name to title case', () => {
13+
expect(ComponentUtils.componentNameToTitleCase('myButton')).to.equal('My Button');
14+
expect(ComponentUtils.componentNameToTitleCase('myButtonGroup')).to.equal('My Button Group');
15+
});
16+
});

yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4162,6 +4162,13 @@
41624162
resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd"
41634163
integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==
41644164

4165+
"@types/xml2js@^0.4.14":
4166+
version "0.4.14"
4167+
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a"
4168+
integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==
4169+
dependencies:
4170+
"@types/node" "*"
4171+
41654172
"@typescript-eslint/eslint-plugin@^6.21.0":
41664173
version "6.21.0"
41674174
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3"

0 commit comments

Comments
 (0)