ReactFlow前端拖拽模型与参数配置功能MVP

This commit is contained in:
ljz committed 2026-07-11 10:49:11 +08:00
1 parent 70c91ed019
commit d3b99304fb
2001 files changed
+349044 -548

No files matched your search

+72
View File
@@ -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
View File
@@ -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
@@ -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)
}
@@ -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)
},
})
}