@atlashub/smartstack-cli 3.4.0 → 3.5.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 CHANGED
@@ -44464,16 +44464,16 @@ var require_chainedTokenCredential = __commonJS({
44464
44464
  // node_modules/uuid/dist/esm-node/rng.js
44465
44465
  function rng() {
44466
44466
  if (poolPtr > rnds8Pool.length - 16) {
44467
- import_crypto2.default.randomFillSync(rnds8Pool);
44467
+ import_crypto4.default.randomFillSync(rnds8Pool);
44468
44468
  poolPtr = 0;
44469
44469
  }
44470
44470
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
44471
44471
  }
44472
- var import_crypto2, rnds8Pool, poolPtr;
44472
+ var import_crypto4, rnds8Pool, poolPtr;
44473
44473
  var init_rng = __esm({
44474
44474
  "node_modules/uuid/dist/esm-node/rng.js"() {
44475
44475
  "use strict";
44476
- import_crypto2 = __toESM(require("crypto"));
44476
+ import_crypto4 = __toESM(require("crypto"));
44477
44477
  rnds8Pool = new Uint8Array(256);
44478
44478
  poolPtr = rnds8Pool.length;
44479
44479
  }
@@ -44678,13 +44678,13 @@ function md5(bytes) {
44678
44678
  } else if (typeof bytes === "string") {
44679
44679
  bytes = Buffer.from(bytes, "utf8");
44680
44680
  }
44681
- return import_crypto3.default.createHash("md5").update(bytes).digest();
44681
+ return import_crypto5.default.createHash("md5").update(bytes).digest();
44682
44682
  }
44683
- var import_crypto3, md5_default;
44683
+ var import_crypto5, md5_default;
44684
44684
  var init_md5 = __esm({
44685
44685
  "node_modules/uuid/dist/esm-node/md5.js"() {
44686
44686
  "use strict";
44687
- import_crypto3 = __toESM(require("crypto"));
44687
+ import_crypto5 = __toESM(require("crypto"));
44688
44688
  md5_default = md5;
44689
44689
  }
44690
44690
  });
@@ -44733,13 +44733,13 @@ function sha1(bytes) {
44733
44733
  } else if (typeof bytes === "string") {
44734
44734
  bytes = Buffer.from(bytes, "utf8");
44735
44735
  }
44736
- return import_crypto4.default.createHash("sha1").update(bytes).digest();
44736
+ return import_crypto6.default.createHash("sha1").update(bytes).digest();
44737
44737
  }
44738
- var import_crypto4, sha1_default;
44738
+ var import_crypto6, sha1_default;
44739
44739
  var init_sha1 = __esm({
44740
44740
  "node_modules/uuid/dist/esm-node/sha1.js"() {
44741
44741
  "use strict";
44742
- import_crypto4 = __toESM(require("crypto"));
44742
+ import_crypto6 = __toESM(require("crypto"));
44743
44743
  sha1_default = sha1;
44744
44744
  }
44745
44745
  });
@@ -115955,6 +115955,7 @@ var import_fs_extra5 = __toESM(require_lib());
115955
115955
  var import_path6 = require("path");
115956
115956
  var import_os3 = require("os");
115957
115957
  var import_child_process5 = require("child_process");
115958
+ var import_crypto2 = require("crypto");
115958
115959
 
115959
115960
  // src/lib/file-tracker.ts
115960
115961
  var import_crypto = require("crypto");
@@ -116145,6 +116146,56 @@ function checkPrerequisites() {
116145
116146
  return { dotnet: false, dotnetVersion: null };
116146
116147
  }
116147
116148
  }
116149
+ function isDockerAvailable() {
116150
+ try {
116151
+ const result = (0, import_child_process5.spawnSync)("docker", ["info"], { encoding: "utf-8", shell: true, timeout: 1e4, stdio: "pipe" });
116152
+ return result.status === 0;
116153
+ } catch {
116154
+ return false;
116155
+ }
116156
+ }
116157
+ async function setupMailPitContainer() {
116158
+ const check = (0, import_child_process5.spawnSync)("docker", ["ps", "-a", "--filter", "name=^mailpit$", "--format", "{{.Status}}"], {
116159
+ encoding: "utf-8",
116160
+ shell: true,
116161
+ timeout: 1e4
116162
+ });
116163
+ if (check.stdout?.trim()) {
116164
+ const status = check.stdout.trim();
116165
+ if (status.startsWith("Up")) {
116166
+ logger.success(`MailPit is already running`);
116167
+ } else {
116168
+ logger.info("Starting existing MailPit container...");
116169
+ (0, import_child_process5.spawnSync)("docker", ["start", "mailpit"], { encoding: "utf-8", shell: true, timeout: 15e3 });
116170
+ logger.success("MailPit started");
116171
+ }
116172
+ } else {
116173
+ logger.info("Starting MailPit container...");
116174
+ const run = (0, import_child_process5.spawnSync)("docker", [
116175
+ "run",
116176
+ "-d",
116177
+ "--name",
116178
+ "mailpit",
116179
+ "--restart",
116180
+ "unless-stopped",
116181
+ "-p",
116182
+ "8025:8025",
116183
+ "-p",
116184
+ "1025:1025",
116185
+ "axllent/mailpit"
116186
+ ], { encoding: "utf-8", shell: true, timeout: 6e4 });
116187
+ if (run.status === 0) {
116188
+ logger.success("MailPit started successfully");
116189
+ } else {
116190
+ logger.warning(`Failed to start MailPit: ${run.stderr?.trim() || "unknown error"}`);
116191
+ logger.info("You can start it manually later:");
116192
+ logger.info(source_default.cyan(" docker run -d --name mailpit --restart unless-stopped -p 8025:8025 -p 1025:1025 axllent/mailpit"));
116193
+ return;
116194
+ }
116195
+ }
116196
+ logger.info(`SMTP server: ${source_default.cyan("localhost:1025")}`);
116197
+ logger.info(`Web interface: ${source_default.cyan("http://localhost:8025")}`);
116198
+ }
116148
116199
  function validateCSharpNamespace(name) {
116149
116200
  if (!name || name.trim().length === 0) {
116150
116201
  return { valid: false, error: "Project name cannot be empty" };
@@ -116688,7 +116739,8 @@ EndGlobal
116688
116739
  throw new Error(`Template not found: ${templatePath}`);
116689
116740
  }
116690
116741
  let appSettingsContent = await import_fs_extra5.default.readFile(templatePath, "utf-8");
116691
- appSettingsContent = appSettingsContent.replace(/\{\{ProjectName\}\}/g, projectName).replace(/\{\{ProjectDomain\}\}/g, `${projectName.toLowerCase()}.app`).replace(/\{\{ProjectNameLower\}\}/g, projectName.toLowerCase());
116742
+ const jwtSecret = (0, import_crypto2.randomBytes)(64).toString("base64");
116743
+ appSettingsContent = appSettingsContent.replace(/\{\{ProjectName\}\}/g, projectName).replace(/\{\{ProjectDomain\}\}/g, `${projectName.toLowerCase()}.app`).replace(/\{\{ProjectNameLower\}\}/g, projectName.toLowerCase()).replace(/\{\{JwtSecret\}\}/g, jwtSecret);
116692
116744
  const appSettings = JSON.parse(appSettingsContent);
116693
116745
  appSettings.MultiTenant = {
116694
116746
  Enabled: config.multiTenant.enabled,
@@ -116698,6 +116750,10 @@ EndGlobal
116698
116750
  SystemTenantName: config.multiTenant.systemTenantName,
116699
116751
  AutoAssignUsersToSystemTenant: config.multiTenant.autoAssignUsersToSystemTenant
116700
116752
  };
116753
+ appSettings.Email.Provider = config.email.provider;
116754
+ if (config.email.provider === "Disabled") {
116755
+ appSettings.Email.Enabled = false;
116756
+ }
116701
116757
  const appSettingsRelPath = `src/${projectName}.Api/appsettings.json`;
116702
116758
  const appSettingsResult = await safeWriteFile(
116703
116759
  (0, import_path6.join)(apiDir2, "appsettings.json"),
@@ -116716,7 +116772,7 @@ EndGlobal
116716
116772
  }
116717
116773
  },
116718
116774
  Email: {
116719
- Provider: "Development"
116775
+ Provider: config.email.provider
116720
116776
  }
116721
116777
  };
