@lcap/nasl
Version:
NetEase Application Specific Language
1,326 lines • 54.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveNaslManual = exports.changeTabTimestamp = exports.loadAppSync = exports.mountDatabaseTypes = exports.initializeCheckRules = exports.loadApp = exports.setStore = exports.getNaslIncrQuery = exports.handleApp = exports.getGlobalChangedTime = exports.databaseTypes = exports.getTabTimestamp = exports.aiCoachActionRecord = exports.breakpoint = exports.cleanUselessUnit = exports.batchInstruct = exports.batchAction = exports.batchQuery = void 0;
const breakpoint_1 = require("../../breakpoint");
const config_1 = require("../../config");
const service_1 = __importDefault(require("./service"));
const uuid_1 = require("uuid");
const stepRecorder_1 = __importDefault(require("../../manager/stepRecorder"));
const checkRules_1 = require("../../checkRules");
const instruct_ruler_1 = require("../instruct-ruler");
const jsoner = __importStar(require("./jsoner"));
const compare_versions_1 = __importDefault(require("compare-versions"));
const nasl_concepts_1 = require("@lcap/nasl-concepts");
const eventBus_1 = __importDefault(require("../../eventBus"));
/// #if !process.env.NODE_ENV || process.env.BUILD_TARGET === 'node'
const fs = __importStar(require("fs-extra"));
/// #endif
const types_1 = require("./types");
const utils_1 = require("./utils");
const concepts_1 = require("../../concepts");
const operation_1 = require("./operation");
const taskQueue_1 = require("./taskQueue");
const cache_1 = require("./cache");
exports.batchQuery = service_1.default.batchQuery;
exports.batchAction = service_1.default.batchAction;
exports.batchInstruct = service_1.default.batchInstruct;
exports.cleanUselessUnit = service_1.default.cleanUselessUnit;
exports.breakpoint = service_1.default.breakpoint;
exports.aiCoachActionRecord = service_1.default.aiCoachActionRecord;
let globalBatchInstructResult = null;
let tabTimestamp;
function getTabTimestamp() {
return tabTimestamp;
}
exports.getTabTimestamp = getTabTimestamp;
exports.databaseTypes = service_1.default.databaseTypes;
let globalChangedTime = 0;
function setGlobalChangedTime(time) {
globalChangedTime = time;
}
function getGlobalChangedTime() {
return globalChangedTime;
}
exports.getGlobalChangedTime = getGlobalChangedTime;
function getLogic(key, app, diffLogicList) {
if (!diffLogicList.includes(key)) {
diffLogicList.push(key);
if (key.startsWith('app')) {
const index = key.lastIndexOf('.');
const name = key.slice(index + 1);
const logic = app.logics.find((item) => item.name === name);
if (logic && logic.body && logic.body.length > 2) {
logic.body.map((item) => getStatement(item, app, diffLogicList));
}
}
else if (key.startsWith('extensions')) {
const _pathArr = key.split('.');
const _name = _pathArr[1];
const _path = _pathArr[3];
const _list = app.dependencies;
const _module = _list.find((it) => {
const name = it.name.replaceAll('-', '_');
return name === _name;
});
const logic = _module?.logics?.find((item) => item.name === _path);
if (logic && logic.body && logic.body.length > 2) {
logic.body.map((item) => getStatement(item, app, diffLogicList));
}
}
}
}
/* 获取LogicITEM */
function getStatement(state, app, diffLogicList) {
let logicItems = [];
switch (state.concept) {
case 'Assignment':
logicItems = [state.left, state.right];
break;
case 'IfStatement':
logicItems = [...state.consequent, ...state.alternate];
break;
case 'ForEachStatement':
logicItems = [...state.body, state.each, state.start, state.end];
break;
case 'Match':
logicItems = [state.expression, ...state.cases.map(getStatement).flat(1)];
break;
case 'MatchCase':
logicItems = [...state.patterns, ...state.body];
break;
case 'SwitchStatement':
logicItems = state.cases.map(getStatement).flat(1);
break;
case 'SwitchCase':
logicItems = [state.test, ...state.consequent];
break;
case 'WhileStatement':
logicItems = [state.test, ...state.body];
break;
case 'CallLogic':
logicItems = state.arguments?.map((item) => item.expression);
getLogic(state.calleewholeKey, app, diffLogicList);
break;
case 'CallFunction':
logicItems = state.arguments?.map((item) => item.expression);
break;
case 'CallInterface':
logicItems = state.arguments?.map((item) => item.expression);
break;
case 'ExternalDestination':
logicItems = [state.anchor, state.link];
break;
case 'BatchAssignment':
logicItems = [state.left?.expression, ...state.left?.members, ...state.rights.map((item) => [item?.expression, ...item?.members]).flat(1)];
break;
case 'MemberExpression':
logicItems = [state.object, state.property];
break;
case 'StringInterpolation':
logicItems = [...state.expressions];
break;
case 'NewComposite':
logicItems = [...state.rights.map((item) => item.expression)];
break;
case 'NewList':
logicItems = [...state.items];
break;
default:
break;
}
return logicItems.filter((item) => !!item);
}
/* 小程序影响的服务端发布 */
function getMiniAppChange(target, obj, action) {
if (!target.app.miniEnable) {
return false;
}
/* 分端以后需要改 */
const len = target.nodePath?.split('.').length;
if (len > 2) {
return false;
}
if (action === 'delete') {
return true;
}
else {
if ('name' in obj || 'title' in obj) {
return true;
}
}
}
let aiExecuted = 0;
let sessionPath = '';
// AI 生成内容是否被采纳
const handleAIPoint = (app, actionItem) => {
const { actionMsg, action } = actionItem || {};
if (aiExecuted > 0) {
app.emit('logic:accepted', { ...actionItem, sessionPath });
aiExecuted -= 1;
}
if (actionMsg.includes('自然语言生成代码') && !action) {
aiExecuted += 1;
// eslint-disable-next-line prefer-destructuring
sessionPath = actionMsg?.match(/:(.*)/)?.[1];
}
};
/**
* 代码补齐 batchAction 埋点
*/
const handleAICompletionPoint = (instructList) => {
const store = getStore();
const { aiCompletionData } = store || {};
const { actions, uuid, realAction } = instructList?.[0] || {};
const { focusedNodePath, position } = aiCompletionData?.request || {};
let nodePath = focusedNodePath;
if (position?.includes('clickInNode-Identifier')) {
nodePath = nodePath.substring(0, nodePath.lastIndexOf('.'));
}
if (!nodePath || !actions || !actions.length && realAction) {
return;
}
// 遍历 actions,比较 nodePath
let needReport = false;
let needReportAction = {};
actions.forEach((action) => {
if (action.action !== 'delete' && action.path === nodePath) {
needReport = true;
needReportAction = { action, uuid };
}
});
if (needReport) {
let requestId = aiCompletionData?.oldRequestId || aiCompletionData?.requestId;
const params = {
coachType: aiCompletionData?.oldRequestId ? 'ai_completion' : aiCompletionData?.completionType,
requestId,
timestamp: Date.now(),
...needReportAction,
};
if (!aiCompletionData.requestId)
return;
service_1.default.aiCoachActionRecord({
body: params,
});
}
};
const handleAICoachPoint = (instructList) => {
// 有 batchAction 时需要更新 tsContext
const store = getStore();
store.setReplaceAITsContext(true);
store.setNaslChange(true);
const supportPoint = window?.appInfo?.saasEnv;
if (!supportPoint)
return;
const aiCompletionSupport = window?.appInfo?.aiTenantResource?.['aiNaslCompletionSupport']?.resourceValue === 'true';
const aiSpecCompletionSupport = window?.appInfo?.aiTenantResource?.['aiSpecificCompletionSupport']?.resourceValue === 'true';
if (!aiCompletionSupport && !aiSpecCompletionSupport)
return;
const { aiCompletionData, aiRecommendData } = store || {};
if (!!aiCompletionData) {
if (aiCompletionData?.action === '1') {
// 采纳补齐推荐内容
instructList[0].sourceType = aiCompletionData?.completionType;
if (aiCompletionData?.completionType === 'ai_specific_completion') {
handleAICompletionPoint(instructList);
}
}
else {
handleAICompletionPoint(instructList);
}
}
if (!!aiRecommendData) {
if (['1', '2', '3', '4', '5'].includes(aiRecommendData?.action)) {
instructList[0].sourceType = 'ai_recommend';
}
}
// nasl 变化,只比对一次,清空相关数据
store.setAICompletionData({});
store.setAIRecommendData({});
};
const isBreakpointEvent = (event) => {
const originEvent = event?.originEvent;
const object = originEvent?.object || {};
const oldObject = originEvent?.oldObject || {};
if (typeof object !== 'object') {
return false;
}
if (typeof oldObject !== 'object') {
return false;
}
const source = { ...object, ...oldObject };
const keys = Object.keys(source);
if (keys.length !== 1) {
return false;
}
return keys[0] === 'breakpoint';
};
/**
* 执行更新
*/
async function doAction(app, actionItem) {
const isOperationRecord = operation_1.operationRecordInfoMap.get('isOperationRecord');
if (isOperationRecord) {
console.log('正在进行操作栈回放操作,如需行任何Nasl操作请刷新页面');
return;
}
let hasFrontEnd = false;
let hasBackEnd = false;
const actionList = [];
const allRiskList = [];
const { list, actionMsg, sourceType, action, extra } = actionItem || {};
handleAIPoint(app, actionItem);
const itemloop = (_i, app, diffArr) => {
const _l = getStatement(_i, app, diffArr);
if (_l.length) {
_l.map((_ii) => itemloop(_ii, app, diffArr));
}
};
const loopEle = (view, checkAuth) => {
if (view instanceof concepts_1.View) {
view?.elements?.forEach((item) => loopEle(item, checkAuth));
}
view?.children?.forEach((item) => loopEle(item, checkAuth));
if (view instanceof concepts_1.ViewElement) {
if (view.bindEvents.length) {
const diffArr = [];
view.bindEvents.forEach((bindEvent) => {
bindEvent.logics.forEach((logic) => {
if (nasl_concepts_1.asserts.isLogic(logic)) {
logic.body.forEach((logicItem) => itemloop(logicItem, app, diffArr));
}
});
});
const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions'));
if (result.length) {
hasBackEnd = true;
}
}
if (view.tag === 'u-uploader' || view.tag === 'van-uploader') {
const bute = view?.bindAttrs.find((item) => item.name === 'url');
const strVal = bute?.value;
const needUpdateBackEnd = strVal?.endsWith('/import') || strVal?.includes('/upload/');
if (checkAuth) {
if (needUpdateBackEnd && !view.view?.parentAuth) {
hasBackEnd = true;
}
}
else {
if (needUpdateBackEnd) {
hasBackEnd = true;
}
}
}
}
};
const loopEleAuth = (viewElement) => {
viewElement?.children?.forEach((item) => {
const bindDirective = item?.bindDirectives?.find((item) => item.name === 'auth');
if (bindDirective) {
hasBackEnd = true;
}
loopEleAuth(item);
});
};
try {
if (Array.isArray(list)) {
list.forEach((event) => {
const emitTarget = event?.originEvent?.target;
const _path = event.originEvent.path;
const _root = _path.split('.')?.[1] || '';
const breakabled = isBreakpointEvent(event);
if (['backend', 'overriddenLogics', 'dataSources', 'processes', 'roles', 'logics', 'enums', 'structures',
'interfaces', 'interfaceDependencies', 'configuration', 'dependencies', 'authLogics',
'authLogicsForCallInterface', 'triggerLaunchers', 'connections', 'processV2s',
'connectorDependencies', 'sharedAppDependencies', 'directories'].some((item) => _root.startsWith(item))) {
if (!breakabled) {
hasBackEnd = true;
}
}
if (emitTarget) {
if (emitTarget.concept === 'App') {
if (event?.object?.preferenceMap || event?.originEvent?.object?.preferenceMap) {
hasBackEnd = true;
hasFrontEnd = true;
}
}
if (emitTarget.concept === 'ValidationRule') {
if (event?.object?.enableServerValidation ||
event?.originEvent?.object?.enableServerValidation) {
hasBackEnd = true;
hasFrontEnd = true;
}
}
if (emitTarget.concept === 'Logic') {
const diffArr = [];
if (emitTarget.body.length > 2) {
emitTarget.body.forEach((it) => {
itemloop(it, app, diffArr);
});
}
const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions'));
if (result.length) {
hasBackEnd = true;
}
}
if ('view' in emitTarget && emitTarget.view) {
hasFrontEnd = true;
}
if (emitTarget.concept === 'BindDirective') {
const isDelete = event?.originEvent?.action === 'delete';
if (isDelete) {
const isAuto = ~event?.originEvent?.path?.indexOf('name=auth');
if (isAuto) {
hasBackEnd = true;
}
}
if (event?.originEvent?.object?.name === 'auth') {
hasBackEnd = true;
}
}
if (emitTarget.concept === 'View') {
if (getMiniAppChange(emitTarget, event?.originEvent?.object, event?.originEvent?.action)) {
hasBackEnd = true;
}
const isDelete = event?.originEvent?.action === 'delete';
const isAuthView = emitTarget.auth && event?.originEvent?.object?.name;
const isUpdateAuth = event?.originEvent?.action === 'update' && "auth" in (event?.originEvent?.object || {});
const isUpdateAuthDes = event?.originEvent?.action === 'update' && "authDescription" in (event?.originEvent?.object || {});
const isUpdateBindRoles = event?.originEvent?.action === 'update' && event?.originEvent?.object && event?.originEvent?.object?.bindRoles;
if (isDelete || isUpdateAuth || isUpdateBindRoles || isAuthView || isUpdateAuthDes) {
hasBackEnd = true;
// const app = emitTarget.app;
// const diffArr: string[] = [];
// emitTarget.logics.forEach((logic: Logic) => {
// if (logic.body.length > 2) {
// logic.body.forEach((it: LogicItem) => {
// itemloop(it, app, diffArr);
// });
// }
// });
// const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions'));
// if (result.length) {
// hasBackEnd = true;
// }
// emitTarget.children.forEach((item: View | ViewElement) => loopEle(item, isDelete));
// emitTarget.elements.forEach((item: View | ViewElement) => loopEle(item, isDelete));
}
hasFrontEnd = true;
}
if (emitTarget.concept === 'ViewElement') {
if (event?.originEvent?.action === 'update' || event?.originEvent?.action === 'delete') {
if ('authDescription' in (event?.originEvent?.object ?? {})) {
hasBackEnd = true;
}
}
if (event?.originEvent?.object?.name) {
const bindDirective = emitTarget.bindDirectives.find((item) => item.name === 'auth');
if (bindDirective) {
hasBackEnd = true;
}
}
if (event?.originEvent?.action === 'delete') {
const bindDirective = emitTarget?.bindDirectives?.find((item) => item.name === 'auth');
if (bindDirective) {
hasBackEnd = true;
}
loopEleAuth(emitTarget);
}
if (event?.originEvent?.action !== 'create') {
if (event?.originEvent?.object && event?.originEvent?.object?.bindRoles) {
hasBackEnd = true;
}
}
loopEle(emitTarget, true);
}
if (emitTarget.concept === 'BindAttribute' && emitTarget.name === 'url') {
const strVal = emitTarget?.value;
const needUpdateBackEnd = strVal?.endsWith('/import') || strVal?.includes('/upload/');
if (!emitTarget?.view?.parentAuth && needUpdateBackEnd) {
hasBackEnd = true;
}
}
if (emitTarget.concept === 'CallLogic') {
const arrlist = [];
getLogic(emitTarget.calleeKey, emitTarget.app, arrlist);
const result = arrlist.filter((item) => item.startsWith('app') || item.startsWith('extensions'));
// console.log(result, 'CallLogic');
if (result.length) {
hasBackEnd = true;
}
}
// 更新端时需要出发后端更新
if (emitTarget.concept === 'Frontend') {
hasBackEnd = true;
}
}
event.eventList.forEach(({ action, riskList, path, object: objItem }) => {
if (riskList) {
allRiskList.push(...(riskList || []));
}
if (Array.isArray(objItem)) {
if (action === 'update') {
actionList.push({
action,
path,
node: objItem[0],
});
}
else {
objItem.forEach((item, index) => {
actionList.push({
action,
path,
node: item,
});
});
}
}
else {
actionList.push({
action,
path,
object: objItem,
});
}
});
});
}
}
catch (error) {
console.log(error);
taskQueue.refreshNasl(app, {
errorInfo: {
message: error?.message,
stack: error?.stack,
},
});
return;
}
const uuid = (0, uuid_1.v4)().replace(/-/g, '');
const instructList = [{
uuid,
realAction: action,
actions: actionList,
riskList: allRiskList,
actionMsg,
sourceType,
}];
if (actionMsg?.includes('自然语言生成代码') && action !== 'undo' && action !== 'redo') {
if (actionMsg.includes('-ui:')) {
instructList[0].sourceType = 'ai_nl2ui';
}
if (actionMsg.includes('-logic:')) {
instructList[0].sourceType = 'ai_nl2logic';
}
if (actionMsg.includes('-partial:')) {
instructList[0].sourceType = 'ai_nl2partial';
}
}
const operation = {
type: 'doAction',
uuid,
actionItem: {
actionMsg,
list: list.map((item) => {
const { path, action, object, oldObject, parentKey, index } = item?.originEvent || {};
return {
path,
action,
parentKey,
index,
object: object?.toJSON?.() || object?.__v_raw || object,
oldObject: oldObject?.toJSON?.() || oldObject?.__v_raw || oldObject,
};
}),
},
};
// 当前选中的编辑器和节点
const store = getStore();
const { activeDesigner, selectedNode } = store || {};
handleAICoachPoint(instructList);
const activeDesignerPath = activeDesigner?.node?.nodePath;
if (activeDesignerPath) {
operation.activeDesignerPath = activeDesignerPath;
}
const selectedNodePath = selectedNode?.nodePath;
if (selectedNodePath) {
operation.selectedNodePath = selectedNodePath;
}
saveNasl({ app, hasFrontEnd, hasBackEnd, instructList, operationList: [operation] });
// 进行nasl操作后需要将Index重置
operation_1.operationRecordInfoMap.set('operationRecordIndex', null);
if (!app._historying) {
if (app._historyIndex !== app._historyList.length) {
app._historyList = app._historyList.splice(0, app._historyIndex);
}
app._historyList.push({
actionMsg,
extra: extra,
list: list.map((item) => item?.originEvent),
});
app._historyIndex = app._historyList.length;
}
else {
const actionMap = {
undo: '已撤销操作:',
redo: '已重做操作:',
};
let msg = actionMap[action];
if (actionMsg?.includes('自然语言生成代码')) {
msg += '自然语言生成代码';
}
else {
msg += actionMsg;
}
if (process.env.BUILD_TARGET !== 'node') {
require('element-plus').ElMessage.info(msg);
}
app._historying = false;
}
stepRecorder_1.default.initialized && stepRecorder_1.default.record(actionMsg, actionItem, actionList);
}
class TaskQueue {
constructor() {
this.queue = [];
this.lastQueue = [];
this.running = false;
this.maxTaskCount = 10;
this.status = undefined;
this.saveTimer = null;
}
removeAppFromQueue(queue) {
return JSON.parse(JSON.stringify(queue, (key, value) => {
return value?.concept === 'App' ? undefined : value;
}));
}
addTask(task) {
this.queue.push(task);
if (this.queue.length >= this.maxTaskCount) {
this.status = types_1.TaskQueueStatus.ExceedMaxTaskCount;
task.app.emit('ExceedMaxTaskCount');
this.debounceSaveToIndexedDB(task.app, {
message: '队列超过最大任务数',
});
}
this.run();
}
saveToIndexedDB(app, errorInfo) {
(0, taskQueue_1.saveTaskQueueToIndexedDB)({
appJson: app.toJSON(),
lastQueue: this.removeAppFromQueue(this.lastQueue),
queue: this.removeAppFromQueue(this.queue),
timestamp: Date.now(),
appId: operation_1.operationRecordInfoMap.get('appId'),
branchId: operation_1.operationRecordInfoMap.get('branchId'),
accountId: operation_1.operationRecordInfoMap.get('accountId'),
userId: operation_1.operationRecordInfoMap.get('userId'),
phone: operation_1.operationRecordInfoMap.get('phone'),
errorInfo,
}).catch(error => {
console.error('Failed to save task queue state to IndexedDB', error);
});
}
debounceSaveToIndexedDB(app, errorInfo) {
if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
this.saveTimer = setTimeout(() => {
this.saveToIndexedDB(app, errorInfo);
this.saveTimer = null;
}, 1000);
}
async clear() {
this.queue = [];
this.lastQueue = [];
this.status = undefined;
this.running = false;
}
async run(queue = this.queue) {
if (this.running)
return;
if (queue.length === 0)
return;
const app = queue[0].app;
let hasFrontEnd = false;
let hasBackEnd = false;
let instructList = [];
let operationList = [];
for (const task of queue) {
if (task.hasFrontEnd)
hasFrontEnd = true;
if (task.hasBackEnd)
hasBackEnd = true;
instructList = instructList.concat(task.instructList);
operationList = operationList.concat(task.operationList);
}
if (queue === this.queue) {
this.lastQueue = [...queue];
this.queue = [];
}
this.running = true;
const error = await _saveNasl({
app, hasFrontEnd, hasBackEnd, instructList, operationList
});
if (window?.appData?.branchStatus === 'merging' && error) {
this.clear();
eventBus_1.default.emit('forceCancelPull', error);
return;
}
// code: 500502 msg: '该应用已在新tab 页打开,请刷新!' 不重试或刷新
// code: 500505 需要刷新浏览器
if (!error)
this.recover(app);
else if (![500502, 500505].includes(error.code)) {
// 连接失败,或超时,或后端持久化异常
if ((error.message === 'Network Error' || error.message?.includes('timeout') || error.code === 500500)
&& this.status !== types_1.TaskQueueStatus.Retrying) {
this.running = false;
this.retry();
}
else
this.refreshNasl(app, {
errorInfo: {
code: error.code,
message: error.message || error.msg,
result: error.result,
},
});
return;
}
this.running = false;
this.run();
}
retry() {
if (this.lastQueue.length === 0)
return;
const app = this.lastQueue[0].app;
this.status = types_1.TaskQueueStatus.Retrying;
app.emit('Retrying');
this.run(this.lastQueue);
}
async refreshNasl(app, options = { errorInfo: {} }) {
const failedQueue = this.lastQueue.map(({ hasFrontEnd, hasBackEnd, instructList }) => ({
hasFrontEnd, hasBackEnd, instructList,
}));
const queue = this.queue.map(({ hasFrontEnd, hasBackEnd, instructList }) => ({
hasFrontEnd, hasBackEnd, instructList,
}));
this.saveToIndexedDB(app, options.errorInfo);
await service_1.default.saveFrontendNasl({
body: {
nasl: app?.toJSON?.(),
failedQueue,
queue,
errorInfo: options.errorInfo,
},
}).catch((err) => {
console.log('备份 nasl 失败:', err);
});
await this.clear();
app?.emit?.('refresh');
}
recover(app) {
switch (this.status) {
case types_1.TaskQueueStatus.ExceedMaxTaskCount:
app.emit('BelowMaxTaskCount');
this.status = undefined;
break;
case types_1.TaskQueueStatus.Retrying:
app.emit('SuccessRetry');
this.status = undefined;
break;
}
}
}
const taskQueue = new TaskQueue();
async function saveNasl(options) {
const store = getStore();
if (store.viewTaskQueue) {
console.log('正在进行操作队列回放操作,如需行任何Nasl操作请刷新页面');
return;
}
taskQueue.addTask(options);
}
// 确保操作非高危
function ensureSafe(instructList) {
const riskList = [];
instructList?.forEach((instruct) => {
if (instruct?.riskList) {
riskList.push(...(instruct.riskList || []));
}
instruct?.actions?.forEach((actionItem) => {
const { path, action, object } = actionItem || {};
if (path === 'app') {
switch (action) {
case 'create':
riskList.push('重复创建App');
break;
case 'delete':
riskList.push('删除App');
break;
case 'update':
if (object?.hasOwnProperty('id')) {
riskList.push('修改AppId');
}
break;
}
}
});
});
return riskList;
}
async function _saveNasl(options) {
const { app, hasFrontEnd, hasBackEnd, instructList, operationList } = options;
app.emit('saving');
let ChangedNASLType = '';
if (hasFrontEnd && hasBackEnd) {
ChangedNASLType = 'both';
}
else if (hasFrontEnd) {
ChangedNASLType = 'web';
}
else if (hasBackEnd) {
ChangedNASLType = 'backend';
}
let res;
let err;
if (config_1.config.storage.protocol === 'http') {
try {
const riskList = ensureSafe(instructList);
if (Array.isArray(riskList) && riskList.length) {
throw Error(`存在以下高危操作:\n${riskList.map((risk, index) => {
return `${index + 1}.${risk};`;
}).join('\n')}\n请及时排查原因`);
}
const transformedInstructList = (0, utils_1.splitInstructList)(instructList);
// 接口请求
res = await service_1.default.batchInstruct({
body: transformedInstructList,
headers: {
appId: app.id,
ChangedNASLType,
tabTimestamp,
// 其他封装在 storageService 里了
},
});
if (res?.globalChangedTime) {
setGlobalChangedTime(res.globalChangedTime);
}
}
catch (error) {
err = error;
console.error(err);
}
const { code, msg, result, success } = err || {};
operationList.forEach((operation) => {
(0, operation_1.doOperationRecord)({
...operation,
res,
err: {
code,
msg,
result,
success,
},
});
});
if (res) {
await (0, instruct_ruler_1.assertBatchInstruct)(operationList, {
app,
naslServer: app.naslServer,
instructStatus: res,
});
}
}
else if (config_1.config.storage.protocol === 'mock') {
// Do nothing
}
else {
const json = await fs.readJSON(config_1.config.storage.basePath);
let actionList = [];
for (const { actions } of instructList) {
actionList = actionList.concat(actions);
}
jsoner.batchAction(json, actionList);
await fs.writeJSON(config_1.config.storage.basePath, json, {
spaces: 4,
});
}
app.emit?.('saved', err);
globalBatchInstructResult = res;
return err;
}
function handleApp(app) {
app._isCollectingCount = 0;
app._collectingList = [];
app._historyList = [];
app._historyIndex = 0;
app._historyName = 'IDE';
app._historying = false;
app._timer = null;
app.on('undo', function () {
const proxyApp = this;
proxyApp._historying = true;
});
app.on('redo', function () {
const proxyApp = this;
proxyApp._historying = true;
});
/**
* 开启收集
*/
app.on('collect:start', function (event) {
const proxyApp = this;
if (proxyApp._timer) {
clearTimeout(proxyApp._timer);
}
if (!proxyApp._noTimer) {
proxyApp._noTimer = event.noTimer;
}
if (!proxyApp._noTimer) {
proxyApp._timer = setTimeout(() => {
console.error('收集超时,请及时排查原因', event);
const { action, actionMsg, sourceType, extra } = event;
const collectingList = proxyApp._collectingList.map((item) => {
const { path, action, object, oldObject, parentKey, index } = item?.originEvent || {};
return {
path,
action,
parentKey,
index,
object: object?.toJSON?.() || object?.__v_raw || object,
oldObject: oldObject?.toJSON?.() || oldObject?.__v_raw || oldObject,
};
});
taskQueue.refreshNasl(proxyApp, {
errorInfo: {
message: '收集超时,请及时排查原因',
event: {
action,
actionMsg,
sourceType,
extra,
collectingList,
},
},
});
}, 2500);
}
if (event?.action === 'move')
proxyApp._action = event.action;
if (!proxyApp._isCollectingCount) {
proxyApp._actionMsg = event?.actionMsg;
proxyApp._sourceType = event?.sourceType;
proxyApp._action = event?.action;
proxyApp._extra = event?.extra;
}
proxyApp._isCollectingCount++;
});
/**
* 结束收集
*/
app.on('collect:end', async function () {
const proxyApp = this;
proxyApp._isCollectingCount--;
if (proxyApp._isCollectingCount === 0) {
if (proxyApp._timer) {
clearTimeout(proxyApp._timer);
}
// 结束收集时,立即将全局收集变量状态重置,防止下次收集数据混乱
const collectingList = proxyApp._collectingList;
const actionMsg = proxyApp._actionMsg;
const sourceType = proxyApp._sourceType || 'manual';
const action = proxyApp._action;
const extra = proxyApp._extra;
proxyApp._collectingList = [];
proxyApp._actionMsg = '';
proxyApp._sourceType = undefined;
proxyApp._action = '';
proxyApp._noTimer = undefined;
if (Array.isArray(collectingList) && collectingList.length) {
mountDatabaseTypes(proxyApp);
proxyApp.naslServer?.embeddedTSEmitter?.emit('change', {
value: collectingList,
});
await doAction(proxyApp, {
list: collectingList,
actionMsg,
sourceType,
action,
extra
});
}
}
else if (proxyApp._isCollectingCount < 0) {
proxyApp._isCollectingCount = 0;
throw Error('关闭收集有问题,请排查');
}
});
/**
* 有变更
*/
app.on('storage', function (event) {
const proxyApp = this;
// 是否正在收集
if (proxyApp._isCollectingCount === 0) {
proxyApp.naslServer?.embeddedTSEmitter?.emit('change', {
value: [event],
});
const { action, target } = event.originEvent || {};
const { concept } = target || {};
const name = concept === 'EnumItem' ? target?.value : target?.name;
const actionMap = {
create: '新增',
delete: '删除',
update: '修改',
};
const actionText = actionMap[action] || '';
const nodeTitle = (0, concepts_1.getConceptConstructor)(concept).nodeTitle;
let actionMsg = name ?
`${actionText}${nodeTitle},${nodeTitle}名称:${name}` :
`${actionText}${nodeTitle}`;
if (['Param', 'Variable', 'Return'].includes(concept)) {
if (action === 'delete') {
if (target?.parentNode?.name) {
const parentNodeTitle = (0, concepts_1.getConceptConstructor)(target.parentNode?.concept).nodeTitle;
actionMsg += `,${parentNodeTitle}:${target.parentNode?.name}`;
}
}
}
doAction(proxyApp, {
list: [event],
actionMsg,
});
}
else {
proxyApp._collectingList.push(event);
}
});
}
exports.handleApp = handleApp;
async function getBatchAdditionalData(appId) {
const { data: { result: [globalChangedTime] }, headers } = await service_1.default.batchQuery({
body: [
{
path: 'app.globalChangedTime',
},
],
headers: {
appId,
checkTabOpenTime: 'true',
},
config: {
shortResponse: false
},
});
return {
globalChangedTime,
preGlobalChangedTime: 0,
tabTimestamp: headers['tabtimestamp'],
};
}
async function getNaslFromServer(appId) {
const [naslData] = await service_1.default.batchQuery({
body: [
{
path: 'app',
},
],
headers: {
appId,
},
});
return naslData;
}
async function getNaslIncrQueryServer(appId, body) {
const res = await service_1.default.incrQuery({
body,
headers: {
appId,
},
});
return res;
}
async function getNaslIncrQuery(appId) {
console.time('读取本地nasl数据库');
const allData = await window?.$IndexedDBHelper.getAllData();
console.timeEnd('读取本地nasl数据库');
const pathTimestampMap = {};
allData.forEach((item) => {
const globalChangedKey = 'appGlobalChanged';
if ([globalChangedKey].includes(item.nodePath))
return;
pathTimestampMap[item.nodePath] = item._changedTime;
});
// 拿到所有的文件和时间, 去请求服务端,
const { incrNasls, appTree, fullNasl } = await getNaslIncrQueryServer(appId, { pathTimestampMap });
// 如果返回了全量 nasl。基本上就是服务端或者 nasl 有错误了,本次直接清除缓存,走全量,下次进来完全请求缓存数据,重新注入
if (fullNasl) {
window?.$IndexedDBHelper.clear();
return { fullNasl };
}
// 增量 nasl 需要排序组装
const naslData = await (0, cache_1.updateCheckNaslCache)(allData, incrNasls);
return { naslAllData: naslData, appTree };
}
exports.getNaslIncrQuery = getNaslIncrQuery;
async function getNaslFromCache(appId) {
const { naslAllData, appTree, fullNasl } = await getNaslIncrQuery(appId);
// 有全量 nasl 直接返回,不需要组装了
if (fullNasl) {
return fullNasl;
}
const appJson = (0, cache_1.getAppTree)(naslAllData, appTree);
return appJson;
}
let getStore = () => {
return {};
};
const setStore = (store) => {
getStore = () => {
return store;
};
};
exports.setStore = setStore;
/**
* 加载 app
* @param appId 如果是从文件读,就不需要传
* @returns app 对象
*/
async function loadApp(appId, needEqual = false) {
try {
// 删除失效数据
(0, operation_1.deleteExpiredRecords)();
(0, taskQueue_1.deleteExpiredTaskQueueRecords)();
}
catch (err) { }
let app;
if (config_1.config.storage.protocol === 'http') {
console.time('batchQuery');
/**
* 1. 判断是否需要读缓存,
* 本地是否有缓存
* 2. 如果有,就拿本地数据库的内容出来, 把更新的时间取出来, 传给服务端,获取增量 nasl
* 3. 返回差异的地方, 更新局部 nasl ,同步本地数据库, 合并 nasl,
*/
const additionalData = await getBatchAdditionalData(appId);
if (additionalData?.globalChangedTime) {
setGlobalChangedTime(additionalData.globalChangedTime);
}
let batchQueryRes = null;
try {
batchQueryRes = await getNaslFromCache(appId);
if (needEqual) {
function deepEqual(obj1, obj2, path = '') {
if (obj1 === obj2) {
return true;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
console.dir(`Different number of keys at path: ${path}`);
return false;
}
for (let key of keys1) {
if (!keys2.includes(key)) {
console.dir(`Key ${key} not found in both objects at path: ${path}`);
return false;
}
const newPath = path ? `${path}.${key}` : key;
if (!deepEqual(obj1[key], obj2[key], newPath)) {
return false;
}
}
return true;
}
// 先在请求下服务端,回来的数据做一个比较看看是不是完全一致
const newData = await getNaslFromServer(appId);
const currentJSON = batchQueryRes;
const apiJSON = newData;
console.dir({ currentJSON, apiJSON });
if (!deepEqual(currentJSON, apiJSON)) {
console.dir('命中 Nasl 前端缓存,但是和服务端数据不一致');
}
else {
console.dir('命中 Nasl 前端缓存,和服务端数据一致');
}
}
}
catch (error) {
console.dir(error, '请求全量服务端');
// 如果有错误,就清除掉缓存;
try {
window?.$IndexedDBHelper.clear();
}
catch (err1) {
console.dir(err1, '清除缓存失败');
}
batchQueryRes = await getNaslFromServer(appId);
}
tabTimestamp = additionalData.tabTimestamp;
console.time('new App');
app = new concepts_1.App(Object.assign(batchQueryRes, { id: appId }));
app.frontendTypes.forEach(ft => {
if (!ft.frameworkKind) {
ft.frameworkKind = 'vue2';
}
});
await (0, instruct_ruler_1.assertIdeInitialize)({
app,
instructStatus: additionalData,
});
(async () => {
const breakpointRes = await service_1.default.breakpoint({
body: { appId }
});
breakpointRes?.forEach?.((item) => {
const { path, breakpointStatus } = item;
if (breakpointStatus) {
const node = app.findNodeByPath(path);
if (node) {
node.breakpoint = breakpointStatus;
}
}
});
})();
console.timeEnd('new App');
if (!app.id) {
app.id = appId;
}
if (process.env.NODE_ENV !== 'testing') {
(0, breakpoint_1.addBreakpointNodesFromApp)(app);
}
}
else if (config_1.config.storage.protocol === 'mock') {
app = new concepts_1.App({
id: appId,
concept: 'App',
name: 'devapp',
title: '开发应用',
envs: 'dev,online',
hasAuth: true,
hasUserCenter: true,
dataSources: [],
structures: [],
enums: [],
logics: [],
interfaces: [],
processes: [],
roles: [],
dependencies: [],
interfaceDependencies: [],
frontendTypes: [],
metadataTypes: []
});
}
else {
const json = await fs.readJSON(config_1.config.storage.basePath);
app = new concepts_1.App(appId ? Object.assign(json, { id: appId }) : json);
}
// config.scope = app.scope;
handleApp(app);
app._isCollectingCount = 0;
mountDatabaseTypes(app);
initializeCheckRules(app);
return app;
}
exports.loadApp = loadApp;
/**
* 初始化检查规则
* @param app
*/
function initializeCheckRules(app) {
const { createdIdeVersion, preferenceMap } = app;
const rulesToApply = getRulesForVersion(createdIdeVersion);
// 初始化 preferenceMap.checkRules 如果不存在
if (!preferenceMap.checkRules) {
preferenceMap.checkRules = JSON.stringify({});
}
// 解析现有的 checkRules
const checkRulesObj = JSON.parse(preferenceMap.checkRules || '{}');
// 对于每个规则,如果不在 checkRulesObj 中,则添加并设置为 true
rulesToApply.forEach(rule => {
if (!(rule in checkRulesObj)) {
checkRulesObj[rule] = true;
}
});
// 将更新后的 checkRules 保存回 preferenceMap
preferenceMap.checkRules = JSON.stringify(checkRulesObj);
}
exports.initializeCheckRules = initializeCheckRules;
function getRulesForVersion(version) {
if (!version)
return [];
const versions = Object.keys(checkRules_1.checkRules).sort((a, b) => (0, compare_versions_1.default)(a, b));
if (versions.length === 0)
return [];
// 如果版本小于最小版本,不应用规则
if ((0, compare_versions_1.default)(version, versions[0]) < 0)
return [];
// 如果版本大于最大版本,使用最大版本的规则
if ((0, compare_versions_1.default)(version, versions[versions.length - 1]) > 0) {
return checkRules_1.checkRules[versions[versions.length - 1]];
}
// 找到第一个大于等于当前版本的版本
for (let i = 0; i < versions.length; i++) {
if ((0, compare_versions_1.default)(version, versions[i]) <= 0) {
return checkRules_1.checkRules[versions[i]];
}
}
return [];
}
// 在app上挂载所有的数据库类型
async function mountDatabaseTypes(app) {
if (!app.__databaseTypeMap) {
app.__databaseTypeMap = {};
}
const dataSources = app?.dataSources;
const dbTypeSet = new Set();
if (Array.isArray(dataSources)) {
dataSources.forEach((dataSource) => {
const dataSourceSqlType = JSON.parse(dataSource.dataSourceConfig?.devProperty?.value || '{}')?.type;
if (dataSourceSqlType && !app.__databaseTypeMap[dataSourceSqlType]) {
dbTypeSet.add(dataSourceSqlType);
}
});
}
const dbTypes = [...dbTypeSet];
if (Array.isArray(dbTypes) && dbTypes.length) {
const res = await (0, exports.databaseTypes)({
query: {
dbTypes: dbTypes.join(','),
},
});
if (Array.isArray(res)) {
res.forEach((databaseType) => {
const { dbType, columnTypes } = databaseType || {};
const columnTypeMap = {};
if (Array.isArray(columnTypes)) {
columnTypes.forEach((columnType) => {
cons