refactor: Switch to Node-based package structure

- unbuild for building npm packages
- vitest for testing
- remove dependency on deno
This commit is contained in:
Shibo Lyu 2025-05-21 03:38:01 +08:00
parent 7f4d6ce3f3
commit 1962ecb298
45 changed files with 2293 additions and 525 deletions

View file

@ -1,33 +0,0 @@
name: Deno
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
# Uncomment this step to verify the use of 'deno fmt' on each commit.
# - name: Verify formatting
# run: deno fmt --check
- name: Run linter
run: deno lint
- name: Run tests
run: deno test -A

View file

@ -1,36 +1,35 @@
name: Publish on JSR & npm
name: Test & Publish on JSR & npm
on:
workflow_run:
workflows: [Deno]
types: [completed]
branches: [main]
push:
branches:
- main
jobs:
publish:
testAndPublish:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read
id-token: write # The OIDC ID token is used for authentication with JSR.
steps:
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22.x"
registry-url: "https://registry.npmjs.org"
- uses: pnpm/action-setup@v4
- name: Checkout repo
uses: actions/checkout@v4
- name: Build & test npm package
run: deno task build:npm
- name: Install dependencies
run: pnpm install
- name: Build & Test
run: pnpm build && pnpm test
- name: Publish to JSR
run: npx jsr publish
run: pnpm dlx jsr publish
- name: Publish to npm
run: |
cd npm
npx is-published@0.2.0 || npm publish --provenance --access public
run: pnpm dlx is-published@0.2.0 || pnpm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

2
.gitignore vendored
View file

@ -1 +1 @@
npm
node_modules

View file

@ -1,31 +0,0 @@
{
"lsp": {
"deno": {
"settings": {
"deno": {
"enable": true
}
}
}
},
"languages": {
"TypeScript": {
"language_servers": [
"deno",
"!typescript-language-server",
"!vtsls",
"!eslint"
],
"formatter": "language_server"
},
"TSX": {
"language_servers": [
"deno",
"!typescript-language-server",
"!vtsls",
"!eslint"
],
"formatter": "language_server"
}
}
}

View file

@ -1,13 +1,6 @@
// Static tasks configuration.
//
// Example:
[
{
"label": "Deno Test",
"command": "deno test -A"
},
{
"label": "Deno Test - Watch",
"command": "deno test -A --watch"
"label": "Test",
"command": "pnpm test"
}
]

View file

@ -1,25 +0,0 @@
import z from "zod";
import type { BlahSignedPayload } from "./mod.ts";
import {
type BlahPayloadSignee,
blahPayloadSigneeSchemaOf,
blahSignedPayloadSchemaOf,
} from "./signedPayload.ts";
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
const testPayloadSchema = z.object({
foo: z.string(),
});
type TestPayload = z.infer<typeof testPayloadSchema>;
Deno.test("type BlahPayloadSignee is accurate", () => {
assertTypeMatchesZodSchema<BlahPayloadSignee<TestPayload>>(
blahPayloadSigneeSchemaOf(testPayloadSchema),
);
});
Deno.test("type BlahSignedPayload is accurate", () => {
assertTypeMatchesZodSchema<BlahSignedPayload<TestPayload>>(
blahSignedPayloadSchemaOf(testPayloadSchema),
);
});

View file

@ -1,31 +0,0 @@
{
"name": "@blah/core",
"version": "0.8.3",
"exports": {
"./crypto": "./crypto/mod.ts",
"./identity": "./identity/mod.ts",
"./richText": "./richText/mod.ts"
},
"imports": {
"@deno/dnt": "jsr:@deno/dnt@^0.42.1",
"@std/expect": "jsr:@std/expect@^1.0.16",
"@std/testing": "jsr:@std/testing@^1.0.12",
"zod": "npm:zod@^3.25.7"
},
"tasks": {
"build:npm": "deno run -A ./scripts/build_npm.ts"
},
"publish": {
"include": ["LICENSE", "README.md", "crypto", "identity", "richText"],
"exclude": ["**/*.test.ts"]
},
"test": {
"exclude": ["npm/"]
},
"lint": {
"exclude": ["npm/"]
},
"fmt": {
"exclude": ["npm/"]
}
}

221
deno.lock generated
View file

