@maxisoft/instantcms-mcp 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/__tests__/artifact-tool.test.js +196 -0
- package/dist/__tests__/db-tool.test.js +152 -0
- package/dist/__tests__/define-tool.test.js +106 -0
- package/dist/__tests__/find-tool.test.js +185 -0
- package/dist/__tests__/get-component-api.test.js +144 -0
- package/dist/__tests__/hardening.test.js +73 -0
- package/dist/__tests__/hooks-tool.test.js +173 -0
- package/dist/__tests__/mcp-integration-extra.test.js +166 -0
- package/dist/__tests__/mcp-integration.test.js +53 -0
- package/dist/__tests__/pagination.test.js +114 -0
- package/dist/__tests__/performance-baseline.test.js +56 -0
- package/dist/__tests__/project-patch.test.js +144 -0
- package/dist/__tests__/project-source.test.js +44 -0
- package/dist/__tests__/project-workflows-extra.test.js +165 -0
- package/dist/__tests__/project-workflows.test.js +51 -0
- package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
- package/dist/__tests__/serialization.test.js +170 -0
- package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
- package/dist/__tests__/template-development.test.js +78 -0
- package/dist/__tests__/template-productivity.test.js +87 -0
- package/dist/__tests__/tools.test.js +2143 -0
- package/dist/data/components.js +2818 -0
- package/dist/data/controllers-map.js +7571 -0
- package/dist/data/core-api.js +9356 -0
- package/dist/data/database-schema.js +4555 -0
- package/dist/data/events-map.js +1339 -0
- package/dist/data/fields-map.js +3692 -0
- package/dist/data/hooks.js +2257 -0
- package/dist/data/js-api.js +123 -0
- package/dist/data/libs-api.js +264 -0
- package/dist/data/routes-map.js +203 -0
- package/dist/data/schemas-validation.js +255 -0
- package/dist/data/schemas.js +1404 -0
- package/dist/data/traits-map.js +1004 -0
- package/dist/data/version-profiles.js +41 -0
- package/dist/data/widgets-map.js +131 -0
- package/dist/data/wysiwyg-map.js +486 -0
- package/dist/generated/components-source.js +14748 -0
- package/dist/generated/hooks-source.js +3399 -0
- package/dist/generated/knowledge-meta.js +101 -0
- package/dist/index.js +13 -0
- package/dist/registry/database-tools.js +112 -0
- package/dist/registry/extension-tools.js +473 -0
- package/dist/registry/generator-tools.js +506 -0
- package/dist/registry/knowledge-tools.js +441 -0
- package/dist/registry/language-tools.js +99 -0
- package/dist/registry/meta-tools.js +152 -0
- package/dist/registry/project-tools.js +48 -0
- package/dist/registry/resources.js +98 -0
- package/dist/registry/source-tools.js +211 -0
- package/dist/registry/template-development-tools.js +79 -0
- package/dist/server.js +32 -0
- package/dist/tools/addon-tool.js +1437 -0
- package/dist/tools/admin-partial-tool.js +498 -0
- package/dist/tools/api-tool.js +294 -0
- package/dist/tools/artifact-tool.js +95 -0
- package/dist/tools/cache-tool.js +360 -0
- package/dist/tools/component-tool.js +415 -0
- package/dist/tools/controllers-tool.js +125 -0
- package/dist/tools/cron-tool.js +197 -0
- package/dist/tools/crud-tool.js +659 -0
- package/dist/tools/db-tool.js +198 -0
- package/dist/tools/email-tool.js +222 -0
- package/dist/tools/external-api-tool.js +596 -0
- package/dist/tools/filter-tool.js +341 -0
- package/dist/tools/form-tool.js +275 -0
- package/dist/tools/grid-tool.js +189 -0
- package/dist/tools/hooks-tool.js +104 -0
- package/dist/tools/import-export-tool.js +548 -0
- package/dist/tools/lang-tool.js +251 -0
- package/dist/tools/layout-override-tool.js +125 -0
- package/dist/tools/layout-tool.js +548 -0
- package/dist/tools/maria-tool.js +127 -0
- package/dist/tools/mariadb.js +201 -0
- package/dist/tools/migration-tool.js +329 -0
- package/dist/tools/oauth-tool.js +520 -0
- package/dist/tools/parser/components-parser.js +76 -0
- package/dist/tools/parser/controllers-parser.js +313 -0
- package/dist/tools/parser/core-parser.js +294 -0
- package/dist/tools/parser/coverage-generator.js +424 -0
- package/dist/tools/parser/events-parser.js +127 -0
- package/dist/tools/parser/fields-parser.js +382 -0
- package/dist/tools/parser/hooks-parser.js +106 -0
- package/dist/tools/parser/sql-parser.js +197 -0
- package/dist/tools/parser/traits-parser.js +161 -0
- package/dist/tools/parser/widgets-parser.js +150 -0
- package/dist/tools/permission-tool.js +348 -0
- package/dist/tools/project-patch-tool.js +73 -0
- package/dist/tools/project-source-tool.js +188 -0
- package/dist/tools/project-workflow-tool.js +186 -0
- package/dist/tools/requirement-tool.js +212 -0
- package/dist/tools/scaffold-tool.js +815 -0
- package/dist/tools/seo-tool.js +412 -0
- package/dist/tools/source-tool.js +155 -0
- package/dist/tools/template-development-tool.js +271 -0
- package/dist/tools/template-overrides-tool.js +294 -0
- package/dist/tools/template-productivity-tool.js +319 -0
- package/dist/tools/template-tool.js +665 -0
- package/dist/tools/test-tool.js +183 -0
- package/dist/tools/webhook-tool.js +427 -0
- package/dist/tools/widget-tool.js +331 -0
- package/dist/tools/wysiwyg-tool.js +137 -0
- package/dist/types/scaffold.js +68 -0
- package/dist/utils/define-tool.js +37 -0
- package/dist/utils/find-tool.js +97 -0
- package/dist/utils/mcp-result.js +18 -0
- package/dist/utils/pagination.js +27 -0
- package/dist/utils/serialization.js +27 -0
- package/package.json +94 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getPool = getPool;
|
|
37
|
+
exports.executeQuery = executeQuery;
|
|
38
|
+
exports.getTableInfo = getTableInfo;
|
|
39
|
+
exports.listTables = listTables;
|
|
40
|
+
exports.getDatabaseInfo = getDatabaseInfo;
|
|
41
|
+
exports.describeTable = describeTable;
|
|
42
|
+
exports.showIndexes = showIndexes;
|
|
43
|
+
exports.closePool = closePool;
|
|
44
|
+
const mysql = __importStar(require("mysql2/promise"));
|
|
45
|
+
let pool = null;
|
|
46
|
+
function getConnection() {
|
|
47
|
+
const host = process.env.DB_HOST || 'localhost';
|
|
48
|
+
const port = parseInt(process.env.DB_PORT || '3306', 10);
|
|
49
|
+
const user = process.env.DB_USER || 'root';
|
|
50
|
+
const password = process.env.DB_PASSWORD || '';
|
|
51
|
+
const database = process.env.DB_DATABASE || 'instantcms';
|
|
52
|
+
return { host, port, user, password, database };
|
|
53
|
+
}
|
|
54
|
+
function getPool() {
|
|
55
|
+
if (!pool) {
|
|
56
|
+
const config = getConnection();
|
|
57
|
+
pool = mysql.createPool({
|
|
58
|
+
host: config.host,
|
|
59
|
+
port: config.port,
|
|
60
|
+
user: config.user,
|
|
61
|
+
password: config.password,
|
|
62
|
+
database: config.database,
|
|
63
|
+
waitForConnections: true,
|
|
64
|
+
connectionLimit: 5,
|
|
65
|
+
queueLimit: 0
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return pool;
|
|
69
|
+
}
|
|
70
|
+
async function executeQuery(sql) {
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
const [rows, fields] = await getPool().execute(sql);
|
|
74
|
+
const executionTime = Date.now() - startTime;
|
|
75
|
+
const columns = Array.isArray(fields) ? fields.map((f) => f.name) : [];
|
|
76
|
+
const rowCount = Array.isArray(rows) ? rows.length : 0;
|
|
77
|
+
return {
|
|
78
|
+
columns,
|
|
79
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
80
|
+
rowCount,
|
|
81
|
+
query: sql,
|
|
82
|
+
executionTime
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const executionTime = Date.now() - startTime;
|
|
87
|
+
return {
|
|
88
|
+
columns: [],
|
|
89
|
+
rows: [],
|
|
90
|
+
rowCount: 0,
|
|
91
|
+
query: sql,
|
|
92
|
+
executionTime,
|
|
93
|
+
error: error instanceof Error ? error.message : String(error)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function getTableInfo(tableName) {
|
|
98
|
+
const [columnsResult, indexesResult, countResult] = await Promise.all([
|
|
99
|
+
executeQuery(`
|
|
100
|
+
SELECT
|
|
101
|
+
COLUMN_NAME as name,
|
|
102
|
+
COLUMN_TYPE as type,
|
|
103
|
+
IS_NULLABLE as nullable,
|
|
104
|
+
COLUMN_KEY as \`key\`,
|
|
105
|
+
COLUMN_DEFAULT as \`default\`,
|
|
106
|
+
EXTRA as extra,
|
|
107
|
+
COLUMN_COMMENT as comment
|
|
108
|
+
FROM information_schema.COLUMNS
|
|
109
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
|
|
110
|
+
`),
|
|
111
|
+
executeQuery(`
|
|
112
|
+
SELECT
|
|
113
|
+
INDEX_NAME as name,
|
|
114
|
+
INDEX_TYPE as type,
|
|
115
|
+
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
|
|
116
|
+
NON_UNIQUE as non_unique
|
|
117
|
+
FROM information_schema.STATISTICS
|
|
118
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
|
|
119
|
+
GROUP BY INDEX_NAME, INDEX_TYPE, NON_UNIQUE
|
|
120
|
+
`),
|
|
121
|
+
executeQuery(`SELECT COUNT(*) as cnt FROM \`${tableName}\``)
|
|
122
|
+
]);
|
|
123
|
+
if (columnsResult.error || columnsResult.rows.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
name: tableName,
|
|
128
|
+
comment: null,
|
|
129
|
+
columns: columnsResult.rows.map(row => ({
|
|
130
|
+
name: String(row.name),
|
|
131
|
+
type: String(row.type),
|
|
132
|
+
nullable: row.nullable === 'YES',
|
|
133
|
+
key: row.key ? String(row.key) : null,
|
|
134
|
+
default: row.default,
|
|
135
|
+
extra: row.extra,
|
|
136
|
+
comment: row.comment
|
|
137
|
+
})),
|
|
138
|
+
indexes: indexesResult.rows.map(row => ({
|
|
139
|
+
name: String(row.name),
|
|
140
|
+
type: row.type ? String(row.type) : null,
|
|
141
|
+
columns: String(row.columns).split(','),
|
|
142
|
+
unique: row.non_unique === 0
|
|
143
|
+
})),
|
|
144
|
+
rowCount: countResult.rows[0]?.cnt
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function listTables() {
|
|
148
|
+
const result = await executeQuery(`
|
|
149
|
+
SELECT TABLE_NAME
|
|
150
|
+
FROM information_schema.TABLES
|
|
151
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
152
|
+
AND TABLE_TYPE = 'BASE TABLE'
|
|
153
|
+
ORDER BY TABLE_NAME
|
|
154
|
+
`);
|
|
155
|
+
return result.rows.map(row => String(row.TABLE_NAME));
|
|
156
|
+
}
|
|
157
|
+
async function getDatabaseInfo() {
|
|
158
|
+
const result = await executeQuery(`
|
|
159
|
+
SELECT
|
|
160
|
+
TABLE_NAME,
|
|
161
|
+
TABLE_ROWS,
|
|
162
|
+
ROUND(DATA_LENGTH / 1024 / 1024, 2) as data_mb,
|
|
163
|
+
ROUND(INDEX_LENGTH / 1024 / 1024, 2) as index_mb
|
|
164
|
+
FROM information_schema.TABLES
|
|
165
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
166
|
+
AND TABLE_TYPE = 'BASE TABLE'
|
|
167
|
+
`);
|
|
168
|
+
const tablesCount = result.rows.length;
|
|
169
|
+
let totalRows = 0;
|
|
170
|
+
let totalData = 0;
|
|
171
|
+
let totalIndex = 0;
|
|
172
|
+
for (const row of result.rows) {
|
|
173
|
+
totalRows += Number(row.TABLE_ROWS) || 0;
|
|
174
|
+
totalData += Number(row.data_mb) || 0;
|
|
175
|
+
totalIndex += Number(row.index_mb) || 0;
|
|
176
|
+
}
|
|
177
|
+
const dbResult = await executeQuery('SELECT DATABASE() as db_name');
|
|
178
|
+
const dbName = String(dbResult.rows[0]?.db_name || 'unknown');
|
|
179
|
+
return {
|
|
180
|
+
name: dbName,
|
|
181
|
+
tablesCount,
|
|
182
|
+
totalRows,
|
|
183
|
+
size: {
|
|
184
|
+
data: totalData,
|
|
185
|
+
index: totalIndex,
|
|
186
|
+
total: totalData + totalIndex
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async function describeTable(tableName) {
|
|
191
|
+
return executeQuery(`DESCRIBE \`${tableName}\``);
|
|
192
|
+
}
|
|
193
|
+
async function showIndexes(tableName) {
|
|
194
|
+
return executeQuery(`SHOW INDEX FROM \`${tableName}\``);
|
|
195
|
+
}
|
|
196
|
+
async function closePool() {
|
|
197
|
+
if (pool) {
|
|
198
|
+
await pool.end();
|
|
199
|
+
pool = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateMigration = generateMigration;
|
|
4
|
+
exports.scaffoldMigration = scaffoldMigration;
|
|
5
|
+
exports.generateFieldSuggestions = generateFieldSuggestions;
|
|
6
|
+
function generateMigration(name, fields, options) {
|
|
7
|
+
const tableName = `cms_${name}`;
|
|
8
|
+
const className = name
|
|
9
|
+
.split('_')
|
|
10
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
11
|
+
.join('');
|
|
12
|
+
const fieldLines = fields.map(f => {
|
|
13
|
+
let line = ` \`${f.name}\` ${f.type}`;
|
|
14
|
+
if (f.nullable !== true) {
|
|
15
|
+
line += ' NOT NULL';
|
|
16
|
+
}
|
|
17
|
+
if (f.default !== undefined) {
|
|
18
|
+
if (f.default === 'NULL') {
|
|
19
|
+
line += ' DEFAULT NULL';
|
|
20
|
+
}
|
|
21
|
+
else if (typeof f.default === 'number') {
|
|
22
|
+
line += ` DEFAULT ${f.default}`;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
line += ` DEFAULT '${f.default}'`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (f.extra) {
|
|
29
|
+
line += ` ${f.extra}`;
|
|
30
|
+
}
|
|
31
|
+
if (f.comment) {
|
|
32
|
+
line += ` COMMENT '${f.comment}'`;
|
|
33
|
+
}
|
|
34
|
+
return line;
|
|
35
|
+
});
|
|
36
|
+
if (!fields.find(f => f.key === 'PRI')) {
|
|
37
|
+
fieldLines.push(' PRIMARY KEY (`id`)');
|
|
38
|
+
}
|
|
39
|
+
const indexes = [];
|
|
40
|
+
const seenKeys = new Set();
|
|
41
|
+
for (const f of fields) {
|
|
42
|
+
if (f.key && f.key !== 'PRI' && !seenKeys.has(f.key + '_' + f.name)) {
|
|
43
|
+
seenKeys.add(f.key + '_' + f.name);
|
|
44
|
+
if (f.key === 'UNI') {
|
|
45
|
+
indexes.push(` UNIQUE KEY \`${f.name}\` (\`${f.name}\`)`);
|
|
46
|
+
}
|
|
47
|
+
else if (f.key === 'MUL') {
|
|
48
|
+
indexes.push(` KEY \`${f.name}\` (\`${f.name}\`)`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options?.indexes) {
|
|
53
|
+
for (const idx of options.indexes) {
|
|
54
|
+
if (idx.type === 'UNIQUE') {
|
|
55
|
+
indexes.push(` UNIQUE KEY \`${idx.name}\` (\`${idx.fields.join('`, `')}\`)`);
|
|
56
|
+
}
|
|
57
|
+
else if (idx.type === 'FULLTEXT') {
|
|
58
|
+
indexes.push(` FULLTEXT KEY \`${idx.name}\` (\`${idx.fields.join('`, `')}\`)`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
indexes.push(` KEY \`${idx.name}\` (\`${idx.fields.join('`, `')}\`)`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const allLines = [...fieldLines, ...indexes];
|
|
66
|
+
const createTable = `CREATE TABLE \`${tableName}\` (
|
|
67
|
+
${allLines.join(',\n')}
|
|
68
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8${options?.comment ? ` COMMENT='${options.comment}'` : ''};`;
|
|
69
|
+
const installCode = `class installer${className} extends cmsInstaller {
|
|
70
|
+
|
|
71
|
+
public function install() {
|
|
72
|
+
|
|
73
|
+
$this->createTable('${name}', [
|
|
74
|
+
${fields
|
|
75
|
+
.map(f => {
|
|
76
|
+
const opts = [];
|
|
77
|
+
if (f.nullable)
|
|
78
|
+
opts.push("'null' => true");
|
|
79
|
+
if (f.default !== undefined)
|
|
80
|
+
opts.push(`'default' => '${f.default}'"`);
|
|
81
|
+
if (f.extra === 'AUTO_INCREMENT')
|
|
82
|
+
opts.push("'auto_increment' => true");
|
|
83
|
+
if (f.comment)
|
|
84
|
+
opts.push(`'comment' => '${f.comment}'"`);
|
|
85
|
+
return ` '${f.name}' => ['${f.type}'${opts.length > 0 ? ', ' + opts.join(', ') : ''}]`;
|
|
86
|
+
})
|
|
87
|
+
.join(',\n')}
|
|
88
|
+
]${options?.comment ? `, '${options.comment}'` : ''});
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public function uninstall() {
|
|
94
|
+
$this->dropTable('${name}');
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}`;
|
|
98
|
+
return {
|
|
99
|
+
table_name: tableName,
|
|
100
|
+
class_name: `installer${className}`,
|
|
101
|
+
sql: createTable,
|
|
102
|
+
install_php: installCode,
|
|
103
|
+
field_count: fields.length,
|
|
104
|
+
index_count: indexes.length,
|
|
105
|
+
conventions: {
|
|
106
|
+
table_name: `cms_${name}`,
|
|
107
|
+
model_class: `model${className}`,
|
|
108
|
+
controller_class: name,
|
|
109
|
+
manifest_example: `<files>
|
|
110
|
+
<file>system/controllers/${name}/</file>
|
|
111
|
+
</files>
|
|
112
|
+
<hooks>
|
|
113
|
+
<hook event="content_after_add_approve" />
|
|
114
|
+
</hooks>`,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function scaffoldMigration(params) {
|
|
119
|
+
const { addon_name, table_name, fields, options = {} } = params;
|
|
120
|
+
const tableName = `cms_${table_name}`;
|
|
121
|
+
const className = addon_name
|
|
122
|
+
.split('_')
|
|
123
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
124
|
+
.join('');
|
|
125
|
+
const installerClass = `installer${className}`;
|
|
126
|
+
// install.php
|
|
127
|
+
const installContent = `<?php
|
|
128
|
+
/**
|
|
129
|
+
* Установка дополнения ${addon_name}
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
class ${installerClass} extends cmsInstaller {
|
|
133
|
+
|
|
134
|
+
public function install() {
|
|
135
|
+
|
|
136
|
+
$this->addModule('${addon_name}', [
|
|
137
|
+
'title' => '${addon_name.toUpperCase()}',
|
|
138
|
+
'description' => '',
|
|
139
|
+
'version' => '1.0.0',
|
|
140
|
+
'author' => '',
|
|
141
|
+
'url' => ''
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
// Создание таблицы
|
|
145
|
+
$this->createTable('${table_name}', [
|
|
146
|
+
${fields
|
|
147
|
+
.map(f => {
|
|
148
|
+
const opts = [];
|
|
149
|
+
if (f.nullable)
|
|
150
|
+
opts.push("'null' => true");
|
|
151
|
+
if (f.default !== undefined && f.default !== 'NULL') {
|
|
152
|
+
const isNumber = typeof f.default === 'number' || /^\d+$/.test(String(f.default));
|
|
153
|
+
opts.push(`'default' => ${isNumber ? f.default : `'${f.default}'`}`);
|
|
154
|
+
}
|
|
155
|
+
else if (f.default === 'NULL') {
|
|
156
|
+
opts.push("'null' => true");
|
|
157
|
+
}
|
|
158
|
+
if (f.extra === 'AUTO_INCREMENT')
|
|
159
|
+
opts.push("'auto_increment' => true");
|
|
160
|
+
if (f.comment)
|
|
161
|
+
opts.push(`'comment' => '${f.comment}'"`);
|
|
162
|
+
return ` '${f.name}' => ['${f.type}'${opts.length > 0 ? ', ' + opts.join(', ') : ''}]`;
|
|
163
|
+
})
|
|
164
|
+
.join(',\n')}
|
|
165
|
+
]${options.comment ? `, '${options.comment}'` : ''});${options.indexes?.length
|
|
166
|
+
? `
|
|
167
|
+
// Индексы
|
|
168
|
+
${options.indexes
|
|
169
|
+
.map(idx => {
|
|
170
|
+
let sqlType = 'INDEX';
|
|
171
|
+
if (idx.type === 'UNIQUE') {
|
|
172
|
+
sqlType = 'UNIQUE INDEX';
|
|
173
|
+
}
|
|
174
|
+
else if (idx.type === 'FULLTEXT') {
|
|
175
|
+
sqlType = 'FULLTEXT INDEX';
|
|
176
|
+
}
|
|
177
|
+
const sql = `CREATE ${sqlType} ${idx.name} ON {${table_name}} (${idx.fields.join(', ')})`;
|
|
178
|
+
return ` \\$this->db->query("${sql}");`;
|
|
179
|
+
})
|
|
180
|
+
.join('\n')}`
|
|
181
|
+
: ''}${options.content_type
|
|
182
|
+
? `
|
|
183
|
+
|
|
184
|
+
// Регистрация типа контента
|
|
185
|
+
$this->registerContentType('${addon_name}', [
|
|
186
|
+
'title' => '${className}',
|
|
187
|
+
'description' => '',
|
|
188
|
+
'options' => ['flags' => ['is_fixed_url']],
|
|
189
|
+
'item_url_mask' => '/${addon_name}/view/{id}',
|
|
190
|
+
'templates' => [
|
|
191
|
+
'list' => '${addon_name}_list',
|
|
192
|
+
'item' => '${addon_name}_item',
|
|
193
|
+
'profile'=> ''
|
|
194
|
+
]
|
|
195
|
+
]);`
|
|
196
|
+
: ''}${options.has_seo
|
|
197
|
+
? `
|
|
198
|
+
|
|
199
|
+
// SEO настройки
|
|
200
|
+
$this->addSeoOptions('${addon_name}', [
|
|
201
|
+
'slug_default' => '${addon_name}',
|
|
202
|
+
'meta' => [
|
|
203
|
+
'title' => '{title}',
|
|
204
|
+
'description' => '{description}',
|
|
205
|
+
'keywords' => ''
|
|
206
|
+
]
|
|
207
|
+
]);`
|
|
208
|
+
: ''}
|
|
209
|
+
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public function uninstall($hard = false) {
|
|
214
|
+
|
|
215
|
+
if ($hard) {
|
|
216
|
+
$this->dropTable('${table_name}');${options.content_type
|
|
217
|
+
? `
|
|
218
|
+
$this->deleteContentType('${addon_name}');`
|
|
219
|
+
: ''}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
$this->removeModule('${addon_name}');
|
|
223
|
+
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}`;
|
|
227
|
+
// uninstall.php
|
|
228
|
+
const uninstallContent = `<?php
|
|
229
|
+
/**
|
|
230
|
+
* Удаление дополнения ${addon_name}
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
class ${installerClass.replace('installer', 'uninstall_') + installerClass.includes('installer') ? '' : 'uninstall_'} extends ${installerClass} {
|
|
234
|
+
|
|
235
|
+
public function uninstall() {
|
|
236
|
+
parent::uninstall(false);
|
|
237
|
+
}
|
|
238
|
+
}`;
|
|
239
|
+
// manifest.xml entries для install
|
|
240
|
+
const manifestEntry = `<files>
|
|
241
|
+
<file>system/controllers/${addon_name}/install.php</file>
|
|
242
|
+
</files>`;
|
|
243
|
+
return {
|
|
244
|
+
addon_name,
|
|
245
|
+
table_name: tableName,
|
|
246
|
+
files: {
|
|
247
|
+
'install.php': installContent,
|
|
248
|
+
'uninstall.php': uninstallContent,
|
|
249
|
+
},
|
|
250
|
+
manifest_xml: manifestEntry,
|
|
251
|
+
notes: {
|
|
252
|
+
'install.php': `Файл должен быть в: system/controllers/${addon_name}/install.php`,
|
|
253
|
+
'uninstall.php': `Файл должен быть в: system/controllers/${addon_name}/uninstall.php`,
|
|
254
|
+
hard_uninstall: 'parent::uninstall(true) удалит таблицы, parent::uninstall(false) только отключит модуль',
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function generateFieldSuggestions(fieldType) {
|
|
259
|
+
const suggestions = {
|
|
260
|
+
string: [
|
|
261
|
+
{ name: 'title', type: 'varchar(255)', nullable: false, comment: 'Заголовок' },
|
|
262
|
+
{ name: 'slug', type: 'varchar(100)', nullable: true, comment: 'URL-псевдоним' },
|
|
263
|
+
{ name: 'email', type: 'varchar(100)', nullable: true, comment: 'Email' },
|
|
264
|
+
{ name: 'phone', type: 'varchar(20)', nullable: true, comment: 'Телефон' },
|
|
265
|
+
],
|
|
266
|
+
text: [
|
|
267
|
+
{ name: 'description', type: 'text', nullable: true, comment: 'Описание' },
|
|
268
|
+
{ name: 'content', type: 'text', nullable: true, comment: 'Контент' },
|
|
269
|
+
{ name: 'content_short', type: 'text', nullable: true, comment: 'Краткое описание' },
|
|
270
|
+
],
|
|
271
|
+
number: [
|
|
272
|
+
{ name: 'price', type: 'decimal(10,2)', nullable: true, comment: 'Цена' },
|
|
273
|
+
{ name: 'quantity', type: 'int(11)', nullable: true, default: '0', comment: 'Количество' },
|
|
274
|
+
{ name: 'sorting', type: 'int(11)', nullable: true, default: '0', comment: 'Сортировка' },
|
|
275
|
+
],
|
|
276
|
+
datetime: [
|
|
277
|
+
{ name: 'date_pub', type: 'datetime', nullable: true, comment: 'Дата публикации' },
|
|
278
|
+
{ name: 'date_end', type: 'datetime', nullable: true, comment: 'Дата окончания' },
|
|
279
|
+
{
|
|
280
|
+
name: 'created_at',
|
|
281
|
+
type: 'timestamp',
|
|
282
|
+
nullable: false,
|
|
283
|
+
default: 'CURRENT_TIMESTAMP',
|
|
284
|
+
comment: 'Создано',
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
user: [
|
|
288
|
+
{
|
|
289
|
+
name: 'user_id',
|
|
290
|
+
type: 'int(11) UNSIGNED',
|
|
291
|
+
nullable: true,
|
|
292
|
+
key: 'MUL',
|
|
293
|
+
comment: 'ID пользователя',
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
bool: [
|
|
297
|
+
{
|
|
298
|
+
name: 'is_pub',
|
|
299
|
+
type: 'tinyint(1) UNSIGNED',
|
|
300
|
+
nullable: false,
|
|
301
|
+
default: '1',
|
|
302
|
+
comment: 'Опубликовано',
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'is_deleted',
|
|
306
|
+
type: 'tinyint(1) UNSIGNED',
|
|
307
|
+
nullable: false,
|
|
308
|
+
default: '0',
|
|
309
|
+
comment: 'Удалено',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'is_featured',
|
|
313
|
+
type: 'tinyint(1) UNSIGNED',
|
|
314
|
+
nullable: false,
|
|
315
|
+
default: '0',
|
|
316
|
+
comment: 'Избранное',
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
return (suggestions[fieldType] || [
|
|
321
|
+
{
|
|
322
|
+
name: 'id',
|
|
323
|
+
type: 'int(11) UNSIGNED',
|
|
324
|
+
nullable: false,
|
|
325
|
+
extra: 'AUTO_INCREMENT',
|
|
326
|
+
key: 'PRI',
|
|
327
|
+
},
|
|
328
|
+
]);
|
|
329
|
+
}
|