@pagopa/dx-cli 0.4.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.
- package/README.md +85 -0
- package/bin/index.js +445 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @pagopa/dx-cli
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
<img src="../../assets/pagopa-logo.png" width="100" alt="PagoPA logo">
|
|
6
|
+
|
|
7
|
+
# DX CLI
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<i align="center">A CLI tool for managing DevEx (Developer Experience) guidelines and best practices 🚀</i>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## 📖 Overview
|
|
16
|
+
|
|
17
|
+
The DX CLI is a command-line tool designed to help developers manage and validate their development setup according to PagoPA's DevEx guidelines. It provides automated checks and validations to ensure repositories follow the established best practices and conventions.
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Repository Validation**: Verify repository setup against DevEx guidelines
|
|
22
|
+
- **Monorepo Script Checking**: Validate that required scripts are present in package.json
|
|
23
|
+
- **Developer Experience Optimization**: Ensure consistent development practices across projects
|
|
24
|
+
|
|
25
|
+
## 🚀 Installation
|
|
26
|
+
|
|
27
|
+
> [!NOTE]
|
|
28
|
+
> The CLI is currently only available locally and is not yet distributed through package managers.
|
|
29
|
+
|
|
30
|
+
### From Source (Development)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Clone the repository
|
|
34
|
+
git clone https://github.com/pagopa/dx.git
|
|
35
|
+
cd dx
|
|
36
|
+
|
|
37
|
+
# Install dependencies
|
|
38
|
+
yarn install
|
|
39
|
+
|
|
40
|
+
# Build the CLI
|
|
41
|
+
yarn build
|
|
42
|
+
|
|
43
|
+
# Run the CLI
|
|
44
|
+
node ./apps/cli/bin/index.js --help
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🛠️ Usage
|
|
48
|
+
|
|
49
|
+
### Available Commands
|
|
50
|
+
|
|
51
|
+
#### `doctor`
|
|
52
|
+
|
|
53
|
+
Verify the repository setup according to the DevEx guidelines.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
dx doctor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This command will:
|
|
60
|
+
|
|
61
|
+
- Check if you're in a valid Git repository
|
|
62
|
+
- Validate that required monorepo scripts are present in package.json
|
|
63
|
+
- Check that the `turbo.json` file exists
|
|
64
|
+
- Verify that the installed `turbo` version meets the minimum requirements
|
|
65
|
+
|
|
66
|
+
**Example output:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
$ dx doctor
|
|
70
|
+
Checking monorepo scripts...
|
|
71
|
+
✅ Monorepo scripts are correctly set up
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Global Options
|
|
75
|
+
|
|
76
|
+
- `--version, -V`: Display version number
|
|
77
|
+
- `--help, -h`: Display help information
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
<div align="center">
|
|
82
|
+
|
|
83
|
+
Made with ❤️ by the PagoPA DevEx Team
|
|
84
|
+
|
|
85
|
+
</div>
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import "core-js/actual/set/index.js";
|
|
5
|
+
import { configure, getConsoleSink, getLogger as getLogger4 } from "@logtape/logtape";
|
|
6
|
+
|
|
7
|
+
// src/adapters/commander/index.ts
|
|
8
|
+
import { Command as Command4 } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/adapters/commander/commands/doctor.ts
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import * as process2 from "process";
|
|
13
|
+
|
|
14
|
+
// src/domain/doctor.ts
|
|
15
|
+
import { ResultAsync as ResultAsync3 } from "neverthrow";
|
|
16
|
+
|
|
17
|
+
// src/domain/package-json.ts
|
|
18
|
+
import { err, ok } from "neverthrow";
|
|
19
|
+
import { z } from "zod/v4";
|
|
20
|
+
var ScriptName = z.string().brand();
|
|
21
|
+
var scriptSchema = z.object({
|
|
22
|
+
name: ScriptName,
|
|
23
|
+
script: z.string()
|
|
24
|
+
});
|
|
25
|
+
var DependencyName = z.string().brand();
|
|
26
|
+
var dependencySchema = z.object({
|
|
27
|
+
name: DependencyName,
|
|
28
|
+
version: z.string()
|
|
29
|
+
});
|
|
30
|
+
var PackageName = z.string().min(1).brand();
|
|
31
|
+
var scriptsSchema = z.record(ScriptName, z.string()).optional().transform(
|
|
32
|
+
(obj) => new Map(
|
|
33
|
+
obj ? Object.entries(obj).map(([name, script]) => [
|
|
34
|
+
ScriptName.parse(name),
|
|
35
|
+
script
|
|
36
|
+
]) : []
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
var dependenciesSchema = z.record(DependencyName, z.string()).optional().transform(
|
|
40
|
+
(obj) => new Map(
|
|
41
|
+
obj ? Object.entries(obj).map(([name, version]) => [
|
|
42
|
+
DependencyName.parse(name),
|
|
43
|
+
version
|
|
44
|
+
]) : []
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
var packageManagerSchema = z.enum(["npm", "pnpm", "yarn"]);
|
|
48
|
+
var packageJsonSchema = z.object({
|
|
49
|
+
dependencies: dependenciesSchema,
|
|
50
|
+
devDependencies: dependenciesSchema,
|
|
51
|
+
name: PackageName,
|
|
52
|
+
packageManager: z.string().transform((str) => str.split("@")[0]).pipe(packageManagerSchema).optional(),
|
|
53
|
+
scripts: scriptsSchema
|
|
54
|
+
});
|
|
55
|
+
var findMissingScripts = (availableScripts, requiredScripts) => {
|
|
56
|
+
const availableScriptNames = new Set(availableScripts.keys());
|
|
57
|
+
const requiredScriptNames = new Set(requiredScripts.keys());
|
|
58
|
+
return requiredScriptNames.difference(availableScriptNames);
|
|
59
|
+
};
|
|
60
|
+
var checkMonorepoScripts = async (dependencies, config2) => {
|
|
61
|
+
const { packageJsonReader: packageJsonReader2 } = dependencies;
|
|
62
|
+
const checkName = "Monorepo Scripts";
|
|
63
|
+
const scriptsResult = await packageJsonReader2.getScripts(
|
|
64
|
+
config2.repository.root
|
|
65
|
+
);
|
|
66
|
+
if (scriptsResult.isErr()) {
|
|
67
|
+
return err(scriptsResult.error);
|
|
68
|
+
}
|
|
69
|
+
const requiredScriptsMap = packageJsonReader2.getRootRequiredScripts();
|
|
70
|
+
const missingScripts = findMissingScripts(
|
|
71
|
+
scriptsResult.value,
|
|
72
|
+
requiredScriptsMap
|
|
73
|
+
);
|
|
74
|
+
if (missingScripts.size === 0) {
|
|
75
|
+
return ok({
|
|
76
|
+
checkName,
|
|
77
|
+
isValid: true,
|
|
78
|
+
successMessage: "Monorepo scripts are correctly set up"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return ok({
|
|
82
|
+
checkName,
|
|
83
|
+
errorMessage: `Missing required scripts: ${Array.from(missingScripts).join(", ")}`,
|
|
84
|
+
isValid: false
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/domain/repository.ts
|
|
89
|
+
import { ok as ok2 } from "neverthrow";
|
|
90
|
+
import fs from "path";
|
|
91
|
+
import coerce from "semver/functions/coerce.js";
|
|
92
|
+
import semverGte from "semver/functions/gte.js";
|
|
93
|
+
var isVersionValid = (version, minVersion) => {
|
|
94
|
+
const minAcceptedSemVer = coerce(minVersion);
|
|
95
|
+
const dependencySemVer = coerce(version);
|
|
96
|
+
if (!minAcceptedSemVer || !dependencySemVer) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return semverGte(dependencySemVer, minAcceptedSemVer);
|
|
100
|
+
};
|
|
101
|
+
var checkPreCommitConfig = async (dependencies, config2) => {
|
|
102
|
+
const { repositoryReader: repositoryReader2 } = dependencies;
|
|
103
|
+
const checkName = "Pre-commit Configuration";
|
|
104
|
+
const preCommitResult = await repositoryReader2.fileExists(
|
|
105
|
+
fs.join(config2.repository.root, ".pre-commit-config.yaml")
|
|
106
|
+
);
|
|
107
|
+
if (preCommitResult.isOk() && preCommitResult.value) {
|
|
108
|
+
return ok2({
|
|
109
|
+
checkName,
|
|
110
|
+
isValid: true,
|
|
111
|
+
successMessage: "Pre-commit configuration is present in the repository root"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const errorMessage = preCommitResult.isErr() ? preCommitResult.error.message : `Pre-commit configuration is not present in the repository root. Please add a .pre-commit-config.yaml file to the repository root.`;
|
|
115
|
+
return ok2({
|
|
116
|
+
checkName,
|
|
117
|
+
errorMessage,
|
|
118
|
+
isValid: false
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
var checkTurboConfig = async (dependencies, config2) => {
|
|
122
|
+
const { packageJsonReader: packageJsonReader2, repositoryReader: repositoryReader2 } = dependencies;
|
|
123
|
+
const checkName = "Turbo Configuration";
|
|
124
|
+
const repoRoot2 = config2.repository.root;
|
|
125
|
+
const turboResult = await repositoryReader2.fileExists(
|
|
126
|
+
fs.join(repoRoot2, "turbo.json")
|
|
127
|
+
);
|
|
128
|
+
if (turboResult.isErr()) {
|
|
129
|
+
return ok2({
|
|
130
|
+
checkName,
|
|
131
|
+
errorMessage: turboResult.error.message,
|
|
132
|
+
isValid: false
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const dependenciesResult = await packageJsonReader2.getDependencies(
|
|
136
|
+
repoRoot2,
|
|
137
|
+
"dev"
|
|
138
|
+
);
|
|
139
|
+
if (dependenciesResult.isErr()) {
|
|
140
|
+
return ok2({
|
|
141
|
+
checkName,
|
|
142
|
+
errorMessage: dependenciesResult.error.message,
|
|
143
|
+
isValid: false
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const turboVersion = dependenciesResult.value.get(
|
|
147
|
+
"turbo"
|
|
148
|
+
);
|
|
149
|
+
if (!turboVersion) {
|
|
150
|
+
return ok2({
|
|
151
|
+
checkName,
|
|
152
|
+
errorMessage: "Turbo dependency not found in devDependencies. Please add 'turbo' to your devDependencies.",
|
|
153
|
+
isValid: false
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (!isVersionValid(turboVersion, config2.minVersions.turbo)) {
|
|
157
|
+
return ok2({
|
|
158
|
+
checkName,
|
|
159
|
+
errorMessage: `Turbo version (${turboVersion}) is too low. Minimum required version is ${config2.minVersions.turbo}.`,
|
|
160
|
+
isValid: false
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return ok2({
|
|
164
|
+
checkName,
|
|
165
|
+
isValid: true,
|
|
166
|
+
successMessage: "Turbo configuration is present in the monorepo root and turbo dependency is installed"
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/domain/doctor.ts
|
|
171
|
+
var runDoctor = (dependencies, config2) => {
|
|
172
|
+
const doctorChecks = [
|
|
173
|
+
ResultAsync3.fromPromise(
|
|
174
|
+
checkPreCommitConfig(dependencies, config2),
|
|
175
|
+
() => new Error("Error checking pre-commit configuration")
|
|
176
|
+
),
|
|
177
|
+
ResultAsync3.fromPromise(
|
|
178
|
+
checkTurboConfig(dependencies, config2),
|
|
179
|
+
() => new Error("Error checking Turbo configuration")
|
|
180
|
+
),
|
|
181
|
+
ResultAsync3.fromPromise(
|
|
182
|
+
checkMonorepoScripts(dependencies, config2),
|
|
183
|
+
() => new Error("Error checking monorepo scripts")
|
|
184
|
+
)
|
|
185
|
+
];
|
|
186
|
+
return ResultAsync3.combine(doctorChecks).match(
|
|
187
|
+
toDoctorResult,
|
|
188
|
+
() => ({
|
|
189
|
+
checks: [],
|
|
190
|
+
hasErrors: true
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
var toDoctorResult = (validationCheckResults) => {
|
|
195
|
+
const checks = validationCheckResults.map((result) => {
|
|
196
|
+
if (result.isOk()) {
|
|
197
|
+
return result.value;
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
checkName: "Unknown",
|
|
201
|
+
errorMessage: result.error.message,
|
|
202
|
+
isValid: false
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
const hasErrors = checks.some((check) => !check.isValid);
|
|
206
|
+
return {
|
|
207
|
+
checks,
|
|
208
|
+
hasErrors
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
var printDoctorResult = ({ validationReporter: validationReporter2 }, result) => result.checks.map(validationReporter2.reportCheckResult);
|
|
212
|
+
|
|
213
|
+
// src/adapters/commander/commands/doctor.ts
|
|
214
|
+
var makeDoctorCommand = (dependencies, config2) => new Command().name("doctor").description(
|
|
215
|
+
"Verify the repository setup according to the DevEx guidelines"
|
|
216
|
+
).action(async () => {
|
|
217
|
+
const result = await runDoctor(dependencies, config2);
|
|
218
|
+
printDoctorResult(dependencies, result);
|
|
219
|
+
const exitCode = result.hasErrors ? 1 : 0;
|
|
220
|
+
process2.exit(exitCode);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// src/adapters/commander/commands/info.ts
|
|
224
|
+
import { Command as Command2 } from "commander";
|
|
225
|
+
|
|
226
|
+
// src/domain/info.ts
|
|
227
|
+
import { getLogger } from "@logtape/logtape";
|
|
228
|
+
import { join } from "path";
|
|
229
|
+
var detectFromLockFile = async (dependencies, config2) => {
|
|
230
|
+
const { repositoryReader: repositoryReader2 } = dependencies;
|
|
231
|
+
const repoRoot2 = config2.repository.root;
|
|
232
|
+
const pnpmResult = await repositoryReader2.fileExists(
|
|
233
|
+
join(repoRoot2, "pnpm-lock.yaml")
|
|
234
|
+
);
|
|
235
|
+
if (pnpmResult.isOk() && pnpmResult.value) return "pnpm";
|
|
236
|
+
const yarnResult = await repositoryReader2.fileExists(
|
|
237
|
+
join(repoRoot2, "yarn.lock")
|
|
238
|
+
);
|
|
239
|
+
if (yarnResult.isOk() && yarnResult.value) return "yarn";
|
|
240
|
+
const npmResult = await repositoryReader2.fileExists(
|
|
241
|
+
join(repoRoot2, "package-lock.json")
|
|
242
|
+
);
|
|
243
|
+
if (npmResult.isOk() && npmResult.value) return "npm";
|
|
244
|
+
return void 0;
|
|
245
|
+
};
|
|
246
|
+
var detectPackageManager = async (dependencies, config2) => {
|
|
247
|
+
const packageManager = dependencies.packageJson.packageManager ?? await detectFromLockFile(dependencies, config2);
|
|
248
|
+
return packageManager ?? "npm";
|
|
249
|
+
};
|
|
250
|
+
var detectNodeVersion = async ({ repositoryReader: repositoryReader2 }, nodeVersionFilePath) => await repositoryReader2.readFile(nodeVersionFilePath).unwrapOr(void 0);
|
|
251
|
+
var detectTerraformVersion = async ({ repositoryReader: repositoryReader2 }, terraformVersionFilePath) => await repositoryReader2.readFile(terraformVersionFilePath).map((tfVersion) => tfVersion.trim()).unwrapOr(void 0);
|
|
252
|
+
var getInfo = async (dependencies, config2) => ({
|
|
253
|
+
node: await detectNodeVersion(
|
|
254
|
+
{ repositoryReader: dependencies.repositoryReader },
|
|
255
|
+
`${config2.repository.root}/.node-version`
|
|
256
|
+
),
|
|
257
|
+
packageManager: await detectPackageManager(dependencies, config2),
|
|
258
|
+
terraform: await detectTerraformVersion(
|
|
259
|
+
dependencies,
|
|
260
|
+
`${config2.repository.root}/.terraform-version`
|
|
261
|
+
)
|
|
262
|
+
});
|
|
263
|
+
var printInfo = (result) => {
|
|
264
|
+
const logger2 = getLogger("json");
|
|
265
|
+
logger2.info(JSON.stringify(result));
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/adapters/commander/commands/info.ts
|
|
269
|
+
var makeInfoCommand = (dependencies, config2) => new Command2().name("info").description("Display information about the project").action(async () => {
|
|
270
|
+
const result = await getInfo(dependencies, config2);
|
|
271
|
+
printInfo(result);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// src/adapters/commander/commands/version.ts
|
|
275
|
+
import { Command as Command3 } from "commander";
|
|
276
|
+
|
|
277
|
+
// src/domain/version.ts
|
|
278
|
+
import { getLogger as getLogger2 } from "@logtape/logtape";
|
|
279
|
+
function printVersion() {
|
|
280
|
+
const logger2 = getLogger2(["dx-cli", "version"]);
|
|
281
|
+
logger2.info(`dx CLI version: ${"0.4.1"}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/adapters/commander/commands/version.ts
|
|
285
|
+
var makeVersionCommand = () => new Command3().name("version").alias("v").action(() => printVersion());
|
|
286
|
+
|
|
287
|
+
// src/adapters/commander/index.ts
|
|
288
|
+
var makeCli = (deps2, config2) => {
|
|
289
|
+
const program2 = new Command4();
|
|
290
|
+
program2.name("dx").description("The CLI for DX-Platform").version("0.4.1");
|
|
291
|
+
program2.addCommand(makeDoctorCommand(deps2, config2));
|
|
292
|
+
program2.addCommand(makeVersionCommand());
|
|
293
|
+
program2.addCommand(makeInfoCommand(deps2, config2));
|
|
294
|
+
return program2;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/adapters/logtape/validation-reporter.ts
|
|
298
|
+
import { getLogger as getLogger3 } from "@logtape/logtape";
|
|
299
|
+
var makeValidationReporter = () => {
|
|
300
|
+
const logger2 = getLogger3(["dx-cli", "validation"]);
|
|
301
|
+
return {
|
|
302
|
+
reportCheckResult(result) {
|
|
303
|
+
if (result.isValid) {
|
|
304
|
+
logger2.info(`\u2705 ${result.successMessage}`);
|
|
305
|
+
} else {
|
|
306
|
+
logger2.error(`\u274C ${result.errorMessage}`);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
reportValidationResult(result) {
|
|
310
|
+
if (result.isOk()) {
|
|
311
|
+
const validation = result.value;
|
|
312
|
+
if (validation.isValid) {
|
|
313
|
+
logger2.info(`\u2705 ${validation.successMessage}`);
|
|
314
|
+
} else {
|
|
315
|
+
logger2.error(`\u274C ${validation.errorMessage}`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
logger2.error(`\u274C ${result.error.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/adapters/node/package-json.ts
|
|
325
|
+
import { join as join2 } from "path";
|
|
326
|
+
import * as process3 from "process";
|
|
327
|
+
|
|
328
|
+
// src/adapters/node/fs/file-reader.ts
|
|
329
|
+
import { Result, ResultAsync as ResultAsync4 } from "neverthrow";
|
|
330
|
+
import fs2 from "fs/promises";
|
|
331
|
+
var readFile = (filePath) => ResultAsync4.fromPromise(
|
|
332
|
+
fs2.readFile(filePath, "utf-8"),
|
|
333
|
+
(cause) => new Error(`Failed to read file: ${filePath}`, { cause })
|
|
334
|
+
);
|
|
335
|
+
var readFileAndDecode = (filePath, schema) => {
|
|
336
|
+
const decode = ResultAsync4.fromThrowable(
|
|
337
|
+
schema.parseAsync,
|
|
338
|
+
() => new Error("File content is not valid for the given schema")
|
|
339
|
+
);
|
|
340
|
+
const toJSON = Result.fromThrowable(
|
|
341
|
+
JSON.parse,
|
|
342
|
+
() => new Error("Failed to parse JSON")
|
|
343
|
+
);
|
|
344
|
+
return readFile(filePath).andThen(toJSON).andThen(decode);
|
|
345
|
+
};
|
|
346
|
+
var fileExists = (path) => ResultAsync4.fromPromise(
|
|
347
|
+
fs2.stat(path),
|
|
348
|
+
() => new Error(`${path} not found.`)
|
|
349
|
+
).map(() => true);
|
|
350
|
+
|
|
351
|
+
// src/adapters/node/package-json.ts
|
|
352
|
+
var makePackageJsonReader = () => ({
|
|
353
|
+
getDependencies: (cwd2 = process3.cwd(), type) => {
|
|
354
|
+
const packageJsonPath = join2(cwd2, "package.json");
|
|
355
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema).map(
|
|
356
|
+
(packageJson2) => {
|
|
357
|
+
const key = type === "dev" ? "devDependencies" : "dependencies";
|
|
358
|
+
return packageJson2[key];
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
getRootRequiredScripts: () => (/* @__PURE__ */ new Map()).set("code-review", "eslint ."),
|
|
363
|
+
getScripts: (cwd2 = process3.cwd()) => {
|
|
364
|
+
const packageJsonPath = join2(cwd2, "package.json");
|
|
365
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema).map(
|
|
366
|
+
({ scripts }) => scripts
|
|
367
|
+
);
|
|
368
|
+
},
|
|
369
|
+
readPackageJson: (cwd2 = process3.cwd()) => {
|
|
370
|
+
const packageJsonPath = join2(cwd2, "package.json");
|
|
371
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// src/adapters/node/repository.ts
|
|
376
|
+
import { join as join3 } from "path";
|
|
377
|
+
var findRepositoryRoot = (dir = process.cwd()) => {
|
|
378
|
+
const gitPath = join3(dir, ".git");
|
|
379
|
+
return fileExists(gitPath).mapErr(
|
|
380
|
+
() => new Error(
|
|
381
|
+
"Could not find repository root. Make sure to have the repo initialized."
|
|
382
|
+
)
|
|
383
|
+
).map(() => dir);
|
|
384
|
+
};
|
|
385
|
+
var makeRepositoryReader = () => ({
|
|
386
|
+
fileExists,
|
|
387
|
+
findRepositoryRoot,
|
|
388
|
+
readFile
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// src/config.ts
|
|
392
|
+
var getConfig = (repositoryRoot2) => ({
|
|
393
|
+
minVersions: {
|
|
394
|
+
turbo: "2"
|
|
395
|
+
},
|
|
396
|
+
repository: {
|
|
397
|
+
root: repositoryRoot2
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/index.ts
|
|
402
|
+
await configure({
|
|
403
|
+
loggers: [
|
|
404
|
+
{ category: ["dx-cli"], lowestLevel: "info", sinks: ["console"] },
|
|
405
|
+
{ category: ["json"], lowestLevel: "info", sinks: ["rawJson"] },
|
|
406
|
+
{
|
|
407
|
+
category: ["logtape", "meta"],
|
|
408
|
+
lowestLevel: "warning",
|
|
409
|
+
sinks: ["console"]
|
|
410
|
+
}
|
|
411
|
+
],
|
|
412
|
+
sinks: {
|
|
413
|
+
console: getConsoleSink(),
|
|
414
|
+
rawJson(record) {
|
|
415
|
+
console.log(record.rawMessage);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
var logger = getLogger4(["dx-cli"]);
|
|
420
|
+
var repositoryReader = makeRepositoryReader();
|
|
421
|
+
var packageJsonReader = makePackageJsonReader();
|
|
422
|
+
var validationReporter = makeValidationReporter();
|
|
423
|
+
var repoRoot = await repositoryReader.findRepositoryRoot(process.cwd());
|
|
424
|
+
if (repoRoot.isErr()) {
|
|
425
|
+
logger.error(
|
|
426
|
+
"Could not find repository root. Make sure to have the repo initialized."
|
|
427
|
+
);
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
var repositoryRoot = repoRoot.value;
|
|
431
|
+
var repoPackageJson = await packageJsonReader.readPackageJson(repositoryRoot);
|
|
432
|
+
if (repoPackageJson.isErr()) {
|
|
433
|
+
logger.error("Repository does not contain a package.json file");
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
var packageJson = repoPackageJson.value;
|
|
437
|
+
var deps = {
|
|
438
|
+
packageJson,
|
|
439
|
+
packageJsonReader,
|
|
440
|
+
repositoryReader,
|
|
441
|
+
validationReporter
|
|
442
|
+
};
|
|
443
|
+
var config = getConfig(repositoryRoot);
|
|
444
|
+
var program = makeCli(deps, config);
|
|
445
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pagopa/dx-cli",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A CLI useful to manage DX tools.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/pagopa/dx.git",
|
|
9
|
+
"directory": "apps/cli"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"DX",
|
|
13
|
+
"CLI"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"bin"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"dx": "./bin/index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@logtape/logtape": "^1.0.0",
|
|
23
|
+
"commander": "^14.0.0",
|
|
24
|
+
"core-js": "^3.44.0",
|
|
25
|
+
"neverthrow": "^8.2.0",
|
|
26
|
+
"semver": "^7.7.2",
|
|
27
|
+
"zod": "^3.25.28"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@tsconfig/node22": "22.0.2",
|
|
31
|
+
"@types/node": "^22.16.2",
|
|
32
|
+
"@types/semver": "^7.7.0",
|
|
33
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
34
|
+
"eslint": "^9.31.0",
|
|
35
|
+
"prettier": "3.6.2",
|
|
36
|
+
"tsup": "^8.5.0",
|
|
37
|
+
"typescript": "~5.8.3",
|
|
38
|
+
"vitest": "^3.2.4",
|
|
39
|
+
"vitest-mock-extended": "^3.1.0",
|
|
40
|
+
"@pagopa/eslint-config": "^5.0.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"lint": "eslint --fix src",
|
|
48
|
+
"lint:check": "eslint src",
|
|
49
|
+
"format": "prettier --write .",
|
|
50
|
+
"format:check": "prettier --check .",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:coverage": "vitest --coverage"
|
|
54
|
+
}
|
|
55
|
+
}
|