@owdproject/core 3.3.1 → 4.0.0-alpha.3

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.
Files changed (93) hide show
  1. package/README.md +91 -59
  2. package/dist/dom/index.d.mts +65 -0
  3. package/dist/dom/index.d.ts +65 -0
  4. package/dist/dom/index.mjs +224 -0
  5. package/dist/events/index.d.mts +70 -0
  6. package/dist/events/index.d.ts +70 -0
  7. package/dist/events/index.mjs +3 -0
  8. package/dist/index.d.mts +73 -0
  9. package/dist/index.d.ts +73 -0
  10. package/dist/index.mjs +115 -0
  11. package/dist/layout/index.d.mts +41 -0
  12. package/dist/layout/index.d.ts +41 -0
  13. package/dist/layout/index.mjs +64 -0
  14. package/dist/machines/index.d.mts +31 -0
  15. package/dist/machines/index.d.ts +31 -0
  16. package/dist/machines/index.mjs +2 -0
  17. package/dist/managers/index.d.mts +117 -0
  18. package/dist/managers/index.d.ts +117 -0
  19. package/dist/managers/index.mjs +6 -0
  20. package/dist/session/index.d.mts +57 -0
  21. package/dist/session/index.d.ts +57 -0
  22. package/dist/session/index.mjs +65 -0
  23. package/dist/shared/core.B0B0KfVK.mjs +865 -0
  24. package/dist/shared/core.B1XxB289.mjs +239 -0
  25. package/dist/shared/core.B3mp7ztE.mjs +237 -0
  26. package/dist/shared/core.BOvFdEVQ.d.ts +114 -0
  27. package/dist/shared/core.CsDFgGnX.mjs +40 -0
  28. package/dist/shared/core.Di1eFcr_.d.mts +114 -0
  29. package/dist/shared/core.Pd_Ogt4g.mjs +101 -0
  30. package/dist/shared/core.b3Xldzyu.mjs +29 -0
  31. package/dist/types/index.d.mts +266 -0
  32. package/dist/types/index.d.ts +266 -0
  33. package/dist/types/index.mjs +1 -0
  34. package/package.json +60 -87
  35. package/bin/cli.js +0 -395
  36. package/bin/desktop.js +0 -5
  37. package/bin/lib/catalog.js +0 -293
  38. package/bin/lib/config.js +0 -109
  39. package/bin/lib/desktopConfig.js +0 -56
  40. package/bin/lib/generateTemplate.js +0 -389
  41. package/bin/lib/install.js +0 -408
  42. package/bin/lib/migrateModulePlayground.config.mjs +0 -164
  43. package/bin/lib/migrateModulePlayground.mjs +0 -313
  44. package/bin/lib/npmVersions.js +0 -124
  45. package/bin/lib/playgroundContext.js +0 -188
  46. package/bin/lib/scaffold.js +0 -197
  47. package/bin/lib/status.js +0 -289
  48. package/bin/lib/validateModule.js +0 -697
  49. package/bin/lib/workspace.js +0 -182
  50. package/bin/owd.js +0 -5
  51. package/bin/tui.js +0 -1379
  52. package/dist/module.d.mts +0 -13
  53. package/dist/module.json +0 -9
  54. package/dist/module.mjs +0 -268
  55. package/dist/types.d.mts +0 -7
  56. package/index.ts +0 -5
  57. package/module.ts +0 -223
  58. package/runtime/components/Desktop/Application/DesktopApplicationRender.vue +0 -20
  59. package/runtime/components/Desktop/Application/DesktopApplicationWindowsRender.vue +0 -38
  60. package/runtime/components/Desktop/Background/DesktopBackground.vue +0 -16
  61. package/runtime/components/Desktop/Core/DesktopCore.vue +0 -90
  62. package/runtime/components/Desktop/Time/DesktopTime.vue +0 -56
  63. package/runtime/components/Desktop/Window/DesktopWindow.vue +0 -204
  64. package/runtime/components/Desktop/Window/DesktopWindowContent.vue +0 -28
  65. package/runtime/components/Desktop/Window/DesktopWindowNav.vue +0 -66
  66. package/runtime/composables/useApplicationEntries.ts +0 -51
  67. package/runtime/composables/useApplicationManager.ts +0 -285
  68. package/runtime/composables/useDesktopManager.ts +0 -49
  69. package/runtime/composables/useTerminalManager.ts +0 -66
  70. package/runtime/composables/useWorkspaceManager.ts +0 -41
  71. package/runtime/internal/controllers/ApplicationController.ts +0 -345
  72. package/runtime/internal/controllers/WindowController.ts +0 -462
  73. package/runtime/plugins/02.desktop-register-desktop-apps.client.ts +0 -17
  74. package/runtime/plugins/resize.client.ts +0 -6
  75. package/runtime/stores/storeApplicationMeta.ts +0 -59
  76. package/runtime/stores/storeApplicationWindows.ts +0 -20
  77. package/runtime/stores/storeDesktop.ts +0 -111
  78. package/runtime/stores/storeDesktopDefaultApps.ts +0 -53
  79. package/runtime/stores/storeDesktopVolume.ts +0 -18
  80. package/runtime/stores/storeDesktopWindow.ts +0 -20
  81. package/runtime/stores/storeDesktopWorkspace.ts +0 -75
  82. package/runtime/utils/captureElementToCanvas.ts +0 -21
  83. package/runtime/utils/explorerEntryPath.ts +0 -10
  84. package/runtime/utils/resolveDesktopConfigPath.ts +0 -52
  85. package/runtime/utils/splitDesktopUserConfig.ts +0 -85
  86. package/runtime/utils/utilApp.ts +0 -37
  87. package/runtime/utils/utilCommon.ts +0 -26
  88. package/runtime/utils/utilDebug.ts +0 -30
  89. package/runtime/utils/utilDesktop.ts +0 -35
  90. package/runtime/utils/utilTerminal.ts +0 -6
  91. package/runtime/utils/utilWindow.ts +0 -15
  92. package/runtime/utils/validateDesktopUserConfig.ts +0 -44
  93. package/types/index.d.ts +0 -420
