@drupal-canvas/cli 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -7
  2. package/dist/index.js +444 -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
+ };
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
10986
  const globalSourceCodeJs = globalAssetLibrary.js.original;
10932
- const globalSourceCodeCss = globalAssetLibrary.css.original;
10933
- const distDir = path11.join(config2.componentDir, "dist");
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,52 @@ 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 {
18218
+ css: { original }
18219
+ } = await apiService.getGlobalAssetLibrary();
18220
+ globalCss = original;
18221
+ s.stop("Global CSS fetched");
18222
+ } else {
18223
+ s.start("Fetching components and global CSS");
18224
+ const [fetchedComponents, globalAssetLibrary] = await Promise.all([
18225
+ apiService.listComponents(),
18226
+ apiService.getGlobalAssetLibrary()
18227
+ ]);
18228
+ components = fetchedComponents;
18229
+ globalCss = globalAssetLibrary.css.original;
18230
+ if (Object.keys(components).length === 0) {
18231
+ s.stop("No components found");
18232
+ p.outro("Download cancelled - no components were found");
18233
+ return;
18234
+ }
18235
+ s.stop(`Found ${Object.keys(components).length} components`);
18088
18236
  }
18089
- s.stop(`Found ${Object.keys(components).length} components`);
18090
18237
  const allFlag = options.all || options.yes && !options.components || false;
