refactor: plugins

pull/9/head^2
xiaojunnuo 2022-11-08 22:10:42 +08:00
parent d66bc33761
commit b04d4cb5c5
113 changed files with 2815 additions and 1 deletions

View File

@ -0,0 +1,21 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}

View File

@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test/user.secret.ts

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,45 @@
{
"name": "@certd/plugin-aliyun",
"private": true,
"version": "0.3.0",
"main": "./src/index.ts",
"module": "./dist/plugin-aliyun.mjs",
"types": "./dist/es/plugin-aliyun.d.ts",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^0.3.0",
"dayjs": "^1.11.6",
"lodash": "^4.17.21",
"node-forge": "^0.10.0",
"@certd/pipeline": "^0.3.0"
},
"devDependencies": {
"@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0",
"@types/node-forge": "^1.3.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"chai": "^4.3.6",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^3.1.0"
}
}

View File

@ -0,0 +1,27 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: "aliyun",
title: "阿里云授权",
desc: "",
input: {
accessKeyId: {
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
},
required: true,
},
accessKeySecret: {
title: "accessKeySecret",
component: {
placeholder: "accessKeySecret",
},
required: true,
},
},
})
export class AliyunAccess extends AbstractAccess {
accessKeyId = "";
accessKeySecret = "";
}

View File

@ -0,0 +1 @@
export * from "./aliyun-access";

View File

@ -0,0 +1,119 @@
import Core from "@alicloud/pop-core";
import _ from "lodash";
import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
@IsDnsProvider({
name: "aliyun",
title: "阿里云",
desc: "阿里云DNS解析提供商",
accessType: "aliyun",
})
export class AliyunDnsProvider extends AbstractDnsProvider implements IDnsProvider {
client: any;
constructor() {
super();
}
async onInit() {
const access: any = this.access;
this.client = new Core({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: "https://alidns.aliyuncs.com",
apiVersion: "2015-01-09",
});
}
async getDomainList() {
const params = {
RegionId: "cn-hangzhou",
};
const requestOption = {
method: "POST",
};
const ret = await this.client.request("DescribeDomains", params, requestOption);
return ret.Domains.Domain;
}
async matchDomain(dnsRecord: string) {
const list = await this.getDomainList();
let domain = null;
for (const item of list) {
if (_.endsWith(dnsRecord, item.DomainName)) {
domain = item.DomainName;
break;
}
}
if (!domain) {
throw new Error("can not find Domain ," + dnsRecord);
}
return domain;
}
async getRecords(domain: string, rr: string, value: string) {
const params: any = {
RegionId: "cn-hangzhou",
DomainName: domain,
RRKeyWord: rr,
ValueKeyWord: undefined,
};
if (value) {
params.ValueKeyWord = value;
}
const requestOption = {
method: "POST",
};
const ret = await this.client.request("DescribeDomainRecords", params, requestOption);
return ret.DomainRecords.Record;
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type } = options;
this.logger.info("添加域名解析:", fullRecord, value);
const domain = await this.matchDomain(fullRecord);
const rr = fullRecord.replace("." + domain, "");
const params = {
RegionId: "cn-hangzhou",
DomainName: domain,
RR: rr,
Type: type,
Value: value,
// Line: 'oversea' // 海外
};
const requestOption = {
method: "POST",
};
try {
const ret = await this.client.request("AddDomainRecord", params, requestOption);
this.logger.info("添加域名解析成功:", value, value, ret.RecordId);
return ret.RecordId;
} catch (e: any) {
if (e.code === "DomainRecordDuplicate") {
return;
}
this.logger.info("添加域名解析出错", e);
throw e;
}
}
async removeRecord(options: RemoveRecordOptions): Promise<any> {
const { fullRecord, value, record } = options;
const params = {
RegionId: "cn-hangzhou",
RecordId: record,
};
const requestOption = {
method: "POST",
};
const ret = await this.client.request("DeleteDomainRecord", params, requestOption);
this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId);
return ret.RecordId;
}
}

View File

@ -0,0 +1 @@
import "./aliyun-dns-provider";

View File

@ -0,0 +1,3 @@
export * from "./access";
export * from "./dns-provider";
export * from "./plugin";

View File

@ -0,0 +1,97 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
import dayjs from "dayjs";
import Core from "@alicloud/pop-core";
import RPCClient from "@alicloud/pop-core";
import { AliyunAccess } from "../../access";
@IsTask(() => {
return {
name: "DeployCertToAliyunCDN",
title: "部署证书至阿里云CDN",
desc: "依赖证书申请前置任务自动部署域名证书至阿里云CDN",
input: {
domainName: {
title: "CDN加速域名",
helper: "你在阿里云上配置的CDN加速域名比如certd.docmirror.cn",
required: true,
},
certName: {
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
accessId: {
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "pi-access-selector",
type: "aliyun",
},
required: true,
},
},
output: {},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
};
})
export class DeployCertToAliyunCDN extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
console.log("开始部署证书到阿里云cdn");
const access = (await this.accessService.getById(input.accessId)) as AliyunAccess;
const client = this.getClient(access);
const params = await this.buildParams(input);
await this.doRequest(client, params);
console.log("部署完成");
return {};
}
getClient(access: AliyunAccess) {
return new Core({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: "https://cdn.aliyuncs.com",
apiVersion: "2018-05-10",
});
}
async buildParams(input: TaskInput) {
const { certName, domainName } = input;
const CertName = (certName ?? "certd") + "-" + dayjs().format("YYYYMMDDHHmmss");
const cert = input.cert;
return {
RegionId: "cn-hangzhou",
DomainName: domainName,
ServerCertificateStatus: "on",
CertName: CertName,
CertType: "upload",
ServerCertificate: cert.crt,
PrivateKey: cert.key,
};
}
async doRequest(client: RPCClient, params: any) {
const requestOption = {
method: "POST",
};
const ret: any = await client.request("SetDomainServerCertificate", params, requestOption);
this.checkRet(ret);
this.logger.info("设置cdn证书成功:", ret.RequestId);
}
checkRet(ret: any) {
if (ret.code != null) {
throw new Error("执行失败:" + ret.Message);
}
}
}

View File

@ -0,0 +1 @@
export * from "./deploy-to-cdn";

View File

