@wyxos/vibe 1.6.12 → 1.6.14

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.
@@ -1,208 +1,236 @@
1
- <template>
2
- <main class="flex flex-col h-screen overflow-hidden bg-slate-50 relative pt-[112px]">
3
- <!-- Fixed Sub-Header -->
4
- <header class="fixed top-[53px] left-0 right-0 z-20 w-full bg-white/80 backdrop-blur-md border-b border-slate-200 shadow-sm transition-all duration-300">
5
- <div class="max-w-7xl mx-auto px-4 h-14 flex items-center justify-end">
6
- <!-- Right: Controls -->
7
- <div class="flex items-center gap-3">
8
- <!-- Status Pill -->
9
- <div v-if="masonry" class="hidden md:flex items-center gap-2 text-xs font-medium text-slate-600 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100">
10
- <span class="flex items-center gap-1.5">
11
- <span class="w-1.5 h-1.5 rounded-full shadow-sm" :class="masonry.isLoading ? 'bg-amber-400 animate-pulse' : 'bg-emerald-400'"></span>
12
- {{ masonry.isLoading ? 'Loading...' : 'Ready' }}
13
- </span>
14
- <span class="w-px h-3 bg-slate-200"></span>
15
- <span>{{ items.length }} items</span>
16
- </div>
17
-
18
- <div class="h-8 w-px bg-slate-100 mx-1 hidden md:block"></div>
19
-
20
- <!-- Action Buttons -->
21
- <div class="flex items-center gap-1">
22
- <button
23
- @click="showLayoutControls = !showLayoutControls"
24
- class="w-9 h-9 flex items-center justify-center text-slate-500 hover:text-blue-600 hover:bg-blue-50 rounded-xl transition-all duration-200"
25
- :class="{ 'text-blue-600 bg-blue-50 ring-2 ring-blue-100': showLayoutControls }"
26
- title="Layout Settings"
27
- >
28
- <i class="fas fa-sliders text-sm"></i>
29
- </button>
30
-
31
- <a
32
- href="https://github.com/wyxos/vibe"
33
- target="_blank"
34
- class="w-9 h-9 flex items-center justify-center text-slate-400 hover:text-slate-900 hover:bg-slate-50 rounded-xl transition-all duration-200"
35
- title="View on GitHub"
36
- >
37
- <i class="fab fa-github text-lg"></i>
38
- </a>
39
- </div>
40
- </div>
41
- </div>
42
-
43
- <!-- Layout Controls Panel -->
44
- <transition
45
- enter-active-class="transition duration-200 ease-out"
46
- enter-from-class="transform -translate-y-2 opacity-0"
47
- enter-to-class="transform translate-y-0 opacity-100"
48
- leave-active-class="transition duration-150 ease-in"
49
- leave-from-class="transform translate-y-0 opacity-100"
50
- leave-to-class="transform -translate-y-2 opacity-0"
51
- >
52
- <div v-if="showLayoutControls" class="absolute top-full left-4 right-4 md:left-auto md:right-4 mt-2 md:w-full md:max-w-lg bg-white/90 backdrop-blur-md border border-slate-200 shadow-xl rounded-xl p-4 md:p-6 pointer-events-auto z-30">
53
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
54
- <!-- Column Settings -->
55
- <div>
56
- <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Column Configuration</h3>
57
- <div class="grid grid-cols-3 sm:grid-cols-6 gap-2 sm:gap-3">
58
- <div v-for="(val, key) in layoutParams.sizes" :key="key" class="flex flex-col gap-1.5">
59
- <label class="text-[10px] font-bold text-slate-500 uppercase text-center">{{ key }}</label>
60
- <input
61
- v-model.number="layoutParams.sizes[key]"
62
- type="number"
63
- min="1"
64
- class="w-full px-1 py-2 bg-slate-50 border border-slate-200 rounded-lg text-center text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
65
- />
66
- </div>
67
- </div>
68
- </div>
69
-
70
- <!-- Spacing Settings -->
71
- <div>
72
- <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Spacing</h3>
73
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
74
- <div class="flex flex-col gap-1.5">
75
- <label class="text-[10px] font-bold text-slate-500 uppercase">Header Offset</label>
76
- <div class="relative">
77
- <input
78
- v-model.number="layoutParams.header"
79
- type="number"
80
- min="0"
81
- class="w-full pl-3 pr-8 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
82
- />
83
- <span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">px</span>
84
- </div>
85
- </div>
86
- <div class="flex flex-col gap-1.5">
87
- <label class="text-[10px] font-bold text-slate-500 uppercase">Footer Offset</label>
88
- <div class="relative">
89
- <input
90
- v-model.number="layoutParams.footer"
91
- type="number"
92
- min="0"
93
- class="w-full pl-3 pr-8 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
94
- />
95
- <span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">px</span>
96
- </div>
97
- </div>
98
- </div>
99
- </div>
100
-
101
- <!-- Device Simulation -->
102
- <div class="md:col-span-2 border-t border-slate-100 pt-6 mt-2">
103
- <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Device Simulation</h3>
104
- <div class="flex flex-wrap gap-2">
105
- <button
106
- v-for="mode in ['auto', 'phone', 'tablet', 'desktop']"
107
- :key="mode"
108
- @click="deviceMode = mode as any"
109
- class="px-4 py-2 rounded-lg text-sm font-medium transition-all capitalize"
110
- :class="deviceMode === mode ? 'bg-blue-600 text-white shadow-md shadow-blue-200' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'"
111
- >
112
- <i class="fas mr-2" :class="{
113
- 'fa-desktop': mode === 'desktop' || mode === 'auto',
114
- 'fa-mobile-alt': mode === 'phone',
115
- 'fa-tablet-alt': mode === 'tablet'
116
- }"></i>
117
- {{ mode }}
118
- </button>
119
- </div>
120
- </div>
121
- </div>
122
- </div>
123
- </transition>
124
- </header>
125
-
126
- <!-- Main Content -->
127
- <div class="flex flex-1 overflow-hidden relative p-5 transition-all duration-300 ease-in-out" :class="{'bg-slate-200/50': deviceMode !== 'auto'}">
128
- <div :style="containerStyle" class="transition-all duration-500 ease-in-out bg-slate-50 shadow-sm relative">
129
- <masonry v-model:items="items" :get-next-page="getPage" :load-at-page="1" :layout="layout" ref="masonry">
130
- <!-- MasonryItem is used automatically, but you can customize it -->
131
- </masonry>
132
- </div>
133
- </div>
134
- </main>
135
- </template>
136
-
137
- <script setup lang="ts">
138
- import Masonry from "../Masonry.vue";
139
- import { ref, reactive, computed } from "vue";
140
- import fixture from "../pages.json";
141
- import type { MasonryItem, GetPageResult } from "../types";
142
-
143
- const items = ref<MasonryItem[]>([]);
144
-
145
- const masonry = ref<InstanceType<typeof Masonry> | null>(null);
146
-
147
- const layoutParams = reactive({
148
- sizes: {
149
- base: 1,
150
- sm: 2,
151
- md: 3,
152
- lg: 4,
153
- xl: 5,
154
- '2xl': 10
155
- },
156
- header: 0,
157
- footer: 0
158
- });
159
-
160
- const layout = computed(() => ({
161
- sizes: { ...layoutParams.sizes },
162
- header: layoutParams.header,
163
- footer: layoutParams.footer
164
- }));
165
-
166
- const showLayoutControls = ref(false);
167
-
168
- const getPage = async (page: number): Promise<GetPageResult> => {
169
- return new Promise((resolve) => {
170
- setTimeout(() => {
171
- // Check if the page exists in the fixture
172
- const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined;
173
-
174
- if (!pageData) {
175
- // Return empty items if page doesn't exist
176
- resolve({
177
- items: [],
178
- nextPage: null // null indicates no more pages
179
- });
180
- return;
181
- }
182
-
183
- const output: GetPageResult = {
184
- items: pageData.items,
185
- nextPage: page < (fixture as any[]).length ? page + 1 : null
186
- };
187
-
188
- resolve(output);
189
- }, 1000);
190
- });
191
- };
192
-
193
- // Device Simulation
194
- const deviceMode = ref<'auto' | 'phone' | 'tablet' | 'desktop'>('auto');
195
-
196
- const containerStyle = computed(() => {
197
- switch (deviceMode.value) {
198
- case 'phone':
199
- return { width: '375px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '20px', overflow: 'hidden', height: '100%' };
200
- case 'tablet':
201
- return { width: '768px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '12px', overflow: 'hidden', height: '100%' };
202
- case 'desktop':
203
- return { width: '1280px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '8px', overflow: 'hidden', height: '100%' };
204
- default:
205
- return { width: '100%', height: '100%' };
206
- }
207
- });
208
- </script>
1
+ <template>
2
+ <main class="flex flex-col h-screen overflow-hidden bg-slate-50 relative pt-[112px]">
3
+ <!-- Fixed Sub-Header -->
4
+ <header class="fixed top-[53px] left-0 right-0 z-20 w-full bg-white/80 backdrop-blur-md border-b border-slate-200 shadow-sm transition-all duration-300">
5
+ <div class="max-w-7xl mx-auto px-4 h-14 flex items-center justify-end">
6
+ <!-- Right: Controls -->
7
+ <div class="flex items-center gap-3">
8
+ <!-- Status Pill -->
9
+ <div v-if="masonry" class="hidden md:flex items-center gap-2 text-xs font-medium text-slate-600 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100">
10
+ <span class="flex items-center gap-1.5">
11
+ <span class="w-1.5 h-1.5 rounded-full shadow-sm" :class="masonry.isLoading ? 'bg-amber-400 animate-pulse' : 'bg-emerald-400'"></span>
12
+ {{ masonry.isLoading ? 'Loading...' : 'Ready' }}
13
+ </span>
14
+ <span class="w-px h-3 bg-slate-200"></span>
15
+ <span>{{ items.length }} items</span>
16
+ </div>
17
+
18
+ <div class="h-8 w-px bg-slate-100 mx-1 hidden md:block"></div>
19
+
20
+ <!-- Action Buttons -->
21
+ <div class="flex items-center gap-1">
22
+ <button
23
+ @click="showLayoutControls = !showLayoutControls"
24
+ class="w-9 h-9 flex items-center justify-center text-slate-500 hover:text-blue-600 hover:bg-blue-50 rounded-xl transition-all duration-200"
25
+ :class="{ 'text-blue-600 bg-blue-50 ring-2 ring-blue-100': showLayoutControls }"
26
+ title="Layout Settings"
27
+ >
28
+ <i class="fas fa-sliders text-sm"></i>
29
+ </button>
30
+
31
+ <a
32
+ href="https://github.com/wyxos/vibe"
33
+ target="_blank"
34
+ class="w-9 h-9 flex items-center justify-center text-slate-400 hover:text-slate-900 hover:bg-slate-50 rounded-xl transition-all duration-200"
35
+ title="View on GitHub"
36
+ >
37
+ <i class="fab fa-github text-lg"></i>
38
+ </a>
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Layout Controls Panel -->
44
+ <transition
45
+ enter-active-class="transition duration-200 ease-out"
46
+ enter-from-class="transform -translate-y-2 opacity-0"
47
+ enter-to-class="transform translate-y-0 opacity-100"
48
+ leave-active-class="transition duration-150 ease-in"
49
+ leave-from-class="transform translate-y-0 opacity-100"
50
+ leave-to-class="transform -translate-y-2 opacity-0"
51
+ >
52
+ <div v-if="showLayoutControls" class="absolute top-full left-4 right-4 md:left-auto md:right-4 mt-2 md:w-full md:max-w-lg bg-white/90 backdrop-blur-md border border-slate-200 shadow-xl rounded-xl p-4 md:p-6 pointer-events-auto z-30">
53
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
54
+ <!-- Column Settings -->
55
+ <div>
56
+ <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Column Configuration</h3>
57
+ <div class="grid grid-cols-3 sm:grid-cols-6 gap-2 sm:gap-3">
58
+ <div v-for="(val, key) in layoutParams.sizes" :key="key" class="flex flex-col gap-1.5">
59
+ <label class="text-[10px] font-bold text-slate-500 uppercase text-center">{{ key }}</label>
60
+ <input
61
+ v-model.number="layoutParams.sizes[key]"
62
+ type="number"
63
+ min="1"
64
+ class="w-full px-1 py-2 bg-slate-50 border border-slate-200 rounded-lg text-center text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
65
+ />
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Spacing Settings -->
71
+ <div>
72
+ <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Spacing</h3>
73
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
74
+ <div class="flex flex-col gap-1.5">
75
+ <label class="text-[10px] font-bold text-slate-500 uppercase">Header Offset</label>
76
+ <div class="relative">
77
+ <input
78
+ v-model.number="layoutParams.header"
79
+ type="number"
80
+ min="0"
81
+ class="w-full pl-3 pr-8 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
82
+ />
83
+ <span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">px</span>
84
+ </div>
85
+ </div>
86
+ <div class="flex flex-col gap-1.5">
87
+ <label class="text-[10px] font-bold text-slate-500 uppercase">Footer Offset</label>
88
+ <div class="relative">
89
+ <input
90
+ v-model.number="layoutParams.footer"
91
+ type="number"
92
+ min="0"
93
+ class="w-full pl-3 pr-8 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
94
+ />
95
+ <span class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">px</span>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Device Simulation -->
102
+ <div class="md:col-span-2 border-t border-slate-100 pt-6 mt-2">
103
+ <h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Device Simulation</h3>
104
+ <div class="flex flex-wrap gap-2">
105
+ <button
106
+ v-for="mode in ['auto', 'phone', 'tablet', 'desktop']"
107
+ :key="mode"
108
+ @click="deviceMode = mode as any"
109
+ class="px-4 py-2 rounded-lg text-sm font-medium transition-all capitalize"
110
+ :class="deviceMode === mode ? 'bg-blue-600 text-white shadow-md shadow-blue-200' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'"
111
+ >
112
+ <i class="fas mr-2" :class="{
113
+ 'fa-desktop': mode === 'desktop' || mode === 'auto',
114
+ 'fa-mobile-alt': mode === 'phone',
115
+ 'fa-tablet-alt': mode === 'tablet'
116
+ }"></i>
117
+ {{ mode }}
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </transition>
124
+ </header>
125
+
126
+ <!-- Main Content -->
127
+ <div class="flex flex-1 overflow-hidden relative p-5 transition-all duration-300 ease-in-out" :class="{'bg-slate-200/50': deviceMode !== 'auto'}">
128
+ <div :style="containerStyle" class="transition-all duration-500 ease-in-out bg-slate-50 shadow-sm relative">
129
+ <masonry v-model:items="items" :get-next-page="getPage" :load-at-page="1" :layout="layout" ref="masonry">
130
+ <!-- Demonstrate header/footer customization in the main demo -->
131
+ <template #item-header="{ item }">
132
+ <div class="h-full flex items-center justify-between px-3">
133
+ <div class="flex items-center gap-2">
134
+ <div class="w-6 h-6 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
135
+ <i :class="item.type === 'video' ? 'fas fa-video text-[10px] text-slate-500' : 'fas fa-image text-[10px] text-slate-500'"></i>
136
+ </div>
137
+ <span class="text-xs font-medium text-slate-700">#{{ String(item.id).split('-')[0] }}</span>
138
+ </div>
139
+ <span v-if="item.title" class="text-[11px] text-slate-600 truncate max-w-[160px]">
140
+ {{ item.title }}
141
+ </span>
142
+ </div>
143
+ </template>
144
+
145
+ <template #item-footer="{ item, remove }">
146
+ <div class="h-full flex items-center justify-between px-3">
147
+ <button
148
+ v-if="remove"
149
+ class="px-2.5 py-1 rounded-full bg-white/90 text-slate-700 text-[11px] shadow-sm hover:bg-red-500 hover:text-white transition-colors"
150
+ @click.stop="remove(item)"
151
+ >
152
+ Remove
153
+ </button>
154
+ <div class="text-[11px] text-slate-600">
155
+ {{ item.width }}×{{ item.height }}
156
+ </div>
157
+ </div>
158
+ </template>
159
+ </masonry>
160
+ </div>
161
+ </div>
162
+ </main>
163
+ </template>
164
+
165
+ <script setup lang="ts">
166
+ import Masonry from "../Masonry.vue";
167
+ import { ref, reactive, computed } from "vue";
168
+ import fixture from "../pages.json";
169
+ import type { MasonryItem, GetPageResult } from "../types";
170
+
171
+ const items = ref<MasonryItem[]>([]);
172
+
173
+ const masonry = ref<InstanceType<typeof Masonry> | null>(null);
174
+
175
+ const layoutParams = reactive({
176
+ sizes: {
177
+ base: 1,
178
+ sm: 2,
179
+ md: 3,
180
+ lg: 4,
181
+ xl: 5,
182
+ '2xl': 10
183
+ },
184
+ header: 36,
185
+ footer: 40
186
+ });
187
+
188
+ const layout = computed(() => ({
189
+ sizes: { ...layoutParams.sizes },
190
+ header: layoutParams.header,
191
+ footer: layoutParams.footer
192
+ }));
193
+
194
+ const showLayoutControls = ref(false);
195
+
196
+ const getPage = async (page: number): Promise<GetPageResult> => {
197
+ return new Promise((resolve) => {
198
+ setTimeout(() => {
199
+ // Check if the page exists in the fixture
200
+ const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined;
201
+
202
+ if (!pageData) {
203
+ // Return empty items if page doesn't exist
204
+ resolve({
205
+ items: [],
206
+ nextPage: null // null indicates no more pages
207
+ });
208
+ return;
209
+ }
210
+
211
+ const output: GetPageResult = {
212
+ items: pageData.items,
213
+ nextPage: page < (fixture as any[]).length ? page + 1 : null
214
+ };
215
+
216
+ resolve(output);
217
+ }, 1000);
218
+ });
219
+ };
220
+
221
+ // Device Simulation
222
+ const deviceMode = ref<'auto' | 'phone' | 'tablet' | 'desktop'>('auto');
223
+
224
+ const containerStyle = computed(() => {
225
+ switch (deviceMode.value) {
226
+ case 'phone':
227
+ return { width: '375px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '20px', overflow: 'hidden', height: '100%' };
228
+ case 'tablet':
229
+ return { width: '768px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '12px', overflow: 'hidden', height: '100%' };
230
+ case 'desktop':
231
+ return { width: '1280px', maxWidth: '100%', margin: '0 auto', border: '1px solid #e2e8f0', borderRadius: '8px', overflow: 'hidden', height: '100%' };
232
+ default:
233
+ return { width: '100%', height: '100%' };
234
+ }
235
+ });
236
+ </script>
package/toggle-link.mjs CHANGED
@@ -1,92 +1,92 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFileSync, writeFileSync } from 'fs';
4
- import { resolve, dirname, relative } from 'path';
5
- import { fileURLToPath } from 'url';
6
- import { execSync } from 'child_process';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
-
11
- // Get the vibe package directory
12
- const vibeDir = resolve(__dirname);
13
- const vibePackageJson = JSON.parse(readFileSync(resolve(vibeDir, 'package.json'), 'utf-8'));
14
- const vibeVersion = vibePackageJson.version;
15
-
16
- // Get the current working directory (where the script is run from)
17
- const cwd = process.cwd();
18
- const targetPackageJsonPath = resolve(cwd, 'package.json');
19
-
20
- try {
21
- const targetPackageJson = JSON.parse(readFileSync(targetPackageJsonPath, 'utf-8'));
22
-
23
- // Check if @wyxos/vibe exists in dependencies or devDependencies
24
- const deps = { ...targetPackageJson.dependencies, ...targetPackageJson.devDependencies };
25
- const currentVibeRef = deps['@wyxos/vibe'];
26
-
27
- if (!currentVibeRef) {
28
- console.error('❌ @wyxos/vibe not found in package.json dependencies or devDependencies');
29
- process.exit(1);
30
- }
31
-
32
- // Determine if it's a local path or version reference
33
- const isLocalPath = currentVibeRef.startsWith('file:') || currentVibeRef.startsWith('../') || currentVibeRef.startsWith('./');
34
-
35
- let newRef;
36
- let action;
37
-
38
- if (isLocalPath) {
39
- // Switch to published version
40
- newRef = `^${vibeVersion}`;
41
- action = 'published version';
42
- } else {
43
- // Switch to local path
44
- // Calculate relative path from target to vibe
45
- const relativePath = relative(cwd, vibeDir).replace(/\\/g, '/');
46
- newRef = `file:${relativePath}`;
47
- action = 'local path';
48
- }
49
-
50
- // Update package.json
51
- if (targetPackageJson.dependencies && targetPackageJson.dependencies['@wyxos/vibe']) {
52
- targetPackageJson.dependencies['@wyxos/vibe'] = newRef;
53
- }
54
- if (targetPackageJson.devDependencies && targetPackageJson.devDependencies['@wyxos/vibe']) {
55
- targetPackageJson.devDependencies['@wyxos/vibe'] = newRef;
56
- }
57
-
58
- // Write updated package.json
59
- writeFileSync(
60
- targetPackageJsonPath,
61
- JSON.stringify(targetPackageJson, null, 2) + '\n',
62
- 'utf-8'
63
- );
64
-
65
- console.log(`✅ Switched @wyxos/vibe to ${action}: ${newRef}`);
66
-
67
- // If switching to local path, ensure the library is built before installation
68
- if (action === 'local path') {
69
- try {
70
- console.log('🔧 Building local @wyxos/vibe (build:lib)...');
71
- execSync('npm run build:lib', { cwd: vibeDir, stdio: 'inherit' });
72
- } catch (e) {
73
- console.warn('⚠️ Failed to build local @wyxos/vibe. Attempting install anyway.');
74
- }
75
- }
76
-
77
- // Run npm install
78
- console.log('📦 Running npm install...');
79
- execSync('npm install', { cwd, stdio: 'inherit' });
80
-
81
- console.log('✨ Done!');
82
-
83
- } catch (error) {
84
- if (error.code === 'ENOENT') {
85
- console.error(`❌ package.json not found in ${cwd}`);
86
- console.error(' Make sure you run this script from a project directory that uses @wyxos/vibe');
87
- } else {
88
- console.error('❌ Error:', error.message);
89
- }
90
- process.exit(1);
91
- }
92
-
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync } from 'fs';
4
+ import { resolve, dirname, relative } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { execSync } from 'child_process';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Get the vibe package directory
12
+ const vibeDir = resolve(__dirname);
13
+ const vibePackageJson = JSON.parse(readFileSync(resolve(vibeDir, 'package.json'), 'utf-8'));
14
+ const vibeVersion = vibePackageJson.version;
15
+
16
+ // Get the current working directory (where the script is run from)
17
+ const cwd = process.cwd();
18
+ const targetPackageJsonPath = resolve(cwd, 'package.json');
19
+
20
+ try {
21
+ const targetPackageJson = JSON.parse(readFileSync(targetPackageJsonPath, 'utf-8'));
22
+
23
+ // Check if @wyxos/vibe exists in dependencies or devDependencies
24
+ const deps = { ...targetPackageJson.dependencies, ...targetPackageJson.devDependencies };
25
+ const currentVibeRef = deps['@wyxos/vibe'];
26
+
27
+ if (!currentVibeRef) {
28
+ console.error('❌ @wyxos/vibe not found in package.json dependencies or devDependencies');
29
+ process.exit(1);
30
+ }
31
+
32
+ // Determine if it's a local path or version reference
33
+ const isLocalPath = currentVibeRef.startsWith('file:') || currentVibeRef.startsWith('../') || currentVibeRef.startsWith('./');
34
+
35
+ let newRef;
36
+ let action;
37
+
38
+ if (isLocalPath) {
39
+ // Switch to published version
40
+ newRef = `^${vibeVersion}`;
41
+ action = 'published version';
42
+ } else {
43
+ // Switch to local path
44
+ // Calculate relative path from target to vibe
45
+ const relativePath = relative(cwd, vibeDir).replace(/\\/g, '/');
46
+ newRef = `file:${relativePath}`;
47
+ action = 'local path';
48
+ }
49
+
50
+ // Update package.json
51
+ if (targetPackageJson.dependencies && targetPackageJson.dependencies['@wyxos/vibe']) {
52
+ targetPackageJson.dependencies['@wyxos/vibe'] = newRef;
53
+ }
54
+ if (targetPackageJson.devDependencies && targetPackageJson.devDependencies['@wyxos/vibe']) {
55
+ targetPackageJson.devDependencies['@wyxos/vibe'] = newRef;
56
+ }
57
+
58
+ // Write updated package.json
59
+ writeFileSync(
60
+ targetPackageJsonPath,
61
+ JSON.stringify(targetPackageJson, null, 2) + '\n',
62
+ 'utf-8'
63
+ );
64
+
65
+ console.log(`✅ Switched @wyxos/vibe to ${action}: ${newRef}`);
66
+
67
+ // If switching to local path, ensure the library is built before installation
68
+ if (action === 'local path') {
69
+ try {
70
+ console.log('🔧 Building local @wyxos/vibe (build:lib)...');
71
+ execSync('npm run build:lib', { cwd: vibeDir, stdio: 'inherit' });
72
+ } catch (e) {
73
+ console.warn('⚠️ Failed to build local @wyxos/vibe. Attempting install anyway.');
74
+ }
75
+ }
76
+
77
+ // Run npm install
78
+ console.log('📦 Running npm install...');
79
+ execSync('npm install', { cwd, stdio: 'inherit' });
80
+
81
+ console.log('✨ Done!');
82
+
83
+ } catch (error) {
84
+ if (error.code === 'ENOENT') {
85
+ console.error(`❌ package.json not found in ${cwd}`);
86
+ console.error(' Make sure you run this script from a project directory that uses @wyxos/vibe');
87
+ } else {
88
+ console.error('❌ Error:', error.message);
89
+ }
90
+ process.exit(1);
91
+ }
92
+