Skip to content

Commit 2ea6c99

Browse files
authored
Merge pull request #876 from salesforcecli/feat/typescript-project-generation
W-21523376: feat: Add --lwc-language flag to project generate command
2 parents 621be92 + 3358b8c commit 2ea6c99

7 files changed

Lines changed: 158 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# plugin-templates
22

3-
This repository provides a series of commands, templates, and generators for various metadata types.
3+
This repository provides a series of commands, templates, and generators for various metadata types, including support for TypeScript Lightning Web Components.
44

55
[![Known Vulnerabilities](https://snyk.io/test/github/salesforcecli/plugin-templates/badge.svg)](https://snyk.io/test/github/salesforcecli/plugin-templates)
66
[![License](https://img.shields.io/npm/l/@salesforce/plugin-templates.svg)](https://github.com/salesforcecli/plugin-templates/blob/master/package.json)

command-snapshot.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"json",
116116
"login-url",
117117
"loglevel",
118+
"lwc-language",
118119
"manifest",
119120
"name",
120121
"namespace",

messages/project.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ By default, the generated sfdx-project.json file sets the sourceApiVersion prope
2626

2727
<%= config.bin %> <%= command.id %> --name mywork --template empty
2828

29+
- Generate a project in which the Lightning Web Components use TypeScript rather than the default JavaScript:
30+
31+
<%= config.bin %> <%= command.id %> --name mywork --lwc-language typescript
32+
2933
# flags.name.summary
3034

3135
Name of the generated project.
@@ -93,3 +97,11 @@ Salesforce instance login URL.
9397
# flags.login-url.description
9498

9599
Normally defaults to https://login.salesforce.com.
100+
101+
# flags.lwc-language.summary
102+
103+
Default language for Lightning Web Components.
104+
105+
# flags.lwc-language.description
106+
107+
Sets the default language for Lightning Web Components in this project. When set to 'typescript', generates TypeScript configuration files (tsconfig.json, package.json with TypeScript dependencies, and TypeScript-aware ESLint config). TypeScript projects compile locally to a dist/ folder for validation, but deploy raw .ts files to Salesforce for server-side type stripping. Defaults to 'javascript'.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"dependencies": {
1010
"@salesforce/core": "^8.27.1",
1111
"@salesforce/sf-plugins-core": "^12",
12-
"@salesforce/templates": "^66.7.4"
12+
"@salesforce/templates": "^66.7.6"
1313
},
1414
"devDependencies": {
1515
"@oclif/plugin-command-snapshot": "^5.3.13",

src/commands/template/generate/project/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ export default class Project extends SfCommand<CreateOutput> {
6363
aliases: ['loginurl'],
6464
deprecateAliases: true,
6565
}),
66+
'lwc-language': Flags.option({
67+
summary: messages.getMessage('flags.lwc-language.summary'),
68+
description: messages.getMessage('flags.lwc-language.description'),
69+
options: ['javascript', 'typescript'] as const,
70+
})(),
6671
loglevel,
6772
'api-version': Flags.orgApiVersion({
6873
summary: messages.getMessage('flags.api-version.summary'),
@@ -82,6 +87,10 @@ export default class Project extends SfCommand<CreateOutput> {
8287
defaultpackagedir: flags['default-package-dir'],
8388
apiversion: flags['api-version'],
8489
};
90+
if (flags['lwc-language']) {
91+
flagsAsOptions.lwcLanguage = flags['lwc-language'];
92+
}
93+
8594
return runGenerator({
8695
templateType: TemplateType.Project,
8796
opts: flagsAsOptions,

test/commands/template/generate/project/index.nut.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,123 @@ describe('template generate project:', () => {
339339
});
340340
});
341341

342+
describe('TypeScript project creation', () => {
343+
it('should create TypeScript project with all required files', () => {
344+
execCmd('template generate project --projectname tsproject --lwc-language typescript', { ensureExitCode: 0 });
345+
346+
// Verify standard files exist
347+
assert.file([path.join(session.project.dir, 'tsproject', 'config', 'project-scratch-def.json')]);
348+
assert.file([path.join(session.project.dir, 'tsproject', 'README.md')]);
349+
assert.file([path.join(session.project.dir, 'tsproject', 'sfdx-project.json')]);
350+
351+
// Verify TypeScript-specific files exist
352+
assert.file([path.join(session.project.dir, 'tsproject', 'tsconfig.json')]);
353+
assert.file([path.join(session.project.dir, 'tsproject', 'package.json')]);
354+
assert.file([path.join(session.project.dir, 'tsproject', 'eslint.config.js')]);
355+
assert.file([path.join(session.project.dir, 'tsproject', '.forceignore')]);
356+
assert.file([path.join(session.project.dir, 'tsproject', '.gitignore')]);
357+
});
358+
359+
it('should verify tsconfig.json has correct TypeScript configuration', () => {
360+
execCmd('template generate project --projectname tsconfig-test --lwc-language typescript', { ensureExitCode: 0 });
361+
362+
const tsconfigPath = path.join(session.project.dir, 'tsconfig-test', 'tsconfig.json');
363+
assert.file([tsconfigPath]);
364+
assert.fileContent(tsconfigPath, '"erasableSyntaxOnly": true');
365+
assert.fileContent(tsconfigPath, '"include"');
366+
assert.fileContent(tsconfigPath, 'force-app/**/*.ts');
367+
assert.fileContent(tsconfigPath, '"exclude"');
368+
assert.fileContent(tsconfigPath, '**/__tests__/**');
369+
});
370+
371+
it('should verify .forceignore excludes dist/ folder', () => {
372+
execCmd('template generate project --projectname forceignore-test --lwc-language typescript', {
373+
ensureExitCode: 0,
374+
});
375+
376+
const forceignorePath = path.join(session.project.dir, 'forceignore-test', '.forceignore');
377+
assert.file([forceignorePath]);
378+
assert.fileContent(forceignorePath, 'tsconfig.json');
379+
assert.fileContent(forceignorePath, '**/tsconfig.*.json');
380+
});
381+
382+
it('should verify .gitignore excludes TypeScript build artifacts', () => {
383+
execCmd('template generate project --projectname gitignore-test --lwc-language typescript', {
384+
ensureExitCode: 0,
385+
});
386+
387+
const gitignorePath = path.join(session.project.dir, 'gitignore-test', '.gitignore');
388+
assert.file([gitignorePath]);
389+
assert.fileContent(gitignorePath, '*.tsbuildinfo');
390+
});
391+
392+
it('should verify package.json has TypeScript dependencies', () => {
393+
execCmd('template generate project --projectname packagejson-test --lwc-language typescript', {
394+
ensureExitCode: 0,
395+
});
396+
397+
const packageJsonPath = path.join(session.project.dir, 'packagejson-test', 'package.json');
398+
assert.file([packageJsonPath]);
399+
assert.fileContent(packageJsonPath, '"typescript"');
400+
assert.fileContent(packageJsonPath, '"@typescript-eslint/eslint-plugin"');
401+
assert.fileContent(packageJsonPath, '"@typescript-eslint/parser"');
402+
assert.fileContent(packageJsonPath, '"@types/jest"');
403+
assert.fileContent(packageJsonPath, '"build": "tsc"');
404+
assert.fileContent(packageJsonPath, '"build:watch": "tsc --watch"');
405+
});
406+
407+
it('should verify sfdx-project.json includes defaultLwcLanguage field', () => {
408+
execCmd('template generate project --projectname sfdx-test --lwc-language typescript', { ensureExitCode: 0 });
409+
410+
const sfdxProjectPath = path.join(session.project.dir, 'sfdx-test', 'sfdx-project.json');
411+
assert.file([sfdxProjectPath]);
412+
assert.fileContent(sfdxProjectPath, '"defaultLwcLanguage": "typescript"');
413+
});
414+
415+
it('should verify ESLint config uses TypeScript parser for both JS and TS', () => {
416+
execCmd('template generate project --projectname eslint-test --lwc-language typescript', { ensureExitCode: 0 });
417+
418+
const eslintConfigPath = path.join(session.project.dir, 'eslint-test', 'eslint.config.js');
419+
assert.file([eslintConfigPath]);
420+
assert.fileContent(eslintConfigPath, "files: ['**/lwc/**/*.{js,ts}']");
421+
assert.fileContent(eslintConfigPath, 'parser: tseslint.parser');
422+
assert.fileContent(eslintConfigPath, '@typescript-eslint');
423+
});
424+
425+
it('should create JavaScript project without TypeScript files when using javascript flag', () => {
426+
execCmd('template generate project --projectname jsproject --lwc-language javascript', { ensureExitCode: 0 });
427+
428+
// TypeScript files should NOT exist
429+
assert.noFile([path.join(session.project.dir, 'jsproject', 'tsconfig.json')]);
430+
431+
// Standard files should exist
432+
assert.file([path.join(session.project.dir, 'jsproject', 'package.json')]);
433+
assert.file([path.join(session.project.dir, 'jsproject', 'eslint.config.js')]);
434+
435+
// Verify sfdx-project.json has javascript as default
436+
const sfdxProjectPath = path.join(session.project.dir, 'jsproject', 'sfdx-project.json');
437+
assert.fileContent(sfdxProjectPath, '"defaultLwcLanguage": "javascript"');
438+
439+
// Verify package.json does NOT have TypeScript dependencies
440+
const packageJsonPath = path.join(session.project.dir, 'jsproject', 'package.json');
441+
const packageContent = fs.readFileSync(packageJsonPath, 'utf8');
442+
expect(packageContent).to.not.contain('typescript');
443+
expect(packageContent).to.not.contain('@typescript-eslint');
444+
});
445+
446+
it('should create default project without TypeScript files when no flag specified', () => {
447+
execCmd('template generate project --projectname defaultproject', { ensureExitCode: 0 });
448+
449+
// TypeScript files should NOT exist
450+
assert.noFile([path.join(session.project.dir, 'defaultproject', 'tsconfig.json')]);
451+
452+
// sfdx-project.json should NOT have defaultLwcLanguage field
453+
const sfdxProjectPath = path.join(session.project.dir, 'defaultproject', 'sfdx-project.json');
454+
const sfdxContent = fs.readFileSync(sfdxProjectPath, 'utf8');
455+
expect(sfdxContent).to.not.contain('defaultLwcLanguage');
456+
});
457+
});
458+
342459
describe('project creation failures', () => {
343460
it('should throw missing required flag error', () => {
344461
const stderr = execCmd('template generate project').shellOutput.stderr;

yarn.lock

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,13 @@
15961596
dependencies:
15971597
"@salesforce/ts-types" "^2.0.12"
15981598

1599+
"@salesforce/kit@^3.2.6":
1600+
version "3.2.6"
1601+
resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.2.6.tgz#6a6c13b463b51694a43d61094af67171086ed4f5"
1602+
integrity sha512-O8S4LWerHa9Zosqh+IoQjgLtpxMOfObRxaRnUdRV4MLtFUi+bQxQiyFvve6eEaBaMP1b1xVDQpvSvQ+PXEDGFQ==
1603+
dependencies:
1604+
"@salesforce/ts-types" "^2.0.12"
1605+
15991606
"@salesforce/plugin-command-reference@^3.1.82":
16001607
version "3.1.82"
16011608
resolved "https://registry.yarnpkg.com/@salesforce/plugin-command-reference/-/plugin-command-reference-3.1.82.tgz#45e439760b05b4b7232763172426ad22b90e52da"
@@ -1649,18 +1656,18 @@
16491656
cli-progress "^3.12.0"
16501657
terminal-link "^3.0.0"
16511658

1652-
"@salesforce/templates@^66.7.4":
1653-
version "66.7.4"
1654-
resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.4.tgz#bd4d69c08084535822e111dd94912a0a93e5551f"
1655-
integrity sha512-nELEovHB5ZAGQCp9Kn7WkTCslM5RvyqPZbES78hvKlidxWSWhIYamnRmjmJpsH0VVTK/Z6serEHGUwS1QurETw==
1659+
"@salesforce/templates@^66.7.6":
1660+
version "66.7.6"
1661+
resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.6.tgz#36c597466acd62b3f98c98e6bc097d33794d769f"
1662+
integrity sha512-lTS4lk2oV5QF0XrWxTIjRjwFKboNfwUTb6G9Jp3rXx/MR5ajwv+vDbld+vhNzDg7gjLoGB3e4GJBQqNn8GnEtg==
16561663
dependencies:
1657-
"@salesforce/kit" "^3.2.4"
1664+
"@salesforce/kit" "^3.2.6"
16581665
ejs "^3.1.10"
16591666
got "^11.8.6"
16601667
hpagent "^1.2.0"
16611668
mime-types "^3.0.2"
16621669
proxy-from-env "^1.1.0"
1663-
tar "^7.5.12"
1670+
tar "^7.5.13"
16641671
tslib "^2.8.1"
16651672

16661673
"@salesforce/ts-types@^2.0.11", "@salesforce/ts-types@^2.0.12":
@@ -7382,10 +7389,10 @@ supports-preserve-symlinks-flag@^1.0.0:
73827389
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
73837390
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
73847391

7385-
tar@^7.5.12:
7386-
version "7.5.12"
7387-
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.12.tgz#f8705c00ca1001b8b60bc44db1ab26573736b871"
7388-
integrity sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==
7392+
tar@^7.5.13:
7393+
version "7.5.13"
7394+
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d"
7395+
integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==
73897396
dependencies:
73907397
"@isaacs/fs-minipass" "^4.0.0"
73917398
chownr "^3.0.0"

0 commit comments

Comments
 (0)