@owdproject/cli 0.0.3 → 0.1.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/LICENSE +20 -0
- package/README.md +50 -16
- package/bin/bridge.js +127 -0
- package/bin/cli.js +433 -0
- package/bin/cp.js +3523 -0
- package/bin/desktop.js +5 -0
- package/bin/lib/catalog.js +501 -0
- package/bin/lib/config.js +130 -0
- package/bin/lib/cpAscii.js +39 -0
- package/bin/lib/cpFormat.js +343 -0
- package/bin/lib/desktopConfig.js +8 -0
- package/bin/lib/generateTemplate.js +426 -0
- package/bin/lib/install.js +525 -0
- package/bin/lib/installWizardFlow.js +726 -0
- package/bin/lib/migrateModulePlayground.config.mjs +187 -0
- package/bin/lib/migrateModulePlayground.mjs +318 -0
- package/bin/lib/npmVersions.js +124 -0
- package/bin/lib/packageSources.js +365 -0
- package/bin/lib/playgroundContext.js +188 -0
- package/bin/lib/scaffold.js +205 -0
- package/bin/lib/status.js +317 -0
- package/bin/lib/validateModule.js +868 -0
- package/bin/lib/workspace.js +222 -0
- package/bin/owd.js +2 -118
- package/go/bridge/bridge.go +200 -0
- package/go/desktop-go +0 -0
- package/go/go.mod +26 -0
- package/go/go.sum +37 -0
- package/go/main.go +30 -0
- package/go/tui/tui.go +3014 -0
- package/package.json +33 -22
- package/src/commands/create.js +0 -11
- package/src/commands/generate.js +0 -12
- package/src/commands/installApp.js +0 -9
- package/src/commands/installTheme.js +0 -9
- package/src/commands/start.js +0 -13
- package/src/utils/cloneRepo.js +0 -25
- package/src/utils/installPackage.js +0 -15
- package/src/utils/logging.js +0 -15
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { execSync } from 'node:child_process'
|
|
4
|
+
import {
|
|
5
|
+
resolveDesktopConfigPath,
|
|
6
|
+
desktopConfigWritePath,
|
|
7
|
+
} from './desktopConfig.js'
|
|
8
|
+
|
|
9
|
+
export const SCOPE = '@owdproject/'
|
|
10
|
+
|
|
11
|
+
/** Not loadable via desktop.config `modules` (core is the shell). */
|
|
12
|
+
export const DESKTOP_NON_INSTALLABLE = new Set([
|
|
13
|
+
'@owdproject/core',
|
|
14
|
+
'@owdproject/cli',
|
|
15
|
+
'@owdproject/nx',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
/** Installed documentation modules (current + deprecated package name). */
|
|
19
|
+
export const DOCS_MODULE_PACKAGES = [
|
|
20
|
+
'@owdproject/module-docs',
|
|
21
|
+
'@owdproject/docs',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export function hasDocsModuleInstalled(config, deps = {}) {
|
|
25
|
+
return DOCS_MODULE_PACKAGES.some(
|
|
26
|
+
(name) => config?.modules?.includes(name) || deps[name],
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function docsBasePathFromConfig(config) {
|
|
31
|
+
const base = config?.docs?.basePath
|
|
32
|
+
if (typeof base === 'string' && base.startsWith('/')) return base
|
|
33
|
+
return '/docs'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Theme-internal UI kits — pulled in by themes, not listed as desktop modules. */
|
|
37
|
+
export function isDesktopKitPackage(pkgName) {
|
|
38
|
+
const isScoped = pkgName.startsWith(SCOPE)
|
|
39
|
+
const short = isScoped ? pkgName.slice(SCOPE.length) : pkgName
|
|
40
|
+
if (!isScoped && !pkgName.startsWith('kit-')) return false
|
|
41
|
+
return short.startsWith('kit-')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Scaffold / blueprint packages — not loadable desktop modules. */
|
|
45
|
+
export function isDesktopTemplatePackage(pkgName) {
|
|
46
|
+
const isScoped = pkgName.startsWith(SCOPE)
|
|
47
|
+
const short = isScoped ? pkgName.slice(SCOPE.length) : pkgName
|
|
48
|
+
if (!isScoped && !pkgName.endsWith('-template')) return false
|
|
49
|
+
return short.endsWith('-template')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isInstallableDesktopModule(pkgName) {
|
|
53
|
+
const isScoped = pkgName.startsWith(SCOPE)
|
|
54
|
+
if (!isScoped && !pkgName.startsWith('module-')) return false
|
|
55
|
+
return (
|
|
56
|
+
!DESKTOP_NON_INSTALLABLE.has(pkgName) &&
|
|
57
|
+
!isDesktopKitPackage(pkgName) &&
|
|
58
|
+
!isDesktopTemplatePackage(pkgName)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const KINDS = {
|
|
63
|
+
app: {
|
|
64
|
+
label: 'Apps',
|
|
65
|
+
topic: 'owd-apps',
|
|
66
|
+
workspaceDir: 'apps',
|
|
67
|
+
configKey: 'apps',
|
|
68
|
+
nx: 'desktop:install-app',
|
|
69
|
+
},
|
|
70
|
+
module: {
|
|
71
|
+
label: 'Modules',
|
|
72
|
+
topic: 'owd-modules',
|
|
73
|
+
workspaceDir: 'packages',
|
|
74
|
+
configKey: 'modules',
|
|
75
|
+
nx: 'desktop:install-module',
|
|
76
|
+
},
|
|
77
|
+
theme: {
|
|
78
|
+
label: 'Themes',
|
|
79
|
+
topic: 'owd-themes',
|
|
80
|
+
workspaceDir: 'themes',
|
|
81
|
+
configKey: 'theme',
|
|
82
|
+
nx: 'desktop:install-theme',
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function findWorkspaceRoot(startDir = process.cwd()) {
|
|
87
|
+
let dir = startDir
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (
|
|
90
|
+
existsSync(join(dir, 'pnpm-workspace.yaml')) &&
|
|
91
|
+
existsSync(join(dir, 'nx.json'))
|
|
92
|
+
) {
|
|
93
|
+
return dir
|
|
94
|
+
}
|
|
95
|
+
const parent = dirname(dir)
|
|
96
|
+
if (parent === dir) return null
|
|
97
|
+
dir = parent
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function desktopMetaDir(workspaceRoot) {
|
|
102
|
+
return join(workspaceRoot, '.desktop')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function settingsPath(workspaceRoot) {
|
|
106
|
+
return join(desktopMetaDir(workspaceRoot), 'settings.json')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @typedef {'npm' | 'workspace'} InstallMode */
|
|
110
|
+
|
|
111
|
+
export function normalizeInstallMode(value) {
|
|
112
|
+
if (value === 'workspace' || value === 'dev') return 'workspace'
|
|
113
|
+
return 'npm'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function isWorkspaceInstallMode(settings) {
|
|
117
|
+
return normalizeInstallMode(settings?.installMode) === 'workspace'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @typedef {'updated' | 'name' | 'stars' | 'installed'} CatalogSortMode */
|
|
121
|
+
|
|
122
|
+
export function normalizeCatalogSort(value) {
|
|
123
|
+
const modes = ['updated', 'name', 'stars', 'installed']
|
|
124
|
+
return modes.includes(value) ? /** @type {CatalogSortMode} */ (value) : 'updated'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function loadSettings(workspaceRoot, metaDir = null) {
|
|
128
|
+
const defaults = {
|
|
129
|
+
devPort: 3000,
|
|
130
|
+
githubUser: null,
|
|
131
|
+
githubOrgs: ['owdproject'],
|
|
132
|
+
installMode: /** @type {InstallMode} */ ('npm'),
|
|
133
|
+
catalogSort: /** @type {CatalogSortMode} */ ('updated'),
|
|
134
|
+
catalogNewDays: 14,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const path = metaDir ? join(metaDir, 'settings.json') : settingsPath(workspaceRoot)
|
|
139
|
+
if (!existsSync(path)) {
|
|
140
|
+
return { ...defaults, ...detectGithubUser(), installMode: 'npm' }
|
|
141
|
+
}
|
|
142
|
+
const merged = { ...defaults, ...detectGithubUser(), ...JSON.parse(readFileSync(path, 'utf8')) }
|
|
143
|
+
merged.installMode = normalizeInstallMode(merged.installMode)
|
|
144
|
+
merged.catalogSort = normalizeCatalogSort(merged.catalogSort)
|
|
145
|
+
merged.catalogNewDays =
|
|
146
|
+
typeof merged.catalogNewDays === 'number' && merged.catalogNewDays > 0
|
|
147
|
+
? merged.catalogNewDays
|
|
148
|
+
: 14
|
|
149
|
+
return merged
|
|
150
|
+
} catch {
|
|
151
|
+
return { ...defaults, ...detectGithubUser(), installMode: 'npm' }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function detectGithubUser() {
|
|
156
|
+
const fromEnv = process.env.OWD_GITHUB_USER?.trim()
|
|
157
|
+
if (fromEnv) return { githubUser: fromEnv }
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const user = execSync('git config --get github.user || git config --get user.username', {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
163
|
+
}).trim()
|
|
164
|
+
if (user) return { githubUser: user }
|
|
165
|
+
} catch {
|
|
166
|
+
/* ignore */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function saveSettings(workspaceRoot, settings, metaDir = null) {
|
|
173
|
+
const dir = metaDir || desktopMetaDir(workspaceRoot)
|
|
174
|
+
mkdirSync(dir, { recursive: true })
|
|
175
|
+
const path = metaDir ? join(metaDir, 'settings.json') : settingsPath(workspaceRoot)
|
|
176
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + '\n')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function desktopPaths(workspaceRoot) {
|
|
180
|
+
const desktop = join(workspaceRoot, 'desktop')
|
|
181
|
+
const resolved = resolveDesktopConfigPath(desktop)
|
|
182
|
+
const configWrite = desktopConfigWritePath(desktop)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
desktop,
|
|
186
|
+
config: resolved?.path ?? configWrite,
|
|
187
|
+
configWrite,
|
|
188
|
+
configLegacy: resolved?.legacy ?? false,
|
|
189
|
+
packageJson: join(desktop, 'package.json'),
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function shortName(pkgName) {
|
|
194
|
+
return pkgName.startsWith(SCOPE) ? pkgName.slice(SCOPE.length) : pkgName
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function fullName(short) {
|
|
198
|
+
return short.startsWith(SCOPE) ? short : `${SCOPE}${short}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function inferKind(shortName) {
|
|
202
|
+
if (shortName.startsWith('app-')) return 'app'
|
|
203
|
+
if (shortName.startsWith('theme-')) return 'theme'
|
|
204
|
+
return 'module'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getPackagesRequiredByTheme(workspaceRoot, themeName) {
|
|
208
|
+
if (!themeName) return []
|
|
209
|
+
const themeShort = shortName(themeName)
|
|
210
|
+
const pkgJsonPath = join(workspaceRoot, 'themes', themeShort, 'package.json')
|
|
211
|
+
if (!existsSync(pkgJsonPath)) return []
|
|
212
|
+
try {
|
|
213
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
214
|
+
const themeDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies }
|
|
215
|
+
return Object.keys(themeDeps).filter(
|
|
216
|
+
(dep) => isDesktopKitPackage(dep) || isInstallableDesktopModule(dep)
|
|
217
|
+
)
|
|
218
|
+
} catch {
|
|
219
|
+
return []
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
package/bin/owd.js
CHANGED
|
@@ -1,121 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import chalk from 'chalk'
|
|
5
|
-
import gradient from 'gradient-string'
|
|
6
|
-
import inquirer from 'inquirer'
|
|
7
|
-
import { execSync } from 'child_process'
|
|
8
|
-
import fs from 'fs'
|
|
9
|
-
import path from 'path'
|
|
3
|
+
import { runCli } from './cli.js'
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const program = new Command()
|
|
14
|
-
const owdGradient = gradient('#0080ff', '#53bdf3', '#0080ff')
|
|
15
|
-
|
|
16
|
-
const logTitle = () => {
|
|
17
|
-
console.log(owdGradient(`\n
|
|
18
|
-
___ __ __ _ ___ _ _
|
|
19
|
-
/___\\_ __ ___ _ __ / / /\\ \\ \\___| |__ / \\___ ___| | _| |_ ___ _ __
|
|
20
|
-
// // '_ \\ / _ \\ '_ \\ \\ \\/ \\/ / _ \\ '_ \\ / /\\ / _ \\/ __| |/ / __/ _ \\| '_ \\
|
|
21
|
-
/ \\_//| |_) | __/ | | | \\ /\\ / __/ |_) | / /_// __/\\__ \\ <| || (_) | |_) |
|
|
22
|
-
\\___/ | .__/ \\___|_| |_| \\/ \\/ \\___|_.__/ /___,' \\___||___/_|\\_\\\\__\\___/| .__/
|
|
23
|
-
|_| |_|
|
|
24
|
-
\n`))
|
|
25
|
-
console.log(chalk.white(summonInspiration()))
|
|
26
|
-
console.log(chalk.white('\n──────────────────────────────────────────────────────────────────────────────\n'))
|
|
27
|
-
console.log(chalk.white(' ➜ ') + chalk.whiteBright('npx @owdproject/cli create') + chalk.gray(' Create a new project from the template'))
|
|
28
|
-
console.log(chalk.white(' ➜ ') + chalk.whiteBright('npx @owdproject/cli start') + chalk.gray(' Start the dev server for local development'))
|
|
29
|
-
console.log(chalk.white(' ➜ ') + chalk.whiteBright('npx @owdproject/cli generate') + chalk.gray(' Generate project for static deployment'))
|
|
30
|
-
console.log(chalk.white('\n──────────────────────────────────────────────────────────────────────────────\n'))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const logServerInfo = () => {
|
|
34
|
-
console.log(`\n ${chalk.red.bold('➜ Local:')} ${chalk.white('http://localhost:3000/')}`)
|
|
35
|
-
console.log(` ${chalk.red.bold('➜ Network:')} ${chalk.white('http://localhost:3000/')} \n`)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
program.command('create')
|
|
39
|
-
.description('Create a new project')
|
|
40
|
-
.action(async () => {
|
|
41
|
-
logTitle()
|
|
42
|
-
console.log(chalk.green('✨ Creating new project\n'))
|
|
43
|
-
|
|
44
|
-
const { projectDir } = await inquirer.prompt([
|
|
45
|
-
{
|
|
46
|
-
type: 'input',
|
|
47
|
-
name: 'projectDir',
|
|
48
|
-
message: 'Enter the directory name for your new project:',
|
|
49
|
-
default: 'owd-client'
|
|
50
|
-
}
|
|
51
|
-
])
|
|
52
|
-
|
|
53
|
-
const targetDir = path.join(process.cwd(), projectDir)
|
|
54
|
-
|
|
55
|
-
if (fs.existsSync(targetDir)) {
|
|
56
|
-
console.log(chalk.red(`Directory ${projectDir} already exists!`))
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
await cloneRepo(targetDir)
|
|
64
|
-
|
|
65
|
-
process.chdir(targetDir)
|
|
66
|
-
|
|
67
|
-
console.log(chalk.green('Installing dependencies...'))
|
|
68
|
-
execSync('npm install', { stdio: 'inherit' })
|
|
69
|
-
|
|
70
|
-
console.log(chalk.blue('Starting development server...'))
|
|
71
|
-
execSync('npm run dev', { stdio: 'inherit' })
|
|
72
|
-
} catch (error) {
|
|
73
|
-
console.error(error)
|
|
74
|
-
console.log(chalk.red('✖ Error during project setup'))
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
program.command('start')
|
|
79
|
-
.description('Start the project in development mode')
|
|
80
|
-
.action(() => {
|
|
81
|
-
logTitle()
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
execSync('npx nuxi dev --host --qr=false', { stdio: 'inherit' })
|
|
85
|
-
console.log(chalk.blue('🚀 Starting development server'))
|
|
86
|
-
logServerInfo()
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.log(chalk.red('✖ Error starting Nuxt server'))
|
|
89
|
-
console.log(chalk.yellow(error.message))
|
|
90
|
-
} finally {
|
|
91
|
-
console.log(chalk.white('👨💻 If you need help, check the logs or visit our docs'))
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
program.command('generate')
|
|
96
|
-
.description('Generate deployment files')
|
|
97
|
-
.action(() => {
|
|
98
|
-
logTitle()
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
execSync('npx nuxi generate', { stdio: 'inherit' })
|
|
102
|
-
console.log(chalk.green('✅ Static site generated successfully!'))
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.log(chalk.red('✖ Error generating static site'))
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
function summonInspiration() {
|
|
109
|
-
const phrases = [
|
|
110
|
-
"Launching your digital workspace with Open Web Desktop",
|
|
111
|
-
"Unleashing the potential of your browser with Open Web Desktop",
|
|
112
|
-
"Crafting a better web experience with Open Web Desktop",
|
|
113
|
-
"Transforming how you showcase your portfolio on a browser",
|
|
114
|
-
"Bringing your digital garden to life, right in the browser",
|
|
115
|
-
"Opening up new possibilities with Open Web Desktop",
|
|
116
|
-
];
|
|
117
|
-
const randomIndex = Math.floor(Math.random() * phrases.length);
|
|
118
|
-
return phrases[randomIndex];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
program.parse()
|
|
5
|
+
await runCli('owd', process.argv.slice(2), { deprecated: true })
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
package bridge
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"os/exec"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type Config struct {
|
|
13
|
+
Theme *string `json:"theme"`
|
|
14
|
+
Apps []string `json:"apps"`
|
|
15
|
+
Modules []string `json:"modules"`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Settings struct {
|
|
19
|
+
DevPort int `json:"devPort"`
|
|
20
|
+
GithubUser *string `json:"githubUser"`
|
|
21
|
+
GithubOrgs []string `json:"githubOrgs"`
|
|
22
|
+
InstallMode string `json:"installMode"`
|
|
23
|
+
CatalogSort string `json:"catalogSort"`
|
|
24
|
+
CatalogNewDays int `json:"catalogNewDays"`
|
|
25
|
+
LastInstallChoices map[string]interface{} `json:"lastInstallChoices"`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type WorkspacePaths struct {
|
|
29
|
+
Desktop string `json:"desktop"`
|
|
30
|
+
Config string `json:"config"`
|
|
31
|
+
ConfigWrite string `json:"configWrite"`
|
|
32
|
+
ConfigLegacy bool `json:"configLegacy"`
|
|
33
|
+
PackageJson string `json:"packageJson"`
|
|
34
|
+
IsPlayground bool `json:"isPlayground"`
|
|
35
|
+
PackageName string `json:"packageName,omitempty"`
|
|
36
|
+
PackageDir string `json:"packageDir,omitempty"`
|
|
37
|
+
MetaDir string `json:"metaDir"`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type WorkspaceContext struct {
|
|
41
|
+
WorkspaceRoot string `json:"workspaceRoot"`
|
|
42
|
+
Settings Settings `json:"settings"`
|
|
43
|
+
Config Config `json:"config"`
|
|
44
|
+
Deps []string `json:"deps"`
|
|
45
|
+
Paths WorkspacePaths `json:"paths"`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type GithubForkInfo struct {
|
|
49
|
+
Owner string `json:"owner"`
|
|
50
|
+
Exists bool `json:"exists"`
|
|
51
|
+
IsFork bool `json:"isFork"`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type GithubSources struct {
|
|
55
|
+
Official struct {
|
|
56
|
+
Owner string `json:"owner"`
|
|
57
|
+
HtmlUrl string `json:"htmlUrl"`
|
|
58
|
+
} `json:"official"`
|
|
59
|
+
Fork *GithubForkInfo `json:"fork"`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type NpmSourceInfo struct {
|
|
63
|
+
Version string `json:"version"`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type SourcesMetadata struct {
|
|
67
|
+
Npm *NpmSourceInfo `json:"npm"`
|
|
68
|
+
Github GithubSources `json:"github"`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type CatalogEntry struct {
|
|
72
|
+
ShortName string `json:"shortName"`
|
|
73
|
+
Description string `json:"description"`
|
|
74
|
+
Stars int `json:"stars"`
|
|
75
|
+
HtmlUrl *string `json:"htmlUrl"`
|
|
76
|
+
Org string `json:"org"`
|
|
77
|
+
Kind string `json:"kind"`
|
|
78
|
+
LocalPath *string `json:"localPath"`
|
|
79
|
+
Remote *string `json:"remote"`
|
|
80
|
+
Version *string `json:"version"`
|
|
81
|
+
Name string `json:"name"`
|
|
82
|
+
Trusted bool `json:"trusted"`
|
|
83
|
+
Installed bool `json:"installed"`
|
|
84
|
+
LocalSource bool `json:"localSource"`
|
|
85
|
+
InPackageJson bool `json:"inPackageJson"`
|
|
86
|
+
ActiveTheme bool `json:"activeTheme"`
|
|
87
|
+
SourcesMeta *SourcesMetadata `json:"sourcesMeta"`
|
|
88
|
+
UpdatedAt *string `json:"updatedAt"`
|
|
89
|
+
PushedAt *string `json:"pushedAt"`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type CatalogResponse struct {
|
|
93
|
+
Entries []CatalogEntry `json:"entries"`
|
|
94
|
+
CacheAge string `json:"cacheAge"`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type WritePayload struct {
|
|
98
|
+
Config *Config `json:"config,omitempty"`
|
|
99
|
+
DepsToAdd map[string]string `json:"depsToAdd,omitempty"`
|
|
100
|
+
DepsToRemove []string `json:"depsToRemove,omitempty"`
|
|
101
|
+
Settings *Settings `json:"settings,omitempty"`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// FindWorkspaceRoot searches upwards from the current directory for pnpm-workspace.yaml
|
|
105
|
+
func FindWorkspaceRoot() (string, error) {
|
|
106
|
+
dir, err := os.Getwd()
|
|
107
|
+
if err != nil {
|
|
108
|
+
return "", err
|
|
109
|
+
}
|
|
110
|
+
for {
|
|
111
|
+
if _, err := os.Stat(filepath.Join(dir, "pnpm-workspace.yaml")); err == nil {
|
|
112
|
+
return dir, nil
|
|
113
|
+
}
|
|
114
|
+
parent := filepath.Dir(dir)
|
|
115
|
+
if parent == dir {
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
dir = parent
|
|
119
|
+
}
|
|
120
|
+
return "", fmt.Errorf("not inside an OWD workspace")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func getBridgePath(root string) string {
|
|
124
|
+
return filepath.Join(root, "packages", "cli", "bin", "bridge.js")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ReadWorkspaceContext runs the node bridge with --read
|
|
128
|
+
func ReadWorkspaceContext(root string) (*WorkspaceContext, error) {
|
|
129
|
+
cmd := exec.Command("node", getBridgePath(root), "--read")
|
|
130
|
+
var stdout, stderr bytes.Buffer
|
|
131
|
+
cmd.Stdout = &stdout
|
|
132
|
+
cmd.Stderr = &stderr
|
|
133
|
+
|
|
134
|
+
if err := cmd.Run(); err != nil {
|
|
135
|
+
return nil, fmt.Errorf("bridge --read failed: %w (stderr: %s)", err, stderr.String())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var ctx WorkspaceContext
|
|
139
|
+
if err := json.Unmarshal(stdout.Bytes(), &ctx); err != nil {
|
|
140
|
+
return nil, fmt.Errorf("failed to unmarshal context JSON: %w", err)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return &ctx, nil
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ReadCatalog runs the node bridge with --catalog
|
|
147
|
+
func ReadCatalog(root string, force bool) (*CatalogResponse, error) {
|
|
148
|
+
args := []string{getBridgePath(root), "--catalog"}
|
|
149
|
+
if force {
|
|
150
|
+
args = append(args, "--force")
|
|
151
|
+
}
|
|
152
|
+
cmd := exec.Command("node", args...)
|
|
153
|
+
var stdout, stderr bytes.Buffer
|
|
154
|
+
cmd.Stdout = &stdout
|
|
155
|
+
cmd.Stderr = &stderr
|
|
156
|
+
|
|
157
|
+
if err := cmd.Run(); err != nil {
|
|
158
|
+
return nil, fmt.Errorf("bridge --catalog failed: %w (stderr: %s)", err, stderr.String())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
var cat CatalogResponse
|
|
162
|
+
if err := json.Unmarshal(stdout.Bytes(), &cat); err != nil {
|
|
163
|
+
return nil, fmt.Errorf("failed to unmarshal catalog JSON: %w", err)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return &cat, nil
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// WriteChanges feeds the write payload to the node bridge via stdin
|
|
170
|
+
func WriteChanges(root string, payload *WritePayload) error {
|
|
171
|
+
cmd := exec.Command("node", getBridgePath(root), "--write")
|
|
172
|
+
var stdout, stderr bytes.Buffer
|
|
173
|
+
cmd.Stdout = &stdout
|
|
174
|
+
cmd.Stderr = &stderr
|
|
175
|
+
|
|
176
|
+
stdinPipe, err := cmd.StdinPipe()
|
|
177
|
+
if err != nil {
|
|
178
|
+
return err
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if err := cmd.Start(); err != nil {
|
|
182
|
+
return err
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
payloadBytes, err := json.Marshal(payload)
|
|
186
|
+
if err != nil {
|
|
187
|
+
return err
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if _, err := stdinPipe.Write(payloadBytes); err != nil {
|
|
191
|
+
return err
|
|
192
|
+
}
|
|
193
|
+
stdinPipe.Close()
|
|
194
|
+
|
|
195
|
+
if err := cmd.Wait(); err != nil {
|
|
196
|
+
return fmt.Errorf("bridge --write failed: %w (stderr: %s)", err, stderr.String())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return nil
|
|
200
|
+
}
|
package/go/desktop-go
ADDED
|
Binary file
|
package/go/go.mod
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module owd-cli
|
|
2
|
+
|
|
3
|
+
go 1.21
|
|
4
|
+
|
|
5
|
+
require (
|
|
6
|
+
github.com/charmbracelet/bubbletea v1.1.1
|
|
7
|
+
github.com/charmbracelet/lipgloss v0.13.0
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
require (
|
|
11
|
+
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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
15
|
+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
16
|
+
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
17
|
+
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
18
|
+
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
19
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
20
|
+
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
21
|
+
github.com/muesli/termenv v0.15.2 // indirect
|
|
22
|
+
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
|
|
25
|
+
golang.org/x/text v0.3.8 // indirect
|
|
26
|
+
)
|
package/go/go.sum
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
2
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
3
|
+
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
|
|
4
|
+
github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
|
5
|
+
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
|
6
|
+
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
|
7
|
+
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
|
|
8
|
+
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
|
9
|
+
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
|
10
|
+
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
|
11
|
+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
12
|
+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
13
|
+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
14
|
+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
15
|
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
16
|
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
17
|
+
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
18
|
+
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
19
|
+
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
|
20
|
+
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
21
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
22
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
23
|
+
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
24
|
+
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
25
|
+
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
|
26
|
+
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
|
27
|
+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
28
|
+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
29
|
+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
30
|
+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
|
31
|
+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
32
|
+
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
33
|
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
34
|
+
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
|
35
|
+
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
36
|
+
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
|
37
|
+
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
package/go/main.go
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
7
|
+
"owd-cli/bridge"
|
|
8
|
+
"owd-cli/tui"
|
|
9
|
+
|
|
10
|
+
tea "github.com/charmbracelet/bubbletea"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var p *tea.Program
|
|
14
|
+
|
|
15
|
+
func main() {
|
|
16
|
+
root, err := bridge.FindWorkspaceRoot()
|
|
17
|
+
if err != nil {
|
|
18
|
+
fmt.Printf("Error: %v\n", err)
|
|
19
|
+
os.Exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
model := tui.NewModel(root)
|
|
23
|
+
p = tea.NewProgram(model, tea.WithAltScreen())
|
|
24
|
+
tui.Program = p
|
|
25
|
+
|
|
26
|
+
if _, err := p.Run(); err != nil {
|
|
27
|
+
fmt.Printf("Alas, there's been an error: %v", err)
|
|
28
|
+
os.Exit(1)
|
|
29
|
+
}
|
|
30
|
+
}
|