@drupal-canvas/cli 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -7
  2. package/dist/index.js +442 -271
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -40,7 +40,6 @@ to get started.
40
40
  | `--client-id` | `CANVAS_CLIENT_ID` | OAuth client ID. |
41
41
  | `--client-secret` | `CANVAS_CLIENT_SECRET` | OAuth client secret. |
42
42
  | `--dir` | `CANVAS_COMPONENT_DIR` | Directory where code components are stored in the filesystem. |
43
- | `--verbose` | `CANVAS_VERBOSE` | Verbose CLI output for troubleshooting. Defaults to `false`. |
44
43
  | `--scope` | `CANVAS_SCOPE` | (Optional) Space-separated list of OAuth scopes to request. |
45
44
 
46
45
  **Note:** The `--scope` parameter defaults to
@@ -66,8 +65,13 @@ npx canvas download [options]
66
65
  - `--all`: Download all components
67
66
  - `-y, --yes`: Skip all confirmation prompts (non-interactive mode)
68
67
  - `--skip-overwrite`: Skip downloading components that already exist locally
68
+ - `--skip-css`: Skip global CSS download
69
+ - `--css-only`: Download only global CSS (skip components)
69
70
 
70
- **Note:** `--components` and `--all` cannot be used together.
71
+ **Notes:**
72
+
73
+ - `--components` and `--all` cannot be used together
74
+ - `--skip-css` and `--css-only` cannot be used together
71
75
 
72
76
  **About prompts:**
73
77
 
@@ -118,12 +122,25 @@ Fully non-interactive, only download new components:
118
122
  npx canvas download --all --yes --skip-overwrite
119
123
  ```
120
124
 
125
+ Download components without global CSS:
126
+
127
+ ```bash
128
+ npx canvas download --all --skip-css
129
+ ```
130
+
131
+ Download only global CSS (skip components):
132
+
133
+ ```bash
134
+ npx canvas download --css-only
135
+ ```
136
+
121
137
  Downloads one or more components from your site. You can select components
122
138
  interactively, specify them with `--components`, or use `--all` to download
123
139
  everything. By default, existing component directories will be overwritten after
124
140
  confirmation. Use `--yes` for non-interactive mode (suitable for CI/CD), or
125
- `--skip-overwrite` to preserve existing components. Also downloads global CSS
126
- assets if available.
141
+ `--skip-overwrite` to preserve existing components. Global CSS assets are
142
+ downloaded by default and can be controlled with `--skip-css` to exclude them or
143
+ `--css-only` to download only CSS without components.
127
144
 
128
145
  ---
129
146
 
@@ -227,8 +244,13 @@ npx canvas upload [options]
227
244
  - `--all`: Upload all components in the directory
228
245
  - `-y, --yes`: Skip confirmation prompts (non-interactive mode)
229
246
  - `--no-tailwind`: Skip Tailwind CSS build and global asset upload
247
+ - `--skip-css`: Skip global CSS upload
248
+ - `--css-only`: Upload only global CSS (skip components)
230
249
 
231
- **Note:** `--components` and `--all` cannot be used together.
250
+ **Notes:**
251
+
252
+ - `--components` and `--all` cannot be used together
253
+ - `--skip-css` and `--css-only` cannot be used together
232
254
 
233
255
  **Examples:**
234
256
 
@@ -268,10 +290,23 @@ CI/CD without Tailwind:
268
290
  npx canvas upload --all --yes --no-tailwind
269
291
  ```
270
292
 
293
+ Upload components without global CSS:
294
+
295
+ ```bash
296
+ npx canvas upload --all --skip-css
297
+ ```
298
+
299
+ Upload only global CSS (skip components):
300
+
301
+ ```bash
302
+ npx canvas upload --css-only
303
+ ```
304
+
271
305
  Builds and uploads the selected (or all) local components to your site. Also
272
306
  builds and uploads global Tailwind CSS assets unless `--no-tailwind` is
273
- specified. Existing components on the site will be updated if they already
274
- exist.
307
+ specified. Global CSS upload can be controlled with `--skip-css` to exclude it
308
+ or `--css-only` to upload only CSS without components. Existing components on
309
+ the site will be updated if they already exist.
275
310
 
276
311
  ---
277
312
 
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { Command } from 'commander';
4
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';
@@ -25,16 +25,16 @@ import { parse } from '@babel/parser';
25
25
 
26
26
  // package.json
27
27
  var package_default = {
28
- version: "0.6.0"};
28
+ version: "0.6.2"};
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
  }
@@ -46,7 +46,6 @@ var config = {
46
46
  clientSecret: process.env.CANVAS_CLIENT_SECRET || "",
47
47
  scope: process.env.CANVAS_SCOPE || "canvas:js_component canvas:asset_library",
48
48
  componentDir: process.env.CANVAS_COMPONENT_DIR || "./components",
49
- verbose: process.env.CANVAS_VERBOSE === "true",
50
49
  userAgent: process.env.CANVAS_USER_AGENT || ""
51
50
  };
52
51
  function getConfig() {
@@ -10672,107 +10671,138 @@ var ApiService = class _ApiService {
10672
10671
  throw new Error("Failed to update global asset library");
10673
10672
  }
10674
10673
  }