@ -0,0 +1,58 @@
export const fakeCrt = `-----BEGIN CERTIFICATE-----
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTQx
NjA1NTFaFw0yMTAzMTQxNjA1NTFaMBsxGTAXBgNVBAMMECouZG9jbWlycm9yLmNs
dWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75tGrYjly+RpcZehQ
my1EpaXElT4L60pINKV2YDKnBrcSSo1c6rO7nFh12eC/ju4WwYUep0RVmBDF8xD0
I1Sd1uuDTQWP0UT1X9yqdXtjvxpUqoCHAzG633f3sJRFul7mDLuC9tRCuae9o7qP
EZ827XOmjBR35dso9I2GEE4828J3YE3tSKtobZlM+30jozLEcsO0PTyM5mq5PPjP
VI3fGLcEaBmLZf5ixz4XkcY9IAhyAMYf03cT2wRoYPBaDdXblgCYL6sFtIMbzl3M
Di94PB8NyoNSsC2nmBdWi54wFOgBvY/4ljsX/q7X3EqlSvcA0/M6/c/J9kJ3eupv
jV8nAgMBAAGjggJ9MIICeTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAkdTjSCV3KD
x28sf98MrwVfyFYgMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHcG
CCsGAQUFBwEBBGswaTAyBggrBgEFBQcwAYYmaHR0cDovL29jc3Auc3RnLWludC14
MS5sZXRzZW5jcnlwdC5vcmcwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0Zy1p
bnQteDEubGV0c2VuY3J5cHQub3JnLzArBgNVHREEJDAighAqLmRvY21pcnJvci5j
bHVigg5kb2NtaXJyb3IuY2x1YjBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE
AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y
ZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1ABboacHRlerXw/iXGuPwdgH3jOG2
nTGoUhi2g38xqBUIAAABdmI3LM4AAAQDAEYwRAIgaiNqXSEq+sxp8eqlJXp/KFdO
so5mT50MoRsLF8Inu0ACIDP46+ekng7I0BlmyIPmbqFcZgnZFVWLLCdLYijhVyOL
AHcA3Zk0/KXnJIDJVmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAF2YjcuxwAABAMA
SDBGAiEAxpeB8/w4YkHZ62nH20h128VtuTSmYDCnF7EK2fQyeZYCIQDbJlF2wehZ
sF1BeE7qnYYqCTP0dYIrQ9HWtBa/MbGOKTANBgkqhkiG9w0BAQsFAAOCAQEAL2di
HKh6XcZtGk0BFxJa51sCZ3MLu9+Zy90kCRD4ooP5x932WxVM25+LBRd+xSzx+TRL
UVrlKp9GdMYX1JXL4Vf2NwzuFO3snPDe/qizD/3+D6yo8eKJ/LD82t5kLWAD2rto
YfVSTKwfNIBBJwHUnjviBPJmheHHCKmz8Ct6/6QxFAeta9TAMn0sFeVCQnmAq7HL
jrunq0tNHR/EKG0ITPLf+6P7MxbmpYNnq918766l0tKsW8oo8ZSGEwKU2LMaSiAa
hasyl/2gMnYXjtKOjDcnR8oLpbrOg0qpVbynmJin1HP835oHPPAZ1gLsqYTTizNz
AHxTaXliTVvS83dogw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
-----END CERTIFICATE-----`;

View File

@ -0,0 +1,11 @@
import { expect } from "chai";
import "mocha";
import { EchoPlugin } from "../src/plugin";
describe("task_plugin", function () {
it("#taskplugin", function () {
const echoPlugin = new EchoPlugin();
const define = echoPlugin.define;
echoPlugin.execute({ context: {}, props: { test: 111 } });
expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,10 @@
import { IAccessService } from "../../src/access/access-service";
import { AbstractAccess, AliyunAccess } from "../../src";
import { aliyunSecret } from "../user.secret";
export class AccessServiceTest implements IAccessService {
async getById(id: any): Promise<AbstractAccess> {
return {
...aliyunSecret,
} as AliyunAccess;
}
}

View File

@ -0,0 +1,15 @@
import { ContextFactory } from "../../src/core/context";
import { FileStorage } from "../../src/core/storage";
import { AccessServiceTest } from "./access-service-test";
import { logger } from "../../src/utils/util.log";
const contextFactory = new ContextFactory(new FileStorage());
const userContext = contextFactory.getContext("user", "test");
const pipelineContext = contextFactory.getContext("pipeline", "test");
export const pluginInitProps = {
accessService: new AccessServiceTest(),
pipelineContext: pipelineContext,
userContext: userContext,
logger: logger,
};

View File

@ -0,0 +1,66 @@
import { ConcurrencyStrategy, NextStrategy, Pipeline, RunStrategy } from "../../src";
let idIndex = 0;
function generateId() {
idIndex++;
return idIndex + "";
}
export const pipeline: Pipeline = {
version: 1,
id: generateId(),
title: "测试管道",
userId: 1,
triggers: [],
stages: [
{
id: generateId(),
title: "证书申请阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "申请证书任务",
steps: [
{
id: generateId(),
title: "申请证书",
type: "CertApply",
input: {
domains: ["*.docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
},
},
],
},
],
},
{
id: generateId(),
title: "证书部署阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "测试输出参数任务",
steps: [
{
id: generateId(),
title: "输出参数echo插件",
type: "EchoPlugin",
input: {
cert: "cert",
},
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
],
},
],
},
],
};

View File

@ -0,0 +1,18 @@
//import { expect } from "chai";
import "mocha";
import { Executor, RunHistory } from "../../src";
import { pipeline } from "./pipeline.define";
import { AccessServiceTest } from "./access-service-test";
import { FileStorage } from "../../src/core/storage";
describe("pipeline", function () {
it("#pipeline", async function () {
this.timeout(120000);
function onChanged(history: RunHistory) {
console.log("changed:");
}
const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() });
await executor.run(1, "user");
// expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { CertApplyPlugin } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("CertApply", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new CertApplyPlugin();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const output = await plugin.execute({
domains: ["*.docmirror.cn", "docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
forceUpdate: true,
});
const cert = output.cert;
expect(plugin.getDefine().name).eq("CertApply");
expect(cert.crt != null).eq(true);
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { DeployCertToAliyunCDN } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("DeployToAliyunCDN", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new DeployCertToAliyunCDN();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const cert = await pluginInitProps.pipelineContext.get("cert");
await plugin.execute({
cert,
domainName: "certd-cdn-upload.docmirror.cn",
});
expect(plugin.getDefine().name).eq("DeployCertToAliyunCDN");
});
});

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "commonjs",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
}

View File

@ -0,0 +1,24 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: "src/index.ts",
name: "pipeline",
},
rollupOptions: {
external: ["vue", "lodash-es", "dayjs", "@fast-crud/fast-crud"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: "Vue",
"lodash-es": "_",
dayjs: "dayjs",
"@fast-crud/fast-crud": "FastCrud",
},
},
},
},
});

View File

@ -0,0 +1,21 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}

