@cporter/sdk 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 +63 -0
- package/dist/client.d.ts +99 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +207 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/hash.d.ts +7 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +17 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @cporter/sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the [cPorter](../../README.md) deploy API. This is the shared
|
|
4
|
+
core that [`@cporter/cli`](../cli), the [GitHub Action](../github-action), and the
|
|
5
|
+
[MCP server](../mcp) are all built on — use it directly for programmatic deploys.
|
|
6
|
+
|
|
7
|
+
Node ≥ 20, zero runtime dependencies (uses the platform `fetch`).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @cporter/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { CporterClient, isSuccess } from '@cporter/sdk';
|
|
19
|
+
|
|
20
|
+
const client = new CporterClient({
|
|
21
|
+
host: 'https://deploy.example.com',
|
|
22
|
+
token: process.env.CPORTER_TOKEN!, // cpk_…
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Verify the key first (optional)
|
|
26
|
+
console.log(await client.whoami());
|
|
27
|
+
|
|
28
|
+
// Upload + deploy (auto single/chunked by size), then wait for the result
|
|
29
|
+
const deployment = await client.deployAndWait('my-site', {
|
|
30
|
+
artifactPath: './out.zip',
|
|
31
|
+
version: 'v1.2.3',
|
|
32
|
+
onProgress: (p) => console.log(p.phase),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!isSuccess(deployment.status)) {
|
|
36
|
+
throw new Error(`Deploy failed: ${deployment.status}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Roll back to the previous release
|
|
40
|
+
await client.rollback('my-site');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
| Method | Purpose |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `whoami()` | Verify the token; returns name, scopes, project binding. |
|
|
48
|
+
| `deploy(project, opts)` | Upload artifact + create deployment. Picks single vs chunked upload by size. Returns the created (non-terminal) deployment. |
|
|
49
|
+
| `waitForDeployment(project, id, opts)` | Poll until terminal (`success`/`failed`/`rolled_back`) or timeout. |
|
|
50
|
+
| `deployAndWait(project, opts)` | `deploy` + `waitForDeployment` in one call. |
|
|
51
|
+
| `getDeployment(project, id)` | Fetch current status. |
|
|
52
|
+
| `rollback(project, { releaseId? })` | Roll back to previous or a given release. |
|
|
53
|
+
|
|
54
|
+
Key behaviours:
|
|
55
|
+
|
|
56
|
+
- **Integrity**: the SHA-256 is streamed from disk and sent as `sha256`; the server rejects
|
|
57
|
+
a mismatch with HTTP 422.
|
|
58
|
+
- **Idempotency**: `idempotencyKey` defaults to the artifact SHA-256, so retrying the same
|
|
59
|
+
build returns the existing deployment instead of duplicating it.
|
|
60
|
+
- **Large artifacts**: files over `chunkThresholdBytes` (default 100 MB) upload in chunks
|
|
61
|
+
automatically; force it with `chunked: true`.
|
|
62
|
+
- **Errors**: non-2xx throws `CporterApiError` (`.status`, `.apiError`, `.body`); a poll
|
|
63
|
+
timeout throws `DeploymentTimeoutError`.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type Deployment, type WhoAmI } from './types.js';
|
|
2
|
+
export interface CporterClientOptions {
|
|
3
|
+
/** Base origin of the cPorter instance, e.g. `https://deploy.example.com`. */
|
|
4
|
+
host: string;
|
|
5
|
+
/** API key (`cpk_…`) — sent as a Bearer token. */
|
|
6
|
+
token: string;
|
|
7
|
+
/** API path prefix. Defaults to `/api/v1`. */
|
|
8
|
+
apiPrefix?: string;
|
|
9
|
+
/** Injectable fetch (for testing / custom agents). Defaults to global `fetch`. */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
export type UploadPhase = 'hashing' | 'uploading' | 'chunk' | 'complete';
|
|
13
|
+
export interface UploadProgress {
|
|
14
|
+
phase: UploadPhase;
|
|
15
|
+
/** Bytes uploaded so far (chunked uploads only). */
|
|
16
|
+
uploadedBytes?: number;
|
|
17
|
+
totalBytes?: number;
|
|
18
|
+
/** 0-based index of the chunk just sent (chunked uploads only). */
|
|
19
|
+
chunkIndex?: number;
|
|
20
|
+
chunkCount?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface DeployOptions {
|
|
23
|
+
/** Path to the artifact `.zip` on disk. */
|
|
24
|
+
artifactPath: string;
|
|
25
|
+
/** Optional human version label; the server falls back to the generated release id. */
|
|
26
|
+
version?: string;
|
|
27
|
+
/** Precomputed lowercase-hex SHA-256. Computed from the file when omitted. */
|
|
28
|
+
sha256?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Idempotency key — replaying the same key returns the existing deployment (HTTP 200)
|
|
31
|
+
* instead of creating a new one. Defaults to the artifact's SHA-256.
|
|
32
|
+
*/
|
|
33
|
+
idempotencyKey?: string;
|
|
34
|
+
/** Force chunked upload regardless of size. */
|
|
35
|
+
chunked?: boolean;
|
|
36
|
+
/** Size above which chunked upload is used automatically. */
|
|
37
|
+
chunkThresholdBytes?: number;
|
|
38
|
+
/** Chunk size for chunked uploads. */
|
|
39
|
+
chunkSizeBytes?: number;
|
|
40
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
export interface WaitOptions {
|
|
44
|
+
/** Poll interval in ms. Default 3000. */
|
|
45
|
+
intervalMs?: number;
|
|
46
|
+
/** Give up after this many ms. Default 600000 (10 min). */
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
/** Called on each poll with the latest deployment. */
|
|
49
|
+
onUpdate?: (deployment: Deployment) => void;
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
}
|
|
52
|
+
export interface RollbackOptions {
|
|
53
|
+
/** Target release id. Omit to roll back to the previous release. */
|
|
54
|
+
releaseId?: number;
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Client for the cPorter deploy API. Wraps authentication, artifact upload (single or
|
|
59
|
+
* chunked), status polling, and rollback so callers never touch the raw HTTP contract.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const client = new CporterClient({ host: 'https://deploy.example.com', token: 'cpk_…' });
|
|
63
|
+
* const dep = await client.deploy('my-site', { artifactPath: './out.zip' });
|
|
64
|
+
* const done = await client.waitForDeployment('my-site', dep.id);
|
|
65
|
+
*/
|
|
66
|
+
export declare class CporterClient {
|
|
67
|
+
private readonly baseUrl;
|
|
68
|
+
private readonly token;
|
|
69
|
+
private readonly fetchImpl;
|
|
70
|
+
constructor(options: CporterClientOptions);
|
|
71
|
+
/** Verify the token and inspect its scopes / project binding. */
|
|
72
|
+
whoami(signal?: AbortSignal): Promise<WhoAmI>;
|
|
73
|
+
/**
|
|
74
|
+
* Upload an artifact and create a deployment. Automatically picks single-request or
|
|
75
|
+
* chunked upload based on file size (override with `chunked` / `chunkThresholdBytes`).
|
|
76
|
+
* Returns the created deployment (still `queued`/`running`) — poll with
|
|
77
|
+
* {@link waitForDeployment} to await the terminal status.
|
|
78
|
+
*/
|
|
79
|
+
deploy(project: string, options: DeployOptions): Promise<Deployment>;
|
|
80
|
+
private deploySingle;
|
|
81
|
+
private deployChunked;
|
|
82
|
+
/** Fetch a single deployment's current state. */
|
|
83
|
+
getDeployment(project: string, deploymentId: number, signal?: AbortSignal): Promise<Deployment>;
|
|
84
|
+
/**
|
|
85
|
+
* Poll a deployment until it reaches a terminal status (`success`/`failed`/`rolled_back`)
|
|
86
|
+
* or the timeout elapses. Returns the terminal deployment — inspect `.status` to tell
|
|
87
|
+
* success from failure; throws {@link DeploymentTimeoutError} on timeout.
|
|
88
|
+
*/
|
|
89
|
+
waitForDeployment(project: string, deploymentId: number, options?: WaitOptions): Promise<Deployment>;
|
|
90
|
+
/** Convenience: deploy and wait for the result in one call. */
|
|
91
|
+
deployAndWait(project: string, options: DeployOptions & {
|
|
92
|
+
wait?: WaitOptions;
|
|
93
|
+
}): Promise<Deployment>;
|
|
94
|
+
/** Roll the project back to the previous (or a specified) release. */
|
|
95
|
+
rollback(project: string, options?: RollbackOptions): Promise<Deployment>;
|
|
96
|
+
/** Low-level request helper: attaches auth, parses `{ data }`, throws on non-2xx. */
|
|
97
|
+
private request;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { openAsBlob } from 'node:fs';
|
|
2
|
+
import { open, stat } from 'node:fs/promises';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { CporterApiError, DeploymentTimeoutError } from './errors.js';
|
|
5
|
+
import { sha256File } from './hash.js';
|
|
6
|
+
import { isTerminal } from './types.js';
|
|
7
|
+
/** Default: artifacts larger than this are uploaded in chunks instead of one request. */
|
|
8
|
+
const DEFAULT_CHUNK_THRESHOLD_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
9
|
+
/** Default size of each raw chunk in a chunked upload. */
|
|
10
|
+
const DEFAULT_CHUNK_SIZE_BYTES = 8 * 1024 * 1024; // 8 MB
|
|
11
|
+
function sleep(ms, signal) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
if (signal?.aborted) {
|
|
14
|
+
reject(signal.reason ?? new Error('Aborted'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const timer = setTimeout(() => {
|
|
18
|
+
signal?.removeEventListener('abort', onAbort);
|
|
19
|
+
resolve();
|
|
20
|
+
}, ms);
|
|
21
|
+
const onAbort = () => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
reject(signal?.reason ?? new Error('Aborted'));
|
|
24
|
+
};
|
|
25
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Client for the cPorter deploy API. Wraps authentication, artifact upload (single or
|
|
30
|
+
* chunked), status polling, and rollback so callers never touch the raw HTTP contract.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const client = new CporterClient({ host: 'https://deploy.example.com', token: 'cpk_…' });
|
|
34
|
+
* const dep = await client.deploy('my-site', { artifactPath: './out.zip' });
|
|
35
|
+
* const done = await client.waitForDeployment('my-site', dep.id);
|
|
36
|
+
*/
|
|
37
|
+
export class CporterClient {
|
|
38
|
+
baseUrl;
|
|
39
|
+
token;
|
|
40
|
+
fetchImpl;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
if (!options.host)
|
|
43
|
+
throw new Error('CporterClient: `host` is required.');
|
|
44
|
+
if (!options.token)
|
|
45
|
+
throw new Error('CporterClient: `token` is required.');
|
|
46
|
+
const host = options.host.replace(/\/+$/, '');
|
|
47
|
+
const prefix = (options.apiPrefix ?? '/api/v1').replace(/\/+$/, '');
|
|
48
|
+
this.baseUrl = `${host}${prefix}`;
|
|
49
|
+
this.token = options.token;
|
|
50
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
51
|
+
if (typeof this.fetchImpl !== 'function') {
|
|
52
|
+
throw new Error('CporterClient: no global `fetch` available (Node >= 20 required).');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Verify the token and inspect its scopes / project binding. */
|
|
56
|
+
async whoami(signal) {
|
|
57
|
+
return this.request('GET', '/whoami', { signal });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Upload an artifact and create a deployment. Automatically picks single-request or
|
|
61
|
+
* chunked upload based on file size (override with `chunked` / `chunkThresholdBytes`).
|
|
62
|
+
* Returns the created deployment (still `queued`/`running`) — poll with
|
|
63
|
+
* {@link waitForDeployment} to await the terminal status.
|
|
64
|
+
*/
|
|
65
|
+
async deploy(project, options) {
|
|
66
|
+
const { size } = await stat(options.artifactPath);
|
|
67
|
+
options.onProgress?.({ phase: 'hashing', totalBytes: size });
|
|
68
|
+
const sha256 = options.sha256 ?? (await sha256File(options.artifactPath));
|
|
69
|
+
const idempotencyKey = options.idempotencyKey ?? sha256;
|
|
70
|
+
const threshold = options.chunkThresholdBytes ?? DEFAULT_CHUNK_THRESHOLD_BYTES;
|
|
71
|
+
const useChunked = options.chunked ?? size > threshold;
|
|
72
|
+
return useChunked
|
|
73
|
+
? this.deployChunked(project, options, sha256, idempotencyKey, size)
|
|
74
|
+
: this.deploySingle(project, options, sha256, idempotencyKey, size);
|
|
75
|
+
}
|
|
76
|
+
async deploySingle(project, options, sha256, idempotencyKey, size) {
|
|
77
|
+
options.onProgress?.({ phase: 'uploading', totalBytes: size, uploadedBytes: 0 });
|
|
78
|
+
const blob = await openAsBlob(options.artifactPath);
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('artifact', blob, basename(options.artifactPath));
|
|
81
|
+
form.append('sha256', sha256);
|
|
82
|
+
if (options.version)
|
|
83
|
+
form.append('version', options.version);
|
|
84
|
+
const deployment = await this.request('POST', `/projects/${encodeURIComponent(project)}/deployments`, { body: form, headers: { 'Idempotency-Key': idempotencyKey }, signal: options.signal });
|
|
85
|
+
options.onProgress?.({ phase: 'complete', totalBytes: size, uploadedBytes: size });
|
|
86
|
+
return deployment;
|
|
87
|
+
}
|
|
88
|
+
async deployChunked(project, options, sha256, idempotencyKey, size) {
|
|
89
|
+
const chunkSize = options.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE_BYTES;
|
|
90
|
+
const chunkCount = Math.max(1, Math.ceil(size / chunkSize));
|
|
91
|
+
const base = `/projects/${encodeURIComponent(project)}/artifacts/uploads`;
|
|
92
|
+
const { upload_id: uploadId } = await this.request('POST', base, {
|
|
93
|
+
signal: options.signal,
|
|
94
|
+
});
|
|
95
|
+
let handle;
|
|
96
|
+
try {
|
|
97
|
+
handle = await open(options.artifactPath, 'r');
|
|
98
|
+
const buffer = Buffer.allocUnsafe(chunkSize);
|
|
99
|
+
let uploadedBytes = 0;
|
|
100
|
+
for (let index = 0; index < chunkCount; index++) {
|
|
101
|
+
const { bytesRead } = await handle.read(buffer, 0, chunkSize, index * chunkSize);
|
|
102
|
+
// Copy the exact slice — the shared buffer may be larger than the last chunk.
|
|
103
|
+
const chunk = Uint8Array.prototype.slice.call(buffer, 0, bytesRead);
|
|
104
|
+
await this.request('PUT', `${base}/${uploadId}/chunks/${index}`, {
|
|
105
|
+
body: chunk,
|
|
106
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
107
|
+
signal: options.signal,
|
|
108
|
+
});
|
|
109
|
+
uploadedBytes += bytesRead;
|
|
110
|
+
options.onProgress?.({
|
|
111
|
+
phase: 'chunk',
|
|
112
|
+
chunkIndex: index,
|
|
113
|
+
chunkCount,
|
|
114
|
+
uploadedBytes,
|
|
115
|
+
totalBytes: size,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await handle?.close();
|
|
121
|
+
}
|
|
122
|
+
const body = { sha256 };
|
|
123
|
+
if (options.version)
|
|
124
|
+
body.version = options.version;
|
|
125
|
+
const deployment = await this.request('POST', `${base}/${uploadId}/complete`, {
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
|
|
128
|
+
signal: options.signal,
|
|
129
|
+
});
|
|
130
|
+
options.onProgress?.({ phase: 'complete', totalBytes: size, uploadedBytes: size });
|
|
131
|
+
return deployment;
|
|
132
|
+
}
|
|
133
|
+
/** Fetch a single deployment's current state. */
|
|
134
|
+
async getDeployment(project, deploymentId, signal) {
|
|
135
|
+
return this.request('GET', `/projects/${encodeURIComponent(project)}/deployments/${deploymentId}`, { signal });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Poll a deployment until it reaches a terminal status (`success`/`failed`/`rolled_back`)
|
|
139
|
+
* or the timeout elapses. Returns the terminal deployment — inspect `.status` to tell
|
|
140
|
+
* success from failure; throws {@link DeploymentTimeoutError} on timeout.
|
|
141
|
+
*/
|
|
142
|
+
async waitForDeployment(project, deploymentId, options = {}) {
|
|
143
|
+
const intervalMs = options.intervalMs ?? 3000;
|
|
144
|
+
const timeoutMs = options.timeoutMs ?? 600_000;
|
|
145
|
+
const deadline = Date.now() + timeoutMs;
|
|
146
|
+
let latest = await this.getDeployment(project, deploymentId, options.signal);
|
|
147
|
+
options.onUpdate?.(latest);
|
|
148
|
+
while (!isTerminal(latest.status)) {
|
|
149
|
+
if (Date.now() >= deadline) {
|
|
150
|
+
throw new DeploymentTimeoutError(deploymentId, latest.status, timeoutMs);
|
|
151
|
+
}
|
|
152
|
+
await sleep(intervalMs, options.signal);
|
|
153
|
+
latest = await this.getDeployment(project, deploymentId, options.signal);
|
|
154
|
+
options.onUpdate?.(latest);
|
|
155
|
+
}
|
|
156
|
+
return latest;
|
|
157
|
+
}
|
|
158
|
+
/** Convenience: deploy and wait for the result in one call. */
|
|
159
|
+
async deployAndWait(project, options) {
|
|
160
|
+
const created = await this.deploy(project, options);
|
|
161
|
+
return this.waitForDeployment(project, created.id, options.wait);
|
|
162
|
+
}
|
|
163
|
+
/** Roll the project back to the previous (or a specified) release. */
|
|
164
|
+
async rollback(project, options = {}) {
|
|
165
|
+
const body = {};
|
|
166
|
+
if (options.releaseId != null)
|
|
167
|
+
body.release_id = options.releaseId;
|
|
168
|
+
return this.request('POST', `/projects/${encodeURIComponent(project)}/rollback`, {
|
|
169
|
+
body: JSON.stringify(body),
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
signal: options.signal,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Low-level request helper: attaches auth, parses `{ data }`, throws on non-2xx. */
|
|
175
|
+
async request(method, path, init = {}) {
|
|
176
|
+
const url = `${this.baseUrl}${path}`;
|
|
177
|
+
const response = await this.fetchImpl(url, {
|
|
178
|
+
method,
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${this.token}`,
|
|
181
|
+
Accept: 'application/json',
|
|
182
|
+
...init.headers,
|
|
183
|
+
},
|
|
184
|
+
body: init.body,
|
|
185
|
+
signal: init.signal,
|
|
186
|
+
});
|
|
187
|
+
const text = await response.text();
|
|
188
|
+
const parsed = text ? safeJsonParse(text) : undefined;
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
throw new CporterApiError(response.status, parsed ?? text, `${method} ${path}`);
|
|
191
|
+
}
|
|
192
|
+
// The API wraps successful payloads in `{ data: ... }`.
|
|
193
|
+
if (parsed && typeof parsed === 'object' && 'data' in parsed) {
|
|
194
|
+
return parsed.data;
|
|
195
|
+
}
|
|
196
|
+
return parsed;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function safeJsonParse(text) {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(text);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return text;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error raised when the API returns a non-2xx response. Carries the HTTP status and the
|
|
3
|
+
* parsed body so callers can branch on, e.g., a 422 hash mismatch (`expected`/`actual`)
|
|
4
|
+
* or a 403 project-scope denial.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CporterApiError extends Error {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly body: unknown;
|
|
9
|
+
/** Convenience: the `error` field the API puts on failures, when present. */
|
|
10
|
+
readonly apiError: string | undefined;
|
|
11
|
+
constructor(status: number, body: unknown, requestSummary: string);
|
|
12
|
+
}
|
|
13
|
+
/** Raised when a deployment does not reach a terminal status within the poll timeout. */
|
|
14
|
+
export declare class DeploymentTimeoutError extends Error {
|
|
15
|
+
readonly deploymentId: number;
|
|
16
|
+
readonly lastStatus: string;
|
|
17
|
+
constructor(deploymentId: number, lastStatus: string, timeoutMs: number);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE1B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM;CAWlE;AAED,yFAAyF;AACzF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAQxE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error raised when the API returns a non-2xx response. Carries the HTTP status and the
|
|
3
|
+
* parsed body so callers can branch on, e.g., a 422 hash mismatch (`expected`/`actual`)
|
|
4
|
+
* or a 403 project-scope denial.
|
|
5
|
+
*/
|
|
6
|
+
export class CporterApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
/** Convenience: the `error` field the API puts on failures, when present. */
|
|
10
|
+
apiError;
|
|
11
|
+
constructor(status, body, requestSummary) {
|
|
12
|
+
const apiError = body && typeof body === 'object' && 'error' in body && typeof body.error === 'string'
|
|
13
|
+
? body.error
|
|
14
|
+
: undefined;
|
|
15
|
+
super(`cPorter API ${status} on ${requestSummary}${apiError ? `: ${apiError}` : ''}`);
|
|
16
|
+
this.name = 'CporterApiError';
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.body = body;
|
|
19
|
+
this.apiError = apiError;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Raised when a deployment does not reach a terminal status within the poll timeout. */
|
|
23
|
+
export class DeploymentTimeoutError extends Error {
|
|
24
|
+
deploymentId;
|
|
25
|
+
lastStatus;
|
|
26
|
+
constructor(deploymentId, lastStatus, timeoutMs) {
|
|
27
|
+
super(`Deployment #${deploymentId} did not finish within ${timeoutMs}ms (last status: ${lastStatus}).`);
|
|
28
|
+
this.name = 'DeploymentTimeoutError';
|
|
29
|
+
this.deploymentId = deploymentId;
|
|
30
|
+
this.lastStatus = lastStatus;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,MAAM,CAAS;IACf,IAAI,CAAU;IACvB,6EAA6E;IACpE,QAAQ,CAAqB;IAEtC,YAAY,MAAc,EAAE,IAAa,EAAE,cAAsB;QAC/D,MAAM,QAAQ,GACZ,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,IAAI,OAAQ,IAA2B,CAAC,KAAK,KAAK,QAAQ;YAC3G,CAAC,CAAE,IAA0B,CAAC,KAAK;YACnC,CAAC,CAAC,SAAS,CAAC;QAChB,KAAK,CAAC,eAAe,MAAM,OAAO,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,yFAAyF;AACzF,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,YAAY,CAAS;IACrB,UAAU,CAAS;IAE5B,YAAY,YAAoB,EAAE,UAAkB,EAAE,SAAiB;QACrE,KAAK,CACH,eAAe,YAAY,0BAA0B,SAAS,oBAAoB,UAAU,IAAI,CACjG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the lowercase hex SHA-256 of a file by streaming it — never loads the whole
|
|
3
|
+
* artifact into memory. This is the digest the API verifies against (`sha256` field);
|
|
4
|
+
* the CLI also reuses it as the default Idempotency-Key.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sha256File(path: string): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxD"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
/**
|
|
4
|
+
* Compute the lowercase hex SHA-256 of a file by streaming it — never loads the whole
|
|
5
|
+
* artifact into memory. This is the digest the API verifies against (`sha256` field);
|
|
6
|
+
* the CLI also reuses it as the default Idempotency-Key.
|
|
7
|
+
*/
|
|
8
|
+
export function sha256File(path) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const hash = createHash('sha256');
|
|
11
|
+
const stream = createReadStream(path);
|
|
12
|
+
stream.on('error', reject);
|
|
13
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
14
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cporter/sdk — official TypeScript SDK for the cPorter deploy API.
|
|
3
|
+
*
|
|
4
|
+
* This is the shared core that the CLI, GitHub Action, and MCP server are built on.
|
|
5
|
+
* @see https://github.com/ (project repo) and docs/SPEC.md for the underlying API.
|
|
6
|
+
*/
|
|
7
|
+
export { CporterClient } from './client.js';
|
|
8
|
+
export type { CporterClientOptions, DeployOptions, WaitOptions, RollbackOptions, UploadProgress, UploadPhase, } from './client.js';
|
|
9
|
+
export { CporterApiError, DeploymentTimeoutError } from './errors.js';
|
|
10
|
+
export { sha256File } from './hash.js';
|
|
11
|
+
export { TERMINAL_STATUSES, isTerminal, isSuccess, type Deployment, type DeploymentStatus, type DeploymentTrigger, type DeploymentStep, type Release, type WhoAmI, } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,MAAM,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cporter/sdk — official TypeScript SDK for the cPorter deploy API.
|
|
3
|
+
*
|
|
4
|
+
* This is the shared core that the CLI, GitHub Action, and MCP server are built on.
|
|
5
|
+
* @see https://github.com/ (project repo) and docs/SPEC.md for the underlying API.
|
|
6
|
+
*/
|
|
7
|
+
export { CporterClient } from './client.js';
|
|
8
|
+
export { CporterApiError, DeploymentTimeoutError } from './errors.js';
|
|
9
|
+
export { sha256File } from './hash.js';
|
|
10
|
+
export { TERMINAL_STATUSES, isTerminal, isSuccess, } from './types.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,GAOV,MAAM,YAAY,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types mirroring the cPorter API contract (apps/api, docs/SPEC.md §5–§7).
|
|
3
|
+
* The API wraps every payload in `{ "data": ... }`; these describe the unwrapped `data`.
|
|
4
|
+
*/
|
|
5
|
+
/** Deployment pipeline lifecycle (App\Enums\DeploymentStatus). */
|
|
6
|
+
export type DeploymentStatus = 'queued' | 'running' | 'hooks_pending' | 'success' | 'failed' | 'rolled_back';
|
|
7
|
+
/** How a deployment was triggered (App\Enums\DeploymentTrigger). */
|
|
8
|
+
export type DeploymentTrigger = 'api' | 'webhook' | 'manual';
|
|
9
|
+
/** Terminal statuses — polling stops once a deployment reaches one of these. */
|
|
10
|
+
export declare const TERMINAL_STATUSES: readonly ["success", "failed", "rolled_back"];
|
|
11
|
+
/** True once the deployment has stopped progressing (mirrors DeploymentStatus::isTerminal). */
|
|
12
|
+
export declare function isTerminal(status: DeploymentStatus): boolean;
|
|
13
|
+
/** True only when the deployment finished successfully. */
|
|
14
|
+
export declare function isSuccess(status: DeploymentStatus): boolean;
|
|
15
|
+
export interface Release {
|
|
16
|
+
id: number;
|
|
17
|
+
project_id: number;
|
|
18
|
+
artifact_id: number | null;
|
|
19
|
+
version: string;
|
|
20
|
+
path: string;
|
|
21
|
+
state: string;
|
|
22
|
+
created_at?: string | null;
|
|
23
|
+
updated_at?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** One pipeline step as recorded on the deployment (shape is engine-defined; kept loose). */
|
|
26
|
+
export interface DeploymentStep {
|
|
27
|
+
name?: string;
|
|
28
|
+
status?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface Deployment {
|
|
32
|
+
id: number;
|
|
33
|
+
project_id: number;
|
|
34
|
+
release_id: number | null;
|
|
35
|
+
trigger: DeploymentTrigger;
|
|
36
|
+
status: DeploymentStatus;
|
|
37
|
+
steps: DeploymentStep[] | null;
|
|
38
|
+
actor: string | null;
|
|
39
|
+
idempotency_key: string | null;
|
|
40
|
+
started_at: string | null;
|
|
41
|
+
finished_at: string | null;
|
|
42
|
+
created_at?: string | null;
|
|
43
|
+
updated_at?: string | null;
|
|
44
|
+
/** Loaded relation (the API returns `->load('release')`). */
|
|
45
|
+
release?: Release | null;
|
|
46
|
+
}
|
|
47
|
+
/** Response of `GET /whoami` — lets a client verify its token before deploying. */
|
|
48
|
+
export interface WhoAmI {
|
|
49
|
+
name: string;
|
|
50
|
+
scopes: string[];
|
|
51
|
+
project_id: number | null;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GACxB,QAAQ,GACR,SAAS,GACT,eAAe,GACf,SAAS,GACT,QAAQ,GACR,aAAa,CAAC;AAElB,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7D,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,+CAAgD,CAAC;AAE/E,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAE5D;AAED,2DAA2D;AAC3D,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAE3D;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,mFAAmF;AACnF,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types mirroring the cPorter API contract (apps/api, docs/SPEC.md §5–§7).
|
|
3
|
+
* The API wraps every payload in `{ "data": ... }`; these describe the unwrapped `data`.
|
|
4
|
+
*/
|
|
5
|
+
/** Terminal statuses — polling stops once a deployment reaches one of these. */
|
|
6
|
+
export const TERMINAL_STATUSES = ['success', 'failed', 'rolled_back'];
|
|
7
|
+
/** True once the deployment has stopped progressing (mirrors DeploymentStatus::isTerminal). */
|
|
8
|
+
export function isTerminal(status) {
|
|
9
|
+
return TERMINAL_STATUSES.includes(status);
|
|
10
|
+
}
|
|
11
|
+
/** True only when the deployment finished successfully. */
|
|
12
|
+
export function isSuccess(status) {
|
|
13
|
+
return status === 'success';
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,gFAAgF;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAU,CAAC;AAE/E,+FAA+F;AAC/F,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,SAAS,CAAC,MAAwB;IAChD,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cporter/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for the cPorter deploy API — the shared core behind the CLI, GitHub Action, and MCP server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ngocmy-spaces/cporter.git",
|
|
10
|
+
"directory": "packages/sdk"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.10.0",
|
|
31
|
+
"typescript": "^5.7.2"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
36
|
+
}
|
|
37
|
+
}
|