@aebzx/slang-tool 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.mjs +16 -0
- package/command.ts +254 -0
- package/dist/cli.js +15785 -0
- package/entry.ts +90 -0
- package/model.ts +90 -0
- package/package.json +34 -0
- package/test/entry.test.ts +102 -0
- package/test/utils.test.ts +72 -0
- package/tsconfig.json +15 -0
- package/utils/ajax.ts +15 -0
- package/utils/index.ts +4 -0
- package/utils/net.ts +75 -0
- package/utils/utils.ts +58 -0
package/entry.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import command from './command.ts'
|
|
2
|
+
import {Command} from 'commander'
|
|
3
|
+
import {fileURLToPath, pathToFileURL} from 'node:url'
|
|
4
|
+
|
|
5
|
+
export function build_command(){
|
|
6
|
+
let c=new Command()
|
|
7
|
+
c.version('1.0.0')
|
|
8
|
+
c.name('slang')
|
|
9
|
+
c.description('slang工具链')
|
|
10
|
+
c.command('init')
|
|
11
|
+
.description('初始化slang项目')
|
|
12
|
+
.action(command.init)
|
|
13
|
+
c.command('compiler')
|
|
14
|
+
.description('编译slang项目')
|
|
15
|
+
.action(command.compiler)
|
|
16
|
+
c.command('run')
|
|
17
|
+
.description('运行slang项目')
|
|
18
|
+
.action(command.run)
|
|
19
|
+
c.command('go')
|
|
20
|
+
.description('编译并运行slang项目')
|
|
21
|
+
.action(command.go)
|
|
22
|
+
c.command('install')
|
|
23
|
+
.option('--name <name>', '库名')
|
|
24
|
+
.option('--version <version>', '版本号')
|
|
25
|
+
.description('安装库')
|
|
26
|
+
.action(async (options:{name:string,version:string})=>{
|
|
27
|
+
await command.install(options.name, options.version)
|
|
28
|
+
})
|
|
29
|
+
c.command('uninstall')
|
|
30
|
+
.argument('<name>','库名')
|
|
31
|
+
.description('卸载库')
|
|
32
|
+
.action(async (name:string)=>{
|
|
33
|
+
await command.uninstall(name)
|
|
34
|
+
})
|
|
35
|
+
//publish 子命令组
|
|
36
|
+
let publish=c.command('publish').description('发布')
|
|
37
|
+
publish.command('module')
|
|
38
|
+
.description('发布模块')
|
|
39
|
+
.action(command.publish_module)
|
|
40
|
+
publish.command('vm')
|
|
41
|
+
.description('发布vm')
|
|
42
|
+
.option('--path <path>', 'vm文件路径')
|
|
43
|
+
.option('--license <license>', '许可证')
|
|
44
|
+
.option('--isa <isa>','指令集')
|
|
45
|
+
.option('--version <version>','版本')
|
|
46
|
+
.action(async (options:{path:string,license:string,isa:string,version:string})=>{
|
|
47
|
+
await command.publish_vm(options.path, options.license, options.isa, options.version)
|
|
48
|
+
})
|
|
49
|
+
publish.command('compiler')
|
|
50
|
+
.description('发布编译器')
|
|
51
|
+
.option('--path <path>','compiler文件路径')
|
|
52
|
+
.option('--large <large>', '所属编译器大版本')
|
|
53
|
+
.option('--small <small>','编译器版本')
|
|
54
|
+
.action(async (options:{path:string,large:string,small:string})=>{
|
|
55
|
+
await command.publish_compiler(options.path, options.large, options.small)
|
|
56
|
+
})
|
|
57
|
+
//create 子命令组
|
|
58
|
+
let create=c.command('create').description('创建')
|
|
59
|
+
create.command('compiler')
|
|
60
|
+
.description('创建编译器大版本')
|
|
61
|
+
.option('--license <license>', '许可证')
|
|
62
|
+
.option('--version <version>', '版本号')
|
|
63
|
+
.action(async (options:{license:string,version:string})=>{
|
|
64
|
+
await command.create_compiler(options.license, options.version)
|
|
65
|
+
})
|
|
66
|
+
//config 子命令组
|
|
67
|
+
let config=c.command('config').description('配置')
|
|
68
|
+
config.command('set')
|
|
69
|
+
.description('设置配置')
|
|
70
|
+
.argument('<key>', '配置键')
|
|
71
|
+
.argument('<value>', '配置值')
|
|
72
|
+
.action(async (key:string,value:string)=>{
|
|
73
|
+
await command.config_set(key,value)
|
|
74
|
+
})
|
|
75
|
+
config.command('verify')
|
|
76
|
+
.description('校验配置')
|
|
77
|
+
.action(command.config_verify)
|
|
78
|
+
c.command('register')
|
|
79
|
+
.description('注册账号')
|
|
80
|
+
.option('--username <username>', '用户名')
|
|
81
|
+
.option('--email <email>', '邮箱')
|
|
82
|
+
.action(async (options:{username:string,email:string})=>{
|
|
83
|
+
await command.register(options.username, options.email)
|
|
84
|
+
})
|
|
85
|
+
return c
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//直接运行时才解析参数;被 import(如测试)时不执行
|
|
89
|
+
if(import.meta.url===pathToFileURL(process.argv[1]).href)
|
|
90
|
+
build_command().parse()
|
package/model.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type GlobalConfig={
|
|
2
|
+
server:string,
|
|
3
|
+
username:string,
|
|
4
|
+
password:string,
|
|
5
|
+
token:string
|
|
6
|
+
}
|
|
7
|
+
export const DefaultGlobalConfig={
|
|
8
|
+
server:'',
|
|
9
|
+
username:'',
|
|
10
|
+
password:'',
|
|
11
|
+
token:''
|
|
12
|
+
} as GlobalConfig
|
|
13
|
+
export type ProjectConfig={
|
|
14
|
+
name:string
|
|
15
|
+
version:string
|
|
16
|
+
author:string
|
|
17
|
+
license:string
|
|
18
|
+
ignore:string[]
|
|
19
|
+
optimize:number
|
|
20
|
+
output:string
|
|
21
|
+
venv:{
|
|
22
|
+
dir:string,
|
|
23
|
+
compiler:string,
|
|
24
|
+
vm:string,
|
|
25
|
+
compiler_version:string,
|
|
26
|
+
vm_version:string
|
|
27
|
+
}
|
|
28
|
+
dependency:{name:string,version:string}[]
|
|
29
|
+
lib:{local:string,data:{name:string,version:string}[]}
|
|
30
|
+
lock?:{name:string,dependencies:{name:string,version:string}[]}[]
|
|
31
|
+
}
|
|
32
|
+
export type ModuleVersion={
|
|
33
|
+
version:string,
|
|
34
|
+
dependencies:{
|
|
35
|
+
name:string,
|
|
36
|
+
version:string
|
|
37
|
+
}[],
|
|
38
|
+
source:string,
|
|
39
|
+
hex:string
|
|
40
|
+
}
|
|
41
|
+
export type Module={
|
|
42
|
+
name:string,
|
|
43
|
+
author:string,
|
|
44
|
+
keywords:string[],
|
|
45
|
+
description: string,
|
|
46
|
+
license:string,
|
|
47
|
+
version:ModuleVersion[]
|
|
48
|
+
}
|
|
49
|
+
export type User={
|
|
50
|
+
email:string,
|
|
51
|
+
token:string,
|
|
52
|
+
username:string
|
|
53
|
+
}
|
|
54
|
+
export type VM={
|
|
55
|
+
version:string,
|
|
56
|
+
isa:string,
|
|
57
|
+
author:string,
|
|
58
|
+
license:string,
|
|
59
|
+
source:string,
|
|
60
|
+
hex:string
|
|
61
|
+
}
|
|
62
|
+
export type Config={
|
|
63
|
+
host:string,
|
|
64
|
+
port:number,
|
|
65
|
+
username:string,
|
|
66
|
+
password:string,
|
|
67
|
+
email:string,
|
|
68
|
+
token:string,
|
|
69
|
+
smtp:string
|
|
70
|
+
}
|
|
71
|
+
export type CompilerChild={
|
|
72
|
+
version:string,
|
|
73
|
+
hex:string,
|
|
74
|
+
source:string
|
|
75
|
+
}
|
|
76
|
+
export type Compiler={
|
|
77
|
+
author:string,
|
|
78
|
+
version:string,
|
|
79
|
+
license:string,
|
|
80
|
+
child:CompilerChild[]
|
|
81
|
+
}
|
|
82
|
+
export type CompilerConfig=Compiler[]
|
|
83
|
+
export type ModuleConfig=Module[]
|
|
84
|
+
export type VMConfig=VM[]
|
|
85
|
+
export type UserConfig=User[]
|
|
86
|
+
export type Result<T>={
|
|
87
|
+
message:string
|
|
88
|
+
data:T,
|
|
89
|
+
code:number
|
|
90
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aebzx/slang-tool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"publish": "npm publish"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"slang": "dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/lzma-native": "^4.0.4",
|
|
22
|
+
"@types/node": "^26.3.0",
|
|
23
|
+
"vitest": "^4.1.11"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@inquirer/prompts": "^8.6.0",
|
|
27
|
+
"axios": "^1.19.0",
|
|
28
|
+
"axios-retry": "^4.5.0",
|
|
29
|
+
"commander": "^15.0.0",
|
|
30
|
+
"lzma-native": "^8.0.6",
|
|
31
|
+
"rolldown": "^1.2.5",
|
|
32
|
+
"tar": "^7.5.22"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//cli 命令注册测试:验证每个子命令、选项、位置参数都已正确配置
|
|
2
|
+
//commander 15 不支持空格命令名,多词命令拆为父命令+子命令(如 publish module)
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import type { Command } from 'commander'
|
|
5
|
+
import { build_command } from '../entry.ts'
|
|
6
|
+
|
|
7
|
+
const c = build_command()
|
|
8
|
+
|
|
9
|
+
//按路径递归查找命令:find('publish vm') 匹配 publish 下的 vm
|
|
10
|
+
function find(name: string): Command {
|
|
11
|
+
const parts = name.split(' ')
|
|
12
|
+
let cur: Command = c
|
|
13
|
+
for (const p of parts) {
|
|
14
|
+
const next = cur.commands.find(i => i.name() == p)
|
|
15
|
+
expect(next, `命令 ${name} 应已注册`).toBeTruthy()
|
|
16
|
+
cur = next!
|
|
17
|
+
}
|
|
18
|
+
return cur
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('命令注册', () => {
|
|
22
|
+
it('注册了所有顶层命令', () => {
|
|
23
|
+
const names = c.commands.map(i => i.name())
|
|
24
|
+
for (const n of ['init', 'compiler', 'run', 'go', 'install', 'uninstall',
|
|
25
|
+
'publish', 'create', 'config', 'register'])
|
|
26
|
+
expect(names).toContain(n)
|
|
27
|
+
})
|
|
28
|
+
it('publish 下有 module/vm/compiler 子命令', () => {
|
|
29
|
+
const names = find('publish').commands.map(i => i.name())
|
|
30
|
+
expect(names).toEqual(expect.arrayContaining(['module', 'vm', 'compiler']))
|
|
31
|
+
})
|
|
32
|
+
it('create 下有 compiler 子命令', () => {
|
|
33
|
+
expect(find('create').commands.map(i => i.name())).toContain('compiler')
|
|
34
|
+
})
|
|
35
|
+
it('config 下有 set/verify 子命令', () => {
|
|
36
|
+
expect(find('config').commands.map(i => i.name())).toEqual(expect.arrayContaining(['set', 'verify']))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('install 命令', () => {
|
|
41
|
+
it('有 --name 和 --version 选项', () => {
|
|
42
|
+
const opts = find('install').options.map(o => o.attributeName())
|
|
43
|
+
expect(opts).toContain('name')
|
|
44
|
+
expect(opts).toContain('version')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('uninstall 命令', () => {
|
|
49
|
+
it('有位置参数 <name>', () => {
|
|
50
|
+
const args = find('uninstall').registeredArguments.map(a => a.name())
|
|
51
|
+
expect(args).toContain('name')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('publish vm 命令', () => {
|
|
56
|
+
it('有 path/license/isa/version 四个选项', () => {
|
|
57
|
+
const opts = find('publish vm').options.map(o => o.attributeName())
|
|
58
|
+
for (const o of ['path', 'license', 'isa', 'version'])
|
|
59
|
+
expect(opts).toContain(o)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('create compiler 命令', () => {
|
|
64
|
+
it('有 license 和 version 选项', () => {
|
|
65
|
+
const opts = find('create compiler').options.map(o => o.attributeName())
|
|
66
|
+
expect(opts).toContain('license')
|
|
67
|
+
expect(opts).toContain('version')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('publish compiler 命令', () => {
|
|
72
|
+
it('有 path/large/small 三个选项', () => {
|
|
73
|
+
const opts = find('publish compiler').options.map(o => o.attributeName())
|
|
74
|
+
for (const o of ['path', 'large', 'small'])
|
|
75
|
+
expect(opts).toContain(o)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('config set 命令', () => {
|
|
80
|
+
it('有 key 和 value 位置参数', () => {
|
|
81
|
+
const args = find('config set').registeredArguments.map(a => a.name())
|
|
82
|
+
expect(args).toContain('key')
|
|
83
|
+
expect(args).toContain('value')
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('register 命令', () => {
|
|
88
|
+
it('有 username 和 email 选项(不再有 password)', () => {
|
|
89
|
+
const opts = find('register').options.map(o => o.attributeName())
|
|
90
|
+
expect(opts).toContain('username')
|
|
91
|
+
expect(opts).toContain('email')
|
|
92
|
+
expect(opts).not.toContain('password')
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('无参命令', () => {
|
|
97
|
+
it('init/compiler/run/go/config verify/publish module 无位置参数', () => {
|
|
98
|
+
for (const n of ['init', 'compiler', 'run', 'go', 'config verify', 'publish module']) {
|
|
99
|
+
expect(find(n).registeredArguments.length, `${n} 不应有位置参数`).toBe(0)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//cli utils 单元测试:压缩/解压/哈希/项目配置
|
|
2
|
+
//注意:global_config 会读写真实 ~/.slang/config.json,此处不测,避免污染用户环境
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
+
import { mkdirSync, mkdtempSync, existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import * as path from 'node:path'
|
|
7
|
+
import {createHash} from 'node:crypto'
|
|
8
|
+
import {compress, decompress, hash_verify, project_config} from '../utils/utils.ts'
|
|
9
|
+
|
|
10
|
+
let dir = ''
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
dir = mkdtempSync(path.join(tmpdir(), 'cli-utils-'))
|
|
13
|
+
})
|
|
14
|
+
afterAll(() => {
|
|
15
|
+
rmSync(dir, { recursive: true, force: true })
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('hash_verify', () => {
|
|
19
|
+
it('正确哈希通过', () => {
|
|
20
|
+
const data = Buffer.from('hello slang')
|
|
21
|
+
const hex = createHash('sha256').update(data).digest('hex')
|
|
22
|
+
expect(hash_verify(data, hex)).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
it('错误哈希不通过', () => {
|
|
25
|
+
expect(hash_verify(Buffer.from('hello'), 'deadbeef')).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
it('字符串数据也能校验', () => {
|
|
28
|
+
const hex = createHash('sha256').update('abc').digest('hex')
|
|
29
|
+
expect(hash_verify('abc', hex)).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('compress/decompress 往返', () => {
|
|
34
|
+
it('压缩排除 ignore 目录后可还原', async () => {
|
|
35
|
+
//项目结构:main.sl 与 lib/ 目录,ignore 排除 lib
|
|
36
|
+
mkdirSync(path.join(dir, 'lib'), { recursive: true })
|
|
37
|
+
writeFileSync(path.join(dir, 'main.sl'), 'print("hi")\n')
|
|
38
|
+
writeFileSync(path.join(dir, 'lib', 'foo.sl'), 'should be ignored')
|
|
39
|
+
const buf = await compress(['lib'], dir)
|
|
40
|
+
expect(Buffer.isBuffer(buf)).toBe(true)
|
|
41
|
+
expect(buf.length).toBeGreaterThan(0)
|
|
42
|
+
|
|
43
|
+
//解压到 output,校验哈希,再解出文件
|
|
44
|
+
const hex = createHash('sha256').update(buf).digest('hex')
|
|
45
|
+
await decompress('mypkg', buf, path.join(dir, 'out'), hex)
|
|
46
|
+
expect(readFileSync(path.join(dir, 'out', 'mypkg', 'main.sl'), 'utf-8')).toBe('print("hi")\n')
|
|
47
|
+
//ignore 的目录不应被打包
|
|
48
|
+
expect(readdirSync(path.join(dir, 'out', 'mypkg'))).not.toContain('lib')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('哈希不匹配时抛文件校验失败', async () => {
|
|
52
|
+
writeFileSync(path.join(dir, 'a.sl'), 'a')
|
|
53
|
+
const buf = await compress([], dir)
|
|
54
|
+
await expect(decompress('bad', buf, path.join(dir, 'out2'), 'deadbeef'))
|
|
55
|
+
.rejects.toThrow('文件校验失败')
|
|
56
|
+
//不应留下任何解压产物
|
|
57
|
+
expect(existsSync(path.join(dir, 'out2', 'bad'))).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('project_config', () => {
|
|
62
|
+
it('读取 slang.json', () => {
|
|
63
|
+
const cfg = {
|
|
64
|
+
name: 'demo', version: '1.0.0', author: 'tester', license: 'MIT',
|
|
65
|
+
ignore: ['lib'], optimize: 0, output: 'a.sbin',
|
|
66
|
+
venv: { dir: 'venv', compiler: '1.0', vm: '2.0', compiler_version: '1.0.0', vm_version: '2.0' },
|
|
67
|
+
dependency: [], lib: { local: 'lib', data: [] }
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(path.join(dir, 'slang.json'), JSON.stringify(cfg))
|
|
70
|
+
expect(project_config(dir)).toEqual(cfg)
|
|
71
|
+
})
|
|
72
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["ES2016","DOM","ES2019"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"strict": false,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
}
|
|
15
|
+
}
|
package/utils/ajax.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import axiosRetry from 'axios-retry'
|
|
3
|
+
import {global_config} from './utils.ts'
|
|
4
|
+
const ajax=axios.create({
|
|
5
|
+
baseURL:global_config().server,
|
|
6
|
+
timeout:5000
|
|
7
|
+
})
|
|
8
|
+
axiosRetry(ajax,{
|
|
9
|
+
retryDelay:(retryCount) => {
|
|
10
|
+
return retryCount * 1000
|
|
11
|
+
},
|
|
12
|
+
retryCondition:axiosRetry.isNetworkOrIdempotentRequestError,
|
|
13
|
+
retries:3
|
|
14
|
+
})
|
|
15
|
+
export default ajax
|
package/utils/index.ts
ADDED
package/utils/net.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Compiler,
|
|
3
|
+
CompilerConfig,
|
|
4
|
+
ModuleConfig,
|
|
5
|
+
Result,
|
|
6
|
+
VM,
|
|
7
|
+
VMConfig,
|
|
8
|
+
Module,
|
|
9
|
+
ModuleVersion,
|
|
10
|
+
CompilerChild
|
|
11
|
+
} from '../model.ts'
|
|
12
|
+
import ajax from './ajax.ts'
|
|
13
|
+
function check<T>(res:Result<T>){
|
|
14
|
+
if(res.code!=200)throw new Error(res.message)
|
|
15
|
+
return res.data
|
|
16
|
+
}
|
|
17
|
+
async function download_vm(version:string){
|
|
18
|
+
return Buffer.from(check((await ajax.post('/api/download/vm',{version})).data) as string,'base64')
|
|
19
|
+
}
|
|
20
|
+
async function download_module(name:string,version:string){
|
|
21
|
+
return Buffer.from(check((await ajax.post('/api/download/module',{name,version})).data) as string,'base64')
|
|
22
|
+
}
|
|
23
|
+
async function download_compiler(large_version:string,small_version:string){
|
|
24
|
+
return check<string>(await ajax.post('/api/download/compiler',{large_version,small_version}))
|
|
25
|
+
}
|
|
26
|
+
async function list_vm(){
|
|
27
|
+
return check<VMConfig>(await ajax.get('/api/list/vm'))
|
|
28
|
+
}
|
|
29
|
+
async function list_module(){
|
|
30
|
+
return check<ModuleConfig>(await ajax.get('/api/list/module'))
|
|
31
|
+
}
|
|
32
|
+
async function list_compiler(){
|
|
33
|
+
return check<CompilerConfig>(await ajax.get('/api/list/compiler'))
|
|
34
|
+
}
|
|
35
|
+
async function publish_vm(author:string,token:string,module:VM,data:Buffer){
|
|
36
|
+
return check<void>(await ajax.post('/api/publish/vm',{author,token,module,data}))
|
|
37
|
+
}
|
|
38
|
+
async function publish_module(author:string,token:string,module:ModuleVersion,data:Buffer){
|
|
39
|
+
return check<void>(await ajax.post('/api/publish/module',{author,token,module,data}))
|
|
40
|
+
}
|
|
41
|
+
async function publish_compiler(author:string,token:string,version:string,type:CompilerChild,data:string){
|
|
42
|
+
return check<void>(await ajax.post('/api/publish/compiler/add',{author,token,version,type,data}))
|
|
43
|
+
}
|
|
44
|
+
async function create_compiler(author:string,token:string,license:string,version:string){
|
|
45
|
+
return check<void>(await ajax.post('/api/publish/compiler/create',{author,token,license,version}))
|
|
46
|
+
}
|
|
47
|
+
async function register(username:string,email:string){
|
|
48
|
+
return check<void>(await ajax.post('/api/register',{username,email}))
|
|
49
|
+
}
|
|
50
|
+
async function verify(username:string,token:string){
|
|
51
|
+
if(((await ajax.get('/api/health')).data as Result<void>).message!='is SPM Server')return '不是SPM服务器'
|
|
52
|
+
return check<boolean>(await ajax.post('/api/verify',{username,token}))?'':'用户不存在或者token配置不正确'
|
|
53
|
+
}
|
|
54
|
+
export default {
|
|
55
|
+
download:{
|
|
56
|
+
vm:download_vm,
|
|
57
|
+
module:download_module,
|
|
58
|
+
compiler:download_compiler
|
|
59
|
+
},
|
|
60
|
+
list:{
|
|
61
|
+
vm:list_vm,
|
|
62
|
+
module:list_module,
|
|
63
|
+
compiler:list_compiler
|
|
64
|
+
},
|
|
65
|
+
publish:{
|
|
66
|
+
vm:publish_vm,
|
|
67
|
+
module:publish_module,
|
|
68
|
+
compiler:publish_compiler,
|
|
69
|
+
compiler_create:create_compiler
|
|
70
|
+
},
|
|
71
|
+
user:{
|
|
72
|
+
register:register,
|
|
73
|
+
verify:verify
|
|
74
|
+
}
|
|
75
|
+
}
|
package/utils/utils.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import lzma from 'lzma-native'
|
|
2
|
+
import {c as _compress,x as _decompress} from 'tar'
|
|
3
|
+
import {existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import {createHash} from 'node:crypto'
|
|
6
|
+
import {DefaultGlobalConfig, GlobalConfig, ProjectConfig} from '../model.ts'
|
|
7
|
+
import os from 'node:os'
|
|
8
|
+
export function lzma_compress(buf:Buffer):Promise<Buffer>{
|
|
9
|
+
return new Promise((resolve)=>{
|
|
10
|
+
lzma.compress(buf,{preset:9,synchronous:true},(res)=>{resolve(res)})
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
export function lzma_decompress(str:Buffer):Promise<Buffer>{
|
|
14
|
+
return new Promise((resolve)=>{
|
|
15
|
+
lzma.decompress(str,{synchronous:true},(res)=>{resolve(res)})
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
export async function compress(ignore:string[], dir:string=process.cwd()):Promise<Buffer>{
|
|
19
|
+
const tar_file=path.join(dir,'ls.tar')
|
|
20
|
+
_compress({gzip:false,cwd:dir,file:tar_file,filter:(p:string)=>{
|
|
21
|
+
return !ignore.map(i=>p.includes(i)).includes(true)&&!p.includes('ls.tar')
|
|
22
|
+
},sync:true},['.'])
|
|
23
|
+
let ret=await lzma_compress(readFileSync(tar_file))
|
|
24
|
+
rmSync(tar_file)
|
|
25
|
+
return ret
|
|
26
|
+
}
|
|
27
|
+
export async function decompress(name:string,str:Buffer,output:string,verify:string){
|
|
28
|
+
//hex 是压缩数据的 sha256(发布时 createHash('sha256').update(data) 对压缩后数据取哈希),
|
|
29
|
+
//必须先对 str(压缩数据)校验,再解压
|
|
30
|
+
if(!hash_verify(str,verify))throw new Error('文件校验失败')
|
|
31
|
+
mkdirSync(output,{recursive:true})
|
|
32
|
+
mkdirSync(path.join(output,name),{recursive:true})
|
|
33
|
+
const plain=await lzma_decompress(str)
|
|
34
|
+
writeFileSync(path.join(output,'ls.tar'),plain)
|
|
35
|
+
_decompress({file:path.join(output,'ls.tar'),cwd:path.join(output,name),sync:true})
|
|
36
|
+
rmSync(path.join(output,'ls.tar'))
|
|
37
|
+
}
|
|
38
|
+
export function hash_verify(data:Buffer|string,hex:string){
|
|
39
|
+
return createHash('sha256').update(data).digest('hex')==hex
|
|
40
|
+
}
|
|
41
|
+
export function global_config():GlobalConfig{
|
|
42
|
+
if(!existsSync(path.join(os.homedir(),'.slang','config.json')))
|
|
43
|
+
writeFileSync(path.join(os.homedir(),'.slang','config.json'),JSON.stringify(DefaultGlobalConfig))
|
|
44
|
+
return JSON.parse(readFileSync(path.join(os.homedir(),'.slang','config.json'),'utf-8')) as GlobalConfig
|
|
45
|
+
}
|
|
46
|
+
export function project_config(dir:string):ProjectConfig{
|
|
47
|
+
return JSON.parse(readFileSync(path.join(dir,'slang.json'),'utf-8')) as ProjectConfig
|
|
48
|
+
}
|
|
49
|
+
export function verify(dir:string){
|
|
50
|
+
if(!existsSync(path.join(dir,'slang.json')))
|
|
51
|
+
throw new Error('不是Slang项目')
|
|
52
|
+
}
|
|
53
|
+
export function write_global_config(config:GlobalConfig){
|
|
54
|
+
writeFileSync(path.join(os.homedir(),'.slang','config.json'),JSON.stringify(config,null,4))
|
|
55
|
+
}
|
|
56
|
+
export function write_project_config(dir:string,config:ProjectConfig){
|
|
57
|
+
writeFileSync(path.join(dir,'slang.json'),JSON.stringify(config,null,4))
|
|
58
|
+
}
|