Skip to content

Commit

Permalink
WIP: Transform generated project to npm workspace
Browse files Browse the repository at this point in the history
- Align template structure with target structure
- Integrate a global tsconfig.build.json
- Unify how build and compile is done
  • Loading branch information
kaisalmen committed Jun 13, 2024
1 parent c8e01a2 commit 216df86
Show file tree
Hide file tree
Showing 54 changed files with 336 additions and 194 deletions.
179 changes: 110 additions & 69 deletions packages/generator-langium/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ import * as url from 'node:url';

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

const TEMPLATE_CORE_DIR = '../templates/core';
const TEMPLATE_VSCODE_DIR = '../templates/vscode';
const TEMPLATE_CLI_DIR = '../templates/cli';
const TEMPLATE_WEB_DIR = '../templates/web';
const TEMPLATE_TEST_DIR = '../templates/test';
const BASE_DIR = '../templates';
const PACKAGE_LANGUAGE = 'packages/language';
const PACKAGE_CLI = 'packages/cli';
const PACKAGE_WEB = 'packages/web';
const PACKAGE_EXTENSION = 'packages/extension';
// const TEMPLATE_TEST_DIR = '../templates/packages/language/test';
const USER_DIR = '.';

const EXTENSION_NAME = /<%= extension-name %>/g;
const RAW_LANGUAGE_NAME = /<%= RawLanguageName %>/g;
const FILE_EXTENSION = /"?<%= file-extension %>"?/g;
const FILE_EXTENSION_GLOB = /<%= file-glob-extension %>/g;
const TSCONFIG_BASE_NAME = /<%= tsconfig %>/g;

const LANGUAGE_NAME = /<%= LanguageName %>/g;
const LANGUAGE_ID = /<%= language-id %>/g;
Expand Down Expand Up @@ -171,96 +171,145 @@ export class LangiumGenerator extends Generator {
);
const languageId = _.kebabCase(this.answers.rawLanguageName);

const referencedTsconfigBaseName = this.answers.includeTest ? 'tsconfig.src.json' : 'tsconfig.json';
const templateCopyOptions: CopyOptions = {
process: content => this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, referencedTsconfigBaseName, content),
process: content => this._replaceTemplateWords(fileExtensionGlob, languageName, languageId, content),
processDestinationPath: path => this._replaceTemplateNames(languageId, path)
};

this.sourceRoot(path.join(__dirname, TEMPLATE_CORE_DIR));
const pkgJson = this.fs.readJSON(path.join(this.sourceRoot(), '.package.json'));
this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4);

for (const path of ['.', '.vscode', '.eslintrc.json']) {
const pathBase = path.join(__dirname, BASE_DIR);
this.sourceRoot(pathBase);
const mainPackageJson = this.fs.readJSON(path.join(this.sourceRoot(), 'package.json'));

const baseFiles = [
'.eslintrc.json',
'tsconfig.json',
'tsconfig.build.json',
'.vscode'
];
for (const path of baseFiles) {
this.fs.copy(
this.templatePath(path),
this._extensionPath(path),
templateCopyOptions
);
}

// .gitignore files don't get published to npm, so we need to copy it under a different name
this.fs.copy(this.templatePath('../gitignore.txt'), this._extensionPath('.gitignore'));
this.fs.copy(this.templatePath('gitignore.txt'), this._extensionPath('.gitignore'));

this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_LANGUAGE}`));
// omit .vscode-extensions.json for now
const languageFiles = [
'package.json',
'langium-config.json',
'langium-quickstart.md',
'tsconfig.json',
'tsconfig.src.json',
'tsconfig.test.json',
'vitest.config.ts',
'src',
'test'
];
for (const path of languageFiles) {
this.fs.copy(
this.templatePath(path),
this._extensionPath(`${PACKAGE_LANGUAGE}/${path}`),
templateCopyOptions
);
}

if (this.answers.includeVSCode) {
this.sourceRoot(path.join(__dirname, TEMPLATE_VSCODE_DIR));
const pkgJson = this.fs.readJSON(path.join(this.sourceRoot(), '.package.json'));
this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4);
this.sourceRoot(path.join(__dirname, TEMPLATE_VSCODE_DIR));
for (const path of ['.', '.vscode', '.vscodeignore']) {
if (this.answers.includeCLI) {
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_CLI}`));
const cliFiles = [
'package.json',
'tsconfig.json',
'bin',
'src'
];
for (const path of cliFiles) {
this.fs.copy(
this.templatePath(path),
this._extensionPath(path),
this._extensionPath(`${PACKAGE_CLI}/${path}`),
templateCopyOptions
);
}
mainPackageJson.workspaces.push('packages/cli');
}

