Skip to content

Commit

Permalink
fix: CLI startup (#560)
Browse files Browse the repository at this point in the history
### 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 <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Sep 15, 2023
1 parent 7998eb1 commit 4bde898
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 86 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
.vscode/

# Compilation/build outputs
bin/
build/
coverage/
dist/
Expand Down
4 changes: 4 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

// eslint-disable-next-line import/no-unresolved
import '../out/cli/main.cjs';
41 changes: 21 additions & 20 deletions docs/development/langium-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <file>` 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 <file>` 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

Expand Down
2 changes: 1 addition & 1 deletion esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions src/cli/cli-util.ts
Original file line number Diff line number Diff line change
@@ -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<LangiumDocument> {
const extensions = services.LanguageMetaData.fileExtensions;
Expand Down
17 changes: 16 additions & 1 deletion src/cli/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
const services = createSafeDsServices(NodeFileSystem).SafeDs;
const module = await extractAstNode<SdsModule>(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);
Expand Down
48 changes: 17 additions & 31 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
@@ -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<void> => {
const services = createSafeDsServices(NodeFileSystem).SafeDs;
const module = await extractAstNode<SdsModule>(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('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-d, --destination <dir>', '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('<file>', `possible file extensions: ${fileExtensions}`)
.option('-d, --destination <dir>', 'destination directory of generation')
.option('-r, --root <dir>', '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);
3 changes: 1 addition & 2 deletions src/language/builtins/workspaceManager.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
19 changes: 19 additions & 0 deletions tests/cli/main.test.ts
Original file line number Diff line number Diff line change
@@ -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]');
});
});
7 changes: 1 addition & 6 deletions tsconfig.eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
}
38 changes: 16 additions & 22 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"
]
}

"include": ["src/**/*.ts"],
"exclude": ["out", "node_modules"]
}

0 comments on commit 4bde898

Please sign in to comment.