26
packages/plugins/plugin-all/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test/user.secret.ts

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,45 @@
{
"name": "@certd/plugin-all",
"private": true,
"version": "0.3.0",
"main": "./src/index.ts",
"module": "./dist/plugin-aliyun.mjs",
"types": "./dist/es/plugin-aliyun.d.ts",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@certd/plugin-aliyun": "^0.3.0",
"@certd/plugin-tencent": "^0.3.0",
"@certd/plugin-host": "^0.3.0",
"@certd/plugin-cert": "^0.3.0"
},
"devDependencies": {
"@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0",
"@types/node-forge": "^1.3.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"chai": "^4.3.6",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^3.1.0"
}
}

View File

@ -0,0 +1,4 @@
export * from "@certd/plugin-aliyun";
export * from "@certd/plugin-cert";
export * from "@certd/plugin-tencent";
export * from "@certd/plugin-host";

View File

@ -0,0 +1,58 @@
export const fakeCrt = `-----BEGIN CERTIFICATE-----
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTQx
NjA1NTFaFw0yMTAzMTQxNjA1NTFaMBsxGTAXBgNVBAMMECouZG9jbWlycm9yLmNs
dWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75tGrYjly+RpcZehQ
my1EpaXElT4L60pINKV2YDKnBrcSSo1c6rO7nFh12eC/ju4WwYUep0RVmBDF8xD0
I1Sd1uuDTQWP0UT1X9yqdXtjvxpUqoCHAzG633f3sJRFul7mDLuC9tRCuae9o7qP
EZ827XOmjBR35dso9I2GEE4828J3YE3tSKtobZlM+30jozLEcsO0PTyM5mq5PPjP
VI3fGLcEaBmLZf5ixz4XkcY9IAhyAMYf03cT2wRoYPBaDdXblgCYL6sFtIMbzl3M
Di94PB8NyoNSsC2nmBdWi54wFOgBvY/4ljsX/q7X3EqlSvcA0/M6/c/J9kJ3eupv
jV8nAgMBAAGjggJ9MIICeTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAkdTjSCV3KD
x28sf98MrwVfyFYgMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHcG
CCsGAQUFBwEBBGswaTAyBggrBgEFBQcwAYYmaHR0cDovL29jc3Auc3RnLWludC14
MS5sZXRzZW5jcnlwdC5vcmcwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0Zy1p
bnQteDEubGV0c2VuY3J5cHQub3JnLzArBgNVHREEJDAighAqLmRvY21pcnJvci5j
bHVigg5kb2NtaXJyb3IuY2x1YjBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE
AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y
ZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1ABboacHRlerXw/iXGuPwdgH3jOG2
nTGoUhi2g38xqBUIAAABdmI3LM4AAAQDAEYwRAIgaiNqXSEq+sxp8eqlJXp/KFdO
so5mT50MoRsLF8Inu0ACIDP46+ekng7I0BlmyIPmbqFcZgnZFVWLLCdLYijhVyOL
AHcA3Zk0/KXnJIDJVmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAF2YjcuxwAABAMA
SDBGAiEAxpeB8/w4YkHZ62nH20h128VtuTSmYDCnF7EK2fQyeZYCIQDbJlF2wehZ
sF1BeE7qnYYqCTP0dYIrQ9HWtBa/MbGOKTANBgkqhkiG9w0BAQsFAAOCAQEAL2di
HKh6XcZtGk0BFxJa51sCZ3MLu9+Zy90kCRD4ooP5x932WxVM25+LBRd+xSzx+TRL
UVrlKp9GdMYX1JXL4Vf2NwzuFO3snPDe/qizD/3+D6yo8eKJ/LD82t5kLWAD2rto
YfVSTKwfNIBBJwHUnjviBPJmheHHCKmz8Ct6/6QxFAeta9TAMn0sFeVCQnmAq7HL
jrunq0tNHR/EKG0ITPLf+6P7MxbmpYNnq918766l0tKsW8oo8ZSGEwKU2LMaSiAa
hasyl/2gMnYXjtKOjDcnR8oLpbrOg0qpVbynmJin1HP835oHPPAZ1gLsqYTTizNz
AHxTaXliTVvS83dogw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
-----END CERTIFICATE-----`;

View File

@ -0,0 +1,11 @@
import { expect } from "chai";
import "mocha";
import { EchoPlugin } from "../src/plugin";
describe("task_plugin", function () {
it("#taskplugin", function () {
const echoPlugin = new EchoPlugin();
const define = echoPlugin.define;
echoPlugin.execute({ context: {}, props: { test: 111 } });
expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,10 @@
import { IAccessService } from "../../src/access/access-service";
import { AbstractAccess, AliyunAccess } from "../../src";
import { aliyunSecret } from "../user.secret";
export class AccessServiceTest implements IAccessService {
async getById(id: any): Promise<AbstractAccess> {
return {
...aliyunSecret,
} as AliyunAccess;
}
}

View File

@ -0,0 +1,15 @@
import { ContextFactory } from "../../src/core/context";
import { FileStorage } from "../../src/core/storage";
import { AccessServiceTest } from "./access-service-test";
import { logger } from "../../src/utils/util.log";
const contextFactory = new ContextFactory(new FileStorage());
const userContext = contextFactory.getContext("user", "test");
const pipelineContext = contextFactory.getContext("pipeline", "test");
export const pluginInitProps = {
accessService: new AccessServiceTest(),
pipelineContext: pipelineContext,
userContext: userContext,
logger: logger,
};

View File

@ -0,0 +1,66 @@
import { ConcurrencyStrategy, NextStrategy, Pipeline, RunStrategy } from "../../src";
let idIndex = 0;
function generateId() {
idIndex++;
return idIndex + "";
}
export const pipeline: Pipeline = {
version: 1,
id: generateId(),
title: "测试管道",
userId: 1,
triggers: [],
stages: [
{
id: generateId(),
title: "证书申请阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "申请证书任务",
steps: [
{
id: generateId(),
title: "申请证书",
type: "CertApply",
input: {
domains: ["*.docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
},
},
],
},
],
},
{
id: generateId(),
title: "证书部署阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "测试输出参数任务",
steps: [
{
id: generateId(),
title: "输出参数echo插件",
type: "EchoPlugin",
input: {
cert: "cert",
},
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
],
},
],
},
],
};

View File

@ -0,0 +1,18 @@
//import { expect } from "chai";
import "mocha";
import { Executor, RunHistory } from "../../src";
import { pipeline } from "./pipeline.define";
import { AccessServiceTest } from "./access-service-test";
import { FileStorage } from "../../src/core/storage";
describe("pipeline", function () {
it("#pipeline", async function () {
this.timeout(120000);
function onChanged(history: RunHistory) {
console.log("changed:");
}
const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() });
await executor.run(1, "user");
// expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { CertApplyPlugin } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("CertApply", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new CertApplyPlugin();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const output = await plugin.execute({
domains: ["*.docmirror.cn", "docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
forceUpdate: true,
});
const cert = output.cert;
expect(plugin.getDefine().name).eq("CertApply");
expect(cert.crt != null).eq(true);
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { DeployCertToAliyunCDN } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("DeployToAliyunCDN", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new DeployCertToAliyunCDN();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const cert = await pluginInitProps.pipelineContext.get("cert");
await plugin.execute({
cert,
domainName: "certd-cdn-upload.docmirror.cn",
});
expect(plugin.getDefine().name).eq("DeployCertToAliyunCDN");
});
});

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "commonjs",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
}

