deplift 1.1.0 → 1.2.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.
@@ -5,16 +5,14 @@ var path = require('node:path');
5
5
  var promises = require('node:fs/promises');
6
6
  var node_child_process = require('node:child_process');
7
7
  var fg = require('fast-glob');
8
+ var yargs = require('yargs');
9
+ var helpers = require('yargs/helpers');
8
10
 
9
- const defaultIgnore = ["**/node_modules/**", "**/dist/**", "**/coverage/**", "**/build/**", "**/.next/**", "**/.docusaurus/**"];
10
- const depSections = ["dependencies", "devDependencies"];
11
- const args = process.argv.slice(2);
12
- const dryRun = args.includes("--dry-run");
13
- const noInstall = args.includes("--no-install");
14
- if (dryRun) console.log("💡 Dry run enabled — no files will be changed or installed.");
15
- const stripPrefix = version => version.replace(/^[^0-9]*/, "");
11
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/build/**', '**/.next/**', '**/.docusaurus/**'];
12
+ const depSections = ['dependencies', 'devDependencies'];
13
+ const stripPrefix = version => version.replace(/^\D+/, '');
16
14
  const isStableRelease = version => /^\d+\.\d+\.\d+$/.test(version);
17
- const extractSemVerParts = semver => semver.split(".").map(Number);
15
+ const extractSemVerParts = semver => semver.split('.').map(Number);
18
16
  function isSemVerGreater(v1, v2) {
19
17
  const [major1, minor1, patch1] = extractSemVerParts(v1);
20
18
  const [major2, minor2, patch2] = extractSemVerParts(v2);
@@ -23,11 +21,11 @@ function isSemVerGreater(v1, v2) {
23
21
  return patch1 > patch2;
24
22
  }
25
23
  const loadConfig = async () => {
26
- const configPath = path.resolve("deplift.config.json");
24
+ const configPath = path.resolve('deplift.config.json');
27
25
  try {
28
- const raw = await promises.readFile(configPath, "utf-8");
26
+ const raw = await promises.readFile(configPath, 'utf-8');
29
27
  const parsed = JSON.parse(raw);
30
- if (parsed && typeof parsed === "object") {
28
+ if (parsed && typeof parsed === 'object') {
31
29
  return parsed;
32
30
  }
33
31
  console.warn(`⚠️ Config file exists but is not a valid object: ${configPath}`);
@@ -55,19 +53,54 @@ const fetchLatestVersion = async dep => {
55
53
  }
56
54
  return dep;
57
55
  };
56
+ const parseArgs = async () => {
57
+ const argv = await yargs(helpers.hideBin(process.argv)).option('major', {
58
+ type: 'array',
59
+ describe: 'major dep=version pairs',
60
+ default: [],
61
+ coerce: pairs => {
62
+ const result = {};
63
+ for (const pair of pairs) {
64
+ const idx = pair.indexOf('=');
65
+ if (idx === -1) {
66
+ throw new Error(`Invalid --major value "${pair}", expected dep=version`);
67
+ }
68
+ const key = pair.slice(0, idx);
69
+ const value = pair.slice(idx + 1);
70
+ result[key] = Number(value);
71
+ }
72
+ return result;
73
+ }
74
+ }).option('dry-run', {
75
+ type: 'boolean',
76
+ describe: 'Run without making changes',
77
+ default: false
78
+ }).option('install', {
79
+ type: 'boolean',
80
+ describe: 'Run npm install',
81
+ default: true
82
+ }).strict().help().parse();
83
+ return argv;
84
+ };
58
85
  async function main() {
86
+ const {
87
+ dryRun,
88
+ install: runInstall,
89
+ major: majorCaps
90
+ } = await parseArgs();
91
+ if (dryRun) console.log('💡 Dry run enabled — no files will be changed or installed.');
59
92
  const config = await loadConfig();
60
93
  const ignorePatterns = Array.isArray(config.ignore) ? Array.from(new Set([...defaultIgnore, ...config.ignore])) : defaultIgnore;
61
- const packageFiles = await fg.glob("**/package.json", {
94
+ const packageFiles = await fg.glob('**/package.json', {
62
95
  ignore: ignorePatterns
63
96
  });
