ReactFlow前端拖拽模型与参数配置功能MVP
This commit is contained in:
1 parent
70c91ed019
commit
d3b99304fb
2001 files changed
+349044
-548
No files matched your search
+72
@@ -0,0 +1,72 @@
|
||||
// Separated out for easier unit testing
|
||||
module.exports = async (process, validateEngines) => {
|
||||
// set it here so that regardless of what happens later, we don't leak any private CLI configs to other programs
|
||||
process.title = 'npm'
|
||||
|
||||
// Patch the global fs module here at the app level
|
||||
require('graceful-fs').gracefulify(require('node:fs'))
|
||||
|
||||
const satisfies = require('semver/functions/satisfies')
|
||||
const ExitHandler = require('./exit-handler.js')
|
||||
const exitHandler = new ExitHandler({ process })
|
||||
const Npm = require('../npm.js')
|
||||
const npm = new Npm()
|
||||
exitHandler.setNpm(npm)
|
||||
|
||||
// only log node and npm paths in argv initially since argv can contain sensitive info. a cleaned version will be logged later
|
||||
const { log, output } = require('proc-log')
|
||||
log.verbose('cli', process.argv.slice(0, 2).join(' '))
|
||||
log.info('using', 'npm@%s', npm.version)
|
||||
log.info('using', 'node@%s', process.version)
|
||||
|
||||
// At this point we've required a few files and can be pretty sure we don't contain invalid syntax for this version of node.
|
||||
// It's possible a lazy require would, but that's unlikely enough that it's not worth catching anymore and we attach the more important exit handlers.
|
||||
validateEngines.off()
|
||||
exitHandler.registerUncaughtHandlers()
|
||||
|
||||
// It is now safe to log a warning if they are using a version of node that is not going to fail on syntax errors but is still unsupported and untested and might not work reliably.
|
||||
// This is safe to use the logger now which we want since this will show up in the error log too.
|
||||
if (!satisfies(validateEngines.node, validateEngines.engines)) {
|
||||
log.warn('cli', validateEngines.unsupportedMessage)
|
||||
}
|
||||
|
||||
// Now actually fire up npm and run the command.
|
||||
// This is how to use npm programmatically:
|
||||
try {
|
||||
const { exec, command, args } = await npm.load()
|
||||
|
||||
if (!exec) {
|
||||
return exitHandler.exit()
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
output.standard(npm.usage)
|
||||
process.exitCode = 1
|
||||
return exitHandler.exit()
|
||||
}
|
||||
|
||||
// Options are prefixed by a hyphen-minus (-, \u2d).
|
||||
// Other dash-type chars look similar but are invalid.
|
||||
const nonDashArgs = npm.argv.filter(a => /^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/.test(a))
|
||||
if (nonDashArgs.length) {
|
||||
log.error(
|
||||
'arg',
|
||||
'Argument starts with non-ascii dash, this is probably invalid:',
|
||||
require('@npmcli/redact').redactLog(nonDashArgs.join(', '))
|
||||
)
|
||||
}
|
||||
|
||||
const execPromise = npm.exec(command, args)
|
||||
|
||||
// this is async but we don't await it, since its ok if it doesnt finish before the command finishes running.
|
||||
// it uses command and argv so it must be initiated here, after the command name is set
|
||||
const updateNotifier = require('./update-notifier.js')
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
updateNotifier(npm).then((msg) => (npm.updateNotification = msg))
|
||||
|
||||
await execPromise
|
||||
return exitHandler.exit()
|
||||
} catch (err) {
|
||||
return exitHandler.exit(err)
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
const { log, output, META } = require('proc-log')
|
||||
const { errorMessage, getExitCodeFromError } = require('../utils/error-message.js')
|
||||
|
||||
class ExitHandler {
|
||||
#npm = null
|
||||
#process = null
|
||||
#exited = false
|
||||
#exitErrorMessage = false
|
||||
|
||||
#noNpmError = false
|
||||
|
||||
get #hasNpm () {
|
||||
return !!this.#npm
|
||||
}
|
||||
|
||||
get #loaded () {
|
||||
return !!this.#npm?.loaded
|
||||
}
|
||||
|
||||
get #showExitErrorMessage () {
|
||||
if (!this.#loaded) {
|
||||
return false
|
||||
}
|
||||
if (!this.#exited) {
|
||||
return true
|
||||
}
|
||||
return this.#exitErrorMessage
|
||||
}
|
||||
|
||||
get #notLoadedOrExited () {
|
||||
return !this.#loaded && !this.#exited
|
||||
}
|
||||
|
||||
setNpm (npm) {
|
||||
this.#npm = npm
|
||||
}
|
||||
|
||||
constructor ({ process }) {
|
||||
this.#process = process
|
||||
this.#process.on('exit', this.#handleProcessExitAndReset)
|
||||
}
|
||||
|
||||
registerUncaughtHandlers () {
|
||||
this.#process.on('uncaughtException', this.#handleExit)
|
||||
this.#process.on('unhandledRejection', this.#handleExit)
|
||||
}
|
||||
|
||||
exit (err) {
|
||||
this.#handleExit(err)
|
||||
}
|
||||
|
||||
#handleProcessExitAndReset = (code) => {
|
||||
this.#handleProcessExit(code)
|
||||
|
||||
// Reset all the state. This is only relevant for tests since in reality the process fully exits here.
|
||||
this.#process.off('exit', this.#handleProcessExitAndReset)
|
||||
this.#process.off('uncaughtException', this.#handleExit)
|
||||
this.#process.off('unhandledRejection', this.#handleExit)
|
||||
if (this.#loaded) {
|
||||
this.#npm.unload()
|
||||
}
|
||||
this.#npm = null
|
||||
this.#exited = false
|
||||
this.#exitErrorMessage = false
|
||||
}
|
||||
|
||||
#handleProcessExit (code) {
|
||||
const numCode = Number(code) || 0
|
||||
// Always exit w/ a non-zero code if exit handler was not called
|
||||
const exitCode = this.#exited ? numCode : (numCode || 1)
|
||||
this.#process.exitCode = exitCode
|
||||
|
||||
if (this.#notLoadedOrExited) {
|
||||
// Exit handler was not called and npm was not loaded so we have to log something
|
||||
this.#logConsoleError(new Error(`Process exited unexpectedly with code: ${exitCode}`))
|
||||
return
|
||||
}
|
||||
|
||||
if (this.#logNoNpmError()) {
|
||||
return
|
||||
}
|
||||
|
||||
const os = require('node:os')
|
||||
log.verbose('cwd', this.#process.cwd())
|
||||
log.verbose('os', `${os.type()} ${os.release()}`)
|
||||
log.verbose('node', this.#process.version)
|
||||
log.verbose('npm ', `v${this.#npm.version}`)
|
||||
|
||||
// only show the notification if it finished
|
||||
if (typeof this.#npm.updateNotification === 'string') {
|
||||
log.notice('', this.#npm.updateNotification, { [META]: true, force: true })
|
||||
}
|
||||
|
||||
if (!this.#exited) {
|
||||
log.error('', 'Exit handler never called!')
|
||||
log.error('', 'This is an error with npm itself. Please report this error at:')
|
||||
log.error('', ' <https://github.com/npm/cli/issues>')
|
||||
if (this.#npm.silent) {
|
||||
output.error('')
|
||||
}
|
||||
}
|
||||
|
||||
log.verbose('exit', exitCode)
|
||||
|
||||
if (exitCode) {
|
||||
log.verbose('code', exitCode)
|
||||
} else {
|
||||
log.info('ok')
|
||||
}
|
||||
|
||||
if (this.#showExitErrorMessage) {
|
||||
log.error('', this.#npm.exitErrorMessage())
|
||||
}
|
||||
}
|
||||
|
||||
#logConsoleError (err) {
|
||||
// Run our error message formatters on all errors even if we have no npm or an unloaded npm.
|
||||
// This will clean the error and possible return a formatted message about EACCESS or something.
|
||||
const { summary, detail } = errorMessage(err, this.#npm)
|
||||
const formatted = [...new Set([...summary, ...detail].flat().filter(Boolean))].join('\n')
|
||||
// If we didn't get anything from the formatted message then just display the full stack
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(formatted === err.message ? err.stack : formatted)
|
||||
}
|
||||
|
||||
#logNoNpmError (err) {
|
||||
if (this.#hasNpm) {
|
||||
return false
|
||||
}
|
||||
// Make sure we only log this error once
|
||||
if (!this.#noNpmError) {
|
||||
this.#noNpmError = true
|
||||
this.#logConsoleError(
|
||||
new Error(`Exit prior to setting npm in exit handler`, err ? { cause: err } : {})
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
#handleExit = (err) => {
|
||||
this.#exited = true
|
||||
|
||||
// No npm at all
|
||||
if (this.#logNoNpmError(err)) {
|
||||
return this.#process.exit(this.#process.exitCode || getExitCodeFromError(err) || 1)
|
||||
}
|
||||
|
||||
// npm was never loaded but we still might have a config loading error or something similar that we can run through the error message formatter to give the user a clue as to what happened.
|
||||
if (!this.#loaded) {
|
||||
this.#logConsoleError(new Error('Exit prior to config file resolving', { cause: err }))
|
||||
return this.#process.exit(this.#process.exitCode || getExitCodeFromError(err) || 1)
|
||||
}
|
||||
|
||||
this.#exitErrorMessage = err?.suppressError === true ? false : !!err
|
||||
|
||||
// Prefer the exit code of the error, then the current process exit code, then set it to 1 if we still have an error.
|
||||
// Otherwise, we call process.exit with undefined so that it can determine the final exit code
|
||||
const exitCode = err?.exitCode ?? this.#process.exitCode ?? (err ? 1 : undefined)
|
||||
|
||||
// explicitly call process.exit now so we don't hang on things like the update notifier
|
||||
// also flush stdout/err beforehand because process.exit doesn't wait for that to happen.
|
||||
this.#process.stderr.write('', () => this.#process.stdout.write('', () => {
|
||||
this.#process.exit(exitCode)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExitHandler
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// print a banner telling the user to upgrade npm to latest but not in CI, and not if we're doing that already.
|
||||
|
||||
const ciInfo = require('ci-info')
|
||||
const gt = require('semver/functions/gt')
|
||||
const gte = require('semver/functions/gte')
|
||||
const parse = require('semver/functions/parse')
|
||||
const { stat, writeFile } = require('node:fs/promises')
|
||||
const { resolve } = require('node:path')
|
||||
|
||||
// update check frequency
|
||||
const DAILY = 1000 * 60 * 60 * 24
|
||||
const WEEKLY = DAILY * 7
|
||||
|
||||
// don't put it in the _cacache folder, just in npm's cache
|
||||
const lastCheckedFile = npm => resolve(npm.flatOptions.cache, '../_update-notifier-last-checked')
|
||||
|
||||
// Actual check for updates. This is a separate function so that we only load this if we are doing the actual update
|
||||
const updateCheck = async (npm, spec, version, current) => {
|
||||
const pacote = require('pacote')
|
||||
|
||||
const mani = await pacote.manifest(`npm@${spec}`, {
|
||||
// always prefer latest, even if doing --tag=whatever on the cmd
|
||||
defaultTag: 'latest',
|
||||
...npm.flatOptions,
|
||||
cache: false,
|
||||
}).catch(() => null)
|
||||
|
||||
// if pacote failed, give up
|
||||
if (!mani) {
|
||||
return null
|
||||
}
|
||||
|
||||
const latest = mani.version
|
||||
|
||||
// if the current version is *greater* than latest, we're on a 'next' and should get the updates from that release train.
|
||||
// Note that this isn't another http request over the network, because the packument will be cached by pacote from previous request.
|
||||
if (gt(version, latest) && spec === '*') {
|
||||
return updateNotifier(npm, `^${version}`)
|
||||
}
|
||||
|
||||
// if we already have something >= the desired spec, then we're done
|
||||
if (gte(version, latest)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const chalk = npm.logChalk
|
||||
|
||||
// ok! notify the user about this update they should get.
|
||||
// The message is saved for printing at process exit so it will not get lost in any other messages being printed as part of the command.
|
||||
const update = parse(mani.version)
|
||||
const type = update.major !== current.major ? 'major'
|
||||
: update.minor !== current.minor ? 'minor'
|
||||
: update.patch !== current.patch ? 'patch'
|
||||
: 'prerelease'
|
||||
const typec = type === 'major' ? 'red'
|
||||
: type === 'minor' ? 'yellow'
|
||||
: 'cyan'
|
||||
const message = [
|
||||
'',
|
||||
`New ${chalk[typec](type)} version of npm available! ${chalk[typec](current)} -> ${chalk.blue(latest)}`,
|
||||
`Changelog: ${chalk.blue(`https://github.com/npm/cli/releases/tag/v${latest}`)}`,
|
||||
`To update run: ${chalk.underline(`npm install -g npm@${latest}`)}`,
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
const updateNotifier = async (npm, spec = '*') => {
|
||||
// if we're on a prerelease train, then updates are coming fast check for a new one daily.
|
||||
// otherwise, weekly.
|
||||
const { version } = npm
|
||||
const current = parse(version)
|
||||
|
||||
// if we're on a beta train, always get the next beta
|
||||
if (current.prerelease.length) {
|
||||
spec = `^${version}`
|
||||
}
|
||||
|
||||
// while on a beta train, get updates daily
|
||||
const duration = current.prerelease.length ? DAILY : WEEKLY
|
||||
|
||||
const t = new Date(Date.now() - duration)
|
||||
// if we don't have a file, then definitely check it.
|
||||
const st = await stat(lastCheckedFile(npm)).catch(() => ({ mtime: t - 1 }))
|
||||
|
||||
// if we've already checked within the specified duration, don't check again
|
||||
if (!(t > st.mtime)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// intentional. do not await this. it's a best-effort update.
|
||||
// if this fails, it's ok.
|
||||
// might be using /dev/null as the cache or something weird like that.
|
||||
writeFile(lastCheckedFile(npm), '').catch(() => {})
|
||||
|
||||
return updateCheck(npm, spec, version, current)
|
||||
}
|
||||
|
||||
module.exports = async npm => {
|
||||
if (
|
||||
// opted out
|
||||
!npm.config.get('update-notifier')
|
||||
// global npm update
|
||||
|| (npm.flatOptions.global &&
|
||||
['install', 'update'].includes(npm.command) &&
|
||||
npm.argv.some(arg => /^npm(@|$)/.test(arg)))
|
||||
// CI
|
||||
|| ciInfo.isCI
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return updateNotifier(npm)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// This is separate to indicate that it should contain code we expect to work in all versions of node >= 6.
|
||||
// This is a best effort to catch syntax errors to give users a good error message if they are using a node version that doesn't allow syntax we are using such as private properties, etc.
|
||||
// This file is linted with ecmaVersion=6 so we don't use invalid syntax, which is set in the .eslintrc.local.json file
|
||||
|
||||
const { engines: { node: engines }, version } = require('../../package.json')
|
||||
const npm = `v${version}`
|
||||
|
||||
module.exports = (process, getCli) => {
|
||||
const node = process.version
|
||||
|
||||
const unsupportedMessage = `npm ${npm} does not support Node.js ${node}. This version of npm supports the following node versions: \`${engines}\`. You can find the latest version at https://nodejs.org/.`
|
||||
|
||||
const brokenMessage = `ERROR: npm ${npm} is known not to run on Node.js ${node}. This version of npm supports the following node versions: \`${engines}\`. You can find the latest version at https://nodejs.org/.`
|
||||
|
||||
// coverage ignored because this is only hit in very unsupported node versions and it's a best effort attempt to show something nice in those cases
|
||||
/* istanbul ignore next */
|
||||
const syntaxErrorHandler = (err) => {
|
||||
if (err instanceof SyntaxError) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`${brokenMessage}\n\nERROR:`)
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err)
|
||||
return process.exit(1)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
process.on('uncaughtException', syntaxErrorHandler)
|
||||
process.on('unhandledRejection', syntaxErrorHandler)
|
||||
|
||||
// require this only after setting up the error handlers
|
||||
const cli = getCli()
|
||||
return cli(process, {
|
||||
node,
|
||||
npm,
|
||||
engines,
|
||||
unsupportedMessage,
|
||||
off: () => {
|
||||
process.off('uncaughtException', syntaxErrorHandler)
|
||||
process.off('unhandledRejection', syntaxErrorHandler)
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user