@pagopa/dx-cli 0.5.0 → 0.7.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.
Files changed (2) hide show
  1. package/bin/index.js +381 -69
  2. package/package.json +9 -4
package/bin/index.js CHANGED
@@ -2,22 +2,10 @@
2
2
 
3
3
  // src/index.ts
4
4
  import "core-js/actual/set/index.js";
5
- import { configure, getConsoleSink, getLogger as getLogger4 } from "@logtape/logtape";
6
-
7
- // src/adapters/codemods/example.ts
8
- import { okAsync } from "neverthrow";
9
- var apply = () => {
10
- console.log("Hello from example codemod!");
11
- return okAsync(void 0);
12
- };
13
- var example_default = {
14
- apply,
15
- description: "An example codemod that does nothing",
16
- id: "example"
17
- };
5
+ import { configure, getConsoleSink, getLogger as getLogger9 } from "@logtape/logtape";
18
6
 
19
7
  // src/adapters/codemods/registry.ts
20
- import { okAsync as okAsync2 } from "neverthrow";
8
+ import { okAsync } from "neverthrow";
21
9
  var LocalCodemodRegistry = class {
22
10
  #m;
23
11
  constructor() {
@@ -27,22 +15,296 @@ var LocalCodemodRegistry = class {
27
15
  this.#m.set(codemod.id, codemod);
28
16
  }
29
17
  getAll() {
30
- return okAsync2(Array.from(this.#m.values()));
18
+ return okAsync(Array.from(this.#m.values()));
31
19
  }
32
20
  getById(id) {
33
- return okAsync2(this.#m.get(id));
21
+ return okAsync(this.#m.get(id));
22
+ }
23
+ };
24
+
25
+ // src/adapters/codemods/update-code-review.ts
26
+ import { getLogger as getLogger2 } from "@logtape/logtape";
27
+ import { replaceInFile } from "replace-in-file";
28
+
29
+ // src/adapters/codemods/git.ts
30
+ import { getLogger } from "@logtape/logtape";
31
+ import { Octokit } from "octokit";
32
+ var getLatestCommitSha = async (owner, repo, ref = "main") => {
33
+ const octokit = new Octokit();
34
+ const response = await octokit.rest.repos.getCommit({
35
+ owner,
36
+ ref,
37
+ repo
38
+ });
39
+ return response.data.sha;
40
+ };
41
+ var getLatestCommitShaOrRef = async (owner, repo, ref = "main") => {
42
+ const logger2 = getLogger(["dx-cli", "codemod"]);
43
+ return getLatestCommitSha(owner, repo, ref).catch(() => {
44
+ logger2.warn(
45
+ "Failed to fetch the latest commit from {owner}/{repo}, fallback to {fallback}",
46
+ { fallback: ref, owner, repo }
47
+ );
48
+ return ref;
49
+ });
50
+ };
51
+
52
+ // src/adapters/codemods/update-code-review.ts
53
+ var updateJSCodeReview = async (sha) => {
54
+ const logger2 = getLogger2(["dx-cli", "codemod"]);
55
+ const results = await replaceInFile({
56
+ allowEmptyPaths: true,
57
+ files: [".github/workflows/*.yaml"],
58
+ from: [/pagopa\/dx\/.github\/workflows\/js_code_review.yaml@(\S+)/g],
59
+ to: [`pagopa/dx/.github/workflows/js_code_review.yaml@${sha}`]
60
+ });
61
+ const updated = results.filter((r) => r.hasChanged).map((r) => r.file);
62
+ updated.forEach((filename) => {
63
+ logger2.info("Workflow {filename} updated", { filename });
64
+ });
65
+ return updated;
66
+ };
67
+ var updateCodeReview = {
68
+ apply: async () => {
69
+ const logger2 = getLogger2(["dx-cli", "codemod"]);
70
+ const owner = "pagopa";
71
+ const repo = "dx";
72
+ return getLatestCommitSha(owner, repo).then(async (sha) => {
73
+ await updateJSCodeReview(sha);
74
+ }).catch(() => {
75
+ logger2.error(
76
+ "Failed to fetch the latest commit sha from {owner}/{repo}",
77
+ {
78
+ owner,
79
+ repo
80
+ }
81
+ );
82
+ });
83
+ },
84
+ description: "Update js_code_review workflow to its latest version",
85
+ id: "update-code-review"
86
+ };
87
+
88
+ // src/adapters/codemods/use-azure-appsvc.ts
89
+ import { getLogger as getLogger3 } from "@logtape/logtape";
90
+ import { replaceInFile as replaceInFile2 } from "replace-in-file";
91
+ import YAML from "yaml";
92
+ var isChildOf = (path2, key) => {
93
+ const ancestor = path2.at(-1);
94
+ return YAML.isPair(ancestor) && YAML.isScalar(ancestor.key) && typeof ancestor.key.value === "string" && ancestor.key.value === key;
95
+ };
96
+ var migrateWorkflow = (sha) => (workflow, filename) => {
97
+ const logger2 = getLogger3(["dx-cli", "codemod"]);
98
+ logger2.debug("Processing {filename} file", { filename });
99
+ const document = YAML.parseDocument(workflow);
100
+ let updated = false;
101
+ YAML.visit(document, {
102
+ Map(_, map, path2) {
103
+ if (isChildOf(path2, "jobs") || isChildOf(path2, "with")) {
104
+ return void 0;
105
+ }
106
+ if (map.has("jobs")) {
107
+ return void 0;
108
+ }
109
+ if (map.has("uses")) {
110
+ const uses = map.get("uses");
111
+ if (typeof uses === "string" && uses.match(
112
+ /^pagopa\/dx\/.github\/workflows\/(web|function)_app_deploy/
113
+ )) {
114
+ logger2.debug("Adding disable_auto_staging_deploy");
115
+ map.addIn(["with", "disable_auto_staging_deploy"], true);
116
+ updated = true;
117
+ return void 0;
118
+ }
119
+ }
120
+ return YAML.visit.SKIP;
121
+ },
122
+ Pair(_, pair) {
123
+ if (YAML.isScalar(pair.key)) {
124
+ if (pair.key.value === "function_app_name") {
125
+ updated = true;
126
+ logger2.debug("Updating function_app_name to web_app_name");
127
+ return new YAML.Pair("web_app_name", pair.value);
128
+ }
129
+ if (pair.key.value === "use_staging_slot") {
130
+ updated = true;
131
+ logger2.debug("Removing use_staging_slot");
132
+ return YAML.visit.REMOVE;
133
+ }
134
+ if (pair.key.value === "uses") {
135
+ updated = true;
136
+ logger2.debug("Updating uses value");
137
+ return new YAML.Pair(
138
+ "uses",
139
+ `pagopa/dx/.github/workflows/release-azure-appsvc.yaml@${sha}`
140
+ );
141
+ }
142
+ }
143
+ }
144
+ });
145
+ if (updated) {
146
+ logger2.info("Workflow {filename} updated", {
147
+ filename
148
+ });
149
+ return YAML.stringify(document);
34
150
  }
151
+ logger2.debug("No changes applied to {filename}", { filename });
152
+ return workflow;
153
+ };
154
+ var useAzureAppsvc = {
155
+ apply: async () => {
156
+ const sha = await getLatestCommitShaOrRef("pagopa", "dx");
157
+ await replaceInFile2({
158
+ allowEmptyPaths: true,
159
+ files: [".github/workflows/*.yaml"],
160
+ processor: migrateWorkflow(sha)
161
+ });
162
+ },
163
+ description: "Refactor legacy deploy workflows to use release-azure-appsvc",
164
+ id: "use-azure-appsvc"
165
+ };
166
+
167
+ // src/adapters/codemods/use-pnpm.ts
168
+ import { getLogger as getLogger4 } from "@logtape/logtape";
169
+ import { $ } from "execa";
170
+ import * as fs from "fs/promises";
171
+ import { replaceInFile as replaceInFile3 } from "replace-in-file";
172
+ import YAML2 from "yaml";
173
+ async function preparePackageJsonForPnpm() {
174
+ const packageJson2 = await fs.readFile("package.json", "utf-8");
175
+ const manifest = JSON.parse(packageJson2);
176
+ let workspaces = [];
177
+ if (Object.hasOwn(manifest, "packageManager")) {
178
+ delete manifest.packageManager;
179
+ }
180
+ if (Object.hasOwn(manifest, "workspaces")) {
181
+ if (Array.isArray(manifest.workspaces)) {
182
+ workspaces = manifest.workspaces;
183
+ }
184
+ delete manifest.workspaces;
185
+ }
186
+ await fs.writeFile("package.json", JSON.stringify(manifest, null, 2));
187
+ return workspaces;
188
+ }
189
+ async function removeFiles(...files) {
190
+ await Promise.all(
191
+ files.map(
192
+ (file) => (
193
+ // Remove the file if it exists, fail silently if it doesn't.
194
+ fs.rm(file, { force: true, recursive: true }).catch(() => void 0)
195
+ )
196
+ )
197
+ );
198
+ }
199
+ async function replaceYarnOccurrences() {
200
+ const logger2 = getLogger4(["dx-cli", "codemod"]);
201
+ logger2.info("Replacing yarn occurrences in files...");
202
+ const results = await replaceInFile3({
203
+ allowEmptyPaths: true,
204
+ files: ["**/*.json", "**/*.md", "**/Dockerfile", "**/docker-compose.yml"],
205
+ from: [
206
+ "https://yarnpkg.com/",
207
+ "https://classic.yarnpkg.com/",
208
+ /yarn workspace (\S+)/g,
209
+ /yarn workspace/g,
210
+ /yarn install --immutable/g,
211
+ /yarn -q dlx/g,
212
+ /Yarn/gi
213
+ ],
214
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
215
+ to: [
216
+ "https://pnpm.io/",
217
+ "https://pnpm.io/",
218
+ "pnpm --filter $1",
219
+ "pnpm --filter <package-selector>",
220
+ "pnpm install --frozen-lockfile",
221
+ "pnpm dlx",
222
+ "pnpm"
223
+ ]
224
+ });
225
+ const count = results.reduce(
226
+ (acc, file) => file.hasChanged ? acc + 1 : acc,
227
+ 0
228
+ );
229
+ logger2.info("Replaced yarn occurrences in {count} files", { count });
230
+ }
231
+ async function updateDXWorkflows() {
232
+ const logger2 = getLogger4(["dx-cli", "codemod"]);
233
+ logger2.info("Updating Github Workflows workflows...");
234
+ const sha = await getLatestCommitShaOrRef("pagopa", "dx");
235
+ const ignore = await updateJSCodeReview(sha);
236
+ await replaceInFile3({
237
+ allowEmptyPaths: true,
238
+ files: [".github/workflows/*.yaml"],
239
+ ignore,
240
+ processor: migrateWorkflow(sha)
241
+ });
242
+ }
243
+ async function writePnpmWorkspaceFile(workspaces) {
244
+ const pnpmWorkspace = {
245
+ packages: workspaces.length > 0 ? workspaces : ["apps/*", "packages/*"]
246
+ };
247
+ const yamlContent = YAML2.stringify(pnpmWorkspace);
248
+ await fs.writeFile("pnpm-workspace.yaml", yamlContent, "utf-8");
249
+ }
250
+ var apply = async (info) => {
251
+ if (info.packageManager === "pnpm") {
252
+ throw new Error("Project is already using pnpm");
253
+ }
254
+ const logger2 = getLogger4(["dx-cli", "codemod"]);
255
+ logger2.info("Remove unused fields from {file}", {
256
+ file: "package.json"
257
+ });
258
+ const workspaces = await preparePackageJsonForPnpm();
259
+ logger2.info("Create {file}", {
260
+ file: "pnpm-workspace.yaml"
261
+ });
262
+ await writePnpmWorkspaceFile(workspaces);
263
+ logger2.info("Remove node_modules and yarn files");
264
+ await removeFiles(
265
+ ".yarnrc",
266
+ ".yarnrc.yml",
267
+ "yarn.config.cjs",
268
+ ".yarn",
269
+ ".pnp.cjs",
270
+ ".pnp.loader.cjs",
271
+ "node_modules"
272
+ );
273
+ logger2.info("Importing {source} to {target}", {
274
+ source: "yarn.lock",
275
+ target: "pnpm-lock.yaml"
276
+ });
277
+ try {
278
+ await fs.access("yarn.lock");
279
+ await $`corepack pnpm@latest import yarn.lock`;
280
+ await removeFiles("yarn.lock");
281
+ } catch {
282
+ logger2.info("No yarn.lock file found, skipping import.");
283
+ }
284
+ await $`corepack pnpm@latest add --config pnpm-plugin-pagopa`;
285
+ await replaceYarnOccurrences();
286
+ await updateDXWorkflows();
287
+ logger2.info("Setting pnpm as the package manager...");
288
+ await $`corepack use pnpm@latest`;
289
+ };
290
+ var use_pnpm_default = {
291
+ apply,
292
+ description: "A codemod that switches the project to use pnpm",
293
+ id: "use-pnpm"
35
294
  };
36
295
 
37
296
  // src/adapters/codemods/index.ts
38
297
  var registry = new LocalCodemodRegistry();
39
- registry.add(example_default);
298
+ registry.add(use_pnpm_default);
299
+ registry.add(useAzureAppsvc);
300
+ registry.add(updateCodeReview);
40
301
  var codemods_default = registry;
41
302
 
42
303
  // src/adapters/commander/index.ts
43
- import { Command as Command5 } from "commander";
304
+ import { Command as Command6 } from "commander";
44
305
 
45
306
  // src/adapters/commander/commands/codemod.ts
307
+ import { getLogger as getLogger5 } from "@logtape/logtape";
46
308
  import { Command } from "commander";
47
309
  var makeCodemodCommand = ({
48
310
  applyCodemodById: applyCodemodById2,
@@ -50,16 +312,14 @@ var makeCodemodCommand = ({
50
312
  }) => new Command("codemod").description("Manage and apply migration scripts to the repository").addCommand(
51
313
  new Command("list").description("List available migration scripts").action(async function() {
52
314
  await listCodemods2().andTee(
53
- (codemods) => (
54
- // eslint-disable-next-line no-console
55
- console.table(codemods, ["id", "description"])
56
- )
315
+ (codemods) => console.table(codemods, ["id", "description"])
57
316
  ).orTee((error) => this.error(error.message));
58
317
  })
59
318
  ).addCommand(
60
319
  new Command("apply").argument("<id>", "The id of the codemod to apply").description("Apply migration scripts to the repository").action(async function(id) {
320
+ const logger2 = getLogger5(["dx-cli", "codemod"]);
61
321
  await applyCodemodById2(id).andTee(() => {
62
- console.log("Codemod applied successfully \u2705");
322
+ logger2.info("Codemod applied \u2705");
63
323
  }).orTee((error) => this.error(error.message));
64
324
  })
65
325
  );
@@ -140,7 +400,7 @@ var checkMonorepoScripts = async (dependencies, config2) => {
140
400
 
141
401
  // src/domain/repository.ts
142
402
  import { ok as ok2 } from "neverthrow";
143
- import fs from "path";
403
+ import fs2 from "path";
144
404
  import coerce from "semver/functions/coerce.js";
145
405
  import semverGte from "semver/functions/gte.js";
146
406
  var isVersionValid = (version, minVersion) => {
@@ -155,7 +415,7 @@ var checkPreCommitConfig = async (dependencies, config2) => {
155
415
  const { repositoryReader: repositoryReader2 } = dependencies;
156
416
  const checkName = "Pre-commit Configuration";
157
417
  const preCommitResult = await repositoryReader2.fileExists(
158
- fs.join(config2.repository.root, ".pre-commit-config.yaml")
418
+ fs2.join(config2.repository.root, ".pre-commit-config.yaml")
159
419
  );
160
420
  if (preCommitResult.isOk() && preCommitResult.value) {
161
421
  return ok2({
@@ -176,7 +436,7 @@ var checkTurboConfig = async (dependencies, config2) => {
176
436
  const checkName = "Turbo Configuration";
177
437
  const repoRoot2 = config2.repository.root;
178
438
  const turboResult = await repositoryReader2.fileExists(
179
- fs.join(repoRoot2, "turbo.json")
439
+ fs2.join(repoRoot2, "turbo.json")
180
440
  );
181
441
  if (turboResult.isErr()) {
182
442
  return ok2({
@@ -315,7 +575,7 @@ var makeDoctorCommand = (dependencies, config2) => new Command2().name("doctor")
315
575
  import { Command as Command3 } from "commander";
316
576
 
317
577
  // src/domain/info.ts
318
- import { getLogger } from "@logtape/logtape";
578
+ import { getLogger as getLogger6 } from "@logtape/logtape";
319
579
  import { join } from "path";
320
580
  var detectFromLockFile = async (dependencies, config2) => {
321
581
  const { repositoryReader: repositoryReader2 } = dependencies;
@@ -341,7 +601,7 @@ var detectPackageManager = async (dependencies, config2) => {
341
601
  var detectNodeVersion = async ({ repositoryReader: repositoryReader2 }, nodeVersionFilePath) => await repositoryReader2.readFile(nodeVersionFilePath).map((nodeVersion) => nodeVersion.trim()).unwrapOr(void 0);
342
602
  var detectTerraformVersion = async ({ repositoryReader: repositoryReader2 }, terraformVersionFilePath) => await repositoryReader2.readFile(terraformVersionFilePath).map((tfVersion) => tfVersion.trim()).unwrapOr(void 0);
343
603
  var detectTurboVersion = ({ devDependencies }) => devDependencies.get("turbo")?.trim();
344
- var getInfo = async (dependencies, config2) => ({
604
+ var getInfo = (dependencies, config2) => async () => ({
345
605
  node: await detectNodeVersion(
346
606
  { repositoryReader: dependencies.repositoryReader },
347
607
  `${config2.repository.root}/.node-version`
@@ -354,36 +614,73 @@ var getInfo = async (dependencies, config2) => ({
354
614
  turbo: detectTurboVersion(dependencies.packageJson)
355
615
  });
356
616
  var printInfo = (result) => {
357
- const logger2 = getLogger("json");
617
+ const logger2 = getLogger6("json");
358
618
  logger2.info(JSON.stringify(result));
359
619
  };
360
620
 
361
621
  // src/adapters/commander/commands/info.ts
362
622
  var makeInfoCommand = (dependencies, config2) => new Command3().name("info").description("Display information about the project").action(async () => {
363
- const result = await getInfo(dependencies, config2);
623
+ const result = await getInfo(dependencies, config2)();
364
624
  printInfo(result);
365
625
  });
366
626
 
367
- // src/adapters/commander/commands/version.ts
627
+ // src/adapters/commander/commands/init.ts
628
+ import scaffoldMonorepo from "@pagopa/monorepo-generator";
368
629
  import { Command as Command4 } from "commander";
630
+ import { Result, ResultAsync as ResultAsync4 } from "neverthrow";
631
+ import nodePlop from "node-plop";
632
+ var initPlop = () => ResultAsync4.fromPromise(
633
+ nodePlop(),
634
+ () => new Error("Failed to initialize plop")
635
+ );
636
+ var getGenerator = (plopAPI) => Result.fromThrowable(
637
+ plopAPI.getGenerator,
638
+ () => new Error("Generator not found")
639
+ );
640
+ var runGenerator = (generator) => ResultAsync4.fromPromise(
641
+ generator.runPrompts(),
642
+ () => new Error("Failed to run the generator prompts")
643
+ ).andThen(
644
+ (answers) => ResultAsync4.fromPromise(
645
+ generator.runActions(answers),
646
+ () => new Error("Failed to run the generator actions")
647
+ )
648
+ );
649
+ var makeInitCommand = () => new Command4().name("init").description(
650
+ "Command to initialize resources (like projects, subscriptions, ...)"
651
+ ).addCommand(
652
+ new Command4("project").description("Initialize a new monorepo project").action(async function() {
653
+ await initPlop().andTee(scaffoldMonorepo).andThen((plop) => getGenerator(plop)("monorepo")).andThen(runGenerator).andTee(() => {
654
+ console.log("Monorepo initialized successfully \u2705");
655
+ }).orTee((err2) => {
656
+ this.error(err2.message);
657
+ });
658
+ })
659
+ );
660
+
661
+ // src/adapters/commander/commands/version.ts
662
+ import { Command as Command5 } from "commander";
369
663
 
370
664
  // src/domain/version.ts
371
- import { getLogger as getLogger2 } from "@logtape/logtape";
665
+ import { getLogger as getLogger7 } from "@logtape/logtape";
372
666
  function printVersion() {
373
- const logger2 = getLogger2(["dx-cli", "version"]);
374
- logger2.info(`dx CLI version: ${"0.5.0"}`);
667
+ const logger2 = getLogger7(["dx-cli", "version"]);
668
+ logger2.info(`dx CLI version: ${"0.7.0"}`);
375
669
  }
376
670
 
377
671
  // src/adapters/commander/commands/version.ts
378
- var makeVersionCommand = () => new Command4().name("version").alias("v").action(() => printVersion());
672
+ var makeVersionCommand = () => new Command5().name("version").alias("v").action(() => printVersion());
379
673
 
380
674
  // src/adapters/commander/index.ts
381
- var makeCli = (deps2, config2) => {
382
- const program2 = new Command5();
383
- program2.name("dx").description("The CLI for DX-Platform").version("0.5.0");
675
+ var makeCli = (deps2, config2, cliDeps) => {
676
+ const program2 = new Command6();
677
+ program2.name("dx").description("The CLI for DX-Platform").version("0.7.0");
384
678
  program2.addCommand(makeDoctorCommand(deps2, config2));
385
679
  if (process.env.ENABLE_CODEMODS) {
386
- program2.addCommand(makeCodemodCommand(deps2));
680
+ program2.addCommand(makeCodemodCommand(cliDeps));
681
+ }
682
+ if (process.env.ENABLE_INIT_COMMAND) {
683
+ program2.addCommand(makeInitCommand());
387
684
  }
388
685
  program2.addCommand(makeVersionCommand());
389
686
  program2.addCommand(makeInfoCommand(deps2, config2));
@@ -391,9 +688,9 @@ var makeCli = (deps2, config2) => {
391
688
  };
392
689
 
393
690
  // src/adapters/logtape/validation-reporter.ts
394
- import { getLogger as getLogger3 } from "@logtape/logtape";
691
+ import { getLogger as getLogger8 } from "@logtape/logtape";
395
692
  var makeValidationReporter = () => {
396
- const logger2 = getLogger3(["dx-cli", "validation"]);
693
+ const logger2 = getLogger8(["dx-cli", "validation"]);
397
694
  return {
398
695
  reportCheckResult(result) {
399
696
  if (result.isValid) {
@@ -410,31 +707,31 @@ import { join as join2 } from "path";
410
707
  import * as process3 from "process";
411
708
 
412
709
  // src/adapters/node/fs/file-reader.ts
413
- import { ResultAsync as ResultAsync5 } from "neverthrow";
414
- import fs2 from "fs/promises";
710
+ import { ResultAsync as ResultAsync6 } from "neverthrow";
711
+ import fs3 from "fs/promises";
415
712
 
416
713
  // src/adapters/zod/index.ts
417
- import { ResultAsync as ResultAsync4 } from "neverthrow";
418
- var decode = (schema) => ResultAsync4.fromThrowable(
714
+ import { ResultAsync as ResultAsync5 } from "neverthrow";
715
+ var decode = (schema) => ResultAsync5.fromThrowable(
419
716
  schema.parseAsync,
420
717
  (cause) => new Error("File content is not valid for the given schema", { cause })
421
718
  );
422
719
 
423
720
  // src/adapters/node/json/index.ts
424
- import { Result } from "neverthrow";
425
- var parseJson = Result.fromThrowable(
721
+ import { Result as Result2 } from "neverthrow";
722
+ var parseJson = Result2.fromThrowable(
426
723
  JSON.parse,
427
724
  (cause) => new Error("Failed to parse JSON", { cause })
428
725
  );
429
726
 
430
727
  // src/adapters/node/fs/file-reader.ts
431
- var readFile = (filePath) => ResultAsync5.fromPromise(
432
- fs2.readFile(filePath, "utf-8"),
728
+ var readFile2 = (filePath) => ResultAsync6.fromPromise(
729
+ fs3.readFile(filePath, "utf-8"),
433
730
  (cause) => new Error(`Failed to read file: ${filePath}`, { cause })
434
731
  );
435
- var readFileAndDecode = (filePath, schema) => readFile(filePath).andThen(parseJson).andThen(decode(schema));
436
- var fileExists = (path2) => ResultAsync5.fromPromise(
437
- fs2.stat(path2),
732
+ var readFileAndDecode = (filePath, schema) => readFile2(filePath).andThen(parseJson).andThen(decode(schema));
733
+ var fileExists = (path2) => ResultAsync6.fromPromise(
734
+ fs3.stat(path2),
438
735
  () => new Error(`${path2} not found.`)
439
736
  ).map(() => true);
440
737
 
@@ -464,14 +761,14 @@ var makePackageJsonReader = () => ({
464
761
 
465
762
  // src/adapters/node/repository.ts
466
763
  import * as glob from "glob";
467
- import { okAsync as okAsync3, ResultAsync as ResultAsync6 } from "neverthrow";
764
+ import { okAsync as okAsync2, ResultAsync as ResultAsync7 } from "neverthrow";
468
765
  import * as path from "path";
469
766
  import { z as z3 } from "zod/v4";
470
767
 
471
768
  // src/adapters/yaml/index.ts
472
- import { Result as Result2 } from "neverthrow";
769
+ import { Result as Result3 } from "neverthrow";
473
770
  import yaml from "yaml";
474
- var parseYaml = Result2.fromThrowable(
771
+ var parseYaml = Result3.fromThrowable(
475
772
  (content) => yaml.parse(content),
476
773
  () => new Error("Failed to parse YAML")
477
774
  );
@@ -485,7 +782,7 @@ var findRepositoryRoot = (dir = process.cwd()) => {
485
782
  )
486
783
  ).map(() => dir);
487
784
  };
488
- var resolveWorkspacePattern = (repoRoot2, pattern) => ResultAsync6.fromPromise(
785
+ var resolveWorkspacePattern = (repoRoot2, pattern) => ResultAsync7.fromPromise(
489
786
  // For now it is not possible to use the fs.glob function (from node:fs/promises)
490
787
  // because it is not possible to run it on Node 20.x
491
788
  glob.glob(pattern, { cwd: repoRoot2 }),
@@ -498,17 +795,17 @@ var resolveWorkspacePattern = (repoRoot2, pattern) => ResultAsync6.fromPromise(
498
795
  subDirectories.map((directory) => path.join(repoRoot2, directory))
499
796
  )
500
797
  );
501
- var getWorkspaces = (repoRoot2) => readFile(path.join(repoRoot2, "pnpm-workspace.yaml")).andThen(parseYaml).andThen(
798
+ var getWorkspaces = (repoRoot2) => readFile2(path.join(repoRoot2, "pnpm-workspace.yaml")).andThen(parseYaml).andThen(
502
799
  (obj) => (
503
800
  // If no packages are defined, go on with an empty array
504
801
  decode(z3.object({ packages: z3.array(z3.string()) }))(obj).orElse(
505
- () => okAsync3({ packages: [] })
802
+ () => okAsync2({ packages: [] })
506
803
  )
507
804
  )
508
805
  ).andThen(
509
806
  ({ packages }) => (
510
807
  // For every package pattern in the pnpm-workspace.yaml file, get the list of subdirectories
511
- ResultAsync6.combine(
808
+ ResultAsync7.combine(
512
809
  packages.map((pattern) => resolveWorkspacePattern(repoRoot2, pattern))
513
810
  ).map((workspacesList) => workspacesList.flat()).andThen((workspaceFolders) => {
514
811
  const workspaceResults = workspaceFolders.map(
@@ -522,7 +819,7 @@ var getWorkspaces = (repoRoot2) => readFile(path.join(repoRoot2, "pnpm-workspace
522
819
  )
523
820
  )
524
821
  );
525
- return ResultAsync6.combine(workspaceResults);
822
+ return ResultAsync7.combine(workspaceResults);
526
823
  })
527
824
  )
528
825
  );
@@ -530,7 +827,7 @@ var makeRepositoryReader = () => ({
530
827
  fileExists,
531
828
  findRepositoryRoot,
532
829
  getWorkspaces,
533
- readFile
830
+ readFile: readFile2
534
831
  });
535
832
 
536
833
  // src/config.ts
@@ -544,10 +841,23 @@ var getConfig = (repositoryRoot2) => ({
544
841
  });
545
842
 
546
843
  // src/use-cases/apply-codemod.ts
547
- import { errAsync, okAsync as okAsync4 } from "neverthrow";
548
- var applyCodemodById = (registry2) => (id) => registry2.getById(id).andThen(
549
- (codemod) => codemod ? okAsync4(codemod) : errAsync(new Error(`Codemod with id ${id} not found`))
550
- ).andThen((codemod) => codemod.apply());
844
+ import { errAsync, okAsync as okAsync3, ResultAsync as ResultAsync8 } from "neverthrow";
845
+ var getCodemodById = (registry2, id) => registry2.getById(id).andThen(
846
+ (codemod) => codemod ? okAsync3(codemod) : errAsync(new Error(`Codemod with id ${id} not found`))
847
+ );
848
+ var safeGetInfo = (getInfo2) => ResultAsync8.fromPromise(
849
+ getInfo2(),
850
+ (error) => new Error("Failed to get info", { cause: error })
851
+ );
852
+ var applyCodemodById = (registry2, getInfo2) => (id) => ResultAsync8.combine([
853
+ safeGetInfo(getInfo2),
854
+ getCodemodById(registry2, id)
855
+ ]).andThen(
856
+ ([info, codemod]) => ResultAsync8.fromPromise(codemod.apply(info), (error) => {
857
+ const message = error instanceof Error ? `: ${error.message}` : "";
858
+ return new Error("Failed to apply codemod" + message, { cause: error });
859
+ })
860
+ );
551
861
 
552
862
  // src/use-cases/list-codemods.ts
553
863
  var listCodemods = (registry2) => () => registry2.getAll();
@@ -570,7 +880,7 @@ await configure({
570
880
  }
571
881
  }
572
882
  });
573
- var logger = getLogger4(["dx-cli"]);
883
+ var logger = getLogger9(["dx-cli"]);
574
884
  var repositoryReader = makeRepositoryReader();
575
885
  var packageJsonReader = makePackageJsonReader();
576
886
  var validationReporter = makeValidationReporter();
@@ -589,13 +899,15 @@ if (repoPackageJson.isErr()) {
589
899
  }
590
900
  var packageJson = repoPackageJson.value;
591
901
  var deps = {
592
- applyCodemodById: applyCodemodById(codemods_default),
593
- listCodemods: listCodemods(codemods_default),
594
902
  packageJson,
595
903
  packageJsonReader,
596
904
  repositoryReader,
597
905
  validationReporter
598
906
  };
599
907
  var config = getConfig(repositoryRoot);
600
- var program = makeCli(deps, config);
908
+ var useCases = {
909
+ applyCodemodById: applyCodemodById(codemods_default, getInfo(deps, config)),
910
+ listCodemods: listCodemods(codemods_default)
911
+ };
912
+ var program = makeCli(deps, config, useCases);
601
913
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "A CLI useful to manage DX tools.",
6
6
  "repository": {
@@ -22,16 +22,21 @@
22
22
  "@logtape/logtape": "^1.0.0",
23
23
  "commander": "^14.0.0",
24
24
  "core-js": "^3.44.0",
25
+ "execa": "^9.6.0",
25
26
  "glob": "^11.0.3",
26
27
  "neverthrow": "^8.2.0",
28
+ "node-plop": "^0.32.1",
29
+ "octokit": "^5.0.3",
30
+ "replace-in-file": "^8.3.0",
27
31
  "semver": "^7.7.2",
28
32
  "yaml": "^2.8.0",
29
- "zod": "^3.25.28"
33
+ "zod": "^3.25.28",
34
+ "@pagopa/monorepo-generator": "^0.6.0"
30
35
  },
31
36
  "devDependencies": {
32
37
  "@tsconfig/node22": "22.0.2",
33
38
  "@types/node": "^22.16.2",
34
- "@types/semver": "^7.7.0",
39
+ "@types/semver": "^7.7.1",
35
40
  "@vitest/coverage-v8": "^3.2.4",
36
41
  "eslint": "^9.30.0",
37
42
  "memfs": "^4.23.0",
@@ -40,7 +45,7 @@
40
45
  "typescript": "~5.8.3",
41
46
  "vitest": "^3.2.4",
42
47
  "vitest-mock-extended": "^3.1.0",
43
- "@pagopa/eslint-config": "^5.0.0"
48
+ "@pagopa/eslint-config": "^5.1.0"
44
49
  },
45
50
  "engines": {
46
51
  "node": ">=22.0.0"