@drupal-canvas/cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
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
- import path11, { win32, posix } from 'path';
7
+ import path3, { win32, posix } from 'path';
8
8
  import dotenv from 'dotenv';
9
9
  import { transform, Features } from 'lightningcss';
10
10
  import axios from 'axios';
@@ -14,27 +14,27 @@ 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.5.0"};
29
29
  function loadEnvFiles() {
30
30
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
31
31
  if (homeDir) {
32
- const homeEnvPath = path11.resolve(homeDir, ".canvasrc");
32
+ const homeEnvPath = path3.resolve(homeDir, ".canvasrc");
33
33
  if (fs__default.existsSync(homeEnvPath)) {
34
34
  dotenv.config({ path: homeEnvPath });
35
35
  }
36
36
  }
37
- const localEnvPath = path11.resolve(process.cwd(), ".env");
37
+ const localEnvPath = path3.resolve(process.cwd(), ".env");
38
38
  if (fs__default.existsSync(localEnvPath)) {
39
39
  dotenv.config({ path: localEnvPath });
40
40
  }
@@ -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 });
@@ -10805,26 +10805,26 @@ function createApiService() {
10805
10805
  userAgent: config2.userAgent
10806
10806
  });
10807
10807
  }
10808
- var CANVAS_CACHE_DIR = path11.join(os.homedir(), ".canvas");
10808
+ var CANVAS_CACHE_DIR = path3.join(os.homedir(), ".canvas");
10809
10809
  async function downloadJsSourceFromCanvas(componentsToDownload) {
10810
10810
  for (const key in componentsToDownload) {
10811
10811
  const component = componentsToDownload[key];
10812
10812
  try {
10813
- const componentDir = path11.join(CANVAS_CACHE_DIR, component.machineName);
10813
+ const componentDir = path3.join(CANVAS_CACHE_DIR, component.machineName);
10814
10814
  await fs2.rm(componentDir, { recursive: true, force: true });
10815
10815
  await fs2.mkdir(componentDir, { recursive: true });
10816
10816
  if (component.sourceCodeJs) {
10817
10817
  await fs2.writeFile(
10818
- path11.join(componentDir, `index.jsx`),
10818
+ path3.join(componentDir, `index.jsx`),
10819
10819
  component.sourceCodeJs,
10820
10820
  "utf-8"
10821
10821
  );
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
  }
@@ -10833,22 +10833,22 @@ async function copyLocalJsSource(componentsToCopy) {
10833
10833
  try {
10834
10834
  await fs2.mkdir(CANVAS_CACHE_DIR, { recursive: true });
10835
10835
  for (const componentPath of componentsToCopy) {
10836
- const baseName = path11.basename(componentPath);
10836
+ const baseName = path3.basename(componentPath);
10837
10837
  const sourcePath = componentPath;
10838
- const targetPath = path11.join(CANVAS_CACHE_DIR, baseName);
10838
+ const targetPath = path3.join(CANVAS_CACHE_DIR, baseName);
10839
10839
  const stats = await fs2.stat(sourcePath);
10840
10840
  if (stats.isDirectory()) {
10841
10841
  await fs2.mkdir(targetPath, { recursive: true });
10842
- const sourceFile = path11.join(sourcePath, "index.jsx");
10843
- const targetFile = path11.join(targetPath, "index.jsx");
10842
+ const sourceFile = path3.join(sourcePath, "index.jsx");
10843
+ const targetFile = path3.join(targetPath, "index.jsx");
10844
10844
  await fs2.copyFile(sourceFile, targetFile);
10845
10845
  }
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
  }
@@ -10858,7 +10858,7 @@ async function cleanUpCacheDirectory() {
10858
10858
  withFileTypes: true
10859
10859
  });
10860
10860
  for (const entry of cacheEntries) {
10861
- const entryPath = path11.join(CANVAS_CACHE_DIR, entry.name);
10861
+ const entryPath = path3.join(CANVAS_CACHE_DIR, entry.name);
10862
10862
  if (entry.isDirectory()) {
10863
10863
  await fs2.rm(entryPath, { recursive: true, force: true });
10864
10864
  } else {
@@ -10866,15 +10866,39 @@ 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
  )
10873
10873
  );
10874
10874
  }
10875
10875
  }
10876
+ async function fileExists(filePath) {
10877
+ try {
10878
+ await fs2.access(filePath);
10879
+ return true;
10880
+ } catch {
10881
+ return false;
10882
+ }
10883
+ }
10884
+ async function directoryExists(dirPath) {
10885
+ return await fs2.stat(dirPath).then(() => true).catch(() => false);
10886
+ }
10876
10887
 
10877
10888
  // src/utils/build-tailwind.ts
10889
+ async function downloadGlobalCssInBackground() {
10890
+ const apiService = await createApiService();
10891
+ const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
10892
+ return globalAssetLibrary.css.original;
10893
+ }
10894
+ async function getGlobalCss(useLocal = true) {
10895
+ const config2 = getConfig();
10896
+ const localGlobalCssPath = path3.join(config2.componentDir, "global.css");
10897
+ if (useLocal && await fileExists(localGlobalCssPath)) {
10898
+ return await promises.readFile(localGlobalCssPath, "utf-8");
10899
+ }
10900
+ return await downloadGlobalCssInBackground();
10901
+ }
10878
10902
  async function getAllClassNameCandidatesFromCacheDir(componentsToDownload, localComponentsToCopy) {
10879
10903
  if (Object.keys(componentsToDownload).length > 0) {
10880
10904
  await downloadJsSourceFromCanvas(componentsToDownload);
@@ -10883,7 +10907,7 @@ async function getAllClassNameCandidatesFromCacheDir(componentsToDownload, local
10883
10907
  const cacheEntries = await promises.readdir(CANVAS_CACHE_DIR, {
10884
10908
  withFileTypes: true
10885
10909
  });
10886
- const cacheDirs = cacheEntries.filter((entry) => entry.isDirectory()).map((dir) => path11.join(CANVAS_CACHE_DIR, dir.name));
10910
+ const cacheDirs = cacheEntries.filter((entry) => entry.isDirectory()).map((dir) => path3.join(CANVAS_CACHE_DIR, dir.name));
10887
10911
  let allClassNameCandidates = [];
10888
10912
  for (const cacheDir of cacheDirs) {
10889
10913
  const componentClassNameCandidates = await getClassNameCandidatesForComponent(cacheDir);
@@ -10901,16 +10925,16 @@ async function buildTailwindCss(classNameCandidates, globalSourceCodeCss, distDi
10901
10925
  globalSourceCodeCss
10902
10926
  );
10903
10927
  const transformedTwCss = await transformCss(compiledTwCss);
10904
- await promises.writeFile(path11.join(distDir, "index.css"), transformedTwCss);
10928
+ await promises.writeFile(path3.join(distDir, "index.css"), transformedTwCss);
10905
10929
  }
10906
10930
  async function getClassNameCandidatesForComponent(dir) {
10907
- const componentName = path11.basename(dir);
10931
+ const componentName = path3.basename(dir);
10908
10932
  const config2 = getConfig();
10909
10933
  const componentsDir = config2.componentDir;
10910
- const distDir = path11.join(componentsDir, "dist");
10911
- const jsSource = await promises.readFile(path11.join(dir, "index.jsx"), "utf-8");
10934
+ const distDir = path3.join(componentsDir, "dist");
10935
+ const jsSource = await promises.readFile(path3.join(dir, "index.jsx"), "utf-8");
10912
10936
  const currentGlobalSourceCodeJs = await promises.readFile(
10913
- path11.join(distDir, "index.js"),
10937
+ path3.join(distDir, "index.js"),
10914
10938
  "utf-8"
10915
10939
  );
10916
10940
  const classNameCandidates = OE(jsSource);
@@ -10919,20 +10943,20 @@ async function getClassNameCandidatesForComponent(dir) {
10919
10943
  componentName,
10920
10944
  classNameCandidates
10921
10945
  );
10922
- await promises.writeFile(path11.join(distDir, "index.js"), globalJSClassNameIndex);
10946
+ await promises.writeFile(path3.join(distDir, "index.js"), globalJSClassNameIndex);
10923
10947
  return nextClassNameCandidates;
10924
10948
  }
10925
- async function buildTailwindForComponents(selectedComponents) {
10949
+ async function buildTailwindForComponents(selectedComponents, useLocalGlobalCss = true) {
10926
10950
  try {
10927
10951
  const config2 = getConfig();
10928
10952
  const apiService = await createApiService();
10929
10953
  const onlineComponents = await apiService.listComponents();
10954
+ const globalSourceCodeCss = await getGlobalCss(useLocalGlobalCss);
10930
10955
  const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
10931
10956
  const globalSourceCodeJs = globalAssetLibrary.js.original;
10932
- const globalSourceCodeCss = globalAssetLibrary.css.original;
10933
- const distDir = path11.join(config2.componentDir, "dist");
10957
+ const distDir = path3.join(config2.componentDir, "dist");
10934
10958
  await promises.mkdir(distDir, { recursive: true });
10935
- const targetFile = path11.join(distDir, "index.js");
10959
+ const targetFile = path3.join(distDir, "index.js");
10936
10960
  await promises.writeFile(targetFile, globalSourceCodeJs, "utf-8");
10937
10961
  const allClassNameCandidates = await getAllClassNameCandidatesFromCacheDir(
10938
10962
  onlineComponents,
@@ -10984,17 +11008,6 @@ function compileJS(source) {
10984
11008
  const { code } = transformSync(source, SWC_OPTIONS);
10985
11009
  return code;
10986
11010
  }
10987
- async function fileExists(filePath) {
10988
- try {
10989
- await fs2.access(filePath);
10990
- return true;
10991
- } catch {
10992
- return false;
10993
- }
10994
- }
10995
- async function directoryExists(dirPath) {
10996
- return await fs2.stat(dirPath).then(() => true).catch(() => false);
10997
- }
10998
11011
  async function validateComponent(componentDir, fix = false) {
10999
11012
  const eslint = new ESLint({
11000
11013
  overrideConfigFile: true,
@@ -11012,7 +11025,7 @@ async function validateComponent(componentDir, fix = false) {
11012
11025
  (msg) => `Line ${msg.line}, Column ${msg.column}: ` + msg.message + (msg.ruleId ? ` (${msg.ruleId})` : "")
11013
11026
  );
11014
11027
  details.push({
11015
- heading: path11.relative(process.cwd(), result.filePath),
11028
+ heading: path3.relative(process.cwd(), result.filePath),
11016
11029
  content: messages.join("\n\n")
11017
11030
  });
11018
11031
  });
@@ -11024,8 +11037,8 @@ async function validateComponent(componentDir, fix = false) {
11024
11037
  }
11025
11038
 
11026
11039
  // src/utils/build.ts
11027
- async function buildComponent(componentDir) {
11028
- const componentName = path11.basename(componentDir);
11040
+ async function buildComponent(componentDir, useLocalGlobalCss = true) {
11041
+ const componentName = path3.basename(componentDir);
11029
11042
  const result = {
11030
11043
  itemName: componentName,
11031
11044
  success: true,
@@ -11037,7 +11050,7 @@ async function buildComponent(componentDir) {
11037
11050
  result.details = validationResult.details;
11038
11051
  return result;
11039
11052
  }
11040
- const distDir = path11.join(componentDir, "dist");
11053
+ const distDir = path3.join(componentDir, "dist");
11041
11054
  try {
11042
11055
  await promises.mkdir(distDir, { recursive: true });
11043
11056
  } catch (error) {
@@ -11050,11 +11063,11 @@ async function buildComponent(componentDir) {
11050
11063
  }
11051
11064
  try {
11052
11065
  const jsSource = await promises.readFile(
11053
- path11.join(componentDir, "index.jsx"),
11066
+ path3.join(componentDir, "index.jsx"),
11054
11067
  "utf-8"
11055
11068
  );
11056
11069
  const jsCompiled = compileJS(jsSource);
11057
- await promises.writeFile(path11.join(distDir, "index.js"), jsCompiled);
11070
+ await promises.writeFile(path3.join(distDir, "index.js"), jsCompiled);
11058
11071
  } catch (error) {
11059
11072
  result.success = false;
11060
11073
  result.details?.push({
@@ -11062,11 +11075,9 @@ async function buildComponent(componentDir) {
11062
11075
  content: String(error)
11063
11076
  });
11064
11077
  }
11065
- const apiService = await createApiService();
11066
- const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
11067
- const globalSourceCodeCss = globalAssetLibrary.css.original;
11078
+ const globalSourceCodeCss = await getGlobalCss(useLocalGlobalCss);
11068
11079
  try {
11069
- const cssPath = path11.join(componentDir, "index.css");
11080
+ const cssPath = path3.join(componentDir, "index.css");
11070
11081
  const cssFileExists = await fileExists(cssPath);
11071
11082
  if (cssFileExists) {
11072
11083
  const cssSource = await promises.readFile(cssPath, "utf-8");
@@ -11075,7 +11086,7 @@ async function buildComponent(componentDir) {
11075
11086
  globalSourceCodeCss
11076
11087
  );
11077
11088
  const cssTranspiled = await transformCss(cssCompiled);
11078
- await promises.writeFile(path11.join(distDir, "index.css"), cssTranspiled);
11089
+ await promises.writeFile(path3.join(distDir, "index.css"), cssTranspiled);
11079
11090
  }
11080
11091
  } catch (error) {
11081
11092
  result.success = false;
@@ -11086,57 +11097,28 @@ async function buildComponent(componentDir) {
11086
11097
  }
11087
11098
  return result;
11088
11099
  }
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
- })
11100
+
11101
+ // src/utils/command-helpers.ts
11102
+ var ALL_COMPONENTS_SELECTOR = "_allComponents";
11103
+ function validateComponentOptions(options) {
11104
+ if (options.components && options.all) {
11105
+ throw new Error(
11106
+ "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
11107
  );
11138
11108
  }
11139
11109
  }
11110
+ function updateConfigFromOptions(options) {
11111
+ if (options.clientId) setConfig({ clientId: options.clientId });
11112
+ if (options.clientSecret) setConfig({ clientSecret: options.clientSecret });
11113
+ if (options.siteUrl) setConfig({ siteUrl: options.siteUrl });
11114
+ if (options.dir) setConfig({ componentDir: options.dir });
11115
+ if (options.scope) setConfig({ scope: options.scope });
11116
+ if (options.all) setConfig({ all: options.all });
11117
+ if (options.verbose) setConfig({ verbose: true });
11118
+ }
11119
+ function pluralizeComponent(count) {
11120
+ return count === 1 ? "component" : "components";
11121
+ }
11140
11122
 
11141
11123
  // ../node_modules/@isaacs/balanced-match/dist/esm/index.js
11142
11124
  var balanced = (a, b, str) => {
@@ -17757,11 +17739,11 @@ async function findComponentDirectories(baseDir) {
17757
17739
  const namedYmls = await glob(`${baseDir}/**/*.component.yml`);
17758
17740
  const allComponentPaths = [...standardYmls, ...namedYmls];
17759
17741
  const uniqueDirs = new Set(
17760
- allComponentPaths.map((filePath) => path11.dirname(filePath))
17742
+ allComponentPaths.map((filePath) => path3.dirname(filePath))
17761
17743
  );
17762
17744
  let componentDirs = Array.from(uniqueDirs).sort();
17763
17745
  let sdcs = await glob(`${baseDir}/**/*.twig`);
17764
- sdcs = sdcs.map((sdc) => path11.dirname(sdc));
17746
+ sdcs = sdcs.map((sdc) => path3.dirname(sdc));
17765
17747
  componentDirs = componentDirs.filter(
17766
17748
  (componentDir) => !sdcs.includes(componentDir)
17767
17749
  );
@@ -17772,50 +17754,333 @@ async function findComponentDirectories(baseDir) {
17772
17754
  }
17773
17755
  }
17774
17756
 
17775
- // src/utils/select-local-components.ts
17776
- async function selectLocalComponents(allFlag, message = "Select components", baseDir) {
17757
+ // src/utils/component-selector.ts
17758
+ var GLOBAL_CSS_SELECTOR = "__GLOBAL_CSS__";
17759
+ function determineGlobalCssSelection(options) {
17760
+ if (options.cssOnly) {
17761
+ return true;
17762
+ }
17763
+ if (options.skipCss) {
17764
+ return false;
17765
+ }
17766
+ if (!options.includeGlobalCss) {
17767
+ return void 0;
17768
+ }
17769
+ return options.globalCssDefault !== false;
17770
+ }
17771
+ async function selectLocalComponents(options) {
17777
17772
  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;
17773
+ const componentDir = options.componentDir || config2.componentDir;
17774
+ const globalCssSelection = determineGlobalCssSelection(options);
17775
+ if (options.cssOnly) {
17776
+ return {
17777
+ directories: [],
17778
+ includeGlobalCss: true
17779
+ };
17783
17780
  }
17784
- if (allFlag) {
17785
- p9.log.info(`Selected all components`);
17786
- return componentDirs;
17781
+ const allLocalDirs = await findComponentDirectories(componentDir);
17782
+ if (allLocalDirs.length === 0) {
17783
+ throw new Error(`No local components were found in ${componentDir}`);
17787
17784
  }
17788
- const selectedDirs = await p9.multiselect({
17789
- message,
17790
- options: [
17791
- {
17792
- value: "_allComponents",
17793
- label: "All components"
17794
- },
17795
- ...componentDirs.map((dir) => ({
17796
- value: dir,
17797
- label: path11.basename(dir)
17798
- }))
17799
- ],
17785
+ if (options.components) {
17786
+ return selectSpecificLocalComponents(
17787
+ options.components,
17788
+ allLocalDirs,
17789
+ options,
17790
+ globalCssSelection
17791
+ );
17792
+ }
17793
+ if (options.all) {
17794
+ if (!options.skipConfirmation) {
17795
+ const confirmed = await confirmSelection(
17796
+ allLocalDirs.length,
17797
+ options.confirmMessage
17798
+ );
17799
+ if (!confirmed) {
17800
+ throw new Error("Operation cancelled by user");
17801
+ }
17802
+ }
17803
+ p.log.info(`Selected all components`);
17804
+ return {
17805
+ directories: allLocalDirs,
17806
+ includeGlobalCss: globalCssSelection
17807
+ };
17808
+ }
17809
+ return selectLocalComponentsInteractive(
17810
+ allLocalDirs,
17811
+ options,
17812
+ globalCssSelection
17813
+ );
17814
+ }
17815
+ async function selectSpecificLocalComponents(componentsInput, allLocalDirs, options, globalCssSelection) {
17816
+ const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17817
+ const notFound = [];
17818
+ const foundDirs = [];
17819
+ for (const requestedName of requestedNames) {
17820
+ const dir = allLocalDirs.find((d2) => path3.basename(d2) === requestedName);
17821
+ if (dir) {
17822
+ foundDirs.push(dir);
17823
+ } else {
17824
+ notFound.push(requestedName);
17825
+ }
17826
+ }
17827
+ if (notFound.length > 0) {
17828
+ const message = options.notFoundMessage || `The following component(s) were not found locally: ${notFound.join(", ")}`;
17829
+ throw new Error(message);
17830
+ }
17831
+ if (!options.skipConfirmation) {
17832
+ const confirmed = await confirmSelection(
17833
+ foundDirs.length,
17834
+ options.confirmMessage
17835
+ );
17836
+ if (!confirmed) {
17837
+ throw new Error("Operation cancelled by user");
17838
+ }
17839
+ }
17840
+ return {
17841
+ directories: foundDirs,
17842
+ includeGlobalCss: globalCssSelection
17843
+ };
17844
+ }
17845
+ async function selectLocalComponentsInteractive(allLocalDirs, options, globalCssSelection) {
17846
+ const multiSelectOptions = [
17847
+ {
17848
+ value: ALL_COMPONENTS_SELECTOR,
17849
+ label: "All components"
17850
+ }
17851
+ ];
17852
+ if (options.includeGlobalCss) {
17853
+ multiSelectOptions.push({
17854
+ value: GLOBAL_CSS_SELECTOR,
17855
+ label: "Global CSS"
17856
+ });
17857
+ }
17858
+ multiSelectOptions.push(
17859
+ ...allLocalDirs.map((dir) => ({
17860
+ value: dir,
17861
+ label: path3.basename(dir)
17862
+ }))
17863
+ );
17864
+ const selectedItems = await p.multiselect({
17865
+ message: options.selectMessage || "Select items",
17866
+ options: multiSelectOptions,
17867
+ initialValues: options.includeGlobalCss && options.globalCssDefault !== false ? [GLOBAL_CSS_SELECTOR] : [],
17800
17868
  required: true
17801
17869
  });
17802
- if (p9.isCancel(selectedDirs)) {
17803
- p9.cancel("Operation cancelled");
17804
- return null;
17870
+ if (p.isCancel(selectedItems)) {
17871
+ throw new Error("Operation cancelled by user");
17872
+ }
17873
+ const includesAllComponents = selectedItems.includes(
17874
+ ALL_COMPONENTS_SELECTOR
17875
+ );
17876
+ const includesGlobalCss = selectedItems.includes(
17877
+ GLOBAL_CSS_SELECTOR
17878
+ );
17879
+ const finalDirs = includesAllComponents ? allLocalDirs : selectedItems.filter(
17880
+ (item) => item !== ALL_COMPONENTS_SELECTOR && item !== GLOBAL_CSS_SELECTOR
17881
+ );
17882
+ if (!options.skipConfirmation) {
17883
+ const confirmed = await confirmSelection(
17884
+ finalDirs.length,
17885
+ options.confirmMessage
17886
+ );
17887
+ if (!confirmed) {
17888
+ throw new Error("Operation cancelled by user");
17889
+ }
17890
+ }
17891
+ const finalGlobalCss = options.includeGlobalCss ? includesGlobalCss : globalCssSelection;
17892
+ return {
17893
+ directories: finalDirs,
17894
+ includeGlobalCss: finalGlobalCss
17895
+ };
17896
+ }
17897
+ async function selectRemoteComponents(allComponents, options) {
17898
+ const componentCount = Object.keys(allComponents).length;
17899
+ const globalCssSelection = determineGlobalCssSelection(options);
17900
+ if (options.cssOnly) {
17901
+ return {
17902
+ components: {},
17903
+ includeGlobalCss: true
17904
+ };
17905
+ }
17906
+ if (componentCount === 0) {
17907
+ throw new Error("No components found");
17908
+ }
17909
+ if (options.all) {
17910
+ if (!options.skipConfirmation) {
17911
+ const confirmed = await confirmSelection(
17912
+ componentCount,
17913
+ options.confirmMessage
17914
+ );
17915
+ if (!confirmed) {
17916
+ throw new Error("Operation cancelled by user");
17917
+ }
17918
+ }
17919
+ return {
17920
+ components: allComponents,
17921
+ includeGlobalCss: globalCssSelection
17922
+ };
17923
+ }
17924
+ if (options.components) {
17925
+ return selectSpecificRemoteComponents(
17926
+ options.components,
17927
+ allComponents,
17928
+ options,
17929
+ globalCssSelection
17930
+ );
17931
+ }
17932
+ return selectRemoteComponentsInteractive(
17933
+ allComponents,
17934
+ options,
17935
+ globalCssSelection
17936
+ );
17937
+ }
17938
+ async function selectSpecificRemoteComponents(componentsInput, allComponents, options, globalCssSelection) {
17939
+ const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17940
+ const notFound = [];
17941
+ const selected = {};
17942
+ for (const requestedName of requestedNames) {
17943
+ const component = allComponents[requestedName];
17944
+ if (component) {
17945
+ selected[requestedName] = component;
17946
+ } else {
17947
+ notFound.push(requestedName);
17948
+ }
17949
+ }
17950
+ if (notFound.length > 0) {
17951
+ const message = options.notFoundMessage || `The following component(s) were not found: ${notFound.join(", ")}`;
17952
+ throw new Error(message);
17953
+ }
17954
+ if (!options.skipConfirmation) {
17955
+ const confirmed = await confirmSelection(
17956
+ Object.keys(selected).length,
17957
+ options.confirmMessage
17958
+ );
17959
+ if (!confirmed) {
17960
+ throw new Error("Operation cancelled by user");
17961
+ }
17962
+ }
17963
+ return {
17964
+ components: selected,
17965
+ includeGlobalCss: globalCssSelection
17966
+ };
17967
+ }
17968
+ async function selectRemoteComponentsInteractive(allComponents, options, globalCssSelection) {
17969
+ const multiSelectOptions = [
17970
+ {
17971
+ value: ALL_COMPONENTS_SELECTOR,
17972
+ label: "All components"
17973
+ }
17974
+ ];
17975
+ if (options.includeGlobalCss) {
17976
+ multiSelectOptions.push({
17977
+ value: GLOBAL_CSS_SELECTOR,
17978
+ label: "Global CSS"
17979
+ });
17805
17980
  }
17806
- const count = selectedDirs.includes("_allComponents") ? componentDirs.length : selectedDirs.length;
17807
- const confirmBuild = await p9.confirm({
17808
- message: `Process ${count} components`,
17981
+ multiSelectOptions.push(
17982
+ ...Object.keys(allComponents).map((key) => ({
17983
+ value: allComponents[key].machineName,
17984
+ label: `${allComponents[key].name} (${allComponents[key].machineName})`
17985
+ }))
17986
+ );
17987
+ const selectedItems = await p.multiselect({
17988
+ message: options.selectMessage || "Select items to download",
17989
+ options: multiSelectOptions,
17990
+ initialValues: options.includeGlobalCss && options.globalCssDefault !== false ? [GLOBAL_CSS_SELECTOR] : [],
17991
+ required: true
17992
+ });
17993
+ if (p.isCancel(selectedItems)) {
17994
+ throw new Error("Operation cancelled by user");
17995
+ }
17996
+ const includesAllComponents = selectedItems.includes(
17997
+ ALL_COMPONENTS_SELECTOR
17998
+ );
17999
+ const includesGlobalCss = selectedItems.includes(
18000
+ GLOBAL_CSS_SELECTOR
18001
+ );
18002
+ const selected = includesAllComponents ? allComponents : Object.fromEntries(
18003
+ Object.entries(allComponents).filter(
18004
+ ([, component]) => selectedItems.includes(component.machineName)
18005
+ )
18006
+ );
18007
+ if (!options.skipConfirmation) {
18008
+ const confirmed = await confirmSelection(
18009
+ Object.keys(selected).length,
18010
+ options.confirmMessage
18011
+ );
18012
+ if (!confirmed) {
18013
+ throw new Error("Operation cancelled by user");
18014
+ }
18015
+ }
18016
+ const finalGlobalCss = options.includeGlobalCss ? includesGlobalCss : globalCssSelection;
18017
+ return {
18018
+ components: selected,
18019
+ includeGlobalCss: finalGlobalCss
18020
+ };
18021
+ }
18022
+ async function confirmSelection(count, customMessage) {
18023
+ const componentLabel = count === 1 ? "component" : "components";
18024
+ const message = customMessage || `Process ${count} ${componentLabel}?`;
18025
+ const confirmed = await p.confirm({
18026
+ message,
17809
18027
  initialValue: true
17810
18028
  });
17811
- if (p9.isCancel(confirmBuild) || !confirmBuild) {
17812
- p9.cancel("Operation cancelled");
17813
- return null;
18029
+ if (p.isCancel(confirmed) || !confirmed) {
18030
+ return false;
17814
18031
  }
17815
- if (selectedDirs.includes("_allComponents")) {
17816
- return componentDirs;
18032
+ return true;
18033
+ }
18034
+ function reportResults(results, title, itemLabel = "Component") {
18035
+ results.sort((a, b) => a.itemName.localeCompare(b.itemName));
18036
+ const successful = results.filter((r) => r.success).length;
18037
+ const failed = results.filter((r) => !r.success).length;
18038
+ const hasDetails = results.some((r) => (r.details?.length ?? 0) > 0);
18039
+ const succeededText = failed === 0 ? chalk2.green(`${successful} succeeded`) : `${successful} succeeded`;
18040
+ const failedText = failed > 0 ? chalk2.red(`${failed} failed`) : chalk2.dim(`${failed} failed`);
18041
+ const summary = `${succeededText}, ${failedText}`;
18042
+ if (results.length > 0) {
18043
+ const tableData = [
18044
+ hasDetails ? [chalk2.bold(title), "", ""] : [chalk2.bold(title), ""],
18045
+ hasDetails ? [itemLabel, "Status", "Details"] : [itemLabel, "Status"],
18046
+ ...results.map(
18047
+ (r) => hasDetails ? [
18048
+ r.itemName,
18049
+ r.success ? chalk2.green("Success") : chalk2.red("Failed"),
18050
+ r.details?.map(
18051
+ (d2) => d2.heading ? `${chalk2.underline(d2.heading)}:
18052
+ ${d2.content}` : d2.content
18053
+ ).join("\n\n") ?? ""
18054
+ ] : [
18055
+ r.itemName,
18056
+ r.success ? chalk2.green("Success") : chalk2.red("Failed")
18057
+ ]
18058
+ ),
18059
+ hasDetails ? ["SUMMARY", "", summary] : ["SUMMARY", summary]
18060
+ ];
18061
+ p.log.info(
18062
+ table(tableData, {
18063
+ spanningCells: [
18064
+ {
18065
+ row: 0,
18066
+ col: 0,
18067
+ colSpan: hasDetails ? 3 : 2,
18068
+ alignment: "center"
18069
+ },
18070
+ {
18071
+ row: results.length + 2,
18072
+ col: 0,
18073
+ colSpan: hasDetails ? 2 : 1,
18074
+ alignment: hasDetails ? "right" : "left"
18075
+ }
18076
+ ],
18077
+ columns: {
18078
+ // Limit the width of the details column for improved readability of long details.
18079
+ 2: { width: 100, wrapWord: true }
18080
+ }
18081
+ })
18082
+ );
17817
18083
  }
17818
- return selectedDirs;
17819
18084
  }
17820
18085
 
17821
18086
  // src/commands/build.ts
@@ -17823,75 +18088,89 @@ function buildCommand(program2) {
17823
18088
  program2.command("build").description("build local components and Tailwind CSS assets").option(
17824
18089
  "-d, --dir <directory>",
17825
18090
  "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
18091
+ ).option("--all", "Build all components").option(
18092
+ "-c, --components <names>",
18093
+ "Specific component(s) to build (comma-separated)"
18094
+ ).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) => {
18095
+ try {
18096
+ p.intro(chalk2.bold("Drupal Canvas CLI: build"));
18097
+ validateComponentOptions(options);
18098
+ const allFlag = options.all || options.yes && !options.components || false;
18099
+ const skipTailwind = !options.tailwind;
18100
+ updateConfigFromOptions(options);
18101
+ if (!skipTailwind) {
18102
+ await ensureConfig(["siteUrl", "clientId", "clientSecret"]);
18103
+ }
18104
+ const { directories: componentsToBuild } = await selectLocalComponents({
18105
+ all: allFlag,
18106
+ components: options.components,
18107
+ skipConfirmation: options.yes,
18108
+ selectMessage: "Select components to build"
18109
+ });
18110
+ const componentLabelPluralized = pluralizeComponent(
18111
+ componentsToBuild.length
17869
18112
  );
17870
- s2.stop(
18113
+ const s1 = p.spinner();
18114
+ s1.start(`Building ${componentLabelPluralized}`);
18115
+ const results = [];
18116
+ for (const componentDir of componentsToBuild) {
18117
+ results.push(await buildComponent(componentDir));
18118
+ }
18119
+ s1.stop(
17871
18120
  chalk2.green(
17872
- `Processed Tailwind CSS classes from ${selectedComponents.length} selected local ${componentLabelPluralized} and all online components`
18121
+ `Processed ${componentsToBuild.length} ${componentLabelPluralized}`
17873
18122
  )
17874
18123
  );
17875
- reportResults([tailwindResult], "Built assets", "Asset");
17876
- if (!tailwindResult.success) {
17877
- return process.exit(1);
18124
+ reportResults(results, "Built components", "Component");
18125
+ if (results.map((result) => result.success).includes(false)) {
18126
+ process.exit(1);
18127
+ }
18128
+ if (skipTailwind) {
18129
+ p.log.info("Skipping Tailwind CSS build");
18130
+ } else {
18131
+ const s2 = p.spinner();
18132
+ s2.start("Building Tailwind CSS");
18133
+ const tailwindResult = await buildTailwindForComponents(
18134
+ componentsToBuild
18135
+ );
18136
+ s2.stop(
18137
+ chalk2.green(
18138
+ `Processed Tailwind CSS classes from ${componentsToBuild.length} selected local ${componentLabelPluralized} and all online components`
18139
+ )
18140
+ );
18141
+ reportResults([tailwindResult], "Built assets", "Asset");
18142
+ if (!tailwindResult.success) {
18143
+ return process.exit(1);
18144
+ }
18145
+ }
18146
+ p.outro(`\u{1F4E6} Build completed`);
18147
+ } catch (error) {
18148
+ if (error instanceof Error) {
18149
+ p.note(chalk2.red(`Error: ${error.message}`));
18150
+ } else {
18151
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
17878
18152
  }
18153
+ process.exit(1);
17879
18154
  }
17880
- p9.outro(`\u{1F4E6} Build completed`);
17881
18155
  });
17882
18156
  }
17883
18157
  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"));
18158
+ 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(
18159
+ "-c, --components <names>",
18160
+ "Specific component(s) to download (comma-separated)"
18161
+ ).option("--all", "Download all components").option("-y, --yes", "Skip all confirmation prompts").option(
18162
+ "--skip-overwrite",
18163
+ "Skip downloading components that already exist locally"
18164
+ ).option("--skip-css", "Skip downloading global CSS").option("--css-only", "Download only global CSS (skip components)").option("--verbose", "Enable verbose output").action(async (options) => {
18165
+ p.intro(chalk2.bold("Drupal Canvas CLI: download"));
17886
18166
  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 });
18167
+ validateComponentOptions(options);
18168
+ if (options.skipCss && options.cssOnly) {
18169
+ throw new Error(
18170
+ "Cannot use both --skip-css and --css-only flags together"
18171
+ );
18172
+ }
18173
+ updateConfigFromOptions(options);
17895
18174
  await ensureConfig([
17896
18175
  "siteUrl",
17897
18176
  "clientId",
@@ -17901,77 +18180,52 @@ function downloadCommand(program2) {
17901
18180
  ]);
17902
18181
  const config2 = getConfig();
17903
18182
  const apiService = await createApiService();
17904
- const s = p9.spinner();
17905
- s.start("Fetching components");
17906
- const components = await apiService.listComponents();
17907
- const {
17908
- css: { original: globalCss }
17909
- } = await apiService.getGlobalAssetLibrary();
17910
- if (Object.keys(components).length === 0) {
17911
- s.stop("No components found");
17912
- p9.outro("Download cancelled - no components were found");
17913
- return;
17914
- }
17915
- 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 };
18183
+ let components = {};
18184
+ let globalCss;
18185
+ const s = p.spinner();
18186
+ if (options.cssOnly) {
18187
+ s.start("Fetching global CSS");
18188
+ const {
18189
+ css: { original }
18190
+ } = await apiService.getGlobalAssetLibrary();
18191
+ globalCss = original;
18192
+ s.stop("Global CSS fetched");
17929
18193
  } 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");
18194
+ s.start("Fetching components and global CSS");
18195
+ const [fetchedComponents, globalAssetLibrary] = await Promise.all([
18196
+ apiService.listComponents(),
18197
+ apiService.getGlobalAssetLibrary()
18198
+ ]);
18199
+ components = fetchedComponents;
18200
+ globalCss = globalAssetLibrary.css.original;
18201
+ if (Object.keys(components).length === 0) {
18202
+ s.stop("No components found");
18203
+ p.outro("Download cancelled - no components were found");
17946
18204
  return;
17947
18205
  }
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
18206
+ s.stop(`Found ${Object.keys(components).length} components`);
18207
+ }
18208
+ const allFlag = options.all || options.yes && !options.components || false;
18209
+ const { components: componentsToDownload, includeGlobalCss } = await selectRemoteComponents(components, {
18210
+ all: allFlag,
18211
+ components: options.components,
18212
+ skipConfirmation: options.yes,
18213
+ skipCss: options.skipCss,
18214
+ cssOnly: options.cssOnly,
18215
+ includeGlobalCss: !options.skipCss,
18216
+ globalCssDefault: true,
18217
+ selectMessage: "Select items to download",
18218
+ confirmMessage: `Download to ${config2.componentDir}?`
17964
18219
  });
17965
- if (p9.isCancel(confirmDownload) || !confirmDownload) {
17966
- p9.cancel("Operation cancelled");
17967
- return;
17968
- }
18220
+ const componentCount = Object.keys(componentsToDownload).length;
18221
+ const componentPluralized = pluralizeComponent(componentCount);
17969
18222
  const results = [];
17970
- s.start(`Downloading ${componentPluralized}`);
18223
+ const downloadMessage = options.cssOnly ? "Downloading global CSS" : componentCount > 0 ? `Downloading ${componentPluralized}` : "Processing request";
18224
+ s.start(downloadMessage);
17971
18225
  for (const key in componentsToDownload) {
17972
18226
  const component = componentsToDownload[key];
17973
18227
  try {
17974
- const componentDir = path11.join(
18228
+ const componentDir = path3.join(
17975
18229
  config2.componentDir,
17976
18230
  component.machineName
17977
18231
  );
@@ -17979,13 +18233,27 @@ function downloadCommand(program2) {
17979
18233
  if (dirExists) {
17980
18234
  const files = await fs2.readdir(componentDir);
17981
18235
  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);
18236
+ if (options.skipOverwrite) {
18237
+ results.push({
18238
+ itemName: component.machineName,
18239
+ success: true,
18240
+ details: [
18241
+ {
18242
+ content: "Skipped (already exists)"
18243
+ }
18244
+ ]
18245
+ });
18246
+ continue;
18247
+ }
18248
+ if (!options.yes) {
18249
+ const confirmDelete = await p.confirm({
18250
+ message: `The "${componentDir}" directory is not empty. Are you sure you want to delete and overwrite this directory?`,
18251
+ initialValue: true
18252
+ });
18253
+ if (p.isCancel(confirmDelete) || !confirmDelete) {
18254
+ p.cancel("Operation cancelled");
18255
+ process.exit(0);
18256
+ }
17989
18257
  }
17990
18258
  }
17991
18259
  }
@@ -18002,20 +18270,20 @@ function downloadCommand(program2) {
18002
18270
  slots: component.slots || {}
18003
18271
  };
18004
18272
  await fs2.writeFile(
18005
- path11.join(componentDir, `component.yml`),
18273
+ path3.join(componentDir, `component.yml`),
18006
18274
  yaml__default.dump(metadata),
18007
18275
  "utf-8"
18008
18276
  );
18009
18277
  if (component.sourceCodeJs) {
18010
18278
  await fs2.writeFile(
18011
- path11.join(componentDir, `index.jsx`),
18279
+ path3.join(componentDir, `index.jsx`),
18012
18280
  component.sourceCodeJs,
18013
18281
  "utf-8"
18014
18282
  );
18015
18283
  }
18016
18284
  if (component.sourceCodeCss) {
18017
18285
  await fs2.writeFile(
18018
- path11.join(componentDir, `index.css`),
18286
+ path3.join(componentDir, `index.css`),
18019
18287
  component.sourceCodeCss,
18020
18288
  "utf-8"
18021
18289
  );
@@ -18036,16 +18304,15 @@ function downloadCommand(program2) {
18036
18304
  });
18037
18305
  }
18038
18306
  }
18039
- s.stop(
18040
- chalk2.green(
18041
- `Processed ${Object.keys(componentsToDownload).length} ${componentPluralized}`
18042
- )
18043
- );
18044
- reportResults(results, "Downloaded components", "Component");
18045
- if (globalCss) {
18307
+ const successMessage = options.cssOnly && componentCount === 0 ? "Global CSS download completed" : `Processed ${componentCount} ${componentPluralized}`;
18308
+ s.stop(chalk2.green(successMessage));
18309
+ if (componentCount > 0) {
18310
+ reportResults(results, "Downloaded components", "Component");
18311
+ }
18312
+ if (includeGlobalCss && typeof globalCss === "string") {
18046
18313
  let globalCssResult;
18047
18314
  try {
18048
- const globalCssPath = path11.join(config2.componentDir, "global.css");
18315
+ const globalCssPath = path3.join(config2.componentDir, "global.css");
18049
18316
  await fs2.writeFile(globalCssPath, globalCss, "utf-8");
18050
18317
  globalCssResult = {
18051
18318
  itemName: "global.css",
@@ -18065,12 +18332,13 @@ function downloadCommand(program2) {
18065
18332
  }
18066
18333
  reportResults([globalCssResult], "Downloaded assets", "Asset");
18067
18334
  }
18068
- p9.outro(`\u2B07\uFE0F Download command completed`);
18335
+ const outroMessage = options.cssOnly && componentCount === 0 ? "\u2B07\uFE0F Global CSS downloaded successfully" : includeGlobalCss && componentCount > 0 ? "\u2B07\uFE0F Components and global CSS downloaded successfully" : componentCount > 0 ? "\u2B07\uFE0F Components downloaded successfully" : "\u2B07\uFE0F Download command completed";
18336
+ p.outro(outroMessage);
18069
18337
  } catch (error) {
18070
18338
  if (error instanceof Error) {
18071
- p9.note(chalk2.red(`Error: ${error.message}`));
18339
+ p.note(chalk2.red(`Error: ${error.message}`));
18072
18340
  } else {
18073
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18341
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18074
18342
  }
18075
18343
  process.exit(1);
18076
18344
  }
@@ -18084,7 +18352,7 @@ function scaffoldCommand(program2) {
18084
18352
  "-d, --dir <directory>",
18085
18353
  "Component directory to create component in"
18086
18354
  ).option("--verbose", "Enable verbose output").action(async (options) => {
18087
- p9.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18355
+ p.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18088
18356
  try {
18089
18357
  if (options.dir) setConfig({ componentDir: options.dir });
18090
18358
  if (options.verbose) setConfig({ verbose: options.verbose });
@@ -18092,7 +18360,7 @@ function scaffoldCommand(program2) {
18092
18360
  const baseDir = config2.componentDir;
18093
18361
  let componentName = options.name;
18094
18362
  if (!componentName) {
18095
- const name = await p9.text({
18363
+ const name = await p.text({
18096
18364
  message: "Enter the component name",
18097
18365
  placeholder: "my-component",
18098
18366
  validate: (value) => {
@@ -18102,57 +18370,57 @@ function scaffoldCommand(program2) {
18102
18370
  return;
18103
18371
  }
18104
18372
  });
18105
- if (p9.isCancel(name)) {
18106
- p9.cancel("Operation cancelled");
18373
+ if (p.isCancel(name)) {
18374
+ p.cancel("Operation cancelled");
18107
18375
  return;
18108
18376
  }
18109
18377
  componentName = name;
18110
18378
  }
18111
- const componentDir = path11.join(baseDir, componentName);
18112
- const s = p9.spinner();
18379
+ const componentDir = path3.join(baseDir, componentName);
18380
+ const s = p.spinner();
18113
18381
  s.start(`Creating component "${componentName}"`);
18114
18382
  try {
18115
18383
  await fs2.mkdir(componentDir, { recursive: true });
18116
- const templateDir = path11.join(
18117
- path11.dirname(new URL(import.meta.url).pathname),
18384
+ const templateDir = path3.join(
18385
+ path3.dirname(new URL(import.meta.url).pathname),
18118
18386
  "templates/hello-world"
18119
18387
  );
18120
18388
  const files = await fs2.readdir(templateDir);
18121
18389
  const newDirFiles = await fs2.readdir(componentDir);
18122
18390
  if (newDirFiles.length > 0) {
18123
- const confirmDelete = await p9.confirm({
18391
+ const confirmDelete = await p.confirm({
18124
18392
  message: `The "${componentDir}" directory is not empty. Do you want to proceed and potentially overwrite existing files?`,
18125
18393
  initialValue: true
18126
18394
  });
18127
- if (p9.isCancel(confirmDelete) || !confirmDelete) {
18128
- p9.cancel("Operation cancelled");
18395
+ if (p.isCancel(confirmDelete) || !confirmDelete) {
18396
+ p.cancel("Operation cancelled");
18129
18397
  process.exit(0);
18130
18398
  }
18131
18399
  }
18132
18400
  for (const file of files) {
18133
- const srcPath = path11.join(templateDir, file);
18134
- const destPath = path11.join(componentDir, file);
18401
+ const srcPath = path3.join(templateDir, file);
18402
+ const destPath = path3.join(componentDir, file);
18135
18403
  let content = await fs2.readFile(srcPath, "utf-8");
18136
18404
  const { pascalCaseName, className, displayName, machineName } = generateComponentNameFormats(componentName);
18137
18405
  content = content.replace(/HelloWorld/g, pascalCaseName).replace(/hello-world-component/g, className).replace(/Hello World/g, displayName).replace(/hello_world/g, machineName);
18138
18406
  await fs2.writeFile(destPath, content, "utf-8");
18139
18407
  }
18140
18408
  s.stop(chalk2.green(`Created component "${componentName}"`));
18141
- p9.note(`Component "${componentName}" has been created:
18409
+ p.note(`Component "${componentName}" has been created:
18142
18410
  - Directory: ${componentDir}
18143
- - Component metadata: ${path11.join(componentDir, `component.yml`)}
18144
- - Source file: ${path11.join(componentDir, `index.jsx`)}
18145
- - CSS file: ${path11.join(componentDir, `index.css`)}`);
18146
- p9.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18411
+ - Component metadata: ${path3.join(componentDir, `component.yml`)}
18412
+ - Source file: ${path3.join(componentDir, `index.jsx`)}
18413
+ - CSS file: ${path3.join(componentDir, `index.css`)}`);
18414
+ p.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18147
18415
  } catch (error) {
18148
18416
  s.stop(chalk2.red(`Failed to create component "${componentName}"`));
18149
18417
  throw error;
18150
18418
  }
18151
18419
  } catch (error) {
18152
18420
  if (error instanceof Error) {
18153
- p9.note(chalk2.red(`Error: ${error.message}`));
18421
+ p.note(chalk2.red(`Error: ${error.message}`));
18154
18422
  } else {
18155
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18423
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18156
18424
  }
18157
18425
  process.exit(1);
18158
18426
  }
@@ -18220,20 +18488,20 @@ var getDataDependenciesFromAst = (ast) => ast.program.body.filter((d2) => d2.typ
18220
18488
  async function processComponentFiles(componentDir) {
18221
18489
  const metadataPath = await findMetadataPath(componentDir);
18222
18490
  const metadata = await readComponentMetadata(metadataPath);
18223
- const distDir = path11.join(componentDir, "dist");
18491
+ const distDir = path3.join(componentDir, "dist");
18224
18492
  const sourceCodeJs = await promises.readFile(
18225
- path11.join(componentDir, "index.jsx"),
18493
+ path3.join(componentDir, "index.jsx"),
18226
18494
  "utf-8"
18227
18495
  );
18228
- const compiledJs = await promises.readFile(path11.join(distDir, "index.js"), "utf-8");
18496
+ const compiledJs = await promises.readFile(path3.join(distDir, "index.js"), "utf-8");
18229
18497
  let sourceCodeCss = "";
18230
18498
  let compiledCss = "";
18231
18499
  try {
18232
18500
  sourceCodeCss = await promises.readFile(
18233
- path11.join(componentDir, "index.css"),
18501
+ path3.join(componentDir, "index.css"),
18234
18502
  "utf-8"
18235
18503
  );
18236
- compiledCss = await promises.readFile(path11.join(distDir, "index.css"), "utf-8");
18504
+ compiledCss = await promises.readFile(path3.join(distDir, "index.css"), "utf-8");
18237
18505
  } catch {
18238
18506
  }
18239
18507
  return {
@@ -18245,7 +18513,7 @@ async function processComponentFiles(componentDir) {
18245
18513
  };
18246
18514
  }
18247
18515
  async function findMetadataPath(componentDir) {
18248
- const metadataPath = path11.join(componentDir, "component.yml");
18516
+ const metadataPath = path3.join(componentDir, "component.yml");
18249
18517
  try {
18250
18518
  await promises.access(metadataPath);
18251
18519
  return metadataPath;
@@ -18266,10 +18534,10 @@ async function readComponentMetadata(filePath) {
18266
18534
  }
18267
18535
  const metadata = rawMetadata;
18268
18536
  if (!metadata.name) {
18269
- metadata.name = path11.basename(path11.dirname(filePath));
18537
+ metadata.name = path3.basename(path3.dirname(filePath));
18270
18538
  }
18271
18539
  if (!metadata.machineName) {
18272
- metadata.machineName = path11.basename(path11.dirname(filePath));
18540
+ metadata.machineName = path3.basename(path3.dirname(filePath));
18273
18541
  }
18274
18542
  if (!metadata.slots || typeof metadata.slots !== "object") {
18275
18543
  metadata.slots = {};
@@ -18347,12 +18615,12 @@ async function processInPool(items, processor, concurrency = 10) {
18347
18615
  }
18348
18616
  return results.sort((a, b) => a.index - b.index);
18349
18617
  }
18350
- function createProgressCallback(spinner5, operation, total) {
18618
+ function createProgressCallback(spinner6, operation, total) {
18351
18619
  let completed = 0;
18352
18620
  return () => {
18353
18621
  completed++;
18354
18622
  const percentage = Math.round(completed / total * 100);
18355
- spinner5.message(`${operation} (${completed}/${total} - ${percentage}%)`);
18623
+ spinner6.message(`${operation} (${completed}/${total} - ${percentage}%)`);
18356
18624
  };
18357
18625
  }
18358
18626
 
@@ -18436,19 +18704,21 @@ async function uploadComponents(uploadTasks, apiService, onProgress) {
18436
18704
  });
18437
18705
  }
18438
18706
  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;
18707
+ 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(
18708
+ "-c, --components <names>",
18709
+ "Specific component(s) to upload (comma-separated)"
18710
+ ).option("--all", "Upload all components").option("-y, --yes", "Skip confirmation prompts").option("--verbose", "Verbose output").option("--no-tailwind", "Skip Tailwind CSS building").option("--skip-css", "Skip global CSS upload").option("--css-only", "Upload only global CSS (skip components)").action(async (options) => {
18711
+ const allFlag = options.all || options.yes && !options.components || false;
18441
18712
  const skipTailwind = !options.tailwind;
18442
18713
  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 });
18714
+ p.intro(chalk2.bold("Drupal Canvas CLI: upload"));
18715
+ validateComponentOptions(options);
18716
+ if (options.skipCss && options.cssOnly) {
18717
+ throw new Error(
18718
+ "Cannot use both --skip-css and --css-only flags together"
18719
+ );
18720
+ }
18721
+ updateConfigFromOptions(options);
18452
18722
  await ensureConfig([
18453
18723
  "siteUrl",
18454
18724
  "clientId",
@@ -18457,28 +18727,42 @@ function uploadCommand(program2) {
18457
18727
  "componentDir"
18458
18728
  ]);
18459
18729
  const config2 = getConfig();
18460
- const componentsToUpload = await selectLocalComponents(
18461
- allFlag,
18462
- "Select components to upload"
18463
- );
18464
- if (!componentsToUpload || componentsToUpload.length === 0) {
18465
- return;
18466
- }
18730
+ const { directories: componentsToUpload, includeGlobalCss } = await selectLocalComponents({
18731
+ all: allFlag,
18732
+ components: options.components,
18733
+ skipConfirmation: options.yes,
18734
+ skipCss: options.skipCss,
18735
+ cssOnly: options.cssOnly,
18736
+ includeGlobalCss: !options.skipCss,
18737
+ globalCssDefault: true,
18738
+ selectMessage: "Select items to upload"
18739
+ });
18467
18740
  const apiService = await createApiService();
18468
- const componentResults = await getBuildAndUploadResults(
18469
- componentsToUpload,
18470
- apiService
18471
- );
18472
- reportResults(componentResults, "Uploaded components", "Component");
18741
+ let componentResults = [];
18742
+ if (!options.cssOnly && componentsToUpload.length > 0) {
18743
+ componentResults = await getBuildAndUploadResults(
18744
+ componentsToUpload,
18745
+ apiService,
18746
+ includeGlobalCss ?? false
18747
+ );
18748
+ reportResults(componentResults, "Uploaded components", "Component");
18749
+ if (componentResults.some((result) => !result.success)) {
18750
+ process.exit(1);
18751
+ }
18752
+ }
18473
18753
  if (skipTailwind) {
18474
- p9.log.info("Skipping Tailwind CSS build");
18754
+ p.log.info("Skipping Tailwind CSS build");
18475
18755
  } else {
18476
- const s2 = p9.spinner();
18756
+ const s2 = p.spinner();
18477
18757
  s2.start("Building Tailwind CSS");
18478
18758
  const tailwindResult = await buildTailwindForComponents(
18479
- componentsToUpload
18759
+ componentsToUpload,
18760
+ includeGlobalCss
18761
+ // Use local CSS if includeGlobalCss is true
18762
+ );
18763
+ const componentLabelPluralized = pluralizeComponent(
18764
+ componentsToUpload.length
18480
18765
  );
18481
- const componentLabelPluralized = componentsToUpload.length === 1 ? "component" : "components";
18482
18766
  s2.stop(
18483
18767
  chalk2.green(
18484
18768
  `Processed Tailwind CSS classes from ${componentsToUpload.length} selected local ${componentLabelPluralized} and all online components`
@@ -18486,23 +18770,29 @@ function uploadCommand(program2) {
18486
18770
  );
18487
18771
  if (!tailwindResult.success && tailwindResult.details) {
18488
18772
  reportResults([tailwindResult], "Built assets", "Asset");
18489
- p9.note(
18773
+ p.note(
18490
18774
  chalk2.red(`Tailwind build failed, global assets upload aborted.`)
18491
18775
  );
18492
18776
  } else {
18493
- const globalCssResult = await uploadGlobalAssetLibrary(
18494
- apiService,
18495
- config2.componentDir
18496
- );
18497
- reportResults([globalCssResult], "Uploaded assets", "Asset");
18777
+ if (includeGlobalCss) {
18778
+ const globalCssResult = await uploadGlobalAssetLibrary(
18779
+ apiService,
18780
+ config2.componentDir
18781
+ );
18782
+ reportResults([globalCssResult], "Uploaded assets", "Asset");
18783
+ } else {
18784
+ p.log.info("Skipping global CSS upload");
18785
+ }
18498
18786
  }
18499
18787
  }
18500
- p9.outro("\u2B06\uFE0F Upload command completed");
18788
+ const componentCount = componentsToUpload.length;
18789
+ const outroMessage = options.cssOnly && componentCount === 0 ? "\u2B06\uFE0F Global CSS uploaded successfully" : includeGlobalCss && componentCount > 0 ? "\u2B06\uFE0F Components and global CSS uploaded successfully" : componentCount > 0 ? "\u2B06\uFE0F Components uploaded successfully" : "\u2B06\uFE0F Upload command completed";
18790
+ p.outro(outroMessage);
18501
18791
  } catch (error) {
18502
18792
  if (error instanceof Error) {
18503
- p9.note(chalk2.red(`Error: ${error.message}`));
18793
+ p.note(chalk2.red(`Error: ${error.message}`));
18504
18794
  } else {
18505
- p9.note(chalk2.red(`Unknown error: ${String(error)}`));
18795
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
18506
18796
  }
18507
18797
  process.exit(1);
18508
18798
  }
@@ -18513,11 +18803,11 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18513
18803
  const failed = [];
18514
18804
  for (const buildResult of successfulBuilds) {
18515
18805
  const dir = buildResult.itemName ? componentsToUpload.find(
18516
- (d2) => path11.basename(d2) === buildResult.itemName
18806
+ (d2) => path3.basename(d2) === buildResult.itemName
18517
18807
  ) : void 0;
18518
18808
  if (!dir) continue;
18519
18809
  try {
18520
- const componentName = path11.basename(dir);
18810
+ const componentName = path3.basename(dir);
18521
18811
  const { sourceCodeJs, compiledJs, sourceCodeCss, compiledCss, metadata } = await processComponentFiles(dir);
18522
18812
  if (!metadata) {
18523
18813
  throw new Error("Invalid metadata file");
@@ -18534,7 +18824,7 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18534
18824
  importedJsComponents = getImportsFromAst(ast, scope);
18535
18825
  dataDependencies = getDataDependenciesFromAst(ast);
18536
18826
  } catch (error) {
18537
- p9.note(chalk2.red(`Error: ${error}`));
18827
+ p.note(chalk2.red(`Error: ${error}`));
18538
18828
  }
18539
18829
  const componentPayloadArg = {
18540
18830
  metadata,
@@ -18570,32 +18860,35 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18570
18860
  }
18571
18861
  return { prepared, failed };
18572
18862
  }
18573
- async function getBuildAndUploadResults(componentsToUpload, apiService) {
18863
+ async function getBuildAndUploadResults(componentsToUpload, apiService, includeGlobalCss) {
18574
18864
  const results = [];
18575
- const spinner5 = p9.spinner();
18576
- spinner5.start("Building components");
18577
- const buildResults = await buildSelectedComponents(componentsToUpload);
18865
+ const spinner6 = p.spinner();
18866
+ spinner6.start("Building components");
18867
+ const buildResults = await buildSelectedComponents(
18868
+ componentsToUpload,
18869
+ includeGlobalCss
18870
+ );
18578
18871
  const successfulBuilds = buildResults.filter((build) => build.success);
18579
18872
  const failedBuilds = buildResults.filter((build) => !build.success);
18580
18873
  if (successfulBuilds.length === 0) {
18581
18874
  const message = "All component builds failed.";
18582
- spinner5.stop(chalk2.red(message));
18875
+ spinner6.stop(chalk2.red(message));
18583
18876
  return failedBuilds;
18584
18877
  }
18585
- spinner5.message("Preparing components for upload");
18878
+ spinner6.message("Preparing components for upload");
18586
18879
  const { prepared: preparedComponents, failed: preparationFailures } = await prepareComponentsForUpload(successfulBuilds, componentsToUpload);
18587
18880
  results.push(...preparationFailures);
18588
18881
  if (preparedComponents.length === 0) {
18589
- spinner5.stop(chalk2.red("All component preparations failed"));
18882
+ spinner6.stop(chalk2.red("All component preparations failed"));
18590
18883
  return [...results, ...failedBuilds];
18591
18884
  }
18592
18885
  const machineNames = preparedComponents.map((c) => c.machineName);
18593
18886
  const existenceProgress = createProgressCallback(
18594
- spinner5,
18887
+ spinner6,
18595
18888
  "Checking component existence",
18596
18889
  machineNames.length
18597
18890
  );
18598
- spinner5.message("Checking component existence");
18891
+ spinner6.message("Checking component existence");
18599
18892
  const existenceResults = await checkComponentsExist(
18600
18893
  machineNames,
18601
18894
  apiService,
@@ -18607,11 +18900,11 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18607
18900
  shouldUpdate: existenceResults[index]?.exists || false
18608
18901
  }));
18609
18902
  const uploadProgress = createProgressCallback(
18610
- spinner5,
18903
+ spinner6,
18611
18904
  "Uploading components",
18612
18905
  uploadTasks.length
18613
18906
  );
18614
- spinner5.message("Uploading components");
18907
+ spinner6.message("Uploading components");
18615
18908
  const uploadResults = await uploadComponents(
18616
18909
  uploadTasks,
18617
18910
  apiService,
@@ -18644,34 +18937,33 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18644
18937
  }
18645
18938
  results.push(...failedBuilds);
18646
18939
  const componentLabelPluralized = results.length === 1 ? "component" : "components";
18647
- spinner5.stop(
18940
+ spinner6.stop(
18648
18941
  chalk2.green(`Processed ${results.length} ${componentLabelPluralized}`)
18649
18942
  );
18650
18943
  return results;
18651
18944
  }
18652
- async function buildSelectedComponents(componentDirs) {
18945
+ async function buildSelectedComponents(componentDirs, useLocalGlobalCss = true) {
18653
18946
  const buildResults = [];
18654
18947
  for (const dir of componentDirs) {
18655
- buildResults.push(await buildComponent(dir));
18948
+ buildResults.push(await buildComponent(dir, useLocalGlobalCss));
18656
18949
  }
18657
18950
  return buildResults;
18658
18951
  }
18659
18952
  async function uploadGlobalAssetLibrary(apiService, componentDir) {
18660
18953
  try {
18661
- const distDir = path11.join(componentDir, "dist");
18662
- const globalCompiledCssPath = path11.join(distDir, "index.css");
18954
+ const distDir = path3.join(componentDir, "dist");
18955
+ const globalCompiledCssPath = path3.join(distDir, "index.css");
18663
18956
  const globalCompiledCssExists = await fileExists(globalCompiledCssPath);
18664
18957
  if (globalCompiledCssExists) {
18665
18958
  const globalCompiledCss = await fs2.readFile(
18666
- path11.join(distDir, "index.css"),
18959
+ path3.join(distDir, "index.css"),
18667
18960
  "utf-8"
18668
18961
  );
18669
18962
  const classNameCandidateIndexFile = await fs2.readFile(
18670
- path11.join(distDir, "index.js"),
18963
+ path3.join(distDir, "index.js"),
18671
18964
  "utf-8"
18672
18965
  );
18673
- const current = await apiService.getGlobalAssetLibrary();
18674
- const originalCss = current.css.original;
18966
+ const originalCss = await getGlobalCss();
18675
18967
  await apiService.updateGlobalAssetLibrary({
18676
18968
  css: {
18677
18969
  original: originalCss,
@@ -18714,33 +19006,54 @@ function validateCommand(program2) {
18714
19006
  program2.command("validate").description("validate local components").option(
18715
19007
  "-d, --dir <directory>",
18716
19008
  "Component directory to validate the components in"
18717
- ).option("--all", "Validate all components").option("--verbose", "Enable verbose output").option(
19009
+ ).option(
19010
+ "-c, --components <names>",
19011
+ "Specific component(s) to validate (comma-separated)"
19012
+ ).option("--all", "Validate all components").option("-y, --yes", "Skip confirmation prompts").option("--verbose", "Enable verbose output").option(
18718
19013
  "--fix",
18719
19014
  "Apply available automatic fixes for linting issues",
18720
19015
  false
18721
19016
  ).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`);
19017
+ try {
19018
+ p.intro(chalk2.bold("Drupal Canvas CLI: validate"));
19019
+ validateComponentOptions(options);
19020
+ const allFlag = options.all || options.yes && !options.components || false;
19021
+ updateConfigFromOptions(options);
19022
+ const { directories: componentsToValidate } = await selectLocalComponents({
19023
+ all: allFlag,
19024
+ components: options.components,
19025
+ skipConfirmation: options.yes,
19026
+ selectMessage: "Select components to validate"
19027
+ });
19028
+ const componentPluralized = pluralizeComponent(
19029
+ componentsToValidate.length
19030
+ );
19031
+ const s = p.spinner();
19032
+ s.start(`Validating ${componentPluralized}`);
19033
+ const results = [];
19034
+ for (const componentDir of componentsToValidate) {
19035
+ results.push(await validateComponent(componentDir, options.fix));
19036
+ }
19037
+ s.stop(
19038
+ chalk2.green(
19039
+ `Processed ${componentsToValidate.length} ${componentPluralized}`
19040
+ )
19041
+ );
19042
+ reportResults(results, "Validation results", "Component");
19043
+ const hasErrors = results.some((r) => !r.success);
19044
+ if (hasErrors) {
19045
+ p.outro(`\u274C Validation completed with errors`);
19046
+ process.exit(1);
19047
+ }
19048
+ p.outro(`\u2705 Validation completed`);
19049
+ } catch (error) {
19050
+ if (error instanceof Error) {
19051
+ p.note(chalk2.red(`Error: ${error.message}`));
19052
+ } else {
19053
+ p.note(chalk2.red(`Unknown error: ${String(error)}`));
19054
+ }
18741
19055
  process.exit(1);
18742
19056
  }
18743
- p9.outro(`\u2705 Validation completed`);
18744
19057
  });
18745
19058
  }
18746
19059