@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
|
@@ -4,23 +4,28 @@
|
|
|
4
4
|
* Shows one namespace in full: the repository publishing it, what that
|
|
5
5
|
* repository says it is for, how much of it this project subscribes to, and
|
|
6
6
|
* every recipe in it with its latest version, its pinned version and its own
|
|
7
|
-
* subscription state. It reads only the indexes sous already has on disk
|
|
7
|
+
* subscription state. It reads only the indexes sous already has on disk,
|
|
8
|
+
* unless `--latest` asks it to read upstream (which saves nothing); `--installed`
|
|
9
|
+
* narrows the recipes to the ones the lockfile pins.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { Args } from "@oclif/core";
|
|
11
13
|
import { BaseCommand } from "../../base-command.js";
|
|
12
14
|
import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
|
|
13
|
-
import {
|
|
14
|
-
import { describeNamespace } from "../../lib/repos/catalog.js";
|
|
15
|
+
import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
|
|
16
|
+
import { describeInstalled, describeNamespace } from "../../lib/repos/catalog.js";
|
|
15
17
|
import {
|
|
16
18
|
INDENT,
|
|
17
|
-
RECIPE_COLUMNS,
|
|
18
19
|
describeCoverage,
|
|
20
|
+
describeIndexSource,
|
|
19
21
|
factIf,
|
|
22
|
+
printBrowsingNotes,
|
|
20
23
|
printFacts,
|
|
24
|
+
recipeColumns,
|
|
21
25
|
recipeRows,
|
|
22
26
|
} from "../../lib/repos/catalog-display.js";
|
|
23
27
|
import { renderTable } from "../../utils/table.js";
|
|
28
|
+
import { browsingFlags } from "../../utils/flags.js";
|
|
24
29
|
import {
|
|
25
30
|
blankLine,
|
|
26
31
|
footer,
|
|
@@ -53,15 +58,17 @@ export default class NamespaceShow extends BaseCommand {
|
|
|
53
58
|
}),
|
|
54
59
|
};
|
|
55
60
|
|
|
56
|
-
static flags = { ...BaseCommand.baseFlags };
|
|
61
|
+
static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
|
|
57
62
|
|
|
58
63
|
async run(): Promise<void> {
|
|
59
|
-
const { args } = await this.parse(NamespaceShow);
|
|
64
|
+
const { args, flags } = await this.parse(NamespaceShow);
|
|
60
65
|
|
|
61
66
|
showCommandVars({
|
|
62
67
|
Project: this.projectLabel,
|
|
63
68
|
Config: this.configContext.configPath,
|
|
64
69
|
Namespace: args.ref,
|
|
70
|
+
Reading: describeIndexSource(flags.latest),
|
|
71
|
+
...(flags.installed ? { Showing: "only what this project has installed" } : {}),
|
|
65
72
|
});
|
|
66
73
|
|
|
67
74
|
const service = subscriptionServiceFor({
|
|
@@ -70,13 +77,16 @@ export default class NamespaceShow extends BaseCommand {
|
|
|
70
77
|
shellEnv: this.shellEnv,
|
|
71
78
|
});
|
|
72
79
|
|
|
73
|
-
const { inputs } =
|
|
80
|
+
const { inputs, notChecked } = await loadCatalogContext({
|
|
74
81
|
service,
|
|
75
82
|
sousDir: this.configContext.sousDir,
|
|
76
83
|
settings: this.settings,
|
|
84
|
+
latest: flags.latest,
|
|
77
85
|
});
|
|
78
86
|
|
|
79
|
-
const detail =
|
|
87
|
+
const detail = flags.installed
|
|
88
|
+
? describeInstalled(inputs, args.ref, describeNamespace, "namespace")
|
|
89
|
+
: describeNamespace(inputs, args.ref);
|
|
80
90
|
|
|
81
91
|
heading(`The namespace ${detail.namespace}`);
|
|
82
92
|
blankLine();
|
|
@@ -85,7 +95,10 @@ export default class NamespaceShow extends BaseCommand {
|
|
|
85
95
|
{ label: "Repository", lines: [detail.repo] },
|
|
86
96
|
...factIf("Location", detail.repoUrl),
|
|
87
97
|
...factIf("About", detail.description),
|
|
88
|
-
{
|
|
98
|
+
{
|
|
99
|
+
label: flags.installed ? "Installed recipes" : "Recipes",
|
|
100
|
+
lines: [String(detail.recipes.length)],
|
|
101
|
+
},
|
|
89
102
|
{ label: "Subscribed", lines: [describeCoverage(detail.subscribed)] },
|
|
90
103
|
]);
|
|
91
104
|
|
|
@@ -99,12 +112,19 @@ export default class NamespaceShow extends BaseCommand {
|
|
|
99
112
|
return;
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
for (const line of renderTable(
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
for (const line of renderTable(
|
|
116
|
+
recipeColumns({ installed: flags.installed }),
|
|
117
|
+
recipeRows(detail.recipes),
|
|
118
|
+
{ indent: INDENT }
|
|
119
|
+
)) {
|
|
105
120
|
log(indent(line, INDENT));
|
|
106
121
|
}
|
|
107
122
|
|
|
123
|
+
printBrowsingNotes({
|
|
124
|
+
notChecked: notChecked.filter((name) => name === detail.repo),
|
|
125
|
+
anyLinked: detail.recipes.some((entry) => entry.linkedPath !== undefined),
|
|
126
|
+
});
|
|
127
|
+
|
|
108
128
|
footer();
|
|
109
129
|
}
|
|
110
130
|
}
|
|
@@ -3,21 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shows every recipe the repositories this project trusts publish, with the
|
|
5
5
|
* latest published version, the version this project pins, and whether it is
|
|
6
|
-
* subscribed.
|
|
7
|
-
* offline; a repository whose index has never been fetched is named at
|
|
8
|
-
* rather than being silently left out.
|
|
6
|
+
* subscribed. By default it reads only the indexes sous already has on disk, so
|
|
7
|
+
* it works offline; a repository whose index has never been fetched is named at
|
|
8
|
+
* the end rather than being silently left out. `--latest` reads the indexes
|
|
9
|
+
* from upstream instead, without saving them, and `--installed` narrows the
|
|
10
|
+
* listing to what the lockfile pins.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { BaseCommand } from "../../base-command.js";
|
|
12
14
|
import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
|
|
13
|
-
import {
|
|
14
|
-
import { listRecipes } from "../../lib/repos/catalog.js";
|
|
15
|
+
import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
|
|
16
|
+
import { listRecipes, narrowToInstalled } from "../../lib/repos/catalog.js";
|
|
15
17
|
import {
|
|
16
18
|
INDENT,
|
|
17
|
-
|
|
19
|
+
describeIndexSource,
|
|
20
|
+
printBrowsingNotes,
|
|
21
|
+
recipeColumns,
|
|
18
22
|
recipeRows,
|
|
19
23
|
} from "../../lib/repos/catalog-display.js";
|
|
20
24
|
import { renderTable } from "../../utils/table.js";
|
|
25
|
+
import { browsingFlags } from "../../utils/flags.js";
|
|
21
26
|
import {
|
|
22
27
|
blankLine,
|
|
23
28
|
footer,
|
|
@@ -37,19 +42,28 @@ export default class RecipeList extends BaseCommand {
|
|
|
37
42
|
*/
|
|
38
43
|
static aliases = ["recipes:list"];
|
|
39
44
|
|
|
40
|
-
static examples = [
|
|
45
|
+
static examples = [
|
|
46
|
+
"<%= config.bin %> recipe list",
|
|
47
|
+
"<%= config.bin %> recipe list --installed --latest",
|
|
48
|
+
];
|
|
41
49
|
|
|
42
|
-
static flags = { ...BaseCommand.baseFlags };
|
|
50
|
+
static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
|
|
43
51
|
|
|
44
52
|
async run(): Promise<void> {
|
|
45
|
-
await this.parse(RecipeList);
|
|
53
|
+
const { flags } = await this.parse(RecipeList);
|
|
46
54
|
|
|
47
55
|
showCommandVars({
|
|
48
56
|
Project: this.projectLabel,
|
|
49
57
|
Config: this.configContext.configPath,
|
|
58
|
+
Reading: describeIndexSource(flags.latest),
|
|
59
|
+
...(flags.installed ? { Showing: "only what this project has installed" } : {}),
|
|
50
60
|
});
|
|
51
61
|
|
|
52
|
-
heading(
|
|
62
|
+
heading(
|
|
63
|
+
flags.installed
|
|
64
|
+
? "Recipes this project has installed"
|
|
65
|
+
: "Recipes in the repositories this project trusts"
|
|
66
|
+
);
|
|
53
67
|
|
|
54
68
|
const service = subscriptionServiceFor({
|
|
55
69
|
configContext: this.configContext,
|
|
@@ -57,13 +71,14 @@ export default class RecipeList extends BaseCommand {
|
|
|
57
71
|
shellEnv: this.shellEnv,
|
|
58
72
|
});
|
|
59
73
|
|
|
60
|
-
const { inputs, notFetched } =
|
|
74
|
+
const { inputs, notFetched, notChecked } = await loadCatalogContext({
|
|
61
75
|
service,
|
|
62
76
|
sousDir: this.configContext.sousDir,
|
|
63
77
|
settings: this.settings,
|
|
78
|
+
latest: flags.latest,
|
|
64
79
|
});
|
|
65
80
|
|
|
66
|
-
const listings = listRecipes(inputs);
|
|
81
|
+
const listings = listRecipes(flags.installed ? narrowToInstalled(inputs) : inputs);
|
|
67
82
|
|
|
68
83
|
blankLine();
|
|
69
84
|
|
|
@@ -72,23 +87,25 @@ export default class RecipeList extends BaseCommand {
|
|
|
72
87
|
inputs.repos.length === 0
|
|
73
88
|
? "Sous has read no repository index for this project, so there are no " +
|
|
74
89
|
"recipes to show."
|
|
75
|
-
:
|
|
90
|
+
: flags.installed
|
|
91
|
+
? "This project has installed no recipe from the repositories it trusts."
|
|
92
|
+
: "The repositories this project trusts publish no recipes."
|
|
76
93
|
);
|
|
77
94
|
} else {
|
|
78
|
-
for (const line of renderTable(
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
for (const line of renderTable(
|
|
96
|
+
recipeColumns({ installed: flags.installed }),
|
|
97
|
+
recipeRows(listings),
|
|
98
|
+
{ indent: INDENT }
|
|
99
|
+
)) {
|
|
81
100
|
log(indent(line, INDENT));
|
|
82
101
|
}
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
}
|
|
104
|
+
printBrowsingNotes({
|
|
105
|
+
notFetched,
|
|
106
|
+
notChecked,
|
|
107
|
+
anyLinked: listings.some((entry) => entry.linkedPath !== undefined),
|
|
108
|
+
});
|
|
92
109
|
|
|
93
110
|
footer();
|
|
94
111
|
}
|
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
* and where its files land in this project. It reads only what sous already has
|
|
7
7
|
* on disk: the repository's cached index, the project's lockfile, and the
|
|
8
8
|
* recipe's own files when they are in the store or a linked working copy.
|
|
9
|
+
* `--latest` reads the repository's index from upstream instead, saving
|
|
10
|
+
* nothing, and `--installed` looks the ref up among installed recipes only.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { Args } from "@oclif/core";
|
|
12
14
|
import { BaseCommand } from "../../base-command.js";
|
|
13
15
|
import { resolveRootScope } from "../../lib/settings.js";
|
|
14
16
|
import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
|
|
15
|
-
import {
|
|
17
|
+
import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
|
|
16
18
|
import {
|
|
19
|
+
describeInstalled,
|
|
17
20
|
describeRecipe,
|
|
18
21
|
type RecipeContentListing,
|
|
19
22
|
type RecipeDependencyListing,
|
|
@@ -22,11 +25,14 @@ import {
|
|
|
22
25
|
} from "../../lib/repos/catalog.js";
|
|
23
26
|
import {
|
|
24
27
|
INDENT,
|
|
28
|
+
describeIndexSource,
|
|
25
29
|
describeVersionStatus,
|
|
26
30
|
factIf,
|
|
31
|
+
printBrowsingNotes,
|
|
27
32
|
printFacts,
|
|
28
33
|
} from "../../lib/repos/catalog-display.js";
|
|
29
34
|
import { renderTable, type TableColumn } from "../../utils/table.js";
|
|
35
|
+
import { browsingFlags } from "../../utils/flags.js";
|
|
30
36
|
import {
|
|
31
37
|
blankLine,
|
|
32
38
|
footer,
|
|
@@ -102,15 +108,17 @@ export default class RecipeShow extends BaseCommand {
|
|
|
102
108
|
}),
|
|
103
109
|
};
|
|
104
110
|
|
|
105
|
-
static flags = { ...BaseCommand.baseFlags };
|
|
111
|
+
static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
|
|
106
112
|
|
|
107
113
|
async run(): Promise<void> {
|
|
108
|
-
const { args } = await this.parse(RecipeShow);
|
|
114
|
+
const { args, flags } = await this.parse(RecipeShow);
|
|
109
115
|
|
|
110
116
|
showCommandVars({
|
|
111
117
|
Project: this.projectLabel,
|
|
112
118
|
Config: this.configContext.configPath,
|
|
113
119
|
Recipe: args.ref,
|
|
120
|
+
Reading: describeIndexSource(flags.latest),
|
|
121
|
+
...(flags.installed ? { Showing: "only what this project has installed" } : {}),
|
|
114
122
|
});
|
|
115
123
|
|
|
116
124
|
const service = subscriptionServiceFor({
|
|
@@ -119,14 +127,17 @@ export default class RecipeShow extends BaseCommand {
|
|
|
119
127
|
shellEnv: this.shellEnv,
|
|
120
128
|
});
|
|
121
129
|
|
|
122
|
-
const { inputs } =
|
|
130
|
+
const { inputs, notChecked } = await loadCatalogContext({
|
|
123
131
|
service,
|
|
124
132
|
sousDir: this.configContext.sousDir,
|
|
125
133
|
settings: this.settings,
|
|
126
134
|
scope: resolveRootScope(this.settings, this.configContext),
|
|
135
|
+
latest: flags.latest,
|
|
127
136
|
});
|
|
128
137
|
|
|
129
|
-
const detail =
|
|
138
|
+
const detail = flags.installed
|
|
139
|
+
? describeInstalled(inputs, args.ref, describeRecipe, "recipe")
|
|
140
|
+
: describeRecipe(inputs, args.ref);
|
|
130
141
|
|
|
131
142
|
heading(detail.key);
|
|
132
143
|
blankLine();
|
|
@@ -137,11 +148,22 @@ export default class RecipeShow extends BaseCommand {
|
|
|
137
148
|
...factIf("About", detail.description),
|
|
138
149
|
{ label: "Folder", lines: [detail.path] },
|
|
139
150
|
{ label: "Latest version", lines: [detail.latest ?? "none published"] },
|
|
140
|
-
{
|
|
151
|
+
{
|
|
152
|
+
label: flags.installed ? "Installed version" : "Pinned version",
|
|
153
|
+
lines: [detail.pinned ?? "this project pins none"],
|
|
154
|
+
},
|
|
155
|
+
...factIf(
|
|
156
|
+
"Linked",
|
|
157
|
+
detail.linkedPath === undefined
|
|
158
|
+
? undefined
|
|
159
|
+
: `builds currently read this recipe from the checkout at ${detail.linkedPath}`
|
|
160
|
+
),
|
|
141
161
|
{ label: "Subscribed", lines: [detail.subscribed ? "yes" : "no"] },
|
|
142
162
|
...factIf("Described below", detail.describing),
|
|
143
163
|
]);
|
|
144
164
|
|
|
165
|
+
printBrowsingNotes({ notChecked: notChecked.filter((name) => name === detail.repo) });
|
|
166
|
+
|
|
145
167
|
this.printVersions(detail.versions);
|
|
146
168
|
this.printDependencies(detail.dependencies, detail.manifestRead);
|
|
147
169
|
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shows every repository this project trusts, what it publishes, and whether it
|
|
5
5
|
* is currently being read from a working copy instead of a published version.
|
|
6
|
-
*
|
|
7
|
-
* never been fetched says so in its own row rather than triggering a
|
|
8
|
-
* so the command is safe to run offline.
|
|
6
|
+
* By default it reads only what sous already has on disk: a repository whose
|
|
7
|
+
* index has never been fetched says so in its own row rather than triggering a
|
|
8
|
+
* download, so the command is safe to run offline. `--latest` reads each index
|
|
9
|
+
* from upstream instead, saving nothing, and `--installed` narrows the listing
|
|
10
|
+
* to the repositories the lockfile pins a recipe from, naming each one under
|
|
11
|
+
* its row.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import { Flags } from "@oclif/core";
|
|
@@ -15,14 +18,24 @@ import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js"
|
|
|
15
18
|
import { readEffectiveLinks } from "../../lib/repos/links.js";
|
|
16
19
|
import { BUILT_IN_ADDED_BY } from "../../lib/repos/defaults.js";
|
|
17
20
|
import { requireProvider } from "../../lib/repos/providers/index.js";
|
|
21
|
+
import { readTrustedIndexes } from "../../lib/repos/catalog-inputs.js";
|
|
22
|
+
import {
|
|
23
|
+
LINKED_REPO_NOTE,
|
|
24
|
+
describeIndexSource,
|
|
25
|
+
printBrowsingNotes,
|
|
26
|
+
} from "../../lib/repos/catalog-display.js";
|
|
18
27
|
import { renderTable, type TableColumn } from "../../utils/table.js";
|
|
28
|
+
import { browsingFlags } from "../../utils/flags.js";
|
|
19
29
|
import {
|
|
20
30
|
footer,
|
|
21
31
|
indent,
|
|
22
32
|
log,
|
|
33
|
+
note,
|
|
23
34
|
paragraph,
|
|
24
35
|
section,
|
|
25
36
|
showCommandVars,
|
|
37
|
+
wrapColumns,
|
|
38
|
+
wrapText,
|
|
26
39
|
} from "../../utils/formatting.js";
|
|
27
40
|
|
|
28
41
|
/** How far every line of this command's output is indented. */
|
|
@@ -71,10 +84,12 @@ export default class RepoList extends BaseCommand {
|
|
|
71
84
|
static examples = [
|
|
72
85
|
"<%= config.bin %> repo list",
|
|
73
86
|
"<%= config.bin %> repo list --verbose",
|
|
87
|
+
"<%= config.bin %> repo list --installed --latest",
|
|
74
88
|
];
|
|
75
89
|
|
|
76
90
|
static flags = {
|
|
77
91
|
...BaseCommand.baseFlags,
|
|
92
|
+
...browsingFlags(),
|
|
78
93
|
verbose: Flags.boolean({
|
|
79
94
|
description: "Show the namespaces each repository publishes, under its row",
|
|
80
95
|
default: false,
|
|
@@ -87,9 +102,15 @@ export default class RepoList extends BaseCommand {
|
|
|
87
102
|
showCommandVars({
|
|
88
103
|
Project: this.projectLabel,
|
|
89
104
|
Config: this.configContext.configPath,
|
|
105
|
+
Reading: describeIndexSource(flags.latest),
|
|
106
|
+
...(flags.installed ? { Showing: "only what this project has installed" } : {}),
|
|
90
107
|
});
|
|
91
108
|
|
|
92
|
-
section(
|
|
109
|
+
section(
|
|
110
|
+
flags.installed
|
|
111
|
+
? "Repositories this project has installed recipes from"
|
|
112
|
+
: "Repositories this project trusts"
|
|
113
|
+
);
|
|
93
114
|
|
|
94
115
|
const service = subscriptionServiceFor({
|
|
95
116
|
configContext: this.configContext,
|
|
@@ -98,7 +119,20 @@ export default class RepoList extends BaseCommand {
|
|
|
98
119
|
});
|
|
99
120
|
|
|
100
121
|
const repos = service.currentRepos();
|
|
101
|
-
const
|
|
122
|
+
const lock = service.lockService.read();
|
|
123
|
+
const names = Object.keys(repos)
|
|
124
|
+
.sort()
|
|
125
|
+
.filter(
|
|
126
|
+
(name) =>
|
|
127
|
+
!flags.installed ||
|
|
128
|
+
Object.values(lock.recipes).some((locked) => locked.repo === name)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (names.length === 0 && flags.installed && Object.keys(repos).length > 0) {
|
|
132
|
+
paragraph("This project has installed no recipe from the repositories it trusts.");
|
|
133
|
+
footer();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
102
136
|
|
|
103
137
|
if (names.length === 0) {
|
|
104
138
|
paragraph(
|
|
@@ -110,10 +144,12 @@ export default class RepoList extends BaseCommand {
|
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
const links = readEffectiveLinks(this.configContext.sousDir);
|
|
147
|
+
const indexes = await readTrustedIndexes(service, { latest: flags.latest });
|
|
148
|
+
const indexFor = new Map(indexes.repos.map((entry) => [entry.name, entry.index]));
|
|
113
149
|
|
|
114
150
|
const rows = names.map((name) => {
|
|
115
151
|
const entry = repos[name]!;
|
|
116
|
-
const index =
|
|
152
|
+
const index = indexFor.get(name);
|
|
117
153
|
const namespaces =
|
|
118
154
|
index === undefined ? "not fetched" : Object.keys(index.namespaces).sort().join(", ");
|
|
119
155
|
// A repository whose index has never been fetched says so in the cell
|
|
@@ -130,19 +166,40 @@ export default class RepoList extends BaseCommand {
|
|
|
130
166
|
namespaces: namespaces.length > 0 ? namespaces : "none",
|
|
131
167
|
recipes,
|
|
132
168
|
linked: links[name] === undefined ? "no" : `yes: ${links[name]!.path}`,
|
|
169
|
+
installed: Object.entries(lock.recipes)
|
|
170
|
+
.filter(([, locked]) => locked.repo === name)
|
|
171
|
+
.map(([key, locked]) => `${key} ${locked.version}`)
|
|
172
|
+
.sort()
|
|
173
|
+
.join(", "),
|
|
133
174
|
};
|
|
134
175
|
});
|
|
135
176
|
|
|
136
177
|
for (const line of renderTable(COLUMNS, rows, {
|
|
137
178
|
indent: INDENT,
|
|
138
|
-
rowNote:
|
|
139
|
-
|
|
140
|
-
:
|
|
179
|
+
rowNote: (row) => {
|
|
180
|
+
const notes: string[] = [];
|
|
181
|
+
if (flags.verbose) notes.push(`Namespaces: ${row.namespaces}`);
|
|
182
|
+
if (flags.installed) notes.push(`Installed: ${row.installed}`);
|
|
183
|
+
// Each note wraps under itself, inside the two indents it is printed at.
|
|
184
|
+
const width = Math.max(20, wrapColumns() - INDENT * 2);
|
|
185
|
+
return notes.length === 0
|
|
186
|
+
? undefined
|
|
187
|
+
: notes
|
|
188
|
+
.flatMap((text) => wrapText(text, width))
|
|
189
|
+
.map((line) => color.gray(indent(line, INDENT)))
|
|
190
|
+
.join("\n");
|
|
191
|
+
},
|
|
141
192
|
})) {
|
|
142
193
|
log(indent(line, INDENT));
|
|
143
194
|
}
|
|
144
195
|
|
|
145
|
-
//
|
|
196
|
+
// The table says everything about each repository; what sits under it is
|
|
197
|
+
// only what the rows could not: a repository upstream could not answer for,
|
|
198
|
+
// and what a linked row means for the installed recipes it names.
|
|
199
|
+
printBrowsingNotes({ notChecked: indexes.notChecked.filter((name) => names.includes(name)) });
|
|
200
|
+
if (flags.installed && names.some((name) => links[name] !== undefined)) {
|
|
201
|
+
note(LINKED_REPO_NOTE);
|
|
202
|
+
}
|
|
146
203
|
footer();
|
|
147
204
|
}
|
|
148
205
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
anythingToCommit,
|
|
12
12
|
buildIndex,
|
|
13
13
|
buildReleasePlan,
|
|
14
|
+
checkSubmissions,
|
|
14
15
|
bumpRecipeVersion,
|
|
15
16
|
commitPaths,
|
|
16
17
|
createAnnotatedTag,
|
|
@@ -391,6 +392,46 @@ export default class RepoRelease extends Command {
|
|
|
391
392
|
log(" records for every version it publishes.");
|
|
392
393
|
}
|
|
393
394
|
reportPending(result, "These versions have no tag yet; they publish when this merges:");
|
|
395
|
+
|
|
396
|
+
// A pull request can be opened without `sous repo submit`, so this check is
|
|
397
|
+
// the one gate every change passes. The release itself is not restricted:
|
|
398
|
+
// whatever publishes such a recipe still releases it.
|
|
399
|
+
section("Checking the recipes that take no proposals");
|
|
400
|
+
const submissions = await checkSubmissions(validation);
|
|
401
|
+
if (submissions.refusing.length > 0) {
|
|
402
|
+
const problems: ValidationProblem[] = submissions.refusing.map((recipe) => ({
|
|
403
|
+
level: "error",
|
|
404
|
+
where: recipe.path,
|
|
405
|
+
message:
|
|
406
|
+
`this change touches '${recipe.key}', which does not take proposed changes ` +
|
|
407
|
+
`(${recipe.declaredBy === "recipe" ? "its own manifest" : "the repository manifest"} ` +
|
|
408
|
+
`says so). Merging it would break whatever publishes the recipe.` +
|
|
409
|
+
(recipe.instead === undefined ? "" : ` Instead: ${recipe.instead}`),
|
|
410
|
+
}));
|
|
411
|
+
reportProblems(problems);
|
|
412
|
+
displayErrorBlock(
|
|
413
|
+
`This change cannot be merged: it touches ` +
|
|
414
|
+
`${describeCount(problems.length, "recipe")} that ${problems.length === 1 ? "does" : "do"} ` +
|
|
415
|
+
`not take proposed changes, listed above.\n` +
|
|
416
|
+
` Take those edits out of the change, and send them where each recipe asks.`
|
|
417
|
+
);
|
|
418
|
+
return this.exit(1);
|
|
419
|
+
}
|
|
420
|
+
if (submissions.comparedWith.kind === "branch") {
|
|
421
|
+
log(
|
|
422
|
+
` Compared with the branch '${submissions.comparedWith.branch}': this change touches no ` +
|
|
423
|
+
`recipe that declines proposals.`
|
|
424
|
+
);
|
|
425
|
+
} else if (submissions.comparedWith.kind === "tags") {
|
|
426
|
+
log(" Compared with each recipe's last release tag: nothing that declines proposals changed.");
|
|
427
|
+
} else if (submissions.comparedWith.kind === "none declined") {
|
|
428
|
+
log(" No recipe in this repository declines proposed changes.");
|
|
429
|
+
} else {
|
|
430
|
+
note(
|
|
431
|
+
"This checkout holds neither a copy of the default branch nor a release tag of any " +
|
|
432
|
+
"recipe that declines proposed changes, so there was nothing to compare with."
|
|
433
|
+
);
|
|
434
|
+
}
|
|
394
435
|
footer();
|
|
395
436
|
}
|
|
396
437
|
|