116722
116778
  const appSettingsDevRelPath = `src/${projectName}.Api/appsettings.Development.json`;
@@ -117670,6 +117726,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117670
117726
  systemTenantSlug: "default",
117671
117727
  systemTenantName: "Default Workspace",
117672
117728
  autoAssignUsersToSystemTenant: true
117729
+ },
117730
+ email: {
117731
+ provider: "Development",
117732
+ setupMailPit: false
117673
117733
  }
117674
117734
  };
117675
117735
  } else {
@@ -117709,6 +117769,27 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117709
117769
  when: (answers2) => answers2.multiTenantEnabled
117710
117770
  }
117711
117771
  ]);
117772
+ const emailAnswers = await lib_default.prompt([
117773
+ {
117774
+ type: "list",
117775
+ name: "emailProvider",
117776
+ message: "Email provider:",
117777
+ choices: [
117778
+ { name: "Development (MailPit - local SMTP testing)", value: "Development" },
117779
+ { name: "Mailgun", value: "Mailgun" },
117780
+ { name: "Azure Communication Services", value: "AzureAcs" },
117781
+ { name: "Disabled", value: "Disabled" }
117782
+ ],
117783
+ default: "Development"
117784
+ },
117785
+ {
117786
+ type: "confirm",
117787
+ name: "setupMailPit",
117788
+ message: "Start MailPit Docker container for email testing?",
117789
+ default: true,
117790
+ when: (a) => a.emailProvider === "Development" && isDockerAvailable()
117791
+ }
117792
+ ]);
117712
117793
  config = {
117713
117794
  name: finalProjectName,
117714
117795
  nameLower: finalProjectName.toLowerCase(),
@@ -117722,6 +117803,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117722
117803
  systemTenantSlug: answers.systemTenantSlug || "default",
117723
117804
  systemTenantName: answers.systemTenantName || "Default Workspace",
117724
117805
  autoAssignUsersToSystemTenant: true
117806
+ },
117807
+ email: {
117808
+ provider: emailAnswers.emailProvider || "Development",
117809
+ setupMailPit: emailAnswers.setupMailPit ?? false
117725
117810
  }
