temp
This commit is contained in:
commit
652e71c4a8
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
dist
|
||||||
|
|
||||||
|
pack-dist
|
||||||
|
|
||||||
|
logs
|
||||||
|
|
||||||
|
.env*
|
||||||
|
!.en*example
|
||||||
|
|
||||||
|
|
||||||
|
kevisual-sync.db
|
13
kevisual.json
Normal file
13
kevisual.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Sync Projects",
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"sync": {
|
||||||
|
"./tsconfig.json": {
|
||||||
|
"url": "https://kevisual.cn/root/ai/tsconfig.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@kevisual/sync",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "bun src/test/index.ts "
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
|
"license": "MIT",
|
||||||
|
"packageManager": "pnpm@10.6.2",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@kevisual/db": "^0.0.1",
|
||||||
|
"@kevisual/router": "^0.0.13",
|
||||||
|
"@kevisual/types": "^0.0.9",
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
"@types/bun": "^1.2.12",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/node": "^22.15.13",
|
||||||
|
"commander": "^13.1.0",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"fast-glob": "^3.3.3",
|
||||||
|
"sequelize": "^6.37.7",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"sqlite3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1473
pnpm-lock.yaml
generated
Normal file
1473
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
src/app.ts
Normal file
6
src/app.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { QueryRouterServer } from '@kevisual/router';
|
||||||
|
import { SyncConfig } from './modules/sync-config/config.ts';
|
||||||
|
|
||||||
|
export const app = new QueryRouterServer();
|
||||||
|
|
||||||
|
export const syncConfig = new SyncConfig();
|
47
src/db/file-model.ts
Normal file
47
src/db/file-model.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { DBModel, DataTypes, SyncOptions } from '@kevisual/db';
|
||||||
|
import { Stat } from './stat.ts';
|
||||||
|
|
||||||
|
export class FileModel extends DBModel {
|
||||||
|
declare id: string;
|
||||||
|
|
||||||
|
declare filepath: string; // 是唯一的
|
||||||
|
declare filename: string;
|
||||||
|
declare stat: Stat;
|
||||||
|
declare hash: string;
|
||||||
|
declare cwd: string;
|
||||||
|
|
||||||
|
static async initModel(sequelize: any, syncOpts?: SyncOptions) {
|
||||||
|
FileModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
filepath: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
stat: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
modelName: 'file',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (syncOpts) {
|
||||||
|
await FileModel.sync(syncOpts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/db/manage.ts
Normal file
4
src/db/manage.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { DataSource } from '@kevisual/db';
|
||||||
|
import { FileModel } from './file-model.ts';
|
||||||
|
|
||||||
|
export const dataSource = new DataSource();
|
72
src/db/model.ts
Normal file
72
src/db/model.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { DBModel, DataTypes, SyncOptions } from '@kevisual/db';
|
||||||
|
import { Stat } from './stat.ts';
|
||||||
|
|
||||||
|
export class SyncModel extends DBModel {
|
||||||
|
declare id: string;
|
||||||
|
/**
|
||||||
|
* URL 同步地址
|
||||||
|
*/
|
||||||
|
declare url: string;
|
||||||
|
declare syncData: Object;
|
||||||
|
declare remoteData: Object;
|
||||||
|
|
||||||
|
declare markId: string;
|
||||||
|
|
||||||
|
declare filepath: string; // 是唯一的
|
||||||
|
declare filename: string;
|
||||||
|
declare stat: Stat;
|
||||||
|
declare hash: string;
|
||||||
|
|
||||||
|
static async initModel(sequelize: any, syncOpts?: SyncOptions) {
|
||||||
|
SyncModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
syncData: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
remoteData: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
markId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
filepath: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
stat: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
modelName: 'sync',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (syncOpts) {
|
||||||
|
await SyncModel.sync(syncOpts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/db/stat.ts
Normal file
23
src/db/stat.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export type Stat = {
|
||||||
|
dev: number;
|
||||||
|
ino: number;
|
||||||
|
mode: number;
|
||||||
|
nlink: number;
|
||||||
|
uid: number;
|
||||||
|
gid: number;
|
||||||
|
rdev: number;
|
||||||
|
size: number;
|
||||||
|
blksize: number;
|
||||||
|
blocks: number;
|
||||||
|
atimeMs: number;
|
||||||
|
mtimeMs: number;
|
||||||
|
ctimeMs: number;
|
||||||
|
birthtimeMs: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStat = async (path: string): Promise<Stat> => {
|
||||||
|
const _stat = await fs.promises.stat(path);
|
||||||
|
return JSON.parse(JSON.stringify(_stat));
|
||||||
|
};
|
4
src/index.ts
Normal file
4
src/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { app, syncConfig } from './app.ts';
|
||||||
|
import './routes/index.ts';
|
||||||
|
|
||||||
|
export { app, syncConfig };
|
56
src/modules/sync-config/config.ts
Normal file
56
src/modules/sync-config/config.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import { KevisualConfig, readKevisualConfig, writeKevisualConfig } from './kevisual.ts';
|
||||||
|
import { createSqlite } from '@kevisual/db';
|
||||||
|
const KEVISUAL_CONFIG_FILENAME = 'kevisual.json';
|
||||||
|
const DB_FILENAME = 'kevisual-sync.db';
|
||||||
|
import path from 'path';
|
||||||
|
export const initConfigPath = (rootPath: string) => {
|
||||||
|
return {
|
||||||
|
workPath: path.resolve(rootPath),
|
||||||
|
dbPath: path.join(rootPath, DB_FILENAME),
|
||||||
|
configPath: path.join(rootPath, KEVISUAL_CONFIG_FILENAME),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SyncConfigOpts = {
|
||||||
|
workPath?: string;
|
||||||
|
init?: boolean;
|
||||||
|
emitter?: EventEmitter;
|
||||||
|
};
|
||||||
|
export class SyncConfig {
|
||||||
|
workPath: string;
|
||||||
|
emitter: EventEmitter;
|
||||||
|
isInit: boolean = false;
|
||||||
|
configPath: ReturnType<typeof initConfigPath>;
|
||||||
|
config: KevisualConfig;
|
||||||
|
sequelize: ReturnType<typeof createSqlite>;
|
||||||
|
constructor(opts?: SyncConfigOpts) {
|
||||||
|
this.workPath = opts?.workPath || process.cwd();
|
||||||
|
if (opts?.init) this.init();
|
||||||
|
this.emitter = opts?.emitter ?? new EventEmitter();
|
||||||
|
this.configPath = initConfigPath(this.workPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.isInit = true;
|
||||||
|
this.config = await readKevisualConfig(this.configPath.configPath);
|
||||||
|
this.emitter.emit('init', true);
|
||||||
|
}
|
||||||
|
async writeConfig(config: KevisualConfig) {
|
||||||
|
this.config = { ...this.config, ...config };
|
||||||
|
await writeKevisualConfig(this.configPath.configPath, this.config);
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
async initDB() {
|
||||||
|
if (this.sequelize) return this.sequelize;
|
||||||
|
console.log('init db', this.configPath.dbPath);
|
||||||
|
this.sequelize = createSqlite({ storage: this.configPath.dbPath, logging: false });
|
||||||
|
return this.sequelize;
|
||||||
|
}
|
||||||
|
async onInit() {
|
||||||
|
if (this.isInit) return true;
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
this.emitter.once('init', resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
26
src/modules/sync-config/kevisual.ts
Normal file
26
src/modules/sync-config/kevisual.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export type KevisualConfig = {
|
||||||
|
ignore?: string[];
|
||||||
|
sync?: {
|
||||||
|
[key: string]: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readKevisualConfig = async (path: string): Promise<KevisualConfig> => {
|
||||||
|
try {
|
||||||
|
const data = await fs.promises.readFile(path, 'utf-8');
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeKevisualConfig = async (path: string, config: KevisualConfig): Promise<KevisualConfig> => {
|
||||||
|
let _config = await readKevisualConfig(path);
|
||||||
|
_config = { ..._config, ...config };
|
||||||
|
await fs.promises.writeFile(path, JSON.stringify(_config, null, 2));
|
||||||
|
return _config;
|
||||||
|
};
|
1
src/routes/files/index.ts
Normal file
1
src/routes/files/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import './list.ts'
|
65
src/routes/files/list.ts
Normal file
65
src/routes/files/list.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { app, syncConfig } from '@/app.ts';
|
||||||
|
import FastGlob from 'fast-glob';
|
||||||
|
import { MD5 } from 'crypto-js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import { getStat } from '@/db/stat.ts';
|
||||||
|
import { FileModel } from '@/db/file-model.ts';
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'file',
|
||||||
|
key: 'list',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const files = await FastGlob(['**/*'], {
|
||||||
|
cwd: syncConfig.workPath,
|
||||||
|
onlyFiles: true,
|
||||||
|
absolute: false,
|
||||||
|
ignore: ['node_modules'],
|
||||||
|
});
|
||||||
|
ctx.body = files;
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'file',
|
||||||
|
key: 'init',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const files = await FastGlob(['**/*'], {
|
||||||
|
cwd: syncConfig.workPath,
|
||||||
|
onlyFiles: true,
|
||||||
|
absolute: false,
|
||||||
|
ignore: ['node_modules'],
|
||||||
|
});
|
||||||
|
const result: any[] = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const content = fs.readFileSync(file, 'utf-8');
|
||||||
|
const hash = MD5(content).toString();
|
||||||
|
const stat = await getStat(file);
|
||||||
|
const filename = file.split('/').pop();
|
||||||
|
result.push({ filepath: file, filename: filename, hash, stat });
|
||||||
|
}
|
||||||
|
const sequelize = await syncConfig.initDB();
|
||||||
|
await FileModel.initModel(sequelize, { alter: true });
|
||||||
|
await FileModel.destroy({ where: {} })
|
||||||
|
const create = await FileModel.bulkCreate(result);
|
||||||
|
console.log(create);
|
||||||
|
ctx.body = result;
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'file',
|
||||||
|
key: 'get-sql',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const sequelize = await syncConfig.initDB();
|
||||||
|
await FileModel.initModel(sequelize);
|
||||||
|
const sql = await FileModel.findAll();
|
||||||
|
ctx.body = sql.map((item) => item.toJSON());
|
||||||
|
})
|
||||||
|
.addTo(app);
|
1
src/routes/index.ts
Normal file
1
src/routes/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import './files/index.ts'
|
3
src/test/cmd.ts
Normal file
3
src/test/cmd.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { app, syncConfig } from '@/index.ts';
|
||||||
|
import { program, Command } from 'commander';
|
||||||
|
export { program, app, syncConfig, Command };
|
30
src/test/command/file-list.ts
Normal file
30
src/test/command/file-list.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { app, syncConfig, program, Command } from '../cmd.ts';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import util from 'node:util';
|
||||||
|
const main = async () => {
|
||||||
|
const res = await app.queryRoute({ path: 'file', key: 'list' });
|
||||||
|
console.log(res);
|
||||||
|
const kv = syncConfig.configPath.configPath;
|
||||||
|
const stat = fs.statSync(kv);
|
||||||
|
console.log(JSON.parse(JSON.stringify(stat)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmd = new Command('file-list').description('list files').action(main);
|
||||||
|
|
||||||
|
program.addCommand(cmd);
|
||||||
|
|
||||||
|
const initMain = async () => {
|
||||||
|
const res = await app.queryRoute({ path: 'file', key: 'init' });
|
||||||
|
console.log(util.inspect(res, { depth: 10, colors: true }));
|
||||||
|
};
|
||||||
|
const cmd2 = new Command('file-init').description('list files').action(initMain);
|
||||||
|
|
||||||
|
program.addCommand(cmd2);
|
||||||
|
|
||||||
|
const getSql = async () => {
|
||||||
|
const res = await app.queryRoute({ path: 'file', key: 'get-sql' });
|
||||||
|
console.log(util.inspect(res, { depth: 10, colors: true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmd3 = new Command('file-get-sql').description('list files').action(getSql);
|
||||||
|
program.addCommand(cmd3);
|
3
src/test/command/resolve.ts
Normal file
3
src/test/command/resolve.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
console.log('resolve', path.resolve('./file-list.ts').replace(process.cwd()+'/', ''))
|
4
src/test/index.ts
Normal file
4
src/test/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { program } from './cmd.ts';
|
||||||
|
import './command/file-list.ts';
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "@kevisual/types/json/backend.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user