uifork 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -2
- package/cli.js +450 -0
- package/dist/components/BranchedComponent.d.ts +10 -0
- package/dist/components/BranchedComponent.d.ts.map +1 -0
- package/dist/components/ComponentSelector.d.ts +21 -0
- package/dist/components/ComponentSelector.d.ts.map +1 -0
- package/dist/components/MenuItem.d.ts +13 -0
- package/dist/components/MenuItem.d.ts.map +1 -0
- package/dist/components/SettingsView.d.ts +12 -0
- package/dist/components/SettingsView.d.ts.map +1 -0
- package/dist/components/Tooltip.d.ts +9 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/UIFork.d.ts +21 -0
- package/dist/components/UIFork.d.ts.map +1 -0
- package/dist/components/VersionActionMenu.d.ts +17 -0
- package/dist/components/VersionActionMenu.d.ts.map +1 -0
- package/dist/components/VersionItem.d.ts +23 -0
- package/dist/components/VersionItem.d.ts.map +1 -0
- package/dist/components/VersionNameEditor.d.ts +10 -0
- package/dist/components/VersionNameEditor.d.ts.map +1 -0
- package/dist/components/VersionsList.d.ts +28 -0
- package/dist/components/VersionsList.d.ts.map +1 -0
- package/dist/components/icons/BranchIcon.d.ts +6 -0
- package/dist/components/icons/BranchIcon.d.ts.map +1 -0
- package/dist/components/icons/CancelIcon.d.ts +6 -0
- package/dist/components/icons/CancelIcon.d.ts.map +1 -0
- package/dist/components/icons/CheckmarkIcon.d.ts +6 -0
- package/dist/components/icons/CheckmarkIcon.d.ts.map +1 -0
- package/dist/components/icons/ChevronDownIcon.d.ts +6 -0
- package/dist/components/icons/ChevronDownIcon.d.ts.map +1 -0
- package/dist/components/icons/ChevronRightIcon.d.ts +6 -0
- package/dist/components/icons/ChevronRightIcon.d.ts.map +1 -0
- package/dist/components/icons/CopyIcon.d.ts +6 -0
- package/dist/components/icons/CopyIcon.d.ts.map +1 -0
- package/dist/components/icons/DeleteIcon.d.ts +6 -0
- package/dist/components/icons/DeleteIcon.d.ts.map +1 -0
- package/dist/components/icons/GearIcon.d.ts +6 -0
- package/dist/components/icons/GearIcon.d.ts.map +1 -0
- package/dist/components/icons/GitForkIcon.d.ts +6 -0
- package/dist/components/icons/GitForkIcon.d.ts.map +1 -0
- package/dist/components/icons/MoreOptionsIcon.d.ts +6 -0
- package/dist/components/icons/MoreOptionsIcon.d.ts.map +1 -0
- package/dist/components/icons/OpenInEditorIcon.d.ts +6 -0
- package/dist/components/icons/OpenInEditorIcon.d.ts.map +1 -0
- package/dist/components/icons/PlusIcon.d.ts +6 -0
- package/dist/components/icons/PlusIcon.d.ts.map +1 -0
- package/dist/components/icons/PromoteIcon.d.ts +6 -0
- package/dist/components/icons/PromoteIcon.d.ts.map +1 -0
- package/dist/components/icons/RenameIcon.d.ts +6 -0
- package/dist/components/icons/RenameIcon.d.ts.map +1 -0
- package/dist/hooks/useClickOutside.d.ts +17 -0
- package/dist/hooks/useClickOutside.d.ts.map +1 -0
- package/dist/hooks/useComponentDiscovery.d.ts +18 -0
- package/dist/hooks/useComponentDiscovery.d.ts.map +1 -0
- package/dist/hooks/useKeyboardShortcuts.d.ts +28 -0
- package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/hooks/useLocalStorage.d.ts +5 -0
- package/dist/hooks/useLocalStorage.d.ts.map +1 -0
- package/dist/hooks/usePopoverPosition.d.ts +13 -0
- package/dist/hooks/usePopoverPosition.d.ts.map +1 -0
- package/dist/hooks/useVersionManagement.d.ts +18 -0
- package/dist/hooks/useVersionManagement.d.ts.map +1 -0
- package/dist/hooks/useWebSocketConnection.d.ts +25 -0
- package/dist/hooks/useWebSocketConnection.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.mjs +1689 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/componentRegistry.d.ts +26 -0
- package/dist/utils/componentRegistry.d.ts.map +1 -0
- package/dist/utils/tooltipManager.d.ts +4 -0
- package/dist/utils/tooltipManager.d.ts.map +1 -0
- package/lib/cli-helpers.js +77 -0
- package/lib/init.js +132 -0
- package/lib/promote.js +285 -0
- package/lib/watch.js +1313 -0
- package/package.json +73 -4
package/README.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
# uifork
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CLI tool for managing UI component versions and switching between them.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Global Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g uifork
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Local Development Setup
|
|
14
|
+
|
|
15
|
+
1. Clone this repository
|
|
16
|
+
2. Install dependencies:
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
3. Make the CLI script executable:
|
|
21
|
+
```bash
|
|
22
|
+
chmod +x cli.js
|
|
23
|
+
```
|
|
24
|
+
4. Link the package globally for development:
|
|
25
|
+
```bash
|
|
26
|
+
npm link
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Initialize a UI Switcher
|
|
32
|
+
|
|
33
|
+
Convert a single component file into a versioned UI switcher:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uifork init frontend/src/SomeDropdownComponent.tsx
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This will:
|
|
40
|
+
|
|
41
|
+
- Rename the original file to `ComponentName.v1.tsx` (or `.ts`)
|
|
42
|
+
- Generate a `ComponentName.versions.ts` file
|
|
43
|
+
- Create a `ComponentName.UISwitcher.tsx` component
|
|
44
|
+
- Create a `ComponentName.tsx` wrapper file that exports the component
|
|
45
|
+
|
|
46
|
+
### Watch for Changes
|
|
47
|
+
|
|
48
|
+
Watch a component directory for version file changes:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uifork watch SomeDropdownComponent
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The watch command will:
|
|
55
|
+
|
|
56
|
+
- Automatically find files matching `*.versions.ts` pattern
|
|
57
|
+
- Watch for new version files (ComponentName.v*.tsx or ComponentName.v*.ts)
|
|
58
|
+
- Auto-update imports in `versions.ts`
|
|
59
|
+
- Handle file renames bidirectionally
|
|
60
|
+
|
|
61
|
+
### Promote a Version
|
|
62
|
+
|
|
63
|
+
Promote a specific version to be the main component and remove all versioning scaffolding:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uifork promote SomeDropdownComponent v2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This will:
|
|
70
|
+
|
|
71
|
+
- Replace `ComponentName.tsx` with the content from `ComponentName.v2.tsx`
|
|
72
|
+
- Delete all version files (`ComponentName.v*.tsx`)
|
|
73
|
+
- Delete `ComponentName.versions.ts`
|
|
74
|
+
- Delete `ComponentName.UISwitcher.tsx`
|
|
75
|
+
- Effectively "undo" the versioning system, leaving just the promoted version as the main component
|
|
76
|
+
|
|
77
|
+
## Component Structure
|
|
78
|
+
|
|
79
|
+
After running `uifork init`, your component files will be created in the same directory:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
src/components/
|
|
83
|
+
├── ComponentName.tsx # Main wrapper export (use this for imports)
|
|
84
|
+
├── ComponentName.UISwitcher.tsx # Version switcher component
|
|
85
|
+
├── ComponentName.versions.ts # Version configuration
|
|
86
|
+
├── ComponentName.v1.tsx # Your original component (version 1)
|
|
87
|
+
├── ComponentName.v2.tsx # Additional versions as needed
|
|
88
|
+
└── ComponentName.v1_1.tsx # Sub-versions (e.g., v1_1, v2_1)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
All files are created in the same directory as your original component, using a flat naming convention.
|
|
92
|
+
|
|
93
|
+
## Adding New Versions
|
|
94
|
+
|
|
95
|
+
1. Create new version files: `ComponentName.v2.tsx`, `ComponentName.v1_1.tsx`, etc.
|
|
96
|
+
2. Run `uifork watch ComponentName.versions.ts` (or just `uifork watch ComponentName`) to automatically update `versions.ts`
|
|
97
|
+
3. Or manually add imports and version entries to `ComponentName.versions.ts`
|
|
98
|
+
|
|
99
|
+
## UI Switcher Controls
|
|
100
|
+
|
|
101
|
+
- **Cmd + Arrow Up/Down**: Cycle through versions
|
|
102
|
+
- **Bottom-right selector**: Click to choose specific version
|
|
103
|
+
- Selections are saved to localStorage per component ID
|
|
104
|
+
|
|
105
|
+
## Commands
|
|
106
|
+
|
|
107
|
+
- `uifork init <component-path>` - Initialize a UI switcher from a component file
|
|
108
|
+
- `uifork watch <component-name>` - Watch for version changes in a component
|
|
109
|
+
- `uifork promote <component-path> <version-id>` - Promote a version to be the main component and remove versioning scaffolding
|
|
110
|
+
- `uifork --help` - Show help information
|
|
111
|
+
- `uifork --version` - Show version number
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Initialize a new UI switcher
|
|
117
|
+
uifork init src/components/Button.tsx
|
|
118
|
+
# Creates: Button.tsx, Button.v1.tsx, Button.versions.ts, Button.UISwitcher.tsx
|
|
119
|
+
|
|
120
|
+
# Watch for changes (by component name or path)
|
|
121
|
+
uifork watch Button
|
|
122
|
+
# or
|
|
123
|
+
uifork watch src/components/Button.versions.ts
|
|
124
|
+
|
|
125
|
+
# Promote a version to be the main component (removes all versioning)
|
|
126
|
+
uifork promote Button v2
|
|
127
|
+
# This replaces Button.tsx with Button.v2.tsx and deletes all version files
|
|
128
|
+
|
|
129
|
+
# Import and use the component
|
|
130
|
+
import Button from './Button' # Uses Button.tsx wrapper
|
|
131
|
+
```
|
package/cli.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { UISwitcherScaffold } = require("./lib/init");
|
|
6
|
+
const { VersionSync } = require("./lib/watch");
|
|
7
|
+
const { VersionPromoter } = require("./lib/promote");
|
|
8
|
+
const { findComponentManager } = require("./lib/cli-helpers");
|
|
9
|
+
|
|
10
|
+
function showHelp() {
|
|
11
|
+
console.log(`
|
|
12
|
+
uifork - A CLI tool for managing UI component versions
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
uifork init <component-path> Initialize a new branched component
|
|
16
|
+
uifork watch [directory] Watch for version changes (defaults to current directory)
|
|
17
|
+
uifork new <component-path> [version-id] Create a new version
|
|
18
|
+
uifork duplicate <component-path> <version-id> [target-version] Duplicate/fork a version
|
|
19
|
+
uifork rename <component-path> <version-id> <new-version-id> Rename a version
|
|
20
|
+
uifork delete <component-path> <version-id> Delete a version
|
|
21
|
+
uifork promote <component-path> <version-id> Promote a version to be the main component
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
uifork init frontend/src/SomeDropdownComponent.tsx
|
|
25
|
+
uifork watch
|
|
26
|
+
uifork watch ./src
|
|
27
|
+
uifork new SomeDropdownComponent
|
|
28
|
+
uifork new SomeDropdownComponent v3
|
|
29
|
+
uifork duplicate SomeDropdownComponent v1 v2
|
|
30
|
+
uifork rename SomeDropdownComponent v1 v2
|
|
31
|
+
uifork delete SomeDropdownComponent v2
|
|
32
|
+
uifork promote SomeDropdownComponent v2
|
|
33
|
+
|
|
34
|
+
Commands:
|
|
35
|
+
init Convert a single component file into a versioned branched component
|
|
36
|
+
watch Start the watch server and discover all versioned components
|
|
37
|
+
new Create a new version (auto-increments if version-id not provided)
|
|
38
|
+
duplicate Duplicate/fork a version (auto-increments target if not provided)
|
|
39
|
+
rename Rename a version
|
|
40
|
+
delete Delete a version
|
|
41
|
+
promote Promote a version to be the main component and remove versioning scaffolding
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
-h, --help Show this help message
|
|
45
|
+
-v, --version Show version number
|
|
46
|
+
--W Don't start watching after init (init command only)
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function showVersion() {
|
|
51
|
+
const packageJson = require("./package.json");
|
|
52
|
+
console.log(`uifork v${packageJson.version}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Parse command line arguments
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
const command = args[0];
|
|
58
|
+
const argument = args[1];
|
|
59
|
+
|
|
60
|
+
// Handle help and version flags
|
|
61
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
62
|
+
showHelp();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
67
|
+
showVersion();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle commands
|
|
72
|
+
switch (command) {
|
|
73
|
+
case "init":
|
|
74
|
+
if (!argument) {
|
|
75
|
+
console.error("Error: Component path is required for init command");
|
|
76
|
+
console.error("Usage: uifork init <component-path>");
|
|
77
|
+
console.error(
|
|
78
|
+
"Example: uifork init frontend/src/modules/chart-builder/ZoomDatePickerDropdown.tsx",
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const shouldWatch = !args.includes("--W");
|
|
85
|
+
const scaffolder = new UISwitcherScaffold(argument, shouldWatch);
|
|
86
|
+
scaffolder.scaffold();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Error during scaffolding: ${error.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case "watch":
|
|
94
|
+
// Watch can be called without arguments - defaults to current directory
|
|
95
|
+
try {
|
|
96
|
+
new VersionSync(argument || process.cwd());
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`Error during watching: ${error.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case "promote":
|
|
104
|
+
if (!argument) {
|
|
105
|
+
console.error(
|
|
106
|
+
"Error: Component path and version ID are required for promote command",
|
|
107
|
+
);
|
|
108
|
+
console.error("Usage: uifork promote <component-path> <version-id>");
|
|
109
|
+
console.error("Example: uifork promote SomeDropdownComponent v2");
|
|
110
|
+
console.error(
|
|
111
|
+
"Example: uifork promote frontend/src/SomeDropdownComponent.tsx v1_2",
|
|
112
|
+
);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const versionId = args[2];
|
|
117
|
+
if (!versionId) {
|
|
118
|
+
console.error("Error: Version ID is required for promote command");
|
|
119
|
+
console.error("Usage: uifork promote <component-path> <version-id>");
|
|
120
|
+
console.error("Example: uifork promote SomeDropdownComponent v2");
|
|
121
|
+
console.error(
|
|
122
|
+
"Example: uifork promote frontend/src/SomeDropdownComponent.tsx v1_2",
|
|
123
|
+
);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const promoter = new VersionPromoter(argument, versionId);
|
|
129
|
+
promoter.promote();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Error during promotion: ${error.message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case "new":
|
|
137
|
+
case "create":
|
|
138
|
+
if (!argument) {
|
|
139
|
+
console.error("Error: Component path is required");
|
|
140
|
+
console.error("Usage: uifork new <component-path> [version-id]");
|
|
141
|
+
console.error("Example: uifork new SomeDropdownComponent");
|
|
142
|
+
console.error("Example: uifork new SomeDropdownComponent v3");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const manager = findComponentManager(argument);
|
|
148
|
+
const versionId = args[2]; // Optional version ID
|
|
149
|
+
|
|
150
|
+
let targetVersion;
|
|
151
|
+
if (versionId) {
|
|
152
|
+
if (!manager.validateVersionKey(versionId)) {
|
|
153
|
+
throw new Error(`Invalid version format: ${versionId}`);
|
|
154
|
+
}
|
|
155
|
+
targetVersion = versionId;
|
|
156
|
+
} else {
|
|
157
|
+
const nextVersionNum = manager.getNextVersionNumber();
|
|
158
|
+
targetVersion = manager.versionNumberToKey(nextVersionNum);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const targetFilePath = manager.getVersionFilePath(targetVersion);
|
|
162
|
+
if (fs.existsSync(targetFilePath)) {
|
|
163
|
+
throw new Error(`Version already exists: ${targetVersion}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const extension = manager.getMostCommonExtension();
|
|
167
|
+
const fileVersion = manager.versionKeyToFileVersion(targetVersion);
|
|
168
|
+
const finalFilePath = path.join(
|
|
169
|
+
manager.watchDir,
|
|
170
|
+
`${manager.componentName}.v${fileVersion}${extension}`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const displayVersion = targetVersion
|
|
174
|
+
.replace(/^v/, "")
|
|
175
|
+
.replace(/_/g, ".")
|
|
176
|
+
.toUpperCase();
|
|
177
|
+
|
|
178
|
+
const importSuffix = manager.versionToImportSuffix(fileVersion);
|
|
179
|
+
const componentName = `${manager.componentName}${importSuffix}`;
|
|
180
|
+
|
|
181
|
+
let templateContent;
|
|
182
|
+
if (extension === ".tsx" || extension === ".jsx") {
|
|
183
|
+
templateContent = `import React from 'react';
|
|
184
|
+
|
|
185
|
+
export default function ${componentName}() {
|
|
186
|
+
return (
|
|
187
|
+
<div>
|
|
188
|
+
${displayVersion}
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
} else {
|
|
194
|
+
templateContent = `import React from 'react';
|
|
195
|
+
|
|
196
|
+
export default function ${componentName}() {
|
|
197
|
+
return React.createElement('div', null, '${displayVersion}');
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(finalFilePath, templateContent, "utf8");
|
|
203
|
+
manager.generateVersionsFile();
|
|
204
|
+
|
|
205
|
+
console.log(`✅ Created new version: ${targetVersion}`);
|
|
206
|
+
console.log(` Component: ${manager.componentName}`);
|
|
207
|
+
console.log(` File: ${path.basename(finalFilePath)}`);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error(`Error creating version: ${error.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case "duplicate":
|
|
215
|
+
case "fork":
|
|
216
|
+
if (!argument) {
|
|
217
|
+
console.error("Error: Component path and version ID are required");
|
|
218
|
+
console.error(
|
|
219
|
+
"Usage: uifork duplicate <component-path> <version-id> [target-version]",
|
|
220
|
+
);
|
|
221
|
+
console.error("Example: uifork duplicate SomeDropdownComponent v1");
|
|
222
|
+
console.error("Example: uifork duplicate SomeDropdownComponent v1 v2");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const sourceVersion = args[2];
|
|
227
|
+
if (!sourceVersion) {
|
|
228
|
+
console.error("Error: Source version ID is required");
|
|
229
|
+
console.error(
|
|
230
|
+
"Usage: uifork duplicate <component-path> <version-id> [target-version]",
|
|
231
|
+
);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const manager = findComponentManager(argument);
|
|
237
|
+
|
|
238
|
+
if (!manager.validateVersionKey(sourceVersion)) {
|
|
239
|
+
throw new Error(`Invalid source version format: ${sourceVersion}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const sourceFilePath = manager.getVersionFilePath(sourceVersion);
|
|
243
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
244
|
+
throw new Error(`Source version file not found: ${sourceVersion}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let targetVersion;
|
|
248
|
+
const newVersion = args[3]; // Optional target version
|
|
249
|
+
if (newVersion) {
|
|
250
|
+
if (!manager.validateVersionKey(newVersion)) {
|
|
251
|
+
throw new Error(`Invalid target version format: ${newVersion}`);
|
|
252
|
+
}
|
|
253
|
+
targetVersion = newVersion;
|
|
254
|
+
} else {
|
|
255
|
+
const nextVersionNum = manager.getNextVersionNumber();
|
|
256
|
+
targetVersion = manager.versionNumberToKey(nextVersionNum);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const targetFilePath = manager.getVersionFilePath(targetVersion);
|
|
260
|
+
if (fs.existsSync(targetFilePath)) {
|
|
261
|
+
throw new Error(`Target version already exists: ${targetVersion}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const sourceContent = fs.readFileSync(sourceFilePath, "utf8");
|
|
265
|
+
const extension = path.extname(sourceFilePath);
|
|
266
|
+
const fileVersion = manager.versionKeyToFileVersion(targetVersion);
|
|
267
|
+
const finalTargetPath = path.join(
|
|
268
|
+
manager.watchDir,
|
|
269
|
+
`${manager.componentName}.v${fileVersion}${extension}`,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
fs.writeFileSync(finalTargetPath, sourceContent, "utf8");
|
|
273
|
+
manager.generateVersionsFile();
|
|
274
|
+
|
|
275
|
+
console.log(`✅ Duplicated version: ${sourceVersion} → ${targetVersion}`);
|
|
276
|
+
console.log(` Component: ${manager.componentName}`);
|
|
277
|
+
console.log(` Source: ${path.basename(sourceFilePath)}`);
|
|
278
|
+
console.log(` Target: ${path.basename(finalTargetPath)}`);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error(`Error duplicating version: ${error.message}`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case "rename":
|
|
286
|
+
if (!argument) {
|
|
287
|
+
console.error(
|
|
288
|
+
"Error: Component path, version ID, and new version ID are required",
|
|
289
|
+
);
|
|
290
|
+
console.error(
|
|
291
|
+
"Usage: uifork rename <component-path> <version-id> <new-version-id>",
|
|
292
|
+
);
|
|
293
|
+
console.error("Example: uifork rename SomeDropdownComponent v1 v2");
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const oldVersion = args[2];
|
|
298
|
+
const newVersionId = args[3];
|
|
299
|
+
|
|
300
|
+
if (!oldVersion || !newVersionId) {
|
|
301
|
+
console.error("Error: Both version ID and new version ID are required");
|
|
302
|
+
console.error(
|
|
303
|
+
"Usage: uifork rename <component-path> <version-id> <new-version-id>",
|
|
304
|
+
);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const manager = findComponentManager(argument);
|
|
310
|
+
|
|
311
|
+
if (!manager.validateVersionKey(oldVersion)) {
|
|
312
|
+
throw new Error(`Invalid source version format: ${oldVersion}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!manager.validateVersionKey(newVersionId)) {
|
|
316
|
+
throw new Error(`Invalid target version format: ${newVersionId}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (oldVersion === newVersionId) {
|
|
320
|
+
throw new Error("Source and target versions are the same");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const fileVersion = manager.versionKeyToFileVersion(oldVersion);
|
|
324
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
325
|
+
let sourceFilePath = null;
|
|
326
|
+
|
|
327
|
+
for (const ext of extensions) {
|
|
328
|
+
const candidatePath = path.join(
|
|
329
|
+
manager.watchDir,
|
|
330
|
+
`${manager.componentName}.v${fileVersion}${ext}`,
|
|
331
|
+
);
|
|
332
|
+
if (fs.existsSync(candidatePath)) {
|
|
333
|
+
sourceFilePath = candidatePath;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!sourceFilePath) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Source version file not found: ${oldVersion}. Checked: ${extensions
|
|
341
|
+
.map((ext) => `${manager.componentName}.v${fileVersion}${ext}`)
|
|
342
|
+
.join(", ")}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const targetFileVersion = manager.versionKeyToFileVersion(newVersionId);
|
|
347
|
+
let targetFilePath = null;
|
|
348
|
+
for (const ext of extensions) {
|
|
349
|
+
const candidatePath = path.join(
|
|
350
|
+
manager.watchDir,
|
|
351
|
+
`${manager.componentName}.v${targetFileVersion}${ext}`,
|
|
352
|
+
);
|
|
353
|
+
if (fs.existsSync(candidatePath)) {
|
|
354
|
+
targetFilePath = candidatePath;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (targetFilePath) {
|
|
360
|
+
throw new Error(`Target version already exists: ${newVersionId}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const extension = path.extname(sourceFilePath);
|
|
364
|
+
const finalTargetPath = path.join(
|
|
365
|
+
manager.watchDir,
|
|
366
|
+
`${manager.componentName}.v${targetFileVersion}${extension}`,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const sourceContent = fs.readFileSync(sourceFilePath, "utf8");
|
|
370
|
+
|
|
371
|
+
const importSuffix = manager.versionToImportSuffix(fileVersion);
|
|
372
|
+
const newImportSuffix = manager.versionToImportSuffix(targetFileVersion);
|
|
373
|
+
const oldComponentName = `${manager.componentName}${importSuffix}`;
|
|
374
|
+
const newComponentName = `${manager.componentName}${newImportSuffix}`;
|
|
375
|
+
|
|
376
|
+
const updatedContent = sourceContent.replace(
|
|
377
|
+
new RegExp(oldComponentName, "g"),
|
|
378
|
+
newComponentName,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
fs.writeFileSync(finalTargetPath, updatedContent, "utf8");
|
|
382
|
+
fs.unlinkSync(sourceFilePath);
|
|
383
|
+
manager.generateVersionsFile();
|
|
384
|
+
|
|
385
|
+
console.log(`✅ Renamed version: ${oldVersion} → ${newVersionId}`);
|
|
386
|
+
console.log(` Component: ${manager.componentName}`);
|
|
387
|
+
console.log(` Source: ${path.basename(sourceFilePath)}`);
|
|
388
|
+
console.log(` Target: ${path.basename(finalTargetPath)}`);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error(`Error renaming version: ${error.message}`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
|
|
395
|
+
case "delete":
|
|
396
|
+
if (!argument) {
|
|
397
|
+
console.error("Error: Component path and version ID are required");
|
|
398
|
+
console.error("Usage: uifork delete <component-path> <version-id>");
|
|
399
|
+
console.error("Example: uifork delete SomeDropdownComponent v2");
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const deleteVersionId = args[2];
|
|
404
|
+
if (!deleteVersionId) {
|
|
405
|
+
console.error("Error: Version ID is required");
|
|
406
|
+
console.error("Usage: uifork delete <component-path> <version-id>");
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
const manager = findComponentManager(argument);
|
|
412
|
+
|
|
413
|
+
if (!manager.validateVersionKey(deleteVersionId)) {
|
|
414
|
+
throw new Error(`Invalid version format: ${deleteVersionId}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const filePath = manager.getVersionFilePath(deleteVersionId);
|
|
418
|
+
if (!fs.existsSync(filePath)) {
|
|
419
|
+
throw new Error(`Version file not found: ${deleteVersionId}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const versionFiles = manager.getVersionFiles();
|
|
423
|
+
if (versionFiles.length === 1) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
"Cannot delete the last remaining version. At least one version must exist.",
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
fs.unlinkSync(filePath);
|
|
430
|
+
manager.generateVersionsFile();
|
|
431
|
+
|
|
432
|
+
console.log(`✅ Deleted version: ${deleteVersionId}`);
|
|
433
|
+
console.log(` Component: ${manager.componentName}`);
|
|
434
|
+
console.log(` File: ${path.basename(filePath)}`);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
console.error(`Error deleting version: ${error.message}`);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
default:
|
|
442
|
+
if (!command) {
|
|
443
|
+
showHelp();
|
|
444
|
+
process.exit(0);
|
|
445
|
+
} else {
|
|
446
|
+
console.error(`Unknown command: ${command}`);
|
|
447
|
+
console.error('Run "uifork --help" for available commands');
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BranchedComponentProps } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* A component that renders a specific version based on localStorage state.
|
|
4
|
+
* Used to wrap components that have multiple versions managed by uifork.
|
|
5
|
+
*
|
|
6
|
+
* The UIFork component controls which version is active by writing to localStorage.
|
|
7
|
+
* BranchedComponent reads from localStorage and renders the appropriate version.
|
|
8
|
+
*/
|
|
9
|
+
export declare function BranchedComponent<T extends Record<string, unknown>>({ id, versions, props, defaultVersion, }: BranchedComponentProps<T>): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
//# sourceMappingURL=BranchedComponent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BranchedComponent.d.ts","sourceRoot":"","sources":["../../src/components/BranchedComponent.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnE,EAAE,EACF,QAAQ,EACR,KAAK,EACL,cAAc,GACf,EAAE,sBAAsB,CAAC,CAAC,CAAC,kDAoF3B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { ComponentInfo } from '../types';
|
|
3
|
+
interface ComponentSelectorProps {
|
|
4
|
+
selectedComponent: string;
|
|
5
|
+
onToggle: () => void;
|
|
6
|
+
onSettingsClick: (e: React.MouseEvent) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function ComponentSelector({ selectedComponent, onToggle, onSettingsClick, }: ComponentSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function ComponentSelectorDropdown({ mountedComponents, selectedComponent, isOpen, position, onSelect, componentSelectorRef, }: {
|
|
10
|
+
mountedComponents: ComponentInfo[];
|
|
11
|
+
selectedComponent: string;
|
|
12
|
+
isOpen: boolean;
|
|
13
|
+
position: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
};
|
|
17
|
+
onSelect: (componentName: string) => void;
|
|
18
|
+
componentSelectorRef: React.RefObject<HTMLDivElement>;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=ComponentSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComponentSelector.d.ts","sourceRoot":"","sources":["../../src/components/ComponentSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,UAAU,sBAAsB;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;CAChD;AAKD,wBAAgB,iBAAiB,CAAC,EAChC,iBAAiB,EACjB,QAAQ,EACR,eAAe,GAChB,EAAE,sBAAsB,2CAiCxB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,iBAAiB,EACjB,iBAAiB,EACjB,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,oBAAoB,GACrB,EAAE;IACD,iBAAiB,EAAE,aAAa,EAAE,CAAC;IACnC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,QAAQ,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,oBAAoB,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;CACvD,kDA6CA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
interface MenuItemProps {
|
|
3
|
+
icon: React.ComponentType<{
|
|
4
|
+
className?: string;
|
|
5
|
+
}>;
|
|
6
|
+
label: string;
|
|
7
|
+
onClick: (e: React.MouseEvent) => void;
|
|
8
|
+
variant?: "default" | "delete";
|
|
9
|
+
stopPropagation?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function MenuItem({ icon: Icon, label, onClick, variant, stopPropagation, }: MenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=MenuItem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuItem.d.ts","sourceRoot":"","sources":["../../src/components/MenuItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,aAAa;IACrB,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EAAE,IAAI,EACV,KAAK,EACL,OAAO,EACP,OAAmB,EACnB,eAAuB,GACxB,EAAE,aAAa,2CAmBf"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface SettingsViewProps {
|
|
2
|
+
onBack: () => void;
|
|
3
|
+
theme: "light" | "dark" | "system";
|
|
4
|
+
setTheme: (theme: "light" | "dark" | "system") => void;
|
|
5
|
+
position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
6
|
+
setPosition: (position: "top-left" | "top-right" | "bottom-left" | "bottom-right") => void;
|
|
7
|
+
codeEditor: "vscode" | "cursor";
|
|
8
|
+
setCodeEditor: (editor: "vscode" | "cursor") => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function SettingsView({ onBack, theme, setTheme, position, setPosition, codeEditor, setCodeEditor, }: SettingsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=SettingsView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SettingsView.d.ts","sourceRoot":"","sources":["../../src/components/SettingsView.tsx"],"names":[],"mappings":"AAIA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;IACvD,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;IACpE,WAAW,EAAE,CACX,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,KAChE,IAAI,CAAC;IACV,UAAU,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAChC,aAAa,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;CACtD;AAED,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EACV,aAAa,GACd,EAAE,iBAAiB,2CA+DnB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
interface TooltipProps {
|
|
3
|
+
label: string;
|
|
4
|
+
children: React.ReactElement;
|
|
5
|
+
placement?: "top" | "bottom" | "left" | "right";
|
|
6
|
+
}
|
|
7
|
+
export declare function Tooltip({ label, children, placement }: TooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=Tooltip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../src/components/Tooltip.tsx"],"names":[],"mappings":"AACA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAS3D,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC;IAC7B,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;CACjD;AAED,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAiB,EAAE,EAAE,YAAY,2CAuL3E"}
|