117726
117811
  };
117727
117812
  }
@@ -117739,6 +117824,8 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117739
117824
  logger.info(` B2C (User Tenants): ${config.multiTenant.enableB2C ? source_default.green("Enabled") : source_default.gray("Disabled")}`);
117740
117825
  logger.info(` System Tenant: ${source_default.cyan(config.multiTenant.systemTenantSlug)} (${config.multiTenant.systemTenantName})`);
117741
117826
  }
117827
+ const emailLabel = config.email.provider === "Development" ? `${source_default.green("Development")} (MailPit - SMTP :1025, Web :8025)` : config.email.provider === "Disabled" ? source_default.gray("Disabled") : source_default.cyan(config.email.provider);
117828
+ logger.info(`Email: ${emailLabel}`);
117742
117829
  console.log();
117743
117830
  try {
117744
117831
  let cliVersion = "0.0.0";
@@ -117780,6 +117867,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117780
117867
  logger.info("Would create .ralph/ directory with configuration");
117781
117868
  }
117782
117869
  });
117870
+ if (config.email.setupMailPit && !dryRun) {
117871
+ console.log();
117872
+ await setupMailPitContainer();
117873
+ }
117783
117874
  if (!dryRun) {
117784
117875
  await saveInitState(finalProjectDir, state);
117785
117876
  }
