@plune-ai/cli 0.4.0 → 0.5.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/dist/index.cjs +149 -3
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +141 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,13 +30,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
NotLoggedInError: () => NotLoggedInError,
|
|
33
34
|
RUN_RESULT_SCHEMA_VERSION: () => RUN_RESULT_SCHEMA_VERSION,
|
|
35
|
+
SyncFileError: () => SyncFileError,
|
|
36
|
+
SyncHttpError: () => SyncHttpError,
|
|
37
|
+
SyncNetworkError: () => SyncNetworkError,
|
|
38
|
+
TokenRejectedError: () => TokenRejectedError,
|
|
34
39
|
assertionConfigSchema: () => assertionConfigSchema,
|
|
35
40
|
assertionResultRecordSchema: () => assertionResultRecordSchema,
|
|
36
41
|
binaryVerdictSchema: () => binaryVerdictSchema,
|
|
37
42
|
parseRunResult: () => parseRunResult,
|
|
43
|
+
reportSyncFailure: () => reportSyncFailure,
|
|
38
44
|
run: () => handleRun,
|
|
39
|
-
runResultSchema: () => runResultSchema
|
|
45
|
+
runResultSchema: () => runResultSchema,
|
|
46
|
+
sync: () => handleSync
|
|
40
47
|
});
|
|
41
48
|
module.exports = __toCommonJS(src_exports);
|
|
42
49
|
|
|
@@ -357,7 +364,7 @@ function normalizeProviderError(err, secret, envVar) {
|
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
// src/providers/retry.ts
|
|
360
|
-
var defaultSleep = (ms) => new Promise((
|
|
367
|
+
var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
361
368
|
function retryAfterMs(err) {
|
|
362
369
|
if (typeof err !== "object" || err === null || !("headers" in err)) return void 0;
|
|
363
370
|
const raw = readHeader(err.headers, "retry-after");
|
|
@@ -1503,6 +1510,10 @@ async function handleRun(options, depsFactory) {
|
|
|
1503
1510
|
}
|
|
1504
1511
|
}
|
|
1505
1512
|
|
|
1513
|
+
// src/cli/commands/sync.ts
|
|
1514
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
1515
|
+
var path5 = __toESM(require("path"), 1);
|
|
1516
|
+
|
|
1506
1517
|
// src/types/run-result-schema.ts
|
|
1507
1518
|
var import_zod2 = require("zod");
|
|
1508
1519
|
var RUN_RESULT_SCHEMA_VERSION = 1;
|
|
@@ -1561,13 +1572,148 @@ var runResultSchema = import_zod2.z.object({
|
|
|
1561
1572
|
function parseRunResult(value) {
|
|
1562
1573
|
return runResultSchema.parse(value);
|
|
1563
1574
|
}
|
|
1575
|
+
|
|
1576
|
+
// src/cli/credentials.ts
|
|
1577
|
+
var import_node_fs = require("fs");
|
|
1578
|
+
var import_node_os = require("os");
|
|
1579
|
+
var import_node_path = require("path");
|
|
1580
|
+
function credentialsFile() {
|
|
1581
|
+
const base = process.env["XDG_CONFIG_HOME"]?.trim() || (0, import_node_path.join)((0, import_node_os.homedir)(), ".config");
|
|
1582
|
+
return (0, import_node_path.join)(base, "plune", "credentials.json");
|
|
1583
|
+
}
|
|
1584
|
+
function loadToken() {
|
|
1585
|
+
try {
|
|
1586
|
+
const parsed = JSON.parse((0, import_node_fs.readFileSync)(credentialsFile(), "utf-8"));
|
|
1587
|
+
return typeof parsed.token === "string" && parsed.token.length > 0 ? parsed.token : null;
|
|
1588
|
+
} catch {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// src/cli/commands/sync.ts
|
|
1594
|
+
var DEFAULT_API_URL = "https://beta-api.plune.ai";
|
|
1595
|
+
var NotLoggedInError = class extends Error {
|
|
1596
|
+
constructor() {
|
|
1597
|
+
super('Not logged in. Run "plune login" first to save your API token.');
|
|
1598
|
+
this.name = "NotLoggedInError";
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
var TokenRejectedError = class extends Error {
|
|
1602
|
+
constructor() {
|
|
1603
|
+
super(
|
|
1604
|
+
'Your API token was rejected (revoked or invalid). Run "plune login" with a fresh token.'
|
|
1605
|
+
);
|
|
1606
|
+
this.name = "TokenRejectedError";
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
var SyncFileError = class extends Error {
|
|
1610
|
+
constructor(message) {
|
|
1611
|
+
super(message);
|
|
1612
|
+
this.name = "SyncFileError";
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
var SyncNetworkError = class extends Error {
|
|
1616
|
+
constructor(apiUrl) {
|
|
1617
|
+
super(
|
|
1618
|
+
`Could not reach the Plune API at ${apiUrl}. Check your connection (or PLUNE_API_URL) and try again.`
|
|
1619
|
+
);
|
|
1620
|
+
this.name = "SyncNetworkError";
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
var SyncHttpError = class extends Error {
|
|
1624
|
+
constructor(status, detail) {
|
|
1625
|
+
super(`Sync failed \u2014 the server returned HTTP ${status}${detail ? ` (${detail})` : ""}.`);
|
|
1626
|
+
this.status = status;
|
|
1627
|
+
this.name = "SyncHttpError";
|
|
1628
|
+
}
|
|
1629
|
+
status;
|
|
1630
|
+
};
|
|
1631
|
+
function resolveApiUrl(explicit) {
|
|
1632
|
+
const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL;
|
|
1633
|
+
return raw.replace(/\/+$/, "");
|
|
1634
|
+
}
|
|
1635
|
+
function readRun(cwd, file) {
|
|
1636
|
+
const target = file !== void 0 ? path5.resolve(cwd, file) : path5.join(cwd, ".plune", "last-run.json");
|
|
1637
|
+
let raw;
|
|
1638
|
+
try {
|
|
1639
|
+
raw = fs6.readFileSync(target, "utf8");
|
|
1640
|
+
} catch {
|
|
1641
|
+
throw new SyncFileError(
|
|
1642
|
+
file !== void 0 ? `Run file not found: ${target}` : 'No run to sync. Run "plune run" first (or pass --file <path>).'
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
let json;
|
|
1646
|
+
try {
|
|
1647
|
+
json = JSON.parse(raw);
|
|
1648
|
+
} catch {
|
|
1649
|
+
throw new SyncFileError(`Run file is not valid JSON: ${target}`);
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
return parseRunResult(json);
|
|
1653
|
+
} catch {
|
|
1654
|
+
throw new SyncFileError(`Run file does not match the RunResult schema: ${target}`);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
async function safeErrorDetail(res) {
|
|
1658
|
+
try {
|
|
1659
|
+
const body = await res.json();
|
|
1660
|
+
return typeof body.error === "string" ? body.error : "";
|
|
1661
|
+
} catch {
|
|
1662
|
+
return "";
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function handleSync(deps = {}) {
|
|
1666
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1667
|
+
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
1668
|
+
const token = (deps.loadToken ?? loadToken)();
|
|
1669
|
+
if (token === null) throw new NotLoggedInError();
|
|
1670
|
+
const run = readRun(cwd, deps.file);
|
|
1671
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
1672
|
+
const endpoint = `${apiUrl}/v1/runs`;
|
|
1673
|
+
let res;
|
|
1674
|
+
try {
|
|
1675
|
+
res = await doFetch(endpoint, {
|
|
1676
|
+
method: "POST",
|
|
1677
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
1678
|
+
body: JSON.stringify(run)
|
|
1679
|
+
});
|
|
1680
|
+
} catch {
|
|
1681
|
+
throw new SyncNetworkError(apiUrl);
|
|
1682
|
+
}
|
|
1683
|
+
if (!res.ok) {
|
|
1684
|
+
const detail = await safeErrorDetail(res);
|
|
1685
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
1686
|
+
throw new SyncHttpError(res.status, detail);
|
|
1687
|
+
}
|
|
1688
|
+
const body = await res.json().catch(() => ({}));
|
|
1689
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
1690
|
+
return { id, url: `${endpoint}/${id}` };
|
|
1691
|
+
}
|
|
1692
|
+
function reportSyncFailure(err, write) {
|
|
1693
|
+
if (err instanceof NotLoggedInError || err instanceof TokenRejectedError || err instanceof SyncFileError) {
|
|
1694
|
+
write(err.message + "\n");
|
|
1695
|
+
return 2;
|
|
1696
|
+
}
|
|
1697
|
+
if (err instanceof SyncNetworkError || err instanceof SyncHttpError) {
|
|
1698
|
+
write(err.message + "\n");
|
|
1699
|
+
return 1;
|
|
1700
|
+
}
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1564
1703
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1565
1704
|
0 && (module.exports = {
|
|
1705
|
+
NotLoggedInError,
|
|
1566
1706
|
RUN_RESULT_SCHEMA_VERSION,
|
|
1707
|
+
SyncFileError,
|
|
1708
|
+
SyncHttpError,
|
|
1709
|
+
SyncNetworkError,
|
|
1710
|
+
TokenRejectedError,
|
|
1567
1711
|
assertionConfigSchema,
|
|
1568
1712
|
assertionResultRecordSchema,
|
|
1569
1713
|
binaryVerdictSchema,
|
|
1570
1714
|
parseRunResult,
|
|
1715
|
+
reportSyncFailure,
|
|
1571
1716
|
run,
|
|
1572
|
-
runResultSchema
|
|
1717
|
+
runResultSchema,
|
|
1718
|
+
sync
|
|
1573
1719
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -214,6 +214,57 @@ interface RunOptions {
|
|
|
214
214
|
}
|
|
215
215
|
declare function handleRun(options: RunOptions, depsFactory?: (config: Config, baseDir: string) => RunDeps): Promise<RunResult>;
|
|
216
216
|
|
|
217
|
+
/** No saved token — the user must `plune login` before syncing. (exit 2) */
|
|
218
|
+
declare class NotLoggedInError extends Error {
|
|
219
|
+
constructor();
|
|
220
|
+
}
|
|
221
|
+
/** The token was present but the server rejected it (401 — revoked or invalid). (exit 2) */
|
|
222
|
+
declare class TokenRejectedError extends Error {
|
|
223
|
+
constructor();
|
|
224
|
+
}
|
|
225
|
+
/** No run to sync, or the run file is unreadable / not a valid RunResult. (exit 2) */
|
|
226
|
+
declare class SyncFileError extends Error {
|
|
227
|
+
constructor(message: string);
|
|
228
|
+
}
|
|
229
|
+
/** The API was unreachable (DNS / TLS / offline). Message names the host, never the token. (exit 1) */
|
|
230
|
+
declare class SyncNetworkError extends Error {
|
|
231
|
+
constructor(apiUrl: string);
|
|
232
|
+
}
|
|
233
|
+
/** The server answered with a non-2xx that is not a 401 (e.g. 400 invalid body, 5xx). (exit 1) */
|
|
234
|
+
declare class SyncHttpError extends Error {
|
|
235
|
+
readonly status: number;
|
|
236
|
+
constructor(status: number, detail: string);
|
|
237
|
+
}
|
|
238
|
+
interface SyncDeps {
|
|
239
|
+
/** Base dir the default run file is read from (config dir or cwd). Defaults to `process.cwd()`. */
|
|
240
|
+
cwd?: string;
|
|
241
|
+
/** Explicit run JSON to sync instead of `<cwd>/.plune/last-run.json`. */
|
|
242
|
+
file?: string;
|
|
243
|
+
/** Override the API base URL. Falls back to `PLUNE_API_URL`, then the beta default. */
|
|
244
|
+
apiUrl?: string;
|
|
245
|
+
/** Token source (injected in tests). Defaults to the real credentials store. */
|
|
246
|
+
loadToken?: () => string | null;
|
|
247
|
+
/** HTTP transport (injected in tests — e.g. a real Hono app's `request`). Defaults to global `fetch`. */
|
|
248
|
+
fetchImpl?: typeof fetch;
|
|
249
|
+
}
|
|
250
|
+
interface SyncResult {
|
|
251
|
+
/** Server-assigned run id. */
|
|
252
|
+
id: string;
|
|
253
|
+
/** The `GET /v1/runs/:id` URL where the uploaded run can be read back. */
|
|
254
|
+
url: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Upload the latest (or `--file`) local run to `POST {apiUrl}/v1/runs` with the stored Bearer token.
|
|
258
|
+
* Returns the server-assigned id + the read-back URL. Throws a typed error (see the classes above) on
|
|
259
|
+
* every failure path so the CLI can exit cleanly without ever leaking the token or a stack trace.
|
|
260
|
+
*/
|
|
261
|
+
declare function handleSync(deps?: SyncDeps): Promise<SyncResult>;
|
|
262
|
+
/**
|
|
263
|
+
* Map a `handleSync` rejection to a friendly stderr line + the exit code to use, or `null` if the error
|
|
264
|
+
* is not a known sync failure (caller then treats it as unexpected).
|
|
265
|
+
*/
|
|
266
|
+
declare function reportSyncFailure(err: unknown, write: (s: string) => void): number | null;
|
|
267
|
+
|
|
217
268
|
declare const assertionConfigSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
218
269
|
type: z.ZodLiteral<"exact-match">;
|
|
219
270
|
value: z.ZodString;
|
|
@@ -748,4 +799,4 @@ type BinaryVerdict = z.infer<typeof binaryVerdictSchema>;
|
|
|
748
799
|
/** Parse + validate an unknown value as a RunResult. Throws ZodError on failure. */
|
|
749
800
|
declare function parseRunResult(value: unknown): ParsedRunResult;
|
|
750
801
|
|
|
751
|
-
export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, handleRun as run, runResultSchema };
|
|
802
|
+
export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
|
package/dist/index.d.ts
CHANGED
|
@@ -214,6 +214,57 @@ interface RunOptions {
|
|
|
214
214
|
}
|
|
215
215
|
declare function handleRun(options: RunOptions, depsFactory?: (config: Config, baseDir: string) => RunDeps): Promise<RunResult>;
|
|
216
216
|
|
|
217
|
+
/** No saved token — the user must `plune login` before syncing. (exit 2) */
|
|
218
|
+
declare class NotLoggedInError extends Error {
|
|
219
|
+
constructor();
|
|
220
|
+
}
|
|
221
|
+
/** The token was present but the server rejected it (401 — revoked or invalid). (exit 2) */
|
|
222
|
+
declare class TokenRejectedError extends Error {
|
|
223
|
+
constructor();
|
|
224
|
+
}
|
|
225
|
+
/** No run to sync, or the run file is unreadable / not a valid RunResult. (exit 2) */
|
|
226
|
+
declare class SyncFileError extends Error {
|
|
227
|
+
constructor(message: string);
|
|
228
|
+
}
|
|
229
|
+
/** The API was unreachable (DNS / TLS / offline). Message names the host, never the token. (exit 1) */
|
|
230
|
+
declare class SyncNetworkError extends Error {
|
|
231
|
+
constructor(apiUrl: string);
|
|
232
|
+
}
|
|
233
|
+
/** The server answered with a non-2xx that is not a 401 (e.g. 400 invalid body, 5xx). (exit 1) */
|
|
234
|
+
declare class SyncHttpError extends Error {
|
|
235
|
+
readonly status: number;
|
|
236
|
+
constructor(status: number, detail: string);
|
|
237
|
+
}
|
|
238
|
+
interface SyncDeps {
|
|
239
|
+
/** Base dir the default run file is read from (config dir or cwd). Defaults to `process.cwd()`. */
|
|
240
|
+
cwd?: string;
|
|
241
|
+
/** Explicit run JSON to sync instead of `<cwd>/.plune/last-run.json`. */
|
|
242
|
+
file?: string;
|
|
243
|
+
/** Override the API base URL. Falls back to `PLUNE_API_URL`, then the beta default. */
|
|
244
|
+
apiUrl?: string;
|
|
245
|
+
/** Token source (injected in tests). Defaults to the real credentials store. */
|
|
246
|
+
loadToken?: () => string | null;
|
|
247
|
+
/** HTTP transport (injected in tests — e.g. a real Hono app's `request`). Defaults to global `fetch`. */
|
|
248
|
+
fetchImpl?: typeof fetch;
|
|
249
|
+
}
|
|
250
|
+
interface SyncResult {
|
|
251
|
+
/** Server-assigned run id. */
|
|
252
|
+
id: string;
|
|
253
|
+
/** The `GET /v1/runs/:id` URL where the uploaded run can be read back. */
|
|
254
|
+
url: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Upload the latest (or `--file`) local run to `POST {apiUrl}/v1/runs` with the stored Bearer token.
|
|
258
|
+
* Returns the server-assigned id + the read-back URL. Throws a typed error (see the classes above) on
|
|
259
|
+
* every failure path so the CLI can exit cleanly without ever leaking the token or a stack trace.
|
|
260
|
+
*/
|
|
261
|
+
declare function handleSync(deps?: SyncDeps): Promise<SyncResult>;
|
|
262
|
+
/**
|
|
263
|
+
* Map a `handleSync` rejection to a friendly stderr line + the exit code to use, or `null` if the error
|
|
264
|
+
* is not a known sync failure (caller then treats it as unexpected).
|
|
265
|
+
*/
|
|
266
|
+
declare function reportSyncFailure(err: unknown, write: (s: string) => void): number | null;
|
|
267
|
+
|
|
217
268
|
declare const assertionConfigSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
218
269
|
type: z.ZodLiteral<"exact-match">;
|
|
219
270
|
value: z.ZodString;
|
|
@@ -748,4 +799,4 @@ type BinaryVerdict = z.infer<typeof binaryVerdictSchema>;
|
|
|
748
799
|
/** Parse + validate an unknown value as a RunResult. Throws ZodError on failure. */
|
|
749
800
|
declare function parseRunResult(value: unknown): ParsedRunResult;
|
|
750
801
|
|
|
751
|
-
export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, handleRun as run, runResultSchema };
|
|
802
|
+
export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
|
package/dist/index.js
CHANGED
|
@@ -315,7 +315,7 @@ function normalizeProviderError(err, secret, envVar) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// src/providers/retry.ts
|
|
318
|
-
var defaultSleep = (ms) => new Promise((
|
|
318
|
+
var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
319
319
|
function retryAfterMs(err) {
|
|
320
320
|
if (typeof err !== "object" || err === null || !("headers" in err)) return void 0;
|
|
321
321
|
const raw = readHeader(err.headers, "retry-after");
|
|
@@ -1461,6 +1461,10 @@ async function handleRun(options, depsFactory) {
|
|
|
1461
1461
|
}
|
|
1462
1462
|
}
|
|
1463
1463
|
|
|
1464
|
+
// src/cli/commands/sync.ts
|
|
1465
|
+
import * as fs6 from "fs";
|
|
1466
|
+
import * as path5 from "path";
|
|
1467
|
+
|
|
1464
1468
|
// src/types/run-result-schema.ts
|
|
1465
1469
|
import { z as z2 } from "zod";
|
|
1466
1470
|
var RUN_RESULT_SCHEMA_VERSION = 1;
|
|
@@ -1519,12 +1523,147 @@ var runResultSchema = z2.object({
|
|
|
1519
1523
|
function parseRunResult(value) {
|
|
1520
1524
|
return runResultSchema.parse(value);
|
|
1521
1525
|
}
|
|
1526
|
+
|
|
1527
|
+
// src/cli/credentials.ts
|
|
1528
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1529
|
+
import { homedir } from "os";
|
|
1530
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
1531
|
+
function credentialsFile() {
|
|
1532
|
+
const base = process.env["XDG_CONFIG_HOME"]?.trim() || join3(homedir(), ".config");
|
|
1533
|
+
return join3(base, "plune", "credentials.json");
|
|
1534
|
+
}
|
|
1535
|
+
function loadToken() {
|
|
1536
|
+
try {
|
|
1537
|
+
const parsed = JSON.parse(readFileSync3(credentialsFile(), "utf-8"));
|
|
1538
|
+
return typeof parsed.token === "string" && parsed.token.length > 0 ? parsed.token : null;
|
|
1539
|
+
} catch {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// src/cli/commands/sync.ts
|
|
1545
|
+
var DEFAULT_API_URL = "https://beta-api.plune.ai";
|
|
1546
|
+
var NotLoggedInError = class extends Error {
|
|
1547
|
+
constructor() {
|
|
1548
|
+
super('Not logged in. Run "plune login" first to save your API token.');
|
|
1549
|
+
this.name = "NotLoggedInError";
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
var TokenRejectedError = class extends Error {
|
|
1553
|
+
constructor() {
|
|
1554
|
+
super(
|
|
1555
|
+
'Your API token was rejected (revoked or invalid). Run "plune login" with a fresh token.'
|
|
1556
|
+
);
|
|
1557
|
+
this.name = "TokenRejectedError";
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
var SyncFileError = class extends Error {
|
|
1561
|
+
constructor(message) {
|
|
1562
|
+
super(message);
|
|
1563
|
+
this.name = "SyncFileError";
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
var SyncNetworkError = class extends Error {
|
|
1567
|
+
constructor(apiUrl) {
|
|
1568
|
+
super(
|
|
1569
|
+
`Could not reach the Plune API at ${apiUrl}. Check your connection (or PLUNE_API_URL) and try again.`
|
|
1570
|
+
);
|
|
1571
|
+
this.name = "SyncNetworkError";
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
var SyncHttpError = class extends Error {
|
|
1575
|
+
constructor(status, detail) {
|
|
1576
|
+
super(`Sync failed \u2014 the server returned HTTP ${status}${detail ? ` (${detail})` : ""}.`);
|
|
1577
|
+
this.status = status;
|
|
1578
|
+
this.name = "SyncHttpError";
|
|
1579
|
+
}
|
|
1580
|
+
status;
|
|
1581
|
+
};
|
|
1582
|
+
function resolveApiUrl(explicit) {
|
|
1583
|
+
const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL;
|
|
1584
|
+
return raw.replace(/\/+$/, "");
|
|
1585
|
+
}
|
|
1586
|
+
function readRun(cwd, file) {
|
|
1587
|
+
const target = file !== void 0 ? path5.resolve(cwd, file) : path5.join(cwd, ".plune", "last-run.json");
|
|
1588
|
+
let raw;
|
|
1589
|
+
try {
|
|
1590
|
+
raw = fs6.readFileSync(target, "utf8");
|
|
1591
|
+
} catch {
|
|
1592
|
+
throw new SyncFileError(
|
|
1593
|
+
file !== void 0 ? `Run file not found: ${target}` : 'No run to sync. Run "plune run" first (or pass --file <path>).'
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
let json;
|
|
1597
|
+
try {
|
|
1598
|
+
json = JSON.parse(raw);
|
|
1599
|
+
} catch {
|
|
1600
|
+
throw new SyncFileError(`Run file is not valid JSON: ${target}`);
|
|
1601
|
+
}
|
|
1602
|
+
try {
|
|
1603
|
+
return parseRunResult(json);
|
|
1604
|
+
} catch {
|
|
1605
|
+
throw new SyncFileError(`Run file does not match the RunResult schema: ${target}`);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
async function safeErrorDetail(res) {
|
|
1609
|
+
try {
|
|
1610
|
+
const body = await res.json();
|
|
1611
|
+
return typeof body.error === "string" ? body.error : "";
|
|
1612
|
+
} catch {
|
|
1613
|
+
return "";
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async function handleSync(deps = {}) {
|
|
1617
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1618
|
+
const apiUrl = resolveApiUrl(deps.apiUrl);
|
|
1619
|
+
const token = (deps.loadToken ?? loadToken)();
|
|
1620
|
+
if (token === null) throw new NotLoggedInError();
|
|
1621
|
+
const run = readRun(cwd, deps.file);
|
|
1622
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
1623
|
+
const endpoint = `${apiUrl}/v1/runs`;
|
|
1624
|
+
let res;
|
|
1625
|
+
try {
|
|
1626
|
+
res = await doFetch(endpoint, {
|
|
1627
|
+
method: "POST",
|
|
1628
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
1629
|
+
body: JSON.stringify(run)
|
|
1630
|
+
});
|
|
1631
|
+
} catch {
|
|
1632
|
+
throw new SyncNetworkError(apiUrl);
|
|
1633
|
+
}
|
|
1634
|
+
if (!res.ok) {
|
|
1635
|
+
const detail = await safeErrorDetail(res);
|
|
1636
|
+
if (res.status === 401) throw new TokenRejectedError();
|
|
1637
|
+
throw new SyncHttpError(res.status, detail);
|
|
1638
|
+
}
|
|
1639
|
+
const body = await res.json().catch(() => ({}));
|
|
1640
|
+
const id = typeof body.id === "string" ? body.id : "";
|
|
1641
|
+
return { id, url: `${endpoint}/${id}` };
|
|
1642
|
+
}
|
|
1643
|
+
function reportSyncFailure(err, write) {
|
|
1644
|
+
if (err instanceof NotLoggedInError || err instanceof TokenRejectedError || err instanceof SyncFileError) {
|
|
1645
|
+
write(err.message + "\n");
|
|
1646
|
+
return 2;
|
|
1647
|
+
}
|
|
1648
|
+
if (err instanceof SyncNetworkError || err instanceof SyncHttpError) {
|
|
1649
|
+
write(err.message + "\n");
|
|
1650
|
+
return 1;
|
|
1651
|
+
}
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1522
1654
|
export {
|
|
1655
|
+
NotLoggedInError,
|
|
1523
1656
|
RUN_RESULT_SCHEMA_VERSION,
|
|
1657
|
+
SyncFileError,
|
|
1658
|
+
SyncHttpError,
|
|
1659
|
+
SyncNetworkError,
|
|
1660
|
+
TokenRejectedError,
|
|
1524
1661
|
assertionConfigSchema,
|
|
1525
1662
|
assertionResultRecordSchema,
|
|
1526
1663
|
binaryVerdictSchema,
|
|
1527
1664
|
parseRunResult,
|
|
1665
|
+
reportSyncFailure,
|
|
1528
1666
|
handleRun as run,
|
|
1529
|
-
runResultSchema
|
|
1667
|
+
runResultSchema,
|
|
1668
|
+
handleSync as sync
|
|
1530
1669
|
};
|
package/package.json
CHANGED