@bimatrix-aud-platform/aud_mcp_server 1.1.52 → 1.1.56

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,174 @@
1
+ /**
2
+ * MTSD/SC 파일 경로 참조 유틸리티
3
+ *
4
+ * 로컬 .mtsd/.sc 파일에서 인라인 스크립트/SQL을 파일 경로 참조로 대체하고,
5
+ * 서버 전송 시 파일 경로를 실제 콘텐츠로 복원하는 기능을 제공합니다.
6
+ *
7
+ * report-publisher.ts 에서 사용합니다.
8
+ */
9
+ import { readFileSync, existsSync, readdirSync } from "fs";
10
+ import { join, resolve } from "path";
11
+ /**
12
+ * 값이 파일 경로 참조인지 판별합니다.
13
+ * - "./"로 시작하고 줄바꿈이 없으면 파일 경로로 판별
14
+ * - SQL/JS/암호화 문자열은 "./"로 시작하지 않으므로 오판 없음
15
+ */
16
+ export function isFilePath(value) {
17
+ if (!value || typeof value !== "string") {
18
+ return false;
19
+ }
20
+ return value.startsWith("./") && !value.includes("\n");
21
+ }
22
+ /**
23
+ * ReportModel의 인라인 콘텐츠를 파일 경로 참조로 치환합니다.
24
+ * Pull/Save 응답을 로컬 .mtsd에 저장할 때 사용합니다.
25
+ *
26
+ * @param reportModel 서버에서 받은 전체 ReportModel (인라인 콘텐츠 포함)
27
+ * @param reportFolder 보고서 폴더 절대 경로 (.mtsd이 있는 폴더)
28
+ * @param reportCode 보고서 코드 (스크립트 파일명 탐색용)
29
+ * @returns 파일 경로 참조가 적용된 deep clone
30
+ */
31
+ export function replaceContentWithFilePaths(reportModel, reportFolder, reportCode) {
32
+ const model = JSON.parse(JSON.stringify(reportModel));
33
+ // 1. ScriptText → 파일 경로
34
+ if (model.ScriptText && typeof model.ScriptText === "string" && model.ScriptText.trim().length > 0) {
35
+ const scriptFile = findScriptFile(reportFolder);
36
+ if (scriptFile) {
37
+ model.ScriptText = "./" + scriptFile;
38
+ }
39
+ }
40
+ // 2. ServerScriptText[] → 파일 경로
41
+ if (model.ServerScriptText && Array.isArray(model.ServerScriptText)) {
42
+ for (const ss of model.ServerScriptText) {
43
+ if (ss.ScriptText && typeof ss.ScriptText === "string" && ss.ScriptText.trim().length > 0) {
44
+ const ssFile = findServerScriptFile(reportFolder, ss.Name);
45
+ if (ssFile) {
46
+ ss.ScriptText = "./" + ssFile;
47
+ }
48
+ }
49
+ }
50
+ }
51
+ // 3. DataSources.Datas[] → 파일 경로
52
+ if (model.DataSources?.Datas && Array.isArray(model.DataSources.Datas)) {
53
+ for (const ds of model.DataSources.Datas) {
54
+ if (ds.SQL && typeof ds.SQL === "string" && ds.SQL.trim().length > 0) {
55
+ const dsFile = findDataSourceFile(reportFolder, ds.Name);
56
+ if (dsFile) {
57
+ ds.SQL = "./" + dsFile;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ return model;
63
+ }
64
+ /**
65
+ * ReportModel의 파일 경로 참조를 실제 파일 콘텐츠로 복원합니다.
66
+ * UploadReport(ZIP 빌드) 시 서버에 전송하기 전에 사용합니다.
67
+ * 인라인 콘텐츠(파일 경로가 아닌 값)는 그대로 유지됩니다.
68
+ *
69
+ * .ts 파일 경로인 경우 빌드된 .js를 outputFolder에서 읽습니다.
70
+ * .sql, .js, .json 파일은 소스 폴더에서 직접 읽습니다.
71
+ *
72
+ * @param reportModel 파일 경로 참조가 포함된 ReportModel
73
+ * @param reportFolder 보고서 소스 폴더 절대 경로
74
+ * @param outputFolder 빌드 출력 폴더 절대 경로 (.ts → .js 매핑용)
75
+ * @returns 인라인 콘텐츠가 복원된 deep clone
76
+ */
77
+ export function resolveFilePathsToContent(reportModel, reportFolder, outputFolder) {
78
+ const model = JSON.parse(JSON.stringify(reportModel));
79
+ // 1. ScriptText — .script.ts → 빌드된 .script.js 읽기
80
+ if (isFilePath(model.ScriptText)) {
81
+ model.ScriptText = readResolvedFile(model.ScriptText, reportFolder, outputFolder);
82
+ }
83
+ // 2. ServerScriptText[]
84
+ if (model.ServerScriptText && Array.isArray(model.ServerScriptText)) {
85
+ for (const ss of model.ServerScriptText) {
86
+ if (isFilePath(ss.ScriptText)) {
87
+ ss.ScriptText = readResolvedFile(ss.ScriptText, reportFolder, outputFolder);
88
+ }
89
+ }
90
+ }
91
+ // 3. DataSources.Datas[]
92
+ if (model.DataSources?.Datas && Array.isArray(model.DataSources.Datas)) {
93
+ for (const ds of model.DataSources.Datas) {
94
+ if (isFilePath(ds.SQL)) {
95
+ ds.SQL = readResolvedFile(ds.SQL, reportFolder, outputFolder);
96
+ }
97
+ }
98
+ }
99
+ return model;
100
+ }
101
+ /**
102
+ * 파일 경로 참조에서 실제 콘텐츠를 읽습니다.
103
+ * .ts 파일이면 outputFolder의 빌드된 .js를 읽고,
104
+ * 그 외(.sql, .js, .json 등)는 소스 폴더에서 직접 읽습니다.
105
+ */
106
+ function readResolvedFile(refPath, reportFolder, outputFolder) {
107
+ if (refPath.toLowerCase().endsWith(".ts")) {
108
+ // .ts → 빌드 출력 폴더의 .js 파일 읽기
109
+ const jsRefPath = refPath.replace(/\.ts$/i, ".js");
110
+ const builtPath = resolve(outputFolder, jsRefPath);
111
+ if (existsSync(builtPath)) {
112
+ return readFileSync(builtPath, "utf-8");
113
+ }
114
+ // 빌드 파일이 없으면 에러
115
+ throw new Error(`빌드된 파일을 찾을 수 없습니다: ${jsRefPath}\n`
116
+ + `경로: ${builtPath}\n`
117
+ + "tsc 명령어로 빌드를 실행해 주세요.");
118
+ }
119
+ // .sql, .js, .json 등은 소스 폴더에서 직접 읽기
120
+ const fullPath = resolve(reportFolder, refPath);
121
+ if (!existsSync(fullPath)) {
122
+ throw new Error(`참조된 파일을 찾을 수 없습니다: ${refPath}\n경로: ${fullPath}`);
123
+ }
124
+ return readFileSync(fullPath, "utf-8");
125
+ }
126
+ // ─── 내부 헬퍼 함수 ────────────────────────────────────────
127
+ /** 보고서 폴더에서 .script.ts 또는 .script.js 파일을 찾습니다 */
128
+ function findScriptFile(reportFolder) {
129
+ if (!existsSync(reportFolder)) {
130
+ return null;
131
+ }
132
+ const files = readdirSync(reportFolder);
133
+ for (const f of files) {
134
+ const lower = f.toLowerCase();
135
+ if (lower.endsWith(".script.ts") || lower.endsWith(".script.js")) {
136
+ return f;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ /** ServerScript 폴더에서 해당 이름의 파일을 찾습니다 (.ts → .js → .json 우선순위) */
142
+ function findServerScriptFile(reportFolder, scriptName) {
143
+ const ssFolder = join(reportFolder, "ServerScript");
144
+ if (!existsSync(ssFolder)) {
145
+ return null;
146
+ }
147
+ const safeName = sanitizeFileName(scriptName);
148
+ for (const ext of [".ts", ".js", ".json"]) {
149
+ const candidate = join(ssFolder, safeName + ext);
150
+ if (existsSync(candidate)) {
151
+ return "ServerScript/" + safeName + ext;
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ /** DataSource 폴더에서 해당 이름의 파일을 찾습니다 (.sql → .ts → .js 우선순위) */
157
+ function findDataSourceFile(reportFolder, dsName) {
158
+ const dsFolder = join(reportFolder, "DataSource");
159
+ if (!existsSync(dsFolder)) {
160
+ return null;
161
+ }
162
+ const safeName = sanitizeFileName(dsName);
163
+ for (const ext of [".sql", ".SQL", ".ts", ".js"]) {
164
+ const candidate = join(dsFolder, safeName + ext);
165
+ if (existsSync(candidate)) {
166
+ return "DataSource/" + safeName + ext;
167
+ }
168
+ }
169
+ return null;
170
+ }
171
+ /** 파일명에서 Windows 금지 문자를 제거합니다 */
172
+ function sanitizeFileName(fileName) {
173
+ return fileName.replace(/[\\\/:*?"<>|]/g, "_");
174
+ }
@@ -10,11 +10,6 @@
10
10
  * - ISaveReportModel 조립
11
11
  * - 서버 응답으로 로컬 파일 업데이트
12
12
  */
13
- export interface AudJson {
14
- ReportCode: string;
15
- ReportName?: string;
16
- ModuleCode?: string;
17
- }
18
13
  export interface DataSourceItem {
19
14
  Id?: string;
20
15
  Name: string;
@@ -45,9 +40,7 @@ export declare function sanitizeFileName(fileName: string): string;
45
40
  * outDir 기반으로 source → output 경로를 계산합니다.
46
41
  */
47
42
  export declare function resolveOutputPath(reportPath: string, sourcePath?: string): string;
48
- /** .aud.json 읽기 */
49
- export declare function readAudJson(reportPath: string): AudJson;
50
- /** .mtsd 또는 .sc 파일 읽기 */
43
+ /** 보고서 문서 파일 읽기 (.design.json .mtsd → .sc 우선순위) */
51
44
  export declare function readReportDocument(reportPath: string): any;
52
45
  /**
53
46
  * DataSource 파일 수집
@@ -89,7 +82,6 @@ export declare function writeReportResponse(reportPath: string, response: any):
89
82
  * Extension의 writeReportInfo() + writeAudJson() 로직 재구현 (Pull 용)
90
83
  *
91
84
  * writeReportResponse와의 차이점:
92
- * 1. .aud.json에 FolderCode, DocumentVersion, Description, ModifyDate, Modifier 추가 반영
93
85
  * 2. JScript 파일이 없으면 서버에서 받은 JScript로 .script.js 생성
94
86
  */
95
87
  export declare function writePullResponse(reportPath: string, response: any): string[];
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync } from "fs";
14
14
  import { join, parse, resolve, dirname, extname, basename } from "path";
15
+ import { replaceContentWithFilePaths } from "./mtsd-file-ref.js";
15
16
  // ─── Utility ───────────────────────────────────────────────
16
17
  /** Windows 금지 문자를 _ 로 치환 */
17
18
  export function sanitizeFileName(fileName) {
@@ -96,27 +97,24 @@ export function resolveOutputPath(reportPath, sourcePath) {
96
97
  return join(resolvedOut, relativePart);
97
98
  }
98
99
  // ─── File Readers ──────────────────────────────────────────
99
- /** .aud.json 읽기 */
100
- export function readAudJson(reportPath) {
101
- const configPath = join(reportPath, ".aud.json");
102
- if (!existsSync(configPath)) {
103
- throw new Error(`.aud.json 파일을 찾을 수 없습니다.\n경로: ${reportPath}`);
104
- }
105
- const text = readFileSync(configPath, "utf-8");
106
- return JSON.parse(text);
107
- }
108
- /** .mtsd 또는 .sc 파일 읽기 */
100
+ /** 보고서 문서 파일 읽기 (.design.json .mtsd → .sc 우선순위) */
109
101
  export function readReportDocument(reportPath) {
102
+ // 1순위: .design.json (고정 파일명)
103
+ const designPath = join(reportPath, ".design.json");
104
+ if (existsSync(designPath)) {
105
+ const text = readFileSync(designPath, "utf-8");
106
+ return JSON.parse(text);
107
+ }
108
+ // 2순위: .mtsd, .sc
110
109
  const files = readdirSync(reportPath);
111
110
  for (const f of files) {
112
111
  const lower = f.toLowerCase();
113
112
  if (lower.endsWith(".mtsd") || lower.endsWith(".sc")) {
114
- const filePath = join(reportPath, f);
115
- const text = readFileSync(filePath, "utf-8");
113
+ const text = readFileSync(join(reportPath, f), "utf-8");
116
114
  return JSON.parse(text);
117
115
  }
118
116
  }
119
- throw new Error("보고서 파일(.mtsd 또는 .sc)을 찾을 수 없습니다.");
117
+ throw new Error("보고서 파일(.design.json, .mtsd 또는 .sc)을 찾을 수 없습니다.");
120
118
  }
121
119
  /**
122
120
  * DataSource 파일 수집
@@ -303,13 +301,15 @@ export function detectDeletedServerScripts(srcPath, outPath, reportDoc) {
303
301
  */
304
302
  export function buildSaveModel(reportPath) {
305
303
  const resolvedPath = resolve(reportPath);
306
- // 1. .aud.json 읽기
307
- const audJson = readAudJson(resolvedPath);
308
- if (!audJson.ReportCode) {
309
- throw new Error(".aud.json에 ReportCode가 없습니다.");
310
- }
311
- // 2. MTSD 문서 읽기
304
+ // 1. 보고서 문서 읽기 (.design.json → .mtsd → .sc)
312
305
  const reportDoc = readReportDocument(resolvedPath);
306
+ const repInfo = reportDoc?.ReportInfo;
307
+ if (!repInfo?.ReportCode) {
308
+ throw new Error("보고서 문서에 ReportInfo.ReportCode가 없습니다.");
309
+ }
310
+ // 2. ModuleCode: ReportInfo.SavePath 확장자로 판별 (.sc → "SC", .mtsd → "SD")
311
+ const savePath = (repInfo.SavePath || "").toLowerCase();
312
+ const moduleCode = savePath.endsWith(".sc") ? "SC" : "SD";
313
313
  // 3. .out 경로 해석
314
314
  const outPath = resolveOutputPath(resolvedPath);
315
315
  // 4. 파일 수집
@@ -321,9 +321,9 @@ export function buildSaveModel(reportPath) {
321
321
  const deletedSS = detectDeletedServerScripts(resolvedPath, outPath, reportDoc);
322
322
  // 6. 모델 조립
323
323
  return {
324
- REPORT_CODE: audJson.ReportCode,
325
- MODULE_CODE: audJson.ModuleCode || "",
326
- REPORT_NAME: audJson.ReportName || reportDoc?.ReportInfo?.ReportName || audJson.ReportCode,
324
+ REPORT_CODE: repInfo.ReportCode,
325
+ MODULE_CODE: moduleCode,
326
+ REPORT_NAME: repInfo.ReportName || repInfo.ReportCode,
327
327
  JScript: jScript,
328
328
  DataSource: dataSources,
329
329
  ServerScript: serverScripts,
@@ -341,10 +341,10 @@ export function buildSaveModel(reportPath) {
341
341
  export function writeReportResponse(reportPath, response) {
342
342
  const resolvedPath = resolve(reportPath);
343
343
  const written = [];
344
- // 1. MTSD 파일 갱신
344
+ // 1. mtsd 경로 결정 (MTSD 쓰기는 모든 파일 생성 후 수행)
345
+ let mtsdPath = "";
345
346
  if (response.ReportModel) {
346
347
  const files = readdirSync(resolvedPath);
347
- let mtsdPath = "";
348
348
  for (const f of files) {
349
349
  const lower = f.toLowerCase();
350
350
  if (lower.endsWith(".mtsd") || lower.endsWith(".sc")) {
@@ -357,19 +357,6 @@ export function writeReportResponse(reportPath, response) {
357
357
  const ext = moduleCode === "SC" ? ".sc" : ".mtsd";
358
358
  mtsdPath = join(resolvedPath, (response.REPORT_CODE || "report") + ext);
359
359
  }
360
- writeFileSync(mtsdPath, JSON.stringify(response.ReportModel, null, " "), "utf-8");
361
- written.push(mtsdPath);
362
- // .aud.json 업데이트
363
- const audJsonPath = join(resolvedPath, ".aud.json");
364
- if (existsSync(audJsonPath)) {
365
- const audJson = JSON.parse(readFileSync(audJsonPath, "utf-8"));
366
- audJson.ReportCode = response.REPORT_CODE || audJson.ReportCode;
367
- if (response.REPORT_NAME)
368
- audJson.ReportName = response.REPORT_NAME;
369
- if (response.MODULE_CODE)
370
- audJson.ModuleCode = response.MODULE_CODE;
371
- writeFileSync(audJsonPath, JSON.stringify(audJson, null, " "), "utf-8");
372
- }
373
360
  }
374
361
  // 2. 새 DataSource 파일 생성 (이미 존재하면 skip)
375
362
  if (response.DataSource && response.DataSource.length > 0) {
@@ -399,6 +386,17 @@ export function writeReportResponse(reportPath, response) {
399
386
  }
400
387
  }
401
388
  }
389
+ // 4. 모든 파일 생성 후 .mtsd + .design.json 저장
390
+ if (response.ReportModel && mtsdPath) {
391
+ // .mtsd — 서버 원본 그대로 저장
392
+ writeFileSync(mtsdPath, JSON.stringify(response.ReportModel, null, " "), "utf-8");
393
+ written.push(mtsdPath);
394
+ // .design.json — 파일 경로 참조 버전 저장 (AI/개발용)
395
+ const designModel = replaceContentWithFilePaths(response.ReportModel, resolvedPath, response.REPORT_CODE || "");
396
+ const designPath = join(resolvedPath, ".design.json");
397
+ writeFileSync(designPath, JSON.stringify(designModel, null, " "), "utf-8");
398
+ written.push(designPath);
399
+ }
402
400
  return written;
403
401
  }
404
402
  // ─── Pull Response Writer ──────────────────────────────────
@@ -407,16 +405,15 @@ export function writeReportResponse(reportPath, response) {
407
405
  * Extension의 writeReportInfo() + writeAudJson() 로직 재구현 (Pull 용)
408
406
  *
409
407
  * writeReportResponse와의 차이점:
410
- * 1. .aud.json에 FolderCode, DocumentVersion, Description, ModifyDate, Modifier 추가 반영
411
408
  * 2. JScript 파일이 없으면 서버에서 받은 JScript로 .script.js 생성
412
409
  */
413
410
  export function writePullResponse(reportPath, response) {
414
411
  const resolvedPath = resolve(reportPath);
415
412
  const written = [];
416
- // 1. MTSD 파일 갱신
413
+ // 1. mtsd 경로 결정 (MTSD 쓰기는 모든 파일 생성 후 수행)
414
+ let mtsdPath = "";
417
415
  if (response.ReportModel) {
418
416
  const files = readdirSync(resolvedPath);
419
- let mtsdPath = "";
420
417
  for (const f of files) {
421
418
  const lower = f.toLowerCase();
422
419
  if (lower.endsWith(".mtsd") || lower.endsWith(".sc")) {
@@ -429,25 +426,6 @@ export function writePullResponse(reportPath, response) {
429
426
  const ext = moduleCode === "SC" ? ".sc" : ".mtsd";
430
427
  mtsdPath = join(resolvedPath, (response.REPORT_CODE || "report") + ext);
431
428
  }
432
- writeFileSync(mtsdPath, JSON.stringify(response.ReportModel, null, " "), "utf-8");
433
- written.push(mtsdPath);
434
- // .aud.json 업데이트 (Pull은 서버 ReportInfo의 모든 메타데이터 반영)
435
- const audJsonPath = join(resolvedPath, ".aud.json");
436
- const repInfo = response.ReportModel?.ReportInfo;
437
- if (repInfo) {
438
- const audJson = {
439
- ReportCode: response.REPORT_CODE || "",
440
- ReportName: repInfo.ReportName || "",
441
- FolderCode: repInfo.FolderCode || "",
442
- ModuleCode: response.MODULE_CODE || "",
443
- DocumentVersion: repInfo.DocumentVersion || "",
444
- Description: repInfo.Description || "",
445
- ModifyDate: repInfo.EditDate || "",
446
- Modifier: repInfo.Editor || "",
447
- };
448
- writeFileSync(audJsonPath, JSON.stringify(audJson, null, " "), "utf-8");
449
- written.push(audJsonPath);
450
- }
451
429
  }
452
430
  // 2. 새 DataSource 파일 생성 (이미 존재하면 skip)
453
431
  if (response.DataSource && response.DataSource.length > 0) {
@@ -492,6 +470,17 @@ export function writePullResponse(reportPath, response) {
492
470
  writeFileSync(scriptPath, response.JScript, "utf-8");
493
471
  written.push(scriptPath);
494
472
  }
473
+ // 5. 모든 파일 생성 후 .mtsd + .design.json 저장
474
+ if (response.ReportModel && mtsdPath) {
475
+ // .mtsd — 서버 원본 그대로 저장
476
+ writeFileSync(mtsdPath, JSON.stringify(response.ReportModel, null, " "), "utf-8");
477
+ written.push(mtsdPath);
478
+ // .design.json — 파일 경로 참조 버전 저장 (AI/개발용)
479
+ const designModel = replaceContentWithFilePaths(response.ReportModel, resolvedPath, response.REPORT_CODE || "");
480
+ const designPath = join(resolvedPath, ".design.json");
481
+ writeFileSync(designPath, JSON.stringify(designModel, null, " "), "utf-8");
482
+ written.push(designPath);
483
+ }
495
484
  return written;
496
485
  }
497
486
  // ─── Helpers (private) ─────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bimatrix-aud-platform/aud_mcp_server",
3
- "version": "1.1.52",
3
+ "version": "1.1.56",
4
4
  "description": "MCP Server for i-AUD MTSD document validation, generation, schema querying, control info extraction, and database operations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,9 +15,9 @@ export interface IMTSDDocument {
15
15
  ReportInfo: IReportInfo;
16
16
  /** 보고서에서 사용하는 데이터소스 목록 */
17
17
  DataSources: IDataSources;
18
- /** 클라이언트에서 실행되는 JavaScript 코드 (이벤트 핸들러 포함) */
18
+ /** 클라이언트에서 실행되는 JavaScript 코드. 로컬 .mtsd에서는 상대 경로 참조(예: "./report.script.ts") 될 수 있음 */
19
19
  ScriptText: string;
20
- /** 서버에서 실행되는 JavaScript 코드 배열 */
20
+ /** 서버에서 실행되는 JavaScript 코드 배열. 각 항목의 ScriptText가 로컬에서 상대 경로(예: "./ServerScript/@name.ts")가 될 수 있음 */
21
21
  ServerScriptText: IServerScript[];
22
22
  /** 화면 양식(탭) 목록 */
23
23
  Forms: IForm[];
@@ -117,7 +117,7 @@ export interface IDataSource {
117
117
  Encrypted: boolean;
118
118
  /** 데이터소스 타입 (0:메타 템플릿,1:메타 쿼리, 2:일반 SQL,3:OLAP Drill to Detail,4:공통데이터소스,5:메타 필터) */
119
119
  DSType: number;
120
- /** 실행할 SQL 쿼리문 */
120
+ /** 실행할 SQL 쿼리문. 로컬 .mtsd에서는 상대 경로 참조(예: "./DataSource/name.sql")가 될 수 있음 */
121
121
  SQL: string;
122
122
  /** SQL 파라미터 배열 */
123
123
  Params: IDataSourceParam[];
@@ -160,7 +160,7 @@ export interface IServerScript {
160
160
  Key: string;
161
161
  /** 스크립트 암호화 여부 */
162
162
  Encrypted: boolean;
163
- /** 스크립트 본문 (JavaScript 코드) */
163
+ /** 스크립트 본문 (JavaScript 코드). 로컬 .mtsd에서는 상대 경로 참조(예: "./ServerScript/@name.ts")가 될 수 있음 */
164
164
  ScriptText: string;
165
165
  }
166
166
 
@@ -8,7 +8,7 @@
8
8
  "properties": {
9
9
  "ReportInfo": { "$ref": "#/$defs/ReportInfo" },
10
10
  "DataSources": { "$ref": "#/$defs/DataSources" },
11
- "ScriptText": { "type": "string", "description": "클라이언트에서 실행되는 JavaScript 코드" },
11
+ "ScriptText": { "type": "string", "description": "클라이언트에서 실행되는 JavaScript 코드. 로컬에서는 상대 경로 참조(예: ./report.script.ts)가 될 수 있음" },
12
12
  "ServerScriptText": {
13
13
  "type": "array",
14
14
  "items": { "$ref": "#/$defs/ServerScript" },
@@ -107,7 +107,7 @@
107
107
  "ConnectionCode": { "type": "string", "description": "연결된 DB 커넥션 코드" },
108
108
  "Encrypted": { "type": "boolean", "description": "SQL 쿼리 암호화 여부" },
109
109
  "DSType": { "type": "integer", "enum": [0, 1, 2, 3,4,5], "description": "데이터소스 타입 (0:메타 템플릿,1:메타 쿼리, 2:일반 SQL,3:OLAP Drill to Detail,4:공통데이터소스,5:메타 필터)" },
110
- "SQL": { "type": "string", "description": "실행할 SQL 쿼리문" },
110
+ "SQL": { "type": "string", "description": "실행할 SQL 쿼리문. 로컬에서는 상대 경로 참조(예: ./DataSource/name.sql)가 될 수 있음" },
111
111
  "Params": {
112
112
  "type": "array",
113
113
  "items": { "$ref": "#/$defs/DataSourceParam" }
@@ -144,7 +144,7 @@
144
144
  "Name": { "type": "string", "description": "스크립트명" },
145
145
  "Key": { "type": "string", "description": "스크립트 키 (고유 식별자)" },
146
146
  "Encrypted": { "type": "boolean", "description": "스크립트 암호화 여부" },
147
- "ScriptText": { "type": "string", "description": "스크립트 본문 (JavaScript 코드)" }
147
+ "ScriptText": { "type": "string", "description": "스크립트 본문 (JavaScript 코드). 로컬에서는 상대 경로 참조(예: ./ServerScript/@name.ts)가 될 수 있음" }
148
148
  }
149
149
  },
150
150
  "Form": {