feat(airi-plugin-vscode): publish workflow
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
.github/**
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
scripts/**
|
||||
src/**
|
||||
test/**
|
||||
tsconfig.json
|
||||
tsdown.config.ts
|
||||
@@ -0,0 +1,3 @@
|
||||
# AIRI VSCode Plugin
|
||||
|
||||
> Official VSCode extension for AIRI, streaming your current working at stuff back to AIRI.
|
||||
@@ -1,14 +1,21 @@
|
||||
{
|
||||
"publisher": "airi",
|
||||
"publisher": "proj-airi",
|
||||
"name": "@proj-airi/airi-plugin-vscode",
|
||||
"displayName": "AIRI",
|
||||
"version": "0.8.0-alpha.6",
|
||||
"private": true,
|
||||
"description": "VSCode extension that shares your coding context with AIRI",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/moeru-ai/airi.git",
|
||||
"directory": "plugins/airi-plugin-vscode"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"preview": true,
|
||||
"main": "./dist/extension.cjs",
|
||||
"icon": "res/logo.jpg",
|
||||
"engines": {
|
||||
"vscode": "^1.106.1"
|
||||
},
|
||||
@@ -18,32 +25,32 @@
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "airi.enable",
|
||||
"command": "airi-vscode.enable",
|
||||
"title": "AIRI: Enable"
|
||||
},
|
||||
{
|
||||
"command": "airi.disable",
|
||||
"command": "airi-vscode.disable",
|
||||
"title": "AIRI: Disable"
|
||||
},
|
||||
{
|
||||
"command": "airi.status",
|
||||
"command": "airi-vscode.status",
|
||||
"title": "AIRI: Show Status"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "AIRI",
|
||||
"properties": {
|
||||
"airi.enabled": {
|
||||
"airi-vscode.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable AIRI companion"
|
||||
"description": "Enable or disable the AIRI extension"
|
||||
},
|
||||
"airi.contextLines": {
|
||||
"airi-vscode.contextLines": {
|
||||
"type": "number",
|
||||
"default": 5,
|
||||
"description": "Number of context lines to send (before and after current line)"
|
||||
},
|
||||
"airi.sendInterval": {
|
||||
"airi-vscode.sendInterval": {
|
||||
"type": "number",
|
||||
"default": 3000,
|
||||
"description": "Interval in milliseconds to send updates (0 for real-time)"
|
||||
@@ -52,9 +59,11 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "pnpm run build",
|
||||
"build": "tsdown",
|
||||
"prepare": "pnpm run update",
|
||||
"update": "tsx ./scripts/vscode-ext-gen.ts",
|
||||
"dev": "tsdown --watch",
|
||||
"build": "tsdown",
|
||||
"publish": "tsx ./scripts/publish.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -62,6 +71,8 @@
|
||||
"@proj-airi/server-sdk": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.106.1"
|
||||
"@types/vscode": "^1.106.1",
|
||||
"tinyexec": "^1.0.2",
|
||||
"vscode-ext-gen": "^1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
@@ -0,0 +1,9 @@
|
||||
// https://github.com/unocss/unocss/blob/dba521e377887ed2b3b38dc86f36d4f292230ac8/packages-integrations/vscode/scripts/dev.ts
|
||||
|
||||
import { packageJSONForVSCode } from './shared'
|
||||
|
||||
async function run() {
|
||||
await packageJSONForVSCode('airi-vscode')
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,58 @@
|
||||
// https://github.com/unocss/unocss/blob/dba521e377887ed2b3b38dc86f36d4f292230ac8/packages-integrations/vscode/scripts/publish.ts
|
||||
|
||||
import process from 'node:process'
|
||||
|
||||
import { copyFile, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { exec } from 'tinyexec'
|
||||
|
||||
import { packageJSONForVSCode } from './shared'
|
||||
|
||||
const dir = typeof __dirname === 'string' ? __dirname : dirname(fileURLToPath(import.meta.url))
|
||||
const root = dirname(dir)
|
||||
|
||||
async function publish() {
|
||||
const { restore, isPreview } = await packageJSONForVSCode('airi-vscode')
|
||||
const pkgPath = join(root, 'package.json')
|
||||
const rawJSON = await readFile(pkgPath, 'utf-8')
|
||||
|
||||
const pkg = JSON.parse(rawJSON)
|
||||
|
||||
if (isPreview)
|
||||
pkg.preview = true
|
||||
else
|
||||
delete pkg.preview
|
||||
|
||||
await writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8')
|
||||
|
||||
await rm(join(root, 'LICENSE'), { force: true }).catch(() => {})
|
||||
await copyFile(join(root, '..', '..', 'LICENSE'), join(root, 'LICENSE'))
|
||||
|
||||
try {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\nPublish to VSCE...\n')
|
||||
const execPublish = exec('pnpx', ['@vscode/vsce', 'publish', '--no-dependencies', '-p', process.env.VSCE_TOKEN!, ...[(isPreview ? '--pre-release' : '')]], { nodeOptions: { cwd: root } })
|
||||
for await (const line of execPublish) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(line)
|
||||
}
|
||||
}
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\nPublish to OVSE...\n')
|
||||
const execPublish = exec('pnpx', ['ovsx', 'publish', '--no-dependencies', '-p', process.env.OVSX_TOKEN!, ...[(isPreview ? '--pre-release' : '')]], { nodeOptions: { cwd: root } })
|
||||
for await (const line of execPublish) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
await restore()
|
||||
}
|
||||
}
|
||||
|
||||
publish()
|
||||
@@ -0,0 +1,93 @@
|
||||
// https://github.com/unocss/unocss/blob/dba521e377887ed2b3b38dc86f36d4f292230ac8/packages-integrations/vscode/scripts/dev.ts
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
|
||||
export async function packageJSONForVSCode(name: string) {
|
||||
const json = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8'))
|
||||
const originalName = json.name
|
||||
const originalVersion = json.version
|
||||
|
||||
if (json.name !== name) {
|
||||
json.name = name
|
||||
|
||||
await writeFile(new URL('../package.json', import.meta.url), JSON.stringify(json, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
const numericVersion = encodeNumericVersion(originalVersion)
|
||||
if (json.version !== numericVersion.version) {
|
||||
json.version = numericVersion.version
|
||||
|
||||
await writeFile(new URL('../package.json', import.meta.url), JSON.stringify(json, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
return {
|
||||
originalName,
|
||||
originalVersion,
|
||||
name,
|
||||
version: numericVersion.version,
|
||||
isPreview: numericVersion.preview,
|
||||
restore: async () => {
|
||||
json.name = originalName
|
||||
json.version = originalVersion
|
||||
|
||||
await writeFile(new URL('../package.json', import.meta.url), JSON.stringify(json, null, 2), 'utf-8')
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NOTICE: VSCE rejects prerelease identifiers, so we encode stage+sequence into a numeric-only patch bucket:
|
||||
// encodedPatch = patch*10000 + stageBucket + sequence.
|
||||
// stageBucket: alpha=1000, beta=2000, rc=3000, stable=9000.
|
||||
// Examples:
|
||||
// 0.8.0-alpha.6 -> 0.8.(0*10000+1000+6)=0.8.1006 (preview=true)
|
||||
// 0.8.0-beta.1 -> 0.8.2001 (preview=true)
|
||||
// 0.8.0 -> 0.8.(0*10000+9000)=0.8.9000 (preview=false)
|
||||
// This keeps ordering: alpha < beta < rc < stable. Unknown prerelease tags default to alpha.
|
||||
export function encodeNumericVersion(version: string) {
|
||||
const match = version.match(/^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-(?<pre>[0-9A-Z.-]+))?$/i)
|
||||
if (!match || !match.groups)
|
||||
throw new Error(`Invalid semver: ${version}`)
|
||||
|
||||
const major = Number.parseInt(match.groups.major, 10)
|
||||
const minor = Number.parseInt(match.groups.minor, 10)
|
||||
const patch = Number.parseInt(match.groups.patch, 10)
|
||||
const prerelease = match.groups.pre
|
||||
|
||||
const multiplier = 10_000
|
||||
const stageBuckets = {
|
||||
alpha: 1_000,
|
||||
beta: 2_000,
|
||||
rc: 3_000,
|
||||
stable: 9_000,
|
||||
} as const
|
||||
|
||||
const { stage, sequence } = parsePrerelease(prerelease)
|
||||
const maxSequence = (multiplier - 1) - stageBuckets[stage]
|
||||
if (sequence > maxSequence) {
|
||||
throw new Error(`Prerelease sequence overflow for ${stage}: ${sequence} exceeds limit ${maxSequence}`)
|
||||
}
|
||||
if (sequence < 0) {
|
||||
throw new Error(`Prerelease sequence must be non-negative: ${sequence}`)
|
||||
}
|
||||
|
||||
const encodedPatch = (patch * multiplier) + (stageBuckets[stage] ?? stageBuckets.alpha) + sequence
|
||||
const encoded = `${major}.${minor}.${encodedPatch}`
|
||||
|
||||
return {
|
||||
version: encoded,
|
||||
preview: stage !== 'stable',
|
||||
}
|
||||
}
|
||||
|
||||
function parsePrerelease(prerelease?: string) {
|
||||
if (!prerelease) {
|
||||
return { stage: 'stable' as const, sequence: 0 }
|
||||
}
|
||||
|
||||
const [stageRaw, sequenceRaw] = prerelease.split('.')
|
||||
const stage = stageRaw === 'beta' || stageRaw === 'rc' || stageRaw === 'alpha' ? stageRaw : 'alpha'
|
||||
const sequenceParsed = Number.parseInt(sequenceRaw ?? '', 10)
|
||||
const sequence = Number.isFinite(sequenceParsed) ? sequenceParsed : 0
|
||||
|
||||
return { stage, sequence }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { dirname } from 'node:path'
|
||||
|
||||
import { exec } from 'tinyexec'
|
||||
|
||||
import { packageJSONForVSCode } from './shared'
|
||||
|
||||
async function run() {
|
||||
const { restore, name } = await packageJSONForVSCode('airi-vscode')
|
||||
|
||||
const execGen = exec('pnpm', ['-F', name, 'exec', 'vscode-ext-gen', '--scope=unocss', '--output', 'src/generated/meta.ts'], { nodeOptions: { cwd: dirname(new URL('../', import.meta.url).pathname) } })
|
||||
for await (const line of execGen) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(line)
|
||||
}
|
||||
|
||||
await restore()
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { AiriEvent } from './types'
|
||||
import type { Events } from './types'
|
||||
|
||||
import { useLogger } from '@guiiai/logg'
|
||||
import { Client } from '@proj-airi/server-sdk'
|
||||
import { Client as ServerClient } from '@proj-airi/server-sdk'
|
||||
|
||||
/**
|
||||
* Airi Channel Server Client
|
||||
*/
|
||||
export class AiriClient {
|
||||
private client: Client<AiriEvent> | null = null
|
||||
export class Client {
|
||||
private client: ServerClient<Events> | null = null
|
||||
|
||||
/**
|
||||
* Connect to Channel Server
|
||||
*/
|
||||
async connect(): Promise<boolean> {
|
||||
try {
|
||||
this.client = new Client({ name: 'proj-airi:plugin-vscode' })
|
||||
this.client = new ServerClient({ name: 'proj-airi:plugin-vscode' })
|
||||
|
||||
useLogger().log('Airi companion connected to Channel Server')
|
||||
return true
|
||||
@@ -39,7 +39,7 @@ export class AiriClient {
|
||||
/**
|
||||
* Send event to Airi
|
||||
*/
|
||||
sendEvent(event: AiriEvent): void {
|
||||
sendEvent(event: Events): void {
|
||||
if (!this.client) {
|
||||
useLogger().warn('Cannot send event: not connected to Airi Channel Server')
|
||||
return
|
||||
|
||||
@@ -2,10 +2,10 @@ import type * as vscode from 'vscode'
|
||||
|
||||
import { initLogger, LoggerFormat, LoggerLevel, useLogger } from '@guiiai/logg'
|
||||
|
||||
import { AiriClient } from './airi-client'
|
||||
import { Client } from './airi-client'
|
||||
import { ContextCollector } from './context-collector'
|
||||
|
||||
let airiClient: AiriClient
|
||||
let client: Client
|
||||
let contextCollector: ContextCollector
|
||||
let updateTimer: NodeJS.Timeout | null = null
|
||||
let isEnabled = true
|
||||
@@ -19,48 +19,48 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const { window, workspace, commands } = await import('vscode')
|
||||
|
||||
useLogger().log('Airi Companion is activating...')
|
||||
useLogger().log('AIRI is activating...')
|
||||
|
||||
// Get the configuration
|
||||
const config = workspace.getConfiguration('airi.companion')
|
||||
const config = workspace.getConfiguration('airi-vscode')
|
||||
isEnabled = config.get<boolean>('enabled', true)
|
||||
const contextLines = config.get<number>('contextLines', 5)
|
||||
const sendInterval = config.get<number>('sendInterval', 3000)
|
||||
|
||||
// Initialize
|
||||
airiClient = new AiriClient()
|
||||
client = new Client()
|
||||
contextCollector = new ContextCollector(contextLines)
|
||||
|
||||
// Connect to Airi Channel Server
|
||||
if (isEnabled) {
|
||||
const connected = await airiClient.connect()
|
||||
const connected = await client.connect()
|
||||
if (connected) {
|
||||
window.showInformationMessage('Airi Companion connected!')
|
||||
window.showInformationMessage('AIRI server channel connected!')
|
||||
}
|
||||
else {
|
||||
window.showWarningMessage('Airi Companion failed to connect to server')
|
||||
window.showWarningMessage('AIRI server channel connection failed!')
|
||||
}
|
||||
}
|
||||
|
||||
// Register commands
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand('airi.companion.enable', async () => {
|
||||
commands.registerCommand('airi-vscode.enable', async () => {
|
||||
isEnabled = true
|
||||
await airiClient.connect()
|
||||
await client.connect()
|
||||
await registerListeners(sendInterval)
|
||||
window.showInformationMessage('Airi Companion enabled')
|
||||
window.showInformationMessage('AIRI enabled!')
|
||||
}),
|
||||
|
||||
commands.registerCommand('airi.companion.disable', () => {
|
||||
commands.registerCommand('airi-vscode.disable', () => {
|
||||
isEnabled = false
|
||||
unregisterListeners()
|
||||
airiClient.disconnect()
|
||||
window.showInformationMessage('Airi Companion disabled')
|
||||
client.disconnect()
|
||||
window.showInformationMessage('AIRI disabled!')
|
||||
}),
|
||||
|
||||
commands.registerCommand('airi.companion.status', () => {
|
||||
const status = isEnabled && airiClient ? 'Connected' : 'Disconnected'
|
||||
window.showInformationMessage(`Airi Companion Status: ${status}`)
|
||||
commands.registerCommand('airi-vscode.status', () => {
|
||||
const status = isEnabled && client ? 'Connected' : 'Disconnected'
|
||||
window.showInformationMessage(`AIRI server channel status: ${status}.`)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
await registerListeners(sendInterval)
|
||||
}
|
||||
|
||||
useLogger().log('Airi Companion activated successfully')
|
||||
useLogger().log('AIRI activated successfully')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ async function registerListeners(sendInterval: number) {
|
||||
if (editor && editor.document === document) {
|
||||
const ctx = await contextCollector.collect(editor)
|
||||
if (ctx) {
|
||||
airiClient.sendEvent({
|
||||
client.sendEvent({
|
||||
type: 'coding:save',
|
||||
data: ctx,
|
||||
})
|
||||
@@ -102,7 +102,7 @@ async function registerListeners(sendInterval: number) {
|
||||
if (editor) {
|
||||
const ctx = await contextCollector.collect(editor)
|
||||
if (ctx) {
|
||||
airiClient.sendEvent({
|
||||
client.sendEvent({
|
||||
type: 'coding:switch-file',
|
||||
data: ctx,
|
||||
})
|
||||
@@ -143,7 +143,7 @@ function startMonitoring(interval: number) {
|
||||
|
||||
const ctx = await contextCollector.collect(editor)
|
||||
if (ctx) {
|
||||
airiClient.sendEvent({
|
||||
client.sendEvent({
|
||||
type: 'coding:context',
|
||||
data: ctx,
|
||||
})
|
||||
@@ -166,6 +166,6 @@ function stopMonitoring() {
|
||||
*/
|
||||
export function deactivate() {
|
||||
unregisterListeners()
|
||||
airiClient?.disconnect()
|
||||
useLogger().log('Airi Companion deactivated')
|
||||
client?.disconnect()
|
||||
useLogger().log('AIRI deactivated!')
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface CodingContext {
|
||||
/**
|
||||
* Event types sent to Airi
|
||||
*/
|
||||
export interface AiriEvent {
|
||||
export interface Events {
|
||||
type: 'coding:context' | 'coding:save' | 'coding:switch-file'
|
||||
data: CodingContext
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user