Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: apply design system to new and convert command #1398

Merged
merged 10 commits into from
May 3, 2024
10 changes: 7 additions & 3 deletions src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { load } from '../models/SpecificationFile';
import { SpecificationFileNotFound } from '../errors/specification-file';
import { convert } from '@asyncapi/converter';
import type { ConvertVersion } from '@asyncapi/converter';
import { cyan, green } from 'picocolors';

// @ts-ignore
import specs from '@asyncapi/specs';
Expand Down Expand Up @@ -35,15 +36,16 @@ export default class Convert extends Command {
try {
// LOAD FILE
this.specFile = await load(filePath);
// eslint-disable-next-line sonarjs/no-duplicate-string
this.metricsMetadata.to_version = flags['target-version'];

// CONVERSION
convertedFile = convert(this.specFile.text(), flags['target-version'] as ConvertVersion);
if (convertedFile) {
if (this.specFile.getFilePath()) {
this.log(`File ${this.specFile.getFilePath()} successfully converted!`);
this.log(`🎉 The ${cyan(this.specFile.getFilePath())} file has been successfully converted to version ${green(flags['target-version'])}!!`);
} else if (this.specFile.getFileURL()) {
this.log(`URL ${this.specFile.getFileURL()} successfully converted!`);
this.log(`🎉 The URL ${cyan(this.specFile.getFileURL())} has been successfully converted to version ${green(flags['target-version'])}!!`);
}
}

Expand All @@ -64,9 +66,11 @@ export default class Convert extends Command {
type: 'invalid-file',
filepath: filePath
}));
} else if (this.specFile?.toJson().asyncapi > flags['target-version']) {
this.error(`The ${cyan(filePath)} file cannot be converted to an older version. Downgrading is not supported.`);
} else {
this.error(err as Error);
}
}
}
}
}
7 changes: 4 additions & 3 deletions src/commands/new/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as inquirer from 'inquirer';
import { start as startStudio, DEFAULT_PORT } from '../../models/Studio';
import { resolve } from 'path';
import { load } from '../../models/SpecificationFile';
import { cyan } from 'picocolors';

const { writeFile, readFile } = fPromises;
const DEFAULT_ASYNCAPI_FILE_NAME = 'asyncapi.yaml';
Expand Down Expand Up @@ -158,16 +159,16 @@ export default class NewFile extends Command {
try {
const content = await readFile(fileNameToWriteToDisk, { encoding: 'utf8' });
if (content !== undefined) {
console.log(`File ${fileNameToWriteToDisk} already exists. Ignoring...`);
console.log(`A file named ${fileNameToWriteToDisk} already exists. Please choose a different name.`);
return;
}
} catch (e:any) {
if (e.code === 'EACCES') {
this.error('Permission denied to read the file. You do not have the necessary permissions.');
this.error('Permission has been denied to access the file.');
}
}
await writeFile(fileNameToWriteToDisk, asyncApiFile, { encoding: 'utf8' });
console.log(`Created file ${fileNameToWriteToDisk}...`);
console.log(`The ${cyan(fileNameToWriteToDisk)} has been successfully created.`);
this.specFile = await load(fileNameToWriteToDisk);
this.metricsMetadata.selected_template = selectedTemplate;
}
Expand Down
36 changes: 26 additions & 10 deletions src/commands/new/glee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,32 @@ import { prompt } from 'inquirer';
// eslint-disable-next-line
// @ts-ignore
import Generator from '@asyncapi/generator';
import { cyan, gray } from 'picocolors';

export const successMessage = (projectName: string) =>
`🎉 Your Glee project has been successfully created!
⏩ Next steps: follow the instructions ${cyan('below')} to manage your project:

cd ${projectName}\t\t ${gray('# Navigate to the project directory')}
npm install\t\t ${gray('# Install the project dependencies')}
npm run dev\t\t ${gray('# Start the project in development mode')}

You can also open the project in your favourite editor and start tweaking it.
`;

