feat(cli): Ohif cli for modes and extensions modification (#2696)
* feat: Add initial cli tool structure * feat: add copying template files * feat: Add mode template and command * feat: Add readme template generation * feat: Add documentation to extension template * feat: Enhance documentation of the template mode * fix: cli module type * feat: Add config-based mode and extension registration (#2660) * feat: Add ohif cli add/remove extension/mode (#2661) * Basic working CLI for add-extension and remove-extension * Basic cli for add/remove extension/mode, lots more to do. * Cleanup and harden] * feat: Add list of tasks to add-mode Co-authored-by: Alireza <ar.sedghi@gmail.com> * feat: Add git initialization for the mode or extension template (#2662) * fix: package json file to include templates * feat: Add git initialization for the mode or extension template * feat: Add more checks of git and target dir * feat: refactore library utilities * feat: Add the list command to print extensions and modes (#2664) * feat: Add the list command to print extensions and modes * Add todo * Feat/ohif cli validation + auto install (#2671) * WIP * Working mode keyword verification * Validation * auto install extensions based on modes * WIP remove unused extensions on removeMove * Working add-mode, remove-mode automatic extension management. * If extension is in used by a mode, don't allow the CLI to uninstall it * Cleanup addExtension * cleanup removeExtension and addMode * Cleanup removeMode * Update existing extensions with the needed keywords/peer deps * Fix broken config * Feat/cli search (#2677) * feat: refactor pretty print for console * feat: add search for modes and extensions * fix: ugly colors * Feat/ohif cli error handling publishing (#2679) * WIP * fix: webpack imports * wip * fix: react router dom private routes * from last commit * wip * fix: webpack prod builds * WIP * Working regsitration with new IDs * Stable Co-authored-by: Alireza <ar.sedghi@gmail.com> * verify extensions when constructing modes. (#2681) * verify extensions when constructing modes. * Add version to unit tests so it conforms to schema * Update ohif utils exposed via @ohif/core * Fix import * fix tests * feat: ohif-cli link local modes/extensions for development (#2682) * feat: enable cli to work with project root * feat: add initial link package * feat: add link and unlink extension * feat: add link and unlink mode * erro handling for link-package * feat: add comment on ohif-cli linking for development (#2686) * Docs/ohif cli (#2687) * feat: Add documentation for templates * feat: Add more documentation * Fix/core publish (#2685) * versions * wip * remove webpack clean output * fix publish * use next as dist tag for v3 for now * fix webpack pro recipe for output * fix: lerna publish next * fix(cli): fix issues when trying to link an extension or a mode (#2725) The generated package.json doesn't contain keywords property which is required by the linkPackage function. The module apth wasn't correclty handled too, and when there is no pluginOptions, it fails while reading the file or while generating a default configuration. * make dicom pdf and video work after cli merge * add axios dependency * comment out the chdir for now * create id and version based on user inputs * customizable path for extension and modes * fix template to make the template mode load * fix the questions to loop if path is not desirable * fix templates * correct package json order * unify the package creation for extension and mode * bump versions for each package * bump extension versions to 3.0 * add gitignore to templates * fix version requirements when ^ * update docs * update docs and fix tests * try to fix the tests * bump node version * remove the version from extensions * remove the version from modes * remove version from extensionManager * fix eslint * revert husky version * fix eslint * fix node version for new eslint * fix documentatoin removing version * fix cicle ci image version * fix circle ci node image * fix circle ci node image * add back the video and pdf Co-authored-by: Matthis Duclos <matthis.duclos@gmail.com> Co-authored-by: James A. Petts <jamesapetts@gmail.com>
This commit is contained in:
181 files changed
+5985
-3689
No files matched your search
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@ohif/cli",
|
||||
"version": "2.0.7",
|
||||
"description": "A CLI to bootstrap new OHIF extension or mode",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"ohif-cli": "src/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"ohif"
|
||||
],
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.10",
|
||||
"chalk": "^5.0.0",
|
||||
"commander": "^8.3.0",
|
||||
"axios": "^0.26.1",
|
||||
"execa": "^6.0.0",
|
||||
"gitignore": "^0.7.0",
|
||||
"inquirer": "^8.2.0",
|
||||
"listr": "^0.14.3",
|
||||
"mustache": "^4.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"node-fetch": "^3.1.1",
|
||||
"pkg-install": "^1.0.0",
|
||||
"registry-url": "^6.0.0",
|
||||
"spdx-license-list": "^6.4.0",
|
||||
"util": "^0.12.4",
|
||||
"yarn-programmatic": "^0.1.2"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"src/",
|
||||
"templates/"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
installNPMPackage,
|
||||
getYarnInfo,
|
||||
validateExtension,
|
||||
getVersionedPackageName,
|
||||
addExtensionToConfig,
|
||||
} from './utils/index.js';
|
||||
|
||||
export default async function addExtension(packageName, version) {
|
||||
console.log(chalk.green.bold(`Adding ohif-extension ${packageName}...`));
|
||||
|
||||
const versionedPackageName = getVersionedPackageName(packageName, version);
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
title: `Searching for extension: ${versionedPackageName}`,
|
||||
task: async () => await validateExtension(packageName, version),
|
||||
},
|
||||
{
|
||||
title: `Installing npm package: ${versionedPackageName}`,
|
||||
task: async () => await installNPMPackage(packageName, version),
|
||||
},
|
||||
{
|
||||
title: 'Adding ohif-extension to the configuration file',
|
||||
task: async ctx => {
|
||||
const yarnInfo = await getYarnInfo(packageName);
|
||||
|
||||
addExtensionToConfig(packageName, yarnInfo);
|
||||
|
||||
ctx.yarnInfo = yarnInfo;
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(ctx => {
|
||||
console.log(
|
||||
`${chalk.green.bold(
|
||||
`Added ohif-extension ${packageName}@${ctx.yarnInfo.version}`
|
||||
)} `
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
import addExtension from './addExtension.js';
|
||||
|
||||
export default async function addExtensions(ohifExtensions) {
|
||||
// Auto generate Listr tasks...
|
||||
const taskEntries = [];
|
||||
|
||||
ohifExtensions.forEach(({ packageName, version }) => {
|
||||
const title = `Adding ohif-extension ${packageName}`;
|
||||
|
||||
taskEntries.push({
|
||||
title,
|
||||
task: async () => await addExtension(packageName, version),
|
||||
});
|
||||
});
|
||||
|
||||
const tasks = new Listr(taskEntries, {
|
||||
exitOnError: true,
|
||||
});
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(() => {
|
||||
let extensonsString = '';
|
||||
|
||||
ohifExtensions.forEach(({ packageName, version }) => {
|
||||
extensonsString += ` ${packageName}@${version}`;
|
||||
});
|
||||
|
||||
console.log(
|
||||
`${chalk.green.bold(`Extensions added:${extensonsString}`)} `
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
installNPMPackage,
|
||||
getYarnInfo,
|
||||
getVersionedPackageName,
|
||||
validateMode,
|
||||
addModeToConfig,
|
||||
findRequiredOhifExtensionsForMode,
|
||||
} from './utils/index.js';
|
||||
import addExtensions from './addExtensions.js';
|
||||
|
||||
export default async function addMode(packageName, version) {
|
||||
console.log(chalk.green.bold(`Adding ohif-mode ${packageName}...`));
|
||||
|
||||
const versionedPackageName = getVersionedPackageName(packageName, version);
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
title: `Searching for mode: ${versionedPackageName}`,
|
||||
task: async () => await validateMode(packageName, version),
|
||||
},
|
||||
{
|
||||
title: `Installing npm package: ${versionedPackageName}`,
|
||||
task: async () => await installNPMPackage(packageName, version),
|
||||
},
|
||||
{
|
||||
title: 'Adding ohif-mode to the configuration file',
|
||||
task: async ctx => {
|
||||
const yarnInfo = await getYarnInfo(packageName);
|
||||
|
||||
addModeToConfig(packageName, yarnInfo);
|
||||
|
||||
ctx.yarnInfo = yarnInfo;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Detecting required ohif-extensions...',
|
||||
task: async ctx => {
|
||||
ctx.ohifExtensions = await findRequiredOhifExtensionsForMode(
|
||||
ctx.yarnInfo
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(async ctx => {
|
||||
console.log(
|
||||
`${chalk.green.bold(
|
||||
`Added ohif-mode ${packageName}@${ctx.yarnInfo.version}`
|
||||
)} `
|
||||
);
|
||||
|
||||
const ohifExtensions = ctx.ohifExtensions;
|
||||
|
||||
if (ohifExtensions.length) {
|
||||
console.log(`${chalk.green.bold(`Installing dependent extensions`)} `);
|
||||
await addExtensions(ohifExtensions);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default 'Not found';
|
||||
@@ -0,0 +1,97 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
|
||||
import {
|
||||
createDirectoryContents,
|
||||
editPackageJson,
|
||||
createLicense,
|
||||
createReadme,
|
||||
initGit,
|
||||
} from './utils/index.js';
|
||||
|
||||
const createPackage = async (options) => {
|
||||
const { packageType } = options; // extension or mode
|
||||
|
||||
if (fs.existsSync(options.targetDir)) {
|
||||
console.error(
|
||||
`%s ${packageType} with the same name already exists in this directory, either delete it or choose a different name`,
|
||||
chalk.red.bold('ERROR')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.mkdirSync(options.targetDir);
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
title: 'Copying template files',
|
||||
task: () =>
|
||||
createDirectoryContents(
|
||||
options.templateDir,
|
||||
options.targetDir,
|
||||
options.prettier
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Editing Package.json with provided information',
|
||||
task: () => editPackageJson(options),
|
||||
},
|
||||
{
|
||||
title: 'Creating a License file',
|
||||
task: () => createLicense(options),
|
||||
},
|
||||
{
|
||||
title: 'Creating a Readme file',
|
||||
task: () => createReadme(options),
|
||||
},
|
||||
{
|
||||
title: 'Initializing a Git Repository',
|
||||
enabled: () => options.gitRepository,
|
||||
task: () => initGit(options),
|
||||
},
|
||||
],
|
||||
{
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
await tasks.run();
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.green(`Done: ${packageType} is ready at`, options.targetDir)
|
||||
);
|
||||
console.log();
|
||||
|
||||
console.log(
|
||||
chalk.green(`NOTE: In order to use this ${packageType} for development,`)
|
||||
);
|
||||
console.log(
|
||||
chalk.green(
|
||||
`run the following command inside the root of the OHIF monorepo`
|
||||
)
|
||||
);
|
||||
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.green.bold(
|
||||
` yarn run cli link-${packageType} ${options.targetDir}`
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
"and when you don't need it anymore, run the following command to unlink it"
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`)
|
||||
);
|
||||
console.log();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default createPackage;
|
||||
@@ -0,0 +1,8 @@
|
||||
const colors = {
|
||||
LIGHT: '#5acce6',
|
||||
MAIN: '#0944b3',
|
||||
DARK: '#090c29',
|
||||
ACTIVE: '#348cfd',
|
||||
};
|
||||
|
||||
export default colors;
|
||||
@@ -0,0 +1,5 @@
|
||||
const endPoints = {
|
||||
NPM_KEYWORD: 'https://registry.npmjs.com/-/v1/search?text=keywords:',
|
||||
};
|
||||
|
||||
export default endPoints;
|
||||
@@ -0,0 +1,5 @@
|
||||
import keywords from './keywords.js';
|
||||
import colors from './colors.js';
|
||||
import endPoints from './endPoints.js';
|
||||
|
||||
export { keywords, colors, endPoints };
|
||||
@@ -0,0 +1,6 @@
|
||||
const keywords = {
|
||||
MODE: 'ohif-mode',
|
||||
EXTENSION: 'ohif-extension',
|
||||
};
|
||||
|
||||
export default keywords;
|
||||
@@ -0,0 +1,23 @@
|
||||
import createPackage from './createPackage.js';
|
||||
import addExtension from './addExtension.js';
|
||||
import removeExtension from './removeExtension.js';
|
||||
import addMode from './addMode.js';
|
||||
import removeMode from './removeMode.js';
|
||||
import listPlugins from './listPlugins.js';
|
||||
import searchPlugins from './searchPlugins.js';
|
||||
import { linkExtension, linkMode } from './linkPackage.js';
|
||||
import { unlinkExtension, unlinkMode } from './unlinkPackage.js';
|
||||
|
||||
export {
|
||||
createPackage,
|
||||
addExtension,
|
||||
removeExtension,
|
||||
addMode,
|
||||
removeMode,
|
||||
listPlugins,
|
||||
searchPlugins,
|
||||
linkExtension,
|
||||
linkMode,
|
||||
unlinkExtension,
|
||||
unlinkMode,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execa } from 'execa';
|
||||
import { keywords } from './enums/index.js';
|
||||
import {
|
||||
validateYarn,
|
||||
addExtensionToConfig,
|
||||
addModeToConfig,
|
||||
} from './utils/index.js';
|
||||
|
||||
async function linkPackage(packageDir, options, addToConfig, keyword) {
|
||||
const { viewerDirectory } = options;
|
||||
|
||||
// read package.json from packageDir
|
||||
const file = fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8');
|
||||
|
||||
// name of the package
|
||||
const packageJSON = JSON.parse(file);
|
||||
const packageName = packageJSON.name;
|
||||
const packageKeywords = packageJSON.keywords;
|
||||
|
||||
// check if package is an extension or a mode
|
||||
if (!packageKeywords.includes(keyword)) {
|
||||
throw new Error(`${packageName} is not ${keyword}`);
|
||||
}
|
||||
|
||||
const version = packageJSON.version;
|
||||
|
||||
// make sure yarn is installed
|
||||
await validateYarn();
|
||||
|
||||
// change directory to packageDir and execute yarn link
|
||||
process.chdir(packageDir);
|
||||
|
||||
let results;
|
||||
results = await execa(`yarn`, ['link']);
|
||||
|
||||
// change directory to OHIF Platform root and execute yarn link
|
||||
process.chdir(viewerDirectory);
|
||||
|
||||
results = await execa(`yarn`, ['link', packageName]);
|
||||
console.log(results.stdout);
|
||||
addToConfig(packageName, { version });
|
||||
}
|
||||
|
||||
function linkExtension(packageDir, options) {
|
||||
const keyword = keywords.EXTENSION;
|
||||
linkPackage(packageDir, options, addExtensionToConfig, keyword);
|
||||
}
|
||||
|
||||
function linkMode(packageDir, options) {
|
||||
const keyword = keywords.MODE;
|
||||
linkPackage(packageDir, options, addModeToConfig, keyword);
|
||||
}
|
||||
|
||||
export { linkExtension, linkMode };
|
||||
@@ -0,0 +1,23 @@
|
||||
import fs from 'fs';
|
||||
import { prettyPrint } from './utils/index.js';
|
||||
import { colors } from './enums/index.js';
|
||||
|
||||
const listPlugins = async configPath => {
|
||||
const pluginConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
|
||||
const { extensions, modes } = pluginConfig;
|
||||
|
||||
const titleOptions = { color: colors.LIGHT, bold: true };
|
||||
const itemsOptions = { color: colors.ACTIVE, bold: true };
|
||||
|
||||
const extensionsItems = extensions.map(
|
||||
extension => `${extension.packageName} @ ${extension.version}`
|
||||
);
|
||||
|
||||
const modesItems = modes.map(mode => `${mode.packageName} @ ${mode.version}`);
|
||||
|
||||
prettyPrint('Extensions', titleOptions, extensionsItems, itemsOptions);
|
||||
prettyPrint('Modes', titleOptions, modesItems, itemsOptions);
|
||||
};
|
||||
|
||||
export default listPlugins;
|
||||
@@ -0,0 +1,49 @@
|
||||
import chalk from 'chalk';
|
||||
import Listr from 'listr';
|
||||
|
||||
import {
|
||||
uninstallNPMPackage,
|
||||
throwIfExtensionUsedByInstalledMode,
|
||||
removeExtensionFromConfig,
|
||||
validateExtensionYarnInfo,
|
||||
} from './utils/index.js';
|
||||
|
||||
export default async function removeExtension(packageName) {
|
||||
console.log(chalk.green.bold(`Removing ohif-extension ${packageName}...`));
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
title: `Searching for installed extension: ${packageName}`,
|
||||
task: async () => await validateExtensionYarnInfo(packageName),
|
||||
},
|
||||
{
|
||||
title: `Checking if ${packageName} is in use by an installed mode`,
|
||||
task: async () =>
|
||||
await throwIfExtensionUsedByInstalledMode(packageName),
|
||||
},
|
||||
{
|
||||
title: `Uninstalling npm package: ${packageName}`,
|
||||
task: async () => await uninstallNPMPackage(packageName),
|
||||
},
|
||||
{
|
||||
title: 'Removing ohif-extension from the configuration file',
|
||||
task: async () => removeExtensionFromConfig(packageName),
|
||||
},
|
||||
],
|
||||
{
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(() => {
|
||||
console.log(
|
||||
`${chalk.green.bold(`Removed ohif-extension ${packageName}`)} `
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
import removeExtension from './removeExtension.js';
|
||||
|
||||
export default async function removeExtensions(ohifExtensionsToRemove) {
|
||||
// Auto generate Listr tasks...
|
||||
const taskEntries = [];
|
||||
|
||||
ohifExtensionsToRemove.forEach(packageName => {
|
||||
const title = `Removing ohif-extension ${packageName}`;
|
||||
|
||||
taskEntries.push({
|
||||
title,
|
||||
task: async () => await removeExtension(packageName),
|
||||
});
|
||||
});
|
||||
|
||||
const tasks = new Listr(taskEntries, {
|
||||
exitOnError: true,
|
||||
});
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(() => {
|
||||
let extensonsString = '';
|
||||
|
||||
ohifExtensionsToRemove.forEach(packageName => {
|
||||
extensonsString += ` ${packageName}`;
|
||||
});
|
||||
|
||||
console.log(
|
||||
`${chalk.green.bold(`Extensions removed:${extensonsString}`)} `
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import Listr from 'listr';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import {
|
||||
uninstallNPMPackage,
|
||||
findOhifExtensionsToRemoveAfterRemovingMode,
|
||||
removeModeFromConfig,
|
||||
validateModeYarnInfo,
|
||||
getYarnInfo,
|
||||
} from './utils/index.js';
|
||||
import removeExtensions from './removeExtensions.js';
|
||||
|
||||
export default async function removeMode(packageName) {
|
||||
console.log(chalk.green.bold(`Removing ohif-mode ${packageName}...`));
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
title: `Searching for installed mode: ${packageName}`,
|
||||
task: async ctx => {
|
||||
ctx.yarnInfo = await getYarnInfo(packageName);
|
||||
await validateModeYarnInfo(packageName);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: `Uninstalling npm package: ${packageName}`,
|
||||
task: async () => await uninstallNPMPackage(packageName),
|
||||
},
|
||||
{
|
||||
title: 'Removing ohif-mode from the configuration file',
|
||||
task: async () => await removeModeFromConfig(packageName),
|
||||
},
|
||||
{
|
||||
title: 'Detecting extensions that can be removed...',
|
||||
task: async ctx => {
|
||||
ctx.ohifExtensionsToRemove = await findOhifExtensionsToRemoveAfterRemovingMode(
|
||||
ctx.yarnInfo
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
|
||||
await tasks
|
||||
.run()
|
||||
.then(async ctx => {
|
||||
// Remove extensions if they aren't used by any other mode.
|
||||
console.log(`${chalk.green.bold(`Removed ohif-mode ${packageName}`)} `);
|
||||
|
||||
const ohifExtensionsToRemove = ctx.ohifExtensionsToRemove;
|
||||
|
||||
if (ohifExtensionsToRemove.length) {
|
||||
console.log(
|
||||
`${chalk.green.bold(
|
||||
`Removing ${ohifExtensionsToRemove.length} extensions no longer used by any installed mode`
|
||||
)}`
|
||||
);
|
||||
|
||||
await removeExtensions(ohifExtensionsToRemove);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.message);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { prettyPrint } from './utils/index.js';
|
||||
import { keywords, colors, endPoints } from './enums/index.js';
|
||||
|
||||
async function searchRegistry(keyword) {
|
||||
const url = `${endPoints.NPM_KEYWORD}${keyword}`;
|
||||
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
const { objects } = response.data;
|
||||
return objects;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchPlugins(options) {
|
||||
const { verbose } = options;
|
||||
|
||||
const extensions = await searchRegistry(keywords.EXTENSION);
|
||||
const modes = await searchRegistry(keywords.MODE);
|
||||
|
||||
const titleOptions = { color: colors.LIGHT, bold: true };
|
||||
const itemsOptions = {};
|
||||
|
||||
const extensionsItems = extensions.map(extension => {
|
||||
const item = [
|
||||
`${extension.package.name} @ ${extension.package.version}`,
|
||||
[`Description: ${extension.package.description}`],
|
||||
];
|
||||
|
||||
if (verbose) {
|
||||
item[1].push(`Repository: ${extension.package.links.repository}`);
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
const modesItems = modes.map(mode => {
|
||||
const item = [
|
||||
`${mode.package.name} @ ${mode.package.version}`,
|
||||
[`Description: ${mode.package.description}`],
|
||||
];
|
||||
|
||||
if (verbose) {
|
||||
item[1].push(`Repository: ${mode.package.links.repository}`);
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
prettyPrint('Extensions', titleOptions, extensionsItems, itemsOptions);
|
||||
prettyPrint('Modes', titleOptions, modesItems, itemsOptions);
|
||||
}
|
||||
|
||||
export default searchPlugins;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { execa } from 'execa';
|
||||
import {
|
||||
validateYarn,
|
||||
removeExtensionFromConfig,
|
||||
removeModeFromConfig,
|
||||
} from './utils/index.js';
|
||||
|
||||
const linkPackage = async (packageName, options, removeFromConfig) => {
|
||||
const { viewerDirectory } = options;
|
||||
|
||||
// make sure yarn is installed
|
||||
await validateYarn();
|
||||
|
||||
// change directory to OHIF Platform root and execute yarn link
|
||||
process.chdir(viewerDirectory);
|
||||
|
||||
const results = await execa(`yarn`, ['unlink', packageName]);
|
||||
console.log(results.stdout);
|
||||
|
||||
//update the plugin.json file
|
||||
removeFromConfig(packageName);
|
||||
};
|
||||
|
||||
function unlinkExtension(extensionName, options) {
|
||||
linkPackage(extensionName, options, removeExtensionFromConfig);
|
||||
}
|
||||
|
||||
function unlinkMode(modeName, options) {
|
||||
linkPackage(modeName, options, removeModeFromConfig);
|
||||
}
|
||||
|
||||
export { unlinkExtension, unlinkMode };
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
addExtensionToConfigJson,
|
||||
addModeToConfigJson,
|
||||
readPluginConfigFile,
|
||||
writePluginConfigFile,
|
||||
} from './private/index.js';
|
||||
|
||||
function addToAndOverwriteConfig(packageName, options, augmentConfigFunction) {
|
||||
const installedVersion = options.version;
|
||||
let pluginConfig = readPluginConfigFile();
|
||||
|
||||
if (!pluginConfig) {
|
||||
pluginConfig = {
|
||||
extensions: [],
|
||||
modes: [],
|
||||
};
|
||||
}
|
||||
|
||||
augmentConfigFunction(pluginConfig, {
|
||||
packageName,
|
||||
version: installedVersion,
|
||||
});
|
||||
writePluginConfigFile(pluginConfig);
|
||||
}
|
||||
|
||||
function addExtensionToConfig(packageName, options) {
|
||||
addToAndOverwriteConfig(packageName, options, addExtensionToConfigJson);
|
||||
}
|
||||
|
||||
function addModeToConfig(packageName, options) {
|
||||
addToAndOverwriteConfig(packageName, options, addModeToConfigJson);
|
||||
}
|
||||
|
||||
export { addExtensionToConfig, addModeToConfig };
|
||||
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs';
|
||||
|
||||
// https://github.dev/leoroese/template-cli/blob/628dd24db7df399ebb520edd0bc301bc7b5e8b66/index.js#L19
|
||||
const createDirectoryContents = (
|
||||
templatePath,
|
||||
targetDirPath,
|
||||
copyPrettierRules
|
||||
) => {
|
||||
const filesToCreate = fs.readdirSync(templatePath);
|
||||
|
||||
filesToCreate.forEach(file => {
|
||||
if (!copyPrettierRules && file === '.prettierrc') {
|
||||
return;
|
||||
}
|
||||
|
||||
const origFilePath = `${templatePath}/${file}`;
|
||||
|
||||
// get stats about the current file
|
||||
const stats = fs.statSync(origFilePath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
const contents = fs.readFileSync(origFilePath, 'utf8');
|
||||
|
||||
// Rename
|
||||
if (file === '.npmignore') file = '.gitignore';
|
||||
|
||||
const writePath = `${targetDirPath}/${file}`;
|
||||
fs.writeFileSync(writePath, contents, 'utf8');
|
||||
} else if (stats.isDirectory()) {
|
||||
fs.mkdirSync(`${targetDirPath}/${file}`);
|
||||
|
||||
// recursive call
|
||||
createDirectoryContents(
|
||||
`${templatePath}/${file}`,
|
||||
`${targetDirPath}/${file}`,
|
||||
copyPrettierRules
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default createDirectoryContents;
|
||||
@@ -0,0 +1,31 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import spdxLicenseList from 'spdx-license-list/full.js';
|
||||
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
async function createLicense(options) {
|
||||
const { targetDir, name, email } = options;
|
||||
const targetPath = path.join(targetDir, 'LICENSE');
|
||||
|
||||
let license;
|
||||
try {
|
||||
license = spdxLicenseList[options.license];
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'%s License %s not found in the list of licenses',
|
||||
chalk.red.bold('ERROR'),
|
||||
options.license
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const licenseContent = license.licenseText
|
||||
.replace('<year>', new Date().getFullYear())
|
||||
.replace('<copyright holders>', `${name} (${email})`);
|
||||
return writeFile(targetPath, licenseContent, 'utf8');
|
||||
}
|
||||
|
||||
export default createLicense;
|
||||
@@ -0,0 +1,23 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import mustache from 'mustache';
|
||||
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
async function createReadme(options) {
|
||||
let template = `# {{name}} \n## Description \n{{description}} \n## Author \n{{author}} \n## License \n{{license}}`;
|
||||
const { name, description, author, license, targetDir } = options;
|
||||
const targetPath = path.join(targetDir, 'README.md');
|
||||
|
||||
const readmeContent = mustache.render(template, {
|
||||
name,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
});
|
||||
|
||||
return writeFile(targetPath, readmeContent, 'utf8');
|
||||
}
|
||||
|
||||
export default createReadme;
|
||||
@@ -0,0 +1,33 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function editPackageJson(options) {
|
||||
const { name, version, description, author, license, targetDir } = options;
|
||||
|
||||
// read package.json from targetDir
|
||||
const dependenciesPath = path.join(targetDir, 'dependencies.json');
|
||||
const rawData = fs.readFileSync(dependenciesPath, 'utf8');
|
||||
const packageJson = JSON.parse(rawData);
|
||||
|
||||
// edit package.json
|
||||
const mergedObj = Object.assign(
|
||||
{
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
files: ['dist', 'README.md'],
|
||||
},
|
||||
packageJson
|
||||
);
|
||||
|
||||
// write package.json back to targetDir
|
||||
const writePath = path.join(targetDir, 'package.json');
|
||||
fs.writeFileSync(writePath, JSON.stringify(mergedObj, null, 2));
|
||||
|
||||
// remove the dependencies.json file
|
||||
fs.unlinkSync(dependenciesPath);
|
||||
}
|
||||
|
||||
export default editPackageJson;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { readPluginConfigFile } from './private/index.js';
|
||||
import getYarnInfo from './getYarnInfo.js';
|
||||
|
||||
export default async function findOhifExtensionsToRemoveAfterRemovingMode(
|
||||
removedModeYarnInfo
|
||||
) {
|
||||
const pluginConfig = readPluginConfigFile();
|
||||
|
||||
if (!pluginConfig) {
|
||||
// No other modes or extensions, no action item.
|
||||
return [];
|
||||
}
|
||||
|
||||
const { modes, extensions } = pluginConfig;
|
||||
|
||||
const registeredExtensions = extensions.map(
|
||||
extension => extension.packageName
|
||||
);
|
||||
// TODO this is not a function
|
||||
const ohifExtensionsOfMode = Object.keys(
|
||||
removedModeYarnInfo.peerDependencies
|
||||
).filter(peerDependency => registeredExtensions.includes(peerDependency));
|
||||
|
||||
const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map(
|
||||
packageName => {
|
||||
return {
|
||||
packageName,
|
||||
used: false,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Check if other modes use each extension used by this mode
|
||||
const otherModes = modes.filter(
|
||||
mode => mode.packageName !== removedModeYarnInfo.name
|
||||
);
|
||||
|
||||
for (let i = 0; i < otherModes.length; i++) {
|
||||
const mode = otherModes[i];
|
||||
const yarnInfo = await getYarnInfo(mode.packageName);
|
||||
|
||||
const peerDependencies = yarnInfo.peerDependencies;
|
||||
|
||||
if (!peerDependencies) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < ohifExtensionsUsedInOtherModes.length; j++) {
|
||||
const ohifExtension = ohifExtensionsUsedInOtherModes[j];
|
||||
if (ohifExtension.used) {
|
||||
// Already accounted that we can't delete this, so don't waste effort
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(peerDependencies).includes(ohifExtension.packageName)) {
|
||||
ohifExtension.used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return list of now unused extensions
|
||||
const ohifExtensionsToRemove = ohifExtensionsUsedInOtherModes
|
||||
.filter(ohifExtension => !ohifExtension.used)
|
||||
.map(ohifExtension => ohifExtension.packageName);
|
||||
|
||||
return ohifExtensionsToRemove;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { validateExtension } from './validate.js';
|
||||
|
||||
export default async function findRequiredOhifExtensionsForMode(yarnInfo) {
|
||||
// Get yarn info file and get peer dependencies
|
||||
if (!yarnInfo.peerDependencies) {
|
||||
// No ohif-extension dependencies
|
||||
return;
|
||||
}
|
||||
|
||||
const peerDependencies = yarnInfo.peerDependencies;
|
||||
const dependencies = [];
|
||||
const ohifExtensions = [];
|
||||
|
||||
Object.keys(peerDependencies).forEach((packageName) => {
|
||||
dependencies.push({
|
||||
packageName,
|
||||
version: peerDependencies[packageName],
|
||||
});
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Fetch each npm json and check which are ohif extensions
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
const dependency = dependencies[i];
|
||||
const { packageName, version } = dependency;
|
||||
const promise = validateExtension(packageName, version)
|
||||
.then(() => {
|
||||
ohifExtensions.push({ packageName, version });
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Await all the extensions // TODO -> Improve so we async install each
|
||||
// extension and await all of those promises instead.
|
||||
await Promise.all(promises);
|
||||
|
||||
return ohifExtensions;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function getVersionedPackageName(packageName, version) {
|
||||
return version === undefined ? packageName : `${packageName}@${version}`;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { info } from 'yarn-programmatic';
|
||||
|
||||
export default async function getYarnInfo(packageName) {
|
||||
return await info(packageName);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import getVersionedPackageName from './getVersionedPackageName.js';
|
||||
import installNPMPackage from './installNPMPackage.js';
|
||||
import uninstallNPMPackage from './uninstallNPMPackage.js';
|
||||
import {
|
||||
validateMode,
|
||||
validateExtension,
|
||||
validateModeYarnInfo,
|
||||
validateExtensionYarnInfo,
|
||||
} from './validate.js';
|
||||
import getYarnInfo from './getYarnInfo.js';
|
||||
import { addExtensionToConfig, addModeToConfig } from './addToConfig.js';
|
||||
import findRequiredOhifExtensionsForMode from './findRequiredOhifExtensionsForMode.js';
|
||||
import {
|
||||
removeExtensionFromConfig,
|
||||
removeModeFromConfig,
|
||||
} from './removeFromConfig.js';
|
||||
import throwIfExtensionUsedByInstalledMode from './throwIfExtensionUsedByInstalledMode.js';
|
||||
import findOhifExtensionsToRemoveAfterRemovingMode from './findOhifExtensionsToRemoveAfterRemovingMode.js';
|
||||
import initGit from './initGit.js';
|
||||
import createDirectoryContents from './createDirectoryContents.js';
|
||||
import editPackageJson from './editPackageJson.js';
|
||||
import createLicense from './createLicense.js';
|
||||
import createReadme from './createReadme.js';
|
||||
import prettyPrint from './prettyPrint.js';
|
||||
import validateYarn from './validateYarn.js';
|
||||
|
||||
export {
|
||||
getYarnInfo,
|
||||
getVersionedPackageName,
|
||||
installNPMPackage,
|
||||
uninstallNPMPackage,
|
||||
validateMode,
|
||||
validateExtension,
|
||||
validateModeYarnInfo,
|
||||
validateExtensionYarnInfo,
|
||||
addExtensionToConfig,
|
||||
addModeToConfig,
|
||||
findRequiredOhifExtensionsForMode,
|
||||
removeExtensionFromConfig,
|
||||
throwIfExtensionUsedByInstalledMode,
|
||||
removeModeFromConfig,
|
||||
findOhifExtensionsToRemoveAfterRemovingMode,
|
||||
initGit,
|
||||
createDirectoryContents,
|
||||
editPackageJson,
|
||||
createLicense,
|
||||
createReadme,
|
||||
prettyPrint,
|
||||
validateYarn,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const exists = promisify(fs.exists);
|
||||
|
||||
async function initGit(options) {
|
||||
const { targetDir } = options;
|
||||
const targetPath = path.join(targetDir, '.git');
|
||||
|
||||
// Check if git is installed
|
||||
try {
|
||||
await execa('git', ['--version']);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'%s Git is not installed. Please install git and try again.',
|
||||
chalk.red.bold('ERROR')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!(await exists(targetPath))) {
|
||||
try {
|
||||
await execa('git', ['init'], { cwd: targetDir });
|
||||
} catch (err) {
|
||||
console.error('%s Failed to initialize git', chalk.red.bold('ERROR'));
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default initGit;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { install } from 'pkg-install';
|
||||
|
||||
const installNPMPackage = async (packageName, version) => {
|
||||
let installObject = {};
|
||||
|
||||
installObject[packageName] = version;
|
||||
|
||||
await install(installObject, {
|
||||
prefer: 'yarn',
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
};
|
||||
|
||||
export default installNPMPackage;
|
||||
@@ -0,0 +1,79 @@
|
||||
import chalk from 'chalk';
|
||||
import { colors } from '../enums/index.js';
|
||||
|
||||
function getStyle({ color, bold }) {
|
||||
return bold ? chalk.hex(color).bold : chalk.hex(color);
|
||||
}
|
||||
|
||||
function levelOnePrint(items) {
|
||||
let output = '';
|
||||
if (Array.isArray(items)) {
|
||||
items.forEach(item => {
|
||||
output += ` |- ${item}\n`;
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
return ` |- ${items}\n`;
|
||||
}
|
||||
|
||||
function levelTwoPrint(items) {
|
||||
let output = '';
|
||||
items.forEach(item => {
|
||||
output += ` | |- ${item}\n`;
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} title Title of the section
|
||||
* @param {object} titleOptions Options for the title includes color and bold
|
||||
* @param { [] | [][] } items Array of items to display, OR a list of lists
|
||||
* @param {object} itemOptions Options for the items includes color and bold
|
||||
*
|
||||
*
|
||||
* items= ['Mode-A', 'Mode-B', 'Mode-C']
|
||||
*
|
||||
* |- Mode-A
|
||||
* |- Mode-B
|
||||
* |- Mode-C
|
||||
*
|
||||
* items = [['Mode-A', ['Description-A', 'Authors-A', 'Repository-A]], ['Mode-B', ['Description-B', 'Authors-B', 'Repository-B]], ['Mode-C', ['Description-C', 'Authors-C', 'Repository-C]]]
|
||||
*
|
||||
* |- Mode-A
|
||||
* | |- Description-A
|
||||
* | |- Authors-A
|
||||
* | |- Repository-A
|
||||
* |
|
||||
* |- Mode-B
|
||||
* | |- Description-B
|
||||
* | |- Authors-B
|
||||
* | |- Repository-B
|
||||
*
|
||||
*
|
||||
*/
|
||||
function prettyPrint(
|
||||
title,
|
||||
titleOptions = { color: colors.MAIN, bold: true },
|
||||
itemsArray = [[]],
|
||||
itemOptions = {}
|
||||
) {
|
||||
console.log('');
|
||||
console.log(getStyle(titleOptions)(title));
|
||||
|
||||
let output = '';
|
||||
itemsArray.forEach(items => {
|
||||
if (!Array.isArray(items)) {
|
||||
output += levelOnePrint(items);
|
||||
} else {
|
||||
output += levelOnePrint(items[0]);
|
||||
output += levelTwoPrint(items[1]);
|
||||
}
|
||||
});
|
||||
|
||||
const itmeStyle = itemOptions.color ? getStyle(itemOptions)(output) : output;
|
||||
console.log(itmeStyle);
|
||||
}
|
||||
|
||||
export default prettyPrint;
|
||||
@@ -0,0 +1,15 @@
|
||||
export default function getPackageNameAndScope(packageName) {
|
||||
let scope;
|
||||
let packageNameLessScope;
|
||||
|
||||
if (packageName.includes('@')) {
|
||||
[scope, packageNameLessScope] = packageName.split('/');
|
||||
} else {
|
||||
packageNameLessScope = packageName;
|
||||
}
|
||||
|
||||
return {
|
||||
scope,
|
||||
packageNameLessScope,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import getPackageNameAndScope from './getPackageNameAndScope.js';
|
||||
import {
|
||||
addExtensionToConfigJson,
|
||||
removeExtensionFromConfigJson,
|
||||
addModeToConfigJson,
|
||||
removeModeFromConfigJson,
|
||||
} from './manipulatePluginConfigFile.js';
|
||||
import writePluginConfigFile from './writePluginConfigFile.js';
|
||||
import readPluginConfigFile from './readPluginConfigFile.js';
|
||||
|
||||
export {
|
||||
getPackageNameAndScope,
|
||||
addExtensionToConfigJson,
|
||||
removeExtensionFromConfigJson,
|
||||
addModeToConfigJson,
|
||||
removeModeFromConfigJson,
|
||||
readPluginConfigFile,
|
||||
writePluginConfigFile,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
function addExtensionToConfigJson(pluginConfig, { packageName, version }) {
|
||||
addToList('extensions', pluginConfig, { packageName, version });
|
||||
}
|
||||
|
||||
function addModeToConfigJson(pluginConfig, { packageName, version }) {
|
||||
addToList('modes', pluginConfig, { packageName, version });
|
||||
}
|
||||
|
||||
function removeExtensionFromConfigJson(pluginConfig, { packageName }) {
|
||||
removeFromList('extensions', pluginConfig, { packageName });
|
||||
}
|
||||
|
||||
function removeModeFromConfigJson(pluginConfig, { packageName }) {
|
||||
removeFromList('modes', pluginConfig, { packageName });
|
||||
}
|
||||
|
||||
function removeFromList(listName, pluginConfig, { packageName }) {
|
||||
const list = pluginConfig[listName];
|
||||
|
||||
const indexOfExistingEntry = list.findIndex(
|
||||
entry => entry.packageName === packageName
|
||||
);
|
||||
|
||||
if (indexOfExistingEntry !== -1) {
|
||||
pluginConfig[listName].splice(indexOfExistingEntry, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function addToList(listName, pluginConfig, { packageName, version }) {
|
||||
removeFromList(listName, pluginConfig, { packageName });
|
||||
|
||||
pluginConfig[listName].push({ packageName, version });
|
||||
}
|
||||
|
||||
export {
|
||||
addExtensionToConfigJson,
|
||||
addModeToConfigJson,
|
||||
removeExtensionFromConfigJson,
|
||||
removeModeFromConfigJson,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import fs from 'fs';
|
||||
|
||||
export default function readPluginConfigFile() {
|
||||
let fileContents;
|
||||
|
||||
try {
|
||||
fileContents = fs.readFileSync('./pluginConfig.json', { flag: 'r' });
|
||||
} catch (err) {
|
||||
return; // File doesn't exist yet.
|
||||
}
|
||||
|
||||
if (fileContents) {
|
||||
fileContents = JSON.parse(fileContents);
|
||||
}
|
||||
|
||||
return fileContents;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import fs from 'fs';
|
||||
|
||||
export default function writePluginConfigFile(pluginConfig) {
|
||||
// Note: Second 2 arguments are to pretty print the JSON so its human readable.
|
||||
const jsonStringOfFileContents = JSON.stringify(pluginConfig, null, 2);
|
||||
|
||||
fs.writeFileSync(
|
||||
`./pluginConfig.json`,
|
||||
jsonStringOfFileContents,
|
||||
{ flag: 'w+' },
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
removeExtensionFromConfigJson,
|
||||
removeModeFromConfigJson,
|
||||
writePluginConfigFile,
|
||||
readPluginConfigFile,
|
||||
} from './private/index.js';
|
||||
|
||||
function removeFromAndOverwriteConfig(packageName, augmentConfigFunction) {
|
||||
const pluginConfig = readPluginConfigFile();
|
||||
|
||||
// Note: if file is not found, nothing to remove.
|
||||
if (pluginConfig) {
|
||||
augmentConfigFunction(pluginConfig, { packageName });
|
||||
writePluginConfigFile(pluginConfig);
|
||||
}
|
||||
}
|
||||
|
||||
function removeExtensionFromConfig(packageName) {
|
||||
removeFromAndOverwriteConfig(packageName, removeExtensionFromConfigJson);
|
||||
}
|
||||
|
||||
function removeModeFromConfig(packageName) {
|
||||
removeFromAndOverwriteConfig(packageName, removeModeFromConfigJson);
|
||||
}
|
||||
|
||||
export { removeExtensionFromConfig, removeModeFromConfig };
|
||||
@@ -0,0 +1,48 @@
|
||||
import { readPluginConfigFile } from './private/index.js';
|
||||
import getYarnInfo from './getYarnInfo.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export default async function throwIfExtensionUsedByInstalledMode(packageName) {
|
||||
const pluginConfig = readPluginConfigFile();
|
||||
|
||||
if (!pluginConfig) {
|
||||
// No other modes, not in use
|
||||
return false;
|
||||
}
|
||||
|
||||
const { modes } = pluginConfig;
|
||||
|
||||
const modesUsingExtension = [];
|
||||
|
||||
for (let i = 0; i < modes.length; i++) {
|
||||
const mode = modes[i];
|
||||
const modePackageName = mode.packageName;
|
||||
const yarnInfo = await getYarnInfo(modePackageName);
|
||||
|
||||
const peerDependencies = yarnInfo.peerDependencies;
|
||||
|
||||
if (!peerDependencies) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.keys(peerDependencies).includes(packageName)) {
|
||||
modesUsingExtension.push(modePackageName);
|
||||
}
|
||||
}
|
||||
|
||||
if (modesUsingExtension.length > 0) {
|
||||
let modesString = '';
|
||||
|
||||
modesUsingExtension.forEach(packageName => {
|
||||
modesString += ` ${packageName}`;
|
||||
});
|
||||
|
||||
const error = new Error(
|
||||
`${chalk.yellow.red(
|
||||
'Error'
|
||||
)} ohif-extension ${packageName} used by installed modes:${modesString}`
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { remove } from 'yarn-programmatic';
|
||||
|
||||
const uninstallNPMPackage = async packageName => {
|
||||
// TODO - Anoyingly pkg-install doesn't seem to have uninstall.
|
||||
// So since we are using yarn we will just use yarn here, but the tool
|
||||
// is certainly less generic. But its a super minor issue.
|
||||
await remove(packageName).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
export default uninstallNPMPackage;
|
||||
@@ -0,0 +1,160 @@
|
||||
import registryUrl from 'registry-url';
|
||||
import keywords from '../enums/keywords.js';
|
||||
import { getPackageNameAndScope } from './private/index.js';
|
||||
import chalk from 'chalk';
|
||||
import fetch from 'node-fetch';
|
||||
import getYarnInfo from './getYarnInfo.js';
|
||||
import NOT_FOUND from '../constants/notFound.js';
|
||||
|
||||
async function validateMode(packageName, version) {
|
||||
return validate(packageName, version, keywords.MODE);
|
||||
}
|
||||
|
||||
async function validateExtension(packageName, version) {
|
||||
return validate(packageName, version, keywords.EXTENSION);
|
||||
}
|
||||
|
||||
async function validateModeYarnInfo(packageName) {
|
||||
return validateYarnInfo(packageName, keywords.MODE);
|
||||
}
|
||||
|
||||
async function validateExtensionYarnInfo(packageName) {
|
||||
return validateYarnInfo(packageName, keywords.EXTENSION);
|
||||
}
|
||||
|
||||
function validateYarnInfo(packageName, keyword) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
function rejectIfNotFound() {
|
||||
const error = new Error(
|
||||
`${chalk.red.bold('Error')} extension ${packageName} not installed`
|
||||
);
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const packageInfo = await getYarnInfo(packageName).catch(() => {
|
||||
rejectIfNotFound();
|
||||
});
|
||||
|
||||
if (!packageInfo) {
|
||||
rejectIfNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
const { keywords } = packageInfo;
|
||||
const isValid = keywords && keywords.includes(keyword);
|
||||
|
||||
if (isValid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
const error = new Error(
|
||||
`${chalk.red.bold('Error')} package ${packageName} is not an ${keyword}`
|
||||
);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getVersion(json, version) {
|
||||
const versions = Object.keys(json.versions);
|
||||
// if no version is defined get the latest
|
||||
if (version === undefined) {
|
||||
return json['dist-tags'].latest;
|
||||
}
|
||||
|
||||
// Get and validate version if it is explicitly defined
|
||||
const allowMinorVersionUpgrade = version.startsWith('^');
|
||||
if (!allowMinorVersionUpgrade) {
|
||||
const isValidVersion = versions.includes(version);
|
||||
|
||||
if (!isValidVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
// Choose version based on the newer minor/patch versions
|
||||
const [majorVersion] = version
|
||||
.split('^')[1]
|
||||
.split('.')
|
||||
.map((v) => parseInt(v));
|
||||
|
||||
// Find the version that matches the major version, but is the latest minor version
|
||||
versions
|
||||
.filter((version) => parseInt(version.split('.')[0]) === majorVersion)
|
||||
.sort((a, b) => {
|
||||
const [majorA, minorA, patchA] = a.split('.').map((v) => parseInt(v));
|
||||
const [majorB, minorB, patchB] = b.split('.').map((v) => parseInt(v));
|
||||
|
||||
if (majorA === majorB) {
|
||||
if (minorA === minorB) {
|
||||
return patchB - patchA;
|
||||
}
|
||||
|
||||
return minorB - minorA;
|
||||
}
|
||||
|
||||
return majorB - majorA;
|
||||
});
|
||||
|
||||
if (versions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return versions[0];
|
||||
}
|
||||
|
||||
function validate(packageName, version, keyword) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { scope } = getPackageNameAndScope(packageName);
|
||||
|
||||
// Gets the registry of the package. Scoped packages may not be using the global default.
|
||||
const registryUrlOfPackage = registryUrl(scope);
|
||||
|
||||
const response = await fetch(`${registryUrlOfPackage}${packageName}`);
|
||||
const json = await response.json();
|
||||
|
||||
if (json.error && json.error === NOT_FOUND) {
|
||||
const error = new Error(
|
||||
`${chalk.red.bold('Error')} package ${packageName} not found`
|
||||
);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const packageVersion = getVersion(json, version);
|
||||
|
||||
if (packageVersion) {
|
||||
const versionedJson = json.versions[packageVersion];
|
||||
const keywords = versionedJson.keywords;
|
||||
|
||||
const isValid = keywords && keywords.includes(keyword);
|
||||
|
||||
if (isValid) {
|
||||
resolve(true);
|
||||
} else {
|
||||
const error = new Error(
|
||||
`${chalk.red.bold(
|
||||
'Error'
|
||||
)} package ${packageName} is not an ${keyword}`
|
||||
);
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
// Particular version undefined
|
||||
const error = new Error(
|
||||
`${chalk.red.bold(
|
||||
'Error'
|
||||
)} version ${packageVersion} of package ${packageName} not found`
|
||||
);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateMode,
|
||||
validateExtension,
|
||||
validateModeYarnInfo,
|
||||
validateExtensionYarnInfo,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import chalk from 'chalk';
|
||||
import { execa } from 'execa';
|
||||
|
||||
export default async function validateYarn() {
|
||||
try {
|
||||
await execa('yarn', ['--version']);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'%s Yarn is not installed, please install it before linking your extension',
|
||||
chalk.red.bold('ERROR')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Executable
+210
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import program from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { getPathQuestions, getRepoQuestions } from './questions.js';
|
||||
import {
|
||||
createPackage,
|
||||
addExtension,
|
||||
removeExtension,
|
||||
addMode,
|
||||
removeMode,
|
||||
listPlugins,
|
||||
searchPlugins,
|
||||
linkExtension,
|
||||
linkMode,
|
||||
unlinkExtension,
|
||||
unlinkMode,
|
||||
} from './commands/index.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const runningDirectory = process.cwd();
|
||||
const viewerDirectory = path.resolve(runningDirectory, 'platform/viewer');
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const packageJsonPath = path.join(runningDirectory, 'package.json');
|
||||
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
if (packageJson.name !== 'ohif-monorepo-root') {
|
||||
console.log(packageJson);
|
||||
console.log(
|
||||
chalk.red('ohif-cli must run from the root of the OHIF platform')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
chalk.red('ohif-cli must run from the root of the OHIF platform')
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function _createPackage(packageType) {
|
||||
const pathQuestions = getPathQuestions(packageType);
|
||||
const repoQuestions = getRepoQuestions(packageType);
|
||||
|
||||
let pathAnswers;
|
||||
|
||||
const askPathQuestions = () => {
|
||||
inquirer.prompt(pathQuestions).then((answers) => {
|
||||
pathAnswers = answers;
|
||||
if (pathAnswers.confirm) {
|
||||
askRepoQuestions(answers.baseDir, answers.name);
|
||||
} else {
|
||||
askPathQuestions();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const askRepoQuestions = () => {
|
||||
inquirer.prompt(repoQuestions).then((repoAnswers) => {
|
||||
const answers = {
|
||||
...pathAnswers,
|
||||
...repoAnswers,
|
||||
};
|
||||
|
||||
const templateDir = path.join(__dirname, `../templates/${packageType}`);
|
||||
answers.templateDir = templateDir;
|
||||
answers.targetDir = path.join(answers.baseDir);
|
||||
answers.packageType = packageType;
|
||||
|
||||
createPackage(answers);
|
||||
});
|
||||
};
|
||||
|
||||
askPathQuestions();
|
||||
}
|
||||
|
||||
// Todo: inject with webpack
|
||||
program.version('2.0.7').description('OHIF CLI');
|
||||
|
||||
program
|
||||
.command('create-extension')
|
||||
.description('Create a new template extension')
|
||||
.action(() => {
|
||||
_createPackage('extension');
|
||||
});
|
||||
|
||||
program
|
||||
.command('create-mode')
|
||||
.description('Create a new template Mode')
|
||||
.action(() => {
|
||||
_createPackage('mode');
|
||||
});
|
||||
|
||||
program
|
||||
.command('add-extension <packageName> [version]')
|
||||
.description('Adds an ohif extension')
|
||||
.action((packageName, version) => {
|
||||
// change directory to viewer
|
||||
process.chdir(viewerDirectory);
|
||||
addExtension(packageName, version);
|
||||
});
|
||||
|
||||
program
|
||||
.command('remove-extension <packageName>')
|
||||
.description('removes an ohif extension')
|
||||
.action((packageName) => {
|
||||
// change directory to viewer
|
||||
process.chdir(viewerDirectory);
|
||||
removeExtension(packageName);
|
||||
});
|
||||
|
||||
program
|
||||
.command('add-mode <packageName> [version]')
|
||||
.description('Removes an ohif mode')
|
||||
.action((packageName, version) => {
|
||||
// change directory to viewer
|
||||
process.chdir(viewerDirectory);
|
||||
addMode(packageName, version);
|
||||
});
|
||||
|
||||
program
|
||||
.command('remove-mode <packageName>')
|
||||
.description('Removes an ohif mode')
|
||||
.action((packageName) => {
|
||||
// change directory to viewer
|
||||
process.chdir(viewerDirectory);
|
||||
removeMode(packageName);
|
||||
});
|
||||
|
||||
program
|
||||
.command('link-extension <packageDir>')
|
||||
.description(
|
||||
'Links a local OHIF extension to the Viewer to be used for development'
|
||||
)
|
||||
.action((packageDir) => {
|
||||
if (!fs.existsSync(packageDir)) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
'The extension directory does not exist, please provide a valid directory'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
linkExtension(packageDir, { viewerDirectory });
|
||||
});
|
||||
|
||||
program
|
||||
.command('unlink-extension <extensionName>')
|
||||
.description('Unlinks a local OHIF extension from the Viewer')
|
||||
.action((extensionName) => {
|
||||
unlinkExtension(extensionName, { viewerDirectory });
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Successfully unlinked extension ${extensionName} from the Viewer, don't forget to run yarn install --force`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command('link-mode <packageDir>')
|
||||
.description(
|
||||
'Links a local OHIF mode to the Viewer to be used for development'
|
||||
)
|
||||
.action((packageDir) => {
|
||||
if (!fs.existsSync(packageDir)) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
'The mode directory does not exist, please provide a valid directory'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
linkMode(packageDir, { viewerDirectory });
|
||||
});
|
||||
|
||||
program
|
||||
.command('unlink-mode <modeName>')
|
||||
.description('Unlinks a local OHIF mode from the Viewer')
|
||||
.action((modeName) => {
|
||||
unlinkMode(modeName, { viewerDirectory });
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Successfully unlinked mode ${modeName} from the Viewer, don't forget to run yarn install --force`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List Added Extensions and Modes')
|
||||
.action(() => {
|
||||
const configPath = path.resolve(viewerDirectory, './pluginConfig.json');
|
||||
listPlugins(configPath);
|
||||
});
|
||||
|
||||
program
|
||||
.command('search')
|
||||
.option('-v, --verbose', 'Verbose output')
|
||||
.description('Search NPM for the list of Modes and Extensions')
|
||||
.action((options) => {
|
||||
searchPlugins(options);
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
@@ -0,0 +1,85 @@
|
||||
import path from 'path';
|
||||
|
||||
function getPathQuestions(packageType) {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: `What is the name of your ${packageType}?`,
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
return 'Please enter a name';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
default: `my-${packageType}`,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'baseDir',
|
||||
message: `What is the target path to create your ${packageType} (we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType}):`,
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
console.log('Please provide a valid target directory path');
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filter: (input, answers) => {
|
||||
return path.resolve(input, answers.name);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Please confirm the above path for generating the ${packageType} folder:`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getRepoQuestions(packageType) {
|
||||
return [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'gitRepository',
|
||||
message: 'Should it be a git repository?',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'prettier',
|
||||
message: 'Should it follow same prettier rules as OHIF?',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'version',
|
||||
message: `What is the version of your ${packageType}?`,
|
||||
default: '0.0.1',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: `What is the description of your ${packageType}?`,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'author',
|
||||
message: `Who is the author of your ${packageType}?`,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'email',
|
||||
message: 'What is your email address?',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'license',
|
||||
message: `What is the license of your ${packageType}?`,
|
||||
default: 'MIT',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export { getPathQuestions, getRepoQuestions };
|
||||
@@ -0,0 +1,104 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
const path = require('path');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
const outputFile = 'index.umd.js';
|
||||
const rootDir = path.resolve(__dirname, '../');
|
||||
const outputFolder = path.join(__dirname, '../dist');
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: rootDir + '/' + pkg.module,
|
||||
devtool: 'inline-source-map',
|
||||
output: {
|
||||
path: outputFolder,
|
||||
filename: outputFile,
|
||||
library: pkg.name,
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true,
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react',
|
||||
},
|
||||
},
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /(\.jsx|\.js)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
extensions: ['.json', '.js', '.jsx'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
[
|
||||
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: 'commonjs',
|
||||
debug: false,
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/plugin-transform-runtime',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: ['react-hot-loader/babel'],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": ["ohif-extension"],
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:dicom-pdf": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/extension-default": "^1.0.1",
|
||||
"@ohif/extension-cornerstone": "^3.0.0",
|
||||
"@ohif/i18n": "^1.0.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router": "^6.2.1",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.2.0",
|
||||
"@babel/plugin-transform-regenerator": "^7.4.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"@babel/preset-env": "^7.5.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^5.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"webpack": "^4.12.2",
|
||||
"webpack-cli": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
@@ -0,0 +1,126 @@
|
||||
import { id } from './id';
|
||||
|
||||
/**
|
||||
* You can remove any of the following modules if you don't need them.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
* You ID can be anything you want, but it should be unique.
|
||||
*/
|
||||
id,
|
||||
|
||||
/**
|
||||
* Perform any pre-registration tasks here. This is called before the extension
|
||||
* is registered. Usually we run tasks such as: configuring the libraries
|
||||
* (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
|
||||
* this extension is providing.
|
||||
*/
|
||||
preRegistration: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
configuration = {},
|
||||
}) => {},
|
||||
/**
|
||||
* PanelModule should provide a list of panels that will be available in OHIF
|
||||
* for Modes to consume and render. Each panel is defined by a {name,
|
||||
* iconName, iconLabel, label, component} object. Example of a panel module
|
||||
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
||||
*/
|
||||
getPanelModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* ViewportModule should provide a list of viewports that will be available in OHIF
|
||||
* for Modes to consume and use in the viewports. Each viewport is defined by
|
||||
* {name, component} object. Example of a viewport module is the CornerstoneViewport
|
||||
* that is provided by the Cornerstone extension in OHIF.
|
||||
*/
|
||||
getViewportModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* ToolbarModule should provide a list of tool buttons that will be available in OHIF
|
||||
* for Modes to consume and use in the toolbar. Each tool button is defined by
|
||||
* {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and
|
||||
* splitButton toolButton that the default extension is providing.
|
||||
*/
|
||||
getToolbarModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* LayoutTemplateMOdule should provide a list of layout templates that will be
|
||||
* available in OHIF for Modes to consume and use to layout the viewer.
|
||||
* Each layout template is defined by a { name, id, component}. Examples include
|
||||
* the default layout template provided by the default extension which renders
|
||||
* a Header, left and right sidebars, and a viewport section in the middle
|
||||
* of the viewer.
|
||||
*/
|
||||
getLayoutTemplateModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
||||
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
||||
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
||||
* Examples include the default sop class handler provided by the default extension
|
||||
*/
|
||||
getSopClassHandlerModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* HangingProtocolModule should provide a list of hanging protocols that will be
|
||||
* available in OHIF for Modes to use to decide on the structure of the viewports
|
||||
* and also the series that hung in the viewports. Each hanging protocol is defined by
|
||||
* { name, protocols}. Examples include the default hanging protocol provided by
|
||||
* the default extension that shows 2x2 viewports.
|
||||
*/
|
||||
getHangingProtocolModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* CommandsModule should provide a list of commands that will be available in OHIF
|
||||
* for Modes to consume and use in the viewports. Each command is defined by
|
||||
* an object of { actions, definitions, defaultContext } where actions is an
|
||||
* object of functions, definitions is an object of available commands, their
|
||||
* options, and defaultContext is the default context for the command to run against.
|
||||
*/
|
||||
getCommandsModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* ContextModule should provide a list of context that will be available in OHIF
|
||||
* and will be provided to the Modes. A context is a state that is shared OHIF.
|
||||
* Context is defined by an object of { name, context, provider }. Examples include
|
||||
* the measurementTracking context provided by the measurementTracking extension.
|
||||
*/
|
||||
getContextModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
/**
|
||||
* DataSourceModule should provide a list of data sources to be used in OHIF.
|
||||
* DataSources can be used to map the external data formats to the OHIF's
|
||||
* native format. DataSources are defined by an object of { name, type, createDataSource }.
|
||||
*/
|
||||
getDataSourcesModule: ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}) => {},
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
const path = require('path');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
const outputFile = 'index.umd.js';
|
||||
const rootDir = path.resolve(__dirname, '../');
|
||||
const outputFolder = path.join(__dirname, '../dist');
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: rootDir + '/' + pkg.module,
|
||||
devtool: 'inline-source-map',
|
||||
output: {
|
||||
path: outputFolder,
|
||||
filename: outputFile,
|
||||
library: pkg.name,
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true,
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react',
|
||||
},
|
||||
},
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /(\.jsx|\.js)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
extensions: ['.json', '.js', '.jsx'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
[
|
||||
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: 'commonjs',
|
||||
debug: false,
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/plugin-transform-runtime',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: ['react-hot-loader/babel'],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": ["ohif-mode"],
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:cornerstone": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.2.0",
|
||||
"@babel/plugin-transform-regenerator": "^7.4.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"@babel/preset-env": "^7.5.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^5.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"webpack": "^4.12.2",
|
||||
"webpack-cli": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
@@ -0,0 +1,106 @@
|
||||
import { id } from './id';
|
||||
|
||||
const ohif = {
|
||||
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
|
||||
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
|
||||
hangingProtocols: '@ohif/extension-default.hangingProtocolModule.default',
|
||||
leftPanel: '@ohif/extension-default.panelModule.seriesList',
|
||||
rightPanel: '@ohif/extension-default.panelModule.measure',
|
||||
};
|
||||
|
||||
const cornerstone = {
|
||||
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
|
||||
};
|
||||
|
||||
/**
|
||||
* Just two dependencies to be able to render a viewport with panels in order
|
||||
* to make sure that the mode is working.
|
||||
*/
|
||||
const extensionDependencies = {
|
||||
'@ohif/extension-default': '^3.0.0',
|
||||
'@ohif/extension-cornerstone': '^3.0.0',
|
||||
};
|
||||
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
return {
|
||||
/**
|
||||
* Mode ID, which should be unique among modes used by the viewer. This ID
|
||||
* is used to identify the mode in the viewer's state.
|
||||
*/
|
||||
id,
|
||||
routeName: 'template',
|
||||
/**
|
||||
* Mode name, which is displayed in the viewer's UI in the workList, for the
|
||||
* user to select the mode.
|
||||
*/
|
||||
displayName: 'Template Mode',
|
||||
/**
|
||||
* Runs when the Mode Route is mounted to the DOM. Usually used to initialize
|
||||
* Services and other resources.
|
||||
*/
|
||||
onModeEnter: ({ servicesManager, extensionManager }) => {},
|
||||
/**
|
||||
* Runs when the Mode Route is unmounted from the DOM. Usually used to clean
|
||||
* up resources and states
|
||||
*/
|
||||
onModeExit: () => {},
|
||||
/** */
|
||||
validationTags: {
|
||||
study: [],
|
||||
series: [],
|
||||
},
|
||||
/**
|
||||
* A boolean return value that indicates whether the mode is valid for the
|
||||
* modalities of the selected studies. For instance a PET/CT mode should be
|
||||
*/
|
||||
isValidMode: ({ modalities }) => true,
|
||||
/**
|
||||
* Mode Routes are used to define the mode's behavior. A list of Mode Route
|
||||
* that includes the mode's path and the layout to be used. The layout will
|
||||
* include the components that are used in the layout. For instance, if the
|
||||
* default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout')
|
||||
* it will include the leftPanels, rightPanels, and viewports. However, if
|
||||
* you define another layoutTemplate that includes a Footer for instance,
|
||||
* you should provide the Footer component here too. Note: We use Strings
|
||||
* to reference the component's ID as they are registered in the internal
|
||||
* ExtensionManager. The template for the string is:
|
||||
* `${extensionId}.{moduleType}.${componentId}`.
|
||||
*/
|
||||
routes: [
|
||||
{
|
||||
path: 'template',
|
||||
layoutTemplate: ({ location, servicesManager }) => {
|
||||
return {
|
||||
id: ohif.layout,
|
||||
props: {
|
||||
leftPanels: [ohif.leftPanel],
|
||||
rightPanels: [ohif.rightPanel],
|
||||
viewports: [
|
||||
{
|
||||
namespace: cornerstone.viewport,
|
||||
displaySetsToDisplay: [ohif.sopClassHandler],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
/** List of extensions that are used by the mode */
|
||||
extensions: extensionDependencies,
|
||||
/** HangingProtocols used by the mode */
|
||||
hangingProtocols: [''],
|
||||
/** SopClassHandlers used by the mode */
|
||||
sopClassHandlers: [ohif.sopClassHandler],
|
||||
/** hotkeys for mode */
|
||||
hotkeys: [''],
|
||||
};
|
||||
}
|
||||
|
||||
const mode = {
|
||||
id,
|
||||
modeFactory,
|
||||
extensionDependencies,
|
||||
};
|
||||
|
||||
export default mode;
|
||||
Reference in new issue
Block a user