View File

@ -0,0 +1,24 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: "src/index.ts",
name: "pipeline",
},
rollupOptions: {
external: ["vue", "lodash-es", "dayjs", "@fast-crud/fast-crud"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: "Vue",
"lodash-es": "_",
dayjs: "dayjs",
"@fast-crud/fast-crud": "FastCrud",
},
},
},
},
});

View File

@ -0,0 +1,21 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}

26
packages/plugins/plugin-cert/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test/user.secret.ts

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,45 @@
{
"name": "@certd/plugin-cert",
"private": true,
"version": "0.3.0",
"main": "./src/index.ts",
"module": "./dist/plugin-aliyun.mjs",
"types": "./dist/es/plugin-aliyun.d.ts",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^0.3.0",
"node-forge": "^0.10.0",
"@certd/pipeline": "^0.3.0"
},
"devDependencies": {
"dayjs": "^1.11.6",
"lodash": "^4.17.21",
"@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.0",
"@types/node-forge": "^1.3.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"chai": "^4.3.6",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^3.1.0"
}
}

View File

@ -0,0 +1 @@
export * from "./plugin";

View File

@ -0,0 +1,196 @@
// @ts-ignore
import * as acme from "@certd/acme-client";
import _ from "lodash";
import { AbstractDnsProvider, IDnsProvider } from "@certd/pipeline";
import { Challenge } from "@certd/acme-client/types/rfc8555";
import { Logger } from "log4js";
import { IContext } from "@certd/pipeline/src/core/context";
export class AcmeService {
userContext: IContext;
logger: Logger;
constructor(options: { userContext: IContext; logger: Logger }) {
this.userContext = options.userContext;
this.logger = options.logger;
acme.setLogger((text: string) => {
this.logger.info(text);
});
}
async getAccountConfig(email: string) {
return (await this.userContext.get(this.buildAccountKey(email))) || {};
}
buildAccountKey(email: string) {
return "acme.config." + email;
}
async saveAccountConfig(email: string, conf: any) {
await this.userContext.set(this.buildAccountKey(email), conf);
}
async getAcmeClient(email: string, isTest = false): Promise<acme.Client> {
const conf = await this.getAccountConfig(email);
if (conf.key == null) {
conf.key = await this.createNewKey();
await this.saveAccountConfig(email, conf);
}
const client = new acme.Client({
directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production,
accountKey: conf.key,
accountUrl: conf.accountUrl,
backoffAttempts: 20,
backoffMin: 5000,
backoffMax: 10000,
});
if (conf.accountUrl == null) {
const accountPayload = {
termsOfServiceAgreed: true,
contact: [`mailto:${email}`],
};
await client.createAccount(accountPayload);
conf.accountUrl = client.getAccountUrl();
await this.saveAccountConfig(email, conf);
}
return client;
}
async createNewKey() {
const key = await acme.forge.createPrivateKey();
return key.toString();
}
async challengeCreateFn(authz: any, challenge: any, keyAuthorization: string, dnsProvider: IDnsProvider) {
this.logger.info("Triggered challengeCreateFn()");
/* http-01 */
if (challenge.type === "http-01") {
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`;
const fileContents = keyAuthorization;
this.logger.info(`Creating challenge response for ${authz.identifier.value} at path: ${filePath}`);
/* Replace this */
this.logger.info(`Would write "${fileContents}" to path "${filePath}"`);
// await fs.writeFileAsync(filePath, fileContents);
} else if (challenge.type === "dns-01") {
/* dns-01 */
const dnsRecord = `_acme-challenge.${authz.identifier.value}`;
const recordValue = keyAuthorization;
this.logger.info(`Creating TXT record for ${authz.identifier.value}: ${dnsRecord}`);
/* Replace this */
this.logger.info(`Would create TXT record "${dnsRecord}" with value "${recordValue}"`);
return await dnsProvider.createRecord({
fullRecord: dnsRecord,
type: "TXT",
value: recordValue,
});
}
}
/**
* Function used to remove an ACME challenge response
*
* @param {object} authz Authorization object
* @param {object} challenge Selected challenge
* @param {string} keyAuthorization Authorization key
* @param recordItem challengeCreateFn create record item
* @param dnsProvider dnsProvider
* @returns {Promise}
*/
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordItem: any, dnsProvider: IDnsProvider) {
this.logger.info("Triggered challengeRemoveFn()");
/* http-01 */
if (challenge.type === "http-01") {
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`;
this.logger.info(`Removing challenge response for ${authz.identifier.value} at path: ${filePath}`);
/* Replace this */
this.logger.info(`Would remove file on path "${filePath}"`);
// await fs.unlinkAsync(filePath);
} else if (challenge.type === "dns-01") {
const dnsRecord = `_acme-challenge.${authz.identifier.value}`;
const recordValue = keyAuthorization;
this.logger.info(`Removing TXT record for ${authz.identifier.value}: ${dnsRecord}`);
/* Replace this */
this.logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`);
await dnsProvider.removeRecord({
fullRecord: dnsRecord,
type: "TXT",
value: keyAuthorization,
record: recordItem,
});
}
}
async order(options: { email: string; domains: string | string[]; dnsProvider: AbstractDnsProvider; csrInfo: any; isTest?: boolean }) {
const { email, isTest, domains, csrInfo, dnsProvider } = options;
const client: acme.Client = await this.getAcmeClient(email, isTest);
/* Create CSR */
const { commonName, altNames } = this.buildCommonNameByDomains(domains);
const [key, csr] = await acme.forge.createCsr({
commonName,
...csrInfo,
altNames,
});
if (dnsProvider == null) {
throw new Error("dnsProvider 不能为空");
}
/* 自动申请证书 */
const crt = await client.auto({
csr,
email: email,
termsOfServiceAgreed: true,
challengePriority: ["dns-01"],
challengeCreateFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string): Promise<any> => {
return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider);
},
challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordItem: any): Promise<any> => {
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider);
},
});
const cert = {
crt: crt.toString(),
key: key.toString(),
csr: csr.toString(),
};
/* Done */
this.logger.debug(`CSR:\n${cert.csr}`);
this.logger.debug(`Certificate:\n${cert.crt}`);
this.logger.info("证书申请成功");
return cert;
}
buildCommonNameByDomains(domains: string | string[]): {
commonName: string;
altNames: string[] | undefined;
} {
if (typeof domains === "string") {
domains = domains.split(",");
}
if (domains.length === 0) {
throw new Error("domain can not be empty");
}
const commonName = domains[0];
let altNames: undefined | string[] = undefined;
if (domains.length > 1) {
altNames = _.slice(domains, 1);
}
return {
commonName,
altNames,
};
}
}

View File

@ -0,0 +1,237 @@
import { AbstractDnsProvider, AbstractPlugin, dnsProviderRegistry, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
import forge from "node-forge";
import dayjs from "dayjs";
import { AcmeService } from "./acme";
import _ from "lodash";
export type CertInfo = {
crt: string;
key: string;
csr: string;
};
@IsTask(() => {
return {
name: "CertApply",
title: "证书申请",
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
input: {
domains: {
title: "域名",
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false,
},
required: true,
col: {
span: 24,
},
helper:
"支持通配符域名,例如: *.foo.com 、 *.test.handsfree.work\n" +
"支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" +
"多级子域名要分成多个域名输入(*.foo.com的证书不能用于xxx.yyy.foo.com\n" +
"输入一个回车之后,再输入下一个",
},
email: {
title: "邮箱",
component: {
name: "a-input",
vModel: "value",
},
required: true,
helper: "请输入邮箱",
},
dnsProviderType: {
title: "DNS提供商",
component: {
name: "pi-dns-provider-selector",
},
required: true,
helper: "请选择dns解析提供商",
},
dnsProviderAccess: {
title: "DNS解析授权",
component: {
name: "pi-access-selector",
},
required: true,
helper: "请选择dns解析提供商授权",
},
renewDays: {
title: "更新天数",
component: {
name: "a-input-number",
vModel: "value",
},
required: true,
helper: "到期前多少天后更新证书",
},
forceUpdate: {
title: "强制更新",
component: {
name: "a-switch",
vModel: "checked",
},
helper: "是否强制重新申请证书",
},
},
default: {
input: {
renewDays: 20,
forceUpdate: false,
},
strategy: {
runStrategy: RunStrategy.AlwaysRun,
},
},
output: {
cert: {
key: "cert",
type: "CertInfo",
title: "域名证书",
},
},
};
})
export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin {
// @ts-ignore
acme: AcmeService;
protected async onInit() {
this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger });
}
async execute(input: TaskInput): Promise<TaskOutput> {
const oldCert = await this.condition(input);
if (oldCert != null) {
return {
cert: oldCert,
};
}
const cert = await this.doCertApply(input);
return { cert };
}
/**
*
* @param input
*/
async condition(input: TaskInput) {
if (input.forceUpdate) {
return null;
}
let oldCert;
try {
oldCert = await this.readCurrentCert();
} catch (e) {
this.logger.warn("读取cert失败", e);
}
if (oldCert == null) {
this.logger.info("还未申请过,准备申请新证书");
return null;
}
const ret = this.isWillExpire(oldCert.expires, input.renewDays);
if (!ret.isWillExpire) {
this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}`);
return oldCert;
}
this.logger.info("即将过期,开始更新证书");
return null;
}
async doCertApply(input: TaskInput) {
const email = input["email"];
const domains = input["domains"];
const dnsProviderType = input["dnsProviderType"];
const dnsProviderAccessId = input["dnsProviderAccess"];
const csrInfo = _.merge(
{
country: "CN",
state: "GuangDong",
locality: "ShengZhen",
organization: "CertD Org.",
organizationUnit: "IT Department",
emailAddress: email,
},
input["csrInfo"]
);
this.logger.info("开始申请证书,", email, domains);
const dnsProviderClass = dnsProviderRegistry.get(dnsProviderType);
const access = await this.accessService.getById(dnsProviderAccessId);
// @ts-ignore
const dnsProvider: AbstractDnsProvider = new dnsProviderClass();
dnsProvider.doInit({ access, logger: this.logger, http: this.http });
const cert = await this.acme.order({
email,
domains,
dnsProvider,
csrInfo,
isTest: false,
});
await this.writeCert(cert);
const ret = await this.readCurrentCert();
return {
...ret,
isNew: true,
};
}
formatCert(pem: string) {
pem = pem.replace(/\r/g, "");
pem = pem.replace(/\n\n/g, "\n");
pem = pem.replace(/\n$/g, "");
return pem;
}
async writeCert(cert: { crt: string; key: string; csr: string }) {
const newCert = {
crt: this.formatCert(cert.crt),
key: this.formatCert(cert.key),
csr: this.formatCert(cert.csr),
};
await this.pipelineContext.set("cert", newCert);
}
async readCurrentCert() {
const cert: CertInfo = await this.pipelineContext.get("cert");
if (cert == null) {
return undefined;
}
const { detail, expires } = this.getCrtDetail(cert.crt);
return {
...cert,
detail,
expires: expires.getTime(),
};
}
getCrtDetail(crt: string) {
const pki = forge.pki;
const detail = pki.certificateFromPem(crt.toString());
const expires = detail.validity.notAfter;
return { detail, expires };
}
/**
* 20
* @param expires
* @param maxDays
* @returns {boolean}
*/
isWillExpire(expires: number, maxDays = 20) {
if (expires == null) {
throw new Error("过期时间不能为空");
}
// 检查有效期
const leftDays = dayjs(expires).diff(dayjs(), "day");
return {
isWillExpire: leftDays < maxDays,
leftDays,
};
}
}

