@tavora/pack 0.0.0-stage → 0.1.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +29 -3
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +13 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +31 -0
- package/dist/keys.js.map +1 -0
- package/dist/link.d.ts +25 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +61 -0
- package/dist/link.js.map +1 -0
- package/dist/pack.d.ts +21 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +92 -0
- package/dist/pack.js.map +1 -0
- package/dist/signature.d.ts +33 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +97 -0
- package/dist/signature.js.map +1 -0
- package/dist/zip.d.ts +28 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +190 -0
- package/dist/zip.js.map +1 -0
- package/package.json +49 -3
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wellbrained
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @tavora/pack
|
|
2
|
+
|
|
3
|
+
Packs a built [Tavora](https://mytavora.app) module into a zip signed with your publisher key, and links that key to your Tavora account so players see your developer name.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install --save-dev @tavora/pack
|
|
7
|
+
npx tavora-pack . # packs the module in this folder into <id>-<version>.zip
|
|
8
|
+
npx tavora-pack link ABCD-EFGH # links your key, with a code from Tavora
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Pack
|
|
12
|
+
|
|
13
|
+
`tavora-pack <module folder> [--out <file.zip>] [--key <key file>]` checks `tavora.module.json`, takes the manifest, the folder its `entry` page is in (usually `dist/`) and the icon, and writes a zip with a signature of every file.
|
|
14
|
+
|
|
15
|
+
The first run makes your publisher key in `~/.tavora/publisher-key.pem`. **Back it up and never share it.** Tavora knows a publisher by the key, not by a name: an update must be signed with the same key, and the data your module saves for players belongs to it. A new key makes you a new publisher.
|
|
16
|
+
|
|
17
|
+
Players install the zip with **Settings > Modules > Install from zip**.
|
|
18
|
+
|
|
19
|
+
## Link your key
|
|
20
|
+
|
|
21
|
+
`tavora-pack link <code> [--key <key file>]`: in Tavora, sign in under **Settings > Account**, choose a developer name under **Module developer** and press **Link a publisher key**. It shows a code that works once for 10 minutes. The command signs the code with your key and sends the key's public half; the private key never leaves your PC.
|
|
22
|
+
|
|
23
|
+
## From code
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { loadOrCreateKey, packModule } from "@tavora/pack";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`packModule`, `readZip` and `verifyFiles` are what Tavora's installer uses too. More in the [developer docs](https://docs.mytavora.app/guide/getting-started#share-the-module).
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tavora-pack <module folder> [--out <file.zip>] [--key <key file>]
|
|
3
|
+
// Packs a built module into a zip signed with your publisher key, made on
|
|
4
|
+
// first use. Back the key up: a new key makes you a new publisher.
|
|
5
|
+
// tavora-pack link <code> [--key <key file>]
|
|
6
|
+
// Links your publisher key to your Tavora account with a code from Tavora
|
|
7
|
+
// (Settings > Account > Link a publisher key), so players see your
|
|
8
|
+
// developer name. Only the key's public half leaves this PC.
|
|
9
|
+
// Installed from npm as @tavora/pack; in Tavora's own repository: pnpm tavora-pack ...
|
|
10
|
+
import { writeFile } from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { parseArgs } from "node:util";
|
|
13
|
+
import { defaultKeyFile, loadOrCreateKey } from "./keys.js";
|
|
14
|
+
import { DEFAULT_API_URL, LinkError, linkKey } from "./link.js";
|
|
15
|
+
import { packModule, PackError } from "./pack.js";
|
|
16
|
+
import { fingerprint } from "./signature.js";
|
|
17
|
+
const USAGE = [
|
|
18
|
+
"usage: tavora-pack <module folder> [--out <file.zip>] [--key <key file>]",
|
|
19
|
+
" tavora-pack link <code> [--key <key file>]",
|
|
20
|
+
].join("\n");
|
|
21
|
+
async function main() {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = parseArgs({
|
|
25
|
+
allowPositionals: true,
|
|
26
|
+
// --api points link at another copy of Tavora's service, for testing.
|
|
27
|
+
options: { out: { type: "string" }, key: { type: "string" }, api: { type: "string" }, help: { type: "boolean", short: "h" } },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(`${error.message}\n${USAGE}`);
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
const { values, positionals } = parsed;
|
|
35
|
+
const linking = positionals[0] === "link";
|
|
36
|
+
if (values.help || positionals.length !== (linking ? 2 : 1) || (linking && values.out) || (!linking && values.api)) {
|
|
37
|
+
console.error(USAGE);
|
|
38
|
+
return values.help ? 0 : 2;
|
|
39
|
+
}
|
|
40
|
+
const keyFile = path.resolve(values.key ?? defaultKeyFile());
|
|
41
|
+
const { key, created } = await loadOrCreateKey(keyFile);
|
|
42
|
+
const owner = fingerprint(key);
|
|
43
|
+
if (created) {
|
|
44
|
+
console.log(`Made your publisher key: ${keyFile}`);
|
|
45
|
+
console.log("Back this file up and never share it. Without it your next version counts as a different publisher.");
|
|
46
|
+
}
|
|
47
|
+
if (linking) {
|
|
48
|
+
const linked = await linkKey({ key, code: positionals[1], apiUrl: values.api ?? DEFAULT_API_URL });
|
|
49
|
+
console.log(`Linked publisher key ${linked.fingerprint} to your Tavora account.`);
|
|
50
|
+
console.log(linked.name ? `Players now see your modules as "by ${linked.name}".` : "Choose a developer name in Tavora so players see it.");
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const dir = path.resolve(positionals[0]);
|
|
54
|
+
const { manifest, zip } = await packModule(dir, key);
|
|
55
|
+
const out = path.resolve(values.out ?? `${manifest.id}-${manifest.version}.zip`);
|
|
56
|
+
await writeFile(out, zip);
|
|
57
|
+
console.log(`Packed ${manifest.name} ${manifest.version} into ${out} (${zip.length} bytes)`);
|
|
58
|
+
console.log(`Signed by publisher ${owner}`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
main().then((code) => (process.exitCode = code), (error) => {
|
|
62
|
+
console.error(error instanceof PackError || error instanceof LinkError ? `tavora-pack: ${error.message}` : error);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { defaultKeyFile, loadOrCreateKey } from "./keys.ts";
|
|
2
|
+
export { cleanLinkCode, DEFAULT_API_URL, LINK_STATEMENT, LinkError, linkKey, linkRequest } from "./link.ts";
|
|
3
|
+
export { collectFiles, MANIFEST_FILE, packModule, PackError } from "./pack.ts";
|
|
4
|
+
export { fingerprint, SIGNATURE_FILE, signFiles, verifyFiles, type PackageSignature, type VerifyResult } from "./signature.ts";
|
|
5
|
+
export { isSafeEntryName, readZip, writeZip, ZIP_LIMITS, ZipError, type ZipFile, type ZipLimits } from "./zip.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5G,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/H,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { defaultKeyFile, loadOrCreateKey } from "./keys.js";
|
|
2
|
+
export { cleanLinkCode, DEFAULT_API_URL, LINK_STATEMENT, LinkError, linkKey, linkRequest } from "./link.js";
|
|
3
|
+
export { collectFiles, MANIFEST_FILE, packModule, PackError } from "./pack.js";
|
|
4
|
+
export { fingerprint, SIGNATURE_FILE, signFiles, verifyFiles } from "./signature.js";
|
|
5
|
+
export { isSafeEntryName, readZip, writeZip, ZIP_LIMITS, ZipError } from "./zip.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC5G,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAA4C,MAAM,gBAAgB,CAAC;AAC/H,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAgC,MAAM,UAAU,CAAC"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type KeyObject } from "node:crypto";
|
|
2
|
+
/** Where the pack command keeps a publisher's key unless told otherwise. */
|
|
3
|
+
export declare function defaultKeyFile(): string;
|
|
4
|
+
/**
|
|
5
|
+
* The publisher's private key, made the first time. The key never leaves
|
|
6
|
+
* this PC: only its public half goes into packages (and, with accounts, into
|
|
7
|
+
* the publisher's profile). `created` is true when it was made just now.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadOrCreateKey(file: string): Promise<{
|
|
10
|
+
key: KeyObject;
|
|
11
|
+
created: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAKpF,4EAA4E;AAC5E,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAajG"}
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { generateKeyPairSync, createPrivateKey } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/** Where the pack command keeps a publisher's key unless told otherwise. */
|
|
6
|
+
export function defaultKeyFile() {
|
|
7
|
+
return path.join(homedir(), ".tavora", "publisher-key.pem");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The publisher's private key, made the first time. The key never leaves
|
|
11
|
+
* this PC: only its public half goes into packages (and, with accounts, into
|
|
12
|
+
* the publisher's profile). `created` is true when it was made just now.
|
|
13
|
+
*/
|
|
14
|
+
export async function loadOrCreateKey(file) {
|
|
15
|
+
try {
|
|
16
|
+
const key = createPrivateKey(await readFile(file, "utf8"));
|
|
17
|
+
if (key.asymmetricKeyType !== "ed25519")
|
|
18
|
+
throw new Error(`${file} is not an Ed25519 key`);
|
|
19
|
+
return { key, created: false };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code !== "ENOENT")
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
26
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
27
|
+
// "wx" never replaces a key that appeared in the meantime; 0600 keeps it the owner's.
|
|
28
|
+
await writeFile(file, privateKey.export({ format: "pem", type: "pkcs8" }), { flag: "wx", mode: 0o600 });
|
|
29
|
+
return { key: privateKey, created: true };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAkB,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,4EAA4E;AAC5E,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3D,IAAI,GAAG,CAAC,iBAAiB,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;QAC1F,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IACtE,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACtD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,sFAAsF;IACtF,MAAM,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxG,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC"}
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type KeyObject } from "node:crypto";
|
|
2
|
+
export declare const DEFAULT_API_URL = "https://api.mytavora.app";
|
|
3
|
+
/** What is signed: this line, then the code. Nothing else Tavora signs starts like it. */
|
|
4
|
+
export declare const LINK_STATEMENT = "tavora-key-link-1";
|
|
5
|
+
export declare class LinkError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** The code as Tavora shows it ("ABCD-EFGH"), also typed in small letters or with spaces; null when it cannot be one. */
|
|
8
|
+
export declare function cleanLinkCode(input: string): string | null;
|
|
9
|
+
/** What the service is sent: the code, the public half and the key's signature over the link statement. */
|
|
10
|
+
export declare function linkRequest(key: KeyObject, code: string): {
|
|
11
|
+
code: string;
|
|
12
|
+
publicKey: string;
|
|
13
|
+
signature: string;
|
|
14
|
+
};
|
|
15
|
+
/** Links the key with the code. Resolves with the key's fingerprint and the developer name players will see. */
|
|
16
|
+
export declare function linkKey(options: {
|
|
17
|
+
key: KeyObject;
|
|
18
|
+
code: string;
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
fingerprint: string;
|
|
23
|
+
name: string | null;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAGpE,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,0FAA0F;AAC1F,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAGlD,qBAAa,SAAU,SAAQ,KAAK;CAAG;AAEvC,yHAAyH;AACzH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1D;AAOD,2GAA2G;AAC3G,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAEhH;AAWD,gHAAgH;AAChH,wBAAsB,OAAO,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAqBrK"}
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Links a publisher key to a Tavora account, so players see the account's developer name instead
|
|
2
|
+
// of the key's fingerprint. The developer gets a one-time code in Tavora (Settings > Account,
|
|
3
|
+
// signed in); this signs the code with the key and sends the signature and the key's public half to
|
|
4
|
+
// Tavora's service. The secret key never leaves this PC.
|
|
5
|
+
import { createPublicKey, sign } from "node:crypto";
|
|
6
|
+
import { fingerprint } from "./signature.js";
|
|
7
|
+
export const DEFAULT_API_URL = "https://api.mytavora.app";
|
|
8
|
+
/** What is signed: this line, then the code. Nothing else Tavora signs starts like it. */
|
|
9
|
+
export const LINK_STATEMENT = "tavora-key-link-1";
|
|
10
|
+
const CODE = /^[23456789ABCDEFGHJKMNPQRSTUVWXYZ]{8}$/;
|
|
11
|
+
export class LinkError extends Error {
|
|
12
|
+
}
|
|
13
|
+
/** The code as Tavora shows it ("ABCD-EFGH"), also typed in small letters or with spaces; null when it cannot be one. */
|
|
14
|
+
export function cleanLinkCode(input) {
|
|
15
|
+
const code = input.toUpperCase().replace(/[\s-]/g, "");
|
|
16
|
+
return CODE.test(code) ? code : null;
|
|
17
|
+
}
|
|
18
|
+
/** The raw 32-byte public half of an Ed25519 key, base64. */
|
|
19
|
+
function publicHalf(key) {
|
|
20
|
+
return (key.type === "private" ? createPublicKey(key) : key).export({ format: "der", type: "spki" }).subarray(12).toString("base64");
|
|
21
|
+
}
|
|
22
|
+
/** What the service is sent: the code, the public half and the key's signature over the link statement. */
|
|
23
|
+
export function linkRequest(key, code) {
|
|
24
|
+
return { code, publicKey: publicHalf(key), signature: sign(null, Buffer.from(`${LINK_STATEMENT}\n${code}`, "utf8"), key).toString("base64") };
|
|
25
|
+
}
|
|
26
|
+
const MESSAGES = {
|
|
27
|
+
"wrong-code": "That code is wrong or has expired. Get a new one in Tavora: Settings > Account > Link a publisher key.",
|
|
28
|
+
"bad-signature": "The service could not check the key's signature.",
|
|
29
|
+
"key-taken": "This key is already linked to another Tavora account.",
|
|
30
|
+
"too-many-keys": "Your account has as many keys as it can link. Unlink one in Tavora first.",
|
|
31
|
+
"too-many": "Too many tries. Wait a few minutes and try again.",
|
|
32
|
+
unavailable: "Linking keys is not available right now.",
|
|
33
|
+
};
|
|
34
|
+
/** Links the key with the code. Resolves with the key's fingerprint and the developer name players will see. */
|
|
35
|
+
export async function linkKey(options) {
|
|
36
|
+
const code = cleanLinkCode(options.code);
|
|
37
|
+
if (!code)
|
|
38
|
+
throw new LinkError("That is not a link code. It looks like ABCD-EFGH, from Tavora: Settings > Account > Link a publisher key.");
|
|
39
|
+
const url = `${(options.apiUrl ?? DEFAULT_API_URL).replace(/\/+$/, "")}/v1/publishers/link`;
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await (options.fetch ?? fetch)(url, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
45
|
+
body: JSON.stringify(linkRequest(options.key, code)),
|
|
46
|
+
redirect: "error",
|
|
47
|
+
signal: AbortSignal.timeout(15_000),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new LinkError(`Tavora's service could not be reached (${url}). Check your internet connection.`);
|
|
52
|
+
}
|
|
53
|
+
const answer = (await response.json().catch(() => ({})));
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
throw new LinkError((typeof answer.reason === "string" && MESSAGES[answer.reason]) || `The service refused the link (${response.status}).`);
|
|
56
|
+
const own = fingerprint(options.key);
|
|
57
|
+
if (answer.fingerprint !== own)
|
|
58
|
+
throw new LinkError("The service answered for a different key.");
|
|
59
|
+
return { fingerprint: own, name: typeof answer.name === "string" ? answer.name : null };
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=link.js.map
|
package/dist/link.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"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"}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KeyObject } from "node:crypto";
|
|
2
|
+
import { type ModuleManifest } from "@tavora/module-schema";
|
|
3
|
+
import { type ZipFile } from "./zip.ts";
|
|
4
|
+
export declare const MANIFEST_FILE = "tavora.module.json";
|
|
5
|
+
export declare class PackError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The files of a module folder that go into its zip: the manifest, the folders
|
|
9
|
+
* its entry pages are in (usually dist/), and the icons. When the entry page sits
|
|
10
|
+
* next to the manifest, everything goes except node_modules and dot files.
|
|
11
|
+
*/
|
|
12
|
+
export declare function collectFiles(dir: string): Promise<{
|
|
13
|
+
manifest: ModuleManifest;
|
|
14
|
+
files: ZipFile[];
|
|
15
|
+
}>;
|
|
16
|
+
/** Packs a module folder into a zip signed with the publisher's key. */
|
|
17
|
+
export declare function packModule(dir: string, key: KeyObject): Promise<{
|
|
18
|
+
manifest: ModuleManifest;
|
|
19
|
+
zip: Buffer;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAmC,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE7F,OAAO,EAA4D,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAElG,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAElD,qBAAa,SAAU,SAAQ,KAAK;CAAG;AAEvC;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC,CAgCvG;AAmBD,wEAAwE;AACxE,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAWhH"}
|
package/dist/pack.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { lstat, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { manifestFiles, validateManifest } from "@tavora/module-schema";
|
|
4
|
+
import { signFiles, SIGNATURE_FILE } from "./signature.js";
|
|
5
|
+
import { isSafeEntryName, readZip, writeZip, ZIP_LIMITS, ZipError } from "./zip.js";
|
|
6
|
+
export const MANIFEST_FILE = "tavora.module.json";
|
|
7
|
+
export class PackError extends Error {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The files of a module folder that go into its zip: the manifest, the folders
|
|
11
|
+
* its entry pages are in (usually dist/), and the icons. When the entry page sits
|
|
12
|
+
* next to the manifest, everything goes except node_modules and dot files.
|
|
13
|
+
*/
|
|
14
|
+
export async function collectFiles(dir) {
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = JSON.parse(await readFile(path.join(dir, MANIFEST_FILE), "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new PackError(`${MANIFEST_FILE} is missing or not valid JSON in ${dir}`);
|
|
21
|
+
}
|
|
22
|
+
const result = validateManifest(raw);
|
|
23
|
+
if (!result.ok)
|
|
24
|
+
throw new PackError(`${MANIFEST_FILE} is not valid: ${result.errors.join("; ")}`);
|
|
25
|
+
const { manifest } = result;
|
|
26
|
+
const names = new Set([MANIFEST_FILE]);
|
|
27
|
+
const { entries, icons } = manifestFiles(manifest);
|
|
28
|
+
// Panel types may have pages of their own; each page's folder goes in.
|
|
29
|
+
const folders = new Set(entries.map((entry) => (entry.includes("/") ? entry.split("/")[0] : "")));
|
|
30
|
+
for (const folder of folders)
|
|
31
|
+
for (const name of await walk(dir, folder))
|
|
32
|
+
names.add(name);
|
|
33
|
+
for (const icon of icons)
|
|
34
|
+
names.add(icon);
|
|
35
|
+
for (const entry of entries)
|
|
36
|
+
if (!names.has(entry))
|
|
37
|
+
throw new PackError(`the entry page ${entry} is missing (is the module built?)`);
|
|
38
|
+
names.delete(SIGNATURE_FILE);
|
|
39
|
+
const files = [];
|
|
40
|
+
let total = 0;
|
|
41
|
+
for (const name of [...names].sort()) {
|
|
42
|
+
if (!isSafeEntryName(name))
|
|
43
|
+
throw new PackError(`${name} cannot go into a module zip (its name is not allowed)`);
|
|
44
|
+
const info = await lstat(path.join(dir, name)).catch(() => null);
|
|
45
|
+
if (!info?.isFile())
|
|
46
|
+
throw new PackError(`${name} is missing or not a plain file`);
|
|
47
|
+
total += info.size;
|
|
48
|
+
if (total > ZIP_LIMITS.maxUnpackedBytes)
|
|
49
|
+
throw new PackError("the module is bigger than a module zip may be");
|
|
50
|
+
files.push({ name, data: await readFile(path.join(dir, name)) });
|
|
51
|
+
}
|
|
52
|
+
if (files.length > ZIP_LIMITS.maxFiles - 1)
|
|
53
|
+
throw new PackError(`a module zip may have at most ${ZIP_LIMITS.maxFiles - 1} files`);
|
|
54
|
+
return { manifest, files };
|
|
55
|
+
}
|
|
56
|
+
async function walk(root, folder) {
|
|
57
|
+
const found = [];
|
|
58
|
+
const visit = async (relative) => {
|
|
59
|
+
for (const entry of await readdir(path.join(root, relative), { withFileTypes: true })) {
|
|
60
|
+
const name = relative ? `${relative}/${entry.name}` : entry.name;
|
|
61
|
+
if (!folder && (entry.name.startsWith(".") || entry.name === "node_modules"))
|
|
62
|
+
continue;
|
|
63
|
+
// Links are never followed: the zip holds what is really in the folder.
|
|
64
|
+
if (entry.isDirectory())
|
|
65
|
+
await visit(name);
|
|
66
|
+
else if (entry.isFile())
|
|
67
|
+
found.push(name);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
await visit(folder).catch((error) => {
|
|
71
|
+
if (error.code !== "ENOENT")
|
|
72
|
+
throw error;
|
|
73
|
+
});
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
/** Packs a module folder into a zip signed with the publisher's key. */
|
|
77
|
+
export async function packModule(dir, key) {
|
|
78
|
+
const { manifest, files } = await collectFiles(dir);
|
|
79
|
+
const zip = writeZip([...files, signFiles(files, key)]);
|
|
80
|
+
if (zip.length > ZIP_LIMITS.maxArchiveBytes)
|
|
81
|
+
throw new PackError("the zip is bigger than a module zip may be");
|
|
82
|
+
try {
|
|
83
|
+
readZip(zip);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof ZipError)
|
|
87
|
+
throw new PackError(`Tavora would refuse this zip: ${error.message}`);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
return { manifest, zip };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=pack.js.map
|
package/dist/pack.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAgB,MAAM,UAAU,CAAC;AAElG,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD,MAAM,OAAO,SAAU,SAAQ,KAAK;CAAG;AAEvC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW;IAC5C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,GAAG,aAAa,oCAAoC,GAAG,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,aAAa,kBAAkB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClG,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAE5B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,CAAC,aAAa,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACnD,uEAAuE;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnG,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1F,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,OAAO;QAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,kBAAkB,KAAK,oCAAoC,CAAC,CAAC;IACrI,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAE7B,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,wDAAwD,CAAC,CAAC;QACjH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,iCAAiC,CAAC,CAAC;QACnF,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,IAAI,KAAK,GAAG,UAAU,CAAC,gBAAgB;YAAE,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAC;QAC9G,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,iCAAiC,UAAU,CAAC,QAAQ,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClI,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAY,EAAE,MAAc;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;QACvC,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACtF,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACjE,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC;gBAAE,SAAS;YACvF,wEAAwE;YACxE,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;iBACtC,IAAI,KAAK,CAAC,MAAM,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;QACzD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;IAC3C,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,GAAc;IAC1D,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,GAAG,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,eAAe;QAAE,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IAC/G,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACrG,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type KeyObject } from "node:crypto";
|
|
2
|
+
import type { ZipFile } from "./zip.ts";
|
|
3
|
+
export declare const SIGNATURE_FILE = "tavora.signature.json";
|
|
4
|
+
declare const FORMAT = "tavora-signature-1";
|
|
5
|
+
export interface PackageSignature {
|
|
6
|
+
format: typeof FORMAT;
|
|
7
|
+
/** The raw 32-byte Ed25519 public key, base64. */
|
|
8
|
+
publicKey: string;
|
|
9
|
+
/** SHA-256 (hex) of every file in the zip except this one. */
|
|
10
|
+
files: Record<string, string>;
|
|
11
|
+
/** Ed25519 signature (base64) over signedBytes(). */
|
|
12
|
+
signature: string;
|
|
13
|
+
}
|
|
14
|
+
export type VerifyResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
owner: string;
|
|
17
|
+
signed: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
/** The owner id of a key (either half): the first 16 bytes of the SHA-256 of the raw public key, in hex. */
|
|
23
|
+
export declare function fingerprint(key: KeyObject): string;
|
|
24
|
+
/** The tavora.signature.json for these files, signed with the publisher's private key. */
|
|
25
|
+
export declare function signFiles(files: readonly ZipFile[], privateKey: KeyObject): ZipFile;
|
|
26
|
+
/**
|
|
27
|
+
* Who owns a package's data. A zip without a signature belongs to `local`,
|
|
28
|
+
* like an unsigned folder. A signature must cover exactly the zip's other
|
|
29
|
+
* files, each with its hash, or the zip is refused.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyFiles(files: readonly ZipFile[]): VerifyResult;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAIA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAExF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,eAAO,MAAM,cAAc,0BAA0B,CAAC;AACtD,QAAA,MAAM,MAAM,uBAAuB,CAAC;AAIpC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,MAAM,CAAC;IACtB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExG,4GAA4G;AAC5G,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAElD;AAoBD,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,GAAG,OAAO,CAUnF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CA8BnE"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Publisher signatures for module zips (the decision log, publisher row of
|
|
2
|
+
// 2026-10-16). The publisher is an Ed25519 key; the owner of a module's data
|
|
3
|
+
// is the fingerprint of that key. The zip carries tavora.signature.json with
|
|
4
|
+
// the public key, a SHA-256 of every other file and a signature over them.
|
|
5
|
+
import { createHash, createPublicKey, sign, verify } from "node:crypto";
|
|
6
|
+
import { isOwnerId, LOCAL_OWNER } from "@tavora/module-schema";
|
|
7
|
+
export const SIGNATURE_FILE = "tavora.signature.json";
|
|
8
|
+
const FORMAT = "tavora-signature-1";
|
|
9
|
+
// DER prefix of an Ed25519 public key in SubjectPublicKeyInfo form; the raw key follows.
|
|
10
|
+
const ED25519_SPKI = Buffer.from("302a300506032b6570032100", "hex");
|
|
11
|
+
/** The owner id of a key (either half): the first 16 bytes of the SHA-256 of the raw public key, in hex. */
|
|
12
|
+
export function fingerprint(key) {
|
|
13
|
+
return createHash("sha256").update(rawPublicKey(key)).digest("hex").slice(0, 32);
|
|
14
|
+
}
|
|
15
|
+
function rawPublicKey(key) {
|
|
16
|
+
const der = (key.type === "private" ? createPublicKey(key) : key).export({ format: "der", type: "spki" });
|
|
17
|
+
if (der.length !== 44 || !der.subarray(0, 12).equals(ED25519_SPKI))
|
|
18
|
+
throw new Error("not an Ed25519 public key");
|
|
19
|
+
return der.subarray(12);
|
|
20
|
+
}
|
|
21
|
+
function sha256(data) {
|
|
22
|
+
return createHash("sha256").update(data).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
/** What is signed: the format, the key and every file's hash in path order, as JSON. */
|
|
25
|
+
function signedBytes(publicKey, files) {
|
|
26
|
+
const sorted = Object.keys(files)
|
|
27
|
+
.sort()
|
|
28
|
+
.map((name) => [name, files[name]]);
|
|
29
|
+
return Buffer.from(JSON.stringify({ format: FORMAT, publicKey, files: sorted }), "utf8");
|
|
30
|
+
}
|
|
31
|
+
/** The tavora.signature.json for these files, signed with the publisher's private key. */
|
|
32
|
+
export function signFiles(files, privateKey) {
|
|
33
|
+
const publicKey = rawPublicKey(privateKey).toString("base64");
|
|
34
|
+
const hashes = {};
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
if (file.name === SIGNATURE_FILE)
|
|
37
|
+
throw new Error(`${SIGNATURE_FILE} is made by the pack command`);
|
|
38
|
+
hashes[file.name] = sha256(file.data);
|
|
39
|
+
}
|
|
40
|
+
const signature = sign(null, signedBytes(publicKey, hashes), privateKey).toString("base64");
|
|
41
|
+
const body = { format: FORMAT, publicKey, files: hashes, signature };
|
|
42
|
+
return { name: SIGNATURE_FILE, data: Buffer.from(`${JSON.stringify(body, null, 2)}\n`, "utf8") };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Who owns a package's data. A zip without a signature belongs to `local`,
|
|
46
|
+
* like an unsigned folder. A signature must cover exactly the zip's other
|
|
47
|
+
* files, each with its hash, or the zip is refused.
|
|
48
|
+
*/
|
|
49
|
+
export function verifyFiles(files) {
|
|
50
|
+
const signatureFile = files.find((file) => file.name === SIGNATURE_FILE);
|
|
51
|
+
if (!signatureFile)
|
|
52
|
+
return { ok: true, owner: LOCAL_OWNER, signed: false };
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
body = JSON.parse(signatureFile.data.toString("utf8"));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { ok: false, reason: "its signature file cannot be read" };
|
|
59
|
+
}
|
|
60
|
+
if (!isSignature(body))
|
|
61
|
+
return { ok: false, reason: "its signature file is not in a format this Tavora knows" };
|
|
62
|
+
const raw = Buffer.from(body.publicKey, "base64");
|
|
63
|
+
if (raw.length !== 32 || raw.toString("base64") !== body.publicKey)
|
|
64
|
+
return { ok: false, reason: "its signature has no valid key" };
|
|
65
|
+
const publicKey = createPublicKey({ key: Buffer.concat([ED25519_SPKI, raw]), format: "der", type: "spki" });
|
|
66
|
+
const signature = Buffer.from(body.signature, "base64");
|
|
67
|
+
if (signature.length !== 64 || !verify(null, signedBytes(body.publicKey, body.files), publicKey, signature)) {
|
|
68
|
+
return { ok: false, reason: "its signature does not match" };
|
|
69
|
+
}
|
|
70
|
+
const others = files.filter((file) => file !== signatureFile);
|
|
71
|
+
const listed = Object.keys(body.files);
|
|
72
|
+
if (listed.length !== others.length)
|
|
73
|
+
return { ok: false, reason: "its files do not match what was signed" };
|
|
74
|
+
for (const file of others) {
|
|
75
|
+
if (!Object.hasOwn(body.files, file.name) || body.files[file.name] !== sha256(file.data)) {
|
|
76
|
+
return { ok: false, reason: "its files do not match what was signed" };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const owner = fingerprint(publicKey);
|
|
80
|
+
if (!isOwnerId(owner))
|
|
81
|
+
return { ok: false, reason: "its signature has no valid key" };
|
|
82
|
+
return { ok: true, owner, signed: true };
|
|
83
|
+
}
|
|
84
|
+
function isSignature(value) {
|
|
85
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
86
|
+
return false;
|
|
87
|
+
const body = value;
|
|
88
|
+
if (Object.keys(body).sort().join() !== "files,format,publicKey,signature")
|
|
89
|
+
return false;
|
|
90
|
+
if (body.format !== FORMAT || typeof body.publicKey !== "string" || typeof body.signature !== "string")
|
|
91
|
+
return false;
|
|
92
|
+
const files = body.files;
|
|
93
|
+
if (typeof files !== "object" || files === null || Array.isArray(files))
|
|
94
|
+
return false;
|
|
95
|
+
return Object.values(files).every((hash) => typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash));
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"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"}
|
package/dist/zip.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ZipLimits {
|
|
2
|
+
maxArchiveBytes: number;
|
|
3
|
+
maxUnpackedBytes: number;
|
|
4
|
+
maxFiles: number;
|
|
5
|
+
maxRatio: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const ZIP_LIMITS: Readonly<ZipLimits>;
|
|
8
|
+
export declare class ZipError extends Error {
|
|
9
|
+
}
|
|
10
|
+
export interface ZipFile {
|
|
11
|
+
/** Path inside the package, with "/" between folders. */
|
|
12
|
+
name: string;
|
|
13
|
+
data: Buffer;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A path a package may contain: relative, "/" only, no "." or ".." parts, no
|
|
17
|
+
* drive letters or other colons, no control characters, and no part Windows
|
|
18
|
+
* cannot create (reserved device names, a trailing dot or space).
|
|
19
|
+
*/
|
|
20
|
+
export declare function isSafeEntryName(name: string): boolean;
|
|
21
|
+
export declare function writeZip(files: readonly ZipFile[]): Buffer;
|
|
22
|
+
/**
|
|
23
|
+
* Reads every file of a package zip into memory, or throws a ZipError saying
|
|
24
|
+
* why the zip is refused. Folder entries are skipped; anything that is not a
|
|
25
|
+
* plain file is refused.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readZip(zip: Buffer, limits?: Readonly<ZipLimits>): ZipFile[];
|
|
28
|
+
//# sourceMappingURL=zip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,SAAS,CAQ1C,CAAC;AAEF,qBAAa,QAAS,SAAQ,KAAK;CAAG;AAEtC,MAAM,WAAW,OAAO;IACtB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAWD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIrD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAiD1D;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,CAAC,SAAS,CAAc,GAAG,OAAO,EAAE,CA0ExF"}
|
package/dist/zip.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// A small zip writer and a strict zip reader for module packages. The reader
|
|
2
|
+
// is the installer's first line (the threat model, "Rules for a future zip
|
|
3
|
+
// installer"): it refuses links, unsafe names, encryption and zip64, and caps
|
|
4
|
+
// sizes and compression before anything is unpacked.
|
|
5
|
+
import { crc32, deflateRawSync, inflateRawSync } from "node:zlib";
|
|
6
|
+
export const ZIP_LIMITS = {
|
|
7
|
+
/** The zip file itself. */
|
|
8
|
+
maxArchiveBytes: 50 * 1024 * 1024,
|
|
9
|
+
/** All files unpacked together. */
|
|
10
|
+
maxUnpackedBytes: 200 * 1024 * 1024,
|
|
11
|
+
maxFiles: 2_000,
|
|
12
|
+
/** Unpacked size over packed size, checked once a package unpacks to more than 1 MiB. */
|
|
13
|
+
maxRatio: 100,
|
|
14
|
+
};
|
|
15
|
+
export class ZipError extends Error {
|
|
16
|
+
}
|
|
17
|
+
const LOCAL = 0x04034b50;
|
|
18
|
+
const CENTRAL = 0x02014b50;
|
|
19
|
+
const END = 0x06054b50;
|
|
20
|
+
const UTF8 = 0x0800;
|
|
21
|
+
// 1980-01-01 00:00, so the same files always give the same zip.
|
|
22
|
+
const DOS_DATE = (1 << 5) | 1;
|
|
23
|
+
const RESERVED = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
|
24
|
+
/**
|
|
25
|
+
* A path a package may contain: relative, "/" only, no "." or ".." parts, no
|
|
26
|
+
* drive letters or other colons, no control characters, and no part Windows
|
|
27
|
+
* cannot create (reserved device names, a trailing dot or space).
|
|
28
|
+
*/
|
|
29
|
+
export function isSafeEntryName(name) {
|
|
30
|
+
if (name.length === 0 || name.length > 256)
|
|
31
|
+
return false;
|
|
32
|
+
if (name.startsWith("/") || name.includes("\\") || name.includes(":") || /[\u0000-\u001f\u007f<>"|?*]/.test(name))
|
|
33
|
+
return false;
|
|
34
|
+
return name.split("/").every((part) => part !== "" && part !== "." && part !== ".." && !RESERVED.test(part) && !/[. ]$/.test(part));
|
|
35
|
+
}
|
|
36
|
+
export function writeZip(files) {
|
|
37
|
+
const locals = [];
|
|
38
|
+
const centrals = [];
|
|
39
|
+
let offset = 0;
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (!isSafeEntryName(file.name))
|
|
42
|
+
throw new ZipError(`unsafe file name: ${file.name}`);
|
|
43
|
+
const name = Buffer.from(file.name, "utf8");
|
|
44
|
+
const packed = deflateRawSync(file.data, { level: 9 });
|
|
45
|
+
const crc = crc32(file.data);
|
|
46
|
+
const local = Buffer.alloc(30);
|
|
47
|
+
local.writeUInt32LE(LOCAL, 0);
|
|
48
|
+
local.writeUInt16LE(20, 4);
|
|
49
|
+
local.writeUInt16LE(UTF8, 6);
|
|
50
|
+
local.writeUInt16LE(8, 8);
|
|
51
|
+
local.writeUInt16LE(0, 10);
|
|
52
|
+
local.writeUInt16LE(DOS_DATE, 12);
|
|
53
|
+
local.writeUInt32LE(crc, 14);
|
|
54
|
+
local.writeUInt32LE(packed.length, 18);
|
|
55
|
+
local.writeUInt32LE(file.data.length, 22);
|
|
56
|
+
local.writeUInt16LE(name.length, 26);
|
|
57
|
+
local.writeUInt16LE(0, 28);
|
|
58
|
+
const central = Buffer.alloc(46);
|
|
59
|
+
central.writeUInt32LE(CENTRAL, 0);
|
|
60
|
+
central.writeUInt16LE(20, 4); // made by: MS-DOS, so no Unix modes are claimed
|
|
61
|
+
central.writeUInt16LE(20, 6);
|
|
62
|
+
central.writeUInt16LE(UTF8, 8);
|
|
63
|
+
central.writeUInt16LE(8, 10);
|
|
64
|
+
central.writeUInt16LE(0, 12);
|
|
65
|
+
central.writeUInt16LE(DOS_DATE, 14);
|
|
66
|
+
central.writeUInt32LE(crc, 16);
|
|
67
|
+
central.writeUInt32LE(packed.length, 20);
|
|
68
|
+
central.writeUInt32LE(file.data.length, 24);
|
|
69
|
+
central.writeUInt16LE(name.length, 28);
|
|
70
|
+
central.writeUInt32LE(offset, 42);
|
|
71
|
+
locals.push(local, name, packed);
|
|
72
|
+
centrals.push(central, name);
|
|
73
|
+
offset += local.length + name.length + packed.length;
|
|
74
|
+
}
|
|
75
|
+
const directory = Buffer.concat(centrals);
|
|
76
|
+
const end = Buffer.alloc(22);
|
|
77
|
+
end.writeUInt32LE(END, 0);
|
|
78
|
+
end.writeUInt16LE(files.length, 8);
|
|
79
|
+
end.writeUInt16LE(files.length, 10);
|
|
80
|
+
end.writeUInt32LE(directory.length, 12);
|
|
81
|
+
end.writeUInt32LE(offset, 16);
|
|
82
|
+
return Buffer.concat([...locals, directory, end]);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reads every file of a package zip into memory, or throws a ZipError saying
|
|
86
|
+
* why the zip is refused. Folder entries are skipped; anything that is not a
|
|
87
|
+
* plain file is refused.
|
|
88
|
+
*/
|
|
89
|
+
export function readZip(zip, limits = ZIP_LIMITS) {
|
|
90
|
+
if (zip.length > limits.maxArchiveBytes)
|
|
91
|
+
throw new ZipError("the zip is too big");
|
|
92
|
+
const endAt = findEnd(zip);
|
|
93
|
+
const count = zip.readUInt16LE(endAt + 10);
|
|
94
|
+
const size = zip.readUInt32LE(endAt + 12);
|
|
95
|
+
const start = zip.readUInt32LE(endAt + 16);
|
|
96
|
+
if (zip.readUInt16LE(endAt + 4) !== 0 || zip.readUInt16LE(endAt + 6) !== 0 || zip.readUInt16LE(endAt + 8) !== count) {
|
|
97
|
+
throw new ZipError("split zips are not supported");
|
|
98
|
+
}
|
|
99
|
+
if (count === 0xffff || size === 0xffffffff || start === 0xffffffff)
|
|
100
|
+
throw new ZipError("zip64 is not supported");
|
|
101
|
+
if (start + size > endAt)
|
|
102
|
+
throw new ZipError("the zip is damaged");
|
|
103
|
+
if (count > limits.maxFiles)
|
|
104
|
+
throw new ZipError("the zip has too many files");
|
|
105
|
+
const files = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
let unpacked = 0;
|
|
108
|
+
let at = start;
|
|
109
|
+
for (let i = 0; i < count; i++) {
|
|
110
|
+
if (at + 46 > endAt || zip.readUInt32LE(at) !== CENTRAL)
|
|
111
|
+
throw new ZipError("the zip is damaged");
|
|
112
|
+
const madeBy = zip.readUInt16LE(at + 4) >> 8;
|
|
113
|
+
const flags = zip.readUInt16LE(at + 8);
|
|
114
|
+
const method = zip.readUInt16LE(at + 10);
|
|
115
|
+
const crc = zip.readUInt32LE(at + 16);
|
|
116
|
+
const packedSize = zip.readUInt32LE(at + 20);
|
|
117
|
+
const plainSize = zip.readUInt32LE(at + 24);
|
|
118
|
+
const nameLength = zip.readUInt16LE(at + 28);
|
|
119
|
+
const extraLength = zip.readUInt16LE(at + 30);
|
|
120
|
+
const commentLength = zip.readUInt16LE(at + 32);
|
|
121
|
+
const external = zip.readUInt32LE(at + 38);
|
|
122
|
+
const localAt = zip.readUInt32LE(at + 42);
|
|
123
|
+
const raw = zip.subarray(at + 46, at + 46 + nameLength);
|
|
124
|
+
at += 46 + nameLength + extraLength + commentLength;
|
|
125
|
+
if (at > endAt)
|
|
126
|
+
throw new ZipError("the zip is damaged");
|
|
127
|
+
const name = new TextDecoder("utf-8", { fatal: false }).decode(raw);
|
|
128
|
+
if (flags & 0x0001)
|
|
129
|
+
throw new ZipError("encrypted zips are not supported");
|
|
130
|
+
// Unix zips keep the file type in the top bits: only plain files (and folders) are allowed.
|
|
131
|
+
const type = madeBy === 3 ? (external >>> 16) & 0o170000 : 0;
|
|
132
|
+
const isFolder = name.endsWith("/");
|
|
133
|
+
if (type !== 0 && type !== (isFolder ? 0o040000 : 0o100000))
|
|
134
|
+
throw new ZipError(`${name} is a link or special file`);
|
|
135
|
+
if (isFolder) {
|
|
136
|
+
if (!isSafeEntryName(name.slice(0, -1)))
|
|
137
|
+
throw new ZipError(`unsafe path: ${name}`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!isSafeEntryName(name))
|
|
141
|
+
throw new ZipError(`unsafe path: ${name}`);
|
|
142
|
+
const folded = name.toLowerCase();
|
|
143
|
+
if (seen.has(folded))
|
|
144
|
+
throw new ZipError(`${name} is in the zip twice`);
|
|
145
|
+
seen.add(folded);
|
|
146
|
+
if (method !== 0 && method !== 8)
|
|
147
|
+
throw new ZipError(`${name} uses a compression zips for Tavora do not support`);
|
|
148
|
+
if (packedSize === 0xffffffff || plainSize === 0xffffffff)
|
|
149
|
+
throw new ZipError("zip64 is not supported");
|
|
150
|
+
unpacked += plainSize;
|
|
151
|
+
if (unpacked > limits.maxUnpackedBytes)
|
|
152
|
+
throw new ZipError("the module is too big unpacked");
|
|
153
|
+
if (localAt + 30 > start || zip.readUInt32LE(localAt) !== LOCAL)
|
|
154
|
+
throw new ZipError("the zip is damaged");
|
|
155
|
+
const dataAt = localAt + 30 + zip.readUInt16LE(localAt + 26) + zip.readUInt16LE(localAt + 28);
|
|
156
|
+
if (!zip.subarray(localAt + 30, localAt + 30 + zip.readUInt16LE(localAt + 26)).equals(raw))
|
|
157
|
+
throw new ZipError("the zip is damaged");
|
|
158
|
+
if (dataAt + packedSize > start)
|
|
159
|
+
throw new ZipError("the zip is damaged");
|
|
160
|
+
const packed = zip.subarray(dataAt, dataAt + packedSize);
|
|
161
|
+
let data;
|
|
162
|
+
if (method === 0) {
|
|
163
|
+
if (packedSize !== plainSize)
|
|
164
|
+
throw new ZipError("the zip is damaged");
|
|
165
|
+
data = Buffer.from(packed);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
try {
|
|
169
|
+
data = inflateRawSync(packed, { maxOutputLength: Math.max(plainSize, 1) });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new ZipError(`${name} cannot be unpacked`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (data.length !== plainSize || crc32(data) !== crc)
|
|
176
|
+
throw new ZipError(`${name} is damaged`);
|
|
177
|
+
files.push({ name, data });
|
|
178
|
+
}
|
|
179
|
+
if (unpacked > 1024 * 1024 && unpacked > zip.length * limits.maxRatio)
|
|
180
|
+
throw new ZipError("the zip unpacks to far more than its size");
|
|
181
|
+
return files;
|
|
182
|
+
}
|
|
183
|
+
function findEnd(zip) {
|
|
184
|
+
for (let at = zip.length - 22; at >= Math.max(0, zip.length - 22 - 0xffff); at--) {
|
|
185
|
+
if (zip.readUInt32LE(at) === END && at + 22 + zip.readUInt16LE(at + 20) === zip.length)
|
|
186
|
+
return at;
|
|
187
|
+
}
|
|
188
|
+
throw new ZipError("this is not a zip file");
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=zip.js.map
|
package/dist/zip.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../src/zip.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tavora/pack",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Packs a Tavora module into a zip signed with your publisher key, links the key to your Tavora account, and reads and checks such zips.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tavora",
|
|
8
|
+
"module",
|
|
9
|
+
"pack",
|
|
10
|
+
"sign",
|
|
11
|
+
"ed25519"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/wellbrained/tavora.git",
|
|
16
|
+
"directory": "packages/pack"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://docs.mytavora.app/guide/getting-started#share-the-module",
|
|
19
|
+
"bugs": "https://github.com/wellbrained/tavora/issues",
|
|
20
|
+
"bin": {
|
|
21
|
+
"tavora-pack": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"CHANGELOG.md"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.12"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@tavora/module-schema": "^0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.20.5"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"typecheck": "tsc --noEmit -p ."
|
|
51
|
+
}
|
|
6
52
|
}
|