@lcap/nasl
Version:
NetEase Application Specific Language
1,312 lines • 51.4 kB
JavaScript
;
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.changeTabTimestamp = exports.loadAppSync = exports.mountDatabaseTypes = exports.loadApp = exports.setStore = exports.handleApp = exports.taskQueue = exports.databaseTypes = exports.aiCoachActionRecord = exports.breakpoint = 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"));
/// #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 cache_1 = require("./cache");
exports.batchQuery = service_1.default.batchQuery;
exports.batchAction = service_1.default.batchAction;
exports.batchInstruct = service_1.default.batchInstruct;
exports.breakpoint = service_1.default.breakpoint;
exports.aiCoachActionRecord = service_1.default.aiCoachActionRecord;
let globalBatchInstructResult = null;
let tabTimestamp;
exports.databaseTypes = service_1.default.databaseTypes;
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) => {
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);
exports.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;
}
addTask(task) {
this.queue.push(task);
if (this.queue.length >= this.maxTaskCount) {
this.status = types_1.TaskQueueStatus.ExceedMaxTaskCount;
task.app.emit('ExceedMaxTaskCount');
}
this.run();
}
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
});
// code: 401650, 检测到当前应用拉取操作已被强制结束, 用户确认后再刷新
// code: 500502 msg: '该应用已在新tab 页打开,请刷新!' 不重试或刷新
// code: 500505 需要刷新浏览器
if (!error)
this.recover(app);
else if (![401650, 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);
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,
}));
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;
}
}
}
exports.taskQueue = new TaskQueue();
async function saveNasl(options) {
exports.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)((0, utils_1.setChangedTimeForInstruct)(instructList));
// 接口请求
res = await service_1.default.batchInstruct({
body: transformedInstructList,
headers: {
appId: app.id,
ChangedNASLType,
tabTimestamp,
// 其他封装在 storageService 里了
},
});
}
catch (error) {
err = error;
console.error(err);
}
try {
(0, cache_1.update)({ res, err, instructList, operationList, app });
}
catch (err) {
console.log(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);
exports.taskQueue.refreshNasl(proxyApp, {
errorInfo: {
message: '收集超时,请及时排查原因',
},
});
}, 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],
});
let actionMsg = '';
const { action, target } = event.originEvent || {};
const { concept, name } = target || {};
switch (action) {
case 'create':
actionMsg = '添加';
break;
case 'delete':
actionMsg = '删除';
break;
case 'update':
actionMsg = '修改';
break;
}
actionMsg += (0, concepts_1.getConceptConstructor)(concept).nodeTitle;
if (name) {
actionMsg += `"${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 getNaslFromCache(appId) {
console.time('读取本地nasl数据库');
const allData = await window?.$IndexedDBHelper.getAllData();
console.timeEnd('读取本地nasl数据库');
const pathTimestampMap = {};
const currentDate = Date.now();
allData.forEach((item) => {
const globalChangedKey = 'appGlobalChanged';
if ([globalChangedKey].includes(item.nodePath))
return;
if (item._changedTime) {
pathTimestampMap[item.nodePath] = item._changedTime;
}
else {
// 没有就传当前时间,服务端会比较时间不一致,返回更新
pathTimestampMap[item.nodePath] = currentDate;
console.warn('没有_changedTime', item, item.nodePath);
}
});
// 拿到所有的文件和时间, 去请求服务端,
const { type, incrNasls, fullNasl, appTree } = await getNaslIncrQueryServer(appId, { pathTimestampMap });
if (type === 'full') {
console.dir('差距太大,返回全部数据,不去命中 Nasl 前端缓存');
return { type, naslData: fullNasl };
}
const naslData = await (0, cache_1.getNaslCacheWithoutCheck)(allData, incrNasls, appTree);
return { type, naslData };
}
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)();
}
catch (err) { }
let app;
if (config_1.config.storage.protocol === 'http') {
console.time('batchQuery');
/**
* 1. 获取到 服务端的更新的时间戳
* 2. 判断是否需要读缓存,
* 本地有缓存, 而且本地的时间 和 服务端一致, 而且,没有本地操作需要重新请求的标识
* 3. 如果有,就拿本地数据库的内容出来, 传给服务端,获取增量 nasl
* 返回差异的地方, 更新局部 nasl ,合并 nasl,同步本地数据库
* 如果差异过大,服务端就会返回全量 nasl,同步本地数据库
* 4. 如果没有,就请求全量 nasl。 ,同步本地数据库
*/
const additionalData = await getBatchAdditionalData(appId);
const useCache = await (0, cache_1.canUseNaslCache)(additionalData.globalChangedTime);
let batchQueryRes = null;
let needAllUpdateCache = false;
if (useCache) {
const cacheData = await getNaslFromCache(appId);
batchQueryRes = cacheData.naslData;
// 全量的需要后续拆分一下更新缓存,其余的都在热更新中处理过了
if (cacheData.type === 'full') {
needAllUpdateCache = true;
}
console.dir('命中 Nasl 前端缓存');
if (needEqual) {
function deepEqual(obj1, obj2, path = '') {
if (obj1 === obj2) {
return true;
}
// 上班 qingyan 整的 上班逻辑, 直接把 原始 store 中的 app 给赋值 改了,字符串变成了 boolean ,又跟服务端不一致,先过滤掉
if (path === 'preferenceMap.metadataTypeEnable' && obj1 + '' === obj2 + '') {
return true;
}
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
console.dir(`Difference found at path: ${path}`);
return false;
}
if (typeof obj2 === 'object' && typeof obj1 === 'object' && Object.keys(obj1 || {}).length && Object.keys(obj2 || {}).length) {
['_changedTime', 'changedTime'].forEach((key) => {
delete obj1[key];
delete obj2[key];
});
Object.keys(obj1).forEach(key => {
if (obj1[key] === undefined || obj1 === null) {
delete obj1[key];
}
});
Object.keys(obj2).forEach(key => {
if (obj2[key] === undefined || obj2 === null) {
delete obj2[key];
}
});
}
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 = new concepts_1.App(batchQueryRes).toJSON();
const apiJSON = new concepts_1.App(newData).toJSON();
console.dir({ currentJSON, apiJSON });
if (!deepEqual(currentJSON, apiJSON)) {
console.dir('命中 Nasl 前端缓存,但是和服务端数据不一致');
}
else {
console.dir('命中 Nasl 前端缓存,和服务端数据一致');
}
}
}
else {
console.dir('未命中 Nasl 前端缓存');
batchQueryRes = await getNaslFromServer(appId);
needAllUpdateCache = true;
}
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;
}
}
});
})();
// 在 new 完 app 之后,在去更新
if (needAllUpdateCache) {
(0, cache_1.updateNaslCache)(batchQueryRes, additionalData.globalChangedTime);
}
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);
}
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) => {
const { naslType, dataBaseTypes } = columnType;
const dataBaseTypeMap = {};
let defaultDataBaseType;
if (Array.isArray(dataBaseTypes)) {
dataBaseTypes.forEach((dataBaseType) => {
const { type } = dataBaseType || {};
if (type) {
dataBaseTypeMap[type] = dataBaseType;
const { defaultType } = dataBaseType;
if (defaultType) {
defaultDataBaseType = dataBaseType;
}
}
});
}
columnTypeMap[naslType] = {
dataBaseTypes,
dataBaseTypeMap,
defaultDataBaseType,
};
});
}
app.__databaseTypeMap[dbType] = columnTypeMap;
});
}
}
if (Array.isArray(dataSources)) {
dataSources.forEach((dataSource) => {
dataSource.__hasDatabaseConfig = !!(app.__databaseTypeMap?.[dataSource.dataSourceSqlType]);
});
}
}
exports.mountDatabaseTypes = mountDatabaseTypes;
/**
* 加载 app
* @returns app 对象
*/
function loadAppSync() {
const json = fs.readJSONSync(config_1.config.storage.basePath);
const app = new concepts_1.App(json);
handleApp(app);
return app;
}
exports.loadAppSync = loadAppSync;
async function changeTabTimestamp(appId) {
const additionalData = await getBatchAdditionalData(appId);
tabTimestamp = additionalData.tabTimestamp;
}
exports.changeTabTimestamp = changeTabTimestamp;
//# sourceMappingURL=init.js.map