zedx 0.8.1 → 0.9.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/README.md +3 -1
- package/dist/index.js +29 -7
- package/dist/snippet.js +1 -1
- package/dist/sync.d.ts +4 -0
- package/dist/sync.js +67 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@ zedx version major # 1.2.3 → 2.0.0
|
|
|
33
33
|
|
|
34
34
|
# Sync Zed settings and extensions via a GitHub repo
|
|
35
35
|
zedx sync init # Link a GitHub repo as the sync target (run once)
|
|
36
|
-
zedx sync # Sync local and remote config
|
|
36
|
+
zedx sync # Sync local and remote config (prompts on conflict)
|
|
37
|
+
zedx sync --local # Sync, always keeping local on conflict
|
|
38
|
+
zedx sync --remote # Sync, always using remote on conflict
|
|
37
39
|
zedx sync status # Show sync state between local config and the remote repo
|
|
38
40
|
zedx sync install # Install an OS daemon to auto-sync when Zed config changes
|
|
39
41
|
zedx sync uninstall # Remove the OS daemon
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import * as p from '@clack/prompts';
|
|
4
5
|
import { Command } from 'commander';
|
|
@@ -11,7 +12,9 @@ import { generateExtension } from './generator.js';
|
|
|
11
12
|
import { installDevExtension } from './install.js';
|
|
12
13
|
import { promptUser, promptThemeDetails, promptLanguageDetails } from './prompts.js';
|
|
13
14
|
import { addLsp } from './snippet.js';
|
|
14
|
-
import { syncInit, runSync, syncStatus } from './sync.js';
|
|
15
|
+
import { syncInit, runSync, syncStatus, syncSelect } from './sync.js';
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const { version } = require('../package.json');
|
|
15
18
|
function bumpVersion(version, type) {
|
|
16
19
|
const [major, minor, patch] = version.split('.').map(Number);
|
|
17
20
|
switch (type) {
|
|
@@ -65,6 +68,7 @@ function printWelcome() {
|
|
|
65
68
|
['zedx install', 'Install as a Zed dev extension'],
|
|
66
69
|
['zedx version <major|minor|patch>', 'Bump extension version'],
|
|
67
70
|
['zedx sync', 'Sync Zed settings via a git repo'],
|
|
71
|
+
['zedx sync select', 'Choose which files to sync interactively'],
|
|
68
72
|
['zedx sync init', 'Link a git repo as the sync target'],
|
|
69
73
|
['zedx sync status', 'Show sync state between local and remote'],
|
|
70
74
|
['zedx sync install', 'Install the OS daemon for auto-sync'],
|
|
@@ -74,7 +78,7 @@ function printWelcome() {
|
|
|
74
78
|
for (const [cmd, desc] of commands) {
|
|
75
79
|
console.log(` ${color.cyan(cmd.padEnd(38))}${color.dim(desc)}`);
|
|
76
80
|
}
|
|
77
|
-
console.log(`\n ${color.dim('Docs:')} ${color.underline(color.blue('https://zed.dev/docs/extensions'))}\n`);
|
|
81
|
+
console.log(`\n ${color.dim('Zed Docs:')} ${color.underline(color.blue('https://zed.dev/docs/extensions'))}\n`);
|
|
78
82
|
}
|
|
79
83
|
async function runCreate() {
|
|
80
84
|
const options = await promptUser();
|
|
@@ -99,7 +103,7 @@ async function runCreate() {
|
|
|
99
103
|
}
|
|
100
104
|
async function main() {
|
|
101
105
|
const program = new Command();
|
|
102
|
-
program.name('zedx').description('The CLI toolkit for Zed Editor.').
|
|
106
|
+
program.name('zedx').description('The CLI toolkit for Zed Editor.').version(`zedx v${version}`);
|
|
103
107
|
program
|
|
104
108
|
.command('create')
|
|
105
109
|
.description('Scaffold a new Zed extension')
|
|
@@ -156,8 +160,19 @@ async function main() {
|
|
|
156
160
|
const syncCmd = program
|
|
157
161
|
.command('sync')
|
|
158
162
|
.description('Sync Zed settings and extensions via a GitHub repo')
|
|
159
|
-
.
|
|
160
|
-
|
|
163
|
+
.option('--local', 'On conflict, always keep the local version')
|
|
164
|
+
.option('--remote', 'On conflict, always use the remote version')
|
|
165
|
+
.action(async (opts) => {
|
|
166
|
+
if (opts.local && opts.remote) {
|
|
167
|
+
p.log.error(color.red('--local and --remote are mutually exclusive.'));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
const conflict = opts.local
|
|
171
|
+
? 'local'
|
|
172
|
+
: opts.remote
|
|
173
|
+
? 'remote'
|
|
174
|
+
: 'prompt';
|
|
175
|
+
await runSync({ conflict });
|
|
161
176
|
});
|
|
162
177
|
syncCmd
|
|
163
178
|
.command('init')
|
|
@@ -171,6 +186,12 @@ async function main() {
|
|
|
171
186
|
.action(async () => {
|
|
172
187
|
await syncStatus();
|
|
173
188
|
});
|
|
189
|
+
syncCmd
|
|
190
|
+
.command('select')
|
|
191
|
+
.description('Interactively choose which files to sync')
|
|
192
|
+
.action(async () => {
|
|
193
|
+
await syncSelect();
|
|
194
|
+
});
|
|
174
195
|
syncCmd
|
|
175
196
|
.command('install')
|
|
176
197
|
.description('Install the OS daemon to auto-sync when Zed config changes')
|
|
@@ -183,10 +204,11 @@ async function main() {
|
|
|
183
204
|
.action(async () => {
|
|
184
205
|
await syncUninstall();
|
|
185
206
|
});
|
|
186
|
-
|
|
207
|
+
const argv = process.argv.filter(arg => arg !== '--');
|
|
208
|
+
if (argv.length <= 2) {
|
|
187
209
|
printWelcome();
|
|
188
210
|
return;
|
|
189
211
|
}
|
|
190
|
-
program.parse(
|
|
212
|
+
program.parse(argv);
|
|
191
213
|
}
|
|
192
214
|
main().catch(console.error);
|
package/dist/snippet.js
CHANGED
|
@@ -165,5 +165,5 @@ export async function addLsp(callerDir) {
|
|
|
165
165
|
p.outro(`${color.green('✓')} LSP snippet added.\n\n` +
|
|
166
166
|
` ${color.dim('1.')} Edit ${color.cyan('src/lib.rs')} — implement ${color.white('language_server_command')}\n` +
|
|
167
167
|
` ${color.dim('2.')} Edit ${color.cyan('Cargo.toml')} — pin ${color.white('zed_extension_api')} to latest version\n` +
|
|
168
|
-
` ${color.dim('3.')} ${color.dim('Docs:')} ${color.underline(color.blue('https://zed.dev/docs/extensions/languages#language-servers'))}`);
|
|
168
|
+
` ${color.dim('3.')} ${color.dim('Zed Docs:')} ${color.underline(color.blue('https://zed.dev/docs/extensions/languages#language-servers'))}`);
|
|
169
169
|
}
|
package/dist/sync.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export declare function syncStatus(): Promise<void>;
|
|
2
2
|
export declare function syncInit(): Promise<void>;
|
|
3
|
+
export type ConflictStrategy = 'local' | 'remote' | 'prompt';
|
|
4
|
+
export declare function syncSelect(): Promise<void>;
|
|
3
5
|
export declare function runSync(opts?: {
|
|
4
6
|
silent?: boolean;
|
|
7
|
+
conflict?: ConflictStrategy;
|
|
8
|
+
selectedFiles?: string[];
|
|
5
9
|
}): Promise<void>;
|
package/dist/sync.js
CHANGED
|
@@ -231,9 +231,38 @@ export async function syncInit() {
|
|
|
231
231
|
p.outro(`${color.green('✓')} Sync config saved to ${color.cyan(ZEDX_CONFIG_PATH)}\n\n` +
|
|
232
232
|
` Run ${color.cyan('zedx sync')} to sync your Zed config.`);
|
|
233
233
|
}
|
|
234
|
+
// zedx sync select
|
|
235
|
+
export async function syncSelect() {
|
|
236
|
+
console.log('');
|
|
237
|
+
p.intro(`${color.bgBlue(color.bold(' zedx sync select '))} ${color.blue('Choose which files to sync…')}`);
|
|
238
|
+
await requireSyncConfig();
|
|
239
|
+
const allFiles = [
|
|
240
|
+
{
|
|
241
|
+
value: 'settings',
|
|
242
|
+
label: 'settings.json',
|
|
243
|
+
hint: 'Zed editor settings',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
value: 'extensions',
|
|
247
|
+
label: 'extensions/index.json',
|
|
248
|
+
hint: 'Installed extensions list',
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
const selected = await p.multiselect({
|
|
252
|
+
message: 'Select files to sync',
|
|
253
|
+
options: allFiles,
|
|
254
|
+
required: true,
|
|
255
|
+
});
|
|
256
|
+
if (p.isCancel(selected)) {
|
|
257
|
+
p.cancel('Cancelled.');
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
const selectedFiles = selected;
|
|
261
|
+
await runSync({ selectedFiles });
|
|
262
|
+
}
|
|
234
263
|
// zedx sync
|
|
235
264
|
export async function runSync(opts = {}) {
|
|
236
|
-
const { silent = false } = opts;
|
|
265
|
+
const { silent = false, conflict = 'prompt', selectedFiles } = opts;
|
|
237
266
|
// In silent mode (daemon/watch), route all UI through plain console.log
|
|
238
267
|
// Interactive conflict prompts fall back to "local wins".
|
|
239
268
|
const log = {
|
|
@@ -282,18 +311,23 @@ export async function runSync(opts = {}) {
|
|
|
282
311
|
}
|
|
283
312
|
// 2. Determine what changed for each file
|
|
284
313
|
const lastSync = config.lastSync ? new Date(config.lastSync) : null;
|
|
285
|
-
const
|
|
314
|
+
const allFiles = [
|
|
286
315
|
{
|
|
316
|
+
key: 'settings',
|
|
287
317
|
repoPath: path.join(tmp, 'settings.json'),
|
|
288
318
|
localPath: zedPaths.settings,
|
|
289
319
|
label: 'settings.json',
|
|
290
320
|
},
|
|
291
321
|
{
|
|
322
|
+
key: 'extensions',
|
|
292
323
|
repoPath: path.join(tmp, 'extensions', 'index.json'),
|
|
293
324
|
localPath: zedPaths.extensions,
|
|
294
325
|
label: 'extensions/index.json',
|
|
295
326
|
},
|
|
296
327
|
];
|
|
328
|
+
const files = selectedFiles
|
|
329
|
+
? allFiles.filter(f => selectedFiles.includes(f.key))
|
|
330
|
+
: allFiles;
|
|
297
331
|
let anyChanges = false;
|
|
298
332
|
for (const file of files) {
|
|
299
333
|
const localExists = await fs.pathExists(file.localPath);
|
|
@@ -366,18 +400,20 @@ export async function runSync(opts = {}) {
|
|
|
366
400
|
}
|
|
367
401
|
}
|
|
368
402
|
else {
|
|
369
|
-
// Both changed
|
|
370
|
-
|
|
403
|
+
// Both changed — resolve based on strategy
|
|
404
|
+
// Determine the effective resolution:
|
|
405
|
+
// - explicit --local / --remote flag always wins
|
|
406
|
+
// - silent (daemon) mode falls back to local
|
|
407
|
+
// - otherwise prompt interactively
|
|
408
|
+
let resolution;
|
|
409
|
+
if (conflict === 'local' || conflict === 'remote') {
|
|
410
|
+
resolution = conflict;
|
|
411
|
+
log.warn(`${file.label}: conflict — using ${color.bold(resolution)} (--${resolution} flag).`);
|
|
412
|
+
}
|
|
413
|
+
else if (silent) {
|
|
371
414
|
// Daemon can't prompt — local wins, will be pushed
|
|
415
|
+
resolution = 'local';
|
|
372
416
|
log.warn(`${file.label}: conflict detected in unattended mode — keeping local.`);
|
|
373
|
-
if (file.label === 'settings.json') {
|
|
374
|
-
await prepareSettingsForPush(file.localPath, file.repoPath);
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
await fs.ensureDir(path.dirname(file.repoPath));
|
|
378
|
-
await fs.copy(file.localPath, file.repoPath, { overwrite: true });
|
|
379
|
-
}
|
|
380
|
-
anyChanges = true;
|
|
381
417
|
}
|
|
382
418
|
else {
|
|
383
419
|
p.log.warn(color.yellow(`conflict between local and remote ${file.label}`));
|
|
@@ -400,26 +436,29 @@ export async function runSync(opts = {}) {
|
|
|
400
436
|
p.cancel('Cancelled.');
|
|
401
437
|
process.exit(0);
|
|
402
438
|
}
|
|
403
|
-
|
|
439
|
+
resolution = choice;
|
|
440
|
+
}
|
|
441
|
+
if (resolution === 'local') {
|
|
442
|
+
if (!silent && conflict === 'prompt')
|
|
404
443
|
p.log.info(`${file.label}: ${color.green('keeping local, will push')}`);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
await fs.ensureDir(path.dirname(file.repoPath));
|
|
410
|
-
await fs.copy(file.localPath, file.repoPath, { overwrite: true });
|
|
411
|
-
}
|
|
412
|
-
anyChanges = true;
|
|
444
|
+
if (file.label === 'settings.json') {
|
|
445
|
+
await prepareSettingsForPush(file.localPath, file.repoPath);
|
|
413
446
|
}
|
|
414
447
|
else {
|
|
448
|
+
await fs.ensureDir(path.dirname(file.repoPath));
|
|
449
|
+
await fs.copy(file.localPath, file.repoPath, { overwrite: true });
|
|
450
|
+
}
|
|
451
|
+
anyChanges = true;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
if (!silent && conflict === 'prompt')
|
|
415
455
|
p.log.info(`${file.label}: ${color.cyan('applying remote')}`);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
456
|
+
if (file.label === 'settings.json') {
|
|
457
|
+
await applyRemoteSettings(file.repoPath, path.join(tmp, 'extensions', 'index.json'), file.localPath, silent);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
await fs.ensureDir(path.dirname(file.localPath));
|
|
461
|
+
await fs.copy(file.repoPath, file.localPath, { overwrite: true });
|
|
423
462
|
}
|
|
424
463
|
}
|
|
425
464
|
}
|