Skip to content

Commit

Permalink
feat: enable noOverwriteGlobs for templates based on react engine (#1234
Browse files Browse the repository at this point in the history
)

Co-authored-by: Lukasz Gornicki <[email protected]>
  • Loading branch information
lmgyuan and derberg authored Jul 31, 2024
1 parent ef865ad commit 81dfd0c
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/enable_noOverwriteGlobs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@asyncapi/generator": minor
---

Enable `noOverwriteGlobs` option for templates based on react rendering engine.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ coverage
# Turbo
.turbo

/.idea
2 changes: 1 addition & 1 deletion apps/generator/lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ class Generator {
if (renderContent === undefined) {
return;
} else if (isReactTemplate(this.templateConfig)) {
await saveRenderedReactContent(renderContent, outputpath);
await saveRenderedReactContent(renderContent, outputpath, this.noOverwriteGlobs);
} else {
await writeFile(outputpath, renderContent);
}
Expand Down
20 changes: 12 additions & 8 deletions apps/generator/lib/logMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ const NODE_MODULES_INSTALL ='Remember that your local template must have its own

const NPM_INSTALL_TRIGGER = 'Installation of template located on disk technically means symlink creation betweed node_modules of the generator and template sources. Your local template must have its own node_modules, "npm install" is not triggered.';

function templateVersion(ver) {
function templateVersion(ver) {
return `Version of used template is ${ver}.`;
}
}

function templateSource(localHtmlTemplate) {
return `Template sources taken from ${localHtmlTemplate}.`;
}
}

function templateNotFound(templateName) {
return `${templateName} not found in local dependencies but found it installed as a global package.`;
}
}

function packageNotAvailable(packageDetails) {
if (packageDetails && packageDetails.pkgPath) {
return `Unable to resolve template location at ${packageDetails.pkgPath}. Package is not available locally.`;
}
}

return `Template is not available locally and expected location is undefined. Known details are: ${JSON.stringify(packageDetails, null, 2)}`;
}
Expand All @@ -38,6 +38,10 @@ function relativeSourceFileNotGenerated(relativeSourceFile , subject) {
return `${relativeSourceFile} was not generated because ${subject} specified in template configuration in conditionalFiles was not found in provided AsyncAPI specification file.`;
}

function skipOverwrite(testFilePath) {
return `Skipping overwrite for: ${testFilePath}`;
}

function conditionalFilesMatched(relativeSourceFile) {
return `${relativeSourceFile} was not generated because condition specified for this file in template configuration in conditionalFiles matched.`;
}
Expand All @@ -54,6 +58,6 @@ module.exports = {
installationDebugMessage,
templateSuccessfullyInstalled,
relativeSourceFileNotGenerated,
conditionalFilesMatched

};
conditionalFilesMatched,
skipOverwrite
};
46 changes: 30 additions & 16 deletions apps/generator/lib/renderer/react.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const path = require('path');
const AsyncReactSDK = require('@asyncapi/generator-react-sdk');
const minimatch = require('minimatch');
const logMessage = require('../logMessages.js');
const log = require('loglevel');
const {
writeFile
} = require('../utils');
Expand All @@ -8,7 +11,7 @@ const reactExport = module.exports;

/**
* Configures React templating system, this handles all the transpilation work.
*
*
* @private
* @param {string} templateLocation located for thetemplate
* @param {string} templateContentDir where the template content are located
Expand All @@ -23,44 +26,44 @@ reactExport.configureReact = async (templateLocation, templateContentDir, transp

/**
* Renders the template with react and returns the content and meta data for the file.
*
*
* @private
* @param {AsyncAPIDocument} asyncapiDocument
* @param {AsyncAPIDocument} asyncapiDocument
* @param {string} filePath path to the template file
* @param {Object} extraTemplateData Extra data to pass to the template.
* @param {string} templateLocation located for thetemplate
* @param {string} templateContentDir where the template content are located
* @param {string} transpiledTemplateLocation folder for the transpiled code
* @param {Object} templateParams provided template parameters
* @param {boolean} debug flag
* @param {string} originalAsyncAPI
* @param {string} originalAsyncAPI
* @return {Promise<TemplateRenderResult>}
*/
reactExport.renderReact = async (asyncapiDocument, filePath, extraTemplateData, templateLocation, templateContentDir, transpiledTemplateLocation, templateParams, debug, originalAsyncAPI) => {
extraTemplateData = extraTemplateData || {};
filePath = filePath.replace(templateContentDir, path.resolve(templateLocation, transpiledTemplateLocation));
return await AsyncReactSDK.renderTemplate(
filePath,
filePath,
{
asyncapi: asyncapiDocument,
params: templateParams,
originalAsyncAPI,
...extraTemplateData
},
},
debug
);
};

/**
* Save the single rendered react content based on the meta data available.
*
*
* @private
* @param {TemplateRenderResult} renderedContent the react content rendered
* @param {String} outputPath Path to the file being rendered.
*/
const saveContentToFile = async (renderedContent, outputPath) => {
const saveContentToFile = async (renderedContent, outputPath, noOverwriteGlobs = []) => {
let filePath = outputPath;
// Might be the same as in the `fs` package, but is an active choice for our default file permission for any rendered files.
// Might be the same as in the `fs` package, but is an active choice for our default file permission for any rendered files.
let permissions = 0o666;
const content = renderedContent.content;

Expand All @@ -78,21 +81,32 @@ const saveContentToFile = async (renderedContent, outputPath) => {
}
}

await writeFile(filePath, content, {
mode: permissions
});
// get the final file name of the file
const finalFileName = path.basename(filePath);
// check whether the filename should be ignored based on user's inputs
const shouldOverwrite = !noOverwriteGlobs.some(globExp => minimatch(finalFileName, globExp));

// Write the file only if it should not be skipped
if (shouldOverwrite) {
await writeFile(filePath, content, {
mode: permissions
});
} else {
await log.debug(logMessage.skipOverwrite(filePath));
}
};

