@shetty4l/core 0.1.3 → 0.1.9
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/package.json +12 -1
- package/src/cli.ts +3 -3
- package/.github/workflows/ci-shared.yml +0 -36
- package/.github/workflows/ci.yml +0 -14
- package/.github/workflows/release-shared.yml +0 -150
- package/.github/workflows/release.yml +0 -19
- package/.husky/pre-commit +0 -3
- package/biome.json +0 -12
- package/bun.lock +0 -65
- package/test/cli.test.ts +0 -61
- package/test/config.test.ts +0 -263
- package/test/daemon.test.ts +0 -89
- package/test/http.test.ts +0 -152
- package/test/result.test.ts +0 -58
- package/test/signals.test.ts +0 -25
- package/test/version.test.ts +0 -55
- package/tsconfig.json +0 -18
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shetty4l/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Shared infrastructure primitives for Bun/TypeScript services",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/shetty4l/core"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"main": "src/index.ts",
|
|
7
11
|
"exports": {
|
|
@@ -14,6 +18,13 @@
|
|
|
14
18
|
"./daemon": "./src/daemon.ts",
|
|
15
19
|
"./http": "./src/http.ts"
|
|
16
20
|
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/",
|
|
23
|
+
"scripts/install-lib.sh"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"registry": "https://registry.npmjs.org"
|
|
27
|
+
},
|
|
17
28
|
"bin": {
|
|
18
29
|
"version-bump": "./src/scripts/version-bump.ts"
|
|
19
30
|
},
|
package/src/cli.ts
CHANGED
|
@@ -45,7 +45,7 @@ export function formatUptime(seconds: number): string {
|
|
|
45
45
|
export type CommandHandler = (
|
|
46
46
|
args: string[],
|
|
47
47
|
json: boolean,
|
|
48
|
-
) => void | Promise<void>;
|
|
48
|
+
) => void | number | Promise<void | number>;
|
|
49
49
|
|
|
50
50
|
export interface RunCliOpts {
|
|
51
51
|
/** Service name, used in error messages. */
|
|
@@ -104,6 +104,6 @@ export async function runCli(opts: RunCliOpts): Promise<void> {
|
|
|
104
104
|
process.exit(1);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
await handler(args, json);
|
|
108
|
-
process.exit(
|
|
107
|
+
const exitCode = (await handler(args, json)) ?? 0;
|
|
108
|
+
process.exit(exitCode);
|
|
109
109
|
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
name: CI (shared)
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_call:
|
|
5
|
-
inputs:
|
|
6
|
-
extra-validate-command:
|
|
7
|
-
description: "Optional extra command to run after validate (e.g. a full test suite)"
|
|
8
|
-
required: false
|
|
9
|
-
type: string
|
|
10
|
-
default: ""
|
|
11
|
-
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
validate:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
steps:
|
|
19
|
-
- name: Checkout
|
|
20
|
-
uses: actions/checkout@v4
|
|
21
|
-
|
|
22
|
-
- name: Setup Bun
|
|
23
|
-
uses: oven-sh/setup-bun@v2
|
|
24
|
-
with:
|
|
25
|
-
bun-version: latest
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies
|
|
28
|
-
run: bun install --frozen-lockfile
|
|
29
|
-
|
|
30
|
-
- name: Validate
|
|
31
|
-
run: bun run validate
|
|
32
|
-
|
|
33
|
-
- name: Extra validation
|
|
34
|
-
if: ${{ inputs.extra-validate-command != '' }}
|
|
35
|
-
shell: bash
|
|
36
|
-
run: ${{ inputs.extra-validate-command }}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
name: Release (shared)
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_call:
|
|
5
|
-
inputs:
|
|
6
|
-
service-name:
|
|
7
|
-
description: "Service name for tarball prefix and release title (e.g. engram, synapse)"
|
|
8
|
-
required: true
|
|
9
|
-
type: string
|
|
10
|
-
extra-tarball-paths:
|
|
11
|
-
description: "Space-separated additional paths to include in tarball (e.g. 'opencode/ scripts/install.sh')"
|
|
12
|
-
required: false
|
|
13
|
-
type: string
|
|
14
|
-
default: ""
|
|
15
|
-
publish-npm:
|
|
16
|
-
description: "Whether to publish to GitHub Packages via npm publish"
|
|
17
|
-
required: false
|
|
18
|
-
type: boolean
|
|
19
|
-
default: false
|
|
20
|
-
attach-install-sh:
|
|
21
|
-
description: "Whether to attach scripts/install.sh as a release asset"
|
|
22
|
-
required: false
|
|
23
|
-
type: boolean
|
|
24
|
-
default: false
|
|
25
|
-
|
|
26
|
-
concurrency:
|
|
27
|
-
group: release
|
|
28
|
-
cancel-in-progress: false
|
|
29
|
-
|
|
30
|
-
jobs:
|
|
31
|
-
release:
|
|
32
|
-
runs-on: ubuntu-latest
|
|
33
|
-
permissions:
|
|
34
|
-
contents: write
|
|
35
|
-
packages: write
|
|
36
|
-
steps:
|
|
37
|
-
- name: Checkout
|
|
38
|
-
uses: actions/checkout@v4
|
|
39
|
-
with:
|
|
40
|
-
ref: ${{ github.event.workflow_run.head_sha }}
|
|
41
|
-
fetch-depth: 0
|
|
42
|
-
|
|
43
|
-
- name: Setup Bun
|
|
44
|
-
uses: oven-sh/setup-bun@v2
|
|
45
|
-
with:
|
|
46
|
-
bun-version: latest
|
|
47
|
-
|
|
48
|
-
- name: Install dependencies
|
|
49
|
-
run: bun install --frozen-lockfile
|
|
50
|
-
|
|
51
|
-
- name: Compute next version
|
|
52
|
-
id: version
|
|
53
|
-
run: |
|
|
54
|
-
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
|
55
|
-
echo "latest_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
|
|
56
|
-
|
|
57
|
-
VERSION="${LATEST_TAG#v}"
|
|
58
|
-
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
|
|
59
|
-
|
|
60
|
-
COMMITS_SINCE="$(git log "${LATEST_TAG}..HEAD" --format='%s' 2>/dev/null || echo "")"
|
|
61
|
-
if echo "$COMMITS_SINCE" | grep -q '\[major\]'; then
|
|
62
|
-
NEXT_VERSION="$((MAJOR + 1)).0.0"
|
|
63
|
-
BUMP_LEVEL="major"
|
|
64
|
-
elif echo "$COMMITS_SINCE" | grep -q '\[minor\]'; then
|
|
65
|
-
NEXT_VERSION="${MAJOR}.$((MINOR + 1)).0"
|
|
66
|
-
BUMP_LEVEL="minor"
|
|
67
|
-
else
|
|
68
|
-
NEXT_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
|
|
69
|
-
BUMP_LEVEL="patch"
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
echo "bump_level=${BUMP_LEVEL}" >> "$GITHUB_OUTPUT"
|
|
73
|
-
echo "next_version=${NEXT_VERSION}" >> "$GITHUB_OUTPUT"
|
|
74
|
-
echo "next_tag=v${NEXT_VERSION}" >> "$GITHUB_OUTPUT"
|
|
75
|
-
echo "Releasing: v${NEXT_VERSION} (previous: ${LATEST_TAG}, bump: ${BUMP_LEVEL})"
|
|
76
|
-
|
|
77
|
-
- name: Write VERSION file
|
|
78
|
-
run: echo "${{ steps.version.outputs.next_version }}" > VERSION
|
|
79
|
-
|
|
80
|
-
- name: Update package.json version
|
|
81
|
-
run: |
|
|
82
|
-
jq --arg v "${{ steps.version.outputs.next_version }}" '.version = $v' package.json > tmp.json
|
|
83
|
-
mv tmp.json package.json
|
|
84
|
-
|
|
85
|
-
- name: Write BUILD_META.json
|
|
86
|
-
run: |
|
|
87
|
-
SHA="${{ github.event.workflow_run.head_sha }}"
|
|
88
|
-
SHORT_SHA="${SHA:0:7}"
|
|
89
|
-
BRANCH="${{ github.event.workflow_run.head_branch }}"
|
|
90
|
-
TITLE="$(git log -1 --format='%s' "$SHA")"
|
|
91
|
-
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
92
|
-
jq -n \
|
|
93
|
-
--arg sha "$SHA" \
|
|
94
|
-
--arg shortSha "$SHORT_SHA" \
|
|
95
|
-
--arg branch "$BRANCH" \
|
|
96
|
-
--arg title "$TITLE" \
|
|
97
|
-
--arg buildTime "$BUILD_TIME" \
|
|
98
|
-
'{
|
|
99
|
-
gitSha: $sha,
|
|
100
|
-
gitShortSha: $shortSha,
|
|
101
|
-
gitBranch: $branch,
|
|
102
|
-
commitTitle: $title,
|
|
103
|
-
buildTimeUtc: $buildTime
|
|
104
|
-
}' > BUILD_META.json
|
|
105
|
-
|
|
106
|
-
- name: Publish to GitHub Packages
|
|
107
|
-
if: ${{ inputs.publish-npm }}
|
|
108
|
-
run: |
|
|
109
|
-
echo "//npm.pkg.github.com/:_authToken=${{ github.token }}" > .npmrc
|
|
110
|
-
echo "@shetty4l:registry=https://npm.pkg.github.com" >> .npmrc
|
|
111
|
-
npm publish --access public
|
|
112
|
-
|
|
113
|
-
- name: Create source tarball
|
|
114
|
-
run: |
|
|
115
|
-
TAG="${{ steps.version.outputs.next_tag }}"
|
|
116
|
-
EXTRA_PATHS="${{ inputs.extra-tarball-paths }}"
|
|
117
|
-
tar czf "${{ inputs.service-name }}-${TAG}.tar.gz" \
|
|
118
|
-
--exclude='node_modules' \
|
|
119
|
-
--exclude='.git' \
|
|
120
|
-
--exclude='test' \
|
|
121
|
-
--exclude='.husky' \
|
|
122
|
-
--exclude='.DS_Store' \
|
|
123
|
-
--exclude='dist' \
|
|
124
|
-
src/ \
|
|
125
|
-
package.json \
|
|
126
|
-
bun.lock \
|
|
127
|
-
tsconfig.json \
|
|
128
|
-
biome.json \
|
|
129
|
-
VERSION \
|
|
130
|
-
BUILD_META.json \
|
|
131
|
-
$EXTRA_PATHS
|
|
132
|
-
|
|
133
|
-
- name: Create tag and release
|
|
134
|
-
env:
|
|
135
|
-
GH_TOKEN: ${{ github.token }}
|
|
136
|
-
run: |
|
|
137
|
-
TAG="${{ steps.version.outputs.next_tag }}"
|
|
138
|
-
ASSETS=("${{ inputs.service-name }}-${TAG}.tar.gz")
|
|
139
|
-
|
|
140
|
-
if [ "${{ inputs.attach-install-sh }}" = "true" ] && [ -f "scripts/install.sh" ]; then
|
|
141
|
-
ASSETS+=("scripts/install.sh")
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
git tag "${TAG}"
|
|
145
|
-
git push origin "${TAG}"
|
|
146
|
-
|
|
147
|
-
gh release create "${TAG}" \
|
|
148
|
-
--title "Release ${TAG}" \
|
|
149
|
-
--notes "Automated release ${TAG}" \
|
|
150
|
-
"${ASSETS[@]}"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_run:
|
|
5
|
-
workflows: ["CI"]
|
|
6
|
-
types: [completed]
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: write
|
|
11
|
-
packages: write
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
release:
|
|
15
|
-
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
16
|
-
uses: ./.github/workflows/release-shared.yml
|
|
17
|
-
with:
|
|
18
|
-
service-name: core
|
|
19
|
-
publish-npm: true
|
package/.husky/pre-commit
DELETED
package/biome.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
|
|
3
|
-
"formatter": {
|
|
4
|
-
"enabled": true,
|
|
5
|
-
"indentStyle": "space",
|
|
6
|
-
"indentWidth": 2
|
|
7
|
-
},
|
|
8
|
-
"linter": {
|
|
9
|
-
"enabled": false
|
|
10
|
-
},
|
|
11
|
-
"assist": { "actions": { "source": { "organizeImports": "on" } } }
|
|
12
|
-
}
|
package/bun.lock
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "@shetty4l/core",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@biomejs/biome": "^2.3.13",
|
|
9
|
-
"@types/bun": "latest",
|
|
10
|
-
"husky": "^9.0.0",
|
|
11
|
-
"oxlint": "^0.12.0",
|
|
12
|
-
"typescript": "^5.0.0",
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
"packages": {
|
|
17
|
-
"@biomejs/biome": ["@biomejs/biome@2.4.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.2", "@biomejs/cli-darwin-x64": "2.4.2", "@biomejs/cli-linux-arm64": "2.4.2", "@biomejs/cli-linux-arm64-musl": "2.4.2", "@biomejs/cli-linux-x64": "2.4.2", "@biomejs/cli-linux-x64-musl": "2.4.2", "@biomejs/cli-win32-arm64": "2.4.2", "@biomejs/cli-win32-x64": "2.4.2" }, "bin": { "biome": "bin/biome" } }, "sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w=="],
|
|
18
|
-
|
|
19
|
-
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA=="],
|
|
20
|
-
|
|
21
|
-
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-P7hK1jLVny+0R9UwyGcECxO6sjETxfPyBm/1dmFjnDOHgdDPjPqozByunrwh4xPKld8sxOr5eAsSqal5uKgeBg=="],
|
|
22
|
-
|
|
23
|
-
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-DI3Mi7GT2zYNgUTDEbSjl3e1KhoP76OjQdm8JpvZYZWtVDRyLd3w8llSr2TWk1z+U3P44kUBWY3X7H9MD1/DGQ=="],
|
|
24
|
-
|
|
25
|
-
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-/x04YK9+7erw6tYEcJv9WXoBHcULI/wMOvNdAyE9S3JStZZ9yJyV67sWAI+90UHuDo/BDhq0d96LDqGlSVv7WA=="],
|
|
26
|
-
|
|
27
|
-
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-GK2ErnrKpWFigYP68cXiCHK4RTL4IUWhK92AFS3U28X/nuAL5+hTuy6hyobc8JZRSt+upXt1nXChK+tuHHx4mA=="],
|
|
28
|
-
|
|
29
|
-
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-wbBmTkeAoAYbOQ33f6sfKG7pcRSydQiF+dTYOBjJsnXO2mWEOQHllKlC2YVnedqZFERp2WZhFUoO7TNRwnwEHQ=="],
|
|
30
|
-
|
|
31
|
-
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-k2uqwLYrNNxnaoiW3RJxoMGnbKda8FuCmtYG3cOtVljs3CzWxaTR+AoXwKGHscC9thax9R4kOrtWqWN0+KdPTw=="],
|
|
32
|
-
|
|
33
|
-
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-9ma7C4g8Sq3cBlRJD2yrsHXB1mnnEBdpy7PhvFrylQWQb4PoyCmPucdX7frvsSBQuFtIiKCrolPl/8tCZrKvgQ=="],
|
|
34
|
-
|
|
35
|
-
"@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@0.12.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UydkjcAImpmBn8JYaMPg0zJrwgWJMGvJagvCnyPfyiBRWAN83Kq+BDgJZgIq+2Te6kvlnoiHWNJKVJmpy0f0BA=="],
|
|
36
|
-
|
|
37
|
-
"@oxlint/darwin-x64": ["@oxlint/darwin-x64@0.12.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-bxLyiAiHzXB56w7cf50YNPpZlK+PMxA8GgHutRSoNK/Z/BR/xsibNLs/9YNUnjHB+PF19+EbIRtJxoHjmbRr8g=="],
|
|
38
|
-
|
|
39
|
-
"@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@0.12.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jVkmfoMjPKFDIZySmpykwrCmx5xhpLJdMpUAR8ycEkFRJFp5qKLWZd6cEjiMb7gxmWN6qcCvDVTF/zEs3aRpyQ=="],
|
|
40
|
-
|
|
41
|
-
"@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@0.12.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-8VdV1nKYDj7AFaw1a03Ih43/+pUS/hhMZbTFLRMpvlVp1cPtdB77c+bl/OdiJ/BwNTzLIzr/GrospwCoEJkQKg=="],
|
|
42
|
-
|
|
43
|
-
"@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@0.12.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MacAt8N4XU5DeoHcseXLom/z+B0seecCz8vGAH4ppF2EH49o7NbN7VvFsw2nZ2QNO/4vw+pdS1BHXLTr9lY6zQ=="],
|
|
44
|
-
|
|
45
|
-
"@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@0.12.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/ZBDJ9wpUE6bB05nniQl29kD5vJUMg6n75LdHD8F6ThXfsHGI/n7Je3gzggnXokgf9UQpTUPWrWlfEuWVCBMag=="],
|
|
46
|
-
|
|
47
|
-
"@oxlint/win32-arm64": ["@oxlint/win32-arm64@0.12.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-hY1ya9dv8VY8113YSSDfMs/989aFmoA2fIZco8uxTxIEVl9nGY6tDtpgKZqUIiGrrMbDO8BBb1G5jsekmfexbA=="],
|
|
48
|
-
|
|
49
|
-
"@oxlint/win32-x64": ["@oxlint/win32-x64@0.12.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NHLJolo4sZk3nu/bPNuaJ+6p5DdHoRuZAjyuSO6CnLgpmZcYqx7LgngA/x2oB/bLgi4Hv9twjHjODc5Ce5o14g=="],
|
|
50
|
-
|
|
51
|
-
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
52
|
-
|
|
53
|
-
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
|
54
|
-
|
|
55
|
-
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
|
56
|
-
|
|
57
|
-
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
|
58
|
-
|
|
59
|
-
"oxlint": ["oxlint@0.12.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.12.0", "@oxlint/darwin-x64": "0.12.0", "@oxlint/linux-arm64-gnu": "0.12.0", "@oxlint/linux-arm64-musl": "0.12.0", "@oxlint/linux-x64-gnu": "0.12.0", "@oxlint/linux-x64-musl": "0.12.0", "@oxlint/win32-arm64": "0.12.0", "@oxlint/win32-x64": "0.12.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-M0vWq8KYtp4vpweRxcdCiVO8QFwzoRyp5bWTMrEL/0Z+GDKCMJltac7H3T3T09FIiktOZLvID733d7OcKk/caw=="],
|
|
60
|
-
|
|
61
|
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
62
|
-
|
|
63
|
-
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
64
|
-
}
|
|
65
|
-
}
|
package/test/cli.test.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { formatUptime, parseArgs } from "../src/cli";
|
|
3
|
-
|
|
4
|
-
// --- parseArgs ---
|
|
5
|
-
|
|
6
|
-
describe("parseArgs", () => {
|
|
7
|
-
test("extracts command and args", () => {
|
|
8
|
-
const result = parseArgs(["start", "--port", "8080"]);
|
|
9
|
-
expect(result.command).toBe("start");
|
|
10
|
-
expect(result.args).toEqual(["--port", "8080"]);
|
|
11
|
-
expect(result.json).toBe(false);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("strips --json flag", () => {
|
|
15
|
-
const result = parseArgs(["status", "--json"]);
|
|
16
|
-
expect(result.command).toBe("status");
|
|
17
|
-
expect(result.args).toEqual([]);
|
|
18
|
-
expect(result.json).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("--json can appear anywhere", () => {
|
|
22
|
-
const result = parseArgs(["--json", "health"]);
|
|
23
|
-
expect(result.command).toBe("health");
|
|
24
|
-
expect(result.json).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("defaults to help when empty", () => {
|
|
28
|
-
const result = parseArgs([]);
|
|
29
|
-
expect(result.command).toBe("help");
|
|
30
|
-
expect(result.args).toEqual([]);
|
|
31
|
-
expect(result.json).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// --- formatUptime ---
|
|
36
|
-
|
|
37
|
-
describe("formatUptime", () => {
|
|
38
|
-
test("formats seconds", () => {
|
|
39
|
-
expect(formatUptime(45)).toBe("45s");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("formats minutes and seconds", () => {
|
|
43
|
-
expect(formatUptime(192)).toBe("3m 12s");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("formats hours and minutes", () => {
|
|
47
|
-
expect(formatUptime(8100)).toBe("2h 15m");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("edge case: exactly 60 seconds", () => {
|
|
51
|
-
expect(formatUptime(60)).toBe("1m 0s");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("edge case: exactly 1 hour", () => {
|
|
55
|
-
expect(formatUptime(3600)).toBe("1h 0m");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("zero seconds", () => {
|
|
59
|
-
expect(formatUptime(0)).toBe("0s");
|
|
60
|
-
});
|
|
61
|
-
});
|
package/test/config.test.ts
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import {
|
|
6
|
-
expandPath,
|
|
7
|
-
getConfigDir,
|
|
8
|
-
getDataDir,
|
|
9
|
-
interpolateDeep,
|
|
10
|
-
interpolateEnvVars,
|
|
11
|
-
loadJsonConfig,
|
|
12
|
-
parsePort,
|
|
13
|
-
} from "../src/config";
|
|
14
|
-
|
|
15
|
-
const TMP = join(import.meta.dir, ".tmp-config");
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
19
|
-
mkdirSync(TMP, { recursive: true });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
24
|
-
// Clean up env vars
|
|
25
|
-
delete process.env.XDG_DATA_HOME;
|
|
26
|
-
delete process.env.XDG_CONFIG_HOME;
|
|
27
|
-
delete process.env.CORE_TEST_VAR;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// --- getDataDir ---
|
|
31
|
-
|
|
32
|
-
describe("getDataDir", () => {
|
|
33
|
-
test("uses XDG_DATA_HOME when set", () => {
|
|
34
|
-
process.env.XDG_DATA_HOME = "/custom/data";
|
|
35
|
-
expect(getDataDir("engram")).toBe("/custom/data/engram");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("falls back to ~/.local/share/{name}", () => {
|
|
39
|
-
delete process.env.XDG_DATA_HOME;
|
|
40
|
-
expect(getDataDir("engram")).toBe(
|
|
41
|
-
join(homedir(), ".local", "share", "engram"),
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// --- getConfigDir ---
|
|
47
|
-
|
|
48
|
-
describe("getConfigDir", () => {
|
|
49
|
-
test("uses XDG_CONFIG_HOME when set", () => {
|
|
50
|
-
process.env.XDG_CONFIG_HOME = "/custom/config";
|
|
51
|
-
expect(getConfigDir("synapse")).toBe("/custom/config/synapse");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("falls back to ~/.config/{name}", () => {
|
|
55
|
-
delete process.env.XDG_CONFIG_HOME;
|
|
56
|
-
expect(getConfigDir("synapse")).toBe(join(homedir(), ".config", "synapse"));
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// --- expandPath ---
|
|
61
|
-
|
|
62
|
-
describe("expandPath", () => {
|
|
63
|
-
test("expands ~ to homedir", () => {
|
|
64
|
-
const result = expandPath("~/foo/bar");
|
|
65
|
-
expect(result).toBe(join(homedir(), "foo/bar"));
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("leaves absolute paths unchanged", () => {
|
|
69
|
-
expect(expandPath("/usr/bin")).toBe("/usr/bin");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("leaves relative paths unchanged", () => {
|
|
73
|
-
expect(expandPath("foo/bar")).toBe("foo/bar");
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// --- interpolateEnvVars ---
|
|
78
|
-
|
|
79
|
-
describe("interpolateEnvVars", () => {
|
|
80
|
-
test("replaces env var references", () => {
|
|
81
|
-
process.env.CORE_TEST_VAR = "hello";
|
|
82
|
-
const result = interpolateEnvVars("prefix-${CORE_TEST_VAR}-suffix");
|
|
83
|
-
expect(result.ok).toBe(true);
|
|
84
|
-
if (result.ok) expect(result.value).toBe("prefix-hello-suffix");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("returns err for missing env var", () => {
|
|
88
|
-
delete process.env.CORE_TEST_VAR;
|
|
89
|
-
const result = interpolateEnvVars("${CORE_TEST_VAR}");
|
|
90
|
-
expect(result.ok).toBe(false);
|
|
91
|
-
if (!result.ok) expect(result.error).toContain("CORE_TEST_VAR");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("returns string unchanged when no vars present", () => {
|
|
95
|
-
const result = interpolateEnvVars("plain string");
|
|
96
|
-
expect(result.ok).toBe(true);
|
|
97
|
-
if (result.ok) expect(result.value).toBe("plain string");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("ignores malformed env var references", () => {
|
|
101
|
-
const result = interpolateEnvVars("${not valid}");
|
|
102
|
-
expect(result.ok).toBe(true);
|
|
103
|
-
if (result.ok) expect(result.value).toBe("${not valid}");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("ignores empty braces", () => {
|
|
107
|
-
const result = interpolateEnvVars("${}");
|
|
108
|
-
expect(result.ok).toBe(true);
|
|
109
|
-
if (result.ok) expect(result.value).toBe("${}");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// --- interpolateDeep ---
|
|
114
|
-
|
|
115
|
-
describe("interpolateDeep", () => {
|
|
116
|
-
test("interpolates strings in nested objects", () => {
|
|
117
|
-
process.env.CORE_TEST_VAR = "world";
|
|
118
|
-
const result = interpolateDeep({
|
|
119
|
-
greeting: "hello ${CORE_TEST_VAR}",
|
|
120
|
-
nested: { value: "${CORE_TEST_VAR}" },
|
|
121
|
-
});
|
|
122
|
-
expect(result.ok).toBe(true);
|
|
123
|
-
if (result.ok) {
|
|
124
|
-
expect(result.value).toEqual({
|
|
125
|
-
greeting: "hello world",
|
|
126
|
-
nested: { value: "world" },
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("interpolates strings in arrays", () => {
|
|
132
|
-
process.env.CORE_TEST_VAR = "item";
|
|
133
|
-
const result = interpolateDeep(["${CORE_TEST_VAR}", 42, true]);
|
|
134
|
-
expect(result.ok).toBe(true);
|
|
135
|
-
if (result.ok) {
|
|
136
|
-
expect(result.value).toEqual(["item", 42, true]);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("passes through non-string primitives", () => {
|
|
141
|
-
const result = interpolateDeep({ num: 42, bool: true, nil: null });
|
|
142
|
-
expect(result.ok).toBe(true);
|
|
143
|
-
if (result.ok) {
|
|
144
|
-
expect(result.value).toEqual({ num: 42, bool: true, nil: null });
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("returns err on missing env var in nested value", () => {
|
|
149
|
-
delete process.env.CORE_TEST_VAR;
|
|
150
|
-
const result = interpolateDeep({ deep: { value: "${CORE_TEST_VAR}" } });
|
|
151
|
-
expect(result.ok).toBe(false);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// --- parsePort ---
|
|
156
|
-
|
|
157
|
-
describe("parsePort", () => {
|
|
158
|
-
test("valid port returns Ok with branded Port", () => {
|
|
159
|
-
const result = parsePort("8080", "TEST");
|
|
160
|
-
expect(result.ok).toBe(true);
|
|
161
|
-
if (result.ok) expect(result.value as number).toBe(8080);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("port 1 is valid", () => {
|
|
165
|
-
const result = parsePort("1", "TEST");
|
|
166
|
-
expect(result.ok).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("port 65535 is valid", () => {
|
|
170
|
-
const result = parsePort("65535", "TEST");
|
|
171
|
-
expect(result.ok).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("port 0 is invalid", () => {
|
|
175
|
-
const result = parsePort("0", "TEST");
|
|
176
|
-
expect(result.ok).toBe(false);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("port 65536 is invalid", () => {
|
|
180
|
-
const result = parsePort("65536", "TEST");
|
|
181
|
-
expect(result.ok).toBe(false);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("non-numeric returns error", () => {
|
|
185
|
-
const result = parsePort("abc", "SOURCE");
|
|
186
|
-
expect(result.ok).toBe(false);
|
|
187
|
-
if (!result.ok) {
|
|
188
|
-
expect(result.error).toContain("abc");
|
|
189
|
-
expect(result.error).toContain("SOURCE");
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// --- loadJsonConfig ---
|
|
195
|
-
|
|
196
|
-
describe("loadJsonConfig", () => {
|
|
197
|
-
const defaults = { host: "localhost", port: 3000 };
|
|
198
|
-
|
|
199
|
-
test("returns defaults when config file missing", () => {
|
|
200
|
-
const result = loadJsonConfig({
|
|
201
|
-
name: "test",
|
|
202
|
-
defaults,
|
|
203
|
-
configPath: join(TMP, "nonexistent.json"),
|
|
204
|
-
});
|
|
205
|
-
expect(result.ok).toBe(true);
|
|
206
|
-
if (result.ok) {
|
|
207
|
-
expect(result.value.config).toEqual(defaults);
|
|
208
|
-
expect(result.value.source).toBe("defaults");
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("merges file config with defaults", () => {
|
|
213
|
-
const configPath = join(TMP, "config.json");
|
|
214
|
-
writeFileSync(configPath, JSON.stringify({ port: 9999 }));
|
|
215
|
-
|
|
216
|
-
const result = loadJsonConfig({ name: "test", defaults, configPath });
|
|
217
|
-
expect(result.ok).toBe(true);
|
|
218
|
-
if (result.ok) {
|
|
219
|
-
expect(result.value.config).toEqual({ host: "localhost", port: 9999 });
|
|
220
|
-
expect(result.value.source).toBe("file");
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("interpolates env vars in config file", () => {
|
|
225
|
-
process.env.CORE_TEST_VAR = "from-env";
|
|
226
|
-
const configPath = join(TMP, "config.json");
|
|
227
|
-
writeFileSync(configPath, JSON.stringify({ host: "${CORE_TEST_VAR}" }));
|
|
228
|
-
|
|
229
|
-
const result = loadJsonConfig({ name: "test", defaults, configPath });
|
|
230
|
-
expect(result.ok).toBe(true);
|
|
231
|
-
if (result.ok) {
|
|
232
|
-
expect(result.value.config.host).toBe("from-env");
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("returns err for invalid JSON", () => {
|
|
237
|
-
const configPath = join(TMP, "config.json");
|
|
238
|
-
writeFileSync(configPath, "not json{{{");
|
|
239
|
-
|
|
240
|
-
const result = loadJsonConfig({ name: "test", defaults, configPath });
|
|
241
|
-
expect(result.ok).toBe(false);
|
|
242
|
-
if (!result.ok) expect(result.error).toContain("invalid JSON");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("returns err for non-object JSON", () => {
|
|
246
|
-
const configPath = join(TMP, "config.json");
|
|
247
|
-
writeFileSync(configPath, "[1, 2, 3]");
|
|
248
|
-
|
|
249
|
-
const result = loadJsonConfig({ name: "test", defaults, configPath });
|
|
250
|
-
expect(result.ok).toBe(false);
|
|
251
|
-
if (!result.ok) expect(result.error).toContain("must be a JSON object");
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("returns err for missing env var in config", () => {
|
|
255
|
-
delete process.env.CORE_TEST_VAR;
|
|
256
|
-
const configPath = join(TMP, "config.json");
|
|
257
|
-
writeFileSync(configPath, JSON.stringify({ host: "${CORE_TEST_VAR}" }));
|
|
258
|
-
|
|
259
|
-
const result = loadJsonConfig({ name: "test", defaults, configPath });
|
|
260
|
-
expect(result.ok).toBe(false);
|
|
261
|
-
if (!result.ok) expect(result.error).toContain("CORE_TEST_VAR");
|
|
262
|
-
});
|
|
263
|
-
});
|
package/test/daemon.test.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import type { DaemonManager } from "../src/daemon";
|
|
5
|
-
import { createDaemonManager } from "../src/daemon";
|
|
6
|
-
|
|
7
|
-
const TMP = join(import.meta.dir, ".tmp-daemon");
|
|
8
|
-
|
|
9
|
-
function setup(): string {
|
|
10
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
11
|
-
mkdirSync(TMP, { recursive: true });
|
|
12
|
-
return TMP;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function teardown() {
|
|
16
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("createDaemonManager", () => {
|
|
20
|
-
test("creates a manager with all methods", () => {
|
|
21
|
-
const configDir = setup();
|
|
22
|
-
try {
|
|
23
|
-
const manager = createDaemonManager({
|
|
24
|
-
name: "test",
|
|
25
|
-
configDir,
|
|
26
|
-
cliPath: "/nonexistent/cli.ts",
|
|
27
|
-
});
|
|
28
|
-
expect(typeof manager.start).toBe("function");
|
|
29
|
-
expect(typeof manager.stop).toBe("function");
|
|
30
|
-
expect(typeof manager.restart).toBe("function");
|
|
31
|
-
expect(typeof manager.status).toBe("function");
|
|
32
|
-
} finally {
|
|
33
|
-
teardown();
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("status returns not running when no PID file", async () => {
|
|
38
|
-
const configDir = setup();
|
|
39
|
-
try {
|
|
40
|
-
const manager = createDaemonManager({
|
|
41
|
-
name: "test",
|
|
42
|
-
configDir,
|
|
43
|
-
cliPath: "/nonexistent/cli.ts",
|
|
44
|
-
});
|
|
45
|
-
const status = await manager.status();
|
|
46
|
-
expect(status.running).toBe(false);
|
|
47
|
-
expect(status.pid).toBeUndefined();
|
|
48
|
-
} finally {
|
|
49
|
-
teardown();
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("status cleans up stale PID file", async () => {
|
|
54
|
-
const configDir = setup();
|
|
55
|
-
try {
|
|
56
|
-
// Write a PID that doesn't correspond to a running process
|
|
57
|
-
writeFileSync(join(configDir, "test.pid"), "999999");
|
|
58
|
-
|
|
59
|
-
const manager = createDaemonManager({
|
|
60
|
-
name: "test",
|
|
61
|
-
configDir,
|
|
62
|
-
cliPath: "/nonexistent/cli.ts",
|
|
63
|
-
});
|
|
64
|
-
const status = await manager.status();
|
|
65
|
-
expect(status.running).toBe(false);
|
|
66
|
-
|
|
67
|
-
// PID file should be cleaned up
|
|
68
|
-
expect(existsSync(join(configDir, "test.pid"))).toBe(false);
|
|
69
|
-
} finally {
|
|
70
|
-
teardown();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("stop returns err when not running", async () => {
|
|
75
|
-
const configDir = setup();
|
|
76
|
-
try {
|
|
77
|
-
const manager = createDaemonManager({
|
|
78
|
-
name: "test",
|
|
79
|
-
configDir,
|
|
80
|
-
cliPath: "/nonexistent/cli.ts",
|
|
81
|
-
});
|
|
82
|
-
const result = await manager.stop();
|
|
83
|
-
expect(result.ok).toBe(false);
|
|
84
|
-
if (!result.ok) expect(result.error).toContain("not running");
|
|
85
|
-
} finally {
|
|
86
|
-
teardown();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
package/test/http.test.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
corsHeaders,
|
|
4
|
-
corsPreflightResponse,
|
|
5
|
-
createServer,
|
|
6
|
-
healthResponse,
|
|
7
|
-
jsonError,
|
|
8
|
-
jsonOk,
|
|
9
|
-
} from "../src/http";
|
|
10
|
-
|
|
11
|
-
// --- corsHeaders ---
|
|
12
|
-
|
|
13
|
-
describe("corsHeaders", () => {
|
|
14
|
-
test("returns CORS headers", () => {
|
|
15
|
-
const headers = corsHeaders();
|
|
16
|
-
expect(headers["Access-Control-Allow-Origin"]).toBe("*");
|
|
17
|
-
expect(headers["Access-Control-Allow-Methods"]).toContain("GET");
|
|
18
|
-
expect(headers["Access-Control-Allow-Headers"]).toContain("Content-Type");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("returns a fresh copy each time", () => {
|
|
22
|
-
const a = corsHeaders();
|
|
23
|
-
const b = corsHeaders();
|
|
24
|
-
expect(a).toEqual(b);
|
|
25
|
-
a["X-Custom"] = "mutated";
|
|
26
|
-
expect(b["X-Custom"]).toBeUndefined();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// --- corsPreflightResponse ---
|
|
31
|
-
|
|
32
|
-
describe("corsPreflightResponse", () => {
|
|
33
|
-
test("returns 204 with CORS headers", () => {
|
|
34
|
-
const res = corsPreflightResponse();
|
|
35
|
-
expect(res.status).toBe(204);
|
|
36
|
-
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// --- jsonOk ---
|
|
41
|
-
|
|
42
|
-
describe("jsonOk", () => {
|
|
43
|
-
test("returns JSON with 200 by default", async () => {
|
|
44
|
-
const res = jsonOk({ message: "hello" });
|
|
45
|
-
expect(res.status).toBe(200);
|
|
46
|
-
const body = await res.json();
|
|
47
|
-
expect(body).toEqual({ message: "hello" });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("supports custom status code", () => {
|
|
51
|
-
const res = jsonOk({ id: 1 }, 201);
|
|
52
|
-
expect(res.status).toBe(201);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("includes CORS headers", () => {
|
|
56
|
-
const res = jsonOk({});
|
|
57
|
-
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// --- jsonError ---
|
|
62
|
-
|
|
63
|
-
describe("jsonError", () => {
|
|
64
|
-
test("returns error JSON", async () => {
|
|
65
|
-
const res = jsonError(400, "bad request");
|
|
66
|
-
expect(res.status).toBe(400);
|
|
67
|
-
const body = await res.json();
|
|
68
|
-
expect(body).toEqual({ error: "bad request" });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("includes CORS headers", () => {
|
|
72
|
-
const res = jsonError(500, "oops");
|
|
73
|
-
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// --- healthResponse ---
|
|
78
|
-
|
|
79
|
-
describe("healthResponse", () => {
|
|
80
|
-
test("returns standard health fields", async () => {
|
|
81
|
-
const startTime = Date.now() - 5000;
|
|
82
|
-
const res = healthResponse("1.0.0", startTime);
|
|
83
|
-
expect(res.status).toBe(200);
|
|
84
|
-
const body = (await res.json()) as Record<string, unknown>;
|
|
85
|
-
expect(body.status).toBe("healthy");
|
|
86
|
-
expect(body.version).toBe("1.0.0");
|
|
87
|
-
expect(typeof body.uptime).toBe("number");
|
|
88
|
-
expect((body.uptime as number) >= 4).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("includes extra fields when provided", async () => {
|
|
92
|
-
const res = healthResponse("1.0.0", Date.now(), {
|
|
93
|
-
memories: 42,
|
|
94
|
-
});
|
|
95
|
-
const body = (await res.json()) as Record<string, unknown>;
|
|
96
|
-
expect(body.memories).toBe(42);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// --- createServer ---
|
|
101
|
-
|
|
102
|
-
describe("createServer", () => {
|
|
103
|
-
let server: ReturnType<typeof createServer> | null = null;
|
|
104
|
-
|
|
105
|
-
afterAll(() => {
|
|
106
|
-
if (server) server.stop();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("starts server and responds to /health", async () => {
|
|
110
|
-
server = createServer({
|
|
111
|
-
port: 0, // random available port
|
|
112
|
-
version: "0.1.0-test",
|
|
113
|
-
onRequest: () => null,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const res = await fetch(`http://localhost:${server.port}/health`);
|
|
117
|
-
expect(res.status).toBe(200);
|
|
118
|
-
const body = (await res.json()) as Record<string, unknown>;
|
|
119
|
-
expect(body.status).toBe("healthy");
|
|
120
|
-
expect(body.version).toBe("0.1.0-test");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("handles OPTIONS preflight", async () => {
|
|
124
|
-
const res = await fetch(`http://localhost:${server!.port}/anything`, {
|
|
125
|
-
method: "OPTIONS",
|
|
126
|
-
});
|
|
127
|
-
expect(res.status).toBe(204);
|
|
128
|
-
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("routes to onRequest for custom paths", async () => {
|
|
132
|
-
server!.stop();
|
|
133
|
-
server = createServer({
|
|
134
|
-
port: 0,
|
|
135
|
-
version: "0.1.0-test",
|
|
136
|
-
onRequest: (_req, url) => {
|
|
137
|
-
if (url.pathname === "/echo") return jsonOk({ path: "/echo" });
|
|
138
|
-
return null;
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const res = await fetch(`http://localhost:${server.port}/echo`);
|
|
143
|
-
expect(res.status).toBe(200);
|
|
144
|
-
const body = (await res.json()) as Record<string, unknown>;
|
|
145
|
-
expect(body.path).toBe("/echo");
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("returns 404 when onRequest returns null", async () => {
|
|
149
|
-
const res = await fetch(`http://localhost:${server!.port}/nonexistent`);
|
|
150
|
-
expect(res.status).toBe(404);
|
|
151
|
-
});
|
|
152
|
-
});
|
package/test/result.test.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { err, ok } from "../src/result";
|
|
3
|
-
|
|
4
|
-
describe("ok", () => {
|
|
5
|
-
test("creates Ok result", () => {
|
|
6
|
-
const result = ok(42);
|
|
7
|
-
expect(result.ok).toBe(true);
|
|
8
|
-
expect(result.value).toBe(42);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("works with string values", () => {
|
|
12
|
-
const result = ok("hello");
|
|
13
|
-
expect(result.ok).toBe(true);
|
|
14
|
-
expect(result.value).toBe("hello");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("works with objects", () => {
|
|
18
|
-
const result = ok({ name: "test" });
|
|
19
|
-
expect(result.ok).toBe(true);
|
|
20
|
-
expect(result.value).toEqual({ name: "test" });
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("err", () => {
|
|
25
|
-
test("creates Err result", () => {
|
|
26
|
-
const result = err("something went wrong");
|
|
27
|
-
expect(result.ok).toBe(false);
|
|
28
|
-
expect(result.error).toBe("something went wrong");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("works with structured errors", () => {
|
|
32
|
-
const result = err({ code: "NOT_FOUND", message: "missing" });
|
|
33
|
-
expect(result.ok).toBe(false);
|
|
34
|
-
expect(result.error).toEqual({ code: "NOT_FOUND", message: "missing" });
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe("type narrowing", () => {
|
|
39
|
-
test("narrows Ok in conditional", () => {
|
|
40
|
-
const result = ok(42);
|
|
41
|
-
if (result.ok) {
|
|
42
|
-
// TypeScript knows this is Ok<number>
|
|
43
|
-
expect(result.value).toBe(42);
|
|
44
|
-
} else {
|
|
45
|
-
throw new Error("should not reach");
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("narrows Err in conditional", () => {
|
|
50
|
-
const result = err("bad");
|
|
51
|
-
if (!result.ok) {
|
|
52
|
-
// TypeScript knows this is Err<string>
|
|
53
|
-
expect(result.error).toBe("bad");
|
|
54
|
-
} else {
|
|
55
|
-
throw new Error("should not reach");
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
package/test/signals.test.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { onShutdown } from "../src/signals";
|
|
3
|
-
|
|
4
|
-
describe("onShutdown", () => {
|
|
5
|
-
test("registers handler without throwing", () => {
|
|
6
|
-
// onShutdown registers process listeners — verify it doesn't throw.
|
|
7
|
-
// We can't easily test signal delivery in unit tests, but we can
|
|
8
|
-
// verify the function accepts valid inputs.
|
|
9
|
-
expect(() => {
|
|
10
|
-
onShutdown(() => {}, { signals: [] });
|
|
11
|
-
}).not.toThrow();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("accepts async cleanup function", () => {
|
|
15
|
-
expect(() => {
|
|
16
|
-
onShutdown(async () => {}, { signals: [] });
|
|
17
|
-
}).not.toThrow();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("accepts custom signals list", () => {
|
|
21
|
-
expect(() => {
|
|
22
|
-
onShutdown(() => {}, { signals: ["SIGUSR1"], timeoutMs: 1000 });
|
|
23
|
-
}).not.toThrow();
|
|
24
|
-
});
|
|
25
|
-
});
|
package/test/version.test.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { readVersion } from "../src/version";
|
|
5
|
-
|
|
6
|
-
const TMP = join(import.meta.dir, ".tmp-version");
|
|
7
|
-
|
|
8
|
-
function setup() {
|
|
9
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
10
|
-
mkdirSync(TMP, { recursive: true });
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function teardown() {
|
|
14
|
-
if (existsSync(TMP)) rmSync(TMP, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("readVersion", () => {
|
|
18
|
-
test("returns fallback when VERSION file missing", () => {
|
|
19
|
-
setup();
|
|
20
|
-
try {
|
|
21
|
-
expect(readVersion(TMP)).toBe("0.0.0-dev");
|
|
22
|
-
} finally {
|
|
23
|
-
teardown();
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("returns custom fallback when VERSION file missing", () => {
|
|
28
|
-
setup();
|
|
29
|
-
try {
|
|
30
|
-
expect(readVersion(TMP, "1.0.0-local")).toBe("1.0.0-local");
|
|
31
|
-
} finally {
|
|
32
|
-
teardown();
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("reads VERSION file when present", () => {
|
|
37
|
-
setup();
|
|
38
|
-
try {
|
|
39
|
-
writeFileSync(join(TMP, "VERSION"), "2.3.4\n");
|
|
40
|
-
expect(readVersion(TMP)).toBe("2.3.4");
|
|
41
|
-
} finally {
|
|
42
|
-
teardown();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("trims whitespace from VERSION file", () => {
|
|
47
|
-
setup();
|
|
48
|
-
try {
|
|
49
|
-
writeFileSync(join(TMP, "VERSION"), " 1.0.0 \n\n");
|
|
50
|
-
expect(readVersion(TMP)).toBe("1.0.0");
|
|
51
|
-
} finally {
|
|
52
|
-
teardown();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"noEmit": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"allowSyntheticDefaultImports": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"isolatedModules": true,
|
|
14
|
-
"types": ["bun"]
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*", "test/**/*"],
|
|
17
|
-
"exclude": ["node_modules"]
|
|
18
|
-
}
|