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 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 automatically
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.').helpOption(false);
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
- .action(async () => {
160
- await runSync();
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
- if (process.argv.length <= 2) {
207
+ const argv = process.argv.filter(arg => arg !== '--');
208
+ if (argv.length <= 2) {
187
209
  printWelcome();
188
210
  return;
189
211
  }
190
- program.parse(process.argv);
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 files = [
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
- if (silent) {
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
- if (choice === 'local') {
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
- if (file.label === 'settings.json') {
406
- await prepareSettingsForPush(file.localPath, file.repoPath);
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
- if (file.label === 'settings.json') {
417
- await applyRemoteSettings(file.repoPath, path.join(tmp, 'extensions', 'index.json'), file.localPath, silent);
418
- }
419
- else {
420
- await fs.ensureDir(path.dirname(file.localPath));
421
- await fs.copy(file.repoPath, file.localPath, { overwrite: true });
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zedx",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Scaffold Zed Editor extensions and sync your settings across machines.",
5
5
  "keywords": [
6
6
  "boilerplate",