1 | import { promisify } from 'util'
|
2 | import semver from 'semver'
|
3 | const { valid, coerce, compare } = semver
|
4 | import { map, flatten } from '@ctx-core/array'
|
5 | import { _h__param } from '@ctx-core/cli-args'
|
6 | import { _queue } from '@ctx-core/queue'
|
7 | import fs from 'fs'
|
8 | import detect_indent from 'detect-indent'
|
9 | import child_process from 'child_process'
|
10 | const exec = promisify(child_process.exec)
|
11 | import globby from 'globby'
|
12 | const readFile = promisify(fs.readFile)
|
13 | const writeFile = promisify(fs.writeFile)
|
14 | export async function _workspaces() {
|
15 | const txt__workspaces = (await exec('yarn workspaces info')).stdout
|
16 | const a1__txt__workspaces = txt__workspaces.split('\n')
|
17 | const line__start__json__workspaces = a1__txt__workspaces.indexOf('{')
|
18 | const line__end__json__workspaces = a1__txt__workspaces.indexOf('}')
|
19 | const json__workspaces =
|
20 | a1__txt__workspaces.slice(
|
21 | line__start__json__workspaces,
|
22 | line__end__json__workspaces + 1
|
23 | ).join('\n')
|
24 | return JSON.parse(json__workspaces)
|
25 | }
|
26 | export async function each__package__json(txt__glob, fn) {
|
27 | const a1__package__json = await globby(txt__glob)
|
28 | const a1__promise = map(a1__package__json, fn)
|
29 | await Promise.all(a1__promise)
|
30 | }
|
31 | export async function cli__npm_check_updates__monorepo() {
|
32 | const h__param = _h__param(process.argv.slice(2), {
|
33 | threads: '-t, --threads',
|
34 | workspace_name: '-w, --workspace-name'
|
35 | }, {
|
36 | threads: 20,
|
37 | })
|
38 | const h1__name__workspace__h0__stdout = await npm_check_updates__monorepo(h__param)
|
39 | for (let name__workspace in h1__name__workspace__h0__stdout) {
|
40 | console.info(name__workspace)
|
41 | console.info(h1__name__workspace__h0__stdout[name__workspace])
|
42 | }
|
43 | }
|
44 | type Opts__threads = {
|
45 | threads?:number
|
46 | workspace_name?:string|string[]
|
47 | }
|
48 | export async function npm_check_updates__monorepo(opts:Opts__threads = {}) {
|
49 | const package_name__x__latest_version = {}
|
50 | const package_name__x__already_warned = {}
|
51 | const queue = _queue(opts.threads || 20)
|
52 | const workspaces = await _workspaces()
|
53 | const a1__workspace_name =
|
54 | opts.workspace_name
|
55 | ? flatten([opts.workspace_name])
|
56 | : Object.keys(workspaces)
|
57 | const a1__promise = _a1__promise(a1__workspace_name, _promise__workspace)
|
58 | if (!opts.workspace_name) {
|
59 | a1__workspace_name.push('.')
|
60 | a1__promise.push(_promise('.'))
|
61 | }
|
62 | const a1__stdout = await Promise.all(a1__promise)
|
63 | return _h1__stdout__h0__name__workspace(a1__workspace_name, a1__stdout)
|
64 | async function _promise(location = '.') {
|
65 | const path__package__json = `${location}/package.json`
|
66 | const pkg_json = (await readFile(path__package__json)).toString()
|
67 | const pkg = JSON.parse(pkg_json)
|
68 | const { dependencies, peerDependencies, devDependencies, noUpdate } = pkg
|
69 | const update_a2 = []
|
70 | update_a2.push(await update__dependencies(dependencies, noUpdate))
|
71 | update_a2.push(await update__dependencies(devDependencies, noUpdate))
|
72 | update_a2.push(await update__dependencies(peerDependencies, noUpdate))
|
73 | const update_a1 = flatten(update_a2)
|
74 | if (update_a1.length) {
|
75 | const indent = detect_indent(pkg_json).indent || '\t'
|
76 | await writeFile(path__package__json, JSON.stringify(pkg, null, indent))
|
77 | }
|
78 | return update_a1.join('\n')
|
79 | }
|
80 | async function _promise__workspace(name__workspace) {
|
81 | const workspace = workspaces[name__workspace]
|
82 | const { location } = workspace
|
83 | return _promise(location)
|
84 | }
|
85 | async function update__dependencies(dependencies, noUpdate = []) {
|
86 | noUpdate = noUpdate || []
|
87 | const update_a1 = []
|
88 | for (let package_name in dependencies) {
|
89 | if (~noUpdate.indexOf(package_name)) continue
|
90 | const dependency_workspace = workspaces[package_name]
|
91 | const version = dependencies[package_name]
|
92 | const has_carrot = version.slice(0, 1) === '^'
|
93 | if (dependency_workspace) {
|
94 | const { location } = dependency_workspace
|
95 | const pkg = JSON.parse(
|
96 | (await readFile(`${location}/package.json`)).toString()
|
97 | )
|
98 | const latest_version =
|
99 | `${version.slice(0, 1) === '^' ? '^' : ''}${pkg.version}`
|
100 | package_name__x__latest_version[package_name] = pkg.version
|
101 | if (compare(coerce(latest_version), coerce(version)) > 0) {
|
102 | push__update_a1(update_a1, package_name, version, latest_version)
|
103 | dependencies[package_name] = latest_version
|
104 | }
|
105 | } else {
|
106 | if (!valid(coerce(dependencies[package_name]))) continue
|
107 | if (package_name__x__latest_version[package_name] == null) {
|
108 | const promise = queue.add(async ()=>
|
109 | (
|
110 | await exec(
|
111 | `npm show ${package_name}@latest | grep latest | grep \\: | cut -f2 -d: | xargs echo`
|
112 | )
|
113 | ).stdout.trim()
|
114 | )
|
115 | package_name__x__latest_version[package_name] = promise
|
116 | }
|
117 | if (package_name__x__latest_version[package_name]?.then) {
|
118 | package_name__x__latest_version[package_name] =
|
119 | (await package_name__x__latest_version[package_name])
|
120 | || ''
|
121 | }
|
122 | const latest_stripped_version = package_name__x__latest_version[package_name]
|
123 | if (!latest_stripped_version && !package_name__x__already_warned[package_name]) {
|
124 | package_name__x__already_warned[package_name] = true
|
125 | console.warn(
|
126 | `WARN: Unable to parse ${package_name} from npm registry`
|
127 | )
|
128 | }
|
129 | if (
|
130 | latest_stripped_version
|
131 | && compare(
|
132 | coerce(latest_stripped_version),
|
133 | coerce(version)
|
134 | ) > 0
|
135 | ) {
|
136 | const latest_version = `${has_carrot ? '^' : ''}${latest_stripped_version}`
|
137 | push__update_a1(update_a1, package_name, version, latest_version)
|
138 | dependencies[package_name] = latest_version
|
139 | }
|
140 | }
|
141 | }
|
142 | return update_a1
|
143 | }
|
144 | function push__update_a1(update_a1, package_name, version, latest_version) {
|
145 | update_a1.push(`${package_name}: ${version} -> ${latest_version}`)
|
146 | }
|
147 | }
|
148 | export async function run_parallel__workspaces(cmd_a1, opts:Opts__threads = {}) {
|
149 | const queue = _queue(opts.threads || 20)
|
150 | const workspaces = await _workspaces()
|
151 | const cmd = cmd_a1.join(' ')
|
152 | const name_a1__workspace = Object.keys(workspaces)
|
153 | const promise_a1 = _a1__promise(name_a1__workspace, _promise)
|
154 | const stdout_a1 = await Promise.all(promise_a1)
|
155 | return _h1__stdout__h0__name__workspace(name_a1__workspace, stdout_a1)
|
156 | async function _promise(name__workspace) {
|
157 | const workspace = workspaces[name__workspace]
|
158 | const { location } = workspace
|
159 | return (
|
160 | queue.add(
|
161 | async ()=>
|
162 | (await exec(`cd ${location}; ${cmd}`)).stdout.trim()
|
163 | )
|
164 | )
|
165 | }
|
166 | }
|
167 | function _a1__promise(a1__workspace, _promise) {
|
168 | const a1__promise = []
|
169 | for (let i = 0; i < a1__workspace.length; i++) {
|
170 | const name__workspace = a1__workspace[i]
|
171 | a1__promise.push(_promise(name__workspace))
|
172 | }
|
173 | return a1__promise
|
174 | }
|
175 | function _h1__stdout__h0__name__workspace(a1__name__workspace, stdout_a1) {
|
176 | const stdout__name__workspace = {}
|
177 | for (let i = 0; i < a1__name__workspace.length; i++) {
|
178 | const name__workspace = a1__name__workspace[i]
|
179 | stdout__name__workspace[name__workspace] = stdout_a1[i]
|
180 | }
|
181 | return stdout__name__workspace
|
182 | }
|