@sous-io/sous 0.2.37 → 1.0.2
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 +1094 -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 -245
- 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 -410
- 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 -413
- 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 -1107
- 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
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical repository identity: what the machine-wide store, the index cache
|
|
3
|
-
* and the lockfile key a repository by.
|
|
4
|
-
*
|
|
5
|
-
* A repository's short name (`sous-recipes`) is a CONSUMER's label. Two
|
|
6
|
-
* projects may call the same repository different things, and two different
|
|
7
|
-
* repositories may be called the same thing in two projects, so a short name
|
|
8
|
-
* can never key anything shared between projects. The identity can: it is
|
|
9
|
-
* derived from the location the repository actually lives at, so every project
|
|
10
|
-
* on a machine agrees about it.
|
|
11
|
-
*
|
|
12
|
-
* github.com/sous-io/sous-recipes
|
|
13
|
-
* gitlab.example.com/group/subgroup/project
|
|
14
|
-
* localhost/home/me/Projects/my-recipes
|
|
15
|
-
*
|
|
16
|
-
* The shape is `<host>/<owner path>/<name>`, lowercased, with any `.git`
|
|
17
|
-
* suffix already gone (the providers strip it while canonicalizing). Short
|
|
18
|
-
* names stay exactly where they were: in a project's config, in its lockfile
|
|
19
|
-
* keys, and in everything sous prints, because that is what a person typed.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import type { CanonicalRepo } from "./providers/provider.js";
|
|
23
|
-
import { REPO_NAME_PATTERN } from "./formats/patterns.js";
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The canonical identity of a repository, as every machine-wide key spells it.
|
|
27
|
-
*
|
|
28
|
-
* repoIdentity({ host: "GitHub.com", owner: "sous-io", name: "sous-recipes" });
|
|
29
|
-
* // -> "github.com/sous-io/sous-recipes"
|
|
30
|
-
*
|
|
31
|
-
* @param canonical - The repository, taken apart by its provider.
|
|
32
|
-
*/
|
|
33
|
-
export function repoIdentity(canonical: CanonicalRepo): string {
|
|
34
|
-
const name = canonical.name.replace(/\.git$/i, "");
|
|
35
|
-
return identitySegments(`${canonical.host}/${canonical.owner}/${name}`).join("/");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The path segments an identity is made of, with empty segments dropped and
|
|
40
|
-
* every segment lowercased. A local repository's owner is an absolute path, so
|
|
41
|
-
* its leading separator would otherwise produce an empty first segment.
|
|
42
|
-
*
|
|
43
|
-
* identitySegments("localhost//home/me/recipes");
|
|
44
|
-
* // -> ["localhost", "home", "me", "recipes"]
|
|
45
|
-
*
|
|
46
|
-
* @param identity - The identity, or the pieces of one joined with slashes.
|
|
47
|
-
*/
|
|
48
|
-
export function identitySegments(identity: string): string[] {
|
|
49
|
-
return identity
|
|
50
|
-
.split("/")
|
|
51
|
-
.map((segment) => segment.trim().toLowerCase())
|
|
52
|
-
.filter((segment) => segment.length > 0);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* A short name derived from an identity, used when sous has to name a
|
|
57
|
-
* repository a project has never added (a dependency's locator URL, for
|
|
58
|
-
* instance) before the person has chosen a name for it.
|
|
59
|
-
*
|
|
60
|
-
* shortNameFromIdentity("github.com/sous-io/sous-recipes"); // -> "sous-recipes"
|
|
61
|
-
*
|
|
62
|
-
* @param identity - The repository's canonical identity.
|
|
63
|
-
*/
|
|
64
|
-
export function shortNameFromIdentity(identity: string): string {
|
|
65
|
-
const segments = identitySegments(identity);
|
|
66
|
-
const last = segments[segments.length - 1] ?? "";
|
|
67
|
-
const cleaned = last
|
|
68
|
-
.replace(/[^a-z0-9-]+/g, "-")
|
|
69
|
-
.replace(/-+/g, "-")
|
|
70
|
-
.replace(/^-+|-+$/g, "");
|
|
71
|
-
|
|
72
|
-
// A short name is lowercase kebab-case starting with a letter. A repository
|
|
73
|
-
// whose name starts with a digit, or is made entirely of characters a name
|
|
74
|
-
// may not carry, still needs something to be called.
|
|
75
|
-
if (REPO_NAME_PATTERN.test(cleaned)) return cleaned;
|
|
76
|
-
return cleaned.length === 0 ? "repository" : `repo-${cleaned}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* True when two identities name the same repository. Identities are already
|
|
81
|
-
* normalized, so this is a plain comparison; it exists so callers read as what
|
|
82
|
-
* they mean rather than as a string equality.
|
|
83
|
-
*
|
|
84
|
-
* @param left - One identity.
|
|
85
|
-
* @param right - The other.
|
|
86
|
-
*/
|
|
87
|
-
export function sameRepoIdentity(left: string, right: string): boolean {
|
|
88
|
-
return left === right;
|
|
89
|
-
}
|
package/src/lib/repos/index.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Repositories layer: every on-disk format, the loaders that read them, and
|
|
3
|
-
* the ref parser that names what is inside them.
|
|
4
|
-
*
|
|
5
|
-
* Import from here rather than from the individual modules, so later phases
|
|
6
|
-
* (the store, the providers, the resolver, the CLI surface) have one place to
|
|
7
|
-
* look for what this layer offers.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// The regular expressions in formats/patterns.js reach here through
|
|
11
|
-
// formats/common.js, which re-exports them; listing patterns.js again would
|
|
12
|
-
// make every one of those names an ambiguous star export.
|
|
13
|
-
export * from "./formats/common.js";
|
|
14
|
-
export * from "./formats/repo-manifest.js";
|
|
15
|
-
export * from "./formats/recipe-manifest.js";
|
|
16
|
-
export * from "./formats/index-file.js";
|
|
17
|
-
export * from "./formats/lockfile.js";
|
|
18
|
-
export * from "./formats/store-entry.js";
|
|
19
|
-
export * from "./formats/links-map.js";
|
|
20
|
-
export * from "./load-manifest.js";
|
|
21
|
-
export * from "./identity.js";
|
|
22
|
-
|
|
23
|
-
// Phase 2a: the machine-wide recipe store, under the user-level sous directory.
|
|
24
|
-
export * from "./store/contract.js";
|
|
25
|
-
export * from "./store/hash.js";
|
|
26
|
-
export * from "./store/recipe-store.js";
|
|
27
|
-
export * from "./store/settings.js";
|
|
28
|
-
|
|
29
|
-
// Phase 6a: the CLI surface for editable checkouts (`sous repo init`, `link`,
|
|
30
|
-
// `unlink`). The links map and its ignore hygiene, the thin git layer both of
|
|
31
|
-
// them use, and the scaffold that `repo init` writes.
|
|
32
|
-
export * from "./links.js";
|
|
33
|
-
export * from "./git-clone.js";
|
|
34
|
-
export * from "./scaffold/index.js";
|
|
35
|
-
|
|
36
|
-
// Phase 2b: the store contract, the providers, the resolver, the trust layer
|
|
37
|
-
// and the lockfile service.
|
|
38
|
-
export * from "./providers/index.js";
|
|
39
|
-
export * from "./resolver.js";
|
|
40
|
-
export * from "./managed-layer.js";
|
|
41
|
-
export * from "./trust.js";
|
|
42
|
-
export * from "./lock-service.js";
|
|
43
|
-
export * from "./freshness.js";
|
|
44
|
-
|
|
45
|
-
// Phase 3: the consumer surface. Where a locked recipe's files are, the
|
|
46
|
-
// namespace resolver and compile targets built from that, and the service the
|
|
47
|
-
// `repo`, `subscribe` and `unsubscribe` commands drive.
|
|
48
|
-
export * from "./locked-recipes.js";
|
|
49
|
-
export * from "./locked-namespace-resolver.js";
|
|
50
|
-
export * from "./subscription-service.js";
|
|
51
|
-
|
|
52
|
-
// Phase 7: the core namespace. The recipe that ships inside the package, the
|
|
53
|
-
// seed that puts it in the store on first run, and the built-in repository and
|
|
54
|
-
// subscription every project gets unless it opts out.
|
|
55
|
-
export * from "./core-recipe.js";
|
|
56
|
-
export * from "./seed.js";
|
|
57
|
-
export * from "./defaults.js";
|
package/src/lib/repos/links.ts
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading and writing the links map, and the ignore hygiene that goes with it.
|
|
3
|
-
*
|
|
4
|
-
* A link redirects one repository's resolution away from the store and at a
|
|
5
|
-
* real working copy on disk, which is how a maintainer edits recipes: edits
|
|
6
|
-
* happen in a checkout, never in the store. `sous repo link` writes an entry
|
|
7
|
-
* here; `sous repo unlink` removes it and leaves the checkout alone unless
|
|
8
|
-
* `--remove` asks it to delete one sous cloned.
|
|
9
|
-
*
|
|
10
|
-
* Two maps exist. The project's `.sous/sous.links.json` covers one project; the
|
|
11
|
-
* machine-wide `$SOUS_HOME/sous.links.json` covers every project on the machine,
|
|
12
|
-
* which is how two projects share one checkout. Both are read, and the project's
|
|
13
|
-
* entries win, because the narrower decision is the more deliberate one.
|
|
14
|
-
*
|
|
15
|
-
* Neither map is committed. A link bypasses versions, the lockfile and freshness
|
|
16
|
-
* checks, and those bypasses belong to one person's machine rather than to the
|
|
17
|
-
* team; the ignore hygiene in this module is what keeps them out of the
|
|
18
|
-
* repository, and `describeLinkedRepos` is what keeps them visible in build
|
|
19
|
-
* output instead of silently changing what a build produces.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import fs from "node:fs";
|
|
23
|
-
import path from "node:path";
|
|
24
|
-
import { ConfigError } from "../errors.js";
|
|
25
|
-
import { resolveSousHome, resolveStoreRoot } from "../sous-home.js";
|
|
26
|
-
import { LINKS_FILENAME } from "./formats/common.js";
|
|
27
|
-
import {
|
|
28
|
-
ensureProjectReposDirectory,
|
|
29
|
-
ensureSousHomeDirectory,
|
|
30
|
-
} from "../../utils/sous-directory.js";
|
|
31
|
-
import {
|
|
32
|
-
createEmptyLinksMap,
|
|
33
|
-
mergeLinksMaps,
|
|
34
|
-
parseLinksMap,
|
|
35
|
-
stringifyLinksMap,
|
|
36
|
-
type LinksMap,
|
|
37
|
-
type RepoLink,
|
|
38
|
-
} from "./formats/links-map.js";
|
|
39
|
-
import { loadJsonFile } from "./load-manifest.js";
|
|
40
|
-
|
|
41
|
-
/** Directory name, inside `.sous/` or `$SOUS_HOME`, holding linked checkouts. */
|
|
42
|
-
export const REPOS_DIRNAME = "repos";
|
|
43
|
-
|
|
44
|
-
/** Opening marker of the block sous maintains in `.sous/.gitignore`. */
|
|
45
|
-
export const IGNORE_BLOCK_START = "# >>> sous managed (do not edit between these markers)";
|
|
46
|
-
|
|
47
|
-
/** Closing marker of the block sous maintains in `.sous/.gitignore`. */
|
|
48
|
-
export const IGNORE_BLOCK_END = "# <<< sous managed";
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The entries sous keeps inside its managed block in `.sous/.gitignore`. All of
|
|
52
|
-
* them are machine-local: the links map, the build state file, the watcher's
|
|
53
|
-
* PID file, the local answers file, and the directory linked checkouts are
|
|
54
|
-
* cloned into.
|
|
55
|
-
*/
|
|
56
|
-
export const IGNORE_BLOCK_ENTRIES = [
|
|
57
|
-
LINKS_FILENAME,
|
|
58
|
-
"sous.state.json",
|
|
59
|
-
"sous.pid",
|
|
60
|
-
".env.local",
|
|
61
|
-
`${REPOS_DIRNAME}/`,
|
|
62
|
-
] as const;
|
|
63
|
-
|
|
64
|
-
// --- Locations ----------------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The user-level sous directory, re-exported under the name this module has
|
|
68
|
-
* always used. The one definition lives in `src/lib/sous-home.ts`, so the store,
|
|
69
|
-
* the links maps and the auto-injected `${sousHome}` variable can never disagree
|
|
70
|
-
* about where it is.
|
|
71
|
-
*
|
|
72
|
-
* @param env - The environment to read, so tests need not mutate the real one.
|
|
73
|
-
*/
|
|
74
|
-
export function resolveSousHomeDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
75
|
-
return resolveSousHome(env);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Path to the project's links map.
|
|
80
|
-
*
|
|
81
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
82
|
-
*/
|
|
83
|
-
export function projectLinksPath(sousDir: string): string {
|
|
84
|
-
return path.join(sousDir, LINKS_FILENAME);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Path to the machine-wide links map.
|
|
89
|
-
*
|
|
90
|
-
* @param env - The environment to read.
|
|
91
|
-
*/
|
|
92
|
-
export function globalLinksPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
93
|
-
return path.join(resolveSousHomeDir(env), LINKS_FILENAME);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* The directory a project's own linked checkouts are cloned into.
|
|
98
|
-
*
|
|
99
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
100
|
-
*/
|
|
101
|
-
export function projectReposDir(sousDir: string): string {
|
|
102
|
-
return path.join(sousDir, REPOS_DIRNAME);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The directory machine-wide linked checkouts are cloned into, shared by every
|
|
107
|
-
* project on the machine.
|
|
108
|
-
*
|
|
109
|
-
* @param env - The environment to read.
|
|
110
|
-
*/
|
|
111
|
-
export function globalReposDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
112
|
-
return path.join(resolveSousHomeDir(env), REPOS_DIRNAME);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* The directories nothing sous deletes may ever reach into: the project's linked
|
|
117
|
-
* checkouts, the machine-wide linked checkouts, and the machine-wide recipe
|
|
118
|
-
* store.
|
|
119
|
-
*
|
|
120
|
-
* A linked checkout is somebody's working copy with unpushed edits in it, and
|
|
121
|
-
* the store is shared by every project on the machine, so a stale state entry
|
|
122
|
-
* pointing into either one must never turn `sous prune` or `sous clear` into a
|
|
123
|
-
* data loss. Prune and clear only ever touch paths recorded in the state file,
|
|
124
|
-
* which nothing here writes; this is the belt that survives a future bug.
|
|
125
|
-
*
|
|
126
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
127
|
-
* @param env - The environment to read.
|
|
128
|
-
*/
|
|
129
|
-
export function protectedRepoPaths(
|
|
130
|
-
sousDir: string,
|
|
131
|
-
env: NodeJS.ProcessEnv = process.env
|
|
132
|
-
): string[] {
|
|
133
|
-
return [projectReposDir(sousDir), globalReposDir(env), resolveStoreRoot(env)];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// --- Reading ------------------------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Reads a links map from a path, returning an empty map when the file does not
|
|
140
|
-
* exist. A file that exists but cannot be read or does not validate raises a
|
|
141
|
-
* ConfigError naming it, rather than being quietly treated as empty: a link
|
|
142
|
-
* changes what a build produces, so losing one must never pass unnoticed.
|
|
143
|
-
*
|
|
144
|
-
* @param filePath - Absolute path to the links file.
|
|
145
|
-
*/
|
|
146
|
-
export function readLinksFile(filePath: string): LinksMap {
|
|
147
|
-
if (!fs.existsSync(filePath)) return createEmptyLinksMap();
|
|
148
|
-
return parseLinksMap(loadJsonFile(filePath, "links map"), filePath);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Reads the project's links map.
|
|
153
|
-
*
|
|
154
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
155
|
-
*/
|
|
156
|
-
export function readProjectLinks(sousDir: string): LinksMap {
|
|
157
|
-
return readLinksFile(projectLinksPath(sousDir));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Reads the machine-wide links map.
|
|
162
|
-
*
|
|
163
|
-
* @param env - The environment to read.
|
|
164
|
-
*/
|
|
165
|
-
export function readGlobalLinks(env: NodeJS.ProcessEnv = process.env): LinksMap {
|
|
166
|
-
return readLinksFile(globalLinksPath(env));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Reads both maps and merges them into the one map the rest of sous consults,
|
|
171
|
-
* with the project's entries winning over the machine-wide ones.
|
|
172
|
-
*
|
|
173
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
174
|
-
* @param env - The environment to read.
|
|
175
|
-
*/
|
|
176
|
-
export function readEffectiveLinks(
|
|
177
|
-
sousDir: string,
|
|
178
|
-
env: NodeJS.ProcessEnv = process.env
|
|
179
|
-
): Record<string, RepoLink> {
|
|
180
|
-
return mergeLinksMaps(readGlobalLinks(env), readProjectLinks(sousDir));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* The working copy sous should read for a repository, or undefined when the
|
|
185
|
-
* repository is not linked and resolution should fall through to the store.
|
|
186
|
-
*
|
|
187
|
-
* @param repoName - The repository's configured short name.
|
|
188
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
189
|
-
* @param env - The environment to read.
|
|
190
|
-
*/
|
|
191
|
-
export function linkedPathFor(
|
|
192
|
-
repoName: string,
|
|
193
|
-
sousDir: string,
|
|
194
|
-
env: NodeJS.ProcessEnv = process.env
|
|
195
|
-
): string | undefined {
|
|
196
|
-
return readEffectiveLinks(sousDir, env)[repoName]?.path;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// --- Writing ------------------------------------------------------------------------------------
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Writes a links map, creating its directory if needed. Returns the path it was
|
|
203
|
-
* written to.
|
|
204
|
-
*
|
|
205
|
-
* @param filePath - Absolute path to the links file.
|
|
206
|
-
* @param map - The map to write.
|
|
207
|
-
*/
|
|
208
|
-
export function writeLinksFile(filePath: string, map: LinksMap): string {
|
|
209
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
210
|
-
fs.writeFileSync(filePath, stringifyLinksMap(map), "utf8");
|
|
211
|
-
return filePath;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Writes the project's links map.
|
|
216
|
-
*
|
|
217
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
218
|
-
* @param map - The map to write.
|
|
219
|
-
*/
|
|
220
|
-
export function writeProjectLinks(sousDir: string, map: LinksMap): string {
|
|
221
|
-
return writeLinksFile(projectLinksPath(sousDir), map);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Writes the machine-wide links map.
|
|
226
|
-
*
|
|
227
|
-
* @param map - The map to write.
|
|
228
|
-
* @param env - The environment to read.
|
|
229
|
-
*/
|
|
230
|
-
export function writeGlobalLinks(
|
|
231
|
-
map: LinksMap,
|
|
232
|
-
env: NodeJS.ProcessEnv = process.env
|
|
233
|
-
): string {
|
|
234
|
-
// The machine-wide map is the first thing many users ever put in `$SOUS_HOME`,
|
|
235
|
-
// so this is where that directory usually gets its own explanation.
|
|
236
|
-
ensureSousHomeDirectory(resolveSousHomeDir(env));
|
|
237
|
-
return writeLinksFile(globalLinksPath(env), map);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// --- Build output notice ------------------------------------------------------------------------
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* The lines a build prints when anything is linked, so a checkout standing in
|
|
244
|
-
* for a published repository is never a silent change. Returns an empty array
|
|
245
|
-
* when nothing is linked, which is the signal to print nothing at all.
|
|
246
|
-
*
|
|
247
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
248
|
-
* @param env - The environment to read.
|
|
249
|
-
*/
|
|
250
|
-
export function describeLinkedRepos(
|
|
251
|
-
sousDir: string,
|
|
252
|
-
env: NodeJS.ProcessEnv = process.env
|
|
253
|
-
): string[] {
|
|
254
|
-
const links = readEffectiveLinks(sousDir, env);
|
|
255
|
-
const names = Object.keys(links).sort();
|
|
256
|
-
if (names.length === 0) return [];
|
|
257
|
-
|
|
258
|
-
const lines = [
|
|
259
|
-
names.length === 1
|
|
260
|
-
? "One repository is LINKED to a working copy on this machine."
|
|
261
|
-
: `${names.length} repositories are LINKED to working copies on this machine.`,
|
|
262
|
-
"Their recipes are read from those checkouts, so versions, the lockfile and",
|
|
263
|
-
"freshness checks do not apply to them.",
|
|
264
|
-
"",
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
for (const name of names) {
|
|
268
|
-
lines.push(`${name} -> ${links[name]!.path}`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
lines.push("");
|
|
272
|
-
lines.push("Run 'sous repo unlink <name>' to go back to the published versions.");
|
|
273
|
-
return lines;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// --- Ignore hygiene -----------------------------------------------------------------------------
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Makes sure git ignores everything sous keeps inside `.sous/` that belongs to
|
|
280
|
-
* one machine rather than to the team. Two files are maintained, and both are
|
|
281
|
-
* safe to write again on every link:
|
|
282
|
-
*
|
|
283
|
-
* - `.sous/repos/.gitignore`, holding a single `*`, so a linked checkout cloned
|
|
284
|
-
* underneath it is invisible to the project's own repository (the `*` covers
|
|
285
|
-
* the ignore file itself, so the directory contributes nothing at all).
|
|
286
|
-
* - a delimited managed block inside `.sous/.gitignore`. Only the lines between
|
|
287
|
-
* the markers are ever rewritten; anything the user put above or below them is
|
|
288
|
-
* left exactly as it was.
|
|
289
|
-
*
|
|
290
|
-
* @param sousDir - The project's discovered `.sous/` directory.
|
|
291
|
-
*/
|
|
292
|
-
export function ensureReposIgnoreFiles(sousDir: string): void {
|
|
293
|
-
const reposDir = ensureProjectReposDirectory(projectReposDir(sousDir));
|
|
294
|
-
writeIfChanged(path.join(reposDir, ".gitignore"), "*\n");
|
|
295
|
-
|
|
296
|
-
const gitignorePath = path.join(sousDir, ".gitignore");
|
|
297
|
-
const existing = fs.existsSync(gitignorePath)
|
|
298
|
-
? fs.readFileSync(gitignorePath, "utf8")
|
|
299
|
-
: undefined;
|
|
300
|
-
writeIfChanged(gitignorePath, applyManagedIgnoreBlock(existing, gitignorePath));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Returns the contents of `.sous/.gitignore` with sous's managed block present
|
|
305
|
-
* and up to date, leaving every line outside the markers untouched. Exported so
|
|
306
|
-
* the behavior can be tested without touching a filesystem.
|
|
307
|
-
*
|
|
308
|
-
* @param existing - The file's current contents, or undefined when there is no file.
|
|
309
|
-
* @param label - The file's path, named if the block turns out to be damaged.
|
|
310
|
-
*/
|
|
311
|
-
export function applyManagedIgnoreBlock(
|
|
312
|
-
existing: string | undefined,
|
|
313
|
-
label = ".sous/.gitignore"
|
|
314
|
-
): string {
|
|
315
|
-
const block = [IGNORE_BLOCK_START, ...IGNORE_BLOCK_ENTRIES, IGNORE_BLOCK_END];
|
|
316
|
-
|
|
317
|
-
if (existing === undefined || existing.trim() === "") {
|
|
318
|
-
return `${block.join("\n")}\n`;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const lines = existing.split("\n");
|
|
322
|
-
const start = lines.findIndex((line) => line.trim() === IGNORE_BLOCK_START);
|
|
323
|
-
|
|
324
|
-
if (start === -1) {
|
|
325
|
-
const prefix = existing.endsWith("\n") ? existing : `${existing}\n`;
|
|
326
|
-
const separator = prefix.endsWith("\n\n") ? "" : "\n";
|
|
327
|
-
return `${prefix}${separator}${block.join("\n")}\n`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const end = lines.findIndex(
|
|
331
|
-
(line, index) => index > start && line.trim() === IGNORE_BLOCK_END
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
if (end === -1) {
|
|
335
|
-
throw new ConfigError(
|
|
336
|
-
`The sous managed block in ${label} has no closing marker.\n` +
|
|
337
|
-
` It opens with '${IGNORE_BLOCK_START}' but the line '${IGNORE_BLOCK_END}' ` +
|
|
338
|
-
`is missing, so sous cannot tell where the block ends.\n` +
|
|
339
|
-
` Add the closing marker back, or delete the opening one, and run the ` +
|
|
340
|
-
`command again.`
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const rebuilt = [...lines.slice(0, start), ...block, ...lines.slice(end + 1)];
|
|
345
|
-
const joined = rebuilt.join("\n");
|
|
346
|
-
return joined.endsWith("\n") ? joined : `${joined}\n`;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// --- Helpers ------------------------------------------------------------------------------------
|
|
350
|
-
|
|
351
|
-
/** Writes a file only when its contents would change, so links stay idempotent. */
|
|
352
|
-
function writeIfChanged(filePath: string, contents: string): void {
|
|
353
|
-
if (fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === contents) return;
|
|
354
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
355
|
-
fs.writeFileSync(filePath, contents, "utf8");
|
|
356
|
-
}
|