Initial commit
This commit is contained in:
64
packages/module-manager/README.md
Normal file
64
packages/module-manager/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Module manager
|
||||
|
||||
Module manager is a tool to manage modules in a project. It can be used to delete or recover some pages.
|
||||
|
||||
## Usage
|
||||
|
||||
### On Root
|
||||
|
||||
```bash
|
||||
$ pnpm module-manager
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```bash
|
||||
pnpm module-manager -d popup
|
||||
```
|
||||
|
||||
### On Module
|
||||
|
||||
```bash
|
||||
$ pnpm start
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```bash
|
||||
pnpm start -d popup
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For complete info about CLI support run:
|
||||
> `pnpm module-manager --help`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you want to remove all tests, and something else with one command, you need to set `tests` as first argument like:
|
||||
> `pnpm module-manager -d tests popup ...`
|
||||
|
||||
### Choose a tool
|
||||
|
||||
```
|
||||
? Choose a tool (Use arrow keys)
|
||||
❯ Delete Feature
|
||||
Recover Feature
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Delete Feature
|
||||
|
||||
When you select an unused module, Module Manager compresses the contents of that folder, takes a snapshot of it, and removes it.
|
||||
|
||||
It also automatically removes anything that needs to be cleared from the manifest.
|
||||
|
||||
### Recover Feature
|
||||
|
||||
When you select a module that has been deleted, The Module Manager will recover that module from the snapshot and add it back to the manifest.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> SAVE ALL FILES FROM /ARCHIVE, WITHOUT THESE FILES' RECOVERING PROCESS WON'T WORK
|
||||
|
||||
> [!IMPORTANT]
|
||||
> IF YOU DECIDE TO REMOVE ONE OF CONTENT SCRIPTs AFTER EDIT, E.G YOU REMOVE OR ADD ANY MATCHES,
|
||||
> REMEMBER TO EDIT ALSO [MODULE_CONFIG](./lib/const.ts) TO BE ABLE TO RECOVER IT EASILY IN THE FUTURE.
|
||||
3
packages/module-manager/index.mts
Normal file
3
packages/module-manager/index.mts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { startManager } from './lib/index.js';
|
||||
|
||||
startManager();
|
||||
51
packages/module-manager/lib/base/cli-args-processor.ts
Normal file
51
packages/module-manager/lib/base/cli-args-processor.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CLI_OPTIONS, DEFAULT_CHOICES_VALUES, HELP_EXAMPLES } from '../const.js';
|
||||
import { checkCliArgsIsValid } from '../helpers/utils.js';
|
||||
import { excludeValuesFromBaseArray } from '@extension/shared';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import type { ICLIOptions, ModuleNameType } from '../types.js';
|
||||
import type { WritableDeep } from '@extension/shared';
|
||||
|
||||
export const processCLIArgs = (): ICLIOptions | null => {
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option('delete', CLI_OPTIONS[0])
|
||||
.option('recover', CLI_OPTIONS[1])
|
||||
.option('delete-exclude', CLI_OPTIONS[2])
|
||||
.option('recover-exclude', CLI_OPTIONS[3])
|
||||
.check(argv => checkCliArgsIsValid(argv))
|
||||
.strict()
|
||||
.example(HELP_EXAMPLES as WritableDeep<typeof HELP_EXAMPLES>)
|
||||
.help()
|
||||
.showHelpOnFail(true)
|
||||
.parseSync();
|
||||
|
||||
if (argv.delete) {
|
||||
return {
|
||||
action: 'delete',
|
||||
targets: argv.delete as ModuleNameType[],
|
||||
};
|
||||
}
|
||||
|
||||
if (argv.recover) {
|
||||
return {
|
||||
action: 'recover',
|
||||
targets: argv.recover as ModuleNameType[],
|
||||
};
|
||||
}
|
||||
|
||||
if (argv.deleteExclude) {
|
||||
return {
|
||||
action: 'delete',
|
||||
targets: excludeValuesFromBaseArray(DEFAULT_CHOICES_VALUES, argv.deleteExclude),
|
||||
};
|
||||
}
|
||||
|
||||
if (argv.recoverExclude) {
|
||||
return {
|
||||
action: 'recover',
|
||||
targets: excludeValuesFromBaseArray(DEFAULT_CHOICES_VALUES, argv.recoverExclude),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
2
packages/module-manager/lib/base/index.ts
Normal file
2
packages/module-manager/lib/base/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './cli-args-processor.js';
|
||||
export * from './run-module-manager.js';
|
||||
49
packages/module-manager/lib/base/run-module-manager.ts
Normal file
49
packages/module-manager/lib/base/run-module-manager.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import manifest from '../../../../chrome-extension/manifest.js';
|
||||
import { MANAGER_ACTION_PROMPT_CONFIG } from '../const.js';
|
||||
import { promptSelection } from '../helpers/utils.js';
|
||||
import { deleteFeature, recoverFeature } from '../processing/index.js';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import type { CliActionType, ModuleNameType } from '../types.js';
|
||||
import type { ManifestType } from '@extension/shared';
|
||||
|
||||
const manifestPath = resolve(import.meta.dirname, '..', '..', '..', '..', 'chrome-extension', 'manifest.ts');
|
||||
|
||||
const manifestObject = JSON.parse(JSON.stringify(manifest)) as ManifestType;
|
||||
const manifestString = readFileSync(manifestPath, 'utf-8');
|
||||
|
||||
export const runModuleManager = async (moduleName?: ModuleNameType, action?: CliActionType, isLastLap = true) => {
|
||||
if (!action) {
|
||||
action = (await promptSelection(MANAGER_ACTION_PROMPT_CONFIG)) as Awaited<CliActionType>;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
await deleteFeature(manifestObject, moduleName);
|
||||
break;
|
||||
case 'recover':
|
||||
await recoverFeature(manifestObject, moduleName);
|
||||
}
|
||||
|
||||
const updatedManifest = manifestString
|
||||
.replace(
|
||||
/const manifest = {[\s\S]*?} satisfies/,
|
||||
'const manifest = ' + JSON.stringify(manifestObject, null, 2) + ' satisfies',
|
||||
)
|
||||
.replace(/ {2}"version": "[\s\S]*?",/, ' version: packageJson.version,');
|
||||
|
||||
writeFileSync(manifestPath, updatedManifest);
|
||||
|
||||
if (isLastLap) {
|
||||
execSync('pnpm i', {
|
||||
stdio: 'inherit',
|
||||
cwd: resolve('..', '..'),
|
||||
});
|
||||
|
||||
execSync('pnpm -F chrome-extension lint:fix', {
|
||||
stdio: 'inherit',
|
||||
cwd: resolve('..', '..'),
|
||||
});
|
||||
}
|
||||
};
|
||||
98
packages/module-manager/lib/const.ts
Normal file
98
packages/module-manager/lib/const.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
const CHOICE_QUESTION = 'Choose feature to';
|
||||
export const RECOVER_CHOICE_QUESTION = `${CHOICE_QUESTION} recover`;
|
||||
export const DELETE_CHOICE_QUESTION = `${CHOICE_QUESTION} delete`;
|
||||
|
||||
export const DEFAULT_CHOICES = [
|
||||
{ name: 'Content Script (Execute JS on Web Page)', value: 'content' },
|
||||
{ name: 'Content Script UI (Render Custom React Component on Web Page)', value: 'content-ui' },
|
||||
{ name: 'Content Script Runtime (Inject JS on Specific Actions like Popup Click)', value: 'content-runtime' },
|
||||
{ name: 'Background Script', value: 'background' },
|
||||
{ name: 'New Tab Override', value: 'new-tab' },
|
||||
{ name: 'Popup (On Extension Icon Click)', value: 'popup' },
|
||||
{ name: 'DevTools (Include DevTools Panel)', value: 'devtools' },
|
||||
{ name: 'Side Panel', value: 'side-panel' },
|
||||
{ name: 'Options Page', value: 'options' },
|
||||
{ name: 'All tests', value: 'tests' },
|
||||
] as const;
|
||||
|
||||
export const DEFAULT_CHOICES_VALUES = DEFAULT_CHOICES.map(item => item.value);
|
||||
|
||||
export const HELP_EXAMPLES = [
|
||||
['-d content-ui content-runtime', 'Delete content-ui and content-runtime'],
|
||||
['--de content devtools', 'Delete everything exclude content and devtools'],
|
||||
['-r options side-panel', 'Recover options and side-panel'],
|
||||
['--re popup new-tab', 'Recover everything exclude popup and new-tab'],
|
||||
] as const;
|
||||
|
||||
export const CLI_OPTIONS = [
|
||||
{ alias: 'd', type: 'array', description: 'Delete specified features' },
|
||||
{ alias: 'r', type: 'array', description: 'Recover specified features' },
|
||||
{ alias: 'de', type: 'array', description: 'Delete all features except specified' },
|
||||
{ alias: 're', type: 'array', description: 'Recover all features except specified' },
|
||||
] as const;
|
||||
|
||||
export const MANAGER_ACTION_PROMPT_CONFIG = {
|
||||
message: 'Choose a tool',
|
||||
choices: [
|
||||
{ name: 'Delete Feature', value: 'delete' },
|
||||
{ name: 'Recover Feature', value: 'recover' },
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const MODULE_CONFIG = {
|
||||
content: {
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ['http://*/*', 'https://*/*', '<all_urls>'],
|
||||
js: ['content/all.iife.js'],
|
||||
},
|
||||
{
|
||||
matches: ['https://example.com/*'],
|
||||
js: ['content/example.iife.js'],
|
||||
},
|
||||
],
|
||||
},
|
||||
'content-ui': {
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ['http://*/*', 'https://*/*', '<all_urls>'],
|
||||
js: ['content-ui/all.iife.js'],
|
||||
},
|
||||
{
|
||||
matches: ['https://example.com/*'],
|
||||
js: ['content-ui/example.iife.js'],
|
||||
},
|
||||
],
|
||||
},
|
||||
background: {
|
||||
background: {
|
||||
service_worker: 'background.js',
|
||||
type: 'module',
|
||||
},
|
||||
},
|
||||
'new-tab': {
|
||||
chrome_url_overrides: {
|
||||
newtab: 'new-tab/index.html',
|
||||
},
|
||||
},
|
||||
popup: {
|
||||
action: {
|
||||
default_popup: 'popup/index.html',
|
||||
default_icon: 'icon-34.png',
|
||||
},
|
||||
},
|
||||
devtools: {
|
||||
devtools_page: 'devtools/index.html',
|
||||
},
|
||||
'side-panel': {
|
||||
side_panel: {
|
||||
default_path: 'side-panel/index.html',
|
||||
},
|
||||
permissions: ['sidePanel'],
|
||||
},
|
||||
options: {
|
||||
options_page: 'options/index.html',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const EXIT_PROMPT_ERROR = 'ExitPromptError';
|
||||
2
packages/module-manager/lib/helpers/index.ts
Normal file
2
packages/module-manager/lib/helpers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './utils.js';
|
||||
export * from './zip-utils.js';
|
||||
113
packages/module-manager/lib/helpers/utils.ts
Normal file
113
packages/module-manager/lib/helpers/utils.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { DEFAULT_CHOICES_VALUES, EXIT_PROMPT_ERROR, MODULE_CONFIG } from '../const.js';
|
||||
import { colorfulLog } from '@extension/shared';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import type { DELETE_CHOICE_QUESTION, RECOVER_CHOICE_QUESTION } from '../const.js';
|
||||
import type {
|
||||
ChoicesType,
|
||||
CliEntriesType,
|
||||
InputConfigType,
|
||||
ModuleNameType,
|
||||
WritableModuleConfigValuesType,
|
||||
} from '../types.js';
|
||||
import type { ConditionalPickDeep, Entries, ManifestType } from '@extension/shared';
|
||||
import type { Arguments } from 'yargs';
|
||||
|
||||
export const isFolderEmpty = (path: string) => !readdirSync(path).length;
|
||||
|
||||
export const promptSelection = async (inputConfig: InputConfigType) =>
|
||||
select(inputConfig).catch(err => {
|
||||
if (err.name === EXIT_PROMPT_ERROR) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
colorfulLog(err.message, 'error');
|
||||
}
|
||||
}) as Promise<string>;
|
||||
|
||||
export const processModuleConfig = (
|
||||
manifestObject: ManifestType,
|
||||
moduleName: ModuleNameType,
|
||||
isRecovering?: boolean,
|
||||
) => {
|
||||
if (moduleName === 'content-runtime' || moduleName === 'devtools-panel' || moduleName === 'tests') {
|
||||
return;
|
||||
}
|
||||
|
||||
const moduleConfigValues = MODULE_CONFIG[moduleName];
|
||||
const moduleConfigEntriesOfKeys = Object.entries(moduleConfigValues) as Entries<typeof moduleConfigValues>;
|
||||
|
||||
if (moduleName === 'content' || moduleName === 'content-ui') {
|
||||
if (isRecovering) {
|
||||
(moduleConfigValues as WritableModuleConfigValuesType<typeof moduleName>).content_scripts.map(script =>
|
||||
manifestObject.content_scripts?.push(script),
|
||||
);
|
||||
} else {
|
||||
const outputFileName = new RegExp(`${moduleName}/+`);
|
||||
|
||||
manifestObject.content_scripts = manifestObject.content_scripts?.filter(
|
||||
script => !outputFileName.test(script.js ? script.js[0] : ''),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
moduleConfigEntriesOfKeys.forEach(([key, value]) => {
|
||||
const manifestValue = manifestObject[key];
|
||||
|
||||
if (manifestValue) {
|
||||
if (manifestValue instanceof Array) {
|
||||
const arrayValues = Object.values(
|
||||
moduleConfigValues[key as ConditionalPickDeep<keyof typeof moduleConfigValues, typeof moduleName>],
|
||||
);
|
||||
|
||||
if (isRecovering) {
|
||||
manifestObject[key] = manifestValue.concat(arrayValues);
|
||||
} else {
|
||||
manifestObject[key] = manifestValue.filter((value: string) => !arrayValues.includes(value));
|
||||
}
|
||||
} else {
|
||||
delete manifestObject[key];
|
||||
}
|
||||
} else if (isRecovering) {
|
||||
Object.assign(manifestObject, { [key]: value });
|
||||
} else {
|
||||
throw new Error(`Key ${key} not found in manifest.ts`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const checkCliArgsIsValid = <T extends Arguments>(argv: T) => {
|
||||
const [key, values] = Object.entries(argv)[1] as CliEntriesType;
|
||||
|
||||
if (Array.isArray(values)) {
|
||||
for (const value of values) {
|
||||
if (!DEFAULT_CHOICES_VALUES.some(moduleName => value === moduleName)) {
|
||||
throw new Error(`All values after --${key} must be names of pages`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const processSelection = async (
|
||||
choices: ChoicesType,
|
||||
question: typeof RECOVER_CHOICE_QUESTION | typeof DELETE_CHOICE_QUESTION,
|
||||
moduleName?: ModuleNameType,
|
||||
) => {
|
||||
if (!choices.length) {
|
||||
colorfulLog('No options available', 'warning');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!moduleName) {
|
||||
const inputConfig = {
|
||||
message: question,
|
||||
choices,
|
||||
} as const;
|
||||
|
||||
return (await promptSelection(inputConfig)) as Awaited<ModuleNameType>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
97
packages/module-manager/lib/helpers/zip-utils.ts
Normal file
97
packages/module-manager/lib/helpers/zip-utils.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { streamFileToZip } from '@extension/dev-utils';
|
||||
import fg from 'fast-glob';
|
||||
import { unzipSync, Zip } from 'fflate';
|
||||
import { rimraf } from 'rimraf';
|
||||
import { createWriteStream, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import type { ModuleNameType } from '../types.js';
|
||||
|
||||
const zipAndDeleteModuleTest = async (moduleName: ModuleNameType, archivePath: string, testsPath: string) => {
|
||||
const moduleTestName = `page-${moduleName}.test.ts`;
|
||||
const moduleTestsPath = resolve(testsPath, moduleTestName);
|
||||
|
||||
await zipFolder(testsPath, resolve(archivePath, `${moduleName}.test.zip`), [moduleTestName]);
|
||||
void rimraf([moduleTestsPath]);
|
||||
};
|
||||
|
||||
export const zipAndDeleteModule = async (
|
||||
moduleName: ModuleNameType,
|
||||
pagesPath: string,
|
||||
archivePath: string,
|
||||
testsPath?: string,
|
||||
) => {
|
||||
const modulePath = resolve(pagesPath, moduleName);
|
||||
|
||||
await zipFolder(resolve(pagesPath, moduleName), resolve(archivePath, `${moduleName}.zip`));
|
||||
void rimraf([modulePath]);
|
||||
|
||||
if (testsPath && existsSync(testsPath)) {
|
||||
await zipAndDeleteModuleTest(moduleName, archivePath, testsPath);
|
||||
}
|
||||
};
|
||||
|
||||
export const zipAndDeleteTests = async (testsPath: string, archivePath: string) => {
|
||||
await zipFolder(testsPath, resolve(archivePath, `tests.zip`));
|
||||
void rimraf(testsPath);
|
||||
};
|
||||
|
||||
export const unZipAndDeleteModule = (zipFilePath: string, destPath: string) => {
|
||||
const unzipped = unzipSync(readFileSync(zipFilePath));
|
||||
|
||||
mkdirSync(destPath, { recursive: true });
|
||||
|
||||
for (const [filePath, fileData] of Object.entries(unzipped)) {
|
||||
const dir = dirname(filePath);
|
||||
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(filePath, fileData);
|
||||
}
|
||||
|
||||
unlinkSync(zipFilePath);
|
||||
};
|
||||
|
||||
export const zipFolder = async (folderPath: string, out: string, filesToInclude?: string[]) => {
|
||||
const fileList = await fg(
|
||||
['!node_modules', '!dist', '!**/*/node_modules', '!**/*/dist'].concat(
|
||||
filesToInclude?.length ? filesToInclude : ['**/*'],
|
||||
),
|
||||
{
|
||||
cwd: folderPath,
|
||||
onlyFiles: true,
|
||||
},
|
||||
);
|
||||
const output = createWriteStream(out);
|
||||
|
||||
return new Promise<void>((resolvePromise, rejectPromise) => {
|
||||
const zip = new Zip((err, data, final) => {
|
||||
if (err) {
|
||||
rejectPromise(err);
|
||||
} else {
|
||||
output.write(data);
|
||||
if (final) {
|
||||
output.end();
|
||||
resolvePromise();
|
||||
console.log(`Archive created at: ${out} for recovery`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const file of fileList) {
|
||||
const absPath = resolve(folderPath, file);
|
||||
|
||||
console.log(`Archiving file: ${absPath}`);
|
||||
streamFileToZip(
|
||||
absPath,
|
||||
absPath,
|
||||
zip,
|
||||
() => {
|
||||
output.end();
|
||||
rejectPromise(new Error('Aborted'));
|
||||
},
|
||||
rejectPromise,
|
||||
);
|
||||
}
|
||||
|
||||
zip.end();
|
||||
});
|
||||
};
|
||||
21
packages/module-manager/lib/index.ts
Normal file
21
packages/module-manager/lib/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { processCLIArgs, runModuleManager } from './base/index.js';
|
||||
import { colorfulLog, sleep } from '@extension/shared';
|
||||
|
||||
const cliOptions = processCLIArgs();
|
||||
|
||||
export const startManager = () => {
|
||||
if (cliOptions) {
|
||||
const targets = cliOptions.targets;
|
||||
|
||||
(async () => {
|
||||
for (const moduleName of targets) {
|
||||
colorfulLog(`Processing module: ${moduleName}`, 'info');
|
||||
const isLastIndex = targets.at(-1) === moduleName;
|
||||
await runModuleManager(moduleName, cliOptions.action, isLastIndex);
|
||||
await sleep(500);
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
void runModuleManager();
|
||||
}
|
||||
};
|
||||
6
packages/module-manager/lib/paths.ts
Normal file
6
packages/module-manager/lib/paths.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export const pagesPath = resolve(import.meta.dirname, '..', '..', '..', 'pages');
|
||||
export const testsPath = resolve(pagesPath, '..', 'tests');
|
||||
export const specsPath = resolve(testsPath, 'e2e', 'specs');
|
||||
export const archivePath = resolve(import.meta.dirname, '..', 'archive');
|
||||
34
packages/module-manager/lib/processing/delete-feature.ts
Normal file
34
packages/module-manager/lib/processing/delete-feature.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { deleteModule } from './modules-handler.js';
|
||||
import { DEFAULT_CHOICES, DELETE_CHOICE_QUESTION } from '../const.js';
|
||||
import { processSelection } from '../helpers/utils.js';
|
||||
import { pagesPath, testsPath } from '../paths.js';
|
||||
import { existsSync, readdirSync } from 'node:fs';
|
||||
import type { ChoicesType, ModuleNameType } from '../types.ts';
|
||||
import type { ManifestType } from '@extension/shared';
|
||||
|
||||
export const deleteFeature = async (manifestObject: ManifestType, moduleName?: ModuleNameType) => {
|
||||
const pageFolders = readdirSync(pagesPath);
|
||||
|
||||
const choices: ChoicesType = DEFAULT_CHOICES.filter(choice => {
|
||||
if (choice.value === 'background') {
|
||||
return !!manifestObject.background;
|
||||
} else if (choice.value === 'tests') {
|
||||
return existsSync(testsPath);
|
||||
}
|
||||
|
||||
return pageFolders.includes(choice.value);
|
||||
});
|
||||
|
||||
const processResult = await processSelection(choices, DELETE_CHOICE_QUESTION, moduleName);
|
||||
|
||||
if (processResult) {
|
||||
moduleName = processResult;
|
||||
}
|
||||
|
||||
if (moduleName === 'devtools') {
|
||||
await deleteModule(manifestObject, moduleName as ModuleNameType);
|
||||
await deleteModule(manifestObject, 'devtools-panel');
|
||||
} else {
|
||||
await deleteModule(manifestObject, moduleName as ModuleNameType);
|
||||
}
|
||||
};
|
||||
3
packages/module-manager/lib/processing/index.ts
Normal file
3
packages/module-manager/lib/processing/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './delete-feature.js';
|
||||
export * from './recover-feature.js';
|
||||
export * from './modules-handler.js';
|
||||
48
packages/module-manager/lib/processing/modules-handler.ts
Normal file
48
packages/module-manager/lib/processing/modules-handler.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { unZipAndDeleteModule, zipAndDeleteModule, zipAndDeleteTests } from '../helpers/index.js';
|
||||
import { isFolderEmpty, processModuleConfig } from '../helpers/utils.js';
|
||||
import { archivePath, pagesPath, specsPath, testsPath } from '../paths.js';
|
||||
import { colorfulLog } from '@extension/shared';
|
||||
import { existsSync, mkdirSync, rmdirSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import type { ModuleNameType } from '../types.ts';
|
||||
import type { ManifestType } from '@extension/shared';
|
||||
|
||||
export const recoverModule = (manifestObject: ManifestType, moduleName: ModuleNameType) => {
|
||||
const zipFilePath = resolve(archivePath, `${moduleName}.zip`);
|
||||
const zipTestFilePath = resolve(archivePath, `${moduleName}.test.zip`);
|
||||
|
||||
if (!existsSync(zipFilePath)) {
|
||||
colorfulLog(`No archive found for ${moduleName}`, 'info');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
processModuleConfig(manifestObject, moduleName, true);
|
||||
|
||||
unZipAndDeleteModule(zipFilePath, pagesPath);
|
||||
|
||||
if (existsSync(zipTestFilePath)) {
|
||||
unZipAndDeleteModule(zipTestFilePath, specsPath);
|
||||
}
|
||||
|
||||
colorfulLog(`Recovered: ${moduleName}`, 'info');
|
||||
|
||||
if (isFolderEmpty(archivePath)) {
|
||||
rmdirSync(archivePath);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteModule = async (manifestObject: ManifestType, moduleName: ModuleNameType) => {
|
||||
processModuleConfig(manifestObject, moduleName);
|
||||
|
||||
if (!existsSync(archivePath)) {
|
||||
mkdirSync(archivePath, { recursive: true });
|
||||
}
|
||||
|
||||
if (moduleName === 'tests') {
|
||||
await zipAndDeleteTests(testsPath, archivePath);
|
||||
} else {
|
||||
await zipAndDeleteModule(moduleName, pagesPath, archivePath, specsPath);
|
||||
}
|
||||
|
||||
colorfulLog(`Deleted: ${moduleName}`, 'info');
|
||||
};
|
||||
41
packages/module-manager/lib/processing/recover-feature.ts
Normal file
41
packages/module-manager/lib/processing/recover-feature.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { recoverModule } from './modules-handler.js';
|
||||
import { DEFAULT_CHOICES, RECOVER_CHOICE_QUESTION } from '../const.js';
|
||||
import { processSelection } from '../helpers/utils.js';
|
||||
import { archivePath } from 'lib/paths.js';
|
||||
import { rimraf } from 'rimraf';
|
||||
import { existsSync, readdirSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import type { ChoicesType, ModuleNameType } from '../types.ts';
|
||||
import type { ManifestType } from '@extension/shared';
|
||||
|
||||
export const recoverFeature = async (manifestObject: ManifestType, moduleName?: ModuleNameType) => {
|
||||
const archiveFiles = existsSync(archivePath) ? readdirSync(archivePath) : [];
|
||||
|
||||
const choices: ChoicesType = DEFAULT_CHOICES.filter(choice => {
|
||||
if (choice.value === 'background') {
|
||||
return !manifestObject.background;
|
||||
}
|
||||
|
||||
return archiveFiles.includes(`${choice.value}.zip`);
|
||||
});
|
||||
|
||||
const processResult = await processSelection(choices, RECOVER_CHOICE_QUESTION, moduleName);
|
||||
|
||||
if (processResult) {
|
||||
moduleName = processResult;
|
||||
}
|
||||
|
||||
if (moduleName === 'tests') {
|
||||
recoverModule(manifestObject, moduleName as ModuleNameType);
|
||||
const testZipFilePath = resolve(archivePath, `${moduleName}.test.zip`);
|
||||
|
||||
if (existsSync(testZipFilePath)) {
|
||||
await rimraf(testZipFilePath);
|
||||
}
|
||||
} else if (moduleName === 'devtools') {
|
||||
recoverModule(manifestObject, moduleName as ModuleNameType);
|
||||
recoverModule(manifestObject, 'devtools-panel');
|
||||
} else {
|
||||
recoverModule(manifestObject, moduleName as ModuleNameType);
|
||||
}
|
||||
};
|
||||
19
packages/module-manager/lib/types.ts
Normal file
19
packages/module-manager/lib/types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { DEFAULT_CHOICES, MODULE_CONFIG } from './const.js';
|
||||
import type { WritableDeep } from '@extension/shared';
|
||||
import type { select } from '@inquirer/prompts';
|
||||
|
||||
type ModuleConfigType = typeof MODULE_CONFIG;
|
||||
|
||||
export type ChoiceType = (typeof DEFAULT_CHOICES)[number];
|
||||
export type ChoicesType = ChoiceType[];
|
||||
export type ModuleNameType = ChoiceType['value'] | 'devtools-panel';
|
||||
export type InputConfigType = Parameters<typeof select>[0];
|
||||
export type WritableModuleConfigValuesType<T extends keyof ModuleConfigType> = WritableDeep<ModuleConfigType[T]>;
|
||||
|
||||
export interface ICLIOptions {
|
||||
action: 'delete' | 'recover';
|
||||
targets: ModuleNameType[];
|
||||
}
|
||||
|
||||
export type CliEntriesType = [string, (string | number)[]][];
|
||||
export type CliActionType = 'delete' | 'recover';
|
||||
23
packages/module-manager/package.json
Normal file
23
packages/module-manager/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@extension/module-manager",
|
||||
"version": "0.5.0",
|
||||
"description": "chrome extension - module manager",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"sideEffects": true,
|
||||
"scripts": {
|
||||
"start": "tsx index.mts",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"format": "prettier . --write --ignore-path ../../.prettierignore"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@extension/dev-utils": "workspace:*",
|
||||
"@extension/shared": "workspace:*",
|
||||
"@extension/tsconfig": "workspace:*",
|
||||
"@inquirer/prompts": "^7.5.1",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"tsx": "^4.19.4",
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
}
|
||||
8
packages/module-manager/tsconfig.json
Normal file
8
packages/module-manager/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@extension/tsconfig/module",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["lib", "index.mts"]
|
||||
}
|
||||
Reference in New Issue
Block a user