From 4bde8983b234666acc0668093d4b107db70158ab Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Fri, 15 Sep 2023 16:05:59 +0200 Subject: [PATCH] fix: CLI startup (#560) ### Summary of Changes The CLI should now start properly when running `node ./bin/cli` in the project root. This PR also adds a test to ensure this in the future. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> --- .gitignore | 1 - bin/cli.js | 4 ++ docs/development/langium-quickstart.md | 41 +++++++++---------- esbuild.mjs | 2 +- src/cli/cli-util.ts | 3 +- src/cli/generator.ts | 17 +++++++- src/cli/main.ts | 48 ++++++++--------------- src/language/builtins/workspaceManager.ts | 3 +- tests/cli/main.test.ts | 19 +++++++++ tsconfig.eslint.json | 7 +--- tsconfig.json | 38 ++++++++---------- 11 files changed, 97 insertions(+), 86 deletions(-) create mode 100644 bin/cli.js create mode 100644 tests/cli/main.test.ts diff --git a/.gitignore b/.gitignore index 2e8f3cf72..6d3d5e96c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ .vscode/ # Compilation/build outputs -bin/ build/ coverage/ dist/ diff --git a/bin/cli.js b/bin/cli.js new file mode 100644 index 000000000..d023fe6fb --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +// eslint-disable-next-line import/no-unresolved +import '../out/cli/main.cjs'; diff --git a/docs/development/langium-quickstart.md b/docs/development/langium-quickstart.md index f72a18e2d..1dbf58fed 100644 --- a/docs/development/langium-quickstart.md +++ b/docs/development/langium-quickstart.md @@ -3,32 +3,33 @@ ## What's in the folder The project folder contains all necessary files for your language extension. - * `package.json` - the manifest file in which you declare your language support. - * `language-configuration.json` - the language configuration used in the VS Code editor, defining the tokens that are used for comments and brackets. - * `src/extension/main.ts` - the main code of the extension, which is responsible for launching a language server and client. - * `src/language/grammar/safe-ds.langium` - the grammar definition of your language. - * `src/language/main.ts` - the entry point of the language server process. - * `src/language/safe-ds-module.ts` - the dependency injection module of your language implementation. Use this to register overridden and added services. - * `src/language/validation/safe-ds-validator.ts` - an example validator. You should change it to reflect the semantics of your language. - * `src/cli/main.ts` - the entry point of the command line interface (CLI) of your language. - * `src/cli/generator.ts` - the code generator used by the CLI to write output files from DSL documents. - * `src/cli/cli-util.ts` - utility code for the CLI. + +* `package.json` - the manifest file in which you declare your language support. +* `language-configuration.json` - the language configuration used in the VS Code editor, defining the tokens that are used for comments and brackets. +* `src/extension/main.ts` - the main code of the extension, which is responsible for launching a language server and client. +* `src/language/grammar/safe-ds.langium` - the grammar definition of your language. +* `src/language/main.ts` - the entry point of the language server process. +* `src/language/safe-ds-module.ts` - the dependency injection module of your language implementation. Use this to register overridden and added services. +* `src/language/validation/safe-ds-validator.ts` - an example validator. You should change it to reflect the semantics of your language. +* `src/cli/main.ts` - the entry point of the command line interface (CLI) of your language. +* `src/cli/generator.ts` - the code generator used by the CLI to write output files from DSL documents. +* `src/cli/cli-util.ts` - utility code for the CLI. ## Get up and running straight away - * Run `npm run langium:generate` to generate TypeScript code from the grammar definition. - * Run `npm run build` to compile all TypeScript code. - * Press `F5` to open a new window with your extension loaded. - * Create a new file with a file name suffix matching your language. - * Verify that syntax highlighting, validation, completion etc. are working as expected. - * Run `./bin/cli` to see options for the CLI; `./bin/cli generate ` generates code for a given DSL file. +* Run `npm run langium:generate` to generate TypeScript code from the grammar definition. +* Run `npm run build` to compile all TypeScript code. +* Press `F5` to open a new window with your extension loaded. +* Create a new file with a file name suffix matching your language. +* Verify that syntax highlighting, validation, completion etc. are working as expected. +* Run `./bin/cli` to see options for the CLI; `./bin/cli generate ` generates code for a given DSL file. ## Make changes - * Run `npm run watch` to have the TypeScript compiler run automatically after every change of the source files. - * Run `npm run langium:watch` to have the Langium generator run automatically after every change of the grammar declaration. - * You can relaunch the extension from the debug toolbar after making changes to the files listed above. - * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. +* Run `npm run watch` to have the TypeScript compiler run automatically after every change of the source files. +* Run `npm run langium:watch` to have the Langium generator run automatically after every change of the grammar declaration. +* You can relaunch the extension from the debug toolbar after making changes to the files listed above. +* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. ## Install your extension diff --git a/esbuild.mjs b/esbuild.mjs index f7a67c134..b001972e8 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -39,7 +39,7 @@ const plugins = [ const ctx = await esbuild.context({ // Entry points for the vscode extension and the language server - entryPoints: ['src/extension/main.ts', 'src/language/main.ts'], + entryPoints: ['src/cli/main.ts', 'src/extension/main.ts', 'src/language/main.ts'], outdir: 'out', bundle: true, target: "ES2017", diff --git a/src/cli/cli-util.ts b/src/cli/cli-util.ts index 1a89321f2..50db9e224 100644 --- a/src/cli/cli-util.ts +++ b/src/cli/cli-util.ts @@ -1,8 +1,7 @@ import chalk from 'chalk'; import path from 'path'; import fs from 'fs'; -import { AstNode, LangiumDocument, LangiumServices } from 'langium'; -import { URI } from 'vscode-uri'; +import { AstNode, LangiumDocument, LangiumServices, URI } from 'langium'; export const extractDocument = async function (fileName: string, services: LangiumServices): Promise { const extensions = services.LanguageMetaData.fileExtensions; diff --git a/src/cli/generator.ts b/src/cli/generator.ts index 48268a850..37c3f53e2 100644 --- a/src/cli/generator.ts +++ b/src/cli/generator.ts @@ -2,7 +2,22 @@ import fs from 'fs'; import { CompositeGeneratorNode, toString } from 'langium'; import path from 'path'; import { SdsModule } from '../language/generated/ast.js'; -import { extractDestinationAndName } from './cli-util.js'; +import { extractAstNode, extractDestinationAndName } from './cli-util.js'; +import chalk from 'chalk'; +import { createSafeDsServices } from '../language/safe-ds-module.js'; +import { NodeFileSystem } from 'langium/node'; + +export const generateAction = async (fileName: string, opts: GenerateOptions): Promise => { + const services = createSafeDsServices(NodeFileSystem).SafeDs; + const module = await extractAstNode(fileName, services); + const generatedFilePath = generatePython(module, fileName, opts.destination); + // eslint-disable-next-line no-console + console.log(chalk.green(`Python code generated successfully: ${generatedFilePath}`)); +}; + +export type GenerateOptions = { + destination?: string; +}; export const generatePython = function (module: SdsModule, filePath: string, destination: string | undefined): string { const data = extractDestinationAndName(filePath, destination); diff --git a/src/cli/main.ts b/src/cli/main.ts index 971c2aabc..45de32bda 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -1,39 +1,25 @@ -import chalk from 'chalk'; import { Command } from 'commander'; -import { SdsModule } from '../language/generated/ast.js'; import { SafeDsLanguageMetaData } from '../language/generated/module.js'; -import { createSafeDsServices } from '../language/safe-ds-module.js'; -import { extractAstNode } from './cli-util.js'; -import { generatePython } from './generator.js'; -import { NodeFileSystem } from 'langium/node'; +import { generateAction } from './generator.js'; +import * as path from 'node:path'; -export const generateAction = async (fileName: string, opts: GenerateOptions): Promise => { - const services = createSafeDsServices(NodeFileSystem).SafeDs; - const module = await extractAstNode(fileName, services); - const generatedFilePath = generatePython(module, fileName, opts.destination); - // eslint-disable-next-line no-console - console.log(chalk.green(`JavaScript code generated successfully: ${generatedFilePath}`)); -}; +const packagePath = path.resolve(__dirname, '..', '..', 'package.json'); -export type GenerateOptions = { - destination?: string; -}; +const fileExtensions = SafeDsLanguageMetaData.fileExtensions.join(', '); -// eslint-disable-next-line import/no-default-export, func-names -export default function (): void { - const program = new Command(); +const program = new Command(); - program - // eslint-disable-next-line @typescript-eslint/no-var-requires - .version(require('../../package.json').version); +program + // eslint-disable-next-line @typescript-eslint/no-var-requires + .version(require(packagePath).version); - const fileExtensions = SafeDsLanguageMetaData.fileExtensions.join(', '); - program - .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') - .description('generates JavaScript code that prints "Hello, {name}!" for each greeting in a source file') - .action(generateAction); +program + .command('generate') + .argument('', `possible file extensions: ${fileExtensions}`) + .option('-d, --destination ', 'destination directory of generation') + .option('-r, --root ', 'source root folder') + .option('-q, --quiet', 'whether the program should print something', false) + .description('generate Python code') + .action(generateAction); - program.parse(process.argv); -} +program.parse(process.argv); diff --git a/src/language/builtins/workspaceManager.ts b/src/language/builtins/workspaceManager.ts index 0aec46d1e..a0bcfc4af 100644 --- a/src/language/builtins/workspaceManager.ts +++ b/src/language/builtins/workspaceManager.ts @@ -1,6 +1,5 @@ -import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices } from 'langium'; +import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium'; import { WorkspaceFolder } from 'vscode-languageserver'; -import { URI } from 'vscode-uri'; import { SAFE_DS_FILE_EXTENSIONS } from '../constants/fileExtensions.js'; import { globSync } from 'glob'; import path from 'path'; diff --git a/tests/cli/main.test.ts b/tests/cli/main.test.ts new file mode 100644 index 000000000..32e8c68ee --- /dev/null +++ b/tests/cli/main.test.ts @@ -0,0 +1,19 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import path from 'node:path'; +import { spawnSync, execSync } from 'node:child_process'; +import url from 'node:url'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); +const projectRoot = path.resolve(__dirname, '..', '..'); + +describe('program', () => { + beforeAll(() => { + execSync('npm run build', { cwd: projectRoot }); + }); + + it('should show usage if no arguments are passed', () => { + const process = spawnSync('node', ['./bin/cli'], { cwd: projectRoot }); + expect(process.stderr.toString()).toContain('Usage: cli [options] [command]'); + }); +}); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 4fdd09a80..6203da700 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -3,11 +3,6 @@ "compilerOptions": { "noEmit": true }, - "include": [ - "./.eslintrc.cjs", - "./docs/javascript/**/*", - "./src/**/*", - "./tests/**/*" - ], + "include": ["./.eslintrc.cjs", "./bin/cli.js", "./docs/javascript/**/*", "./src/**/*", "./tests/**/*"], "exclude": [] } diff --git a/tsconfig.json b/tsconfig.json index 1a286f858..0ac163e0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,25 +1,19 @@ { "compilerOptions": { - "target": "ES2017", - "module": "Node16", - "lib": ["ESNext", "DOM", "WebWorker"], - "sourceMap": true, - "outDir": "out", - "strict": true, - "noUnusedLocals": true, - "noImplicitReturns": true, - "noImplicitOverride": true, - "moduleResolution": "Node16", - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "target": "ES2017", + "module": "Node16", + "lib": ["ESNext", "DOM", "WebWorker"], + "sourceMap": true, + "outDir": "out", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "moduleResolution": "Node16", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "out", - "node_modules" - ] - } - \ No newline at end of file + "include": ["src/**/*.ts"], + "exclude": ["out", "node_modules"] +}