Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# plugin-templates

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

[![Known Vulnerabilities](https://snyk.io/test/github/salesforcecli/plugin-templates/badge.svg)](https://snyk.io/test/github/salesforcecli/plugin-templates)
[![License](https://img.shields.io/npm/l/@salesforce/plugin-templates.svg)](https://github.com/salesforcecli/plugin-templates/blob/master/package.json)
Expand Down
1 change: 1 addition & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"json",
"login-url",
"loglevel",
"lwc-language",
"manifest",
"name",
"namespace",
Expand Down
12 changes: 12 additions & 0 deletions messages/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ By default, the generated sfdx-project.json file sets the sourceApiVersion prope

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

- Generate a project in which the Lightning Web Components use TypeScript rather than the default JavaScript:

<%= config.bin %> <%= command.id %> --name mywork --lwc-language typescript

# flags.name.summary

Name of the generated project.
Expand Down Expand Up @@ -93,3 +97,11 @@ Salesforce instance login URL.
# flags.login-url.description

Normally defaults to https://login.salesforce.com.

# flags.lwc-language.summary

Default language for Lightning Web Components.

# flags.lwc-language.description

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'.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dependencies": {
"@salesforce/core": "^8.27.1",
"@salesforce/sf-plugins-core": "^12",
"@salesforce/templates": "^66.7.4"
"@salesforce/templates": "^66.7.6"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^5.3.13",
Expand Down
9 changes: 9 additions & 0 deletions src/commands/template/generate/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export default class Project extends SfCommand<CreateOutput> {
aliases: ['loginurl'],
deprecateAliases: true,
}),
'lwc-language': Flags.option({
summary: messages.getMessage('flags.lwc-language.summary'),
description: messages.getMessage('flags.lwc-language.description'),
options: ['javascript', 'typescript'] as const,
Comment thread
shugo111 marked this conversation as resolved.
})(),
loglevel,
'api-version': Flags.orgApiVersion({
summary: messages.getMessage('flags.api-version.summary'),
Expand All @@ -82,6 +87,10 @@ export default class Project extends SfCommand<CreateOutput> {
defaultpackagedir: flags['default-package-dir'],
apiversion: flags['api-version'],
};
if (flags['lwc-language']) {
flagsAsOptions.lwcLanguage = flags['lwc-language'];
}

return runGenerator({
templateType: TemplateType.Project,
opts: flagsAsOptions,
Expand Down
117 changes: 117 additions & 0 deletions test/commands/template/generate/project/index.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,123 @@ describe('template generate project:', () => {
});
});

describe('TypeScript project creation', () => {
it('should create TypeScript project with all required files', () => {
execCmd('template generate project --projectname tsproject --lwc-language typescript', { ensureExitCode: 0 });

// Verify standard files exist
assert.file([path.join(session.project.dir, 'tsproject', 'config', 'project-scratch-def.json')]);
assert.file([path.join(session.project.dir, 'tsproject', 'README.md')]);
assert.file([path.join(session.project.dir, 'tsproject', 'sfdx-project.json')]);

// Verify TypeScript-specific files exist
assert.file([path.join(session.project.dir, 'tsproject', 'tsconfig.json')]);
assert.file([path.join(session.project.dir, 'tsproject', 'package.json')]);
assert.file([path.join(session.project.dir, 'tsproject', 'eslint.config.js')]);
assert.file([path.join(session.project.dir, 'tsproject', '.forceignore')]);
assert.file([path.join(session.project.dir, 'tsproject', '.gitignore')]);
});

it('should verify tsconfig.json has correct TypeScript configuration', () => {
execCmd('template generate project --projectname tsconfig-test --lwc-language typescript', { ensureExitCode: 0 });

const tsconfigPath = path.join(session.project.dir, 'tsconfig-test', 'tsconfig.json');
assert.file([tsconfigPath]);
assert.fileContent(tsconfigPath, '"erasableSyntaxOnly": true');
assert.fileContent(tsconfigPath, '"include"');
assert.fileContent(tsconfigPath, 'force-app/**/*.ts');
assert.fileContent(tsconfigPath, '"exclude"');
assert.fileContent(tsconfigPath, '**/__tests__/**');
});

it('should verify .forceignore excludes dist/ folder', () => {
execCmd('template generate project --projectname forceignore-test --lwc-language typescript', {
ensureExitCode: 0,
});

const forceignorePath = path.join(session.project.dir, 'forceignore-test', '.forceignore');
assert.file([forceignorePath]);
assert.fileContent(forceignorePath, 'tsconfig.json');
assert.fileContent(forceignorePath, '**/tsconfig.*.json');
});

it('should verify .gitignore excludes TypeScript build artifacts', () => {
execCmd('template generate project --projectname gitignore-test --lwc-language typescript', {
ensureExitCode: 0,
});

const gitignorePath = path.join(session.project.dir, 'gitignore-test', '.gitignore');
assert.file([gitignorePath]);
assert.fileContent(gitignorePath, '*.tsbuildinfo');
});

it('should verify package.json has TypeScript dependencies', () => {
execCmd('template generate project --projectname packagejson-test --lwc-language typescript', {
ensureExitCode: 0,
});

const packageJsonPath = path.join(session.project.dir, 'packagejson-test', 'package.json');
assert.file([packageJsonPath]);
assert.fileContent(packageJsonPath, '"typescript"');
assert.fileContent(packageJsonPath, '"@typescript-eslint/eslint-plugin"');
assert.fileContent(packageJsonPath, '"@typescript-eslint/parser"');
assert.fileContent(packageJsonPath, '"@types/jest"');
assert.fileContent(packageJsonPath, '"build": "tsc"');
assert.fileContent(packageJsonPath, '"build:watch": "tsc --watch"');
});

it('should verify sfdx-project.json includes defaultLwcLanguage field', () => {
execCmd('template generate project --projectname sfdx-test --lwc-language typescript', { ensureExitCode: 0 });

const sfdxProjectPath = path.join(session.project.dir, 'sfdx-test', 'sfdx-project.json');
assert.file([sfdxProjectPath]);
assert.fileContent(sfdxProjectPath, '"defaultLwcLanguage": "typescript"');
});

it('should verify ESLint config uses TypeScript parser for both JS and TS', () => {
execCmd('template generate project --projectname eslint-test --lwc-language typescript', { ensureExitCode: 0 });

const eslintConfigPath = path.join(session.project.dir, 'eslint-test', 'eslint.config.js');
assert.file([eslintConfigPath]);
assert.fileContent(eslintConfigPath, "files: ['**/lwc/**/*.{js,ts}']");
assert.fileContent(eslintConfigPath, 'parser: tseslint.parser');
assert.fileContent(eslintConfigPath, '@typescript-eslint');
});

it('should create JavaScript project without TypeScript files when using javascript flag', () => {
execCmd('template generate project --projectname jsproject --lwc-language javascript', { ensureExitCode: 0 });

// TypeScript files should NOT exist
assert.noFile([path.join(session.project.dir, 'jsproject', 'tsconfig.json')]);

// Standard files should exist
assert.file([path.join(session.project.dir, 'jsproject', 'package.json')]);
assert.file([path.join(session.project.dir, 'jsproject', 'eslint.config.js')]);

// Verify sfdx-project.json has javascript as default
const sfdxProjectPath = path.join(session.project.dir, 'jsproject', 'sfdx-project.json');
assert.fileContent(sfdxProjectPath, '"defaultLwcLanguage": "javascript"');

// Verify package.json does NOT have TypeScript dependencies
const packageJsonPath = path.join(session.project.dir, 'jsproject', 'package.json');
const packageContent = fs.readFileSync(packageJsonPath, 'utf8');
expect(packageContent).to.not.contain('typescript');
expect(packageContent).to.not.contain('@typescript-eslint');
});

it('should create default project without TypeScript files when no flag specified', () => {
execCmd('template generate project --projectname defaultproject', { ensureExitCode: 0 });

// TypeScript files should NOT exist
assert.noFile([path.join(session.project.dir, 'defaultproject', 'tsconfig.json')]);

// sfdx-project.json should NOT have defaultLwcLanguage field
const sfdxProjectPath = path.join(session.project.dir, 'defaultproject', 'sfdx-project.json');
const sfdxContent = fs.readFileSync(sfdxProjectPath, 'utf8');
expect(sfdxContent).to.not.contain('defaultLwcLanguage');
});
});

describe('project creation failures', () => {
it('should throw missing required flag error', () => {
const stderr = execCmd('template generate project').shellOutput.stderr;
Expand Down
27 changes: 17 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,13 @@
dependencies:
"@salesforce/ts-types" "^2.0.12"

"@salesforce/kit@^3.2.6":
version "3.2.6"
resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.2.6.tgz#6a6c13b463b51694a43d61094af67171086ed4f5"
integrity sha512-O8S4LWerHa9Zosqh+IoQjgLtpxMOfObRxaRnUdRV4MLtFUi+bQxQiyFvve6eEaBaMP1b1xVDQpvSvQ+PXEDGFQ==
dependencies:
"@salesforce/ts-types" "^2.0.12"

"@salesforce/plugin-command-reference@^3.1.82":
version "3.1.82"
resolved "https://registry.yarnpkg.com/@salesforce/plugin-command-reference/-/plugin-command-reference-3.1.82.tgz#45e439760b05b4b7232763172426ad22b90e52da"
Expand Down Expand Up @@ -1649,18 +1656,18 @@
cli-progress "^3.12.0"
terminal-link "^3.0.0"

"@salesforce/templates@^66.7.4":
version "66.7.4"
resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.4.tgz#bd4d69c08084535822e111dd94912a0a93e5551f"
integrity sha512-nELEovHB5ZAGQCp9Kn7WkTCslM5RvyqPZbES78hvKlidxWSWhIYamnRmjmJpsH0VVTK/Z6serEHGUwS1QurETw==
"@salesforce/templates@^66.7.6":
version "66.7.6"
resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-66.7.6.tgz#36c597466acd62b3f98c98e6bc097d33794d769f"
integrity sha512-lTS4lk2oV5QF0XrWxTIjRjwFKboNfwUTb6G9Jp3rXx/MR5ajwv+vDbld+vhNzDg7gjLoGB3e4GJBQqNn8GnEtg==
dependencies:
"@salesforce/kit" "^3.2.4"
"@salesforce/kit" "^3.2.6"
ejs "^3.1.10"
got "^11.8.6"
hpagent "^1.2.0"
mime-types "^3.0.2"
proxy-from-env "^1.1.0"
tar "^7.5.12"
tar "^7.5.13"
tslib "^2.8.1"

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

tar@^7.5.12:
version "7.5.12"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.12.tgz#f8705c00ca1001b8b60bc44db1ab26573736b871"
integrity sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==
tar@^7.5.13:
version "7.5.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d"
integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==
dependencies:
"@isaacs/fs-minipass" "^4.0.0"
chownr "^3.0.0"
Expand Down
Loading