View File

@ -0,0 +1 @@
export * from "./cert-plugin";

View File

@ -0,0 +1,17 @@
import { FileStorage } from "@certd/pipeline/src/core/storage";
import { ContextFactory } from "@certd/pipeline/src/core/context";
import { AccessServiceTest } from "@certd/pipeline/test/pipeline/access-service-test";
import { logger } from "@certd/pipeline";
import { request } from "@certd/pipeline/src/utils/util.request";
const contextFactory = new ContextFactory(new FileStorage());
const userContext = contextFactory.getContext("user", "test");
const pipelineContext = contextFactory.getContext("pipeline", "test");
export const pluginInitProps = {
accessService: new AccessServiceTest(),
pipelineContext: pipelineContext,
userContext: userContext,
logger: logger,
http: request,
};

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { pluginInitProps } from "../init.test";
import { CertApplyPlugin } from "../../../src";
describe("CertApply", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new CertApplyPlugin();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const output = await plugin.execute({
domains: ["*.docmirror.cn", "docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
forceUpdate: true,
});
const cert = output.cert;
expect(plugin.getDefine().name).eq("CertApply");
expect(cert.crt != null).eq(true);
});
});

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "commonjs",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
}

View File

@ -0,0 +1,24 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: "src/index.ts",
name: "pipeline",
},
rollupOptions: {
external: ["vue", "lodash-es", "dayjs", "@fast-crud/fast-crud"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: "Vue",
"lodash-es": "_",
dayjs: "dayjs",
"@fast-crud/fast-crud": "FastCrud",
},
},
},
},
});

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1 @@
export * from "./ssh-access";

