@reddoorla/maintenance 0.6.6 → 0.6.7
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 +90 -160
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +12 -21
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.js +72 -83
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.js +7 -6
- package/dist/recipes/sync-configs.js.map +1 -1
- package/dist/util/git.js +2 -3
- package/dist/util/git.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { readFile, writeFile } from "fs/promises";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
|
|
5
|
+
// src/util/site.ts
|
|
6
|
+
function siteLabel(site) {
|
|
7
|
+
return site.name ?? site.path;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
// src/recipes/sync-configs/templates.ts
|
|
6
11
|
var eslint = {
|
|
7
12
|
config: "eslint",
|
|
@@ -160,9 +165,8 @@ async function git(cwd, args) {
|
|
|
160
165
|
return exec("git", args, { cwd, env: process.env });
|
|
161
166
|
}
|
|
162
167
|
function branchName(recipe, when = /* @__PURE__ */ new Date()) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
return `maint/${recipe}-${trimmed}`;
|
|
168
|
+
const compact = when.toISOString().replace(/[-:.]/g, "");
|
|
169
|
+
return `maint/${recipe}-${compact}`;
|
|
166
170
|
}
|
|
167
171
|
async function isWorkingTreeClean(cwd) {
|
|
168
172
|
const { stdout } = await git(cwd, ["status", "--porcelain"]);
|
|
@@ -204,9 +208,6 @@ var ALL_CONFIG_NAMES = [
|
|
|
204
208
|
function isConfigName(value) {
|
|
205
209
|
return ALL_CONFIG_NAMES.includes(value);
|
|
206
210
|
}
|
|
207
|
-
function siteLabel(site) {
|
|
208
|
-
return site.name ?? site.path;
|
|
209
|
-
}
|
|
210
211
|
async function readMaybe(path) {
|
|
211
212
|
try {
|
|
212
213
|
return await readFile(path, "utf-8");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/recipes/sync-configs.ts","../../src/recipes/sync-configs/templates.ts","../../src/recipes/sync-configs/gitignore.ts","../../src/util/git.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport {\n branchName,\n commit,\n createBranch,\n isWorkingTreeClean,\n listTrackedFiles,\n removeFromIndex,\n} from \"../util/git.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nfunction siteLabel(site: Site): string {\n return site.name ?? site.path;\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing !== t.contents) diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n | { kind: \"noop\" }\n | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const label = siteLabel(site);\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return {\n recipe: \"sync-configs\",\n site: label,\n status: \"noop\",\n commits: [],\n notes: \"all targeted configs already match\",\n };\n }\n\n if (!(await isWorkingTreeClean(site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${site.path}`);\n }\n\n const branch = branchName(\"sync-configs\");\n await createBranch(site.path, branch);\n\n const shas: string[] = [];\n for (const t of templateDiffs) {\n await writeFile(join(site.path, t.path), t.contents, \"utf-8\");\n const sha = await commit(\n site.path,\n `chore: sync ${t.config} config from @reddoorla/maintenance`,\n );\n if (sha) shas.push(sha);\n }\n\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);\n if (sha) shas.push(sha);\n }\n\n return {\n recipe: \"sync-configs\",\n site: label,\n status: \"applied\",\n commits: shas,\n notes: `branch: ${branch}`,\n };\n}\n","import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-auto\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter() },\n});\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n lighthouse,\n playwrightA11y,\n svelte,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n const iso = when.toISOString().replace(/[-:.]/g, \"\").replace(/Z$/, \"Z\");\n // 2026-05-20T10:30:00.000Z → 20260520T103000000Z; trim millis:\n const trimmed = iso.replace(/(\\d{8}T\\d{6})\\d+(Z)$/, \"$1$2\");\n return `maint/${recipe}-${trimmed}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/recipes/sync-configs.ts","../../src/util/site.ts","../../src/recipes/sync-configs/templates.ts","../../src/recipes/sync-configs/gitignore.ts","../../src/util/git.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport {\n branchName,\n commit,\n createBranch,\n isWorkingTreeClean,\n listTrackedFiles,\n removeFromIndex,\n} from \"../util/git.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing !== t.contents) diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n | { kind: \"noop\" }\n | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const label = siteLabel(site);\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return {\n recipe: \"sync-configs\",\n site: label,\n status: \"noop\",\n commits: [],\n notes: \"all targeted configs already match\",\n };\n }\n\n if (!(await isWorkingTreeClean(site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${site.path}`);\n }\n\n const branch = branchName(\"sync-configs\");\n await createBranch(site.path, branch);\n\n const shas: string[] = [];\n for (const t of templateDiffs) {\n await writeFile(join(site.path, t.path), t.contents, \"utf-8\");\n const sha = await commit(\n site.path,\n `chore: sync ${t.config} config from @reddoorla/maintenance`,\n );\n if (sha) shas.push(sha);\n }\n\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);\n if (sha) shas.push(sha);\n }\n\n return {\n recipe: \"sync-configs\",\n site: label,\n status: \"applied\",\n commits: shas,\n notes: `branch: ${branch}`,\n };\n}\n","import type { Site } from \"../types.js\";\n\n/** Human-friendly label for log/output formatting. Prefer the inventory's\n * `name` when present (e.g. \"caltex-landing\") and fall back to the\n * filesystem `path` when unnamed. Every audit + recipe uses this. */\nexport function siteLabel(site: Site): string {\n return site.name ?? site.path;\n}\n","import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-auto\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter() },\n});\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n lighthouse,\n playwrightA11y,\n svelte,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n"],"mappings":"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","names":[]}
|
package/dist/util/git.js
CHANGED
|
@@ -6,9 +6,8 @@ async function git(cwd, args) {
|
|
|
6
6
|
return exec("git", args, { cwd, env: process.env });
|
|
7
7
|
}
|
|
8
8
|
function branchName(recipe, when = /* @__PURE__ */ new Date()) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
return `maint/${recipe}-${trimmed}`;
|
|
9
|
+
const compact = when.toISOString().replace(/[-:.]/g, "");
|
|
10
|
+
return `maint/${recipe}-${compact}`;
|
|
12
11
|
}
|
|
13
12
|
async function currentBranch(cwd) {
|
|
14
13
|
const { stdout } = await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
package/dist/util/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/git.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n
|
|
1
|
+
{"version":3,"sources":["../../src/util/git.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAE1B,IAAM,OAAO,UAAU,QAAQ;AAE/B,eAAe,IAAI,KAAa,MAA6D;AAC3F,SAAO,KAAK,OAAO,MAAM,EAAE,KAAK,KAAK,QAAQ,IAAI,CAAC;AACpD;AAEO,SAAS,WAAW,QAAgB,OAAa,oBAAI,KAAK,GAAW;AAG1E,QAAM,UAAU,KAAK,YAAY,EAAE,QAAQ,UAAU,EAAE;AACvD,SAAO,SAAS,MAAM,IAAI,OAAO;AACnC;AAEA,eAAsB,cAAc,KAA8B;AAChE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,aAAa,gBAAgB,MAAM,CAAC;AACvE,SAAO,OAAO,KAAK;AACrB;AAEA,eAAsB,mBAAmB,KAA+B;AACtE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,aAAa,CAAC;AAC3D,SAAO,OAAO,KAAK,EAAE,WAAW;AAClC;AAEA,eAAsB,aAAa,KAAa,MAA6B;AAC3E,QAAM,IAAI,KAAK,CAAC,YAAY,MAAM,IAAI,CAAC;AACzC;AAEA,eAAsB,SAAS,KAA4B;AACzD,QAAM,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC;AAC9B;AAEA,eAAsB,iBAAiB,KAAgC;AACrE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;AAC9C,SAAO,OACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/B;AAEA,eAAsB,gBAAgB,KAAa,OAAgC;AACjF,MAAI,MAAM,WAAW,EAAG;AACxB,QAAM,IAAI,KAAK,CAAC,MAAM,MAAM,YAAY,MAAM,GAAG,KAAK,CAAC;AACzD;AAMA,eAAsB,OAAO,KAAa,SAAyC;AACjF,QAAM,SAAS,GAAG;AAClB,QAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,aAAa,CAAC;AACnE,MAAI,OAAO,KAAK,EAAE,WAAW,EAAG,QAAO;AACvC,QAAM,IAAI,KAAK,CAAC,UAAU,MAAM,OAAO,CAAC;AACxC,QAAM,EAAE,QAAQ,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,CAAC;AAC5D,SAAO,IAAI,KAAK;AAClB;","names":[]}
|