Browse Source

Merge pull request #56 from beeman/beeman/sync-package-json

feat: add script to sync versions in package.json files
Nick Frostbutter 1 year ago
parent
commit
86ab1de06e

+ 17 - 0
package.json

@@ -0,0 +1,17 @@
+{
+  "name": "program-examples",
+  "version": "1.0.0",
+  "description": "### :crab: Rust. :snake: Python. :ice_cube: Solidity. :link: All on-chain.",
+  "scripts": {
+    "sync-package-json": "ts-node scripts/sync-package-json.ts"
+  },
+  "keywords": [],
+  "author": "Solana Foundation",
+  "license": "MIT",
+  "devDependencies": {
+    "@types/node": "^20.9.0",
+    "picocolors": "^1.0.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^5.2.2"
+  }
+}

+ 14 - 0
scripts/lib/change-package-version.ts

@@ -0,0 +1,14 @@
+import { readFileSync } from 'node:fs'
+
+export function changePackageVersion(file: string, pkgName: string, pkgVersion: string): [boolean, string] {
+  const content = JSON.parse(readFileSync(file).toString('utf-8'))
+  if (content.dependencies && content.dependencies[pkgName] && content.dependencies[pkgName] !== pkgVersion) {
+    content.dependencies[pkgName] = pkgVersion
+    return [true, content]
+  }
+  if (content.devDependencies && content.devDependencies[pkgName] && content.devDependencies[pkgName] !== pkgVersion) {
+    content.devDependencies[pkgName] = pkgVersion
+    return [true, content]
+  }
+  return [false, content]
+}

+ 42 - 0
scripts/lib/command-check.ts

@@ -0,0 +1,42 @@
+import { basename } from 'node:path'
+import * as p from 'picocolors'
+import { getDepsCount } from './get-deps-count'
+import { getRecursiveFileList } from './get-recursive-file-list'
+
+export function commandCheck(path: string = '.') {
+  const files = getRecursiveFileList(path).filter((file) => basename(file) === 'package.json')
+  const depsCounter = getDepsCount(files)
+
+  const single: string[] = []
+  const multiple: string[] = []
+
+  Object.keys(depsCounter)
+    .sort()
+    .map((pkg) => {
+      const versions = depsCounter[pkg]
+      const versionMap = Object.keys(versions).sort()
+      const versionsLength = versionMap.length
+
+      if (versionsLength === 1) {
+        const count = versions[versionMap[0]].length
+        single.push(`${p.green(`✔`)} ${pkg}@${versionMap[0]} (${count})`)
+        return
+      }
+
+      const versionCount: { version: string; count: number }[] = []
+      for (const version of versionMap) {
+        versionCount.push({ version, count: versions[version].length })
+      }
+      versionCount.sort((a, b) => b.count - a.count)
+
+      multiple.push(`${p.yellow(`⚠`)} ${pkg} has ${versionsLength} versions:`)
+
+      for (const { count, version } of versionCount) {
+        multiple.push(`  - ${p.bold(version)} (${count})`)
+      }
+    })
+
+  for (const string of [...single.sort(), ...multiple]) {
+    console.log(string)
+  }
+}

+ 26 - 0
scripts/lib/command-help.ts

@@ -0,0 +1,26 @@
+export function commandHelp() {
+  console.log(`Usage: yarn sync-package-json <command> [options]`)
+  console.log(``)
+  console.log(`Commands:`)
+  console.log(`  check  <path>        Check package.json files`)
+  console.log(`  help                 Show this help`)
+  console.log(`  list   <path>        List package.json files`)
+  console.log(`  set    [ver] <path>  Set specific version in package.json files`)
+  console.log(`  update <path> <pkgs> Update all versions in package.json files`)
+  console.log(``)
+  console.log(`Arguments:`)
+  console.log(`  path    Path to directory`)
+  console.log(``)
+  console.log(`Examples:`)
+  console.log(`  yarn sync-package-json check`)
+  console.log(`  yarn sync-package-json check basics`)
+  console.log(`  yarn sync-package-json list`)
+  console.log(`  yarn sync-package-json list basics`)
+  console.log(`  yarn sync-package-json help`)
+  console.log(`  yarn sync-package-json set @coral-xyz/anchor@0.29.0`)
+  console.log(`  yarn sync-package-json set @coral-xyz/anchor@0.29.0 basics`)
+  console.log(`  yarn sync-package-json update`)
+  console.log(`  yarn sync-package-json update basics`)
+  console.log(`  yarn sync-package-json update . @solana/web3.js @solana/spl-token`)
+  process.exit(0)
+}

