@zalosai/zalos-cli 0.2.13
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/PUBLISHING.md +97 -0
- package/README.md +231 -0
- package/dist/client.js +79 -0
- package/dist/commands/api-token.js +74 -0
- package/dist/commands/auth.js +196 -0
- package/dist/commands/credential.js +15 -0
- package/dist/commands/cycle.js +47 -0
- package/dist/commands/doctor.js +106 -0
- package/dist/commands/execution.js +117 -0
- package/dist/commands/file.js +257 -0
- package/dist/commands/label.js +30 -0
- package/dist/commands/member.js +23 -0
- package/dist/commands/page-markdown.js +213 -0
- package/dist/commands/page.js +178 -0
- package/dist/commands/project.js +97 -0
- package/dist/commands/schema.js +178 -0
- package/dist/commands/setup.js +34 -0
- package/dist/commands/state.js +30 -0
- package/dist/commands/task-comment.js +75 -0
- package/dist/commands/task.js +245 -0
- package/dist/commands/team.js +88 -0
- package/dist/commands/template.js +21 -0
- package/dist/commands/workflow.js +245 -0
- package/dist/credentials.js +123 -0
- package/dist/environments.js +42 -0
- package/dist/main.js +86 -0
- package/dist/utils/cli-auth.js +186 -0
- package/dist/utils/developer-db.js +78 -0
- package/dist/utils/download-file.js +65 -0
- package/dist/utils/errors.js +60 -0
- package/dist/utils/output.js +30 -0
- package/dist/utils/product-api.js +48 -0
- package/dist/utils/stdin.js +23 -0
- package/dist/utils/validate.js +39 -0
- package/dist/workflow-file.js +34 -0
- package/package.json +53 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export const DATABASE_INSTALL = 'npm install --global @zalos-io/developer-db@0.3.2';
|
|
3
|
+
const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
4
|
+
export class DatabaseToolError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(code === 'NOT_INSTALLED'
|
|
8
|
+
? `zalos-db is missing. Authenticate to GitHub Packages, then run: ${DATABASE_INSTALL}`
|
|
9
|
+
: 'Database access check failed. Run zalos-db login and doctor with the selected environment.');
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** The child owns AWS and database credentials; never echo its arbitrary output. */
|
|
14
|
+
export function runDatabaseTool(args) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const env = { ...process.env };
|
|
17
|
+
delete env.ZL_API_TOKEN;
|
|
18
|
+
const child = spawn('zalos-db', args, { shell: false, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
19
|
+
let stdout = '';
|
|
20
|
+
let stderr = '';
|
|
21
|
+
let bytes = 0;
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
child.kill('SIGTERM');
|
|
24
|
+
reject(new DatabaseToolError('TIMEOUT'));
|
|
25
|
+
}, 60_000);
|
|
26
|
+
const capture = (chunk, retain) => {
|
|
27
|
+
bytes += chunk.byteLength;
|
|
28
|
+
if (bytes > MAX_OUTPUT_BYTES) {
|
|
29
|
+
child.kill('SIGTERM');
|
|
30
|
+
reject(new DatabaseToolError('OUTPUT_LIMIT'));
|
|
31
|
+
}
|
|
32
|
+
else if (retain)
|
|
33
|
+
stdout += chunk.toString('utf8');
|
|
34
|
+
else
|
|
35
|
+
stderr += chunk.toString('utf8');
|
|
36
|
+
};
|
|
37
|
+
child.stdout.on('data', (chunk) => capture(chunk, true));
|
|
38
|
+
child.stderr.on('data', (chunk) => capture(chunk, false));
|
|
39
|
+
child.on('error', (error) => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
reject(new DatabaseToolError(error.code === 'ENOENT' ? 'NOT_INSTALLED' : 'CHECK_FAILED'));
|
|
42
|
+
});
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
if (code !== 0) {
|
|
46
|
+
let failure = 'CHECK_FAILED';
|
|
47
|
+
try {
|
|
48
|
+
const payload = JSON.parse(stderr);
|
|
49
|
+
const known = [
|
|
50
|
+
'aws_session',
|
|
51
|
+
'aws_identity',
|
|
52
|
+
'sso_login',
|
|
53
|
+
'database_connect',
|
|
54
|
+
'database_identity',
|
|
55
|
+
'database_secret',
|
|
56
|
+
'tunnel_start',
|
|
57
|
+
'tunnel_readiness',
|
|
58
|
+
'ca_bundle',
|
|
59
|
+
];
|
|
60
|
+
if (typeof payload.code === 'string' && known.includes(payload.code))
|
|
61
|
+
failure = payload.code;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* No arbitrary child output escapes this boundary. */
|
|
65
|
+
}
|
|
66
|
+
reject(new DatabaseToolError(failure));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
resolve(JSON.parse(stdout));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
reject(new DatabaseToolError('INVALID_RESPONSE'));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=developer-db.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { open, unlink } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Readable, Transform } from 'node:stream';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import { ApiError, ValidationError } from './errors.js';
|
|
6
|
+
function validateDownloadUrl(value, options) {
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = new URL(value);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new ApiError(502, 'The file API returned an invalid download URL');
|
|
13
|
+
}
|
|
14
|
+
const protocolAllowed = url.protocol === 'https:' ||
|
|
15
|
+
url.protocol === 'http:' ||
|
|
16
|
+
(options.allowDataUrl === true && url.protocol === 'data:');
|
|
17
|
+
if (!protocolAllowed) {
|
|
18
|
+
throw new ApiError(502, 'The file API returned an unsupported download URL');
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
}
|
|
22
|
+
export async function downloadToNewFile(urlValue, output, options = {}) {
|
|
23
|
+
if (!output.trim())
|
|
24
|
+
throw new ValidationError('output', '--output must not be empty');
|
|
25
|
+
const url = validateDownloadUrl(urlValue, options);
|
|
26
|
+
const destination = resolve(output);
|
|
27
|
+
let handle;
|
|
28
|
+
try {
|
|
29
|
+
handle = await open(destination, 'wx', 0o600);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code === 'EEXIST') {
|
|
33
|
+
throw new ValidationError('output', `Output already exists: ${destination}`);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
let bytes = 0;
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(url, {
|
|
40
|
+
redirect: 'error',
|
|
41
|
+
signal: AbortSignal.timeout(10 * 60_000),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new ApiError(response.status, `File download failed with HTTP ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
if (!response.body) {
|
|
47
|
+
await handle.close();
|
|
48
|
+
return { output: destination, bytes: 0 };
|
|
49
|
+
}
|
|
50
|
+
const counter = new Transform({
|
|
51
|
+
transform(chunk, _encoding, callback) {
|
|
52
|
+
bytes += chunk.length;
|
|
53
|
+
callback(null, chunk);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
await pipeline(Readable.from(response.body), counter, handle.createWriteStream());
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
await handle.close().catch(() => undefined);
|
|
60
|
+
await unlink(destination).catch(() => undefined);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
return { output: destination, bytes };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=download-file.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export class AuthError extends Error {
|
|
2
|
+
code = 'AUTH_ERROR';
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'AuthError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class ApiError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code = 'API_ERROR';
|
|
11
|
+
constructor(status, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = 'ApiError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class ValidationError extends Error {
|
|
18
|
+
field;
|
|
19
|
+
code = 'VALIDATION_ERROR';
|
|
20
|
+
constructor(field, message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.field = field;
|
|
23
|
+
this.name = 'ValidationError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class NotFoundError extends Error {
|
|
27
|
+
resource;
|
|
28
|
+
id;
|
|
29
|
+
code = 'NOT_FOUND';
|
|
30
|
+
constructor(resource, id) {
|
|
31
|
+
super(`${resource} not found: ${id}`);
|
|
32
|
+
this.resource = resource;
|
|
33
|
+
this.id = id;
|
|
34
|
+
this.name = 'NotFoundError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function handleError(error) {
|
|
38
|
+
let payload;
|
|
39
|
+
if (error instanceof AuthError) {
|
|
40
|
+
payload = { error: error.message, code: error.code };
|
|
41
|
+
}
|
|
42
|
+
else if (error instanceof NotFoundError) {
|
|
43
|
+
payload = { error: error.message, code: error.code, resource: error.resource, id: error.id };
|
|
44
|
+
}
|
|
45
|
+
else if (error instanceof ApiError) {
|
|
46
|
+
payload = { error: error.message, code: error.code, status: error.status };
|
|
47
|
+
}
|
|
48
|
+
else if (error instanceof ValidationError) {
|
|
49
|
+
payload = { error: error.message, code: error.code, field: error.field };
|
|
50
|
+
}
|
|
51
|
+
else if (error instanceof Error) {
|
|
52
|
+
payload = { error: error.message, code: 'UNKNOWN_ERROR' };
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
payload = { error: 'Unknown error', code: 'UNKNOWN_ERROR' };
|
|
56
|
+
}
|
|
57
|
+
process.stderr.write(JSON.stringify(payload) + '\n');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function printJson(data) {
|
|
2
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
3
|
+
}
|
|
4
|
+
export function filterFields(items, fields) {
|
|
5
|
+
if (!fields)
|
|
6
|
+
return items;
|
|
7
|
+
const allowed = new Set(fields.split(',').map((f) => f.trim()));
|
|
8
|
+
return items.map((item) => {
|
|
9
|
+
const filtered = {};
|
|
10
|
+
for (const key of allowed) {
|
|
11
|
+
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
|
12
|
+
filtered[key] = item[key];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return filtered;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function filterSingle(item, fields) {
|
|
19
|
+
if (!fields)
|
|
20
|
+
return item;
|
|
21
|
+
return filterFields([item], fields)[0];
|
|
22
|
+
}
|
|
23
|
+
export function printList(items, fields, nextCursor) {
|
|
24
|
+
const data = filterFields(items, fields);
|
|
25
|
+
const result = { data };
|
|
26
|
+
if (nextCursor !== undefined)
|
|
27
|
+
result['nextCursor'] = nextCursor;
|
|
28
|
+
printJson(result);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ApiError, handleError, ValidationError } from './errors.js';
|
|
2
|
+
import { filterFields, printJson } from './output.js';
|
|
3
|
+
export function isRow(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function dataRow(value) {
|
|
7
|
+
if (!isRow(value) || !isRow(value.data))
|
|
8
|
+
throw new ApiError(502, 'Expected API response with object data');
|
|
9
|
+
return value.data;
|
|
10
|
+
}
|
|
11
|
+
export function dataRows(value) {
|
|
12
|
+
if (!isRow(value) || !Array.isArray(value.data) || !value.data.every(isRow))
|
|
13
|
+
throw new ApiError(502, 'Expected API response with array data');
|
|
14
|
+
return value.data;
|
|
15
|
+
}
|
|
16
|
+
export function integer(value, fallback, minimum = 0, maximum = 10000) {
|
|
17
|
+
const raw = value ?? String(fallback);
|
|
18
|
+
const result = Number(raw);
|
|
19
|
+
if (!/^\d+$/.test(raw) || !Number.isSafeInteger(result) || result < minimum || result > maximum)
|
|
20
|
+
throw new ValidationError('pagination', `Expected integer between ${minimum} and ${maximum}`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
export function printPage(rows, options) {
|
|
24
|
+
const offset = integer(options.offset, 0);
|
|
25
|
+
const limit = integer(options.limit, 100, 1, 100);
|
|
26
|
+
printJson({
|
|
27
|
+
data: filterFields(rows.slice(offset, offset + limit), options.fields),
|
|
28
|
+
count: rows.length,
|
|
29
|
+
...(offset + limit < rows.length ? { nextOffset: offset + limit } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function resourceId(id) {
|
|
33
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id))
|
|
34
|
+
throw new ValidationError('id', 'Invalid resource ID');
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
export function fields(program) {
|
|
38
|
+
return program.optsWithGlobals();
|
|
39
|
+
}
|
|
40
|
+
export async function action(run) {
|
|
41
|
+
try {
|
|
42
|
+
await run();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
handleError(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=product-api.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function readStdin() {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
let data = '';
|
|
4
|
+
const onData = (chunk) => {
|
|
5
|
+
data += chunk;
|
|
6
|
+
};
|
|
7
|
+
const onEnd = () => {
|
|
8
|
+
process.stdin.off('data', onData);
|
|
9
|
+
process.stdin.off('error', onError);
|
|
10
|
+
resolve(data);
|
|
11
|
+
};
|
|
12
|
+
const onError = (err) => {
|
|
13
|
+
process.stdin.off('data', onData);
|
|
14
|
+
process.stdin.off('end', onEnd);
|
|
15
|
+
reject(err);
|
|
16
|
+
};
|
|
17
|
+
process.stdin.setEncoding('utf8');
|
|
18
|
+
process.stdin.on('data', onData);
|
|
19
|
+
process.stdin.once('end', onEnd);
|
|
20
|
+
process.stdin.once('error', onError);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=stdin.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ValidationError } from './errors.js';
|
|
2
|
+
const DANGEROUS_CHARS_RE = /[?#%]/;
|
|
3
|
+
const PATH_TRAVERSAL_RE = /(^|[\\/])\.\.($|[\\/])/;
|
|
4
|
+
function hasControlChars(value, allowWhitespace = false) {
|
|
5
|
+
for (let i = 0; i < value.length; i++) {
|
|
6
|
+
const code = value.charCodeAt(i);
|
|
7
|
+
if (code < 0x20) {
|
|
8
|
+
if (allowWhitespace && (code === 0x09 || code === 0x0a || code === 0x0d))
|
|
9
|
+
continue;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export function validateId(id, label = 'ID') {
|
|
16
|
+
if (!id || DANGEROUS_CHARS_RE.test(id) || hasControlChars(id) || PATH_TRAVERSAL_RE.test(id)) {
|
|
17
|
+
throw new ValidationError(label, `Invalid ${label}: contains forbidden characters`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function validateString(value, label, maxLength = 10_000) {
|
|
21
|
+
if (hasControlChars(value, true)) {
|
|
22
|
+
throw new ValidationError(label, `${label} contains control characters`);
|
|
23
|
+
}
|
|
24
|
+
if (PATH_TRAVERSAL_RE.test(value)) {
|
|
25
|
+
throw new ValidationError(label, `${label} contains path traversal`);
|
|
26
|
+
}
|
|
27
|
+
if (value.length > maxLength) {
|
|
28
|
+
throw new ValidationError(label, `${label} exceeds max length of ${maxLength}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function parseJson(raw, label = '--json') {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new ValidationError(label, `${label} is not valid JSON`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { ValidationError } from './utils/errors.js';
|
|
3
|
+
import { isRow } from './utils/product-api.js';
|
|
4
|
+
export function parseWorkflowFile(raw, expectedId) {
|
|
5
|
+
let value;
|
|
6
|
+
try {
|
|
7
|
+
value = JSON.parse(raw);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new ValidationError('input', 'Workflow file is not valid JSON');
|
|
11
|
+
}
|
|
12
|
+
if (!isRow(value) ||
|
|
13
|
+
!Array.isArray(value.nodes) ||
|
|
14
|
+
!value.nodes.every(isRow) ||
|
|
15
|
+
!isRow(value.connections))
|
|
16
|
+
throw new ValidationError('input', 'Workflow requires nodes array and connections object');
|
|
17
|
+
if (expectedId && value.id !== undefined && value.id !== expectedId)
|
|
18
|
+
throw new ValidationError('id', 'Workflow file ID does not match requested ID');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export async function readWorkflowFile(path, expectedId) {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = await readFile(path, 'utf8');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new ValidationError('input', 'Cannot read workflow file');
|
|
28
|
+
}
|
|
29
|
+
return parseWorkflowFile(raw, expectedId);
|
|
30
|
+
}
|
|
31
|
+
export function serializeWorkflow(value, pretty = false) {
|
|
32
|
+
return JSON.stringify(value, null, pretty ? 2 : undefined) + '\n';
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=workflow-file.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zalosai/zalos-cli",
|
|
3
|
+
"version": "0.2.13",
|
|
4
|
+
"description": "API-only product command-line interface for Zalos",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Zalos-io/mesh.git",
|
|
8
|
+
"directory": "packages/zalos-cli"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "dist/main.js",
|
|
12
|
+
"bin": {
|
|
13
|
+
"zl": "dist/main.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/**/*.js",
|
|
17
|
+
"README.md",
|
|
18
|
+
"PUBLISHING.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@tiptap/core": "3.27.1",
|
|
25
|
+
"@tiptap/extension-heading": "3.27.1",
|
|
26
|
+
"@tiptap/extension-image": "3.27.1",
|
|
27
|
+
"@tiptap/extension-list": "3.27.1",
|
|
28
|
+
"@tiptap/extension-paragraph": "3.27.1",
|
|
29
|
+
"@tiptap/markdown": "3.27.1",
|
|
30
|
+
"@tiptap/pm": "3.27.1",
|
|
31
|
+
"@tiptap/static-renderer": "3.27.1",
|
|
32
|
+
"@tiptap/starter-kit": "3.27.1",
|
|
33
|
+
"commander": "^13.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"tsx": "^4.0.0",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"vitest": "^2.1.8"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc --build",
|
|
47
|
+
"clean": "tsc --build --clean && rm -rf dist",
|
|
48
|
+
"dev": "tsx src/main.ts",
|
|
49
|
+
"smoke:package": "node scripts/smoke-package.mjs",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"typecheck": "tsgo --noEmit --types node --incremental --tsBuildInfoFile dist/tsconfig.typecheck.tsbuildinfo"
|
|
52
|
+
}
|
|
53
|
+
}
|