feat: init project
This commit is contained in:
12
.eslintignore
Normal file
12
.eslintignore
Normal file
@@ -0,0 +1,12 @@
|
||||
*.global.js
|
||||
build
|
||||
node_modules
|
||||
interactive/guides/vendor/*.md
|
||||
interactive/data/guides.ts
|
||||
defaultConfig.ts
|
||||
packages/preset-icons/src/collections.json
|
||||
packages/eslint-plugin/fixtures
|
||||
bench/source/gen*.js
|
||||
!.vitepress
|
||||
docs/.vitepress/cache/deps/*.*
|
||||
test/cases
|
||||
39
.eslintrc
Normal file
39
.eslintrc
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"extends": ["@antfu"],
|
||||
"rules": {
|
||||
"yml/no-empty-document": "off",
|
||||
"react/no-unknown-property": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"playground/**/*.*",
|
||||
"examples/**/*.*",
|
||||
"test/fixtures/**/*.*"
|
||||
],
|
||||
"rules": {
|
||||
"no-restricted-imports": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"packages/gpt-runner-vscode/**/*.*"
|
||||
],
|
||||
"rules": {
|
||||
"unicorn/prefer-node-protocol": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"**/*.md/*.*"
|
||||
],
|
||||
"rules": {
|
||||
"no-restricted-imports": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-labels": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
.cache
|
||||
.DS_Store
|
||||
.idea
|
||||
.nuxt
|
||||
.output
|
||||
.temp
|
||||
*.local
|
||||
*.log
|
||||
*.vsix
|
||||
bench/source/gen*.js
|
||||
coverage
|
||||
dist
|
||||
examples/**/pnpm-lock.yaml
|
||||
node_modules
|
||||
packages/gpt-runner/README.md
|
||||
packages/gpt-runner-vscode/LICENSE
|
||||
result.json
|
||||
.svelte-kit
|
||||
.eslintcache
|
||||
vite.config.ts.timestamp-*
|
||||
4
.npmrc
Normal file
4
.npmrc
Normal file
@@ -0,0 +1,4 @@
|
||||
ignore-workspace-root-check=true
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
16
.tazerc.json
Normal file
16
.tazerc.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"exclude": [
|
||||
"find-up",
|
||||
"gzip-size",
|
||||
"@types/vscode",
|
||||
"codemirror",
|
||||
"@vue/cli-plugin-babel",
|
||||
"@vue/cli-service",
|
||||
"unbuild",
|
||||
"msw"
|
||||
],
|
||||
"packageMode": {
|
||||
"vue": "minor",
|
||||
"nuxt": "minor"
|
||||
}
|
||||
}
|
||||
17
.vscode/settings.json
vendored
Normal file
17
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.enable": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.formatOnSave": false,
|
||||
"cSpell.words": [
|
||||
"nicepkg",
|
||||
"tagify"
|
||||
],
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#22285E",
|
||||
"titleBar.activeBackground": "#303884",
|
||||
"titleBar.activeForeground": "#FCFCFE"
|
||||
}
|
||||
}
|
||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@@ -0,0 +1 @@
|
||||
Please refer to https://github.com/antfu/contribute
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-PRESENT Jinming Yang <https://github.com/2214962083>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
13
alias.ts
Normal file
13
alias.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
function r(p: string) {
|
||||
return resolve(__dirname, p)
|
||||
}
|
||||
|
||||
export const alias: Record<string, string> = {
|
||||
'@nicepkg/gpt-runner': r('./packages/gpt-runner/src/'),
|
||||
'@nicepkg/gpt-runner-cli': r('./packages/gpt-runner-cli/src/'),
|
||||
'@nicepkg/gpt-runner-config': r('./packages/gpt-runner-config/src/'),
|
||||
'@nicepkg/gpt-runner-core': r('./packages/gpt-runner-core/src/'),
|
||||
'@nicepkg/gpt-runner-shared': r('./packages/gpt-runner-shared/src/'),
|
||||
}
|
||||
16
netlify.toml
Executable file
16
netlify.toml
Executable file
@@ -0,0 +1,16 @@
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
NODE_OPTIONS = "--max_old_space_size=4096"
|
||||
|
||||
[build]
|
||||
publish = "docs/dist"
|
||||
command = "pnpm run deploy"
|
||||
|
||||
[functions]
|
||||
node_bundler = "esbuild"
|
||||
|
||||
[[redirects]]
|
||||
from = "/play/*"
|
||||
to = "/play/index.html"
|
||||
status = 200
|
||||
|
||||
73
package.json
Normal file
73
package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "gpt-runner",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.4.0",
|
||||
"scripts": {
|
||||
"taze": "taze minor -wIr && pnpm -r --parallel run update-post",
|
||||
"build": "rimraf packages/*/dist && esno scripts/copy-files.ts && pnpm -r --filter=./packages/* run build && pnpm -r run build-post",
|
||||
"dev": "pnpm stub",
|
||||
"play": "npm -C playground run dev",
|
||||
"lint": "eslint --cache .",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"release": "bumpp -r",
|
||||
"stub": "pnpm -r --filter=./packages/* --parallel run stub",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"test:update": "vitest -u",
|
||||
"test:ci": "pnpm build && pnpm typecheck && pnpm lint && pnpm test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.38.6",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/node": "^18.16.4",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/react": "^18.2.6",
|
||||
"@nicepkg/gpt-runner": "workspace:*",
|
||||
"@nicepkg/gpt-runner-cli": "workspace:*",
|
||||
"@nicepkg/gpt-runner-config": "workspace:*",
|
||||
"@nicepkg/gpt-runner-core": "workspace:*",
|
||||
"@nicepkg/gpt-runner-shared": "workspace:*",
|
||||
"@vitejs/plugin-legacy": "^4.0.3",
|
||||
"@vitest/ui": "^0.31.0",
|
||||
"bumpp": "^9.1.0",
|
||||
"eslint": "8.39.0",
|
||||
"esno": "^0.16.3",
|
||||
"execa": "^7.1.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jsdom": "^22.0.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"msw": "1.0.1",
|
||||
"pnpm": "8.4.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "^18.2.0",
|
||||
"rollup": "^3.21.5",
|
||||
"semver": "^7.5.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"taze": "^0.10.1",
|
||||
"terser": "^5.17.1",
|
||||
"tsup": "^6.7.0",
|
||||
"typescript": "^5.0.4",
|
||||
"unbuild": "^0.8.11",
|
||||
"unplugin-auto-import": "^0.15.3",
|
||||
"vite": "^4.3.5",
|
||||
"vite-plugin-inspect": "^0.7.25",
|
||||
"vite-plugin-pages": "^0.29.0",
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"magic-string": "^0.30.0"
|
||||
}
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "npx lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx,vue,md}": [
|
||||
"eslint --cache --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
packages/README.md
Normal file
1
packages/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Gpt-runner Packages
|
||||
21
packages/gpt-runner-cli/LICENSE
Normal file
21
packages/gpt-runner-cli/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023-PRESENT Jinming Yang <https://github.com/2214962083>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
packages/gpt-runner-cli/README.md
Normal file
1
packages/gpt-runner-cli/README.md
Normal file
@@ -0,0 +1 @@
|
||||
no...
|
||||
3
packages/gpt-runner-cli/bin/gpt-runner.mjs
Executable file
3
packages/gpt-runner-cli/bin/gpt-runner.mjs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
import('../dist/cli.mjs')
|
||||
13
packages/gpt-runner-cli/build.config.ts
Normal file
13
packages/gpt-runner-cli/build.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
'src/cli',
|
||||
],
|
||||
clean: true,
|
||||
declaration: true,
|
||||
rollup: {
|
||||
inlineDependencies: true,
|
||||
},
|
||||
})
|
||||
59
packages/gpt-runner-cli/package.json
Normal file
59
packages/gpt-runner-cli/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@nicepkg/gpt-runner-cli",
|
||||
"version": "0.0.1",
|
||||
"description": "CLI for GPT Runner",
|
||||
"author": {
|
||||
"name": "Jinming Yang",
|
||||
"email": "2214962083@qq.com",
|
||||
"url": "https://github.com/2214962083"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/2214962083",
|
||||
"homepage": "https://github.com/nicepkg/gpt-runner/tree/main/packages/gpt-runner-cli#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nicepkg/gpt-runner",
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nicepkg/gpt-runner/issues"
|
||||
},
|
||||
"keywords": [],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"gptr": "./bin/gpt-runner.mjs"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"@nicepkg/gpt-runner-config": "workspace:*",
|
||||
"@nicepkg/gpt-runner-core": "workspace:*",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^3.5.3",
|
||||
"colorette": "^2.0.20",
|
||||
"consola": "^3.1.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"magic-string": "^0.30.0",
|
||||
"pathe": "^1.1.0",
|
||||
"perfect-debounce": "^1.0.0"
|
||||
}
|
||||
}
|
||||
48
packages/gpt-runner-cli/src/cli-start.ts
Normal file
48
packages/gpt-runner-cli/src/cli-start.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { cac } from 'cac'
|
||||
import { loadConfig } from '@nicepkg/gpt-runner-config'
|
||||
import { toArray } from '@nicepkg/gpt-runner-core'
|
||||
import { version } from '../package.json'
|
||||
import type { CliOptions } from './types'
|
||||
import { build } from './index'
|
||||
|
||||
export async function startCli(cwd = process.cwd(), argv = process.argv, options: CliOptions = {}) {
|
||||
const cli = cac('gptr')
|
||||
|
||||
cli
|
||||
.command('[...patterns]', 'Glob patterns', {
|
||||
ignoreOptionDefaultValue: true,
|
||||
})
|
||||
.option('-o, --out-file <file>', 'Output file', {
|
||||
default: cwd,
|
||||
})
|
||||
.option('-c, --config [file]', 'Config file')
|
||||
.option('-w, --watch', 'Watch for file changes')
|
||||
.option('--preflights', 'Enable preflights', { default: true })
|
||||
.option('-m, --minify', 'Minify generated CSS', { default: false })
|
||||
.action(async (patterns: Array<string>, flags) => {
|
||||
Object.assign(options, {
|
||||
cwd,
|
||||
...flags,
|
||||
})
|
||||
|
||||
if (patterns)
|
||||
options.patterns = patterns
|
||||
const { config } = await loadConfig(cwd, options.config)
|
||||
|
||||
const entries = toArray(config.cli?.entry || options)
|
||||
await Promise.all(entries.map(entry =>
|
||||
build({
|
||||
...options,
|
||||
...entry,
|
||||
}),
|
||||
))
|
||||
})
|
||||
|
||||
cli.help()
|
||||
cli.version(version)
|
||||
|
||||
// Parse CLI args without running the command to
|
||||
// handle command errors globally
|
||||
cli.parse(argv, { run: false })
|
||||
await cli.runMatchedCommand()
|
||||
}
|
||||
4
packages/gpt-runner-cli/src/cli.ts
Normal file
4
packages/gpt-runner-cli/src/cli.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { startCli } from './cli-start'
|
||||
import { handleError } from './errors'
|
||||
|
||||
startCli().catch(handleError)
|
||||
4
packages/gpt-runner-cli/src/config.ts
Normal file
4
packages/gpt-runner-cli/src/config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { UserConfig } from '@nicepkg/gpt-runner-core'
|
||||
|
||||
export const defaultConfig: UserConfig = {
|
||||
}
|
||||
21
packages/gpt-runner-cli/src/errors.ts
Normal file
21
packages/gpt-runner-cli/src/errors.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { consola } from 'consola'
|
||||
|
||||
export class PrettyError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
|
||||
if (typeof Error.captureStackTrace === 'function')
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
|
||||
else
|
||||
this.stack = new Error(message).stack
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(error: unknown) {
|
||||
if (error instanceof PrettyError)
|
||||
consola.error(error.message)
|
||||
|
||||
process.exitCode = 1
|
||||
}
|
||||
150
packages/gpt-runner-cli/src/index.ts
Normal file
150
packages/gpt-runner-cli/src/index.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { existsSync, promises as fs } from 'node:fs'
|
||||
import { basename, dirname, normalize, relative, resolve } from 'pathe'
|
||||
import fg from 'fast-glob'
|
||||
import { consola } from 'consola'
|
||||
import { cyan, dim, green } from 'colorette'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import { toArray } from '@nicepkg/gpt-runner-core'
|
||||
import type { SourceCodeTransformerEnforce, UserConfig } from '@nicepkg/gpt-runner-core'
|
||||
import { createContext } from '../../shared-integration/src/context'
|
||||
import { applyTransformers } from '../../shared-integration/src/transformers'
|
||||
import { version } from '../package.json'
|
||||
import { defaultConfig } from './config'
|
||||
import { PrettyError, handleError } from './errors'
|
||||
import { getWatcher } from './watcher'
|
||||
import type { CliOptions, ResolvedCliOptions } from './types'
|
||||
|
||||
const name = 'gptr'
|
||||
|
||||
export async function resolveOptions(options: CliOptions) {
|
||||
if (!options.patterns?.length) {
|
||||
throw new PrettyError(
|
||||
`No glob patterns, try ${cyan(`${name} <path/to/**/*>`)}`,
|
||||
)
|
||||
}
|
||||
|
||||
return options as ResolvedCliOptions
|
||||
}
|
||||
|
||||
export async function build(_options: CliOptions) {
|
||||
const fileCache = new Map<string, string>()
|
||||
|
||||
const cwd = _options.cwd || process.cwd()
|
||||
const options = await resolveOptions(_options)
|
||||
|
||||
async function loadConfig() {
|
||||
const ctx = createContext<UserConfig>(options.config, defaultConfig)
|
||||
const configSources = (await ctx.updateRoot(cwd)).sources.map(i => normalize(i))
|
||||
return { ctx, configSources }
|
||||
}
|
||||
|
||||
const { ctx, configSources } = await loadConfig()
|
||||
const files = await fg(options.patterns, { cwd, absolute: true })
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
fileCache.set(file, await fs.readFile(file, 'utf8'))
|
||||
}),
|
||||
)
|
||||
|
||||
consola.log(green(`${name} v${version}`))
|
||||
consola.start(`GPT Runner ${options.watch ? 'in watch mode...' : 'for production...'}`)
|
||||
|
||||
const debouncedBuild = debounce(
|
||||
async () => {
|
||||
generate(options).catch(handleError)
|
||||
},
|
||||
100,
|
||||
)
|
||||
|
||||
const startWatcher = async () => {
|
||||
if (!options.watch)
|
||||
return
|
||||
const { patterns } = options
|
||||
|
||||
const watcher = await getWatcher(options)
|
||||
|
||||
if (configSources.length)
|
||||
watcher.add(configSources)
|
||||
|
||||
watcher.on('all', async (type, file) => {
|
||||
const absolutePath = resolve(cwd, file)
|
||||
|
||||
if (configSources.includes(absolutePath)) {
|
||||
await ctx.reloadConfig()
|
||||
consola.info(`${cyan(basename(file))} changed, setting new config`)
|
||||
}
|
||||
else {
|
||||
consola.log(`${green(type)} ${dim(file)}`)
|
||||
|
||||
if (type.startsWith('unlink'))
|
||||
fileCache.delete(absolutePath)
|
||||
else
|
||||
fileCache.set(absolutePath, await fs.readFile(absolutePath, 'utf8'))
|
||||
}
|
||||
|
||||
debouncedBuild()
|
||||
})
|
||||
|
||||
consola.info(
|
||||
`Watching for changes in ${
|
||||
toArray(patterns)
|
||||
.map(i => cyan(i))
|
||||
.join(', ')}`,
|
||||
)
|
||||
}
|
||||
|
||||
await generate(options)
|
||||
|
||||
await startWatcher().catch(handleError)
|
||||
|
||||
function transformFiles(sources: { id: string; code: string; transformedCode?: string | undefined }[], enforce: SourceCodeTransformerEnforce = 'default') {
|
||||
return Promise.all(
|
||||
sources.map(({ id, code, transformedCode }) => new Promise<{ id: string; code: string; transformedCode: string | undefined }>((resolve) => {
|
||||
applyTransformers(ctx, code, id, enforce)
|
||||
.then((transformsRes) => {
|
||||
resolve({ id, code, transformedCode: transformsRes?.code || transformedCode })
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
async function generate(options: ResolvedCliOptions) {
|
||||
const sourceCache = Array.from(fileCache).map(([id, code]) => ({ id, code }))
|
||||
|
||||
const outFile = resolve(options.cwd || process.cwd(), options.outFile ?? 'uno.css')
|
||||
|
||||
const preTransform = await transformFiles(sourceCache, 'pre')
|
||||
const defaultTransform = await transformFiles(preTransform)
|
||||
const postTransform = await transformFiles(defaultTransform, 'post')
|
||||
|
||||
// update source file
|
||||
await Promise.all(
|
||||
postTransform
|
||||
.filter(({ transformedCode }) => !!transformedCode)
|
||||
.map(({ transformedCode, id }) => new Promise<void>((resolve) => {
|
||||
if (existsSync(id))
|
||||
fs.writeFile(id, transformedCode as string, 'utf-8').then(resolve)
|
||||
})),
|
||||
)
|
||||
|
||||
const { css, matched } = await ctx.uno.generate(
|
||||
[...postTransform.map(({ code, transformedCode }) => transformedCode ?? code)].join('\n'),
|
||||
{
|
||||
preflights: options.preflights,
|
||||
minify: options.minify,
|
||||
},
|
||||
)
|
||||
|
||||
const dir = dirname(outFile)
|
||||
if (!existsSync(dir))
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await fs.writeFile(outFile, css, 'utf-8')
|
||||
|
||||
if (!options.watch) {
|
||||
consola.success(
|
||||
`${[...matched].length} utilities generated to ${cyan(
|
||||
relative(process.cwd(), outFile),
|
||||
)}\n`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
packages/gpt-runner-cli/src/types.ts
Normal file
16
packages/gpt-runner-cli/src/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/** Mark some properties as required, leaving others unchanged */
|
||||
declare type MarkRequired<T, RK extends keyof T> = Exclude<T, RK> & Required<Pick<T, RK>>
|
||||
|
||||
export interface CliOptions {
|
||||
cwd?: string
|
||||
patterns?: Array<string>
|
||||
outFile?: string
|
||||
watch?: boolean
|
||||
config?: string
|
||||
|
||||
// generate options
|
||||
preflights?: boolean
|
||||
minify?: boolean
|
||||
}
|
||||
|
||||
export type ResolvedCliOptions = MarkRequired<CliOptions, 'patterns'>
|
||||
22
packages/gpt-runner-cli/src/watcher.ts
Normal file
22
packages/gpt-runner-cli/src/watcher.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { FSWatcher } from 'chokidar'
|
||||
import type { CliOptions } from './types'
|
||||
|
||||
let watcher: FSWatcher
|
||||
|
||||
export async function getWatcher(options?: CliOptions) {
|
||||
// test case entry without options
|
||||
if (watcher && !options)
|
||||
return watcher
|
||||
|
||||
const { watch } = await import('chokidar')
|
||||
const ignored = ['**/{.git,node_modules}/**']
|
||||
// cli may create multiple watchers
|
||||
const newWatcher = watch(options?.patterns as string[], {
|
||||
ignoreInitial: true,
|
||||
ignorePermissionErrors: true,
|
||||
ignored,
|
||||
cwd: options?.cwd || process.cwd(),
|
||||
})
|
||||
watcher = newWatcher
|
||||
return newWatcher
|
||||
}
|
||||
12
packages/gpt-runner-config/build.config.ts
Normal file
12
packages/gpt-runner-config/build.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
],
|
||||
clean: true,
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
})
|
||||
43
packages/gpt-runner-config/package.json
Normal file
43
packages/gpt-runner-config/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@nicepkg/gpt-runner-config",
|
||||
"version": "0.0.1",
|
||||
"description": "Config loader for GPT Runner",
|
||||
"author": "Jinming Yang <2214962083@qq.com>",
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/2214962083",
|
||||
"homepage": "https://github.com/nicepkg/gpt-runner/tree/main/packages/gpt-runner-config#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nicepkg/gpt-runner",
|
||||
"directory": "packages/gpt-runner-config"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nicepkg/gpt-runner/issues"
|
||||
},
|
||||
"keywords": [],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.cjs",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nicepkg/gpt-runner-core": "workspace:*",
|
||||
"unconfig": "^0.3.7"
|
||||
}
|
||||
}
|
||||
68
packages/gpt-runner-config/src/index.ts
Normal file
68
packages/gpt-runner-config/src/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import type { UserConfig, UserConfigDefaults } from '@nicepkg/gpt-runner'
|
||||
import type { LoadConfigResult, LoadConfigSource } from 'unconfig'
|
||||
import { createConfigLoader as createLoader } from 'unconfig'
|
||||
|
||||
export type { LoadConfigResult, LoadConfigSource }
|
||||
|
||||
export async function loadConfig<U extends UserConfig>(
|
||||
cwd = process.cwd(),
|
||||
configOrPath: string | U = cwd,
|
||||
extraConfigSources: LoadConfigSource[] = [],
|
||||
defaults: UserConfigDefaults = {},
|
||||
): Promise<LoadConfigResult<U>> {
|
||||
let inlineConfig = {} as U
|
||||
if (typeof configOrPath !== 'string') {
|
||||
inlineConfig = configOrPath
|
||||
if (inlineConfig.configFile === false) {
|
||||
return {
|
||||
config: inlineConfig as U,
|
||||
sources: [],
|
||||
}
|
||||
}
|
||||
else {
|
||||
configOrPath = inlineConfig.configFile || process.cwd()
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = resolve(configOrPath)
|
||||
|
||||
let isFile = false
|
||||
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
|
||||
isFile = true
|
||||
cwd = dirname(resolved)
|
||||
}
|
||||
|
||||
const loader = createLoader<U>({
|
||||
sources: isFile
|
||||
? [
|
||||
{
|
||||
files: resolved,
|
||||
extensions: [],
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
files: [
|
||||
'gpt-runner.config',
|
||||
'gpt.config',
|
||||
],
|
||||
},
|
||||
...extraConfigSources,
|
||||
],
|
||||
cwd,
|
||||
defaults: inlineConfig,
|
||||
})
|
||||
|
||||
const result = await loader.load()
|
||||
result.config = Object.assign(defaults, result.config || inlineConfig)
|
||||
if (result.config.configDeps) {
|
||||
result.sources = [
|
||||
...result.sources,
|
||||
...result.config.configDeps.map(i => resolve(cwd, i)),
|
||||
]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
1
packages/gpt-runner-core/README.md
Normal file
1
packages/gpt-runner-core/README.md
Normal file
@@ -0,0 +1 @@
|
||||
no...
|
||||
16
packages/gpt-runner-core/build.config.ts
Normal file
16
packages/gpt-runner-core/build.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
],
|
||||
clean: true,
|
||||
declaration: true,
|
||||
externals: [
|
||||
'unconfig',
|
||||
],
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
inlineDependencies: true,
|
||||
},
|
||||
})
|
||||
44
packages/gpt-runner-core/package.json
Normal file
44
packages/gpt-runner-core/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@nicepkg/gpt-runner-core",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "Jinming Yang <2214962083@qq.com>",
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/2214962083",
|
||||
"homepage": "https://github.com/nicepkg/gpt-runner/tree/main/packages/gpt-runner-core#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nicepkg/gpt-runner",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nicepkg/gpt-runner/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"gpt-runner",
|
||||
"chatgpt",
|
||||
"prompt",
|
||||
"ai"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.cjs",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"devDependencies": {
|
||||
"unconfig": "^0.3.7"
|
||||
}
|
||||
}
|
||||
1
packages/gpt-runner-core/src/index.ts
Normal file
1
packages/gpt-runner-core/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const pkg = 'core'
|
||||
1
packages/gpt-runner-shared/README.md
Normal file
1
packages/gpt-runner-shared/README.md
Normal file
@@ -0,0 +1 @@
|
||||
no...
|
||||
8
packages/gpt-runner-shared/package.json
Normal file
8
packages/gpt-runner-shared/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@nicepkg/gpt-runner-shared",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@nicepkg/gpt-runner-core": "workspace:*"
|
||||
}
|
||||
}
|
||||
1
packages/gpt-runner-shared/src/index.ts
Normal file
1
packages/gpt-runner-shared/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const pkg = 'shared'
|
||||
18
packages/gpt-runner-vscode/.vscode/launch.json
vendored
Normal file
18
packages/gpt-runner-vscode/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
packages/gpt-runner-vscode/.vscode/tasks.json
vendored
Normal file
26
packages/gpt-runner-vscode/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"base": "$ts-webpack-watch",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "Build start",
|
||||
"endsPattern": "Build success"
|
||||
}
|
||||
}
|
||||
],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
packages/gpt-runner-vscode/.vscodeignore
Normal file
4
packages/gpt-runner-vscode/.vscodeignore
Normal file
@@ -0,0 +1,4 @@
|
||||
.github/**
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
scripts/**
|
||||
1
packages/gpt-runner-vscode/README.md
Normal file
1
packages/gpt-runner-vscode/README.md
Normal file
@@ -0,0 +1 @@
|
||||
no...
|
||||
78
packages/gpt-runner-vscode/package.json
Normal file
78
packages/gpt-runner-vscode/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"publisher": "2214962083",
|
||||
"name": "@nicepkg/gpt-runner-vscode",
|
||||
"displayName": "GPT Runner",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "GPT Runner for VS Code",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nicepkg/gpt-runner",
|
||||
"directory": "packages/gpt-runner-vscode"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"preview": true,
|
||||
"icon": "res/logo.png",
|
||||
"engines": {
|
||||
"vscode": "^1.71.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onStartupFinished"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "gpt-runner.reload",
|
||||
"title": "Reload GPT Runner",
|
||||
"category": "GPT Runner"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "GPT Runner",
|
||||
"properties": {
|
||||
"gpt-runner.disable": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Disable the GPT Runner extension"
|
||||
},
|
||||
"gpt-runner.languageIds": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"gpt-runner.root": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Project root that contains the GPT Runner configuration file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch src",
|
||||
"publish": "esno ./scripts/publish.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.71.0",
|
||||
"@nicepkg/gpt-runner": "workspace:*",
|
||||
"esno": "^0.16.3",
|
||||
"jiti": "^1.18.2",
|
||||
"prettier": "^2.8.8",
|
||||
"tsup": "^6.7.0",
|
||||
"unconfig": "^0.3.7"
|
||||
}
|
||||
}
|
||||
BIN
packages/gpt-runner-vscode/res/logo.png
Normal file
BIN
packages/gpt-runner-vscode/res/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
30
packages/gpt-runner-vscode/scripts/publish.ts
Normal file
30
packages/gpt-runner-vscode/scripts/publish.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable no-console */
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import fs from 'fs-extra'
|
||||
import { execa } from 'execa'
|
||||
|
||||
const dir = typeof __dirname === 'string' ? __dirname : dirname(fileURLToPath(import.meta.url))
|
||||
const root = dirname(dir)
|
||||
|
||||
async function publish() {
|
||||
const pkgPath = join(root, 'package.json')
|
||||
const rawJSON = await fs.readFile(pkgPath, 'utf-8')
|
||||
const pkg = JSON.parse(rawJSON)
|
||||
pkg.name = 'gpt-runner'
|
||||
await fs.writeJSON(pkgPath, pkg, { spaces: 2 })
|
||||
|
||||
await execa('npm', ['run', 'build'], { cwd: root, stdio: 'inherit' })
|
||||
|
||||
try {
|
||||
console.log('\nPublish to VSCE...\n')
|
||||
await execa('npx', ['vsce', 'publish', '--no-dependencies', '-p', process.env.VSCE_TOKEN!], { cwd: root, stdio: 'inherit' })
|
||||
// console.log('\nPublish to OVSE...\n')
|
||||
// await execa('npx', ['ovsx', 'publish', '--no-dependencies', '-p', process.env.OVSX_TOKEN!], { cwd: root, stdio: 'inherit' })
|
||||
}
|
||||
finally {
|
||||
await fs.writeFile(pkgPath, rawJSON, 'utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
publish()
|
||||
180
packages/gpt-runner-vscode/src/annotation.ts
Normal file
180
packages/gpt-runner-vscode/src/annotation.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import path from 'path'
|
||||
import type { DecorationOptions, ExtensionContext, StatusBarItem } from 'vscode'
|
||||
import { DecorationRangeBehavior, MarkdownString, Range, window, workspace } from 'vscode'
|
||||
import { INCLUDE_COMMENT_IDE, getMatchedPositionsFromCode, isCssId } from './integration'
|
||||
import { log } from './log'
|
||||
import { getColorString, getPrettiedMarkdown, isSubdir, throttle } from './utils'
|
||||
import type { ContextLoader } from './contextLoader'
|
||||
|
||||
export async function registerAnnotations(
|
||||
cwd: string,
|
||||
contextLoader: ContextLoader,
|
||||
status: StatusBarItem,
|
||||
ext: ExtensionContext,
|
||||
) {
|
||||
let underline: boolean = workspace.getConfiguration().get('gpt-runner.underline') ?? true
|
||||
let colorPreview: boolean = workspace.getConfiguration().get('gpt-runner.colorPreview') ?? true
|
||||
|
||||
ext.subscriptions.push(workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration('gpt-runner.underline')) {
|
||||
underline = workspace.getConfiguration().get('gpt-runner.underline') ?? true
|
||||
updateAnnotation()
|
||||
}
|
||||
if (event.affectsConfiguration('gpt-runner.colorPreview')) {
|
||||
colorPreview = workspace.getConfiguration().get('gpt-runner.colorPreview') ?? true
|
||||
updateAnnotation()
|
||||
}
|
||||
}))
|
||||
|
||||
workspace.onDidSaveTextDocument(async (doc) => {
|
||||
const id = doc.uri.fsPath
|
||||
const dir = path.dirname(id)
|
||||
|
||||
if (contextLoader.contextsMap.has(dir)) {
|
||||
const ctx = contextLoader.contextsMap.get(dir)!
|
||||
if (!ctx.getConfigFileList().includes(id))
|
||||
return
|
||||
try {
|
||||
await ctx.reloadConfig()
|
||||
log.appendLine(`🛠 Config reloaded by ${path.relative(cwd, doc.uri.fsPath)}`)
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine('⚠️ Error on loading config')
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const UnderlineDecoration = window.createTextEditorDecorationType({
|
||||
textDecoration: 'none; border-bottom: 1px dashed currentColor',
|
||||
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
|
||||
})
|
||||
|
||||
const NoneDecoration = window.createTextEditorDecorationType({
|
||||
textDecoration: 'none',
|
||||
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
|
||||
})
|
||||
|
||||
const colorDecoration = window.createTextEditorDecorationType({
|
||||
before: {
|
||||
width: '0.9em',
|
||||
height: '0.9em',
|
||||
contentText: ' ',
|
||||
border: '1px solid',
|
||||
margin: 'auto 0.2em auto 0;vertical-align: middle;border-radius:50%;',
|
||||
},
|
||||
dark: {
|
||||
before: {
|
||||
borderColor: '#eeeeee50',
|
||||
},
|
||||
},
|
||||
light: {
|
||||
before: {
|
||||
borderColor: '#00000050',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
async function updateAnnotation(editor = window.activeTextEditor) {
|
||||
try {
|
||||
const doc = editor?.document
|
||||
if (!doc)
|
||||
return reset()
|
||||
|
||||
const id = doc.uri.fsPath
|
||||
if (!isSubdir(cwd, id))
|
||||
return reset()
|
||||
|
||||
const code = doc.getText()
|
||||
if (!code)
|
||||
return reset()
|
||||
|
||||
let ctx = await contextLoader.resolveContext(code, id)
|
||||
if (!ctx)
|
||||
ctx = await contextLoader.resolveClosestContext(code, id)
|
||||
|
||||
const isTarget = ctx.filter(code, id) // normal gpt runner filter
|
||||
|| code.includes(INCLUDE_COMMENT_IDE) // force include
|
||||
|| contextLoader.configSources.includes(id) // include config files
|
||||
|| isCssId(id) // include css files
|
||||
|
||||
if (!isTarget)
|
||||
return reset()
|
||||
|
||||
const result = await ctx.uno.generate(code, { id, preflights: false, minify: true })
|
||||
|
||||
const colorRanges: DecorationOptions[] = []
|
||||
|
||||
const ranges: DecorationOptions[] = (
|
||||
await Promise.all(
|
||||
(await getMatchedPositionsFromCode(ctx.uno, code))
|
||||
.map(async (i): Promise<DecorationOptions> => {
|
||||
try {
|
||||
const md = await getPrettiedMarkdown(ctx!.uno, i[2])
|
||||
|
||||
if (colorPreview) {
|
||||
const color = getColorString(md)
|
||||
if (color && !colorRanges.find(r => r.range.start.isEqual(doc.positionAt(i[0])))) {
|
||||
colorRanges.push({
|
||||
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
|
||||
renderOptions: { before: { backgroundColor: color } },
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
|
||||
get hoverMessage() {
|
||||
return new MarkdownString(md)
|
||||
},
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine(`⚠️ Failed to parse ${i[2]}`)
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
return undefined!
|
||||
}
|
||||
}),
|
||||
)
|
||||
).filter(Boolean)
|
||||
|
||||
editor.setDecorations(colorDecoration, colorRanges)
|
||||
|
||||
if (underline) {
|
||||
editor.setDecorations(NoneDecoration, [])
|
||||
editor.setDecorations(UnderlineDecoration, ranges)
|
||||
}
|
||||
else {
|
||||
editor.setDecorations(UnderlineDecoration, [])
|
||||
editor.setDecorations(NoneDecoration, ranges)
|
||||
}
|
||||
|
||||
status.text = `GPT Runner: ${result.matched.size}`
|
||||
status.tooltip = new MarkdownString(`${result.matched.size} utilities used in this file`)
|
||||
status.show()
|
||||
|
||||
function reset() {
|
||||
editor?.setDecorations(UnderlineDecoration, [])
|
||||
editor?.setDecorations(NoneDecoration, [])
|
||||
editor?.setDecorations(colorDecoration, [])
|
||||
status.hide()
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine('⚠️ Error on annotation')
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
}
|
||||
}
|
||||
|
||||
const throttledUpdateAnnotation = throttle(updateAnnotation, 200)
|
||||
|
||||
window.onDidChangeActiveTextEditor(updateAnnotation)
|
||||
workspace.onDidChangeTextDocument((e) => {
|
||||
if (e.document === window.activeTextEditor?.document)
|
||||
throttledUpdateAnnotation()
|
||||
})
|
||||
contextLoader.events.on('reload', async () => {
|
||||
await updateAnnotation()
|
||||
})
|
||||
|
||||
await updateAnnotation()
|
||||
}
|
||||
181
packages/gpt-runner-vscode/src/autocomplete.ts
Normal file
181
packages/gpt-runner-vscode/src/autocomplete.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import type { UnocssAutocomplete } from '@unocss/autocomplete'
|
||||
import { createAutocomplete } from '@unocss/autocomplete'
|
||||
import type { CompletionItemProvider, Disposable, ExtensionContext } from 'vscode'
|
||||
import { CompletionItem, CompletionItemKind, CompletionList, MarkdownString, Range, languages, window, workspace } from 'vscode'
|
||||
import type { UnoGenerator, UnocssPluginContext } from '@unocss/core'
|
||||
import { getCSS, getColorString, getPrettiedCSS, getPrettiedMarkdown, isSubdir } from './utils'
|
||||
import { log } from './log'
|
||||
import type { ContextLoader } from './contextLoader'
|
||||
import { isCssId } from './integration'
|
||||
|
||||
const defaultLanguageIds = [
|
||||
'erb',
|
||||
'haml',
|
||||
'hbs',
|
||||
'html',
|
||||
'css',
|
||||
'postcss',
|
||||
'javascript',
|
||||
'javascriptreact',
|
||||
'markdown',
|
||||
'ejs',
|
||||
'php',
|
||||
'svelte',
|
||||
'typescript',
|
||||
'typescriptreact',
|
||||
'vue-html',
|
||||
'vue',
|
||||
'sass',
|
||||
'scss',
|
||||
'less',
|
||||
'stylus',
|
||||
'astro',
|
||||
'rust',
|
||||
]
|
||||
const delimiters = ['-', ':']
|
||||
|
||||
class UnoCompletionItem extends CompletionItem {
|
||||
uno: UnoGenerator
|
||||
|
||||
constructor(label: string, kind: CompletionItemKind, uno: UnoGenerator) {
|
||||
super(label, kind)
|
||||
this.uno = uno
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerAutoComplete(
|
||||
cwd: string,
|
||||
contextLoader: ContextLoader,
|
||||
ext: ExtensionContext,
|
||||
) {
|
||||
const allLanguages = await languages.getLanguages()
|
||||
const autoCompletes = new Map<UnocssPluginContext, UnocssAutocomplete>()
|
||||
contextLoader.events.on('contextReload', (ctx) => {
|
||||
autoCompletes.delete(ctx)
|
||||
})
|
||||
contextLoader.events.on('contextUnload', (ctx) => {
|
||||
autoCompletes.delete(ctx)
|
||||
})
|
||||
|
||||
function getAutocomplete(ctx: UnocssPluginContext) {
|
||||
const cached = autoCompletes.get(ctx)
|
||||
if (cached)
|
||||
return cached
|
||||
|
||||
const autocomplete = createAutocomplete(ctx.uno)
|
||||
|
||||
autoCompletes.set(ctx, autocomplete)
|
||||
return autocomplete
|
||||
}
|
||||
|
||||
async function getMarkdown(uno: UnoGenerator, util: string) {
|
||||
return new MarkdownString(await getPrettiedMarkdown(uno, util))
|
||||
}
|
||||
|
||||
function validateLanguages(targets: string[]) {
|
||||
const unValidLanguages: string[] = []
|
||||
const validLanguages = targets.filter((language) => {
|
||||
if (!allLanguages.includes(language)) {
|
||||
unValidLanguages.push(language)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (unValidLanguages.length)
|
||||
window.showWarningMessage(`These language configurations are illegal: ${unValidLanguages.join(',')}`)
|
||||
|
||||
return validLanguages
|
||||
}
|
||||
|
||||
const provider: CompletionItemProvider<UnoCompletionItem> = {
|
||||
async provideCompletionItems(doc, position) {
|
||||
const id = doc.uri.fsPath
|
||||
if (!isSubdir(cwd, id))
|
||||
return null
|
||||
|
||||
const code = doc.getText()
|
||||
if (!code)
|
||||
return null
|
||||
|
||||
let ctx = await contextLoader.resolveContext(code, id)
|
||||
if (!ctx)
|
||||
ctx = await contextLoader.resolveClosestContext(code, id)
|
||||
|
||||
if (!ctx.filter(code, id) && !isCssId(id))
|
||||
return null
|
||||
|
||||
try {
|
||||
const autoComplete = getAutocomplete(ctx)
|
||||
|
||||
const result = await autoComplete.suggestInFile(code, doc.offsetAt(position))
|
||||
|
||||
log.appendLine(`🤖 ${id} | ${result.suggestions.slice(0, 10).map(v => `[${v[0]}, ${v[1]}]`).join(', ')}`)
|
||||
|
||||
if (!result.suggestions.length)
|
||||
return
|
||||
|
||||
const completionItems: UnoCompletionItem[] = []
|
||||
for (const [value, label] of result.suggestions) {
|
||||
const css = await getCSS(ctx!.uno, value)
|
||||
const colorString = getColorString(css)
|
||||
const itemKind = colorString ? CompletionItemKind.Color : CompletionItemKind.EnumMember
|
||||
const item = new UnoCompletionItem(value, itemKind, ctx!.uno)
|
||||
const resolved = result.resolveReplacement(value)
|
||||
|
||||
item.insertText = resolved.replacement
|
||||
item.range = new Range(doc.positionAt(resolved.start), doc.positionAt(resolved.end))
|
||||
|
||||
if (colorString) {
|
||||
item.documentation = colorString
|
||||
item.sortText = /-\d$/.test(label) ? '1' : '2' // reorder color completions
|
||||
}
|
||||
completionItems.push(item)
|
||||
}
|
||||
|
||||
return new CompletionList(completionItems, true)
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine('⚠️ Error on getting autocompletion items')
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
async resolveCompletionItem(item) {
|
||||
if (item.kind === CompletionItemKind.Color)
|
||||
item.detail = await (await getPrettiedCSS(item.uno, item.label as string)).prettified
|
||||
else
|
||||
item.documentation = await getMarkdown(item.uno, item.label as string)
|
||||
return item
|
||||
},
|
||||
}
|
||||
|
||||
let completeUnregister: Disposable
|
||||
|
||||
const registerProvider = () => {
|
||||
completeUnregister?.dispose?.()
|
||||
|
||||
const languagesIds: string[] = workspace.getConfiguration().get('unocss.languageIds') || []
|
||||
|
||||
const validLanguages = validateLanguages(languagesIds)
|
||||
|
||||
completeUnregister = languages.registerCompletionItemProvider(
|
||||
defaultLanguageIds.concat(validLanguages),
|
||||
provider,
|
||||
...delimiters,
|
||||
)
|
||||
return completeUnregister
|
||||
}
|
||||
|
||||
ext.subscriptions.push(workspace.onDidChangeConfiguration(async (event) => {
|
||||
if (event.affectsConfiguration('unocss.languageIds')) {
|
||||
ext.subscriptions.push(
|
||||
registerProvider(),
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
ext.subscriptions.push(
|
||||
registerProvider(),
|
||||
)
|
||||
}
|
||||
261
packages/gpt-runner-vscode/src/contextLoader.ts
Normal file
261
packages/gpt-runner-vscode/src/contextLoader.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { readdir } from 'fs/promises'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import type { UnocssPluginContext, UserConfig, UserConfigDefaults } from '@unocss/core'
|
||||
import { notNull } from '@unocss/core'
|
||||
import { sourceObjectFields, sourcePluginFactory } from 'unconfig/presets'
|
||||
import presetUno from '@unocss/preset-uno'
|
||||
import { resolveOptions as resolveNuxtOptions } from '../../nuxt/src/options'
|
||||
import { createNanoEvents } from '../../core/src/utils/events'
|
||||
import { createContext, isCssId } from './integration'
|
||||
import { isSubdir } from './utils'
|
||||
import { log } from './log'
|
||||
|
||||
export class ContextLoader {
|
||||
public cwd: string
|
||||
public ready: Promise<void>
|
||||
public defaultContext: UnocssPluginContext<UserConfig<any>>
|
||||
public contextsMap = new Map<string, UnocssPluginContext<UserConfig<any>> | null>()
|
||||
public configSources: string[] = []
|
||||
|
||||
private fileContextCache = new Map<string, UnocssPluginContext<UserConfig<any>> | null>()
|
||||
private configExistsCache = new Map<string, boolean>()
|
||||
private defaultUnocssConfig: UserConfigDefaults = {
|
||||
presets: [presetUno()],
|
||||
}
|
||||
|
||||
public events = createNanoEvents<{
|
||||
reload: () => void
|
||||
contextLoaded: (context: UnocssPluginContext<UserConfig<any>>) => void
|
||||
contextReload: (context: UnocssPluginContext<UserConfig<any>>) => void
|
||||
contextUnload: (context: UnocssPluginContext<UserConfig<any>>) => void
|
||||
}>()
|
||||
|
||||
constructor(cwd: string) {
|
||||
this.cwd = cwd
|
||||
this.defaultContext = createContext(this.defaultUnocssConfig)
|
||||
|
||||
this.ready = this.reload()
|
||||
.then(async () => {
|
||||
await this.defaultContext.ready
|
||||
})
|
||||
}
|
||||
|
||||
get contexts() {
|
||||
return Array.from(new Set(this.contextsMap.values())).filter(notNull)
|
||||
}
|
||||
|
||||
async reload() {
|
||||
this.ready = this._reload()
|
||||
await this.ready
|
||||
this.events.emit('reload')
|
||||
}
|
||||
|
||||
private async _reload() {
|
||||
for (const dir of this.contextsMap.keys())
|
||||
this.unloadContext(dir)
|
||||
this.fileContextCache.clear()
|
||||
this.configExistsCache.clear()
|
||||
|
||||
await this.loadContextInDirectory(this.cwd)
|
||||
}
|
||||
|
||||
async unloadContext(configDir: string) {
|
||||
const context = this.contextsMap.get(configDir)
|
||||
if (!context)
|
||||
return
|
||||
|
||||
this.contextsMap.delete(configDir)
|
||||
|
||||
for (const [path, ctx] of this.fileContextCache) {
|
||||
if (ctx === context)
|
||||
this.fileContextCache.delete(path)
|
||||
}
|
||||
|
||||
this.events.emit('contextUnload', context)
|
||||
}
|
||||
|
||||
async configExists(dir: string) {
|
||||
if (!this.configExistsCache.has(dir)) {
|
||||
const files = await readdir(dir)
|
||||
this.configExistsCache.set(dir, files.some(f => /^(vite|svelte|astro|iles|nuxt|unocss|uno)\.config/.test(f)))
|
||||
}
|
||||
return this.configExistsCache.get(dir)!
|
||||
}
|
||||
|
||||
async loadContextInDirectory(dir: string) {
|
||||
const cached = this.contextsMap.get(dir)
|
||||
if (cached !== undefined)
|
||||
return cached
|
||||
|
||||
const load = async () => {
|
||||
log.appendLine(`🛠 Resolving config for ${dir}`)
|
||||
|
||||
// @ts-expect-error support global utils
|
||||
globalThis.defineNuxtConfig = config => config
|
||||
|
||||
const context = createContext(
|
||||
dir,
|
||||
this.defaultUnocssConfig,
|
||||
[
|
||||
sourcePluginFactory({
|
||||
files: [
|
||||
'vite.config',
|
||||
'svelte.config',
|
||||
'iles.config',
|
||||
],
|
||||
targetModule: 'unocss/vite',
|
||||
parameters: [{ command: 'serve', mode: 'development' }],
|
||||
}),
|
||||
sourcePluginFactory({
|
||||
files: [
|
||||
'astro.config',
|
||||
],
|
||||
targetModule: 'unocss/astro',
|
||||
}),
|
||||
sourceObjectFields({
|
||||
files: 'nuxt.config',
|
||||
fields: 'unocss',
|
||||
}),
|
||||
],
|
||||
(result) => {
|
||||
if (result.sources.some(s => s.includes('nuxt.config')))
|
||||
resolveNuxtOptions(result.config)
|
||||
},
|
||||
)
|
||||
|
||||
context.updateRoot(dir)
|
||||
let sources = []
|
||||
try {
|
||||
sources = (await context.ready).sources
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine(`⚠️ Error on loading config. Config directory: ${dir}`)
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
|
||||
this.configSources = sources
|
||||
|
||||
if (!sources.length)
|
||||
return null
|
||||
|
||||
const baseDir = path.dirname(sources[0])
|
||||
if (baseDir !== dir) {
|
||||
// exists on upper level, skip
|
||||
this.contextsMap.set(dir, null)
|
||||
return null
|
||||
}
|
||||
|
||||
context.onReload(() => {
|
||||
for (const [path, ctx] of this.fileContextCache) {
|
||||
if (ctx === context || !ctx)
|
||||
this.fileContextCache.delete(path)
|
||||
}
|
||||
this.configExistsCache.clear()
|
||||
this.events.emit('contextReload', context)
|
||||
})
|
||||
|
||||
for (const [path, ctx] of this.fileContextCache) {
|
||||
if (!ctx)
|
||||
this.fileContextCache.delete(path)
|
||||
}
|
||||
|
||||
this.events.emit('contextLoaded', context)
|
||||
|
||||
log.appendLine(`🛠 New configuration loaded from\n${sources.map(s => ` - ${s}`).join('\n')}`)
|
||||
log.appendLine(`ℹ️ ${context.uno.config.presets.length} presets, ${context.uno.config.rulesSize} rules, ${context.uno.config.shortcuts.length} shortcuts, ${context.uno.config.variants.length} variants, ${context.uno.config.transformers?.length || 0} transformers loaded`)
|
||||
|
||||
if (!sources.some(i => i.match(/\buno(css)?\.config\./))) {
|
||||
log.appendLine('💡 To have the best IDE experience, it\'s recommended to move UnoCSS configurations into a standalone `uno.config.ts` file at the root of your project.')
|
||||
log.appendLine('👉 Learn more at https://unocss.dev/guide/config-file')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const context = await load()
|
||||
if (!this.contextsMap.has(dir))
|
||||
this.contextsMap.set(dir, context)
|
||||
return context
|
||||
}
|
||||
|
||||
async resolveContext(code: string, file: string) {
|
||||
if (file.match(/[\/](node_modules|dist|\.temp|\.cache)[\/]/g))
|
||||
return
|
||||
|
||||
const cached = this.fileContextCache.get(file)
|
||||
if (cached !== undefined)
|
||||
return cached
|
||||
|
||||
// try finding an existing context that includes the file
|
||||
for (const [configDir, context] of this.contextsMap) {
|
||||
if (!context)
|
||||
continue
|
||||
|
||||
if (!isSubdir(configDir, file))
|
||||
continue
|
||||
|
||||
if (!context.filter(code, file) && !isCssId(file))
|
||||
continue
|
||||
|
||||
this.fileContextCache.set(file, context)
|
||||
return context
|
||||
}
|
||||
|
||||
// try finding a config from disk
|
||||
if (fs.existsSync(file)) {
|
||||
let dir = path.dirname(file)
|
||||
while (isSubdir(this.cwd, dir)) {
|
||||
if (await this.configExists(dir)) {
|
||||
const context = await this.loadContextInDirectory(dir)
|
||||
if (context?.filter(code, file) || isCssId(file)) {
|
||||
this.fileContextCache.set(file, context)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
const newDir = path.dirname(dir)
|
||||
if (newDir === dir)
|
||||
break
|
||||
dir = newDir
|
||||
}
|
||||
}
|
||||
|
||||
this.fileContextCache.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
async resolveClosestContext(code: string, file: string) {
|
||||
const cached = this.fileContextCache.get(file)
|
||||
if (cached)
|
||||
return cached
|
||||
|
||||
for (const [configDir, context] of this.contextsMap) {
|
||||
if (!context)
|
||||
continue
|
||||
|
||||
if (!isSubdir(configDir, file))
|
||||
continue
|
||||
|
||||
if (!context.filter(code, file) && !isCssId(file))
|
||||
continue
|
||||
|
||||
this.fileContextCache.set(file, context)
|
||||
return context
|
||||
}
|
||||
|
||||
for (const [configDir, context] of this.contextsMap) {
|
||||
if (!context)
|
||||
continue
|
||||
|
||||
if (isSubdir(configDir, file)) {
|
||||
this.fileContextCache.set(file, context)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
return this.defaultContext
|
||||
}
|
||||
}
|
||||
90
packages/gpt-runner-vscode/src/index.ts
Normal file
90
packages/gpt-runner-vscode/src/index.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import path from 'path'
|
||||
import type { ExtensionContext, StatusBarItem } from 'vscode'
|
||||
import { StatusBarAlignment, commands, window, workspace } from 'vscode'
|
||||
import { version } from '../package.json'
|
||||
import { log } from './log'
|
||||
import { registerAnnotations } from './annotation'
|
||||
import { registerAutoComplete } from './autocomplete'
|
||||
import { ContextLoader } from './contextLoader'
|
||||
import { registerSelectionStyle } from './selectionStyle'
|
||||
|
||||
async function registerRoot(ext: ExtensionContext, status: StatusBarItem, cwd: string) {
|
||||
const contextLoader = new ContextLoader(cwd)
|
||||
|
||||
await contextLoader.ready
|
||||
|
||||
const hasConfig = await contextLoader.loadContextInDirectory(cwd)
|
||||
// TODO: improve this to re-enable after configuration created
|
||||
if (!hasConfig)
|
||||
throw new Error(`➖ No config found in ${cwd}`)
|
||||
|
||||
registerAutoComplete(cwd, contextLoader, ext)
|
||||
registerAnnotations(cwd, contextLoader, status, ext)
|
||||
registerSelectionStyle(cwd, contextLoader)
|
||||
|
||||
return contextLoader
|
||||
}
|
||||
|
||||
export async function activate(ext: ExtensionContext) {
|
||||
log.appendLine(`⚪️ UnoCSS for VS Code v${version}\n`)
|
||||
|
||||
const projectPath = workspace.workspaceFolders?.[0].uri.fsPath
|
||||
if (!projectPath) {
|
||||
log.appendLine('➖ No active workspace found, UnoCSS is disabled')
|
||||
return
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('unocss')
|
||||
const disabled = config.get<boolean>('disable', false)
|
||||
if (disabled) {
|
||||
log.appendLine('➖ Disabled by configuration')
|
||||
return
|
||||
}
|
||||
|
||||
const status = window.createStatusBarItem(StatusBarAlignment.Right, 200)
|
||||
status.text = 'UnoCSS'
|
||||
|
||||
const root = config.get<string | string[]>('root')
|
||||
|
||||
if (Array.isArray(root) && root.length) {
|
||||
const cwds = root.map(dir => path.resolve(projectPath, dir))
|
||||
|
||||
try {
|
||||
const contextLoaders = await Promise.all(cwds.map(cwd => registerRoot(ext, status, cwd)))
|
||||
|
||||
ext.subscriptions.push(
|
||||
commands.registerCommand('unocss.reload', async () => {
|
||||
log.appendLine('🔁 Reloading...')
|
||||
await Promise.all(contextLoaders.map(ctxLoader => ctxLoader.reload))
|
||||
log.appendLine('✅ Reloaded.')
|
||||
}),
|
||||
)
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// now if the root is an array, then it is an empty array
|
||||
const cwd = (root && !Array.isArray(root))
|
||||
? path.resolve(projectPath, root)
|
||||
: projectPath
|
||||
|
||||
try {
|
||||
const contextLoader = await registerRoot(ext, status, cwd)
|
||||
|
||||
ext.subscriptions.push(
|
||||
commands.registerCommand('unocss.reload', async () => {
|
||||
log.appendLine('🔁 Reloading...')
|
||||
await contextLoader.reload()
|
||||
log.appendLine('✅ Reloaded.')
|
||||
}),
|
||||
)
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
}
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
2
packages/gpt-runner-vscode/src/integration.ts
Normal file
2
packages/gpt-runner-vscode/src/integration.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from '../../shared-integration/src'
|
||||
export * from '../../shared-common/src'
|
||||
3
packages/gpt-runner-vscode/src/log.ts
Normal file
3
packages/gpt-runner-vscode/src/log.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { window } from 'vscode'
|
||||
|
||||
export const log = window.createOutputChannel('UnoCSS')
|
||||
95
packages/gpt-runner-vscode/src/selectionStyle.ts
Normal file
95
packages/gpt-runner-vscode/src/selectionStyle.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { MarkdownString, Position, Range, window, workspace } from 'vscode'
|
||||
import parserCSS from 'prettier/parser-postcss'
|
||||
import prettier from 'prettier/standalone'
|
||||
import type { TextEditorSelectionChangeEvent } from 'vscode'
|
||||
import { TwoKeyMap, regexScopePlaceholder } from '@unocss/core'
|
||||
import { log } from './log'
|
||||
import { throttle } from './utils'
|
||||
import type { ContextLoader } from './contextLoader'
|
||||
import { getMatchedPositionsFromCode } from './integration'
|
||||
|
||||
export async function registerSelectionStyle(cwd: string, contextLoader: ContextLoader) {
|
||||
const hasSelectionStyle = (): boolean => workspace.getConfiguration().get('unocss.selectionStyle') ?? true
|
||||
|
||||
const integrationDecoration = window.createTextEditorDecorationType({})
|
||||
|
||||
async function selectionStyle(editor: TextEditorSelectionChangeEvent) {
|
||||
try {
|
||||
if (!hasSelectionStyle())
|
||||
return reset()
|
||||
|
||||
const doc = editor.textEditor.document
|
||||
if (!doc)
|
||||
return reset()
|
||||
|
||||
const id = doc.uri.fsPath
|
||||
const selection = editor.textEditor.selection
|
||||
const range = new Range(
|
||||
new Position(selection.start.line, selection.start.character),
|
||||
new Position(selection.end.line, selection.end.character),
|
||||
)
|
||||
let code = editor.textEditor.document.getText(range).trim()
|
||||
if (!code.startsWith('<'))
|
||||
code = `<div ${code}`
|
||||
if (!code.endsWith('>'))
|
||||
code = `${code} >`
|
||||
const ctx = await contextLoader.resolveContext(code, id) || (await contextLoader.resolveClosestContext(code, id))
|
||||
const result = await getMatchedPositionsFromCode(ctx.uno, code)
|
||||
if (result.length <= 1)
|
||||
return reset()
|
||||
|
||||
const uniqMap = new Map<string, string>()
|
||||
for (const [start, end, className] of result)
|
||||
uniqMap.set(`${start}-${end}`, className)
|
||||
|
||||
const classNamePlaceholder = '___'
|
||||
const sheetMap = new TwoKeyMap<string | undefined, string, string>()
|
||||
await Promise.all(Array.from(uniqMap.values())
|
||||
.map(async (name) => {
|
||||
const tokens = await ctx.uno.parseToken(name, classNamePlaceholder) || []
|
||||
tokens.forEach(([, className, cssText, media]) => {
|
||||
if (className && cssText) {
|
||||
const selector = className
|
||||
.replace(`.${classNamePlaceholder}`, '&')
|
||||
.replace(regexScopePlaceholder, ' ')
|
||||
.trim()
|
||||
sheetMap.set(media, selector, (sheetMap.get(media, selector) || '') + cssText)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const css = Array.from(sheetMap._map.entries())
|
||||
.map(([media, map]) => {
|
||||
const body = Array.from(map.keys())
|
||||
.sort()
|
||||
.map(selector => `${selector}{${map.get(selector)}}`)
|
||||
.join('\n')
|
||||
return media ? `${media}{${body}}` : body
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
const prettified = prettier.format(css, {
|
||||
parser: 'css',
|
||||
plugins: [parserCSS],
|
||||
})
|
||||
|
||||
editor.textEditor.setDecorations(integrationDecoration, [{
|
||||
range,
|
||||
get hoverMessage() {
|
||||
return new MarkdownString(`UnoCSS utilities in the selection will be equivalent to:\n\`\`\`css\n${prettified.trim()}\n\`\`\``)
|
||||
},
|
||||
}])
|
||||
|
||||
function reset() {
|
||||
editor.textEditor.setDecorations(integrationDecoration, [])
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
log.appendLine('⚠️ Error on selectionStyle')
|
||||
log.appendLine(String(e.stack ?? e))
|
||||
}
|
||||
}
|
||||
|
||||
window.onDidChangeTextEditorSelection(throttle(selectionStyle, 200))
|
||||
}
|
||||
124
packages/gpt-runner-vscode/src/utils.ts
Normal file
124
packages/gpt-runner-vscode/src/utils.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import path from 'path'
|
||||
import type { UnoGenerator } from '@unocss/core'
|
||||
import prettier from 'prettier/standalone'
|
||||
import parserCSS from 'prettier/parser-postcss'
|
||||
|
||||
export function throttle<T extends ((...args: any) => any)>(func: T, timeFrame: number): T {
|
||||
let lastTime = 0
|
||||
let timer: any
|
||||
return function (...args) {
|
||||
const now = Date.now()
|
||||
clearTimeout(timer)
|
||||
if (now - lastTime >= timeFrame) {
|
||||
lastTime = now
|
||||
return func(...args)
|
||||
}
|
||||
else {
|
||||
timer = setTimeout(func, timeFrame, ...args)
|
||||
}
|
||||
} as T
|
||||
}
|
||||
|
||||
export async function getCSS(uno: UnoGenerator, utilName: string) {
|
||||
const { css } = await uno.generate(utilName, { preflights: false, safelist: false })
|
||||
return css
|
||||
}
|
||||
|
||||
export async function getPrettiedCSS(uno: UnoGenerator, util: string) {
|
||||
const result = (await uno.generate(new Set([util]), { preflights: false, safelist: false }))
|
||||
const prettified = prettier.format(result.css, {
|
||||
parser: 'css',
|
||||
plugins: [parserCSS],
|
||||
})
|
||||
|
||||
return {
|
||||
...result,
|
||||
prettified,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPrettiedMarkdown(uno: UnoGenerator, util: string) {
|
||||
return `\`\`\`css\n${(await getPrettiedCSS(uno, util)).prettified}\n\`\`\``
|
||||
}
|
||||
|
||||
function getCssVariables(code: string) {
|
||||
const regex = /(?<key>--\S+?):\s*(?<value>.+?);/gm
|
||||
const cssVariables = new Map<string, string>()
|
||||
for (const match of code.matchAll(regex)) {
|
||||
const key = match.groups?.key
|
||||
if (key)
|
||||
cssVariables.set(key, match.groups?.value ?? '')
|
||||
}
|
||||
|
||||
return cssVariables
|
||||
}
|
||||
|
||||
const matchCssVarNameRegex = /var\((?<cssVarName>--[^,|)]+)(?:,\s*(?<fallback>[^)]+))?\)/gm
|
||||
const cssColorRegex = /(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\(.*\)/gm
|
||||
|
||||
/**
|
||||
* Get CSS color string from CSS string
|
||||
*
|
||||
* @example Input with CSS var
|
||||
* ```css
|
||||
*.dark [border="dark\:gray-700"] {
|
||||
* --un-border-opacity: 1;
|
||||
* border-color: rgba(55, 65, 81, var(--un-border-opacity));
|
||||
*}
|
||||
* ```
|
||||
* return `rgba(55, 65, 81, 1)`
|
||||
*
|
||||
* @example Input with no-value CSS var and its fallback value
|
||||
* ```css
|
||||
*.bg-brand-primary {
|
||||
* background-color: hsla(217, 78%, 51%, var(--no-value, 0.5));
|
||||
*}
|
||||
* ```
|
||||
* return `hsla(217, 78%, 51%, 0.5)`
|
||||
*
|
||||
* @example Input with no-value CSS var
|
||||
* ```css
|
||||
*.bg-brand-primary {
|
||||
* background-color: hsla(217, 78%, 51%, var(--no-value));
|
||||
*}
|
||||
* ```
|
||||
* return `hsla(217, 78%, 51%)`
|
||||
*
|
||||
* @param str - CSS string
|
||||
* @returns The **first** CSS color string (hex, rgb[a], hsl[a]) or `undefined`
|
||||
*/
|
||||
export function getColorString(str: string) {
|
||||
let colorString = str.match(cssColorRegex)?.[0] // e.g rgba(248, 113, 113, var(--maybe-css-var))
|
||||
|
||||
if (!colorString)
|
||||
return
|
||||
|
||||
const cssVars = getCssVariables(str)
|
||||
|
||||
// replace `var(...)` with its value
|
||||
for (const match of colorString.matchAll(matchCssVarNameRegex)) {
|
||||
const matchedString = match[0]
|
||||
const cssVarName = match.groups?.cssVarName
|
||||
const fallback = match.groups?.fallback
|
||||
|
||||
if (cssVarName && cssVars.get(cssVarName))
|
||||
// rgba(248, 113, 113, var(--un-text-opacity)) => rgba(248, 113, 113, 1)
|
||||
colorString = colorString.replaceAll(matchedString, cssVars.get(cssVarName) ?? matchedString)
|
||||
else if (fallback)
|
||||
// rgba(248, 113, 113, var(--no-value, 0.5)) => rgba(248, 113, 113, 0.5)
|
||||
colorString = colorString.replaceAll(matchedString, fallback)
|
||||
|
||||
// rgba(248, 113, 113, var(--no-value)) => rgba(248, 113, 113)
|
||||
colorString = colorString.replaceAll(/,?\s+var\(--.*?\)/gm, '')
|
||||
}
|
||||
|
||||
// if (!(new TinyColor(colorString).isValid))
|
||||
// return
|
||||
|
||||
return colorString
|
||||
}
|
||||
|
||||
export function isSubdir(parent: string, child: string) {
|
||||
const relative = path.relative(parent, child)
|
||||
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
|
||||
}
|
||||
3
packages/gpt-runner-vscode/tsconfig.json
Normal file
3
packages/gpt-runner-vscode/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
14
packages/gpt-runner-vscode/tsup.config.ts
Normal file
14
packages/gpt-runner-vscode/tsup.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'src/index.ts',
|
||||
],
|
||||
external: [
|
||||
'vscode',
|
||||
],
|
||||
format: [
|
||||
'cjs',
|
||||
],
|
||||
shims: false,
|
||||
})
|
||||
14
packages/gpt-runner/build.config.ts
Normal file
14
packages/gpt-runner/build.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
],
|
||||
clean: true,
|
||||
declaration: true,
|
||||
externals: [
|
||||
],
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
})
|
||||
2
packages/gpt-runner/index.d.ts
vendored
Normal file
2
packages/gpt-runner/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './dist/index'
|
||||
export { default } from './dist/index'
|
||||
50
packages/gpt-runner/package.json
Normal file
50
packages/gpt-runner/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@nicepkg/gpt-runner",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "Jinming Yang <2214962083@qq.com>",
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/2214962083",
|
||||
"homepage": "https://github.com/nicepkg/gpt-runner#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nicepkg/gpt-runner"
|
||||
},
|
||||
"sponsor": {
|
||||
"url": "https://github.com/sponsors/2214962083"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nicepkg/gpt-runner/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"gpt-runner",
|
||||
"chatgpt",
|
||||
"prompt",
|
||||
"ai"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.cjs",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"*.d.ts"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nicepkg/gpt-runner-core": "workspace:*"
|
||||
}
|
||||
}
|
||||
7
packages/gpt-runner/src/index.ts
Normal file
7
packages/gpt-runner/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { UserConfig } from '@nicepkg/gpt-runner-core'
|
||||
|
||||
export * from '@nicepkg/gpt-runner-core'
|
||||
|
||||
export function defineConfig(config: UserConfig) {
|
||||
return config
|
||||
}
|
||||
11
playground/package.json
Normal file
11
playground/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "playground",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
"build": "vite build",
|
||||
"update-post": "vite build",
|
||||
"serve": "vite preview"
|
||||
}
|
||||
}
|
||||
8145
pnpm-lock.yaml
generated
Normal file
8145
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
pnpm-workspace.yaml
Normal file
5
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
packages:
|
||||
- docs
|
||||
- playground
|
||||
- 'packages/*'
|
||||
- 'test/fixtures/*'
|
||||
14
scripts/copy-files.ts
Normal file
14
scripts/copy-files.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { copyFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
// relative to scripts directory
|
||||
const destinations = [
|
||||
['../LICENSE', '../packages/gpt-runner-vscode/LICENSE'],
|
||||
['../README.md', '../packages/gpt-runner-ui/README.md'],
|
||||
]
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url)
|
||||
destinations.forEach(([src, dest]) => {
|
||||
copyFileSync(resolve(_filename, '..', src), resolve(_filename, '..', dest))
|
||||
})
|
||||
26
scripts/prepare.ts
Normal file
26
scripts/prepare.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { basename, dirname, extname, relative } from 'node:path'
|
||||
import fg from 'fast-glob'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
const exportSubmodules = '/* @export-submodules */'
|
||||
|
||||
const files = await fg('packages/**/index.ts', {
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
],
|
||||
absolute: true,
|
||||
})
|
||||
|
||||
for (const file of files) {
|
||||
let content = await fs.readFile(file, 'utf-8')
|
||||
const index = content.indexOf(exportSubmodules)
|
||||
if (index !== -1) {
|
||||
const submodules = await fg(['**/*.ts'], { cwd: dirname(file), absolute: true })
|
||||
const imports = submodules
|
||||
.filter(i => i !== file)
|
||||
.map(i => relative(dirname(file), i))
|
||||
.map(i => `export * from './${basename(i, extname(i))}'`).join('\n')
|
||||
content = `${content.slice(0, index) + exportSubmodules}\n${imports}\n`
|
||||
await fs.writeFile(file, content, 'utf-8')
|
||||
}
|
||||
}
|
||||
15
test/setup.ts
Normal file
15
test/setup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { afterAll, afterEach, beforeAll } from 'vitest'
|
||||
import { setupServer } from 'msw/node'
|
||||
import { rest } from 'msw'
|
||||
|
||||
export const restHandlers = [
|
||||
rest.get(/google/, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.text('@font-face mocked {}'))
|
||||
}),
|
||||
]
|
||||
|
||||
const server = setupServer(...restHandlers)
|
||||
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
||||
afterAll(() => server.close())
|
||||
afterEach(() => server.resetHandlers())
|
||||
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client"
|
||||
],
|
||||
"paths": {
|
||||
"@nicepkg/gpt-runner": ["./packages/gpt-runner/src/index.ts"],
|
||||
"@nicepkg/gpt-runner-cli": ["./packages/gpt-runner-cli/src/index.ts"],
|
||||
"@nicepkg/gpt-runner-config": ["./packages/gpt-runner-config/src/index.ts"],
|
||||
"@nicepkg/gpt-runner-core": ["./packages/gpt-runner-core/src/index.ts"],
|
||||
"@nicepkg/gpt-runner-shared": ["./packages/gpt-runner-shared/src/index.ts"],
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"**/dist/**",
|
||||
"**/node_modules/**",
|
||||
"**/playground/**",
|
||||
"**/examples/**",
|
||||
"**/fixtures/**",
|
||||
"**/test/dts/**"
|
||||
]
|
||||
}
|
||||
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import { alias } from './alias'
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
entries: [],
|
||||
},
|
||||
resolve: {
|
||||
alias,
|
||||
},
|
||||
test: {
|
||||
setupFiles: ['./test/setup.ts'],
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user