@atlashub/smartstack-cli 3.54.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 +850 -172
- 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 +1 -1
- package/templates/skills/apex/references/smartstack-api.md +6 -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/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, ScaffoldDataExportInputSchema, 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";
|
|
@@ -26079,6 +26144,14 @@ var init_types3 = __esm({
|
|
|
26079
26144
|
dryRun: external_exports.boolean().default(false).describe("Preview without writing files (default: false)")
|
|
26080
26145
|
}).optional()
|
|
26081
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
|
+
});
|
|
26082
26155
|
AnalyzeHierarchyPatternsInputSchema = external_exports.object({
|
|
26083
26156
|
path: external_exports.string().optional().describe("Project path to analyze (default: SmartStack.app path)"),
|
|
26084
26157
|
entityFilter: external_exports.string().optional().describe('Filter entities by name pattern (e.g., "User*", "*Group*")'),
|
|
@@ -27271,8 +27344,8 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27271
27344
|
const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
27272
27345
|
if (!routeAttrMatch) continue;
|
|
27273
27346
|
const routeValue = routeAttrMatch[1];
|
|
27274
|
-
const
|
|
27275
|
-
const dirParts = path8.dirname(
|
|
27347
|
+
const relativePath2 = path8.relative(structure.api, file);
|
|
27348
|
+
const dirParts = path8.dirname(relativePath2).split(path8.sep);
|
|
27276
27349
|
const moduleDir = dirParts.slice(0, Math.min(dirParts.length, 3)).join("/");
|
|
27277
27350
|
if (!routesByDirectory.has(moduleDir)) {
|
|
27278
27351
|
routesByDirectory.set(moduleDir, []);
|
|
@@ -29876,13 +29949,13 @@ var require_ast = __commonJS({
|
|
|
29876
29949
|
helperExpression: function helperExpression(node) {
|
|
29877
29950
|
return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
|
|
29878
29951
|
},
|
|
29879
|
-
scopedId: function scopedId(
|
|
29880
|
-
return /^\.|this\b/.test(
|
|
29952
|
+
scopedId: function scopedId(path32) {
|
|
29953
|
+
return /^\.|this\b/.test(path32.original);
|
|
29881
29954
|
},
|
|
29882
29955
|
// an ID is simple if it only has one part, and that part is not
|
|
29883
29956
|
// `..` or `this`.
|
|
29884
|
-
simpleId: function simpleId(
|
|
29885
|
-
return
|
|
29957
|
+
simpleId: function simpleId(path32) {
|
|
29958
|
+
return path32.parts.length === 1 && !AST2.helpers.scopedId(path32) && !path32.depth;
|
|
29886
29959
|
}
|
|
29887
29960
|
}
|
|
29888
29961
|
};
|
|
@@ -30956,12 +31029,12 @@ var require_helpers2 = __commonJS({
|
|
|
30956
31029
|
loc
|
|
30957
31030
|
};
|
|
30958
31031
|
}
|
|
30959
|
-
function prepareMustache(
|
|
31032
|
+
function prepareMustache(path32, params, hash, open, strip, locInfo) {
|
|
30960
31033
|
var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
30961
31034
|
var decorator = /\*/.test(open);
|
|
30962
31035
|
return {
|
|
30963
31036
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
30964
|
-
path:
|
|
31037
|
+
path: path32,
|
|
30965
31038
|
params,
|
|
30966
31039
|
hash,
|
|
30967
31040
|
escaped,
|
|
@@ -31233,9 +31306,9 @@ var require_compiler = __commonJS({
|
|
|
31233
31306
|
},
|
|
31234
31307
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
31235
31308
|
var program = decorator.program && this.compileProgram(decorator.program);
|
|
31236
|
-
var params = this.setupFullMustacheParams(decorator, program, void 0),
|
|
31309
|
+
var params = this.setupFullMustacheParams(decorator, program, void 0), path32 = decorator.path;
|
|
31237
31310
|
this.useDecorators = true;
|
|
31238
|
-
this.opcode("registerDecorator", params.length,
|
|
31311
|
+
this.opcode("registerDecorator", params.length, path32.original);
|
|
31239
31312
|
},
|
|
31240
31313
|
PartialStatement: function PartialStatement(partial2) {
|
|
31241
31314
|
this.usePartial = true;
|
|
@@ -31299,46 +31372,46 @@ var require_compiler = __commonJS({
|
|
|
31299
31372
|
}
|
|
31300
31373
|
},
|
|
31301
31374
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
|
|
31302
|
-
var
|
|
31303
|
-
this.opcode("getContext",
|
|
31375
|
+
var path32 = sexpr.path, name = path32.parts[0], isBlock = program != null || inverse != null;
|
|
31376
|
+
this.opcode("getContext", path32.depth);
|
|
31304
31377
|
this.opcode("pushProgram", program);
|
|
31305
31378
|
this.opcode("pushProgram", inverse);
|
|
31306
|
-
|
|
31307
|
-
this.accept(
|
|
31379
|
+
path32.strict = true;
|
|
31380
|
+
this.accept(path32);
|
|
31308
31381
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
31309
31382
|
},
|
|
31310
31383
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
31311
|
-
var
|
|
31312
|
-
|
|
31313
|
-
this.accept(
|
|
31384
|
+
var path32 = sexpr.path;
|
|
31385
|
+
path32.strict = true;
|
|
31386
|
+
this.accept(path32);
|
|
31314
31387
|
this.opcode("resolvePossibleLambda");
|
|
31315
31388
|
},
|
|
31316
31389
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
31317
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
31390
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path32 = sexpr.path, name = path32.parts[0];
|
|
31318
31391
|
if (this.options.knownHelpers[name]) {
|
|
31319
31392
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
31320
31393
|
} else if (this.options.knownHelpersOnly) {
|
|
31321
31394
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
31322
31395
|
} else {
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
this.accept(
|
|
31326
|
-
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));
|
|
31327
31400
|
}
|
|
31328
31401
|
},
|
|
31329
|
-
PathExpression: function PathExpression(
|
|
31330
|
-
this.addDepth(
|
|
31331
|
-
this.opcode("getContext",
|
|
31332
|
-
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);
|
|
31333
31406
|
if (blockParamId) {
|
|
31334
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
31407
|
+
this.opcode("lookupBlockParam", blockParamId, path32.parts);
|
|
31335
31408
|
} else if (!name) {
|
|
31336
31409
|
this.opcode("pushContext");
|
|
31337
|
-
} else if (
|
|
31410
|
+
} else if (path32.data) {
|
|
31338
31411
|
this.options.data = true;
|
|
31339
|
-
this.opcode("lookupData",
|
|
31412
|
+
this.opcode("lookupData", path32.depth, path32.parts, path32.strict);
|
|
31340
31413
|
} else {
|
|
31341
|
-
this.opcode("lookupOnContext",
|
|
31414
|
+
this.opcode("lookupOnContext", path32.parts, path32.falsy, path32.strict, scoped);
|
|
31342
31415
|
}
|
|
31343
31416
|
},
|
|
31344
31417
|
StringLiteral: function StringLiteral(string3) {
|
|
@@ -31694,16 +31767,16 @@ var require_util2 = __commonJS({
|
|
|
31694
31767
|
}
|
|
31695
31768
|
exports.urlGenerate = urlGenerate;
|
|
31696
31769
|
function normalize2(aPath) {
|
|
31697
|
-
var
|
|
31770
|
+
var path32 = aPath;
|
|
31698
31771
|
var url2 = urlParse(aPath);
|
|
31699
31772
|
if (url2) {
|
|
31700
31773
|
if (!url2.path) {
|
|
31701
31774
|
return aPath;
|
|
31702
31775
|
}
|
|
31703
|
-
|
|
31776
|
+
path32 = url2.path;
|
|
31704
31777
|
}
|
|
31705
|
-
var isAbsolute = exports.isAbsolute(
|
|
31706
|
-
var parts =
|
|
31778
|
+
var isAbsolute = exports.isAbsolute(path32);
|
|
31779
|
+
var parts = path32.split(/\/+/);
|
|
31707
31780
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
31708
31781
|
part = parts[i];
|
|
31709
31782
|
if (part === ".") {
|
|
@@ -31720,15 +31793,15 @@ var require_util2 = __commonJS({
|
|
|
31720
31793
|
}
|
|
31721
31794
|
}
|
|
31722
31795
|
}
|
|
31723
|
-
|
|
31724
|
-
if (
|
|
31725
|
-
|
|
31796
|
+
path32 = parts.join("/");
|
|
31797
|
+
if (path32 === "") {
|
|
31798
|
+
path32 = isAbsolute ? "/" : ".";
|
|
31726
31799
|
}
|
|
31727
31800
|
if (url2) {
|
|
31728
|
-
url2.path =
|
|
31801
|
+
url2.path = path32;
|
|
31729
31802
|
return urlGenerate(url2);
|
|
31730
31803
|
}
|
|
31731
|
-
return
|
|
31804
|
+
return path32;
|
|
31732
31805
|
}
|
|
31733
31806
|
exports.normalize = normalize2;
|
|
31734
31807
|
function join2(aRoot, aPath) {
|
|
@@ -34529,8 +34602,8 @@ var require_printer = __commonJS({
|
|
|
34529
34602
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
34530
34603
|
};
|
|
34531
34604
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
34532
|
-
var
|
|
34533
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
34605
|
+
var path32 = id.parts.join("/");
|
|
34606
|
+
return (id.data ? "@" : "") + "PATH:" + path32;
|
|
34534
34607
|
};
|
|
34535
34608
|
PrintVisitor.prototype.StringLiteral = function(string3) {
|
|
34536
34609
|
return '"' + string3.value + '"';
|
|
@@ -46127,11 +46200,11 @@ var require_mime_types = __commonJS({
|
|
|
46127
46200
|
}
|
|
46128
46201
|
return exts[0];
|
|
46129
46202
|
}
|
|
46130
|
-
function lookup(
|
|
46131
|
-
if (!
|
|
46203
|
+
function lookup(path32) {
|
|
46204
|
+
if (!path32 || typeof path32 !== "string") {
|
|
46132
46205
|
return false;
|
|
46133
46206
|
}
|
|
46134
|
-
var extension2 = extname("x." +
|
|
46207
|
+
var extension2 = extname("x." + path32).toLowerCase().substr(1);
|
|
46135
46208
|
if (!extension2) {
|
|
46136
46209
|
return false;
|
|
46137
46210
|
}
|
|
@@ -47294,7 +47367,7 @@ var require_form_data = __commonJS({
|
|
|
47294
47367
|
init_esm_shims();
|
|
47295
47368
|
var CombinedStream = require_combined_stream();
|
|
47296
47369
|
var util4 = __require("util");
|
|
47297
|
-
var
|
|
47370
|
+
var path32 = __require("path");
|
|
47298
47371
|
var http3 = __require("http");
|
|
47299
47372
|
var https2 = __require("https");
|
|
47300
47373
|
var parseUrl = __require("url").parse;
|
|
@@ -47422,11 +47495,11 @@ var require_form_data = __commonJS({
|
|
|
47422
47495
|
FormData3.prototype._getContentDisposition = function(value, options) {
|
|
47423
47496
|
var filename;
|
|
47424
47497
|
if (typeof options.filepath === "string") {
|
|
47425
|
-
filename =
|
|
47498
|
+
filename = path32.normalize(options.filepath).replace(/\\/g, "/");
|
|
47426
47499
|
} else if (options.filename || value && (value.name || value.path)) {
|
|
47427
|
-
filename =
|
|
47500
|
+
filename = path32.basename(options.filename || value && (value.name || value.path));
|
|
47428
47501
|
} else if (value && value.readable && hasOwn(value, "httpVersion")) {
|
|
47429
|
-
filename =
|
|
47502
|
+
filename = path32.basename(value.client._httpMessage.path || "");
|
|
47430
47503
|
}
|
|
47431
47504
|
if (filename) {
|
|
47432
47505
|
return 'filename="' + filename + '"';
|
|
@@ -47625,9 +47698,9 @@ function isVisitable(thing) {
|
|
|
47625
47698
|
function removeBrackets(key) {
|
|
47626
47699
|
return utils_default.endsWith(key, "[]") ? key.slice(0, -2) : key;
|
|
47627
47700
|
}
|
|
47628
|
-
function renderKey(
|
|
47629
|
-
if (!
|
|
47630
|
-
return
|
|
47701
|
+
function renderKey(path32, key, dots) {
|
|
47702
|
+
if (!path32) return key;
|
|
47703
|
+
return path32.concat(key).map(function each(token, i) {
|
|
47631
47704
|
token = removeBrackets(token);
|
|
47632
47705
|
return !dots && i ? "[" + token + "]" : token;
|
|
47633
47706
|
}).join(dots ? "." : "");
|
|
@@ -47672,9 +47745,9 @@ function toFormData(obj, formData, options) {
|
|
|
47672
47745
|
}
|
|
47673
47746
|
return value;
|
|
47674
47747
|
}
|
|
47675
|
-
function defaultVisitor(value, key,
|
|
47748
|
+
function defaultVisitor(value, key, path32) {
|
|
47676
47749
|
let arr = value;
|
|
47677
|
-
if (value && !
|
|
47750
|
+
if (value && !path32 && typeof value === "object") {
|
|
47678
47751
|
if (utils_default.endsWith(key, "{}")) {
|
|
47679
47752
|
key = metaTokens ? key : key.slice(0, -2);
|
|
47680
47753
|
value = JSON.stringify(value);
|
|
@@ -47693,7 +47766,7 @@ function toFormData(obj, formData, options) {
|
|
|
47693
47766
|
if (isVisitable(value)) {
|
|
47694
47767
|
return true;
|
|
47695
47768
|
}
|
|
47696
|
-
formData.append(renderKey(
|
|
47769
|
+
formData.append(renderKey(path32, key, dots), convertValue(value));
|
|
47697
47770
|
return false;
|
|
47698
47771
|
}
|
|
47699
47772
|
const stack = [];
|
|
@@ -47702,10 +47775,10 @@ function toFormData(obj, formData, options) {
|
|
|
47702
47775
|
convertValue,
|
|
47703
47776
|
isVisitable
|
|
47704
47777
|
});
|
|
47705
|
-
function build(value,
|
|
47778
|
+
function build(value, path32) {
|
|
47706
47779
|
if (utils_default.isUndefined(value)) return;
|
|
47707
47780
|
if (stack.indexOf(value) !== -1) {
|
|
47708
|
-
throw Error("Circular reference detected in " +
|
|
47781
|
+
throw Error("Circular reference detected in " + path32.join("."));
|
|
47709
47782
|
}
|
|
47710
47783
|
stack.push(value);
|
|
47711
47784
|
utils_default.forEach(value, function each(el, key) {
|
|
@@ -47713,11 +47786,11 @@ function toFormData(obj, formData, options) {
|
|
|
47713
47786
|
formData,
|
|
47714
47787
|
el,
|
|
47715
47788
|
utils_default.isString(key) ? key.trim() : key,
|
|
47716
|
-
|
|
47789
|
+
path32,
|
|
47717
47790
|
exposedHelpers
|
|
47718
47791
|
);
|
|
47719
47792
|
if (result === true) {
|
|
47720
|
-
build(el,
|
|
47793
|
+
build(el, path32 ? path32.concat(key) : [key]);
|
|
47721
47794
|
}
|
|
47722
47795
|
});
|
|
47723
47796
|
stack.pop();
|
|
@@ -48003,7 +48076,7 @@ var init_platform = __esm({
|
|
|
48003
48076
|
// node_modules/axios/lib/helpers/toURLEncodedForm.js
|
|
48004
48077
|
function toURLEncodedForm(data, options) {
|
|
48005
48078
|
return toFormData_default(data, new platform_default.classes.URLSearchParams(), {
|
|
48006
|
-
visitor: function(value, key,
|
|
48079
|
+
visitor: function(value, key, path32, helpers) {
|
|
48007
48080
|
if (platform_default.isNode && utils_default.isBuffer(value)) {
|
|
48008
48081
|
this.append(key, value.toString("base64"));
|
|
48009
48082
|
return false;
|
|
@@ -48042,11 +48115,11 @@ function arrayToObject(arr) {
|
|
|
48042
48115
|
return obj;
|
|
48043
48116
|
}
|
|
48044
48117
|
function formDataToJSON(formData) {
|
|
48045
|
-
function buildPath(
|
|
48046
|
-
let name =
|
|
48118
|
+
function buildPath(path32, value, target, index) {
|
|
48119
|
+
let name = path32[index++];
|
|
48047
48120
|
if (name === "__proto__") return true;
|
|
48048
48121
|
const isNumericKey = Number.isFinite(+name);
|
|
48049
|
-
const isLast = index >=
|
|
48122
|
+
const isLast = index >= path32.length;
|
|
48050
48123
|
name = !name && utils_default.isArray(target) ? target.length : name;
|
|
48051
48124
|
if (isLast) {
|
|
48052
48125
|
if (utils_default.hasOwnProp(target, name)) {
|
|
@@ -48059,7 +48132,7 @@ function formDataToJSON(formData) {
|
|
|
48059
48132
|
if (!target[name] || !utils_default.isObject(target[name])) {
|
|
48060
48133
|
target[name] = [];
|
|
48061
48134
|
}
|
|
48062
|
-
const result = buildPath(
|
|
48135
|
+
const result = buildPath(path32, value, target[name], index);
|
|
48063
48136
|
if (result && utils_default.isArray(target[name])) {
|
|
48064
48137
|
target[name] = arrayToObject(target[name]);
|
|
48065
48138
|
}
|
|
@@ -50959,9 +51032,9 @@ var init_http = __esm({
|
|
|
50959
51032
|
auth = urlUsername + ":" + urlPassword;
|
|
50960
51033
|
}
|
|
50961
51034
|
auth && headers.delete("authorization");
|
|
50962
|
-
let
|
|
51035
|
+
let path32;
|
|
50963
51036
|
try {
|
|
50964
|
-
|
|
51037
|
+
path32 = buildURL(
|
|
50965
51038
|
parsed.pathname + parsed.search,
|
|
50966
51039
|
config2.params,
|
|
50967
51040
|
config2.paramsSerializer
|
|
@@ -50979,7 +51052,7 @@ var init_http = __esm({
|
|
|
50979
51052
|
false
|
|
50980
51053
|
);
|
|
50981
51054
|
const options = {
|
|
50982
|
-
path:
|
|
51055
|
+
path: path32,
|
|
50983
51056
|
method,
|
|
50984
51057
|
headers: headers.toJSON(),
|
|
50985
51058
|
agents: { http: config2.httpAgent, https: config2.httpsAgent },
|
|
@@ -51232,14 +51305,14 @@ var init_cookies = __esm({
|
|
|
51232
51305
|
cookies_default = platform_default.hasStandardBrowserEnv ? (
|
|
51233
51306
|
// Standard browser envs support document.cookie
|
|
51234
51307
|
{
|
|
51235
|
-
write(name, value, expires,
|
|
51308
|
+
write(name, value, expires, path32, domain, secure, sameSite) {
|
|
51236
51309
|
if (typeof document === "undefined") return;
|
|
51237
51310
|
const cookie = [`${name}=${encodeURIComponent(value)}`];
|
|
51238
51311
|
if (utils_default.isNumber(expires)) {
|
|
51239
51312
|
cookie.push(`expires=${new Date(expires).toUTCString()}`);
|
|
51240
51313
|
}
|
|
51241
|
-
if (utils_default.isString(
|
|
51242
|
-
cookie.push(`path=${
|
|
51314
|
+
if (utils_default.isString(path32)) {
|
|
51315
|
+
cookie.push(`path=${path32}`);
|
|
51243
51316
|
}
|
|
51244
51317
|
if (utils_default.isString(domain)) {
|
|
51245
51318
|
cookie.push(`domain=${domain}`);
|
|
@@ -57600,8 +57673,8 @@ function formatResult4(result, input) {
|
|
|
57600
57673
|
lines.push("## Generated Files");
|
|
57601
57674
|
lines.push("");
|
|
57602
57675
|
for (const file of result.files) {
|
|
57603
|
-
const
|
|
57604
|
-
lines.push(`### ${
|
|
57676
|
+
const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
|
|
57677
|
+
lines.push(`### ${relativePath2}`);
|
|
57605
57678
|
lines.push("");
|
|
57606
57679
|
lines.push("```typescript");
|
|
57607
57680
|
lines.push(file.content.substring(0, 1500) + (file.content.length > 1500 ? "\n// ... (truncated)" : ""));
|
|
@@ -58486,8 +58559,8 @@ function formatResult5(result, input) {
|
|
|
58486
58559
|
lines.push("## Generated Files");
|
|
58487
58560
|
lines.push("");
|
|
58488
58561
|
for (const file of result.files) {
|
|
58489
|
-
const
|
|
58490
|
-
lines.push(`### ${
|
|
58562
|
+
const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
|
|
58563
|
+
lines.push(`### ${relativePath2}`);
|
|
58491
58564
|
lines.push("");
|
|
58492
58565
|
lines.push("```tsx");
|
|
58493
58566
|
lines.push(file.content.substring(0, 2e3) + (file.content.length > 2e3 ? "\n// ... (truncated)" : ""));
|
|
@@ -58703,7 +58776,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58703
58776
|
for (const file of clientFiles) {
|
|
58704
58777
|
try {
|
|
58705
58778
|
const content = await readText(file);
|
|
58706
|
-
const
|
|
58779
|
+
const relativePath2 = path20.relative(webPath, file);
|
|
58707
58780
|
const usesRegistry = content.includes("getRoute('") || content.includes('getRoute("');
|
|
58708
58781
|
if (!usesRegistry) {
|
|
58709
58782
|
const hardcodedMatch = content.match(/apiClient\.(get|post|put|delete)\s*[<(]\s*['"`]([^'"`]+)['"`]/);
|
|
@@ -58711,7 +58784,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58711
58784
|
result.apiClients.issues.push({
|
|
58712
58785
|
type: "invalid-path",
|
|
58713
58786
|
severity: "warning",
|
|
58714
|
-
file:
|
|
58787
|
+
file: relativePath2,
|
|
58715
58788
|
message: `Hardcoded API path: ${hardcodedMatch[2]}`,
|
|
58716
58789
|
suggestion: "Use getRoute() from navRoutes.generated.ts instead"
|
|
58717
58790
|
});
|
|
@@ -58725,7 +58798,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
|
|
|
58725
58798
|
result.apiClients.issues.push({
|
|
58726
58799
|
type: "missing-route",
|
|
58727
58800
|
severity: "error",
|
|
58728
|
-
file:
|
|
58801
|
+
file: relativePath2,
|
|
58729
58802
|
navRoute,
|
|
58730
58803
|
message: `NavRoute "${navRoute}" not found in backend controllers`,
|
|
58731
58804
|
suggestion: "Verify the NavRoute path or update the backend controller"
|
|
@@ -60106,11 +60179,11 @@ async function analyzeFile(filePath, srcPath, target) {
|
|
|
60106
60179
|
try {
|
|
60107
60180
|
const content = await readText(filePath);
|
|
60108
60181
|
if (!content) return null;
|
|
60109
|
-
const
|
|
60182
|
+
const relativePath2 = path21.relative(srcPath, filePath).replace(/\\/g, "/");
|
|
60110
60183
|
const componentName = extractComponentName(content, filePath);
|
|
60111
60184
|
const isPage = filePath.includes("/pages/") || filePath.includes("\\pages\\");
|
|
60112
60185
|
const analysis = {
|
|
60113
|
-
file:
|
|
60186
|
+
file: relativePath2,
|
|
60114
60187
|
componentName,
|
|
60115
60188
|
type: isPage ? "page" : "component",
|
|
60116
60189
|
suggestedSlots: [],
|
|
@@ -60120,7 +60193,7 @@ async function analyzeFile(filePath, srcPath, target) {
|
|
|
60120
60193
|
imports: extractImports(content),
|
|
60121
60194
|
lineCount: content.split("\n").length
|
|
60122
60195
|
};
|
|
60123
|
-
const slotPrefix = generateSlotPrefix(componentName,
|
|
60196
|
+
const slotPrefix = generateSlotPrefix(componentName, relativePath2);
|
|
60124
60197
|
if (target === "forms" && !analysis.hasForm) return null;
|
|
60125
60198
|
if (target === "tables" && !analysis.hasTable) return null;
|
|
60126
60199
|
if (isPage) {
|
|
@@ -62186,6 +62259,8 @@ async function scaffoldDataExport(input, config2) {
|
|
|
62186
62259
|
result.instructions.push(`Add the export permission "${permissionPath}.export" to PermissionConfiguration.cs`);
|
|
62187
62260
|
result.instructions.push(`Add the seed data to DataExportEndpointConfiguration or call the fragment from your seeder`);
|
|
62188
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`);
|
|
62189
62264
|
if (includeFrontendClient) {
|
|
62190
62265
|
result.instructions.push(`Import the client: import { export${name}Api } from './services/api/export${name}';`);
|
|
62191
62266
|
}
|
|
@@ -62205,6 +62280,7 @@ namespace ${baseNamespace}.Api.Controllers.DataExport.v1;
|
|
|
62205
62280
|
/// <summary>
|
|
62206
62281
|
/// Data Export endpoint for ${name} entities.
|
|
62207
62282
|
/// Security: JWT authentication + RequirePermission + Rate limiting + DataExportAccessMiddleware
|
|
62283
|
+
/// Tenant isolation: Required tenantId query parameter
|
|
62208
62284
|
/// </summary>
|
|
62209
62285
|
[ApiController]
|
|
62210
62286
|
[Route("api/v1/export")]
|
|
@@ -62220,20 +62296,34 @@ public class Export${name}Controller : ControllerBase
|
|
|
62220
62296
|
}
|
|
62221
62297
|
|
|
62222
62298
|
/// <summary>
|
|
62223
|
-
/// Export ${name} data with pagination and optional modified-since filter.
|
|
62299
|
+
/// Export ${name} data with pagination, tenant isolation, and optional modified-since filter.
|
|
62224
62300
|
/// </summary>
|
|
62225
62301
|
[HttpGet("${namePlural}")]
|
|
62226
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)]
|
|
62227
62306
|
public async Task<ActionResult<PaginatedResult<Export${name}Dto>>> Export(
|
|
62307
|
+
[FromQuery] Guid tenantId,
|
|
62228
62308
|
[FromQuery] int page = 1,
|
|
62229
62309
|
[FromQuery] int pageSize = 100,
|
|
62230
62310
|
[FromQuery] DateTime? modifiedSince = null,
|
|
62231
62311
|
CancellationToken ct = default)
|
|
62232
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
|
+
|
|
62233
62323
|
pageSize = Math.Clamp(pageSize, 1, ${maxPageSize});
|
|
62234
62324
|
page = Math.Max(1, page);
|
|
62235
62325
|
|
|
62236
|
-
var result = await _exportService.ExportAsync(page, pageSize, modifiedSince, ct);
|
|
62326
|
+
var result = await _exportService.ExportAsync(tenantId, page, pageSize, modifiedSince, ct);
|
|
62237
62327
|
return Ok(result);
|
|
62238
62328
|
}
|
|
62239
62329
|
}
|
|
@@ -62247,13 +62337,15 @@ namespace ${baseNamespace}.Application.Common.Interfaces;
|
|
|
62247
62337
|
|
|
62248
62338
|
/// <summary>
|
|
62249
62339
|
/// Service interface for ${name} data export.
|
|
62340
|
+
/// Enforces tenant isolation via tenantId parameter.
|
|
62250
62341
|
/// </summary>
|
|
62251
62342
|
public interface IExport${name}Service
|
|
62252
62343
|
{
|
|
62253
62344
|
/// <summary>
|
|
62254
|
-
/// Export ${name} data with pagination and optional modified-since filter.
|
|
62345
|
+
/// Export ${name} data with tenant isolation, pagination, and optional modified-since filter.
|
|
62255
62346
|
/// </summary>
|
|
62256
62347
|
Task<PaginatedResult<Export${name}Dto>> ExportAsync(
|
|
62348
|
+
Guid tenantId,
|
|
62257
62349
|
int page = 1,
|
|
62258
62350
|
int pageSize = 100,
|
|
62259
62351
|
DateTime? modifiedSince = null,
|
|
@@ -62282,35 +62374,35 @@ namespace ${baseNamespace}.Infrastructure.Services.DataExport;
|
|
|
62282
62374
|
|
|
62283
62375
|
/// <summary>
|
|
62284
62376
|
/// Data export service for ${name} entities.
|
|
62285
|
-
/// Enforces tenant isolation via
|
|
62377
|
+
/// Enforces tenant isolation via tenantId parameter validation.
|
|
62286
62378
|
/// </summary>
|
|
62287
62379
|
public class Export${name}Service : IExport${name}Service
|
|
62288
62380
|
{
|
|
62289
62381
|
private readonly ApplicationDbContext _db;
|
|
62290
|
-
private readonly ICurrentTenantService _currentTenant;
|
|
62291
62382
|
private readonly ICurrentUserService _currentUser;
|
|
62292
62383
|
private readonly ILogger<Export${name}Service> _logger;
|
|
62293
62384
|
|
|
62294
62385
|
public Export${name}Service(
|
|
62295
62386
|
ApplicationDbContext db,
|
|
62296
|
-
ICurrentTenantService currentTenant,
|
|
62297
62387
|
ICurrentUserService currentUser,
|
|
62298
62388
|
ILogger<Export${name}Service> logger)
|
|
62299
62389
|
{
|
|
62300
62390
|
_db = db;
|
|
62301
|
-
_currentTenant = currentTenant;
|
|
62302
62391
|
_currentUser = currentUser;
|
|
62303
62392
|
_logger = logger;
|
|
62304
62393
|
}
|
|
62305
62394
|
|
|
62306
62395
|
public async Task<PaginatedResult<Export${name}Dto>> ExportAsync(
|
|
62396
|
+
Guid tenantId,
|
|
62307
62397
|
int page = 1,
|
|
62308
62398
|
int pageSize = 100,
|
|
62309
62399
|
DateTime? modifiedSince = null,
|
|
62310
62400
|
CancellationToken ct = default)
|
|
62311
62401
|
{
|
|
62312
|
-
|
|
62313
|
-
|
|
62402
|
+
if (tenantId == Guid.Empty)
|
|
62403
|
+
{
|
|
62404
|
+
throw new ArgumentException("Tenant ID cannot be empty", nameof(tenantId));
|
|
62405
|
+
}
|
|
62314
62406
|
|
|
62315
62407
|
_logger.LogInformation("Data export requested for ${name} by {UserId}, tenant {TenantId}, page {Page}",
|
|
62316
62408
|
_currentUser.UserId, tenantId, page);
|
|
@@ -62401,6 +62493,7 @@ function generateFrontendClient(name, nameLower, namePlural) {
|
|
|
62401
62493
|
*
|
|
62402
62494
|
* Auto-generated by SmartStack MCP - DO NOT EDIT MANUALLY
|
|
62403
62495
|
* Endpoint: /api/v1/export/${namePlural}
|
|
62496
|
+
* Note: tenantId is required and must be passed for all requests
|
|
62404
62497
|
*/
|
|
62405
62498
|
|
|
62406
62499
|
import { apiClient } from '../lib/apiClient';
|
|
@@ -62413,6 +62506,7 @@ export interface Export${name}Dto {
|
|
|
62413
62506
|
}
|
|
62414
62507
|
|
|
62415
62508
|
export interface ExportParams {
|
|
62509
|
+
tenantId: string; // Required: tenant identifier
|
|
62416
62510
|
page?: number;
|
|
62417
62511
|
pageSize?: number;
|
|
62418
62512
|
modifiedSince?: string;
|
|
@@ -62432,19 +62526,25 @@ const EXPORT_ENDPOINT = '/api/v1/export/${namePlural}';
|
|
|
62432
62526
|
|
|
62433
62527
|
export const export${name}Api = {
|
|
62434
62528
|
/**
|
|
62435
|
-
* Fetch paginated export data
|
|
62529
|
+
* Fetch paginated export data for a specific tenant
|
|
62530
|
+
* @param tenantId - Required tenant identifier
|
|
62531
|
+
* @param params - Pagination and filter parameters
|
|
62436
62532
|
*/
|
|
62437
|
-
async getPage(params?: ExportParams): Promise<PaginatedExportResult<Export${name}Dto>> {
|
|
62438
|
-
const response = await apiClient.get<PaginatedExportResult<Export${name}Dto>>(EXPORT_ENDPOINT, {
|
|
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
|
+
});
|
|
62439
62537
|
return response.data;
|
|
62440
62538
|
},
|
|
62441
62539
|
|
|
62442
62540
|
/**
|
|
62443
|
-
* Download all pages as a blob (CSV/JSON)
|
|
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)
|
|
62444
62544
|
*/
|
|
62445
|
-
async downloadAll(format: 'json' | 'csv' = 'json'): Promise<Blob> {
|
|
62545
|
+
async downloadAll(tenantId: string, format: 'json' | 'csv' = 'json'): Promise<Blob> {
|
|
62446
62546
|
const response = await apiClient.get(EXPORT_ENDPOINT, {
|
|
62447
|
-
params: { pageSize: 1000, format },
|
|
62547
|
+
params: { tenantId, pageSize: 1000, format },
|
|
62448
62548
|
responseType: 'blob',
|
|
62449
62549
|
});
|
|
62450
62550
|
return response.data;
|
|
@@ -62482,8 +62582,8 @@ function formatResult9(result, input) {
|
|
|
62482
62582
|
lines.push("## Generated Files");
|
|
62483
62583
|
lines.push("");
|
|
62484
62584
|
for (const file of result.files) {
|
|
62485
|
-
const
|
|
62486
|
-
const parts =
|
|
62585
|
+
const relativePath2 = file.path.replace(/\\/g, "/");
|
|
62586
|
+
const parts = relativePath2.split("/");
|
|
62487
62587
|
const srcIdx = parts.findIndex((p) => ["Controllers", "Application", "Infrastructure", "services", "Persistence"].includes(p));
|
|
62488
62588
|
const displayPath = srcIdx >= 0 ? parts.slice(srcIdx).join("/") : parts.slice(-3).join("/");
|
|
62489
62589
|
lines.push(`### ${displayPath}`);
|
|
@@ -62601,8 +62701,581 @@ documented in smartstack-api.md.`,
|
|
|
62601
62701
|
}
|
|
62602
62702
|
});
|
|
62603
62703
|
|
|
62604
|
-
// src/mcp/tools/
|
|
62704
|
+
// src/mcp/tools/prompt-data-export-m2m.ts
|
|
62605
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";
|
|
62606
63279
|
async function handleAnalyzeHierarchyPatterns(args, config2) {
|
|
62607
63280
|
const input = AnalyzeHierarchyPatternsInputSchema.parse(args);
|
|
62608
63281
|
const projectPath = input.path || config2.smartstack.projectPath;
|
|
@@ -62681,7 +63354,7 @@ async function buildEntityGraph(domainPath, filter3) {
|
|
|
62681
63354
|
return entityMap;
|
|
62682
63355
|
}
|
|
62683
63356
|
function parseEntity(content, file, domainPath) {
|
|
62684
|
-
const fileName =
|
|
63357
|
+
const fileName = path27.basename(file, ".cs");
|
|
62685
63358
|
if (fileName.endsWith("Dto") || fileName.endsWith("Command") || fileName.endsWith("Query") || fileName.endsWith("Handler") || fileName.endsWith("Validator") || fileName.endsWith("Exception") || fileName.startsWith("I")) {
|
|
62686
63359
|
return null;
|
|
62687
63360
|
}
|
|
@@ -62725,7 +63398,7 @@ function parseEntity(content, file, domainPath) {
|
|
|
62725
63398
|
const parentEntity = foreignKeys.length > 0 ? foreignKeys[0].referencedEntity : void 0;
|
|
62726
63399
|
return {
|
|
62727
63400
|
name: entityName,
|
|
62728
|
-
file:
|
|
63401
|
+
file: path27.relative(domainPath, file),
|
|
62729
63402
|
baseClass: hasSystemEntity ? "SystemEntity" : "BaseEntity",
|
|
62730
63403
|
isTenantAware: hasITenantEntity,
|
|
62731
63404
|
isSystemEntity: hasSystemEntity,
|
|
@@ -62756,24 +63429,24 @@ function detectCircularDependencies(entityMap) {
|
|
|
62756
63429
|
const cycles = [];
|
|
62757
63430
|
for (const [name] of entityMap) {
|
|
62758
63431
|
let dfs2 = function(current) {
|
|
62759
|
-
if (
|
|
62760
|
-
const cycleStart =
|
|
62761
|
-
cycles.push([...
|
|
63432
|
+
if (path32.includes(current)) {
|
|
63433
|
+
const cycleStart = path32.indexOf(current);
|
|
63434
|
+
cycles.push([...path32.slice(cycleStart), current]);
|
|
62762
63435
|
return true;
|
|
62763
63436
|
}
|
|
62764
63437
|
if (visited.has(current)) return false;
|
|
62765
63438
|
visited.add(current);
|
|
62766
|
-
|
|
63439
|
+
path32.push(current);
|
|
62767
63440
|
const node = entityMap.get(current);
|
|
62768
63441
|
if (node?.parent) {
|
|
62769
63442
|
dfs2(node.parent);
|
|
62770
63443
|
}
|
|
62771
|
-
|
|
63444
|
+
path32.pop();
|
|
62772
63445
|
return false;
|
|
62773
63446
|
};
|
|
62774
63447
|
var dfs = dfs2;
|
|
62775
63448
|
const visited = /* @__PURE__ */ new Set();
|
|
62776
|
-
const
|
|
63449
|
+
const path32 = [];
|
|
62777
63450
|
dfs2(name);
|
|
62778
63451
|
}
|
|
62779
63452
|
const unique = /* @__PURE__ */ new Map();
|
|
@@ -65385,13 +66058,13 @@ async function getFilesToAnalyze(projectPath, scope, _structure) {
|
|
|
65385
66058
|
try {
|
|
65386
66059
|
const fullPath = filePath.startsWith(projectPath) ? filePath : `${projectPath}/${filePath}`;
|
|
65387
66060
|
const content = await readText(fullPath);
|
|
65388
|
-
const
|
|
66061
|
+
const relativePath2 = filePath.replace(projectPath, "").replace(/^[/\\]/, "");
|
|
65389
66062
|
files.push({
|
|
65390
66063
|
path: fullPath,
|
|
65391
|
-
relativePath,
|
|
66064
|
+
relativePath: relativePath2,
|
|
65392
66065
|
content,
|
|
65393
66066
|
language: detectLanguage(filePath),
|
|
65394
|
-
layer: detectLayer(
|
|
66067
|
+
layer: detectLayer(relativePath2),
|
|
65395
66068
|
lineCount: content.split("\n").length
|
|
65396
66069
|
});
|
|
65397
66070
|
} catch {
|
|
@@ -65421,8 +66094,8 @@ function detectLanguage(filePath) {
|
|
|
65421
66094
|
return "other";
|
|
65422
66095
|
}
|
|
65423
66096
|
}
|
|
65424
|
-
function detectLayer(
|
|
65425
|
-
const lower =
|
|
66097
|
+
function detectLayer(relativePath2) {
|
|
66098
|
+
const lower = relativePath2.toLowerCase();
|
|
65426
66099
|
if (lower.includes("/domain/") || lower.includes("\\domain\\")) return "domain";
|
|
65427
66100
|
if (lower.includes("/application/") || lower.includes("\\application\\")) return "application";
|
|
65428
66101
|
if (lower.includes("/infrastructure/") || lower.includes("\\infrastructure\\")) return "infrastructure";
|
|
@@ -66922,7 +67595,7 @@ var init_conventions = __esm({
|
|
|
66922
67595
|
});
|
|
66923
67596
|
|
|
66924
67597
|
// src/mcp/resources/project-info.ts
|
|
66925
|
-
import
|
|
67598
|
+
import path28 from "path";
|
|
66926
67599
|
async function getProjectInfoResource(config2) {
|
|
66927
67600
|
const projectPath = config2.smartstack.projectPath;
|
|
66928
67601
|
const projectInfo = await detectProject(projectPath);
|
|
@@ -66953,16 +67626,16 @@ async function getProjectInfoResource(config2) {
|
|
|
66953
67626
|
lines.push("```");
|
|
66954
67627
|
lines.push(`${projectInfo.name}/`);
|
|
66955
67628
|
if (structure.domain) {
|
|
66956
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67629
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.domain)}/ # Domain layer (entities)`);
|
|
66957
67630
|
}
|
|
66958
67631
|
if (structure.application) {
|
|
66959
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67632
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.application)}/ # Application layer (services)`);
|
|
66960
67633
|
}
|
|
66961
67634
|
if (structure.infrastructure) {
|
|
66962
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67635
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
|
|
66963
67636
|
}
|
|
66964
67637
|
if (structure.api) {
|
|
66965
|
-
lines.push(`\u251C\u2500\u2500 ${
|
|
67638
|
+
lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.api)}/ # API layer (controllers)`);
|
|
66966
67639
|
}
|
|
66967
67640
|
if (structure.web) {
|
|
66968
67641
|
lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
|
|
@@ -66975,9 +67648,9 @@ async function getProjectInfoResource(config2) {
|
|
|
66975
67648
|
lines.push("| Project | Path |");
|
|
66976
67649
|
lines.push("|---------|------|");
|
|
66977
67650
|
for (const csproj of projectInfo.csprojFiles) {
|
|
66978
|
-
const name =
|
|
66979
|
-
const
|
|
66980
|
-
lines.push(`| ${name} | \`${
|
|
67651
|
+
const name = path28.basename(csproj, ".csproj");
|
|
67652
|
+
const relativePath2 = path28.relative(projectPath, csproj);
|
|
67653
|
+
lines.push(`| ${name} | \`${relativePath2}\` |`);
|
|
66981
67654
|
}
|
|
66982
67655
|
lines.push("");
|
|
66983
67656
|
}
|
|
@@ -66986,10 +67659,10 @@ async function getProjectInfoResource(config2) {
|
|
|
66986
67659
|
cwd: structure.migrations,
|
|
66987
67660
|
ignore: ["*.Designer.cs"]
|
|
66988
67661
|
});
|
|
66989
|
-
const migrations = migrationFiles.map((f) =>
|
|
67662
|
+
const migrations = migrationFiles.map((f) => path28.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
|
|
66990
67663
|
lines.push("## EF Core Migrations");
|
|
66991
67664
|
lines.push("");
|
|
66992
|
-
lines.push(`**Location**: \`${
|
|
67665
|
+
lines.push(`**Location**: \`${path28.relative(projectPath, structure.migrations)}\``);
|
|
66993
67666
|
lines.push(`**Total Migrations**: ${migrations.length}`);
|
|
66994
67667
|
lines.push("");
|
|
66995
67668
|
if (migrations.length > 0) {
|
|
@@ -67024,11 +67697,11 @@ async function getProjectInfoResource(config2) {
|
|
|
67024
67697
|
lines.push("dotnet build");
|
|
67025
67698
|
lines.push("");
|
|
67026
67699
|
lines.push("# Run API");
|
|
67027
|
-
lines.push(`cd ${structure.api ?
|
|
67700
|
+
lines.push(`cd ${structure.api ? path28.relative(projectPath, structure.api) : "src/Api"}`);
|
|
67028
67701
|
lines.push("dotnet run");
|
|
67029
67702
|
lines.push("");
|
|
67030
67703
|
lines.push("# Run frontend");
|
|
67031
|
-
lines.push(`cd ${structure.web ?
|
|
67704
|
+
lines.push(`cd ${structure.web ? path28.relative(projectPath, structure.web) : "web"}`);
|
|
67032
67705
|
lines.push("npm run dev");
|
|
67033
67706
|
lines.push("");
|
|
67034
67707
|
lines.push("# Create migration");
|
|
@@ -67066,7 +67739,7 @@ var init_project_info = __esm({
|
|
|
67066
67739
|
});
|
|
67067
67740
|
|
|
67068
67741
|
// src/mcp/resources/api-endpoints.ts
|
|
67069
|
-
import
|
|
67742
|
+
import path29 from "path";
|
|
67070
67743
|
async function getApiEndpointsResource(config2, endpointFilter) {
|
|
67071
67744
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
67072
67745
|
if (!structure.api) {
|
|
@@ -67085,7 +67758,7 @@ async function getApiEndpointsResource(config2, endpointFilter) {
|
|
|
67085
67758
|
}
|
|
67086
67759
|
async function parseController(filePath, _rootPath) {
|
|
67087
67760
|
const content = await readText(filePath);
|
|
67088
|
-
const fileName =
|
|
67761
|
+
const fileName = path29.basename(filePath, ".cs");
|
|
67089
67762
|
const controllerName = fileName.replace("Controller", "");
|
|
67090
67763
|
const endpoints = [];
|
|
67091
67764
|
const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
|
|
@@ -67247,7 +67920,7 @@ var init_api_endpoints = __esm({
|
|
|
67247
67920
|
});
|
|
67248
67921
|
|
|
67249
67922
|
// src/mcp/resources/db-schema.ts
|
|
67250
|
-
import
|
|
67923
|
+
import path30 from "path";
|
|
67251
67924
|
async function getDbSchemaResource(config2, tableFilter) {
|
|
67252
67925
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
67253
67926
|
if (!structure.domain && !structure.infrastructure) {
|
|
@@ -67331,7 +68004,7 @@ async function parseEntity2(filePath, rootPath, _config) {
|
|
|
67331
68004
|
tableName,
|
|
67332
68005
|
properties,
|
|
67333
68006
|
relationships,
|
|
67334
|
-
file:
|
|
68007
|
+
file: path30.relative(rootPath, filePath)
|
|
67335
68008
|
};
|
|
67336
68009
|
}
|
|
67337
68010
|
async function enrichFromConfigurations(entities, infrastructurePath, _config) {
|
|
@@ -67492,7 +68165,7 @@ var init_db_schema = __esm({
|
|
|
67492
68165
|
});
|
|
67493
68166
|
|
|
67494
68167
|
// src/mcp/resources/entities.ts
|
|
67495
|
-
import
|
|
68168
|
+
import path31 from "path";
|
|
67496
68169
|
async function getEntitiesResource(config2, entityFilter) {
|
|
67497
68170
|
const structure = await findSmartStackStructure(config2.smartstack.projectPath);
|
|
67498
68171
|
if (!structure.domain) {
|
|
@@ -67546,7 +68219,7 @@ async function parseEntitySummary(filePath, rootPath, config2) {
|
|
|
67546
68219
|
hasSoftDelete,
|
|
67547
68220
|
hasRowVersion,
|
|
67548
68221
|
file: filePath,
|
|
67549
|
-
relativePath:
|
|
68222
|
+
relativePath: path31.relative(rootPath, filePath)
|
|
67550
68223
|
};
|
|
67551
68224
|
}
|
|
67552
68225
|
function inferTableInfo(entityName, config2) {
|
|
@@ -67764,8 +68437,9 @@ async function createServer() {
|
|
|
67764
68437
|
// Frontend Extension Tools
|
|
67765
68438
|
scaffoldFrontendExtensionTool,
|
|
67766
68439
|
analyzeExtensionPointsTool,
|
|
67767
|
-
// Data Export
|
|
68440
|
+
// Data Export Tools
|
|
67768
68441
|
scaffoldDataExportTool,
|
|
68442
|
+
promptDataExportM2mTool,
|
|
67769
68443
|
// Security & Code Quality Tools
|
|
67770
68444
|
validateSecurityTool,
|
|
67771
68445
|
analyzeCodeQualityTool,
|
|
@@ -67835,10 +68509,13 @@ async function createServer() {
|
|
|
67835
68509
|
case "analyze_extension_points":
|
|
67836
68510
|
result = await handleAnalyzeExtensionPoints(args ?? {}, config2);
|
|
67837
68511
|
break;
|
|
67838
|
-
// Data Export
|
|
68512
|
+
// Data Export Tools
|
|
67839
68513
|
case "scaffold_data_export":
|
|
67840
68514
|
result = await handleScaffoldDataExport(args ?? {}, config2);
|
|
67841
68515
|
break;
|
|
68516
|
+
case "prompt_data_export_m2m":
|
|
68517
|
+
result = await handlePromptDataExportM2m(args ?? {}, config2);
|
|
68518
|
+
break;
|
|
67842
68519
|
// Security & Code Quality Tools
|
|
67843
68520
|
case "validate_security":
|
|
67844
68521
|
result = await handleValidateSecurity(args ?? {}, config2);
|
|
@@ -67968,6 +68645,7 @@ var init_server3 = __esm({
|
|
|
67968
68645
|
init_validate_security();
|
|
67969
68646
|
init_analyze_code_quality();
|
|
67970
68647
|
init_scaffold_data_export();
|
|
68648
|
+
init_prompt_data_export_m2m();
|
|
67971
68649
|
init_analyze_hierarchy_patterns();
|
|
67972
68650
|
init_review_code();
|
|
67973
68651
|
init_conventions();
|