View File

@ -0,0 +1,48 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: "ssh",
title: "主机登录授权",
desc: "",
input: {
host: {
title: "主机地址",
component: {
placeholder: "主机域名或IP地址",
},
required: true,
},
port: {
title: "端口",
value: "22",
component: {
placeholder: "22",
},
rules: [{ required: true, message: "此项必填" }],
},
username: {
title: "用户名",
value: "root",
rules: [{ required: true, message: "此项必填" }],
},
password: {
title: "密码",
component: {
name: "a-input-password",
vModel: "value",
},
helper: "登录密码或密钥必填一项",
},
privateKey: {
title: "密钥",
helper: "密钥或密码必填一项",
},
},
})
export class SshAccess extends AbstractAccess {
host = "";
port = 22;
username = "root";
password?: string;
privateKey?: string;
}

View File

@ -0,0 +1,3 @@
export * from "./access";
export * from "./lib/ssh";
export * from "./plugin";

View File

@ -0,0 +1,2 @@
export * from "./host-shell-execute";
export * from "./upload-to-host";

View File

@ -0,0 +1,58 @@
export const fakeCrt = `-----BEGIN CERTIFICATE-----
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTQx
NjA1NTFaFw0yMTAzMTQxNjA1NTFaMBsxGTAXBgNVBAMMECouZG9jbWlycm9yLmNs
dWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75tGrYjly+RpcZehQ
my1EpaXElT4L60pINKV2YDKnBrcSSo1c6rO7nFh12eC/ju4WwYUep0RVmBDF8xD0
I1Sd1uuDTQWP0UT1X9yqdXtjvxpUqoCHAzG633f3sJRFul7mDLuC9tRCuae9o7qP
EZ827XOmjBR35dso9I2GEE4828J3YE3tSKtobZlM+30jozLEcsO0PTyM5mq5PPjP
VI3fGLcEaBmLZf5ixz4XkcY9IAhyAMYf03cT2wRoYPBaDdXblgCYL6sFtIMbzl3M
Di94PB8NyoNSsC2nmBdWi54wFOgBvY/4ljsX/q7X3EqlSvcA0/M6/c/J9kJ3eupv
jV8nAgMBAAGjggJ9MIICeTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAkdTjSCV3KD
x28sf98MrwVfyFYgMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHcG
CCsGAQUFBwEBBGswaTAyBggrBgEFBQcwAYYmaHR0cDovL29jc3Auc3RnLWludC14
MS5sZXRzZW5jcnlwdC5vcmcwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0Zy1p
bnQteDEubGV0c2VuY3J5cHQub3JnLzArBgNVHREEJDAighAqLmRvY21pcnJvci5j
bHVigg5kb2NtaXJyb3IuY2x1YjBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE
AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y
ZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1ABboacHRlerXw/iXGuPwdgH3jOG2
nTGoUhi2g38xqBUIAAABdmI3LM4AAAQDAEYwRAIgaiNqXSEq+sxp8eqlJXp/KFdO
so5mT50MoRsLF8Inu0ACIDP46+ekng7I0BlmyIPmbqFcZgnZFVWLLCdLYijhVyOL
AHcA3Zk0/KXnJIDJVmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAF2YjcuxwAABAMA
SDBGAiEAxpeB8/w4YkHZ62nH20h128VtuTSmYDCnF7EK2fQyeZYCIQDbJlF2wehZ
sF1BeE7qnYYqCTP0dYIrQ9HWtBa/MbGOKTANBgkqhkiG9w0BAQsFAAOCAQEAL2di
HKh6XcZtGk0BFxJa51sCZ3MLu9+Zy90kCRD4ooP5x932WxVM25+LBRd+xSzx+TRL
UVrlKp9GdMYX1JXL4Vf2NwzuFO3snPDe/qizD/3+D6yo8eKJ/LD82t5kLWAD2rto
YfVSTKwfNIBBJwHUnjviBPJmheHHCKmz8Ct6/6QxFAeta9TAMn0sFeVCQnmAq7HL
jrunq0tNHR/EKG0ITPLf+6P7MxbmpYNnq918766l0tKsW8oo8ZSGEwKU2LMaSiAa
hasyl/2gMnYXjtKOjDcnR8oLpbrOg0qpVbynmJin1HP835oHPPAZ1gLsqYTTizNz
AHxTaXliTVvS83dogw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
-----END CERTIFICATE-----`;

View File

