@sous-io/sous 0.1.1 → 0.2.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 +115 -35
- package/bin/run.js +10 -1
- package/docs/markdown/README.md +27 -0
- package/docs/markdown/_sidebar.md +18 -0
- package/docs/markdown/commands.md +308 -0
- package/docs/markdown/config-discovery.md +74 -0
- package/docs/markdown/config-inspection.md +69 -0
- package/docs/markdown/config-layers.md +92 -0
- package/docs/markdown/config-variables.md +79 -0
- package/docs/markdown/configuration.md +71 -0
- package/docs/markdown/design-principles.md +59 -0
- package/docs/markdown/repositories-authoring.md +409 -0
- package/docs/markdown/repositories-consuming.md +580 -0
- package/docs/markdown/repositories-file-formats.md +1084 -0
- package/docs/markdown/repositories-variables.md +387 -0
- package/docs/markdown/repositories.md +303 -0
- package/docs/markdown/skill-categories.md +58 -0
- package/package.json +72 -8
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
- package/sous.config.schema.json +337 -0
- package/src/base-command.ts +220 -67
- package/src/commands/build.ts +150 -73
- package/src/commands/clear.ts +23 -15
- package/src/commands/compile.ts +74 -16
- package/src/commands/config/get.ts +110 -0
- package/src/commands/config/show.ts +32 -0
- package/src/commands/config/validate.ts +53 -0
- package/src/commands/help.ts +46 -0
- package/src/commands/launch.ts +36 -14
- package/src/commands/lock/rebuild.ts +241 -0
- package/src/commands/lock/show.ts +115 -0
- package/src/commands/namespace/list.ts +117 -0
- package/src/commands/namespace/show.ts +110 -0
- package/src/commands/prune.ts +3 -11
- package/src/commands/recipe/list.ts +95 -0
- package/src/commands/recipe/show.ts +301 -0
- package/src/commands/repo/add.ts +145 -0
- package/src/commands/repo/gc.ts +172 -0
- package/src/commands/repo/init.ts +136 -0
- package/src/commands/repo/link.ts +500 -0
- package/src/commands/repo/list.ts +179 -0
- package/src/commands/repo/release.ts +625 -0
- package/src/commands/repo/remove.ts +193 -0
- package/src/commands/repo/search.ts +189 -0
- package/src/commands/repo/submit.ts +133 -0
- package/src/commands/repo/unlink.ts +147 -0
- package/src/commands/subscription/add.ts +285 -0
- package/src/commands/subscription/list.ts +129 -0
- package/src/commands/subscription/remove.ts +181 -0
- package/src/commands/vars/ask.ts +374 -0
- package/src/commands/vars/index.ts +79 -0
- package/src/commands/vars/list.ts +67 -0
- package/src/commands/vars/show.ts +77 -0
- package/src/config-command.ts +30 -0
- package/src/lib/build-service.ts +206 -54
- package/src/lib/config-discovery.ts +220 -27
- package/src/lib/config-inspect.ts +145 -0
- package/src/lib/config-kernel.mjs +377 -0
- package/src/lib/config-schema.ts +361 -0
- package/src/lib/env-file.ts +328 -0
- package/src/lib/env-local.ts +18 -1
- package/src/lib/errors.ts +32 -0
- package/src/lib/include-resolver.ts +108 -15
- package/src/lib/interactive.ts +165 -0
- package/src/lib/markdown-compiler.ts +118 -37
- package/src/lib/package-info.ts +25 -0
- package/src/lib/pid-service.ts +32 -21
- package/src/lib/refs/find.ts +589 -0
- package/src/lib/refs/index.ts +12 -0
- package/src/lib/refs/pick.ts +147 -0
- package/src/lib/refs/scopes.ts +61 -0
- package/src/lib/repos/catalog-display.ts +116 -0
- package/src/lib/repos/catalog-inputs.ts +160 -0
- package/src/lib/repos/catalog.ts +722 -0
- package/src/lib/repos/core-recipe.ts +105 -0
- package/src/lib/repos/defaults.ts +175 -0
- package/src/lib/repos/formats/common.ts +389 -0
- package/src/lib/repos/formats/index-file.ts +215 -0
- package/src/lib/repos/formats/links-map.ts +96 -0
- package/src/lib/repos/formats/lockfile.ts +167 -0
- package/src/lib/repos/formats/patterns.ts +57 -0
- package/src/lib/repos/formats/recipe-manifest.ts +395 -0
- package/src/lib/repos/formats/repo-manifest.ts +88 -0
- package/src/lib/repos/formats/store-entry.ts +84 -0
- package/src/lib/repos/freshness.ts +208 -0
- package/src/lib/repos/git-clone.ts +312 -0
- package/src/lib/repos/identity.ts +89 -0
- package/src/lib/repos/index.ts +58 -0
- package/src/lib/repos/links.ts +353 -0
- package/src/lib/repos/load-manifest.ts +236 -0
- package/src/lib/repos/lock-service.ts +453 -0
- package/src/lib/repos/locked-namespace-resolver.ts +90 -0
- package/src/lib/repos/locked-recipes.ts +254 -0
- package/src/lib/repos/managed-layer.ts +422 -0
- package/src/lib/repos/namespace-resolver.ts +370 -0
- package/src/lib/repos/providers/base.ts +206 -0
- package/src/lib/repos/providers/git.ts +233 -0
- package/src/lib/repos/providers/github.ts +294 -0
- package/src/lib/repos/providers/gitlab.ts +263 -0
- package/src/lib/repos/providers/http.ts +102 -0
- package/src/lib/repos/providers/index-cache.ts +382 -0
- package/src/lib/repos/providers/index.ts +106 -0
- package/src/lib/repos/providers/local.ts +391 -0
- package/src/lib/repos/providers/provider.ts +401 -0
- package/src/lib/repos/recipe-config-layers.ts +287 -0
- package/src/lib/repos/recipe-targets.ts +223 -0
- package/src/lib/repos/ref-search.ts +46 -0
- package/src/lib/repos/ref.ts +513 -0
- package/src/lib/repos/reference-report.ts +122 -0
- package/src/lib/repos/release/bump.ts +161 -0
- package/src/lib/repos/release/git-state.ts +305 -0
- package/src/lib/repos/release/index-builder.ts +635 -0
- package/src/lib/repos/release/index.ts +16 -0
- package/src/lib/repos/release/plan.ts +512 -0
- package/src/lib/repos/release/submit-service.ts +496 -0
- package/src/lib/repos/release/tags.ts +243 -0
- package/src/lib/repos/release/validate.ts +463 -0
- package/src/lib/repos/resolver.ts +789 -0
- package/src/lib/repos/scaffold/index.ts +238 -0
- package/src/lib/repos/scaffold/templates.ts +415 -0
- package/src/lib/repos/seed.ts +414 -0
- package/src/lib/repos/store/contract.ts +64 -0
- package/src/lib/repos/store/hash.ts +114 -0
- package/src/lib/repos/store/recipe-store.ts +599 -0
- package/src/lib/repos/store/settings.ts +58 -0
- package/src/lib/repos/subscription-service.ts +2678 -0
- package/src/lib/repos/trust.ts +447 -0
- package/src/lib/settings.ts +546 -189
- package/src/lib/sous-home.ts +104 -0
- package/src/lib/state.ts +52 -20
- package/src/lib/vars/ask.ts +1152 -0
- package/src/lib/vars/definition-source.ts +252 -0
- package/src/lib/vars/display.ts +233 -0
- package/src/lib/vars/index.ts +18 -0
- package/src/lib/vars/ladder.ts +282 -0
- package/src/lib/vars/mappings.ts +265 -0
- package/src/lib/vars/names.ts +94 -0
- package/src/lib/vars/preanswers.ts +395 -0
- package/src/lib/vars/question-plan.ts +218 -0
- package/src/lib/vars/report.ts +228 -0
- package/src/lib/vars/safe-regex.ts +235 -0
- package/src/lib/vars/validate.ts +312 -0
- package/src/lib/watch-loop.ts +148 -0
- package/src/templating/init-liquid-engine.ts +58 -16
- package/src/utils/choice-prompt.ts +143 -0
- package/src/utils/command-errors.ts +186 -0
- package/src/utils/command-help.ts +45 -0
- package/src/utils/confirm-prompt.ts +110 -0
- package/src/utils/flags.ts +153 -0
- package/src/utils/formatting.ts +540 -55
- package/src/utils/prompts.ts +35 -1
- package/src/utils/sous-directory.ts +245 -0
- package/src/utils/table.ts +603 -0
- package/src/utils/value-prompt.ts +119 -0
- package/shared-prompts/_partials/resume-task.md +0 -51
- package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
- package/shared-prompts/_partials/update-task-file.md +0 -52
- package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
- package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
- package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
- package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
- package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
- package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
- package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
- package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
- package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
- package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
- package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
- package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
- package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
package/src/lib/build-service.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import type { ConfigContext, Settings } from "./settings.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveAliases, resolveCompilation, resolveRootScope } from "./settings.js";
|
|
5
5
|
import { resolveIncludeCandidates } from "./include-resolver.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { NamespaceResolver } from "./repos/namespace-resolver.js";
|
|
7
|
+
import { createProjectNamespaceResolver } from "./repos/locked-namespace-resolver.js";
|
|
8
|
+
import { buildRecipeTargets, type RecipeTargets } from "./repos/recipe-targets.js";
|
|
9
|
+
import { CompilationService, resolveOutputPath } from "./markdown-compiler.js";
|
|
7
10
|
import type { CompilationConfig, CompilationTarget } from "./markdown-compiler.js";
|
|
8
11
|
import { StateService } from "./state.js";
|
|
9
|
-
import {
|
|
12
|
+
import { isProtectedPath } from "./state.js";
|
|
13
|
+
import { protectedRepoPaths } from "./repos/links.js";
|
|
14
|
+
import { log, warning } from "../utils/formatting.js";
|
|
10
15
|
|
|
11
16
|
export type BuildOptions = {
|
|
12
17
|
strict?: boolean;
|
|
@@ -25,8 +30,108 @@ export type BuildOptions = {
|
|
|
25
30
|
* `${sousDir}` resolves and so state/PID paths default into `.sous/`.
|
|
26
31
|
*/
|
|
27
32
|
configContext?: ConfigContext;
|
|
33
|
+
/**
|
|
34
|
+
* Resolves `~namespace` include and render paths against recipe namespaces.
|
|
35
|
+
* Passed straight through to the compiler and to the include-graph walk, so a
|
|
36
|
+
* partial rebuild follows namespace includes too.
|
|
37
|
+
*
|
|
38
|
+
* When it is omitted and `configContext` is given, the build builds the
|
|
39
|
+
* project's own resolver from its lockfile, links map and store. Pass one
|
|
40
|
+
* explicitly to override that, which is what tests do.
|
|
41
|
+
*/
|
|
42
|
+
namespaceResolver?: NamespaceResolver;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** An empty recipe-target result, for a build with no config context. */
|
|
46
|
+
const NO_RECIPE_TARGETS: RecipeTargets = {
|
|
47
|
+
targets: [],
|
|
48
|
+
destinations: [],
|
|
49
|
+
watchDirs: [],
|
|
50
|
+
warnings: [],
|
|
28
51
|
};
|
|
29
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The compile targets a project's subscribed recipes contribute, for the project
|
|
55
|
+
* the options describe. Empty when the caller gave no config context, which is
|
|
56
|
+
* the case only in tests that build a settings object by hand.
|
|
57
|
+
*
|
|
58
|
+
* @param settings - The merged project config.
|
|
59
|
+
* @param rootScope - The resolved settings scope, for `${var}` in destinations.
|
|
60
|
+
* @param configContext - Where the active config was discovered.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveRecipeTargets(
|
|
63
|
+
settings: Settings,
|
|
64
|
+
rootScope: Record<string, string>,
|
|
65
|
+
configContext?: ConfigContext
|
|
66
|
+
): RecipeTargets {
|
|
67
|
+
if (configContext === undefined) return NO_RECIPE_TARGETS;
|
|
68
|
+
return buildRecipeTargets({
|
|
69
|
+
sousDir: configContext.sousDir,
|
|
70
|
+
settings,
|
|
71
|
+
scope: rootScope,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Adds the recipe targets to a project's own compilation config. A project with
|
|
77
|
+
* no compilation block of its own still compiles its recipes, so the config is
|
|
78
|
+
* created when there is none and there is something to compile.
|
|
79
|
+
*
|
|
80
|
+
* @param config - The project's own compilation config, or null when it has none.
|
|
81
|
+
* @param recipes - The targets the subscribed recipes contribute.
|
|
82
|
+
* @param settings - The merged project config, for its aliases.
|
|
83
|
+
* @param rootScope - The resolved settings scope.
|
|
84
|
+
*/
|
|
85
|
+
export function withRecipeTargets(
|
|
86
|
+
config: CompilationConfig | null,
|
|
87
|
+
recipes: RecipeTargets,
|
|
88
|
+
settings: Settings,
|
|
89
|
+
rootScope: Record<string, string>
|
|
90
|
+
): CompilationConfig | null {
|
|
91
|
+
if (recipes.targets.length === 0) return config;
|
|
92
|
+
if (config === null) {
|
|
93
|
+
return {
|
|
94
|
+
targets: recipes.targets,
|
|
95
|
+
aliases: resolveAliases(settings, rootScope),
|
|
96
|
+
includeScope: rootScope,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { ...config, targets: [...config.targets, ...recipes.targets] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The directories a build's deletions must never reach into, for the project the
|
|
104
|
+
* options describe. Empty when the caller gave no config context, which is the
|
|
105
|
+
* case only in tests that build a settings object by hand.
|
|
106
|
+
*
|
|
107
|
+
* @param options - The build options, for the config context.
|
|
108
|
+
*/
|
|
109
|
+
function protectedPathsFor(options: BuildOptions): string[] {
|
|
110
|
+
if (options.configContext === undefined) return [];
|
|
111
|
+
return protectedRepoPaths(options.configContext.sousDir);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The namespace resolver a build should use: the one the caller supplied, or the
|
|
116
|
+
* project's own, built from its lockfile. A project that locks no recipes gets
|
|
117
|
+
* undefined, which leaves `~` in an include line meaning an alias and nothing
|
|
118
|
+
* else.
|
|
119
|
+
*
|
|
120
|
+
* @param settings - The merged project config.
|
|
121
|
+
* @param options - The build options, for the config context and any override.
|
|
122
|
+
*/
|
|
123
|
+
function resolveNamespaceResolver(
|
|
124
|
+
settings: Settings,
|
|
125
|
+
options: BuildOptions
|
|
126
|
+
): NamespaceResolver | undefined {
|
|
127
|
+
if (options.namespaceResolver !== undefined) return options.namespaceResolver;
|
|
128
|
+
if (options.configContext === undefined) return undefined;
|
|
129
|
+
return createProjectNamespaceResolver({
|
|
130
|
+
sousDir: options.configContext.sousDir,
|
|
131
|
+
settings,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
30
135
|
/**
|
|
31
136
|
* Recursively collects all file paths reachable from `filePath` via @include chains.
|
|
32
137
|
* Returns a Set of absolute paths. The `visited` set prevents infinite loops.
|
|
@@ -37,7 +142,11 @@ export type BuildOptions = {
|
|
|
37
142
|
*/
|
|
38
143
|
function collectIncludeGraph(
|
|
39
144
|
filePath: string,
|
|
40
|
-
resolveOpts: {
|
|
145
|
+
resolveOpts: {
|
|
146
|
+
aliases?: Record<string, string[]>;
|
|
147
|
+
scope?: Record<string, string>;
|
|
148
|
+
namespaceResolver?: NamespaceResolver;
|
|
149
|
+
} = {},
|
|
41
150
|
visited: Set<string> = new Set()
|
|
42
151
|
): Set<string> {
|
|
43
152
|
if (visited.has(filePath)) return visited;
|
|
@@ -62,6 +171,8 @@ function collectIncludeGraph(
|
|
|
62
171
|
aliases: resolveOpts.aliases,
|
|
63
172
|
scope: resolveOpts.scope,
|
|
64
173
|
baseDir,
|
|
174
|
+
namespaceResolver: resolveOpts.namespaceResolver,
|
|
175
|
+
fromFile: filePath,
|
|
65
176
|
});
|
|
66
177
|
const fullPath = candidates.find((c) => fs.existsSync(c)) ?? candidates[0];
|
|
67
178
|
collectIncludeGraph(fullPath, resolveOpts, visited);
|
|
@@ -77,85 +188,97 @@ function collectIncludeGraph(
|
|
|
77
188
|
*
|
|
78
189
|
* Uses a simple recursive file scan — reads each .md file and checks for
|
|
79
190
|
* @<path> include lines. Does not compile; just walks the include graph.
|
|
191
|
+
*
|
|
192
|
+
* @param filePath - The changed file.
|
|
193
|
+
* @param config - The resolved compilation config.
|
|
194
|
+
* @param namespaceResolver - Optional resolver so `~namespace` includes are followed too.
|
|
80
195
|
*/
|
|
81
196
|
export function findAffectedTargets(
|
|
82
197
|
filePath: string,
|
|
83
|
-
config: CompilationConfig
|
|
198
|
+
config: CompilationConfig,
|
|
199
|
+
namespaceResolver?: NamespaceResolver
|
|
84
200
|
): CompilationTarget[] {
|
|
85
201
|
return config.targets.filter(target => {
|
|
86
202
|
const graph = collectIncludeGraph(target.rootInputPath, {
|
|
87
203
|
aliases: config.aliases,
|
|
88
204
|
scope: config.includeScope,
|
|
205
|
+
namespaceResolver,
|
|
89
206
|
});
|
|
90
207
|
return graph.has(filePath);
|
|
91
208
|
});
|
|
92
209
|
}
|
|
93
210
|
|
|
94
211
|
/**
|
|
95
|
-
* Resolves the state file path for a
|
|
212
|
+
* Resolves the state file path for a config.
|
|
96
213
|
*
|
|
97
|
-
* The path is derived from the
|
|
98
|
-
*
|
|
99
|
-
* from the root scope alone was a bug: project-level values were ignored and
|
|
100
|
-
* state silently landed in cwd.
|
|
214
|
+
* The path is derived from the resolved settings scope, so a `stateFilePath`
|
|
215
|
+
* or `sousDir` defined in `_vars` (or injected by discovery) is honoured.
|
|
101
216
|
*
|
|
102
|
-
* @param
|
|
103
|
-
* @param settings - The loaded root settings.
|
|
217
|
+
* @param settings - The loaded settings.
|
|
104
218
|
* @param configContext - Where the config was discovered (supplies `sousDir`).
|
|
105
|
-
* @returns Absolute path to the
|
|
219
|
+
* @returns Absolute path to the state file.
|
|
106
220
|
*/
|
|
107
221
|
export function resolveStateFilePath(
|
|
108
|
-
projectKey: string,
|
|
109
222
|
settings: Settings,
|
|
110
223
|
configContext?: ConfigContext
|
|
111
224
|
): string {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const projectScope = resolveScope(project?._vars ?? {}, rootScope);
|
|
115
|
-
const projectCount = Object.keys(settings.projects ?? {}).length;
|
|
116
|
-
return new StateService().getFilePath(projectKey, projectScope, projectCount);
|
|
225
|
+
const scope = resolveRootScope(settings, configContext);
|
|
226
|
+
return new StateService().getFilePath(scope);
|
|
117
227
|
}
|
|
118
228
|
|
|
119
229
|
export class BuildService {
|
|
120
230
|
/**
|
|
121
|
-
* Runs compile + prune for
|
|
231
|
+
* Runs compile + prune for the configured project.
|
|
122
232
|
* Returns true if all steps succeeded.
|
|
123
233
|
*/
|
|
124
|
-
async build(
|
|
125
|
-
projectKey: string,
|
|
126
|
-
settings: Settings,
|
|
127
|
-
options: BuildOptions = {}
|
|
128
|
-
): Promise<boolean> {
|
|
234
|
+
async build(settings: Settings, options: BuildOptions = {}): Promise<boolean> {
|
|
129
235
|
const rootScope = resolveRootScope(settings, options.configContext);
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
if (!project) {
|
|
133
|
-
throw new Error(`Project '${projectKey}' not found in settings`);
|
|
134
|
-
}
|
|
236
|
+
const namespaceResolver = resolveNamespaceResolver(settings, options);
|
|
237
|
+
const protectedPaths = protectedPathsFor(options);
|
|
135
238
|
|
|
136
239
|
const stateService = new StateService();
|
|
137
|
-
const stateFilePath = resolveStateFilePath(
|
|
240
|
+
const stateFilePath = resolveStateFilePath(settings, options.configContext);
|
|
138
241
|
|
|
139
242
|
let success = true;
|
|
140
243
|
|
|
141
|
-
// When --rebuild, clear all previously written files before compiling so
|
|
142
|
-
//
|
|
143
|
-
//
|
|
244
|
+
// When --rebuild, clear all previously written files before compiling so the
|
|
245
|
+
// rebuild starts from a physically clean slate. Orphaned outputs are also
|
|
246
|
+
// caught by prune (compile carries prior state entries forward), so this is
|
|
247
|
+
// the aggressive path: delete everything up front rather than prune after.
|
|
144
248
|
if (options.rebuild && !options.dryRun && !options.noCompile) {
|
|
145
249
|
const existingState = await stateService.load(stateFilePath);
|
|
146
250
|
if (existingState?.files.length) {
|
|
147
|
-
stateService.deleteTrackedFiles(
|
|
251
|
+
stateService.deleteTrackedFiles(
|
|
252
|
+
existingState.files,
|
|
253
|
+
existingState.dirs,
|
|
254
|
+
protectedPaths
|
|
255
|
+
);
|
|
148
256
|
}
|
|
149
257
|
}
|
|
150
258
|
|
|
151
|
-
// Compile step
|
|
259
|
+
// Compile step. The recipes this project subscribes to contribute compile
|
|
260
|
+
// targets alongside its own, so a recipe's files are compiled by exactly the
|
|
261
|
+
// same machinery as everything else, and are pruned and cleared by it too.
|
|
152
262
|
if (!options.noCompile) {
|
|
153
|
-
const
|
|
263
|
+
const recipes = resolveRecipeTargets(settings, rootScope, options.configContext);
|
|
264
|
+
for (const notice of recipes.warnings) warning(notice);
|
|
265
|
+
|
|
266
|
+
const config = withRecipeTargets(
|
|
267
|
+
resolveCompilation(settings, rootScope),
|
|
268
|
+
recipes,
|
|
269
|
+
settings,
|
|
270
|
+
rootScope
|
|
271
|
+
);
|
|
272
|
+
|
|
154
273
|
if (config) {
|
|
155
274
|
let effectiveConfig: CompilationConfig = config;
|
|
156
275
|
|
|
157
276
|
if (options.changedFile) {
|
|
158
|
-
const affectedTargets = findAffectedTargets(
|
|
277
|
+
const affectedTargets = findAffectedTargets(
|
|
278
|
+
options.changedFile,
|
|
279
|
+
config,
|
|
280
|
+
namespaceResolver
|
|
281
|
+
);
|
|
159
282
|
if (affectedTargets.length === 0) {
|
|
160
283
|
log(` ⊘ No targets affected by change to ${options.changedFile} — skipping compilation`);
|
|
161
284
|
} else {
|
|
@@ -164,6 +287,7 @@ export class BuildService {
|
|
|
164
287
|
strict: options.strict,
|
|
165
288
|
rebuild: options.rebuild,
|
|
166
289
|
dryRun: options.dryRun,
|
|
290
|
+
namespaceResolver,
|
|
167
291
|
});
|
|
168
292
|
const compileOk = await compiler.compile(effectiveConfig, stateFilePath);
|
|
169
293
|
if (!compileOk) success = false;
|
|
@@ -173,6 +297,7 @@ export class BuildService {
|
|
|
173
297
|
strict: options.strict,
|
|
174
298
|
rebuild: options.rebuild,
|
|
175
299
|
dryRun: options.dryRun,
|
|
300
|
+
namespaceResolver,
|
|
176
301
|
});
|
|
177
302
|
const compileOk = await compiler.compile(effectiveConfig, stateFilePath);
|
|
178
303
|
if (!compileOk) success = false;
|
|
@@ -182,13 +307,7 @@ export class BuildService {
|
|
|
182
307
|
|
|
183
308
|
// Prune step
|
|
184
309
|
if (!options.noPrune && success) {
|
|
185
|
-
await this.prune(
|
|
186
|
-
projectKey,
|
|
187
|
-
settings,
|
|
188
|
-
stateFilePath,
|
|
189
|
-
options.dryRun,
|
|
190
|
-
options.configContext
|
|
191
|
-
);
|
|
310
|
+
await this.prune(settings, stateFilePath, options.dryRun, options.configContext);
|
|
192
311
|
}
|
|
193
312
|
|
|
194
313
|
return success;
|
|
@@ -199,7 +318,6 @@ export class BuildService {
|
|
|
199
318
|
* Also removes Sous-created directories that are now empty.
|
|
200
319
|
*/
|
|
201
320
|
async prune(
|
|
202
|
-
projectKey: string,
|
|
203
321
|
settings: Settings,
|
|
204
322
|
stateFilePath: string,
|
|
205
323
|
dryRun = false,
|
|
@@ -209,11 +327,10 @@ export class BuildService {
|
|
|
209
327
|
const state = await stateService.load(stateFilePath);
|
|
210
328
|
if (!state || state.files.length === 0) return;
|
|
211
329
|
|
|
330
|
+
const protectedPaths = configContext ? protectedRepoPaths(configContext.sousDir) : [];
|
|
212
331
|
const rootScope = resolveRootScope(settings, configContext);
|
|
213
|
-
const
|
|
214
|
-
const config =
|
|
215
|
-
? resolveProjectCompilation(project, rootScope, settings, projectKey)
|
|
216
|
-
: null;
|
|
332
|
+
const recipes = resolveRecipeTargets(settings, rootScope, configContext);
|
|
333
|
+
const config = resolveCompilation(settings, rootScope);
|
|
217
334
|
|
|
218
335
|
// Collect the current output set: explicit files and active destinationDir prefixes
|
|
219
336
|
const currentOutputFiles = new Set<string>();
|
|
@@ -227,6 +344,17 @@ export class BuildService {
|
|
|
227
344
|
}
|
|
228
345
|
}
|
|
229
346
|
|
|
347
|
+
// Recipe targets are counted file by file rather than by their destination
|
|
348
|
+
// directory. Every subscribed recipe writes into the same directory, so a
|
|
349
|
+
// directory prefix would make everything ever written there look current and
|
|
350
|
+
// an unsubscribed recipe's files would stay forever.
|
|
351
|
+
for (const target of recipes.targets) {
|
|
352
|
+
for (const output of target.outputs) {
|
|
353
|
+
const dest = resolveOutputPath(target, output);
|
|
354
|
+
if (dest !== undefined) currentOutputFiles.add(dest);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
230
358
|
// A state entry is current if it matches an explicit destinationFile, or if its dest
|
|
231
359
|
// path falls under an active destinationDir (glob target output).
|
|
232
360
|
function isCurrentOutput(dest: string): boolean {
|
|
@@ -237,8 +365,11 @@ export class BuildService {
|
|
|
237
365
|
return false;
|
|
238
366
|
}
|
|
239
367
|
|
|
240
|
-
// Find files to prune
|
|
241
|
-
|
|
368
|
+
// Find files to prune. Anything inside a linked checkout or the shared
|
|
369
|
+
// recipe store is never a prune candidate, whatever the state file says.
|
|
370
|
+
const toDelete = state.files.filter(
|
|
371
|
+
f => !isCurrentOutput(f.dest) && !isProtectedPath(f.dest, protectedPaths)
|
|
372
|
+
);
|
|
242
373
|
|
|
243
374
|
if (dryRun) {
|
|
244
375
|
for (const entry of toDelete) {
|
|
@@ -247,12 +378,33 @@ export class BuildService {
|
|
|
247
378
|
return;
|
|
248
379
|
}
|
|
249
380
|
|
|
250
|
-
stateService.deleteTrackedFiles(toDelete, state.dirs);
|
|
381
|
+
stateService.deleteTrackedFiles(toDelete, state.dirs, protectedPaths);
|
|
251
382
|
for (const entry of toDelete) console.log(` ✗ pruned: ${entry.dest}`);
|
|
252
383
|
|
|
253
384
|
// Update state: remove pruned entries and any dirs that no longer exist
|
|
254
|
-
|
|
385
|
+
const deleted = new Set(toDelete.map(entry => entry.dest));
|
|
386
|
+
state.files = state.files.filter(f => !deleted.has(f.dest));
|
|
255
387
|
state.dirs = state.dirs.filter(d => fs.existsSync(d));
|
|
256
388
|
await stateService.save(stateFilePath, state);
|
|
257
389
|
}
|
|
258
390
|
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Compiles and prunes a project exactly the way `sous build` does with no flags.
|
|
394
|
+
*
|
|
395
|
+
* Shared by the subscription commands, which rebuild the project the moment they
|
|
396
|
+
* have changed what it subscribes to: a newly subscribed recipe's files appear,
|
|
397
|
+
* and a removed one's files are pruned, without anyone having to remember a
|
|
398
|
+
* second command. Every option is left at its default on purpose, because the
|
|
399
|
+
* point is to run the ordinary build and nothing else.
|
|
400
|
+
*
|
|
401
|
+
* @param settings - The merged project config, reloaded after the change.
|
|
402
|
+
* @param configContext - Where the active config was discovered.
|
|
403
|
+
* @returns True when compile and prune both succeeded.
|
|
404
|
+
*/
|
|
405
|
+
export async function buildProjectOutputs(
|
|
406
|
+
settings: Settings,
|
|
407
|
+
configContext: ConfigContext
|
|
408
|
+
): Promise<boolean> {
|
|
409
|
+
return new BuildService().build(settings, { configContext });
|
|
410
|
+
}
|