hunter-harness 0.2.5 → 0.2.6
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/bin.js +486 -167
- package/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -140,8 +140,18 @@ var baselineManifestSchema = z2.object({
|
|
|
140
140
|
var finalizeProposalSchema = z2.object({
|
|
141
141
|
schema_version: z2.literal(1),
|
|
142
142
|
manifest_sha256: sha256Schema,
|
|
143
|
-
base_artifact_id: z2.string().regex(/^art_/).nullable()
|
|
144
|
-
|
|
143
|
+
base_artifact_id: z2.string().regex(/^art_/).nullable(),
|
|
144
|
+
sensitive_scan_skip: z2.literal(true).optional(),
|
|
145
|
+
sensitive_scan_skip_reason: z2.string().max(500).optional()
|
|
146
|
+
}).strict().superRefine((value, ctx) => {
|
|
147
|
+
if (value.sensitive_scan_skip_reason !== void 0 && value.sensitive_scan_skip !== true) {
|
|
148
|
+
ctx.addIssue({
|
|
149
|
+
code: "custom",
|
|
150
|
+
message: "sensitive_scan_skip_reason requires sensitive_scan_skip",
|
|
151
|
+
path: ["sensitive_scan_skip_reason"]
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
145
155
|
var requestMetadataSchema = z2.object({
|
|
146
156
|
request_id: z2.uuid(),
|
|
147
157
|
idempotency_key: z2.uuid(),
|
|
@@ -3100,8 +3110,9 @@ function scanSensitiveFiles(files, options = {}) {
|
|
|
3100
3110
|
const path = normalizeManagedPath(inputPath);
|
|
3101
3111
|
const ignores = parseInlineIgnores(content);
|
|
3102
3112
|
const knowledgeMetadata = path === ".harness/knowledge" || path.startsWith(".harness/knowledge/");
|
|
3113
|
+
const archiveMetadata = path === ".harness/archive" || path.startsWith(".harness/archive/");
|
|
3103
3114
|
for (const raw of rawFindings(content)) {
|
|
3104
|
-
if (knowledgeMetadata && raw.ruleId === "HH_WINDOWS_ABSOLUTE_PATH") {
|
|
3115
|
+
if ((knowledgeMetadata || archiveMetadata) && raw.ruleId === "HH_WINDOWS_ABSOLUTE_PATH") {
|
|
3105
3116
|
continue;
|
|
3106
3117
|
}
|
|
3107
3118
|
const position = location(content, raw.offset);
|
|
@@ -3172,29 +3183,147 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
3172
3183
|
};
|
|
3173
3184
|
}
|
|
3174
3185
|
|
|
3175
|
-
// ../core/dist/push/
|
|
3176
|
-
import {
|
|
3177
|
-
import { join as
|
|
3186
|
+
// ../core/dist/push/credentials.js
|
|
3187
|
+
import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
3188
|
+
import { join as join7 } from "node:path";
|
|
3178
3189
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3190
|
+
var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
|
|
3191
|
+
var CREDENTIALS_GITIGNORE_LINES = [
|
|
3192
|
+
".harness/credentials.local.yaml",
|
|
3193
|
+
".harness/credentials.local.*"
|
|
3194
|
+
];
|
|
3195
|
+
var InvalidCredentialsError = class extends Error {
|
|
3196
|
+
constructor(message) {
|
|
3197
|
+
super(message);
|
|
3198
|
+
this.name = "InvalidCredentialsError";
|
|
3199
|
+
}
|
|
3200
|
+
};
|
|
3201
|
+
function assertHttpsServerUrl(url) {
|
|
3202
|
+
try {
|
|
3203
|
+
const parsed = new URL(url.trim());
|
|
3204
|
+
if (parsed.protocol !== "https:") {
|
|
3205
|
+
throw new InvalidCredentialsError("server_url must use HTTPS");
|
|
3206
|
+
}
|
|
3207
|
+
return parsed.toString().replace(/\/$/, "");
|
|
3208
|
+
} catch (error) {
|
|
3209
|
+
if (error instanceof InvalidCredentialsError) {
|
|
3210
|
+
throw error;
|
|
3211
|
+
}
|
|
3212
|
+
throw new InvalidCredentialsError("server_url is invalid");
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
function validateLocalCredentials(credentials) {
|
|
3216
|
+
const token = typeof credentials.token === "string" && credentials.token.trim().length > 0 ? credentials.token.trim() : void 0;
|
|
3217
|
+
const serverUrl = typeof credentials.server_url === "string" && credentials.server_url.trim().length > 0 ? assertHttpsServerUrl(credentials.server_url) : void 0;
|
|
3218
|
+
if (token === void 0 && serverUrl === void 0) {
|
|
3219
|
+
throw new InvalidCredentialsError("credentials.local must include token and/or server_url");
|
|
3220
|
+
}
|
|
3221
|
+
return {
|
|
3222
|
+
...token === void 0 ? {} : { token },
|
|
3223
|
+
...serverUrl === void 0 ? {} : { server_url: serverUrl }
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
function parseLocalCredentials(raw) {
|
|
3227
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
3228
|
+
return null;
|
|
3229
|
+
}
|
|
3230
|
+
const record = raw;
|
|
3231
|
+
const token = typeof record.token === "string" && record.token.trim().length > 0 ? record.token.trim() : void 0;
|
|
3232
|
+
const serverUrlRaw = typeof record.server_url === "string" && record.server_url.trim().length > 0 ? record.server_url.trim() : void 0;
|
|
3233
|
+
let serverUrl;
|
|
3234
|
+
if (serverUrlRaw !== void 0) {
|
|
3235
|
+
try {
|
|
3236
|
+
serverUrl = assertHttpsServerUrl(serverUrlRaw);
|
|
3237
|
+
} catch {
|
|
3238
|
+
return null;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
if (token === void 0 && serverUrl === void 0) {
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
return {
|
|
3245
|
+
...token === void 0 ? {} : { token },
|
|
3246
|
+
...serverUrl === void 0 ? {} : { server_url: serverUrl }
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function mergeLocalCredentials(existing, patch) {
|
|
3250
|
+
return validateLocalCredentials({
|
|
3251
|
+
...existing?.token === void 0 ? {} : { token: existing.token },
|
|
3252
|
+
...existing?.server_url === void 0 ? {} : { server_url: existing.server_url },
|
|
3253
|
+
...patch
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
async function readLocalCredentials(projectRoot) {
|
|
3257
|
+
try {
|
|
3258
|
+
const raw = await readFile6(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
|
|
3259
|
+
return parseLocalCredentials(parseYaml4(raw));
|
|
3260
|
+
} catch (error) {
|
|
3261
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3262
|
+
return null;
|
|
3263
|
+
}
|
|
3264
|
+
throw error;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
async function writeLocalCredentials(projectRoot, credentials) {
|
|
3268
|
+
const normalized = validateLocalCredentials(credentials);
|
|
3269
|
+
await writeFile3(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
|
|
3270
|
+
}
|
|
3271
|
+
async function ensureCredentialsGitignore(projectRoot) {
|
|
3272
|
+
const gitignorePath = join7(projectRoot, ".gitignore");
|
|
3273
|
+
let content = "";
|
|
3274
|
+
try {
|
|
3275
|
+
content = await readFile6(gitignorePath, "utf8");
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
3278
|
+
throw error;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
const existing = new Set(content.split("\n").map((line) => line.trim()));
|
|
3282
|
+
const missing = CREDENTIALS_GITIGNORE_LINES.filter((line) => !existing.has(line));
|
|
3283
|
+
if (missing.length === 0) {
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
3287
|
+
await writeFile3(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
|
|
3288
|
+
}
|
|
3289
|
+
function resolvePushAuth(input) {
|
|
3290
|
+
const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
|
|
3291
|
+
const envToken = input.env[tokenEnv]?.trim();
|
|
3292
|
+
const localToken = input.local?.token?.trim();
|
|
3293
|
+
const token = envToken && envToken.length > 0 ? envToken : localToken && localToken.length > 0 ? localToken : void 0;
|
|
3294
|
+
const serverUrl = input.serverUrlFlag ?? input.local?.server_url ?? input.projectUrl ?? void 0;
|
|
3295
|
+
if (serverUrl === void 0 || serverUrl === null || serverUrl.trim() === "") {
|
|
3296
|
+
return { code: "SERVER_URL_REQUIRED" };
|
|
3297
|
+
}
|
|
3298
|
+
if (token === void 0) {
|
|
3299
|
+
return { code: "TOKEN_INVALID" };
|
|
3300
|
+
}
|
|
3301
|
+
return { serverUrl, token };
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
// ../core/dist/push/push.js
|
|
3305
|
+
import { lstat as lstat2, readFile as readFile9, readdir as readdir4, rm as rm4 } from "node:fs/promises";
|
|
3306
|
+
import { join as join10, relative, resolve as resolve5 } from "node:path";
|
|
3307
|
+
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
3179
3308
|
|
|
3180
3309
|
// ../core/dist/state/baseline.js
|
|
3181
|
-
import { readFile as
|
|
3182
|
-
import { join as
|
|
3310
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
3311
|
+
import { join as join8 } from "node:path";
|
|
3183
3312
|
async function readBaseline(projectRoot) {
|
|
3184
|
-
const content = await
|
|
3313
|
+
const content = await readFile7(join8(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
3185
3314
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
3186
3315
|
}
|
|
3187
3316
|
|
|
3188
3317
|
// ../core/dist/state/locks.js
|
|
3189
3318
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3190
|
-
import { readFile as
|
|
3191
|
-
import { join as
|
|
3319
|
+
import { readFile as readFile8, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
3320
|
+
import { join as join9 } from "node:path";
|
|
3192
3321
|
async function readLock(path) {
|
|
3193
|
-
return JSON.parse(await
|
|
3322
|
+
return JSON.parse(await readFile8(path, "utf8"));
|
|
3194
3323
|
}
|
|
3195
3324
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
3196
3325
|
const layout = await ensureStateLayout(projectRoot);
|
|
3197
|
-
const lockPath =
|
|
3326
|
+
const lockPath = join9(layout.locks, "protocol.lock");
|
|
3198
3327
|
const now = options.now ?? Date.now();
|
|
3199
3328
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
3200
3329
|
const record = {
|
|
@@ -3207,7 +3336,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3207
3336
|
};
|
|
3208
3337
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3209
3338
|
try {
|
|
3210
|
-
await
|
|
3339
|
+
await writeFile4(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
3211
3340
|
return {
|
|
3212
3341
|
path: lockPath,
|
|
3213
3342
|
operation,
|
|
@@ -3239,11 +3368,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3239
3368
|
var PushWorkflowError = class extends Error {
|
|
3240
3369
|
exitCode;
|
|
3241
3370
|
code;
|
|
3242
|
-
|
|
3371
|
+
details;
|
|
3372
|
+
constructor(message, exitCode, code, details) {
|
|
3243
3373
|
super(message);
|
|
3244
3374
|
this.name = "PushWorkflowError";
|
|
3245
3375
|
this.exitCode = exitCode;
|
|
3246
3376
|
this.code = code;
|
|
3377
|
+
if (details !== void 0) {
|
|
3378
|
+
this.details = details;
|
|
3379
|
+
}
|
|
3247
3380
|
}
|
|
3248
3381
|
};
|
|
3249
3382
|
var SHARED_MANAGED_ROOTS = [
|
|
@@ -3271,7 +3404,7 @@ async function walkFiles(root, current, output) {
|
|
|
3271
3404
|
return;
|
|
3272
3405
|
}
|
|
3273
3406
|
for (const item2 of await readdir4(current, { withFileTypes: true })) {
|
|
3274
|
-
const path =
|
|
3407
|
+
const path = join10(current, item2.name);
|
|
3275
3408
|
if (item2.isSymbolicLink()) {
|
|
3276
3409
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
3277
3410
|
}
|
|
@@ -3293,7 +3426,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
3293
3426
|
return;
|
|
3294
3427
|
for (const item2 of await readdir4(directory, { withFileTypes: true })) {
|
|
3295
3428
|
if (item2.name.startsWith("harness-")) {
|
|
3296
|
-
const path =
|
|
3429
|
+
const path = join10(directory, item2.name);
|
|
3297
3430
|
if (item2.isDirectory()) {
|
|
3298
3431
|
await walkFiles(root, path, output);
|
|
3299
3432
|
} else if (item2.isFile()) {
|
|
@@ -3312,25 +3445,25 @@ async function managedFiles(projectRoot, project) {
|
|
|
3312
3445
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
3313
3446
|
];
|
|
3314
3447
|
for (const path of managedFiles2) {
|
|
3315
|
-
if (await exists3(
|
|
3448
|
+
if (await exists3(join10(root, path))) {
|
|
3316
3449
|
paths.push(path);
|
|
3317
3450
|
}
|
|
3318
3451
|
}
|
|
3319
3452
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
3320
|
-
await walkFiles(root,
|
|
3453
|
+
await walkFiles(root, join10(root, path), paths);
|
|
3321
3454
|
}
|
|
3322
3455
|
for (const adapter of adapters) {
|
|
3323
3456
|
if (adapter.rulesRoot !== null) {
|
|
3324
|
-
await walkFiles(root,
|
|
3457
|
+
await walkFiles(root, join10(root, adapter.rulesRoot), paths);
|
|
3325
3458
|
}
|
|
3326
|
-
await walkHarnessEntries(root,
|
|
3459
|
+
await walkHarnessEntries(root, join10(root, adapter.skillsRoot), paths);
|
|
3327
3460
|
if (adapter.agentsRoot !== null) {
|
|
3328
|
-
await walkHarnessEntries(root,
|
|
3461
|
+
await walkHarnessEntries(root, join10(root, adapter.agentsRoot), paths);
|
|
3329
3462
|
}
|
|
3330
3463
|
}
|
|
3331
3464
|
const result = {};
|
|
3332
3465
|
for (const path of [...new Set(paths)].sort()) {
|
|
3333
|
-
result[path] = await
|
|
3466
|
+
result[path] = await readFile9(join10(root, path), "utf8");
|
|
3334
3467
|
}
|
|
3335
3468
|
return result;
|
|
3336
3469
|
}
|
|
@@ -3339,14 +3472,14 @@ function proposalBaseline(baseline) {
|
|
|
3339
3472
|
}
|
|
3340
3473
|
async function readProject(root) {
|
|
3341
3474
|
try {
|
|
3342
|
-
return projectConfigSchema.parse(
|
|
3475
|
+
return projectConfigSchema.parse(parseYaml5(await readFile9(join10(root, ".harness", "project.yaml"), "utf8")));
|
|
3343
3476
|
} catch {
|
|
3344
3477
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
3345
3478
|
}
|
|
3346
3479
|
}
|
|
3347
3480
|
async function readOptionalJson(path) {
|
|
3348
3481
|
try {
|
|
3349
|
-
return JSON.parse(await
|
|
3482
|
+
return JSON.parse(await readFile9(path, "utf8"));
|
|
3350
3483
|
} catch (error) {
|
|
3351
3484
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3352
3485
|
return null;
|
|
@@ -3358,7 +3491,7 @@ async function clientIdFor(root, explicit) {
|
|
|
3358
3491
|
if (explicit !== void 0) {
|
|
3359
3492
|
return explicit;
|
|
3360
3493
|
}
|
|
3361
|
-
const path =
|
|
3494
|
+
const path = join10(root, ".harness", "state", "local", "client.json");
|
|
3362
3495
|
const existing = await readOptionalJson(path);
|
|
3363
3496
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
3364
3497
|
return existing.client_id;
|
|
@@ -3403,6 +3536,50 @@ function resetSession(state, proposalManifestHash) {
|
|
|
3403
3536
|
chunk_idempotency_keys: {}
|
|
3404
3537
|
};
|
|
3405
3538
|
}
|
|
3539
|
+
function summarizeFindings(security) {
|
|
3540
|
+
const blocked = security.findings.filter((finding) => finding.disposition === "blocked");
|
|
3541
|
+
return {
|
|
3542
|
+
findings: blocked.map((finding) => ({
|
|
3543
|
+
path: finding.path,
|
|
3544
|
+
rule_id: finding.rule_id,
|
|
3545
|
+
severity: finding.severity,
|
|
3546
|
+
overridable: finding.overridable,
|
|
3547
|
+
fingerprint: finding.fingerprint,
|
|
3548
|
+
line: finding.line,
|
|
3549
|
+
column: finding.column
|
|
3550
|
+
})),
|
|
3551
|
+
finding_count: blocked.length,
|
|
3552
|
+
scanner_version: security.scanner_version
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
function sensitiveBlockedError(preview) {
|
|
3556
|
+
return new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED", summarizeFindings(preview.security));
|
|
3557
|
+
}
|
|
3558
|
+
async function resolveSensitiveScanSkip(preview, options) {
|
|
3559
|
+
if (!preview.blocked) {
|
|
3560
|
+
return { skip: false };
|
|
3561
|
+
}
|
|
3562
|
+
if (options.sensitiveScanSkip === true) {
|
|
3563
|
+
return {
|
|
3564
|
+
skip: true,
|
|
3565
|
+
...options.sensitiveScanSkipReason === void 0 ? {} : { reason: options.sensitiveScanSkipReason }
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
if (options.confirmSensitiveScanSkip !== void 0) {
|
|
3569
|
+
const answer = await options.confirmSensitiveScanSkip(preview);
|
|
3570
|
+
if (answer === "cancelled") {
|
|
3571
|
+
return { skip: false, cancelled: true };
|
|
3572
|
+
}
|
|
3573
|
+
return answer.skip ? { skip: true, ...answer.reason === void 0 ? {} : { reason: answer.reason } } : { skip: false };
|
|
3574
|
+
}
|
|
3575
|
+
throw sensitiveBlockedError(preview);
|
|
3576
|
+
}
|
|
3577
|
+
function assertPreviewAllowed(preview, skip) {
|
|
3578
|
+
if (preview.blocked && !skip) {
|
|
3579
|
+
throw sensitiveBlockedError(preview);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
var CREDENTIALS_HINT = "\u53EF\u5728\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u5F55\u5165\uFF0C\u6216\u5199\u5165 .harness/credentials.local.yaml\uFF08\u52FF\u63D0\u4EA4 git\uFF09";
|
|
3406
3583
|
function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3407
3584
|
const filteredFiles = {};
|
|
3408
3585
|
for (const [path, content] of Object.entries(files)) {
|
|
@@ -3430,7 +3607,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
3430
3607
|
{
|
|
3431
3608
|
operation: "modify",
|
|
3432
3609
|
path: ".harness/project.yaml",
|
|
3433
|
-
content:
|
|
3610
|
+
content: stringifyYaml4(nextProject, { sortMapEntries: true })
|
|
3434
3611
|
},
|
|
3435
3612
|
{
|
|
3436
3613
|
operation: "modify",
|
|
@@ -3447,24 +3624,38 @@ async function pushProject(options) {
|
|
|
3447
3624
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
3448
3625
|
const installedPaths = profile === null ? /* @__PURE__ */ new Set() : new Set(await Promise.all(enabledHarnessAgents(project).map((agent) => managedBundleTargets(options.resourcesRoot, profile, agent))).then((targets) => targets.flatMap((target) => [...target])));
|
|
3449
3626
|
let preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
|
|
3450
|
-
|
|
3451
|
-
|
|
3627
|
+
const initialSkip = await resolveSensitiveScanSkip(preview, options);
|
|
3628
|
+
if (initialSkip.cancelled === true) {
|
|
3629
|
+
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3452
3630
|
}
|
|
3631
|
+
let sensitiveScanSkip = initialSkip.skip;
|
|
3632
|
+
let sensitiveScanSkipReason = initialSkip.reason;
|
|
3633
|
+
assertPreviewAllowed(preview, sensitiveScanSkip);
|
|
3453
3634
|
if (options.dryRun) {
|
|
3454
3635
|
return { preview, proposalId: null, projectId: project.project.project_id };
|
|
3455
3636
|
}
|
|
3456
3637
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
3457
3638
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3458
3639
|
}
|
|
3459
|
-
const
|
|
3460
|
-
|
|
3461
|
-
|
|
3640
|
+
const localCredentials = await readLocalCredentials(root);
|
|
3641
|
+
const auth = resolvePushAuth({
|
|
3642
|
+
...options.serverUrl === void 0 ? {} : { serverUrlFlag: options.serverUrl },
|
|
3643
|
+
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
3644
|
+
env: options.env,
|
|
3645
|
+
local: localCredentials,
|
|
3646
|
+
projectUrl: project.server.url,
|
|
3647
|
+
projectTokenEnv: project.server.token_env
|
|
3648
|
+
});
|
|
3649
|
+
if ("code" in auth) {
|
|
3650
|
+
if (auth.code === "SERVER_URL_REQUIRED") {
|
|
3651
|
+
throw new PushWorkflowError("server_url is required; use --server-url, project.yaml server.url, or " + CREDENTIALS_HINT, 3, "SERVER_URL_REQUIRED");
|
|
3652
|
+
}
|
|
3653
|
+
const tokenEnv2 = options.tokenEnv ?? project.server.token_env;
|
|
3654
|
+
throw new PushWorkflowError("API token is unset; set " + tokenEnv2 + " or " + CREDENTIALS_HINT, 8, "TOKEN_INVALID");
|
|
3462
3655
|
}
|
|
3656
|
+
const serverUrl = auth.serverUrl;
|
|
3657
|
+
const token = auth.token;
|
|
3463
3658
|
const tokenEnv = options.tokenEnv ?? project.server.token_env;
|
|
3464
|
-
const token = options.env[tokenEnv];
|
|
3465
|
-
if (token === void 0 || token.trim() === "") {
|
|
3466
|
-
throw new PushWorkflowError("API token environment variable is unset", 8, "TOKEN_INVALID");
|
|
3467
|
-
}
|
|
3468
3659
|
if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
|
|
3469
3660
|
throw new PushWorkflowError("token_env is invalid", 3, "TOKEN_ENV_INVALID");
|
|
3470
3661
|
}
|
|
@@ -3477,7 +3668,7 @@ async function pushProject(options) {
|
|
|
3477
3668
|
if (parsedServerUrl.protocol !== "https:") {
|
|
3478
3669
|
throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
|
|
3479
3670
|
}
|
|
3480
|
-
const workflowPath =
|
|
3671
|
+
const workflowPath = join10(root, ".harness", "state", "local", "push-workflow.json");
|
|
3481
3672
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
3482
3673
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
3483
3674
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -3505,9 +3696,15 @@ async function pushProject(options) {
|
|
|
3505
3696
|
workflow.project_id = resolved.project_id;
|
|
3506
3697
|
await atomicWriteJson(workflowPath, workflow);
|
|
3507
3698
|
preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths, workflow.created_at);
|
|
3508
|
-
if (
|
|
3509
|
-
|
|
3699
|
+
if (!sensitiveScanSkip) {
|
|
3700
|
+
const reboundSkip = await resolveSensitiveScanSkip(preview, options);
|
|
3701
|
+
if (reboundSkip.cancelled === true) {
|
|
3702
|
+
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
3703
|
+
}
|
|
3704
|
+
sensitiveScanSkip = reboundSkip.skip;
|
|
3705
|
+
sensitiveScanSkipReason = reboundSkip.reason;
|
|
3510
3706
|
}
|
|
3707
|
+
assertPreviewAllowed(preview, sensitiveScanSkip);
|
|
3511
3708
|
}
|
|
3512
3709
|
const projectId = project.project.project_id;
|
|
3513
3710
|
if (projectId === null) {
|
|
@@ -3570,9 +3767,13 @@ async function pushProject(options) {
|
|
|
3570
3767
|
const finalized = await client.finalizeProposal(session.session_id, {
|
|
3571
3768
|
schema_version: 1,
|
|
3572
3769
|
manifest_sha256: proposalManifestHash,
|
|
3573
|
-
base_artifact_id: baseline.latest_artifact_id ?? null
|
|
3770
|
+
base_artifact_id: baseline.latest_artifact_id ?? null,
|
|
3771
|
+
...sensitiveScanSkip ? {
|
|
3772
|
+
sensitive_scan_skip: true,
|
|
3773
|
+
...sensitiveScanSkipReason === void 0 ? {} : { sensitive_scan_skip_reason: sensitiveScanSkipReason }
|
|
3774
|
+
} : {}
|
|
3574
3775
|
}, requestId, workflow.finalize_idempotency_key);
|
|
3575
|
-
await atomicWriteJson(
|
|
3776
|
+
await atomicWriteJson(join10(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
3576
3777
|
schema_version: 1,
|
|
3577
3778
|
request_id: requestId,
|
|
3578
3779
|
project_id: projectId,
|
|
@@ -3592,6 +3793,14 @@ async function pushProject(options) {
|
|
|
3592
3793
|
if (error.code === "STALE_PUSH") {
|
|
3593
3794
|
throw new PushWorkflowError("\u670D\u52A1\u7AEF\u5DF2\u6709\u66F4\u65B0\u7684\u63A8\u9001\uFF0C\u8BF7\u5148 git pull \u540E\u6267\u884C npx hunter-harness update \u518D\u63A8", 5, "STALE_PUSH");
|
|
3594
3795
|
}
|
|
3796
|
+
if (error.code === "SENSITIVE_CONTENT_BLOCKED") {
|
|
3797
|
+
const details = error.details;
|
|
3798
|
+
throw new PushWorkflowError(error.message, 6, "SENSITIVE_CONTENT_BLOCKED", details === null || typeof details !== "object" ? void 0 : {
|
|
3799
|
+
...typeof details.finding_count === "number" ? { finding_count: details.finding_count } : {},
|
|
3800
|
+
...typeof details.scanner_version === "string" ? { scanner_version: details.scanner_version } : {},
|
|
3801
|
+
...Array.isArray(details.findings) ? { findings: details.findings } : {}
|
|
3802
|
+
});
|
|
3803
|
+
}
|
|
3595
3804
|
const exitCode = error.status === 401 ? 8 : error.status === 409 ? 5 : 4;
|
|
3596
3805
|
throw new PushWorkflowError(error.message, exitCode, error.code);
|
|
3597
3806
|
}
|
|
@@ -3602,8 +3811,8 @@ async function pushProject(options) {
|
|
|
3602
3811
|
}
|
|
3603
3812
|
|
|
3604
3813
|
// ../core/dist/state/cleanup.js
|
|
3605
|
-
import { readFile as
|
|
3606
|
-
import { join as
|
|
3814
|
+
import { readFile as readFile10, readdir as readdir5, rm as rm5 } from "node:fs/promises";
|
|
3815
|
+
import { join as join11 } from "node:path";
|
|
3607
3816
|
function isSafeEntryName(name) {
|
|
3608
3817
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
3609
3818
|
}
|
|
@@ -3627,7 +3836,7 @@ async function cleanupProject(options) {
|
|
|
3627
3836
|
continue;
|
|
3628
3837
|
let journal;
|
|
3629
3838
|
try {
|
|
3630
|
-
journal = JSON.parse(await
|
|
3839
|
+
journal = JSON.parse(await readFile10(join11(layout.transactions, name, "journal.json"), "utf8"));
|
|
3631
3840
|
} catch {
|
|
3632
3841
|
continue;
|
|
3633
3842
|
}
|
|
@@ -3645,7 +3854,7 @@ async function cleanupProject(options) {
|
|
|
3645
3854
|
continue;
|
|
3646
3855
|
pruned.push(entry.id);
|
|
3647
3856
|
if (!options.dryRun) {
|
|
3648
|
-
await rm5(
|
|
3857
|
+
await rm5(join11(layout.transactions, entry.id), { recursive: true, force: true });
|
|
3649
3858
|
}
|
|
3650
3859
|
}
|
|
3651
3860
|
}
|
|
@@ -3654,7 +3863,7 @@ async function cleanupProject(options) {
|
|
|
3654
3863
|
continue;
|
|
3655
3864
|
removedCache.push(name);
|
|
3656
3865
|
if (!options.dryRun) {
|
|
3657
|
-
await rm5(
|
|
3866
|
+
await rm5(join11(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
3658
3867
|
}
|
|
3659
3868
|
}
|
|
3660
3869
|
return {
|
|
@@ -3665,11 +3874,11 @@ async function cleanupProject(options) {
|
|
|
3665
3874
|
}
|
|
3666
3875
|
|
|
3667
3876
|
// ../core/dist/skill/frontmatter.js
|
|
3668
|
-
import { parse as
|
|
3877
|
+
import { parse as parseYaml6 } from "yaml";
|
|
3669
3878
|
import { z as z14 } from "zod";
|
|
3670
3879
|
|
|
3671
3880
|
// ../core/dist/skill/fixer.js
|
|
3672
|
-
import { parse as
|
|
3881
|
+
import { parse as parseYaml7, stringify as stringifyYaml5 } from "yaml";
|
|
3673
3882
|
|
|
3674
3883
|
// ../core/dist/skill/agents.js
|
|
3675
3884
|
var AGENT_DESCRIPTORS = {
|
|
@@ -3708,10 +3917,10 @@ var AGENT_DESCRIPTORS = {
|
|
|
3708
3917
|
var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
|
|
3709
3918
|
|
|
3710
3919
|
// ../core/dist/transaction/recovery.js
|
|
3711
|
-
import { readFile as
|
|
3712
|
-
import { join as
|
|
3920
|
+
import { readFile as readFile11, readdir as readdir6, rm as rm6, stat as stat3 } from "node:fs/promises";
|
|
3921
|
+
import { join as join12 } from "node:path";
|
|
3713
3922
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
3714
|
-
const journal = JSON.parse(await
|
|
3923
|
+
const journal = JSON.parse(await readFile11(join12(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
3715
3924
|
if (journal.state === "committed") {
|
|
3716
3925
|
return { transactionId, status: "committed" };
|
|
3717
3926
|
}
|
|
@@ -3738,7 +3947,7 @@ async function listTransactions(projectRoot) {
|
|
|
3738
3947
|
const transactions = [];
|
|
3739
3948
|
for (const transactionId of names) {
|
|
3740
3949
|
try {
|
|
3741
|
-
const journal = JSON.parse(await
|
|
3950
|
+
const journal = JSON.parse(await readFile11(join12(root, transactionId, "journal.json"), "utf8"));
|
|
3742
3951
|
transactions.push({
|
|
3743
3952
|
transactionId,
|
|
3744
3953
|
kind: journal.kind,
|
|
@@ -3769,11 +3978,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
3769
3978
|
if (latest === void 0) {
|
|
3770
3979
|
throw new Error("no committed update transaction is available for rollback");
|
|
3771
3980
|
}
|
|
3772
|
-
const transactionRoot =
|
|
3773
|
-
const journal = JSON.parse(await
|
|
3774
|
-
const after = JSON.parse(await
|
|
3981
|
+
const transactionRoot = join12(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
3982
|
+
const journal = JSON.parse(await readFile11(join12(transactionRoot, "journal.json"), "utf8"));
|
|
3983
|
+
const after = JSON.parse(await readFile11(join12(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
3775
3984
|
for (const entry of after) {
|
|
3776
|
-
const target =
|
|
3985
|
+
const target = join12(projectRoot, entry.path);
|
|
3777
3986
|
const exists5 = await pathExists(target);
|
|
3778
3987
|
if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
|
|
3779
3988
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -3786,10 +3995,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
3786
3995
|
continue;
|
|
3787
3996
|
}
|
|
3788
3997
|
seen.add(snapshot.path);
|
|
3789
|
-
const target =
|
|
3998
|
+
const target = join12(projectRoot, snapshot.path);
|
|
3790
3999
|
const exists5 = await pathExists(target);
|
|
3791
4000
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
3792
|
-
const content = await
|
|
4001
|
+
const content = await readFile11(join12(transactionRoot, "before", snapshot.snapshot_name));
|
|
3793
4002
|
operations.push({
|
|
3794
4003
|
operation: exists5 ? "modify" : "add",
|
|
3795
4004
|
path: snapshot.path,
|
|
@@ -3818,7 +4027,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
3818
4027
|
if (!removable) {
|
|
3819
4028
|
continue;
|
|
3820
4029
|
}
|
|
3821
|
-
await rm6(
|
|
4030
|
+
await rm6(join12(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
3822
4031
|
recursive: true,
|
|
3823
4032
|
force: true
|
|
3824
4033
|
});
|
|
@@ -3850,9 +4059,9 @@ function managedBlockDirty(currentContent, managedBlockHash) {
|
|
|
3850
4059
|
}
|
|
3851
4060
|
|
|
3852
4061
|
// ../core/dist/update/update.js
|
|
3853
|
-
import { lstat as lstat3, readFile as
|
|
3854
|
-
import { join as
|
|
3855
|
-
import { parse as
|
|
4062
|
+
import { lstat as lstat3, readFile as readFile12, rm as rm7 } from "node:fs/promises";
|
|
4063
|
+
import { join as join13, resolve as resolve6 } from "node:path";
|
|
4064
|
+
import { parse as parseYaml8 } from "yaml";
|
|
3856
4065
|
var UpdateWorkflowError = class extends Error {
|
|
3857
4066
|
exitCode;
|
|
3858
4067
|
code;
|
|
@@ -3875,7 +4084,7 @@ async function pathExists2(path) {
|
|
|
3875
4084
|
}
|
|
3876
4085
|
}
|
|
3877
4086
|
async function optionalContent(path) {
|
|
3878
|
-
return await pathExists2(path) ?
|
|
4087
|
+
return await pathExists2(path) ? readFile12(path, "utf8") : null;
|
|
3879
4088
|
}
|
|
3880
4089
|
function manifestPayloadHash(manifest) {
|
|
3881
4090
|
const payload = { ...manifest };
|
|
@@ -3893,10 +4102,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
3893
4102
|
return null;
|
|
3894
4103
|
}
|
|
3895
4104
|
const hash = operation.content_sha256;
|
|
3896
|
-
const cacheRoot =
|
|
3897
|
-
const cachePath =
|
|
4105
|
+
const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4106
|
+
const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
3898
4107
|
if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
|
|
3899
|
-
return
|
|
4108
|
+
return readFile12(cachePath, "utf8");
|
|
3900
4109
|
}
|
|
3901
4110
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
3902
4111
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -3946,7 +4155,7 @@ async function updateProject(options) {
|
|
|
3946
4155
|
const root = resolve6(options.projectRoot);
|
|
3947
4156
|
let project;
|
|
3948
4157
|
try {
|
|
3949
|
-
project = projectConfigSchema.parse(
|
|
4158
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
|
|
3950
4159
|
} catch {
|
|
3951
4160
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
3952
4161
|
}
|
|
@@ -4031,8 +4240,8 @@ async function updateProject(options) {
|
|
|
4031
4240
|
skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
|
|
4032
4241
|
continue;
|
|
4033
4242
|
}
|
|
4034
|
-
const sourceContent = await optionalContent(
|
|
4035
|
-
const targetContent = target === source ? sourceContent : await optionalContent(
|
|
4243
|
+
const sourceContent = await optionalContent(join13(root, source));
|
|
4244
|
+
const targetContent = target === source ? sourceContent : await optionalContent(join13(root, target));
|
|
4036
4245
|
const incoming = blobs.get(operation) ?? null;
|
|
4037
4246
|
const incomingHash = contentHash(operation);
|
|
4038
4247
|
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
@@ -4092,7 +4301,7 @@ async function updateProject(options) {
|
|
|
4092
4301
|
dryRun: true
|
|
4093
4302
|
};
|
|
4094
4303
|
}
|
|
4095
|
-
await atomicWriteJson(
|
|
4304
|
+
await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4096
4305
|
const lock = await acquireProtocolLock(root, "update", { requestId });
|
|
4097
4306
|
try {
|
|
4098
4307
|
const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
|
|
@@ -4174,8 +4383,8 @@ async function updateProject(options) {
|
|
|
4174
4383
|
}
|
|
4175
4384
|
|
|
4176
4385
|
// src/config/init-config.ts
|
|
4177
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4178
|
-
import { readFile as
|
|
4386
|
+
import { isAbsolute as isAbsolute2, join as join14 } from "node:path";
|
|
4387
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
4179
4388
|
var InitConfigurationError = class extends Error {
|
|
4180
4389
|
exitCode;
|
|
4181
4390
|
code;
|
|
@@ -4256,9 +4465,9 @@ function hasOwn(record, key) {
|
|
|
4256
4465
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
4257
4466
|
let fileConfig = {};
|
|
4258
4467
|
if (flags.config !== void 0) {
|
|
4259
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
4468
|
+
const path = isAbsolute2(flags.config) ? flags.config : join14(cwd, flags.config);
|
|
4260
4469
|
try {
|
|
4261
|
-
fileConfig = JSON.parse(await
|
|
4470
|
+
fileConfig = JSON.parse(await readFile13(path, "utf8"));
|
|
4262
4471
|
} catch (error) {
|
|
4263
4472
|
throw new InitConfigurationError(
|
|
4264
4473
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -4340,20 +4549,20 @@ function serializeCliResult(result) {
|
|
|
4340
4549
|
}
|
|
4341
4550
|
|
|
4342
4551
|
// src/commands/refresh.ts
|
|
4343
|
-
import { readFile as
|
|
4344
|
-
import { join as
|
|
4345
|
-
import { parse as
|
|
4552
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4553
|
+
import { join as join15 } from "node:path";
|
|
4554
|
+
import { parse as parseYaml9 } from "yaml";
|
|
4346
4555
|
async function detectProject(root) {
|
|
4347
4556
|
let content;
|
|
4348
4557
|
try {
|
|
4349
|
-
content = await
|
|
4558
|
+
content = await readFile14(join15(root, ".harness", "project.yaml"), "utf8");
|
|
4350
4559
|
} catch (error) {
|
|
4351
4560
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4352
4561
|
return { status: "absent" };
|
|
4353
4562
|
}
|
|
4354
4563
|
throw error;
|
|
4355
4564
|
}
|
|
4356
|
-
const parsed = projectConfigSchema.safeParse(
|
|
4565
|
+
const parsed = projectConfigSchema.safeParse(parseYaml9(content));
|
|
4357
4566
|
if (!parsed.success) {
|
|
4358
4567
|
return { status: "invalid" };
|
|
4359
4568
|
}
|
|
@@ -4733,82 +4942,192 @@ async function runCleanup(options, dependencies) {
|
|
|
4733
4942
|
}
|
|
4734
4943
|
|
|
4735
4944
|
// src/commands/push.ts
|
|
4945
|
+
function formatFindings(details) {
|
|
4946
|
+
if (details?.findings === void 0 || details.findings.length === 0) {
|
|
4947
|
+
return "";
|
|
4948
|
+
}
|
|
4949
|
+
const lines = [
|
|
4950
|
+
"\u654F\u611F\u4FE1\u606F\u626B\u63CF\u53D1\u73B0 " + details.findings.length + " \u4E2A\u95EE\u9898\uFF1A"
|
|
4951
|
+
];
|
|
4952
|
+
for (const finding of details.findings) {
|
|
4953
|
+
lines.push(
|
|
4954
|
+
" - " + finding.path + ":" + finding.line + " " + finding.rule_id + " (" + finding.severity + (finding.overridable ? ", \u53EF\u8986\u76D6" : ", \u4E0D\u53EF\u8986\u76D6") + ")"
|
|
4955
|
+
);
|
|
4956
|
+
}
|
|
4957
|
+
return lines.join("\n") + "\n";
|
|
4958
|
+
}
|
|
4959
|
+
function errorPayload(error) {
|
|
4960
|
+
const exitCode = error instanceof PushWorkflowError ? error.exitCode : 1;
|
|
4961
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4962
|
+
const code = error instanceof PushWorkflowError ? error.code : "GENERAL_FAILURE";
|
|
4963
|
+
if (error instanceof PushWorkflowError && error.details !== void 0) {
|
|
4964
|
+
return { exitCode, message, code, details: error.details };
|
|
4965
|
+
}
|
|
4966
|
+
return { exitCode, message, code };
|
|
4967
|
+
}
|
|
4968
|
+
async function promptForCredentials(dependencies, missing) {
|
|
4969
|
+
const serverUrl = missing === "token" ? void 0 : (await dependencies.prompt("\u670D\u52A1\u7AEF URL (https://...): ")).trim();
|
|
4970
|
+
const token = missing === "url" ? void 0 : (await dependencies.prompt("API Token: ")).trim();
|
|
4971
|
+
if ((missing === "url" || missing === "both") && (serverUrl === void 0 || serverUrl === "")) {
|
|
4972
|
+
return null;
|
|
4973
|
+
}
|
|
4974
|
+
if ((missing === "token" || missing === "both") && (token === void 0 || token === "")) {
|
|
4975
|
+
return null;
|
|
4976
|
+
}
|
|
4977
|
+
if (serverUrl !== void 0 && serverUrl !== "") {
|
|
4978
|
+
try {
|
|
4979
|
+
assertHttpsServerUrl(serverUrl);
|
|
4980
|
+
} catch (error) {
|
|
4981
|
+
if (error instanceof InvalidCredentialsError) {
|
|
4982
|
+
dependencies.stderr(error.message + "\n");
|
|
4983
|
+
return null;
|
|
4984
|
+
}
|
|
4985
|
+
throw error;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
return {
|
|
4989
|
+
...serverUrl === void 0 ? {} : { serverUrl },
|
|
4990
|
+
...token === void 0 ? {} : { token }
|
|
4991
|
+
};
|
|
4992
|
+
}
|
|
4736
4993
|
async function runPush(options, dependencies) {
|
|
4737
4994
|
const requestId = uuidV7();
|
|
4738
4995
|
if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
|
|
4739
4996
|
dependencies.stderr("\u975E\u4EA4\u4E92\u6A21\u5F0F\u63A8\u9001\u9700\u8981 --yes\n");
|
|
4740
4997
|
return 2;
|
|
4741
4998
|
}
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4999
|
+
if (options.nonInteractive === true && options.skipSensitiveScan === true && options.yes !== true) {
|
|
5000
|
+
dependencies.stderr("\u975E\u4EA4\u4E92\u8DF3\u8FC7\u654F\u611F\u626B\u63CF\u9700\u8981 --yes\n");
|
|
5001
|
+
return 2;
|
|
5002
|
+
}
|
|
5003
|
+
async function executePush(attempt = 0) {
|
|
5004
|
+
try {
|
|
5005
|
+
const result = await pushProject({
|
|
5006
|
+
projectRoot: dependencies.cwd,
|
|
5007
|
+
resourcesRoot: dependencies.resourcesRoot,
|
|
5008
|
+
...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
|
|
5009
|
+
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
5010
|
+
env: dependencies.env,
|
|
5011
|
+
dryRun: options.dryRun === true,
|
|
5012
|
+
fetch: dependencies.fetch,
|
|
5013
|
+
...options.skipSensitiveScan === true ? { sensitiveScanSkip: true } : {},
|
|
5014
|
+
...options.yes === true || options.nonInteractive === true ? {} : { confirmProposal: async () => {
|
|
5015
|
+
const answer = await dependencies.prompt("Create this proposal? [y/N]: ");
|
|
5016
|
+
return /^(?:y|yes)$/i.test(answer.trim());
|
|
5017
|
+
} },
|
|
5018
|
+
...options.yes === true || options.nonInteractive === true || options.skipSensitiveScan === true ? {} : { confirmSensitiveScanSkip: async (preview) => {
|
|
5019
|
+
dependencies.stderr(formatFindings({
|
|
5020
|
+
findings: preview.security.findings.filter((finding) => finding.disposition === "blocked").map((finding) => ({
|
|
5021
|
+
path: finding.path,
|
|
5022
|
+
rule_id: finding.rule_id,
|
|
5023
|
+
severity: finding.severity,
|
|
5024
|
+
overridable: finding.overridable,
|
|
5025
|
+
fingerprint: finding.fingerprint,
|
|
5026
|
+
line: finding.line,
|
|
5027
|
+
column: finding.column
|
|
5028
|
+
})),
|
|
5029
|
+
finding_count: preview.security.findings.filter(
|
|
5030
|
+
(finding) => finding.disposition === "blocked"
|
|
5031
|
+
).length,
|
|
5032
|
+
scanner_version: preview.security.scanner_version
|
|
5033
|
+
}));
|
|
5034
|
+
const answer = await dependencies.prompt(
|
|
5035
|
+
"\u654F\u611F\u626B\u63CF\u5DF2\u963B\u65AD\u63A8\u9001\u3002\u662F\u5426\u663E\u5F0F\u8DF3\u8FC7\u5E76\u7EE7\u7EED\uFF1F[y/N]: "
|
|
5036
|
+
);
|
|
5037
|
+
if (!/^(?:y|yes)$/i.test(answer.trim())) {
|
|
5038
|
+
return "cancelled";
|
|
5039
|
+
}
|
|
5040
|
+
const reasonAnswer = await dependencies.prompt("\u8DF3\u8FC7\u539F\u56E0\uFF08\u53EF\u9009\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09: ");
|
|
5041
|
+
const reason = reasonAnswer.trim();
|
|
5042
|
+
return reason === "" ? { skip: true } : { skip: true, reason };
|
|
5043
|
+
} }
|
|
5044
|
+
});
|
|
5045
|
+
if ("cancelled" in result && result.cancelled === true) {
|
|
5046
|
+
return 2;
|
|
5047
|
+
}
|
|
5048
|
+
const items = result.preview.operations.map((operation) => ({
|
|
5049
|
+
path: operation.operation === "rename" ? operation.to_path : operation.path,
|
|
5050
|
+
operation: operation.operation,
|
|
5051
|
+
file_kind: operation.file_kind,
|
|
5052
|
+
status: options.dryRun === true ? "planned" : "submitted",
|
|
5053
|
+
reason: null,
|
|
5054
|
+
size_bytes: "size_bytes" in operation ? operation.size_bytes : 0,
|
|
5055
|
+
content_sha256: "content_sha256" in operation ? operation.content_sha256 : operation.tombstone.previous_sha256
|
|
5056
|
+
}));
|
|
5057
|
+
const output = {
|
|
4794
5058
|
schema_version: 1,
|
|
4795
5059
|
command: "push",
|
|
4796
5060
|
request_id: requestId,
|
|
4797
5061
|
dry_run: options.dryRun === true,
|
|
4798
|
-
ok:
|
|
4799
|
-
exit_code:
|
|
4800
|
-
project_id:
|
|
4801
|
-
summary: {
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
5062
|
+
ok: true,
|
|
5063
|
+
exit_code: 0,
|
|
5064
|
+
project_id: result.projectId,
|
|
5065
|
+
summary: {
|
|
5066
|
+
planned: result.preview.operations.length,
|
|
5067
|
+
submitted: options.dryRun === true ? 0 : result.preview.operations.length,
|
|
5068
|
+
skipped: result.preview.skipped.length,
|
|
5069
|
+
findings: result.preview.security.findings.length
|
|
5070
|
+
},
|
|
5071
|
+
items,
|
|
5072
|
+
warnings: result.preview.skipped,
|
|
5073
|
+
errors: []
|
|
5074
|
+
};
|
|
5075
|
+
dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + result.artifactId + " (proposal " + result.proposalId + ").\n");
|
|
5076
|
+
return 0;
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
if (attempt === 0 && options.nonInteractive !== true && options.dryRun !== true && error instanceof PushWorkflowError && (error.code === "SERVER_URL_REQUIRED" || error.code === "TOKEN_INVALID")) {
|
|
5079
|
+
const missing = error.code === "SERVER_URL_REQUIRED" ? "url" : error.code === "TOKEN_INVALID" ? "token" : "both";
|
|
5080
|
+
dependencies.stderr(
|
|
5081
|
+
error.message + "\n\u53EF\u5728\u4E0B\u65B9\u5F55\u5165\u5E76\u5199\u5165 .harness/credentials.local.yaml\u3002\n"
|
|
5082
|
+
);
|
|
5083
|
+
const entered = await promptForCredentials(dependencies, missing);
|
|
5084
|
+
if (entered !== null) {
|
|
5085
|
+
const existing = await readLocalCredentials(dependencies.cwd);
|
|
5086
|
+
try {
|
|
5087
|
+
await writeLocalCredentials(dependencies.cwd, mergeLocalCredentials(existing, {
|
|
5088
|
+
...entered.serverUrl === void 0 ? {} : { server_url: entered.serverUrl },
|
|
5089
|
+
...entered.token === void 0 ? {} : { token: entered.token }
|
|
5090
|
+
}));
|
|
5091
|
+
} catch (error2) {
|
|
5092
|
+
if (error2 instanceof InvalidCredentialsError) {
|
|
5093
|
+
dependencies.stderr(error2.message + "\n");
|
|
5094
|
+
return 3;
|
|
5095
|
+
}
|
|
5096
|
+
throw error2;
|
|
5097
|
+
}
|
|
5098
|
+
await ensureCredentialsGitignore(dependencies.cwd);
|
|
5099
|
+
dependencies.stderr(
|
|
5100
|
+
"\u5DF2\u5199\u5165 .harness/credentials.local.yaml\uFF1Bupload session \u7531 CLI \u81EA\u52A8\u7BA1\u7406\uFF0C\u65E0\u9700\u624B\u914D\u3002\n"
|
|
5101
|
+
);
|
|
5102
|
+
return executePush(attempt + 1);
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
const payload = errorPayload(error);
|
|
5106
|
+
dependencies.stderr(payload.message + "\n");
|
|
5107
|
+
dependencies.stderr(formatFindings(payload.details));
|
|
5108
|
+
if (options.json === true) {
|
|
5109
|
+
dependencies.stdout(serializeCliResult({
|
|
5110
|
+
schema_version: 1,
|
|
5111
|
+
command: "push",
|
|
5112
|
+
request_id: requestId,
|
|
5113
|
+
dry_run: options.dryRun === true,
|
|
5114
|
+
ok: false,
|
|
5115
|
+
exit_code: payload.exitCode,
|
|
5116
|
+
project_id: null,
|
|
5117
|
+
summary: { planned: 0, submitted: 0 },
|
|
5118
|
+
items: [],
|
|
5119
|
+
warnings: [],
|
|
5120
|
+
errors: [{
|
|
5121
|
+
code: payload.code,
|
|
5122
|
+
message: payload.message,
|
|
5123
|
+
...payload.details === void 0 ? {} : { details: payload.details }
|
|
5124
|
+
}]
|
|
5125
|
+
}));
|
|
5126
|
+
}
|
|
5127
|
+
return payload.exitCode;
|
|
4809
5128
|
}
|
|
4810
|
-
return exitCode;
|
|
4811
5129
|
}
|
|
5130
|
+
return executePush();
|
|
4812
5131
|
}
|
|
4813
5132
|
|
|
4814
5133
|
// src/commands/update.ts
|
|
@@ -4904,7 +5223,7 @@ async function runUpdate(options, dependencies) {
|
|
|
4904
5223
|
|
|
4905
5224
|
// src/commands/recovery.ts
|
|
4906
5225
|
import { stat as stat4 } from "node:fs/promises";
|
|
4907
|
-
import { join as
|
|
5226
|
+
import { join as join16 } from "node:path";
|
|
4908
5227
|
async function exists4(path) {
|
|
4909
5228
|
try {
|
|
4910
5229
|
await stat4(path);
|
|
@@ -4964,7 +5283,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
4964
5283
|
return 5;
|
|
4965
5284
|
}
|
|
4966
5285
|
}
|
|
4967
|
-
const initialized = await exists4(
|
|
5286
|
+
const initialized = await exists4(join16(dependencies.cwd, ".harness", "project.yaml"));
|
|
4968
5287
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
4969
5288
|
return null;
|
|
4970
5289
|
}
|
|
@@ -5007,8 +5326,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
5007
5326
|
}
|
|
5008
5327
|
|
|
5009
5328
|
// src/workflow-data/resolve.ts
|
|
5010
|
-
import { cp, mkdir as mkdir4, readdir as readdir7, readFile as
|
|
5011
|
-
import { dirname as dirname4, join as
|
|
5329
|
+
import { cp, mkdir as mkdir4, readdir as readdir7, readFile as readFile15, rm as rm8, stat as stat5 } from "node:fs/promises";
|
|
5330
|
+
import { dirname as dirname4, join as join17, relative as relative2 } from "node:path";
|
|
5012
5331
|
import { fileURLToPath } from "node:url";
|
|
5013
5332
|
var WorkflowDataResolutionError = class extends Error {
|
|
5014
5333
|
code;
|
|
@@ -5045,14 +5364,14 @@ async function listFilesRecursive(root, base = root) {
|
|
|
5045
5364
|
const entries = await readdir7(root, { withFileTypes: true });
|
|
5046
5365
|
const files = [];
|
|
5047
5366
|
for (const entry of entries) {
|
|
5048
|
-
const absolute =
|
|
5367
|
+
const absolute = join17(root, entry.name);
|
|
5049
5368
|
if (entry.isDirectory()) {
|
|
5050
5369
|
files.push(...await listFilesRecursive(absolute, base));
|
|
5051
5370
|
continue;
|
|
5052
5371
|
}
|
|
5053
5372
|
if (!entry.isFile()) continue;
|
|
5054
5373
|
const rel = relative2(base, absolute).replaceAll("\\", "/");
|
|
5055
|
-
files.push({ path: rel, content: await
|
|
5374
|
+
files.push({ path: rel, content: await readFile15(absolute, "utf8") });
|
|
5056
5375
|
}
|
|
5057
5376
|
return files;
|
|
5058
5377
|
}
|
|
@@ -5065,7 +5384,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
5065
5384
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
5066
5385
|
const expected = manifest.content_sha256;
|
|
5067
5386
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
5068
|
-
const harnessRoot =
|
|
5387
|
+
const harnessRoot = join17(resourcesRoot, "harness");
|
|
5069
5388
|
if (!await pathExists3(harnessRoot)) {
|
|
5070
5389
|
throw new WorkflowDataResolutionError(
|
|
5071
5390
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -5084,31 +5403,31 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
5084
5403
|
}
|
|
5085
5404
|
}
|
|
5086
5405
|
async function monorepoResourcesRoot() {
|
|
5406
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
5087
5407
|
const candidates = [
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
fileURLToPath(new URL("../../../../resources", import.meta.url))
|
|
5408
|
+
join17(here, "../../../../resources"),
|
|
5409
|
+
join17(here, "../../../../../resources")
|
|
5091
5410
|
];
|
|
5092
5411
|
for (const candidate of candidates) {
|
|
5093
|
-
if (await pathExists3(
|
|
5412
|
+
if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
|
|
5094
5413
|
}
|
|
5095
5414
|
return null;
|
|
5096
5415
|
}
|
|
5097
5416
|
async function siblingWorkflowPackage(cwd) {
|
|
5098
5417
|
const candidates = [
|
|
5099
|
-
|
|
5100
|
-
|
|
5418
|
+
join17(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
5419
|
+
join17(dirname4(fileURLToPath(import.meta.url)), "..", "..", "workflow-data-harness")
|
|
5101
5420
|
];
|
|
5102
5421
|
for (const candidate of candidates) {
|
|
5103
|
-
if (await pathExists3(
|
|
5422
|
+
if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
|
|
5104
5423
|
}
|
|
5105
5424
|
return null;
|
|
5106
5425
|
}
|
|
5107
5426
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
5108
|
-
const manifestPath =
|
|
5427
|
+
const manifestPath = join17(cacheRoot, "package.json");
|
|
5109
5428
|
if (!await pathExists3(manifestPath)) return null;
|
|
5110
5429
|
try {
|
|
5111
|
-
const pkg = JSON.parse(await
|
|
5430
|
+
const pkg = JSON.parse(await readFile15(manifestPath, "utf8"));
|
|
5112
5431
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
5113
5432
|
} catch {
|
|
5114
5433
|
return null;
|
|
@@ -5131,12 +5450,12 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
5131
5450
|
}
|
|
5132
5451
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
5133
5452
|
await mkdir4(cacheRoot, { recursive: true });
|
|
5134
|
-
const staging =
|
|
5453
|
+
const staging = join17(cacheRoot, ".extract");
|
|
5135
5454
|
await rm8(staging, { recursive: true, force: true });
|
|
5136
5455
|
await mkdir4(staging, { recursive: true });
|
|
5137
5456
|
await extract(packageSpec, staging);
|
|
5138
|
-
const packageDir = await pathExists3(
|
|
5139
|
-
if (!await pathExists3(
|
|
5457
|
+
const packageDir = await pathExists3(join17(staging, "harness", "manifests")) ? staging : join17(staging, "package");
|
|
5458
|
+
if (!await pathExists3(join17(packageDir, "harness", "manifests"))) {
|
|
5140
5459
|
throw new WorkflowDataResolutionError(
|
|
5141
5460
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
5142
5461
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -5165,8 +5484,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
5165
5484
|
const packageName = workflowPackageName(family, options.env);
|
|
5166
5485
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
5167
5486
|
const cacheKey = packageSpec.replace("/", "+");
|
|
5168
|
-
const cacheRoot =
|
|
5169
|
-
if (await pathExists3(
|
|
5487
|
+
const cacheRoot = join17(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
5488
|
+
if (await pathExists3(join17(cacheRoot, "harness", "manifests"))) {
|
|
5170
5489
|
if (version === "latest") {
|
|
5171
5490
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
5172
5491
|
if (stale) {
|
|
@@ -5212,9 +5531,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
5212
5531
|
return `\u65E0\u6CD5\u83B7\u53D6\u5DE5\u4F5C\u6D41\u6570\u636E\u5305 ${packageSpec}\uFF1A\u672C\u5730\u7F13\u5B58\u4E0D\u5B58\u5728\u4E14\u83B7\u53D6\u5931\u8D25\u3002\u53EF${hintRoot}\u3002\u539F\u56E0\uFF1A${code !== "" ? code + " " : ""}${detail}`;
|
|
5213
5532
|
}
|
|
5214
5533
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
5215
|
-
const manifestPath =
|
|
5534
|
+
const manifestPath = join17(resourcesRoot, "hunter-workflow-family.json");
|
|
5216
5535
|
if (!await pathExists3(manifestPath)) return {};
|
|
5217
|
-
return JSON.parse(await
|
|
5536
|
+
return JSON.parse(await readFile15(manifestPath, "utf8"));
|
|
5218
5537
|
}
|
|
5219
5538
|
|
|
5220
5539
|
// src/bin.ts
|
|
@@ -5283,7 +5602,7 @@ async function runCli(argv, overrides = {}) {
|
|
|
5283
5602
|
dependencies
|
|
5284
5603
|
);
|
|
5285
5604
|
});
|
|
5286
|
-
addCommonOptions(program.command("push")).description("\u521B\u5EFA\u53D7\u6CBB\u7406\u7684\u53D8\u66F4\u63D0\u6848").action(async (options) => {
|
|
5605
|
+
addCommonOptions(program.command("push")).description("\u521B\u5EFA\u53D7\u6CBB\u7406\u7684\u53D8\u66F4\u63D0\u6848").option("--skip-sensitive-scan", "\u663E\u5F0F\u8DF3\u8FC7\u654F\u611F\u626B\u63CF\u963B\u65AD\uFF08\u975E\u4EA4\u4E92\u9700\u914D\u5408 --yes\uFF09").action(async (options) => {
|
|
5287
5606
|
exitCode = await runPush({ ...program.opts(), ...options }, dependencies);
|
|
5288
5607
|
});
|
|
5289
5608
|
addCommonOptions(program.command("cleanup")).description("\u6E05\u7406\u5DF2\u5B8C\u6210\u4E8B\u52A1\u548C\u8FC7\u671F\u670D\u52A1\u7AEF\u7F13\u5B58").action(async (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hunter-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Local-first, server-governed agent harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"dist/bin.js"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "tsc -p tsconfig.json && npm run bundle",
|
|
18
|
-
"bundle": "esbuild src/bin.ts --bundle --platform=node --format=esm --target=node24 --external:commander --external:yaml --external:zod --external:pacote --outfile=dist/bin.js",
|
|
19
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
17
|
+
"build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && npm run bundle",
|
|
18
|
+
"bundle": "node ../../node_modules/esbuild/bin/esbuild src/bin.ts --bundle --platform=node --format=esm --target=node24 --external:commander --external:yaml --external:zod --external:pacote --outfile=dist/bin.js",
|
|
19
|
+
"typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
|
|
20
20
|
"prepack": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|