diff --git a/scripts/localInstall.js b/scripts/localInstall.js new file mode 100755 index 0000000000..6fa503c14c --- /dev/null +++ b/scripts/localInstall.js @@ -0,0 +1,109 @@ +#!/usr/bin/env node + +const shell = require('shelljs'); +const fs = require('fs'); +const path = require('path'); +const { run, execSilent } = require('./util'); + +/** + * Script for setting up the library in another local project + * + * ### Usage + * + * 1) `./localInstall install /path/to/target` + * + * Generate and install a tarball package to a target npm module. Useful for locally + * testing a build that will be sent to NPM. Accepts a relative or absolute path. + * + * 2) `./localInstall.js link /path/to/target`. + * + * Link the library to another project for development purposes. Changes made in this + * project will be automatically reflected in the target module. + * + * 3) `./localInstall.js unlink /path/to/target` + * + * Unlink the library from another project. + */ + +const COMMANDS = ['install', 'link', 'unlink']; + +function showHelp() { + console.log('Commands:'); + console.log( + ' install [path to target module]\tCreates an NPM package of the project and installs it to the target module' + ); + console.log(' link [path to target module]\t\tLink the project to another module for quick development'); + console.log(' unlink [path to target module]\tUnlink the project from the target module'); +} + +function main() { + const command = process.argv[2]; + if (!COMMANDS.includes(command)) { + showHelp(); + process.exit(0); + } + + const targetPackagePath = !path.isAbsolute(process.argv[3]) + ? path.resolve(process.cwd(), process.argv[3]) + : process.argv[3]; + + if (!fs.existsSync(targetPackagePath)) { + console.log(`A valid target package path is required`); + process.exit(1); + } + + const isDirectory = fs.lstatSync(targetPackagePath).isDirectory(); + const packageJsonPath = path.join(targetPackagePath, 'package.json'); + if (!isDirectory || !fs.existsSync(packageJsonPath)) { + console.log('Path must be to a valid npm package'); + process.exit(1); + } + + const localPackagePath = path.join(__dirname, '..'); + const { name, version } = JSON.parse(fs.readFileSync(path.join(localPackagePath, 'package.json')).toString()); + + switch (command) { + case COMMANDS[0]: // install + let tarballPath; + run('Building project and creating package', () => { + shell.cd(localPackagePath); + execSilent('yarn build'); + execSilent('yarn pack'); + tarballPath = execSilent('find $(pwd) -type f -iname *.tgz').replace('\n', ''); + }); + + run(`Installing v${version} to ${targetPackagePath}`, () => { + shell.cd(targetPackagePath); + const yarnLockPath = path.join(targetPackagePath, 'yarn.lock'); + if (fs.existsSync(yarnLockPath)) { + execSilent(`yarn remove ${name}`, true); + execSilent(`yarn cache clean`); + execSilent(`yarn add ${tarballPath}`); + } else { + execSilent(`npm uninstall ${name}`); + execSilent(`npm install ${tarballPath}`); + } + }); + break; + case COMMANDS[1]: // link + run(`Linking ${name} to ${targetPackagePath}`, () => { + shell.cd(localPackagePath); + execSilent('yarn link'); + shell.cd(targetPackagePath); + execSilent(`yarn link ${name}`); + }); + break; + case COMMANDS[2]: // unlink + run(`Unlinking ${name} from ${targetPackagePath}`, () => { + shell.cd(targetPackagePath); + execSilent(`yarn unlink ${name}`); + shell.cd(localPackagePath); + execSilent('yarn unlink'); + }); + break; + default: + showHelp(); + } +} + +main(); diff --git a/scripts/util.js b/scripts/util.js new file mode 100644 index 0000000000..7bd2a99020 --- /dev/null +++ b/scripts/util.js @@ -0,0 +1,41 @@ +const shell = require('shelljs'); +require('shelljs/global'); + +const terminalCodes = { + Red: '\\033[31m', + LightGrey: '\\033[37m', + Bold: '\\033[1m', + ResetAll: '\\033[0m', +}; + +module.exports = { + run: (status, f) => { + let result; + shell.exec(`printf "🐎 ${status}..."`); + const { LightGrey, Bold, ResetAll } = terminalCodes; + try { + result = f(); + } catch (e) { + shell.exec(`printf "\\r❗️ ${status}...${Bold}${LightGrey}failed${ResetAll}\\n"`); + shell.exec(`printf "${e.message}"`); + process.exit(1); + } + shell.exec(`printf "\\r✅ ${status}...${Bold}${LightGrey}done${ResetAll}\\n"`); + return result; + }, + execSilent: (command, swallowError) => { + const prevConfig = config.fatal; + config.fatal = true; + try { + return shell.exec(command, { silent: true }); + } catch (e) { + if (swallowError) { + return; + } + throw e; + } finally { + config.fatal = prevConfig; + } + }, + terminalCodes, +}; diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index dee77ba019..72ebe624c1 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -320,9 +320,9 @@ const parseAsFolderMetadataXml = if (parts.length > 1) { const folderContentTypesDirs = getFolderContentTypeDirNames(registry); // check if the path contains a folder content name as a directory - // e.g., `/reports/` and if it does return that folder name. + const pathWithoutFile = parts.slice(0, -1); folderContentTypesDirs.some((dirName) => { - if (fsPath.includes(`${sep}${dirName}${sep}`)) { + if (pathWithoutFile.includes(dirName)) { folderName = dirName; } }); diff --git a/test/resolve/metadataResolver.test.ts b/test/resolve/metadataResolver.test.ts index 72f4457858..0e153a0d53 100644 --- a/test/resolve/metadataResolver.test.ts +++ b/test/resolve/metadataResolver.test.ts @@ -398,6 +398,31 @@ describe('MetadataResolver', () => { expect(comp[0]).to.have.deep.property('type', registryAccess.getTypeByName('ReportFolder')); }); + it('should resolve folder metadata and reports from zip paths without leading directory', () => { + const registryAccess = new RegistryAccess(); + const reportFolderDir = 'reports'; + const reportSubFolder = join(reportFolderDir, 'TestFolder'); + const virtualFS: VirtualDirectory[] = [ + { dirPath: reportFolderDir, children: ['TestFolder-meta.xml', 'TestFolder'] }, + { dirPath: reportSubFolder, children: ['MyReport.report-meta.xml'] }, + ]; + const tree = new VirtualTreeContainer(virtualFS); + const mdResolver = new MetadataResolver(registryAccess, tree); + + const components = mdResolver.getComponentsFromPath(reportFolderDir); + expect(components).to.be.an('array').with.lengthOf(2); + + const folderComp = components.find((c) => c.type.name === 'ReportFolder'); + expect(folderComp).to.exist; + expect(folderComp).to.have.property('name', 'TestFolder'); + expect(folderComp).to.have.deep.property('type', registryAccess.getTypeByName('ReportFolder')); + + const reportComp = components.find((c) => c.type.name === 'Report'); + expect(reportComp).to.exist; + expect(reportComp).to.have.property('name', 'TestFolder/MyReport'); + expect(reportComp).to.have.deep.property('type', registryAccess.getTypeByName('Report')); + }); + it('Should not mistake folder component of a mixed content type as that type', () => { // this test has coveage on non-mixedContent types as well by nature of the execution path const path = mixedContentInFolder.FOLDER_XML_PATH;