chore: isolate shared dsh plugins into independent monorepo
Some checks failed
build-and-publish / build-test (push) Failing after 1m6s
build-and-publish / publish (push) Has been skipped

This commit is contained in:
2026-08-26 22:44:31 +07:00
parent 0e0b68fed5
commit 81159a22e8
37 changed files with 3456 additions and 2 deletions

View File

@@ -0,0 +1,54 @@
import fs from 'node:fs'
import path from 'node:path'
/**
* Print the names of packages/* whose current version is NOT yet published
* to the Gitea npm registry. Makes a publish workflow idempotent.
*
* Reads publishConfig.registry from each package.json; auth token from env
* GITEA_NPM_TOKEN. Prints one bare package name per line (suitable for the
* `pnpm --filter <name> publish` loop). Never prints the token.
*/
const token = process.env.GITEA_NPM_TOKEN
if (!token) {
console.error('GITEA_NPM_TOKEN not set')
process.exit(2)
}
const packagesDir = path.resolve('packages')
const unpublished = []
for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue
const pkgPath = path.join(packagesDir, entry.name, 'package.json')
if (!fs.existsSync(pkgPath)) continue
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
if (pkg.private) continue
const registry = pkg.publishConfig?.registry
if (!registry) {
console.error(`skipping ${pkg.name}: no publishConfig.registry`)
continue
}
// Gitea npm registry URL form: .../api/packages/{owner}/npm/<name>
const esc = (pkg.name).replace('/', '%2F')
const url = `${registry}${esc}`
try {
const res = await fetch(url, {
headers: { Authorization: `token ${token}` },
})
if (res.status === 404) {
unpublished.push(pkg.name)
} else if (res.ok) {
// registry returns 200 when the package exists
console.error(`already published: ${pkg.name}`)
} else {
console.error(`registry check ${pkg.name}: HTTP ${res.status}`)
}
} catch (err) {
console.error(`registry check ${pkg.name} failed: ${err.message}`)
}
}
if (unpublished.length) {
process.stdout.write(unpublished.join('\n') + '\n')
}

View File

