Skip to content

Commit 96f0312

Browse files
committed
feat(plugin-js-packages): add setup wizard binding
1 parent 7a1f95a commit 96f0312

8 files changed

Lines changed: 405 additions & 4 deletions

File tree

‎packages/create-cli/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ Each plugin exposes its own configuration keys that can be passed as CLI argumen
4949
|**`--coverage.continueOnFail`**|`boolean`|`true`| Continue if test command fails |
5050
|**`--coverage.categories`**|`boolean`|`true`| Add Code coverage categories |
5151

52+
#### JS Packages
53+
54+
| Option | Type | Default | Description |
55+
| ------------------------------------ | ---------------------------------------------------------- | ------------- | -------------------------- |
56+
|**`--js-packages.packageManager`**|`'npm'`\|`'yarn-classic'`\|`'yarn-modern'`\|`'pnpm'`| auto-detected | Package manager |
57+
|**`--js-packages.checks`**|`('audit'`\|`'outdated')[]`| both | Checks to run |
58+
|**`--js-packages.dependencyGroups`**|`('prod'`\|`'dev'`\|`'optional')[]`|`prod`, `dev`| Dependency groups |
59+
|**`--js-packages.categories`**|`boolean`|`true`| Add JS packages categories |
60+
5261
### Examples
5362

5463
Run interactively (default):

