@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
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffolding a new recipe repository, which is what `sous repo init` does.
|
|
3
|
+
*
|
|
4
|
+
* The scaffold is built in memory first, validated with the very parsers sous
|
|
5
|
+
* uses to read a real repository, and only then written to disk. A scaffold
|
|
6
|
+
* that sous itself cannot read would be worse than no scaffold at all, so
|
|
7
|
+
* `scaffoldRepo` reads every manifest back after writing it and reports the
|
|
8
|
+
* failure against the file it just produced.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { ConfigError } from "../../errors.js";
|
|
14
|
+
import {
|
|
15
|
+
INDEX_FILENAME,
|
|
16
|
+
MANIFEST_EXTENSIONS,
|
|
17
|
+
RECIPE_MANIFEST_BASENAME,
|
|
18
|
+
REPO_MANIFEST_BASENAME,
|
|
19
|
+
} from "../formats/common.js";
|
|
20
|
+
import { parseIndexFile } from "../formats/index-file.js";
|
|
21
|
+
import { parseRecipeManifest } from "../formats/recipe-manifest.js";
|
|
22
|
+
import { parseRepoManifest } from "../formats/repo-manifest.js";
|
|
23
|
+
import { findRepoManifest, loadJsonFile, loadManifestFile } from "../load-manifest.js";
|
|
24
|
+
import {
|
|
25
|
+
buildExampleSkill,
|
|
26
|
+
buildGitignore,
|
|
27
|
+
buildIndexFile,
|
|
28
|
+
buildReadme,
|
|
29
|
+
buildRecipeManifest,
|
|
30
|
+
buildReleaseWorkflow,
|
|
31
|
+
buildRepoManifest,
|
|
32
|
+
exampleRecipePath,
|
|
33
|
+
type ScaffoldContext,
|
|
34
|
+
} from "./templates.js";
|
|
35
|
+
|
|
36
|
+
export * from "./templates.js";
|
|
37
|
+
|
|
38
|
+
/** The name given to the one example recipe every scaffold writes. */
|
|
39
|
+
export const EXAMPLE_RECIPE_NAME = "example";
|
|
40
|
+
|
|
41
|
+
/** What to scaffold, and where. */
|
|
42
|
+
export type ScaffoldOptions = {
|
|
43
|
+
/** Absolute path to the directory the repository is created in. */
|
|
44
|
+
directory: string;
|
|
45
|
+
/** The repository's short name. Defaults to the directory's own name. */
|
|
46
|
+
name?: string;
|
|
47
|
+
/** The one namespace to declare. Defaults to the repository's name. */
|
|
48
|
+
namespace?: string;
|
|
49
|
+
/** Overwrite an existing repository rather than refusing to touch it. */
|
|
50
|
+
force?: boolean;
|
|
51
|
+
/** Work out every file and validate the plan, but write nothing. */
|
|
52
|
+
dryRun?: boolean;
|
|
53
|
+
/** The version of sous recorded in the generated index. */
|
|
54
|
+
sousVersion: string;
|
|
55
|
+
/** When the scaffold ran, recorded in the generated index. Defaults to now. */
|
|
56
|
+
now?: Date;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** What a scaffold produced. */
|
|
60
|
+
export type ScaffoldResult = {
|
|
61
|
+
/** The directory the repository was created in. */
|
|
62
|
+
directory: string;
|
|
63
|
+
/** The repository's short name. */
|
|
64
|
+
name: string;
|
|
65
|
+
/** The namespace that was declared. */
|
|
66
|
+
namespace: string;
|
|
67
|
+
/** Paths of every file, relative to the directory, in the order they were written. */
|
|
68
|
+
files: string[];
|
|
69
|
+
/** True when nothing was actually written. */
|
|
70
|
+
dryRun: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** One planned file: where it goes, and what goes in it. */
|
|
74
|
+
type PlannedFile = {
|
|
75
|
+
/** Path relative to the repository root. */
|
|
76
|
+
relativePath: string;
|
|
77
|
+
/** The complete file contents. */
|
|
78
|
+
contents: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a new recipe repository: a repo manifest, one example recipe with a
|
|
83
|
+
* placeholder skill, an empty but valid index, a README, release automation and
|
|
84
|
+
* a `.gitignore`.
|
|
85
|
+
*
|
|
86
|
+
* @param options - What to scaffold, and where.
|
|
87
|
+
*/
|
|
88
|
+
export function scaffoldRepo(options: ScaffoldOptions): ScaffoldResult {
|
|
89
|
+
const directory = path.resolve(options.directory);
|
|
90
|
+
const name = normalizeName(options.name ?? path.basename(directory), "--name");
|
|
91
|
+
const namespace = normalizeName(options.namespace ?? name, "--namespace");
|
|
92
|
+
const dryRun = options.dryRun === true;
|
|
93
|
+
|
|
94
|
+
assertNoExistingRepo(directory, options.force === true);
|
|
95
|
+
|
|
96
|
+
const context: ScaffoldContext = {
|
|
97
|
+
name,
|
|
98
|
+
namespace,
|
|
99
|
+
recipe: EXAMPLE_RECIPE_NAME,
|
|
100
|
+
sousVersion: options.sousVersion,
|
|
101
|
+
generatedAt: (options.now ?? new Date()).toISOString(),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const files = planFiles(context);
|
|
105
|
+
|
|
106
|
+
if (!dryRun) {
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
const target = path.join(directory, file.relativePath);
|
|
109
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
110
|
+
fs.writeFileSync(target, file.contents, "utf8");
|
|
111
|
+
}
|
|
112
|
+
verifyScaffold(directory, context);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
directory,
|
|
117
|
+
name,
|
|
118
|
+
namespace,
|
|
119
|
+
files: files.map((file) => file.relativePath),
|
|
120
|
+
dryRun,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Builds every file the scaffold writes, in the order they are written. */
|
|
125
|
+
function planFiles(context: ScaffoldContext): PlannedFile[] {
|
|
126
|
+
const recipeDir = exampleRecipePath(context);
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
relativePath: `${REPO_MANIFEST_BASENAME}${MANIFEST_EXTENSIONS[0]}`,
|
|
130
|
+
contents: buildRepoManifest(context),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
relativePath: INDEX_FILENAME,
|
|
134
|
+
contents: buildIndexFile(context),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
relativePath: `${recipeDir}/${RECIPE_MANIFEST_BASENAME}${MANIFEST_EXTENSIONS[0]}`,
|
|
138
|
+
contents: buildRecipeManifest(context),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
relativePath: `${recipeDir}/skills/example-skill/SKILL.md`,
|
|
142
|
+
contents: buildExampleSkill(context),
|
|
143
|
+
},
|
|
144
|
+
{ relativePath: "README.md", contents: buildReadme(context) },
|
|
145
|
+
{
|
|
146
|
+
relativePath: ".github/workflows/sous-release.yml",
|
|
147
|
+
contents: buildReleaseWorkflow(),
|
|
148
|
+
},
|
|
149
|
+
{ relativePath: ".gitignore", contents: buildGitignore() },
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Refuses to scaffold over a repository that already exists, unless the caller
|
|
155
|
+
* asked to. The manifest is the thing that makes a directory a repository, so
|
|
156
|
+
* that is what is checked; an ordinary directory with unrelated files in it is
|
|
157
|
+
* a perfectly reasonable place to create one.
|
|
158
|
+
*/
|
|
159
|
+
function assertNoExistingRepo(directory: string, force: boolean): void {
|
|
160
|
+
if (force) return;
|
|
161
|
+
|
|
162
|
+
const existing = findRepoManifest(directory);
|
|
163
|
+
if (existing !== undefined) {
|
|
164
|
+
throw new ConfigError(
|
|
165
|
+
`${directory} is already a sous repository.\n` +
|
|
166
|
+
` It holds ${path.basename(existing)}, which sous will not overwrite.\n` +
|
|
167
|
+
` Pass --force to write the scaffold over it, or choose another directory.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reads back every file that has to parse, using the same loaders and schemas
|
|
174
|
+
* that read a published repository, so a scaffold is never reported as a
|
|
175
|
+
* success unless sous can actually read it.
|
|
176
|
+
*/
|
|
177
|
+
function verifyScaffold(directory: string, context: ScaffoldContext): void {
|
|
178
|
+
const manifestPath = path.join(
|
|
179
|
+
directory,
|
|
180
|
+
`${REPO_MANIFEST_BASENAME}${MANIFEST_EXTENSIONS[0]}`
|
|
181
|
+
);
|
|
182
|
+
const manifest = parseRepoManifest(loadManifestFile(manifestPath), manifestPath);
|
|
183
|
+
|
|
184
|
+
const indexPath = path.join(directory, INDEX_FILENAME);
|
|
185
|
+
parseIndexFile(loadJsonFile(indexPath, "repo index"), indexPath);
|
|
186
|
+
|
|
187
|
+
for (const recipePath of manifest.recipes) {
|
|
188
|
+
const recipeManifestPath = path.join(
|
|
189
|
+
directory,
|
|
190
|
+
recipePath,
|
|
191
|
+
`${RECIPE_MANIFEST_BASENAME}${MANIFEST_EXTENSIONS[0]}`
|
|
192
|
+
);
|
|
193
|
+
const recipe = parseRecipeManifest(
|
|
194
|
+
loadManifestFile(recipeManifestPath),
|
|
195
|
+
recipeManifestPath
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (!Object.hasOwn(manifest.namespaces, recipe.namespace)) {
|
|
199
|
+
throw new ConfigError(
|
|
200
|
+
`The scaffold in ${directory} is inconsistent.\n` +
|
|
201
|
+
` The recipe at ${recipePath} declares the namespace ` +
|
|
202
|
+
`'${recipe.namespace}', which ${REPO_MANIFEST_BASENAME}` +
|
|
203
|
+
`${MANIFEST_EXTENSIONS[0]} does not declare.\n` +
|
|
204
|
+
` This is a bug in sous; please report it.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (manifest.name !== context.name) {
|
|
210
|
+
throw new ConfigError(
|
|
211
|
+
`The scaffold in ${directory} is inconsistent: the repo manifest names it ` +
|
|
212
|
+
`'${manifest.name}' rather than '${context.name}'.\n` +
|
|
213
|
+
` This is a bug in sous; please report it.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Checks that a name sous is about to write into a manifest is a name the
|
|
220
|
+
* manifest schema accepts, and says how to fix it when it is not. Lower-cases
|
|
221
|
+
* the value first, so a directory called `My-Recipes` yields `my-recipes`
|
|
222
|
+
* instead of an error.
|
|
223
|
+
*/
|
|
224
|
+
function normalizeName(value: string, flagName: string): string {
|
|
225
|
+
const normalized = value.trim().toLowerCase();
|
|
226
|
+
|
|
227
|
+
if (!/^[a-z][a-z0-9-]*$/.test(normalized)) {
|
|
228
|
+
throw new ConfigError(
|
|
229
|
+
`'${value}' cannot be used as a name here.\n` +
|
|
230
|
+
` A repository name and a namespace name are lowercase kebab-case: a ` +
|
|
231
|
+
`letter, then letters, digits or hyphens (for example 'sous-recipes').\n` +
|
|
232
|
+
` Pass ${flagName} to choose one, or run the command in a directory whose ` +
|
|
233
|
+
`own name fits.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return normalized;
|
|
238
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files `sous repo init` writes, as plain string builders.
|
|
3
|
+
*
|
|
4
|
+
* These are deliberately not templates run through a template engine. A
|
|
5
|
+
* scaffolded repository is read by a person before it is read by a machine, so
|
|
6
|
+
* every file here is written to be explained: the comments are the point, and a
|
|
7
|
+
* rendering step would only stand between the author of the scaffold and the
|
|
8
|
+
* author of the new repository.
|
|
9
|
+
*
|
|
10
|
+
* Every builder returns a complete file, ending in a newline.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
INDEX_FILENAME,
|
|
15
|
+
RECIPE_MANIFEST_BASENAME,
|
|
16
|
+
REPO_MANIFEST_BASENAME,
|
|
17
|
+
} from "../formats/common.js";
|
|
18
|
+
import { stringifyIndexFile, type IndexFile } from "../formats/index-file.js";
|
|
19
|
+
|
|
20
|
+
/** What every builder needs to know about the repository being scaffolded. */
|
|
21
|
+
export type ScaffoldContext = {
|
|
22
|
+
/** The repository's short name, used as its suggested name and in the README. */
|
|
23
|
+
name: string;
|
|
24
|
+
/** The one namespace the scaffold declares. */
|
|
25
|
+
namespace: string;
|
|
26
|
+
/** The example recipe's name. */
|
|
27
|
+
recipe: string;
|
|
28
|
+
/** The version of sous doing the scaffolding, recorded in the index. */
|
|
29
|
+
sousVersion: string;
|
|
30
|
+
/** When the scaffold ran, recorded in the index. */
|
|
31
|
+
generatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** The path, relative to the repository root, of the example recipe's folder. */
|
|
35
|
+
export function exampleRecipePath(context: ScaffoldContext): string {
|
|
36
|
+
return `recipes/${context.namespace}/${context.recipe}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The repo manifest at the root: what the repository publishes, and where each
|
|
41
|
+
* recipe folder lives.
|
|
42
|
+
*
|
|
43
|
+
* @param context - The repository being scaffolded.
|
|
44
|
+
*/
|
|
45
|
+
export function buildRepoManifest(context: ScaffoldContext): string {
|
|
46
|
+
return `# The repo manifest: what this repository publishes.
|
|
47
|
+
#
|
|
48
|
+
# It is read before anything is downloaded, and it is never executable, so
|
|
49
|
+
# anyone deciding whether to trust this repository can read its whole surface
|
|
50
|
+
# without running any of its code.
|
|
51
|
+
|
|
52
|
+
formatVersion: 1
|
|
53
|
+
|
|
54
|
+
# A suggested short name. Each project chooses the name it actually uses when it
|
|
55
|
+
# runs 'sous repo add', so two repositories suggesting the same name never clash.
|
|
56
|
+
name: ${context.name}
|
|
57
|
+
|
|
58
|
+
description: >-
|
|
59
|
+
One paragraph saying what this repository publishes and who it is for.
|
|
60
|
+
|
|
61
|
+
# Where to send a change. Shown to anyone whose provider cannot open a proposal
|
|
62
|
+
# for them, so a contributor is never left without a route.
|
|
63
|
+
# contribute: https://example.com/contributing
|
|
64
|
+
|
|
65
|
+
# Namespaces group recipes. They are not versioned, and a project may subscribe
|
|
66
|
+
# to a whole namespace, which means every recipe in it, including ones published
|
|
67
|
+
# later.
|
|
68
|
+
namespaces:
|
|
69
|
+
${context.namespace}:
|
|
70
|
+
description: What the recipes in this namespace have in common.
|
|
71
|
+
|
|
72
|
+
# Every recipe folder in this repository, as a path relative to this file. Each
|
|
73
|
+
# folder holds one '${RECIPE_MANIFEST_BASENAME}.yaml'.
|
|
74
|
+
recipes:
|
|
75
|
+
- ${exampleRecipePath(context)}
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The example recipe manifest: one publishable unit, with the variables section
|
|
81
|
+
* shown as commented-out example.
|
|
82
|
+
*
|
|
83
|
+
* @param context - The repository being scaffolded.
|
|
84
|
+
*/
|
|
85
|
+
export function buildRecipeManifest(context: ScaffoldContext): string {
|
|
86
|
+
return `# A recipe manifest: one publishable unit, with its own version.
|
|
87
|
+
#
|
|
88
|
+
# Copy this folder to start a new recipe, then add its path to the 'recipes'
|
|
89
|
+
# list in the ${REPO_MANIFEST_BASENAME}.yaml at the root of this repository.
|
|
90
|
+
|
|
91
|
+
formatVersion: 1
|
|
92
|
+
|
|
93
|
+
namespace: ${context.namespace}
|
|
94
|
+
name: ${context.recipe}
|
|
95
|
+
|
|
96
|
+
# Recipe metadata is the source of truth for versions. 'sous repo release'
|
|
97
|
+
# bumps this field and keeps the matching git tag consistent with it.
|
|
98
|
+
version: 0.1.0
|
|
99
|
+
|
|
100
|
+
description: >-
|
|
101
|
+
One paragraph saying what this recipe gives a project that subscribes to it.
|
|
102
|
+
|
|
103
|
+
# Build dependencies: fetched, pinned and addressable from this recipe's own
|
|
104
|
+
# files, but their files do NOT enter a subscriber's output.
|
|
105
|
+
# depends:
|
|
106
|
+
# - core/shared-partials@^1.0.0
|
|
107
|
+
|
|
108
|
+
# Co-subscriptions: subscribing to this recipe subscribes the project to these
|
|
109
|
+
# as well, in full. Their questions run and their files DO enter the output.
|
|
110
|
+
# subscribes:
|
|
111
|
+
# - workflow/task-files
|
|
112
|
+
|
|
113
|
+
# The files this recipe contributes, grouped by what they are. Paths are
|
|
114
|
+
# relative to this folder.
|
|
115
|
+
contents:
|
|
116
|
+
- kind: skills
|
|
117
|
+
include:
|
|
118
|
+
- skills/**/*.md
|
|
119
|
+
|
|
120
|
+
# Variables this recipe needs answered. A definition is a specification, never a
|
|
121
|
+
# value: sous asks the question only when a subscribed recipe needs the variable
|
|
122
|
+
# and no valid answer is already in scope.
|
|
123
|
+
#
|
|
124
|
+
# Every definition must carry a 'description' and an 'example'. The description
|
|
125
|
+
# is the paragraph shown above the question and by 'sous vars show <name>'; it
|
|
126
|
+
# says, in full sentences, what the setting is for, what the default does, and
|
|
127
|
+
# what else is acceptable. The prompt is one plain question, nothing more. The
|
|
128
|
+
# example is a realistic sample answer, shown with the question so nobody has to
|
|
129
|
+
# guess what a good one looks like; it is documentation only and is never
|
|
130
|
+
# stored, so use 'default' for a value a project should actually start with.
|
|
131
|
+
#
|
|
132
|
+
# variables:
|
|
133
|
+
# - name: apiBaseUrl
|
|
134
|
+
# type: url
|
|
135
|
+
# prompt: Which API should this project talk to?
|
|
136
|
+
# description: >-
|
|
137
|
+
# Every request this recipe generates is sent to one deployment of the API,
|
|
138
|
+
# and this setting says which one. The default points at the public
|
|
139
|
+
# production host, but any deployment you can reach works, including a
|
|
140
|
+
# staging host or a service running on your own machine.
|
|
141
|
+
# example: https://api.example.com
|
|
142
|
+
# default: https://api.example.com
|
|
143
|
+
# required: true
|
|
144
|
+
#
|
|
145
|
+
# - name: serviceToken
|
|
146
|
+
# type: string
|
|
147
|
+
# # Name an environment variable explicitly to reuse a value the environment
|
|
148
|
+
# # already carries. When omitted, 'sous repo release' derives one.
|
|
149
|
+
# env: SERVICE_TOKEN
|
|
150
|
+
# prompt: What is this project's service token?
|
|
151
|
+
# description: >-
|
|
152
|
+
# This recipe authenticates every call it makes with a service token, which
|
|
153
|
+
# is issued per project and is not shared between them. Create one under
|
|
154
|
+
# Settings, then Tokens, and give it read access to the project you are
|
|
155
|
+
# configuring. There is no default; a token is always specific to you, and
|
|
156
|
+
# it is stored in the gitignored env file so it never reaches git.
|
|
157
|
+
# example: svc_0123456789abcdef0123
|
|
158
|
+
# # A secret is always written to the gitignored '.sous/.env.local'.
|
|
159
|
+
# secret: true
|
|
160
|
+
# scope: local
|
|
161
|
+
# validate:
|
|
162
|
+
# minLength: 20
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The placeholder skill the example recipe contributes.
|
|
168
|
+
*
|
|
169
|
+
* @param context - The repository being scaffolded.
|
|
170
|
+
*/
|
|
171
|
+
export function buildExampleSkill(context: ScaffoldContext): string {
|
|
172
|
+
return `---
|
|
173
|
+
name: example-skill
|
|
174
|
+
description: >-
|
|
175
|
+
Replace this with the sentence that tells an agent when to load the skill.
|
|
176
|
+
Say what the skill covers and name the situations that should trigger it.
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
# Example skill
|
|
180
|
+
|
|
181
|
+
This file is a placeholder written by 'sous repo init'. Replace it with a real
|
|
182
|
+
skill, or delete it once the ${context.recipe} recipe has content of its own.
|
|
183
|
+
|
|
184
|
+
A skill is ordinary markdown. Everything under this recipe's 'contents' entry is
|
|
185
|
+
copied into a subscribing project's agent skill directory, so what you write
|
|
186
|
+
here is what an agent reads there.
|
|
187
|
+
|
|
188
|
+
## What to put here
|
|
189
|
+
|
|
190
|
+
Describe the concept the skill covers, then the rules an agent should follow
|
|
191
|
+
when it applies. Keep it short enough to be read in full, and point at reference
|
|
192
|
+
files for anything long.
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The empty but valid index. `sous repo release` rewrites it; it exists from the
|
|
198
|
+
* first commit so the repository is readable by sous before anything is
|
|
199
|
+
* published.
|
|
200
|
+
*
|
|
201
|
+
* @param context - The repository being scaffolded.
|
|
202
|
+
*/
|
|
203
|
+
export function buildIndexFile(context: ScaffoldContext): string {
|
|
204
|
+
const index: IndexFile = {
|
|
205
|
+
formatVersion: 1,
|
|
206
|
+
name: context.name,
|
|
207
|
+
generatedAt: context.generatedAt,
|
|
208
|
+
generator: context.sousVersion,
|
|
209
|
+
namespaces: {
|
|
210
|
+
[context.namespace]: {
|
|
211
|
+
description: "What the recipes in this namespace have in common.",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
recipes: {},
|
|
215
|
+
};
|
|
216
|
+
return stringifyIndexFile(index);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The README, explaining the layout in plain language to whoever opens the
|
|
221
|
+
* repository next.
|
|
222
|
+
*
|
|
223
|
+
* @param context - The repository being scaffolded.
|
|
224
|
+
*/
|
|
225
|
+
export function buildReadme(context: ScaffoldContext): string {
|
|
226
|
+
const recipeDir = exampleRecipePath(context);
|
|
227
|
+
return `# ${context.name}
|
|
228
|
+
|
|
229
|
+
A sous recipe repository. It publishes **recipes**, grouped into
|
|
230
|
+
**namespaces**, that other projects subscribe to.
|
|
231
|
+
|
|
232
|
+
## Layout
|
|
233
|
+
|
|
234
|
+
\`\`\`
|
|
235
|
+
${REPO_MANIFEST_BASENAME}.yaml what this repository publishes
|
|
236
|
+
${INDEX_FILENAME} the published catalog, written by sous
|
|
237
|
+
${recipeDir}/
|
|
238
|
+
${RECIPE_MANIFEST_BASENAME}.yaml one recipe: its version, contents and variables
|
|
239
|
+
skills/ the files that recipe contributes
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
## The three files
|
|
243
|
+
|
|
244
|
+
**\`${REPO_MANIFEST_BASENAME}.yaml\`** declares the namespaces this repository
|
|
245
|
+
publishes and lists every recipe folder in it. It is hand-written, and it is the
|
|
246
|
+
first thing sous reads.
|
|
247
|
+
|
|
248
|
+
**\`${RECIPE_MANIFEST_BASENAME}.yaml\`** describes one recipe: which namespace it
|
|
249
|
+
belongs to, what version it is at, what it depends on, which of its files a
|
|
250
|
+
subscriber receives, and which variables it needs answered. It is hand-written
|
|
251
|
+
too, and its \`version\` field is the source of truth for versions.
|
|
252
|
+
|
|
253
|
+
**\`${INDEX_FILENAME}\`** is the catalog: every recipe, every published version,
|
|
254
|
+
and a content hash for each one. It is written by \`sous repo release\` and
|
|
255
|
+
committed. Do not edit it by hand.
|
|
256
|
+
|
|
257
|
+
## Adding a recipe
|
|
258
|
+
|
|
259
|
+
1. Copy \`${recipeDir}\` to a new folder under \`recipes/\`.
|
|
260
|
+
2. Edit its \`${RECIPE_MANIFEST_BASENAME}.yaml\`: set the namespace, the name, the
|
|
261
|
+
version and the contents.
|
|
262
|
+
3. Add the new folder's path to the \`recipes\` list in
|
|
263
|
+
\`${REPO_MANIFEST_BASENAME}.yaml\`.
|
|
264
|
+
4. Open a pull request. The workflow in \`.github/workflows/sous-release.yml\`
|
|
265
|
+
checks that everything is consistent before it can be merged.
|
|
266
|
+
|
|
267
|
+
## Publishing
|
|
268
|
+
|
|
269
|
+
Commit your recipe changes, then run \`sous repo release\`. It shows you what it
|
|
270
|
+
would publish and asks once, then raises the version of every recipe whose files
|
|
271
|
+
changed since the tag that last published it, regenerates \`${INDEX_FILENAME}\`,
|
|
272
|
+
commits both, and cuts an annotated tag for each version. Add \`--push\` to push
|
|
273
|
+
the commit and the tags, or push them yourself.
|
|
274
|
+
|
|
275
|
+
Useful ways to narrow or steer it:
|
|
276
|
+
|
|
277
|
+
\`\`\`bash
|
|
278
|
+
sous repo release --dry-run # show the plan and stop
|
|
279
|
+
sous repo release --namespace ${context.namespace} # only this namespace
|
|
280
|
+
sous repo release --recipe ${context.namespace}/${context.recipe} # only this recipe
|
|
281
|
+
sous repo release --bump minor # a minor step instead of a patch
|
|
282
|
+
sous repo release --include-unchanged # release everything in scope anyway
|
|
283
|
+
\`\`\`
|
|
284
|
+
|
|
285
|
+
On a branch other than \`main\`, a release bumps and commits but cuts no tags:
|
|
286
|
+
tags are cut on the default branch, by the workflow in
|
|
287
|
+
\`.github/workflows/sous-release.yml\` after the merge. Pass \`--tag\` to cut them
|
|
288
|
+
anyway. Tags are shaped \`namespace/recipe@version\`, and a version is published
|
|
289
|
+
when its tag exists.
|
|
290
|
+
|
|
291
|
+
To propose a change to a repository you do not maintain, commit it and run
|
|
292
|
+
\`sous repo submit\`, which validates everything first and then opens a pull
|
|
293
|
+
request through your provider's own command line tool.
|
|
294
|
+
|
|
295
|
+
## Using it
|
|
296
|
+
|
|
297
|
+
In any project that has a sous config:
|
|
298
|
+
|
|
299
|
+
\`\`\`bash
|
|
300
|
+
sous repo add <the URL of this repository>
|
|
301
|
+
sous subscribe ${context.namespace}/${context.recipe}
|
|
302
|
+
sous build
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
Adding a repository is what trusts it, so read a repository before you add it.
|
|
306
|
+
|
|
307
|
+
## Working on it
|
|
308
|
+
|
|
309
|
+
\`sous repo link\` points a project at a working copy of this repository instead
|
|
310
|
+
of at a published version, so you can edit a recipe and rebuild without
|
|
311
|
+
releasing anything:
|
|
312
|
+
|
|
313
|
+
\`\`\`bash
|
|
314
|
+
sous repo link ${context.name} /path/to/this/checkout
|
|
315
|
+
\`\`\`
|
|
316
|
+
|
|
317
|
+
Builds say loudly when a repository is linked. Run \`sous repo unlink ${context.name}\`
|
|
318
|
+
to go back to published versions.
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The release workflow: validate every pull request, publish on merge. */
|
|
323
|
+
export function buildReleaseWorkflow(): string {
|
|
324
|
+
return `# Release automation for this sous recipe repository.
|
|
325
|
+
#
|
|
326
|
+
# Two jobs, both running the sous CLI straight from npm so nothing needs to be
|
|
327
|
+
# installed into this repository:
|
|
328
|
+
#
|
|
329
|
+
# 'sous repo release --check' validates every manifest, confirms each recipe
|
|
330
|
+
# folder matches what the repo manifest lists, and confirms the committed
|
|
331
|
+
# index agrees with the versions and dependencies the recipe manifests
|
|
332
|
+
# declare. It only reads; it never writes, commits or tags. That makes it the
|
|
333
|
+
# right thing to run on a pull request.
|
|
334
|
+
#
|
|
335
|
+
# 'sous repo release --ci --push --yes' does the same validation and then
|
|
336
|
+
# publishes. '--ci' raises no versions, accepts the plan it prints, and asks
|
|
337
|
+
# no questions: the version bump belongs in the change being merged, so a
|
|
338
|
+
# recipe that changed without one fails here rather than being given a version
|
|
339
|
+
# nobody reviewed. It cuts an annotated tag for every version that does not
|
|
340
|
+
# have one yet, dependency-first, and '--push' pushes the commit and those
|
|
341
|
+
# tags. '--ci' implies '--yes'; passing it as well keeps this workflow working
|
|
342
|
+
# with a sous old enough that it did not.
|
|
343
|
+
|
|
344
|
+
name: sous release
|
|
345
|
+
|
|
346
|
+
on:
|
|
347
|
+
pull_request:
|
|
348
|
+
push:
|
|
349
|
+
branches:
|
|
350
|
+
- main
|
|
351
|
+
|
|
352
|
+
jobs:
|
|
353
|
+
check:
|
|
354
|
+
name: Validate recipes
|
|
355
|
+
if: github.event_name == 'pull_request'
|
|
356
|
+
runs-on: ubuntu-latest
|
|
357
|
+
steps:
|
|
358
|
+
- uses: actions/checkout@v4
|
|
359
|
+
with:
|
|
360
|
+
# The whole history, so the tags a version is checked against are
|
|
361
|
+
# visible.
|
|
362
|
+
fetch-depth: 0
|
|
363
|
+
- uses: actions/setup-node@v4
|
|
364
|
+
with:
|
|
365
|
+
node-version: 22
|
|
366
|
+
- name: Validate every manifest, the index and the tags
|
|
367
|
+
run: npx --yes @sous-io/sous repo release --check
|
|
368
|
+
|
|
369
|
+
release:
|
|
370
|
+
name: Publish recipes
|
|
371
|
+
if: github.event_name == 'push'
|
|
372
|
+
runs-on: ubuntu-latest
|
|
373
|
+
permissions:
|
|
374
|
+
# Needed to push the release commit and the new tags.
|
|
375
|
+
contents: write
|
|
376
|
+
steps:
|
|
377
|
+
- uses: actions/checkout@v4
|
|
378
|
+
with:
|
|
379
|
+
# The whole history, so existing tags are visible and versions that
|
|
380
|
+
# were already published are not cut a second time.
|
|
381
|
+
fetch-depth: 0
|
|
382
|
+
- uses: actions/setup-node@v4
|
|
383
|
+
with:
|
|
384
|
+
node-version: 22
|
|
385
|
+
- name: Identify the committer, in case the index has to be rewritten
|
|
386
|
+
run: |
|
|
387
|
+
git config user.name "github-actions[bot]"
|
|
388
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
389
|
+
- name: Publish every new version
|
|
390
|
+
run: npx --yes @sous-io/sous repo release --ci --push --yes
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The repository's `.gitignore`. A recipe repository holds text, so this stays
|
|
396
|
+
* short: editor and operating system leftovers, and the machine-local files
|
|
397
|
+
* sous writes when the repository is linked into a project.
|
|
398
|
+
*/
|
|
399
|
+
export function buildGitignore(): string {
|
|
400
|
+
return `# Operating system and editor leftovers
|
|
401
|
+
.DS_Store
|
|
402
|
+
Thumbs.db
|
|
403
|
+
*.swp
|
|
404
|
+
|
|
405
|
+
# Dependencies, if a recipe ever needs any
|
|
406
|
+
node_modules/
|
|
407
|
+
|
|
408
|
+
# Machine-local sous files, written when this repository is used from a project
|
|
409
|
+
.sous/sous.state.json
|
|
410
|
+
.sous/sous.pid
|
|
411
|
+
.sous/sous.links.json
|
|
412
|
+
.sous/repos/
|
|
413
|
+
.sous/.env.local
|
|
414
|
+
`;
|
|
415
|
+
}
|