@atlashub/smartstack-cli 3.52.0 → 3.54.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.
@@ -471,8 +471,8 @@ var init_parseUtil = __esm({
471
471
  init_errors();
472
472
  init_en();
473
473
  makeIssue = (params) => {
474
- const { data, path: path30, errorMaps, issueData } = params;
475
- const fullPath = [...path30, ...issueData.path || []];
474
+ const { data, path: path31, errorMaps, issueData } = params;
475
+ const fullPath = [...path31, ...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, path30, key) {
789
+ constructor(parent, value, path31, key) {
790
790
  this._cachedPath = [];
791
791
  this.parent = parent;
792
792
  this.data = value;
793
- this._path = path30;
793
+ this._path = path31;
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, path30) {
4371
- if (!path30)
4370
+ function getElementAtPath(obj, path31) {
4371
+ if (!path31)
4372
4372
  return obj;
4373
- return path30.reduce((acc, key) => acc?.[key], obj);
4373
+ return path31.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(path30, issues) {
4622
+ function prefixIssues(path31, issues) {
4623
4623
  return issues.map((iss) => {
4624
4624
  var _a;
4625
4625
  (_a = iss).path ?? (_a.path = []);
4626
- iss.path.unshift(path30);
4626
+ iss.path.unshift(path31);
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(path30) {
14451
- let input = path30;
14450
+ function removeDotSegments(path31) {
14451
+ let input = path31;
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 [path30, query] = wsComponent.resourceName.split("?");
14652
- wsComponent.path = path30 && path30 !== "/" ? path30 : void 0;
14651
+ const [path31, query] = wsComponent.resourceName.split("?");
14652
+ wsComponent.path = path31 && path31 !== "/" ? path31 : 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(path30) {
23045
- if (!path30) {
23044
+ resolve(path31) {
23045
+ if (!path31) {
23046
23046
  return this;
23047
23047
  }
23048
- const rootPath = this.getRootString(path30);
23049
- const dir = path30.substring(rootPath.length);
23048
+ const rootPath = this.getRootString(path31);
23049
+ const dir = path31.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(path30) {
23802
- return win32.parse(path30).root;
23801
+ getRootString(path31) {
23802
+ return win32.parse(path31).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(path30) {
23849
- return path30.startsWith("/") ? "/" : "";
23848
+ getRootString(path31) {
23849
+ return path31.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(path30 = this.cwd) {
23939
- if (typeof path30 === "string") {
23940
- path30 = this.cwd.resolve(path30);
23938
+ depth(path31 = this.cwd) {
23939
+ if (typeof path31 === "string") {
23940
+ path31 = this.cwd.resolve(path31);
23941
23941
  }
23942
- return path30.depth();
23942
+ return path31.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(path30 = this.cwd) {
24429
+ chdir(path31 = this.cwd) {
24430
24430
  const oldCwd = this.cwd;
24431
- this.cwd = typeof path30 === "string" ? this.cwd.resolve(path30) : path30;
24431
+ this.cwd = typeof path31 === "string" ? this.cwd.resolve(path31) : path31;
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(([path30, n]) => [
24813
- path30,
24812
+ return [...this.store.entries()].map(([path31, n]) => [
24813
+ path31,
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, path30, opts) {
25028
+ constructor(patterns, path31, opts) {
25029
25029
  this.patterns = patterns;
25030
- this.path = path30;
25030
+ this.path = path31;
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(path30) {
25050
- return this.seen.has(path30) || !!this.#ignore?.ignored?.(path30);
25049
+ #ignored(path31) {
25050
+ return this.seen.has(path31) || !!this.#ignore?.ignored?.(path31);
25051
25051
  }
25052
- #childrenIgnored(path30) {
25053
- return !!this.#ignore?.childrenIgnored?.(path30);
25052
+ #childrenIgnored(path31) {
25053
+ return !!this.#ignore?.childrenIgnored?.(path31);
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, path30, opts) {
25269
- super(patterns, path30, opts);
25268
+ constructor(patterns, path31, opts) {
25269
+ super(patterns, path31, 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, path30, opts) {
25307
- super(patterns, path30, opts);
25306
+ constructor(patterns, path31, opts) {
25307
+ super(patterns, path31, opts);
25308
25308
  this.results = new Minipass({
25309
25309
  signal: this.signal,
25310
25310
  objectMode: true
@@ -25741,10 +25741,10 @@ var init_fs = __esm({
25741
25741
  init_esm_shims();
25742
25742
  init_esm8();
25743
25743
  FileSystemError = class extends Error {
25744
- constructor(message, operation, path30, cause) {
25744
+ constructor(message, operation, path31, cause) {
25745
25745
  super(message);
25746
25746
  this.operation = operation;
25747
- this.path = path30;
25747
+ this.path = path31;
25748
25748
  this.cause = cause;
25749
25749
  this.name = "FileSystemError";
25750
25750
  }
@@ -25787,7 +25787,7 @@ function tenantModeToTemplateFlags(mode) {
25787
25787
  tenantMode: mode
25788
25788
  };
25789
25789
  }
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;
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;
25791
25791
  var init_types3 = __esm({
25792
25792
  "src/mcp/types/index.ts"() {
25793
25793
  "use strict";
@@ -26067,6 +26067,18 @@ var init_types3 = __esm({
26067
26067
  target: external_exports.enum(["pages", "components", "forms", "tables", "all"]).default("all").describe("Type of components to analyze"),
26068
26068
  filter: external_exports.string().optional().describe('Filter by file name pattern (e.g., "User*")')
26069
26069
  });
26070
+ ScaffoldDataExportInputSchema = external_exports.object({
26071
+ name: external_exports.string().min(1).describe('Entity name in PascalCase (e.g., "Product", "Order")'),
26072
+ navRoute: external_exports.string().min(1).describe('NavRoute of the source entity (e.g., "business.sales.products") \u2014 used for permission derivation'),
26073
+ options: external_exports.object({
26074
+ entityProperties: external_exports.array(EntityPropertySchema).optional().describe("Properties for the export DTO"),
26075
+ rateLimitPerMinute: external_exports.number().int().positive().default(60).describe("Rate limit per minute (default: 60)"),
26076
+ maxPageSize: external_exports.number().int().positive().default(1e3).describe("Max page size (default: 1000)"),
26077
+ includeAuditFields: external_exports.boolean().default(true).describe("Include CreatedAt/UpdatedAt in DTO (default: true)"),
26078
+ includeFrontendClient: external_exports.boolean().default(false).describe("Generate TypeScript API client (default: false)"),
26079
+ dryRun: external_exports.boolean().default(false).describe("Preview without writing files (default: false)")
26080
+ }).optional()
26081
+ });
26070
26082
  AnalyzeHierarchyPatternsInputSchema = external_exports.object({
26071
26083
  path: external_exports.string().optional().describe("Project path to analyze (default: SmartStack.app path)"),
26072
26084
  entityFilter: external_exports.string().optional().describe('Filter entities by name pattern (e.g., "User*", "*Group*")'),
@@ -26394,7 +26406,8 @@ var init_config = __esm({
26394
26406
  "ref_",
26395
26407
  "loc_",
26396
26408
  "lic_",
26397
- "tenant_"
26409
+ "tenant_",
26410
+ "ext_"
26398
26411
  ],
26399
26412
  customTablePrefixes: [],
26400
26413
  scopeTypes: ["Core", "Extension", "Partner", "Community"],
@@ -29863,13 +29876,13 @@ var require_ast = __commonJS({
29863
29876
  helperExpression: function helperExpression(node) {
29864
29877
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
29865
29878
  },
29866
- scopedId: function scopedId(path30) {
29867
- return /^\.|this\b/.test(path30.original);
29879
+ scopedId: function scopedId(path31) {
29880
+ return /^\.|this\b/.test(path31.original);
29868
29881
  },
29869
29882
  // an ID is simple if it only has one part, and that part is not
29870
29883
  // `..` or `this`.
29871
- simpleId: function simpleId(path30) {
29872
- return path30.parts.length === 1 && !AST2.helpers.scopedId(path30) && !path30.depth;
29884
+ simpleId: function simpleId(path31) {
29885
+ return path31.parts.length === 1 && !AST2.helpers.scopedId(path31) && !path31.depth;
29873
29886
  }
29874
29887
  }
29875
29888
  };
@@ -30943,12 +30956,12 @@ var require_helpers2 = __commonJS({
30943
30956
  loc
30944
30957
  };
30945
30958
  }
30946
- function prepareMustache(path30, params, hash, open, strip, locInfo) {
30959
+ function prepareMustache(path31, params, hash, open, strip, locInfo) {
30947
30960
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
30948
30961
  var decorator = /\*/.test(open);
30949
30962
  return {
30950
30963
  type: decorator ? "Decorator" : "MustacheStatement",
30951
- path: path30,
30964
+ path: path31,
30952
30965
  params,
30953
30966
  hash,
30954
30967
  escaped,
@@ -31220,9 +31233,9 @@ var require_compiler = __commonJS({
31220
31233
  },
31221
31234
  DecoratorBlock: function DecoratorBlock(decorator) {
31222
31235
  var program = decorator.program && this.compileProgram(decorator.program);
31223
- var params = this.setupFullMustacheParams(decorator, program, void 0), path30 = decorator.path;
31236
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path31 = decorator.path;
31224
31237
  this.useDecorators = true;
31225
- this.opcode("registerDecorator", params.length, path30.original);
31238
+ this.opcode("registerDecorator", params.length, path31.original);
31226
31239
  },
31227
31240
  PartialStatement: function PartialStatement(partial2) {
31228
31241
  this.usePartial = true;
@@ -31286,46 +31299,46 @@ var require_compiler = __commonJS({
31286
31299
  }
31287
31300
  },
31288
31301
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
31289
- var path30 = sexpr.path, name = path30.parts[0], isBlock = program != null || inverse != null;
31290
- this.opcode("getContext", path30.depth);
31302
+ var path31 = sexpr.path, name = path31.parts[0], isBlock = program != null || inverse != null;
31303
+ this.opcode("getContext", path31.depth);
31291
31304
  this.opcode("pushProgram", program);
31292
31305
  this.opcode("pushProgram", inverse);
31293
- path30.strict = true;
31294
- this.accept(path30);
31306
+ path31.strict = true;
31307
+ this.accept(path31);
31295
31308
  this.opcode("invokeAmbiguous", name, isBlock);
31296
31309
  },
31297
31310
  simpleSexpr: function simpleSexpr(sexpr) {
31298
- var path30 = sexpr.path;
31299
- path30.strict = true;
31300
- this.accept(path30);
31311
+ var path31 = sexpr.path;
31312
+ path31.strict = true;
31313
+ this.accept(path31);
31301
31314
  this.opcode("resolvePossibleLambda");
31302
31315
  },
31303
31316
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
31304
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path30 = sexpr.path, name = path30.parts[0];
31317
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path31 = sexpr.path, name = path31.parts[0];
31305
31318
  if (this.options.knownHelpers[name]) {
31306
31319
  this.opcode("invokeKnownHelper", params.length, name);
31307
31320
  } else if (this.options.knownHelpersOnly) {
31308
31321
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
31309
31322
  } else {
31310
- path30.strict = true;
31311
- path30.falsy = true;
31312
- this.accept(path30);
31313
- this.opcode("invokeHelper", params.length, path30.original, _ast2["default"].helpers.simpleId(path30));
31323
+ path31.strict = true;
31324
+ path31.falsy = true;
31325
+ this.accept(path31);
31326
+ this.opcode("invokeHelper", params.length, path31.original, _ast2["default"].helpers.simpleId(path31));
31314
31327
  }
31315
31328
  },
31316
- PathExpression: function PathExpression(path30) {
31317
- this.addDepth(path30.depth);
31318
- this.opcode("getContext", path30.depth);
31319
- var name = path30.parts[0], scoped = _ast2["default"].helpers.scopedId(path30), blockParamId = !path30.depth && !scoped && this.blockParamIndex(name);
31329
+ PathExpression: function PathExpression(path31) {
31330
+ this.addDepth(path31.depth);
31331
+ this.opcode("getContext", path31.depth);
31332
+ var name = path31.parts[0], scoped = _ast2["default"].helpers.scopedId(path31), blockParamId = !path31.depth && !scoped && this.blockParamIndex(name);
31320
31333
  if (blockParamId) {
31321
- this.opcode("lookupBlockParam", blockParamId, path30.parts);
31334
+ this.opcode("lookupBlockParam", blockParamId, path31.parts);
31322
31335
  } else if (!name) {
31323
31336
  this.opcode("pushContext");
31324
- } else if (path30.data) {
31337
+ } else if (path31.data) {
31325
31338
  this.options.data = true;
31326
- this.opcode("lookupData", path30.depth, path30.parts, path30.strict);
31339
+ this.opcode("lookupData", path31.depth, path31.parts, path31.strict);
31327
31340
  } else {
31328
- this.opcode("lookupOnContext", path30.parts, path30.falsy, path30.strict, scoped);
31341
+ this.opcode("lookupOnContext", path31.parts, path31.falsy, path31.strict, scoped);
31329
31342
  }
31330
31343
  },
31331
31344
  StringLiteral: function StringLiteral(string3) {
@@ -31681,16 +31694,16 @@ var require_util2 = __commonJS({
31681
31694
  }
31682
31695
  exports.urlGenerate = urlGenerate;
31683
31696
  function normalize2(aPath) {
31684
- var path30 = aPath;
31697
+ var path31 = aPath;
31685
31698
  var url2 = urlParse(aPath);
31686
31699
  if (url2) {
31687
31700
  if (!url2.path) {
31688
31701
  return aPath;
31689
31702
  }
31690
- path30 = url2.path;
31703
+ path31 = url2.path;
31691
31704
  }
31692
- var isAbsolute = exports.isAbsolute(path30);
31693
- var parts = path30.split(/\/+/);
31705
+ var isAbsolute = exports.isAbsolute(path31);
31706
+ var parts = path31.split(/\/+/);
31694
31707
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
31695
31708
  part = parts[i];
31696
31709
  if (part === ".") {
@@ -31707,15 +31720,15 @@ var require_util2 = __commonJS({
31707
31720
  }
31708
31721
  }
31709
31722
  }
31710
- path30 = parts.join("/");
31711
- if (path30 === "") {
31712
- path30 = isAbsolute ? "/" : ".";
31723
+ path31 = parts.join("/");
31724
+ if (path31 === "") {
31725
+ path31 = isAbsolute ? "/" : ".";
31713
31726
  }
31714
31727
  if (url2) {
31715
- url2.path = path30;
31728
+ url2.path = path31;
31716
31729
  return urlGenerate(url2);
31717
31730
  }
31718
- return path30;
31731
+ return path31;
31719
31732
  }
31720
31733
  exports.normalize = normalize2;
31721
31734
  function join2(aRoot, aPath) {
@@ -34516,8 +34529,8 @@ var require_printer = __commonJS({
34516
34529
  return this.accept(sexpr.path) + " " + params + hash;
34517
34530
  };
34518
34531
  PrintVisitor.prototype.PathExpression = function(id) {
34519
- var path30 = id.parts.join("/");
34520
- return (id.data ? "@" : "") + "PATH:" + path30;
34532
+ var path31 = id.parts.join("/");
34533
+ return (id.data ? "@" : "") + "PATH:" + path31;
34521
34534
  };
34522
34535
  PrintVisitor.prototype.StringLiteral = function(string3) {
34523
34536
  return '"' + string3.value + '"';
@@ -46114,11 +46127,11 @@ var require_mime_types = __commonJS({
46114
46127
  }
46115
46128
  return exts[0];
46116
46129
  }
46117
- function lookup(path30) {
46118
- if (!path30 || typeof path30 !== "string") {
46130
+ function lookup(path31) {
46131
+ if (!path31 || typeof path31 !== "string") {
46119
46132
  return false;
46120
46133
  }
46121
- var extension2 = extname("x." + path30).toLowerCase().substr(1);
46134
+ var extension2 = extname("x." + path31).toLowerCase().substr(1);
46122
46135
  if (!extension2) {
46123
46136
  return false;
46124
46137
  }
@@ -47281,7 +47294,7 @@ var require_form_data = __commonJS({
47281
47294
  init_esm_shims();
47282
47295
  var CombinedStream = require_combined_stream();
47283
47296
  var util4 = __require("util");
47284
- var path30 = __require("path");
47297
+ var path31 = __require("path");
47285
47298
  var http3 = __require("http");
47286
47299
  var https2 = __require("https");
47287
47300
  var parseUrl = __require("url").parse;
@@ -47409,11 +47422,11 @@ var require_form_data = __commonJS({
47409
47422
  FormData3.prototype._getContentDisposition = function(value, options) {
47410
47423
  var filename;
47411
47424
  if (typeof options.filepath === "string") {
47412
- filename = path30.normalize(options.filepath).replace(/\\/g, "/");
47425
+ filename = path31.normalize(options.filepath).replace(/\\/g, "/");
47413
47426
  } else if (options.filename || value && (value.name || value.path)) {
47414
- filename = path30.basename(options.filename || value && (value.name || value.path));
47427
+ filename = path31.basename(options.filename || value && (value.name || value.path));
47415
47428
  } else if (value && value.readable && hasOwn(value, "httpVersion")) {
47416
- filename = path30.basename(value.client._httpMessage.path || "");
47429
+ filename = path31.basename(value.client._httpMessage.path || "");
47417
47430
  }
47418
47431
  if (filename) {
47419
47432
  return 'filename="' + filename + '"';
@@ -47612,9 +47625,9 @@ function isVisitable(thing) {
47612
47625
  function removeBrackets(key) {
47613
47626
  return utils_default.endsWith(key, "[]") ? key.slice(0, -2) : key;
47614
47627
  }
47615
- function renderKey(path30, key, dots) {
47616
- if (!path30) return key;
47617
- return path30.concat(key).map(function each(token, i) {
47628
+ function renderKey(path31, key, dots) {
47629
+ if (!path31) return key;
47630
+ return path31.concat(key).map(function each(token, i) {
47618
47631
  token = removeBrackets(token);
47619
47632
  return !dots && i ? "[" + token + "]" : token;
47620
47633
  }).join(dots ? "." : "");
@@ -47659,9 +47672,9 @@ function toFormData(obj, formData, options) {
47659
47672
  }
47660
47673
  return value;
47661
47674
  }
47662
- function defaultVisitor(value, key, path30) {
47675
+ function defaultVisitor(value, key, path31) {
47663
47676
  let arr = value;
47664
- if (value && !path30 && typeof value === "object") {
47677
+ if (value && !path31 && typeof value === "object") {
47665
47678
  if (utils_default.endsWith(key, "{}")) {
47666
47679
  key = metaTokens ? key : key.slice(0, -2);
47667
47680
  value = JSON.stringify(value);
@@ -47680,7 +47693,7 @@ function toFormData(obj, formData, options) {
47680
47693
  if (isVisitable(value)) {
47681
47694
  return true;
47682
47695
  }
47683
- formData.append(renderKey(path30, key, dots), convertValue(value));
47696
+ formData.append(renderKey(path31, key, dots), convertValue(value));
47684
47697
  return false;
47685
47698
  }
47686
47699
  const stack = [];
@@ -47689,10 +47702,10 @@ function toFormData(obj, formData, options) {
47689
47702
  convertValue,
47690
47703
  isVisitable
47691
47704
  });
47692
- function build(value, path30) {
47705
+ function build(value, path31) {
47693
47706
  if (utils_default.isUndefined(value)) return;
47694
47707
  if (stack.indexOf(value) !== -1) {
47695
- throw Error("Circular reference detected in " + path30.join("."));
47708
+ throw Error("Circular reference detected in " + path31.join("."));
47696
47709
  }
47697
47710
  stack.push(value);
47698
47711
  utils_default.forEach(value, function each(el, key) {
@@ -47700,11 +47713,11 @@ function toFormData(obj, formData, options) {
47700
47713
  formData,
47701
47714
  el,
47702
47715
  utils_default.isString(key) ? key.trim() : key,
47703
- path30,
47716
+ path31,
47704
47717
  exposedHelpers
47705
47718
  );
47706
47719
  if (result === true) {
47707
- build(el, path30 ? path30.concat(key) : [key]);
47720
+ build(el, path31 ? path31.concat(key) : [key]);
47708
47721
  }
47709
47722
  });
47710
47723
  stack.pop();
@@ -47990,7 +48003,7 @@ var init_platform = __esm({
47990
48003
  // node_modules/axios/lib/helpers/toURLEncodedForm.js
47991
48004
  function toURLEncodedForm(data, options) {
47992
48005
  return toFormData_default(data, new platform_default.classes.URLSearchParams(), {
47993
- visitor: function(value, key, path30, helpers) {
48006
+ visitor: function(value, key, path31, helpers) {
47994
48007
  if (platform_default.isNode && utils_default.isBuffer(value)) {
47995
48008
  this.append(key, value.toString("base64"));
47996
48009
  return false;
@@ -48029,11 +48042,11 @@ function arrayToObject(arr) {
48029
48042
  return obj;
48030
48043
  }
48031
48044
  function formDataToJSON(formData) {
48032
- function buildPath(path30, value, target, index) {
48033
- let name = path30[index++];
48045
+ function buildPath(path31, value, target, index) {
48046
+ let name = path31[index++];
48034
48047
  if (name === "__proto__") return true;
48035
48048
  const isNumericKey = Number.isFinite(+name);
48036
- const isLast = index >= path30.length;
48049
+ const isLast = index >= path31.length;
48037
48050
  name = !name && utils_default.isArray(target) ? target.length : name;
48038
48051
  if (isLast) {
48039
48052
  if (utils_default.hasOwnProp(target, name)) {
@@ -48046,7 +48059,7 @@ function formDataToJSON(formData) {
48046
48059
  if (!target[name] || !utils_default.isObject(target[name])) {
48047
48060
  target[name] = [];
48048
48061
  }
48049
- const result = buildPath(path30, value, target[name], index);
48062
+ const result = buildPath(path31, value, target[name], index);
48050
48063
  if (result && utils_default.isArray(target[name])) {
48051
48064
  target[name] = arrayToObject(target[name]);
48052
48065
  }
@@ -50946,9 +50959,9 @@ var init_http = __esm({
50946
50959
  auth = urlUsername + ":" + urlPassword;
50947
50960
  }
50948
50961
  auth && headers.delete("authorization");
50949
- let path30;
50962
+ let path31;
50950
50963
  try {
50951
- path30 = buildURL(
50964
+ path31 = buildURL(
50952
50965
  parsed.pathname + parsed.search,
50953
50966
  config2.params,
50954
50967
  config2.paramsSerializer
@@ -50966,7 +50979,7 @@ var init_http = __esm({
50966
50979
  false
50967
50980
  );
50968
50981
  const options = {
50969
- path: path30,
50982
+ path: path31,
50970
50983
  method,
50971
50984
  headers: headers.toJSON(),
50972
50985
  agents: { http: config2.httpAgent, https: config2.httpsAgent },
@@ -51219,14 +51232,14 @@ var init_cookies = __esm({
51219
51232
  cookies_default = platform_default.hasStandardBrowserEnv ? (
51220
51233
  // Standard browser envs support document.cookie
51221
51234
  {
51222
- write(name, value, expires, path30, domain, secure, sameSite) {
51235
+ write(name, value, expires, path31, domain, secure, sameSite) {
51223
51236
  if (typeof document === "undefined") return;
51224
51237
  const cookie = [`${name}=${encodeURIComponent(value)}`];
51225
51238
  if (utils_default.isNumber(expires)) {
51226
51239
  cookie.push(`expires=${new Date(expires).toUTCString()}`);
51227
51240
  }
51228
- if (utils_default.isString(path30)) {
51229
- cookie.push(`path=${path30}`);
51241
+ if (utils_default.isString(path31)) {
51242
+ cookie.push(`path=${path31}`);
51230
51243
  }
51231
51244
  if (utils_default.isString(domain)) {
51232
51245
  cookie.push(`domain=${domain}`);
@@ -62091,8 +62104,505 @@ var init_analyze_code_quality = __esm({
62091
62104
  }
62092
62105
  });
62093
62106
 
62094
- // src/mcp/tools/analyze-hierarchy-patterns.ts
62107
+ // src/mcp/tools/scaffold-data-export.ts
62095
62108
  import path25 from "path";
62109
+ async function handleScaffoldDataExport(args, config2) {
62110
+ const input = ScaffoldDataExportInputSchema.parse(args);
62111
+ logger.info("Scaffolding Data Export", { name: input.name, navRoute: input.navRoute });
62112
+ const result = await scaffoldDataExport(input, config2);
62113
+ return formatResult9(result, input);
62114
+ }
62115
+ async function scaffoldDataExport(input, config2) {
62116
+ const result = {
62117
+ success: true,
62118
+ files: [],
62119
+ instructions: []
62120
+ };
62121
+ const { name, navRoute, options } = input;
62122
+ const dryRun = options?.dryRun ?? false;
62123
+ const includeAuditFields = options?.includeAuditFields ?? true;
62124
+ const includeFrontendClient = options?.includeFrontendClient ?? false;
62125
+ const rateLimitPerMinute = options?.rateLimitPerMinute ?? 60;
62126
+ const maxPageSize = options?.maxPageSize ?? 1e3;
62127
+ const entityProperties = options?.entityProperties ?? [];
62128
+ const nameLower = name.charAt(0).toLowerCase() + name.slice(1);
62129
+ const namePlural = pluralize(nameLower);
62130
+ const segments = navRoute.split(".");
62131
+ const permissionPath = segments.join(".");
62132
+ const baseNamespace = config2.conventions.namespaces.api.replace(".Api", "");
62133
+ const appSegment = segments.length >= 2 ? toPascalCase2(segments[1]) : "Default";
62134
+ const moduleSegment = segments.length >= 3 ? toPascalCase2(segments[2]) : name;
62135
+ const projectRoot = config2.smartstack.projectPath;
62136
+ const structure = await findSmartStackStructure(projectRoot);
62137
+ const apiPath = structure.api || path25.join(projectRoot, "Api");
62138
+ const applicationPath = structure.application || path25.join(projectRoot, "Application");
62139
+ const infrastructurePath = structure.infrastructure || path25.join(projectRoot, "Infrastructure");
62140
+ const webPath = structure.web || path25.join(projectRoot, "web");
62141
+ const controllerContent = generateExportController(name, namePlural, permissionPath, baseNamespace, maxPageSize);
62142
+ const controllerFile = path25.join(apiPath, "Controllers", "DataExport", "v1", `Export${name}Controller.cs`);
62143
+ if (!dryRun) {
62144
+ await ensureDirectory(path25.dirname(controllerFile));
62145
+ await writeText(controllerFile, controllerContent);
62146
+ }
62147
+ result.files.push({ path: controllerFile, content: controllerContent, type: "created" });
62148
+ const interfaceContent = generateExportServiceInterface(name, baseNamespace);
62149
+ const interfaceFile = path25.join(applicationPath, "Common", "Interfaces", `IExport${name}Service.cs`);
62150
+ if (!dryRun) {
62151
+ await ensureDirectory(path25.dirname(interfaceFile));
62152
+ await writeText(interfaceFile, interfaceContent);
62153
+ }
62154
+ result.files.push({ path: interfaceFile, content: interfaceContent, type: "created" });
62155
+ const serviceContent = generateExportService(name, namePlural, baseNamespace, entityProperties, includeAuditFields);
62156
+ const serviceFile = path25.join(infrastructurePath, "Services", "DataExport", `Export${name}Service.cs`);
62157
+ if (!dryRun) {
62158
+ await ensureDirectory(path25.dirname(serviceFile));
62159
+ await writeText(serviceFile, serviceContent);
62160
+ }
62161
+ result.files.push({ path: serviceFile, content: serviceContent, type: "created" });
62162
+ const dtoContent = generateExportDto(name, baseNamespace, entityProperties, includeAuditFields);
62163
+ const dtoFile = path25.join(applicationPath, "DataExport", "Dtos", `Export${name}Dto.cs`);
62164
+ if (!dryRun) {
62165
+ await ensureDirectory(path25.dirname(dtoFile));
62166
+ await writeText(dtoFile, dtoContent);
62167
+ }
62168
+ result.files.push({ path: dtoFile, content: dtoContent, type: "created" });
62169
+ const seedContent = generateSeedDataFragment(name, namePlural, permissionPath, appSegment, moduleSegment, rateLimitPerMinute, maxPageSize);
62170
+ const seedFile = path25.join(infrastructurePath, "Persistence", "Seeding", "Data", "DataExport", `Export${name}EndpointSeedData.cs`);
62171
+ if (!dryRun) {
62172
+ await ensureDirectory(path25.dirname(seedFile));
62173
+ await writeText(seedFile, seedContent);
62174
+ }
62175
+ result.files.push({ path: seedFile, content: seedContent, type: "created" });
62176
+ if (includeFrontendClient) {
62177
+ const clientContent = generateFrontendClient(name, nameLower, namePlural);
62178
+ const clientFile = path25.join(webPath, "src", "services", "api", `export${name}.ts`);
62179
+ if (!dryRun) {
62180
+ await ensureDirectory(path25.dirname(clientFile));
62181
+ await writeText(clientFile, clientContent);
62182
+ }
62183
+ result.files.push({ path: clientFile, content: clientContent, type: "created" });
62184
+ }
62185
+ result.instructions.push(`Register IExport${name}Service in DI container (Program.cs or DependencyInjection.cs)`);
62186
+ result.instructions.push(`Add the export permission "${permissionPath}.export" to PermissionConfiguration.cs`);
62187
+ result.instructions.push(`Add the seed data to DataExportEndpointConfiguration or call the fragment from your seeder`);
62188
+ result.instructions.push(`Create an EF Core migration to apply the new seed data`);
62189
+ if (includeFrontendClient) {
62190
+ result.instructions.push(`Import the client: import { export${name}Api } from './services/api/export${name}';`);
62191
+ }
62192
+ return result;
62193
+ }
62194
+ function generateExportController(name, namePlural, permissionPath, baseNamespace, maxPageSize) {
62195
+ return `using Microsoft.AspNetCore.Authorization;
62196
+ using Microsoft.AspNetCore.Mvc;
62197
+ using Microsoft.AspNetCore.RateLimiting;
62198
+ using ${baseNamespace}.Application.Common.Models;
62199
+ using ${baseNamespace}.Application.DataExport.Dtos;
62200
+ using ${baseNamespace}.Application.Common.Interfaces;
62201
+ using ${baseNamespace}.Api.Authorization;
62202
+
62203
+ namespace ${baseNamespace}.Api.Controllers.DataExport.v1;
62204
+
62205
+ /// <summary>
62206
+ /// Data Export endpoint for ${name} entities.
62207
+ /// Security: JWT authentication + RequirePermission + Rate limiting + DataExportAccessMiddleware
62208
+ /// </summary>
62209
+ [ApiController]
62210
+ [Route("api/v1/export")]
62211
+ [Authorize]
62212
+ [EnableRateLimiting("ExternalApp")]
62213
+ public class Export${name}Controller : ControllerBase
62214
+ {
62215
+ private readonly IExport${name}Service _exportService;
62216
+
62217
+ public Export${name}Controller(IExport${name}Service exportService)
62218
+ {
62219
+ _exportService = exportService;
62220
+ }
62221
+
62222
+ /// <summary>
62223
+ /// Export ${name} data with pagination and optional modified-since filter.
62224
+ /// </summary>
62225
+ [HttpGet("${namePlural}")]
62226
+ [RequirePermission(Permissions.${permissionPath.split(".").map((s) => toPascalCase2(s)).join(".")}.Export)]
62227
+ public async Task<ActionResult<PaginatedResult<Export${name}Dto>>> Export(
62228
+ [FromQuery] int page = 1,
62229
+ [FromQuery] int pageSize = 100,
62230
+ [FromQuery] DateTime? modifiedSince = null,
62231
+ CancellationToken ct = default)
62232
+ {
62233
+ pageSize = Math.Clamp(pageSize, 1, ${maxPageSize});
62234
+ page = Math.Max(1, page);
62235
+
62236
+ var result = await _exportService.ExportAsync(page, pageSize, modifiedSince, ct);
62237
+ return Ok(result);
62238
+ }
62239
+ }
62240
+ `;
62241
+ }
62242
+ function generateExportServiceInterface(name, baseNamespace) {
62243
+ return `using ${baseNamespace}.Application.Common.Models;
62244
+ using ${baseNamespace}.Application.DataExport.Dtos;
62245
+
62246
+ namespace ${baseNamespace}.Application.Common.Interfaces;
62247
+
62248
+ /// <summary>
62249
+ /// Service interface for ${name} data export.
62250
+ /// </summary>
62251
+ public interface IExport${name}Service
62252
+ {
62253
+ /// <summary>
62254
+ /// Export ${name} data with pagination and optional modified-since filter.
62255
+ /// </summary>
62256
+ Task<PaginatedResult<Export${name}Dto>> ExportAsync(
62257
+ int page = 1,
62258
+ int pageSize = 100,
62259
+ DateTime? modifiedSince = null,
62260
+ CancellationToken ct = default);
62261
+ }
62262
+ `;
62263
+ }
62264
+ function generateExportService(name, namePlural, baseNamespace, entityProperties, includeAuditFields) {
62265
+ const projectionFields = [" Id = x.Id"];
62266
+ for (const prop of entityProperties) {
62267
+ projectionFields.push(` ${prop.name} = x.${prop.name}`);
62268
+ }
62269
+ if (includeAuditFields) {
62270
+ projectionFields.push(" CreatedAt = x.CreatedAt");
62271
+ projectionFields.push(" UpdatedAt = x.UpdatedAt");
62272
+ }
62273
+ const projectionBlock = projectionFields.join(",\n");
62274
+ return `using Microsoft.EntityFrameworkCore;
62275
+ using Microsoft.Extensions.Logging;
62276
+ using ${baseNamespace}.Application.Common.Interfaces;
62277
+ using ${baseNamespace}.Application.Common.Models;
62278
+ using ${baseNamespace}.Application.DataExport.Dtos;
62279
+ using ${baseNamespace}.Infrastructure.Persistence;
62280
+
62281
+ namespace ${baseNamespace}.Infrastructure.Services.DataExport;
62282
+
62283
+ /// <summary>
62284
+ /// Data export service for ${name} entities.
62285
+ /// Enforces tenant isolation via ICurrentTenantService.
62286
+ /// </summary>
62287
+ public class Export${name}Service : IExport${name}Service
62288
+ {
62289
+ private readonly ApplicationDbContext _db;
62290
+ private readonly ICurrentTenantService _currentTenant;
62291
+ private readonly ICurrentUserService _currentUser;
62292
+ private readonly ILogger<Export${name}Service> _logger;
62293
+
62294
+ public Export${name}Service(
62295
+ ApplicationDbContext db,
62296
+ ICurrentTenantService currentTenant,
62297
+ ICurrentUserService currentUser,
62298
+ ILogger<Export${name}Service> logger)
62299
+ {
62300
+ _db = db;
62301
+ _currentTenant = currentTenant;
62302
+ _currentUser = currentUser;
62303
+ _logger = logger;
62304
+ }
62305
+
62306
+ public async Task<PaginatedResult<Export${name}Dto>> ExportAsync(
62307
+ int page = 1,
62308
+ int pageSize = 100,
62309
+ DateTime? modifiedSince = null,
62310
+ CancellationToken ct = default)
62311
+ {
62312
+ var tenantId = _currentTenant.TenantId
62313
+ ?? throw new UnauthorizedAccessException("Tenant context is required");
62314
+
62315
+ _logger.LogInformation("Data export requested for ${name} by {UserId}, tenant {TenantId}, page {Page}",
62316
+ _currentUser.UserId, tenantId, page);
62317
+
62318
+ var query = _db.${toPascalCase2(namePlural)}
62319
+ .Where(x => x.TenantId == tenantId)
62320
+ .AsNoTracking();
62321
+
62322
+ if (modifiedSince.HasValue)
62323
+ {
62324
+ query = query.Where(x => x.UpdatedAt >= modifiedSince.Value
62325
+ || x.CreatedAt >= modifiedSince.Value);
62326
+ }
62327
+
62328
+ var totalCount = await query.CountAsync(ct);
62329
+
62330
+ var items = await query
62331
+ .OrderBy(x => x.Id)
62332
+ .Skip((page - 1) * pageSize)
62333
+ .Take(pageSize)
62334
+ .Select(x => new Export${name}Dto
62335
+ {
62336
+ ${projectionBlock}
62337
+ })
62338
+ .ToListAsync(ct);
62339
+
62340
+ return new PaginatedResult<Export${name}Dto>(items, totalCount, page, pageSize);
62341
+ }
62342
+ }
62343
+ `;
62344
+ }
62345
+ function generateExportDto(name, baseNamespace, entityProperties, includeAuditFields) {
62346
+ const properties = [" public Guid Id { get; init; }"];
62347
+ for (const prop of entityProperties) {
62348
+ const nullableSuffix = prop.required ? "" : "?";
62349
+ properties.push(` public ${prop.type}${nullableSuffix} ${prop.name} { get; init; }`);
62350
+ }
62351
+ if (includeAuditFields) {
62352
+ properties.push(" public DateTime CreatedAt { get; init; }");
62353
+ properties.push(" public DateTime? UpdatedAt { get; init; }");
62354
+ }
62355
+ return `namespace ${baseNamespace}.Application.DataExport.Dtos;
62356
+
62357
+ /// <summary>
62358
+ /// Flat DTO for ${name} data export (external consumption).
62359
+ /// Auto-generated by SmartStack MCP \u2014 customize as needed.
62360
+ /// </summary>
62361
+ public class Export${name}Dto
62362
+ {
62363
+ ${properties.join("\n")}
62364
+ }
62365
+ `;
62366
+ }
62367
+ function generateSeedDataFragment(name, namePlural, permissionPath, appSegment, moduleSegment, rateLimitPerMinute, maxPageSize) {
62368
+ return `// ============================================================================
62369
+ // Data Export Endpoint Seed Data \u2014 ${name}
62370
+ // Auto-generated by SmartStack MCP \u2014 add to DataExportEndpointConfiguration.cs
62371
+ // ============================================================================
62372
+ //
62373
+ // Copy the HasData() block below into your DataExportEndpointConfiguration
62374
+ // or IClientSeedDataProvider to register this export endpoint.
62375
+ //
62376
+ // builder.HasData(new
62377
+ // {
62378
+ // Id = DeterministicGuid.Create("data-export:${namePlural}"),
62379
+ // Code = "${namePlural}",
62380
+ // Name = "${name} Export",
62381
+ // RouteTemplate = "/api/v1/export/${namePlural}",
62382
+ // RequiredPermission = "${permissionPath}.export",
62383
+ // EntityType = "${name}",
62384
+ // IsActive = true,
62385
+ // DefaultRateLimitPerMinute = ${rateLimitPerMinute},
62386
+ // DefaultMaxPageSize = ${maxPageSize},
62387
+ // CreatedAt = seedDate
62388
+ // });
62389
+ //
62390
+ // IMPORTANT: NavigationApplicationId and NavigationModuleId must reference
62391
+ // actual seed data IDs from NavigationApplicationSeedData and
62392
+ // NavigationModuleSeedData respectively.
62393
+ //
62394
+ // Permission to add to PermissionConfiguration.cs:
62395
+ // "${permissionPath}.export" \u2014 Export ${name} data via M2M API
62396
+ `;
62397
+ }
62398
+ function generateFrontendClient(name, nameLower, namePlural) {
62399
+ return `/**
62400
+ * ${name} Data Export API Client
62401
+ *
62402
+ * Auto-generated by SmartStack MCP - DO NOT EDIT MANUALLY
62403
+ * Endpoint: /api/v1/export/${namePlural}
62404
+ */
62405
+
62406
+ import { apiClient } from '../lib/apiClient';
62407
+
62408
+ export interface Export${name}Dto {
62409
+ id: string;
62410
+ [key: string]: unknown;
62411
+ createdAt: string;
62412
+ updatedAt?: string;
62413
+ }
62414
+
62415
+ export interface ExportParams {
62416
+ page?: number;
62417
+ pageSize?: number;
62418
+ modifiedSince?: string;
62419
+ }
62420
+
62421
+ export interface PaginatedExportResult<T> {
62422
+ items: T[];
62423
+ totalCount: number;
62424
+ page: number;
62425
+ pageSize: number;
62426
+ totalPages: number;
62427
+ hasPreviousPage: boolean;
62428
+ hasNextPage: boolean;
62429
+ }
62430
+
62431
+ const EXPORT_ENDPOINT = '/api/v1/export/${namePlural}';
62432
+
62433
+ export const export${name}Api = {
62434
+ /**
62435
+ * Fetch paginated export data
62436
+ */
62437
+ async getPage(params?: ExportParams): Promise<PaginatedExportResult<Export${name}Dto>> {
62438
+ const response = await apiClient.get<PaginatedExportResult<Export${name}Dto>>(EXPORT_ENDPOINT, { params });
62439
+ return response.data;
62440
+ },
62441
+
62442
+ /**
62443
+ * Download all pages as a blob (CSV/JSON)
62444
+ */
62445
+ async downloadAll(format: 'json' | 'csv' = 'json'): Promise<Blob> {
62446
+ const response = await apiClient.get(EXPORT_ENDPOINT, {
62447
+ params: { pageSize: 1000, format },
62448
+ responseType: 'blob',
62449
+ });
62450
+ return response.data;
62451
+ },
62452
+ };
62453
+
62454
+ export default export${name}Api;
62455
+ `;
62456
+ }
62457
+ function formatResult9(result, input) {
62458
+ const lines = [];
62459
+ const namePlural = pluralize(input.name.charAt(0).toLowerCase() + input.name.slice(1));
62460
+ lines.push(`# Scaffold Data Export: ${input.name}`);
62461
+ lines.push("");
62462
+ if (input.options?.dryRun) {
62463
+ lines.push("> **DRY RUN** - No files were written");
62464
+ lines.push("");
62465
+ }
62466
+ lines.push("## Export Endpoint");
62467
+ lines.push("");
62468
+ lines.push(`- **Entity**: \`${input.name}\``);
62469
+ lines.push(`- **NavRoute**: \`${input.navRoute}\``);
62470
+ lines.push(`- **Permission**: \`${input.navRoute}.export\``);
62471
+ lines.push(`- **Endpoint**: \`GET /api/v1/export/${namePlural}\``);
62472
+ lines.push(`- **Rate Limit**: ${input.options?.rateLimitPerMinute ?? 60}/min`);
62473
+ lines.push(`- **Max Page Size**: ${input.options?.maxPageSize ?? 1e3}`);
62474
+ lines.push("");
62475
+ lines.push("## Security Layers");
62476
+ lines.push("");
62477
+ lines.push("1. **Authentication** \u2014 `[Authorize]` + JWT assertion from ExternalApplicationAuthService");
62478
+ lines.push(`2. **Authorization** \u2014 \`[RequirePermission("${input.navRoute}.export")]\``);
62479
+ lines.push("3. **Access Control** \u2014 DataExportAccessMiddleware verifies per-app endpoint access");
62480
+ lines.push(`4. **Rate Limiting** \u2014 \`[EnableRateLimiting("ExternalApp")]\` (${input.options?.rateLimitPerMinute ?? 60}/min)`);
62481
+ lines.push("");
62482
+ lines.push("## Generated Files");
62483
+ lines.push("");
62484
+ for (const file of result.files) {
62485
+ const relativePath = file.path.replace(/\\/g, "/");
62486
+ const parts = relativePath.split("/");
62487
+ const srcIdx = parts.findIndex((p) => ["Controllers", "Application", "Infrastructure", "services", "Persistence"].includes(p));
62488
+ const displayPath = srcIdx >= 0 ? parts.slice(srcIdx).join("/") : parts.slice(-3).join("/");
62489
+ lines.push(`### ${displayPath}`);
62490
+ lines.push("");
62491
+ const ext2 = file.path.endsWith(".ts") ? "typescript" : "csharp";
62492
+ const truncated = file.content.length > 2e3 ? file.content.substring(0, 2e3) + "\n// ... (truncated)" : file.content;
62493
+ lines.push(`\`\`\`${ext2}`);
62494
+ lines.push(truncated);
62495
+ lines.push("```");
62496
+ lines.push("");
62497
+ }
62498
+ lines.push("## Next Steps");
62499
+ lines.push("");
62500
+ for (const instruction of result.instructions) {
62501
+ lines.push(`- ${instruction}`);
62502
+ }
62503
+ return lines.join("\n");
62504
+ }
62505
+ function toPascalCase2(s) {
62506
+ return s.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
62507
+ }
62508
+ function pluralize(s) {
62509
+ if (s.endsWith("y") && !["ay", "ey", "oy", "uy"].some((v) => s.endsWith(v))) {
62510
+ return s.slice(0, -1) + "ies";
62511
+ }
62512
+ if (s.endsWith("s") || s.endsWith("x") || s.endsWith("z") || s.endsWith("ch") || s.endsWith("sh")) {
62513
+ return s + "es";
62514
+ }
62515
+ return s + "s";
62516
+ }
62517
+ var scaffoldDataExportTool;
62518
+ var init_scaffold_data_export = __esm({
62519
+ "src/mcp/tools/scaffold-data-export.ts"() {
62520
+ "use strict";
62521
+ init_esm_shims();
62522
+ init_types3();
62523
+ init_logger();
62524
+ init_detector();
62525
+ init_fs();
62526
+ scaffoldDataExportTool = {
62527
+ name: "scaffold_data_export",
62528
+ description: `Generate Data Export API for a SmartStack entity.
62529
+
62530
+ Creates all code needed to expose an entity via the M2M Data Export API with 3-layer security:
62531
+ - Export Controller (versioned, rate-limited, permission-protected)
62532
+ - Export Service (interface + implementation with tenant isolation)
62533
+ - Export DTO (flat projection for external consumption)
62534
+ - Seed data fragment (DataExportEndpointConfiguration HasData)
62535
+ - Frontend client (optional, TypeScript blob download)
62536
+
62537
+ Example:
62538
+ scaffold_data_export name="Product" navRoute="business.sales.products"
62539
+
62540
+ The generated code follows the External Application & Data Export pattern
62541
+ documented in smartstack-api.md.`,
62542
+ inputSchema: {
62543
+ type: "object",
62544
+ properties: {
62545
+ name: {
62546
+ type: "string",
62547
+ description: 'Entity name in PascalCase (e.g., "Product", "Order")'
62548
+ },
62549
+ navRoute: {
62550
+ type: "string",
62551
+ description: 'NavRoute of the source entity (e.g., "business.sales.products") \u2014 used for permission derivation'
62552
+ },
62553
+ options: {
62554
+ type: "object",
62555
+ properties: {
62556
+ entityProperties: {
62557
+ type: "array",
62558
+ items: {
62559
+ type: "object",
62560
+ properties: {
62561
+ name: { type: "string" },
62562
+ type: { type: "string" },
62563
+ required: { type: "boolean" },
62564
+ maxLength: { type: "number" }
62565
+ },
62566
+ required: ["name", "type"]
62567
+ },
62568
+ description: "Properties for the export DTO"
62569
+ },
62570
+ rateLimitPerMinute: {
62571
+ type: "number",
62572
+ default: 60,
62573
+ description: "Rate limit per minute (default: 60)"
62574
+ },
62575
+ maxPageSize: {
62576
+ type: "number",
62577
+ default: 1e3,
62578
+ description: "Max page size (default: 1000)"
62579
+ },
62580
+ includeAuditFields: {
62581
+ type: "boolean",
62582
+ default: true,
62583
+ description: "Include CreatedAt/UpdatedAt in DTO (default: true)"
62584
+ },
62585
+ includeFrontendClient: {
62586
+ type: "boolean",
62587
+ default: false,
62588
+ description: "Generate TypeScript API client (default: false)"
62589
+ },
62590
+ dryRun: {
62591
+ type: "boolean",
62592
+ default: false,
62593
+ description: "Preview without writing files (default: false)"
62594
+ }
62595
+ }
62596
+ }
62597
+ },
62598
+ required: ["name", "navRoute"]
62599
+ }
62600
+ };
62601
+ }
62602
+ });
62603
+
62604
+ // src/mcp/tools/analyze-hierarchy-patterns.ts
62605
+ import path26 from "path";
62096
62606
  async function handleAnalyzeHierarchyPatterns(args, config2) {
62097
62607
  const input = AnalyzeHierarchyPatternsInputSchema.parse(args);
62098
62608
  const projectPath = input.path || config2.smartstack.projectPath;
@@ -62115,7 +62625,7 @@ async function handleAnalyzeHierarchyPatterns(args, config2) {
62115
62625
  recommendations,
62116
62626
  circularDependencies: detectCircularDependencies(entityGraph)
62117
62627
  };
62118
- return formatResult9(result, outputFormat, structure.root);
62628
+ return formatResult10(result, outputFormat, structure.root);
62119
62629
  }
62120
62630
  async function buildEntityGraph(domainPath, filter3) {
62121
62631
  const entityFiles = await findFiles("**/*.cs", { cwd: domainPath });
@@ -62171,7 +62681,7 @@ async function buildEntityGraph(domainPath, filter3) {
62171
62681
  return entityMap;
62172
62682
  }
62173
62683
  function parseEntity(content, file, domainPath) {
62174
- const fileName = path25.basename(file, ".cs");
62684
+ const fileName = path26.basename(file, ".cs");
62175
62685
  if (fileName.endsWith("Dto") || fileName.endsWith("Command") || fileName.endsWith("Query") || fileName.endsWith("Handler") || fileName.endsWith("Validator") || fileName.endsWith("Exception") || fileName.startsWith("I")) {
62176
62686
  return null;
62177
62687
  }
@@ -62215,7 +62725,7 @@ function parseEntity(content, file, domainPath) {
62215
62725
  const parentEntity = foreignKeys.length > 0 ? foreignKeys[0].referencedEntity : void 0;
62216
62726
  return {
62217
62727
  name: entityName,
62218
- file: path25.relative(domainPath, file),
62728
+ file: path26.relative(domainPath, file),
62219
62729
  baseClass: hasSystemEntity ? "SystemEntity" : "BaseEntity",
62220
62730
  isTenantAware: hasITenantEntity,
62221
62731
  isSystemEntity: hasSystemEntity,
@@ -62246,24 +62756,24 @@ function detectCircularDependencies(entityMap) {
62246
62756
  const cycles = [];
62247
62757
  for (const [name] of entityMap) {
62248
62758
  let dfs2 = function(current) {
62249
- if (path30.includes(current)) {
62250
- const cycleStart = path30.indexOf(current);
62251
- cycles.push([...path30.slice(cycleStart), current]);
62759
+ if (path31.includes(current)) {
62760
+ const cycleStart = path31.indexOf(current);
62761
+ cycles.push([...path31.slice(cycleStart), current]);
62252
62762
  return true;
62253
62763
  }
62254
62764
  if (visited.has(current)) return false;
62255
62765
  visited.add(current);
62256
- path30.push(current);
62766
+ path31.push(current);
62257
62767
  const node = entityMap.get(current);
62258
62768
  if (node?.parent) {
62259
62769
  dfs2(node.parent);
62260
62770
  }
62261
- path30.pop();
62771
+ path31.pop();
62262
62772
  return false;
62263
62773
  };
62264
62774
  var dfs = dfs2;
62265
62775
  const visited = /* @__PURE__ */ new Set();
62266
- const path30 = [];
62776
+ const path31 = [];
62267
62777
  dfs2(name);
62268
62778
  }
62269
62779
  const unique = /* @__PURE__ */ new Map();
@@ -62470,7 +62980,7 @@ function getImplementationSteps(pattern, node) {
62470
62980
  return [];
62471
62981
  }
62472
62982
  }
62473
- function formatResult9(result, format, _rootPath) {
62983
+ function formatResult10(result, format, _rootPath) {
62474
62984
  if (format === "json") {
62475
62985
  return JSON.stringify(result, null, 2);
62476
62986
  }
@@ -66412,7 +66922,7 @@ var init_conventions = __esm({
66412
66922
  });
66413
66923
 
66414
66924
  // src/mcp/resources/project-info.ts
66415
- import path26 from "path";
66925
+ import path27 from "path";
66416
66926
  async function getProjectInfoResource(config2) {
66417
66927
  const projectPath = config2.smartstack.projectPath;
66418
66928
  const projectInfo = await detectProject(projectPath);
@@ -66443,16 +66953,16 @@ async function getProjectInfoResource(config2) {
66443
66953
  lines.push("```");
66444
66954
  lines.push(`${projectInfo.name}/`);
66445
66955
  if (structure.domain) {
66446
- lines.push(`\u251C\u2500\u2500 ${path26.basename(structure.domain)}/ # Domain layer (entities)`);
66956
+ lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.domain)}/ # Domain layer (entities)`);
66447
66957
  }
66448
66958
  if (structure.application) {
66449
- lines.push(`\u251C\u2500\u2500 ${path26.basename(structure.application)}/ # Application layer (services)`);
66959
+ lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.application)}/ # Application layer (services)`);
66450
66960
  }
66451
66961
  if (structure.infrastructure) {
66452
- lines.push(`\u251C\u2500\u2500 ${path26.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
66962
+ lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
66453
66963
  }
66454
66964
  if (structure.api) {
66455
- lines.push(`\u251C\u2500\u2500 ${path26.basename(structure.api)}/ # API layer (controllers)`);
66965
+ lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.api)}/ # API layer (controllers)`);
66456
66966
  }
66457
66967
  if (structure.web) {
66458
66968
  lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
@@ -66465,8 +66975,8 @@ async function getProjectInfoResource(config2) {
66465
66975
  lines.push("| Project | Path |");
66466
66976
  lines.push("|---------|------|");
66467
66977
  for (const csproj of projectInfo.csprojFiles) {
66468
- const name = path26.basename(csproj, ".csproj");
66469
- const relativePath = path26.relative(projectPath, csproj);
66978
+ const name = path27.basename(csproj, ".csproj");
66979
+ const relativePath = path27.relative(projectPath, csproj);
66470
66980
  lines.push(`| ${name} | \`${relativePath}\` |`);
66471
66981
  }
66472
66982
  lines.push("");
@@ -66476,10 +66986,10 @@ async function getProjectInfoResource(config2) {
66476
66986
  cwd: structure.migrations,
66477
66987
  ignore: ["*.Designer.cs"]
66478
66988
  });
66479
- const migrations = migrationFiles.map((f) => path26.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
66989
+ const migrations = migrationFiles.map((f) => path27.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
66480
66990
  lines.push("## EF Core Migrations");
66481
66991
  lines.push("");
66482
- lines.push(`**Location**: \`${path26.relative(projectPath, structure.migrations)}\``);
66992
+ lines.push(`**Location**: \`${path27.relative(projectPath, structure.migrations)}\``);
66483
66993
  lines.push(`**Total Migrations**: ${migrations.length}`);
66484
66994
  lines.push("");
66485
66995
  if (migrations.length > 0) {
@@ -66514,11 +67024,11 @@ async function getProjectInfoResource(config2) {
66514
67024
  lines.push("dotnet build");
66515
67025
  lines.push("");
66516
67026
  lines.push("# Run API");
66517
- lines.push(`cd ${structure.api ? path26.relative(projectPath, structure.api) : "src/Api"}`);
67027
+ lines.push(`cd ${structure.api ? path27.relative(projectPath, structure.api) : "src/Api"}`);
66518
67028
  lines.push("dotnet run");
66519
67029
  lines.push("");
66520
67030
  lines.push("# Run frontend");
66521
- lines.push(`cd ${structure.web ? path26.relative(projectPath, structure.web) : "web"}`);
67031
+ lines.push(`cd ${structure.web ? path27.relative(projectPath, structure.web) : "web"}`);
66522
67032
  lines.push("npm run dev");
66523
67033
  lines.push("");
66524
67034
  lines.push("# Create migration");
@@ -66556,7 +67066,7 @@ var init_project_info = __esm({
66556
67066
  });
66557
67067
 
66558
67068
  // src/mcp/resources/api-endpoints.ts
66559
- import path27 from "path";
67069
+ import path28 from "path";
66560
67070
  async function getApiEndpointsResource(config2, endpointFilter) {
66561
67071
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
66562
67072
  if (!structure.api) {
@@ -66575,7 +67085,7 @@ async function getApiEndpointsResource(config2, endpointFilter) {
66575
67085
  }
66576
67086
  async function parseController(filePath, _rootPath) {
66577
67087
  const content = await readText(filePath);
66578
- const fileName = path27.basename(filePath, ".cs");
67088
+ const fileName = path28.basename(filePath, ".cs");
66579
67089
  const controllerName = fileName.replace("Controller", "");
66580
67090
  const endpoints = [];
66581
67091
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
@@ -66737,7 +67247,7 @@ var init_api_endpoints = __esm({
66737
67247
  });
66738
67248
 
66739
67249
  // src/mcp/resources/db-schema.ts
66740
- import path28 from "path";
67250
+ import path29 from "path";
66741
67251
  async function getDbSchemaResource(config2, tableFilter) {
66742
67252
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
66743
67253
  if (!structure.domain && !structure.infrastructure) {
@@ -66821,7 +67331,7 @@ async function parseEntity2(filePath, rootPath, _config) {
66821
67331
  tableName,
66822
67332
  properties,
66823
67333
  relationships,
66824
- file: path28.relative(rootPath, filePath)
67334
+ file: path29.relative(rootPath, filePath)
66825
67335
  };
66826
67336
  }
66827
67337
  async function enrichFromConfigurations(entities, infrastructurePath, _config) {
@@ -66982,7 +67492,7 @@ var init_db_schema = __esm({
66982
67492
  });
66983
67493
 
66984
67494
  // src/mcp/resources/entities.ts
66985
- import path29 from "path";
67495
+ import path30 from "path";
66986
67496
  async function getEntitiesResource(config2, entityFilter) {
66987
67497
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
66988
67498
  if (!structure.domain) {
@@ -67036,7 +67546,7 @@ async function parseEntitySummary(filePath, rootPath, config2) {
67036
67546
  hasSoftDelete,
67037
67547
  hasRowVersion,
67038
67548
  file: filePath,
67039
- relativePath: path29.relative(rootPath, filePath)
67549
+ relativePath: path30.relative(rootPath, filePath)
67040
67550
  };
67041
67551
  }
67042
67552
  function inferTableInfo(entityName, config2) {
@@ -67094,11 +67604,11 @@ function inferTableInfo(entityName, config2) {
67094
67604
  if (!validPrefixes.includes(tablePrefix)) {
67095
67605
  tablePrefix = "ref_";
67096
67606
  }
67097
- const tableName = `${tablePrefix}${pluralize(entityName)}`;
67607
+ const tableName = `${tablePrefix}${pluralize2(entityName)}`;
67098
67608
  const schema = config2.conventions.schemas.platform;
67099
67609
  return { tableName, tablePrefix, schema };
67100
67610
  }
67101
- function pluralize(name) {
67611
+ function pluralize2(name) {
67102
67612
  if (name.endsWith("y") && !/[aeiou]y$/i.test(name)) {
67103
67613
  return name.slice(0, -1) + "ies";
67104
67614
  }
@@ -67254,6 +67764,8 @@ async function createServer() {
67254
67764
  // Frontend Extension Tools
67255
67765
  scaffoldFrontendExtensionTool,
67256
67766
  analyzeExtensionPointsTool,
67767
+ // Data Export Tool
67768
+ scaffoldDataExportTool,
67257
67769
  // Security & Code Quality Tools
67258
67770
  validateSecurityTool,
67259
67771
  analyzeCodeQualityTool,
@@ -67323,6 +67835,10 @@ async function createServer() {
67323
67835
  case "analyze_extension_points":
67324
67836
  result = await handleAnalyzeExtensionPoints(args ?? {}, config2);
67325
67837
  break;
67838
+ // Data Export Tool
67839
+ case "scaffold_data_export":
67840
+ result = await handleScaffoldDataExport(args ?? {}, config2);
67841
+ break;
67326
67842
  // Security & Code Quality Tools
67327
67843
  case "validate_security":
67328
67844
  result = await handleValidateSecurity(args ?? {}, config2);
@@ -67451,6 +67967,7 @@ var init_server3 = __esm({
67451
67967
  init_scaffold_frontend_tests();
67452
67968
  init_validate_security();
67453
67969
  init_analyze_code_quality();
67970
+ init_scaffold_data_export();
67454
67971
  init_analyze_hierarchy_patterns();
67455
67972
  init_review_code();
67456
67973
  init_conventions();