feat: 升级midway,支持esm

This commit is contained in:
xiaojunnuo
2024-07-15 00:30:33 +08:00
parent 970c7fd8a0
commit 485e603b51
246 changed files with 3821 additions and 1532 deletions
+3
View File
@@ -34,3 +34,6 @@ gen
docker/image/workspace docker/image/workspace
/packages/core/lego /packages/core/lego
tsconfig.tsbuildinfo
test/**/*.js
+2 -1
View File
@@ -1 +1,2 @@
link-workspace-packages=deep link-workspace-packages=true
prefer-workspace-packages=true
+7
View File
@@ -0,0 +1,7 @@
{
"printWidth": 160,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}
+3 -2
View File
@@ -16,11 +16,12 @@
"afterpublishOnly": "", "afterpublishOnly": "",
"prepublishOnly1": "npm run before-build && lerna run build ", "prepublishOnly1": "npm run before-build && lerna run build ",
"before-build": "cd ./packages/core/acme-client && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"", "before-build": "cd ./packages/core/acme-client && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules deploy.js " "deploy1": "node --experimental-json-modules deploy.js ",
"init": "lerna run build"
}, },
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.7.2",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"workspaces": [ "workspaces": [
+2 -1
View File
@@ -3,9 +3,10 @@
"module": "commonjs", "module": "commonjs",
"lib": ["es6"], "lib": ["es6"],
"strict": true, "strict": true,
"noEmit": true, "noEmit": false,
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": ".", "baseUrl": ".",
"composite": true,
"paths": { "acme-client": ["."] } "paths": { "acme-client": ["."] }
} }
} }
+1 -1
View File
@@ -23,4 +23,4 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
test/user.secret.ts test/user.secret.*
+2
View File
@@ -0,0 +1,2 @@
link-workspace-packages=true
prefer-workspace-packages=true
@@ -0,0 +1,96 @@
import * as fs from "fs";
import * as path from "path";
// https://gist.github.com/lovasoa/8691344
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) {
yield* walk(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
function resolveImportPath(sourceFile, importPath, options) {
const sourceFileAbs = path.resolve(process.cwd(), sourceFile);
const root = path.dirname(sourceFileAbs);
const { moduleFilter = defaultModuleFilter } = options;
if (moduleFilter(importPath)) {
const importPathAbs = path.resolve(root, importPath);
let possiblePath = [path.resolve(importPathAbs, "./index.ts"), path.resolve(importPathAbs, "./index.js"), importPathAbs + ".ts", importPathAbs + ".js"];
if (possiblePath.length) {
for (let i = 0; i < possiblePath.length; i++) {
let entry = possiblePath[i];
if (fs.existsSync(entry)) {
const resolved = path.relative(root, entry.replace(/\.ts$/, ".js"));
if (!resolved.startsWith(".")) {
return "./" + resolved;
}
return resolved;
}
}
}
}
return null;
}
function replace(filePath, outFilePath, options) {
const code = fs.readFileSync(filePath).toString();
const newCode = code.replace(/(import|export) (.+?) from ('[^\n']+'|"[^\n"]+");/gs, function (found, action, imported, from) {
const importPath = from.slice(1, -1);
let resolvedPath = resolveImportPath(filePath, importPath, options);
if (resolvedPath) {
resolvedPath = resolvedPath.replaceAll("\\", "/");
console.log("\t", importPath, resolvedPath);
return `${action} ${imported} from "${resolvedPath}";`;
}
return found;
});
if (code !== newCode) {
fs.writeFileSync(outFilePath, newCode);
}
}
// Then, use it with a simple async for loop
async function run(srcDir, options = defaultOptions) {
const { sourceFileFilter = defaultSourceFileFilter } = options;
for await (const entry of walk(srcDir)) {
if (sourceFileFilter(entry)) {
console.log(entry);
replace(entry, entry, options);
}
}
}
const defaultSourceFileFilter = function (sourceFilePath) {
return /\.(js|ts)$/.test(sourceFilePath) && !/node_modules/.test(sourceFilePath);
};
const defaultModuleFilter = function (importedModule) {
return !path.isAbsolute(importedModule) && !importedModule.startsWith("@") && !importedModule.endsWith(".js");
};
const defaultOptions = {
sourceFileFilter: defaultSourceFileFilter,
moduleFilter: defaultModuleFilter,
};
// Switch this to test on one file or directly run on a directory.
const DEBUG = false;
if (DEBUG) {
replace("./src/index.ts", "./out.ts", defaultOptions);
} else {
await run("./src/", defaultOptions);
}
+9 -12
View File
@@ -2,22 +2,20 @@
"name": "@certd/pipeline", "name": "@certd/pipeline",
"private": false, "private": false,
"version": "1.21.0", "version": "1.21.0",
"main": "./src/index.ts", "type": "module",
"module": "./src/index.ts", "main": "./dist/index.js",
"types": "./src/index.ts", "types": "./dist/index.d.ts",
"publishConfig": {
"main": "./dist/bundle.js",
"module": "./dist/bundle.mjs",
"types": "./dist/d/index.d.ts"
},
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "rollup -c", "build": "tsc --skipLibCheck",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build", "build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^1.4.0", "@types/lodash-es": "^4.17.12",
"axios": "^1.7.2",
"lodash-es": "^4.17.21",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"qs": "^6.11.2" "qs": "^6.11.2"
@@ -30,7 +28,6 @@
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.5", "@types/chai": "^4.3.5",
"@types/lodash": "^4.14.194",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node-forge": "^1.3.2", "@types/node-forge": "^1.3.2",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
@@ -43,7 +40,6 @@
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"lodash": "^4.17.21",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
@@ -52,6 +48,7 @@
"rollup-plugin-typescript2": "^0.34.1", "rollup-plugin-typescript2": "^0.34.1",
"rollup-plugin-visualizer": "^5.8.2", "rollup-plugin-visualizer": "^5.8.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsc-esm-fix": "^3.0.0",
"tslib": "^2.5.2", "tslib": "^2.5.2",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.3.8", "vite": "^4.3.8",
+1 -1
View File
@@ -28,7 +28,7 @@ module.exports = {
], ],
external: [ external: [
"vue", "vue",
"lodash", "lodash-es",
"dayjs", "dayjs",
"@certd/acme-client", "@certd/acme-client",
"@certd/pipeline", "@certd/pipeline",
+2 -2
View File
@@ -1,5 +1,5 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry/index.js";
import { FormItemProps } from "../d.ts"; import { FormItemProps } from "../dt/index.js";
export type AccessInputDefine = FormItemProps & { export type AccessInputDefine = FormItemProps & {
title: string; title: string;
@@ -1,8 +1,8 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import { AccessDefine, AccessInputDefine } from "./api"; import { AccessDefine, AccessInputDefine } from "./api.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { accessRegistry } from "./registry"; import { accessRegistry } from "./registry.js";
// 提供一个唯一 key // 提供一个唯一 key
export const ACCESS_CLASS_KEY = "pipeline:access"; export const ACCESS_CLASS_KEY = "pipeline:access";
+3 -3
View File
@@ -1,3 +1,3 @@
export * from "./api"; export * from "./api.js";
export * from "./registry"; export * from "./registry.js";
export * from "./decorator"; export * from "./decorator.js";
@@ -1,4 +1,4 @@
import { Registry } from "../registry"; import { Registry } from "../registry/index.js";
// @ts-ignore // @ts-ignore
export const accessRegistry = new Registry("access"); export const accessRegistry = new Registry("access");
+1 -1
View File
@@ -1,5 +1,5 @@
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { IContext } from "../core"; import { IContext } from "../core/index.js";
export type HttpClient = AxiosInstance; export type HttpClient = AxiosInstance;
export type UserContext = IContext; export type UserContext = IContext;
+1 -1
View File
@@ -1,4 +1,4 @@
import { IStorage, MemoryStorage } from "./storage"; import { IStorage, MemoryStorage } from "./storage.js";
const CONTEXT_VERSION_KEY = "contextVersion"; const CONTEXT_VERSION_KEY = "contextVersion";
export interface IContext { export interface IContext {
getInt(key: string): Promise<number>; getInt(key: string): Promise<number>;
+31 -24
View File
@@ -1,18 +1,18 @@
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts"; import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { RunHistory, RunnableCollection } from "./run-history"; import { RunHistory, RunnableCollection } from "./run-history.js";
import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext } from "../plugin"; import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext } from "../plugin/index.js";
import { ContextFactory, IContext } from "./context"; import { ContextFactory, IContext } from "./context.js";
import { IStorage } from "./storage"; import { IStorage } from "./storage.js";
import { logger } from "../utils/util.log"; import { logger } from "../utils/util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { createAxiosService } from "../utils/util.request"; import { createAxiosService } from "../utils/util.request.js";
import { IAccessService } from "../access"; import { IAccessService } from "../access/index.js";
import { RegistryItem } from "../registry"; import { RegistryItem } from "../registry/index.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import { IEmailService } from "../service"; import { IEmailService } from "../service/index.js";
import { FileStore } from "./file-store"; import { FileStore } from "./file-store.js";
import { TimeoutPromise } from "../utils/util.promise"; // import { TimeoutPromise } from "../utils/util.promise.js";
export type ExecutorOptions = { export type ExecutorOptions = {
userId: any; userId: any;
@@ -33,7 +33,7 @@ export class Executor {
lastRuntime!: RunHistory; lastRuntime!: RunHistory;
options: ExecutorOptions; options: ExecutorOptions;
canceled = false; canceled = false;
onChanged: (history: RunHistory) => void; onChanged: (history: RunHistory) => Promise<void>;
constructor(options: ExecutorOptions) { constructor(options: ExecutorOptions) {
this.options = options; this.options = options;
this.pipeline = _.cloneDeep(options.pipeline); this.pipeline = _.cloneDeep(options.pipeline);
@@ -110,12 +110,13 @@ export class Executor {
const intervalFlushLogId = setInterval(async () => { const intervalFlushLogId = setInterval(async () => {
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
}, 5000); }, 5000);
const timeout = runnable.timeout ?? 20 * 60 * 1000;
// const timeout = runnable.timeout ?? 20 * 60 * 1000;
try { try {
if (this.canceled) { if (this.canceled) {
throw new Error("task canceled"); return ResultType.canceled;
} }
await TimeoutPromise(run, timeout); await run();
this.runtime.success(runnable); this.runtime.success(runnable);
return ResultType.success; return ResultType.success;
} catch (e: any) { } catch (e: any) {
@@ -142,19 +143,25 @@ export class Executor {
async runStage(stage: Stage) { async runStage(stage: Stage) {
const runnerList = []; const runnerList = [];
for (const task of stage.tasks) { for (const task of stage.tasks) {
const runner = this.runWithHistory(task, "task", async () => { const runner = async () => {
await this.runTask(task); return this.runWithHistory(task, "task", async () => {
}); await this.runTask(task);
});
};
runnerList.push(runner); runnerList.push(runner);
} }
let resList: ResultType[] = []; let resList: ResultType[] = [];
if (stage.concurrency === ConcurrencyStrategy.Parallel) { if (stage.concurrency === ConcurrencyStrategy.Parallel) {
resList = await Promise.all(runnerList); const pList = [];
for (const item of runnerList) {
pList.push(item());
}
resList = await Promise.all(pList);
} else { } else {
for (let i = 0; i < runnerList.length; i++) { for (let i = 0; i < runnerList.length; i++) {
const runner = runnerList[i]; const runner = runnerList[i];
resList[i] = await runner; resList[i] = await runner();
} }
} }
return this.compositionResultType(resList); return this.compositionResultType(resList);
@@ -239,7 +246,7 @@ export class Executor {
this.lastStatusMap.clear(); this.lastStatusMap.clear();
} }
//输出到output context //输出到output context
_.forEach(define.output, (item, key) => { _.forEach(define.output, (item: any, key: any) => {
step.status!.output[key] = instance[key]; step.status!.output[key] = instance[key];
const stepOutputKey = `step.${step.id}.${key}`; const stepOutputKey = `step.${step.id}.${key}`;
this.runtime.context[stepOutputKey] = instance[key]; this.runtime.context[stepOutputKey] = instance[key];
@@ -1,8 +1,8 @@
import { fileUtils } from "../utils"; import { fileUtils } from "../utils/index.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export type FileStoreOptions = { export type FileStoreOptions = {
rootDir?: string; rootDir?: string;
+5 -5
View File
@@ -1,5 +1,5 @@
export * from "./executor"; export * from "./executor.js";
export * from "./run-history"; export * from "./run-history.js";
export * from "./context"; export * from "./context.js";
export * from "./storage"; export * from "./storage.js";
export * from "./file-store"; export * from "./file-store.js";
@@ -1,6 +1,6 @@
import { Context, HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../d.ts"; import { Context, HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { buildLogger } from "../utils/util.log"; import { buildLogger } from "../utils/util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
export type HistoryStatus = { export type HistoryStatus = {
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { fileUtils } from "../utils/util.file"; import { fileUtils } from "../utils/util.file.js";
export interface IStorage { export interface IStorage {
get(scope: string, namespace: string, version: string, key: string): Promise<string | null>; get(scope: string, namespace: string, version: string, key: string): Promise<string | null>;
@@ -70,6 +70,7 @@ export type Runnable = {
default?: { default?: {
[key: string]: any; [key: string]: any;
}; };
context?: Context;
}; };
export type EmailOptions = { export type EmailOptions = {
@@ -1,4 +1,4 @@
import { Decorator } from "./index"; import { Decorator } from "./index.js";
export type AutowireProp = { export type AutowireProp = {
name?: string; name?: string;
@@ -1,2 +1,2 @@
export * from "./utils"; export * from "./utils.js";
export * from "./common"; export * from "./common.js";
@@ -1,4 +1,4 @@
import _ from "lodash"; import _ from "lodash-es";
const propertyMap: any = {}; const propertyMap: any = {};
function attachProperty(target: any, propertyKey: string | symbol) { function attachProperty(target: any, propertyKey: string | symbol) {
@@ -25,7 +25,7 @@ function target(target: any, propertyKey?: string | symbol) {
} }
function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) { function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) {
_.forEach(define, (item, key) => { _.forEach(define, (item: any, key: any) => {
if (preHandler) { if (preHandler) {
preHandler(item, key, instance, context); preHandler(item, key, instance, context);
} }
+115
View File
@@ -0,0 +1,115 @@
/**
* [x]-col的配置
*/
export type ColProps = {
span?: number;
[props: string]: any;
};
export type FormItemProps = {
/**
* 字段label
*/
title?: string;
/**
* 表单字段组件配置
*/
component?: ComponentProps;
/**
* 表单字段 [a|el|n]-col的配置
* 一般用来配置跨列:{span:24} 占满一行
*/
col?: ColProps;
/**
* 默认值
*/
value?: any;
/**
* 帮助提示配置
*/
helper?: string | FormItemHelperProps;
/**
* 排序号
*/
order?: number;
/**
* 是否显示此字段
*/
show?: boolean;
/**
* 是否是空白占位栏
*/
blank?: boolean;
[key: string]: any;
};
/**
* 表单字段帮助说明配置
*/
export type FormItemHelperProps = {
/**
* 自定义渲染帮助说明
* @param scope
*/
render?: (scope: any) => any;
/**
* 帮助文本
*/
text?: string;
/**
* 帮助说明所在的位置,[ undefined | label]
*/
position?: string;
/**
* [a|el|n]-tooltip配置
*/
tooltip?: object;
[key: string]: any;
};
/**
* 组件配置
*/
export type ComponentProps = {
/**
* 组件的名称
*/
name?: string | object;
/**
* vmodel绑定的目标属性名
*/
vModel?: string;
/**
* 当原始组件名的参数被以上属性名占用时,可以配置在这里
* 例如:原始组件有一个叫name的属性,你想要配置它,则可以按如下配置
* ```
* component:{
* name:"组件的名称"
* props:{
* name:"组件的name属性" <-----------
* }
* }
* ```
*/
props?: {
[key: string]: any;
};
/**
* 组件事件监听
*/
on?: {
[key: string]: (context?: any) => void;
};
/**
* 组件其他参数
* 事件:onXxx:(event)=>void 组件原始事件监听
* on.onXxx:(context)=>void 组件事件监听(对原始事件包装)
* 样式:style、class等
*/
[key: string]: any;
};
+2
View File
@@ -0,0 +1,2 @@
export * from "./pipeline.js";
export * from "./fast-crud.js";
+139
View File
@@ -0,0 +1,139 @@
export enum RunStrategy {
AlwaysRun,
SkipWhenSucceed,
}
export enum ConcurrencyStrategy {
Serial,
Parallel,
}
export enum NextStrategy {
AllSuccess,
OneSuccess,
}
export enum HandlerType {
//清空后续任务的状态
ClearFollowStatus,
SendEmail,
}
export type EventHandler = {
type: HandlerType;
params: {
[key: string]: any;
};
};
export type RunnableStrategy = {
runStrategy?: RunStrategy;
onSuccess?: EventHandler[];
onError?: EventHandler[];
};
export type Step = Runnable & {
type: string; //插件类型
input: {
[key: string]: any;
};
};
export type Task = Runnable & {
steps: Step[];
};
export type Stage = Runnable & {
tasks: Task[];
concurrency: ConcurrencyStrategy;
next: NextStrategy;
};
export type Trigger = {
id: string;
title: string;
cron: string;
type: string;
};
export type FileItem = {
id: string;
filename: string;
path: string;
};
export type Runnable = {
id: string;
title: string;
strategy?: RunnableStrategy;
runnableType?: string; // pipeline, stage, task , step
status?: HistoryResult;
timeout?: number;
default?: {
[key: string]: any;
};
};
export type EmailOptions = {
receivers: string[];
};
export type NotificationWhen = "error" | "success" | "turnToSuccess" | "start";
export type NotificationType = "email" | "url";
export type Notification = {
type: NotificationType;
when: NotificationWhen[];
options: EmailOptions;
};
export type Pipeline = Runnable & {
version?: number;
userId: any;
stages: Stage[];
triggers: Trigger[];
notifications?: Notification[];
};
export type Context = {
[key: string]: any;
};
export type Log = {
title: string;
time: number;
level: string;
text: string;
};
export enum ResultType {
start = "start",
success = "success",
error = "error",
canceled = "canceled",
skip = "skip",
none = "none",
}
export type HistoryResultGroup = {
[key: string]: {
runnable: Runnable;
res: HistoryResult;
};
};
export type HistoryResult = {
input: any;
output: any;
files?: FileItem[];
/**
* 任务状态
*/
status: ResultType;
startTime: number;
endTime?: number;
/**
* 处理结果
*/
result?: ResultType; //success, error,skip
message?: string;
};
export type RunnableMap = {
[id: string]: Runnable;
};
+9 -9
View File
@@ -1,10 +1,10 @@
import "util"; import "util";
export * from "./core"; export * from "./core/index.js";
export * from "./d.ts"; export * from "./dt/index.js";
export * from "./access"; export * from "./access/index.js";
export * from "./registry"; export * from "./registry/index.js";
export * from "./plugin"; export * from "./plugin/index.js";
export * from "./utils"; export * from "./utils/index.js";
export * from "./context"; export * from "./context/index.js";
export * from "./decorator"; export * from "./decorator/index.js";
export * from "./service"; export * from "./service/index.js";
+16 -9
View File
@@ -1,12 +1,12 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../d.ts"; import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store"; import { FileStore } from "../core/file-store.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { IAccessService } from "../access"; import { IAccessService } from "../access/index.js";
import { IEmailService } from "../service"; import { IEmailService } from "../service/index.js";
import { IContext } from "../core"; import { IContext } from "../core/index.js";
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export enum ContextScope { export enum ContextScope {
global, global,
@@ -83,7 +83,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
return Math.random().toString(36).substring(2, 9); return Math.random().toString(36).substring(2, 9);
} }
linkFile(file: FileItem) { linkFile(file: FileItem) {
this._result.files!.push({ this._result.files?.push({
...file, ...file,
id: this.randomFileId(), id: this.randomFileId(),
}); });
@@ -91,13 +91,20 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
saveFile(filename: string, file: Buffer) { saveFile(filename: string, file: Buffer) {
const filePath = this.ctx.fileStore.writeFile(filename, file); const filePath = this.ctx.fileStore.writeFile(filename, file);
logger.info(`saveFile:${filePath}`); logger.info(`saveFile:${filePath}`);
this._result.files!.push({ this._result.files?.push({
id: this.randomFileId(), id: this.randomFileId(),
filename, filename,
path: filePath, path: filePath,
}); });
} }
extendsFiles() {
if (this._result.files == null) {
this._result.files = [];
}
this._result.files.push(...(this.ctx.lastStatus?.status?.files || []));
}
get pipeline() { get pipeline() {
return this.ctx.pipeline; return this.ctx.pipeline;
} }
@@ -1,8 +1,8 @@
import _ from "lodash"; import _ from "lodash-es";
import { pluginRegistry } from "./registry"; import { pluginRegistry } from "./registry.js";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api"; import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import { AUTOWIRE_KEY } from "../decorator"; import { AUTOWIRE_KEY } from "../decorator/index.js";
import "reflect-metadata"; import "reflect-metadata";
// 提供一个唯一 key // 提供一个唯一 key
export const PLUGIN_CLASS_KEY = "pipeline:plugin"; export const PLUGIN_CLASS_KEY = "pipeline:plugin";
+3 -3
View File
@@ -1,3 +1,3 @@
export * from "./api"; export * from "./api.js";
export * from "./registry"; export * from "./registry.js";
export * from "./decorator"; export * from "./decorator.js";
@@ -1,4 +1,4 @@
import { Registry } from "../registry"; import { Registry } from "../registry/index.js";
import { AbstractTaskPlugin } from "./api"; import { AbstractTaskPlugin } from "./api.js";
export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin"); export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin");
@@ -1,6 +1,5 @@
import { ITaskPlugin } from "../api"; import { ITaskPlugin } from "../api.js";
import { IsTaskPlugin, TaskInput } from "../decorator"; import { IsTaskPlugin, TaskInput } from "../decorator.js";
import { Autowire } from "../../decorator";
@IsTaskPlugin({ @IsTaskPlugin({
name: "EchoPlugin", name: "EchoPlugin",
+1 -1
View File
@@ -1 +1 @@
export * from "./registry"; export * from "./registry.js";
@@ -1,4 +1,4 @@
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export type Registrable = { export type Registrable = {
name: string; name: string;
+1 -1
View File
@@ -1 +1 @@
export * from "./email"; export * from "./email.js";
+4 -4
View File
@@ -1,7 +1,7 @@
import sleep from "./util.sleep"; import sleep from "./util.sleep.js";
import { request } from "./util.request"; import { request } from "./util.request.js";
export * from "./util.log"; export * from "./util.log.js";
export * from "./util.file"; export * from "./util.file.js";
export const utils = { export const utils = {
sleep, sleep,
http: request, http: request,
@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
// @ts-ignore // @ts-ignore
import qs from "qs"; import qs from "qs";
import { logger } from "./util.log"; import { logger } from "./util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
/** /**
* @description 创建请求实例 * @description 创建请求实例
+33 -15
View File
@@ -1,23 +1,41 @@
{ {
"compileOnSave": true,
"compilerOptions": { "compilerOptions": {
"importHelpers": false,
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"paths": { "emitDecoratorMetadata": true,
"tslib" : ["./node_modules/tslib/tslib.d.ts"] "inlineSourceMap":true,
} "noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [ "./typings", "./node_modules/@types"],
"outDir": "dist",
"rootDir": "src",
"composite": true,
"useDefineForClassFields": true,
"strict": true,
// "sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": false,
"lib": ["ESNext", "DOM"],
}, },
"include": [
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","test/**/*.ts","rollup.config.ts"], "src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.json"
],
"exclude": [
"*.js",
"*.ts",
"dist",
"node_modules",
"test"
],
} }
+1 -1
View File
@@ -24,7 +24,7 @@ export default defineConfig({
], ],
external: [ external: [
"vue", "vue",
"lodash", "lodash-es",
"dayjs", "dayjs",
"@certd/acme-client", "@certd/acme-client",
"@certd/plugin-cert", "@certd/plugin-cert",
+28
View File
@@ -0,0 +1,28 @@
# 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
rollup.cache
@@ -0,0 +1,96 @@
import * as fs from "fs";
import * as path from "path";
// https://gist.github.com/lovasoa/8691344
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) {
yield* walk(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
function resolveImportPath(sourceFile, importPath, options) {
const sourceFileAbs = path.resolve(process.cwd(), sourceFile);
const root = path.dirname(sourceFileAbs);
const { moduleFilter = defaultModuleFilter } = options;
if (moduleFilter(importPath)) {
const importPathAbs = path.resolve(root, importPath);
let possiblePath = [path.resolve(importPathAbs, "./index.ts"), path.resolve(importPathAbs, "./index.js"), importPathAbs + ".ts", importPathAbs + ".js"];
if (possiblePath.length) {
for (let i = 0; i < possiblePath.length; i++) {
let entry = possiblePath[i];
if (fs.existsSync(entry)) {
const resolved = path.relative(root, entry.replace(/\.ts$/, ".js"));
if (!resolved.startsWith(".")) {
return "./" + resolved;
}
return resolved;
}
}
}
}
return null;
}
function replace(filePath, outFilePath, options) {
const code = fs.readFileSync(filePath).toString();
const newCode = code.replace(/(import|export) (.+?) from ('[^\n']+'|"[^\n"]+");/gs, function (found, action, imported, from) {
const importPath = from.slice(1, -1);
let resolvedPath = resolveImportPath(filePath, importPath, options);
if (resolvedPath) {
resolvedPath = resolvedPath.replaceAll("\\", "/");
console.log("\t", importPath, resolvedPath);
return `${action} ${imported} from "${resolvedPath}";`;
}
return found;
});
if (code !== newCode) {
fs.writeFileSync(outFilePath, newCode);
}
}
// Then, use it with a simple async for loop
async function run(srcDir, options = defaultOptions) {
const { sourceFileFilter = defaultSourceFileFilter } = options;
for await (const entry of walk(srcDir)) {
if (sourceFileFilter(entry)) {
console.log(entry);
replace(entry, entry, options);
}
}
}
const defaultSourceFileFilter = function (sourceFilePath) {
return /\.(js|ts)$/.test(sourceFilePath) && !/node_modules/.test(sourceFilePath);
};
const defaultModuleFilter = function (importedModule) {
return !path.isAbsolute(importedModule) && !importedModule.startsWith("@") && !importedModule.endsWith(".js");
};
const defaultOptions = {
sourceFileFilter: defaultSourceFileFilter,
moduleFilter: defaultModuleFilter,
};
// Switch this to test on one file or directly run on a directory.
const DEBUG = false;
if (DEBUG) {
replace("./src/index.ts", "./out.ts", defaultOptions);
} else {
await run("./src/", defaultOptions);
}
+20
View File
@@ -0,0 +1,20 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.21.0",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
"scripts": {
"dev": "vite",
"build": "rollup -c ",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@certd/pipeline": "1.21.0",
"axios": "^1.7.2",
"rollup": "^3.7.4"
},
"gitHead": "a31f1c7f5e71fa946de9bf0283e11d6ce049b3e9"
}
@@ -32,22 +32,5 @@ module.exports = {
json(), json(),
terser(), terser(),
], ],
external: [ external: ["vue", "lodash-es", "dayjs", "log4js", "@midwayjs/core", "@certd/pipeline", "axios"],
"vue",
"lodash",
"dayjs",
"@certd/acme-client",
"@certd/pipeline",
"@certd/plugin-cert",
"@certd/plugin-aliyun",
"@certd/plugin-tencent",
"@certd/plugin-huawei",
"@certd/plugin-host",
"@certd/plugin-tencent",
"@certd/plugin-util",
"log4js",
"@midwayjs/core",
"@midwayjs/decorator",
"kubernetes-client",
],
}; };
+2
View File
@@ -0,0 +1,2 @@
export { HuaweiYunClient } from "./lib/client.js";
export { ApiRequestOptions } from "./lib/client.js";
+12
View File
@@ -0,0 +1,12 @@
import { HuaweiAccess } from "../access/index.js";
export type ApiRequestOptions = {
method: string;
url: string;
headers?: any;
data?: any;
};
export declare class HuaweiYunClient {
access: HuaweiAccess;
constructor(access: HuaweiAccess);
request(options: ApiRequestOptions): Promise<any>;
}
+41
View File
@@ -0,0 +1,41 @@
import { Signer, SigHttpRequest } from "./signer.js";
import axios from "axios";
export class HuaweiYunClient {
access;
constructor(access, logger) {
this.access = access;
}
async request(options) {
const sig = new Signer(this.access.accessKeyId, this.access.accessKeySecret);
//The following example shows how to set the request URL and parameters to query a VPC list.
//Specify a request method, such as GET, PUT, POST, DELETE, HEAD, and PATCH.
//Set request host.
//Set request URI.
//Set parameters for the request URL.
let body = undefined;
if (options.data) {
body = JSON.stringify(options.data);
}
const r = new SigHttpRequest(options.method, options.url, options.headers, body);
//Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for invoking a project-level service.
r.headers = { "Content-Type": "application/json" };
//Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation mark ("), contained in the body must be escaped.
// r.body = option;
const opt = sig.Sign(r);
try {
const res = await axios.request({
url: options.url,
method: options.method,
headers: opt.headers,
data: body,
});
return res.data;
} catch (e) {
this.logger.error("华为云接口请求出错:", e?.response?.data);
const error = new Error(e?.response?.data.message);
error.code = e?.response?.code;
throw error;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3BsdWdpbnMvcGx1Z2luLWh1YXdlaS9saWIvY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsY0FBYyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRXJELE9BQU8sS0FBSyxNQUFNLE9BQU8sQ0FBQztBQUMxQixPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFRekMsTUFBTSxPQUFPLGVBQWU7SUFDMUIsTUFBTSxDQUFlO0lBQ3JCLFlBQVksTUFBb0I7UUFDOUIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDdkIsQ0FBQztJQUNELEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBMEI7UUFDdEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQ3BCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxFQUN2QixJQUFJLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FDNUIsQ0FBQztRQUVGLDRGQUE0RjtRQUM1Riw0RUFBNEU7UUFDNUUsbUJBQW1CO1FBQ25CLGtCQUFrQjtRQUNsQixxQ0FBcUM7UUFDckMsSUFBSSxJQUFJLEdBQUcsU0FBUyxDQUFDO1FBQ3JCLElBQUksT0FBTyxDQUFDLElBQUksRUFBRTtZQUNoQixJQUFJLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDckM7UUFDRCxNQUFNLENBQUMsR0FBRyxJQUFJLGNBQWMsQ0FDMUIsT0FBTyxDQUFDLE1BQU0sRUFDZCxPQUFPLENBQUMsR0FBRyxFQUNYLE9BQU8sQ0FBQyxPQUFPLEVBQ2YsSUFBSSxDQUNMLENBQUM7UUFDRixzSUFBc0k7UUFDdEksQ0FBQyxDQUFDLE9BQU8sR0FBRyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRSxDQUFDO1FBQ25ELDRKQUE0SjtRQUM1SixtQkFBbUI7UUFDbkIsTUFBTSxHQUFHLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN4QixJQUFJO1lBQ0YsTUFBTSxHQUFHLEdBQUcsTUFBTSxLQUFLLENBQUMsT0FBTyxDQUFDO2dCQUM5QixHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUc7Z0JBQ2hCLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTTtnQkFDdEIsT0FBTyxFQUFFLEdBQUcsQ0FBQyxPQUFPO2dCQUNwQixJQUFJLEVBQUUsSUFBSTthQUNYLENBQUMsQ0FBQztZQUNILE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQztTQUNqQjtRQUFDLE9BQU8sQ0FBTSxFQUFFO1lBQ2YsTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUM5QyxNQUFNLEtBQUssR0FBUSxJQUFJLEtBQUssQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUN4RCxLQUFLLENBQUMsSUFBSSxHQUFHLENBQUMsRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDO1lBQy9CLE1BQU0sS0FBSyxDQUFDO1NBQ2I7SUFDSCxDQUFDO0NBQ0YifQ==
+20
View File
@@ -0,0 +1,20 @@
export declare class SigHttpRequest {
method: string;
host: string;
uri: string;
query: any;
headers: any;
body: string;
constructor(method: any, url: any, headers: any, body: any);
}
export declare class Signer {
Key: string;
Secret: string;
constructor(Key: any, Secret: any);
Sign(r: any): {
hostname: any;
path: string;
method: any;
headers: any;
};
}
File diff suppressed because one or more lines are too long
+41
View File
@@ -0,0 +1,41 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [ "./typings", "./node_modules/@types"],
"outDir": "dist",
"rootDir": "src",
"composite": true,
"useDefineForClassFields": true,
"strict": false,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": false,
"lib": ["ESNext", "DOM"],
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.js",
"src/**/*.json"
],
"exclude": [
"*.ts",
"dist",
"node_modules",
"test"
],
}
+23
View File
@@ -0,0 +1,23 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}
+2
View File
@@ -0,0 +1,2 @@
node_modules
src
+7
View File
@@ -0,0 +1,7 @@
{
"printWidth": 160,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}
+128
View File
@@ -0,0 +1,128 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.21.0](https://github.com/certd/certd/compare/v1.20.17...v1.21.0) (2024-07-03)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.17](https://github.com/certd/certd/compare/v1.20.16...v1.20.17) (2024-07-03)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.12](https://github.com/certd/certd/compare/v1.20.10...v1.20.12) (2024-06-17)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.10](https://github.com/certd/certd/compare/v1.20.9...v1.20.10) (2024-05-30)
### Performance Improvements
* 优化文件下载包名 ([d9eb927](https://github.com/certd/certd/commit/d9eb927b0a1445feab08b1958aa9ea80637a5ae6))
## [1.20.9](https://github.com/certd/certd/compare/v1.20.8...v1.20.9) (2024-03-22)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.8](https://github.com/certd/certd/compare/v1.20.7...v1.20.8) (2024-03-22)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.7](https://github.com/certd/certd/compare/v1.20.6...v1.20.7) (2024-03-22)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.6](https://github.com/certd/certd/compare/v1.20.5...v1.20.6) (2024-03-21)
### Performance Improvements
* 插件贡献文档及示例 ([72fb20a](https://github.com/certd/certd/commit/72fb20abf3ba5bdd862575d2907703a52fd7eb17))
## [1.20.5](https://github.com/certd/certd/compare/v1.20.2...v1.20.5) (2024-03-11)
**Note:** Version bump only for package @certd/plugin-util
## [1.20.2](https://github.com/certd/certd/compare/v1.2.1...v1.20.2) (2024-02-28)
**Note:** Version bump only for package @certd/plugin-util
## [1.2.1](https://github.com/certd/certd/compare/v1.2.0...v1.2.1) (2023-12-12)
**Note:** Version bump only for package @certd/plugin-util
**Note:** Version bump only for package @certd/plugin-util
# [1.2.0](https://github.com/certd/certd/compare/v1.1.6...v1.2.0) (2023-10-27)
**Note:** Version bump only for package @certd/plugin-util
## [1.1.6](https://github.com/certd/certd/compare/v1.1.5...v1.1.6) (2023-07-10)
**Note:** Version bump only for package @certd/plugin-util
## [1.1.5](https://github.com/certd/certd/compare/v1.1.4...v1.1.5) (2023-07-03)
**Note:** Version bump only for package @certd/plugin-util
## [1.1.4](https://github.com/certd/certd/compare/v1.1.3...v1.1.4) (2023-07-03)
### Performance Improvements
* timeout ([3eeb1f7](https://github.com/certd/certd/commit/3eeb1f77aa2922f3545f3d2067f561d95621d54f))
## [1.1.3](https://github.com/certd/certd/compare/v1.1.2...v1.1.3) (2023-07-03)
**Note:** Version bump only for package @certd/plugin-util
## [1.1.2](https://github.com/certd/certd/compare/v1.1.1...v1.1.2) (2023-07-03)
**Note:** Version bump only for package @certd/plugin-util
## [1.1.1](https://github.com/certd/certd/compare/v1.1.0...v1.1.1) (2023-06-28)
**Note:** Version bump only for package @certd/plugin-util
# [1.1.0](https://github.com/certd/certd/compare/v1.0.6...v1.1.0) (2023-06-28)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.6](https://github.com/certd/certd/compare/v1.0.5...v1.0.6) (2023-05-25)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.5](https://github.com/certd/certd/compare/v1.0.4...v1.0.5) (2023-05-25)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.4](https://github.com/certd/certd/compare/v1.0.3...v1.0.4) (2023-05-25)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.3](https://github.com/certd/certd/compare/v1.0.2...v1.0.3) (2023-05-25)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.2](https://github.com/certd/certd/compare/v1.0.1...v1.0.2) (2023-05-24)
**Note:** Version bump only for package @certd/plugin-util
## [1.0.1](https://github.com/certd/certd/compare/v1.0.0...v1.0.1) (2023-05-24)
**Note:** Version bump only for package @certd/plugin-util
+16
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).
+96
View File
@@ -0,0 +1,96 @@
import * as fs from "fs";
import * as path from "path";
// https://gist.github.com/lovasoa/8691344
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) {
yield* walk(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
function resolveImportPath(sourceFile, importPath, options) {
const sourceFileAbs = path.resolve(process.cwd(), sourceFile);
const root = path.dirname(sourceFileAbs);
const { moduleFilter = defaultModuleFilter } = options;
if (moduleFilter(importPath)) {
const importPathAbs = path.resolve(root, importPath);
let possiblePath = [path.resolve(importPathAbs, "./index.ts"), path.resolve(importPathAbs, "./index.js"), importPathAbs + ".ts", importPathAbs + ".js"];
if (possiblePath.length) {
for (let i = 0; i < possiblePath.length; i++) {
let entry = possiblePath[i];
if (fs.existsSync(entry)) {
const resolved = path.relative(root, entry.replace(/\.ts$/, ".js"));
if (!resolved.startsWith(".")) {
return "./" + resolved;
}
return resolved;
}
}
}
}
return null;
}
function replace(filePath, outFilePath, options) {
const code = fs.readFileSync(filePath).toString();
const newCode = code.replace(/(import|export) (.+?) from ('[^\n']+'|"[^\n"]+");/gs, function (found, action, imported, from) {
const importPath = from.slice(1, -1);
let resolvedPath = resolveImportPath(filePath, importPath, options);
if (resolvedPath) {
resolvedPath = resolvedPath.replaceAll("\\", "/");
console.log("\t", importPath, resolvedPath);
return `${action} ${imported} from "${resolvedPath}";`;
}
return found;
});
if (code !== newCode) {
fs.writeFileSync(outFilePath, newCode);
}
}
// Then, use it with a simple async for loop
async function run(srcDir, options = defaultOptions) {
const { sourceFileFilter = defaultSourceFileFilter } = options;
for await (const entry of walk(srcDir)) {
if (sourceFileFilter(entry)) {
console.log(entry);
replace(entry, entry, options);
}
}
}
const defaultSourceFileFilter = function (sourceFilePath) {
return /\.(js|ts)$/.test(sourceFilePath) && !/node_modules/.test(sourceFilePath);
};
const defaultModuleFilter = function (importedModule) {
return !path.isAbsolute(importedModule) && !importedModule.startsWith("@") && !importedModule.endsWith(".js");
};
const defaultOptions = {
sourceFileFilter: defaultSourceFileFilter,
moduleFilter: defaultModuleFilter,
};
// Switch this to test on one file or directly run on a directory.
const DEBUG = false;
if (DEBUG) {
replace("./src/index.ts", "./out.ts", defaultOptions);
} else {
await run("./src/", defaultOptions);
}
@@ -1,24 +1,20 @@
{ {
"name": "@certd/plugin-util", "name": "@certd/lib-k8s",
"private": false, "private": false,
"version": "1.21.0", "version": "1.21.0",
"main": "./src/index.ts", "type": "module",
"module": "./src/index.ts", "main": "./dist/index.js",
"types": "./src/index.ts", "types": "./dist/index.d.ts",
"publishConfig": {
"main": "./dist/bundle.js",
"module": "./dist/bundle.mjs",
"types": "./dist/d/index.d.ts"
},
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "rollup -c", "build": "tsc --skipLibCheck",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build", "build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"kubernetes-client": "^9.0.0", "dns": "^0.2.2",
"shelljs": "^0.8.5" "kubernetes-client": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@certd/pipeline": "workspace:^1.21.0", "@certd/pipeline": "workspace:^1.21.0",
@@ -28,7 +24,6 @@
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
"@types/lodash": "^4.14.186",
"@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1", "@typescript-eslint/parser": "^5.38.1",
"eslint": "^8.24.0", "eslint": "^8.24.0",
+1
View File
@@ -0,0 +1 @@
export * from "./lib/k8s.client.js";
@@ -1,6 +1,6 @@
import kubernetesClient from "kubernetes-client"; import kubernetesClient from 'kubernetes-client';
import dns from "dns"; import dns from 'dns';
import { logger } from "@certd/pipeline"; import { logger } from '@certd/pipeline';
// @ts-ignore // @ts-ignore
const { KubeConfig, Client, Request } = kubernetesClient; const { KubeConfig, Client, Request } = kubernetesClient;
@@ -23,7 +23,7 @@ export class K8sClient {
} }
const backend = new Request(reqOpts); const backend = new Request(reqOpts);
this.client = new Client({ backend, version: "1.13" }); this.client = new Client({ backend, version: '1.13' });
} }
/** /**
@@ -32,9 +32,9 @@ export class K8sClient {
*/ */
setLookup(localRecords: { [key: string]: { ip: string } }) { setLookup(localRecords: { [key: string]: { ip: string } }) {
this.lookup = (hostnameReq: any, options: any, callback: any) => { this.lookup = (hostnameReq: any, options: any, callback: any) => {
logger.info("custom lookup", hostnameReq, localRecords); logger.info('custom lookup', hostnameReq, localRecords);
if (localRecords[hostnameReq]) { if (localRecords[hostnameReq]) {
logger.info("local record", hostnameReq, localRecords[hostnameReq]); logger.info('local record', hostnameReq, localRecords[hostnameReq]);
callback(null, localRecords[hostnameReq].ip, 4); callback(null, localRecords[hostnameReq].ip, 4);
} else { } else {
dns.lookup(hostnameReq, options, callback); dns.lookup(hostnameReq, options, callback);
@@ -49,7 +49,7 @@ export class K8sClient {
* @returns secretsList * @returns secretsList
*/ */
async getSecret(opts: { namespace: string }) { async getSecret(opts: { namespace: string }) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
return await this.client.api.v1.namespaces(namespace).secrets.get(); return await this.client.api.v1.namespaces(namespace).secrets.get();
} }
@@ -59,19 +59,19 @@ export class K8sClient {
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
async createSecret(opts: any) { async createSecret(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
const created = await this.client.api.v1.namespaces(namespace).secrets.post({ const created = await this.client.api.v1.namespaces(namespace).secrets.post({
body: opts.body, body: opts.body,
}); });
logger.info("new secrets:", created); logger.info('new secrets:', created);
return created; return created;
} }
async updateSecret(opts: any) { async updateSecret(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
const secretName = opts.secretName; const secretName = opts.secretName;
if (secretName == null) { if (secretName == null) {
throw new Error("secretName 不能为空"); throw new Error('secretName 不能为空');
} }
return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({ return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({
body: opts.body, body: opts.body,
@@ -79,10 +79,10 @@ export class K8sClient {
} }
async patchSecret(opts: any) { async patchSecret(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
const secretName = opts.secretName; const secretName = opts.secretName;
if (secretName == null) { if (secretName == null) {
throw new Error("secretName 不能为空"); throw new Error('secretName 不能为空');
} }
return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({ return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({
body: opts.body, body: opts.body,
@@ -90,24 +90,24 @@ export class K8sClient {
} }
async getIngressList(opts: any) { async getIngressList(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get(); return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get();
} }
async getIngress(opts: any) { async getIngress(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
const ingressName = opts.ingressName; const ingressName = opts.ingressName;
if (!ingressName) { if (!ingressName) {
throw new Error("ingressName 不能为空"); throw new Error('ingressName 不能为空');
} }
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get(); return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get();
} }
async patchIngress(opts: any) { async patchIngress(opts: any) {
const namespace = opts.namespace || "default"; const namespace = opts.namespace || 'default';
const ingressName = opts.ingressName; const ingressName = opts.ingressName;
if (!ingressName) { if (!ingressName) {
throw new Error("ingressName 不能为空"); throw new Error('ingressName 不能为空');
} }
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({ return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({
body: opts.body, body: opts.body,
+41
View File
@@ -0,0 +1,41 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":true,
"noImplicitThis": true,
"noUnusedLocals": true,
"stripInternal": true,
"skipLibCheck": true,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [ "./typings", "./node_modules/@types"],
"outDir": "dist",
"rootDir": "src",
"composite": true,
"useDefineForClassFields": true,
"strict": false,
// "sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"lib": ["ESNext", "DOM"],
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.json"
],
"exclude": [
"*.js",
"*.ts",
"dist",
"node_modules",
"test"
],
}
@@ -0,0 +1,16 @@
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
!dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*
/data/db.sqlite
@@ -0,0 +1,11 @@
# 🎨 editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
@@ -0,0 +1,7 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
}
}
+28
View File
@@ -0,0 +1,28 @@
# 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
tsconfig.tsbuildinfo
@@ -0,0 +1,2 @@
node_modules
src
@@ -0,0 +1,7 @@
{
"printWidth": 160,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Greper
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+16
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).
@@ -0,0 +1,125 @@
# midway-flyway-js
[English](./README.md) | [简体中文](./README_zhCN.md)
`midway-flyway-js`是基于typeorm的flyway的js实现。
本项目被构建为midway组件,可与midway无缝集成。
# flyway
flyway是一款java版本的数据库升级迁移解决方案。
它能在server启动时自动检查脚本目录,执行sql升级脚本,记录执行历史。
本项目根据类似flyway的思路实现数据库升级迁移方案
# 快速开始
## 1. 准备
* nodejs环境
* midway项目
* [配置typeorm](https://www.yuque.com/midwayjs/midway_v2/orm)
## 2. 安装
```
npm install midway-flyway-js
# or
yarn add midway-flyway-js
```
## 3. 集成
```js
import * as orm from 'typeorm';
import * as flyway from 'midway-flyway-js';
@Configuration({
imports: [
orm, // 加载 orm 组件
flyway, //加载flyway组件
],
})
export class ContainerConfiguration {}
```
## 4. 配置参数【可选】
`/src/config/config.default.js`文件
```js
export const flyway ={
// 脚本目录
// 默认值 "./db/migrition"
scriptDir:"./db/migrition",
// 基线,基线脚本及之前的脚本都跳过不执行
// 默认值:null
// 如果你原本就是空数据库,那么不需要配置此项
baseline: 'v1__init.sql',
// 执行记录表名
// 默认值 flyway_history
flywayTableName:'flyway_history',
// 是否允许hash值不同
// 默认值:false
// 相同名称sql文件被改动后,hash会变化
// 此时运行会报hash conflict错误
// 配置此参数为true,将忽略hash conflict错误
allowHashNotMatch:false
}
```
## 5. 编写升级sql
将你的sql升级脚本,放到 `/src/db/migrition`目录下
建议命名规则`v{version}__{name}.sql`,例如`v1__init.sql`
## 6. 启动你的midway服务
```
npm run dev
```
## 7. 运行效果
以下效果为midway自动启动后,自动执行`v1__init.sql`脚本的记录
```
2021-06-26 15:45:39,630 INFO 12245 [ midfly ] start-------------
query: SELECT * FROM "sqlite_master" WHERE "type" = 'table' AND "name" = 'flyway_history'
query: CREATE TABLE "flyway_history" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "timestamp" bigint NOT NULL, "name" varchar NOT NULL, "hash" varchar, "success" boolean)
query: BEGIN TRANSACTION
query: SELECT "FlywayHistory"."id" AS "FlywayHistory_id", "FlywayHistory"."name" AS "FlywayHistory_name", "FlywayHistory"."hash" AS "FlywayHistory_hash", "FlywayHistory"."timestamp" AS "FlywayHistory_timestamp", "FlywayHistory"."success" AS "FlywayHistory_success" FROM "flyway_history" "FlywayHistory" WHERE "FlywayHistory"."name" = ? AND "FlywayHistory"."success" = ? LIMIT 1 -- PARAMETERS: ["v1__init.sql",1]
2021-06-26 15:45:39,664 INFO 12245 need exec script file:
2021-06-26 15:45:39,666 INFO 12245 [ midfly ] exec
query: -- 表:sys_permission
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
query: INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636);
query: -- 表:sys_role
CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
query: INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537);
query: -- 表:sys_role_permission
CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id"));
query: INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1);
query: -- 表:sys_user
CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
query: INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
query: -- 表:sys_user_role
CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id"));
query: INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1);
query: -- 索引:IDX_223de54d6badbe43a5490450c3
CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name");
query: -- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da
CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username");
query: DELETE FROM "flyway_history" WHERE "name" = ? -- PARAMETERS: ["v1__init.sql"]
query: INSERT INTO "flyway_history"("id", "name", "hash", "timestamp", "success") VALUES (NULL, ?, ?, ?, ?) -- PARAMETERS: ["v1__init.sql","0c661bd7afebac224bbaa60bc5bb56e9",1624693539781,1]
query: SELECT "FlywayHistory"."id" AS "FlywayHistory_id", "FlywayHistory"."success" AS "FlywayHistory_success" FROM "flyway_history" "FlywayHistory" WHERE "FlywayHistory"."id" = ? -- PARAMETERS: [1]
query: COMMIT
2021-06-26 15:45:39,800 INFO 12245 [ midfly ] end-------------
```
# 注意事项
1. 升级sql文件最后一行请不要有注释,应该以一条sql语句的分号结尾。
# 他们在用
* [fs-server-js](https://github.com/fast-crud/fs-server-js)
# 参考项目
* [flyway](https://github.com/flyway/flyway) : java版flyway
* [flyway-js](https://github.com/wanglihui/flyway-js) : Sequelize版flyway
感谢以上项目
@@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
};
@@ -0,0 +1,7 @@
{
"type": "sqlite",
"database": "./data/db.sqlite",
"synchronize": false,
"logging": true,
"entities": [ "src/**/entity.ts"]
}
@@ -0,0 +1,56 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.21.0",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc --skipLibCheck",
"test": "midway-bin test --ts -V",
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
"cov": "midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix",
"prepublish": "npm run build",
"pub": "npm publish"
},
"keywords": [],
"author": "greper",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts"
],
"license": "MIT",
"devDependencies": {
"@midwayjs/core": "^3",
"@midwayjs/logger": "^3",
"@midwayjs/typeorm": "^3",
"typeorm": "^0.3.11",
"@types/node": "16",
"cross-env": "^6.0.0",
"mwts": "^1.3.0",
"mwtsc": "^1.4.0",
"sqlite3": "^5.0.2",
"typescript": "~5.1.0",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.3",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"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",
"prettier": "^2.8.8",
"rollup": "^3.7.4",
"rollup-plugin-visualizer": "^5.8.2",
"ts-node": "^10.9.1",
"tslib": "^2.5.2"
}
}
@@ -0,0 +1,23 @@
import { Config, Configuration, Logger } from '@midwayjs/core';
import { Flyway } from './flyway.js';
import type { ILogger } from '@midwayjs/logger';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import type { IMidwayContainer } from '@midwayjs/core';
@Configuration({
namespace: 'flyway',
//importConfigs: [join(__dirname, './config')],
})
export class FlywayConfiguration {
@Config()
flyway!: any;
@Logger()
logger!: ILogger;
async onReady(container: IMidwayContainer) {
this.logger.info('flyway start:' + JSON.stringify(this.flyway));
const dataSourceManager = await container.getAsync(TypeORMDataSourceManager);
const dataSourceName = this.flyway.dataSourceName || 'default';
const connection = dataSourceManager.getDataSource(dataSourceName);
await new Flyway({ ...this.flyway, logger: this.logger, connection }).run();
}
}
@@ -0,0 +1,24 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('flyway_history')
export class FlywayHistory {
@PrimaryGeneratedColumn()
id?: number;
@Column({ comment: '文件名', length: 100 })
name?: string;
@Column({ comment: 'hash', length: 32 })
hash?: string;
@Column({
comment: '执行时间',
})
timestamp?: Date;
@Column({
comment: '执行成功',
default: true,
})
success?: boolean;
}
@@ -0,0 +1,315 @@
import * as path from 'path';
import * as fs from 'fs';
import { QueryRunner, Table } from 'typeorm';
import { FlywayHistory } from './entity.js';
import * as crypto from 'crypto';
/**
* 脚本文件信息
*/
class ScriptFile {
script: string;
isBaseline = false;
constructor(fileName: any, isBaseline: any) {
this.script = fileName;
this.isBaseline = isBaseline;
}
}
const DefaultLogger = {
debug: function (...args: any) {
console.log(args);
},
info: function (...args: any) {
console.log(args);
},
warn: function (...args: any) {
console.warn(args);
},
error: function (...args: any) {
console.error(args);
},
};
export class Flyway {
scriptDir;
flywayTableName;
baseline;
allowHashNotMatch;
connection;
logger;
constructor(opts: any) {
this.scriptDir = opts.scriptDir ?? 'db/migration';
this.flywayTableName = opts.flywayTableName ?? 'flyway_history';
this.baseline = opts.baseline ?? false;
this.allowHashNotMatch = opts.allowHashNotMatch ?? false;
this.logger = opts.logger || DefaultLogger;
this.connection = opts.connection;
}
async run(ignores?: (RegExp | string)[]) {
this.logger.info('[ midfly ] start-------------');
if (!fs.existsSync(this.scriptDir)) {
this.logger.info('[ midfly ] scriptDir<' + this.scriptDir + '> not found');
return;
}
const scriptFiles = await this.loadScriptFiles();
const queryRunner = this.connection.createQueryRunner();
await this.prepare(queryRunner);
for (const file of scriptFiles) {
if (this.isNeedIgnore(file.script, ignores)) {
continue;
}
const filepath = path.resolve(this.scriptDir, file.script);
await queryRunner.startTransaction();
try {
//查找是否已经执行
if (await this.hasExec(file.script, filepath, queryRunner)) {
await queryRunner.commitTransaction();
continue;
}
if (!file.isBaseline) {
this.logger.info('need exec script file: ', file.script);
//执行sql文件
if (/\.sql$/.test(file.script)) {
await this.execSql(filepath, queryRunner);
}
// 执行js或者ts
// if (/\.(js|ts)$/.test(file.script)) {
// await this.execJsOrTs(filepath, t);
// }
} else {
this.logger.info('baseline script file: ', file.script);
}
await this.storeSqlExecLog(file.script, filepath, true, queryRunner);
await queryRunner.commitTransaction();
} catch (err) {
this.logger.error(err);
await this.storeSqlExecLog(file.script, filepath, false, queryRunner);
await queryRunner.rollbackTransaction();
throw err;
}
}
this.logger.info('[ midfly ] end-------------');
}
private async storeSqlExecLog(filename: string, filepath: string, success: boolean, queryRunner: QueryRunner) {
const hash = await this.getFileHash(filepath);
//先删除
await queryRunner.manager.delete(FlywayHistory, {
name: filename,
});
const history = await queryRunner.manager.insert(FlywayHistory, {
name: filename,
hash,
timestamp: new Date().getTime(),
success,
});
return history;
}
/**
* 读取升级脚本文件
* @private
*/
private async loadScriptFiles() {
const files = fs.readdirSync(this.scriptDir);
files.sort();
// 获取基准脚本的位置
const local = files.indexOf(this.baseline);
const scriptFiles = new Array<ScriptFile>();
files.forEach((file, index) => {
if (index <= local) {
// 基准脚本和基准脚本之前的脚本都不执行
scriptFiles.push(new ScriptFile(file, true));
} else {
scriptFiles.push(new ScriptFile(file, false));
}
});
return scriptFiles;
}
/**
* 创建history表
* @private
*/
private async prepare(queryRunner: QueryRunner) {
await this.createFlywayTableIfNotExist(queryRunner);
}
/**
* Creates table "flyway_history" that will store information about executed migrations.
*/
protected async createFlywayTableIfNotExist(queryRunner: QueryRunner): Promise<void> {
// If driver is mongo no need to create
// if (this.connection.driver instanceof MongoDriver) {
// return;
// }
const tableExist = await queryRunner.hasTable(this.flywayTableName); // todo: table name should be configurable
if (!tableExist) {
await queryRunner.createTable(
new Table({
name: this.flywayTableName,
columns: [
{
name: 'id',
type: this.connection.driver.normalizeType({
type: this.connection.driver.mappedDataTypes.migrationId,
}),
isGenerated: true,
generationStrategy: 'increment',
isPrimary: true,
isNullable: false,
},
{
name: 'timestamp',
type: this.connection.driver.normalizeType({
type: this.connection.driver.mappedDataTypes.migrationTimestamp,
}),
isPrimary: false,
isNullable: false,
},
{
name: 'name',
type: this.connection.driver.normalizeType({
type: this.connection.driver.mappedDataTypes.migrationName,
}),
isNullable: false,
},
{
name: 'hash',
type: this.connection.driver.normalizeType({
type: this.connection.driver.mappedDataTypes.migrationName,
}),
isNullable: true,
},
{
name: 'success',
type: this.connection.driver.normalizeType({
type: 'boolean',
}),
isNullable: true,
},
],
})
);
}
}
private isNeedIgnore(file: string, ignores?: (RegExp | string)[]): boolean {
if (!ignores) {
ignores = [/\.js\.map$/, /\.d\.ts$/];
}
let ret = false;
for (const ignore of ignores) {
if (typeof ignore === 'string' && file === ignore) {
ret = true;
break;
}
if (ignore instanceof RegExp && ignore.test(file)) {
ret = true;
break;
}
}
return ret;
}
private async hasExec(file: string, filepath: string, queryRunner: QueryRunner): Promise<boolean> {
const hash = await this.getFileHash(filepath);
const history = await queryRunner.manager.findOne(FlywayHistory, {
where: { name: file, success: true },
});
if (history) {
if (history.hash !== hash && this.allowHashNotMatch === false) {
throw new Error(file + `hash conflict ,old: ${history.hash} != new: ${hash}`);
}
return true;
}
return false;
}
private async getFileHash(filepath: string) {
const content = fs.readFileSync(filepath).toString();
return crypto.createHash('md5').update(content.toString()).digest('hex');
}
private async execSql(filepath: string, queryRunner: QueryRunner) {
this.logger.info('[ midfly ] exec ', filepath);
const content = fs.readFileSync(filepath).toString().trim();
const arr = this.splitSql2Array(content);
for (const s of arr) {
await this.execOnePart(s, queryRunner);
}
}
private async execOnePart(sql: string, queryRunner: QueryRunner) {
this.logger.debug('exec sql index: ', sql);
try {
await queryRunner.query(sql);
} catch (err: any) {
this.logger.error('exec sql error ', err.message, err);
throw err;
}
}
/**
* 将字符串分割为数组
* @param {string} str 字符串
*/
splitSql2Array(str: any) {
if (!str) {
return [];
}
const temp = String(str).trim();
if (temp === 'null') {
return [];
}
const semicolon = ';';
const deepChars = ['"', "'"];
const splits = [];
const deepQueue: any = [];
for (let i = 0; i < temp.length; i++) {
const charAt = temp.charAt(i);
if (deepChars.indexOf(charAt) >= 0) {
//如果是深度char
if (i !== 0 && temp.charAt(i - 1) === '\\') {
//如果前一个是转义字符,忽略它
} else {
//说明需要进出深度了
if (deepQueue.length === 0 || deepQueue[deepQueue.length - 1] !== charAt) {
//进入深度
deepQueue.push(charAt);
} else {
//退出深度
deepQueue.pop();
}
}
}
//当深度为0,则记录分割点
if (charAt === semicolon && deepQueue.length === 0) {
splits.push(i + 1);
}
}
//分割sql
const arr = [];
let lastIndex = 0;
for (const index of splits) {
const sql = temp.substring(lastIndex, index);
lastIndex = index;
arr.push(sql.trim());
}
return arr;
}
}
@@ -0,0 +1,6 @@
// src/index.ts
export { FlywayConfiguration as Configuration } from './configuration.js';
// eslint-disable-next-line node/no-unpublished-import
export { Flyway } from './flyway.js';
// eslint-disable-next-line node/no-unpublished-import
export { FlywayHistory } from './entity.js';
+4
View File
@@ -0,0 +1,4 @@
end -----
```
# test
The last line of the SQL file should be uncommented and should end with a semicolon of the SQL statement.
@@ -0,0 +1,3 @@
-- no sql
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
@@ -0,0 +1,32 @@
-- 表:sys_permission
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636);
-- 表:sys_role
CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537);
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (2, '只读角色', 1, 1623749138537);
-- 表:sys_role_permission
CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id"));
INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1);
-- 表:sys_user
CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (2, 'readonly', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
-- 表:sys_user_role
CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id"));
INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1);
INSERT INTO sys_user_role (role_id, user_id) VALUES (2, 2);
-- 索引:IDX_223de54d6badbe43a5490450c3
CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name");
-- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da
CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username");
@@ -0,0 +1,3 @@
-- this is blank sql, 注释不要放在结尾
select 1;
@@ -0,0 +1,32 @@
-- 表:sys_permission
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636);
-- 表:sys_role
CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537);
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (2, '只读角色', 1, 1623749138537);
-- 表:sys_role_permission
CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id"));
INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1);
-- 表:sys_user
CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (2, 'readonly', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
-- 表:sys_user_role
CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id"));
INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1);
INSERT INTO sys_user_role (role_id, user_id) VALUES (2, 2);
-- 索引:IDX_223de54d6badbe43a5490450c3
CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name");
-- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da
CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username");
@@ -0,0 +1,2 @@
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (3, 'admin1', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (4, 'readonly1', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
@@ -0,0 +1,3 @@
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (3, '管理员1', 1, 1623749138537);
-- hash check
select 1;
@@ -0,0 +1,32 @@
-- 表:sys_permission
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636);
-- 表:sys_role
CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537);
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (2, '只读角色', 1, 1623749138537);
-- 表:sys_role_permission
CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id"));
INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1);
-- 表:sys_user
CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (2, 'readonly', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
-- 表:sys_user_role
CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id"));
INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1);
INSERT INTO sys_user_role (role_id, user_id) VALUES (2, 2);
-- 索引:IDX_223de54d6badbe43a5490450c3
CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name");
-- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da
CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username");
@@ -0,0 +1,2 @@
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (3, 'admin1', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (4, 'readonly1', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
@@ -0,0 +1,2 @@
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (3, '管理员1', 1, 1623749138537);
INSERT INTO sys_role (id, name, create_time, update_time) VALUES (4, '只读角色1', 1, 1623749138537);
@@ -0,0 +1,5 @@
-- 表:sys_permission
CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time)
VALUES (1, '系统管理;', 'sys', -1, 1, 1, 1624085863636);
@@ -0,0 +1,2 @@
'"test;";\'test;\'';

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