@reddoorla/maintenance 0.6.7 → 0.6.8
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 +272 -314
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.js +272 -314
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.js +87 -43
- package/dist/recipes/sync-configs.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -841,6 +841,65 @@ async function commit(cwd, message) {
|
|
|
841
841
|
return sha.trim();
|
|
842
842
|
}
|
|
843
843
|
|
|
844
|
+
// src/recipes/_with-recipe.ts
|
|
845
|
+
async function withRecipe(body) {
|
|
846
|
+
const label = siteLabel(body.site);
|
|
847
|
+
if (body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
|
|
848
|
+
throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
|
|
849
|
+
}
|
|
850
|
+
const planned = await body.plan();
|
|
851
|
+
if (planned.kind === "noop") {
|
|
852
|
+
return {
|
|
853
|
+
recipe: body.name,
|
|
854
|
+
site: label,
|
|
855
|
+
status: "noop",
|
|
856
|
+
commits: [],
|
|
857
|
+
...planned.notes ? { notes: planned.notes } : {}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
if (planned.kind === "failed") {
|
|
861
|
+
return {
|
|
862
|
+
recipe: body.name,
|
|
863
|
+
site: label,
|
|
864
|
+
status: "failed",
|
|
865
|
+
commits: [],
|
|
866
|
+
notes: planned.notes
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (!body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
|
|
870
|
+
throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
|
|
871
|
+
}
|
|
872
|
+
const branch = branchName(body.name);
|
|
873
|
+
await createBranch(body.site.path, branch);
|
|
874
|
+
const shas = [];
|
|
875
|
+
const result = await body.apply(planned.plan, {
|
|
876
|
+
cwd: body.site.path,
|
|
877
|
+
branch,
|
|
878
|
+
commit: async (msg) => {
|
|
879
|
+
const sha = await commit(body.site.path, msg);
|
|
880
|
+
if (sha) shas.push(sha);
|
|
881
|
+
return sha;
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
if (result.kind === "failed") {
|
|
885
|
+
return {
|
|
886
|
+
recipe: body.name,
|
|
887
|
+
site: label,
|
|
888
|
+
status: "failed",
|
|
889
|
+
commits: shas,
|
|
890
|
+
notes: result.notes
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;
|
|
894
|
+
return {
|
|
895
|
+
recipe: body.name,
|
|
896
|
+
site: label,
|
|
897
|
+
status: shas.length > 0 ? "applied" : "noop",
|
|
898
|
+
commits: shas,
|
|
899
|
+
notes
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
844
903
|
// src/recipes/sync-configs.ts
|
|
845
904
|
var GITIGNORE_CONFIG = "gitignore";
|
|
846
905
|
async function readMaybe(path) {
|
|
@@ -873,48 +932,33 @@ async function applyGitignore(cwd, plan) {
|
|
|
873
932
|
}
|
|
874
933
|
}
|
|
875
934
|
async function syncConfigs(site, opts = {}) {
|
|
876
|
-
const label = siteLabel(site);
|
|
877
935
|
const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
|
|
878
936
|
const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
|
|
879
937
|
const templates = templatesByName(templateNames);
|
|
880
938
|
const includeGitignore = requested.includes(GITIGNORE_CONFIG);
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
site:
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
if (sha) shas.push(sha);
|
|
905
|
-
}
|
|
906
|
-
if (gitignorePlan.kind === "apply") {
|
|
907
|
-
await applyGitignore(site.path, gitignorePlan);
|
|
908
|
-
const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);
|
|
909
|
-
if (sha) shas.push(sha);
|
|
910
|
-
}
|
|
911
|
-
return {
|
|
912
|
-
recipe: "sync-configs",
|
|
913
|
-
site: label,
|
|
914
|
-
status: "applied",
|
|
915
|
-
commits: shas,
|
|
916
|
-
notes: `branch: ${branch}`
|
|
917
|
-
};
|
|
939
|
+
return withRecipe({
|
|
940
|
+
name: "sync-configs",
|
|
941
|
+
site,
|
|
942
|
+
plan: async () => {
|
|
943
|
+
const templateDiffs = await planTemplateDiffs(site.path, templates);
|
|
944
|
+
const gitignorePlan = includeGitignore ? await planGitignore(site.path) : { kind: "noop" };
|
|
945
|
+
if (templateDiffs.length === 0 && gitignorePlan.kind === "noop") {
|
|
946
|
+
return { kind: "noop", notes: "all targeted configs already match" };
|
|
947
|
+
}
|
|
948
|
+
return { kind: "apply", plan: { templateDiffs, gitignorePlan } };
|
|
949
|
+
},
|
|
950
|
+
apply: async ({ templateDiffs, gitignorePlan }, { commit: commit2 }) => {
|
|
951
|
+
for (const t of templateDiffs) {
|
|
952
|
+
await writeFile3(join5(site.path, t.path), t.contents, "utf-8");
|
|
953
|
+
await commit2(`chore: sync ${t.config} config from @reddoorla/maintenance`);
|
|
954
|
+
}
|
|
955
|
+
if (gitignorePlan.kind === "apply") {
|
|
956
|
+
await applyGitignore(site.path, gitignorePlan);
|
|
957
|
+
await commit2(`chore: sync gitignore from @reddoorla/maintenance`);
|
|
958
|
+
}
|
|
959
|
+
return { kind: "ok" };
|
|
960
|
+
}
|
|
961
|
+
});
|
|
918
962
|
}
|
|
919
963
|
|
|
920
964
|
// src/recipes/bump-deps.ts
|
|
@@ -938,62 +982,51 @@ function upFlagsForGroup(group) {
|
|
|
938
982
|
return [];
|
|
939
983
|
}
|
|
940
984
|
async function bumpDeps(site, opts = {}) {
|
|
941
|
-
const label = siteLabel(site);
|
|
942
985
|
const group = opts.group ?? "minor";
|
|
943
986
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
987
|
+
return withRecipe({
|
|
988
|
+
name: "bump-deps",
|
|
989
|
+
site,
|
|
990
|
+
// pnpm install (in plan) mutates the lockfile, so the clean-tree check
|
|
991
|
+
// MUST happen first — otherwise a desynced-lockfile resync would silently
|
|
992
|
+
// land on top of whatever else was in the tree.
|
|
993
|
+
checkTreeFirst: true,
|
|
994
|
+
plan: async () => {
|
|
995
|
+
const hasPnpmLock = await exists(join6(site.path, "pnpm-lock.yaml"));
|
|
996
|
+
if (!hasPnpmLock) {
|
|
997
|
+
const hasNpmLock = await exists(join6(site.path, "package-lock.json"));
|
|
998
|
+
const hasYarnLock = await exists(join6(site.path, "yarn.lock"));
|
|
999
|
+
if (hasNpmLock || hasYarnLock) {
|
|
1000
|
+
const competing = hasNpmLock ? "package-lock.json" : "yarn.lock";
|
|
1001
|
+
return {
|
|
1002
|
+
kind: "failed",
|
|
1003
|
+
notes: `site has ${competing} but no pnpm-lock.yaml \u2014 run convert-to-pnpm first`
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
1008
|
+
const outdated = await spawn2(
|
|
1009
|
+
"pnpm",
|
|
1010
|
+
["outdated", "--json", ...outdatedFlagsForGroup(group)],
|
|
1011
|
+
{ cwd: site.path }
|
|
1012
|
+
);
|
|
1013
|
+
let parsed;
|
|
1014
|
+
try {
|
|
1015
|
+
parsed = JSON.parse(outdated.stdout || "{}");
|
|
1016
|
+
} catch {
|
|
1017
|
+
parsed = {};
|
|
1018
|
+
}
|
|
1019
|
+
if (Object.keys(parsed).length === 0) {
|
|
1020
|
+
return { kind: "noop", notes: `pnpm outdated reported nothing for group=${group}` };
|
|
1021
|
+
}
|
|
1022
|
+
return { kind: "apply", plan: { group } };
|
|
1023
|
+
},
|
|
1024
|
+
apply: async ({ group: g }, { commit: commit2, cwd }) => {
|
|
1025
|
+
await spawn2("pnpm", ["up", ...upFlagsForGroup(g)], { cwd, streaming: true });
|
|
1026
|
+
await commit2(`chore(deps): bump dependencies (${g})`);
|
|
1027
|
+
return { kind: "ok" };
|
|
957
1028
|
}
|
|
958
|
-
}
|
|
959
|
-
if (!await isWorkingTreeClean(site.path)) {
|
|
960
|
-
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
961
|
-
}
|
|
962
|
-
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
963
|
-
const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
|
|
964
|
-
cwd: site.path
|
|
965
1029
|
});
|
|
966
|
-
let parsed;
|
|
967
|
-
try {
|
|
968
|
-
parsed = JSON.parse(outdated.stdout || "{}");
|
|
969
|
-
} catch {
|
|
970
|
-
parsed = {};
|
|
971
|
-
}
|
|
972
|
-
const nothingToDo = Object.keys(parsed).length === 0;
|
|
973
|
-
if (nothingToDo) {
|
|
974
|
-
return {
|
|
975
|
-
recipe: "bump-deps",
|
|
976
|
-
site: label,
|
|
977
|
-
status: "noop",
|
|
978
|
-
commits: [],
|
|
979
|
-
notes: `pnpm outdated reported nothing for group=${group}`
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
const branch = branchName("bump-deps");
|
|
983
|
-
await createBranch(site.path, branch);
|
|
984
|
-
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
985
|
-
cwd: site.path,
|
|
986
|
-
streaming: true
|
|
987
|
-
});
|
|
988
|
-
const sha = await commit(site.path, `chore(deps): bump dependencies (${group})`);
|
|
989
|
-
const shas = sha ? [sha] : [];
|
|
990
|
-
return {
|
|
991
|
-
recipe: "bump-deps",
|
|
992
|
-
site: label,
|
|
993
|
-
status: shas.length > 0 ? "applied" : "noop",
|
|
994
|
-
commits: shas,
|
|
995
|
-
notes: `branch: ${branch}`
|
|
996
|
-
};
|
|
997
1030
|
}
|
|
998
1031
|
|
|
999
1032
|
// src/recipes/svelte-5/index.ts
|
|
@@ -1573,108 +1606,73 @@ async function alreadyOnSvelte5(cwd) {
|
|
|
1573
1606
|
}
|
|
1574
1607
|
}
|
|
1575
1608
|
async function upgradeSvelte4to5(site, opts = {}) {
|
|
1576
|
-
const label = siteLabel(site);
|
|
1577
1609
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
site.path,
|
|
1620
|
-
`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`
|
|
1621
|
-
);
|
|
1622
|
-
if (sha) shas.push(sha);
|
|
1623
|
-
}
|
|
1624
|
-
await verifyMigration(site.path, spawn2);
|
|
1625
|
-
const verifySha = await commit(site.path, "chore(svelte5): pnpm install + check");
|
|
1626
|
-
if (verifySha) shas.push(verifySha);
|
|
1627
|
-
await writeMigrationSummary({
|
|
1628
|
-
cwd: site.path,
|
|
1629
|
-
filesChangedByCodemods: codemods.filesChanged,
|
|
1630
|
-
svelteMigrateRan: migrate.ran,
|
|
1631
|
-
tailwindUpgraded: tw.ran
|
|
1610
|
+
return withRecipe({
|
|
1611
|
+
name: "svelte-4-to-5",
|
|
1612
|
+
site,
|
|
1613
|
+
plan: async () => {
|
|
1614
|
+
if (await alreadyOnSvelte5(site.path)) {
|
|
1615
|
+
return { kind: "noop", notes: "site already declares svelte ^5.x" };
|
|
1616
|
+
}
|
|
1617
|
+
return { kind: "apply", plan: true };
|
|
1618
|
+
},
|
|
1619
|
+
apply: async (_plan, { commit: commit2, cwd }) => {
|
|
1620
|
+
const bumped = await bumpToSvelte5Versions(cwd);
|
|
1621
|
+
if (bumped) {
|
|
1622
|
+
await commit2("chore(svelte5): bump svelte/kit/vite/vite-plugin-svelte");
|
|
1623
|
+
}
|
|
1624
|
+
const configChanged = await migrateSvelteConfig(cwd);
|
|
1625
|
+
if (configChanged) {
|
|
1626
|
+
await commit2("refactor(svelte5): migrate svelte.config.js (drop vitePreprocess)");
|
|
1627
|
+
}
|
|
1628
|
+
const migrate = await runSvelteMigrate(cwd, spawn2);
|
|
1629
|
+
if (migrate.ran) {
|
|
1630
|
+
await commit2("refactor(svelte5): run official svelte-migrate codemod");
|
|
1631
|
+
}
|
|
1632
|
+
const tw = await upgradeTailwind(cwd, spawn2);
|
|
1633
|
+
if (tw.ran) {
|
|
1634
|
+
await commit2("chore(svelte5): tailwindcss 3 \u2192 4 upgrade");
|
|
1635
|
+
}
|
|
1636
|
+
const codemods = await applyGotchaCodemods(cwd);
|
|
1637
|
+
if (codemods.filesChanged > 0) {
|
|
1638
|
+
await commit2(`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`);
|
|
1639
|
+
}
|
|
1640
|
+
await verifyMigration(cwd, spawn2);
|
|
1641
|
+
await commit2("chore(svelte5): pnpm install + check");
|
|
1642
|
+
await writeMigrationSummary({
|
|
1643
|
+
cwd,
|
|
1644
|
+
filesChangedByCodemods: codemods.filesChanged,
|
|
1645
|
+
svelteMigrateRan: migrate.ran,
|
|
1646
|
+
tailwindUpgraded: tw.ran
|
|
1647
|
+
});
|
|
1648
|
+
await commit2("docs(svelte5): add MIGRATION_SVELTE_5.md summary");
|
|
1649
|
+
return { kind: "ok" };
|
|
1650
|
+
}
|
|
1632
1651
|
});
|
|
1633
|
-
const summarySha = await commit(site.path, "docs(svelte5): add MIGRATION_SVELTE_5.md summary");
|
|
1634
|
-
if (summarySha) shas.push(summarySha);
|
|
1635
|
-
return {
|
|
1636
|
-
recipe: "svelte-4-to-5",
|
|
1637
|
-
site: label,
|
|
1638
|
-
status: shas.length > 0 ? "applied" : "noop",
|
|
1639
|
-
commits: shas,
|
|
1640
|
-
notes: `branch: ${branch}`
|
|
1641
|
-
};
|
|
1642
1652
|
}
|
|
1643
1653
|
|
|
1644
1654
|
// src/recipes/svelte-codemods.ts
|
|
1645
1655
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
1646
1656
|
import { join as join13 } from "path";
|
|
1647
1657
|
async function svelteCodemods(site) {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
const sha = await commit(
|
|
1668
|
-
site.path,
|
|
1669
|
-
`refactor(svelte5): apply codemods (${changes.length} files)`
|
|
1670
|
-
);
|
|
1671
|
-
return {
|
|
1672
|
-
recipe: "svelte-codemods",
|
|
1673
|
-
site: label,
|
|
1674
|
-
status: "applied",
|
|
1675
|
-
commits: sha ? [sha] : [],
|
|
1676
|
-
notes: `branch: ${branch}`
|
|
1677
|
-
};
|
|
1658
|
+
return withRecipe({
|
|
1659
|
+
name: "svelte-codemods",
|
|
1660
|
+
site,
|
|
1661
|
+
plan: async () => {
|
|
1662
|
+
const changes = await planGotchaCodemods(site.path);
|
|
1663
|
+
if (changes.length === 0) {
|
|
1664
|
+
return { kind: "noop", notes: "no codemod targets matched" };
|
|
1665
|
+
}
|
|
1666
|
+
return { kind: "apply", plan: changes };
|
|
1667
|
+
},
|
|
1668
|
+
apply: async (changes, { commit: commit2, cwd }) => {
|
|
1669
|
+
for (const c of changes) {
|
|
1670
|
+
await writeFile8(join13(cwd, c.rel), c.after, "utf-8");
|
|
1671
|
+
}
|
|
1672
|
+
await commit2(`refactor(svelte5): apply codemods (${changes.length} files)`);
|
|
1673
|
+
return { kind: "ok" };
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1678
1676
|
}
|
|
1679
1677
|
|
|
1680
1678
|
// src/recipes/convert-to-pnpm.ts
|
|
@@ -1710,80 +1708,55 @@ async function exists2(path) {
|
|
|
1710
1708
|
}
|
|
1711
1709
|
}
|
|
1712
1710
|
async function convertToPnpm(site, opts = {}) {
|
|
1713
|
-
const label = siteLabel(site);
|
|
1714
1711
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1715
1712
|
const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;
|
|
1716
1713
|
const pnpmLockPath = join14(site.path, "pnpm-lock.yaml");
|
|
1717
1714
|
const npmLockPath = join14(site.path, "package-lock.json");
|
|
1718
1715
|
const yarnLockPath = join14(site.path, "yarn.lock");
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1716
|
+
return withRecipe({
|
|
1717
|
+
name: "convert-to-pnpm",
|
|
1718
|
+
site,
|
|
1719
|
+
plan: async () => {
|
|
1720
|
+
if (await exists2(pnpmLockPath)) {
|
|
1721
|
+
return { kind: "noop", notes: "site already has pnpm-lock.yaml" };
|
|
1722
|
+
}
|
|
1723
|
+
const hasNpmLock = await exists2(npmLockPath);
|
|
1724
|
+
const hasYarnLock = await exists2(yarnLockPath);
|
|
1725
|
+
if (!hasNpmLock && !hasYarnLock) {
|
|
1726
|
+
return {
|
|
1727
|
+
kind: "noop",
|
|
1728
|
+
notes: "no convertible lockfile (package-lock.json or yarn.lock) at site root"
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
return { kind: "apply", plan: { hasNpmLock, hasYarnLock } };
|
|
1732
|
+
},
|
|
1733
|
+
apply: async ({ hasNpmLock, hasYarnLock }, { commit: commit2, cwd }) => {
|
|
1734
|
+
if (hasNpmLock) await rm3(npmLockPath, { force: true });
|
|
1735
|
+
if (hasYarnLock) await rm3(yarnLockPath, { force: true });
|
|
1736
|
+
const sourceLock = hasNpmLock ? "package-lock.json" : "yarn.lock";
|
|
1737
|
+
await commit2(`chore(pnpm): remove ${sourceLock}`);
|
|
1738
|
+
const pkgPath = join14(cwd, "package.json");
|
|
1739
|
+
const pkg = await readPackageJson(pkgPath);
|
|
1740
|
+
const next = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };
|
|
1741
|
+
if (pkg.scripts && typeof pkg.scripts === "object") {
|
|
1742
|
+
const { scripts: rewritten, changedCount } = rewriteScriptsForPnpm(
|
|
1743
|
+
pkg.scripts
|
|
1744
|
+
);
|
|
1745
|
+
if (changedCount > 0) {
|
|
1746
|
+
next.scripts = rewritten;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
await writePackageJson(pkgPath, next);
|
|
1750
|
+
await commit2("chore(pnpm): pin packageManager + rewrite npm scripts");
|
|
1751
|
+
await rm3(join14(cwd, "node_modules"), { recursive: true, force: true });
|
|
1752
|
+
const installResult = await spawn2("pnpm", ["install"], { cwd, streaming: true });
|
|
1753
|
+
if (installResult.code !== 0) {
|
|
1754
|
+
return { kind: "failed", notes: `pnpm install failed (exit ${installResult.code})` };
|
|
1755
|
+
}
|
|
1756
|
+
await commit2("chore(pnpm): add pnpm-lock.yaml");
|
|
1757
|
+
return { kind: "ok" };
|
|
1759
1758
|
}
|
|
1760
|
-
}
|
|
1761
|
-
await writePackageJson(pkgPath, next);
|
|
1762
|
-
const pkgSha = await commit(site.path, "chore(pnpm): pin packageManager + rewrite npm scripts");
|
|
1763
|
-
if (pkgSha) shas.push(pkgSha);
|
|
1764
|
-
await rm3(join14(site.path, "node_modules"), { recursive: true, force: true });
|
|
1765
|
-
const installResult = await spawn2("pnpm", ["install"], {
|
|
1766
|
-
cwd: site.path,
|
|
1767
|
-
streaming: true
|
|
1768
1759
|
});
|
|
1769
|
-
if (installResult.code !== 0) {
|
|
1770
|
-
return {
|
|
1771
|
-
recipe: "convert-to-pnpm",
|
|
1772
|
-
site: label,
|
|
1773
|
-
status: "failed",
|
|
1774
|
-
commits: shas,
|
|
1775
|
-
notes: `pnpm install failed (exit ${installResult.code}). branch ${branch} left for inspection.`
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
const installSha = await commit(site.path, "chore(pnpm): add pnpm-lock.yaml");
|
|
1779
|
-
if (installSha) shas.push(installSha);
|
|
1780
|
-
return {
|
|
1781
|
-
recipe: "convert-to-pnpm",
|
|
1782
|
-
site: label,
|
|
1783
|
-
status: "applied",
|
|
1784
|
-
commits: shas,
|
|
1785
|
-
notes: `branch: ${branch}`
|
|
1786
|
-
};
|
|
1787
1760
|
}
|
|
1788
1761
|
|
|
1789
1762
|
// src/recipes/onboard.ts
|
|
@@ -1840,74 +1813,59 @@ function isDeclared(pkg, name) {
|
|
|
1840
1813
|
return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
|
|
1841
1814
|
}
|
|
1842
1815
|
async function onboard(site, opts = {}) {
|
|
1843
|
-
const label = siteLabel(site);
|
|
1844
1816
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1845
1817
|
const audits = opts.audits ?? ["lighthouse", "a11y"];
|
|
1846
1818
|
const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1819
|
+
return withRecipe({
|
|
1820
|
+
name: "onboard",
|
|
1821
|
+
site,
|
|
1822
|
+
plan: async () => {
|
|
1823
|
+
if (!await exists3(join16(site.path, "pnpm-lock.yaml"))) {
|
|
1824
|
+
return {
|
|
1825
|
+
kind: "failed",
|
|
1826
|
+
notes: "no pnpm-lock.yaml at site root \u2014 run convert-to-pnpm first"
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
const pkgPath = join16(site.path, "package.json");
|
|
1830
|
+
const pkg = await readPackageJson(pkgPath);
|
|
1831
|
+
const toAdd = [];
|
|
1832
|
+
if (!isDeclared(pkg, PACKAGE_NAME)) {
|
|
1833
|
+
toAdd.push({ name: PACKAGE_NAME, version: packageVersion });
|
|
1834
|
+
}
|
|
1835
|
+
for (const audit of audits) {
|
|
1836
|
+
for (const dep of AUDIT_DEPS[audit]) {
|
|
1837
|
+
if (!isDeclared(pkg, dep.name)) toAdd.push(dep);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (toAdd.length === 0) {
|
|
1841
|
+
return {
|
|
1842
|
+
kind: "noop",
|
|
1843
|
+
notes: `site already has ${PACKAGE_NAME} and audit deps (${audits.join("+")})`
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
return { kind: "apply", plan: { pkg, toAdd } };
|
|
1847
|
+
},
|
|
1848
|
+
apply: async ({ pkg, toAdd }, { commit: commit2, cwd }) => {
|
|
1849
|
+
const pkgPath = join16(cwd, "package.json");
|
|
1850
|
+
let next = pkg;
|
|
1851
|
+
for (const dep of toAdd) {
|
|
1852
|
+
next = bumpDep(next, dep.name, dep.version);
|
|
1853
|
+
}
|
|
1854
|
+
await writePackageJson(pkgPath, next);
|
|
1855
|
+
const installResult = await spawn2("pnpm", ["install"], { cwd, streaming: true });
|
|
1856
|
+
if (installResult.code !== 0) {
|
|
1857
|
+
return {
|
|
1858
|
+
kind: "failed",
|
|
1859
|
+
notes: `pnpm install failed (exit ${installResult.code})`
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
await commit2(`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`);
|
|
1863
|
+
return {
|
|
1864
|
+
kind: "ok",
|
|
1865
|
+
notes: `Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(", ")}`
|
|
1866
|
+
};
|
|
1865
1867
|
}
|
|
1866
|
-
}
|
|
1867
|
-
if (toAdd.length === 0) {
|
|
1868
|
-
return {
|
|
1869
|
-
recipe: "onboard",
|
|
1870
|
-
site: label,
|
|
1871
|
-
status: "noop",
|
|
1872
|
-
commits: [],
|
|
1873
|
-
notes: `site already has ${PACKAGE_NAME} and audit deps (${audits.join("+")})`
|
|
1874
|
-
};
|
|
1875
|
-
}
|
|
1876
|
-
if (!await isWorkingTreeClean(site.path)) {
|
|
1877
|
-
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
1878
|
-
}
|
|
1879
|
-
const branch = branchName("onboard");
|
|
1880
|
-
await createBranch(site.path, branch);
|
|
1881
|
-
let next = pkg;
|
|
1882
|
-
for (const dep of toAdd) {
|
|
1883
|
-
next = bumpDep(next, dep.name, dep.version);
|
|
1884
|
-
}
|
|
1885
|
-
await writePackageJson(pkgPath, next);
|
|
1886
|
-
const installResult = await spawn2("pnpm", ["install"], {
|
|
1887
|
-
cwd: site.path,
|
|
1888
|
-
streaming: true
|
|
1889
1868
|
});
|
|
1890
|
-
if (installResult.code !== 0) {
|
|
1891
|
-
return {
|
|
1892
|
-
recipe: "onboard",
|
|
1893
|
-
site: label,
|
|
1894
|
-
status: "failed",
|
|
1895
|
-
commits: [],
|
|
1896
|
-
notes: `pnpm install failed (exit ${installResult.code}). branch ${branch} left for inspection.`
|
|
1897
|
-
};
|
|
1898
|
-
}
|
|
1899
|
-
const sha = await commit(
|
|
1900
|
-
site.path,
|
|
1901
|
-
`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`
|
|
1902
|
-
);
|
|
1903
|
-
const shas = sha ? [sha] : [];
|
|
1904
|
-
return {
|
|
1905
|
-
recipe: "onboard",
|
|
1906
|
-
site: label,
|
|
1907
|
-
status: "applied",
|
|
1908
|
-
commits: shas,
|
|
1909
|
-
notes: `branch: ${branch}. Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(", ")}`
|
|
1910
|
-
};
|
|
1911
1869
|
}
|
|
1912
1870
|
|
|
1913
1871
|
// src/recipes/index.ts
|