64
97
  if (packageFiles.length === 0) {
65
- console.log("❌ No package.json files found.");
98
+ console.log('❌ No package.json files found.');
66
99
  process.exit(0);
67
100
  }
68
101
  for (const packageJson of packageFiles) {
69
102
  const packageJsonPath = path.resolve(packageJson);
70
- const pkgRaw = await promises.readFile(packageJsonPath, "utf-8");
103
+ const pkgRaw = await promises.readFile(packageJsonPath, 'utf-8');
71
104
  let pkgData;
72
105
  try {
73
106
  pkgData = JSON.parse(pkgRaw);
@@ -79,7 +112,7 @@ async function main() {
79
112
  const dependencies = depSections.reduce((accu, section) => {
80
113
  const sectionData = pkgData[section];
81
114
  if (!sectionData) return accu;
82
- const entries = Object.entries(sectionData).filter(([_, version]) => !version.startsWith("file:")).map(([pkg, current]) => ({
115
+ const entries = Object.entries(sectionData).filter(([_, version]) => !version.startsWith('file:')).map(([pkg, current]) => ({
83
116
  section,
84
117
  pkg,
85
118
  current
@@ -91,52 +124,54 @@ async function main() {
91
124
  for (const {
92
125
  section,
93
126
  pkg,
94
- current,
127
+ current: rawCurrent,
95
128
  latest
96
129
  } of latestDeps) {
97
130
  // Failed to fetch the pkg
98
131
  if (!latest) continue;
99
- if (!isStableRelease(latest)) {
100
- console.log(` ⚠️ [skipped] ${pkg}: latest version is not a stable release (${latest})`);
132
+ const current = stripPrefix(rawCurrent);
133
+ if (current === latest) {
134
+ console.log(` ${pkg} is already up to date (${latest})`);
101
135
  continue;
102
136
  }
103
- const currentVersion = stripPrefix(current);
104
- if (currentVersion === latest) {
105
- console.log(` ${pkg} is already up to date (${latest})`);
137
+ if (isStableRelease(current) && !isStableRelease(latest)) {
138
+ console.log(` ⚠️ [skipped] ${pkg}: latest version is not a stable release (${latest})`);
106
139
  continue;
107
140
  }
108
- if (isSemVerGreater(currentVersion, latest)) {
109
- console.log(` ⚠️ [skipped] ${pkg}: current (${currentVersion}) version is higher than the latest (${latest})`);
141
+ if (isSemVerGreater(current, latest)) {
142
+ console.log(` ⚠️ [skipped] ${pkg}: current (${current}) version is higher than the latest (${latest})`);
110
143
  continue;
111
144
  }
112
- const [currentMajor] = extractSemVerParts(currentVersion);
113
145
  const [latestMajor] = extractSemVerParts(latest);
114
- console.log(` ${currentMajor === latestMajor ? "✔" : "🚨[major]"} ${pkg}(${section}): ${current} → ^${latest}`);
146
+ if (latestMajor > majorCaps[pkg]) {
147
+ console.log(` ⚠️ [skipped] ${pkg}: ${latest} is available, but the major version is capped at v${majorCaps[pkg]}`);
148
+ continue;
149
+ }
150
+ const [currentMajor] = extractSemVerParts(current);
151
+ console.log(` ${currentMajor === latestMajor ? '✔' : '🚨[major]'} ${pkg}(${section}): ${rawCurrent} → ^${latest}`);
115
152
  updated = true;
116
153
  if (!dryRun) {
117
154
  pkgData[section][pkg] = `^${latest}`;
118
155
  }
119
156
  }
120
157
  if (updated) {
121
- await promises.writeFile(packageJsonPath, JSON.stringify(pkgData, null, 2) + "\n");
122
- console.log(` 💾 ${packageJson} updated.`);
158
+ if (!dryRun) {
159
+ await promises.writeFile(packageJsonPath, JSON.stringify(pkgData, null, 2) + '\n');
160
+ console.log(` 💾 ${packageJson} updated.`);
161
+ }
123
162
  } else {
124
163
  console.log(` ✅ No changes needed for ${packageJson}.`);
125
164
  }
126
- if (noInstall) continue;
127
- if (dryRun) {
128
- console.log(` 📥 [Dry run] "npm install" for ${packageJson}.`);
129
- continue;
130
- }
165
+ if (!runInstall || dryRun || !updated) continue;
131
166
  try {
132
167
  const targetDir = path.dirname(packageJsonPath);
133
- console.log(" 📥 Installing...");
134
- node_child_process.execSync("npm install", {
135
- stdio: "inherit",
168
+ console.log(' 📥 Installing...');
169
+ node_child_process.execSync('npm install', {
170
+ stdio: 'inherit',
136
171
  cwd: targetDir
137
172
  });
138
- node_child_process.execSync("npm audit fix", {
139
- stdio: "inherit",
173
+ node_child_process.execSync('npm audit fix', {
174
+ stdio: 'inherit',
140
175
  cwd: targetDir
141
176
  });
142
177
  } catch (err) {
@@ -144,7 +179,7 @@ async function main() {
144
179
  }
145
180
  }
146
181
  }
147
- main().then(() => console.log("\n[deplift] ✅ All dependency updates completed!")).catch(err => {
148
- console.error("\n[deplift] ❌ Unexpected error:", err);
182
+ main().then(() => console.log('\n[deplift] ✅ All dependency updates completed!')).catch(err => {
183
+ console.error('\n[deplift] ❌ Unexpected error:', err);
149
184
  process.exit(1);
150
185
  });
@@ -3,16 +3,14 @@ import path from 'node:path';
3
3
  import { readFile, writeFile } from 'node:fs/promises';
4
4
  import { execSync } from 'node:child_process';
5
5
  import fg from 'fast-glob';
6
+ import yargs from 'yargs';
7
+ import { hideBin } from 'yargs/helpers';
6
8
 
7
- const defaultIgnore = ["**/node_modules/**", "**/dist/**", "**/coverage/**", "**/build/**", "**/.next/**", "**/.docusaurus/**"];
8
- const depSections = ["dependencies", "devDependencies"];
9
- const args = process.argv.slice(2);
10
- const dryRun = args.includes("--dry-run");
11
- const noInstall = args.includes("--no-install");
12
- if (dryRun) console.log("💡 Dry run enabled — no files will be changed or installed.");
13
- const stripPrefix = version => version.replace(/^[^0-9]*/, "");
9
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**/build/**', '**/.next/**', '**/.docusaurus/**'];
10
+ const depSections = ['dependencies', 'devDependencies'];
11
+ const stripPrefix = version => version.replace(/^\D+/, '');
14
12
  const isStableRelease = version => /^\d+\.\d+\.\d+$/.test(version);
15
- const extractSemVerParts = semver => semver.split(".").map(Number);
13
+ const extractSemVerParts = semver => semver.split('.').map(Number);
16
14
  function isSemVerGreater(v1, v2) {
17
15
  const [major1, minor1, patch1] = extractSemVerParts(v1);
18
16
  const [major2, minor2, patch2] = extractSemVerParts(v2);
@@ -21,11 +19,11 @@ function isSemVerGreater(v1, v2) {
21
19
  return patch1 > patch2;
22
20
  }
23
21
  const loadConfig = async () => {
24
- const configPath = path.resolve("deplift.config.json");
22
+ const configPath = path.resolve('deplift.config.json');
25
23
  try {
26
- const raw = await readFile(configPath, "utf-8");
24
+ const raw = await readFile(configPath, 'utf-8');
27
25
  const parsed = JSON.parse(raw);
28
- if (parsed && typeof parsed === "object") {
26
+ if (parsed && typeof parsed === 'object') {
29
27
  return parsed;
30
28
  }
31
29
  console.warn(`⚠️ Config file exists but is not a valid object: ${configPath}`);
@@ -53,19 +51,54 @@ const fetchLatestVersion = async dep => {
53
51
  }
54
52
  return dep;
55
53
  };
54
+ const parseArgs = async () => {
55
+ const argv = await yargs(hideBin(process.argv)).option('major', {
56
+ type: 'array',
57
+ describe: 'major dep=version pairs',
58
+ default: [],
59
+ coerce: pairs => {
60
+ const result = {};
61
+ for (const pair of pairs) {
62
+ const idx = pair.indexOf('=');
63
+ if (idx === -1) {
64
+ throw new Error(`Invalid --major value "${pair}", expected dep=version`);
65
+ }
66
+ const key = pair.slice(0, idx);
67
+ const value = pair.slice(idx + 1);
68
+ result[key] = Number(value);
69
+ }
70
+ return result;
71
+ }
72
+ }).option('dry-run', {
73
+ type: 'boolean',
74
+ describe: 'Run without making changes',
75
+ default: false
76
+ }).option('install', {
77
+ type: 'boolean',
78
+ describe: 'Run npm install',
79
+ default: true
80
+ }).strict().help().parse();
81
+ return argv;
82
+ };
56
83
  async function main() {
84
+ const {
85
+ dryRun,
86
+ install: runInstall,
87
+ major: majorCaps
88
+ } = await parseArgs();
89
+ if (dryRun) console.log('💡 Dry run enabled — no files will be changed or installed.');
57
90
  const config = await loadConfig();
58
91
  const ignorePatterns = Array.isArray(config.ignore) ? Array.from(new Set([...defaultIgnore, ...config.ignore])) : defaultIgnore;
59
- const packageFiles = await fg.glob("**/package.json", {
92
+ const packageFiles = await fg.glob('**/package.json', {
60
93
  ignore: ignorePatterns
61
94
  });
62
95
  if (packageFiles.length === 0) {
63
- console.log("❌ No package.json files found.");
96
+ console.log('❌ No package.json files found.');
64
97
  process.exit(0);
65
98
  }
66
99
  for (const packageJson of packageFiles) {
67
100
  const packageJsonPath = path.resolve(packageJson);
68
- const pkgRaw = await readFile(packageJsonPath, "utf-8");
101
+ const pkgRaw = await readFile(packageJsonPath, 'utf-8');
69
102
  let pkgData;
70
103
  try {
71
104
  pkgData = JSON.parse(pkgRaw);
@@ -77,7 +110,7 @@ async function main() {
77
110
  const dependencies = depSections.reduce((accu, section) => {
78
111
  const sectionData = pkgData[section];
79
112
  if (!sectionData) return accu;
80
- const entries = Object.entries(sectionData).filter(([_, version]) => !version.startsWith("file:")).map(([pkg, current]) => ({
113
+ const entries = Object.entries(sectionData).filter(([_, version]) => !version.startsWith('file:')).map(([pkg, current]) => ({
81
114
  section,
82
115
  pkg,
83
116
  current
@@ -89,52 +122,54 @@ async function main() {
89
122
  for (const {
90
123
  section,
91
124
  pkg,
92
- current,
125
+ current: rawCurrent,
93
126
  latest
94
127
  } of latestDeps) {
95
128
  // Failed to fetch the pkg
96
129
  if (!latest) continue;
97
- if (!isStableRelease(latest)) {
98
- console.log(` ⚠️ [skipped] ${pkg}: latest version is not a stable release (${latest})`);
130
+ const current = stripPrefix(rawCurrent);
131
+ if (current === latest) {
132
+ console.log(` ${pkg} is already up to date (${latest})`);
99
133
  continue;
100
134
  }
101
- const currentVersion = stripPrefix(current);
102
- if (currentVersion === latest) {
103
- console.log(` ${pkg} is already up to date (${latest})`);
135
+ if (isStableRelease(current) && !isStableRelease(latest)) {
136
+ console.log(` ⚠️ [skipped] ${pkg}: latest version is not a stable release (${latest})`);
104
137
  continue;
105
138
  }
106
- if (isSemVerGreater(currentVersion, latest)) {
107
- console.log(` ⚠️ [skipped] ${pkg}: current (${currentVersion}) version is higher than the latest (${latest})`);
139
+ if (isSemVerGreater(current, latest)) {
140
+ console.log(` ⚠️ [skipped] ${pkg}: current (${current}) version is higher than the latest (${latest})`);
108
141
  continue;
109
142
  }
110
- const [currentMajor] = extractSemVerParts(currentVersion);
111
143
  const [latestMajor] = extractSemVerParts(latest);
112
- console.log(` ${currentMajor === latestMajor ? "✔" : "🚨[major]"} ${pkg}(${section}): ${current} → ^${latest}`);
144
+ if (latestMajor > majorCaps[pkg]) {
145
+ console.log(` ⚠️ [skipped] ${pkg}: ${latest} is available, but the major version is capped at v${majorCaps[pkg]}`);
146
+ continue;
147
+ }
148
+ const [currentMajor] = extractSemVerParts(current);
149
+ console.log(` ${currentMajor === latestMajor ? '✔' : '🚨[major]'} ${pkg}(${section}): ${rawCurrent} → ^${latest}`);
113
150
  updated = true;
114
151
  if (!dryRun) {
115
152
  pkgData[section][pkg] = `^${latest}`;
116
153
  }
117
154
  }
118
155
  if (updated) {
119
- await writeFile(packageJsonPath, JSON.stringify(pkgData, null, 2) + "\n");
120
- console.log(` 💾 ${packageJson} updated.`);
156
+ if (!dryRun) {
157
+ await writeFile(packageJsonPath, JSON.stringify(pkgData, null, 2) + '\n');
158
+ console.log(` 💾 ${packageJson} updated.`);
159
+ }
121
160
  } else {
122
161
  console.log(` ✅ No changes needed for ${packageJson}.`);
123
162
  }
124
- if (noInstall) continue;
125
- if (dryRun) {
126
- console.log(` 📥 [Dry run] "npm install" for ${packageJson}.`);
127
- continue;
128
- }
163
+ if (!runInstall || dryRun || !updated) continue;
129
164
  try {
130
165
  const targetDir = path.dirname(packageJsonPath);
131
- console.log(" 📥 Installing...");
132
- execSync("npm install", {
133
- stdio: "inherit",
166
+ console.log(' 📥 Installing...');
167
+ execSync('npm install', {
168
+ stdio: 'inherit',
134
169
  cwd: targetDir
135
170
  });
136
- execSync("npm audit fix", {
137
- stdio: "inherit",
171
+ execSync('npm audit fix', {
172
+ stdio: 'inherit',
138
173
  cwd: targetDir
139
174
  });
140
175
  } catch (err) {
@@ -142,7 +177,7 @@ async function main() {
142
177
  }
143
178
  }
144
179
  }
145
- main().then(() => console.log("\n[deplift] ✅ All dependency updates completed!")).catch(err => {
146
- console.error("\n[deplift] ❌ Unexpected error:", err);
180
+ main().then(() => console.log('\n[deplift] ✅ All dependency updates completed!')).catch(err => {
181
+ console.error('\n[deplift] ❌ Unexpected error:', err);
147
182
  process.exit(1);
148
183
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deplift",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI to update deps in monorepos",
5
5
  "author": "Zheng Song",
6
6
  "license": "MIT",
@@ -27,23 +27,28 @@
27
27
  "watch": "rollup -c -w",
28
28
  "clean": "rm -Rf dist types",
29
29
  "types": "tsc",
30
+ "pret": "prettier -c .",
31
+ "pret:fix": "prettier -w .",
30
32
  "prepare": "rm -Rf types/__tests__",
31
- "build": "run-s clean types bundle",
33
+ "build": "run-s pret clean types bundle",
32
34
  "deplift": "node ./dist/cjs/index.cjs",
33
35
  "deplift:dry-run": "node ./dist/cjs/index.cjs --dry-run"
34
36
  },
35
37
  "devDependencies": {
36
- "@babel/core": "^7.28.5",
37
- "@babel/preset-env": "^7.28.5",
38
+ "@babel/core": "^7.29.0",
39
+ "@babel/preset-env": "^7.29.0",
38
40
  "@babel/preset-typescript": "^7.28.5",
39
41
  "@rollup/plugin-babel": "^6.1.0",
40
42
  "@rollup/plugin-node-resolve": "^16.0.3",
41
- "@types/node": "^25.0.5",
43
+ "@types/node": "^25.2.2",
44
+ "@types/yargs": "^17.0.35",
42
45
  "npm-run-all": "^4.1.5",
43
- "rollup": "^4.55.1",
46
+ "prettier": "^3.8.1",
47
+ "rollup": "^4.57.1",
44
48
  "typescript": "^5.9.3"
45
49
  },
46
50
  "dependencies": {
47
- "fast-glob": "^3.3.3"
51
+ "fast-glob": "^3.3.3",
52
+ "yargs": "^18.0.0"
48
53
  }
49
54
  }