@atlashub/smartstack-cli 3.4.1 → 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
@@ -116146,6 +116146,56 @@ function checkPrerequisites() {
116146
116146
  return { dotnet: false, dotnetVersion: null };
116147
116147
  }
116148
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
+ }
116149
116199
  function validateCSharpNamespace(name) {
116150
116200
  if (!name || name.trim().length === 0) {
116151
116201
  return { valid: false, error: "Project name cannot be empty" };
@@ -116700,6 +116750,10 @@ EndGlobal
116700
116750
  SystemTenantName: config.multiTenant.systemTenantName,
116701
116751
  AutoAssignUsersToSystemTenant: config.multiTenant.autoAssignUsersToSystemTenant
116702
116752
  };
116753
+ appSettings.Email.Provider = config.email.provider;
116754
+ if (config.email.provider === "Disabled") {
116755
+ appSettings.Email.Enabled = false;
116756
+ }
116703
116757
  const appSettingsRelPath = `src/${projectName}.Api/appsettings.json`;
116704
116758
  const appSettingsResult = await safeWriteFile(
116705
116759
  (0, import_path6.join)(apiDir2, "appsettings.json"),
@@ -116718,7 +116772,7 @@ EndGlobal
116718
116772
  }
116719
116773
  },
116720
116774
  Email: {
116721
- Provider: "Development"
116775
+ Provider: config.email.provider
116722
116776
  }
116723
116777
  };
116724
116778
  const appSettingsDevRelPath = `src/${projectName}.Api/appsettings.Development.json`;
@@ -117672,6 +117726,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117672
117726
  systemTenantSlug: "default",
117673
117727
  systemTenantName: "Default Workspace",
117674
117728
  autoAssignUsersToSystemTenant: true
117729
+ },
117730
+ email: {
117731
+ provider: "Development",
117732
+ setupMailPit: false
117675
117733
  }
117676
117734
  };
117677
117735
  } else {
@@ -117711,6 +117769,27 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117711
117769
  when: (answers2) => answers2.multiTenantEnabled
117712
117770
  }
117713
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
+ ]);
117714
117793
  config = {
117715
117794
  name: finalProjectName,
117716
117795
  nameLower: finalProjectName.toLowerCase(),
@@ -117724,6 +117803,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117724
117803
  systemTenantSlug: answers.systemTenantSlug || "default",
117725
117804
  systemTenantName: answers.systemTenantName || "Default Workspace",
117726
117805
  autoAssignUsersToSystemTenant: true
117806
+ },
117807
+ email: {
117808
+ provider: emailAnswers.emailProvider || "Development",
117809
+ setupMailPit: emailAnswers.setupMailPit ?? false
117727
117810
  }
117728
117811
  };
117729
117812
  }
@@ -117741,6 +117824,8 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117741
117824
  logger.info(` B2C (User Tenants): ${config.multiTenant.enableB2C ? source_default.green("Enabled") : source_default.gray("Disabled")}`);
117742
117825
  logger.info(` System Tenant: ${source_default.cyan(config.multiTenant.systemTenantSlug)} (${config.multiTenant.systemTenantName})`);
117743
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}`);
117744
117829
  console.log();
117745
117830
  try {
117746
117831
  let cliVersion = "0.0.0";
@@ -117782,6 +117867,10 @@ var initCommand = new Command("init").description("Initialize a new SmartStack p
117782
117867
  logger.info("Would create .ralph/ directory with configuration");
117783
117868
  }
117784
117869
  });
117870
+ if (config.email.setupMailPit && !dryRun) {
117871
+ console.log();
117872
+ await setupMailPitContainer();
117873
+ }
117785
117874
  if (!dryRun) {
117786
117875
  await saveInitState(finalProjectDir, state);
117787
117876
  }
@@ -126219,9 +126308,11 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126219
126308
  logger.error(`No connection string found in ${(0, import_path10.basename)(selectedFile)}`);
126220
126309
  process.exit(1);
126221
126310
  }
126222
- logger.info(`Using: ${source_default.cyan((0, import_path10.basename)(selectedFile))}`);
126223
126311
  }
126224
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)}`);
126225
126316
  if (!options.force) {
126226
126317
  const { confirm } = await lib_default.prompt([
126227
126318
  {
@@ -126237,7 +126328,6 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126237
126328
  }
126238
126329
  }
126239
126330
  const spinner = logger.spinner("Connecting to database...");
126240
- const connInfo = parseConnectionString(connectionString);
126241
126331
  const resetViaSqlCmd = async (sqlAuth) => {
126242
126332
  const authLabel = sqlAuth ? "SQL Server Authentication" : "Windows Authentication";
126243
126333
  spinner.text = `Using ${authLabel} (sqlcmd)...`;
@@ -126265,6 +126355,8 @@ adminCommand.command("reset").description("Reset the localAdmin account password
126265
126355
  console.log(source_default.green.bold(" LOCAL ADMINISTRATOR PASSWORD RESET"));
126266
126356
  console.log(source_default.green("\u2550".repeat(60)));
126267
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));
126268
126360
  console.log(source_default.white(" Email: "), source_default.cyan(email));
126269
126361
  console.log(source_default.white(" Password: "), source_default.yellow.bold(password));
126270
126362
  console.log();
@@ -126809,6 +126901,42 @@ function validateForPrdExtraction(feature) {
126809
126901
  }
126810
126902
  return errors;
126811
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
+ }
126812
126940
 
126813
126941
  // src/commands/derive-prd.ts
126814
126942
  function readSmartStackNamespace() {
@@ -126897,12 +127025,26 @@ Processing module: ${source_default.bold(moduleName)}`));
126897
127025
  }
126898
127026
  await import_fs_extra9.default.writeJson(outputPath, prd, { spaces: 2 });
126899
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
+ }
126900
127040
  const relOutput = (0, import_path11.relative)(process.cwd(), outputPath);
126901
127041
  console.log(source_default.green(` Generated: ${relOutput}`));
126902
127042
  console.log(source_default.gray(` UCs: ${prd.requirements.useCases.length} | FRs: ${prd.requirements.functionalRequirements.length} | BRs: ${prd.businessRules.length}`));
126903
127043
  console.log(source_default.gray(` Endpoints: ${prd.architecture.apiEndpoints.length} | Sections: ${prd.architecture.sections.length}`));
126904
- const fileCount = Object.values(prd.implementation.filesToCreate).reduce((sum, files) => sum + (files?.length ?? 0), 0);
126905
- 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}`));
126906
127048
  }
126907
127049
  console.log();
126908
127050
  if (totalErrors > 0) {