init: sudah ganti logo, hilangin setting, dan investigational use dialog
This commit is contained in:
3044
platform/cli/CHANGELOG.md
Normal file
3044
platform/cli/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
44
platform/cli/package.json
Normal file
44
platform/cli/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@ohif/cli",
|
||||
"version": "3.10.0-beta.111",
|
||||
"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": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"ohif"
|
||||
],
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"axios": "^0.28.0",
|
||||
"chalk": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"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/"
|
||||
]
|
||||
}
|
||||
53
platform/cli/src/commands/addExtension.js
Normal file
53
platform/cli/src/commands/addExtension.js
Normal file
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
36
platform/cli/src/commands/addExtensions.js
Normal file
36
platform/cli/src/commands/addExtensions.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
66
platform/cli/src/commands/addMode.js
Normal file
66
platform/cli/src/commands/addMode.js
Normal file
@@ -0,0 +1,66 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
1
platform/cli/src/commands/constants/notFound.js
Normal file
1
platform/cli/src/commands/constants/notFound.js
Normal file
@@ -0,0 +1 @@
|
||||
export default 'Not found';
|
||||
77
platform/cli/src/commands/createPackage.js
Normal file
77
platform/cli/src/commands/createPackage.js
Normal file
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
8
platform/cli/src/commands/enums/colors.js
Normal file
8
platform/cli/src/commands/enums/colors.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const colors = {
|
||||
LIGHT: '#5acce6',
|
||||
MAIN: '#0944b3',
|
||||
DARK: '#090c29',
|
||||
ACTIVE: '#348cfd',
|
||||
};
|
||||
|
||||
export default colors;
|
||||
5
platform/cli/src/commands/enums/endPoints.js
Normal file
5
platform/cli/src/commands/enums/endPoints.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const endPoints = {
|
||||
NPM_KEYWORD: 'https://registry.npmjs.com/-/v1/search?text=keywords:',
|
||||
};
|
||||
|
||||
export default endPoints;
|
||||
5
platform/cli/src/commands/enums/index.js
Normal file
5
platform/cli/src/commands/enums/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import keywords from './keywords.js';
|
||||
import colors from './colors.js';
|
||||
import endPoints from './endPoints.js';
|
||||
|
||||
export { keywords, colors, endPoints };
|
||||
6
platform/cli/src/commands/enums/keywords.js
Normal file
6
platform/cli/src/commands/enums/keywords.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const keywords = {
|
||||
MODE: 'ohif-mode',
|
||||
EXTENSION: 'ohif-extension',
|
||||
};
|
||||
|
||||
export default keywords;
|
||||
23
platform/cli/src/commands/index.js
Normal file
23
platform/cli/src/commands/index.js
Normal file
@@ -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,
|
||||
};
|
||||
79
platform/cli/src/commands/linkPackage.js
Normal file
79
platform/cli/src/commands/linkPackage.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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);
|
||||
|
||||
// Add the node_modules of the linked package so that webpack
|
||||
// can find the linked package externals if there are
|
||||
const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js');
|
||||
|
||||
async function updateWebpackConfig(webpackConfigPath, packageDir) {
|
||||
const packageNodeModules = path.join(packageDir, 'node_modules');
|
||||
const fileContent = await fs.promises.readFile(webpackConfigPath, 'utf8');
|
||||
|
||||
const newLine = `path.resolve(__dirname, '${packageNodeModules}'),`;
|
||||
const modifiedFileContent = fileContent.replace(
|
||||
/(modules:\s*\[)([\s\S]*?)(\])/,
|
||||
`$1$2 ${newLine.replace(/\\/g, '/')}$3`
|
||||
);
|
||||
|
||||
await fs.promises.writeFile(webpackConfigPath, modifiedFileContent);
|
||||
}
|
||||
|
||||
await updateWebpackConfig(webpackPwaPath, packageDir);
|
||||
|
||||
// change directory to viewer packages and add the config item
|
||||
process.chdir(viewerDirectory);
|
||||
addToConfig(packageName, {
|
||||
version,
|
||||
});
|
||||
|
||||
// run prettier on the webpack config
|
||||
results = await execa(`yarn`, ['prettier', '--write', webpackPwaPath]);
|
||||
}
|
||||
|
||||
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 };
|
||||
23
platform/cli/src/commands/listPlugins.js
Normal file
23
platform/cli/src/commands/listPlugins.js
Normal file
@@ -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;
|
||||
46
platform/cli/src/commands/removeExtension.js
Normal file
46
platform/cli/src/commands/removeExtension.js
Normal file
@@ -0,0 +1,46 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
36
platform/cli/src/commands/removeExtensions.js
Normal file
36
platform/cli/src/commands/removeExtensions.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
68
platform/cli/src/commands/removeMode.js
Normal file
68
platform/cli/src/commands/removeMode.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
57
platform/cli/src/commands/searchPlugins.js
Normal file
57
platform/cli/src/commands/searchPlugins.js
Normal file
@@ -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;
|
||||
66
platform/cli/src/commands/unlinkPackage.js
Normal file
66
platform/cli/src/commands/unlinkPackage.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { execa } from 'execa';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
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);
|
||||
|
||||
const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js');
|
||||
|
||||
await removePathFromWebpackConfig(webpackPwaPath, packageName);
|
||||
|
||||
//update the plugin.json file
|
||||
removeFromConfig(packageName);
|
||||
|
||||
// run prettier on the webpack config
|
||||
await execa(`yarn`, ['prettier', '--write', webpackPwaPath]);
|
||||
};
|
||||
|
||||
async function removePathFromWebpackConfig(webpackConfigPath, packageName) {
|
||||
const fileContent = await fs.promises.readFile(webpackConfigPath, 'utf8');
|
||||
|
||||
const packageNameSubstring = `${packageName}/node_modules`;
|
||||
const pathResolveStart = 'path.resolve(';
|
||||
const closingParenthesis = ')';
|
||||
|
||||
let startIndex = fileContent.indexOf(packageNameSubstring);
|
||||
|
||||
if (startIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the start of the "path.resolve" line.
|
||||
startIndex = fileContent.lastIndexOf(pathResolveStart, startIndex);
|
||||
|
||||
// Find the end of the line with the closing parenthesis.
|
||||
let endIndex = fileContent.indexOf(closingParenthesis, startIndex) + 1;
|
||||
|
||||
// Check if there's a comma after the closing parenthesis and remove it as well.
|
||||
if (fileContent[endIndex] === ',') {
|
||||
endIndex++;
|
||||
}
|
||||
|
||||
const modifiedFileContent = fileContent.slice(0, startIndex) + fileContent.slice(endIndex);
|
||||
|
||||
await fs.promises.writeFile(webpackConfigPath, modifiedFileContent);
|
||||
}
|
||||
|
||||
function unlinkExtension(extensionName, options) {
|
||||
linkPackage(extensionName, options, removeExtensionFromConfig);
|
||||
}
|
||||
|
||||
function unlinkMode(modeName, options) {
|
||||
linkPackage(modeName, options, removeModeFromConfig);
|
||||
}
|
||||
|
||||
export { unlinkExtension, unlinkMode };
|
||||
34
platform/cli/src/commands/utils/addToConfig.js
Normal file
34
platform/cli/src/commands/utils/addToConfig.js
Normal file
@@ -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 };
|
||||
40
platform/cli/src/commands/utils/createDirectoryContents.js
Normal file
40
platform/cli/src/commands/utils/createDirectoryContents.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
31
platform/cli/src/commands/utils/createLicense.js
Normal file
31
platform/cli/src/commands/utils/createLicense.js
Normal file
@@ -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;
|
||||
23
platform/cli/src/commands/utils/createReadme.js
Normal file
23
platform/cli/src/commands/utils/createReadme.js
Normal file
@@ -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;
|
||||
38
platform/cli/src/commands/utils/editPackageJson.js
Normal file
38
platform/cli/src/commands/utils/editPackageJson.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function editPackageJson(options) {
|
||||
const { name, version, description, author, license, targetDir } = options;
|
||||
|
||||
const ohifVersion = fs.readFileSync('./version.txt', 'utf8').trim();
|
||||
|
||||
// read package.json from targetDir
|
||||
const dependenciesPath = path.join(targetDir, 'dependencies.json');
|
||||
const rawData = fs.readFileSync(dependenciesPath, 'utf8');
|
||||
|
||||
const dataWithOHIFVersion = rawData.replace(/\{LATEST_OHIF_VERSION\}/g, ohifVersion);
|
||||
const packageJson = JSON.parse(dataWithOHIFVersion);
|
||||
|
||||
// edit package.json
|
||||
const mergedObj = Object.assign(
|
||||
{
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
author,
|
||||
license,
|
||||
main: `dist/umd/${name}/index.umd.js`,
|
||||
files: ['dist/**', 'public/**', '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,59 @@
|
||||
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}`;
|
||||
}
|
||||
5
platform/cli/src/commands/utils/getYarnInfo.js
Normal file
5
platform/cli/src/commands/utils/getYarnInfo.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { info } from 'yarn-programmatic';
|
||||
|
||||
export default async function getYarnInfo(packageName) {
|
||||
return await info(packageName);
|
||||
}
|
||||
47
platform/cli/src/commands/utils/index.js
Normal file
47
platform/cli/src/commands/utils/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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,
|
||||
};
|
||||
35
platform/cli/src/commands/utils/initGit.js
Normal file
35
platform/cli/src/commands/utils/initGit.js
Normal file
@@ -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;
|
||||
14
platform/cli/src/commands/utils/installNPMPackage.js
Normal file
14
platform/cli/src/commands/utils/installNPMPackage.js
Normal file
@@ -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;
|
||||
79
platform/cli/src/commands/utils/prettyPrint.js
Normal file
79
platform/cli/src/commands/utils/prettyPrint.js
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
19
platform/cli/src/commands/utils/private/index.js
Normal file
19
platform/cli/src/commands/utils/private/index.js
Normal file
@@ -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,38 @@
|
||||
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 + '\n', // Add a newline character at the end
|
||||
{ flag: 'w+' },
|
||||
err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
26
platform/cli/src/commands/utils/removeFromConfig.js
Normal file
26
platform/cli/src/commands/utils/removeFromConfig.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
12
platform/cli/src/commands/utils/uninstallNPMPackage.js
Normal file
12
platform/cli/src/commands/utils/uninstallNPMPackage.js
Normal file
@@ -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;
|
||||
152
platform/cli/src/commands/utils/validate.js
Normal file
152
platform/cli/src/commands/utils/validate.js
Normal file
@@ -0,0 +1,152 @@
|
||||
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);
|
||||
let options = {};
|
||||
if (process.env.NPM_TOKEN) {
|
||||
options['headers'] = {
|
||||
Authorization: `Bearer ${process.env.NPM_TOKEN}`,
|
||||
};
|
||||
}
|
||||
const response = await fetch(`${registryUrlOfPackage}${packageName}`, options);
|
||||
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 };
|
||||
14
platform/cli/src/commands/utils/validateYarn.js
Normal file
14
platform/cli/src/commands/utils/validateYarn.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
203
platform/cli/src/index.js
Executable file
203
platform/cli/src/index.js
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } 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/app');
|
||||
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();
|
||||
}
|
||||
|
||||
// for now ohif-cli is ran through yarn only.
|
||||
// see ohif-cli.md section # OHIF Command Line Interface for reference.
|
||||
const program = new Command('yarn run cli');
|
||||
// Todo: inject with webpack
|
||||
program
|
||||
.version('2.0.7')
|
||||
.description('OHIF CLI')
|
||||
.configureHelp({ sortOptions: true, sortSubcommands: true })
|
||||
.showHelpAfterError('(add --help for additional information)');
|
||||
|
||||
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('Add 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);
|
||||
95
platform/cli/src/questions.js
Normal file
95
platform/cli/src/questions.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
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}?`,
|
||||
suffix: `\n(we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType})`,
|
||||
maxLength: 40,
|
||||
validate: input => {
|
||||
if (!input) {
|
||||
console.log('Please provide a valid target directory path');
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filter: (input, answers) => {
|
||||
// Replace ~ with the user's home directory
|
||||
const expandedPath = input.replace(/^~(?=$|\/|\\)/, os.homedir());
|
||||
|
||||
// Resolve the path to an absolute path
|
||||
const resolvedPath = path.resolve(expandedPath, answers.name);
|
||||
|
||||
return resolvedPath;
|
||||
},
|
||||
},
|
||||
{
|
||||
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?',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
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 };
|
||||
104
platform/cli/templates/extension/.gitignore
vendored
Normal file
104
platform/cli/templates/extension/.gitignore
vendored
Normal file
@@ -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
|
||||
11
platform/cli/templates/extension/.prettierrc
Normal file
11
platform/cli/templates/extension/.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
96
platform/cli/templates/extension/.webpack/webpack.prod.js
Normal file
96
platform/cli/templates/extension/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,96 @@
|
||||
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/umd/${pkg.name}/`);
|
||||
|
||||
// Todo: add ESM build for the extension in addition to umd build
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: rootDir + '/' + pkg.module,
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
path: outputFolder,
|
||||
filename: outputFile,
|
||||
library: pkg.name,
|
||||
libraryTarget: 'umd',
|
||||
chunkFilename: '[name].chunk.js',
|
||||
umdNamedDefine: true,
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react',
|
||||
},
|
||||
'@ohif/core': {
|
||||
commonjs2: '@ohif/core',
|
||||
commonjs: '@ohif/core',
|
||||
amd: '@ohif/core',
|
||||
root: '@ohif/core',
|
||||
},
|
||||
'@ohif/ui': {
|
||||
commonjs2: '@ohif/ui',
|
||||
commonjs: '@ohif/ui',
|
||||
amd: '@ohif/ui',
|
||||
root: '@ohif/ui',
|
||||
},
|
||||
},
|
||||
],
|
||||
module: {
|
||||
|
||||
rules: [
|
||||
{
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
svgoConfig: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
titleProp: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
49
platform/cli/templates/extension/babel.config.js
Normal file
49
platform/cli/templates/extension/babel.config.js
Normal file
@@ -0,0 +1,49 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-transform-typescript',
|
||||
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
||||
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||
],
|
||||
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',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'@babel/plugin-transform-typescript',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
62
platform/cli/templates/extension/dependencies.json
Normal file
62
platform/cli/templates/extension/dependencies.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": ["ohif-extension"],
|
||||
"module": "src/index.tsx",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:my-extension": "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": "^{LATEST_OHIF_VERSION}",
|
||||
"@ohif/extension-default": "^{LATEST_OHIF_VERSION}",
|
||||
"@ohif/extension-cornerstone": "^{LATEST_OHIF_VERSION}",
|
||||
"@ohif/i18n": "^1.0.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
|
||||
"babel-eslint": "9.x",
|
||||
"babel-loader": "^8.2.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-cli": "^5.0.2"
|
||||
}
|
||||
}
|
||||
5
platform/cli/templates/extension/src/id.js
Normal file
5
platform/cli/templates/extension/src/id.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
86
platform/cli/templates/extension/src/index.tsx
Normal file
86
platform/cli/templates/extension/src/index.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
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 }) => {},
|
||||
};
|
||||
104
platform/cli/templates/mode/.gitignore
vendored
Normal file
104
platform/cli/templates/mode/.gitignore
vendored
Normal file
@@ -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
|
||||
11
platform/cli/templates/mode/.prettierrc
Normal file
11
platform/cli/templates/mode/.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
100
platform/cli/templates/mode/.webpack/webpack.prod.js
Normal file
100
platform/cli/templates/mode/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,100 @@
|
||||
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/umd/${pkg.name}/`);
|
||||
|
||||
// Todo: add ESM build for the mode in addition to umd build
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: rootDir + '/' + pkg.module,
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
path: outputFolder,
|
||||
filename: outputFile,
|
||||
library: pkg.name,
|
||||
libraryTarget: 'umd',
|
||||
chunkFilename: '[name].chunk.js',
|
||||
umdNamedDefine: true,
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react',
|
||||
},
|
||||
'@ohif/core': {
|
||||
commonjs2: '@ohif/core',
|
||||
commonjs: '@ohif/core',
|
||||
amd: '@ohif/core',
|
||||
root: '@ohif/core',
|
||||
},
|
||||
'@ohif/ui': {
|
||||
commonjs2: '@ohif/ui',
|
||||
commonjs: '@ohif/ui',
|
||||
amd: '@ohif/ui',
|
||||
root: '@ohif/ui',
|
||||
},
|
||||
'@ohif/mode-longitudinal': {
|
||||
commonjs2: '@ohif/mode-longitudinal',
|
||||
commonjs: '@ohif/mode-longitudinal',
|
||||
amd: '@ohif/mode-longitudinal',
|
||||
root: '@ohif/mode-longitudinal',
|
||||
}
|
||||
},
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
svgoConfig: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
titleProp: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
43
platform/cli/templates/mode/babel.config.js
Normal file
43
platform/cli/templates/mode/babel.config.js
Normal file
@@ -0,0 +1,43 @@
|
||||
module.exports = {
|
||||
plugins: ['@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-typescript',
|
||||
],
|
||||
'@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',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
53
platform/cli/templates/mode/dependencies.json
Normal file
53
platform/cli/templates/mode/dependencies.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": [
|
||||
"ohif-mode"
|
||||
],
|
||||
"module": "src/index.tsx",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --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": "^{LATEST_OHIF_VERSION}"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-cli": "^5.0.2"
|
||||
}
|
||||
}
|
||||
5
platform/cli/templates/mode/src/id.js
Normal file
5
platform/cli/templates/mode/src/id.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
140
platform/cli/templates/mode/src/index.tsx
Normal file
140
platform/cli/templates/mode/src/index.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { hotkeys } from '@ohif/core';
|
||||
import { initToolGroups, toolbarButtons } from '@ohif/mode-longitudinal';
|
||||
import { id } from './id';
|
||||
|
||||
const ohif = {
|
||||
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
|
||||
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
|
||||
hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default',
|
||||
leftPanel: '@ohif/extension-default.panelModule.seriesList',
|
||||
rightPanel: '@ohif/extension-cornerstone.panelModule.panelMeasurement',
|
||||
};
|
||||
|
||||
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, commandsManager }: withAppTypes) => {
|
||||
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
|
||||
|
||||
measurementService.clearMeasurements();
|
||||
|
||||
// Init Default and SR ToolGroups
|
||||
initToolGroups(extensionManager, toolGroupService, commandsManager);
|
||||
|
||||
toolbarService.addButtons(toolbarButtons);
|
||||
toolbarService.createButtonSection('primary', [
|
||||
'MeasurementTools',
|
||||
'Zoom',
|
||||
'WindowLevel',
|
||||
'Pan',
|
||||
'Capture',
|
||||
'Layout',
|
||||
'Crosshairs',
|
||||
'MoreTools',
|
||||
]);
|
||||
},
|
||||
onModeExit: ({ servicesManager }: withAppTypes) => {
|
||||
const {
|
||||
toolGroupService,
|
||||
syncGroupService,
|
||||
segmentationService,
|
||||
cornerstoneViewportService,
|
||||
uiDialogService,
|
||||
uiModalService,
|
||||
} = servicesManager.services;
|
||||
|
||||
uiDialogService.dismissAll();
|
||||
uiModalService.hide();
|
||||
toolGroupService.destroy();
|
||||
syncGroupService.destroy();
|
||||
segmentationService.destroy();
|
||||
cornerstoneViewportService.destroy();
|
||||
},
|
||||
/** */
|
||||
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 }) => {
|
||||
return { valid: 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,
|
||||
/** HangingProtocol used by the mode */
|
||||
// hangingProtocol: [''],
|
||||
/** SopClassHandlers used by the mode */
|
||||
sopClassHandlers: [ohif.sopClassHandler],
|
||||
/** hotkeys for mode */
|
||||
};
|
||||
}
|
||||
|
||||
const mode = {
|
||||
id,
|
||||
modeFactory,
|
||||
extensionDependencies,
|
||||
};
|
||||
|
||||
export default mode;
|
||||
Reference in New Issue
Block a user