@pikku/inspector 0.12.91 → 0.12.94
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/CHANGELOG.md +33 -0
- package/dist/add/add-trigger.js +4 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/addon-resolution.d.ts +10 -0
- package/dist/utils/addon-resolution.js +43 -0
- package/dist/utils/ensure-function-metadata.js +3 -0
- package/dist/utils/load-addon-functions-meta.js +1 -40
- package/dist/utils/post-process.js +67 -36
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## 0.12.94
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 4323bcf: Inline wiring functions now record their source file, so `pikku release` leaves scaffold-injected ones out of the API surface.
|
|
6
|
+
|
|
7
|
+
## 0.12.93
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 658f047: A function whose services are all singletons no longer builds wire services, so an addon's credential-bound wire factory does not run for, or fail, a webhook `receive` step that needs no connection. The inspector decides this from the `Services` type and records it as `singletonServicesOnly` on the function's runtime meta; the runner only reads the flag.
|
|
12
|
+
- 658f047: Webhook signing secrets live in the credential store. `WebhookSigningSecret.fromCredential(provider, credentialService, name)` reads the secret per delivery via `load()`, so a `setup` step (or a handshake) that stores a new one with `credentialService.set` takes effect without a deploy.
|
|
13
|
+
|
|
14
|
+
Breaking: `setup` no longer returns `secret`, `wireTriggerWebhookSource` no longer takes `secret`, lifecycle outcomes drop `secretName`/`secret`, and `pikku webhooks setup` drops `--secretsOut`.
|
|
15
|
+
|
|
16
|
+
- 658f047: A webhook source's `method` may be a list, mounting one route per method, for providers that verify the URL with a GET and deliver events with a POST (WhatsApp, Strava, Onfleet, Mailchimp), or a HEAD (Trello, Mandrill, SurveyMonkey).
|
|
17
|
+
- Updated dependencies [658f047]
|
|
18
|
+
- Updated dependencies [de63ab2]
|
|
19
|
+
- Updated dependencies [658f047]
|
|
20
|
+
- Updated dependencies [658f047]
|
|
21
|
+
- Updated dependencies [658f047]
|
|
22
|
+
- Updated dependencies [658f047]
|
|
23
|
+
- @pikku/core@0.12.128
|
|
24
|
+
|
|
25
|
+
## 0.12.92
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- f84e173: An addon wired from a workspace package now resolves from that package. The installed-addon check (PKU340), the remote addon `devDependencies` check and `pikku db generate`'s addon schemas all looked only at the project root, so an addon declared where it is wired — the only place bun links it — failed codegen unless it was also declared at the root.
|
|
30
|
+
- Updated dependencies [f02585e]
|
|
31
|
+
- Updated dependencies [5586749]
|
|
32
|
+
- @pikku/core@0.12.127
|
|
33
|
+
|
|
1
34
|
## 0.12.91
|
|
2
35
|
|
|
3
36
|
### Patch Changes
|
package/dist/add/add-trigger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
2
|
+
import { getArrayPropertyValue, getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
5
|
import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
|
|
@@ -114,9 +114,10 @@ const addWireTriggerWebhookSource = (logger, node, checker, state, firstArg) =>
|
|
|
114
114
|
logger.critical(ErrorCode.MISSING_NAME, `wireTriggerWebhookSource in ${node.getSourceFile().fileName} needs 'name' as a string literal.`);
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
const method =
|
|
117
|
+
const method = getArrayPropertyValue(obj, 'method') ??
|
|
118
|
+
getPropertyValue(obj, 'method') ??
|
|
119
|
+
'post';
|
|
118
120
|
const route = getPropertyValue(obj, 'route') ?? `/webhooks/${name}`;
|
|
119
|
-
let secret = getPropertyValue(obj, 'secret');
|
|
120
121
|
const eventsInitializer = getPropertyAssignmentInitializer(obj, 'events', false, checker);
|
|
121
122
|
const events = eventsInitializer
|
|
122
123
|
? checker
|
|
@@ -151,23 +152,11 @@ const addWireTriggerWebhookSource = (logger, node, checker, state, firstArg) =>
|
|
|
151
152
|
steps[step] = pikkuFuncId;
|
|
152
153
|
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
153
154
|
}
|
|
154
|
-
// An addon's receive reads the secret by its own name; the app may have
|
|
155
|
-
// renamed it for this instance.
|
|
156
|
-
const namespace = steps.receive?.includes(':')
|
|
157
|
-
? steps.receive.slice(0, steps.receive.indexOf(':'))
|
|
158
|
-
: null;
|
|
159
|
-
const addon = namespace
|
|
160
|
-
? state.rpc.wireAddonDeclarations.get(namespace)
|
|
161
|
-
: undefined;
|
|
162
|
-
if (secret && addon?.secretOverrides?.[secret]) {
|
|
163
|
-
secret = addon.secretOverrides[secret];
|
|
164
|
-
}
|
|
165
155
|
const meta = {
|
|
166
156
|
name,
|
|
167
157
|
method: method,
|
|
168
158
|
route,
|
|
169
159
|
events,
|
|
170
|
-
...(secret ? { secret } : {}),
|
|
171
160
|
...steps,
|
|
172
161
|
};
|
|
173
162
|
state.triggers.webhookSourceMeta[name] = meta;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,3 +26,4 @@ export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils
|
|
|
26
26
|
export { resolveCoreType } from './utils/resolve-core-type.js';
|
|
27
27
|
export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
|
|
28
28
|
export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
|
|
29
|
+
export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
|
package/dist/index.js
CHANGED
|
@@ -15,3 +15,4 @@ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
|
|
|
15
15
|
export { resolveCoreType } from './utils/resolve-core-type.js';
|
|
16
16
|
export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
|
|
17
17
|
export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
|
|
18
|
+
export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type AddonResolver = {
|
|
2
|
+
resolve: (id: string) => string;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* An addon is installed wherever the package that calls `wireAddon` lists it,
|
|
6
|
+
* which in a workspace is usually not the repo root. Resolve from the declaring
|
|
7
|
+
* package first, as its code does at runtime, then from the root.
|
|
8
|
+
*/
|
|
9
|
+
export declare const addonResolutionDirs: (rootDir: string, declFile?: string) => string[];
|
|
10
|
+
export declare const createAddonResolver: (dirs: string[]) => AddonResolver;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { join, dirname, parse, resolve } from 'path';
|
|
4
|
+
const nearestPackageDir = (file) => {
|
|
5
|
+
const root = parse(file).root;
|
|
6
|
+
let dir = dirname(file);
|
|
7
|
+
while (dir && dir !== root) {
|
|
8
|
+
if (existsSync(join(dir, 'package.json')))
|
|
9
|
+
return dir;
|
|
10
|
+
const parent = dirname(dir);
|
|
11
|
+
if (parent === dir)
|
|
12
|
+
break;
|
|
13
|
+
dir = parent;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* An addon is installed wherever the package that calls `wireAddon` lists it,
|
|
19
|
+
* which in a workspace is usually not the repo root. Resolve from the declaring
|
|
20
|
+
* package first, as its code does at runtime, then from the root.
|
|
21
|
+
*/
|
|
22
|
+
export const addonResolutionDirs = (rootDir, declFile) => {
|
|
23
|
+
const root = resolve(rootDir);
|
|
24
|
+
const declDir = declFile ? nearestPackageDir(resolve(declFile)) : null;
|
|
25
|
+
return declDir && declDir !== root ? [declDir, root] : [root];
|
|
26
|
+
};
|
|
27
|
+
export const createAddonResolver = (dirs) => {
|
|
28
|
+
const requires = dirs.map((dir) => createRequire(join(dir, 'package.json')));
|
|
29
|
+
return {
|
|
30
|
+
resolve: (id) => {
|
|
31
|
+
let lastError;
|
|
32
|
+
for (const req of requires) {
|
|
33
|
+
try {
|
|
34
|
+
return req.resolve(id);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
lastError = e;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw lastError;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -176,6 +176,9 @@ export function ensureFunctionMetadata(state, pikkuFuncId, fallbackName, funcIni
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
const meta = state.functions.meta[pikkuFuncId];
|
|
179
|
+
if (!meta.sourceFile && funcInitializer) {
|
|
180
|
+
meta.sourceFile = funcInitializer.getSourceFile().fileName;
|
|
181
|
+
}
|
|
179
182
|
if (funcInitializer && checker) {
|
|
180
183
|
let pikkuFuncCall = null;
|
|
181
184
|
if (ts.isCallExpression(funcInitializer)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { readFile, readdir } from 'fs/promises';
|
|
3
|
-
import { createRequire } from 'module';
|
|
4
3
|
import { join, dirname, parse, relative } from 'path';
|
|
4
|
+
import { addonResolutionDirs, createAddonResolver, } from './addon-resolution.js';
|
|
5
5
|
import { ErrorCode } from '../error-codes.js';
|
|
6
6
|
const isHTTPRouteConfig = (value) => typeof value === 'object' &&
|
|
7
7
|
value !== null &&
|
|
@@ -90,45 +90,6 @@ const resolveAddonMeta = (require, packageName, baseName) => {
|
|
|
90
90
|
}
|
|
91
91
|
return null;
|
|
92
92
|
};
|
|
93
|
-
const nearestPackageDir = (file) => {
|
|
94
|
-
const root = parse(file).root;
|
|
95
|
-
let dir = dirname(file);
|
|
96
|
-
while (dir && dir !== root) {
|
|
97
|
-
if (existsSync(join(dir, 'package.json')))
|
|
98
|
-
return dir;
|
|
99
|
-
const parent = dirname(dir);
|
|
100
|
-
if (parent === dir)
|
|
101
|
-
break;
|
|
102
|
-
dir = parent;
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
|
-
/**
|
|
107
|
-
* An addon is installed wherever the package that calls `wireAddon` lists it,
|
|
108
|
-
* which in a workspace is usually not the repo root. Resolve from the declaring
|
|
109
|
-
* package first, as its code does at runtime, then from the root.
|
|
110
|
-
*/
|
|
111
|
-
const addonResolutionDirs = (rootDir, declFile) => {
|
|
112
|
-
const declDir = declFile ? nearestPackageDir(declFile) : null;
|
|
113
|
-
return declDir && declDir !== rootDir ? [declDir, rootDir] : [rootDir];
|
|
114
|
-
};
|
|
115
|
-
const createAddonResolver = (dirs) => {
|
|
116
|
-
const requires = dirs.map((dir) => createRequire(join(dir, 'package.json')));
|
|
117
|
-
return {
|
|
118
|
-
resolve: (id) => {
|
|
119
|
-
let lastError;
|
|
120
|
-
for (const req of requires) {
|
|
121
|
-
try {
|
|
122
|
-
return req.resolve(id);
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
lastError = e;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
throw lastError;
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
93
|
const findInstalledPackageDir = (dirs, packageName) => {
|
|
133
94
|
for (const start of dirs) {
|
|
134
95
|
const root = parse(start).root;
|
|
@@ -10,6 +10,7 @@ import { relative } from 'node:path';
|
|
|
10
10
|
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
|
|
11
11
|
import { flattenScopeDefinitions } from '@pikku/core/scope';
|
|
12
12
|
import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js';
|
|
13
|
+
import { addonResolutionDirs } from './addon-resolution.js';
|
|
13
14
|
/**
|
|
14
15
|
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
15
16
|
* handler's function meta.
|
|
@@ -93,6 +94,24 @@ function extractAllServices(state) {
|
|
|
93
94
|
state.serviceAggregation.allWireServices = extractTypeKeys(servicesType)
|
|
94
95
|
.filter((name) => !singletonSet.has(name))
|
|
95
96
|
.sort();
|
|
97
|
+
markSingletonServicesOnly(state, new Set(state.serviceAggregation.allWireServices));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Marks the functions that never read a wire service, so the runner can skip
|
|
102
|
+
* `createWireServices` for them. Only called once the `Services` type has
|
|
103
|
+
* resolved: an unresolved type yields no wire services, which would otherwise
|
|
104
|
+
* mark every function.
|
|
105
|
+
*/
|
|
106
|
+
function markSingletonServicesOnly(state, wireServices) {
|
|
107
|
+
for (const meta of Object.values(state.functions.meta)) {
|
|
108
|
+
if (meta.services?.optimized &&
|
|
109
|
+
!meta.services.services.some((name) => wireServices.has(name))) {
|
|
110
|
+
meta.singletonServicesOnly = true;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
delete meta.singletonServicesOnly;
|
|
114
|
+
}
|
|
96
115
|
}
|
|
97
116
|
}
|
|
98
117
|
/**
|
|
@@ -460,32 +479,34 @@ export function validateVariableOverrides(logger, state) {
|
|
|
460
479
|
}
|
|
461
480
|
}
|
|
462
481
|
/**
|
|
463
|
-
* Can `pkg` be resolved from `
|
|
482
|
+
* Can `pkg` be resolved from any of `dirs`, by any means the loader would use?
|
|
464
483
|
*
|
|
465
484
|
* Resolution is attempted against `<pkg>/package.json` first and the bare
|
|
466
485
|
* specifier second, because neither alone is conclusive. A package with a
|
|
467
486
|
* restrictive `exports` map refuses the first with
|
|
468
487
|
* `ERR_PACKAGE_PATH_NOT_EXPORTED` — which is not a failure at all: reaching the
|
|
469
|
-
* exports map means the package was found. Only a module-not-found from
|
|
470
|
-
*
|
|
488
|
+
* exports map means the package was found. Only a module-not-found from every
|
|
489
|
+
* attempt means it is genuinely absent.
|
|
471
490
|
*/
|
|
472
|
-
const isPackageResolvable = (pkg,
|
|
491
|
+
const isPackageResolvable = (pkg, dirs) => {
|
|
473
492
|
const NOT_FOUND = new Set(['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND']);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
req = createRequire(join(rootDir, 'package.json'));
|
|
477
|
-
}
|
|
478
|
-
catch {
|
|
479
|
-
return true; // cannot ask; do not accuse
|
|
480
|
-
}
|
|
481
|
-
for (const specifier of [`${pkg}/package.json`, pkg]) {
|
|
493
|
+
for (const dir of dirs) {
|
|
494
|
+
let req;
|
|
482
495
|
try {
|
|
483
|
-
req.
|
|
484
|
-
return true;
|
|
496
|
+
req = createRequire(join(dir, 'package.json'));
|
|
485
497
|
}
|
|
486
|
-
catch
|
|
487
|
-
|
|
498
|
+
catch {
|
|
499
|
+
return true; // cannot ask; do not accuse
|
|
500
|
+
}
|
|
501
|
+
for (const specifier of [`${pkg}/package.json`, pkg]) {
|
|
502
|
+
try {
|
|
503
|
+
req.resolve(specifier);
|
|
488
504
|
return true;
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
if (!NOT_FOUND.has(e?.code))
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
489
510
|
}
|
|
490
511
|
}
|
|
491
512
|
return false;
|
|
@@ -523,35 +544,45 @@ export function validateRemoteAddonDependencies(logger, state) {
|
|
|
523
544
|
for (const [namespace, decl] of wireAddonDeclarations.entries()) {
|
|
524
545
|
if (decl.remote)
|
|
525
546
|
continue;
|
|
526
|
-
|
|
547
|
+
const dirs = addonResolutionDirs(state.rootDir, decl.file);
|
|
548
|
+
if (isPackageResolvable(decl.package, dirs))
|
|
527
549
|
continue;
|
|
528
|
-
logger.critical(ErrorCode.ADDON_NOT_INSTALLED, `Addon '${namespace}' ('${decl.package}') is wired with wireAddon but cannot be resolved — every ref('${namespace}:…') will resolve to nothing and the surface will be dead at runtime.
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
550
|
+
logger.critical(ErrorCode.ADDON_NOT_INSTALLED, `Addon '${namespace}' ('${decl.package}') is wired with wireAddon but cannot be resolved from ${dirs.join(' or ')} — every ref('${namespace}:…') will resolve to nothing and the surface will be dead at runtime. Add it to the dependencies of ${join(dirs[0], 'package.json')} and install, or remove the wireAddon call.`);
|
|
551
|
+
}
|
|
552
|
+
// A remote addon belongs in the devDependencies of the package that wires
|
|
553
|
+
// it, which in a workspace is not the root.
|
|
554
|
+
const manifests = new Map();
|
|
555
|
+
const readManifest = (path) => {
|
|
556
|
+
if (manifests.has(path))
|
|
557
|
+
return manifests.get(path);
|
|
558
|
+
let manifest = null;
|
|
559
|
+
if (existsSync(path)) {
|
|
560
|
+
try {
|
|
561
|
+
manifest = JSON.parse(readFileSync(path, 'utf-8'));
|
|
562
|
+
}
|
|
563
|
+
catch (e) {
|
|
564
|
+
logger.warn(`Could not read ${path} to verify remote addon dependencies: ${e?.message ?? e}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
manifests.set(path, manifest);
|
|
568
|
+
return manifest;
|
|
569
|
+
};
|
|
545
570
|
for (const [namespace, decl] of wireAddonDeclarations.entries()) {
|
|
546
571
|
if (!decl.remote)
|
|
547
572
|
continue;
|
|
573
|
+
const pkgJsonPath = join(addonResolutionDirs(state.rootDir, decl.file)[0], 'package.json');
|
|
574
|
+
const pkgJson = readManifest(pkgJsonPath);
|
|
575
|
+
if (!pkgJson)
|
|
576
|
+
continue; // no manifest to check (e.g. some tests)
|
|
577
|
+
const prodDeps = pkgJson.dependencies ?? {};
|
|
578
|
+
const devDeps = pkgJson.devDependencies ?? {};
|
|
548
579
|
if (decl.package in devDeps)
|
|
549
580
|
continue; // correct
|
|
550
581
|
if (decl.package in prodDeps) {
|
|
551
|
-
logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is a production dependency, but wireRemoteAddon consumes it for types only — its handlers run on the host. Move '${decl.package}' from "dependencies" to "devDependencies".`);
|
|
582
|
+
logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is a production dependency in ${pkgJsonPath}, but wireRemoteAddon consumes it for types only — its handlers run on the host. Move '${decl.package}' from "dependencies" to "devDependencies".`);
|
|
552
583
|
}
|
|
553
584
|
else {
|
|
554
|
-
logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is wired with wireRemoteAddon but is not in "devDependencies". Add '${decl.package}' to "devDependencies" (types only).`);
|
|
585
|
+
logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is wired with wireRemoteAddon but is not in the "devDependencies" of ${pkgJsonPath}. Add '${decl.package}' to "devDependencies" (types only).`);
|
|
555
586
|
}
|
|
556
587
|
}
|
|
557
588
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.94",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"zod": "^4.4.3"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@pikku/core": "^0.12.
|
|
57
|
+
"@pikku/core": "^0.12.128"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@pikku/core": "^0.12.
|
|
60
|
+
"@pikku/core": "^0.12.128",
|
|
61
61
|
"@types/node": "^24.13.3"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|