@ -0,0 +1,11 @@
import { expect } from "chai";
import "mocha";
import { EchoPlugin } from "../src/plugin";
describe("task_plugin", function () {
it("#taskplugin", function () {
const echoPlugin = new EchoPlugin();
const define = echoPlugin.define;
echoPlugin.execute({ context: {}, props: { test: 111 } });
expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,10 @@
import { IAccessService } from "../../src/access/access-service";
import { AbstractAccess, HostAccess } from "../../src";
import { aliyunSecret } from "../user.secret";
export class AccessServiceTest implements IAccessService {
async getById(id: any): Promise<AbstractAccess> {
return {
...aliyunSecret,
} as HostAccess;
}
}

View File

@ -0,0 +1,15 @@
import { ContextFactory } from "../../src/core/context";
import { FileStorage } from "../../src/core/storage";
import { AccessServiceTest } from "./access-service-test";
import { logger } from "../../src/utils/util.log";
const contextFactory = new ContextFactory(new FileStorage());
const userContext = contextFactory.getContext("user", "test");
const pipelineContext = contextFactory.getContext("pipeline", "test");
export const pluginInitProps = {
accessService: new AccessServiceTest(),
pipelineContext: pipelineContext,
userContext: userContext,
logger: logger,
};

View File

@ -0,0 +1,66 @@
import { ConcurrencyStrategy, NextStrategy, Pipeline, RunStrategy } from "../../src";
let idIndex = 0;
function generateId() {
idIndex++;
return idIndex + "";
}
export const pipeline: Pipeline = {
version: 1,
id: generateId(),
title: "测试管道",
userId: 1,
triggers: [],
stages: [
{
id: generateId(),
title: "证书申请阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "申请证书任务",
steps: [
{
id: generateId(),
title: "申请证书",
type: "CertApply",
input: {
domains: ["*.docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
},
},
],
},
],
},
{
id: generateId(),
title: "证书部署阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "测试输出参数任务",
steps: [
{
id: generateId(),
title: "输出参数echo插件",
type: "EchoPlugin",
input: {
cert: "cert",
},
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
],
},
],
},
],
};

View File

@ -0,0 +1,18 @@
//import { expect } from "chai";
import "mocha";
import { Executor, RunHistory } from "../../src";
import { pipeline } from "./pipeline.define";
import { AccessServiceTest } from "./access-service-test";
import { FileStorage } from "../../src/core/storage";
describe("pipeline", function () {
it("#pipeline", async function () {
this.timeout(120000);
function onChanged(history: RunHistory) {
console.log("changed:");
}
const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() });
await executor.run(1, "user");
// expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { CertApplyPlugin } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("CertApply", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new CertApplyPlugin();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const output = await plugin.execute({
domains: ["*.docmirror.cn", "docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
forceUpdate: true,
});
const cert = output.cert;
expect(plugin.getDefine().name).eq("CertApply");
expect(cert.crt != null).eq(true);
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { DeployCertToAliyunCDN } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("DeployToAliyunCDN", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new DeployCertToAliyunCDN();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const cert = await pluginInitProps.pipelineContext.get("cert");
await plugin.execute({
cert,
domainName: "certd-cdn-upload.docmirror.cn",
});
expect(plugin.getDefine().name).eq("DeployCertToAliyunCDN");
});
});

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "commonjs",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
}

View File

@ -0,0 +1,27 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: "src/index.ts",
name: "pipeline",
},
rollupOptions: {
external: ["vue", "lodash-es", "dayjs", "@fast-crud/fast-crud", "log4js", "lodash", "@certd/pipeline"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: "Vue",
"lodash-es": "_",
lodash: "_",
dayjs: "dayjs",
"@fast-crud/fast-crud": "FastCrud",
log4js: "log4js",
"@certd/pipeline": "pipeline",
},
},
},
},
});

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,27 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: "dnspod",
title: "dnspod",
desc: "腾讯云的域名解析接口已迁移到dnspod",
input: {
id: {
title: "账户id",
component: {
placeholder: "dnspod接口账户id",
},
rules: [{ required: true, message: "该项必填" }],
},
token: {
title: "token",
component: {
placeholder: "开放接口token",
},
rules: [{ required: true, message: "该项必填" }],
},
},
})
export class DnspodAccess extends AbstractAccess {
token = "";
id = "";
}

View File

@ -0,0 +1,2 @@
export * from "./dnspod-access";
export * from "./tencent-access";

View File

@ -0,0 +1,3 @@
export * from "./access";
export * from "./plugin";
export * from "./dns-provider";

View File

@ -0,0 +1,101 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { TencentAccess } from "../../access";
@IsTask(() => {
return {
name: "DeployCertToTencentCDN",
title: "部署到腾讯云CDN",
input: {
domainName: {
title: "cdn加速域名",
rules: [{ required: true, message: "该项必填" }],
},
certName: {
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
from: "CertApply",
},
required: true,
},
accessId: {
title: "Access提供者",
helper: "access 授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
},
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {},
};
})
export class DeployToCdnPlugin extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
const { cert, accessId } = input;
const accessProvider: TencentAccess = (await this.accessService.getById(accessId)) as TencentAccess;
const client = this.getClient(accessProvider);
const params = this.buildParams(input, cert);
await this.doRequest(client, params);
return {};
}
getClient(accessProvider: TencentAccess) {
const CdnClient = tencentcloud.cdn.v20180606.Client;
const clientConfig = {
credential: {
secretId: accessProvider.secretId,
secretKey: accessProvider.secretKey,
},
region: "",
profile: {
httpProfile: {
endpoint: "cdn.tencentcloudapi.com",
},
},
};
return new CdnClient(clientConfig);
}
buildParams(props: TaskInput, cert: any) {
const { domainName } = props;
return {
Https: {
Switch: "on",
CertInfo: {
Certificate: cert.crt,
PrivateKey: cert.key,
},
},
Domain: domainName,
};
}
async doRequest(client: any, params: any) {
const ret = await client.UpdateDomainConfig(params);
this.checkRet(ret);
this.logger.info("设置腾讯云CDN证书成功:", ret.RequestId);
return ret.RequestId;
}
checkRet(ret: any) {
if (!ret || ret.Error) {
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
}
}
}

View File

@ -0,0 +1,4 @@
export * from "./deploy-to-clb";
export * from "./deploy-to-tke-ingress";
export * from "./deploy-to-cdn";
export * from "./upload-to-tencent";

View File

