@sous-io/sous 0.2.37 → 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 -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,422 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The managed config layers.
|
|
3
|
-
*
|
|
4
|
-
* Three files in a project's `conf.d/` directory are written by sous rather
|
|
5
|
-
* than by a person: `500-repos.jsonc`, which holds the repositories the project
|
|
6
|
-
* trusts, `510-subscriptions.jsonc`, which holds what it subscribes to, and
|
|
7
|
-
* `520-var-mappings.jsonc` (written from `vars/mappings.ts`), which holds
|
|
8
|
-
* variable mapping records. All three sit in the 5xx band reserved for
|
|
9
|
-
* machine-written layers, so a user's own primary config and non-5xx layers are
|
|
10
|
-
* never touched.
|
|
11
|
-
*
|
|
12
|
-
* They are `.jsonc`, not `.json`, so they can carry real comments: each one
|
|
13
|
-
* opens with a header saying what it holds and who writes it. Sous edits them
|
|
14
|
-
* BY KEY, through `jsonc-parser`, which rewrites only the bytes of the entry it
|
|
15
|
-
* is changing. A comment somebody adds beside an entry, the order they put the
|
|
16
|
-
* keys in, and the way they formatted the file all survive a sous edit.
|
|
17
|
-
*
|
|
18
|
-
* A layer that still exists under its old `.json` name is read as a fallback
|
|
19
|
-
* and migrates on the next write: the `.jsonc` file is written and the `.json`
|
|
20
|
-
* one is removed, so a project never ends up with both (two layers with the
|
|
21
|
-
* same baseName are a hard config error).
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import fs from "node:fs";
|
|
25
|
-
import path from "node:path";
|
|
26
|
-
import {
|
|
27
|
-
applyEdits,
|
|
28
|
-
findNodeAtLocation,
|
|
29
|
-
modify,
|
|
30
|
-
parse as parseJsonc,
|
|
31
|
-
parseTree,
|
|
32
|
-
type ParseError,
|
|
33
|
-
} from "jsonc-parser";
|
|
34
|
-
import { CONFD_DIR_NAME } from "../config-discovery.js";
|
|
35
|
-
import { ConfigError } from "../errors.js";
|
|
36
|
-
import { stableJsonStringify } from "./formats/common.js";
|
|
37
|
-
import { ensureConfdDirectory } from "../../utils/sous-directory.js";
|
|
38
|
-
|
|
39
|
-
/** The machine-written layer holding the repositories a project trusts. */
|
|
40
|
-
export const REPOS_LAYER_FILENAME = "500-repos.jsonc";
|
|
41
|
-
|
|
42
|
-
/** The machine-written layer holding a project's subscriptions. */
|
|
43
|
-
export const SUBSCRIPTIONS_LAYER_FILENAME = "510-subscriptions.jsonc";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The policy every managed layer states in its own header: sous edits it by
|
|
47
|
-
* key, and a person may edit it too.
|
|
48
|
-
*/
|
|
49
|
-
export const MANAGED_LAYER_COMMENT =
|
|
50
|
-
"This file is managed by sous. Sous edits these files by key; you may edit them " +
|
|
51
|
-
"too, and your comments, key order and formatting are kept.";
|
|
52
|
-
|
|
53
|
-
/** The closing lines of every managed layer header, describing the format. */
|
|
54
|
-
const MANAGED_LAYER_FORMAT_NOTE = [
|
|
55
|
-
"It is JSON with comments (.jsonc): line comments, block comments and trailing",
|
|
56
|
-
"commas are all allowed here.",
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
/** What each managed layer holds, and which commands write it. */
|
|
60
|
-
const MANAGED_LAYER_DESCRIPTIONS: Record<string, string[]> = {
|
|
61
|
-
[REPOS_LAYER_FILENAME]: [
|
|
62
|
-
"It records the repositories this project trusts. The 'sous repo add' and",
|
|
63
|
-
"'sous repo remove' commands write the entries under 'repos'.",
|
|
64
|
-
],
|
|
65
|
-
[SUBSCRIPTIONS_LAYER_FILENAME]: [
|
|
66
|
-
"It records what this project subscribes to. The 'sous subscription add' and",
|
|
67
|
-
"'sous subscription remove' commands write the entries under 'subscriptions'.",
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/** Wraps a sentence into `//` comment lines of at most `width` characters. */
|
|
72
|
-
function commentLines(text: string, width = 78): string[] {
|
|
73
|
-
const lines: string[] = [];
|
|
74
|
-
let current = "";
|
|
75
|
-
for (const word of text.split(/\s+/)) {
|
|
76
|
-
if (current.length === 0) {
|
|
77
|
-
current = word;
|
|
78
|
-
} else if (`${current} ${word}`.length + 3 <= width) {
|
|
79
|
-
current = `${current} ${word}`;
|
|
80
|
-
} else {
|
|
81
|
-
lines.push(current);
|
|
82
|
-
current = word;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (current.length > 0) lines.push(current);
|
|
86
|
-
return lines;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The header comment block a managed layer opens with. It states the policy,
|
|
91
|
-
* says what the file holds, and names the format.
|
|
92
|
-
*
|
|
93
|
-
* @param fileName - The layer's file name, which selects the description.
|
|
94
|
-
* @param description - Lines describing the file, for a layer this module does
|
|
95
|
-
* not know about (the variable mapping layer passes its own).
|
|
96
|
-
*/
|
|
97
|
-
export function managedLayerHeader(fileName: string, description?: string[]): string {
|
|
98
|
-
const what = description ?? MANAGED_LAYER_DESCRIPTIONS[fileName] ?? [];
|
|
99
|
-
const blocks = [commentLines(MANAGED_LAYER_COMMENT), what, MANAGED_LAYER_FORMAT_NOTE].filter(
|
|
100
|
-
(block) => block.length > 0
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
blocks
|
|
105
|
-
.map((block) => block.map((line) => `// ${line}`.trimEnd()).join("\n"))
|
|
106
|
-
.join("\n//\n") + "\n"
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Where a managed layer lives, and how it is written. */
|
|
111
|
-
export type ManagedLayerOptions = {
|
|
112
|
-
/**
|
|
113
|
-
* The `conf.d/` directory to use instead of `<sousDir>/conf.d`. Set it from
|
|
114
|
-
* the discovered config context, so `--sous-confd` and `SOUS_CONFD` are
|
|
115
|
-
* respected.
|
|
116
|
-
*/
|
|
117
|
-
confDir?: string;
|
|
118
|
-
/**
|
|
119
|
-
* The header comment block a newly created layer opens with, for a layer this
|
|
120
|
-
* module has no description for. Defaults to the header for `fileName`.
|
|
121
|
-
*/
|
|
122
|
-
header?: string;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The directory a managed layer is written into.
|
|
127
|
-
*
|
|
128
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
129
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
130
|
-
*/
|
|
131
|
-
export function managedLayerDir(sousDir: string, options: ManagedLayerOptions = {}): string {
|
|
132
|
-
return options.confDir ?? path.join(sousDir, CONFD_DIR_NAME);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* The full path of a managed layer.
|
|
137
|
-
*
|
|
138
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
139
|
-
* @param fileName - The layer's file name, such as `500-repos.jsonc`.
|
|
140
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
141
|
-
*/
|
|
142
|
-
export function managedLayerPath(
|
|
143
|
-
sousDir: string,
|
|
144
|
-
fileName: string,
|
|
145
|
-
options: ManagedLayerOptions = {}
|
|
146
|
-
): string {
|
|
147
|
-
return path.join(managedLayerDir(sousDir, options), fileName);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* The path the same layer had before managed layers became `.jsonc`, or
|
|
152
|
-
* undefined when the name is not a `.jsonc` one. Read as a fallback, and
|
|
153
|
-
* removed by the first write that migrates the layer.
|
|
154
|
-
*
|
|
155
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
156
|
-
* @param fileName - The layer's file name.
|
|
157
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
158
|
-
*/
|
|
159
|
-
export function legacyManagedLayerPath(
|
|
160
|
-
sousDir: string,
|
|
161
|
-
fileName: string,
|
|
162
|
-
options: ManagedLayerOptions = {}
|
|
163
|
-
): string | undefined {
|
|
164
|
-
if (!fileName.endsWith(".jsonc")) return undefined;
|
|
165
|
-
return path.join(managedLayerDir(sousDir, options), `${fileName.slice(0, -1)}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** True when the path exists and is a regular file. */
|
|
169
|
-
function isFile(candidate: string): boolean {
|
|
170
|
-
try {
|
|
171
|
-
return fs.statSync(candidate).isFile();
|
|
172
|
-
} catch {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* The managed layer file that actually exists: the `.jsonc` one, or the old
|
|
179
|
-
* `.json` one when only that is there. Undefined when the layer has never been
|
|
180
|
-
* written.
|
|
181
|
-
*/
|
|
182
|
-
function existingManagedLayerPath(
|
|
183
|
-
sousDir: string,
|
|
184
|
-
fileName: string,
|
|
185
|
-
options: ManagedLayerOptions
|
|
186
|
-
): string | undefined {
|
|
187
|
-
const current = managedLayerPath(sousDir, fileName, options);
|
|
188
|
-
if (isFile(current)) return current;
|
|
189
|
-
|
|
190
|
-
const legacy = legacyManagedLayerPath(sousDir, fileName, options);
|
|
191
|
-
if (legacy !== undefined && isFile(legacy)) return legacy;
|
|
192
|
-
|
|
193
|
-
return undefined;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Parses a managed layer's text, allowing comments and trailing commas. */
|
|
197
|
-
function parseManagedLayerText(text: string, filePath: string): unknown {
|
|
198
|
-
const errors: ParseError[] = [];
|
|
199
|
-
const value = parseJsonc(text, errors, {
|
|
200
|
-
allowTrailingComma: true,
|
|
201
|
-
disallowComments: false,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
if (errors.length > 0) {
|
|
205
|
-
throw new ConfigError(
|
|
206
|
-
`Sous could not read its own config layer at ${filePath}.\n` +
|
|
207
|
-
` The file is not valid JSON with comments.\n` +
|
|
208
|
-
` Sous writes this file itself. Restoring it from version control, or deleting ` +
|
|
209
|
-
`it and adding the entries again, both fix this.`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return value;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Reads a managed layer, returning an empty object when the file does not exist
|
|
218
|
-
* yet. A file that does not parse is an error naming it, because sous wrote it
|
|
219
|
-
* and is about to edit it: silently discarding somebody's edits would be worse
|
|
220
|
-
* than stopping.
|
|
221
|
-
*
|
|
222
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
223
|
-
* @param fileName - The layer's file name.
|
|
224
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
225
|
-
*/
|
|
226
|
-
export function readManagedLayer(
|
|
227
|
-
sousDir: string,
|
|
228
|
-
fileName: string,
|
|
229
|
-
options: ManagedLayerOptions = {}
|
|
230
|
-
): Record<string, unknown> {
|
|
231
|
-
const filePath = existingManagedLayerPath(sousDir, fileName, options);
|
|
232
|
-
if (filePath === undefined) return {};
|
|
233
|
-
|
|
234
|
-
let text: string;
|
|
235
|
-
try {
|
|
236
|
-
text = fs.readFileSync(filePath, "utf8");
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
|
|
239
|
-
throw new ConfigError(
|
|
240
|
-
`Sous could not read its own config layer at ${filePath}.\n` +
|
|
241
|
-
` ${(error as Error).message}`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const parsed = parseManagedLayerText(text, filePath);
|
|
246
|
-
|
|
247
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
248
|
-
throw new ConfigError(
|
|
249
|
-
`The config layer at ${filePath} is not a JSON object.\n` +
|
|
250
|
-
` Sous writes this file itself; it always holds one object.`
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return parsed as Record<string, unknown>;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Writes a layer's text to its `.jsonc` path and removes the old `.json` name
|
|
259
|
-
* when there was one, so a migrated layer never exists twice. The file is
|
|
260
|
-
* staged under a temporary name in the same directory and renamed into place,
|
|
261
|
-
* so a reader never sees a half-written layer.
|
|
262
|
-
*/
|
|
263
|
-
function writeLayerText(
|
|
264
|
-
sousDir: string,
|
|
265
|
-
fileName: string,
|
|
266
|
-
body: string,
|
|
267
|
-
options: ManagedLayerOptions
|
|
268
|
-
): string {
|
|
269
|
-
const directory = managedLayerDir(sousDir, options);
|
|
270
|
-
const filePath = path.join(directory, fileName);
|
|
271
|
-
|
|
272
|
-
// Created with its README, so somebody who finds a machine-written layer can
|
|
273
|
-
// read what the directory is for without leaving it.
|
|
274
|
-
ensureConfdDirectory(directory);
|
|
275
|
-
|
|
276
|
-
const temporary = path.join(directory, `.${fileName}.tmp-${process.pid}`);
|
|
277
|
-
try {
|
|
278
|
-
fs.writeFileSync(temporary, body, "utf8");
|
|
279
|
-
fs.renameSync(temporary, filePath);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
fs.rmSync(temporary, { force: true });
|
|
282
|
-
throw new ConfigError(
|
|
283
|
-
`Sous could not write its config layer at ${filePath}.\n ${(error as Error).message}`
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const legacy = legacyManagedLayerPath(sousDir, fileName, options);
|
|
288
|
-
if (legacy !== undefined && legacy !== filePath) fs.rmSync(legacy, { force: true });
|
|
289
|
-
|
|
290
|
-
return filePath;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Writes a managed layer, replacing whatever was there: the header comment,
|
|
295
|
-
* then the content as sorted JSON. Use `updateManagedLayer` for an ordinary
|
|
296
|
-
* change; this is for creating a layer from nothing, or for replacing one whose
|
|
297
|
-
* whole content sous is generating.
|
|
298
|
-
*
|
|
299
|
-
* The header comment is added for you; there is no need to pass one.
|
|
300
|
-
*
|
|
301
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
302
|
-
* @param fileName - The layer's file name.
|
|
303
|
-
* @param content - The layer's whole content.
|
|
304
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
305
|
-
*/
|
|
306
|
-
export function writeManagedLayer(
|
|
307
|
-
sousDir: string,
|
|
308
|
-
fileName: string,
|
|
309
|
-
content: Record<string, unknown>,
|
|
310
|
-
options: ManagedLayerOptions = {}
|
|
311
|
-
): string {
|
|
312
|
-
const header = options.header ?? managedLayerHeader(fileName);
|
|
313
|
-
return writeLayerText(sousDir, fileName, header + stableJsonStringify(content), options);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* True when the layer's text actually holds something at a key path. Used to
|
|
318
|
-
* tell a removal that has work to do from one that does not.
|
|
319
|
-
*
|
|
320
|
-
* @param text - The layer's text.
|
|
321
|
-
* @param keyPath - The key path to look for.
|
|
322
|
-
*/
|
|
323
|
-
function hasNodeAt(text: string, keyPath: (string | number)[]): boolean {
|
|
324
|
-
const root = parseTree(text);
|
|
325
|
-
if (root === undefined) return false;
|
|
326
|
-
return findNodeAtLocation(root, keyPath) !== undefined;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/** One key-path edit to a managed layer. `undefined` removes the key. */
|
|
330
|
-
export type ManagedLayerEdit = {
|
|
331
|
-
/** The key path to change, such as `["repos", "team-recipes"]`. */
|
|
332
|
-
path: (string | number)[];
|
|
333
|
-
/** The value to write there, or undefined to remove the key. */
|
|
334
|
-
value: unknown | undefined;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Applies key-path edits to a managed layer, rewriting only the bytes of the
|
|
339
|
-
* entries that change. Comments, key order and formatting elsewhere in the file
|
|
340
|
-
* are left exactly as they were, which is what lets a person keep notes beside
|
|
341
|
-
* the entries sous manages.
|
|
342
|
-
*
|
|
343
|
-
* A layer that does not exist yet is created with its header comment and an
|
|
344
|
-
* empty object, and the edits are applied to that. A layer still under its old
|
|
345
|
-
* `.json` name is edited and written back as `.jsonc`, and the `.json` file is
|
|
346
|
-
* removed.
|
|
347
|
-
*
|
|
348
|
-
* New keys are inserted in sorted position, so a layer sous has written from
|
|
349
|
-
* the start stays in a stable order and its diffs stay small.
|
|
350
|
-
*
|
|
351
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
352
|
-
* @param fileName - The layer's file name.
|
|
353
|
-
* @param edits - The key paths to set, or to remove by passing undefined.
|
|
354
|
-
* @param options - An explicit `conf.d/` directory, and a header for a layer
|
|
355
|
-
* this module has no description for.
|
|
356
|
-
* @returns The path of the layer file that was written.
|
|
357
|
-
*/
|
|
358
|
-
export function updateManagedLayer(
|
|
359
|
-
sousDir: string,
|
|
360
|
-
fileName: string,
|
|
361
|
-
edits: ManagedLayerEdit[],
|
|
362
|
-
options: ManagedLayerOptions = {}
|
|
363
|
-
): string {
|
|
364
|
-
const existing = existingManagedLayerPath(sousDir, fileName, options);
|
|
365
|
-
|
|
366
|
-
let text: string;
|
|
367
|
-
if (existing === undefined) {
|
|
368
|
-
text = (options.header ?? managedLayerHeader(fileName)) + "{}\n";
|
|
369
|
-
} else {
|
|
370
|
-
try {
|
|
371
|
-
text = fs.readFileSync(existing, "utf8");
|
|
372
|
-
} catch (error) {
|
|
373
|
-
throw new ConfigError(
|
|
374
|
-
`Sous could not read its own config layer at ${existing}.\n` +
|
|
375
|
-
` ${(error as Error).message}`
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
// Refuse to edit a file that does not parse, rather than writing over it.
|
|
379
|
-
parseManagedLayerText(text, existing);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
for (const edit of edits) {
|
|
383
|
-
// Removing a key that is not there is already done. Asking `modify` to do it
|
|
384
|
-
// anyway throws, because there is no parent object to remove it from, and a
|
|
385
|
-
// layer somebody has hand-edited is exactly where that happens.
|
|
386
|
-
if (edit.value === undefined && !hasNodeAt(text, edit.path)) continue;
|
|
387
|
-
|
|
388
|
-
const last = edit.path[edit.path.length - 1];
|
|
389
|
-
const changes = modify(text, edit.path, edit.value, {
|
|
390
|
-
formattingOptions: { tabSize: 2, insertSpaces: true, eol: "\n" },
|
|
391
|
-
getInsertionIndex:
|
|
392
|
-
typeof last === "string"
|
|
393
|
-
? (properties) => properties.filter((name) => name < last).length
|
|
394
|
-
: undefined,
|
|
395
|
-
});
|
|
396
|
-
text = applyEdits(text, changes);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (!text.endsWith("\n")) text += "\n";
|
|
400
|
-
|
|
401
|
-
return writeLayerText(sousDir, fileName, text, options);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Removes a managed layer entirely, which is what emptying one comes down to: a
|
|
406
|
-
* layer holding nothing but its own header comment is noise in a project. The
|
|
407
|
-
* old `.json` name is removed too, so a migration in progress leaves nothing
|
|
408
|
-
* behind.
|
|
409
|
-
*
|
|
410
|
-
* @param sousDir - The project's `.sous/` directory.
|
|
411
|
-
* @param fileName - The layer's file name.
|
|
412
|
-
* @param options - An explicit `conf.d/` directory, when there is one.
|
|
413
|
-
*/
|
|
414
|
-
export function removeManagedLayer(
|
|
415
|
-
sousDir: string,
|
|
416
|
-
fileName: string,
|
|
417
|
-
options: ManagedLayerOptions = {}
|
|
418
|
-
): void {
|
|
419
|
-
fs.rmSync(managedLayerPath(sousDir, fileName, options), { force: true });
|
|
420
|
-
const legacy = legacyManagedLayerPath(sousDir, fileName, options);
|
|
421
|
-
if (legacy !== undefined) fs.rmSync(legacy, { force: true });
|
|
422
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The built-in `#memories` view: every memory an active recipe publishes, at
|
|
3
|
-
* `#memories/<namespace>/<recipe>/<path>`. See `recipe-memories.ts` for what is
|
|
4
|
-
* listed and in which order.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { makeInjectable } from "../../services/ref-resolver/injectable.js";
|
|
8
|
-
import type { HashName, HashViewContext, ViewFile } from "../../services/ref-resolver/hash-names.js";
|
|
9
|
-
import { listMemories } from "./recipe-memories.js";
|
|
10
|
-
|
|
11
|
-
/** The `#memories` view. */
|
|
12
|
-
export class MemoriesHashName implements HashName {
|
|
13
|
-
readonly name = "memories";
|
|
14
|
-
readonly registeredBy = "sous";
|
|
15
|
-
readonly description =
|
|
16
|
-
"the memories of every active recipe, listed in dependency order as #memories/<namespace>/<recipe>/<path>.";
|
|
17
|
-
|
|
18
|
-
bases(): string[] {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
view(context: HashViewContext): ViewFile[] {
|
|
23
|
-
const memories = context.settings.recipes?.memories;
|
|
24
|
-
return listMemories({
|
|
25
|
-
sousDir: context.sousDir,
|
|
26
|
-
...(context.env === undefined ? {} : { env: context.env }),
|
|
27
|
-
first: memories?.first,
|
|
28
|
-
exclude: memories?.exclude,
|
|
29
|
-
}).map((memory) => ({ path: memory.path, file: memory.file }));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
makeInjectable(MemoriesHashName);
|