Skip to content

Commit 325efc4

Browse files
author
Shubham Goyal
committed
feat: add TypeScript support to VS Code extension commands
- Add 3-tier priority cascade for LWC language selection (sfdx-project.json → legacy flag → prompt) - Add backward compatibility for preview.typeScriptSupport flag with deprecation warning - Add error recovery to Effect chain in createLwc command - Pass lwcLanguage parameter to project generation command - Add comprehensive test coverage for parameterGatherers with 7 new test cases - Add npm-packages-offline-cache to .gitignore
1 parent 9fff9d1 commit 325efc4

6 files changed

Lines changed: 126 additions & 26 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,4 @@ packages/salesforcedx-vscode-lightning/tern/
148148

149149
# Generated resources (build:icons in salesforcedx-vscode-services)
150150
packages/salesforcedx-vscode-services/resources/
151+
npm-packages-offline-cache/

packages/salesforcedx-vscode-core/package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,6 @@
8181
"connected_org_walkthrough_set_alt": "changing default org",
8282
"connected_org_walkthrough_open_title": "Open and Manage Your Default Org",
8383
"connected_org_walkthrough_open_description": "Once you have a default org set, you can easily access it:\n\n**Open your default org:**\n1. Click the open org icon next to the default org icon in the footer, or\n2. Use Command Palette: **SFDX: Open Default Org**\n\n**Manage your org:**\nTo manage your default org, you can:\n1. **SFDX: Log Out from Default Org**\n2. **SFDX: Display Org Details for Default Org**\n\nOpening your org launches it in your default browser for easy access to the Salesforce interface.",
84-
"connected_org_walkthrough_open_alt": "opening and managing org"
84+
"connected_org_walkthrough_open_alt": "opening and managing org",
85+
"typescript_legacy_flag_deprecation": "The 'preview.typeScriptSupport' setting is deprecated. Please set 'defaultLWCLanguage': 'typescript' in your sfdx-project.json instead."
8586
}

packages/salesforcedx-vscode-core/src/commands/projectGenerate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class LibraryProjectGenerateExecutor extends LibraryBaseTemplateCommand<ProjectN
5656
}
5757

