@amanpareek/depkit 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -2
- package/bin/cli.js +2 -0
- package/package.json +42 -4
- package/src/commands/add.js +68 -0
- package/src/commands/create.js +51 -0
- package/src/commands/delete.js +21 -0
- package/src/commands/list.js +22 -0
- package/src/commands/remove.js +31 -0
- package/src/commands/save.js +33 -0
- package/src/commands/show.js +24 -0
- package/src/commands/update.js +73 -0
- package/src/commands/use.js +67 -0
- package/src/config.js +10 -0
- package/src/core/npm.js +54 -0
- package/src/core/preset.js +73 -0
- package/src/core/project.js +23 -0
- package/src/core/registry.js +52 -0
- package/src/core/store.js +46 -0
- package/src/index.js +22 -0
- package/src/utils/args.js +25 -0
- package/src/utils/logger.js +6 -0
- package/src/utils/prompt.js +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aman Pareek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,84 @@
|
|
|
1
|
-
#
|
|
1
|
+
# depkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@amanpareek/depkit)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
**Stop retyping the same `npm install` in every project.**
|
|
7
|
+
|
|
8
|
+
Save your dependencies once as a named preset. Reinstall them anywhere with one command.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Before
|
|
12
|
+
npm i express mongoose dotenv jsonwebtoken bcrypt cors
|
|
13
|
+
npm i -D nodemon
|
|
14
|
+
|
|
15
|
+
# After
|
|
16
|
+
dk use mern-backend
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
depkit is not a project generator or boilerplate tool. It only saves and installs lists of npm packages.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i -g @amanpareek/depkit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `depkit` or the short `dk`. Requires Node.js 18+ and npm.
|
|
28
|
+
|
|
29
|
+
## 30-second tour
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dk save mern-backend # snapshot this project's dependencies
|
|
33
|
+
dk create react-ui react react-dom -D prettier eslint # or create a custom preset
|
|
34
|
+
dk use mern-backend # install all its packages in any future project
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
| Command | What it does |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `dk save <name>` | Save the current project's dependencies as a preset |
|
|
42
|
+
| `dk create <name> [packages...] [-D devPackages...]` | Create a preset from package names |
|
|
43
|
+
| `dk add <preset> [packages...] [-D devPackages...]` | Add new packages to a preset |
|
|
44
|
+
| `dk remove <preset> <packages...>` | Remove packages from a preset |
|
|
45
|
+
| `dk update <preset> <packages...>` | Set packages to latest, or to `name@version` |
|
|
46
|
+
| `dk update <preset> --all` | Set every package to latest |
|
|
47
|
+
| `dk use <name>` | Install a preset into the current project |
|
|
48
|
+
| `dk list` / `dk show <name>` | See your presets and what is inside |
|
|
49
|
+
| `dk delete <name>` | Delete a preset |
|
|
50
|
+
|
|
51
|
+
Add `--dry-run` to `use` and `update` to preview. Add `--force` to `save`, `create` and `delete` to skip the confirmation.
|
|
52
|
+
|
|
53
|
+
## Good to know
|
|
54
|
+
|
|
55
|
+
**Dev dependencies.** Everything after `-D` is a dev dependency. Write normal packages before it.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
dk add api express zod # both normal
|
|
59
|
+
dk add api express -D nodemon # express normal, nodemon dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Versions.** No version means `latest`, resolved when you run `dk use`, so old presets never go stale. Add a version when your code needs one: `express@4`, `zod@3.22.0`. Versions are checked against npm before saving, so a typo never gets in.
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
No version of pdf-lib matches "1.17.2". Latest is 1.17.1.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Nothing changes by accident.** `add` never overwrites a package that is already in the preset, and `update` only touches the packages you name (or everything, with `--all`).
|
|
69
|
+
|
|
70
|
+
**Windows CMD.** `^` is an escape character there, so quote it: `dk add api "pdf-lib@^1.17.1"`. PowerShell does not need this.
|
|
71
|
+
|
|
72
|
+
**Where presets live.** One JSON file: `~/.depkit/presets.json` (`C:\Users\<you>\.depkit` on Windows). Set `DEPKIT_HOME` to change the folder.
|
|
73
|
+
|
|
74
|
+
## Coming next
|
|
75
|
+
|
|
76
|
+
Export and import presets as shareable files.
|
|
77
|
+
|
|
78
|
+
## Contributing
|
|
79
|
+
|
|
80
|
+
Issues and pull requests are welcome at [github.com/amanp30/depkit](https://github.com/amanp30/depkit).
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT © [Aman Pareek](https://github.com/amanp30)
|
package/bin/cli.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amanpareek/depkit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Save your project's npm dependencies as named presets and reinstall them in any project with one command.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"depkit": "./bin/cli.js",
|
|
8
|
+
"dk": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"npm",
|
|
16
|
+
"cli",
|
|
17
|
+
"dependencies",
|
|
18
|
+
"presets",
|
|
19
|
+
"dependency-manager",
|
|
20
|
+
"nodejs"
|
|
21
|
+
],
|
|
22
|
+
"author": "Aman Pareek <https://github.com/amanp30>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/amanp30/depkit.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/amanp30/depkit/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/amanp30/depkit#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "^12.1.0",
|
|
40
|
+
"prompts": "^2.4.2",
|
|
41
|
+
"semver": "^7.8.5",
|
|
42
|
+
"validate-npm-package-name": "^5.0.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { findIn, parseSpec } = require("../core/preset");
|
|
3
|
+
const { resolveSpec } = require("../core/registry");
|
|
4
|
+
const { splitPackages } = require("../utils/args");
|
|
5
|
+
const log = require("../utils/logger");
|
|
6
|
+
|
|
7
|
+
const EXAMPLE = "dk add api zod -D nodemon";
|
|
8
|
+
|
|
9
|
+
function register(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("add <preset> [packages...]")
|
|
12
|
+
.description(
|
|
13
|
+
"Add new packages to a preset. Everything after -D is a dev dependency",
|
|
14
|
+
)
|
|
15
|
+
.option(
|
|
16
|
+
"-D, --dev [packages...]",
|
|
17
|
+
"packages written after -D go to devDependencies",
|
|
18
|
+
)
|
|
19
|
+
.action(async (presetName, packages, opts) => {
|
|
20
|
+
const preset = store.getPreset(presetName);
|
|
21
|
+
if (!preset) throw new Error(`Preset "${presetName}" not found.`);
|
|
22
|
+
|
|
23
|
+
const { normal, dev } = splitPackages(packages, opts.dev, EXAMPLE);
|
|
24
|
+
|
|
25
|
+
// jo package pehle se preset me hai use jodna nahi, update ka rasta dikhana hai
|
|
26
|
+
const skipped = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const onlyNew = (list) =>
|
|
29
|
+
list.filter((spec) => {
|
|
30
|
+
const { name } = parseSpec(spec);
|
|
31
|
+
if (seen.has(name)) return false; // ek command me dobara likha naam
|
|
32
|
+
seen.add(name);
|
|
33
|
+
const key = findIn(preset, name);
|
|
34
|
+
if (!key) return true;
|
|
35
|
+
skipped.push({ name, current: preset[key][name] });
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
const normalNew = onlyNew(normal);
|
|
39
|
+
const devNew = onlyNew(dev);
|
|
40
|
+
|
|
41
|
+
for (const s of skipped) {
|
|
42
|
+
log.warn(
|
|
43
|
+
`"${s.name}" is already in "${presetName}" (${s.current}). ` +
|
|
44
|
+
`To change it: dk update ${presetName} ${s.name}@<version> (or just ${s.name} for latest)`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (normalNew.length + devNew.length === 0) return;
|
|
48
|
+
|
|
49
|
+
const [normalResolved, devResolved] = await Promise.all([
|
|
50
|
+
Promise.all(normalNew.map(resolveSpec)),
|
|
51
|
+
Promise.all(devNew.map(resolveSpec)),
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
for (const { name, version } of normalResolved) {
|
|
55
|
+
preset.dependencies[name] = version;
|
|
56
|
+
log.success(`Added ${name}@${version}`);
|
|
57
|
+
}
|
|
58
|
+
for (const { name, version } of devResolved) {
|
|
59
|
+
preset.devDependencies[name] = version;
|
|
60
|
+
log.success(`Added ${name}@${version} (dev)`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
preset.updatedAt = new Date().toISOString();
|
|
64
|
+
store.setPreset(presetName, preset);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = register;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { assertPresetName } = require("../core/preset");
|
|
3
|
+
const { resolveSpec } = require("../core/registry");
|
|
4
|
+
const { splitPackages } = require("../utils/args");
|
|
5
|
+
const { confirm } = require("../utils/prompt");
|
|
6
|
+
const log = require("../utils/logger");
|
|
7
|
+
|
|
8
|
+
const EXAMPLE = "dk create api express -D nodemon";
|
|
9
|
+
|
|
10
|
+
async function toMap(specs) {
|
|
11
|
+
const resolved = await Promise.all(specs.map(resolveSpec));
|
|
12
|
+
return Object.fromEntries(resolved.map((r) => [r.name, r.version]));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function register(program) {
|
|
16
|
+
program
|
|
17
|
+
.command("create <name> [packages...]")
|
|
18
|
+
.description(
|
|
19
|
+
"Create a preset from package names. Everything after -D is a dev dependency",
|
|
20
|
+
)
|
|
21
|
+
.option(
|
|
22
|
+
"-D, --dev [packages...]",
|
|
23
|
+
"packages written after -D go to devDependencies",
|
|
24
|
+
)
|
|
25
|
+
.option("-f, --force", "overwrite without asking")
|
|
26
|
+
.action(async (name, packages, opts) => {
|
|
27
|
+
assertPresetName(name);
|
|
28
|
+
const { normal, dev } = splitPackages(packages, opts.dev, EXAMPLE);
|
|
29
|
+
|
|
30
|
+
if (store.getPreset(name) && !opts.force) {
|
|
31
|
+
if (!(await confirm(`Preset "${name}" already exists. Overwrite?`))) {
|
|
32
|
+
return log.info("Cancelled.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [dependencies, devDependencies] = await Promise.all([
|
|
37
|
+
toMap(normal),
|
|
38
|
+
toMap(dev),
|
|
39
|
+
]);
|
|
40
|
+
store.setPreset(name, {
|
|
41
|
+
dependencies,
|
|
42
|
+
devDependencies,
|
|
43
|
+
updatedAt: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
log.success(
|
|
46
|
+
`Created "${name}" (${Object.keys(dependencies).length} deps, ${Object.keys(devDependencies).length} dev).`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = register;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { confirm } = require("../utils/prompt");
|
|
3
|
+
const log = require("../utils/logger");
|
|
4
|
+
|
|
5
|
+
function register(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("delete <name>")
|
|
8
|
+
.description("Delete a preset")
|
|
9
|
+
.option("-f, --force", "delete without asking")
|
|
10
|
+
.action(async (name, opts) => {
|
|
11
|
+
if (!store.getPreset(name))
|
|
12
|
+
throw new Error(`Preset "${name}" not found.`);
|
|
13
|
+
if (!opts.force && !(await confirm(`Delete preset "${name}"?`))) {
|
|
14
|
+
return log.info("Cancelled.");
|
|
15
|
+
}
|
|
16
|
+
store.deletePreset(name);
|
|
17
|
+
log.success(`Deleted "${name}".`);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = register;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const log = require("../utils/logger");
|
|
3
|
+
|
|
4
|
+
function register(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("list")
|
|
7
|
+
.description("List saved presets")
|
|
8
|
+
.action(() => {
|
|
9
|
+
const presets = store.listPresets();
|
|
10
|
+
const names = Object.keys(presets);
|
|
11
|
+
if (names.length === 0)
|
|
12
|
+
return log.info("No presets yet. Try: depkit save <name>");
|
|
13
|
+
for (const n of names) {
|
|
14
|
+
const p = presets[n];
|
|
15
|
+
const d = Object.keys(p.dependencies).length;
|
|
16
|
+
const dev = Object.keys(p.devDependencies).length;
|
|
17
|
+
log.info(`${n} (${d} deps, ${dev} dev)`);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = register;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { parseSpec, findIn } = require("../core/preset");
|
|
3
|
+
const log = require("../utils/logger");
|
|
4
|
+
|
|
5
|
+
function register(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("remove <preset> <packages...>")
|
|
8
|
+
.description("Remove packages from a preset")
|
|
9
|
+
.action((presetName, packages) => {
|
|
10
|
+
const preset = store.getPreset(presetName);
|
|
11
|
+
if (!preset) throw new Error(`Preset "${presetName}" not found.`);
|
|
12
|
+
|
|
13
|
+
let removed = 0;
|
|
14
|
+
for (const pkg of packages) {
|
|
15
|
+
const { name } = parseSpec(pkg);
|
|
16
|
+
const key = findIn(preset, name);
|
|
17
|
+
if (!key) {
|
|
18
|
+
log.warn(`"${name}" is not in "${presetName}".`);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
delete preset[key][name];
|
|
22
|
+
removed++;
|
|
23
|
+
log.success(`Removed ${name}`);
|
|
24
|
+
}
|
|
25
|
+
if (removed === 0) return;
|
|
26
|
+
preset.updatedAt = new Date().toISOString();
|
|
27
|
+
store.setPreset(presetName, preset);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = register;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { readProjectDeps } = require("../core/project");
|
|
2
|
+
const store = require("../core/store");
|
|
3
|
+
const { assertPresetName, buildPreset } = require("../core/preset");
|
|
4
|
+
const { confirm } = require("../utils/prompt");
|
|
5
|
+
const log = require("../utils/logger");
|
|
6
|
+
|
|
7
|
+
function register(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("save <name>")
|
|
10
|
+
.description("Save this project's dependencies as a preset")
|
|
11
|
+
.option("-f, --force", "overwrite without asking")
|
|
12
|
+
.action(async (name, opts) => {
|
|
13
|
+
assertPresetName(name);
|
|
14
|
+
const { preset, skipped } = buildPreset(readProjectDeps());
|
|
15
|
+
const d = Object.keys(preset.dependencies).length;
|
|
16
|
+
const dev = Object.keys(preset.devDependencies).length;
|
|
17
|
+
if (d + dev === 0)
|
|
18
|
+
throw new Error("This project has no dependencies to save.");
|
|
19
|
+
|
|
20
|
+
if (store.getPreset(name) && !opts.force) {
|
|
21
|
+
if (!(await confirm(`Preset "${name}" already exists. Overwrite?`))) {
|
|
22
|
+
return log.info("Cancelled.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
store.setPreset(name, preset);
|
|
26
|
+
skipped.forEach((s) =>
|
|
27
|
+
log.warn(`Skipped (not a registry version): ${s}`),
|
|
28
|
+
);
|
|
29
|
+
log.success(`Saved "${name}" (${d} dependencies, ${dev} dev).`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = register;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const log = require("../utils/logger");
|
|
3
|
+
|
|
4
|
+
function print(title, map) {
|
|
5
|
+
const entries = Object.entries(map);
|
|
6
|
+
if (entries.length === 0) return;
|
|
7
|
+
log.info(`\n${title}`);
|
|
8
|
+
entries.forEach(([n, v]) => log.info(` ${n} ${v}`));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function register(program) {
|
|
12
|
+
program
|
|
13
|
+
.command("show <name>")
|
|
14
|
+
.description("Show what a preset contains")
|
|
15
|
+
.action((name) => {
|
|
16
|
+
const p = store.getPreset(name);
|
|
17
|
+
if (!p) throw new Error(`Preset "${name}" not found.`);
|
|
18
|
+
log.info(name);
|
|
19
|
+
print("Dependencies", p.dependencies);
|
|
20
|
+
print("Dev dependencies", p.devDependencies);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = register;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { findIn, parseSpec, isRegistryVersion } = require("../core/preset");
|
|
3
|
+
const { assertVersionExists } = require("../core/registry");
|
|
4
|
+
const log = require("../utils/logger");
|
|
5
|
+
|
|
6
|
+
function register(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("update <preset> [packages...]")
|
|
9
|
+
.description(
|
|
10
|
+
"Set named packages to latest, or to name@version. Use --all for everything",
|
|
11
|
+
)
|
|
12
|
+
.option("--all", "set every package in the preset to latest")
|
|
13
|
+
.option("--dry-run", "show changes without saving")
|
|
14
|
+
.action(async (presetName, packages, opts) => {
|
|
15
|
+
const preset = store.getPreset(presetName);
|
|
16
|
+
if (!preset) throw new Error(`Preset "${presetName}" not found.`);
|
|
17
|
+
|
|
18
|
+
if (opts.all && packages.length > 0) {
|
|
19
|
+
throw new Error("Use either package names or --all, not both.");
|
|
20
|
+
}
|
|
21
|
+
if (!opts.all && packages.length === 0) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Name the package(s) to update, or use --all. Example: dk update ${presetName} express`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const targets = opts.all
|
|
28
|
+
? [
|
|
29
|
+
...Object.keys(preset.dependencies),
|
|
30
|
+
...Object.keys(preset.devDependencies),
|
|
31
|
+
].map((name) => ({ name, version: "latest" }))
|
|
32
|
+
: packages.map((p) => {
|
|
33
|
+
const t = parseSpec(p);
|
|
34
|
+
return { name: t.name, version: t.version || "latest" };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
for (const t of targets) {
|
|
38
|
+
if (!findIn(preset, t.name)) {
|
|
39
|
+
throw new Error(`"${t.name}" is not in preset "${presetName}".`);
|
|
40
|
+
}
|
|
41
|
+
if (t.version !== "latest" && !isRegistryVersion(t.version)) {
|
|
42
|
+
throw new Error(`Invalid version "${t.version}" for ${t.name}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// diye hue versions registry me verify hote hain ("latest" ko check nahi chahiye)
|
|
47
|
+
await Promise.all(
|
|
48
|
+
targets
|
|
49
|
+
.filter((t) => t.version !== "latest")
|
|
50
|
+
.map((t) => assertVersionExists(t.name, t.version)),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let changed = 0;
|
|
54
|
+
for (const { name, version } of targets) {
|
|
55
|
+
const key = findIn(preset, name);
|
|
56
|
+
const old = preset[key][name];
|
|
57
|
+
if (old === version) continue;
|
|
58
|
+
log.info(`${name} ${old} -> ${version}`);
|
|
59
|
+
preset[key][name] = version;
|
|
60
|
+
changed++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (changed === 0) return log.info("Nothing to change.");
|
|
64
|
+
if (opts.dryRun)
|
|
65
|
+
return log.info(`${changed} package(s) would change (dry run).`);
|
|
66
|
+
|
|
67
|
+
preset.updatedAt = new Date().toISOString();
|
|
68
|
+
store.setPreset(presetName, preset);
|
|
69
|
+
log.success(`Updated ${changed} package(s) in "${presetName}".`);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = register;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const store = require("../core/store");
|
|
2
|
+
const { readProjectDeps } = require("../core/project");
|
|
3
|
+
const { toSpecs } = require("../core/preset");
|
|
4
|
+
const npm = require("../core/npm");
|
|
5
|
+
const log = require("../utils/logger");
|
|
6
|
+
|
|
7
|
+
const seconds = (start) => ((Date.now() - start) / 1000).toFixed(1);
|
|
8
|
+
|
|
9
|
+
function register(program) {
|
|
10
|
+
program
|
|
11
|
+
.command("use <name>")
|
|
12
|
+
.description("Install a preset into the current project")
|
|
13
|
+
.option("--dry-run", "show what would be installed")
|
|
14
|
+
.action(async (name, opts) => {
|
|
15
|
+
const preset = store.getPreset(name);
|
|
16
|
+
if (!preset)
|
|
17
|
+
throw new Error(`Preset "${name}" not found. Run "depkit list".`);
|
|
18
|
+
readProjectDeps(); // package.json hona zaroori hai
|
|
19
|
+
|
|
20
|
+
const steps = [
|
|
21
|
+
{
|
|
22
|
+
label: "dependencies",
|
|
23
|
+
specs: toSpecs(preset.dependencies),
|
|
24
|
+
dev: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: "dev dependencies",
|
|
28
|
+
specs: toSpecs(preset.devDependencies),
|
|
29
|
+
dev: true,
|
|
30
|
+
},
|
|
31
|
+
].filter((s) => s.specs.length > 0);
|
|
32
|
+
|
|
33
|
+
if (steps.length === 0) throw new Error(`Preset "${name}" is empty.`);
|
|
34
|
+
|
|
35
|
+
log.info(`${opts.dryRun ? "[dry run] " : ""}Preset "${name}"`);
|
|
36
|
+
const started = Date.now();
|
|
37
|
+
|
|
38
|
+
for (const [i, step] of steps.entries()) {
|
|
39
|
+
const verb = opts.dryRun ? "Would install" : "Installing";
|
|
40
|
+
log.info(
|
|
41
|
+
`\n[${i + 1}/${steps.length}] ${verb} ${step.label} (${step.specs.length})`,
|
|
42
|
+
);
|
|
43
|
+
step.specs.forEach((s) => log.info(` - ${s}`));
|
|
44
|
+
if (opts.dryRun) continue;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await npm.install(step.specs, { dev: step.dev });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Failed while installing ${step.label}: ${err.message}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
log.success(`${step.label} installed`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (opts.dryRun) return;
|
|
57
|
+
const summary = steps
|
|
58
|
+
.map((s) => `${s.specs.length} ${s.label}`)
|
|
59
|
+
.join(", ");
|
|
60
|
+
log.info("");
|
|
61
|
+
log.success(
|
|
62
|
+
`Done. Preset "${name}" installed (${summary}) in ${seconds(started)}s`,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = register;
|
package/src/config.js
ADDED
package/src/core/npm.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
// sirf in characters ki ijazat, taaki kuch ghus na sake
|
|
6
|
+
const SAFE_SPEC = /^[A-Za-z0-9@/._~^*<>=|+\- ]+$/;
|
|
7
|
+
|
|
8
|
+
// Windows par npm.cmd ko bina shell ke chalana allowed nahi,
|
|
9
|
+
// isliye node se seedha npm-cli.js chalate hain (wo node.exe ke saath hi hota hai)
|
|
10
|
+
function findNpmCli() {
|
|
11
|
+
const cli = path.join(
|
|
12
|
+
path.dirname(process.execPath),
|
|
13
|
+
"node_modules",
|
|
14
|
+
"npm",
|
|
15
|
+
"bin",
|
|
16
|
+
"npm-cli.js",
|
|
17
|
+
);
|
|
18
|
+
return fs.existsSync(cli) ? cli : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function run(args, cwd) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let child;
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
const cli = findNpmCli();
|
|
26
|
+
child = cli
|
|
27
|
+
? spawn(process.execPath, [cli, ...args], { cwd, stdio: "inherit" })
|
|
28
|
+
: spawn(
|
|
29
|
+
"npm",
|
|
30
|
+
args.map((a) => `"${a}"`),
|
|
31
|
+
{ cwd, stdio: "inherit", shell: true },
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
child = spawn("npm", args, { cwd, stdio: "inherit" });
|
|
35
|
+
}
|
|
36
|
+
child.on("error", reject);
|
|
37
|
+
child.on("close", (code) =>
|
|
38
|
+
code === 0
|
|
39
|
+
? resolve()
|
|
40
|
+
: reject(new Error(`npm exited with code ${code}`)),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function install(specs, { dev = false, cwd = process.cwd() } = {}) {
|
|
46
|
+
if (specs.length === 0) return Promise.resolve();
|
|
47
|
+
for (const s of specs) {
|
|
48
|
+
if (!SAFE_SPEC.test(s))
|
|
49
|
+
return Promise.reject(new Error(`Unsafe package spec: ${s}`));
|
|
50
|
+
}
|
|
51
|
+
return run(["install", ...specs, ...(dev ? ["--save-dev"] : [])], cwd);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { install };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const validate = require("validate-npm-package-name");
|
|
2
|
+
|
|
3
|
+
const PRESET_NAME = /^[a-z0-9][a-z0-9_-]{0,49}$/i;
|
|
4
|
+
// semver ranges aur tags (latest, next). file:, git+, workspace: wagairah reject.
|
|
5
|
+
const REGISTRY_VERSION = /^[A-Za-z0-9.^~*<>=|+\- ]+$/;
|
|
6
|
+
|
|
7
|
+
function assertPresetName(name) {
|
|
8
|
+
if (!PRESET_NAME.test(name)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
'Preset name must be letters, numbers, "-" or "_" (max 50 chars).',
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function assertPackageName(name) {
|
|
16
|
+
if (name.startsWith("-") || !validate(name).validForOldPackages) {
|
|
17
|
+
throw new Error(`Invalid package name: "${name}"`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isRegistryVersion = (v) =>
|
|
22
|
+
typeof v === "string" && REGISTRY_VERSION.test(v);
|
|
23
|
+
|
|
24
|
+
function clean(map, skipped) {
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [name, version] of Object.entries(map)) {
|
|
27
|
+
if (!isRegistryVersion(version)) {
|
|
28
|
+
skipped.push(`${name}@${version}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
assertPackageName(name);
|
|
32
|
+
out[name] = version;
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildPreset({ dependencies = {}, devDependencies = {} }) {
|
|
38
|
+
const skipped = [];
|
|
39
|
+
const preset = {
|
|
40
|
+
dependencies: clean(dependencies, skipped),
|
|
41
|
+
devDependencies: clean(devDependencies, skipped),
|
|
42
|
+
updatedAt: new Date().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
return { preset, skipped };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const toSpecs = (map) => Object.entries(map).map(([n, v]) => `${n}@${v}`);
|
|
48
|
+
|
|
49
|
+
// "react@18" -> { name: 'react', version: '18' }, "@types/node" -> { name: '@types/node', version: '' }
|
|
50
|
+
function parseSpec(spec) {
|
|
51
|
+
const at = spec.lastIndexOf("@");
|
|
52
|
+
if (at > 0) return { name: spec.slice(0, at), version: spec.slice(at + 1) };
|
|
53
|
+
return { name: spec, version: "" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// package kis section me hai: 'dependencies', 'devDependencies' ya null
|
|
57
|
+
function findIn(preset, name) {
|
|
58
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
59
|
+
if (Object.prototype.hasOwnProperty.call(preset[key] || {}, name))
|
|
60
|
+
return key;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
assertPresetName,
|
|
67
|
+
assertPackageName,
|
|
68
|
+
isRegistryVersion,
|
|
69
|
+
buildPreset,
|
|
70
|
+
toSpecs,
|
|
71
|
+
parseSpec,
|
|
72
|
+
findIn,
|
|
73
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function readProjectDeps(dir = process.cwd()) {
|
|
5
|
+
const file = path.join(dir, "package.json");
|
|
6
|
+
if (!fs.existsSync(file)) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
'No package.json found in this folder. Run "npm init -y" first.',
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
let pkg;
|
|
12
|
+
try {
|
|
13
|
+
pkg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("package.json is not valid JSON.");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
dependencies: pkg.dependencies || {},
|
|
19
|
+
devDependencies: pkg.devDependencies || {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { readProjectDeps };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const semver = require('semver');
|
|
2
|
+
const { parseSpec, isRegistryVersion, assertPackageName } = require('./preset');
|
|
3
|
+
|
|
4
|
+
const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
5
|
+
|
|
6
|
+
// Registry se package ki (chhoti) metadata laata hai: dist-tags aur versions ki list
|
|
7
|
+
async function fetchMeta(name) {
|
|
8
|
+
const url = `https://registry.npmjs.org/${name.replace('/', '%2F')}`;
|
|
9
|
+
let res;
|
|
10
|
+
try {
|
|
11
|
+
res = await fetch(url, {
|
|
12
|
+
headers: { Accept: 'application/vnd.npm.install-v1+json' },
|
|
13
|
+
signal: AbortSignal.timeout(10000),
|
|
14
|
+
});
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error(`Could not reach the npm registry for "${name}". Check your internet.`);
|
|
17
|
+
}
|
|
18
|
+
if (res.status === 404) throw new Error(`Package "${name}" not found on npm.`);
|
|
19
|
+
if (!res.ok) throw new Error(`npm registry error ${res.status} for "${name}".`);
|
|
20
|
+
return res.json();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Diya hua version, range ya tag sach me maujood hai ya nahi
|
|
24
|
+
async function assertVersionExists(name, version) {
|
|
25
|
+
const meta = await fetchMeta(name);
|
|
26
|
+
const tags = meta['dist-tags'] || {};
|
|
27
|
+
if (has(tags, version)) return; // jaise "next" ya "beta"
|
|
28
|
+
if (!semver.validRange(version)) {
|
|
29
|
+
throw new Error(`"${version}" is not a valid version, range or tag for ${name}.`);
|
|
30
|
+
}
|
|
31
|
+
if (!semver.maxSatisfying(Object.keys(meta.versions || {}), version)) {
|
|
32
|
+
throw new Error(`No version of ${name} matches "${version}". Latest is ${tags.latest}.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Version nahi diya (ya "latest") -> "latest" save hota hai, install ke waqt resolve hoga.
|
|
37
|
+
// Diya hua version registry me verify hota hai.
|
|
38
|
+
async function resolveSpec(spec) {
|
|
39
|
+
const { name, version } = parseSpec(spec);
|
|
40
|
+
assertPackageName(name);
|
|
41
|
+
if (!version || version === 'latest') {
|
|
42
|
+
await fetchMeta(name); // sirf ye dekhne ke liye ki package maujood hai
|
|
43
|
+
return { name, version: 'latest' };
|
|
44
|
+
}
|
|
45
|
+
if (!isRegistryVersion(version)) {
|
|
46
|
+
throw new Error(`Invalid version "${version}" for ${name}`);
|
|
47
|
+
}
|
|
48
|
+
await assertVersionExists(name, version);
|
|
49
|
+
return { name, version };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { assertVersionExists, resolveSpec };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { DIR, STORE_FILE, SCHEMA_VERSION } = require("../config");
|
|
3
|
+
|
|
4
|
+
const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
5
|
+
|
|
6
|
+
function load() {
|
|
7
|
+
if (!fs.existsSync(STORE_FILE))
|
|
8
|
+
return { schemaVersion: SCHEMA_VERSION, presets: {} };
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(fs.readFileSync(STORE_FILE, "utf8"));
|
|
11
|
+
} catch {
|
|
12
|
+
throw new Error(`Presets file is corrupted: ${STORE_FILE}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function write(data) {
|
|
17
|
+
fs.mkdirSync(DIR, { recursive: true });
|
|
18
|
+
const tmp = `${STORE_FILE}.tmp`;
|
|
19
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
20
|
+
fs.renameSync(tmp, STORE_FILE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getPreset(name) {
|
|
24
|
+
const { presets } = load();
|
|
25
|
+
return has(presets, name) ? presets[name] : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setPreset(name, preset) {
|
|
29
|
+
const data = load();
|
|
30
|
+
data.presets[name] = preset;
|
|
31
|
+
write(data);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function deletePreset(name) {
|
|
35
|
+
const data = load();
|
|
36
|
+
if (!has(data.presets, name)) return false;
|
|
37
|
+
delete data.presets[name];
|
|
38
|
+
write(data);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function listPresets() {
|
|
43
|
+
return load().presets;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { getPreset, setPreset, deletePreset, listPresets };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { Command } = require("commander");
|
|
2
|
+
const pkg = require("../package.json");
|
|
3
|
+
const log = require("./utils/logger");
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program.name("depkit").description(pkg.description).version(pkg.version);
|
|
7
|
+
|
|
8
|
+
require("./commands/save")(program);
|
|
9
|
+
require("./commands/use")(program);
|
|
10
|
+
require("./commands/list")(program);
|
|
11
|
+
require("./commands/show")(program);
|
|
12
|
+
|
|
13
|
+
require("./commands/create")(program);
|
|
14
|
+
require("./commands/add")(program);
|
|
15
|
+
require("./commands/remove")(program);
|
|
16
|
+
require("./commands/update")(program);
|
|
17
|
+
require("./commands/delete")(program);
|
|
18
|
+
|
|
19
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
20
|
+
log.error(err.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { parseSpec } = require('../core/preset');
|
|
2
|
+
|
|
3
|
+
// "-D" se pehle likhe packages normal hain, baad ke saare dev hain.
|
|
4
|
+
function splitPackages(packages, devOption, example) {
|
|
5
|
+
if (devOption === true) {
|
|
6
|
+
throw new Error(`-D needs package names after it. Example: ${example}`);
|
|
7
|
+
}
|
|
8
|
+
const normal = packages;
|
|
9
|
+
const dev = Array.isArray(devOption) ? devOption : [];
|
|
10
|
+
|
|
11
|
+
if (normal.length + dev.length === 0) {
|
|
12
|
+
throw new Error(`Give at least one package. Example: ${example}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const normalNames = new Set(normal.map((p) => parseSpec(p).name));
|
|
16
|
+
for (const d of dev) {
|
|
17
|
+
const { name } = parseSpec(d);
|
|
18
|
+
if (normalNames.has(name)) {
|
|
19
|
+
throw new Error(`"${name}" is listed both before and after -D.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { normal, dev };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { splitPackages };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const prompts = require("prompts");
|
|
2
|
+
|
|
3
|
+
async function confirm(message) {
|
|
4
|
+
if (!process.stdin.isTTY) return false;
|
|
5
|
+
const { ok } = await prompts({
|
|
6
|
+
type: "confirm",
|
|
7
|
+
name: "ok",
|
|
8
|
+
message,
|
|
9
|
+
initial: false,
|
|
10
|
+
});
|
|
11
|
+
return Boolean(ok);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { confirm };
|