@ -1,221 +0,0 @@
{
"version": "5",
"specifiers": {
"jsr:@david/code-block-writer@^13.0.2": "13.0.3",
"jsr:@deno/cache-dir@0.20": "0.20.1",
"jsr:@deno/dnt@~0.42.1": "0.42.1",
"jsr:@deno/graph@0.86": "0.86.9",
"jsr:@std/assert@^1.0.13": "1.0.13",
"jsr:@std/assert@^1.0.5": "1.0.5",
"jsr:@std/async@^1.0.13": "1.0.13",
"jsr:@std/bytes@^1.0.5": "1.0.6",
"jsr:@std/data-structures@^1.0.8": "1.0.8",
"jsr:@std/expect@^1.0.16": "1.0.16",
"jsr:@std/fmt@1": "1.0.8",
"jsr:@std/fmt@^1.0.3": "1.0.8",
"jsr:@std/fs@1": "1.0.17",
"jsr:@std/fs@^1.0.17": "1.0.17",
"jsr:@std/fs@^1.0.4": "1.0.4",
"jsr:@std/fs@^1.0.5": "1.0.6",
"jsr:@std/fs@^1.0.6": "1.0.17",
"jsr:@std/fs@^1.0.9": "1.0.13",
"jsr:@std/internal@^1.0.3": "1.0.3",
"jsr:@std/internal@^1.0.6": "1.0.7",
"jsr:@std/internal@^1.0.7": "1.0.7",
"jsr:@std/io@0.225": "0.225.2",
"jsr:@std/path@1": "1.0.9",
"jsr:@std/path@^1.0.4": "1.0.6",
"jsr:@std/path@^1.0.6": "1.0.6",
"jsr:@std/path@^1.0.8": "1.0.9",
"jsr:@std/path@^1.0.9": "1.0.9",
"jsr:@std/testing@^1.0.12": "1.0.12",
"jsr:@ts-morph/bootstrap@0.25": "0.25.0",
"jsr:@ts-morph/common@0.25": "0.25.0",
"npm:zod@^3.25.7": "3.25.7"
},
"jsr": {
"@david/code-block-writer@13.0.2": {
"integrity": "14dd3baaafa3a2dea8bf7dfbcddeccaa13e583da2d21d666c01dc6d681cd74ad"
},
"@david/code-block-writer@13.0.3": {
"integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f"
},
"@deno/cache-dir@0.20.1": {
"integrity": "dc4f3add14307f3ff3b712441ea4acabcbfc9a13f67c5adc78c3aac16ac5e2a0",
"dependencies": [
"jsr:@deno/graph",
"jsr:@std/fmt@^1.0.3",
"jsr:@std/fs@^1.0.6",
"jsr:@std/io",
"jsr:@std/path@^1.0.8"
]
},
"@deno/dnt@0.42.1": {
"integrity": "85322b38eb40d4e8c5216d62536152c35b1bda9dc47c8c60860610397b960223",
"dependencies": [
"jsr:@david/code-block-writer",
"jsr:@deno/cache-dir",
"jsr:@std/fmt@1",
"jsr:@std/fs@1",
"jsr:@std/path@1",
"jsr:@ts-morph/bootstrap"
]
},
"@deno/graph@0.86.9": {
"integrity": "c4f353a695bcc5246c099602977dabc6534eacea9999a35a8cb24e807192e6a1"
},
"@std/assert@1.0.5": {
"integrity": "e37da8e4033490ce613eec4ac1d78dba1faf5b02a3f6c573a28f15365b9b440f",
"dependencies": [
"jsr:@std/internal@^1.0.3"
]
},
"@std/assert@1.0.12": {
"integrity": "08009f0926dda9cbd8bef3a35d3b6a4b964b0ab5c3e140a4e0351fbf34af5b9a",
"dependencies": [
"jsr:@std/internal@^1.0.6"
]
},
"@std/assert@1.0.13": {
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
"dependencies": [
"jsr:@std/internal@^1.0.6"
]
},
"@std/async@1.0.13": {
"integrity": "1d76ca5d324aef249908f7f7fe0d39aaf53198e5420604a59ab5c035adc97c96"
},
"@std/bytes@1.0.6": {
"integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"
},
"@std/data-structures@1.0.8": {
"integrity": "2fb7219247e044c8fcd51341788547575653c82ae2c759ff209e0263ba7d9b66"
},
"@std/expect@1.0.3": {
"integrity": "d9cbd03323ef7feafd1e969ed85d5edb04ebbd9937b0fe7a52d5ff53be8e913a",
"dependencies": [
"jsr:@std/assert@^1.0.5",
"jsr:@std/internal@^1.0.3"
]
},
"@std/expect@1.0.16": {
"integrity": "ceeef6dda21f256a5f0f083fcc0eaca175428b523359a9b1d9b3a1df11cc7391",
"dependencies": [
"jsr:@std/assert@^1.0.13",
"jsr:@std/internal@^1.0.7"
]
},
"@std/fmt@1.0.2": {
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
},
"@std/fmt@1.0.5": {
"integrity": "0cfab43364bc36650d83c425cd6d99910fc20c4576631149f0f987eddede1a4d"
},
"@std/fmt@1.0.8": {
"integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
},
"@std/fs@1.0.3": {
"integrity": "3cb839b1360b0a42d8b367c3093bfe4071798e6694fa44cf1963e04a8edba4fe",
"dependencies": [
"jsr:@std/path@^1.0.4"
]
},
"@std/fs@1.0.4": {
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
"dependencies": [
"jsr:@std/path@^1.0.6"
]
},
"@std/fs@1.0.6": {
"integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2",
"dependencies": [
"jsr:@std/path@^1.0.8"
]
},
"@std/fs@1.0.13": {
"integrity": "756d3ff0ade91c9e72b228e8012b6ff00c3d4a4ac9c642c4dac083536bf6c605",
"dependencies": [
"jsr:@std/path@^1.0.8"
]
},
"@std/fs@1.0.17": {
"integrity": "1c00c632677c1158988ef7a004cb16137f870aafdb8163b9dce86ec652f3952b",
"dependencies": [
"jsr:@std/path@^1.0.9"
]
},
"@std/internal@1.0.3": {
"integrity": "208e9b94a3d5649bd880e9ca38b885ab7651ab5b5303a56ed25de4755fb7b11e"
},
"@std/internal@1.0.7": {
"integrity": "39eeb5265190a7bc5d5591c9ff019490bd1f2c3907c044a11b0d545796158a0f"
},
"@std/io@0.225.2": {
"integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7",
"dependencies": [
"jsr:@std/bytes"
]
},
"@std/path@1.0.6": {
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
},
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
},
"@std/path@1.0.9": {
"integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e"
},
"@std/testing@1.0.12": {
"integrity": "fec973a45ccc62c540fb89296199051fee142409138fd6e3eae409366bcd4720",
"dependencies": [
"jsr:@std/assert@^1.0.13",
"jsr:@std/async",
"jsr:@std/data-structures",
"jsr:@std/fs@^1.0.17",
"jsr:@std/internal@^1.0.7",
"jsr:@std/path@^1.0.9"
]
},
"@ts-morph/bootstrap@0.25.0": {
"integrity": "3cd33ee80ac0aab8e5d2660c639a02187f0c8abfe454636ce86c00eb7e8407db",
"dependencies": [
"jsr:@ts-morph/common"
]
},
"@ts-morph/common@0.25.0": {
"integrity": "e3ed1771e2fb61fbc3d2cb39ebbc4f89cd686d6d9bc6d91a71372be055ac1967",
"dependencies": [
"jsr:@std/fs@1",
"jsr:@std/path@1"
]
}
},
"npm": {
"zod@3.25.7": {
"integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg=="
}
},
"remote": {
"https://deno.land/x/zod@v3.23.8/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52",
"https://deno.land/x/zod@v3.23.8/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef",
"https://deno.land/x/zod@v3.23.8/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe",
"https://deno.land/x/zod@v3.23.8/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c",
"https://deno.land/x/zod@v3.23.8/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7",
"https://deno.land/x/zod@v3.23.8/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77",
"https://deno.land/x/zod@v3.23.8/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7",
"https://deno.land/x/zod@v3.23.8/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e",
"https://deno.land/x/zod@v3.23.8/helpers/util.ts": "30c273131661ca5dc973f2cfb196fa23caf3a43e224cdde7a683b72e101a31fc",
"https://deno.land/x/zod@v3.23.8/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268",
"https://deno.land/x/zod@v3.23.8/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c",
"https://deno.land/x/zod@v3.23.8/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039",
"https://deno.land/x/zod@v3.23.8/types.ts": "1b172c90782b1eaa837100ebb6abd726d79d6c1ec336350c8e851e0fd706bf5c"
},
"workspace": {
"dependencies": [
"jsr:@deno/dnt@~0.42.1",
"jsr:@std/expect@^1.0.16",
"jsr:@std/testing@^1.0.12",
"npm:zod@^3.25.7"
]
}
}