/**
* Save the rendered react content based on the meta data available.
*
*
* @private
* @param {TemplateRenderResult[] | TemplateRenderResult} renderedContent the react content rendered
* @param {String} outputPath Path to the file being rendered.
* @param noOverwriteGlobs Array of globs to skip overwriting files.
*/
reactExport.saveRenderedReactContent = async (renderedContent, outputPath) => {
reactExport.saveRenderedReactContent = async (renderedContent, outputPath, noOverwriteGlobs = []) => {
if (Array.isArray(renderedContent)) {
return Promise.all(renderedContent.map(content => saveContentToFile(content, outputPath)));
return Promise.all(renderedContent.map(content => saveContentToFile(content, outputPath, noOverwriteGlobs)));
}
return saveContentToFile(renderedContent, outputPath);
return await saveContentToFile(renderedContent, outputPath, noOverwriteGlobs);
};
5 changes: 2 additions & 3 deletions apps/generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
"test": "npm run test:unit && npm run test:integration && npm run test:cli",
"test:unit": "jest --coverage --testPathIgnorePatterns=integration --testPathIgnorePatterns=test-project",
"test:dev": "npm run test:unit -- --watchAll",
"test:integration": "npm run test:cleanup && jest --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__'",
"test:integration:update": "jest --updateSnapshot --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__'",
"test:cli": "node cli.js ./test/docs/dummy.yml ./test/test-templates/react-template -o test/output --force-write --debug && test -e test/output/test-file.md",
"test:integration": "npm run test:cleanup && jest --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__(?!\\/loglevel\\.js$)'",
"test:integration:update": "jest --updateSnapshot --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__(?!\\/loglevel\\.js$)'", "test:cli": "node cli.js ./test/docs/dummy.yml ./test/test-templates/react-template -o test/output --force-write --debug && test -e test/output/test-file.md",
"test:cleanup": "rimraf \"test/temp\"",
"docs": "jsdoc2md --partial docs/jsdoc2md-handlebars/custom-sig-name.hbs docs/jsdoc2md-handlebars/main.hbs docs/jsdoc2md-handlebars/docs.hbs docs/jsdoc2md-handlebars/header.hbs docs/jsdoc2md-handlebars/defaultvalue.hbs docs/jsdoc2md-handlebars/link.hbs docs/jsdoc2md-handlebars/params-table.hbs --files lib/generator.js > docs/api.md",
"docker:build": "docker build -t asyncapi/generator:latest .",
Expand Down
35 changes: 32 additions & 3 deletions apps/generator/test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment node
*/

const { readFile } = require('fs').promises;
const { mkdir, writeFile, readFile } = require('fs').promises;
const path = require('path');
const Generator = require('../lib/generator');
const dummySpecPath = path.resolve(__dirname, './docs/dummy.yml');
Expand All @@ -24,7 +24,7 @@ describe('Integration testing generateFromFile() to make sure the result of the

it('generated using Nunjucks template', async () => {
const outputDir = generateFolderName();
const generator = new Generator(nunjucksTemplate, outputDir, {
const generator = new Generator(nunjucksTemplate, outputDir, {
forceWrite: true,
templateParams: { version: 'v1', mode: 'production' }
});
Expand All @@ -35,7 +35,7 @@ describe('Integration testing generateFromFile() to make sure the result of the

it('generate using React template', async () => {
const outputDir = generateFolderName();
const generator = new Generator(reactTemplate, outputDir, {
const generator = new Generator(reactTemplate, outputDir, {
forceWrite: true ,
templateParams: { version: 'v1', mode: 'production' }
});
Expand All @@ -55,4 +55,33 @@ describe('Integration testing generateFromFile() to make sure the result of the
const file = await readFile(path.join(outputDir, testOutputFile), 'utf8');
expect(file).toMatchSnapshot();
});

it('should ignore specified files with noOverwriteGlobs', async () => {
const outputDir = generateFolderName();
// Manually create a file to test if it's not overwritten
await mkdir(outputDir, { recursive: true });
// Create a variable to store the file content
const testContent = '<script>const initialContent = "This should not change";</script>';
// eslint-disable-next-line sonarjs/no-duplicate-string
const testFilePath = path.normalize(path.resolve(outputDir, testOutputFile));
await writeFile(testFilePath, testContent);

// Manually create an output first, before generation, with additional custom file to validate if later it is still there, not overwritten
const generator = new Generator(reactTemplate, outputDir, {
forceWrite: true,
noOverwriteGlobs: [`**/${testOutputFile}`],
debug: true,
});

await generator.generateFromFile(dummySpecPath);

// Read the file to confirm it was not overwritten
const fileContent = await readFile(testFilePath, 'utf8');
// Check if the files have been overwritten
expect(fileContent).toBe(testContent);
// Check if the log debug message was printed
/*TODO:
Include log message test in the future to ensure that the log.debug for skipping overwrite is called
*/
});
});

0 comments on commit 81dfd0c

Please sign in to comment.