@alint-js/core 0.0.38 → 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.
- package/README.md +33 -0
- package/dist/agent.d.mts +1 -1
- package/dist/{define-CnBawoAI.d.mts → define-8BzWDcPs.d.mts} +1 -1
- package/dist/dsl.d.mts +3 -3
- package/dist/index.d.mts +59 -20
- package/dist/index.mjs +1687 -787
- package/dist/structured-output.d.mts +1 -1
- package/dist/{types-m5T_UL76.d.mts → types-DKylUrAn.d.mts} +17 -4
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,15 @@ import { n as definePlugin, r as defineRule, t as defineConfig } from "./define-
|
|
|
3
3
|
import { a as sliceRange, i as sliceLines, n as createSourceFile, r as createSourceRuntime, t as extractJsSourceTargets } from "./extract-CIWssxNK.mjs";
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
5
|
import { isAbsolute, relative, resolve } from "pathe";
|
|
6
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
7
|
-
import { dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
8
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { access, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
7
|
+
import { basename, dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
9
8
|
import process, { cwd } from "node:process";
|
|
10
|
-
import {
|
|
9
|
+
import { Buffer } from "node:buffer";
|
|
11
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
11
|
import { statSync } from "node:fs";
|
|
12
|
+
import { array, boolean, check, custom, finite, getDotPath, literal, number, object, optional, parse, pipe, record, safeParse, string, union, unknown } from "valibot";
|
|
13
13
|
import { errorMessageFrom } from "@moeru/std/error";
|
|
14
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
15
|
//#region src/config/config-array.ts
|
|
15
16
|
const effectiveBasePathSymbol = Symbol("effectiveBasePath");
|
|
16
17
|
const inheritedMatcherScopesSymbol = Symbol("inheritedMatcherScopes");
|
|
@@ -253,7 +254,7 @@ function withLanguage$1(file, language) {
|
|
|
253
254
|
}
|
|
254
255
|
//#endregion
|
|
255
256
|
//#region src/core/languages/registry.ts
|
|
256
|
-
function createLanguageRegistry() {
|
|
257
|
+
function createLanguageRegistry$1() {
|
|
257
258
|
return {
|
|
258
259
|
byExtension: /* @__PURE__ */ new Map(),
|
|
259
260
|
languages: /* @__PURE__ */ new Map()
|
|
@@ -305,7 +306,10 @@ function withLanguage(file, language) {
|
|
|
305
306
|
//#endregion
|
|
306
307
|
//#region src/core/languages/resolve.ts
|
|
307
308
|
function resolveLanguage(file, registry, options) {
|
|
308
|
-
|
|
309
|
+
return resolveLanguageForPath(file.path, registry, options);
|
|
310
|
+
}
|
|
311
|
+
function resolveLanguageForPath(filePath, registry, options) {
|
|
312
|
+
const languageName = options.language ?? options.processedLanguage ?? registry.byExtension.get(extname(filePath)) ?? "text/plain";
|
|
309
313
|
const language = registry.languages.get(languageName);
|
|
310
314
|
if (!language) throw new Error(`Unknown language "${languageName}".`);
|
|
311
315
|
return language;
|
|
@@ -313,232 +317,172 @@ function resolveLanguage(file, registry, options) {
|
|
|
313
317
|
//#endregion
|
|
314
318
|
//#region src/core/languages/index.ts
|
|
315
319
|
function createBuiltInLanguageRegistry() {
|
|
316
|
-
const registry = createLanguageRegistry();
|
|
320
|
+
const registry = createLanguageRegistry$1();
|
|
317
321
|
registerLanguage(registry, textLanguage);
|
|
318
322
|
registerLanguage(registry, javascriptLanguage);
|
|
319
323
|
registerLanguage(registry, typescriptLanguage);
|
|
320
324
|
return registry;
|
|
321
325
|
}
|
|
322
326
|
//#endregion
|
|
323
|
-
//#region
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const normalizedEntry = normalizeRuleConfigEntry(entry);
|
|
338
|
-
const severity = normalizedEntry.severity;
|
|
339
|
-
if (severity === "off") continue;
|
|
340
|
-
enabledRules.push({
|
|
341
|
-
id,
|
|
342
|
-
localId: localIds.get(id) ?? id,
|
|
343
|
-
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
344
|
-
rule,
|
|
345
|
-
severity
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
enabledRules,
|
|
350
|
-
rules
|
|
327
|
+
//#region package.json
|
|
328
|
+
var version = "0.1.1";
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/core/hash.ts
|
|
331
|
+
function createStableHasher() {
|
|
332
|
+
const hash = createHash("sha256");
|
|
333
|
+
const api = {
|
|
334
|
+
digest: () => hash.digest("hex"),
|
|
335
|
+
update: (value) => {
|
|
336
|
+
const serialized = stableStringify(value);
|
|
337
|
+
hash.update(`${serialized.length}:`);
|
|
338
|
+
hash.update(serialized);
|
|
339
|
+
return api;
|
|
340
|
+
}
|
|
351
341
|
};
|
|
342
|
+
return api;
|
|
352
343
|
}
|
|
353
|
-
function
|
|
354
|
-
return
|
|
355
|
-
const path = getDotPath(issue);
|
|
356
|
-
return path === null ? `"${index}": ${issue.message}` : `"${index}.${path}": ${issue.message}`;
|
|
357
|
-
}).join("; ");
|
|
358
|
-
}
|
|
359
|
-
function isRuleConfigTuple(entry) {
|
|
360
|
-
return Array.isArray(entry);
|
|
361
|
-
}
|
|
362
|
-
function normalizeRuleConfigEntry(entry) {
|
|
363
|
-
if (isRuleConfigTuple(entry)) {
|
|
364
|
-
const [severity = "warn", ...options] = entry;
|
|
365
|
-
return {
|
|
366
|
-
options,
|
|
367
|
-
severity
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
options: [],
|
|
372
|
-
severity: entry ?? "warn"
|
|
373
|
-
};
|
|
344
|
+
function hashText(text) {
|
|
345
|
+
return createHash("sha256").update(text).digest("hex");
|
|
374
346
|
}
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
if (result.success) return result.output;
|
|
378
|
-
if (value === void 0) {
|
|
379
|
-
const emptyObjectResult = safeParse(schema, {});
|
|
380
|
-
if (emptyObjectResult.success) return emptyObjectResult.output;
|
|
381
|
-
}
|
|
382
|
-
throw new TypeError(`Invalid options for rule "${id}": ${formatRuleOptionIssues(index, result.issues)}`);
|
|
347
|
+
function stableHash(value) {
|
|
348
|
+
return hashText(stableStringify(value));
|
|
383
349
|
}
|
|
384
|
-
function
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
350
|
+
function stableStringify(value) {
|
|
351
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
352
|
+
if (typeof value === "object" && value !== null) {
|
|
353
|
+
const record = value;
|
|
354
|
+
return `{${Object.keys(record).sort().flatMap((key) => {
|
|
355
|
+
const property = record[key];
|
|
356
|
+
return property === void 0 ? [] : [`${JSON.stringify(key)}:${stableStringify(property)}`];
|
|
357
|
+
}).join(",")}}`;
|
|
388
358
|
}
|
|
389
|
-
|
|
390
|
-
return schema.map((optionSchema, index) => parseRuleOption(id, index, optionSchema, options[index]));
|
|
359
|
+
return JSON.stringify(value);
|
|
391
360
|
}
|
|
392
361
|
//#endregion
|
|
393
|
-
//#region src/
|
|
394
|
-
|
|
395
|
-
const candidates = flattenModels(registry);
|
|
396
|
-
const ruleId = options.ruleId ?? "<unknown>";
|
|
397
|
-
if (options.request !== void 0) {
|
|
398
|
-
const request = options.request;
|
|
399
|
-
const candidate = candidates.find((candidate) => matchesRequest(candidate, request));
|
|
400
|
-
if (candidate === void 0) throw new Error(`Unknown model "${request}".`);
|
|
401
|
-
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
402
|
-
return toResolvedModel(candidate, options.requirement);
|
|
403
|
-
}
|
|
404
|
-
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
405
|
-
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
406
|
-
return toResolvedModel(candidate, options.requirement);
|
|
407
|
-
}
|
|
408
|
-
function flattenModels(registry) {
|
|
409
|
-
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
410
|
-
model,
|
|
411
|
-
provider
|
|
412
|
-
})));
|
|
413
|
-
}
|
|
414
|
-
function matchesRequest(candidate, request) {
|
|
415
|
-
return [
|
|
416
|
-
candidate.model.id,
|
|
417
|
-
candidate.model.name,
|
|
418
|
-
...candidate.model.aliases ?? []
|
|
419
|
-
].filter((name) => name !== void 0).some((name) => name === request || `${candidate.provider.id}/${name}` === request);
|
|
420
|
-
}
|
|
421
|
-
function preferSize(candidates, requirement) {
|
|
422
|
-
if (requirement?.size === void 0) return candidates[0];
|
|
423
|
-
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
424
|
-
}
|
|
425
|
-
function satisfiesHardRequirements(model, requirement) {
|
|
426
|
-
if (requirement === void 0) return true;
|
|
427
|
-
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
428
|
-
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
429
|
-
return true;
|
|
430
|
-
}
|
|
431
|
-
function toResolvedModel(candidate, requirement) {
|
|
432
|
-
const { model, provider } = candidate;
|
|
433
|
-
return {
|
|
434
|
-
aliases: [...model.aliases ?? []],
|
|
435
|
-
capabilities: [...model.capabilities ?? []],
|
|
436
|
-
contextWindow: model.contextWindow,
|
|
437
|
-
id: model.id,
|
|
438
|
-
name: model.name ?? model.id,
|
|
439
|
-
params: {
|
|
440
|
-
...model.defaultParams ?? {},
|
|
441
|
-
...requirement?.params ?? {}
|
|
442
|
-
},
|
|
443
|
-
provider: {
|
|
444
|
-
endpoint: provider.endpoint,
|
|
445
|
-
headers: { ...provider.headers ?? {} },
|
|
446
|
-
id: provider.id,
|
|
447
|
-
type: provider.type
|
|
448
|
-
},
|
|
449
|
-
size: model.size
|
|
450
|
-
};
|
|
451
|
-
}
|
|
362
|
+
//#region src/core/cache/types.ts
|
|
363
|
+
const CACHE_MAGIC = "ALINT_CACHE";
|
|
452
364
|
//#endregion
|
|
453
|
-
//#region src/core/cache.ts
|
|
454
|
-
const CACHE_SCHEMA_VERSION = 1;
|
|
365
|
+
//#region src/core/cache/store.ts
|
|
455
366
|
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
367
|
+
const FiniteNumberSchema = pipe(number(), finite());
|
|
368
|
+
const PositionSchema = object({
|
|
369
|
+
column: FiniteNumberSchema,
|
|
370
|
+
line: FiniteNumberSchema
|
|
371
|
+
});
|
|
372
|
+
const SourceLocationSchema = object({
|
|
373
|
+
end: PositionSchema,
|
|
374
|
+
start: PositionSchema
|
|
375
|
+
});
|
|
376
|
+
const DiagnosticLocationSchema = object({
|
|
377
|
+
end: optional(PositionSchema),
|
|
378
|
+
start: PositionSchema
|
|
379
|
+
});
|
|
380
|
+
const SourceRangeSchema = object({
|
|
381
|
+
end: FiniteNumberSchema,
|
|
382
|
+
start: FiniteNumberSchema
|
|
383
|
+
});
|
|
384
|
+
const DiagnosticSchema = object({
|
|
385
|
+
cached: optional(boolean()),
|
|
386
|
+
evidence: optional(custom(isJsonValue)),
|
|
387
|
+
filePath: string(),
|
|
388
|
+
loc: optional(DiagnosticLocationSchema),
|
|
389
|
+
message: string(),
|
|
390
|
+
model: optional(object({
|
|
391
|
+
providerId: string(),
|
|
392
|
+
requested: optional(string()),
|
|
393
|
+
resolvedId: string()
|
|
394
|
+
})),
|
|
395
|
+
ruleId: string(),
|
|
396
|
+
severity: union([literal("error"), literal("warn")])
|
|
397
|
+
});
|
|
398
|
+
const UsageSchema = object({
|
|
399
|
+
filePath: optional(string()),
|
|
400
|
+
inputTokens: optional(FiniteNumberSchema),
|
|
401
|
+
metadata: optional(custom(isJsonValue)),
|
|
402
|
+
modelId: string(),
|
|
403
|
+
outputTokens: optional(FiniteNumberSchema),
|
|
404
|
+
providerId: string(),
|
|
405
|
+
ruleId: string(),
|
|
406
|
+
totalTokens: optional(FiniteNumberSchema)
|
|
407
|
+
});
|
|
408
|
+
const CacheEntrySchema = object({
|
|
409
|
+
diagnostics: array(DiagnosticSchema),
|
|
410
|
+
fingerprint: object({
|
|
411
|
+
configHash: string(),
|
|
412
|
+
modelHash: string(),
|
|
413
|
+
ruleHash: string(),
|
|
414
|
+
targetHash: string()
|
|
415
|
+
}),
|
|
416
|
+
target: object({
|
|
417
|
+
hash: string(),
|
|
418
|
+
identity: string(),
|
|
419
|
+
kind: string(),
|
|
420
|
+
loc: optional(SourceLocationSchema),
|
|
421
|
+
name: optional(string()),
|
|
422
|
+
range: optional(SourceRangeSchema)
|
|
423
|
+
}),
|
|
424
|
+
usage: array(UsageSchema)
|
|
425
|
+
});
|
|
426
|
+
function objectRecord(value) {
|
|
427
|
+
return pipe(unknown(), check((input) => typeof input === "object" && input !== null && !Array.isArray(input)), record(string(), value));
|
|
428
|
+
}
|
|
429
|
+
const CacheFileBodySchema = pipe(object({
|
|
430
|
+
createdAt: string(),
|
|
431
|
+
entries: objectRecord(CacheEntrySchema),
|
|
432
|
+
owners: objectRecord(object({
|
|
433
|
+
contentHash: optional(string()),
|
|
434
|
+
kind: union([literal("file"), literal("project")]),
|
|
435
|
+
path: string(),
|
|
436
|
+
slots: array(string())
|
|
437
|
+
})),
|
|
438
|
+
updatedAt: string()
|
|
439
|
+
}), check((body) => {
|
|
440
|
+
const entryKeys = Object.keys(body.entries);
|
|
441
|
+
const referencedSlots = Object.values(body.owners).flatMap((owner) => owner.slots);
|
|
442
|
+
const uniqueSlots = new Set(referencedSlots);
|
|
443
|
+
return uniqueSlots.size === referencedSlots.length && uniqueSlots.size === entryKeys.length && entryKeys.every((key) => uniqueSlots.has(key));
|
|
444
|
+
}));
|
|
497
445
|
async function createCacheStore(options) {
|
|
498
446
|
const location = resolveCacheLocation(options.cwd, options.location);
|
|
499
447
|
if (!options.enabled) return createNoopCacheStore(location);
|
|
500
|
-
const
|
|
448
|
+
const header = `${CACHE_MAGIC} 2 ${options.alintVersion ?? version}`;
|
|
449
|
+
const body = await loadCacheBody(location, header, options.readOnly === true);
|
|
450
|
+
if (options.readOnly) return createReadOnlyCacheStore(location, body, options.cwd);
|
|
451
|
+
const fileExists = options.fileExists ?? defaultFileExists;
|
|
452
|
+
const writeFile$1 = options.writeFile ?? writeFile;
|
|
501
453
|
return {
|
|
502
|
-
|
|
454
|
+
beginOwner: (owner) => beginOwner(body, owner, options.cwd),
|
|
503
455
|
location,
|
|
504
|
-
markFile: (filePath, contentHash, entries) => {
|
|
505
|
-
const path = normalizeCachePath(options.cwd, filePath);
|
|
506
|
-
cacheFile.files[path] = {
|
|
507
|
-
contentHash,
|
|
508
|
-
entries,
|
|
509
|
-
path
|
|
510
|
-
};
|
|
511
|
-
},
|
|
512
456
|
reconcile: async () => {
|
|
513
|
-
await
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
await writeFile(tempPath, `${JSON.stringify(cacheFile, null, 2)}\n`);
|
|
517
|
-
await rename(tempPath, location);
|
|
518
|
-
},
|
|
519
|
-
set: (key, entry) => {
|
|
520
|
-
cacheFile.entries[key] = entry;
|
|
457
|
+
await collectMissingFileOwners(body, options.cwd, fileExists);
|
|
458
|
+
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
459
|
+
await persistCacheBody(location, header, body, writeFile$1);
|
|
521
460
|
}
|
|
522
461
|
};
|
|
523
462
|
}
|
|
524
463
|
function createTargetIdentityResolver(targets) {
|
|
525
464
|
const baseCounts = /* @__PURE__ */ new Map();
|
|
465
|
+
const duplicateCandidateCounts = /* @__PURE__ */ new Map();
|
|
526
466
|
for (const target of targets) {
|
|
527
467
|
const base = createBaseTargetIdentity(target);
|
|
528
468
|
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
529
469
|
}
|
|
530
|
-
|
|
470
|
+
for (const target of targets) {
|
|
471
|
+
const base = createBaseTargetIdentity(target);
|
|
472
|
+
if ((baseCounts.get(base) ?? 0) <= 1) continue;
|
|
473
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
474
|
+
duplicateCandidateCounts.set(candidate, (duplicateCandidateCounts.get(candidate) ?? 0) + 1);
|
|
475
|
+
}
|
|
476
|
+
return (target, targetIndex) => {
|
|
531
477
|
const base = createBaseTargetIdentity(target);
|
|
532
478
|
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
533
|
-
|
|
534
|
-
return
|
|
479
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
480
|
+
if ((duplicateCandidateCounts.get(candidate) ?? 0) <= 1) return candidate;
|
|
481
|
+
return `${candidate}:${targetIndex}`;
|
|
535
482
|
};
|
|
536
483
|
}
|
|
537
|
-
function hashText(text) {
|
|
538
|
-
return createHash("sha256").update(text).digest("hex");
|
|
539
|
-
}
|
|
540
484
|
function normalizeCachePath(cwd, filePath) {
|
|
541
|
-
return relative$1(cwd, isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath)).split(sep).join("/");
|
|
485
|
+
return relative$1(cwd, isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath)).split(sep).join("/") || ".";
|
|
542
486
|
}
|
|
543
487
|
function normalizeRunnerCacheConfig(cache, cwd) {
|
|
544
488
|
if (cache === false) return {
|
|
@@ -559,12 +503,50 @@ function resolveCacheLocation(cwd, location) {
|
|
|
559
503
|
const resolved = isAbsolute$1(location) ? resolve$1(location) : resolve$1(cwd, location);
|
|
560
504
|
if (location.endsWith("/") || location.endsWith("\\")) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
561
505
|
try {
|
|
562
|
-
if (
|
|
506
|
+
if (statSync(resolved).isDirectory()) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
563
507
|
} catch {}
|
|
564
508
|
return resolved;
|
|
565
509
|
}
|
|
566
|
-
function
|
|
567
|
-
|
|
510
|
+
function beginOwner(body, owner, cwd) {
|
|
511
|
+
const normalizedOwner = {
|
|
512
|
+
kind: owner.kind,
|
|
513
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
514
|
+
};
|
|
515
|
+
const key = ownerKey(normalizedOwner, cwd);
|
|
516
|
+
const previousOwner = body.owners[key];
|
|
517
|
+
const nextEntries = /* @__PURE__ */ new Map();
|
|
518
|
+
return {
|
|
519
|
+
commit: (metadata = {}) => {
|
|
520
|
+
const committedEntries = metadata.mode === "merge" ? ownerEntries(body, body.owners[key]) : /* @__PURE__ */ new Map();
|
|
521
|
+
for (const [entryKey, cacheEntry] of nextEntries) committedEntries.set(entryKey, cacheEntry);
|
|
522
|
+
const replacedSlots = /* @__PURE__ */ new Set([...previousOwner?.slots ?? [], ...body.owners[key]?.slots ?? []]);
|
|
523
|
+
for (const replacedSlot of replacedSlots) delete body.entries[replacedSlot];
|
|
524
|
+
for (const [entryKey, cacheEntry] of committedEntries) body.entries[entryKey] = cacheEntry;
|
|
525
|
+
body.owners[key] = {
|
|
526
|
+
contentHash: metadata.contentHash,
|
|
527
|
+
kind: normalizedOwner.kind,
|
|
528
|
+
path: normalizedOwner.path,
|
|
529
|
+
slots: [...committedEntries.keys()].sort()
|
|
530
|
+
};
|
|
531
|
+
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
532
|
+
},
|
|
533
|
+
discard: (cacheSlot) => nextEntries.delete(slotKey(normalizedOwner, cacheSlot, cwd)),
|
|
534
|
+
lookup: (cacheSlot, fingerprint) => {
|
|
535
|
+
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
536
|
+
const cached = body.entries[entryKey];
|
|
537
|
+
if (!cached || !fingerprintsEqual(cached.fingerprint, fingerprint)) return void 0;
|
|
538
|
+
nextEntries.set(entryKey, cached);
|
|
539
|
+
return cached;
|
|
540
|
+
},
|
|
541
|
+
put: (cacheSlot, cacheEntry) => nextEntries.set(slotKey(normalizedOwner, cacheSlot, cwd), cacheEntry)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async function collectMissingFileOwners(body, cwd, fileExists) {
|
|
545
|
+
for (const [key, owner] of Object.entries(body.owners)) {
|
|
546
|
+
if (owner.kind !== "file" || await fileExists(resolve$1(cwd, owner.path))) continue;
|
|
547
|
+
for (const entryKey of owner.slots) delete body.entries[entryKey];
|
|
548
|
+
delete body.owners[key];
|
|
549
|
+
}
|
|
568
550
|
}
|
|
569
551
|
function createBaseTargetIdentity(target) {
|
|
570
552
|
if (target.identity && (target.kind !== "file" || target.identity !== "file")) return target.filePath ? `${target.kind}:${target.filePath}:${target.identity}` : `${target.kind}:${target.identity}`;
|
|
@@ -573,53 +555,216 @@ function createBaseTargetIdentity(target) {
|
|
|
573
555
|
if (target.range) return `${target.kind}:${target.range.start}:${target.range.end}`;
|
|
574
556
|
return target.kind;
|
|
575
557
|
}
|
|
576
|
-
function
|
|
558
|
+
function createEmptyCacheBody() {
|
|
577
559
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
578
560
|
return {
|
|
579
561
|
createdAt: now,
|
|
580
562
|
entries: {},
|
|
581
|
-
|
|
582
|
-
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
563
|
+
owners: {},
|
|
583
564
|
updatedAt: now
|
|
584
565
|
};
|
|
585
566
|
}
|
|
586
567
|
function createNoopCacheStore(location) {
|
|
568
|
+
const transaction = {
|
|
569
|
+
commit: () => {},
|
|
570
|
+
discard: () => {},
|
|
571
|
+
lookup: () => void 0,
|
|
572
|
+
put: () => {}
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
beginOwner: () => transaction,
|
|
576
|
+
location,
|
|
577
|
+
reconcile: async () => {}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function createReadOnlyCacheStore(location, body, cwd) {
|
|
581
|
+
const transaction = (owner) => ({
|
|
582
|
+
commit: () => {},
|
|
583
|
+
discard: () => {},
|
|
584
|
+
lookup: (slot, fingerprint) => {
|
|
585
|
+
const cached = body.entries[slotKey(owner, slot, cwd)];
|
|
586
|
+
return cached && fingerprintsEqual(cached.fingerprint, fingerprint) ? cached : void 0;
|
|
587
|
+
},
|
|
588
|
+
put: () => {}
|
|
589
|
+
});
|
|
587
590
|
return {
|
|
588
|
-
|
|
591
|
+
beginOwner: transaction,
|
|
589
592
|
location,
|
|
590
|
-
|
|
591
|
-
reconcile: async () => {},
|
|
592
|
-
set: () => {}
|
|
593
|
+
reconcile: async () => {}
|
|
593
594
|
};
|
|
594
595
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
596
|
+
async function defaultFileExists(path) {
|
|
597
|
+
try {
|
|
598
|
+
await access(path);
|
|
599
|
+
return true;
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (isMissingFileError(error)) return false;
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function fingerprintsEqual(left, right) {
|
|
606
|
+
return left.configHash === right.configHash && left.modelHash === right.modelHash && left.ruleHash === right.ruleHash && left.targetHash === right.targetHash;
|
|
607
|
+
}
|
|
608
|
+
function hasJsonArrayProperties(input, ancestors) {
|
|
609
|
+
let arrayLength;
|
|
610
|
+
let indexCount = 0;
|
|
611
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
612
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
613
|
+
if (!descriptor || !("value" in descriptor)) return false;
|
|
614
|
+
if (key === "length") {
|
|
615
|
+
if (descriptor.enumerable || typeof descriptor.value !== "number") return false;
|
|
616
|
+
arrayLength = descriptor.value;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (typeof key !== "string" || !isCanonicalArrayIndex(key) || !descriptor.enumerable) return false;
|
|
620
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
621
|
+
indexCount += 1;
|
|
622
|
+
}
|
|
623
|
+
return arrayLength !== void 0 && indexCount === arrayLength;
|
|
624
|
+
}
|
|
625
|
+
function hasJsonObjectProperties(input, ancestors) {
|
|
626
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
627
|
+
if (typeof key !== "string") return false;
|
|
628
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
629
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
630
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
631
|
+
}
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
function isCanonicalArrayIndex(key) {
|
|
635
|
+
const index = Number(key);
|
|
636
|
+
return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
|
|
597
637
|
}
|
|
598
|
-
function
|
|
599
|
-
return
|
|
638
|
+
function isJsonValue(input) {
|
|
639
|
+
return isJsonValueAt(input, /* @__PURE__ */ new WeakSet());
|
|
600
640
|
}
|
|
601
|
-
|
|
641
|
+
function isJsonValueAt(input, ancestors) {
|
|
642
|
+
if (input === null || typeof input === "string" || typeof input === "boolean") return true;
|
|
643
|
+
if (typeof input === "number") return Number.isFinite(input);
|
|
644
|
+
if (typeof input !== "object") return false;
|
|
645
|
+
if (ancestors.has(input)) return false;
|
|
646
|
+
ancestors.add(input);
|
|
602
647
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
648
|
+
if (Array.isArray(input)) {
|
|
649
|
+
if (Object.getPrototypeOf(input) !== Array.prototype) return false;
|
|
650
|
+
return hasJsonArrayProperties(input, ancestors);
|
|
651
|
+
}
|
|
652
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) return false;
|
|
653
|
+
return hasJsonObjectProperties(input, ancestors);
|
|
654
|
+
} catch {
|
|
655
|
+
return false;
|
|
656
|
+
} finally {
|
|
657
|
+
ancestors.delete(input);
|
|
658
|
+
}
|
|
607
659
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
660
|
+
function isMissingFileError(error) {
|
|
661
|
+
return isNodeErrorCode(error, "ENOENT") || isNodeErrorCode(error, "ENOTDIR");
|
|
662
|
+
}
|
|
663
|
+
function isNodeErrorCode(error, code) {
|
|
664
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
665
|
+
}
|
|
666
|
+
async function loadCacheBody(location, expectedHeader, readOnly) {
|
|
667
|
+
let handle;
|
|
668
|
+
try {
|
|
669
|
+
handle = await open(location, "r");
|
|
670
|
+
const header = await readHeader(handle);
|
|
671
|
+
await handle.close();
|
|
672
|
+
handle = void 0;
|
|
673
|
+
if (header !== expectedHeader) {
|
|
674
|
+
if (!readOnly) await rm(location, { force: true });
|
|
675
|
+
return createEmptyCacheBody();
|
|
676
|
+
}
|
|
677
|
+
const text = await readFile(location, "utf8");
|
|
678
|
+
return parseCacheBody(text.slice(text.indexOf("\n") + 1));
|
|
679
|
+
} catch (error) {
|
|
680
|
+
await handle?.close().catch(() => {});
|
|
681
|
+
if (isMissingFileError(error)) return createEmptyCacheBody();
|
|
682
|
+
if (!readOnly) await rm(location, { force: true }).catch(() => {});
|
|
683
|
+
return createEmptyCacheBody();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function ownerEntries(body, owner) {
|
|
687
|
+
const entries = /* @__PURE__ */ new Map();
|
|
688
|
+
for (const slot of owner?.slots ?? []) {
|
|
689
|
+
const cacheEntry = body.entries[slot];
|
|
690
|
+
if (cacheEntry) entries.set(slot, cacheEntry);
|
|
691
|
+
}
|
|
692
|
+
return entries;
|
|
693
|
+
}
|
|
694
|
+
function ownerKey(owner, cwd) {
|
|
695
|
+
return stableHash({
|
|
696
|
+
kind: owner.kind,
|
|
697
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function parseCacheBody(text) {
|
|
701
|
+
const value = JSON.parse(text);
|
|
702
|
+
return parse(CacheFileBodySchema, value);
|
|
703
|
+
}
|
|
704
|
+
async function persistCacheBody(location, header, body, writeFile) {
|
|
705
|
+
const validatedBody = parse(CacheFileBodySchema, body);
|
|
706
|
+
await mkdir(dirname(location), { recursive: true });
|
|
707
|
+
const tempPath = join(dirname(location), `.${basename(location)}.${process.pid}.${randomUUID()}.tmp`);
|
|
708
|
+
try {
|
|
709
|
+
await writeFile(tempPath, `${header}\n${JSON.stringify(validatedBody)}\n`);
|
|
710
|
+
await rename(tempPath, location);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
616
715
|
}
|
|
617
|
-
function
|
|
618
|
-
|
|
716
|
+
async function readHeader(handle) {
|
|
717
|
+
const buffer = Buffer.alloc(256);
|
|
718
|
+
const { bytesRead } = await handle.read(buffer, 0, 256, 0);
|
|
719
|
+
const newline = buffer.subarray(0, bytesRead).indexOf(10);
|
|
720
|
+
return newline < 0 ? void 0 : buffer.subarray(0, newline).toString("utf8");
|
|
721
|
+
}
|
|
722
|
+
function slotKey(owner, slot, cwd) {
|
|
723
|
+
return stableHash({
|
|
724
|
+
owner: ownerKey(owner, cwd),
|
|
725
|
+
...slot
|
|
726
|
+
});
|
|
619
727
|
}
|
|
620
728
|
//#endregion
|
|
621
|
-
//#region
|
|
622
|
-
|
|
729
|
+
//#region src/core/execution/records.ts
|
|
730
|
+
function snapshotDiagnostic(diagnostic) {
|
|
731
|
+
const snapshot = { ...diagnostic };
|
|
732
|
+
if (diagnostic.loc) snapshot.loc = {
|
|
733
|
+
...diagnostic.loc.end ? { end: { ...diagnostic.loc.end } } : {},
|
|
734
|
+
start: { ...diagnostic.loc.start }
|
|
735
|
+
};
|
|
736
|
+
if (diagnostic.model) snapshot.model = { ...diagnostic.model };
|
|
737
|
+
return snapshot;
|
|
738
|
+
}
|
|
739
|
+
function snapshotDiagnostics(diagnostics) {
|
|
740
|
+
return diagnostics.map(snapshotDiagnostic);
|
|
741
|
+
}
|
|
742
|
+
function snapshotFailure(failure) {
|
|
743
|
+
return {
|
|
744
|
+
job: snapshotProgressJobRef(failure.job),
|
|
745
|
+
kind: failure.kind,
|
|
746
|
+
message: failure.message
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function snapshotProgressJobRef(job) {
|
|
750
|
+
return {
|
|
751
|
+
id: job.id,
|
|
752
|
+
index: job.index,
|
|
753
|
+
inputPath: job.inputPath,
|
|
754
|
+
ruleId: job.ruleId,
|
|
755
|
+
target: {
|
|
756
|
+
identity: job.target.identity,
|
|
757
|
+
kind: job.target.kind,
|
|
758
|
+
name: job.target.name
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function snapshotUsage(record) {
|
|
763
|
+
return { ...record };
|
|
764
|
+
}
|
|
765
|
+
function snapshotUsageRecords(records) {
|
|
766
|
+
return records.map(snapshotUsage);
|
|
767
|
+
}
|
|
623
768
|
//#endregion
|
|
624
769
|
//#region src/core/execution/job.ts
|
|
625
770
|
var CacheReplayProcessingError = class {
|
|
@@ -628,76 +773,43 @@ var CacheReplayProcessingError = class {
|
|
|
628
773
|
this.cause = cause;
|
|
629
774
|
}
|
|
630
775
|
};
|
|
631
|
-
function
|
|
632
|
-
|
|
633
|
-
const total = plans.reduce((sum, plan) => sum + plan.planned, 0);
|
|
634
|
-
const ruleTotals = /* @__PURE__ */ new Map();
|
|
635
|
-
const ruleIndexes = /* @__PURE__ */ new Map();
|
|
636
|
-
for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
|
|
637
|
-
const ruleId = execution.runtime.enabledRule.id;
|
|
638
|
-
ruleTotals.set(ruleId, (ruleTotals.get(ruleId) ?? 0) + 1);
|
|
639
|
-
}
|
|
640
|
-
for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
|
|
641
|
-
const ruleId = execution.runtime.enabledRule.id;
|
|
642
|
-
const index = jobs.length + 1;
|
|
643
|
-
const ruleIndex = (ruleIndexes.get(ruleId) ?? 0) + 1;
|
|
644
|
-
ruleIndexes.set(ruleId, ruleIndex);
|
|
645
|
-
jobs.push({
|
|
646
|
-
execution,
|
|
647
|
-
job: {
|
|
648
|
-
id: stableHash({
|
|
649
|
-
index,
|
|
650
|
-
planId: plan.id,
|
|
651
|
-
ruleId,
|
|
652
|
-
targetIdentity: target.identity
|
|
653
|
-
}),
|
|
654
|
-
index,
|
|
655
|
-
inputPath: plan.path,
|
|
656
|
-
ruleId,
|
|
657
|
-
ruleIndex,
|
|
658
|
-
ruleTotal: ruleTotals.get(ruleId) ?? 0,
|
|
659
|
-
target: {
|
|
660
|
-
identity: target.identity,
|
|
661
|
-
kind: target.kind,
|
|
662
|
-
name: target.name
|
|
663
|
-
},
|
|
664
|
-
total
|
|
665
|
-
},
|
|
666
|
-
target
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
return jobs;
|
|
776
|
+
function compareJobOrder(left, right) {
|
|
777
|
+
return scopeRank(left.scope) - scopeRank(right.scope) || left.inputIndex - right.inputIndex || left.targetIndex - right.targetIndex || left.ruleIndex - right.ruleIndex;
|
|
670
778
|
}
|
|
671
779
|
async function executeRuleJob(job, options) {
|
|
672
780
|
const bucket = {
|
|
673
781
|
diagnostics: [],
|
|
674
782
|
usage: []
|
|
675
783
|
};
|
|
676
|
-
if (options.runSignal?.aborted) return finish(job,
|
|
784
|
+
if (options.runSignal?.aborted) return finish(job, bucket, {
|
|
677
785
|
cache: "miss",
|
|
678
786
|
state: "cancelled"
|
|
679
787
|
});
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
788
|
+
const slot = job.target.cacheOwner && job.execution.runtime.cacheable ? {
|
|
789
|
+
ruleId: job.execution.runtime.enabledRule.id,
|
|
790
|
+
scope: job.target.kind,
|
|
791
|
+
targetIdentity: job.target.identity
|
|
792
|
+
} : void 0;
|
|
793
|
+
const fingerprint = slot ? createFingerprint(job, options.cache.modelHash) : void 0;
|
|
794
|
+
const cachedEntry = slot && fingerprint ? job.target.cacheOwner?.lookup(slot, fingerprint) : void 0;
|
|
795
|
+
if (slot && cachedEntry) {
|
|
684
796
|
try {
|
|
685
|
-
replayCachedEntry(cachedEntry, bucket, job.
|
|
797
|
+
replayCachedEntry(cachedEntry, bucket, job.jobRef, options.progress, options.runProgress);
|
|
686
798
|
} catch (error) {
|
|
687
799
|
if (!(error instanceof CacheReplayProcessingError)) throw error;
|
|
688
|
-
|
|
800
|
+
job.target.cacheOwner?.discard(slot);
|
|
801
|
+
return finish(job, bucket, {
|
|
689
802
|
cache: "hit",
|
|
690
|
-
cause: error.cause,
|
|
691
803
|
failure: failure(error.cause, job, "cache-replay"),
|
|
692
804
|
state: "failed"
|
|
693
805
|
});
|
|
694
806
|
}
|
|
695
|
-
return finish(job,
|
|
807
|
+
return finish(job, bucket, {
|
|
696
808
|
cache: "hit",
|
|
697
809
|
state: "cached"
|
|
698
810
|
});
|
|
699
811
|
}
|
|
700
|
-
if (options.cacheOnly) return finish(job,
|
|
812
|
+
if (options.cacheOnly) return finish(job, bucket, {
|
|
701
813
|
cache: "miss",
|
|
702
814
|
state: "skipped"
|
|
703
815
|
});
|
|
@@ -715,8 +827,10 @@ async function executeRuleJob(job, options) {
|
|
|
715
827
|
const state = {
|
|
716
828
|
activeFilePath: job.target.activeFilePath,
|
|
717
829
|
bucket,
|
|
718
|
-
|
|
830
|
+
jobRef: job.jobRef,
|
|
719
831
|
reporterFailed: false,
|
|
832
|
+
runProgress: options.runProgress,
|
|
833
|
+
sealed: false,
|
|
720
834
|
signal: controller.signal
|
|
721
835
|
};
|
|
722
836
|
let handlerCause;
|
|
@@ -728,31 +842,29 @@ async function executeRuleJob(job, options) {
|
|
|
728
842
|
handlerCause = cause;
|
|
729
843
|
handlerFailed = true;
|
|
730
844
|
} finally {
|
|
845
|
+
state.sealed = true;
|
|
731
846
|
if (timer != null) clearTimeout(timer);
|
|
732
847
|
options.runSignal?.removeEventListener("abort", forwardRunAbort);
|
|
733
848
|
}
|
|
734
849
|
if (state.reporterFailed) throw state.reporterCause;
|
|
735
|
-
if (
|
|
736
|
-
options.cache.store.set(cacheKey, createCacheEntry(job, options.cache, bucket));
|
|
737
|
-
rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
|
|
738
|
-
} catch {}
|
|
739
|
-
if (options.runSignal?.aborted) return finish(job, options, bucket, {
|
|
850
|
+
if (options.runSignal?.aborted) return finish(job, bucket, {
|
|
740
851
|
cache: "miss",
|
|
741
852
|
state: "cancelled"
|
|
742
853
|
});
|
|
743
|
-
if (timedOut) return finish(job,
|
|
854
|
+
if (timedOut) return finish(job, bucket, {
|
|
744
855
|
cache: "miss",
|
|
745
|
-
cause: deadlineError,
|
|
746
856
|
failure: failure(deadlineError, job, "timeout"),
|
|
747
857
|
state: "failed"
|
|
748
858
|
});
|
|
749
|
-
if (handlerFailed) return finish(job,
|
|
859
|
+
if (handlerFailed) return finish(job, bucket, {
|
|
750
860
|
cache: "miss",
|
|
751
|
-
cause: handlerCause,
|
|
752
861
|
failure: failure(handlerCause, job, "handler"),
|
|
753
862
|
state: "failed"
|
|
754
863
|
});
|
|
755
|
-
|
|
864
|
+
if (slot && fingerprint) try {
|
|
865
|
+
job.target.cacheOwner?.put(slot, createCacheEntry(job, fingerprint, bucket));
|
|
866
|
+
} catch {}
|
|
867
|
+
return finish(job, bucket, {
|
|
756
868
|
cache: "miss",
|
|
757
869
|
state: "completed"
|
|
758
870
|
});
|
|
@@ -762,44 +874,44 @@ function resolveRuleExecutionTimeout(timeoutMs) {
|
|
|
762
874
|
if (!Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("Rule execution timeout must be a finite positive integer.");
|
|
763
875
|
return timeoutMs;
|
|
764
876
|
}
|
|
765
|
-
function createCacheEntry(job,
|
|
877
|
+
function createCacheEntry(job, fingerprint, bucket) {
|
|
766
878
|
return {
|
|
767
|
-
diagnostics: bucket.diagnostics,
|
|
768
|
-
|
|
769
|
-
fingerprint: {
|
|
770
|
-
alintVersion: version,
|
|
771
|
-
configHash: job.target.configHash,
|
|
772
|
-
modelHash: cache.modelHash,
|
|
773
|
-
ruleHash: job.execution.runtime.ruleHash
|
|
774
|
-
},
|
|
879
|
+
diagnostics: snapshotDiagnostics(bucket.diagnostics),
|
|
880
|
+
fingerprint,
|
|
775
881
|
target: {
|
|
776
|
-
hash:
|
|
882
|
+
hash: fingerprint.targetHash,
|
|
777
883
|
identity: job.target.identity,
|
|
778
884
|
kind: job.target.kind,
|
|
779
|
-
loc: job.target.loc
|
|
885
|
+
loc: job.target.loc ? {
|
|
886
|
+
end: {
|
|
887
|
+
column: job.target.loc.end.column,
|
|
888
|
+
line: job.target.loc.end.line
|
|
889
|
+
},
|
|
890
|
+
start: {
|
|
891
|
+
column: job.target.loc.start.column,
|
|
892
|
+
line: job.target.loc.start.line
|
|
893
|
+
}
|
|
894
|
+
} : void 0,
|
|
780
895
|
name: job.target.name,
|
|
781
|
-
range: job.target.range
|
|
896
|
+
range: job.target.range ? {
|
|
897
|
+
end: job.target.range.end,
|
|
898
|
+
start: job.target.range.start
|
|
899
|
+
} : void 0
|
|
782
900
|
},
|
|
783
|
-
usage: bucket.usage
|
|
901
|
+
usage: snapshotUsageRecords(bucket.usage)
|
|
784
902
|
};
|
|
785
903
|
}
|
|
786
|
-
function
|
|
787
|
-
|
|
788
|
-
return createCacheKey({
|
|
789
|
-
alintVersion: version,
|
|
904
|
+
function createFingerprint(job, modelHash) {
|
|
905
|
+
return {
|
|
790
906
|
configHash: job.target.configHash,
|
|
791
|
-
|
|
792
|
-
modelHash: cache.modelHash,
|
|
907
|
+
modelHash,
|
|
793
908
|
ruleHash: job.execution.runtime.ruleHash,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
targetIdentity: job.target.identity,
|
|
797
|
-
targetKind: job.target.kind
|
|
798
|
-
});
|
|
909
|
+
targetHash: createTargetHash(job)
|
|
910
|
+
};
|
|
799
911
|
}
|
|
800
912
|
function createTargetHash(job) {
|
|
801
913
|
const target = job.target;
|
|
802
|
-
return stableHash({
|
|
914
|
+
return target.cacheTargetHash ?? stableHash({
|
|
803
915
|
language: target.language,
|
|
804
916
|
loc: target.loc,
|
|
805
917
|
metadata: target.metadata,
|
|
@@ -811,36 +923,32 @@ function createTargetHash(job) {
|
|
|
811
923
|
}
|
|
812
924
|
function failure(cause, job, kind) {
|
|
813
925
|
return {
|
|
814
|
-
job: job.
|
|
926
|
+
job: snapshotProgressJobRef(job.jobRef),
|
|
815
927
|
kind,
|
|
816
|
-
message:
|
|
928
|
+
message: failureMessage$1(cause)
|
|
817
929
|
};
|
|
818
930
|
}
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
};
|
|
825
|
-
options.progress?.onJobEnd?.({
|
|
826
|
-
cache: outcome.cache,
|
|
827
|
-
endedAt: options.clock(),
|
|
828
|
-
failure: outcome.failure,
|
|
829
|
-
job: job.job,
|
|
830
|
-
startedAt: options.startedAt,
|
|
831
|
-
state: outcome.state
|
|
832
|
-
});
|
|
833
|
-
return outcome;
|
|
834
|
-
}
|
|
835
|
-
function rememberCacheEntry(cache, filePaths, cacheKey) {
|
|
836
|
-
for (const filePath of filePaths) {
|
|
837
|
-
const normalizedPath = normalizeCachePath(cache.cwd, filePath);
|
|
838
|
-
const entries = cache.fileEntryKeys.get(normalizedPath) ?? /* @__PURE__ */ new Set();
|
|
839
|
-
entries.add(cacheKey);
|
|
840
|
-
cache.fileEntryKeys.set(normalizedPath, entries);
|
|
931
|
+
function failureMessage$1(cause) {
|
|
932
|
+
try {
|
|
933
|
+
return errorMessageFrom(cause) ?? "Unknown rule failure.";
|
|
934
|
+
} catch {
|
|
935
|
+
return "Unknown rule failure.";
|
|
841
936
|
}
|
|
842
937
|
}
|
|
843
|
-
function
|
|
938
|
+
function finish(job, bucket, terminal) {
|
|
939
|
+
const detachedTerminal = terminal.state === "failed" ? {
|
|
940
|
+
...terminal,
|
|
941
|
+
failure: snapshotFailure(terminal.failure)
|
|
942
|
+
} : terminal;
|
|
943
|
+
return {
|
|
944
|
+
diagnostics: snapshotDiagnostics(bucket.diagnostics),
|
|
945
|
+
jobRef: snapshotProgressJobRef(job.jobRef),
|
|
946
|
+
orderKey: { ...job.orderKey },
|
|
947
|
+
usage: snapshotUsageRecords(bucket.usage),
|
|
948
|
+
...detachedTerminal
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function replayCachedEntry(entry, bucket, job, progress, runProgress) {
|
|
844
952
|
let diagnostics;
|
|
845
953
|
let usage;
|
|
846
954
|
try {
|
|
@@ -850,452 +958,209 @@ function replayCachedEntry(entry, bucket, job, progress) {
|
|
|
850
958
|
throw new CacheReplayProcessingError(cause);
|
|
851
959
|
}
|
|
852
960
|
for (const cachedDiagnostic of diagnostics) {
|
|
853
|
-
const diagnostic = {
|
|
961
|
+
const diagnostic = snapshotDiagnostic({
|
|
854
962
|
...cachedDiagnostic,
|
|
855
963
|
cached: true
|
|
856
|
-
};
|
|
964
|
+
});
|
|
857
965
|
bucket.diagnostics.push(diagnostic);
|
|
858
966
|
progress?.onDiagnostic?.({
|
|
859
|
-
diagnostic,
|
|
860
|
-
job
|
|
967
|
+
diagnostic: snapshotDiagnostic(diagnostic),
|
|
968
|
+
job: snapshotProgressJobRef(job),
|
|
969
|
+
progress: runProgress.snapshot()
|
|
861
970
|
});
|
|
862
971
|
}
|
|
863
972
|
for (const record of usage) {
|
|
864
|
-
|
|
973
|
+
const usageRecord = snapshotUsage(record);
|
|
974
|
+
bucket.usage.push(usageRecord);
|
|
865
975
|
progress?.onUsage?.({
|
|
866
|
-
job,
|
|
867
|
-
|
|
976
|
+
job: snapshotProgressJobRef(job),
|
|
977
|
+
progress: runProgress.snapshot(),
|
|
978
|
+
record: snapshotUsage(usageRecord)
|
|
868
979
|
});
|
|
869
980
|
}
|
|
870
981
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
return
|
|
875
|
-
const executions = directory.ruleRuntimes.map((runtime) => {
|
|
876
|
-
if (runtime.handlers.onTargetWith) return {
|
|
877
|
-
run: () => runtime.handlers.onTargetWith?.(directory.target),
|
|
878
|
-
runtime
|
|
879
|
-
};
|
|
880
|
-
if (runtime.handlers.onTargetDirectory) return {
|
|
881
|
-
run: () => runtime.handlers.onTargetDirectory?.(directory.target),
|
|
882
|
-
runtime
|
|
883
|
-
};
|
|
884
|
-
}).filter((execution) => execution !== void 0);
|
|
885
|
-
const targets = executions.length === 0 ? [] : [{
|
|
886
|
-
activeFilePath: directory.target.path,
|
|
887
|
-
cacheFilePaths: [],
|
|
888
|
-
configHash: directory.configHash,
|
|
889
|
-
executions,
|
|
890
|
-
identity: directory.target.path,
|
|
891
|
-
kind: "directory",
|
|
892
|
-
language: "directory",
|
|
893
|
-
text: directory.target.path
|
|
894
|
-
}];
|
|
895
|
-
return {
|
|
896
|
-
id: `directory:${directory.target.path}`,
|
|
897
|
-
index: sourcePlanCount + directoryOffset + 1,
|
|
898
|
-
kind: "directory",
|
|
899
|
-
path: directory.target.path,
|
|
900
|
-
planned: executions.length,
|
|
901
|
-
targets
|
|
902
|
-
};
|
|
903
|
-
});
|
|
982
|
+
function scopeRank(scope) {
|
|
983
|
+
if (scope === "source") return 0;
|
|
984
|
+
if (scope === "directory") return 1;
|
|
985
|
+
return 2;
|
|
904
986
|
}
|
|
905
987
|
//#endregion
|
|
906
|
-
//#region src/core/
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
988
|
+
//#region src/core/execution/progress.ts
|
|
989
|
+
function createRunProgress(filesTotal) {
|
|
990
|
+
assertNonNegativeInteger(filesTotal, "File total");
|
|
991
|
+
const execution = createCounts();
|
|
992
|
+
let final = false;
|
|
993
|
+
const snapshot = () => {
|
|
994
|
+
const jobsCompleted = terminalCount(execution);
|
|
995
|
+
return {
|
|
996
|
+
execution: { ...execution },
|
|
997
|
+
filesTotal,
|
|
998
|
+
final,
|
|
999
|
+
jobsCompleted,
|
|
1000
|
+
jobsStarted: jobsCompleted + execution.running,
|
|
1001
|
+
jobsTotal: execution.planned
|
|
917
1002
|
};
|
|
918
|
-
}).filter((execution) => execution !== void 0);
|
|
919
|
-
if (executions.length === 0) return;
|
|
920
|
-
const target = {
|
|
921
|
-
cacheFilePaths: options.files.map((file) => file.file.path),
|
|
922
|
-
configHash: options.configHash,
|
|
923
|
-
executions,
|
|
924
|
-
identity: "project",
|
|
925
|
-
kind: "project",
|
|
926
|
-
language: "project",
|
|
927
|
-
text: stableHash({
|
|
928
|
-
files: options.files.map((file) => ({
|
|
929
|
-
configHash: file.configHash,
|
|
930
|
-
contentHash: hashText(file.file.text),
|
|
931
|
-
path: normalizeCachePath(options.root, file.file.path)
|
|
932
|
-
})).sort((left, right) => left.path.localeCompare(right.path)),
|
|
933
|
-
targets: project.targets.map((target) => ({
|
|
934
|
-
filePath: normalizeCachePath(options.root, target.file.path),
|
|
935
|
-
identity: target.identity,
|
|
936
|
-
kind: target.kind,
|
|
937
|
-
language: target.language,
|
|
938
|
-
loc: target.loc,
|
|
939
|
-
metadata: target.metadata,
|
|
940
|
-
name: target.name,
|
|
941
|
-
origin: target.origin,
|
|
942
|
-
range: target.range,
|
|
943
|
-
text: target.text
|
|
944
|
-
})),
|
|
945
|
-
tree: createProjectTreeShape(options.files, options.root)
|
|
946
|
-
})
|
|
947
|
-
};
|
|
948
|
-
return {
|
|
949
|
-
id: `project:${options.root}`,
|
|
950
|
-
index: options.index,
|
|
951
|
-
kind: "project",
|
|
952
|
-
path: options.root,
|
|
953
|
-
planned: executions.length,
|
|
954
|
-
targets: [target]
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
function createProjectTarget(root, files) {
|
|
958
|
-
const preparedFiles = [...files].sort((left, right) => left.file.path.localeCompare(right.file.path));
|
|
959
|
-
return {
|
|
960
|
-
files: preparedFiles.map((file) => file.file),
|
|
961
|
-
kind: "project",
|
|
962
|
-
root,
|
|
963
|
-
targets: preparedFiles.flatMap((file) => file.targets)
|
|
964
1003
|
};
|
|
965
|
-
}
|
|
966
|
-
function createProjectTreeShape(files, root) {
|
|
967
|
-
const directories = /* @__PURE__ */ new Set();
|
|
968
|
-
const paths = files.map((file) => normalizeCachePath(root, file.file.path)).sort();
|
|
969
|
-
for (const path of paths) {
|
|
970
|
-
const parts = path.split("/");
|
|
971
|
-
for (let index = 1; index < parts.length; index += 1) directories.add(parts.slice(0, index).join("/"));
|
|
972
|
-
}
|
|
973
1004
|
return {
|
|
974
|
-
|
|
975
|
-
|
|
1005
|
+
finalize: () => {
|
|
1006
|
+
if (execution.queued !== 0 || execution.running !== 0) throw new Error("Cannot finalize progress while jobs are queued or running.");
|
|
1007
|
+
final = true;
|
|
1008
|
+
return snapshot();
|
|
1009
|
+
},
|
|
1010
|
+
finish: (from, to) => {
|
|
1011
|
+
assertMutable(final);
|
|
1012
|
+
if (execution[from] <= 0) throw new Error(`Cannot finish a job without a ${from} job.`);
|
|
1013
|
+
execution[from] -= 1;
|
|
1014
|
+
execution[to] += 1;
|
|
1015
|
+
return snapshot();
|
|
1016
|
+
},
|
|
1017
|
+
queue: (count = 1) => {
|
|
1018
|
+
assertMutable(final);
|
|
1019
|
+
assertNonNegativeInteger(count, "Queued job count");
|
|
1020
|
+
assertSafeSum(execution.planned, count, "Planned job count");
|
|
1021
|
+
assertSafeSum(execution.queued, count, "Queued job count");
|
|
1022
|
+
execution.planned += count;
|
|
1023
|
+
execution.queued += count;
|
|
1024
|
+
return snapshot();
|
|
1025
|
+
},
|
|
1026
|
+
snapshot,
|
|
1027
|
+
start: () => {
|
|
1028
|
+
assertMutable(final);
|
|
1029
|
+
if (execution.queued <= 0) throw new Error("Cannot start a job without a queued job.");
|
|
1030
|
+
execution.queued -= 1;
|
|
1031
|
+
execution.running += 1;
|
|
1032
|
+
return snapshot();
|
|
1033
|
+
}
|
|
976
1034
|
};
|
|
977
1035
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
function createSourceExecutionPlans(files, cwd) {
|
|
981
|
-
return files.map((preparedFile, fileOffset) => {
|
|
982
|
-
const targets = collectExecutionTargets(preparedFile);
|
|
983
|
-
const resolveTargetIdentity = createTargetIdentityResolver(targets.map((target) => toTargetIdentityInput(cwd, preparedFile.file.path, target)));
|
|
984
|
-
for (const target of targets) target.identity = resolveTargetIdentity(toTargetIdentityInput(cwd, preparedFile.file.path, target));
|
|
985
|
-
const filePlan = {
|
|
986
|
-
id: `source:${preparedFile.file.path}`,
|
|
987
|
-
index: fileOffset + 1,
|
|
988
|
-
kind: "source",
|
|
989
|
-
path: preparedFile.file.path,
|
|
990
|
-
planned: 0,
|
|
991
|
-
preparedFile,
|
|
992
|
-
targets
|
|
993
|
-
};
|
|
994
|
-
filePlan.planned = calculateFilePlanExecutions(filePlan);
|
|
995
|
-
return filePlan;
|
|
996
|
-
});
|
|
1036
|
+
function assertMutable(final) {
|
|
1037
|
+
if (final) throw new Error("Cannot update finalized progress.");
|
|
997
1038
|
}
|
|
998
|
-
function
|
|
999
|
-
|
|
1000
|
-
}
|
|
1001
|
-
function collectExecutionTargets(preparedFile) {
|
|
1002
|
-
const targets = [];
|
|
1003
|
-
for (const sourceTarget of preparedFile.targets) {
|
|
1004
|
-
const executions = preparedFile.ruleRuntimes.map((runtime) => {
|
|
1005
|
-
return createSourceTargetExecution(runtime, sourceTarget);
|
|
1006
|
-
}).filter((execution) => execution !== void 0);
|
|
1007
|
-
if (executions.length === 0) continue;
|
|
1008
|
-
targets.push({
|
|
1009
|
-
activeFilePath: preparedFile.file.path,
|
|
1010
|
-
cacheFilePaths: [preparedFile.file.path],
|
|
1011
|
-
configHash: preparedFile.configHash,
|
|
1012
|
-
executions,
|
|
1013
|
-
identity: sourceTarget.identity,
|
|
1014
|
-
kind: sourceTarget.kind,
|
|
1015
|
-
language: sourceTarget.language,
|
|
1016
|
-
loc: sourceTarget.loc,
|
|
1017
|
-
metadata: sourceTarget.metadata,
|
|
1018
|
-
name: sourceTarget.name,
|
|
1019
|
-
origin: sourceTarget.origin,
|
|
1020
|
-
range: sourceTarget.range,
|
|
1021
|
-
text: sourceTarget.text
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
return targets;
|
|
1039
|
+
function assertNonNegativeInteger(value, name) {
|
|
1040
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${name} must be a non-negative integer.`);
|
|
1025
1041
|
}
|
|
1026
|
-
function
|
|
1027
|
-
if (
|
|
1028
|
-
run: () => runtime.handlers.onTargetWith?.(target),
|
|
1029
|
-
runtime
|
|
1030
|
-
};
|
|
1031
|
-
if (target.kind === "class" && runtime.handlers.onTargetClass) return {
|
|
1032
|
-
run: () => runtime.handlers.onTargetClass?.(target),
|
|
1033
|
-
runtime
|
|
1034
|
-
};
|
|
1035
|
-
if (target.kind === "file" && runtime.handlers.onTargetFile) return {
|
|
1036
|
-
run: () => runtime.handlers.onTargetFile?.(target),
|
|
1037
|
-
runtime
|
|
1038
|
-
};
|
|
1039
|
-
if (target.kind === "function" && runtime.handlers.onTargetFunction) return {
|
|
1040
|
-
run: () => runtime.handlers.onTargetFunction?.(target),
|
|
1041
|
-
runtime
|
|
1042
|
-
};
|
|
1042
|
+
function assertSafeSum(left, right, name) {
|
|
1043
|
+
if (!Number.isSafeInteger(left + right)) throw new TypeError(`${name} must remain a safe integer.`);
|
|
1043
1044
|
}
|
|
1044
|
-
function
|
|
1045
|
+
function createCounts() {
|
|
1045
1046
|
return {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1047
|
+
cached: 0,
|
|
1048
|
+
cancelled: 0,
|
|
1049
|
+
completed: 0,
|
|
1050
|
+
failed: 0,
|
|
1051
|
+
planned: 0,
|
|
1052
|
+
queued: 0,
|
|
1053
|
+
running: 0,
|
|
1054
|
+
skipped: 0
|
|
1051
1055
|
};
|
|
1052
1056
|
}
|
|
1057
|
+
function terminalCount(execution) {
|
|
1058
|
+
return execution.cached + execution.cancelled + execution.completed + execution.failed + execution.skipped;
|
|
1059
|
+
}
|
|
1053
1060
|
//#endregion
|
|
1054
|
-
//#region src/
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
failures;
|
|
1072
|
-
result;
|
|
1073
|
-
constructor(message, result, options = {}) {
|
|
1074
|
-
super(message, { cause: options.cause });
|
|
1075
|
-
this.name = "AlintRunError";
|
|
1076
|
-
this.failures = options.failures ?? [];
|
|
1077
|
-
this.result = result;
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
async function runAlint(options = {}) {
|
|
1081
|
-
const timeoutMs = resolveRuleExecutionTimeout(options.runner?.timeoutMs);
|
|
1082
|
-
const concurrency = resolveRuleConcurrency(options.runner?.ruleConcurrency);
|
|
1083
|
-
const cwd$1 = options.cwd ?? cwd();
|
|
1084
|
-
const config = options.config ?? [];
|
|
1085
|
-
const setupConfig = options.setupConfig ?? {
|
|
1086
|
-
providers: [],
|
|
1087
|
-
version: 1
|
|
1088
|
-
};
|
|
1089
|
-
const clock = Date.now;
|
|
1090
|
-
const src = createSourceRuntime();
|
|
1091
|
-
const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
|
|
1092
|
-
const cacheStore = await createCacheStore({
|
|
1093
|
-
cwd: cwd$1,
|
|
1094
|
-
enabled: normalizedCacheConfig.enabled,
|
|
1095
|
-
location: normalizedCacheConfig.location
|
|
1096
|
-
});
|
|
1097
|
-
const cacheContext = {
|
|
1098
|
-
cwd: cwd$1,
|
|
1099
|
-
enabled: normalizedCacheConfig.enabled,
|
|
1100
|
-
fileEntryKeys: /* @__PURE__ */ new Map(),
|
|
1101
|
-
modelHash: stableHash({
|
|
1102
|
-
modelOverride: options.modelOverride,
|
|
1103
|
-
outputLanguage: options.outputLanguage,
|
|
1104
|
-
setupConfig
|
|
1105
|
-
}),
|
|
1106
|
-
store: cacheStore
|
|
1107
|
-
};
|
|
1108
|
-
const pendingFiles = [...options.files ?? []];
|
|
1109
|
-
const pendingDirectories = [...options.directories ?? []];
|
|
1110
|
-
const files = (await Promise.all(pendingFiles.map(async (filePath) => {
|
|
1111
|
-
const file = await src.readFile(resolve(cwd$1, filePath));
|
|
1112
|
-
const resolvedConfig = resolveConfigForFile(file.path, config, { cwd: cwd$1 });
|
|
1113
|
-
if (resolvedConfig.ignored) return;
|
|
1114
|
-
const effectiveConfig = resolvedConfig.config;
|
|
1115
|
-
const languageRegistry = createBuiltInLanguageRegistry();
|
|
1116
|
-
for (const plugin of Object.values(effectiveConfig.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(languageRegistry, language);
|
|
1117
|
-
const language = resolveLanguage(file, languageRegistry, { language: effectiveConfig.language });
|
|
1118
|
-
const targets = await language.extract(file, {
|
|
1119
|
-
cwd: cwd$1,
|
|
1120
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
1121
|
-
src
|
|
1122
|
-
});
|
|
1123
|
-
const registry = buildRuleRegistry(effectiveConfig);
|
|
1124
|
-
const ruleRuntimes = createRuleRuntimes({
|
|
1125
|
-
cwd: cwd$1,
|
|
1126
|
-
effectiveAgent: effectiveConfig.agent,
|
|
1127
|
-
effectiveSettings: effectiveConfig.settings,
|
|
1128
|
-
options,
|
|
1129
|
-
progress: options.progress,
|
|
1130
|
-
registry,
|
|
1131
|
-
setupConfig,
|
|
1132
|
-
src
|
|
1133
|
-
});
|
|
1134
|
-
return {
|
|
1135
|
-
configHash: stableHash({
|
|
1136
|
-
language: effectiveConfig.language,
|
|
1137
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
1138
|
-
processor: effectiveConfig.processor,
|
|
1139
|
-
resolvedLanguage: language.name,
|
|
1140
|
-
settings: effectiveConfig.settings
|
|
1141
|
-
}),
|
|
1142
|
-
file,
|
|
1143
|
-
ruleRuntimes,
|
|
1144
|
-
targets
|
|
1145
|
-
};
|
|
1146
|
-
}))).filter((file) => file !== void 0);
|
|
1147
|
-
const directories = pendingDirectories.map((directoryPath) => {
|
|
1148
|
-
const target = {
|
|
1149
|
-
kind: "directory",
|
|
1150
|
-
path: resolve(cwd$1, directoryPath)
|
|
1151
|
-
};
|
|
1152
|
-
const resolvedConfig = resolveConfigForDirectory(target.path, config, { cwd: cwd$1 });
|
|
1153
|
-
if (resolvedConfig.ignored) return;
|
|
1154
|
-
const effectiveConfig = resolvedConfig.config;
|
|
1155
|
-
const registry = buildRuleRegistry(effectiveConfig);
|
|
1156
|
-
return {
|
|
1157
|
-
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1158
|
-
ruleRuntimes: createRuleRuntimes({
|
|
1159
|
-
cwd: cwd$1,
|
|
1160
|
-
effectiveAgent: effectiveConfig.agent,
|
|
1161
|
-
effectiveSettings: effectiveConfig.settings,
|
|
1162
|
-
options,
|
|
1163
|
-
progress: options.progress,
|
|
1164
|
-
registry,
|
|
1165
|
-
setupConfig,
|
|
1166
|
-
src
|
|
1167
|
-
}),
|
|
1168
|
-
target
|
|
1169
|
-
};
|
|
1170
|
-
}).filter((directory) => directory !== void 0);
|
|
1171
|
-
const resolvedProjectConfig = resolveConfigForProject(cwd$1, config, { cwd: cwd$1 });
|
|
1172
|
-
const projectConfig = resolvedProjectConfig.config;
|
|
1173
|
-
const projectRuleRuntimes = resolvedProjectConfig.ignored ? [] : createRuleRuntimes({
|
|
1174
|
-
cwd: cwd$1,
|
|
1175
|
-
effectiveAgent: projectConfig.agent,
|
|
1176
|
-
effectiveSettings: projectConfig.settings,
|
|
1177
|
-
options,
|
|
1178
|
-
progress: options.progress,
|
|
1179
|
-
registry: buildRuleRegistry(projectConfig),
|
|
1180
|
-
setupConfig,
|
|
1181
|
-
src
|
|
1182
|
-
});
|
|
1183
|
-
const filePlans = createSourceExecutionPlans(files, cwd$1);
|
|
1184
|
-
const directoryPlans = createDirectoryExecutionPlans(directories, filePlans.length);
|
|
1185
|
-
const projectPlan = resolvedProjectConfig.ignored || options.projectTargets === false ? void 0 : createProjectExecutionPlan({
|
|
1186
|
-
configHash: stableHash({ settings: projectConfig.settings }),
|
|
1187
|
-
files,
|
|
1188
|
-
index: filePlans.length + directoryPlans.length + 1,
|
|
1189
|
-
root: cwd$1,
|
|
1190
|
-
ruleRuntimes: projectRuleRuntimes
|
|
1191
|
-
});
|
|
1192
|
-
const jobs = createRuleJobs([
|
|
1193
|
-
...filePlans,
|
|
1194
|
-
...directoryPlans,
|
|
1195
|
-
...projectPlan ? [projectPlan] : []
|
|
1196
|
-
]);
|
|
1197
|
-
const runStartedAt = clock();
|
|
1198
|
-
const settledOutcomes = new Array(jobs.length);
|
|
1199
|
-
let infrastructureError;
|
|
1200
|
-
let infrastructureFailed = false;
|
|
1201
|
-
options.progress?.onRunStart?.({
|
|
1202
|
-
jobsTotal: jobs.length,
|
|
1203
|
-
startedAt: runStartedAt
|
|
1204
|
-
});
|
|
1205
|
-
for (const job of jobs) options.progress?.onJobQueued?.({ job: job.job });
|
|
1206
|
-
try {
|
|
1207
|
-
let cursor = 0;
|
|
1208
|
-
const worker = async () => {
|
|
1209
|
-
while (cursor < jobs.length) {
|
|
1210
|
-
const index = cursor;
|
|
1211
|
-
cursor += 1;
|
|
1212
|
-
const job = jobs[index];
|
|
1213
|
-
if (options.signal?.aborted) {
|
|
1214
|
-
settledOutcomes[index] = {
|
|
1215
|
-
bucket: {
|
|
1216
|
-
diagnostics: [],
|
|
1217
|
-
usage: []
|
|
1218
|
-
},
|
|
1219
|
-
cache: "miss",
|
|
1220
|
-
job,
|
|
1221
|
-
state: "cancelled"
|
|
1222
|
-
};
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
try {
|
|
1226
|
-
const startedAt = clock();
|
|
1227
|
-
options.progress?.onJobStart?.({
|
|
1228
|
-
job: job.job,
|
|
1229
|
-
startedAt
|
|
1230
|
-
});
|
|
1231
|
-
settledOutcomes[index] = await executeRuleJob(job, {
|
|
1232
|
-
cache: cacheContext,
|
|
1233
|
-
cacheOnly: options.cacheOnly,
|
|
1234
|
-
clock,
|
|
1235
|
-
progress: options.progress,
|
|
1236
|
-
runSignal: options.signal,
|
|
1237
|
-
startedAt,
|
|
1238
|
-
timeoutMs
|
|
1239
|
-
});
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
if (!infrastructureFailed) {
|
|
1242
|
-
infrastructureError = error;
|
|
1243
|
-
infrastructureFailed = true;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1061
|
+
//#region src/models/resolve.ts
|
|
1062
|
+
function resolveModel(registry, options = {}) {
|
|
1063
|
+
const candidates = flattenModels(registry);
|
|
1064
|
+
const ruleId = options.ruleId ?? "<unknown>";
|
|
1065
|
+
if (options.request !== void 0) {
|
|
1066
|
+
const request = options.request;
|
|
1067
|
+
const canonicalCandidates = candidates.filter((candidate) => canonicalIdentity(candidate) === request);
|
|
1068
|
+
if (canonicalCandidates.length > 1) throw duplicateDefinitionError(request);
|
|
1069
|
+
let candidate = canonicalCandidates[0];
|
|
1070
|
+
if (candidate === void 0) {
|
|
1071
|
+
const matchingCandidates = candidates.filter((candidate) => matchesRequest(candidate, request));
|
|
1072
|
+
const matchesByCanonical = /* @__PURE__ */ new Map();
|
|
1073
|
+
for (const matchingCandidate of matchingCandidates) {
|
|
1074
|
+
const identity = canonicalIdentity(matchingCandidate);
|
|
1075
|
+
const matches = matchesByCanonical.get(identity) ?? [];
|
|
1076
|
+
matches.push(matchingCandidate);
|
|
1077
|
+
matchesByCanonical.set(identity, matches);
|
|
1246
1078
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1079
|
+
const duplicateIdentity = [...matchesByCanonical].find(([, matches]) => matches.length > 1)?.[0];
|
|
1080
|
+
if (duplicateIdentity !== void 0) throw duplicateDefinitionError(duplicateIdentity);
|
|
1081
|
+
if (matchingCandidates.length === 0) throw new Error(`Unknown model "${request}".`);
|
|
1082
|
+
if (matchingCandidates.length > 1) {
|
|
1083
|
+
const choices = [...matchesByCanonical.keys()];
|
|
1084
|
+
throw new Error(`Ambiguous model "${request}".\nSpecify a provider-qualified model:\n${choices.map((choice) => ` ${choice}`).join("\n")}`);
|
|
1085
|
+
}
|
|
1086
|
+
candidate = matchingCandidates[0];
|
|
1087
|
+
}
|
|
1088
|
+
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
1089
|
+
return toResolvedModel(candidate, options.requirement);
|
|
1251
1090
|
}
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1091
|
+
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
1092
|
+
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
1093
|
+
return toResolvedModel(candidate, options.requirement);
|
|
1094
|
+
}
|
|
1095
|
+
function canonicalIdentity({ model, provider }) {
|
|
1096
|
+
return `${provider.id}/${model.id}`;
|
|
1097
|
+
}
|
|
1098
|
+
function duplicateDefinitionError(identity) {
|
|
1099
|
+
return /* @__PURE__ */ new Error(`Model "${identity}" is configured more than once.\nRemove duplicate provider/model definitions from the setup configuration.`);
|
|
1100
|
+
}
|
|
1101
|
+
function flattenModels(registry) {
|
|
1102
|
+
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
1103
|
+
model,
|
|
1104
|
+
provider
|
|
1105
|
+
})));
|
|
1106
|
+
}
|
|
1107
|
+
function matchesRequest(candidate, request) {
|
|
1108
|
+
return [
|
|
1109
|
+
candidate.model.id,
|
|
1110
|
+
candidate.model.name,
|
|
1111
|
+
...candidate.model.aliases ?? []
|
|
1112
|
+
].filter((name) => name !== void 0).some((name) => name === request || `${candidate.provider.id}/${name}` === request);
|
|
1113
|
+
}
|
|
1114
|
+
function preferSize(candidates, requirement) {
|
|
1115
|
+
if (requirement?.size === void 0) return candidates[0];
|
|
1116
|
+
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
1117
|
+
}
|
|
1118
|
+
function satisfiesHardRequirements(model, requirement) {
|
|
1119
|
+
if (requirement === void 0) return true;
|
|
1120
|
+
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
1121
|
+
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
function toResolvedModel(candidate, requirement) {
|
|
1125
|
+
const { model, provider } = candidate;
|
|
1126
|
+
return {
|
|
1127
|
+
aliases: [...model.aliases ?? []],
|
|
1128
|
+
capabilities: [...model.capabilities ?? []],
|
|
1129
|
+
contextWindow: model.contextWindow,
|
|
1130
|
+
id: model.id,
|
|
1131
|
+
name: model.name ?? model.id,
|
|
1132
|
+
params: {
|
|
1133
|
+
...model.defaultParams ?? {},
|
|
1134
|
+
...requirement?.params ?? {}
|
|
1256
1135
|
},
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
diagnostics: outcomes.flatMap((outcome) => outcome.bucket.diagnostics),
|
|
1265
|
-
execution: executionCounts(outcomes),
|
|
1266
|
-
usage: runUsage(outcomes)
|
|
1136
|
+
provider: {
|
|
1137
|
+
endpoint: provider.endpoint,
|
|
1138
|
+
headers: { ...provider.headers ?? {} },
|
|
1139
|
+
id: provider.id,
|
|
1140
|
+
type: provider.type
|
|
1141
|
+
},
|
|
1142
|
+
size: model.size
|
|
1267
1143
|
};
|
|
1268
|
-
options.progress?.onRunEnd?.({
|
|
1269
|
-
diagnostics: result.diagnostics,
|
|
1270
|
-
endedAt: clock(),
|
|
1271
|
-
execution: result.execution,
|
|
1272
|
-
startedAt: runStartedAt,
|
|
1273
|
-
usage: result.usage
|
|
1274
|
-
});
|
|
1275
|
-
if (infrastructureFailed) throw infrastructureError;
|
|
1276
|
-
if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
|
|
1277
|
-
if (failures.length > 0) throw new AlintRunError(`${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
|
|
1278
|
-
cause: new AggregateError(failedOutcomes.map((outcome) => outcome.cause), "Rule execution failures."),
|
|
1279
|
-
failures
|
|
1280
|
-
});
|
|
1281
|
-
return result;
|
|
1282
1144
|
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/core/execution/runtime.ts
|
|
1283
1147
|
function createRuleRuntimes(options) {
|
|
1284
|
-
return options.
|
|
1148
|
+
return options.rules.map(({ enabledRule, ruleIndex }) => {
|
|
1285
1149
|
const executionState = new AsyncLocalStorage();
|
|
1286
1150
|
const context = {
|
|
1287
1151
|
agent: options.effectiveAgent ? withAgentRetry((request) => options.effectiveAgent({
|
|
1288
1152
|
...request,
|
|
1289
1153
|
signal: combineAbortSignals(executionState.getStore()?.signal, request.signal)
|
|
1290
|
-
}), options.
|
|
1154
|
+
}), options.runOptions.runner?.agentRetries, { onRetry: ({ attempt, maxAttempts }) => {
|
|
1291
1155
|
const state = executionState.getStore();
|
|
1292
|
-
if (!state) return;
|
|
1156
|
+
if (!state || state.sealed) return;
|
|
1293
1157
|
const startedAt = Date.now();
|
|
1294
1158
|
try {
|
|
1295
1159
|
options.progress?.onJobRetry?.({
|
|
1296
1160
|
attempt,
|
|
1297
|
-
job: state.
|
|
1161
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1298
1162
|
maxAttempts,
|
|
1163
|
+
progress: state.runProgress.snapshot(),
|
|
1299
1164
|
startedAt
|
|
1300
1165
|
});
|
|
1301
1166
|
} catch (cause) {
|
|
@@ -1313,15 +1178,17 @@ function createRuleRuntimes(options) {
|
|
|
1313
1178
|
metering: { recordUsage: (record) => {
|
|
1314
1179
|
const state = executionState.getStore();
|
|
1315
1180
|
if (!state) throw new Error("Cannot record usage outside an active rule job.");
|
|
1316
|
-
|
|
1181
|
+
if (state.sealed) return;
|
|
1182
|
+
const usageRecord = snapshotUsage({
|
|
1317
1183
|
...record,
|
|
1318
1184
|
ruleId: record.ruleId ?? enabledRule.id
|
|
1319
|
-
};
|
|
1185
|
+
});
|
|
1320
1186
|
state.bucket.usage.push(usageRecord);
|
|
1321
1187
|
try {
|
|
1322
1188
|
options.progress?.onUsage?.({
|
|
1323
|
-
job: state.
|
|
1324
|
-
|
|
1189
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1190
|
+
progress: state.runProgress.snapshot(),
|
|
1191
|
+
record: snapshotUsage(usageRecord)
|
|
1325
1192
|
});
|
|
1326
1193
|
} catch (cause) {
|
|
1327
1194
|
if (!state.reporterFailed) {
|
|
@@ -1332,7 +1199,7 @@ function createRuleRuntimes(options) {
|
|
|
1332
1199
|
}
|
|
1333
1200
|
} },
|
|
1334
1201
|
model: async (selector) => {
|
|
1335
|
-
const request = options.
|
|
1202
|
+
const request = options.runOptions.modelOverride ?? (typeof selector === "string" ? selector : void 0);
|
|
1336
1203
|
const requirement = mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector);
|
|
1337
1204
|
const resolvedModel = resolveModel(options.setupConfig, {
|
|
1338
1205
|
request,
|
|
@@ -1340,19 +1207,20 @@ function createRuleRuntimes(options) {
|
|
|
1340
1207
|
ruleId: enabledRule.id
|
|
1341
1208
|
});
|
|
1342
1209
|
const state = executionState.getStore();
|
|
1343
|
-
if (state) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
1210
|
+
if (state && !state.sealed) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
1344
1211
|
return resolvedModel;
|
|
1345
1212
|
},
|
|
1346
1213
|
options: enabledRule.options,
|
|
1347
|
-
outputLanguage: options.
|
|
1214
|
+
outputLanguage: options.runOptions.outputLanguage,
|
|
1348
1215
|
report: (descriptor) => {
|
|
1349
1216
|
const state = executionState.getStore();
|
|
1350
1217
|
if (!state) throw new Error("Cannot report a diagnostic outside an active rule job.");
|
|
1218
|
+
if (state.sealed) return;
|
|
1351
1219
|
const filePath = descriptor.filePath ?? state.activeFilePath;
|
|
1352
1220
|
if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
|
|
1353
1221
|
const diagnosticModel = state.currentModel ? { ...state.currentModel } : void 0;
|
|
1354
1222
|
state.currentModel = void 0;
|
|
1355
|
-
const diagnostic = {
|
|
1223
|
+
const diagnostic = snapshotDiagnostic({
|
|
1356
1224
|
evidence: descriptor.evidence,
|
|
1357
1225
|
filePath,
|
|
1358
1226
|
loc: descriptor.loc,
|
|
@@ -1360,12 +1228,13 @@ function createRuleRuntimes(options) {
|
|
|
1360
1228
|
model: diagnosticModel,
|
|
1361
1229
|
ruleId: enabledRule.id,
|
|
1362
1230
|
severity: enabledRule.severity
|
|
1363
|
-
};
|
|
1231
|
+
});
|
|
1364
1232
|
state.bucket.diagnostics.push(diagnostic);
|
|
1365
1233
|
try {
|
|
1366
1234
|
options.progress?.onDiagnostic?.({
|
|
1367
|
-
diagnostic,
|
|
1368
|
-
job: state.
|
|
1235
|
+
diagnostic: snapshotDiagnostic(diagnostic),
|
|
1236
|
+
job: snapshotProgressJobRef(state.jobRef),
|
|
1237
|
+
progress: state.runProgress.snapshot()
|
|
1369
1238
|
});
|
|
1370
1239
|
} catch (cause) {
|
|
1371
1240
|
if (!state.reporterFailed) {
|
|
@@ -1395,26 +1264,13 @@ function createRuleRuntimes(options) {
|
|
|
1395
1264
|
model: enabledRule.rule.model,
|
|
1396
1265
|
options: enabledRule.options,
|
|
1397
1266
|
severity: enabledRule.severity
|
|
1398
|
-
})
|
|
1267
|
+
}),
|
|
1268
|
+
ruleIndex
|
|
1399
1269
|
};
|
|
1400
1270
|
});
|
|
1401
1271
|
}
|
|
1402
|
-
function executionCounts(outcomes) {
|
|
1403
|
-
const counts = {
|
|
1404
|
-
cached: 0,
|
|
1405
|
-
cancelled: 0,
|
|
1406
|
-
completed: 0,
|
|
1407
|
-
failed: 0,
|
|
1408
|
-
planned: outcomes.length,
|
|
1409
|
-
queued: 0,
|
|
1410
|
-
running: 0,
|
|
1411
|
-
skipped: 0
|
|
1412
|
-
};
|
|
1413
|
-
for (const outcome of outcomes) counts[outcome.state] += 1;
|
|
1414
|
-
return counts;
|
|
1415
|
-
}
|
|
1416
1272
|
function mergeCapabilities(base, extra) {
|
|
1417
|
-
if (!base && !extra) return;
|
|
1273
|
+
if (!base && !extra) return void 0;
|
|
1418
1274
|
return [.../* @__PURE__ */ new Set([...base ?? [], ...extra ?? []])];
|
|
1419
1275
|
}
|
|
1420
1276
|
function mergeMinContextWindow(base, extra) {
|
|
@@ -1423,7 +1279,7 @@ function mergeMinContextWindow(base, extra) {
|
|
|
1423
1279
|
return Math.max(base, extra);
|
|
1424
1280
|
}
|
|
1425
1281
|
function mergeModelRequirement(base, extra) {
|
|
1426
|
-
if (!base && !extra) return;
|
|
1282
|
+
if (!base && !extra) return void 0;
|
|
1427
1283
|
const capabilities = mergeCapabilities(base?.capabilities, extra?.capabilities);
|
|
1428
1284
|
const params = {
|
|
1429
1285
|
...base?.params ?? {},
|
|
@@ -1436,36 +1292,1080 @@ function mergeModelRequirement(base, extra) {
|
|
|
1436
1292
|
size: extra?.size ?? base?.size
|
|
1437
1293
|
};
|
|
1438
1294
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
cacheContext.store.markFile(file.path, hashText(file.text), entries);
|
|
1446
|
-
}
|
|
1447
|
-
await cacheContext.store.reconcile();
|
|
1448
|
-
} catch {}
|
|
1295
|
+
function toDiagnosticModel(model, request) {
|
|
1296
|
+
return {
|
|
1297
|
+
providerId: model.provider.id,
|
|
1298
|
+
requested: request,
|
|
1299
|
+
resolvedId: model.id
|
|
1300
|
+
};
|
|
1449
1301
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1302
|
+
//#endregion
|
|
1303
|
+
//#region src/core/execution/scheduler.ts
|
|
1304
|
+
var RuleScheduler = class {
|
|
1305
|
+
options;
|
|
1306
|
+
accepting = true;
|
|
1307
|
+
active = 0;
|
|
1308
|
+
cancellingQueued = false;
|
|
1309
|
+
closePromise;
|
|
1310
|
+
closeReject;
|
|
1311
|
+
closeResolve;
|
|
1312
|
+
infrastructureError;
|
|
1313
|
+
infrastructureFailed = false;
|
|
1314
|
+
lanes = [];
|
|
1315
|
+
nextAdmissionIndex = 1;
|
|
1316
|
+
pumpQueued = false;
|
|
1317
|
+
readyLanes = [];
|
|
1318
|
+
constructor(options) {
|
|
1319
|
+
this.options = options;
|
|
1320
|
+
resolveRuleConcurrency(options.concurrency);
|
|
1321
|
+
if (options.signal?.aborted) this.accepting = false;
|
|
1322
|
+
else options.signal?.addEventListener("abort", this.abort, { once: true });
|
|
1323
|
+
}
|
|
1324
|
+
cancelWithError(error) {
|
|
1325
|
+
this.fail(error);
|
|
1326
|
+
}
|
|
1327
|
+
close() {
|
|
1328
|
+
this.accepting = false;
|
|
1329
|
+
if (!this.closePromise) this.closePromise = new Promise((resolve, reject) => {
|
|
1330
|
+
this.closeResolve = resolve;
|
|
1331
|
+
this.closeReject = reject;
|
|
1332
|
+
});
|
|
1333
|
+
this.queuePump();
|
|
1334
|
+
this.settleClose();
|
|
1335
|
+
return this.closePromise;
|
|
1336
|
+
}
|
|
1337
|
+
schedule(jobs) {
|
|
1338
|
+
if (!this.accepting || jobs.length === 0) return {
|
|
1339
|
+
jobsAdded: 0,
|
|
1340
|
+
outcomes: Promise.resolve([])
|
|
1341
|
+
};
|
|
1342
|
+
const lane = createLane();
|
|
1343
|
+
this.lanes.push(lane);
|
|
1344
|
+
let jobsAdded = 0;
|
|
1345
|
+
for (const job of jobs) {
|
|
1346
|
+
if (!this.accepting) break;
|
|
1347
|
+
const index = jobsAdded;
|
|
1348
|
+
try {
|
|
1349
|
+
job.jobRef.index = this.nextAdmissionIndex;
|
|
1350
|
+
this.nextAdmissionIndex += 1;
|
|
1351
|
+
const progress = this.options.progress.queue();
|
|
1352
|
+
jobsAdded += 1;
|
|
1353
|
+
lane.remaining += 1;
|
|
1354
|
+
lane.items.push({
|
|
1355
|
+
index,
|
|
1356
|
+
job,
|
|
1357
|
+
lane
|
|
1358
|
+
});
|
|
1359
|
+
this.options.reporter?.onJobQueued?.({
|
|
1360
|
+
job: snapshotProgressJobRef(job.jobRef),
|
|
1361
|
+
progress
|
|
1362
|
+
});
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
this.fail(error);
|
|
1365
|
+
throw error;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
lane.outcomes.length = jobsAdded;
|
|
1369
|
+
if (jobsAdded === 0) this.settleLane(lane);
|
|
1370
|
+
else {
|
|
1371
|
+
this.enqueue(lane);
|
|
1372
|
+
this.queuePump();
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
jobsAdded,
|
|
1376
|
+
outcomes: lane.promise
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
snapshot() {
|
|
1380
|
+
return this.options.progress.snapshot();
|
|
1381
|
+
}
|
|
1382
|
+
abort = () => {
|
|
1383
|
+
this.accepting = false;
|
|
1384
|
+
this.cancelQueued();
|
|
1385
|
+
this.settleClose();
|
|
1386
|
+
};
|
|
1387
|
+
cancel(item, from, startedAt) {
|
|
1388
|
+
const outcome = cancelledOutcome(item.job);
|
|
1389
|
+
const progress = this.options.progress.finish(from, "cancelled");
|
|
1390
|
+
item.lane.outcomes[item.index] = outcome;
|
|
1391
|
+
item.lane.remaining -= 1;
|
|
1392
|
+
try {
|
|
1393
|
+
this.options.reporter?.onJobEnd?.({
|
|
1394
|
+
cache: "miss",
|
|
1395
|
+
endedAt: this.options.clock(),
|
|
1396
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1397
|
+
progress,
|
|
1398
|
+
startedAt,
|
|
1399
|
+
state: "cancelled"
|
|
1400
|
+
});
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
this.fail(error);
|
|
1403
|
+
}
|
|
1404
|
+
this.settleLane(item.lane);
|
|
1405
|
+
}
|
|
1406
|
+
cancelQueued() {
|
|
1407
|
+
if (this.cancellingQueued) return;
|
|
1408
|
+
this.cancellingQueued = true;
|
|
1409
|
+
this.readyLanes = [];
|
|
1410
|
+
for (const lane of this.lanes) lane.ready = false;
|
|
1411
|
+
try {
|
|
1412
|
+
for (const lane of [...this.lanes]) {
|
|
1413
|
+
const queued = lane.items.splice(0);
|
|
1414
|
+
for (const item of queued) this.cancel(item, "queued");
|
|
1415
|
+
}
|
|
1416
|
+
} finally {
|
|
1417
|
+
this.cancellingQueued = false;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
enqueue(lane) {
|
|
1421
|
+
if (lane.ready || lane.settled || lane.items.length === 0) return;
|
|
1422
|
+
lane.ready = true;
|
|
1423
|
+
this.readyLanes.push(lane);
|
|
1424
|
+
}
|
|
1425
|
+
fail(error) {
|
|
1426
|
+
this.recordFailure(error);
|
|
1427
|
+
this.accepting = false;
|
|
1428
|
+
this.cancelQueued();
|
|
1429
|
+
this.settleClose();
|
|
1430
|
+
}
|
|
1431
|
+
finish(item, outcome, startedAt) {
|
|
1432
|
+
const progress = this.options.progress.finish("running", outcome.state);
|
|
1433
|
+
item.lane.outcomes[item.index] = outcome;
|
|
1434
|
+
item.lane.remaining -= 1;
|
|
1435
|
+
try {
|
|
1436
|
+
this.options.reporter?.onJobEnd?.({
|
|
1437
|
+
cache: outcome.cache,
|
|
1438
|
+
endedAt: this.options.clock(),
|
|
1439
|
+
failure: outcome.failure ? snapshotFailure(outcome.failure) : void 0,
|
|
1440
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1441
|
+
progress,
|
|
1442
|
+
startedAt,
|
|
1443
|
+
state: outcome.state
|
|
1444
|
+
});
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
this.fail(error);
|
|
1447
|
+
}
|
|
1448
|
+
this.settleLane(item.lane);
|
|
1449
|
+
}
|
|
1450
|
+
next() {
|
|
1451
|
+
while (this.readyLanes.length > 0) {
|
|
1452
|
+
const lane = this.readyLanes.shift();
|
|
1453
|
+
lane.ready = false;
|
|
1454
|
+
const item = lane.items.shift();
|
|
1455
|
+
this.enqueue(lane);
|
|
1456
|
+
if (item) return item;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
pump() {
|
|
1460
|
+
this.pumpQueued = false;
|
|
1461
|
+
if (this.infrastructureFailed || this.options.signal?.aborted) {
|
|
1462
|
+
this.cancelQueued();
|
|
1463
|
+
this.settleClose();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
while (this.active < this.options.concurrency) {
|
|
1467
|
+
const item = this.next();
|
|
1468
|
+
if (!item) break;
|
|
1469
|
+
let startedAt;
|
|
1470
|
+
try {
|
|
1471
|
+
startedAt = this.options.clock();
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
this.fail(error);
|
|
1474
|
+
this.cancel(item, "queued");
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
const progress = this.options.progress.start();
|
|
1479
|
+
this.options.reporter?.onJobStart?.({
|
|
1480
|
+
job: snapshotProgressJobRef(item.job.jobRef),
|
|
1481
|
+
progress,
|
|
1482
|
+
startedAt
|
|
1483
|
+
});
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
this.fail(error);
|
|
1486
|
+
this.cancel(item, "running");
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
this.active += 1;
|
|
1490
|
+
this.run(item, startedAt);
|
|
1491
|
+
}
|
|
1492
|
+
this.settleClose();
|
|
1493
|
+
}
|
|
1494
|
+
queuePump() {
|
|
1495
|
+
if (this.pumpQueued) return;
|
|
1496
|
+
this.pumpQueued = true;
|
|
1497
|
+
queueMicrotask(() => this.pump());
|
|
1498
|
+
}
|
|
1499
|
+
recordFailure(error) {
|
|
1500
|
+
if (this.infrastructureFailed) return;
|
|
1501
|
+
this.infrastructureFailed = true;
|
|
1502
|
+
this.infrastructureError = error;
|
|
1503
|
+
}
|
|
1504
|
+
removeLane(lane) {
|
|
1505
|
+
const index = this.lanes.indexOf(lane);
|
|
1506
|
+
if (index < 0) return;
|
|
1507
|
+
this.lanes.splice(index, 1);
|
|
1508
|
+
if (lane.ready) {
|
|
1509
|
+
const readyIndex = this.readyLanes.indexOf(lane);
|
|
1510
|
+
if (readyIndex >= 0) this.readyLanes.splice(readyIndex, 1);
|
|
1511
|
+
lane.ready = false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
async run(item, startedAt) {
|
|
1515
|
+
try {
|
|
1516
|
+
const outcome = await this.options.execute(item.job, startedAt);
|
|
1517
|
+
this.finish(item, outcome, startedAt);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
this.fail(error);
|
|
1520
|
+
this.cancel(item, "running", startedAt);
|
|
1521
|
+
} finally {
|
|
1522
|
+
this.active -= 1;
|
|
1523
|
+
this.queuePump();
|
|
1524
|
+
this.settleClose();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
settleClose() {
|
|
1528
|
+
if (!this.closePromise || this.active !== 0 || this.lanes.length !== 0) return;
|
|
1529
|
+
this.options.signal?.removeEventListener("abort", this.abort);
|
|
1530
|
+
if (this.infrastructureFailed) this.closeReject?.(this.infrastructureError);
|
|
1531
|
+
else this.closeResolve?.();
|
|
1532
|
+
}
|
|
1533
|
+
settleLane(lane) {
|
|
1534
|
+
if (lane.settled || lane.remaining !== 0) return;
|
|
1535
|
+
lane.settled = true;
|
|
1536
|
+
this.removeLane(lane);
|
|
1537
|
+
lane.resolve(lane.outcomes.filter((outcome) => outcome !== void 0));
|
|
1538
|
+
this.settleClose();
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
function cancelledOutcome(job) {
|
|
1542
|
+
return {
|
|
1543
|
+
cache: "miss",
|
|
1544
|
+
diagnostics: [],
|
|
1545
|
+
jobRef: snapshotProgressJobRef(job.jobRef),
|
|
1546
|
+
orderKey: { ...job.orderKey },
|
|
1547
|
+
state: "cancelled",
|
|
1548
|
+
usage: []
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function resolveRuleConcurrency(ruleConcurrency) {
|
|
1552
|
+
if (ruleConcurrency === void 0) return 4;
|
|
1452
1553
|
if (!Number.isFinite(ruleConcurrency) || !Number.isInteger(ruleConcurrency) || ruleConcurrency <= 0) throw new TypeError("Rule execution concurrency must be a finite positive integer.");
|
|
1453
1554
|
return ruleConcurrency;
|
|
1454
1555
|
}
|
|
1556
|
+
function createLane() {
|
|
1557
|
+
let resolve;
|
|
1558
|
+
return {
|
|
1559
|
+
items: [],
|
|
1560
|
+
outcomes: [],
|
|
1561
|
+
promise: new Promise((resolvePromise) => {
|
|
1562
|
+
resolve = resolvePromise;
|
|
1563
|
+
}),
|
|
1564
|
+
ready: false,
|
|
1565
|
+
remaining: 0,
|
|
1566
|
+
resolve,
|
|
1567
|
+
settled: false
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
//#endregion
|
|
1571
|
+
//#region src/dsl/registry.ts
|
|
1572
|
+
function buildRuleRegistry(config) {
|
|
1573
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1574
|
+
const localIds = /* @__PURE__ */ new Map();
|
|
1575
|
+
const enabledRules = [];
|
|
1576
|
+
for (const [alias, plugin] of Object.entries(config.plugins)) for (const [localId, rule] of Object.entries(plugin.rules ?? {})) {
|
|
1577
|
+
const id = `${alias}/${localId}`;
|
|
1578
|
+
if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
|
|
1579
|
+
rules.set(id, rule);
|
|
1580
|
+
localIds.set(id, localId);
|
|
1581
|
+
}
|
|
1582
|
+
for (const [id, entry] of Object.entries(config.rules)) {
|
|
1583
|
+
const rule = rules.get(id);
|
|
1584
|
+
if (!rule) throw new Error(`Unknown rule "${id}".`);
|
|
1585
|
+
const normalizedEntry = normalizeRuleConfigEntry(entry);
|
|
1586
|
+
const severity = normalizedEntry.severity;
|
|
1587
|
+
if (severity === "off") continue;
|
|
1588
|
+
enabledRules.push({
|
|
1589
|
+
id,
|
|
1590
|
+
localId: localIds.get(id) ?? id,
|
|
1591
|
+
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
1592
|
+
rule,
|
|
1593
|
+
severity
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
enabledRules,
|
|
1598
|
+
rules
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function formatRuleOptionIssues(index, issues) {
|
|
1602
|
+
return issues.map((issue) => {
|
|
1603
|
+
const path = getDotPath(issue);
|
|
1604
|
+
return path === null ? `"${index}": ${issue.message}` : `"${index}.${path}": ${issue.message}`;
|
|
1605
|
+
}).join("; ");
|
|
1606
|
+
}
|
|
1607
|
+
function isRuleConfigTuple(entry) {
|
|
1608
|
+
return Array.isArray(entry);
|
|
1609
|
+
}
|
|
1610
|
+
function normalizeRuleConfigEntry(entry) {
|
|
1611
|
+
if (isRuleConfigTuple(entry)) {
|
|
1612
|
+
const [severity = "warn", ...options] = entry;
|
|
1613
|
+
return {
|
|
1614
|
+
options,
|
|
1615
|
+
severity
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
return {
|
|
1619
|
+
options: [],
|
|
1620
|
+
severity: entry ?? "warn"
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
function parseRuleOption(id, index, schema, value) {
|
|
1624
|
+
const result = safeParse(schema, value);
|
|
1625
|
+
if (result.success) return result.output;
|
|
1626
|
+
if (value === void 0) {
|
|
1627
|
+
const emptyObjectResult = safeParse(schema, {});
|
|
1628
|
+
if (emptyObjectResult.success) return emptyObjectResult.output;
|
|
1629
|
+
}
|
|
1630
|
+
throw new TypeError(`Invalid options for rule "${id}": ${formatRuleOptionIssues(index, result.issues)}`);
|
|
1631
|
+
}
|
|
1632
|
+
function parseRuleOptions(id, schema, options) {
|
|
1633
|
+
if (!schema) {
|
|
1634
|
+
if (options.length > 0) throw new TypeError(`Rule "${id}" does not accept options.`);
|
|
1635
|
+
return [];
|
|
1636
|
+
}
|
|
1637
|
+
if (options.length > schema.length) throw new TypeError(`Invalid options for rule "${id}": Unexpected option at index ${schema.length}.`);
|
|
1638
|
+
return schema.map((optionSchema, index) => parseRuleOption(id, index, optionSchema, options[index]));
|
|
1639
|
+
}
|
|
1640
|
+
//#endregion
|
|
1641
|
+
//#region src/core/preparation.ts
|
|
1642
|
+
function prepareRun(options = {}) {
|
|
1643
|
+
const cwd$2 = options.cwd ?? cwd();
|
|
1644
|
+
const config = options.config ?? [];
|
|
1645
|
+
const files = [];
|
|
1646
|
+
const directories = [];
|
|
1647
|
+
for (const filePath of options.files ?? []) {
|
|
1648
|
+
const path = resolve(cwd$2, filePath);
|
|
1649
|
+
const resolvedConfig = resolveConfigForFile(path, config, { cwd: cwd$2 });
|
|
1650
|
+
if (resolvedConfig.ignored) continue;
|
|
1651
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1652
|
+
const language = resolveLanguageForPath(path, createLanguageRegistry(effectiveConfig), { language: effectiveConfig.language });
|
|
1653
|
+
files.push({
|
|
1654
|
+
agent: effectiveConfig.agent,
|
|
1655
|
+
configHash: stableHash({
|
|
1656
|
+
language: effectiveConfig.language,
|
|
1657
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1658
|
+
processor: effectiveConfig.processor,
|
|
1659
|
+
resolvedLanguage: language.name,
|
|
1660
|
+
settings: effectiveConfig.settings
|
|
1661
|
+
}),
|
|
1662
|
+
fileIndex: files.length,
|
|
1663
|
+
language,
|
|
1664
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1665
|
+
path,
|
|
1666
|
+
rules: prepareRules(effectiveConfig),
|
|
1667
|
+
settings: effectiveConfig.settings
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
for (const directoryPath of options.directories ?? []) {
|
|
1671
|
+
const path = resolve(cwd$2, directoryPath);
|
|
1672
|
+
const resolvedConfig = resolveConfigForDirectory(path, config, { cwd: cwd$2 });
|
|
1673
|
+
if (resolvedConfig.ignored) continue;
|
|
1674
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1675
|
+
directories.push({
|
|
1676
|
+
agent: effectiveConfig.agent,
|
|
1677
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1678
|
+
directoryIndex: directories.length,
|
|
1679
|
+
rules: prepareRules(effectiveConfig),
|
|
1680
|
+
settings: effectiveConfig.settings,
|
|
1681
|
+
target: {
|
|
1682
|
+
kind: "directory",
|
|
1683
|
+
path
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
directories,
|
|
1689
|
+
files,
|
|
1690
|
+
project: options.projectTargets === false ? void 0 : prepareProject(cwd$2, config)
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function createLanguageRegistry(config) {
|
|
1694
|
+
const registry = createBuiltInLanguageRegistry();
|
|
1695
|
+
for (const plugin of Object.values(config.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(registry, language);
|
|
1696
|
+
return registry;
|
|
1697
|
+
}
|
|
1698
|
+
function prepareProject(root, config) {
|
|
1699
|
+
const resolvedConfig = resolveConfigForProject(root, config, { cwd: root });
|
|
1700
|
+
if (resolvedConfig.ignored) return void 0;
|
|
1701
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1702
|
+
return {
|
|
1703
|
+
agent: effectiveConfig.agent,
|
|
1704
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1705
|
+
root,
|
|
1706
|
+
rules: prepareRules(effectiveConfig),
|
|
1707
|
+
settings: effectiveConfig.settings
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function prepareRules(config) {
|
|
1711
|
+
return buildRuleRegistry(config).enabledRules.map((enabledRule, ruleIndex) => ({
|
|
1712
|
+
enabledRule,
|
|
1713
|
+
ruleIndex
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
//#endregion
|
|
1717
|
+
//#region src/core/project/index.ts
|
|
1718
|
+
var ProjectIndexBuilder = class {
|
|
1719
|
+
files = [];
|
|
1720
|
+
hash;
|
|
1721
|
+
hasher = createStableHasher();
|
|
1722
|
+
nextFileIndex = 0;
|
|
1723
|
+
pending = /* @__PURE__ */ new Map();
|
|
1724
|
+
root;
|
|
1725
|
+
targets = [];
|
|
1726
|
+
constructor(root) {
|
|
1727
|
+
this.root = root;
|
|
1728
|
+
this.hasher.update({ root });
|
|
1729
|
+
}
|
|
1730
|
+
add(snapshot) {
|
|
1731
|
+
if (this.hash !== void 0) throw new Error("Cannot add a project file after the index has been built.");
|
|
1732
|
+
if (!Number.isSafeInteger(snapshot.fileIndex) || snapshot.fileIndex < 0) throw new TypeError("Project file index must be a non-negative safe integer.");
|
|
1733
|
+
if (snapshot.fileIndex < this.nextFileIndex || this.pending.has(snapshot.fileIndex)) throw new Error(`Project file index ${snapshot.fileIndex} has already been added.`);
|
|
1734
|
+
this.pending.set(snapshot.fileIndex, copySnapshot(snapshot));
|
|
1735
|
+
this.flushContiguousSnapshots();
|
|
1736
|
+
}
|
|
1737
|
+
build() {
|
|
1738
|
+
if (this.pending.size > 0) throw new Error(`Cannot build project index with a gap at file index ${this.nextFileIndex}.`);
|
|
1739
|
+
this.hash ??= this.hasher.digest();
|
|
1740
|
+
return {
|
|
1741
|
+
hash: this.hash,
|
|
1742
|
+
target: {
|
|
1743
|
+
files: this.files.map(copyFile),
|
|
1744
|
+
kind: "project",
|
|
1745
|
+
root: this.root,
|
|
1746
|
+
targets: this.targets.map(copyDescriptor)
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
flushContiguousSnapshots() {
|
|
1751
|
+
let snapshot = this.pending.get(this.nextFileIndex);
|
|
1752
|
+
while (snapshot) {
|
|
1753
|
+
this.pending.delete(this.nextFileIndex);
|
|
1754
|
+
this.files.push(copyFile(snapshot.file));
|
|
1755
|
+
this.targets.push(...snapshot.targets.map((target) => copyDescriptor(target.descriptor)));
|
|
1756
|
+
this.hasher.update({
|
|
1757
|
+
configHash: snapshot.configHash,
|
|
1758
|
+
file: snapshot.file,
|
|
1759
|
+
targets: snapshot.targets.map((target) => ({
|
|
1760
|
+
descriptor: target.descriptor,
|
|
1761
|
+
semanticHash: target.semanticHash
|
|
1762
|
+
}))
|
|
1763
|
+
});
|
|
1764
|
+
this.nextFileIndex += 1;
|
|
1765
|
+
snapshot = this.pending.get(this.nextFileIndex);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
function createProjectJobs(options) {
|
|
1770
|
+
const executions = options.runtimes.map((runtime) => projectExecution(runtime, options.project.target)).filter((execution) => execution !== void 0);
|
|
1771
|
+
if (executions.length === 0) return { jobs: [] };
|
|
1772
|
+
const owner = options.cacheStore.beginOwner({
|
|
1773
|
+
kind: "project",
|
|
1774
|
+
path: options.project.target.root
|
|
1775
|
+
});
|
|
1776
|
+
const target = {
|
|
1777
|
+
cacheOwner: owner,
|
|
1778
|
+
cacheTargetHash: options.project.hash,
|
|
1779
|
+
configHash: options.configHash,
|
|
1780
|
+
identity: "project",
|
|
1781
|
+
kind: "project",
|
|
1782
|
+
language: "project",
|
|
1783
|
+
text: options.project.hash
|
|
1784
|
+
};
|
|
1785
|
+
return {
|
|
1786
|
+
jobs: executions.map((execution) => {
|
|
1787
|
+
const ruleId = execution.runtime.enabledRule.id;
|
|
1788
|
+
return {
|
|
1789
|
+
execution,
|
|
1790
|
+
jobRef: {
|
|
1791
|
+
id: stableHash({
|
|
1792
|
+
input: options.project.target.root,
|
|
1793
|
+
ruleId,
|
|
1794
|
+
targetIdentity: "project",
|
|
1795
|
+
targetIndex: 0
|
|
1796
|
+
}),
|
|
1797
|
+
index: 0,
|
|
1798
|
+
inputPath: options.project.target.root,
|
|
1799
|
+
ruleId,
|
|
1800
|
+
target: {
|
|
1801
|
+
identity: "project",
|
|
1802
|
+
kind: "project"
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
orderKey: {
|
|
1806
|
+
inputIndex: 0,
|
|
1807
|
+
ruleIndex: execution.runtime.ruleIndex,
|
|
1808
|
+
scope: "project",
|
|
1809
|
+
targetIndex: 0
|
|
1810
|
+
},
|
|
1811
|
+
target
|
|
1812
|
+
};
|
|
1813
|
+
}),
|
|
1814
|
+
owner
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function copyDescriptor(descriptor) {
|
|
1818
|
+
return {
|
|
1819
|
+
filePath: descriptor.filePath,
|
|
1820
|
+
identity: descriptor.identity,
|
|
1821
|
+
kind: descriptor.kind,
|
|
1822
|
+
name: descriptor.name,
|
|
1823
|
+
range: descriptor.range && { ...descriptor.range }
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
function copyFile(file) {
|
|
1827
|
+
return {
|
|
1828
|
+
contentHash: file.contentHash,
|
|
1829
|
+
language: file.language,
|
|
1830
|
+
path: file.path,
|
|
1831
|
+
targetCount: file.targetCount
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
function copySnapshot(snapshot) {
|
|
1835
|
+
return {
|
|
1836
|
+
configHash: snapshot.configHash,
|
|
1837
|
+
file: copyFile(snapshot.file),
|
|
1838
|
+
fileIndex: snapshot.fileIndex,
|
|
1839
|
+
targets: snapshot.targets.map(copyTargetSnapshot)
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
function copyTargetSnapshot(target) {
|
|
1843
|
+
return {
|
|
1844
|
+
descriptor: copyDescriptor(target.descriptor),
|
|
1845
|
+
semanticHash: target.semanticHash
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function projectExecution(runtime, target) {
|
|
1849
|
+
if (runtime.handlers.onTargetWith) return {
|
|
1850
|
+
run: () => runtime.handlers.onTargetWith?.(target),
|
|
1851
|
+
runtime
|
|
1852
|
+
};
|
|
1853
|
+
if (runtime.handlers.onTargetProject) return {
|
|
1854
|
+
run: () => runtime.handlers.onTargetProject?.(target),
|
|
1855
|
+
runtime
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
async function executeSourceSession(input, options) {
|
|
1859
|
+
openSession(options.metrics);
|
|
1860
|
+
try {
|
|
1861
|
+
if (options.signal?.aborted) return { outcomes: [] };
|
|
1862
|
+
let file;
|
|
1863
|
+
try {
|
|
1864
|
+
file = await options.src.readFile(input.path);
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
return {
|
|
1867
|
+
failure: fileFailure(input, "read", failureMessage(error, "Failed to read source file.")),
|
|
1868
|
+
outcomes: []
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (options.signal?.aborted) return { outcomes: [] };
|
|
1872
|
+
let targets;
|
|
1873
|
+
try {
|
|
1874
|
+
targets = await input.language.extract(file, {
|
|
1875
|
+
cwd: options.cwd,
|
|
1876
|
+
languageOptions: input.languageOptions,
|
|
1877
|
+
src: options.src
|
|
1878
|
+
});
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
return {
|
|
1881
|
+
failure: fileFailure(input, "extract", failureMessage(error, "Failed to extract source targets.")),
|
|
1882
|
+
outcomes: []
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
const contentHash = hashText(file.text);
|
|
1886
|
+
const project = options.projectSnapshots === false ? void 0 : createProjectSnapshot(input, file.language, file.path, contentHash, targets);
|
|
1887
|
+
const cacheOwner = options.cacheStore.beginOwner({
|
|
1888
|
+
kind: "file",
|
|
1889
|
+
path: file.path
|
|
1890
|
+
});
|
|
1891
|
+
const jobs = createSourceJobs(input, createExecutionTargets(input, targets, options.ruleRuntimes, options.cwd, cacheOwner));
|
|
1892
|
+
if (options.signal?.aborted) {
|
|
1893
|
+
cacheOwner.commit({
|
|
1894
|
+
contentHash,
|
|
1895
|
+
mode: "merge"
|
|
1896
|
+
});
|
|
1897
|
+
return {
|
|
1898
|
+
outcomes: [],
|
|
1899
|
+
project
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
const batch = options.scheduler.schedule(jobs);
|
|
1903
|
+
try {
|
|
1904
|
+
options.progress?.onFileReady?.({
|
|
1905
|
+
fileIndex: input.fileIndex,
|
|
1906
|
+
inputPath: input.path,
|
|
1907
|
+
jobsAdded: batch.jobsAdded,
|
|
1908
|
+
progress: options.scheduler.snapshot()
|
|
1909
|
+
});
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
options.scheduler.cancelWithError(error);
|
|
1912
|
+
throw error;
|
|
1913
|
+
}
|
|
1914
|
+
const outcomes = await batch.outcomes;
|
|
1915
|
+
cacheOwner.commit(options.signal?.aborted ? {
|
|
1916
|
+
contentHash,
|
|
1917
|
+
mode: "merge"
|
|
1918
|
+
} : { contentHash });
|
|
1919
|
+
return {
|
|
1920
|
+
outcomes,
|
|
1921
|
+
project
|
|
1922
|
+
};
|
|
1923
|
+
} finally {
|
|
1924
|
+
closeSession(options.metrics);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
async function executeSourceSessions(inputs, options) {
|
|
1928
|
+
const results = new Array(inputs.length);
|
|
1929
|
+
let cursor = 0;
|
|
1930
|
+
let firstError;
|
|
1931
|
+
let failed = false;
|
|
1932
|
+
let stopping = false;
|
|
1933
|
+
const workerCount = Math.min(options.sourceWindow, inputs.length);
|
|
1934
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
1935
|
+
while (!stopping && !options.signal?.aborted) {
|
|
1936
|
+
const index = cursor;
|
|
1937
|
+
cursor += 1;
|
|
1938
|
+
const input = inputs[index];
|
|
1939
|
+
if (!input) return;
|
|
1940
|
+
try {
|
|
1941
|
+
results[index] = await executeSourceSession(input, {
|
|
1942
|
+
...options,
|
|
1943
|
+
ruleRuntimes: options.createRuleRuntimes(input)
|
|
1944
|
+
});
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
stopping = true;
|
|
1947
|
+
if (!failed) {
|
|
1948
|
+
failed = true;
|
|
1949
|
+
firstError = error;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
await Promise.all(workers);
|
|
1955
|
+
if (failed) throw firstError;
|
|
1956
|
+
return results.filter((result) => result !== void 0);
|
|
1957
|
+
}
|
|
1958
|
+
function resolveSourceWindow(ruleConcurrency) {
|
|
1959
|
+
return Math.max(1, Math.min(ruleConcurrency, 4));
|
|
1960
|
+
}
|
|
1961
|
+
function closeSession(metrics) {
|
|
1962
|
+
if (!metrics) return;
|
|
1963
|
+
metrics.active -= 1;
|
|
1964
|
+
metrics.closed += 1;
|
|
1965
|
+
}
|
|
1966
|
+
function createExecutionTargets(input, targets, runtimes, cwd, cacheOwner) {
|
|
1967
|
+
const resolveIdentity = createTargetIdentityResolver(targets.map((target) => ({
|
|
1968
|
+
filePath: target.kind === "file" ? normalizeCachePath(cwd, input.path) : void 0,
|
|
1969
|
+
identity: target.identity,
|
|
1970
|
+
kind: target.kind,
|
|
1971
|
+
name: target.name,
|
|
1972
|
+
range: target.range
|
|
1973
|
+
})));
|
|
1974
|
+
return targets.map((target, targetIndex) => {
|
|
1975
|
+
return {
|
|
1976
|
+
executions: runtimes.map((runtime) => sourceExecution(runtime, target)).filter((execution) => execution !== void 0),
|
|
1977
|
+
target: {
|
|
1978
|
+
activeFilePath: input.path,
|
|
1979
|
+
cacheOwner,
|
|
1980
|
+
configHash: input.configHash,
|
|
1981
|
+
identity: resolveIdentity({
|
|
1982
|
+
filePath: target.kind === "file" ? normalizeCachePath(cwd, input.path) : void 0,
|
|
1983
|
+
identity: target.identity,
|
|
1984
|
+
kind: target.kind,
|
|
1985
|
+
name: target.name,
|
|
1986
|
+
range: target.range
|
|
1987
|
+
}, targetIndex),
|
|
1988
|
+
kind: target.kind,
|
|
1989
|
+
language: target.language,
|
|
1990
|
+
loc: target.loc,
|
|
1991
|
+
metadata: target.metadata,
|
|
1992
|
+
name: target.name,
|
|
1993
|
+
origin: target.origin,
|
|
1994
|
+
range: target.range,
|
|
1995
|
+
text: target.text
|
|
1996
|
+
},
|
|
1997
|
+
targetIndex
|
|
1998
|
+
};
|
|
1999
|
+
}).filter((target) => target.executions.length > 0);
|
|
2000
|
+
}
|
|
2001
|
+
function createProjectSnapshot(input, language, path, contentHash, targets) {
|
|
2002
|
+
return {
|
|
2003
|
+
configHash: input.configHash,
|
|
2004
|
+
file: {
|
|
2005
|
+
contentHash,
|
|
2006
|
+
language,
|
|
2007
|
+
path,
|
|
2008
|
+
targetCount: targets.length
|
|
2009
|
+
},
|
|
2010
|
+
fileIndex: input.fileIndex,
|
|
2011
|
+
targets: targets.map((target) => ({
|
|
2012
|
+
descriptor: {
|
|
2013
|
+
filePath: target.file.path,
|
|
2014
|
+
identity: target.identity,
|
|
2015
|
+
kind: target.kind,
|
|
2016
|
+
...target.name === void 0 ? {} : { name: target.name },
|
|
2017
|
+
...target.range === void 0 ? {} : { range: { ...target.range } }
|
|
2018
|
+
},
|
|
2019
|
+
semanticHash: stableHash({
|
|
2020
|
+
language: target.language,
|
|
2021
|
+
loc: target.loc,
|
|
2022
|
+
metadata: target.metadata,
|
|
2023
|
+
name: target.name,
|
|
2024
|
+
origin: target.origin,
|
|
2025
|
+
range: target.range,
|
|
2026
|
+
text: target.text
|
|
2027
|
+
})
|
|
2028
|
+
}))
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function createSourceJobs(input, targets) {
|
|
2032
|
+
return targets.flatMap(({ executions, target, targetIndex }) => executions.map((execution) => {
|
|
2033
|
+
const ruleId = execution.runtime.enabledRule.id;
|
|
2034
|
+
return {
|
|
2035
|
+
execution,
|
|
2036
|
+
jobRef: {
|
|
2037
|
+
id: stableHash({
|
|
2038
|
+
fileIndex: input.fileIndex,
|
|
2039
|
+
input: input.path,
|
|
2040
|
+
ruleId,
|
|
2041
|
+
targetIdentity: target.identity,
|
|
2042
|
+
targetIndex
|
|
2043
|
+
}),
|
|
2044
|
+
index: 0,
|
|
2045
|
+
inputPath: input.path,
|
|
2046
|
+
ruleId,
|
|
2047
|
+
target: {
|
|
2048
|
+
identity: target.identity,
|
|
2049
|
+
kind: target.kind,
|
|
2050
|
+
name: target.name
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
orderKey: {
|
|
2054
|
+
inputIndex: input.fileIndex,
|
|
2055
|
+
ruleIndex: execution.runtime.ruleIndex,
|
|
2056
|
+
scope: "source",
|
|
2057
|
+
targetIndex
|
|
2058
|
+
},
|
|
2059
|
+
target
|
|
2060
|
+
};
|
|
2061
|
+
}));
|
|
2062
|
+
}
|
|
2063
|
+
function failureMessage(error, fallback) {
|
|
2064
|
+
try {
|
|
2065
|
+
return errorMessageFrom(error) ?? fallback;
|
|
2066
|
+
} catch {
|
|
2067
|
+
return fallback;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
function fileFailure(input, kind, message) {
|
|
2071
|
+
return {
|
|
2072
|
+
file: {
|
|
2073
|
+
index: input.fileIndex,
|
|
2074
|
+
path: input.path
|
|
2075
|
+
},
|
|
2076
|
+
kind,
|
|
2077
|
+
message
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
function openSession(metrics) {
|
|
2081
|
+
if (!metrics) return;
|
|
2082
|
+
metrics.active += 1;
|
|
2083
|
+
metrics.opened += 1;
|
|
2084
|
+
metrics.maximumActive = Math.max(metrics.maximumActive, metrics.active);
|
|
2085
|
+
}
|
|
2086
|
+
function sourceExecution(runtime, target) {
|
|
2087
|
+
if (runtime.handlers.onTargetWith) return {
|
|
2088
|
+
run: () => runtime.handlers.onTargetWith?.(target),
|
|
2089
|
+
runtime
|
|
2090
|
+
};
|
|
2091
|
+
if (target.kind === "class" && runtime.handlers.onTargetClass) return {
|
|
2092
|
+
run: () => runtime.handlers.onTargetClass?.(target),
|
|
2093
|
+
runtime
|
|
2094
|
+
};
|
|
2095
|
+
if (target.kind === "file" && runtime.handlers.onTargetFile) return {
|
|
2096
|
+
run: () => runtime.handlers.onTargetFile?.(target),
|
|
2097
|
+
runtime
|
|
2098
|
+
};
|
|
2099
|
+
if (target.kind === "function" && runtime.handlers.onTargetFunction) return {
|
|
2100
|
+
run: () => runtime.handlers.onTargetFunction?.(target),
|
|
2101
|
+
runtime
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
//#endregion
|
|
2105
|
+
//#region src/core/targets/directory.ts
|
|
2106
|
+
function createDirectoryJobs(input, runtimes) {
|
|
2107
|
+
const target = input.target;
|
|
2108
|
+
return runtimes.flatMap((runtime) => {
|
|
2109
|
+
const execution = directoryExecution(runtime, target);
|
|
2110
|
+
if (!execution) return [];
|
|
2111
|
+
const ruleId = runtime.enabledRule.id;
|
|
2112
|
+
return [{
|
|
2113
|
+
execution,
|
|
2114
|
+
jobRef: {
|
|
2115
|
+
id: stableHash({
|
|
2116
|
+
directoryIndex: input.directoryIndex,
|
|
2117
|
+
input: target.path,
|
|
2118
|
+
ruleId,
|
|
2119
|
+
targetIdentity: target.path
|
|
2120
|
+
}),
|
|
2121
|
+
index: 0,
|
|
2122
|
+
inputPath: target.path,
|
|
2123
|
+
ruleId,
|
|
2124
|
+
target: {
|
|
2125
|
+
identity: target.path,
|
|
2126
|
+
kind: "directory"
|
|
2127
|
+
}
|
|
2128
|
+
},
|
|
2129
|
+
orderKey: {
|
|
2130
|
+
inputIndex: input.directoryIndex,
|
|
2131
|
+
ruleIndex: runtime.ruleIndex,
|
|
2132
|
+
scope: "directory",
|
|
2133
|
+
targetIndex: 0
|
|
2134
|
+
},
|
|
2135
|
+
target: {
|
|
2136
|
+
activeFilePath: target.path,
|
|
2137
|
+
configHash: input.configHash,
|
|
2138
|
+
identity: target.path,
|
|
2139
|
+
kind: "directory",
|
|
2140
|
+
language: "directory",
|
|
2141
|
+
text: target.path
|
|
2142
|
+
}
|
|
2143
|
+
}];
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
function directoryExecution(runtime, target) {
|
|
2147
|
+
if (runtime.handlers.onTargetWith) return {
|
|
2148
|
+
run: () => runtime.handlers.onTargetWith?.(target),
|
|
2149
|
+
runtime
|
|
2150
|
+
};
|
|
2151
|
+
if (runtime.handlers.onTargetDirectory) return {
|
|
2152
|
+
run: () => runtime.handlers.onTargetDirectory?.(target),
|
|
2153
|
+
runtime
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
//#endregion
|
|
2157
|
+
//#region src/core/run.ts
|
|
2158
|
+
var AlintRunCancelledError = class extends Error {
|
|
2159
|
+
result;
|
|
2160
|
+
constructor(result, cause) {
|
|
2161
|
+
super("Alint run cancelled.", { cause });
|
|
2162
|
+
this.name = "AlintRunCancelledError";
|
|
2163
|
+
this.result = result;
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
/** Cancellation error carrying the partial result gathered before the abort. */
|
|
2167
|
+
var AlintAbortError = class extends AlintRunCancelledError {
|
|
2168
|
+
constructor(result, options = {}) {
|
|
2169
|
+
super(result, options.cause);
|
|
2170
|
+
this.name = "AlintAbortError";
|
|
2171
|
+
}
|
|
2172
|
+
};
|
|
2173
|
+
var AlintRunError = class extends Error {
|
|
2174
|
+
failures;
|
|
2175
|
+
result;
|
|
2176
|
+
constructor(message, result, options = {}) {
|
|
2177
|
+
super(message, { cause: options.cause });
|
|
2178
|
+
this.name = "AlintRunError";
|
|
2179
|
+
this.failures = options.failures ?? [];
|
|
2180
|
+
this.result = result;
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
async function runAlint(options = {}) {
|
|
2184
|
+
const clock = Date.now;
|
|
2185
|
+
const timeoutMs = resolveRuleExecutionTimeout(options.runner?.timeoutMs);
|
|
2186
|
+
const concurrency = resolveRuleConcurrency(options.runner?.ruleConcurrency);
|
|
2187
|
+
const prepareStartedAt = clock();
|
|
2188
|
+
options.progress?.onPrepareStart?.({ startedAt: prepareStartedAt });
|
|
2189
|
+
const preparation = prepareRun(options);
|
|
2190
|
+
options.progress?.onPrepareEnd?.({
|
|
2191
|
+
endedAt: clock(),
|
|
2192
|
+
filesTotal: preparation.files.length,
|
|
2193
|
+
startedAt: prepareStartedAt
|
|
2194
|
+
});
|
|
2195
|
+
const runProgress = createRunProgress(preparation.files.length);
|
|
2196
|
+
const runStartedAt = clock();
|
|
2197
|
+
options.progress?.onExecuteStart?.({
|
|
2198
|
+
progress: runProgress.snapshot(),
|
|
2199
|
+
startedAt: runStartedAt
|
|
2200
|
+
});
|
|
2201
|
+
const cwd$1 = options.cwd ?? cwd();
|
|
2202
|
+
const setupConfig = options.setupConfig ?? {
|
|
2203
|
+
providers: [],
|
|
2204
|
+
version: 1
|
|
2205
|
+
};
|
|
2206
|
+
const src = createSourceRuntime();
|
|
2207
|
+
const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
|
|
2208
|
+
const cacheStore = await createCacheStore({
|
|
2209
|
+
cwd: cwd$1,
|
|
2210
|
+
enabled: normalizedCacheConfig.enabled,
|
|
2211
|
+
location: normalizedCacheConfig.location,
|
|
2212
|
+
readOnly: options.cacheOnly
|
|
2213
|
+
});
|
|
2214
|
+
const cacheContext = { modelHash: stableHash({
|
|
2215
|
+
modelOverride: options.modelOverride,
|
|
2216
|
+
outputLanguage: options.outputLanguage,
|
|
2217
|
+
setupConfig
|
|
2218
|
+
}) };
|
|
2219
|
+
const createRuntimes = (input) => createRuleRuntimes({
|
|
2220
|
+
cwd: cwd$1,
|
|
2221
|
+
effectiveAgent: input.agent,
|
|
2222
|
+
effectiveSettings: input.settings,
|
|
2223
|
+
progress: options.progress,
|
|
2224
|
+
rules: input.rules,
|
|
2225
|
+
runOptions: options,
|
|
2226
|
+
runProgress,
|
|
2227
|
+
setupConfig,
|
|
2228
|
+
src
|
|
2229
|
+
});
|
|
2230
|
+
const scheduler = new RuleScheduler({
|
|
2231
|
+
clock,
|
|
2232
|
+
concurrency,
|
|
2233
|
+
execute: (job, _startedAt) => executeRuleJob(job, {
|
|
2234
|
+
cache: cacheContext,
|
|
2235
|
+
cacheOnly: options.cacheOnly,
|
|
2236
|
+
progress: options.progress,
|
|
2237
|
+
runProgress,
|
|
2238
|
+
runSignal: options.signal,
|
|
2239
|
+
timeoutMs
|
|
2240
|
+
}),
|
|
2241
|
+
progress: runProgress,
|
|
2242
|
+
reporter: options.progress,
|
|
2243
|
+
signal: options.signal
|
|
2244
|
+
});
|
|
2245
|
+
const outcomes = [];
|
|
2246
|
+
const fileFailures = [];
|
|
2247
|
+
let infrastructureError;
|
|
2248
|
+
let infrastructureFailed = false;
|
|
2249
|
+
let admissionFailed = false;
|
|
2250
|
+
try {
|
|
2251
|
+
const projectRuntimes = preparation.project ? createRuntimes(preparation.project) : [];
|
|
2252
|
+
const projectBuilder = preparation.project && projectRuntimes.some((runtime) => runtime.handlers.onTargetWith || runtime.handlers.onTargetProject) ? new ProjectIndexBuilder(preparation.project.root) : void 0;
|
|
2253
|
+
const sourceResults = await executeSourceSessions(preparation.files, {
|
|
2254
|
+
cacheStore,
|
|
2255
|
+
createRuleRuntimes: createRuntimes,
|
|
2256
|
+
cwd: cwd$1,
|
|
2257
|
+
progress: options.progress,
|
|
2258
|
+
projectSnapshots: projectBuilder !== void 0,
|
|
2259
|
+
scheduler,
|
|
2260
|
+
signal: options.signal,
|
|
2261
|
+
sourceWindow: resolveSourceWindow(concurrency),
|
|
2262
|
+
src
|
|
2263
|
+
});
|
|
2264
|
+
for (const result of sourceResults) {
|
|
2265
|
+
outcomes.push(...result.outcomes);
|
|
2266
|
+
if (result.failure) fileFailures.push(result.failure);
|
|
2267
|
+
}
|
|
2268
|
+
if (projectBuilder) {
|
|
2269
|
+
for (const result of [...sourceResults].sort((left, right) => (left.project?.fileIndex ?? Number.MAX_SAFE_INTEGER) - (right.project?.fileIndex ?? Number.MAX_SAFE_INTEGER))) if (result.project) projectBuilder.add(result.project);
|
|
2270
|
+
}
|
|
2271
|
+
const directoryBatches = preparation.directories.map((input) => {
|
|
2272
|
+
return scheduler.schedule(createDirectoryJobs(input, createRuntimes(input)));
|
|
2273
|
+
});
|
|
2274
|
+
outcomes.push(...(await Promise.all(directoryBatches.map((batch) => batch.outcomes))).flat());
|
|
2275
|
+
if (fileFailures.length === 0 && preparation.project && !options.signal?.aborted) {
|
|
2276
|
+
if (projectBuilder) {
|
|
2277
|
+
const project = createProjectJobs({
|
|
2278
|
+
cacheStore,
|
|
2279
|
+
configHash: preparation.project.configHash,
|
|
2280
|
+
project: projectBuilder.build(),
|
|
2281
|
+
runtimes: projectRuntimes
|
|
2282
|
+
});
|
|
2283
|
+
const projectOwner = project.owner;
|
|
2284
|
+
const batch = scheduler.schedule(project.jobs);
|
|
2285
|
+
outcomes.push(...await batch.outcomes);
|
|
2286
|
+
projectOwner?.commit({ mode: options.signal?.aborted ? "merge" : "replace" });
|
|
2287
|
+
} else if (!options.cacheOnly) cacheStore.beginOwner({
|
|
2288
|
+
kind: "project",
|
|
2289
|
+
path: preparation.project.root
|
|
2290
|
+
}).commit({ mode: "replace" });
|
|
2291
|
+
}
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
scheduler.cancelWithError(error);
|
|
2294
|
+
infrastructureError = error;
|
|
2295
|
+
infrastructureFailed = true;
|
|
2296
|
+
admissionFailed = true;
|
|
2297
|
+
} finally {
|
|
2298
|
+
try {
|
|
2299
|
+
await scheduler.close();
|
|
2300
|
+
} catch (error) {
|
|
2301
|
+
infrastructureError ??= error;
|
|
2302
|
+
infrastructureFailed = true;
|
|
2303
|
+
}
|
|
2304
|
+
if (!admissionFailed && !options.cacheOnly) try {
|
|
2305
|
+
await cacheStore.reconcile();
|
|
2306
|
+
} catch {}
|
|
2307
|
+
}
|
|
2308
|
+
runProgress.finalize();
|
|
2309
|
+
if (admissionFailed) throw infrastructureError;
|
|
2310
|
+
outcomes.sort((left, right) => compareJobOrder(left.orderKey, right.orderKey));
|
|
2311
|
+
const ruleFailures = outcomes.flatMap((outcome) => outcome.state === "failed" ? [outcome.failure] : []);
|
|
2312
|
+
const failures = [...fileFailures.sort((left, right) => left.file.index - right.file.index), ...ruleFailures];
|
|
2313
|
+
const result = {
|
|
2314
|
+
diagnostics: outcomes.flatMap((outcome) => outcome.diagnostics),
|
|
2315
|
+
execution: executionCounts(outcomes),
|
|
2316
|
+
usage: runUsage(outcomes)
|
|
2317
|
+
};
|
|
2318
|
+
options.progress?.onExecuteEnd?.({
|
|
2319
|
+
endedAt: clock(),
|
|
2320
|
+
progress: runProgress.snapshot()
|
|
2321
|
+
});
|
|
2322
|
+
options.progress?.onRunEnd?.({
|
|
2323
|
+
diagnostics: snapshotDiagnostics(result.diagnostics),
|
|
2324
|
+
endedAt: clock(),
|
|
2325
|
+
execution: { ...result.execution },
|
|
2326
|
+
progress: runProgress.snapshot(),
|
|
2327
|
+
startedAt: runStartedAt,
|
|
2328
|
+
usage: snapshotRunUsage(result.usage)
|
|
2329
|
+
});
|
|
2330
|
+
if (infrastructureFailed) throw infrastructureError;
|
|
2331
|
+
if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
|
|
2332
|
+
if (failures.length > 0) throw new AlintRunError(fileFailures.length > 0 ? `${failures.length} alint execution${failures.length === 1 ? "" : "s"} failed.` : `${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
|
|
2333
|
+
cause: new AggregateError(failures.map((failure) => new Error(failure.message)), "Alint execution failures."),
|
|
2334
|
+
failures
|
|
2335
|
+
});
|
|
2336
|
+
return result;
|
|
2337
|
+
}
|
|
2338
|
+
function executionCounts(outcomes) {
|
|
2339
|
+
const counts = {
|
|
2340
|
+
cached: 0,
|
|
2341
|
+
cancelled: 0,
|
|
2342
|
+
completed: 0,
|
|
2343
|
+
failed: 0,
|
|
2344
|
+
planned: outcomes.length,
|
|
2345
|
+
queued: 0,
|
|
2346
|
+
running: 0,
|
|
2347
|
+
skipped: 0
|
|
2348
|
+
};
|
|
2349
|
+
for (const outcome of outcomes) counts[outcome.state] += 1;
|
|
2350
|
+
return counts;
|
|
2351
|
+
}
|
|
1455
2352
|
function runUsage(outcomes) {
|
|
1456
2353
|
const live = [];
|
|
1457
2354
|
const cached = [];
|
|
1458
|
-
for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.
|
|
2355
|
+
for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.usage);
|
|
1459
2356
|
return {
|
|
1460
2357
|
...usageTotals(live),
|
|
1461
2358
|
...cached.length > 0 ? { cached: usageTotals(cached) } : {}
|
|
1462
2359
|
};
|
|
1463
2360
|
}
|
|
1464
|
-
function
|
|
2361
|
+
function snapshotRunUsage(usage) {
|
|
1465
2362
|
return {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
2363
|
+
...usage,
|
|
2364
|
+
...usage.cached ? { cached: {
|
|
2365
|
+
...usage.cached,
|
|
2366
|
+
records: snapshotUsageRecords(usage.cached.records)
|
|
2367
|
+
} } : {},
|
|
2368
|
+
records: snapshotUsageRecords(usage.records)
|
|
1469
2369
|
};
|
|
1470
2370
|
}
|
|
1471
2371
|
function usageTotals(records) {
|