@elia97/officina 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/README.md +57 -15
- package/dist/bin.js +0 -0
- package/dist/bundle.d.ts +1 -0
- package/dist/bundle.js +1 -0
- package/dist/checks/analytics.js +10 -4
- package/dist/checks/bundle.js +10 -2
- package/dist/checks/doctor.js +33 -6
- package/dist/checks/lighthouse.d.ts +1 -1
- package/dist/checks/lighthouse.js +8 -4
- package/dist/checks/roadmap.d.ts +1 -1
- package/dist/checks/roadmap.js +11 -5
- package/dist/checks/smoke.js +3 -2
- package/dist/checks/vercel-cli.d.ts +1 -1
- package/dist/checks/vercel-cli.js +35 -21
- package/dist/gen/hook-points.d.mts +6 -0
- package/dist/gen/hook-points.mjs +13 -0
- package/dist/gen/inject-section.d.mts +25 -2
- package/dist/gen/inject-section.mjs +13 -13
- package/dist/gen/inject-strings.d.mts +3 -0
- package/dist/gen/inject-strings.mjs +7 -3
- package/dist/gen/section-targets.d.mts +5 -0
- package/dist/gen/section-targets.mjs +23 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +2 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -4
- package/dist/lib/alignment.d.ts +2 -6
- package/dist/lib/alignment.js +25 -52
- package/dist/lib/anchors.d.ts +2 -0
- package/dist/lib/anchors.js +35 -0
- package/dist/lib/bundle-budget.d.ts +6 -1
- package/dist/lib/bundle-budget.js +9 -6
- package/dist/lib/config-gaps.d.ts +7 -0
- package/dist/lib/config-gaps.js +29 -0
- package/dist/lib/config.d.ts +16 -1
- package/dist/lib/config.js +36 -4
- package/dist/lib/contract.d.ts +8 -0
- package/dist/lib/contract.js +51 -6
- package/dist/lib/pins.d.ts +10 -0
- package/dist/lib/pins.js +48 -0
- package/dist/lib/routes.d.ts +6 -2
- package/dist/lib/routes.js +16 -7
- package/dist/lib/tooling.d.ts +3 -0
- package/dist/lib/tooling.js +24 -0
- package/dist/lib/validate.d.ts +11 -0
- package/dist/lib/validate.js +40 -0
- package/dist/lib/versions.d.ts +5 -0
- package/dist/lib/versions.js +38 -0
- package/dist/lib/workflows.d.ts +6 -0
- package/dist/lib/workflows.js +70 -0
- package/dist/sh/lhci-local.sh +2 -2
- package/dist/smoke.d.ts +2 -0
- package/dist/smoke.js +4 -0
- package/package.json +28 -12
- package/dist/lib/vercel-cli-pin.d.ts +0 -2
- package/dist/lib/vercel-cli-pin.js +0 -29
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { peerRange, withinRange } from './versions.js';
|
|
2
|
+
// Strumenti che i preset e le action danno per scontati: senza, il progetto sembra allineato e poi
|
|
3
|
+
// il primo commit o il primo job li cerca e non li trova.
|
|
4
|
+
const REQUIRED = [
|
|
5
|
+
{ name: '@biomejs/biome', reason: 'il progetto estende `@elia97/officina/biome`' },
|
|
6
|
+
{ name: 'lefthook', reason: 'il preset dei git hook è suo, e `prepare` lo installa' },
|
|
7
|
+
{ name: '@commitlint/cli', reason: 'il preset di lefthook lo lancia su ogni messaggio di commit' },
|
|
8
|
+
{ name: '@commitlint/config-conventional', reason: 'è la configurazione che commitlint estende' },
|
|
9
|
+
{ name: 'fallow', reason: '`check:deadcode` e `check:health` lo lanciano' },
|
|
10
|
+
];
|
|
11
|
+
const BIOME = '@biomejs/biome';
|
|
12
|
+
const MANIFEST = 'package.json';
|
|
13
|
+
// Il preset usa una regola `nursery`, che fra una major e l'altra di Biome cambia nome o sparisce:
|
|
14
|
+
// l'intervallo che regge lo dichiara il pacchetto, in un posto solo.
|
|
15
|
+
function biomeRangeGap(declared) {
|
|
16
|
+
const range = peerRange(BIOME);
|
|
17
|
+
return withinRange(declared, range) ? undefined : `\`${BIOME}\` è \`${declared}\`: il preset regge \`${range}\``;
|
|
18
|
+
}
|
|
19
|
+
export function toolingGaps({ devDependencies = {} }) {
|
|
20
|
+
const gaps = REQUIRED.filter(({ name }) => devDependencies[name] === undefined).map(({ name, reason }) => ({ path: MANIFEST, message: `\`${name}\` non è fra le devDependencies: ${reason}` }));
|
|
21
|
+
const biome = devDependencies[BIOME];
|
|
22
|
+
const range = biome === undefined ? undefined : biomeRangeGap(biome);
|
|
23
|
+
return range === undefined ? gaps : [...gaps, { path: MANIFEST, message: range }];
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Check = (value: unknown, path: string) => string[];
|
|
2
|
+
export declare function typeName(value: unknown): string;
|
|
3
|
+
export declare const problem: (path: string, expected: string, value: unknown) => string;
|
|
4
|
+
export declare const aString: Check;
|
|
5
|
+
export declare const aNumber: Check;
|
|
6
|
+
export declare const aFunction: Check;
|
|
7
|
+
export declare const aStringOrNull: Check;
|
|
8
|
+
export declare const anArrayOf: (item: Check) => Check;
|
|
9
|
+
export declare const aRecordOf: (item: Check) => Check;
|
|
10
|
+
export declare const oneOf: (expected: string, allowed: readonly unknown[]) => Check;
|
|
11
|
+
export declare const aShape: (fields: Record<string, Check>) => Check;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
const NAMES = new Map([
|
|
3
|
+
['string', 'una stringa'],
|
|
4
|
+
['number', 'un numero'],
|
|
5
|
+
['boolean', 'un booleano'],
|
|
6
|
+
['function', 'una funzione'],
|
|
7
|
+
['undefined', 'niente'],
|
|
8
|
+
]);
|
|
9
|
+
export function typeName(value) {
|
|
10
|
+
if (value === null)
|
|
11
|
+
return 'null';
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return 'un array';
|
|
14
|
+
return NAMES.get(typeof value) ?? 'un oggetto';
|
|
15
|
+
}
|
|
16
|
+
export const problem = (path, expected, value) => `${path}: atteso ${expected}, ricevuto ${typeName(value)}`;
|
|
17
|
+
export const aString = (value, path) => (typeof value === 'string' ? [] : [problem(path, 'una stringa', value)]);
|
|
18
|
+
export const aNumber = (value, path) => (typeof value === 'number' ? [] : [problem(path, 'un numero', value)]);
|
|
19
|
+
export const aFunction = (value, path) => typeof value === 'function' ? [] : [problem(path, 'una funzione', value)];
|
|
20
|
+
export const aStringOrNull = (value, path) => typeof value === 'string' || value === null ? [] : [problem(path, 'una stringa oppure null', value)];
|
|
21
|
+
export const anArrayOf = (item) => (value, path) => Array.isArray(value)
|
|
22
|
+
? value.flatMap((entry, index) => item(entry, `${path}[${index}]`))
|
|
23
|
+
: [problem(path, 'un array', value)];
|
|
24
|
+
export const aRecordOf = (item) => (value, path) => isRecord(value)
|
|
25
|
+
? Object.entries(value).flatMap(([key, entry]) => item(entry, `${path}.${key}`))
|
|
26
|
+
: [problem(path, 'un oggetto', value)];
|
|
27
|
+
export const oneOf = (expected, allowed) => (value, path) => allowed.includes(value) ? [] : [problem(path, expected, value)];
|
|
28
|
+
// Una voce non dichiarata è un errore: `sitUrl` al posto di `siteUrl` non deve passare in
|
|
29
|
+
// silenzio, che è il modo esatto in cui una configurazione smette di dire qualcosa.
|
|
30
|
+
export const aShape = (fields) => (value, path) => {
|
|
31
|
+
if (!isRecord(value))
|
|
32
|
+
return [problem(path, 'un oggetto', value)];
|
|
33
|
+
const prefix = path === '' ? '' : `${path}.`;
|
|
34
|
+
return Object.entries(value).flatMap(([key, entry]) => {
|
|
35
|
+
const check = fields[key];
|
|
36
|
+
if (check === undefined)
|
|
37
|
+
return [`${prefix}${key}: voce sconosciuta`];
|
|
38
|
+
return entry === undefined ? [] : check(entry, `${prefix}${key}`);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const packageVersion: () => string;
|
|
2
|
+
/** L'intervallo che il pacchetto dichiara di reggere: sta in un posto solo, le sue peerDependencies. */
|
|
3
|
+
export declare const peerRange: (name: string) => string;
|
|
4
|
+
/** Vero solo se ogni versione che `spec` può installare sta dentro `range`, scritto `>=x.y.z <a.b.c`. */
|
|
5
|
+
export declare function withinRange(spec: string, range: string): boolean;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
const self = () => JSON.parse(readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
|
|
4
|
+
export const packageVersion = () => self().version;
|
|
5
|
+
/** L'intervallo che il pacchetto dichiara di reggere: sta in un posto solo, le sue peerDependencies. */
|
|
6
|
+
export const peerRange = (name) => self().peerDependencies?.[name] ?? '';
|
|
7
|
+
const EXACT = /(\d+)\.(\d+)\.(\d+)/;
|
|
8
|
+
function parseVersion(text) {
|
|
9
|
+
const found = EXACT.exec(text);
|
|
10
|
+
return found === null ? undefined : [Number(found[1]), Number(found[2]), Number(found[3])];
|
|
11
|
+
}
|
|
12
|
+
const compare = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
13
|
+
// Il limite superiore escluso di ciò che lo specificatore può installare: `^` sale fino alla major
|
|
14
|
+
// dopo (fino alla minor dopo, sotto lo zero), `~` fino alla minor dopo, una versione esatta a sé.
|
|
15
|
+
function ceiling(spec, [major, minor, patch]) {
|
|
16
|
+
if (spec.startsWith('^'))
|
|
17
|
+
return major === 0 ? [0, minor + 1, 0] : [major + 1, 0, 0];
|
|
18
|
+
if (spec.startsWith('~'))
|
|
19
|
+
return [major, minor + 1, 0];
|
|
20
|
+
return EXACT.test(spec.replace(/^[=v]/, '')) && !/[<>|*x\s]/.test(spec) ? [major, minor, patch + 1] : undefined;
|
|
21
|
+
}
|
|
22
|
+
function parseBounds(range) {
|
|
23
|
+
const [low = '', high = '', extra] = range.split(' ');
|
|
24
|
+
if (extra !== undefined || !low.startsWith('>=') || !high.startsWith('<'))
|
|
25
|
+
return undefined;
|
|
26
|
+
const min = parseVersion(low);
|
|
27
|
+
const max = parseVersion(high);
|
|
28
|
+
return min === undefined || max === undefined ? undefined : { min, max };
|
|
29
|
+
}
|
|
30
|
+
/** Vero solo se ogni versione che `spec` può installare sta dentro `range`, scritto `>=x.y.z <a.b.c`. */
|
|
31
|
+
export function withinRange(spec, range) {
|
|
32
|
+
const bounds = parseBounds(range);
|
|
33
|
+
const floor = parseVersion(spec);
|
|
34
|
+
if (bounds === undefined || floor === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
const top = ceiling(spec, floor);
|
|
37
|
+
return top === undefined ? false : compare(floor, bounds.min) >= 0 && compare(top, bounds.max) <= 0;
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProjectFiles } from './alignment.ts';
|
|
2
|
+
import type { ContractGap } from './contract.ts';
|
|
3
|
+
export declare const mentions: (source: string | undefined, needle: string) => boolean;
|
|
4
|
+
/** Le action del progetto devono stare alla stessa versione del pacchetto installato. */
|
|
5
|
+
export declare function workflowGaps({ read }: ProjectFiles, version: string): ContractGap[];
|
|
6
|
+
export declare function dependabotGaps({ read }: ProjectFiles): ContractGap[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// I workflow restano una ricerca testuale — non c'è un parser YAML nel pacchetto — ma una riga
|
|
2
|
+
// commentata non deve soddisfare niente: è il modo più facile per avere un progetto «a posto»
|
|
3
|
+
// che in CI non lancia nessuno di questi passi.
|
|
4
|
+
const liveLines = (source) => source.split(/\r?\n/).filter((line) => !line.trimStart().startsWith('#'));
|
|
5
|
+
export const mentions = (source, needle) => source !== undefined && liveLines(source).some((line) => line.includes(needle));
|
|
6
|
+
const ACTIONS = [
|
|
7
|
+
{ workflow: '.github/workflows/ci.yml', action: 'ci' },
|
|
8
|
+
{ workflow: '.github/workflows/ci.yml', action: 'review' },
|
|
9
|
+
{ workflow: '.github/workflows/deploy.yml', action: 'deploy' },
|
|
10
|
+
{ workflow: '.github/workflows/lighthouse.yml', action: 'lighthouse' },
|
|
11
|
+
];
|
|
12
|
+
// Un tag di versione o uno SHA intero: `@main` farebbe girare in produzione passi mai collaudati.
|
|
13
|
+
const TAG = /^v\d+\.\d+\.\d+$/;
|
|
14
|
+
const SHA = /^[0-9a-f]{40}$/;
|
|
15
|
+
const COMMENTED_VERSION = /#\s*(v\d+\.\d+\.\d+)/;
|
|
16
|
+
function refOf(source, name) {
|
|
17
|
+
const pattern = new RegExp(`${name}@(\\S+)`);
|
|
18
|
+
for (const line of liveLines(source)) {
|
|
19
|
+
const ref = pattern.exec(line)?.[1];
|
|
20
|
+
if (ref !== undefined)
|
|
21
|
+
return { line, ref };
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function refGap(line, ref, name, expected) {
|
|
26
|
+
if (TAG.test(ref)) {
|
|
27
|
+
return ref === expected ? undefined : `\`${name}@${ref}\`: il pacchetto installato è la ${expected.slice(1)}`;
|
|
28
|
+
}
|
|
29
|
+
if (!SHA.test(ref))
|
|
30
|
+
return `\`${name}@${ref}\`: il riferimento va fissato a un tag di versione o a uno SHA`;
|
|
31
|
+
const declared = COMMENTED_VERSION.exec(line)?.[1];
|
|
32
|
+
if (declared === undefined) {
|
|
33
|
+
return `\`${name}@${ref.slice(0, 7)}…\`: uno SHA non dice che versione è — scrivila nel commento (\`# ${expected}\`)`;
|
|
34
|
+
}
|
|
35
|
+
return declared === expected
|
|
36
|
+
? undefined
|
|
37
|
+
: `\`${name}@${ref.slice(0, 7)}… ${declared}\`: il pacchetto installato è la ${expected.slice(1)}`;
|
|
38
|
+
}
|
|
39
|
+
function actionGap(source, action, version) {
|
|
40
|
+
const name = `Elia97/officina/actions/${action}`;
|
|
41
|
+
if (source === undefined)
|
|
42
|
+
return `manca: i suoi passi arrivano da \`${name}\``;
|
|
43
|
+
const found = refOf(source, name);
|
|
44
|
+
return found === undefined ? `non usa \`${name}\`` : refGap(found.line, found.ref, name, `v${version}`);
|
|
45
|
+
}
|
|
46
|
+
/** Le action del progetto devono stare alla stessa versione del pacchetto installato. */
|
|
47
|
+
export function workflowGaps({ read }, version) {
|
|
48
|
+
return ACTIONS.flatMap(({ workflow, action }) => {
|
|
49
|
+
const message = actionGap(read(workflow), action, version);
|
|
50
|
+
return message === undefined ? [] : [{ path: workflow, message }];
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const DEPENDABOT = '.github/dependabot.yml';
|
|
54
|
+
// Senza il gruppo, Dependabot apre due PR — una per il pacchetto npm, una per le action — e il
|
|
55
|
+
// controllo di coerenza qui sopra le boccia entrambe, perché nessuna delle due allinea l'altra.
|
|
56
|
+
const GROUPED = [
|
|
57
|
+
{ needle: 'multi-ecosystem-groups', reason: 'il pacchetto e le sue action si alzano in una PR sola' },
|
|
58
|
+
{ needle: '@elia97/officina', reason: 'il gruppo deve prendere il pacchetto da npm' },
|
|
59
|
+
{ needle: 'Elia97/officina/*', reason: 'il gruppo deve prendere le action da github-actions' },
|
|
60
|
+
];
|
|
61
|
+
export function dependabotGaps({ read }) {
|
|
62
|
+
const source = read(DEPENDABOT);
|
|
63
|
+
if (source === undefined) {
|
|
64
|
+
return [{ path: DEPENDABOT, message: 'manca: senza, il pacchetto e le sue action si alzano separati' }];
|
|
65
|
+
}
|
|
66
|
+
return GROUPED.filter(({ needle }) => !mentions(source, needle)).map(({ needle, reason }) => ({
|
|
67
|
+
path: DEPENDABOT,
|
|
68
|
+
message: `non nomina \`${needle}\`: ${reason}`,
|
|
69
|
+
}));
|
|
70
|
+
}
|
package/dist/sh/lhci-local.sh
CHANGED
|
@@ -38,7 +38,7 @@ SERVER_PID=''
|
|
|
38
38
|
cleanup() {
|
|
39
39
|
if [[ -n "${SERVER_PID}" ]]; then
|
|
40
40
|
kill -- "-${SERVER_PID}" 2>/dev/null || kill "${SERVER_PID}" 2>/dev/null || true
|
|
41
|
-
pkill -f "serve@14 .*--listen ${PORT}" 2>/dev/null || true
|
|
41
|
+
pkill -f "serve@14.2.6 .*--listen ${PORT}" 2>/dev/null || true
|
|
42
42
|
fi
|
|
43
43
|
find . -maxdepth 1 \( -name '*wsl.localhost*' -o -name 'undefined*' \) -exec rm -rf {} + 2>/dev/null || true
|
|
44
44
|
}
|
|
@@ -55,7 +55,7 @@ fi
|
|
|
55
55
|
echo "✓ robots.txt: Allow: /"
|
|
56
56
|
|
|
57
57
|
echo "→ avvio del server statico su :${PORT}"
|
|
58
|
-
setsid pnpm dlx serve@14 "${OUT_DIR}/client" --listen "${PORT}" --no-clipboard >/dev/null 2>&1 &
|
|
58
|
+
setsid pnpm dlx serve@14.2.6 "${OUT_DIR}/client" --listen "${PORT}" --no-clipboard >/dev/null 2>&1 &
|
|
59
59
|
SERVER_PID=$!
|
|
60
60
|
|
|
61
61
|
for _ in $(seq 1 60); do
|
package/dist/smoke.d.ts
ADDED
package/dist/smoke.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// I pezzi con cui un progetto compone i propri controlli di smoke: `smoke.checks` sostituisce la
|
|
2
|
+
// lista del pacchetto, quindi chi ne toglie uno ricompone la lista da qui.
|
|
3
|
+
export { NON_HTML_ROUTES } from './lib/routes.js';
|
|
4
|
+
export { checkBotIdChallenge, checkCanonicalHost, checkPages, checkSecurityHeaders, checkTrailingSlash, DEFAULT_CHECKS, SECURITY_HEADERS, } from './lib/smoke-production.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elia97/officina",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Gli strumenti dei progetti nati dai template di Elia: gate di qualità, generatori e verifiche di build e di produzione.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./smoke": {
|
|
17
|
+
"types": "./dist/smoke.d.ts",
|
|
18
|
+
"default": "./dist/smoke.js"
|
|
19
|
+
},
|
|
20
|
+
"./bundle": {
|
|
21
|
+
"types": "./dist/bundle.d.ts",
|
|
22
|
+
"default": "./dist/bundle.js"
|
|
23
|
+
},
|
|
24
|
+
"./icons": {
|
|
25
|
+
"types": "./dist/icons.d.ts",
|
|
26
|
+
"default": "./dist/icons.js"
|
|
27
|
+
},
|
|
16
28
|
"./biome": "./presets/biome.base.json"
|
|
17
29
|
},
|
|
18
30
|
"bin": {
|
|
@@ -24,11 +36,23 @@
|
|
|
24
36
|
"README.md"
|
|
25
37
|
],
|
|
26
38
|
"engines": {
|
|
27
|
-
"node": ">=24.
|
|
39
|
+
"node": ">=24.2.0"
|
|
28
40
|
},
|
|
41
|
+
"packageManager": "pnpm@11.22.0",
|
|
29
42
|
"publishConfig": {
|
|
30
43
|
"access": "public"
|
|
31
44
|
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp -r src/gen/templates dist/gen/templates && cp -r src/sh dist/sh",
|
|
47
|
+
"prepack": "pnpm run build",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run --coverage",
|
|
50
|
+
"format": "biome check --write .",
|
|
51
|
+
"check:deadcode": "fallow dead-code",
|
|
52
|
+
"check:health": "fallow health --complexity --coverage coverage/coverage-final.json",
|
|
53
|
+
"ci": "biome ci --error-on-warnings . && pnpm run typecheck && pnpm run check:deadcode && pnpm run test && pnpm run check:health",
|
|
54
|
+
"test:pack": "bash scripts/pack-test.sh"
|
|
55
|
+
},
|
|
32
56
|
"devDependencies": {
|
|
33
57
|
"@biomejs/biome": "2.5.10",
|
|
34
58
|
"@types/node": "^24.13.5",
|
|
@@ -42,20 +66,12 @@
|
|
|
42
66
|
"ts-morph": "^28.0.0"
|
|
43
67
|
},
|
|
44
68
|
"peerDependencies": {
|
|
69
|
+
"@biomejs/biome": ">=2.5.10 <3.0.0",
|
|
45
70
|
"sharp": ">=0.34.0"
|
|
46
71
|
},
|
|
47
72
|
"peerDependenciesMeta": {
|
|
48
73
|
"sharp": {
|
|
49
74
|
"optional": true
|
|
50
75
|
}
|
|
51
|
-
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp -r src/gen/templates dist/gen/templates && cp -r src/sh dist/sh",
|
|
54
|
-
"typecheck": "tsc --noEmit",
|
|
55
|
-
"test": "vitest run --coverage",
|
|
56
|
-
"format": "biome check --write .",
|
|
57
|
-
"check:deadcode": "fallow dead-code",
|
|
58
|
-
"check:health": "fallow health --complexity --coverage coverage/coverage-final.json",
|
|
59
|
-
"ci": "biome ci --error-on-warnings . && pnpm run typecheck && pnpm run check:deadcode && pnpm run test && pnpm run check:health"
|
|
60
76
|
}
|
|
61
|
-
}
|
|
77
|
+
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { readLines } from './cli.js';
|
|
2
|
-
const PIN = /\bvercel@(\d+)\b/;
|
|
3
|
-
function pinnedVersions(workflow) {
|
|
4
|
-
return readLines(workflow).flatMap(({ n, text }) => {
|
|
5
|
-
const major = Number(PIN.exec(text)?.[1]);
|
|
6
|
-
return Number.isNaN(major) ? [] : [{ line: n, major }];
|
|
7
|
-
});
|
|
8
|
-
}
|
|
9
|
-
function publishedMajor(version) {
|
|
10
|
-
return Number(/^\s*(\d+)\./.exec(version)?.[1]);
|
|
11
|
-
}
|
|
12
|
-
export function pinFindings(workflow, latest) {
|
|
13
|
-
const pins = pinnedVersions(workflow);
|
|
14
|
-
if (pins.length === 0)
|
|
15
|
-
return [{ line: 1, message: 'nessun `vercel@<major>`: il pin è sparito dal workflow' }];
|
|
16
|
-
const majors = [...new Set(pins.map(({ major }) => major))].sort((a, b) => a - b);
|
|
17
|
-
if (majors.length > 1)
|
|
18
|
-
return pins.map(({ line, major }) => ({
|
|
19
|
-
line,
|
|
20
|
-
message: `vercel@${major}: il workflow porta major diverse (${majors.join(', ')})`,
|
|
21
|
-
}));
|
|
22
|
-
const published = publishedMajor(latest);
|
|
23
|
-
if (Number.isNaN(published))
|
|
24
|
-
return [{ line: 1, message: `versione pubblicata illeggibile: ${JSON.stringify(latest)}` }];
|
|
25
|
-
const pinned = Math.max(...pins.map(({ major }) => major));
|
|
26
|
-
if (pinned >= published)
|
|
27
|
-
return [];
|
|
28
|
-
return pins.map(({ line }) => ({ line, message: `vercel@${pinned}: su npm è pubblicata la major ${published}` }));
|
|
29
|
-
}
|