@zhin.js/runtime 1.1.2 → 1.1.4
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/lib/config-composer.d.ts +564 -0
- package/lib/config-composer.js +1 -1
- package/lib/hmr-coordinator.js +48 -22
- package/lib/invalidation-planner.js +5 -4
- package/lib/module-dependency-index.d.ts +17 -0
- package/lib/module-dependency-index.js +403 -0
- package/lib/module-runtime.d.ts +6 -0
- package/lib/native-development-runtime.d.ts +6 -3
- package/lib/native-development-runtime.js +69 -18
- package/lib/plugin-scope-assembler.d.ts +4 -1
- package/lib/plugin-scope-assembler.js +12 -0
- package/lib/root-runtime.js +11 -9
- package/lib/source-ownership.d.ts +1 -0
- package/lib/source-ownership.js +9 -2
- package/lib/source-path.d.ts +4 -0
- package/lib/source-path.js +25 -0
- package/lib/subtree-generation-preparer.js +1 -0
- package/lib/topology-generation-preparer.js +1 -0
- package/lib/typescript-specifier-remap.d.ts +7 -9
- package/lib/typescript-specifier-remap.js +28 -3
- package/package.json +15 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationAdmissionGate, type GenerationHandoffRegistry, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
1
|
+
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationAdmissionGate, type GenerationHandoffRegistry, type RuntimeSnapshot, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { type RuntimeEnvironment } from './environment.js';
|
|
3
3
|
import { type EnvironmentLayers } from './environment-store.js';
|
|
4
4
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
@@ -10,6 +10,8 @@ export type PluginConfigResolver = (node: PluginGraphNode) => unknown;
|
|
|
10
10
|
export interface RootResourceContext {
|
|
11
11
|
/** Exact shadow generation being assembled; never inferred from a latest snapshot. */
|
|
12
12
|
readonly generation: number;
|
|
13
|
+
/** Exact projected candidate, available during handoff activation, never the committed previous generation. */
|
|
14
|
+
readonly readCandidateSnapshot?: () => RuntimeSnapshot;
|
|
13
15
|
readonly signal: AbortSignal;
|
|
14
16
|
readonly resources: Scope;
|
|
15
17
|
readonly lifecycle: DisposeStack;
|
|
@@ -51,6 +53,7 @@ export declare class PluginScopeAssembler {
|
|
|
51
53
|
setupTree(node: PluginGraphNode, signal: AbortSignal): Promise<void>;
|
|
52
54
|
synchronizeTree(node: PluginGraphNode): void;
|
|
53
55
|
createdScopeDisposers(): readonly (readonly [PluginId, Dispose])[];
|
|
56
|
+
bindCandidateSnapshot(snapshot: RuntimeSnapshot): void;
|
|
54
57
|
generationHandoff(): GenerationHandoff | undefined;
|
|
55
58
|
setupCapabilities(): readonly Readonly<SetupCapabilityRegistration>[];
|
|
56
59
|
/** Root resources are process-owned; every Plugin Scope receives an owner-bound facade. */
|
|
@@ -21,6 +21,7 @@ export class PluginScopeAssembler {
|
|
|
21
21
|
#handoffs = new GenerationHandoffStack();
|
|
22
22
|
#admission = createGenerationAdmissionGate();
|
|
23
23
|
#envStores;
|
|
24
|
+
#candidateSnapshot;
|
|
24
25
|
#setupFeatureAliases = new Map();
|
|
25
26
|
constructor(modules, configResolver, environment, primaryConfigDocument, generation, installResources, environmentLayers = {}, seed, isolation) {
|
|
26
27
|
this.modules = modules;
|
|
@@ -71,6 +72,11 @@ export class PluginScopeAssembler {
|
|
|
71
72
|
scope.provide(primaryConfigToken, config);
|
|
72
73
|
await this.installResources?.({
|
|
73
74
|
generation: this.generation,
|
|
75
|
+
readCandidateSnapshot: () => {
|
|
76
|
+
if (!this.#candidateSnapshot)
|
|
77
|
+
throw new Error('Candidate snapshot is not projected yet');
|
|
78
|
+
return this.#candidateSnapshot;
|
|
79
|
+
},
|
|
74
80
|
signal,
|
|
75
81
|
resources: scope,
|
|
76
82
|
lifecycle: scope.disposers,
|
|
@@ -206,6 +212,12 @@ export class PluginScopeAssembler {
|
|
|
206
212
|
return [owner, () => scope.disposers.dispose()];
|
|
207
213
|
});
|
|
208
214
|
}
|
|
215
|
+
bindCandidateSnapshot(snapshot) {
|
|
216
|
+
if (snapshot.generation !== this.generation || this.#candidateSnapshot) {
|
|
217
|
+
throw new Error('Candidate snapshot does not match this Plugin assembly');
|
|
218
|
+
}
|
|
219
|
+
this.#candidateSnapshot = snapshot;
|
|
220
|
+
}
|
|
209
221
|
generationHandoff() {
|
|
210
222
|
return this.#handoffs.seal();
|
|
211
223
|
}
|
package/lib/root-runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSourcePath } from './source-path.js';
|
|
1
2
|
import { resolve } from 'node:path';
|
|
2
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
import { DisposeStack, GenerationHandoffStack, RootController, ConfigDocumentDivergenceError, createSnapshotView, rootPluginId, } from '@zhin.js/plugin-runtime';
|
|
@@ -41,7 +42,7 @@ export class RootRuntime {
|
|
|
41
42
|
#controller;
|
|
42
43
|
#disabledPluginInstanceKeys;
|
|
43
44
|
constructor(options) {
|
|
44
|
-
this.#projectRoot =
|
|
45
|
+
this.#projectRoot = normalizeSourcePath(options.projectRoot);
|
|
45
46
|
this.#modules = options.modules;
|
|
46
47
|
this.#environment = defineRuntimeEnvironment(options.environment);
|
|
47
48
|
this.#environmentLayers = defineEnvironmentLayers(options.environmentVariables);
|
|
@@ -55,7 +56,7 @@ export class RootRuntime {
|
|
|
55
56
|
this.#runtimeInputSources = new Set([
|
|
56
57
|
...(this.#configPort?.sources ?? []),
|
|
57
58
|
...(this.#environmentSource?.sources ?? []),
|
|
58
|
-
].map((source) =>
|
|
59
|
+
].map((source) => normalizeSourcePath(source)));
|
|
59
60
|
this.#installResources = options.installResources;
|
|
60
61
|
this.#isolation = options.isolation;
|
|
61
62
|
this.#disabledPluginInstanceKeys = Object.freeze([...(options.disabledPluginInstanceKeys ?? [])]);
|
|
@@ -105,7 +106,7 @@ export class RootRuntime {
|
|
|
105
106
|
modules: this.#modules,
|
|
106
107
|
ownership: () => this.sourceOwnership,
|
|
107
108
|
runtime: {
|
|
108
|
-
handlesSource: (source) => this.#runtimeInputSources.has(
|
|
109
|
+
handlesSource: (source) => this.#runtimeInputSources.has(normalizeSourcePath(source)),
|
|
109
110
|
reload: async (plan) => {
|
|
110
111
|
const result = await this.#reloadPlan(plan);
|
|
111
112
|
return isProcessPlan(result) ? result : undefined;
|
|
@@ -139,7 +140,7 @@ export class RootRuntime {
|
|
|
139
140
|
? Object.freeze({
|
|
140
141
|
...plan,
|
|
141
142
|
subtrees: collapseInvalidationSubtrees([...plan.subtrees, ...inputs.roots]),
|
|
142
|
-
fallbacks: Object.freeze(plan.fallbacks.filter((fallback) => !inputs.inputSources.has(
|
|
143
|
+
fallbacks: Object.freeze(plan.fallbacks.filter((fallback) => !inputs.inputSources.has(normalizeSourcePath(fallback.source)))),
|
|
143
144
|
})
|
|
144
145
|
: plan;
|
|
145
146
|
let restart;
|
|
@@ -189,11 +190,11 @@ export class RootRuntime {
|
|
|
189
190
|
return this.#controller.committed.state.model;
|
|
190
191
|
}
|
|
191
192
|
async #planRuntimeInputs(changed) {
|
|
192
|
-
const inputSources = new Set(changed.map((source) =>
|
|
193
|
+
const inputSources = new Set(changed.map((source) => normalizeSourcePath(source)).filter((source) => this.#runtimeInputSources.has(source)));
|
|
193
194
|
if (inputSources.size === 0 || !this.#model)
|
|
194
195
|
return undefined;
|
|
195
|
-
const configChanged = this.#configPort?.sources?.some((source) => inputSources.has(
|
|
196
|
-
const environmentChanged = this.#environmentSource?.sources.some((source) => inputSources.has(
|
|
196
|
+
const configChanged = this.#configPort?.sources?.some((source) => inputSources.has(normalizeSourcePath(source))) ?? false;
|
|
197
|
+
const environmentChanged = this.#environmentSource?.sources.some((source) => inputSources.has(normalizeSourcePath(source))) ?? false;
|
|
197
198
|
const configSnapshot = configChanged ? await this.#configPort?.read() : undefined;
|
|
198
199
|
const document = configSnapshot?.document ?? requireConfigDocument(this.#configDocument);
|
|
199
200
|
const environmentLayers = environmentChanged && this.#environmentSource
|
|
@@ -210,7 +211,7 @@ export class RootRuntime {
|
|
|
210
211
|
if (inputPlan.hostKeys.length > 0) {
|
|
211
212
|
return Object.freeze({
|
|
212
213
|
kind: 'process',
|
|
213
|
-
changed: Object.freeze([...changed].map((source) =>
|
|
214
|
+
changed: Object.freeze([...changed].map((source) => normalizeSourcePath(source))),
|
|
214
215
|
reasons: Object.freeze(inputPlan.hostKeys.map((key) => `Host configuration changed: ${key}`)),
|
|
215
216
|
});
|
|
216
217
|
}
|
|
@@ -263,7 +264,7 @@ export class RootRuntime {
|
|
|
263
264
|
if (plan.subtrees.length === 0 || plan.subtrees.includes(rootPluginId()))
|
|
264
265
|
return false;
|
|
265
266
|
return plan.changed.every((source) => {
|
|
266
|
-
if (this.#runtimeInputSources.has(
|
|
267
|
+
if (this.#runtimeInputSources.has(normalizeSourcePath(source)))
|
|
267
268
|
return true;
|
|
268
269
|
const records = this.sourceOwnership.recordsFor(source);
|
|
269
270
|
return records.length > 0 && records.every((record) => record.role === 'plugin' || record.role === 'schema');
|
|
@@ -519,6 +520,7 @@ class GenerationAssembler {
|
|
|
519
520
|
}
|
|
520
521
|
const state = projected.state;
|
|
521
522
|
const snapshot = createSnapshotView(this.generation, state);
|
|
523
|
+
this.#plugins.bindCandidateSnapshot(snapshot);
|
|
522
524
|
const ownership = SourceOwnershipIndex.fromGeneration(this.graph, snapshot, this.#featureIdsByPackageRoot);
|
|
523
525
|
const assets = GenerationAssets.create(this.#plugins.createdScopeDisposers(), this.#projectionDisposers);
|
|
524
526
|
return prepareRuntimeGeneration({
|
|
@@ -17,6 +17,7 @@ export declare class SourceOwnershipIndex {
|
|
|
17
17
|
addPackageRoot(packageRoot: string, owner: PluginId): void;
|
|
18
18
|
recordsFor(source: string): readonly SourceOwnershipRecord[];
|
|
19
19
|
ownersForPath(source: string): readonly PluginId[];
|
|
20
|
+
moduleSources(): readonly string[];
|
|
20
21
|
watchRoots(): readonly ModuleWatchRoot[];
|
|
21
22
|
addWatchRoot(root: ModuleWatchRoot): void;
|
|
22
23
|
}
|
package/lib/source-ownership.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSourcePath } from './source-path.js';
|
|
1
2
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
2
3
|
export class SourceOwnershipIndex {
|
|
3
4
|
#records = new Map();
|
|
@@ -92,11 +93,17 @@ export class SourceOwnershipIndex {
|
|
|
92
93
|
}
|
|
93
94
|
return Object.freeze([...owners]);
|
|
94
95
|
}
|
|
96
|
+
moduleSources() {
|
|
97
|
+
return Object.freeze([...this.#records.entries()]
|
|
98
|
+
.filter(([, records]) => records.some((record) => (record.role === 'plugin' || record.role === 'feature' || record.role === 'capability')))
|
|
99
|
+
.map(([source]) => source)
|
|
100
|
+
.sort());
|
|
101
|
+
}
|
|
95
102
|
watchRoots() {
|
|
96
103
|
return Object.freeze([...this.#watchRoots.values()]);
|
|
97
104
|
}
|
|
98
105
|
addWatchRoot(root) {
|
|
99
|
-
const normalized =
|
|
106
|
+
const normalized = normalizeSourcePath(root.root);
|
|
100
107
|
this.#watchRoots.set(normalized, Object.freeze({ ...root, root: normalized }));
|
|
101
108
|
}
|
|
102
109
|
}
|
|
@@ -106,7 +113,7 @@ function visitPlugin(node, visit) {
|
|
|
106
113
|
visitPlugin(child, visit);
|
|
107
114
|
}
|
|
108
115
|
function normalizeSource(source) {
|
|
109
|
-
return
|
|
116
|
+
return normalizeSourcePath(source);
|
|
110
117
|
}
|
|
111
118
|
function contains(packageRoot, source) {
|
|
112
119
|
const path = relative(packageRoot, source);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
/** Keep file events and scanned sources in the same physical path namespace.
|
|
4
|
+
* Resolve the nearest existing parent for unlink events and newly added files.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeSourcePath(source) {
|
|
7
|
+
const absolute = resolve(source);
|
|
8
|
+
let parent = absolute;
|
|
9
|
+
const suffix = [];
|
|
10
|
+
for (;;) {
|
|
11
|
+
try {
|
|
12
|
+
return join(realpathSync(parent), ...suffix);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const code = error.code;
|
|
16
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR')
|
|
17
|
+
throw error;
|
|
18
|
+
const next = dirname(parent);
|
|
19
|
+
if (next === parent)
|
|
20
|
+
return absolute;
|
|
21
|
+
suffix.unshift(basename(parent));
|
|
22
|
+
parent = next;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -82,6 +82,7 @@ export class SubtreeGenerationPreparer {
|
|
|
82
82
|
projectionDisposers.set(feature, dispose);
|
|
83
83
|
}
|
|
84
84
|
const snapshot = createSnapshotView(current.generation + 1, projected.state);
|
|
85
|
+
plugins.bindCandidateSnapshot(snapshot);
|
|
85
86
|
const ownership = SourceOwnershipIndex.fromGeneration(this.graph, snapshot, this.model.featureIdsByPackageRoot);
|
|
86
87
|
const replacements = new Map(plugins.createdScopeDisposers());
|
|
87
88
|
// Local prepare still commits a complete immutable generation. The
|
|
@@ -89,6 +89,7 @@ export class TopologyGenerationPreparer {
|
|
|
89
89
|
projectionDisposers.set(feature, dispose);
|
|
90
90
|
}
|
|
91
91
|
const snapshot = createSnapshotView(current.generation + 1, projected.state);
|
|
92
|
+
plugins.bindCandidateSnapshot(snapshot);
|
|
92
93
|
const ownership = SourceOwnershipIndex.fromGeneration(this.graph, snapshot, featureTopology.featureIdsByPackageRoot);
|
|
93
94
|
const replacements = new Map(plugins.createdScopeDisposers());
|
|
94
95
|
const assets = this.model.assets.replaceScopes(graphOrder(this.graph), replacements, projectionDisposers);
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
interface ResolveResult {
|
|
2
|
+
readonly url: string;
|
|
3
|
+
readonly shortCircuit?: boolean;
|
|
4
|
+
readonly format?: string;
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
7
|
* ESM resolve hook: when a relative/absolute `.js` specifier fails and a
|
|
3
8
|
* sibling `.ts` / `.tsx` exists, rewrite to that source file.
|
|
@@ -10,14 +15,7 @@ export declare function resolve(specifier: string, context: {
|
|
|
10
15
|
parentURL?: string;
|
|
11
16
|
}, nextResolve: (specifier: string, context: {
|
|
12
17
|
parentURL?: string;
|
|
13
|
-
}) => Promise<
|
|
14
|
-
url: string;
|
|
15
|
-
shortCircuit?: boolean;
|
|
16
|
-
format?: string;
|
|
17
|
-
}>): Promise<{
|
|
18
|
-
url: string;
|
|
19
|
-
shortCircuit?: boolean;
|
|
20
|
-
format?: string;
|
|
21
|
-
}>;
|
|
18
|
+
}) => Promise<ResolveResult>): Promise<ResolveResult>;
|
|
22
19
|
/** Register once for the current process (safe to call from CLI / RootHost). */
|
|
23
20
|
export declare function ensureTypeScriptSpecifierRemap(): void;
|
|
21
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access } from 'node:fs/promises';
|
|
2
2
|
import { register } from 'node:module';
|
|
3
|
-
import { sep } from 'node:path';
|
|
3
|
+
import { isAbsolute, sep } from 'node:path';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
/**
|
|
6
6
|
* ESM resolve hook: when a relative/absolute `.js` specifier fails and a
|
|
@@ -11,8 +11,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
11
11
|
* remap missing `.js` → `.ts` the way `tsc`/tsx do.
|
|
12
12
|
*/
|
|
13
13
|
export async function resolve(specifier, context, nextResolve) {
|
|
14
|
+
let resolved;
|
|
14
15
|
try {
|
|
15
|
-
|
|
16
|
+
resolved = await nextResolve(specifier, context);
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
19
|
if (!shouldTryTypeScript(specifier, error))
|
|
@@ -20,8 +21,9 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
20
21
|
const candidate = await firstExistingTypeScript(specifier, context.parentURL);
|
|
21
22
|
if (!candidate)
|
|
22
23
|
throw error;
|
|
23
|
-
|
|
24
|
+
resolved = await nextResolve(candidate, context);
|
|
24
25
|
}
|
|
26
|
+
return inheritGenerationRevision(specifier, context.parentURL, resolved);
|
|
25
27
|
}
|
|
26
28
|
let registered = false;
|
|
27
29
|
/** Register once for the current process (safe to call from CLI / RootHost). */
|
|
@@ -39,6 +41,29 @@ function shouldTryTypeScript(specifier, error) {
|
|
|
39
41
|
return false;
|
|
40
42
|
return /\.[cm]?js$/u.test(specifier) || specifier.endsWith('.jsx');
|
|
41
43
|
}
|
|
44
|
+
function inheritGenerationRevision(specifier, parentURL, resolved) {
|
|
45
|
+
if (!parentURL || !isLocalSpecifier(specifier) || !resolved.url.startsWith('file:')) {
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
|
48
|
+
let revision;
|
|
49
|
+
try {
|
|
50
|
+
revision = new URL(parentURL).searchParams.get('zhin-generation');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
if (revision === null)
|
|
56
|
+
return resolved;
|
|
57
|
+
const url = new URL(resolved.url);
|
|
58
|
+
url.searchParams.set('zhin-generation', revision);
|
|
59
|
+
return { ...resolved, url: url.href };
|
|
60
|
+
}
|
|
61
|
+
function isLocalSpecifier(specifier) {
|
|
62
|
+
return specifier.startsWith('.')
|
|
63
|
+
|| specifier.startsWith('#')
|
|
64
|
+
|| specifier.startsWith('file:')
|
|
65
|
+
|| isAbsolute(specifier);
|
|
66
|
+
}
|
|
42
67
|
async function firstExistingTypeScript(specifier, parentURL) {
|
|
43
68
|
const resolvedJs = resolveAgainstParent(specifier, parentURL);
|
|
44
69
|
if (!resolvedJs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/runtime",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Static Plugin graph, generation transaction and HMR Root runtime for Zhin.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -17,25 +17,27 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ajv": "8.18.0",
|
|
20
|
+
"es-module-lexer": "2.3.1",
|
|
21
|
+
"import-meta-resolve": "4.2.0",
|
|
20
22
|
"semver": "7.8.5",
|
|
21
|
-
"@zhin.js/
|
|
22
|
-
"@zhin.js/
|
|
23
|
+
"@zhin.js/plugin-runtime": "1.1.11",
|
|
24
|
+
"@zhin.js/feature-kit": "1.1.2"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@types/node": "^26.1.2",
|
|
26
28
|
"tsx": "^4.23.1",
|
|
27
29
|
"typescript": "^6.0.3",
|
|
28
|
-
"@zhin.js/adapter": "1.1.
|
|
29
|
-
"@zhin.js/agent-feature": "1.1.
|
|
30
|
-
"@zhin.js/command": "1.1.
|
|
31
|
-
"@zhin.js/component": "1.1.
|
|
32
|
-
"@zhin.js/
|
|
33
|
-
"@zhin.js/
|
|
34
|
-
"@zhin.js/
|
|
35
|
-
"@zhin.js/
|
|
30
|
+
"@zhin.js/adapter": "1.1.16",
|
|
31
|
+
"@zhin.js/agent-feature": "1.1.3",
|
|
32
|
+
"@zhin.js/command": "1.1.4",
|
|
33
|
+
"@zhin.js/component": "1.1.3",
|
|
34
|
+
"@zhin.js/mcp-feature": "1.1.3",
|
|
35
|
+
"@zhin.js/layout": "1.1.3",
|
|
36
|
+
"@zhin.js/page": "1.1.3",
|
|
37
|
+
"@zhin.js/middleware": "1.1.3",
|
|
36
38
|
"@zhin.js/prompt-section": "1.1.2",
|
|
37
|
-
"@zhin.js/skill": "1.1.
|
|
38
|
-
"@zhin.js/tool": "1.1.
|
|
39
|
+
"@zhin.js/skill": "1.1.3",
|
|
40
|
+
"@zhin.js/tool": "1.1.3"
|
|
39
41
|
},
|
|
40
42
|
"repository": {
|
|
41
43
|
"type": "git",
|