View file

@ -1,6 +0,0 @@
import { type BlahActKeyRecord, blahActKeyRecordSchema } from "./actKey.ts";
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
Deno.test("type BlahActKeyRecord is accurate", () => {
assertTypeMatchesZodSchema<BlahActKeyRecord>(blahActKeyRecordSchema);
});

11
jsr.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "@blah/core",
"version": "0.9.0",
"exports": {
"./crypto": "./src/crypto/mod.ts",
"./identity": "./src/identity/mod.ts",
"./richText": "./src/richText/mod.ts"
},
"include": ["LICENSE", "README.md", "src"],
"exclude": ["**/*.test.ts"]
}

48
package.json Normal file
View file

@ -0,0 +1,48 @@
{
"name": "@blah-im/core",
"version": "0.9.0",
"description": "Core logic & types for Blah IM.",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/Blah-IM/typescript-core.git"
},
"bugs": {
"url": "https://github.com/Blah-IM/typescript-core/issues"
},
"exports": {
"./crypto": {
"import": "./dist/crypto/mod.mjs",
"require": "./dist/crypto/mod.cjs",
"types": "./dist/crypto/mod.d.ts"
},
"./identity": {
"import": "./dist/identity/mod.mjs",
"require": "./dist/identity/mod.cjs",
"types": "./dist/identity/mod.d.ts"
},
"./richText": {
"import": "./dist/richText/mod.mjs",
"require": "./dist/richText/mod.cjs",
"types": "./dist/richText/mod.d.ts"
}
},
"files": [
"dist",
"LICENSE",
"README.md"
],
"scripts": {
"test": "vitest --typecheck",
"build": "unbuild"
},
"dependencies": {
"zod": "^3.25.7"
},
"devDependencies": {
"jsr": "^0.13.4",
"unbuild": "^3.5.0",
"vitest": "^3.1.4"
},
"packageManager": "pnpm@10.11.0"
}

