comze 0.2.0 → 0.3.1

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/README.md CHANGED
@@ -10,6 +10,7 @@ A [taze](https://github.com/antfu/taze)-like CLI for updating `composer.json` de
10
10
  - 🎯 Respects `minimum-stability` and `prefer-stable`
11
11
  - 🚫 Major updates hidden by default
12
12
  - ✍️ Interactive selection mode
13
+ - ⚠️ Detects deprecated packages
13
14
 
14
15
  ## Installation
15
16
 
@@ -50,16 +51,16 @@ comze --exclude vendor/package
50
51
 
51
52
  ## Options
52
53
 
53
- | Flag | Description |
54
- |------|-------------|
55
- | `-w, --write` | Write changes to `composer.json` |
56
- | `-i, --install` | Write changes and run `composer update` |
57
- | `-I, --interactive` | Select updates manually |
58
- | `--major` | Include major updates (default: false) |
59
- | `--minor` | Include minor updates (default: true) |
60
- | `--patch` | Include patch updates (default: true) |
61
- | `--exclude <pkgs>` | Exclude packages (comma-separated) |
62
- | `--dry-run` | Preview changes without writing |
54
+ | Flag | Description |
55
+ | ------------------- | --------------------------------------- |
56
+ | `-w, --write` | Write changes to `composer.json` |
57
+ | `-i, --install` | Write changes and run `composer update` |
58
+ | `-I, --interactive` | Select updates manually |
59
+ | `--major` | Include major updates (default: false) |
60
+ | `--minor` | Include minor updates (default: true) |
61
+ | `--patch` | Include patch updates (default: true) |
62
+ | `--exclude <pkgs>` | Exclude packages (comma-separated) |
63
+ | `--dry-run` | Preview changes without writing |
63
64
 
64
65
  ## Composer Stability
65
66
 
@@ -76,15 +77,15 @@ comze reads `minimum-stability` and `prefer-stable` from your `composer.json`:
76
77
 
77
78
  All Composer version constraints are supported:
78
79
 
79
- | Type | Example |
80
- |------|---------|
81
- | Exact | `1.0.2` |
82
- | Caret | `^1.2.3` |
83
- | Tilde | `~1.2` |
84
- | Wildcard | `1.0.*` |
85
- | Range | `>=1.0 <2.0` |
86
- | Hyphenated | `1.0 - 2.0` |
87
- | Dev | `dev-main`, `1.x-dev` |
80
+ | Type | Example |
81
+ | ---------- | --------------------- |
82
+ | Exact | `1.0.2` |
83
+ | Caret | `^1.2.3` |
84
+ | Tilde | `~1.2` |
85
+ | Wildcard | `1.0.*` |
86
+ | Range | `>=1.0 <2.0` |
87
+ | Hyphenated | `1.0 - 2.0` |
88
+ | Dev | `dev-main`, `1.x-dev` |
88
89
 
89
90
  ## Development
90
91
 
package/bin/comze.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import '../dist/cli.mjs';
2
+ import "../dist/cli.mjs";
package/dist/cli.mjs CHANGED
@@ -7422,11 +7422,23 @@ function parseConstraint(constraint) {
7422
7422
  }
7423
7423
  if (original.startsWith("^")) {
7424
7424
  const version = original.slice(1);
7425
- return { type: "caret", prefix: "^", baseVersion: normalizeVersionString(version), original, isDev: hasDevSuffix(version) };
7425
+ return {
7426
+ type: "caret",
7427
+ prefix: "^",
7428
+ baseVersion: normalizeVersionString(version),
7429
+ original,
7430
+ isDev: hasDevSuffix(version)
7431
+ };
7426
7432
  }
7427
7433
  if (original.startsWith("~")) {
7428
7434
  const version = original.slice(1);
7429
- return { type: "tilde", prefix: "~", baseVersion: normalizeVersionString(version), original, isDev: hasDevSuffix(version) };
7435
+ return {
7436
+ type: "tilde",
7437
+ prefix: "~",
7438
+ baseVersion: normalizeVersionString(version),
7439
+ original,
7440
+ isDev: hasDevSuffix(version)
7441
+ };
7430
7442
  }
7431
7443
  if (original.includes("*")) {
7432
7444
  const basePart = (original.split("*")[0] ?? "").replace(/\.$/, "");
@@ -7434,16 +7446,34 @@ function parseConstraint(constraint) {
7434
7446
  }
7435
7447
  if (original.includes(" - ")) {
7436
7448
  const startVersion = (original.split(" - ")[0] ?? "").trim();
7437
- return { type: "hyphen", prefix: "", baseVersion: normalizeVersionString(startVersion), original, isDev: false };
7449
+ return {
7450
+ type: "hyphen",
7451
+ prefix: "",
7452
+ baseVersion: normalizeVersionString(startVersion),
7453
+ original,
7454
+ isDev: false
7455
+ };
7438
7456
  }
7439
7457
  const rangeMatch = original.match(/^([><!=]+)/);
7440
7458
  if (rangeMatch) {
7441
7459
  const versionMatch = original.match(/([><!=]+)\s*(\d+(?:\.\d+)*(?:-[\w.]+)?)/);
7442
7460
  const version = versionMatch?.[2] ?? original.replace(/[><!=]+/g, "");
7443
7461
  const prefix = rangeMatch[1] ?? ">=";
7444
- return { type: "range", prefix, baseVersion: normalizeVersionString(version), original, isDev: hasDevSuffix(version) };
7462
+ return {
7463
+ type: "range",
7464
+ prefix,
7465
+ baseVersion: normalizeVersionString(version),
7466
+ original,
7467
+ isDev: hasDevSuffix(version)
7468
+ };
7445
7469
  }
7446
- return { type: "exact", prefix: "", baseVersion: normalizeVersionString(original), original, isDev: hasDevSuffix(original) };
7470
+ return {
7471
+ type: "exact",
7472
+ prefix: "",
7473
+ baseVersion: normalizeVersionString(original),
7474
+ original,
7475
+ isDev: hasDevSuffix(original)
7476
+ };
7447
7477
  }
7448
7478
  function hasDevSuffix(version) {
7449
7479
  const lower = version.toLowerCase();
@@ -7521,12 +7551,78 @@ function formatNewVersion(originalConstraint, newVersion) {
7521
7551
  return `${parsed.prefix}${cleaned}`;
7522
7552
  }
7523
7553
 
7554
+ // src/cache.ts
7555
+ import fs from "node:fs/promises";
7556
+ import path from "node:path";
7557
+ import os from "node:os";
7558
+ function getCacheDir() {
7559
+ const home = os.homedir();
7560
+ const platform = os.platform();
7561
+ const overrideDir = process.env.COMZE_CACHE_DIR?.trim();
7562
+ if (overrideDir) {
7563
+ return overrideDir;
7564
+ }
7565
+ if (platform === "win32") {
7566
+ return path.join(process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "comze", "Cache");
7567
+ }
7568
+ if (platform === "darwin") {
7569
+ return path.join(home, "Library", "Caches", "comze");
7570
+ }
7571
+ return path.join(process.env.XDG_CACHE_HOME || path.join(home, ".cache"), "comze");
7572
+ }
7573
+ async function getCacheEntry(key) {
7574
+ try {
7575
+ const cacheDir = getCacheDir();
7576
+ const filePath = path.join(cacheDir, `${key}.json`);
7577
+ const content = await fs.readFile(filePath, "utf-8");
7578
+ const data = JSON.parse(content);
7579
+ return data;
7580
+ } catch {
7581
+ return null;
7582
+ }
7583
+ }
7584
+ async function setCache(key, value, meta = {}) {
7585
+ try {
7586
+ const cacheDir = getCacheDir();
7587
+ await fs.mkdir(cacheDir, { recursive: true });
7588
+ const filePath = path.join(cacheDir, `${key}.json`);
7589
+ const data = {
7590
+ timestamp: Date.now(),
7591
+ value,
7592
+ lastModified: meta.lastModified,
7593
+ etag: meta.etag
7594
+ };
7595
+ await fs.writeFile(filePath, JSON.stringify(data), "utf-8");
7596
+ } catch {}
7597
+ }
7598
+ async function touchCache(key) {
7599
+ try {
7600
+ const entry = await getCacheEntry(key);
7601
+ if (entry) {
7602
+ await setCache(key, entry.value, { lastModified: entry.lastModified, etag: entry.etag });
7603
+ }
7604
+ } catch {}
7605
+ }
7606
+
7524
7607
  // src/fetcher.ts
7525
7608
  var PACKAGIST_API = "https://repo.packagist.org/p2";
7526
- async function fetchPackage(packageName, minStability = "stable", preferStable = true, currentVersion, allowMajor = true) {
7609
+ async function fetchPackage(packageName, minStability = "stable", preferStable = true, currentVersion, allowMajor = true, noCache = false) {
7610
+ const cacheKey = packageName.replace("/", "_");
7611
+ let cachedEntry = null;
7612
+ const headers = {};
7613
+ if (!noCache) {
7614
+ cachedEntry = await getCacheEntry(cacheKey);
7615
+ if (cachedEntry?.lastModified) {
7616
+ headers["If-Modified-Since"] = cachedEntry.lastModified;
7617
+ }
7618
+ }
7527
7619
  try {
7528
7620
  const url = `${PACKAGIST_API}/${packageName}.json`;
7529
- const response = await fetch(url);
7621
+ const response = await fetch(url, { headers });
7622
+ if (response.status === 304 && cachedEntry) {
7623
+ await touchCache(cacheKey);
7624
+ return cachedEntry.value;
7625
+ }
7530
7626
  if (!response.ok)
7531
7627
  return null;
7532
7628
  const data = await response.json();
@@ -7580,24 +7676,38 @@ async function fetchPackage(packageName, minStability = "stable", preferStable =
7580
7676
  }
7581
7677
  }
7582
7678
  }
7583
- return {
7679
+ const deprecatedInfo = {};
7680
+ if (typeof selectedVersion.abandoned === "string") {
7681
+ deprecatedInfo.deprecated = true;
7682
+ deprecatedInfo.replacement = selectedVersion.abandoned;
7683
+ } else if (selectedVersion.abandoned) {
7684
+ deprecatedInfo.deprecated = true;
7685
+ }
7686
+ const result = {
7584
7687
  latestVersion: selectedVersion.version,
7585
7688
  releaseTime: selectedVersion.time,
7586
7689
  phpRequirement: selectedVersion.require?.php ?? phpRequirement,
7587
- majorVersion: majorDetected
7690
+ majorVersion: majorDetected,
7691
+ deprecated: deprecatedInfo.deprecated,
7692
+ replacement: deprecatedInfo.replacement
7588
7693
  };
7694
+ if (!noCache) {
7695
+ const lastModified = response.headers.get("Last-Modified") || undefined;
7696
+ await setCache(cacheKey, result, { lastModified });
7697
+ }
7698
+ return result;
7589
7699
  } catch {
7590
7700
  return null;
7591
7701
  }
7592
7702
  }
7593
- async function fetchAllPackages(packages, minStability = "stable", preferStable = true, allowMajor = true) {
7703
+ async function fetchAllPackages(packages, minStability = "stable", preferStable = true, allowMajor = true, noCache = false) {
7594
7704
  const results = new Map;
7595
7705
  const entries = Object.entries(packages);
7596
7706
  const CONCURRENCY = 5;
7597
7707
  for (let i = 0;i < entries.length; i += CONCURRENCY) {
7598
7708
  const batch = entries.slice(i, i + CONCURRENCY);
7599
7709
  const promises = batch.map(async ([name, version]) => {
7600
- const result = await fetchPackage(name, minStability, preferStable, version, allowMajor);
7710
+ const result = await fetchPackage(name, minStability, preferStable, version, allowMajor, noCache);
7601
7711
  if (result)
7602
7712
  results.set(name, result);
7603
7713
  });
@@ -7712,11 +7822,11 @@ function detectIndent(string) {
7712
7822
  }
7713
7823
 
7714
7824
  // src/writer.ts
7715
- async function readComposerJson(path) {
7716
- if (!existsSync(path))
7825
+ async function readComposerJson(path2) {
7826
+ if (!existsSync(path2))
7717
7827
  return null;
7718
7828
  try {
7719
- const raw = await readFile(path, "utf-8");
7829
+ const raw = await readFile(path2, "utf-8");
7720
7830
  const indent = detectIndent(raw).indent || " ";
7721
7831
  const content = JSON.parse(raw);
7722
7832
  return { content, raw, indent };
@@ -7724,8 +7834,8 @@ async function readComposerJson(path) {
7724
7834
  return null;
7725
7835
  }
7726
7836
  }
7727
- async function writeComposerJson(path, updates, dryRun = false) {
7728
- const result = await readComposerJson(path);
7837
+ async function writeComposerJson(path2, updates, dryRun = false) {
7838
+ const result = await readComposerJson(path2);
7729
7839
  if (!result)
7730
7840
  return false;
7731
7841
  const { content, indent } = result;
@@ -7752,7 +7862,7 @@ async function writeComposerJson(path, updates, dryRun = false) {
7752
7862
  try {
7753
7863
  const newContent = JSON.stringify(content, null, indent) + `
7754
7864
  `;
7755
- await writeFile(path, newContent, "utf-8");
7865
+ await writeFile(path2, newContent, "utf-8");
7756
7866
  return true;
7757
7867
  } catch {
7758
7868
  return false;
@@ -7854,6 +7964,12 @@ function renderTable(packages) {
7854
7964
  if (pkg.phpRequirement) {
7855
7965
  extra += import_picocolors.default.gray(` php ${pkg.phpRequirement}`);
7856
7966
  }
7967
+ if (pkg.deprecated) {
7968
+ extra += import_picocolors.default.red(" deprecated");
7969
+ if (pkg.replacement) {
7970
+ extra += import_picocolors.default.yellow(` → ${pkg.replacement}`);
7971
+ }
7972
+ }
7857
7973
  console.log(` ${name} ${oldVer} ${arrow} ${coloredNewVer} ${diffLabel} ${age}${extra}`);
7858
7974
  }
7859
7975
  console.log("");
@@ -7896,8 +8012,24 @@ function formatPackageChoice(pkg) {
7896
8012
  if (pkg.phpRequirement) {
7897
8013
  extra += import_picocolors.default.gray(` php ${pkg.phpRequirement}`);
7898
8014
  }
8015
+ if (pkg.deprecated) {
8016
+ extra += import_picocolors.default.red(" deprecated");
8017
+ if (pkg.replacement) {
8018
+ extra += import_picocolors.default.yellow(` → ${pkg.replacement}`);
8019
+ }
8020
+ }
7899
8021
  return `${import_picocolors.default.bold(pkg.name)} ${pkg.currentVersion} ${arrow} ${coloredNewVer} ${diffLabel} ${age}${extra}`;
7900
8022
  }
8023
+ function renderDeprecated(packages) {
8024
+ if (packages.length === 0)
8025
+ return;
8026
+ console.log(import_picocolors.default.yellow(" Deprecated packages detected:"));
8027
+ for (const pkg of packages) {
8028
+ const replacement = pkg.replacement ? import_picocolors.default.yellow(` → ${pkg.replacement}`) : "";
8029
+ console.log(` ${import_picocolors.default.red("!")} ${import_picocolors.default.bold(pkg.name)} ${import_picocolors.default.gray(pkg.currentVersion)}${replacement}`);
8030
+ }
8031
+ console.log("");
8032
+ }
7901
8033
 
7902
8034
  // src/interactive.ts
7903
8035
  var import_prompts = __toESM(require_prompts3(), 1);
@@ -7922,11 +8054,62 @@ async function selectPackages(packages) {
7922
8054
  const selectedNames = new Set(response.selected);
7923
8055
  return packages.filter((pkg) => selectedNames.has(pkg.name));
7924
8056
  }
8057
+ // package.json
8058
+ var package_default = {
8059
+ name: "comze",
8060
+ version: "0.3.1",
8061
+ description: "A taze-like CLI for updating composer.json dependencies",
8062
+ author: "qoqn",
8063
+ license: "MIT",
8064
+ keywords: [
8065
+ "composer",
8066
+ "php",
8067
+ "dependencies",
8068
+ "update",
8069
+ "cli",
8070
+ "taze"
8071
+ ],
8072
+ type: "module",
8073
+ main: "./dist/cli.mjs",
8074
+ module: "./dist/cli.mjs",
8075
+ bin: {
8076
+ comze: "bin/comze.mjs"
8077
+ },
8078
+ files: [
8079
+ "dist",
8080
+ "bin"
8081
+ ],
8082
+ scripts: {
8083
+ dev: "bun run src/cli.ts",
8084
+ build: "bun build src/cli.ts --outfile dist/cli.mjs --target node",
8085
+ prepublishOnly: "bun run build",
8086
+ test: "bun test"
8087
+ },
8088
+ engines: {
8089
+ node: ">=18"
8090
+ },
8091
+ repository: {
8092
+ type: "git",
8093
+ url: "git+https://github.com/qoqn/comze.git"
8094
+ },
8095
+ devDependencies: {
8096
+ "@types/bun": "latest",
8097
+ "@types/prompts": "^2.4.9",
8098
+ "@types/semver": "^7.7.1",
8099
+ typescript: "^5"
8100
+ },
8101
+ dependencies: {
8102
+ cac: "^6.7.14",
8103
+ "detect-indent": "^7.0.2",
8104
+ picocolors: "^1.1.1",
8105
+ prompts: "^2.4.2",
8106
+ semver: "^7.7.3"
8107
+ }
8108
+ };
7925
8109
 
7926
8110
  // src/index.ts
7927
- var VERSION = "0.1.0";
7928
8111
  async function run(options) {
7929
- renderHeader(VERSION);
8112
+ renderHeader(package_default.version);
7930
8113
  const composerPath = resolve(process.cwd(), "composer.json");
7931
8114
  const composer = await readComposerJson(composerPath);
7932
8115
  if (!composer) {
@@ -7950,12 +8133,20 @@ async function run(options) {
7950
8133
  console.log(import_picocolors2.default.gray(` Checking ${Object.keys(filteredPackages).length} packages...`));
7951
8134
  console.log(import_picocolors2.default.gray(` Stability: ${minStability}${preferStable ? " (prefer-stable)" : ""}
7952
8135
  `));
7953
- const results = await fetchAllPackages(filteredPackages, minStability, preferStable, options.major);
8136
+ const results = await fetchAllPackages(filteredPackages, minStability, preferStable, options.major, options.noCache);
7954
8137
  const updates = [];
8138
+ const deprecatedPackages = [];
7955
8139
  for (const [name, currentVersion] of Object.entries(filteredPackages)) {
7956
8140
  const result = results.get(name);
7957
8141
  if (!result)
7958
8142
  continue;
8143
+ if (result.deprecated) {
8144
+ deprecatedPackages.push({
8145
+ name,
8146
+ currentVersion,
8147
+ replacement: result.replacement
8148
+ });
8149
+ }
7959
8150
  const diffType = getDiffType(currentVersion, result.latestVersion);
7960
8151
  if (!diffType)
7961
8152
  continue;
@@ -7975,12 +8166,15 @@ async function run(options) {
7975
8166
  age: formatAge(result.releaseTime),
7976
8167
  ageMonths: getAgeMonths(result.releaseTime),
7977
8168
  majorAvailable,
7978
- phpRequirement: result.phpRequirement
8169
+ phpRequirement: result.phpRequirement,
8170
+ deprecated: result.deprecated,
8171
+ replacement: result.replacement
7979
8172
  });
7980
8173
  }
7981
8174
  const order = { major: 0, minor: 1, patch: 2 };
7982
8175
  updates.sort((a, b) => order[a.diffType] - order[b.diffType]);
7983
8176
  renderTable(updates);
8177
+ renderDeprecated(deprecatedPackages);
7984
8178
  if (updates.length === 0)
7985
8179
  return;
7986
8180
  let selectedUpdates = updates;
@@ -8027,9 +8221,13 @@ async function run(options) {
8027
8221
 
8028
8222
  // src/cli.ts
8029
8223
  var cli = dist_default("comze");
8030
- cli.option("-w, --write", "Write changes to composer.json", { default: false }).option("-i, --install", "Write changes and run composer update", { default: false }).option("-I, --interactive", "Select updates manually", { default: false }).option("--major", "Include major updates", { default: false }).option("--minor", "Include minor updates", { default: true }).option("--patch", "Include patch updates", { default: true }).option("--exclude <packages>", "Exclude packages (comma-separated)", { default: "" }).option("--dry-run", "Run without making changes", { default: false });
8224
+ cli.option("-w, --write", "Write changes to composer.json", { default: false }).option("-i, --install", "Write changes and run composer update", {
8225
+ default: false
8226
+ }).option("-I, --interactive", "Select updates manually", { default: false }).option("--major", "Include major updates", { default: false }).option("--minor", "Include minor updates", { default: true }).option("--patch", "Include patch updates", { default: true }).option("--exclude <packages>", "Exclude packages (comma-separated)", {
8227
+ default: ""
8228
+ }).option("--dry-run", "Run without making changes", { default: false }).option("--no-cache", "Bypass cache and force fetch from network", { default: false });
8031
8229
  cli.help();
8032
- cli.version("0.1.0");
8230
+ cli.version(package_default.version);
8033
8231
  cli.command("", "Check for updates in composer.json").action(async (cliOptions) => {
8034
8232
  const options = {
8035
8233
  write: cliOptions.write || cliOptions.install,
@@ -8039,7 +8237,8 @@ cli.command("", "Check for updates in composer.json").action(async (cliOptions)
8039
8237
  minor: cliOptions.minor,
8040
8238
  patch: cliOptions.patch,
8041
8239
  exclude: cliOptions.exclude ? cliOptions.exclude.split(",").map((s) => s.trim()) : [],
8042
- dryRun: cliOptions.dryRun
8240
+ dryRun: cliOptions.dryRun,
8241
+ noCache: cliOptions.noCache
8043
8242
  };
8044
8243
  await run(options);
8045
8244
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comze",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "A taze-like CLI for updating composer.json dependencies",
5
5
  "author": "qoqn",
6
6
  "license": "MIT",