const errorMessages = {
alreadyExists: (projectName: string) =>
`Unable to create the project because the directory "${cyan(projectName)}" already exists at "${process.cwd()}/${projectName}".
To specify a different name for the new project, please run the command below with a unique project name:

${gray('asyncapi new glee --name ') + gray(projectName) + gray('-1')}`,
};

export default class NewGlee extends Command {
static description = 'Creates a new Glee project';
protected commandName = 'glee';
static readonly successMessage = successMessage;
static readonly errorMessages = errorMessages;

static flags = {
help: Flags.help({ char: 'h' }),
Expand Down Expand Up @@ -93,12 +115,10 @@ export default class NewGlee extends Command {
fs.existsSync(PROJECT_DIRECTORY) &&
fs.readdirSync(PROJECT_DIRECTORY).length > 0
) {
throw new Error(
`Unable to create the project. We tried to use "${projectName}" as the directory of your new project but it already exists (${PROJECT_DIRECTORY}). Please specify a different name for the new project. For example, run the following command instead:\n\n asyncapi new ${this.commandName} -f ${file} --name ${projectName}-1\n`
);
throw new Error(errorMessages.alreadyExists(projectName));
}
} catch (error: any) {
this.error(error.message);
this.log(error.message);
}
}

Expand Down Expand Up @@ -199,9 +219,7 @@ export default class NewGlee extends Command {
} catch (err: any) {
switch (err.code) {
case 'EEXIST':
this.error(
`Unable to create the project. We tried to use "${projectName}" as the directory of your new project but it already exists (${PROJECT_DIRECTORY}). Please specify a different name for the new project. For example, run the following command instead:\n\n asyncapi new ${this.commandName} --name ${projectName}-1\n`
);
this.error(errorMessages.alreadyExists(projectName));
break;
case 'EACCES':
this.error(
Expand Down Expand Up @@ -234,9 +252,7 @@ export default class NewGlee extends Command {
`${PROJECT_DIRECTORY}/README-template.md`,
`${PROJECT_DIRECTORY}/README.md`
);
this.log(
`Your project "${projectName}" has been created successfully!\n\nNext steps:\n\n cd ${projectName}\n npm install\n npm run dev\n\nAlso, you can already open the project in your favorite editor and start tweaking it.`
);
this.log(successMessage(projectName));
} catch (err) {
this.error(
`Unable to create the project. Please check the following message for further info about the error:\n\n${err}`
Expand Down
4 changes: 3 additions & 1 deletion src/models/Studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import chokidar from 'chokidar';
import open from 'open';
import path from 'path';
import { version as studioVersion } from '@asyncapi/studio/package.json';
import { gray } from 'picocolors';

const { readFile, writeFile } = fPromises;

Expand Down Expand Up @@ -99,7 +100,8 @@ export function start(filePath: string, port: number = DEFAULT_PORT): void {

server.listen(port, () => {
const url = `http://localhost:${port}?liveServer=${port}&studio-version=${studioVersion}`;
console.log(`Studio is running at ${url}`);
console.log(`Studio is now running at ${url}.`);
console.log(`You can open this URL in your web browser, and if needed, press ${gray('Ctrl + C')} to stop the process.`);
console.log(`Watching changes on file ${filePath}`);
open(url);
});
Expand Down
32 changes: 29 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AvroSchemaParser } from '@asyncapi/avro-schema-parser';
import { OpenAPISchemaParser } from '@asyncapi/openapi-schema-parser';
import { Parser, convertToOldAPI } from '@asyncapi/parser/cjs';
import { DiagnosticSeverity, Parser, convertToOldAPI } from '@asyncapi/parser/cjs';
import { RamlDTSchemaParser } from '@asyncapi/raml-dt-schema-parser';
import { Flags } from '@oclif/core';
import { ProtoBuffSchemaParser } from '@asyncapi/protobuf-schema-parser';
import { getDiagnosticSeverity } from '@stoplight/spectral-core';
import { OutputFormat } from '@stoplight/spectral-cli/dist/services/config';
import { html, json, junit, pretty, stylish, teamcity, text } from '@stoplight/spectral-formatters';
import { red, yellow, green, cyan } from 'chalk';

import type { Diagnostic } from '@asyncapi/parser/cjs';
import type Command from './base';
Expand Down Expand Up @@ -102,9 +103,10 @@ function logDiagnostics(diagnostics: Diagnostic[], command: Command, specFile: S
}

export function formatOutput(diagnostics: Diagnostic[], format: `${OutputFormat}`, failSeverity: SeverityKind) {
const options = { failSeverity: getDiagnosticSeverity(failSeverity) };
const diagnosticSeverity = getDiagnosticSeverity(failSeverity);
const options = { failSeverity: diagnosticSeverity !== -1 ? diagnosticSeverity : DiagnosticSeverity.Error };
switch (format) {
case 'stylish': return stylish(diagnostics, options);
case 'stylish': return formatStylish(diagnostics, options);
case 'json': return json(diagnostics, options);
case 'junit': return junit(diagnostics, options);
case 'html': return html(diagnostics, options);
Expand All @@ -115,6 +117,30 @@ export function formatOutput(diagnostics: Diagnostic[], format: `${OutputFormat}
}
}

function formatStylish(diagnostics: Diagnostic[], options: { failSeverity: DiagnosticSeverity }) {
const groupedDiagnostics = diagnostics.reduce((acc, diagnostic) => {
const severity = diagnostic.severity;
if (!acc[severity as DiagnosticSeverity]) {
acc[severity as DiagnosticSeverity] = [];
}
acc[severity as DiagnosticSeverity].push(diagnostic);
return acc;
}, {} as Record<DiagnosticSeverity, Diagnostic[]>);

return Object.entries(groupedDiagnostics).map(([severity, diagnostics]) => {
return `${getSeverityTitle(Number(severity))} ${stylish(diagnostics, options)}`;
}).join('\n');
}

function getSeverityTitle(severity: DiagnosticSeverity) {
switch (severity) {
case DiagnosticSeverity.Error: return red('Errors');
case DiagnosticSeverity.Warning: return yellow('Warnings');
case DiagnosticSeverity.Information: return cyan('Information');
case DiagnosticSeverity.Hint: return green('Hints');
}
}

function hasFailSeverity(diagnostics: Diagnostic[], failSeverity: SeverityKind) {
const diagnosticSeverity = getDiagnosticSeverity(failSeverity);
return diagnostics.some(diagnostic => diagnostic.severity <= diagnosticSeverity);
Expand Down
10 changes: 5 additions & 5 deletions test/integration/convert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('convert', () => {
.stdout()
.command(['convert', filePath])
.it('works when file path is passed', (ctx, done) => {
expect(ctx.stdout).to.contain('File ./test/fixtures/specification.yml successfully converted!\n');
expect(ctx.stdout).to.contain('The ./test/fixtures/specification.yml file has been successfully converted to version 3.0.0!!');
expect(ctx.stderr).to.equal('');
done();
});
Expand All @@ -52,7 +52,7 @@ describe('convert', () => {
.stdout()
.command(['convert', 'http://localhost:8080/dummySpec.yml'])
.it('works when url is passed', (ctx, done) => {
expect(ctx.stdout).to.contain('URL http://localhost:8080/dummySpec.yml successfully converted!\n');
expect(ctx.stdout).to.contain('The URL http://localhost:8080/dummySpec.yml has been successfully converted to version 3.0.0!!');
expect(ctx.stderr).to.equal('');
done();
});
Expand All @@ -73,7 +73,7 @@ describe('convert', () => {
.stdout()
.command(['convert'])
.it('converts from current context', (ctx, done) => {
expect(ctx.stdout).to.contain(`File ${path.resolve(__dirname, '../fixtures/specification.yml')} successfully converted!\n`);
expect(ctx.stdout).to.contain(`The ${path.resolve(__dirname, '../fixtures/specification.yml')} file has been successfully converted to version 3.0.0!!\n`);
expect(ctx.stderr).to.equal('');
done();
});
Expand Down Expand Up @@ -159,7 +159,7 @@ describe('convert', () => {
.stdout()
.command(['convert', filePath, '-o=./test/fixtures/specification_output.yml'])
.it('works when .yml file is passed', (ctx, done) => {
expect(ctx.stdout).to.equal(`File ${filePath} successfully converted!\n`);
expect(ctx.stdout).to.contain(`The ${filePath} file has been successfully converted to version 3.0.0!!`);
expect(fs.existsSync('./test/fixtures/specification_output.yml')).to.equal(true);
expect(ctx.stderr).to.equal('');
fs.unlinkSync('./test/fixtures/specification_output.yml');
Expand All @@ -171,7 +171,7 @@ describe('convert', () => {
.stdout()
.command(['convert', JSONFilePath, '-o=./test/fixtures/specification_output.json'])
.it('works when .json file is passed', (ctx, done) => {
expect(ctx.stdout).to.equal(`File ${JSONFilePath} successfully converted!\n`);
expect(ctx.stdout).to.contain(`The ${JSONFilePath} file has been successfully converted to version 3.0.0!!`);
expect(fs.existsSync('./test/fixtures/specification_output.json')).to.equal(true);
expect(ctx.stderr).to.equal('');
fs.unlinkSync('./test/fixtures/specification_output.json');
Expand Down
6 changes: 3 additions & 3 deletions test/integration/new/file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('new', () => {
.command(['new', '--no-tty', '-n=specification.yaml'])
.it('runs new command', async (ctx,done) => {
expect(ctx.stderr).to.equal('');
expect(ctx.stdout).to.equal('Created file specification.yaml...\n');
expect(ctx.stdout).to.equal('The specification.yaml has been successfully created.\n');
done();
});

Expand All @@ -36,7 +36,7 @@ describe('new', () => {
.command(['new:file', '--no-tty', '-n=specification.yaml'])
.it('runs new file command', async (ctx,done) => {
expect(ctx.stderr).to.equal('');
expect(ctx.stdout).to.equal('Created file specification.yaml...\n');
expect(ctx.stdout).to.equal('The specification.yaml has been successfully created.\n');
done();
});
});
Expand All @@ -62,7 +62,7 @@ describe('new', () => {
.command(['new:file', '--no-tty', '-n=specification.yaml'])
.it('should inform about the existing file and finish the process', async (ctx,done) => {
expect(ctx.stderr).to.equal('');
expect(ctx.stdout).to.equal('File specification.yaml already exists. Ignoring...\n');
expect(ctx.stdout).to.equal('A file named specification.yaml already exists. Please choose a different name.\n');
done();
});
});
Expand Down
11 changes: 9 additions & 2 deletions test/integration/new/glee.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import { PROJECT_DIRECTORY_PATH } from '../../helpers';
import { expect } from '@oclif/test';

const testHelper = new TestHelper();
const successMessage = (projectName: string) =>
'🎉 Your Glee project has been successfully created!';

const errorMessages = {
alreadyExists: (projectName: string) =>
`Unable to create the project because the directory "${projectName}" already exists at "${process.cwd()}/${projectName}".
To specify a different name for the new project, please run the command below with a unique project name:`};

describe('new glee', () => {
before(() => {
Expand All @@ -27,7 +34,7 @@ describe('new glee', () => {
.command(['new:glee', '-n=test-project'])
.it('runs new glee command with name flag', async (ctx,done) => {
expect(ctx.stderr).to.equal('');
expect(ctx.stdout).to.equal('Your project "test-project" has been created successfully!\n\nNext steps:\n\n cd test-project\n npm install\n npm run dev\n\nAlso, you can already open the project in your favorite editor and start tweaking it.\n');
expect(ctx.stdout).to.contains(successMessage('test-project'));
done();
});
});
Expand All @@ -52,7 +59,7 @@ describe('new glee', () => {
.stdout()
.command(['new:glee', '-n=test-project'])
.it('should throw error if name of the new project already exists', async (ctx,done) => {
expect(ctx.stderr).to.equal(`Error: Unable to create the project. We tried to use "test-project" as the directory of your new project but it already exists (${PROJECT_DIRECTORY_PATH}). Please specify a different name for the new project. For example, run the following command instead:\n\n asyncapi new glee --name test-project-1\n\n`);
expect(ctx.stderr).to.contains(`Error: ${errorMessages.alreadyExists('test-project')}`);
expect(ctx.stdout).to.equal('');
done();
});
Expand Down
10 changes: 5 additions & 5 deletions test/integration/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('validate', () => {
.stdout()
.command(['validate', './test/fixtures/specification.yml'])
.it('works when file path is passed', (ctx, done) => {
expect(ctx.stdout).to.match(/File .\/test\/fixtures\/specification.yml is valid but has \(itself and\/or referenced documents\) governance issues.\n\ntest\/fixtures\/specification.yml/);
expect(ctx.stdout).to.contain('File ./test/fixtures/specification.yml is valid but has (itself and/or referenced documents) governance issues.\n');
expect(ctx.stderr).to.equal('');
done();
});
Expand All @@ -41,7 +41,7 @@ describe('validate', () => {
.stdout()
.command(['validate', './test/fixtures/specification-avro.yml'])
.it('works when file path is passed and schema is avro', (ctx, done) => {
expect(ctx.stdout).to.match(/File .\/test\/fixtures\/specification-avro.yml is valid but has \(itself and\/or referenced documents\) governance issues.\n/);
expect(ctx.stdout).to.contain('File ./test/fixtures/specification-avro.yml is valid but has (itself and/or referenced documents) governance issues.\n');
expect(ctx.stderr).to.equal('');
done();
});
Expand All @@ -61,7 +61,7 @@ describe('validate', () => {
.stdout()
.command(['validate', 'http://localhost:8080/dummySpec.yml'])
.it('works when url is passed', (ctx, done) => {
expect(ctx.stdout).to.match(/URL http:\/\/localhost:8080\/dummySpec.yml is valid but has \(itself and\/or referenced documents\) governance issues.\n\nhttp:\/\/localhost:8080\/dummySpec.yml/);
expect(ctx.stdout).to.contain('URL http://localhost:8080/dummySpec.yml is valid but has (itself and/or referenced documents) governance issues.\n');
expect(ctx.stderr).to.equal('');
done();
});
Expand Down Expand Up @@ -180,7 +180,7 @@ describe('validate', () => {
.stdout()
.command(['validate', './test/fixtures/specification.yml', '--log-diagnostics'])
.it('works with --log-diagnostics', (ctx, done) => {
expect(ctx.stdout).to.match(/File .\/test\/fixtures\/specification.yml is valid but has \(itself and\/or referenced documents\) governance issues.\n\ntest\/fixtures\/specification.yml/);
expect(ctx.stdout).to.contain('File ./test/fixtures/specification.yml is valid but has (itself and/or referenced documents) governance issues.\n');
expect(ctx.stderr).to.equal('');
done();
});
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('validate', () => {
.stdout()
.command(['validate', './test/fixtures/specification.yml', '--fail-severity=warn'])
.it('works with --fail-severity', (ctx, done) => {
expect(ctx.stderr).to.include('\nFile ./test/fixtures/specification.yml and/or referenced documents have governance issues.\n\ntest/fixtures/specification.yml');
expect(ctx.stderr).to.contain('File ./test/fixtures/specification.yml and/or referenced documents have governance issues.');
expect(process.exitCode).to.equal(1);
done();
});
Expand Down
Loading