‎packages/create-cli/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"dependencies": {
2929
"@code-pushup/coverage-plugin": "0.121.0",
3030
"@code-pushup/eslint-plugin": "0.121.0",
31+
"@code-pushup/js-packages-plugin": "0.121.0",
3132
"@code-pushup/models": "0.121.0",
3233
"@code-pushup/utils": "0.121.0",
3334
"@inquirer/prompts": "^8.0.0",

‎packages/create-cli/src/index.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import yargs from 'yargs';
33
import{hideBin}from'yargs/helpers';
44
import{coverageSetupBinding}from'@code-pushup/coverage-plugin';
55
import{eslintSetupBinding}from'@code-pushup/eslint-plugin';
6+
import{jsPackagesSetupBinding}from'@code-pushup/js-packages-plugin';
67
import{parsePluginSlugs,validatePluginSlugs}from'./lib/setup/plugins.js';
78
import{
89
CI_PROVIDERS,
@@ -12,10 +13,11 @@ import {
1213
}from'./lib/setup/types.js';
1314
import{runSetupWizard}from'./lib/setup/wizard.js';
1415

15-
// TODO: create, import and pass remaining plugin bindings (lighthouse, typescript, js-packages, jsdocs, axe)
16+
// TODO: create, import and pass remaining plugin bindings (lighthouse, typescript, jsdocs, axe)
1617
constbindings: PluginSetupBinding[]=[
1718
eslintSetupBinding,
1819
coverageSetupBinding,
20+
jsPackagesSetupBinding,
1921
];
2022

2123
constargv=awaityargs(hideBin(process.argv))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import{jsPackagesPlugin}from'./lib/js-packages-plugin.js';
22

33
exportdefaultjsPackagesPlugin;
4+
export{jsPackagesSetupBinding}from'./lib/binding.js';
45
exporttype{JSPackagesPluginConfig}from'./lib/config.js';
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import{createRequire}from'node:module';
2+
importpathfrom'node:path';
3+
importtype{
4+
CategoryConfig,
5+
PluginAnswer,
6+
PluginSetupBinding,
7+
}from'@code-pushup/models';
8+
import{
9+
answerArray,
10+
answerBoolean,
11+
answerString,
12+
fileExists,
13+
singleQuote,
14+
}from'@code-pushup/utils';
15+
importtype{PackageManagerId}from'./config.js';
16+
import{
17+
DEFAULT_CHECKS,
18+
DEFAULT_DEPENDENCY_GROUPS,
19+
JS_PACKAGES_PLUGIN_SLUG,
20+
JS_PACKAGES_PLUGIN_TITLE,
21+
}from'./constants.js';
22+
import{derivePackageManager}from'./package-managers/derive-package-manager.js';
23+
24+
const{name: PACKAGE_NAME}=createRequire(import.meta.url)(
25+
'../../package.json',
26+
)astypeofimport('../../package.json');
27+
28+
constDEFAULT_PACKAGE_MANAGER='npm';
29+
30+
constPACKAGE_MANAGERS=[
31+
{name: 'npm',value: DEFAULT_PACKAGE_MANAGER},
32+
{name: 'yarn (classic)',value: 'yarn-classic'},
33+
{name: 'yarn (modern)',value: 'yarn-modern'},
34+
{name: 'pnpm',value: 'pnpm'},
35+
]asconst;
36+
37+
constCHECKS=[
38+
{name: 'audit (security vulnerabilities)',value: 'audit'},
39+
{name: 'outdated (outdated dependencies)',value: 'outdated'},
40+
]asconst;
41+
42+
constDEPENDENCY_GROUPS=[
43+
{name: 'production',value: 'prod'},
44+
{name: 'development',value: 'dev'},
45+
{name: 'optional',value: 'optional'},
46+
]asconst;
47+
48+
constCATEGORIES=[
49+
{
50+
check: 'audit',
51+
slug: 'security',
52+
title: 'Security',
53+
description: 'Finds known **vulnerabilities** in third-party packages.',
54+
},
55+
{
56+
check: 'outdated',
57+
slug: 'updates',
58+
title: 'Updates',
59+
description: 'Finds **outdated** third-party packages.',
60+
},
61+
];
62+
63+
typeJsPackagesOptions={
64+
packageManager: string;
65+
checks: string[];
66+
dependencyGroups: string[];
67+
categories: boolean;
68+
};
69+
70+
exportconstjsPackagesSetupBinding={
71+
slug: JS_PACKAGES_PLUGIN_SLUG,
72+
title: JS_PACKAGES_PLUGIN_TITLE,
73+
packageName: PACKAGE_NAME,
74+
isRecommended,
75+
prompts: async(targetDir: string)=>{
76+
constpackageManager=awaitdetectPackageManager(targetDir);
77+
return[
78+
{
79+
key: 'js-packages.packageManager',
80+
message: 'Package manager',
81+
type: 'select',
82+
choices: [...PACKAGE_MANAGERS],
83+
default: packageManager,
84+
},
85+
{
86+
key: 'js-packages.checks',
87+
message: 'Checks to run',
88+
type: 'checkbox',
89+
choices: [...CHECKS],
90+
default: [...DEFAULT_CHECKS],
91+
},
92+
{
93+
key: 'js-packages.dependencyGroups',
94+
message: 'Dependency groups',
95+
type: 'checkbox',
96+
choices: [...DEPENDENCY_GROUPS],
97+
default: [...DEFAULT_DEPENDENCY_GROUPS],
98+
},
99+
{
100+
key: 'js-packages.categories',
101+
message: 'Add JS packages categories?',
102+
type: 'confirm',
103+
default: true,
104+
},
105+
];
106+
},
107+
generateConfig: (answers: Record<string,PluginAnswer>)=>{
108+
constoptions=parseAnswers(answers);
109+
return{
110+
imports: [
111+
{moduleSpecifier: PACKAGE_NAME,defaultImport: 'jsPackagesPlugin'},
112+
],
113+
pluginInit: formatPluginInit(options),
114+
...(options.categories ? {categories: createCategories(options)} : {}),
115+
};
116+
},
117+
}satisfiesPluginSetupBinding;
118+
119+
functionparseAnswers(
120+
answers: Record<string,PluginAnswer>,
121+
): JsPackagesOptions{
122+
return{
123+
packageManager:
124+
answerString(answers,'js-packages.packageManager')||
125+
DEFAULT_PACKAGE_MANAGER,
126+
checks: answerArray(answers,'js-packages.checks'),
127+
dependencyGroups: answerArray(answers,'js-packages.dependencyGroups'),
128+
categories: answerBoolean(answers,'js-packages.categories'),
129+
};
130+
}
131+
132+
functionformatPluginInit(options: JsPackagesOptions): string[]{
133+
const{ packageManager, checks, dependencyGroups }=options;
134+
135+
consthasNonDefaultChecks=
136+
checks.length>0&&checks.length<DEFAULT_CHECKS.length;
137+
consthasNonDefaultDepGroups=
138+
dependencyGroups.length!==DEFAULT_DEPENDENCY_GROUPS.length||
139+
!DEFAULT_DEPENDENCY_GROUPS.every(g=>dependencyGroups.includes(g));
140+
141+
constbody=[
142+
`packageManager: ${singleQuote(packageManager)},`,
143+
hasNonDefaultChecks
144+
? `checks: [${checks.map(singleQuote).join(', ')}],`
145+
: '',
146+
hasNonDefaultDepGroups
147+
? `dependencyGroups: [${dependencyGroups.map(singleQuote).join(', ')}],`
148+
: '',
149+
].filter(Boolean);
150+
151+
return['await jsPackagesPlugin({', ...body.map(line=>` ${line}`),'}),'];
152+
}
153+
154+
functioncreateCategories({
155+
packageManager,
156+
checks,
157+
}: JsPackagesOptions): CategoryConfig[]{
158+
returnCATEGORIES.filter(({ check })=>checks.includes(check)).map(
159+
({ check, slug, title, description })=>({
160+
slug,
161+
title,
162+
description,
163+
refs: [
164+
{
165+
type: 'group',
166+
plugin: JS_PACKAGES_PLUGIN_SLUG,
167+
slug: `${packageManager}-${check}`,
168+
weight: 1,
169+
},
170+
],
171+
}),
172+
);
173+
}
174+
175+
asyncfunctionisRecommended(targetDir: string): Promise<boolean>{
176+
returnfileExists(path.join(targetDir,'package.json'));
177+
}
178+
179+
asyncfunctiondetectPackageManager(
180+
targetDir: string,
181+
): Promise<PackageManagerId>{
182+
try{
183+
returnawaitderivePackageManager(targetDir);
184+
}catch{
185+
returnDEFAULT_PACKAGE_MANAGER;
186+
}
187+
}

0 commit comments

Comments
 (0)