@astryxdesign/cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -1
- package/README.md +49 -55
- package/docs/layout.doc.dense.mjs +90 -0
- package/docs/layout.doc.mjs +160 -0
- package/docs/principles.doc.dense.mjs +2 -2
- package/docs/principles.doc.mjs +4 -0
- package/docs/principles.doc.zh.mjs +2 -2
- package/docs/theme.doc.mjs +2 -2
- package/package.json +13 -1
- package/src/api/blog.mjs +192 -0
- package/src/api/blog.test.mjs +144 -0
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +14 -7
- package/src/api/doctor.mjs +10 -25
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +11 -10
- package/src/api/layout.test.mjs +4 -1
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +242 -84
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
- package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
- package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/agent-docs.mjs +4 -1
- package/src/commands/blog.mjs +60 -0
- package/src/commands/build-theme.import-path.test.mjs +2 -12
- package/src/commands/build-theme.mjs +117 -0
- package/src/commands/build-theme.prose.test.mjs +5 -15
- package/src/commands/build-theme.watch.test.mjs +149 -0
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/ensure-core-built.mjs +120 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/swizzle.mjs +224 -235
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -170
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +7 -20
- package/src/config.test.mjs +89 -13
- package/src/index.mjs +8 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +41 -55
- package/src/lib/error-codes.mjs +15 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +92 -115
- package/src/lib/integrations.test.mjs +88 -107
- package/src/lib/manifest.mjs +5 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/site.mjs +20 -0
- package/src/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +56 -85
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -237
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +14 -0
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +28 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +34 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +21 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +15 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +16 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +19 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +34 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +30 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +14 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +31 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +26 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +18 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +27 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +32 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +14 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +30 -0
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -6
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +27 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +1 -5
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +20 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +23 -0
- package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +14 -0
- package/templates/blocks/components/FileInput/FileInputBasic.tsx +22 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +14 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +38 -0
- package/templates/blocks/components/HStack/HStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/HStack/HStackBasic.tsx +16 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +57 -0
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +27 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +23 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +40 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +22 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +41 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +23 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +40 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +22 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +37 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.tsx +53 -0
- package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +4 -3
- package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxVideo.tsx +27 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.tsx +33 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +19 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +23 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +42 -0
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -6
- package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +14 -0
- package/templates/blocks/components/NavIcon/NavIconBasic.tsx +16 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +30 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +25 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.tsx +67 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +21 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +39 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +55 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +38 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +49 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +71 -0
- package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +14 -0
- package/templates/blocks/components/StackItem/StackItemFill.tsx +28 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +23 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +22 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +21 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +20 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +46 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +16 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +26 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +43 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +43 -0
- package/templates/blocks/components/VStack/VStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/VStack/VStackBasic.tsx +20 -0
- package/templates/pages/kanban-board/page.tsx +729 -0
- package/templates/pages/kanban-board/template.doc.mjs +12 -0
- package/templates/pages/shell-nav/page.tsx +321 -0
- package/templates/pages/shell-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-side-nav/page.tsx +241 -0
- package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-top-nav/page.tsx +224 -0
- package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -464
- package/src/commands/gap-report.test.mjs +0 -168
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -113
- package/src/lib/config.test.mjs +0 -91
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +0 -64
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +0 -60
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/src/commands/swizzle.mjs
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file swizzle command — Copy component source for customization
|
|
5
5
|
*
|
|
6
|
-
* Resolves component
|
|
7
|
-
* copies non-test files to the output directory, and
|
|
8
|
-
* relative imports to use
|
|
6
|
+
* Resolves a component's owning package (core or a configured integration),
|
|
7
|
+
* copies its non-test/non-doc source files to the output directory, and
|
|
8
|
+
* rewrites escaping relative imports to use the OWNER package's subpaths.
|
|
9
9
|
*
|
|
10
|
-
* After swizzling,
|
|
11
|
-
*
|
|
10
|
+
* After swizzling, prints a short maintainer feedback note pointing users at
|
|
11
|
+
* the owner's issue tracker so they can let the team know what gap led them to
|
|
12
|
+
* customize the component. The core feedback URL is routed through app config
|
|
13
|
+
* (`config.issuesUrl`); integration components use their manifest `issuesUrl`.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import * as fs from 'node:fs';
|
|
@@ -20,27 +22,36 @@ import {
|
|
|
20
22
|
PathSafetyError,
|
|
21
23
|
isNonInteractive,
|
|
22
24
|
} from '../utils/path-safety.mjs';
|
|
23
|
-
import {isInteractive} from '../utils/interactive.mjs';
|
|
24
25
|
import {jsonOut, humanLog} from '../lib/json.mjs';
|
|
25
26
|
import {cliError} from '../lib/cli-error.mjs';
|
|
26
27
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
28
|
+
import {checkGhCli} from '../utils/github.mjs';
|
|
29
|
+
import {Project} from '../lib/project.mjs';
|
|
27
30
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
CORE_PACKAGE,
|
|
32
|
+
findIntegrationComponentDoc,
|
|
33
|
+
findIntegrationComponentSource,
|
|
34
|
+
} from '../lib/component-discovery.mjs';
|
|
35
|
+
|
|
36
|
+
/** Default issue tracker for maintainer feedback after swizzling. */
|
|
37
|
+
const DEFAULT_ISSUES_URL = 'https://github.com/facebook/astryx/issues/new';
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
* Rewrite relative imports that point outside the component directory
|
|
38
|
-
*
|
|
40
|
+
* Rewrite relative imports that point outside the component directory to use
|
|
41
|
+
* the OWNER package's subpaths. Imports within the copied directory (./x) are
|
|
42
|
+
* left untouched.
|
|
39
43
|
*
|
|
40
|
-
* e.g.
|
|
44
|
+
* e.g. with ownerPackage '@astryxdesign/core':
|
|
45
|
+
* '../theme/tokens.stylex' -> '@astryxdesign/core/theme'
|
|
41
46
|
* '../utils/mergeProps' -> '@astryxdesign/core/utils'
|
|
47
|
+
*
|
|
48
|
+
* `ownerPackage` defaults to '@astryxdesign/core' so existing core behavior is
|
|
49
|
+
* unchanged; integration components pass their own owning package.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} content
|
|
52
|
+
* @param {string} [ownerPackage]
|
|
42
53
|
*/
|
|
43
|
-
export function rewriteImports(content) {
|
|
54
|
+
export function rewriteImports(content, ownerPackage = CORE_PACKAGE) {
|
|
44
55
|
// Match import/export from statements with relative paths going up
|
|
45
56
|
return content.replace(
|
|
46
57
|
/(from\s+['"])(\.\.\/.+?)(['"])/g,
|
|
@@ -51,12 +62,42 @@ export function rewriteImports(content) {
|
|
|
51
62
|
const parts = importPath.replace(/^\.\.\//, '').split('/');
|
|
52
63
|
const topDir = parts[0];
|
|
53
64
|
|
|
54
|
-
// Map to
|
|
55
|
-
return `${prefix}
|
|
65
|
+
// Map to the owner package subpath
|
|
66
|
+
return `${prefix}${ownerPackage}/${topDir}${suffix}`;
|
|
56
67
|
},
|
|
57
68
|
);
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Build the maintainer feedback note for a swizzled component.
|
|
73
|
+
*
|
|
74
|
+
* Returns { issuesUrl, ghCommand? } or null when no issues URL is available
|
|
75
|
+
* (an integration that ships no `issuesUrl`). When the issues URL is a GitHub
|
|
76
|
+
* issues URL and the `gh` CLI is available, a ready-to-run `gh issue create`
|
|
77
|
+
* command is included so the user can file feedback without leaving the
|
|
78
|
+
* terminal.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} component bare component name (used in the issue title)
|
|
81
|
+
* @param {string|undefined} issuesUrl owner's issue tracker URL
|
|
82
|
+
* @returns {{issuesUrl: string, ghCommand?: string} | null}
|
|
83
|
+
*/
|
|
84
|
+
function buildFeedback(component, issuesUrl) {
|
|
85
|
+
if (!issuesUrl) return null;
|
|
86
|
+
|
|
87
|
+
const feedback = {issuesUrl};
|
|
88
|
+
|
|
89
|
+
// Accept any github.com/<owner>/<repo>/issues(/new)? form.
|
|
90
|
+
const match = issuesUrl.match(
|
|
91
|
+
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues(?:\/new)?\/?$/,
|
|
92
|
+
);
|
|
93
|
+
if (match && checkGhCli()) {
|
|
94
|
+
const [, owner, repo] = match;
|
|
95
|
+
feedback.ghCommand = `gh issue create --repo ${owner}/${repo} --title "[${component}] Swizzle feedback"`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return feedback;
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
function isCancel(value) {
|
|
61
102
|
if (p.isCancel(value)) {
|
|
62
103
|
p.cancel('Cancelled.');
|
|
@@ -65,26 +106,85 @@ function isCancel(value) {
|
|
|
65
106
|
return value;
|
|
66
107
|
}
|
|
67
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Load the configured integrations + core issues URL for `cwd`, swallowing any
|
|
111
|
+
* config errors so swizzle never hard-fails on a malformed/absent config. An
|
|
112
|
+
* empty list means "core only". The core issues URL is routed through the
|
|
113
|
+
* Project (config.issuesUrl, falling back to the default core tracker).
|
|
114
|
+
* @param {string} cwd
|
|
115
|
+
* @returns {Promise<{loadedIntegrations: Array<object>, issuesUrl: string|undefined, project: Project|null}>}
|
|
116
|
+
*/
|
|
117
|
+
async function loadConfigSafely(cwd) {
|
|
118
|
+
try {
|
|
119
|
+
const project = await Project.load(cwd);
|
|
120
|
+
return {
|
|
121
|
+
loadedIntegrations: project.loadedIntegrations,
|
|
122
|
+
issuesUrl: project.config.issuesUrl,
|
|
123
|
+
project,
|
|
124
|
+
};
|
|
125
|
+
} catch {
|
|
126
|
+
return {loadedIntegrations: [], issuesUrl: undefined, project: null};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build the set of OWNER packages that provide a component with `name` across
|
|
132
|
+
* core + every loaded integration.
|
|
133
|
+
*
|
|
134
|
+
* Core ownership is determined by the source directory existing on disk
|
|
135
|
+
* (`coreDir/src/<name>`) so plain core components without a `.doc.mjs` still
|
|
136
|
+
* resolve as today. Integration ownership is determined by a same-stem doc file
|
|
137
|
+
* (`<name>.doc.{ts,mjs,js}`) via findIntegrationComponentDoc.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} coreDir
|
|
140
|
+
* @param {Array<{name: string, components?: string, issuesUrl?: string}>} loadedIntegrations
|
|
141
|
+
* @param {string} name bare component name (no XDS/Astryx prefix)
|
|
142
|
+
* @param {string|undefined} coreIssuesUrl
|
|
143
|
+
* @returns {Array<{package: string, sourceDir: string|null, ownerPackage: string, issuesUrl: string|undefined}>}
|
|
144
|
+
*/
|
|
145
|
+
function resolveOwners(coreDir, loadedIntegrations, name, coreIssuesUrl) {
|
|
146
|
+
const owners = [];
|
|
147
|
+
|
|
148
|
+
const coreComponentDir = path.join(coreDir, 'src', name);
|
|
149
|
+
if (fs.existsSync(coreComponentDir)) {
|
|
150
|
+
owners.push({
|
|
151
|
+
package: CORE_PACKAGE,
|
|
152
|
+
sourceDir: coreComponentDir,
|
|
153
|
+
ownerPackage: CORE_PACKAGE,
|
|
154
|
+
issuesUrl: coreIssuesUrl || DEFAULT_ISSUES_URL,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const integration of loadedIntegrations) {
|
|
159
|
+
const docPath = findIntegrationComponentDoc(integration, name);
|
|
160
|
+
if (!docPath) continue;
|
|
161
|
+
const sourcePath = findIntegrationComponentSource(integration, name);
|
|
162
|
+
owners.push({
|
|
163
|
+
package: integration.name,
|
|
164
|
+
// The component's own folder — the directory containing its source.
|
|
165
|
+
sourceDir: sourcePath ? path.dirname(sourcePath) : null,
|
|
166
|
+
ownerPackage: integration.name,
|
|
167
|
+
issuesUrl: integration.issuesUrl,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return owners;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Whether a filename should be excluded from the swizzle copy. */
|
|
175
|
+
function isExcludedFromCopy(file) {
|
|
176
|
+
return (
|
|
177
|
+
file.includes('.test.') || file.includes('.doc.') || file === 'README.md'
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
68
181
|
export function registerSwizzle(program) {
|
|
69
182
|
program
|
|
70
183
|
.command('swizzle [component]')
|
|
71
184
|
.description('Copy component source for customization')
|
|
72
185
|
.option('--output <dir>', 'Output directory', './components/astryx')
|
|
186
|
+
.option('--package <pkg>', 'Scope to a specific owning package')
|
|
73
187
|
.option('--list', 'List available components')
|
|
74
|
-
.option('--gap <reason>', 'File a gap report explaining why you swizzled')
|
|
75
|
-
.option('--gap-category <category>', 'Gap category (for --gap mode)')
|
|
76
|
-
.option(
|
|
77
|
-
'--no-report',
|
|
78
|
-
'Suppress all gap reporting (interactive prompt AND --gap auto-filing)',
|
|
79
|
-
)
|
|
80
|
-
.option(
|
|
81
|
-
'--dry-run',
|
|
82
|
-
'For --gap: print the issue that would be filed without contacting GitHub. Default in non-TTY / --json mode.',
|
|
83
|
-
)
|
|
84
|
-
.option(
|
|
85
|
-
'--commit',
|
|
86
|
-
'For --gap: actually file the issue. Required in non-interactive mode.',
|
|
87
|
-
)
|
|
88
188
|
.option('-f, --overwrite', 'Overwrite existing files without prompting')
|
|
89
189
|
.action(async (component, options) => {
|
|
90
190
|
const coreDir = findCoreDir(process.cwd());
|
|
@@ -115,9 +215,25 @@ export function registerSwizzle(program) {
|
|
|
115
215
|
}
|
|
116
216
|
|
|
117
217
|
const dirName = component.replace(/^XDS/, '');
|
|
118
|
-
const componentDir = path.join(coreDir, 'src', dirName);
|
|
119
218
|
|
|
120
|
-
|
|
219
|
+
// Resolve the component's owning package(s) across core + integrations.
|
|
220
|
+
const {loadedIntegrations, project} = await loadConfigSafely(
|
|
221
|
+
process.cwd(),
|
|
222
|
+
);
|
|
223
|
+
// Core feedback URL is routed through the Project (config.issuesUrl,
|
|
224
|
+
// falling back to the default core tracker). When config load failed,
|
|
225
|
+
// resolveOwners applies the same default fallback.
|
|
226
|
+
const coreIssuesUrl = project
|
|
227
|
+
? project.issuesUrl({package: CORE_PACKAGE})
|
|
228
|
+
: undefined;
|
|
229
|
+
const allOwners = resolveOwners(
|
|
230
|
+
coreDir,
|
|
231
|
+
loadedIntegrations,
|
|
232
|
+
dirName,
|
|
233
|
+
coreIssuesUrl,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (allOwners.length === 0) {
|
|
121
237
|
cliError(`Component "${component}" not found.`, {
|
|
122
238
|
suggestions: components.slice(0, 10).map(n => ({name: n})),
|
|
123
239
|
code: ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
@@ -125,6 +241,48 @@ export function registerSwizzle(program) {
|
|
|
125
241
|
return;
|
|
126
242
|
}
|
|
127
243
|
|
|
244
|
+
let owner;
|
|
245
|
+
if (options.package) {
|
|
246
|
+
owner = allOwners.find(o => o.package === options.package);
|
|
247
|
+
if (!owner) {
|
|
248
|
+
cliError(
|
|
249
|
+
`Component "${dirName}" is not provided by package "${options.package}".`,
|
|
250
|
+
{
|
|
251
|
+
suggestions: allOwners.map(o => ({
|
|
252
|
+
name: o.package,
|
|
253
|
+
reason: 'provides this component',
|
|
254
|
+
})),
|
|
255
|
+
code: ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
} else if (allOwners.length > 1) {
|
|
261
|
+
cliError(
|
|
262
|
+
`Component "${dirName}" is provided by multiple packages. Re-run with --package <pkg> to choose one.`,
|
|
263
|
+
{
|
|
264
|
+
suggestions: allOwners.map(o => ({
|
|
265
|
+
name: o.package,
|
|
266
|
+
reason: 'provides this component',
|
|
267
|
+
})),
|
|
268
|
+
code: ERROR_CODES.ERR_AMBIGUOUS_COMPONENT,
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
return;
|
|
272
|
+
} else {
|
|
273
|
+
owner = allOwners[0];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!owner.sourceDir || !fs.existsSync(owner.sourceDir)) {
|
|
277
|
+
cliError(
|
|
278
|
+
`No source found for "${dirName}" in package "${owner.package}".`,
|
|
279
|
+
{code: ERROR_CODES.ERR_NO_SOURCE},
|
|
280
|
+
);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const componentDir = owner.sourceDir;
|
|
285
|
+
|
|
128
286
|
// Path-safety: --output must resolve inside cwd. Reject absolute
|
|
129
287
|
// paths and `..` traversal up front, before any directory is created.
|
|
130
288
|
let outputBase;
|
|
@@ -144,7 +302,7 @@ export function registerSwizzle(program) {
|
|
|
144
302
|
// Pre-flight overwrite check: collect files we'd write and detect
|
|
145
303
|
// collisions before mkdir/writeFile so we never half-clobber.
|
|
146
304
|
const sourceFiles = fs.readdirSync(componentDir).filter(file => {
|
|
147
|
-
if (
|
|
305
|
+
if (isExcludedFromCopy(file)) return false;
|
|
148
306
|
const stat = fs.statSync(path.join(componentDir, file));
|
|
149
307
|
return stat.isFile();
|
|
150
308
|
});
|
|
@@ -178,13 +336,13 @@ export function registerSwizzle(program) {
|
|
|
178
336
|
|
|
179
337
|
fs.mkdirSync(outputDir, {recursive: true});
|
|
180
338
|
|
|
181
|
-
// Copy all non-test, non-README files
|
|
339
|
+
// Copy all non-test, non-doc, non-README files
|
|
182
340
|
const files = fs.readdirSync(componentDir);
|
|
183
341
|
let copied = 0;
|
|
184
342
|
|
|
185
343
|
for (const file of files) {
|
|
186
|
-
// Skip test files and README
|
|
187
|
-
if (
|
|
344
|
+
// Skip test files, doc files, and README
|
|
345
|
+
if (isExcludedFromCopy(file)) continue;
|
|
188
346
|
|
|
189
347
|
const srcPath = path.join(componentDir, file);
|
|
190
348
|
const stat = fs.statSync(srcPath);
|
|
@@ -192,9 +350,9 @@ export function registerSwizzle(program) {
|
|
|
192
350
|
|
|
193
351
|
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
194
352
|
|
|
195
|
-
// Rewrite imports for .ts/.tsx files
|
|
353
|
+
// Rewrite escaping imports for .ts/.tsx files to the owner package.
|
|
196
354
|
if (file.endsWith('.ts') || file.endsWith('.tsx')) {
|
|
197
|
-
content = rewriteImports(content);
|
|
355
|
+
content = rewriteImports(content, owner.ownerPackage);
|
|
198
356
|
}
|
|
199
357
|
|
|
200
358
|
fs.writeFileSync(path.join(outputDir, file), content);
|
|
@@ -204,214 +362,45 @@ export function registerSwizzle(program) {
|
|
|
204
362
|
const relOutput = path.relative(process.cwd(), outputDir);
|
|
205
363
|
const copiedFiles = files.filter(
|
|
206
364
|
f =>
|
|
207
|
-
!f
|
|
208
|
-
f !== 'README.md' &&
|
|
365
|
+
!isExcludedFromCopy(f) &&
|
|
209
366
|
fs.statSync(path.join(componentDir, f)).isFile(),
|
|
210
367
|
);
|
|
211
368
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const gapConfig = await loadGapReportConfig();
|
|
215
|
-
let gapReportUrl = null;
|
|
216
|
-
let gapDryRunPreview = null;
|
|
369
|
+
const feedback = buildFeedback(dirName, owner.issuesUrl);
|
|
217
370
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const reportingSuppressed = options.report === false;
|
|
222
|
-
|
|
223
|
-
if (options.gap && !reportingSuppressed && gapConfig.enabled) {
|
|
224
|
-
const category = options.gapCategory || 'other';
|
|
225
|
-
const previewArgs = {
|
|
226
|
-
component: dirName,
|
|
227
|
-
category,
|
|
228
|
-
intention: options.gap,
|
|
229
|
-
source: 'llm-auto',
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const willFile = shouldActuallyFile({
|
|
233
|
-
commit: options.commit,
|
|
234
|
-
dryRun: options.dryRun,
|
|
235
|
-
json,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const preview = await buildGapReportPreview(previewArgs);
|
|
239
|
-
|
|
240
|
-
if (!willFile) {
|
|
241
|
-
// Dry-run: do NOT call gh. Surface what would have been filed.
|
|
242
|
-
gapDryRunPreview = {
|
|
243
|
-
dryRun: true,
|
|
244
|
-
wouldFile: preview.mode !== 'disabled',
|
|
245
|
-
mode: preview.mode,
|
|
246
|
-
title: preview.title,
|
|
247
|
-
body: preview.body,
|
|
248
|
-
repo: preview.repo,
|
|
249
|
-
command: preview.command || null,
|
|
250
|
-
};
|
|
251
|
-
} else if (gapConfig.command || checkGhCli()) {
|
|
252
|
-
try {
|
|
253
|
-
gapReportUrl = await createGapReport(previewArgs);
|
|
254
|
-
} catch (err) {
|
|
255
|
-
if (!json)
|
|
256
|
-
console.error(
|
|
257
|
-
`Warning: Could not file gap report: ${err.message}`,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (options.gap) {
|
|
264
|
-
if (json)
|
|
265
|
-
return jsonOut('swizzle.copy', {
|
|
266
|
-
component: dirName,
|
|
267
|
-
outputDir: relOutput,
|
|
268
|
-
filesCopied: copied,
|
|
269
|
-
files: copiedFiles.map(f => f),
|
|
270
|
-
gapReport: gapReportUrl,
|
|
271
|
-
gapReportDryRun: gapDryRunPreview,
|
|
272
|
-
gapReportSuppressed: reportingSuppressed || !gapConfig.enabled,
|
|
273
|
-
});
|
|
274
|
-
humanLog(`\n✓ Copied ${copied} files to ${relOutput}/\n`);
|
|
275
|
-
humanLog(
|
|
276
|
-
'Relative imports have been rewritten to use @astryxdesign/core.',
|
|
277
|
-
);
|
|
278
|
-
humanLog('You can now customize the component source freely.\n');
|
|
279
|
-
if (gapReportUrl) {
|
|
280
|
-
humanLog(`✓ Gap report filed: ${gapReportUrl}\n`);
|
|
281
|
-
} else if (gapDryRunPreview) {
|
|
282
|
-
humanLog(
|
|
283
|
-
formatPreview(
|
|
284
|
-
await buildGapReportPreview({
|
|
285
|
-
component: dirName,
|
|
286
|
-
category: options.gapCategory || 'other',
|
|
287
|
-
intention: options.gap,
|
|
288
|
-
source: 'llm-auto',
|
|
289
|
-
}),
|
|
290
|
-
),
|
|
291
|
-
);
|
|
292
|
-
humanLog(
|
|
293
|
-
'\n[dry-run] No gap report was filed. Re-run with --commit to file.',
|
|
294
|
-
);
|
|
295
|
-
} else if (reportingSuppressed) {
|
|
296
|
-
humanLog('Gap reporting suppressed by --no-report.');
|
|
297
|
-
} else if (!gapConfig.enabled) {
|
|
298
|
-
humanLog('Gap reporting is disabled via configuration.');
|
|
299
|
-
} else if (!gapConfig.command && !checkGhCli()) {
|
|
300
|
-
humanLog('Skipping gap report: gh CLI not available.');
|
|
301
|
-
}
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (json)
|
|
306
|
-
return jsonOut('swizzle.copy', {
|
|
371
|
+
if (json) {
|
|
372
|
+
/** @type {Record<string, unknown>} */
|
|
373
|
+
const payload = {
|
|
307
374
|
component: dirName,
|
|
375
|
+
package: owner.package,
|
|
308
376
|
outputDir: relOutput,
|
|
309
377
|
filesCopied: copied,
|
|
310
378
|
files: copiedFiles.map(f => f),
|
|
311
|
-
}
|
|
379
|
+
};
|
|
380
|
+
if (feedback) payload.feedback = feedback;
|
|
381
|
+
return jsonOut('swizzle.copy', payload);
|
|
382
|
+
}
|
|
312
383
|
|
|
313
384
|
humanLog(`\n✓ Copied ${copied} files to ${relOutput}/\n`);
|
|
314
385
|
humanLog(
|
|
315
|
-
|
|
386
|
+
`Relative imports have been rewritten to use ${owner.ownerPackage}.`,
|
|
316
387
|
);
|
|
317
388
|
humanLog('You can now customize the component source freely.\n');
|
|
318
389
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (!gapConfig.command && !checkGhCli()) {
|
|
334
|
-
// Silently skip if gh isn't available and no custom command configured
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const shouldReport = isCancel(
|
|
339
|
-
await p.confirm({
|
|
340
|
-
message: 'Would you like to report why you swizzled this component?',
|
|
341
|
-
initialValue: false,
|
|
342
|
-
}),
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
if (!shouldReport) return;
|
|
346
|
-
|
|
347
|
-
const category = isCancel(
|
|
348
|
-
await p.select({
|
|
349
|
-
message: 'What kind of gap is this?',
|
|
350
|
-
options: GAP_CATEGORIES,
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
const intention = isCancel(
|
|
355
|
-
await p.text({
|
|
356
|
-
message: 'What were you trying to achieve?',
|
|
357
|
-
placeholder:
|
|
358
|
-
'e.g. "Need a compact variant for use in dense data tables"',
|
|
359
|
-
validate: val => {
|
|
360
|
-
if (!val.trim())
|
|
361
|
-
return 'Please describe what you were trying to do';
|
|
362
|
-
},
|
|
363
|
-
}),
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const detail = isCancel(
|
|
367
|
-
await p.text({
|
|
368
|
-
message: 'Any additional context? (optional)',
|
|
369
|
-
placeholder: 'Press Enter to skip',
|
|
370
|
-
}),
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
const previewArgs = {
|
|
374
|
-
component: dirName,
|
|
375
|
-
category,
|
|
376
|
-
intention: intention.trim(),
|
|
377
|
-
detail: detail?.trim() || undefined,
|
|
378
|
-
source: 'interactive',
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const preview = await buildGapReportPreview(previewArgs);
|
|
382
|
-
|
|
383
|
-
p.note(
|
|
384
|
-
`${preview.mode === 'github' ? `Repo: ${preview.repo}` : `Custom command: ${preview.command}`}\n\n` +
|
|
385
|
-
`Title:\n ${preview.title}\n\n` +
|
|
386
|
-
`Body:\n${preview.body
|
|
387
|
-
.split('\n')
|
|
388
|
-
.map(l => ` ${l}`)
|
|
389
|
-
.join('\n')}`,
|
|
390
|
-
'Preview — this is exactly what will be filed',
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
const confirmFile = isCancel(
|
|
394
|
-
await p.confirm({
|
|
395
|
-
message: 'File this gap report now?',
|
|
396
|
-
initialValue: false,
|
|
397
|
-
}),
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
if (!confirmFile) {
|
|
401
|
-
humanLog('Cancelled — nothing was filed.');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const s = p.spinner();
|
|
406
|
-
s.start('Filing gap report');
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const url = await createGapReport(previewArgs);
|
|
410
|
-
s.stop('Gap report filed');
|
|
411
|
-
humanLog(`✓ ${url}\n`);
|
|
412
|
-
} catch (err) {
|
|
413
|
-
s.stop('Failed to file gap report');
|
|
414
|
-
console.error(`Warning: Could not file gap report: ${err.message}`);
|
|
390
|
+
// Maintainer feedback note. If we couldn't swizzle cleanly, the team
|
|
391
|
+
// wants to know — point users at the issue tracker. Skipped when the
|
|
392
|
+
// owning package ships no issues URL.
|
|
393
|
+
if (feedback) {
|
|
394
|
+
humanLog(
|
|
395
|
+
'Customizing a component often signals a gap in the design system.',
|
|
396
|
+
);
|
|
397
|
+
humanLog('Let the maintainers know what you needed:');
|
|
398
|
+
if (feedback.ghCommand) {
|
|
399
|
+
humanLog(` ${feedback.ghCommand}`);
|
|
400
|
+
} else {
|
|
401
|
+
humanLog(` ${feedback.issuesUrl}`);
|
|
402
|
+
}
|
|
403
|
+
humanLog('');
|
|
415
404
|
}
|
|
416
405
|
});
|
|
417
406
|
}
|
|
@@ -69,7 +69,7 @@ describe('swizzle path safety', () => {
|
|
|
69
69
|
const {project, outside} = buildFakeRepo(tmpDir);
|
|
70
70
|
|
|
71
71
|
const result = runCli(
|
|
72
|
-
['swizzle', 'Button', '--output', '../outside-project'
|
|
72
|
+
['swizzle', 'Button', '--output', '../outside-project'],
|
|
73
73
|
project,
|
|
74
74
|
);
|
|
75
75
|
|
|
@@ -87,7 +87,7 @@ describe('swizzle path safety', () => {
|
|
|
87
87
|
const absTarget = path.join(tmpDir, 'absolute-target');
|
|
88
88
|
|
|
89
89
|
const result = runCli(
|
|
90
|
-
['swizzle', 'Button', '--output', absTarget
|
|
90
|
+
['swizzle', 'Button', '--output', absTarget],
|
|
91
91
|
project,
|
|
92
92
|
);
|
|
93
93
|
|
|
@@ -105,7 +105,7 @@ describe('swizzle path safety', () => {
|
|
|
105
105
|
|
|
106
106
|
// Use --json to force non-interactive mode.
|
|
107
107
|
const result = runCli(
|
|
108
|
-
['--json', 'swizzle', 'Button'
|
|
108
|
+
['--json', 'swizzle', 'Button'],
|
|
109
109
|
project,
|
|
110
110
|
);
|
|
111
111
|
|