10675
- handleApiError(error) {
10676
- const config2 = getConfig();
10677
- const verbose = config2.verbose;
10678
- if (axios.isAxiosError(error)) {
10679
- if (error.response) {
10680
- const status = error.response.status;
10681
- const data = error.response.data;
10682
- if (verbose && status !== 404) {
10683
- console.error("API Error Details:");
10684
- console.error(`- Status: ${status}`);
10685
- console.error(`- URL: ${error.config?.url || "unknown"}`);
10686
- console.error(
10687
- `- Method: ${error.config?.method?.toUpperCase() || "unknown"}`
10688
- );
10689
- console.error("- Response data:", JSON.stringify(data, null, 2));
10690
- const safeHeaders = { ...error.config?.headers };
10691
- if (safeHeaders && safeHeaders.Authorization) {
10692
- safeHeaders.Authorization = "Bearer ********";
10693
- }
10694
- console.error(
10695
- "- Request headers:",
10696
- JSON.stringify(safeHeaders, null, 2)
10697
- );
10698
- }
10699
- if (status === 401) {
10700
- throw new Error(
10701
- "Authentication failed. Please check your client ID and secret."
10702
- );
10703
- } else if (status === 403) {
10704
- throw new Error(
10705
- "You do not have permission to perform this action. Check your configured scope."
10706
- );
10707
- } else if (data && (data.error || data.error_description || data.hint)) {
10708
- throw new Error(
10709
- `API Error (${status}): ${[
10710
- data.error,
10711
- data.error_description,
10712
- data.hint
10713
- ].filter(Boolean).join(" | ")}`
10714
- );
10715
- } else {
10716
- throw new Error(`API Error (${status}): ${error.message}`);
10674
+ /**
10675
+ * Parse Canvas API error responses into user-friendly messages.
10676
+ * Handles both structured validation errors and simple string errors.
10677
+ */
10678
+ parseCanvasErrors(data) {
10679
+ if (data && typeof data === "object" && "errors" in data && Array.isArray(data.errors)) {
10680
+ return data.errors.map((err) => {
10681
+ if (typeof err === "string") {
10682
+ return err.trim();
10717
10683
  }
10718
- } else if (error.request) {
10719
- if (verbose) {
10720
- console.error("Network Error Details:");
10721
- console.error(`- No response received from server`);
10722
- console.error(`- URL: ${error.config?.url || "unknown"}`);
10723
- console.error(
10724
- `- Method: ${error.config?.method?.toUpperCase() || "unknown"}`
10725
- );
10726
- const safeHeaders = { ...error.config?.headers };
10727
- if (safeHeaders && safeHeaders.Authorization) {
10728
- safeHeaders.Authorization = "Bearer ********";
10684
+ if (err && typeof err === "object" && "detail" in err) {
10685
+ let message = typeof err.detail === "string" ? err.detail : String(err.detail);
10686
+ message = message.replace(/<[^>]*>/g, "").replace(/&quot;/g, '"').replace(/&#039;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").trim();
10687
+ if (!message) {
10688
+ return "";
10729
10689
  }
10730
- console.error(
10731
- "- Request headers:",
10732
- JSON.stringify(safeHeaders, null, 2)
10733
- );
10734
- if (this.siteUrl.includes("ddev.site")) {
10735
- console.error("\nDDEV Local Development Troubleshooting Tips:");
10736
- console.error('1. Make sure DDEV is running: try "ddev status"');
10737
- console.error(
10738
- '2. Try using HTTP instead of HTTPS: use "http://drupal-dev.ddev.site" as URL'
10739
- );
10740
- console.error("3. Check if the site is accessible in your browser");
10741
- console.error(
10742
- '4. For HTTPS issues: Try "ddev auth ssl" to set up local SSL certificates'
10743
- );
10690
+ if ("source" in err && err.source && typeof err.source === "object" && "pointer" in err.source && typeof err.source.pointer === "string" && err.source.pointer !== "") {
10691
+ message = `[${err.source.pointer}] ${message}`;
10744
10692
  }
10693
+ return message;
10745
10694
  }
10746
- if (this.siteUrl.includes("ddev.site")) {
10747
- throw new Error(
10748
- `Network error: No response from DDEV site. Is DDEV running? Try using HTTP instead of HTTPS.`
10749
- );
10750
- } else {
10751
- throw new Error(
10752
- `Network error: No response from server. Check your site URL and internet connection.`
10753
- );
10754
- }
10695
+ return "";
10696
+ }).filter((msg) => msg !== "");
10697
+ }
10698
+ return [];
10699
+ }
10700
+ /**
10701
+ * Throws an appropriate error based on the API response.
10702
+ */
10703
+ throwApiError(status, data, error, canvasErrors) {
10704
+ if (canvasErrors.length > 0) {
10705
+ const errorList = canvasErrors.join("\n\n").trim();
10706
+ if (errorList) {
10707
+ throw new Error(errorList);
10708
+ }
10709
+ }
10710
+ if (status === 401) {
10711
+ let message = "Authentication failed. Please check your client ID and secret.";
10712
+ if (data && typeof data === "object" && "error_description" in data && typeof data.error_description === "string") {
10713
+ message = `Authentication Error: ${data.error_description}
10714
+
10715
+ ${message}`;
10716
+ }
10717
+ throw new Error(message);
10718
+ }
10719
+ if (status === 403) {
10720
+ throw new Error(
10721
+ "You do not have permission to perform this action. Check your configured scope."
10722
+ );
10723
+ }
10724
+ if (status === 404) {
10725
+ const url2 = error.config?.url || "unknown";
10726
+ let message = `API endpoint not found: ${url2}
10727
+
10728
+ `;
10729
+ if (this.siteUrl.includes("ddev.site")) {
10730
+ message += "Possible causes:\n";
10731
+ message += " \u2022 DDEV is not running (run: ddev start)\n";
10732
+ message += " \u2022 Canvas module is not enabled (run: ddev drush en canvas -y)\n";
10733
+ message += " \u2022 Site URL is incorrect";
10755
10734
  } else {
10756
- if (verbose) {
10757
- console.error("Request Setup Error:");
10758
- console.error(`- Error: ${error.message}`);
10759
- console.error("- Stack:", error.stack);
10760
- }
10761
- throw new Error(`Request setup error: ${error.message}`);
10735
+ message += "Possible causes:\n";
10736
+ message += " \u2022 Canvas module is not enabled\n";
10737
+ message += " \u2022 Site URL is incorrect\n";
10738
+ message += " \u2022 Server is not responding correctly";
10739
+ }
10740
+ throw new Error(message);
10741
+ }
10742
+ if (data && typeof data === "object" && "message" in data && typeof data.message === "string") {
10743
+ throw new Error(data.message);
10744
+ }
10745
+ if (data && typeof data === "object") {
10746
+ const errorParts = [];
10747
+ if ("error" in data && typeof data.error === "string") {
10748
+ errorParts.push(data.error);
10749
+ }
10750
+ if ("error_description" in data && typeof data.error_description === "string") {
10751
+ errorParts.push(data.error_description);
10752
+ }
10753
+ if ("hint" in data && typeof data.hint === "string") {
10754
+ errorParts.push(data.hint);
10762
10755
  }
10763
- } else if (error instanceof Error) {
10764
- if (verbose) {
10765
- console.error("General Error:");
10766
- console.error(`- Message: ${error.message}`);
10767
- console.error("- Stack:", error.stack);
10756
+ if (errorParts.length > 0) {
10757
+ throw new Error(`API Error (${status}): ${errorParts.join(" | ")}`);
10768
10758
  }
10769
- throw new Error(`Network error: ${error.message}`);
10759
+ }
10760
+ const url = error.config?.url || "unknown";
10761
+ const method = error.config?.method?.toUpperCase() || "unknown";
10762
+ throw new Error(
10763
+ `API Error (${status}): ${error.message}
10764
+
10765
+ URL: ${url}
10766
+ Method: ${method}`
10767
+ );
10768
+ }
10769
+ /**
10770
+ * Handles network errors (no response from server).
10771
+ */
10772
+ handleNetworkError() {
10773
+ let message = `No response from: ${this.siteUrl}
10774
+
10775
+ `;
10776
+ if (this.siteUrl.includes("ddev.site")) {
10777
+ message += "Troubleshooting tips:\n";
10778
+ message += " \u2022 Check if DDEV is running: ddev status\n";
10779
+ message += " \u2022 Try HTTP instead of HTTPS\n";
10780
+ message += " \u2022 Verify site is accessible in browser\n";
10781
+ message += " \u2022 For HTTPS issues, try: ddev auth ssl";
10770
10782
  } else {
10771
- if (verbose) {
10772
- console.error("Unknown Error:", error);
10783
+ message += "Check your site URL and internet connection.";
10784
+ }
10785
+ throw new Error(message);
10786
+ }
10787
+ /**
10788
+ * Main error handler for API requests.
10789
+ */
10790
+ handleApiError(error) {
10791
+ if (!axios.isAxiosError(error)) {
10792
+ if (error instanceof Error) {
10793
+ throw error;
10773
10794
  }
10774
10795
  throw new Error("Unknown API error occurred");
10775
10796
  }
10797
+ if (error.response) {
10798
+ const { status, data } = error.response;
10799
+ const canvasErrors = this.parseCanvasErrors(data);
10800
+ this.throwApiError(status, data, error, canvasErrors);
10801
+ }
10802
+ if (error.request) {
10803
+ this.handleNetworkError();
10804
+ }
10805
+ throw new Error(`Request setup error: ${error.message}`);
10776
10806
  }
10777
10807
  };
10778
10808
  function createApiService() {
@@ -10805,17 +10835,17 @@ function createApiService() {
10805
10835
  userAgent: config2.userAgent
10806
10836
  });
10807
10837
  }
10808
- var CANVAS_CACHE_DIR = path11.join(os.homedir(), ".canvas");
10838
+ var CANVAS_CACHE_DIR = path3.join(os.homedir(), ".canvas");
10809
10839
  async function downloadJsSourceFromCanvas(componentsToDownload) {
10810
10840
  for (const key in componentsToDownload) {
10811
10841
  const component = componentsToDownload[key];
10812
10842
  try {
10813
- const componentDir = path11.join(CANVAS_CACHE_DIR, component.machineName);
10843
+ const componentDir = path3.join(CANVAS_CACHE_DIR, component.machineName);
10814
10844
  await fs2.rm(componentDir, { recursive: true, force: true });
10815
10845
  await fs2.mkdir(componentDir, { recursive: true });
10816
10846
  if (component.sourceCodeJs) {
10817
10847
  await fs2.writeFile(
10818
- path11.join(componentDir, `index.jsx`),
10848
+ path3.join(componentDir, `index.jsx`),
10819
10849
  component.sourceCodeJs,
10820
10850
  "utf-8"
10821
10851
  );
@@ -10833,14 +10863,14 @@ async function copyLocalJsSource(componentsToCopy) {
10833
10863
  try {
10834
10864
  await fs2.mkdir(CANVAS_CACHE_DIR, { recursive: true });
10835
10865
  for (const componentPath of componentsToCopy) {
10836
- const baseName = path11.basename(componentPath);
10866
+ const baseName = path3.basename(componentPath);
10837
10867
  const sourcePath = componentPath;
10838
- const targetPath = path11.join(CANVAS_CACHE_DIR, baseName);
10868
+ const targetPath = path3.join(CANVAS_CACHE_DIR, baseName);
10839
10869
  const stats = await fs2.stat(sourcePath);
10840
10870
  if (stats.isDirectory()) {
10841
10871
  await fs2.mkdir(targetPath, { recursive: true });
10842
- const sourceFile = path11.join(sourcePath, "index.jsx");
10843
- const targetFile = path11.join(targetPath, "index.jsx");
10872
+ const sourceFile = path3.join(sourcePath, "index.jsx");
10873
+ const targetFile = path3.join(targetPath, "index.jsx");
10844
10874
  await fs2.copyFile(sourceFile, targetFile);
10845
10875
  }
10846
10876
  }
@@ -10858,7 +10888,7 @@ async function cleanUpCacheDirectory() {
10858
10888
  withFileTypes: true
10859
10889
  });
10860
10890
  for (const entry of cacheEntries) {
10861
- const entryPath = path11.join(CANVAS_CACHE_DIR, entry.name);
10891
+ const entryPath = path3.join(CANVAS_CACHE_DIR, entry.name);
10862
10892
  if (entry.isDirectory()) {
10863
10893
  await fs2.rm(entryPath, { recursive: true, force: true });
10864
10894
  } else {
@@ -10873,8 +10903,32 @@ async function cleanUpCacheDirectory() {
10873
10903
  );
10874
10904
  }
10875
10905
  }
10906
+ async function fileExists(filePath) {
10907
+ try {
10908
+ await fs2.access(filePath);
10909
+ return true;
10910
+ } catch {
10911
+ return false;
10912
+ }
10913
+ }
10914
+ async function directoryExists(dirPath) {
10915
+ return await fs2.stat(dirPath).then(() => true).catch(() => false);
10916
+ }
10876
10917
 
10877
10918
  // src/utils/build-tailwind.ts
10919
+ async function downloadGlobalCssInBackground() {
10920
+ const apiService = await createApiService();
10921
+ const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
10922
+ return globalAssetLibrary?.css?.original || "";
10923
+ }
10924
+ async function getGlobalCss(useLocal = true) {
10925
+ const config2 = getConfig();
10926
+ const localGlobalCssPath = path3.join(config2.componentDir, "global.css");
10927
+ if (useLocal && await fileExists(localGlobalCssPath)) {
10928
+ return await promises.readFile(localGlobalCssPath, "utf-8");
10929
+ }
10930
+ return await downloadGlobalCssInBackground();
10931
+ }
10878
10932
  async function getAllClassNameCandidatesFromCacheDir(componentsToDownload, localComponentsToCopy) {
10879
10933
  if (Object.keys(componentsToDownload).length > 0) {
10880
10934
  await downloadJsSourceFromCanvas(componentsToDownload);
@@ -10883,7 +10937,7 @@ async function getAllClassNameCandidatesFromCacheDir(componentsToDownload, local
10883
10937
  const cacheEntries = await promises.readdir(CANVAS_CACHE_DIR, {
10884
10938
  withFileTypes: true
10885
10939
  });
10886
- const cacheDirs = cacheEntries.filter((entry) => entry.isDirectory()).map((dir) => path11.join(CANVAS_CACHE_DIR, dir.name));
10940
+ const cacheDirs = cacheEntries.filter((entry) => entry.isDirectory()).map((dir) => path3.join(CANVAS_CACHE_DIR, dir.name));
10887
10941
  let allClassNameCandidates = [];
10888
10942
  for (const cacheDir of cacheDirs) {
10889
10943
  const componentClassNameCandidates = await getClassNameCandidatesForComponent(cacheDir);
@@ -10901,16 +10955,16 @@ async function buildTailwindCss(classNameCandidates, globalSourceCodeCss, distDi
10901
10955
  globalSourceCodeCss
10902
10956
  );
10903
10957
  const transformedTwCss = await transformCss(compiledTwCss);
10904
- await promises.writeFile(path11.join(distDir, "index.css"), transformedTwCss);
10958
+ await promises.writeFile(path3.join(distDir, "index.css"), transformedTwCss);
10905
10959
  }
10906
10960
  async function getClassNameCandidatesForComponent(dir) {
10907
- const componentName = path11.basename(dir);
10961
+ const componentName = path3.basename(dir);
10908
10962
  const config2 = getConfig();
10909
10963
  const componentsDir = config2.componentDir;
10910
- const distDir = path11.join(componentsDir, "dist");
10911
- const jsSource = await promises.readFile(path11.join(dir, "index.jsx"), "utf-8");
10964
+ const distDir = path3.join(componentsDir, "dist");
10965
+ const jsSource = await promises.readFile(path3.join(dir, "index.jsx"), "utf-8");
10912
10966
  const currentGlobalSourceCodeJs = await promises.readFile(
10913
- path11.join(distDir, "index.js"),
10967
+ path3.join(distDir, "index.js"),
10914
10968
  "utf-8"
10915
10969
  );
10916
10970
  const classNameCandidates = OE(jsSource);
@@ -10919,20 +10973,20 @@ async function getClassNameCandidatesForComponent(dir) {
10919
10973
  componentName,
10920
10974
  classNameCandidates
10921
10975
  );
10922
- await promises.writeFile(path11.join(distDir, "index.js"), globalJSClassNameIndex);
10976
+ await promises.writeFile(path3.join(distDir, "index.js"), globalJSClassNameIndex);
10923
10977
  return nextClassNameCandidates;
10924
10978
  }
10925
- async function buildTailwindForComponents(selectedComponents) {
10979
+ async function buildTailwindForComponents(selectedComponents, useLocalGlobalCss = true) {
10926
10980
  try {
10927
10981
  const config2 = getConfig();
10928
10982
  const apiService = await createApiService();
10929
10983
  const onlineComponents = await apiService.listComponents();
10984
+ const globalSourceCodeCss = await getGlobalCss(useLocalGlobalCss);
10930
10985
  const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
10931
- const globalSourceCodeJs = globalAssetLibrary.js.original;
10932
- const globalSourceCodeCss = globalAssetLibrary.css.original;
10933
- const distDir = path11.join(config2.componentDir, "dist");
10986
+ const globalSourceCodeJs = globalAssetLibrary?.js?.original || "";
10987
+ const distDir = path3.join(config2.componentDir, "dist");
10934
10988
  await promises.mkdir(distDir, { recursive: true });
10935
- const targetFile = path11.join(distDir, "index.js");
10989
+ const targetFile = path3.join(distDir, "index.js");
10936
10990
  await promises.writeFile(targetFile, globalSourceCodeJs, "utf-8");
10937
10991
  const allClassNameCandidates = await getAllClassNameCandidatesFromCacheDir(
10938
10992
  onlineComponents,
@@ -10984,17 +11038,6 @@ function compileJS(source) {
10984
11038
  const { code } = transformSync(source, SWC_OPTIONS);
10985
11039
  return code;
10986
11040
  }
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
11041
  async function validateComponent(componentDir, fix = false) {
10999
11042
  const eslint = new ESLint({
11000
11043
  overrideConfigFile: true,
@@ -11012,7 +11055,7 @@ async function validateComponent(componentDir, fix = false) {
11012
11055
  (msg) => `Line ${msg.line}, Column ${msg.column}: ` + msg.message + (msg.ruleId ? ` (${msg.ruleId})` : "")
11013
11056
  );
11014
11057
  details.push({
11015
- heading: path11.relative(process.cwd(), result.filePath),
11058
+ heading: path3.relative(process.cwd(), result.filePath),
11016
11059
  content: messages.join("\n\n")
11017
11060
  });
11018
11061
  });
@@ -11024,8 +11067,8 @@ async function validateComponent(componentDir, fix = false) {
11024
11067
  }
11025
11068
 
11026
11069
  // src/utils/build.ts
11027
- async function buildComponent(componentDir) {
11028
- const componentName = path11.basename(componentDir);
11070
+ async function buildComponent(componentDir, useLocalGlobalCss = true) {
11071
+ const componentName = path3.basename(componentDir);
11029
11072
  const result = {
11030
11073
  itemName: componentName,
11031
11074
  success: true,
@@ -11037,7 +11080,7 @@ async function buildComponent(componentDir) {
11037
11080
  result.details = validationResult.details;
11038
11081
  return result;
11039
11082
  }
11040
- const distDir = path11.join(componentDir, "dist");
11083
+ const distDir = path3.join(componentDir, "dist");
11041
11084
  try {
11042
11085
  await promises.mkdir(distDir, { recursive: true });
11043
11086
  } catch (error) {
@@ -11050,11 +11093,11 @@ async function buildComponent(componentDir) {
11050
11093
  }
11051
11094
  try {
11052
11095
  const jsSource = await promises.readFile(
11053
- path11.join(componentDir, "index.jsx"),
11096
+ path3.join(componentDir, "index.jsx"),
11054
11097
  "utf-8"
11055
11098
  );
11056
11099
  const jsCompiled = compileJS(jsSource);
11057
- await promises.writeFile(path11.join(distDir, "index.js"), jsCompiled);
11100
+ await promises.writeFile(path3.join(distDir, "index.js"), jsCompiled);
11058
11101
  } catch (error) {
11059
11102
  result.success = false;
11060
11103
  result.details?.push({
@@ -11062,11 +11105,9 @@ async function buildComponent(componentDir) {
11062
11105
  content: String(error)
11063
11106
  });
11064
11107
  }
11065
- const apiService = await createApiService();
11066
- const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
11067
- const globalSourceCodeCss = globalAssetLibrary.css.original;
11108
+ const globalSourceCodeCss = await getGlobalCss(useLocalGlobalCss);
11068
11109
  try {
11069
- const cssPath = path11.join(componentDir, "index.css");
11110
+ const cssPath = path3.join(componentDir, "index.css");
11070
11111
  const cssFileExists = await fileExists(cssPath);
11071
11112
  if (cssFileExists) {
11072
11113
  const cssSource = await promises.readFile(cssPath, "utf-8");
@@ -11075,7 +11116,7 @@ async function buildComponent(componentDir) {
11075
11116
  globalSourceCodeCss
11076
11117
  );
11077
11118
  const cssTranspiled = await transformCss(cssCompiled);
11078
- await promises.writeFile(path11.join(distDir, "index.css"), cssTranspiled);
11119
+ await promises.writeFile(path3.join(distDir, "index.css"), cssTranspiled);
11079
11120
  }
11080
11121
  } catch (error) {
11081
11122
  result.success = false;
@@ -11103,7 +11144,6 @@ function updateConfigFromOptions(options) {
11103
11144
  if (options.dir) setConfig({ componentDir: options.dir });
11104
11145
  if (options.scope) setConfig({ scope: options.scope });
11105
11146
  if (options.all) setConfig({ all: options.all });
11106
- if (options.verbose) setConfig({ verbose: true });
11107
11147
  }
11108
11148
  function pluralizeComponent(count) {
11109
11149
  return count === 1 ? "component" : "components";
@@ -17728,11 +17768,11 @@ async function findComponentDirectories(baseDir) {
17728
17768
  const namedYmls = await glob(`${baseDir}/**/*.component.yml`);
17729
17769
  const allComponentPaths = [...standardYmls, ...namedYmls];
17730
17770
  const uniqueDirs = new Set(
17731
- allComponentPaths.map((filePath) => path11.dirname(filePath))
17771
+ allComponentPaths.map((filePath) => path3.dirname(filePath))
17732
17772
  );
17733
17773
  let componentDirs = Array.from(uniqueDirs).sort();
17734
17774
  let sdcs = await glob(`${baseDir}/**/*.twig`);
17735
- sdcs = sdcs.map((sdc) => path11.dirname(sdc));
17775
+ sdcs = sdcs.map((sdc) => path3.dirname(sdc));
17736
17776
  componentDirs = componentDirs.filter(
17737
17777
  (componentDir) => !sdcs.includes(componentDir)
17738
17778
  );
@@ -17744,9 +17784,29 @@ async function findComponentDirectories(baseDir) {
17744
17784
  }
17745
17785
 
17746
17786
  // src/utils/component-selector.ts
17787
+ var GLOBAL_CSS_SELECTOR = "__GLOBAL_CSS__";
17788
+ function determineGlobalCssSelection(options) {
17789
+ if (options.cssOnly) {
17790
+ return true;
17791
+ }
17792
+ if (options.skipCss) {
17793
+ return false;
17794
+ }
17795
+ if (!options.includeGlobalCss) {
17796
+ return void 0;
17797
+ }
17798
+ return options.globalCssDefault !== false;
17799
+ }
17747
17800
  async function selectLocalComponents(options) {
17748
17801
  const config2 = getConfig();
17749
17802
  const componentDir = options.componentDir || config2.componentDir;
17803
+ const globalCssSelection = determineGlobalCssSelection(options);
17804
+ if (options.cssOnly) {
17805
+ return {
17806
+ directories: [],
17807
+ includeGlobalCss: true
17808
+ };
17809
+ }
17750
17810
  const allLocalDirs = await findComponentDirectories(componentDir);
17751
17811
  if (allLocalDirs.length === 0) {
17752
17812
  throw new Error(`No local components were found in ${componentDir}`);
@@ -17755,7 +17815,8 @@ async function selectLocalComponents(options) {
17755
17815
  return selectSpecificLocalComponents(
17756
17816
  options.components,
17757
17817
  allLocalDirs,
17758
- options
17818
+ options,
17819
+ globalCssSelection
17759
17820
  );
17760
17821
  }
17761
17822
  if (options.all) {
@@ -17769,16 +17830,23 @@ async function selectLocalComponents(options) {
17769
17830
  }
17770
17831
  }
17771
17832
  p.log.info(`Selected all components`);
17772
- return { directories: allLocalDirs };
17833
+ return {
17834
+ directories: allLocalDirs,
17835
+ includeGlobalCss: globalCssSelection
17836
+ };
17773
17837
  }
17774
- return selectLocalComponentsInteractive(allLocalDirs, options);
17838
+ return selectLocalComponentsInteractive(
17839
+ allLocalDirs,
17840
+ options,
17841
+ globalCssSelection
17842
+ );
17775
17843
  }
17776
- async function selectSpecificLocalComponents(componentsInput, allLocalDirs, options) {
17844
+ async function selectSpecificLocalComponents(componentsInput, allLocalDirs, options, globalCssSelection) {
17777
17845
  const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17778
17846
  const notFound = [];
17779
17847
  const foundDirs = [];
17780
17848
  for (const requestedName of requestedNames) {
17781
- const dir = allLocalDirs.find((d2) => path11.basename(d2) === requestedName);
17849
+ const dir = allLocalDirs.find((d2) => path3.basename(d2) === requestedName);
17782
17850
  if (dir) {
17783
17851
  foundDirs.push(dir);
17784
17852
  } else {
@@ -17798,27 +17866,48 @@ async function selectSpecificLocalComponents(componentsInput, allLocalDirs, opti
17798
17866
  throw new Error("Operation cancelled by user");
17799
17867
  }
17800
17868
  }
17801
- return { directories: foundDirs };
17869
+ return {
17870
+ directories: foundDirs,
17871
+ includeGlobalCss: globalCssSelection
17872
+ };
17802
17873
  }
17803
- async function selectLocalComponentsInteractive(allLocalDirs, options) {
17804
- const selectedDirs = await p.multiselect({
17805
- message: options.selectMessage || "Select components",
17806
- options: [
17807
- {
17808
- value: ALL_COMPONENTS_SELECTOR,
17809
- label: "All components"
17810
- },
17811
- ...allLocalDirs.map((dir) => ({
17812
- value: dir,
17813
- label: path11.basename(dir)
17814
- }))
17815
- ],
17874
+ async function selectLocalComponentsInteractive(allLocalDirs, options, globalCssSelection) {
17875
+ const multiSelectOptions = [
17876
+ {
17877
+ value: ALL_COMPONENTS_SELECTOR,
17878
+ label: "All components"
17879
+ }
17880
+ ];
17881
+ if (options.includeGlobalCss) {
17882
+ multiSelectOptions.push({
17883
+ value: GLOBAL_CSS_SELECTOR,
17884
+ label: "Global CSS"
17885
+ });
17886
+ }
17887
+ multiSelectOptions.push(
17888
+ ...allLocalDirs.map((dir) => ({
17889
+ value: dir,
17890
+ label: path3.basename(dir)
17891
+ }))
17892
+ );
17893
+ const selectedItems = await p.multiselect({
17894
+ message: options.selectMessage || "Select items",
17895
+ options: multiSelectOptions,
17896
+ initialValues: options.includeGlobalCss && options.globalCssDefault !== false ? [GLOBAL_CSS_SELECTOR] : [],
17816
17897
  required: true
17817
17898
  });
17818
- if (p.isCancel(selectedDirs)) {
17899
+ if (p.isCancel(selectedItems)) {
17819
17900
  throw new Error("Operation cancelled by user");
17820
17901
  }
17821
- const finalDirs = selectedDirs.includes(ALL_COMPONENTS_SELECTOR) ? allLocalDirs : selectedDirs;
17902
+ const includesAllComponents = selectedItems.includes(
17903
+ ALL_COMPONENTS_SELECTOR
17904
+ );
17905
+ const includesGlobalCss = selectedItems.includes(
17906
+ GLOBAL_CSS_SELECTOR
17907
+ );
17908
+ const finalDirs = includesAllComponents ? allLocalDirs : selectedItems.filter(
17909
+ (item) => item !== ALL_COMPONENTS_SELECTOR && item !== GLOBAL_CSS_SELECTOR
17910
+ );
17822
17911
  if (!options.skipConfirmation) {
17823
17912
  const confirmed = await confirmSelection(
17824
17913
  finalDirs.length,
@@ -17828,10 +17917,21 @@ async function selectLocalComponentsInteractive(allLocalDirs, options) {
17828
17917
  throw new Error("Operation cancelled by user");
17829
17918
  }
17830
17919
  }
17831
- return { directories: finalDirs };
17920
+ const finalGlobalCss = options.includeGlobalCss ? includesGlobalCss : globalCssSelection;
17921
+ return {
17922
+ directories: finalDirs,
17923
+ includeGlobalCss: finalGlobalCss
17924
+ };
17832
17925
  }
17833
17926
  async function selectRemoteComponents(allComponents, options) {
17834
17927
  const componentCount = Object.keys(allComponents).length;
17928
+ const globalCssSelection = determineGlobalCssSelection(options);
17929
+ if (options.cssOnly) {
17930
+ return {
17931
+ components: {},
17932
+ includeGlobalCss: true
17933
+ };
17934
+ }
17835
17935
  if (componentCount === 0) {
17836
17936
  throw new Error("No components found");
17837
17937
  }
@@ -17845,18 +17945,26 @@ async function selectRemoteComponents(allComponents, options) {
17845
17945
  throw new Error("Operation cancelled by user");
17846
17946
  }
17847
17947
  }
17848
- return { components: allComponents };
17948
+ return {
17949
+ components: allComponents,
17950
+ includeGlobalCss: globalCssSelection
17951
+ };
17849
17952
  }
17850
17953
  if (options.components) {
17851
17954
  return selectSpecificRemoteComponents(
17852
17955
  options.components,
17853
17956
  allComponents,
17854
- options
17957
+ options,
17958
+ globalCssSelection
17855
17959
  );
17856
17960
  }
17857
- return selectRemoteComponentsInteractive(allComponents, options);
17961
+ return selectRemoteComponentsInteractive(
17962
+ allComponents,
17963
+ options,
17964
+ globalCssSelection
17965
+ );
17858
17966
  }
17859
- async function selectSpecificRemoteComponents(componentsInput, allComponents, options) {
17967
+ async function selectSpecificRemoteComponents(componentsInput, allComponents, options, globalCssSelection) {
17860
17968
  const requestedNames = componentsInput.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
17861
17969
  const notFound = [];
17862
17970
  const selected = {};
@@ -17881,31 +17989,48 @@ async function selectSpecificRemoteComponents(componentsInput, allComponents, op
17881
17989
  throw new Error("Operation cancelled by user");
17882
17990
  }
17883
17991
  }
17884
- return { components: selected };
17992
+ return {
17993
+ components: selected,
17994
+ includeGlobalCss: globalCssSelection
17995
+ };
17885
17996
  }
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
- ],
17997
+ async function selectRemoteComponentsInteractive(allComponents, options, globalCssSelection) {
17998
+ const multiSelectOptions = [
17999
+ {
18000
+ value: ALL_COMPONENTS_SELECTOR,
18001
+ label: "All components"
18002
+ }
18003
+ ];
18004
+ if (options.includeGlobalCss) {
18005
+ multiSelectOptions.push({
18006
+ value: GLOBAL_CSS_SELECTOR,
18007
+ label: "Global CSS"
18008
+ });
18009
+ }
18010
+ multiSelectOptions.push(
18011
+ ...Object.keys(allComponents).map((key) => ({
18012
+ value: allComponents[key].machineName,
18013
+ label: `${allComponents[key].name} (${allComponents[key].machineName})`
18014
+ }))
18015
+ );
18016
+ const selectedItems = await p.multiselect({
18017
+ message: options.selectMessage || "Select items to download",
18018
+ options: multiSelectOptions,
18019
+ initialValues: options.includeGlobalCss && options.globalCssDefault !== false ? [GLOBAL_CSS_SELECTOR] : [],
17899
18020
  required: true
17900
18021
  });
17901
- if (p.isCancel(selectedMachineNames)) {
18022
+ if (p.isCancel(selectedItems)) {
17902
18023
  throw new Error("Operation cancelled by user");
17903
18024
  }
17904
- const selected = selectedMachineNames.includes(
18025
+ const includesAllComponents = selectedItems.includes(
17905
18026
  ALL_COMPONENTS_SELECTOR
17906
- ) ? allComponents : Object.fromEntries(
18027
+ );
18028
+ const includesGlobalCss = selectedItems.includes(
18029
+ GLOBAL_CSS_SELECTOR
18030
+ );
18031
+ const selected = includesAllComponents ? allComponents : Object.fromEntries(
17907
18032
  Object.entries(allComponents).filter(
17908
- ([, component]) => selectedMachineNames.includes(component.machineName)
18033
+ ([, component]) => selectedItems.includes(component.machineName)
17909
18034
  )
17910
18035
  );
17911
18036
  if (!options.skipConfirmation) {
@@ -17917,7 +18042,11 @@ async function selectRemoteComponentsInteractive(allComponents, options) {
17917
18042
  throw new Error("Operation cancelled by user");
17918
18043
  }
17919
18044
  }
17920
- return { components: selected };
18045
+ const finalGlobalCss = options.includeGlobalCss ? includesGlobalCss : globalCssSelection;
18046
+ return {
18047
+ components: selected,
18048
+ includeGlobalCss: finalGlobalCss
18049
+ };
17921
18050
  }
17922
18051
  async function confirmSelection(count, customMessage) {
17923
18052
  const componentLabel = count === 1 ? "component" : "components";
@@ -17991,7 +18120,7 @@ function buildCommand(program2) {
17991
18120
  ).option("--all", "Build all components").option(
17992
18121
  "-c, --components <names>",
17993
18122
  "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) => {
18123
+ ).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").action(async (options) => {
17995
18124
  try {
17996
18125
  p.intro(chalk2.bold("Drupal Canvas CLI: build"));
17997
18126
  validateComponentOptions(options);
@@ -18061,10 +18190,15 @@ function downloadCommand(program2) {
18061
18190
  ).option("--all", "Download all components").option("-y, --yes", "Skip all confirmation prompts").option(
18062
18191
  "--skip-overwrite",
18063
18192
  "Skip downloading components that already exist locally"
18064
- ).option("--verbose", "Enable verbose output").action(async (options) => {
18193
+ ).option("--skip-css", "Skip downloading global CSS").option("--css-only", "Download only global CSS (skip components)").action(async (options) => {
18065
18194
  p.intro(chalk2.bold("Drupal Canvas CLI: download"));
18066
18195
  try {
18067
18196
  validateComponentOptions(options);
18197
+ if (options.skipCss && options.cssOnly) {
18198
+ throw new Error(
18199
+ "Cannot use both --skip-css and --css-only flags together"
18200
+ );
18201
+ }
18068
18202
  updateConfigFromOptions(options);
18069
18203
  await ensureConfig([
18070
18204
  "siteUrl",
@@ -18075,35 +18209,50 @@ function downloadCommand(program2) {
18075
18209
  ]);
18076
18210
  const config2 = getConfig();
18077
18211
  const apiService = await createApiService();
18212
+ let components = {};
18213
+ let globalCss;
18078
18214
  const s = p.spinner();
18079
- s.start("Fetching components");
18080
- const components = await apiService.listComponents();
18081
- const {
18082
- css: { original: globalCss }
18083
- } = await apiService.getGlobalAssetLibrary();
18084
- if (Object.keys(components).length === 0) {
18085
- s.stop("No components found");
18086
- p.outro("Download cancelled - no components were found");
18087
- return;
18215
+ if (options.cssOnly) {
18216
+ s.start("Fetching global CSS");
18217
+ const globalAssetLibrary = await apiService.getGlobalAssetLibrary();
18218
+ globalCss = globalAssetLibrary?.css?.original || "";
18219
+ s.stop("Global CSS fetched");
18220
+ } else {
18221
+ s.start("Fetching components and global CSS");
18222
+ const [fetchedComponents, globalAssetLibrary] = await Promise.all([
18223
+ apiService.listComponents(),
18224
+ apiService.getGlobalAssetLibrary()
18225
+ ]);
18226
+ components = fetchedComponents;
18227
+ globalCss = globalAssetLibrary?.css?.original || "";
18228
+ if (Object.keys(components).length === 0) {
18229
+ s.stop("No components found");
18230
+ p.outro("Download cancelled - no components were found");
18231
+ return;
18232
+ }
18233
+ s.stop(`Found ${Object.keys(components).length} components`);
18088
18234
  }
18089
- s.stop(`Found ${Object.keys(components).length} components`);
18090
18235
  const allFlag = options.all || options.yes && !options.components || false;
18091
- const { components: componentsToDownload } = await selectRemoteComponents(components, {
18236
+ const { components: componentsToDownload, includeGlobalCss } = await selectRemoteComponents(components, {
18092
18237
  all: allFlag,
18093
18238
  components: options.components,
18094
18239
  skipConfirmation: options.yes,
18095
- selectMessage: "Select components to download",
18240
+ skipCss: options.skipCss,
18241
+ cssOnly: options.cssOnly,
18242
+ includeGlobalCss: !options.skipCss,
18243
+ globalCssDefault: true,
18244
+ selectMessage: "Select items to download",
18096
18245
  confirmMessage: `Download to ${config2.componentDir}?`
18097
18246
  });
18098
- const componentPluralized = pluralizeComponent(
18099
- Object.keys(componentsToDownload).length
18100
- );
18247
+ const componentCount = Object.keys(componentsToDownload).length;
18248
+ const componentPluralized = pluralizeComponent(componentCount);
18101
18249
  const results = [];
18102
- s.start(`Downloading ${componentPluralized}`);
18250
+ const downloadMessage = options.cssOnly ? "Downloading global CSS" : componentCount > 0 ? `Downloading ${componentPluralized}` : "Processing request";
18251
+ s.start(downloadMessage);
18103
18252
  for (const key in componentsToDownload) {
18104
18253
  const component = componentsToDownload[key];
18105
18254
  try {
18106
- const componentDir = path11.join(
18255
+ const componentDir = path3.join(
18107
18256
  config2.componentDir,
18108
18257
  component.machineName
18109
18258
  );
@@ -18148,20 +18297,20 @@ function downloadCommand(program2) {
18148
18297
  slots: component.slots || {}
18149
18298
  };
18150
18299
  await fs2.writeFile(
18151
- path11.join(componentDir, `component.yml`),
18300
+ path3.join(componentDir, `component.yml`),
18152
18301
  yaml__default.dump(metadata),
18153
18302
  "utf-8"
18154
18303
  );
18155
18304
  if (component.sourceCodeJs) {
18156
18305
  await fs2.writeFile(
18157
- path11.join(componentDir, `index.jsx`),
18306
+ path3.join(componentDir, `index.jsx`),
18158
18307
  component.sourceCodeJs,
18159
18308
  "utf-8"
18160
18309
  );
18161
18310
  }
18162
18311
  if (component.sourceCodeCss) {
18163
18312
  await fs2.writeFile(
18164
- path11.join(componentDir, `index.css`),
18313
+ path3.join(componentDir, `index.css`),
18165
18314
  component.sourceCodeCss,
18166
18315
  "utf-8"
18167
18316
  );
@@ -18182,16 +18331,15 @@ function downloadCommand(program2) {
18182
18331
  });
18183
18332
  }
18184
18333
  }
18185
- s.stop(
18186
- chalk2.green(
18187
- `Processed ${Object.keys(componentsToDownload).length} ${componentPluralized}`
18188
- )
18189
- );
18190
- reportResults(results, "Downloaded components", "Component");
18191
- if (globalCss) {
18334
+ const successMessage = options.cssOnly && componentCount === 0 ? "Global CSS download completed" : `Processed ${componentCount} ${componentPluralized}`;
18335
+ s.stop(chalk2.green(successMessage));
18336
+ if (componentCount > 0) {
18337
+ reportResults(results, "Downloaded components", "Component");
18338
+ }
18339
+ if (includeGlobalCss && typeof globalCss === "string") {
18192
18340
  let globalCssResult;
18193
18341
  try {
18194
- const globalCssPath = path11.join(config2.componentDir, "global.css");
18342
+ const globalCssPath = path3.join(config2.componentDir, "global.css");
18195
18343
  await fs2.writeFile(globalCssPath, globalCss, "utf-8");
18196
18344
  globalCssResult = {
18197
18345
  itemName: "global.css",
@@ -18211,7 +18359,8 @@ function downloadCommand(program2) {
18211
18359
  }
18212
18360
  reportResults([globalCssResult], "Downloaded assets", "Asset");
18213
18361
  }
18214
- p.outro(`\u2B07\uFE0F Download command completed`);
18362
+ 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";
18363
+ p.outro(outroMessage);
18215
18364
  } catch (error) {
18216
18365
  if (error instanceof Error) {
18217
18366
  p.note(chalk2.red(`Error: ${error.message}`));
@@ -18229,11 +18378,10 @@ function scaffoldCommand(program2) {
18229
18378
  ).option(
18230
18379
  "-d, --dir <directory>",
18231
18380
  "Component directory to create component in"
18232
- ).option("--verbose", "Enable verbose output").action(async (options) => {
18381
+ ).action(async (options) => {
18233
18382
  p.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18234
18383
  try {
18235
18384
  if (options.dir) setConfig({ componentDir: options.dir });
18236
- if (options.verbose) setConfig({ verbose: options.verbose });
18237
18385
  const config2 = getConfig();
18238
18386
  const baseDir = config2.componentDir;
18239
18387
  let componentName = options.name;
@@ -18254,13 +18402,13 @@ function scaffoldCommand(program2) {
18254
18402
  }
18255
18403
  componentName = name;
18256
18404
  }
18257
- const componentDir = path11.join(baseDir, componentName);
18405
+ const componentDir = path3.join(baseDir, componentName);
18258
18406
  const s = p.spinner();
18259
18407
  s.start(`Creating component "${componentName}"`);
18260
18408
  try {
18261
18409
  await fs2.mkdir(componentDir, { recursive: true });
18262
- const templateDir = path11.join(
18263
- path11.dirname(new URL(import.meta.url).pathname),
18410
+ const templateDir = path3.join(
18411
+ path3.dirname(new URL(import.meta.url).pathname),
18264
18412
  "templates/hello-world"
18265
18413
  );
18266
18414
  const files = await fs2.readdir(templateDir);
@@ -18276,8 +18424,8 @@ function scaffoldCommand(program2) {
18276
18424
  }
18277
18425
  }
18278
18426
  for (const file of files) {
18279
- const srcPath = path11.join(templateDir, file);
18280
- const destPath = path11.join(componentDir, file);
18427
+ const srcPath = path3.join(templateDir, file);
18428
+ const destPath = path3.join(componentDir, file);
18281
18429
  let content = await fs2.readFile(srcPath, "utf-8");
18282
18430
  const { pascalCaseName, className, displayName, machineName } = generateComponentNameFormats(componentName);
18283
18431
  content = content.replace(/HelloWorld/g, pascalCaseName).replace(/hello-world-component/g, className).replace(/Hello World/g, displayName).replace(/hello_world/g, machineName);
@@ -18286,9 +18434,9 @@ function scaffoldCommand(program2) {
18286
18434
  s.stop(chalk2.green(`Created component "${componentName}"`));
18287
18435
  p.note(`Component "${componentName}" has been created:
18288
18436
  - Directory: ${componentDir}
18289
- - Component metadata: ${path11.join(componentDir, `component.yml`)}
18290
- - Source file: ${path11.join(componentDir, `index.jsx`)}
18291
- - CSS file: ${path11.join(componentDir, `index.css`)}`);
18437
+ - Component metadata: ${path3.join(componentDir, `component.yml`)}
18438
+ - Source file: ${path3.join(componentDir, `index.jsx`)}
18439
+ - CSS file: ${path3.join(componentDir, `index.css`)}`);
18292
18440
  p.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18293
18441
  } catch (error) {
18294
18442
  s.stop(chalk2.red(`Failed to create component "${componentName}"`));
@@ -18366,20 +18514,20 @@ var getDataDependenciesFromAst = (ast) => ast.program.body.filter((d2) => d2.typ
18366
18514
  async function processComponentFiles(componentDir) {
18367
18515
  const metadataPath = await findMetadataPath(componentDir);
18368
18516
  const metadata = await readComponentMetadata(metadataPath);
18369
- const distDir = path11.join(componentDir, "dist");
18517
+ const distDir = path3.join(componentDir, "dist");
18370
18518
  const sourceCodeJs = await promises.readFile(
18371
- path11.join(componentDir, "index.jsx"),
18519
+ path3.join(componentDir, "index.jsx"),
18372
18520
  "utf-8"
18373
18521
  );
18374
- const compiledJs = await promises.readFile(path11.join(distDir, "index.js"), "utf-8");
18522
+ const compiledJs = await promises.readFile(path3.join(distDir, "index.js"), "utf-8");
18375
18523
  let sourceCodeCss = "";
18376
18524
  let compiledCss = "";
18377
18525
  try {
18378
18526
  sourceCodeCss = await promises.readFile(
18379
- path11.join(componentDir, "index.css"),
18527
+ path3.join(componentDir, "index.css"),
18380
18528
  "utf-8"
18381
18529
  );
18382
- compiledCss = await promises.readFile(path11.join(distDir, "index.css"), "utf-8");
18530
+ compiledCss = await promises.readFile(path3.join(distDir, "index.css"), "utf-8");
18383
18531
  } catch {
18384
18532
  }
18385
18533
  return {
@@ -18391,7 +18539,7 @@ async function processComponentFiles(componentDir) {
18391
18539
  };
18392
18540
  }
18393
18541
  async function findMetadataPath(componentDir) {
18394
- const metadataPath = path11.join(componentDir, "component.yml");
18542
+ const metadataPath = path3.join(componentDir, "component.yml");
18395
18543
  try {
18396
18544
  await promises.access(metadataPath);
18397
18545
  return metadataPath;
@@ -18412,10 +18560,10 @@ async function readComponentMetadata(filePath) {
18412
18560
  }
18413
18561
  const metadata = rawMetadata;
18414
18562
  if (!metadata.name) {
18415
- metadata.name = path11.basename(path11.dirname(filePath));
18563
+ metadata.name = path3.basename(path3.dirname(filePath));
18416
18564
  }
18417
18565
  if (!metadata.machineName) {
18418
- metadata.machineName = path11.basename(path11.dirname(filePath));
18566
+ metadata.machineName = path3.basename(path3.dirname(filePath));
18419
18567
  }
18420
18568
  if (!metadata.slots || typeof metadata.slots !== "object") {
18421
18569
  metadata.slots = {};
@@ -18585,12 +18733,17 @@ function uploadCommand(program2) {
18585
18733
  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
18734
  "-c, --components <names>",
18587
18735
  "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) => {
18736
+ ).option("--all", "Upload all components").option("-y, --yes", "Skip confirmation prompts").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) => {
18589
18737
  const allFlag = options.all || options.yes && !options.components || false;
18590
18738
  const skipTailwind = !options.tailwind;
18591
18739
  try {
18592
18740
  p.intro(chalk2.bold("Drupal Canvas CLI: upload"));
18593
18741
  validateComponentOptions(options);
18742
+ if (options.skipCss && options.cssOnly) {
18743
+ throw new Error(
18744
+ "Cannot use both --skip-css and --css-only flags together"
18745
+ );
18746
+ }
18594
18747
  updateConfigFromOptions(options);
18595
18748
  await ensureConfig([
18596
18749
  "siteUrl",
@@ -18600,22 +18753,29 @@ function uploadCommand(program2) {
18600
18753
  "componentDir"
18601
18754
  ]);
18602
18755
  const config2 = getConfig();
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
- }
18610
- );
18756
+ const { directories: componentsToUpload, includeGlobalCss } = await selectLocalComponents({
18757
+ all: allFlag,
18758
+ components: options.components,
18759
+ skipConfirmation: options.yes,
18760
+ skipCss: options.skipCss,
18761
+ cssOnly: options.cssOnly,
18762
+ includeGlobalCss: !options.skipCss,
18763
+ globalCssDefault: true,
18764
+ selectMessage: "Select items to upload"
18765
+ });
18611
18766
  const apiService = await createApiService();
18612
- const componentResults = await getBuildAndUploadResults(
18613
- componentsToUpload,
18614
- apiService
18615
- );
18616
- reportResults(componentResults, "Uploaded components", "Component");
18617
- if (componentResults.some((result) => !result.success)) {
18618
- process.exit(1);
18767
+ await apiService.listComponents();
18768
+ let componentResults = [];
18769
+ if (!options.cssOnly && componentsToUpload.length > 0) {
18770
+ componentResults = await getBuildAndUploadResults(
18771
+ componentsToUpload,
18772
+ apiService,
18773
+ includeGlobalCss ?? false
18774
+ );
18775
+ reportResults(componentResults, "Uploaded components", "Component");
18776
+ if (componentResults.some((result) => !result.success)) {
18777
+ process.exit(1);
18778
+ }
18619
18779
  }
18620
18780
  if (skipTailwind) {
18621
18781
  p.log.info("Skipping Tailwind CSS build");
@@ -18623,7 +18783,9 @@ function uploadCommand(program2) {
18623
18783
  const s2 = p.spinner();
18624
18784
  s2.start("Building Tailwind CSS");
18625
18785
  const tailwindResult = await buildTailwindForComponents(
18626
- componentsToUpload
18786
+ componentsToUpload,
18787
+ includeGlobalCss
18788
+ // Use local CSS if includeGlobalCss is true
18627
18789
  );
18628
18790
  const componentLabelPluralized = pluralizeComponent(
18629
18791
  componentsToUpload.length
@@ -18639,14 +18801,20 @@ function uploadCommand(program2) {
18639
18801
  chalk2.red(`Tailwind build failed, global assets upload aborted.`)
18640
18802
  );
18641
18803
  } else {
18642
- const globalCssResult = await uploadGlobalAssetLibrary(
18643
- apiService,
18644
- config2.componentDir
18645
- );
18646
- reportResults([globalCssResult], "Uploaded assets", "Asset");
18804
+ if (includeGlobalCss) {
18805
+ const globalCssResult = await uploadGlobalAssetLibrary(
18806
+ apiService,
18807
+ config2.componentDir
18808
+ );
18809
+ reportResults([globalCssResult], "Uploaded assets", "Asset");
18810
+ } else {
18811
+ p.log.info("Skipping global CSS upload");
18812
+ }
18647
18813
  }
18648
18814
  }
18649
- p.outro("\u2B06\uFE0F Upload command completed");
18815
+ const componentCount = componentsToUpload.length;
18816
+ 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";
18817
+ p.outro(outroMessage);
18650
18818
  } catch (error) {
18651
18819
  if (error instanceof Error) {
18652
18820
  p.note(chalk2.red(`Error: ${error.message}`));
@@ -18662,11 +18830,11 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18662
18830
  const failed = [];
18663
18831
  for (const buildResult of successfulBuilds) {
18664
18832
  const dir = buildResult.itemName ? componentsToUpload.find(
18665
- (d2) => path11.basename(d2) === buildResult.itemName
18833
+ (d2) => path3.basename(d2) === buildResult.itemName
18666
18834
  ) : void 0;
18667
18835
  if (!dir) continue;
18668
18836
  try {
18669
- const componentName = path11.basename(dir);
18837
+ const componentName = path3.basename(dir);
18670
18838
  const { sourceCodeJs, compiledJs, sourceCodeCss, compiledCss, metadata } = await processComponentFiles(dir);
18671
18839
  if (!metadata) {
18672
18840
  throw new Error("Invalid metadata file");
@@ -18719,11 +18887,14 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18719
18887
  }
18720
18888
  return { prepared, failed };
18721
18889
  }
18722
- async function getBuildAndUploadResults(componentsToUpload, apiService) {
18890
+ async function getBuildAndUploadResults(componentsToUpload, apiService, includeGlobalCss) {
18723
18891
  const results = [];
18724
18892
  const spinner6 = p.spinner();
18725
18893
  spinner6.start("Building components");
18726
- const buildResults = await buildSelectedComponents(componentsToUpload);
18894
+ const buildResults = await buildSelectedComponents(
18895
+ componentsToUpload,
18896
+ includeGlobalCss
18897
+ );
18727
18898
  const successfulBuilds = buildResults.filter((build) => build.success);
18728
18899
  const failedBuilds = buildResults.filter((build) => !build.success);
18729
18900
  if (successfulBuilds.length === 0) {
@@ -18780,12 +18951,13 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18780
18951
  ]
18781
18952
  });
18782
18953
  } else {
18954
+ const errorMessage = uploadResult.error?.message || "Unknown upload error";
18783
18955
  results.push({
18784
18956
  itemName: component.componentName,
18785
18957
  success: false,
18786
18958
  details: [
18787
18959
  {
18788
- content: uploadResult.error?.message || "Unknown upload error"
18960
+ content: errorMessage.trim() || "Unknown upload error"
18789
18961
  }
18790
18962
  ]
18791
18963
  });
@@ -18798,29 +18970,28 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18798
18970
  );
18799
18971
  return results;
18800
18972
  }
18801
- async function buildSelectedComponents(componentDirs) {
18973
+ async function buildSelectedComponents(componentDirs, useLocalGlobalCss = true) {
18802
18974
  const buildResults = [];
18803
18975
  for (const dir of componentDirs) {
18804
- buildResults.push(await buildComponent(dir));
18976
+ buildResults.push(await buildComponent(dir, useLocalGlobalCss));
18805
18977
  }
18806
18978
  return buildResults;
18807
18979
  }
18808
18980
  async function uploadGlobalAssetLibrary(apiService, componentDir) {
18809
18981
  try {
18810
- const distDir = path11.join(componentDir, "dist");
18811
- const globalCompiledCssPath = path11.join(distDir, "index.css");
18982
+ const distDir = path3.join(componentDir, "dist");
18983
+ const globalCompiledCssPath = path3.join(distDir, "index.css");
18812
18984
  const globalCompiledCssExists = await fileExists(globalCompiledCssPath);
18813
18985
  if (globalCompiledCssExists) {
18814
18986
  const globalCompiledCss = await fs2.readFile(
18815
- path11.join(distDir, "index.css"),
18987
+ path3.join(distDir, "index.css"),
18816
18988
  "utf-8"
18817
18989
  );
18818
18990
  const classNameCandidateIndexFile = await fs2.readFile(
18819
- path11.join(distDir, "index.js"),
18991
+ path3.join(distDir, "index.js"),
18820
18992
  "utf-8"
18821
18993
  );
18822
- const current = await apiService.getGlobalAssetLibrary();
18823
- const originalCss = current.css.original;
18994
+ const originalCss = await getGlobalCss();
18824
18995
  await apiService.updateGlobalAssetLibrary({
18825
18996
  css: {
18826
18997
  original: originalCss,
@@ -18866,7 +19037,7 @@ function validateCommand(program2) {
18866
19037
  ).option(
18867
19038
  "-c, --components <names>",
18868
19039
  "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(
19040
+ ).option("--all", "Validate all components").option("-y, --yes", "Skip confirmation prompts").option(
18870
19041
  "--fix",
18871
19042
  "Apply available automatic fixes for linting issues",
18872
19043
  false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drupal-canvas/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "CLI tool for managing Drupal Canvas code components",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "@babel/parser": "^7.26.9",
53
53
  "@clack/prompts": "^0.11.0",
54
- "@drupal-canvas/eslint-config": "^0.0.1",
54
+ "@drupal-canvas/eslint-config": "^0.1.0",
55
55
  "@swc/wasm": "^1.12.1",
56
56
  "axios": "^1.9.0",
57
57
  "chalk": "^5.4.1",