@drupal-canvas/cli 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk2 from 'chalk';
3
3
  import { Command } from 'commander';
4
- import * as p9 from '@clack/prompts';
4
+ import * as p from '@clack/prompts';
5
5
  import * as fs from 'fs';
6
6
  import fs__default, { realpathSync as realpathSync$1, readlinkSync, readdirSync, readdir as readdir$1, lstatSync, promises } from 'fs';
7
7
  import path11, { win32, posix } from 'path';
@@ -14,18 +14,18 @@ import { transformSync } from '@swc/wasm';
14
14
  import { basename } from 'path/win32';
15
15
  import { ESLint } from 'eslint';
16
16
  import { required } from '@drupal-canvas/eslint-config';
17
- import { table } from 'table';
18
17
  import { fileURLToPath } from 'url';
19
18
  import { EventEmitter } from 'events';
20
19
  import Stream from 'stream';
21
20
  import { StringDecoder } from 'string_decoder';
21
+ import { table } from 'table';
22
22
  import * as yaml from 'js-yaml';
23
23
  import yaml__default from 'js-yaml';
24
24
  import { parse } from '@babel/parser';
25
25
 
26
26
  // package.json
27
27
  var package_default = {
28
- version: "0.3.0"};
28
+ version: "0.4.0"};
29
29
  function loadEnvFiles() {
30
30
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
31
31
  if (homeDir) {
@@ -65,7 +65,7 @@ async function ensureConfig(requiredKeys) {
65
65
  async function promptForConfig(key) {
66
66
  switch (key) {
67
67
  case "siteUrl": {
68
- const value = await p9.text({
68
+ const value = await p.text({
69
69
  message: "Enter the site URL",
70
70
  placeholder: "https://example.com",
71
71
  validate: (value2) => {
@@ -75,45 +75,45 @@ async function promptForConfig(key) {
75
75
  return;
76
76
  }
77
77
  });
78
- if (p9.isCancel(value)) {
79
- p9.cancel("Operation cancelled");
78
+ if (p.isCancel(value)) {
79
+ p.cancel("Operation cancelled");
80
80
  process.exit(0);
81
81
  }
82
82
  setConfig({ siteUrl: value });
83
83
  break;
84
84
  }
85
85
  case "clientId": {
86
- const value = await p9.text({
86
+ const value = await p.text({
87
87
  message: "Enter your client ID",
88
88
  validate: (value2) => {
89
89
  if (!value2) return "Client ID is required";
90
90
  return;
91
91
  }
92
92
  });
93
- if (p9.isCancel(value)) {
94
- p9.cancel("Operation cancelled");
93
+ if (p.isCancel(value)) {
94
+ p.cancel("Operation cancelled");
95
95
  process.exit(0);
96
96
  }
97
97
  setConfig({ clientId: value });
98
98
  break;
99
99
  }
100
100
  case "clientSecret": {
101
- const value = await p9.password({
101
+ const value = await p.password({
102
102
  message: "Enter your client secret",
103
103
  validate: (value2) => {
104
104
  if (!value2) return "Client secret is required";
105
105
  return;
106
106
  }
107
107
  });
108
- if (p9.isCancel(value)) {
109
- p9.cancel("Operation cancelled");
108
+ if (p.isCancel(value)) {
109
+ p.cancel("Operation cancelled");
110
110
  process.exit(0);
111
111
  }
112
112
  setConfig({ clientSecret: value });
113
113
  break;
114
114
  }
115
115
  case "componentDir": {
116
- const value = await p9.text({
116
+ const value = await p.text({
117
117
  message: "Enter the component directory",
118
118
  placeholder: "./components",
119
119
  validate: (value2) => {
@@ -121,8 +121,8 @@ async function promptForConfig(key) {
121
121
  return;
122
122
  }
123
123
  });
124
- if (p9.isCancel(value)) {
125
- p9.cancel("Operation cancelled");
124
+ if (p.isCancel(value)) {
125
+ p.cancel("Operation cancelled");
126
126
  process.exit(0);
127
127
  }
128
128
  setConfig({ componentDir: value });
@@ -10822,9 +10822,9 @@ async function downloadJsSourceFromCanvas(componentsToDownload) {
10822
10822
  }
10823
10823
  } catch (error) {
10824
10824
  if (error instanceof Error) {
10825
- p9.note(chalk2.red(`Error: ${error.message}`));
10825
+ p.note(chalk2.red(`Error: ${error.message}`));
10826
10826
  } else {
10827
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
10827
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
10828
10828
  }
10829
10829
  }
10830
10830
  }
@@ -10846,9 +10846,9 @@ async function copyLocalJsSource(componentsToCopy) {
10846
10846
  }
10847
10847
  } catch (error) {
10848
10848
  if (error instanceof Error) {
10849
- p9.note(chalk2.red(`Error: ${error.message}`));
10849
+ p.note(chalk2.red(`Error: ${error.message}`));
10850
10850
  } else {
10851
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
10851
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
10852
10852
  }
10853
10853
  }
10854
10854
  }
@@ -10866,7 +10866,7 @@ async function cleanUpCacheDirectory() {
10866
10866
  }
10867
10867
  }
10868
10868
  } catch (error) {
10869
- p9.note(
10869
+ p.note(
10870
10870
  chalk2.red(
10871
10871
  `Failed to clean cache directory contents: ${error instanceof Error ? error.message : String(error)}`
10872
10872
  )
@@ -11086,57 +11086,28 @@ async function buildComponent(componentDir) {
11086
11086
  }
11087
11087
  return result;
11088
11088
  }
11089
- function reportResults(results, title, itemLabel = "Component") {
11090
- results.sort((a, b) => a.itemName.localeCompare(b.itemName));
11091
- const successful = results.filter((r) => r.success).length;
11092
- const failed = results.filter((r) => !r.success).length;
11093
- const hasDetails = results.some((r) => (r.details?.length ?? 0) > 0);
11094
- const succeededText = failed === 0 ? chalk2.green(`${successful} succeeded`) : `${successful} succeeded`;
11095
- const failedText = failed > 0 ? chalk2.red(`${failed} failed`) : chalk2.dim(`${failed} failed`);
11096
- const summary = `${succeededText}, ${failedText}`;
11097
- if (results.length > 0) {
11098
- const tableData = [
11099
- hasDetails ? [chalk2.bold(title), "", ""] : [chalk2.bold(title), ""],
11100
- hasDetails ? [itemLabel, "Status", "Details"] : [itemLabel, "Status"],
11101
- ...results.map(
11102
- (r) => hasDetails ? [
11103
- r.itemName,
11104
- r.success ? chalk2.green("Success") : chalk2.red("Failed"),
11105
- r.details?.map(
11106
- (d2) => d2.heading ? `${chalk2.underline(d2.heading)}:
11107
- ${d2.content}` : d2.content
11108
- ).join("\n\n")
11109
- ] : [
11110
- r.itemName,
11111
- r.success ? chalk2.green("Success") : chalk2.red("Failed")
11112
- ]
11113
- ),
11114
- hasDetails ? ["SUMMARY", "", summary] : ["SUMMARY", summary]
11115
- ];
11116
- p9.log.info(
11117
- table(tableData, {
11118
- spanningCells: [
11119
- {
11120
- row: 0,
11121
- col: 0,
11122
- colSpan: hasDetails ? 3 : 2,
11123
- alignment: "center"
11124
- },
11125
- {
11126
- row: results.length + 2,
11127
- col: 0,
11128
- colSpan: hasDetails ? 2 : 1,
11129
- alignment: hasDetails ? "right" : "left"
11130
- }
11131
- ],
11132
- columns: {
11133
- // Limit the width of the details column for improved readability of long details.
11134
- 2: { width: 100, wrapWord: true }
11135
- }
11136
- })
11089
+
11090
+ // src/utils/command-helpers.ts
11091
+ var ALL_COMPONENTS_SELECTOR = "_allComponents";
11092
+ function validateComponentOptions(options) {
11093
+ if (options.components && options.all) {
11094
+ throw new Error(
11095
+ "Cannot use --all and --components options together. Please use either:\n \u2022 --components to specify specific components, or\n \u2022 --all to process everything."
11137
11096
  );
11138
11097
  }
11139
11098
  }
11099
+ function updateConfigFromOptions(options) {
11100
+ if (options.clientId) setConfig({ clientId: options.clientId });
11101
+ if (options.clientSecret) setConfig({ clientSecret: options.clientSecret });
11102
+ if (options.siteUrl) setConfig({ siteUrl: options.siteUrl });
11103
+ if (options.dir) setConfig({ componentDir: options.dir });
11104
+ if (options.scope) setConfig({ scope: options.scope });
11105
+ if (options.all) setConfig({ all: options.all });
11106
+ if (options.verbose) setConfig({ verbose: true });
11107
+ }
11108
+ function pluralizeComponent(count) {
11109
+ return count === 1 ? "component" : "components";
11110
+ }
11140
11111
 
11141
11112
  // ../node_modules/@isaacs/balanced-match/dist/esm/index.js
11142
11113
  var balanced = (a, b, str) => {
@@ -17772,50 +17743,244 @@ async function findComponentDirectories(baseDir) {
17772
17743
  }
17773
17744
  }
17774
17745
 
17775
- // src/utils/select-local-components.ts
17776
- async function selectLocalComponents(allFlag, message = "Select components", baseDir) {
17746
+ // src/utils/component-selector.ts
17747
+ async function selectLocalComponents(options) {
17777
17748
  const config2 = getConfig();
17778
- const searchDir = config2.componentDir;
17779
- const componentDirs = await findComponentDirectories(searchDir);
17780
- if (componentDirs.length === 0) {
17781
- p9.outro(`\u{1F4C2} No local components were found in ${searchDir}`);
17782
- return null;
17749
+ const componentDir = options.componentDir || config2.componentDir;
17750
+ const allLocalDirs = await findComponentDirectories(componentDir);
17751
+ if (allLocalDirs.length === 0) {
17752
+ throw new Error(`No local components were found in ${componentDir}`);
17753
+ }
17754
+ if (options.components) {
17755
+ return selectSpecificLocalComponents(
17756
+ options.components,
17757
+ allLocalDirs,
17758
+ options
17759
+ );
17783
17760
  }
17784
- if (allFlag) {
17785
- p9.log.info(`Selected all components`);
17786
- return componentDirs;
17761
+ if (options.all) {
17762
+ if (!options.skipConfirmation) {
17763
+ const confirmed = await confirmSelection(
17764
+ allLocalDirs.length,
17765
+ options.confirmMessage
17766
+ );
17767
+ if (!confirmed) {
17768
+ throw new Error("Operation cancelled by user");
17769
+ }
17770
+ }
17771
+ p.log.info(`Selected all components`);
17772
+ return { directories: allLocalDirs };
17787
17773
  }
17788
- const selectedDirs = await p9.multiselect({
17789
- message,
17774
+ return selectLocalComponentsInteractive(allLocalDirs, options);
17775
+ }
17776
+ async function selectSpecificLocalComponents(componentsInput, allLocalDirs, options) {
17777
+ const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17778
+ const notFound = [];
17779
+ const foundDirs = [];
17780
+ for (const requestedName of requestedNames) {
17781
+ const dir = allLocalDirs.find((d2) => path11.basename(d2) === requestedName);
17782
+ if (dir) {
17783
+ foundDirs.push(dir);
17784
+ } else {
17785
+ notFound.push(requestedName);
17786
+ }
17787
+ }
17788
+ if (notFound.length > 0) {
17789
+ const message = options.notFoundMessage || `The following component(s) were not found locally: ${notFound.join(", ")}`;
17790
+ throw new Error(message);
17791
+ }
17792
+ if (!options.skipConfirmation) {
17793
+ const confirmed = await confirmSelection(
17794
+ foundDirs.length,
17795
+ options.confirmMessage
17796
+ );
17797
+ if (!confirmed) {
17798
+ throw new Error("Operation cancelled by user");
17799
+ }
17800
+ }
17801
+ return { directories: foundDirs };
17802
+ }
17803
+ async function selectLocalComponentsInteractive(allLocalDirs, options) {
17804
+ const selectedDirs = await p.multiselect({
17805
+ message: options.selectMessage || "Select components",
17790
17806
  options: [
17791
17807
  {
17792
- value: "_allComponents",
17808
+ value: ALL_COMPONENTS_SELECTOR,
17793
17809
  label: "All components"
17794
17810
  },
17795
- ...componentDirs.map((dir) => ({
17811
+ ...allLocalDirs.map((dir) => ({
17796
17812
  value: dir,
17797
17813
  label: path11.basename(dir)
17798
17814
  }))
17799
17815
  ],
17800
17816
  required: true
17801
17817
  });
17802
- if (p9.isCancel(selectedDirs)) {
17803
- p9.cancel("Operation cancelled");
17804
- return null;
17818
+ if (p.isCancel(selectedDirs)) {
17819
+ throw new Error("Operation cancelled by user");
17820
+ }
17821
+ const finalDirs = selectedDirs.includes(ALL_COMPONENTS_SELECTOR) ? allLocalDirs : selectedDirs;
17822
+ if (!options.skipConfirmation) {
17823
+ const confirmed = await confirmSelection(
17824
+ finalDirs.length,
17825
+ options.confirmMessage
17826
+ );
17827
+ if (!confirmed) {
17828
+ throw new Error("Operation cancelled by user");
17829
+ }
17830
+ }
17831
+ return { directories: finalDirs };
17832
+ }
17833
+ async function selectRemoteComponents(allComponents, options) {
17834
+ const componentCount = Object.keys(allComponents).length;
17835
+ if (componentCount === 0) {
17836
+ throw new Error("No components found");
17837
+ }
17838
+ if (options.all) {
17839
+ if (!options.skipConfirmation) {
17840
+ const confirmed = await confirmSelection(
17841
+ componentCount,
17842
+ options.confirmMessage
17843
+ );
17844
+ if (!confirmed) {
17845
+ throw new Error("Operation cancelled by user");
17846
+ }
17847
+ }
17848
+ return { components: allComponents };
17849
+ }
17850
+ if (options.components) {
17851
+ return selectSpecificRemoteComponents(
17852
+ options.components,
17853
+ allComponents,
17854
+ options
17855
+ );
17856
+ }
17857
+ return selectRemoteComponentsInteractive(allComponents, options);
17858
+ }
17859
+ async function selectSpecificRemoteComponents(componentsInput, allComponents, options) {
17860
+ const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17861
+ const notFound = [];
17862
+ const selected = {};
17863
+ for (const requestedName of requestedNames) {
17864
+ const component = allComponents[requestedName];
17865
+ if (component) {
17866
+ selected[requestedName] = component;
17867
+ } else {
17868
+ notFound.push(requestedName);
17869
+ }
17870
+ }
17871
+ if (notFound.length > 0) {
17872
+ const message = options.notFoundMessage || `The following component(s) were not found: ${notFound.join(", ")}`;
17873
+ throw new Error(message);
17874
+ }
17875
+ if (!options.skipConfirmation) {
17876
+ const confirmed = await confirmSelection(
17877
+ Object.keys(selected).length,
17878
+ options.confirmMessage
17879
+ );
17880
+ if (!confirmed) {
17881
+ throw new Error("Operation cancelled by user");
17882
+ }
17805
17883
  }
17806
- const count = selectedDirs.includes("_allComponents") ? componentDirs.length : selectedDirs.length;
17807
- const confirmBuild = await p9.confirm({
17808
- message: `Process ${count} components`,
17884
+ return { components: selected };
17885
+ }
17886
+ async function selectRemoteComponentsInteractive(allComponents, options) {
17887
+ const selectedMachineNames = await p.multiselect({
17888
+ message: options.selectMessage || "Select components to download",
17889
+ options: [
17890
+ {
17891
+ value: ALL_COMPONENTS_SELECTOR,
17892
+ label: "All components"
17893
+ },
17894
+ ...Object.keys(allComponents).map((key) => ({
17895
+ value: allComponents[key].machineName,
17896
+ label: `${allComponents[key].name} (${allComponents[key].machineName})`
17897
+ }))
17898
+ ],
17899
+ required: true
17900
+ });
17901
+ if (p.isCancel(selectedMachineNames)) {
17902
+ throw new Error("Operation cancelled by user");
17903
+ }
17904
+ const selected = selectedMachineNames.includes(
17905
+ ALL_COMPONENTS_SELECTOR
17906
+ ) ? allComponents : Object.fromEntries(
17907
+ Object.entries(allComponents).filter(
17908
+ ([, component]) => selectedMachineNames.includes(component.machineName)
17909
+ )
17910
+ );
17911
+ if (!options.skipConfirmation) {
17912
+ const confirmed = await confirmSelection(
17913
+ Object.keys(selected).length,
17914
+ options.confirmMessage
17915
+ );
17916
+ if (!confirmed) {
17917
+ throw new Error("Operation cancelled by user");
17918
+ }
17919
+ }
17920
+ return { components: selected };
17921
+ }
17922
+ async function confirmSelection(count, customMessage) {
17923
+ const componentLabel = count === 1 ? "component" : "components";
17924
+ const message = customMessage || `Process ${count} ${componentLabel}?`;
17925
+ const confirmed = await p.confirm({
17926
+ message,
17809
17927
  initialValue: true
17810
17928
  });
17811
- if (p9.isCancel(confirmBuild) || !confirmBuild) {
17812
- p9.cancel("Operation cancelled");
17813
- return null;
17929
+ if (p.isCancel(confirmed) || !confirmed) {
17930
+ return false;
17814
17931
  }
17815
- if (selectedDirs.includes("_allComponents")) {
17816
- return componentDirs;
17932
+ return true;
17933
+ }
17934
+ function reportResults(results, title, itemLabel = "Component") {
17935
+ results.sort((a, b) => a.itemName.localeCompare(b.itemName));
17936
+ const successful = results.filter((r) => r.success).length;
17937
+ const failed = results.filter((r) => !r.success).length;
17938
+ const hasDetails = results.some((r) => (r.details?.length ?? 0) > 0);
17939
+ const succeededText = failed === 0 ? chalk2.green(`${successful} succeeded`) : `${successful} succeeded`;
17940
+ const failedText = failed > 0 ? chalk2.red(`${failed} failed`) : chalk2.dim(`${failed} failed`);
17941
+ const summary = `${succeededText}, ${failedText}`;
17942
+ if (results.length > 0) {
17943
+ const tableData = [
17944
+ hasDetails ? [chalk2.bold(title), "", ""] : [chalk2.bold(title), ""],
17945
+ hasDetails ? [itemLabel, "Status", "Details"] : [itemLabel, "Status"],
17946
+ ...results.map(
17947
+ (r) => hasDetails ? [
17948
+ r.itemName,
17949
+ r.success ? chalk2.green("Success") : chalk2.red("Failed"),
17950
+ r.details?.map(
17951
+ (d2) => d2.heading ? `${chalk2.underline(d2.heading)}:
17952
+ ${d2.content}` : d2.content
17953
+ ).join("\n\n") ?? ""
17954
+ ] : [
17955
+ r.itemName,
17956
+ r.success ? chalk2.green("Success") : chalk2.red("Failed")
17957
+ ]
17958
+ ),
17959
+ hasDetails ? ["SUMMARY", "", summary] : ["SUMMARY", summary]
17960
+ ];
17961
+ p.log.info(
17962
+ table(tableData, {
17963
+ spanningCells: [
17964
+ {
17965
+ row: 0,
17966
+ col: 0,
17967
+ colSpan: hasDetails ? 3 : 2,
17968
+ alignment: "center"
17969
+ },
17970
+ {
17971
+ row: results.length + 2,
17972
+ col: 0,
17973
+ colSpan: hasDetails ? 2 : 1,
17974
+ alignment: hasDetails ? "right" : "left"
17975
+ }
17976
+ ],
17977
+ columns: {
17978
+ // Limit the width of the details column for improved readability of long details.
17979
+ 2: { width: 100, wrapWord: true }
17980
+ }
17981
+ })
17982
+ );
17817
17983
  }
17818
- return selectedDirs;
17819
17984
  }
17820
17985
 
17821
17986
  // src/commands/build.ts
@@ -17823,75 +17988,84 @@ function buildCommand(program2) {
17823
17988
  program2.command("build").description("build local components and Tailwind CSS assets").option(
17824
17989
  "-d, --dir <directory>",
17825
17990
  "Component directory to build the components in"
17826
- ).option("--all", "Build all components").option("--no-tailwind", "Skip Tailwind CSS building").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--verbose", "Enable verbose output").action(async (options) => {
17827
- p9.intro(chalk2.bold("Drupal Canvas CLI: build"));
17828
- const allFlag = options.all || false;
17829
- const skipTailwind = !options.tailwind;
17830
- if (options.dir) setConfig({ componentDir: options.dir });
17831
- if (options.clientId) setConfig({ clientId: options.clientId });
17832
- if (options.clientSecret)
17833
- setConfig({ clientSecret: options.clientSecret });
17834
- if (options.siteUrl) setConfig({ siteUrl: options.siteUrl });
17835
- if (options.verbose) setConfig({ verbose: true });
17836
- if (!skipTailwind) {
17837
- await ensureConfig(["siteUrl", "clientId", "clientSecret"]);
17838
- }
17839
- const selectedComponents = await selectLocalComponents(
17840
- allFlag,
17841
- "Select components to build"
17842
- );
17843
- if (!selectedComponents || selectedComponents.length === 0) {
17844
- return;
17845
- }
17846
- const componentLabelPluralized = selectedComponents.length === 1 ? "component" : "components";
17847
- const s1 = p9.spinner();
17848
- s1.start(`Building ${componentLabelPluralized}`);
17849
- const results = [];
17850
- for (const componentDir of selectedComponents) {
17851
- results.push(await buildComponent(componentDir));
17852
- }
17853
- s1.stop(
17854
- chalk2.green(
17855
- `Processed ${selectedComponents.length} ${componentLabelPluralized}`
17856
- )
17857
- );
17858
- reportResults(results, "Built components", "Component");
17859
- if (results.map((result) => result.success).includes(false)) {
17860
- process.exit(1);
17861
- }
17862
- if (skipTailwind) {
17863
- p9.log.info("Skipping Tailwind CSS build");
17864
- } else {
17865
- const s2 = p9.spinner();
17866
- s2.start("Building Tailwind CSS");
17867
- const tailwindResult = await buildTailwindForComponents(
17868
- selectedComponents
17991
+ ).option("--all", "Build all components").option(
17992
+ "-c, --components <names>",
17993
+ "Specific component(s) to build (comma-separated)"
17994
+ ).option("--no-tailwind", "Skip Tailwind CSS building").option("-y, --yes", "Skip confirmation prompts").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--verbose", "Enable verbose output").action(async (options) => {
17995
+ try {
17996
+ p.intro(chalk2.bold("Drupal Canvas CLI: build"));
17997
+ validateComponentOptions(options);
17998
+ const allFlag = options.all || options.yes && !options.components || false;
17999
+ const skipTailwind = !options.tailwind;
18000
+ updateConfigFromOptions(options);
18001
+ if (!skipTailwind) {
18002
+ await ensureConfig(["siteUrl", "clientId", "clientSecret"]);
18003
+ }
18004
+ const { directories: componentsToBuild } = await selectLocalComponents({
18005
+ all: allFlag,
18006
+ components: options.components,
18007
+ skipConfirmation: options.yes,
18008
+ selectMessage: "Select components to build"
18009
+ });
18010
+ const componentLabelPluralized = pluralizeComponent(
18011
+ componentsToBuild.length
17869
18012
  );
17870
- s2.stop(
18013
+ const s1 = p.spinner();
18014
+ s1.start(`Building ${componentLabelPluralized}`);
18015
+ const results = [];
18016
+ for (const componentDir of componentsToBuild) {
18017
+ results.push(await buildComponent(componentDir));
18018
+ }
18019
+ s1.stop(
17871
18020
  chalk2.green(
17872
- `Processed Tailwind CSS classes from ${selectedComponents.length} selected local ${componentLabelPluralized} and all online components`
18021
+ `Processed ${componentsToBuild.length} ${componentLabelPluralized}`
17873
18022
  )
17874
18023
  );
17875
- reportResults([tailwindResult], "Built assets", "Asset");
17876
- if (!tailwindResult.success) {
17877
- return process.exit(1);
18024
+ reportResults(results, "Built components", "Component");
18025
+ if (results.map((result) => result.success).includes(false)) {
18026
+ process.exit(1);
18027
+ }
18028
+ if (skipTailwind) {
18029
+ p.log.info("Skipping Tailwind CSS build");
18030
+ } else {
18031
+ const s2 = p.spinner();
18032
+ s2.start("Building Tailwind CSS");
18033
+ const tailwindResult = await buildTailwindForComponents(
18034
+ componentsToBuild
18035
+ );
18036
+ s2.stop(
18037
+ chalk2.green(
18038
+ `Processed Tailwind CSS classes from ${componentsToBuild.length} selected local ${componentLabelPluralized} and all online components`
18039
+ )
18040
+ );
18041
+ reportResults([tailwindResult], "Built assets", "Asset");
18042
+ if (!tailwindResult.success) {
18043
+ return process.exit(1);
18044
+ }
17878
18045
  }
18046
+ p.outro(`\u{1F4E6} Build completed`);
18047
+ } catch (error) {
18048
+ if (error instanceof Error) {
18049
+ p.note(chalk2.red(`Error: ${error.message}`));
18050
+ } else {
18051
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18052
+ }
18053
+ process.exit(1);
17879
18054
  }
17880
- p9.outro(`\u{1F4E6} Build completed`);
17881
18055
  });
17882
18056
  }
17883
18057
  function downloadCommand(program2) {
17884
- program2.command("download").description("download components to your local filesystem").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--scope <scope>", "Scope").option("-d, --dir <directory>", "Component directory").option("-c, --component <name>", "Specific component to download").option("--all", "Download all components").option("--verbose", "Enable verbose output").action(async (options) => {
17885
- p9.intro(chalk2.bold("Drupal Canvas CLI: download"));
18058
+ program2.command("download").description("download components to your local filesystem").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--scope <scope>", "Scope").option("-d, --dir <directory>", "Component directory").option(
18059
+ "-c, --components <names>",
18060
+ "Specific component(s) to download (comma-separated)"
18061
+ ).option("--all", "Download all components").option("-y, --yes", "Skip all confirmation prompts").option(
18062
+ "--skip-overwrite",
18063
+ "Skip downloading components that already exist locally"
18064
+ ).option("--verbose", "Enable verbose output").action(async (options) => {
18065
+ p.intro(chalk2.bold("Drupal Canvas CLI: download"));
17886
18066
  try {
17887
- if (options.clientId) setConfig({ clientId: options.clientId });
17888
- if (options.clientSecret)
17889
- setConfig({ clientSecret: options.clientSecret });
17890
- if (options.siteUrl) setConfig({ siteUrl: options.siteUrl });
17891
- if (options.dir) setConfig({ componentDir: options.dir });
17892
- if (options.all) setConfig({ all: options.all });
17893
- if (options.scope) setConfig({ scope: options.scope });
17894
- if (options.verbose) setConfig({ verbose: true });
18067
+ validateComponentOptions(options);
18068
+ updateConfigFromOptions(options);
17895
18069
  await ensureConfig([
17896
18070
  "siteUrl",
17897
18071
  "clientId",
@@ -17901,7 +18075,7 @@ function downloadCommand(program2) {
17901
18075
  ]);
17902
18076
  const config2 = getConfig();
17903
18077
  const apiService = await createApiService();
17904
- const s = p9.spinner();
18078
+ const s = p.spinner();
17905
18079
  s.start("Fetching components");
17906
18080
  const components = await apiService.listComponents();
17907
18081
  const {
@@ -17909,63 +18083,21 @@ function downloadCommand(program2) {
17909
18083
  } = await apiService.getGlobalAssetLibrary();
17910
18084
  if (Object.keys(components).length === 0) {
17911
18085
  s.stop("No components found");
17912
- p9.outro("Download cancelled - no components were found");
18086
+ p.outro("Download cancelled - no components were found");
17913
18087
  return;
17914
18088
  }
17915
18089
  s.stop(`Found ${Object.keys(components).length} components`);
17916
- let componentsToDownload = {};
17917
- if (options.all) {
17918
- componentsToDownload = components;
17919
- } else if (options.component) {
17920
- const component = Object.values(components).find(
17921
- (c) => c.machineName === options.component || c.name === options.component
17922
- );
17923
- if (!component) {
17924
- p9.note(chalk2.red(`Component "${options.component}" not found`));
17925
- p9.outro("Download cancelled");
17926
- return;
17927
- }
17928
- componentsToDownload = { component };
17929
- } else {
17930
- const selectedComponents = await p9.multiselect({
17931
- message: "Select components to download",
17932
- options: [
17933
- {
17934
- value: "_allComponents",
17935
- label: "All components"
17936
- },
17937
- ...Object.keys(components).map((key) => ({
17938
- value: components[key].machineName,
17939
- label: `${components[key].name} (${components[key].machineName})`
17940
- }))
17941
- ],
17942
- required: true
17943
- });
17944
- if (p9.isCancel(selectedComponents)) {
17945
- p9.cancel("Operation cancelled");
17946
- return;
17947
- }
17948
- if (selectedComponents.includes("_allComponents")) {
17949
- componentsToDownload = components;
17950
- } else {
17951
- componentsToDownload = Object.fromEntries(
17952
- Object.entries(components).filter(
17953
- ([, component]) => selectedComponents.includes(
17954
- component.machineName
17955
- )
17956
- )
17957
- );
17958
- }
17959
- }
17960
- const componentPluralized = `component${Object.keys(componentsToDownload).length > 1 ? "s" : ""}`;
17961
- const confirmDownload = await p9.confirm({
17962
- message: `Download ${Object.keys(componentsToDownload).length} ${componentPluralized} to ${config2.componentDir}?`,
17963
- initialValue: true
18090
+ const allFlag = options.all || options.yes && !options.components || false;
18091
+ const { components: componentsToDownload } = await selectRemoteComponents(components, {
18092
+ all: allFlag,
18093
+ components: options.components,
18094
+ skipConfirmation: options.yes,
18095
+ selectMessage: "Select components to download",
18096
+ confirmMessage: `Download to ${config2.componentDir}?`
17964
18097
  });
17965
- if (p9.isCancel(confirmDownload) || !confirmDownload) {
17966
- p9.cancel("Operation cancelled");
17967
- return;
17968
- }
18098
+ const componentPluralized = pluralizeComponent(
18099
+ Object.keys(componentsToDownload).length
18100
+ );
17969
18101
  const results = [];
17970
18102
  s.start(`Downloading ${componentPluralized}`);
17971
18103
  for (const key in componentsToDownload) {
@@ -17979,13 +18111,27 @@ function downloadCommand(program2) {
17979
18111
  if (dirExists) {
17980
18112
  const files = await fs2.readdir(componentDir);
17981
18113
  if (files.length > 0) {
17982
- const confirmDelete = await p9.confirm({
17983
- message: `The "${componentDir}" directory is not empty. Are you sure you want to delete and overwrite this directory?`,
17984
- initialValue: true
17985
- });
17986
- if (p9.isCancel(confirmDelete) || !confirmDelete) {
17987
- p9.cancel("Operation cancelled");
17988
- process.exit(0);
18114
+ if (options.skipOverwrite) {
18115
+ results.push({
18116
+ itemName: component.machineName,
18117
+ success: true,
18118
+ details: [
18119
+ {
18120
+ content: "Skipped (already exists)"
18121
+ }
18122
+ ]
18123
+ });
18124
+ continue;
18125
+ }
18126
+ if (!options.yes) {
18127
+ const confirmDelete = await p.confirm({
18128
+ message: `The "${componentDir}" directory is not empty. Are you sure you want to delete and overwrite this directory?`,
18129
+ initialValue: true
18130
+ });
18131
+ if (p.isCancel(confirmDelete) || !confirmDelete) {
18132
+ p.cancel("Operation cancelled");
18133
+ process.exit(0);
18134
+ }
17989
18135
  }
17990
18136
  }
17991
18137
  }
@@ -18065,12 +18211,12 @@ function downloadCommand(program2) {
18065
18211
  }
18066
18212
  reportResults([globalCssResult], "Downloaded assets", "Asset");
18067
18213
  }
18068
- p9.outro(`\u2B07\uFE0F Download command completed`);
18214
+ p.outro(`\u2B07\uFE0F Download command completed`);
18069
18215
  } catch (error) {
18070
18216
  if (error instanceof Error) {
18071
- p9.note(chalk2.red(`Error: ${error.message}`));
18217
+ p.note(chalk2.red(`Error: ${error.message}`));
18072
18218
  } else {
18073
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18219
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18074
18220
  }
18075
18221
  process.exit(1);
18076
18222
  }
@@ -18084,7 +18230,7 @@ function scaffoldCommand(program2) {
18084
18230
  "-d, --dir <directory>",
18085
18231
  "Component directory to create component in"
18086
18232
  ).option("--verbose", "Enable verbose output").action(async (options) => {
18087
- p9.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18233
+ p.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18088
18234
  try {
18089
18235
  if (options.dir) setConfig({ componentDir: options.dir });
18090
18236
  if (options.verbose) setConfig({ verbose: options.verbose });
@@ -18092,7 +18238,7 @@ function scaffoldCommand(program2) {
18092
18238
  const baseDir = config2.componentDir;
18093
18239
  let componentName = options.name;
18094
18240
  if (!componentName) {
18095
- const name = await p9.text({
18241
+ const name = await p.text({
18096
18242
  message: "Enter the component name",
18097
18243
  placeholder: "my-component",
18098
18244
  validate: (value) => {
@@ -18102,14 +18248,14 @@ function scaffoldCommand(program2) {
18102
18248
  return;
18103
18249
  }
18104
18250
  });
18105
- if (p9.isCancel(name)) {
18106
- p9.cancel("Operation cancelled");
18251
+ if (p.isCancel(name)) {
18252
+ p.cancel("Operation cancelled");
18107
18253
  return;
18108
18254
  }
18109
18255
  componentName = name;
18110
18256
  }
18111
18257
  const componentDir = path11.join(baseDir, componentName);
18112
- const s = p9.spinner();
18258
+ const s = p.spinner();
18113
18259
  s.start(`Creating component "${componentName}"`);
18114
18260
  try {
18115
18261
  await fs2.mkdir(componentDir, { recursive: true });
@@ -18120,12 +18266,12 @@ function scaffoldCommand(program2) {
18120
18266
  const files = await fs2.readdir(templateDir);
18121
18267
  const newDirFiles = await fs2.readdir(componentDir);
18122
18268
  if (newDirFiles.length > 0) {
18123
- const confirmDelete = await p9.confirm({
18269
+ const confirmDelete = await p.confirm({
18124
18270
  message: `The "${componentDir}" directory is not empty. Do you want to proceed and potentially overwrite existing files?`,
18125
18271
  initialValue: true
18126
18272
  });
18127
- if (p9.isCancel(confirmDelete) || !confirmDelete) {
18128
- p9.cancel("Operation cancelled");
18273
+ if (p.isCancel(confirmDelete) || !confirmDelete) {
18274
+ p.cancel("Operation cancelled");
18129
18275
  process.exit(0);
18130
18276
  }
18131
18277
  }
@@ -18138,21 +18284,21 @@ function scaffoldCommand(program2) {
18138
18284
  await fs2.writeFile(destPath, content, "utf-8");
18139
18285
  }
18140
18286
  s.stop(chalk2.green(`Created component "${componentName}"`));
18141
- p9.note(`Component "${componentName}" has been created:
18287
+ p.note(`Component "${componentName}" has been created:
18142
18288
  - Directory: ${componentDir}
18143
18289
  - Component metadata: ${path11.join(componentDir, `component.yml`)}
18144
18290
  - Source file: ${path11.join(componentDir, `index.jsx`)}
18145
18291
  - CSS file: ${path11.join(componentDir, `index.css`)}`);
18146
- p9.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18292
+ p.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18147
18293
  } catch (error) {
18148
18294
  s.stop(chalk2.red(`Failed to create component "${componentName}"`));
18149
18295
  throw error;
18150
18296
  }
18151
18297
  } catch (error) {
18152
18298
  if (error instanceof Error) {
18153
- p9.note(chalk2.red(`Error: ${error.message}`));
18299
+ p.note(chalk2.red(`Error: ${error.message}`));
18154
18300
  } else {
18155
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18301
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18156
18302
  }
18157
18303
  process.exit(1);
18158
18304
  }
@@ -18347,12 +18493,12 @@ async function processInPool(items, processor, concurrency = 10) {
18347
18493
  }
18348
18494
  return results.sort((a, b) => a.index - b.index);
18349
18495
  }
18350
- function createProgressCallback(spinner5, operation, total) {
18496
+ function createProgressCallback(spinner6, operation, total) {
18351
18497
  let completed = 0;
18352
18498
  return () => {
18353
18499
  completed++;
18354
18500
  const percentage = Math.round(completed / total * 100);
18355
- spinner5.message(`${operation} (${completed}/${total} - ${percentage}%)`);
18501
+ spinner6.message(`${operation} (${completed}/${total} - ${percentage}%)`);
18356
18502
  };
18357
18503
  }
18358
18504
 
@@ -18436,19 +18582,16 @@ async function uploadComponents(uploadTasks, apiService, onProgress) {
18436
18582
  });
18437
18583
  }
18438
18584
  function uploadCommand(program2) {
18439
- program2.command("upload").description("build and upload local components and global CSS assets").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--scope <scope>", "Scope").option("-d, --dir <directory>", "Component directory").option("--all", "Upload all components").option("--verbose", "Verbose output").option("--no-tailwind", "Skip Tailwind CSS building").action(async (options) => {
18440
- const allFlag = options.all || false;
18585
+ program2.command("upload").description("build and upload local components and global CSS assets").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client Secret").option("--site-url <url>", "Site URL").option("--scope <scope>", "Scope").option("-d, --dir <directory>", "Component directory").option(
18586
+ "-c, --components <names>",
18587
+ "Specific component(s) to upload (comma-separated)"
18588
+ ).option("--all", "Upload all components").option("-y, --yes", "Skip confirmation prompts").option("--verbose", "Verbose output").option("--no-tailwind", "Skip Tailwind CSS building").action(async (options) => {
18589
+ const allFlag = options.all || options.yes && !options.components || false;
18441
18590
  const skipTailwind = !options.tailwind;
18442
18591
  try {
18443
- p9.intro(chalk2.bold("Drupal Canvas CLI: upload"));
18444
- if (options.clientId) setConfig({ clientId: options.clientId });
18445
- if (options.clientSecret)
18446
- setConfig({ clientSecret: options.clientSecret });
18447
- if (options.siteUrl) setConfig({ siteUrl: options.siteUrl });
18448
- if (options.dir) setConfig({ componentDir: options.dir });
18449
- if (options.scope) setConfig({ scope: options.scope });
18450
- if (options.all) setConfig({ all: options.all });
18451
- if (options.verbose) setConfig({ verbose: options.verbose });
18592
+ p.intro(chalk2.bold("Drupal Canvas CLI: upload"));
18593
+ validateComponentOptions(options);
18594
+ updateConfigFromOptions(options);
18452
18595
  await ensureConfig([
18453
18596
  "siteUrl",
18454
18597
  "clientId",
@@ -18457,28 +18600,34 @@ function uploadCommand(program2) {
18457
18600
  "componentDir"
18458
18601
  ]);
18459
18602
  const config2 = getConfig();
18460
- const componentsToUpload = await selectLocalComponents(
18461
- allFlag,
18462
- "Select components to upload"
18603
+ const { directories: componentsToUpload } = await selectLocalComponents(
18604
+ {
18605
+ all: allFlag,
18606
+ components: options.components,
18607
+ skipConfirmation: options.yes,
18608
+ selectMessage: "Select components to upload"
18609
+ }
18463
18610
  );
18464
- if (!componentsToUpload || componentsToUpload.length === 0) {
18465
- return;
18466
- }
18467
18611
  const apiService = await createApiService();
18468
18612
  const componentResults = await getBuildAndUploadResults(
18469
18613
  componentsToUpload,
18470
18614
  apiService
18471
18615
  );
18472
18616
  reportResults(componentResults, "Uploaded components", "Component");
18617
+ if (componentResults.some((result) => !result.success)) {
18618
+ process.exit(1);
18619
+ }
18473
18620
  if (skipTailwind) {
18474
- p9.log.info("Skipping Tailwind CSS build");
18621
+ p.log.info("Skipping Tailwind CSS build");
18475
18622
  } else {
18476
- const s2 = p9.spinner();
18623
+ const s2 = p.spinner();
18477
18624
  s2.start("Building Tailwind CSS");
18478
18625
  const tailwindResult = await buildTailwindForComponents(
18479
18626
  componentsToUpload
18480
18627
  );
18481
- const componentLabelPluralized = componentsToUpload.length === 1 ? "component" : "components";
18628
+ const componentLabelPluralized = pluralizeComponent(
18629
+ componentsToUpload.length
18630
+ );
18482
18631
  s2.stop(
18483
18632
  chalk2.green(
18484
18633
  `Processed Tailwind CSS classes from ${componentsToUpload.length} selected local ${componentLabelPluralized} and all online components`
@@ -18486,7 +18635,7 @@ function uploadCommand(program2) {
18486
18635
  );
18487
18636
  if (!tailwindResult.success && tailwindResult.details) {
18488
18637
  reportResults([tailwindResult], "Built assets", "Asset");
18489
- p9.note(
18638
+ p.note(
18490
18639
  chalk2.red(`Tailwind build failed, global assets upload aborted.`)
18491
18640
  );
18492
18641
  } else {
@@ -18497,12 +18646,12 @@ function uploadCommand(program2) {
18497
18646
  reportResults([globalCssResult], "Uploaded assets", "Asset");
18498
18647
  }
18499
18648
  }
18500
- p9.outro("\u2B06\uFE0F Upload command completed");
18649
+ p.outro("\u2B06\uFE0F Upload command completed");
18501
18650
  } catch (error) {
18502
18651
  if (error instanceof Error) {
18503
- p9.note(chalk2.red(`Error: ${error.message}`));
18652
+ p.note(chalk2.red(`Error: ${error.message}`));
18504
18653
  } else {
18505
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18654
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18506
18655
  }
18507
18656
  process.exit(1);
18508
18657
  }
@@ -18534,7 +18683,7 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18534
18683
  importedJsComponents = getImportsFromAst(ast, scope);
18535
18684
  dataDependencies = getDataDependenciesFromAst(ast);
18536
18685
  } catch (error) {
18537
- p9.note(chalk2.red(`Error: ${error}`));
18686
+ p.note(chalk2.red(`Error: ${error}`));
18538
18687
  }
18539
18688
  const componentPayloadArg = {
18540
18689
  metadata,
@@ -18572,30 +18721,30 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18572
18721
  }
18573
18722
  async function getBuildAndUploadResults(componentsToUpload, apiService) {
18574
18723
  const results = [];
18575
- const spinner5 = p9.spinner();
18576
- spinner5.start("Building components");
18724
+ const spinner6 = p.spinner();
18725
+ spinner6.start("Building components");
18577
18726
  const buildResults = await buildSelectedComponents(componentsToUpload);
18578
18727
  const successfulBuilds = buildResults.filter((build) => build.success);
18579
18728
  const failedBuilds = buildResults.filter((build) => !build.success);
18580
18729
  if (successfulBuilds.length === 0) {
18581
18730
  const message = "All component builds failed.";
18582
- spinner5.stop(chalk2.red(message));
18731
+ spinner6.stop(chalk2.red(message));
18583
18732
  return failedBuilds;
18584
18733
  }
18585
- spinner5.message("Preparing components for upload");
18734
+ spinner6.message("Preparing components for upload");
18586
18735
  const { prepared: preparedComponents, failed: preparationFailures } = await prepareComponentsForUpload(successfulBuilds, componentsToUpload);
18587
18736
  results.push(...preparationFailures);
18588
18737
  if (preparedComponents.length === 0) {
18589
- spinner5.stop(chalk2.red("All component preparations failed"));
18738
+ spinner6.stop(chalk2.red("All component preparations failed"));
18590
18739
  return [...results, ...failedBuilds];
18591
18740
  }
18592
18741
  const machineNames = preparedComponents.map((c) => c.machineName);
18593
18742
  const existenceProgress = createProgressCallback(
18594
- spinner5,
18743
+ spinner6,
18595
18744
  "Checking component existence",
18596
18745
  machineNames.length
18597
18746
  );
18598
- spinner5.message("Checking component existence");
18747
+ spinner6.message("Checking component existence");
18599
18748
  const existenceResults = await checkComponentsExist(
18600
18749
  machineNames,
18601
18750
  apiService,
@@ -18607,11 +18756,11 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18607
18756
  shouldUpdate: existenceResults[index]?.exists || false
18608
18757
  }));
18609
18758
  const uploadProgress = createProgressCallback(
18610
- spinner5,
18759
+ spinner6,
18611
18760
  "Uploading components",
18612
18761
  uploadTasks.length
18613
18762
  );
18614
- spinner5.message("Uploading components");
18763
+ spinner6.message("Uploading components");
18615
18764
  const uploadResults = await uploadComponents(
18616
18765
  uploadTasks,
18617
18766
  apiService,
@@ -18644,7 +18793,7 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18644
18793
  }
18645
18794
  results.push(...failedBuilds);
18646
18795
  const componentLabelPluralized = results.length === 1 ? "component" : "components";
18647
- spinner5.stop(
18796
+ spinner6.stop(
18648
18797
  chalk2.green(`Processed ${results.length} ${componentLabelPluralized}`)
18649
18798
  );
18650
18799
  return results;
@@ -18714,33 +18863,54 @@ function validateCommand(program2) {
18714
18863
  program2.command("validate").description("validate local components").option(
18715
18864
  "-d, --dir <directory>",
18716
18865
  "Component directory to validate the components in"
18717
- ).option("--all", "Validate all components").option("--verbose", "Enable verbose output").option(
18866
+ ).option(
18867
+ "-c, --components <names>",
18868
+ "Specific component(s) to validate (comma-separated)"
18869
+ ).option("--all", "Validate all components").option("-y, --yes", "Skip confirmation prompts").option("--verbose", "Enable verbose output").option(
18718
18870
  "--fix",
18719
18871
  "Apply available automatic fixes for linting issues",
18720
18872
  false
18721
18873
  ).action(async (options) => {
18722
- p9.intro(chalk2.bold("Drupal Canvas CLI: validate"));
18723
- const allFlag = options.all || false;
18724
- if (options.dir) setConfig({ componentDir: options.dir });
18725
- if (options.verbose) setConfig({ verbose: true });
18726
- const selectedComponents = await selectLocalComponents(
18727
- allFlag,
18728
- "Select components to validate"
18729
- );
18730
- if (!selectedComponents || selectedComponents.length === 0) {
18731
- return;
18732
- }
18733
- const results = [];
18734
- for (const componentDir of selectedComponents) {
18735
- results.push(await validateComponent(componentDir, options.fix));
18736
- }
18737
- reportResults(results, "Validation results");
18738
- const hasErrors = results.some((r) => !r.success);
18739
- if (hasErrors) {
18740
- p9.outro(`\u274C Validation completed with errors`);
18874
+ try {
18875
+ p.intro(chalk2.bold("Drupal Canvas CLI: validate"));
18876
+ validateComponentOptions(options);
18877
+ const allFlag = options.all || options.yes && !options.components || false;
18878
+ updateConfigFromOptions(options);
18879
+ const { directories: componentsToValidate } = await selectLocalComponents({
18880
+ all: allFlag,
18881
+ components: options.components,
18882
+ skipConfirmation: options.yes,
18883
+ selectMessage: "Select components to validate"
18884
+ });
18885
+ const componentPluralized = pluralizeComponent(
18886
+ componentsToValidate.length
18887
+ );
18888
+ const s = p.spinner();
18889
+ s.start(`Validating ${componentPluralized}`);
18890
+ const results = [];
18891
+ for (const componentDir of componentsToValidate) {
18892
+ results.push(await validateComponent(componentDir, options.fix));
18893
+ }
18894
+ s.stop(
18895
+ chalk2.green(
18896
+ `Processed ${componentsToValidate.length} ${componentPluralized}`
18897
+ )
18898
+ );
18899
+ reportResults(results, "Validation results", "Component");
18900
+ const hasErrors = results.some((r) => !r.success);
18901
+ if (hasErrors) {
18902
+ p.outro(`\u274C Validation completed with errors`);
18903
+ process.exit(1);
18904
+ }
18905
+ p.outro(`\u2705 Validation completed`);
18906
+ } catch (error) {
18907
+ if (error instanceof Error) {
18908
+ p.note(chalk2.red(`Error: ${error.message}`));
18909
+ } else {
18910
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18911
+ }
18741
18912
  process.exit(1);
18742
18913
  }
18743
- p9.outro(`\u2705 Validation completed`);
18744
18914
  });
18745
18915
  }
18746
18916