repokeeper 0.4.7 → 0.6.0
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/.github/workflows/commitlint.yml +49 -0
- package/.github/workflows/release-please.yml +59 -0
- package/.github/workflows/repokeeper-check.yml +30 -0
- package/.github/workflows/stack-dart.yml +72 -0
- package/.github/workflows/stack-dotnet.yml +68 -0
- package/.github/workflows/stack-go.yml +59 -0
- package/.github/workflows/stack-java.yml +70 -0
- package/.github/workflows/stack-node.yml +85 -0
- package/.github/workflows/stack-php.yml +66 -0
- package/.github/workflows/stack-python.yml +70 -0
- package/.github/workflows/stack-ruby.yml +65 -0
- package/.github/workflows/stack-rust.yml +60 -0
- package/.github/workflows/stack-script.yml +87 -0
- package/README.md +60 -63
- package/dist/cli.js +56 -8
- package/dist/commands/context.js +26 -3
- package/dist/commands/eject.js +74 -0
- package/dist/commands/github.js +2 -0
- package/dist/commands/gitlab.js +46 -0
- package/dist/commands/init.js +110 -13
- package/dist/commands/presets.js +114 -0
- package/dist/commands/report.js +23 -3
- package/dist/commands/update.js +3 -2
- package/dist/config/load.js +8 -1
- package/dist/config/schema.js +45 -1
- package/dist/config/types.js +3 -3
- package/dist/duplicates.js +2 -2
- package/dist/errors.js +1 -1
- package/dist/git.js +33 -4
- package/dist/gitlab/api.js +50 -0
- package/dist/gitlab/settings.js +181 -0
- package/dist/model.js +8 -0
- package/dist/modules/commits.js +3 -2
- package/dist/modules/deps.js +1 -1
- package/dist/modules/health.js +5 -8
- package/dist/modules/hooks.js +7 -5
- package/dist/modules/release.js +18 -5
- package/dist/platforms/github.js +77 -13
- package/dist/platforms/gitlab.js +233 -0
- package/dist/platforms/index.js +5 -0
- package/dist/stacks/index.js +56 -0
- package/dist/stacks/support.js +19 -0
- package/dist/templates.js +13 -1
- package/dist/version.js +9 -0
- package/package.json +6 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { defaultBranch } from "../config/types.js";
|
|
2
|
+
import { MANAGED_HEADER } from "../model.js";
|
|
3
|
+
import { PACKAGE_VERSION, TOOL_VERSIONS } from "../version.js";
|
|
4
|
+
const md = (path, body) => ({
|
|
5
|
+
kind: "file",
|
|
6
|
+
module: "health",
|
|
7
|
+
path,
|
|
8
|
+
content: `<!-- ${MANAGED_HEADER} -->\n\n${body}`,
|
|
9
|
+
});
|
|
10
|
+
/** Web address of the GitLab instance the repository lives on. */
|
|
11
|
+
const baseUrl = (repo) => `https://${repo.host ?? "gitlab.com"}`;
|
|
12
|
+
const CI_FILE = ".gitlab-ci.yml";
|
|
13
|
+
const CI_KEYS = ["workflow", "node", "go", "commits", "repokeeper", "renovate", "release"];
|
|
14
|
+
/** One managed top-level key of .gitlab-ci.yml; keys the user adds are left alone. */
|
|
15
|
+
const ciKey = (module, key, value) => ({
|
|
16
|
+
kind: "yaml",
|
|
17
|
+
module,
|
|
18
|
+
path: CI_FILE,
|
|
19
|
+
keyPath: [key],
|
|
20
|
+
value,
|
|
21
|
+
order: CI_KEYS,
|
|
22
|
+
});
|
|
23
|
+
const NOT_SCHEDULED = '$CI_PIPELINE_SOURCE != "schedule"';
|
|
24
|
+
/** The checks of a change: merge requests and the default branch, not schedules and not tag pipelines. */
|
|
25
|
+
const FOR_CHANGES = `${NOT_SCHEDULED} && $CI_COMMIT_TAG == null`;
|
|
26
|
+
/** Image of the jobs that only run tools, whatever Node.js versions the project tests on. */
|
|
27
|
+
const TOOL_IMAGE = "node:24";
|
|
28
|
+
/** The node stack's CI inputs, as the GitHub reusable workflow receives them, as a GitLab job. */
|
|
29
|
+
function nodeJob(input) {
|
|
30
|
+
const pm = input["package-manager"] ?? "npm";
|
|
31
|
+
const cached = input.cache === "npm";
|
|
32
|
+
const scripts = JSON.parse(input.scripts ?? "[]");
|
|
33
|
+
return {
|
|
34
|
+
stage: "test",
|
|
35
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: a GitLab CI variable
|
|
36
|
+
image: "node:${NODE_VERSION}",
|
|
37
|
+
parallel: { matrix: [{ NODE_VERSION: JSON.parse(input["node-versions"] ?? '["22","24"]') }] },
|
|
38
|
+
rules: [{ if: FOR_CHANGES }],
|
|
39
|
+
variables: {
|
|
40
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
41
|
+
...(cached ? { npm_config_cache: "$CI_PROJECT_DIR/.npm" } : {}),
|
|
42
|
+
},
|
|
43
|
+
...(cached ? { cache: { key: { files: ["package-lock.json"] }, paths: [".npm/"] } } : {}),
|
|
44
|
+
script: [
|
|
45
|
+
...(pm === "npm" ? [] : ["corepack enable"]),
|
|
46
|
+
input["install-command"] ?? "npm ci",
|
|
47
|
+
...scripts.map((script) => `${pm} run ${script}`),
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The go stack's CI inputs as a GitLab job. No module cache: it would have to live inside the project
|
|
53
|
+
* folder, where `gofmt -l .` would read it as the project's own code.
|
|
54
|
+
*/
|
|
55
|
+
function goJob(input) {
|
|
56
|
+
// setup-go's "stable" is the image's `latest` tag
|
|
57
|
+
const versions = JSON.parse(input["go-versions"] ?? '["stable"]').map((version) => version === "stable" ? "latest" : version);
|
|
58
|
+
return {
|
|
59
|
+
stage: "test",
|
|
60
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: a GitLab CI variable
|
|
61
|
+
image: "golang:${GO_VERSION}",
|
|
62
|
+
parallel: { matrix: [{ GO_VERSION: versions }] },
|
|
63
|
+
rules: [{ if: FOR_CHANGES }],
|
|
64
|
+
script: JSON.parse(input.commands ?? "[]"),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** commitlint from a scratch directory, so the project needs no commitlint of its own. */
|
|
68
|
+
const COMMITLINT = `dir="$(mktemp -d)"
|
|
69
|
+
(cd "$dir" && echo '{ "private": true }' > package.json && npm install --no-audit --no-fund @commitlint/cli@${TOOL_VERSIONS.commitlintCli} @commitlint/config-conventional@${TOOL_VERSIONS.commitlintConventional})
|
|
70
|
+
cat > "$dir/commitlint.config.mjs" <<'EOF'
|
|
71
|
+
export default { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [2, "always", 100] } };
|
|
72
|
+
EOF
|
|
73
|
+
lint() { "$dir/node_modules/.bin/commitlint" --config "$dir/commitlint.config.mjs" --verbose "$@"; }
|
|
74
|
+
# a merged results pipeline runs on a merge commit; the commits to lint end at the source branch
|
|
75
|
+
head="$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA"
|
|
76
|
+
[ -n "$head" ] || head="$CI_COMMIT_SHA"
|
|
77
|
+
if [ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]; then
|
|
78
|
+
lint --from "$CI_MERGE_REQUEST_DIFF_BASE_SHA" --to "$head"
|
|
79
|
+
else
|
|
80
|
+
lint --last
|
|
81
|
+
fi
|
|
82
|
+
`;
|
|
83
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
84
|
+
/**
|
|
85
|
+
* semantic-release and the plugins it does not bundle, from a scratch directory. It resolves plugins next to
|
|
86
|
+
* itself before it looks in the project, so the project needs none of them installed.
|
|
87
|
+
*/
|
|
88
|
+
const SEMANTIC_RELEASE = `dir="$(mktemp -d)"
|
|
89
|
+
(cd "$dir" && echo '{ "private": true }' > package.json && npm install --no-audit --no-fund semantic-release@${TOOL_VERSIONS.semanticRelease} @semantic-release/changelog@${TOOL_VERSIONS.semanticReleaseChangelog} @semantic-release/git@${TOOL_VERSIONS.semanticReleaseGit} @semantic-release/gitlab@${TOOL_VERSIONS.semanticReleaseGitlab} conventional-changelog-conventionalcommits@${TOOL_VERSIONS.conventionalCommitsPreset})
|
|
90
|
+
"$dir/node_modules/.bin/semantic-release"
|
|
91
|
+
`;
|
|
92
|
+
export const gitlabPlatform = {
|
|
93
|
+
id: "gitlab",
|
|
94
|
+
stacks: ["node", "go"],
|
|
95
|
+
changeRequest: "merge request",
|
|
96
|
+
profileUrl: (repo) => (repo.owner ? `${baseUrl(repo)}/${repo.owner}` : null),
|
|
97
|
+
securityReport: (repo) => repo.owner
|
|
98
|
+
? `by opening a [confidential issue](${baseUrl(repo)}/${repo.owner}/${repo.name}/-/issues/new?issue%5Bconfidential%5D=true)`
|
|
99
|
+
: "by opening a confidential issue in the repository",
|
|
100
|
+
communityFiles(ctx) {
|
|
101
|
+
const outputs = [
|
|
102
|
+
md(".gitlab/issue_templates/Bug.md", "## What happened?\n\n<!-- Include the steps to reproduce it. -->\n\n## What did you expect?\n\n## Version\n\n/label ~bug\n"),
|
|
103
|
+
md(".gitlab/issue_templates/Feature.md", "## What problem would this solve?\n\n## What do you propose?\n\n/label ~enhancement\n"),
|
|
104
|
+
md(".gitlab/merge_request_templates/Default.md", "## Summary\n\n## Testing\n\n- [ ] Tests added or updated\n- [ ] Commit messages follow Conventional Commits\n"),
|
|
105
|
+
];
|
|
106
|
+
const health = ctx.config.modules.health;
|
|
107
|
+
if (health && health.codeowners.length > 0) {
|
|
108
|
+
outputs.push({
|
|
109
|
+
kind: "file",
|
|
110
|
+
module: "health",
|
|
111
|
+
path: ".gitlab/CODEOWNERS",
|
|
112
|
+
content: `# ${MANAGED_HEADER}\n* ${health.codeowners.join(" ")}\n`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return outputs;
|
|
116
|
+
},
|
|
117
|
+
// Renovate finds the package managers itself, so the Dependabot ecosystem names are not needed
|
|
118
|
+
dependencyUpdates() {
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
kind: "file",
|
|
122
|
+
module: "deps",
|
|
123
|
+
path: "renovate.json",
|
|
124
|
+
content: json({
|
|
125
|
+
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
|
126
|
+
// chore commits pass commitlint, as the Dependabot prefix does on GitHub. No schedule preset: the
|
|
127
|
+
// pipeline schedule sets the cadence, and a Renovate schedule would skip runs outside its own window
|
|
128
|
+
extends: ["config:recommended", ":semanticCommits", ":semanticCommitTypeAll(chore)"],
|
|
129
|
+
packageRules: [
|
|
130
|
+
{ matchUpdateTypes: ["minor", "patch"], groupName: "minor and patch updates" },
|
|
131
|
+
// @types/node majors track the Node.js line a project runs on, which the project chooses
|
|
132
|
+
{ matchPackageNames: ["@types/node"], matchUpdateTypes: ["major"], enabled: false },
|
|
133
|
+
],
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
// runs from a pipeline schedule of the repository; without the token or a schedule it never starts
|
|
137
|
+
ciKey("deps", "renovate", {
|
|
138
|
+
stage: "test",
|
|
139
|
+
image: `renovate/renovate:${TOOL_VERSIONS.renovate}`,
|
|
140
|
+
rules: [{ if: '$CI_PIPELINE_SOURCE == "schedule" && $RENOVATE_TOKEN' }],
|
|
141
|
+
variables: {
|
|
142
|
+
RENOVATE_PLATFORM: "gitlab",
|
|
143
|
+
RENOVATE_ENDPOINT: "$CI_API_V4_URL",
|
|
144
|
+
RENOVATE_AUTODISCOVER: "false",
|
|
145
|
+
RENOVATE_ONBOARDING: "false",
|
|
146
|
+
},
|
|
147
|
+
script: ['renovate "$CI_PROJECT_PATH"'],
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
150
|
+
},
|
|
151
|
+
ciWorkflow(ctx) {
|
|
152
|
+
const jobs = [];
|
|
153
|
+
const node = ctx.stacks.find((stack) => stack.id === "node")?.ci;
|
|
154
|
+
if (node)
|
|
155
|
+
jobs.push(ciKey("ci", "node", nodeJob(node.with)));
|
|
156
|
+
const go = ctx.stacks.find((stack) => stack.id === "go")?.ci;
|
|
157
|
+
if (go)
|
|
158
|
+
jobs.push(ciKey("ci", "go", goJob(go.with)));
|
|
159
|
+
if (ctx.config.modules.commits) {
|
|
160
|
+
jobs.push(ciKey("ci", "commits", {
|
|
161
|
+
stage: "test",
|
|
162
|
+
image: TOOL_IMAGE,
|
|
163
|
+
rules: [{ if: FOR_CHANGES }],
|
|
164
|
+
variables: { GIT_DEPTH: "0" },
|
|
165
|
+
script: [COMMITLINT],
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
if (ctx.config.modules.drift) {
|
|
169
|
+
jobs.push(ciKey("ci", "repokeeper", {
|
|
170
|
+
stage: "test",
|
|
171
|
+
image: TOOL_IMAGE,
|
|
172
|
+
rules: [{ if: FOR_CHANGES }],
|
|
173
|
+
script: [`npx --yes repokeeper@${PACKAGE_VERSION} check`],
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
if (jobs.length === 0)
|
|
177
|
+
return [];
|
|
178
|
+
return [
|
|
179
|
+
// one pipeline per merge request, per push to the default branch and per schedule; none for other branches.
|
|
180
|
+
// Tags are let through for jobs of the user's own, such as publishing: no managed job runs on one
|
|
181
|
+
ciKey("ci", "workflow", {
|
|
182
|
+
rules: [
|
|
183
|
+
{ if: '$CI_PIPELINE_SOURCE == "merge_request_event"' },
|
|
184
|
+
{ if: '$CI_PIPELINE_SOURCE == "schedule"' },
|
|
185
|
+
{ if: `$CI_COMMIT_BRANCH == "${defaultBranch(ctx.config)}"` },
|
|
186
|
+
{ if: "$CI_COMMIT_TAG" },
|
|
187
|
+
],
|
|
188
|
+
}),
|
|
189
|
+
...jobs,
|
|
190
|
+
];
|
|
191
|
+
},
|
|
192
|
+
releaseAutomation(ctx) {
|
|
193
|
+
const branch = defaultBranch(ctx.config);
|
|
194
|
+
// a Go module's version is its tag: there is no file to bump, so the release is the changelog and the tag
|
|
195
|
+
const npm = ctx.stacks.some((stack) => stack.release.type === "node");
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
kind: "file",
|
|
199
|
+
module: "release",
|
|
200
|
+
path: ".releaserc.json",
|
|
201
|
+
content: json({
|
|
202
|
+
branches: [branch],
|
|
203
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: a semantic-release template
|
|
204
|
+
tagFormat: "v${version}",
|
|
205
|
+
plugins: [
|
|
206
|
+
["@semantic-release/commit-analyzer", { preset: "conventionalcommits" }],
|
|
207
|
+
["@semantic-release/release-notes-generator", { preset: "conventionalcommits" }],
|
|
208
|
+
["@semantic-release/changelog", { changelogFile: "CHANGELOG.md" }],
|
|
209
|
+
// bumps package.json and the npm lock file; publishing stays the project's own business
|
|
210
|
+
...(npm ? [["@semantic-release/npm", { npmPublish: false }]] : []),
|
|
211
|
+
[
|
|
212
|
+
"@semantic-release/git",
|
|
213
|
+
{
|
|
214
|
+
assets: ["CHANGELOG.md", ...(npm ? ["package.json", "package-lock.json", "npm-shrinkwrap.json"] : [])],
|
|
215
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: a semantic-release template
|
|
216
|
+
message: "chore(release): ${nextRelease.version} [skip ci]",
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
"@semantic-release/gitlab",
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
// semantic-release pushes a commit and a tag, which the job token cannot do; without GITLAB_TOKEN the job is absent
|
|
224
|
+
ciKey("release", "release", {
|
|
225
|
+
stage: "deploy",
|
|
226
|
+
image: TOOL_IMAGE,
|
|
227
|
+
rules: [{ if: `${NOT_SCHEDULED} && $CI_COMMIT_BRANCH == "${branch}" && $GITLAB_TOKEN` }],
|
|
228
|
+
variables: { GIT_DEPTH: "0" },
|
|
229
|
+
script: [SEMANTIC_RELEASE],
|
|
230
|
+
}),
|
|
231
|
+
];
|
|
232
|
+
},
|
|
233
|
+
};
|
package/dist/stacks/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { STACK_IDS } from "../config/types.js";
|
|
2
4
|
import { dartStack } from "./dart.js";
|
|
3
5
|
import { dotnetStack } from "./dotnet.js";
|
|
@@ -34,3 +36,57 @@ export async function detectStacks(root) {
|
|
|
34
36
|
// scripts beside another stack belong to that stack; the script pack is for script-only repositories
|
|
35
37
|
return found.length > 1 ? found.filter((id) => id !== "script") : found;
|
|
36
38
|
}
|
|
39
|
+
/** Folders that hold a project's by-products or samples rather than a project of the repository. */
|
|
40
|
+
const NOT_A_PROJECT = new Set([
|
|
41
|
+
"node_modules",
|
|
42
|
+
"vendor",
|
|
43
|
+
"dist",
|
|
44
|
+
"build",
|
|
45
|
+
"target",
|
|
46
|
+
"out",
|
|
47
|
+
"bin",
|
|
48
|
+
"obj",
|
|
49
|
+
"coverage",
|
|
50
|
+
"example",
|
|
51
|
+
"examples",
|
|
52
|
+
"test",
|
|
53
|
+
"tests",
|
|
54
|
+
"fixtures",
|
|
55
|
+
"doc",
|
|
56
|
+
"docs",
|
|
57
|
+
]);
|
|
58
|
+
/**
|
|
59
|
+
* The stacks of a repository and where they are: at the root or, when the root holds no project
|
|
60
|
+
* (a monorepo such as frontend/ and backend/), in its immediate folders.
|
|
61
|
+
*/
|
|
62
|
+
export async function detectStackLayout(root) {
|
|
63
|
+
const atRoot = await detectStacks(root);
|
|
64
|
+
const rooted = { stacks: atRoot, directories: {}, skipped: [] };
|
|
65
|
+
// loose scripts at the root do not make it a project: a monorepo often keeps some beside its folders
|
|
66
|
+
if (atRoot.some((id) => id !== "script"))
|
|
67
|
+
return rooted;
|
|
68
|
+
const directories = {};
|
|
69
|
+
const skipped = [];
|
|
70
|
+
const folders = readdirSync(root, { withFileTypes: true })
|
|
71
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !NOT_A_PROJECT.has(entry.name))
|
|
72
|
+
.map((entry) => entry.name)
|
|
73
|
+
.sort();
|
|
74
|
+
for (const folder of folders) {
|
|
75
|
+
for (const id of await detectStacks(join(root, folder))) {
|
|
76
|
+
if (directories[id] === undefined)
|
|
77
|
+
directories[id] = folder;
|
|
78
|
+
else
|
|
79
|
+
skipped.push({ stack: id, directory: folder });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let stacks = STACK_IDS.filter((id) => directories[id] !== undefined);
|
|
83
|
+
// nothing but scripts in the folders either: the root's own answer stands
|
|
84
|
+
if (atRoot.length > 0 && stacks.every((id) => id === "script"))
|
|
85
|
+
return rooted;
|
|
86
|
+
// as at the root: a folder of scripts beside real projects is tooling, not a stack
|
|
87
|
+
if (stacks.length > 1 && directories.script !== undefined) {
|
|
88
|
+
stacks = stacks.filter((id) => id !== "script");
|
|
89
|
+
delete directories.script;
|
|
90
|
+
}
|
|
91
|
+
return { stacks, directories, skipped: skipped.filter((entry) => stacks.includes(entry.stack)) };
|
|
92
|
+
}
|
package/dist/stacks/support.js
CHANGED
|
@@ -10,6 +10,25 @@ export function checkKeys(stack, options, keys) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The `directory` option every stack takes: the folder of a monorepo the stack lives in.
|
|
15
|
+
* Returns it with forward slashes and no trailing slash, or undefined for the repository root.
|
|
16
|
+
*/
|
|
17
|
+
export function stackDirectory(stack, options) {
|
|
18
|
+
const value = options.directory;
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const key = `${CONFIG_FILE}: stack_options.${stack}.directory`;
|
|
22
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
23
|
+
throw new ConfigError(`${key} must be a folder name`);
|
|
24
|
+
const directory = value.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
25
|
+
if (directory === "" || directory === ".")
|
|
26
|
+
return undefined;
|
|
27
|
+
if (directory.startsWith("/") || /^[A-Za-z]:/.test(directory) || directory.split("/").includes("..")) {
|
|
28
|
+
throw new ConfigError(`${key} must be a folder inside the repository, such as backend or apps/api`);
|
|
29
|
+
}
|
|
30
|
+
return directory;
|
|
31
|
+
}
|
|
13
32
|
/** A list option; YAML numbers such as `[22, 24]` count as strings. */
|
|
14
33
|
export function stringList(stack, options, key) {
|
|
15
34
|
const value = options[key];
|
package/dist/templates.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { normalizeEol } from "./sync/hash.js";
|
|
3
3
|
/** Reads a bundled template with LF endings; works from src/ (tests) and dist/ (package). */
|
|
4
4
|
export function readTemplate(relativePath) {
|
|
5
5
|
return normalizeEol(readFileSync(new URL(`../templates/${relativePath}`, import.meta.url), "utf8"));
|
|
6
6
|
}
|
|
7
|
+
/** File names of the reusable workflows generated CI and release files can call. */
|
|
8
|
+
export const REUSABLE_WORKFLOW = /^(stack-[a-z]+|commitlint|release-please|repokeeper-check)\.yml$/;
|
|
9
|
+
/** Reads one of the reusable workflows shipped with the package, with LF endings. */
|
|
10
|
+
export function readWorkflow(file) {
|
|
11
|
+
return normalizeEol(readFileSync(new URL(`../.github/workflows/${file}`, import.meta.url), "utf8"));
|
|
12
|
+
}
|
|
13
|
+
/** Every reusable workflow shipped with the package, by file name. */
|
|
14
|
+
export function listWorkflows() {
|
|
15
|
+
return readdirSync(new URL("../.github/workflows/", import.meta.url))
|
|
16
|
+
.filter((name) => REUSABLE_WORKFLOW.test(name))
|
|
17
|
+
.sort();
|
|
18
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -6,11 +6,20 @@ export const TOOL_VERSIONS = {
|
|
|
6
6
|
lefthook: "2.1.14",
|
|
7
7
|
commitlintCli: "21.2.3",
|
|
8
8
|
commitlintConventional: "21.2.3",
|
|
9
|
+
semanticRelease: "25.0.9",
|
|
10
|
+
semanticReleaseChangelog: "7.0.0",
|
|
11
|
+
semanticReleaseGit: "11.0.1",
|
|
12
|
+
semanticReleaseGitlab: "13.3.3",
|
|
13
|
+
// 10.x needs conventional-changelog-writer 9, and semantic-release 25's notes generator bundles 8
|
|
14
|
+
conventionalCommitsPreset: "9.3.1",
|
|
15
|
+
renovate: "44.128.1",
|
|
9
16
|
};
|
|
10
17
|
/** Repository hosting the reusable workflows that generated CI files call. */
|
|
11
18
|
export const REUSABLE_REPO = "vannt-dev/repokeeper";
|
|
12
19
|
/** Moving tag callers pin to: the major version of this repokeeper (v0 during 0.x). */
|
|
13
20
|
export const WORKFLOW_REF = `v${PACKAGE_VERSION.split(".")[0]}`;
|
|
21
|
+
/** The tag of this release, for callers that pin to it instead of following the major tag. */
|
|
22
|
+
export const EXACT_WORKFLOW_REF = `v${PACKAGE_VERSION}`;
|
|
14
23
|
export function compareVersions(a, b) {
|
|
15
24
|
const pa = a.split(".").map(Number);
|
|
16
25
|
const pb = b.split(".").map(Number);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repokeeper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Keep every repository on one maintained standard: commits, git hooks, CI, releases, dependency updates and repo settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/",
|
|
11
|
-
"templates/"
|
|
11
|
+
"templates/",
|
|
12
|
+
".github/workflows/stack-*.yml",
|
|
13
|
+
".github/workflows/commitlint.yml",
|
|
14
|
+
".github/workflows/release-please.yml",
|
|
15
|
+
".github/workflows/repokeeper-check.yml"
|
|
12
16
|
],
|
|
13
17
|
"engines": {
|
|
14
18
|
"node": ">=22.12.0"
|