@rtorcato/repo-tooling 3.1.0 → 3.2.1
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.
|
@@ -9,7 +9,7 @@ import { checkGitIdentity } from '../../base/git-identity.js';
|
|
|
9
9
|
import { LOCKFILE_VERSION, readLockfile } from '../utils/lockfile.js';
|
|
10
10
|
import { declinedInLock, getFixTargetForCheck } from './fix-targets.js';
|
|
11
11
|
import { checkAiSetup, checkCodeowners, checkCodeQL, checkCommunityHealth, checkCoverageUpload, checkDependabot, checkEditorConfig, checkFile, checkGitHooks, checkGitHubActions, checkGitLabCI, checkPrePushHook, checkReadmeBadges, COMMITLINT_FILE_CHECK, } from '../../base/checks.js';
|
|
12
|
-
import { allDeps, checkAreTheTypesWrong, checkDocsSite, checkEnginesNode, checkKnip, checkLintStaged, checkNodeVersionConsistency, checkNodeVersionPin, checkPackageJson, checkPublint, checkSemanticRelease, checkSizeLimit, checkTailwind, checkPnpmWorkspace, checkTreeshakeSetup, checkTurborepo, checkTypedoc, checkVerifyScript, checkVscodeExtensions, evaluateNodeVersion, FILE_CHECKS, findDocsAppDir, jsBadgeAudience, jsGitHooksProfile, readPackageJson, } from '../../languages/js/checks.js';
|
|
12
|
+
import { allDeps, checkAreTheTypesWrong, checkConfigSchemaVersions, checkDocsSite, checkEnginesNode, checkGitDependencies, checkKnip, checkLintStaged, checkNodeVersionConsistency, checkNodeVersionPin, checkPackageJson, checkPublint, checkSemanticRelease, checkSizeLimit, checkTailwind, checkPnpmWorkspace, checkTreeshakeSetup, checkTurborepo, checkTypedoc, checkVerifyScript, checkVscodeExtensions, evaluateNodeVersion, FILE_CHECKS, findDocsAppDir, jsBadgeAudience, jsGitHooksProfile, readPackageJson, } from '../../languages/js/checks.js';
|
|
13
13
|
export { evaluateNodeVersion };
|
|
14
14
|
const PACKAGE = '@rtorcato/repo-tooling';
|
|
15
15
|
// Detects the broken-release-on-protected-main footgun: a workflow that runs
|
|
@@ -218,6 +218,8 @@ export async function runDoctor(dir) {
|
|
|
218
218
|
results.push(evaluateNodeVersion(process.version));
|
|
219
219
|
results.push(checkPackageJson(pkg));
|
|
220
220
|
results.push(checkEnginesNode(pkg));
|
|
221
|
+
results.push(await checkConfigSchemaVersions(targetDir, pkg));
|
|
222
|
+
results.push(checkGitDependencies(pkg));
|
|
221
223
|
results.push(await checkVscodeExtensions(targetDir));
|
|
222
224
|
results.push(await checkNodeVersionPin(targetDir));
|
|
223
225
|
results.push(await checkNodeVersionConsistency(targetDir, pkg));
|
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { generateSwiftProject } from '../../languages/swift/scaffold.js';
|
|
5
5
|
import { installAiSetup } from './agent-rules.js';
|
|
6
6
|
import { bundlerNeedsEsbuild, ensureBuildApprovals, generateBuildConfigs } from './build.js';
|
|
7
|
-
import { ensurePnpmSettings } from './pnpm-workspace.js';
|
|
7
|
+
import { ensurePnpmSettings, familyGlob } from './pnpm-workspace.js';
|
|
8
8
|
import { generateGitConfigs } from './git.js';
|
|
9
9
|
import { generateGitHubActions } from './github-actions.js';
|
|
10
10
|
import { generateLintingConfigs } from './linting.js';
|
|
@@ -84,7 +84,7 @@ export async function generateConfigs(config, targetDir) {
|
|
|
84
84
|
await ensureBuildApprovals(config, targetDir);
|
|
85
85
|
// Family-wide pnpm settings (#314). Runs last of the workspace writers so it
|
|
86
86
|
// merges into whatever they wrote rather than racing them for the file.
|
|
87
|
-
await ensurePnpmSettings(targetDir, bundlerNeedsEsbuild(config));
|
|
87
|
+
await ensurePnpmSettings(targetDir, bundlerNeedsEsbuild(config), familyGlob(config.projectName));
|
|
88
88
|
// Turborepo task pipeline (pnpm-workspace monorepos, when opted-in)
|
|
89
89
|
if (config.turborepo) {
|
|
90
90
|
await generateTurborepo(targetDir);
|
|
@@ -12,12 +12,24 @@ import fs from 'fs-extra';
|
|
|
12
12
|
export const WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
13
13
|
/**
|
|
14
14
|
* pnpm's `minimumReleaseAge` cutoff holds back freshly published versions —
|
|
15
|
-
* good against a typosquat
|
|
16
|
-
* same-day
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* good against a typosquat or a hijacked account, but it also stalls every
|
|
16
|
+
* consumer of a same-day fix in a sibling package for 24h. Exempting the
|
|
17
|
+
* repo's *own* scope trades that off only for packages it already publishes.
|
|
18
|
+
*
|
|
19
|
+
* Derived from the consuming package's name rather than hardcoded: this is a
|
|
20
|
+
* public CLI, and writing one organisation's scope into a stranger's config
|
|
21
|
+
* would loosen a supply-chain guard for packages they neither use nor chose.
|
|
22
|
+
* An unscoped package gets no such setting at all — there is no "family" to
|
|
23
|
+
* infer, and guessing one would be worse than leaving it alone.
|
|
24
|
+
*
|
|
25
|
+
* One glob covers a whole scope: pnpm matches these entries with
|
|
26
|
+
* `@pnpm/config.matcher`, so there's no package list to keep in sync.
|
|
19
27
|
*/
|
|
20
|
-
|
|
28
|
+
export function familyGlob(packageName) {
|
|
29
|
+
const name = typeof packageName === 'string' ? packageName : '';
|
|
30
|
+
const scope = /^(@[^/]+)\//.exec(name)?.[1];
|
|
31
|
+
return scope ? `${scope}/*` : null;
|
|
32
|
+
}
|
|
21
33
|
/** Bundlers that pull in esbuild, whose install script pnpm 11 refuses to run unapproved. */
|
|
22
34
|
const ESBUILD_BUNDLERS = ['esbuild', 'tsup', 'vite'];
|
|
23
35
|
/** True when the repo depends on a bundler that drags esbuild in. */
|
|
@@ -43,7 +55,29 @@ function section(yaml, key) {
|
|
|
43
55
|
}
|
|
44
56
|
return body;
|
|
45
57
|
}
|
|
46
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The managed settings for one repo. A function rather than a constant because
|
|
60
|
+
* the release-age exemption is scope-derived, and a repo with no scope to
|
|
61
|
+
* derive doesn't get that setting at all.
|
|
62
|
+
*/
|
|
63
|
+
function settingsFor(glob) {
|
|
64
|
+
return glob ? [...BASE_SETTINGS, releaseAgeSetting(glob)] : BASE_SETTINGS;
|
|
65
|
+
}
|
|
66
|
+
function releaseAgeSetting(glob) {
|
|
67
|
+
return {
|
|
68
|
+
label: `minimumReleaseAgeExclude: ${glob}`,
|
|
69
|
+
applies: () => true,
|
|
70
|
+
satisfied: (yaml) => (section(yaml, 'minimumReleaseAgeExclude') ?? []).some((l) => l.includes(glob)),
|
|
71
|
+
key: 'minimumReleaseAgeExclude',
|
|
72
|
+
block: `# Exempt this package's own scope from pnpm's minimumReleaseAge cutoff, so a
|
|
73
|
+
# same-day fix in a sibling package is installable today rather than tomorrow.
|
|
74
|
+
minimumReleaseAgeExclude:
|
|
75
|
+
- '${glob}'
|
|
76
|
+
`,
|
|
77
|
+
item: ` - '${glob}'`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const BASE_SETTINGS = [
|
|
47
81
|
{
|
|
48
82
|
label: 'verifyDepsBeforeRun: false',
|
|
49
83
|
applies: () => true,
|
|
@@ -57,18 +91,6 @@ verifyDepsBeforeRun: false
|
|
|
57
91
|
`,
|
|
58
92
|
item: '',
|
|
59
93
|
},
|
|
60
|
-
{
|
|
61
|
-
label: `minimumReleaseAgeExclude: ${FAMILY_GLOB}`,
|
|
62
|
-
applies: () => true,
|
|
63
|
-
satisfied: (yaml) => (section(yaml, 'minimumReleaseAgeExclude') ?? []).some(hasFamilyGlob),
|
|
64
|
-
key: 'minimumReleaseAgeExclude',
|
|
65
|
-
block: `# Exempt the @rtorcato family from pnpm's minimumReleaseAge cutoff, so a
|
|
66
|
-
# same-day fix in a sibling package is installable today rather than tomorrow.
|
|
67
|
-
minimumReleaseAgeExclude:
|
|
68
|
-
- '${FAMILY_GLOB}'
|
|
69
|
-
`,
|
|
70
|
-
item: ` - '${FAMILY_GLOB}'`,
|
|
71
|
-
},
|
|
72
94
|
{
|
|
73
95
|
label: 'allowBuilds: esbuild',
|
|
74
96
|
applies: (needsEsbuild) => needsEsbuild,
|
|
@@ -82,12 +104,11 @@ allowBuilds:
|
|
|
82
104
|
item: ' esbuild: true',
|
|
83
105
|
},
|
|
84
106
|
];
|
|
85
|
-
function hasFamilyGlob(line) {
|
|
86
|
-
return line.includes(FAMILY_GLOB);
|
|
87
|
-
}
|
|
88
107
|
/** Managed settings absent from `yaml`, named as doctor reports them. */
|
|
89
|
-
export function missingPnpmSettings(yaml, needsEsbuild) {
|
|
90
|
-
return
|
|
108
|
+
export function missingPnpmSettings(yaml, needsEsbuild, glob) {
|
|
109
|
+
return settingsFor(glob)
|
|
110
|
+
.filter((s) => s.applies(needsEsbuild) && !s.satisfied(yaml))
|
|
111
|
+
.map((s) => s.label);
|
|
91
112
|
}
|
|
92
113
|
/** Insert `item` directly under an existing `key:` line, keeping the rest untouched. */
|
|
93
114
|
function insertUnder(yaml, key, item) {
|
|
@@ -97,9 +118,9 @@ function insertUnder(yaml, key, item) {
|
|
|
97
118
|
return lines.join('\n');
|
|
98
119
|
}
|
|
99
120
|
/** Merge every missing managed setting into `yaml` and return the new contents. */
|
|
100
|
-
export function upsertPnpmSettings(yaml, needsEsbuild) {
|
|
121
|
+
export function upsertPnpmSettings(yaml, needsEsbuild, glob) {
|
|
101
122
|
let next = yaml;
|
|
102
|
-
for (const setting of
|
|
123
|
+
for (const setting of settingsFor(glob)) {
|
|
103
124
|
if (!setting.applies(needsEsbuild) || setting.satisfied(next))
|
|
104
125
|
continue;
|
|
105
126
|
if (setting.item && section(next, setting.key)) {
|
|
@@ -115,10 +136,10 @@ export function upsertPnpmSettings(yaml, needsEsbuild) {
|
|
|
115
136
|
* Merge the managed pnpm settings into `pnpm-workspace.yaml`, creating it when
|
|
116
137
|
* absent. Returns the relative path if anything changed, else null.
|
|
117
138
|
*/
|
|
118
|
-
export async function ensurePnpmSettings(targetDir, needsEsbuild) {
|
|
139
|
+
export async function ensurePnpmSettings(targetDir, needsEsbuild, glob) {
|
|
119
140
|
const file = path.join(targetDir, WORKSPACE_FILE);
|
|
120
141
|
const current = (await fs.pathExists(file)) ? await fs.readFile(file, 'utf-8') : '';
|
|
121
|
-
const next = upsertPnpmSettings(current, needsEsbuild);
|
|
142
|
+
const next = upsertPnpmSettings(current, needsEsbuild, glob);
|
|
122
143
|
if (next === current)
|
|
123
144
|
return null;
|
|
124
145
|
await fs.writeFile(file, next.replace(/^\n+/, ''));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { hookHasUncommented } from '../../base/checks.js';
|
|
4
|
-
import { WORKSPACE_FILE, dependsOnEsbuild, missingPnpmSettings, } from '../../cli/generators/pnpm-workspace.js';
|
|
4
|
+
import { WORKSPACE_FILE, dependsOnEsbuild, familyGlob, missingPnpmSettings, } from '../../cli/generators/pnpm-workspace.js';
|
|
5
5
|
const PACKAGE = '@rtorcato/repo-tooling';
|
|
6
6
|
const NODE_MIN_MAJOR = 22;
|
|
7
7
|
const NODE_LTS_REQUIREMENTS = {
|
|
@@ -849,7 +849,7 @@ export async function checkPnpmWorkspace(dir, pkg) {
|
|
|
849
849
|
return { check, status: 'ok', detail: 'not a pnpm repo' };
|
|
850
850
|
}
|
|
851
851
|
const yaml = exists ? await fs.readFile(file, 'utf-8') : '';
|
|
852
|
-
const missing = missingPnpmSettings(yaml, dependsOnEsbuild(allDeps(pkg)));
|
|
852
|
+
const missing = missingPnpmSettings(yaml, dependsOnEsbuild(allDeps(pkg)), familyGlob(pkg?.name));
|
|
853
853
|
if (missing.length === 0) {
|
|
854
854
|
return { check, status: 'ok', detail: `${WORKSPACE_FILE} carries the managed settings` };
|
|
855
855
|
}
|
|
@@ -947,3 +947,161 @@ export async function checkTailwind(dir, pkg) {
|
|
|
947
947
|
hint: 'Run `npx @rtorcato/repo-tooling fix tailwind` to scaffold the v4 PostCSS wiring',
|
|
948
948
|
};
|
|
949
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Both checks below read `package.json` alone — no network, no lockfile. They
|
|
952
|
+
* report `optional-missing` rather than `drift`: each is a policy call a repo
|
|
953
|
+
* can legitimately decide against, so they surface in doctor's output and can
|
|
954
|
+
* be declined in `.repo-tooling.json`, but neither fails a build.
|
|
955
|
+
*/
|
|
956
|
+
/** `[major, minor, patch]` floor of a range, or null when it hasn't got one. */
|
|
957
|
+
export function rangeFloor(range) {
|
|
958
|
+
const m = /^[\s^~>=<v]*(\d+)\.(\d+)\.(\d+)/.exec(range);
|
|
959
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Major.minor only — patch is deliberately ignored. New config keys and new
|
|
963
|
+
* behaviour arrive in minors; a dev floor five patches above the peer floor
|
|
964
|
+
* (`^2.5.0` vs `^2.5.5`) is just a routine bump and flagging it would make
|
|
965
|
+
* this check fire on almost every package.
|
|
966
|
+
*/
|
|
967
|
+
function compareFloor(a, b) {
|
|
968
|
+
return a[0] - b[0] || a[1] - b[1];
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Config files whose `$schema` URL carries the tool version the config is
|
|
972
|
+
* written for, and the package that reads them. One entry per tool; adding
|
|
973
|
+
* another is a line here.
|
|
974
|
+
*/
|
|
975
|
+
const SCHEMA_CONFIGS = [
|
|
976
|
+
{ files: ['biome.json', 'biome.jsonc'], host: 'biomejs.dev', pkg: '@biomejs/biome' },
|
|
977
|
+
];
|
|
978
|
+
/** The version out of `https://biomejs.dev/schemas/2.5.0/schema.json`. */
|
|
979
|
+
export function schemaUrlVersion(url) {
|
|
980
|
+
const m = /\/(\d+\.\d+\.\d+)\//.exec(url);
|
|
981
|
+
return m?.[1] ? rangeFloor(m[1]) : null;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* `"$schema": "..."` without parsing the file. Biome configs may be JSONC, and
|
|
985
|
+
* a comment is enough to break `JSON.parse` — the one field this needs is
|
|
986
|
+
* cheaper and safer to read directly.
|
|
987
|
+
*/
|
|
988
|
+
function readSchemaUrl(contents) {
|
|
989
|
+
return /"\$schema"\s*:\s*"([^"]+)"/.exec(contents)?.[1] ?? null;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* A shipped config written for a newer tool version than the package claims to
|
|
993
|
+
* support (#330). The `$schema` URL is an explicit, machine-readable statement
|
|
994
|
+
* of which version the config targets, so comparing it against the declared
|
|
995
|
+
* dependency floor is exact — no heuristics, no false positives.
|
|
996
|
+
*
|
|
997
|
+
* This is the #330 defect precisely: `tooling/biome/biome.json` carries
|
|
998
|
+
* `$schema` 2.5.0 and uses `linter.rules.preset`, a 2.5 key, while
|
|
999
|
+
* `peerDependencies` advertised `@biomejs/biome: ^2.0.0`. Consumers on 2.0–2.4
|
|
1000
|
+
* got `Found an unknown key \`preset\`` with nothing pointing at the range.
|
|
1001
|
+
*
|
|
1002
|
+
* It reads the same way in a consuming repo: a `biome.json` targeting 2.5.0
|
|
1003
|
+
* with `@biomejs/biome: ^2.3.0` in devDependencies is the identical failure,
|
|
1004
|
+
* one level down.
|
|
1005
|
+
*
|
|
1006
|
+
* The floor is what matters, not the ceiling — `^2.0.0` resolves to the newest
|
|
1007
|
+
* 2.x today, so the repo's own install works and only consumers pinned lower
|
|
1008
|
+
* break. That is exactly why this goes unnoticed.
|
|
1009
|
+
*/
|
|
1010
|
+
export async function checkConfigSchemaVersions(dir, pkg) {
|
|
1011
|
+
const check = 'Config schema versions';
|
|
1012
|
+
// peerDependencies is the contract a publisher offers; devDependencies is
|
|
1013
|
+
// what a leaf repo actually installs. Prefer the former when present.
|
|
1014
|
+
const declared = {
|
|
1015
|
+
...(pkg?.dependencies ?? {}),
|
|
1016
|
+
...(pkg?.devDependencies ?? {}),
|
|
1017
|
+
...(pkg?.peerDependencies ?? {}),
|
|
1018
|
+
};
|
|
1019
|
+
const mismatches = [];
|
|
1020
|
+
let checked = 0;
|
|
1021
|
+
for (const spec of SCHEMA_CONFIGS) {
|
|
1022
|
+
for (const file of spec.files) {
|
|
1023
|
+
const filepath = path.join(dir, file);
|
|
1024
|
+
if (!(await fs.pathExists(filepath)))
|
|
1025
|
+
continue;
|
|
1026
|
+
const url = readSchemaUrl(await fs.readFile(filepath, 'utf8'));
|
|
1027
|
+
if (!url?.includes(spec.host))
|
|
1028
|
+
continue;
|
|
1029
|
+
const schemaVersion = schemaUrlVersion(url);
|
|
1030
|
+
const range = declared[spec.pkg];
|
|
1031
|
+
if (!schemaVersion || !range)
|
|
1032
|
+
continue;
|
|
1033
|
+
const floor = rangeFloor(range);
|
|
1034
|
+
if (!floor)
|
|
1035
|
+
continue;
|
|
1036
|
+
checked++;
|
|
1037
|
+
if (compareFloor(schemaVersion, floor) > 0) {
|
|
1038
|
+
const v = schemaVersion.join('.');
|
|
1039
|
+
mismatches.push(`${file} targets ${spec.pkg} ${v} but the range is ${range}`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (checked === 0) {
|
|
1044
|
+
return { check, status: 'ok', detail: 'no versioned config schemas to compare' };
|
|
1045
|
+
}
|
|
1046
|
+
if (mismatches.length === 0) {
|
|
1047
|
+
return {
|
|
1048
|
+
check,
|
|
1049
|
+
status: 'ok',
|
|
1050
|
+
detail: `${checked} config schema${checked === 1 ? '' : 's'} within the declared version range`,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
check,
|
|
1055
|
+
status: 'optional-missing',
|
|
1056
|
+
detail: `${mismatches.length} config${mismatches.length === 1 ? '' : 's'} written for a newer tool than declared: ${mismatches.join('; ')}`,
|
|
1057
|
+
hint: 'Raise the dependency floor to the version the config targets, or rewrite the config for the oldest version supported. Anyone resolving below the schema version gets a config-parse error that never mentions the version range.',
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/** npm git specifiers, including the bare `owner/repo` GitHub shorthand. */
|
|
1061
|
+
const GIT_PROTOCOL = /^(?:github|gitlab|bitbucket|gist):|^git\+|^git:\/\//;
|
|
1062
|
+
const GITHUB_SHORTHAND = /^[A-Za-z0-9][\w.-]*\/[A-Za-z0-9][\w.-]*(?:#.*)?$/;
|
|
1063
|
+
export function isGitSpecifier(spec) {
|
|
1064
|
+
return GIT_PROTOCOL.test(spec) || GITHUB_SHORTHAND.test(spec);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* A git dependency with no `#ref` (#332). The package manager resolves it once
|
|
1068
|
+
* and pins the commit in the lockfile, so it never moves again until somebody
|
|
1069
|
+
* re-resolves by hand — with no version mismatch, no Dependabot PR, and no
|
|
1070
|
+
* signal of any kind that it has gone stale.
|
|
1071
|
+
*
|
|
1072
|
+
* This is how the docs homepage kept advertising `@rtorcato/js-tooling` for
|
|
1073
|
+
* days after the rename: `github:rtorcato/shared-docs` had been pinned to a
|
|
1074
|
+
* pre-rename commit and nothing could tell.
|
|
1075
|
+
*/
|
|
1076
|
+
export function checkGitDependencies(pkg) {
|
|
1077
|
+
const check = 'Git dependencies';
|
|
1078
|
+
const fields = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
1079
|
+
const refless = [];
|
|
1080
|
+
let total = 0;
|
|
1081
|
+
for (const field of fields) {
|
|
1082
|
+
const deps = pkg?.[field] ?? {};
|
|
1083
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
1084
|
+
if (typeof spec !== 'string' || !isGitSpecifier(spec))
|
|
1085
|
+
continue;
|
|
1086
|
+
total++;
|
|
1087
|
+
if (!spec.includes('#'))
|
|
1088
|
+
refless.push(`${name} (${spec})`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (total === 0) {
|
|
1092
|
+
return { check, status: 'ok', detail: 'no git dependencies' };
|
|
1093
|
+
}
|
|
1094
|
+
if (refless.length === 0) {
|
|
1095
|
+
return {
|
|
1096
|
+
check,
|
|
1097
|
+
status: 'ok',
|
|
1098
|
+
detail: `${total} git dependenc${total === 1 ? 'y' : 'ies'}, all with an explicit ref`,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
check,
|
|
1103
|
+
status: 'optional-missing',
|
|
1104
|
+
detail: `${refless.length} git dependenc${refless.length === 1 ? 'y' : 'ies'} with no ref — pinned to whatever commit was current at install time: ${refless.join('; ')}`,
|
|
1105
|
+
hint: 'Prefer publishing to a registry and depending on a semver range. Failing that, add an explicit ref — `#semver:^1.2.0` is strongest, `#main` at least makes the intent legible and `pnpm update` meaningful.',
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
@@ -10,7 +10,7 @@ import { GH_WORKFLOWS, generateGhWorkflow } from '../../cli/generators/github-wo
|
|
|
10
10
|
import { generateESLintConfig, generatePrettierConfig } from '../../cli/generators/linting.js';
|
|
11
11
|
import { alignNodeVersion, ensureEnginesNode, generateKnipConfig, generateNvmrc, generateSizeLimitConfig, generateVscodeExtensions, } from '../../cli/generators/misc.js';
|
|
12
12
|
import { composeVerifyScriptFromPkg } from '../../cli/generators/package-json.js';
|
|
13
|
-
import { WORKSPACE_FILE, dependsOnEsbuild, ensurePnpmSettings, } from '../../cli/generators/pnpm-workspace.js';
|
|
13
|
+
import { WORKSPACE_FILE, dependsOnEsbuild, ensurePnpmSettings, familyGlob, } from '../../cli/generators/pnpm-workspace.js';
|
|
14
14
|
import { generatePostcss } from '../../cli/generators/postcss.js';
|
|
15
15
|
import { generateCypressConfig, generateVitestConfig } from '../../cli/generators/testing.js';
|
|
16
16
|
import { generateTreeshakeCheck, inferSubpathsFromExports } from '../../cli/generators/treeshake.js';
|
|
@@ -351,7 +351,7 @@ export const FIXERS = [
|
|
|
351
351
|
...(pkg?.dependencies ?? {}),
|
|
352
352
|
...(pkg?.devDependencies ?? {}),
|
|
353
353
|
};
|
|
354
|
-
const written = await ensurePnpmSettings(targetDir, dependsOnEsbuild(deps));
|
|
354
|
+
const written = await ensurePnpmSettings(targetDir, dependsOnEsbuild(deps), familyGlob(pkg?.name));
|
|
355
355
|
return { filesWritten: written ? [written] : [] };
|
|
356
356
|
},
|
|
357
357
|
},
|
package/package.json
CHANGED