@reddoorla/maintenance 0.4.0 → 0.5.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/cli/bin.js CHANGED
@@ -904,6 +904,93 @@ function templatesByName(which) {
904
904
  return ALL_TEMPLATES.filter((t) => which.includes(t.config));
905
905
  }
906
906
 
907
+ // src/recipes/sync-configs/gitignore.ts
908
+ var MANAGED_MARKER = "# canonical entries from @reddoorla/maintenance sync-configs";
909
+ var CANONICAL_GITIGNORE_ENTRIES = [
910
+ "node_modules/",
911
+ "build/",
912
+ "dist/",
913
+ ".svelte-kit/",
914
+ "coverage/",
915
+ ".vitest-cache/",
916
+ "playwright-report/",
917
+ "test-results/",
918
+ ".lighthouseci/",
919
+ ".tsbuildinfo",
920
+ ".env",
921
+ ".env.*",
922
+ "!.env.example",
923
+ ".DS_Store",
924
+ "*.log",
925
+ ".vercel/",
926
+ ".netlify/"
927
+ ];
928
+ function stripLeadingSlash(s) {
929
+ return s.startsWith("/") ? s.slice(1) : s;
930
+ }
931
+ function stripTrailingSlash(s) {
932
+ return s.endsWith("/") ? s.slice(0, -1) : s;
933
+ }
934
+ function normalizePresence(line) {
935
+ return stripTrailingSlash(stripLeadingSlash(line.trim()));
936
+ }
937
+ function presentSet(existing) {
938
+ const set = /* @__PURE__ */ new Set();
939
+ for (const raw of existing.split(/\r?\n/)) {
940
+ const trimmed = raw.trim();
941
+ if (!trimmed) continue;
942
+ if (trimmed.startsWith("#")) continue;
943
+ set.add(normalizePresence(trimmed));
944
+ }
945
+ return set;
946
+ }
947
+ function mergeGitignore(existing, canonical) {
948
+ if (existing === null) {
949
+ const body = [MANAGED_MARKER, ...canonical].join("\n") + "\n";
950
+ return { content: body, added: [...canonical] };
951
+ }
952
+ const present = presentSet(existing);
953
+ const added = [];
954
+ for (const entry of canonical) {
955
+ const norm = normalizePresence(entry);
956
+ if (!present.has(norm)) {
957
+ added.push(entry);
958
+ present.add(norm);
959
+ }
960
+ }
961
+ if (added.length === 0) {
962
+ return { content: existing, added: [] };
963
+ }
964
+ let base = existing;
965
+ if (!base.endsWith("\n")) base += "\n";
966
+ const block = ["", MANAGED_MARKER, ...added].join("\n") + "\n";
967
+ return { content: base + block, added };
968
+ }
969
+ function findTrackedArtifacts(tracked, canonical) {
970
+ const dirEntries = [];
971
+ for (const raw of canonical) {
972
+ const t = raw.trim();
973
+ if (!t) continue;
974
+ if (t.startsWith("!")) continue;
975
+ if (/[*?[]/.test(t)) continue;
976
+ const noLead = stripLeadingSlash(t);
977
+ if (!noLead.endsWith("/")) continue;
978
+ const name = stripTrailingSlash(noLead);
979
+ if (!name) continue;
980
+ dirEntries.push(name);
981
+ }
982
+ const matched = [];
983
+ for (const path of tracked) {
984
+ for (const dir of dirEntries) {
985
+ if (path === dir || path.startsWith(dir + "/")) {
986
+ matched.push(path);
987
+ break;
988
+ }
989
+ }
990
+ }
991
+ return matched;
992
+ }
993
+
907
994
  // src/util/git.ts
908
995
  import { execFile } from "child_process";
909
996
  import { promisify } from "util";
@@ -926,6 +1013,14 @@ async function createBranch(cwd, name) {
926
1013
  async function stageAll(cwd) {
927
1014
  await git(cwd, ["add", "-A"]);
928
1015
  }
1016
+ async function listTrackedFiles(cwd) {
1017
+ const { stdout } = await git(cwd, ["ls-files"]);
1018
+ return stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
1019
+ }
1020
+ async function removeFromIndex(cwd, paths) {
1021
+ if (paths.length === 0) return;
1022
+ await git(cwd, ["rm", "-r", "--cached", "--", ...paths]);
1023
+ }
929
1024
  async function commit(cwd, message) {
930
1025
  await stageAll(cwd);
931
1026
  const { stdout: status } = await git(cwd, ["status", "--porcelain"]);
@@ -936,6 +1031,7 @@ async function commit(cwd, message) {
936
1031
  }
937
1032
 
938
1033
  // src/recipes/sync-configs.ts
1034
+ var GITIGNORE_CONFIG = "gitignore";
939
1035
  function siteLabel6(site) {
940
1036
  return site.name ?? site.path;
941
1037
  }
@@ -946,7 +1042,7 @@ async function readMaybe(path) {
946
1042
  return null;
947
1043
  }
948
1044
  }
949
- async function planDiffs(cwd, templates) {
1045
+ async function planTemplateDiffs(cwd, templates) {
950
1046
  const diffs = [];
951
1047
  for (const t of templates) {
952
1048
  const existing = await readMaybe(join6(cwd, t.path));
@@ -954,11 +1050,29 @@ async function planDiffs(cwd, templates) {
954
1050
  }
955
1051
  return diffs;
956
1052
  }
1053
+ async function planGitignore(cwd) {
1054
+ const existing = await readMaybe(join6(cwd, ".gitignore"));
1055
+ const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);
1056
+ const tracked = await listTrackedFiles(cwd);
1057
+ const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);
1058
+ if (merge.added.length === 0 && toUntrack.length === 0) return { kind: "noop" };
1059
+ return { kind: "apply", content: merge.content, toUntrack, added: merge.added };
1060
+ }
1061
+ async function applyGitignore(cwd, plan) {
1062
+ await writeFile3(join6(cwd, ".gitignore"), plan.content, "utf-8");
1063
+ if (plan.toUntrack.length > 0) {
1064
+ await removeFromIndex(cwd, plan.toUntrack);
1065
+ }
1066
+ }
957
1067
  async function syncConfigs(site, opts = {}) {
958
1068
  const label = siteLabel6(site);
959
- const targets = opts.which ? templatesByName(opts.which) : ALL_TEMPLATES;
960
- const diffs = await planDiffs(site.path, targets);
961
- if (diffs.length === 0) {
1069
+ const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
1070
+ const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
1071
+ const templates = templatesByName(templateNames);
1072
+ const includeGitignore = requested.includes(GITIGNORE_CONFIG);
1073
+ const templateDiffs = await planTemplateDiffs(site.path, templates);
1074
+ const gitignorePlan = includeGitignore ? await planGitignore(site.path) : { kind: "noop" };
1075
+ if (templateDiffs.length === 0 && gitignorePlan.kind === "noop") {
962
1076
  return {
963
1077
  recipe: "sync-configs",
964
1078
  site: label,
@@ -973,7 +1087,7 @@ async function syncConfigs(site, opts = {}) {
973
1087
  const branch = branchName("sync-configs");
974
1088
  await createBranch(site.path, branch);
975
1089
  const shas = [];
976
- for (const t of diffs) {
1090
+ for (const t of templateDiffs) {
977
1091
  await writeFile3(join6(site.path, t.path), t.contents, "utf-8");
978
1092
  const sha = await commit(
979
1093
  site.path,
@@ -981,6 +1095,11 @@ async function syncConfigs(site, opts = {}) {
981
1095
  );
982
1096
  if (sha) shas.push(sha);
983
1097
  }
1098
+ if (gitignorePlan.kind === "apply") {
1099
+ await applyGitignore(site.path, gitignorePlan);
1100
+ const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);
1101
+ if (sha) shas.push(sha);
1102
+ }
984
1103
  return {
985
1104
  recipe: "sync-configs",
986
1105
  site: label,