package/bin/cli.js DELETED
@@ -1,395 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { runDevForeground } from './lib/status.js'
3
- import { existsSync } from 'node:fs'
4
- import { join } from 'node:path'
5
- import getopts from 'getopts'
6
- import {
7
- resolveSource,
8
- printPlan,
9
- printNextSteps,
10
- runWorkspaceInstall,
11
- runNpmInstall,
12
- } from './lib/install.js'
13
- import { findWorkspaceRoot, inferKind } from './lib/workspace.js'
14
- import { resolveDevTarget } from './lib/playgroundContext.js'
15
- import { scaffoldProject } from './lib/scaffold.js'
16
-
17
- const SCOPE = '@owdproject/'
18
-
19
- /** @type {Record<string, { configKey: string, workspaceDir: string, nx: string, label: string }>} */
20
- const KINDS = {
21
- app: {
22
- label: 'app',
23
- configKey: 'apps',
24
- workspaceDir: 'apps',
25
- nx: 'desktop:install-app',
26
- },
27
- module: {
28
- label: 'module',
29
- configKey: 'modules',
30
- workspaceDir: 'packages',
31
- nx: 'desktop:install-module',
32
- },
33
- theme: {
34
- label: 'theme',
35
- configKey: 'theme',
36
- workspaceDir: 'themes',
37
- nx: 'desktop:install-theme',
38
- },
39
- }
40
-
41
- const LEGACY_COMMANDS = {
42
- 'install-app': 'app',
43
- 'install-module': 'module',
44
- 'install-theme': 'theme',
45
- }
46
-
47
- /** @type {string} */
48
- let commandName = 'desktop'
49
-
50
- function buildHelp(name) {
51
- return `
52
- ${name} — add apps, modules, and themes to your Open Web Desktop
53
-
54
- USAGE
55
- ${name} Open the control panel (interactive TUI)
56
- ${name} dev [--playground] Start dev server (auto-detects module playground)
57
-
58
- CONTROL PANEL (TUI)
59
- m Open command menu (all actions)
60
- s Start the Nuxt dev server
61
- x Stop the Nuxt dev server
62
- R Reboot the Nuxt dev server (stop + start)
63
- w Save catalog/theme changes to desktop.config.ts
64
- i Open in-app docs (when module-docs is installed)
65
- d Toggle User (npm) / Dev (git clone) install mode
66
- g Settings (GitHub username for fork clones)
67
- r Refresh package list from GitHub
68
- b Run pnpm run generate
69
- q / Esc Quit
70
- ${name} init [dir] Create a new OWD project (then opens the control panel)
71
- ${name} add <package> [options]
72
- ${name} add <kind> <name> [options]
73
- ${name} validate [path...] Check Nuxt module + playground layout
74
- ${name} template [--dry-run] [--check] Regenerate client/template/ (maintainers)
75
-
76
- TEMPLATE (monorepo maintainers)
77
- ${name} template Write template/ from blueprint + desktop/ + latest npm versions
78
- ${name} template --dry-run Show planned @owdproject/* and starter versions
79
- ${name} template --check Fail if committed template/ differs (CI)
80
-
81
- VALIDATE
82
- ${name} validate Validate cwd package, or all apps/themes/modules at repo root
83
- ${name} validate . Validate explicit package directory
84
- ${name} validate apps Validate every @owdproject/* module under apps/
85
- --json Machine-readable output
86
- --strict Treat warnings as failures
87
- --smoke Run dev:prepare + nuxt build playground (slow, CI)
88
-
89
- INSTALL MODES
90
- User (default) npm registry — for end users and generated projects
91
- Dev clone into apps/, packages/, or themes/ in the monorepo
92
-
93
- EXAMPLES
94
- pnpm ${name} # control panel
95
- pnpm ${name} dev # monorepo desktop (from repo root)
96
- cd apps/app-about && pnpm ${name} dev # app-about playground
97
- pnpm ${name} dev --playground # force playground when cwd is in a module
98
- ${name} init my-desktop # scaffold + pnpm install + control panel
99
- ${name} add app-todo --npm
100
- ${name} add app-todo --dev
101
- ${name} add module-persistence --from dxlliv
102
-
103
- KINDS (optional — inferred from the package name)
104
- app app-* → apps/
105
- module module-* → packages/ (kit-* and *-template are not listed in the control panel)
106
- theme theme-* → themes/
107
-
108
- OPTIONS
109
- --playground Start the module playground dev server (when cwd is inside an @owdproject/* package with playground/)
110
- --from <source> Git source (user, user/repo, or URL)
111
- --branch <name> Git branch to clone
112
- --npm Install from npm (default when --from is omitted)
113
- --dev, --workspace Clone from github.com/owdproject/<package>
114
- --dry-run Print the plan without changing anything
115
- -h, --help Show this help
116
-
117
- --from <source>
118
- (omit) npm registry (default)
119
- npm npm registry
120
- owdproject Clone official repo (monorepo only)
121
- <github-user> Clone github.com/<user>/<package> (your fork)
122
- <user>/<repo> Explicit GitHub repo
123
- <git-url> Full clone URL
124
-
125
- LEGACY (still supported)
126
- ${name} install-app @owdproject/app-todo
127
- → prefer: ${name} add app-todo
128
- `
129
- }
130
-
131
- async function openControlPanel(name) {
132
- const { runTui } = await import('./tui.js')
133
- await runTui(name)
134
- }
135
-
136
- function fail(message, hint) {
137
- console.error(`\n✗ ${message}`)
138
- if (hint) console.error(` ${hint}`)
139
- console.error(`\nRun \`${commandName} --help\` for usage.\n`)
140
- process.exit(1)
141
- }
142
-
143
- function warn(message) {
144
- console.warn(`\n⚠ ${message}\n`)
145
- }
146
-
147
- /**
148
- * @param {string} input
149
- * @param {string | undefined} explicitKind
150
- */
151
- function normalizePackage(input, explicitKind) {
152
- let raw = input.trim()
153
- if (raw.startsWith(SCOPE)) raw = raw.slice(SCOPE.length)
154
-
155
- if (!raw) {
156
- fail('Package name is required.', `Example: ${commandName} add app-todo`)
157
- }
158
-
159
- const kind = explicitKind ?? inferKind(raw)
160
-
161
- if (explicitKind) {
162
- const prefixes = {
163
- app: 'app-',
164
- theme: 'theme-',
165
- module: ['module-', 'kit-'],
166
- }
167
- const expected = prefixes[explicitKind]
168
- const list = Array.isArray(expected) ? expected : [expected]
169
- if (!list.some((p) => raw.startsWith(p))) {
170
- raw = `${list[0]}${raw}`
171
- }
172
- }
173
-
174
- return { pkgName: `${SCOPE}${raw}`, shortName: raw, kind }
175
- }
176
-
177
- function mapLegacyFlags(parsed) {
178
- const { dev, workspace, fork, repo, branch, from, npm } = parsed
179
- let mappedFrom = from
180
- let mappedNpm = npm
181
- const forceDev = dev || workspace
182
-
183
- if (fork || repo) {
184
- warn('`--fork` and `--repo` are deprecated — use `--from` instead.')
185
- mappedFrom = repo || fork
186
- }
187
- if (forceDev && !mappedFrom && !mappedNpm) {
188
- mappedFrom = 'owdproject'
189
- }
190
- if (mappedNpm) mappedFrom = 'npm'
191
-
192
- return { from: mappedFrom, branch, npm: mappedNpm, dev: forceDev }
193
- }
194
-
195
- function parseAddArgs(positionals) {
196
- const kindNames = Object.keys(KINDS)
197
- if (positionals.length === 0) {
198
- fail('Missing package name.', `Example: ${commandName} add app-todo`)
199
- }
200
-
201
- if (kindNames.includes(positionals[0])) {
202
- const kind = positionals[0]
203
- const name = positionals[1]
204
- if (!name) {
205
- fail(`Missing name for kind "${kind}".`, `Example: ${commandName} add ${kind} todo`)
206
- }
207
- return normalizePackage(name, kind)
208
- }
209
-
210
- return normalizePackage(positionals[0])
211
- }
212
-
213
- function parseLegacyInstall(cmd, positionals) {
214
- const kind = LEGACY_COMMANDS[cmd]
215
- const raw = positionals[0]
216
- if (!raw) {
217
- fail('Missing package name.', `Example: ${commandName} ${cmd} ${SCOPE}app-todo`)
218
- }
219
- warn(`\`${cmd}\` is deprecated — use \`${commandName} add …\` instead.`)
220
- return normalizePackage(raw, kind)
221
- }
222
-
223
- /**
224
- * @param {string} name CLI binary name (`desktop` or deprecated `owd`)
225
- * @param {string[]} argv Arguments (without node and script path)
226
- * @param {{ deprecated?: boolean }} [options]
227
- */
228
- export async function runCli(name, argv, options = {}) {
229
- commandName = name
230
-
231
- if (options.deprecated) {
232
- warn('The `owd` command is deprecated. Use `desktop` instead.')
233
- }
234
-
235
- const parsed = getopts(argv, {
236
- alias: { h: 'help', b: 'branch' },
237
- boolean: ['help', 'npm', 'dry-run', 'dev', 'workspace', 'playground', 'json', 'strict', 'smoke', 'check'],
238
- string: ['from', 'branch', 'fork', 'repo'],
239
- })
240
-
241
- const { _, help, npm: npmFlag } = parsed
242
- const dryRun = parsed['dry-run'] === true
243
- const { from, branch, npm: npmFromLegacy, dev: devFlag } = mapLegacyFlags(parsed)
244
- const useNpm = npmFlag || npmFromLegacy || (!from && !devFlag)
245
-
246
- if (help || _[0] === 'help') {
247
- console.log(buildHelp(name))
248
- process.exit(0)
249
- }
250
-
251
- const cmd = _[0]
252
-
253
- if (cmd === 'init') {
254
- const projectDir = (_[1] || 'owd-client').trim()
255
- const cwd = process.cwd()
256
-
257
- if (findWorkspaceRoot(cwd)) {
258
- fail(
259
- 'Already inside an OWD workspace.',
260
- `Use \`${name}\` to manage this project, or run init from a parent directory.`,
261
- )
262
- }
263
-
264
- try {
265
- await scaffoldProject({ dir: projectDir, cwd, commandName: name })
266
- } catch (error) {
267
- fail(error.message ?? String(error))
268
- }
269
- return
270
- }
271
-
272
- const workspaceRoot = findWorkspaceRoot()
273
-
274
- if (cmd === 'dev') {
275
- if (!workspaceRoot) {
276
- fail(
277
- 'Not inside an OWD workspace.',
278
- 'Run from the client repo, or try: desktop · desktop add <package>',
279
- )
280
- }
281
- const forcePlayground = parsed.playground === true
282
- const devTarget = resolveDevTarget(process.cwd(), workspaceRoot, { forcePlayground })
283
- if (forcePlayground && !devTarget) {
284
- fail(
285
- 'No @owdproject module with playground/desktop.config.ts found from this directory.',
286
- 'Run from an app/theme/module package (e.g. apps/app-about), or omit --playground for the monorepo desktop.',
287
- )
288
- }
289
- runDevForeground(devTarget)
290
- return
291
- }
292
-
293
- if (cmd === 'validate') {
294
- const { runValidateCli, formatValidationReport } = await import('./lib/validateModule.js')
295
- const strict = parsed.strict === true
296
- const json = parsed.json === true
297
- const smoke = parsed.smoke === true
298
- const paths = _.slice(1).map(String)
299
- const { exitCode, results } = runValidateCli(paths, {
300
- workspaceRoot,
301
- json,
302
- strict,
303
- smoke,
304
- })
305
- for (const result of results) {
306
- console.log(formatValidationReport(result, { json }))
307
- if (!json) console.log('')
308
- }
309
- process.exit(exitCode)
310
- }
311
-
312
- if (cmd === 'template') {
313
- if (!workspaceRoot) {
314
- fail(
315
- 'Not inside an OWD workspace.',
316
- `Run from the client monorepo root to regenerate template/.`,
317
- )
318
- }
319
- const { runGenerateTemplateCli } = await import('./lib/generateTemplate.js')
320
- await runGenerateTemplateCli({
321
- workspaceRoot,
322
- dryRun: parsed['dry-run'] === true,
323
- check: parsed.check === true,
324
- })
325
- return
326
- }
327
-
328
- if (!cmd) {
329
- if (!workspaceRoot) {
330
- fail(
331
- 'Not inside an OWD workspace.',
332
- 'Run from the client repo, or try: desktop · desktop add <package>',
333
- )
334
- }
335
- await openControlPanel(name)
336
- return
337
- }
338
-
339
- if (cmd === 'ui') {
340
- fail('The `ui` subcommand was removed.', `Use \`${name}\` to open the control panel.`)
341
- }
342
-
343
- if (!workspaceRoot) {
344
- fail(
345
- 'Not inside an OWD workspace.',
346
- 'Run from the client repo (needs pnpm-workspace.yaml + nx.json), or use a generated project with the same layout.',
347
- )
348
- }
349
-
350
- let pkgInfo
351
-
352
- if (cmd === 'add') {
353
- pkgInfo = parseAddArgs(_.slice(1))
354
- } else if (LEGACY_COMMANDS[cmd]) {
355
- pkgInfo = parseLegacyInstall(cmd, _.slice(1))
356
- } else {
357
- fail(`Unknown command: ${cmd}`, `Use \`${name}\`, \`${name} dev\`, or \`${name} add <package>\`.`)
358
- }
359
-
360
- const { pkgName, shortName, kind } = pkgInfo
361
- const inMonorepo = Boolean(workspaceRoot)
362
- const source = resolveSource(useNpm ? 'npm' : from, shortName, inMonorepo, {
363
- npm: useNpm,
364
- dev: devFlag,
365
- })
366
-
367
- if (source.mode === 'npm') {
368
- if (dryRun) {
369
- printPlan({
370
- pkgName,
371
- kind,
372
- targetDir: '(npm → node_modules)',
373
- source,
374
- branch: null,
375
- dryRun: true,
376
- })
377
- process.exit(0)
378
- }
379
- runNpmInstall(kind, pkgName, workspaceRoot)
380
- } else {
381
- try {
382
- await runWorkspaceInstall({
383
- pkgName,
384
- shortName,
385
- kind,
386
- workspaceRoot,
387
- source,
388
- branch,
389
- dryRun,
390
- })
391
- } catch (error) {
392
- fail(error.message ?? String(error))
393
- }
394
- }
395
- }
package/bin/desktop.js DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runCli } from './cli.js'
4
-
5
- await runCli('desktop', process.argv.slice(2))
@@ -1,293 +0,0 @@
1
- import {
2
- existsSync,
3
- readFileSync,
4
- readdirSync,
5
- mkdirSync,
6
- writeFileSync,
7
- statSync,
8
- } from 'node:fs'
9
- import { join } from 'node:path'
10
- import { execSync } from 'node:child_process'
11
- import {
12
- KINDS,
13
- SCOPE,
14
- fullName,
15
- shortName,
16
- inferKind,
17
- isInstallableDesktopModule,
18
- desktopMetaDir,
19
- } from './workspace.js'
20
- import { hasLocalWorkspaceSource } from './install.js'
21
-
22
- const CACHE_TTL_MS = 60 * 60 * 1000
23
-
24
- function cachePath(workspaceRoot) {
25
- return join(desktopMetaDir(workspaceRoot), 'catalog-cache.json')
26
- }
27
-
28
- function readCache(workspaceRoot) {
29
- const path = cachePath(workspaceRoot)
30
- if (!existsSync(path)) return null
31
- try {
32
- const data = JSON.parse(readFileSync(path, 'utf8'))
33
- if (Date.now() - data.fetchedAt > CACHE_TTL_MS) return null
34
- const ageMin = Math.round((Date.now() - data.fetchedAt) / 60_000)
35
- return { entries: data.entries, cacheAge: `${ageMin}m` }
36
- } catch {
37
- return null
38
- }
39
- }
40
-
41
- function formatCacheAge(fetchedAt) {
42
- const ageMin = Math.round((Date.now() - fetchedAt) / 60_000)
43
- if (ageMin < 1) return '<1m'
44
- if (ageMin < 60) return `${ageMin}m`
45
- return `${Math.round(ageMin / 60)}h`
46
- }
47
-
48
- function writeCache(workspaceRoot, entries) {
49
- mkdirSync(desktopMetaDir(workspaceRoot), { recursive: true })
50
- writeFileSync(
51
- cachePath(workspaceRoot),
52
- JSON.stringify({ fetchedAt: Date.now(), entries }, null, 2),
53
- )
54
- }
55
-
56
- async function fetchTopic(topic, org) {
57
- const q = encodeURIComponent(`topic:${topic} org:${org}`)
58
- const url = `https://api.github.com/search/repositories?q=${q}&sort=stars&per_page=100`
59
- const headers = {
60
- Accept: 'application/vnd.github+json',
61
- 'User-Agent': 'owd-desktop-cli',
62
- }
63
- if (process.env.GITHUB_TOKEN) {
64
- headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
65
- }
66
-
67
- const res = await fetch(url, { headers })
68
- if (!res.ok) throw new Error(`GitHub API ${res.status} for ${org}/${topic}`)
69
- const json = await res.json()
70
- return (json.items ?? []).map((repo) => ({
71
- shortName: repo.name,
72
- description: repo.description ?? '',
73
- stars: repo.stargazers_count ?? 0,
74
- htmlUrl: repo.html_url,
75
- org: repo.owner?.login ?? org,
76
- kind: inferKind(repo.name),
77
- }))
78
- }
79
-
80
- async function fetchUserRepos(user) {
81
- const url = `https://api.github.com/users/${encodeURIComponent(user)}/repos?per_page=100&sort=updated`
82
- const headers = {
83
- Accept: 'application/vnd.github+json',
84
- 'User-Agent': 'owd-desktop-cli',
85
- }
86
- if (process.env.GITHUB_TOKEN) {
87
- headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
88
- }
89
-
90
- const res = await fetch(url, { headers })
91
- if (!res.ok) return []
92
- const repos = await res.json()
93
- return repos
94
- .filter((repo) => /^(app|theme|module|kit)-/.test(repo.name))
95
- .map((repo) => ({
96
- shortName: repo.name,
97
- description: repo.description ?? '',
98
- stars: repo.stargazers_count ?? 0,
99
- htmlUrl: repo.html_url,
100
- org: repo.owner?.login ?? user,
101
- kind: inferKind(repo.name),
102
- }))
103
- }
104
-
105
- function scanLocal(workspaceRoot, kind) {
106
- const dir = join(workspaceRoot, KINDS[kind].workspaceDir)
107
- if (!existsSync(dir)) return []
108
-
109
- return readdirSync(dir, { withFileTypes: true })
110
- .filter((d) => d.isDirectory())
111
- .map((d) => {
112
- const pkgPath = join(dir, d.name, 'package.json')
113
- if (!existsSync(pkgPath)) return null
114
- try {
115
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
116
- if (!pkg.name?.startsWith(SCOPE)) return null
117
- let remote = null
118
- try {
119
- remote = execSync(`git -C ${join(dir, d.name)} remote get-url origin`, {
120
- encoding: 'utf8',
121
- stdio: ['ignore', 'pipe', 'ignore'],
122
- }).trim()
123
- } catch {
124
- /* no git */
125
- }
126
- return {
127
- shortName: shortName(pkg.name),
128
- description: pkg.description ?? '',
129
- stars: 0,
130
- htmlUrl: pkg.homepage ?? pkg.repository?.url ?? null,
131
- org: 'workspace',
132
- kind,
133
- localPath: join(KINDS[kind].workspaceDir, d.name),
134
- remote,
135
- version: pkg.version,
136
- }
137
- } catch {
138
- return null
139
- }
140
- })
141
- .filter(Boolean)
142
- }
143
-
144
- export async function loadCatalog(workspaceRoot, settings) {
145
- const cached = readCache(workspaceRoot)
146
- let remote = cached?.entries ?? null
147
- let cacheAge = cached?.cacheAge ?? null
148
-
149
- if (!remote) {
150
- remote = []
151
- const orgs = [...new Set([...(settings.githubOrgs ?? ['owdproject']), settings.githubUser].filter(Boolean))]
152
-
153
- for (const org of orgs) {
154
- for (const kind of Object.keys(KINDS)) {
155
- try {
156
- const items = await fetchTopic(KINDS[kind].topic, org)
157
- remote.push(...items)
158
- } catch {
159
- /* offline — use local only */
160
- }
161
- }
162
- }
163
-
164
- if (settings.githubUser && !orgs.includes(settings.githubUser)) {
165
- try {
166
- remote.push(...(await fetchUserRepos(settings.githubUser)))
167
- } catch {
168
- /* ignore */
169
- }
170
- }
171
-
172
- if (remote.length) {
173
- const at = Date.now()
174
- writeCache(workspaceRoot, remote)
175
- cacheAge = formatCacheAge(at)
176
- }
177
- }
178
-
179
- const byName = new Map()
180
-
181
- for (const item of remote) {
182
- byName.set(item.shortName, { ...item, sources: [item.org] })
183
- }
184
-
185
- for (const kind of Object.keys(KINDS)) {
186
- for (const item of scanLocal(workspaceRoot, kind)) {
187
- const prev = byName.get(item.shortName)
188
- if (prev) {
189
- byName.set(item.shortName, {
190
- ...prev,
191
- ...item,
192
- localPath: item.localPath,
193
- remote: item.remote ?? prev.htmlUrl,
194
- sources: [...new Set([...(prev.sources ?? []), 'workspace'])],
195
- })
196
- } else {
197
- byName.set(item.shortName, { ...item, sources: ['workspace'] })
198
- }
199
- }
200
- }
201
-
202
- const entries = [...byName.values()].map((entry) => ({
203
- ...entry,
204
- name: fullName(entry.shortName),
205
- kind: entry.kind ?? inferKind(entry.shortName),
206
- }))
207
-
208
- return {
209
- entries,
210
- cacheAge: cacheAge ?? 'live',
211
- }
212
- }
213
-
214
- export function filterCatalog(catalog, kind) {
215
- return catalog
216
- .filter((e) => e.kind === kind)
217
- .filter((e) => kind !== 'module' || isInstallableDesktopModule(e.name))
218
- .sort((a, b) => {
219
- if (a.org === 'workspace' && b.org !== 'workspace') return -1
220
- if (b.org === 'workspace' && a.org !== 'workspace') return 1
221
- return (b.stars ?? 0) - (a.stars ?? 0) || a.shortName.localeCompare(b.shortName)
222
- })
223
- }
224
-
225
- export function mergeInstalled(catalog, config, deps, workspaceRoot) {
226
- const installedApps = new Set(config.apps ?? [])
227
- const installedModules = new Set(config.modules ?? [])
228
- const activeTheme = config.theme
229
-
230
- return catalog.map((entry) => {
231
- let installed = false
232
- if (entry.kind === 'app') installed = installedApps.has(entry.name)
233
- else if (entry.kind === 'module') installed = installedModules.has(entry.name)
234
- else if (entry.kind === 'theme') installed = activeTheme === entry.name
235
-
236
- const localSource = workspaceRoot
237
- ? hasLocalWorkspaceSource(workspaceRoot, entry.name)
238
- : entry.org === 'workspace'
239
-
240
- return {
241
- ...entry,
242
- installed,
243
- localSource,
244
- inPackageJson: deps.includes(entry.name),
245
- activeTheme: entry.kind === 'theme' && activeTheme === entry.name,
246
- }
247
- })
248
- }
249
-
250
- export function readThemeMeta(workspaceRoot, themePkgName) {
251
- const short = shortName(themePkgName)
252
- const localDir = join(workspaceRoot, 'themes', short)
253
- const pkgPath = join(localDir, 'package.json')
254
-
255
- if (!existsSync(pkgPath)) {
256
- return {
257
- name: themePkgName,
258
- description: 'Theme package not in workspace',
259
- version: '—',
260
- author: '—',
261
- remote: null,
262
- license: '—',
263
- }
264
- }
265
-
266
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
267
- let remote = null
268
- try {
269
- remote = execSync(`git -C ${localDir} remote get-url origin`, {
270
- encoding: 'utf8',
271
- stdio: ['ignore', 'pipe', 'ignore'],
272
- }).trim()
273
- } catch {
274
- remote = pkg.repository?.url ?? pkg.homepage ?? null
275
- }
276
-
277
- const author =
278
- typeof pkg.author === 'string'
279
- ? pkg.author
280
- : pkg.author?.name
281
- ? `${pkg.author.name}${pkg.author.url ? ` · ${pkg.author.url}` : ''}`
282
- : '—'
283
-
284
- return {
285
- name: pkg.name,
286
- description: pkg.description ?? '',
287
- version: pkg.version ?? '—',
288
- author,
289
- license: pkg.license ?? 'MIT',
290
- remote,
291
- homepage: pkg.homepage ?? null,
292
- }
293
- }