@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.
- package/README.md +7 -7
- package/bin/bridge.js +4 -2
- package/bin/cli.js +63 -5
- package/bin/cp.js +42 -23
- package/bin/lib/catalog.js +3 -0
- package/bin/lib/config.js +51 -8
- package/bin/lib/cpFormat.js +2 -2
- package/bin/lib/create.js +246 -0
- package/bin/lib/desktopConfig.js +48 -8
- package/bin/lib/generateTemplate.js +2 -1
- package/bin/lib/install.js +17 -21
- package/bin/lib/installWizardFlow.js +90 -24
- package/bin/lib/packageSources.js +10 -0
- package/bin/lib/status.js +2 -0
- package/bin/lib/validateModule.js +112 -2
- package/bin/lib/workspace.js +68 -1
- package/go/bridge/bridge.go +20 -0
- package/go/desktop-go +0 -0
- package/go/go.mod +17 -10
- package/go/go.sum +34 -19
- package/go/main.go +7 -3
- package/go/owd-cli +0 -0
- package/go/tui/engine.go +472 -0
- package/go/tui/engine_test.go +40 -0
- package/go/tui/handlers.go +604 -0
- package/go/tui/helpers.go +916 -0
- package/go/tui/helpers_test.go +97 -0
- package/go/tui/modals.go +423 -0
- package/go/tui/resolver.go +377 -0
- package/go/tui/resolver_test.go +86 -0
- package/go/tui/styles.go +153 -0
- package/go/tui/tasks.go +850 -0
- package/go/tui/tui.go +451 -2596
- package/go/tui/tui_test.go +69 -0
- package/go/tui/types.go +230 -0
- package/go/tui/view.go +812 -0
- package/go/tui/workqueue.go +179 -0
- package/go/tui/workqueue_test.go +90 -0
- package/package.json +2 -2
package/bin/lib/desktopConfig.js
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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, '
|
|
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) {
|
package/bin/lib/install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
ctx.
|
|
338
|
-
ctx.
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
ctx.
|
|
696
|
-
ctx.
|
|
697
|
-
|
|
698
|
-
|
|
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
|
@@ -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-
|
|
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
|
-
|
|
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',
|
package/bin/lib/workspace.js
CHANGED
|
@@ -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
|
+
|
package/go/bridge/bridge.go
CHANGED
|
@@ -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.
|
|
3
|
+
go 1.24.2
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
-
github.com/charmbracelet/
|
|
7
|
-
github.com/charmbracelet/
|
|
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/
|
|
13
|
-
github.com/charmbracelet/x/
|
|
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.
|
|
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.
|
|
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.
|
|
28
|
+
github.com/muesli/termenv v0.16.0 // indirect
|
|
22
29
|
github.com/rivo/uniseg v0.4.7 // indirect
|
|
23
|
-
|
|
24
|
-
golang.org/x/sys v0.
|
|
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
|
)
|