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