@ -0,0 +1,58 @@
export const fakeCrt = `-----BEGIN CERTIFICATE-----
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTQx
NjA1NTFaFw0yMTAzMTQxNjA1NTFaMBsxGTAXBgNVBAMMECouZG9jbWlycm9yLmNs
dWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75tGrYjly+RpcZehQ
my1EpaXElT4L60pINKV2YDKnBrcSSo1c6rO7nFh12eC/ju4WwYUep0RVmBDF8xD0
I1Sd1uuDTQWP0UT1X9yqdXtjvxpUqoCHAzG633f3sJRFul7mDLuC9tRCuae9o7qP
EZ827XOmjBR35dso9I2GEE4828J3YE3tSKtobZlM+30jozLEcsO0PTyM5mq5PPjP
VI3fGLcEaBmLZf5ixz4XkcY9IAhyAMYf03cT2wRoYPBaDdXblgCYL6sFtIMbzl3M
Di94PB8NyoNSsC2nmBdWi54wFOgBvY/4ljsX/q7X3EqlSvcA0/M6/c/J9kJ3eupv
jV8nAgMBAAGjggJ9MIICeTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAkdTjSCV3KD
x28sf98MrwVfyFYgMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHcG
CCsGAQUFBwEBBGswaTAyBggrBgEFBQcwAYYmaHR0cDovL29jc3Auc3RnLWludC14
MS5sZXRzZW5jcnlwdC5vcmcwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0Zy1p
bnQteDEubGV0c2VuY3J5cHQub3JnLzArBgNVHREEJDAighAqLmRvY21pcnJvci5j
bHVigg5kb2NtaXJyb3IuY2x1YjBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE
AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y
ZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1ABboacHRlerXw/iXGuPwdgH3jOG2
nTGoUhi2g38xqBUIAAABdmI3LM4AAAQDAEYwRAIgaiNqXSEq+sxp8eqlJXp/KFdO
so5mT50MoRsLF8Inu0ACIDP46+ekng7I0BlmyIPmbqFcZgnZFVWLLCdLYijhVyOL
AHcA3Zk0/KXnJIDJVmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAF2YjcuxwAABAMA
SDBGAiEAxpeB8/w4YkHZ62nH20h128VtuTSmYDCnF7EK2fQyeZYCIQDbJlF2wehZ
sF1BeE7qnYYqCTP0dYIrQ9HWtBa/MbGOKTANBgkqhkiG9w0BAQsFAAOCAQEAL2di
HKh6XcZtGk0BFxJa51sCZ3MLu9+Zy90kCRD4ooP5x932WxVM25+LBRd+xSzx+TRL
UVrlKp9GdMYX1JXL4Vf2NwzuFO3snPDe/qizD/3+D6yo8eKJ/LD82t5kLWAD2rto
YfVSTKwfNIBBJwHUnjviBPJmheHHCKmz8Ct6/6QxFAeta9TAMn0sFeVCQnmAq7HL
jrunq0tNHR/EKG0ITPLf+6P7MxbmpYNnq918766l0tKsW8oo8ZSGEwKU2LMaSiAa
hasyl/2gMnYXjtKOjDcnR8oLpbrOg0qpVbynmJin1HP835oHPPAZ1gLsqYTTizNz
AHxTaXliTVvS83dogw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
-----END CERTIFICATE-----`;

View File

@ -0,0 +1,11 @@
import { expect } from "chai";
import "mocha";
import { EchoPlugin } from "../src/plugin";
describe("task_plugin", function () {
it("#taskplugin", function () {
const echoPlugin = new EchoPlugin();
const define = echoPlugin.define;
echoPlugin.execute({ context: {}, props: { test: 111 } });
expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,10 @@
import { IAccessService } from "../../src/access/access-service";
import { AbstractAccess, HostAccess } from "../../src";
import { aliyunSecret } from "../user.secret";
export class AccessServiceTest implements IAccessService {
async getById(id: any): Promise<AbstractAccess> {
return {
...aliyunSecret,
} as HostAccess;
}
}

View File

@ -0,0 +1,15 @@
import { ContextFactory } from "../../src/core/context";
import { FileStorage } from "../../src/core/storage";
import { AccessServiceTest } from "./access-service-test";
import { logger } from "../../src/utils/util.log";
const contextFactory = new ContextFactory(new FileStorage());
const userContext = contextFactory.getContext("user", "test");
const pipelineContext = contextFactory.getContext("pipeline", "test");
export const pluginInitProps = {
accessService: new AccessServiceTest(),
pipelineContext: pipelineContext,
userContext: userContext,
logger: logger,
};

View File

@ -0,0 +1,66 @@
import { ConcurrencyStrategy, NextStrategy, Pipeline, RunStrategy } from "../../src";
let idIndex = 0;
function generateId() {
idIndex++;
return idIndex + "";
}
export const pipeline: Pipeline = {
version: 1,
id: generateId(),
title: "测试管道",
userId: 1,
triggers: [],
stages: [
{
id: generateId(),
title: "证书申请阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "申请证书任务",
steps: [
{
id: generateId(),
title: "申请证书",
type: "CertApply",
input: {
domains: ["*.docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
},
},
],
},
],
},
{
id: generateId(),
title: "证书部署阶段",
concurrency: ConcurrencyStrategy.Serial,
next: NextStrategy.AllSuccess,
tasks: [
{
id: generateId(),
title: "测试输出参数任务",
steps: [
{
id: generateId(),
title: "输出参数echo插件",
type: "EchoPlugin",
input: {
cert: "cert",
},
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
],
},
],
},
],
};

View File

@ -0,0 +1,18 @@
//import { expect } from "chai";
import "mocha";
import { Executor, RunHistory } from "../../src";
import { pipeline } from "./pipeline.define";
import { AccessServiceTest } from "./access-service-test";
import { FileStorage } from "../../src/core/storage";
describe("pipeline", function () {
it("#pipeline", async function () {
this.timeout(120000);
function onChanged(history: RunHistory) {
console.log("changed:");
}
const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() });
await executor.run(1, "user");
// expect(define.name).eq("EchoPlugin");
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { CertApplyPlugin } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("CertApply", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new CertApplyPlugin();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const output = await plugin.execute({
domains: ["*.docmirror.cn", "docmirror.cn"],
email: "xiaojunnuo@qq.com",
dnsProviderType: "aliyun",
accessId: "111",
forceUpdate: true,
});
const cert = output.cert;
expect(plugin.getDefine().name).eq("CertApply");
expect(cert.crt != null).eq(true);
});
});

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import "mocha";
import { DeployCertToAliyunCDN } from "../../../src/plugin";
import { pluginInitProps } from "../init.test";
describe("DeployToAliyunCDN", function () {
it("#execute", async function () {
this.timeout(120000);
const plugin = new DeployCertToAliyunCDN();
// @ts-ignore
delete plugin.define;
await plugin.doInit(pluginInitProps);
const cert = await pluginInitProps.pipelineContext.get("cert");
await plugin.execute({
cert,
domainName: "certd-cdn-upload.docmirror.cn",
});
expect(plugin.getDefine().name).eq("DeployCertToAliyunCDN");
});
});

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "commonjs",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts"],
}

View File

@ -0,0 +1,28 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: "src/index.ts",
name: "pipeline",
},
rollupOptions: {
external: ["vue", "lodash-es", "dayjs", "@fast-crud/fast-crud", "log4js", "lodash", "@certd/pipeline", "axios"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: "Vue",
"lodash-es": "_",
lodash: "_",
dayjs: "dayjs",
"@fast-crud/fast-crud": "FastCrud",
log4js: "log4js",
"@certd/pipeline": "pipeline",
axios: "axios",
},
},
},
},
});

View File

@ -0,0 +1,21 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}

26
packages/plugins/plugin-util/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test/user.secret.ts

View File

@ -0,0 +1,5 @@
{
"extension": ["ts"],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
}

View File

@ -0,0 +1,3 @@
{
"printWidth": 160
}

View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

View File

@ -0,0 +1 @@
export * from "./src";

Some files were not shown because too many files have changed in this diff Show More