bref-ui 0.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/cli/commands/add/approve-overwrites.ts +17 -0
- package/cli/commands/add/display-changes.ts +11 -0
- package/cli/commands/add/index.ts +30 -0
- package/cli/commands/add/review-changes.ts +18 -0
- package/cli/commands/add/utils.ts +11 -0
- package/cli/commands/execute.ts +39 -0
- package/cli/commands/index.ts +19 -0
- package/cli/commands/init.ts +17 -0
- package/cli/commands/parse.ts +24 -0
- package/cli/commands/types.ts +2 -0
- package/cli/config/read/index.ts +15 -0
- package/cli/config/read/normalize-config.ts +21 -0
- package/cli/config/read/read-config-file.ts +13 -0
- package/cli/config/resolve/index.ts +20 -0
- package/cli/config/resolve/resolve-alias-target.ts +17 -0
- package/cli/config/resolve/resolve-alias.ts +17 -0
- package/cli/config/types.ts +5 -0
- package/cli/index.ts +4 -0
- package/cli/installation/copy/index.ts +25 -0
- package/cli/installation/copy/validate-overwrite-approvals.ts +18 -0
- package/cli/installation/copy/write-prepared-files.ts +26 -0
- package/cli/installation/exports/collect-exports.ts +19 -0
- package/cli/installation/exports/create-additions.ts +26 -0
- package/cli/installation/exports/index.ts +27 -0
- package/cli/installation/exports/merge-content.ts +12 -0
- package/cli/installation/exports/read-declared-export-names.ts +24 -0
- package/cli/installation/exports/read-exported-names.ts +21 -0
- package/cli/installation/exports/read-index.ts +14 -0
- package/cli/installation/exports/read-reexported-names.ts +24 -0
- package/cli/installation/exports/types.ts +10 -0
- package/cli/installation/exports/utils.ts +54 -0
- package/cli/installation/index.ts +35 -0
- package/cli/installation/parser/index.ts +29 -0
- package/cli/installation/parser/types.ts +12 -0
- package/cli/installation/parser/utils.ts +45 -0
- package/cli/installation/prepare/collect-source-files.ts +15 -0
- package/cli/installation/prepare/index.ts +15 -0
- package/cli/installation/prepare/read-source-files.ts +21 -0
- package/cli/installation/prepare/rewrite-imports.ts +50 -0
- package/cli/installation/prepare/rewrite-source.ts +36 -0
- package/cli/installation/prepare/types.ts +7 -0
- package/cli/installation/preview/compare-prepared-files.ts +18 -0
- package/cli/installation/preview/index.ts +18 -0
- package/cli/installation/preview/read-destination-files.ts +25 -0
- package/cli/installation/preview/types.ts +9 -0
- package/cli/installation/types.ts +15 -0
- package/cli/pack.ts +6 -0
- package/cli/registry/load/create-registry.ts +25 -0
- package/cli/registry/load/discover-manifests.ts +9 -0
- package/cli/registry/load/index.ts +21 -0
- package/cli/registry/load/load-entry.ts +22 -0
- package/cli/registry/load/utils.ts +70 -0
- package/cli/registry/load/validate-dependencies.ts +33 -0
- package/cli/registry/plan/collect-entries.ts +28 -0
- package/cli/registry/plan/index.ts +14 -0
- package/cli/registry/types.ts +17 -0
- package/dist/button/button.svelte +126 -0
- package/dist/button/button.svelte.d.ts +4 -0
- package/dist/button/registry.json +6 -0
- package/dist/button/types.d.ts +32 -0
- package/dist/checkbox/checkbox.svelte +143 -0
- package/dist/checkbox/checkbox.svelte.d.ts +4 -0
- package/dist/checkbox/registry.json +6 -0
- package/dist/checkbox/types.d.ts +6 -0
- package/dist/combobox/combobox.svelte +156 -0
- package/dist/combobox/combobox.svelte.d.ts +4 -0
- package/dist/combobox/form.svelte +61 -0
- package/dist/combobox/form.svelte.d.ts +4 -0
- package/dist/combobox/input.svelte +132 -0
- package/dist/combobox/input.svelte.d.ts +4 -0
- package/dist/combobox/list.svelte +78 -0
- package/dist/combobox/list.svelte.d.ts +4 -0
- package/dist/combobox/option.svelte +77 -0
- package/dist/combobox/option.svelte.d.ts +4 -0
- package/dist/combobox/registry.json +6 -0
- package/dist/combobox/types.d.ts +61 -0
- package/dist/dialog/dialog.svelte +182 -0
- package/dist/dialog/dialog.svelte.d.ts +4 -0
- package/dist/dialog/footer.svelte +26 -0
- package/dist/dialog/footer.svelte.d.ts +7 -0
- package/dist/dialog/header.svelte +56 -0
- package/dist/dialog/header.svelte.d.ts +4 -0
- package/dist/dialog/registry.json +6 -0
- package/dist/dialog/types.d.ts +31 -0
- package/dist/icon/icon.svelte +108 -0
- package/dist/icon/icon.svelte.d.ts +4 -0
- package/dist/icon/names.d.ts +7 -0
- package/dist/icon/registry.json +6 -0
- package/dist/icon/types.d.ts +16 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.js +20 -2
- package/dist/pill/content.svelte +23 -0
- package/dist/pill/content.svelte.d.ts +4 -0
- package/dist/pill/on-delete.svelte +26 -0
- package/dist/pill/on-delete.svelte.d.ts +4 -0
- package/dist/pill/pill.svelte +200 -0
- package/dist/{base/pill → pill}/pill.svelte.d.ts +1 -1
- package/dist/pill/registry.json +6 -0
- package/dist/pill/types.d.ts +19 -0
- package/dist/pill-choice-group/pill-choice-group.svelte +131 -0
- package/dist/pill-choice-group/pill-choice-group.svelte.d.ts +4 -0
- package/dist/pill-choice-group/registry.json +6 -0
- package/dist/pill-choice-group/types.d.ts +13 -0
- package/dist/pill-group/container.svelte +40 -0
- package/dist/pill-group/container.svelte.d.ts +4 -0
- package/dist/pill-group/pill-group.svelte +27 -0
- package/dist/pill-group/pill-group.svelte.d.ts +4 -0
- package/dist/pill-group/registry.json +6 -0
- package/dist/pill-group/types.d.ts +13 -0
- package/dist/popover/popover.svelte +86 -0
- package/dist/popover/popover.svelte.d.ts +4 -0
- package/dist/popover/registry.json +6 -0
- package/dist/popover/types.d.ts +8 -0
- package/dist/progress/progress.svelte +256 -0
- package/dist/progress/progress.svelte.d.ts +4 -0
- package/dist/progress/registry.json +6 -0
- package/dist/progress/types.d.ts +11 -0
- package/dist/select/empty.svelte +25 -0
- package/dist/select/empty.svelte.d.ts +4 -0
- package/dist/select/list.svelte +27 -0
- package/dist/select/list.svelte.d.ts +4 -0
- package/dist/select/option.svelte +64 -0
- package/dist/select/option.svelte.d.ts +4 -0
- package/dist/select/registry.json +6 -0
- package/dist/select/select.svelte +70 -0
- package/dist/{base/select → select}/select.svelte.d.ts +2 -2
- package/dist/select/trigger.svelte +86 -0
- package/dist/select/trigger.svelte.d.ts +4 -0
- package/dist/select/types.d.ts +31 -0
- package/dist/select/types.js +1 -0
- package/dist/spinner/registry.json +6 -0
- package/dist/spinner/spinner.svelte +114 -0
- package/dist/spinner/spinner.svelte.d.ts +4 -0
- package/dist/spinner/types.d.ts +6 -0
- package/dist/spinner/types.js +1 -0
- package/dist/surface/registry.json +6 -0
- package/dist/surface/surface.svelte +96 -0
- package/dist/surface/surface.svelte.d.ts +4 -0
- package/dist/surface/types.d.ts +25 -0
- package/dist/surface/types.js +1 -0
- package/dist/switch/registry.json +6 -0
- package/dist/switch/switch.svelte +162 -0
- package/dist/switch/switch.svelte.d.ts +4 -0
- package/dist/switch/types.d.ts +8 -0
- package/dist/switch/types.js +1 -0
- package/dist/text-area/registry.json +8 -0
- package/dist/text-area/text-area.svelte +186 -0
- package/dist/text-area/text-area.svelte.d.ts +4 -0
- package/dist/text-area/types.d.ts +12 -0
- package/dist/text-area/types.js +1 -0
- package/dist/text-input/registry.json +8 -0
- package/dist/text-input/text-input.svelte +189 -0
- package/dist/text-input/text-input.svelte.d.ts +4 -0
- package/dist/text-input/types.d.ts +11 -0
- package/dist/text-input/types.js +1 -0
- package/dist/theme/cli-theme.svelte +16 -0
- package/dist/theme/cli-theme.svelte.d.ts +7 -0
- package/dist/theme/colors.svelte +102 -0
- package/dist/theme/colors.svelte.d.ts +4 -0
- package/dist/theme/fonts.svelte +10 -0
- package/dist/theme/fonts.svelte.d.ts +26 -0
- package/dist/theme/registry.json +13 -0
- package/dist/theme/styles.svelte +50 -0
- package/dist/theme/styles.svelte.d.ts +26 -0
- package/dist/theme/surface.d.ts +3 -0
- package/dist/theme/surface.js +10 -0
- package/dist/theme/surfaces.svelte +208 -0
- package/dist/theme/surfaces.svelte.d.ts +18 -0
- package/dist/theme/theme-mode-toggle.svelte +9 -0
- package/dist/theme/theme-mode-toggle.svelte.d.ts +4 -0
- package/dist/theme/theme.svelte +16 -0
- package/dist/theme/theme.svelte.d.ts +4 -0
- package/dist/theme/types.d.ts +17 -0
- package/dist/theme/types.js +1 -0
- package/dist/tree-view/registry.json +15 -0
- package/dist/tree-view/tree-node/chevron-button.svelte +56 -0
- package/dist/tree-view/tree-node/chevron-button.svelte.d.ts +4 -0
- package/dist/tree-view/tree-node/content.svelte +62 -0
- package/dist/tree-view/tree-node/content.svelte.d.ts +4 -0
- package/dist/tree-view/tree-node/delete-button.svelte +52 -0
- package/dist/tree-view/tree-node/delete-button.svelte.d.ts +4 -0
- package/dist/tree-view/tree-node/tree-node.svelte +111 -0
- package/dist/{base/tree-view → tree-view/tree-node}/tree-node.svelte.d.ts +2 -3
- package/dist/tree-view/tree-view-section.svelte +42 -0
- package/dist/tree-view/tree-view-section.svelte.d.ts +4 -0
- package/dist/tree-view/tree-view.svelte +65 -0
- package/dist/{base/tree-view → tree-view}/tree-view.svelte.d.ts +2 -2
- package/dist/tree-view/types.d.ts +31 -0
- package/dist/tree-view/types.js +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +1 -0
- package/package.json +39 -45
- package/readme.md +157 -0
- package/src/lib/button/button.svelte +126 -0
- package/src/lib/button/registry.json +6 -0
- package/src/lib/button/types.ts +42 -0
- package/src/lib/checkbox/checkbox.svelte +143 -0
- package/src/lib/checkbox/registry.json +6 -0
- package/src/lib/checkbox/types.ts +7 -0
- package/src/lib/combobox/combobox.svelte +156 -0
- package/src/lib/combobox/form.svelte +61 -0
- package/src/lib/combobox/input.svelte +132 -0
- package/src/lib/combobox/list.svelte +78 -0
- package/src/lib/combobox/option.svelte +77 -0
- package/src/lib/combobox/registry.json +6 -0
- package/src/lib/combobox/types.ts +89 -0
- package/src/lib/dialog/dialog.svelte +182 -0
- package/src/lib/dialog/footer.svelte +26 -0
- package/src/lib/dialog/header.svelte +56 -0
- package/src/lib/dialog/registry.json +6 -0
- package/src/lib/dialog/types.ts +35 -0
- package/src/lib/icon/icon.svelte +108 -0
- package/src/lib/icon/names.ts +3794 -0
- package/src/lib/icon/registry.json +6 -0
- package/src/lib/icon/types.ts +18 -0
- package/src/lib/index.ts +20 -0
- package/src/lib/pill/content.svelte +23 -0
- package/src/lib/pill/on-delete.svelte +26 -0
- package/src/lib/pill/pill.svelte +200 -0
- package/src/lib/pill/registry.json +6 -0
- package/src/lib/pill/types.ts +22 -0
- package/src/lib/pill-choice-group/pill-choice-group.svelte +131 -0
- package/src/lib/pill-choice-group/registry.json +6 -0
- package/src/lib/pill-choice-group/types.ts +14 -0
- package/src/lib/pill-group/container.svelte +40 -0
- package/src/lib/pill-group/pill-group.svelte +27 -0
- package/src/lib/pill-group/registry.json +6 -0
- package/src/lib/pill-group/types.ts +15 -0
- package/src/lib/popover/popover.svelte +86 -0
- package/src/lib/popover/registry.json +6 -0
- package/src/lib/popover/types.ts +9 -0
- package/src/lib/progress/progress.svelte +256 -0
- package/src/lib/progress/registry.json +6 -0
- package/src/lib/progress/types.ts +15 -0
- package/src/lib/select/empty.svelte +25 -0
- package/src/lib/select/list.svelte +27 -0
- package/src/lib/select/option.svelte +64 -0
- package/src/lib/select/registry.json +6 -0
- package/src/lib/select/select.svelte +70 -0
- package/src/lib/select/trigger.svelte +86 -0
- package/src/lib/select/types.ts +41 -0
- package/src/lib/spinner/registry.json +6 -0
- package/src/lib/spinner/spinner.svelte +114 -0
- package/src/lib/spinner/types.ts +7 -0
- package/src/lib/surface/registry.json +6 -0
- package/src/lib/surface/surface.svelte +96 -0
- package/src/lib/surface/types.ts +28 -0
- package/src/lib/switch/registry.json +6 -0
- package/src/lib/switch/switch.svelte +162 -0
- package/src/lib/switch/types.ts +12 -0
- package/src/lib/text-area/registry.json +8 -0
- package/src/lib/text-area/text-area.svelte +186 -0
- package/src/lib/text-area/types.ts +13 -0
- package/src/lib/text-input/registry.json +8 -0
- package/src/lib/text-input/text-input.svelte +189 -0
- package/src/lib/text-input/types.ts +12 -0
- package/src/lib/theme/cli-theme.svelte +16 -0
- package/src/lib/theme/colors.svelte +102 -0
- package/src/lib/theme/fonts.svelte +10 -0
- package/src/lib/theme/registry.json +13 -0
- package/src/lib/theme/styles.svelte +50 -0
- package/src/lib/theme/surface.ts +18 -0
- package/src/lib/theme/surfaces.svelte +208 -0
- package/src/lib/theme/theme-mode-toggle.svelte +9 -0
- package/src/lib/theme/theme.svelte +16 -0
- package/src/lib/theme/types.ts +18 -0
- package/src/lib/tree-view/registry.json +15 -0
- package/src/lib/tree-view/tree-node/chevron-button.svelte +56 -0
- package/src/lib/tree-view/tree-node/content.svelte +62 -0
- package/src/lib/tree-view/tree-node/delete-button.svelte +52 -0
- package/src/lib/tree-view/tree-node/tree-node.svelte +111 -0
- package/src/lib/tree-view/tree-view-section.svelte +42 -0
- package/src/lib/tree-view/tree-view.svelte +65 -0
- package/src/lib/tree-view/types.ts +39 -0
- package/src/lib/types.ts +45 -0
- package/README.md +0 -110
- package/dist/base/button/button.svelte +0 -260
- package/dist/base/button/button.svelte.d.ts +0 -6
- package/dist/base/button/icon-button.svelte +0 -226
- package/dist/base/button/icon-button.svelte.d.ts +0 -6
- package/dist/base/button/index.d.ts +0 -2
- package/dist/base/button/index.js +0 -2
- package/dist/base/button/types.d.ts +0 -17
- package/dist/base/icon/icon.svelte +0 -118
- package/dist/base/icon/icon.svelte.d.ts +0 -7
- package/dist/base/icon/index.d.ts +0 -2
- package/dist/base/icon/index.js +0 -2
- package/dist/base/icon/types.d.ts +0 -10
- package/dist/base/index.d.ts +0 -12
- package/dist/base/index.js +0 -12
- package/dist/base/loading/circular-loading.svelte +0 -95
- package/dist/base/loading/circular-loading.svelte.d.ts +0 -4
- package/dist/base/loading/index.d.ts +0 -4
- package/dist/base/loading/index.js +0 -4
- package/dist/base/loading/morphing-shapes-loading.svelte +0 -241
- package/dist/base/loading/morphing-shapes-loading.svelte.d.ts +0 -4
- package/dist/base/loading/pulsing-dots-loading.svelte +0 -115
- package/dist/base/loading/pulsing-dots-loading.svelte.d.ts +0 -4
- package/dist/base/loading/textual-loading.svelte +0 -179
- package/dist/base/loading/textual-loading.svelte.d.ts +0 -4
- package/dist/base/loading/types.d.ts +0 -16
- package/dist/base/pill/index.d.ts +0 -1
- package/dist/base/pill/index.js +0 -1
- package/dist/base/pill/pill.svelte +0 -146
- package/dist/base/pill/types.d.ts +0 -9
- package/dist/base/progress-bar/index.d.ts +0 -1
- package/dist/base/progress-bar/index.js +0 -1
- package/dist/base/progress-bar/progress-bar.svelte +0 -174
- package/dist/base/progress-bar/progress-bar.svelte.d.ts +0 -4
- package/dist/base/progress-bar/types.d.ts +0 -9
- package/dist/base/select/index.d.ts +0 -2
- package/dist/base/select/index.js +0 -2
- package/dist/base/select/select.svelte +0 -145
- package/dist/base/select/types.d.ts +0 -16
- package/dist/base/slider/index.d.ts +0 -1
- package/dist/base/slider/index.js +0 -1
- package/dist/base/slider/slider.svelte +0 -192
- package/dist/base/slider/slider.svelte.d.ts +0 -6
- package/dist/base/slider/types.d.ts +0 -12
- package/dist/base/text-input/area-text-input.svelte +0 -105
- package/dist/base/text-input/area-text-input.svelte.d.ts +0 -6
- package/dist/base/text-input/index.d.ts +0 -2
- package/dist/base/text-input/index.js +0 -2
- package/dist/base/text-input/text-input.svelte +0 -213
- package/dist/base/text-input/text-input.svelte.d.ts +0 -6
- package/dist/base/text-input/types.d.ts +0 -27
- package/dist/base/theme/color-tokens.d.ts +0 -36
- package/dist/base/theme/color-tokens.js +0 -105
- package/dist/base/theme/index.d.ts +0 -3
- package/dist/base/theme/index.js +0 -3
- package/dist/base/theme/mode.d.ts +0 -3
- package/dist/base/theme/mode.js +0 -14
- package/dist/base/theme/theme.svelte +0 -87
- package/dist/base/theme/theme.svelte.d.ts +0 -3
- package/dist/base/theme/types.d.ts +0 -27
- package/dist/base/theme/types.js +0 -20
- package/dist/base/toggle/index.d.ts +0 -1
- package/dist/base/toggle/index.js +0 -1
- package/dist/base/toggle/toggle.svelte +0 -109
- package/dist/base/toggle/toggle.svelte.d.ts +0 -4
- package/dist/base/toggle/types.d.ts +0 -7
- package/dist/base/tree-view/index.d.ts +0 -3
- package/dist/base/tree-view/index.js +0 -3
- package/dist/base/tree-view/tree-node.svelte +0 -132
- package/dist/base/tree-view/tree-view.svelte +0 -56
- package/dist/base/tree-view/types.d.ts +0 -23
- package/dist/base/types.d.ts +0 -4
- /package/dist/{base/button → button}/types.js +0 -0
- /package/dist/{base/icon → checkbox}/types.js +0 -0
- /package/dist/{base/loading → combobox}/types.js +0 -0
- /package/dist/{base/pill → dialog}/types.js +0 -0
- /package/dist/{base/progress-bar/types.js → icon/names.js} +0 -0
- /package/dist/{base/select → icon}/types.js +0 -0
- /package/dist/{base/toggle → pill}/types.js +0 -0
- /package/dist/{base/slider → pill-choice-group}/types.js +0 -0
- /package/dist/{base/text-input → pill-group}/types.js +0 -0
- /package/dist/{base/tree-view → popover}/types.js +0 -0
- /package/dist/{base → progress}/types.js +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bref contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PreviewChange } from '../../installation/types.js';
|
|
2
|
+
import { confirm } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Request an individual approval for each conflicting file in preview order.
|
|
6
|
+
* @param changes Full preview; additions and identical files need no overwrite approval.
|
|
7
|
+
* @returns Approved relative paths, or null on the first declined or closed prompt.
|
|
8
|
+
* No further prompts are shown after cancellation and no files are written.
|
|
9
|
+
* @throws When reading a confirmation fails.
|
|
10
|
+
*/
|
|
11
|
+
export const approveOverwrites = (changes: PreviewChange[]): string[] | null =>
|
|
12
|
+
changes
|
|
13
|
+
.filter(({ status }) => status === 'conflict')
|
|
14
|
+
.reduce<string[] | null>((approved, { path }) => {
|
|
15
|
+
if (approved === null) return null;
|
|
16
|
+
return confirm(`Overwrite ${path}?`) ? [...approved, path] : null;
|
|
17
|
+
}, []);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PreviewChange } from '../../installation/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Print every preview status and its path relative to the configured UI directory.
|
|
5
|
+
* @param changes Ordered additions, identical files and conflicts to display.
|
|
6
|
+
* @throws When writing the terminal output fails.
|
|
7
|
+
*/
|
|
8
|
+
export const displayChanges = (changes: PreviewChange[]): void => {
|
|
9
|
+
console.log('Planned UI changes:');
|
|
10
|
+
console.log(changes.map(({ status, path }) => ` ${status}: ${path}`).join('\n'));
|
|
11
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readConfig } from '../../config/read/index.js';
|
|
2
|
+
import { resolveTargetDir } from '../../config/resolve/index.js';
|
|
3
|
+
import { installComponent } from '../../installation/index.js';
|
|
4
|
+
import { loadRegistry } from '../../registry/load/index.js';
|
|
5
|
+
import { planDependencies } from '../../registry/plan/index.js';
|
|
6
|
+
import { reviewChanges } from './review-changes.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Install a component and its dependencies after reviewing changes in the terminal.
|
|
10
|
+
* @param componentId Registry ID of the requested component.
|
|
11
|
+
* @param projectRoot Consumer project directory containing bref.config.json.
|
|
12
|
+
* @param sourceRoot Directory containing canonical component folders and manifests.
|
|
13
|
+
* @param aliases Project alias mappings; supplied values override the default $lib mapping.
|
|
14
|
+
* @returns installed after accepted changes are copied, or cancelled when any
|
|
15
|
+
* confirmation is declined or input closes. Cancellation writes no files.
|
|
16
|
+
* @throws When configuration, alias resolution, registry loading, planning or
|
|
17
|
+
* installation fails. Copy failures may leave partial writes and are not rolled back.
|
|
18
|
+
*/
|
|
19
|
+
export const addComponent = async (
|
|
20
|
+
componentId: string,
|
|
21
|
+
projectRoot: string,
|
|
22
|
+
sourceRoot: string,
|
|
23
|
+
aliases: Record<string, string> = {}
|
|
24
|
+
): Promise<'installed' | 'cancelled'> => {
|
|
25
|
+
const config = await readConfig(projectRoot);
|
|
26
|
+
const destination = resolveTargetDir(projectRoot, config.ui, aliases);
|
|
27
|
+
const registry = await loadRegistry(sourceRoot);
|
|
28
|
+
const entries = planDependencies(componentId, registry);
|
|
29
|
+
return installComponent(entries, destination, reviewChanges);
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PreviewChange } from '../../installation/types.js';
|
|
2
|
+
import { approveOverwrites } from './approve-overwrites.js';
|
|
3
|
+
import { displayChanges } from './display-changes.js';
|
|
4
|
+
import { confirm } from './utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Show the full preview, collect overwrite approvals and confirm installation.
|
|
8
|
+
* @param changes Prepared source and index.ts changes relative to the UI directory.
|
|
9
|
+
* @returns Approved conflicting paths, or null if any confirmation is declined
|
|
10
|
+
* or input closes. An empty list accepts changes that need no overwrites.
|
|
11
|
+
* @throws When terminal output or prompting fails; destination files remain untouched.
|
|
12
|
+
*/
|
|
13
|
+
export const reviewChanges = async (changes: PreviewChange[]): Promise<string[] | null> => {
|
|
14
|
+
displayChanges(changes);
|
|
15
|
+
const overwrites = approveOverwrites(changes);
|
|
16
|
+
if (overwrites === null) return null;
|
|
17
|
+
return confirm('Install these changes?') ? overwrites : null;
|
|
18
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a confirmation using Bun's native terminal prompt, defaulting to refusal.
|
|
3
|
+
* @param message Question to display before the yes/no hint.
|
|
4
|
+
* @returns true only for y or yes, ignoring case and surrounding whitespace;
|
|
5
|
+
* empty input, other answers and closed input return false.
|
|
6
|
+
* @throws When Bun cannot read terminal input.
|
|
7
|
+
*/
|
|
8
|
+
export const confirm = (message: string): boolean => {
|
|
9
|
+
const answer = prompt(`${message} [y/N] `);
|
|
10
|
+
return answer !== null && /^(y|yes)$/i.test(answer.trim());
|
|
11
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { addComponent } from './add/index.js';
|
|
2
|
+
import { initConfig } from './init.js';
|
|
3
|
+
import type { Command } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run a validated request and print its outcome using the registry shipped beside the executable.
|
|
7
|
+
* @param command Parsed help, init or add request.
|
|
8
|
+
* @param projectRoot Consumer project directory, supplied by the executable's cwd.
|
|
9
|
+
* @throws When initialization or installation fails; the executable reports the error.
|
|
10
|
+
*/
|
|
11
|
+
export const execute = async (command: Command, projectRoot: string): Promise<void> => {
|
|
12
|
+
if (command.name === 'help') {
|
|
13
|
+
console.log(
|
|
14
|
+
'Usage: bref-ui <command>\n\n' +
|
|
15
|
+
'Commands:\n' +
|
|
16
|
+
' init Create bref.config.json with the default UI alias\n' +
|
|
17
|
+
' add <component> Preview and install a component and its dependencies\n\n' +
|
|
18
|
+
'Options:\n' +
|
|
19
|
+
' -h, --help Show this help\n\n' +
|
|
20
|
+
'Commands use the current directory as the consumer project.'
|
|
21
|
+
);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (command.name === 'init') {
|
|
25
|
+
const result = await initConfig(projectRoot);
|
|
26
|
+
console.log(
|
|
27
|
+
result === 'created' ? 'Created bref.config.json.' : 'bref.config.json already exists.'
|
|
28
|
+
);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sourceRoot = Bun.fileURLToPath(new URL('../../src/lib/', import.meta.url));
|
|
33
|
+
const result = await addComponent(command.componentId, projectRoot, sourceRoot);
|
|
34
|
+
console.log(
|
|
35
|
+
result === 'installed'
|
|
36
|
+
? `Installed ${command.componentId} and its dependencies.`
|
|
37
|
+
: 'Installation cancelled.'
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execute } from './execute.js';
|
|
2
|
+
import { parse } from './parse.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse executable arguments, run the requested command and report failures.
|
|
6
|
+
* @param args Terminal arguments after the executable filename.
|
|
7
|
+
* @param projectRoot Consumer project directory supplied by the executable's cwd.
|
|
8
|
+
* @returns After help, command completion or cancellation; these exit successfully.
|
|
9
|
+
* Invalid arguments and command failures are reported to stderr and set exit code 1.
|
|
10
|
+
*/
|
|
11
|
+
export const runCommand = async (args: string[], projectRoot: string): Promise<void> => {
|
|
12
|
+
try {
|
|
13
|
+
const command = parse(args);
|
|
14
|
+
await execute(command, projectRoot);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create bref.config.json with the default UI alias when the file is absent.
|
|
3
|
+
* @param projectRoot Existing consumer project directory.
|
|
4
|
+
* @returns created after writing the default configuration, or exists when a
|
|
5
|
+
* configuration file is already present. Existing contents are not read or changed.
|
|
6
|
+
* @throws When checking or writing the configuration fails, including a missing
|
|
7
|
+
* project directory. The existence check and write are not atomic; concurrent
|
|
8
|
+
* initialization of the same project is unsupported.
|
|
9
|
+
*/
|
|
10
|
+
export const initConfig = async (projectRoot: string): Promise<'created' | 'exists'> => {
|
|
11
|
+
const config = Bun.file(`${projectRoot}/bref.config.json`);
|
|
12
|
+
if (await config.exists()) return 'exists';
|
|
13
|
+
|
|
14
|
+
const content = `${JSON.stringify({ ui: '$lib/ui' }, null, '\t')}\n`;
|
|
15
|
+
await Bun.write(config, content, { createPath: false });
|
|
16
|
+
return 'created';
|
|
17
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Command } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate supported command arguments without reading or writing project files.
|
|
5
|
+
* @param args Arguments after the executable filename.
|
|
6
|
+
* @returns A help, init or add request. No arguments also requests help.
|
|
7
|
+
* @throws When a command is unknown, arguments are missing or extra, or an
|
|
8
|
+
* unsupported option is supplied.
|
|
9
|
+
*/
|
|
10
|
+
export const parse = (args: string[]): Command => {
|
|
11
|
+
const [name, componentId] = args;
|
|
12
|
+
if (
|
|
13
|
+
args.length === 0 ||
|
|
14
|
+
(args.length === 1 && ['help', '--help', '-h'].includes(name)) ||
|
|
15
|
+
(args.length === 2 && ['init', 'add'].includes(name) && ['--help', '-h'].includes(componentId))
|
|
16
|
+
)
|
|
17
|
+
return { name: 'help' };
|
|
18
|
+
|
|
19
|
+
if (name === 'init' && args.length === 1) return { name: 'init' };
|
|
20
|
+
if (name === 'add' && args.length === 2 && componentId.length > 0 && !componentId.startsWith('-'))
|
|
21
|
+
return { name: 'add', componentId };
|
|
22
|
+
|
|
23
|
+
throw new Error('Expected init or add <component>. Use --help for usage.');
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { normalizeConfig } from './normalize-config.js';
|
|
2
|
+
import { readConfigFile } from './read-config-file.js';
|
|
3
|
+
import type { BrefConfig } from '../types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read bref.config.json, defaulting the UI alias to $lib/ui when omitted.
|
|
7
|
+
* @param projectRoot Directory containing the consumer project's configuration.
|
|
8
|
+
* @returns The supported configuration values with defaults applied.
|
|
9
|
+
* @throws When the file cannot be read or parsed, the root is not an object,
|
|
10
|
+
* or the supplied UI alias is not a string.
|
|
11
|
+
*/
|
|
12
|
+
export const readConfig = async (projectRoot: string): Promise<BrefConfig> => {
|
|
13
|
+
const { configPath, config } = await readConfigFile(projectRoot);
|
|
14
|
+
return normalizeConfig(configPath, config);
|
|
15
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BrefConfig } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate the supported configuration field and apply its default.
|
|
5
|
+
* @param configPath Path used to report configuration validation errors.
|
|
6
|
+
* @param config Parsed configuration JSON value.
|
|
7
|
+
* @returns The supported configuration values with defaults applied.
|
|
8
|
+
* @throws When the root is not an object or the supplied UI alias is not a string.
|
|
9
|
+
*/
|
|
10
|
+
export const normalizeConfig = (configPath: string, config: unknown): BrefConfig => {
|
|
11
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config))
|
|
12
|
+
throw new Error(`${configPath}: expected a JSON object.`);
|
|
13
|
+
|
|
14
|
+
if ('ui' in config) {
|
|
15
|
+
if (typeof config.ui !== 'string') throw new Error(`${configPath}: "ui" must be a string.`);
|
|
16
|
+
|
|
17
|
+
return { ui: config.ui };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { ui: '$lib/ui' };
|
|
21
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read and parse the consumer project's bref.config.json file.
|
|
3
|
+
* @param projectRoot Directory containing the consumer project's configuration.
|
|
4
|
+
* @returns The configuration file path and parsed JSON value.
|
|
5
|
+
* @throws When the file cannot be read or contains invalid JSON.
|
|
6
|
+
*/
|
|
7
|
+
export const readConfigFile = async (
|
|
8
|
+
projectRoot: string
|
|
9
|
+
): Promise<{ configPath: string; config: unknown }> => {
|
|
10
|
+
const configPath = `${projectRoot}/bref.config.json`;
|
|
11
|
+
const config: unknown = await Bun.file(configPath).json();
|
|
12
|
+
return { configPath, config };
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolveAlias } from './resolve-alias.js';
|
|
2
|
+
import { resolveAliasTarget } from './resolve-alias-target.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a UI alias using the longest matching alias prefix.
|
|
6
|
+
* Supplied mappings override the default $lib mapping to src/lib.
|
|
7
|
+
* @param projectRoot Base directory for relative alias targets.
|
|
8
|
+
* @param ui Configured UI alias, optionally followed by a subdirectory.
|
|
9
|
+
* @param aliases Project alias mappings to relative or absolute directories.
|
|
10
|
+
* @returns The normalized absolute destination path, without accessing the filesystem.
|
|
11
|
+
* @throws When no alias matches the configured UI location.
|
|
12
|
+
*/
|
|
13
|
+
export const resolveTargetDir = (
|
|
14
|
+
projectRoot: string,
|
|
15
|
+
ui: string,
|
|
16
|
+
aliases: Record<string, string> = {}
|
|
17
|
+
): string => {
|
|
18
|
+
const [alias, target] = resolveAlias(ui, aliases);
|
|
19
|
+
return resolveAliasTarget(projectRoot, ui, alias, target);
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an alias match to a normalized absolute filesystem path.
|
|
3
|
+
* @param projectRoot Base directory for relative alias targets.
|
|
4
|
+
* @param ui Configured UI alias, optionally followed by a subdirectory.
|
|
5
|
+
* @param alias Matching alias prefix.
|
|
6
|
+
* @param target Directory mapped from the alias.
|
|
7
|
+
* @returns The normalized absolute destination path, without accessing the filesystem.
|
|
8
|
+
*/
|
|
9
|
+
export const resolveAliasTarget = (
|
|
10
|
+
projectRoot: string,
|
|
11
|
+
ui: string,
|
|
12
|
+
alias: string,
|
|
13
|
+
target: string
|
|
14
|
+
): string => {
|
|
15
|
+
const directory = target.startsWith('/') ? target : `${projectRoot}/${target}`;
|
|
16
|
+
return Bun.fileURLToPath(Bun.pathToFileURL(`${directory}${ui.slice(alias.length)}`));
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the longest configured alias prefix matching a UI path.
|
|
3
|
+
* @param ui Configured UI alias, optionally followed by a subdirectory.
|
|
4
|
+
* @param aliases Project alias mappings that override the default $lib mapping.
|
|
5
|
+
* @returns The matching alias and its destination mapping.
|
|
6
|
+
* @throws When no alias matches the configured UI location.
|
|
7
|
+
*/
|
|
8
|
+
export const resolveAlias = (ui: string, aliases: Record<string, string>): [string, string] => {
|
|
9
|
+
const mappings = Object.entries({ $lib: 'src/lib', ...aliases }).sort(
|
|
10
|
+
([left], [right]) => right.length - left.length
|
|
11
|
+
);
|
|
12
|
+
const match = mappings.find(([alias]) => ui === alias || ui.startsWith(`${alias}/`));
|
|
13
|
+
|
|
14
|
+
if (!match) throw new Error(`Cannot resolve UI alias "${ui}".`);
|
|
15
|
+
|
|
16
|
+
return match;
|
|
17
|
+
};
|
package/cli/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { previewChanges } from '../preview/index.js';
|
|
2
|
+
import type { PreparedFile } from '../types.js';
|
|
3
|
+
import { validateOverwriteApprovals } from './validate-overwrite-approvals.js';
|
|
4
|
+
import { writePreparedFiles } from './write-prepared-files.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Copy prepared files after checking every conflict against explicit approvals.
|
|
8
|
+
* Call only after the user accepts the preview; cancellation must skip this call.
|
|
9
|
+
* Checks and writes are not atomic; avoid concurrent edits to the destination.
|
|
10
|
+
* @param files Prepared files with paths relative to the destination directory.
|
|
11
|
+
* @param destination Consumer project's configured UI directory.
|
|
12
|
+
* @param overwrites Relative file paths explicitly approved for overwriting.
|
|
13
|
+
* @returns Completion after all required writes; identical files are skipped.
|
|
14
|
+
* @throws Before writing when any conflict lacks approval, or when previewing fails.
|
|
15
|
+
* @throws If an addition appears before its write, or a write fails; some files may be copied.
|
|
16
|
+
*/
|
|
17
|
+
export const copyFiles = async (
|
|
18
|
+
files: PreparedFile[],
|
|
19
|
+
destination: string,
|
|
20
|
+
overwrites: string[] = []
|
|
21
|
+
): Promise<void> => {
|
|
22
|
+
const changes = await previewChanges(files, destination);
|
|
23
|
+
validateOverwriteApprovals(changes, overwrites);
|
|
24
|
+
await writePreparedFiles(files, changes, destination);
|
|
25
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PreviewChange } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reject the first conflicting path that has no explicit overwrite approval.
|
|
5
|
+
* @param changes Preview results in prepared-file order.
|
|
6
|
+
* @param overwrites Relative paths explicitly approved for overwriting.
|
|
7
|
+
* @returns Nothing when every conflict is approved.
|
|
8
|
+
* @throws When a conflict lacks overwrite approval.
|
|
9
|
+
*/
|
|
10
|
+
export const validateOverwriteApprovals = (
|
|
11
|
+
changes: PreviewChange[],
|
|
12
|
+
overwrites: string[]
|
|
13
|
+
): void => {
|
|
14
|
+
const approved = new Set(overwrites);
|
|
15
|
+
const conflict = changes.find(({ path, status }) => status === 'conflict' && !approved.has(path));
|
|
16
|
+
|
|
17
|
+
if (conflict) throw new Error(`Overwrite approval required for "${conflict.path}".`);
|
|
18
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PreparedFile, PreviewChange } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Write additions and approved conflicts, skipping identical destination files.
|
|
5
|
+
* @param files Prepared files in the same order as changes.
|
|
6
|
+
* @param changes Preview results for these files.
|
|
7
|
+
* @param destination Consumer project's configured UI directory.
|
|
8
|
+
* @returns Completion after all required writes.
|
|
9
|
+
* @throws If an addition appears before its write or a write fails; some files may be copied.
|
|
10
|
+
*/
|
|
11
|
+
export const writePreparedFiles = async (
|
|
12
|
+
files: PreparedFile[],
|
|
13
|
+
changes: PreviewChange[],
|
|
14
|
+
destination: string
|
|
15
|
+
): Promise<void> =>
|
|
16
|
+
Promise.all(
|
|
17
|
+
files.map(async ({ path, content }, index) => {
|
|
18
|
+
const { status } = changes[index];
|
|
19
|
+
if (status === 'unchanged') return;
|
|
20
|
+
|
|
21
|
+
const target = `${destination}/${path}`;
|
|
22
|
+
if (status === 'add' && (await Bun.file(target).exists()))
|
|
23
|
+
throw new Error(`Destination appeared after preview for "${path}".`);
|
|
24
|
+
await Bun.write(target, content, { createPath: true });
|
|
25
|
+
})
|
|
26
|
+
).then(() => undefined);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseScript } from '../parser/index.js';
|
|
2
|
+
import { readExportedNames } from './read-exported-names.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collect public names and matching direct default re-export module paths.
|
|
6
|
+
* Null marks a different export form or a duplicate name that cannot be reused.
|
|
7
|
+
* @param content Existing index.ts content, parsed without changing its formatting.
|
|
8
|
+
* @returns Export names mapped to their reusable component module or null.
|
|
9
|
+
* @throws Invalid script syntax or an unresolved wildcard re-export.
|
|
10
|
+
*/
|
|
11
|
+
export const collectExports = (content: string): Map<string, string | null> => {
|
|
12
|
+
const { statements } = parseScript(content, 'index.ts');
|
|
13
|
+
return statements.reduce((exports, statement) => {
|
|
14
|
+
readExportedNames(statement).forEach(({ name, componentPath }) => {
|
|
15
|
+
exports.set(name, exports.has(name) ? null : componentPath);
|
|
16
|
+
});
|
|
17
|
+
return exports;
|
|
18
|
+
}, new Map<string, string | null>());
|
|
19
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RegistryEntry } from '../../registry/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build missing default re-exports using each component's source folder name.
|
|
5
|
+
* @param entries Validated dependency-first registry entries.
|
|
6
|
+
* @param existing Export names already present; updated as additions are planned.
|
|
7
|
+
* @returns Missing export statements in entry and manifest order.
|
|
8
|
+
* @throws When a requested name already refers to another export.
|
|
9
|
+
*/
|
|
10
|
+
export const createAdditions = (
|
|
11
|
+
entries: RegistryEntry[],
|
|
12
|
+
existing: Map<string, string | null>
|
|
13
|
+
): string[] => {
|
|
14
|
+
return entries.flatMap((entry) => {
|
|
15
|
+
const directory = entry.directory.split('/').filter(Boolean).at(-1);
|
|
16
|
+
return Object.entries(entry.exports).flatMap(([name, file]) => {
|
|
17
|
+
const module = `./${directory}/${file}`;
|
|
18
|
+
if (existing.has(name)) {
|
|
19
|
+
if (existing.get(name) === module) return [];
|
|
20
|
+
throw new Error(`index.ts: export "${name}" conflicts with "${module}".`);
|
|
21
|
+
}
|
|
22
|
+
existing.set(name, module);
|
|
23
|
+
return [`export { default as ${name} } from ${JSON.stringify(module)};`];
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RegistryEntry } from '../../registry/types.js';
|
|
2
|
+
import type { PreparedFile } from '../types.js';
|
|
3
|
+
import { readIndex } from './read-index.js';
|
|
4
|
+
import { collectExports } from './collect-exports.js';
|
|
5
|
+
import { createAdditions } from './create-additions.js';
|
|
6
|
+
import { mergeContent } from './merge-content.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prepare missing component re-exports while preserving the existing index.ts text.
|
|
10
|
+
* Matching direct default re-exports are skipped; other uses of their names conflict.
|
|
11
|
+
* @param entries Validated, dependency-first entries returned by planDependencies.
|
|
12
|
+
* @param destination Absolute consumer UI directory containing index.ts.
|
|
13
|
+
* @returns The prepared index.ts for the existing preview and copy stages; no writes occur.
|
|
14
|
+
* @throws When reading or parsing fails, an export name conflicts, or a wildcard
|
|
15
|
+
* export prevents determining which names are already exported.
|
|
16
|
+
*/
|
|
17
|
+
export const prepareExports = async (
|
|
18
|
+
entries: RegistryEntry[],
|
|
19
|
+
destination: string
|
|
20
|
+
): Promise<PreparedFile> => {
|
|
21
|
+
const content = await readIndex(destination);
|
|
22
|
+
if (entries.length === 0) return { path: 'index.ts', content };
|
|
23
|
+
|
|
24
|
+
const existing = collectExports(content);
|
|
25
|
+
const additions = createAdditions(entries, existing);
|
|
26
|
+
return { path: 'index.ts', content: mergeContent(content, additions) };
|
|
27
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append new statements while preserving existing text and its newline style.
|
|
3
|
+
* @param content Original destination index.ts content.
|
|
4
|
+
* @param additions Missing export statements to append.
|
|
5
|
+
* @returns Unchanged content when no exports are missing, otherwise the merged text.
|
|
6
|
+
*/
|
|
7
|
+
export const mergeContent = (content: string, additions: string[]): string => {
|
|
8
|
+
if (additions.length === 0) return content;
|
|
9
|
+
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
10
|
+
const separator = content.length > 0 && !content.endsWith('\n') ? newline : '';
|
|
11
|
+
return content + separator + additions.join(newline) + newline;
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ScriptStatement } from '../parser/types.js';
|
|
2
|
+
import type { ExistingExport } from './types.js';
|
|
3
|
+
import { readBindingNames, readName } from './utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reserve names defined by exported variables, functions, classes and type declarations.
|
|
7
|
+
* These declarations have no reusable component path; default declarations reserve default.
|
|
8
|
+
* @param statement Parsed top-level declaration or another statement to ignore.
|
|
9
|
+
* @returns Declared public names, including destructured variables, or an empty list.
|
|
10
|
+
*/
|
|
11
|
+
export const readDeclaredExportNames = (statement: ScriptStatement): ExistingExport[] => {
|
|
12
|
+
if (statement.type === 'ExportDefaultDeclaration')
|
|
13
|
+
return [{ name: 'default', componentPath: null }];
|
|
14
|
+
if (statement.type !== 'ExportNamedDeclaration' || !statement.declaration) return [];
|
|
15
|
+
|
|
16
|
+
const declaration = statement.declaration;
|
|
17
|
+
if (declaration.type === 'VariableDeclaration')
|
|
18
|
+
return declaration.declarations.flatMap(({ id }) =>
|
|
19
|
+
readBindingNames(id).map((name) => ({ name, componentPath: null }))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const name = 'id' in declaration ? readName(declaration.id) : null;
|
|
23
|
+
return name === null ? [] : [{ name, componentPath: null }];
|
|
24
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ScriptStatement } from '../parser/types.js';
|
|
2
|
+
import { readReExportedNames } from './read-reexported-names.js';
|
|
3
|
+
import { readDeclaredExportNames } from './read-declared-export-names.js';
|
|
4
|
+
import type { ExistingExport } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Identify the public names introduced by one top-level index.ts statement.
|
|
8
|
+
* A matching component path lets installation reuse an existing default re-export;
|
|
9
|
+
* a null path reserves the name and prevents installation from replacing it.
|
|
10
|
+
* @param statement Parsed statement, such as a re-export or an exported declaration.
|
|
11
|
+
* @returns Named records for its exports, or an empty list when it exports no names.
|
|
12
|
+
* @throws For export *, whose names cannot be known without inspecting another file.
|
|
13
|
+
*/
|
|
14
|
+
export const readExportedNames = (statement: ScriptStatement): ExistingExport[] => {
|
|
15
|
+
if (
|
|
16
|
+
statement.type === 'ExportAllDeclaration' ||
|
|
17
|
+
(statement.type === 'ExportNamedDeclaration' && !statement.declaration)
|
|
18
|
+
)
|
|
19
|
+
return readReExportedNames(statement);
|
|
20
|
+
return readDeclaredExportNames(statement);
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the destination index, treating only a missing file as empty content.
|
|
3
|
+
* @param destination Consumer UI directory.
|
|
4
|
+
* @returns Existing content or an empty string for ENOENT.
|
|
5
|
+
* @throws Any other filesystem read error.
|
|
6
|
+
*/
|
|
7
|
+
export const readIndex = async (destination: string): Promise<string> => {
|
|
8
|
+
try {
|
|
9
|
+
return await Bun.file(`${destination}/index.ts`).text();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ExportAllDeclaration, ExportNamedDeclaration } from 'estree';
|
|
2
|
+
import type { ExistingExport } from './types.js';
|
|
3
|
+
import { readDefaultReExportPath, readName } from './utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read public aliases from export lists and namespace re-exports.
|
|
7
|
+
* Direct runtime default re-exports record their module path; other names reserve a null path.
|
|
8
|
+
* @param declaration Parsed export list, namespace re-export or wildcard re-export.
|
|
9
|
+
* @returns Export records in source order.
|
|
10
|
+
* @throws For export * without a namespace, because its names are unknown here.
|
|
11
|
+
*/
|
|
12
|
+
export const readReExportedNames = (
|
|
13
|
+
declaration: ExportNamedDeclaration | ExportAllDeclaration
|
|
14
|
+
): ExistingExport[] => {
|
|
15
|
+
if (declaration.type === 'ExportAllDeclaration') {
|
|
16
|
+
const name = readName(declaration.exported);
|
|
17
|
+
if (name === null) throw new Error('index.ts: wildcard exports must be made explicit first.');
|
|
18
|
+
return [{ name, componentPath: null }];
|
|
19
|
+
}
|
|
20
|
+
return declaration.specifiers.map((specifier) => ({
|
|
21
|
+
name: readName(specifier.exported)!,
|
|
22
|
+
componentPath: readDefaultReExportPath(declaration, specifier)
|
|
23
|
+
}));
|
|
24
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One name already exported by the consumer's index.ts. */
|
|
2
|
+
export interface ExistingExport {
|
|
3
|
+
/** Public name used by consumers, such as Button. */
|
|
4
|
+
name: string;
|
|
5
|
+
/**
|
|
6
|
+
* Module path for a direct value re-export of default, such as ./button/button.svelte.
|
|
7
|
+
* Null means the name is occupied by another export and cannot be reused.
|
|
8
|
+
*/
|
|
9
|
+
componentPath: string | null;
|
|
10
|
+
}
|