+ 9 - 0
scripts/lib/command-list.ts

@@ -0,0 +1,9 @@
+import { basename } from 'node:path'
+import { getRecursiveFileList } from './get-recursive-file-list'
+
+export function commandList(path: string) {
+  const files = getRecursiveFileList(path).filter((file) => basename(file) === 'package.json')
+  for (const file of files) {
+    console.log(file)
+  }
+}

+ 43 - 0
scripts/lib/command-set.ts

@@ -0,0 +1,43 @@
+import { writeFileSync } from 'fs'
+import { basename } from 'node:path'
+import { changePackageVersion } from './change-package-version'
+import { getRecursiveFileList } from './get-recursive-file-list'
+
+export function commandSet(version: string, path: string = '.') {
+  if (!version) {
+    console.error(`Version is required`)
+    process.exit(1)
+  }
+  if (
+    !version
+      // Strip first character if it's a `@`
+      .replace(/^@/, '')
+      .includes('@')
+  ) {
+    console.error(`Invalid package version: ${version}. Provide package with version, e.g. @solana/web3.js@1.0.0`)
+    process.exit(1)
+  }
+  // Take anything after the second `@` as the version, the rest is the package name
+  const [pkg, ...rest] = version.split('@').reverse()
+  const pkgName = rest.reverse().join('@')
+
+  // Make sure pkgVersions has a ^ prefix, if not add it
+  const pkgVersion = pkg.startsWith('^') ? pkg : `^${pkg}`
+
+  console.log(`Setting package ${pkgName} to ${pkgVersion} in ${path}`)
+
+  const files = getRecursiveFileList(path).filter((file) => basename(file) === 'package.json')
+  let count = 0
+  for (const file of files) {
+    const [changed, content] = changePackageVersion(file, pkgName, pkgVersion)
+    if (changed) {
+      writeFileSync(file, JSON.stringify(content, null, 2) + '\n')
+      count++
+    }
+  }
+  if (count === 0) {
+    console.log(`No files updated`)
+  } else {
+    console.log(`Updated ${count} files`)
+  }
+}

+ 45 - 0
scripts/lib/command-update.ts

@@ -0,0 +1,45 @@
+import { execSync } from 'child_process'
+import { writeFileSync } from 'fs'
+import { basename } from 'node:path'
+import * as p from 'picocolors'
+import { changePackageVersion } from './change-package-version'
+
+import { getDepsCount } from './get-deps-count'
+import { getRecursiveFileList } from './get-recursive-file-list'
+
+export function commandUpdate(path: string = '.', packageNames: string[] = []) {
+  const files = getRecursiveFileList(path).filter((file) => basename(file) === 'package.json')
+  const depsCounter = getDepsCount(files)
+  const pkgNames = Object.keys(depsCounter).sort()
+  if (packageNames.length > 0) {
+    console.log(`Updating ${packageNames.join(', ')} in ${files.length} files`)
+  }
+
+  let total = 0
+  for (const pkgName of pkgNames.filter((pkgName) => packageNames.length === 0 || packageNames.includes(pkgName))) {
+    // Get latest version from npm
+    const npmVersion = execSync(`npm view ${pkgName} version`).toString().trim()
+
+    let count = 0
+    for (const file of files) {
+      const [changed, content] = changePackageVersion(file, pkgName, `^${npmVersion}`)
+      if (changed) {
+        writeFileSync(file, JSON.stringify(content, null, 2) + '\n')
+        count++
+      }
+    }
+    total += count
+
+    if (count === 0) {
+      console.log(p.dim(`Package ${pkgName} is up to date ${npmVersion}`))
+      continue
+    }
+    console.log(p.green(` -> Updated ${count} files with ${pkgName} ${npmVersion}`))
+  }
+
+  if (total === 0) {
+    console.log(`No files updated`)
+  } else {
+    console.log(`Updated ${total} files`)
+  }
+}

+ 32 - 0
scripts/lib/get-deps-count.ts

@@ -0,0 +1,32 @@
+import { readFileSync } from 'node:fs'
+
+export function getDepsCount(files: string[] = []): Record<string, Record<string, string[]>> {
+  const map: Record<string, JSON> = {}
+  const depsCounter: Record<string, Record<string, string[]>> = {}
+
+  for (const file of files) {
+    const content = JSON.parse(readFileSync(file).toString('utf-8'))
+    map[file] = content
+
+    const deps = content.dependencies ?? {}
+    const devDeps = content.devDependencies ?? {}
+
+    const merged = { ...deps, ...devDeps }
+
+    Object.keys(merged)
+      .sort()
+      .map((pkg) => {
+        const pkgVersion = merged[pkg]
+        if (!depsCounter[pkg]) {
+          depsCounter[pkg] = { [pkgVersion]: [file] }
+          return
+        }
+        if (!depsCounter[pkg][pkgVersion]) {
+          depsCounter[pkg][pkgVersion] = [file]
+          return
+        }
+        depsCounter[pkg][pkgVersion] = [...depsCounter[pkg][pkgVersion], file]
+      })
+  }
+  return depsCounter
+}

