@selfchecks/selfchecks-cli 0.1.1

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.
@@ -0,0 +1,48 @@
1
+ export type DeploySummary = {
2
+ checks: Array<{
3
+ enabled: boolean;
4
+ entrypoint?: string;
5
+ frequency?: {
6
+ intervalMinutes: number;
7
+ };
8
+ groupKey?: string;
9
+ groupName?: string;
10
+ key: string;
11
+ name: string;
12
+ request?: {
13
+ assertions: Array<{
14
+ operator: string;
15
+ source: string;
16
+ target?: unknown;
17
+ }>;
18
+ body?: string;
19
+ headers: Record<string, string>;
20
+ method: string;
21
+ url: string;
22
+ };
23
+ retryStrategy?: {
24
+ baseBackoffSeconds?: number;
25
+ maxDurationSeconds?: number;
26
+ maxRetries?: number;
27
+ onlyOn?: string[];
28
+ sameRegion?: boolean;
29
+ type: "EXPONENTIAL" | "FIXED" | "LINEAR" | "NO_RETRIES";
30
+ };
31
+ tags: string[];
32
+ type: "api" | "browser";
33
+ }>;
34
+ created: number;
35
+ projectSlug: string;
36
+ removed: number;
37
+ updated: number;
38
+ warnings: string[];
39
+ };
40
+ export type RemoteDeployOptions = {
41
+ allowRemovals: boolean;
42
+ apiToken: string;
43
+ apiUrl: string;
44
+ projectSlug: string;
45
+ rootDir: string;
46
+ };
47
+ export declare function runRemoteDeploy(options: RemoteDeployOptions): Promise<DeploySummary>;
48
+ //# sourceMappingURL=remote-deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-deploy.d.ts","sourceRoot":"","sources":["../../../../cli/src/remote-deploy.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,KAAK,CAAC;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE;YAAE,eAAe,EAAE,MAAM,CAAA;SAAE,CAAC;QACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE;YACR,UAAU,EAAE,KAAK,CAAC;gBAAE,QAAQ,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE,OAAO,CAAA;aAAE,CAAC,CAAC;YAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAChC,MAAM,EAAE,MAAM,CAAC;YACf,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;QACF,aAAa,CAAC,EAAE;YACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;YAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;YAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;YAClB,UAAU,CAAC,EAAE,OAAO,CAAC;YACrB,IAAI,EAAE,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC;SACzD,CAAC;QACF,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;KACzB,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAgBF,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAiBxB"}
@@ -0,0 +1,34 @@
1
+ import { createAuthorizationHeaders, createRemoteBundleFormData, fetchRemoteStatus, normalizeApiUrl, readApiError, readJsonResponse, } from "./remote-test-session.js";
2
+ const POLL_INTERVAL_MS = 2_000;
3
+ const POLL_TIMEOUT_MS = 60 * 60_000;
4
+ export async function runRemoteDeploy(options) {
5
+ const apiUrl = normalizeApiUrl(options.apiUrl);
6
+ const response = await fetch(`${apiUrl}/api/cli/deployments`, {
7
+ body: await createRemoteBundleFormData(options.rootDir, {
8
+ allowRemovals: options.allowRemovals,
9
+ projectSlug: options.projectSlug,
10
+ }),
11
+ headers: createAuthorizationHeaders(options.apiToken),
12
+ method: "POST",
13
+ });
14
+ const deployment = await readJsonResponse(response);
15
+ if (!response.ok) {
16
+ throw new Error(readApiError(deployment, "Unable to queue remote deployment."));
17
+ }
18
+ return pollDeployment(apiUrl, options.apiToken, deployment);
19
+ }
20
+ async function pollDeployment(apiUrl, apiToken, deployment) {
21
+ const startedAt = Date.now();
22
+ const statusUrl = new URL(deployment.statusUrl, `${apiUrl}/`).toString();
23
+ while (Date.now() - startedAt < POLL_TIMEOUT_MS) {
24
+ const status = await fetchRemoteStatus(statusUrl, apiToken, "Unable to read remote deployment.");
25
+ if (status.status === "completed" && status.summary) {
26
+ return status.summary;
27
+ }
28
+ if (status.status === "failed") {
29
+ throw new Error(status.error || `Deployment ${deployment.deploymentId} failed.`);
30
+ }
31
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
32
+ }
33
+ throw new Error(`Timed out waiting for deployment ${deployment.deploymentId}.`);
34
+ }
@@ -0,0 +1,57 @@
1
+ export type EnvVar = {
2
+ name: string;
3
+ value: string;
4
+ };
5
+ export type RemoteCheckType = "api" | "browser";
6
+ export type RunChecksSummary = {
7
+ durationMs: number;
8
+ failed: number;
9
+ passed: number;
10
+ results: Array<{
11
+ checkKey: string;
12
+ checkName: string;
13
+ durationMs: number;
14
+ errorMessage?: string;
15
+ runId?: string;
16
+ status: "cancelled" | "failed" | "passed" | "queued" | "running" | "timed_out";
17
+ }>;
18
+ sessionId?: string;
19
+ skipped: number;
20
+ total: number;
21
+ };
22
+ export type RemoteTestSessionOptions = {
23
+ apiToken: string;
24
+ apiUrl: string;
25
+ checkKeys: string[];
26
+ checkTypes: RemoteCheckType[];
27
+ commitSha?: string;
28
+ env: EnvVar[];
29
+ jobUrl?: string;
30
+ pipelineUrl?: string;
31
+ projectSlug: string;
32
+ ref?: string;
33
+ reporter: string;
34
+ repository?: string;
35
+ retries?: number;
36
+ rootDir: string;
37
+ source?: string;
38
+ tagSets: string[][];
39
+ testSessionName?: string;
40
+ };
41
+ type BundleFile = {
42
+ content: Uint8Array;
43
+ path: string;
44
+ };
45
+ export declare function runRemoteTestSession(options: RemoteTestSessionOptions): Promise<RunChecksSummary>;
46
+ export declare function cancelRemoteTestSession(apiUrlValue: string, apiToken: string, sessionId: string): Promise<void>;
47
+ export declare function createRemoteBundleFormData(rootDir: string, metadata: Record<string, unknown>): Promise<FormData>;
48
+ export declare function collectBundleFiles(rootDir: string): Promise<BundleFile[]>;
49
+ export declare function createAuthorizationHeaders(apiToken: string): {
50
+ Authorization: string;
51
+ };
52
+ export declare function fetchRemoteStatus<T>(statusUrl: string, apiToken: string, fallbackError: string): Promise<T>;
53
+ export declare function normalizeApiUrl(value: string): string;
54
+ export declare function readJsonResponse<T>(response: Response): Promise<T>;
55
+ export declare function readApiError(value: unknown, fallback: string): string;
56
+ export {};
57
+ //# sourceMappingURL=remote-test-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-test-session.d.ts","sourceRoot":"","sources":["../../../../cli/src/remote-test-session.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,SAAS,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;KAChF,CAAC,CAAC;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAgCF,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAyC3B;AAED,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAuCD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,QAAQ,CAAC,CAsBnB;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA+C/E;AAiED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;;EAI1D;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CA0DZ;AAYD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQrD;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAcxE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWrE"}
@@ -0,0 +1,260 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const MAX_BUNDLE_BYTES = 40 * 1024 * 1024;
4
+ const MAX_BUNDLE_FILES = 10_000;
5
+ const POLL_INTERVAL_MS = 2_000;
6
+ const POLL_TIMEOUT_MS = 6 * 60 * 60_000;
7
+ const REMOTE_STATUS_MAX_ATTEMPTS = 5;
8
+ const REMOTE_STATUS_RETRY_BASE_DELAY_MS = 1_000;
9
+ const TERMINAL_STATUSES = new Set(["cancelled", "failed", "passed", "timed_out"]);
10
+ const IGNORED_DIRECTORIES = new Set([
11
+ ".git",
12
+ ".npm-cache",
13
+ ".selfchecks",
14
+ ".turbo",
15
+ "allure-results",
16
+ "node_modules",
17
+ "playwright-report",
18
+ "test-results",
19
+ ]);
20
+ const IGNORED_FILES = new Set(["checkly-github-report.md"]);
21
+ export async function runRemoteTestSession(options) {
22
+ const formData = await createRemoteBundleFormData(options.rootDir, {
23
+ checkKeys: options.checkKeys,
24
+ checkTypes: options.checkTypes,
25
+ commitSha: options.commitSha,
26
+ env: options.env,
27
+ jobUrl: options.jobUrl,
28
+ pipelineUrl: options.pipelineUrl,
29
+ projectSlug: options.projectSlug,
30
+ ref: options.ref,
31
+ reporter: options.reporter,
32
+ repository: options.repository,
33
+ retries: options.retries,
34
+ source: options.source,
35
+ tagSets: options.tagSets,
36
+ testSessionName: options.testSessionName,
37
+ });
38
+ const apiUrl = normalizeApiUrl(options.apiUrl);
39
+ const response = await fetch(`${apiUrl}/api/cli/test-sessions`, {
40
+ body: formData,
41
+ headers: createAuthorizationHeaders(options.apiToken),
42
+ method: "POST",
43
+ });
44
+ const session = await readJsonResponse(response);
45
+ if (!response.ok) {
46
+ throw new Error(readApiError(session, "Unable to create remote test session."));
47
+ }
48
+ const unregisterSignalHandlers = registerCancellationSignalHandlers(apiUrl, options.apiToken, session.sessionId);
49
+ try {
50
+ return await pollTestSession(apiUrl, options.apiToken, session);
51
+ }
52
+ finally {
53
+ unregisterSignalHandlers();
54
+ }
55
+ }
56
+ export async function cancelRemoteTestSession(apiUrlValue, apiToken, sessionId) {
57
+ const apiUrl = normalizeApiUrl(apiUrlValue);
58
+ const response = await fetch(`${apiUrl}/api/cli/test-sessions/${encodeURIComponent(sessionId)}`, {
59
+ headers: createAuthorizationHeaders(apiToken),
60
+ method: "DELETE",
61
+ signal: AbortSignal.timeout(10_000),
62
+ });
63
+ if (!response.ok) {
64
+ const body = await readJsonResponse(response);
65
+ throw new Error(readApiError(body, "Unable to cancel remote test session."));
66
+ }
67
+ }
68
+ function registerCancellationSignalHandlers(apiUrl, apiToken, sessionId) {
69
+ let cancelling = false;
70
+ const handlers = new Map();
71
+ const unregister = () => {
72
+ handlers.forEach((handler, signal) => process.off(signal, handler));
73
+ };
74
+ for (const signal of ["SIGINT", "SIGTERM"]) {
75
+ const handler = () => {
76
+ if (cancelling) {
77
+ return;
78
+ }
79
+ cancelling = true;
80
+ void cancelRemoteTestSession(apiUrl, apiToken, sessionId)
81
+ .catch((error) => {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ process.stderr.write(`${message}\n`);
84
+ })
85
+ .finally(() => {
86
+ unregister();
87
+ process.kill(process.pid, signal);
88
+ });
89
+ };
90
+ handlers.set(signal, handler);
91
+ process.once(signal, handler);
92
+ }
93
+ return unregister;
94
+ }
95
+ export async function createRemoteBundleFormData(rootDir, metadata) {
96
+ const files = await collectBundleFiles(rootDir);
97
+ const formData = new FormData();
98
+ formData.set("metadata", JSON.stringify(metadata));
99
+ formData.set("manifest", JSON.stringify(files.map((file) => ({ path: file.path, size: file.content.length }))));
100
+ files.forEach((file, index) => {
101
+ const content = file.content.buffer.slice(file.content.byteOffset, file.content.byteOffset + file.content.byteLength);
102
+ formData.set(`file-${index}`, new Blob([content]), path.posix.basename(file.path));
103
+ });
104
+ return formData;
105
+ }
106
+ export async function collectBundleFiles(rootDir) {
107
+ const files = [];
108
+ let totalBytes = 0;
109
+ async function walk(directory) {
110
+ const entries = await readdir(directory, { withFileTypes: true });
111
+ for (const entry of entries) {
112
+ if (entry.isDirectory()) {
113
+ if (!IGNORED_DIRECTORIES.has(entry.name)) {
114
+ await walk(path.join(directory, entry.name));
115
+ }
116
+ continue;
117
+ }
118
+ if (!entry.isFile() || isIgnoredFile(entry.name)) {
119
+ continue;
120
+ }
121
+ if (files.length >= MAX_BUNDLE_FILES) {
122
+ throw new Error(`Selfchecks test bundle exceeds ${MAX_BUNDLE_FILES} files.`);
123
+ }
124
+ const filePath = path.join(directory, entry.name);
125
+ const relativePath = path.relative(rootDir, filePath).split(path.sep).join("/");
126
+ const content = transformRuntimeFile(relativePath, await readFile(filePath));
127
+ totalBytes += content.length;
128
+ if (totalBytes > MAX_BUNDLE_BYTES) {
129
+ throw new Error("Selfchecks test bundle exceeds 40 MB.");
130
+ }
131
+ files.push({
132
+ content,
133
+ path: relativePath,
134
+ });
135
+ }
136
+ }
137
+ await walk(path.resolve(rootDir));
138
+ if (files.length === 0) {
139
+ throw new Error(`No files found in ${rootDir}.`);
140
+ }
141
+ return files.sort((left, right) => left.path.localeCompare(right.path));
142
+ }
143
+ function isIgnoredFile(fileName) {
144
+ return (fileName === ".env" || fileName.startsWith(".env.") || IGNORED_FILES.has(fileName));
145
+ }
146
+ async function pollTestSession(apiUrl, apiToken, session) {
147
+ const startedAt = Date.now();
148
+ const statusUrl = new URL(session.statusUrl, `${apiUrl}/`).toString();
149
+ while (Date.now() - startedAt < POLL_TIMEOUT_MS) {
150
+ const status = await fetchRemoteStatus(statusUrl, apiToken, "Unable to read remote test session.");
151
+ if (TERMINAL_STATUSES.has(status.status)) {
152
+ if (!status.summary) {
153
+ throw new Error(status.error || `Test session ${session.sessionId} failed.`);
154
+ }
155
+ return status.summary;
156
+ }
157
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
158
+ }
159
+ throw new Error(`Timed out waiting for test session ${session.sessionId}.`);
160
+ }
161
+ function transformRuntimeFile(relativePath, content) {
162
+ if (relativePath === "package.json") {
163
+ const packageJson = JSON.parse(content.toString("utf8"));
164
+ return Buffer.from(`${JSON.stringify({
165
+ dependencies: packageJson.dependencies ?? {},
166
+ name: packageJson.name ?? "@selfchecks/test-session",
167
+ private: true,
168
+ version: packageJson.version ?? "0.0.0",
169
+ }, null, 2)}\n`);
170
+ }
171
+ if (relativePath === "tsconfig.json") {
172
+ const tsconfig = JSON.parse(content.toString("utf8"));
173
+ delete tsconfig.extends;
174
+ return Buffer.from(`${JSON.stringify(tsconfig, null, 2)}\n`);
175
+ }
176
+ return content;
177
+ }
178
+ export function createAuthorizationHeaders(apiToken) {
179
+ return {
180
+ Authorization: `Bearer ${apiToken}`,
181
+ };
182
+ }
183
+ export async function fetchRemoteStatus(statusUrl, apiToken, fallbackError) {
184
+ let lastError = new Error(fallbackError);
185
+ for (let attempt = 0; attempt < REMOTE_STATUS_MAX_ATTEMPTS; attempt += 1) {
186
+ let response;
187
+ try {
188
+ response = await fetch(statusUrl, {
189
+ headers: createAuthorizationHeaders(apiToken),
190
+ });
191
+ }
192
+ catch (error) {
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ lastError = new Error(`${fallbackError} ${message}`);
195
+ if (attempt === REMOTE_STATUS_MAX_ATTEMPTS - 1) {
196
+ throw lastError;
197
+ }
198
+ await waitForRemoteStatusRetry(attempt);
199
+ continue;
200
+ }
201
+ let body;
202
+ try {
203
+ body = await readJsonResponse(response);
204
+ }
205
+ catch (error) {
206
+ if (!isRetryableRemoteStatus(response.status) ||
207
+ attempt === REMOTE_STATUS_MAX_ATTEMPTS - 1) {
208
+ throw error;
209
+ }
210
+ lastError = error instanceof Error ? error : new Error(String(error));
211
+ await waitForRemoteStatusRetry(attempt);
212
+ continue;
213
+ }
214
+ if (response.ok) {
215
+ return body;
216
+ }
217
+ lastError = new Error(readApiError(body, `${fallbackError} (HTTP ${response.status}).`));
218
+ if (!isRetryableRemoteStatus(response.status) ||
219
+ attempt === REMOTE_STATUS_MAX_ATTEMPTS - 1) {
220
+ throw lastError;
221
+ }
222
+ await waitForRemoteStatusRetry(attempt);
223
+ }
224
+ throw lastError;
225
+ }
226
+ function isRetryableRemoteStatus(status) {
227
+ return status === 408 || status === 425 || status === 429 || status >= 500;
228
+ }
229
+ async function waitForRemoteStatusRetry(attempt) {
230
+ const delayMs = REMOTE_STATUS_RETRY_BASE_DELAY_MS * 2 ** attempt;
231
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
232
+ }
233
+ export function normalizeApiUrl(value) {
234
+ const url = new URL(value);
235
+ if (!/^https?:$/.test(url.protocol)) {
236
+ throw new Error("SELFCHECKS_URL must use http or https.");
237
+ }
238
+ return url.toString().replace(/\/$/, "");
239
+ }
240
+ export async function readJsonResponse(response) {
241
+ const body = await response.text();
242
+ if (!body) {
243
+ return {};
244
+ }
245
+ try {
246
+ return JSON.parse(body);
247
+ }
248
+ catch {
249
+ throw new Error(`Selfchecks API returned an invalid response (${response.status}).`);
250
+ }
251
+ }
252
+ export function readApiError(value, fallback) {
253
+ if (value &&
254
+ typeof value === "object" &&
255
+ "error" in value &&
256
+ typeof value.error === "string") {
257
+ return value.error;
258
+ }
259
+ return fallback;
260
+ }
@@ -0,0 +1,20 @@
1
+ import { type RunChecksSummary } from "./remote-test-session.js";
2
+ export type RemoteTriggerOptions = {
3
+ apiToken: string;
4
+ apiUrl: string;
5
+ commitSha?: string;
6
+ env: Array<{
7
+ name: string;
8
+ value: string;
9
+ }>;
10
+ jobUrl?: string;
11
+ pipelineUrl?: string;
12
+ projectSlug: string;
13
+ ref?: string;
14
+ reporter: string;
15
+ repository?: string;
16
+ retries?: number;
17
+ testSessionName?: string;
18
+ };
19
+ export declare function runRemoteTrigger(options: RemoteTriggerOptions): Promise<RunChecksSummary>;
20
+ //# sourceMappingURL=remote-trigger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-trigger.d.ts","sourceRoot":"","sources":["../../../../cli/src/remote-trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAgBF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CA4B3B"}
@@ -0,0 +1,45 @@
1
+ import { createAuthorizationHeaders, fetchRemoteStatus, normalizeApiUrl, readApiError, readJsonResponse, } from "./remote-test-session.js";
2
+ const POLL_INTERVAL_MS = 2_000;
3
+ const POLL_TIMEOUT_MS = 6 * 60 * 60_000;
4
+ export async function runRemoteTrigger(options) {
5
+ const apiUrl = normalizeApiUrl(options.apiUrl);
6
+ const response = await fetch(`${apiUrl}/api/cli/triggers`, {
7
+ body: JSON.stringify({
8
+ commitSha: options.commitSha,
9
+ env: options.env,
10
+ jobUrl: options.jobUrl,
11
+ pipelineUrl: options.pipelineUrl,
12
+ projectSlug: options.projectSlug,
13
+ ref: options.ref,
14
+ reporter: options.reporter,
15
+ repository: options.repository,
16
+ retries: options.retries,
17
+ testSessionName: options.testSessionName,
18
+ }),
19
+ headers: {
20
+ ...createAuthorizationHeaders(options.apiToken),
21
+ "Content-Type": "application/json",
22
+ },
23
+ method: "POST",
24
+ });
25
+ const trigger = await readJsonResponse(response);
26
+ if (!response.ok) {
27
+ throw new Error(readApiError(trigger, "Unable to queue remote trigger."));
28
+ }
29
+ return pollTrigger(apiUrl, options.apiToken, trigger);
30
+ }
31
+ async function pollTrigger(apiUrl, apiToken, trigger) {
32
+ const startedAt = Date.now();
33
+ const statusUrl = new URL(trigger.statusUrl, `${apiUrl}/`).toString();
34
+ while (Date.now() - startedAt < POLL_TIMEOUT_MS) {
35
+ const status = await fetchRemoteStatus(statusUrl, apiToken, "Unable to read remote trigger.");
36
+ if (status.status === "completed" && status.summary) {
37
+ return status.summary;
38
+ }
39
+ if (status.status === "failed") {
40
+ throw new Error(status.error || `Trigger ${trigger.triggerId} failed.`);
41
+ }
42
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
43
+ }
44
+ throw new Error(`Timed out waiting for trigger ${trigger.triggerId}.`);
45
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../../src/bin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { createRemoteSelfchecksProgram } from "./program.js";
3
+ const program = createRemoteSelfchecksProgram();
4
+ program.parseAsync().catch((error) => {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ process.stderr.write(`${message}\n`);
7
+ process.exitCode = 1;
8
+ });
@@ -0,0 +1,5 @@
1
+ export { createRemoteSelfchecksProgram, parseCheckType, parseEnv, parseEnvJson, parseRetries, type CliCommandOutput, type CreateRemoteSelfchecksProgramOptions, } from "./program.js";
2
+ export { runRemoteDeploy, type RemoteDeployOptions, } from "../../cli/src/remote-deploy.js";
3
+ export { cancelRemoteTestSession, collectBundleFiles, createRemoteBundleFormData, runRemoteTestSession, type RemoteTestSessionOptions, } from "../../cli/src/remote-test-session.js";
4
+ export { runRemoteTrigger, type RemoteTriggerOptions, } from "../../cli/src/remote-trigger.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,oCAAoC,GAC1C,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,gBAAgB,EAChB,KAAK,oBAAoB,GAC1B,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { createRemoteSelfchecksProgram, parseCheckType, parseEnv, parseEnvJson, parseRetries, } from "./program.js";
2
+ export { runRemoteDeploy, } from "../../cli/src/remote-deploy.js";
3
+ export { cancelRemoteTestSession, collectBundleFiles, createRemoteBundleFormData, runRemoteTestSession, } from "../../cli/src/remote-test-session.js";
4
+ export { runRemoteTrigger, } from "../../cli/src/remote-trigger.js";
@@ -0,0 +1,64 @@
1
+ import { Command } from "commander";
2
+ import { runRemoteDeploy, type RemoteDeployOptions } from "../../cli/src/remote-deploy.js";
3
+ import { type RemoteTestSessionOptions } from "../../cli/src/remote-test-session.js";
4
+ import { type RemoteTriggerOptions } from "../../cli/src/remote-trigger.js";
5
+ export type CheckType = "api" | "browser";
6
+ export type EnvVar = {
7
+ name: string;
8
+ value: string;
9
+ };
10
+ export type RunChecksSummary = {
11
+ durationMs: number;
12
+ failed: number;
13
+ passed: number;
14
+ results: unknown[];
15
+ sessionId?: string;
16
+ skipped: number;
17
+ total: number;
18
+ };
19
+ type DeploySummary = Awaited<ReturnType<typeof runRemoteDeploy>>;
20
+ export type CliCommandOutput = {
21
+ command: "deploy";
22
+ configPath: string;
23
+ dryRun: false;
24
+ force: boolean;
25
+ projectSlug: string;
26
+ rootDir: string;
27
+ status: "deployed";
28
+ summary: DeploySummary;
29
+ } | {
30
+ checkKeys: string[];
31
+ checkTypes: CheckType[];
32
+ command: "test";
33
+ env: EnvVar[];
34
+ projectSlug: string;
35
+ record: boolean;
36
+ reporter: string;
37
+ rootDir: string;
38
+ status: "completed";
39
+ summary: RunChecksSummary;
40
+ tagSets: string[][];
41
+ } | {
42
+ command: "trigger";
43
+ projectSlug: string;
44
+ record: boolean;
45
+ reporter: string;
46
+ retries?: number;
47
+ rootDir: string;
48
+ status: "completed";
49
+ summary: RunChecksSummary;
50
+ testSessionName?: string;
51
+ };
52
+ export type CreateRemoteSelfchecksProgramOptions = {
53
+ deployRemotely?: (options: RemoteDeployOptions) => Promise<DeploySummary>;
54
+ runChecksRemotely?: (options: RemoteTestSessionOptions) => Promise<RunChecksSummary>;
55
+ triggerRemotely?: (options: RemoteTriggerOptions) => Promise<RunChecksSummary>;
56
+ write?: (value: CliCommandOutput) => void;
57
+ };
58
+ export declare function parseEnv(value: string): EnvVar;
59
+ export declare function parseRetries(value: string): number;
60
+ export declare function parseCheckType(value: string): CheckType;
61
+ export declare function parseEnvJson(value: string | undefined): EnvVar[];
62
+ export declare function createRemoteSelfchecksProgram(options?: CreateRemoteSelfchecksProgramOptions): Command;
63
+ export {};
64
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../../src/program.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,241 @@
1
+ import path from "node:path";
2
+ import { Command } from "commander";
3
+ import { runRemoteDeploy, } from "../../cli/src/remote-deploy.js";
4
+ import { runRemoteTestSession, } from "../../cli/src/remote-test-session.js";
5
+ import { runRemoteTrigger, } from "../../cli/src/remote-trigger.js";
6
+ import { SELFCHECKS_CLI_VERSION } from "./version.js";
7
+ function collect(value, previous = []) {
8
+ return [...previous, value];
9
+ }
10
+ export function parseEnv(value) {
11
+ const separatorIndex = value.indexOf("=");
12
+ if (separatorIndex <= 0) {
13
+ throw new Error(`Expected environment value in NAME=value format: ${value}`);
14
+ }
15
+ return {
16
+ name: value.slice(0, separatorIndex),
17
+ value: value.slice(separatorIndex + 1),
18
+ };
19
+ }
20
+ export function parseRetries(value) {
21
+ if (!/^\d+$/.test(value)) {
22
+ throw new Error(`Expected retries to be a non-negative integer: ${value}`);
23
+ }
24
+ const retries = Number.parseInt(value, 10);
25
+ if (!Number.isSafeInteger(retries)) {
26
+ throw new Error(`Expected retries to be a non-negative integer: ${value}`);
27
+ }
28
+ return retries;
29
+ }
30
+ export function parseCheckType(value) {
31
+ const type = value.trim().toLowerCase();
32
+ if (type !== "api" && type !== "browser") {
33
+ throw new Error(`Expected check type to be api or browser: ${value}`);
34
+ }
35
+ return type;
36
+ }
37
+ export function parseEnvJson(value) {
38
+ if (!value) {
39
+ return [];
40
+ }
41
+ const parsed = JSON.parse(value);
42
+ if (!Array.isArray(parsed)) {
43
+ throw new Error("SELFCHECKS_ENV_JSON must contain an array.");
44
+ }
45
+ return parsed.map((item) => {
46
+ if (!item ||
47
+ typeof item !== "object" ||
48
+ !("name" in item) ||
49
+ !("value" in item) ||
50
+ typeof item.name !== "string" ||
51
+ typeof item.value !== "string") {
52
+ throw new Error("SELFCHECKS_ENV_JSON contains an invalid environment value.");
53
+ }
54
+ return { name: item.name, value: item.value };
55
+ });
56
+ }
57
+ function normalizeTags(tags) {
58
+ return [...new Set([...tags].map((tag) => tag.trim()).filter(Boolean))].sort();
59
+ }
60
+ function resolveDeployRootDir(commandOptions) {
61
+ if (typeof commandOptions.root === "string") {
62
+ return commandOptions.root;
63
+ }
64
+ if (typeof commandOptions.config === "string") {
65
+ return path.dirname(commandOptions.config);
66
+ }
67
+ return process.cwd();
68
+ }
69
+ function requireRemoteConfig(apiUrl, apiToken, command) {
70
+ if (!apiUrl || !apiToken) {
71
+ throw new Error(`SELFCHECKS_URL and SELFCHECKS_API_TOKEN are required for ${command}.`);
72
+ }
73
+ return { apiToken: String(apiToken), apiUrl: String(apiUrl) };
74
+ }
75
+ export function createRemoteSelfchecksProgram(options = {}) {
76
+ const write = options.write ??
77
+ ((value) => {
78
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
79
+ });
80
+ const deployRemotely = options.deployRemotely ?? runRemoteDeploy;
81
+ const runChecksRemotely = options.runChecksRemotely ?? runRemoteTestSession;
82
+ const triggerRemotely = options.triggerRemotely ?? runRemoteTrigger;
83
+ const program = new Command();
84
+ program
85
+ .name("selfchecks")
86
+ .description("Remote client for the Selfchecks synthetic checks service.")
87
+ .version(SELFCHECKS_CLI_VERSION);
88
+ program
89
+ .command("deploy")
90
+ .description("Upload and deploy check definitions.")
91
+ .option("-c, --config <path>", "Path to a Checkly-compatible config file")
92
+ .option("--force", "Deploy even when the diff contains removals")
93
+ .option("--project <slug>", "Project slug", "default")
94
+ .option("--root <path>", "Repository root")
95
+ .option("--api-url <url>", "Selfchecks API URL", process.env.SELFCHECKS_URL)
96
+ .option("--api-token <token>", "Selfchecks API token", process.env.SELFCHECKS_API_TOKEN)
97
+ .action(async (commandOptions) => {
98
+ const projectSlug = String(commandOptions.project ?? "default");
99
+ const rootDir = resolveDeployRootDir(commandOptions);
100
+ const remote = requireRemoteConfig(commandOptions.apiUrl, commandOptions.apiToken, "deploy");
101
+ const summary = await deployRemotely({
102
+ allowRemovals: Boolean(commandOptions.force),
103
+ ...remote,
104
+ projectSlug,
105
+ rootDir,
106
+ });
107
+ write({
108
+ command: "deploy",
109
+ configPath: String(commandOptions.config ?? "checkly.config.ts"),
110
+ dryRun: false,
111
+ force: Boolean(commandOptions.force),
112
+ projectSlug,
113
+ rootDir,
114
+ status: "deployed",
115
+ summary,
116
+ });
117
+ });
118
+ program
119
+ .command("test")
120
+ .description("Upload and run selected checks in an isolated test session.")
121
+ .option("--tags <tags>", "Comma-separated tag selector", collect, [])
122
+ .option("--check <key>", "Run a specific check key", collect, [])
123
+ .option("--type <type>", "Run checks of a specific type", collect, [])
124
+ .option("-e, --env <name=value>", "Runtime environment variable", collect, [])
125
+ .option("--project <slug>", "Project slug", "default")
126
+ .option("--record", "Persist the run and artifacts")
127
+ .option("--reporter <name>", "Reporter name", "list")
128
+ .option("--retries <count>", "Override configured failed-check retries")
129
+ .option("--root <path>", "Repository root", process.cwd())
130
+ .option("--test-session-name <name>", "Display name for the test session")
131
+ .option("--repository <path>", "CI repository path", process.env.CI_PROJECT_PATH)
132
+ .option("--ref <ref>", "CI branch or tag", resolveCiRef())
133
+ .option("--commit-sha <sha>", "CI commit SHA", process.env.CI_COMMIT_SHA)
134
+ .option("--pipeline-url <url>", "CI pipeline URL", process.env.CI_PIPELINE_URL)
135
+ .option("--job-url <url>", "CI job URL", process.env.CI_JOB_URL)
136
+ .option("--api-url <url>", "Selfchecks API URL", process.env.SELFCHECKS_URL)
137
+ .option("--api-token <token>", "Selfchecks API token", process.env.SELFCHECKS_API_TOKEN)
138
+ .action(async (commandOptions) => {
139
+ const checkTypes = commandOptions.type.map(parseCheckType);
140
+ const tagSets = commandOptions.tags.map((tagSet) => normalizeTags(tagSet.split(",")));
141
+ const env = [
142
+ ...parseEnvJson(process.env.SELFCHECKS_ENV_JSON),
143
+ ...commandOptions.env.map(parseEnv),
144
+ ];
145
+ const retries = commandOptions.retries
146
+ ? parseRetries(String(commandOptions.retries))
147
+ : undefined;
148
+ const remote = requireRemoteConfig(commandOptions.apiUrl, commandOptions.apiToken, "test");
149
+ const summary = await runChecksRemotely({
150
+ ...remote,
151
+ checkKeys: commandOptions.check,
152
+ checkTypes,
153
+ commitSha: commandOptions.commitSha,
154
+ env,
155
+ jobUrl: commandOptions.jobUrl,
156
+ pipelineUrl: commandOptions.pipelineUrl,
157
+ projectSlug: commandOptions.project,
158
+ ref: commandOptions.ref,
159
+ reporter: commandOptions.reporter,
160
+ repository: commandOptions.repository,
161
+ retries,
162
+ rootDir: commandOptions.root,
163
+ tagSets,
164
+ testSessionName: commandOptions.testSessionName,
165
+ });
166
+ write({
167
+ checkKeys: commandOptions.check,
168
+ checkTypes,
169
+ command: "test",
170
+ env,
171
+ projectSlug: commandOptions.project,
172
+ record: Boolean(commandOptions.record),
173
+ reporter: commandOptions.reporter,
174
+ rootDir: commandOptions.root,
175
+ status: "completed",
176
+ summary,
177
+ tagSets,
178
+ });
179
+ if (summary.failed > 0) {
180
+ process.exitCode = 1;
181
+ }
182
+ });
183
+ program
184
+ .command("trigger")
185
+ .description("Queue the latest deployed checks for execution.")
186
+ .option("-e, --env <name=value>", "Runtime environment variable", collect, [])
187
+ .option("--project <slug>", "Project slug", "default")
188
+ .option("--record", "Persist the run and artifacts")
189
+ .option("--reporter <name>", "Reporter name", "list")
190
+ .option("--retries <count>", "Override configured failed-check retries")
191
+ .option("--root <path>", "Repository root", process.cwd())
192
+ .option("--test-session-name <name>", "Display name for the test session")
193
+ .option("--repository <path>", "CI repository path", process.env.CI_PROJECT_PATH)
194
+ .option("--ref <ref>", "CI branch or tag", resolveCiRef())
195
+ .option("--commit-sha <sha>", "CI commit SHA", process.env.CI_COMMIT_SHA)
196
+ .option("--pipeline-url <url>", "CI pipeline URL", process.env.CI_PIPELINE_URL)
197
+ .option("--job-url <url>", "CI job URL", process.env.CI_JOB_URL)
198
+ .option("--api-url <url>", "Selfchecks API URL", process.env.SELFCHECKS_URL)
199
+ .option("--api-token <token>", "Selfchecks API token", process.env.SELFCHECKS_API_TOKEN)
200
+ .action(async (commandOptions) => {
201
+ const retries = commandOptions.retries
202
+ ? parseRetries(String(commandOptions.retries))
203
+ : undefined;
204
+ const env = [
205
+ ...parseEnvJson(process.env.SELFCHECKS_ENV_JSON),
206
+ ...commandOptions.env.map(parseEnv),
207
+ ];
208
+ const remote = requireRemoteConfig(commandOptions.apiUrl, commandOptions.apiToken, "trigger");
209
+ const summary = await triggerRemotely({
210
+ ...remote,
211
+ commitSha: commandOptions.commitSha,
212
+ env,
213
+ jobUrl: commandOptions.jobUrl,
214
+ pipelineUrl: commandOptions.pipelineUrl,
215
+ projectSlug: commandOptions.project,
216
+ ref: commandOptions.ref,
217
+ reporter: commandOptions.reporter,
218
+ repository: commandOptions.repository,
219
+ retries,
220
+ testSessionName: commandOptions.testSessionName,
221
+ });
222
+ write({
223
+ command: "trigger",
224
+ projectSlug: commandOptions.project,
225
+ record: Boolean(commandOptions.record),
226
+ reporter: commandOptions.reporter,
227
+ ...(typeof retries === "number" ? { retries } : {}),
228
+ rootDir: commandOptions.root,
229
+ status: "completed",
230
+ summary,
231
+ testSessionName: commandOptions.testSessionName,
232
+ });
233
+ if (summary.failed > 0) {
234
+ process.exitCode = 1;
235
+ }
236
+ });
237
+ return program;
238
+ }
239
+ function resolveCiRef() {
240
+ return process.env.CI_COMMIT_TAG || process.env.CI_COMMIT_REF_NAME;
241
+ }
@@ -0,0 +1,2 @@
1
+ export declare const SELFCHECKS_CLI_VERSION = "0.1.1";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export const SELFCHECKS_CLI_VERSION = "0.1.1";
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@selfchecks/selfchecks-cli",
3
+ "version": "0.1.1",
4
+ "description": "Remote CLI for Selfchecks",
5
+ "type": "module",
6
+ "files": [
7
+ "dist/**/*.d.ts",
8
+ "dist/**/*.d.ts.map",
9
+ "dist/**/*.js"
10
+ ],
11
+ "bin": {
12
+ "selfchecks": "./dist/npm-cli/src/bin.js"
13
+ },
14
+ "main": "./dist/npm-cli/src/index.js",
15
+ "types": "./dist/npm-cli/src/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/npm-cli/src/index.d.ts",
19
+ "import": "./dist/npm-cli/src/index.js"
20
+ }
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/selfchecks/selfchecks.git",
28
+ "directory": "packages/npm-cli"
29
+ },
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/npm-cli/src/bin.js', 0o755)\"",
35
+ "test": "vitest run --root ../.. packages/npm-cli/src/program.test.ts",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit"
37
+ },
38
+ "dependencies": {
39
+ "commander": "^14.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.8.3",
43
+ "vitest": "^3.2.4"
44
+ }
45
+ }