@sous-io/sous 0.2.15 → 0.2.17
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/docs/markdown/commands.md +63 -16
- package/docs/markdown/repositories-authoring.md +52 -11
- package/docs/markdown/repositories-consuming.md +32 -1
- package/docs/markdown/repositories-file-formats.md +20 -0
- package/docs/markdown/repositories-providers.md +20 -10
- package/package.json +1 -1
- package/recipes/core/sous-skills/sous.recipe.yaml +8 -1
- package/src/commands/namespace/list.ts +42 -21
- package/src/commands/namespace/show.ts +32 -12
- package/src/commands/recipe/list.ts +40 -23
- package/src/commands/recipe/show.ts +28 -6
- package/src/commands/repo/list.ts +67 -10
- package/src/commands/repo/release.ts +41 -0
- package/src/commands/repo/search.ts +68 -15
- package/src/commands/repo/submit.ts +245 -35
- package/src/commands/subscription/list.ts +98 -19
- package/src/lib/build-preparation.ts +44 -1
- package/src/lib/repos/catalog-display.ts +101 -2
- package/src/lib/repos/catalog-inputs.ts +145 -17
- package/src/lib/repos/catalog.ts +92 -5
- package/src/lib/repos/formats/common.ts +20 -0
- package/src/lib/repos/formats/recipe-manifest.ts +7 -0
- package/src/lib/repos/formats/repo-manifest.ts +8 -0
- package/src/lib/repos/freshness.ts +56 -0
- package/src/lib/repos/providers/base.ts +33 -1
- package/src/lib/repos/providers/github.ts +276 -3
- package/src/lib/repos/providers/gitlab.ts +1 -0
- package/src/lib/repos/providers/http.ts +7 -2
- package/src/lib/repos/providers/index-cache.ts +56 -17
- package/src/lib/repos/providers/provider.ts +121 -3
- package/src/lib/repos/release/changelog.ts +448 -0
- package/src/lib/repos/release/git-state.ts +101 -15
- package/src/lib/repos/release/index.ts +2 -0
- package/src/lib/repos/release/submissions.ts +214 -0
- package/src/lib/repos/release/submit-checkout.ts +271 -0
- package/src/lib/repos/release/submit-questions.ts +153 -0
- package/src/lib/repos/release/submit-service.ts +581 -174
- package/src/lib/repos/subscription-service.ts +138 -9
- package/src/utils/flags.ts +24 -0
|
@@ -107,10 +107,12 @@ import { RecipeStore } from "./store/recipe-store.js";
|
|
|
107
107
|
import type { RecipeStoreLike, StoreKey } from "./store/contract.js";
|
|
108
108
|
import { resolveStoreSettings } from "./store/settings.js";
|
|
109
109
|
import {
|
|
110
|
+
NEWER_VERSION_CHECK_TIMEOUT_MS,
|
|
110
111
|
effectiveRangeForHolders,
|
|
111
112
|
findNewerInRange,
|
|
112
113
|
recordUpstreamCheck,
|
|
113
114
|
shouldCheckUpstream,
|
|
115
|
+
withDeadline,
|
|
114
116
|
} from "./freshness.js";
|
|
115
117
|
import { REPO_NAME_PATTERN } from "./formats/patterns.js";
|
|
116
118
|
import {
|
|
@@ -388,6 +390,17 @@ export type UpstreamCheckReport = {
|
|
|
388
390
|
updated: Array<{ key: string; from: string; to: string }>;
|
|
389
391
|
/** Repositories whose check failed; the last good answer still stands. */
|
|
390
392
|
failed: Array<{ repo: string; reason: string }>;
|
|
393
|
+
/**
|
|
394
|
+
* Locked recipes with a newer version inside the range their holders
|
|
395
|
+
* declared, in a repository that does not prefer newer versions. Nothing
|
|
396
|
+
* moved; this is only what a build tells the person running it.
|
|
397
|
+
*/
|
|
398
|
+
newer: Array<{ key: string; repo: string; from: string; to: string }>;
|
|
399
|
+
/**
|
|
400
|
+
* Repositories sous looked at only to find newer versions, whose look failed
|
|
401
|
+
* or ran out of time. Kept quiet: the cached index answered instead.
|
|
402
|
+
*/
|
|
403
|
+
unchecked: Array<{ repo: string; reason: string }>;
|
|
391
404
|
};
|
|
392
405
|
|
|
393
406
|
// --- The service --------------------------------------------------------------------------------
|
|
@@ -1828,16 +1841,28 @@ export class SubscriptionService {
|
|
|
1828
1841
|
}
|
|
1829
1842
|
|
|
1830
1843
|
/**
|
|
1831
|
-
* Looks upstream for
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1844
|
+
* Looks upstream for every repository the lockfile pins something from, on
|
|
1845
|
+
* the freshness window. A repository that prefers a newer in-range version
|
|
1846
|
+
* moves the lockfile to it; every other repository is only read, and a newer
|
|
1847
|
+
* in-range version it publishes is reported in `newer` without moving any
|
|
1848
|
+
* pin. A failed check never breaks a build: for a repository that prefers
|
|
1849
|
+
* newer versions it is warned about, and for the others it is quiet, capped
|
|
1850
|
+
* at a short deadline, and the cached index answers instead.
|
|
1834
1851
|
*
|
|
1835
|
-
* @param options - Whether to check regardless of the freshness window,
|
|
1852
|
+
* @param options - Whether to check regardless of the freshness window, which
|
|
1853
|
+
* window to use, and how long to wait for a repository checked only for the
|
|
1854
|
+
* newer-version report.
|
|
1836
1855
|
*/
|
|
1837
1856
|
async checkUpstream(
|
|
1838
|
-
options: { force?: boolean; freshnessSeconds?: number } = {}
|
|
1857
|
+
options: { force?: boolean; freshnessSeconds?: number; newerCheckTimeoutMs?: number } = {}
|
|
1839
1858
|
): Promise<UpstreamCheckReport> {
|
|
1840
|
-
const report: UpstreamCheckReport = {
|
|
1859
|
+
const report: UpstreamCheckReport = {
|
|
1860
|
+
checked: [],
|
|
1861
|
+
updated: [],
|
|
1862
|
+
failed: [],
|
|
1863
|
+
newer: [],
|
|
1864
|
+
unchecked: [],
|
|
1865
|
+
};
|
|
1841
1866
|
const lock = this.lock.read();
|
|
1842
1867
|
if (Object.keys(lock.recipes).length === 0) return report;
|
|
1843
1868
|
|
|
@@ -1850,19 +1875,32 @@ export class SubscriptionService {
|
|
|
1850
1875
|
const recipes: Record<string, LockedRecipe> = { ...lock.recipes };
|
|
1851
1876
|
|
|
1852
1877
|
for (const repoName of Object.keys(lock.repos).sort()) {
|
|
1853
|
-
if (!this.prefersNewer(repoName, repos[repoName], lock, subscriptions)) continue;
|
|
1854
|
-
|
|
1855
1878
|
const identity = this.identityForRepo(repoName, lock);
|
|
1856
1879
|
if (identity === undefined) continue;
|
|
1857
1880
|
|
|
1881
|
+
const prefersNewer = this.prefersNewer(repoName, repos[repoName], lock, subscriptions);
|
|
1858
1882
|
const meta = this.indexCache.readMeta(identity);
|
|
1859
1883
|
const due = shouldCheckUpstream({
|
|
1860
1884
|
...(meta?.lastCheckedAt === undefined ? {} : { lastCheckedAt: meta.lastCheckedAt }),
|
|
1861
1885
|
freshnessSeconds,
|
|
1862
|
-
alwaysPull:
|
|
1886
|
+
alwaysPull: prefersNewer,
|
|
1863
1887
|
...(options.force === undefined ? {} : { force: options.force }),
|
|
1864
1888
|
now: this.now(),
|
|
1865
1889
|
});
|
|
1890
|
+
|
|
1891
|
+
if (!prefersNewer) {
|
|
1892
|
+
await this.reportNewerVersions({
|
|
1893
|
+
repoName,
|
|
1894
|
+
identity,
|
|
1895
|
+
due,
|
|
1896
|
+
lock,
|
|
1897
|
+
subscriptions,
|
|
1898
|
+
report,
|
|
1899
|
+
timeoutMs: options.newerCheckTimeoutMs ?? NEWER_VERSION_CHECK_TIMEOUT_MS,
|
|
1900
|
+
});
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1866
1904
|
if (!due) continue;
|
|
1867
1905
|
|
|
1868
1906
|
report.checked.push(repoName);
|
|
@@ -1938,6 +1976,97 @@ export class SubscriptionService {
|
|
|
1938
1976
|
return report;
|
|
1939
1977
|
}
|
|
1940
1978
|
|
|
1979
|
+
/**
|
|
1980
|
+
* The newer-version report for one repository that does not prefer newer
|
|
1981
|
+
* versions: reads its index (from upstream when the freshness window has
|
|
1982
|
+
* lapsed, within a short deadline, and from the cache otherwise) and records
|
|
1983
|
+
* every locked recipe from it that has a newer version in range. Nothing
|
|
1984
|
+
* moves, and nothing here is ever an error: a check that fails or runs out of
|
|
1985
|
+
* time is noted in `unchecked` and the cached index answers instead.
|
|
1986
|
+
*
|
|
1987
|
+
* @param input - The repository, whether a check is due, and the report to fill.
|
|
1988
|
+
*/
|
|
1989
|
+
private async reportNewerVersions(input: {
|
|
1990
|
+
repoName: string;
|
|
1991
|
+
identity: string;
|
|
1992
|
+
due: boolean;
|
|
1993
|
+
lock: Lockfile;
|
|
1994
|
+
subscriptions: Record<string, SubscriptionEntry>;
|
|
1995
|
+
report: UpstreamCheckReport;
|
|
1996
|
+
timeoutMs: number;
|
|
1997
|
+
}): Promise<void> {
|
|
1998
|
+
const { repoName, identity, lock, report } = input;
|
|
1999
|
+
let index: IndexFile | undefined;
|
|
2000
|
+
|
|
2001
|
+
if (input.due) {
|
|
2002
|
+
report.checked.push(repoName);
|
|
2003
|
+
const repo = this.currentRepos()[repoName];
|
|
2004
|
+
const url = repo?.url ?? lock.repos[repoName]!.url;
|
|
2005
|
+
try {
|
|
2006
|
+
index = (
|
|
2007
|
+
await withDeadline(
|
|
2008
|
+
(signal) =>
|
|
2009
|
+
this.indexCache.refresh(identity, {
|
|
2010
|
+
url,
|
|
2011
|
+
label: repoName,
|
|
2012
|
+
...(repo?.provider === undefined ? {} : { provider: repo.provider }),
|
|
2013
|
+
signal,
|
|
2014
|
+
}),
|
|
2015
|
+
input.timeoutMs
|
|
2016
|
+
)
|
|
2017
|
+
).index;
|
|
2018
|
+
} catch (error) {
|
|
2019
|
+
report.unchecked.push({ repo: repoName, reason: describeError(error) });
|
|
2020
|
+
}
|
|
2021
|
+
// Recorded whatever happened, so an unreachable host costs one short wait
|
|
2022
|
+
// per freshness window rather than one per build.
|
|
2023
|
+
recordUpstreamCheck(this.indexCache, identity, this.now());
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
index ??= this.indexCache.readCached(identity);
|
|
2027
|
+
if (index === undefined) return;
|
|
2028
|
+
|
|
2029
|
+
for (const [key, entry] of Object.entries(lock.recipes).sort(([a], [b]) =>
|
|
2030
|
+
a < b ? -1 : a > b ? 1 : 0
|
|
2031
|
+
)) {
|
|
2032
|
+
if (entry.repo !== repoName) continue;
|
|
2033
|
+
const range = this.effectiveRangeFor(key, entry, input.subscriptions);
|
|
2034
|
+
if (range === undefined) continue;
|
|
2035
|
+
const subscription =
|
|
2036
|
+
input.subscriptions[key] ?? input.subscriptions[key.split("/")[0]!];
|
|
2037
|
+
|
|
2038
|
+
const newer = findNewerInRange({
|
|
2039
|
+
index,
|
|
2040
|
+
key,
|
|
2041
|
+
lockedVersion: entry.version,
|
|
2042
|
+
...(range === "*" ? {} : { range }),
|
|
2043
|
+
...(subscription?.prerelease === undefined
|
|
2044
|
+
? {}
|
|
2045
|
+
: { prerelease: subscription.prerelease }),
|
|
2046
|
+
});
|
|
2047
|
+
if (newer !== undefined) report.newer.push({ ...newer, repo: repoName });
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* Reads one trusted repository's index from upstream, without writing it to
|
|
2053
|
+
* the cache: what a browsing command asked to read the latest uses. A failure
|
|
2054
|
+
* is raised, so the caller can fall back to the cached copy and say so.
|
|
2055
|
+
*
|
|
2056
|
+
* @param name - The repository's short name.
|
|
2057
|
+
*/
|
|
2058
|
+
async upstreamIndex(name: string): Promise<IndexFile> {
|
|
2059
|
+
const repo = this.currentRepos()[name];
|
|
2060
|
+
if (repo === undefined) {
|
|
2061
|
+
throw new ConfigError(`This project trusts no repository called '${name}'.`);
|
|
2062
|
+
}
|
|
2063
|
+
const identity = this.identityOf(repo.url, repo.provider);
|
|
2064
|
+
return this.indexCache.fetchUpstream(identity, {
|
|
2065
|
+
url: repo.url,
|
|
2066
|
+
...(repo.provider === undefined ? {} : { provider: repo.provider }),
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1941
2070
|
/**
|
|
1942
2071
|
* Everything a build needs done before it compiles: seed the packaged core
|
|
1943
2072
|
* recipe, restore whatever else the store is missing, then look upstream for
|
package/src/utils/flags.ts
CHANGED
|
@@ -151,3 +151,27 @@ export function nonInteractiveFlag() {
|
|
|
151
151
|
default: false,
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The two flags every browsing command takes, defined once so they read the
|
|
157
|
+
* same on each: `--latest` (also `--remote`) reads each repository's index from
|
|
158
|
+
* upstream rather than the cache, and `--installed` narrows what is shown to
|
|
159
|
+
* what this project's lockfile pins. They combine. The behavior lives in
|
|
160
|
+
* `lib/repos/catalog-inputs.ts` and `lib/repos/catalog.ts`.
|
|
161
|
+
*/
|
|
162
|
+
export function browsingFlags() {
|
|
163
|
+
const latestAliases = ["remote"];
|
|
164
|
+
return {
|
|
165
|
+
latest: Flags.boolean({
|
|
166
|
+
aliases: latestAliases,
|
|
167
|
+
description:
|
|
168
|
+
"Read each repository's index from upstream instead of the cache, without " +
|
|
169
|
+
`saving it${aliasSuffix(latestAliases)}`,
|
|
170
|
+
default: false,
|
|
171
|
+
}),
|
|
172
|
+
installed: Flags.boolean({
|
|
173
|
+
description: "Show only what this project has installed, at the version its lockfile pins",
|
|
174
|
+
default: false,
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
177
|
+
}
|