if (this.answers.includeCLI) {
this.sourceRoot(path.join(__dirname, TEMPLATE_CLI_DIR));
const pkgJson = this.fs.readJSON(path.join(this.sourceRoot(), '.package.json'));
this.fs.extendJSON(this._extensionPath('package-template.json'),pkgJson, undefined, 4);
for (const path of ['.']) {
if (this.answers.includeWeb) {
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_WEB}`));
const webFiles = [
'package.json',
'langium-config.json',
'language-configuration.json',
'tsconfig.json',
'vite.config.ts',
'src',
'static'
];
for (const path of webFiles) {
this.fs.copy(
this.templatePath(path),
this._extensionPath(path),
this._extensionPath(`${PACKAGE_WEB}/${path}`),
templateCopyOptions
);
}
}

if (this.answers.includeWeb) {
this.sourceRoot(path.join(__dirname, TEMPLATE_WEB_DIR));
const pkgJson = this.fs.readJSON(path.join(this.sourceRoot(), '.package.json'));
this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4);
this.sourceRoot(path.join(__dirname, TEMPLATE_WEB_DIR));
for (const path of ['.']) {
if (this.answers.includeVSCode) {
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_EXTENSION}`));
const extensionFiles = [
'.vscodeignore',
'esbuild.mjs',
'language-configuration.json',
'package.json',
'tsconfig.json',
'src'
];
for (const path of extensionFiles) {
this.fs.copy(
this.templatePath(path),
this._extensionPath(path),
this._extensionPath(`${PACKAGE_EXTENSION}/${path}`),
templateCopyOptions
);
}
}

if (this.answers.includeTest) {
this.sourceRoot(path.join(__dirname, TEMPLATE_TEST_DIR));

this.fs.copy(
this.templatePath('.'),
this._extensionPath(),
templateCopyOptions
);

// update the scripts section in the package.json to use 'tsconfig.src.json' for building
const pkgJson = this.fs.readJSON(this.templatePath('.package.json'));
this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4);

// update the 'includes' property in the existing 'tsconfig.json' and adds '"noEmit": true'
const tsconfigJson = this.fs.readJSON(this.templatePath('.tsconfig.json'));
this.fs.extendJSON(this._extensionPath('tsconfig.json'), tsconfigJson, undefined, 4);

// the initial '.vscode/extensions.json' can't be extended as above, as it contains comments, which is tolerated by vscode,
// but not by `this.fs.extendJSON(...)`, so
this.fs.copy(this.templatePath('.vscode-extensions.json'), this._extensionPath('.vscode/extensions.json'), templateCopyOptions);
}

this.fs.writeJSON(this._extensionPath('.package.json'), mainPackageJson, undefined, 4);
this.fs.copy(
this._extensionPath('package-template.json'),
this._extensionPath('.package.json'),
this._extensionPath('package.json'),
templateCopyOptions
);
this.fs.delete(this._extensionPath('package-template.json'));
this.fs.delete(this._extensionPath('.package.json'));

// if (this.answers.includeTest) {
// this.sourceRoot(path.join(__dirname, TEMPLATE_TEST_DIR));

// this.fs.copy(
// this.templatePath('.'),
// this._extensionPath(),
// templateCopyOptions
// );

// // update the scripts section in the package.json to use 'tsconfig.src.json' for building
// const pkgJson = this.fs.readJSON(this.templatePath('.package.json'));
// this.fs.extendJSON(this._extensionPath('package-template.json'), pkgJson, undefined, 4);

// // update the 'includes' property in the existing 'tsconfig.json' and adds '"noEmit": true'
// const tsconfigJson = this.fs.readJSON(this.templatePath('.tsconfig.json'));
// this.fs.extendJSON(this._extensionPath('tsconfig.json'), tsconfigJson, undefined, 4);