+ 28 - 0
scripts/lib/get-recursive-file-list.ts

@@ -0,0 +1,28 @@
+// Point method at path and return a list of all the files in the directory recursively
+import { readdirSync, statSync } from 'node:fs'
+
+export function getRecursiveFileList(path: string): string[] {
+  const ignore = ['.git', '.github', '.idea', '.next', '.vercel', '.vscode', 'coverage', 'dist', 'node_modules']
+  const files: string[] = []
+
+  const items = readdirSync(path)
+  items.forEach((item) => {
+    if (ignore.includes(item)) {
+      return
+    }
+    // Check out if it's a directory or a file
+    const isDir = statSync(`${path}/${item}`).isDirectory()
+    if (isDir) {
+      // If it's a directory, recursively call the method
+      files.push(...getRecursiveFileList(`${path}/${item}`))
+    } else {
+      // If it's a file, add it to the array of files
+      files.push(`${path}/${item}`)
+    }
+  })
+
+  return files.filter((file) => {
+    // Remove package.json from the root directory
+    return path === '.' ? file !== './package.json' : true
+  })
+}

+ 5 - 0
scripts/lib/index.ts

@@ -0,0 +1,5 @@
+export * from './command-check'
+export * from './command-help'
+export * from './command-list'
+export * from './command-set'
+export * from './command-update'

+ 22 - 0
scripts/sync-package-json.ts

@@ -0,0 +1,22 @@
+import { commandCheck, commandHelp, commandList, commandSet, commandUpdate } from './lib'
+
+const params: string[] = process.argv.slice(3)
+
+switch (process.argv[2]) {
+  case 'check':
+    commandCheck(params[0])
+    break
+  case 'list':
+    commandList(params[0])
+    break
+  case 'set':
+    commandSet(params[0], params[1])
+    break
+  case 'update':
+    commandUpdate(params[0], params.slice(1))
+    break
+  case 'help':
+  default:
+    commandHelp()
+    break
+}

+ 129 - 0
yarn.lock

@@ -0,0 +1,129 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@cspotcode/source-map-support@^0.8.0":
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
+  integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
+  dependencies:
+    "@jridgewell/trace-mapping" "0.3.9"
+
+"@jridgewell/resolve-uri@^3.0.3":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+  integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+  version "1.4.15"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@0.3.9":
+  version "0.3.9"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+  integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+  dependencies:
+    "@jridgewell/resolve-uri" "^3.0.3"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@tsconfig/node10@^1.0.7":
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
+  integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
+
+"@tsconfig/node12@^1.0.7":
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
+  integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
+
+"@tsconfig/node14@^1.0.0":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
+  integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
+
+"@tsconfig/node16@^1.0.2":
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
+  integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+
+"@types/node@^20.9.0":
+  version "20.9.0"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
+  integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
+  dependencies:
+    undici-types "~5.26.4"
+
+acorn-walk@^8.1.1:
+  version "8.3.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f"
+  integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==
+
+acorn@^8.4.1:
+  version "8.11.2"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
+  integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
+
+arg@^4.1.0:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+  integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
+
+create-require@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
+  integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+
+diff@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
+make-error@^1.1.1:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
+picocolors@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+ts-node@^10.9.1:
+  version "10.9.1"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
+  integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
+  dependencies:
+    "@cspotcode/source-map-support" "^0.8.0"
+    "@tsconfig/node10" "^1.0.7"
+    "@tsconfig/node12" "^1.0.7"
+    "@tsconfig/node14" "^1.0.0"
+    "@tsconfig/node16" "^1.0.2"
+    acorn "^8.4.1"
+    acorn-walk "^8.1.1"
+    arg "^4.1.0"
+    create-require "^1.1.0"
+    diff "^4.0.1"
+    make-error "^1.1.1"
+    v8-compile-cache-lib "^3.0.1"
+    yn "3.1.1"
+
+typescript@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
+  integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
+
+undici-types@~5.26.4:
+  version "5.26.5"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+  integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
+v8-compile-cache-lib@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
+  integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
+
+yn@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==