@bensandee/tooling 0.34.0 → 0.36.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/dist/bin.mjs +262 -453
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import JSON5 from "json5";
|
|
|
10
10
|
import { parse } from "jsonc-parser";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
|
|
13
|
-
import { isMap, isScalar,
|
|
13
|
+
import { isMap, isScalar, parse as parse$1, parseDocument, stringify, visit } from "yaml";
|
|
14
14
|
import picomatch from "picomatch";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
//#region src/types.ts
|
|
@@ -956,17 +956,82 @@ function runDockerPublish(executor, config) {
|
|
|
956
956
|
//#endregion
|
|
957
957
|
//#region src/utils/yaml-merge.ts
|
|
958
958
|
const IGNORE_PATTERN = "@bensandee/tooling:ignore";
|
|
959
|
+
const CUSTOM_START = "# @tooling:custom";
|
|
960
|
+
const CUSTOM_END = "# @tooling:endcustom";
|
|
959
961
|
const FORGEJO_SCHEMA_COMMENT = "# yaml-language-server: $schema=../../.vscode/forgejo-workflow.schema.json\n";
|
|
962
|
+
/** Extract custom blocks from a workflow file, recording their anchor lines. */
|
|
963
|
+
function extractCustomBlocks(content) {
|
|
964
|
+
const lines = content.split("\n");
|
|
965
|
+
const blocks = [];
|
|
966
|
+
let i = 0;
|
|
967
|
+
while (i < lines.length) if (lines[i].trim() === CUSTOM_START) {
|
|
968
|
+
let anchor = "";
|
|
969
|
+
for (let j = i - 1; j >= 0; j--) if (lines[j].trim() !== "") {
|
|
970
|
+
anchor = lines[j].trim();
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
const blockLines = [lines[i]];
|
|
974
|
+
i++;
|
|
975
|
+
while (i < lines.length && lines[i].trim() !== CUSTOM_END) {
|
|
976
|
+
blockLines.push(lines[i]);
|
|
977
|
+
i++;
|
|
978
|
+
}
|
|
979
|
+
if (i < lines.length) {
|
|
980
|
+
blockLines.push(lines[i]);
|
|
981
|
+
i++;
|
|
982
|
+
}
|
|
983
|
+
blocks.push({
|
|
984
|
+
anchor,
|
|
985
|
+
lines: blockLines
|
|
986
|
+
});
|
|
987
|
+
} else i++;
|
|
988
|
+
return blocks;
|
|
989
|
+
}
|
|
990
|
+
/** Remove custom blocks (markers + content) from a workflow string. */
|
|
991
|
+
function stripCustomBlocks(content) {
|
|
992
|
+
const lines = content.split("\n");
|
|
993
|
+
const result = [];
|
|
994
|
+
let inCustom = false;
|
|
995
|
+
for (const line of lines) {
|
|
996
|
+
if (line.trim() === CUSTOM_START) {
|
|
997
|
+
inCustom = true;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (inCustom && line.trim() === CUSTOM_END) {
|
|
1001
|
+
inCustom = false;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (!inCustom) result.push(line);
|
|
1005
|
+
}
|
|
1006
|
+
return result.join("\n");
|
|
1007
|
+
}
|
|
1008
|
+
/** Insert previously extracted custom blocks into freshly generated content, anchored by preceding line. */
|
|
1009
|
+
function insertCustomBlocks(generated, blocks) {
|
|
1010
|
+
if (blocks.length === 0) return generated;
|
|
1011
|
+
const lines = generated.split("\n");
|
|
1012
|
+
const insertions = [];
|
|
1013
|
+
for (const block of blocks) {
|
|
1014
|
+
let insertAfter = -1;
|
|
1015
|
+
for (let i = 0; i < lines.length; i++) if (lines[i].trim() === block.anchor) {
|
|
1016
|
+
insertAfter = i;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
if (insertAfter < 0) insertAfter = lines.length - 1;
|
|
1020
|
+
insertions.push({
|
|
1021
|
+
afterIndex: insertAfter,
|
|
1022
|
+
block
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
insertions.sort((a, b) => b.afterIndex - a.afterIndex);
|
|
1026
|
+
for (const { afterIndex, block } of insertions) lines.splice(afterIndex + 1, 0, ...block.lines);
|
|
1027
|
+
return lines.join("\n");
|
|
1028
|
+
}
|
|
960
1029
|
/** Prepend the Forgejo schema comment if it's not already present. No-op for GitHub. */
|
|
961
1030
|
function ensureSchemaComment(content, ci) {
|
|
962
1031
|
if (ci !== "forgejo") return content;
|
|
963
1032
|
if (content.includes("yaml-language-server")) return content;
|
|
964
1033
|
return FORGEJO_SCHEMA_COMMENT + content;
|
|
965
1034
|
}
|
|
966
|
-
/** Migrate content from old tooling binary name to new. */
|
|
967
|
-
function migrateToolingBinary(content) {
|
|
968
|
-
return content.replaceAll("pnpm exec tooling ", "pnpm exec bst ");
|
|
969
|
-
}
|
|
970
1035
|
/** Check if a YAML file has an opt-out comment in the first 10 lines. */
|
|
971
1036
|
function isToolingIgnored(content) {
|
|
972
1037
|
return content.split("\n", 10).some((line) => line.includes(IGNORE_PATTERN));
|
|
@@ -1011,14 +1076,42 @@ function mergeLefthookCommands(existing, requiredCommands) {
|
|
|
1011
1076
|
};
|
|
1012
1077
|
}
|
|
1013
1078
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* Normalize a workflow YAML string for comparison purposes.
|
|
1081
|
+
* Parses the YAML DOM to strip action versions from `uses:` values and remove all comments,
|
|
1082
|
+
* then re-serializes so that pinned hashes, older tags, or formatting differences
|
|
1083
|
+
* don't cause unnecessary overwrites.
|
|
1084
|
+
*/
|
|
1085
|
+
function normalizeWorkflow(content) {
|
|
1086
|
+
const doc = parseDocument(stripCustomBlocks(content).replaceAll(/node\s+packages\/tooling-cli\/dist\/bin\.mjs/g, "pnpm exec bst"));
|
|
1087
|
+
doc.comment = null;
|
|
1088
|
+
doc.commentBefore = null;
|
|
1089
|
+
visit(doc, {
|
|
1090
|
+
Node(_key, node) {
|
|
1091
|
+
if ("comment" in node) node.comment = null;
|
|
1092
|
+
if ("commentBefore" in node) node.commentBefore = null;
|
|
1093
|
+
},
|
|
1094
|
+
Pair(_key, node) {
|
|
1095
|
+
if (isScalar(node.key) && node.key.value === "uses" && isScalar(node.value) && typeof node.value.value === "string") node.value.value = node.value.value.replace(/@.*$/, "");
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
return doc.toString({
|
|
1099
|
+
lineWidth: 0,
|
|
1100
|
+
nullStr: ""
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
const DEV_BINARY_PATTERN = /node\s+packages\/tooling-cli\/dist\/bin\.mjs/;
|
|
1104
|
+
/**
|
|
1105
|
+
* If the existing file uses the dev binary path (`node packages/tooling-cli/dist/bin.mjs`),
|
|
1106
|
+
* substitute it back into the generated content so we don't overwrite it with `pnpm exec bst`.
|
|
1107
|
+
*/
|
|
1108
|
+
function preserveDevBinaryPath(generated, existing) {
|
|
1109
|
+
if (!existing) return generated;
|
|
1110
|
+
const match = DEV_BINARY_PATTERN.exec(existing);
|
|
1111
|
+
if (!match) return generated;
|
|
1112
|
+
return generated.replaceAll("pnpm exec bst", match[0]);
|
|
1020
1113
|
}
|
|
1021
|
-
/** Build a complete workflow YAML string from structured options. Single source of truth for
|
|
1114
|
+
/** Build a complete workflow YAML string from structured options. Single source of truth for workflow files. */
|
|
1022
1115
|
function buildWorkflowYaml(options) {
|
|
1023
1116
|
const doc = { name: options.name };
|
|
1024
1117
|
if (options.enableEmailNotifications) doc["enable-email-notifications"] = true;
|
|
@@ -1032,132 +1125,7 @@ function buildWorkflowYaml(options) {
|
|
|
1032
1125
|
return ensureSchemaComment(stringify(doc, {
|
|
1033
1126
|
lineWidth: 0,
|
|
1034
1127
|
nullStr: ""
|
|
1035
|
-
}), options.ci);
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Ensure required steps exist in a workflow job's steps array.
|
|
1039
|
-
* Only adds missing steps at the end — never modifies existing ones.
|
|
1040
|
-
* Returns unchanged content if the file has an opt-out comment or can't be parsed.
|
|
1041
|
-
*/
|
|
1042
|
-
function mergeWorkflowSteps(existing, jobName, requiredSteps) {
|
|
1043
|
-
if (isToolingIgnored(existing)) return {
|
|
1044
|
-
content: existing,
|
|
1045
|
-
changed: false
|
|
1046
|
-
};
|
|
1047
|
-
try {
|
|
1048
|
-
const doc = parseDocument(existing);
|
|
1049
|
-
const steps = doc.getIn([
|
|
1050
|
-
"jobs",
|
|
1051
|
-
jobName,
|
|
1052
|
-
"steps"
|
|
1053
|
-
]);
|
|
1054
|
-
if (!isSeq(steps)) return {
|
|
1055
|
-
content: existing,
|
|
1056
|
-
changed: false
|
|
1057
|
-
};
|
|
1058
|
-
let changed = false;
|
|
1059
|
-
for (const { match, step } of requiredSteps) if (!steps.items.some((item) => {
|
|
1060
|
-
if (!isMap(item)) return false;
|
|
1061
|
-
if (match.run) {
|
|
1062
|
-
const run = item.get("run");
|
|
1063
|
-
return typeof run === "string" && run.includes(match.run);
|
|
1064
|
-
}
|
|
1065
|
-
if (match.uses) {
|
|
1066
|
-
const uses = item.get("uses");
|
|
1067
|
-
return typeof uses === "string" && uses.startsWith(match.uses);
|
|
1068
|
-
}
|
|
1069
|
-
return false;
|
|
1070
|
-
})) {
|
|
1071
|
-
steps.add(doc.createNode(step));
|
|
1072
|
-
changed = true;
|
|
1073
|
-
}
|
|
1074
|
-
return {
|
|
1075
|
-
content: changed ? doc.toString() : existing,
|
|
1076
|
-
changed
|
|
1077
|
-
};
|
|
1078
|
-
} catch {
|
|
1079
|
-
return {
|
|
1080
|
-
content: existing,
|
|
1081
|
-
changed: false
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
/**
|
|
1086
|
-
* Add a job to an existing workflow YAML if it doesn't already exist.
|
|
1087
|
-
* Returns unchanged content if the job already exists, the file has an opt-out comment,
|
|
1088
|
-
* or the document can't be parsed.
|
|
1089
|
-
*/
|
|
1090
|
-
/**
|
|
1091
|
-
* Ensure a `concurrency` block exists at the workflow top level.
|
|
1092
|
-
* Adds it if missing — never modifies an existing one.
|
|
1093
|
-
* Returns unchanged content if the file has an opt-out comment or can't be parsed.
|
|
1094
|
-
*/
|
|
1095
|
-
/**
|
|
1096
|
-
* Ensure `on.push` has `tags-ignore: ["**"]` so tag pushes don't trigger CI.
|
|
1097
|
-
* Only adds the filter when `on.push` exists and `tags-ignore` is absent.
|
|
1098
|
-
*/
|
|
1099
|
-
function ensureWorkflowTagsIgnore(existing) {
|
|
1100
|
-
if (isToolingIgnored(existing)) return {
|
|
1101
|
-
content: existing,
|
|
1102
|
-
changed: false
|
|
1103
|
-
};
|
|
1104
|
-
try {
|
|
1105
|
-
const doc = parseDocument(existing);
|
|
1106
|
-
const on = doc.get("on");
|
|
1107
|
-
if (!isMap(on)) return {
|
|
1108
|
-
content: existing,
|
|
1109
|
-
changed: false
|
|
1110
|
-
};
|
|
1111
|
-
const push = on.get("push");
|
|
1112
|
-
if (!isMap(push)) return {
|
|
1113
|
-
content: existing,
|
|
1114
|
-
changed: false
|
|
1115
|
-
};
|
|
1116
|
-
if (push.has("tags-ignore")) return {
|
|
1117
|
-
content: existing,
|
|
1118
|
-
changed: false
|
|
1119
|
-
};
|
|
1120
|
-
push.set("tags-ignore", ["**"]);
|
|
1121
|
-
return {
|
|
1122
|
-
content: doc.toString(),
|
|
1123
|
-
changed: true
|
|
1124
|
-
};
|
|
1125
|
-
} catch {
|
|
1126
|
-
return {
|
|
1127
|
-
content: existing,
|
|
1128
|
-
changed: false
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
function ensureWorkflowConcurrency(existing, concurrency) {
|
|
1133
|
-
if (isToolingIgnored(existing)) return {
|
|
1134
|
-
content: existing,
|
|
1135
|
-
changed: false
|
|
1136
|
-
};
|
|
1137
|
-
try {
|
|
1138
|
-
const doc = parseDocument(existing);
|
|
1139
|
-
if (doc.has("concurrency")) return {
|
|
1140
|
-
content: existing,
|
|
1141
|
-
changed: false
|
|
1142
|
-
};
|
|
1143
|
-
doc.set("concurrency", concurrency);
|
|
1144
|
-
const contents = doc.contents;
|
|
1145
|
-
if (isMap(contents)) {
|
|
1146
|
-
const items = contents.items;
|
|
1147
|
-
const nameIdx = items.findIndex((p) => isScalar(p.key) && p.key.value === "name");
|
|
1148
|
-
const concPair = items.pop();
|
|
1149
|
-
if (concPair) items.splice(nameIdx + 1, 0, concPair);
|
|
1150
|
-
}
|
|
1151
|
-
return {
|
|
1152
|
-
content: doc.toString(),
|
|
1153
|
-
changed: true
|
|
1154
|
-
};
|
|
1155
|
-
} catch {
|
|
1156
|
-
return {
|
|
1157
|
-
content: existing,
|
|
1158
|
-
changed: false
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1128
|
+
}).replace(/^(?=\S)/gm, (match, offset) => offset === 0 ? match : `\n${match}`), options.ci);
|
|
1161
1129
|
}
|
|
1162
1130
|
//#endregion
|
|
1163
1131
|
//#region src/generators/ci-utils.ts
|
|
@@ -1177,38 +1145,24 @@ function computeNodeVersionYaml(ctx) {
|
|
|
1177
1145
|
//#region src/generators/publish-ci.ts
|
|
1178
1146
|
function publishSteps(nodeVersionYaml) {
|
|
1179
1147
|
return [
|
|
1180
|
-
{
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
},
|
|
1199
|
-
{
|
|
1200
|
-
match: { run: "docker:publish" },
|
|
1201
|
-
step: {
|
|
1202
|
-
name: "Publish Docker images",
|
|
1203
|
-
env: {
|
|
1204
|
-
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
1205
|
-
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE"),
|
|
1206
|
-
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
1207
|
-
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD")
|
|
1208
|
-
},
|
|
1209
|
-
run: "pnpm exec bst docker:publish"
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1148
|
+
{ step: { uses: "actions/checkout@v6" } },
|
|
1149
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
1150
|
+
{ step: {
|
|
1151
|
+
uses: "actions/setup-node@v6",
|
|
1152
|
+
with: nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" }
|
|
1153
|
+
} },
|
|
1154
|
+
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
1155
|
+
{ step: {
|
|
1156
|
+
name: "Publish Docker images",
|
|
1157
|
+
env: {
|
|
1158
|
+
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
1159
|
+
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE"),
|
|
1160
|
+
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
1161
|
+
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
1162
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
1163
|
+
},
|
|
1164
|
+
run: "pnpm exec bst docker:publish"
|
|
1165
|
+
} }
|
|
1212
1166
|
];
|
|
1213
1167
|
}
|
|
1214
1168
|
const DockerMapSchema = z.object({ docker: z.record(z.string(), z.unknown()).optional() });
|
|
@@ -1257,59 +1211,26 @@ async function generateDeployCi(ctx) {
|
|
|
1257
1211
|
jobName: "publish",
|
|
1258
1212
|
steps
|
|
1259
1213
|
});
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
action: "updated",
|
|
1270
|
-
description: "Migrated tooling binary name in publish workflow"
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
return {
|
|
1274
|
-
filePath: workflowPath,
|
|
1275
|
-
action: "skipped",
|
|
1276
|
-
description: "Publish workflow already up to date"
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
const merged = mergeWorkflowSteps(existing, "publish", toRequiredSteps(steps));
|
|
1280
|
-
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
1281
|
-
if (!merged.changed) {
|
|
1282
|
-
if (withComment !== raw) {
|
|
1283
|
-
ctx.write(workflowPath, withComment);
|
|
1284
|
-
return {
|
|
1285
|
-
filePath: workflowPath,
|
|
1286
|
-
action: "updated",
|
|
1287
|
-
description: existing !== raw ? "Migrated tooling binary name in publish workflow" : "Added schema comment to publish workflow"
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
return {
|
|
1291
|
-
filePath: workflowPath,
|
|
1292
|
-
action: "skipped",
|
|
1293
|
-
description: "Existing publish workflow preserved"
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
ctx.write(workflowPath, withComment);
|
|
1297
|
-
return {
|
|
1298
|
-
filePath: workflowPath,
|
|
1299
|
-
action: "updated",
|
|
1300
|
-
description: "Added missing steps to publish workflow"
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
return {
|
|
1214
|
+
const alreadyExists = ctx.exists(workflowPath);
|
|
1215
|
+
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
1216
|
+
if (existing) {
|
|
1217
|
+
if (isToolingIgnored(existing)) return {
|
|
1218
|
+
filePath: workflowPath,
|
|
1219
|
+
action: "skipped",
|
|
1220
|
+
description: "Publish workflow has ignore comment"
|
|
1221
|
+
};
|
|
1222
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
1304
1223
|
filePath: workflowPath,
|
|
1305
1224
|
action: "skipped",
|
|
1306
1225
|
description: "Publish workflow already up to date"
|
|
1307
1226
|
};
|
|
1308
1227
|
}
|
|
1309
|
-
|
|
1228
|
+
const withDevPath = preserveDevBinaryPath(content, existing);
|
|
1229
|
+
const final = existing ? insertCustomBlocks(withDevPath, extractCustomBlocks(existing)) : withDevPath;
|
|
1230
|
+
ctx.write(workflowPath, final);
|
|
1310
1231
|
return {
|
|
1311
1232
|
filePath: workflowPath,
|
|
1312
|
-
action: "created",
|
|
1233
|
+
action: alreadyExists ? "updated" : "created",
|
|
1313
1234
|
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions publish workflow`
|
|
1314
1235
|
};
|
|
1315
1236
|
}
|
|
@@ -1577,7 +1498,7 @@ function getAddedDevDepNames(config) {
|
|
|
1577
1498
|
const deps = { ...ROOT_DEV_DEPS };
|
|
1578
1499
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
1579
1500
|
deps["@bensandee/config"] = "0.9.1";
|
|
1580
|
-
deps["@bensandee/tooling"] = "0.
|
|
1501
|
+
deps["@bensandee/tooling"] = "0.36.0";
|
|
1581
1502
|
if (config.formatter === "oxfmt") deps["oxfmt"] = {
|
|
1582
1503
|
"@changesets/cli": "2.30.0",
|
|
1583
1504
|
"@release-it/bumper": "7.0.5",
|
|
@@ -1632,7 +1553,7 @@ async function generatePackageJson(ctx) {
|
|
|
1632
1553
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1633
1554
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1634
1555
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
|
|
1635
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1556
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.36.0";
|
|
1636
1557
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1637
1558
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1638
1559
|
"@changesets/cli": "2.30.0",
|
|
@@ -1680,10 +1601,6 @@ async function generatePackageJson(ctx) {
|
|
|
1680
1601
|
changes.push("set type: \"module\"");
|
|
1681
1602
|
}
|
|
1682
1603
|
const existingScripts = pkg.scripts ?? {};
|
|
1683
|
-
for (const [key, value] of Object.entries(existingScripts)) if (typeof value === "string" && value.includes("pnpm exec tooling ")) {
|
|
1684
|
-
existingScripts[key] = migrateToolingBinary(value);
|
|
1685
|
-
changes.push(`migrated script: ${key}`);
|
|
1686
|
-
}
|
|
1687
1604
|
for (const [key, value] of Object.entries(allScripts)) if (!(key in existingScripts)) {
|
|
1688
1605
|
existingScripts[key] = value;
|
|
1689
1606
|
changes.push(`added script: ${key}`);
|
|
@@ -2170,41 +2087,58 @@ async function generateGitignore(ctx) {
|
|
|
2170
2087
|
}
|
|
2171
2088
|
//#endregion
|
|
2172
2089
|
//#region src/generators/ci.ts
|
|
2090
|
+
/** Build the release step for the check job (changesets strategy). */
|
|
2091
|
+
function changesetsReleaseStep(ci, publishesNpm) {
|
|
2092
|
+
if (ci === "github") return { step: {
|
|
2093
|
+
uses: "changesets/action@v1",
|
|
2094
|
+
if: "github.ref == 'refs/heads/main'",
|
|
2095
|
+
with: {
|
|
2096
|
+
publish: "pnpm changeset publish",
|
|
2097
|
+
version: "pnpm changeset version"
|
|
2098
|
+
},
|
|
2099
|
+
env: {
|
|
2100
|
+
GITHUB_TOKEN: actionsExpr("github.token"),
|
|
2101
|
+
...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
2102
|
+
}
|
|
2103
|
+
} };
|
|
2104
|
+
return { step: {
|
|
2105
|
+
name: "Release",
|
|
2106
|
+
if: "github.ref == 'refs/heads/main'",
|
|
2107
|
+
env: {
|
|
2108
|
+
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2109
|
+
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2110
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
2111
|
+
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") },
|
|
2112
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2113
|
+
},
|
|
2114
|
+
run: "pnpm exec bst release:changesets"
|
|
2115
|
+
} };
|
|
2116
|
+
}
|
|
2173
2117
|
const CI_CONCURRENCY = {
|
|
2174
2118
|
group: `ci-${actionsExpr("github.ref")}`,
|
|
2175
2119
|
"cancel-in-progress": actionsExpr("github.ref != 'refs/heads/main'")
|
|
2176
2120
|
};
|
|
2177
|
-
function checkSteps(nodeVersionYaml) {
|
|
2121
|
+
function checkSteps(nodeVersionYaml, publishesNpm) {
|
|
2122
|
+
const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
|
|
2178
2123
|
return [
|
|
2179
|
-
{
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
},
|
|
2183
|
-
{
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
uses: "actions/setup-node@v6",
|
|
2191
|
-
with: {
|
|
2192
|
-
...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2193
|
-
cache: "pnpm"
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
},
|
|
2197
|
-
{
|
|
2198
|
-
match: { run: "pnpm install" },
|
|
2199
|
-
step: { run: "pnpm install --frozen-lockfile" }
|
|
2200
|
-
},
|
|
2201
|
-
{
|
|
2202
|
-
match: { run: "check" },
|
|
2203
|
-
step: {
|
|
2204
|
-
name: "Run all checks",
|
|
2205
|
-
run: "pnpm ci:check"
|
|
2124
|
+
{ step: {
|
|
2125
|
+
uses: "actions/checkout@v6",
|
|
2126
|
+
with: { "fetch-depth": 0 }
|
|
2127
|
+
} },
|
|
2128
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
2129
|
+
{ step: {
|
|
2130
|
+
uses: "actions/setup-node@v6",
|
|
2131
|
+
with: {
|
|
2132
|
+
...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2133
|
+
cache: "pnpm",
|
|
2134
|
+
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
2206
2135
|
}
|
|
2207
|
-
}
|
|
2136
|
+
} },
|
|
2137
|
+
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
2138
|
+
{ step: {
|
|
2139
|
+
name: "Run all checks",
|
|
2140
|
+
run: "pnpm ci:check"
|
|
2141
|
+
} }
|
|
2208
2142
|
];
|
|
2209
2143
|
}
|
|
2210
2144
|
/** Resolve the CI workflow filename based on release strategy. */
|
|
@@ -2220,7 +2154,9 @@ async function generateCi(ctx) {
|
|
|
2220
2154
|
const isGitHub = ctx.config.ci === "github";
|
|
2221
2155
|
const isForgejo = !isGitHub;
|
|
2222
2156
|
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
2223
|
-
const
|
|
2157
|
+
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2158
|
+
const publishesNpm = ctx.config.publishNpm === true;
|
|
2159
|
+
const steps = [...checkSteps(nodeVersionYaml, isChangesets && publishesNpm), ...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : []];
|
|
2224
2160
|
const content = buildWorkflowYaml({
|
|
2225
2161
|
ci: ctx.config.ci,
|
|
2226
2162
|
name: "CI",
|
|
@@ -2237,42 +2173,26 @@ async function generateCi(ctx) {
|
|
|
2237
2173
|
steps
|
|
2238
2174
|
});
|
|
2239
2175
|
const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
if (isChangesets) {
|
|
2250
|
-
const withConcurrency = ensureWorkflowConcurrency(result.content, CI_CONCURRENCY);
|
|
2251
|
-
result = {
|
|
2252
|
-
content: withConcurrency.content,
|
|
2253
|
-
changed: result.changed || withConcurrency.changed
|
|
2254
|
-
};
|
|
2255
|
-
}
|
|
2256
|
-
const withComment = ensureSchemaComment(result.content, isGitHub ? "github" : "forgejo");
|
|
2257
|
-
if (result.changed || withComment !== result.content) {
|
|
2258
|
-
ctx.write(filePath, withComment);
|
|
2259
|
-
return {
|
|
2260
|
-
filePath,
|
|
2261
|
-
action: "updated",
|
|
2262
|
-
description: "Added missing steps to CI workflow"
|
|
2263
|
-
};
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
return {
|
|
2176
|
+
const alreadyExists = ctx.exists(filePath);
|
|
2177
|
+
const existing = alreadyExists ? ctx.read(filePath) : void 0;
|
|
2178
|
+
if (existing) {
|
|
2179
|
+
if (isToolingIgnored(existing)) return {
|
|
2180
|
+
filePath,
|
|
2181
|
+
action: "skipped",
|
|
2182
|
+
description: "CI workflow has ignore comment"
|
|
2183
|
+
};
|
|
2184
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
2267
2185
|
filePath,
|
|
2268
2186
|
action: "skipped",
|
|
2269
2187
|
description: "CI workflow already up to date"
|
|
2270
2188
|
};
|
|
2271
2189
|
}
|
|
2272
|
-
|
|
2190
|
+
const withDevPath = preserveDevBinaryPath(content, existing);
|
|
2191
|
+
const final = existing ? insertCustomBlocks(withDevPath, extractCustomBlocks(existing)) : withDevPath;
|
|
2192
|
+
ctx.write(filePath, final);
|
|
2273
2193
|
return {
|
|
2274
2194
|
filePath,
|
|
2275
|
-
action: "created",
|
|
2195
|
+
action: alreadyExists ? "updated" : "created",
|
|
2276
2196
|
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions CI workflow`
|
|
2277
2197
|
};
|
|
2278
2198
|
}
|
|
@@ -2749,47 +2669,33 @@ async function generateChangesets(ctx) {
|
|
|
2749
2669
|
function commonSteps(nodeVersionYaml, publishesNpm) {
|
|
2750
2670
|
const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
|
|
2751
2671
|
return [
|
|
2752
|
-
{
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
{
|
|
2764
|
-
match: { uses: "actions/setup-node" },
|
|
2765
|
-
step: {
|
|
2766
|
-
uses: "actions/setup-node@v6",
|
|
2767
|
-
with: {
|
|
2768
|
-
...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2769
|
-
cache: "pnpm",
|
|
2770
|
-
...publishesNpm && { "registry-url": "https://registry.npmjs.org" }
|
|
2771
|
-
}
|
|
2672
|
+
{ step: {
|
|
2673
|
+
uses: "actions/checkout@v6",
|
|
2674
|
+
with: { "fetch-depth": 0 }
|
|
2675
|
+
} },
|
|
2676
|
+
{ step: { uses: "pnpm/action-setup@v5" } },
|
|
2677
|
+
{ step: {
|
|
2678
|
+
uses: "actions/setup-node@v6",
|
|
2679
|
+
with: {
|
|
2680
|
+
...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
|
|
2681
|
+
cache: "pnpm",
|
|
2682
|
+
...publishesNpm && { "registry-url": actionsExpr("vars.NPM_REGISTRY_URL || 'https://registry.npmjs.org'") }
|
|
2772
2683
|
}
|
|
2773
|
-
},
|
|
2774
|
-
{
|
|
2775
|
-
match: { run: "pnpm install" },
|
|
2776
|
-
step: { run: "pnpm install --frozen-lockfile" }
|
|
2777
|
-
}
|
|
2684
|
+
} },
|
|
2685
|
+
{ step: { run: "pnpm install --frozen-lockfile" } }
|
|
2778
2686
|
];
|
|
2779
2687
|
}
|
|
2780
2688
|
function releaseItSteps(ci, nodeVersionYaml, publishesNpm) {
|
|
2781
2689
|
const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
|
|
2782
2690
|
const npmEnv = publishesNpm ? { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") } : {};
|
|
2783
|
-
return [...commonSteps(nodeVersionYaml, publishesNpm), {
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
...npmEnv
|
|
2790
|
-
}
|
|
2691
|
+
return [...commonSteps(nodeVersionYaml, publishesNpm), { step: {
|
|
2692
|
+
run: "pnpm release-it --ci",
|
|
2693
|
+
env: {
|
|
2694
|
+
...tokenEnv,
|
|
2695
|
+
...npmEnv,
|
|
2696
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2791
2697
|
}
|
|
2792
|
-
}];
|
|
2698
|
+
} }];
|
|
2793
2699
|
}
|
|
2794
2700
|
/** Build the workflow_dispatch trigger with optional inputs for the simple strategy. */
|
|
2795
2701
|
function simpleWorkflowDispatchTrigger() {
|
|
@@ -2827,70 +2733,36 @@ function simpleReleaseCommand() {
|
|
|
2827
2733
|
].join("\n");
|
|
2828
2734
|
}
|
|
2829
2735
|
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
2830
|
-
const releaseStep = {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
env: ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
2736
|
+
const releaseStep = { step: {
|
|
2737
|
+
name: "Release",
|
|
2738
|
+
env: {
|
|
2739
|
+
...ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
2835
2740
|
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2836
2741
|
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2837
2742
|
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2838
2743
|
},
|
|
2839
|
-
|
|
2840
|
-
}
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
};
|
|
2744
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2745
|
+
},
|
|
2746
|
+
run: simpleReleaseCommand()
|
|
2747
|
+
} };
|
|
2748
|
+
const dockerStep = { step: {
|
|
2749
|
+
name: "Publish Docker images",
|
|
2750
|
+
if: "success()",
|
|
2751
|
+
env: {
|
|
2752
|
+
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
2753
|
+
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE"),
|
|
2754
|
+
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
2755
|
+
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
2756
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2757
|
+
},
|
|
2758
|
+
run: "pnpm exec bst docker:publish"
|
|
2759
|
+
} };
|
|
2856
2760
|
return [
|
|
2857
2761
|
...commonSteps(nodeVersionYaml, publishesNpm),
|
|
2858
2762
|
releaseStep,
|
|
2859
2763
|
...hasDocker ? [dockerStep] : []
|
|
2860
2764
|
];
|
|
2861
2765
|
}
|
|
2862
|
-
/** Build the required release step for the check job (changesets). */
|
|
2863
|
-
function changesetsReleaseStep(ci, publishesNpm) {
|
|
2864
|
-
if (ci === "github") return {
|
|
2865
|
-
match: { uses: "changesets/action" },
|
|
2866
|
-
step: {
|
|
2867
|
-
uses: "changesets/action@v1",
|
|
2868
|
-
if: "github.ref == 'refs/heads/main'",
|
|
2869
|
-
with: {
|
|
2870
|
-
publish: "pnpm changeset publish",
|
|
2871
|
-
version: "pnpm changeset version"
|
|
2872
|
-
},
|
|
2873
|
-
env: {
|
|
2874
|
-
GITHUB_TOKEN: actionsExpr("github.token"),
|
|
2875
|
-
...publishesNpm && { NPM_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
return {
|
|
2880
|
-
match: { run: "release:changesets" },
|
|
2881
|
-
step: {
|
|
2882
|
-
name: "Release",
|
|
2883
|
-
if: "github.ref == 'refs/heads/main'",
|
|
2884
|
-
env: {
|
|
2885
|
-
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2886
|
-
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2887
|
-
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
2888
|
-
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
2889
|
-
},
|
|
2890
|
-
run: "pnpm exec bst release:changesets"
|
|
2891
|
-
}
|
|
2892
|
-
};
|
|
2893
|
-
}
|
|
2894
2766
|
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
2895
2767
|
switch (strategy) {
|
|
2896
2768
|
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
@@ -2898,40 +2770,6 @@ function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker) {
|
|
|
2898
2770
|
default: return null;
|
|
2899
2771
|
}
|
|
2900
2772
|
}
|
|
2901
|
-
function generateChangesetsReleaseCi(ctx, publishesNpm) {
|
|
2902
|
-
const ciPath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
2903
|
-
const raw = ctx.read(ciPath);
|
|
2904
|
-
if (!raw) return {
|
|
2905
|
-
filePath: ciPath,
|
|
2906
|
-
action: "skipped",
|
|
2907
|
-
description: "CI workflow not found — run check generator first"
|
|
2908
|
-
};
|
|
2909
|
-
const existing = migrateToolingBinary(raw);
|
|
2910
|
-
const merged = mergeWorkflowSteps(existing, "check", [changesetsReleaseStep(ctx.config.ci, publishesNpm)]);
|
|
2911
|
-
if (!merged.changed) {
|
|
2912
|
-
if (existing !== raw) {
|
|
2913
|
-
const withComment = ensureSchemaComment(existing, ctx.config.ci);
|
|
2914
|
-
ctx.write(ciPath, withComment);
|
|
2915
|
-
return {
|
|
2916
|
-
filePath: ciPath,
|
|
2917
|
-
action: "updated",
|
|
2918
|
-
description: "Migrated tooling binary name in CI workflow"
|
|
2919
|
-
};
|
|
2920
|
-
}
|
|
2921
|
-
return {
|
|
2922
|
-
filePath: ciPath,
|
|
2923
|
-
action: "skipped",
|
|
2924
|
-
description: "Release step in CI workflow already up to date"
|
|
2925
|
-
};
|
|
2926
|
-
}
|
|
2927
|
-
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2928
|
-
ctx.write(ciPath, withComment);
|
|
2929
|
-
return {
|
|
2930
|
-
filePath: ciPath,
|
|
2931
|
-
action: "updated",
|
|
2932
|
-
description: "Added release step to CI workflow"
|
|
2933
|
-
};
|
|
2934
|
-
}
|
|
2935
2773
|
async function generateReleaseCi(ctx) {
|
|
2936
2774
|
const filePath = "release-ci";
|
|
2937
2775
|
if (ctx.config.releaseStrategy === "none" || ctx.config.ci === "none") return {
|
|
@@ -2940,7 +2778,11 @@ async function generateReleaseCi(ctx) {
|
|
|
2940
2778
|
description: "Release CI workflow not applicable"
|
|
2941
2779
|
};
|
|
2942
2780
|
const publishesNpm = ctx.config.publishNpm === true;
|
|
2943
|
-
if (ctx.config.releaseStrategy === "changesets") return
|
|
2781
|
+
if (ctx.config.releaseStrategy === "changesets") return {
|
|
2782
|
+
filePath,
|
|
2783
|
+
action: "skipped",
|
|
2784
|
+
description: "Release step included in CI workflow"
|
|
2785
|
+
};
|
|
2944
2786
|
const isGitHub = ctx.config.ci === "github";
|
|
2945
2787
|
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
2946
2788
|
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
@@ -2960,59 +2802,26 @@ async function generateReleaseCi(ctx) {
|
|
|
2960
2802
|
jobName: "release",
|
|
2961
2803
|
steps
|
|
2962
2804
|
});
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
action: "updated",
|
|
2973
|
-
description: "Migrated tooling binary name in release workflow"
|
|
2974
|
-
};
|
|
2975
|
-
}
|
|
2976
|
-
return {
|
|
2977
|
-
filePath: workflowPath,
|
|
2978
|
-
action: "skipped",
|
|
2979
|
-
description: "Release workflow already up to date"
|
|
2980
|
-
};
|
|
2981
|
-
}
|
|
2982
|
-
const merged = mergeWorkflowSteps(existing, "release", toRequiredSteps(steps));
|
|
2983
|
-
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2984
|
-
if (!merged.changed) {
|
|
2985
|
-
if (withComment !== raw) {
|
|
2986
|
-
ctx.write(workflowPath, withComment);
|
|
2987
|
-
return {
|
|
2988
|
-
filePath: workflowPath,
|
|
2989
|
-
action: "updated",
|
|
2990
|
-
description: existing !== raw ? "Migrated tooling binary name in release workflow" : "Added schema comment to release workflow"
|
|
2991
|
-
};
|
|
2992
|
-
}
|
|
2993
|
-
return {
|
|
2994
|
-
filePath: workflowPath,
|
|
2995
|
-
action: "skipped",
|
|
2996
|
-
description: "Existing release workflow preserved"
|
|
2997
|
-
};
|
|
2998
|
-
}
|
|
2999
|
-
ctx.write(workflowPath, withComment);
|
|
3000
|
-
return {
|
|
3001
|
-
filePath: workflowPath,
|
|
3002
|
-
action: "updated",
|
|
3003
|
-
description: "Added missing steps to release workflow"
|
|
3004
|
-
};
|
|
3005
|
-
}
|
|
3006
|
-
return {
|
|
2805
|
+
const alreadyExists = ctx.exists(workflowPath);
|
|
2806
|
+
const existing = alreadyExists ? ctx.read(workflowPath) : void 0;
|
|
2807
|
+
if (existing) {
|
|
2808
|
+
if (isToolingIgnored(existing)) return {
|
|
2809
|
+
filePath: workflowPath,
|
|
2810
|
+
action: "skipped",
|
|
2811
|
+
description: "Release workflow has ignore comment"
|
|
2812
|
+
};
|
|
2813
|
+
if (normalizeWorkflow(existing) === normalizeWorkflow(content)) return {
|
|
3007
2814
|
filePath: workflowPath,
|
|
3008
2815
|
action: "skipped",
|
|
3009
2816
|
description: "Release workflow already up to date"
|
|
3010
2817
|
};
|
|
3011
2818
|
}
|
|
3012
|
-
|
|
2819
|
+
const withDevPath = preserveDevBinaryPath(content, existing);
|
|
2820
|
+
const final = existing ? insertCustomBlocks(withDevPath, extractCustomBlocks(existing)) : withDevPath;
|
|
2821
|
+
ctx.write(workflowPath, final);
|
|
3013
2822
|
return {
|
|
3014
2823
|
filePath: workflowPath,
|
|
3015
|
-
action: "created",
|
|
2824
|
+
action: alreadyExists ? "updated" : "created",
|
|
3016
2825
|
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
3017
2826
|
};
|
|
3018
2827
|
}
|
|
@@ -3308,7 +3117,7 @@ function generateMigratePrompt(results, config, detected) {
|
|
|
3308
3117
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3309
3118
|
sections.push("# Migration Prompt");
|
|
3310
3119
|
sections.push("");
|
|
3311
|
-
sections.push(`_Generated by \`@bensandee/tooling@0.
|
|
3120
|
+
sections.push(`_Generated by \`@bensandee/tooling@0.36.0 repo:sync\` on ${timestamp}_`);
|
|
3312
3121
|
sections.push("");
|
|
3313
3122
|
sections.push("The following prompt was generated by `@bensandee/tooling repo:sync`. Paste it into Claude Code or another AI assistant to finish migrating this repository.");
|
|
3314
3123
|
sections.push("");
|
|
@@ -5181,7 +4990,7 @@ const dockerCheckCommand = defineCommand({
|
|
|
5181
4990
|
const main = defineCommand({
|
|
5182
4991
|
meta: {
|
|
5183
4992
|
name: "bst",
|
|
5184
|
-
version: "0.
|
|
4993
|
+
version: "0.36.0",
|
|
5185
4994
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
5186
4995
|
},
|
|
5187
4996
|
subCommands: {
|
|
@@ -5197,7 +5006,7 @@ const main = defineCommand({
|
|
|
5197
5006
|
"docker:check": dockerCheckCommand
|
|
5198
5007
|
}
|
|
5199
5008
|
});
|
|
5200
|
-
console.log(`@bensandee/tooling v0.
|
|
5009
|
+
console.log(`@bensandee/tooling v0.36.0`);
|
|
5201
5010
|
async function run() {
|
|
5202
5011
|
await runMain(main);
|
|
5203
5012
|
process.exit(process.exitCode ?? 0);
|