18091
- const { components: componentsToDownload } = await selectRemoteComponents(components, {
18238
+ const { components: componentsToDownload, includeGlobalCss } = await selectRemoteComponents(components, {
18092
18239
  all: allFlag,
18093
18240
  components: options.components,
18094
18241
  skipConfirmation: options.yes,
18095
- selectMessage: "Select components to download",
18242
+ skipCss: options.skipCss,
18243
+ cssOnly: options.cssOnly,
18244
+ includeGlobalCss: !options.skipCss,
18245
+ globalCssDefault: true,
18246
+ selectMessage: "Select items to download",
18096
18247
  confirmMessage: `Download to ${config2.componentDir}?`
18097
18248
  });
18098
- const componentPluralized = pluralizeComponent(
18099
- Object.keys(componentsToDownload).length
18100
- );
18249
+ const componentCount = Object.keys(componentsToDownload).length;
18250
+ const componentPluralized = pluralizeComponent(componentCount);
18101
18251
  const results = [];
18102
- s.start(`Downloading ${componentPluralized}`);
18252
+ const downloadMessage = options.cssOnly ? "Downloading global CSS" : componentCount > 0 ? `Downloading ${componentPluralized}` : "Processing request";
18253
+ s.start(downloadMessage);
18103
18254
  for (const key in componentsToDownload) {
18104
18255
  const component = componentsToDownload[key];
18105
18256
  try {
18106
- const componentDir = path11.join(
18257
+ const componentDir = path3.join(
18107
18258
  config2.componentDir,
18108
18259
  component.machineName
18109
18260
  );
@@ -18148,20 +18299,20 @@ function downloadCommand(program2) {
18148
18299
  slots: component.slots || {}
18149
18300
  };
18150
18301
  await fs2.writeFile(
18151
- path11.join(componentDir, `component.yml`),
18302
+ path3.join(componentDir, `component.yml`),
18152
18303
  yaml__default.dump(metadata),
18153
18304
  "utf-8"
18154
18305
  );
18155
18306
  if (component.sourceCodeJs) {
18156
18307
  await fs2.writeFile(
18157
- path11.join(componentDir, `index.jsx`),
18308
+ path3.join(componentDir, `index.jsx`),
18158
18309
  component.sourceCodeJs,
18159
18310
  "utf-8"
18160
18311
  );
18161
18312
  }
18162
18313
  if (component.sourceCodeCss) {
18163
18314
  await fs2.writeFile(
18164
- path11.join(componentDir, `index.css`),
18315
+ path3.join(componentDir, `index.css`),
18165
18316
  component.sourceCodeCss,
18166
18317
  "utf-8"
18167
18318
  );
@@ -18182,16 +18333,15 @@ function downloadCommand(program2) {
18182
18333
  });
18183
18334
  }
18184
18335
  }
18185
- s.stop(
18186
- chalk2.green(
18187
- `Processed ${Object.keys(componentsToDownload).length} ${componentPluralized}`
18188
- )
18189
- );
18190
- reportResults(results, "Downloaded components", "Component");
18191
- if (globalCss) {
18336
+ const successMessage = options.cssOnly && componentCount === 0 ? "Global CSS download completed" : `Processed ${componentCount} ${componentPluralized}`;
18337
+ s.stop(chalk2.green(successMessage));
18338
+ if (componentCount > 0) {
18339
+ reportResults(results, "Downloaded components", "Component");
18340
+ }
18341
+ if (includeGlobalCss && typeof globalCss === "string") {
18192
18342
  let globalCssResult;
18193
18343
  try {
18194
- const globalCssPath = path11.join(config2.componentDir, "global.css");
18344
+ const globalCssPath = path3.join(config2.componentDir, "global.css");
18195
18345
  await fs2.writeFile(globalCssPath, globalCss, "utf-8");
18196
18346
  globalCssResult = {
18197
18347
  itemName: "global.css",
@@ -18211,7 +18361,8 @@ function downloadCommand(program2) {
18211
18361
  }
18212
18362
  reportResults([globalCssResult], "Downloaded assets", "Asset");
18213
18363
  }
18214
- p.outro(`\u2B07\uFE0F Download command completed`);
18364
+ 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";
18365
+ p.outro(outroMessage);
18215
18366
  } catch (error) {
18216
18367
  if (error instanceof Error) {
18217
18368
  p.note(chalk2.red(`Error: ${error.message}`));
@@ -18229,11 +18380,10 @@ function scaffoldCommand(program2) {
18229
18380
  ).option(
18230
18381
  "-d, --dir <directory>",
18231
18382
  "Component directory to create component in"
18232
- ).option("--verbose", "Enable verbose output").action(async (options) => {
18383
+ ).action(async (options) => {
18233
18384
  p.intro(chalk2.bold("Drupal Canvas CLI: scaffold"));
18234
18385
  try {
18235
18386
  if (options.dir) setConfig({ componentDir: options.dir });
18236
- if (options.verbose) setConfig({ verbose: options.verbose });
18237
18387
  const config2 = getConfig();
18238
18388
  const baseDir = config2.componentDir;
18239
18389
  let componentName = options.name;
@@ -18254,13 +18404,13 @@ function scaffoldCommand(program2) {
18254
18404
  }
18255
18405
  componentName = name;
18256
18406
  }
18257
- const componentDir = path11.join(baseDir, componentName);
18407
+ const componentDir = path3.join(baseDir, componentName);
18258
18408
  const s = p.spinner();
18259
18409
  s.start(`Creating component "${componentName}"`);
18260
18410
  try {
18261
18411
  await fs2.mkdir(componentDir, { recursive: true });
18262
- const templateDir = path11.join(
18263
- path11.dirname(new URL(import.meta.url).pathname),
18412
+ const templateDir = path3.join(
18413
+ path3.dirname(new URL(import.meta.url).pathname),
18264
18414
  "templates/hello-world"
18265
18415
  );
18266
18416
  const files = await fs2.readdir(templateDir);
@@ -18276,8 +18426,8 @@ function scaffoldCommand(program2) {
18276
18426
  }
18277
18427
  }
18278
18428
  for (const file of files) {
18279
- const srcPath = path11.join(templateDir, file);
18280
- const destPath = path11.join(componentDir, file);
18429
+ const srcPath = path3.join(templateDir, file);
18430
+ const destPath = path3.join(componentDir, file);
18281
18431
  let content = await fs2.readFile(srcPath, "utf-8");
18282
18432
  const { pascalCaseName, className, displayName, machineName } = generateComponentNameFormats(componentName);
18283
18433
  content = content.replace(/HelloWorld/g, pascalCaseName).replace(/hello-world-component/g, className).replace(/Hello World/g, displayName).replace(/hello_world/g, machineName);
@@ -18286,9 +18436,9 @@ function scaffoldCommand(program2) {
18286
18436
  s.stop(chalk2.green(`Created component "${componentName}"`));
18287
18437
  p.note(`Component "${componentName}" has been created:
18288
18438
  - 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`)}`);
18439
+ - Component metadata: ${path3.join(componentDir, `component.yml`)}
18440
+ - Source file: ${path3.join(componentDir, `index.jsx`)}
18441
+ - CSS file: ${path3.join(componentDir, `index.css`)}`);
18292
18442
  p.outro("\u{1F3D7}\uFE0F Scaffold command completed");
18293
18443
  } catch (error) {
18294
18444
  s.stop(chalk2.red(`Failed to create component "${componentName}"`));
@@ -18366,20 +18516,20 @@ var getDataDependenciesFromAst = (ast) => ast.program.body.filter((d2) => d2.typ
18366
18516
  async function processComponentFiles(componentDir) {
18367
18517
  const metadataPath = await findMetadataPath(componentDir);
18368
18518
  const metadata = await readComponentMetadata(metadataPath);
18369
- const distDir = path11.join(componentDir, "dist");
18519
+ const distDir = path3.join(componentDir, "dist");
18370
18520
  const sourceCodeJs = await promises.readFile(
18371
- path11.join(componentDir, "index.jsx"),
18521
+ path3.join(componentDir, "index.jsx"),
18372
18522
  "utf-8"
18373
18523
  );
18374
- const compiledJs = await promises.readFile(path11.join(distDir, "index.js"), "utf-8");
18524
+ const compiledJs = await promises.readFile(path3.join(distDir, "index.js"), "utf-8");
18375
18525
  let sourceCodeCss = "";
18376
18526
  let compiledCss = "";
18377
18527
  try {
18378
18528
  sourceCodeCss = await promises.readFile(
18379
- path11.join(componentDir, "index.css"),
18529
+ path3.join(componentDir, "index.css"),
18380
18530
  "utf-8"
18381
18531
  );
18382
- compiledCss = await promises.readFile(path11.join(distDir, "index.css"), "utf-8");
18532
+ compiledCss = await promises.readFile(path3.join(distDir, "index.css"), "utf-8");
18383
18533
  } catch {
18384
18534
  }
18385
18535
  return {
@@ -18391,7 +18541,7 @@ async function processComponentFiles(componentDir) {
18391
18541
  };
18392
18542
  }
18393
18543
  async function findMetadataPath(componentDir) {
18394
- const metadataPath = path11.join(componentDir, "component.yml");
18544
+ const metadataPath = path3.join(componentDir, "component.yml");
18395
18545
  try {
18396
18546
  await promises.access(metadataPath);
18397
18547
  return metadataPath;
@@ -18412,10 +18562,10 @@ async function readComponentMetadata(filePath) {
18412
18562
  }
18413
18563
  const metadata = rawMetadata;
18414
18564
  if (!metadata.name) {
18415
- metadata.name = path11.basename(path11.dirname(filePath));
18565
+ metadata.name = path3.basename(path3.dirname(filePath));
18416
18566
  }
18417
18567
  if (!metadata.machineName) {
18418
- metadata.machineName = path11.basename(path11.dirname(filePath));
18568
+ metadata.machineName = path3.basename(path3.dirname(filePath));
18419
18569
  }
18420
18570
  if (!metadata.slots || typeof metadata.slots !== "object") {
18421
18571
  metadata.slots = {};
@@ -18585,12 +18735,17 @@ function uploadCommand(program2) {
18585
18735
  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
18736
  "-c, --components <names>",
18587
18737
  "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) => {
18738
+ ).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
18739
  const allFlag = options.all || options.yes && !options.components || false;
18590
18740
  const skipTailwind = !options.tailwind;
18591
18741
  try {
18592
18742
  p.intro(chalk2.bold("Drupal Canvas CLI: upload"));
18593
18743
  validateComponentOptions(options);
18744
+ if (options.skipCss && options.cssOnly) {
18745
+ throw new Error(
18746
+ "Cannot use both --skip-css and --css-only flags together"
18747
+ );
18748
+ }
18594
18749
  updateConfigFromOptions(options);
18595
18750
  await ensureConfig([
18596
18751
  "siteUrl",
@@ -18600,22 +18755,29 @@ function uploadCommand(program2) {
18600
18755
  "componentDir"
18601
18756
  ]);
18602
18757
  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
- );
18758
+ const { directories: componentsToUpload, includeGlobalCss } = await selectLocalComponents({
18759
+ all: allFlag,
18760
+ components: options.components,
18761
+ skipConfirmation: options.yes,
18762
+ skipCss: options.skipCss,
18763
+ cssOnly: options.cssOnly,
18764
+ includeGlobalCss: !options.skipCss,
18765
+ globalCssDefault: true,
18766
+ selectMessage: "Select items to upload"
18767
+ });
18611
18768
  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);
18769
+ await apiService.listComponents();
18770
+ let componentResults = [];
18771
+ if (!options.cssOnly && componentsToUpload.length > 0) {
18772
+ componentResults = await getBuildAndUploadResults(
18773
+ componentsToUpload,
18774
+ apiService,
18775
+ includeGlobalCss ?? false
18776
+ );
18777
+ reportResults(componentResults, "Uploaded components", "Component");
18778
+ if (componentResults.some((result) => !result.success)) {
18779
+ process.exit(1);
18780
+ }
18619
18781
  }
18620
18782
  if (skipTailwind) {
18621
18783
  p.log.info("Skipping Tailwind CSS build");
@@ -18623,7 +18785,9 @@ function uploadCommand(program2) {
18623
18785
  const s2 = p.spinner();
18624
18786
  s2.start("Building Tailwind CSS");
18625
18787
  const tailwindResult = await buildTailwindForComponents(
18626
- componentsToUpload
18788
+ componentsToUpload,
18789
+ includeGlobalCss
18790
+ // Use local CSS if includeGlobalCss is true
18627
18791
  );
18628
18792
  const componentLabelPluralized = pluralizeComponent(
18629
18793
  componentsToUpload.length
@@ -18639,14 +18803,20 @@ function uploadCommand(program2) {
18639
18803
  chalk2.red(`Tailwind build failed, global assets upload aborted.`)
18640
18804
  );
18641
18805
  } else {
18642
- const globalCssResult = await uploadGlobalAssetLibrary(
18643
- apiService,
18644
- config2.componentDir
18645
- );
18646
- reportResults([globalCssResult], "Uploaded assets", "Asset");
18806
+ if (includeGlobalCss) {
18807
+ const globalCssResult = await uploadGlobalAssetLibrary(
18808
+ apiService,
18809
+ config2.componentDir
18810
+ );
18811
+ reportResults([globalCssResult], "Uploaded assets", "Asset");
18812
+ } else {
18813
+ p.log.info("Skipping global CSS upload");
18814
+ }
18647
18815
  }
18648
18816
  }
18649
- p.outro("\u2B06\uFE0F Upload command completed");
18817
+ const componentCount = componentsToUpload.length;
18818
+ 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";
18819
+ p.outro(outroMessage);
18650
18820
  } catch (error) {
18651
18821
  if (error instanceof Error) {
18652
18822
  p.note(chalk2.red(`Error: ${error.message}`));
@@ -18662,11 +18832,11 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18662
18832
  const failed = [];
18663
18833
  for (const buildResult of successfulBuilds) {
18664
18834
  const dir = buildResult.itemName ? componentsToUpload.find(
18665
- (d2) => path11.basename(d2) === buildResult.itemName
18835
+ (d2) => path3.basename(d2) === buildResult.itemName
18666
18836
  ) : void 0;
18667
18837
  if (!dir) continue;
18668
18838
  try {
18669
- const componentName = path11.basename(dir);
18839
+ const componentName = path3.basename(dir);
18670
18840
  const { sourceCodeJs, compiledJs, sourceCodeCss, compiledCss, metadata } = await processComponentFiles(dir);
18671
18841
  if (!metadata) {
18672
18842
  throw new Error("Invalid metadata file");
@@ -18719,11 +18889,14 @@ async function prepareComponentsForUpload(successfulBuilds, componentsToUpload)
18719
18889
  }
18720
18890
  return { prepared, failed };
18721
18891
  }
18722
- async function getBuildAndUploadResults(componentsToUpload, apiService) {
18892
+ async function getBuildAndUploadResults(componentsToUpload, apiService, includeGlobalCss) {
18723
18893
  const results = [];
18724
18894
  const spinner6 = p.spinner();
18725
18895
  spinner6.start("Building components");
18726
- const buildResults = await buildSelectedComponents(componentsToUpload);
18896
+ const buildResults = await buildSelectedComponents(
18897
+ componentsToUpload,
18898
+ includeGlobalCss
18899
+ );
18727
18900
  const successfulBuilds = buildResults.filter((build) => build.success);
18728
18901
  const failedBuilds = buildResults.filter((build) => !build.success);
18729
18902
  if (successfulBuilds.length === 0) {
@@ -18780,12 +18953,13 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18780
18953
  ]
18781
18954
  });
18782
18955
  } else {
18956
+ const errorMessage = uploadResult.error?.message || "Unknown upload error";
18783
18957
  results.push({
18784
18958
  itemName: component.componentName,
18785
18959
  success: false,
18786
18960
  details: [
18787
18961
  {
18788
- content: uploadResult.error?.message || "Unknown upload error"
18962
+ content: errorMessage.trim() || "Unknown upload error"
18789
18963
  }
18790
18964
  ]
18791
18965
  });
@@ -18798,29 +18972,28 @@ async function getBuildAndUploadResults(componentsToUpload, apiService) {
18798
18972
  );
18799
18973
  return results;
18800
18974
  }
18801
- async function buildSelectedComponents(componentDirs) {
18975
+ async function buildSelectedComponents(componentDirs, useLocalGlobalCss = true) {
18802
18976
  const buildResults = [];
18803
18977
  for (const dir of componentDirs) {
18804
- buildResults.push(await buildComponent(dir));
18978
+ buildResults.push(await buildComponent(dir, useLocalGlobalCss));
18805
18979
  }
18806
18980
  return buildResults;
18807
18981
  }
18808
18982
  async function uploadGlobalAssetLibrary(apiService, componentDir) {
18809
18983
  try {
18810
- const distDir = path11.join(componentDir, "dist");
18811
- const globalCompiledCssPath = path11.join(distDir, "index.css");
18984
+ const distDir = path3.join(componentDir, "dist");
18985
+ const globalCompiledCssPath = path3.join(distDir, "index.css");
18812
18986
  const globalCompiledCssExists = await fileExists(globalCompiledCssPath);
18813
18987
  if (globalCompiledCssExists) {
18814
18988
  const globalCompiledCss = await fs2.readFile(
18815
- path11.join(distDir, "index.css"),
18989
+ path3.join(distDir, "index.css"),
18816
18990
  "utf-8"
18817
18991
  );
18818
18992
  const classNameCandidateIndexFile = await fs2.readFile(
18819
- path11.join(distDir, "index.js"),
18993
+ path3.join(distDir, "index.js"),
18820
18994
  "utf-8"
18821
18995
  );
18822
- const current = await apiService.getGlobalAssetLibrary();
18823
- const originalCss = current.css.original;
18996
+ const originalCss = await getGlobalCss();
18824
18997
  await apiService.updateGlobalAssetLibrary({
18825
18998
  css: {
18826
18999
  original: originalCss,
@@ -18866,7 +19039,7 @@ function validateCommand(program2) {
18866
19039
  ).option(
18867
19040
  "-c, --components <names>",
18868
19041
  "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(
19042
+ ).option("--all", "Validate all components").option("-y, --yes", "Skip confirmation prompts").option(
18870
19043
  "--fix",
18871
19044
  "Apply available automatic fixes for linting issues",
18872
19045
  false
@@ -18917,7 +19090,7 @@ function validateCommand(program2) {
18917
19090
  // src/index.ts
18918
19091
  var version = package_default.version;
18919
19092
  var program = new Command();
18920
- program.name("canvas").description("CLI tool for managing Drupal Canvas code components").version(version);
19093
+ program.name("canvas").description("CLI tool for managing Drupal Canvas code components").version(version ?? "0.0.0");
18921
19094
  downloadCommand(program);
18922
19095
  scaffoldCommand(program);
18923
19096
  uploadCommand(program);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@drupal-canvas/cli",
3
- "version": "0.6.0",
4
3
  "description": "CLI tool for managing Drupal Canvas code components",
4
+ "version": "0.6.1",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "repository": {
7
7
  "type": "git",
@@ -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",