@@ -0,0 +1,74 @@
<#
.SYNOPSIS
Installs shared @deepseek-ai/* plugins from the Gitea npm registry into a
DSH profile and wires them into cordis.patch.yml.
.DESCRIPTION
Ensures the profile .npmrc maps the @deepseek-ai scope to the Gitea npm
registry, installs the three shared plugins into the profile, removes stale
link: dependencies, and idempotently appends insert rows to cordis.patch.yml.
.PARAMETER Profile
Profile name (directory under $DshHome\profiles). Default: 'web'.
.PARAMETER DshHome
DSH config root. Default: $env:DSH_HOME, else ~/.dsh.
.PARAMETER Token
Gitea token for the private npm registry. Default: $env:GITEA_NPM_TOKEN.
.EXAMPLE
.\scripts\dsh-plugin-install.ps1 -Token <GITEA_NPM_TOKEN>
#>
[CmdletBinding()]
param(
[string]$Profile = 'web',
[string]$DshHome,
[string]$Token = $env:GITEA_NPM_TOKEN
)
$registry = 'https://git.byte-mate.ru/api/packages/Coder/npm/'
$plugins = @(
@{ Name = '@deepseek-ai/dsh-tool-lab'; Version = '0.1.0-rc.7'; Id = 'tool-lab' },
@{ Name = '@deepseek-ai/dsh-web-search-searxng'; Version = '0.1.0-rc.7'; Id = 'web-search-searxng' },
@{ Name = '@deepseek-ai/dsh-telegram-remote'; Version = '0.1.0'; Id = 'telegram-remote' }
)
if (-not $DshHome) { $DshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $HOME '.dsh' } }
$profileDir = Join-Path $DshHome "profiles\$Profile"
if (-not (Test-Path $profileDir)) {
Write-Error "Profile dir not found: $profileDir"
exit 1
}
Write-Host "Profile dir: $profileDir"
# 1. Ensure .npmrc scoped registry + token.
$npmrc = Join-Path $profileDir '.npmrc'
$npmrcLines = if (Test-Path $npmrc) { Get-Content $npmrc } else { @() }
$scopeLine = "@deepseek-ai:registry=$registry"
$authLine = "//$($registry -replace 'https://','')/:_authToken=$token"
if ($npmrcLines -notcontains $scopeLine) { $npmrcLines += $scopeLine }
if ($Token) {
$npmrcLines = ($npmrcLines | Where-Object { $_ -notlike '//*:_authToken=*' })
$npmrcLines += $authLine
}
Set-Content -Path $npmrc -Value $npmrcLines -Encoding ascii
Write-Host "Wrote $npmrc"
# 2. Remove stale link: dependencies.
& pnpm --dir $profileDir remove @deepseek-ai/dsh-web-search-searxng dsh-telegram-remote 2>$null | Out-Null
Write-Host "Removed stale link dependencies (if any)"
# 3. Install the shared plugins from the Gitea registry.
$specs = $plugins | ForEach-Object { "$($_.Name)@$($_.Version)" }
& pnpm --dir $profileDir add $specs
# 4. Idempotently append insert blocks to cordis.patch.yml.
$patchFile = Join-Path $profileDir 'cordis.patch.yml'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
foreach ($p in $plugins) {
& node (Join-Path $scriptDir 'patch-cordis.mjs') --file $patchFile --name $p.Name --id $p.Id
}
Write-Host "`nDone. Plugins installed into $Profile profile. Restart dsh to load them."
exit 0

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# dsh-plugin-install.sh — install shared @deepseek-ai/* plugins from the Gitea
# npm registry into a DSH profile and wire them into cordis.patch.yml.
set -euo pipefail
PROFILE="${1:-web}"
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
TOKEN="${GITEA_NPM_TOKEN:-}"
REGISTRY="https://git.byte-mate.ru/api/packages/Coder/npm/"
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
if [ ! -d "$PROFILE_DIR" ]; then
echo "Profile dir not found: $PROFILE_DIR" >&2
exit 1
fi
echo "Profile dir: $PROFILE_DIR"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 1. Ensure .npmrc scoped registry + token.
NPMRC="$PROFILE_DIR/.npmrc"
touch "$NPMRC"
SCOPE_LINE="@deepseek-ai:registry=$REGISTRY"
if ! grep -qF "$SCOPE_LINE" "$NPMRC"; then
echo "$SCOPE_LINE" >> "$NPMRC"
fi
if [ -n "$TOKEN" ]; then
sed -i.bak "/^\/\/.*:_authToken=/d" "$NPMRC" && rm -f "$NPMRC.bak"
printf '//%s/:_authToken=%s\n' "$(printf '%s' "$REGISTRY" | sed 's#^https://##; s#/$##')" "$TOKEN" >> "$NPMRC"
fi
echo "Wrote $NPMRC"
# 2. Remove stale link: dependencies.
pnpm --dir "$PROFILE_DIR" remove @deepseek-ai/dsh-web-search-searxng dsh-telegram-remote >/dev/null 2>&1 || true
echo "Removed stale link dependencies (if any)"
# 3. Install the shared plugins from the Gitea registry.
pnpm --dir "$PROFILE_DIR" add \
"@deepseek-ai/dsh-tool-lab@0.1.0-rc.7" \
"@deepseek-ai/dsh-web-search-searxng@0.1.0-rc.7" \
"@deepseek-ai/dsh-telegram-remote@0.1.0"
# 4. Idempotently append insert blocks to cordis.patch.yml.
PATCH_FILE="$PROFILE_DIR/cordis.patch.yml"
node "$SCRIPT_DIR/patch-cordis.mjs" --file "$PATCH_FILE" --name "@deepseek-ai/dsh-tool-lab" --id tool-lab
node "$SCRIPT_DIR/patch-cordis.mjs" --file "$PATCH_FILE" --name "@deepseek-ai/dsh-web-search-searxng" --id web-search-searxng
node "$SCRIPT_DIR/patch-cordis.mjs" --file "$PATCH_FILE" --name "@deepseek-ai/dsh-telegram-remote" --id telegram-remote
echo ""
echo "Done. Plugins installed into $PROFILE profile. Restart dsh to load them."

49
scripts/patch-cordis.mjs Normal file
View File

@@ -0,0 +1,49 @@
import fs from 'node:fs'
/**
* Idempotently append an `- insert:` patch block to a DSH cordis.patch.yml
* file unless a row with the same full package name already exists.
*
* Usage: node patch-cordis.mjs --file <path> --name <full pkg name> [--id <id>]
*/
function parseArg(key) {
const i = process.argv.indexOf(key)
return i >= 0 ? process.argv[i + 1] : undefined
}
const file = parseArg('--file')
const name = parseArg('--name')
const id = parseArg('--id') || (name ? name.split('/').pop() : undefined)
if (!file || !name || !id) {
console.error('usage: node patch-cordis.mjs --file <path> --name <full-pkg-name> [--id <id>]')
process.exit(2)
}
let text
try {
text = fs.readFileSync(file, 'utf8')
} catch (err) {
console.error(`cannot read ${file}: ${err.message}`)
process.exit(1)
}
// Idempotency: if a row with this name already exists, nothing to do.
if (text.includes(`name: '${name}'`) || text.includes(`name: "${name}"`)) {
console.log(`already present: ${name} — no change`)
process.exit(0)
}
const block = [
'',
'- insert:',
' - id: ' + id,
` name: '${name}'`,
' config: {}',
'',
].join('\n')
// Append preserving a single trailing newline.
const trimmed = text.endsWith('\n') ? text.slice(0, -1) : text
fs.writeFileSync(file, trimmed + '\n' + block + '\n')
console.log(`appended insert for ${name} (id=${id}) to ${file}`)