2045
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

2
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild

View file

@ -1,8 +0,0 @@
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
import { type BlahRichText, blahRichTextSchema } from "./richText.ts";
Deno.test("type BlahRichText is accurate", () => {
assertTypeMatchesZodSchema<BlahRichText>(
blahRichTextSchema,
);
});

View file

@ -1,8 +0,0 @@
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
import { type BlahRichTextSpan, blahRichTextSpanSchema } from "./span.ts";
Deno.test("type BlahRichTextSpan is accurate", () => {
assertTypeMatchesZodSchema<BlahRichTextSpan>(
blahRichTextSpanSchema,
);
});

View file

@ -1,43 +0,0 @@
import { build, emptyDir } from "@deno/dnt";
import denoJson from "../deno.json" with { type: "json" };
await emptyDir("./npm");
await build({
entryPoints: [
{ name: "./crypto", path: "crypto/mod.ts" },
{ name: "./identity", path: "identity/mod.ts" },
{ name: "./richText", path: "richText/mod.ts" },
],
outDir: "./npm",
importMap: "deno.json",
compilerOptions: {
lib: ["ESNext", "DOM"],
},
shims: {
deno: {
test: "dev",
},
crypto: false,
webSocket: true,
},
package: {
// package.json properties
name: "@blah-im/core",
version: denoJson.version,
description: "Core logic & types for Blah IM.",
license: "MIT",
repository: {
type: "git",
url: "git+https://github.com/Blah-IM/typescript-core.git",
},
bugs: {
url: "https://github.com/Blah-IM/typescript-core/issues",
},
},
postBuild() {
// steps to run after building and before running the tests
Deno.copyFileSync("LICENSE", "npm/LICENSE");
Deno.copyFileSync("README.md", "npm/README.md");
},
});