@@ -124559,6 +124650,7 @@ var glob = Object.assign(glob_, {
124559
124650
  glob.glob = glob;
124560
124651
 
124561
124652
  // src/lib/config-sync.ts
124653
+ var import_crypto3 = require("crypto");
124562
124654
  function addMissingKeys(target, template, prefix = "") {
124563
124655
  const added = [];
124564
124656
  for (const key of Object.keys(template)) {
@@ -124578,7 +124670,7 @@ function addMissingKeys(target, template, prefix = "") {
124578
124670
  return added;
124579
124671
  }
124580
124672
  function resolveTemplatePlaceholders(content, projectName) {
124581
- return content.replace(/\{\{ProjectName\}\}/g, projectName).replace(/\{\{ProjectDomain\}\}/g, `${projectName.toLowerCase()}.app`).replace(/\{\{ProjectNameLower\}\}/g, projectName.toLowerCase());
124673
+ return content.replace(/\{\{ProjectName\}\}/g, projectName).replace(/\{\{ProjectDomain\}\}/g, `${projectName.toLowerCase()}.app`).replace(/\{\{ProjectNameLower\}\}/g, projectName.toLowerCase()).replace(/\{\{JwtSecret\}\}/g, (0, import_crypto3.randomBytes)(64).toString("base64"));
124582
124674
  }
124583
124675
  function isPlainObject(value) {
124584
124676
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -126216,9 +126308,11 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126216
126308
  logger.error(`No connection string found in ${(0, import_path10.basename)(selectedFile)}`);
126217
126309
  process.exit(1);
126218
126310
  }
126219
- logger.info(`Using: ${source_default.cyan((0, import_path10.basename)(selectedFile))}`);
126220
126311
  }
126221
126312
  const adminEmail = options.email;
126313
+ const connInfo = parseConnectionString(connectionString);
126314
+ logger.info(`Server: ${source_default.cyan(connInfo.server)}`);
126315
+ logger.info(`Database: ${source_default.cyan(connInfo.database)}`);
126222
126316
  if (!options.force) {
126223
126317
  const { confirm } = await lib_default.prompt([
126224
126318
  {
@@ -126234,7 +126328,6 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126234
126328
  }
126235
126329
  }
126236
126330
  const spinner = logger.spinner("Connecting to database...");
126237
- const connInfo = parseConnectionString(connectionString);
126238
126331
  const resetViaSqlCmd = async (sqlAuth) => {
126239
126332
  const authLabel = sqlAuth ? "SQL Server Authentication" : "Windows Authentication";
126240
126333
  spinner.text = `Using ${authLabel} (sqlcmd)...`;
@@ -126262,6 +126355,8 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126262
126355
  console.log(source_default.green.bold(" LOCAL ADMINISTRATOR PASSWORD RESET"));
126263
126356
  console.log(source_default.green("\u2550".repeat(60)));
126264
126357
  console.log();
126358
+ console.log(source_default.white(" Server: "), source_default.cyan(connInfo.server));
126359
+ console.log(source_default.white(" Database: "), source_default.cyan(connInfo.database));
126265
126360
  console.log(source_default.white(" Email: "), source_default.cyan(email));
126266
126361
  console.log(source_default.white(" Password: "), source_default.yellow.bold(password));
126267
126362
  console.log();
@@ -126806,6 +126901,42 @@ function validateForPrdExtraction(feature) {
126806
126901
  }
126807
126902
  return errors;
126808
126903
  }
126904
+ function validatePrdCompleteness(prd, feature) {
126905
+ const warnings = [];
126906
+ const ftc = prd.implementation.filesToCreate;
126907
+ const handoffFtc = feature.handoff?.filesToCreate;
126908
+ const categories = [
126909
+ { key: "domain", label: "domain", check: () => !!feature.analysis?.entities?.length },
126910
+ { key: "application", label: "application", check: () => !!feature.analysis?.entities?.length },
126911
+ { key: "infrastructure", label: "infrastructure", check: () => !!feature.analysis?.entities?.length },
126912
+ { key: "api", label: "api", check: () => !!feature.specification?.apiEndpoints?.length },
126913
+ { key: "frontend", label: "frontend", check: () => !!(feature.specification?.uiWireframes?.length || feature.specification?.sections?.length) },
126914
+ { key: "seedData", label: "seedData", check: () => !!(feature.specification?.seedDataCore?.length || feature.specification?.seedDataBusiness?.length) },
126915
+ { key: "tests", label: "tests", check: () => !!feature.analysis?.entities?.length }
126916
+ ];
126917
+ for (const cat of categories) {
126918
+ const prdCount = ftc[cat.key]?.length ?? 0;
126919
+ const handoffCount = handoffFtc?.[cat.key]?.length ?? 0;
126920
+ if (prdCount === 0 && handoffCount > 0) {
126921
+ warnings.push(`${cat.label}: 0 files in PRD but ${handoffCount} in feature.json handoff`);
126922
+ } else if (prdCount === 0 && cat.check()) {
126923
+ warnings.push(`${cat.label}: 0 files but feature.json has relevant data`);
126924
+ }
126925
+ }
126926
+ return warnings;
126927
+ }
126928
+ function getPrdFileCounts(prd) {
126929
+ const ftc = prd.implementation.filesToCreate;
126930
+ return {
126931
+ domain: ftc.domain?.length ?? 0,
126932
+ application: ftc.application?.length ?? 0,
126933
+ infrastructure: ftc.infrastructure?.length ?? 0,
126934
+ api: ftc.api?.length ?? 0,
126935
+ frontend: ftc.frontend?.length ?? 0,
126936
+ seedData: ftc.seedData?.length ?? 0,
126937
+ tests: ftc.tests?.length ?? 0
126938
+ };
126939
+ }
126809
126940
 
126810
126941
  // src/commands/derive-prd.ts
126811
126942
  function readSmartStackNamespace() {
@@ -126894,12 +127025,26 @@ Processing module: ${source_default.bold(moduleName)}`));
126894
127025
  }
126895
127026
  await import_fs_extra9.default.writeJson(outputPath, prd, { spaces: 2 });
126896
127027
  totalGenerated++;
127028
+ const completenessWarnings = validatePrdCompleteness(prd, featureJson);
127029
+ if (completenessWarnings.length > 0) {
127030
+ console.log(source_default.yellow(` Completeness warnings:`));
127031
+ for (const w of completenessWarnings) {
127032
+ console.log(source_default.yellow(` - ${w}`));
127033
+ }
127034
+ if (options.strict) {
127035
+ console.log(source_default.red(` Skipping ${moduleName} \u2014 incomplete PRD (--strict mode)`));
127036
+ totalErrors++;
127037
+ continue;
127038
+ }
127039
+ }
126897
127040
  const relOutput = (0, import_path11.relative)(process.cwd(), outputPath);
126898
127041
  console.log(source_default.green(` Generated: ${relOutput}`));
126899
127042
  console.log(source_default.gray(` UCs: ${prd.requirements.useCases.length} | FRs: ${prd.requirements.functionalRequirements.length} | BRs: ${prd.businessRules.length}`));
126900
127043
  console.log(source_default.gray(` Endpoints: ${prd.architecture.apiEndpoints.length} | Sections: ${prd.architecture.sections.length}`));
126901
- const fileCount = Object.values(prd.implementation.filesToCreate).reduce((sum, files) => sum + (files?.length ?? 0), 0);
126902
- console.log(source_default.gray(` Files to create: ${fileCount} | BR mappings: ${prd.implementation.brToCodeMapping.length}`));
127044
+ const counts = getPrdFileCounts(prd);
127045
+ const totalFiles = Object.values(counts).reduce((s, n) => s + n, 0);
127046
+ console.log(source_default.gray(` Files to create: ${totalFiles} (domain: ${counts.domain}, app: ${counts.application}, infra: ${counts.infrastructure}, api: ${counts.api}, frontend: ${counts.frontend}, seed: ${counts.seedData}, tests: ${counts.tests})`));
127047
+ console.log(source_default.gray(` BR mappings: ${prd.implementation.brToCodeMapping.length}`));
126903
127048
  }
126904
127049
  console.log();
126905
127050
  if (totalErrors > 0) {