@certd/pipeline 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.
Files changed (45) hide show
  1. package/.eslintrc +21 -0
  2. package/.mocharc.json +5 -0
  3. package/.prettierrc +3 -0
  4. package/README.md +16 -0
  5. package/index.ts +1 -0
  6. package/package.json +61 -0
  7. package/src/access/api.ts +18 -0
  8. package/src/access/decorator.ts +39 -0
  9. package/src/access/index.ts +3 -0
  10. package/src/access/registry.ts +4 -0
  11. package/src/context/index.ts +6 -0
  12. package/src/core/context.ts +88 -0
  13. package/src/core/executor.ts +207 -0
  14. package/src/core/index.ts +4 -0
  15. package/src/core/run-history.ts +158 -0
  16. package/src/core/storage.ts +140 -0
  17. package/src/d.ts/fast-crud.ts +115 -0
  18. package/src/d.ts/index.ts +2 -0
  19. package/src/d.ts/pipeline.ts +119 -0
  20. package/src/decorator/common.ts +17 -0
  21. package/src/decorator/index.ts +2 -0
  22. package/src/decorator/utils.ts +42 -0
  23. package/src/index.ts +9 -0
  24. package/src/midway/configuration.ts +52 -0
  25. package/src/midway/index.ts +5 -0
  26. package/src/plugin/api.ts +60 -0
  27. package/src/plugin/decorator.ts +63 -0
  28. package/src/plugin/index.ts +3 -0
  29. package/src/plugin/registry.ts +4 -0
  30. package/src/plugin/test/echo-plugin.ts +31 -0
  31. package/src/registry/index.ts +1 -0
  32. package/src/registry/registry.ts +57 -0
  33. package/src/utils/index.ts +7 -0
  34. package/src/utils/util.log.ts +35 -0
  35. package/src/utils/util.request.ts +58 -0
  36. package/src/utils/util.sleep.ts +7 -0
  37. package/test/cert.fake.test.ts +58 -0
  38. package/test/echo-plugin.ts +30 -0
  39. package/test/index.test.ts +16 -0
  40. package/test/pipeline/access-service-test.ts +10 -0
  41. package/test/pipeline/init.test.ts +15 -0
  42. package/test/pipeline/pipeline.define.ts +66 -0
  43. package/test/pipeline/pipeline.test.ts +18 -0
  44. package/tsconfig.json +20 -0
  45. package/vite.config.js +35 -0
