@atlashub/smartstack-cli 3.53.0 → 4.0.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.
- package/dist/index.js +1748 -780
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +1353 -158
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +115 -115
- package/templates/skills/apex/SKILL.md +11 -9
- package/templates/skills/apex/references/agent-teams-protocol.md +32 -16
- package/templates/skills/apex/references/core-seed-data.md +27 -1
- package/templates/skills/apex/references/smartstack-api.md +40 -0
- package/templates/skills/apex/references/smartstack-layers.md +125 -118
- package/templates/skills/apex/steps/step-02-plan.md +33 -18
- package/templates/skills/apex/steps/step-03-execute.md +297 -120
- package/templates/skills/apex/steps/step-04-examine.md +15 -6
- package/templates/skills/apex/steps/step-07-tests.md +60 -40
- package/templates/skills/apex/steps/step-08-run-tests.md +5 -3
- package/templates/skills/application/references/backend-table-prefix-mapping.md +1 -0
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +2 -0
package/dist/mcp-entry.mjs
CHANGED
|
@@ -471,8 +471,8 @@ var init_parseUtil = __esm({
|
|
|
471
471
|
init_errors();
|
|
472
472
|
init_en();
|
|
473
473
|
makeIssue = (params) => {
|
|
474
|
-
const { data, path:
|
|
475
|
-
const fullPath = [...
|
|
474
|
+
const { data, path: path32, errorMaps, issueData } = params;
|
|
475
|
+
const fullPath = [...path32, ...issueData.path || []];
|
|
476
476
|
const fullIssue = {
|
|
477
477
|
...issueData,
|
|
478
478
|
path: fullPath
|
|
@@ -786,11 +786,11 @@ var init_types = __esm({
|
|
|
786
786
|
init_parseUtil();
|
|
787
787
|
init_util();
|
|
788
788
|
ParseInputLazyPath = class {
|
|
789
|
-
constructor(parent, value,
|
|
789
|
+
constructor(parent, value, path32, key) {
|
|
790
790
|
this._cachedPath = [];
|
|
791
791
|
this.parent = parent;
|
|
792
792
|
this.data = value;
|
|
793
|
-
this._path =
|
|
793
|
+
this._path = path32;
|
|
794
794
|
this._key = key;
|
|
795
795
|
}
|
|
796
796
|
get path() {
|
|
@@ -4367,10 +4367,10 @@ function assignProp(target, prop, value) {
|
|
|
4367
4367
|
configurable: true
|
|
4368
4368
|
});
|
|
4369
4369
|
}
|
|
4370
|
-
function getElementAtPath(obj,
|
|
4371
|
-
if (!
|
|
4370
|
+
function getElementAtPath(obj, path32) {
|
|
4371
|
+
if (!path32)
|
|
4372
4372
|
return obj;
|
|
4373
|
-
return
|
|
4373
|
+
return path32.reduce((acc, key) => acc?.[key], obj);
|
|
4374
4374
|
}
|
|
4375
4375
|
function promiseAllObject(promisesObj) {
|
|
4376
4376
|
const keys = Object.keys(promisesObj);
|
|
@@ -4619,11 +4619,11 @@ function aborted(x, startIndex = 0) {
|
|
|
4619
4619
|
}
|
|
4620
4620
|
return false;
|
|
4621
4621
|
}
|
|
4622
|
-
function prefixIssues(
|
|
4622
|
+
function prefixIssues(path32, issues) {
|
|
4623
4623
|
return issues.map((iss) => {
|
|
4624
4624
|
var _a;
|
|
4625
4625
|
(_a = iss).path ?? (_a.path = []);
|
|
4626
|
-
iss.path.unshift(
|
|
4626
|
+
iss.path.unshift(path32);
|
|
4627
4627
|
return iss;
|
|
4628
4628
|
});
|
|
4629
4629
|
}
|
|
@@ -14447,8 +14447,8 @@ var require_utils = __commonJS({
|
|
|
14447
14447
|
}
|
|
14448
14448
|
return ind;
|
|
14449
14449
|
}
|
|
14450
|
-
function removeDotSegments(
|
|
14451
|
-
let input =
|
|
14450
|
+
function removeDotSegments(path32) {
|
|
14451
|
+
let input = path32;
|
|
14452
14452
|
const output = [];
|
|
14453
14453
|
let nextSlash = -1;
|
|
14454
14454
|
let len = 0;
|
|
@@ -14648,8 +14648,8 @@ var require_schemes = __commonJS({
|
|
|
14648
14648
|
wsComponent.secure = void 0;
|
|
14649
14649
|
}
|
|
14650
14650
|
if (wsComponent.resourceName) {
|
|
14651
|
-
const [
|
|
14652
|
-
wsComponent.path =
|
|
14651
|
+
const [path32, query] = wsComponent.resourceName.split("?");
|
|
14652
|
+
wsComponent.path = path32 && path32 !== "/" ? path32 : void 0;
|
|
14653
14653
|
wsComponent.query = query;
|
|
14654
14654
|
wsComponent.resourceName = void 0;
|
|
14655
14655
|
}
|
|
@@ -23041,12 +23041,12 @@ var init_esm7 = __esm({
|
|
|
23041
23041
|
/**
|
|
23042
23042
|
* Get the Path object referenced by the string path, resolved from this Path
|
|
23043
23043
|
*/
|
|
23044
|
-
resolve(
|
|
23045
|
-
if (!
|
|
23044
|
+
resolve(path32) {
|
|
23045
|
+
if (!path32) {
|
|
23046
23046
|
return this;
|
|
23047
23047
|
}
|
|
23048
|
-
const rootPath = this.getRootString(
|
|
23049
|
-
const dir =
|
|
23048
|
+
const rootPath = this.getRootString(path32);
|
|
23049
|
+
const dir = path32.substring(rootPath.length);
|
|
23050
23050
|
const dirParts = dir.split(this.splitSep);
|
|
23051
23051
|
const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
|
|
23052
23052
|
return result;
|
|
@@ -23798,8 +23798,8 @@ var init_esm7 = __esm({
|
|
|
23798
23798
|
/**
|
|
23799
23799
|
* @internal
|
|
23800
23800
|
*/
|
|
23801
|
-
getRootString(
|
|
23802
|
-
return win32.parse(
|
|
23801
|
+
getRootString(path32) {
|
|
23802
|
+
return win32.parse(path32).root;
|
|
23803
23803
|
}
|
|
23804
23804
|
/**
|
|
23805
23805
|
* @internal
|
|
@@ -23845,8 +23845,8 @@ var init_esm7 = __esm({
|
|
|
23845
23845
|
/**
|
|
23846
23846
|
* @internal
|
|
23847
23847
|
*/
|
|
23848
|
-
getRootString(
|
|
23849
|
-
return
|
|
23848
|
+
getRootString(path32) {
|
|
23849
|
+
return path32.startsWith("/") ? "/" : "";
|
|
23850
23850
|
}
|
|
23851
23851
|
/**
|
|
23852
23852
|
* @internal
|
|
@@ -23935,11 +23935,11 @@ var init_esm7 = __esm({
|
|
|
23935
23935
|
/**
|
|
23936
23936
|
* Get the depth of a provided path, string, or the cwd
|
|
23937
23937
|
*/
|
|
23938
|
-
depth(
|
|
23939
|
-
if (typeof
|
|
23940
|
-
|
|
23938
|
+
depth(path32 = this.cwd) {
|
|
23939
|
+
if (typeof path32 === "string") {
|
|
23940
|
+
path32 = this.cwd.resolve(path32);
|
|
23941
23941
|
}
|
|
23942
|
-
return
|
|
23942
|
+
return path32.depth();
|
|
23943
23943
|
}
|
|
23944
23944
|
/**
|
|
23945
23945
|
* Return the cache of child entries. Exposed so subclasses can create
|
|
@@ -24426,9 +24426,9 @@ var init_esm7 = __esm({
|
|
|
24426
24426
|
process3();
|
|
24427
24427
|
return results;
|
|
24428
24428
|
}
|
|
24429
|
-
chdir(
|
|
24429
|
+
chdir(path32 = this.cwd) {
|
|
24430
24430
|
const oldCwd = this.cwd;
|
|
24431
|
-
this.cwd = typeof
|
|
24431
|
+
this.cwd = typeof path32 === "string" ? this.cwd.resolve(path32) : path32;
|
|
24432
24432
|
this.cwd[setAsCwd](oldCwd);
|
|
24433
24433
|
}
|
|
24434
24434
|
};
|
|
@@ -24809,8 +24809,8 @@ var init_processor = __esm({
|
|
|
24809
24809
|
}
|
|
24810
24810
|
// match, absolute, ifdir
|
|
24811
24811
|
entries() {
|
|
24812
|
-
return [...this.store.entries()].map(([
|
|
24813
|
-
|
|
24812
|
+
return [...this.store.entries()].map(([path32, n]) => [
|
|
24813
|
+
path32,
|
|
24814
24814
|
!!(n & 2),
|
|
24815
24815
|
!!(n & 1)
|
|
24816
24816
|
]);
|
|
@@ -25025,9 +25025,9 @@ var init_walker = __esm({
|
|
|
25025
25025
|
signal;
|
|
25026
25026
|
maxDepth;
|
|
25027
25027
|
includeChildMatches;
|
|
25028
|
-
constructor(patterns,
|
|
25028
|
+
constructor(patterns, path32, opts) {
|
|
25029
25029
|
this.patterns = patterns;
|
|
25030
|
-
this.path =
|
|
25030
|
+
this.path = path32;
|
|
25031
25031
|
this.opts = opts;
|
|
25032
25032
|
this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
|
|
25033
25033
|
this.includeChildMatches = opts.includeChildMatches !== false;
|
|
@@ -25046,11 +25046,11 @@ var init_walker = __esm({
|
|
|
25046
25046
|
});
|
|
25047
25047
|
}
|
|
25048
25048
|
}
|
|
25049
|
-
#ignored(
|
|
25050
|
-
return this.seen.has(
|
|
25049
|
+
#ignored(path32) {
|
|
25050
|
+
return this.seen.has(path32) || !!this.#ignore?.ignored?.(path32);
|
|
25051
25051
|
}
|
|
25052
|
-
#childrenIgnored(
|
|
25053
|
-
return !!this.#ignore?.childrenIgnored?.(
|
|
25052
|
+
#childrenIgnored(path32) {
|
|
25053
|
+
return !!this.#ignore?.childrenIgnored?.(path32);
|
|
25054
25054
|
}
|
|
25055
25055
|
// backpressure mechanism
|
|
25056
25056
|
pause() {
|
|
@@ -25265,8 +25265,8 @@ var init_walker = __esm({
|
|
|
25265
25265
|
};
|
|
25266
25266
|
GlobWalker = class extends GlobUtil {
|
|
25267
25267
|
matches = /* @__PURE__ */ new Set();
|
|
25268
|
-
constructor(patterns,
|
|
25269
|
-
super(patterns,
|
|
25268
|
+
constructor(patterns, path32, opts) {
|
|
25269
|
+
super(patterns, path32, opts);
|
|
25270
25270
|
}
|
|
25271
25271
|
matchEmit(e) {
|
|
25272
25272
|
this.matches.add(e);
|
|
@@ -25303,8 +25303,8 @@ var init_walker = __esm({
|
|
|
25303
25303
|
};
|
|
25304
25304
|
GlobStream = class extends GlobUtil {
|
|
25305
25305
|
results;
|
|
25306
|
-
constructor(patterns,
|
|
25307
|
-
super(patterns,
|
|
25306
|
+
constructor(patterns, path32, opts) {
|
|
25307
|
+
super(patterns, path32, opts);
|
|
25308
25308
|
this.results = new Minipass({
|
|
25309
25309
|
signal: this.signal,
|
|
25310
25310
|
objectMode: true
|
|
@@ -25634,6 +25634,26 @@ var init_esm8 = __esm({
|
|
|
25634
25634
|
});
|
|
25635
25635
|
|
|
25636
25636
|
// src/mcp/utils/fs.ts
|
|
25637
|
+
var fs_exports = {};
|
|
25638
|
+
__export(fs_exports, {
|
|
25639
|
+
FileSystemError: () => FileSystemError,
|
|
25640
|
+
copyFile: () => copyFile,
|
|
25641
|
+
directoryExists: () => directoryExists,
|
|
25642
|
+
ensureDirectory: () => ensureDirectory,
|
|
25643
|
+
fileExists: () => fileExists,
|
|
25644
|
+
findDirectories: () => findDirectories,
|
|
25645
|
+
findFiles: () => findFiles,
|
|
25646
|
+
normalizePath: () => normalizePath,
|
|
25647
|
+
readJson: () => readJson,
|
|
25648
|
+
readText: () => readText,
|
|
25649
|
+
relativePath: () => relativePath,
|
|
25650
|
+
removeDirectory: () => removeDirectory,
|
|
25651
|
+
removeFile: () => removeFile,
|
|
25652
|
+
safeJoinPath: () => safeJoinPath,
|
|
25653
|
+
validatePathSecurity: () => validatePathSecurity,
|
|
25654
|
+
writeJson: () => writeJson,
|
|
25655
|
+
writeText: () => writeText
|
|
25656
|
+
});
|
|
25637
25657
|
import { stat, mkdir, readFile, writeFile, cp, rm } from "fs/promises";
|
|
25638
25658
|
import path3 from "path";
|
|
25639
25659
|
function validatePathSecurity(targetPath, baseDir) {
|
|
@@ -25697,6 +25717,20 @@ async function readJson(filePath) {
|
|
|
25697
25717
|
);
|
|
25698
25718
|
}
|
|
25699
25719
|
}
|
|
25720
|
+
async function writeJson(filePath, data) {
|
|
25721
|
+
try {
|
|
25722
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
25723
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
25724
|
+
} catch (error2) {
|
|
25725
|
+
const err = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
25726
|
+
throw new FileSystemError(
|
|
25727
|
+
`Failed to write JSON file: ${filePath} - ${err.message}`,
|
|
25728
|
+
"writeJson",
|
|
25729
|
+
filePath,
|
|
25730
|
+
err
|
|
25731
|
+
);
|
|
25732
|
+
}
|
|
25733
|
+
}
|
|
25700
25734
|
async function readText(filePath) {
|
|
25701
25735
|
try {
|
|
25702
25736
|
return await readFile(filePath, "utf-8");
|
|
@@ -25724,6 +25758,16 @@ async function writeText(filePath, content) {
|
|
|
25724
25758
|
);
|
|
25725
25759
|
}
|
|
25726
25760
|
}
|
|
25761
|
+
async function copyFile(src, dest) {
|
|
25762
|
+
await mkdir(path3.dirname(dest), { recursive: true });
|
|
25763
|
+
await cp(src, dest, { recursive: true });
|
|
25764
|
+
}
|
|
25765
|
+
async function removeFile(filePath) {
|
|
25766
|
+
await rm(filePath, { force: true });
|
|
25767
|
+
}
|
|
25768
|
+
async function removeDirectory(dirPath) {
|
|
25769
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
25770
|
+
}
|
|
25727
25771
|
async function findFiles(pattern, options = {}) {
|
|
25728
25772
|
const { cwd = process.cwd(), ignore = [] } = options;
|
|
25729
25773
|
const files = await glob(pattern, {
|
|
@@ -25734,6 +25778,27 @@ async function findFiles(pattern, options = {}) {
|
|
|
25734
25778
|
});
|
|
25735
25779
|
return files;
|
|
25736
25780
|
}
|
|
25781
|
+
async function findDirectories(pattern, options = {}) {
|
|
25782
|
+
const { cwd = process.cwd(), ignore = [] } = options;
|
|
25783
|
+
const dirs = await glob(pattern, {
|
|
25784
|
+
cwd,
|
|
25785
|
+
ignore: ["**/node_modules/**", "**/bin/**", "**/obj/**", ...ignore],
|
|
25786
|
+
absolute: true
|
|
25787
|
+
});
|
|
25788
|
+
const results = [];
|
|
25789
|
+
for (const dir of dirs) {
|
|
25790
|
+
if (await directoryExists(dir)) {
|
|
25791
|
+
results.push(dir);
|
|
25792
|
+
}
|
|
25793
|
+
}
|
|
25794
|
+
return results;
|
|
25795
|
+
}
|
|
25796
|
+
function relativePath(from, to) {
|
|
25797
|
+
return path3.relative(from, to).replace(/\\/g, "/");
|
|
25798
|
+
}
|
|
25799
|
+
function normalizePath(filePath) {
|
|
25800
|
+
return filePath.replace(/\\/g, "/");
|
|
25801
|
+
}
|
|
25737
25802
|
var FileSystemError;
|
|
25738
25803
|
var init_fs = __esm({
|
|
25739
25804
|
"src/mcp/utils/fs.ts"() {
|
|
@@ -25741,10 +25806,10 @@ var init_fs = __esm({
|
|
|
25741
25806
|
init_esm_shims();
|
|
25742
25807
|
init_esm8();
|
|
25743
25808
|
FileSystemError = class extends Error {
|
|
25744
|
-
constructor(message, operation,
|
|
25809
|
+
constructor(message, operation, path32, cause) {
|
|
25745
25810
|
super(message);
|
|
25746
25811
|
this.operation = operation;
|
|
25747
|
-
this.path =
|
|
25812
|
+
this.path = path32;
|
|
25748
25813
|
this.cause = cause;
|
|
25749
25814
|
this.name = "FileSystemError";
|
|
25750
25815
|
}
|
|
@@ -25787,7 +25852,7 @@ function tenantModeToTemplateFlags(mode) {
|
|
|
25787
25852
|
tenantMode: mode
|
|
25788
25853
|
};
|
|
25789
25854
|
}
|
|
25790
|
-
var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, TenantModeSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
|
|
25855
|
+
var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, TenantModeSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, ScaffoldDataExportInputSchema, PromptDataExportM2mInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
|
|
25791
25856
|
var init_types3 = __esm({
|
|
25792
25857
|
"src/mcp/types/index.ts"() {
|
|
25793
25858
|
"use strict";
|
|
@@ -26067,6 +26132,26 @@ var init_types3 = __esm({
|
|
|
26067
26132
|
target: external_exports.enum(["pages", "components", "forms", "tables", "all"]).default("all").describe("Type of components to analyze"),
|
|
26068
26133
|
filter: external_exports.string().optional().describe('Filter by file name pattern (e.g., "User*")')
|
|
26069
26134
|
});
|
|
26135
|
+
ScaffoldDataExportInputSchema = external_exports.object({
|
|
26136
|
+
name: external_exports.string().min(1).describe('Entity name in PascalCase (e.g., "Product", "Order")'),
|
|
26137
|
+
navRoute: external_exports.string().min(1).describe('NavRoute of the source entity (e.g., "business.sales.products") \u2014 used for permission derivation'),
|
|
26138
|
+
options: external_exports.object({
|
|
26139
|
+
entityProperties: external_exports.array(EntityPropertySchema).optional().describe("Properties for the export DTO"),
|
|
26140
|
+
rateLimitPerMinute: external_exports.number().int().positive().default(60).describe("Rate limit per minute (default: 60)"),
|
|
26141
|
+
maxPageSize: external_exports.number().int().positive().default(1e3).describe("Max page size (default: 1000)"),
|
|
26142
|
+
includeAuditFields: external_exports.boolean().default(true).describe("Include CreatedAt/UpdatedAt in DTO (default: true)"),
|
|
26143
|
+
includeFrontendClient: external_exports.boolean().default(false).describe("Generate TypeScript API client (default: false)"),
|
|
26144
|
+
dryRun: external_exports.boolean().default(false).describe("Preview without writing files (default: false)")
|
|
26145
|
+
}).optional()
|
|
26146
|
+
});
|
|
26147
|
+
PromptDataExportM2mInputSchema = external_exports.object({
|
|
26148
|
+
endpoints: external_exports.array(external_exports.string()).optional().describe('Endpoint codes to include (e.g., ["users", "tenants"]). If empty, discovers all available endpoints.'),
|
|
26149
|
+
clientId: external_exports.string().optional().describe("Optional clientId to pre-fill in the prompt"),
|
|
26150
|
+
options: external_exports.object({
|
|
26151
|
+
includeBasePrompt: external_exports.boolean().default(true).describe("Include authentication and common sections (default: true)"),
|
|
26152
|
+
format: external_exports.enum(["full", "endpoints-only"]).default("full").describe('Output format: "full" or "endpoints-only"')
|
|
26153
|
+
}).optional()
|
|
26154
|
+
});
|
|
26070
26155
|
AnalyzeHierarchyPatternsInputSchema = external_exports.object({
|
|
26071
26156
|
path: external_exports.string().optional().describe("Project path to analyze (default: SmartStack.app path)"),
|
|
26072
26157
|
entityFilter: external_exports.string().optional().describe('Filter entities by name pattern (e.g., "User*", "*Group*")'),
|
|
@@ -26394,7 +26479,8 @@ var init_config = __esm({
|
|
|
26394
26479
|
"ref_",
|
|
26395
26480
|
"loc_",
|
|
26396
26481
|
"lic_",
|
|
26397
|
-
"tenant_"
|
|
26482
|
+
"tenant_",
|
|
26483
|
+
"ext_"
|
|
26398
26484
|
],
|
|
26399
26485
|
customTablePrefixes: [],
|
|
26400
26486
|
scopeTypes: ["Core", "Extension", "Partner", "Community"],
|
|
@@ -27258,8 +27344,8 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27258
27344
|
const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
27259
27345
|
if (!routeAttrMatch) continue;
|
|
27260
27346
|
const routeValue = routeAttrMatch[1];
|
|
27261
|
-
const
|
|
27262
|
-
const dirParts = path8.dirname(
|
|
27347
|
+
const relativePath2 = path8.relative(structure.api, file);
|
|
27348
|
+
const dirParts = path8.dirname(relativePath2).split(path8.sep);
|
|
27263
27349
|
const moduleDir = dirParts.slice(0, Math.min(dirParts.length, 3)).join("/");
|
|
27264
27350
|
if (!routesByDirectory.has(moduleDir)) {
|
|
27265
27351
|
routesByDirectory.set(moduleDir, []);
|
|
@@ -29863,13 +29949,13 @@ var require_ast = __commonJS({
|
|
|
29863
29949
|
helperExpression: function helperExpression(node) {
|
|
29864
29950
|
return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
|
|
29865
29951
|
},
|
|
29866
|
-
scopedId: function scopedId(
|
|
29867
|
-
return /^\.|this\b/.test(
|
|
29952
|
+
scopedId: function scopedId(path32) {
|
|
29953
|
+
return /^\.|this\b/.test(path32.original);
|
|
29868
29954
|
},
|
|
29869
29955
|
// an ID is simple if it only has one part, and that part is not
|
|
29870
29956
|
// `..` or `this`.
|
|
29871
|
-
simpleId: function simpleId(
|
|
29872
|
-
return
|
|
29957
|
+
simpleId: function simpleId(path32) {
|
|
29958
|
+
return path32.parts.length === 1 && !AST2.helpers.scopedId(path32) && !path32.depth;
|
|
29873
29959
|
}
|
|
29874
29960
|
}
|
|
29875
29961
|
};
|
|
@@ -30943,12 +31029,12 @@ var require_helpers2 = __commonJS({
|
|
|
30943
31029
|
loc
|
|
30944
31030
|
};
|
|
30945
31031
|
}
|
|
30946
|
-
function prepareMustache(
|
|
31032
|
+
function prepareMustache(path32, params, hash, open, strip, locInfo) {
|
|
30947
31033
|
var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
30948
31034
|
var decorator = /\*/.test(open);
|
|
30949
31035
|
return {
|
|
30950
31036
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
30951
|
-
path:
|
|
31037
|
+
path: path32,
|
|
30952
31038
|
params,
|
|
30953
31039
|
hash,
|
|
30954
31040
|
escaped,
|
|
@@ -31220,9 +31306,9 @@ var require_compiler = __commonJS({
|
|
|
31220
31306
|
},
|
|
31221
31307
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
31222
31308
|
var program = decorator.program && this.compileProgram(decorator.program);
|
|
31223
|
-
var params = this.setupFullMustacheParams(decorator, program, void 0),
|
|
31309
|
+
var params = this.setupFullMustacheParams(decorator, program, void 0), path32 = decorator.path;
|
|
31224
31310
|
this.useDecorators = true;
|
|
31225
|
-
this.opcode("registerDecorator", params.length,
|
|
31311
|
+
this.opcode("registerDecorator", params.length, path32.original);
|
|
31226
31312
|
},
|
|
31227
31313
|
PartialStatement: function PartialStatement(partial2) {
|
|
31228
31314
|
this.usePartial = true;
|
|
@@ -31286,46 +31372,46 @@ var require_compiler = __commonJS({
|
|
|
31286
31372
|
}
|
|
31287
31373
|
},
|
|
31288
31374
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
|
|
31289
|
-
var
|
|
31290
|
-
this.opcode("getContext",
|
|
31375
|
+
var path32 = sexpr.path, name = path32.parts[0], isBlock = program != null || inverse != null;
|
|
31376
|
+
this.opcode("getContext", path32.depth);
|
|
31291
31377
|
this.opcode("pushProgram", program);
|
|
31292
31378
|
this.opcode("pushProgram", inverse);
|
|
31293
|
-
|
|
31294
|
-
this.accept(
|
|
31379
|
+
path32.strict = true;
|
|
31380
|
+
this.accept(path32);
|
|
31295
31381
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
31296
31382
|
},
|
|
31297
31383
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
31298
|
-
var
|
|
31299
|
-
|
|
31300
|
-
this.accept(
|
|
31384
|
+
var path32 = sexpr.path;
|
|
31385
|
+
path32.strict = true;
|
|
31386
|
+
this.accept(path32);
|
|
31301
31387
|
this.opcode("resolvePossibleLambda");
|
|
31302
31388
|
},
|
|
31303
31389
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
31304
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
31390
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path32 = sexpr.path, name = path32.parts[0];
|
|
31305
31391
|
if (this.options.knownHelpers[name]) {
|
|
31306
31392
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
31307
31393
|
} else if (this.options.knownHelpersOnly) {
|
|
31308
31394
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
31309
31395
|
} else {
|
|
31310
|
-
|
|
31311
|
-
|
|
31312
|
-
this.accept(
|
|
31313
|
-
this.opcode("invokeHelper", params.length,
|
|
31396
|
+
path32.strict = true;
|
|
31397
|
+
path32.falsy = true;
|
|
31398
|
+
this.accept(path32);
|
|
31399
|
+
this.opcode("invokeHelper", params.length, path32.original, _ast2["default"].helpers.simpleId(path32));
|
|
31314
31400
|
}
|
|
31315
31401
|
},
|
|
31316
|
-
PathExpression: function PathExpression(
|
|
31317
|
-
this.addDepth(
|
|
31318
|
-
this.opcode("getContext",
|
|
31319
|
-
var name =
|
|
31402
|
+
PathExpression: function PathExpression(path32) {
|
|
31403
|
+
this.addDepth(path32.depth);
|
|
31404
|
+
this.opcode("getContext", path32.depth);
|
|
31405
|
+
var name = path32.parts[0], scoped = _ast2["default"].helpers.scopedId(path32), blockParamId = !path32.depth && !scoped && this.blockParamIndex(name);
|
|
31320
31406
|
if (blockParamId) {
|
|
31321
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
31407
|
+
this.opcode("lookupBlockParam", blockParamId, path32.parts);
|
|
31322
31408
|
} else if (!name) {
|
|
31323
31409
|
this.opcode("pushContext");
|
|
31324
|
-
} else if (
|
|
31410
|
+
} else if (path32.data) {
|
|
31325
31411
|
this.options.data = true;
|
|
31326
|
-
this.opcode("lookupData",
|
|
31412
|
+
this.opcode("lookupData", path32.depth, path32.parts, path32.strict);
|
|
31327
31413
|
} else {
|
|
31328
|
-
this.opcode("lookupOnContext",
|
|
31414
|
+
this.opcode("lookupOnContext", path32.parts, path32.falsy, path32.strict, scoped);
|
|
31329
31415
|
}
|
|
31330
31416
|
},
|
|
31331
31417
|
StringLiteral: function StringLiteral(string3) {
|
|
@@ -31681,16 +31767,16 @@ var require_util2 = __commonJS({
|
|
|
31681
31767
|
}
|
|
31682
31768
|
exports.urlGenerate = urlGenerate;
|
|
31683
31769
|
function normalize2(aPath) {
|
|
31684
|
-
var
|
|
31770
|
+
var path32 = aPath;
|
|
31685
31771
|
var url2 = urlParse(aPath);
|
|
31686
31772
|
if (url2) {
|
|
31687
31773
|
if (!url2.path) {
|
|
31688
31774
|
return aPath;
|
|
31689
31775
|
}
|
|
31690
|
-
|
|
31776
|
+
path32 = url2.path;
|
|
31691
31777
|
}
|
|
31692
|
-
var isAbsolute = exports.isAbsolute(
|
|
31693
|
-
var parts =
|
|
31778
|
+
var isAbsolute = exports.isAbsolute(path32);
|
|
31779
|
+
var parts = path32.split(/\/+/);
|
|
31694
31780
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
31695
31781
|
part = parts[i];
|
|
31696
31782
|
if (part === ".") {
|
|
@@ -31707,15 +31793,15 @@ var require_util2 = __commonJS({
|
|
|
31707
31793
|
}
|
|
31708
31794
|
}
|
|
31709
31795
|
}
|
|
31710
|
-
|
|
31711
|
-
if (
|
|
31712
|
-
|
|
31796
|
+
path32 = parts.join("/");
|
|
31797
|
+
if (path32 === "") {
|
|
31798
|
+
path32 = isAbsolute ? "/" : ".";
|
|
31713
31799
|
}
|
|
31714
31800
|
if (url2) {
|
|
31715
|
-
url2.path =
|
|
31801
|
+
url2.path = path32;
|
|
31716
31802
|
return urlGenerate(url2);
|
|
31717
31803
|
}
|
|
31718
|
-
return
|
|
31804
|
+
return path32;
|
|
31719
31805
|
}
|
|
31720
31806
|
exports.normalize = normalize2;
|
|
31721
31807
|
function join2(aRoot, aPath) {
|
|
@@ -34516,8 +34602,8 @@ var require_printer = __commonJS({
|
|
|
34516
34602
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
34517
34603
|
};
|
|
34518
34604
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
34519
|
-
var
|
|
34520
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
34605
|
+
var path32 = id.parts.join("/");
|
|
34606
|
+
return (id.data ? "@" : "") + "PATH:" + path32;
|
|
34521
34607
|
};
|
|
34522
34608
|
PrintVisitor.prototype.StringLiteral = function(string3) {
|
|
34523
34609
|
return '"' + string3.value + '"';
|
|
@@ -46114,11 +46200,11 @@ var require_mime_types = __commonJS({
|
|
|
46114
46200
|
}
|
|
46115
46201
|
return exts[0];
|
|
46116
46202
|
}
|
|
46117
|
-
function lookup(
|
|
46118
|
-
if (!
|
|
46203
|
+
function lookup(path32) {
|
|
46204
|
+
if (!path32 || typeof path32 !== "string") {
|
|
46119
46205
|
return false;
|
|
46120
46206
|
}
|
|
46121
|
-
var extension2 = extname("x." +
|
|
46207
|
+
var extension2 = extname("x." + path32).toLowerCase().substr(1);
|
|
46122
46208
|
if (!extension2) {
|
|
46123
46209
|
return false;
|
|
46124
46210
|
}
|
|
@@ -47281,7 +47367,7 @@ var require_form_data = __commonJS({
|
|
|
47281
47367
|
init_esm_shims();
|
|
47282
47368
|
var CombinedStream = require_combined_stream();
|
|
47283
47369
|
var util4 = __require("util");
|
|
47284
|
-
var
|
|
47370
|
+
var path32 = __require("path");
|
|
47285
47371
|
var http3 = __require("http");
|
|
47286
47372
|
var https2 = __require("https");
|
|
47287
47373
|
var parseUrl = __require("url").parse;
|
|
@@ -47409,11 +47495,11 @@ var require_form_data = __commonJS({
|
|
|
47409
47495
|
FormData3.prototype._getContentDisposition = function(value, options) {
|
|
47410
47496
|
var filename;
|
|
47411
47497
|
if (typeof options.filepath === "string") {
|
|
47412
|
-
filename =
|
|
47498
|
+
filename = path32.normalize(options.filepath).replace(/\\/g, "/");
|
|
47413
47499
|
} else if (options.filename || value && (value.name || value.path)) {
|
|
47414
|
-
filename =
|
|
47500
|
+
filename = path32.basename(options.filename || value && (value.name || value.path));
|
|
47415
47501
|
} else if (value && value.readable && hasOwn(value, "httpVersion")) {
|
|
47416
|
-
filename =
|
|
47502
|
+
filename = path32.basename(value.client._httpMessage.path || "");
|
|
47417
47503
|
}
|
|
47418
47504
|
if (filename) {
|
|
47419
47505
|
return 'filename="' + filename + '"';
|
|
@@ -47612,9 +47698,9 @@ function isVisitable(thing) {
|
|
|
47612
47698
|
function removeBrackets(key) {
|
|
47613
47699
|
return utils_default.endsWith(key, "[]") ? key.slice(0, -2) : key;
|
|
47614
47700
|
}
|
|
47615
|
-
function renderKey(
|
|
47616
|
-
if (!
|
|
47617
|
-
return
|
|
47701
|
+
function renderKey(path32, key, dots) {
|
|
47702
|
+
if (!path32) return key;
|
|
47703
|
+
return path32.concat(key).map(function each(token, i) {
|
|
47618
47704
|
token = removeBrackets(token);
|
|
47619
47705
|
return !dots && i ? "[" + token + "]" : token;
|
|
47620
47706
|
}).join(dots ? "." : "");
|
|
@@ -47659,9 +47745,9 @@ function toFormData(obj, formData, options) {
|
|
|
47659
47745
|
}
|
|
47660
47746
|
return value;
|
|
47661
47747
|
}
|
|
47662
|
-
function defaultVisitor(value, key,
|
|
47748
|
+
function defaultVisitor(value, key, path32) {
|
|
47663
47749
|
let arr = value;
|
|
47664
|
-
if (value && !
|
|
47750
|
+
if (value && !path32 && typeof value === "object") {
|
|
47665
47751
|
if (utils_default.endsWith(key, "{}")) {
|
|
47666
47752
|
key = metaTokens ? key : key.slice(0, -2);
|
|
47667
47753
|
value = JSON.stringify(value);
|
|
@@ -47680,7 +47766,7 @@ function toFormData(obj, formData, options) {
|
|
|
47680
47766
|
if (isVisitable(value)) {
|
|
47681
47767
|
return true;
|
|
47682
47768
|
}
|
|
47683
|
-
formData.append(renderKey(
|
|
47769
|
+
formData.append(renderKey(path32, key, dots), convertValue(value));
|
|
47684
47770
|
return false;
|
|
47685
47771
|
}
|
|
47686
47772
|
const stack = [];
|
|
@@ -47689,10 +47775,10 @@ function toFormData(obj, formData, options) {
|
|
|
47689
47775
|
convertValue,
|
|
47690
47776
|
isVisitable
|
|
47691
47777
|
});
|
|
47692
|
-
function build(value,
|
|
47778
|
+
function build(value, path32) {
|
|
47693
47779
|
if (utils_default.isUndefined(value)) return;
|
|
47694
47780
|
if (stack.indexOf(value) !== -1) {
|
|
47695
|
-
throw Error("Circular reference detected in " +
|
|
47781
|
+
throw Error("Circular reference detected in " + path32.join("."));
|
|
47696
47782
|
}
|
|
47697
47783
|
stack.push(value);
|
|
47698
47784
|
utils_default.forEach(value, function each(el, key) {
|
|
@@ -47700,11 +47786,11 @@ function toFormData(obj, formData, options) {
|
|
|
47700
47786
|
formData,
|
|
47701
47787
|
el,
|
|
47702
47788
|
utils_default.isString(key) ? key.trim() : key,
|
|
47703
|
-
|
|
47789
|
+
path32,
|
|
47704
47790
|
exposedHelpers
|
|
47705
47791
|
);
|
|
47706
47792
|
if (result === true) {
|
|
47707
|
-
build(el,
|
|
47793
|
+
build(el, path32 ? path32.concat(key) : [key]);
|
|
47708
47794
|
}
|
|
47709
47795
|
});
|
|
47710
47796
|
stack.pop();
|
|
@@ -47990,7 +48076,7 @@ var init_platform = __esm({
|
|
|
47990
48076
|
// node_modules/axios/lib/helpers/toURLEncodedForm.js
|
|
47991
48077
|
function toURLEncodedForm(data, options) {
|
|
47992
48078
|
return toFormData_default(data, new platform_default.classes.URLSearchParams(), {
|
|
47993
|
-
visitor: function(value, key,
|
|
48079
|
+
visitor: function(value, key, path32, helpers) {
|
|
47994
48080
|
if (platform_default.isNode && utils_default.isBuffer(value)) {
|
|
47995
48081
|
this.append(key, value.toString("base64"));
|
|
47996
48082
|
return false;
|
|
@@ -48029,11 +48115,11 @@ function arrayToObject(arr) {
|
|
|
48029
48115
|
return obj;
|
|
48030
48116
|
}
|
|
48031
48117
|
function formDataToJSON(formData) {
|
|
48032
|
-
function buildPath(
|
|
48033
|
-
let name =
|
|
48118
|
+
function buildPath(path32, value, target, index) {
|
|
48119
|
+
let name = path32[index++];
|
|
48034
48120
|
if (name === "__proto__") return true;
|
|
48035
48121
|
const isNumericKey = Number.isFinite(+name);
|
|
48036
|
-
const isLast = index >=
|
|
48122
|
+
const isLast = index >= path32.length;
|
|
48037
48123
|
name = !name && utils_default.isArray(target) ? target.length : name;
|
|
48038
48124
|
if (isLast) {
|
|
48039
48125
|
if (utils_default.hasOwnProp(target, name)) {
|
|
@@ -48046,7 +48132,7 @@ function formDataToJSON(formData) {
|
|
|
48046
48132
|
if (!target[name] || !utils_default.isObject(target[name])) {
|
|
48047
48133
|
target[name] = [];
|
|
48048
48134
|
}
|
|
48049
|
-
const result = buildPath(
|
|
48135
|
+
const result = buildPath(path32, value, target[name], index);
|
|
48050
48136
|
if (result && utils_default.isArray(target[name])) {
|
|
48051
48137
|
target[name] = arrayToObject(target[name]);
|
|
48052
48138
|
}
|
|
@@ -50946,9 +51032,9 @@ var init_http = __esm({
|
|
|
50946
51032
|
auth = urlUsername + ":" + urlPassword;
|
|
50947
51033
|
}
|
|
50948
51034
|
auth && headers.delete("authorization");
|
|
50949
|
-
let
|
|
51035
|
+
let path32;
|
|
50950
51036
|
try {
|
|
50951
|
-
|
|
51037
|
+
path32 = buildURL(
|
|
50952
51038
|
parsed.pathname + parsed.search,
|
|
50953
51039
|
config2.params,
|
|
50954
51040
|
config2.paramsSerializer
|
|
@@ -50966,7 +51052,7 @@ var init_http = __esm({
|
|
|
50966
51052
|
false
|
|
50967
51053
|
);
|
|
50968
51054
|
const options = {
|
|
50969
|
-
path:
|
|
51055
|
+
path: path32,
|
|
50970
51056
|
method,
|
|
50971
51057
|
headers: headers.toJSON(),
|
|
50972
51058
|
agents: { http: config2.httpAgent, https: config2.httpsAgent },
|
|
@@ -51219,14 +51305,14 @@ var init_cookies = __esm({
|
|
|
51219
51305
|
cookies_default = platform_default.hasStandardBrowserEnv ? (
|
|
51220
51306
|
// Standard browser envs support document.cookie
|
|
51221
51307
|
{
|
|
51222
|
-
write(name, value, expires,
|
|
51308
|
+
write(name, value, expires, path32, domain, secure, sameSite) {
|
|
51223
51309
|
if (typeof document === "undefined") return;
|
|
51224
51310
|
const cookie = [`${name}=${encodeURIComponent(value)}`];
|
|
51225
51311
|
if (utils_default.isNumber(expires)) {
|
|
51226
51312
|
cookie.push(`expires=${new Date(expires).toUTCString()}`);
|
|
51227
51313
|
}
|
|
51228
|
-
if (utils_default.isString(
|
|
51229
|
-
cookie.push(`path=${
|
|
51314
|
+
if (utils_default.isString(path32)) {
|
|
51315
|
+
cookie.push(`path=${path32}`);
|
|
51230
51316
|
}
|
|
51231
51317
|
if (utils_default.isString(domain)) {
|
|
51232
51318
|
cookie.push(`domain=${domain}`);
|
|
@@ -57587,8 +57673,8 @@ function formatResult4(result, input) {
|
|
|
57587
57673
|
lines.push("## Generated Files");
|
|
57588
57674
|
lines.push("");
|
|
57589
57675
|
for (const file of result.files) {
|
|
57590
|
-
const
|
|
57591
|
-
lines.push(`### ${
|
|
57676
|
+
const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
|
|
57677
|
+
lines.push(`### ${relativePath2}`);
|
|
57592
57678
|
lines.push("");
|
|
57593
57679
|
lines.push("```typescript");
|
|
57594
57680
|
lines.push(file.content.substring(0, 1500) + (file.content.length > 1500 ? "\n// ... (truncated)" : ""));
|
|
@@ -58473,8 +58559,8 @@ function formatResult5(result, input) {
|
|
|
58473
58559
|
lines.push("## Generated Files");
|
|
58474
58560
|
lines.push("");
|
|
58475
58561
|
for (const file of result.files) {
|
|
58476
|
-
const
|
|
58477
|
-
lines.push(`### ${
|
|
58562
|
+
const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
|
|
58563
|
+
lines.push(`### ${relativePath2}`);
|
|
58478
58564
|
lines.push("");
|
|
58479
58565
|
lines.push("```tsx");
|
|
58480
58566
|
lines.push(file.content.substring(0, 2e3) + (file.content.length > 2e3 ? "\n// ... (truncated)" : ""));
|
|
@@ -58690,7 +58776,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58690
58776
|
for (const file of clientFiles) {
|
|
58691
58777
|
try {
|
|
58692
58778
|
const content = await readText(file);
|
|
58693
|
-
const
|
|
58779
|
+
const relativePath2 = path20.relative(webPath, file);
|
|
58694
58780
|
const usesRegistry = content.includes("getRoute('") || content.includes('getRoute("');
|
|
58695
58781
|
if (!usesRegistry) {
|
|
58696
58782
|
const hardcodedMatch = content.match(/apiClient\.(get|post|put|delete)\s*[<(]\s*['"`]([^'"`]+)['"`]/);
|
|
@@ -58698,7 +58784,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58698
58784
|
result.apiClients.issues.push({
|
|
58699
58785
|
type: "invalid-path",
|
|
58700
58786
|
severity: "warning",
|
|
58701
|
-
file:
|
|
58787
|
+
file: relativePath2,
|
|
58702
58788
|
message: `Hardcoded API path: ${hardcodedMatch[2]}`,
|
|
58703
58789
|
suggestion: "Use getRoute() from navRoutes.generated.ts instead"
|
|
58704
58790
|
});
|
|
@@ -58712,7 +58798,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58712
58798
|
result.apiClients.issues.push({
|
|
58713
58799
|
type: "missing-route",
|
|
58714
58800
|
severity: "error",
|
|
58715
|
-
file:
|
|
58801
|
+
file: relativePath2,
|
|
58716
58802
|
navRoute,
|
|
58717
58803
|
message: `NavRoute "${navRoute}" not found in backend controllers`,
|
|
58718
58804
|
suggestion: "Verify the NavRoute path or update the backend controller"
|
|
@@ -60093,11 +60179,11 @@ async function analyzeFile(filePath, srcPath, target) {
|
|
|
60093
60179
|
try {
|
|
60094
60180
|
const content = await readText(filePath);
|
|
60095
60181
|
if (!content) return null;
|
|
60096
|
-
const
|
|
60182
|
+
const relativePath2 = path21.relative(srcPath, filePath).replace(/\\/g, "/");
|
|
60097
60183
|
const componentName = extractComponentName(content, filePath);
|
|
60098
60184
|
const isPage = filePath.includes("/pages/") || filePath.includes("\\pages\\");
|
|
60099
60185
|
const analysis = {
|
|
60100
|
-
file:
|
|
60186
|
+
file: relativePath2,
|
|
60101
60187
|
componentName,
|
|
60102
60188
|
type: isPage ? "page" : "component",
|
|
60103
60189
|
suggestedSlots: [],
|
|
@@ -60107,7 +60193,7 @@ async function analyzeFile(filePath, srcPath, target) {
|
|
|
60107
60193
|
imports: extractImports(content),
|
|
60108
60194
|
lineCount: content.split("\n").length
|
|
60109
60195
|
};
|
|
60110
|
-
const slotPrefix = generateSlotPrefix(componentName,
|
|
60196
|
+
const slotPrefix = generateSlotPrefix(componentName, relativePath2);
|
|
60111
60197
|
if (target === "forms" && !analysis.hasForm) return null;
|
|
60112
60198
|
if (target === "tables" && !analysis.hasTable) return null;
|
|
60113
60199
|
if (isPage) {
|
|
@@ -62091,8 +62177,1105 @@ var init_analyze_code_quality = __esm({
|
|
|
62091
62177
|
}
|
|
62092
62178
|
});
|
|
62093
62179
|
|
|
62094
|
-
// src/mcp/tools/
|
|
62180
|
+
// src/mcp/tools/scaffold-data-export.ts
|
|
62095
62181
|
import path25 from "path";
|
|
62182
|
+
async function handleScaffoldDataExport(args, config2) {
|
|
62183
|
+
const input = ScaffoldDataExportInputSchema.parse(args);
|
|
62184
|
+
logger.info("Scaffolding Data Export", { name: input.name, navRoute: input.navRoute });
|
|
62185
|
+
const result = await scaffoldDataExport(input, config2);
|
|
62186
|
+
return formatResult9(result, input);
|
|
62187
|
+
}
|
|
62188
|
+
async function scaffoldDataExport(input, config2) {
|
|
62189
|
+
const result = {
|
|
62190
|
+
success: true,
|
|
62191
|
+
files: [],
|
|
62192
|
+
instructions: []
|
|
62193
|
+
};
|
|
62194
|
+
const { name, navRoute, options } = input;
|
|
62195
|
+
const dryRun = options?.dryRun ?? false;
|
|
62196
|
+
const includeAuditFields = options?.includeAuditFields ?? true;
|
|
62197
|
+
const includeFrontendClient = options?.includeFrontendClient ?? false;
|
|
62198
|
+
const rateLimitPerMinute = options?.rateLimitPerMinute ?? 60;
|
|
62199
|
+
const maxPageSize = options?.maxPageSize ?? 1e3;
|
|
62200
|
+
const entityProperties = options?.entityProperties ?? [];
|
|
62201
|
+
const nameLower = name.charAt(0).toLowerCase() + name.slice(1);
|
|
62202
|
+
const namePlural = pluralize(nameLower);
|
|
62203
|
+
const segments = navRoute.split(".");
|
|
62204
|
+
const permissionPath = segments.join(".");
|
|
62205
|
+
const baseNamespace = config2.conventions.namespaces.api.replace(".Api", "");
|
|
62206
|
+
const appSegment = segments.length >= 2 ? toPascalCase2(segments[1]) : "Default";
|
|
62207
|
+
const moduleSegment = segments.length >= 3 ? toPascalCase2(segments[2]) : name;
|
|
62208
|
+
const projectRoot = config2.smartstack.projectPath;
|
|
62209
|
+
const structure = await findSmartStackStructure(projectRoot);
|
|
62210
|
+
const apiPath = structure.api || path25.join(projectRoot, "Api");
|
|
62211
|
+
const applicationPath = structure.application || path25.join(projectRoot, "Application");
|
|
62212
|
+
const infrastructurePath = structure.infrastructure || path25.join(projectRoot, "Infrastructure");
|
|
62213
|
+
const webPath = structure.web || path25.join(projectRoot, "web");
|
|
62214
|
+
const controllerContent = generateExportController(name, namePlural, permissionPath, baseNamespace, maxPageSize);
|
|
62215
|
+
const controllerFile = path25.join(apiPath, "Controllers", "DataExport", "v1", `Export${name}Controller.cs`);
|
|
62216
|
+
if (!dryRun) {
|
|
62217
|
+
await ensureDirectory(path25.dirname(controllerFile));
|
|
62218
|
+
await writeText(controllerFile, controllerContent);
|
|
62219
|
+
}
|
|
62220
|
+
result.files.push({ path: controllerFile, content: controllerContent, type: "created" });
|
|
62221
|
+
const interfaceContent = generateExportServiceInterface(name, baseNamespace);
|
|
62222
|
+
const interfaceFile = path25.join(applicationPath, "Common", "Interfaces", `IExport${name}Service.cs`);
|
|
62223
|
+
if (!dryRun) {
|
|
62224
|
+
await ensureDirectory(path25.dirname(interfaceFile));
|
|
62225
|
+
await writeText(interfaceFile, interfaceContent);
|
|
62226
|
+
}
|
|
62227
|
+
result.files.push({ path: interfaceFile, content: interfaceContent, type: "created" });
|
|
62228
|
+
const serviceContent = generateExportService(name, namePlural, baseNamespace, entityProperties, includeAuditFields);
|
|
62229
|
+
const serviceFile = path25.join(infrastructurePath, "Services", "DataExport", `Export${name}Service.cs`);
|
|
62230
|
+
if (!dryRun) {
|
|
62231
|
+
await ensureDirectory(path25.dirname(serviceFile));
|
|
62232
|
+
await writeText(serviceFile, serviceContent);
|
|
62233
|
+
}
|
|
62234
|
+
result.files.push({ path: serviceFile, content: serviceContent, type: "created" });
|
|
62235
|
+
const dtoContent = generateExportDto(name, baseNamespace, entityProperties, includeAuditFields);
|
|
62236
|
+
const dtoFile = path25.join(applicationPath, "DataExport", "Dtos", `Export${name}Dto.cs`);
|
|
62237
|
+
if (!dryRun) {
|
|
62238
|
+
await ensureDirectory(path25.dirname(dtoFile));
|
|
62239
|
+
await writeText(dtoFile, dtoContent);
|
|
62240
|
+
}
|
|
62241
|
+
result.files.push({ path: dtoFile, content: dtoContent, type: "created" });
|
|
62242
|
+
const seedContent = generateSeedDataFragment(name, namePlural, permissionPath, appSegment, moduleSegment, rateLimitPerMinute, maxPageSize);
|
|
62243
|
+
const seedFile = path25.join(infrastructurePath, "Persistence", "Seeding", "Data", "DataExport", `Export${name}EndpointSeedData.cs`);
|
|
62244
|
+
if (!dryRun) {
|
|
62245
|
+
await ensureDirectory(path25.dirname(seedFile));
|
|
62246
|
+
await writeText(seedFile, seedContent);
|
|
62247
|
+
}
|
|
62248
|
+
result.files.push({ path: seedFile, content: seedContent, type: "created" });
|
|
62249
|
+
if (includeFrontendClient) {
|
|
62250
|
+
const clientContent = generateFrontendClient(name, nameLower, namePlural);
|
|
62251
|
+
const clientFile = path25.join(webPath, "src", "services", "api", `export${name}.ts`);
|
|
62252
|
+
if (!dryRun) {
|
|
62253
|
+
await ensureDirectory(path25.dirname(clientFile));
|
|
62254
|
+
await writeText(clientFile, clientContent);
|
|
62255
|
+
}
|
|
62256
|
+
result.files.push({ path: clientFile, content: clientContent, type: "created" });
|
|
62257
|
+
}
|
|
62258
|
+
result.instructions.push(`Register IExport${name}Service in DI container (Program.cs or DependencyInjection.cs)`);
|
|
62259
|
+
result.instructions.push(`Add the export permission "${permissionPath}.export" to PermissionConfiguration.cs`);
|
|
62260
|
+
result.instructions.push(`Add the seed data to DataExportEndpointConfiguration or call the fragment from your seeder`);
|
|
62261
|
+
result.instructions.push(`Create an EF Core migration to apply the new seed data`);
|
|
62262
|
+
result.instructions.push(`Update the KNOWN_ENDPOINTS registry in prompt-data-export-m2m.ts (MCP) and ExternalAppPromptTab.tsx (frontend) with the new endpoint metadata`);
|
|
62263
|
+
result.instructions.push(`Use prompt_data_export_m2m to generate an integration prompt for this endpoint`);
|
|
62264
|
+
if (includeFrontendClient) {
|
|
62265
|
+
result.instructions.push(`Import the client: import { export${name}Api } from './services/api/export${name}';`);
|
|
62266
|
+
}
|
|
62267
|
+
return result;
|
|
62268
|
+
}
|
|
62269
|
+
function generateExportController(name, namePlural, permissionPath, baseNamespace, maxPageSize) {
|
|
62270
|
+
return `using Microsoft.AspNetCore.Authorization;
|
|
62271
|
+
using Microsoft.AspNetCore.Mvc;
|
|
62272
|
+
using Microsoft.AspNetCore.RateLimiting;
|
|
62273
|
+
using ${baseNamespace}.Application.Common.Models;
|
|
62274
|
+
using ${baseNamespace}.Application.DataExport.Dtos;
|
|
62275
|
+
using ${baseNamespace}.Application.Common.Interfaces;
|
|
62276
|
+
using ${baseNamespace}.Api.Authorization;
|
|
62277
|
+
|
|
62278
|
+
namespace ${baseNamespace}.Api.Controllers.DataExport.v1;
|
|
62279
|
+
|
|
62280
|
+
/// <summary>
|
|
62281
|
+
/// Data Export endpoint for ${name} entities.
|
|
62282
|
+
/// Security: JWT authentication + RequirePermission + Rate limiting + DataExportAccessMiddleware
|
|
62283
|
+
/// Tenant isolation: Required tenantId query parameter
|
|
62284
|
+
/// </summary>
|
|
62285
|
+
[ApiController]
|
|
62286
|
+
[Route("api/v1/export")]
|
|
62287
|
+
[Authorize]
|
|
62288
|
+
[EnableRateLimiting("ExternalApp")]
|
|
62289
|
+
public class Export${name}Controller : ControllerBase
|
|
62290
|
+
{
|
|
62291
|
+
private readonly IExport${name}Service _exportService;
|
|
62292
|
+
|
|
62293
|
+
public Export${name}Controller(IExport${name}Service exportService)
|
|
62294
|
+
{
|
|
62295
|
+
_exportService = exportService;
|
|
62296
|
+
}
|
|
62297
|
+
|
|
62298
|
+
/// <summary>
|
|
62299
|
+
/// Export ${name} data with pagination, tenant isolation, and optional modified-since filter.
|
|
62300
|
+
/// </summary>
|
|
62301
|
+
[HttpGet("${namePlural}")]
|
|
62302
|
+
[RequirePermission(Permissions.${permissionPath.split(".").map((s) => toPascalCase2(s)).join(".")}.Export)]
|
|
62303
|
+
[ProducesResponseType(typeof(PaginatedResult<Export${name}Dto>), StatusCodes.Status200OK)]
|
|
62304
|
+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
|
62305
|
+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
|
62306
|
+
public async Task<ActionResult<PaginatedResult<Export${name}Dto>>> Export(
|
|
62307
|
+
[FromQuery] Guid tenantId,
|
|
62308
|
+
[FromQuery] int page = 1,
|
|
62309
|
+
[FromQuery] int pageSize = 100,
|
|
62310
|
+
[FromQuery] DateTime? modifiedSince = null,
|
|
62311
|
+
CancellationToken ct = default)
|
|
62312
|
+
{
|
|
62313
|
+
if (tenantId == Guid.Empty)
|
|
62314
|
+
{
|
|
62315
|
+
return BadRequest(new ProblemDetails
|
|
62316
|
+
{
|
|
62317
|
+
Title = "Invalid Tenant",
|
|
62318
|
+
Detail = "tenantId is required and must be a valid GUID",
|
|
62319
|
+
Status = StatusCodes.Status400BadRequest
|
|
62320
|
+
});
|
|
62321
|
+
}
|
|
62322
|
+
|
|
62323
|
+
pageSize = Math.Clamp(pageSize, 1, ${maxPageSize});
|
|
62324
|
+
page = Math.Max(1, page);
|
|
62325
|
+
|
|
62326
|
+
var result = await _exportService.ExportAsync(tenantId, page, pageSize, modifiedSince, ct);
|
|
62327
|
+
return Ok(result);
|
|
62328
|
+
}
|
|
62329
|
+
}
|
|
62330
|
+
`;
|
|
62331
|
+
}
|
|
62332
|
+
function generateExportServiceInterface(name, baseNamespace) {
|
|
62333
|
+
return `using ${baseNamespace}.Application.Common.Models;
|
|
62334
|
+
using ${baseNamespace}.Application.DataExport.Dtos;
|
|
62335
|
+
|
|
62336
|
+
namespace ${baseNamespace}.Application.Common.Interfaces;
|
|
62337
|
+
|
|
62338
|
+
/// <summary>
|
|
62339
|
+
/// Service interface for ${name} data export.
|
|
62340
|
+
/// Enforces tenant isolation via tenantId parameter.
|
|
62341
|
+
/// </summary>
|
|
62342
|
+
public interface IExport${name}Service
|
|
62343
|
+
{
|
|
62344
|
+
/// <summary>
|
|
62345
|
+
/// Export ${name} data with tenant isolation, pagination, and optional modified-since filter.
|
|
62346
|
+
/// </summary>
|
|
62347
|
+
Task<PaginatedResult<Export${name}Dto>> ExportAsync(
|
|
62348
|
+
Guid tenantId,
|
|
62349
|
+
int page = 1,
|
|
62350
|
+
int pageSize = 100,
|
|
62351
|
+
DateTime? modifiedSince = null,
|
|
62352
|
+
CancellationToken ct = default);
|
|
62353
|
+
}
|
|
62354
|
+
`;
|
|
62355
|
+
}
|
|
62356
|
+
function generateExportService(name, namePlural, baseNamespace, entityProperties, includeAuditFields) {
|
|
62357
|
+
const projectionFields = [" Id = x.Id"];
|
|
62358
|
+
for (const prop of entityProperties) {
|
|
62359
|
+
projectionFields.push(` ${prop.name} = x.${prop.name}`);
|
|
62360
|
+
}
|
|
62361
|
+
if (includeAuditFields) {
|
|
62362
|
+
projectionFields.push(" CreatedAt = x.CreatedAt");
|
|
62363
|
+
projectionFields.push(" UpdatedAt = x.UpdatedAt");
|
|
62364
|
+
}
|
|
62365
|
+
const projectionBlock = projectionFields.join(",\n");
|
|
62366
|
+
return `using Microsoft.EntityFrameworkCore;
|
|
62367
|
+
using Microsoft.Extensions.Logging;
|
|
62368
|
+
using ${baseNamespace}.Application.Common.Interfaces;
|
|
62369
|
+
using ${baseNamespace}.Application.Common.Models;
|
|
62370
|
+
using ${baseNamespace}.Application.DataExport.Dtos;
|
|
62371
|
+
using ${baseNamespace}.Infrastructure.Persistence;
|
|
62372
|
+
|
|
62373
|
+
namespace ${baseNamespace}.Infrastructure.Services.DataExport;
|
|
62374
|
+
|
|
62375
|
+
/// <summary>
|
|
62376
|
+
/// Data export service for ${name} entities.
|
|
62377
|
+
/// Enforces tenant isolation via tenantId parameter validation.
|
|
62378
|
+
/// </summary>
|
|
62379
|
+
public class Export${name}Service : IExport${name}Service
|
|
62380
|
+
{
|
|
62381
|
+
private readonly ApplicationDbContext _db;
|
|
62382
|
+
private readonly ICurrentUserService _currentUser;
|
|
62383
|
+
private readonly ILogger<Export${name}Service> _logger;
|
|
62384
|
+
|
|
62385
|
+
public Export${name}Service(
|
|
62386
|
+
ApplicationDbContext db,
|
|
62387
|
+
ICurrentUserService currentUser,
|
|
62388
|
+
ILogger<Export${name}Service> logger)
|
|
62389
|
+
{
|
|
62390
|
+
_db = db;
|
|
62391
|
+
_currentUser = currentUser;
|
|
62392
|
+
_logger = logger;
|
|
62393
|
+
}
|
|
62394
|
+
|
|
62395
|
+
public async Task<PaginatedResult<Export${name}Dto>> ExportAsync(
|
|
62396
|
+
Guid tenantId,
|
|
62397
|
+
int page = 1,
|
|
62398
|
+
int pageSize = 100,
|
|
62399
|
+
DateTime? modifiedSince = null,
|
|
62400
|
+
CancellationToken ct = default)
|
|
62401
|
+
{
|
|
62402
|
+
if (tenantId == Guid.Empty)
|
|
62403
|
+
{
|
|
62404
|
+
throw new ArgumentException("Tenant ID cannot be empty", nameof(tenantId));
|
|
62405
|
+
}
|
|
62406
|
+
|
|
62407
|
+
_logger.LogInformation("Data export requested for ${name} by {UserId}, tenant {TenantId}, page {Page}",
|
|
62408
|
+
_currentUser.UserId, tenantId, page);
|
|
62409
|
+
|
|
62410
|
+
var query = _db.${toPascalCase2(namePlural)}
|
|
62411
|
+
.Where(x => x.TenantId == tenantId)
|
|
62412
|
+
.AsNoTracking();
|
|
62413
|
+
|
|
62414
|
+
if (modifiedSince.HasValue)
|
|
62415
|
+
{
|
|
62416
|
+
query = query.Where(x => x.UpdatedAt >= modifiedSince.Value
|
|
62417
|
+
|| x.CreatedAt >= modifiedSince.Value);
|
|
62418
|
+
}
|
|
62419
|
+
|
|
62420
|
+
var totalCount = await query.CountAsync(ct);
|
|
62421
|
+
|
|
62422
|
+
var items = await query
|
|
62423
|
+
.OrderBy(x => x.Id)
|
|
62424
|
+
.Skip((page - 1) * pageSize)
|
|
62425
|
+
.Take(pageSize)
|
|
62426
|
+
.Select(x => new Export${name}Dto
|
|
62427
|
+
{
|
|
62428
|
+
${projectionBlock}
|
|
62429
|
+
})
|
|
62430
|
+
.ToListAsync(ct);
|
|
62431
|
+
|
|
62432
|
+
return new PaginatedResult<Export${name}Dto>(items, totalCount, page, pageSize);
|
|
62433
|
+
}
|
|
62434
|
+
}
|
|
62435
|
+
`;
|
|
62436
|
+
}
|
|
62437
|
+
function generateExportDto(name, baseNamespace, entityProperties, includeAuditFields) {
|
|
62438
|
+
const properties = [" public Guid Id { get; init; }"];
|
|
62439
|
+
for (const prop of entityProperties) {
|
|
62440
|
+
const nullableSuffix = prop.required ? "" : "?";
|
|
62441
|
+
properties.push(` public ${prop.type}${nullableSuffix} ${prop.name} { get; init; }`);
|
|
62442
|
+
}
|
|
62443
|
+
if (includeAuditFields) {
|
|
62444
|
+
properties.push(" public DateTime CreatedAt { get; init; }");
|
|
62445
|
+
properties.push(" public DateTime? UpdatedAt { get; init; }");
|
|
62446
|
+
}
|
|
62447
|
+
return `namespace ${baseNamespace}.Application.DataExport.Dtos;
|
|
62448
|
+
|
|
62449
|
+
/// <summary>
|
|
62450
|
+
/// Flat DTO for ${name} data export (external consumption).
|
|
62451
|
+
/// Auto-generated by SmartStack MCP \u2014 customize as needed.
|
|
62452
|
+
/// </summary>
|
|
62453
|
+
public class Export${name}Dto
|
|
62454
|
+
{
|
|
62455
|
+
${properties.join("\n")}
|
|
62456
|
+
}
|
|
62457
|
+
`;
|
|
62458
|
+
}
|
|
62459
|
+
function generateSeedDataFragment(name, namePlural, permissionPath, appSegment, moduleSegment, rateLimitPerMinute, maxPageSize) {
|
|
62460
|
+
return `// ============================================================================
|
|
62461
|
+
// Data Export Endpoint Seed Data \u2014 ${name}
|
|
62462
|
+
// Auto-generated by SmartStack MCP \u2014 add to DataExportEndpointConfiguration.cs
|
|
62463
|
+
// ============================================================================
|
|
62464
|
+
//
|
|
62465
|
+
// Copy the HasData() block below into your DataExportEndpointConfiguration
|
|
62466
|
+
// or IClientSeedDataProvider to register this export endpoint.
|
|
62467
|
+
//
|
|
62468
|
+
// builder.HasData(new
|
|
62469
|
+
// {
|
|
62470
|
+
// Id = DeterministicGuid.Create("data-export:${namePlural}"),
|
|
62471
|
+
// Code = "${namePlural}",
|
|
62472
|
+
// Name = "${name} Export",
|
|
62473
|
+
// RouteTemplate = "/api/v1/export/${namePlural}",
|
|
62474
|
+
// RequiredPermission = "${permissionPath}.export",
|
|
62475
|
+
// EntityType = "${name}",
|
|
62476
|
+
// IsActive = true,
|
|
62477
|
+
// DefaultRateLimitPerMinute = ${rateLimitPerMinute},
|
|
62478
|
+
// DefaultMaxPageSize = ${maxPageSize},
|
|
62479
|
+
// CreatedAt = seedDate
|
|
62480
|
+
// });
|
|
62481
|
+
//
|
|
62482
|
+
// IMPORTANT: NavigationApplicationId and NavigationModuleId must reference
|
|
62483
|
+
// actual seed data IDs from NavigationApplicationSeedData and
|
|
62484
|
+
// NavigationModuleSeedData respectively.
|
|
62485
|
+
//
|
|
62486
|
+
// Permission to add to PermissionConfiguration.cs:
|
|
62487
|
+
// "${permissionPath}.export" \u2014 Export ${name} data via M2M API
|
|
62488
|
+
`;
|
|
62489
|
+
}
|
|
62490
|
+
function generateFrontendClient(name, nameLower, namePlural) {
|
|
62491
|
+
return `/**
|
|
62492
|
+
* ${name} Data Export API Client
|
|
62493
|
+
*
|
|
62494
|
+
* Auto-generated by SmartStack MCP - DO NOT EDIT MANUALLY
|
|
62495
|
+
* Endpoint: /api/v1/export/${namePlural}
|
|
62496
|
+
* Note: tenantId is required and must be passed for all requests
|
|
62497
|
+
*/
|
|
62498
|
+
|
|
62499
|
+
import { apiClient } from '../lib/apiClient';
|
|
62500
|
+
|
|
62501
|
+
export interface Export${name}Dto {
|
|
62502
|
+
id: string;
|
|
62503
|
+
[key: string]: unknown;
|
|
62504
|
+
createdAt: string;
|
|
62505
|
+
updatedAt?: string;
|
|
62506
|
+
}
|
|
62507
|
+
|
|
62508
|
+
export interface ExportParams {
|
|
62509
|
+
tenantId: string; // Required: tenant identifier
|
|
62510
|
+
page?: number;
|
|
62511
|
+
pageSize?: number;
|
|
62512
|
+
modifiedSince?: string;
|
|
62513
|
+
}
|
|
62514
|
+
|
|
62515
|
+
export interface PaginatedExportResult<T> {
|
|
62516
|
+
items: T[];
|
|
62517
|
+
totalCount: number;
|
|
62518
|
+
page: number;
|
|
62519
|
+
pageSize: number;
|
|
62520
|
+
totalPages: number;
|
|
62521
|
+
hasPreviousPage: boolean;
|
|
62522
|
+
hasNextPage: boolean;
|
|
62523
|
+
}
|
|
62524
|
+
|
|
62525
|
+
const EXPORT_ENDPOINT = '/api/v1/export/${namePlural}';
|
|
62526
|
+
|
|
62527
|
+
export const export${name}Api = {
|
|
62528
|
+
/**
|
|
62529
|
+
* Fetch paginated export data for a specific tenant
|
|
62530
|
+
* @param tenantId - Required tenant identifier
|
|
62531
|
+
* @param params - Pagination and filter parameters
|
|
62532
|
+
*/
|
|
62533
|
+
async getPage(tenantId: string, params?: Omit<ExportParams, 'tenantId'>): Promise<PaginatedExportResult<Export${name}Dto>> {
|
|
62534
|
+
const response = await apiClient.get<PaginatedExportResult<Export${name}Dto>>(EXPORT_ENDPOINT, {
|
|
62535
|
+
params: { tenantId, ...params }
|
|
62536
|
+
});
|
|
62537
|
+
return response.data;
|
|
62538
|
+
},
|
|
62539
|
+
|
|
62540
|
+
/**
|
|
62541
|
+
* Download all pages as a blob (CSV/JSON) for a specific tenant
|
|
62542
|
+
* @param tenantId - Required tenant identifier
|
|
62543
|
+
* @param format - Output format (default: json)
|
|
62544
|
+
*/
|
|
62545
|
+
async downloadAll(tenantId: string, format: 'json' | 'csv' = 'json'): Promise<Blob> {
|
|
62546
|
+
const response = await apiClient.get(EXPORT_ENDPOINT, {
|
|
62547
|
+
params: { tenantId, pageSize: 1000, format },
|
|
62548
|
+
responseType: 'blob',
|
|
62549
|
+
});
|
|
62550
|
+
return response.data;
|
|
62551
|
+
},
|
|
62552
|
+
};
|
|
62553
|
+
|
|
62554
|
+
export default export${name}Api;
|
|
62555
|
+
`;
|
|
62556
|
+
}
|
|
62557
|
+
function formatResult9(result, input) {
|
|
62558
|
+
const lines = [];
|
|
62559
|
+
const namePlural = pluralize(input.name.charAt(0).toLowerCase() + input.name.slice(1));
|
|
62560
|
+
lines.push(`# Scaffold Data Export: ${input.name}`);
|
|
62561
|
+
lines.push("");
|
|
62562
|
+
if (input.options?.dryRun) {
|
|
62563
|
+
lines.push("> **DRY RUN** - No files were written");
|
|
62564
|
+
lines.push("");
|
|
62565
|
+
}
|
|
62566
|
+
lines.push("## Export Endpoint");
|
|
62567
|
+
lines.push("");
|
|
62568
|
+
lines.push(`- **Entity**: \`${input.name}\``);
|
|
62569
|
+
lines.push(`- **NavRoute**: \`${input.navRoute}\``);
|
|
62570
|
+
lines.push(`- **Permission**: \`${input.navRoute}.export\``);
|
|
62571
|
+
lines.push(`- **Endpoint**: \`GET /api/v1/export/${namePlural}\``);
|
|
62572
|
+
lines.push(`- **Rate Limit**: ${input.options?.rateLimitPerMinute ?? 60}/min`);
|
|
62573
|
+
lines.push(`- **Max Page Size**: ${input.options?.maxPageSize ?? 1e3}`);
|
|
62574
|
+
lines.push("");
|
|
62575
|
+
lines.push("## Security Layers");
|
|
62576
|
+
lines.push("");
|
|
62577
|
+
lines.push("1. **Authentication** \u2014 `[Authorize]` + JWT assertion from ExternalApplicationAuthService");
|
|
62578
|
+
lines.push(`2. **Authorization** \u2014 \`[RequirePermission("${input.navRoute}.export")]\``);
|
|
62579
|
+
lines.push("3. **Access Control** \u2014 DataExportAccessMiddleware verifies per-app endpoint access");
|
|
62580
|
+
lines.push(`4. **Rate Limiting** \u2014 \`[EnableRateLimiting("ExternalApp")]\` (${input.options?.rateLimitPerMinute ?? 60}/min)`);
|
|
62581
|
+
lines.push("");
|
|
62582
|
+
lines.push("## Generated Files");
|
|
62583
|
+
lines.push("");
|
|
62584
|
+
for (const file of result.files) {
|
|
62585
|
+
const relativePath2 = file.path.replace(/\\/g, "/");
|
|
62586
|
+
const parts = relativePath2.split("/");
|
|
62587
|
+
const srcIdx = parts.findIndex((p) => ["Controllers", "Application", "Infrastructure", "services", "Persistence"].includes(p));
|
|
62588
|
+
const displayPath = srcIdx >= 0 ? parts.slice(srcIdx).join("/") : parts.slice(-3).join("/");
|
|
62589
|
+
lines.push(`### ${displayPath}`);
|
|
62590
|
+
lines.push("");
|
|
62591
|
+
const ext2 = file.path.endsWith(".ts") ? "typescript" : "csharp";
|
|
62592
|
+
const truncated = file.content.length > 2e3 ? file.content.substring(0, 2e3) + "\n// ... (truncated)" : file.content;
|
|
62593
|
+
lines.push(`\`\`\`${ext2}`);
|
|
62594
|
+
lines.push(truncated);
|
|
62595
|
+
lines.push("```");
|
|
62596
|
+
lines.push("");
|
|
62597
|
+
}
|
|
62598
|
+
lines.push("## Next Steps");
|
|
62599
|
+
lines.push("");
|
|
62600
|
+
for (const instruction of result.instructions) {
|
|
62601
|
+
lines.push(`- ${instruction}`);
|
|
62602
|
+
}
|
|
62603
|
+
return lines.join("\n");
|
|
62604
|
+
}
|
|
62605
|
+
function toPascalCase2(s) {
|
|
62606
|
+
return s.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
62607
|
+
}
|
|
62608
|
+
function pluralize(s) {
|
|
62609
|
+
if (s.endsWith("y") && !["ay", "ey", "oy", "uy"].some((v) => s.endsWith(v))) {
|
|
62610
|
+
return s.slice(0, -1) + "ies";
|
|
62611
|
+
}
|
|
62612
|
+
if (s.endsWith("s") || s.endsWith("x") || s.endsWith("z") || s.endsWith("ch") || s.endsWith("sh")) {
|
|
62613
|
+
return s + "es";
|
|
62614
|
+
}
|
|
62615
|
+
return s + "s";
|
|
62616
|
+
}
|
|
62617
|
+
var scaffoldDataExportTool;
|
|
62618
|
+
var init_scaffold_data_export = __esm({
|
|
62619
|
+
"src/mcp/tools/scaffold-data-export.ts"() {
|
|
62620
|
+
"use strict";
|
|
62621
|
+
init_esm_shims();
|
|
62622
|
+
init_types3();
|
|
62623
|
+
init_logger();
|
|
62624
|
+
init_detector();
|
|
62625
|
+
init_fs();
|
|
62626
|
+
scaffoldDataExportTool = {
|
|
62627
|
+
name: "scaffold_data_export",
|
|
62628
|
+
description: `Generate Data Export API for a SmartStack entity.
|
|
62629
|
+
|
|
62630
|
+
Creates all code needed to expose an entity via the M2M Data Export API with 3-layer security:
|
|
62631
|
+
- Export Controller (versioned, rate-limited, permission-protected)
|
|
62632
|
+
- Export Service (interface + implementation with tenant isolation)
|
|
62633
|
+
- Export DTO (flat projection for external consumption)
|
|
62634
|
+
- Seed data fragment (DataExportEndpointConfiguration HasData)
|
|
62635
|
+
- Frontend client (optional, TypeScript blob download)
|
|
62636
|
+
|
|
62637
|
+
Example:
|
|
62638
|
+
scaffold_data_export name="Product" navRoute="business.sales.products"
|
|
62639
|
+
|
|
62640
|
+
The generated code follows the External Application & Data Export pattern
|
|
62641
|
+
documented in smartstack-api.md.`,
|
|
62642
|
+
inputSchema: {
|
|
62643
|
+
type: "object",
|
|
62644
|
+
properties: {
|
|
62645
|
+
name: {
|
|
62646
|
+
type: "string",
|
|
62647
|
+
description: 'Entity name in PascalCase (e.g., "Product", "Order")'
|
|
62648
|
+
},
|
|
62649
|
+
navRoute: {
|
|
62650
|
+
type: "string",
|
|
62651
|
+
description: 'NavRoute of the source entity (e.g., "business.sales.products") \u2014 used for permission derivation'
|
|
62652
|
+
},
|
|
62653
|
+
options: {
|
|
62654
|
+
type: "object",
|
|
62655
|
+
properties: {
|
|
62656
|
+
entityProperties: {
|
|
62657
|
+
type: "array",
|
|
62658
|
+
items: {
|
|
62659
|
+
type: "object",
|
|
62660
|
+
properties: {
|
|
62661
|
+
name: { type: "string" },
|
|
62662
|
+
type: { type: "string" },
|
|
62663
|
+
required: { type: "boolean" },
|
|
62664
|
+
maxLength: { type: "number" }
|
|
62665
|
+
},
|
|
62666
|
+
required: ["name", "type"]
|
|
62667
|
+
},
|
|
62668
|
+
description: "Properties for the export DTO"
|
|
62669
|
+
},
|
|
62670
|
+
rateLimitPerMinute: {
|
|
62671
|
+
type: "number",
|
|
62672
|
+
default: 60,
|
|
62673
|
+
description: "Rate limit per minute (default: 60)"
|
|
62674
|
+
},
|
|
62675
|
+
maxPageSize: {
|
|
62676
|
+
type: "number",
|
|
62677
|
+
default: 1e3,
|
|
62678
|
+
description: "Max page size (default: 1000)"
|
|
62679
|
+
},
|
|
62680
|
+
includeAuditFields: {
|
|
62681
|
+
type: "boolean",
|
|
62682
|
+
default: true,
|
|
62683
|
+
description: "Include CreatedAt/UpdatedAt in DTO (default: true)"
|
|
62684
|
+
},
|
|
62685
|
+
includeFrontendClient: {
|
|
62686
|
+
type: "boolean",
|
|
62687
|
+
default: false,
|
|
62688
|
+
description: "Generate TypeScript API client (default: false)"
|
|
62689
|
+
},
|
|
62690
|
+
dryRun: {
|
|
62691
|
+
type: "boolean",
|
|
62692
|
+
default: false,
|
|
62693
|
+
description: "Preview without writing files (default: false)"
|
|
62694
|
+
}
|
|
62695
|
+
}
|
|
62696
|
+
}
|
|
62697
|
+
},
|
|
62698
|
+
required: ["name", "navRoute"]
|
|
62699
|
+
}
|
|
62700
|
+
};
|
|
62701
|
+
}
|
|
62702
|
+
});
|
|
62703
|
+
|
|
62704
|
+
// src/mcp/tools/prompt-data-export-m2m.ts
|
|
62705
|
+
import path26 from "path";
|
|
62706
|
+
async function handlePromptDataExportM2m(args, config2) {
|
|
62707
|
+
const input = PromptDataExportM2mInputSchema.parse(args);
|
|
62708
|
+
logger.info("Generating M2M data export prompt", {
|
|
62709
|
+
endpoints: input.endpoints,
|
|
62710
|
+
clientId: input.clientId ? "***" : void 0,
|
|
62711
|
+
format: input.options?.format
|
|
62712
|
+
});
|
|
62713
|
+
let selectedEndpoints;
|
|
62714
|
+
if (input.endpoints && input.endpoints.length > 0) {
|
|
62715
|
+
selectedEndpoints = KNOWN_ENDPOINTS.filter((e) => input.endpoints.includes(e.code));
|
|
62716
|
+
const unknown2 = input.endpoints.filter((code) => !KNOWN_ENDPOINTS.find((e) => e.code === code));
|
|
62717
|
+
if (unknown2.length > 0) {
|
|
62718
|
+
const discovered = await discoverEndpointsFromProject(config2, unknown2);
|
|
62719
|
+
selectedEndpoints.push(...discovered);
|
|
62720
|
+
const stillUnknown = unknown2.filter((code) => !discovered.find((e) => e.code === code));
|
|
62721
|
+
if (stillUnknown.length > 0) {
|
|
62722
|
+
logger.warn("Unknown endpoint codes", { codes: stillUnknown });
|
|
62723
|
+
}
|
|
62724
|
+
}
|
|
62725
|
+
} else {
|
|
62726
|
+
selectedEndpoints = [...KNOWN_ENDPOINTS];
|
|
62727
|
+
const discovered = await discoverEndpointsFromProject(config2);
|
|
62728
|
+
for (const ep of discovered) {
|
|
62729
|
+
if (!selectedEndpoints.find((e) => e.code === ep.code)) {
|
|
62730
|
+
selectedEndpoints.push(ep);
|
|
62731
|
+
}
|
|
62732
|
+
}
|
|
62733
|
+
}
|
|
62734
|
+
if (selectedEndpoints.length === 0) {
|
|
62735
|
+
return "# No Endpoints Found\n\nNo data export endpoints were found. Use `scaffold_data_export` to create one.";
|
|
62736
|
+
}
|
|
62737
|
+
const format = input.options?.format ?? "full";
|
|
62738
|
+
const includeBase = input.options?.includeBasePrompt !== false;
|
|
62739
|
+
const clientId = input.clientId ?? "<your-client-id>";
|
|
62740
|
+
const prompt = buildPrompt(selectedEndpoints, clientId, format, includeBase);
|
|
62741
|
+
return `# M2M Data Export Integration Prompt
|
|
62742
|
+
|
|
62743
|
+
**Endpoints included**: ${selectedEndpoints.map((e) => e.code).join(", ")}
|
|
62744
|
+
**Format**: ${format}
|
|
62745
|
+
|
|
62746
|
+
---
|
|
62747
|
+
|
|
62748
|
+
\`\`\`markdown
|
|
62749
|
+
${prompt}
|
|
62750
|
+
\`\`\`
|
|
62751
|
+
|
|
62752
|
+
---
|
|
62753
|
+
|
|
62754
|
+
> Copy the content above (inside the code block) and paste it into your VibeCoding session.
|
|
62755
|
+
> The AI will have all the context needed to implement the full M2M client.`;
|
|
62756
|
+
}
|
|
62757
|
+
function buildPrompt(endpoints, clientId, format, includeBase) {
|
|
62758
|
+
const lines = [];
|
|
62759
|
+
if (includeBase && format === "full") {
|
|
62760
|
+
lines.push("## Contexte");
|
|
62761
|
+
lines.push("");
|
|
62762
|
+
lines.push("Tu dois implementer un client HTTP qui consomme l'API DataExport de SmartStack en mode **machine-to-machine** (M2M). Il n'y a aucune interaction utilisateur : ton application s'authentifie avec un `clientId` et un `clientSecret`, obtient un token JWT, puis appelle les endpoints d'export de donnees.");
|
|
62763
|
+
lines.push("");
|
|
62764
|
+
lines.push("---");
|
|
62765
|
+
lines.push("");
|
|
62766
|
+
lines.push("## 1. Authentification (HMAC-JWT Assertion)");
|
|
62767
|
+
lines.push("");
|
|
62768
|
+
lines.push("L'authentification se fait en 2 etapes :");
|
|
62769
|
+
lines.push("");
|
|
62770
|
+
lines.push("### Etape 1 : Creer une assertion JWT signee HMAC-SHA256");
|
|
62771
|
+
lines.push("");
|
|
62772
|
+
lines.push("Ton application doit generer un JWT **signe avec le `clientSecret`** (HMAC-SHA256) :");
|
|
62773
|
+
lines.push("");
|
|
62774
|
+
lines.push("```");
|
|
62775
|
+
lines.push('Header: { "alg": "HS256", "typ": "JWT" }');
|
|
62776
|
+
lines.push("Payload: {");
|
|
62777
|
+
lines.push(` "sub": "${clientId}", // ton clientId`);
|
|
62778
|
+
lines.push(' "iat": <timestamp-unix-actuel>, // timestamp Unix actuel');
|
|
62779
|
+
lines.push(' "exp": <timestamp-unix + 300>, // expiration (max 5 min apres iat)');
|
|
62780
|
+
lines.push(' "jti": "<uuid-unique>" // nonce anti-replay');
|
|
62781
|
+
lines.push("}");
|
|
62782
|
+
lines.push("Signature: HMAC-SHA256(header.payload, Base64Decode(clientSecret))");
|
|
62783
|
+
lines.push("```");
|
|
62784
|
+
lines.push("");
|
|
62785
|
+
lines.push("**Important** : Le `clientSecret` est encode en Base64. Tu dois le **decoder** avant de l'utiliser comme cle HMAC.");
|
|
62786
|
+
lines.push("");
|
|
62787
|
+
lines.push("### Etape 2 : Echanger l'assertion contre un access token");
|
|
62788
|
+
lines.push("");
|
|
62789
|
+
lines.push("```http");
|
|
62790
|
+
lines.push("POST {baseUrl}/api/auth/external-app/token");
|
|
62791
|
+
lines.push("Content-Type: application/json");
|
|
62792
|
+
lines.push("");
|
|
62793
|
+
lines.push("{");
|
|
62794
|
+
lines.push(' "assertion": "<jwt-assertion-de-etape-1>"');
|
|
62795
|
+
lines.push("}");
|
|
62796
|
+
lines.push("```");
|
|
62797
|
+
lines.push("");
|
|
62798
|
+
lines.push("**Reponse succes (200)** :");
|
|
62799
|
+
lines.push("```json");
|
|
62800
|
+
lines.push("{");
|
|
62801
|
+
lines.push(' "accessToken": "eyJhbGciOiJIUz...",');
|
|
62802
|
+
lines.push(' "expiresAt": "2026-02-25T11:00:00Z",');
|
|
62803
|
+
lines.push(' "permissions": ["administration.users.read", ...]');
|
|
62804
|
+
lines.push("}");
|
|
62805
|
+
lines.push("```");
|
|
62806
|
+
lines.push("");
|
|
62807
|
+
lines.push("### Erreurs d'authentification");
|
|
62808
|
+
lines.push("");
|
|
62809
|
+
lines.push("| HTTP | errorCode | Cause |");
|
|
62810
|
+
lines.push("|------|-----------|-------|");
|
|
62811
|
+
lines.push("| 401 | `invalid_assertion` | JWT mal forme ou invalide |");
|
|
62812
|
+
lines.push("| 401 | `missing_client_id` | Claim `sub` absent du JWT |");
|
|
62813
|
+
lines.push("| 401 | `unknown_client` | ClientId inconnu |");
|
|
62814
|
+
lines.push("| 401 | `app_inactive` | Application desactivee |");
|
|
62815
|
+
lines.push("| 401 | `ip_blocked` | IP pas dans la whitelist |");
|
|
62816
|
+
lines.push("| 401 | `invalid_signature` | Signature HMAC invalide |");
|
|
62817
|
+
lines.push("");
|
|
62818
|
+
lines.push("### Regles de gestion du token");
|
|
62819
|
+
lines.push("");
|
|
62820
|
+
lines.push("- Cache le token et renouvelle-le 5 min avant `expiresAt`");
|
|
62821
|
+
lines.push("- N'appelle PAS `/token` a chaque requete");
|
|
62822
|
+
lines.push("");
|
|
62823
|
+
lines.push("---");
|
|
62824
|
+
lines.push("");
|
|
62825
|
+
}
|
|
62826
|
+
lines.push(`## ${format === "full" ? "2" : "1"}. Endpoints a implementer (${endpoints.length})`);
|
|
62827
|
+
lines.push("");
|
|
62828
|
+
for (const ep of endpoints) {
|
|
62829
|
+
lines.push(`### ${ep.name}`);
|
|
62830
|
+
lines.push("");
|
|
62831
|
+
lines.push("```http");
|
|
62832
|
+
lines.push(`${ep.method} {baseUrl}${ep.route}`);
|
|
62833
|
+
lines.push("Authorization: Bearer <accessToken>");
|
|
62834
|
+
lines.push("```");
|
|
62835
|
+
lines.push("");
|
|
62836
|
+
if (ep.params.length > 0) {
|
|
62837
|
+
lines.push("**Parametres (query string)** :");
|
|
62838
|
+
lines.push("");
|
|
62839
|
+
lines.push("| Parametre | Type | Defaut | Description |");
|
|
62840
|
+
lines.push("|-----------|------|--------|-------------|");
|
|
62841
|
+
for (const p of ep.params) {
|
|
62842
|
+
lines.push(`| \`${p.name}\` | ${p.type} | ${p.default} | ${p.description} |`);
|
|
62843
|
+
}
|
|
62844
|
+
lines.push("");
|
|
62845
|
+
} else {
|
|
62846
|
+
lines.push("**Aucun parametre** - appel direct sans query string.");
|
|
62847
|
+
lines.push("");
|
|
62848
|
+
}
|
|
62849
|
+
if (ep.paginated) {
|
|
62850
|
+
lines.push("**Reponse (paginee)** :");
|
|
62851
|
+
lines.push("```json");
|
|
62852
|
+
lines.push("{");
|
|
62853
|
+
lines.push(' "items": [');
|
|
62854
|
+
lines.push(" {");
|
|
62855
|
+
for (let i = 0; i < ep.responseFields.length; i++) {
|
|
62856
|
+
const f = ep.responseFields[i];
|
|
62857
|
+
const comma = i < ep.responseFields.length - 1 ? "," : "";
|
|
62858
|
+
lines.push(` "${f.name}": ${getJsonExample(f.type)}${comma} // ${f.type} - ${f.description}`);
|
|
62859
|
+
}
|
|
62860
|
+
lines.push(" }");
|
|
62861
|
+
lines.push(" ],");
|
|
62862
|
+
lines.push(' "page": 1,');
|
|
62863
|
+
lines.push(' "pageSize": 100,');
|
|
62864
|
+
lines.push(' "totalCount": 250,');
|
|
62865
|
+
lines.push(' "totalPages": 3,');
|
|
62866
|
+
lines.push(' "hasMore": true');
|
|
62867
|
+
lines.push("}");
|
|
62868
|
+
lines.push("```");
|
|
62869
|
+
} else {
|
|
62870
|
+
lines.push("**Reponse (tableau direct, NON pagine)** :");
|
|
62871
|
+
lines.push("```json");
|
|
62872
|
+
lines.push("[");
|
|
62873
|
+
lines.push(" {");
|
|
62874
|
+
for (let i = 0; i < ep.responseFields.length; i++) {
|
|
62875
|
+
const f = ep.responseFields[i];
|
|
62876
|
+
const comma = i < ep.responseFields.length - 1 ? "," : "";
|
|
62877
|
+
lines.push(` "${f.name}": ${getJsonExample(f.type)}${comma} // ${f.type} - ${f.description}`);
|
|
62878
|
+
}
|
|
62879
|
+
lines.push(" }");
|
|
62880
|
+
lines.push("]");
|
|
62881
|
+
lines.push("```");
|
|
62882
|
+
}
|
|
62883
|
+
lines.push("");
|
|
62884
|
+
lines.push(`**Rate limit** : ${ep.defaultRateLimit} requetes/minute | Page size max : ${ep.defaultMaxPageSize}`);
|
|
62885
|
+
if (ep.notes) {
|
|
62886
|
+
lines.push(`**Note** : ${ep.notes}`);
|
|
62887
|
+
}
|
|
62888
|
+
lines.push("");
|
|
62889
|
+
}
|
|
62890
|
+
if (format === "full") {
|
|
62891
|
+
lines.push("---");
|
|
62892
|
+
lines.push("");
|
|
62893
|
+
lines.push("## 3. Isolation par tenant");
|
|
62894
|
+
lines.push("");
|
|
62895
|
+
lines.push("**Important** : Tous les endpoints (sauf `navigation`) necessitent le parametre `tenantId` pour delimiter l'export a un tenant specifique. Cela assure la securite multi-tenant et evite les fuites de donnees.");
|
|
62896
|
+
lines.push("");
|
|
62897
|
+
lines.push("```");
|
|
62898
|
+
lines.push("GET /api/v1/export/users?tenantId=<uuid>&page=1");
|
|
62899
|
+
lines.push("GET /api/v1/export/roles?tenantId=<uuid>&page=1");
|
|
62900
|
+
lines.push("GET /api/v1/export/tickets?tenantId=<uuid>&page=1");
|
|
62901
|
+
lines.push("```");
|
|
62902
|
+
lines.push("");
|
|
62903
|
+
lines.push("Exemple avec boucle :");
|
|
62904
|
+
lines.push("```");
|
|
62905
|
+
lines.push("1. Recuprer la liste de tous les tenants via GET /api/v1/export/navigation");
|
|
62906
|
+
lines.push("2. Pour chaque tenant, exporter ses donnees : users, roles, tickets");
|
|
62907
|
+
lines.push("3. Concatener les resultats ou synchroniser par tenant");
|
|
62908
|
+
lines.push("```");
|
|
62909
|
+
lines.push("");
|
|
62910
|
+
lines.push("---");
|
|
62911
|
+
lines.push("");
|
|
62912
|
+
const hasPaginated = endpoints.some((e) => e.paginated);
|
|
62913
|
+
if (hasPaginated) {
|
|
62914
|
+
lines.push("## 4. Pagination");
|
|
62915
|
+
lines.push("");
|
|
62916
|
+
lines.push("Pour parcourir toutes les pages : boucle tant que `hasMore === true`, en incrementant `page`.");
|
|
62917
|
+
lines.push("Les resultats sont tries par `createdAt ASC` (ordre stable).");
|
|
62918
|
+
lines.push("");
|
|
62919
|
+
lines.push("---");
|
|
62920
|
+
lines.push("");
|
|
62921
|
+
lines.push("## 5. Synchronisation incrementale");
|
|
62922
|
+
lines.push("");
|
|
62923
|
+
lines.push("Utilise `modifiedSince` pour ne recuperer que les changements :");
|
|
62924
|
+
lines.push("1. Premiere synchro : appelle sans `modifiedSince` (recupere tout)");
|
|
62925
|
+
lines.push("2. Stocke `DateTime.UtcNow` du debut de la synchro");
|
|
62926
|
+
lines.push("3. Synchros suivantes : passe `modifiedSince=<timestamp-stocke>`");
|
|
62927
|
+
lines.push("");
|
|
62928
|
+
lines.push("Le filtre retourne les entites ou `UpdatedAt >= modifiedSince OR CreatedAt >= modifiedSince`.");
|
|
62929
|
+
lines.push("");
|
|
62930
|
+
lines.push("---");
|
|
62931
|
+
lines.push("");
|
|
62932
|
+
}
|
|
62933
|
+
lines.push("## 6. Rate Limiting");
|
|
62934
|
+
lines.push("");
|
|
62935
|
+
lines.push("| Aspect | Valeur |");
|
|
62936
|
+
lines.push("|--------|--------|");
|
|
62937
|
+
lines.push("| Algorithme | Fenetre fixe de 1 minute |");
|
|
62938
|
+
lines.push("| Cle de partition | {clientId}:{endpointCode} (independant par endpoint) |");
|
|
62939
|
+
lines.push("");
|
|
62940
|
+
lines.push("Quand tu depasses la limite :");
|
|
62941
|
+
lines.push("```");
|
|
62942
|
+
lines.push("HTTP/1.1 429 Too Many Requests");
|
|
62943
|
+
lines.push("Retry-After: 42");
|
|
62944
|
+
lines.push("```");
|
|
62945
|
+
lines.push("Lis le header `Retry-After` et attends. Implemente un backoff exponentiel.");
|
|
62946
|
+
lines.push("");
|
|
62947
|
+
lines.push("---");
|
|
62948
|
+
lines.push("");
|
|
62949
|
+
lines.push("## 7. Gestion des erreurs");
|
|
62950
|
+
lines.push("");
|
|
62951
|
+
lines.push("| HTTP | Action |");
|
|
62952
|
+
lines.push("|------|--------|");
|
|
62953
|
+
lines.push("| 200 | Traite les donnees |");
|
|
62954
|
+
lines.push("| 400 | TenantId manquant ou invalide - verifie le parametre |");
|
|
62955
|
+
lines.push("| 401 | Renouvelle le token et relance |");
|
|
62956
|
+
lines.push("| 403 | Acces export non accorde - contacte l'admin |");
|
|
62957
|
+
lines.push("| 404 | Tenant non trouve - verifie le tenantId |");
|
|
62958
|
+
lines.push("| 429 | Attends `Retry-After` secondes |");
|
|
62959
|
+
lines.push("| 500 | Retry avec backoff exponentiel |");
|
|
62960
|
+
lines.push("");
|
|
62961
|
+
lines.push("---");
|
|
62962
|
+
lines.push("");
|
|
62963
|
+
lines.push("## 8. Checklist d'implementation");
|
|
62964
|
+
lines.push("");
|
|
62965
|
+
lines.push("### 8.1 Configuration");
|
|
62966
|
+
lines.push("```");
|
|
62967
|
+
lines.push("SMARTSTACK_BASE_URL=https://smartstack.example.com");
|
|
62968
|
+
lines.push(`SMARTSTACK_CLIENT_ID=${clientId}`);
|
|
62969
|
+
lines.push("SMARTSTACK_CLIENT_SECRET=<base64-encoded-secret>");
|
|
62970
|
+
lines.push("TENANT_IDS=<comma-separated-uuids> # Ou recuperer dynamiquement via navigation");
|
|
62971
|
+
lines.push("```");
|
|
62972
|
+
lines.push("Ne hardcode JAMAIS le clientId/clientSecret. Utilise des variables d'environnement ou un vault.");
|
|
62973
|
+
lines.push("");
|
|
62974
|
+
lines.push("### 8.2 Service d'authentification");
|
|
62975
|
+
lines.push("```");
|
|
62976
|
+
lines.push("SmartStackAuthService:");
|
|
62977
|
+
lines.push(" - generateAssertion() \u2192 JWT HMAC-SHA256 signe avec le secret decode du Base64");
|
|
62978
|
+
lines.push(" - getAccessToken() \u2192 POST /api/auth/external-app/token");
|
|
62979
|
+
lines.push(" - cachedToken / expiresAt \u2192 cache le token, renouvelle 5 min avant expiration");
|
|
62980
|
+
lines.push("```");
|
|
62981
|
+
lines.push("");
|
|
62982
|
+
lines.push("### 8.3 Client HTTP generique");
|
|
62983
|
+
lines.push("```");
|
|
62984
|
+
lines.push("SmartStackExportClient:");
|
|
62985
|
+
lines.push(" - fetchExport(endpointCode, tenantId, page?, pageSize?, modifiedSince?, extraParams?)");
|
|
62986
|
+
lines.push(" \u2192 GET /api/v1/export/{endpointCode}?tenantId=<uuid>");
|
|
62987
|
+
lines.push(" - fetchAllPages(endpointCode, tenantId, pageSize?, modifiedSince?)");
|
|
62988
|
+
lines.push(" \u2192 boucle automatique sur toutes les pages avec tenantId");
|
|
62989
|
+
lines.push(" - Gere 400 (tenantId invalide), 401 (retry token), 404 (tenant non trouve), 429 (Retry-After), 5xx (backoff)");
|
|
62990
|
+
lines.push("```");
|
|
62991
|
+
lines.push("");
|
|
62992
|
+
lines.push("### 8.4 Synchroniseur incremental par tenant");
|
|
62993
|
+
lines.push("```");
|
|
62994
|
+
lines.push("SmartStackSyncService:");
|
|
62995
|
+
lines.push(` - endpoints: [${endpoints.map((e) => `"${e.code}"`).join(", ")}]`);
|
|
62996
|
+
lines.push(" - syncAllTenants() \u2192 pour chaque tenant, appelle syncTenant()");
|
|
62997
|
+
lines.push(" - syncTenant(tenantId):");
|
|
62998
|
+
lines.push(" 1. Pour chaque endpoint (sauf navigation):");
|
|
62999
|
+
lines.push(" a. Lit le dernier timestamp de synchro pour ce tenant+endpoint");
|
|
63000
|
+
lines.push(" b. Appelle fetchAllPages(endpoint, tenantId, modifiedSince)");
|
|
63001
|
+
lines.push(" c. Upsert les donnees (INSERT si nouveau, UPDATE si existant, par `id`)");
|
|
63002
|
+
lines.push(" d. Sauve le timestamp");
|
|
63003
|
+
lines.push("```");
|
|
63004
|
+
lines.push("");
|
|
63005
|
+
lines.push("### 8.5 Securite");
|
|
63006
|
+
lines.push("");
|
|
63007
|
+
lines.push("- Stocke le clientSecret dans un vault (Azure Key Vault, etc.)");
|
|
63008
|
+
lines.push("- L'assertion JWT a une duree courte (5 min max)");
|
|
63009
|
+
lines.push("- Ne log jamais le token d'acces");
|
|
63010
|
+
lines.push("- Toutes les requetes sont auditees cote SmartStack (IP, User-Agent)");
|
|
63011
|
+
}
|
|
63012
|
+
return lines.join("\n");
|
|
63013
|
+
}
|
|
63014
|
+
async function discoverEndpointsFromProject(config2, filterCodes) {
|
|
63015
|
+
const discovered = [];
|
|
63016
|
+
try {
|
|
63017
|
+
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
63018
|
+
if (!structure.api) return discovered;
|
|
63019
|
+
const exportControllerDir = path26.join(structure.api, "Controllers", "DataExport", "v1");
|
|
63020
|
+
const { findFiles: findFiles2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
63021
|
+
const controllerFiles = await findFiles2(path26.join(exportControllerDir, "Export*Controller.cs"));
|
|
63022
|
+
for (const file of controllerFiles) {
|
|
63023
|
+
const content = await readText(file);
|
|
63024
|
+
if (!content) continue;
|
|
63025
|
+
const routeMatch = content.match(/\[HttpGet\("api\/v1\/export\/([^"]+)"\)\]/);
|
|
63026
|
+
if (!routeMatch) continue;
|
|
63027
|
+
const code = routeMatch[1];
|
|
63028
|
+
if (KNOWN_ENDPOINTS.find((e) => e.code === code)) continue;
|
|
63029
|
+
if (filterCodes && !filterCodes.includes(code)) continue;
|
|
63030
|
+
const classMatch = content.match(/class Export(\w+)Controller/);
|
|
63031
|
+
const entityName = classMatch ? classMatch[1] : code;
|
|
63032
|
+
discovered.push({
|
|
63033
|
+
code,
|
|
63034
|
+
name: `${entityName} Export`,
|
|
63035
|
+
route: `/api/v1/export/${code}`,
|
|
63036
|
+
method: "GET",
|
|
63037
|
+
paginated: content.includes("PaginatedExportResult"),
|
|
63038
|
+
params: [
|
|
63039
|
+
{ name: "page", type: "int", default: "1", description: "Numero de page" },
|
|
63040
|
+
{ name: "pageSize", type: "int", default: "100", description: "Elements par page" },
|
|
63041
|
+
{ name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
|
|
63042
|
+
],
|
|
63043
|
+
responseFields: [
|
|
63044
|
+
{ name: "id", type: "UUID", description: "Identifiant unique" },
|
|
63045
|
+
{ name: "...", type: "...", description: `Voir Export${entityName}Dto pour la structure complete` }
|
|
63046
|
+
],
|
|
63047
|
+
defaultRateLimit: 60,
|
|
63048
|
+
defaultMaxPageSize: 1e3,
|
|
63049
|
+
notes: `Endpoint decouvert automatiquement. Verifier le DTO Export${entityName}Dto pour la structure exacte.`
|
|
63050
|
+
});
|
|
63051
|
+
}
|
|
63052
|
+
} catch {
|
|
63053
|
+
logger.debug("Could not discover endpoints from project");
|
|
63054
|
+
}
|
|
63055
|
+
return discovered;
|
|
63056
|
+
}
|
|
63057
|
+
function getJsonExample(type) {
|
|
63058
|
+
switch (type) {
|
|
63059
|
+
case "UUID":
|
|
63060
|
+
return '"3fa85f64-5717-4562-b3fc-2c963f66afa6"';
|
|
63061
|
+
case "UUID?":
|
|
63062
|
+
return "null";
|
|
63063
|
+
case "string":
|
|
63064
|
+
return '"..."';
|
|
63065
|
+
case "string?":
|
|
63066
|
+
return '"..."';
|
|
63067
|
+
case "boolean":
|
|
63068
|
+
return "true";
|
|
63069
|
+
case "int":
|
|
63070
|
+
return "0";
|
|
63071
|
+
case "DateTime":
|
|
63072
|
+
return '"2026-01-15T10:30:00Z"';
|
|
63073
|
+
case "DateTime?":
|
|
63074
|
+
return "null";
|
|
63075
|
+
case "string[]":
|
|
63076
|
+
return '["item1", "item2"]';
|
|
63077
|
+
case "UUID[]":
|
|
63078
|
+
return '["uuid1", "uuid2"]';
|
|
63079
|
+
case "object[]":
|
|
63080
|
+
return '[{"id": "...", "code": "...", "label": "..."}]';
|
|
63081
|
+
default:
|
|
63082
|
+
return '"..."';
|
|
63083
|
+
}
|
|
63084
|
+
}
|
|
63085
|
+
var promptDataExportM2mTool, KNOWN_ENDPOINTS;
|
|
63086
|
+
var init_prompt_data_export_m2m = __esm({
|
|
63087
|
+
"src/mcp/tools/prompt-data-export-m2m.ts"() {
|
|
63088
|
+
"use strict";
|
|
63089
|
+
init_esm_shims();
|
|
63090
|
+
init_types3();
|
|
63091
|
+
init_logger();
|
|
63092
|
+
init_detector();
|
|
63093
|
+
init_fs();
|
|
63094
|
+
promptDataExportM2mTool = {
|
|
63095
|
+
name: "prompt_data_export_m2m",
|
|
63096
|
+
description: `Generate a ready-to-use M2M integration prompt for SmartStack Data Export API.
|
|
63097
|
+
|
|
63098
|
+
Creates a complete prompt containing:
|
|
63099
|
+
- HMAC-JWT authentication flow (assertion + token exchange)
|
|
63100
|
+
- Selected endpoint details (route, parameters, response structure)
|
|
63101
|
+
- Pagination and incremental sync patterns
|
|
63102
|
+
- Rate limiting and error handling
|
|
63103
|
+
- Implementation checklist (auth service, HTTP client, sync service)
|
|
63104
|
+
|
|
63105
|
+
The prompt is designed to be pasted into a VibeCoding session (Claude, Cursor, etc.)
|
|
63106
|
+
so the AI can implement the full client without additional context.
|
|
63107
|
+
|
|
63108
|
+
Example:
|
|
63109
|
+
prompt_data_export_m2m endpoints=["users","tenants","roles"]
|
|
63110
|
+
prompt_data_export_m2m endpoints=["users"] clientId="app_abc123"
|
|
63111
|
+
|
|
63112
|
+
If no endpoints specified, scans the project to discover all available export endpoints.`,
|
|
63113
|
+
inputSchema: {
|
|
63114
|
+
type: "object",
|
|
63115
|
+
properties: {
|
|
63116
|
+
endpoints: {
|
|
63117
|
+
type: "array",
|
|
63118
|
+
items: { type: "string" },
|
|
63119
|
+
description: 'Endpoint codes to include (e.g., ["users", "tenants", "roles"]). If empty, discovers all available endpoints.'
|
|
63120
|
+
},
|
|
63121
|
+
clientId: {
|
|
63122
|
+
type: "string",
|
|
63123
|
+
description: 'Optional clientId to pre-fill in the prompt (e.g., "app_abc123...")'
|
|
63124
|
+
},
|
|
63125
|
+
options: {
|
|
63126
|
+
type: "object",
|
|
63127
|
+
properties: {
|
|
63128
|
+
includeBasePrompt: {
|
|
63129
|
+
type: "boolean",
|
|
63130
|
+
default: true,
|
|
63131
|
+
description: "Include authentication and common sections (default: true)"
|
|
63132
|
+
},
|
|
63133
|
+
format: {
|
|
63134
|
+
type: "string",
|
|
63135
|
+
enum: ["full", "endpoints-only"],
|
|
63136
|
+
default: "full",
|
|
63137
|
+
description: 'Output format: "full" includes auth+endpoints+checklist, "endpoints-only" just endpoint details'
|
|
63138
|
+
}
|
|
63139
|
+
}
|
|
63140
|
+
}
|
|
63141
|
+
}
|
|
63142
|
+
}
|
|
63143
|
+
};
|
|
63144
|
+
KNOWN_ENDPOINTS = [
|
|
63145
|
+
{
|
|
63146
|
+
code: "users",
|
|
63147
|
+
name: "Users Export",
|
|
63148
|
+
route: "/api/v1/export/users",
|
|
63149
|
+
method: "GET",
|
|
63150
|
+
paginated: true,
|
|
63151
|
+
params: [
|
|
63152
|
+
{ name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses utilisateurs" },
|
|
63153
|
+
{ name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
|
|
63154
|
+
{ name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
|
|
63155
|
+
{ name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC - entites creees ou modifiees apres cette date" },
|
|
63156
|
+
{ name: "isActive", type: "bool?", default: "null", description: "Filtre par statut actif/inactif" }
|
|
63157
|
+
],
|
|
63158
|
+
responseFields: [
|
|
63159
|
+
{ name: "id", type: "UUID", description: "Identifiant unique" },
|
|
63160
|
+
{ name: "email", type: "string", description: "Adresse email" },
|
|
63161
|
+
{ name: "firstName", type: "string?", description: "Prenom" },
|
|
63162
|
+
{ name: "lastName", type: "string?", description: "Nom de famille" },
|
|
63163
|
+
{ name: "displayName", type: "string?", description: "Nom affiche" },
|
|
63164
|
+
{ name: "isActive", type: "boolean", description: "Compte actif" },
|
|
63165
|
+
{ name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
|
|
63166
|
+
{ name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
|
|
63167
|
+
{ name: "roles", type: "string[]", description: "Noms des roles attribues" },
|
|
63168
|
+
{ name: "tenantIds", type: "UUID[]", description: "IDs des tenants associes" }
|
|
63169
|
+
],
|
|
63170
|
+
defaultRateLimit: 60,
|
|
63171
|
+
defaultMaxPageSize: 1e3
|
|
63172
|
+
},
|
|
63173
|
+
{
|
|
63174
|
+
code: "tenants",
|
|
63175
|
+
name: "Tenants Export",
|
|
63176
|
+
route: "/api/v1/export/tenants",
|
|
63177
|
+
method: "GET",
|
|
63178
|
+
paginated: true,
|
|
63179
|
+
params: [
|
|
63180
|
+
{ name: "tenantId", type: "UUID", default: "required", description: "ID du tenant a exporter - retourne le tenant specifie et ses donnees associees" },
|
|
63181
|
+
{ name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
|
|
63182
|
+
{ name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
|
|
63183
|
+
{ name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
|
|
63184
|
+
],
|
|
63185
|
+
responseFields: [
|
|
63186
|
+
{ name: "id", type: "UUID", description: "Identifiant unique" },
|
|
63187
|
+
{ name: "name", type: "string", description: "Nom du tenant" },
|
|
63188
|
+
{ name: "slug", type: "string", description: "Slug URL-friendly" },
|
|
63189
|
+
{ name: "type", type: "string", description: "Type de tenant" },
|
|
63190
|
+
{ name: "status", type: "string", description: "Statut (Active, Suspended, etc.)" },
|
|
63191
|
+
{ name: "description", type: "string?", description: "Description" },
|
|
63192
|
+
{ name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
|
|
63193
|
+
{ name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
|
|
63194
|
+
{ name: "memberCount", type: "int", description: "Nombre de membres" }
|
|
63195
|
+
],
|
|
63196
|
+
defaultRateLimit: 60,
|
|
63197
|
+
defaultMaxPageSize: 1e3
|
|
63198
|
+
},
|
|
63199
|
+
{
|
|
63200
|
+
code: "roles",
|
|
63201
|
+
name: "Roles Export",
|
|
63202
|
+
route: "/api/v1/export/roles",
|
|
63203
|
+
method: "GET",
|
|
63204
|
+
paginated: true,
|
|
63205
|
+
params: [
|
|
63206
|
+
{ name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses roles" },
|
|
63207
|
+
{ name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
|
|
63208
|
+
{ name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
|
|
63209
|
+
{ name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
|
|
63210
|
+
],
|
|
63211
|
+
responseFields: [
|
|
63212
|
+
{ name: "id", type: "UUID", description: "Identifiant unique" },
|
|
63213
|
+
{ name: "name", type: "string", description: "Nom du role" },
|
|
63214
|
+
{ name: "shortName", type: "string?", description: "Nom court" },
|
|
63215
|
+
{ name: "code", type: "string?", description: "Code technique" },
|
|
63216
|
+
{ name: "category", type: "string", description: "Categorie (Global, Admin, Manager, Contributor, Viewer, Custom)" },
|
|
63217
|
+
{ name: "description", type: "string?", description: "Description du role" },
|
|
63218
|
+
{ name: "isSystem", type: "boolean", description: "Role systeme (non modifiable)" },
|
|
63219
|
+
{ name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
|
|
63220
|
+
{ name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
|
|
63221
|
+
{ name: "permissions", type: "string[]", description: "Codes des permissions attribuees" }
|
|
63222
|
+
],
|
|
63223
|
+
defaultRateLimit: 60,
|
|
63224
|
+
defaultMaxPageSize: 1e3
|
|
63225
|
+
},
|
|
63226
|
+
{
|
|
63227
|
+
code: "tickets",
|
|
63228
|
+
name: "Tickets Export",
|
|
63229
|
+
route: "/api/v1/export/tickets",
|
|
63230
|
+
method: "GET",
|
|
63231
|
+
paginated: true,
|
|
63232
|
+
params: [
|
|
63233
|
+
{ name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses tickets" },
|
|
63234
|
+
{ name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
|
|
63235
|
+
{ name: "pageSize", type: "int", default: "100", description: "Elements par page (max 500)" },
|
|
63236
|
+
{ name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
|
|
63237
|
+
],
|
|
63238
|
+
responseFields: [
|
|
63239
|
+
{ name: "id", type: "UUID", description: "Identifiant unique" },
|
|
63240
|
+
{ name: "title", type: "string", description: "Titre du ticket" },
|
|
63241
|
+
{ name: "type", type: "string", description: "Type (Bug, Feature, Support, etc.)" },
|
|
63242
|
+
{ name: "status", type: "string", description: "Statut (Open, InProgress, Resolved, Closed)" },
|
|
63243
|
+
{ name: "priority", type: "string", description: "Priorite (Low, Medium, High, Critical)" },
|
|
63244
|
+
{ name: "assignedToUserId", type: "UUID?", description: "ID utilisateur assigne" },
|
|
63245
|
+
{ name: "createdByUserId", type: "UUID", description: "ID createur" },
|
|
63246
|
+
{ name: "tenantId", type: "UUID?", description: "ID tenant associe" },
|
|
63247
|
+
{ name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
|
|
63248
|
+
{ name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
|
|
63249
|
+
{ name: "resolvedAt", type: "DateTime?", description: "Date de resolution (UTC)" },
|
|
63250
|
+
{ name: "closedAt", type: "DateTime?", description: "Date de fermeture (UTC)" }
|
|
63251
|
+
],
|
|
63252
|
+
defaultRateLimit: 30,
|
|
63253
|
+
defaultMaxPageSize: 500,
|
|
63254
|
+
notes: "Rate limit reduit a 30 req/min. Page size max 500."
|
|
63255
|
+
},
|
|
63256
|
+
{
|
|
63257
|
+
code: "navigation",
|
|
63258
|
+
name: "Navigation Export",
|
|
63259
|
+
route: "/api/v1/export/navigation",
|
|
63260
|
+
method: "GET",
|
|
63261
|
+
paginated: false,
|
|
63262
|
+
params: [],
|
|
63263
|
+
responseFields: [
|
|
63264
|
+
{ name: "id", type: "UUID", description: "Identifiant de l'application" },
|
|
63265
|
+
{ name: "code", type: "string", description: "Code technique de l'application" },
|
|
63266
|
+
{ name: "label", type: "string", description: "Libelle affiche" },
|
|
63267
|
+
{ name: "modules", type: "object[]", description: "Liste des modules (chacun: id, code, label)" }
|
|
63268
|
+
],
|
|
63269
|
+
defaultRateLimit: 60,
|
|
63270
|
+
defaultMaxPageSize: 1e3,
|
|
63271
|
+
notes: "Endpoint NON pagine. Retourne directement un tableau JSON (pas d'envelope de pagination). Contient la structure de navigation : applications avec leurs modules."
|
|
63272
|
+
}
|
|
63273
|
+
];
|
|
63274
|
+
}
|
|
63275
|
+
});
|
|
63276
|
+
|
|
63277
|
+
// src/mcp/tools/analyze-hierarchy-patterns.ts
|
|
63278
|
+
import path27 from "path";
|
|
62096
63279
|
async function handleAnalyzeHierarchyPatterns(args, config2) {
|
|
62097
63280
|
const input = AnalyzeHierarchyPatternsInputSchema.parse(args);
|
|
62098
63281
|
const projectPath = input.path || config2.smartstack.projectPath;
|
|
@@ -62115,7 +63298,7 @@ async function handleAnalyzeHierarchyPatterns(args, config2) {
|
|
|
62115
63298
|
recommendations,
|
|
62116
63299
|
circularDependencies: detectCircularDependencies(entityGraph)
|
|
62117
63300
|
};
|
|
62118
|
-
return
|
|
63301
|
+
return formatResult10(result, outputFormat, structure.root);
|
|
62119
63302
|
}
|
|
62120
63303
|
async function buildEntityGraph(domainPath, filter3) {
|
|
62121
63304
|
const entityFiles = await findFiles("**/*.cs", { cwd: domainPath });
|
|
@@ -62171,7 +63354,7 @@ async function buildEntityGraph(domainPath, filter3) {
|
|
|
62171
63354
|
return entityMap;
|
|
62172
63355
|
}
|
|
62173
63356
|
function parseEntity(content, file, domainPath) {
|
|
62174
|
-
const fileName =
|
|
63357
|
+
const fileName = path27.basename(file, ".cs");
|
|
62175
63358
|
if (fileName.endsWith("Dto") || fileName.endsWith("Command") || fileName.endsWith("Query") || fileName.endsWith("Handler") || fileName.endsWith("Validator") || fileName.endsWith("Exception") || fileName.startsWith("I")) {
|
|
62176
63359
|
return null;
|
|
62177
63360
|
}
|
|
@@ -62215,7 +63398,7 @@ function parseEntity(content, file, domainPath) {
|
|
|
62215
63398
|
const parentEntity = foreignKeys.length > 0 ? foreignKeys[0].referencedEntity : void 0;
|
|
62216
63399
|
return {
|
|
62217
63400
|
name: entityName,
|
|
62218
|
-
file:
|
|
63401
|
+
file: path27.relative(domainPath, file),
|
|
62219
63402
|
baseClass: hasSystemEntity ? "SystemEntity" : "BaseEntity",
|
|
62220
63403
|
isTenantAware: hasITenantEntity,
|
|
62221
63404
|
isSystemEntity: hasSystemEntity,
|
|
@@ -62246,24 +63429,24 @@ function detectCircularDependencies(entityMap) {
|
|
|
62246
63429
|
const cycles = [];
|
|
62247
63430
|
for (const [name] of entityMap) {
|
|
62248
63431
|
let dfs2 = function(current) {
|
|
62249
|
-
if (
|
|
62250
|
-
const cycleStart =
|
|
62251
|
-
cycles.push([...
|
|
63432
|
+
if (path32.includes(current)) {
|
|
63433
|
+
const cycleStart = path32.indexOf(current);
|
|
63434
|
+
cycles.push([...path32.slice(cycleStart), current]);
|
|
62252
63435
|
return true;
|
|
62253
63436
|
}
|
|
62254
63437
|
if (visited.has(current)) return false;
|
|
62255
63438
|
visited.add(current);
|
|
62256
|
-
|
|
63439
|
+
path32.push(current);
|
|
62257
63440
|
const node = entityMap.get(current);
|
|
62258
63441
|
if (node?.parent) {
|
|
62259
63442
|
dfs2(node.parent);
|
|
62260
63443
|
}
|
|
62261
|
-
|
|
63444
|
+
path32.pop();
|
|
62262
63445
|
return false;
|
|
62263
63446
|
};
|
|
62264
63447
|
var dfs = dfs2;
|
|
62265
63448
|
const visited = /* @__PURE__ */ new Set();
|
|
62266
|
-
const
|
|
63449
|
+
const path32 = [];
|
|
62267
63450
|
dfs2(name);
|
|
62268
63451
|
}
|
|
62269
63452
|
const unique = /* @__PURE__ */ new Map();
|
|
@@ -62470,7 +63653,7 @@ function getImplementationSteps(pattern, node) {
|
|
|
62470
63653
|
return [];
|
|
62471
63654
|
}
|
|
62472
63655
|
}
|
|
62473
|
-
function
|
|
63656
|
+
function formatResult10(result, format, _rootPath) {
|
|
62474
63657
|
if (format === "json") {
|
|
62475
63658
|
return JSON.stringify(result, null, 2);
|
|
62476
63659
|
}
|
|
@@ -64875,13 +66058,13 @@ async function getFilesToAnalyze(projectPath, scope, _structure) {
|
|
|
64875
66058
|
try {
|
|
64876
66059
|
const fullPath = filePath.startsWith(projectPath) ? filePath : `${projectPath}/${filePath}`;
|
|
64877
66060
|
const content = await readText(fullPath);
|
|
64878
|
-
const
|
|
66061
|
+
const relativePath2 = filePath.replace(projectPath, "").replace(/^[/\\]/, "");
|
|
64879
66062
|
files.push({
|
|
64880
66063
|
path: fullPath,
|
|
64881
|
-
relativePath,
|
|
66064
|
+
relativePath: relativePath2,
|
|
64882
66065
|
content,
|
|
64883
66066
|
language: detectLanguage(filePath),
|
|
64884
|
-
layer: detectLayer(
|
|
66067
|
+
layer: detectLayer(relativePath2),
|
|
64885
66068
|
lineCount: content.split("\n").length
|
|
64886
66069
|
});
|
|
64887
66070
|
} catch {
|
|
@@ -64911,8 +66094,8 @@ function detectLanguage(filePath) {
|
|
|
64911
66094
|
return "other";
|
|
64912
66095
|
}
|
|
64913
66096
|
}
|
|
64914
|
-
function detectLayer(
|
|
64915
|
-
const lower =
|
|
66097
|
+
function detectLayer(relativePath2) {
|
|
66098
|
+
const lower = relativePath2.toLowerCase();
|
|
64916
66099
|
if (lower.includes("/domain/") || lower.includes("\\domain\\")) return "domain";
|
|
64917
66100
|
if (lower.includes("/application/") || lower.includes("\\application\\")) return "application";
|
|
64918
66101
|
if (lower.includes("/infrastructure/") || lower.includes("\\infrastructure\\")) return "infrastructure";
|
|
@@ -66412,7 +67595,7 @@ var init_conventions = __esm({
|
|
|
66412
67595
|
});
|
|
66413
67596
|
|
|
66414
67597
|
// src/mcp/resources/project-info.ts
|
|
66415
|
-
import
|
|
67598
|
+
import path28 from "path";
|
|
66416
67599
|
async function getProjectInfoResource(config2) {
|
|
66417
67600
|
const projectPath = config2.smartstack.projectPath;
|
|
66418
67601
|
const projectInfo = await detectProject(projectPath);
|
|
@@ -66443,16 +67626,16 @@ async function getProjectInfoResource(config2) {
|
|
|
66443
67626
|
lines.push("```");
|
|
66444
67627
|
lines.push(`${projectInfo.name}/`);
|
|
66445
67628
|
if (structure.domain) {
|
|
66446
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67629
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.domain)}/ # Domain layer (entities)`);
|
|
66447
67630
|
}
|
|
66448
67631
|
if (structure.application) {
|
|
66449
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67632
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.application)}/ # Application layer (services)`);
|
|
66450
67633
|
}
|
|
66451
67634
|
if (structure.infrastructure) {
|
|
66452
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67635
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
|
|
66453
67636
|
}
|
|
66454
67637
|
if (structure.api) {
|
|
66455
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67638
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.api)}/ # API layer (controllers)`);
|
|
66456
67639
|
}
|
|
66457
67640
|
if (structure.web) {
|
|
66458
67641
|
lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
|
|
@@ -66465,9 +67648,9 @@ async function getProjectInfoResource(config2) {
|
|
|
66465
67648
|
lines.push("| Project | Path |");
|
|
66466
67649
|
lines.push("|---------|------|");
|
|
66467
67650
|
for (const csproj of projectInfo.csprojFiles) {
|
|
66468
|
-
const name =
|
|
66469
|
-
const
|
|
66470
|
-
lines.push(`| ${name} | \`${
|
|
67651
|
+
const name = path28.basename(csproj, ".csproj");
|
|
67652
|
+
const relativePath2 = path28.relative(projectPath, csproj);
|
|
67653
|
+
lines.push(`| ${name} | \`${relativePath2}\` |`);
|
|
66471
67654
|
}
|
|
66472
67655
|
lines.push("");
|
|
66473
67656
|
}
|
|
@@ -66476,10 +67659,10 @@ async function getProjectInfoResource(config2) {
|
|
|
66476
67659
|
cwd: structure.migrations,
|
|
66477
67660
|
ignore: ["*.Designer.cs"]
|
|
66478
67661
|
});
|
|
66479
|
-
const migrations = migrationFiles.map((f) =>
|
|
67662
|
+
const migrations = migrationFiles.map((f) => path28.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
|
|
66480
67663
|
lines.push("## EF Core Migrations");
|
|
66481
67664
|
lines.push("");
|
|
66482
|
-
lines.push(`**Location**: \`${
|
|
67665
|
+
lines.push(`**Location**: \`${path28.relative(projectPath, structure.migrations)}\``);
|
|
66483
67666
|
lines.push(`**Total Migrations**: ${migrations.length}`);
|
|
66484
67667
|
lines.push("");
|
|
66485
67668
|
if (migrations.length > 0) {
|
|
@@ -66514,11 +67697,11 @@ async function getProjectInfoResource(config2) {
|
|
|
66514
67697
|
lines.push("dotnet build");
|
|
66515
67698
|
lines.push("");
|
|
66516
67699
|
lines.push("# Run API");
|
|
66517
|
-
lines.push(`cd ${structure.api ?
|
|
67700
|
+
lines.push(`cd ${structure.api ? path28.relative(projectPath, structure.api) : "src/Api"}`);
|
|
66518
67701
|
lines.push("dotnet run");
|
|
66519
67702
|
lines.push("");
|
|
66520
67703
|
lines.push("# Run frontend");
|
|
66521
|
-
lines.push(`cd ${structure.web ?
|
|
67704
|
+
lines.push(`cd ${structure.web ? path28.relative(projectPath, structure.web) : "web"}`);
|
|
66522
67705
|
lines.push("npm run dev");
|
|
66523
67706
|
lines.push("");
|
|
66524
67707
|
lines.push("# Create migration");
|
|
@@ -66556,7 +67739,7 @@ var init_project_info = __esm({
|
|
|
66556
67739
|
});
|
|
66557
67740
|
|
|
66558
67741
|
// src/mcp/resources/api-endpoints.ts
|
|
66559
|
-
import
|
|
67742
|
+
import path29 from "path";
|
|
66560
67743
|
async function getApiEndpointsResource(config2, endpointFilter) {
|
|
66561
67744
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
66562
67745
|
if (!structure.api) {
|
|
@@ -66575,7 +67758,7 @@ async function getApiEndpointsResource(config2, endpointFilter) {
|
|
|
66575
67758
|
}
|
|
66576
67759
|
async function parseController(filePath, _rootPath) {
|
|
66577
67760
|
const content = await readText(filePath);
|
|
66578
|
-
const fileName =
|
|
67761
|
+
const fileName = path29.basename(filePath, ".cs");
|
|
66579
67762
|
const controllerName = fileName.replace("Controller", "");
|
|
66580
67763
|
const endpoints = [];
|
|
66581
67764
|
const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
@@ -66737,7 +67920,7 @@ var init_api_endpoints = __esm({
|
|
|
66737
67920
|
});
|
|
66738
67921
|
|
|
66739
67922
|
// src/mcp/resources/db-schema.ts
|
|
66740
|
-
import
|
|
67923
|
+
import path30 from "path";
|
|
66741
67924
|
async function getDbSchemaResource(config2, tableFilter) {
|
|
66742
67925
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
66743
67926
|
if (!structure.domain && !structure.infrastructure) {
|
|
@@ -66821,7 +68004,7 @@ async function parseEntity2(filePath, rootPath, _config) {
|
|
|
66821
68004
|
tableName,
|
|
66822
68005
|
properties,
|
|
66823
68006
|
relationships,
|
|
66824
|
-
file:
|
|
68007
|
+
file: path30.relative(rootPath, filePath)
|
|
66825
68008
|
};
|
|
66826
68009
|
}
|
|
66827
68010
|
async function enrichFromConfigurations(entities, infrastructurePath, _config) {
|
|
@@ -66982,7 +68165,7 @@ var init_db_schema = __esm({
|
|
|
66982
68165
|
});
|
|
66983
68166
|
|
|
66984
68167
|
// src/mcp/resources/entities.ts
|
|
66985
|
-
import
|
|
68168
|
+
import path31 from "path";
|
|
66986
68169
|
async function getEntitiesResource(config2, entityFilter) {
|
|
66987
68170
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
66988
68171
|
if (!structure.domain) {
|
|
@@ -67036,7 +68219,7 @@ async function parseEntitySummary(filePath, rootPath, config2) {
|
|
|
67036
68219
|
hasSoftDelete,
|
|
67037
68220
|
hasRowVersion,
|
|
67038
68221
|
file: filePath,
|
|
67039
|
-
relativePath:
|
|
68222
|
+
relativePath: path31.relative(rootPath, filePath)
|
|
67040
68223
|
};
|
|
67041
68224
|
}
|
|
67042
68225
|
function inferTableInfo(entityName, config2) {
|
|
@@ -67094,11 +68277,11 @@ function inferTableInfo(entityName, config2) {
|
|
|
67094
68277
|
if (!validPrefixes.includes(tablePrefix)) {
|
|
67095
68278
|
tablePrefix = "ref_";
|
|
67096
68279
|
}
|
|
67097
|
-
const tableName = `${tablePrefix}${
|
|
68280
|
+
const tableName = `${tablePrefix}${pluralize2(entityName)}`;
|
|
67098
68281
|
const schema = config2.conventions.schemas.platform;
|
|
67099
68282
|
return { tableName, tablePrefix, schema };
|
|
67100
68283
|
}
|
|
67101
|
-
function
|
|
68284
|
+
function pluralize2(name) {
|
|
67102
68285
|
if (name.endsWith("y") && !/[aeiou]y$/i.test(name)) {
|
|
67103
68286
|
return name.slice(0, -1) + "ies";
|
|
67104
68287
|
}
|
|
@@ -67254,6 +68437,9 @@ async function createServer() {
|
|
|
67254
68437
|
// Frontend Extension Tools
|
|
67255
68438
|
scaffoldFrontendExtensionTool,
|
|
67256
68439
|
analyzeExtensionPointsTool,
|
|
68440
|
+
// Data Export Tools
|
|
68441
|
+
scaffoldDataExportTool,
|
|
68442
|
+
promptDataExportM2mTool,
|
|
67257
68443
|
// Security & Code Quality Tools
|
|
67258
68444
|
validateSecurityTool,
|
|
67259
68445
|
analyzeCodeQualityTool,
|
|
@@ -67323,6 +68509,13 @@ async function createServer() {
|
|
|
67323
68509
|
case "analyze_extension_points":
|
|
67324
68510
|
result = await handleAnalyzeExtensionPoints(args ?? {}, config2);
|
|
67325
68511
|
break;
|
|
68512
|
+
// Data Export Tools
|
|
68513
|
+
case "scaffold_data_export":
|
|
68514
|
+
result = await handleScaffoldDataExport(args ?? {}, config2);
|
|
68515
|
+
break;
|
|
68516
|
+
case "prompt_data_export_m2m":
|
|
68517
|
+
result = await handlePromptDataExportM2m(args ?? {}, config2);
|
|
68518
|
+
break;
|
|
67326
68519
|
// Security & Code Quality Tools
|
|
67327
68520
|
case "validate_security":
|
|
67328
68521
|
result = await handleValidateSecurity(args ?? {}, config2);
|
|
@@ -67451,6 +68644,8 @@ var init_server3 = __esm({
|
|
|
67451
68644
|
init_scaffold_frontend_tests();
|
|
67452
68645
|
init_validate_security();
|
|
67453
68646
|
init_analyze_code_quality();
|
|
68647
|
+
init_scaffold_data_export();
|
|
68648
|
+
init_prompt_data_export_m2m();
|
|
67454
68649
|
init_analyze_hierarchy_patterns();
|
|
67455
68650
|
init_review_code();
|
|
67456
68651
|
init_conventions();
|