5858
public constructTemplateOptions(data: ProjectNameAndPathAndTemplate) {
59+
// NOTE: lwcLanguage support requires @salesforce/templates >= 66.5.0 (or next version after 66.4.1)
60+
// Ensure package.json dependency is updated before merging this PR
5961
const templateOptions: ProjectOptions = {
6062
projectname: data.projectName,
6163
template: data.projectTemplate,

packages/salesforcedx-vscode-core/src/commands/util/parameterGatherers.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,30 @@ export class SelectLwcComponentType implements ParametersGatherer<{ extension: s
180180
} else if (defaultLWCLanguage === 'javascript') {
181181
return { type: 'CONTINUE', data: { extension: 'JavaScript' } };
182182
}
183-
} catch {
184-
// Project config not available, continue to prompt
183+
} catch (error) {
184+
// Project config not available, continue to fallback mechanisms
185+
console.warn('Could not read defaultLWCLanguage from sfdx-project.json:', error);
185186
}
186187

187-
// Priority 2: No default set, prompt user to choose (TypeScript is always visible)
188+
// Priority 2: Check legacy preview.typeScriptSupport flag for backward compatibility
189+
try {
190+
const legacyFlag = vscode.workspace
191+
.getConfiguration('salesforcedx-vscode-lwc')
192+
.get<boolean>('preview.typeScriptSupport', false);
193+
194+
if (legacyFlag) {
195+
// Show deprecation warning
196+
void vscode.window.showInformationMessage(
197+
nls.localize('typescript_legacy_flag_deprecation') ??
198+
'The "preview.typeScriptSupport" setting is deprecated. Please set "defaultLWCLanguage": "typescript" in your sfdx-project.json instead.'
199+
);
200+
return { type: 'CONTINUE', data: { extension: 'TypeScript' } };
201+
}
202+
} catch (error) {
203+
console.warn('Could not read legacy preview.typeScriptSupport flag:', error);
204+
}
205+
206+
// Priority 3: No default set, prompt user to choose (TypeScript is always visible)
188207
const lwcComponentTypes = ['JavaScript', 'TypeScript'];
189208
const lwcComponentType = await this.showMenu(lwcComponentTypes, 'parameter_gatherer_select_lwc_type');
190209
return lwcComponentType

packages/salesforcedx-vscode-core/test/jest/commands/util/parameterGatherers.test.ts

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import * as vscode from 'vscode';
99
import { SelectFileName, SelectLwcComponentType } from '../../../../src/commands/util/parameterGatherers';
1010
import { nls } from '../../../../src/messages';
11+
import { SalesforceProjectConfig } from '../../../../src/salesforceProject';
1112

1213
describe('ParameterGatherers Unit Tests.', () => {
1314
describe('SelectFileName', () => {
@@ -94,63 +95,136 @@ describe('ParameterGatherers Unit Tests.', () => {
9495
});
9596

9697
describe('SelectLwcComponentType', () => {
97-
let typeScriptSupportSetting = true;
98-
let workspaceConfigurationSpy: jest.SpyInstance;
99-
const mockConfig = {
100-
get: () => typeScriptSupportSetting
101-
};
98+
let getValueSpy: jest.SpyInstance;
99+
let getConfigSpy: jest.SpyInstance;
100+
let showInfoMessageSpy: jest.SpyInstance;
101+
102102
beforeEach(() => {
103-
typeScriptSupportSetting = true;
104-
workspaceConfigurationSpy = jest.spyOn(vscode.workspace, 'getConfiguration');
103+
getValueSpy = jest.spyOn(SalesforceProjectConfig, 'getValue');
104+
getConfigSpy = jest.spyOn(vscode.workspace, 'getConfiguration');
105+
showInfoMessageSpy = jest.spyOn(vscode.window, 'showInformationMessage');
106+
});
107+
108+
afterEach(() => {
109+
jest.restoreAllMocks();
105110
});
106-
it('Should return JavaScript option when no config value', async () => {
107-
typeScriptSupportSetting = false;
108-
workspaceConfigurationSpy.mockReturnValue(mockConfig);
111+
112+
it('Should return TypeScript when defaultLWCLanguage is typescript', async () => {
113+
getValueSpy.mockResolvedValue('typescript');
109114
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
110-
const showMenuSpy = jest.spyOn(selectLwcComponentTypeInstance, 'showMenu').mockImplementation(jest.fn());
115+
const showMenuSpy = jest.spyOn(selectLwcComponentTypeInstance, 'showMenu');
116+
111117
const result = await selectLwcComponentTypeInstance.gather();
118+
119+
expect(result).toEqual({
120+
type: 'CONTINUE',
121+
data: { extension: 'TypeScript' }
122+
});
123+
expect(showMenuSpy).not.toHaveBeenCalled();
124+
});
125+
126+
it('Should return JavaScript when defaultLWCLanguage is javascript', async () => {
127+
getValueSpy.mockResolvedValue('javascript');
128+
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
129+
const showMenuSpy = jest.spyOn(selectLwcComponentTypeInstance, 'showMenu');
130+
131+
const result = await selectLwcComponentTypeInstance.gather();
132+
112133
expect(result).toEqual({
113134
type: 'CONTINUE',
114135
data: { extension: 'JavaScript' }
115136
});
116137
expect(showMenuSpy).not.toHaveBeenCalled();
117138
});
118139

119-
it('Should return TypeScript when selected from menu', async () => {
120-
workspaceConfigurationSpy.mockReturnValue(mockConfig);
140+
it('Should prompt user when defaultLWCLanguage is undefined', async () => {
141+
getValueSpy.mockResolvedValue(undefined);
142+
const mockConfig = { get: jest.fn().mockReturnValue(false) };
143+
getConfigSpy.mockReturnValue(mockConfig);
144+
121145
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
122146
const showMenuSpy = jest
123147
.spyOn(selectLwcComponentTypeInstance, 'showMenu')
124-
.mockImplementation(jest.fn().mockReturnValueOnce('TypeScript'));
148+
.mockResolvedValue('TypeScript');
149+
125150
const result = await selectLwcComponentTypeInstance.gather();
151+
126152
expect(result).toEqual({
127153
type: 'CONTINUE',
128154
data: { extension: 'TypeScript' }
129155
});
130156
expect(showMenuSpy).toHaveBeenCalled();
131157
});
132158

133-
it('Should return JavaScript when selected from menu', async () => {
134-
workspaceConfigurationSpy.mockReturnValue(mockConfig);
159+
it('Should fall back to legacy flag when defaultLWCLanguage throws error', async () => {
160+
getValueSpy.mockRejectedValue(new Error('Config not available'));
161+
const mockConfig = { get: jest.fn().mockReturnValue(true) };
162+
getConfigSpy.mockReturnValue(mockConfig);
163+
showInfoMessageSpy.mockResolvedValue(undefined);
164+
165+
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
166+
const showMenuSpy = jest.spyOn(selectLwcComponentTypeInstance, 'showMenu');
167+
168+
const result = await selectLwcComponentTypeInstance.gather();
169+
170+
expect(result).toEqual({
171+
type: 'CONTINUE',
172+
data: { extension: 'TypeScript' }
173+
});
174+
expect(showInfoMessageSpy).toHaveBeenCalled();
175+
expect(showMenuSpy).not.toHaveBeenCalled();
176+
});
177+
178+
it('Should show deprecation warning for legacy flag', async () => {
179+
getValueSpy.mockResolvedValue(undefined);
180+
const mockConfig = { get: jest.fn().mockReturnValue(true) };
181+
getConfigSpy.mockReturnValue(mockConfig);
182+
showInfoMessageSpy.mockResolvedValue(undefined);
183+
184+
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
185+
186+
const result = await selectLwcComponentTypeInstance.gather();
187+
188+
expect(result).toEqual({
189+
type: 'CONTINUE',
190+
data: { extension: 'TypeScript' }
191+
});
192+
expect(showInfoMessageSpy).toHaveBeenCalledWith(
193+
expect.stringContaining('deprecated')
194+
);
195+
});
196+
197+
it('Should prompt user when both configs are not set', async () => {
198+
getValueSpy.mockResolvedValue(undefined);
199+
const mockConfig = { get: jest.fn().mockReturnValue(false) };
200+
getConfigSpy.mockReturnValue(mockConfig);
201+
135202
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
136203
const showMenuSpy = jest
137204
.spyOn(selectLwcComponentTypeInstance, 'showMenu')
138-
.mockImplementation(jest.fn().mockReturnValueOnce('JavaScript'));
205+
.mockResolvedValue('JavaScript');
206+
139207
const result = await selectLwcComponentTypeInstance.gather();
208+
140209
expect(result).toEqual({
141210
type: 'CONTINUE',
142211
data: { extension: 'JavaScript' }
143212
});
144-
expect(showMenuSpy).toHaveBeenCalled();
213+
expect(showMenuSpy).toHaveBeenCalledWith(['JavaScript', 'TypeScript'], 'parameter_gatherer_select_lwc_type');
145214
});
146215

147-
it('Should cancel when selected from menu', async () => {
148-
workspaceConfigurationSpy.mockReturnValue(mockConfig);
216+
it('Should return CANCEL when user cancels prompt', async () => {
217+
getValueSpy.mockResolvedValue(undefined);
218+
const mockConfig = { get: jest.fn().mockReturnValue(false) };
219+
getConfigSpy.mockReturnValue(mockConfig);
220+
149221
const selectLwcComponentTypeInstance = new SelectLwcComponentType();
150222
const showMenuSpy = jest
151223
.spyOn(selectLwcComponentTypeInstance, 'showMenu')
152-
.mockImplementation(jest.fn().mockReturnValueOnce(null));
224+
.mockResolvedValue(undefined);
225+
153226
const result = await selectLwcComponentTypeInstance.gather();
227+
154228
expect(result).toEqual({
155229
type: 'CANCEL'
156230
});

packages/salesforcedx-vscode-metadata/src/commands/createLwc.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ export const createLwcCommand = Effect.fn('createLwcCommand')(function* (outputD
118118
const outputDirUri = outputDirParam ?? (yield* promptForOutputDir(project));
119119
if (!outputDirUri) return undefined;
120120

121-
const templateOpt = yield* determineComponentTemplate(project);
121+
// Determine template with error recovery - if project config fails, prompt user
122+
const templateOpt = yield* determineComponentTemplate(project).pipe(
123+
Effect.catchAll(() => promptForComponentType())
124+
);
122125
if (Option.isNone(templateOpt)) return undefined;
123126
const template = templateOpt.value;
124127

0 commit comments

Comments
 (0)