Skip to content

Commit 2d00d54

Browse files
author
winjo
committed
feat: add cli package
1 parent afd8cc0 commit 2d00d54

16 files changed

Lines changed: 593 additions & 5 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,6 @@ configs/tsconfig/.references
9090
packages/*/lib
9191
packages/*/esm
9292
packages/*/types
93+
94+
packages/spacex/extensions
95+
packages/spacex/kaitian-extensions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.cjs.json",
3+
"compilerOptions": {
4+
"rootDir": "../../../packages/cli/src",
5+
"outDir": "../../../packages/cli/lib"
6+
},
7+
"include": [
8+
"../../../packages/cli/src"
9+
]
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.esm.json",
3+
"compilerOptions": {
4+
"rootDir": "../../../packages/cli/src",
5+
"outDir": "../../../packages/cli/esm"
6+
},
7+
"include": [
8+
"../../../packages/cli/src"
9+
]
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.types.json",
3+
"compilerOptions": {
4+
"rootDir": "../../../packages/cli/src",
5+
"declarationDir": "../../../packages/cli/types"
6+
},
7+
"include": [
8+
"../../../packages/cli/src"
9+
]
10+
}

configs/tsconfig/tsconfig.build.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
},
99
{
1010
"path": "./core/tsconfig.types.json"
11+
},
12+
{
13+
"path": "./cli/tsconfig.cjs.json"
14+
},
15+
{
16+
"path": "./cli/tsconfig.esm.json"
17+
},
18+
{
19+
"path": "./cli/tsconfig.types.json"
1120
}
1221
],
1322
"files": [],

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,17 @@
5757
"tnpm": {
5858
"mode": "yarn",
5959
"lockfile": "enable"
60-
}
60+
},
61+
"kaitian-extensions": [
62+
{
63+
"publisher": "kaitian",
64+
"name": "ide-dark-theme",
65+
"version": "2.0.0"
66+
},
67+
{
68+
"publisher": "vscode-extensions",
69+
"name": "vscode-icons",
70+
"version": "9.4.0"
71+
}
72+
]
6173
}

packages/cli/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@alipay/spacex-cli",
3+
"version": "0.0.1",
4+
"description": "@alipay/spacex-cli",
5+
"main": "lib/index.js",
6+
"module": "esm/index.js",
7+
"typing": "types/index.d.ts",
8+
"files": [
9+
"lib",
10+
"esm",
11+
"types"
12+
],
13+
"repository": {
14+
"type": "git",
15+
"url": "git@code.alipay.com:winjo.gwj/SpaceX.git"
16+
},
17+
"keywords": [
18+
"kaitian AntCodespaces"
19+
],
20+
"engines": {
21+
"kaitian": "1.23.1"
22+
},
23+
"scripts": {},
24+
"publishConfig": {
25+
"registry": "https://registry.npm.alibaba-inc.com"
26+
},
27+
"dependencies": {
28+
"@ali/ide-extension-installer": "^2.0.0",
29+
"@alipay/spacex-core": "0.0.1",
30+
"fs-extra": "^9.0.1",
31+
"npmlog": "^4.1.2",
32+
"semver": "^7.3.2"
33+
},
34+
"tnpm": {
35+
"mode": "yarn",
36+
"lockfile": "enable"
37+
},
38+
"devDependencies": {
39+
"@types/npmlog": "^4.1.2",
40+
"@types/semver": "^7.3.4"
41+
}
42+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as path from 'path'
2+
3+
export const LOG_PREFIX = 'spacex'
4+
5+
export const FRAMEWORK_NAME = '@alipay/spacex'
6+
7+
export const FRAMEWORK_PATH = resolveFramework()
8+
9+
export const EXTENSION_DIR = path.join(FRAMEWORK_PATH, 'kaitian-extensions')
10+
11+
export const EXTENSION_METADATA_DIR = path.join(FRAMEWORK_PATH, 'extensions')
12+
13+
function resolveFramework() {
14+
try {
15+
const pkgPath = require.resolve(`${FRAMEWORK_NAME}/package.json`)
16+
return path.join(pkgPath, '..')
17+
} catch(err) {
18+
return ''
19+
}
20+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as path from 'path'
2+
import { ExtensionInstaller, Extension } from '@ali/ide-extension-installer';
3+
import log from 'npmlog'
4+
import semver from 'semver'
5+
import * as fse from 'fs-extra';
6+
import { parallelLimit } from './../util'
7+
import { EXTENSION_DIR, FRAMEWORK_PATH, FRAMEWORK_NAME, LOG_PREFIX, EXTENSION_METADATA_DIR } from './constant'
8+
import { ExtensionScanner } from './scanner'
9+
import { IWorkExtensionMetaData } from './type';
10+
11+
if (!FRAMEWORK_PATH) {
12+
log.error(LOG_PREFIX, `cli 无法单独使用,需要配合 spacex 一起使用,请直接安装 ${FRAMEWORK_NAME}`)
13+
process.exit(1)
14+
}
15+
16+
const pkgJSON = fse.readJSONSync(path.join(__dirname, '../../package.json'))
17+
18+
const extensionInstaller = new ExtensionInstaller({
19+
accountId: 'nGJBcqs1D-ma32P3mBftgsfq',
20+
masterKey: '-nzxLbuqvrKh8arE0grj2f1H',
21+
frameworkVersion: pkgJSON.engines.kaitian,
22+
dist: EXTENSION_DIR,
23+
ignoreIncreaseCount: true,
24+
})
25+
26+
export const install = async (extensions: Extension[]) => {
27+
if (!extensions?.length) {
28+
// 使用 package.json 中的配置
29+
extensions = await getExtensionFromPackage()
30+
}
31+
checkExtensions(extensions)
32+
await parallelLimit(extensions.map(ext => () => installExtension(ext)), 5)
33+
const metadata = await new ExtensionScanner([EXTENSION_DIR], {}).run()
34+
await writeMetadata(metadata)
35+
log.info(FRAMEWORK_NAME, 'extensions installed successfully')
36+
}
37+
38+
async function getExtensionFromPackage() {
39+
try {
40+
const projectPkgJSON = await fse.readJSON(path.resolve('package.json'))
41+
return projectPkgJSON?.['kaitian-extensions'] ?? []
42+
} catch (err) {
43+
return []
44+
}
45+
}
46+
47+
function checkExtensions(extensions: Extension[]) {
48+
extensions.forEach((ext) => {
49+
if (!ext.publisher) {
50+
log.error(FRAMEWORK_NAME, `${JSON.stringify(ext)} 缺少 publisher`)
51+
throw new Error('exit')
52+
}
53+
if (!ext.name) {
54+
log.error(FRAMEWORK_NAME, `${JSON.stringify(ext)} 缺少 name`)
55+
throw new Error('exit')
56+
}
57+
if (!ext.version) {
58+
log.error(FRAMEWORK_NAME, `${JSON.stringify(ext)} 缺少 version`)
59+
throw new Error('exit')
60+
} else if (!semver.valid(ext.version)) {
61+
log.error(FRAMEWORK_NAME, `${JSON.stringify(ext)} 中的 version 无效`)
62+
throw new Error('exit')
63+
}
64+
})
65+
}
66+
67+
async function installExtension(extension: Extension) {
68+
try {
69+
await extensionInstaller.install({
70+
publisher: extension.publisher,
71+
name: extension.name,
72+
version: extension.version
73+
})
74+
} catch(err) {
75+
log.error(FRAMEWORK_NAME, `${extension.publisher}.${extension.name}@${extension.version} 安装失败,请稍后重试`)
76+
}
77+
}
78+
79+
async function writeMetadata(metadata: IWorkExtensionMetaData[]) {
80+
await fse.ensureDir(EXTENSION_METADATA_DIR)
81+
return Promise.all(metadata.map(async data => {
82+
try {
83+
const p = path.join(EXTENSION_METADATA_DIR, `${data.extensionId}.js`)
84+
await fse.writeFile(p, `
85+
module.exports = ${JSON.stringify(data, null, 2)}
86+
`.trim() + '\n')
87+
} catch (err) {
88+
log.error(FRAMEWORK_NAME, `${data.extensionId} 安装失败`)
89+
}
90+
}))
91+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* handle `kaitianContributes` and `contributes`
3+
*/
4+
import mergeWith from 'lodash/mergeWith';
5+
6+
import { IExtensionContributions, IKaitianExtensionContributions } from './type';
7+
8+
export function mergeContributes(
9+
contributes: IExtensionContributions | undefined,
10+
kaitianContributes: IKaitianExtensionContributions | undefined,
11+
): IKaitianExtensionContributions {
12+
if (contributes === undefined) {
13+
return kaitianContributes || {};
14+
}
15+
16+
if (kaitianContributes === undefined) {
17+
return contributes || {};
18+
}
19+
20+
return mergeWith(kaitianContributes, contributes, (value, srcValue, key, object, source) => {
21+
if (value === undefined || srcValue === undefined) {
22+
return value || srcValue;
23+
}
24+
25+
if (['menus', 'viewsContainers', 'views'].includes(key)) {
26+
const childKeySet = new Set(Object.keys(value).concat(Object.keys(srcValue)));
27+
const result = {};
28+
// 合并掉相同 menuId 下的 menu items
29+
// TODO: 是否需要去重
30+
for (const childKey of childKeySet) {
31+
result[childKey] = (value[childKey] || []).concat(srcValue[childKey] || []);
32+
}
33+
return result;
34+
}
35+
36+
if (key === 'configuration') {
37+
value = asArray(value);
38+
srcValue = asArray(srcValue);
39+
}
40+
41+
if (Array.isArray(value) && Array.isArray(srcValue)) {
42+
return value.concat(srcValue);
43+
}
44+
});
45+
}
46+
47+
function asArray<T>(x: T | T[]): T[] {
48+
return Array.isArray(x) ? x : [x];
49+
}

0 commit comments

Comments
 (0)