dsh-db-tool 1.5.9 → 1.6.0

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.
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 构建 vendor/codemirror-sql.cjs:CodeMirror 6 + lang-sql + sql-formatter 单文件 bundle(esbuild JS API)。
4
+ *
5
+ * 模式与 scripts/build-mongodb.mjs 一致:esbuild bundle:true + platform:node(node 平台默认
6
+ * format=cjs),产物为 CJS,供 client.js 运行时经 createRequire 以相对路径加载。
7
+ * 差异点:CM6 分包(@codemirror/state|view|language|commands|autocomplete|lang-sql)与
8
+ * sql-formatter 均为纯浏览器/纯 JS 代码,无 node 内置依赖,无需 external;入口代码以
9
+ * esbuild stdin 内嵌(resolveDir 指向仓库根),不产生中间文件。
10
+ *
11
+ * 每次执行全量重建(esbuild 亚秒级),不做幂等跳过,避免"源升级产物旧"。
12
+ */
13
+ import { build } from 'esbuild';
14
+ import { mkdirSync, statSync } from 'node:fs';
15
+ import path from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+ const outfile = path.join(root, 'vendor', 'codemirror-sql.cjs');
20
+
21
+ /** bundle 所需 npm 包(均为 devDependencies 或其传递依赖),缺失则要求先 npm install */
22
+ const pkgs = [
23
+ '@codemirror/state',
24
+ '@codemirror/view',
25
+ '@codemirror/language',
26
+ '@codemirror/commands',
27
+ '@codemirror/autocomplete',
28
+ '@codemirror/lang-sql',
29
+ '@lezer/highlight',
30
+ 'sql-formatter',
31
+ ];
32
+ for (const pkg of pkgs) {
33
+ if (!statSync(path.join(root, 'node_modules', pkg), { throwIfNoEntry: false })) {
34
+ console.error(`[build-codemirror] 未找到 node_modules/${pkg} —— 请先 npm install`);
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ // 入口源码内嵌:String.raw 保证正则(\w 等)不被模板字符串转义吞掉。
40
+ const ENTRY = String.raw`
41
+ // CodeMirror 6 SQL 控制台编辑器入口(由 scripts/build-codemirror.mjs 内嵌打包)。
42
+ // 颜色一律 var(--dbt-*, 字面回退),跟随 client.js .dbt-panel 作用域 token(含浅色 media 覆盖)。
43
+
44
+ import { Compartment } from '@codemirror/state';
45
+ import { EditorView, drawSelection, keymap } from '@codemirror/view';
46
+ import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
47
+ import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
48
+ import { autocompletion } from '@codemirror/autocomplete';
49
+ import { MySQL, PLSQL, PostgreSQL, SQLite, sql } from '@codemirror/lang-sql';
50
+ import { tags } from '@lezer/highlight';
51
+ import {
52
+ formatDialect,
53
+ mysql as fmtMysql,
54
+ plsql as fmtPlsql,
55
+ postgresql as fmtPostgresql,
56
+ sqlite as fmtSqlite,
57
+ } from 'sql-formatter';
58
+
59
+ // kind → lang-sql 内置方言(官方清单)。gaussdb 按 PG 方言、oracle/dmdb 按 PL/SQL。
60
+ const SQL_DIALECTS = {
61
+ mysql: MySQL,
62
+ postgresql: PostgreSQL,
63
+ gaussdb: PostgreSQL,
64
+ sqlite: SQLite,
65
+ oracle: PLSQL,
66
+ dmdb: PLSQL,
67
+ };
68
+
69
+ // kind → sql-formatter 方言对象。mongodb/redis 无 SQL 方言,format 原文返回。
70
+ const FMT_DIALECTS = {
71
+ mysql: fmtMysql,
72
+ postgresql: fmtPostgresql,
73
+ gaussdb: fmtPostgresql,
74
+ sqlite: fmtSqlite,
75
+ oracle: fmtPlsql,
76
+ dmdb: fmtPlsql,
77
+ };
78
+
79
+ // redis 常见命令表(大写原样补全)
80
+ const REDIS_COMMANDS = [
81
+ 'APPEND', 'AUTH', 'BGREWRITEAOF', 'BGSAVE', 'CLIENT', 'CLUSTER', 'CONFIG', 'DBSIZE', 'DECR', 'DECRBY',
82
+ 'DEL', 'DISCARD', 'DUMP', 'ECHO', 'EVAL', 'EXISTS', 'EXPIRE', 'EXPIREAT', 'FLUSHALL', 'FLUSHDB',
83
+ 'GET', 'GETDEL', 'GETRANGE', 'GETSET', 'HDEL', 'HEXISTS', 'HGET', 'HGETALL', 'HINCRBY', 'HKEYS',
84
+ 'HLEN', 'HMGET', 'HMSET', 'HSCAN', 'HSET', 'HSETNX', 'HVALS', 'INCR', 'INCRBY', 'INFO',
85
+ 'KEYS', 'LINDEX', 'LLEN', 'LPOP', 'LPUSH', 'LRANGE', 'LREM', 'LSET', 'LTRIM', 'MGET',
86
+ 'MSET', 'MULTI', 'PERSIST', 'PEXPIRE', 'PING', 'PSETEX', 'PTTL', 'PUBLISH', 'RANDOMKEY', 'RENAME',
87
+ 'RPOP', 'RPUSH', 'SADD', 'SCAN', 'SCARD', 'SDIFF', 'SELECT', 'SET', 'SETBIT', 'SETEX',
88
+ 'SETNX', 'SETRANGE', 'SINTER', 'SISMEMBER', 'SMEMBERS', 'SPOP', 'SRANDMEMBER', 'SREM', 'SSCAN', 'STRLEN',
89
+ 'SUBSCRIBE', 'SUNION', 'TIME', 'TTL', 'TYPE', 'UNWATCH', 'WATCH', 'XADD', 'XLEN',
90
+ 'XRANGE', 'XREAD', 'ZADD', 'ZCARD', 'ZCOUNT', 'ZINCRBY', 'ZRANGE', 'ZRANK', 'ZREM', 'ZSCORE',
91
+ ];
92
+ // mongodb 常见集合/库方法(小写驼峰原样补全)
93
+ const MONGODB_METHODS = [
94
+ 'aggregate', 'bulkWrite', 'countDocuments', 'createIndex', 'createCollection', 'deleteMany', 'deleteOne',
95
+ 'distinct', 'drop', 'dropIndex', 'dropIndexes', 'estimatedDocumentCount', 'explain', 'find', 'findAndModify',
96
+ 'findOne', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'getIndexes', 'getIndexSpecs',
97
+ 'getCollectionNames', 'getCollectionInfos', 'insertMany', 'insertOne', 'isCapped', 'mapReduce',
98
+ 'renameCollection', 'replaceOne', 'stats', 'updateMany', 'updateOne', 'watch', 'listDatabases', 'listCollections',
99
+ ];
100
+
101
+ // 语法高亮:CM6 无稳定 CSS 类名,用 HighlightStyle 按语法 tag 上色,颜色走 --dbt-* token 带字面回退。
102
+ const sqlHighlight = HighlightStyle.define([
103
+ { tag: tags.keyword, color: 'var(--dbt-accent, #0a84ff)' },
104
+ { tag: [tags.typeName, tags.bool, tags.null], color: 'var(--dbt-warning, #ffd60a)' },
105
+ { tag: tags.number, color: 'var(--dbt-warning, #ffd60a)' },
106
+ { tag: tags.string, color: 'var(--dbt-success, #30d158)' },
107
+ { tag: tags.special(tags.string), color: 'var(--dbt-success, #30d158)' },
108
+ { tag: tags.standard(tags.name), color: 'var(--dbt-danger, #ff453a)' },
109
+ { tag: tags.special(tags.name), color: 'var(--dbt-danger, #ff453a)' },
110
+ { tag: [tags.lineComment, tags.blockComment], color: 'var(--dbt-text-secondary, rgba(235,235,245,.6))', fontStyle: 'italic' },
111
+ { tag: [tags.operator, tags.punctuation], color: 'var(--dbt-text-secondary, rgba(235,235,245,.6))' },
112
+ ]);
113
+
114
+ // 编辑器主题:外壳配色全部 var(--dbt-*) 带字面回退;dark 标志仅影响 CM 内部兜底(可见色均已自定义)。
115
+ const editorTheme = EditorView.theme({
116
+ '&': {
117
+ height: '100%',
118
+ color: 'var(--dbt-text, rgba(235,235,245,.92))',
119
+ backgroundColor: 'var(--dbt-surface, rgba(120,120,128,.12))',
120
+ border: '1px solid var(--dbt-separator, rgba(120,120,128,.24))',
121
+ borderRadius: 'var(--dbt-radius-ctrl, 8px)',
122
+ fontSize: '12px',
123
+ fontFamily: 'var(--dbt-mono, ui-monospace, SF Mono, Menlo, Consolas, monospace)',
124
+ },
125
+ '&.cm-focused': { outline: '1px solid var(--dbt-accent, #0a84ff)', outlineOffset: '-1px' },
126
+ '.cm-scroller': { overflow: 'auto', lineHeight: '1.55' },
127
+ '.cm-content': { caretColor: 'var(--dbt-accent, #0a84ff)', padding: '8px 0' },
128
+ '.cm-line': { padding: '0 10px' },
129
+ '.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--dbt-accent, #0a84ff)' },
130
+ '.cm-selectionBackground': { backgroundColor: 'var(--dbt-seg-active, rgba(255,255,255,.14)) !important' },
131
+ '.cm-activeLine': { backgroundColor: 'var(--dbt-surface-strong, rgba(120,120,128,.18))' },
132
+ '.cm-tooltip': {
133
+ backgroundColor: 'var(--dbt-dialog-bg, rgba(40,40,44,.85))',
134
+ border: '1px solid var(--dbt-separator, rgba(120,120,128,.24))',
135
+ borderRadius: 'var(--dbt-radius-ctrl, 8px)',
136
+ fontFamily: 'var(--dbt-mono, ui-monospace, SF Mono, Menlo, Consolas, monospace)',
137
+ },
138
+ '.cm-tooltip-autocomplete ul li[aria-selected]': { backgroundColor: 'var(--dbt-accent, #0a84ff)', color: '#fff' },
139
+ }, { dark: true });
140
+
141
+ // mongodb/redis:无 SQL 高亮的纯文本 + 关键字补全(redis 用命令表,mongo 用方法表)。
142
+ function nosqlCompletion(kind) {
143
+ const options = (kind === 'redis' ? REDIS_COMMANDS : MONGODB_METHODS).map((label) => ({
144
+ label,
145
+ type: kind === 'redis' ? 'keyword' : 'method',
146
+ }));
147
+ return (context) => {
148
+ const word = context.matchBefore(/[\w$.]+/);
149
+ if (!word && !context.explicit) return null;
150
+ return { from: word ? word.from : context.pos, options, validFor: /^[\w$.]*$/ };
151
+ };
152
+ }
153
+
154
+ // 语言扩展整体构建(SQL:lang-sql 官方 API;mongodb/redis:纯文本 + 关键字补全 override)。
155
+ // schema 为 SQLNamespace({ 表名: [列名,...] } 或 {self,children}),经 sql({dialect, schema}) 生效。
156
+ function languageExtension(kind, schema) {
157
+ const dialect = SQL_DIALECTS[kind];
158
+ if (dialect) {
159
+ return [
160
+ sql({ dialect, schema, upperCaseKeywords: true }),
161
+ autocompletion(),
162
+ ];
163
+ }
164
+ if (kind === 'mongodb' || kind === 'redis') {
165
+ return [autocompletion({ override: [nosqlCompletion(kind)] })];
166
+ }
167
+ // 未知 kind:降级纯文本,不炸编辑器
168
+ return [];
169
+ }
170
+
171
+ // 创建控制台编辑器。opts: { kind, schema?, onRunAll?, onRunSelection?, onChange? }
172
+ export function createConsoleEditor(container, opts = {}) {
173
+ const kind = typeof opts.kind === 'string' ? opts.kind : 'mysql';
174
+ if (!SQL_DIALECTS[kind] && kind !== 'mongodb' && kind !== 'redis') {
175
+ console.warn('[codemirror-sql] 未知 kind: ' + kind + ',按纯文本处理');
176
+ }
177
+ const languageCompartment = new Compartment();
178
+
179
+ // 自定义运行快捷键,必须插在 defaultKeymap 之前保证优先
180
+ const runKeymap = [];
181
+ if (typeof opts.onRunAll === 'function') {
182
+ runKeymap.push({ key: 'Mod-Enter', run: () => { opts.onRunAll(); return true; } });
183
+ }
184
+ if (typeof opts.onRunSelection === 'function') {
185
+ runKeymap.push({ key: 'Mod-Shift-Enter', run: () => { opts.onRunSelection(); return true; } });
186
+ }
187
+
188
+ container.classList.add('dbt-cm-root');
189
+ const view = new EditorView({
190
+ parent: container,
191
+ extensions: [
192
+ languageCompartment.of(languageExtension(kind, opts.schema)),
193
+ keymap.of(runKeymap),
194
+ history(),
195
+ keymap.of(defaultKeymap),
196
+ keymap.of(historyKeymap),
197
+ drawSelection(),
198
+ syntaxHighlighting(sqlHighlight),
199
+ EditorView.lineWrapping,
200
+ editorTheme,
201
+ EditorView.updateListener.of((update) => {
202
+ if (update.docChanged && typeof opts.onChange === 'function') {
203
+ opts.onChange(update.state.doc.toString());
204
+ }
205
+ }),
206
+ ],
207
+ });
208
+
209
+ return {
210
+ view,
211
+ destroy() {
212
+ view.destroy();
213
+ },
214
+ // 换连接/换库后动态更新表列元数据:Compartment 挂 language 扩展整体重建
215
+ setSchema(schema) {
216
+ view.dispatch({ effects: languageCompartment.reconfigure(languageExtension(kind, schema)) });
217
+ },
218
+ setDoc(text) {
219
+ view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: String(text == null ? '' : text) } });
220
+ },
221
+ getDoc() {
222
+ return view.state.doc.toString();
223
+ },
224
+ getSelection() {
225
+ const main = view.state.selection.main;
226
+ return view.state.sliceDoc(main.from, main.to);
227
+ },
228
+ // 格式化:sql-formatter formatDialect;mongodb/redis 原文返回;解析失败回退原文不抛错
229
+ format() {
230
+ const text = view.state.doc.toString();
231
+ const fmtDialect = FMT_DIALECTS[kind];
232
+ if (!fmtDialect) return text;
233
+ try {
234
+ const formatted = formatDialect(text, { dialect: fmtDialect, tabWidth: 2, keywordCase: 'upper' });
235
+ if (formatted !== text) {
236
+ view.dispatch({ changes: { from: 0, to: text.length, insert: formatted } });
237
+ }
238
+ return formatted;
239
+ } catch {
240
+ return text;
241
+ }
242
+ },
243
+ };
244
+ }
245
+ `;
246
+
247
+ mkdirSync(path.dirname(outfile), { recursive: true });
248
+ await build({
249
+ stdin: { contents: ENTRY, resolveDir: root, sourcefile: 'codemirror-entry.js', loader: 'js' },
250
+ outfile,
251
+ bundle: true,
252
+ platform: 'node', // node 平台默认 format=cjs,与 vendor/mongodb-driver.cjs 一致
253
+ format: 'cjs',
254
+ target: 'node20',
255
+ legalComments: 'none',
256
+ sourcemap: false,
257
+ minify: false,
258
+ logLevel: 'warning',
259
+ });
260
+
261
+ const size = statSync(outfile).size;
262
+ console.log(`[build-codemirror] wrote vendor/codemirror-sql.cjs (${(size / 1024 / 1024).toFixed(2)} MB)`);
263
+ console.log('[build-codemirror] done');