@sous-io/sous 0.2.36 → 1.0.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 +14 -256
- package/dist/bin.js +11 -0
- package/dist/sous.js +1093 -0
- package/docs/markdown/README.md +5 -4
- package/docs/markdown/_sidebar.md +21 -1
- package/docs/markdown/commands.md +294 -431
- package/docs/markdown/config-discovery.md +25 -14
- package/docs/markdown/config-inspection.md +53 -31
- package/docs/markdown/config-layers.md +168 -66
- package/docs/markdown/config-variables.md +44 -11
- package/docs/markdown/configuration.md +104 -17
- package/docs/markdown/design-principles.md +4 -1
- package/docs/markdown/repositories-authoring.md +30 -0
- package/docs/markdown/repositories-consuming.md +72 -14
- package/docs/markdown/repositories-file-formats.md +31 -13
- package/docs/markdown/repositories-providers.md +48 -57
- package/docs/markdown/repositories-quickstart.md +3 -2
- package/docs/markdown/repositories-troubleshooting.md +27 -1
- package/docs/markdown/repositories-variables.md +6 -1
- package/docs/markdown/repositories.md +18 -8
- package/package.json +48 -107
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +4 -6
- package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +2 -0
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +10 -9
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +35 -14
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +51 -20
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/sous.config.schema.json +351 -132
- package/bin/run.js +0 -51
- package/src/base-command.ts +0 -457
- package/src/commands/build.ts +0 -181
- package/src/commands/clear.ts +0 -79
- package/src/commands/compile.ts +0 -155
- package/src/commands/config/get.ts +0 -110
- package/src/commands/config/show.ts +0 -32
- package/src/commands/config/validate.ts +0 -53
- package/src/commands/help.ts +0 -46
- package/src/commands/init.ts +0 -289
- package/src/commands/launch.ts +0 -139
- package/src/commands/lock/rebuild.ts +0 -239
- package/src/commands/lock/show.ts +0 -115
- package/src/commands/namespace/list.ts +0 -138
- package/src/commands/namespace/show.ts +0 -130
- package/src/commands/prune.ts +0 -43
- package/src/commands/recipe/list.ts +0 -112
- package/src/commands/recipe/show.ts +0 -439
- package/src/commands/repo/add.ts +0 -145
- package/src/commands/repo/contribute.ts +0 -604
- package/src/commands/repo/gc.ts +0 -173
- package/src/commands/repo/init.ts +0 -136
- package/src/commands/repo/link.ts +0 -899
- package/src/commands/repo/list.ts +0 -236
- package/src/commands/repo/release.ts +0 -685
- package/src/commands/repo/remove.ts +0 -194
- package/src/commands/repo/search.ts +0 -243
- package/src/commands/repo/submit.ts +0 -341
- package/src/commands/repo/unlink.ts +0 -461
- package/src/commands/subscription/add.ts +0 -286
- package/src/commands/subscription/list.ts +0 -208
- package/src/commands/subscription/remove.ts +0 -182
- package/src/commands/subscription/update.ts +0 -216
- package/src/commands/update.ts +0 -178
- package/src/commands/vars/ask.ts +0 -417
- package/src/commands/vars/index.ts +0 -79
- package/src/commands/vars/list.ts +0 -67
- package/src/commands/vars/show.ts +0 -77
- package/src/config-command.ts +0 -30
- package/src/lib/build-preparation.ts +0 -128
- package/src/lib/build-service.ts +0 -580
- package/src/lib/config-discovery.ts +0 -378
- package/src/lib/config-inspect.ts +0 -145
- package/src/lib/config-kernel.mjs +0 -379
- package/src/lib/config-schema.ts +0 -401
- package/src/lib/env-file.ts +0 -328
- package/src/lib/env-local.ts +0 -212
- package/src/lib/errors.ts +0 -32
- package/src/lib/glob-pattern.ts +0 -24
- package/src/lib/include-resolver.ts +0 -523
- package/src/lib/interactive.ts +0 -165
- package/src/lib/markdown-compiler.ts +0 -947
- package/src/lib/package-info.ts +0 -25
- package/src/lib/pid-service.ts +0 -99
- package/src/lib/project-install.d.mts +0 -44
- package/src/lib/project-install.mjs +0 -234
- package/src/lib/project-scaffold/index.ts +0 -332
- package/src/lib/project-scaffold/templates.ts +0 -240
- package/src/lib/repos/catalog-display.ts +0 -215
- package/src/lib/repos/catalog-inputs.ts +0 -288
- package/src/lib/repos/catalog.ts +0 -842
- package/src/lib/repos/contribute.ts +0 -452
- package/src/lib/repos/core-recipe.ts +0 -105
- package/src/lib/repos/declarations.ts +0 -187
- package/src/lib/repos/defaults.ts +0 -175
- package/src/lib/repos/formats/common.ts +0 -440
- package/src/lib/repos/formats/index-file.ts +0 -253
- package/src/lib/repos/formats/links-map.ts +0 -98
- package/src/lib/repos/formats/lockfile.ts +0 -169
- package/src/lib/repos/formats/patterns.ts +0 -57
- package/src/lib/repos/formats/recipe-manifest.ts +0 -205
- package/src/lib/repos/formats/repo-manifest.ts +0 -97
- package/src/lib/repos/formats/store-entry.ts +0 -84
- package/src/lib/repos/formats/variable-definition.ts +0 -268
- package/src/lib/repos/freshness.ts +0 -264
- package/src/lib/repos/git-clone.ts +0 -850
- package/src/lib/repos/identity.ts +0 -89
- package/src/lib/repos/index.ts +0 -57
- package/src/lib/repos/links.ts +0 -356
- package/src/lib/repos/load-manifest.ts +0 -237
- package/src/lib/repos/lock-service.ts +0 -454
- package/src/lib/repos/locked-namespace-resolver.ts +0 -232
- package/src/lib/repos/locked-recipe-lookup.ts +0 -123
- package/src/lib/repos/locked-recipes.ts +0 -254
- package/src/lib/repos/managed-layer.ts +0 -422
- package/src/lib/repos/memories-hash-name.ts +0 -33
- package/src/lib/repos/namespace-resolver.ts +0 -527
- package/src/lib/repos/providers/base.ts +0 -268
- package/src/lib/repos/providers/git.ts +0 -237
- package/src/lib/repos/providers/github.ts +0 -589
- package/src/lib/repos/providers/gitlab.ts +0 -332
- package/src/lib/repos/providers/http.ts +0 -128
- package/src/lib/repos/providers/index-cache.ts +0 -421
- package/src/lib/repos/providers/index.ts +0 -106
- package/src/lib/repos/providers/local.ts +0 -391
- package/src/lib/repos/providers/provider.ts +0 -615
- package/src/lib/repos/recipe-config-layers.ts +0 -316
- package/src/lib/repos/recipe-key-matcher.ts +0 -62
- package/src/lib/repos/recipe-memories.ts +0 -178
- package/src/lib/repos/recipe-targets.ts +0 -236
- package/src/lib/repos/ref-search.ts +0 -46
- package/src/lib/repos/release/bump.ts +0 -161
- package/src/lib/repos/release/changelog.ts +0 -461
- package/src/lib/repos/release/git-state.ts +0 -460
- package/src/lib/repos/release/index-builder.ts +0 -1108
- package/src/lib/repos/release/index.ts +0 -19
- package/src/lib/repos/release/plan.ts +0 -521
- package/src/lib/repos/release/settle.ts +0 -154
- package/src/lib/repos/release/submissions.ts +0 -214
- package/src/lib/repos/release/submit-checkout.ts +0 -290
- package/src/lib/repos/release/submit-questions.ts +0 -153
- package/src/lib/repos/release/submit-service.ts +0 -898
- package/src/lib/repos/release/tags.ts +0 -239
- package/src/lib/repos/release/validate.ts +0 -463
- package/src/lib/repos/resolver.ts +0 -1028
- package/src/lib/repos/scaffold/index.ts +0 -245
- package/src/lib/repos/scaffold/templates.ts +0 -517
- package/src/lib/repos/seed.ts +0 -473
- package/src/lib/repos/store/contract.ts +0 -64
- package/src/lib/repos/store/hash.ts +0 -191
- package/src/lib/repos/store/recipe-store.ts +0 -599
- package/src/lib/repos/store/settings.ts +0 -58
- package/src/lib/repos/subscription-service.ts +0 -3859
- package/src/lib/repos/trust.ts +0 -447
- package/src/lib/repos/update-plan.ts +0 -235
- package/src/lib/self-update/index.ts +0 -14
- package/src/lib/self-update/installs.ts +0 -412
- package/src/lib/self-update/managers.ts +0 -126
- package/src/lib/self-update/plan.ts +0 -315
- package/src/lib/self-update/registry.ts +0 -99
- package/src/lib/self-update/run.ts +0 -506
- package/src/lib/self-update/versions.ts +0 -243
- package/src/lib/settings.ts +0 -1105
- package/src/lib/sous-home.ts +0 -104
- package/src/lib/state.ts +0 -167
- package/src/lib/vars/answers.ts +0 -192
- package/src/lib/vars/ask.ts +0 -1152
- package/src/lib/vars/definition-source.ts +0 -261
- package/src/lib/vars/display.ts +0 -231
- package/src/lib/vars/index.ts +0 -20
- package/src/lib/vars/ladder.ts +0 -282
- package/src/lib/vars/mappings.ts +0 -260
- package/src/lib/vars/names.ts +0 -94
- package/src/lib/vars/preanswers.ts +0 -395
- package/src/lib/vars/question-plan.ts +0 -241
- package/src/lib/vars/report.ts +0 -228
- package/src/lib/vars/safe-regex.ts +0 -235
- package/src/lib/vars/secrets.ts +0 -131
- package/src/lib/vars/validate.ts +0 -312
- package/src/lib/version-report.ts +0 -90
- package/src/lib/watch-loop.ts +0 -148
- package/src/lib/watch-service.ts +0 -116
- package/src/services/ref-resolver/container.ts +0 -107
- package/src/services/ref-resolver/dependency.ts +0 -60
- package/src/services/ref-resolver/format.ts +0 -197
- package/src/services/ref-resolver/glob.ts +0 -115
- package/src/services/ref-resolver/hash-names.ts +0 -155
- package/src/services/ref-resolver/index.ts +0 -55
- package/src/services/ref-resolver/injectable.ts +0 -36
- package/src/services/ref-resolver/location.ts +0 -62
- package/src/services/ref-resolver/lookups/cached-index-lookup.ts +0 -53
- package/src/services/ref-resolver/lookups/catalog-lookup.ts +0 -23
- package/src/services/ref-resolver/lookups/catalog-matcher.ts +0 -261
- package/src/services/ref-resolver/lookups/chained-lookup.ts +0 -27
- package/src/services/ref-resolver/lookups/env-var-lookup.ts +0 -65
- package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +0 -150
- package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +0 -62
- package/src/services/ref-resolver/lookups/ref-lookup.ts +0 -54
- package/src/services/ref-resolver/lookups/variable-lookup.ts +0 -123
- package/src/services/ref-resolver/named-refs.ts +0 -144
- package/src/services/ref-resolver/parser/env-var-splitter.ts +0 -27
- package/src/services/ref-resolver/parser/location-splitter.ts +0 -239
- package/src/services/ref-resolver/parser/name-path-splitter.ts +0 -200
- package/src/services/ref-resolver/parser/partial-ref.ts +0 -84
- package/src/services/ref-resolver/parser/query-splitter.ts +0 -61
- package/src/services/ref-resolver/parser/range-splitter.ts +0 -52
- package/src/services/ref-resolver/parser/ref-parser.ts +0 -115
- package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +0 -51
- package/src/services/ref-resolver/parts.ts +0 -115
- package/src/services/ref-resolver/pruners/command-line-pruner.ts +0 -47
- package/src/services/ref-resolver/pruners/include-pruner.ts +0 -66
- package/src/services/ref-resolver/pruners/manifest-pruner.ts +0 -130
- package/src/services/ref-resolver/pruners/ref-pruner.ts +0 -135
- package/src/services/ref-resolver/pruners/rule-helpers.ts +0 -44
- package/src/services/ref-resolver/pruners/stored-key-pruner.ts +0 -136
- package/src/services/ref-resolver/ref-helpers.ts +0 -62
- package/src/services/ref-resolver/ref-picker-service.ts +0 -204
- package/src/services/ref-resolver/ref-report.ts +0 -84
- package/src/services/ref-resolver/ref-resolve-arguments.ts +0 -47
- package/src/services/ref-resolver/ref-resolve-result.ts +0 -36
- package/src/services/ref-resolver/ref-resolver-service.ts +0 -323
- package/src/services/ref-resolver/source.ts +0 -48
- package/src/services/ref-resolver/tokens.ts +0 -25
- package/src/services/ref-resolver/types.ts +0 -155
- package/src/templating/filters/bullet-list.ts +0 -9
- package/src/templating/filters/index.ts +0 -8
- package/src/templating/init-liquid-engine.ts +0 -139
- package/src/templating/lib/glob-files.ts +0 -74
- package/src/templating/lib/import-export.ts +0 -32
- package/src/templating/lib/secret-scope.ts +0 -41
- package/src/templating/lib/tag-args.ts +0 -19
- package/src/templating/tags/exportScalarVarsJs.ts +0 -46
- package/src/templating/tags/getFiles.ts +0 -89
- package/src/templating/tags/index.ts +0 -14
- package/src/templating/tags/listFiles.ts +0 -54
- package/src/templating/tags/showVars.ts +0 -28
- package/src/utils/choice-prompt.ts +0 -143
- package/src/utils/command-errors.ts +0 -186
- package/src/utils/command-help.ts +0 -45
- package/src/utils/confirm-prompt.ts +0 -110
- package/src/utils/flags.ts +0 -177
- package/src/utils/formatting.ts +0 -849
- package/src/utils/prompts.ts +0 -53
- package/src/utils/sous-directory.ts +0 -245
- package/src/utils/table.ts +0 -603
- package/src/utils/value-prompt.ts +0 -119
package/src/lib/repos/seed.ts
DELETED
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seeding the store with the core recipe that ships inside the package.
|
|
3
|
-
*
|
|
4
|
-
* Every project is subscribed to the `core` namespace, and a project must be
|
|
5
|
-
* able to build with no network at all, including the very first time sous runs
|
|
6
|
-
* on a machine. That is what this module is for: it copies the packaged core
|
|
7
|
-
* recipe into the machine-wide store and, when nothing has ever been fetched
|
|
8
|
-
* from the official repository, writes a small stand-in index so the resolver
|
|
9
|
-
* can find the recipe it has just seeded.
|
|
10
|
-
*
|
|
11
|
-
* Three properties matter, and each one is deliberate:
|
|
12
|
-
*
|
|
13
|
-
* - IDEMPOTENT. Seeding runs before every build. When the store already holds
|
|
14
|
-
* a verifying entry at this version, nothing is copied and nothing is
|
|
15
|
-
* written.
|
|
16
|
-
* - OFFLINE. Nothing here touches the network, and nothing here fails a build.
|
|
17
|
-
* A store that cannot be written is reported and the build carries on
|
|
18
|
-
* without core, which is far better than refusing to run.
|
|
19
|
-
* - REPLACEABLE. The stand-in index is written only when the cache holds no
|
|
20
|
-
* real index for the official repository. It carries a note saying sous
|
|
21
|
-
* wrote it, so a later run recognizes its own placeholder and is willing to
|
|
22
|
-
* replace it; the first successful fetch overwrites it with the real thing.
|
|
23
|
-
*
|
|
24
|
-
* The stand-in alone is not enough, and that is what `coreIndexOverlay` is for.
|
|
25
|
-
* A machine that has already fetched the real index keeps it, quite rightly, and
|
|
26
|
-
* that index publishes whatever versions of the core recipe the repository has
|
|
27
|
-
* released. Upgrade sous and the version it asks for is, for a while, one the
|
|
28
|
-
* repository has not published yet: the real index wins over the stand-in, the
|
|
29
|
-
* resolver finds nothing satisfying the range, and the project silently loses
|
|
30
|
-
* its core skills until the release pipeline catches up. So the packaged version
|
|
31
|
-
* is folded into the index IN MEMORY whenever it is missing, carrying the hash
|
|
32
|
-
* of the entry that was just seeded. The cached file is never touched, so it
|
|
33
|
-
* stays an honest record of what upstream served, and the moment upstream does
|
|
34
|
-
* publish that version its own entry is what gets used.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
import fs from "node:fs";
|
|
38
|
-
import path from "node:path";
|
|
39
|
-
import semver from "semver";
|
|
40
|
-
import {
|
|
41
|
-
parseIndexFile,
|
|
42
|
-
stringifyIndexFile,
|
|
43
|
-
type IndexFile,
|
|
44
|
-
} from "./formats/index-file.js";
|
|
45
|
-
import {
|
|
46
|
-
INDEX_CACHE_DIRNAME,
|
|
47
|
-
INDEX_SIDECAR_SUFFIX,
|
|
48
|
-
type IndexOverlay,
|
|
49
|
-
} from "./providers/index-cache.js";
|
|
50
|
-
import { warning } from "../../utils/formatting.js";
|
|
51
|
-
import { hashDirectorySync } from "./store/hash.js";
|
|
52
|
-
import { identitySegments } from "./identity.js";
|
|
53
|
-
import { ensureIndexCacheDirectory } from "../../utils/sous-directory.js";
|
|
54
|
-
import type { RecipeStoreLike, StoreKey } from "./store/contract.js";
|
|
55
|
-
import {
|
|
56
|
-
CORE_NAMESPACE,
|
|
57
|
-
CORE_RECIPE_KEY,
|
|
58
|
-
CORE_RECIPE_NAME,
|
|
59
|
-
CORE_RECIPE_PATH,
|
|
60
|
-
OFFICIAL_REPO_IDENTITY,
|
|
61
|
-
OFFICIAL_REPO_NAME,
|
|
62
|
-
packagedCoreRecipeDir,
|
|
63
|
-
readPackagedCoreManifest,
|
|
64
|
-
} from "./core-recipe.js";
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The note the seed index carries in its `$comment` field. A cached index
|
|
68
|
-
* carrying this exact text is sous's own placeholder rather than something a
|
|
69
|
-
* repository published, so a later seed may overwrite it.
|
|
70
|
-
*/
|
|
71
|
-
export const SEED_INDEX_COMMENT =
|
|
72
|
-
"Written by sous itself from the core recipe inside the installed package, so a " +
|
|
73
|
-
"project can build before it has ever reached the network. The first successful " +
|
|
74
|
-
"fetch of the real index replaces this file.";
|
|
75
|
-
|
|
76
|
-
/** The description the seed index gives the core namespace. */
|
|
77
|
-
const CORE_NAMESPACE_DESCRIPTION =
|
|
78
|
-
"Skills that teach an agent about sous itself: what sous manages, how its " +
|
|
79
|
-
"configuration works, how templates render, and how skills are written. " +
|
|
80
|
-
"Auto-subscribed in every project, with opt-out-only semantics.";
|
|
81
|
-
|
|
82
|
-
/** What the seed is asked to do. */
|
|
83
|
-
export type SeedCoreRecipeOptions = {
|
|
84
|
-
/** The store to seed. */
|
|
85
|
-
store: RecipeStoreLike;
|
|
86
|
-
/** The store's root directory, which is where the index cache lives. */
|
|
87
|
-
storeRoot?: string;
|
|
88
|
-
/** The version to seed, which is by rule the running sous version. */
|
|
89
|
-
sousVersion: string;
|
|
90
|
-
/** The installed package's root directory. Defaults to the running CLI's own. */
|
|
91
|
-
packageRoot?: string;
|
|
92
|
-
/** The clock, so a written timestamp is predictable in tests. */
|
|
93
|
-
now?: () => Date;
|
|
94
|
-
/**
|
|
95
|
-
* The index cache to teach about the packaged version, once the seed knows
|
|
96
|
-
* its hash. Without this the seeded recipe is resolvable only on a machine
|
|
97
|
-
* whose cached index is sous's own stand-in.
|
|
98
|
-
*/
|
|
99
|
-
indexCache?: IndexOverlayTarget;
|
|
100
|
-
/** Where warnings go. Defaults to the console warning banner. */
|
|
101
|
-
warn?: (message: string) => void;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The part of the index cache the seed uses: somewhere to install what it has
|
|
106
|
-
* learned. Named as a small structural type so the seed does not depend on the
|
|
107
|
-
* cache's implementation.
|
|
108
|
-
*/
|
|
109
|
-
export type IndexOverlayTarget = {
|
|
110
|
-
setOverlay(overlay: IndexOverlay | undefined): void;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/** What the seed did. */
|
|
114
|
-
export type SeedCoreRecipeReport = {
|
|
115
|
-
/** True when the recipe's files were copied into the store on this run. */
|
|
116
|
-
seeded: boolean;
|
|
117
|
-
/** True when the store already held a verifying entry, so nothing was copied. */
|
|
118
|
-
alreadyPresent: boolean;
|
|
119
|
-
/** True when a stand-in index was written for the official repository. */
|
|
120
|
-
wroteIndex: boolean;
|
|
121
|
-
/** The version that was seeded. */
|
|
122
|
-
version: string;
|
|
123
|
-
/** The content hash of the seeded entry, when there is one. */
|
|
124
|
-
hash?: string;
|
|
125
|
-
/**
|
|
126
|
-
* Why the seed did nothing, when it could not run. A complete sentence, meant
|
|
127
|
-
* to be shown as a warning; the seed never throws.
|
|
128
|
-
*/
|
|
129
|
-
skippedBecause?: string;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Copies the packaged core recipe into the store and, when nothing real is
|
|
134
|
-
* cached, writes a stand-in index for the official repository so the resolver
|
|
135
|
-
* can see what was just seeded.
|
|
136
|
-
*
|
|
137
|
-
* @param options - The store to seed, the version to seed it at, and the clock.
|
|
138
|
-
*/
|
|
139
|
-
export async function seedCoreRecipe(
|
|
140
|
-
options: SeedCoreRecipeOptions
|
|
141
|
-
): Promise<SeedCoreRecipeReport> {
|
|
142
|
-
const version = options.sousVersion;
|
|
143
|
-
const now = options.now ?? (() => new Date());
|
|
144
|
-
const storeRoot = options.storeRoot ?? options.store.root;
|
|
145
|
-
|
|
146
|
-
const key: StoreKey = {
|
|
147
|
-
identity: OFFICIAL_REPO_IDENTITY,
|
|
148
|
-
namespace: CORE_NAMESPACE,
|
|
149
|
-
name: CORE_RECIPE_NAME,
|
|
150
|
-
version,
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const report: SeedCoreRecipeReport = {
|
|
154
|
-
seeded: false,
|
|
155
|
-
alreadyPresent: false,
|
|
156
|
-
wroteIndex: false,
|
|
157
|
-
version,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const existing = await options.store.get(key);
|
|
162
|
-
|
|
163
|
-
if (existing !== undefined) {
|
|
164
|
-
report.alreadyPresent = true;
|
|
165
|
-
report.hash = existing.entry.hash;
|
|
166
|
-
} else {
|
|
167
|
-
const source = packagedCoreRecipeDir(options.packageRoot);
|
|
168
|
-
const entry = await options.store.put(key, source);
|
|
169
|
-
report.seeded = true;
|
|
170
|
-
report.hash = entry.hash;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// The cache is taught before the stand-in is considered, because the two
|
|
174
|
-
// answer different halves of the same problem: the stand-in covers a machine
|
|
175
|
-
// that has never fetched anything, and the overlay covers one that has.
|
|
176
|
-
options.indexCache?.setOverlay(
|
|
177
|
-
coreIndexOverlay({
|
|
178
|
-
version,
|
|
179
|
-
hash: report.hash!,
|
|
180
|
-
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
181
|
-
...(options.warn === undefined ? {} : { warn: options.warn }),
|
|
182
|
-
})
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
report.wroteIndex = writeSeedIndex({
|
|
186
|
-
storeRoot,
|
|
187
|
-
version,
|
|
188
|
-
hash: report.hash!,
|
|
189
|
-
now: now(),
|
|
190
|
-
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
191
|
-
});
|
|
192
|
-
} catch (error) {
|
|
193
|
-
// Seeding is a convenience, not a precondition: a read-only store or a
|
|
194
|
-
// damaged package must not stop a build that may not even use recipes.
|
|
195
|
-
report.skippedBecause =
|
|
196
|
-
`Sous could not seed the core recipe it ships with, so the skills in the ` +
|
|
197
|
-
`'core' namespace are unavailable until this is fixed.\n` +
|
|
198
|
-
`${error instanceof Error ? error.message : String(error)}`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return report;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** What the overlay needs to know about the version sous ships. */
|
|
205
|
-
export type CoreIndexOverlayOptions = {
|
|
206
|
-
/** The packaged version, which is by rule the running sous version. */
|
|
207
|
-
version: string;
|
|
208
|
-
/**
|
|
209
|
-
* The content hash of the entry the seed put in the store, or a function
|
|
210
|
-
* that works it out the first time it is needed. The function form is what
|
|
211
|
-
* lets an overlay be installed before anything has been seeded.
|
|
212
|
-
*/
|
|
213
|
-
hash: string | (() => string);
|
|
214
|
-
/** The installed package's root directory. Defaults to the running CLI's own. */
|
|
215
|
-
packageRoot?: string;
|
|
216
|
-
/** Where the one warning this can produce goes. Defaults to the console banner. */
|
|
217
|
-
warn?: (message: string) => void;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* The overlay every index cache a command builds starts with: the packaged core
|
|
222
|
-
* version, hashed from the package itself the first time the official
|
|
223
|
-
* repository's index is read.
|
|
224
|
-
*
|
|
225
|
-
* Seeding installs a precise overlay of its own (carrying the hash of the entry
|
|
226
|
-
* it just wrote), but only the commands that seed get that one. This is what
|
|
227
|
-
* makes the packaged version resolvable everywhere else too: a lockfile rebuild,
|
|
228
|
-
* a browsing command, anything that reads the official index. The hash of the
|
|
229
|
-
* packaged folder is the hash a seeded store entry carries, because the store
|
|
230
|
-
* copies the folder byte for byte.
|
|
231
|
-
*
|
|
232
|
-
* A package whose core recipe cannot be read adds nothing, silently: seeding
|
|
233
|
-
* reports that failure in full, and a read-only command has nothing better to
|
|
234
|
-
* say about it.
|
|
235
|
-
*
|
|
236
|
-
* @param options - The packaged version, and where the package is.
|
|
237
|
-
*/
|
|
238
|
-
export function packagedCoreIndexOverlay(options: {
|
|
239
|
-
/** The packaged version, which is by rule the running sous version. */
|
|
240
|
-
version: string;
|
|
241
|
-
/** The installed package's root directory. Defaults to the running CLI's own. */
|
|
242
|
-
packageRoot?: string;
|
|
243
|
-
/** Where the one warning the overlay can produce goes. */
|
|
244
|
-
warn?: (message: string) => void;
|
|
245
|
-
}): IndexOverlay {
|
|
246
|
-
let hash: string | null | undefined;
|
|
247
|
-
const packagedHash = (): string | null => {
|
|
248
|
-
if (hash === undefined) {
|
|
249
|
-
try {
|
|
250
|
-
hash = hashDirectorySync(packagedCoreRecipeDir(options.packageRoot));
|
|
251
|
-
} catch {
|
|
252
|
-
hash = null;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return hash;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const overlay = coreIndexOverlay({
|
|
259
|
-
version: options.version,
|
|
260
|
-
hash: () => packagedHash()!,
|
|
261
|
-
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
262
|
-
...(options.warn === undefined ? {} : { warn: options.warn }),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return (identity, index) => {
|
|
266
|
-
if (identity !== OFFICIAL_REPO_IDENTITY) return index;
|
|
267
|
-
if (packagedHash() === null) return index;
|
|
268
|
-
return overlay(identity, index);
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Builds the overlay that makes the packaged core recipe resolvable whatever
|
|
274
|
-
* the official repository has published so far.
|
|
275
|
-
*
|
|
276
|
-
* Three cases, and the order matters:
|
|
277
|
-
*
|
|
278
|
-
* - The index already publishes this version with this hash: nothing to add,
|
|
279
|
-
* and the index is returned untouched. This is the ordinary case once the
|
|
280
|
-
* release pipeline has caught up.
|
|
281
|
-
* - The index publishes this version with a DIFFERENT hash: upstream wins, and
|
|
282
|
-
* sous says so once. The two are built from the same bytes, so this should
|
|
283
|
-
* not happen; when it does, the version a repository published is the one a
|
|
284
|
-
* lockfile should be able to pin on any machine, seeded or not.
|
|
285
|
-
* - The index does not publish this version at all: it is added, carrying the
|
|
286
|
-
* hash of the entry the seed just wrote and marked `seeded` so a listing can
|
|
287
|
-
* say it came packaged with sous.
|
|
288
|
-
*
|
|
289
|
-
* Nothing here writes anything. The returned index is a copy; the one passed in
|
|
290
|
-
* is left exactly as it was read.
|
|
291
|
-
*
|
|
292
|
-
* @param options - The packaged version, its hash, and where a warning goes.
|
|
293
|
-
*/
|
|
294
|
-
export function coreIndexOverlay(options: CoreIndexOverlayOptions): IndexOverlay {
|
|
295
|
-
let warned = false;
|
|
296
|
-
const packagedHash = (): string =>
|
|
297
|
-
typeof options.hash === "function" ? options.hash() : options.hash;
|
|
298
|
-
|
|
299
|
-
return (identity: string, index: IndexFile): IndexFile => {
|
|
300
|
-
if (identity !== OFFICIAL_REPO_IDENTITY) return index;
|
|
301
|
-
|
|
302
|
-
const published = index.recipes[CORE_RECIPE_KEY]?.versions[options.version];
|
|
303
|
-
|
|
304
|
-
if (published !== undefined) {
|
|
305
|
-
if (published.hash !== packagedHash() && !warned) {
|
|
306
|
-
warned = true;
|
|
307
|
-
(options.warn ?? warning)(
|
|
308
|
-
`The repository '${OFFICIAL_REPO_NAME}' publishes version ${options.version} of ` +
|
|
309
|
-
`'${CORE_RECIPE_KEY}' with different contents from the copy inside this ` +
|
|
310
|
-
`installation of sous, so sous is using the published one.\n` +
|
|
311
|
-
` Published: ${published.hash}\n` +
|
|
312
|
-
` Packaged: ${packagedHash()}\n` +
|
|
313
|
-
` Reinstalling sous will bring the two back into line.`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
return index;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const recipe = index.recipes[CORE_RECIPE_KEY];
|
|
320
|
-
const description = recipe?.description ?? packagedCoreDescription(options.packageRoot);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
...index,
|
|
324
|
-
namespaces: {
|
|
325
|
-
...index.namespaces,
|
|
326
|
-
[CORE_NAMESPACE]: index.namespaces[CORE_NAMESPACE] ?? {
|
|
327
|
-
description: CORE_NAMESPACE_DESCRIPTION,
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
recipes: {
|
|
331
|
-
...index.recipes,
|
|
332
|
-
[CORE_RECIPE_KEY]: {
|
|
333
|
-
path: recipe?.path ?? CORE_RECIPE_PATH,
|
|
334
|
-
...(description === undefined ? {} : { description }),
|
|
335
|
-
versions: {
|
|
336
|
-
...recipe?.versions,
|
|
337
|
-
[options.version]: {
|
|
338
|
-
hash: packagedHash(),
|
|
339
|
-
tag: `${CORE_RECIPE_KEY}@${options.version}`,
|
|
340
|
-
prerelease: semver.prerelease(options.version) !== null,
|
|
341
|
-
seeded: true,
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
};
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* The packaged recipe's one-paragraph summary, or undefined when the manifest
|
|
352
|
-
* cannot be read. A description is decoration; losing it must not cost a project
|
|
353
|
-
* its core skills.
|
|
354
|
-
*
|
|
355
|
-
* @param packageRoot - The installed package's root directory.
|
|
356
|
-
*/
|
|
357
|
-
function packagedCoreDescription(packageRoot?: string): string | undefined {
|
|
358
|
-
try {
|
|
359
|
-
return readPackagedCoreManifest(packageRoot).description;
|
|
360
|
-
} catch {
|
|
361
|
-
return undefined;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Writes the stand-in index for the official repository, unless the cache
|
|
367
|
-
* already holds one that a repository actually published.
|
|
368
|
-
*
|
|
369
|
-
* Returns true when a file was written.
|
|
370
|
-
*
|
|
371
|
-
* @param input - Where the cache is, and what the seeded entry looks like.
|
|
372
|
-
*/
|
|
373
|
-
function writeSeedIndex(input: {
|
|
374
|
-
storeRoot: string;
|
|
375
|
-
version: string;
|
|
376
|
-
hash: string;
|
|
377
|
-
now: Date;
|
|
378
|
-
packageRoot?: string;
|
|
379
|
-
}): boolean {
|
|
380
|
-
// The cache files an index under the repository's identity, which is several
|
|
381
|
-
// directories deep; the seed writes to exactly the same place a real fetch
|
|
382
|
-
// would, so the first successful fetch replaces this copy rather than
|
|
383
|
-
// sitting beside it.
|
|
384
|
-
const segments = identitySegments(OFFICIAL_REPO_IDENTITY);
|
|
385
|
-
const last = segments.pop()!;
|
|
386
|
-
const directory = path.join(input.storeRoot, INDEX_CACHE_DIRNAME, ...segments);
|
|
387
|
-
const indexPath = path.join(directory, `${last}.json`);
|
|
388
|
-
const sidecarPath = path.join(directory, `${last}${INDEX_SIDECAR_SUFFIX}`);
|
|
389
|
-
|
|
390
|
-
if (!isReplaceableIndex(indexPath, input.version)) return false;
|
|
391
|
-
|
|
392
|
-
const manifest = readPackagedCoreManifest(input.packageRoot);
|
|
393
|
-
const timestamp = input.now.toISOString();
|
|
394
|
-
|
|
395
|
-
const index: IndexFile = {
|
|
396
|
-
$comment: SEED_INDEX_COMMENT,
|
|
397
|
-
formatVersion: 1,
|
|
398
|
-
name: OFFICIAL_REPO_NAME,
|
|
399
|
-
generatedAt: timestamp,
|
|
400
|
-
generator: input.version,
|
|
401
|
-
namespaces: {
|
|
402
|
-
[CORE_NAMESPACE]: { description: CORE_NAMESPACE_DESCRIPTION },
|
|
403
|
-
},
|
|
404
|
-
recipes: {
|
|
405
|
-
[CORE_RECIPE_KEY]: {
|
|
406
|
-
path: CORE_RECIPE_PATH,
|
|
407
|
-
...(manifest.description === undefined
|
|
408
|
-
? {}
|
|
409
|
-
: { description: manifest.description }),
|
|
410
|
-
versions: {
|
|
411
|
-
[input.version]: {
|
|
412
|
-
hash: input.hash,
|
|
413
|
-
tag: `${CORE_RECIPE_KEY}@${input.version}`,
|
|
414
|
-
prerelease: semver.prerelease(input.version) !== null,
|
|
415
|
-
releasedAt: timestamp,
|
|
416
|
-
seeded: true,
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
},
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
// No sidecar is written, deliberately. The sidecar is what says "this copy was
|
|
424
|
-
// fetched at such a time", and this copy was not fetched at all. Without one,
|
|
425
|
-
// sous treats the stand-in as infinitely old and tries upstream on the very
|
|
426
|
-
// next command: the first run with a network gets the real index immediately
|
|
427
|
-
// rather than waiting out a freshness window it never earned. When there is no
|
|
428
|
-
// network the fetch fails, the stand-in is used, and sous says so, which is the
|
|
429
|
-
// same last-good behavior every other repository gets.
|
|
430
|
-
//
|
|
431
|
-
// Any sidecar left over from an earlier fetch is removed for the same reason.
|
|
432
|
-
ensureIndexCacheDirectory(path.join(input.storeRoot, INDEX_CACHE_DIRNAME));
|
|
433
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
434
|
-
fs.writeFileSync(indexPath, stringifyIndexFile(index), "utf8");
|
|
435
|
-
fs.rmSync(sidecarPath, { force: true });
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* True when the seed may write over whatever is at `indexPath`.
|
|
441
|
-
*
|
|
442
|
-
* Three cases, in order:
|
|
443
|
-
*
|
|
444
|
-
* - Nothing cached, or a cached file that no longer parses: write. A damaged
|
|
445
|
-
* copy is worth less than a stand-in that works.
|
|
446
|
-
* - A stand-in sous wrote itself that does not name the version being seeded:
|
|
447
|
-
* write. This is how a machine that upgrades sous while offline ends up with
|
|
448
|
-
* an index naming the new version.
|
|
449
|
-
* - Anything else, including sous's own stand-in that already names this
|
|
450
|
-
* version: leave it alone. Rewriting it on every build would reset the
|
|
451
|
-
* freshness clock, and sous would then never look upstream for the real one.
|
|
452
|
-
*
|
|
453
|
-
* @param indexPath - Where the cached index for the official repository lives.
|
|
454
|
-
* @param version - The version being seeded.
|
|
455
|
-
*/
|
|
456
|
-
function isReplaceableIndex(indexPath: string, version: string): boolean {
|
|
457
|
-
let text: string;
|
|
458
|
-
try {
|
|
459
|
-
text = fs.readFileSync(indexPath, "utf8");
|
|
460
|
-
} catch {
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
let cached: IndexFile;
|
|
465
|
-
try {
|
|
466
|
-
cached = parseIndexFile(JSON.parse(text), indexPath);
|
|
467
|
-
} catch {
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (cached.$comment !== SEED_INDEX_COMMENT) return false;
|
|
472
|
-
return cached.recipes[CORE_RECIPE_KEY]?.versions[version] === undefined;
|
|
473
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The contract between the recipe store (Phase 2a) and everything that fills or reads it
|
|
3
|
-
* (providers, resolver, lockfile restore, the build). Both sides are written against this
|
|
4
|
-
* file; keep it dependency-free so either side can compile without the other.
|
|
5
|
-
*/
|
|
6
|
-
import type { StoreEntry } from "../formats/store-entry.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Identifies one recipe version inside one repository.
|
|
10
|
-
*
|
|
11
|
-
* The store is MACHINE-WIDE, so it is keyed by the repository's canonical
|
|
12
|
-
* identity rather than by a project's short name for it: two projects that call
|
|
13
|
-
* the same repository different things still share one cached copy, and two
|
|
14
|
-
* projects that use the same short name for different repositories never
|
|
15
|
-
* collide.
|
|
16
|
-
*/
|
|
17
|
-
export interface StoreKey {
|
|
18
|
-
/** The repository's canonical identity, such as `github.com/sous-io/sous-recipes`. */
|
|
19
|
-
identity: string;
|
|
20
|
-
namespace: string;
|
|
21
|
-
name: string;
|
|
22
|
-
version: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** What a successful lookup returns: where the files are and the verified marker. */
|
|
26
|
-
export interface StoreHit {
|
|
27
|
-
dir: string;
|
|
28
|
-
entry: StoreEntry;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Options for a garbage-collection pass. */
|
|
32
|
-
export interface StoreGcOptions {
|
|
33
|
-
/** Upper bound for the whole store in bytes; entries are evicted least-recently-used first. */
|
|
34
|
-
maxBytes: number;
|
|
35
|
-
/** Keys that must survive no matter what (everything a lockfile still pins). */
|
|
36
|
-
keep?: StoreKey[];
|
|
37
|
-
dryRun?: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface StoreGcReport {
|
|
41
|
-
evicted: StoreEntry[];
|
|
42
|
-
kept: StoreEntry[];
|
|
43
|
-
bytesBefore: number;
|
|
44
|
-
bytesAfter: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** The recipe store: one immutable folder per recipe version, verified against its content hash. */
|
|
48
|
-
export interface RecipeStoreLike {
|
|
49
|
-
/** The absolute root directory of this store. */
|
|
50
|
-
readonly root: string;
|
|
51
|
-
/** The directory an entry lives in (whether or not it exists). */
|
|
52
|
-
entryDir(key: StoreKey): string;
|
|
53
|
-
/** Copies `sourceDir` into the store, hashes it, verifies against `expectedHash` when given, writes the marker. */
|
|
54
|
-
put(key: StoreKey, sourceDir: string, expectedHash?: string): Promise<StoreEntry>;
|
|
55
|
-
/** Returns the entry if present and its hash still verifies; touches last access. Undefined when absent. */
|
|
56
|
-
get(key: StoreKey): Promise<StoreHit | undefined>;
|
|
57
|
-
has(key: StoreKey): Promise<boolean>;
|
|
58
|
-
remove(key: StoreKey): Promise<void>;
|
|
59
|
-
list(): Promise<StoreEntry[]>;
|
|
60
|
-
gc(options: StoreGcOptions): Promise<StoreGcReport>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Computes the canonical `sha256-<hex>` content hash of a directory tree. */
|
|
64
|
-
export type DirectoryHasher = (dir: string) => Promise<string>;
|