@owdproject/cli 0.1.0 → 0.2.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.
@@ -1,8 +1,48 @@
1
- export {
2
- DESKTOP_CONFIG_FILENAME,
3
- LEGACY_DESKTOP_CONFIG_FILENAME,
4
- LEGACY_CONFIG_DEPRECATION,
5
- resolveDesktopConfigPath,
6
- warnLegacyDesktopConfig,
7
- desktopConfigWritePath,
8
- } from '@owdproject/core/kit/authoring.ts'
1
+ import { existsSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ export const DESKTOP_CONFIG_FILENAME = 'desktop.config.ts'
5
+ export const LEGACY_DESKTOP_CONFIG_FILENAME = 'owd.config.ts'
6
+
7
+ export const LEGACY_CONFIG_DEPRECATION =
8
+ '[@owdproject/core] owd.config.ts is deprecated and kept only for backward compatibility; rename to desktop.config.ts (required from @owdproject/core 3.2).'
9
+
10
+ export function resolveDesktopConfigPath(rootDir) {
11
+ const desktopPath = join(rootDir, DESKTOP_CONFIG_FILENAME)
12
+ const legacyPath = join(rootDir, LEGACY_DESKTOP_CONFIG_FILENAME)
13
+ const hasDesktop = existsSync(desktopPath)
14
+ const hasLegacy = existsSync(legacyPath)
15
+
16
+ if (hasDesktop && hasLegacy) {
17
+ console.warn(
18
+ '[@owdproject/core] Both desktop.config.ts and owd.config.ts exist; using desktop.config.ts. Remove owd.config.ts.',
19
+ )
20
+ return { path: desktopPath, file: DESKTOP_CONFIG_FILENAME, legacy: false }
21
+ }
22
+
23
+ if (hasDesktop) {
24
+ return { path: desktopPath, file: DESKTOP_CONFIG_FILENAME, legacy: false }
25
+ }
26
+
27
+ if (hasLegacy) {
28
+ return {
29
+ path: legacyPath,
30
+ file: LEGACY_DESKTOP_CONFIG_FILENAME,
31
+ legacy: true,
32
+ }
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ export function warnLegacyDesktopConfig(resolved) {
39
+ if (resolved?.legacy) {
40
+ console.warn(LEGACY_CONFIG_DEPRECATION)
41
+ }
42
+ }
43
+
44
+ /** Path used when creating or updating config from the CLI (always the new filename). */
45
+ export function desktopConfigWritePath(desktopDir) {
46
+ return join(desktopDir, DESKTOP_CONFIG_FILENAME)
47
+ }
48
+
@@ -20,7 +20,7 @@ import {
20
20
  } from './npmVersions.js'
21
21
 
22
22
  const __dirname = dirname(fileURLToPath(import.meta.url))
23
- const BLUEPRINT_ROOT = resolve(__dirname, '../../template-blueprint')
23
+ const BLUEPRINT_ROOT = resolve(__dirname, '../../../core/template-blueprint')
24
24
 
25
25
  export const STARTER_DESKTOP_DEPS = [
26
26
  '@owdproject/core',
@@ -367,6 +367,7 @@ export async function generateTemplate(workspaceRoot, options = {}) {
367
367
 
368
368
  writeGeneratedFiles(workspaceRoot, outputRoot, { versions })
369
369
 
370
+
370
371
  if (check) {
371
372
  const diffs = diffTemplateTrees(templateOut, outputRoot)
372
373
  if (diffs.length === 0) {
@@ -17,6 +17,7 @@ import {
17
17
  resolveDesktopConfigPath,
18
18
  desktopConfigWritePath,
19
19
  } from './desktopConfig.js'
20
+ import { addToDesktopConfig } from './config.js'
20
21
 
21
22
  const require = createRequire(import.meta.url)
22
23
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -340,16 +341,7 @@ export async function runPrepareModules(workspaceRoot, _stdio = 'inherit') {
340
341
  }
341
342
  }
342
343
 
343
- function loadConfigUtil(workspaceRoot) {
344
- const utilPath = join(
345
- workspaceRoot,
346
- 'node_modules/@owdproject/nx/dist/utils/utilConfig.js',
347
- )
348
- if (!existsSync(utilPath)) {
349
- throw new Error('Workspace install needs @owdproject/nx. Run `pnpm install` at the repo root.')
350
- }
351
- return require(utilPath)
352
- }
344
+
353
345
 
354
346
  export function printPlan({ pkgName, kind, targetDir, source, branch, dryRun }) {
355
347
  const kindMeta = KINDS[kind]
@@ -453,8 +445,7 @@ export async function runWorkspaceInstall({
453
445
  await cloneRepo(targetDir, source.gitUrl, branch, workspaceRoot)
454
446
  await linkWorkspacePackage(desktopPath, pkgName)
455
447
 
456
- const { addToDesktopConfig } = loadConfigUtil(workspaceRoot)
457
- addToDesktopConfig(configPath, KINDS[kind].configKey, pkgName)
448
+ addToDesktopConfig(configPath, workspaceRoot, KINDS[kind].configKey, pkgName)
458
449
 
459
450
  await runDevPrepare(workspaceRoot, pkgName)
460
451
 
@@ -463,16 +454,21 @@ export async function runWorkspaceInstall({
463
454
  }
464
455
 
465
456
  export function runNpmInstall(kind, pkgName, workspaceRoot, stdio = 'inherit') {
466
- const nxTarget = KINDS[kind].nx
467
- const child = spawn('pnpm', ['nx', 'run', nxTarget, `--name=${pkgName}`], {
468
- stdio,
469
- shell: true,
470
- cwd: workspaceRoot,
471
- })
472
- child.on('exit', (code) => {
473
- if (stdio === 'inherit') process.exit(code ?? 1)
457
+ return new Promise((resolve, reject) => {
458
+ const child = spawn('node', ['scripts/install-app.mjs', pkgName, `--kind=${kind}`], {
459
+ stdio,
460
+ shell: true,
461
+ cwd: workspaceRoot,
462
+ })
463
+ child.on('error', reject)
464
+ child.on('exit', (code) => {
465
+ if (code === 0) {
466
+ resolve()
467
+ } else {
468
+ reject(new Error(`Installation failed with exit code ${code}`))
469
+ }
470
+ })
474
471
  })
475
- return child
476
472
  }
477
473
 
478
474
  /** Preview label for TUI list rows */
@@ -4,7 +4,8 @@ import {
4
4
  isInstallableDesktopModule,
5
5
  isDesktopKitPackage,
6
6
  shortName,
7
- getPackagesRequiredByTheme
7
+ getPackagesRequiredByTheme,
8
+ findMissingWorkspaceDependencies,
8
9
  } from './workspace.js'
9
10
  import {
10
11
  readDesktopConfig,
@@ -275,6 +276,42 @@ export async function executeInstallPlan(ctx) {
275
276
  }
276
277
  }
277
278
 
279
+ // Detect and clone any missing workspace:* dependencies recursively
280
+ let missingWorkspaceDeps = findMissingWorkspaceDependencies(workspaceRoot)
281
+ const autoClonedDeps = new Set()
282
+
283
+ while (missingWorkspaceDeps.length > 0) {
284
+ const dep = missingWorkspaceDeps.shift()
285
+ if (autoClonedDeps.has(dep)) continue
286
+ autoClonedDeps.add(dep)
287
+
288
+ bump(`Cloning dependency ${shortName(dep)}…`)
289
+ ctx.clearLogs()
290
+
291
+ try {
292
+ const plan = await resolveInstallPlan(dep, settings, workspaceRoot)
293
+ if (plan.error) {
294
+ throw new Error(plan.error)
295
+ }
296
+ if (plan.targetDir && plan.source?.gitUrl) {
297
+ await cloneRepo(plan.targetDir, plan.source.gitUrl, undefined, workspaceRoot, { quiet: true })
298
+ didClone = true
299
+ clonedPackages.push(dep)
300
+ const newMissing = findMissingWorkspaceDependencies(workspaceRoot)
301
+ for (const m of newMissing) {
302
+ if (!autoClonedDeps.has(m) && !missingWorkspaceDeps.includes(m)) {
303
+ missingWorkspaceDeps.push(m)
304
+ }
305
+ }
306
+ } else {
307
+ throw new Error('Unknown git clone URL')
308
+ }
309
+ } catch (err) {
310
+ const msg = err.message?.split('\n').find(l => l.trim()) ?? String(err)
311
+ failures.push({ pkg: dep, error: `Failed to resolve workspace dependency ${shortName(dep)}: ${msg}` })
312
+ }
313
+ }
314
+
278
315
  if (didClone) {
279
316
  bump('Installing dependencies…')
280
317
  ctx.clearLogs()
@@ -331,17 +368,13 @@ export async function executeInstallPlan(ctx) {
331
368
  ctx.setConfigRestartHintTimer(null)
332
369
  }
333
370
  if (ctx.isDevServerUp()) {
334
- if (nextTheme !== config.theme) {
335
- ctx.rebootDevServer()
336
- } else {
337
- ctx.setConfigRestartHintUntil(Date.now() + 5000)
338
- ctx.setConfigRestartHintTimer(setTimeout(() => {
339
- ctx.setConfigRestartHintUntil(0)
340
- ctx.setConfigRestartHintTimer(null)
341
- ctx.renderClient()
342
- ctx.screen.render()
343
- }, 5000))
344
- }
371
+ ctx.setConfigRestartHintUntil(Date.now() + 5000)
372
+ ctx.setConfigRestartHintTimer(setTimeout(() => {
373
+ ctx.setConfigRestartHintUntil(0)
374
+ ctx.setConfigRestartHintTimer(null)
375
+ ctx.renderClient()
376
+ ctx.screen.render()
377
+ }, 5000))
345
378
  }
346
379
 
347
380
  ctx.renderAll()
@@ -440,7 +473,7 @@ export async function runStartupInstallFlow(ctx, { isStartup = false } = {}) {
440
473
 
441
474
  const merged = mergeInstalled(catalog, config, deps, workspaceRoot)
442
475
  const missingAppsModules = merged
443
- .filter((item) => item.installed && !item.localSource && item.kind !== 'theme')
476
+ .filter((item) => item.installed && !item.localSource && !item.inPackageJson && item.kind !== 'theme')
444
477
  .map((item) => item.name)
445
478
 
446
479
  const desiredTheme = ctx.getPendingTheme() ?? config.theme
@@ -601,6 +634,43 @@ export async function runStartupInstallFlow(ctx, { isStartup = false } = {}) {
601
634
  }
602
635
  }
603
636
 
637
+ // Detect and clone any missing workspace:* dependencies recursively
638
+ let missingWorkspaceDeps = findMissingWorkspaceDependencies(workspaceRoot)
639
+ const autoClonedDeps = new Set()
640
+
641
+ while (missingWorkspaceDeps.length > 0) {
642
+ const dep = missingWorkspaceDeps.shift()
643
+ if (autoClonedDeps.has(dep)) continue
644
+ autoClonedDeps.add(dep)
645
+
646
+ bump(`Cloning dependency ${shortName(dep)}…`)
647
+ ctx.clearLogs()
648
+
649
+ try {
650
+ const plan = await resolveInstallPlan(dep, settings, workspaceRoot)
651
+ if (plan.error) {
652
+ throw new Error(plan.error)
653
+ }
654
+ if (plan.targetDir && plan.source?.gitUrl) {
655
+ await cloneRepo(plan.targetDir, plan.source.gitUrl, undefined, workspaceRoot, { quiet: true })
656
+ didClone = true
657
+ allCloned.push(dep)
658
+ const newMissing = findMissingWorkspaceDependencies(workspaceRoot)
659
+ for (const m of newMissing) {
660
+ if (!autoClonedDeps.has(m) && !missingWorkspaceDeps.includes(m)) {
661
+ missingWorkspaceDeps.push(m)
662
+ }
663
+ }
664
+ } else {
665
+ throw new Error('Unknown git clone URL')
666
+ }
667
+ } catch (err) {
668
+ const msg = err.message?.split('\n').find(l => l.trim()) ?? String(err)
669
+ ctx.setStatus(`Failed to resolve workspace dependency ${shortName(dep)}: ${msg}`, 'error')
670
+ ctx.screen.render()
671
+ }
672
+ }
673
+
604
674
  if (didClone) {
605
675
  if (!ctx.isInstalling()) {
606
676
  dynTotal += 1 + allCloned.length + 1
@@ -689,17 +759,13 @@ export async function runStartupInstallFlow(ctx, { isStartup = false } = {}) {
689
759
  ctx.setConfigRestartHintTimer(null)
690
760
  }
691
761
  if (ctx.isDevServerUp()) {
692
- if (nextTheme !== config.theme) {
693
- ctx.rebootDevServer()
694
- } else {
695
- ctx.setConfigRestartHintUntil(Date.now() + 5000)
696
- ctx.setConfigRestartHintTimer(setTimeout(() => {
697
- ctx.setConfigRestartHintUntil(0)
698
- ctx.setConfigRestartHintTimer(null)
699
- ctx.renderClient()
700
- ctx.screen.render()
701
- }, 5000))
702
- }
762
+ ctx.setConfigRestartHintUntil(Date.now() + 5000)
763
+ ctx.setConfigRestartHintTimer(setTimeout(() => {
764
+ ctx.setConfigRestartHintUntil(0)
765
+ ctx.setConfigRestartHintTimer(null)
766
+ ctx.renderClient()
767
+ ctx.screen.render()
768
+ }, 5000))
703
769
  }
704
770
 
705
771
  ctx.renderAll()
@@ -255,6 +255,16 @@ export function buildSourceOptions(metadata, settings, sshAuth = {}) {
255
255
  /** @type {SourceOption[]} */
256
256
  const options = []
257
257
 
258
+ if (metadata.local) {
259
+ options.push({
260
+ id: 'local',
261
+ kind: 'local',
262
+ label: `Use Existing Local Folder — ${metadata.shortName}`,
263
+ url: '',
264
+ choice: { type: 'local' },
265
+ })
266
+ }
267
+
258
268
  const official = metadata.github.official
259
269
  if (official?.owner) {
260
270
  options.push({
package/bin/lib/status.js CHANGED
@@ -49,6 +49,8 @@ export function findDevProcess() {
49
49
  const patterns = [
50
50
  'nuxt dev',
51
51
  'nuxi dev',
52
+ 'nuxt\\.mjs dev',
53
+ 'nuxi\\.mjs dev',
52
54
  'nx run desktop:serve',
53
55
  'node.*\\.nuxt',
54
56
  'desktop.*nuxt',
@@ -407,6 +407,13 @@ export function validateOwdModule(packageDir, options = {}) {
407
407
  'Do not add a "prepare" script on publishable packages — it runs on npm publish/install and breaks release. Use "prepack" (full build) and "dev:prepare" (local stub + playground) only.',
408
408
  )
409
409
  }
410
+ if (!scripts.validate?.includes('desktop validate')) {
411
+ issue(
412
+ 'warning',
413
+ 'validate-script',
414
+ 'scripts.validate should run "desktop validate ."',
415
+ )
416
+ }
410
417
  if (!rootExport?.development?.includes('src/module')) {
411
418
  issue(
412
419
  'warning',
@@ -454,6 +461,13 @@ export function validateOwdModule(packageDir, options = {}) {
454
461
  'legacy layout: move runtime/ under src/runtime/',
455
462
  )
456
463
  }
464
+ if (existsSync(join(dir, 'runtime')) && existsSync(join(dir, 'src', 'runtime'))) {
465
+ issue(
466
+ 'error',
467
+ 'duplicate-runtime-root',
468
+ 'remove legacy runtime/ at package root — keep only src/runtime/ (duplicate layout fails CI and confuses module resolution)',
469
+ )
470
+ }
457
471
 
458
472
  const moduleTs = join(dir, 'src', 'module.ts')
459
473
  if (!existsSync(moduleTs)) {
@@ -498,11 +512,56 @@ export function validateOwdModule(packageDir, options = {}) {
498
512
  'theme src/module.ts should call registerThemeTailwindPath from @owdproject/kit-primevue/kit/registerTailwindPath',
499
513
  )
500
514
  }
515
+
516
+ // Check required theme components
517
+ const componentsDir = join(dir, 'src', 'runtime', 'components')
518
+ if (!existsSync(componentsDir)) {
519
+ issue(
520
+ 'error',
521
+ 'missing-components-dir',
522
+ 'theme src/runtime/components/ directory is required',
523
+ )
524
+ } else {
525
+ const foundComponents = new Set()
526
+ const scanComponents = (current) => {
527
+ for (const entry of readdirSync(current)) {
528
+ const full = join(current, entry)
529
+ let stat
530
+ try {
531
+ stat = statSync(full)
532
+ } catch {
533
+ continue
534
+ }
535
+ if (stat.isDirectory()) {
536
+ scanComponents(full)
537
+ } else if (/\.(vue|ts|js)$/i.test(entry)) {
538
+ const name = entry.replace(/\.(vue|ts|js)$/i, '')
539
+ foundComponents.add(name)
540
+ }
541
+ }
542
+ }
543
+ scanComponents(componentsDir)
544
+
545
+ const requiredComponents = [
546
+ 'DesktopWindow',
547
+ 'DesktopWindowNav',
548
+ 'DesktopWindowIframe',
549
+ ]
550
+ for (const req of requiredComponents) {
551
+ if (!foundComponents.has(req)) {
552
+ issue(
553
+ 'error',
554
+ `missing-theme-component-${req.toLowerCase()}`,
555
+ `theme must implement the "${req}" component under src/runtime/components/`,
556
+ )
557
+ }
558
+ }
559
+ }
501
560
  } else if (kind === 'app' && !hasKitTailwind) {
502
561
  issue(
503
562
  'warning',
504
563
  'tailwind-path',
505
- 'src/module.ts should call registerTailwindPath from @owdproject/kit-primevue/kit/registerTailwindPath for Vue components',
564
+ 'src/module.ts should call registerTailwindPath from @owdproject/kit-tailwind/kit/registerTailwindPath for Vue components',
506
565
  )
507
566
  }
508
567
 
@@ -653,9 +712,24 @@ export function validateOwdModule(packageDir, options = {}) {
653
712
  }
654
713
 
655
714
  if (kind === 'app') {
715
+ const appDeps = /** @type {Record<string, string>} */ (pkg.dependencies ?? {})
716
+ if (appDeps[DESKTOP_KIT_PRIMEVUE_PACKAGE]) {
717
+ issue(
718
+ 'warning',
719
+ 'app-kit-primevue-dep',
720
+ 'remove @owdproject/kit-primevue from app dependencies — PrimeVue comes from the theme at runtime; apps only need @owdproject/kit-tailwind',
721
+ )
722
+ }
723
+
656
724
  const appConfigPath = join(dir, 'src', 'runtime', 'app.config.ts')
657
725
  if (!existsSync(appConfigPath)) {
658
726
  issue('error', 'app-config', 'src/runtime/app.config.ts is required for apps')
727
+ } else if (/position\s*:\s*\{[\s\S]*?\bx\s*:/m.test(readFileSync(appConfigPath, 'utf8'))) {
728
+ issue(
729
+ 'warning',
730
+ 'app-config-position',
731
+ 'omit windows.*.position in app.config.ts for pre-open centering; set explicit size.width and size.height',
732
+ )
659
733
  }
660
734
 
661
735
  const pluginPath = join(dir, 'src', 'runtime', 'plugin.ts')
@@ -705,10 +779,46 @@ export function validateOwdModule(packageDir, options = {}) {
705
779
  'launch-plugin',
706
780
  'optional: add playground/app/plugins/launch-*.client.ts for dev auto-open',
707
781
  )
782
+ } else {
783
+ for (const launchFile of launchPlugins) {
784
+ const launchPath = join(playgroundDir, 'app', 'plugins', launchFile)
785
+ const launchSrc = readFileSync(launchPath, 'utf8')
786
+ if (/if\s*\(\s*!?\s*import\.meta\.dev\s*\)/.test(launchSrc)) {
787
+ issue(
788
+ 'warning',
789
+ 'launch-dev-guard',
790
+ `${launchFile}: remove import.meta.dev guard — GitHub Pages static generate needs the same auto-start path`,
791
+ )
792
+ }
793
+ if (!launchSrc.includes('autoStartPlaygroundApps')) {
794
+ issue(
795
+ 'warning',
796
+ 'launch-auto-start',
797
+ `${launchFile}: prefer autoStartPlaygroundApps(nuxtApp, [...]) from @owdproject/core (handles workArea + stale windows)`,
798
+ )
799
+ }
800
+ }
708
801
  }
709
802
  }
710
803
 
711
- if (!existsSync(join(dir, '.github', 'workflows', 'pages.yml'))) {
804
+ const pagesWorkflowPath = join(dir, '.github', 'workflows', 'pages.yml')
805
+ if (existsSync(pagesWorkflowPath)) {
806
+ const pagesWorkflow = readFileSync(pagesWorkflowPath, 'utf8')
807
+ if (/\bnypm\b/.test(pagesWorkflow)) {
808
+ issue(
809
+ 'warning',
810
+ 'pages-nypm',
811
+ 'pages.yml uses nypm in the app repo alone — breaks workspace:*; use client checkout overlay (see PLAYGROUND.md)',
812
+ )
813
+ }
814
+ if (!existsSync(join(dir, '.github', 'workflows', 'ci.yml'))) {
815
+ issue(
816
+ 'warning',
817
+ 'ci-workflow',
818
+ 'add .github/workflows/ci.yml with validate-and-build (dev:prepare → validate → dev:generate)',
819
+ )
820
+ }
821
+ } else {
712
822
  issue(
713
823
  'warning',
714
824
  'pages-workflow',
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
3
  import { execSync } from 'node:child_process'
4
4
  import {
@@ -220,3 +220,70 @@ export function getPackagesRequiredByTheme(workspaceRoot, themeName) {
220
220
  }
221
221
  }
222
222
 
223
+ /**
224
+ * Scans all package.json files in the workspace (including desktop/)
225
+ * and returns any @owdproject/ dependencies that use the 'workspace:' protocol
226
+ * but are not present locally on disk.
227
+ * @param {string} workspaceRoot
228
+ * @returns {string[]}
229
+ */
230
+ export function findMissingWorkspaceDependencies(workspaceRoot) {
231
+ const missing = new Set()
232
+ const localPackages = new Set()
233
+ const pkgPaths = []
234
+
235
+ // 1. Scan KINDS subdirectories for local packages
236
+ for (const kind of Object.values(KINDS)) {
237
+ const dir = join(workspaceRoot, kind.workspaceDir)
238
+ if (!existsSync(dir)) continue
239
+ try {
240
+ const entries = readdirSync(dir, { withFileTypes: true })
241
+ for (const entry of entries) {
242
+ if (entry.isDirectory()) {
243
+ const pkgJsonPath = join(dir, entry.name, 'package.json')
244
+ if (existsSync(pkgJsonPath)) {
245
+ const pkgPath = join(dir, entry.name)
246
+ pkgPaths.push(pkgPath)
247
+ try {
248
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
249
+ if (pkgJson.name) {
250
+ localPackages.add(pkgJson.name)
251
+ }
252
+ } catch {}
253
+ }
254
+ }
255
+ }
256
+ } catch {}
257
+ }
258
+
259
+ // 2. Include the desktop app itself
260
+ const desktopDir = join(workspaceRoot, 'desktop')
261
+ if (existsSync(join(desktopDir, 'package.json'))) {
262
+ pkgPaths.push(desktopDir)
263
+ try {
264
+ const pkgJson = JSON.parse(readFileSync(join(desktopDir, 'package.json'), 'utf8'))
265
+ if (pkgJson.name) {
266
+ localPackages.add(pkgJson.name)
267
+ }
268
+ } catch {}
269
+ }
270
+
271
+ // 3. Find missing workspace:* dependencies
272
+ for (const pkgPath of pkgPaths) {
273
+ try {
274
+ const pkgJson = JSON.parse(readFileSync(join(pkgPath, 'package.json'), 'utf8'))
275
+ const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies }
276
+ for (const [depName, version] of Object.entries(deps)) {
277
+ if (depName.startsWith(SCOPE) && typeof version === 'string' && version.startsWith('workspace:')) {
278
+ if (!localPackages.has(depName)) {
279
+ missing.add(depName)
280
+ }
281
+ }
282
+ }
283
+ } catch {}
284
+ }
285
+
286
+ return Array.from(missing)
287
+ }
288
+
289
+
@@ -127,6 +127,7 @@ func getBridgePath(root string) string {
127
127
  // ReadWorkspaceContext runs the node bridge with --read
128
128
  func ReadWorkspaceContext(root string) (*WorkspaceContext, error) {
129
129
  cmd := exec.Command("node", getBridgePath(root), "--read")
130
+ cmd.Dir = root
130
131
  var stdout, stderr bytes.Buffer
131
132
  cmd.Stdout = &stdout
132
133
  cmd.Stderr = &stderr
@@ -150,6 +151,7 @@ func ReadCatalog(root string, force bool) (*CatalogResponse, error) {
150
151
  args = append(args, "--force")
151
152
  }
152
153
  cmd := exec.Command("node", args...)
154
+ cmd.Dir = root
153
155
  var stdout, stderr bytes.Buffer
154
156
  cmd.Stdout = &stdout
155
157
  cmd.Stderr = &stderr
@@ -166,9 +168,27 @@ func ReadCatalog(root string, force bool) (*CatalogResponse, error) {
166
168
  return &cat, nil
167
169
  }
168
170
 
171
+ // WriteConfigOnly writes desktop.config.ts (theme/apps/modules only).
172
+ func WriteConfigOnly(root string, config *Config) error {
173
+ return writeBridge(root, &WritePayload{Config: config})
174
+ }
175
+
176
+ // WritePackageJsonDeps updates desktop/package.json dependencies.
177
+ func WritePackageJsonDeps(root string, toAdd map[string]string, toRemove []string) error {
178
+ return writeBridge(root, &WritePayload{
179
+ DepsToAdd: toAdd,
180
+ DepsToRemove: toRemove,
181
+ })
182
+ }
183
+
169
184
  // WriteChanges feeds the write payload to the node bridge via stdin
170
185
  func WriteChanges(root string, payload *WritePayload) error {
186
+ return writeBridge(root, payload)
187
+ }
188
+
189
+ func writeBridge(root string, payload *WritePayload) error {
171
190
  cmd := exec.Command("node", getBridgePath(root), "--write")
191
+ cmd.Dir = root
172
192
  var stdout, stderr bytes.Buffer
173
193
  cmd.Stdout = &stdout
174
194
  cmd.Stderr = &stderr
package/go/desktop-go CHANGED
Binary file
package/go/go.mod CHANGED
@@ -1,26 +1,33 @@
1
1
  module owd-cli
2
2
 
3
- go 1.21
3
+ go 1.24.2
4
4
 
5
5
  require (
6
- github.com/charmbracelet/bubbletea v1.1.1
7
- github.com/charmbracelet/lipgloss v0.13.0
6
+ github.com/charmbracelet/bubbles v1.0.0
7
+ github.com/charmbracelet/bubbletea v1.3.10
8
+ github.com/charmbracelet/lipgloss v1.1.0
8
9
  )
9
10
 
10
11
  require (
12
+ github.com/atotto/clipboard v0.1.4 // indirect
11
13
  github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
12
- github.com/charmbracelet/x/ansi v0.2.3 // indirect
13
- github.com/charmbracelet/x/term v0.2.0 // indirect
14
+ github.com/charmbracelet/colorprofile v0.4.1 // indirect
15
+ github.com/charmbracelet/x/ansi v0.11.6 // indirect
16
+ github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
17
+ github.com/charmbracelet/x/term v0.2.2 // indirect
18
+ github.com/clipperhouse/displaywidth v0.9.0 // indirect
19
+ github.com/clipperhouse/stringish v0.1.1 // indirect
20
+ github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
14
21
  github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
15
- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
22
+ github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
16
23
  github.com/mattn/go-isatty v0.0.20 // indirect
17
24
  github.com/mattn/go-localereader v0.0.1 // indirect
18
- github.com/mattn/go-runewidth v0.0.15 // indirect
25
+ github.com/mattn/go-runewidth v0.0.19 // indirect
19
26
  github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
20
27
  github.com/muesli/cancelreader v0.2.2 // indirect
21
- github.com/muesli/termenv v0.15.2 // indirect
28
+ github.com/muesli/termenv v0.16.0 // indirect
22
29
  github.com/rivo/uniseg v0.4.7 // indirect
23
- golang.org/x/sync v0.8.0 // indirect
24
- golang.org/x/sys v0.24.0 // indirect
30
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
31
+ golang.org/x/sys v0.38.0 // indirect
25
32
  golang.org/x/text v0.3.8 // indirect
26
33
  )