(
return (
diff --git a/packages/canon/src/components/Text/styles.css b/packages/canon/src/components/Text/styles.css
index 9bf22ccab5..a338c6bca1 100644
--- a/packages/canon/src/components/Text/styles.css
+++ b/packages/canon/src/components/Text/styles.css
@@ -14,37 +14,37 @@
* limitations under the License.
*/
-.text {
+.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
padding: 0;
margin: 0;
-
- &.text-body {
- font-size: var(--canon-font-size-body);
- line-height: 140%;
- }
-
- &.text-subtitle {
- font-size: var(--canon-font-size-subtitle);
- line-height: 140%;
- }
-
- &.text-caption {
- font-size: var(--canon-font-size-caption);
- line-height: 140%;
- }
-
- &.text-label {
- font-size: var(--canon-font-size-label);
- line-height: 140%;
- }
-
- &.text-regular {
- font-weight: var(--canon-font-weight-regular);
- }
-
- &.text-bold {
- font-weight: var(--canon-font-weight-bold);
- }
+}
+
+.canon-Text--variant-body {
+ font-size: var(--canon-font-size-body);
+ line-height: 140%;
+}
+
+.canon-Text--variant-subtitle {
+ font-size: var(--canon-font-size-subtitle);
+ line-height: 140%;
+}
+
+.canon-Text--variant-caption {
+ font-size: var(--canon-font-size-caption);
+ line-height: 140%;
+}
+
+.canon-Text--variant-label {
+ font-size: var(--canon-font-size-label);
+ line-height: 140%;
+}
+
+.canon-Text--weight-regular {
+ font-weight: var(--canon-font-weight-regular);
+}
+
+.canon-Text--weight-bold {
+ font-weight: var(--canon-font-weight-bold);
}
diff --git a/packages/canon/src/components/Text/types.ts b/packages/canon/src/components/Text/types.ts
index 7e11bed6e3..4fdc824120 100644
--- a/packages/canon/src/components/Text/types.ts
+++ b/packages/canon/src/components/Text/types.ts
@@ -27,5 +27,6 @@ export interface TextProps {
| 'label'
| Partial>;
weight?: 'regular' | 'bold' | Partial>;
+ className?: string;
style?: CSSProperties;
}
diff --git a/packages/canon/src/contexts/canon.tsx b/packages/canon/src/contexts/canon.tsx
index 5a0e6b4315..92b36ce396 100644
--- a/packages/canon/src/contexts/canon.tsx
+++ b/packages/canon/src/contexts/canon.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+'use client';
+
import React, { createContext, useContext, ReactNode } from 'react';
import { IconMap, IconNames } from '../components/Icon/types';
import { icons } from '../components/Icon/icons';
diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css
index ed602b7ab7..b84ce7702b 100644
--- a/packages/canon/src/css/components.css
+++ b/packages/canon/src/css/components.css
@@ -15,6 +15,7 @@
*/
/* Components */
+@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/Stack/styles.css';
@import '../components/Inline/styles.css';
@@ -25,3 +26,5 @@
@import '../components/Table/styles.css';
@import '../components/Text/styles.css';
@import '../components/Heading/styles.css';
+@import '../components/Input/Input.styles.css';
+@import '../components/Field/Field.styles.css';
diff --git a/packages/canon/src/css/core.css b/packages/canon/src/css/core.css
index 4f15d571c7..abba9f3d9c 100644
--- a/packages/canon/src/css/core.css
+++ b/packages/canon/src/css/core.css
@@ -20,11 +20,9 @@
/* Light theme tokens */
-[data-theme='light'] {
+:root {
/* Font families */
- --canon-font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
- 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
- 'Segoe UI Symbol', 'Noto Color Emoji';
+ --canon-font-regular: system-ui;
--canon-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas',
'Liberation Mono', 'Courier New', monospace;
@@ -45,17 +43,26 @@
--canon-font-size-display: 5.75rem; /* 92px */
/* Spacing */
- --canon-spacing-5xs: 0.125rem; /* 2px */
- --canon-spacing-4xs: 0.25rem; /* 4px */
- --canon-spacing-3xs: 0.375rem; /* 6px */
- --canon-spacing-2xs: 0.5rem; /* 8px */
- --canon-spacing-xs: 0.75rem; /* 12px */
- --canon-spacing-sm: 1rem; /* 16px */
- --canon-spacing-md: 1.5rem; /* 24px */
- --canon-spacing-lg: 2rem; /* 32px */
- --canon-spacing-xl: 2.5rem; /* 40px */
- --canon-spacing-2xl: 3rem; /* 48px */
- --canon-spacing-3xl: 3.5rem; /* 56px */
+ /* This is the spacing multiplier */
+ --canon-space: 0.25rem; /* 4px */
+
+ /* These are the spacing scale */
+ --canon-space-0_5: calc(var(--canon-space) * 0.5); /* 2px */
+ --canon-space-1: var(--canon-space); /* 4px */
+ --canon-space-1_5: calc(var(--canon-space) * 1.5); /* 6px */
+ --canon-space-2: calc(var(--canon-space) * 2); /* 8px */
+ --canon-space-3: calc(var(--canon-space) * 3); /* 12px */
+ --canon-space-4: calc(var(--canon-space) * 4); /* 16px */
+ --canon-space-5: calc(var(--canon-space) * 5); /* 20px */
+ --canon-space-6: calc(var(--canon-space) * 6); /* 24px */
+ --canon-space-7: calc(var(--canon-space) * 7); /* 28px */
+ --canon-space-8: calc(var(--canon-space) * 8); /* 32px */
+ --canon-space-9: calc(var(--canon-space) * 9); /* 36px */
+ --canon-space-10: calc(var(--canon-space) * 10); /* 40px */
+ --canon-space-11: calc(var(--canon-space) * 11); /* 44px */
+ --canon-space-12: calc(var(--canon-space) * 12); /* 48px */
+ --canon-space-13: calc(var(--canon-space) * 13); /* 52px */
+ --canon-space-14: calc(var(--canon-space) * 14); /* 56px */
/* Border radius */
--canon-border-radius-2xs: 0.125rem; /* 2px */
@@ -66,10 +73,6 @@
--canon-border-radius-xl: 1.25rem; /* 20px */
--canon-border-radius-2xl: 1.5rem; /* 24px */
- /* Container */
- --canon-container-max-width: 1200px;
- --canon-container-padding: 1rem;
-
/* Colors */
--canon-accent: #000;
--canon-background: #f8f8f8;
@@ -82,7 +85,7 @@
--canon-border-hover: rgba(0, 0, 0, 0.2);
--canon-border-warning: #e36d05;
--canon-border-error: #e22b2b;
- --canon-border-selected: #1db954;
+ --canon-border-selected: rgba(0, 0, 0, 0.4);
/* States - Add more states */
--canon-error: #f50000;
@@ -95,55 +98,6 @@
/* Dark theme tokens */
[data-theme='dark'] {
- /* Font families */
- --canon-font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
- 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
- 'Segoe UI Symbol', 'Noto Color Emoji';
- --canon-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas',
- 'Liberation Mono', 'Courier New', monospace;
-
- /* Font weights */
- --canon-font-weight-regular: 400;
- --canon-font-weight-bold: 600;
-
- /* Font sizes */
- --canon-font-size-label: 0.625rem; /* 10px */
- --canon-font-size-caption: 0.75rem; /* 12px */
- --canon-font-size-body: 0.875rem; /* 14px */
- --canon-font-size-subtitle: 1rem; /* 16px */
- --canon-font-size-title5: 1.25rem; /* 20px */
- --canon-font-size-title4: 1.5rem; /* 24px */
- --canon-font-size-title3: 2rem; /* 32px */
- --canon-font-size-title2: 3rem; /* 48px */
- --canon-font-size-title1: 4rem; /* 64px */
- --canon-font-size-display: 5.75rem; /* 92px */
-
- /* Spacing */
- --canon-spacing-5xs: 0.125rem; /* 2px */
- --canon-spacing-4xs: 0.25rem; /* 4px */
- --canon-spacing-3xs: 0.375rem; /* 6px */
- --canon-spacing-2xs: 0.5rem; /* 8px */
- --canon-spacing-xs: 0.75rem; /* 12px */
- --canon-spacing-sm: 1rem; /* 16px */
- --canon-spacing-md: 1.5rem; /* 24px */
- --canon-spacing-lg: 2rem; /* 32px */
- --canon-spacing-xl: 2.5rem; /* 40px */
- --canon-spacing-2xl: 3rem; /* 48px */
- --canon-spacing-3xl: 3.5rem; /* 56px */
-
- /* Border radius */
- --canon-border-radius-2xs: 0.125rem; /* 2px */
- --canon-border-radius-xs: 0.25rem; /* 4px */
- --canon-border-radius-sm: 0.5rem; /* 8px */
- --canon-border-radius-md: 0.75rem; /* 12px */
- --canon-border-radius-lg: 1rem; /* 16px */
- --canon-border-radius-xl: 1.25rem; /* 20px */
- --canon-border-radius-2xl: 1.5rem; /* 24px */
-
- /* Container */
- --canon-container-max-width: 1200px;
- --canon-container-padding: 1rem;
-
/* Colors */
--canon-accent: #fff;
--canon-background: #000;
@@ -152,9 +106,10 @@
/* Borders */
--canon-border-base: rgba(255, 255, 255, 0.2);
+ --canon-border-hover: rgba(255, 255, 255, 0.3);
--canon-border-warning: #f50000;
--canon-border-error: #f87503;
- --canon-border-selected: #25d262;
+ --canon-border-selected: rgba(255, 255, 255, 0.4);
/* States - Add more states */
--canon-error: #f50000;
diff --git a/packages/canon/src/css/utilities/2xl.css b/packages/canon/src/css/utilities/2xl.css
index 4ea3d82db0..bbf51c3af6 100644
--- a/packages/canon/src/css/utilities/2xl.css
+++ b/packages/canon/src/css/utilities/2xl.css
@@ -238,15 +238,15 @@
}
.cu-2xl-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-2xl-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-2xl-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-2xl-gap-none {
@@ -254,15 +254,15 @@
}
.cu-2xl-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-2xl-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-2xl-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-2xl-grid {
@@ -374,19 +374,19 @@
}
.cu-2xl-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-2xl-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-2xl-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-2xl-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-2xl-m-none {
@@ -394,31 +394,31 @@
}
.cu-2xl-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-2xl-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-2xl-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-2xl-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-2xl-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-2xl-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-2xl-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-2xl-mb-none {
@@ -426,31 +426,31 @@
}
.cu-2xl-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-2xl-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-2xl-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-2xl-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-2xl-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-2xl-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-2xl-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-2xl-ml-none {
@@ -458,31 +458,31 @@
}
.cu-2xl-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-2xl-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-2xl-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-2xl-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-2xl-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-2xl-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-2xl-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-2xl-mr-none {
@@ -490,31 +490,31 @@
}
.cu-2xl-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-2xl-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-2xl-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-2xl-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-2xl-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-2xl-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-2xl-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-2xl-mt-none {
@@ -522,35 +522,35 @@
}
.cu-2xl-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-2xl-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-2xl-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-2xl-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-2xl-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-2xl-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-2xl-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-2xl-mx-none {
@@ -559,38 +559,38 @@
}
.cu-2xl-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-2xl-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-2xl-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-2xl-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-2xl-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-2xl-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-2xl-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-2xl-my-none {
@@ -599,34 +599,34 @@
}
.cu-2xl-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-2xl-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-2xl-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-2xl-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-2xl-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-2xl-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-2xl-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-2xl-p-none {
@@ -634,31 +634,31 @@
}
.cu-2xl-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-2xl-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-2xl-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-2xl-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-2xl-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-2xl-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-2xl-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-2xl-pb-none {
@@ -666,31 +666,31 @@
}
.cu-2xl-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-2xl-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-2xl-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-2xl-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-2xl-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-2xl-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-2xl-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-2xl-pl-none {
@@ -698,31 +698,31 @@
}
.cu-2xl-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-2xl-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-2xl-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-2xl-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-2xl-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-2xl-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-2xl-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-2xl-pr-none {
@@ -730,31 +730,31 @@
}
.cu-2xl-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-2xl-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-2xl-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-2xl-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-2xl-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-2xl-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-2xl-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-2xl-pt-none {
@@ -762,35 +762,35 @@
}
.cu-2xl-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-2xl-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-2xl-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-2xl-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-2xl-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-2xl-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-2xl-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-2xl-px-none {
@@ -799,38 +799,38 @@
}
.cu-2xl-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-2xl-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-2xl-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-2xl-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-2xl-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-2xl-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-2xl-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-2xl-py-none {
@@ -839,18 +839,18 @@
}
.cu-2xl-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-2xl-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-2xl-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-2xl-rounded-2xl {
diff --git a/packages/canon/src/css/utilities/lg.css b/packages/canon/src/css/utilities/lg.css
index caa2adb310..e2bb93a355 100644
--- a/packages/canon/src/css/utilities/lg.css
+++ b/packages/canon/src/css/utilities/lg.css
@@ -238,15 +238,15 @@
}
.cu-lg-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-lg-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-lg-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-lg-gap-none {
@@ -254,15 +254,15 @@
}
.cu-lg-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-lg-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-lg-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-lg-grid {
@@ -374,19 +374,19 @@
}
.cu-lg-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-lg-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-lg-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-lg-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-lg-m-none {
@@ -394,31 +394,31 @@
}
.cu-lg-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-lg-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-lg-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-lg-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-lg-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-lg-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-lg-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-lg-mb-none {
@@ -426,31 +426,31 @@
}
.cu-lg-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-lg-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-lg-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-lg-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-lg-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-lg-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-lg-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-lg-ml-none {
@@ -458,31 +458,31 @@
}
.cu-lg-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-lg-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-lg-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-lg-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-lg-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-lg-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-lg-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-lg-mr-none {
@@ -490,31 +490,31 @@
}
.cu-lg-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-lg-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-lg-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-lg-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-lg-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-lg-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-lg-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-lg-mt-none {
@@ -522,35 +522,35 @@
}
.cu-lg-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-lg-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-lg-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-lg-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-lg-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-lg-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-lg-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-lg-mx-none {
@@ -559,38 +559,38 @@
}
.cu-lg-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-lg-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-lg-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-lg-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-lg-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-lg-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-lg-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-lg-my-none {
@@ -599,34 +599,34 @@
}
.cu-lg-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-lg-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-lg-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-lg-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-lg-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-lg-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-lg-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-lg-p-none {
@@ -634,31 +634,31 @@
}
.cu-lg-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-lg-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-lg-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-lg-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-lg-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-lg-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-lg-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-lg-pb-none {
@@ -666,31 +666,31 @@
}
.cu-lg-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-lg-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-lg-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-lg-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-lg-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-lg-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-lg-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-lg-pl-none {
@@ -698,31 +698,31 @@
}
.cu-lg-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-lg-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-lg-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-lg-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-lg-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-lg-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-lg-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-lg-pr-none {
@@ -730,31 +730,31 @@
}
.cu-lg-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-lg-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-lg-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-lg-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-lg-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-lg-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-lg-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-lg-pt-none {
@@ -762,35 +762,35 @@
}
.cu-lg-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-lg-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-lg-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-lg-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-lg-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-lg-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-lg-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-lg-px-none {
@@ -799,38 +799,38 @@
}
.cu-lg-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-lg-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-lg-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-lg-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-lg-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-lg-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-lg-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-lg-py-none {
@@ -839,18 +839,18 @@
}
.cu-lg-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-lg-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-lg-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-lg-rounded-2xl {
diff --git a/packages/canon/src/css/utilities/md.css b/packages/canon/src/css/utilities/md.css
index fe6d56ad7c..19842e2eab 100644
--- a/packages/canon/src/css/utilities/md.css
+++ b/packages/canon/src/css/utilities/md.css
@@ -238,15 +238,15 @@
}
.cu-md-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-md-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-md-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-md-gap-none {
@@ -254,15 +254,15 @@
}
.cu-md-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-md-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-md-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-md-grid {
@@ -374,19 +374,19 @@
}
.cu-md-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-md-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-md-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-md-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-md-m-none {
@@ -394,31 +394,31 @@
}
.cu-md-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-md-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-md-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-md-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-md-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-md-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-md-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-md-mb-none {
@@ -426,31 +426,31 @@
}
.cu-md-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-md-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-md-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-md-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-md-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-md-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-md-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-md-ml-none {
@@ -458,31 +458,31 @@
}
.cu-md-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-md-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-md-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-md-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-md-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-md-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-md-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-md-mr-none {
@@ -490,31 +490,31 @@
}
.cu-md-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-md-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-md-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-md-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-md-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-md-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-md-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-md-mt-none {
@@ -522,35 +522,35 @@
}
.cu-md-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-md-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-md-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-md-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-md-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-md-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-md-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-md-mx-none {
@@ -559,38 +559,38 @@
}
.cu-md-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-md-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-md-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-md-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-md-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-md-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-md-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-md-my-none {
@@ -599,34 +599,34 @@
}
.cu-md-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-md-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-md-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-md-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-md-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-md-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-md-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-md-p-none {
@@ -634,31 +634,31 @@
}
.cu-md-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-md-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-md-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-md-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-md-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-md-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-md-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-md-pb-none {
@@ -666,31 +666,31 @@
}
.cu-md-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-md-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-md-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-md-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-md-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-md-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-md-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-md-pl-none {
@@ -698,31 +698,31 @@
}
.cu-md-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-md-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-md-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-md-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-md-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-md-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-md-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-md-pr-none {
@@ -730,31 +730,31 @@
}
.cu-md-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-md-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-md-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-md-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-md-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-md-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-md-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-md-pt-none {
@@ -762,35 +762,35 @@
}
.cu-md-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-md-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-md-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-md-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-md-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-md-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-md-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-md-px-none {
@@ -799,38 +799,38 @@
}
.cu-md-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-md-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-md-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-md-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-md-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-md-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-md-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-md-py-none {
@@ -839,18 +839,18 @@
}
.cu-md-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-md-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-md-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-md-rounded-2xl {
diff --git a/packages/canon/src/css/utilities/sm.css b/packages/canon/src/css/utilities/sm.css
index 87657c2222..7a4e9a0cc7 100644
--- a/packages/canon/src/css/utilities/sm.css
+++ b/packages/canon/src/css/utilities/sm.css
@@ -238,15 +238,15 @@
}
.cu-sm-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-sm-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-sm-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-sm-gap-none {
@@ -254,15 +254,15 @@
}
.cu-sm-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-sm-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-sm-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-sm-grid {
@@ -374,19 +374,19 @@
}
.cu-sm-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-sm-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-sm-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-sm-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-sm-m-none {
@@ -394,31 +394,31 @@
}
.cu-sm-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-sm-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-sm-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-sm-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-sm-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-sm-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-sm-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-sm-mb-none {
@@ -426,31 +426,31 @@
}
.cu-sm-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-sm-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-sm-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-sm-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-sm-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-sm-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-sm-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-sm-ml-none {
@@ -458,31 +458,31 @@
}
.cu-sm-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-sm-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-sm-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-sm-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-sm-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-sm-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-sm-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-sm-mr-none {
@@ -490,31 +490,31 @@
}
.cu-sm-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-sm-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-sm-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-sm-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-sm-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-sm-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-sm-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-sm-mt-none {
@@ -522,35 +522,35 @@
}
.cu-sm-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-sm-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-sm-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-sm-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-sm-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-sm-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-sm-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-sm-mx-none {
@@ -559,38 +559,38 @@
}
.cu-sm-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-sm-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-sm-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-sm-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-sm-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-sm-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-sm-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-sm-my-none {
@@ -599,34 +599,34 @@
}
.cu-sm-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-sm-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-sm-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-sm-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-sm-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-sm-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-sm-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-sm-p-none {
@@ -634,31 +634,31 @@
}
.cu-sm-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-sm-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-sm-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-sm-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-sm-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-sm-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-sm-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-sm-pb-none {
@@ -666,31 +666,31 @@
}
.cu-sm-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-sm-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-sm-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-sm-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-sm-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-sm-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-sm-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-sm-pl-none {
@@ -698,31 +698,31 @@
}
.cu-sm-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-sm-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-sm-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-sm-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-sm-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-sm-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-sm-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-sm-pr-none {
@@ -730,31 +730,31 @@
}
.cu-sm-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-sm-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-sm-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-sm-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-sm-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-sm-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-sm-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-sm-pt-none {
@@ -762,35 +762,35 @@
}
.cu-sm-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-sm-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-sm-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-sm-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-sm-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-sm-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-sm-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-sm-px-none {
@@ -799,38 +799,38 @@
}
.cu-sm-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-sm-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-sm-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-sm-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-sm-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-sm-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-sm-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-sm-py-none {
@@ -839,18 +839,18 @@
}
.cu-sm-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-sm-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-sm-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-sm-rounded-2xl {
diff --git a/packages/canon/src/css/utilities/xl.css b/packages/canon/src/css/utilities/xl.css
index 7d9e3b8add..b4d0b3b3d3 100644
--- a/packages/canon/src/css/utilities/xl.css
+++ b/packages/canon/src/css/utilities/xl.css
@@ -238,15 +238,15 @@
}
.cu-xl-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-xl-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-xl-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-xl-gap-none {
@@ -254,15 +254,15 @@
}
.cu-xl-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-xl-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-xl-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-xl-grid {
@@ -374,19 +374,19 @@
}
.cu-xl-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-xl-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-xl-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-xl-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-xl-m-none {
@@ -394,31 +394,31 @@
}
.cu-xl-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-xl-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-xl-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-xl-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-xl-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-xl-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-xl-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-xl-mb-none {
@@ -426,31 +426,31 @@
}
.cu-xl-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-xl-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-xl-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-xl-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-xl-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-xl-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-xl-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-xl-ml-none {
@@ -458,31 +458,31 @@
}
.cu-xl-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-xl-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-xl-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-xl-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-xl-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-xl-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-xl-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-xl-mr-none {
@@ -490,31 +490,31 @@
}
.cu-xl-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-xl-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-xl-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-xl-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-xl-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-xl-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-xl-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-xl-mt-none {
@@ -522,35 +522,35 @@
}
.cu-xl-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-xl-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-xl-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-xl-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-xl-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-xl-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-xl-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-xl-mx-none {
@@ -559,38 +559,38 @@
}
.cu-xl-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-xl-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-xl-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-xl-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-xl-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-xl-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-xl-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-xl-my-none {
@@ -599,34 +599,34 @@
}
.cu-xl-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-xl-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-xl-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-xl-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-xl-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-xl-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-xl-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-xl-p-none {
@@ -634,31 +634,31 @@
}
.cu-xl-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-xl-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-xl-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-xl-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-xl-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-xl-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-xl-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-xl-pb-none {
@@ -666,31 +666,31 @@
}
.cu-xl-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-xl-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-xl-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-xl-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-xl-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-xl-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-xl-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-xl-pl-none {
@@ -698,31 +698,31 @@
}
.cu-xl-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-xl-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-xl-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-xl-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-xl-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-xl-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-xl-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-xl-pr-none {
@@ -730,31 +730,31 @@
}
.cu-xl-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-xl-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-xl-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-xl-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-xl-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-xl-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-xl-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-xl-pt-none {
@@ -762,35 +762,35 @@
}
.cu-xl-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-xl-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-xl-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-xl-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-xl-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-xl-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-xl-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-xl-px-none {
@@ -799,38 +799,38 @@
}
.cu-xl-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-xl-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-xl-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-xl-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-xl-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-xl-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-xl-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-xl-py-none {
@@ -839,18 +839,18 @@
}
.cu-xl-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-xl-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-xl-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-xl-rounded-2xl {
diff --git a/packages/canon/src/css/utilities/xs.css b/packages/canon/src/css/utilities/xs.css
index 926a2cc627..37a56a54c4 100644
--- a/packages/canon/src/css/utilities/xs.css
+++ b/packages/canon/src/css/utilities/xs.css
@@ -237,15 +237,15 @@
}
.cu-gap-2xl {
- gap: var(--canon-spacing-2xl);
+ gap: var(--canon-space-12);
}
.cu-gap-lg {
- gap: var(--canon-spacing-lg);
+ gap: var(--canon-space-8);
}
.cu-gap-md {
- gap: var(--canon-spacing-md);
+ gap: var(--canon-space-6);
}
.cu-gap-none {
@@ -253,15 +253,15 @@
}
.cu-gap-sm {
- gap: var(--canon-spacing-sm);
+ gap: var(--canon-space-4);
}
.cu-gap-xl {
- gap: var(--canon-spacing-xl);
+ gap: var(--canon-space-10);
}
.cu-gap-xs {
- gap: var(--canon-spacing-xs);
+ gap: var(--canon-space-3);
}
.cu-grid {
@@ -373,19 +373,19 @@
}
.cu-m-2xl {
- margin: var(--canon-spacing-2xl);
+ margin: var(--canon-space-12);
}
.cu-m-2xs {
- margin: var(--canon-spacing-2xs);
+ margin: var(--canon-space-2);
}
.cu-m-lg {
- margin: var(--canon-spacing-lg);
+ margin: var(--canon-space-8);
}
.cu-m-md {
- margin: var(--canon-spacing-md);
+ margin: var(--canon-space-6);
}
.cu-m-none {
@@ -393,31 +393,31 @@
}
.cu-m-sm {
- margin: var(--canon-spacing-sm);
+ margin: var(--canon-space-4);
}
.cu-m-xl {
- margin: var(--canon-spacing-xl);
+ margin: var(--canon-space-10);
}
.cu-m-xs {
- margin: var(--canon-spacing-xs);
+ margin: var(--canon-space-3);
}
.cu-mb-2xl {
- margin-bottom: var(--canon-spacing-2xl);
+ margin-bottom: var(--canon-space-12);
}
.cu-mb-2xs {
- margin-bottom: var(--canon-spacing-2xs);
+ margin-bottom: var(--canon-space-2);
}
.cu-mb-lg {
- margin-bottom: var(--canon-spacing-lg);
+ margin-bottom: var(--canon-space-8);
}
.cu-mb-md {
- margin-bottom: var(--canon-spacing-md);
+ margin-bottom: var(--canon-space-6);
}
.cu-mb-none {
@@ -425,31 +425,31 @@
}
.cu-mb-sm {
- margin-bottom: var(--canon-spacing-sm);
+ margin-bottom: var(--canon-space-4);
}
.cu-mb-xl {
- margin-bottom: var(--canon-spacing-xl);
+ margin-bottom: var(--canon-space-10);
}
.cu-mb-xs {
- margin-bottom: var(--canon-spacing-xs);
+ margin-bottom: var(--canon-space-3);
}
.cu-ml-2xl {
- margin-left: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
}
.cu-ml-2xs {
- margin-left: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
}
.cu-ml-lg {
- margin-left: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
}
.cu-ml-md {
- margin-left: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
}
.cu-ml-none {
@@ -457,31 +457,31 @@
}
.cu-ml-sm {
- margin-left: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
}
.cu-ml-xl {
- margin-left: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
}
.cu-ml-xs {
- margin-left: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
}
.cu-mr-2xl {
- margin-right: var(--canon-spacing-2xl);
+ margin-right: var(--canon-space-12);
}
.cu-mr-2xs {
- margin-right: var(--canon-spacing-2xs);
+ margin-right: var(--canon-space-2);
}
.cu-mr-lg {
- margin-right: var(--canon-spacing-lg);
+ margin-right: var(--canon-space-8);
}
.cu-mr-md {
- margin-right: var(--canon-spacing-md);
+ margin-right: var(--canon-space-6);
}
.cu-mr-none {
@@ -489,31 +489,31 @@
}
.cu-mr-sm {
- margin-right: var(--canon-spacing-sm);
+ margin-right: var(--canon-space-4);
}
.cu-mr-xl {
- margin-right: var(--canon-spacing-xl);
+ margin-right: var(--canon-space-10);
}
.cu-mr-xs {
- margin-right: var(--canon-spacing-xs);
+ margin-right: var(--canon-space-3);
}
.cu-mt-2xl {
- margin-top: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
}
.cu-mt-2xs {
- margin-top: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
}
.cu-mt-lg {
- margin-top: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
}
.cu-mt-md {
- margin-top: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
}
.cu-mt-none {
@@ -521,35 +521,35 @@
}
.cu-mt-sm {
- margin-top: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
}
.cu-mt-xl {
- margin-top: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
}
.cu-mt-xs {
- margin-top: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
}
.cu-mx-2xl {
- margin-left: var(--canon-spacing-2xl);
- margin-right: var(--canon-spacing-2xl);
+ margin-left: var(--canon-space-12);
+ margin-right: var(--canon-space-12);
}
.cu-mx-2xs {
- margin-left: var(--canon-spacing-2xs);
- margin-right: var(--canon-spacing-2xs);
+ margin-left: var(--canon-space-2);
+ margin-right: var(--canon-space-2);
}
.cu-mx-lg {
- margin-left: var(--canon-spacing-lg);
- margin-right: var(--canon-spacing-lg);
+ margin-left: var(--canon-space-8);
+ margin-right: var(--canon-space-8);
}
.cu-mx-md {
- margin-left: var(--canon-spacing-md);
- margin-right: var(--canon-spacing-md);
+ margin-left: var(--canon-space-6);
+ margin-right: var(--canon-space-6);
}
.cu-mx-none {
@@ -558,38 +558,38 @@
}
.cu-mx-sm {
- margin-left: var(--canon-spacing-sm);
- margin-right: var(--canon-spacing-sm);
+ margin-left: var(--canon-space-4);
+ margin-right: var(--canon-space-4);
}
.cu-mx-xl {
- margin-left: var(--canon-spacing-xl);
- margin-right: var(--canon-spacing-xl);
+ margin-left: var(--canon-space-10);
+ margin-right: var(--canon-space-10);
}
.cu-mx-xs {
- margin-left: var(--canon-spacing-xs);
- margin-right: var(--canon-spacing-xs);
+ margin-left: var(--canon-space-3);
+ margin-right: var(--canon-space-3);
}
.cu-my-2xl {
- margin-top: var(--canon-spacing-2xl);
- margin-bottom: var(--canon-spacing-2xl);
+ margin-top: var(--canon-space-12);
+ margin-bottom: var(--canon-space-12);
}
.cu-my-2xs {
- margin-top: var(--canon-spacing-2xs);
- margin-bottom: var(--canon-spacing-2xs);
+ margin-top: var(--canon-space-2);
+ margin-bottom: var(--canon-space-2);
}
.cu-my-lg {
- margin-top: var(--canon-spacing-lg);
- margin-bottom: var(--canon-spacing-lg);
+ margin-top: var(--canon-space-8);
+ margin-bottom: var(--canon-space-8);
}
.cu-my-md {
- margin-top: var(--canon-spacing-md);
- margin-bottom: var(--canon-spacing-md);
+ margin-top: var(--canon-space-6);
+ margin-bottom: var(--canon-space-6);
}
.cu-my-none {
@@ -598,34 +598,34 @@
}
.cu-my-sm {
- margin-top: var(--canon-spacing-sm);
- margin-bottom: var(--canon-spacing-sm);
+ margin-top: var(--canon-space-4);
+ margin-bottom: var(--canon-space-4);
}
.cu-my-xl {
- margin-top: var(--canon-spacing-xl);
- margin-bottom: var(--canon-spacing-xl);
+ margin-top: var(--canon-space-10);
+ margin-bottom: var(--canon-space-10);
}
.cu-my-xs {
- margin-top: var(--canon-spacing-xs);
- margin-bottom: var(--canon-spacing-xs);
+ margin-top: var(--canon-space-3);
+ margin-bottom: var(--canon-space-3);
}
.cu-p-2xl {
- padding: var(--canon-spacing-2xl);
+ padding: var(--canon-space-12);
}
.cu-p-2xs {
- padding: var(--canon-spacing-2xs);
+ padding: var(--canon-space-2);
}
.cu-p-lg {
- padding: var(--canon-spacing-lg);
+ padding: var(--canon-space-8);
}
.cu-p-md {
- padding: var(--canon-spacing-md);
+ padding: var(--canon-space-6);
}
.cu-p-none {
@@ -633,31 +633,31 @@
}
.cu-p-sm {
- padding: var(--canon-spacing-sm);
+ padding: var(--canon-space-4);
}
.cu-p-xl {
- padding: var(--canon-spacing-xl);
+ padding: var(--canon-space-10);
}
.cu-p-xs {
- padding: var(--canon-spacing-xs);
+ padding: var(--canon-space-3);
}
.cu-pb-2xl {
- padding-bottom: var(--canon-spacing-2xl);
+ padding-bottom: var(--canon-space-12);
}
.cu-pb-2xs {
- padding-bottom: var(--canon-spacing-2xs);
+ padding-bottom: var(--canon-space-2);
}
.cu-pb-lg {
- padding-bottom: var(--canon-spacing-lg);
+ padding-bottom: var(--canon-space-8);
}
.cu-pb-md {
- padding-bottom: var(--canon-spacing-md);
+ padding-bottom: var(--canon-space-6);
}
.cu-pb-none {
@@ -665,31 +665,31 @@
}
.cu-pb-sm {
- padding-bottom: var(--canon-spacing-sm);
+ padding-bottom: var(--canon-space-4);
}
.cu-pb-xl {
- padding-bottom: var(--canon-spacing-xl);
+ padding-bottom: var(--canon-space-10);
}
.cu-pb-xs {
- padding-bottom: var(--canon-spacing-xs);
+ padding-bottom: var(--canon-space-3);
}
.cu-pl-2xl {
- padding-left: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
}
.cu-pl-2xs {
- padding-left: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
}
.cu-pl-lg {
- padding-left: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
}
.cu-pl-md {
- padding-left: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
}
.cu-pl-none {
@@ -697,31 +697,31 @@
}
.cu-pl-sm {
- padding-left: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
}
.cu-pl-xl {
- padding-left: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
}
.cu-pl-xs {
- padding-left: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
}
.cu-pr-2xl {
- padding-right: var(--canon-spacing-2xl);
+ padding-right: var(--canon-space-12);
}
.cu-pr-2xs {
- padding-right: var(--canon-spacing-2xs);
+ padding-right: var(--canon-space-2);
}
.cu-pr-lg {
- padding-right: var(--canon-spacing-lg);
+ padding-right: var(--canon-space-8);
}
.cu-pr-md {
- padding-right: var(--canon-spacing-md);
+ padding-right: var(--canon-space-6);
}
.cu-pr-none {
@@ -729,31 +729,31 @@
}
.cu-pr-sm {
- padding-right: var(--canon-spacing-sm);
+ padding-right: var(--canon-space-4);
}
.cu-pr-xl {
- padding-right: var(--canon-spacing-xl);
+ padding-right: var(--canon-space-10);
}
.cu-pr-xs {
- padding-right: var(--canon-spacing-xs);
+ padding-right: var(--canon-space-3);
}
.cu-pt-2xl {
- padding-top: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
}
.cu-pt-2xs {
- padding-top: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
}
.cu-pt-lg {
- padding-top: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
}
.cu-pt-md {
- padding-top: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
}
.cu-pt-none {
@@ -761,35 +761,35 @@
}
.cu-pt-sm {
- padding-top: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
}
.cu-pt-xl {
- padding-top: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
}
.cu-pt-xs {
- padding-top: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
}
.cu-px-2xl {
- padding-left: var(--canon-spacing-2xl);
- padding-right: var(--canon-spacing-2xl);
+ padding-left: var(--canon-space-12);
+ padding-right: var(--canon-space-12);
}
.cu-px-2xs {
- padding-left: var(--canon-spacing-2xs);
- padding-right: var(--canon-spacing-2xs);
+ padding-left: var(--canon-space-2);
+ padding-right: var(--canon-space-2);
}
.cu-px-lg {
- padding-left: var(--canon-spacing-lg);
- padding-right: var(--canon-spacing-lg);
+ padding-left: var(--canon-space-8);
+ padding-right: var(--canon-space-8);
}
.cu-px-md {
- padding-left: var(--canon-spacing-md);
- padding-right: var(--canon-spacing-md);
+ padding-left: var(--canon-space-6);
+ padding-right: var(--canon-space-6);
}
.cu-px-none {
@@ -798,38 +798,38 @@
}
.cu-px-sm {
- padding-left: var(--canon-spacing-sm);
- padding-right: var(--canon-spacing-sm);
+ padding-left: var(--canon-space-4);
+ padding-right: var(--canon-space-4);
}
.cu-px-xl {
- padding-left: var(--canon-spacing-xl);
- padding-right: var(--canon-spacing-xl);
+ padding-left: var(--canon-space-10);
+ padding-right: var(--canon-space-10);
}
.cu-px-xs {
- padding-left: var(--canon-spacing-xs);
- padding-right: var(--canon-spacing-xs);
+ padding-left: var(--canon-space-3);
+ padding-right: var(--canon-space-3);
}
.cu-py-2xl {
- padding-top: var(--canon-spacing-2xl);
- padding-bottom: var(--canon-spacing-2xl);
+ padding-top: var(--canon-space-12);
+ padding-bottom: var(--canon-space-12);
}
.cu-py-2xs {
- padding-top: var(--canon-spacing-2xs);
- padding-bottom: var(--canon-spacing-2xs);
+ padding-top: var(--canon-space-2);
+ padding-bottom: var(--canon-space-2);
}
.cu-py-lg {
- padding-top: var(--canon-spacing-lg);
- padding-bottom: var(--canon-spacing-lg);
+ padding-top: var(--canon-space-8);
+ padding-bottom: var(--canon-space-8);
}
.cu-py-md {
- padding-top: var(--canon-spacing-md);
- padding-bottom: var(--canon-spacing-md);
+ padding-top: var(--canon-space-6);
+ padding-bottom: var(--canon-space-6);
}
.cu-py-none {
@@ -838,18 +838,18 @@
}
.cu-py-sm {
- padding-top: var(--canon-spacing-sm);
- padding-bottom: var(--canon-spacing-sm);
+ padding-top: var(--canon-space-4);
+ padding-bottom: var(--canon-space-4);
}
.cu-py-xl {
- padding-top: var(--canon-spacing-xl);
- padding-bottom: var(--canon-spacing-xl);
+ padding-top: var(--canon-space-10);
+ padding-bottom: var(--canon-space-10);
}
.cu-py-xs {
- padding-top: var(--canon-spacing-xs);
- padding-bottom: var(--canon-spacing-xs);
+ padding-top: var(--canon-space-3);
+ padding-bottom: var(--canon-space-3);
}
.cu-rounded-2xl {
diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts
index f6cca4bed6..987aa959de 100644
--- a/packages/canon/src/index.ts
+++ b/packages/canon/src/index.ts
@@ -37,6 +37,7 @@ export * from './components/Button';
export * from './components/Icon';
export * from './components/Checkbox';
export * from './components/Table';
-
+export * from './components/Input';
+export * from './components/Field';
// Types
export * from './types';
diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md
index 96c511d97e..6d45557f52 100644
--- a/packages/catalog-client/CHANGELOG.md
+++ b/packages/catalog-client/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/catalog-client
+## 1.9.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
## 1.9.1-next.0
### Patch Changes
diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json
index 4dbd595089..e0c68750a4 100644
--- a/packages/catalog-client/package.json
+++ b/packages/catalog-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
- "version": "1.9.1-next.0",
+ "version": "1.9.1",
"description": "An isomorphic client for the catalog backend",
"backstage": {
"role": "common-library"
diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md
index a8447c64fe..daee1fbab0 100644
--- a/packages/catalog-model/CHANGELOG.md
+++ b/packages/catalog-model/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/catalog-model
+## 1.7.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/errors@1.2.7
+
## 1.7.3-next.0
### Patch Changes
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 0e14357671..743eccc50d 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "1.7.3-next.0",
+ "version": "1.7.3",
"description": "Types and validators that help describe the model of a Backstage Catalog",
"backstage": {
"role": "common-library"
diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md
index a2f6104715..054cfb6758 100644
--- a/packages/cli-node/CHANGELOG.md
+++ b/packages/cli-node/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/cli-node
+## 0.2.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
## 0.2.12-next.0
### Patch Changes
diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json
index 38e2fcd285..b2439a9b27 100644
--- a/packages/cli-node/package.json
+++ b/packages/cli-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli-node",
- "version": "0.2.12-next.0",
+ "version": "0.2.12",
"description": "Node.js library for Backstage CLIs",
"backstage": {
"role": "node-library"
diff --git a/packages/cli-node/report.api.md b/packages/cli-node/report.api.md
index 7c1a3bc744..bec25ea251 100644
--- a/packages/cli-node/report.api.md
+++ b/packages/cli-node/report.api.md
@@ -72,6 +72,8 @@ export interface BackstagePackageJson {
[key: string]: string;
};
// (undocumented)
+ type?: 'module' | 'commonjs';
+ // (undocumented)
types?: string;
// (undocumented)
typesVersions?: Record>;
diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts
index 721aa79e50..1da896a057 100644
--- a/packages/cli-node/src/monorepo/PackageGraph.ts
+++ b/packages/cli-node/src/monorepo/PackageGraph.ts
@@ -43,6 +43,8 @@ export interface BackstagePackageJson {
// that the package bundles all of its dependencies in its build output.
bundled?: boolean;
+ type?: 'module' | 'commonjs';
+
backstage?: {
role?: PackageRole;
moved?: string;
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index f2396cdd3b..8a99ad689b 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/cli
+## 0.29.5
+
+### Patch Changes
+
+- e937ce0: Fixed incompatible `@typescript-eslint` versions with current `eslint@8.x.x`
+- 8557e09: Removed the `EXPERIMENTAL_VITE` flag for using Vite as a dev server. If you were using this feature, we recommend switching to Rspack via the `EXPERIMENTAL_RSPACK` flag.
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/config-loader@1.9.5
+ - @backstage/integration@1.16.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.12
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+
## 0.29.5-next.1
### Patch Changes
diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js
index 45d744d7bc..560c9bf708 100644
--- a/packages/cli/config/jest.js
+++ b/packages/cli/config/jest.js
@@ -31,6 +31,14 @@ const FRONTEND_ROLES = [
'frontend-plugin-module',
];
+const NODE_ROLES = [
+ 'backend',
+ 'cli',
+ 'node-library',
+ 'backend-plugin',
+ 'backend-plugin-module',
+];
+
const envOptions = {
oldTests: Boolean(process.env.BACKSTAGE_OLD_TESTS),
};
@@ -130,11 +138,97 @@ const transformIgnorePattern = [
].join('|');
// Provides additional config that's based on the role of the target package
-function getRoleConfig(role) {
+function getRoleConfig(role, pkgJson) {
+ // Only Node.js package roles support native ESM modules, frontend and common
+ // packages are always transpiled to CommonJS.
+ const moduleOpts = NODE_ROLES.includes(role)
+ ? {
+ module: {
+ ignoreDynamic: true,
+ exportInteropAnnotation: true,
+ },
+ }
+ : undefined;
+
+ const transform = {
+ '\\.(mjs|cjs|js)$': [
+ require.resolve('./jestSwcTransform'),
+ {
+ ...moduleOpts,
+ jsc: {
+ parser: {
+ syntax: 'ecmascript',
+ },
+ },
+ },
+ ],
+ '\\.jsx$': [
+ require.resolve('./jestSwcTransform'),
+ {
+ jsc: {
+ parser: {
+ syntax: 'ecmascript',
+ jsx: true,
+ },
+ transform: {
+ react: {
+ runtime: 'automatic',
+ },
+ },
+ },
+ },
+ ],
+ '\\.(mts|cts|ts)$': [
+ require.resolve('./jestSwcTransform'),
+ {
+ ...moduleOpts,
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ },
+ },
+ },
+ ],
+ '\\.tsx$': [
+ require.resolve('./jestSwcTransform'),
+ {
+ jsc: {
+ parser: {
+ syntax: 'typescript',
+ tsx: true,
+ },
+ transform: {
+ react: {
+ runtime: 'automatic',
+ },
+ },
+ },
+ },
+ ],
+ '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$':
+ require.resolve('./jestFileTransform.js'),
+ '\\.(yaml)$': require.resolve('./jestYamlTransform'),
+ };
if (FRONTEND_ROLES.includes(role)) {
- return { testEnvironment: require.resolve('jest-environment-jsdom') };
+ return {
+ testEnvironment: require.resolve('jest-environment-jsdom'),
+ transform,
+ };
}
- return { testEnvironment: require.resolve('jest-environment-node') };
+ return {
+ testEnvironment: require.resolve('jest-environment-node'),
+ moduleFileExtensions: [...SRC_EXTS, 'json', 'node'],
+ // Jest doesn't let us dynamically detect type=module per transformed file,
+ // so we have to assume that if the entry point is ESM, all TS files are
+ // ESM.
+ //
+ // This means you can't switch a package to type=module until all of its
+ // monorepo dependencies are also type=module or does not contain any .ts
+ // files.
+ extensionsToTreatAsEsm:
+ pkgJson.type === 'module' ? ['.ts', '.mts'] : ['.mts'],
+ transform,
+ };
}
async function getProjectConfig(targetPath, extraConfig, extraOptions) {
@@ -160,64 +254,6 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) {
'\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
},
- transform: {
- '\\.(mjs|cjs|js)$': [
- require.resolve('./jestSwcTransform'),
- {
- jsc: {
- parser: {
- syntax: 'ecmascript',
- },
- },
- },
- ],
- '\\.jsx$': [
- require.resolve('./jestSwcTransform'),
- {
- jsc: {
- parser: {
- syntax: 'ecmascript',
- jsx: true,
- },
- transform: {
- react: {
- runtime: 'automatic',
- },
- },
- },
- },
- ],
- '\\.ts$': [
- require.resolve('./jestSwcTransform'),
- {
- jsc: {
- parser: {
- syntax: 'typescript',
- },
- },
- },
- ],
- '\\.tsx$': [
- require.resolve('./jestSwcTransform'),
- {
- jsc: {
- parser: {
- syntax: 'typescript',
- tsx: true,
- },
- transform: {
- react: {
- runtime: 'automatic',
- },
- },
- },
- },
- ],
- '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$':
- require.resolve('./jestFileTransform.js'),
- '\\.(yaml)$': require.resolve('./jestYamlTransform'),
- },
-
// A bit more opinionated
testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`],
@@ -226,7 +262,7 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) {
: require.resolve('./jestCachingModuleLoader'),
transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`],
- ...getRoleConfig(pkgJson.backstage?.role),
+ ...getRoleConfig(pkgJson.backstage?.role, pkgJson),
};
options.setupFilesAfterEnv = options.setupFilesAfterEnv || [];
diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js
index 95c6212123..dd22f25f6f 100644
--- a/packages/cli/config/jestCachingModuleLoader.js
+++ b/packages/cli/config/jestCachingModuleLoader.js
@@ -19,6 +19,11 @@ const { default: JestRuntime } = require('jest-runtime');
const scriptTransformCache = new Map();
module.exports = class CachingJestRuntime extends JestRuntime {
+ constructor(config, ...restAgs) {
+ super(config, ...restAgs);
+ this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts');
+ }
+
// This may or may not be a good idea. Theoretically I don't know why this would impact
// test correctness and flakiness, but it seems like it may introduce flakiness and strange failures.
// It does seem to speed up test execution by a fair amount though.
@@ -33,4 +38,13 @@ module.exports = class CachingJestRuntime extends JestRuntime {
}
return script;
}
+
+ // Unfortunately we need to use this unstable API to make sure that .js files
+ // are only loaded as modules where ESM is supported, i.e. Node.js packages.
+ unstable_shouldLoadAsEsm(path, ...restArgs) {
+ if (!this.allowLoadAsEsm) {
+ return false;
+ }
+ return super.unstable_shouldLoadAsEsm(path, ...restArgs);
+ }
};
diff --git a/packages/cli/config/jestSwcTransform.js b/packages/cli/config/jestSwcTransform.js
index 83abacc9b5..a77683b939 100644
--- a/packages/cli/config/jestSwcTransform.js
+++ b/packages/cli/config/jestSwcTransform.js
@@ -23,10 +23,16 @@ function createTransformer(config) {
...config,
});
const process = (source, filePath, jestOptions) => {
+ // Skip transformation of .js files without ESM syntax, we never transform from CJS to ESM
if (filePath.endsWith('.js') && !ESM_REGEX.test(source)) {
return { code: source };
}
+ // Skip transformation of .mjs files, they should only be used if ESM support is available
+ if (filePath.endsWith('.mjs')) {
+ return { code: source };
+ }
+
return swcTransformer.process(source, filePath, jestOptions);
};
diff --git a/packages/cli/config/nodeTransform.cjs b/packages/cli/config/nodeTransform.cjs
index 1cf79cb1bb..f54527b4a7 100644
--- a/packages/cli/config/nodeTransform.cjs
+++ b/packages/cli/config/nodeTransform.cjs
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+const { pathToFileURL } = require('url');
const { transformSync } = require('@swc/core');
const { addHook } = require('pirates');
const { Module } = require('module');
@@ -55,7 +56,10 @@ addHook(
const transformed = transformSync(code, {
filename,
sourceMaps: 'inline',
- module: { type: 'commonjs' },
+ module: {
+ type: 'commonjs',
+ ignoreDynamic: true,
+ },
jsc: {
target: 'es2022',
parser: {
@@ -76,3 +80,8 @@ addHook(
},
{ extensions: ['.js', '.cjs'], ignoreNodeModules: true },
);
+
+// Register module hooks, used by "type": "module" in package.json, .mjs and
+// .mts files, as well as dynamic import(...)s, although dynamic imports will be
+// handled be the CommonJS hooks in this file if what it points to is CommonJS.
+Module.register('./nodeTransformHooks.mjs', pathToFileURL(__filename));
diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs
new file mode 100644
index 0000000000..5d37ac05c0
--- /dev/null
+++ b/packages/cli/config/nodeTransformHooks.mjs
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { dirname, extname, resolve as resolvePath } from 'path';
+import { fileURLToPath } from 'url';
+import { transformFile } from '@swc/core';
+import { isBuiltin } from 'node:module';
+import { readFile } from 'fs/promises';
+import { existsSync } from 'fs';
+
+// @ts-check
+
+// No explicit file extension, no type in package.json
+const DEFAULT_MODULE_FORMAT = 'commonjs';
+
+// Source file extensions to look for when using bundle resolution strategy
+const SRC_EXTS = ['.ts', '.js'];
+const TS_EXTS = ['.ts', '.mts', '.cts'];
+const moduleTypeTable = {
+ '.mjs': 'module',
+ '.mts': 'module',
+ '.cjs': 'commonjs',
+ '.cts': 'commonjs',
+ '.ts': undefined,
+ '.js': undefined,
+};
+
+/** @type {import('module').ResolveHook} */
+export async function resolve(specifier, context, nextResolve) {
+ // Built-in modules are handled by the default resolver
+ if (isBuiltin(specifier)) {
+ return nextResolve(specifier, context);
+ }
+
+ const ext = extname(specifier);
+
+ // Unless there's an explicit import attribute, JSON files are loaded with our custom loader that's defined below.
+ if (ext === '.json' && !context.importAttributes?.type) {
+ const jsonResult = await nextResolve(specifier, context);
+ return {
+ ...jsonResult,
+ format: 'commonjs',
+ importAttributes: { type: 'json' },
+ };
+ }
+
+ // Anything else with an explicit extension is handled by the default
+ // resolver, except that we help determine the module type where needed.
+ if (ext !== '') {
+ return withDetectedModuleType(await nextResolve(specifier, context));
+ }
+
+ // Other external modules are handled by the default resolver, but again we
+ // help determine the module type where needed.
+ if (!specifier.startsWith('.')) {
+ return withDetectedModuleType(await nextResolve(specifier, context));
+ }
+
+ // The rest of this function handles the case of resolving imports that do not
+ // specify any extension and might point to a directory with an `index.*`
+ // file. We resolve those using the same logic as most JS bundlers would, with
+ // the addition of checking if there's an explicit module format listed in the
+ // closest `package.json` file.
+ //
+ // We use a bundle resolution strategy in order to keep code consistent across
+ // Backstage codebases that contains code both for Web and Node.js, and to
+ // support packages with common code that can be used in both environments.
+ try {
+ // This is expected to throw, but in the event that this module specifier is
+ // supported we prefer to use the default resolver.
+ return await nextResolve(specifier, context);
+ } catch (error) {
+ if (error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
+ const spec = `${specifier}${specifier.endsWith('/') ? '' : '/'}index`;
+ const resolved = await resolveWithoutExt(spec, context, nextResolve);
+ if (resolved) {
+ return withDetectedModuleType(resolved);
+ }
+ } else if (error.code === 'ERR_MODULE_NOT_FOUND') {
+ const resolved = await resolveWithoutExt(specifier, context, nextResolve);
+ if (resolved) {
+ return withDetectedModuleType(resolved);
+ }
+ }
+
+ // Unexpected error or no resolution found
+ throw error;
+ }
+}
+
+/**
+ * Populates the `format` field in the resolved object based on the closest `package.json` file.
+ *
+ * @param {import('module').ResolveFnOutput} resolved
+ * @returns {Promise}
+ */
+async function withDetectedModuleType(resolved) {
+ // Already has an explicit format
+ if (resolved.format) {
+ return resolved;
+ }
+ // Happens in Node.js v22 when there's a package.json without an explicit "type" field. Use the default.
+ if (resolved.format === null) {
+ return { ...resolved, format: DEFAULT_MODULE_FORMAT };
+ }
+
+ const ext = extname(resolved.url);
+
+ const explicitFormat = moduleTypeTable[ext];
+ if (explicitFormat) {
+ return {
+ ...resolved,
+ format: explicitFormat,
+ };
+ }
+
+ // TODO(Rugvip): Afaik this should never happen and we can remove this check, but want it here for a little while to verify.
+ if (ext === '.js') {
+ throw new Error('Unexpected .js file without explicit format');
+ }
+
+ // TODO(Rugvip): Does this need caching? kept it simple for now but worth exploring
+ const packageJsonPath = await findPackageJSON(fileURLToPath(resolved.url));
+ if (!packageJsonPath) {
+ return resolved;
+ }
+
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
+ return {
+ ...resolved,
+ format: packageJson.type ?? DEFAULT_MODULE_FORMAT,
+ };
+}
+
+/**
+ * Find the closest package.json file from the given path.
+ *
+ * TODO(Rugvip): This can be replaced with the Node.js built-in with the same name once it is stable.
+ * @param {string} startPath
+ * @returns {Promise}
+ */
+async function findPackageJSON(startPath) {
+ let path = startPath;
+
+ // Some confidence check to avoid infinite loop
+ for (let i = 0; i < 1000; i++) {
+ const packagePath = resolvePath(path, 'package.json');
+ if (existsSync(packagePath)) {
+ return packagePath;
+ }
+
+ const newPath = dirname(path);
+ if (newPath === path) {
+ return undefined;
+ }
+ path = newPath;
+ }
+
+ throw new Error(
+ `Iteration limit reached when searching for package.json at ${startPath}`,
+ );
+}
+
+/** @type {import('module').ResolveHook} */
+async function resolveWithoutExt(specifier, context, nextResolve) {
+ for (const tryExt of SRC_EXTS) {
+ try {
+ const resolved = await nextResolve(specifier + tryExt, {
+ ...context,
+ format: 'commonjs',
+ });
+ return {
+ ...resolved,
+ format: moduleTypeTable[tryExt] ?? resolved.format,
+ };
+ } catch {
+ /* ignore */
+ }
+ }
+ return undefined;
+}
+
+/** @type {import('module').LoadHook} */
+export async function load(url, context, nextLoad) {
+ // Non-file URLs are handled by the default loader
+ if (!url.startsWith('file://')) {
+ return nextLoad(url, context);
+ }
+
+ // JSON files loaded as CommonJS are handled by this custom loader, because
+ // the default one doesn't work. For JSON loading to work we'd need the
+ // synchronous hooks that aren't supported yet, or avoid using the CommonJS
+ // compatibility.
+ if (
+ context.format === 'commonjs' &&
+ context.importAttributes?.type === 'json'
+ ) {
+ try {
+ // TODO(Rugvip): Make sure this is valid JSON
+ const content = await readFile(fileURLToPath(url), 'utf8');
+ return {
+ source: `module.exports = (${content})`,
+ format: 'commonjs',
+ shortCircuit: true,
+ };
+ } catch {
+ // Let the default loader generate the error
+ return nextLoad(url, context);
+ }
+ }
+
+ const ext = extname(url);
+
+ // Non-TS files are handled by the default loader
+ if (!TS_EXTS.includes(ext)) {
+ return nextLoad(url, context);
+ }
+
+ const format = context.format ?? DEFAULT_MODULE_FORMAT;
+
+ // We have two choices at this point, we can either transform CommonJS files
+ // and return the transformed source code, or let the default loader handle
+ // them. If we transform them ourselves we will enter CommonJS compatibility
+ // mode in the new module system in Node.js, this effectively means all
+ // CommonJS loaded via `require` calls from this point will all be treated as
+ // if it was loaded via `import` calls from modules.
+ //
+ // The CommonJS compatibility layer will try to identify named exports and
+ // make them available directly, which is convenient as it avoids things like
+ // `import(...).then(m => m.default.foo)`, allowing you to instead write
+ // `import(...).then(m => m.foo)`. The compatibility layer doesn't always work
+ // all that well though, and can lead to module loading issues in many cases,
+ // especially for older code.
+
+ // This `if` block opts-out of using CommonJS compatibility mode by default,
+ // and instead leaves it to our existing loader to transform CommonJS. We do
+ // however use compatibility mode for the more explicit .cts file extension,
+ // allows for a way to opt-in to the new behavior.
+ //
+ // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead
+ if (format === 'commonjs' && ext !== '.cts') {
+ return nextLoad(url, { ...context, format });
+ }
+
+ const transformed = await transformFile(fileURLToPath(url), {
+ sourceMaps: 'inline',
+ module: {
+ type: format === 'module' ? 'es6' : 'commonjs',
+ ignoreDynamic: true,
+
+ // This helps the Node.js CommonJS compat layer identify named exports.
+ exportInteropAnnotation: true,
+ },
+ jsc: {
+ target: 'es2022',
+ parser: {
+ syntax: 'typescript',
+ },
+ },
+ });
+
+ return {
+ ...context,
+ shortCircuit: true,
+ source: transformed.code,
+ format,
+ responseURL: url,
+ };
+}
diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json
index aa25e59451..ac5e62b52e 100644
--- a/packages/cli/config/tsconfig.json
+++ b/packages/cli/config/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "allowImportingTsExtensions": true,
"allowJs": true,
"declaration": true,
"declarationMap": false,
diff --git a/packages/cli/package.json b/packages/cli/package.json
index ce936223a3..4ebf78ec6f 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli",
- "version": "0.29.5-next.1",
+ "version": "0.29.5",
"description": "CLI for developing Backstage plugins and apps",
"backstage": {
"role": "cli"
@@ -147,6 +147,7 @@
"tar": "^6.1.12",
"terser-webpack-plugin": "^5.1.3",
"ts-morph": "^24.0.0",
+ "undici": "^7.2.3",
"util": "^0.12.3",
"webpack": "^5.94.0",
"webpack-dev-server": "^5.0.0",
@@ -192,28 +193,16 @@
"@types/terser-webpack-plugin": "^5.0.4",
"@types/webpack-sources": "^3.2.3",
"@types/yarnpkg__lockfile": "^1.1.4",
- "@vitejs/plugin-react": "^4.3.1",
"del": "^8.0.0",
"msw": "^1.0.0",
- "nodemon": "^3.0.1",
- "vite": "^5.0.0",
- "vite-plugin-html": "^3.2.2",
- "vite-plugin-node-polyfills": "^0.22.0"
+ "nodemon": "^3.0.1"
},
"peerDependencies": {
- "@modyfi/vite-plugin-yaml": "^1.1.0",
"@rspack/core": "^1.0.10",
"@rspack/dev-server": "^1.0.9",
- "@rspack/plugin-react-refresh": "^1.0.0",
- "@vitejs/plugin-react": "^4.0.4",
- "vite": "^5.0.0",
- "vite-plugin-html": "^3.2.0",
- "vite-plugin-node-polyfills": "^0.22.0"
+ "@rspack/plugin-react-refresh": "^1.0.0"
},
"peerDependenciesMeta": {
- "@modyfi/vite-plugin-yaml": {
- "optional": true
- },
"@rspack/core": {
"optional": true
},
@@ -222,18 +211,6 @@
},
"@rspack/plugin-react-refresh": {
"optional": true
- },
- "@vitejs/plugin-react": {
- "optional": true
- },
- "vite": {
- "optional": true
- },
- "vite-plugin-html": {
- "optional": true
- },
- "vite-plugin-node-polyfills": {
- "optional": true
}
},
"configSchema": {
diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts
index 4b2cfa4842..f6cd2157ae 100644
--- a/packages/cli/src/alpha.ts
+++ b/packages/cli/src/alpha.ts
@@ -24,6 +24,6 @@ import chalk from 'chalk';
),
);
const initializer = new CliInitializer();
- initializer.add(import('./modules/config/alpha').then(m => m.default));
+ initializer.add(import('./modules/config/alpha'));
await initializer.run();
})();
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 0f38fc1e8a..f1119053ec 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -43,7 +43,7 @@ export function registerRepoCommand(program: Command) {
'--minify',
'Minify the generated code. Does not apply to app package (app is minified by default).',
)
- .action(lazy(() => import('./repo/build').then(m => m.command)));
+ .action(lazy(() => import('./repo/build'), 'command'));
command
.command('lint')
@@ -70,7 +70,7 @@ export function registerRepoCommand(program: Command) {
'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
)
.option('--fix', 'Attempt to automatically fix violations')
- .action(lazy(() => import('./repo/lint').then(m => m.command)));
+ .action(lazy(() => import('./repo/lint'), 'command'));
command
.command('fix')
@@ -83,20 +83,18 @@ export function registerRepoCommand(program: Command) {
'--check',
'Fail if any packages would have been changed by the command',
)
- .action(lazy(() => import('./repo/fix').then(m => m.command)));
+ .action(lazy(() => import('./repo/fix'), 'command'));
command
.command('clean')
.description('Delete cache and output directories')
- .action(lazy(() => import('./repo/clean').then(m => m.command)));
+ .action(lazy(() => import('./repo/clean'), 'command'));
command
.command('list-deprecations')
.description('List deprecations')
.option('--json', 'Output as JSON')
- .action(
- lazy(() => import('./repo/list-deprecations').then(m => m.command)),
- );
+ .action(lazy(() => import('./repo/list-deprecations'), 'command'));
command
.command('test')
@@ -118,7 +116,7 @@ export function registerRepoCommand(program: Command) {
'Show help for Jest CLI options, which are passed through',
)
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
- .action(lazy(() => import('./repo/test').then(m => m.command)));
+ .action(lazy(() => import('./repo/test'), 'command'));
}
export function registerScriptCommand(program: Command) {
@@ -139,7 +137,7 @@ export function registerScriptCommand(program: Command) {
)
.option('--require ', 'Add a --require argument to the node process')
.option('--link ', 'Link an external workspace for module resolution')
- .action(lazy(() => import('./start').then(m => m.command)));
+ .action(lazy(() => import('./start'), 'command'));
command
.command('build')
@@ -163,7 +161,7 @@ export function registerScriptCommand(program: Command) {
(opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]),
Array(),
)
- .action(lazy(() => import('./build').then(m => m.command)));
+ .action(lazy(() => import('./build'), 'command'));
command
.command('lint [directories...]')
@@ -182,29 +180,29 @@ export function registerScriptCommand(program: Command) {
'Fail if more than this number of warnings. -1 allows warnings. (default: 0)',
)
.description('Lint a package')
- .action(lazy(() => import('./lint').then(m => m.default)));
+ .action(lazy(() => import('./lint'), 'default'));
command
.command('test')
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
.helpOption(', --backstage-cli-help') // Let Jest handle help
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
- .action(lazy(() => import('./test').then(m => m.default)));
+ .action(lazy(() => import('./test'), 'default'));
command
.command('clean')
.description('Delete cache directories')
- .action(lazy(() => import('./clean/clean').then(m => m.default)));
+ .action(lazy(() => import('./clean/clean'), 'default'));
command
.command('prepack')
.description('Prepares a package for packaging before publishing')
- .action(lazy(() => import('./pack').then(m => m.pre)));
+ .action(lazy(() => import('./pack'), 'pre'));
command
.command('postpack')
.description('Restores the changes made by the prepack command')
- .action(lazy(() => import('./pack').then(m => m.post)));
+ .action(lazy(() => import('./pack'), 'post'));
}
export function registerMigrateCommand(program: Command) {
@@ -215,39 +213,31 @@ export function registerMigrateCommand(program: Command) {
command
.command('package-roles')
.description(`Add package role field to packages that don't have it`)
- .action(lazy(() => import('./migrate/packageRole').then(m => m.default)));
+ .action(lazy(() => import('./migrate/packageRole'), 'default'));
command
.command('package-scripts')
.description('Set package scripts according to each package role')
- .action(
- lazy(() => import('./migrate/packageScripts').then(m => m.command)),
- );
+ .action(lazy(() => import('./migrate/packageScripts'), 'command'));
command
.command('package-exports')
.description('Synchronize package subpath export definitions')
- .action(
- lazy(() => import('./migrate/packageExports').then(m => m.command)),
- );
+ .action(lazy(() => import('./migrate/packageExports'), 'command'));
command
.command('package-lint-configs')
.description(
'Migrates all packages to use @backstage/cli/config/eslint-factory',
)
- .action(
- lazy(() => import('./migrate/packageLintConfigs').then(m => m.command)),
- );
+ .action(lazy(() => import('./migrate/packageLintConfigs'), 'command'));
command
.command('react-router-deps')
.description(
'Migrates the react-router dependencies for all packages to be peer dependencies',
)
- .action(
- lazy(() => import('./migrate/reactRouterDeps').then(m => m.command)),
- );
+ .action(lazy(() => import('./migrate/reactRouterDeps'), 'command'));
}
export function registerCommands(program: Command) {
@@ -281,7 +271,7 @@ export function registerCommands(program: Command) {
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
- .action(lazy(() => import('./new/new').then(m => m.default)));
+ .action(lazy(() => import('./new/new'), 'default'));
registerConfigCommands(program);
registerRepoCommand(program);
@@ -302,7 +292,7 @@ export function registerCommands(program: Command) {
.option('--skip-install', 'Skips yarn install step')
.option('--skip-migrate', 'Skips migration of any moved packages')
.description('Bump Backstage packages to the latest versions')
- .action(lazy(() => import('./versions/bump').then(m => m.default)));
+ .action(lazy(() => import('./versions/bump'), 'default'));
program
.command('versions:migrate')
@@ -317,7 +307,7 @@ export function registerCommands(program: Command) {
.description(
'Migrate any plugins that have been moved to the @backstage-community namespace automatically',
)
- .action(lazy(() => import('./versions/migrate').then(m => m.default)));
+ .action(lazy(() => import('./versions/migrate'), 'default'));
program
.command('build-workspace [packages...]')
@@ -334,17 +324,17 @@ export function registerCommands(program: Command) {
'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)',
)
.description('Builds a temporary dist workspace from the provided packages')
- .action(lazy(() => import('./buildWorkspace').then(m => m.default)));
+ .action(lazy(() => import('./buildWorkspace'), 'default'));
program
.command('create-github-app ')
.description('Create new GitHub App in your organization.')
- .action(lazy(() => import('./create-github-app').then(m => m.default)));
+ .action(lazy(() => import('./create-github-app'), 'default'));
program
.command('info')
.description('Show helpful information for debugging and reporting bugs')
- .action(lazy(() => import('./info').then(m => m.default)));
+ .action(lazy(() => import('./info'), 'default'));
// Notifications for removed commands
program
diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts
index 96e8de517b..d3ff0c1fbe 100644
--- a/packages/cli/src/commands/versions/bump.test.ts
+++ b/packages/cli/src/commands/versions/bump.test.ts
@@ -28,8 +28,14 @@ import {
createMockDirectory,
} from '@backstage/backend-test-utils';
-// Avoid mutating the global http(s) agent used in other tests
-jest.mock('global-agent/bootstrap', () => {});
+// Avoid mutating the global agents used in other tests
+jest.mock('global-agent', () => ({
+ bootstrap: jest.fn(),
+}));
+jest.mock('undici', () => ({
+ setGlobalDispatcher: jest.fn(),
+ EnvHttpProxyAgent: class {},
+}));
// Remove log coloring to simplify log matching
jest.mock('chalk', () => ({
diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts
index 76e6092140..1e1e5deff2 100644
--- a/packages/cli/src/commands/versions/bump.ts
+++ b/packages/cli/src/commands/versions/bump.ts
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-import { bootstrap } from 'global-agent';
-
-if (shouldUseGlobalAgent()) {
- bootstrap();
-}
+maybeBootstrapProxy();
import fs from 'fs-extra';
import chalk from 'chalk';
@@ -46,17 +42,24 @@ import { migrateMovedPackages } from './migrate';
import { runYarnInstall } from './utils';
import { run } from '../../lib/run';
-function shouldUseGlobalAgent(): boolean {
+function maybeBootstrapProxy() {
// see https://www.npmjs.com/package/global-agent
- const namespace =
+ const globalAgentNamespace =
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE ?? 'GLOBAL_AGENT_';
if (
- process.env[`${namespace}HTTP_PROXY`] ||
- process.env[`${namespace}HTTPS_PROXY`]
+ process.env[`${globalAgentNamespace}HTTP_PROXY`] ||
+ process.env[`${globalAgentNamespace}HTTPS_PROXY`]
) {
- return true;
+ const globalAgent =
+ require('global-agent') as typeof import('global-agent');
+ globalAgent.bootstrap();
+ }
+
+ if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) {
+ const { setGlobalDispatcher, EnvHttpProxyAgent } =
+ require('undici') as typeof import('undici');
+ setGlobalDispatcher(new EnvHttpProxyAgent());
}
- return false;
}
const DEP_TYPES = [
diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts
index e419503649..3b55a5a451 100644
--- a/packages/cli/src/lib/builder/config.ts
+++ b/packages/cli/src/lib/builder/config.ts
@@ -16,7 +16,11 @@
import chalk from 'chalk';
import fs from 'fs-extra';
-import { relative as relativePath, resolve as resolvePath } from 'path';
+import {
+ extname,
+ relative as relativePath,
+ resolve as resolvePath,
+} from 'path';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import postcss from 'rollup-plugin-postcss';
@@ -29,6 +33,7 @@ import {
RollupOptions,
OutputOptions,
WarningHandlerWithDefault,
+ OutputPlugin,
} from 'rollup';
import { forwardFileImports } from './plugins';
@@ -40,6 +45,12 @@ import { readEntryPoints } from '../entryPoints';
const SCRIPT_EXTS = ['.js', '.jsx', '.ts', '.tsx'];
+const MODULE_EXTS = ['.mjs', '.mts'];
+const COMMONJS_EXTS = ['.cjs', '.cts'];
+const MOD_EXT = '.mjs';
+const CJS_EXT = '.cjs';
+const CJS_JS_EXT = '.cjs.js';
+
function isFileImport(source: string) {
if (source.startsWith('.')) {
return true;
@@ -68,6 +79,39 @@ function buildInternalImportPattern(options: BuildOptions) {
return new RegExp(`^(?:${names.join('|')})(?:$|/)`);
}
+// This Rollup output plugin enables support for mixed CommonJS and ESM output.
+// It does it be filtering out the unwanted output files that don't match the
+// input file format, allowing the rollup configuration to have overlapping
+// output configurations for different formats.
+function multiOutputFormat(): OutputPlugin {
+ return {
+ name: 'backstage-multi-output-format',
+ generateBundle(opts, bundle) {
+ const filter: (name: string) => boolean =
+ opts.format === 'cjs'
+ ? s => s.endsWith(MOD_EXT)
+ : s => !s.endsWith(MOD_EXT);
+
+ // Delete any files that don't match the current output format
+ for (const name in bundle) {
+ if (filter(name)) {
+ delete bundle[name];
+ delete bundle[`${name}.map`];
+ }
+ }
+ },
+ renderDynamicImport(opts) {
+ if (opts.format === 'cjs') {
+ return {
+ left: 'import(',
+ right: ')',
+ };
+ }
+ return undefined;
+ },
+ };
+}
+
export async function makeRollupConfigs(
options: BuildOptions,
): Promise {
@@ -120,18 +164,47 @@ export async function makeRollupConfigs(
const rewriteNodeModules = (name: string) =>
name.replaceAll('node_modules', 'node_modules_dist');
+ // For CommonJS we build both CommonJS and ESM output. Each of these outputs
+ // can output both .cjs and .mjs files. The files from each of these outputs
+ // will overlap, but we trim away files where the format doesn't match the
+ // file extensions. That way we are left with a combination of .cjs and .mjs
+ // files where the module format in the file matches the file extension.
if (options.outputs.has(Output.cjs)) {
- output.push({
+ const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_JS_EXT;
+ const outputOpts: OutputOptions = {
dir: distDir,
- entryFileNames: chunkInfo =>
- `${rewriteNodeModules(chunkInfo.name)}.cjs.js`,
- chunkFileNames: `cjs/[name]-[hash].cjs.js`,
- format: 'commonjs',
- interop: 'compat',
+ entryFileNames(chunkInfo) {
+ const cleanName = rewriteNodeModules(chunkInfo.name);
+
+ const inputId = chunkInfo.facadeModuleId;
+ if (!inputId) {
+ return cleanName + defaultExt;
+ }
+
+ const inputExt = extname(inputId);
+ if (MODULE_EXTS.includes(inputExt)) {
+ return cleanName + MOD_EXT;
+ }
+ if (COMMONJS_EXTS.includes(inputExt)) {
+ return cleanName + CJS_EXT;
+ }
+ return cleanName + defaultExt;
+ },
sourcemap: true,
preserveModules: true,
preserveModulesRoot: `${targetDir}/src`,
+ interop: 'compat',
exports: 'named',
+ plugins: [multiOutputFormat()],
+ };
+
+ output.push({
+ ...outputOpts,
+ format: 'cjs',
+ });
+ output.push({
+ ...outputOpts,
+ format: 'module',
});
}
if (options.outputs.has(Output.esm)) {
@@ -160,7 +233,10 @@ export async function makeRollupConfigs(
// All module imports are always marked as external
external,
plugins: [
- resolve({ mainFields }),
+ resolve({
+ mainFields,
+ extensions: SCRIPT_EXTS,
+ }),
commonjs({
include: /node_modules/,
exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/],
diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts
index be9bc9a6ea..b709cb0408 100644
--- a/packages/cli/src/lib/builder/packager.ts
+++ b/packages/cli/src/lib/builder/packager.ts
@@ -107,7 +107,7 @@ export const buildPackage = async (options: BuildOptions) => {
const rollupConfigs = await makeRollupConfigs(options);
- await fs.remove(paths.resolveTarget('dist'));
+ await fs.remove(resolvePath(options.targetDir ?? paths.targetDir, 'dist'));
const buildTasks = rollupConfigs.map(rollupBuild);
diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts
index 1b62c82ce5..2931b3f53e 100644
--- a/packages/cli/src/lib/bundler/server.ts
+++ b/packages/cli/src/lib/bundler/server.ts
@@ -27,7 +27,6 @@ import { createConfig, resolveBaseUrl, resolveEndpoint } from './config';
import { createDetectedModulesEntryPoint } from './packageDetection';
import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths';
import { ServeOptions } from './types';
-import { hasReactDomClient } from './hasReactDomClient';
export async function serveBundle(options: ServeOptions) {
const paths = resolveBundlingPaths(options);
@@ -54,17 +53,11 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
const { name } = await fs.readJson(libPaths.resolveTarget('package.json'));
let webpackServer: WebpackDevServer | undefined = undefined;
- // @ts-ignore
- let viteServer: import('vite').ViteDevServer | undefined = undefined;
let latestFrontendAppConfigs: AppConfig[] = [];
/** Triggers a full reload of all clients */
const triggerReload = () => {
- if (viteServer) {
- viteServer.restart();
- }
-
if (webpackServer) {
webpackServer.invalidate();
@@ -147,167 +140,84 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
moduleFederation: options.moduleFederation,
});
- if (process.env.EXPERIMENTAL_VITE) {
- const vite = require('vite') as typeof import('vite');
- const { default: viteReact } =
- require('@vitejs/plugin-react') as typeof import('@vitejs/plugin-react');
- const { default: viteYaml } =
- require('@modyfi/vite-plugin-yaml') as typeof import('@modyfi/vite-plugin-yaml');
- const { nodePolyfills: viteNodePolyfills } =
- require('vite-plugin-node-polyfills') as typeof import('vite-plugin-node-polyfills');
- const { createHtmlPlugin: viteHtml } =
- require('vite-plugin-html') as typeof import('vite-plugin-html');
+ const bundler = (rspack ?? webpack) as typeof webpack;
+ const DevServer: typeof WebpackDevServer = rspack
+ ? require('@rspack/dev-server').RspackDevServer
+ : WebpackDevServer;
- viteServer = await vite.createServer({
- define: {
- 'process.argv': JSON.stringify(process.argv),
- 'process.env.APP_CONFIG': JSON.stringify(cliConfig.frontendAppConfigs),
- // This allows for conditional imports of react-dom/client, since there's no way
- // to check for presence of it in source code without module resolution errors.
- 'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify(hasReactDomClient()),
- },
- optimizeDeps: {
- esbuildOptions: {
- plugins: [
- {
- name: 'custom-define',
- setup(build) {
- const define = (build.initialOptions.define ||= {});
- define['process.env.HAS_REACT_DOM_CLIENT'] = JSON.stringify(
- hasReactDomClient(),
- );
- define['process.env.NODE_ENV'] = JSON.stringify('development');
- },
- },
- ],
- },
- },
- plugins: [
- viteReact(),
- viteNodePolyfills({
- include: [
- 'buffer',
- 'events',
- 'fs',
- 'http',
- 'https',
- 'os',
- 'path',
- 'process',
- 'querystring',
- 'stream',
- 'url',
- 'util',
- 'zlib',
- ],
- globals: {
- global: true,
- Buffer: true,
- process: true,
- },
- }),
- viteYaml(),
- viteHtml({
- entry: paths.targetEntry,
- // todo(blam): we should look at contributing to thPe plugin here
- // to support absolute paths, but works in the interim at least.
- template: 'public/index.html',
- inject: {
- data: {
- config: frontendConfig,
- publicPath: config.output?.publicPath,
- },
- },
- }),
- ],
- server: {
- host,
- port,
- },
- publicDir: paths.targetPublic,
- root: paths.targetPath,
- });
- } else {
- const bundler = (rspack ?? webpack) as typeof webpack;
- const DevServer: typeof WebpackDevServer = rspack
- ? require('@rspack/dev-server').RspackDevServer
- : WebpackDevServer;
-
- if (rspack) {
- console.log(
- chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`),
- );
- }
-
- const publicPaths = await resolveOptionalBundlingPaths({
- entry: 'src/index-public-experimental',
- dist: 'dist/public',
- });
- if (publicPaths) {
- console.log(
- chalk.yellow(
- `⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`,
- ),
- );
- }
- const compiler = publicPaths
- ? bundler([config, await createConfig(publicPaths, commonConfigOptions)])
- : bundler(config);
-
- webpackServer = new DevServer(
- {
- hot: !process.env.CI,
- devMiddleware: {
- publicPath: config.output?.publicPath as string,
- stats: 'errors-warnings',
- },
- static: paths.targetPublic
- ? {
- publicPath: config.output?.publicPath as string,
- directory: paths.targetPublic,
- }
- : undefined,
- historyApiFallback:
- options.moduleFederation?.mode === 'remote'
- ? false
- : {
- // Paths with dots should still use the history fallback.
- // See https://github.com/facebookincubator/create-react-app/issues/387.
- disableDotRule: true,
-
- // The index needs to be rewritten relative to the new public path, including subroutes.
- index: `${config.output?.publicPath}index.html`,
- },
- server:
- url.protocol === 'https:'
- ? {
- type: 'https',
- options: {
- cert: fullConfig.getString('app.https.certificate.cert'),
- key: fullConfig.getString('app.https.certificate.key'),
- },
- }
- : {},
- host,
- port,
- proxy: targetPkg.proxy,
- // When the dev server is behind a proxy, the host and public hostname differ
- allowedHosts: [url.hostname],
- client: {
- webSocketURL: { hostname: host, port },
- },
- headers: {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET, OPTIONS',
- 'Access-Control-Allow-Headers':
- 'X-Requested-With, content-type, Authorization',
- },
- },
- compiler,
+ if (rspack) {
+ console.log(
+ chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`),
);
}
- await viteServer?.listen();
+ const publicPaths = await resolveOptionalBundlingPaths({
+ entry: 'src/index-public-experimental',
+ dist: 'dist/public',
+ });
+ if (publicPaths) {
+ console.log(
+ chalk.yellow(
+ `⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`,
+ ),
+ );
+ }
+ const compiler = publicPaths
+ ? bundler([config, await createConfig(publicPaths, commonConfigOptions)])
+ : bundler(config);
+
+ webpackServer = new DevServer(
+ {
+ hot: !process.env.CI,
+ devMiddleware: {
+ publicPath: config.output?.publicPath as string,
+ stats: 'errors-warnings',
+ },
+ static: paths.targetPublic
+ ? {
+ publicPath: config.output?.publicPath as string,
+ directory: paths.targetPublic,
+ }
+ : undefined,
+ historyApiFallback:
+ options.moduleFederation?.mode === 'remote'
+ ? false
+ : {
+ // Paths with dots should still use the history fallback.
+ // See https://github.com/facebookincubator/create-react-app/issues/387.
+ disableDotRule: true,
+
+ // The index needs to be rewritten relative to the new public path, including subroutes.
+ index: `${config.output?.publicPath}index.html`,
+ },
+ server:
+ url.protocol === 'https:'
+ ? {
+ type: 'https',
+ options: {
+ cert: fullConfig.getString('app.https.certificate.cert'),
+ key: fullConfig.getString('app.https.certificate.key'),
+ },
+ }
+ : {},
+ host,
+ port,
+ proxy: targetPkg.proxy,
+ // When the dev server is behind a proxy, the host and public hostname differ
+ allowedHosts: [url.hostname],
+ client: {
+ webSocketURL: { hostname: host, port },
+ },
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
+ 'Access-Control-Allow-Headers':
+ 'X-Requested-With, content-type, Authorization',
+ },
+ },
+ compiler,
+ );
+
await new Promise(async (resolve, reject) => {
if (webpackServer) {
webpackServer.startCallback((err?: Error) => {
@@ -330,7 +240,6 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.on(signal, () => {
webpackServer?.stop();
- viteServer?.close();
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
process.exit();
});
diff --git a/packages/cli/src/lib/lazy.ts b/packages/cli/src/lib/lazy.ts
index 6d2cb1cd4f..d1255ea1bc 100644
--- a/packages/cli/src/lib/lazy.ts
+++ b/packages/cli/src/lib/lazy.ts
@@ -17,13 +17,25 @@
import { assertError } from '@backstage/errors';
import { exitWithError } from '../lib/errors';
+type ActionFunc = (...args: any[]) => Promise;
+type ActionExports = {
+ [KName in keyof TModule as TModule[KName] extends ActionFunc
+ ? KName
+ : never]: TModule[KName];
+};
+
// Wraps an action function so that it always exits and handles errors
-export function lazy(
- getActionFunc: () => Promise<(...args: any[]) => Promise>,
+export function lazy(
+ moduleLoader: () => Promise,
+ exportName: keyof ActionExports,
): (...args: any[]) => Promise {
return async (...args: any[]) => {
try {
- const actionFunc = await getActionFunc();
+ const mod = await moduleLoader();
+ const actualModule = (
+ mod as unknown as { default: ActionExports }
+ ).default;
+ const actionFunc = actualModule[exportName] as ActionFunc;
await actionFunc(...args);
process.exit(0);
diff --git a/packages/cli/src/modules/config/alpha.ts b/packages/cli/src/modules/config/alpha.ts
index c32982086a..9ce6080a4a 100644
--- a/packages/cli/src/modules/config/alpha.ts
+++ b/packages/cli/src/modules/config/alpha.ts
@@ -32,7 +32,7 @@ export default createCliPlugin({
'Only include the schema that applies to the given package',
)
.description('Browse the configuration reference documentation')
- .action(lazy(() => import('./commands/docs').then(m => m.default)));
+ .action(lazy(() => import('./commands/docs'), 'default'));
await defaultCommand.parseAsync(args, { from: 'user' });
},
diff --git a/packages/cli/src/modules/config/index.ts b/packages/cli/src/modules/config/index.ts
index 0c6a79577d..a1436d0c21 100644
--- a/packages/cli/src/modules/config/index.ts
+++ b/packages/cli/src/modules/config/index.ts
@@ -32,7 +32,7 @@ export function registerCommands(program: Command) {
'Only include the schema that applies to the given package',
)
.description('Browse the configuration reference documentation')
- .action(lazy(() => import('./commands/docs').then(m => m.default)));
+ .action(lazy(() => import('./commands/docs'), 'default'));
program
.command('config:print')
@@ -49,7 +49,7 @@ export function registerCommands(program: Command) {
)
.option(...configOption)
.description('Print the app configuration for the current package')
- .action(lazy(() => import('./commands/print').then(m => m.default)));
+ .action(lazy(() => import('./commands/print'), 'default'));
program
.command('config:check')
@@ -68,7 +68,7 @@ export function registerCommands(program: Command) {
.description(
'Validate that the given configuration loads and matches schema',
)
- .action(lazy(() => import('./commands/validate').then(m => m.default)));
+ .action(lazy(() => import('./commands/validate'), 'default'));
program
.command('config:schema')
@@ -83,5 +83,5 @@ export function registerCommands(program: Command) {
.option('--merge', 'Print the config schemas merged', true)
.option('--no-merge', 'Print the config schemas not merged')
.description('Print configuration schema')
- .action(lazy(() => import('./commands/schema').then(m => m.default)));
+ .action(lazy(() => import('./commands/schema'), 'default'));
}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/.gitignore b/packages/cli/src/tests/transforms/__fixtures__/.gitignore
new file mode 100644
index 0000000000..dd13a98e05
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/.gitignore
@@ -0,0 +1,2 @@
+!node_modules
+dist
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js
new file mode 100644
index 0000000000..67606e3f86
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js
@@ -0,0 +1 @@
+module.exports = 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js
new file mode 100644
index 0000000000..0710f9dbe6
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js
@@ -0,0 +1 @@
+exports.value = 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs
new file mode 100644
index 0000000000..a3bb49043e
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs
@@ -0,0 +1 @@
+export default 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs
new file mode 100644
index 0000000000..18049c8488
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs
@@ -0,0 +1 @@
+export const value = 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs
new file mode 100644
index 0000000000..7212f4d5a7
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs
@@ -0,0 +1 @@
+module.exports = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs
new file mode 100644
index 0000000000..c1dcb4b923
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs
@@ -0,0 +1 @@
+exports.value = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts
new file mode 100644
index 0000000000..bdc1e24a02
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts
@@ -0,0 +1,15 @@
+export const namedA: string
+export const namedB: string
+export const namedC: string
+export const defaultA: string
+export const defaultB: string
+export const defaultC: string
+
+export namespace dyn {
+ export const namedA: Promise
+ export const namedB: Promise
+ export const namedC: Promise
+ export const defaultA: Promise
+ export const defaultB: Promise
+ export const defaultC: Promise
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js
new file mode 100644
index 0000000000..d59f7789bd
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js
@@ -0,0 +1,14 @@
+exports.namedA = require('./a-named').value;
+// exports.namedB = require('./b-named.mjs').value;
+exports.namedC = require('./c-named.cjs').value;
+exports.defaultA = require('./a-default');
+// exports.defaultB = require('./b-default.mjs').default;
+exports.defaultC = require('./c-default.cjs');
+exports.dyn = {
+ namedA: import('./a-named').then(m => m.value),
+ namedB: import('./b-named.mjs').then(m => m.value),
+ namedC: import('./c-named.cjs').then(m => m.value),
+ defaultA: import('./a-default').then(m => m.default),
+ defaultB: import('./b-default.mjs').then(m => m.default),
+ defaultC: import('./c-default.cjs').then(m => m.default),
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json
new file mode 100644
index 0000000000..447f95f566
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dep-commonjs",
+ "type": "commonjs",
+ "exports": {
+ ".": "./main.js"
+ },
+ "typesVersions": {
+ "*": {
+ "*": [
+ "main.d.ts"
+ ]
+ }
+ }
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js
new file mode 100644
index 0000000000..67606e3f86
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js
@@ -0,0 +1 @@
+module.exports = 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js
new file mode 100644
index 0000000000..0710f9dbe6
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js
@@ -0,0 +1 @@
+exports.value = 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs
new file mode 100644
index 0000000000..a3bb49043e
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs
@@ -0,0 +1 @@
+export default 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs
new file mode 100644
index 0000000000..18049c8488
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs
@@ -0,0 +1 @@
+export const value = 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs
new file mode 100644
index 0000000000..7212f4d5a7
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs
@@ -0,0 +1 @@
+module.exports = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs
new file mode 100644
index 0000000000..c1dcb4b923
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs
@@ -0,0 +1 @@
+exports.value = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts
new file mode 100644
index 0000000000..bdc1e24a02
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts
@@ -0,0 +1,15 @@
+export const namedA: string
+export const namedB: string
+export const namedC: string
+export const defaultA: string
+export const defaultB: string
+export const defaultC: string
+
+export namespace dyn {
+ export const namedA: Promise
+ export const namedB: Promise
+ export const namedC: Promise
+ export const defaultA: Promise
+ export const defaultB: Promise
+ export const defaultC: Promise
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js
new file mode 100644
index 0000000000..d59f7789bd
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js
@@ -0,0 +1,14 @@
+exports.namedA = require('./a-named').value;
+// exports.namedB = require('./b-named.mjs').value;
+exports.namedC = require('./c-named.cjs').value;
+exports.defaultA = require('./a-default');
+// exports.defaultB = require('./b-default.mjs').default;
+exports.defaultC = require('./c-default.cjs');
+exports.dyn = {
+ namedA: import('./a-named').then(m => m.value),
+ namedB: import('./b-named.mjs').then(m => m.value),
+ namedC: import('./c-named.cjs').then(m => m.value),
+ defaultA: import('./a-default').then(m => m.default),
+ defaultB: import('./b-default.mjs').then(m => m.default),
+ defaultC: import('./c-default.cjs').then(m => m.default),
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json
new file mode 100644
index 0000000000..e3ceaa6fd7
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "dep-default",
+ "exports": {
+ ".": "./main.js"
+ },
+ "typesVersions": {
+ "*": {
+ "*": [
+ "main.d.ts"
+ ]
+ }
+ }
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js
new file mode 100644
index 0000000000..90bd54cd7f
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js
@@ -0,0 +1 @@
+export default 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js
new file mode 100644
index 0000000000..7fea2538a3
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js
@@ -0,0 +1 @@
+export const value = 'a'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs
new file mode 100644
index 0000000000..a3bb49043e
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs
@@ -0,0 +1 @@
+export default 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs
new file mode 100644
index 0000000000..18049c8488
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs
@@ -0,0 +1 @@
+export const value = 'b'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs
new file mode 100644
index 0000000000..7212f4d5a7
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs
@@ -0,0 +1 @@
+module.exports = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs
new file mode 100644
index 0000000000..c1dcb4b923
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs
@@ -0,0 +1 @@
+exports.value = 'c'
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts
new file mode 100644
index 0000000000..bdc1e24a02
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts
@@ -0,0 +1,15 @@
+export const namedA: string
+export const namedB: string
+export const namedC: string
+export const defaultA: string
+export const defaultB: string
+export const defaultC: string
+
+export namespace dyn {
+ export const namedA: Promise
+ export const namedB: Promise
+ export const namedC: Promise
+ export const defaultA: Promise
+ export const defaultB: Promise
+ export const defaultC: Promise
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js
new file mode 100644
index 0000000000..8b42a09d4a
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js
@@ -0,0 +1,14 @@
+export { value as namedA } from './a-named'
+export { value as namedB } from './b-named.mjs'
+export { value as namedC } from './c-named.cjs'
+export { default as defaultA } from './a-default'
+export { default as defaultB } from './b-default.mjs'
+export { default as defaultC } from './c-default.cjs'
+export const dyn = {
+ namedA: import('./a-named').then(m => m.value),
+ namedB: import('./b-named.mjs').then(m => m.value),
+ namedC: import('./c-named.cjs').then(m => m.value),
+ defaultA: import('./a-default').then(m => m.default),
+ defaultB: import('./b-default.mjs').then(m => m.default),
+ defaultC: import('./c-default.cjs').then(m => m.default),
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json
new file mode 100644
index 0000000000..17d9e50e5a
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dep-module",
+ "type": "module",
+ "exports": {
+ ".": "./main.js"
+ },
+ "typesVersions": {
+ "*": {
+ "*": [
+ "main.d.ts"
+ ]
+ }
+ }
+}
diff --git a/packages/canon/docs/components/Title/index.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts
similarity index 94%
rename from packages/canon/docs/components/Title/index.ts
rename to packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts
index 998dc81882..a3d64880a9 100644
--- a/packages/canon/docs/components/Title/index.ts
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { Title } from './Title';
+export default 'a';
diff --git a/packages/canon/docs/components/ComponentStatus/index.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts
similarity index 91%
rename from packages/canon/docs/components/ComponentStatus/index.ts
rename to packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts
index 238c001289..132570f2bb 100644
--- a/packages/canon/docs/components/ComponentStatus/index.ts
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { ComponentStatus } from './ComponentStatus';
+export const value = 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts
new file mode 100644
index 0000000000..57d07f2c30
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts
new file mode 100644
index 0000000000..be9bb39348
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts
new file mode 100644
index 0000000000..48f5b4f136
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts
new file mode 100644
index 0000000000..c3e5b58572
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts
new file mode 100644
index 0000000000..d6bdfb30d1
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as depCommonJs from 'dep-commonjs';
+// import * as depModule from 'dep-module';
+import * as depDefault from 'dep-default';
+import { value as namedA } from './a-named';
+// import { value as namedB } from './b-named.mts';
+import { value as namedC } from './c-named.cts';
+import { default as defaultA } from './a-default';
+// import { default as defaultB } from './b-default.mts';
+import { default as defaultC } from './c-default.cts';
+
+async function resolveAll(obj: object): Promise {
+ const val = await obj;
+ if (typeof val !== 'object' || val === null) {
+ return val;
+ }
+ if (Array.isArray(val)) {
+ return await Promise.all(val.map(resolveAll));
+ }
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(obj).map(async ([key, value]) => [
+ key,
+ await resolveAll(await value),
+ ]),
+ ),
+ );
+}
+
+export const values = resolveAll({
+ depCommonJs,
+ // depModule,
+ depDefault,
+ dynCommonJs: import('dep-commonjs'),
+ dynModule: import('dep-module'),
+ dynDefault: import('dep-default'),
+ dep: {
+ namedA,
+ // namedB,
+ namedC,
+ defaultA,
+ // defaultB,
+ defaultC,
+ },
+ dyn: {
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ namedA: import('./a-named').then(m => m.default.value),
+ namedB: import('./b-named.mts').then(m => m.value),
+ namedC: import('./c-named.cts').then(m => m.value),
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ defaultA: import('./a-default').then(m => m.default.default),
+ defaultB: import('./b-default.mts').then(m => m.default),
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ defaultC: import('./c-default.cts').then(m => m.default.default),
+ },
+});
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json
new file mode 100644
index 0000000000..b39408ee58
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "pkg-commonjs",
+ "type": "commonjs",
+ "exports": {
+ ".": "./main.ts",
+ "./print": "./print.ts"
+ }
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts
new file mode 100644
index 0000000000..7c24f99f37
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { values } from './main';
+
+values.then(obj => console.log(JSON.stringify(obj, null, 2)));
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts
new file mode 100644
index 0000000000..a3d64880a9
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts
new file mode 100644
index 0000000000..132570f2bb
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts
new file mode 100644
index 0000000000..57d07f2c30
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts
new file mode 100644
index 0000000000..be9bb39348
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts
new file mode 100644
index 0000000000..48f5b4f136
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts
new file mode 100644
index 0000000000..c3e5b58572
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts
new file mode 100644
index 0000000000..d6bdfb30d1
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as depCommonJs from 'dep-commonjs';
+// import * as depModule from 'dep-module';
+import * as depDefault from 'dep-default';
+import { value as namedA } from './a-named';
+// import { value as namedB } from './b-named.mts';
+import { value as namedC } from './c-named.cts';
+import { default as defaultA } from './a-default';
+// import { default as defaultB } from './b-default.mts';
+import { default as defaultC } from './c-default.cts';
+
+async function resolveAll(obj: object): Promise {
+ const val = await obj;
+ if (typeof val !== 'object' || val === null) {
+ return val;
+ }
+ if (Array.isArray(val)) {
+ return await Promise.all(val.map(resolveAll));
+ }
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(obj).map(async ([key, value]) => [
+ key,
+ await resolveAll(await value),
+ ]),
+ ),
+ );
+}
+
+export const values = resolveAll({
+ depCommonJs,
+ // depModule,
+ depDefault,
+ dynCommonJs: import('dep-commonjs'),
+ dynModule: import('dep-module'),
+ dynDefault: import('dep-default'),
+ dep: {
+ namedA,
+ // namedB,
+ namedC,
+ defaultA,
+ // defaultB,
+ defaultC,
+ },
+ dyn: {
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ namedA: import('./a-named').then(m => m.default.value),
+ namedB: import('./b-named.mts').then(m => m.value),
+ namedC: import('./c-named.cts').then(m => m.value),
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ defaultA: import('./a-default').then(m => m.default.default),
+ defaultB: import('./b-default.mts').then(m => m.default),
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ defaultC: import('./c-default.cts').then(m => m.default.default),
+ },
+});
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json
new file mode 100644
index 0000000000..5b0b075a3c
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "pkg-default",
+ "exports": {
+ ".": "./main.ts",
+ "./print": "./print.ts"
+ }
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts
new file mode 100644
index 0000000000..7c24f99f37
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { values } from './main';
+
+values.then(obj => console.log(JSON.stringify(obj, null, 2)));
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts
new file mode 100644
index 0000000000..a3d64880a9
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts
new file mode 100644
index 0000000000..a3d64880a9
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts
new file mode 100644
index 0000000000..132570f2bb
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts
new file mode 100644
index 0000000000..132570f2bb
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'a';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts
new file mode 100644
index 0000000000..57d07f2c30
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts
new file mode 100644
index 0000000000..be9bb39348
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'b';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts
new file mode 100644
index 0000000000..48f5b4f136
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts
new file mode 100644
index 0000000000..c3e5b58572
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const value = 'c';
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts
new file mode 100644
index 0000000000..7f45ea506c
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @ts-nocheck
+
+import * as depCommonJs from 'dep-commonjs';
+import * as depModule from 'dep-module';
+import * as depDefault from 'dep-default';
+import { value as namedA } from './a-named-explicit';
+import { value as namedB } from './b-named';
+import cNamed from './c-named';
+import defaultA from './a-default-explicit';
+import defaultB from './b-default';
+import cDefault from './c-default';
+
+const { default: defaultC } = cDefault;
+const { value: namedC } = cNamed;
+
+async function resolveAll(obj): Promise {
+ const val = await obj;
+ if (typeof val !== 'object' || val === null) {
+ return val;
+ }
+ if (Array.isArray(val)) {
+ return await Promise.all(val.map(resolveAll));
+ }
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(obj).map(async ([key, value]) => [
+ key,
+ await resolveAll(await value),
+ ]),
+ ),
+ );
+}
+
+export const values = resolveAll({
+ depCommonJs,
+ depModule,
+ depDefault,
+ dynCommonJs: import('dep-commonjs'),
+ dynModule: import('dep-module'),
+ dynDefault: import('dep-default'),
+ dep: {
+ namedA,
+ namedB,
+ namedC,
+ defaultA,
+ defaultB,
+ defaultC,
+ },
+ dyn: {
+ namedA: import('./a-named-explicit').then(m => m.value),
+ namedB: import('./b-named').then(m => m.value),
+ namedC: import('./c-named').then(m => m.default.value),
+ defaultA: import('./a-default-explicit').then(m => m.default),
+ defaultB: import('./b-default').then(m => m.default),
+ defaultC: import('./c-default').then(m => m.default.default),
+ },
+});
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts
new file mode 100644
index 0000000000..a4133495c7
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as depCommonJs from 'dep-commonjs';
+import * as depModule from 'dep-module';
+import * as depDefault from 'dep-default';
+import { value as namedA } from './a-named';
+import { value as namedB } from './b-named.mts';
+import { value as namedC } from './c-named.cts';
+import defaultA from './a-default';
+import defaultB from './b-default.mts';
+import cDefault from './c-default.cts';
+
+// @ts-expect-error Default exports from CommonJS are not well supported
+const { default: defaultC } = cDefault;
+
+async function resolveAll(obj: object): Promise {
+ const val = await obj;
+ if (typeof val !== 'object' || val === null) {
+ return val;
+ }
+ if (Array.isArray(val)) {
+ return await Promise.all(val.map(resolveAll));
+ }
+ return Object.fromEntries(
+ await Promise.all(
+ Object.entries(obj).map(async ([key, value]) => [
+ key,
+ await resolveAll(await value),
+ ]),
+ ),
+ );
+}
+
+export const values = resolveAll({
+ depCommonJs,
+ depModule,
+ depDefault,
+ dynCommonJs: import('dep-commonjs'),
+ dynModule: import('dep-module'),
+ dynDefault: import('dep-default'),
+ dep: {
+ namedA,
+ namedB,
+ namedC,
+ defaultA,
+ defaultB,
+ defaultC,
+ },
+ dyn: {
+ namedA: import('./a-named').then(m => m.value),
+ namedB: import('./b-named.mts').then(m => m.value),
+ namedC: import('./c-named.cts').then(m => m.value),
+ defaultA: import('./a-default').then(m => m.default),
+ defaultB: import('./b-default.mts').then(m => m.default),
+ // @ts-expect-error Default exports from CommonJS are not well supported
+ defaultC: import('./c-default.cts').then(m => m.default.default),
+ },
+});
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json
new file mode 100644
index 0000000000..f7b7e1ab30
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "pkg-module",
+ "type": "module",
+ "exports": {
+ ".": "./main.ts",
+ "./print": "./print.ts"
+ }
+}
diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts
new file mode 100644
index 0000000000..7c24f99f37
--- /dev/null
+++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { values } from './main';
+
+values.then(obj => console.log(JSON.stringify(obj, null, 2)));
diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts
new file mode 100644
index 0000000000..52af9a47d3
--- /dev/null
+++ b/packages/cli/src/tests/transforms/transforms.test.ts
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { execFileSync } from 'child_process';
+import { resolve as resolvePath } from 'path';
+import { Output, buildPackage } from '../../lib/builder';
+
+const exportValues = {
+ all: {
+ namedA: 'a',
+ namedB: 'b',
+ namedC: 'c',
+ defaultA: 'a',
+ defaultB: 'b',
+ defaultC: 'c',
+ },
+ commonJs: {
+ namedA: 'a',
+ namedC: 'c',
+ defaultA: 'a',
+ defaultC: 'c',
+ },
+};
+
+const expectedExports = {
+ commonJs: {
+ ...exportValues.commonJs,
+ dyn: exportValues.all,
+ default: {
+ ...exportValues.commonJs,
+ dyn: exportValues.all,
+ },
+ },
+ module: {
+ ...exportValues.all,
+ dyn: exportValues.all,
+ },
+};
+
+function loadFixture(fixture: string) {
+ const output = execFileSync(
+ 'node',
+ [
+ '--import',
+ '@backstage/cli/config/nodeTransform.cjs',
+ resolvePath(__dirname, `__fixtures__/${fixture}`),
+ ],
+ { encoding: 'utf8' },
+ );
+ return JSON.parse(output);
+}
+
+describe('node runtime module transforms', () => {
+ it('should load from commonjs format', async () => {
+ expect(loadFixture('pkg-commonjs/print.ts')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should load from default format', async () => {
+ expect(loadFixture('pkg-default/print.ts')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should load from module format', async () => {
+ expect(loadFixture('pkg-module/print.ts')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ depModule: expectedExports.module,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.all,
+ dyn: exportValues.all,
+ });
+ });
+});
+
+describe('Jest runtime module transforms', () => {
+ it('should load from commonjs format', async () => {
+ const values = await import('./__fixtures__/pkg-commonjs/main').then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should load from default format', async () => {
+ const values = await import('./__fixtures__/pkg-default/main').then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should load from module format', async () => {
+ // This uses a separate entry point with an explicit .mts extension. This is
+ // because we can't cleanly switch the Jest behavior based on type=module in
+ // package.json for .ts files in Jest. If a module type is detected we
+ // instead need to switch the transforms for the entire Jest project, which
+ // we can't do for this test. We instead use the explicit .mts extension to
+ // verify the transform behavior.
+
+ // @ts-expect-error Cannot find module './__fixtures__/pkg-module/main-explicit' or its corresponding type declarations.
+ const values = await import('./__fixtures__/pkg-module/main-explicit').then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ depModule: expectedExports.module,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.all,
+ dyn: exportValues.all,
+ });
+ });
+});
+
+describe('package build transforms', () => {
+ it('should build and load from commonjs format', async () => {
+ const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-commonjs');
+
+ await buildPackage({
+ targetDir: pkgPath,
+ outputs: new Set([Output.cjs]),
+ workspacePackages: [],
+ });
+ const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+
+ expect(loadFixture('pkg-commonjs/dist/print.cjs.js')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should build and load from default format', async () => {
+ const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-default');
+
+ await buildPackage({
+ targetDir: pkgPath,
+ outputs: new Set([Output.cjs]),
+ workspacePackages: [],
+ });
+ const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+
+ expect(loadFixture('pkg-default/dist/print.cjs.js')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.commonJs,
+ dyn: exportValues.all,
+ });
+ });
+
+ it('should build and load from module format', async () => {
+ const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-module');
+
+ await buildPackage({
+ targetDir: pkgPath,
+ outputs: new Set([Output.cjs]),
+ workspacePackages: [],
+ });
+ const values = await import(resolvePath(pkgPath, 'dist/index.mjs')).then(
+ m => m.values,
+ );
+ expect(values).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ depModule: expectedExports.module,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.all,
+ dyn: exportValues.all,
+ });
+
+ expect(loadFixture('pkg-module/dist/print.mjs')).toEqual({
+ depCommonJs: expectedExports.commonJs,
+ depDefault: expectedExports.commonJs,
+ depModule: expectedExports.module,
+ dynCommonJs: expectedExports.commonJs,
+ dynDefault: expectedExports.commonJs,
+ dynModule: expectedExports.module,
+ dep: exportValues.all,
+ dyn: exportValues.all,
+ });
+ });
+});
diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts
index 9fe8bb8084..5acb88151f 100644
--- a/packages/cli/src/wiring/CliInitializer.ts
+++ b/packages/cli/src/wiring/CliInitializer.ts
@@ -22,16 +22,23 @@ import { version } from '../lib/version';
import chalk from 'chalk';
import { exitWithError } from '../lib/errors';
import { assertError } from '@backstage/errors';
+import { isPromise } from 'util/types';
-type UninitializedFeature = CliFeature | Promise;
+type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>;
export class CliInitializer {
private graph = new CommandGraph();
private commandRegistry = new CommandRegistry(this.graph);
#uninitiazedFeatures: Promise[] = [];
- add(module: UninitializedFeature) {
- this.#uninitiazedFeatures.push(Promise.resolve(module));
+ add(feature: UninitializedFeature) {
+ if (isPromise(feature)) {
+ this.#uninitiazedFeatures.push(
+ feature.then(f => unwrapFeature(f.default)),
+ );
+ } else {
+ this.#uninitiazedFeatures.push(Promise.resolve(feature));
+ }
}
async #register(feature: CliFeature) {
@@ -136,3 +143,22 @@ function isCliPlugin(feature: CliFeature): feature is InternalCliPlugin {
// Backwards compatibility for v1 registrations that use duck typing
return 'plugin' in internal;
}
+
+/** @internal */
+export function unwrapFeature(
+ feature: CliFeature | { default: CliFeature },
+): CliFeature {
+ if ('$$type' in feature) {
+ return feature;
+ }
+
+ // This is a workaround where default exports get transpiled to `exports['default'] = ...`
+ // in CommonJS modules, which in turn results in a double `{ default: { default: ... } }` nesting
+ // when importing using a dynamic import.
+ // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS.
+ if ('default' in feature) {
+ return feature.default;
+ }
+
+ return feature;
+}
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index fd99a9cac4..8a6d14c7e6 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/config-loader
+## 1.9.5
+
+### Patch Changes
+
+- 8ecf8cb: Exclude `@backstage/backend-common` from schema collection if `@backstage/backend-defaults` is present
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 1.9.5-next.1
### Patch Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index c964e01c41..30b8d28145 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config-loader",
- "version": "1.9.5-next.1",
+ "version": "1.9.5",
"description": "Config loading functionality used by Backstage backend, and CLI",
"backstage": {
"role": "node-library"
diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts
index c7af64e0f3..16bd654946 100644
--- a/packages/config-loader/src/schema/collect.ts
+++ b/packages/config-loader/src/schema/collect.ts
@@ -183,9 +183,8 @@ async function compileTsSchemas(
// Lazy loaded, because this brings up all of TypeScript and we don't
// want that eagerly loaded in tests
- const { getProgramFromFiles, buildGenerator } = await import(
- 'typescript-json-schema'
- );
+ const { getProgramFromFiles, buildGenerator } =
+ require('typescript-json-schema') as typeof import('typescript-json-schema');
const program = getProgramFromFiles(
entries.map(({ path }) => path),
diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md
index 6a1758386d..00c940e206 100644
--- a/packages/config/CHANGELOG.md
+++ b/packages/config/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/config
+## 1.3.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/errors@1.2.7
+
## 1.3.2-next.0
### Patch Changes
diff --git a/packages/config/package.json b/packages/config/package.json
index 37e011f363..e4ace9bdd8 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config",
- "version": "1.3.2-next.0",
+ "version": "1.3.2",
"description": "Config API used by Backstage core, backend, and CLI",
"backstage": {
"role": "common-library"
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index b4fcc236c8..827cf2e1f2 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/core-app-api
+## 1.15.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/types@1.2.1
+ - @backstage/config@1.3.2
+ - @backstage/version-bridge@1.0.10
+
## 1.15.4-next.0
### Patch Changes
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 9cb1f68af1..d2adb5cf14 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-app-api",
- "version": "1.15.4-next.0",
+ "version": "1.15.4",
"description": "Core app API used by Backstage apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md
index 4feac45ec7..c9fb596a0e 100644
--- a/packages/core-compat-api/CHANGELOG.md
+++ b/packages/core-compat-api/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/core-compat-api
+## 0.3.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.9.4
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/version-bridge@1.0.10
+
## 0.3.5-next.0
### Patch Changes
diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json
index 4c9409356c..9233f3dddc 100644
--- a/packages/core-compat-api/package.json
+++ b/packages/core-compat-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-compat-api",
- "version": "0.3.5-next.0",
+ "version": "0.3.5",
"backstage": {
"role": "web-library"
},
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 2fd2cb6973..8f27d49e4c 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/core-components
+## 0.16.3
+
+### Patch Changes
+
+- 4ec6f7b: Allow passing component for `ContentHeader` description
+- Updated dependencies
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.6.3
+ - @backstage/version-bridge@1.0.10
+
## 0.16.3-next.0
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 5689b9e4f7..13d3785c4d 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-components",
- "version": "0.16.3-next.0",
+ "version": "0.16.3",
"description": "Core components used by Backstage plugins and apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx
index e2a6e70620..1e8ab5e3ff 100644
--- a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx
+++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx
@@ -84,10 +84,30 @@ const ContentHeaderTitle = ({ title, className }: ContentHeaderTitleProps) => (
);
+type ContentHeaderDescriptionProps = {
+ description?: string;
+ className?: string;
+};
+
+const ContentHeaderDescription = ({
+ description,
+ className,
+}: ContentHeaderDescriptionProps) =>
+ description ? (
+
+ {description}
+
+ ) : null;
+
type ContentHeaderProps = {
title?: ContentHeaderTitleProps['title'];
titleComponent?: ReactNode;
- description?: string;
+ description?: ContentHeaderDescriptionProps['description'];
+ descriptionComponent?: ReactNode;
textAlign?: 'left' | 'right' | 'center';
};
@@ -104,6 +124,7 @@ export function ContentHeader(props: PropsWithChildren) {
title,
titleComponent: TitleComponent = undefined,
children,
+ descriptionComponent: DescriptionComponent = undefined,
textAlign = 'left',
} = props;
const classes = useStyles({ textAlign })();
@@ -114,17 +135,22 @@ export function ContentHeader(props: PropsWithChildren) {
);
+ const renderedDescription = DescriptionComponent ? (
+ DescriptionComponent
+ ) : (
+
+ );
+
return (
<>
{renderedTitle}
- {description && (
-
- {description}
-
- )}
+ {renderedDescription}
{children}
diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md
index bb29bc4a10..4dfac74ff8 100644
--- a/packages/core-plugin-api/CHANGELOG.md
+++ b/packages/core-plugin-api/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/core-plugin-api
+## 1.10.3
+
+### Patch Changes
+
+- b40eb41: Move `Expand` and `ExpandRecursive` to `@backstage/types`
+- Updated dependencies
+ - @backstage/types@1.2.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/version-bridge@1.0.10
+
## 1.10.3-next.0
### Patch Changes
diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json
index e3959b3990..3e1122da49 100644
--- a/packages/core-plugin-api/package.json
+++ b/packages/core-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-plugin-api",
- "version": "1.10.3-next.0",
+ "version": "1.10.3",
"description": "Core API used by Backstage plugins",
"backstage": {
"role": "web-library"
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index e6b2cda208..dace3d49f0 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/create-app
+## 0.5.24
+
+### Patch Changes
+
+- 2e3fbc1: Bumped create-app version.
+- 0980e40: Bumped create-app version.
+- 828c993: Added `--jobs unlimited` to `dev` script to help cases where the backend does not start up during local development
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
## 0.5.24-next.2
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index df834fe126..ddcc526e0b 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/create-app",
- "version": "0.5.24-next.2",
+ "version": "0.5.24",
"description": "A CLI that helps you create your own Backstage app",
"backstage": {
"role": "cli"
diff --git a/packages/create-app/seed-yarn.lock b/packages/create-app/seed-yarn.lock
index 00a86105cc..21b2aa543d 100644
--- a/packages/create-app/seed-yarn.lock
+++ b/packages/create-app/seed-yarn.lock
@@ -16,3 +16,28 @@
// package: the name of the package, e.g. @testing-library/react
// query: the version query to pin the version for, e.g. ^14.0.0
// version: the version to pin to, must be in range of the query, e.g. 14.11.0
+
+"@google-cloud/storage@^7.0.0":
+ version "7.14.0"
+ resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.14.0.tgz#eda9715f68507949214af804c906eba6d168a214"
+ integrity sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==
+
+"@octokit/types@npm:^13.0.0":
+ version "13.6.2"
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
+ integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
+
+"@octokit/types@npm:^13.1.0":
+ version "13.6.2"
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
+ integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
+
+"@octokit/types@npm:^13.5.0":
+ version "13.6.2"
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
+ integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
+
+"@octokit/openapi-types@^22.2.0":
+ version "22.2.0"
+ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e"
+ integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==
diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs
index ea2a438429..7b9413e63e 100644
--- a/packages/create-app/templates/default-app/package.json.hbs
+++ b/packages/create-app/templates/default-app/package.json.hbs
@@ -6,7 +6,7 @@
"node": "20 || 22"
},
"scripts": {
- "dev": "yarn workspaces foreach -A --include backend --include app --parallel -j 2 -v -i run start",
+ "dev": "yarn workspaces foreach -A --include backend --include app --parallel --jobs unlimited -v -i run start",
"start": "yarn workspace app start",
"start-backend": "yarn workspace backend start",
"build:backend": "yarn workspace backend build",
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index 4166201ca7..ba7ba1ca6d 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/dev-utils
+## 1.1.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.15.1
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/core-components@0.16.3
+ - @backstage/app-defaults@1.5.16
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.15.4
+ - @backstage/integration-react@1.2.3
+ - @backstage/theme@0.6.3
+
## 1.1.6-next.1
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index 9af46d1f91..0dcb7a182f 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/dev-utils",
- "version": "1.1.6-next.1",
+ "version": "1.1.6",
"description": "Utilities for developing Backstage plugins.",
"backstage": {
"role": "web-library"
diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md
index 87fe1442c0..e933205e09 100644
--- a/packages/e2e-test/CHANGELOG.md
+++ b/packages/e2e-test/CHANGELOG.md
@@ -1,5 +1,14 @@
# e2e-test
+## 0.2.24
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.24
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
## 0.2.24-next.2
### Patch Changes
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index 548aedb4f5..358b42962f 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,6 +1,6 @@
{
"name": "e2e-test",
- "version": "0.2.24-next.2",
+ "version": "0.2.24",
"description": "E2E test for verifying Backstage packages",
"backstage": {
"role": "cli"
diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md
index 3f78104038..fa187b9229 100644
--- a/packages/errors/CHANGELOG.md
+++ b/packages/errors/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/errors
+## 1.2.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/types@1.2.1
+
## 1.2.7-next.0
### Patch Changes
diff --git a/packages/errors/package.json b/packages/errors/package.json
index 021067f1cd..d90e791d4c 100644
--- a/packages/errors/package.json
+++ b/packages/errors/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/errors",
- "version": "1.2.7-next.0",
+ "version": "1.2.7",
"description": "Common utilities for error handling within Backstage",
"backstage": {
"role": "common-library"
diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md
index 5e97506f00..8e0e696242 100644
--- a/packages/frontend-app-api/CHANGELOG.md
+++ b/packages/frontend-app-api/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/frontend-app-api
+## 0.10.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.9.4
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/types@1.2.1
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.15.4
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-defaults@0.1.5
+ - @backstage/version-bridge@1.0.10
+
## 0.10.4-next.0
### Patch Changes
diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index 44f4248f8b..7df0489c2c 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-app-api",
- "version": "0.10.4-next.0",
+ "version": "0.10.4",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
index e3f06a896d..5616346d75 100644
--- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
@@ -31,6 +31,7 @@ import {
RouteResolutionApi,
createApiFactory,
routeResolutionApiRef,
+ AppNode,
} from '@backstage/frontend-plugin-api';
import {
AnyApiFactory,
@@ -68,7 +69,8 @@ import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
import { BackstageRouteObject } from '../routing/types';
-import { FrontendFeature } from './types';
+import { FrontendFeature, RouteInfo } from './types';
+import { matchRoutes } from 'react-router-dom';
function deduplicateFeatures(
allFeatures: FrontendFeature[],
@@ -95,21 +97,47 @@ function deduplicateFeatures(
// Helps delay callers from reaching out to the API before the app tree has been materialized
class AppTreeApiProxy implements AppTreeApi {
- #safeToUse: boolean = false;
+ #routeInfo?: RouteInfo;
- constructor(private readonly tree: AppTree) {}
+ constructor(
+ private readonly tree: AppTree,
+ private readonly appBasePath: string,
+ ) {}
- getTree() {
- if (!this.#safeToUse) {
+ private checkIfInitialized() {
+ if (!this.#routeInfo) {
throw new Error(
`You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,
);
}
+ }
+
+ getTree() {
+ this.checkIfInitialized();
+
return { tree: this.tree };
}
- initialize() {
- this.#safeToUse = true;
+ getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] } {
+ this.checkIfInitialized();
+
+ let path = sourcePath;
+ if (path.startsWith(this.appBasePath)) {
+ path = path.slice(this.appBasePath.length);
+ }
+
+ const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);
+
+ const matchedAppNodes =
+ matchedRoutes
+ ?.filter(routeObj => !!routeObj.route.appNode)
+ .map(routeObj => routeObj.route.appNode!) || [];
+
+ return { nodes: matchedAppNodes };
+ }
+
+ initialize(routeInfo: RouteInfo) {
+ this.#routeInfo = routeInfo;
}
}
@@ -119,12 +147,11 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
#routeObjects: BackstageRouteObject[] | undefined;
constructor(
- private readonly tree: AppTree,
private readonly routeBindings: Map<
ExternalRouteRef,
RouteRef | SubRouteRef
>,
- private readonly basePath: string,
+ private readonly appBasePath: string,
) {}
resolve(
@@ -143,15 +170,13 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
return this.#delegate.resolve(anyRouteRef, options);
}
- initialize() {
- const routeInfo = extractRouteInfoFromAppNode(this.tree.root);
-
+ initialize(routeInfo: RouteInfo) {
this.#delegate = new RouteResolver(
routeInfo.routePaths,
routeInfo.routeParents,
routeInfo.routeObjects,
this.routeBindings,
- this.basePath,
+ this.appBasePath,
);
this.#routeObjects = routeInfo.routeObjects;
@@ -190,15 +215,15 @@ export function createSpecializedApp(options?: {
);
const factories = createApiFactories({ tree });
- const appTreeApi = new AppTreeApiProxy(tree);
+ const appBasePath = getBasePath(config);
+ const appTreeApi = new AppTreeApiProxy(tree, appBasePath);
const routeResolutionApi = new RouteResolutionApiProxy(
- tree,
resolveRouteBindings(
options?.bindRoutes,
config,
collectRouteIds(features),
),
- getBasePath(config),
+ appBasePath,
);
const appIdentityProxy = new AppIdentityProxy();
@@ -237,8 +262,10 @@ export function createSpecializedApp(options?: {
// Now instantiate the entire tree, which will skip anything that's already been instantiated
instantiateAppNodeTree(tree.root, apiHolder);
- routeResolutionApi.initialize();
- appTreeApi.initialize();
+ const routeInfo = extractRouteInfoFromAppNode(tree.root);
+
+ routeResolutionApi.initialize(routeInfo);
+ appTreeApi.initialize(routeInfo);
const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement);
diff --git a/packages/frontend-app-api/src/wiring/types.ts b/packages/frontend-app-api/src/wiring/types.ts
index fb887534e3..18d9c51d0e 100644
--- a/packages/frontend-app-api/src/wiring/types.ts
+++ b/packages/frontend-app-api/src/wiring/types.ts
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { RouteRef } from '@backstage/frontend-plugin-api';
import { FrontendModule, FrontendPlugin } from '@backstage/frontend-plugin-api';
+import { BackstageRouteObject } from '../routing/types';
/** @public */
export type FrontendFeature =
@@ -22,3 +24,10 @@ export type FrontendFeature =
// TODO(blam): This is just forwards backwards compatibility, remove after v1.31.0
| { $$type: '@backstage/ExtensionOverrides' }
| { $$type: '@backstage/BackstagePlugin' };
+
+/** @internal */
+export type RouteInfo = {
+ routePaths: Map;
+ routeParents: Map;
+ routeObjects: BackstageRouteObject[];
+};
diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md
index 91b44e3747..5dbbac9e0a 100644
--- a/packages/frontend-defaults/CHANGELOG.md
+++ b/packages/frontend-defaults/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/frontend-defaults
+## 0.1.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.9.4
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-app-api@0.10.4
+ - @backstage/plugin-app@0.1.5
+
## 0.1.5-next.0
### Patch Changes
diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json
index 2db3d377ea..c12fedf1c1 100644
--- a/packages/frontend-defaults/package.json
+++ b/packages/frontend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-defaults",
- "version": "0.1.5-next.0",
+ "version": "0.1.5",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md
index 8c9a4e222a..c1e2445ca9 100644
--- a/packages/frontend-internal/CHANGELOG.md
+++ b/packages/frontend-internal/CHANGELOG.md
@@ -1,5 +1,14 @@
# @internal/frontend
+## 0.0.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.9.4
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.10
+
## 0.0.5-next.0
### Patch Changes
diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json
index 7e5cb9ed74..454cc86b8f 100644
--- a/packages/frontend-internal/package.json
+++ b/packages/frontend-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/frontend",
- "version": "0.0.5-next.0",
+ "version": "0.0.5",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md
index 9a0edc47d8..ab820da3a8 100644
--- a/packages/frontend-plugin-api/CHANGELOG.md
+++ b/packages/frontend-plugin-api/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/frontend-plugin-api
+## 0.9.4
+
+### Patch Changes
+
+- b40eb41: Move `Expand` and `ExpandRecursive` to `@backstage/types`
+- Updated dependencies
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/types@1.2.1
+ - @backstage/core-components@0.16.3
+ - @backstage/version-bridge@1.0.10
+
## 0.9.4-next.0
### Patch Changes
diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json
index 0c45b10693..06b676ff7d 100644
--- a/packages/frontend-plugin-api/package.json
+++ b/packages/frontend-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-plugin-api",
- "version": "0.9.4-next.0",
+ "version": "0.9.4",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md
index 24c2ff6d3b..6202eeaaa2 100644
--- a/packages/frontend-plugin-api/report.api.md
+++ b/packages/frontend-plugin-api/report.api.md
@@ -300,6 +300,9 @@ export interface AppTree {
// @public
export interface AppTreeApi {
+ getNodesByRoutePath(sourcePath: string): {
+ nodes: AppNode[];
+ };
getTree(): {
tree: AppTree;
};
diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
index 5f25eddead..6ced6706b9 100644
--- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
+++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
@@ -104,6 +104,11 @@ export interface AppTreeApi {
* Get the {@link AppTree} for the app.
*/
getTree(): { tree: AppTree };
+
+ /**
+ * Get all nodes in the app that are mounted at a given route path.
+ */
+ getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] };
}
/**
diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md
index bb9408eed9..7519df4559 100644
--- a/packages/frontend-test-utils/CHANGELOG.md
+++ b/packages/frontend-test-utils/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/frontend-test-utils
+## 0.2.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.9.4
+ - @backstage/types@1.2.1
+ - @backstage/config@1.3.2
+ - @backstage/frontend-app-api@0.10.4
+ - @backstage/test-utils@1.7.4
+ - @backstage/version-bridge@1.0.10
+ - @backstage/plugin-app@0.1.5
+
## 0.2.5-next.0
### Patch Changes
diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json
index 87dd5937e9..f2c4226568 100644
--- a/packages/frontend-test-utils/package.json
+++ b/packages/frontend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-test-utils",
- "version": "0.2.5-next.0",
+ "version": "0.2.5",
"backstage": {
"role": "web-library"
},
diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md
index 93241411fb..67060c78c2 100644
--- a/packages/integration-aws-node/CHANGELOG.md
+++ b/packages/integration-aws-node/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/integration-aws-node
+## 0.1.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.1.15-next.0
### Patch Changes
diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json
index e6054cf1cf..28cf435fd7 100644
--- a/packages/integration-aws-node/package.json
+++ b/packages/integration-aws-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration-aws-node",
- "version": "0.1.15-next.0",
+ "version": "0.1.15",
"description": "Helpers for fetching AWS account credentials",
"backstage": {
"role": "node-library"
diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md
index 3e285a1498..c6382221f2 100644
--- a/packages/integration-react/CHANGELOG.md
+++ b/packages/integration-react/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/integration-react
+## 1.2.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/integration@1.16.1
+ - @backstage/config@1.3.2
+
## 1.2.3-next.0
### Patch Changes
diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json
index 53ad6f1bf7..2dc998703a 100644
--- a/packages/integration-react/package.json
+++ b/packages/integration-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration-react",
- "version": "1.2.3-next.0",
+ "version": "1.2.3",
"description": "Frontend package for managing integrations towards external systems",
"backstage": {
"role": "web-library"
diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md
index cf58088ec2..3e9c912904 100644
--- a/packages/integration/CHANGELOG.md
+++ b/packages/integration/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/integration
+## 1.16.1
+
+### Patch Changes
+
+- d2b16db: A new Gerrit helper function (`buildGerritGitilesArchiveUrlFromLocation`) has been added. This
+ constructs a Gitiles URL to download an archive. It is similar to the existing
+ `buildGerritGitilesArchiveUrl` but also support content referenced by a full commit `SHA`.
+
+ **DEPRECATIONS**: The function `buildGerritGitilesArchiveUrl` is deprecated, use the
+ `buildGerritGitilesArchiveUrlFromLocation` function instead.
+
+ **DEPRECATIONS**: The function `parseGerritGitilesUrl` is deprecated, use the
+ `parseGitilesUrlRef` function instead.
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 1.16.1-next.0
### Patch Changes
diff --git a/packages/integration/package.json b/packages/integration/package.json
index 23b5924a43..a51850ed9c 100644
--- a/packages/integration/package.json
+++ b/packages/integration/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
- "version": "1.16.1-next.0",
+ "version": "1.16.1",
"description": "Helpers for managing integrations towards external systems",
"backstage": {
"role": "common-library"
diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md
index 5129dbb7c2..6bb48b8185 100644
--- a/packages/integration/report.api.md
+++ b/packages/integration/report.api.md
@@ -299,7 +299,7 @@ export type BitbucketServerIntegrationConfig = {
password?: string;
};
-// @public
+// @public @deprecated
export function buildGerritGitilesArchiveUrl(
config: GerritIntegrationConfig,
project: string,
@@ -307,6 +307,12 @@ export function buildGerritGitilesArchiveUrl(
filePath: string,
): string;
+// @public
+export function buildGerritGitilesArchiveUrlFromLocation(
+ config: GerritIntegrationConfig,
+ url: string,
+): string;
+
// @public
export class DefaultAzureCredentialsManager implements AzureCredentialsManager {
static fromIntegrations(
@@ -802,7 +808,7 @@ export interface IntegrationsByType {
harness: ScmIntegrationsGroup;
}
-// @public
+// @public @deprecated
export function parseGerritGitilesUrl(
config: GerritIntegrationConfig,
url: string,
diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts
index c031612c40..5899196f65 100644
--- a/packages/integration/src/gerrit/core.test.ts
+++ b/packages/integration/src/gerrit/core.test.ts
@@ -21,6 +21,7 @@ import { registerMswTestHooks } from '../helpers';
import { GerritIntegrationConfig } from './config';
import {
buildGerritGitilesArchiveUrl,
+ buildGerritGitilesArchiveUrlFromLocation,
buildGerritGitilesUrl,
getGerritBranchApiUrl,
getGerritCloneRepoUrl,
@@ -115,6 +116,94 @@ describe('gerrit core', () => {
});
});
+ describe('buildGerritGitilesArchiveUrlFromLocation', () => {
+ const config: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ baseUrl: 'https://gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com/gitiles',
+ };
+ const configWithPath: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ baseUrl: 'https://gerrit.com/gerrit',
+ gitilesBaseUrl: 'https://gerrit.com/gerrit/plugins/gitiles',
+ };
+ const configWithDedicatedGitiles: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ baseUrl: 'https://gerrit.com/gerrit',
+ gitilesBaseUrl: 'https://dedicated-gitiles-server.com/gerrit/gitiles',
+ };
+ it('can create an archive url for a branch', () => {
+ expect(
+ buildGerritGitilesArchiveUrlFromLocation(
+ config,
+ 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/',
+ ),
+ ).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz',
+ );
+ });
+ it('can create an archive url for a sha', () => {
+ expect(
+ buildGerritGitilesArchiveUrlFromLocation(
+ config,
+ 'https://gerrit.com/gitiles/repo/+/2846e8dc327ae2f60249983b1c3b96f42f205bae/',
+ ),
+ ).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/2846e8dc327ae2f60249983b1c3b96f42f205bae.tar.gz',
+ );
+ });
+ it('can create an archive url for a sha with a specific directory', () => {
+ expect(
+ buildGerritGitilesArchiveUrlFromLocation(
+ config,
+ 'https://gerrit.com/gitiles/repo/+/2846e8dc327ae2f60249983b1c3b96f42f205bae/docs',
+ ),
+ ).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/2846e8dc327ae2f60249983b1c3b96f42f205bae/docs.tar.gz',
+ );
+ });
+ it('can create an archive url for a specific directory', () => {
+ expect(
+ buildGerritGitilesArchiveUrlFromLocation(
+ config,
+ 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/docs/',
+ ),
+ ).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
+ );
+ });
+ it('can create an authenticated url when auth is enabled and an url-path is used', () => {
+ const authConfig = {
+ ...configWithPath,
+ username: 'username',
+ password: 'password',
+ };
+ expect(
+ buildGerritGitilesArchiveUrlFromLocation(
+ authConfig,
+ 'https://gerrit.com/gerrit/plugins/gitiles/repo/+/refs/heads/dev/docs/',
+ ),
+ ).toEqual(
+ 'https://gerrit.com/gerrit/a/plugins/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
+ );
+ });
+ it('Cannot build an authenticated url when a dedicated Gitiles server is used', () => {
+ const authConfig = {
+ ...configWithDedicatedGitiles,
+ username: 'username',
+ password: 'password',
+ };
+ expect(() =>
+ buildGerritGitilesArchiveUrlFromLocation(
+ authConfig,
+ 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/',
+ ),
+ ).toThrow(
+ 'Since the baseUrl (Gerrit) is not part of the gitilesBaseUrl, an authentication URL could not be constructed.',
+ );
+ });
+ });
+
describe('buildGerritGitilesUrl', () => {
it('can create an url from arguments', () => {
const config: GerritIntegrationConfig = {
@@ -426,6 +515,39 @@ describe('gerrit core', () => {
'https://gerrit.com/a/projects/web%2Fproject/branches/master/files/README.md/content',
);
});
+ it('can create an authenticated url for a commit.', () => {
+ const authConfig: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ baseUrl: 'https://gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com',
+ username: 'u',
+ password: 'u',
+ };
+
+ const authFileContentUrl = getGerritFileContentsApiUrl(
+ authConfig,
+ 'https://gerrit.com/web/project/+/157f862803d45b9d269f0e390f88aece1ded51e8/README.md',
+ );
+ expect(authFileContentUrl).toEqual(
+ 'https://gerrit.com/a/projects/web%2Fproject/commits/157f862803d45b9d269f0e390f88aece1ded51e8/files/README.md/content',
+ );
+ });
+ it('will throw for unsupported ref types (tag).', () => {
+ const authConfig: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ baseUrl: 'https://gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com',
+ username: 'u',
+ password: 'u',
+ };
+
+ expect(() =>
+ getGerritFileContentsApiUrl(
+ authConfig,
+ 'https://gerrit.com/modules/events-broker/+/refs/tags/v3.5.6/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
+ ),
+ ).toThrow(/gitiles ref type/);
+ });
});
describe('parseGerritJsonResponse', () => {
diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts
index e50ce97bbb..d44865881d 100644
--- a/packages/integration/src/gerrit/core.ts
+++ b/packages/integration/src/gerrit/core.ts
@@ -41,8 +41,9 @@ const GERRIT_BODY_PREFIX = ")]}'";
*
* @param url - An URL pointing to a file stored in git.
* @public
+ * @deprecated `parseGerritGitilesUrl` is deprecated. Use
+ * {@link parseGitilesUrlRef} instead.
*/
-
export function parseGerritGitilesUrl(
config: GerritIntegrationConfig,
url: string,
@@ -215,6 +216,8 @@ export function buildGerritGitilesUrl(
* @param branch - The branch we will target.
* @param filePath - The absolute file path.
* @public
+ * @deprecated `buildGerritGitilesArchiveUrl` is deprecated. Use
+ * {@link buildGerritGitilesArchiveUrlFromLocation} instead.
*/
export function buildGerritGitilesArchiveUrl(
config: GerritIntegrationConfig,
@@ -229,6 +232,38 @@ export function buildGerritGitilesArchiveUrl(
)}/${project}/+archive/refs/heads/${branch}${archiveName}`;
}
+/**
+ * Build a Gerrit Gitiles archive url from a Gitiles url.
+ *
+ * @param config - A Gerrit provider config.
+ * @param url - The gitiles url
+ * @public
+ */
+export function buildGerritGitilesArchiveUrlFromLocation(
+ config: GerritIntegrationConfig,
+ url: string,
+): string {
+ const {
+ path: filePath,
+ ref,
+ project,
+ refType,
+ } = parseGitilesUrlRef(config, url);
+ const archiveName =
+ filePath === '/' || filePath === '' ? '.tar.gz' : `/${filePath}.tar.gz`;
+ if (refType === 'branch') {
+ return `${getGitilesAuthenticationUrl(
+ config,
+ )}/${project}/+archive/refs/heads/${ref}${archiveName}`;
+ }
+ if (refType === 'sha') {
+ return `${getGitilesAuthenticationUrl(
+ config,
+ )}/${project}/+archive/${ref}${archiveName}`;
+ }
+ throw new Error(`Unsupported gitiles ref type: ${refType}`);
+}
+
/**
* Return the authentication prefix.
*
@@ -324,13 +359,25 @@ export function getGerritFileContentsApiUrl(
config: GerritIntegrationConfig,
url: string,
) {
- const { branch, filePath, project } = parseGerritGitilesUrl(config, url);
+ const { ref, refType, path, project } = parseGitilesUrlRef(config, url);
- return `${config.baseUrl}${getAuthenticationPrefix(
- config,
- )}projects/${encodeURIComponent(
- project,
- )}/branches/${branch}/files/${encodeURIComponent(filePath)}/content`;
+ // https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content
+ if (refType === 'branch') {
+ return `${config.baseUrl}${getAuthenticationPrefix(
+ config,
+ )}projects/${encodeURIComponent(
+ project,
+ )}/branches/${ref}/files/${encodeURIComponent(path)}/content`;
+ }
+ // https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content-from-commit
+ if (refType === 'sha') {
+ return `${config.baseUrl}${getAuthenticationPrefix(
+ config,
+ )}projects/${encodeURIComponent(
+ project,
+ )}/commits/${ref}/files/${encodeURIComponent(path)}/content`;
+ }
+ throw new Error(`Unsupported gitiles ref type: ${refType}`);
}
/**
diff --git a/packages/integration/src/gerrit/index.ts b/packages/integration/src/gerrit/index.ts
index f2e5934c95..3df52bf02b 100644
--- a/packages/integration/src/gerrit/index.ts
+++ b/packages/integration/src/gerrit/index.ts
@@ -20,6 +20,7 @@ export {
} from './config';
export {
buildGerritGitilesArchiveUrl,
+ buildGerritGitilesArchiveUrlFromLocation,
getGerritBranchApiUrl,
getGerritCloneRepoUrl,
getGerritFileContentsApiUrl,
diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md
index 66425b0ebf..23c4e3135b 100644
--- a/packages/repo-tools/CHANGELOG.md
+++ b/packages/repo-tools/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/repo-tools
+## 0.12.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.9.5
+ - @backstage/backend-plugin-api@1.1.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.12
+ - @backstage/errors@1.2.7
+
## 0.12.1-next.1
### Patch Changes
diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md
index da483fe016..672c22944a 100644
--- a/packages/repo-tools/cli-report.md
+++ b/packages/repo-tools/cli-report.md
@@ -33,6 +33,7 @@ Options:
--ci
--tsc
--docs
+ --sql-reports
--include
--exclude
-a, --allow-warnings
diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json
index 8b5ba736c8..f5c6a0c060 100644
--- a/packages/repo-tools/package.json
+++ b/packages/repo-tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/repo-tools",
- "version": "0.12.1-next.1",
+ "version": "0.12.1",
"description": "CLI for Backstage repo tooling ",
"backstage": {
"role": "cli"
@@ -49,6 +49,7 @@
"@backstage/cli-node": "workspace:^",
"@backstage/config-loader": "workspace:^",
"@backstage/errors": "workspace:^",
+ "@electric-sql/pglite": "^0.2.15",
"@manypkg/get-packages": "^1.1.3",
"@microsoft/api-documenter": "^7.25.7",
"@microsoft/api-extractor": "^7.47.2",
@@ -70,6 +71,9 @@
"glob": "^8.0.3",
"is-glob": "^4.0.3",
"js-yaml": "^4.1.0",
+ "just-diff": "^6.0.2",
+ "knex": "^3.0.0",
+ "knex-pglite": "^0.11.0",
"lodash": "^4.17.21",
"minimatch": "^9.0.0",
"p-limit": "^3.0.2",
diff --git a/packages/repo-tools/src/commands/api-reports/api-extractor.ts b/packages/repo-tools/src/commands/api-reports/api-extractor.ts
deleted file mode 100644
index c55410ce4a..0000000000
--- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts
+++ /dev/null
@@ -1,1515 +0,0 @@
-/*
- * Copyright 2021 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { groupBy } from 'lodash';
-import {
- basename,
- join,
- relative as relativePath,
- resolve as resolvePath,
-} from 'path';
-import fs from 'fs-extra';
-import {
- CompilerState,
- Extractor,
- ExtractorConfig,
- ExtractorLogLevel,
- ExtractorMessage,
-} from '@microsoft/api-extractor';
-import { Program } from 'typescript';
-import {
- DocBlockTag,
- DocLinkTag,
- DocNode,
- DocPlainText,
- IDocNodeContainerParameters,
- Standardization,
- TSDocConfiguration,
- TSDocTagSyntaxKind,
-} from '@microsoft/tsdoc';
-import { TSDocConfigFile } from '@microsoft/tsdoc-config';
-import {
- ApiItem,
- ApiItemKind,
- ApiModel,
- ApiPackage,
-} from '@microsoft/api-extractor-model';
-import {
- IMarkdownDocumenterOptions,
- MarkdownDocumenter,
-} from '@microsoft/api-documenter/lib/documenters/MarkdownDocumenter';
-import { DocTable } from '@microsoft/api-documenter/lib/nodes/DocTable';
-import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
-import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
-import {
- CustomMarkdownEmitter,
- ICustomMarkdownEmitterOptions,
-} from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
-import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
-import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
-import { paths as cliPaths } from '../../lib/paths';
-import { minimatch } from 'minimatch';
-import { getPackageExportDetails } from '../../lib/getPackageExportDetails';
-import { createBinRunner } from '../util';
-
-const tmpDir = cliPaths.resolveTargetRoot(
- './node_modules/.cache/api-extractor',
-);
-
-/**
- * All of this monkey patching below is for apply prettier to the API reports. This has to be patched into
- * the middle of the process as API Extractor does a comparison of the contents of the old
- * and new files during generation. This inserts the formatting just before that comparison.
- */
-const {
- ApiReportGenerator,
-} = require('@microsoft/api-extractor/lib/generators/ApiReportGenerator');
-
-function patchFileMessageFetcher(
- router: any,
- transform: (messages: ExtractorMessage[], ast?: AstDeclaration) => void,
-) {
- const {
- fetchAssociatedMessagesForReviewFile,
- fetchUnassociatedMessagesForReviewFile,
- } = router;
-
- router.fetchAssociatedMessagesForReviewFile =
- function patchedFetchAssociatedMessagesForReviewFile(
- ast: AstDeclaration | undefined,
- ) {
- const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
- return transform(messages, ast);
- };
- router.fetchUnassociatedMessagesForReviewFile =
- function patchedFetchUnassociatedMessagesForReviewFile() {
- const messages = fetchUnassociatedMessagesForReviewFile.call(this);
- return transform(messages);
- };
-}
-
-const originalGenerateReviewFileContent =
- ApiReportGenerator.generateReviewFileContent;
-ApiReportGenerator.generateReviewFileContent =
- function decoratedGenerateReviewFileContent(
- collector: { program: Program; messageRouter: any },
- ...moreArgs: any[]
- ) {
- const program = collector.program as Program;
- // The purpose of this override is to allow the @ignore tag to be used to ignore warnings
- // of the form "Warning: (ae-forgotten-export) The symbol "FooBar" needs to be exported by the entry point index.d.ts"
- patchFileMessageFetcher(
- collector.messageRouter,
- (messages: ExtractorMessage[]) => {
- return messages.filter(message => {
- if (message.messageId !== 'ae-forgotten-export') {
- return true;
- }
-
- // Symbol name has to be extracted from the message :(
- // There's frequently no AST for these exports because type literals
- // aren't traversed by the generator.
- const symbolMatch = message.text.match(/The symbol "([^"]+)"/);
- if (!symbolMatch) {
- throw new Error(
- `Failed to extract symbol name from message "${message.text}"`,
- );
- }
- const [, symbolName] = symbolMatch;
-
- const sourceFile =
- message.sourceFilePath &&
- program.getSourceFile(message.sourceFilePath);
- if (!sourceFile) {
- throw new Error(
- `Failed to find source file in program at path "${message.sourceFilePath}"`,
- );
- }
-
- // The local name of the symbol within the file, rather than the exported name
- let localName = (sourceFile as any).identifiers?.get(symbolName);
-
- if (!localName) {
- // Sometimes the symbol name is suffixed with a number to disambiguate,
- // e.g. "Props_14" instead of "Props" if there are multiple Props interfaces
- // so we try to strip that suffix and look up the symbol again.
- const [, trimmedSymbolName] = symbolName.match(/(.*)_\d+/) || [];
- localName = (sourceFile as any).identifiers?.get(trimmedSymbolName);
- }
-
- if (!localName) {
- throw new Error(
- `Unable to find local name of "${symbolName}" in ${sourceFile.fileName}`,
- );
- }
-
- // The local AST node of the export that we're missing
- const local = (sourceFile as any).locals?.get(localName);
- if (!local) {
- return true;
- }
-
- // Use the type checker to look up the actual declaration(s) rather than the one in the local file
- const type = program.getTypeChecker().getDeclaredTypeOfSymbol(local);
- if (!type) {
- throw new Error(
- `Unable to find type declaration of "${symbolName}" in ${sourceFile.fileName}`,
- );
- }
- const declarations = type.aliasSymbol?.declarations;
- if (!declarations || declarations.length === 0) {
- return true;
- }
-
- // If any of the TSDoc comments contain a @ignore tag, we ignore this message
- const isIgnored = declarations.some(declaration => {
- const tags = [(declaration as any).jsDoc]
- .flat()
- .filter(Boolean)
- .flatMap((tagNode: any) => tagNode.tags);
-
- return tags.some(tag => tag?.tagName.text === 'ignore');
- });
-
- return !isIgnored;
- });
- },
- );
-
- const content = originalGenerateReviewFileContent.call(
- this,
- collector,
- ...moreArgs,
- );
-
- try {
- const prettier = require('prettier') as typeof import('prettier');
-
- const config = prettier.resolveConfig.sync(cliPaths.targetRoot) ?? {};
- return prettier.format(content, {
- ...config,
- parser: 'markdown',
- });
- } catch (e) {
- return content;
- }
- };
-
-export async function createTemporaryTsConfig(includedPackageDirs: string[]) {
- const path = cliPaths.resolveTargetRoot('tsconfig.tmp.json');
-
- process.once('exit', () => {
- fs.removeSync(path);
- });
-
- let assetTypeFile: string[] = [];
-
- try {
- assetTypeFile = [
- require.resolve('@backstage/cli/asset-types/asset-types.d.ts'),
- ];
- } catch {
- /** ignore */
- }
-
- await fs.writeJson(path, {
- extends: './tsconfig.json',
- include: [
- // These two contain global definitions that are needed for stable API report generation
- ...assetTypeFile,
- ...includedPackageDirs.map(dir => join(dir, 'src')),
- ],
- // we don't exclude node_modules so that we can use the asset-types.d.ts file
- exclude: [],
- });
-
- return path;
-}
-
-export async function countApiReportWarnings(reportPath: string) {
- try {
- const content = await fs.readFile(reportPath, 'utf8');
- const lines = content.split('\n');
-
- const lineWarnings = lines.filter(line =>
- line.includes('// Warning:'),
- ).length;
-
- const trailerStart = lines.findIndex(
- line => line === '// Warnings were encountered during analysis:',
- );
- const trailerWarnings =
- trailerStart === -1
- ? 0
- : lines.length -
- trailerStart -
- 4; /* 4 lines at the trailer and after are not warnings */
-
- return lineWarnings + trailerWarnings;
- } catch (error) {
- if (error.code === 'ENOENT') {
- return 0;
- }
- throw error;
- }
-}
-
-export async function getTsDocConfig() {
- const tsdocConfigFile = await TSDocConfigFile.loadFile(
- require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
- );
- tsdocConfigFile.addTagDefinition({
- tagName: '@ignore',
- syntaxKind: TSDocTagSyntaxKind.ModifierTag,
- });
- tsdocConfigFile.addTagDefinition({
- tagName: '@config',
- syntaxKind: TSDocTagSyntaxKind.BlockTag,
- });
- tsdocConfigFile.setSupportForTag('@ignore', true);
- tsdocConfigFile.setSupportForTag('@config', true);
- return tsdocConfigFile;
-}
-
-function logApiReportInstructions() {
- console.log('');
- console.log(
- '*************************************************************************************',
- );
- console.log(
- '* You have uncommitted changes to the public API or reports of a package. *',
- );
- console.log(
- '* To solve this, run `yarn build:api-reports` and commit all md file changes. *',
- );
- console.log(
- '*************************************************************************************',
- );
- console.log('');
-}
-
-async function findPackageEntryPoints(packageDirs: string[]): Promise<
- Array<{
- // package dir relative to root, e.g. "packages/backend-app-api"
- packageDir: string;
- // the name of the export, e.g. "index" or "alpha"
- name: string;
- // the path within the dist directory for this export, e.g. "alpha.d.ts"
- distPath: string;
- // the path within the dist-types directory of this package for this export,
- // e.g. "src/entrypoints/foo/index.d.ts"
- distTypesPath: string;
- }>
-> {
- return Promise.all(
- packageDirs.map(async packageDir => {
- const pkg = await fs.readJson(
- cliPaths.resolveTargetRoot(packageDir, 'package.json'),
- );
-
- return getPackageExportDetails(pkg).map(details => {
- return { packageDir, ...details };
- });
- }),
- ).then(results => results.flat());
-}
-
-interface ApiExtractionOptions {
- packageDirs: string[];
- outputDir: string;
- isLocalBuild: boolean;
- tsconfigFilePath: string;
- allowWarnings?: boolean | string[];
- omitMessages?: string[];
- validateReleaseTags?: boolean;
-}
-
-export async function runApiExtraction({
- packageDirs,
- outputDir,
- isLocalBuild,
- tsconfigFilePath,
- allowWarnings = false,
- omitMessages = [],
- validateReleaseTags = false,
-}: ApiExtractionOptions) {
- await fs.remove(outputDir);
-
- // The collection of all entry points of all packages, as a single list
- const allEntryPoints = await findPackageEntryPoints(packageDirs);
-
- // The path (relative to the root) to ALL dist-types entry points (e.g.
- // "dist-types/packages/backend-app-api/src/index.d.ts"). These are used as
- // "extra"/contextual entry points for the extractor so that it can see the
- // full context of things that are required by the local entry point being
- // inspected.
- const allDistTypesEntryPointPaths = allEntryPoints.map(
- ({ packageDir, distTypesPath }) => {
- return cliPaths.resolveTargetRoot(
- './dist-types',
- packageDir,
- distTypesPath,
- );
- },
- );
-
- let compilerState: CompilerState | undefined = undefined;
-
- const allowWarningPkg = Array.isArray(allowWarnings) ? allowWarnings : [];
-
- const messagesConf: { [key: string]: { logLevel: string } } = {};
- for (const messageCode of omitMessages) {
- messagesConf[messageCode] = {
- logLevel: 'none',
- };
- }
-
- const warnings = new Array();
-
- for (const [packageDir, packageEntryPoints] of Object.entries(
- groupBy(allEntryPoints, ep => ep.packageDir),
- )) {
- console.log(`## Processing ${packageDir}`);
- const noBail = Array.isArray(allowWarnings)
- ? allowWarnings.some(aw => aw === packageDir || minimatch(packageDir, aw))
- : allowWarnings;
-
- const projectFolder = cliPaths.resolveTargetRoot(packageDir);
- const packageFolder = cliPaths.resolveTargetRoot(
- './dist-types',
- packageDir,
- );
-
- const remainingReportFiles = new Set(
- fs.readdirSync(projectFolder).filter(
- filename =>
- // https://regex101.com/r/QDZIV0/2
- filename !== 'knip-report.md' &&
- // this has to temporarily match all old api report formats
- filename.match(/^.*?(api-)?report(-[^.-]+)?(.*?)\.md$/),
- ),
- );
-
- for (const packageEntryPoint of packageEntryPoints) {
- const suffix =
- packageEntryPoint.name === 'index' ? '' : `-${packageEntryPoint.name}`;
- const reportFileName = `report${suffix}`;
- const reportPath = resolvePath(projectFolder, `${reportFileName}.api.md`);
-
- const warningCountBefore = await countApiReportWarnings(reportPath);
-
- const extractorConfig = ExtractorConfig.prepare({
- configObject: {
- mainEntryPointFilePath: resolvePath(
- packageFolder,
- packageEntryPoint.distTypesPath,
- ),
- bundledPackages: [],
-
- compiler: {
- tsconfigFilePath,
- },
-
- apiReport: {
- enabled: true,
- reportFileName,
- reportFolder: projectFolder,
- reportTempFolder: resolvePath(
- outputDir,
- `${suffix}`,
- ),
- },
-
- docModel: {
- // TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but
- // most likely it makes sense to wait for API Extractor to natively support exports.
- enabled: packageEntryPoint.name === 'index',
- apiJsonFilePath: resolvePath(
- outputDir,
- `${suffix}.api.json`,
- ),
- },
-
- dtsRollup: {
- enabled: false,
- },
-
- tsdocMetadata: {
- enabled: false,
- },
-
- messages: {
- // Silence compiler warnings, as these will prevent the CI build to work
- compilerMessageReporting: {
- default: {
- logLevel: 'none' as ExtractorLogLevel.None,
- // These contain absolute file paths, so can't be included in the report
- // addToApiReportFile: true,
- },
- },
- extractorMessageReporting: {
- default: {
- logLevel: 'warning' as ExtractorLogLevel.Warning,
- addToApiReportFile: true,
- },
- ...messagesConf,
- },
- tsdocMessageReporting: {
- default: {
- logLevel: 'warning' as ExtractorLogLevel.Warning,
- addToApiReportFile: true,
- },
- },
- },
-
- newlineKind: 'lf',
-
- projectFolder,
- },
- configObjectFullPath: projectFolder,
- packageJsonFullPath: resolvePath(projectFolder, 'package.json'),
- tsdocConfigFile: await getTsDocConfig(),
- ignoreMissingEntryPoint: true,
- });
-
- // remove extracted reports from current list
- for (const reportConfig of extractorConfig.reportConfigs) {
- remainingReportFiles.delete(reportConfig.fileName);
- }
-
- // The `packageFolder` needs to point to the location within `dist-types` in order for relative
- // paths to be logged. Unfortunately the `prepare` method above derives it from the `packageJsonFullPath`,
- // which needs to point to the actual file, so we override `packageFolder` afterwards.
- (
- extractorConfig as {
- packageFolder: string;
- }
- ).packageFolder = packageFolder;
-
- if (!compilerState) {
- compilerState = CompilerState.create(extractorConfig, {
- additionalEntryPoints: allDistTypesEntryPointPaths,
- });
- }
-
- // Message verbosity can't be configured, so just skip the check instead
- (Extractor as any)._checkCompilerCompatibility = () => {};
-
- let shouldLogInstructions = false;
- let conflictingFile: undefined | string = undefined;
-
- // Invoke API Extractor
- const extractorResult = Extractor.invoke(extractorConfig, {
- localBuild: isLocalBuild,
- showVerboseMessages: false,
- showDiagnostics: false,
- messageCallback(message) {
- if (message.text.includes('The API report file is missing')) {
- shouldLogInstructions = true;
- }
-
- // Detect messages like the following being output by the generator:
- // Warning: You have changed the API signature for this project. Please copy the file "/home/runner/work/backstage/backstage/node_modules/.cache/api-extractor/backend-test-utils/report.api.md" to "report.api.md", or perform a local build (which does this automatically). See the Git repo documentation for more info.
- if (
- message.text.includes(
- 'You have changed the API signature for this project.',
- )
- ) {
- shouldLogInstructions = true;
- const match = message.text.match(
- /Please copy the file "(.*)" to "report\.api\.md"/,
- );
- if (match) {
- conflictingFile = match[1];
- }
- }
- },
- compilerState,
- });
-
- // This release tag validation makes sure that the release tag of known entry points match expectations.
- // The root index entry point is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta.
- if (
- validateReleaseTags &&
- fs.pathExistsSync(extractorConfig.reportFilePath)
- ) {
- if (['index', 'alpha', 'beta'].includes(packageEntryPoint.name)) {
- const report = await fs.readFile(
- extractorConfig.reportFilePath,
- 'utf8',
- );
- const lines = report.split(/\r?\n/);
- const expectedTag =
- packageEntryPoint.name === 'index'
- ? 'public'
- : packageEntryPoint.name;
- for (let i = 0; i < lines.length; i += 1) {
- const line = lines[i];
- const match = line.match(/^\/\/ @(alpha|beta|public)/);
- if (match && match[1] !== expectedTag) {
- // Because of limitations in the type script rollup logic we need to allow public exports from the other release stages
- // TODO(Rugvip): Try to work around the need for this exception
- if (expectedTag !== 'public' && match[1] === 'public') {
- continue;
- }
- throw new Error(
- `Unexpected release tag ${match[1]} in ${
- extractorConfig.reportFilePath
- } at line ${i + 1}`,
- );
- }
- }
- }
- }
-
- if (!extractorResult.succeeded) {
- if (shouldLogInstructions) {
- logApiReportInstructions();
-
- if (conflictingFile) {
- console.log('');
- console.log(
- `The conflicting file is ${relativePath(
- tmpDir,
- conflictingFile,
- )}, with the following content:`,
- );
- console.log('');
-
- const content = await fs.readFile(conflictingFile, 'utf8');
- console.log(content);
-
- logApiReportInstructions();
- }
- }
-
- throw new Error(
- `API Extractor completed with ${extractorResult.errorCount} errors` +
- ` and ${extractorResult.warningCount} warnings`,
- );
- }
-
- const warningCountAfter = await countApiReportWarnings(reportPath);
-
- if (noBail) {
- console.log(`Skipping warnings check for ${packageDir}`);
- }
- if (warningCountAfter > 0 && !noBail) {
- throw new Error(
- `The API Report for ${packageDir} is not allowed to have warnings`,
- );
- }
- if (warningCountAfter === 0 && allowWarningPkg.includes(packageDir)) {
- console.log(
- `No need to allow warnings for ${packageDir}, it does not have any`,
- );
- }
- if (warningCountAfter > warningCountBefore) {
- warnings.push(
- `The API Report for ${packageDir} introduces new warnings. ` +
- 'Please fix these warnings in order to keep the API Reports tidy.',
- );
- }
- }
-
- if (remainingReportFiles.size > 0) {
- if (isLocalBuild) {
- for (const f of remainingReportFiles) {
- fs.rmSync(resolvePath(projectFolder, f));
- console.log(`Deleted deprecated API report ${f}`);
- }
- } else {
- const staleList = [...remainingReportFiles]
- .map(f => join(packageDir, f))
- .join(', ');
- throw new Error(
- `The API Report(s) ${staleList} are no longer relevant and should be deleted`,
- );
- }
- }
- }
-
- if (warnings.length > 0) {
- console.warn();
- for (const warning of warnings) {
- console.warn(warning);
- }
- console.warn();
- }
-}
-
-/*
-WARNING: Bring a blanket if you're gonna read the code below
-
-There's some weird shit going on here, and it's because we cba
-forking rushstack to modify the api-documenter markdown generation,
-which otherwise is the recommended way to do customizations.
-*/
-
-type ExcerptToken = {
- kind: string;
- text: string;
- canonicalReference?: string;
-};
-
-class ExcerptTokenMatcher {
- readonly #tokens: ExcerptToken[];
-
- constructor(tokens: ExcerptToken[]) {
- this.#tokens = tokens.slice();
- }
-
- nextContent() {
- const token = this.#tokens.shift();
- if (token?.kind === 'Content') {
- return token.text;
- }
- return undefined;
- }
-
- matchContent(expectedText: string) {
- const text = this.nextContent();
- return text !== expectedText;
- }
-
- getTokensUntilArrow() {
- const tokens = [];
- for (;;) {
- const token = this.#tokens.shift();
- if (token === undefined) {
- return undefined;
- }
- if (token.kind === 'Content' && token.text === ') => ') {
- return tokens;
- }
- tokens.push(token);
- }
- }
-
- getComponentReturnTokens() {
- const first = this.#tokens.shift();
- if (!first) {
- return undefined;
- }
- const second = this.#tokens.shift();
-
- if (this.#tokens.length !== 0) {
- return undefined;
- }
- if (first.kind !== 'Reference' || first.text !== 'JSX.Element') {
- return undefined;
- }
- if (!second) {
- return [first];
- } else if (second.kind === 'Content' && second.text === ' | null') {
- return [first, second];
- }
- return undefined;
- }
-}
-
-class ApiModelTransforms {
- static deserializeWithTransforms(
- serialized: any,
- transforms: Array<(member: any) => any>,
- ): ApiPackage {
- if (serialized.kind !== 'Package') {
- throw new Error(
- `Unexpected root kind in serialized ApiPackage, ${serialized.kind}`,
- );
- }
- if (serialized.members.length !== 1) {
- throw new Error(
- `Unexpected members in serialized ApiPackage, [${serialized.members
- .map((m: { kind: any }) => m.kind)
- .join(' ')}]`,
- );
- }
- const [entryPoint] = serialized.members;
- if (entryPoint.kind !== 'EntryPoint') {
- throw new Error(
- `Unexpected kind in serialized ApiPackage member, ${entryPoint.kind}`,
- );
- }
-
- const transformed = {
- ...serialized,
- members: [
- {
- ...entryPoint,
- members: entryPoint.members.map((member: any) =>
- transforms.reduce((m, t) => t(m), member),
- ),
- },
- ],
- };
-
- return ApiPackage.deserialize(
- transformed,
- transformed.metadata,
- ) as ApiPackage;
- }
-
- static transformArrowComponents = (member: any) => {
- if (member.kind !== 'Variable') {
- return member;
- }
-
- const { name, excerptTokens } = member;
-
- // First letter in name must be uppercase
- const [firstChar] = name;
- if (firstChar.toLocaleUpperCase('en-US') !== firstChar) {
- return member;
- }
-
- // First content must match expected declaration format
- const tokens = new ExcerptTokenMatcher(excerptTokens);
- if (tokens.nextContent() !== `${name}: `) {
- return member;
- }
-
- // Next needs to be an arrow with `props` parameters or no parameters
- // followed by a return type of `JSX.Element | null` or just `JSX.Element`
- const declStart = tokens.nextContent();
- if (declStart === '(props: ' || declStart === '(_props: ') {
- const props = tokens.getTokensUntilArrow();
- const ret = tokens.getComponentReturnTokens();
- if (props && ret) {
- return this.makeComponentMember(member, ret, props);
- }
- } else if (declStart === '() => ') {
- const ret = tokens.getComponentReturnTokens();
- if (ret) {
- return this.makeComponentMember(member, ret);
- }
- }
- return member;
- };
-
- static makeComponentMember(
- member: any,
- ret: ExcerptToken[],
- props?: ExcerptToken[],
- ) {
- const declTokens = props
- ? [
- {
- kind: 'Content',
- text: `export declare function ${member.name}(props: `,
- },
- ...props,
- {
- kind: 'Content',
- text: '): ',
- },
- ]
- : [
- {
- kind: 'Content',
- text: `export declare function ${member.name}(): `,
- },
- ];
-
- return {
- kind: 'Function',
- name: member.name,
- releaseTag: member.releaseTag,
- docComment: member.docComment ?? '',
- canonicalReference: member.canonicalReference,
- excerptTokens: [...declTokens, ...ret],
- returnTypeTokenRange: {
- startIndex: declTokens.length,
- endIndex: declTokens.length + ret.length,
- },
- parameters: props
- ? [
- {
- parameterName: 'props',
- parameterTypeTokenRange: {
- startIndex: 1,
- endIndex: 1 + props.length,
- },
- },
- ]
- : [],
- overloadIndex: 1,
- };
- }
-
- static transformTrimDeclare = (member: any) => {
- const { excerptTokens } = member;
- const firstContent = new ExcerptTokenMatcher(excerptTokens).nextContent();
- if (firstContent && firstContent.startsWith('export declare ')) {
- return {
- ...member,
- excerptTokens: [
- {
- kind: 'Content',
- text: firstContent.slice('export declare '.length),
- },
- ...excerptTokens.slice(1),
- ],
- };
- }
- return member;
- };
-}
-
-export async function buildDocs({
- inputDir,
- outputDir,
-}: {
- inputDir: string;
- outputDir: string;
-}) {
- // We start by constructing our own model from the files so that
- // we get a change to modify them, as the model is otherwise read-only.
- const parseFile = async (filename: string): Promise => {
- console.log(`Reading ${filename}`);
- return fs.readJson(resolvePath(inputDir, filename));
- };
-
- const filenames = await fs.readdir(inputDir);
- const serializedPackages = await Promise.all(
- filenames
- .filter(filename => filename.match(/\.api\.json$/i))
- .map(parseFile),
- );
-
- const newModel = new ApiModel();
- for (const serialized of serializedPackages) {
- newModel.addMember(
- ApiModelTransforms.deserializeWithTransforms(serialized, [
- ApiModelTransforms.transformArrowComponents,
- ApiModelTransforms.transformTrimDeclare,
- ]),
- );
- }
-
- // The doc AST need to be extended with custom nodes if we want to
- // add any extra content.
- // This one is for the YAML front matter that we need for the microsite.
- class DocFrontMatter extends DocNode {
- static kind = 'DocFrontMatter';
-
- public readonly values: { [name: string]: unknown };
-
- public constructor(
- parameters: IDocNodeContainerParameters & {
- values: { [name: string]: unknown };
- },
- ) {
- super(parameters);
- this.values = parameters.values;
- }
-
- /** @override */
- public get kind(): string {
- return DocFrontMatter.kind;
- }
- }
-
- // This class only propose is to have a different kind and be able to render links with backticks
- class DocCodeSpanLink extends DocLinkTag {
- static kind = 'DocCodeSpanLink';
-
- /** @override */
- public get kind(): string {
- return DocCodeSpanLink.kind;
- }
- }
-
- // This is where we actually write the markdown and where we can hook
- // in the rendering of our own nodes.
- class CustomCustomMarkdownEmitter extends CustomMarkdownEmitter {
- // Until https://github.com/microsoft/rushstack/issues/2914 gets fixed or we change markdown renderer we need a fix
- // to render pipe | character correctly.
- protected getEscapedText(text: string): string {
- return text
- .replace(/\\/g, '\\\\') // first replace the escape character
- .replace(/[*#[\]_`~]/g, x => `\\${x}`) // then escape any special characters
- .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/\|/g, '|');
- }
- /** @override */
- protected writeNode(
- docNode: DocNode,
- context: IMarkdownEmitterContext,
- docNodeSiblings: boolean,
- ): void {
- switch (docNode.kind) {
- case DocFrontMatter.kind: {
- const node = docNode as DocFrontMatter;
- context.writer.writeLine('---');
- for (const [name, value] of Object.entries(node.values)) {
- if (value) {
- context.writer.writeLine(
- `${name}: "${String(value).replace(/\"/g, '')}"`,
- );
- }
- }
- context.writer.writeLine('---');
- context.writer.writeLine();
- break;
- }
- case 'BlockTag': {
- const node = docNode as DocBlockTag;
- if (node.tagName === '@config') {
- context.writer.writeLine('## Related config ');
- }
- break;
- }
- case DocCodeSpanLink.kind: {
- const node = docNode as DocLinkTag;
- if (node.codeDestination) {
- // TODO @sarabadu understand if we need `codeDestination` at all on this custom DocCodeSpanLink
- super.writeLinkTagWithCodeDestination(node, context);
- } else if (node.urlDestination) {
- const linkText =
- node.linkText !== undefined ? node.linkText : node.urlDestination;
- const encodedLinkText = this.getEscapedText(
- linkText.replace(/\s+/g, ' '),
- );
- context.writer.write('[');
- context.writer.write(`\`${encodedLinkText}\``);
- context.writer.write(`](${node.urlDestination})`);
- } else if (node.linkText) {
- this.writePlainText(node.linkText, context);
- }
- break;
- }
- default:
- super.writeNode(docNode, context, docNodeSiblings);
- }
- }
-
- /** @override */
- emit(
- stringBuilder: any,
- docNode: DocNode,
- options: ICustomMarkdownEmitterOptions,
- ) {
- // Hack to get rid of the leading comment of each file, since
- // we want the front matter to come first
- stringBuilder._chunks.length = 0;
- return super.emit(stringBuilder, docNode, options);
- }
- }
-
- class CustomMarkdownDocumenter extends (MarkdownDocumenter as any) {
- constructor(options: IMarkdownDocumenterOptions) {
- super(options);
-
- // It's a strict model, we gotta register the allowed usage of our new node
- (
- this._tsdocConfiguration as TSDocConfiguration
- ).docNodeManager.registerDocNodes('@backstage/docs', [
- { docNodeKind: DocFrontMatter.kind, constructor: DocFrontMatter },
- ]);
- (
- this._tsdocConfiguration as TSDocConfiguration
- ).docNodeManager.registerDocNodes('@backstage/docs', [
- { docNodeKind: DocCodeSpanLink.kind, constructor: DocCodeSpanLink },
- ]);
- (
- this._tsdocConfiguration as TSDocConfiguration
- ).docNodeManager.registerAllowableChildren('Paragraph', [
- DocFrontMatter.kind,
- DocCodeSpanLink.kind,
- ]);
-
- const def = {
- tagName: '@config',
- syntaxKind: TSDocTagSyntaxKind.BlockTag,
- tagNameWithUpperCase: '@CONFIG',
- standardization: Standardization.Extended,
- allowMultiple: false,
- };
- (this._tsdocConfiguration as TSDocConfiguration).addTagDefinition(def);
- (this._tsdocConfiguration as TSDocConfiguration).setSupportForTag(
- def,
- true,
- );
- this._markdownEmitter = new CustomCustomMarkdownEmitter(newModel);
- }
-
- private _getFilenameForApiItem(apiItem: ApiItem): string {
- const filename: string = super._getFilenameForApiItem(apiItem);
-
- if (filename.includes('.html.')) {
- return filename.replace(/\.html\./g, '._html.');
- }
-
- return filename;
- }
-
- // We don't really get many chances to modify the generated AST
- // so we hook in wherever we can. In this case we add the front matter
- // just before writing the breadcrumbs at the top.
- /** @override */
- _writeBreadcrumb(output: any, apiItem: ApiItem & { name: string }) {
- let title;
- let description;
-
- const name = apiItem.getScopedNameWithinPackage();
- if (name) {
- title = name;
- description = `API reference for ${apiItem.getScopedNameWithinPackage()}`;
- } else if (apiItem.kind === 'Model') {
- title = 'Package Index';
- description = 'Index of all Backstage Packages';
- } else if (apiItem.name) {
- title = apiItem.name;
- description = `API Reference for ${apiItem.name}`;
- } else {
- title = apiItem.displayName;
- description = `API Reference for ${apiItem.displayName}`;
- }
-
- // Add our front matter
- output.appendNodeInParagraph(
- new DocFrontMatter({
- configuration: this._tsdocConfiguration,
- values: {
- id: this._getFilenameForApiItem(apiItem).slice(0, -3),
- title,
- description,
- },
- }),
- );
-
- const configuration: TSDocConfiguration = this._tsdocConfiguration;
-
- output.appendNodeInParagraph(
- new DocLinkTag({
- configuration,
- tagName: '@link',
- linkText: 'Home',
- urlDestination: this._getLinkFilenameForApiItem(this._apiModel),
- }),
- );
-
- for (const hierarchyItem of apiItem.getHierarchy()) {
- switch (hierarchyItem.kind) {
- case ApiItemKind.Model:
- case ApiItemKind.EntryPoint:
- // We don't show the model as part of the breadcrumb because it is the root-level container.
- // We don't show the entry point because today API Extractor doesn't support multiple entry points;
- // this may change in the future.
- break;
- default:
- output.appendNodesInParagraph([
- new DocPlainText({
- configuration,
- text: ' > ',
- }),
- new DocCodeSpanLink({
- configuration,
- tagName: '@link',
- linkText: hierarchyItem.displayName,
- urlDestination: this._getLinkFilenameForApiItem(hierarchyItem),
- }),
- ]);
- }
- }
-
- // We wanna ignore the header that always gets written after the breadcrumb
- // This otherwise becomes more or less a duplicate of the title in the front matter
- const oldAppendNode = output.appendNode;
- output.appendNode = () => {
- output.appendNode = oldAppendNode;
- };
- }
-
- _writeModelTable(
- output: { appendNode: (arg0: DocTable | DocHeading) => void },
- apiModel: { members: any },
- ): void {
- const configuration = this._tsdocConfiguration;
-
- const packagesTable = new DocTable({
- configuration,
- headerTitles: ['Package', 'Description'],
- });
-
- const pluginsTable = new DocTable({
- configuration,
- headerTitles: ['Package', 'Description'],
- });
-
- for (const apiMember of apiModel.members) {
- const row = new DocTableRow({ configuration }, [
- this._createTitleCell(apiMember),
- this._createDescriptionCell(apiMember),
- ]);
-
- if (apiMember.kind === 'Package') {
- this._writeApiItemPage(apiMember);
-
- if (apiMember.name.startsWith('@backstage/plugin-')) {
- pluginsTable.addRow(row);
- } else {
- packagesTable.addRow(row);
- }
- }
- }
-
- if (packagesTable.rows.length > 0) {
- output.appendNode(
- new DocHeading({
- configuration: this._tsdocConfiguration,
- title: 'Packages',
- }),
- );
- output.appendNode(packagesTable);
- }
-
- if (pluginsTable.rows.length > 0) {
- output.appendNode(
- new DocHeading({
- configuration: this._tsdocConfiguration,
- title: 'Plugins',
- }),
- );
- output.appendNode(pluginsTable);
- }
- }
- }
-
- // This is root of the documentation generation, but it's not directly
- // responsible for generating markdown, it just constructs an AST that
- // is the consumed by an emitter to actually write the files.
- const documenter = new CustomMarkdownDocumenter({
- apiModel: newModel,
- documenterConfig: {
- outputTarget: 'markdown',
- newlineKind: '\n',
- // De ba dålig kod
- configFilePath: '',
- configFile: {},
- } as any,
- outputFolder: outputDir,
- });
-
- // Clean up existing stuff and write ALL the docs!
- await fs.remove(outputDir);
- await fs.ensureDir(outputDir);
- documenter.generateFiles();
-}
-
-export async function categorizePackageDirs(packageDirs: string[]) {
- const dirs = packageDirs.slice();
- const tsPackageDirs = new Array();
- const cliPackageDirs = new Array();
-
- await Promise.all(
- Array(10)
- .fill(0)
- .map(async () => {
- for (;;) {
- const dir = dirs.pop();
- if (!dir) {
- return;
- }
-
- const pkgJson = await fs
- .readJson(cliPaths.resolveTargetRoot(dir, 'package.json'))
- .catch(error => {
- if (error.code === 'ENOENT') {
- return undefined;
- }
- throw error;
- });
- const role = pkgJson?.backstage?.role;
- if (!role) {
- return; // Ignore packages without roles
- }
- // TODO(Rugvip): Inlined packages are ignored because we can't handle @internal exports
- // gracefully, and we don't want to have to mark all exports @public etc.
- // It would be good if we could include these packages though.
- if (pkgJson?.backstage?.inline) {
- return;
- }
- if (role === 'cli') {
- cliPackageDirs.push(dir);
- } else if (role !== 'frontend' && role !== 'backend') {
- tsPackageDirs.push(dir);
- }
- }
- }),
- );
-
- return { tsPackageDirs, cliPackageDirs };
-}
-
-function parseHelpPage(helpPageContent: string) {
- const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? [];
- const lines = helpPageContent.split(/\r?\n/);
-
- let options = new Array();
- let commands = new Array();
- let commandArguments = new Array();
-
- while (lines.length > 0) {
- while (lines.length > 0 && !lines[0].endsWith(':')) {
- lines.shift();
- }
- if (lines.length > 0) {
- // Start of a new section, e.g. "Options:"
- const sectionName = lines.shift();
- // Take lines until we hit the next section or the end
- const sectionEndIndex = lines.findIndex(
- line => line && !line.match(/^\s/),
- );
- const sectionLines = lines.slice(0, sectionEndIndex);
- lines.splice(0, sectionLines.length);
-
- // Trim away documentation
- const sectionItems = sectionLines
- .map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
- .filter(Boolean) as string[];
-
- if (sectionName?.toLocaleLowerCase('en-US') === 'options:') {
- options = sectionItems;
- } else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') {
- commands = sectionItems;
- } else if (sectionName?.toLocaleLowerCase('en-US') === 'arguments:') {
- commandArguments = sectionItems;
- } else {
- throw new Error(`Unknown CLI section: ${sectionName}`);
- }
- }
- }
-
- return {
- usage,
- options,
- commands,
- commandArguments,
- };
-}
-
-// Represents the help page os a CLI command
-interface CliHelpPage {
- // Path of commands to reach this page
- path: string[];
- // Parsed content
- usage: string | undefined;
- options: string[];
- commands: string[];
- commandArguments: string[];
-}
-
-async function exploreCliHelpPages(
- run: (...args: string[]) => Promise,
-): Promise {
- const helpPages = new Array();
-
- async function exploreHelpPage(...path: string[]) {
- const content = await run(...path, '--help');
- const parsed = parseHelpPage(content);
- helpPages.push({ path, ...parsed });
-
- await Promise.all(
- parsed.commands.map(async fullCommand => {
- const command = fullCommand.split(/[|\s]/)[0];
- if (command !== 'help') {
- await exploreHelpPage(...path, command);
- }
- }),
- );
- }
-
- await exploreHelpPage();
-
- helpPages.sort((a, b) => a.path.join(' ').localeCompare(b.path.join(' ')));
-
- return helpPages;
-}
-
-// The API model for a CLI entry point
-interface CliModel {
- name: string;
- helpPages: CliHelpPage[];
-}
-
-function generateCliReport(name: string, models: CliModel[]): string {
- const content = [
- `## CLI Report file for "${name}"`,
- '',
- '> Do not edit this file. It is a report generated by `yarn build:api-reports`',
- '',
- ];
-
- for (const model of models) {
- for (const helpPage of model.helpPages) {
- content.push(
- `### \`${[model.name, ...helpPage.path].join(' ')}\``,
- '',
- '```',
- `Usage: ${helpPage.usage ?? ''}`,
- );
-
- if (helpPage.options.length > 0) {
- content.push('', 'Options:', ...helpPage.options.map(l => ` ${l}`));
- }
-
- if (helpPage.commands.length > 0) {
- content.push('', 'Commands:', ...helpPage.commands.map(l => ` ${l}`));
- }
- content.push('```', '');
- }
- }
-
- return content.join('\n');
-}
-
-interface CliExtractionOptions {
- packageDirs: string[];
- isLocalBuild: boolean;
-}
-
-export async function runCliExtraction({
- packageDirs,
- isLocalBuild,
-}: CliExtractionOptions) {
- for (const packageDir of packageDirs) {
- console.log(`## Processing ${packageDir}`);
- const fullDir = cliPaths.resolveTargetRoot(packageDir);
- const pkgJson = await fs.readJson(resolvePath(fullDir, 'package.json'));
-
- if (!pkgJson.bin) {
- throw new Error(`CLI Package in ${packageDir} has no bin field`);
- }
-
- const models = new Array();
- if (typeof pkgJson.bin === 'string') {
- const run = createBinRunner(fullDir, pkgJson.bin);
- const helpPages = await exploreCliHelpPages(run);
- models.push({ name: basename(pkgJson.bin), helpPages });
- } else {
- for (const [name, path] of Object.entries(pkgJson.bin)) {
- const run = createBinRunner(fullDir, path);
- const helpPages = await exploreCliHelpPages(run);
- models.push({ name, helpPages });
- }
- }
-
- const report = generateCliReport(pkgJson.name, models);
-
- const reportPath = resolvePath(fullDir, 'cli-report.md');
- const existingReport = await fs
- .readFile(reportPath, 'utf8')
- .catch(error => {
- if (error.code === 'ENOENT') {
- return undefined;
- }
- throw error;
- });
-
- if (existingReport !== report) {
- if (isLocalBuild) {
- console.warn(`CLI report changed for ${packageDir}`);
- await fs.writeFile(reportPath, report);
- } else {
- logApiReportInstructions();
-
- if (existingReport) {
- console.log('');
- console.log(
- `The conflicting file is ${relativePath(
- cliPaths.targetRoot,
- reportPath,
- )}, expecting the following content:`,
- );
- console.log('');
-
- console.log(report);
-
- logApiReportInstructions();
- }
- throw new Error(`CLI report changed for ${packageDir}, `);
- }
- }
- }
-}
-
-interface KnipExtractionOptions {
- packageDirs: string[];
- isLocalBuild: boolean;
-}
-
-export async function runKnipReports({
- packageDirs,
- isLocalBuild,
-}: KnipExtractionOptions) {
- const knipDir = cliPaths.resolveTargetRoot('./node_modules/knip/bin/');
-
- for (const packageDir of packageDirs) {
- console.log(`## Processing ${packageDir}`);
- const fullDir = cliPaths.resolveTargetRoot(packageDir);
- const reportPath = resolvePath(fullDir, 'knip-report.md');
- const run = createBinRunner(fullDir, '');
-
- const report = await run(
- `${knipDir}/knip.js`,
- `--directory ${fullDir}`, // Run in the package directory
- '--no-exit-code', // Removing this will end the process in case there are findings by knip
- '--no-progress', // Remove unnecessary debugging from output
- // TODO: Add more checks when dependencies start to look ok, see https://knip.dev/reference/cli#--include
- '--include dependencies,unlisted',
- '--reporter markdown',
- );
-
- const existingReport = await fs
- .readFile(reportPath, 'utf8')
- .catch(error => {
- if (error.code === 'ENOENT') {
- return undefined;
- }
- throw error;
- });
-
- if (existingReport !== report) {
- if (isLocalBuild) {
- console.warn(`Knip report changed for ${packageDir}`);
- await fs.writeFile(reportPath, report);
- } else {
- logApiReportInstructions();
-
- if (existingReport) {
- console.log('');
- console.log(
- `The conflicting file is ${relativePath(
- cliPaths.targetRoot,
- reportPath,
- )}, expecting the following content:`,
- );
- console.log('');
-
- console.log(report);
-
- logApiReportInstructions();
- }
- throw new Error(`Knip report changed for ${packageDir}, `);
- }
- }
- }
-}
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts b/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts
new file mode 100644
index 0000000000..42b585818d
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/buildDocs.ts
@@ -0,0 +1,609 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { resolve as resolvePath } from 'path';
+import fs from 'fs-extra';
+import {
+ DocBlockTag,
+ DocLinkTag,
+ DocNode,
+ DocPlainText,
+ IDocNodeContainerParameters,
+ Standardization,
+ TSDocConfiguration,
+ TSDocTagSyntaxKind,
+} from '@microsoft/tsdoc';
+import {
+ ApiItem,
+ ApiItemKind,
+ ApiModel,
+ ApiPackage,
+} from '@microsoft/api-extractor-model';
+import {
+ IMarkdownDocumenterOptions,
+ MarkdownDocumenter,
+} from '@microsoft/api-documenter/lib/documenters/MarkdownDocumenter';
+import { DocTable } from '@microsoft/api-documenter/lib/nodes/DocTable';
+import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
+import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
+import {
+ CustomMarkdownEmitter,
+ ICustomMarkdownEmitterOptions,
+} from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
+import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
+
+/*
+WARNING: Bring a blanket if you're gonna read the code below
+
+There's some weird shit going on here, and it's because we cba
+forking rushstack to modify the api-documenter markdown generation,
+which otherwise is the recommended way to do customizations.
+*/
+
+type ExcerptToken = {
+ kind: string;
+ text: string;
+ canonicalReference?: string;
+};
+
+class ExcerptTokenMatcher {
+ readonly #tokens: ExcerptToken[];
+
+ constructor(tokens: ExcerptToken[]) {
+ this.#tokens = tokens.slice();
+ }
+
+ nextContent() {
+ const token = this.#tokens.shift();
+ if (token?.kind === 'Content') {
+ return token.text;
+ }
+ return undefined;
+ }
+
+ matchContent(expectedText: string) {
+ const text = this.nextContent();
+ return text !== expectedText;
+ }
+
+ getTokensUntilArrow() {
+ const tokens = [];
+ for (;;) {
+ const token = this.#tokens.shift();
+ if (token === undefined) {
+ return undefined;
+ }
+ if (token.kind === 'Content' && token.text === ') => ') {
+ return tokens;
+ }
+ tokens.push(token);
+ }
+ }
+
+ getComponentReturnTokens() {
+ const first = this.#tokens.shift();
+ if (!first) {
+ return undefined;
+ }
+ const second = this.#tokens.shift();
+
+ if (this.#tokens.length !== 0) {
+ return undefined;
+ }
+ if (first.kind !== 'Reference' || first.text !== 'JSX.Element') {
+ return undefined;
+ }
+ if (!second) {
+ return [first];
+ } else if (second.kind === 'Content' && second.text === ' | null') {
+ return [first, second];
+ }
+ return undefined;
+ }
+}
+
+class ApiModelTransforms {
+ static deserializeWithTransforms(
+ serialized: any,
+ transforms: Array<(member: any) => any>,
+ ): ApiPackage {
+ if (serialized.kind !== 'Package') {
+ throw new Error(
+ `Unexpected root kind in serialized ApiPackage, ${serialized.kind}`,
+ );
+ }
+ if (serialized.members.length !== 1) {
+ throw new Error(
+ `Unexpected members in serialized ApiPackage, [${serialized.members
+ .map((m: { kind: any }) => m.kind)
+ .join(' ')}]`,
+ );
+ }
+ const [entryPoint] = serialized.members;
+ if (entryPoint.kind !== 'EntryPoint') {
+ throw new Error(
+ `Unexpected kind in serialized ApiPackage member, ${entryPoint.kind}`,
+ );
+ }
+
+ const transformed = {
+ ...serialized,
+ members: [
+ {
+ ...entryPoint,
+ members: entryPoint.members.map((member: any) =>
+ transforms.reduce((m, t) => t(m), member),
+ ),
+ },
+ ],
+ };
+
+ return ApiPackage.deserialize(
+ transformed,
+ transformed.metadata,
+ ) as ApiPackage;
+ }
+
+ static transformArrowComponents = (member: any) => {
+ if (member.kind !== 'Variable') {
+ return member;
+ }
+
+ const { name, excerptTokens } = member;
+
+ // First letter in name must be uppercase
+ const [firstChar] = name;
+ if (firstChar.toLocaleUpperCase('en-US') !== firstChar) {
+ return member;
+ }
+
+ // First content must match expected declaration format
+ const tokens = new ExcerptTokenMatcher(excerptTokens);
+ if (tokens.nextContent() !== `${name}: `) {
+ return member;
+ }
+
+ // Next needs to be an arrow with `props` parameters or no parameters
+ // followed by a return type of `JSX.Element | null` or just `JSX.Element`
+ const declStart = tokens.nextContent();
+ if (declStart === '(props: ' || declStart === '(_props: ') {
+ const props = tokens.getTokensUntilArrow();
+ const ret = tokens.getComponentReturnTokens();
+ if (props && ret) {
+ return this.makeComponentMember(member, ret, props);
+ }
+ } else if (declStart === '() => ') {
+ const ret = tokens.getComponentReturnTokens();
+ if (ret) {
+ return this.makeComponentMember(member, ret);
+ }
+ }
+ return member;
+ };
+
+ static makeComponentMember(
+ member: any,
+ ret: ExcerptToken[],
+ props?: ExcerptToken[],
+ ) {
+ const declTokens = props
+ ? [
+ {
+ kind: 'Content',
+ text: `export declare function ${member.name}(props: `,
+ },
+ ...props,
+ {
+ kind: 'Content',
+ text: '): ',
+ },
+ ]
+ : [
+ {
+ kind: 'Content',
+ text: `export declare function ${member.name}(): `,
+ },
+ ];
+
+ return {
+ kind: 'Function',
+ name: member.name,
+ releaseTag: member.releaseTag,
+ docComment: member.docComment ?? '',
+ canonicalReference: member.canonicalReference,
+ excerptTokens: [...declTokens, ...ret],
+ returnTypeTokenRange: {
+ startIndex: declTokens.length,
+ endIndex: declTokens.length + ret.length,
+ },
+ parameters: props
+ ? [
+ {
+ parameterName: 'props',
+ parameterTypeTokenRange: {
+ startIndex: 1,
+ endIndex: 1 + props.length,
+ },
+ },
+ ]
+ : [],
+ overloadIndex: 1,
+ };
+ }
+
+ static transformTrimDeclare = (member: any) => {
+ const { excerptTokens } = member;
+ const firstContent = new ExcerptTokenMatcher(excerptTokens).nextContent();
+ if (firstContent && firstContent.startsWith('export declare ')) {
+ return {
+ ...member,
+ excerptTokens: [
+ {
+ kind: 'Content',
+ text: firstContent.slice('export declare '.length),
+ },
+ ...excerptTokens.slice(1),
+ ],
+ };
+ }
+ return member;
+ };
+}
+
+export async function buildDocs({
+ inputDir,
+ outputDir,
+}: {
+ inputDir: string;
+ outputDir: string;
+}) {
+ // We start by constructing our own model from the files so that
+ // we get a change to modify them, as the model is otherwise read-only.
+ const parseFile = async (filename: string): Promise => {
+ console.log(`Reading ${filename}`);
+ return fs.readJson(resolvePath(inputDir, filename));
+ };
+
+ const filenames = await fs.readdir(inputDir);
+ const serializedPackages = await Promise.all(
+ filenames
+ .filter(filename => filename.match(/\.api\.json$/i))
+ .map(parseFile),
+ );
+
+ const newModel = new ApiModel();
+ for (const serialized of serializedPackages) {
+ newModel.addMember(
+ ApiModelTransforms.deserializeWithTransforms(serialized, [
+ ApiModelTransforms.transformArrowComponents,
+ ApiModelTransforms.transformTrimDeclare,
+ ]),
+ );
+ }
+
+ // The doc AST need to be extended with custom nodes if we want to
+ // add any extra content.
+ // This one is for the YAML front matter that we need for the microsite.
+ class DocFrontMatter extends DocNode {
+ static kind = 'DocFrontMatter';
+
+ public readonly values: { [name: string]: unknown };
+
+ public constructor(
+ parameters: IDocNodeContainerParameters & {
+ values: { [name: string]: unknown };
+ },
+ ) {
+ super(parameters);
+ this.values = parameters.values;
+ }
+
+ /** @override */
+ public get kind(): string {
+ return DocFrontMatter.kind;
+ }
+ }
+
+ // This class only propose is to have a different kind and be able to render links with backticks
+ class DocCodeSpanLink extends DocLinkTag {
+ static kind = 'DocCodeSpanLink';
+
+ /** @override */
+ public get kind(): string {
+ return DocCodeSpanLink.kind;
+ }
+ }
+
+ // This is where we actually write the markdown and where we can hook
+ // in the rendering of our own nodes.
+ class CustomCustomMarkdownEmitter extends CustomMarkdownEmitter {
+ // Until https://github.com/microsoft/rushstack/issues/2914 gets fixed or we change markdown renderer we need a fix
+ // to render pipe | character correctly.
+ protected getEscapedText(text: string): string {
+ return text
+ .replace(/\\/g, '\\\\') // first replace the escape character
+ .replace(/[*#[\]_`~]/g, x => `\\${x}`) // then escape any special characters
+ .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\|/g, '|');
+ }
+ /** @override */
+ protected writeNode(
+ docNode: DocNode,
+ context: IMarkdownEmitterContext,
+ docNodeSiblings: boolean,
+ ): void {
+ switch (docNode.kind) {
+ case DocFrontMatter.kind: {
+ const node = docNode as DocFrontMatter;
+ context.writer.writeLine('---');
+ for (const [name, value] of Object.entries(node.values)) {
+ if (value) {
+ context.writer.writeLine(
+ `${name}: "${String(value).replace(/\"/g, '')}"`,
+ );
+ }
+ }
+ context.writer.writeLine('---');
+ context.writer.writeLine();
+ break;
+ }
+ case 'BlockTag': {
+ const node = docNode as DocBlockTag;
+ if (node.tagName === '@config') {
+ context.writer.writeLine('## Related config ');
+ }
+ break;
+ }
+ case DocCodeSpanLink.kind: {
+ const node = docNode as DocLinkTag;
+ if (node.codeDestination) {
+ // TODO @sarabadu understand if we need `codeDestination` at all on this custom DocCodeSpanLink
+ super.writeLinkTagWithCodeDestination(node, context);
+ } else if (node.urlDestination) {
+ const linkText =
+ node.linkText !== undefined ? node.linkText : node.urlDestination;
+ const encodedLinkText = this.getEscapedText(
+ linkText.replace(/\s+/g, ' '),
+ );
+ context.writer.write('[');
+ context.writer.write(`\`${encodedLinkText}\``);
+ context.writer.write(`](${node.urlDestination})`);
+ } else if (node.linkText) {
+ this.writePlainText(node.linkText, context);
+ }
+ break;
+ }
+ default:
+ super.writeNode(docNode, context, docNodeSiblings);
+ }
+ }
+
+ /** @override */
+ emit(
+ stringBuilder: any,
+ docNode: DocNode,
+ options: ICustomMarkdownEmitterOptions,
+ ) {
+ // Hack to get rid of the leading comment of each file, since
+ // we want the front matter to come first
+ stringBuilder._chunks.length = 0;
+ return super.emit(stringBuilder, docNode, options);
+ }
+ }
+
+ class CustomMarkdownDocumenter extends (MarkdownDocumenter as any) {
+ constructor(options: IMarkdownDocumenterOptions) {
+ super(options);
+
+ // It's a strict model, we gotta register the allowed usage of our new node
+ (
+ this._tsdocConfiguration as TSDocConfiguration
+ ).docNodeManager.registerDocNodes('@backstage/docs', [
+ { docNodeKind: DocFrontMatter.kind, constructor: DocFrontMatter },
+ ]);
+ (
+ this._tsdocConfiguration as TSDocConfiguration
+ ).docNodeManager.registerDocNodes('@backstage/docs', [
+ { docNodeKind: DocCodeSpanLink.kind, constructor: DocCodeSpanLink },
+ ]);
+ (
+ this._tsdocConfiguration as TSDocConfiguration
+ ).docNodeManager.registerAllowableChildren('Paragraph', [
+ DocFrontMatter.kind,
+ DocCodeSpanLink.kind,
+ ]);
+
+ const def = {
+ tagName: '@config',
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
+ tagNameWithUpperCase: '@CONFIG',
+ standardization: Standardization.Extended,
+ allowMultiple: false,
+ };
+ (this._tsdocConfiguration as TSDocConfiguration).addTagDefinition(def);
+ (this._tsdocConfiguration as TSDocConfiguration).setSupportForTag(
+ def,
+ true,
+ );
+ this._markdownEmitter = new CustomCustomMarkdownEmitter(newModel);
+ }
+
+ private _getFilenameForApiItem(apiItem: ApiItem): string {
+ const filename: string = super._getFilenameForApiItem(apiItem);
+
+ if (filename.includes('.html.')) {
+ return filename.replace(/\.html\./g, '._html.');
+ }
+
+ return filename;
+ }
+
+ // We don't really get many chances to modify the generated AST
+ // so we hook in wherever we can. In this case we add the front matter
+ // just before writing the breadcrumbs at the top.
+ /** @override */
+ _writeBreadcrumb(output: any, apiItem: ApiItem & { name: string }) {
+ let title;
+ let description;
+
+ const name = apiItem.getScopedNameWithinPackage();
+ if (name) {
+ title = name;
+ description = `API reference for ${apiItem.getScopedNameWithinPackage()}`;
+ } else if (apiItem.kind === 'Model') {
+ title = 'Package Index';
+ description = 'Index of all Backstage Packages';
+ } else if (apiItem.name) {
+ title = apiItem.name;
+ description = `API Reference for ${apiItem.name}`;
+ } else {
+ title = apiItem.displayName;
+ description = `API Reference for ${apiItem.displayName}`;
+ }
+
+ // Add our front matter
+ output.appendNodeInParagraph(
+ new DocFrontMatter({
+ configuration: this._tsdocConfiguration,
+ values: {
+ id: this._getFilenameForApiItem(apiItem).slice(0, -3),
+ title,
+ description,
+ },
+ }),
+ );
+
+ const configuration: TSDocConfiguration = this._tsdocConfiguration;
+
+ output.appendNodeInParagraph(
+ new DocLinkTag({
+ configuration,
+ tagName: '@link',
+ linkText: 'Home',
+ urlDestination: this._getLinkFilenameForApiItem(this._apiModel),
+ }),
+ );
+
+ for (const hierarchyItem of apiItem.getHierarchy()) {
+ switch (hierarchyItem.kind) {
+ case ApiItemKind.Model:
+ case ApiItemKind.EntryPoint:
+ // We don't show the model as part of the breadcrumb because it is the root-level container.
+ // We don't show the entry point because today API Extractor doesn't support multiple entry points;
+ // this may change in the future.
+ break;
+ default:
+ output.appendNodesInParagraph([
+ new DocPlainText({
+ configuration,
+ text: ' > ',
+ }),
+ new DocCodeSpanLink({
+ configuration,
+ tagName: '@link',
+ linkText: hierarchyItem.displayName,
+ urlDestination: this._getLinkFilenameForApiItem(hierarchyItem),
+ }),
+ ]);
+ }
+ }
+
+ // We wanna ignore the header that always gets written after the breadcrumb
+ // This otherwise becomes more or less a duplicate of the title in the front matter
+ const oldAppendNode = output.appendNode;
+ output.appendNode = () => {
+ output.appendNode = oldAppendNode;
+ };
+ }
+
+ _writeModelTable(
+ output: { appendNode: (arg0: DocTable | DocHeading) => void },
+ apiModel: { members: any },
+ ): void {
+ const configuration = this._tsdocConfiguration;
+
+ const packagesTable = new DocTable({
+ configuration,
+ headerTitles: ['Package', 'Description'],
+ });
+
+ const pluginsTable = new DocTable({
+ configuration,
+ headerTitles: ['Package', 'Description'],
+ });
+
+ for (const apiMember of apiModel.members) {
+ const row = new DocTableRow({ configuration }, [
+ this._createTitleCell(apiMember),
+ this._createDescriptionCell(apiMember),
+ ]);
+
+ if (apiMember.kind === 'Package') {
+ this._writeApiItemPage(apiMember);
+
+ if (apiMember.name.startsWith('@backstage/plugin-')) {
+ pluginsTable.addRow(row);
+ } else {
+ packagesTable.addRow(row);
+ }
+ }
+ }
+
+ if (packagesTable.rows.length > 0) {
+ output.appendNode(
+ new DocHeading({
+ configuration: this._tsdocConfiguration,
+ title: 'Packages',
+ }),
+ );
+ output.appendNode(packagesTable);
+ }
+
+ if (pluginsTable.rows.length > 0) {
+ output.appendNode(
+ new DocHeading({
+ configuration: this._tsdocConfiguration,
+ title: 'Plugins',
+ }),
+ );
+ output.appendNode(pluginsTable);
+ }
+ }
+ }
+
+ // This is root of the documentation generation, but it's not directly
+ // responsible for generating markdown, it just constructs an AST that
+ // is the consumed by an emitter to actually write the files.
+ const documenter = new CustomMarkdownDocumenter({
+ apiModel: newModel,
+ documenterConfig: {
+ outputTarget: 'markdown',
+ newlineKind: '\n',
+ // De ba dålig kod
+ configFilePath: '',
+ configFile: {},
+ } as any,
+ outputFolder: outputDir,
+ });
+
+ // Clean up existing stuff and write ALL the docs!
+ await fs.remove(outputDir);
+ await fs.ensureDir(outputDir);
+ documenter.generateFiles();
+}
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts b/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts
new file mode 100644
index 0000000000..538fdd4ceb
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/createTemporaryTsConfig.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs-extra';
+import { join } from 'path';
+import { paths as cliPaths } from '../../../lib/paths';
+
+export async function createTemporaryTsConfig(includedPackageDirs: string[]) {
+ const path = cliPaths.resolveTargetRoot('tsconfig.tmp.json');
+
+ process.once('exit', () => {
+ fs.removeSync(path);
+ });
+
+ let assetTypeFile: string[] = [];
+
+ try {
+ assetTypeFile = [
+ require.resolve('@backstage/cli/asset-types/asset-types.d.ts'),
+ ];
+ } catch {
+ /** ignore */
+ }
+
+ await fs.writeJson(path, {
+ extends: './tsconfig.json',
+ include: [
+ // These two contain global definitions that are needed for stable API report generation
+ ...assetTypeFile,
+ ...includedPackageDirs.map(dir => join(dir, 'src')),
+ ],
+ // we don't exclude node_modules so that we can use the asset-types.d.ts file
+ exclude: [],
+ });
+
+ return path;
+}
diff --git a/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts
similarity index 96%
rename from packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts
rename to packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts
index 43ee2aa85f..d7304553c0 100644
--- a/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/generateTypeDeclarations.ts
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { spawnSync } from 'child_process';
-import { paths as cliPaths } from '../../lib/paths';
+import { paths as cliPaths } from '../../../lib/paths';
/**
* Generates the TypeScript declaration files for the specified project, using the provided `tsconfig.json` file.
diff --git a/packages/canon/docs/components/Text/Text.tsx b/packages/repo-tools/src/commands/api-reports/api-reports/index.ts
similarity index 71%
rename from packages/canon/docs/components/Text/Text.tsx
rename to packages/repo-tools/src/commands/api-reports/api-reports/index.ts
index 90804b7da7..f5c8d1fcda 100644
--- a/packages/canon/docs/components/Text/Text.tsx
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/index.ts
@@ -14,18 +14,7 @@
* limitations under the License.
*/
-import React from 'react';
-
-export const Text = ({
- children,
- style,
-}: {
- children: React.ReactNode;
- style?: React.CSSProperties;
-}) => {
- return (
-
- {children}
-
- );
-};
+export { runApiExtraction } from './runApiExtraction';
+export { buildDocs } from './buildDocs';
+export { createTemporaryTsConfig } from './createTemporaryTsConfig';
+export { generateTypeDeclarations } from './generateTypeDeclarations';
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts
new file mode 100644
index 0000000000..982707e815
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ExtractorMessage } from '@microsoft/api-extractor';
+import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
+import { Program } from 'typescript';
+import { tryRunPrettier } from '../common';
+
+let applied = false;
+
+export function patchApiReportGeneration() {
+ // Make sure we only apply the patches once
+ if (applied) {
+ return;
+ }
+ applied = true;
+
+ const {
+ ApiReportGenerator,
+ } = require('@microsoft/api-extractor/lib/generators/ApiReportGenerator');
+
+ function patchFileMessageFetcher(
+ router: any,
+ transform: (messages: ExtractorMessage[], ast?: AstDeclaration) => void,
+ ) {
+ const {
+ fetchAssociatedMessagesForReviewFile,
+ fetchUnassociatedMessagesForReviewFile,
+ } = router;
+
+ router.fetchAssociatedMessagesForReviewFile =
+ function patchedFetchAssociatedMessagesForReviewFile(
+ ast: AstDeclaration | undefined,
+ ) {
+ const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
+ return transform(messages, ast);
+ };
+ router.fetchUnassociatedMessagesForReviewFile =
+ function patchedFetchUnassociatedMessagesForReviewFile() {
+ const messages = fetchUnassociatedMessagesForReviewFile.call(this);
+ return transform(messages);
+ };
+ }
+
+ const originalGenerateReviewFileContent =
+ ApiReportGenerator.generateReviewFileContent;
+ ApiReportGenerator.generateReviewFileContent =
+ function decoratedGenerateReviewFileContent(
+ collector: { program: Program; messageRouter: any },
+ ...moreArgs: any[]
+ ) {
+ const program = collector.program as Program;
+ // The purpose of this override is to allow the @ignore tag to be used to ignore warnings
+ // of the form "Warning: (ae-forgotten-export) The symbol "FooBar" needs to be exported by the entry point index.d.ts"
+ patchFileMessageFetcher(
+ collector.messageRouter,
+ (messages: ExtractorMessage[]) => {
+ return messages.filter(message => {
+ if (message.messageId !== 'ae-forgotten-export') {
+ return true;
+ }
+
+ // Symbol name has to be extracted from the message :(
+ // There's frequently no AST for these exports because type literals
+ // aren't traversed by the generator.
+ const symbolMatch = message.text.match(/The symbol "([^"]+)"/);
+ if (!symbolMatch) {
+ throw new Error(
+ `Failed to extract symbol name from message "${message.text}"`,
+ );
+ }
+ const [, symbolName] = symbolMatch;
+
+ const sourceFile =
+ message.sourceFilePath &&
+ program.getSourceFile(message.sourceFilePath);
+ if (!sourceFile) {
+ throw new Error(
+ `Failed to find source file in program at path "${message.sourceFilePath}"`,
+ );
+ }
+
+ // The local name of the symbol within the file, rather than the exported name
+ let localName = (sourceFile as any).identifiers?.get(symbolName);
+
+ if (!localName) {
+ // Sometimes the symbol name is suffixed with a number to disambiguate,
+ // e.g. "Props_14" instead of "Props" if there are multiple Props interfaces
+ // so we try to strip that suffix and look up the symbol again.
+ const [, trimmedSymbolName] = symbolName.match(/(.*)_\d+/) || [];
+ localName = (sourceFile as any).identifiers?.get(
+ trimmedSymbolName,
+ );
+ }
+
+ if (!localName) {
+ throw new Error(
+ `Unable to find local name of "${symbolName}" in ${sourceFile.fileName}`,
+ );
+ }
+
+ // The local AST node of the export that we're missing
+ const local = (sourceFile as any).locals?.get(localName);
+ if (!local) {
+ return true;
+ }
+
+ // Use the type checker to look up the actual declaration(s) rather than the one in the local file
+ const type = program
+ .getTypeChecker()
+ .getDeclaredTypeOfSymbol(local);
+ if (!type) {
+ throw new Error(
+ `Unable to find type declaration of "${symbolName}" in ${sourceFile.fileName}`,
+ );
+ }
+ const declarations = type.aliasSymbol?.declarations;
+ if (!declarations || declarations.length === 0) {
+ return true;
+ }
+
+ // If any of the TSDoc comments contain a @ignore tag, we ignore this message
+ const isIgnored = declarations.some(declaration => {
+ const tags = [(declaration as any).jsDoc]
+ .flat()
+ .filter(Boolean)
+ .flatMap((tagNode: any) => tagNode.tags);
+
+ return tags.some(tag => tag?.tagName.text === 'ignore');
+ });
+
+ return !isIgnored;
+ });
+ },
+ );
+
+ /**
+ * This monkey patching is for applying prettier to the API reports. This has to be patched into
+ * the middle of the process as API Extractor does a comparison of the contents of the old
+ * and new files during generation. This inserts the formatting just before that comparison.
+ */
+ const content = originalGenerateReviewFileContent.call(
+ this,
+ collector,
+ ...moreArgs,
+ );
+
+ return tryRunPrettier(content);
+ };
+}
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts
new file mode 100644
index 0000000000..1ba8852ed4
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/api-reports/runApiExtraction.ts
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ CompilerState,
+ Extractor,
+ ExtractorConfig,
+ ExtractorLogLevel,
+} from '@microsoft/api-extractor';
+import { TSDocTagSyntaxKind } from '@microsoft/tsdoc';
+import { TSDocConfigFile } from '@microsoft/tsdoc-config';
+import fs from 'fs-extra';
+import { groupBy } from 'lodash';
+import { minimatch } from 'minimatch';
+import { join, relative as relativePath, resolve as resolvePath } from 'path';
+import { getPackageExportDetails } from '../../../lib/getPackageExportDetails';
+import { paths as cliPaths } from '../../../lib/paths';
+import { logApiReportInstructions } from '../common';
+import { patchApiReportGeneration } from './patchApiReportGeneration';
+
+const tmpDir = cliPaths.resolveTargetRoot(
+ './node_modules/.cache/api-extractor',
+);
+
+export async function countApiReportWarnings(reportPath: string) {
+ try {
+ const content = await fs.readFile(reportPath, 'utf8');
+ const lines = content.split('\n');
+
+ const lineWarnings = lines.filter(line =>
+ line.includes('// Warning:'),
+ ).length;
+
+ const trailerStart = lines.findIndex(
+ line => line === '// Warnings were encountered during analysis:',
+ );
+ const trailerWarnings =
+ trailerStart === -1
+ ? 0
+ : lines.length -
+ trailerStart -
+ 4; /* 4 lines at the trailer and after are not warnings */
+
+ return lineWarnings + trailerWarnings;
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ return 0;
+ }
+ throw error;
+ }
+}
+
+export async function getTsDocConfig() {
+ const tsdocConfigFile = await TSDocConfigFile.loadFile(
+ require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
+ );
+ tsdocConfigFile.addTagDefinition({
+ tagName: '@ignore',
+ syntaxKind: TSDocTagSyntaxKind.ModifierTag,
+ });
+ tsdocConfigFile.addTagDefinition({
+ tagName: '@config',
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
+ });
+ tsdocConfigFile.setSupportForTag('@ignore', true);
+ tsdocConfigFile.setSupportForTag('@config', true);
+ return tsdocConfigFile;
+}
+
+async function findPackageEntryPoints(packageDirs: string[]): Promise<
+ Array<{
+ // package dir relative to root, e.g. "packages/backend-app-api"
+ packageDir: string;
+ // the name of the export, e.g. "index" or "alpha"
+ name: string;
+ // the path within the dist directory for this export, e.g. "alpha.d.ts"
+ distPath: string;
+ // the path within the dist-types directory of this package for this export,
+ // e.g. "src/entrypoints/foo/index.d.ts"
+ distTypesPath: string;
+ }>
+> {
+ return Promise.all(
+ packageDirs.map(async packageDir => {
+ const pkg = await fs.readJson(
+ cliPaths.resolveTargetRoot(packageDir, 'package.json'),
+ );
+
+ return getPackageExportDetails(pkg).map(details => {
+ return { packageDir, ...details };
+ });
+ }),
+ ).then(results => results.flat());
+}
+
+interface ApiExtractionOptions {
+ packageDirs: string[];
+ outputDir: string;
+ isLocalBuild: boolean;
+ tsconfigFilePath: string;
+ allowWarnings?: boolean | string[];
+ omitMessages?: string[];
+ validateReleaseTags?: boolean;
+}
+
+export async function runApiExtraction({
+ packageDirs,
+ outputDir,
+ isLocalBuild,
+ tsconfigFilePath,
+ allowWarnings = false,
+ omitMessages = [],
+ validateReleaseTags = false,
+}: ApiExtractionOptions) {
+ patchApiReportGeneration();
+
+ await fs.remove(outputDir);
+
+ // The collection of all entry points of all packages, as a single list
+ const allEntryPoints = await findPackageEntryPoints(packageDirs);
+
+ // The path (relative to the root) to ALL dist-types entry points (e.g.
+ // "dist-types/packages/backend-app-api/src/index.d.ts"). These are used as
+ // "extra"/contextual entry points for the extractor so that it can see the
+ // full context of things that are required by the local entry point being
+ // inspected.
+ const allDistTypesEntryPointPaths = allEntryPoints.map(
+ ({ packageDir, distTypesPath }) => {
+ return cliPaths.resolveTargetRoot(
+ './dist-types',
+ packageDir,
+ distTypesPath,
+ );
+ },
+ );
+
+ let compilerState: CompilerState | undefined = undefined;
+
+ const allowWarningPkg = Array.isArray(allowWarnings) ? allowWarnings : [];
+
+ const messagesConf: { [key: string]: { logLevel: string } } = {};
+ for (const messageCode of omitMessages) {
+ messagesConf[messageCode] = {
+ logLevel: 'none',
+ };
+ }
+
+ const warnings = new Array();
+
+ for (const [packageDir, packageEntryPoints] of Object.entries(
+ groupBy(allEntryPoints, ep => ep.packageDir),
+ )) {
+ console.log(`## Processing ${packageDir}`);
+ const noBail = Array.isArray(allowWarnings)
+ ? allowWarnings.some(aw => aw === packageDir || minimatch(packageDir, aw))
+ : allowWarnings;
+
+ const projectFolder = cliPaths.resolveTargetRoot(packageDir);
+ const packageFolder = cliPaths.resolveTargetRoot(
+ './dist-types',
+ packageDir,
+ );
+
+ const remainingReportFiles = new Set(
+ fs.readdirSync(projectFolder).filter(
+ filename =>
+ // https://regex101.com/r/QDZIV0/2
+ filename !== 'knip-report.md' &&
+ !filename.endsWith('.sql.md') &&
+ // this has to temporarily match all old api report formats
+ filename.match(/^.*?(api-)?report(-[^.-]+)?(.*?)\.md$/),
+ ),
+ );
+
+ for (const packageEntryPoint of packageEntryPoints) {
+ const suffix =
+ packageEntryPoint.name === 'index' ? '' : `-${packageEntryPoint.name}`;
+ const reportFileName = `report${suffix}`;
+ const reportPath = resolvePath(projectFolder, `${reportFileName}.api.md`);
+
+ const warningCountBefore = await countApiReportWarnings(reportPath);
+
+ const extractorConfig = ExtractorConfig.prepare({
+ configObject: {
+ mainEntryPointFilePath: resolvePath(
+ packageFolder,
+ packageEntryPoint.distTypesPath,
+ ),
+ bundledPackages: [],
+
+ compiler: {
+ tsconfigFilePath,
+ },
+
+ apiReport: {
+ enabled: true,
+ reportFileName,
+ reportFolder: projectFolder,
+ reportTempFolder: resolvePath(
+ outputDir,
+ `${suffix}`,
+ ),
+ },
+
+ docModel: {
+ // TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but
+ // most likely it makes sense to wait for API Extractor to natively support exports.
+ enabled: packageEntryPoint.name === 'index',
+ apiJsonFilePath: resolvePath(
+ outputDir,
+ `${suffix}.api.json`,
+ ),
+ },
+
+ dtsRollup: {
+ enabled: false,
+ },
+
+ tsdocMetadata: {
+ enabled: false,
+ },
+
+ messages: {
+ // Silence compiler warnings, as these will prevent the CI build to work
+ compilerMessageReporting: {
+ default: {
+ logLevel: 'none' as ExtractorLogLevel.None,
+ // These contain absolute file paths, so can't be included in the report
+ // addToApiReportFile: true,
+ },
+ },
+ extractorMessageReporting: {
+ default: {
+ logLevel: 'warning' as ExtractorLogLevel.Warning,
+ addToApiReportFile: true,
+ },
+ ...messagesConf,
+ },
+ tsdocMessageReporting: {
+ default: {
+ logLevel: 'warning' as ExtractorLogLevel.Warning,
+ addToApiReportFile: true,
+ },
+ },
+ },
+
+ newlineKind: 'lf',
+
+ projectFolder,
+ },
+ configObjectFullPath: projectFolder,
+ packageJsonFullPath: resolvePath(projectFolder, 'package.json'),
+ tsdocConfigFile: await getTsDocConfig(),
+ ignoreMissingEntryPoint: true,
+ });
+
+ // remove extracted reports from current list
+ for (const reportConfig of extractorConfig.reportConfigs) {
+ remainingReportFiles.delete(reportConfig.fileName);
+ }
+
+ // The `packageFolder` needs to point to the location within `dist-types` in order for relative
+ // paths to be logged. Unfortunately the `prepare` method above derives it from the `packageJsonFullPath`,
+ // which needs to point to the actual file, so we override `packageFolder` afterwards.
+ (
+ extractorConfig as {
+ packageFolder: string;
+ }
+ ).packageFolder = packageFolder;
+
+ if (!compilerState) {
+ compilerState = CompilerState.create(extractorConfig, {
+ additionalEntryPoints: allDistTypesEntryPointPaths,
+ });
+ }
+
+ // Message verbosity can't be configured, so just skip the check instead
+ (Extractor as any)._checkCompilerCompatibility = () => {};
+
+ let shouldLogInstructions = false;
+ let conflictingFile: undefined | string = undefined;
+
+ // Invoke API Extractor
+ const extractorResult = Extractor.invoke(extractorConfig, {
+ localBuild: isLocalBuild,
+ showVerboseMessages: false,
+ showDiagnostics: false,
+ messageCallback(message) {
+ if (message.text.includes('The API report file is missing')) {
+ shouldLogInstructions = true;
+ }
+
+ // Detect messages like the following being output by the generator:
+ // Warning: You have changed the API signature for this project. Please copy the file "/home/runner/work/backstage/backstage/node_modules/.cache/api-extractor/backend-test-utils/report.api.md" to "report.api.md", or perform a local build (which does this automatically). See the Git repo documentation for more info.
+ if (
+ message.text.includes(
+ 'You have changed the API signature for this project.',
+ )
+ ) {
+ shouldLogInstructions = true;
+ const match = message.text.match(
+ /Please copy the file "(.*)" to "report\.api\.md"/,
+ );
+ if (match) {
+ conflictingFile = match[1];
+ }
+ }
+ },
+ compilerState,
+ });
+
+ // This release tag validation makes sure that the release tag of known entry points match expectations.
+ // The root index entry point is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta.
+ if (
+ validateReleaseTags &&
+ fs.pathExistsSync(extractorConfig.reportFilePath)
+ ) {
+ if (['index', 'alpha', 'beta'].includes(packageEntryPoint.name)) {
+ const report = await fs.readFile(
+ extractorConfig.reportFilePath,
+ 'utf8',
+ );
+ const lines = report.split(/\r?\n/);
+ const expectedTag =
+ packageEntryPoint.name === 'index'
+ ? 'public'
+ : packageEntryPoint.name;
+ for (let i = 0; i < lines.length; i += 1) {
+ const line = lines[i];
+ const match = line.match(/^\/\/ @(alpha|beta|public)/);
+ if (match && match[1] !== expectedTag) {
+ // Because of limitations in the type script rollup logic we need to allow public exports from the other release stages
+ // TODO(Rugvip): Try to work around the need for this exception
+ if (expectedTag !== 'public' && match[1] === 'public') {
+ continue;
+ }
+ throw new Error(
+ `Unexpected release tag ${match[1]} in ${
+ extractorConfig.reportFilePath
+ } at line ${i + 1}`,
+ );
+ }
+ }
+ }
+ }
+
+ if (!extractorResult.succeeded) {
+ if (shouldLogInstructions) {
+ logApiReportInstructions();
+
+ if (conflictingFile) {
+ console.log('');
+ console.log(
+ `The conflicting file is ${relativePath(
+ tmpDir,
+ conflictingFile,
+ )}, with the following content:`,
+ );
+ console.log('');
+
+ const content = await fs.readFile(conflictingFile, 'utf8');
+ console.log(content);
+
+ logApiReportInstructions();
+ }
+ }
+
+ throw new Error(
+ `API Extractor completed with ${extractorResult.errorCount} errors` +
+ ` and ${extractorResult.warningCount} warnings`,
+ );
+ }
+
+ const warningCountAfter = await countApiReportWarnings(reportPath);
+
+ if (noBail) {
+ console.log(`Skipping warnings check for ${packageDir}`);
+ }
+ if (warningCountAfter > 0 && !noBail) {
+ throw new Error(
+ `The API Report for ${packageDir} is not allowed to have warnings`,
+ );
+ }
+ if (warningCountAfter === 0 && allowWarningPkg.includes(packageDir)) {
+ console.log(
+ `No need to allow warnings for ${packageDir}, it does not have any`,
+ );
+ }
+ if (warningCountAfter > warningCountBefore) {
+ warnings.push(
+ `The API Report for ${packageDir} introduces new warnings. ` +
+ 'Please fix these warnings in order to keep the API Reports tidy.',
+ );
+ }
+ }
+
+ if (remainingReportFiles.size > 0) {
+ if (isLocalBuild) {
+ for (const f of remainingReportFiles) {
+ fs.rmSync(resolvePath(projectFolder, f));
+ console.log(`Deleted deprecated API report ${f}`);
+ }
+ } else {
+ const staleList = [...remainingReportFiles]
+ .map(f => join(packageDir, f))
+ .join(', ');
+ throw new Error(
+ `The API Report(s) ${staleList} are no longer relevant and should be deleted`,
+ );
+ }
+ }
+ }
+
+ if (warnings.length > 0) {
+ console.warn();
+ for (const warning of warnings) {
+ console.warn(warning);
+ }
+ console.warn();
+ }
+}
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports.test.ts b/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts
similarity index 96%
rename from packages/repo-tools/src/commands/api-reports/api-reports.test.ts
rename to packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts
index 94ae42d715..cbe6fedad6 100644
--- a/packages/repo-tools/src/commands/api-reports/api-reports.test.ts
+++ b/packages/repo-tools/src/commands/api-reports/buildApiReports.test.ts
@@ -18,32 +18,37 @@ import { createMockDirectory } from '@backstage/backend-test-utils';
import { normalize } from 'path';
import * as pathsLib from '../../lib/paths';
-import {
- buildDocs,
- categorizePackageDirs,
- runApiExtraction,
- runCliExtraction,
-} from './api-extractor';
+import { categorizePackageDirs } from './categorizePackageDirs';
-import { buildApiReports } from './api-reports';
-import { generateTypeDeclarations } from './generateTypeDeclarations';
+import { buildApiReports } from './buildApiReports';
+import { generateTypeDeclarations } from './api-reports';
import { PackageGraph } from '@backstage/cli-node';
+import { runCliExtraction } from './cli-reports';
+import { runApiExtraction, buildDocs } from './api-reports/index';
-jest.mock('./generateTypeDeclarations');
// create mocks for the dependencies of the `buildApiReports` function
-jest.mock('./api-extractor', () => ({
+jest.mock('./api-reports', () => ({
+ generateTypeDeclarations: jest.fn(),
createTemporaryTsConfig: jest.fn(),
+ runApiExtraction: jest.fn(),
+ runCliExtraction: jest.fn(),
+ buildDocs: jest.fn(),
+}));
+jest.mock('./cli-reports', () => ({
+ runCliExtraction: jest.fn(),
+}));
+jest.mock('./sql-reports', () => ({
+ runSqlExtraction: jest.fn(),
+}));
+jest.mock('./categorizePackageDirs', () => ({
categorizePackageDirs: jest.fn().mockImplementation(async (p: string[]) => {
console.log('categorizePackageDirs', p);
return {
tsPackageDirs: p,
cliPackageDirs: p,
+ sqlPackageDirs: [],
};
}),
- runApiExtraction: jest.fn(),
- runCliExtraction: jest.fn(),
- buildDocs: jest.fn(),
- runKnipReports: jest.fn(),
}));
const projectPaths = pathsLib.paths;
diff --git a/packages/repo-tools/src/commands/api-reports/api-reports.ts b/packages/repo-tools/src/commands/api-reports/buildApiReports.ts
similarity index 85%
rename from packages/repo-tools/src/commands/api-reports/api-reports.ts
rename to packages/repo-tools/src/commands/api-reports/buildApiReports.ts
index 4e568102d9..74b110236f 100644
--- a/packages/repo-tools/src/commands/api-reports/api-reports.ts
+++ b/packages/repo-tools/src/commands/api-reports/buildApiReports.ts
@@ -15,15 +15,16 @@
*/
import { OptionValues } from 'commander';
-import {
- buildDocs,
- categorizePackageDirs,
- createTemporaryTsConfig,
- runApiExtraction,
- runCliExtraction,
-} from './api-extractor';
+import { categorizePackageDirs } from './categorizePackageDirs';
import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths';
-import { generateTypeDeclarations } from './generateTypeDeclarations';
+import { runSqlExtraction } from './sql-reports';
+import { runCliExtraction } from './cli-reports';
+import {
+ runApiExtraction,
+ buildDocs,
+ createTemporaryTsConfig,
+ generateTypeDeclarations,
+} from './api-reports/index';
type Options = {
ci?: boolean;
@@ -35,7 +36,7 @@ type Options = {
validateReleaseTags?: boolean;
} & OptionValues;
-export const buildApiReports = async (paths: string[] = [], opts: Options) => {
+export async function buildApiReports(paths: string[] = [], opts: Options) {
const tmpDir = cliPaths.resolveTargetRoot(
'./node_modules/.cache/api-extractor',
);
@@ -79,9 +80,8 @@ export const buildApiReports = async (paths: string[] = [], opts: Options) => {
await generateTypeDeclarations(tsconfigFilePath);
}
- const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
- selectedPackageDirs,
- );
+ const { tsPackageDirs, cliPackageDirs, sqlPackageDirs } =
+ await categorizePackageDirs(selectedPackageDirs);
if (tsPackageDirs.length > 0) {
console.log('# Generating package API reports');
@@ -104,6 +104,14 @@ export const buildApiReports = async (paths: string[] = [], opts: Options) => {
});
}
+ if (sqlPackageDirs.length > 0 && opts.sqlReports) {
+ console.log('# Generating package SQL reports');
+ await runSqlExtraction({
+ packageDirs: sqlPackageDirs,
+ isLocalBuild: !isCiBuild,
+ });
+ }
+
if (isDocsBuild) {
console.log('# Generating package documentation');
await buildDocs({
@@ -111,7 +119,7 @@ export const buildApiReports = async (paths: string[] = [], opts: Options) => {
outputDir: cliPaths.resolveTargetRoot('docs/reference'),
});
}
-};
+}
/**
* Splits the input string on comma, and returns an array of the resulting substrings.
diff --git a/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts b/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts
new file mode 100644
index 0000000000..86694c3d68
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/categorizePackageDirs.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs-extra';
+import { paths as cliPaths } from '../../lib/paths';
+
+export async function categorizePackageDirs(packageDirs: string[]) {
+ const dirs = packageDirs.slice();
+ const tsPackageDirs = new Array();
+ const cliPackageDirs = new Array();
+ const sqlPackageDirs = new Array();
+
+ await Promise.all(
+ Array(10)
+ .fill(0)
+ .map(async () => {
+ for (;;) {
+ const dir = dirs.pop();
+ if (!dir) {
+ return;
+ }
+
+ const pkgJson = await fs
+ .readJson(cliPaths.resolveTargetRoot(dir, 'package.json'))
+ .catch(error => {
+ if (error.code === 'ENOENT') {
+ return undefined;
+ }
+ throw error;
+ });
+ const role = pkgJson?.backstage?.role;
+ if (!role) {
+ return; // Ignore packages without roles
+ }
+ if (
+ await fs.pathExists(cliPaths.resolveTargetRoot(dir, 'migrations'))
+ ) {
+ sqlPackageDirs.push(dir);
+ }
+ // TODO(Rugvip): Inlined packages are ignored because we can't handle @internal exports
+ // gracefully, and we don't want to have to mark all exports @public etc.
+ // It would be good if we could include these packages though.
+ if (pkgJson?.backstage?.inline) {
+ return;
+ }
+ if (role === 'cli') {
+ cliPackageDirs.push(dir);
+ } else if (role !== 'frontend' && role !== 'backend') {
+ tsPackageDirs.push(dir);
+ }
+ }
+ }),
+ );
+
+ return { tsPackageDirs, cliPackageDirs, sqlPackageDirs };
+}
diff --git a/packages/repo-tools/src/commands/api-reports/cli-reports/generateCliReport.ts b/packages/repo-tools/src/commands/api-reports/cli-reports/generateCliReport.ts
new file mode 100644
index 0000000000..7df5058723
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/cli-reports/generateCliReport.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CliModel } from './types';
+
+export function generateCliReport(options: {
+ packageName: string;
+ models: CliModel[];
+}): string {
+ const content = [
+ `## CLI Report file for "${options.packageName}"`,
+ '',
+ '> Do not edit this file. It is a report generated by `yarn build:api-reports`',
+ '',
+ ];
+
+ for (const model of options.models) {
+ for (const helpPage of model.helpPages) {
+ content.push(
+ `### \`${[model.name, ...helpPage.path].join(' ')}\``,
+ '',
+ '```',
+ `Usage: ${helpPage.usage ?? ''}`,
+ );
+
+ if (helpPage.options.length > 0) {
+ content.push('', 'Options:', ...helpPage.options.map(l => ` ${l}`));
+ }
+
+ if (helpPage.commands.length > 0) {
+ content.push('', 'Commands:', ...helpPage.commands.map(l => ` ${l}`));
+ }
+ content.push('```', '');
+ }
+ }
+
+ return content.join('\n');
+}
diff --git a/packages/repo-tools/src/commands/api-reports/cli-reports/index.ts b/packages/repo-tools/src/commands/api-reports/cli-reports/index.ts
new file mode 100644
index 0000000000..631cf41520
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/cli-reports/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { runCliExtraction } from './runCliExtraction';
diff --git a/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts b/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts
new file mode 100644
index 0000000000..cb4256f2fc
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/cli-reports/runCliExtraction.ts
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ basename,
+ resolve as resolvePath,
+ relative as relativePath,
+} from 'path';
+import fs from 'fs-extra';
+import { createBinRunner } from '../../util';
+import { CliHelpPage, CliModel } from './types';
+import { paths as cliPaths } from '../../../lib/paths';
+import { generateCliReport } from './generateCliReport';
+import { logApiReportInstructions } from '../common';
+
+function parseHelpPage(helpPageContent: string) {
+ const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? [];
+ const lines = helpPageContent.split(/\r?\n/);
+
+ let options = new Array();
+ let commands = new Array();
+ let commandArguments = new Array();
+
+ while (lines.length > 0) {
+ while (lines.length > 0 && !lines[0].endsWith(':')) {
+ lines.shift();
+ }
+ if (lines.length > 0) {
+ // Start of a new section, e.g. "Options:"
+ const sectionName = lines.shift();
+ // Take lines until we hit the next section or the end
+ const sectionEndIndex = lines.findIndex(
+ line => line && !line.match(/^\s/),
+ );
+ const sectionLines = lines.slice(0, sectionEndIndex);
+ lines.splice(0, sectionLines.length);
+
+ // Trim away documentation
+ const sectionItems = sectionLines
+ .map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
+ .filter(Boolean) as string[];
+
+ if (sectionName?.toLocaleLowerCase('en-US') === 'options:') {
+ options = sectionItems;
+ } else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') {
+ commands = sectionItems;
+ } else if (sectionName?.toLocaleLowerCase('en-US') === 'arguments:') {
+ commandArguments = sectionItems;
+ } else {
+ throw new Error(`Unknown CLI section: ${sectionName}`);
+ }
+ }
+ }
+
+ return {
+ usage,
+ options,
+ commands,
+ commandArguments,
+ };
+}
+
+async function exploreCliHelpPages(
+ run: (...args: string[]) => Promise,
+): Promise {
+ const helpPages = new Array();
+
+ async function exploreHelpPage(...path: string[]) {
+ const content = await run(...path, '--help');
+ const parsed = parseHelpPage(content);
+ helpPages.push({ path, ...parsed });
+
+ await Promise.all(
+ parsed.commands.map(async fullCommand => {
+ const command = fullCommand.split(/[|\s]/)[0];
+ if (command !== 'help') {
+ await exploreHelpPage(...path, command);
+ }
+ }),
+ );
+ }
+
+ await exploreHelpPage();
+
+ helpPages.sort((a, b) => a.path.join(' ').localeCompare(b.path.join(' ')));
+
+ return helpPages;
+}
+
+interface CliExtractionOptions {
+ packageDirs: string[];
+ isLocalBuild: boolean;
+}
+
+export async function runCliExtraction({
+ packageDirs,
+ isLocalBuild,
+}: CliExtractionOptions) {
+ for (const packageDir of packageDirs) {
+ console.log(`## Processing ${packageDir}`);
+ const fullDir = cliPaths.resolveTargetRoot(packageDir);
+ const pkgJson = await fs.readJson(resolvePath(fullDir, 'package.json'));
+
+ if (!pkgJson.bin) {
+ throw new Error(`CLI Package in ${packageDir} has no bin field`);
+ }
+
+ const models = new Array();
+ if (typeof pkgJson.bin === 'string') {
+ const run = createBinRunner(fullDir, pkgJson.bin);
+ const helpPages = await exploreCliHelpPages(run);
+ models.push({ name: basename(pkgJson.bin), helpPages });
+ } else {
+ for (const [name, path] of Object.entries(pkgJson.bin)) {
+ const run = createBinRunner(fullDir, path);
+ const helpPages = await exploreCliHelpPages(run);
+ models.push({ name, helpPages });
+ }
+ }
+
+ const report = generateCliReport({ packageName: pkgJson.name, models });
+
+ const reportPath = resolvePath(fullDir, 'cli-report.md');
+ const existingReport = await fs
+ .readFile(reportPath, 'utf8')
+ .catch(error => {
+ if (error.code === 'ENOENT') {
+ return undefined;
+ }
+ throw error;
+ });
+
+ if (existingReport !== report) {
+ if (isLocalBuild) {
+ console.warn(`CLI report changed for ${packageDir}`);
+ await fs.writeFile(reportPath, report);
+ } else {
+ logApiReportInstructions();
+
+ if (existingReport) {
+ console.log('');
+ console.log(
+ `The conflicting file is ${relativePath(
+ cliPaths.targetRoot,
+ reportPath,
+ )}, expecting the following content:`,
+ );
+ console.log('');
+
+ console.log(report);
+
+ logApiReportInstructions();
+ }
+ throw new Error(`CLI report changed for ${packageDir}, `);
+ }
+ }
+ }
+}
diff --git a/packages/canon/docs/components/index.ts b/packages/repo-tools/src/commands/api-reports/cli-reports/types.ts
similarity index 62%
rename from packages/canon/docs/components/index.ts
rename to packages/repo-tools/src/commands/api-reports/cli-reports/types.ts
index 0e5ff2e325..27eb370564 100644
--- a/packages/canon/docs/components/index.ts
+++ b/packages/repo-tools/src/commands/api-reports/cli-reports/types.ts
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-export * from './Banner';
-export * from './Chip';
-export * from './Columns';
-export * from './ComponentStatus';
-export * from './IconLibrary';
-export * from './LayoutComponents';
-export * from './PropsTable';
-export * from './Roadmap';
-export * from './Table';
-export * from './Text';
-export * from './Title';
+// Represents the help page os a CLI command
+export type CliHelpPage = {
+ // Path of commands to reach this page
+ path: string[];
+ // Parsed content
+ usage: string | undefined;
+ options: string[];
+ commands: string[];
+ commandArguments: string[];
+};
+
+// The API model for a CLI entry point
+export type CliModel = {
+ name: string;
+ helpPages: CliHelpPage[];
+};
diff --git a/packages/repo-tools/src/commands/api-reports/common/index.ts b/packages/repo-tools/src/commands/api-reports/common/index.ts
new file mode 100644
index 0000000000..34bd95bfbe
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/common/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { tryRunPrettier } from './tryRunPrettier';
+export { logApiReportInstructions } from './logApiReportInstructions';
diff --git a/packages/repo-tools/src/commands/api-reports/common/logApiReportInstructions.ts b/packages/repo-tools/src/commands/api-reports/common/logApiReportInstructions.ts
new file mode 100644
index 0000000000..d4ab63f4db
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/common/logApiReportInstructions.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export function logApiReportInstructions() {
+ console.log('');
+ console.log(
+ '*************************************************************************************',
+ );
+ console.log(
+ '* You have uncommitted changes to the public API or reports of a package. *',
+ );
+ console.log(
+ '* To solve this, run `yarn build:api-reports` and commit all md file changes. *',
+ );
+ console.log(
+ '*************************************************************************************',
+ );
+ console.log('');
+}
diff --git a/packages/canon/docs/components/Text/styles.css b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts
similarity index 56%
rename from packages/canon/docs/components/Text/styles.css
rename to packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts
index b1bc40a1ac..020853d726 100644
--- a/packages/canon/docs/components/Text/styles.css
+++ b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts
@@ -14,30 +14,22 @@
* limitations under the License.
*/
-.sb-text {
- font-size: 16px;
- line-height: 28px;
- margin: 0;
- margin-bottom: 16px;
- color: #4f4f4f;
+import { paths as cliPaths } from '../../../lib/paths';
+import type { Config } from 'prettier';
- & p {
- font-size: 16px;
- line-height: 28px;
- margin: 0;
- }
+export function tryRunPrettier(
+ content: string,
+ extraConfig: Config = { parser: 'markdown' },
+): string {
+ try {
+ const prettier = require('prettier') as typeof import('prettier');
- & code {
- font-size: 13px;
- color: #215cff;
- background-color: #f1f3fc;
- padding: 4px 4px;
- border-radius: 4px;
- }
-
- & a {
- color: #215cff;
- text-decoration: none;
- border-bottom: 1px solid #215cff;
+ const config = prettier.resolveConfig.sync(cliPaths.targetRoot) ?? {};
+ return prettier.format(content, {
+ ...config,
+ ...extraConfig,
+ });
+ } catch (e) {
+ return content;
}
}
diff --git a/packages/repo-tools/src/commands/api-reports/index.ts b/packages/repo-tools/src/commands/api-reports/index.ts
new file mode 100644
index 0000000000..9c09590d86
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { buildApiReports } from './buildApiReports';
diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/generateSqlReport.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/generateSqlReport.ts
new file mode 100644
index 0000000000..c804c96450
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/sql-reports/generateSqlReport.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { SchemaInfo } from './types';
+
+function sortedEntries(obj: Record): [string, T][] {
+ return Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
+}
+
+function code(str: unknown): string {
+ if (str === '-') {
+ return str;
+ }
+ return `\`${str}\``;
+}
+
+export function generateSqlReport(options: {
+ reportName: string;
+ failedDownMigration?: string;
+ schemaInfo: SchemaInfo;
+}): string {
+ const { reportName, failedDownMigration, schemaInfo } = options;
+
+ const output = [
+ `## SQL Report file for "${reportName}"`,
+ '',
+ '> Do not edit this file. It is a report generated by `yarn build:api-reports`',
+ '',
+ ];
+
+ if (failedDownMigration) {
+ output.push('> [!WARNING]');
+ output.push(`> Failed to migrate down from '${failedDownMigration}'`);
+ output.push('');
+ }
+
+ if (Object.keys(schemaInfo.sequences).length > 0) {
+ output.push('## Sequences');
+ output.push('');
+ for (const [sequenceName, sequenceInfo] of sortedEntries(
+ schemaInfo.sequences,
+ )) {
+ output.push(`- ${code(sequenceName)} (${sequenceInfo.type})`);
+ }
+ output.push('');
+ }
+
+ for (const [tableName, tableInfo] of sortedEntries(schemaInfo.tables)) {
+ output.push(`## Table ${code(tableName)}`);
+ output.push('');
+ output.push(' | Column | Type | Nullable | Max Length | Default |');
+ output.push(' |--------|------|----------|------------|---------|');
+ for (const [columnName, columnInfo] of sortedEntries(tableInfo.columns)) {
+ output.push(
+ ` | ${code(columnName)} | ${code(columnInfo.type)} | ${
+ columnInfo.nullable
+ } | ${columnInfo.maxLength ?? '-'} | ${code(
+ columnInfo.defaultValue ?? '-',
+ )} |`,
+ );
+ }
+ output.push('');
+
+ if (Object.keys(tableInfo.indices).length > 0) {
+ output.push('### Indices');
+ output.push('');
+ for (const [indexName, indexInfo] of sortedEntries(tableInfo.indices)) {
+ const indexType = [
+ indexInfo.unique && 'unique',
+ indexInfo.primary && 'primary',
+ ]
+ .filter(Boolean)
+ .join(' ');
+ output.push(
+ `- ${code(indexName)} (${indexInfo.columns.map(code).join(', ')})${
+ indexType ? ` ${indexType}` : ''
+ }`,
+ );
+ }
+ output.push('');
+ }
+ }
+
+ return output.join('\n');
+}
diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/getPgSchemaInfo.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/getPgSchemaInfo.ts
new file mode 100644
index 0000000000..fc0da743ae
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/sql-reports/getPgSchemaInfo.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Knex } from 'knex';
+import { SchemaIndexInfo, SchemaInfo, SchemaSequenceInfo } from './types';
+
+export async function getPgSchemaInfo(knex: Knex): Promise {
+ const { rows: tableNames } = await knex.raw<{ rows: { name: string }[] }>(`
+ SELECT table_name as name
+ FROM information_schema.tables
+ WHERE
+ table_schema = 'public'
+ AND table_type = 'BASE TABLE'
+ AND table_name NOT LIKE 'knex_migrations%'
+ `);
+
+ const tables = Object.fromEntries(
+ await Promise.all(
+ tableNames.map(async ({ name }) => {
+ const columns = await knex.table(name).columnInfo();
+ const { rows: indices } = await knex.raw<{
+ rows: SchemaIndexInfo[];
+ }>(
+ `
+ SELECT
+ index_class.relname as name,
+ index.indisunique as unique,
+ index.indisprimary as primary,
+ json_agg(attribute.attname ORDER BY keys.rn) as columns
+ FROM
+ pg_class table_class,
+ pg_class index_class,
+ pg_index index,
+ UNNEST(index.indkey) WITH ORDINALITY keys(id, rn)
+ INNER JOIN pg_attribute attribute
+ ON attribute.attnum = keys.id
+ WHERE
+ table_class.oid = index.indrelid
+ AND table_class.relkind = 'r'
+ AND table_class.relname = ?
+ AND index_class.oid = index.indexrelid
+ AND attribute.attrelid = table_class.oid
+ GROUP BY index_class.relname, index.indexrelid
+ `,
+ [name],
+ );
+ return [
+ name,
+ {
+ name,
+ columns,
+ indices: Object.fromEntries(
+ indices.map(index => [index.name, index]),
+ ),
+ },
+ ];
+ }),
+ ),
+ );
+
+ const { rows: sequences } = await knex.raw<{
+ rows: SchemaSequenceInfo[];
+ }>(`
+ SELECT sequence_name as name, data_type as type
+ FROM information_schema.sequences
+ WHERE
+ sequence_schema = 'public'
+ AND sequence_name NOT LIKE 'knex_migrations%'
+ `);
+
+ return {
+ tables,
+ sequences: Object.fromEntries(sequences.map(seq => [seq.name, seq])),
+ };
+}
diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/index.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/index.ts
new file mode 100644
index 0000000000..d6b29d6f82
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/sql-reports/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { runSqlExtraction } from './runSqlExtraction';
diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts
new file mode 100644
index 0000000000..9462412436
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs, { readJson } from 'fs-extra';
+import { relative as relativePath } from 'path';
+import { paths as cliPaths } from '../../../lib/paths';
+import { diff as justDiff } from 'just-diff';
+import { SchemaInfo } from './types';
+import { getPgSchemaInfo } from './getPgSchemaInfo';
+import { generateSqlReport } from './generateSqlReport';
+import type { Knex } from 'knex';
+import { logApiReportInstructions, tryRunPrettier } from '../common';
+
+interface SqlExtractionOptions {
+ packageDirs: string[];
+ isLocalBuild: boolean;
+}
+
+export async function runSqlExtraction(options: SqlExtractionOptions) {
+ const { default: Knex } = await import('knex');
+ const { default: ClientPgLite } = await import('knex-pglite');
+
+ // Since we're passing this as the client we need to replace the `config.client` with `pg` afterwards
+ class WrappedClientPgLite extends ClientPgLite {
+ constructor(config: any) {
+ super({ ...config, client: 'pg' });
+ }
+ }
+
+ let dbIndex = 1;
+
+ for (const packageDir of options.packageDirs) {
+ const migrationDir = cliPaths.resolveTargetRoot(packageDir, 'migrations');
+ if (!(await fs.pathExists(migrationDir))) {
+ console.log(`No SQL migrations found in ${packageDir}`);
+ continue;
+ }
+
+ const { name: pkgName } = await readJson(
+ cliPaths.resolveTargetRoot(packageDir, 'package.json'),
+ );
+
+ const migrationFiles = await fs.readdir(migrationDir, {
+ withFileTypes: true,
+ });
+
+ const migrationTargets = migrationFiles
+ .filter(entry => entry.isDirectory())
+ .map(entry => entry.name);
+ if (migrationFiles.some(entry => entry.isFile())) {
+ migrationTargets.push('.');
+ }
+
+ for (const migrationTarget of migrationTargets) {
+ const database = `extractor-${dbIndex++}`;
+
+ const knex = Knex({
+ client: WrappedClientPgLite,
+ dialect: 'postgres',
+ connection: {
+ database,
+ },
+ });
+
+ await knex.raw('CREATE DATABASE ??', [database]);
+
+ await runSingleSqlExtraction(
+ packageDir,
+ migrationTarget,
+ pkgName,
+ knex,
+ options,
+ );
+ }
+ }
+}
+
+async function runSingleSqlExtraction(
+ targetDir: string,
+ migrationTarget: string,
+ pkgName: string,
+ knex: Knex,
+ options: SqlExtractionOptions,
+) {
+ const migrationDir = cliPaths.resolveTargetRoot(
+ targetDir,
+ 'migrations',
+ migrationTarget,
+ );
+
+ const reportName =
+ migrationTarget === '.' ? pkgName : `${pkgName}/${migrationTarget}`;
+
+ console.log(`Generating SQL report for ${reportName}`);
+
+ const migrationsListResult = await knex.migrate.list({
+ directory: migrationDir,
+ });
+ const migrations: string[] = migrationsListResult[1].map(
+ (m: { file: string }) => m.file,
+ );
+
+ const schemaInfoBeforeMigration = new Map();
+
+ for (const migration of migrations) {
+ const schemaInfo = await getPgSchemaInfo(knex);
+ schemaInfoBeforeMigration.set(migration, schemaInfo);
+
+ await knex.migrate.up({
+ directory: migrationDir,
+ name: migration,
+ });
+ }
+
+ const schemaInfo = await getPgSchemaInfo(knex);
+
+ let failedDownMigration: string | undefined = undefined;
+ for (const migration of migrations.toReversed()) {
+ await knex.migrate.down({
+ directory: migrationDir,
+ name: migration,
+ });
+ const after = await getPgSchemaInfo(knex);
+ const before = schemaInfoBeforeMigration.get(migration);
+ if (!before) {
+ throw new Error(`No previous result for migration ${migration}`);
+ }
+
+ const diff = justDiff(before, after);
+ if (diff.length !== 0) {
+ console.log(
+ `Migration ${migration} is not reversible: ${JSON.stringify(
+ diff,
+ null,
+ 2,
+ )}`,
+ );
+ failedDownMigration = migration;
+ break;
+ }
+ }
+
+ const report = tryRunPrettier(
+ generateSqlReport({
+ reportName,
+ failedDownMigration,
+ schemaInfo,
+ }),
+ );
+
+ const reportPath = cliPaths.resolveTargetRoot(
+ targetDir,
+ `report${migrationTarget === '.' ? '' : `-${migrationTarget}`}.sql.md`,
+ );
+ const existingReport = await fs.readFile(reportPath, 'utf8').catch(error => {
+ if (error.code === 'ENOENT') {
+ return undefined;
+ }
+ throw error;
+ });
+ if (existingReport !== report) {
+ if (options.isLocalBuild) {
+ console.warn(`SQL report changed for ${targetDir}`);
+ await fs.writeFile(reportPath, report);
+ } else {
+ logApiReportInstructions();
+
+ if (existingReport) {
+ console.log('');
+ console.log(
+ `The conflicting file is ${relativePath(
+ cliPaths.targetRoot,
+ reportPath,
+ )}, expecting the following content:`,
+ );
+ console.log('');
+
+ console.log(report);
+
+ logApiReportInstructions();
+ }
+ throw new Error(`Report ${reportPath} is out of date`);
+ }
+ }
+}
diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/types.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/types.ts
new file mode 100644
index 0000000000..c45d3fa01d
--- /dev/null
+++ b/packages/repo-tools/src/commands/api-reports/sql-reports/types.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Knex } from 'knex';
+
+export type SchemaColumnInfo = {
+ name: string;
+ type: string;
+ nullable: boolean;
+ maxLength: number | null;
+ defaultValue: Knex.Value;
+};
+
+export type SchemaIndexInfo = {
+ name: string;
+ unique: boolean;
+ primary: boolean;
+ columns: string[];
+};
+
+export type SchemaTableInfo = {
+ name: string;
+ columns: Record;
+ indices: Record;
+};
+
+export type SchemaSequenceInfo = {
+ name: string;
+ type: string;
+};
+
+export type SchemaInfo = {
+ tables: Record;
+ sequences: Record;
+};
diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts
index e3c4c9e41f..c298384375 100644
--- a/packages/repo-tools/src/commands/index.ts
+++ b/packages/repo-tools/src/commands/index.ts
@@ -39,9 +39,7 @@ function registerPackageCommand(program: Command) {
'Initialize any required files to use the OpenAPI tooling for this package.',
)
.action(
- lazy(() =>
- import('./package/schema/openapi/init').then(m => m.singleCommand),
- ),
+ lazy(() => import('./package/schema/openapi/init'), 'singleCommand'),
);
openApiCommand
@@ -60,11 +58,7 @@ function registerPackageCommand(program: Command) {
)
.option('--watch')
.description('Watch the OpenAPI spec for changes and regenerate on save.')
- .action(
- lazy(() =>
- import('./package/schema/openapi/generate').then(m => m.command),
- ),
- );
+ .action(lazy(() => import('./package/schema/openapi/generate'), 'command'));
openApiCommand
.command('fuzz')
@@ -81,18 +75,14 @@ function registerPackageCommand(program: Command) {
'--exclude-checks ',
'Exclude checks from schemathesis run',
)
- .action(
- lazy(() => import('./package/schema/openapi/fuzz').then(m => m.command)),
- );
+ .action(lazy(() => import('./package/schema/openapi/fuzz'), 'command'));
openApiCommand
.command('diff')
.option('--ignore', 'Ignore linting failures and only log the results.')
.option('--json', 'Output the results as JSON')
.option('--since [', 'Diff the API against a specific ref')
- .action(
- lazy(() => import('./package/schema/openapi/diff').then(m => m.command)),
- );
+ .action(lazy(() => import('./package/schema/openapi/diff'), 'command'));
}
function registerRepoCommand(program: Command) {
@@ -113,11 +103,7 @@ function registerRepoCommand(program: Command) {
.description(
'Verify that all OpenAPI schemas are valid and set up correctly.',
)
- .action(
- lazy(() =>
- import('./repo/schema/openapi/verify').then(m => m.bulkCommand),
- ),
- );
+ .action(lazy(() => import('./repo/schema/openapi/verify'), 'bulkCommand'));
openApiCommand
.command('lint [paths...]')
@@ -126,17 +112,13 @@ function registerRepoCommand(program: Command) {
'--strict',
'Fail on any linting severity messages, not just errors.',
)
- .action(
- lazy(() => import('./repo/schema/openapi/lint').then(m => m.bulkCommand)),
- );
+ .action(lazy(() => import('./repo/schema/openapi/lint'), 'bulkCommand'));
openApiCommand
.command('test [paths...]')
.description('Test OpenAPI schemas against written tests')
.option('--update', 'Update the spec on failure.')
- .action(
- lazy(() => import('./repo/schema/openapi/test').then(m => m.bulkCommand)),
- );
+ .action(lazy(() => import('./repo/schema/openapi/test'), 'bulkCommand'));
openApiCommand
.command('fuzz')
@@ -145,9 +127,7 @@ function registerRepoCommand(program: Command) {
'--since ][',
'Only fuzz packages that have changed since the given ref',
)
- .action(
- lazy(() => import('./repo/schema/openapi/fuzz').then(m => m.command)),
- );
+ .action(lazy(() => import('./repo/schema/openapi/fuzz'), 'command'));
openApiCommand
.command('diff')
@@ -159,9 +139,7 @@ function registerRepoCommand(program: Command) {
'Diff the API against a specific ref',
'origin/master',
)
- .action(
- lazy(() => import('./repo/schema/openapi/diff').then(m => m.command)),
- );
+ .action(lazy(() => import('./repo/schema/openapi/diff'), 'command'));
}
function registerLintCommand(program: Command) {
@@ -174,10 +152,10 @@ function registerLintCommand(program: Command) {
'Lint backend plugin packages for legacy exports and make sure it conforms to the new export pattern',
)
.action(
- lazy(() =>
- import(
- './lint-legacy-backend-exports/lint-legacy-backend-exports'
- ).then(m => m.lint),
+ lazy(
+ () =>
+ import('./lint-legacy-backend-exports/lint-legacy-backend-exports'),
+ 'lint',
),
);
}
@@ -187,6 +165,7 @@ export function registerCommands(program: Command) {
.option('--ci', 'CI run checks that there is no changes on API reports')
.option('--tsc', 'executes the tsc compilation before extracting the APIs')
.option('--docs', 'generates the api documentation')
+ .option('--sql-reports', 'Also generate SQL reports from migration files')
.option(
'--include ',
'Only include packages matching the provided patterns',
@@ -215,16 +194,12 @@ export function registerCommands(program: Command) {
'Turn on release tag validation for the public, beta, and alpha APIs',
)
.description('Generate an API report for selected packages')
- .action(
- lazy(() =>
- import('./api-reports/api-reports').then(m => m.buildApiReports),
- ),
- );
+ .action(lazy(() => import('./api-reports'), 'buildApiReports'));
program
.command('type-deps')
.description('Find inconsistencies in types of all packages and plugins')
- .action(lazy(() => import('./type-deps/type-deps').then(m => m.default)));
+ .action(lazy(() => import('./type-deps/type-deps'), 'default'));
program
.command('peer-deps')
@@ -232,7 +207,7 @@ export function registerCommands(program: Command) {
'Ensure your packages are using the correct peer dependency format.',
)
.option('--fix', 'Fix the issues found')
- .action(lazy(() => import('./peer-deps/peer-deps').then(m => m.default)));
+ .action(lazy(() => import('./peer-deps/peer-deps'), 'default'));
program
.command('generate-catalog-info')
@@ -246,10 +221,9 @@ export function registerCommands(program: Command) {
)
.description('Create or fix info yaml files for all backstage packages')
.action(
- lazy(() =>
- import('./generate-catalog-info/generate-catalog-info').then(
- m => m.default,
- ),
+ lazy(
+ () => import('./generate-catalog-info/generate-catalog-info'),
+ 'default',
),
);
@@ -278,20 +252,14 @@ export function registerCommands(program: Command) {
.description(
'Generate a patch for the selected package in the target repository',
)
- .action(
- lazy(() =>
- import('./generate-patch/generate-patch').then(m => m.default),
- ),
- );
+ .action(lazy(() => import('./generate-patch/generate-patch'), 'default'));
program
.command('knip-reports [paths...]')
.option('--ci', 'CI run checks that there is no changes on knip reports')
.description('Generate a knip report for selected packages')
.action(
- lazy(() =>
- import('./knip-reports/knip-reports').then(m => m.buildKnipReports),
- ),
+ lazy(() => import('./knip-reports/knip-reports'), 'buildKnipReports'),
);
registerPackageCommand(program);
@@ -299,13 +267,25 @@ export function registerCommands(program: Command) {
registerLintCommand(program);
}
+type ActionFunc = (...args: any[]) => Promise;
+type ActionExports = {
+ [KName in keyof TModule as TModule[KName] extends ActionFunc
+ ? KName
+ : never]: TModule[KName];
+};
+
// Wraps an action function so that it always exits and handles errors
-function lazy(
- getActionFunc: () => Promise<(...args: any[]) => Promise>,
+export function lazy(
+ moduleLoader: () => Promise,
+ exportName: keyof ActionExports,
): (...args: any[]) => Promise {
return async (...args: any[]) => {
try {
- const actionFunc = await getActionFunc();
+ const mod = await moduleLoader();
+ const actualModule = (
+ mod as unknown as { default: ActionExports }
+ ).default;
+ const actionFunc = actualModule[exportName] as ActionFunc;
await actionFunc(...args);
process.exit(0);
diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts
index 61aadc5d0c..9eab1c77f5 100644
--- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts
+++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts
@@ -54,7 +54,7 @@ async function verify(directoryPath: string) {
schemaPath = join(directoryPath, OLD_SCHEMA_PATH);
}
- const schema = await import(resolvePath(schemaPath));
+ const { default: schema } = await import(resolvePath(schemaPath));
if (!schema.spec) {
throw new Error(`\`${TS_SCHEMA_PATH}\` needs to have a 'spec' export.`);
diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md
index 585c0edb51..16e7d83f1e 100644
--- a/packages/scaffolder-internal/CHANGELOG.md
+++ b/packages/scaffolder-internal/CHANGELOG.md
@@ -1,5 +1,13 @@
# @internal/scaffolder
+## 0.0.5
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-react@1.14.3
+ - @backstage/frontend-plugin-api@0.9.4
+
## 0.0.5-next.2
### Patch Changes
diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json
index 4f4479919d..2426c3548b 100644
--- a/packages/scaffolder-internal/package.json
+++ b/packages/scaffolder-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/scaffolder",
- "version": "0.0.5-next.2",
+ "version": "0.0.5",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md
index c39a760304..edba52c2c0 100644
--- a/packages/techdocs-cli-embedded-app/CHANGELOG.md
+++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md
@@ -1,5 +1,24 @@
# techdocs-cli-embedded-app
+## 0.2.104
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.29.5
+ - @backstage/plugin-techdocs@1.12.1
+ - @backstage/core-plugin-api@1.10.3
+ - @backstage/core-components@0.16.3
+ - @backstage/plugin-catalog@1.26.1
+ - @backstage/app-defaults@1.5.16
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.15.4
+ - @backstage/integration-react@1.2.3
+ - @backstage/test-utils@1.7.4
+ - @backstage/theme@0.6.3
+ - @backstage/plugin-techdocs-react@1.2.13
+
## 0.2.104-next.2
### Patch Changes
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index c7685aab83..8c8cde75f0 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -1,6 +1,6 @@
{
"name": "techdocs-cli-embedded-app",
- "version": "0.2.104-next.2",
+ "version": "0.2.104",
"backstage": {
"role": "frontend"
},
diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md
index ea73d21769..94fb6b9e9d 100644
--- a/packages/techdocs-cli/CHANGELOG.md
+++ b/packages/techdocs-cli/CHANGELOG.md
@@ -1,5 +1,16 @@
# @techdocs/cli
+## 1.8.25
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.7.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/plugin-techdocs-node@1.12.16
+
## 1.8.25-next.1
### Patch Changes
diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md
index e7ae51053b..22f0093067 100644
--- a/packages/techdocs-cli/README.md
+++ b/packages/techdocs-cli/README.md
@@ -44,7 +44,8 @@ yarn techdocs-cli:dev [...options]
```sh
# Prior to executing the techdocs-cli command
-export GLOBAL_AGENT_HTTPS_PROXY=${HTTP_PROXY}
+export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY}
+export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY}
export GLOBAL_AGENT_NO_PROXY=${NO_PROXY}
```
diff --git a/packages/techdocs-cli/cli-report.md b/packages/techdocs-cli/cli-report.md
index 9777b2c4b8..5b0f4c9857 100644
--- a/packages/techdocs-cli/cli-report.md
+++ b/packages/techdocs-cli/cli-report.md
@@ -83,6 +83,7 @@ Options:
--awsS3sse
--awsS3ForcePathStyle
--awsBucketRootPath
+ --awsMaxAttempts
--osCredentialId
--osSecret
--osAuthUrl
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index 77e0f56974..a38e7b5965 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@techdocs/cli",
- "version": "1.8.25-next.1",
+ "version": "1.8.25",
"description": "Utility CLI for managing TechDocs sites in Backstage.",
"backstage": {
"role": "cli"
diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts
index ac9523e639..253a2900fd 100644
--- a/packages/techdocs-cli/src/commands/index.ts
+++ b/packages/techdocs-cli/src/commands/index.ts
@@ -81,7 +81,7 @@ export function registerCommands(program: Command) {
false,
)
.alias('build')
- .action(lazy(() => import('./generate/generate').then(m => m.default)));
+ .action(lazy(() => import('./generate/generate'), 'default'));
program
.command('migrate')
@@ -143,7 +143,7 @@ export function registerCommands(program: Command) {
'25',
)
.option('-v --verbose', 'Enable verbose output.', false)
- .action(lazy(() => import('./migrate/migrate').then(m => m.default)));
+ .action(lazy(() => import('./migrate/migrate'), 'default'));
program
.command('publish')
@@ -196,6 +196,10 @@ export function registerCommands(program: Command) {
'--awsBucketRootPath ',
'Optional sub-directory to store files in Amazon S3',
)
+ .option(
+ '--awsMaxAttempts ]