Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions scripts/check-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ const MODULE_SETTINGS = [
{ module: 'commonjs', moduleResolution: 'node10' },
];

const CONSUMER_SOURCE = `import { DEFAULT_PROFILES, Npkill } from 'npkill';
import type { ScanFoundTarget } from 'npkill';
const CONSUMER_SOURCE = `import { Npkill } from 'npkill';
import type { ScanFoundTarget, TargetRule } from 'npkill';

const rust: TargetRule = { target: 'target', nextTo: ['Cargo.toml'] };
const paths: string[] = [];
new Npkill()
.startScan$('.', { targets: DEFAULT_PROFILES.node.targets })
.startScan$('.', { targets: ['node_modules', rust] })
.subscribe((target: ScanFoundTarget) => paths.push(target.path));
`;

Expand Down
13 changes: 9 additions & 4 deletions src/cli/cli.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { Npkill, ConfigService, ProfilesService } from '../core/index.js';
import { LoggerService } from '../core/services/logger.service.js';
import { ScanStatus } from '../core/interfaces/search-status.model.js';
import { isSafeToDelete } from '../utils/is-safe-to-delete.js';
import { targetNames } from '../core/services/targets/index.js';
import { getFileContent } from '../utils/get-file-content.js';
import { isOlderThan } from '../utils/is-older-than.js';
import { ResultDetailsUi } from './ui/components/result-details.ui.js';
Expand Down Expand Up @@ -403,7 +404,9 @@ export class CliController {
const isDefault = defaultProfiles.includes(name);
const entry =
` ${pc.green(name)}${isDefault ? pc.italic(pc.magenta(' (default)')) : ''} ${pc.cyan('(user-defined)')} - ${profile.description}\n` +
pc.gray(` ${profile.targets.join(pc.italic(','))}\n\n`);
pc.gray(
` ${targetNames(profile.targets).join(pc.italic(','))}\n\n`,
);
return acc + entry;
},
'',
Expand All @@ -415,7 +418,9 @@ export class CliController {
const isDefault = defaultProfiles.includes(name);
const entry =
` ${pc.green(name)}${isDefault ? pc.italic(pc.magenta(' (default)')) : ''} - ${profile.description}\n` +
pc.gray(` ${profile.targets.join(pc.italic(','))}\n\n`);
pc.gray(
` ${targetNames(profile.targets).join(pc.italic(','))}\n\n`,
);
return acc + entry;
},
'',
Expand All @@ -430,7 +435,7 @@ export class CliController {
// TODO mejorar mensaje e incluir tip buscar lista targets de un profile.
console.log('--delete-all only can be used with --targets.');
console.log(
'You can copy all targets from a profile with `npkill --profiles`.',
'To use profiles, run npkill without --delete-all and pick the results.',
);
this.exitWithError();
}
Expand Down Expand Up @@ -520,7 +525,7 @@ export class CliController {
const targets =
this.profilesService.getTargetsFromProfiles(selectedProfiles);
this.logger.info(
`Using profiles ${selectedProfiles.join(', ')} | With targets ${targets.join(', ')}`,
`Using profiles ${selectedProfiles.join(', ')} | With targets ${targetNames(targets).join(', ')}`,
);
this.config.profiles = selectedProfiles;
this.config.targets = targets;
Expand Down
4 changes: 3 additions & 1 deletion src/cli/interfaces/config.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TargetDefinition } from '../../core/interfaces/target.interface.js';

export interface IConfig {
profiles: string[];
folderRoot: string;
Expand All @@ -7,7 +9,7 @@ export interface IConfig {
maxSimultaneousSearch: number;
showErrors: boolean;
sortBy: string;
targets: string[];
targets: TargetDefinition[];
exclude: string[];
excludeSensitiveResults: boolean;
disableDefaultIgnore: boolean;
Expand Down
21 changes: 18 additions & 3 deletions src/cli/ui/components/options.ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import path from 'path';
import { existsSync } from 'fs';
import { IConfig } from '../../../cli/interfaces/config.interface.js';
import { OPTIONS_HINTS_BY_TYPE } from '../../../constants/options.constants.js';
import {
targetName,
targetNames,
} from '../../../core/services/targets/index.js';

type OptionType = 'checkbox' | 'dropdown' | 'input';

Expand Down Expand Up @@ -101,7 +105,7 @@ export class OptionsUi extends BaseUi implements InteractiveUi {
type: 'input',
key: 'targets',
value: Array.isArray(this.config.targets)
? this.config.targets.join(',')
? targetNames(this.config.targets).join(',')
: '',
},
];
Expand Down Expand Up @@ -175,8 +179,19 @@ export class OptionsUi extends BaseUi implements InteractiveUi {
.split(',')
.map((s) => s.trim())
.filter(Boolean);
this.config[opt.key] = arrValue;
this.emitConfigChange(opt.key, arrValue);
if (opt.key === 'targets') {
const targets = arrValue.flatMap((name) => {
const existing = this.config.targets.filter(
(target) => targetName(target) === name,
);
return existing.length ? existing : [name];
});
this.config.targets = targets;
this.emitConfigChange('targets', targets);
} else {
this.config.exclude = arrValue;
this.emitConfigChange('exclude', arrValue);
}
opt.value = this.editBuffer;
} else {
const key = opt.key as keyof Pick<
Expand Down
3 changes: 2 additions & 1 deletion src/cli/ui/components/results.ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import pc from 'picocolors';
import { resolve } from 'node:path';
import { CliScanFoundTarget } from '../../../cli/interfaces/stats.interface.js';
import { formatSize } from '../../../utils/unit-conversions.js';
import { targetNames } from '../../../core/services/targets/index.js';
import {
ResultColumnId,
ResultColumnLayout,
Expand Down Expand Up @@ -409,7 +410,7 @@ export class ResultsUi extends HeavyUi implements InteractiveUi {

private noResults(): void {
const targetColored: string = pc.yellowBright(
this.config.targets.join(', '),
targetNames(this.config.targets).join(', '),
);
const message = `No ${targetColored} found!`;
this.printAt(message, {
Expand Down
183 changes: 78 additions & 105 deletions src/core/constants/profiles.constants.ts
Original file line number Diff line number Diff line change
@@ -1,147 +1,120 @@
/* eslint-disable quotes */
import { Profile } from '../interfaces/profile.interface.js';

export const DEFAULT_PROFILE = 'node';

export const BASE_PROFILES: { [profileName: string]: Profile } = {
node: {
description:
'All the usual suspects related with the node/web/javascript dev toolchain: node_modules, caches, build artifacts, and assorted JavaScript junk. Safe to clean and your disk will thank you.',
description: 'Node.js dependencies and generated artifacts.',
targets: [
'node_modules',
'.npm',
'.pnpm-store',
'.next',
'.nuxt',
'.angular',
'.svelte-kit',
'.vite',
'.nx',
'.turbo',
'.parcel-cache',
'.rpt2_cache',
'.eslintcache',
'.esbuild',
'.cache',
'.rollup.cache',
'storybook-static',
'coverage',
'.nyc_output',
'.jest',
'gatsby_cache',
'.docusaurus',
'.swc',
'.stylelintcache',
'deno_cache',
{ target: '.next', nextTo: ['package.json'] },
{ target: '.nuxt', nextTo: ['package.json'] },
{ target: '.svelte-kit', nextTo: ['package.json'] },
{ target: '.vite', contains: ['deps'] },
{ target: '.turbo', nextTo: ['turbo.json'] },
{ target: '.turbo', nextTo: ['turbo.jsonc'] },
{ target: '.parcel-cache', nextTo: ['package.json'] },
{
target: 'storybook-static',
nextTo: ['package.json', '.storybook'],
contains: ['index.html', 'iframe.html'],
},
{
target: '.docusaurus',
nextTo: ['package.json', 'docusaurus.config.js'],
},
{
target: '.docusaurus',
nextTo: ['package.json', 'docusaurus.config.ts'],
},
{
target: '.docusaurus',
nextTo: ['package.json', 'docusaurus.config.mjs'],
},
],
},
python: {
description:
"The usual Python leftovers — caches, virtual environments, and test artifacts. Safe to clear once you've closed your IDE and virtualenvs.",
description: 'Python caches and virtual environments.',
targets: [
'__pycache__',
'.pytest_cache',
'.mypy_cache',
'.ruff_cache',
'.tox',
'.nox',
'.pytype',
'.pyre',
'htmlcov',
'.venv',
'venv',
{ target: '.pytest_cache', contains: ['CACHEDIR.TAG'] },
{ target: '.mypy_cache', contains: ['CACHEDIR.TAG'] },
{ target: '.ruff_cache', contains: ['CACHEDIR.TAG'] },
{ target: '.tox', nextTo: ['tox.ini'] },
{ target: '.tox', nextTo: ['pyproject.toml'] },
{ target: '.nox', nextTo: ['noxfile.py'] },
{ target: '.venv', contains: ['pyvenv.cfg'] },
{ target: 'venv', contains: ['pyvenv.cfg'] },
],
},
'data-science': {
description:
'Jupyter checkpoints, virtualenvs, MLflow runs, and experiment outputs. Great for learning, terrible for disk space.',
java: {
description: 'Maven build outputs and project-local Gradle caches.',
targets: [
'.ipynb_checkpoints',
'__pycache__',
'.venv',
'venv',
'outputs',
'.dvc',
'.mlruns',
{ target: 'target', nextTo: ['pom.xml'] },
{ target: '.gradle', nextTo: ['settings.gradle'] },
{ target: '.gradle', nextTo: ['settings.gradle.kts'] },
{ target: '.gradle', nextTo: ['build.gradle'] },
{ target: '.gradle', nextTo: ['build.gradle.kts'] },
],
},
java: {
description: 'Build outputs and Gradle junk.',
targets: ['target', '.gradle', 'out'],
},
android: {
description:
"Native build caches and intermediate files from Android Studio. Deleting won't hurt, but expect a rebuild marathon next time.",
targets: ['.cxx', 'externalNativeBuild'],
},
swift: {
description:
"Xcode's playground leftovers and Swift package builds. Heavy, harmless, and happy to go.",
targets: ['DerivedData', '.swiftpm'],
description: 'Android native build intermediates.',
targets: [
{ target: '.cxx', nextTo: ['build.gradle'] },
{ target: '.cxx', nextTo: ['build.gradle.kts'] },
],
},
dotnet: {
description:
"Compilation artifacts and Visual Studio cache folders. Disposable once you're done building or testing.",
targets: ['obj', 'TestResults', '.vs'],
description: 'NuGet-backed .NET build intermediates.',
targets: [{ target: 'obj', contains: ['project.assets.json'] }],
},
rust: {
description:
'Cargo build targets. Huge, regenerable, and surprisingly clingy, your disk will appreciate the reset.',
targets: ['target'],
},
ruby: {
description: 'Bundler caches and dependency leftovers.',
targets: ['.bundle'],
description: 'Cargo build outputs in the workspace root.',
targets: [{ target: 'target', nextTo: ['Cargo.toml'] }],
},
elixir: {
description:
'Mix build folders, dependencies, and coverage reports. Easy to regenerate, safe to purge.',
targets: ['_build', 'deps', 'cover'],
description: 'Mix build outputs and dependencies.',
targets: [
{ target: '_build', nextTo: ['mix.exs'] },
{ target: 'deps', nextTo: ['mix.exs'] },
],
},
haskell: {
description:
"GHC and Stack build outputs. A collection of intermediate binaries you definitely don't need anymore.",
targets: ['dist-newstyle', '.stack-work'],
description: 'Cabal and Stack build artifacts.',
targets: [
{ target: 'dist-newstyle', nextTo: ['cabal.project'] },
{ target: '.stack-work', nextTo: ['stack.yaml'] },
],
},
scala: {
description: 'Bloop, Metals, and build outputs from Scala projects.',
targets: ['.bloop', '.metals', 'target'],
description: 'sbt build outputs.',
targets: [{ target: 'target', nextTo: ['build.sbt'] }],
},
cpp: {
description:
'CMake build directories and temporary artifacts. Rebuilds take time, but space is priceless.',
targets: ['CMakeFiles', 'cmake-build-debug', 'cmake-build-release'],
description: 'CMake intermediates, keeping CMakeCache.txt intact.',
targets: [{ target: 'CMakeFiles', nextTo: ['CMakeCache.txt'] }],
},
unity: {
description:
"Unity's cache and build artifacts. Expect longer load times next launch but it can save tons of space on unused projects.",
targets: ['Library', 'Temp', 'Obj'],
},
unreal: {
description:
'Intermediate and binary build caches. Safe to clean. Unreal will (happily?) recompile.',
targets: ['Intermediate', 'DerivedDataCache', 'Binaries'],
'Unity asset cache. Close the editor first and expect a full reimport.',
targets: [{ target: 'Library', nextTo: ['Assets', 'ProjectSettings'] }],
},
godot: {
description:
'Editor caches and import data. Godot can recreate these in a blink.',
targets: ['.import', '.godot'],
},
infra: {
description:
'Leftovers from deployment tools like Serverless, Vercel, Netlify, and Terraform.',
'Godot import caches and local editor state. Close the editor first.',
targets: [
'.serverless',
'.vercel',
'.netlify',
'.terraform',
'.sass-cache',
'.cpcache',
'elm_stuff',
'nimcache',
'deno_cache',
{ target: '.import', nextTo: ['project.godot'] },
{ target: '.godot', nextTo: ['project.godot'] },
],
},
clojure: {
description: 'Clojure CLI classpath caches.',
targets: [{ target: '.cpcache', nextTo: ['deps.edn'] }],
},
elm: {
description: 'Elm build artifacts.',
targets: [{ target: 'elm-stuff', nextTo: ['elm.json'] }],
},
};

const ALL_TARGETS = [
Expand All @@ -156,7 +129,7 @@ export const DEFAULT_PROFILES: Readonly<Record<string, Profile>> =
all: {
targets: ALL_TARGETS,
description:
'Includes all targets listed above. Not recommended, as it mixes unrelated ecosystems and may remove context-specific data (a good recipe for chaos if used recklessly).',
'All the profiles above together. Review results before deleting.',
},
});

Expand Down
3 changes: 2 additions & 1 deletion src/core/interfaces/npkill.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export interface NpkillInterface {
* `options.useDefaultIgnore` is false. Names are compared exactly, case
* included.
* @param rootPath Root directory to scan from.
* @param options Scan options. `targets` must be a non-empty array of names.
* @param options Scan options. `targets` must be a non-empty array of names
* or rules.
* Names with `* ? [ ] { } / \` are rejected because those characters are
* reserved for patterns.
* @returns Observable that emits the results that are found in real time.
Expand Down
6 changes: 4 additions & 2 deletions src/core/interfaces/profile.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { TargetDefinition } from './target.interface.js';

/**
* Represents a profile with target directories and description.
*/
export interface Profile {
/** Array of directory names to search for */
readonly targets: readonly string[];
/** Directory names or rules to search for. */
readonly targets: readonly TargetDefinition[];
/** Description of what this profile is for */
readonly description: string;
}
Loading
Loading