diff --git a/package.json b/package.json new file mode 100644 index 00000000..db3a5441 --- /dev/null +++ b/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" + } +} diff --git a/scripts/lib/change-package-version.ts b/scripts/lib/change-package-version.ts new file mode 100644 index 00000000..5dc6ecd4 --- /dev/null +++ b/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] +} diff --git a/scripts/lib/command-check.ts b/scripts/lib/command-check.ts new file mode 100644 index 00000000..e824ea12 --- /dev/null +++ b/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) + } +} diff --git a/scripts/lib/command-help.ts b/scripts/lib/command-help.ts new file mode 100644 index 00000000..88895923 --- /dev/null +++ b/scripts/lib/command-help.ts @@ -0,0 +1,26 @@ +export function commandHelp() { + console.log(`Usage: yarn sync-package-json [options]`) + console.log(``) + console.log(`Commands:`) + console.log(` check Check package.json files`) + console.log(` help Show this help`) + console.log(` list List package.json files`) + console.log(` set [ver] Set specific version in package.json files`) + console.log(` update 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) +} diff --git a/scripts/lib/command-list.ts b/scripts/lib/command-list.ts new file mode 100644 index 00000000..2082006e --- /dev/null +++ b/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) + } +} diff --git a/scripts/lib/command-set.ts b/scripts/lib/command-set.ts new file mode 100644 index 00000000..6c3065d5 --- /dev/null +++ b/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`) + } +} diff --git a/scripts/lib/command-update.ts b/scripts/lib/command-update.ts new file mode 100644 index 00000000..07e3b050 --- /dev/null +++ b/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`) + } +} diff --git a/scripts/lib/get-deps-count.ts b/scripts/lib/get-deps-count.ts new file mode 100644 index 00000000..799e1489 --- /dev/null +++ b/scripts/lib/get-deps-count.ts @@ -0,0 +1,32 @@ +import { readFileSync } from 'node:fs' + +export function getDepsCount(files: string[] = []): Record> { + const map: Record = {} + const depsCounter: Record> = {} + + 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 +} diff --git a/scripts/lib/get-recursive-file-list.ts b/scripts/lib/get-recursive-file-list.ts new file mode 100644 index 00000000..13bb42fa --- /dev/null +++ b/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 + }) +} diff --git a/scripts/lib/index.ts b/scripts/lib/index.ts new file mode 100644 index 00000000..f4859ac8 --- /dev/null +++ b/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' diff --git a/scripts/sync-package-json.ts b/scripts/sync-package-json.ts new file mode 100644 index 00000000..f0679c55 --- /dev/null +++ b/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 +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..a7d40a81 --- /dev/null +++ b/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==