trackfw 6.10.0 → 7.0.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,184 +0,0 @@
1
- 'use strict'
2
- const { Command } = require('commander')
3
- const os = require('os')
4
- const path = require('path')
5
- const fs = require('fs')
6
- const https = require('https')
7
- const { t } = require('../i18n')
8
-
9
- const REGISTRY_URL = 'https://raw.githubusercontent.com/kgsaran/trackfw-plugins/main/registry.yaml'
10
- const MAX_PLUGIN_SIZE = 50 * 1024 * 1024
11
- const REQUEST_TIMEOUT_MS = 30000
12
-
13
- function fetchRegistry() {
14
- return new Promise((resolve, reject) => {
15
- https.get(REGISTRY_URL, (res) => {
16
- let data = ''
17
- res.on('data', chunk => { data += chunk })
18
- res.on('end', () => resolve(data))
19
- }).on('error', reject)
20
- })
21
- }
22
-
23
- function parseRegistryYAML(text) {
24
- const entries = []
25
- let current = null
26
- const lines = text.split('\n')
27
- for (const line of lines) {
28
- const trimmed = line.trim()
29
- if (!trimmed || trimmed === 'plugins:') continue
30
- if (trimmed.startsWith('- name:')) {
31
- if (current) entries.push(current)
32
- current = { name: trimmed.slice('- name:'.length).trim(), repo: '', description: '', tags: [] }
33
- continue
34
- }
35
- if (!current) continue
36
- if (trimmed.startsWith('repo:')) {
37
- current.repo = trimmed.slice('repo:'.length).trim()
38
- } else if (trimmed.startsWith('description:')) {
39
- let desc = trimmed.slice('description:'.length).trim()
40
- desc = desc.replace(/^"|"$/g, '')
41
- current.description = desc
42
- } else if (trimmed.startsWith('tags:')) {
43
- const raw = trimmed.slice('tags:'.length).trim().replace(/^\[|\]$/g, '')
44
- current.tags = raw.split(',').map(s => s.trim()).filter(Boolean)
45
- }
46
- }
47
- if (current) entries.push(current)
48
- return entries
49
- }
50
-
51
- function matchesKeyword(entry, kw) {
52
- const lkw = kw.toLowerCase()
53
- if (entry.name.toLowerCase().includes(lkw)) return true
54
- if (entry.description.toLowerCase().includes(lkw)) return true
55
- for (const tag of entry.tags) {
56
- if (tag.toLowerCase().includes(lkw)) return true
57
- }
58
- return false
59
- }
60
-
61
- function pluginsDir() {
62
- return path.join(os.homedir(), '.trackfw', 'plugins')
63
- }
64
-
65
- function platformOS() {
66
- if (process.platform === 'win32') return 'windows'
67
- if (process.platform === 'darwin') return 'darwin'
68
- return 'linux'
69
- }
70
-
71
- function platformArch() {
72
- if (process.arch === 'x64') return 'amd64'
73
- return process.arch
74
- }
75
-
76
- function listPlugins() {
77
- const dir = pluginsDir()
78
- fs.mkdirSync(dir, { recursive: true })
79
- return fs.readdirSync(dir).filter(f => fs.statSync(path.join(dir, f)).isFile())
80
- }
81
-
82
- async function installPlugin(repo) {
83
- let base = repo
84
- let tag = 'latest'
85
- const atIdx = repo.indexOf('@')
86
- if (atIdx !== -1) {
87
- base = repo.slice(0, atIdx)
88
- tag = repo.slice(atIdx + 1)
89
- }
90
- const pluginName = path.basename(base)
91
- const assetName = `trackfw-plugin-${pluginName}-${platformOS()}-${platformArch()}`
92
- const url = tag === 'latest'
93
- ? `https://github.com/${base}/releases/latest/download/${assetName}`
94
- : `https://github.com/${base}/releases/download/${tag}/${assetName}`
95
-
96
- const res = await fetch(url, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) })
97
- if (!res.ok) throw new Error(t('errors.downloadFailed', { status: res.status, url }))
98
- const declaredSize = Number(res.headers.get('content-length') || 0)
99
- if (declaredSize > MAX_PLUGIN_SIZE) throw new Error(`Plugin exceeds ${MAX_PLUGIN_SIZE} bytes`)
100
-
101
- const dir = pluginsDir()
102
- fs.mkdirSync(dir, { recursive: true })
103
- const content = Buffer.from(await res.arrayBuffer())
104
- if (content.length > MAX_PLUGIN_SIZE) throw new Error(`Plugin exceeds ${MAX_PLUGIN_SIZE} bytes`)
105
- const destination = path.join(dir, pluginName)
106
- const temporary = path.join(dir, `.${pluginName}-${process.pid}.tmp`)
107
- try {
108
- fs.writeFileSync(temporary, content, { mode: 0o755 })
109
- fs.renameSync(temporary, destination)
110
- } finally {
111
- if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
112
- }
113
- }
114
-
115
- function removePlugin(name) {
116
- if (!name || path.basename(name) !== name) throw new Error(`Invalid plugin name: ${name}`)
117
- const filePath = path.join(pluginsDir(), name)
118
- if (!fs.existsSync(filePath)) throw new Error(t('errors.pluginNotFound', { name }))
119
- fs.unlinkSync(filePath)
120
- }
121
-
122
- const cmd = new Command('plugins')
123
- cmd.description(t('plugins.description'))
124
-
125
- cmd.command('list')
126
- .description(t('plugins.list.description'))
127
- .action(() => {
128
- const plugins = listPlugins()
129
- if (plugins.length === 0) {
130
- console.log(t('plugins.list.empty'))
131
- return
132
- }
133
- plugins.forEach(p => console.log(p))
134
- })
135
-
136
- cmd.command('add <repo>')
137
- .description(t('plugins.add.description'))
138
- .action(async (repo) => {
139
- try {
140
- console.log(t('plugins.add.installing', { repo }))
141
- await installPlugin(repo)
142
- const name = repo.split('@')[0].split('/').pop()
143
- console.log(t('plugins.add.success', { name }))
144
- } catch (err) {
145
- console.error(`Error: ${err.message}`)
146
- process.exit(1)
147
- }
148
- })
149
-
150
- cmd.command('remove <name>')
151
- .description(t('plugins.remove.description'))
152
- .action((name) => {
153
- try {
154
- removePlugin(name)
155
- console.log(t('plugins.remove.success', { name }))
156
- } catch (err) {
157
- console.error(`Error: ${err.message}`)
158
- process.exit(1)
159
- }
160
- })
161
-
162
- cmd.command('search <keyword>')
163
- .description('Search the plugin registry')
164
- .action(async (keyword) => {
165
- let entries
166
- try {
167
- const body = await fetchRegistry()
168
- entries = parseRegistryYAML(body).filter(e => matchesKeyword(e, keyword))
169
- } catch (err) {
170
- console.log(`Registry unavailable: ${err.message}`)
171
- return
172
- }
173
- if (entries.length === 0) {
174
- console.log(`No plugins found for "${keyword}"`)
175
- return
176
- }
177
- console.log(String('NAME').padEnd(30) + String('REPO').padEnd(30) + 'DESCRIPTION')
178
- console.log('-'.repeat(90))
179
- for (const e of entries) {
180
- console.log(e.name.padEnd(30) + e.repo.padEnd(30) + e.description)
181
- }
182
- })
183
-
184
- module.exports = cmd