@@ -0,0 +1,140 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+
5
+
6
+ export interface IStorage {
7
+ get(scope: string, namespace: string, version: string, key: string): Promise<string | null>;
8
+ set(scope: string, namespace: string, version: string, key: string, value: string): Promise<void>;
9
+ remove(scope: string, namespace: string, version: string, key: string): Promise<void>;
10
+ }
11
+
12
+ export class FileStorage implements IStorage {
13
+ root: string;
14
+ constructor(rootDir?: string) {
15
+ if (rootDir == null) {
16
+ const userHome = process.env.HOME || process.env.USERPROFILE;
17
+ rootDir = userHome + "/.certd/storage/";
18
+ }
19
+ this.root = rootDir;
20
+
21
+ if (!fs.existsSync(this.root)) {
22
+ fs.mkdirSync(this.root, { recursive: true });
23
+ }
24
+ }
25
+
26
+ async remove(scope: string, namespace: string, version: string, key: string): Promise<void> {
27
+ if (key) {
28
+ fs.unlinkSync(this.buildPath(scope, namespace, version, key));
29
+ } else if (version) {
30
+ fs.rmdirSync(this.buildPath(scope, namespace, version));
31
+ } else if (namespace) {
32
+ fs.rmdirSync(this.buildPath(scope, namespace));
33
+ } else {
34
+ fs.rmdirSync(this.buildPath(scope));
35
+ }
36
+ }
37
+
38
+ writeFile(filePath: string, value: string) {
39
+ const dir = path.dirname(filePath);
40
+ if (!fs.existsSync(dir)) {
41
+ fs.mkdirSync(dir, { recursive: true });
42
+ }
43
+ fs.writeFileSync(filePath, value);
44
+ return filePath;
45
+ }
46
+
47
+ readFile(filePath: string) {
48
+ if (!fs.existsSync(filePath)) {
49
+ return null;
50
+ }
51
+ return fs.readFileSync(filePath).toString();
52
+ }
53
+
54
+ async get(scope: string, namespace: string, version: string, key: string): Promise<string | null> {
55
+ const path = this.buildPath(scope, namespace, version, key);
56
+ return this.readFile(path);
57
+ }
58
+
59
+ async set(scope: string, namespace: string, version: string, key: string, value: string): Promise<void> {
60
+ const path = this.buildPath(scope, namespace, version, key);
61
+ this.writeFile(path, value);
62
+ }
63
+
64
+ private buildPath(scope: string, namespace?: string, version?: string, key?: string) {
65
+ if (key) {
66
+ return `${this.root}/${scope}/${namespace}/${version}/${key}`;
67
+ } else if (version) {
68
+ return `${this.root}/${scope}/${namespace}/${version}`;
69
+ } else if (namespace) {
70
+ return `${this.root}/${scope}/${namespace}`;
71
+ } else {
72
+ return `${this.root}/${scope}`;
73
+ }
74
+ }
75
+ }
76
+
77
+ export class MemoryStorage implements IStorage {
78
+ /**
79
+ * 范围: user / pipeline / runtime / task
80
+ */
81
+ scope: any;
82
+ namespace: any;
83
+ context: {
84
+ [scope: string]: {
85
+ [key: string]: any;
86
+ };
87
+ } = {};
88
+
89
+ async get(scope: string, namespace: string, version: string, key: string): Promise<string | null> {
90
+ const scopeContext = this.context[scope];
91
+ if (scopeContext == null) {
92
+ return null;
93
+ }
94
+ const nsContext = scopeContext[namespace];
95
+ if (nsContext == null) {
96
+ return null;
97
+ }
98
+ const versionContext = nsContext[version];
99
+ if (versionContext == null) {
100
+ return null;
101
+ }
102
+ return versionContext[key];
103
+ }
104
+
105
+ async set(scope: string, namespace: string, version: string, key: string, value: string): Promise<void> {
106
+ let scopeContext = this.context[scope];
107
+ if (scopeContext == null) {
108
+ scopeContext = scopeContext[scope];
109
+ }
110
+ let nsContext = scopeContext[namespace];
111
+ if (nsContext == null) {
112
+ nsContext = {};
113
+ scopeContext[namespace] = nsContext;
114
+ }
115
+ let versionContext = nsContext[version];
116
+ if (versionContext == null) {
117
+ versionContext = {};
118
+ nsContext[version] = versionContext;
119
+ }
120
+ versionContext[key] = value;
121
+ }
122
+
123
+ async remove(scope: string, namespace = "", version = "", key = "") {
124
+ if (key) {
125
+ if (this.context[scope] && this.context[scope][namespace] && this.context[scope][namespace][version]) {
126
+ delete this.context[scope][namespace][version][key];
127
+ }
128
+ } else if (version) {
129
+ if (this.context[scope] && this.context[scope][namespace]) {
130
+ delete this.context[scope][namespace][version];
131
+ }
132
+ } else if (namespace) {
133
+ if (this.context[scope]) {
134
+ delete this.context[scope][namespace];
135
+ }
136
+ } else {
137
+ delete this.context[scope];
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * [x]-col的配置
3
+ */
4
+ export type ColProps = {
5
+ span?: number;
6
+ [props: string]: any;
7
+ };
8
+
9
+ export type FormItemProps = {
10
+ /**
11
+ * 字段label
12
+ */
13
+ title?: string;
14
+ /**
15
+ * 表单字段组件配置
16
+ */
17
+ component?: ComponentProps;
18
+ /**
19
+ * 表单字段 [a|el|n]-col的配置
20
+ * 一般用来配置跨列:{span:24} 占满一行
21
+ */
22
+ col?: ColProps;
23
+ /**
24
+ * 默认值
25
+ */
26
+ value?: any;
27
+ /**
28
+ * 帮助提示配置
29
+ */
30
+ helper?: string | FormItemHelperProps;
31
+ /**
32
+ * 排序号
33
+ */
34
+ order?: number;
35
+ /**
36
+ * 是否显示此字段
37
+ */
38
+ show?: boolean;
39
+ /**
40
+ * 是否是空白占位栏
41
+ */
42
+ blank?: boolean;
43
+
44
+ [key: string]: any;
45
+ };
46
+
47
+ /**
48
+ * 表单字段帮助说明配置
49
+ */
50
+ export type FormItemHelperProps = {
51
+ /**
52
+ * 自定义渲染帮助说明
53
+ * @param scope
54
+ */
55
+ render?: (scope: any) => any;
56
+ /**
57
+ * 帮助文本
58
+ */
59
+ text?: string;
60
+ /**
61
+ * 帮助说明所在的位置,[ undefined | label]
62
+ */
63
+ position?: string;
64
+ /**
65
+ * [a|el|n]-tooltip配置
66
+ */
67
+ tooltip?: object;
68
+
69
+ [key: string]: any;
70
+ };
71
+
72
+ /**
73
+ * 组件配置
74
+ */
75
+ export type ComponentProps = {
76
+ /**
77
+ * 组件的名称
78
+ */
79
+ name?: string | object;
80
+ /**
81
+ * vmodel绑定的目标属性名
82
+ */
83
+ vModel?: string;
84
+
85
+ /**
86
+ * 当原始组件名的参数被以上属性名占用时,可以配置在这里
87
+ * 例如:原始组件有一个叫name的属性,你想要配置它,则可以按如下配置
88
+ * ```
89
+ * component:{
90
+ * name:"组件的名称"
91
+ * props:{
92
+ * name:"组件的name属性" <-----------
93
+ * }
94
+ * }
95
+ * ```
96
+ */
97
+ props?: {
98
+ [key: string]: any;
99
+ };
100
+
101
+ /**
102
+ * 组件事件监听
103
+ */
104
+ on?: {
105
+ [key: string]: (context?: any) => void;
106
+ };
107
+
108
+ /**
109
+ * 组件其他参数
110
+ * 事件:onXxx:(event)=>void 组件原始事件监听
111
+ * on.onXxx:(context)=>void 组件事件监听(对原始事件包装)
112
+ * 样式:style、class等
113
+ */
114
+ [key: string]: any;
115
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./pipeline";
2
+ export * from "./fast-crud";
@@ -0,0 +1,119 @@
1
+ export enum RunStrategy {
2
+ AlwaysRun,
3
+ SkipWhenSucceed,
4
+ }
5
+
6
+ export enum ConcurrencyStrategy {
7
+ Serial,
8
+ Parallel,
9
+ }
10
+
11
+ export enum NextStrategy {
12
+ AllSuccess,
13
+ OneSuccess,
14
+ }
15
+
16
+ export enum HandlerType {
17
+ //清空后续任务的状态
18
+ ClearFollowStatus,
19
+ SendEmail,
20
+ }
21
+
22
+ export type EventHandler = {
23
+ type: HandlerType;
24
+ params: {
25
+ [key: string]: any;
26
+ };
27
+ };
28
+
29
+ export type RunnableStrategy = {
30
+ runStrategy?: RunStrategy;
31
+ onSuccess?: EventHandler[];
32
+ onError?: EventHandler[];
33
+ };
34
+
35
+ export type Step = Runnable & {
36
+ type: string; //插件类型
37
+ input: {
38
+ [key: string]: any;
39
+ };
40
+ };
41
+ export type Task = Runnable & {
42
+ steps: Step[];
43
+ };
44
+
45
+ export type Stage = Runnable & {
46
+ tasks: Task[];
47
+ concurrency: ConcurrencyStrategy;
48
+ next: NextStrategy;
49
+ };
50
+
51
+ export type Trigger = {
52
+ id: string;
53
+ title: string;
54
+ cron: string;
55
+ type: string;
56
+ };
57
+
58
+ export type Runnable = {
59
+ id: string;
60
+ title: string;
61
+ strategy?: RunnableStrategy;
62
+ runnableType?: string; // pipeline, stage, task , step
63
+ status?: HistoryResult;
64
+ default?: {
65
+ [key: string]: any;
66
+ };
67
+ };
68
+
69
+ export type Pipeline = Runnable & {
70
+ version?: number;
71
+ userId: any;
72
+ stages: Stage[];
73
+ triggers: Trigger[];
74
+ };
75
+
76
+ export type Context = {
77
+ [key: string]: any;
78
+ };
79
+
80
+ export type Log = {
81
+ title: string;
82
+ time: number;
83
+ level: string;
84
+ text: string;
85
+ };
86
+
87
+ export enum ResultType {
88
+ start = "start",
89
+ success = "success",
90
+ error = "error",
91
+ skip = "skip",
92
+ none = "none",
93
+ }
94
+
95
+ export type HistoryResultGroup = {
96
+ [key: string]: {
97
+ runnable: Runnable;
98
+ res: HistoryResult;
99
+ };
100
+ };
101
+ export type HistoryResult = {
102
+ input: any;
103
+ output: any;
104
+ /**
105
+ * 任务状态
106
+ */
107
+ status: ResultType;
108
+ startTime: number;
109
+ endTime?: number;
110
+ /**
111
+ * 处理结果
112
+ */
113
+ result?: ResultType; //success, error,skip
114
+ message?: string;
115
+ };
116
+
117
+ export type RunnableMap = {
118
+ [id: string]: Runnable;
119
+ };
@@ -0,0 +1,17 @@
1
+ import { Decorator } from "./index";
2
+
3
+ export type AutowireProp = {
4
+ name?: string;
5
+ type?: any;
6
+ };
7
+ export const AUTOWIRE_KEY = "pipeline:autowire";
8
+
9
+ export function Autowire(props?: AutowireProp): PropertyDecorator {
10
+ return (target, propertyKey) => {
11
+ const _type = Reflect.getMetadata("design:type", target, propertyKey);
12
+ target = Decorator.target(target, propertyKey);
13
+ props = props || {};
14
+ props.type = _type;
15
+ Reflect.defineMetadata(AUTOWIRE_KEY, props || {}, target, propertyKey);
16
+ };
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./utils";
2
+ export * from "./common";
@@ -0,0 +1,42 @@
1
+ import _ from "lodash";
2
+
3
+ const propertyMap: any = {};
4
+ function attachProperty(target: any, propertyKey: string | symbol) {
5
+ let props = propertyMap[target];
6
+ if (props == null) {
7
+ props = {};
8
+ propertyMap[target] = props;
9
+ }
10
+ props[propertyKey] = true;
11
+ }
12
+
13
+ function getClassProperties(target: any) {
14
+ return propertyMap[target] || {};
15
+ }
16
+
17
+ function target(target: any, propertyKey?: string | symbol) {
18
+ if (typeof target === "object" && target.constructor) {
19
+ target = target.constructor;
20
+ }
21
+ if (propertyKey != null) {
22
+ attachProperty(target, propertyKey);
23
+ }
24
+ return target;
25
+ }
26
+
27
+ function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) {
28
+ _.forEach(define, (item, key) => {
29
+ if (preHandler) {
30
+ preHandler(item, key, instance, context);
31
+ }
32
+ if (context[key] != undefined) {
33
+ instance[key] = context[key];
34
+ }
35
+ });
36
+ }
37
+ export const Decorator = {
38
+ target,
39
+ attachProperty,
40
+ getClassProperties,
41
+ inject,
42
+ };
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from "./core";
2
+ export * from "./d.ts";
3
+ export * from "./access";
4
+ export * from "./registry";
5
+ export * from "./plugin";
6
+ export * from "./utils";
7
+ export * from "./midway";
8
+ export * from "./context";
9
+ export * from "./decorator";
@@ -0,0 +1,52 @@
1
+ import { Config, Configuration, Inject, Logger } from "@midwayjs/decorator";
2
+ // @ts-ignore
3
+ import { ILogger } from "@midwayjs/logger";
4
+ import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core";
5
+
6
+ // ... (see below) ...
7
+ @Configuration({
8
+ namespace: "pipeline",
9
+ //importConfigs: [join(__dirname, './config')],
10
+ })
11
+ export class PipelineConfiguration {
12
+ @Config()
13
+ // @ts-ignore
14
+ config;
15
+ @Logger()
16
+ // @ts-ignore
17
+ logger: ILogger;
18
+
19
+ @Inject()
20
+ // @ts-ignore
21
+ decoratorService: MidwayDecoratorService;
22
+
23
+ async onReady(container: IMidwayContainer) {
24
+ this.logger.info("pipeline install");
25
+
26
+ //this.implPropertyDecorator(container);
27
+ this.logger.info("pipeline installed");
28
+ }
29
+
30
+ // implPropertyDecorator(container: IMidwayContainer) {
31
+ // this.logger.info("初始化 property decorator");
32
+ // // 实现装饰器
33
+ // this.decoratorService.registerPropertyHandler(CLASS_INPUTS_KEY, (propertyName, meta) => {
34
+ // return undefined;
35
+ // });
36
+ //
37
+ // const autowireWhiteList: any = {
38
+ // logger: true,
39
+ // };
40
+ // this.decoratorService.registerPropertyHandler(CLASS_AUTOWIRE_KEY, (propertyName, meta) => {
41
+ // // eslint-disable-next-line no-debugger
42
+ // debugger;
43
+ // const className = meta.name;
44
+ // if (autowireWhiteList[className]) {
45
+ // //在白名单里面,注入
46
+ // return container.get(className);
47
+ // }
48
+ // this.logger.warn(`autowire failed:${className} class is not in white list`);
49
+ // return undefined;
50
+ // });
51
+ // }
52
+ }
@@ -0,0 +1,5 @@
1
+ // src/index.ts
2
+ export { PipelineConfiguration } from "./configuration";
3
+ // export * from './controller/user';
4
+ // export * from './controller/api';
5
+ // export * from './service/user';
@@ -0,0 +1,60 @@
1
+ import { Registrable } from "../registry";
2
+ import { FormItemProps } from "../d.ts";
3
+
4
+ export enum ContextScope {
5
+ global,
6
+ pipeline,
7
+ runtime,
8
+ }
9
+
10
+ export type Storage = {
11
+ scope: ContextScope;
12
+ path: string;
13
+ };
14
+
15
+ export type TaskOutputDefine = {
16
+ title: string;
17
+ value?: any;
18
+ storage?: Storage;
19
+ };
20
+ export type TaskInputDefine = FormItemProps;
21
+
22
+ export type PluginDefine = Registrable & {
23
+ default?: any;
24
+ input?: {
25
+ [key: string]: TaskInputDefine;
26
+ };
27
+ output?: {
28
+ [key: string]: TaskOutputDefine;
29
+ };
30
+
31
+ autowire?: {
32
+ [key: string]: any;
33
+ };
34
+ };
35
+
36
+ export type ITaskPlugin = {
37
+ onInstance(): Promise<void>;
38
+ execute(): Promise<void>;
39
+ [key: string]: any;
40
+ };
41
+
42
+ export type TaskResult = {
43
+ clearLastStatus?: boolean;
44
+ };
45
+ export abstract class AbstractTaskPlugin implements ITaskPlugin {
46
+ result: TaskResult = {};
47
+ clearLastStatus() {
48
+ this.result.clearLastStatus = true;
49
+ }
50
+ async onInstance(): Promise<void> {
51
+ return;
52
+ }
53
+ abstract execute(): Promise<void>;
54
+ }
55
+
56
+ export type OutputVO = {
57
+ key: string;
58
+ title: string;
59
+ value: any;
60
+ };
@@ -0,0 +1,63 @@
1
+ import _ from "lodash";
2
+ import { pluginRegistry } from "./registry";
3
+ import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api";
4
+ import { Decorator } from "../decorator";
5
+ import { AUTOWIRE_KEY } from "../decorator";
6
+
7
+ // 提供一个唯一 key
8
+ export const PLUGIN_CLASS_KEY = "pipeline:plugin";
9
+
10
+ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
11
+ console.log("IsTaskPlugin");
12
+ return (target: any) => {
13
+ target = Decorator.target(target);
14
+
15
+ const inputs: any = {};
16
+ const autowires: any = {};
17
+ const outputs: any = {};
18
+ const properties = Decorator.getClassProperties(target);
19
+ for (const property in properties) {
20
+ const input = Reflect.getMetadata(PLUGIN_INPUT_KEY, target, property);
21
+ if (input) {
22
+ inputs[property] = input;
23
+ }
24
+
25
+ const autowire = Reflect.getMetadata(AUTOWIRE_KEY, target, property);
26
+ if (autowire) {
27
+ autowires[property] = autowire;
28
+ }
29
+
30
+ const output = Reflect.getMetadata(PLUGIN_OUTPUT_KEY, target, property);
31
+ if (output) {
32
+ outputs[property] = output;
33
+ }
34
+ }
35
+ _.merge(define, { input: inputs, autowire: autowires, output: outputs });
36
+
37
+ Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);
38
+
39
+ target.define = define;
40
+ pluginRegistry.register(define.name, {
41
+ define,
42
+ target,
43
+ });
44
+ };
45
+ }
46
+
47
+ export const PLUGIN_INPUT_KEY = "pipeline:plugin:input";
48
+
49
+ export function TaskInput(input?: TaskInputDefine): PropertyDecorator {
50
+ return (target, propertyKey) => {
51
+ target = Decorator.target(target, propertyKey);
52
+ Reflect.defineMetadata(PLUGIN_INPUT_KEY, input, target, propertyKey);
53
+ };
54
+ }
55
+
56
+ // 装饰器内部的唯一 id
57
+ export const PLUGIN_OUTPUT_KEY = "pipeline:plugin:output";
58
+ export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator {
59
+ return (target, propertyKey) => {
60
+ target = Decorator.target(target, propertyKey);
61
+ Reflect.defineMetadata(PLUGIN_OUTPUT_KEY, output, target, propertyKey);
62
+ };
63
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./api";
2
+ export * from "./registry";
3
+ export * from "./decorator";
@@ -0,0 +1,4 @@
1
+ import { Registry } from "../registry";
2
+ import { AbstractTaskPlugin } from "./api";
3
+
4
+ export const pluginRegistry = new Registry<AbstractTaskPlugin>();
@@ -0,0 +1,31 @@
1
+ import { ILogger } from "@midwayjs/logger";
2
+ import { ITaskPlugin } from "../api";
3
+ import { IsTaskPlugin, TaskInput } from "../decorator";
4
+ import { Autowire } from "../../decorator";
5
+
6
+ @IsTaskPlugin({
7
+ name: "EchoPlugin",
8
+ title: "测试插件",
9
+ desc: "test",
10
+ })
11
+ export class EchoPlugin implements ITaskPlugin {
12
+ @TaskInput({
13
+ title: "测试属性",
14
+ component: {
15
+ name: "text",
16
+ },
17
+ })
18
+ test?: string;
19
+
20
+ @Autowire()
21
+ // @ts-ignore
22
+ logger: ILogger;
23
+
24
+ onInstance(): Promise<void> {
25
+ throw new Error("Method not implemented.");
26
+ }
27
+
28
+ async execute(): Promise<void> {
29
+ return Promise.resolve(undefined);
30
+ }
31
+ }
@@ -0,0 +1 @@
1
+ export * from "./registry";