fix(ptc-runtime-node): correct Script await handling and guidance
This commit is contained in:
1 parent
c3473d1ea6
commit
e82dddc45c
10 files changed
+71
-61
No files matched your search
@@ -39,8 +39,8 @@
|
||||
en: 53e24b6ba2614d4c
|
||||
zh: 0fe4d5ace9d2ef75
|
||||
/deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-and-built-bootstraps:
|
||||
en: 3e30860547f88777
|
||||
zh: d7ba84b1f88c3fb2
|
||||
en: 735eb081e90834a6
|
||||
zh: a657d9ef17be9871
|
||||
/deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-map:
|
||||
en: a797a875fbfb0598
|
||||
zh: 0cc0b19f8241f6bc
|
||||
@@ -54,8 +54,8 @@
|
||||
en: 40294c1c31b74824
|
||||
zh: d91e7b78c0619c7e
|
||||
/deepseek-ai-dsh-ptc-runtime-node/known-limitations-and-deferred-work:
|
||||
en: f4c6a8a3e0b1220f
|
||||
zh: 03fb9482097eabe7
|
||||
en: 5a2f3b56db7cb92a
|
||||
zh: c3eb766cbefc5918
|
||||
/deepseek-ai-dsh-ptc-runtime-node/known-limitations-and-deferred-work/dev-note:
|
||||
en: 6373aba4e08f7205
|
||||
zh: 2a8c33d1f2839df4
|
||||
@@ -127,9 +127,9 @@ Length-framed JSON travels separately from stdout/stderr. Every evaluation carri
|
||||
|
||||
### Source and built bootstraps
|
||||
|
||||
Source execution loads an erasable-only bootstrap closure without relying on sibling built exports. Built script execution uses `process.js` for every mode; `persistent-process.js` delegates to the same loop with a REPL default for hosts that omit program syntax. Persistent mode requires a `node-script` launch. A remote script deployment supplies the matching `launch.bootstrapPath`. Embedded carriers launch their private worker through `launch.executable`. `resolveLaunch()` selects the local default during construction; an explicit remote invocation never depends on whether the client itself is packaged. A host path is never assumed to name the same remote file.
|
||||
Source execution loads an erasable-only bootstrap closure without relying on sibling built exports; its evaluator modules and installed Acorn dependencies must be available in the execution world. Built script execution uses `process.js` for every mode; `persistent-process.js` delegates to the same loop with a REPL default for hosts that omit program syntax. Persistent mode requires a `node-script` launch. A remote script deployment supplies the matching `launch.bootstrapPath`. Embedded carriers launch their private worker through `launch.executable`. `resolveLaunch()` selects the local default during construction; an explicit remote invocation never depends on whether the client itself is packaged. A host path is never assumed to name the same remote file.
|
||||
|
||||
Scripts accepted by the native compiler execute unchanged. When compilation requires top-level await support, Acorn parses an async wrapper around the source. The transform follows Node's REPL visitors, declaration hoisting and character-position edits, including its boxed final-expression result; the evaluator unwraps that result and awaits its value. A local AST walker keeps Acorn as the only added dependency. Dynamic `import()` expressions remain native and use the compiling Script's `importModuleDynamically` option. Both private process bundles include Acorn; source execution imports the package directly.
|
||||
Source containing `await` is checked for top-level await before native compilation, which can otherwise treat `await` as an identifier. Acorn parses an async wrapper, and acorn-walk traverses it using Node's REPL visitors, declaration hoisting and character-position edits. Computed class-method keys are traversed while method bodies remain separate evaluations. The transform boxes the final expression; the evaluator unwraps and awaits its value. Source that needs no lowering uses native compilation; submissions without `await` retain native syntax diagnostics. Dynamic `import()` expressions remain native and use the compiling Script's `importModuleDynamically` option. Both private process bundles include Acorn and acorn-walk; source execution imports the installed packages.
|
||||
|
||||
### Source map
|
||||
|
||||
@@ -188,6 +188,8 @@ These limits qualify the execution guarantees and retained output.
|
||||
- **Cleanup inherits subprocess observability** — escaped descendants on a fallback platform may remain outside the managed range; see the subprocess provider's stated limits.
|
||||
- **Default execution is one-shot** — retained state requires `persistent-in-memory` mode or Script mode with `scriptPersistence: true`; no mode offers a yield/wait API.
|
||||
- **Await lowering relaxes declaration semantics** — in transformed programs, top-level `let`, `const`, and `class` bindings are mutable and initialized to `undefined` before execution; the native temporal dead zone does not apply. Function declarations populate retained globals when execution reaches them, and a `var` declaration without an initializer resets its binding to `undefined`. Direct-eval declarations stay within that evaluation. Top-level `using` declarations are rejected; put disposable bindings inside a block. Runtime stack locations refer to the transformed source. Scripts that compile without lowering keep native semantics.
|
||||
- **Failed native lexical initialization can reserve a name** — if a top-level `let`, `const`, or `class` initializer throws before initializing its binding, that name remains uninitialized in the retained Script context. Reading, assigning or redeclaring it fails; use a new name or release and recreate the environment.
|
||||
- **Script values can cross realms** — binding results, binding rejection classes and imported modules can use constructors from outside the Script context. `instanceof Array` or `instanceof Error` can be false for those values; use `Array.isArray` and the declared binding error class.
|
||||
- **In-memory environments retain one process per live owner** — idle owners keep their process and heap until explicit release, owner disposal or failure. Fan-out can retain many processes; idle expiration remains deferred.
|
||||
- **Output caps reject rather than retain every byte** — spill can preserve only the bounded result delivered by this provider.
|
||||
- **Bindings are bounded at transport admission** — control limits do not bound the memory a host binding allocates while producing its result.
|
||||
|
||||
@@ -127,9 +127,9 @@ Host 擦除可擦除类型;需要启动进程时,在配置的执行世界中
|
||||
|
||||
### 源代码与构建后 bootstrap
|
||||
|
||||
源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出。构建后脚本在所有模式下都使用 `process.js`;`persistent-process.js` 委托同一个循环,供未指定程序语法的宿主默认使用 REPL。保留模式要求 `node-script` 启动方式。远端脚本部署提供对应的 `launch.bootstrapPath`。内嵌载体通过 `launch.executable` 启动私有 worker。`resolveLaunch()` 在构造时选择本地默认值;显式的远端调用不依赖客户端自身是否打包。不会假设 Host 路径对应同一个远程文件。
|
||||
源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出;执行世界中必须提供求值器模块和已安装的 Acorn 依赖。构建后脚本在所有模式下都使用 `process.js`;`persistent-process.js` 委托同一个循环,供未指定程序语法的宿主默认使用 REPL。保留模式要求 `node-script` 启动方式。远端脚本部署提供对应的 `launch.bootstrapPath`。内嵌载体通过 `launch.executable` 启动私有 worker。`resolveLaunch()` 在构造时选择本地默认值;显式的远端调用不依赖客户端自身是否打包。不会假设 Host 路径对应同一个远程文件。
|
||||
|
||||
原生编译器接受的脚本直接执行。当编译需要顶层 await 支持时,Acorn 解析包住源码的异步函数。转换沿用 Node REPL 的 visitor、声明提升和按字符位置修改源码的方式,包括对末尾表达式结果的包装;求值器解包后等待该值。局部 AST 遍历器使新增依赖仍只有 Acorn。动态 `import()` 表达式保持原生形式,使用编译该 Script 时的 `importModuleDynamically` 选项。两个私有进程 bundle 都包含 Acorn;源码执行直接导入该包。
|
||||
包含 `await` 的源码会在原生编译前检查顶层 await,否则原生编译可能将 `await` 当作标识符。Acorn 解析包住源码的异步函数,acorn-walk 沿用 Node REPL 的 visitor、声明提升和按字符位置修改源码的方式进行遍历。类方法的计算属性名参与遍历,方法体仍独立求值。转换包装末尾表达式,求值器解包并等待其值。无需转换的源码使用原生编译;不含 `await` 的提交保留原生语法诊断。动态 `import()` 表达式保持原生形式,使用编译该 Script 时的 `importModuleDynamically` 选项。两个私有进程 bundle 都包含 Acorn 和 acorn-walk;源码执行导入已安装的包。
|
||||
|
||||
### 源码索引
|
||||
|
||||
@@ -188,6 +188,8 @@ Host 擦除可擦除类型;需要启动进程时,在配置的执行世界中
|
||||
- **清理继承子进程的可观测范围**——使用 fallback 的平台上,逃逸的后代可能仍在受管范围之外;参见子进程提供方声明的限制。
|
||||
- **默认执行是一次性的**:保留状态需要设置 `persistent-in-memory` 模式,或在 Script 模式下启用 `scriptPersistence: true`;所有模式都不提供 yield/wait API。
|
||||
- **await 转换放宽声明语义**——转换后程序的顶层 `let`、`const` 和 `class` 绑定可修改,且在执行前初始化为 `undefined`,不再具有原生暂时性死区。执行到函数声明时才会为其设置保留的全局变量;没有初始化器的 `var` 声明会将其绑定重置为 `undefined`。直接 eval 中的声明仅在本次求值内有效。不接受顶层 `using` 声明;可释放绑定需放在块内。运行时堆栈位置指向转换后的源码。无需转换即可编译的脚本保持原生语义。
|
||||
- **原生词法初始化失败可能占用名称**——顶层 `let`、`const` 或 `class` 的初始化器在完成绑定初始化前抛错时,该名称会在保留的 Script context 中持续处于未初始化状态。读取、赋值和重新声明都会失败;应使用新名称,或释放并重新创建环境。
|
||||
- **Script 值可能跨 realm**——绑定结果、绑定拒绝类和导入模块可能使用 Script context 之外的构造器。这些值的 `instanceof Array` 或 `instanceof Error` 可能为 false;应使用 `Array.isArray` 和声明的绑定错误类。
|
||||
- **内存保留环境为每个存活所有者保留一个进程**——空闲所有者保留进程及堆内存,直到显式释放、所有者销毁或执行失败。并行创建多个所有者会保留多个进程;空闲过期机制留待后续处理。
|
||||
- **输出上限拒绝超量内容,而不保留每个字节**——溢出只能保存本提供方交付的有界结果。
|
||||
- **绑定在传输接纳时受限**——控制限制不约束 Host 绑定生成结果期间分配的内存。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-ptc-runtime-node",
|
||||
"description": "Node PTC execution with per-call sandboxed processes or in-memory REPL state",
|
||||
"description": "Node PTC execution with async function, REPL and VM Script evaluators and optional retained state",
|
||||
"version": "0.2.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -46,7 +46,8 @@
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@deepseek-ai/schemastery": "workspace:~",
|
||||
"acorn": "^8.18.0"
|
||||
"acorn": "^8.18.0",
|
||||
"acorn-walk": "^8.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Node REPL top-level-await transformation adapted for complete Script submissions.
|
||||
* Source: Node.js v26.6.0, lib/internal/repl/await.js:
|
||||
* https://github.com/nodejs/node/blob/v26.6.0/lib/internal/repl/await.js
|
||||
* Uses public Acorn and a local walker; incomplete input is a SyntaxError.
|
||||
* Uses public Acorn and acorn-walk; incomplete input is a SyntaxError.
|
||||
*/
|
||||
/*! @license
|
||||
* Copyright Node.js contributors. All rights reserved.
|
||||
@@ -27,11 +27,7 @@
|
||||
*/
|
||||
import { Parser } from 'acorn'
|
||||
import type { AnyNode, BlockStatement, Pattern, Position, Program, Statement, VariableDeclarator } from 'acorn'
|
||||
|
||||
type Walk = (node: AnyNode, state: TransformState) => void
|
||||
type Visitor<K extends AnyNode['type'] = AnyNode['type']> = (
|
||||
node: Extract<AnyNode, { type: K }>, state: TransformState, c: Walk,
|
||||
) => void
|
||||
import * as walk from 'acorn-walk'
|
||||
|
||||
interface TransformState {
|
||||
body: BlockStatement
|
||||
@@ -49,7 +45,8 @@ function isTopLevelDeclaration(state: TransformState): boolean {
|
||||
}
|
||||
|
||||
const noop = (): void => {}
|
||||
const visitorsWithoutAncestors: { [K in AnyNode['type']]?: Visitor<K> } = {
|
||||
const base = walk.base as Required<walk.RecursiveVisitors<TransformState>>
|
||||
const visitorsWithoutAncestors: walk.RecursiveVisitors<TransformState> = {
|
||||
ClassDeclaration(node, state, c) {
|
||||
if (node.id === null) throw new SyntaxError('Script declarations must be named')
|
||||
if (isTopLevelDeclaration(state)) {
|
||||
@@ -57,13 +54,13 @@ const visitorsWithoutAncestors: { [K in AnyNode['type']]?: Visitor<K> } = {
|
||||
state.hoistedDeclarationStatements.push(`let ${node.id.name}; `)
|
||||
}
|
||||
|
||||
visitChildren(node, state, c)
|
||||
base.ClassDeclaration(node, state, c)
|
||||
},
|
||||
ForOfStatement(node, state, c) {
|
||||
if (node.await) {
|
||||
state.containsAwait = true
|
||||
}
|
||||
visitChildren(node, state, c)
|
||||
base.ForOfStatement(node, state, c)
|
||||
},
|
||||
FunctionDeclaration(node, state) {
|
||||
if (node.id === null) throw new SyntaxError('Script declarations must be named')
|
||||
@@ -72,15 +69,18 @@ const visitorsWithoutAncestors: { [K in AnyNode['type']]?: Visitor<K> } = {
|
||||
},
|
||||
FunctionExpression: noop,
|
||||
ArrowFunctionExpression: noop,
|
||||
MethodDefinition: noop,
|
||||
MethodDefinition(node, state, c) {
|
||||
// Computed keys execute while the class is defined; method bodies do not.
|
||||
if (node.computed) c(node.key, state)
|
||||
},
|
||||
StaticBlock: noop,
|
||||
AwaitExpression(node, state, c) {
|
||||
state.containsAwait = true
|
||||
visitChildren(node, state, c)
|
||||
base.AwaitExpression(node, state, c)
|
||||
},
|
||||
ReturnStatement(node, state, c) {
|
||||
state.containsReturn = true
|
||||
visitChildren(node, state, c)
|
||||
base.ReturnStatement(node, state, c)
|
||||
},
|
||||
VariableDeclaration(node, state, c) {
|
||||
const variableKind = node.kind
|
||||
@@ -155,33 +155,24 @@ const visitorsWithoutAncestors: { [K in AnyNode['type']]?: Visitor<K> } = {
|
||||
})
|
||||
}
|
||||
|
||||
visitChildren(node, state, c)
|
||||
base.VariableDeclaration(node, state, c)
|
||||
},
|
||||
}
|
||||
|
||||
/** Acorn's child nodes provide the recursive traversal without an acorn-walk dependency. */
|
||||
function visitChildren(node: AnyNode, state: TransformState, c: Walk): void {
|
||||
const visit = (value: unknown): void => {
|
||||
if (value !== null && typeof value === 'object' && 'type' in value) c(value as AnyNode, state)
|
||||
}
|
||||
for (const value of Object.values(node as AnyNode & Record<string, unknown>)) {
|
||||
if (Array.isArray(value)) value.forEach(visit)
|
||||
else visit(value)
|
||||
}
|
||||
}
|
||||
|
||||
function recursive(node: AnyNode, state: TransformState): void {
|
||||
// Indexing by node.type selects the visitor accepting this exact Acorn node type.
|
||||
const callback = (visitorsWithoutAncestors[node.type] ?? visitChildren) as Visitor
|
||||
const isNew = node !== state.ancestors[state.ancestors.length - 1]
|
||||
if (isNew) {
|
||||
state.ancestors.push(node)
|
||||
}
|
||||
callback(node, state, recursive)
|
||||
if (isNew) {
|
||||
state.ancestors.pop()
|
||||
}
|
||||
}
|
||||
type Visitor = (node: AnyNode, state: TransformState, c: walk.WalkerCallback<TransformState>) => void
|
||||
const visitors: walk.RecursiveVisitors<TransformState> = Object.fromEntries(
|
||||
Object.entries({ ...base, ...visitorsWithoutAncestors }).map(([type, visitor]) => {
|
||||
// acorn-walk selects the callback by the matching node or aggregate type.
|
||||
const callback = visitor as Visitor
|
||||
const withAncestors: Visitor = (node, state, c) => {
|
||||
const isNew = node !== state.ancestors[state.ancestors.length - 1]
|
||||
if (isNew) state.ancestors.push(node)
|
||||
callback(node, state, c)
|
||||
if (isNew) state.ancestors.pop()
|
||||
}
|
||||
return [type, withAncestors]
|
||||
}),
|
||||
)
|
||||
|
||||
/** Keep the native-Node bootstrap independent of workspace packages' built exports. */
|
||||
function assertNever(value: never): never {
|
||||
@@ -205,7 +196,6 @@ export function processTopLevelAwait(src: string): string | null {
|
||||
if (!(error instanceof SyntaxError)) throw error
|
||||
// Acorn syntax errors include offsets and positions; batches cannot request more REPL input.
|
||||
const e = error as SyntaxError & { pos: number; loc: Position }
|
||||
if (e.message.startsWith('Unterminated ')) throw e
|
||||
// If the parse error is before the first "await", then use the execution
|
||||
// error. Otherwise we must emit this parse error, making it look like a
|
||||
// proper syntax error.
|
||||
@@ -253,7 +243,7 @@ export function processTopLevelAwait(src: string): string | null {
|
||||
containsReturn: false,
|
||||
}
|
||||
|
||||
recursive(body, state)
|
||||
walk.recursive(body, state, visitors)
|
||||
|
||||
// Do not transform if
|
||||
// 1. False alarm: there isn't actually an await expression.
|
||||
|
||||
@@ -67,16 +67,18 @@ async function evaluateScript(code: string, context: Context): Promise<unknown>
|
||||
}
|
||||
}
|
||||
|
||||
/** Native scripts bypass conversion; only compilation failures enter the await transformer. */
|
||||
/** Detect top-level await before native compilation can interpret await as an identifier. */
|
||||
function compileScript(code: string, options: ScriptOptions): { script: Script; transformed: boolean } {
|
||||
try {
|
||||
return { script: new Script(code, options), transformed: false }
|
||||
} catch (error: unknown) {
|
||||
if (!(error instanceof SyntaxError)) throw error
|
||||
if (code.includes('await')) {
|
||||
// Complete submissions need no REPL continuation; the newline terminates trailing comments.
|
||||
const source = code.startsWith('#!') ? `//${code.slice(2)}` : code
|
||||
const transformed = processTopLevelAwait(`${source}\n`)
|
||||
if (transformed === null) throw error
|
||||
return { script: new Script(transformed, options), transformed: true }
|
||||
let transformed: string | null
|
||||
try { transformed = processTopLevelAwait(`${source}\n`) } catch (error: unknown) {
|
||||
// Parser diagnostics describe the program; transformer stack frames do not.
|
||||
if (error instanceof SyntaxError) error.stack = `${error.name}: ${error.message}`
|
||||
throw error
|
||||
}
|
||||
if (transformed !== null) return { script: new Script(transformed, options), transformed: true }
|
||||
}
|
||||
return { script: new Script(code, options), transformed: false }
|
||||
}
|
||||
@@ -166,11 +166,13 @@ function resolveMode(config: ResolvedConfig): ExecutionMode {
|
||||
const lifetime = config.scriptPersistence
|
||||
? 'Variables, functions, and imported modules remain in memory for this Agent across calls.'
|
||||
: 'Each call runs in a fresh Node process.'
|
||||
const recovery = config.scriptPersistence ? ' ' + RETAINED_STATE_INSTRUCTIONS : ''
|
||||
const recovery = config.scriptPersistence
|
||||
? ' ' + RETAINED_STATE_INSTRUCTIONS + ' If a native top-level let, const, or class initializer throws, its name may remain permanently uninitialized; use a new name or restart the environment.'
|
||||
: ''
|
||||
return {
|
||||
format: 'script',
|
||||
retainsState: config.scriptPersistence,
|
||||
executionInstructions: 'The program contains TypeScript script statements; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level declarations and await are supported; top-level return is not supported. Emit output with console.log(...) or a final lossless JSON expression. The final expression is awaited if it is a Promise. Only printed values and that final expression become program output. ' + lifetime + ' Use await import(...) for modules. Relative paths use the supplied working directory. Direct filesystem access follows this execution\'s sandbox policy; process.env starts empty.' + recovery,
|
||||
executionInstructions: 'The program contains TypeScript script statements; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level declarations and await are supported; top-level return is not supported. Emit output with console.log(...) or a final lossless JSON expression. The final expression is awaited if it is a Promise. Only printed values and that final expression become program output. ' + lifetime + ' Use await import(...) for modules. Relative paths use the supplied working directory. Direct filesystem access follows this execution\'s sandbox policy; process.env starts empty. Await asynchronous work: an uncaught callback exception or unhandled rejection terminates the process and clears its variables. Values from bindings and imports can belong to another realm; use Array.isArray for arrays and the supplied binding error classes for failures, because instanceof Array or Error may be false.' + recovery,
|
||||
stripTypes: stripTypeScriptTypes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Lossless-JSON snapshots for the dependency-free source bootstrap closure.
|
||||
* Lossless-JSON snapshots without workspace runtime imports in the source bootstrap.
|
||||
* @module @deepseek-ai/dsh-ptc-runtime-node/json-wire
|
||||
*/
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ import { defineConfig } from 'tsdown'
|
||||
/** The Node provider and its private process bootstraps are published bundles. */
|
||||
export default defineConfig([
|
||||
{ entry: ['lib/types/index.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false },
|
||||
{ entry: { process: 'lib/types/process-entry.js' }, outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false, noExternal: ['@deepseek-ai/dsh-subprocess/control', 'acorn'] },
|
||||
{ entry: { 'persistent-process': 'lib/types/persistent-process-entry.js' }, outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false, noExternal: ['@deepseek-ai/dsh-subprocess/control', 'acorn'] },
|
||||
{ entry: { process: 'lib/types/process-entry.js' }, outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false, noExternal: ['@deepseek-ai/dsh-subprocess/control', 'acorn', 'acorn-walk'] },
|
||||
{ entry: { 'persistent-process': 'lib/types/persistent-process-entry.js' }, outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false, noExternal: ['@deepseek-ai/dsh-subprocess/control', 'acorn', 'acorn-walk'] },
|
||||
])
|
||||
Generated
+11
@@ -12117,6 +12117,9 @@ importers:
|
||||
acorn:
|
||||
specifier: ^8.18.0
|
||||
version: 8.18.0
|
||||
acorn-walk:
|
||||
specifier: ^8.3.5
|
||||
version: 8.3.5
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
specifier: workspace:~
|
||||
@@ -19933,6 +19936,10 @@ packages:
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -27374,6 +27381,10 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.17.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
Reference in new issue
Block a user