@droposs/plugin-cli 0.1.0 → 0.2.0
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/bin/drop-plugin.js +60 -5
- package/dist/builder.d.ts +10 -0
- package/dist/builder.js +84 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/scaffolder.d.ts +9 -0
- package/dist/scaffolder.js +51 -0
- package/dist/signer.d.ts +6 -1
- package/dist/signer.js +60 -7
- package/dist/testRunner.d.ts +1 -0
- package/dist/testRunner.js +52 -0
- package/package.json +10 -5
package/bin/drop-plugin.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
signPlugin,
|
|
4
|
+
packPlugin,
|
|
5
|
+
buildPlugin,
|
|
6
|
+
testPlugin,
|
|
7
|
+
initPlugin,
|
|
8
|
+
validateManifest,
|
|
9
|
+
} from "../dist/index.js";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
3
12
|
|
|
4
13
|
const command = process.argv[2];
|
|
5
14
|
const args = process.argv.slice(3);
|
|
@@ -9,26 +18,72 @@ async function main() {
|
|
|
9
18
|
case "sign": {
|
|
10
19
|
const dir = args[0] || ".";
|
|
11
20
|
const res = await signPlugin(dir);
|
|
12
|
-
console.log(
|
|
21
|
+
console.log(
|
|
22
|
+
`Signed bundle at ${dir}: ${res.fileCount} files verified (signature: ${res.signed ? "yes" : "no"})`,
|
|
23
|
+
);
|
|
13
24
|
break;
|
|
14
25
|
}
|
|
15
26
|
case "pack": {
|
|
16
27
|
const dir = args[0] || ".";
|
|
17
28
|
const outDir = args[1];
|
|
18
29
|
const res = await packPlugin(dir, outDir);
|
|
19
|
-
console.log(
|
|
30
|
+
console.log(
|
|
31
|
+
`Packed plugin '${res.id}' v${res.version} to ${res.packagePath}`,
|
|
32
|
+
);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case "build": {
|
|
36
|
+
const dir = args[0] || ".";
|
|
37
|
+
const res = await buildPlugin(dir);
|
|
38
|
+
console.log(
|
|
39
|
+
`Built plugin at ${dir} (server: ${res.serverBuilt ? "yes" : "no"}, client: ${res.clientBuilt ? "yes" : "no"})`,
|
|
40
|
+
);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "test": {
|
|
44
|
+
const dir = args[0] || ".";
|
|
45
|
+
await testPlugin(dir);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "init": {
|
|
49
|
+
const dir = args[0] || "my-drop-plugin";
|
|
50
|
+
const res = await initPlugin(dir);
|
|
51
|
+
console.log(`Initialized new Drop plugin '${res.id}' at ${res.targetPath}`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "validate": {
|
|
55
|
+
const dir = args[0] || ".";
|
|
56
|
+
const manifestPath = path.join(path.resolve(process.cwd(), dir), "drop-plugin.json");
|
|
57
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
58
|
+
const validation = await validateManifest(manifest);
|
|
59
|
+
if (!validation.valid) {
|
|
60
|
+
console.error("Validation failed:");
|
|
61
|
+
for (const err of validation.errors) {
|
|
62
|
+
console.error(` - ${err}`);
|
|
63
|
+
}
|
|
64
|
+
process.exit(1);
|
|
65
|
+
} else {
|
|
66
|
+
console.log(`Manifest at ${manifestPath} is valid.`);
|
|
67
|
+
}
|
|
20
68
|
break;
|
|
21
69
|
}
|
|
22
70
|
case "help":
|
|
71
|
+
case "--help":
|
|
72
|
+
case "-h":
|
|
23
73
|
default:
|
|
24
74
|
console.log(`Drop Plugin CLI (drop-plugin)
|
|
25
75
|
|
|
26
76
|
Usage:
|
|
77
|
+
drop-plugin init [dir] Initialize a new plugin from starter template
|
|
78
|
+
drop-plugin build [dir] Bundle server/client entry points with esbuild and sign
|
|
27
79
|
drop-plugin sign [dir] Calculate SHA-256 digests and sign drop-plugin.json
|
|
80
|
+
drop-plugin validate [dir] Validate drop-plugin.json against official schema
|
|
81
|
+
drop-plugin test [dir] Run plugin tests with Node test runner
|
|
28
82
|
drop-plugin pack [dir] [out] Verify, sign, and package bundle into .dropplugin archive
|
|
29
|
-
drop-plugin build Compile plugin bundle
|
|
30
|
-
drop-plugin test Run plugin tests
|
|
31
83
|
`);
|
|
84
|
+
if (command && command !== "help" && command !== "--help" && command !== "-h") {
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
32
87
|
break;
|
|
33
88
|
}
|
|
34
89
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface BuildOptions {
|
|
2
|
+
minify?: boolean;
|
|
3
|
+
sourcemap?: boolean;
|
|
4
|
+
sign?: boolean;
|
|
5
|
+
signingKey?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildPlugin(targetDir?: string, options?: BuildOptions): Promise<{
|
|
8
|
+
serverBuilt: boolean;
|
|
9
|
+
clientBuilt: boolean;
|
|
10
|
+
}>;
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as esbuild from "esbuild";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { signPlugin } from "./signer.js";
|
|
5
|
+
export async function buildPlugin(targetDir = ".", options = {}) {
|
|
6
|
+
const dir = path.resolve(process.cwd(), targetDir);
|
|
7
|
+
const manifestPath = path.join(dir, "drop-plugin.json");
|
|
8
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
9
|
+
let serverBuilt = false;
|
|
10
|
+
let clientBuilt = false;
|
|
11
|
+
// 1. Build Server Entry if source exists
|
|
12
|
+
const serverSourceCandidates = [
|
|
13
|
+
manifest.server?.source,
|
|
14
|
+
"src/index.ts",
|
|
15
|
+
"src/index.js",
|
|
16
|
+
"src/server.ts",
|
|
17
|
+
].filter(Boolean);
|
|
18
|
+
let serverEntrySource = null;
|
|
19
|
+
for (const candidate of serverSourceCandidates) {
|
|
20
|
+
const p = path.join(dir, candidate);
|
|
21
|
+
if (await stat(p).catch(() => null)) {
|
|
22
|
+
serverEntrySource = p;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const serverOutFile = path.resolve(dir, manifest.server?.entry ?? manifest.entry ?? "dist/src/index.js");
|
|
27
|
+
if (serverEntrySource) {
|
|
28
|
+
await esbuild.build({
|
|
29
|
+
entryPoints: [serverEntrySource],
|
|
30
|
+
outfile: serverOutFile,
|
|
31
|
+
bundle: true,
|
|
32
|
+
platform: "node",
|
|
33
|
+
target: "node22",
|
|
34
|
+
format: "esm",
|
|
35
|
+
sourcemap: options.sourcemap ?? true,
|
|
36
|
+
minify: options.minify ?? false,
|
|
37
|
+
external: [
|
|
38
|
+
"@droposs/plugin-sdk",
|
|
39
|
+
"@drop/plugin-sdk",
|
|
40
|
+
"h3",
|
|
41
|
+
"pino",
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
serverBuilt = true;
|
|
45
|
+
}
|
|
46
|
+
// 2. Build Client Entry if source exists
|
|
47
|
+
const clientSourceCandidates = [
|
|
48
|
+
manifest.client?.source,
|
|
49
|
+
"src/client.ts",
|
|
50
|
+
"src/client.js",
|
|
51
|
+
].filter(Boolean);
|
|
52
|
+
let clientEntrySource = null;
|
|
53
|
+
for (const candidate of clientSourceCandidates) {
|
|
54
|
+
const p = path.join(dir, candidate);
|
|
55
|
+
if (await stat(p).catch(() => null)) {
|
|
56
|
+
clientEntrySource = p;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const clientOutFile = path.resolve(dir, manifest.client?.entry ?? manifest.clientEntry ?? "dist/src/client.js");
|
|
61
|
+
if (clientEntrySource) {
|
|
62
|
+
await esbuild.build({
|
|
63
|
+
entryPoints: [clientEntrySource],
|
|
64
|
+
outfile: clientOutFile,
|
|
65
|
+
bundle: true,
|
|
66
|
+
platform: "browser",
|
|
67
|
+
target: "es2022",
|
|
68
|
+
format: "esm",
|
|
69
|
+
sourcemap: options.sourcemap ?? true,
|
|
70
|
+
minify: options.minify ?? false,
|
|
71
|
+
external: [
|
|
72
|
+
"vue",
|
|
73
|
+
"@droposs/plugin-sdk",
|
|
74
|
+
"@drop/plugin-sdk",
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
clientBuilt = true;
|
|
78
|
+
}
|
|
79
|
+
// 3. Automatically re-sign the plugin bundle after building
|
|
80
|
+
if (options.sign !== false) {
|
|
81
|
+
await signPlugin(dir, options.signingKey);
|
|
82
|
+
}
|
|
83
|
+
return { serverBuilt, clientBuilt };
|
|
84
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { cp, mkdir, readFile, writeFile, stat } from "node:fs/promises";
|
|
3
|
+
export async function initPlugin(targetDir, options = {}) {
|
|
4
|
+
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
5
|
+
await mkdir(targetPath, { recursive: true });
|
|
6
|
+
const candidates = [
|
|
7
|
+
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../../templates/starter-plugin"),
|
|
8
|
+
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../templates/starter-plugin"),
|
|
9
|
+
path.resolve(process.cwd(), "templates/starter-plugin"),
|
|
10
|
+
];
|
|
11
|
+
let templateDir = null;
|
|
12
|
+
for (const cand of candidates) {
|
|
13
|
+
if (await stat(cand).catch(() => null)) {
|
|
14
|
+
templateDir = cand;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!templateDir) {
|
|
19
|
+
throw new Error("Starter plugin template directory not found");
|
|
20
|
+
}
|
|
21
|
+
await cp(templateDir, targetPath, {
|
|
22
|
+
recursive: true,
|
|
23
|
+
filter: (src) => {
|
|
24
|
+
const basename = path.basename(src);
|
|
25
|
+
return basename !== "node_modules" && basename !== "dist";
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const pluginId = options.id ||
|
|
29
|
+
path
|
|
30
|
+
.basename(targetPath)
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9._-]/g, "-");
|
|
33
|
+
const pluginName = options.name || pluginId;
|
|
34
|
+
// Update drop-plugin.json
|
|
35
|
+
const manifestPath = path.join(targetPath, "drop-plugin.json");
|
|
36
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
37
|
+
manifest.id = pluginId;
|
|
38
|
+
manifest.name = pluginName;
|
|
39
|
+
if (options.author)
|
|
40
|
+
manifest.author = options.author;
|
|
41
|
+
delete manifest.checksum;
|
|
42
|
+
delete manifest.files;
|
|
43
|
+
delete manifest.signature;
|
|
44
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
45
|
+
// Update package.json
|
|
46
|
+
const pkgPath = path.join(targetPath, "package.json");
|
|
47
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
48
|
+
pkg.name = `drop-${pluginId}`;
|
|
49
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
50
|
+
return { targetPath, id: pluginId };
|
|
51
|
+
}
|
package/dist/signer.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
export declare function isInside(base: string, candidate: string): boolean;
|
|
2
|
+
export declare function validateManifest(manifest: unknown): Promise<{
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}>;
|
|
1
6
|
export declare function listFiles(root: string, prefix?: string): Promise<string[]>;
|
|
2
|
-
export declare function signPlugin(targetDir: string, signingKey?: string): Promise<{
|
|
7
|
+
export declare function signPlugin(targetDir: string, signingKey?: string, validate?: boolean): Promise<{
|
|
3
8
|
fileCount: number;
|
|
4
9
|
signed: boolean;
|
|
5
10
|
}>;
|
package/dist/signer.js
CHANGED
|
@@ -1,22 +1,66 @@
|
|
|
1
1
|
import { createHash, createHmac } from "node:crypto";
|
|
2
2
|
import { readdir, readFile, realpath, stat, writeFile, mkdir } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import Ajv from "ajv";
|
|
4
6
|
const MANIFEST_FILE = "drop-plugin.json";
|
|
5
|
-
function isInside(base, candidate) {
|
|
6
|
-
const
|
|
7
|
-
return
|
|
7
|
+
export function isInside(base, candidate) {
|
|
8
|
+
const rel = path.relative(path.resolve(base), path.resolve(candidate));
|
|
9
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
10
|
+
}
|
|
11
|
+
async function loadSchema() {
|
|
12
|
+
try {
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const schemaPath = require.resolve("@droposs/plugin-sdk/schema.json");
|
|
15
|
+
return JSON.parse(await readFile(schemaPath, "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
const fallback = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../plugin-sdk/schema/drop-plugin.schema.json");
|
|
19
|
+
return JSON.parse(await readFile(fallback, "utf-8"));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let cachedValidator = null;
|
|
23
|
+
let cachedErrors = [];
|
|
24
|
+
export async function validateManifest(manifest) {
|
|
25
|
+
if (!cachedValidator) {
|
|
26
|
+
const schema = await loadSchema();
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
const AjvClass = Ajv.default ?? Ajv;
|
|
29
|
+
const ajv = new AjvClass({ allErrors: true, strict: false });
|
|
30
|
+
const compiled = ajv.compile(schema);
|
|
31
|
+
cachedValidator = (data) => {
|
|
32
|
+
const ok = compiled(data);
|
|
33
|
+
if (!ok && compiled.errors) {
|
|
34
|
+
cachedErrors = compiled.errors.map((err) => `${err.instancePath || "/"} ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
cachedErrors = [];
|
|
38
|
+
}
|
|
39
|
+
return Boolean(ok);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const valid = cachedValidator(manifest);
|
|
43
|
+
return { valid, errors: [...cachedErrors] };
|
|
8
44
|
}
|
|
9
45
|
export async function listFiles(root, prefix = "") {
|
|
10
46
|
const results = [];
|
|
11
|
-
const
|
|
47
|
+
const dirPath = path.join(root, prefix);
|
|
48
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
12
49
|
for (const entry of entries) {
|
|
13
50
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
14
|
-
|
|
51
|
+
const fullPath = path.join(root, rel);
|
|
52
|
+
// Enforce confinement invariant: symlinks or traversal outside bundle root are rejected
|
|
53
|
+
const real = await realpath(fullPath).catch(() => null);
|
|
54
|
+
if (!real || !isInside(root, real)) {
|
|
55
|
+
throw new Error(`Path traversal or symlink escape detected: ${rel}`);
|
|
56
|
+
}
|
|
57
|
+
const s = await stat(real);
|
|
58
|
+
if (s.isDirectory()) {
|
|
15
59
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
16
60
|
continue;
|
|
17
61
|
results.push(...(await listFiles(root, rel)));
|
|
18
62
|
}
|
|
19
|
-
else if (
|
|
63
|
+
else if (s.isFile()) {
|
|
20
64
|
if (!prefix && entry.name === MANIFEST_FILE)
|
|
21
65
|
continue;
|
|
22
66
|
results.push(rel);
|
|
@@ -24,7 +68,7 @@ export async function listFiles(root, prefix = "") {
|
|
|
24
68
|
}
|
|
25
69
|
return results.sort((a, b) => a.localeCompare(b));
|
|
26
70
|
}
|
|
27
|
-
export async function signPlugin(targetDir, signingKey) {
|
|
71
|
+
export async function signPlugin(targetDir, signingKey, validate = true) {
|
|
28
72
|
const resolvedPath = path.resolve(process.cwd(), targetDir);
|
|
29
73
|
const bundleDir = await realpath(resolvedPath).catch(() => null);
|
|
30
74
|
if (!bundleDir) {
|
|
@@ -32,12 +76,21 @@ export async function signPlugin(targetDir, signingKey) {
|
|
|
32
76
|
}
|
|
33
77
|
const manifestPath = path.join(bundleDir, MANIFEST_FILE);
|
|
34
78
|
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
79
|
+
if (validate) {
|
|
80
|
+
const validation = await validateManifest(manifest);
|
|
81
|
+
if (!validation.valid) {
|
|
82
|
+
throw new Error(`Manifest validation failed against schema:\n ${validation.errors.join("\n ")}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
35
85
|
// Identify primary entry (v1 or v2 server/client entry)
|
|
36
86
|
const entry = manifest.entry ??
|
|
37
87
|
manifest.server?.entry ??
|
|
38
88
|
manifest.client?.entry ??
|
|
39
89
|
"index.js";
|
|
40
90
|
const entryPath = path.resolve(bundleDir, entry);
|
|
91
|
+
if (!isInside(bundleDir, entryPath) && entryPath !== bundleDir) {
|
|
92
|
+
throw new Error(`Entry path outside bundle directory: ${entry}`);
|
|
93
|
+
}
|
|
41
94
|
const entryExists = await stat(entryPath).catch(() => null);
|
|
42
95
|
if (entryExists) {
|
|
43
96
|
const entryBytes = await readFile(entryPath);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function testPlugin(targetDir?: string): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readdir, stat } from "node:fs/promises";
|
|
4
|
+
export async function testPlugin(targetDir = ".") {
|
|
5
|
+
const dir = path.resolve(process.cwd(), targetDir);
|
|
6
|
+
const candidates = [
|
|
7
|
+
path.join(dir, "dist", "test"),
|
|
8
|
+
path.join(dir, "test"),
|
|
9
|
+
];
|
|
10
|
+
let testFiles = [];
|
|
11
|
+
for (const cand of candidates) {
|
|
12
|
+
if (await stat(cand).catch(() => null)) {
|
|
13
|
+
const files = await readdir(cand, { recursive: true });
|
|
14
|
+
for (const f of files) {
|
|
15
|
+
if (typeof f === "string" &&
|
|
16
|
+
(f.endsWith(".test.js") || f.endsWith(".spec.js"))) {
|
|
17
|
+
testFiles.push(path.join(cand, f));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (testFiles.length > 0)
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (testFiles.length === 0) {
|
|
25
|
+
const testDir = path.join(dir, "test");
|
|
26
|
+
if (await stat(testDir).catch(() => null)) {
|
|
27
|
+
const files = await readdir(testDir, { recursive: true });
|
|
28
|
+
testFiles = files
|
|
29
|
+
.filter((f) => typeof f === "string" &&
|
|
30
|
+
(f.endsWith(".test.ts") || f.endsWith(".spec.ts")))
|
|
31
|
+
.map((f) => path.join(testDir, f));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (testFiles.length === 0) {
|
|
35
|
+
console.log(`No test files found in ${targetDir}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const child = spawn(process.execPath, ["--test", ...testFiles], {
|
|
40
|
+
cwd: dir,
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
});
|
|
43
|
+
child.on("close", (code) => {
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
resolve();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
reject(new Error(`Tests exited with code ${code}`));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@droposs/plugin-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Drop Plugin build, test, signing, and packaging CLI for Drop OSS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"drop-plugin": "./bin/drop-plugin.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "dist/
|
|
10
|
-
"types": "dist/
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
11
|
"files": [
|
|
12
12
|
"bin",
|
|
13
13
|
"dist"
|
|
@@ -35,10 +35,15 @@
|
|
|
35
35
|
"playnite"
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"typescript": "^5.7.0"
|
|
40
40
|
},
|
|
41
41
|
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@droposs/plugin-sdk": "0.2.0",
|
|
44
|
+
"ajv": "^8.20.0",
|
|
45
|
+
"esbuild": "^0.28.2"
|
|
46
|
+
},
|
|
42
47
|
"scripts": {
|
|
43
48
|
"build": "tsc",
|
|
44
49
|
"test": "node --test"
|