dsh-plugin-syncup 0.0.0-stage → 4.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 +21 -0
- package/README.en.md +183 -0
- package/README.md +362 -2
- package/client.js +1506 -0
- package/cordis.patch.yml +6 -0
- package/icon.svg +26 -0
- package/index.js +354 -0
- package/lib/config.js +330 -0
- package/lib/errors.js +81 -0
- package/lib/github.js +305 -0
- package/lib/http.js +285 -0
- package/lib/jobs.js +107 -0
- package/lib/manifest.js +272 -0
- package/lib/manual.js +85 -0
- package/lib/preview.js +217 -0
- package/lib/pushplan.js +200 -0
- package/lib/spec.js +127 -0
- package/lib/state.js +110 -0
- package/lib/sync.js +1636 -0
- package/lib/versions.js +447 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +52 -3
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" role="img" aria-label="Plugin Sync">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="8" y1="4" x2="56" y2="60" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#0B1220"/>
|
|
5
|
+
<stop offset="1" stop-color="#0B3B3C"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="accent" x1="18" y1="18" x2="46" y2="46" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop stop-color="#34E0C8"/>
|
|
9
|
+
<stop offset="1" stop-color="#3B82F6"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect width="64" height="64" rx="15" fill="url(#bg)"/>
|
|
13
|
+
<rect x="1" y="1" width="62" height="62" rx="14" fill="none" stroke="#FFFFFF" stroke-opacity=".08"/>
|
|
14
|
+
<g fill="none" stroke="url(#accent)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
|
15
|
+
<path d="M17 27a15 15 0 0 1 26-7"/>
|
|
16
|
+
<path d="M43 15v7h-7"/>
|
|
17
|
+
<path d="M47 37a15 15 0 0 1-26 7"/>
|
|
18
|
+
<path d="M21 49v-7h7"/>
|
|
19
|
+
</g>
|
|
20
|
+
<g fill="url(#accent)">
|
|
21
|
+
<rect x="25" y="24" width="14" height="16" rx="3" opacity=".92"/>
|
|
22
|
+
<path d="M28 29h8M28 33h6" stroke="#07111F" stroke-width="1.8" stroke-linecap="round" opacity=".85"/>
|
|
23
|
+
</g>
|
|
24
|
+
<circle cx="17" cy="27" r="3.2" fill="#0B1220" stroke="#34E0C8" stroke-width="2"/>
|
|
25
|
+
<circle cx="47" cy="37" r="3.2" fill="#0B1220" stroke="#3B82F6" stroke-width="2"/>
|
|
26
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { normalizeConfig } from './lib/config.js'
|
|
5
|
+
import { registerApiRoutes } from './lib/http.js'
|
|
6
|
+
import { createSyncRuntime } from './lib/sync.js'
|
|
7
|
+
|
|
8
|
+
export { normalizeConfig }
|
|
9
|
+
|
|
10
|
+
export const name = 'dsh-plugin-syncup'
|
|
11
|
+
export const inject = ['tools', 'pluginManager']
|
|
12
|
+
|
|
13
|
+
const ACTIONS = new Set(['status', 'preview', 'apply', 'latest', 'push', 'plan-push', 'log'])
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Locate the runtime directory that holds this DSH's bundled node and pnpm.
|
|
17
|
+
*
|
|
18
|
+
* The manual-update command should work when pasted as-is, so it points at the
|
|
19
|
+
* exact binaries DSH itself uses rather than assuming a global `pnpm` on PATH.
|
|
20
|
+
* Candidate order: DSH_PACK_HOME, then walking up from this module's real path
|
|
21
|
+
* (profile → <dsh home>/dsh-runtimes/<runtime>).
|
|
22
|
+
*/
|
|
23
|
+
function resolvePackHome() {
|
|
24
|
+
const explicit = process.env.DSH_PACK_HOME
|
|
25
|
+
if (typeof explicit === 'string' && explicit.trim() !== '') return explicit.trim()
|
|
26
|
+
let current
|
|
27
|
+
try {
|
|
28
|
+
current = dirname(fileURLToPath(import.meta.url))
|
|
29
|
+
} catch {
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
33
|
+
const candidate = join(current, 'dependencies', 'pnpm', 'bin', 'pnpm.cjs')
|
|
34
|
+
if (existsSync(candidate)) return current
|
|
35
|
+
const nested = join(current, 'dsh-runtimes')
|
|
36
|
+
if (existsSync(nested)) {
|
|
37
|
+
const primary = join(nested, 'dsh-primary-runtime')
|
|
38
|
+
if (existsSync(join(primary, 'dependencies', 'pnpm', 'bin', 'pnpm.cjs'))) return primary
|
|
39
|
+
}
|
|
40
|
+
const parent = resolve(current, '..')
|
|
41
|
+
if (parent === current) break
|
|
42
|
+
current = parent
|
|
43
|
+
}
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function summarizeError(error) {
|
|
52
|
+
if (error === null || typeof error !== 'object') return String(error)
|
|
53
|
+
return typeof error.message === 'string' ? error.message : JSON.stringify(error).slice(0, 500)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function shortSha(value) {
|
|
57
|
+
return typeof value === 'string' && value.length > 12 ? value.slice(0, 12) : value ?? ''
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function conflictLabel(type) {
|
|
61
|
+
if (type === 'host-disagreement') return 'hosts disagree'
|
|
62
|
+
if (type === 'downgrade') return 'remote is older'
|
|
63
|
+
if (type === 'spec-change') return 'same version, different source'
|
|
64
|
+
if (type === 'enable-state') return 'installed but disabled locally'
|
|
65
|
+
return type
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderPreview(preview, lines) {
|
|
69
|
+
lines.push(
|
|
70
|
+
`Preview: ${preview.counts.actionable} default action(s), ${preview.counts.conflicts} conflict(s), `
|
|
71
|
+
+ `${preview.counts.localOnly} local-only`,
|
|
72
|
+
)
|
|
73
|
+
const visible = preview.entries.filter((entry) => (
|
|
74
|
+
entry.defaultChoice === 'remote'
|
|
75
|
+
|| entry.conflict !== null
|
|
76
|
+
|| ['install', 'upgrade', 'downgrade', 'spec-change', 'enable'].includes(entry.kind)
|
|
77
|
+
))
|
|
78
|
+
for (const entry of visible.slice(0, 30)) {
|
|
79
|
+
const localVersion = entry.local?.version ? `local ${entry.local.version}` : 'not installed'
|
|
80
|
+
const remoteVersion = entry.remote?.version ? `remote ${entry.remote.version}` : 'remote none'
|
|
81
|
+
const conflict = entry.conflict === null ? '' : ` [${conflictLabel(entry.conflict.type)}]`
|
|
82
|
+
lines.push(` ${entry.name}: ${entry.kind} (${localVersion} -> ${remoteVersion})${conflict}`)
|
|
83
|
+
}
|
|
84
|
+
if (visible.length > 30) lines.push(` … ${visible.length - 30} more`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function renderToolResult(_args, value) {
|
|
88
|
+
const lines = []
|
|
89
|
+
if (!isRecord(value)) return [{ type: 'text', text: 'Plugin Sync returned an unexpected result.' }]
|
|
90
|
+
const title = typeof value.action === 'string' ? value.action : 'status'
|
|
91
|
+
lines.push(`Plugin Sync — ${title}`)
|
|
92
|
+
lines.push(value.ok === false
|
|
93
|
+
? `FAILED: ${value.error === undefined ? 'one or more operations failed' : summarizeError(value.error)}`
|
|
94
|
+
: 'OK')
|
|
95
|
+
if (typeof value.hostId === 'string') lines.push(`Host: ${value.hostId}`)
|
|
96
|
+
if (isRecord(value.job)) {
|
|
97
|
+
lines.push(`Job: ${value.job.id}`)
|
|
98
|
+
} else if (typeof value.gistId === 'string') {
|
|
99
|
+
lines.push(value.gistId !== ''
|
|
100
|
+
? `Gist: ${value.gistId}${typeof value.gistUrl === 'string' ? ` (${value.gistUrl})` : ''}`
|
|
101
|
+
: 'Gist: not created yet')
|
|
102
|
+
}
|
|
103
|
+
if (isRecord(value.config)) {
|
|
104
|
+
lines.push(`File: ${value.config.fileName ?? 'dsh-plugins.json'}`)
|
|
105
|
+
}
|
|
106
|
+
if (isRecord(value.token)) {
|
|
107
|
+
lines.push(`Token: ${value.token.configured === true ? `configured (${value.token.source})` : 'not configured'}`)
|
|
108
|
+
}
|
|
109
|
+
if (isRecord(value.local)) {
|
|
110
|
+
lines.push(
|
|
111
|
+
`Local: ${value.local.profileBundles ?? 0} profile bundles, ${value.local.portableBundles ?? 0} portable`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
if (isRecord(value.state)) {
|
|
115
|
+
if (value.state.lastPreviewAt !== undefined) lines.push(`Last preview: ${value.state.lastPreviewAt}`)
|
|
116
|
+
if (value.state.lastApplyAt !== undefined) lines.push(`Last apply: ${value.state.lastApplyAt}`)
|
|
117
|
+
if (value.state.lastPushAt !== undefined) lines.push(`Last push: ${value.state.lastPushAt}`)
|
|
118
|
+
if (Array.isArray(value.state.pendingUpgrades) && value.state.pendingUpgrades.length > 0) {
|
|
119
|
+
lines.push(`Unfinished upgrades: ${value.state.pendingUpgrades.length}`)
|
|
120
|
+
for (const item of value.state.pendingUpgrades.slice(0, 8)) {
|
|
121
|
+
lines.push(` ? ${item.name}: ${item.fromVersion || item.fromSpec} -> ${item.toSpec} (started ${item.startedAt})`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (typeof value.previewId === 'string' && isRecord(value.counts)) {
|
|
126
|
+
renderPreview(value, lines)
|
|
127
|
+
lines.push(`Preview ID: ${value.previewId}`)
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(value.removed)) {
|
|
130
|
+
lines.push(`Removed: ${value.removed.length}`)
|
|
131
|
+
for (const item of value.removed.slice(0, 12)) {
|
|
132
|
+
lines.push(` - ${item.name}${item.restartRequired ? ' (restart required)' : ''}`)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(value.applied)) {
|
|
136
|
+
lines.push(`Applied: ${value.applied.length}`)
|
|
137
|
+
for (const item of value.applied.slice(0, 12)) {
|
|
138
|
+
lines.push(` + ${item.name}${item.restartRequired ? ' (restart required)' : ''}`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(value.failed) && value.failed.length > 0) {
|
|
142
|
+
lines.push(`Failed: ${value.failed.length}`)
|
|
143
|
+
for (const item of value.failed.slice(0, 12)) {
|
|
144
|
+
lines.push(` ! ${item.name}: ${item.error ?? item.message ?? summarizeError(item)}`)
|
|
145
|
+
}
|
|
146
|
+
for (const item of value.failed.filter((entry) => entry.manual !== undefined).slice(0, 4)) {
|
|
147
|
+
lines.push(` Manual update for ${item.name}:`)
|
|
148
|
+
for (const step of item.manual.steps ?? []) {
|
|
149
|
+
if (typeof step.command === 'string' && step.command !== '') lines.push(` ${step.command}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (isRecord(value.counts) && Array.isArray(value.entries) && value.action === 'latest') {
|
|
154
|
+
lines.push(
|
|
155
|
+
`Latest check: ${value.counts.outdated} outdated, ${value.counts.current} current, `
|
|
156
|
+
+ `${value.counts.unsupported} not checkable`,
|
|
157
|
+
)
|
|
158
|
+
for (const entry of value.entries.filter((item) => item.outdated === true).slice(0, 15)) {
|
|
159
|
+
lines.push(` ^ ${entry.name}: ${entry.local?.version ?? '?'} -> ${entry.latest}`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(value.updatedList) && value.updatedList.length > 0) {
|
|
163
|
+
lines.push(`Updated: ${value.updatedList.length}`)
|
|
164
|
+
for (const item of value.updatedList.slice(0, 12)) {
|
|
165
|
+
lines.push(` ^ ${item.name}: ${item.replaced ?? ''} -> ${item.version ?? item.latest ?? ''}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (typeof value.status === 'string' && value.action === 'push') {
|
|
169
|
+
lines.push(`Push: ${value.status}${value.version ? ` (${shortSha(value.version)})` : ''}`)
|
|
170
|
+
}
|
|
171
|
+
if (typeof value.planId === 'string' && Array.isArray(value.entries) && isRecord(value.counts)) {
|
|
172
|
+
lines.push(
|
|
173
|
+
`Push plan: ${value.counts.conflict} conflict(s), ${value.counts.remoteOnly} remote-only, `
|
|
174
|
+
+ `${value.counts.localOnly} local-only, ${value.counts.same} unchanged`,
|
|
175
|
+
)
|
|
176
|
+
for (const entry of value.entries.filter((item) => item.kind === 'conflict').slice(0, 12)) {
|
|
177
|
+
lines.push(` ~ ${entry.name}: local ${entry.local?.version || '?'} / remote ${entry.remote?.version || '?'} -> keep ${entry.newest}`)
|
|
178
|
+
}
|
|
179
|
+
for (const entry of value.entries.filter((item) => item.kind === 'remote-only').slice(0, 12)) {
|
|
180
|
+
lines.push(` ? ${entry.name}: remote ${entry.remote?.version || '?'}, kept by default (choose drop to remove)`)
|
|
181
|
+
}
|
|
182
|
+
for (const entry of value.entries.filter((item) => item.kind === 'local-only').slice(0, 12)) {
|
|
183
|
+
lines.push(` + ${entry.name}: local ${entry.local?.version || '?'}, pushed`)
|
|
184
|
+
}
|
|
185
|
+
lines.push(`Plan ID: ${value.planId}`)
|
|
186
|
+
}
|
|
187
|
+
if (isRecord(value.report)) {
|
|
188
|
+
const report = value.report
|
|
189
|
+
if (Array.isArray(report.conflicts) && report.conflicts.length > 0) {
|
|
190
|
+
lines.push(`Resolved conflicts: ${report.conflicts.length}`)
|
|
191
|
+
for (const item of report.conflicts.slice(0, 12)) lines.push(` ~ ${item.name}: kept ${item.side} ${item.version}`)
|
|
192
|
+
}
|
|
193
|
+
if (Array.isArray(report.dropped) && report.dropped.length > 0) {
|
|
194
|
+
lines.push(`Dropped from manifest: ${report.dropped.join(', ')}`)
|
|
195
|
+
}
|
|
196
|
+
if (Array.isArray(report.kept) && report.kept.length > 0) {
|
|
197
|
+
lines.push(`Kept in manifest: ${report.kept.join(', ')}`)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (isRecord(value.remote) && value.remote.hostCount !== undefined) {
|
|
201
|
+
lines.push(`Remote: ${value.remote.hostCount} host(s), ${value.remote.bundleCount} bundle record(s)`)
|
|
202
|
+
}
|
|
203
|
+
if (isRecord(value.job)) {
|
|
204
|
+
const job = value.job
|
|
205
|
+
lines.push(
|
|
206
|
+
`Job ${job.id} (${job.kind}): ${job.status}`
|
|
207
|
+
+ (job.progress?.total > 0 ? ` ${job.progress.done}/${job.progress.total}` : ''),
|
|
208
|
+
)
|
|
209
|
+
for (const line of (job.lines ?? []).slice(-40)) {
|
|
210
|
+
lines.push(` ${line.message}`)
|
|
211
|
+
}
|
|
212
|
+
if (typeof job.error === 'string' && job.error !== '') lines.push(` ! ${job.error}`)
|
|
213
|
+
}
|
|
214
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function apply(ctx, rawConfig) {
|
|
218
|
+
const config = normalizeConfig({
|
|
219
|
+
...rawConfig,
|
|
220
|
+
packHome: resolvePackHome(),
|
|
221
|
+
})
|
|
222
|
+
const runtime = createSyncRuntime({
|
|
223
|
+
config,
|
|
224
|
+
pluginManager: ctx.pluginManager,
|
|
225
|
+
credentialsProvider: () => ctx.get('credentials'),
|
|
226
|
+
logger: ctx.logger,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
registerApiRoutes({ ctx, runtime, config })
|
|
230
|
+
|
|
231
|
+
ctx.effect(() => ctx.tools.register({
|
|
232
|
+
name: 'plugin_sync',
|
|
233
|
+
description:
|
|
234
|
+
'Preview and apply a GitHub Gist plugin manifest. Manual only: no background sync. '
|
|
235
|
+
+ '`preview` fetches the Gist and returns install/upgrade/downgrade/enable/local-only entries plus conflicts; '
|
|
236
|
+
+ '`apply` installs, enables, or removes only the entries the caller selects from that preview; '
|
|
237
|
+
+ '`plan-push` compares the Gist with this host: conflicts keep the newer version by default, '
|
|
238
|
+
+ 'remote-only entries are kept by default, local-only entries are pushed directly; '
|
|
239
|
+
+ '`push` uploads this host\'s portable inventory, optionally following that plan '
|
|
240
|
+
+ '(creating a secret Gist when no Gist id is configured); '
|
|
241
|
+
+ '`log` returns the latest install log with per-item progress; `status` reports config and last-run state. '
|
|
242
|
+
+ 'Installs and removals go through the DSH plugin manager. The same operations are available in the plugin page.',
|
|
243
|
+
parameters: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
properties: {
|
|
247
|
+
action: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
enum: [...ACTIONS],
|
|
250
|
+
description: 'status, preview, apply, latest, plan-push, push, or log.',
|
|
251
|
+
},
|
|
252
|
+
planId: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Plan id returned by `plan-push`; pass it with `push` to follow the confirmed comparison.',
|
|
255
|
+
},
|
|
256
|
+
previewId: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'Preview id returned by `preview`; required for `apply`.',
|
|
259
|
+
},
|
|
260
|
+
names: {
|
|
261
|
+
type: 'array',
|
|
262
|
+
items: { type: 'string' },
|
|
263
|
+
description: 'Bundle names for `latest`: omitted or empty checks the newest release of every bundle; a non-empty list updates those bundles to their newest release.',
|
|
264
|
+
},
|
|
265
|
+
selections: {
|
|
266
|
+
type: 'object',
|
|
267
|
+
additionalProperties: { type: 'string', enum: ['remote', 'local', 'skip', 'remove', 'newest', 'keep', 'drop', 'push'] },
|
|
268
|
+
description: 'Map of bundle name to choice. For `apply`: remote, local, skip, remove. For `plan-push`/`push`: newest, local, remote (conflict), keep, drop (remote-only). Omitted entries use the default.',
|
|
269
|
+
},
|
|
270
|
+
jobId: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
description: 'Job id for `log`; omitted returns the latest job.',
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
required: ['action'],
|
|
276
|
+
},
|
|
277
|
+
output: {
|
|
278
|
+
schema: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
additionalProperties: true,
|
|
281
|
+
properties: {
|
|
282
|
+
ok: { type: 'boolean' },
|
|
283
|
+
action: { type: 'string' },
|
|
284
|
+
hostId: { type: 'string' },
|
|
285
|
+
error: { type: 'object' },
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
render: renderToolResult,
|
|
289
|
+
},
|
|
290
|
+
timeoutMs: 15 * 60 * 1000,
|
|
291
|
+
isConcurrencySafe: (args) => args?.action === 'status',
|
|
292
|
+
execute: async (args, exec) => {
|
|
293
|
+
const action = typeof args?.action === 'string' ? args.action : 'status'
|
|
294
|
+
if (!ACTIONS.has(action)) return { ok: false, action, error: { code: 'invalid-action', message: `Unknown action: ${action}` } }
|
|
295
|
+
try {
|
|
296
|
+
if (action === 'status') return { ...(await runtime.status()), action: 'status' }
|
|
297
|
+
if (action === 'preview') return { ...(await runtime.preview({ signal: exec?.signal })), action: 'preview' }
|
|
298
|
+
if (action === 'push') {
|
|
299
|
+
return {
|
|
300
|
+
...(await runtime.push({
|
|
301
|
+
planId: typeof args?.planId === 'string' && args.planId !== '' ? args.planId : null,
|
|
302
|
+
selections: isRecord(args?.selections) ? args.selections : {},
|
|
303
|
+
signal: exec?.signal,
|
|
304
|
+
})),
|
|
305
|
+
action: 'push',
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (action === 'plan-push') return { ...(await runtime.planPush({ signal: exec?.signal })), action }
|
|
309
|
+
if (action === 'latest') {
|
|
310
|
+
const names = Array.isArray(args?.names) ? args.names.filter((name) => typeof name === 'string') : []
|
|
311
|
+
if (names.length === 0) {
|
|
312
|
+
return { ...(await runtime.checkLatest({ signal: exec?.signal })), action }
|
|
313
|
+
}
|
|
314
|
+
return { ...(await runtime.applyLatest(names, { signal: exec?.signal })), action }
|
|
315
|
+
}
|
|
316
|
+
if (action === 'log') {
|
|
317
|
+
const jobId = typeof args?.jobId === 'string' && args.jobId !== '' ? args.jobId : undefined
|
|
318
|
+
const job = jobId === undefined ? runtime.latestJob() : runtime.job(jobId)
|
|
319
|
+
if (job === null) {
|
|
320
|
+
return { ok: false, action, error: { code: 'job-not-found', message: 'No install job is recorded yet' } }
|
|
321
|
+
}
|
|
322
|
+
return { ok: true, action, job }
|
|
323
|
+
}
|
|
324
|
+
if (typeof args?.previewId !== 'string' || args.previewId === '') {
|
|
325
|
+
return {
|
|
326
|
+
ok: false,
|
|
327
|
+
action,
|
|
328
|
+
error: { code: 'missing-preview-id', message: 'previewId is required for apply' },
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const selections = isRecord(args?.selections) ? args.selections : {}
|
|
332
|
+
return {
|
|
333
|
+
...(await runtime.apply(args.previewId, selections, { signal: exec?.signal })),
|
|
334
|
+
action: 'apply',
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
action,
|
|
340
|
+
error: {
|
|
341
|
+
code: typeof error?.code === 'string' ? error.code : 'operation-failed',
|
|
342
|
+
message: error instanceof Error ? error.message : String(error),
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
}), 'plugin-sync: tool')
|
|
348
|
+
|
|
349
|
+
if (config.configError !== undefined) {
|
|
350
|
+
runtime.log('warn', config.configError)
|
|
351
|
+
} else if (config.gistId === '') {
|
|
352
|
+
runtime.log('info', 'no Gist id configured; push once to create a secret Gist')
|
|
353
|
+
}
|
|
354
|
+
}
|