View file

@ -1,20 +1,21 @@
import { expect } from "@std/expect";
import { expect, test } from "vitest";
import { BlahKeyPair } from "./mod.ts";
let keypair: BlahKeyPair;
Deno.test("generate keypair", async () => {
test("generate keypair", async () => {
keypair = await BlahKeyPair.generate();
});
Deno.test("encode & decode keypair", async () => {
test("encode & decode keypair", async () => {
const encoded = await keypair.encode();
const decoded = await BlahKeyPair.fromEncoded(encoded);
expect(decoded.id).toBe(keypair.id);
});
Deno.test("encode & decode keypair w/ password", async () => {
test("encode & decode keypair w/ password", async () => {
const password = "password";
const encoded = await keypair.encode(password);
const decoded = await BlahKeyPair.fromEncoded(encoded, password);

View file

@ -1,4 +1,5 @@
import { expect } from "@std/expect";
import { expect, test } from "vitest";
import { BlahKeyPair } from "./keypair.ts";
import { z } from "zod";
import { BlahPublicKey } from "./publicKey.ts";
@ -6,19 +7,17 @@ import type { SignOrVerifyOptions } from "./signAndVerify.ts";
let keypair: BlahKeyPair;
Deno.test("sign & verify payload", async () => {
test("sign & verify payload", async () => {
keypair = await BlahKeyPair.generate();
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
const verifiedPayload = await keypair.publicKey.verifyPayload(
signedPayload,
);
const verifiedPayload = await keypair.publicKey.verifyPayload(signedPayload);
expect(verifiedPayload).toEqual(payload);
});
Deno.test("sign and verify with POW", async () => {
test("sign and verify with POW", async () => {
const payload = { foo: "bar-pow", baz: 123 };
const options: SignOrVerifyOptions = { powDifficulty: 1 };
const signedPayload = await keypair.signPayload(payload, options);
@ -30,59 +29,58 @@ Deno.test("sign and verify with POW", async () => {
expect(verifiedPayload).toEqual(payload);
});
Deno.test("sign and verify with unmet POW", async () => {
test("sign and verify with unmet POW", async () => {
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload, {
powDifficulty: 1,
});
await expect(keypair.publicKey.verifyPayload(
signedPayload,
{ powDifficulty: 6 },
)).rejects.toMatch(/proof-of-work/);
await expect(
keypair.publicKey.verifyPayload(signedPayload, { powDifficulty: 6 }),
).rejects.toThrowError(/proof-of-work/);
});
Deno.test("parse and verify payload", async () => {
const payloadSchema = z.object({
foo: z.string(),
baz: z.number(),
}).strict();
test("parse and verify payload", async () => {
const payloadSchema = z
.object({
foo: z.string(),
baz: z.number(),
})
.strict();
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
const { payload: verifiedPayload, key } = await BlahPublicKey
.parseAndVerifyPayload(
payloadSchema,
signedPayload,
);
const { payload: verifiedPayload, key } =
await BlahPublicKey.parseAndVerifyPayload(payloadSchema, signedPayload);
expect(verifiedPayload).toEqual(payload);
expect(key.id).toBe(keypair.id);
});
Deno.test("parse and verify corrupted payload", async () => {
const payloadSchema = z.object({
foo: z.string(),
baz: z.number(),
}).strict();
test("parse and verify corrupted payload", async () => {
const payloadSchema = z
.object({
foo: z.string(),
baz: z.number(),
})
.strict();
const payload = { foo: "bar", baz: 123, qux: "quux" };
const signedPayload = await keypair.signPayload(payload);
await expect(BlahPublicKey
.parseAndVerifyPayload(
payloadSchema,
signedPayload,
)).rejects.toMatch(/unrecognized/);
await expect(
BlahPublicKey.parseAndVerifyPayload(payloadSchema, signedPayload),
).rejects.toThrowError(/unrecognized/);
});
Deno.test("sign & verify payload with wrong keypair", async () => {
test("sign & verify payload with wrong keypair", async () => {
const keypair2 = await BlahKeyPair.generate();
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
expect(keypair2.publicKey.verifyPayload(signedPayload))
.rejects.toMatch(/sign/);
await expect(
keypair2.publicKey.verifyPayload(signedPayload),
).rejects.toThrowError(/sign/);
});
Deno.test("sign & verify payload with wrong key order but should still work", async () => {
test("sign & verify payload with wrong key order but should still work", async () => {
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
const signedPayload2 = {
@ -95,8 +93,6 @@ Deno.test("sign & verify payload with wrong key order but should still work", as
timestamp: signedPayload.signee.timestamp,
},
};
const verifiedPayload = await keypair.publicKey.verifyPayload(
signedPayload2,
);
const verifiedPayload = await keypair.publicKey.verifyPayload(signedPayload2);
expect(verifiedPayload).toEqual(payload);
});

View file

@ -0,0 +1,28 @@
import { expectTypeOf, test } from "vitest";
import z from "zod";
import type { BlahSignedPayload } from "./mod.ts";
import {
type BlahPayloadSignee,
blahPayloadSigneeSchemaOf,
blahSignedPayloadSchemaOf,
} from "./signedPayload.ts";
const testPayloadSchema = z.object({
foo: z.string(),
});
type TestPayload = z.infer<typeof testPayloadSchema>;
test("BlahPayloadSignee typed correctly", () => {
const signeeSchema = blahPayloadSigneeSchemaOf(testPayloadSchema);
expectTypeOf<z.infer<typeof signeeSchema>>().toEqualTypeOf<
BlahPayloadSignee<TestPayload>
>();
});
test("BlahSignedPayload typed correctly", () => {
const signedPayloadSchema = blahSignedPayloadSchemaOf(testPayloadSchema);
expectTypeOf<z.infer<typeof signedPayloadSchema>>().toEqualTypeOf<
BlahSignedPayload<TestPayload>
>();
});

View file

@ -0,0 +1,10 @@
import { expectTypeOf, test } from "vitest";
import { type BlahActKeyRecord, blahActKeyRecordSchema } from "./actKey.ts";
import z from "zod";
test("BlahActKeyRecord typed correctly", () => {
expectTypeOf<
z.infer<typeof blahActKeyRecordSchema>
>().toEqualTypeOf<BlahActKeyRecord>();
});

View file

@ -1,4 +1,4 @@
import { expect } from "@std/expect";
import { expect, test } from "vitest";
import { BlahKeyPair, type BlahSignedPayload } from "../crypto/mod.ts";
import { BlahIdentity } from "./identity.ts";
import type { BlahIdentityDescription, BlahProfile } from "./mod.ts";
@ -17,17 +17,17 @@ let identity: BlahIdentity;
let identityDesc: BlahIdentityDescription;
let identityFromFile: BlahIdentity;
Deno.test("create identity", async () => {
test("create identity", async () => {
idKeyPair = await BlahKeyPair.generate();
actKeyPair = await BlahKeyPair.generate();
identity = await BlahIdentity.create(idKeyPair, actKeyPair, profile);
});
Deno.test("generate identity description", () => {
test("generate identity description", () => {
identityDesc = identity.generateIdentityDescription();
});
Deno.test("created identity act key signed correctly", async () => {
test("created identity act key signed correctly", async () => {
const record = await identity.idPublicKey.verifyPayload(
identityDesc.act_keys[0],
);
@ -37,7 +37,7 @@ Deno.test("created identity act key signed correctly", async () => {
expect(record.act_key).toBe(actKeyPair.id);
});
Deno.test("created identity profile signed correctly", async () => {
test("created identity profile signed correctly", async () => {
const record = await actKeyPair.publicKey.verifyPayload(
identityDesc.profile,
{ identityKeyId: identityDesc.id_key },
@ -49,26 +49,29 @@ Deno.test("created identity profile signed correctly", async () => {
expect(record.id_urls).toEqual(["https://localhost"]);
});
Deno.test("parse identity description", async () => {
test("parse identity description", async () => {
identityFromFile = await BlahIdentity.fromIdentityDescription(identityDesc);
expect(identityFromFile.idPublicKey.id).toBe(idKeyPair.id);
expect(identityFromFile.actKeys[0].publicKey.id).toBe(actKeyPair.id);
expect(identityFromFile.profileSigValid).toBe(true);
});
Deno.test("identity description profile sigs are properly verfied", async () => {
test("identity description profile sigs are properly verfied", async () => {
const identityDescWithProfileInvalidProfileSig: BlahIdentityDescription = {
...identityDesc,
profile: { ...identityDesc.profile, sig: "_ obviously not a valid sig _" },
profile: {
...identityDesc.profile,
sig: "_ obviously not a valid sig _",
},
};
const identityWithProfileInvalidProfileSig = await BlahIdentity
.fromIdentityDescription(
const identityWithProfileInvalidProfileSig =
await BlahIdentity.fromIdentityDescription(
identityDescWithProfileInvalidProfileSig,
);
expect(identityWithProfileInvalidProfileSig.profileSigValid).toBe(false);
});
Deno.test("identity description profile must be signed with correct id_key", async () => {
test("identity description profile must be signed with correct id_key", async () => {
const rawProfile: BlahProfile = identityDesc.profile.signee.payload;
const profileSignedWithActKeyAsIdKey: BlahSignedPayload<BlahProfile> =
await actKeyPair.signPayload(rawProfile);
@ -76,12 +79,13 @@ Deno.test("identity description profile must be signed with correct id_key", asy
...identityDesc,
profile: profileSignedWithActKeyAsIdKey,
};
const identityWithWrongIdKey = await BlahIdentity
.fromIdentityDescription(identityDescWithWrongIdKey);
const identityWithWrongIdKey = await BlahIdentity.fromIdentityDescription(
identityDescWithWrongIdKey,
);
expect(identityWithWrongIdKey.profileSigValid).toBe(false);
});
Deno.test("identity description act key sigs are properly verfied", async () => {
test("identity description act key sigs are properly verfied", async () => {
const identityDescWithActKeyInvalidActKeySig: BlahIdentityDescription = {
...identityDesc,
act_keys: [
@ -91,14 +95,14 @@ Deno.test("identity description act key sigs are properly verfied", async () =>
},
],
};
const identityWithActKeyInvalidActKeySig = await BlahIdentity
.fromIdentityDescription(
const identityWithActKeyInvalidActKeySig =
await BlahIdentity.fromIdentityDescription(
identityDescWithActKeyInvalidActKeySig,
);
expect(identityWithActKeyInvalidActKeySig.actKeys[0].isSigValid).toBe(false);
});
Deno.test("add a second act key", async () => {
test("add a second act key", async () => {
const actKeyPair2 = await BlahKeyPair.generate();
await identity.addActKey(actKeyPair2, { comment: "test" });
identityDesc = identity.generateIdentityDescription();
@ -113,7 +117,7 @@ Deno.test("add a second act key", async () => {
expect(record.act_key).toBe(actKeyPair2.id);
});
Deno.test("update first act key", async () => {
test("update first act key", async () => {
await identity.updateActKey(actKeyPair.id, { comment: "test2" });
identityDesc = identity.generateIdentityDescription();
@ -124,13 +128,13 @@ Deno.test("update first act key", async () => {
expect(record.comment).toBe("test2");
});
Deno.test("act key properly expires", async () => {
test("act key properly expires", async () => {
expect(identity.actKeys[0].isExpired).toBe(false);
await identity.updateActKey(actKeyPair.id, { expiresAt: new Date(10000) });
expect(identity.actKeys[0].isExpired).toBe(true);
});
Deno.test("update profile", async () => {
test("update profile", async () => {
const newProfile: BlahProfile = {
typ: "profile",
name: "Shibo Lyu",
@ -144,7 +148,8 @@ Deno.test("update profile", async () => {
expect(identityDesc.profile.signee.payload).toEqual(newProfile);
});
Deno.test("throw when try writing to identity without id key pair", () => {
expect(identityFromFile.updateActKey(actKeyPair.id, { comment: "test2" }))
.rejects.toMatch(/key pair/i);
test("throw when try writing to identity without id key pair", async () => {
await expect(
identityFromFile.updateActKey(actKeyPair.id, { comment: "test2" }),
).rejects.toThrowError(/key pair/i);
});

View file

@ -1,19 +1,20 @@
import { expect, test, expectTypeOf } from "vitest";
import {
type BlahIdentityDescription,
blahIdentityDescriptionSchema,
getIdentityDescriptionFileURL,
identityDescriptionFilePath,
} from "./identityDescription.ts";
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
import { expect } from "@std/expect";
import { z } from "zod";
Deno.test("type BlahIdentityDescription is accurate", () => {
assertTypeMatchesZodSchema<BlahIdentityDescription>(
blahIdentityDescriptionSchema,
);
test("BlahIdentityDescription typed correctly", () => {
expectTypeOf<
z.infer<typeof blahIdentityDescriptionSchema>
>().toEqualTypeOf<BlahIdentityDescription>();
});
Deno.test("getIdentityDescriptionFileURL", () => {
test("getIdentityDescriptionFileURL", () => {
expect(getIdentityDescriptionFileURL("https://lao.sb")).toBe(
"https://lao.sb" + identityDescriptionFilePath,
);
@ -22,6 +23,7 @@ Deno.test("getIdentityDescriptionFileURL", () => {
"https://test.lao.sb" + identityDescriptionFilePath,
);
expect(() => getIdentityDescriptionFileURL("https://trailing-slash.lao.sb/"))
.toThrow();
expect(() =>
getIdentityDescriptionFileURL("https://trailing-slash.lao.sb/"),
).toThrow();
});

View file

@ -1,22 +1,25 @@
import { expect, test, expectTypeOf } from "vitest";
import z from "zod";
import {
type BlahProfile,
blahProfileSchema,
validateIDURLFormat,
} from "./profile.ts";
import { assertTypeMatchesZodSchema } from "../test/utils.ts";
import { expect } from "@std/expect";
Deno.test("type BlahProfile is accurate", () => {
assertTypeMatchesZodSchema<BlahProfile>(blahProfileSchema);
test("BlahProfile typed correctly", () => {
expectTypeOf<
z.infer<typeof blahProfileSchema>
>().toEqualTypeOf<BlahProfile>();
});
Deno.test("ID URL format - valid", () => {
test("ID URL format - valid", () => {
expect(validateIDURLFormat("https://lao.sb")).toBe(true);
expect(validateIDURLFormat("https://test.lao.sb")).toBe(true);
expect(validateIDURLFormat("https://🧧.lao.sb")).toBe(true);
});
Deno.test("ID URL format - invalid", () => {
test("ID URL format - invalid", () => {
// Must be valid URL
expect(validateIDURLFormat("lao.sb")).toBe(false);
// No trailing slash

View file

@ -0,0 +1,10 @@
import { expectTypeOf, test } from "vitest";
import { type BlahRichText, blahRichTextSchema } from "./richText.ts";
import z from "zod";
test("BlahRichText typed correctly", () => {
expectTypeOf<
z.input<typeof blahRichTextSchema>
>().toEqualTypeOf<BlahRichText>();
});

10
src/richText/span.test.ts Normal file
View file

@ -0,0 +1,10 @@
import { expectTypeOf, test } from "vitest";
import { type BlahRichTextSpan, blahRichTextSpanSchema } from "./span.ts";
import z from "zod";
test("BlahRichTextSpan typed correctly", () => {
expectTypeOf<BlahRichTextSpan>().toEqualTypeOf<
z.input<typeof blahRichTextSpanSchema>
>();
});

View file

@ -1,8 +1,9 @@
import { expect } from "@std/expect";
import { expect, test } from "vitest";
import type { BlahRichText } from "./mod.ts";
import { toPlainText } from "./toPlainText.ts";
Deno.test("toPlainText", () => {
test("toPlainText", () => {
const richText: BlahRichText = ["hello ", ["world", { b: true }]];
expect(toPlainText(richText)).toBe("hello world");
});

View file

@ -1,10 +0,0 @@
import { assertType, type IsExact } from "@std/testing/types";
import type { z } from "zod";
export function assertTypeMatchesZodSchema<T>(
schema: z.ZodTypeAny,
) {
assertType<IsExact<T, z.infer<typeof schema>>>(
true as IsExact<T, z.infer<typeof schema>>,
);
}

20
tsconfig.json Normal file
View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "Preserve",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"allowJs": false,
"strict": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"allowImportingTsExtensions": true,
"noImplicitOverride": true,
"noEmit": true
},
"include": ["src"]
}