// // the initial '.vscode/extensions.json' can't be extended as above, as it contains comments, which is tolerated by vscode,
// // but not by `this.fs.extendJSON(...)`, so
// this.fs.copy(this.templatePath('.vscode-extensions.json'), this._extensionPath('.vscode/extensions.json'), templateCopyOptions);
// }

// this.fs.copy(
// this._extensionPath('package-template.json'),
// this._extensionPath('package.json'),
// templateCopyOptions
// );
// this.fs.delete(this._extensionPath('package-template.json'));
}

async install(): Promise<void> {
Expand All @@ -271,14 +320,7 @@ export class LangiumGenerator extends Generator {
this.spawnSync('npm', ['install'], opts);
}
this.spawnSync('npm', ['run', 'langium:generate'], opts);

if (this.answers.includeVSCode || this.answers.includeCLI) {
this.spawnSync('npm', ['run', 'build'], opts);
}

if (this.answers.includeWeb) {
this.spawnSync('npm', ['run', 'build:web'], opts);
}
this.spawnSync('npm', ['run', 'build'], opts);
}

async end(): Promise<void> {
Expand Down Expand Up @@ -310,15 +352,14 @@ export class LangiumGenerator extends Generator {
return this.destinationPath(USER_DIR, this.answers.extensionName, ...path);
}

_replaceTemplateWords(fileExtensionGlob: string, languageName: string, languageId: string, tsconfigBaseName: string, content: string | Buffer): string {
_replaceTemplateWords(fileExtensionGlob: string, languageName: string, languageId: string, content: string | Buffer): string {
return content.toString()
.replace(EXTENSION_NAME, this.answers.extensionName)
.replace(RAW_LANGUAGE_NAME, this.answers.rawLanguageName)
.replace(FILE_EXTENSION, this.answers.fileExtensions)
.replace(FILE_EXTENSION_GLOB, fileExtensionGlob)
.replace(LANGUAGE_NAME, languageName)
.replace(LANGUAGE_ID, languageId)
.replace(TSCONFIG_BASE_NAME, tsconfigBaseName)
.replace(NEWLINES, EOL);
}

Expand Down
17 changes: 0 additions & 17 deletions packages/generator-langium/templates/cli/.package.json

This file was deleted.

32 changes: 0 additions & 32 deletions packages/generator-langium/templates/core/.package.json

This file was deleted.

24 changes: 0 additions & 24 deletions packages/generator-langium/templates/core/tsconfig.json

This file was deleted.

34 changes: 34 additions & 0 deletions packages/generator-langium/templates/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "<%= extension-name %>",
"description": "Base workspace package",
"version": "0.0.1",
"type": "module",
"private": true,
"files": [
"out",
"src"
],
"scripts": {
"compile": "tsc -b tsconfig.build.json",
"watch": "tsc -b tsconfig.build.json --watch",
"build": "npm run compile && npm run build --workspaces",
"lint": "eslint src --ext ts",
"langium:generate": "npm run --workspace packages/language langium:generate",
"langium:watch": "npm run --workspace packages/language langium:watch",
"test": "vitest run"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@typescript-eslint/parser": "~7.11.0",
"@typescript-eslint/eslint-plugin": "~7.11.0",
"eslint": "~8.57.0",
"typescript": "~5.4.5"
},
"volta": {
"node": "18.20.3",
"npm": "10.7.0"
},
"workspaces": [
"packages/language"
]
}
31 changes: 31 additions & 0 deletions packages/generator-langium/templates/packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "<%= extension-name %>-cli",
"description": "The cli specific package",
"version": "0.0.1",
"type": "module",
"engines": {
"node": ">=18.0.0"
},
"files": [
"bin",
"out",
"src"
],
"bin": {
"<%= language-id %>-cli": "./bin/cli.js"
},
"scripts": {
"build": "echo 'No build step'"
},
"dependencies": {
"<%= extension-name %>-language": "0.0.1",
"chalk": "~5.3.0",
"commander": "~11.0.0"
},
"devDependencies": {
},
"volta": {
"node": "18.20.3",
"npm": "10.7.0"
}
}
Loading

0 comments on commit 216df86

Please sign in to comment.