@monosmith/cli 2.0.0-beta.4 → 2.0.0-beta.41
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/bin/smith.js +145 -26
- package/package.json +7 -7
package/bin/smith.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
// Runs the smith binary npm installed for this platform, from one of the
|
|
4
|
-
// optional dependencies; nothing is downloaded after install.
|
|
5
|
-
|
|
4
|
+
// optional dependencies; nothing is downloaded after install. When that package
|
|
5
|
+
// is missing, it says why and offers a fix, and changes nothing without a yes.
|
|
6
|
+
const { spawn, spawnSync } = require('node:child_process')
|
|
6
7
|
const fs = require('node:fs')
|
|
7
8
|
|
|
8
9
|
const platforms = {
|
|
@@ -13,33 +14,151 @@ const platforms = {
|
|
|
13
14
|
"win32-arm64": "@monosmith/cli-win32-arm64",
|
|
14
15
|
"win32-x64": "@monosmith/cli-win32-x64"
|
|
15
16
|
}
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const NAME = 'smith'
|
|
18
|
+
const PROBE_TIMEOUT_MS = 5000
|
|
19
|
+
const RETRY_EVERY_MS = 30000
|
|
20
|
+
const RETRY_FOR_MS = 10 * 60 * 1000
|
|
21
|
+
|
|
22
|
+
// probe asks npm, with a short limit, whether pkg@version is on the registry.
|
|
23
|
+
// It returns 'exists', 'absent' or 'unknown' (npm could not be asked).
|
|
24
|
+
function probe(d, pkg) {
|
|
25
|
+
const r = d.npm(['view', pkg + '@' + d.version, 'version'])
|
|
26
|
+
if (r.error || r.signal || r.status === null) return 'unknown'
|
|
27
|
+
if (r.status === 0) return String(r.stdout).trim() === d.version ? 'exists' : 'absent'
|
|
28
|
+
return /E404|404 Not Found|is not in this registry/i.test(String(r.stderr)) ? 'absent' : 'unknown'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ask puts a question and returns true for yes. Enter takes the default.
|
|
32
|
+
async function ask(d, question, defaultYes) {
|
|
33
|
+
const answer = String(await d.ask(question + (defaultYes ? ' [Y/n] ' : ' [y/N] '))).trim().toLowerCase()
|
|
34
|
+
if (answer === '') return defaultYes
|
|
35
|
+
return answer === 'y' || answer === 'yes'
|
|
21
36
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.error('smith: ' + pkg + ' is not installed; reinstall without --omit=optional or --no-optional')
|
|
28
|
-
process.exit(1)
|
|
37
|
+
|
|
38
|
+
// install runs the exact command it printed, and says if it worked.
|
|
39
|
+
function install(d, pkg) {
|
|
40
|
+
const r = d.npm(['install', '-g', pkg + '@' + d.version], { stdio: 'inherit', timeout: 0 })
|
|
41
|
+
return !r.error && r.status === 0
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
|
|
44
|
+
// offerInstall is situation (a): the package is on the registry now.
|
|
45
|
+
async function offerInstall(d, pkg) {
|
|
46
|
+
const command = 'npm install -g ' + pkg + '@' + d.version
|
|
47
|
+
d.log(NAME + ': ' + pkg + '@' + d.version + ' is on the npm registry, so your install missed it (an interrupted or partial install).')
|
|
48
|
+
d.log(NAME + ': to fix it, run: ' + command)
|
|
49
|
+
let yes = false
|
|
50
|
+
if (d.env.SMITH_AUTO_FIX === '1') {
|
|
51
|
+
d.log(NAME + ': SMITH_AUTO_FIX=1 is set, so it runs now.')
|
|
52
|
+
yes = true
|
|
53
|
+
} else if (d.isTTY) {
|
|
54
|
+
yes = await ask(d, 'Install it now?', true)
|
|
55
|
+
}
|
|
56
|
+
if (!yes) return null
|
|
57
|
+
if (!install(d, pkg)) {
|
|
58
|
+
d.log(NAME + ': the install failed; run the command above yourself to see why.')
|
|
59
|
+
return null
|
|
35
60
|
}
|
|
61
|
+
return d.resolve(pkg)
|
|
36
62
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
|
|
64
|
+
// waitAndRetry is situation (b) when the person agrees to wait.
|
|
65
|
+
async function waitAndRetry(d, pkg) {
|
|
66
|
+
for (let waited = 0; waited < RETRY_FOR_MS; waited += RETRY_EVERY_MS) {
|
|
67
|
+
d.log(NAME + ': waiting for ' + pkg + '@' + d.version + ' to reach the registry (' + Math.round(waited / 1000) + ' s of ' + RETRY_FOR_MS / 1000 + ' s); press Ctrl+C to stop.')
|
|
68
|
+
await d.sleep(RETRY_EVERY_MS)
|
|
69
|
+
const state = probe(d, pkg)
|
|
70
|
+
if (state === 'exists') return offerInstall(d, pkg)
|
|
71
|
+
if (state === 'unknown') {
|
|
72
|
+
d.log(NAME + ': npm could not be asked any more, so it stops waiting.')
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
d.log(NAME + ': ' + pkg + '@' + d.version + ' is still not on the registry after ' + RETRY_FOR_MS / 60000 + ' minutes. Try again later.')
|
|
77
|
+
return null
|
|
41
78
|
}
|
|
42
|
-
|
|
43
|
-
|
|
79
|
+
|
|
80
|
+
// resolveBinary returns the path of the binary of this platform. When the
|
|
81
|
+
// platform package is missing it tells which of three situations this is
|
|
82
|
+
// (on the registry now, not there yet, or npm cannot be asked) and returns
|
|
83
|
+
// null unless a fix was agreed to and worked.
|
|
84
|
+
async function resolveBinary(d) {
|
|
85
|
+
const pkg = platforms[d.key]
|
|
86
|
+
if (!pkg) {
|
|
87
|
+
d.log(NAME + ' has no build for ' + d.key)
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
const found = d.resolve(pkg)
|
|
91
|
+
if (found) return found
|
|
92
|
+
d.log(NAME + ': ' + pkg + ' is not installed. npm skips an optional package without an error when it cannot get it.')
|
|
93
|
+
switch (probe(d, pkg)) {
|
|
94
|
+
case 'exists':
|
|
95
|
+
return offerInstall(d, pkg)
|
|
96
|
+
case 'absent':
|
|
97
|
+
d.log(NAME + ': ' + pkg + '@' + d.version + ' is not on the npm registry yet. A new release can take 5 to 10 minutes to spread. Try again in a few minutes.')
|
|
98
|
+
if (d.isTTY && await ask(d, 'Wait and retry every 30 seconds, for up to 10 minutes?', false)) return waitAndRetry(d, pkg)
|
|
99
|
+
return null
|
|
100
|
+
default:
|
|
101
|
+
d.log(NAME + ': npm could not be asked (it is not in PATH, it did not answer in ' + PROBE_TIMEOUT_MS / 1000 + ' s, or there is no network).')
|
|
102
|
+
d.log(NAME + ': when you are online, run: npm install -g ' + pkg + '@' + d.version + ' (or reinstall without --omit=optional or --no-optional)')
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// realDeps is what the launcher uses; tests pass their own.
|
|
108
|
+
function realDeps() {
|
|
109
|
+
const file = process.platform === 'win32' ? NAME + '.exe' : NAME
|
|
110
|
+
const isTTY = Boolean(process.stdin.isTTY && process.stderr.isTTY)
|
|
111
|
+
return {
|
|
112
|
+
key: process.platform + '-' + process.arch,
|
|
113
|
+
version: require('../package.json').version,
|
|
114
|
+
env: process.env,
|
|
115
|
+
isTTY,
|
|
116
|
+
log: (line) => console.error(line),
|
|
117
|
+
resolve: (pkg) => {
|
|
118
|
+
try { return require.resolve(pkg + '/bin/' + file) } catch { return null }
|
|
119
|
+
},
|
|
120
|
+
// npm is a .cmd file on Windows, which needs a shell to start.
|
|
121
|
+
npm: (args, options) => spawnSync('npm', args, { encoding: 'utf8', timeout: PROBE_TIMEOUT_MS, shell: process.platform === 'win32', ...options }),
|
|
122
|
+
ask: (question) => new Promise((resolve) => {
|
|
123
|
+
const rl = require('node:readline').createInterface({ input: process.stdin, output: process.stderr })
|
|
124
|
+
rl.question(question, (answer) => { rl.close(); resolve(answer) })
|
|
125
|
+
}),
|
|
126
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function launch(binary) {
|
|
131
|
+
if (process.platform !== 'win32') {
|
|
132
|
+
try {
|
|
133
|
+
fs.accessSync(binary, fs.constants.X_OK)
|
|
134
|
+
} catch {
|
|
135
|
+
try { fs.chmodSync(binary, 0o755) } catch { /* a read-only install keeps its mode */ }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// A stop request must reach the binary: forward it, never orphan the child.
|
|
139
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' })
|
|
140
|
+
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
141
|
+
process.on(sig, () => { child.kill(sig) })
|
|
142
|
+
}
|
|
143
|
+
child.on('error', (error) => {
|
|
144
|
+
console.error(NAME + ': ' + error.message)
|
|
145
|
+
process.exit(1)
|
|
146
|
+
})
|
|
147
|
+
child.on('exit', (code, signal) => {
|
|
148
|
+
if (signal) {
|
|
149
|
+
process.removeAllListeners(signal)
|
|
150
|
+
process.kill(process.pid, signal)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
process.exit(code ?? 1)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { resolveBinary, probe }
|
|
158
|
+
|
|
159
|
+
if (require.main === module) {
|
|
160
|
+
resolveBinary(realDeps()).then((binary) => {
|
|
161
|
+
if (binary) launch(binary)
|
|
162
|
+
else process.exit(1)
|
|
163
|
+
})
|
|
44
164
|
}
|
|
45
|
-
process.exit(result.status ?? 1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monosmith/cli",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.41",
|
|
4
4
|
"description": "Runs a monorepo in Node, Go, Python, Rust, .NET, Ruby, PHP, C++, Dart, the JVM, Bicep, Lua, AppleScript, VBA, Swift and Objective-C: tasks from a cache, CI and releases",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/monosmith-app/monosmith",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"node": "\u003e=18"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|
|
21
|
-
"@monosmith/cli-darwin-arm64": "2.0.0-beta.
|
|
22
|
-
"@monosmith/cli-darwin-x64": "2.0.0-beta.
|
|
23
|
-
"@monosmith/cli-linux-arm64": "2.0.0-beta.
|
|
24
|
-
"@monosmith/cli-linux-x64": "2.0.0-beta.
|
|
25
|
-
"@monosmith/cli-win32-arm64": "2.0.0-beta.
|
|
26
|
-
"@monosmith/cli-win32-x64": "2.0.0-beta.
|
|
21
|
+
"@monosmith/cli-darwin-arm64": "2.0.0-beta.41",
|
|
22
|
+
"@monosmith/cli-darwin-x64": "2.0.0-beta.41",
|
|
23
|
+
"@monosmith/cli-linux-arm64": "2.0.0-beta.41",
|
|
24
|
+
"@monosmith/cli-linux-x64": "2.0.0-beta.41",
|
|
25
|
+
"@monosmith/cli-win32-arm64": "2.0.0-beta.41",
|
|
26
|
+
"@monosmith/cli-win32-x64": "2.0.0-beta.41"
|
|
27
27
|
}
|
|
28
28
|
}
|